Merge pull request #2415 from LenAnderson/parser-followup-2
Parser followup 2
This commit is contained in:
commit
11608e0cb8
|
@ -0,0 +1,149 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="48"
|
||||||
|
height="48"
|
||||||
|
viewBox="0 0 48 48"
|
||||||
|
version="1.1"
|
||||||
|
id="svg2120"
|
||||||
|
xml:space="preserve"
|
||||||
|
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
|
||||||
|
sodipodi:docname="step-into.svg"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
||||||
|
id="namedview2122"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="11.313708"
|
||||||
|
inkscape:cx="58.910834"
|
||||||
|
inkscape:cy="25.323262"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="992"
|
||||||
|
inkscape:window-x="-8"
|
||||||
|
inkscape:window-y="-8"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="g2714" /><defs
|
||||||
|
id="defs2117"><inkscape:path-effect
|
||||||
|
effect="spiro"
|
||||||
|
id="path-effect2144"
|
||||||
|
is_visible="true"
|
||||||
|
lpeversion="1" /><inkscape:path-effect
|
||||||
|
effect="bspline"
|
||||||
|
id="path-effect2138"
|
||||||
|
is_visible="true"
|
||||||
|
lpeversion="1"
|
||||||
|
weight="33.333333"
|
||||||
|
steps="2"
|
||||||
|
helper_size="0"
|
||||||
|
apply_no_weight="true"
|
||||||
|
apply_with_weight="true"
|
||||||
|
only_selected="false" /></defs><g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer4"
|
||||||
|
inkscape:label="img"
|
||||||
|
style="display:none"><image
|
||||||
|
width="305.68866"
|
||||||
|
height="70.374367"
|
||||||
|
preserveAspectRatio="none"
|
||||||
|
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAARYAAABACAYAAADf7VgRAAAABHNCSVQICAgIfAhkiAAABdxJREFU
|
||||||
|
eJzt3T9vo0gYBvB3by+SsZRiKCI5SCkyJS6dzny3+26UpoSSFJFMOaTyUETaK3zDAQYbzGBgeH7S
|
||||||
|
SutN1kKj8cM7fxj/2u/3fwgAQKO/xr4AADAPggUAtEOwAIB2CBYA0A7BAgDaIVgAQDsECwBoh2AB
|
||||||
|
AO0QLACgHYIFALRDsACAdn+PfQFw5nkeWZZFRERSSvJ9f+Qrmg7XdSnLMorjeOxLgZYQLDBpruuS
|
||||||
|
4zj5a4TLPGAoBJNVDRXOOXHOR7wiaAvBApNUDRUF4TIPCBaYnKZQURAu04dggUm5FSoKwmXaECww
|
||||||
|
GW1DRUG4TNfDV4VUx1mtViSEICKiLMtIStn4f4qdJ8syOh6Pw14kPFxdqAghyLKs0jI8EeWvif7v
|
||||||
|
G1gtmpbBg8V1XbJtm4jKHYKoHBhCCIrjuDZkqr+HYDFLU6gEQUCe55X+3ff90p4fIoTLFA0WLI7j
|
||||||
|
kOu6rX/ftm2ybZuEEBRF0dUKBsxxLVSaIFymT/scC2OMdrtdp1Apsm2bPM8jzvlFhQPmybKs9PpW
|
||||||
|
qCi+71/cfKrvZQrXdYkxNvZldKK1YmGM0cfHR+3PhBAkpaTv728iIjqdTmTbNq1WK7Jtu3aYxBij
|
||||||
|
KIp0XuLoXNelz8/PzhWZZVnkuq5x1ZyqMDjnrUNFKVYuURQZOUTe7XZ5NR+GIaVpOvYltaItWCzL
|
||||||
|
qg0VNXdS1yDFf2OM0Xa7LQWMbdt3Vz5TVOwkQRC0DgjLsvK5BlPD5d5Jed/3yXEcI0OlOj+53W5n
|
||||||
|
Ey5ahkLFjq9IKelwOFAQBK0aIk1T8n3/ooOohp27aifZ7XathnrVtjUtbJU+wWBiqBDRRWWrwqXv
|
||||||
|
sKi40jYULRVLtaP3eTo3iiLKssy4/QlJkpSGfCpcrlUudYFNhAnKpZBSUhAEpZvQvZWLGkoXb9RS
|
||||||
|
SkqSZJD+1Lti4ZxfVBVhGPZ6zziOjfvwpGlKYRhe3IGaKpemUDkcDrMohUEPFS59KhfOOXmed/E5
|
||||||
|
tSwr/5nuCqZ3xfL6+lp63bXjq0naJVDhUpxLqgsXhAoU9alcGGM3q3/VB3WeAdSrYqmO1aSUnTs+
|
||||||
|
Yyyf0Kz+MVFT5XILQmXZ7q1cmlZpqyzL6vQ4xS29KpZqtZIkSa+LWYq6yuUahIo56irRLqr95Vrl
|
||||||
|
0nUksNlstE2EawsWKaVx8yJDahsuCBWzDLEa0xQu6/W68/vo0itYdIzJumyIMs2tcEGoQFt1/We1
|
||||||
|
WvV+j3vhzNuRNYULQsVMfTc2Nn346/qLEKLTtg2d+4FGOzbhdDrlz3aYtIv0HtVwQaiYq0+V33W1
|
||||||
|
UJ0U0LYS0fms1cOCpelQHjzNfKbCRf0dzqSUed9Ych+5ZwuClJLCMGy1MqQevdHl136//6Pt3Rqo
|
||||||
|
Z2SuMfUhMoC++u5runXSngognTe0wSuWup25dd7f3/MnoAHgTMdmyTiOKUmSh27pHzRY1Jbhtr/7
|
||||||
|
+vqKJWuA/+jcga022FWP+RzKpFaFlrK1vw5jjNbrdX4W8K1zgJdiye1S9xR734n9R7XdoMFS3Zl7
|
||||||
|
i6nb+K+pOxyLcz7IuHdO0C7necfi8GVOq4W/397e/hnqzdfrNb28vLT+fSklfX19DXU5k8M5p+12
|
||||||
|
W/uzp6enfGl+Lp1JF7TL2c/PD6VpSs/Pz7ML00G/V0h9vUdbSylxidrPPy3t7F+0S5maG5lTqBAN
|
||||||
|
HCzFPQhtLGnitsspcCaeGNcE7WKGwb8Jse3ZrkKI2aVyH13mk+oOGzcV2sUMgweLmmy7Fi5dT2ef
|
||||||
|
O3wY6qFdzPGQ5WZ1ULY6LU4dEHU8HilJkkVVKvda0vxTF2iXaXroPpYlzaFco+ae2t6hl/LhQbuY
|
||||||
|
Y/ChENTrctrekk7mQ7uYAcEykjiOW91xl3YyH9rFDAiWEd1aMRNC9P4qlTlCu8zfQ45NgOscx6HN
|
||||||
|
ZlOa1M6ybPF3ZLTLfCFYAEA7DIUAQDsECwBoh2ABAO0QLACgHYIFALRDsACAdggWANAOwQIA2iFY
|
||||||
|
AEA7BAsAaIdgAQDt/gUoDXNStc/rMQAAAABJRU5ErkJggg==
|
||||||
|
"
|
||||||
|
id="image2132"
|
||||||
|
x="-82"
|
||||||
|
y="-11.9" /></g><g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer5"
|
||||||
|
inkscape:label="dot" /><g
|
||||||
|
inkscape:label="over"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
style="display:none"><ellipse
|
||||||
|
style="fill:#000000;stroke:#000000;stroke-width:5.27982;stroke-dasharray:none"
|
||||||
|
id="path2637"
|
||||||
|
cx="24"
|
||||||
|
cy="33"
|
||||||
|
rx="4.3600898"
|
||||||
|
ry="4.3600893" /><path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none"
|
||||||
|
d="M 9,24 C 9.7590836,20.626295 11.695032,17.529367 14.395314,15.369142 17.095595,13.208917 20.541953,12 24,12 c 3.458047,0 6.904405,1.208917 9.604686,3.369142 C 36.304968,17.529367 38.240916,20.626295 39,24"
|
||||||
|
id="path2142"
|
||||||
|
inkscape:path-effect="#path-effect2144"
|
||||||
|
inkscape:original-d="m 9,24 c 4.959859,-3.824406 10.021901,-8.173595 15,-12 4.978099,-3.8264055 10.285024,8.398237 15,12"
|
||||||
|
sodipodi:nodetypes="csc" /><path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none"
|
||||||
|
d="M 26,22 H 39 V 9"
|
||||||
|
id="path2626"
|
||||||
|
sodipodi:nodetypes="ccc" /></g><g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer6"
|
||||||
|
inkscape:label="into"
|
||||||
|
style="display:inline"><ellipse
|
||||||
|
style="fill:#000000;stroke:#000000;stroke-width:5.27982;stroke-dasharray:none"
|
||||||
|
id="path2637-3"
|
||||||
|
cx="24"
|
||||||
|
cy="38.5"
|
||||||
|
rx="4.3600898"
|
||||||
|
ry="4.3600893" /><path
|
||||||
|
style="fill:#000000;stroke:#000000;stroke-width:5;stroke-dasharray:none"
|
||||||
|
d="M 24,2 V 24"
|
||||||
|
id="path2668"
|
||||||
|
sodipodi:nodetypes="cc" /><path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none"
|
||||||
|
d="M 14.807612,16.994936 24,26.187324 33.192388,16.994936"
|
||||||
|
id="path2626-8"
|
||||||
|
sodipodi:nodetypes="ccc" /></g><g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="g2714"
|
||||||
|
inkscape:label="out"
|
||||||
|
style="display:none"><ellipse
|
||||||
|
style="fill:#000000;stroke:#000000;stroke-width:5.27982;stroke-dasharray:none"
|
||||||
|
id="ellipse2708"
|
||||||
|
cx="24"
|
||||||
|
cy="38.5"
|
||||||
|
rx="4.3600898"
|
||||||
|
ry="4.3600893" /><path
|
||||||
|
style="fill:#000000;stroke:#000000;stroke-width:5;stroke-dasharray:none"
|
||||||
|
d="M 24,29.722858 V 7.7228579"
|
||||||
|
id="path2710"
|
||||||
|
sodipodi:nodetypes="cc" /><path
|
||||||
|
style="display:inline;fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none"
|
||||||
|
d="M 33.192388,14.727922 24,5.5355339 14.807612,14.727922"
|
||||||
|
id="path2712"
|
||||||
|
sodipodi:nodetypes="ccc" /></g></svg>
|
After Width: | Height: | Size: 6.3 KiB |
|
@ -0,0 +1,149 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="48"
|
||||||
|
height="48"
|
||||||
|
viewBox="0 0 48 48"
|
||||||
|
version="1.1"
|
||||||
|
id="svg2120"
|
||||||
|
xml:space="preserve"
|
||||||
|
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
|
||||||
|
sodipodi:docname="step-out.svg"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
||||||
|
id="namedview2122"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="11.313708"
|
||||||
|
inkscape:cx="58.910834"
|
||||||
|
inkscape:cy="25.323262"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="992"
|
||||||
|
inkscape:window-x="-8"
|
||||||
|
inkscape:window-y="-8"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="g2714" /><defs
|
||||||
|
id="defs2117"><inkscape:path-effect
|
||||||
|
effect="spiro"
|
||||||
|
id="path-effect2144"
|
||||||
|
is_visible="true"
|
||||||
|
lpeversion="1" /><inkscape:path-effect
|
||||||
|
effect="bspline"
|
||||||
|
id="path-effect2138"
|
||||||
|
is_visible="true"
|
||||||
|
lpeversion="1"
|
||||||
|
weight="33.333333"
|
||||||
|
steps="2"
|
||||||
|
helper_size="0"
|
||||||
|
apply_no_weight="true"
|
||||||
|
apply_with_weight="true"
|
||||||
|
only_selected="false" /></defs><g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer4"
|
||||||
|
inkscape:label="img"
|
||||||
|
style="display:none"><image
|
||||||
|
width="305.68866"
|
||||||
|
height="70.374367"
|
||||||
|
preserveAspectRatio="none"
|
||||||
|
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAARYAAABACAYAAADf7VgRAAAABHNCSVQICAgIfAhkiAAABdxJREFU
|
||||||
|
eJzt3T9vo0gYBvB3by+SsZRiKCI5SCkyJS6dzny3+26UpoSSFJFMOaTyUETaK3zDAQYbzGBgeH7S
|
||||||
|
SutN1kKj8cM7fxj/2u/3fwgAQKO/xr4AADAPggUAtEOwAIB2CBYA0A7BAgDaIVgAQDsECwBoh2AB
|
||||||
|
AO0QLACgHYIFALRDsACAdn+PfQFw5nkeWZZFRERSSvJ9f+Qrmg7XdSnLMorjeOxLgZYQLDBpruuS
|
||||||
|
4zj5a4TLPGAoBJNVDRXOOXHOR7wiaAvBApNUDRUF4TIPCBaYnKZQURAu04dggUm5FSoKwmXaECww
|
||||||
|
GW1DRUG4TNfDV4VUx1mtViSEICKiLMtIStn4f4qdJ8syOh6Pw14kPFxdqAghyLKs0jI8EeWvif7v
|
||||||
|
G1gtmpbBg8V1XbJtm4jKHYKoHBhCCIrjuDZkqr+HYDFLU6gEQUCe55X+3ff90p4fIoTLFA0WLI7j
|
||||||
|
kOu6rX/ftm2ybZuEEBRF0dUKBsxxLVSaIFymT/scC2OMdrtdp1Apsm2bPM8jzvlFhQPmybKs9PpW
|
||||||
|
qCi+71/cfKrvZQrXdYkxNvZldKK1YmGM0cfHR+3PhBAkpaTv728iIjqdTmTbNq1WK7Jtu3aYxBij
|
||||||
|
KIp0XuLoXNelz8/PzhWZZVnkuq5x1ZyqMDjnrUNFKVYuURQZOUTe7XZ5NR+GIaVpOvYltaItWCzL
|
||||||
|
qg0VNXdS1yDFf2OM0Xa7LQWMbdt3Vz5TVOwkQRC0DgjLsvK5BlPD5d5Jed/3yXEcI0OlOj+53W5n
|
||||||
|
Ey5ahkLFjq9IKelwOFAQBK0aIk1T8n3/ooOohp27aifZ7XathnrVtjUtbJU+wWBiqBDRRWWrwqXv
|
||||||
|
sKi40jYULRVLtaP3eTo3iiLKssy4/QlJkpSGfCpcrlUudYFNhAnKpZBSUhAEpZvQvZWLGkoXb9RS
|
||||||
|
SkqSZJD+1Lti4ZxfVBVhGPZ6zziOjfvwpGlKYRhe3IGaKpemUDkcDrMohUEPFS59KhfOOXmed/E5
|
||||||
|
tSwr/5nuCqZ3xfL6+lp63bXjq0naJVDhUpxLqgsXhAoU9alcGGM3q3/VB3WeAdSrYqmO1aSUnTs+
|
||||||
|
Yyyf0Kz+MVFT5XILQmXZ7q1cmlZpqyzL6vQ4xS29KpZqtZIkSa+LWYq6yuUahIo56irRLqr95Vrl
|
||||||
|
0nUksNlstE2EawsWKaVx8yJDahsuCBWzDLEa0xQu6/W68/vo0itYdIzJumyIMs2tcEGoQFt1/We1
|
||||||
|
WvV+j3vhzNuRNYULQsVMfTc2Nn346/qLEKLTtg2d+4FGOzbhdDrlz3aYtIv0HtVwQaiYq0+V33W1
|
||||||
|
UJ0U0LYS0fms1cOCpelQHjzNfKbCRf0dzqSUed9Ych+5ZwuClJLCMGy1MqQevdHl136//6Pt3Rqo
|
||||||
|
Z2SuMfUhMoC++u5runXSngognTe0wSuWup25dd7f3/MnoAHgTMdmyTiOKUmSh27pHzRY1Jbhtr/7
|
||||||
|
+vqKJWuA/+jcga022FWP+RzKpFaFlrK1vw5jjNbrdX4W8K1zgJdiye1S9xR734n9R7XdoMFS3Zl7
|
||||||
|
i6nb+K+pOxyLcz7IuHdO0C7necfi8GVOq4W/397e/hnqzdfrNb28vLT+fSklfX19DXU5k8M5p+12
|
||||||
|
W/uzp6enfGl+Lp1JF7TL2c/PD6VpSs/Pz7ML00G/V0h9vUdbSylxidrPPy3t7F+0S5maG5lTqBAN
|
||||||
|
HCzFPQhtLGnitsspcCaeGNcE7WKGwb8Jse3ZrkKI2aVyH13mk+oOGzcV2sUMgweLmmy7Fi5dT2ef
|
||||||
|
O3wY6qFdzPGQ5WZ1ULY6LU4dEHU8HilJkkVVKvda0vxTF2iXaXroPpYlzaFco+ae2t6hl/LhQbuY
|
||||||
|
Y/ChENTrctrekk7mQ7uYAcEykjiOW91xl3YyH9rFDAiWEd1aMRNC9P4qlTlCu8zfQ45NgOscx6HN
|
||||||
|
ZlOa1M6ybPF3ZLTLfCFYAEA7DIUAQDsECwBoh2ABAO0QLACgHYIFALRDsACAdggWANAOwQIA2iFY
|
||||||
|
AEA7BAsAaIdgAQDt/gUoDXNStc/rMQAAAABJRU5ErkJggg==
|
||||||
|
"
|
||||||
|
id="image2132"
|
||||||
|
x="-82"
|
||||||
|
y="-11.9" /></g><g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer5"
|
||||||
|
inkscape:label="dot" /><g
|
||||||
|
inkscape:label="over"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
style="display:none"><ellipse
|
||||||
|
style="fill:#000000;stroke:#000000;stroke-width:5.27982;stroke-dasharray:none"
|
||||||
|
id="path2637"
|
||||||
|
cx="24"
|
||||||
|
cy="33"
|
||||||
|
rx="4.3600898"
|
||||||
|
ry="4.3600893" /><path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none"
|
||||||
|
d="M 9,24 C 9.7590836,20.626295 11.695032,17.529367 14.395314,15.369142 17.095595,13.208917 20.541953,12 24,12 c 3.458047,0 6.904405,1.208917 9.604686,3.369142 C 36.304968,17.529367 38.240916,20.626295 39,24"
|
||||||
|
id="path2142"
|
||||||
|
inkscape:path-effect="#path-effect2144"
|
||||||
|
inkscape:original-d="m 9,24 c 4.959859,-3.824406 10.021901,-8.173595 15,-12 4.978099,-3.8264055 10.285024,8.398237 15,12"
|
||||||
|
sodipodi:nodetypes="csc" /><path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none"
|
||||||
|
d="M 26,22 H 39 V 9"
|
||||||
|
id="path2626"
|
||||||
|
sodipodi:nodetypes="ccc" /></g><g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer6"
|
||||||
|
inkscape:label="into"
|
||||||
|
style="display:none"><ellipse
|
||||||
|
style="fill:#000000;stroke:#000000;stroke-width:5.27982;stroke-dasharray:none"
|
||||||
|
id="path2637-3"
|
||||||
|
cx="24"
|
||||||
|
cy="38.5"
|
||||||
|
rx="4.3600898"
|
||||||
|
ry="4.3600893" /><path
|
||||||
|
style="fill:#000000;stroke:#000000;stroke-width:5;stroke-dasharray:none"
|
||||||
|
d="M 24,2 V 24"
|
||||||
|
id="path2668"
|
||||||
|
sodipodi:nodetypes="cc" /><path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none"
|
||||||
|
d="M 14.807612,16.994936 24,26.187324 33.192388,16.994936"
|
||||||
|
id="path2626-8"
|
||||||
|
sodipodi:nodetypes="ccc" /></g><g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="g2714"
|
||||||
|
inkscape:label="out"
|
||||||
|
style="display:inline"><ellipse
|
||||||
|
style="fill:#000000;stroke:#000000;stroke-width:5.27982;stroke-dasharray:none"
|
||||||
|
id="ellipse2708"
|
||||||
|
cx="24"
|
||||||
|
cy="38.5"
|
||||||
|
rx="4.3600898"
|
||||||
|
ry="4.3600893" /><path
|
||||||
|
style="fill:#000000;stroke:#000000;stroke-width:5;stroke-dasharray:none"
|
||||||
|
d="M 24,29.722858 V 7.7228579"
|
||||||
|
id="path2710"
|
||||||
|
sodipodi:nodetypes="cc" /><path
|
||||||
|
style="display:inline;fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none"
|
||||||
|
d="M 33.192388,14.727922 24,5.5355339 14.807612,14.727922"
|
||||||
|
id="path2712"
|
||||||
|
sodipodi:nodetypes="ccc" /></g></svg>
|
After Width: | Height: | Size: 6.3 KiB |
|
@ -0,0 +1,149 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="48"
|
||||||
|
height="48"
|
||||||
|
viewBox="0 0 48 48"
|
||||||
|
version="1.1"
|
||||||
|
id="svg2120"
|
||||||
|
xml:space="preserve"
|
||||||
|
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
|
||||||
|
sodipodi:docname="step-over.svg"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
||||||
|
id="namedview2122"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="11.313708"
|
||||||
|
inkscape:cx="58.910834"
|
||||||
|
inkscape:cy="25.323262"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="992"
|
||||||
|
inkscape:window-x="-8"
|
||||||
|
inkscape:window-y="-8"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="g2714" /><defs
|
||||||
|
id="defs2117"><inkscape:path-effect
|
||||||
|
effect="spiro"
|
||||||
|
id="path-effect2144"
|
||||||
|
is_visible="true"
|
||||||
|
lpeversion="1" /><inkscape:path-effect
|
||||||
|
effect="bspline"
|
||||||
|
id="path-effect2138"
|
||||||
|
is_visible="true"
|
||||||
|
lpeversion="1"
|
||||||
|
weight="33.333333"
|
||||||
|
steps="2"
|
||||||
|
helper_size="0"
|
||||||
|
apply_no_weight="true"
|
||||||
|
apply_with_weight="true"
|
||||||
|
only_selected="false" /></defs><g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer4"
|
||||||
|
inkscape:label="img"
|
||||||
|
style="display:none"><image
|
||||||
|
width="305.68866"
|
||||||
|
height="70.374367"
|
||||||
|
preserveAspectRatio="none"
|
||||||
|
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAARYAAABACAYAAADf7VgRAAAABHNCSVQICAgIfAhkiAAABdxJREFU
|
||||||
|
eJzt3T9vo0gYBvB3by+SsZRiKCI5SCkyJS6dzny3+26UpoSSFJFMOaTyUETaK3zDAQYbzGBgeH7S
|
||||||
|
SutN1kKj8cM7fxj/2u/3fwgAQKO/xr4AADAPggUAtEOwAIB2CBYA0A7BAgDaIVgAQDsECwBoh2AB
|
||||||
|
AO0QLACgHYIFALRDsACAdn+PfQFw5nkeWZZFRERSSvJ9f+Qrmg7XdSnLMorjeOxLgZYQLDBpruuS
|
||||||
|
4zj5a4TLPGAoBJNVDRXOOXHOR7wiaAvBApNUDRUF4TIPCBaYnKZQURAu04dggUm5FSoKwmXaECww
|
||||||
|
GW1DRUG4TNfDV4VUx1mtViSEICKiLMtIStn4f4qdJ8syOh6Pw14kPFxdqAghyLKs0jI8EeWvif7v
|
||||||
|
G1gtmpbBg8V1XbJtm4jKHYKoHBhCCIrjuDZkqr+HYDFLU6gEQUCe55X+3ff90p4fIoTLFA0WLI7j
|
||||||
|
kOu6rX/ftm2ybZuEEBRF0dUKBsxxLVSaIFymT/scC2OMdrtdp1Apsm2bPM8jzvlFhQPmybKs9PpW
|
||||||
|
qCi+71/cfKrvZQrXdYkxNvZldKK1YmGM0cfHR+3PhBAkpaTv728iIjqdTmTbNq1WK7Jtu3aYxBij
|
||||||
|
KIp0XuLoXNelz8/PzhWZZVnkuq5x1ZyqMDjnrUNFKVYuURQZOUTe7XZ5NR+GIaVpOvYltaItWCzL
|
||||||
|
qg0VNXdS1yDFf2OM0Xa7LQWMbdt3Vz5TVOwkQRC0DgjLsvK5BlPD5d5Jed/3yXEcI0OlOj+53W5n
|
||||||
|
Ey5ahkLFjq9IKelwOFAQBK0aIk1T8n3/ooOohp27aifZ7XathnrVtjUtbJU+wWBiqBDRRWWrwqXv
|
||||||
|
sKi40jYULRVLtaP3eTo3iiLKssy4/QlJkpSGfCpcrlUudYFNhAnKpZBSUhAEpZvQvZWLGkoXb9RS
|
||||||
|
SkqSZJD+1Lti4ZxfVBVhGPZ6zziOjfvwpGlKYRhe3IGaKpemUDkcDrMohUEPFS59KhfOOXmed/E5
|
||||||
|
tSwr/5nuCqZ3xfL6+lp63bXjq0naJVDhUpxLqgsXhAoU9alcGGM3q3/VB3WeAdSrYqmO1aSUnTs+
|
||||||
|
Yyyf0Kz+MVFT5XILQmXZ7q1cmlZpqyzL6vQ4xS29KpZqtZIkSa+LWYq6yuUahIo56irRLqr95Vrl
|
||||||
|
0nUksNlstE2EawsWKaVx8yJDahsuCBWzDLEa0xQu6/W68/vo0itYdIzJumyIMs2tcEGoQFt1/We1
|
||||||
|
WvV+j3vhzNuRNYULQsVMfTc2Nn346/qLEKLTtg2d+4FGOzbhdDrlz3aYtIv0HtVwQaiYq0+V33W1
|
||||||
|
UJ0U0LYS0fms1cOCpelQHjzNfKbCRf0dzqSUed9Ych+5ZwuClJLCMGy1MqQevdHl136//6Pt3Rqo
|
||||||
|
Z2SuMfUhMoC++u5runXSngognTe0wSuWup25dd7f3/MnoAHgTMdmyTiOKUmSh27pHzRY1Jbhtr/7
|
||||||
|
+vqKJWuA/+jcga022FWP+RzKpFaFlrK1vw5jjNbrdX4W8K1zgJdiye1S9xR734n9R7XdoMFS3Zl7
|
||||||
|
i6nb+K+pOxyLcz7IuHdO0C7necfi8GVOq4W/397e/hnqzdfrNb28vLT+fSklfX19DXU5k8M5p+12
|
||||||
|
W/uzp6enfGl+Lp1JF7TL2c/PD6VpSs/Pz7ML00G/V0h9vUdbSylxidrPPy3t7F+0S5maG5lTqBAN
|
||||||
|
HCzFPQhtLGnitsspcCaeGNcE7WKGwb8Jse3ZrkKI2aVyH13mk+oOGzcV2sUMgweLmmy7Fi5dT2ef
|
||||||
|
O3wY6qFdzPGQ5WZ1ULY6LU4dEHU8HilJkkVVKvda0vxTF2iXaXroPpYlzaFco+ae2t6hl/LhQbuY
|
||||||
|
Y/ChENTrctrekk7mQ7uYAcEykjiOW91xl3YyH9rFDAiWEd1aMRNC9P4qlTlCu8zfQ45NgOscx6HN
|
||||||
|
ZlOa1M6ybPF3ZLTLfCFYAEA7DIUAQDsECwBoh2ABAO0QLACgHYIFALRDsACAdggWANAOwQIA2iFY
|
||||||
|
AEA7BAsAaIdgAQDt/gUoDXNStc/rMQAAAABJRU5ErkJggg==
|
||||||
|
"
|
||||||
|
id="image2132"
|
||||||
|
x="-82"
|
||||||
|
y="-11.9" /></g><g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer5"
|
||||||
|
inkscape:label="dot" /><g
|
||||||
|
inkscape:label="over"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
style="display:inline"><ellipse
|
||||||
|
style="fill:#000000;stroke:#000000;stroke-width:5.27982;stroke-dasharray:none"
|
||||||
|
id="path2637"
|
||||||
|
cx="24"
|
||||||
|
cy="33"
|
||||||
|
rx="4.3600898"
|
||||||
|
ry="4.3600893" /><path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none"
|
||||||
|
d="M 9,24 C 9.7590836,20.626295 11.695032,17.529367 14.395314,15.369142 17.095595,13.208917 20.541953,12 24,12 c 3.458047,0 6.904405,1.208917 9.604686,3.369142 C 36.304968,17.529367 38.240916,20.626295 39,24"
|
||||||
|
id="path2142"
|
||||||
|
inkscape:path-effect="#path-effect2144"
|
||||||
|
inkscape:original-d="m 9,24 c 4.959859,-3.824406 10.021901,-8.173595 15,-12 4.978099,-3.8264055 10.285024,8.398237 15,12"
|
||||||
|
sodipodi:nodetypes="csc" /><path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none"
|
||||||
|
d="M 26,22 H 39 V 9"
|
||||||
|
id="path2626"
|
||||||
|
sodipodi:nodetypes="ccc" /></g><g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer6"
|
||||||
|
inkscape:label="into"
|
||||||
|
style="display:none"><ellipse
|
||||||
|
style="fill:#000000;stroke:#000000;stroke-width:5.27982;stroke-dasharray:none"
|
||||||
|
id="path2637-3"
|
||||||
|
cx="24"
|
||||||
|
cy="38.5"
|
||||||
|
rx="4.3600898"
|
||||||
|
ry="4.3600893" /><path
|
||||||
|
style="fill:#000000;stroke:#000000;stroke-width:5;stroke-dasharray:none"
|
||||||
|
d="M 24,2 V 24"
|
||||||
|
id="path2668"
|
||||||
|
sodipodi:nodetypes="cc" /><path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none"
|
||||||
|
d="M 14.807612,16.994936 24,26.187324 33.192388,16.994936"
|
||||||
|
id="path2626-8"
|
||||||
|
sodipodi:nodetypes="ccc" /></g><g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="g2714"
|
||||||
|
inkscape:label="out"
|
||||||
|
style="display:none"><ellipse
|
||||||
|
style="fill:#000000;stroke:#000000;stroke-width:5.27982;stroke-dasharray:none"
|
||||||
|
id="ellipse2708"
|
||||||
|
cx="24"
|
||||||
|
cy="38.5"
|
||||||
|
rx="4.3600898"
|
||||||
|
ry="4.3600893" /><path
|
||||||
|
style="fill:#000000;stroke:#000000;stroke-width:5;stroke-dasharray:none"
|
||||||
|
d="M 24,29.722858 V 7.7228579"
|
||||||
|
id="path2710"
|
||||||
|
sodipodi:nodetypes="cc" /><path
|
||||||
|
style="display:inline;fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none"
|
||||||
|
d="M 33.192388,14.727922 24,5.5355339 14.807612,14.727922"
|
||||||
|
id="path2712"
|
||||||
|
sodipodi:nodetypes="ccc" /></g></svg>
|
After Width: | Height: | Size: 6.3 KiB |
|
@ -0,0 +1,218 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="48"
|
||||||
|
height="48"
|
||||||
|
viewBox="0 0 48 48"
|
||||||
|
version="1.1"
|
||||||
|
id="svg2120"
|
||||||
|
xml:space="preserve"
|
||||||
|
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
|
||||||
|
sodipodi:docname="step-resume.svg"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
||||||
|
id="namedview2122"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="11.315"
|
||||||
|
inkscape:cx="4.7282369"
|
||||||
|
inkscape:cy="24.259832"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="992"
|
||||||
|
inkscape:window-x="-8"
|
||||||
|
inkscape:window-y="-8"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer8" /><defs
|
||||||
|
id="defs2117"><inkscape:path-effect
|
||||||
|
effect="spiro"
|
||||||
|
id="path-effect2144"
|
||||||
|
is_visible="true"
|
||||||
|
lpeversion="1" /><inkscape:path-effect
|
||||||
|
effect="bspline"
|
||||||
|
id="path-effect2138"
|
||||||
|
is_visible="true"
|
||||||
|
lpeversion="1"
|
||||||
|
weight="33.333333"
|
||||||
|
steps="2"
|
||||||
|
helper_size="0"
|
||||||
|
apply_no_weight="true"
|
||||||
|
apply_with_weight="true"
|
||||||
|
only_selected="false" /></defs><g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer4"
|
||||||
|
inkscape:label="img"
|
||||||
|
style="display:none"><image
|
||||||
|
width="305.68866"
|
||||||
|
height="70.374367"
|
||||||
|
preserveAspectRatio="none"
|
||||||
|
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAARYAAABACAYAAADf7VgRAAAABHNCSVQICAgIfAhkiAAABdxJREFU
|
||||||
|
eJzt3T9vo0gYBvB3by+SsZRiKCI5SCkyJS6dzny3+26UpoSSFJFMOaTyUETaK3zDAQYbzGBgeH7S
|
||||||
|
SutN1kKj8cM7fxj/2u/3fwgAQKO/xr4AADAPggUAtEOwAIB2CBYA0A7BAgDaIVgAQDsECwBoh2AB
|
||||||
|
AO0QLACgHYIFALRDsACAdn+PfQFw5nkeWZZFRERSSvJ9f+Qrmg7XdSnLMorjeOxLgZYQLDBpruuS
|
||||||
|
4zj5a4TLPGAoBJNVDRXOOXHOR7wiaAvBApNUDRUF4TIPCBaYnKZQURAu04dggUm5FSoKwmXaECww
|
||||||
|
GW1DRUG4TNfDV4VUx1mtViSEICKiLMtIStn4f4qdJ8syOh6Pw14kPFxdqAghyLKs0jI8EeWvif7v
|
||||||
|
G1gtmpbBg8V1XbJtm4jKHYKoHBhCCIrjuDZkqr+HYDFLU6gEQUCe55X+3ff90p4fIoTLFA0WLI7j
|
||||||
|
kOu6rX/ftm2ybZuEEBRF0dUKBsxxLVSaIFymT/scC2OMdrtdp1Apsm2bPM8jzvlFhQPmybKs9PpW
|
||||||
|
qCi+71/cfKrvZQrXdYkxNvZldKK1YmGM0cfHR+3PhBAkpaTv728iIjqdTmTbNq1WK7Jtu3aYxBij
|
||||||
|
KIp0XuLoXNelz8/PzhWZZVnkuq5x1ZyqMDjnrUNFKVYuURQZOUTe7XZ5NR+GIaVpOvYltaItWCzL
|
||||||
|
qg0VNXdS1yDFf2OM0Xa7LQWMbdt3Vz5TVOwkQRC0DgjLsvK5BlPD5d5Jed/3yXEcI0OlOj+53W5n
|
||||||
|
Ey5ahkLFjq9IKelwOFAQBK0aIk1T8n3/ooOohp27aifZ7XathnrVtjUtbJU+wWBiqBDRRWWrwqXv
|
||||||
|
sKi40jYULRVLtaP3eTo3iiLKssy4/QlJkpSGfCpcrlUudYFNhAnKpZBSUhAEpZvQvZWLGkoXb9RS
|
||||||
|
SkqSZJD+1Lti4ZxfVBVhGPZ6zziOjfvwpGlKYRhe3IGaKpemUDkcDrMohUEPFS59KhfOOXmed/E5
|
||||||
|
tSwr/5nuCqZ3xfL6+lp63bXjq0naJVDhUpxLqgsXhAoU9alcGGM3q3/VB3WeAdSrYqmO1aSUnTs+
|
||||||
|
Yyyf0Kz+MVFT5XILQmXZ7q1cmlZpqyzL6vQ4xS29KpZqtZIkSa+LWYq6yuUahIo56irRLqr95Vrl
|
||||||
|
0nUksNlstE2EawsWKaVx8yJDahsuCBWzDLEa0xQu6/W68/vo0itYdIzJumyIMs2tcEGoQFt1/We1
|
||||||
|
WvV+j3vhzNuRNYULQsVMfTc2Nn346/qLEKLTtg2d+4FGOzbhdDrlz3aYtIv0HtVwQaiYq0+V33W1
|
||||||
|
UJ0U0LYS0fms1cOCpelQHjzNfKbCRf0dzqSUed9Ych+5ZwuClJLCMGy1MqQevdHl136//6Pt3Rqo
|
||||||
|
Z2SuMfUhMoC++u5runXSngognTe0wSuWup25dd7f3/MnoAHgTMdmyTiOKUmSh27pHzRY1Jbhtr/7
|
||||||
|
+vqKJWuA/+jcga022FWP+RzKpFaFlrK1vw5jjNbrdX4W8K1zgJdiye1S9xR734n9R7XdoMFS3Zl7
|
||||||
|
i6nb+K+pOxyLcz7IuHdO0C7necfi8GVOq4W/397e/hnqzdfrNb28vLT+fSklfX19DXU5k8M5p+12
|
||||||
|
W/uzp6enfGl+Lp1JF7TL2c/PD6VpSs/Pz7ML00G/V0h9vUdbSylxidrPPy3t7F+0S5maG5lTqBAN
|
||||||
|
HCzFPQhtLGnitsspcCaeGNcE7WKGwb8Jse3ZrkKI2aVyH13mk+oOGzcV2sUMgweLmmy7Fi5dT2ef
|
||||||
|
O3wY6qFdzPGQ5WZ1ULY6LU4dEHU8HilJkkVVKvda0vxTF2iXaXroPpYlzaFco+ae2t6hl/LhQbuY
|
||||||
|
Y/ChENTrctrekk7mQ7uYAcEykjiOW91xl3YyH9rFDAiWEd1aMRNC9P4qlTlCu8zfQ45NgOscx6HN
|
||||||
|
ZlOa1M6ybPF3ZLTLfCFYAEA7DIUAQDsECwBoh2ABAO0QLACgHYIFALRDsACAdggWANAOwQIA2iFY
|
||||||
|
AEA7BAsAaIdgAQDt/gUoDXNStc/rMQAAAABJRU5ErkJggg==
|
||||||
|
"
|
||||||
|
id="image2132"
|
||||||
|
x="-82"
|
||||||
|
y="-11.9" /><image
|
||||||
|
width="460"
|
||||||
|
height="71"
|
||||||
|
preserveAspectRatio="none"
|
||||||
|
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAcwAAABHCAYAAACK23cpAAAABHNCSVQICAgIfAhkiAAACuZJREFU
|
||||||
|
eJzt3T9o3FgeB/Cv77aZBHInYZPD+JLF4iDxDl44ZNiAvVfNhC2vCEqxWQJpJn3MwlRXTeP0drNF
|
||||||
|
NtfIKa5cYnVxkQMLDoztbU6GgBkwDiPOEHuOK/aK7FM0Gs3M01gzkp6+HzBknPFEkSV93z/9NLO6
|
||||||
|
uvoLiIiIaKjfZL0BRERERcDAJCIiksDAJCIiksDAJCIiksDAJCIiksDAJCIiksDAJCIiksDAJCIi
|
||||||
|
ksDAJCIiksDAJCIiksDAJCIiksDAJCIikvBZ1htAlIZmswlN0wAAvu+j1WplvEX5YVkWOp0OHMfJ
|
||||||
|
elMoBYuLi8Gfj4+PM9yS8mFgEinMsiyYphm8ZmgSjY9DskSKioZlvV5HrVbLcIuoyEzTDEZxyoqB
|
||||||
|
SaSgaFgKDE0aR6PRCI6pMocmA5NIMYPCUmBoUhKNRgOGYQD4eOyUOTQZmEQKGRWWAkOTZPm+3/O6
|
||||||
|
zKHJwCRShGxYCgxNkmHbNlzX7fleWUNzqqtk5xaWMbuwHLx+d+Tg4vw0eH3txs2e19RPXBA1TQuW
|
||||||
|
lHc6nb5WYFj4ouj7ft/BT8UXF5ae50HX9Z7bbQD0XOTq9ToArp6l4WzbBoC+RWQA4Lru0OuPSqYa
|
||||||
|
mLMLy1i69yh4/f5kvycgv3nyI47evuwL0jKzLCuYPxjWmvM8D47jxIanOLDF+xiYahkUlltbW2g2
|
||||||
|
mz3fb7VaPfesAgxNksPQzOGQ7NK9R/jLgw3MhXqiZWSaJjY2NoJhj1FDH4ZhBCvZyjZMUmbDwnKQ
|
||||||
|
VqsV26ji8CyNUvbh2VwWLrh24ya+frCBs5N9uK+fl6q3aRgGarVa0Ksc5+ebzSZ2dnZK0+ors06n
|
||||||
|
0/N6VFgKcT1NVY8Vy7Lgui48z8t6Uwpr1HVpUE9T0zQ8ffp0rDAVo2F5GhHLXQ8zbG5hGd88+RG3
|
||||||
|
l2q4duNm1pszcaKXGHdQioPHtm3Yto2tra2hoViv15XsbY77f9I0DY1GQ7n94TgOdnZ2AMiHpRDu
|
||||||
|
acb1HFTQaDRgmmbP1AYlI86dUfsvrqfp+z42NzfHaowZhgHLsnI18pHLHmaUef+Z8r1NcVBGibnJ
|
||||||
|
uNZx+Hvi4AofrOJ7qhAnrWEYiU5CTdOCuTzLsmDbtlK9Kcdxxl7M1Wq1YJqmkmEZnf8Xv3v2NJMJ
|
||||||
|
r4GQfW+4IS9Cc9yeZp7m2H9769atv03rH5tbWMbcH78MXkcX94QXBEVdv3ETf/rzXwEAF+en+N9/
|
||||||
|
P0xuQ6csfEEXfN/HixcvgovhKL7vY3d3F7quY35+Pvi+rut97yvixdGyLFSrVQBApVJBtVrFwcEB
|
||||||
|
ut0uAGBtbQ2VSgUA0O12sbu7C6B/34r9U8R9MEy73Y79/qD9IvOzRddut1GtVoP/f6VSgWEYaLfb
|
||||||
|
V2owaZqGSqUSHHvTNu1h9MePH/e89jwPvu/3fIWvM4Zh4PLyEr7vB/uo2+3i4OAA8/PzfT876Cv6
|
||||||
|
mb7vZ36s5raHeXF+ig/np32Lf5buPcLnX9Thvn6Os5P9jLYuXdFe4FWetmHbNjqdTqJWYRG4rgvD
|
||||||
|
MIKLhZgbGdbTjGuIAPloqdLkxfVsxu1pip8LD0v6vo+9vb1SHU+Dhv3j6hYD/T3NJFMG0Tl2y7KC
|
||||||
|
sM5KbucwP5yf4s2r9dhhWLEo6OsHG4Wf24ybSBfLt8cVntdShed5fUOpwxYUDArLra0tDsmVSNwc
|
||||||
|
Wlz4DVOr1dBsNvver2ka6vV634W9jCaxejauMTzusG5acjske3F+indHDv5zdox//+sfuP67P+D3
|
||||||
|
c70HrBimFUO0RRymffjwYTBkBCS/oNdqteDADH/put43HAsUd0gWQDAkYxhGzzBbtVrtOYkqlQrW
|
||||||
|
1tb6fr6MYSkzJKs6MRw4zvCszDoAcQxOa99Oe0g2PFo17PpxeHjYNyUUNzwra9DvLTodM0257WFG
|
||||||
|
ua+f482r9dhFP+b9ZzDvPytcbzN6f6Xv+4kv6GIRTNyXigb1NEcpY1jSJ+P2NOMW4sXRNC1RWUJV
|
||||||
|
pd3THPZ7y6Knmds5zDhnJ/v46YfvcPerb/H5F/WegBS3oBSpUlD0BNvb28toS4pFhKbsScOwVEfc
|
||||||
|
MHsS0eNl2Jxm0kanqquNk0q7IpCYK15ZWQl+f4ZhBPt7mnOahelhhv38z78PXPRTpEpBKysrwZ99
|
||||||
|
3y/V4oGriutpxmFYqkWMyoz7Negz43qaSXswcVMgZZV2T9NxnL4Ohfi8aSpUDzPsQwF6kKOMuxI2
|
||||||
|
LMmqM9WM6mkyLEnWoIVjV/2MMptETzNq2o2UQgbm3a++HXjP5rsjB+7r51PeIsrKoNBkWKrpqsNv
|
||||||
|
g0It7ngRTwOSxeHYfmmFZtziK3HuT1OhAnNuYRl37z2KHW49O9nHz29fKnNv5iDiwPN9P6gjqlLV
|
||||||
|
mnFEQ5Nhqa6rjMokvdVIPPlHtucYretLH101NEXJ0LCkZSDTUpjANO8/w+2l+JqC7uvneHek9vyf
|
||||||
|
uH0kSnYuT3Xh1ibD8pNOp8OGFca7L9f3fdi2LbVSVpSwpHjjhmZcydCkBRDSlPvAvL1Ug3n/Wezf
|
||||||
|
qV5fVhhW+Fg8nUTV4tlJMCj7lXmOW7hKEQvP87CzszO0chYX7MlJGppiMVaYaMRkJbeBef3Xaj5x
|
||||||
|
w68X56dKlcYbRvZRX/V6PfOyUUR5k0bFJ8dx4LouS+OlIGloRhf1ZF08P7eBee3GzdhCBGVa1CNK
|
||||||
|
b8m+1zRNnrxEv0qzPKIYBhTzmWVqmIbncXVdly7mkITMU07ysDYht4EZVZZFPVdhGEZpA1MUZtc0
|
||||||
|
DcfHx8GCjbIr836JK2l31YtuWfZdWKfT6SleP6nbZ4aFpmEYmYclUJDALMOinjhJb8pVtRzeMHEr
|
||||||
|
6IBPcx15OMmywP2CYOW0OC/y0EMpItu2p1b0fFBo5mV9Rm6LrwMfe5VvXq3j/QR6lbOzs7i4uEj9
|
||||||
|
c9Ok63rwDEgZ4pmYZVGr1QYWxq5UKkGDI+n9dEXH/fJRt9uF53mYn5/H9va2MmE57eLr3W4X7XYb
|
||||||
|
mqZhZmam52ERk3CVgu2Tlsse5qQX9czOzuL777/H+vr6RD4/LUlP8DLdByY7v1uv16debzJL3C+9
|
||||||
|
srwFQSWe5020wSHzPM08yF0t2XdHDn764buJhuWTJ08m8tlpE08el1Wm+ctRj1wa971Fx/1CRTSJ
|
||||||
|
52lOwlR7mO9P9nH09mXwOloP9s2r9Yku6hFhOTs7O7F/I23RJ8YPMukWYN4kma8VC1/y1FKdFO4X
|
||||||
|
Kqq0a89OwlQD8+xkf2ggMiz7iUUawx5llVWZqKzkqcWZJ9wvVHR5D81czmGmrahhKXieh1arFRQx
|
||||||
|
0HUdmqbBdV24rluqnuW4sj7R8or7hfImz6GpfGAWPSzDHMcp1TzlIGJuV7ZHVZZQ4H4hVeQ1NHO3
|
||||||
|
6CdNKoUl9Yo+TDat9xYd9wupIo8LgZQNTIal2hzHkWpllq0wNvcLqWRQaGZVpEXJwGRYlsPm5ubQ
|
||||||
|
cMjiAbN5wP1CKomGpud5mVX+mWqln2lIEpZsYRdbt9vF7u4ufN9HpVIJqpC4rouDgwNsb2+Xcp6O
|
||||||
|
+0Vt0670kweHh4fQdR2Xl5eZ3hEws7q6+ktm/3rKkvYs817ph4iI8kOZVbKi3B0REdEkKNXDJCIi
|
||||||
|
mhQlF/0QERGljYFJREQkgYFJREQkgYFJREQkgYFJREQkgYFJREQkgYFJREQkgYFJREQkgYFJREQk
|
||||||
|
4bPFxcWst4GIiCj32MMkIiKSMHPnzh3WkiUiIhqBPUwiIiIJDEwiIiIJDEwiIiIJ/weH8+Ed/xCz
|
||||||
|
fAAAAABJRU5ErkJggg==
|
||||||
|
"
|
||||||
|
id="image2845"
|
||||||
|
x="-15.489594"
|
||||||
|
y="-8.2945251" /></g><g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer5"
|
||||||
|
inkscape:label="dot" /><g
|
||||||
|
inkscape:label="over"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
style="display:none"><ellipse
|
||||||
|
style="fill:#000000;stroke:#000000;stroke-width:5.27982;stroke-dasharray:none"
|
||||||
|
id="path2637"
|
||||||
|
cx="24"
|
||||||
|
cy="33"
|
||||||
|
rx="4.3600898"
|
||||||
|
ry="4.3600893" /><path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none"
|
||||||
|
d="M 9,24 C 9.7590836,20.626295 11.695032,17.529367 14.395314,15.369142 17.095595,13.208917 20.541953,12 24,12 c 3.458047,0 6.904405,1.208917 9.604686,3.369142 C 36.304968,17.529367 38.240916,20.626295 39,24"
|
||||||
|
id="path2142"
|
||||||
|
inkscape:path-effect="#path-effect2144"
|
||||||
|
inkscape:original-d="m 9,24 c 4.959859,-3.824406 10.021901,-8.173595 15,-12 4.978099,-3.8264055 10.285024,8.398237 15,12"
|
||||||
|
sodipodi:nodetypes="csc" /><path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none"
|
||||||
|
d="M 26,22 H 39 V 9"
|
||||||
|
id="path2626"
|
||||||
|
sodipodi:nodetypes="ccc" /></g><g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer6"
|
||||||
|
inkscape:label="into"
|
||||||
|
style="display:none"><ellipse
|
||||||
|
style="fill:#000000;stroke:#000000;stroke-width:5.27982;stroke-dasharray:none"
|
||||||
|
id="path2637-3"
|
||||||
|
cx="24"
|
||||||
|
cy="38.5"
|
||||||
|
rx="4.3600898"
|
||||||
|
ry="4.3600893" /><path
|
||||||
|
style="fill:#000000;stroke:#000000;stroke-width:5;stroke-dasharray:none"
|
||||||
|
d="M 24,2 V 24"
|
||||||
|
id="path2668"
|
||||||
|
sodipodi:nodetypes="cc" /><path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none"
|
||||||
|
d="M 14.807612,16.994936 24,26.187324 33.192388,16.994936"
|
||||||
|
id="path2626-8"
|
||||||
|
sodipodi:nodetypes="ccc" /></g><g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="g2714"
|
||||||
|
inkscape:label="out"
|
||||||
|
style="display:none"><ellipse
|
||||||
|
style="fill:#000000;stroke:#000000;stroke-width:5.27982;stroke-dasharray:none"
|
||||||
|
id="ellipse2708"
|
||||||
|
cx="24"
|
||||||
|
cy="38.5"
|
||||||
|
rx="4.3600898"
|
||||||
|
ry="4.3600893" /><path
|
||||||
|
style="fill:#000000;stroke:#000000;stroke-width:5;stroke-dasharray:none"
|
||||||
|
d="M 24,29.722858 V 7.7228579"
|
||||||
|
id="path2710"
|
||||||
|
sodipodi:nodetypes="cc" /><path
|
||||||
|
style="display:inline;fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none"
|
||||||
|
d="M 33.192388,14.727922 24,5.5355339 14.807612,14.727922"
|
||||||
|
id="path2712"
|
||||||
|
sodipodi:nodetypes="ccc" /></g><g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer8"
|
||||||
|
inkscape:label="resume"><path
|
||||||
|
style="fill:#000000;stroke:#000000;stroke-width:5;stroke-dasharray:none"
|
||||||
|
d="M 13,12 V 38"
|
||||||
|
id="path2850"
|
||||||
|
sodipodi:nodetypes="cc" /><path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none"
|
||||||
|
d="M 21,16 V 34 L 36.5,25 Z"
|
||||||
|
id="path2852"
|
||||||
|
sodipodi:nodetypes="cccc" /></g></svg>
|
After Width: | Height: | Size: 11 KiB |
|
@ -4268,6 +4268,16 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div title="Determines which keys select an item from the AutoComplete suggestions">
|
||||||
|
<label data-i18n="Keyboard">
|
||||||
|
<small>Keyboard:</small>
|
||||||
|
</label>
|
||||||
|
<select id="stscript_autocomplete_select">
|
||||||
|
<option value="3" data-i18n="Select with Tab or Enter">Select with Tab or Enter</option>
|
||||||
|
<option value="1" data-i18n="Select with Tab">Select with Tab</option>
|
||||||
|
<option value="2" data-i18n="Select with Enter">Select with Enter</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
<div class="flex-container flexFlowColumn gap0" title="Sets the font size of the autocomplete." data-i18n="[title]Sets the font size of the autocomplete.">
|
<div class="flex-container flexFlowColumn gap0" title="Sets the font size of the autocomplete." data-i18n="[title]Sets the font size of the autocomplete.">
|
||||||
<label for="stscript_autocomplete_font_scale"><small>Font Scale</small></label>
|
<label for="stscript_autocomplete_font_scale"><small>Font Scale</small></label>
|
||||||
<input class="neo-range-slider" type="range" id="stscript_autocomplete_font_scale" min="0.5" max="2" step="0.01">
|
<input class="neo-range-slider" type="range" id="stscript_autocomplete_font_scale" min="0.5" max="2" step="0.01">
|
||||||
|
|
|
@ -16,8 +16,15 @@ export const AUTOCOMPLETE_WIDTH = {
|
||||||
'FULL': 2,
|
'FULL': 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**@readonly*/
|
||||||
|
/**@enum {Number}*/
|
||||||
|
export const AUTOCOMPLETE_SELECT_KEY = {
|
||||||
|
'TAB': 1, // 2^0
|
||||||
|
'ENTER': 2, // 2^1
|
||||||
|
};
|
||||||
|
|
||||||
export class AutoComplete {
|
export class AutoComplete {
|
||||||
/**@type {HTMLTextAreaElement}*/ textarea;
|
/**@type {HTMLTextAreaElement|HTMLInputElement}*/ textarea;
|
||||||
/**@type {boolean}*/ isFloating = false;
|
/**@type {boolean}*/ isFloating = false;
|
||||||
/**@type {()=>boolean}*/ checkIfActivate;
|
/**@type {()=>boolean}*/ checkIfActivate;
|
||||||
/**@type {(text:string, index:number) => Promise<AutoCompleteNameResult>}*/ getNameAt;
|
/**@type {(text:string, index:number) => Promise<AutoCompleteNameResult>}*/ getNameAt;
|
||||||
|
@ -56,6 +63,8 @@ export class AutoComplete {
|
||||||
/**@type {function}*/ updateDetailsPositionDebounced;
|
/**@type {function}*/ updateDetailsPositionDebounced;
|
||||||
/**@type {function}*/ updateFloatingPositionDebounced;
|
/**@type {function}*/ updateFloatingPositionDebounced;
|
||||||
|
|
||||||
|
/**@type {(item:AutoCompleteOption)=>any}*/ onSelect;
|
||||||
|
|
||||||
get matchType() {
|
get matchType() {
|
||||||
return power_user.stscript.matching ?? 'fuzzy';
|
return power_user.stscript.matching ?? 'fuzzy';
|
||||||
}
|
}
|
||||||
|
@ -68,7 +77,7 @@ export class AutoComplete {
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {HTMLTextAreaElement} textarea The textarea to receive autocomplete.
|
* @param {HTMLTextAreaElement|HTMLInputElement} textarea The textarea to receive autocomplete.
|
||||||
* @param {() => boolean} checkIfActivate Function should return true only if under the current conditions, autocomplete should display (e.g., for slash commands: autoComplete.text[0] == '/')
|
* @param {() => boolean} checkIfActivate Function should return true only if under the current conditions, autocomplete should display (e.g., for slash commands: autoComplete.text[0] == '/')
|
||||||
* @param {(text: string, index: number) => Promise<AutoCompleteNameResult>} getNameAt Function should return (unfiltered, matching against input is done in AutoComplete) information about name options at index in text.
|
* @param {(text: string, index: number) => Promise<AutoCompleteNameResult>} getNameAt Function should return (unfiltered, matching against input is done in AutoComplete) information about name options at index in text.
|
||||||
* @param {boolean} isFloating Whether autocomplete should float at the keyboard cursor.
|
* @param {boolean} isFloating Whether autocomplete should float at the keyboard cursor.
|
||||||
|
@ -102,10 +111,15 @@ export class AutoComplete {
|
||||||
this.updateDetailsPositionDebounced = debounce(this.updateDetailsPosition.bind(this), 10);
|
this.updateDetailsPositionDebounced = debounce(this.updateDetailsPosition.bind(this), 10);
|
||||||
this.updateFloatingPositionDebounced = debounce(this.updateFloatingPosition.bind(this), 10);
|
this.updateFloatingPositionDebounced = debounce(this.updateFloatingPosition.bind(this), 10);
|
||||||
|
|
||||||
textarea.addEventListener('input', ()=>this.text != this.textarea.value && this.show(true, this.wasForced));
|
textarea.addEventListener('input', ()=>{
|
||||||
|
this.selectionStart = this.textarea.selectionStart;
|
||||||
|
if (this.text != this.textarea.value) this.show(true, this.wasForced);
|
||||||
|
});
|
||||||
textarea.addEventListener('keydown', (evt)=>this.handleKeyDown(evt));
|
textarea.addEventListener('keydown', (evt)=>this.handleKeyDown(evt));
|
||||||
textarea.addEventListener('click', ()=>this.isActive ? this.show() : null);
|
textarea.addEventListener('click', ()=>{
|
||||||
textarea.addEventListener('selectionchange', ()=>this.show());
|
this.selectionStart = this.textarea.selectionStart;
|
||||||
|
if (this.isActive) this.show();
|
||||||
|
});
|
||||||
textarea.addEventListener('blur', ()=>this.hide());
|
textarea.addEventListener('blur', ()=>this.hide());
|
||||||
if (isFloating) {
|
if (isFloating) {
|
||||||
textarea.addEventListener('scroll', ()=>this.updateFloatingPositionDebounced());
|
textarea.addEventListener('scroll', ()=>this.updateFloatingPositionDebounced());
|
||||||
|
@ -189,6 +203,11 @@ export class AutoComplete {
|
||||||
* @returns The option.
|
* @returns The option.
|
||||||
*/
|
*/
|
||||||
fuzzyScore(option) {
|
fuzzyScore(option) {
|
||||||
|
// might have been matched by the options matchProvider function instead
|
||||||
|
if (!this.fuzzyRegex.test(option.name)) {
|
||||||
|
option.score = new AutoCompleteFuzzyScore(Number.MAX_SAFE_INTEGER, -1);
|
||||||
|
return option;
|
||||||
|
}
|
||||||
const parts = this.fuzzyRegex.exec(option.name).slice(1, -1);
|
const parts = this.fuzzyRegex.exec(option.name).slice(1, -1);
|
||||||
let start = null;
|
let start = null;
|
||||||
let consecutive = [];
|
let consecutive = [];
|
||||||
|
@ -339,7 +358,7 @@ export class AutoComplete {
|
||||||
|
|
||||||
this.result = this.effectiveParserResult.optionList
|
this.result = this.effectiveParserResult.optionList
|
||||||
// filter the list of options by the partial name according to the matching type
|
// filter the list of options by the partial name according to the matching type
|
||||||
.filter(it => this.isReplaceable || it.name == '' ? matchers[this.matchType](it.name) : it.name.toLowerCase() == this.name)
|
.filter(it => this.isReplaceable || it.name == '' ? (it.matchProvider ? it.matchProvider(this.name) : matchers[this.matchType](it.name)) : it.name.toLowerCase() == this.name)
|
||||||
// remove aliases
|
// remove aliases
|
||||||
.filter((it,idx,list) => list.findIndex(opt=>opt.value == it.value) == idx);
|
.filter((it,idx,list) => list.findIndex(opt=>opt.value == it.value) == idx);
|
||||||
|
|
||||||
|
@ -357,10 +376,11 @@ export class AutoComplete {
|
||||||
// build element
|
// build element
|
||||||
option.dom = this.makeItem(option);
|
option.dom = this.makeItem(option);
|
||||||
// update replacer and add quotes if necessary
|
// update replacer and add quotes if necessary
|
||||||
|
const optionName = option.valueProvider ? option.valueProvider(this.name) : option.name;
|
||||||
if (this.effectiveParserResult.canBeQuoted) {
|
if (this.effectiveParserResult.canBeQuoted) {
|
||||||
option.replacer = option.name.includes(' ') || this.startQuote || this.endQuote ? `"${option.name}"` : `${option.name}`;
|
option.replacer = optionName.includes(' ') || this.startQuote || this.endQuote ? `"${optionName.replace(/"/g, '\\"')}"` : `${optionName}`;
|
||||||
} else {
|
} else {
|
||||||
option.replacer = option.name;
|
option.replacer = optionName;
|
||||||
}
|
}
|
||||||
// calculate fuzzy score if matching is fuzzy
|
// calculate fuzzy score if matching is fuzzy
|
||||||
if (this.matchType == 'fuzzy') this.fuzzyScore(option);
|
if (this.matchType == 'fuzzy') this.fuzzyScore(option);
|
||||||
|
@ -399,7 +419,7 @@ export class AutoComplete {
|
||||||
,
|
,
|
||||||
);
|
);
|
||||||
this.result.push(option);
|
this.result.push(option);
|
||||||
} else if (this.result.length == 1 && this.effectiveParserResult && this.result[0].name == this.effectiveParserResult.name) {
|
} else if (this.result.length == 1 && this.effectiveParserResult && this.effectiveParserResult != this.secondaryParserResult && this.result[0].name == this.effectiveParserResult.name) {
|
||||||
// only one result that is exactly the current value? just show hint, no autocomplete
|
// only one result that is exactly the current value? just show hint, no autocomplete
|
||||||
this.isReplaceable = false;
|
this.isReplaceable = false;
|
||||||
this.isShowingDetails = false;
|
this.isShowingDetails = false;
|
||||||
|
@ -439,11 +459,14 @@ export class AutoComplete {
|
||||||
} else {
|
} else {
|
||||||
item.dom.classList.remove('selected');
|
item.dom.classList.remove('selected');
|
||||||
}
|
}
|
||||||
|
if (!item.isSelectable) {
|
||||||
|
item.dom.classList.add('not-selectable');
|
||||||
|
}
|
||||||
frag.append(item.dom);
|
frag.append(item.dom);
|
||||||
}
|
}
|
||||||
this.dom.append(frag);
|
this.dom.append(frag);
|
||||||
this.updatePosition();
|
this.updatePosition();
|
||||||
getTopmostModalLayer().append(this.domWrap);
|
this.getLayer().append(this.domWrap);
|
||||||
} else {
|
} else {
|
||||||
this.domWrap.remove();
|
this.domWrap.remove();
|
||||||
}
|
}
|
||||||
|
@ -458,10 +481,17 @@ export class AutoComplete {
|
||||||
if (!this.isShowingDetails && this.isReplaceable) return this.detailsWrap.remove();
|
if (!this.isShowingDetails && this.isReplaceable) return this.detailsWrap.remove();
|
||||||
this.detailsDom.innerHTML = '';
|
this.detailsDom.innerHTML = '';
|
||||||
this.detailsDom.append(this.selectedItem?.renderDetails() ?? 'NO ITEM');
|
this.detailsDom.append(this.selectedItem?.renderDetails() ?? 'NO ITEM');
|
||||||
getTopmostModalLayer().append(this.detailsWrap);
|
this.getLayer().append(this.detailsWrap);
|
||||||
this.updateDetailsPositionDebounced();
|
this.updateDetailsPositionDebounced();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {HTMLElement} closest ancestor dialog or body
|
||||||
|
*/
|
||||||
|
getLayer() {
|
||||||
|
return this.textarea.closest('dialog, body');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -474,7 +504,7 @@ export class AutoComplete {
|
||||||
const rect = {};
|
const rect = {};
|
||||||
rect[AUTOCOMPLETE_WIDTH.INPUT] = this.textarea.getBoundingClientRect();
|
rect[AUTOCOMPLETE_WIDTH.INPUT] = this.textarea.getBoundingClientRect();
|
||||||
rect[AUTOCOMPLETE_WIDTH.CHAT] = document.querySelector('#sheld').getBoundingClientRect();
|
rect[AUTOCOMPLETE_WIDTH.CHAT] = document.querySelector('#sheld').getBoundingClientRect();
|
||||||
rect[AUTOCOMPLETE_WIDTH.FULL] = getTopmostModalLayer().getBoundingClientRect();
|
rect[AUTOCOMPLETE_WIDTH.FULL] = this.getLayer().getBoundingClientRect();
|
||||||
this.domWrap.style.setProperty('--bottom', `${window.innerHeight - rect[AUTOCOMPLETE_WIDTH.INPUT].top}px`);
|
this.domWrap.style.setProperty('--bottom', `${window.innerHeight - rect[AUTOCOMPLETE_WIDTH.INPUT].top}px`);
|
||||||
this.dom.style.setProperty('--bottom', `${window.innerHeight - rect[AUTOCOMPLETE_WIDTH.INPUT].top}px`);
|
this.dom.style.setProperty('--bottom', `${window.innerHeight - rect[AUTOCOMPLETE_WIDTH.INPUT].top}px`);
|
||||||
this.domWrap.style.bottom = `${window.innerHeight - rect[AUTOCOMPLETE_WIDTH.INPUT].top}px`;
|
this.domWrap.style.bottom = `${window.innerHeight - rect[AUTOCOMPLETE_WIDTH.INPUT].top}px`;
|
||||||
|
@ -501,7 +531,7 @@ export class AutoComplete {
|
||||||
const rect = {};
|
const rect = {};
|
||||||
rect[AUTOCOMPLETE_WIDTH.INPUT] = this.textarea.getBoundingClientRect();
|
rect[AUTOCOMPLETE_WIDTH.INPUT] = this.textarea.getBoundingClientRect();
|
||||||
rect[AUTOCOMPLETE_WIDTH.CHAT] = document.querySelector('#sheld').getBoundingClientRect();
|
rect[AUTOCOMPLETE_WIDTH.CHAT] = document.querySelector('#sheld').getBoundingClientRect();
|
||||||
rect[AUTOCOMPLETE_WIDTH.FULL] = getTopmostModalLayer().getBoundingClientRect();
|
rect[AUTOCOMPLETE_WIDTH.FULL] = this.getLayer().getBoundingClientRect();
|
||||||
if (this.isReplaceable) {
|
if (this.isReplaceable) {
|
||||||
this.detailsWrap.classList.remove('full');
|
this.detailsWrap.classList.remove('full');
|
||||||
const selRect = this.selectedItem.dom.children[0].getBoundingClientRect();
|
const selRect = this.selectedItem.dom.children[0].getBoundingClientRect();
|
||||||
|
@ -527,32 +557,34 @@ export class AutoComplete {
|
||||||
updateFloatingPosition() {
|
updateFloatingPosition() {
|
||||||
const location = this.getCursorPosition();
|
const location = this.getCursorPosition();
|
||||||
const rect = this.textarea.getBoundingClientRect();
|
const rect = this.textarea.getBoundingClientRect();
|
||||||
|
const layerRect = this.getLayer().getBoundingClientRect();
|
||||||
// cursor is out of view -> hide
|
// cursor is out of view -> hide
|
||||||
if (location.bottom < rect.top || location.top > rect.bottom || location.left < rect.left || location.left > rect.right) {
|
if (location.bottom < rect.top || location.top > rect.bottom || location.left < rect.left || location.left > rect.right) {
|
||||||
return this.hide();
|
return this.hide();
|
||||||
}
|
}
|
||||||
const left = Math.max(rect.left, location.left);
|
const left = Math.max(rect.left, location.left) - layerRect.left;
|
||||||
this.domWrap.style.setProperty('--targetOffset', `${left}`);
|
this.domWrap.style.setProperty('--targetOffset', `${left}`);
|
||||||
if (location.top <= window.innerHeight / 2) {
|
if (location.top <= window.innerHeight / 2) {
|
||||||
// if cursor is in lower half of window, show list above line
|
// if cursor is in lower half of window, show list above line
|
||||||
this.domWrap.style.top = `${location.bottom}px`;
|
this.domWrap.style.top = `${location.bottom - layerRect.top}px`;
|
||||||
this.domWrap.style.bottom = 'auto';
|
this.domWrap.style.bottom = 'auto';
|
||||||
this.domWrap.style.maxHeight = `calc(${location.bottom}px - 1vh)`;
|
this.domWrap.style.maxHeight = `calc(${location.bottom - layerRect.top}px - ${this.textarea.closest('dialog') ? '0' : '1vh'})`;
|
||||||
} else {
|
} else {
|
||||||
// if cursor is in upper half of window, show list below line
|
// if cursor is in upper half of window, show list below line
|
||||||
this.domWrap.style.top = 'auto';
|
this.domWrap.style.top = 'auto';
|
||||||
this.domWrap.style.bottom = `calc(100vh - ${location.top}px)`;
|
this.domWrap.style.bottom = `calc(${layerRect.height}px - ${location.top - layerRect.top}px)`;
|
||||||
this.domWrap.style.maxHeight = `calc(${location.top}px - 1vh)`;
|
this.domWrap.style.maxHeight = `calc(${location.top - layerRect.top}px - ${this.textarea.closest('dialog') ? '0' : '1vh'})`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateFloatingDetailsPosition(location = null) {
|
updateFloatingDetailsPosition(location = null) {
|
||||||
if (!location) location = this.getCursorPosition();
|
if (!location) location = this.getCursorPosition();
|
||||||
const rect = this.textarea.getBoundingClientRect();
|
const rect = this.textarea.getBoundingClientRect();
|
||||||
|
const layerRect = this.getLayer().getBoundingClientRect();
|
||||||
if (location.bottom < rect.top || location.top > rect.bottom || location.left < rect.left || location.left > rect.right) {
|
if (location.bottom < rect.top || location.top > rect.bottom || location.left < rect.left || location.left > rect.right) {
|
||||||
return this.hide();
|
return this.hide();
|
||||||
}
|
}
|
||||||
const left = Math.max(rect.left, location.left);
|
const left = Math.max(rect.left, location.left) - layerRect.left;
|
||||||
this.detailsWrap.style.setProperty('--targetOffset', `${left}`);
|
this.detailsWrap.style.setProperty('--targetOffset', `${left}`);
|
||||||
if (this.isReplaceable) {
|
if (this.isReplaceable) {
|
||||||
this.detailsWrap.classList.remove('full');
|
this.detailsWrap.classList.remove('full');
|
||||||
|
@ -572,14 +604,14 @@ export class AutoComplete {
|
||||||
}
|
}
|
||||||
if (location.top <= window.innerHeight / 2) {
|
if (location.top <= window.innerHeight / 2) {
|
||||||
// if cursor is in lower half of window, show list above line
|
// if cursor is in lower half of window, show list above line
|
||||||
this.detailsWrap.style.top = `${location.bottom}px`;
|
this.detailsWrap.style.top = `${location.bottom - layerRect.top}px`;
|
||||||
this.detailsWrap.style.bottom = 'auto';
|
this.detailsWrap.style.bottom = 'auto';
|
||||||
this.detailsWrap.style.maxHeight = `calc(${location.bottom}px - 1vh)`;
|
this.detailsWrap.style.maxHeight = `calc(${location.bottom - layerRect.top}px - ${this.textarea.closest('dialog') ? '0' : '1vh'})`;
|
||||||
} else {
|
} else {
|
||||||
// if cursor is in upper half of window, show list below line
|
// if cursor is in upper half of window, show list below line
|
||||||
this.detailsWrap.style.top = 'auto';
|
this.detailsWrap.style.top = 'auto';
|
||||||
this.detailsWrap.style.bottom = `calc(100vh - ${location.top}px)`;
|
this.detailsWrap.style.bottom = `calc(${layerRect.height}px - ${location.top - layerRect.top}px)`;
|
||||||
this.detailsWrap.style.maxHeight = `calc(${location.top}px - 1vh)`;
|
this.detailsWrap.style.maxHeight = `calc(${location.top - layerRect.top}px - ${this.textarea.closest('dialog') ? '0' : '1vh'})`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -597,7 +629,7 @@ export class AutoComplete {
|
||||||
}
|
}
|
||||||
this.clone.style.position = 'fixed';
|
this.clone.style.position = 'fixed';
|
||||||
this.clone.style.visibility = 'hidden';
|
this.clone.style.visibility = 'hidden';
|
||||||
getTopmostModalLayer().append(this.clone);
|
document.body.append(this.clone);
|
||||||
const mo = new MutationObserver(muts=>{
|
const mo = new MutationObserver(muts=>{
|
||||||
if (muts.find(it=>Array.from(it.removedNodes).includes(this.textarea))) {
|
if (muts.find(it=>Array.from(it.removedNodes).includes(this.textarea))) {
|
||||||
this.clone.remove();
|
this.clone.remove();
|
||||||
|
@ -656,6 +688,7 @@ export class AutoComplete {
|
||||||
}
|
}
|
||||||
this.wasForced = false;
|
this.wasForced = false;
|
||||||
this.textarea.dispatchEvent(new Event('input', { bubbles:true }));
|
this.textarea.dispatchEvent(new Event('input', { bubbles:true }));
|
||||||
|
this.onSelect?.(this.selectedItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -708,8 +741,10 @@ export class AutoComplete {
|
||||||
}
|
}
|
||||||
case 'Enter': {
|
case 'Enter': {
|
||||||
// pick the selected item to autocomplete
|
// pick the selected item to autocomplete
|
||||||
|
if ((power_user.stscript.autocomplete.select & AUTOCOMPLETE_SELECT_KEY.ENTER) != AUTOCOMPLETE_SELECT_KEY.ENTER) break;
|
||||||
if (evt.ctrlKey || evt.altKey || evt.shiftKey || this.selectedItem.value == '') break;
|
if (evt.ctrlKey || evt.altKey || evt.shiftKey || this.selectedItem.value == '') break;
|
||||||
if (this.selectedItem.name == this.name) break;
|
if (this.selectedItem.name == this.name) break;
|
||||||
|
if (!this.selectedItem.isSelectable) break;
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
evt.stopImmediatePropagation();
|
evt.stopImmediatePropagation();
|
||||||
this.select();
|
this.select();
|
||||||
|
@ -717,9 +752,11 @@ export class AutoComplete {
|
||||||
}
|
}
|
||||||
case 'Tab': {
|
case 'Tab': {
|
||||||
// pick the selected item to autocomplete
|
// pick the selected item to autocomplete
|
||||||
|
if ((power_user.stscript.autocomplete.select & AUTOCOMPLETE_SELECT_KEY.TAB) != AUTOCOMPLETE_SELECT_KEY.TAB) break;
|
||||||
if (evt.ctrlKey || evt.altKey || evt.shiftKey || this.selectedItem.value == '') break;
|
if (evt.ctrlKey || evt.altKey || evt.shiftKey || this.selectedItem.value == '') break;
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
evt.stopImmediatePropagation();
|
evt.stopImmediatePropagation();
|
||||||
|
if (!this.selectedItem.isSelectable) break;
|
||||||
this.select();
|
this.select();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -772,13 +809,7 @@ export class AutoComplete {
|
||||||
// ignore keydown on modifier keys
|
// ignore keydown on modifier keys
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
switch (evt.key) {
|
// await keyup to see if cursor position or text has changed
|
||||||
case 'ArrowUp':
|
|
||||||
case 'ArrowDown':
|
|
||||||
case 'ArrowRight':
|
|
||||||
case 'ArrowLeft': {
|
|
||||||
if (this.isActive) {
|
|
||||||
// keyboard navigation, wait for keyup to complete cursor move
|
|
||||||
const oldText = this.textarea.value;
|
const oldText = this.textarea.value;
|
||||||
await new Promise(resolve=>{
|
await new Promise(resolve=>{
|
||||||
window.addEventListener('keyup', resolve, { once:true });
|
window.addEventListener('keyup', resolve, { once:true });
|
||||||
|
@ -786,16 +817,8 @@ export class AutoComplete {
|
||||||
if (this.selectionStart != this.textarea.selectionStart) {
|
if (this.selectionStart != this.textarea.selectionStart) {
|
||||||
this.selectionStart = this.textarea.selectionStart;
|
this.selectionStart = this.textarea.selectionStart;
|
||||||
this.show(this.isReplaceable || oldText != this.textarea.value);
|
this.show(this.isReplaceable || oldText != this.textarea.value);
|
||||||
}
|
} else if (this.isActive) {
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
if (this.isActive) {
|
|
||||||
this.text != this.textarea.value && this.show(this.isReplaceable);
|
this.text != this.textarea.value && this.show(this.isReplaceable);
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,36 +1,9 @@
|
||||||
import { SlashCommandNamedArgumentAutoCompleteOption } from '../slash-commands/SlashCommandNamedArgumentAutoCompleteOption.js';
|
import { AutoCompleteNameResultBase } from './AutoCompleteNameResultBase.js';
|
||||||
import { AutoCompleteOption } from './AutoCompleteOption.js';
|
import { AutoCompleteSecondaryNameResult } from './AutoCompleteSecondaryNameResult.js';
|
||||||
// import { AutoCompleteSecondaryNameResult } from './AutoCompleteSecondaryNameResult.js';
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export class AutoCompleteNameResult {
|
export class AutoCompleteNameResult extends AutoCompleteNameResultBase {
|
||||||
/**@type {string} */ name;
|
|
||||||
/**@type {number} */ start;
|
|
||||||
/**@type {AutoCompleteOption[]} */ optionList = [];
|
|
||||||
/**@type {boolean} */ canBeQuoted = false;
|
|
||||||
/**@type {()=>string} */ makeNoMatchText = ()=>`No matches found for "${this.name}"`;
|
|
||||||
/**@type {()=>string} */ makeNoOptionsText = ()=>'No options';
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} name Name (potentially partial) of the name at the requested index.
|
|
||||||
* @param {number} start Index where the name starts.
|
|
||||||
* @param {AutoCompleteOption[]} optionList A list of autocomplete options found in the current scope.
|
|
||||||
* @param {boolean} canBeQuoted Whether the name can be inside quotes.
|
|
||||||
* @param {()=>string} makeNoMatchText Function that returns text to show when no matches where found.
|
|
||||||
* @param {()=>string} makeNoOptionsText Function that returns text to show when no options are available to match against.
|
|
||||||
*/
|
|
||||||
constructor(name, start, optionList = [], canBeQuoted = false, makeNoMatchText = null, makeNoOptionsText = null) {
|
|
||||||
this.name = name;
|
|
||||||
this.start = start;
|
|
||||||
this.optionList = optionList;
|
|
||||||
this.canBeQuoted = canBeQuoted;
|
|
||||||
this.noMatchText = makeNoMatchText ?? this.makeNoMatchText;
|
|
||||||
this.noOptionstext = makeNoOptionsText ?? this.makeNoOptionsText;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {string} text The whole text
|
* @param {string} text The whole text
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { SlashCommandNamedArgumentAutoCompleteOption } from '../slash-commands/SlashCommandNamedArgumentAutoCompleteOption.js';
|
||||||
|
import { AutoCompleteOption } from './AutoCompleteOption.js';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export class AutoCompleteNameResultBase {
|
||||||
|
/**@type {string} */ name;
|
||||||
|
/**@type {number} */ start;
|
||||||
|
/**@type {AutoCompleteOption[]} */ optionList = [];
|
||||||
|
/**@type {boolean} */ canBeQuoted = false;
|
||||||
|
/**@type {()=>string} */ makeNoMatchText = ()=>`No matches found for "${this.name}"`;
|
||||||
|
/**@type {()=>string} */ makeNoOptionsText = ()=>'No options';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} name Name (potentially partial) of the name at the requested index.
|
||||||
|
* @param {number} start Index where the name starts.
|
||||||
|
* @param {AutoCompleteOption[]} optionList A list of autocomplete options found in the current scope.
|
||||||
|
* @param {boolean} canBeQuoted Whether the name can be inside quotes.
|
||||||
|
* @param {()=>string} makeNoMatchText Function that returns text to show when no matches where found.
|
||||||
|
* @param {()=>string} makeNoOptionsText Function that returns text to show when no options are available to match against.
|
||||||
|
*/
|
||||||
|
constructor(name, start, optionList = [], canBeQuoted = false, makeNoMatchText = null, makeNoOptionsText = null) {
|
||||||
|
this.name = name;
|
||||||
|
this.start = start;
|
||||||
|
this.optionList = optionList;
|
||||||
|
this.canBeQuoted = canBeQuoted;
|
||||||
|
this.noMatchText = makeNoMatchText ?? this.makeNoMatchText;
|
||||||
|
this.noOptionstext = makeNoOptionsText ?? this.makeNoOptionsText;
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,6 +11,9 @@ export class AutoCompleteOption {
|
||||||
/**@type {AutoCompleteFuzzyScore}*/ score;
|
/**@type {AutoCompleteFuzzyScore}*/ score;
|
||||||
/**@type {string}*/ replacer;
|
/**@type {string}*/ replacer;
|
||||||
/**@type {HTMLElement}*/ dom;
|
/**@type {HTMLElement}*/ dom;
|
||||||
|
/**@type {(input:string)=>boolean}*/ matchProvider;
|
||||||
|
/**@type {(input:string)=>string}*/ valueProvider;
|
||||||
|
/**@type {boolean}*/ makeSelectable = false;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -21,14 +24,21 @@ export class AutoCompleteOption {
|
||||||
return this.name;
|
return this.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isSelectable() {
|
||||||
|
return this.makeSelectable || !this.valueProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} name
|
* @param {string} name
|
||||||
*/
|
*/
|
||||||
constructor(name, typeIcon = ' ', type = '') {
|
constructor(name, typeIcon = ' ', type = '', matchProvider = null, valueProvider = null, makeSelectable = false) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.typeIcon = typeIcon;
|
this.typeIcon = typeIcon;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
|
this.matchProvider = matchProvider;
|
||||||
|
this.valueProvider = valueProvider;
|
||||||
|
this.makeSelectable = makeSelectable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { AutoCompleteNameResult } from './AutoCompleteNameResult.js';
|
import { AutoCompleteNameResultBase } from './AutoCompleteNameResultBase.js';
|
||||||
|
|
||||||
export class AutoCompleteSecondaryNameResult extends AutoCompleteNameResult {
|
export class AutoCompleteSecondaryNameResult extends AutoCompleteNameResultBase {
|
||||||
/**@type {boolean}*/ isRequired = false;
|
/**@type {boolean}*/ isRequired = false;
|
||||||
/**@type {boolean}*/ forceMatch = true;
|
/**@type {boolean}*/ forceMatch = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,10 +23,18 @@ export class QuickReplyApi {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {QuickReply} qr
|
||||||
|
* @returns {QuickReplySet}
|
||||||
|
*/
|
||||||
|
getSetByQr(qr) {
|
||||||
|
return QuickReplySet.list.find(it=>it.qrList.includes(qr));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds and returns an existing Quick Reply Set by its name.
|
* Finds and returns an existing Quick Reply Set by its name.
|
||||||
*
|
*
|
||||||
* @param {String} name name of the quick reply set
|
* @param {string} name name of the quick reply set
|
||||||
* @returns the quick reply set, or undefined if not found
|
* @returns the quick reply set, or undefined if not found
|
||||||
*/
|
*/
|
||||||
getSetByName(name) {
|
getSetByName(name) {
|
||||||
|
@ -36,13 +44,14 @@ export class QuickReplyApi {
|
||||||
/**
|
/**
|
||||||
* Finds and returns an existing Quick Reply by its set's name and its label.
|
* Finds and returns an existing Quick Reply by its set's name and its label.
|
||||||
*
|
*
|
||||||
* @param {String} setName name of the quick reply set
|
* @param {string} setName name of the quick reply set
|
||||||
* @param {String} label label of the quick reply
|
* @param {string|number} label label or numeric ID of the quick reply
|
||||||
* @returns the quick reply, or undefined if not found
|
* @returns the quick reply, or undefined if not found
|
||||||
*/
|
*/
|
||||||
getQrByLabel(setName, label) {
|
getQrByLabel(setName, label) {
|
||||||
const set = this.getSetByName(setName);
|
const set = this.getSetByName(setName);
|
||||||
if (!set) return;
|
if (!set) return;
|
||||||
|
if (Number.isInteger(label)) return set.qrList.find(it=>it.id == label);
|
||||||
return set.qrList.find(it=>it.label == label);
|
return set.qrList.find(it=>it.label == label);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,24 +79,25 @@ export class QuickReplyApi {
|
||||||
/**
|
/**
|
||||||
* Executes an existing quick reply.
|
* Executes an existing quick reply.
|
||||||
*
|
*
|
||||||
* @param {String} setName name of the existing quick reply set
|
* @param {string} setName name of the existing quick reply set
|
||||||
* @param {String} label label of the existing quick reply (text on the button)
|
* @param {string|number} label label of the existing quick reply (text on the button) or its numeric ID
|
||||||
* @param {Object} [args] optional arguments
|
* @param {object} [args] optional arguments
|
||||||
|
* @param {import('../../../slash-commands.js').ExecuteSlashCommandsOptions} [options] optional execution options
|
||||||
*/
|
*/
|
||||||
async executeQuickReply(setName, label, args = {}) {
|
async executeQuickReply(setName, label, args = {}, options = {}) {
|
||||||
const qr = this.getQrByLabel(setName, label);
|
const qr = this.getQrByLabel(setName, label);
|
||||||
if (!qr) {
|
if (!qr) {
|
||||||
throw new Error(`No quick reply with label "${label}" in set "${setName}" found.`);
|
throw new Error(`No quick reply with label "${label}" in set "${setName}" found.`);
|
||||||
}
|
}
|
||||||
return await qr.execute(args);
|
return await qr.execute(args, false, false, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds or removes a quick reply set to the list of globally active quick reply sets.
|
* Adds or removes a quick reply set to the list of globally active quick reply sets.
|
||||||
*
|
*
|
||||||
* @param {String} name the name of the set
|
* @param {string} name the name of the set
|
||||||
* @param {Boolean} isVisible whether to show the set's buttons or not
|
* @param {boolean} isVisible whether to show the set's buttons or not
|
||||||
*/
|
*/
|
||||||
toggleGlobalSet(name, isVisible = true) {
|
toggleGlobalSet(name, isVisible = true) {
|
||||||
const set = this.getSetByName(name);
|
const set = this.getSetByName(name);
|
||||||
|
@ -104,8 +114,8 @@ export class QuickReplyApi {
|
||||||
/**
|
/**
|
||||||
* Adds a quick reply set to the list of globally active quick reply sets.
|
* Adds a quick reply set to the list of globally active quick reply sets.
|
||||||
*
|
*
|
||||||
* @param {String} name the name of the set
|
* @param {string} name the name of the set
|
||||||
* @param {Boolean} isVisible whether to show the set's buttons or not
|
* @param {boolean} isVisible whether to show the set's buttons or not
|
||||||
*/
|
*/
|
||||||
addGlobalSet(name, isVisible = true) {
|
addGlobalSet(name, isVisible = true) {
|
||||||
const set = this.getSetByName(name);
|
const set = this.getSetByName(name);
|
||||||
|
@ -118,7 +128,7 @@ export class QuickReplyApi {
|
||||||
/**
|
/**
|
||||||
* Removes a quick reply set from the list of globally active quick reply sets.
|
* Removes a quick reply set from the list of globally active quick reply sets.
|
||||||
*
|
*
|
||||||
* @param {String} name the name of the set
|
* @param {string} name the name of the set
|
||||||
*/
|
*/
|
||||||
removeGlobalSet(name) {
|
removeGlobalSet(name) {
|
||||||
const set = this.getSetByName(name);
|
const set = this.getSetByName(name);
|
||||||
|
@ -132,8 +142,8 @@ export class QuickReplyApi {
|
||||||
/**
|
/**
|
||||||
* Adds or removes a quick reply set to the list of the current chat's active quick reply sets.
|
* Adds or removes a quick reply set to the list of the current chat's active quick reply sets.
|
||||||
*
|
*
|
||||||
* @param {String} name the name of the set
|
* @param {string} name the name of the set
|
||||||
* @param {Boolean} isVisible whether to show the set's buttons or not
|
* @param {boolean} isVisible whether to show the set's buttons or not
|
||||||
*/
|
*/
|
||||||
toggleChatSet(name, isVisible = true) {
|
toggleChatSet(name, isVisible = true) {
|
||||||
if (!this.settings.chatConfig) return;
|
if (!this.settings.chatConfig) return;
|
||||||
|
@ -151,8 +161,8 @@ export class QuickReplyApi {
|
||||||
/**
|
/**
|
||||||
* Adds a quick reply set to the list of the current chat's active quick reply sets.
|
* Adds a quick reply set to the list of the current chat's active quick reply sets.
|
||||||
*
|
*
|
||||||
* @param {String} name the name of the set
|
* @param {string} name the name of the set
|
||||||
* @param {Boolean} isVisible whether to show the set's buttons or not
|
* @param {boolean} isVisible whether to show the set's buttons or not
|
||||||
*/
|
*/
|
||||||
addChatSet(name, isVisible = true) {
|
addChatSet(name, isVisible = true) {
|
||||||
if (!this.settings.chatConfig) return;
|
if (!this.settings.chatConfig) return;
|
||||||
|
@ -166,7 +176,7 @@ export class QuickReplyApi {
|
||||||
/**
|
/**
|
||||||
* Removes a quick reply set from the list of the current chat's active quick reply sets.
|
* Removes a quick reply set from the list of the current chat's active quick reply sets.
|
||||||
*
|
*
|
||||||
* @param {String} name the name of the set
|
* @param {string} name the name of the set
|
||||||
*/
|
*/
|
||||||
removeChatSet(name) {
|
removeChatSet(name) {
|
||||||
if (!this.settings.chatConfig) return;
|
if (!this.settings.chatConfig) return;
|
||||||
|
@ -181,21 +191,25 @@ export class QuickReplyApi {
|
||||||
/**
|
/**
|
||||||
* Creates a new quick reply in an existing quick reply set.
|
* Creates a new quick reply in an existing quick reply set.
|
||||||
*
|
*
|
||||||
* @param {String} setName name of the quick reply set to insert the new quick reply into
|
* @param {string} setName name of the quick reply set to insert the new quick reply into
|
||||||
* @param {String} label label for the new quick reply (text on the button)
|
* @param {string} label label for the new quick reply (text on the button)
|
||||||
* @param {Object} [props]
|
* @param {object} [props]
|
||||||
* @param {String} [props.message] the message to be sent or slash command to be executed by the new quick reply
|
* @param {string} [props.icon] the icon to show on the QR button
|
||||||
* @param {String} [props.title] the title / tooltip to be shown on the quick reply button
|
* @param {boolean} [props.showLabel] whether to show the label even when an icon is assigned
|
||||||
* @param {Boolean} [props.isHidden] whether to hide or show the button
|
* @param {string} [props.message] the message to be sent or slash command to be executed by the new quick reply
|
||||||
* @param {Boolean} [props.executeOnStartup] whether to execute the quick reply when SillyTavern starts
|
* @param {string} [props.title] the title / tooltip to be shown on the quick reply button
|
||||||
* @param {Boolean} [props.executeOnUser] whether to execute the quick reply after a user has sent a message
|
* @param {boolean} [props.isHidden] whether to hide or show the button
|
||||||
* @param {Boolean} [props.executeOnAi] whether to execute the quick reply after the AI has sent a message
|
* @param {boolean} [props.executeOnStartup] whether to execute the quick reply when SillyTavern starts
|
||||||
* @param {Boolean} [props.executeOnChatChange] whether to execute the quick reply when a new chat is loaded
|
* @param {boolean} [props.executeOnUser] whether to execute the quick reply after a user has sent a message
|
||||||
* @param {Boolean} [props.executeOnGroupMemberDraft] whether to execute the quick reply when a group member is selected
|
* @param {boolean} [props.executeOnAi] whether to execute the quick reply after the AI has sent a message
|
||||||
* @param {String} [props.automationId] when not empty, the quick reply will be executed when the WI with the given automation ID is activated
|
* @param {boolean} [props.executeOnChatChange] whether to execute the quick reply when a new chat is loaded
|
||||||
|
* @param {boolean} [props.executeOnGroupMemberDraft] whether to execute the quick reply when a group member is selected
|
||||||
|
* @param {string} [props.automationId] when not empty, the quick reply will be executed when the WI with the given automation ID is activated
|
||||||
* @returns {QuickReply} the new quick reply
|
* @returns {QuickReply} the new quick reply
|
||||||
*/
|
*/
|
||||||
createQuickReply(setName, label, {
|
createQuickReply(setName, label, {
|
||||||
|
icon,
|
||||||
|
showLabel,
|
||||||
message,
|
message,
|
||||||
title,
|
title,
|
||||||
isHidden,
|
isHidden,
|
||||||
|
@ -212,6 +226,8 @@ export class QuickReplyApi {
|
||||||
}
|
}
|
||||||
const qr = set.addQuickReply();
|
const qr = set.addQuickReply();
|
||||||
qr.label = label ?? '';
|
qr.label = label ?? '';
|
||||||
|
qr.icon = icon ?? '';
|
||||||
|
qr.showLabel = showLabel ?? false;
|
||||||
qr.message = message ?? '';
|
qr.message = message ?? '';
|
||||||
qr.title = title ?? '';
|
qr.title = title ?? '';
|
||||||
qr.isHidden = isHidden ?? false;
|
qr.isHidden = isHidden ?? false;
|
||||||
|
@ -228,22 +244,26 @@ export class QuickReplyApi {
|
||||||
/**
|
/**
|
||||||
* Updates an existing quick reply.
|
* Updates an existing quick reply.
|
||||||
*
|
*
|
||||||
* @param {String} setName name of the existing quick reply set
|
* @param {string} setName name of the existing quick reply set
|
||||||
* @param {String} label label of the existing quick reply (text on the button)
|
* @param {string|number} label label of the existing quick reply (text on the button) or its numeric ID
|
||||||
* @param {Object} [props]
|
* @param {object} [props]
|
||||||
* @param {String} [props.newLabel] new label for quick reply (text on the button)
|
* @param {string} [props.icon] the icon to show on the QR button
|
||||||
* @param {String} [props.message] the message to be sent or slash command to be executed by the quick reply
|
* @param {boolean} [props.showLabel] whether to show the label even when an icon is assigned
|
||||||
* @param {String} [props.title] the title / tooltip to be shown on the quick reply button
|
* @param {string} [props.newLabel] new label for quick reply (text on the button)
|
||||||
* @param {Boolean} [props.isHidden] whether to hide or show the button
|
* @param {string} [props.message] the message to be sent or slash command to be executed by the quick reply
|
||||||
* @param {Boolean} [props.executeOnStartup] whether to execute the quick reply when SillyTavern starts
|
* @param {string} [props.title] the title / tooltip to be shown on the quick reply button
|
||||||
* @param {Boolean} [props.executeOnUser] whether to execute the quick reply after a user has sent a message
|
* @param {boolean} [props.isHidden] whether to hide or show the button
|
||||||
* @param {Boolean} [props.executeOnAi] whether to execute the quick reply after the AI has sent a message
|
* @param {boolean} [props.executeOnStartup] whether to execute the quick reply when SillyTavern starts
|
||||||
* @param {Boolean} [props.executeOnChatChange] whether to execute the quick reply when a new chat is loaded
|
* @param {boolean} [props.executeOnUser] whether to execute the quick reply after a user has sent a message
|
||||||
* @param {Boolean} [props.executeOnGroupMemberDraft] whether to execute the quick reply when a group member is selected
|
* @param {boolean} [props.executeOnAi] whether to execute the quick reply after the AI has sent a message
|
||||||
* @param {String} [props.automationId] when not empty, the quick reply will be executed when the WI with the given automation ID is activated
|
* @param {boolean} [props.executeOnChatChange] whether to execute the quick reply when a new chat is loaded
|
||||||
|
* @param {boolean} [props.executeOnGroupMemberDraft] whether to execute the quick reply when a group member is selected
|
||||||
|
* @param {string} [props.automationId] when not empty, the quick reply will be executed when the WI with the given automation ID is activated
|
||||||
* @returns {QuickReply} the altered quick reply
|
* @returns {QuickReply} the altered quick reply
|
||||||
*/
|
*/
|
||||||
updateQuickReply(setName, label, {
|
updateQuickReply(setName, label, {
|
||||||
|
icon,
|
||||||
|
showLabel,
|
||||||
newLabel,
|
newLabel,
|
||||||
message,
|
message,
|
||||||
title,
|
title,
|
||||||
|
@ -259,6 +279,8 @@ export class QuickReplyApi {
|
||||||
if (!qr) {
|
if (!qr) {
|
||||||
throw new Error(`No quick reply with label "${label}" in set "${setName}" found.`);
|
throw new Error(`No quick reply with label "${label}" in set "${setName}" found.`);
|
||||||
}
|
}
|
||||||
|
qr.updateIcon(icon ?? qr.icon);
|
||||||
|
qr.updateShowLabel(showLabel ?? qr.showLabel);
|
||||||
qr.updateLabel(newLabel ?? qr.label);
|
qr.updateLabel(newLabel ?? qr.label);
|
||||||
qr.updateMessage(message ?? qr.message);
|
qr.updateMessage(message ?? qr.message);
|
||||||
qr.updateTitle(title ?? qr.title);
|
qr.updateTitle(title ?? qr.title);
|
||||||
|
@ -276,8 +298,8 @@ export class QuickReplyApi {
|
||||||
/**
|
/**
|
||||||
* Deletes an existing quick reply.
|
* Deletes an existing quick reply.
|
||||||
*
|
*
|
||||||
* @param {String} setName name of the existing quick reply set
|
* @param {string} setName name of the existing quick reply set
|
||||||
* @param {String} label label of the existing quick reply (text on the button)
|
* @param {string|number} label label of the existing quick reply (text on the button) or its numeric ID
|
||||||
*/
|
*/
|
||||||
deleteQuickReply(setName, label) {
|
deleteQuickReply(setName, label) {
|
||||||
const qr = this.getQrByLabel(setName, label);
|
const qr = this.getQrByLabel(setName, label);
|
||||||
|
@ -291,10 +313,10 @@ export class QuickReplyApi {
|
||||||
/**
|
/**
|
||||||
* Adds an existing quick reply set as a context menu to an existing quick reply.
|
* Adds an existing quick reply set as a context menu to an existing quick reply.
|
||||||
*
|
*
|
||||||
* @param {String} setName name of the existing quick reply set containing the quick reply
|
* @param {string} setName name of the existing quick reply set containing the quick reply
|
||||||
* @param {String} label label of the existing quick reply
|
* @param {string|number} label label of the existing quick reply or its numeric ID
|
||||||
* @param {String} contextSetName name of the existing quick reply set to be used as a context menu
|
* @param {string} contextSetName name of the existing quick reply set to be used as a context menu
|
||||||
* @param {Boolean} isChained whether or not to chain the context menu quick replies
|
* @param {boolean} isChained whether or not to chain the context menu quick replies
|
||||||
*/
|
*/
|
||||||
createContextItem(setName, label, contextSetName, isChained = false) {
|
createContextItem(setName, label, contextSetName, isChained = false) {
|
||||||
const qr = this.getQrByLabel(setName, label);
|
const qr = this.getQrByLabel(setName, label);
|
||||||
|
@ -314,9 +336,9 @@ export class QuickReplyApi {
|
||||||
/**
|
/**
|
||||||
* Removes a quick reply set from a quick reply's context menu.
|
* Removes a quick reply set from a quick reply's context menu.
|
||||||
*
|
*
|
||||||
* @param {String} setName name of the existing quick reply set containing the quick reply
|
* @param {string} setName name of the existing quick reply set containing the quick reply
|
||||||
* @param {String} label label of the existing quick reply
|
* @param {string|number} label label of the existing quick reply or its numeric ID
|
||||||
* @param {String} contextSetName name of the existing quick reply set to be used as a context menu
|
* @param {string} contextSetName name of the existing quick reply set to be used as a context menu
|
||||||
*/
|
*/
|
||||||
deleteContextItem(setName, label, contextSetName) {
|
deleteContextItem(setName, label, contextSetName) {
|
||||||
const qr = this.getQrByLabel(setName, label);
|
const qr = this.getQrByLabel(setName, label);
|
||||||
|
@ -333,8 +355,8 @@ export class QuickReplyApi {
|
||||||
/**
|
/**
|
||||||
* Removes all entries from a quick reply's context menu.
|
* Removes all entries from a quick reply's context menu.
|
||||||
*
|
*
|
||||||
* @param {String} setName name of the existing quick reply set containing the quick reply
|
* @param {string} setName name of the existing quick reply set containing the quick reply
|
||||||
* @param {String} label label of the existing quick reply
|
* @param {string|number} label label of the existing quick reply or its numeric ID
|
||||||
*/
|
*/
|
||||||
clearContextMenu(setName, label) {
|
clearContextMenu(setName, label) {
|
||||||
const qr = this.getQrByLabel(setName, label);
|
const qr = this.getQrByLabel(setName, label);
|
||||||
|
@ -348,11 +370,11 @@ export class QuickReplyApi {
|
||||||
/**
|
/**
|
||||||
* Create a new quick reply set.
|
* Create a new quick reply set.
|
||||||
*
|
*
|
||||||
* @param {String} name name of the new quick reply set
|
* @param {string} name name of the new quick reply set
|
||||||
* @param {Object} [props]
|
* @param {object} [props]
|
||||||
* @param {Boolean} [props.disableSend] whether or not to send the quick replies or put the message or slash command into the char input box
|
* @param {boolean} [props.disableSend] whether or not to send the quick replies or put the message or slash command into the char input box
|
||||||
* @param {Boolean} [props.placeBeforeInput] whether or not to place the quick reply contents before the existing user input
|
* @param {boolean} [props.placeBeforeInput] whether or not to place the quick reply contents before the existing user input
|
||||||
* @param {Boolean} [props.injectInput] whether or not to automatically inject the user input at the end of the quick reply
|
* @param {boolean} [props.injectInput] whether or not to automatically inject the user input at the end of the quick reply
|
||||||
* @returns {Promise<QuickReplySet>} the new quick reply set
|
* @returns {Promise<QuickReplySet>} the new quick reply set
|
||||||
*/
|
*/
|
||||||
async createSet(name, {
|
async createSet(name, {
|
||||||
|
@ -384,11 +406,11 @@ export class QuickReplyApi {
|
||||||
/**
|
/**
|
||||||
* Update an existing quick reply set.
|
* Update an existing quick reply set.
|
||||||
*
|
*
|
||||||
* @param {String} name name of the existing quick reply set
|
* @param {string} name name of the existing quick reply set
|
||||||
* @param {Object} [props]
|
* @param {object} [props]
|
||||||
* @param {Boolean} [props.disableSend] whether or not to send the quick replies or put the message or slash command into the char input box
|
* @param {boolean} [props.disableSend] whether or not to send the quick replies or put the message or slash command into the char input box
|
||||||
* @param {Boolean} [props.placeBeforeInput] whether or not to place the quick reply contents before the existing user input
|
* @param {boolean} [props.placeBeforeInput] whether or not to place the quick reply contents before the existing user input
|
||||||
* @param {Boolean} [props.injectInput] whether or not to automatically inject the user input at the end of the quick reply
|
* @param {boolean} [props.injectInput] whether or not to automatically inject the user input at the end of the quick reply
|
||||||
* @returns {Promise<QuickReplySet>} the altered quick reply set
|
* @returns {Promise<QuickReplySet>} the altered quick reply set
|
||||||
*/
|
*/
|
||||||
async updateSet(name, {
|
async updateSet(name, {
|
||||||
|
@ -411,7 +433,7 @@ export class QuickReplyApi {
|
||||||
/**
|
/**
|
||||||
* Delete an existing quick reply set.
|
* Delete an existing quick reply set.
|
||||||
*
|
*
|
||||||
* @param {String} name name of the existing quick reply set
|
* @param {string} name name of the existing quick reply set
|
||||||
*/
|
*/
|
||||||
async deleteSet(name) {
|
async deleteSet(name) {
|
||||||
const set = this.getSetByName(name);
|
const set = this.getSetByName(name);
|
||||||
|
@ -451,7 +473,7 @@ export class QuickReplyApi {
|
||||||
/**
|
/**
|
||||||
* Gets a list of all quick replies in the quick reply set.
|
* Gets a list of all quick replies in the quick reply set.
|
||||||
*
|
*
|
||||||
* @param {String} setName name of the existing quick reply set
|
* @param {string} setName name of the existing quick reply set
|
||||||
* @returns array with the labels of this set's quick replies
|
* @returns array with the labels of this set's quick replies
|
||||||
*/
|
*/
|
||||||
listQuickReplies(setName) {
|
listQuickReplies(setName) {
|
||||||
|
|
|
@ -2,10 +2,23 @@
|
||||||
<div id="qr--main">
|
<div id="qr--main">
|
||||||
<h3 data-i18n="Labels and Message">Labels and Message</h3>
|
<h3 data-i18n="Labels and Message">Labels and Message</h3>
|
||||||
<div class="qr--labels">
|
<div class="qr--labels">
|
||||||
<label>
|
<label class="qr--fit">
|
||||||
<span class="qr--labelText" data-i18n="Label">Label</span>
|
<span class="qr--labelText" data-i18n="Label">Icon</span>
|
||||||
<input type="text" class="text_pole" id="qr--modal-label">
|
<small class="qr--labelHint"> </small>
|
||||||
|
<div class="menu_button fa-fw" id="qr--modal-icon" title="Click to change icon"></div>
|
||||||
</label>
|
</label>
|
||||||
|
<div class="label">
|
||||||
|
<span class="qr--labelText" data-i18n="Label">Label</span>
|
||||||
|
<small class="qr--labelHint" data-i18n="(label of the button, if no icon is chosen) ">(label of the button, if no icon is chosen)</small>
|
||||||
|
<div class="qr--inputGroup">
|
||||||
|
<label class="checkbox_label" title="Show label even if an icon is assigned">
|
||||||
|
<input type="checkbox" id="qr--modal-showLabel">
|
||||||
|
Show
|
||||||
|
</label>
|
||||||
|
<input type="text" class="text_pole" id="qr--modal-label">
|
||||||
|
<div class="menu_button fa-fw fa-solid fa-chevron-down" id="qr--modal-switcher" title="Switch to another QR"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<label>
|
<label>
|
||||||
<span class="qr--labelText" data-i18n="Title">Title</span>
|
<span class="qr--labelText" data-i18n="Title">Title</span>
|
||||||
<small class="qr--labelHint" data-i18n="(tooltip, leave empty to show message or /command)">(tooltip, leave empty to show message or /command)</small>
|
<small class="qr--labelHint" data-i18n="(tooltip, leave empty to show message or /command)">(tooltip, leave empty to show message or /command)</small>
|
||||||
|
@ -33,6 +46,8 @@
|
||||||
<input type="checkbox" id="qr--modal-syntax">
|
<input type="checkbox" id="qr--modal-syntax">
|
||||||
<span>Syntax highlight</span>
|
<span>Syntax highlight</span>
|
||||||
</label>
|
</label>
|
||||||
|
<small>Ctrl+Alt+Click (or F9) to set / remove breakpoints</small>
|
||||||
|
<small>Ctrl+<span id="qr--modal-commentKey"></span> to toggle block comments</small>
|
||||||
</div>
|
</div>
|
||||||
<div id="qr--modal-messageHolder">
|
<div id="qr--modal-messageHolder">
|
||||||
<pre id="qr--modal-messageSyntax"><code id="qr--modal-messageSyntaxInner" class="hljs language-stscript"></code></pre>
|
<pre id="qr--modal-messageSyntax"><code id="qr--modal-messageSyntaxInner" class="hljs language-stscript"></code></pre>
|
||||||
|
@ -43,6 +58,10 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div id="qr--resizeHandle"></div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div id="qr--qrOptions">
|
<div id="qr--qrOptions">
|
||||||
<h3 data-i18n="Context Menu">Context Menu</h3>
|
<h3 data-i18n="Context Menu">Context Menu</h3>
|
||||||
<div id="qr--ctxEditor">
|
<div id="qr--ctxEditor">
|
||||||
|
@ -64,7 +83,7 @@
|
||||||
|
|
||||||
|
|
||||||
<h3 data-i18n="Auto-Execute">Auto-Execute</h3>
|
<h3 data-i18n="Auto-Execute">Auto-Execute</h3>
|
||||||
<div class="flex-container flexFlowColumn">
|
<div id="qr--autoExec" class="flex-container flexFlowColumn">
|
||||||
<label class="checkbox_label" title="Prevent this quick reply from triggering other auto-executed quick replies while auto-executing (i.e., prevent recursive auto-execution)">
|
<label class="checkbox_label" title="Prevent this quick reply from triggering other auto-executed quick replies while auto-executing (i.e., prevent recursive auto-execution)">
|
||||||
<input type="checkbox" id="qr--preventAutoExecute" >
|
<input type="checkbox" id="qr--preventAutoExecute" >
|
||||||
<span><i class="fa-solid fa-fw fa-plane-slash"></i><span data-i18n="Don't trigger auto-execute">Don't trigger auto-execute</span></span>
|
<span><i class="fa-solid fa-fw fa-plane-slash"></i><span data-i18n="Don't trigger auto-execute">Don't trigger auto-execute</span></span>
|
||||||
|
@ -117,11 +136,18 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="qr--modal-executeProgress"></div>
|
<div id="qr--modal-executeProgress"></div>
|
||||||
<label class="checkbox_label">
|
|
||||||
<input type="checkbox" id="qr--modal-executeHide">
|
|
||||||
<span title="Hide editor while executing"> Hide editor while executing</span>
|
|
||||||
</label>
|
|
||||||
<div id="qr--modal-executeErrors"></div>
|
<div id="qr--modal-executeErrors"></div>
|
||||||
<div id="qr--modal-executeResult"></div>
|
<div id="qr--modal-executeResult"></div>
|
||||||
|
|
||||||
|
<div id="qr--modal-debugButtons">
|
||||||
|
<div title="Resume" id="qr--modal-resume" class="qr--modal-debugButton menu_button"></div>
|
||||||
|
<div title="Step Over" id="qr--modal-step" class="qr--modal-debugButton menu_button"></div>
|
||||||
|
<div title="Step Into" id="qr--modal-stepInto" class="qr--modal-debugButton menu_button"></div>
|
||||||
|
<div title="Step Out" id="qr--modal-stepOut" class="qr--modal-debugButton menu_button"></div>
|
||||||
|
<div title="Minimize" id="qr--modal-minimize" class="qr--modal-debugButton menu_button fa-solid fa-minimize"></div>
|
||||||
|
<div title="Maximize" id="qr--modal-maximize" class="qr--modal-debugButton menu_button fa-solid fa-maximize"></div>
|
||||||
|
</div>
|
||||||
|
<textarea rows="1" id="qr--modal-send_textarea" placeholder="Chat input for use with {{input}}" title="Chat input for use with {{input}}"></textarea>
|
||||||
|
<div id="qr--modal-debugState"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -60,10 +60,20 @@
|
||||||
<label class="flex-container" id="qr--injectInputContainer">
|
<label class="flex-container" id="qr--injectInputContainer">
|
||||||
<input type="checkbox" id="qr--injectInput"> <span><span data-i18n="Inject user input automatically">Inject user input automatically</span> <small><span data-i18n="(if disabled, use ">(if disabled, use</span><code>{{input}}</code> <span data-i18n="macro for manual injection)">macro for manual injection)</span></small></span>
|
<input type="checkbox" id="qr--injectInput"> <span><span data-i18n="Inject user input automatically">Inject user input automatically</span> <small><span data-i18n="(if disabled, use ">(if disabled, use</span><code>{{input}}</code> <span data-i18n="macro for manual injection)">macro for manual injection)</span></small></span>
|
||||||
</label>
|
</label>
|
||||||
|
<div class="flex-container alignItemsCenter">
|
||||||
|
<toolcool-color-picker id="qr--color"></toolcool-color-picker>
|
||||||
|
<div class="menu_button" id="qr--colorClear">Clear</div>
|
||||||
|
<span data-i18n="Color">Color</span>
|
||||||
|
</div>
|
||||||
|
<label class="flex-container" id="qr--onlyBorderColorContainer">
|
||||||
|
<input type="checkbox" id="qr--onlyBorderColor"> <span data-i18n="Only apply color as accent">Only apply color as accent</span>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div id="qr--set-qrList" class="qr--qrList"></div>
|
<div id="qr--set-qrList" class="qr--qrList"></div>
|
||||||
<div class="qr--set-qrListActions">
|
<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 class="qr--add menu_button menu_button_icon fa-solid fa-plus" id="qr--set-add" title="Add quick reply"></div>
|
||||||
|
<div class="qr--paste menu_button menu_button_icon fa-solid fa-paste" id="qr--set-paste" title="Paste quick reply from clipboard"></div>
|
||||||
|
<div class="qr--import menu_button menu_button_icon fa-solid fa-file-import" id="qr--set-importQr" title="Import quick reply from file"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -176,7 +176,7 @@ const init = async () => {
|
||||||
buttons.show();
|
buttons.show();
|
||||||
settings.onSave = ()=>buttons.refresh();
|
settings.onSave = ()=>buttons.refresh();
|
||||||
|
|
||||||
window['executeQuickReplyByName'] = async(name, args = {}) => {
|
window['executeQuickReplyByName'] = async(name, args = {}, options = {}) => {
|
||||||
let qr = [...settings.config.setList, ...(settings.chatConfig?.setList ?? [])]
|
let qr = [...settings.config.setList, ...(settings.chatConfig?.setList ?? [])]
|
||||||
.map(it=>it.set.qrList)
|
.map(it=>it.set.qrList)
|
||||||
.flat()
|
.flat()
|
||||||
|
@ -191,7 +191,7 @@ const init = async () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (qr && qr.onExecute) {
|
if (qr && qr.onExecute) {
|
||||||
return await qr.execute(args, false, true);
|
return await qr.execute(args, false, true, options);
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`No Quick Reply found for "${name}".`);
|
throw new Error(`No Quick Reply found for "${name}".`);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,769 @@
|
||||||
|
var DOCUMENT_FRAGMENT_NODE = 11;
|
||||||
|
|
||||||
|
function morphAttrs(fromNode, toNode) {
|
||||||
|
var toNodeAttrs = toNode.attributes;
|
||||||
|
var attr;
|
||||||
|
var attrName;
|
||||||
|
var attrNamespaceURI;
|
||||||
|
var attrValue;
|
||||||
|
var fromValue;
|
||||||
|
|
||||||
|
// document-fragments dont have attributes so lets not do anything
|
||||||
|
if (toNode.nodeType === DOCUMENT_FRAGMENT_NODE || fromNode.nodeType === DOCUMENT_FRAGMENT_NODE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update attributes on original DOM element
|
||||||
|
for (var i = toNodeAttrs.length - 1; i >= 0; i--) {
|
||||||
|
attr = toNodeAttrs[i];
|
||||||
|
attrName = attr.name;
|
||||||
|
attrNamespaceURI = attr.namespaceURI;
|
||||||
|
attrValue = attr.value;
|
||||||
|
|
||||||
|
if (attrNamespaceURI) {
|
||||||
|
attrName = attr.localName || attrName;
|
||||||
|
fromValue = fromNode.getAttributeNS(attrNamespaceURI, attrName);
|
||||||
|
|
||||||
|
if (fromValue !== attrValue) {
|
||||||
|
if (attr.prefix === 'xmlns'){
|
||||||
|
attrName = attr.name; // It's not allowed to set an attribute with the XMLNS namespace without specifying the `xmlns` prefix
|
||||||
|
}
|
||||||
|
fromNode.setAttributeNS(attrNamespaceURI, attrName, attrValue);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fromValue = fromNode.getAttribute(attrName);
|
||||||
|
|
||||||
|
if (fromValue !== attrValue) {
|
||||||
|
fromNode.setAttribute(attrName, attrValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove any extra attributes found on the original DOM element that
|
||||||
|
// weren't found on the target element.
|
||||||
|
var fromNodeAttrs = fromNode.attributes;
|
||||||
|
|
||||||
|
for (var d = fromNodeAttrs.length - 1; d >= 0; d--) {
|
||||||
|
attr = fromNodeAttrs[d];
|
||||||
|
attrName = attr.name;
|
||||||
|
attrNamespaceURI = attr.namespaceURI;
|
||||||
|
|
||||||
|
if (attrNamespaceURI) {
|
||||||
|
attrName = attr.localName || attrName;
|
||||||
|
|
||||||
|
if (!toNode.hasAttributeNS(attrNamespaceURI, attrName)) {
|
||||||
|
fromNode.removeAttributeNS(attrNamespaceURI, attrName);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!toNode.hasAttribute(attrName)) {
|
||||||
|
fromNode.removeAttribute(attrName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var range; // Create a range object for efficently rendering strings to elements.
|
||||||
|
var NS_XHTML = 'http://www.w3.org/1999/xhtml';
|
||||||
|
|
||||||
|
var doc = typeof document === 'undefined' ? undefined : document;
|
||||||
|
var HAS_TEMPLATE_SUPPORT = !!doc && 'content' in doc.createElement('template');
|
||||||
|
var HAS_RANGE_SUPPORT = !!doc && doc.createRange && 'createContextualFragment' in doc.createRange();
|
||||||
|
|
||||||
|
function createFragmentFromTemplate(str) {
|
||||||
|
var template = doc.createElement('template');
|
||||||
|
template.innerHTML = str;
|
||||||
|
return template.content.childNodes[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
function createFragmentFromRange(str) {
|
||||||
|
if (!range) {
|
||||||
|
range = doc.createRange();
|
||||||
|
range.selectNode(doc.body);
|
||||||
|
}
|
||||||
|
|
||||||
|
var fragment = range.createContextualFragment(str);
|
||||||
|
return fragment.childNodes[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
function createFragmentFromWrap(str) {
|
||||||
|
var fragment = doc.createElement('body');
|
||||||
|
fragment.innerHTML = str;
|
||||||
|
return fragment.childNodes[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is about the same
|
||||||
|
* var html = new DOMParser().parseFromString(str, 'text/html');
|
||||||
|
* return html.body.firstChild;
|
||||||
|
*
|
||||||
|
* @method toElement
|
||||||
|
* @param {String} str
|
||||||
|
*/
|
||||||
|
function toElement(str) {
|
||||||
|
str = str.trim();
|
||||||
|
if (HAS_TEMPLATE_SUPPORT) {
|
||||||
|
// avoid restrictions on content for things like `<tr><th>Hi</th></tr>` which
|
||||||
|
// createContextualFragment doesn't support
|
||||||
|
// <template> support not available in IE
|
||||||
|
return createFragmentFromTemplate(str);
|
||||||
|
} else if (HAS_RANGE_SUPPORT) {
|
||||||
|
return createFragmentFromRange(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
return createFragmentFromWrap(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if two node's names are the same.
|
||||||
|
*
|
||||||
|
* NOTE: We don't bother checking `namespaceURI` because you will never find two HTML elements with the same
|
||||||
|
* nodeName and different namespace URIs.
|
||||||
|
*
|
||||||
|
* @param {Element} a
|
||||||
|
* @param {Element} b The target element
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
function compareNodeNames(fromEl, toEl) {
|
||||||
|
var fromNodeName = fromEl.nodeName;
|
||||||
|
var toNodeName = toEl.nodeName;
|
||||||
|
var fromCodeStart, toCodeStart;
|
||||||
|
|
||||||
|
if (fromNodeName === toNodeName) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fromCodeStart = fromNodeName.charCodeAt(0);
|
||||||
|
toCodeStart = toNodeName.charCodeAt(0);
|
||||||
|
|
||||||
|
// If the target element is a virtual DOM node or SVG node then we may
|
||||||
|
// need to normalize the tag name before comparing. Normal HTML elements that are
|
||||||
|
// in the "http://www.w3.org/1999/xhtml"
|
||||||
|
// are converted to upper case
|
||||||
|
if (fromCodeStart <= 90 && toCodeStart >= 97) { // from is upper and to is lower
|
||||||
|
return fromNodeName === toNodeName.toUpperCase();
|
||||||
|
} else if (toCodeStart <= 90 && fromCodeStart >= 97) { // to is upper and from is lower
|
||||||
|
return toNodeName === fromNodeName.toUpperCase();
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an element, optionally with a known namespace URI.
|
||||||
|
*
|
||||||
|
* @param {string} name the element name, e.g. 'div' or 'svg'
|
||||||
|
* @param {string} [namespaceURI] the element's namespace URI, i.e. the value of
|
||||||
|
* its `xmlns` attribute or its inferred namespace.
|
||||||
|
*
|
||||||
|
* @return {Element}
|
||||||
|
*/
|
||||||
|
function createElementNS(name, namespaceURI) {
|
||||||
|
return !namespaceURI || namespaceURI === NS_XHTML ?
|
||||||
|
doc.createElement(name) :
|
||||||
|
doc.createElementNS(namespaceURI, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copies the children of one DOM element to another DOM element
|
||||||
|
*/
|
||||||
|
function moveChildren(fromEl, toEl) {
|
||||||
|
var curChild = fromEl.firstChild;
|
||||||
|
while (curChild) {
|
||||||
|
var nextChild = curChild.nextSibling;
|
||||||
|
toEl.appendChild(curChild);
|
||||||
|
curChild = nextChild;
|
||||||
|
}
|
||||||
|
return toEl;
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncBooleanAttrProp(fromEl, toEl, name) {
|
||||||
|
if (fromEl[name] !== toEl[name]) {
|
||||||
|
fromEl[name] = toEl[name];
|
||||||
|
if (fromEl[name]) {
|
||||||
|
fromEl.setAttribute(name, '');
|
||||||
|
} else {
|
||||||
|
fromEl.removeAttribute(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var specialElHandlers = {
|
||||||
|
OPTION: function(fromEl, toEl) {
|
||||||
|
var parentNode = fromEl.parentNode;
|
||||||
|
if (parentNode) {
|
||||||
|
var parentName = parentNode.nodeName.toUpperCase();
|
||||||
|
if (parentName === 'OPTGROUP') {
|
||||||
|
parentNode = parentNode.parentNode;
|
||||||
|
parentName = parentNode && parentNode.nodeName.toUpperCase();
|
||||||
|
}
|
||||||
|
if (parentName === 'SELECT' && !parentNode.hasAttribute('multiple')) {
|
||||||
|
if (fromEl.hasAttribute('selected') && !toEl.selected) {
|
||||||
|
// Workaround for MS Edge bug where the 'selected' attribute can only be
|
||||||
|
// removed if set to a non-empty value:
|
||||||
|
// https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/12087679/
|
||||||
|
fromEl.setAttribute('selected', 'selected');
|
||||||
|
fromEl.removeAttribute('selected');
|
||||||
|
}
|
||||||
|
// We have to reset select element's selectedIndex to -1, otherwise setting
|
||||||
|
// fromEl.selected using the syncBooleanAttrProp below has no effect.
|
||||||
|
// The correct selectedIndex will be set in the SELECT special handler below.
|
||||||
|
parentNode.selectedIndex = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
syncBooleanAttrProp(fromEl, toEl, 'selected');
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* The "value" attribute is special for the <input> element since it sets
|
||||||
|
* the initial value. Changing the "value" attribute without changing the
|
||||||
|
* "value" property will have no effect since it is only used to the set the
|
||||||
|
* initial value. Similar for the "checked" attribute, and "disabled".
|
||||||
|
*/
|
||||||
|
INPUT: function(fromEl, toEl) {
|
||||||
|
syncBooleanAttrProp(fromEl, toEl, 'checked');
|
||||||
|
syncBooleanAttrProp(fromEl, toEl, 'disabled');
|
||||||
|
|
||||||
|
if (fromEl.value !== toEl.value) {
|
||||||
|
fromEl.value = toEl.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!toEl.hasAttribute('value')) {
|
||||||
|
fromEl.removeAttribute('value');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
TEXTAREA: function(fromEl, toEl) {
|
||||||
|
var newValue = toEl.value;
|
||||||
|
if (fromEl.value !== newValue) {
|
||||||
|
fromEl.value = newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var firstChild = fromEl.firstChild;
|
||||||
|
if (firstChild) {
|
||||||
|
// Needed for IE. Apparently IE sets the placeholder as the
|
||||||
|
// node value and vise versa. This ignores an empty update.
|
||||||
|
var oldValue = firstChild.nodeValue;
|
||||||
|
|
||||||
|
if (oldValue == newValue || (!newValue && oldValue == fromEl.placeholder)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
firstChild.nodeValue = newValue;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
SELECT: function(fromEl, toEl) {
|
||||||
|
if (!toEl.hasAttribute('multiple')) {
|
||||||
|
var selectedIndex = -1;
|
||||||
|
var i = 0;
|
||||||
|
// We have to loop through children of fromEl, not toEl since nodes can be moved
|
||||||
|
// from toEl to fromEl directly when morphing.
|
||||||
|
// At the time this special handler is invoked, all children have already been morphed
|
||||||
|
// and appended to / removed from fromEl, so using fromEl here is safe and correct.
|
||||||
|
var curChild = fromEl.firstChild;
|
||||||
|
var optgroup;
|
||||||
|
var nodeName;
|
||||||
|
while(curChild) {
|
||||||
|
nodeName = curChild.nodeName && curChild.nodeName.toUpperCase();
|
||||||
|
if (nodeName === 'OPTGROUP') {
|
||||||
|
optgroup = curChild;
|
||||||
|
curChild = optgroup.firstChild;
|
||||||
|
} else {
|
||||||
|
if (nodeName === 'OPTION') {
|
||||||
|
if (curChild.hasAttribute('selected')) {
|
||||||
|
selectedIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
curChild = curChild.nextSibling;
|
||||||
|
if (!curChild && optgroup) {
|
||||||
|
curChild = optgroup.nextSibling;
|
||||||
|
optgroup = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fromEl.selectedIndex = selectedIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var ELEMENT_NODE = 1;
|
||||||
|
var DOCUMENT_FRAGMENT_NODE$1 = 11;
|
||||||
|
var TEXT_NODE = 3;
|
||||||
|
var COMMENT_NODE = 8;
|
||||||
|
|
||||||
|
function noop() {}
|
||||||
|
|
||||||
|
function defaultGetNodeKey(node) {
|
||||||
|
if (node) {
|
||||||
|
return (node.getAttribute && node.getAttribute('id')) || node.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function morphdomFactory(morphAttrs) {
|
||||||
|
|
||||||
|
return function morphdom(fromNode, toNode, options) {
|
||||||
|
if (!options) {
|
||||||
|
options = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof toNode === 'string') {
|
||||||
|
if (fromNode.nodeName === '#document' || fromNode.nodeName === 'HTML' || fromNode.nodeName === 'BODY') {
|
||||||
|
var toNodeHtml = toNode;
|
||||||
|
toNode = doc.createElement('html');
|
||||||
|
toNode.innerHTML = toNodeHtml;
|
||||||
|
} else {
|
||||||
|
toNode = toElement(toNode);
|
||||||
|
}
|
||||||
|
} else if (toNode.nodeType === DOCUMENT_FRAGMENT_NODE$1) {
|
||||||
|
toNode = toNode.firstElementChild;
|
||||||
|
}
|
||||||
|
|
||||||
|
var getNodeKey = options.getNodeKey || defaultGetNodeKey;
|
||||||
|
var onBeforeNodeAdded = options.onBeforeNodeAdded || noop;
|
||||||
|
var onNodeAdded = options.onNodeAdded || noop;
|
||||||
|
var onBeforeElUpdated = options.onBeforeElUpdated || noop;
|
||||||
|
var onElUpdated = options.onElUpdated || noop;
|
||||||
|
var onBeforeNodeDiscarded = options.onBeforeNodeDiscarded || noop;
|
||||||
|
var onNodeDiscarded = options.onNodeDiscarded || noop;
|
||||||
|
var onBeforeElChildrenUpdated = options.onBeforeElChildrenUpdated || noop;
|
||||||
|
var skipFromChildren = options.skipFromChildren || noop;
|
||||||
|
var addChild = options.addChild || function(parent, child){ return parent.appendChild(child); };
|
||||||
|
var childrenOnly = options.childrenOnly === true;
|
||||||
|
|
||||||
|
// This object is used as a lookup to quickly find all keyed elements in the original DOM tree.
|
||||||
|
var fromNodesLookup = Object.create(null);
|
||||||
|
var keyedRemovalList = [];
|
||||||
|
|
||||||
|
function addKeyedRemoval(key) {
|
||||||
|
keyedRemovalList.push(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
function walkDiscardedChildNodes(node, skipKeyedNodes) {
|
||||||
|
if (node.nodeType === ELEMENT_NODE) {
|
||||||
|
var curChild = node.firstChild;
|
||||||
|
while (curChild) {
|
||||||
|
|
||||||
|
var key = undefined;
|
||||||
|
|
||||||
|
if (skipKeyedNodes && (key = getNodeKey(curChild))) {
|
||||||
|
// If we are skipping keyed nodes then we add the key
|
||||||
|
// to a list so that it can be handled at the very end.
|
||||||
|
addKeyedRemoval(key);
|
||||||
|
} else {
|
||||||
|
// Only report the node as discarded if it is not keyed. We do this because
|
||||||
|
// at the end we loop through all keyed elements that were unmatched
|
||||||
|
// and then discard them in one final pass.
|
||||||
|
onNodeDiscarded(curChild);
|
||||||
|
if (curChild.firstChild) {
|
||||||
|
walkDiscardedChildNodes(curChild, skipKeyedNodes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
curChild = curChild.nextSibling;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a DOM node out of the original DOM
|
||||||
|
*
|
||||||
|
* @param {Node} node The node to remove
|
||||||
|
* @param {Node} parentNode The nodes parent
|
||||||
|
* @param {Boolean} skipKeyedNodes If true then elements with keys will be skipped and not discarded.
|
||||||
|
* @return {undefined}
|
||||||
|
*/
|
||||||
|
function removeNode(node, parentNode, skipKeyedNodes) {
|
||||||
|
if (onBeforeNodeDiscarded(node) === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parentNode) {
|
||||||
|
parentNode.removeChild(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
onNodeDiscarded(node);
|
||||||
|
walkDiscardedChildNodes(node, skipKeyedNodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// // TreeWalker implementation is no faster, but keeping this around in case this changes in the future
|
||||||
|
// function indexTree(root) {
|
||||||
|
// var treeWalker = document.createTreeWalker(
|
||||||
|
// root,
|
||||||
|
// NodeFilter.SHOW_ELEMENT);
|
||||||
|
//
|
||||||
|
// var el;
|
||||||
|
// while((el = treeWalker.nextNode())) {
|
||||||
|
// var key = getNodeKey(el);
|
||||||
|
// if (key) {
|
||||||
|
// fromNodesLookup[key] = el;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // NodeIterator implementation is no faster, but keeping this around in case this changes in the future
|
||||||
|
//
|
||||||
|
// function indexTree(node) {
|
||||||
|
// var nodeIterator = document.createNodeIterator(node, NodeFilter.SHOW_ELEMENT);
|
||||||
|
// var el;
|
||||||
|
// while((el = nodeIterator.nextNode())) {
|
||||||
|
// var key = getNodeKey(el);
|
||||||
|
// if (key) {
|
||||||
|
// fromNodesLookup[key] = el;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
function indexTree(node) {
|
||||||
|
if (node.nodeType === ELEMENT_NODE || node.nodeType === DOCUMENT_FRAGMENT_NODE$1) {
|
||||||
|
var curChild = node.firstChild;
|
||||||
|
while (curChild) {
|
||||||
|
var key = getNodeKey(curChild);
|
||||||
|
if (key) {
|
||||||
|
fromNodesLookup[key] = curChild;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walk recursively
|
||||||
|
indexTree(curChild);
|
||||||
|
|
||||||
|
curChild = curChild.nextSibling;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
indexTree(fromNode);
|
||||||
|
|
||||||
|
function handleNodeAdded(el) {
|
||||||
|
onNodeAdded(el);
|
||||||
|
|
||||||
|
var curChild = el.firstChild;
|
||||||
|
while (curChild) {
|
||||||
|
var nextSibling = curChild.nextSibling;
|
||||||
|
|
||||||
|
var key = getNodeKey(curChild);
|
||||||
|
if (key) {
|
||||||
|
var unmatchedFromEl = fromNodesLookup[key];
|
||||||
|
// if we find a duplicate #id node in cache, replace `el` with cache value
|
||||||
|
// and morph it to the child node.
|
||||||
|
if (unmatchedFromEl && compareNodeNames(curChild, unmatchedFromEl)) {
|
||||||
|
curChild.parentNode.replaceChild(unmatchedFromEl, curChild);
|
||||||
|
morphEl(unmatchedFromEl, curChild);
|
||||||
|
} else {
|
||||||
|
handleNodeAdded(curChild);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// recursively call for curChild and it's children to see if we find something in
|
||||||
|
// fromNodesLookup
|
||||||
|
handleNodeAdded(curChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
curChild = nextSibling;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanupFromEl(fromEl, curFromNodeChild, curFromNodeKey) {
|
||||||
|
// We have processed all of the "to nodes". If curFromNodeChild is
|
||||||
|
// non-null then we still have some from nodes left over that need
|
||||||
|
// to be removed
|
||||||
|
while (curFromNodeChild) {
|
||||||
|
var fromNextSibling = curFromNodeChild.nextSibling;
|
||||||
|
if ((curFromNodeKey = getNodeKey(curFromNodeChild))) {
|
||||||
|
// Since the node is keyed it might be matched up later so we defer
|
||||||
|
// the actual removal to later
|
||||||
|
addKeyedRemoval(curFromNodeKey);
|
||||||
|
} else {
|
||||||
|
// NOTE: we skip nested keyed nodes from being removed since there is
|
||||||
|
// still a chance they will be matched up later
|
||||||
|
removeNode(curFromNodeChild, fromEl, true /* skip keyed nodes */);
|
||||||
|
}
|
||||||
|
curFromNodeChild = fromNextSibling;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function morphEl(fromEl, toEl, childrenOnly) {
|
||||||
|
var toElKey = getNodeKey(toEl);
|
||||||
|
|
||||||
|
if (toElKey) {
|
||||||
|
// If an element with an ID is being morphed then it will be in the final
|
||||||
|
// DOM so clear it out of the saved elements collection
|
||||||
|
delete fromNodesLookup[toElKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!childrenOnly) {
|
||||||
|
// optional
|
||||||
|
var beforeUpdateResult = onBeforeElUpdated(fromEl, toEl);
|
||||||
|
if (beforeUpdateResult === false) {
|
||||||
|
return;
|
||||||
|
} else if (beforeUpdateResult instanceof HTMLElement) {
|
||||||
|
fromEl = beforeUpdateResult;
|
||||||
|
// reindex the new fromEl in case it's not in the same
|
||||||
|
// tree as the original fromEl
|
||||||
|
// (Phoenix LiveView sometimes returns a cloned tree,
|
||||||
|
// but keyed lookups would still point to the original tree)
|
||||||
|
indexTree(fromEl);
|
||||||
|
}
|
||||||
|
|
||||||
|
// update attributes on original DOM element first
|
||||||
|
morphAttrs(fromEl, toEl);
|
||||||
|
// optional
|
||||||
|
onElUpdated(fromEl);
|
||||||
|
|
||||||
|
if (onBeforeElChildrenUpdated(fromEl, toEl) === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fromEl.nodeName !== 'TEXTAREA') {
|
||||||
|
morphChildren(fromEl, toEl);
|
||||||
|
} else {
|
||||||
|
specialElHandlers.TEXTAREA(fromEl, toEl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function morphChildren(fromEl, toEl) {
|
||||||
|
var skipFrom = skipFromChildren(fromEl, toEl);
|
||||||
|
var curToNodeChild = toEl.firstChild;
|
||||||
|
var curFromNodeChild = fromEl.firstChild;
|
||||||
|
var curToNodeKey;
|
||||||
|
var curFromNodeKey;
|
||||||
|
|
||||||
|
var fromNextSibling;
|
||||||
|
var toNextSibling;
|
||||||
|
var matchingFromEl;
|
||||||
|
|
||||||
|
// walk the children
|
||||||
|
outer: while (curToNodeChild) {
|
||||||
|
toNextSibling = curToNodeChild.nextSibling;
|
||||||
|
curToNodeKey = getNodeKey(curToNodeChild);
|
||||||
|
|
||||||
|
// walk the fromNode children all the way through
|
||||||
|
while (!skipFrom && curFromNodeChild) {
|
||||||
|
fromNextSibling = curFromNodeChild.nextSibling;
|
||||||
|
|
||||||
|
if (curToNodeChild.isSameNode && curToNodeChild.isSameNode(curFromNodeChild)) {
|
||||||
|
curToNodeChild = toNextSibling;
|
||||||
|
curFromNodeChild = fromNextSibling;
|
||||||
|
continue outer;
|
||||||
|
}
|
||||||
|
|
||||||
|
curFromNodeKey = getNodeKey(curFromNodeChild);
|
||||||
|
|
||||||
|
var curFromNodeType = curFromNodeChild.nodeType;
|
||||||
|
|
||||||
|
// this means if the curFromNodeChild doesnt have a match with the curToNodeChild
|
||||||
|
var isCompatible = undefined;
|
||||||
|
|
||||||
|
if (curFromNodeType === curToNodeChild.nodeType) {
|
||||||
|
if (curFromNodeType === ELEMENT_NODE) {
|
||||||
|
// Both nodes being compared are Element nodes
|
||||||
|
|
||||||
|
if (curToNodeKey) {
|
||||||
|
// The target node has a key so we want to match it up with the correct element
|
||||||
|
// in the original DOM tree
|
||||||
|
if (curToNodeKey !== curFromNodeKey) {
|
||||||
|
// The current element in the original DOM tree does not have a matching key so
|
||||||
|
// let's check our lookup to see if there is a matching element in the original
|
||||||
|
// DOM tree
|
||||||
|
if ((matchingFromEl = fromNodesLookup[curToNodeKey])) {
|
||||||
|
if (fromNextSibling === matchingFromEl) {
|
||||||
|
// Special case for single element removals. To avoid removing the original
|
||||||
|
// DOM node out of the tree (since that can break CSS transitions, etc.),
|
||||||
|
// we will instead discard the current node and wait until the next
|
||||||
|
// iteration to properly match up the keyed target element with its matching
|
||||||
|
// element in the original tree
|
||||||
|
isCompatible = false;
|
||||||
|
} else {
|
||||||
|
// We found a matching keyed element somewhere in the original DOM tree.
|
||||||
|
// Let's move the original DOM node into the current position and morph
|
||||||
|
// it.
|
||||||
|
|
||||||
|
// NOTE: We use insertBefore instead of replaceChild because we want to go through
|
||||||
|
// the `removeNode()` function for the node that is being discarded so that
|
||||||
|
// all lifecycle hooks are correctly invoked
|
||||||
|
fromEl.insertBefore(matchingFromEl, curFromNodeChild);
|
||||||
|
|
||||||
|
// fromNextSibling = curFromNodeChild.nextSibling;
|
||||||
|
|
||||||
|
if (curFromNodeKey) {
|
||||||
|
// Since the node is keyed it might be matched up later so we defer
|
||||||
|
// the actual removal to later
|
||||||
|
addKeyedRemoval(curFromNodeKey);
|
||||||
|
} else {
|
||||||
|
// NOTE: we skip nested keyed nodes from being removed since there is
|
||||||
|
// still a chance they will be matched up later
|
||||||
|
removeNode(curFromNodeChild, fromEl, true /* skip keyed nodes */);
|
||||||
|
}
|
||||||
|
|
||||||
|
curFromNodeChild = matchingFromEl;
|
||||||
|
curFromNodeKey = getNodeKey(curFromNodeChild);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// The nodes are not compatible since the "to" node has a key and there
|
||||||
|
// is no matching keyed node in the source tree
|
||||||
|
isCompatible = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (curFromNodeKey) {
|
||||||
|
// The original has a key
|
||||||
|
isCompatible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
isCompatible = isCompatible !== false && compareNodeNames(curFromNodeChild, curToNodeChild);
|
||||||
|
if (isCompatible) {
|
||||||
|
// We found compatible DOM elements so transform
|
||||||
|
// the current "from" node to match the current
|
||||||
|
// target DOM node.
|
||||||
|
// MORPH
|
||||||
|
morphEl(curFromNodeChild, curToNodeChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (curFromNodeType === TEXT_NODE || curFromNodeType == COMMENT_NODE) {
|
||||||
|
// Both nodes being compared are Text or Comment nodes
|
||||||
|
isCompatible = true;
|
||||||
|
// Simply update nodeValue on the original node to
|
||||||
|
// change the text value
|
||||||
|
if (curFromNodeChild.nodeValue !== curToNodeChild.nodeValue) {
|
||||||
|
curFromNodeChild.nodeValue = curToNodeChild.nodeValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isCompatible) {
|
||||||
|
// Advance both the "to" child and the "from" child since we found a match
|
||||||
|
// Nothing else to do as we already recursively called morphChildren above
|
||||||
|
curToNodeChild = toNextSibling;
|
||||||
|
curFromNodeChild = fromNextSibling;
|
||||||
|
continue outer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No compatible match so remove the old node from the DOM and continue trying to find a
|
||||||
|
// match in the original DOM. However, we only do this if the from node is not keyed
|
||||||
|
// since it is possible that a keyed node might match up with a node somewhere else in the
|
||||||
|
// target tree and we don't want to discard it just yet since it still might find a
|
||||||
|
// home in the final DOM tree. After everything is done we will remove any keyed nodes
|
||||||
|
// that didn't find a home
|
||||||
|
if (curFromNodeKey) {
|
||||||
|
// Since the node is keyed it might be matched up later so we defer
|
||||||
|
// the actual removal to later
|
||||||
|
addKeyedRemoval(curFromNodeKey);
|
||||||
|
} else {
|
||||||
|
// NOTE: we skip nested keyed nodes from being removed since there is
|
||||||
|
// still a chance they will be matched up later
|
||||||
|
removeNode(curFromNodeChild, fromEl, true /* skip keyed nodes */);
|
||||||
|
}
|
||||||
|
|
||||||
|
curFromNodeChild = fromNextSibling;
|
||||||
|
} // END: while(curFromNodeChild) {}
|
||||||
|
|
||||||
|
// If we got this far then we did not find a candidate match for
|
||||||
|
// our "to node" and we exhausted all of the children "from"
|
||||||
|
// nodes. Therefore, we will just append the current "to" node
|
||||||
|
// to the end
|
||||||
|
if (curToNodeKey && (matchingFromEl = fromNodesLookup[curToNodeKey]) && compareNodeNames(matchingFromEl, curToNodeChild)) {
|
||||||
|
// MORPH
|
||||||
|
if(!skipFrom){ addChild(fromEl, matchingFromEl); }
|
||||||
|
morphEl(matchingFromEl, curToNodeChild);
|
||||||
|
} else {
|
||||||
|
var onBeforeNodeAddedResult = onBeforeNodeAdded(curToNodeChild);
|
||||||
|
if (onBeforeNodeAddedResult !== false) {
|
||||||
|
if (onBeforeNodeAddedResult) {
|
||||||
|
curToNodeChild = onBeforeNodeAddedResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (curToNodeChild.actualize) {
|
||||||
|
curToNodeChild = curToNodeChild.actualize(fromEl.ownerDocument || doc);
|
||||||
|
}
|
||||||
|
addChild(fromEl, curToNodeChild);
|
||||||
|
handleNodeAdded(curToNodeChild);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
curToNodeChild = toNextSibling;
|
||||||
|
curFromNodeChild = fromNextSibling;
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanupFromEl(fromEl, curFromNodeChild, curFromNodeKey);
|
||||||
|
|
||||||
|
var specialElHandler = specialElHandlers[fromEl.nodeName];
|
||||||
|
if (specialElHandler) {
|
||||||
|
specialElHandler(fromEl, toEl);
|
||||||
|
}
|
||||||
|
} // END: morphChildren(...)
|
||||||
|
|
||||||
|
var morphedNode = fromNode;
|
||||||
|
var morphedNodeType = morphedNode.nodeType;
|
||||||
|
var toNodeType = toNode.nodeType;
|
||||||
|
|
||||||
|
if (!childrenOnly) {
|
||||||
|
// Handle the case where we are given two DOM nodes that are not
|
||||||
|
// compatible (e.g. <div> --> <span> or <div> --> TEXT)
|
||||||
|
if (morphedNodeType === ELEMENT_NODE) {
|
||||||
|
if (toNodeType === ELEMENT_NODE) {
|
||||||
|
if (!compareNodeNames(fromNode, toNode)) {
|
||||||
|
onNodeDiscarded(fromNode);
|
||||||
|
morphedNode = moveChildren(fromNode, createElementNS(toNode.nodeName, toNode.namespaceURI));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Going from an element node to a text node
|
||||||
|
morphedNode = toNode;
|
||||||
|
}
|
||||||
|
} else if (morphedNodeType === TEXT_NODE || morphedNodeType === COMMENT_NODE) { // Text or comment node
|
||||||
|
if (toNodeType === morphedNodeType) {
|
||||||
|
if (morphedNode.nodeValue !== toNode.nodeValue) {
|
||||||
|
morphedNode.nodeValue = toNode.nodeValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return morphedNode;
|
||||||
|
} else {
|
||||||
|
// Text node to something else
|
||||||
|
morphedNode = toNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (morphedNode === toNode) {
|
||||||
|
// The "to node" was not compatible with the "from node" so we had to
|
||||||
|
// toss out the "from node" and use the "to node"
|
||||||
|
onNodeDiscarded(fromNode);
|
||||||
|
} else {
|
||||||
|
if (toNode.isSameNode && toNode.isSameNode(morphedNode)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
morphEl(morphedNode, toNode, childrenOnly);
|
||||||
|
|
||||||
|
// We now need to loop over any keyed nodes that might need to be
|
||||||
|
// removed. We only do the removal if we know that the keyed node
|
||||||
|
// never found a match. When a keyed node is matched up we remove
|
||||||
|
// it out of fromNodesLookup and we use fromNodesLookup to determine
|
||||||
|
// if a keyed node has been matched up or not
|
||||||
|
if (keyedRemovalList) {
|
||||||
|
for (var i=0, len=keyedRemovalList.length; i<len; i++) {
|
||||||
|
var elToRemove = fromNodesLookup[keyedRemovalList[i]];
|
||||||
|
if (elToRemove) {
|
||||||
|
removeNode(elToRemove, elToRemove.parentNode, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!childrenOnly && morphedNode !== fromNode && fromNode.parentNode) {
|
||||||
|
if (morphedNode.actualize) {
|
||||||
|
morphedNode = morphedNode.actualize(fromNode.ownerDocument || doc);
|
||||||
|
}
|
||||||
|
// If we had to swap out the from node with a new node because the old
|
||||||
|
// node was not compatible with the target node then we need to
|
||||||
|
// replace the old DOM node in the original DOM tree. This is only
|
||||||
|
// possible if the original DOM node was part of a DOM tree which
|
||||||
|
// we know is the case if it has a parent node.
|
||||||
|
fromNode.parentNode.replaceChild(morphedNode, fromNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
return morphedNode;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var morphdom = morphdomFactory(morphAttrs);
|
||||||
|
|
||||||
|
export default morphdom;
|
|
@ -0,0 +1,21 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) Patrick Steele-Idem <pnidem@gmail.com> (psteeleidem.com)
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
File diff suppressed because it is too large
Load Diff
|
@ -60,7 +60,12 @@ export class QuickReplyConfig {
|
||||||
/**@type {HTMLElement}*/
|
/**@type {HTMLElement}*/
|
||||||
this.setListDom = root.querySelector('.qr--setList');
|
this.setListDom = root.querySelector('.qr--setList');
|
||||||
root.querySelector('.qr--setListAdd').addEventListener('click', ()=>{
|
root.querySelector('.qr--setListAdd').addEventListener('click', ()=>{
|
||||||
this.addSet(QuickReplySet.list[0]);
|
const newSet = QuickReplySet.list.find(qr=>!this.setList.find(qrl=>qrl.set == qr));
|
||||||
|
if (newSet) {
|
||||||
|
this.addSet(newSet);
|
||||||
|
} else {
|
||||||
|
toastr.warning('All existing QR Sets have already been added.');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
this.updateSetListDom();
|
this.updateSetListDom();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import { getRequestHeaders, substituteParams } from '../../../../script.js';
|
import { getRequestHeaders, substituteParams } from '../../../../script.js';
|
||||||
|
import { Popup, POPUP_RESULT, POPUP_TYPE } from '../../../popup.js';
|
||||||
import { executeSlashCommands, executeSlashCommandsOnChatInput, executeSlashCommandsWithOptions } from '../../../slash-commands.js';
|
import { executeSlashCommands, executeSlashCommandsOnChatInput, executeSlashCommandsWithOptions } from '../../../slash-commands.js';
|
||||||
|
import { SlashCommandParser } from '../../../slash-commands/SlashCommandParser.js';
|
||||||
import { SlashCommandScope } from '../../../slash-commands/SlashCommandScope.js';
|
import { SlashCommandScope } from '../../../slash-commands/SlashCommandScope.js';
|
||||||
import { debounceAsync, warn } from '../index.js';
|
import { debounceAsync, log, warn } from '../index.js';
|
||||||
import { QuickReply } from './QuickReply.js';
|
import { QuickReply } from './QuickReply.js';
|
||||||
|
|
||||||
export class QuickReplySet {
|
export class QuickReplySet {
|
||||||
|
@ -16,7 +18,7 @@ export class QuickReplySet {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {String} name - name of the QuickReplySet
|
* @param {string} name - name of the QuickReplySet
|
||||||
*/
|
*/
|
||||||
static get(name) {
|
static get(name) {
|
||||||
return this.list.find(it=>it.name == name);
|
return this.list.find(it=>it.name == name);
|
||||||
|
@ -25,17 +27,19 @@ export class QuickReplySet {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**@type {String}*/ name;
|
/**@type {string}*/ name;
|
||||||
/**@type {Boolean}*/ disableSend = false;
|
/**@type {boolean}*/ disableSend = false;
|
||||||
/**@type {Boolean}*/ placeBeforeInput = false;
|
/**@type {boolean}*/ placeBeforeInput = false;
|
||||||
/**@type {Boolean}*/ injectInput = false;
|
/**@type {boolean}*/ injectInput = false;
|
||||||
|
/**@type {string}*/ color = 'transparent';
|
||||||
|
/**@type {boolean}*/ onlyBorderColor = false;
|
||||||
/**@type {QuickReply[]}*/ qrList = [];
|
/**@type {QuickReply[]}*/ qrList = [];
|
||||||
|
|
||||||
/**@type {Number}*/ idIndex = 0;
|
/**@type {number}*/ idIndex = 0;
|
||||||
|
|
||||||
/**@type {Boolean}*/ isDeleted = false;
|
/**@type {boolean}*/ isDeleted = false;
|
||||||
|
|
||||||
/**@type {Function}*/ save;
|
/**@type {function}*/ save;
|
||||||
|
|
||||||
/**@type {HTMLElement}*/ dom;
|
/**@type {HTMLElement}*/ dom;
|
||||||
/**@type {HTMLElement}*/ settingsDom;
|
/**@type {HTMLElement}*/ settingsDom;
|
||||||
|
@ -64,6 +68,7 @@ export class QuickReplySet {
|
||||||
const root = document.createElement('div'); {
|
const root = document.createElement('div'); {
|
||||||
this.dom = root;
|
this.dom = root;
|
||||||
root.classList.add('qr--buttons');
|
root.classList.add('qr--buttons');
|
||||||
|
this.updateColor();
|
||||||
this.qrList.filter(qr=>!qr.isHidden).forEach(qr=>{
|
this.qrList.filter(qr=>!qr.isHidden).forEach(qr=>{
|
||||||
root.append(qr.render());
|
root.append(qr.render());
|
||||||
});
|
});
|
||||||
|
@ -78,6 +83,22 @@ export class QuickReplySet {
|
||||||
this.dom.append(qr.render());
|
this.dom.append(qr.render());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
updateColor() {
|
||||||
|
if (!this.dom) return;
|
||||||
|
if (this.color && this.color != 'transparent') {
|
||||||
|
this.dom.style.setProperty('--qr--color', this.color);
|
||||||
|
this.dom.classList.add('qr--color');
|
||||||
|
if (this.onlyBorderColor) {
|
||||||
|
this.dom.classList.add('qr--borderColor');
|
||||||
|
} else {
|
||||||
|
this.dom.classList.remove('qr--borderColor');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.dom.style.setProperty('--qr--color', 'transparent');
|
||||||
|
this.dom.classList.remove('qr--color');
|
||||||
|
this.dom.classList.remove('qr--borderColor');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -93,6 +114,11 @@ export class QuickReplySet {
|
||||||
}
|
}
|
||||||
return this.settingsDom;
|
return this.settingsDom;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {QuickReply} qr
|
||||||
|
* @param {number} idx
|
||||||
|
*/
|
||||||
renderSettingsItem(qr, idx) {
|
renderSettingsItem(qr, idx) {
|
||||||
this.settingsDom.append(qr.renderSettings(idx));
|
this.settingsDom.append(qr.renderSettings(idx));
|
||||||
}
|
}
|
||||||
|
@ -100,6 +126,18 @@ export class QuickReplySet {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {QuickReply} qr
|
||||||
|
*/
|
||||||
|
async debug(qr) {
|
||||||
|
const parser = new SlashCommandParser();
|
||||||
|
const closure = parser.parse(qr.message, true, [], qr.abortController, qr.debugController);
|
||||||
|
closure.source = `${this.name}.${qr.label}`;
|
||||||
|
closure.onProgress = (done, total) => qr.updateEditorProgress(done, total);
|
||||||
|
closure.scope.setMacro('arg::*', '');
|
||||||
|
return (await closure.execute())?.pipe;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {QuickReply} qr The QR to execute.
|
* @param {QuickReply} qr The QR to execute.
|
||||||
|
@ -109,6 +147,7 @@ export class QuickReplySet {
|
||||||
* @param {boolean} [options.isEditor] (false) whether the execution is triggered by the QR editor
|
* @param {boolean} [options.isEditor] (false) whether the execution is triggered by the QR editor
|
||||||
* @param {boolean} [options.isRun] (false) whether the execution is triggered by /run or /: (window.executeQuickReplyByName)
|
* @param {boolean} [options.isRun] (false) whether the execution is triggered by /run or /: (window.executeQuickReplyByName)
|
||||||
* @param {SlashCommandScope} [options.scope] (null) scope to be used when running the command
|
* @param {SlashCommandScope} [options.scope] (null) scope to be used when running the command
|
||||||
|
* @param {import('../../../slash-commands.js').ExecuteSlashCommandsOptions} [options.executionOptions] ({}) further execution options
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
async executeWithOptions(qr, options = {}) {
|
async executeWithOptions(qr, options = {}) {
|
||||||
|
@ -118,7 +157,9 @@ export class QuickReplySet {
|
||||||
isEditor:false,
|
isEditor:false,
|
||||||
isRun:false,
|
isRun:false,
|
||||||
scope:null,
|
scope:null,
|
||||||
|
executionOptions:{},
|
||||||
}, options);
|
}, options);
|
||||||
|
const execOptions = options.executionOptions;
|
||||||
/**@type {HTMLTextAreaElement}*/
|
/**@type {HTMLTextAreaElement}*/
|
||||||
const ta = document.querySelector('#send_textarea');
|
const ta = document.querySelector('#send_textarea');
|
||||||
const finalMessage = options.message ?? qr.message;
|
const finalMessage = options.message ?? qr.message;
|
||||||
|
@ -136,21 +177,24 @@ export class QuickReplySet {
|
||||||
if (input[0] == '/' && !this.disableSend) {
|
if (input[0] == '/' && !this.disableSend) {
|
||||||
let result;
|
let result;
|
||||||
if (options.isAutoExecute || options.isRun) {
|
if (options.isAutoExecute || options.isRun) {
|
||||||
result = await executeSlashCommandsWithOptions(input, {
|
result = await executeSlashCommandsWithOptions(input, Object.assign(execOptions, {
|
||||||
handleParserErrors: true,
|
handleParserErrors: true,
|
||||||
scope: options.scope,
|
scope: options.scope,
|
||||||
});
|
source: `${this.name}.${qr.label}`,
|
||||||
|
}));
|
||||||
} else if (options.isEditor) {
|
} else if (options.isEditor) {
|
||||||
result = await executeSlashCommandsWithOptions(input, {
|
result = await executeSlashCommandsWithOptions(input, Object.assign(execOptions, {
|
||||||
handleParserErrors: false,
|
handleParserErrors: false,
|
||||||
scope: options.scope,
|
scope: options.scope,
|
||||||
abortController: qr.abortController,
|
abortController: qr.abortController,
|
||||||
|
source: `${this.name}.${qr.label}`,
|
||||||
onProgress: (done, total) => qr.updateEditorProgress(done, total),
|
onProgress: (done, total) => qr.updateEditorProgress(done, total),
|
||||||
});
|
}));
|
||||||
} else {
|
} else {
|
||||||
result = await executeSlashCommandsOnChatInput(input, {
|
result = await executeSlashCommandsOnChatInput(input, Object.assign(execOptions, {
|
||||||
scope: options.scope,
|
scope: options.scope,
|
||||||
});
|
source: `${this.name}.${qr.label}`,
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
return typeof result === 'object' ? result?.pipe : '';
|
return typeof result === 'object' ? result?.pipe : '';
|
||||||
}
|
}
|
||||||
|
@ -165,7 +209,7 @@ export class QuickReplySet {
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* @param {QuickReply} qr
|
* @param {QuickReply} qr
|
||||||
* @param {String} [message] - optional altered message to be used
|
* @param {string} [message] - optional altered message to be used
|
||||||
* @param {SlashCommandScope} [scope] - optional scope to be used when running the command
|
* @param {SlashCommandScope} [scope] - optional scope to be used when running the command
|
||||||
*/
|
*/
|
||||||
async execute(qr, message = null, isAutoExecute = false, scope = null) {
|
async execute(qr, message = null, isAutoExecute = false, scope = null) {
|
||||||
|
@ -179,10 +223,11 @@ export class QuickReplySet {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
addQuickReply() {
|
addQuickReply(data = {}) {
|
||||||
const id = Math.max(this.idIndex, this.qrList.reduce((max,qr)=>Math.max(max,qr.id),0)) + 1;
|
const id = Math.max(this.idIndex, this.qrList.reduce((max,qr)=>Math.max(max,qr.id),0)) + 1;
|
||||||
|
data.id =
|
||||||
this.idIndex = id + 1;
|
this.idIndex = id + 1;
|
||||||
const qr = QuickReply.from({ id });
|
const qr = QuickReply.from(data);
|
||||||
this.qrList.push(qr);
|
this.qrList.push(qr);
|
||||||
this.hookQuickReply(qr);
|
this.hookQuickReply(qr);
|
||||||
if (this.settingsDom) {
|
if (this.settingsDom) {
|
||||||
|
@ -194,11 +239,131 @@ export class QuickReplySet {
|
||||||
this.save();
|
this.save();
|
||||||
return qr;
|
return qr;
|
||||||
}
|
}
|
||||||
|
addQuickReplyFromText(qrJson) {
|
||||||
|
let data;
|
||||||
|
if (qrJson) {
|
||||||
|
try {
|
||||||
|
data = JSON.parse(qrJson ?? '{}');
|
||||||
|
delete data.id;
|
||||||
|
} catch {
|
||||||
|
// not JSON data
|
||||||
|
}
|
||||||
|
if (data) {
|
||||||
|
// JSON data
|
||||||
|
if (data.label === undefined || data.message === undefined) {
|
||||||
|
// not a QR
|
||||||
|
toastr.error('Not a QR.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// no JSON, use plaintext as QR message
|
||||||
|
data = { message: qrJson };
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
data = {};
|
||||||
|
}
|
||||||
|
const newQr = this.addQuickReply(data);
|
||||||
|
return newQr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {QuickReply} qr
|
||||||
|
*/
|
||||||
hookQuickReply(qr) {
|
hookQuickReply(qr) {
|
||||||
|
qr.onDebug = ()=>this.debug(qr);
|
||||||
qr.onExecute = (_, options)=>this.executeWithOptions(qr, options);
|
qr.onExecute = (_, options)=>this.executeWithOptions(qr, options);
|
||||||
qr.onDelete = ()=>this.removeQuickReply(qr);
|
qr.onDelete = ()=>this.removeQuickReply(qr);
|
||||||
qr.onUpdate = ()=>this.save();
|
qr.onUpdate = ()=>this.save();
|
||||||
|
qr.onInsertBefore = (qrJson)=>{
|
||||||
|
this.addQuickReplyFromText(qrJson);
|
||||||
|
const newQr = this.qrList.pop();
|
||||||
|
this.qrList.splice(this.qrList.indexOf(qr), 0, newQr);
|
||||||
|
if (qr.settingsDom) {
|
||||||
|
qr.settingsDom.insertAdjacentElement('beforebegin', newQr.settingsDom);
|
||||||
|
}
|
||||||
|
this.save();
|
||||||
|
};
|
||||||
|
qr.onTransfer = async()=>{
|
||||||
|
/**@type {HTMLSelectElement} */
|
||||||
|
let sel;
|
||||||
|
let isCopy = false;
|
||||||
|
const dom = document.createElement('div'); {
|
||||||
|
dom.classList.add('qr--transferModal');
|
||||||
|
const title = document.createElement('h3'); {
|
||||||
|
title.textContent = 'Transfer Quick Reply';
|
||||||
|
dom.append(title);
|
||||||
|
}
|
||||||
|
const subTitle = document.createElement('h4'); {
|
||||||
|
const entryName = qr.label;
|
||||||
|
const bookName = this.name;
|
||||||
|
subTitle.textContent = `${bookName}: ${entryName}`;
|
||||||
|
dom.append(subTitle);
|
||||||
|
}
|
||||||
|
sel = document.createElement('select'); {
|
||||||
|
sel.classList.add('qr--transferSelect');
|
||||||
|
sel.setAttribute('autofocus', '1');
|
||||||
|
const noOpt = document.createElement('option'); {
|
||||||
|
noOpt.value = '';
|
||||||
|
noOpt.textContent = '-- Select QR Set --';
|
||||||
|
sel.append(noOpt);
|
||||||
|
}
|
||||||
|
for (const qrs of QuickReplySet.list) {
|
||||||
|
const opt = document.createElement('option'); {
|
||||||
|
opt.value = qrs.name;
|
||||||
|
opt.textContent = qrs.name;
|
||||||
|
sel.append(opt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sel.addEventListener('keyup', (evt)=>{
|
||||||
|
if (evt.key == 'Shift') {
|
||||||
|
(dlg.dom ?? dlg.dlg).classList.remove('qr--isCopy');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
sel.addEventListener('keydown', (evt)=>{
|
||||||
|
if (evt.key == 'Shift') {
|
||||||
|
(dlg.dom ?? dlg.dlg).classList.add('qr--isCopy');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!evt.ctrlKey && !evt.altKey && evt.key == 'Enter') {
|
||||||
|
evt.preventDefault();
|
||||||
|
if (evt.shiftKey) isCopy = true;
|
||||||
|
dlg.completeAffirmative();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
dom.append(sel);
|
||||||
|
}
|
||||||
|
const hintP = document.createElement('p'); {
|
||||||
|
const hint = document.createElement('small'); {
|
||||||
|
hint.textContent = 'Type or arrows to select QR Set. Enter to transfer. Shift+Enter to copy.';
|
||||||
|
hintP.append(hint);
|
||||||
|
}
|
||||||
|
dom.append(hintP);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const dlg = new Popup(dom, POPUP_TYPE.CONFIRM, null, { okButton:'Transfer', cancelButton:'Cancel' });
|
||||||
|
const copyBtn = document.createElement('div'); {
|
||||||
|
copyBtn.classList.add('qr--copy');
|
||||||
|
copyBtn.classList.add('menu_button');
|
||||||
|
copyBtn.textContent = 'Copy';
|
||||||
|
copyBtn.addEventListener('click', ()=>{
|
||||||
|
isCopy = true;
|
||||||
|
dlg.completeAffirmative();
|
||||||
|
});
|
||||||
|
(dlg.ok ?? dlg.okButton).insertAdjacentElement('afterend', copyBtn);
|
||||||
|
}
|
||||||
|
const prom = dlg.show();
|
||||||
|
sel.focus();
|
||||||
|
await prom;
|
||||||
|
if (dlg.result == POPUP_RESULT.AFFIRMATIVE) {
|
||||||
|
const qrs = QuickReplySet.list.find(it=>it.name == sel.value);
|
||||||
|
qrs.addQuickReply(qr.toJSON());
|
||||||
|
if (!isCopy) {
|
||||||
|
qr.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
removeQuickReply(qr) {
|
removeQuickReply(qr) {
|
||||||
|
@ -214,6 +379,8 @@ export class QuickReplySet {
|
||||||
disableSend: this.disableSend,
|
disableSend: this.disableSend,
|
||||||
placeBeforeInput: this.placeBeforeInput,
|
placeBeforeInput: this.placeBeforeInput,
|
||||||
injectInput: this.injectInput,
|
injectInput: this.injectInput,
|
||||||
|
color: this.color,
|
||||||
|
onlyBorderColor: this.onlyBorderColor,
|
||||||
qrList: this.qrList,
|
qrList: this.qrList,
|
||||||
idIndex: this.idIndex,
|
idIndex: this.idIndex,
|
||||||
};
|
};
|
||||||
|
@ -245,8 +412,12 @@ export class QuickReplySet {
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
this.unrender();
|
this.unrender();
|
||||||
const idx = QuickReplySet.list.indexOf(this);
|
const idx = QuickReplySet.list.indexOf(this);
|
||||||
|
if (idx > -1) {
|
||||||
QuickReplySet.list.splice(idx, 1);
|
QuickReplySet.list.splice(idx, 1);
|
||||||
this.isDeleted = true;
|
this.isDeleted = true;
|
||||||
|
} else {
|
||||||
|
warn(`Deleted Quick Reply Set was not found in the list of sets: ${this.name}`);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
warn(`Failed to delete Quick Reply Set: ${this.name}`);
|
warn(`Failed to delete Quick Reply Set: ${this.name}`);
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,7 +45,7 @@ export class QuickReplySetLink {
|
||||||
this.set = QuickReplySet.get(set.value);
|
this.set = QuickReplySet.get(set.value);
|
||||||
this.update();
|
this.update();
|
||||||
});
|
});
|
||||||
QuickReplySet.list.forEach(qrs=>{
|
QuickReplySet.list.toSorted((a,b)=>a.name.toLowerCase().localeCompare(b.name.toLowerCase())).forEach(qrs=>{
|
||||||
const opt = document.createElement('option'); {
|
const opt = document.createElement('option'); {
|
||||||
opt.value = qrs.name;
|
opt.value = qrs.name;
|
||||||
opt.textContent = qrs.name;
|
opt.textContent = qrs.name;
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
import { SlashCommand } from '../../../slash-commands/SlashCommand.js';
|
import { SlashCommand } from '../../../slash-commands/SlashCommand.js';
|
||||||
|
import { SlashCommandAbortController } from '../../../slash-commands/SlashCommandAbortController.js';
|
||||||
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../../slash-commands/SlashCommandArgument.js';
|
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../../slash-commands/SlashCommandArgument.js';
|
||||||
|
import { SlashCommandClosure } from '../../../slash-commands/SlashCommandClosure.js';
|
||||||
import { enumIcons } from '../../../slash-commands/SlashCommandCommonEnumsProvider.js';
|
import { enumIcons } from '../../../slash-commands/SlashCommandCommonEnumsProvider.js';
|
||||||
|
import { SlashCommandDebugController } from '../../../slash-commands/SlashCommandDebugController.js';
|
||||||
import { SlashCommandEnumValue, enumTypes } from '../../../slash-commands/SlashCommandEnumValue.js';
|
import { SlashCommandEnumValue, enumTypes } from '../../../slash-commands/SlashCommandEnumValue.js';
|
||||||
import { SlashCommandParser } from '../../../slash-commands/SlashCommandParser.js';
|
import { SlashCommandParser } from '../../../slash-commands/SlashCommandParser.js';
|
||||||
|
import { SlashCommandScope } from '../../../slash-commands/SlashCommandScope.js';
|
||||||
import { isTrueBoolean } from '../../../utils.js';
|
import { isTrueBoolean } from '../../../utils.js';
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
import { QuickReplyApi } from '../api/QuickReplyApi.js';
|
import { QuickReplyApi } from '../api/QuickReplyApi.js';
|
||||||
|
@ -47,6 +51,13 @@ export class SlashCommandHandler {
|
||||||
return new SlashCommandEnumValue(qr.label, message, enumTypes.enum, enumIcons.qr);
|
return new SlashCommandEnumValue(qr.label, message, enumTypes.enum, enumIcons.qr);
|
||||||
}) ?? [],
|
}) ?? [],
|
||||||
|
|
||||||
|
/** All QRs inside a set, utilizing the "set" named argument, returns the QR's ID */
|
||||||
|
qrIds: (executor) => QuickReplySet.get(String(executor.namedArgumentList.find(x => x.name == 'set')?.value))?.qrList.map(qr => {
|
||||||
|
const icons = getExecutionIcons(qr);
|
||||||
|
const message = `${qr.automationId ? `[${qr.automationId}]` : ''}${icons ? `[auto: ${icons}]` : ''} ${qr.title || qr.message}`.trim();
|
||||||
|
return new SlashCommandEnumValue(qr.label, message, enumTypes.enum, enumIcons.qr, null, ()=>qr.id.toString(), true);
|
||||||
|
}) ?? [],
|
||||||
|
|
||||||
/** All QRs as a set.name string, to be able to execute, for example via the /run command */
|
/** All QRs as a set.name string, to be able to execute, for example via the /run command */
|
||||||
qrExecutables: () => {
|
qrExecutables: () => {
|
||||||
const globalSetList = this.api.settings.config.setList;
|
const globalSetList = this.api.settings.config.setList;
|
||||||
|
@ -63,7 +74,7 @@ export class SlashCommandHandler {
|
||||||
...otherQrs.map(x => new SlashCommandEnumValue(`${x.set.name}.${x.qr.label}`, `${x.qr.title || x.qr.message}`, enumTypes.qr, enumIcons.qr)),
|
...otherQrs.map(x => new SlashCommandEnumValue(`${x.set.name}.${x.qr.label}`, `${x.qr.title || x.qr.message}`, enumTypes.qr, enumIcons.qr)),
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
window['qrEnumProviderExecutables'] = localEnumProviders.qrExecutables;
|
window['qrEnumProviderExecutables'] = localEnumProviders.qrExecutables;
|
||||||
|
|
||||||
|
@ -234,8 +245,20 @@ export class SlashCommandHandler {
|
||||||
name: 'label',
|
name: 'label',
|
||||||
description: 'text on the button, e.g., label=MyButton',
|
description: 'text on the button, e.g., label=MyButton',
|
||||||
typeList: [ARGUMENT_TYPE.STRING],
|
typeList: [ARGUMENT_TYPE.STRING],
|
||||||
isRequired: true,
|
isRequired: false,
|
||||||
enumProvider: localEnumProviders.qrLabels,
|
enumProvider: localEnumProviders.qrEntries,
|
||||||
|
}),
|
||||||
|
SlashCommandNamedArgument.fromProps({
|
||||||
|
name: 'icon',
|
||||||
|
description: 'icon to show on the button, e.g., icon=fa-pencil',
|
||||||
|
typeList: [ARGUMENT_TYPE.STRING],
|
||||||
|
isRequired: false,
|
||||||
|
}),
|
||||||
|
SlashCommandNamedArgument.fromProps({
|
||||||
|
name: 'showlabel',
|
||||||
|
description: 'whether to show the label even when an icon is assigned, e.g., icon=fa-pencil showlabel=true',
|
||||||
|
typeList: [ARGUMENT_TYPE.BOOLEAN],
|
||||||
|
isRequired: false,
|
||||||
}),
|
}),
|
||||||
new SlashCommandNamedArgument('hidden', 'whether the button should be hidden, e.g., hidden=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'),
|
new SlashCommandNamedArgument('hidden', 'whether the button should be hidden, e.g., hidden=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'),
|
||||||
new SlashCommandNamedArgument('startup', 'auto execute on app startup, e.g., startup=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'),
|
new SlashCommandNamedArgument('startup', 'auto execute on app startup, e.g., startup=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'),
|
||||||
|
@ -247,6 +270,13 @@ export class SlashCommandHandler {
|
||||||
];
|
];
|
||||||
const qrUpdateArgs = [
|
const qrUpdateArgs = [
|
||||||
new SlashCommandNamedArgument('newlabel', 'new text for the button', [ARGUMENT_TYPE.STRING], false),
|
new SlashCommandNamedArgument('newlabel', 'new text for the button', [ARGUMENT_TYPE.STRING], false),
|
||||||
|
SlashCommandNamedArgument.fromProps({
|
||||||
|
name: 'id',
|
||||||
|
description: 'numeric ID of the QR, e.g., id=42',
|
||||||
|
typeList: [ARGUMENT_TYPE.NUMBER],
|
||||||
|
isRequired: false,
|
||||||
|
enumProvider: localEnumProviders.qrIds,
|
||||||
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-create',
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-create',
|
||||||
|
@ -272,13 +302,61 @@ export class SlashCommandHandler {
|
||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
}));
|
}));
|
||||||
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-get',
|
||||||
|
callback: (args, _) => {
|
||||||
|
return this.getQuickReply(args);
|
||||||
|
},
|
||||||
|
namedArgumentList: [
|
||||||
|
SlashCommandNamedArgument.fromProps({
|
||||||
|
name: 'set',
|
||||||
|
description: 'name of the QR set, e.g., set=PresetName1',
|
||||||
|
typeList: [ARGUMENT_TYPE.STRING],
|
||||||
|
isRequired: true,
|
||||||
|
enumProvider: localEnumProviders.qrSets,
|
||||||
|
}),
|
||||||
|
SlashCommandNamedArgument.fromProps({
|
||||||
|
name: 'label',
|
||||||
|
description: 'text on the button, e.g., label=MyButton',
|
||||||
|
typeList: [ARGUMENT_TYPE.STRING],
|
||||||
|
isRequired: false,
|
||||||
|
enumProvider: localEnumProviders.qrEntries,
|
||||||
|
}),
|
||||||
|
SlashCommandNamedArgument.fromProps({
|
||||||
|
name: 'id',
|
||||||
|
description: 'numeric ID of the QR, e.g., id=42',
|
||||||
|
typeList: [ARGUMENT_TYPE.NUMBER],
|
||||||
|
isRequired: false,
|
||||||
|
enumProvider: localEnumProviders.qrIds,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
returns: 'a dictionary with all the QR\'s properties',
|
||||||
|
helpString: `
|
||||||
|
<div>Get a Quick Reply's properties.</div>
|
||||||
|
<div>
|
||||||
|
<strong>Examples:</strong>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<pre><code>/qr-get set=MyPreset label=MyButton | /echo</code></pre>
|
||||||
|
<pre><code>/qr-get set=MyPreset id=42 | /echo</code></pre>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
}));
|
||||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-update',
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-update',
|
||||||
callback: (args, message) => {
|
callback: (args, message) => {
|
||||||
this.updateQuickReply(args, message);
|
this.updateQuickReply(args, message);
|
||||||
return '';
|
return '';
|
||||||
},
|
},
|
||||||
returns: 'updated quick reply',
|
returns: 'updated quick reply',
|
||||||
namedArgumentList: [...qrUpdateArgs, ...qrArgs],
|
namedArgumentList: [...qrUpdateArgs, ...qrArgs.map(it=>{
|
||||||
|
if (it.name == 'label') {
|
||||||
|
const clone = SlashCommandNamedArgument.fromProps(it);
|
||||||
|
clone.isRequired = false;
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
return it;
|
||||||
|
})],
|
||||||
unnamedArgumentList: [
|
unnamedArgumentList: [
|
||||||
new SlashCommandArgument('command', [ARGUMENT_TYPE.STRING]),
|
new SlashCommandArgument('command', [ARGUMENT_TYPE.STRING]),
|
||||||
],
|
],
|
||||||
|
@ -315,6 +393,12 @@ export class SlashCommandHandler {
|
||||||
typeList: [ARGUMENT_TYPE.STRING],
|
typeList: [ARGUMENT_TYPE.STRING],
|
||||||
enumProvider: localEnumProviders.qrEntries,
|
enumProvider: localEnumProviders.qrEntries,
|
||||||
}),
|
}),
|
||||||
|
SlashCommandNamedArgument.fromProps({
|
||||||
|
name: 'id',
|
||||||
|
description: 'numeric ID of the QR, e.g., id=42',
|
||||||
|
typeList: [ARGUMENT_TYPE.NUMBER],
|
||||||
|
enumProvider: localEnumProviders.qrIds,
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
unnamedArgumentList: [
|
unnamedArgumentList: [
|
||||||
SlashCommandArgument.fromProps({
|
SlashCommandArgument.fromProps({
|
||||||
|
@ -344,6 +428,12 @@ export class SlashCommandHandler {
|
||||||
typeList: [ARGUMENT_TYPE.STRING],
|
typeList: [ARGUMENT_TYPE.STRING],
|
||||||
enumProvider: localEnumProviders.qrEntries,
|
enumProvider: localEnumProviders.qrEntries,
|
||||||
}),
|
}),
|
||||||
|
SlashCommandNamedArgument.fromProps({
|
||||||
|
name: 'id',
|
||||||
|
description: 'numeric ID of the QR, e.g., id=42',
|
||||||
|
typeList: [ARGUMENT_TYPE.NUMBER],
|
||||||
|
enumProvider: localEnumProviders.qrIds,
|
||||||
|
}),
|
||||||
new SlashCommandNamedArgument(
|
new SlashCommandNamedArgument(
|
||||||
'chain', 'boolean', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false',
|
'chain', 'boolean', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false',
|
||||||
),
|
),
|
||||||
|
@ -389,6 +479,12 @@ export class SlashCommandHandler {
|
||||||
typeList: [ARGUMENT_TYPE.STRING],
|
typeList: [ARGUMENT_TYPE.STRING],
|
||||||
enumProvider: localEnumProviders.qrEntries,
|
enumProvider: localEnumProviders.qrEntries,
|
||||||
}),
|
}),
|
||||||
|
SlashCommandNamedArgument.fromProps({
|
||||||
|
name: 'id',
|
||||||
|
description: 'numeric ID of the QR, e.g., id=42',
|
||||||
|
typeList: [ARGUMENT_TYPE.NUMBER],
|
||||||
|
enumProvider: localEnumProviders.qrIds,
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
unnamedArgumentList: [
|
unnamedArgumentList: [
|
||||||
SlashCommandArgument.fromProps({
|
SlashCommandArgument.fromProps({
|
||||||
|
@ -425,6 +521,12 @@ export class SlashCommandHandler {
|
||||||
isRequired: true,
|
isRequired: true,
|
||||||
enumProvider: localEnumProviders.qrSets,
|
enumProvider: localEnumProviders.qrSets,
|
||||||
}),
|
}),
|
||||||
|
SlashCommandNamedArgument.fromProps({
|
||||||
|
name: 'id',
|
||||||
|
description: 'numeric ID of the QR, e.g., id=42',
|
||||||
|
typeList: [ARGUMENT_TYPE.NUMBER],
|
||||||
|
enumProvider: localEnumProviders.qrIds,
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
unnamedArgumentList: [
|
unnamedArgumentList: [
|
||||||
SlashCommandArgument.fromProps({
|
SlashCommandArgument.fromProps({
|
||||||
|
@ -454,8 +556,8 @@ export class SlashCommandHandler {
|
||||||
new SlashCommandNamedArgument('inject', 'inject user input automatically (if disabled use {{input}})', [ARGUMENT_TYPE.BOOLEAN], false),
|
new SlashCommandNamedArgument('inject', 'inject user input automatically (if disabled use {{input}})', [ARGUMENT_TYPE.BOOLEAN], false),
|
||||||
];
|
];
|
||||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set-create',
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set-create',
|
||||||
callback: (args, name) => {
|
callback: async (args, name) => {
|
||||||
this.createSet(name, args);
|
await this.createSet(name, args);
|
||||||
return '';
|
return '';
|
||||||
},
|
},
|
||||||
aliases: ['qr-presetadd'],
|
aliases: ['qr-presetadd'],
|
||||||
|
@ -485,8 +587,8 @@ export class SlashCommandHandler {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set-update',
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set-update',
|
||||||
callback: (args, name) => {
|
callback: async (args, name) => {
|
||||||
this.updateSet(name, args);
|
await this.updateSet(name, args);
|
||||||
return '';
|
return '';
|
||||||
},
|
},
|
||||||
aliases: ['qr-presetupdate'],
|
aliases: ['qr-presetupdate'],
|
||||||
|
@ -510,8 +612,8 @@ export class SlashCommandHandler {
|
||||||
`,
|
`,
|
||||||
}));
|
}));
|
||||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set-delete',
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set-delete',
|
||||||
callback: (_, name) => {
|
callback: async (_, name) => {
|
||||||
this.deleteSet(name);
|
await this.deleteSet(name);
|
||||||
return '';
|
return '';
|
||||||
},
|
},
|
||||||
aliases: ['qr-presetdelete'],
|
aliases: ['qr-presetdelete'],
|
||||||
|
@ -533,6 +635,134 @@ export class SlashCommandHandler {
|
||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-arg',
|
||||||
|
callback: ({ _scope }, [key, value]) => {
|
||||||
|
_scope.setMacro(`arg::${key}`, value, key.includes('*'));
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
unnamedArgumentList: [
|
||||||
|
SlashCommandArgument.fromProps({ description: 'argument name',
|
||||||
|
typeList: ARGUMENT_TYPE.STRING,
|
||||||
|
isRequired: true,
|
||||||
|
}),
|
||||||
|
SlashCommandArgument.fromProps({ description: 'argument value',
|
||||||
|
typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.BOOLEAN, ARGUMENT_TYPE.LIST, ARGUMENT_TYPE.DICTIONARY],
|
||||||
|
isRequired: true,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
splitUnnamedArgument: true,
|
||||||
|
splitUnnamedArgumentCount: 2,
|
||||||
|
helpString: `
|
||||||
|
<div>
|
||||||
|
Set a fallback value for a Quick Reply argument.
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>Example:</strong>
|
||||||
|
<pre><code>/qr-arg x foo |\n/echo {{arg::x}}</code></pre>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
}));
|
||||||
|
|
||||||
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'import',
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {{_scope:SlashCommandScope, _abortController:SlashCommandAbortController, _debugController:SlashCommandDebugController, from:string}} args
|
||||||
|
* @param {string} value
|
||||||
|
*/
|
||||||
|
callback: (args, value) => {
|
||||||
|
if (!args.from) throw new Error('/import requires from= to be set.');
|
||||||
|
if (!value) throw new Error('/import requires the unnamed argument to be set.');
|
||||||
|
let qr = [...this.api.listGlobalSets(), ...this.api.listChatSets()]
|
||||||
|
.map(it=>this.api.getSetByName(it)?.qrList ?? [])
|
||||||
|
.flat()
|
||||||
|
.find(it=>it.label == args.from)
|
||||||
|
;
|
||||||
|
if (!qr) {
|
||||||
|
let [setName, ...qrNameParts] = args.from.split('.');
|
||||||
|
let qrName = qrNameParts.join('.');
|
||||||
|
let qrs = QuickReplySet.get(setName);
|
||||||
|
if (qrs) {
|
||||||
|
qr = qrs.qrList.find(it=>it.label == qrName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (qr) {
|
||||||
|
const parser = new SlashCommandParser();
|
||||||
|
const closure = parser.parse(qr.message, true, [], args._abortController, args._debugController);
|
||||||
|
if (args._debugController) {
|
||||||
|
closure.source = args.from;
|
||||||
|
}
|
||||||
|
const testCandidates = (executor)=>{
|
||||||
|
return (
|
||||||
|
executor.namedArgumentList.find(arg=>arg.name == 'key')
|
||||||
|
&& executor.unnamedArgumentList.length > 0
|
||||||
|
&& executor.unnamedArgumentList[0].value instanceof SlashCommandClosure
|
||||||
|
) || (
|
||||||
|
!executor.namedArgumentList.find(arg=>arg.name == 'key')
|
||||||
|
&& executor.unnamedArgumentList.length > 1
|
||||||
|
&& executor.unnamedArgumentList[1].value instanceof SlashCommandClosure
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const candidates = closure.executorList
|
||||||
|
.filter(executor=>['let', 'var'].includes(executor.command.name))
|
||||||
|
.filter(testCandidates)
|
||||||
|
.map(executor=>({
|
||||||
|
key: executor.namedArgumentList.find(arg=>arg.name == 'key')?.value ?? executor.unnamedArgumentList[0].value,
|
||||||
|
value: executor.unnamedArgumentList[executor.namedArgumentList.find(arg=>arg.name == 'key') ? 0 : 1].value,
|
||||||
|
}))
|
||||||
|
;
|
||||||
|
for (let i = 0; i < value.length; i++) {
|
||||||
|
const srcName = value[i];
|
||||||
|
let dstName = srcName;
|
||||||
|
if (i + 2 < value.length && value[i + 1] == 'as') {
|
||||||
|
dstName = value[i + 2];
|
||||||
|
i += 2;
|
||||||
|
}
|
||||||
|
const pick = candidates.find(it=>it.key == srcName);
|
||||||
|
if (!pick) throw new Error(`No scoped closure named "${srcName}" found in "${args.from}"`);
|
||||||
|
if (args._scope.existsVariableInScope(dstName)) {
|
||||||
|
args._scope.setVariable(dstName, pick.value);
|
||||||
|
} else {
|
||||||
|
args._scope.letVariable(dstName, pick.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error(`No Quick Reply found for "${name}".`);
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
namedArgumentList: [
|
||||||
|
SlashCommandNamedArgument.fromProps({ name: 'from',
|
||||||
|
description: 'Quick Reply to import from (QRSet.QRLabel)',
|
||||||
|
typeList: ARGUMENT_TYPE.STRING,
|
||||||
|
isRequired: true,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
unnamedArgumentList: [
|
||||||
|
SlashCommandArgument.fromProps({ description: 'what to import (x or x as y)',
|
||||||
|
acceptsMultiple: true,
|
||||||
|
typeList: ARGUMENT_TYPE.STRING,
|
||||||
|
isRequired: true,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
splitUnnamedArgument: true,
|
||||||
|
helpString: `
|
||||||
|
<div>
|
||||||
|
Import one or more closures from another Quick Reply.
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Only imports closures that are directly assigned a scoped variable via <code>/let</code> or <code>/var</code>.
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>Examples:</strong>
|
||||||
|
<ul>
|
||||||
|
<li><pre><code>/import from=LibraryQrSet.FooBar foo |\n/:foo</code></pre></li>
|
||||||
|
<li><pre><code>/import from=LibraryQrSet.FooBar\n\tfoo\n\tbar\n|\n/:foo |\n/:bar</code></pre></li>
|
||||||
|
<li><pre><code>/import from=LibraryQrSet.FooBar\n\tfoo as x\n\tbar as y\n|\n/:x |\n/:y</code></pre></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -618,6 +848,8 @@ export class SlashCommandHandler {
|
||||||
args.set ?? '',
|
args.set ?? '',
|
||||||
args.label ?? '',
|
args.label ?? '',
|
||||||
{
|
{
|
||||||
|
icon: args.icon,
|
||||||
|
showLabel: args.showlabel === undefined ? undefined : isTrueBoolean(args.showlabel),
|
||||||
message: message ?? '',
|
message: message ?? '',
|
||||||
title: args.title,
|
title: args.title,
|
||||||
isHidden: isTrueBoolean(args.hidden),
|
isHidden: isTrueBoolean(args.hidden),
|
||||||
|
@ -633,12 +865,21 @@ export class SlashCommandHandler {
|
||||||
toastr.error(ex.message);
|
toastr.error(ex.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
getQuickReply(args) {
|
||||||
|
try {
|
||||||
|
return JSON.stringify(this.api.getQrByLabel(args.set, args.id !== undefined ? Number(args.id) : args.label));
|
||||||
|
} catch (ex) {
|
||||||
|
toastr.error(ex.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
updateQuickReply(args, message) {
|
updateQuickReply(args, message) {
|
||||||
try {
|
try {
|
||||||
this.api.updateQuickReply(
|
this.api.updateQuickReply(
|
||||||
args.set ?? '',
|
args.set ?? '',
|
||||||
args.label ?? '',
|
args.id !== undefined ? Number(args.id) : (args.label ?? ''),
|
||||||
{
|
{
|
||||||
|
icon: args.icon,
|
||||||
|
showLabel: args.showlabel === undefined ? undefined : isTrueBoolean(args.showlabel),
|
||||||
newLabel: args.newlabel,
|
newLabel: args.newlabel,
|
||||||
message: (message ?? '').trim().length > 0 ? message : undefined,
|
message: (message ?? '').trim().length > 0 ? message : undefined,
|
||||||
title: args.title,
|
title: args.title,
|
||||||
|
@ -657,7 +898,7 @@ export class SlashCommandHandler {
|
||||||
}
|
}
|
||||||
deleteQuickReply(args, label) {
|
deleteQuickReply(args, label) {
|
||||||
try {
|
try {
|
||||||
this.api.deleteQuickReply(args.set, args.label ?? label);
|
this.api.deleteQuickReply(args.set, args.id !== undefined ? Number(args.id) : (args.label ?? label));
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
toastr.error(ex.message);
|
toastr.error(ex.message);
|
||||||
}
|
}
|
||||||
|
@ -692,9 +933,9 @@ export class SlashCommandHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
createSet(name, args) {
|
async createSet(name, args) {
|
||||||
try {
|
try {
|
||||||
this.api.createSet(
|
await this.api.createSet(
|
||||||
args.name ?? name ?? '',
|
args.name ?? name ?? '',
|
||||||
{
|
{
|
||||||
disableSend: isTrueBoolean(args.nosend),
|
disableSend: isTrueBoolean(args.nosend),
|
||||||
|
@ -706,9 +947,9 @@ export class SlashCommandHandler {
|
||||||
toastr.error(ex.message);
|
toastr.error(ex.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
updateSet(name, args) {
|
async updateSet(name, args) {
|
||||||
try {
|
try {
|
||||||
this.api.updateSet(
|
await this.api.updateSet(
|
||||||
args.name ?? name ?? '',
|
args.name ?? name ?? '',
|
||||||
{
|
{
|
||||||
disableSend: args.nosend !== undefined ? isTrueBoolean(args.nosend) : undefined,
|
disableSend: args.nosend !== undefined ? isTrueBoolean(args.nosend) : undefined,
|
||||||
|
@ -720,9 +961,9 @@ export class SlashCommandHandler {
|
||||||
toastr.error(ex.message);
|
toastr.error(ex.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
deleteSet(name) {
|
async deleteSet(name) {
|
||||||
try {
|
try {
|
||||||
this.api.deleteSet(name ?? '');
|
await this.api.deleteSet(name ?? '');
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
toastr.error(ex.message);
|
toastr.error(ex.message);
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,8 @@ export class SettingsUi {
|
||||||
/**@type {HTMLInputElement}*/ disableSend;
|
/**@type {HTMLInputElement}*/ disableSend;
|
||||||
/**@type {HTMLInputElement}*/ placeBeforeInput;
|
/**@type {HTMLInputElement}*/ placeBeforeInput;
|
||||||
/**@type {HTMLInputElement}*/ injectInput;
|
/**@type {HTMLInputElement}*/ injectInput;
|
||||||
|
/**@type {HTMLInputElement}*/ color;
|
||||||
|
/**@type {HTMLInputElement}*/ onlyBorderColor;
|
||||||
/**@type {HTMLSelectElement}*/ currentSet;
|
/**@type {HTMLSelectElement}*/ currentSet;
|
||||||
|
|
||||||
|
|
||||||
|
@ -117,10 +119,29 @@ export class SettingsUi {
|
||||||
this.dom.querySelector('#qr--set-add').addEventListener('click', async()=>{
|
this.dom.querySelector('#qr--set-add').addEventListener('click', async()=>{
|
||||||
this.currentQrSet.addQuickReply();
|
this.currentQrSet.addQuickReply();
|
||||||
});
|
});
|
||||||
|
this.dom.querySelector('#qr--set-paste').addEventListener('click', async()=>{
|
||||||
|
const text = await navigator.clipboard.readText();
|
||||||
|
this.currentQrSet.addQuickReplyFromText(text);
|
||||||
|
});
|
||||||
|
this.dom.querySelector('#qr--set-importQr').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.currentQrSet.addQuickReply(JSON.parse(text));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
inp.click();
|
||||||
|
}
|
||||||
|
});
|
||||||
this.qrList = this.dom.querySelector('#qr--set-qrList');
|
this.qrList = this.dom.querySelector('#qr--set-qrList');
|
||||||
this.currentSet = this.dom.querySelector('#qr--set');
|
this.currentSet = this.dom.querySelector('#qr--set');
|
||||||
this.currentSet.addEventListener('change', ()=>this.onQrSetChange());
|
this.currentSet.addEventListener('change', ()=>this.onQrSetChange());
|
||||||
QuickReplySet.list.forEach(qrs=>{
|
QuickReplySet.list.toSorted((a,b)=>a.name.toLowerCase().localeCompare(b.name.toLowerCase())).forEach(qrs=>{
|
||||||
const opt = document.createElement('option'); {
|
const opt = document.createElement('option'); {
|
||||||
opt.value = qrs.name;
|
opt.value = qrs.name;
|
||||||
opt.textContent = qrs.name;
|
opt.textContent = qrs.name;
|
||||||
|
@ -145,6 +166,34 @@ export class SettingsUi {
|
||||||
qrs.injectInput = this.injectInput.checked;
|
qrs.injectInput = this.injectInput.checked;
|
||||||
qrs.save();
|
qrs.save();
|
||||||
});
|
});
|
||||||
|
let initialColorChange = true;
|
||||||
|
this.color = this.dom.querySelector('#qr--color');
|
||||||
|
this.color.color = this.currentQrSet?.color ?? 'transparent';
|
||||||
|
this.color.addEventListener('change', (evt)=>{
|
||||||
|
if (!this.dom.closest('body')) return;
|
||||||
|
const qrs = this.currentQrSet;
|
||||||
|
if (initialColorChange) {
|
||||||
|
initialColorChange = false;
|
||||||
|
this.color.color = qrs.color;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
qrs.color = evt.detail.rgb;
|
||||||
|
qrs.save();
|
||||||
|
this.currentQrSet.updateColor();
|
||||||
|
});
|
||||||
|
this.dom.querySelector('#qr--colorClear').addEventListener('click', (evt)=>{
|
||||||
|
const qrs = this.currentQrSet;
|
||||||
|
this.color.color = 'transparent';
|
||||||
|
qrs.save();
|
||||||
|
this.currentQrSet.updateColor();
|
||||||
|
});
|
||||||
|
this.onlyBorderColor = this.dom.querySelector('#qr--onlyBorderColor');
|
||||||
|
this.onlyBorderColor.addEventListener('click', ()=>{
|
||||||
|
const qrs = this.currentQrSet;
|
||||||
|
qrs.onlyBorderColor = this.onlyBorderColor.checked;
|
||||||
|
qrs.save();
|
||||||
|
this.currentQrSet.updateColor();
|
||||||
|
});
|
||||||
this.onQrSetChange();
|
this.onQrSetChange();
|
||||||
}
|
}
|
||||||
onQrSetChange() {
|
onQrSetChange() {
|
||||||
|
@ -152,6 +201,8 @@ export class SettingsUi {
|
||||||
this.disableSend.checked = this.currentQrSet.disableSend;
|
this.disableSend.checked = this.currentQrSet.disableSend;
|
||||||
this.placeBeforeInput.checked = this.currentQrSet.placeBeforeInput;
|
this.placeBeforeInput.checked = this.currentQrSet.placeBeforeInput;
|
||||||
this.injectInput.checked = this.currentQrSet.injectInput;
|
this.injectInput.checked = this.currentQrSet.injectInput;
|
||||||
|
this.color.color = this.currentQrSet.color ?? 'transparent';
|
||||||
|
this.onlyBorderColor.checked = this.currentQrSet.onlyBorderColor;
|
||||||
this.qrList.innerHTML = '';
|
this.qrList.innerHTML = '';
|
||||||
const qrsDom = this.currentQrSet.renderSettings();
|
const qrsDom = this.currentQrSet.renderSettings();
|
||||||
this.qrList.append(qrsDom);
|
this.qrList.append(qrsDom);
|
||||||
|
@ -265,7 +316,7 @@ export class SettingsUi {
|
||||||
const qrs = new QuickReplySet();
|
const qrs = new QuickReplySet();
|
||||||
qrs.name = name;
|
qrs.name = name;
|
||||||
qrs.addQuickReply();
|
qrs.addQuickReply();
|
||||||
const idx = QuickReplySet.list.findIndex(it=>it.name.localeCompare(name) == 1);
|
const idx = QuickReplySet.list.findIndex(it=>it.name.toLowerCase().localeCompare(name.toLowerCase()) == 1);
|
||||||
if (idx > -1) {
|
if (idx > -1) {
|
||||||
QuickReplySet.list.splice(idx, 0, qrs);
|
QuickReplySet.list.splice(idx, 0, qrs);
|
||||||
} else {
|
} else {
|
||||||
|
@ -321,7 +372,7 @@ export class SettingsUi {
|
||||||
this.prepareChatSetList();
|
this.prepareChatSetList();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const idx = QuickReplySet.list.findIndex(it=>it.name.localeCompare(qrs.name) == 1);
|
const idx = QuickReplySet.list.findIndex(it=>it.name.toLowerCase().localeCompare(qrs.name.toLowerCase()) == 1);
|
||||||
if (idx > -1) {
|
if (idx > -1) {
|
||||||
QuickReplySet.list.splice(idx, 0, qrs);
|
QuickReplySet.list.splice(idx, 0, qrs);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -33,11 +33,14 @@ export class ContextMenu {
|
||||||
*/
|
*/
|
||||||
build(qr, chainedMessage = null, hierarchy = [], labelHierarchy = []) {
|
build(qr, chainedMessage = null, hierarchy = [], labelHierarchy = []) {
|
||||||
const tree = {
|
const tree = {
|
||||||
|
icon: qr.icon,
|
||||||
|
showLabel: qr.showLabel,
|
||||||
label: qr.label,
|
label: qr.label,
|
||||||
message: (chainedMessage && qr.message ? `${chainedMessage} | ` : '') + qr.message,
|
message: (chainedMessage && qr.message ? `${chainedMessage} | ` : '') + qr.message,
|
||||||
children: [],
|
children: [],
|
||||||
};
|
};
|
||||||
qr.contextList.forEach((cl) => {
|
qr.contextList.forEach((cl) => {
|
||||||
|
if (!cl.set) return;
|
||||||
if (!hierarchy.includes(cl.set)) {
|
if (!hierarchy.includes(cl.set)) {
|
||||||
const nextHierarchy = [...hierarchy, cl.set];
|
const nextHierarchy = [...hierarchy, cl.set];
|
||||||
const nextLabelHierarchy = [...labelHierarchy, tree.label];
|
const nextLabelHierarchy = [...labelHierarchy, tree.label];
|
||||||
|
@ -45,6 +48,8 @@ export class ContextMenu {
|
||||||
cl.set.qrList.forEach(subQr => {
|
cl.set.qrList.forEach(subQr => {
|
||||||
const subTree = this.build(subQr, cl.isChained ? tree.message : null, nextHierarchy, nextLabelHierarchy);
|
const subTree = this.build(subQr, cl.isChained ? tree.message : null, nextHierarchy, nextLabelHierarchy);
|
||||||
tree.children.push(new MenuItem(
|
tree.children.push(new MenuItem(
|
||||||
|
subTree.icon,
|
||||||
|
subTree.showLabel,
|
||||||
subTree.label,
|
subTree.label,
|
||||||
subTree.message,
|
subTree.message,
|
||||||
(evt) => {
|
(evt) => {
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { MenuItem } from './MenuItem.js';
|
||||||
|
|
||||||
export class MenuHeader extends MenuItem {
|
export class MenuHeader extends MenuItem {
|
||||||
constructor(/**@type {String}*/label) {
|
constructor(/**@type {String}*/label) {
|
||||||
super(label, null, null);
|
super(null, null, label, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,21 +1,34 @@
|
||||||
import { SubMenu } from './SubMenu.js';
|
import { SubMenu } from './SubMenu.js';
|
||||||
|
|
||||||
export class MenuItem {
|
export class MenuItem {
|
||||||
/**@type {String}*/ label;
|
/**@type {string}*/ icon;
|
||||||
/**@type {Object}*/ value;
|
/**@type {boolean}*/ showLabel;
|
||||||
/**@type {Function}*/ callback;
|
/**@type {string}*/ label;
|
||||||
|
/**@type {object}*/ value;
|
||||||
|
/**@type {function}*/ callback;
|
||||||
/**@type {MenuItem[]}*/ childList = [];
|
/**@type {MenuItem[]}*/ childList = [];
|
||||||
/**@type {SubMenu}*/ subMenu;
|
/**@type {SubMenu}*/ subMenu;
|
||||||
/**@type {Boolean}*/ isForceExpanded = false;
|
/**@type {boolean}*/ isForceExpanded = false;
|
||||||
|
|
||||||
/**@type {HTMLElement}*/ root;
|
/**@type {HTMLElement}*/ root;
|
||||||
|
|
||||||
/**@type {Function}*/ onExpand;
|
/**@type {function}*/ onExpand;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
constructor(/**@type {String}*/label, /**@type {Object}*/value, /**@type {function}*/callback, /**@type {MenuItem[]}*/children = []) {
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} icon
|
||||||
|
* @param {boolean} showLabel
|
||||||
|
* @param {string} label
|
||||||
|
* @param {object} value
|
||||||
|
* @param {function} callback
|
||||||
|
* @param {MenuItem[]} children
|
||||||
|
*/
|
||||||
|
constructor(icon, showLabel, label, value, callback, children = []) {
|
||||||
|
this.icon = icon;
|
||||||
|
this.showLabel = showLabel;
|
||||||
this.label = label;
|
this.label = label;
|
||||||
this.value = value;
|
this.value = value;
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
|
@ -33,7 +46,21 @@ export class MenuItem {
|
||||||
if (this.callback) {
|
if (this.callback) {
|
||||||
item.addEventListener('click', (evt) => this.callback(evt, this));
|
item.addEventListener('click', (evt) => this.callback(evt, this));
|
||||||
}
|
}
|
||||||
item.append(this.label);
|
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);
|
||||||
|
item.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;
|
||||||
|
item.append(lbl);
|
||||||
|
}
|
||||||
if (this.childList.length > 0) {
|
if (this.childList.length > 0) {
|
||||||
item.classList.add('ctx-has-children');
|
item.classList.add('ctx-has-children');
|
||||||
const sub = new SubMenu(this.childList);
|
const sub = new SubMenu(this.childList);
|
||||||
|
|
|
@ -1,3 +1,20 @@
|
||||||
|
@keyframes qr--success {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
color: var(--SmartThemeBodyColor);
|
||||||
|
}
|
||||||
|
25%,
|
||||||
|
75% {
|
||||||
|
color: #51a351;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.qr--success {
|
||||||
|
animation-name: qr--success;
|
||||||
|
animation-duration: 3s;
|
||||||
|
animation-timing-function: linear;
|
||||||
|
animation-delay: 0s;
|
||||||
|
animation-iteration-count: 1;
|
||||||
|
}
|
||||||
#qr--bar {
|
#qr--bar {
|
||||||
outline: none;
|
outline: none;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
@ -41,6 +58,7 @@
|
||||||
}
|
}
|
||||||
#qr--bar > .qr--buttons,
|
#qr--bar > .qr--buttons,
|
||||||
#qr--popout > .qr--body > .qr--buttons {
|
#qr--popout > .qr--body > .qr--buttons {
|
||||||
|
--qr--color: transparent;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -49,10 +67,44 @@
|
||||||
gap: 5px;
|
gap: 5px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
#qr--bar > .qr--buttons.qr--color,
|
||||||
|
#qr--popout > .qr--body > .qr--buttons.qr--color {
|
||||||
|
background-color: var(--qr--color);
|
||||||
|
}
|
||||||
|
#qr--bar > .qr--buttons.qr--borderColor,
|
||||||
|
#qr--popout > .qr--body > .qr--buttons.qr--borderColor {
|
||||||
|
background-color: transparent;
|
||||||
|
border-left: 5px solid var(--qr--color);
|
||||||
|
border-right: 5px solid var(--qr--color);
|
||||||
|
}
|
||||||
|
#qr--bar > .qr--buttons:has(.qr--buttons.qr--color),
|
||||||
|
#qr--popout > .qr--body > .qr--buttons:has(.qr--buttons.qr--color) {
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
#qr--bar > .qr--buttons > .qr--buttons,
|
#qr--bar > .qr--buttons > .qr--buttons,
|
||||||
#qr--popout > .qr--body > .qr--buttons > .qr--buttons {
|
#qr--popout > .qr--body > .qr--buttons > .qr--buttons {
|
||||||
display: contents;
|
display: contents;
|
||||||
}
|
}
|
||||||
|
#qr--bar > .qr--buttons > .qr--buttons.qr--color .qr--button:before,
|
||||||
|
#qr--popout > .qr--body > .qr--buttons > .qr--buttons.qr--color .qr--button:before {
|
||||||
|
content: '';
|
||||||
|
background-color: var(--qr--color);
|
||||||
|
position: absolute;
|
||||||
|
inset: -5px;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
#qr--bar > .qr--buttons > .qr--buttons.qr--color.qr--borderColor .qr--button:before,
|
||||||
|
#qr--popout > .qr--body > .qr--buttons > .qr--buttons.qr--color.qr--borderColor .qr--button:before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
#qr--bar > .qr--buttons > .qr--buttons.qr--color.qr--borderColor:before,
|
||||||
|
#qr--popout > .qr--body > .qr--buttons > .qr--buttons.qr--color.qr--borderColor:before,
|
||||||
|
#qr--bar > .qr--buttons > .qr--buttons.qr--color.qr--borderColor:after,
|
||||||
|
#qr--popout > .qr--body > .qr--buttons > .qr--buttons.qr--color.qr--borderColor:after {
|
||||||
|
content: '';
|
||||||
|
width: 5px;
|
||||||
|
background-color: var(--qr--color);
|
||||||
|
}
|
||||||
#qr--bar > .qr--buttons .qr--button,
|
#qr--bar > .qr--buttons .qr--button,
|
||||||
#qr--popout > .qr--body > .qr--buttons .qr--button {
|
#qr--popout > .qr--body > .qr--buttons .qr--button {
|
||||||
color: var(--SmartThemeBodyColor);
|
color: var(--SmartThemeBodyColor);
|
||||||
|
@ -66,11 +118,19 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
#qr--bar > .qr--buttons .qr--button:hover,
|
#qr--bar > .qr--buttons .qr--button:hover,
|
||||||
#qr--popout > .qr--body > .qr--buttons .qr--button:hover {
|
#qr--popout > .qr--body > .qr--buttons .qr--button:hover {
|
||||||
opacity: 1;
|
background-color: #4d4d4d;
|
||||||
filter: brightness(1.2);
|
}
|
||||||
|
#qr--bar > .qr--buttons .qr--button .qr--hidden,
|
||||||
|
#qr--popout > .qr--body > .qr--buttons .qr--button .qr--hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
#qr--bar > .qr--buttons .qr--button .qr--button-icon,
|
||||||
|
#qr--popout > .qr--body > .qr--buttons .qr--button .qr--button-icon {
|
||||||
|
margin: 0 0.5em;
|
||||||
}
|
}
|
||||||
#qr--bar > .qr--buttons .qr--button > .qr--button-expander,
|
#qr--bar > .qr--buttons .qr--button > .qr--button-expander,
|
||||||
#qr--popout > .qr--body > .qr--buttons .qr--button > .qr--button-expander {
|
#qr--popout > .qr--body > .qr--buttons .qr--button > .qr--button-expander {
|
||||||
|
@ -170,36 +230,80 @@
|
||||||
#qr--settings #qr--set-qrList .qr--set-qrListContents {
|
#qr--settings #qr--set-qrList .qr--set-qrListContents {
|
||||||
padding: 0 0.5em;
|
padding: 0 0.5em;
|
||||||
}
|
}
|
||||||
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item {
|
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--set-itemAdder {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
opacity: 0;
|
||||||
|
transition: 100ms;
|
||||||
|
margin: -2px 0 -11px 0;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--set-itemAdder .qr--actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.25em;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--set-itemAdder .qr--actions .qr--action {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--set-itemAdder:before,
|
||||||
|
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--set-itemAdder:after {
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
border: 1px solid;
|
||||||
|
margin: 0 1em;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--set-itemAdder:hover,
|
||||||
|
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--set-itemAdder:focus-within {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--content {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
padding: 0.25em 0;
|
padding: 0.25em 0;
|
||||||
}
|
}
|
||||||
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item > :nth-child(1) {
|
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--content > :nth-child(2) {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item > :nth-child(2) {
|
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--content > :nth-child(2) {
|
||||||
flex: 1 1 25%;
|
flex: 1 1 25%;
|
||||||
}
|
}
|
||||||
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item > :nth-child(3) {
|
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--content > :nth-child(3) {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item > :nth-child(4) {
|
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--content > :nth-child(4) {
|
||||||
flex: 1 1 75%;
|
flex: 1 1 75%;
|
||||||
}
|
}
|
||||||
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item > :nth-child(5) {
|
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--content > :nth-child(5) {
|
||||||
flex: 0 0 auto;
|
flex: 0 1 auto;
|
||||||
|
display: flex;
|
||||||
|
gap: 0.25em;
|
||||||
|
justify-content: flex-end;
|
||||||
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item > .drag-handle {
|
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--content > .drag-handle {
|
||||||
padding: 0.75em;
|
padding: 0.75em;
|
||||||
}
|
}
|
||||||
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--set-itemLabel,
|
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--content .qr--set-itemLabelContainer {
|
||||||
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--action {
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5em;
|
||||||
|
}
|
||||||
|
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--content .qr--set-itemLabelContainer .qr--set-itemIcon:not(.fa-solid) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--content .qr--set-itemLabelContainer .qr--set-itemLabel {
|
||||||
|
min-width: 24px;
|
||||||
|
}
|
||||||
|
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--content .qr--set-itemLabel,
|
||||||
|
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--content .qr--action {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--set-itemMessage {
|
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--content .qr--set-itemMessage {
|
||||||
font-size: smaller;
|
font-size: smaller;
|
||||||
}
|
}
|
||||||
#qr--settings .qr--set-qrListActions {
|
#qr--settings .qr--set-qrListActions {
|
||||||
|
@ -212,6 +316,7 @@
|
||||||
#qr--qrOptions {
|
#qr--qrOptions {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
padding-right: 1px;
|
||||||
}
|
}
|
||||||
#qr--qrOptions > #qr--ctxEditor .qr--ctxItem {
|
#qr--qrOptions > #qr--ctxEditor .qr--ctxItem {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -219,6 +324,12 @@
|
||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
}
|
}
|
||||||
|
#qr--qrOptions > #qr--autoExec .checkbox_label {
|
||||||
|
text-wrap: nowrap;
|
||||||
|
}
|
||||||
|
#qr--qrOptions > #qr--autoExec .checkbox_label .fa-fw {
|
||||||
|
margin-right: 2px;
|
||||||
|
}
|
||||||
@media screen and (max-width: 750px) {
|
@media screen and (max-width: 750px) {
|
||||||
body .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor {
|
body .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
@ -238,6 +349,72 @@
|
||||||
.popup:has(#qr--modalEditor) {
|
.popup:has(#qr--modalEditor) {
|
||||||
aspect-ratio: unset;
|
aspect-ratio: unset;
|
||||||
}
|
}
|
||||||
|
.popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized) {
|
||||||
|
min-width: unset;
|
||||||
|
min-height: unset;
|
||||||
|
height: auto !important;
|
||||||
|
width: min-content !important;
|
||||||
|
position: absolute;
|
||||||
|
right: 1em;
|
||||||
|
top: 1em;
|
||||||
|
left: unset;
|
||||||
|
bottom: unset;
|
||||||
|
margin: unset;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized)::backdrop {
|
||||||
|
backdrop-filter: unset;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized) .popup-body {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
height: min-content;
|
||||||
|
width: min-content;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized) .popup-content {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized) .popup-content > #qr--modalEditor {
|
||||||
|
max-height: 50vh;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized) .popup-content > #qr--modalEditor > #qr--main,
|
||||||
|
.popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized) .popup-content > #qr--modalEditor > #qr--resizeHandle,
|
||||||
|
.popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized) .popup-content > #qr--modalEditor > #qr--qrOptions > h3,
|
||||||
|
.popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized) .popup-content > #qr--modalEditor > #qr--qrOptions > #qr--modal-executeButtons,
|
||||||
|
.popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized) .popup-content > #qr--modalEditor > #qr--qrOptions > #qr--modal-executeProgress {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized) .popup-content > #qr--modalEditor #qr--qrOptions {
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized) .popup-content > #qr--modalEditor #qr--modal-debugButtons .qr--modal-debugButton#qr--modal-maximize {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized) .popup-content > #qr--modalEditor #qr--modal-debugButtons .qr--modal-debugButton#qr--modal-minimize {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized) .popup-content > #qr--modalEditor #qr--modal-debugState {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor):has(.qr--isExecuting) .popup-controls {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor):has(.qr--isExecuting) .qr--highlight {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 50000;
|
||||||
|
pointer-events: none;
|
||||||
|
background-color: rgba(47, 150, 180, 0.5);
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor):has(.qr--isExecuting) .qr--highlight.qr--unresolved {
|
||||||
|
background-color: rgba(255, 255, 0, 0.5);
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor):has(.qr--isExecuting) .qr--highlight-secondary {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 50000;
|
||||||
|
pointer-events: none;
|
||||||
|
border: 3px solid red;
|
||||||
|
}
|
||||||
.popup:has(#qr--modalEditor) .popup-content {
|
.popup:has(#qr--modalEditor) .popup-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
@ -249,6 +426,67 @@
|
||||||
gap: 1em;
|
gap: 1em;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--main > h3:first-child,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--main > .qr--labels,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--main > .qr--modal-messageContainer > .qr--modal-editorSettings,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--qrOptions > h3:first-child,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--qrOptions > #qr--ctxEditor,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--qrOptions > .qr--ctxEditorActions,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--qrOptions > .qr--ctxEditorActions + h3,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--qrOptions > .qr--ctxEditorActions + h3 + div {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder > #qr--modal-message {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--modal-debugButtons {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--modal-debugButtons .menu_button:not(#qr--modal-minimize, #qr--modal-maximize) {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.5;
|
||||||
|
pointer-events: none;
|
||||||
|
transition: 200ms;
|
||||||
|
border-color: transparent;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting.qr--isPaused #qr--modal-debugButtons .menu_button:not(#qr--modal-minimize, #qr--modal-maximize) {
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting.qr--isPaused #qr--modal-debugButtons .menu_button:not(#qr--modal-minimize, #qr--modal-maximize)#qr--modal-resume {
|
||||||
|
animation-name: qr--debugPulse;
|
||||||
|
animation-duration: 1500ms;
|
||||||
|
animation-timing-function: ease-in-out;
|
||||||
|
animation-delay: 0s;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting.qr--isPaused #qr--modal-debugButtons .menu_button:not(#qr--modal-minimize, #qr--modal-maximize)#qr--modal-resume {
|
||||||
|
border-color: #51a351;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting.qr--isPaused #qr--modal-debugButtons .menu_button:not(#qr--modal-minimize, #qr--modal-maximize)#qr--modal-step {
|
||||||
|
border-color: var(--SmartThemeQuoteColor);
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting.qr--isPaused #qr--modal-debugButtons .menu_button:not(#qr--modal-minimize, #qr--modal-maximize)#qr--modal-stepInto {
|
||||||
|
border-color: var(--SmartThemeQuoteColor);
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting.qr--isPaused #qr--modal-debugButtons .menu_button:not(#qr--modal-minimize, #qr--modal-maximize)#qr--modal-stepOut {
|
||||||
|
border-color: var(--SmartThemeQuoteColor);
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--resizeHandle {
|
||||||
|
width: 6px;
|
||||||
|
background-color: var(--SmartThemeBorderColor);
|
||||||
|
border: 2px solid var(--SmartThemeBlurTintColor);
|
||||||
|
transition: border-color 200ms, background-color 200ms;
|
||||||
|
cursor: w-resize;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--resizeHandle:hover {
|
||||||
|
background-color: var(--SmartThemeQuoteColor);
|
||||||
|
border-color: var(--SmartThemeQuoteColor);
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--qrOptions {
|
||||||
|
width: var(--width, auto);
|
||||||
|
}
|
||||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main {
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -260,20 +498,114 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
|
padding: 1px;
|
||||||
}
|
}
|
||||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label {
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label {
|
||||||
flex: 1 1 1px;
|
flex: 1 1 1px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label > .qr--labelText {
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label.qr--fit,
|
||||||
flex: 1 1 auto;
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label.qr--fit {
|
||||||
}
|
|
||||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label > .qr--labelHint {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
}
|
|
||||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label > input {
|
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--inputGroup,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--inputGroup {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 0.5em;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--inputGroup input,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--inputGroup input {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--labelText,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--labelText {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--labelHint,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--labelHint {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label input,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label input {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--modal-switcherList,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--modal-switcherList {
|
||||||
|
background-color: var(--stcdx--bgColor);
|
||||||
|
border: 1px solid var(--SmartThemeBorderColor);
|
||||||
|
backdrop-filter: blur(var(--SmartThemeBlurStrength));
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: smaller;
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
overflow: auto;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0.5em;
|
||||||
|
max-height: 50vh;
|
||||||
|
list-style: none;
|
||||||
|
z-index: 40000;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--modal-switcherList .qr--modal-switcherItem,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--modal-switcherList .qr--modal-switcherItem {
|
||||||
|
display: flex;
|
||||||
|
gap: 1em;
|
||||||
|
text-align: left;
|
||||||
|
opacity: 0.75;
|
||||||
|
transition: 200ms;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--modal-switcherList .qr--modal-switcherItem:hover,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--modal-switcherList .qr--modal-switcherItem:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--modal-switcherList .qr--modal-switcherItem.qr--current,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--modal-switcherList .qr--modal-switcherItem.qr--current {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--modal-switcherList .qr--modal-switcherItem.qr--current .qr--label,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--modal-switcherList .qr--modal-switcherItem.qr--current .qr--label,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--modal-switcherList .qr--modal-switcherItem.qr--current .qr--id,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--modal-switcherList .qr--modal-switcherItem.qr--current .qr--id {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--modal-switcherList .qr--label,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--modal-switcherList .qr--label {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--modal-switcherList .qr--label .menu_button,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--modal-switcherList .qr--label .menu_button {
|
||||||
|
display: inline-block;
|
||||||
|
height: min-content;
|
||||||
|
width: min-content;
|
||||||
|
margin: 0 0.5em 0 0;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--modal-switcherList .qr--id,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--modal-switcherList .qr--id {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--modal-switcherList .qr--id:before,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--modal-switcherList .qr--id:before {
|
||||||
|
content: "[";
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--modal-switcherList .qr--id:after,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--modal-switcherList .qr--id:after {
|
||||||
|
content: "]";
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--modal-switcherList .qr--message,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--modal-switcherList .qr--message {
|
||||||
|
height: 1lh;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer {
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
|
@ -283,8 +615,9 @@
|
||||||
}
|
}
|
||||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > .qr--modal-editorSettings {
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > .qr--modal-editorSettings {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
gap: 1em;
|
column-gap: 1em;
|
||||||
color: var(--grey70);
|
color: var(--grey70);
|
||||||
font-size: smaller;
|
font-size: smaller;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
|
@ -308,6 +641,11 @@
|
||||||
background-color: var(--ac-style-color-background);
|
background-color: var(--ac-style-color-background);
|
||||||
color: var(--ac-style-color-text);
|
color: var(--ac-style-color-text);
|
||||||
}
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder.qr--noSyntax > #qr--modal-message::-webkit-scrollbar,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder.qr--noSyntax > #qr--modal-message::-webkit-scrollbar-thumb {
|
||||||
|
visibility: visible;
|
||||||
|
cursor: unset;
|
||||||
|
}
|
||||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder.qr--noSyntax > #qr--modal-message::selection {
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder.qr--noSyntax > #qr--modal-message::selection {
|
||||||
color: unset;
|
color: unset;
|
||||||
background-color: rgba(108 171 251 / 0.25);
|
background-color: rgba(108 171 251 / 0.25);
|
||||||
|
@ -357,11 +695,15 @@
|
||||||
font-family: var(--monoFontFamily);
|
font-family: var(--monoFontFamily);
|
||||||
padding: 0.75em;
|
padding: 0.75em;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
border: none;
|
|
||||||
resize: none;
|
resize: none;
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
border: 1px solid var(--SmartThemeBorderColor);
|
border: 1px solid var(--SmartThemeBorderColor);
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-icon {
|
||||||
|
height: 100%;
|
||||||
|
aspect-ratio: 1 / 1;
|
||||||
}
|
}
|
||||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeButtons {
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeButtons {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -410,6 +752,46 @@
|
||||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeButtons #qr--modal-stop {
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeButtons #qr--modal-stop {
|
||||||
border-color: #d78872;
|
border-color: #d78872;
|
||||||
}
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugButtons {
|
||||||
|
display: none;
|
||||||
|
gap: 1em;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugButtons .qr--modal-debugButton {
|
||||||
|
aspect-ratio: 1.25 / 1;
|
||||||
|
width: 2.25em;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugButtons .qr--modal-debugButton:not(.fa-solid) {
|
||||||
|
border-width: 1px;
|
||||||
|
border-style: solid;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugButtons .qr--modal-debugButton:not(.fa-solid):after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 3px;
|
||||||
|
background-color: var(--SmartThemeBodyColor);
|
||||||
|
mask-size: contain;
|
||||||
|
mask-position: center;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugButtons .qr--modal-debugButton#qr--modal-resume:after {
|
||||||
|
mask-image: url('/img/step-resume.svg');
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugButtons .qr--modal-debugButton#qr--modal-step:after {
|
||||||
|
mask-image: url('/img/step-over.svg');
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugButtons .qr--modal-debugButton#qr--modal-stepInto:after {
|
||||||
|
mask-image: url('/img/step-into.svg');
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugButtons .qr--modal-debugButton#qr--modal-stepOut:after {
|
||||||
|
mask-image: url('/img/step-out.svg');
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugButtons .qr--modal-debugButton#qr--modal-maximize {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-send_textarea {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeProgress {
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeProgress {
|
||||||
--prog: 0;
|
--prog: 0;
|
||||||
--progColor: #92befc;
|
--progColor: #92befc;
|
||||||
|
@ -417,6 +799,7 @@
|
||||||
--progSuccessColor: #51a351;
|
--progSuccessColor: #51a351;
|
||||||
--progErrorColor: #bd362f;
|
--progErrorColor: #bd362f;
|
||||||
--progAbortedColor: #d78872;
|
--progAbortedColor: #d78872;
|
||||||
|
flex: 0 0 auto;
|
||||||
height: 0.5em;
|
height: 0.5em;
|
||||||
background-color: var(--black50a);
|
background-color: var(--black50a);
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -469,6 +852,7 @@
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
min-width: 100%;
|
min-width: 100%;
|
||||||
width: 0;
|
width: 0;
|
||||||
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeResult.qr--hasResult {
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeResult.qr--hasResult {
|
||||||
display: block;
|
display: block;
|
||||||
|
@ -476,6 +860,150 @@
|
||||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeResult:before {
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeResult:before {
|
||||||
content: 'Result: ';
|
content: 'Result: ';
|
||||||
}
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState {
|
||||||
|
display: none;
|
||||||
|
text-align: left;
|
||||||
|
font-size: smaller;
|
||||||
|
font-family: var(--monoFontFamily);
|
||||||
|
color: white;
|
||||||
|
padding: 0.5em 0;
|
||||||
|
overflow: auto;
|
||||||
|
min-width: 100%;
|
||||||
|
width: 0;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState.qr--active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 0fr 1fr 1fr;
|
||||||
|
column-gap: 0em;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--title {
|
||||||
|
grid-column: 1 / 4;
|
||||||
|
font-weight: bold;
|
||||||
|
font-family: var(--mainFontFamily);
|
||||||
|
background-color: var(--black50a);
|
||||||
|
padding: 0.25em;
|
||||||
|
margin-top: 0.5em;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe {
|
||||||
|
display: contents;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var:nth-child(2n + 1) .qr--key,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro:nth-child(2n + 1) .qr--key,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe:nth-child(2n + 1) .qr--key,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var:nth-child(2n + 1) .qr--val,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro:nth-child(2n + 1) .qr--val,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe:nth-child(2n + 1) .qr--val {
|
||||||
|
background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.25);
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var:nth-child(2n + 1) .qr--val:nth-child(2n),
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro:nth-child(2n + 1) .qr--val:nth-child(2n),
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe:nth-child(2n + 1) .qr--val:nth-child(2n) {
|
||||||
|
background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.125);
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var:nth-child(2n + 1) .qr--val:hover,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro:nth-child(2n + 1) .qr--val:hover,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe:nth-child(2n + 1) .qr--val:hover {
|
||||||
|
background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.5);
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var:nth-child(2n) .qr--val:nth-child(2n),
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro:nth-child(2n) .qr--val:nth-child(2n),
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe:nth-child(2n) .qr--val:nth-child(2n) {
|
||||||
|
background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.0625);
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var:nth-child(2n) .qr--val:hover,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro:nth-child(2n) .qr--val:hover,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe:nth-child(2n) .qr--val:hover {
|
||||||
|
background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.5);
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var.qr--isHidden .qr--key,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro.qr--isHidden .qr--key,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe.qr--isHidden .qr--key,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var.qr--isHidden .qr--val,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro.qr--isHidden .qr--val,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe.qr--isHidden .qr--val {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var .qr--val,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro .qr--val,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe .qr--val {
|
||||||
|
grid-column: 2 / 4;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var .qr--val.qr--singleCol,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro .qr--val.qr--singleCol,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe .qr--val.qr--singleCol {
|
||||||
|
grid-column: unset;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var .qr--val.qr--simple:before,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro .qr--val.qr--simple:before,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe .qr--val.qr--simple:before,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var .qr--val.qr--simple:after,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro .qr--val.qr--simple:after,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe .qr--val.qr--simple:after {
|
||||||
|
content: '"';
|
||||||
|
color: var(--SmartThemeQuoteColor);
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var .qr--val.qr--unresolved:after,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro .qr--val.qr--unresolved:after,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe .qr--val.qr--unresolved:after {
|
||||||
|
content: '-UNRESOLVED-';
|
||||||
|
font-style: italic;
|
||||||
|
color: var(--SmartThemeQuoteColor);
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--key {
|
||||||
|
margin-left: 0.5em;
|
||||||
|
padding-right: 1em;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--key:after {
|
||||||
|
content: ": ";
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe > .qr--key:before,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro > .qr--key:before {
|
||||||
|
content: "{{";
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe > .qr--key:after,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro > .qr--key:after {
|
||||||
|
content: "}}: ";
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--scope {
|
||||||
|
display: contents;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--scope .qr--pipe .qr--key,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--scope .qr--pipe .qr--val {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--stack {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 0fr;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--stack .qr--title {
|
||||||
|
grid-column: 1 / 3;
|
||||||
|
font-weight: bold;
|
||||||
|
font-family: var(--mainFontFamily);
|
||||||
|
background-color: var(--black50a);
|
||||||
|
padding: 0.25em;
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--stack .qr--item {
|
||||||
|
display: contents;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--stack .qr--item:nth-child(2n + 1) .qr--name,
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--stack .qr--item:nth-child(2n + 1) .qr--source {
|
||||||
|
background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.25);
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--stack .qr--item .qr--name {
|
||||||
|
margin-left: 0.5em;
|
||||||
|
}
|
||||||
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--stack .qr--item .qr--source {
|
||||||
|
opacity: 0.5;
|
||||||
|
text-align: right;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
@keyframes qr--progressPulse {
|
@keyframes qr--progressPulse {
|
||||||
0%,
|
0%,
|
||||||
100% {
|
100% {
|
||||||
|
@ -485,9 +1013,63 @@
|
||||||
background-color: var(--progFlashColor);
|
background-color: var(--progFlashColor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@keyframes qr--debugPulse {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
border-color: #51a351;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
border-color: #92befc;
|
||||||
|
}
|
||||||
|
}
|
||||||
.popup.qr--hide {
|
.popup.qr--hide {
|
||||||
opacity: 0 !important;
|
opacity: 0 !important;
|
||||||
}
|
}
|
||||||
.popup.qr--hide::backdrop {
|
.popup.qr--hide::backdrop {
|
||||||
opacity: 0 !important;
|
opacity: 0 !important;
|
||||||
}
|
}
|
||||||
|
.popup.qr--hide::backdrop {
|
||||||
|
opacity: 0 !important;
|
||||||
|
}
|
||||||
|
.popup:has(.qr--transferModal) .popup-button-ok {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
white-space: pre;
|
||||||
|
font-weight: normal;
|
||||||
|
box-shadow: 0 0 0;
|
||||||
|
transition: 200ms;
|
||||||
|
}
|
||||||
|
.popup:has(.qr--transferModal) .popup-button-ok:after {
|
||||||
|
content: 'Transfer';
|
||||||
|
height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.popup:has(.qr--transferModal) .qr--copy {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
white-space: pre;
|
||||||
|
font-weight: normal;
|
||||||
|
box-shadow: 0 0 0;
|
||||||
|
transition: 200ms;
|
||||||
|
}
|
||||||
|
.popup:has(.qr--transferModal) .qr--copy:after {
|
||||||
|
content: 'Copy';
|
||||||
|
height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.popup:has(.qr--transferModal):has(.qr--transferSelect:focus) .popup-button-ok {
|
||||||
|
font-weight: bold;
|
||||||
|
box-shadow: 0 0 10px;
|
||||||
|
}
|
||||||
|
.popup:has(.qr--transferModal):has(.qr--transferSelect:focus).qr--isCopy .popup-button-ok {
|
||||||
|
font-weight: normal;
|
||||||
|
box-shadow: 0 0 0;
|
||||||
|
}
|
||||||
|
.popup:has(.qr--transferModal):has(.qr--transferSelect:focus).qr--isCopy .qr--copy {
|
||||||
|
font-weight: bold;
|
||||||
|
box-shadow: 0 0 10px;
|
||||||
|
}
|
||||||
|
|
|
@ -1,3 +1,18 @@
|
||||||
|
@keyframes qr--success {
|
||||||
|
0%, 100% {
|
||||||
|
color: var(--SmartThemeBodyColor);
|
||||||
|
}
|
||||||
|
25%, 75% {
|
||||||
|
color: rgb(81, 163, 81);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.qr--success {
|
||||||
|
animation-name: qr--success;
|
||||||
|
animation-duration: 3s;
|
||||||
|
animation-timing-function: linear;
|
||||||
|
animation-delay: 0s;
|
||||||
|
animation-iteration-count: 1;
|
||||||
|
}
|
||||||
#qr--bar {
|
#qr--bar {
|
||||||
outline: none;
|
outline: none;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
@ -50,6 +65,18 @@
|
||||||
#qr--bar,
|
#qr--bar,
|
||||||
#qr--popout>.qr--body {
|
#qr--popout>.qr--body {
|
||||||
>.qr--buttons {
|
>.qr--buttons {
|
||||||
|
--qr--color: transparent;
|
||||||
|
&.qr--color {
|
||||||
|
background-color: var(--qr--color);
|
||||||
|
}
|
||||||
|
&.qr--borderColor {
|
||||||
|
background-color: transparent;
|
||||||
|
border-left: 5px solid var(--qr--color);
|
||||||
|
border-right: 5px solid var(--qr--color);
|
||||||
|
}
|
||||||
|
&:has(.qr--buttons.qr--color) {
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -60,6 +87,25 @@
|
||||||
|
|
||||||
>.qr--buttons {
|
>.qr--buttons {
|
||||||
display: contents;
|
display: contents;
|
||||||
|
&.qr--color {
|
||||||
|
.qr--button:before {
|
||||||
|
content: '';
|
||||||
|
background-color: var(--qr--color);
|
||||||
|
position: absolute;
|
||||||
|
inset: -5px;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
&.qr--borderColor {
|
||||||
|
.qr--button:before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
&:before, &:after {
|
||||||
|
content: '';
|
||||||
|
width: 5px;
|
||||||
|
background-color: var(--qr--color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.qr--button {
|
.qr--button {
|
||||||
|
@ -75,10 +121,17 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
opacity: 1;
|
background-color: rgb(30% 30% 30%);
|
||||||
filter: brightness(1.2);
|
}
|
||||||
|
|
||||||
|
.qr--hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.qr--button-icon {
|
||||||
|
margin: 0 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
>.qr--button-expander {
|
>.qr--button-expander {
|
||||||
|
@ -211,14 +264,41 @@
|
||||||
.qr--set-qrListContents> {
|
.qr--set-qrListContents> {
|
||||||
padding: 0 0.5em;
|
padding: 0 0.5em;
|
||||||
|
|
||||||
>.qr--set-item {
|
>.qr--set-item .qr--set-itemAdder {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
opacity: 0;
|
||||||
|
transition: 100ms;
|
||||||
|
margin: -2px 0 -11px 0;
|
||||||
|
position: relative;
|
||||||
|
.qr--actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.25em;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
.qr--action {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:before, &:after {
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
border: 1px solid;
|
||||||
|
margin: 0 1em;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
&:hover, &:focus-within {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>.qr--set-item .qr--content {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
padding: 0.25em 0;
|
padding: 0.25em 0;
|
||||||
|
|
||||||
> :nth-child(1) {
|
> :nth-child(2) {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,13 +315,29 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
> :nth-child(5) {
|
> :nth-child(5) {
|
||||||
flex: 0 0 auto;
|
flex: 0 1 auto;
|
||||||
|
display: flex;
|
||||||
|
gap: 0.25em;
|
||||||
|
justify-content: flex-end;
|
||||||
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
>.drag-handle {
|
>.drag-handle {
|
||||||
padding: 0.75em;
|
padding: 0.75em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.qr--set-itemLabelContainer {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5em;
|
||||||
|
.qr--set-itemIcon:not(.fa-solid) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.qr--set-itemLabel {
|
||||||
|
min-width: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.qr--set-itemLabel,
|
.qr--set-itemLabel,
|
||||||
.qr--action {
|
.qr--action {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
@ -251,6 +347,8 @@
|
||||||
font-size: smaller;
|
font-size: smaller;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -270,6 +368,7 @@
|
||||||
#qr--qrOptions {
|
#qr--qrOptions {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
padding-right: 1px;
|
||||||
|
|
||||||
>#qr--ctxEditor {
|
>#qr--ctxEditor {
|
||||||
.qr--ctxItem {
|
.qr--ctxItem {
|
||||||
|
@ -279,6 +378,15 @@
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
>#qr--autoExec {
|
||||||
|
.checkbox_label {
|
||||||
|
text-wrap: nowrap;
|
||||||
|
|
||||||
|
.fa-fw {
|
||||||
|
margin-right: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -306,6 +414,78 @@
|
||||||
.popup:has(#qr--modalEditor) {
|
.popup:has(#qr--modalEditor) {
|
||||||
aspect-ratio: unset;
|
aspect-ratio: unset;
|
||||||
|
|
||||||
|
&:has(.qr--isExecuting.qr--minimized) {
|
||||||
|
min-width: unset;
|
||||||
|
min-height: unset;
|
||||||
|
height: auto !important;
|
||||||
|
width: min-content !important;
|
||||||
|
position: absolute;
|
||||||
|
right: 1em;
|
||||||
|
top: 1em;
|
||||||
|
left: unset;
|
||||||
|
bottom: unset;
|
||||||
|
margin: unset;
|
||||||
|
padding: 0;
|
||||||
|
&::backdrop {
|
||||||
|
backdrop-filter: unset;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
.popup-body {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
height: min-content;
|
||||||
|
width: min-content;
|
||||||
|
}
|
||||||
|
.popup-content {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
margin-top: 0;
|
||||||
|
|
||||||
|
> #qr--modalEditor {
|
||||||
|
max-height: 50vh;
|
||||||
|
> #qr--main,
|
||||||
|
> #qr--resizeHandle,
|
||||||
|
> #qr--qrOptions > h3,
|
||||||
|
> #qr--qrOptions > #qr--modal-executeButtons,
|
||||||
|
> #qr--qrOptions > #qr--modal-executeProgress
|
||||||
|
{
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
#qr--qrOptions {
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
#qr--modal-debugButtons .qr--modal-debugButton#qr--modal-maximize {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
#qr--modal-debugButtons .qr--modal-debugButton#qr--modal-minimize {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
#qr--modal-debugState {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:has(.qr--isExecuting) {
|
||||||
|
.popup-controls {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qr--highlight {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 50000;
|
||||||
|
pointer-events: none;
|
||||||
|
background-color: rgb(47 150 180 / 0.5);
|
||||||
|
&.qr--unresolved {
|
||||||
|
background-color: rgb(255 255 0 / 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.qr--highlight-secondary {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 50000;
|
||||||
|
pointer-events: none;
|
||||||
|
border: 3px solid red;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.popup-content {
|
.popup-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
@ -317,86 +497,213 @@
|
||||||
gap: 1em;
|
gap: 1em;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
|
&.qr--isExecuting {
|
||||||
|
#qr--main > h3:first-child,
|
||||||
|
#qr--main > .qr--labels,
|
||||||
|
#qr--main > .qr--modal-messageContainer > .qr--modal-editorSettings,
|
||||||
|
#qr--qrOptions > h3:first-child,
|
||||||
|
#qr--qrOptions > #qr--ctxEditor,
|
||||||
|
#qr--qrOptions > .qr--ctxEditorActions,
|
||||||
|
#qr--qrOptions > .qr--ctxEditorActions + h3,
|
||||||
|
#qr--qrOptions > .qr--ctxEditorActions + h3 + div
|
||||||
|
{
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
#qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder > #qr--modal-message {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
#qr--modal-debugButtons {
|
||||||
|
display: flex;
|
||||||
|
.menu_button:not(#qr--modal-minimize, #qr--modal-maximize) {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.5;
|
||||||
|
pointer-events: none;
|
||||||
|
transition: 200ms;
|
||||||
|
border-color: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.qr--isPaused #qr--modal-debugButtons {
|
||||||
|
.menu_button:not(#qr--modal-minimize, #qr--modal-maximize) {
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: all;
|
||||||
|
&#qr--modal-resume {
|
||||||
|
animation-name: qr--debugPulse;
|
||||||
|
animation-duration: 1500ms;
|
||||||
|
animation-timing-function: ease-in-out;
|
||||||
|
animation-delay: 0s;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
}
|
||||||
|
&#qr--modal-resume {
|
||||||
|
border-color: rgb(81, 163, 81);
|
||||||
|
}
|
||||||
|
&#qr--modal-step {
|
||||||
|
border-color: var(--SmartThemeQuoteColor);
|
||||||
|
}
|
||||||
|
&#qr--modal-stepInto {
|
||||||
|
border-color: var(--SmartThemeQuoteColor);
|
||||||
|
}
|
||||||
|
&#qr--modal-stepOut {
|
||||||
|
border-color: var(--SmartThemeQuoteColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#qr--resizeHandle {
|
||||||
|
width: 6px;
|
||||||
|
background-color: var(--SmartThemeBorderColor);
|
||||||
|
border: 2px solid var(--SmartThemeBlurTintColor);
|
||||||
|
transition: border-color 200ms, background-color 200ms;
|
||||||
|
cursor: w-resize;
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--SmartThemeQuoteColor);
|
||||||
|
border-color: var(--SmartThemeQuoteColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#qr--qrOptions {
|
||||||
|
width: var(--width, auto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
> #qr--main {
|
> #qr--main {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
> .qr--labels {
|
> .qr--labels {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
|
padding: 1px;
|
||||||
>label {
|
> label, > .label {
|
||||||
flex: 1 1 1px;
|
flex: 1 1 1px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
>.qr--labelText {
|
&.qr--fit {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.qr--inputGroup {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 0.5em;
|
||||||
|
input {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
>.qr--labelHint {
|
.qr--labelText {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
}
|
}
|
||||||
|
.qr--labelHint {
|
||||||
>input {
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
input {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
|
.qr--modal-switcherList {
|
||||||
|
background-color: var(--stcdx--bgColor);
|
||||||
|
border: 1px solid var(--SmartThemeBorderColor);
|
||||||
|
backdrop-filter: blur(var(--SmartThemeBlurStrength));
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: smaller;
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
overflow: auto;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0.5em;
|
||||||
|
max-height: 50vh;
|
||||||
|
list-style: none;
|
||||||
|
z-index: 40000;
|
||||||
|
max-width: 100%;
|
||||||
|
.qr--modal-switcherItem {
|
||||||
|
display: flex;
|
||||||
|
gap: 1em;
|
||||||
|
text-align: left;
|
||||||
|
opacity: 0.75;
|
||||||
|
transition: 200ms;
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
&.qr--current {
|
||||||
|
opacity: 1;
|
||||||
|
.qr--label, .qr--id {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.qr--label {
|
||||||
|
white-space: nowrap;
|
||||||
|
.menu_button {
|
||||||
|
display: inline-block;
|
||||||
|
height: min-content;
|
||||||
|
width: min-content;
|
||||||
|
margin: 0 0.5em 0 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.qr--id {
|
||||||
|
opacity: 0.5;
|
||||||
|
&:before { content: "["; }
|
||||||
|
&:after { content: "]"; }
|
||||||
|
}
|
||||||
|
.qr--message {
|
||||||
|
height: 1lh;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> .qr--modal-messageContainer {
|
> .qr--modal-messageContainer {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
> .qr--modal-editorSettings {
|
> .qr--modal-editorSettings {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
gap: 1em;
|
column-gap: 1em;
|
||||||
color: var(--grey70);
|
color: var(--grey70);
|
||||||
font-size: smaller;
|
font-size: smaller;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
|
|
||||||
> .checkbox_label {
|
> .checkbox_label {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
||||||
> input {
|
> input {
|
||||||
font-size: inherit;
|
font-size: inherit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> #qr--modal-messageHolder {
|
> #qr--modal-messageHolder {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
display: grid;
|
display: grid;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
&.qr--noSyntax {
|
&.qr--noSyntax {
|
||||||
> #qr--modal-messageSyntax {
|
> #qr--modal-messageSyntax {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
> #qr--modal-message {
|
> #qr--modal-message {
|
||||||
background-color: var(--ac-style-color-background);
|
background-color: var(--ac-style-color-background);
|
||||||
color: var(--ac-style-color-text);
|
color: var(--ac-style-color-text);
|
||||||
|
&::-webkit-scrollbar, &::-webkit-scrollbar-thumb {
|
||||||
|
visibility: visible;
|
||||||
|
cursor: unset;
|
||||||
|
}
|
||||||
&::selection {
|
&::selection {
|
||||||
color: unset;
|
color: unset;
|
||||||
background-color: rgba(108 171 251 / 0.25);
|
background-color: rgba(108 171 251 / 0.25);
|
||||||
|
|
||||||
@supports (color: rgb(from white r g b / 0.25)) {
|
@supports (color: rgb(from white r g b / 0.25)) {
|
||||||
background-color: rgb(from var(--ac-style-color-matchedText) r g b / 0.25);
|
background-color: rgb(from var(--ac-style-color-matchedText) r g b / 0.25);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> #qr--modal-messageSyntax {
|
> #qr--modal-messageSyntax {
|
||||||
grid-column: 1;
|
grid-column: 1;
|
||||||
grid-row: 1;
|
grid-row: 1;
|
||||||
|
@ -406,12 +713,10 @@
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
min-width: 100%;
|
min-width: 100%;
|
||||||
width: 0;
|
width: 0;
|
||||||
|
|
||||||
> #qr--modal-messageSyntaxInner {
|
> #qr--modal-messageSyntaxInner {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> #qr--modal-message {
|
> #qr--modal-message {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
color: transparent;
|
color: transparent;
|
||||||
|
@ -419,38 +724,35 @@
|
||||||
grid-row: 1;
|
grid-row: 1;
|
||||||
caret-color: var(--ac-style-color-text);
|
caret-color: var(--ac-style-color-text);
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
&::-webkit-scrollbar, &::-webkit-scrollbar-thumb {
|
||||||
&::-webkit-scrollbar,
|
|
||||||
&::-webkit-scrollbar-thumb {
|
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
&::selection {
|
&::selection {
|
||||||
color: transparent;
|
color: transparent;
|
||||||
background-color: rgba(108 171 251 / 0.25);
|
background-color: rgba(108 171 251 / 0.25);
|
||||||
|
|
||||||
@supports (color: rgb(from white r g b / 0.25)) {
|
@supports (color: rgb(from white r g b / 0.25)) {
|
||||||
background-color: rgb(from var(--ac-style-color-matchedText) r g b / 0.25);
|
background-color: rgb(from var(--ac-style-color-matchedText) r g b / 0.25);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#qr--modal-message, #qr--modal-messageSyntaxInner {
|
||||||
#qr--modal-message,
|
|
||||||
#qr--modal-messageSyntaxInner {
|
|
||||||
font-family: var(--monoFontFamily);
|
font-family: var(--monoFontFamily);
|
||||||
padding: 0.75em;
|
padding: 0.75em;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
border: none;
|
|
||||||
resize: none;
|
resize: none;
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
border: 1px solid var(--SmartThemeBorderColor);
|
border: 1px solid var(--SmartThemeBorderColor);
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#qr--modal-icon {
|
||||||
|
height: 100%;
|
||||||
|
aspect-ratio: 1 / 1;
|
||||||
|
}
|
||||||
#qr--modal-executeButtons {
|
#qr--modal-executeButtons {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 1em;
|
gap: 1em;
|
||||||
|
@ -510,6 +812,47 @@
|
||||||
border-color: rgb(215, 136, 114);
|
border-color: rgb(215, 136, 114);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#qr--modal-debugButtons {
|
||||||
|
display: none;
|
||||||
|
gap: 1em;
|
||||||
|
.qr--modal-debugButton {
|
||||||
|
aspect-ratio: 1.25 / 1;
|
||||||
|
width: 2.25em;
|
||||||
|
position: relative;
|
||||||
|
&:not(.fa-solid) {
|
||||||
|
border-width: 1px;
|
||||||
|
border-style: solid;
|
||||||
|
&:after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 3px;
|
||||||
|
background-color: var(--SmartThemeBodyColor);
|
||||||
|
mask-size: contain;
|
||||||
|
mask-position: center;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&#qr--modal-resume:after {
|
||||||
|
mask-image: url('/img/step-resume.svg');
|
||||||
|
}
|
||||||
|
&#qr--modal-step:after {
|
||||||
|
mask-image: url('/img/step-over.svg');
|
||||||
|
}
|
||||||
|
&#qr--modal-stepInto:after {
|
||||||
|
mask-image: url('/img/step-into.svg');
|
||||||
|
}
|
||||||
|
&#qr--modal-stepOut:after {
|
||||||
|
mask-image: url('/img/step-out.svg');
|
||||||
|
}
|
||||||
|
&#qr--modal-maximize {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#qr--modal-send_textarea {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
#qr--modal-executeProgress {
|
#qr--modal-executeProgress {
|
||||||
--prog: 0;
|
--prog: 0;
|
||||||
|
@ -518,6 +861,7 @@
|
||||||
--progSuccessColor: rgb(81, 163, 81);
|
--progSuccessColor: rgb(81, 163, 81);
|
||||||
--progErrorColor: rgb(189, 54, 47);
|
--progErrorColor: rgb(189, 54, 47);
|
||||||
--progAbortedColor: rgb(215, 136, 114);
|
--progAbortedColor: rgb(215, 136, 114);
|
||||||
|
flex: 0 0 auto;
|
||||||
height: 0.5em;
|
height: 0.5em;
|
||||||
background-color: var(--black50a);
|
background-color: var(--black50a);
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -588,6 +932,135 @@
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
min-width: 100%;
|
min-width: 100%;
|
||||||
width: 0;
|
width: 0;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
#qr--modal-debugState {
|
||||||
|
display: none;
|
||||||
|
&.qr--active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
text-align: left;
|
||||||
|
font-size: smaller;
|
||||||
|
font-family: var(--monoFontFamily);
|
||||||
|
// background-color: rgb(146, 190, 252);
|
||||||
|
color: white;
|
||||||
|
padding: 0.5em 0;
|
||||||
|
overflow: auto;
|
||||||
|
min-width: 100%;
|
||||||
|
width: 0;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
|
||||||
|
.qr--scope {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 0fr 1fr 1fr;
|
||||||
|
column-gap: 0em;
|
||||||
|
.qr--title {
|
||||||
|
grid-column: 1 / 4;
|
||||||
|
font-weight: bold;
|
||||||
|
font-family: var(--mainFontFamily);
|
||||||
|
background-color: var(--black50a);
|
||||||
|
padding: 0.25em;
|
||||||
|
margin-top: 0.5em;
|
||||||
|
}
|
||||||
|
.qr--var, .qr--macro, .qr--pipe {
|
||||||
|
display: contents;
|
||||||
|
&:nth-child(2n + 1) {
|
||||||
|
.qr--key, .qr--val {
|
||||||
|
background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.25);
|
||||||
|
}
|
||||||
|
.qr--val {
|
||||||
|
&:nth-child(2n) {
|
||||||
|
background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.125);
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:nth-child(2n) {
|
||||||
|
.qr--val {
|
||||||
|
&:nth-child(2n) {
|
||||||
|
background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.0625);
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.qr--isHidden {
|
||||||
|
.qr--key, .qr--val {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.qr--val {
|
||||||
|
grid-column: 2 / 4;
|
||||||
|
&.qr--singleCol {
|
||||||
|
grid-column: unset;
|
||||||
|
}
|
||||||
|
&.qr--simple {
|
||||||
|
&:before, &:after {
|
||||||
|
content: '"';
|
||||||
|
color: var(--SmartThemeQuoteColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.qr--unresolved {
|
||||||
|
&:after {
|
||||||
|
content: '-UNRESOLVED-';
|
||||||
|
font-style: italic;
|
||||||
|
color: var(--SmartThemeQuoteColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.qr--key {
|
||||||
|
margin-left: 0.5em;
|
||||||
|
padding-right: 1em;
|
||||||
|
&:after { content: ": "; }
|
||||||
|
}
|
||||||
|
.qr--pipe, .qr--macro {
|
||||||
|
> .qr--key {
|
||||||
|
&:before { content: "{{"; }
|
||||||
|
&:after { content: "}}: "; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.qr--scope {
|
||||||
|
display: contents;
|
||||||
|
.qr--pipe {
|
||||||
|
.qr--key, .qr--val {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.qr--stack {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 0fr;
|
||||||
|
.qr--title {
|
||||||
|
grid-column: 1 / 3;
|
||||||
|
font-weight: bold;
|
||||||
|
font-family: var(--mainFontFamily);
|
||||||
|
background-color: var(--black50a);
|
||||||
|
padding: 0.25em;
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
.qr--item {
|
||||||
|
display: contents;
|
||||||
|
&:nth-child(2n + 1) {
|
||||||
|
.qr--name, .qr--source {
|
||||||
|
background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.25);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.qr--name {
|
||||||
|
margin-left: 0.5em;
|
||||||
|
}
|
||||||
|
.qr--source {
|
||||||
|
opacity: 0.5;
|
||||||
|
text-align: right;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -605,10 +1078,75 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes qr--debugPulse {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
border-color: rgb(81, 163, 81);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
border-color: rgb(146, 190, 252);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.popup.qr--hide {
|
.popup.qr--hide {
|
||||||
opacity: 0 !important;
|
opacity: 0 !important;
|
||||||
|
&::backdrop {
|
||||||
|
opacity: 0 !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.popup.qr--hide::backdrop {
|
.popup.qr--hide::backdrop {
|
||||||
opacity: 0 !important;
|
opacity: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.popup:has(.qr--transferModal) {
|
||||||
|
.popup-button-ok {
|
||||||
|
&:after {
|
||||||
|
content: 'Transfer';
|
||||||
|
height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
white-space: pre;
|
||||||
|
font-weight: normal;
|
||||||
|
box-shadow: 0 0 0;
|
||||||
|
transition: 200ms;
|
||||||
|
}
|
||||||
|
.qr--copy {
|
||||||
|
&:after {
|
||||||
|
content: 'Copy';
|
||||||
|
height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
white-space: pre;
|
||||||
|
font-weight: normal;
|
||||||
|
box-shadow: 0 0 0;
|
||||||
|
transition: 200ms;
|
||||||
|
}
|
||||||
|
&:has(.qr--transferSelect:focus) {
|
||||||
|
.popup-button-ok {
|
||||||
|
font-weight: bold;
|
||||||
|
box-shadow: 0 0 10px;
|
||||||
|
}
|
||||||
|
&.qr--isCopy {
|
||||||
|
.popup-button-ok {
|
||||||
|
font-weight: normal;
|
||||||
|
box-shadow: 0 0 0;
|
||||||
|
}
|
||||||
|
.qr--copy {
|
||||||
|
font-weight: bold;
|
||||||
|
box-shadow: 0 0 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -526,6 +526,15 @@ export class Popup {
|
||||||
|
|
||||||
return this.#promise;
|
return this.#promise;
|
||||||
}
|
}
|
||||||
|
async completeAffirmative() {
|
||||||
|
return await this.complete(POPUP_RESULT.AFFIRMATIVE);
|
||||||
|
}
|
||||||
|
async completeNegative() {
|
||||||
|
return await this.complete(POPUP_RESULT.NEGATIVE);
|
||||||
|
}
|
||||||
|
async completeCancelled() {
|
||||||
|
return await this.complete(POPUP_RESULT.CANCELLED);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hides the popup, using the internal resolver to return the value to the original show promise
|
* Hides the popup, using the internal resolver to return the value to the original show promise
|
||||||
|
|
|
@ -45,7 +45,7 @@ import { FILTER_TYPES } from './filters.js';
|
||||||
import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js';
|
import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js';
|
||||||
import { SlashCommand } from './slash-commands/SlashCommand.js';
|
import { SlashCommand } from './slash-commands/SlashCommand.js';
|
||||||
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
|
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
|
||||||
import { AUTOCOMPLETE_WIDTH } from './autocomplete/AutoComplete.js';
|
import { AUTOCOMPLETE_SELECT_KEY, AUTOCOMPLETE_WIDTH } from './autocomplete/AutoComplete.js';
|
||||||
import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';
|
import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';
|
||||||
import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
|
import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
|
||||||
import { POPUP_TYPE, callGenericPopup } from './popup.js';
|
import { POPUP_TYPE, callGenericPopup } from './popup.js';
|
||||||
|
@ -277,6 +277,7 @@ let power_user = {
|
||||||
left: AUTOCOMPLETE_WIDTH.CHAT,
|
left: AUTOCOMPLETE_WIDTH.CHAT,
|
||||||
right: AUTOCOMPLETE_WIDTH.CHAT,
|
right: AUTOCOMPLETE_WIDTH.CHAT,
|
||||||
},
|
},
|
||||||
|
select: AUTOCOMPLETE_SELECT_KEY.TAB + AUTOCOMPLETE_SELECT_KEY.ENTER,
|
||||||
},
|
},
|
||||||
parser: {
|
parser: {
|
||||||
/**@type {Object.<PARSER_FLAG,boolean>} */
|
/**@type {Object.<PARSER_FLAG,boolean>} */
|
||||||
|
@ -1494,6 +1495,9 @@ function loadPowerUserSettings(settings, data) {
|
||||||
if (power_user.stscript.autocomplete.style === undefined) {
|
if (power_user.stscript.autocomplete.style === undefined) {
|
||||||
power_user.stscript.autocomplete.style = power_user.stscript.autocomplete_style || defaultStscript.autocomplete.style;
|
power_user.stscript.autocomplete.style = power_user.stscript.autocomplete_style || defaultStscript.autocomplete.style;
|
||||||
}
|
}
|
||||||
|
if (power_user.stscript.autocomplete.select === undefined) {
|
||||||
|
power_user.stscript.autocomplete.select = defaultStscript.autocomplete.select;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (power_user.stscript.parser === undefined) {
|
if (power_user.stscript.parser === undefined) {
|
||||||
power_user.stscript.parser = defaultStscript.parser;
|
power_user.stscript.parser = defaultStscript.parser;
|
||||||
|
@ -1658,6 +1662,7 @@ function loadPowerUserSettings(settings, data) {
|
||||||
$('#stscript_matching').val(power_user.stscript.matching ?? 'fuzzy');
|
$('#stscript_matching').val(power_user.stscript.matching ?? 'fuzzy');
|
||||||
$('#stscript_autocomplete_style').val(power_user.stscript.autocomplete.style ?? 'theme');
|
$('#stscript_autocomplete_style').val(power_user.stscript.autocomplete.style ?? 'theme');
|
||||||
document.body.setAttribute('data-stscript-style', power_user.stscript.autocomplete.style);
|
document.body.setAttribute('data-stscript-style', power_user.stscript.autocomplete.style);
|
||||||
|
$('#stscript_autocomplete_select').val(power_user.stscript.autocomplete.select ?? (AUTOCOMPLETE_SELECT_KEY.TAB + AUTOCOMPLETE_SELECT_KEY.ENTER));
|
||||||
$('#stscript_parser_flag_strict_escaping').prop('checked', power_user.stscript.parser.flags[PARSER_FLAG.STRICT_ESCAPING] ?? false);
|
$('#stscript_parser_flag_strict_escaping').prop('checked', power_user.stscript.parser.flags[PARSER_FLAG.STRICT_ESCAPING] ?? false);
|
||||||
$('#stscript_parser_flag_replace_getvar').prop('checked', power_user.stscript.parser.flags[PARSER_FLAG.REPLACE_GETVAR] ?? false);
|
$('#stscript_parser_flag_replace_getvar').prop('checked', power_user.stscript.parser.flags[PARSER_FLAG.REPLACE_GETVAR] ?? false);
|
||||||
$('#stscript_autocomplete_font_scale').val(power_user.stscript.autocomplete.font.scale ?? defaultStscript.autocomplete.font.scale);
|
$('#stscript_autocomplete_font_scale').val(power_user.stscript.autocomplete.font.scale ?? defaultStscript.autocomplete.font.scale);
|
||||||
|
@ -3848,6 +3853,12 @@ $(document).ready(() => {
|
||||||
saveSettingsDebounced();
|
saveSettingsDebounced();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('#stscript_autocomplete_select').on('change', function () {
|
||||||
|
const value = $(this).find(':selected').val();
|
||||||
|
power_user.stscript.autocomplete.select = parseInt(String(value));
|
||||||
|
saveSettingsDebounced();
|
||||||
|
});
|
||||||
|
|
||||||
$('#stscript_autocomplete_font_scale').on('input', function () {
|
$('#stscript_autocomplete_font_scale').on('input', function () {
|
||||||
const value = $(this).val();
|
const value = $(this).val();
|
||||||
$('#stscript_autocomplete_font_scale_counter').val(value);
|
$('#stscript_autocomplete_font_scale_counter').val(value);
|
||||||
|
@ -4016,6 +4027,7 @@ $(document).ready(() => {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
helpString: 'Enter message deletion mode, and auto-deletes last N messages if numeric argument is provided.',
|
helpString: 'Enter message deletion mode, and auto-deletes last N messages if numeric argument is provided.',
|
||||||
|
returns: 'The text of the deleted messages.',
|
||||||
}));
|
}));
|
||||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||||
name: 'cut',
|
name: 'cut',
|
||||||
|
|
|
@ -51,7 +51,7 @@ import { autoSelectPersona, retriggerFirstMessageOnEmptyChat, setPersonaLockStat
|
||||||
import { addEphemeralStoppingString, chat_styles, flushEphemeralStoppingStrings, power_user } from './power-user.js';
|
import { addEphemeralStoppingString, chat_styles, flushEphemeralStoppingStrings, power_user } from './power-user.js';
|
||||||
import { textgen_types, textgenerationwebui_settings } from './textgen-settings.js';
|
import { textgen_types, textgenerationwebui_settings } from './textgen-settings.js';
|
||||||
import { decodeTextTokens, getFriendlyTokenizerName, getTextTokens, getTokenCountAsync } from './tokenizers.js';
|
import { decodeTextTokens, getFriendlyTokenizerName, getTextTokens, getTokenCountAsync } from './tokenizers.js';
|
||||||
import { debounce, delay, isFalseBoolean, isTrueBoolean, stringToRange, trimToEndSentence, trimToStartSentence, waitUntilCondition } from './utils.js';
|
import { debounce, delay, isFalseBoolean, isTrueBoolean, showFontAwesomePicker, stringToRange, trimToEndSentence, trimToStartSentence, waitUntilCondition } from './utils.js';
|
||||||
import { registerVariableCommands, resolveVariable } from './variables.js';
|
import { registerVariableCommands, resolveVariable } from './variables.js';
|
||||||
import { background_settings } from './backgrounds.js';
|
import { background_settings } from './backgrounds.js';
|
||||||
import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js';
|
import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js';
|
||||||
|
@ -64,6 +64,9 @@ import { SlashCommandNamedArgumentAssignment } from './slash-commands/SlashComma
|
||||||
import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';
|
import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';
|
||||||
import { POPUP_TYPE, Popup, callGenericPopup } from './popup.js';
|
import { POPUP_TYPE, Popup, callGenericPopup } from './popup.js';
|
||||||
import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
|
import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
|
||||||
|
import { SlashCommandDebugController } from './slash-commands/SlashCommandDebugController.js';
|
||||||
|
import { SlashCommandBreakController } from './slash-commands/SlashCommandBreakController.js';
|
||||||
|
import { SlashCommandExecutionError } from './slash-commands/SlashCommandExecutionError.js';
|
||||||
export {
|
export {
|
||||||
executeSlashCommands, executeSlashCommandsWithOptions, getSlashCommandsHelp, registerSlashCommand,
|
executeSlashCommands, executeSlashCommandsWithOptions, getSlashCommandsHelp, registerSlashCommand,
|
||||||
};
|
};
|
||||||
|
@ -1120,10 +1123,10 @@ export function initDefaultSlashCommands() {
|
||||||
unnamedArgumentList: [
|
unnamedArgumentList: [
|
||||||
SlashCommandArgument.fromProps({
|
SlashCommandArgument.fromProps({
|
||||||
description: 'scoped variable or qr label',
|
description: 'scoped variable or qr label',
|
||||||
typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING],
|
typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.CLOSURE],
|
||||||
isRequired: true,
|
isRequired: true,
|
||||||
enumProvider: () => [
|
enumProvider: (executor, scope) => [
|
||||||
...commonEnumProviders.variables('scope')(),
|
...commonEnumProviders.variables('scope')(executor, scope),
|
||||||
...(typeof window['qrEnumProviderExecutables'] === 'function') ? window['qrEnumProviderExecutables']() : [],
|
...(typeof window['qrEnumProviderExecutables'] === 'function') ? window['qrEnumProviderExecutables']() : [],
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
|
@ -1477,6 +1480,21 @@ export function initDefaultSlashCommands() {
|
||||||
],
|
],
|
||||||
helpString: 'Sets the specified prompt manager entry/entries on or off.',
|
helpString: 'Sets the specified prompt manager entry/entries on or off.',
|
||||||
}));
|
}));
|
||||||
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'pick-icon',
|
||||||
|
callback: async()=>((await showFontAwesomePicker()) ?? false).toString(),
|
||||||
|
returns: 'The chosen icon name or false if cancelled.',
|
||||||
|
helpString: `
|
||||||
|
<div>Opens a popup with all the available Font Awesome icons and returns the selected icon's name.</div>
|
||||||
|
<div>
|
||||||
|
<strong>Example:</strong>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<pre><code>/pick-icon |\n/if left={{pipe}} rule=eq right=false\n\telse={: /echo chosen icon: "{{pipe}}" :}\n\t{: /echo cancelled icon selection :}\n|</code></pre>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
}));
|
||||||
|
|
||||||
registerVariableCommands();
|
registerVariableCommands();
|
||||||
}
|
}
|
||||||
|
@ -1821,6 +1839,11 @@ async function runCallback(args, name) {
|
||||||
throw new Error('No name provided for /run command');
|
throw new Error('No name provided for /run command');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (name instanceof SlashCommandClosure) {
|
||||||
|
name.breakController = new SlashCommandBreakController();
|
||||||
|
return (await name.execute())?.pipe;
|
||||||
|
}
|
||||||
|
|
||||||
/**@type {SlashCommandScope} */
|
/**@type {SlashCommandScope} */
|
||||||
const scope = args._scope;
|
const scope = args._scope;
|
||||||
if (scope.existsVariable(name)) {
|
if (scope.existsVariable(name)) {
|
||||||
|
@ -1829,6 +1852,11 @@ async function runCallback(args, name) {
|
||||||
throw new Error(`"${name}" is not callable.`);
|
throw new Error(`"${name}" is not callable.`);
|
||||||
}
|
}
|
||||||
closure.scope.parent = scope;
|
closure.scope.parent = scope;
|
||||||
|
closure.breakController = new SlashCommandBreakController();
|
||||||
|
if (args._debugController && !closure.debugController) {
|
||||||
|
closure.debugController = args._debugController;
|
||||||
|
}
|
||||||
|
while (closure.providedArgumentList.pop());
|
||||||
closure.argumentList.forEach(arg => {
|
closure.argumentList.forEach(arg => {
|
||||||
if (Object.keys(args).includes(arg.name)) {
|
if (Object.keys(args).includes(arg.name)) {
|
||||||
const providedArg = new SlashCommandNamedArgumentAssignment();
|
const providedArg = new SlashCommandNamedArgumentAssignment();
|
||||||
|
@ -1847,9 +1875,14 @@ async function runCallback(args, name) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
name = name.trim();
|
name = name.trim();
|
||||||
return await window['executeQuickReplyByName'](name, args);
|
/**@type {ExecuteSlashCommandsOptions} */
|
||||||
|
const options = {
|
||||||
|
abortController: args._abortController,
|
||||||
|
debugController: args._debugController,
|
||||||
|
};
|
||||||
|
return await window['executeQuickReplyByName'](name, args, options);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(`Error running Quick Reply "${name}": ${error.message}`, 'Error');
|
throw new Error(`Error running Quick Reply "${name}": ${error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3428,7 +3461,9 @@ const clearCommandProgressDebounced = debounce(clearCommandProgress);
|
||||||
* @prop {boolean} [handleExecutionErrors] (false) Whether to handle execution errors (show toast on error) or throw
|
* @prop {boolean} [handleExecutionErrors] (false) Whether to handle execution errors (show toast on error) or throw
|
||||||
* @prop {{[id:PARSER_FLAG]:boolean}} [parserFlags] (null) Parser flags to apply
|
* @prop {{[id:PARSER_FLAG]:boolean}} [parserFlags] (null) Parser flags to apply
|
||||||
* @prop {SlashCommandAbortController} [abortController] (null) Controller used to abort or pause command execution
|
* @prop {SlashCommandAbortController} [abortController] (null) Controller used to abort or pause command execution
|
||||||
|
* @prop {SlashCommandDebugController} [debugController] (null) Controller used to control debug execution
|
||||||
* @prop {(done:number, total:number)=>void} [onProgress] (null) Callback to handle progress events
|
* @prop {(done:number, total:number)=>void} [onProgress] (null) Callback to handle progress events
|
||||||
|
* @prop {string} [source] (null) String indicating where the code come from (e.g., QR name)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -3436,6 +3471,7 @@ const clearCommandProgressDebounced = debounce(clearCommandProgress);
|
||||||
* @prop {SlashCommandScope} [scope] (null) The scope to be used when executing the commands.
|
* @prop {SlashCommandScope} [scope] (null) The scope to be used when executing the commands.
|
||||||
* @prop {{[id:PARSER_FLAG]:boolean}} [parserFlags] (null) Parser flags to apply
|
* @prop {{[id:PARSER_FLAG]:boolean}} [parserFlags] (null) Parser flags to apply
|
||||||
* @prop {boolean} [clearChatInput] (false) Whether to clear the chat input textarea
|
* @prop {boolean} [clearChatInput] (false) Whether to clear the chat input textarea
|
||||||
|
* @prop {string} [source] (null) String indicating where the code come from (e.g., QR name)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -3451,6 +3487,7 @@ export async function executeSlashCommandsOnChatInput(text, options = {}) {
|
||||||
scope: null,
|
scope: null,
|
||||||
parserFlags: null,
|
parserFlags: null,
|
||||||
clearChatInput: false,
|
clearChatInput: false,
|
||||||
|
source: null,
|
||||||
}, options);
|
}, options);
|
||||||
|
|
||||||
isExecutingCommandsFromChatInput = true;
|
isExecutingCommandsFromChatInput = true;
|
||||||
|
@ -3473,13 +3510,21 @@ export async function executeSlashCommandsOnChatInput(text, options = {}) {
|
||||||
|
|
||||||
/**@type {SlashCommandClosureResult} */
|
/**@type {SlashCommandClosureResult} */
|
||||||
let result = null;
|
let result = null;
|
||||||
|
let currentProgress = 0;
|
||||||
try {
|
try {
|
||||||
commandsFromChatInputAbortController = new SlashCommandAbortController();
|
commandsFromChatInputAbortController = new SlashCommandAbortController();
|
||||||
result = await executeSlashCommandsWithOptions(text, {
|
result = await executeSlashCommandsWithOptions(text, {
|
||||||
abortController: commandsFromChatInputAbortController,
|
abortController: commandsFromChatInputAbortController,
|
||||||
onProgress: (done, total) => ta.style.setProperty('--prog', `${done / total * 100}%`),
|
onProgress: (done, total) => {
|
||||||
|
const newProgress = done / total;
|
||||||
|
if (newProgress > currentProgress) {
|
||||||
|
currentProgress = newProgress;
|
||||||
|
ta.style.setProperty('--prog', `${newProgress * 100}%`);
|
||||||
|
}
|
||||||
|
},
|
||||||
parserFlags: options.parserFlags,
|
parserFlags: options.parserFlags,
|
||||||
scope: options.scope,
|
scope: options.scope,
|
||||||
|
source: options.source,
|
||||||
});
|
});
|
||||||
if (commandsFromChatInputAbortController.signal.aborted) {
|
if (commandsFromChatInputAbortController.signal.aborted) {
|
||||||
document.querySelector('#form_sheld').classList.add('script_aborted');
|
document.querySelector('#form_sheld').classList.add('script_aborted');
|
||||||
|
@ -3492,8 +3537,24 @@ export async function executeSlashCommandsOnChatInput(text, options = {}) {
|
||||||
result.isError = true;
|
result.isError = true;
|
||||||
result.errorMessage = e.message || 'An unknown error occurred';
|
result.errorMessage = e.message || 'An unknown error occurred';
|
||||||
if (e.cause !== 'abort') {
|
if (e.cause !== 'abort') {
|
||||||
|
if (e instanceof SlashCommandExecutionError) {
|
||||||
|
/**@type {SlashCommandExecutionError}*/
|
||||||
|
const ex = e;
|
||||||
|
const toast = `
|
||||||
|
<div>${ex.message}</div>
|
||||||
|
<div>Line: ${ex.line} Column: ${ex.column}</div>
|
||||||
|
<pre style="text-align:left;">${ex.hint}</pre>
|
||||||
|
`;
|
||||||
|
const clickHint = '<p>Click to see details</p>';
|
||||||
|
toastr.error(
|
||||||
|
`${toast}${clickHint}`,
|
||||||
|
'SlashCommandExecutionError',
|
||||||
|
{ escapeHtml: false, timeOut: 10000, onclick: () => callPopup(toast, 'text') },
|
||||||
|
);
|
||||||
|
} else {
|
||||||
toastr.error(result.errorMessage);
|
toastr.error(result.errorMessage);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
delay(1000).then(() => clearCommandProgressDebounced());
|
delay(1000).then(() => clearCommandProgressDebounced());
|
||||||
|
|
||||||
|
@ -3520,7 +3581,9 @@ async function executeSlashCommandsWithOptions(text, options = {}) {
|
||||||
handleExecutionErrors: false,
|
handleExecutionErrors: false,
|
||||||
parserFlags: null,
|
parserFlags: null,
|
||||||
abortController: null,
|
abortController: null,
|
||||||
|
debugController: null,
|
||||||
onProgress: null,
|
onProgress: null,
|
||||||
|
source: null,
|
||||||
}, options);
|
}, options);
|
||||||
|
|
||||||
let closure;
|
let closure;
|
||||||
|
@ -3528,6 +3591,8 @@ async function executeSlashCommandsWithOptions(text, options = {}) {
|
||||||
closure = parser.parse(text, true, options.parserFlags, options.abortController ?? new SlashCommandAbortController());
|
closure = parser.parse(text, true, options.parserFlags, options.abortController ?? new SlashCommandAbortController());
|
||||||
closure.scope.parent = options.scope;
|
closure.scope.parent = options.scope;
|
||||||
closure.onProgress = options.onProgress;
|
closure.onProgress = options.onProgress;
|
||||||
|
closure.debugController = options.debugController;
|
||||||
|
closure.source = options.source;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (options.handleParserErrors && e instanceof SlashCommandParserError) {
|
if (options.handleParserErrors && e instanceof SlashCommandParserError) {
|
||||||
/**@type {SlashCommandParserError}*/
|
/**@type {SlashCommandParserError}*/
|
||||||
|
@ -3559,7 +3624,23 @@ async function executeSlashCommandsWithOptions(text, options = {}) {
|
||||||
return result;
|
return result;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (options.handleExecutionErrors) {
|
if (options.handleExecutionErrors) {
|
||||||
|
if (e instanceof SlashCommandExecutionError) {
|
||||||
|
/**@type {SlashCommandExecutionError}*/
|
||||||
|
const ex = e;
|
||||||
|
const toast = `
|
||||||
|
<div>${ex.message}</div>
|
||||||
|
<div>Line: ${ex.line} Column: ${ex.column}</div>
|
||||||
|
<pre style="text-align:left;">${ex.hint}</pre>
|
||||||
|
`;
|
||||||
|
const clickHint = '<p>Click to see details</p>';
|
||||||
|
toastr.error(
|
||||||
|
`${toast}${clickHint}`,
|
||||||
|
'SlashCommandExecutionError',
|
||||||
|
{ escapeHtml: false, timeOut: 10000, onclick: () => callPopup(toast, 'text') },
|
||||||
|
);
|
||||||
|
} else {
|
||||||
toastr.error(e.message);
|
toastr.error(e.message);
|
||||||
|
}
|
||||||
const result = new SlashCommandClosureResult();
|
const result = new SlashCommandClosureResult();
|
||||||
result.isError = true;
|
result.isError = true;
|
||||||
result.errorMessage = e.message;
|
result.errorMessage = e.message;
|
||||||
|
@ -3596,6 +3677,7 @@ async function executeSlashCommands(text, handleParserErrors = true, scope = nul
|
||||||
*
|
*
|
||||||
* @param {HTMLTextAreaElement} textarea The textarea to receive autocomplete
|
* @param {HTMLTextAreaElement} textarea The textarea to receive autocomplete
|
||||||
* @param {Boolean} isFloating Whether to show the auto complete as a floating window (e.g., large QR editor)
|
* @param {Boolean} isFloating Whether to show the auto complete as a floating window (e.g., large QR editor)
|
||||||
|
* @returns {Promise<AutoComplete>}
|
||||||
*/
|
*/
|
||||||
export async function setSlashCommandAutoComplete(textarea, isFloating = false) {
|
export async function setSlashCommandAutoComplete(textarea, isFloating = false) {
|
||||||
function canUseNegativeLookbehind() {
|
function canUseNegativeLookbehind() {
|
||||||
|
@ -3619,6 +3701,7 @@ export async function setSlashCommandAutoComplete(textarea, isFloating = false)
|
||||||
async (text, index) => await parser.getNameAt(text, index),
|
async (text, index) => await parser.getNameAt(text, index),
|
||||||
isFloating,
|
isFloating,
|
||||||
);
|
);
|
||||||
|
return ac;
|
||||||
}
|
}
|
||||||
/**@type {HTMLTextAreaElement} */
|
/**@type {HTMLTextAreaElement} */
|
||||||
const sendTextarea = document.querySelector('#send_textarea');
|
const sendTextarea = document.querySelector('#send_textarea');
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { SlashCommandAbortController } from './SlashCommandAbortController.js';
|
import { SlashCommandAbortController } from './SlashCommandAbortController.js';
|
||||||
import { SlashCommandArgument, SlashCommandNamedArgument } from './SlashCommandArgument.js';
|
import { SlashCommandArgument, SlashCommandNamedArgument } from './SlashCommandArgument.js';
|
||||||
import { SlashCommandClosure } from './SlashCommandClosure.js';
|
import { SlashCommandClosure } from './SlashCommandClosure.js';
|
||||||
|
import { SlashCommandDebugController } from './SlashCommandDebugController.js';
|
||||||
import { PARSER_FLAG } from './SlashCommandParser.js';
|
import { PARSER_FLAG } from './SlashCommandParser.js';
|
||||||
import { SlashCommandScope } from './SlashCommandScope.js';
|
import { SlashCommandScope } from './SlashCommandScope.js';
|
||||||
|
|
||||||
|
@ -12,6 +13,7 @@ import { SlashCommandScope } from './SlashCommandScope.js';
|
||||||
* _scope:SlashCommandScope,
|
* _scope:SlashCommandScope,
|
||||||
* _parserFlags:{[id:PARSER_FLAG]:boolean},
|
* _parserFlags:{[id:PARSER_FLAG]:boolean},
|
||||||
* _abortController:SlashCommandAbortController,
|
* _abortController:SlashCommandAbortController,
|
||||||
|
* _debugController:SlashCommandDebugController,
|
||||||
* _hasUnnamedArgument:boolean,
|
* _hasUnnamedArgument:boolean,
|
||||||
* [id:string]:string|SlashCommandClosure,
|
* [id:string]:string|SlashCommandClosure,
|
||||||
* }} NamedArguments
|
* }} NamedArguments
|
||||||
|
@ -36,6 +38,7 @@ export class SlashCommand {
|
||||||
* @param {(namedArguments:NamedArguments|NamedArgumentsCapture, unnamedArguments:string|SlashCommandClosure|(string|SlashCommandClosure)[])=>string|SlashCommandClosure|Promise<string|SlashCommandClosure>} [props.callback]
|
* @param {(namedArguments:NamedArguments|NamedArgumentsCapture, unnamedArguments:string|SlashCommandClosure|(string|SlashCommandClosure)[])=>string|SlashCommandClosure|Promise<string|SlashCommandClosure>} [props.callback]
|
||||||
* @param {string} [props.helpString]
|
* @param {string} [props.helpString]
|
||||||
* @param {boolean} [props.splitUnnamedArgument]
|
* @param {boolean} [props.splitUnnamedArgument]
|
||||||
|
* @param {Number} [props.splitUnnamedArgumentCount]
|
||||||
* @param {string[]} [props.aliases]
|
* @param {string[]} [props.aliases]
|
||||||
* @param {string} [props.returns]
|
* @param {string} [props.returns]
|
||||||
* @param {SlashCommandNamedArgument[]} [props.namedArgumentList]
|
* @param {SlashCommandNamedArgument[]} [props.namedArgumentList]
|
||||||
|
@ -50,9 +53,10 @@ export class SlashCommand {
|
||||||
|
|
||||||
|
|
||||||
/**@type {string}*/ name;
|
/**@type {string}*/ name;
|
||||||
/**@type {(namedArguments:{_pipe:string|SlashCommandClosure, _scope:SlashCommandScope, _abortController:SlashCommandAbortController, [id:string]:string|SlashCommandClosure}, unnamedArguments:string|SlashCommandClosure|(string|SlashCommandClosure)[])=>string|SlashCommandClosure|Promise<string|SlashCommandClosure>}*/ callback;
|
/**@type {(namedArguments:{_scope:SlashCommandScope, _abortController:SlashCommandAbortController, [id:string]:string|SlashCommandClosure}, unnamedArguments:string|SlashCommandClosure|(string|SlashCommandClosure)[])=>string|SlashCommandClosure|Promise<string|SlashCommandClosure>}*/ callback;
|
||||||
/**@type {string}*/ helpString;
|
/**@type {string}*/ helpString;
|
||||||
/**@type {boolean}*/ splitUnnamedArgument = false;
|
/**@type {boolean}*/ splitUnnamedArgument = false;
|
||||||
|
/**@type {Number}*/ splitUnnamedArgumentCount;
|
||||||
/**@type {string[]}*/ aliases = [];
|
/**@type {string[]}*/ aliases = [];
|
||||||
/**@type {string}*/ returns;
|
/**@type {string}*/ returns;
|
||||||
/**@type {SlashCommandNamedArgument[]}*/ namedArgumentList = [];
|
/**@type {SlashCommandNamedArgument[]}*/ namedArgumentList = [];
|
||||||
|
@ -61,6 +65,10 @@ export class SlashCommand {
|
||||||
/**@type {Object.<string, HTMLElement>}*/ helpCache = {};
|
/**@type {Object.<string, HTMLElement>}*/ helpCache = {};
|
||||||
/**@type {Object.<string, DocumentFragment>}*/ helpDetailsCache = {};
|
/**@type {Object.<string, DocumentFragment>}*/ helpDetailsCache = {};
|
||||||
|
|
||||||
|
/**@type {boolean}*/ isExtension = false;
|
||||||
|
/**@type {boolean}*/ isThirdParty = false;
|
||||||
|
/**@type {string}*/ source;
|
||||||
|
|
||||||
renderHelpItem(key = null) {
|
renderHelpItem(key = null) {
|
||||||
key = key ?? this.name;
|
key = key ?? this.name;
|
||||||
if (!this.helpCache[key]) {
|
if (!this.helpCache[key]) {
|
||||||
|
@ -225,12 +233,35 @@ export class SlashCommand {
|
||||||
const aliasList = [cmd.name, ...(cmd.aliases ?? [])].filter(it=>it != key);
|
const aliasList = [cmd.name, ...(cmd.aliases ?? [])].filter(it=>it != key);
|
||||||
const specs = document.createElement('div'); {
|
const specs = document.createElement('div'); {
|
||||||
specs.classList.add('specs');
|
specs.classList.add('specs');
|
||||||
|
const head = document.createElement('div'); {
|
||||||
|
head.classList.add('head');
|
||||||
const name = document.createElement('div'); {
|
const name = document.createElement('div'); {
|
||||||
name.classList.add('name');
|
name.classList.add('name');
|
||||||
name.classList.add('monospace');
|
name.classList.add('monospace');
|
||||||
name.title = 'command name';
|
name.title = 'command name';
|
||||||
name.textContent = `/${key}`;
|
name.textContent = `/${key}`;
|
||||||
specs.append(name);
|
head.append(name);
|
||||||
|
}
|
||||||
|
const src = document.createElement('div'); {
|
||||||
|
src.classList.add('source');
|
||||||
|
src.classList.add('fa-solid');
|
||||||
|
if (this.isExtension) {
|
||||||
|
src.classList.add('isExtension');
|
||||||
|
src.classList.add('fa-cubes');
|
||||||
|
if (this.isThirdParty) src.classList.add('isThirdParty');
|
||||||
|
else src.classList.add('isCore');
|
||||||
|
} else {
|
||||||
|
src.classList.add('isCore');
|
||||||
|
src.classList.add('fa-star-of-life');
|
||||||
|
}
|
||||||
|
src.title = [
|
||||||
|
this.isExtension ? 'Extension' : 'Core',
|
||||||
|
this.isThirdParty ? 'Third Party' : (this.isExtension ? 'Core' : null),
|
||||||
|
this.source,
|
||||||
|
].filter(it=>it).join('\n');
|
||||||
|
head.append(src);
|
||||||
|
}
|
||||||
|
specs.append(head);
|
||||||
}
|
}
|
||||||
const body = document.createElement('div'); {
|
const body = document.createElement('div'); {
|
||||||
body.classList.add('body');
|
body.classList.add('body');
|
||||||
|
@ -303,6 +334,8 @@ export class SlashCommand {
|
||||||
for (const arg of unnamedArguments) {
|
for (const arg of unnamedArguments) {
|
||||||
const listItem = document.createElement('li'); {
|
const listItem = document.createElement('li'); {
|
||||||
listItem.classList.add('argumentItem');
|
listItem.classList.add('argumentItem');
|
||||||
|
const argSpec = document.createElement('div'); {
|
||||||
|
argSpec.classList.add('argumentSpec');
|
||||||
const argItem = document.createElement('div'); {
|
const argItem = document.createElement('div'); {
|
||||||
argItem.classList.add('argument');
|
argItem.classList.add('argument');
|
||||||
argItem.classList.add('unnamedArgument');
|
argItem.classList.add('unnamedArgument');
|
||||||
|
@ -336,7 +369,17 @@ export class SlashCommand {
|
||||||
argItem.append(types);
|
argItem.append(types);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
listItem.append(argItem);
|
argSpec.append(argItem);
|
||||||
|
}
|
||||||
|
if (arg.defaultValue !== null) {
|
||||||
|
const argDefault = document.createElement('div'); {
|
||||||
|
argDefault.classList.add('argument-default');
|
||||||
|
argDefault.title = 'default value';
|
||||||
|
argDefault.textContent = arg.defaultValue.toString();
|
||||||
|
argSpec.append(argDefault);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
listItem.append(argSpec);
|
||||||
}
|
}
|
||||||
const desc = document.createElement('div'); {
|
const desc = document.createElement('div'); {
|
||||||
desc.classList.add('argument-description');
|
desc.classList.add('argument-description');
|
||||||
|
|
|
@ -2,6 +2,9 @@ import { SlashCommandClosure } from './SlashCommandClosure.js';
|
||||||
import { commonEnumProviders } from './SlashCommandCommonEnumsProvider.js';
|
import { commonEnumProviders } from './SlashCommandCommonEnumsProvider.js';
|
||||||
import { SlashCommandEnumValue } from './SlashCommandEnumValue.js';
|
import { SlashCommandEnumValue } from './SlashCommandEnumValue.js';
|
||||||
import { SlashCommandExecutor } from './SlashCommandExecutor.js';
|
import { SlashCommandExecutor } from './SlashCommandExecutor.js';
|
||||||
|
import { SlashCommandScope } from './SlashCommandScope.js';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**@readonly*/
|
/**@readonly*/
|
||||||
/**@enum {string}*/
|
/**@enum {string}*/
|
||||||
|
@ -27,7 +30,7 @@ export class SlashCommandArgument {
|
||||||
* @param {boolean} [props.acceptsMultiple=false] default: false - whether argument accepts multiple values
|
* @param {boolean} [props.acceptsMultiple=false] default: false - whether argument accepts multiple values
|
||||||
* @param {string|SlashCommandClosure} [props.defaultValue=null] default value if no value is provided
|
* @param {string|SlashCommandClosure} [props.defaultValue=null] default value if no value is provided
|
||||||
* @param {string|SlashCommandEnumValue|(string|SlashCommandEnumValue)[]} [props.enumList=[]] list of accepted values
|
* @param {string|SlashCommandEnumValue|(string|SlashCommandEnumValue)[]} [props.enumList=[]] list of accepted values
|
||||||
* @param {(executor:SlashCommandExecutor)=>SlashCommandEnumValue[]} [props.enumProvider=null] function that returns auto complete options
|
* @param {(executor:SlashCommandExecutor, scope:SlashCommandScope)=>SlashCommandEnumValue[]} [props.enumProvider=null] function that returns auto complete options
|
||||||
* @param {boolean} [props.forceEnum=false] default: false - whether the input must match one of the enum values
|
* @param {boolean} [props.forceEnum=false] default: false - whether the input must match one of the enum values
|
||||||
*/
|
*/
|
||||||
static fromProps(props) {
|
static fromProps(props) {
|
||||||
|
@ -49,7 +52,7 @@ export class SlashCommandArgument {
|
||||||
/**@type {boolean}*/ acceptsMultiple = false;
|
/**@type {boolean}*/ acceptsMultiple = false;
|
||||||
/**@type {string|SlashCommandClosure}*/ defaultValue;
|
/**@type {string|SlashCommandClosure}*/ defaultValue;
|
||||||
/**@type {SlashCommandEnumValue[]}*/ enumList = [];
|
/**@type {SlashCommandEnumValue[]}*/ enumList = [];
|
||||||
/**@type {(executor:SlashCommandExecutor)=>SlashCommandEnumValue[]}*/ enumProvider = null;
|
/**@type {(executor:SlashCommandExecutor, scope:SlashCommandScope)=>SlashCommandEnumValue[]}*/ enumProvider = null;
|
||||||
/**@type {boolean}*/ forceEnum = false;
|
/**@type {boolean}*/ forceEnum = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -57,7 +60,7 @@ export class SlashCommandArgument {
|
||||||
* @param {ARGUMENT_TYPE|ARGUMENT_TYPE[]} types
|
* @param {ARGUMENT_TYPE|ARGUMENT_TYPE[]} types
|
||||||
* @param {string|SlashCommandClosure} defaultValue
|
* @param {string|SlashCommandClosure} defaultValue
|
||||||
* @param {string|SlashCommandEnumValue|(string|SlashCommandEnumValue)[]} enums
|
* @param {string|SlashCommandEnumValue|(string|SlashCommandEnumValue)[]} enums
|
||||||
* @param {(executor:SlashCommandExecutor)=>SlashCommandEnumValue[]} enumProvider function that returns auto complete options
|
* @param {(executor:SlashCommandExecutor, scope:SlashCommandScope)=>SlashCommandEnumValue[]} enumProvider function that returns auto complete options
|
||||||
*/
|
*/
|
||||||
constructor(description, types, isRequired = false, acceptsMultiple = false, defaultValue = null, enums = [], enumProvider = null, forceEnum = false) {
|
constructor(description, types, isRequired = false, acceptsMultiple = false, defaultValue = null, enums = [], enumProvider = null, forceEnum = false) {
|
||||||
this.description = description;
|
this.description = description;
|
||||||
|
@ -89,7 +92,7 @@ export class SlashCommandNamedArgument extends SlashCommandArgument {
|
||||||
* @param {boolean} [props.acceptsMultiple=false] default: false - whether argument accepts multiple values
|
* @param {boolean} [props.acceptsMultiple=false] default: false - whether argument accepts multiple values
|
||||||
* @param {string|SlashCommandClosure} [props.defaultValue=null] default value if no value is provided
|
* @param {string|SlashCommandClosure} [props.defaultValue=null] default value if no value is provided
|
||||||
* @param {string|SlashCommandEnumValue|(string|SlashCommandEnumValue)[]} [props.enumList=[]] list of accepted values
|
* @param {string|SlashCommandEnumValue|(string|SlashCommandEnumValue)[]} [props.enumList=[]] list of accepted values
|
||||||
* @param {(executor:SlashCommandExecutor)=>SlashCommandEnumValue[]} [props.enumProvider=null] function that returns auto complete options
|
* @param {(executor:SlashCommandExecutor, scope:SlashCommandScope)=>SlashCommandEnumValue[]} [props.enumProvider=null] function that returns auto complete options
|
||||||
* @param {boolean} [props.forceEnum=false] default: false - whether the input must match one of the enum values
|
* @param {boolean} [props.forceEnum=false] default: false - whether the input must match one of the enum values
|
||||||
*/
|
*/
|
||||||
static fromProps(props) {
|
static fromProps(props) {
|
||||||
|
@ -119,7 +122,7 @@ export class SlashCommandNamedArgument extends SlashCommandArgument {
|
||||||
* @param {string|SlashCommandClosure} [defaultValue=null]
|
* @param {string|SlashCommandClosure} [defaultValue=null]
|
||||||
* @param {string|SlashCommandEnumValue|(string|SlashCommandEnumValue)[]} [enums=[]]
|
* @param {string|SlashCommandEnumValue|(string|SlashCommandEnumValue)[]} [enums=[]]
|
||||||
* @param {string[]} [aliases=[]]
|
* @param {string[]} [aliases=[]]
|
||||||
* @param {(executor:SlashCommandExecutor)=>SlashCommandEnumValue[]} [enumProvider=null] function that returns auto complete options
|
* @param {(executor:SlashCommandExecutor, scope:SlashCommandScope)=>SlashCommandEnumValue[]} [enumProvider=null] function that returns auto complete options
|
||||||
* @param {boolean} [forceEnum=false]
|
* @param {boolean} [forceEnum=false]
|
||||||
*/
|
*/
|
||||||
constructor(name, description, types, isRequired = false, acceptsMultiple = false, defaultValue = null, enums = [], aliases = [], enumProvider = null, forceEnum = false) {
|
constructor(name, description, types, isRequired = false, acceptsMultiple = false, defaultValue = null, enums = [], aliases = [], enumProvider = null, forceEnum = false) {
|
||||||
|
|
|
@ -8,15 +8,18 @@ import { SlashCommandCommandAutoCompleteOption } from './SlashCommandCommandAuto
|
||||||
import { SlashCommandEnumAutoCompleteOption } from './SlashCommandEnumAutoCompleteOption.js';
|
import { SlashCommandEnumAutoCompleteOption } from './SlashCommandEnumAutoCompleteOption.js';
|
||||||
import { SlashCommandExecutor } from './SlashCommandExecutor.js';
|
import { SlashCommandExecutor } from './SlashCommandExecutor.js';
|
||||||
import { SlashCommandNamedArgumentAutoCompleteOption } from './SlashCommandNamedArgumentAutoCompleteOption.js';
|
import { SlashCommandNamedArgumentAutoCompleteOption } from './SlashCommandNamedArgumentAutoCompleteOption.js';
|
||||||
|
import { SlashCommandScope } from './SlashCommandScope.js';
|
||||||
|
|
||||||
export class SlashCommandAutoCompleteNameResult extends AutoCompleteNameResult {
|
export class SlashCommandAutoCompleteNameResult extends AutoCompleteNameResult {
|
||||||
/**@type {SlashCommandExecutor}*/ executor;
|
/**@type {SlashCommandExecutor}*/ executor;
|
||||||
|
/**@type {SlashCommandScope}*/ scope;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {SlashCommandExecutor} executor
|
* @param {SlashCommandExecutor} executor
|
||||||
|
* @param {SlashCommandScope} scope
|
||||||
* @param {Object.<string,SlashCommand>} commands
|
* @param {Object.<string,SlashCommand>} commands
|
||||||
*/
|
*/
|
||||||
constructor(executor, commands) {
|
constructor(executor, scope, commands) {
|
||||||
super(
|
super(
|
||||||
executor.name,
|
executor.name,
|
||||||
executor.start,
|
executor.start,
|
||||||
|
@ -29,6 +32,7 @@ export class SlashCommandAutoCompleteNameResult extends AutoCompleteNameResult {
|
||||||
()=>'No slash commands found!',
|
()=>'No slash commands found!',
|
||||||
);
|
);
|
||||||
this.executor = executor;
|
this.executor = executor;
|
||||||
|
this.scope = scope;
|
||||||
}
|
}
|
||||||
|
|
||||||
getSecondaryNameAt(text, index, isSelect) {
|
getSecondaryNameAt(text, index, isSelect) {
|
||||||
|
@ -86,8 +90,8 @@ export class SlashCommandAutoCompleteNameResult extends AutoCompleteNameResult {
|
||||||
}
|
}
|
||||||
} else if (unamedArgLength > 0 && index >= this.executor.startUnnamedArgs && index <= this.executor.endUnnamedArgs) {
|
} else if (unamedArgLength > 0 && index >= this.executor.startUnnamedArgs && index <= this.executor.endUnnamedArgs) {
|
||||||
// cursor is somewhere within the unnamed arguments
|
// cursor is somewhere within the unnamed arguments
|
||||||
//TODO if index is in first array item and that is a string, treat it as an unfinished named arg
|
// if index is in first array item and that is a string, treat it as an unfinished named arg
|
||||||
if (typeof this.executor.unnamedArgumentList[0].value == 'string') {
|
if (typeof this.executor.unnamedArgumentList[0]?.value == 'string') {
|
||||||
if (index <= this.executor.startUnnamedArgs + this.executor.unnamedArgumentList[0].value.length) {
|
if (index <= this.executor.startUnnamedArgs + this.executor.unnamedArgumentList[0].value.length) {
|
||||||
name = this.executor.unnamedArgumentList[0].value.slice(0, index - this.executor.startUnnamedArgs);
|
name = this.executor.unnamedArgumentList[0].value.slice(0, index - this.executor.startUnnamedArgs);
|
||||||
start = this.executor.startUnnamedArgs;
|
start = this.executor.startUnnamedArgs;
|
||||||
|
@ -103,7 +107,7 @@ export class SlashCommandAutoCompleteNameResult extends AutoCompleteNameResult {
|
||||||
|
|
||||||
if (name.includes('=') && cmdArg) {
|
if (name.includes('=') && cmdArg) {
|
||||||
// if cursor is already behind "=" check for enums
|
// if cursor is already behind "=" check for enums
|
||||||
const enumList = cmdArg?.enumProvider?.(this.executor) ?? cmdArg?.enumList;
|
const enumList = cmdArg?.enumProvider?.(this.executor, this.scope) ?? cmdArg?.enumList;
|
||||||
if (cmdArg && enumList?.length) {
|
if (cmdArg && enumList?.length) {
|
||||||
if (isSelect && enumList.find(it=>it.value == value) && argAssign && argAssign.end == index) {
|
if (isSelect && enumList.find(it=>it.value == value) && argAssign && argAssign.end == index) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -111,7 +115,7 @@ export class SlashCommandAutoCompleteNameResult extends AutoCompleteNameResult {
|
||||||
const result = new AutoCompleteSecondaryNameResult(
|
const result = new AutoCompleteSecondaryNameResult(
|
||||||
value,
|
value,
|
||||||
start + name.length,
|
start + name.length,
|
||||||
enumList.map(it=>new SlashCommandEnumAutoCompleteOption(this.executor.command, it)),
|
enumList.map(it=>SlashCommandEnumAutoCompleteOption.from(this.executor.command, it)),
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
result.isRequired = true;
|
result.isRequired = true;
|
||||||
|
@ -150,7 +154,10 @@ export class SlashCommandAutoCompleteNameResult extends AutoCompleteNameResult {
|
||||||
if (idx > -1) {
|
if (idx > -1) {
|
||||||
argAssign = this.executor.unnamedArgumentList[idx];
|
argAssign = this.executor.unnamedArgumentList[idx];
|
||||||
cmdArg = this.executor.command.unnamedArgumentList[idx];
|
cmdArg = this.executor.command.unnamedArgumentList[idx];
|
||||||
const enumList = cmdArg?.enumProvider?.(this.executor) ?? cmdArg?.enumList;
|
if (cmdArg === undefined && this.executor.command.unnamedArgumentList.slice(-1)[0]?.acceptsMultiple) {
|
||||||
|
cmdArg = this.executor.command.unnamedArgumentList.slice(-1)[0];
|
||||||
|
}
|
||||||
|
const enumList = cmdArg?.enumProvider?.(this.executor, this.scope) ?? cmdArg?.enumList;
|
||||||
if (cmdArg && enumList.length > 0) {
|
if (cmdArg && enumList.length > 0) {
|
||||||
value = argAssign.value.toString().slice(0, index - argAssign.start);
|
value = argAssign.value.toString().slice(0, index - argAssign.start);
|
||||||
start = argAssign.start;
|
start = argAssign.start;
|
||||||
|
@ -161,23 +168,26 @@ export class SlashCommandAutoCompleteNameResult extends AutoCompleteNameResult {
|
||||||
value = '';
|
value = '';
|
||||||
start = index;
|
start = index;
|
||||||
cmdArg = notProvidedArguments[0];
|
cmdArg = notProvidedArguments[0];
|
||||||
|
if (cmdArg === undefined && this.executor.command.unnamedArgumentList.slice(-1)[0]?.acceptsMultiple) {
|
||||||
|
cmdArg = this.executor.command.unnamedArgumentList.slice(-1)[0];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const enumList = cmdArg?.enumProvider?.(this.executor) ?? cmdArg?.enumList;
|
const enumList = cmdArg?.enumProvider?.(this.executor, this.scope) ?? cmdArg?.enumList;
|
||||||
if (cmdArg == null || enumList.length == 0) return null;
|
if (cmdArg == null || enumList.length == 0) return null;
|
||||||
|
|
||||||
const result = new AutoCompleteSecondaryNameResult(
|
const result = new AutoCompleteSecondaryNameResult(
|
||||||
value,
|
value,
|
||||||
start,
|
start,
|
||||||
enumList.map(it=>new SlashCommandEnumAutoCompleteOption(this.executor.command, it)),
|
enumList.map(it=>SlashCommandEnumAutoCompleteOption.from(this.executor.command, it)),
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
const isCompleteValue = enumList.find(it=>it.value == value);
|
const isCompleteValue = enumList.find(it=>it.value == value);
|
||||||
const isSelectedValue = isSelect && isCompleteValue;
|
const isSelectedValue = isSelect && isCompleteValue;
|
||||||
result.isRequired = cmdArg.isRequired && !isSelectedValue && !isCompleteValue;
|
result.isRequired = cmdArg.isRequired && !isSelectedValue;
|
||||||
result.forceMatch = cmdArg.forceEnum;
|
result.forceMatch = cmdArg.forceEnum;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { SlashCommandExecutor } from './SlashCommandExecutor.js';
|
||||||
|
|
||||||
|
export class SlashCommandBreak extends SlashCommandExecutor {
|
||||||
|
get value() {
|
||||||
|
return this.unnamedArgumentList[0]?.value;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
export class SlashCommandBreakController {
|
||||||
|
/**@type {boolean} */ isBreak = false;
|
||||||
|
|
||||||
|
break() {
|
||||||
|
this.isBreak = true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
import { SlashCommandExecutor } from './SlashCommandExecutor.js';
|
||||||
|
|
||||||
|
export class SlashCommandBreakPoint extends SlashCommandExecutor {}
|
|
@ -1,9 +1,13 @@
|
||||||
import { substituteParams } from '../../script.js';
|
import { substituteParams } from '../../script.js';
|
||||||
import { delay, escapeRegex } from '../utils.js';
|
import { delay, escapeRegex, uuidv4 } from '../utils.js';
|
||||||
import { SlashCommand } from './SlashCommand.js';
|
import { SlashCommand } from './SlashCommand.js';
|
||||||
import { SlashCommandAbortController } from './SlashCommandAbortController.js';
|
import { SlashCommandAbortController } from './SlashCommandAbortController.js';
|
||||||
import { SlashCommandClosureExecutor } from './SlashCommandClosureExecutor.js';
|
import { SlashCommandBreak } from './SlashCommandBreak.js';
|
||||||
|
import { SlashCommandBreakController } from './SlashCommandBreakController.js';
|
||||||
|
import { SlashCommandBreakPoint } from './SlashCommandBreakPoint.js';
|
||||||
import { SlashCommandClosureResult } from './SlashCommandClosureResult.js';
|
import { SlashCommandClosureResult } from './SlashCommandClosureResult.js';
|
||||||
|
import { SlashCommandDebugController } from './SlashCommandDebugController.js';
|
||||||
|
import { SlashCommandExecutionError } from './SlashCommandExecutionError.js';
|
||||||
import { SlashCommandExecutor } from './SlashCommandExecutor.js';
|
import { SlashCommandExecutor } from './SlashCommandExecutor.js';
|
||||||
import { SlashCommandNamedArgumentAssignment } from './SlashCommandNamedArgumentAssignment.js';
|
import { SlashCommandNamedArgumentAssignment } from './SlashCommandNamedArgumentAssignment.js';
|
||||||
import { SlashCommandScope } from './SlashCommandScope.js';
|
import { SlashCommandScope } from './SlashCommandScope.js';
|
||||||
|
@ -17,8 +21,20 @@ export class SlashCommandClosure {
|
||||||
/**@type {SlashCommandNamedArgumentAssignment[]}*/ providedArgumentList = [];
|
/**@type {SlashCommandNamedArgumentAssignment[]}*/ providedArgumentList = [];
|
||||||
/**@type {SlashCommandExecutor[]}*/ executorList = [];
|
/**@type {SlashCommandExecutor[]}*/ executorList = [];
|
||||||
/**@type {SlashCommandAbortController}*/ abortController;
|
/**@type {SlashCommandAbortController}*/ abortController;
|
||||||
|
/**@type {SlashCommandBreakController}*/ breakController;
|
||||||
|
/**@type {SlashCommandDebugController}*/ debugController;
|
||||||
/**@type {(done:number, total:number)=>void}*/ onProgress;
|
/**@type {(done:number, total:number)=>void}*/ onProgress;
|
||||||
/**@type {string}*/ rawText;
|
/**@type {string}*/ rawText;
|
||||||
|
/**@type {string}*/ fullText;
|
||||||
|
/**@type {string}*/ parserContext;
|
||||||
|
/**@type {string}*/ #source = uuidv4();
|
||||||
|
get source() { return this.#source; }
|
||||||
|
set source(value) {
|
||||||
|
this.#source = value;
|
||||||
|
for (const executor of this.executorList) {
|
||||||
|
executor.source = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**@type {number}*/
|
/**@type {number}*/
|
||||||
get commandCount() {
|
get commandCount() {
|
||||||
|
@ -30,7 +46,7 @@ export class SlashCommandClosure {
|
||||||
}
|
}
|
||||||
|
|
||||||
toString() {
|
toString() {
|
||||||
return '[Closure]';
|
return `[Closure]${this.executeNow ? '()' : ''}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -43,16 +59,37 @@ export class SlashCommandClosure {
|
||||||
let isList = false;
|
let isList = false;
|
||||||
let listValues = [];
|
let listValues = [];
|
||||||
scope = scope ?? this.scope;
|
scope = scope ?? this.scope;
|
||||||
const macros = scope.macroList.map(it=>escapeRegex(it.key)).join('|');
|
const escapeMacro = (it, isAnchored = false)=>{
|
||||||
const re = new RegExp(`({{pipe}})|(?:{{var::([^\\s]+?)(?:::((?!}}).+))?}})|(?:{{(${macros})}})`);
|
const regexText = escapeRegex(it.key.replace(/\*/g, '~~~WILDCARD~~~'))
|
||||||
|
.replaceAll('~~~WILDCARD~~~', '(?:(?:(?!(?:::|}})).)*)')
|
||||||
|
;
|
||||||
|
if (isAnchored) {
|
||||||
|
return `^${regexText}$`;
|
||||||
|
}
|
||||||
|
return regexText;
|
||||||
|
};
|
||||||
|
const macroList = scope.macroList.toSorted((a,b)=>{
|
||||||
|
if (a.key.includes('*') && !b.key.includes('*')) return 1;
|
||||||
|
if (!a.key.includes('*') && b.key.includes('*')) return -1;
|
||||||
|
if (a.key.includes('*') && b.key.includes('*')) return b.key.indexOf('*') - a.key.indexOf('*');
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
const macros = macroList.map(it=>escapeMacro(it)).join('|');
|
||||||
|
const re = new RegExp(`(?<pipe>{{pipe}})|(?:{{var::(?<var>[^\\s]+?)(?:::(?<varIndex>(?!}}).+))?}})|(?:{{(?<macro>${macros})}})`);
|
||||||
let done = '';
|
let done = '';
|
||||||
let remaining = text;
|
let remaining = text;
|
||||||
while (re.test(remaining)) {
|
while (re.test(remaining)) {
|
||||||
const match = re.exec(remaining);
|
const match = re.exec(remaining);
|
||||||
const before = substituteParams(remaining.slice(0, match.index));
|
const before = substituteParams(remaining.slice(0, match.index));
|
||||||
const after = remaining.slice(match.index + match[0].length);
|
const after = remaining.slice(match.index + match[0].length);
|
||||||
const replacer = match[1] ? scope.pipe : match[2] ? scope.getVariable(match[2], match[3]) : scope.macroList.find(it=>it.key == match[4])?.value;
|
const replacer = match.groups.pipe ? scope.pipe : match.groups.var ? scope.getVariable(match.groups.var, match.groups.index) : macroList.find(it=>it.key == match.groups.macro || new RegExp(escapeMacro(it, true)).test(match.groups.macro))?.value;
|
||||||
if (replacer instanceof SlashCommandClosure) {
|
if (replacer instanceof SlashCommandClosure) {
|
||||||
|
replacer.abortController = this.abortController;
|
||||||
|
replacer.breakController = this.breakController;
|
||||||
|
replacer.scope.parent = this.scope;
|
||||||
|
if (this.debugController && !replacer.debugController) {
|
||||||
|
replacer.debugController = this.debugController;
|
||||||
|
}
|
||||||
isList = true;
|
isList = true;
|
||||||
if (match.index > 0) {
|
if (match.index > 0) {
|
||||||
listValues.push(before);
|
listValues.push(before);
|
||||||
|
@ -87,6 +124,12 @@ export class SlashCommandClosure {
|
||||||
closure.providedArgumentList = this.providedArgumentList;
|
closure.providedArgumentList = this.providedArgumentList;
|
||||||
closure.executorList = this.executorList;
|
closure.executorList = this.executorList;
|
||||||
closure.abortController = this.abortController;
|
closure.abortController = this.abortController;
|
||||||
|
closure.breakController = this.breakController;
|
||||||
|
closure.debugController = this.debugController;
|
||||||
|
closure.rawText = this.rawText;
|
||||||
|
closure.fullText = this.fullText;
|
||||||
|
closure.parserContext = this.parserContext;
|
||||||
|
closure.source = this.source;
|
||||||
closure.onProgress = this.onProgress;
|
closure.onProgress = this.onProgress;
|
||||||
return closure;
|
return closure;
|
||||||
}
|
}
|
||||||
|
@ -96,11 +139,22 @@ export class SlashCommandClosure {
|
||||||
* @returns {Promise<SlashCommandClosureResult>}
|
* @returns {Promise<SlashCommandClosureResult>}
|
||||||
*/
|
*/
|
||||||
async execute() {
|
async execute() {
|
||||||
|
// execute a copy of the closure to no taint it and its scope with the effects of its execution
|
||||||
|
// as this would affect the closure being called a second time (e.g., loop, multiple /run calls)
|
||||||
const closure = this.getCopy();
|
const closure = this.getCopy();
|
||||||
return await closure.executeDirect();
|
const gen = closure.executeDirect();
|
||||||
|
let step;
|
||||||
|
while (!step?.done) {
|
||||||
|
step = await gen.next(this.debugController?.testStepping(this) ?? false);
|
||||||
|
if (!(step.value instanceof SlashCommandClosureResult) && this.debugController) {
|
||||||
|
this.debugController.isStepping = await this.debugController.awaitBreakPoint(step.value.closure, step.value.executor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return step.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
async executeDirect() {
|
async * executeDirect() {
|
||||||
|
this.debugController?.down(this);
|
||||||
// closure arguments
|
// closure arguments
|
||||||
for (const arg of this.argumentList) {
|
for (const arg of this.argumentList) {
|
||||||
let v = arg.value;
|
let v = arg.value;
|
||||||
|
@ -108,6 +162,7 @@ export class SlashCommandClosure {
|
||||||
/**@type {SlashCommandClosure}*/
|
/**@type {SlashCommandClosure}*/
|
||||||
const closure = v;
|
const closure = v;
|
||||||
closure.scope.parent = this.scope;
|
closure.scope.parent = this.scope;
|
||||||
|
closure.breakController = this.breakController;
|
||||||
if (closure.executeNow) {
|
if (closure.executeNow) {
|
||||||
v = (await closure.execute())?.pipe;
|
v = (await closure.execute())?.pipe;
|
||||||
} else {
|
} else {
|
||||||
|
@ -131,6 +186,7 @@ export class SlashCommandClosure {
|
||||||
/**@type {SlashCommandClosure}*/
|
/**@type {SlashCommandClosure}*/
|
||||||
const closure = v;
|
const closure = v;
|
||||||
closure.scope.parent = this.scope;
|
closure.scope.parent = this.scope;
|
||||||
|
closure.breakController = this.breakController;
|
||||||
if (closure.executeNow) {
|
if (closure.executeNow) {
|
||||||
v = (await closure.execute())?.pipe;
|
v = (await closure.execute())?.pipe;
|
||||||
} else {
|
} else {
|
||||||
|
@ -149,34 +205,190 @@ export class SlashCommandClosure {
|
||||||
this.scope.setVariable(arg.name, v);
|
this.scope.setVariable(arg.name, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
let done = 0;
|
|
||||||
if (this.executorList.length == 0) {
|
if (this.executorList.length == 0) {
|
||||||
this.scope.pipe = '';
|
this.scope.pipe = '';
|
||||||
}
|
}
|
||||||
|
const stepper = this.executeStep();
|
||||||
|
let step;
|
||||||
|
while (!step?.done && !this.breakController?.isBreak) {
|
||||||
|
// get executor before execution
|
||||||
|
step = await stepper.next();
|
||||||
|
if (step.value instanceof SlashCommandBreakPoint) {
|
||||||
|
console.log('encountered SlashCommandBreakPoint');
|
||||||
|
if (this.debugController) {
|
||||||
|
// resolve args
|
||||||
|
step = await stepper.next();
|
||||||
|
// "execute" breakpoint
|
||||||
|
step = await stepper.next();
|
||||||
|
// get next executor
|
||||||
|
step = await stepper.next();
|
||||||
|
// breakpoint has to yield before arguments are resolved if one of the
|
||||||
|
// arguments is an immediate closure, otherwise you cannot step into the
|
||||||
|
// immediate closure
|
||||||
|
const hasImmediateClosureInNamedArgs = /**@type {SlashCommandExecutor}*/(step.value)?.namedArgumentList?.find(it=>it.value instanceof SlashCommandClosure && it.value.executeNow);
|
||||||
|
const hasImmediateClosureInUnnamedArgs = /**@type {SlashCommandExecutor}*/(step.value)?.unnamedArgumentList?.find(it=>it.value instanceof SlashCommandClosure && it.value.executeNow);
|
||||||
|
if (hasImmediateClosureInNamedArgs || hasImmediateClosureInUnnamedArgs) {
|
||||||
|
this.debugController.isStepping = yield { closure:this, executor:step.value };
|
||||||
|
} else {
|
||||||
|
this.debugController.isStepping = true;
|
||||||
|
this.debugController.stepStack[this.debugController.stepStack.length - 1] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (!step.done && this.debugController?.testStepping(this)) {
|
||||||
|
this.debugController.isSteppingInto = false;
|
||||||
|
// if stepping, have to yield before arguments are resolved if one of the arguments
|
||||||
|
// is an immediate closure, otherwise you cannot step into the immediate closure
|
||||||
|
const hasImmediateClosureInNamedArgs = /**@type {SlashCommandExecutor}*/(step.value)?.namedArgumentList?.find(it=>it.value instanceof SlashCommandClosure && it.value.executeNow);
|
||||||
|
const hasImmediateClosureInUnnamedArgs = /**@type {SlashCommandExecutor}*/(step.value)?.unnamedArgumentList?.find(it=>it.value instanceof SlashCommandClosure && it.value.executeNow);
|
||||||
|
if (hasImmediateClosureInNamedArgs || hasImmediateClosureInUnnamedArgs) {
|
||||||
|
this.debugController.isStepping = yield { closure:this, executor:step.value };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// resolve args
|
||||||
|
step = await stepper.next();
|
||||||
|
if (step.value instanceof SlashCommandBreak) {
|
||||||
|
console.log('encountered SlashCommandBreak');
|
||||||
|
if (this.breakController) {
|
||||||
|
this.breakController?.break();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (!step.done && this.debugController?.testStepping(this)) {
|
||||||
|
this.debugController.isSteppingInto = false;
|
||||||
|
this.debugController.isStepping = yield { closure:this, executor:step.value };
|
||||||
|
}
|
||||||
|
// execute executor
|
||||||
|
step = await stepper.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
// if execution has returned a closure result, return that (should only happen on abort)
|
||||||
|
if (step.value instanceof SlashCommandClosureResult) {
|
||||||
|
this.debugController?.up();
|
||||||
|
return step.value;
|
||||||
|
}
|
||||||
|
/**@type {SlashCommandClosureResult} */
|
||||||
|
const result = Object.assign(new SlashCommandClosureResult(), { pipe: this.scope.pipe, isBreak: this.breakController?.isBreak ?? false });
|
||||||
|
this.debugController?.up();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Generator that steps through the executor list.
|
||||||
|
* Every executor is split into three steps:
|
||||||
|
* - before arguments are resolved
|
||||||
|
* - after arguments are resolved
|
||||||
|
* - after execution
|
||||||
|
*/
|
||||||
|
async * executeStep() {
|
||||||
|
let done = 0;
|
||||||
|
let isFirst = true;
|
||||||
for (const executor of this.executorList) {
|
for (const executor of this.executorList) {
|
||||||
this.onProgress?.(done, this.commandCount);
|
this.onProgress?.(done, this.commandCount);
|
||||||
if (executor instanceof SlashCommandClosureExecutor) {
|
if (this.debugController) {
|
||||||
const closure = this.scope.getVariable(executor.name);
|
this.debugController.setExecutor(executor);
|
||||||
if (!closure || !(closure instanceof SlashCommandClosure)) throw new Error(`${executor.name} is not a closure.`);
|
this.debugController.namedArguments = undefined;
|
||||||
closure.scope.parent = this.scope;
|
this.debugController.unnamedArguments = undefined;
|
||||||
closure.providedArgumentList = executor.providedArgumentList;
|
}
|
||||||
const result = await closure.execute();
|
// yield before doing anything with this executor, the debugger might want to do
|
||||||
this.scope.pipe = result.pipe;
|
// something with it (e.g., breakpoint, immediate closures that need resolving
|
||||||
} else {
|
// or stepping into)
|
||||||
|
yield executor;
|
||||||
/**@type {import('./SlashCommand.js').NamedArguments} */
|
/**@type {import('./SlashCommand.js').NamedArguments} */
|
||||||
|
// @ts-ignore
|
||||||
let args = {
|
let args = {
|
||||||
_scope: this.scope,
|
_scope: this.scope,
|
||||||
_parserFlags: executor.parserFlags,
|
_parserFlags: executor.parserFlags,
|
||||||
_abortController: this.abortController,
|
_abortController: this.abortController,
|
||||||
|
_debugController: this.debugController,
|
||||||
_hasUnnamedArgument: executor.unnamedArgumentList.length > 0,
|
_hasUnnamedArgument: executor.unnamedArgumentList.length > 0,
|
||||||
};
|
};
|
||||||
let value;
|
if (executor instanceof SlashCommandBreakPoint) {
|
||||||
|
// nothing to do for breakpoints, just raise counter and yield for "before exec"
|
||||||
|
done++;
|
||||||
|
yield executor;
|
||||||
|
isFirst = false;
|
||||||
|
} else if (executor instanceof SlashCommandBreak) {
|
||||||
|
// /break need to resolve the unnamed arg and put it into pipe, then yield
|
||||||
|
// for "before exec"
|
||||||
|
const value = await this.substituteUnnamedArgument(executor, isFirst, args);
|
||||||
|
done += this.executorList.length - this.executorList.indexOf(executor);
|
||||||
|
this.scope.pipe = value ?? this.scope.pipe;
|
||||||
|
yield executor;
|
||||||
|
isFirst = false;
|
||||||
|
} else {
|
||||||
|
// regular commands do all the argument resolving logic...
|
||||||
|
await this.substituteNamedArguments(executor, args);
|
||||||
|
let value = await this.substituteUnnamedArgument(executor, isFirst, args);
|
||||||
|
|
||||||
|
let abortResult = await this.testAbortController();
|
||||||
|
if (abortResult) {
|
||||||
|
return abortResult;
|
||||||
|
}
|
||||||
|
if (this.debugController) {
|
||||||
|
this.debugController.namedArguments = args;
|
||||||
|
this.debugController.unnamedArguments = value ?? '';
|
||||||
|
}
|
||||||
|
// then yield for "before exec"
|
||||||
|
yield executor;
|
||||||
|
// followed by command execution
|
||||||
|
executor.onProgress = (subDone, subTotal)=>this.onProgress?.(done + subDone, this.commandCount);
|
||||||
|
const isStepping = this.debugController?.testStepping(this);
|
||||||
|
if (this.debugController) {
|
||||||
|
this.debugController.isStepping = false || this.debugController.isSteppingInto;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
this.scope.pipe = await executor.command.callback(args, value ?? '');
|
||||||
|
} catch (ex) {
|
||||||
|
throw new SlashCommandExecutionError(ex, ex.message, executor.name, executor.start, executor.end, this.fullText.slice(executor.start, executor.end), this.fullText);
|
||||||
|
}
|
||||||
|
if (this.debugController) {
|
||||||
|
this.debugController.namedArguments = undefined;
|
||||||
|
this.debugController.unnamedArguments = undefined;
|
||||||
|
this.debugController.isStepping = isStepping;
|
||||||
|
}
|
||||||
|
this.#lintPipe(executor.command);
|
||||||
|
done += executor.commandCount;
|
||||||
|
this.onProgress?.(done, this.commandCount);
|
||||||
|
abortResult = await this.testAbortController();
|
||||||
|
if (abortResult) {
|
||||||
|
return abortResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// finally, yield for "after exec"
|
||||||
|
yield executor;
|
||||||
|
isFirst = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async testPaused() {
|
||||||
|
while (!this.abortController?.signal?.aborted && this.abortController?.signal?.paused) {
|
||||||
|
await delay(200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async testAbortController() {
|
||||||
|
await this.testPaused();
|
||||||
|
if (this.abortController?.signal?.aborted) {
|
||||||
|
const result = new SlashCommandClosureResult();
|
||||||
|
result.isAborted = true;
|
||||||
|
result.isQuietlyAborted = this.abortController.signal.isQuiet;
|
||||||
|
result.abortReason = this.abortController.signal.reason.toString();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {SlashCommandExecutor} executor
|
||||||
|
* @param {import('./SlashCommand.js').NamedArguments} args
|
||||||
|
*/
|
||||||
|
async substituteNamedArguments(executor, args) {
|
||||||
// substitute named arguments
|
// substitute named arguments
|
||||||
for (const arg of executor.namedArgumentList) {
|
for (const arg of executor.namedArgumentList) {
|
||||||
if (arg.value instanceof SlashCommandClosure) {
|
if (arg.value instanceof SlashCommandClosure) {
|
||||||
/**@type {SlashCommandClosure}*/
|
/**@type {SlashCommandClosure}*/
|
||||||
const closure = arg.value;
|
const closure = arg.value;
|
||||||
closure.scope.parent = this.scope;
|
closure.scope.parent = this.scope;
|
||||||
|
closure.breakController = this.breakController;
|
||||||
|
if (this.debugController && !closure.debugController) {
|
||||||
|
closure.debugController = this.debugController;
|
||||||
|
}
|
||||||
if (closure.executeNow) {
|
if (closure.executeNow) {
|
||||||
args[arg.name] = (await closure.execute())?.pipe;
|
args[arg.name] = (await closure.execute())?.pipe;
|
||||||
} else {
|
} else {
|
||||||
|
@ -193,10 +405,19 @@ export class SlashCommandClosure {
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {SlashCommandExecutor} executor
|
||||||
|
* @param {boolean} isFirst
|
||||||
|
* @param {import('./SlashCommand.js').NamedArguments} args
|
||||||
|
* @returns {Promise<string|SlashCommandClosure|(string|SlashCommandClosure)[]>}
|
||||||
|
*/
|
||||||
|
async substituteUnnamedArgument(executor, isFirst, args) {
|
||||||
|
let value;
|
||||||
// substitute unnamed argument
|
// substitute unnamed argument
|
||||||
if (executor.unnamedArgumentList.length == 0) {
|
if (executor.unnamedArgumentList.length == 0) {
|
||||||
if (executor.injectPipe) {
|
if (!isFirst && executor.injectPipe) {
|
||||||
value = this.scope.pipe;
|
value = this.scope.pipe;
|
||||||
args._hasUnnamedArgument = this.scope.pipe !== null && this.scope.pipe !== undefined;
|
args._hasUnnamedArgument = this.scope.pipe !== null && this.scope.pipe !== undefined;
|
||||||
}
|
}
|
||||||
|
@ -208,6 +429,10 @@ export class SlashCommandClosure {
|
||||||
/**@type {SlashCommandClosure}*/
|
/**@type {SlashCommandClosure}*/
|
||||||
const closure = v;
|
const closure = v;
|
||||||
closure.scope.parent = this.scope;
|
closure.scope.parent = this.scope;
|
||||||
|
closure.breakController = this.breakController;
|
||||||
|
if (this.debugController && !closure.debugController) {
|
||||||
|
closure.debugController = this.debugController;
|
||||||
|
}
|
||||||
if (closure.executeNow) {
|
if (closure.executeNow) {
|
||||||
v = (await closure.execute())?.pipe;
|
v = (await closure.execute())?.pipe;
|
||||||
} else {
|
} else {
|
||||||
|
@ -242,41 +467,7 @@ export class SlashCommandClosure {
|
||||||
return v;
|
return v;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
return value;
|
||||||
let abortResult = await this.testAbortController();
|
|
||||||
if (abortResult) {
|
|
||||||
return abortResult;
|
|
||||||
}
|
|
||||||
executor.onProgress = (subDone, subTotal)=>this.onProgress?.(done + subDone, this.commandCount);
|
|
||||||
this.scope.pipe = await executor.command.callback(args, value ?? '');
|
|
||||||
this.#lintPipe(executor.command);
|
|
||||||
done += executor.commandCount;
|
|
||||||
this.onProgress?.(done, this.commandCount);
|
|
||||||
abortResult = await this.testAbortController();
|
|
||||||
if (abortResult) {
|
|
||||||
return abortResult;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**@type {SlashCommandClosureResult} */
|
|
||||||
const result = Object.assign(new SlashCommandClosureResult(), { pipe: this.scope.pipe });
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
async testPaused() {
|
|
||||||
while (!this.abortController?.signal?.aborted && this.abortController?.signal?.paused) {
|
|
||||||
await delay(200);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async testAbortController() {
|
|
||||||
await this.testPaused();
|
|
||||||
if (this.abortController?.signal?.aborted) {
|
|
||||||
const result = new SlashCommandClosureResult();
|
|
||||||
result.isAborted = true;
|
|
||||||
result.isQuietlyAborted = this.abortController.signal.isQuiet;
|
|
||||||
result.abortReason = this.abortController.signal.reason.toString();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -285,8 +476,11 @@ export class SlashCommandClosure {
|
||||||
*/
|
*/
|
||||||
#lintPipe(command) {
|
#lintPipe(command) {
|
||||||
if (this.scope.pipe === undefined || this.scope.pipe === null) {
|
if (this.scope.pipe === undefined || this.scope.pipe === null) {
|
||||||
console.warn(`${command.name} returned undefined or null. Auto-fixing to empty string.`);
|
console.warn(`/${command.name} returned undefined or null. Auto-fixing to empty string.`);
|
||||||
this.scope.pipe = '';
|
this.scope.pipe = '';
|
||||||
|
} else if (!(typeof this.scope.pipe == 'string' || this.scope.pipe instanceof SlashCommandClosure)) {
|
||||||
|
console.warn(`/${command.name} returned illegal type (${typeof this.scope.pipe} - ${this.scope.pipe.constructor?.name ?? ''}). Auto-fixing to stringified JSON.`);
|
||||||
|
this.scope.pipe = JSON.stringify(this.scope.pipe) ?? '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
import { SlashCommandNamedArgumentAssignment } from './SlashCommandNamedArgumentAssignment.js';
|
|
||||||
|
|
||||||
export class SlashCommandClosureExecutor {
|
|
||||||
/**@type {String}*/ name = '';
|
|
||||||
// @ts-ignore
|
|
||||||
/**@type {SlashCommandNamedArgumentAssignment[]}*/ providedArgumentList = [];
|
|
||||||
}
|
|
|
@ -1,6 +1,7 @@
|
||||||
export class SlashCommandClosureResult {
|
export class SlashCommandClosureResult {
|
||||||
/**@type {boolean}*/ interrupt = false;
|
/**@type {boolean}*/ interrupt = false;
|
||||||
/**@type {string}*/ pipe;
|
/**@type {string}*/ pipe;
|
||||||
|
/**@type {boolean}*/ isBreak = false;
|
||||||
/**@type {boolean}*/ isAborted = false;
|
/**@type {boolean}*/ isAborted = false;
|
||||||
/**@type {boolean}*/ isQuietlyAborted = false;
|
/**@type {boolean}*/ isQuietlyAborted = false;
|
||||||
/**@type {string}*/ abortReason;
|
/**@type {string}*/ abortReason;
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { searchCharByName, getTagsList, tags } from '../tags.js';
|
||||||
import { world_names } from '../world-info.js';
|
import { world_names } from '../world-info.js';
|
||||||
import { SlashCommandClosure } from './SlashCommandClosure.js';
|
import { SlashCommandClosure } from './SlashCommandClosure.js';
|
||||||
import { SlashCommandEnumValue, enumTypes } from './SlashCommandEnumValue.js';
|
import { SlashCommandEnumValue, enumTypes } from './SlashCommandEnumValue.js';
|
||||||
|
import { SlashCommandScope } from "./SlashCommandScope.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A collection of regularly used enum icons
|
* A collection of regularly used enum icons
|
||||||
|
@ -134,16 +135,16 @@ export const commonEnumProviders = {
|
||||||
* Can be filtered by `type` to only show global or local variables
|
* Can be filtered by `type` to only show global or local variables
|
||||||
*
|
*
|
||||||
* @param {...('global'|'local'|'scope'|'all')} type - The type of variables to include in the array. Can be 'all', 'global', or 'local'.
|
* @param {...('global'|'local'|'scope'|'all')} type - The type of variables to include in the array. Can be 'all', 'global', or 'local'.
|
||||||
* @returns {() => SlashCommandEnumValue[]}
|
* @returns {(executor:SlashCommandExecutor, scope:SlashCommandScope) => SlashCommandEnumValue[]}
|
||||||
*/
|
*/
|
||||||
variables: (...type) => () => {
|
variables: (...type) => (executor, scope) => {
|
||||||
const types = type.flat();
|
const types = type.flat();
|
||||||
const isAll = types.includes('all');
|
const isAll = types.includes('all');
|
||||||
return [
|
return [
|
||||||
...isAll || types.includes('global') ? Object.keys(extension_settings.variables.global ?? []).map(name => new SlashCommandEnumValue(name, null, enumTypes.macro, enumIcons.globalVariable)) : [],
|
...isAll || types.includes('scope') ? scope.allVariableNames.map(name => new SlashCommandEnumValue(name, null, enumTypes.variable, enumIcons.scopeVariable)) : [],
|
||||||
...isAll || types.includes('local') ? Object.keys(chat_metadata.variables ?? []).map(name => new SlashCommandEnumValue(name, null, enumTypes.name, enumIcons.localVariable)) : [],
|
...isAll || types.includes('local') ? Object.keys(chat_metadata.variables ?? []).map(name => new SlashCommandEnumValue(name, null, enumTypes.name, enumIcons.localVariable)) : [],
|
||||||
...isAll || types.includes('scope') ? [].map(name => new SlashCommandEnumValue(name, null, enumTypes.variable, enumIcons.scopeVariable)) : [], // TODO: Add scoped variables here, Lenny
|
...isAll || types.includes('global') ? Object.keys(extension_settings.variables.global ?? []).map(name => new SlashCommandEnumValue(name, null, enumTypes.macro, enumIcons.globalVariable)) : [],
|
||||||
];
|
].filter((item, idx, list)=>idx == list.findIndex(it=>it.value == item.value));
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
import { SlashCommandClosure } from './SlashCommandClosure.js';
|
||||||
|
import { SlashCommandExecutor } from './SlashCommandExecutor.js';
|
||||||
|
|
||||||
|
export class SlashCommandDebugController {
|
||||||
|
/**@type {SlashCommandClosure[]} */ stack = [];
|
||||||
|
/**@type {SlashCommandExecutor[]} */ cmdStack = [];
|
||||||
|
/**@type {boolean[]} */ stepStack = [];
|
||||||
|
/**@type {boolean} */ isStepping = false;
|
||||||
|
/**@type {boolean} */ isSteppingInto = false;
|
||||||
|
/**@type {boolean} */ isSteppingOut = false;
|
||||||
|
|
||||||
|
/**@type {object} */ namedArguments;
|
||||||
|
/**@type {string|SlashCommandClosure|(string|SlashCommandClosure)[]} */ unnamedArguments;
|
||||||
|
|
||||||
|
/**@type {Promise<boolean>} */ continuePromise;
|
||||||
|
/**@type {(boolean)=>void} */ continueResolver;
|
||||||
|
|
||||||
|
/**@type {(closure:SlashCommandClosure, executor:SlashCommandExecutor)=>Promise<boolean>} */ onBreakPoint;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
testStepping(closure) {
|
||||||
|
return this.stepStack[this.stack.indexOf(closure)];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
down(closure) {
|
||||||
|
this.stack.push(closure);
|
||||||
|
if (this.stepStack.length < this.stack.length) {
|
||||||
|
this.stepStack.push(this.isSteppingInto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
up() {
|
||||||
|
this.stack.pop();
|
||||||
|
while (this.cmdStack.length > this.stack.length) this.cmdStack.pop();
|
||||||
|
this.stepStack.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
setExecutor(executor) {
|
||||||
|
this.cmdStack[this.stack.length - 1] = executor;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
resume() {
|
||||||
|
this.continueResolver?.(false);
|
||||||
|
this.continuePromise = null;
|
||||||
|
this.stepStack.forEach((_,idx)=>this.stepStack[idx] = false);
|
||||||
|
}
|
||||||
|
step() {
|
||||||
|
this.stepStack.forEach((_,idx)=>this.stepStack[idx] = true);
|
||||||
|
this.continueResolver?.(true);
|
||||||
|
this.continuePromise = null;
|
||||||
|
}
|
||||||
|
stepInto() {
|
||||||
|
this.isSteppingInto = true;
|
||||||
|
this.stepStack.forEach((_,idx)=>this.stepStack[idx] = true);
|
||||||
|
this.continueResolver?.(true);
|
||||||
|
this.continuePromise = null;
|
||||||
|
}
|
||||||
|
stepOut() {
|
||||||
|
this.isSteppingOut = true;
|
||||||
|
this.stepStack[this.stepStack.length - 1] = false;
|
||||||
|
this.continueResolver?.(false);
|
||||||
|
this.continuePromise = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async awaitContinue() {
|
||||||
|
this.continuePromise ??= new Promise(resolve=>{
|
||||||
|
this.continueResolver = resolve;
|
||||||
|
});
|
||||||
|
this.isStepping = await this.continuePromise;
|
||||||
|
return this.isStepping;
|
||||||
|
}
|
||||||
|
|
||||||
|
async awaitBreakPoint(closure, executor) {
|
||||||
|
this.isStepping = await this.onBreakPoint(closure, executor);
|
||||||
|
return this.isStepping;
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,17 @@ import { SlashCommand } from './SlashCommand.js';
|
||||||
import { SlashCommandEnumValue } from './SlashCommandEnumValue.js';
|
import { SlashCommandEnumValue } from './SlashCommandEnumValue.js';
|
||||||
|
|
||||||
export class SlashCommandEnumAutoCompleteOption extends AutoCompleteOption {
|
export class SlashCommandEnumAutoCompleteOption extends AutoCompleteOption {
|
||||||
|
/**
|
||||||
|
* @param {SlashCommand} cmd
|
||||||
|
* @param {SlashCommandEnumValue} enumValue
|
||||||
|
* @returns {SlashCommandEnumAutoCompleteOption}
|
||||||
|
*/
|
||||||
|
static from(cmd, enumValue) {
|
||||||
|
const mapped = this.valueToOptionMap.find(it=>enumValue instanceof it.value)?.option ?? this;
|
||||||
|
return new mapped(cmd, enumValue);
|
||||||
|
}
|
||||||
|
/**@type {{value:(typeof SlashCommandEnumValue), option:(typeof SlashCommandEnumAutoCompleteOption)}[]} */
|
||||||
|
static valueToOptionMap = [];
|
||||||
/**@type {SlashCommand}*/ cmd;
|
/**@type {SlashCommand}*/ cmd;
|
||||||
/**@type {SlashCommandEnumValue}*/ enumValue;
|
/**@type {SlashCommandEnumValue}*/ enumValue;
|
||||||
|
|
||||||
|
@ -13,7 +24,7 @@ export class SlashCommandEnumAutoCompleteOption extends AutoCompleteOption {
|
||||||
* @param {SlashCommandEnumValue} enumValue
|
* @param {SlashCommandEnumValue} enumValue
|
||||||
*/
|
*/
|
||||||
constructor(cmd, enumValue) {
|
constructor(cmd, enumValue) {
|
||||||
super(enumValue.value, enumValue.typeIcon, enumValue.type);
|
super(enumValue.value, enumValue.typeIcon, enumValue.type, enumValue.matchProvider, enumValue.valueProvider, enumValue.makeSelectable);
|
||||||
this.cmd = cmd;
|
this.cmd = cmd;
|
||||||
this.enumValue = enumValue;
|
this.enumValue = enumValue;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
import { SlashCommandExecutor } from './SlashCommandExecutor.js';
|
||||||
|
import { SlashCommandScope } from './SlashCommandScope.js';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {'enum' | 'command' | 'namedArgument' | 'variable' | 'qr' | 'macro' | 'number' | 'name'} EnumType
|
* @typedef {'enum' | 'command' | 'namedArgument' | 'variable' | 'qr' | 'macro' | 'number' | 'name'} EnumType
|
||||||
|
@ -37,14 +40,17 @@ export const enumTypes = {
|
||||||
getBasedOnIndex(index) {
|
getBasedOnIndex(index) {
|
||||||
const keys = Object.keys(this);
|
const keys = Object.keys(this);
|
||||||
return this[keys[(index ?? 0) % keys.length]];
|
return this[keys[(index ?? 0) % keys.length]];
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
export class SlashCommandEnumValue {
|
export class SlashCommandEnumValue {
|
||||||
/**@type {string}*/ value;
|
/**@type {string}*/ value;
|
||||||
/**@type {string}*/ description;
|
/**@type {string}*/ description;
|
||||||
/**@type {EnumType}*/ type = 'enum';
|
/**@type {EnumType}*/ type = 'enum';
|
||||||
/**@type {string}*/ typeIcon = '◊';
|
/**@type {string}*/ typeIcon = '◊';
|
||||||
|
/**@type {(input:string)=>boolean}*/ matchProvider;
|
||||||
|
/**@type {(input:string)=>string}*/ valueProvider;
|
||||||
|
/**@type {boolean}*/ makeSelectable = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A constructor for creating a SlashCommandEnumValue instance.
|
* A constructor for creating a SlashCommandEnumValue instance.
|
||||||
|
@ -52,13 +58,19 @@ export class SlashCommandEnumValue {
|
||||||
* @param {string} value - The value
|
* @param {string} value - The value
|
||||||
* @param {string?} description - Optional description, displayed in a second line
|
* @param {string?} description - Optional description, displayed in a second line
|
||||||
* @param {EnumType?} type - type of the enum (defining its color)
|
* @param {EnumType?} type - type of the enum (defining its color)
|
||||||
* @param {string} typeIcon - The icon to display (Can be pulled from `enumIcons` for common ones)
|
* @param {string?} typeIcon - The icon to display (Can be pulled from `enumIcons` for common ones)
|
||||||
|
* @param {(input:string)=>boolean?} matchProvider - A custom function to match autocomplete input instead of startsWith/includes/fuzzy. Should only be used for generic options like "any number" or "any string". "input" is the part of the text that is getting auto completed.
|
||||||
|
* @param {(input:string)=>string?} valueProvider - A function returning a value to be used in autocomplete instead of the enum value. "input" is the part of the text that is getting auto completed. By default, values with a valueProvider will not be selectable in the autocomplete (with tab/enter).
|
||||||
|
* @param {boolean?} makeSelectable - Set to true to make the value selectable (through tab/enter) even though a valueProvider exists.
|
||||||
*/
|
*/
|
||||||
constructor(value, description = null, type = 'enum', typeIcon = '◊') {
|
constructor(value, description = null, type = 'enum', typeIcon = '◊', matchProvider = null, valueProvider = null, makeSelectable = false) {
|
||||||
this.value = value;
|
this.value = value;
|
||||||
this.description = description;
|
this.description = description;
|
||||||
this.type = type ?? 'enum';
|
this.type = type ?? 'enum';
|
||||||
this.typeIcon = typeIcon;
|
this.typeIcon = typeIcon;
|
||||||
|
this.matchProvider = matchProvider;
|
||||||
|
this.valueProvider = valueProvider;
|
||||||
|
this.makeSelectable = makeSelectable;
|
||||||
}
|
}
|
||||||
|
|
||||||
toString() {
|
toString() {
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
export class SlashCommandExecutionError extends Error {
|
||||||
|
/**@type {string} */ commandName;
|
||||||
|
/**@type {number} */ start;
|
||||||
|
/**@type {number} */ end;
|
||||||
|
/**@type {string} */ commandText;
|
||||||
|
|
||||||
|
/**@type {string} */ text;
|
||||||
|
get index() { return this.start; }
|
||||||
|
|
||||||
|
get line() {
|
||||||
|
return this.text.slice(0, this.index).replace(/[^\n]/g, '').length;
|
||||||
|
}
|
||||||
|
get column() {
|
||||||
|
return this.text.slice(0, this.index).split('\n').pop().length;
|
||||||
|
}
|
||||||
|
get hint() {
|
||||||
|
let lineOffset = this.line.toString().length;
|
||||||
|
let lineStart = this.index;
|
||||||
|
let start = this.index;
|
||||||
|
let end = this.index;
|
||||||
|
let offset = 0;
|
||||||
|
let lineCount = 0;
|
||||||
|
while (offset < 10000 && lineCount < 3 && start >= 0) {
|
||||||
|
if (this.text[start] == '\n') lineCount++;
|
||||||
|
if (lineCount == 0) lineStart--;
|
||||||
|
offset++;
|
||||||
|
start--;
|
||||||
|
}
|
||||||
|
if (this.text[start + 1] == '\n') start++;
|
||||||
|
offset = 0;
|
||||||
|
while (offset < 10000 && this.text[end] != '\n') {
|
||||||
|
offset++;
|
||||||
|
end++;
|
||||||
|
}
|
||||||
|
let hint = [];
|
||||||
|
let lines = this.text.slice(start + 1, end - 1).split('\n');
|
||||||
|
let lineNum = this.line - lines.length + 1;
|
||||||
|
let tabOffset = 0;
|
||||||
|
for (const line of lines) {
|
||||||
|
const num = `${' '.repeat(lineOffset - lineNum.toString().length)}${lineNum}`;
|
||||||
|
lineNum++;
|
||||||
|
const untabbedLine = line.replace(/\t/g, ' '.repeat(4));
|
||||||
|
tabOffset = untabbedLine.length - line.length;
|
||||||
|
hint.push(`${num}: ${untabbedLine}`);
|
||||||
|
}
|
||||||
|
hint.push(`${' '.repeat(this.index - lineStart + lineOffset + 1 + tabOffset)}^^^^^`);
|
||||||
|
return hint.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
constructor(cause, message, commandName, start, end, commandText, fullText) {
|
||||||
|
super(message, { cause });
|
||||||
|
this.commandName = commandName;
|
||||||
|
this.start = start;
|
||||||
|
this.end = end;
|
||||||
|
this.commandText = commandText;
|
||||||
|
this.text = fullText;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
import { uuidv4 } from '../utils.js';
|
||||||
import { SlashCommand } from './SlashCommand.js';
|
import { SlashCommand } from './SlashCommand.js';
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
import { SlashCommandClosure } from './SlashCommandClosure.js';
|
import { SlashCommandClosure } from './SlashCommandClosure.js';
|
||||||
|
@ -16,6 +17,17 @@ export class SlashCommandExecutor {
|
||||||
/**@type {Number}*/ startUnnamedArgs;
|
/**@type {Number}*/ startUnnamedArgs;
|
||||||
/**@type {Number}*/ endUnnamedArgs;
|
/**@type {Number}*/ endUnnamedArgs;
|
||||||
/**@type {String}*/ name = '';
|
/**@type {String}*/ name = '';
|
||||||
|
/**@type {String}*/ #source = uuidv4();
|
||||||
|
get source() { return this.#source; }
|
||||||
|
set source(value) {
|
||||||
|
this.#source = value;
|
||||||
|
for (const arg of this.namedArgumentList.filter(it=>it.value instanceof SlashCommandClosure)) {
|
||||||
|
arg.value.source = value;
|
||||||
|
}
|
||||||
|
for (const arg of this.unnamedArgumentList.filter(it=>it.value instanceof SlashCommandClosure)) {
|
||||||
|
arg.value.source = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
/**@type {SlashCommand}*/ command;
|
/**@type {SlashCommand}*/ command;
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
/**@type {SlashCommandNamedArgumentAssignment[]}*/ namedArgumentList = [];
|
/**@type {SlashCommandNamedArgumentAssignment[]}*/ namedArgumentList = [];
|
||||||
|
|
|
@ -17,7 +17,10 @@ import { SlashCommandAutoCompleteNameResult } from './SlashCommandAutoCompleteNa
|
||||||
import { SlashCommandUnnamedArgumentAssignment } from './SlashCommandUnnamedArgumentAssignment.js';
|
import { SlashCommandUnnamedArgumentAssignment } from './SlashCommandUnnamedArgumentAssignment.js';
|
||||||
import { SlashCommandEnumValue } from './SlashCommandEnumValue.js';
|
import { SlashCommandEnumValue } from './SlashCommandEnumValue.js';
|
||||||
import { MacroAutoCompleteOption } from '../autocomplete/MacroAutoCompleteOption.js';
|
import { MacroAutoCompleteOption } from '../autocomplete/MacroAutoCompleteOption.js';
|
||||||
|
import { SlashCommandBreakPoint } from './SlashCommandBreakPoint.js';
|
||||||
|
import { SlashCommandDebugController } from './SlashCommandDebugController.js';
|
||||||
import { commonEnumProviders } from './SlashCommandCommonEnumsProvider.js';
|
import { commonEnumProviders } from './SlashCommandCommonEnumsProvider.js';
|
||||||
|
import { SlashCommandBreak } from './SlashCommandBreak.js';
|
||||||
|
|
||||||
/** @typedef {import('./SlashCommand.js').NamedArgumentsCapture} NamedArgumentsCapture */
|
/** @typedef {import('./SlashCommand.js').NamedArgumentsCapture} NamedArgumentsCapture */
|
||||||
/** @typedef {import('./SlashCommand.js').NamedArguments} NamedArguments */
|
/** @typedef {import('./SlashCommand.js').NamedArguments} NamedArguments */
|
||||||
|
@ -53,7 +56,7 @@ export class SlashCommandParser {
|
||||||
* @param {SlashCommand} command
|
* @param {SlashCommand} command
|
||||||
*/
|
*/
|
||||||
static addCommandObject(command) {
|
static addCommandObject(command) {
|
||||||
const reserved = ['/', '#', ':', 'parser-flag'];
|
const reserved = ['/', '#', ':', 'parser-flag', 'breakpoint'];
|
||||||
for (const start of reserved) {
|
for (const start of reserved) {
|
||||||
if (command.name.toLowerCase().startsWith(start) || (command.aliases ?? []).find(a=>a.toLowerCase().startsWith(start))) {
|
if (command.name.toLowerCase().startsWith(start) || (command.aliases ?? []).find(a=>a.toLowerCase().startsWith(start))) {
|
||||||
throw new Error(`Illegal Name. Slash command name cannot begin with "${start}".`);
|
throw new Error(`Illegal Name. Slash command name cannot begin with "${start}".`);
|
||||||
|
@ -70,6 +73,18 @@ export class SlashCommandParser {
|
||||||
console.trace('WARN: Duplicate slash command registered!', [command.name, ...command.aliases]);
|
console.trace('WARN: Duplicate slash command registered!', [command.name, ...command.aliases]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const stack = new Error().stack.split('\n').map(it=>it.trim());
|
||||||
|
command.isExtension = stack.find(it=>it.includes('/scripts/extensions/')) != null;
|
||||||
|
command.isThirdParty = stack.find(it=>it.includes('/scripts/extensions/third-party/')) != null;
|
||||||
|
if (command.isThirdParty) {
|
||||||
|
command.source = stack.find(it=>it.includes('/scripts/extensions/third-party/')).replace(/^.*?\/scripts\/extensions\/third-party\/([^/]+)\/.*$/, '$1');
|
||||||
|
} else if (command.isExtension) {
|
||||||
|
command.source = stack.find(it=>it.includes('/scripts/extensions/')).replace(/^.*?\/scripts\/extensions\/([^/]+)\/.*$/, '$1');
|
||||||
|
} else {
|
||||||
|
const idx = stack.findLastIndex(it=>it.includes('at SlashCommandParser.')) + 1;
|
||||||
|
command.source = stack[idx].replace(/^.*?\/((?:scripts\/)?(?:[^/]+)\.js).*$/, '$1');
|
||||||
|
}
|
||||||
|
|
||||||
this.commands[command.name] = command;
|
this.commands[command.name] = command;
|
||||||
|
|
||||||
if (Array.isArray(command.aliases)) {
|
if (Array.isArray(command.aliases)) {
|
||||||
|
@ -89,6 +104,7 @@ export class SlashCommandParser {
|
||||||
/**@type {string}*/ text;
|
/**@type {string}*/ text;
|
||||||
/**@type {number}*/ index;
|
/**@type {number}*/ index;
|
||||||
/**@type {SlashCommandAbortController}*/ abortController;
|
/**@type {SlashCommandAbortController}*/ abortController;
|
||||||
|
/**@type {SlashCommandDebugController}*/ debugController;
|
||||||
/**@type {SlashCommandScope}*/ scope;
|
/**@type {SlashCommandScope}*/ scope;
|
||||||
/**@type {SlashCommandClosure}*/ closure;
|
/**@type {SlashCommandClosure}*/ closure;
|
||||||
|
|
||||||
|
@ -101,6 +117,8 @@ export class SlashCommandParser {
|
||||||
/**@type {SlashCommandExecutor[]}*/ commandIndex;
|
/**@type {SlashCommandExecutor[]}*/ commandIndex;
|
||||||
/**@type {SlashCommandScope[]}*/ scopeIndex;
|
/**@type {SlashCommandScope[]}*/ scopeIndex;
|
||||||
|
|
||||||
|
/**@type {string}*/ parserContext;
|
||||||
|
|
||||||
get userIndex() { return this.index; }
|
get userIndex() { return this.index; }
|
||||||
|
|
||||||
get ahead() {
|
get ahead() {
|
||||||
|
@ -154,6 +172,21 @@ export class SlashCommandParser {
|
||||||
helpString: 'Write a comment.',
|
helpString: 'Write a comment.',
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
if (!Object.keys(this.commands).includes('breakpoint')) {
|
||||||
|
SlashCommandParser.addCommandObjectUnsafe(SlashCommand.fromProps({ name: 'breakpoint',
|
||||||
|
helpString: 'Set a breakpoint for debugging in the QR Editor.',
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
if (!Object.keys(this.commands).includes('break')) {
|
||||||
|
SlashCommandParser.addCommandObjectUnsafe(SlashCommand.fromProps({ name: 'break',
|
||||||
|
helpString: 'Break out of a loop or closure executed through /run or /:',
|
||||||
|
unnamedArgumentList: [
|
||||||
|
SlashCommandArgument.fromProps({ description: 'value to pass down the pipe instead of the current pipe value',
|
||||||
|
typeList: Object.values(ARGUMENT_TYPE),
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
//TODO should not be re-registered from every instance
|
//TODO should not be re-registered from every instance
|
||||||
this.registerLanguage();
|
this.registerLanguage();
|
||||||
|
@ -191,13 +224,19 @@ export class SlashCommandParser {
|
||||||
|
|
||||||
function getQuotedRunRegex() {
|
function getQuotedRunRegex() {
|
||||||
try {
|
try {
|
||||||
return new RegExp('(".+?(?<!\\\\)")|(\\S+?)');
|
return new RegExp('(".+?(?<!\\\\)")|(\\S+?)(\\||$|\\s)');
|
||||||
} catch {
|
} catch {
|
||||||
// fallback for browsers that don't support lookbehind
|
// fallback for browsers that don't support lookbehind
|
||||||
return /(".+?")|(\S+?)/;
|
return /(".+?")|(\S+?)(\||$|\s)/;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const BLOCK_COMMENT = {
|
||||||
|
scope: 'comment',
|
||||||
|
begin: /\/\*/,
|
||||||
|
end: /\*\|/,
|
||||||
|
contains: [],
|
||||||
|
};
|
||||||
const COMMENT = {
|
const COMMENT = {
|
||||||
scope: 'comment',
|
scope: 'comment',
|
||||||
begin: /\/[/#]/,
|
begin: /\/[/#]/,
|
||||||
|
@ -205,9 +244,29 @@ export class SlashCommandParser {
|
||||||
contains: [],
|
contains: [],
|
||||||
};
|
};
|
||||||
const ABORT = {
|
const ABORT = {
|
||||||
scope: 'abort',
|
begin: /\/(abort|breakpoint)/,
|
||||||
begin: /\/abort/,
|
beginScope: 'abort',
|
||||||
end: /\||$|:}/,
|
end: /\||$|(?=:})/,
|
||||||
|
excludeEnd: false,
|
||||||
|
returnEnd: true,
|
||||||
|
contains: [],
|
||||||
|
};
|
||||||
|
const IMPORT = {
|
||||||
|
scope: 'command',
|
||||||
|
begin: /\/(import)/,
|
||||||
|
beginScope: 'keyword',
|
||||||
|
end: /\||$|(?=:})/,
|
||||||
|
excludeEnd: false,
|
||||||
|
returnEnd: true,
|
||||||
|
contains: [],
|
||||||
|
};
|
||||||
|
const BREAK = {
|
||||||
|
scope: 'command',
|
||||||
|
begin: /\/(break)/,
|
||||||
|
beginScope: 'keyword',
|
||||||
|
end: /\||$|(?=:})/,
|
||||||
|
excludeEnd: false,
|
||||||
|
returnEnd: true,
|
||||||
contains: [],
|
contains: [],
|
||||||
};
|
};
|
||||||
const LET = {
|
const LET = {
|
||||||
|
@ -218,26 +277,31 @@ export class SlashCommandParser {
|
||||||
1: 'variable',
|
1: 'variable',
|
||||||
},
|
},
|
||||||
end: /\||$|:}/,
|
end: /\||$|:}/,
|
||||||
|
excludeEnd: false,
|
||||||
|
returnEnd: true,
|
||||||
contains: [],
|
contains: [],
|
||||||
};
|
};
|
||||||
const SETVAR = {
|
const SETVAR = {
|
||||||
begin: /\/(setvar|setglobalvar)\s+/,
|
begin: /\/(setvar|setglobalvar)\s+/,
|
||||||
beginScope: 'variable',
|
beginScope: 'variable',
|
||||||
end: /\||$|:}/,
|
end: /\||$|:}/,
|
||||||
excludeEnd: true,
|
excludeEnd: false,
|
||||||
|
returnEnd: true,
|
||||||
contains: [],
|
contains: [],
|
||||||
};
|
};
|
||||||
const GETVAR = {
|
const GETVAR = {
|
||||||
begin: /\/(getvar|getglobalvar)\s+/,
|
begin: /\/(getvar|getglobalvar)\s+/,
|
||||||
beginScope: 'variable',
|
beginScope: 'variable',
|
||||||
end: /\||$|:}/,
|
end: /\||$|:}/,
|
||||||
excludeEnd: true,
|
excludeEnd: false,
|
||||||
|
returnEnd: true,
|
||||||
contains: [],
|
contains: [],
|
||||||
};
|
};
|
||||||
const RUN = {
|
const RUN = {
|
||||||
match: [
|
match: [
|
||||||
/\/:/,
|
/\/:/,
|
||||||
getQuotedRunRegex(),
|
getQuotedRunRegex(),
|
||||||
|
/\||$|(?=:})/,
|
||||||
],
|
],
|
||||||
className: {
|
className: {
|
||||||
1: 'variable.language',
|
1: 'variable.language',
|
||||||
|
@ -250,7 +314,8 @@ export class SlashCommandParser {
|
||||||
begin: /\/\S+/,
|
begin: /\/\S+/,
|
||||||
beginScope: 'title.function',
|
beginScope: 'title.function',
|
||||||
end: /\||$|(?=:})/,
|
end: /\||$|(?=:})/,
|
||||||
excludeEnd: true,
|
excludeEnd: false,
|
||||||
|
returnEnd: true,
|
||||||
contains: [], // defined later
|
contains: [], // defined later
|
||||||
};
|
};
|
||||||
const CLOSURE = {
|
const CLOSURE = {
|
||||||
|
@ -271,6 +336,19 @@ export class SlashCommandParser {
|
||||||
begin: /{{/,
|
begin: /{{/,
|
||||||
end: /}}/,
|
end: /}}/,
|
||||||
};
|
};
|
||||||
|
const PIPEBREAK = {
|
||||||
|
beginScope: 'pipebreak',
|
||||||
|
begin: /\|\|/,
|
||||||
|
end: '',
|
||||||
|
};
|
||||||
|
const PIPE = {
|
||||||
|
beginScope: 'pipe',
|
||||||
|
begin: /\|/,
|
||||||
|
end: '',
|
||||||
|
};
|
||||||
|
BLOCK_COMMENT.contains.push(
|
||||||
|
BLOCK_COMMENT,
|
||||||
|
);
|
||||||
RUN.contains.push(
|
RUN.contains.push(
|
||||||
hljs.BACKSLASH_ESCAPE,
|
hljs.BACKSLASH_ESCAPE,
|
||||||
NAMED_ARG,
|
NAMED_ARG,
|
||||||
|
@ -279,6 +357,22 @@ export class SlashCommandParser {
|
||||||
MACRO,
|
MACRO,
|
||||||
CLOSURE,
|
CLOSURE,
|
||||||
);
|
);
|
||||||
|
IMPORT.contains.push(
|
||||||
|
hljs.BACKSLASH_ESCAPE,
|
||||||
|
NAMED_ARG,
|
||||||
|
NUMBER,
|
||||||
|
MACRO,
|
||||||
|
CLOSURE,
|
||||||
|
hljs.QUOTE_STRING_MODE,
|
||||||
|
);
|
||||||
|
BREAK.contains.push(
|
||||||
|
hljs.BACKSLASH_ESCAPE,
|
||||||
|
NAMED_ARG,
|
||||||
|
NUMBER,
|
||||||
|
MACRO,
|
||||||
|
CLOSURE,
|
||||||
|
hljs.QUOTE_STRING_MODE,
|
||||||
|
);
|
||||||
LET.contains.push(
|
LET.contains.push(
|
||||||
hljs.BACKSLASH_ESCAPE,
|
hljs.BACKSLASH_ESCAPE,
|
||||||
NAMED_ARG,
|
NAMED_ARG,
|
||||||
|
@ -303,6 +397,14 @@ export class SlashCommandParser {
|
||||||
MACRO,
|
MACRO,
|
||||||
CLOSURE,
|
CLOSURE,
|
||||||
);
|
);
|
||||||
|
ABORT.contains.push(
|
||||||
|
hljs.BACKSLASH_ESCAPE,
|
||||||
|
NAMED_ARG,
|
||||||
|
NUMBER,
|
||||||
|
MACRO,
|
||||||
|
CLOSURE,
|
||||||
|
hljs.QUOTE_STRING_MODE,
|
||||||
|
);
|
||||||
COMMAND.contains.push(
|
COMMAND.contains.push(
|
||||||
hljs.BACKSLASH_ESCAPE,
|
hljs.BACKSLASH_ESCAPE,
|
||||||
NAMED_ARG,
|
NAMED_ARG,
|
||||||
|
@ -313,8 +415,11 @@ export class SlashCommandParser {
|
||||||
);
|
);
|
||||||
CLOSURE.contains.push(
|
CLOSURE.contains.push(
|
||||||
hljs.BACKSLASH_ESCAPE,
|
hljs.BACKSLASH_ESCAPE,
|
||||||
|
BLOCK_COMMENT,
|
||||||
COMMENT,
|
COMMENT,
|
||||||
ABORT,
|
ABORT,
|
||||||
|
IMPORT,
|
||||||
|
BREAK,
|
||||||
NAMED_ARG,
|
NAMED_ARG,
|
||||||
NUMBER,
|
NUMBER,
|
||||||
MACRO,
|
MACRO,
|
||||||
|
@ -325,20 +430,27 @@ export class SlashCommandParser {
|
||||||
COMMAND,
|
COMMAND,
|
||||||
'self',
|
'self',
|
||||||
hljs.QUOTE_STRING_MODE,
|
hljs.QUOTE_STRING_MODE,
|
||||||
|
PIPEBREAK,
|
||||||
|
PIPE,
|
||||||
);
|
);
|
||||||
hljs.registerLanguage('stscript', ()=>({
|
hljs.registerLanguage('stscript', ()=>({
|
||||||
case_insensitive: false,
|
case_insensitive: false,
|
||||||
keywords: ['|'],
|
keywords: [],
|
||||||
contains: [
|
contains: [
|
||||||
hljs.BACKSLASH_ESCAPE,
|
hljs.BACKSLASH_ESCAPE,
|
||||||
|
BLOCK_COMMENT,
|
||||||
COMMENT,
|
COMMENT,
|
||||||
ABORT,
|
ABORT,
|
||||||
|
IMPORT,
|
||||||
|
BREAK,
|
||||||
RUN,
|
RUN,
|
||||||
LET,
|
LET,
|
||||||
GETVAR,
|
GETVAR,
|
||||||
SETVAR,
|
SETVAR,
|
||||||
COMMAND,
|
COMMAND,
|
||||||
CLOSURE,
|
CLOSURE,
|
||||||
|
PIPEBREAK,
|
||||||
|
PIPE,
|
||||||
],
|
],
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
@ -415,7 +527,7 @@ export class SlashCommandParser {
|
||||||
);
|
);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
const result = new SlashCommandAutoCompleteNameResult(executor, this.commands);
|
const result = new SlashCommandAutoCompleteNameResult(executor, this.scopeIndex[this.commandIndex.indexOf(executor)], this.commands);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -515,11 +627,14 @@ export class SlashCommandParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
replaceGetvar(value) {
|
replaceGetvar(value) {
|
||||||
return value.replace(/{{(get(?:global)?var)::([^}]+)}}/gi, (_, cmd, name) => {
|
return value.replace(/{{(get(?:global)?var)::([^}]+)}}/gi, (match, cmd, name, idx) => {
|
||||||
name = name.trim();
|
name = name.trim();
|
||||||
|
const startIdx = this.index - value.length + idx;
|
||||||
|
const endIdx = this.index - value.length + idx + match.length;
|
||||||
// store pipe
|
// store pipe
|
||||||
const pipeName = `_PARSER_${uuidv4()}`;
|
const pipeName = `_PARSER_PIPE_${uuidv4()}`;
|
||||||
const storePipe = new SlashCommandExecutor(null); {
|
const storePipe = new SlashCommandExecutor(startIdx); {
|
||||||
|
storePipe.end = endIdx;
|
||||||
storePipe.command = this.commands['let'];
|
storePipe.command = this.commands['let'];
|
||||||
storePipe.name = 'let';
|
storePipe.name = 'let';
|
||||||
const nameAss = new SlashCommandUnnamedArgumentAssignment();
|
const nameAss = new SlashCommandUnnamedArgumentAssignment();
|
||||||
|
@ -530,17 +645,19 @@ export class SlashCommandParser {
|
||||||
this.closure.executorList.push(storePipe);
|
this.closure.executorList.push(storePipe);
|
||||||
}
|
}
|
||||||
// getvar / getglobalvar
|
// getvar / getglobalvar
|
||||||
const getvar = new SlashCommandExecutor(null); {
|
const getvar = new SlashCommandExecutor(startIdx); {
|
||||||
|
getvar.end = endIdx;
|
||||||
getvar.command = this.commands[cmd];
|
getvar.command = this.commands[cmd];
|
||||||
getvar.name = 'cmd';
|
getvar.name = cmd;
|
||||||
const nameAss = new SlashCommandUnnamedArgumentAssignment();
|
const nameAss = new SlashCommandUnnamedArgumentAssignment();
|
||||||
nameAss.value = name;
|
nameAss.value = name;
|
||||||
getvar.unnamedArgumentList = [nameAss];
|
getvar.unnamedArgumentList = [nameAss];
|
||||||
this.closure.executorList.push(getvar);
|
this.closure.executorList.push(getvar);
|
||||||
}
|
}
|
||||||
// set to temp scoped var
|
// set to temp scoped var
|
||||||
const varName = `_PARSER_${uuidv4()}`;
|
const varName = `_PARSER_VAR_${uuidv4()}`;
|
||||||
const setvar = new SlashCommandExecutor(null); {
|
const setvar = new SlashCommandExecutor(startIdx); {
|
||||||
|
setvar.end = endIdx;
|
||||||
setvar.command = this.commands['let'];
|
setvar.command = this.commands['let'];
|
||||||
setvar.name = 'let';
|
setvar.name = 'let';
|
||||||
const nameAss = new SlashCommandUnnamedArgumentAssignment();
|
const nameAss = new SlashCommandUnnamedArgumentAssignment();
|
||||||
|
@ -551,7 +668,8 @@ export class SlashCommandParser {
|
||||||
this.closure.executorList.push(setvar);
|
this.closure.executorList.push(setvar);
|
||||||
}
|
}
|
||||||
// return pipe
|
// return pipe
|
||||||
const returnPipe = new SlashCommandExecutor(null); {
|
const returnPipe = new SlashCommandExecutor(startIdx); {
|
||||||
|
returnPipe.end = endIdx;
|
||||||
returnPipe.command = this.commands['return'];
|
returnPipe.command = this.commands['return'];
|
||||||
returnPipe.name = 'return';
|
returnPipe.name = 'return';
|
||||||
const varAss = new SlashCommandUnnamedArgumentAssignment();
|
const varAss = new SlashCommandUnnamedArgumentAssignment();
|
||||||
|
@ -564,12 +682,13 @@ export class SlashCommandParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
parse(text, verifyCommandNames = true, flags = null, abortController = null) {
|
parse(text, verifyCommandNames = true, flags = null, abortController = null, debugController = null) {
|
||||||
this.verifyCommandNames = verifyCommandNames;
|
this.verifyCommandNames = verifyCommandNames;
|
||||||
for (const key of Object.keys(PARSER_FLAG)) {
|
for (const key of Object.keys(PARSER_FLAG)) {
|
||||||
this.flags[PARSER_FLAG[key]] = flags?.[PARSER_FLAG[key]] ?? power_user.stscript.parser.flags[PARSER_FLAG[key]] ?? false;
|
this.flags[PARSER_FLAG[key]] = flags?.[PARSER_FLAG[key]] ?? power_user.stscript.parser.flags[PARSER_FLAG[key]] ?? false;
|
||||||
}
|
}
|
||||||
this.abortController = abortController;
|
this.abortController = abortController;
|
||||||
|
this.debugController = debugController;
|
||||||
this.text = text;
|
this.text = text;
|
||||||
this.index = 0;
|
this.index = 0;
|
||||||
this.scope = null;
|
this.scope = null;
|
||||||
|
@ -577,6 +696,7 @@ export class SlashCommandParser {
|
||||||
this.commandIndex = [];
|
this.commandIndex = [];
|
||||||
this.scopeIndex = [];
|
this.scopeIndex = [];
|
||||||
this.macroIndex = [];
|
this.macroIndex = [];
|
||||||
|
this.parserContext = uuidv4();
|
||||||
const closure = this.parseClosure(true);
|
const closure = this.parseClosure(true);
|
||||||
return closure;
|
return closure;
|
||||||
}
|
}
|
||||||
|
@ -604,8 +724,12 @@ export class SlashCommandParser {
|
||||||
if (!isRoot) this.take(2); // discard opening {:
|
if (!isRoot) this.take(2); // discard opening {:
|
||||||
const textStart = this.index;
|
const textStart = this.index;
|
||||||
let closure = new SlashCommandClosure(this.scope);
|
let closure = new SlashCommandClosure(this.scope);
|
||||||
|
closure.parserContext = this.parserContext;
|
||||||
|
closure.fullText = this.text;
|
||||||
closure.abortController = this.abortController;
|
closure.abortController = this.abortController;
|
||||||
|
closure.debugController = this.debugController;
|
||||||
this.scope = closure.scope;
|
this.scope = closure.scope;
|
||||||
|
const oldClosure = this.closure;
|
||||||
this.closure = closure;
|
this.closure = closure;
|
||||||
this.discardWhitespace();
|
this.discardWhitespace();
|
||||||
while (this.testNamedArgument()) {
|
while (this.testNamedArgument()) {
|
||||||
|
@ -615,7 +739,9 @@ export class SlashCommandParser {
|
||||||
this.discardWhitespace();
|
this.discardWhitespace();
|
||||||
}
|
}
|
||||||
while (!this.testClosureEnd()) {
|
while (!this.testClosureEnd()) {
|
||||||
if (this.testComment()) {
|
if (this.testBlockComment()) {
|
||||||
|
this.parseBlockComment();
|
||||||
|
} else if (this.testComment()) {
|
||||||
this.parseComment();
|
this.parseComment();
|
||||||
} else if (this.testParserFlag()) {
|
} else if (this.testParserFlag()) {
|
||||||
this.parseParserFlag();
|
this.parseParserFlag();
|
||||||
|
@ -623,6 +749,14 @@ export class SlashCommandParser {
|
||||||
const cmd = this.parseRunShorthand();
|
const cmd = this.parseRunShorthand();
|
||||||
closure.executorList.push(cmd);
|
closure.executorList.push(cmd);
|
||||||
injectPipe = true;
|
injectPipe = true;
|
||||||
|
} else if (this.testBreakPoint()) {
|
||||||
|
const bp = this.parseBreakPoint();
|
||||||
|
if (this.debugController) {
|
||||||
|
closure.executorList.push(bp);
|
||||||
|
}
|
||||||
|
} else if (this.testBreak()) {
|
||||||
|
const b = this.parseBreak();
|
||||||
|
closure.executorList.push(b);
|
||||||
} else if (this.testCommand()) {
|
} else if (this.testCommand()) {
|
||||||
const cmd = this.parseCommand();
|
const cmd = this.parseCommand();
|
||||||
cmd.injectPipe = injectPipe;
|
cmd.injectPipe = injectPipe;
|
||||||
|
@ -651,14 +785,83 @@ export class SlashCommandParser {
|
||||||
}
|
}
|
||||||
closureIndexEntry.end = this.index - 1;
|
closureIndexEntry.end = this.index - 1;
|
||||||
this.scope = closure.scope.parent;
|
this.scope = closure.scope.parent;
|
||||||
|
this.closure = oldClosure ?? closure;
|
||||||
return closure;
|
return closure;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
testBreakPoint() {
|
||||||
|
return this.testSymbol(/\/breakpoint\s*\|/);
|
||||||
|
}
|
||||||
|
parseBreakPoint() {
|
||||||
|
const cmd = new SlashCommandBreakPoint();
|
||||||
|
cmd.name = 'breakpoint';
|
||||||
|
cmd.command = this.commands['breakpoint'];
|
||||||
|
cmd.start = this.index + 1;
|
||||||
|
this.take('/breakpoint'.length);
|
||||||
|
cmd.end = this.index;
|
||||||
|
this.commandIndex.push(cmd);
|
||||||
|
this.scopeIndex.push(this.scope.getCopy());
|
||||||
|
return cmd;
|
||||||
|
}
|
||||||
|
|
||||||
|
testBreak() {
|
||||||
|
return this.testSymbol(/\/break(\s|\||$)/);
|
||||||
|
}
|
||||||
|
parseBreak() {
|
||||||
|
const cmd = new SlashCommandBreak();
|
||||||
|
cmd.name = 'break';
|
||||||
|
cmd.command = this.commands['break'];
|
||||||
|
cmd.start = this.index + 1;
|
||||||
|
this.take('/break'.length);
|
||||||
|
this.discardWhitespace();
|
||||||
|
if (this.testUnnamedArgument()) {
|
||||||
|
cmd.unnamedArgumentList.push(...this.parseUnnamedArgument());
|
||||||
|
}
|
||||||
|
cmd.end = this.index;
|
||||||
|
this.commandIndex.push(cmd);
|
||||||
|
this.scopeIndex.push(this.scope.getCopy());
|
||||||
|
return cmd;
|
||||||
|
}
|
||||||
|
|
||||||
|
testBlockComment() {
|
||||||
|
return this.testSymbol('/*');
|
||||||
|
}
|
||||||
|
testBlockCommentEnd() {
|
||||||
|
if (!this.verifyCommandNames) {
|
||||||
|
if (this.index >= this.text.length) return true;
|
||||||
|
} else {
|
||||||
|
if (this.ahead.length < 1) throw new SlashCommandParserError(`Unclosed block comment at position ${this.userIndex}`, this.text, this.index);
|
||||||
|
}
|
||||||
|
return this.testSymbol('*|');
|
||||||
|
}
|
||||||
|
parseBlockComment() {
|
||||||
|
const start = this.index + 1;
|
||||||
|
const cmd = new SlashCommandExecutor(start);
|
||||||
|
cmd.command = this.commands['*'];
|
||||||
|
this.commandIndex.push(cmd);
|
||||||
|
this.scopeIndex.push(this.scope.getCopy());
|
||||||
|
this.take(); // discard "/"
|
||||||
|
cmd.name = this.take(); //set "*" as name
|
||||||
|
while (!this.testBlockCommentEnd()) {
|
||||||
|
if (this.testBlockComment()) {
|
||||||
|
this.parseBlockComment();
|
||||||
|
}
|
||||||
|
this.take();
|
||||||
|
}
|
||||||
|
this.take(2); // take closing "*|"
|
||||||
|
cmd.end = this.index - 1;
|
||||||
|
}
|
||||||
|
|
||||||
testComment() {
|
testComment() {
|
||||||
return this.testSymbol(/\/[/#]/);
|
return this.testSymbol(/\/[/#]/);
|
||||||
}
|
}
|
||||||
testCommentEnd() {
|
testCommentEnd() {
|
||||||
return this.testCommandEnd();
|
if (!this.verifyCommandNames) {
|
||||||
|
if (this.index >= this.text.length) return true;
|
||||||
|
} else {
|
||||||
|
if (this.endOfText) throw new SlashCommandParserError(`Unclosed comment at position ${this.userIndex}`, this.text, this.index);
|
||||||
|
}
|
||||||
|
return this.testSymbol('|');
|
||||||
}
|
}
|
||||||
parseComment() {
|
parseComment() {
|
||||||
const start = this.index + 1;
|
const start = this.index + 1;
|
||||||
|
@ -719,11 +922,13 @@ export class SlashCommandParser {
|
||||||
else assignment.value = this.parseValue();
|
else assignment.value = this.parseValue();
|
||||||
cmd.unnamedArgumentList = [assignment];
|
cmd.unnamedArgumentList = [assignment];
|
||||||
this.discardWhitespace();
|
this.discardWhitespace();
|
||||||
|
cmd.startNamedArgs = this.index;
|
||||||
while (this.testNamedArgument()) {
|
while (this.testNamedArgument()) {
|
||||||
const arg = this.parseNamedArgument();
|
const arg = this.parseNamedArgument();
|
||||||
cmd.namedArgumentList.push(arg);
|
cmd.namedArgumentList.push(arg);
|
||||||
this.discardWhitespace();
|
this.discardWhitespace();
|
||||||
}
|
}
|
||||||
|
cmd.endNamedArgs = this.index;
|
||||||
this.discardWhitespace();
|
this.discardWhitespace();
|
||||||
// /run shorthand does not take unnamed arguments (the command name practically *is* the unnamed argument)
|
// /run shorthand does not take unnamed arguments (the command name practically *is* the unnamed argument)
|
||||||
if (this.testRunShorthandEnd()) {
|
if (this.testRunShorthandEnd()) {
|
||||||
|
@ -761,10 +966,10 @@ export class SlashCommandParser {
|
||||||
this.discardWhitespace();
|
this.discardWhitespace();
|
||||||
}
|
}
|
||||||
this.discardWhitespace();
|
this.discardWhitespace();
|
||||||
cmd.startUnnamedArgs = this.index;
|
cmd.startUnnamedArgs = this.index - (/\s(\s*)$/s.exec(this.behind)?.[1]?.length ?? 0);
|
||||||
cmd.endUnnamedArgs = this.index;
|
cmd.endUnnamedArgs = this.index;
|
||||||
if (this.testUnnamedArgument()) {
|
if (this.testUnnamedArgument()) {
|
||||||
cmd.unnamedArgumentList = this.parseUnnamedArgument(cmd.command?.unnamedArgumentList?.length && cmd?.command?.splitUnnamedArgument);
|
cmd.unnamedArgumentList = this.parseUnnamedArgument(cmd.command?.unnamedArgumentList?.length && cmd?.command?.splitUnnamedArgument, cmd?.command?.splitUnnamedArgumentCount);
|
||||||
cmd.endUnnamedArgs = this.index;
|
cmd.endUnnamedArgs = this.index;
|
||||||
if (cmd.name == 'let') {
|
if (cmd.name == 'let') {
|
||||||
const keyArg = cmd.namedArgumentList.find(it=>it.name == 'key');
|
const keyArg = cmd.namedArgumentList.find(it=>it.name == 'key');
|
||||||
|
@ -773,6 +978,17 @@ export class SlashCommandParser {
|
||||||
} else if (typeof cmd.unnamedArgumentList[0]?.value == 'string') {
|
} else if (typeof cmd.unnamedArgumentList[0]?.value == 'string') {
|
||||||
this.scope.variableNames.push(cmd.unnamedArgumentList[0].value);
|
this.scope.variableNames.push(cmd.unnamedArgumentList[0].value);
|
||||||
}
|
}
|
||||||
|
} else if (cmd.name == 'import') {
|
||||||
|
const value = /**@type {string[]}*/(cmd.unnamedArgumentList.map(it=>it.value));
|
||||||
|
for (let i = 0; i < value.length; i++) {
|
||||||
|
const srcName = value[i];
|
||||||
|
let dstName = srcName;
|
||||||
|
if (i + 2 < value.length && value[i + 1] == 'as') {
|
||||||
|
dstName = value[i + 2];
|
||||||
|
i += 2;
|
||||||
|
}
|
||||||
|
this.scope.variableNames.push(dstName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.testCommandEnd()) {
|
if (this.testCommandEnd()) {
|
||||||
|
@ -813,25 +1029,61 @@ export class SlashCommandParser {
|
||||||
testUnnamedArgumentEnd() {
|
testUnnamedArgumentEnd() {
|
||||||
return this.testCommandEnd();
|
return this.testCommandEnd();
|
||||||
}
|
}
|
||||||
parseUnnamedArgument(split) {
|
parseUnnamedArgument(split, splitCount = null) {
|
||||||
|
const wasSplit = split;
|
||||||
/**@type {SlashCommandClosure|String}*/
|
/**@type {SlashCommandClosure|String}*/
|
||||||
let value = this.jumpedEscapeSequence ? this.take() : ''; // take the first, already tested, char if it is an escaped one
|
let value = this.jumpedEscapeSequence ? this.take() : ''; // take the first, already tested, char if it is an escaped one
|
||||||
let isList = split;
|
let isList = split;
|
||||||
let listValues = [];
|
let listValues = [];
|
||||||
|
let listQuoted = []; // keep track of which listValues were quoted
|
||||||
/**@type {SlashCommandUnnamedArgumentAssignment}*/
|
/**@type {SlashCommandUnnamedArgumentAssignment}*/
|
||||||
let assignment = new SlashCommandUnnamedArgumentAssignment();
|
let assignment = new SlashCommandUnnamedArgumentAssignment();
|
||||||
assignment.start = this.index;
|
assignment.start = this.index;
|
||||||
|
if (!split && this.testQuotedValue()) {
|
||||||
|
// if the next bit is a quoted value, take the whole value and gather contents as a list
|
||||||
|
assignment.value = this.parseQuotedValue();
|
||||||
|
assignment.end = this.index;
|
||||||
|
isList = true;
|
||||||
|
listValues.push(assignment);
|
||||||
|
listQuoted.push(true);
|
||||||
|
assignment = new SlashCommandUnnamedArgumentAssignment();
|
||||||
|
assignment.start = this.index;
|
||||||
|
}
|
||||||
while (!this.testUnnamedArgumentEnd()) {
|
while (!this.testUnnamedArgumentEnd()) {
|
||||||
|
if (split && splitCount && listValues.length >= splitCount) {
|
||||||
|
// the split count has just been reached: stop splitting, the rest is one singular value
|
||||||
|
split = false;
|
||||||
|
if (this.testQuotedValue()) {
|
||||||
|
// if the next bit is a quoted value, take the whole value
|
||||||
|
assignment.value = this.parseQuotedValue();
|
||||||
|
assignment.end = this.index;
|
||||||
|
listValues.push(assignment);
|
||||||
|
listQuoted.push(true);
|
||||||
|
assignment = new SlashCommandUnnamedArgumentAssignment();
|
||||||
|
assignment.start = this.index;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (this.testClosure()) {
|
if (this.testClosure()) {
|
||||||
isList = true;
|
isList = true;
|
||||||
if (value.length > 0) {
|
if (value.length > 0) {
|
||||||
this.indexMacros(this.index - value.length, value);
|
this.indexMacros(this.index - value.length, value);
|
||||||
assignment.value = value;
|
assignment.value = value;
|
||||||
listValues.push(assignment);
|
listValues.push(assignment);
|
||||||
|
listQuoted.push(false);
|
||||||
assignment = new SlashCommandUnnamedArgumentAssignment();
|
assignment = new SlashCommandUnnamedArgumentAssignment();
|
||||||
assignment.start = this.index;
|
assignment.start = this.index;
|
||||||
|
if (!split && this.testQuotedValue()) {
|
||||||
|
// if where currently not splitting and the next bit is a quoted value, take the whole value
|
||||||
|
assignment.value = this.parseQuotedValue();
|
||||||
|
assignment.end = this.index;
|
||||||
|
listValues.push(assignment);
|
||||||
|
listQuoted.push(true);
|
||||||
|
assignment = new SlashCommandUnnamedArgumentAssignment();
|
||||||
|
assignment.start = this.index;
|
||||||
|
} else {
|
||||||
value = '';
|
value = '';
|
||||||
}
|
}
|
||||||
|
}
|
||||||
assignment.start = this.index;
|
assignment.start = this.index;
|
||||||
assignment.value = this.parseClosure();
|
assignment.value = this.parseClosure();
|
||||||
assignment.end = this.index;
|
assignment.end = this.index;
|
||||||
|
@ -845,18 +1097,21 @@ export class SlashCommandParser {
|
||||||
assignment.value = this.parseQuotedValue();
|
assignment.value = this.parseQuotedValue();
|
||||||
assignment.end = this.index;
|
assignment.end = this.index;
|
||||||
listValues.push(assignment);
|
listValues.push(assignment);
|
||||||
|
listQuoted.push(true);
|
||||||
assignment = new SlashCommandUnnamedArgumentAssignment();
|
assignment = new SlashCommandUnnamedArgumentAssignment();
|
||||||
} else if (this.testListValue()) {
|
} else if (this.testListValue()) {
|
||||||
assignment.start = this.index;
|
assignment.start = this.index;
|
||||||
assignment.value = this.parseListValue();
|
assignment.value = this.parseListValue();
|
||||||
assignment.end = this.index;
|
assignment.end = this.index;
|
||||||
listValues.push(assignment);
|
listValues.push(assignment);
|
||||||
|
listQuoted.push(false);
|
||||||
assignment = new SlashCommandUnnamedArgumentAssignment();
|
assignment = new SlashCommandUnnamedArgumentAssignment();
|
||||||
} else if (this.testValue()) {
|
} else if (this.testValue()) {
|
||||||
assignment.start = this.index;
|
assignment.start = this.index;
|
||||||
assignment.value = this.parseValue();
|
assignment.value = this.parseValue();
|
||||||
assignment.end = this.index;
|
assignment.end = this.index;
|
||||||
listValues.push(assignment);
|
listValues.push(assignment);
|
||||||
|
listQuoted.push(false);
|
||||||
assignment = new SlashCommandUnnamedArgumentAssignment();
|
assignment = new SlashCommandUnnamedArgumentAssignment();
|
||||||
} else {
|
} else {
|
||||||
throw new SlashCommandParserError(`Unexpected end of unnamed argument at index ${this.userIndex}.`);
|
throw new SlashCommandParserError(`Unexpected end of unnamed argument at index ${this.userIndex}.`);
|
||||||
|
@ -870,8 +1125,48 @@ export class SlashCommandParser {
|
||||||
if (isList && value.length > 0) {
|
if (isList && value.length > 0) {
|
||||||
assignment.value = value;
|
assignment.value = value;
|
||||||
listValues.push(assignment);
|
listValues.push(assignment);
|
||||||
|
listQuoted.push(false);
|
||||||
}
|
}
|
||||||
if (isList) {
|
if (isList) {
|
||||||
|
const firstVal = listValues[0];
|
||||||
|
if (typeof firstVal?.value == 'string') {
|
||||||
|
if (!listQuoted[0]) {
|
||||||
|
// only trim the first part if it wasn't quoted
|
||||||
|
firstVal.value = firstVal.value.trimStart();
|
||||||
|
}
|
||||||
|
if (firstVal.value.length == 0) {
|
||||||
|
listValues.shift();
|
||||||
|
listQuoted.shift();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const lastVal = listValues.slice(-1)[0];
|
||||||
|
if (typeof lastVal?.value == 'string') {
|
||||||
|
if (!listQuoted.slice(-1)[0]) {
|
||||||
|
// only trim the last part if it wasn't quoted
|
||||||
|
lastVal.value = lastVal.value.trimEnd();
|
||||||
|
}
|
||||||
|
if (lastVal.value.length == 0) {
|
||||||
|
listValues.pop();
|
||||||
|
listQuoted.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (wasSplit && splitCount && splitCount + 1 < listValues.length) {
|
||||||
|
// if split with a split count and there are more values than expected
|
||||||
|
// -> should be result of quoting + additional (non-whitespace) text
|
||||||
|
// -> join the parts into one and restore quotes
|
||||||
|
const joined = new SlashCommandUnnamedArgumentAssignment();
|
||||||
|
joined.start = listValues[splitCount].start;
|
||||||
|
joined.end = listValues.slice(-1)[0].end;
|
||||||
|
joined.value = '';
|
||||||
|
for (let i = splitCount; i < listValues.length; i++) {
|
||||||
|
if (listQuoted[i]) joined.value += `"${listValues[i].value}"`;
|
||||||
|
else joined.value += listValues[i].value;
|
||||||
|
}
|
||||||
|
listValues = [
|
||||||
|
...listValues.slice(0, splitCount),
|
||||||
|
joined,
|
||||||
|
];
|
||||||
|
}
|
||||||
return listValues;
|
return listValues;
|
||||||
}
|
}
|
||||||
this.indexMacros(this.index - value.length, value);
|
this.indexMacros(this.index - value.length, value);
|
||||||
|
|
|
@ -38,9 +38,11 @@ export class SlashCommandScope {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
setMacro(key, value) {
|
setMacro(key, value, overwrite = true) {
|
||||||
|
if (overwrite || !this.macroList.find(it=>it.key == key)) {
|
||||||
this.macros[key] = value;
|
this.macros[key] = value;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
existsVariableInScope(key) {
|
existsVariableInScope(key) {
|
||||||
|
@ -95,7 +97,7 @@ export class SlashCommandScope {
|
||||||
return v ?? '';
|
return v ?? '';
|
||||||
} else {
|
} else {
|
||||||
const value = this.variables[key];
|
const value = this.variables[key];
|
||||||
return (value === '' || isNaN(Number(value))) ? (value || '') : Number(value);
|
return (value?.trim?.() === '' || isNaN(Number(value))) ? (value || '') : Number(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.parent) {
|
if (this.parent) {
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { getRequestHeaders } from '../script.js';
|
||||||
import { isMobile } from './RossAscends-mods.js';
|
import { isMobile } from './RossAscends-mods.js';
|
||||||
import { collapseNewlines } from './power-user.js';
|
import { collapseNewlines } from './power-user.js';
|
||||||
import { debounce_timeout } from './constants.js';
|
import { debounce_timeout } from './constants.js';
|
||||||
import { Popup } from './popup.js';
|
import { Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pagination status string template.
|
* Pagination status string template.
|
||||||
|
@ -1929,3 +1929,75 @@ export function getFreeName(name, list, numberFormatter = (n) => ` #${n}`) {
|
||||||
}
|
}
|
||||||
return `${name}${numberFormatter(counter)}`;
|
return `${name}${numberFormatter(counter)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function fetchFaFile(name) {
|
||||||
|
const style = document.createElement('style');
|
||||||
|
style.innerHTML = await (await fetch(`/css/${name}`)).text();
|
||||||
|
document.head.append(style);
|
||||||
|
const sheet = style.sheet;
|
||||||
|
style.remove();
|
||||||
|
return [...sheet.cssRules].filter(it=>it.style?.content).map(it=>it.selectorText.split('::').shift().slice(1));
|
||||||
|
}
|
||||||
|
export async function fetchFa() {
|
||||||
|
return [...new Set((await Promise.all([
|
||||||
|
fetchFaFile('fontawesome.min.css'),
|
||||||
|
])).flat())];
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Opens a popup with all the available Font Awesome icons and returns the selected icon's name.
|
||||||
|
* @prop {string[]} customList A custom list of Font Awesome icons to use instead of all available icons.
|
||||||
|
* @returns {Promise<string>} The icon name (fa-pencil) or null if cancelled.
|
||||||
|
*/
|
||||||
|
export async function showFontAwesomePicker(customList = null) {
|
||||||
|
const faList = customList ?? await fetchFa();
|
||||||
|
const fas = {};
|
||||||
|
const dom = document.createElement('div'); {
|
||||||
|
dom.classList.add('faPicker-container');
|
||||||
|
const search = document.createElement('div'); {
|
||||||
|
search.classList.add('faQuery-container');
|
||||||
|
const qry = document.createElement('input'); {
|
||||||
|
qry.classList.add('text_pole');
|
||||||
|
qry.classList.add('faQuery');
|
||||||
|
qry.type = 'search';
|
||||||
|
qry.placeholder = 'Filter icons';
|
||||||
|
qry.autofocus = true;
|
||||||
|
const qryDebounced = debounce(()=>{
|
||||||
|
const result = faList.filter(it=>it.includes(qry.value));
|
||||||
|
for (const fa of faList) {
|
||||||
|
if (!result.includes(fa)) {
|
||||||
|
fas[fa].classList.add('hidden');
|
||||||
|
} else {
|
||||||
|
fas[fa].classList.remove('hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
qry.addEventListener('input', () => qryDebounced());
|
||||||
|
search.append(qry);
|
||||||
|
}
|
||||||
|
dom.append(search);
|
||||||
|
}
|
||||||
|
const grid = document.createElement('div'); {
|
||||||
|
grid.classList.add('faPicker');
|
||||||
|
for (const fa of faList) {
|
||||||
|
const opt = document.createElement('div'); {
|
||||||
|
fas[fa] = opt;
|
||||||
|
opt.classList.add('menu_button');
|
||||||
|
opt.classList.add('fa-solid');
|
||||||
|
opt.classList.add(fa);
|
||||||
|
opt.title = fa.slice(3);
|
||||||
|
opt.dataset.result = POPUP_RESULT.AFFIRMATIVE.toString();
|
||||||
|
opt.addEventListener('click', () => value = fa);
|
||||||
|
grid.append(opt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dom.append(grid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let value = '';
|
||||||
|
const picker = new Popup(dom, POPUP_TYPE.TEXT, null, { allowVerticalScrolling: true, okButton: 'No Icon', cancelButton: 'Cancel' });
|
||||||
|
await picker.show();
|
||||||
|
if (picker.result == POPUP_RESULT.AFFIRMATIVE) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
|
@ -4,10 +4,11 @@ import { executeSlashCommandsWithOptions } from './slash-commands.js';
|
||||||
import { SlashCommand } from './slash-commands/SlashCommand.js';
|
import { SlashCommand } from './slash-commands/SlashCommand.js';
|
||||||
import { SlashCommandAbortController } from './slash-commands/SlashCommandAbortController.js';
|
import { SlashCommandAbortController } from './slash-commands/SlashCommandAbortController.js';
|
||||||
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
|
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
|
||||||
|
import { SlashCommandBreakController } from './slash-commands/SlashCommandBreakController.js';
|
||||||
import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js';
|
import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js';
|
||||||
import { SlashCommandClosureResult } from './slash-commands/SlashCommandClosureResult.js';
|
import { SlashCommandClosureResult } from './slash-commands/SlashCommandClosureResult.js';
|
||||||
import { commonEnumProviders } from './slash-commands/SlashCommandCommonEnumsProvider.js';
|
import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
|
||||||
import { SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js';
|
import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';
|
||||||
import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js';
|
import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js';
|
||||||
import { SlashCommandScope } from './slash-commands/SlashCommandScope.js';
|
import { SlashCommandScope } from './slash-commands/SlashCommandScope.js';
|
||||||
import { isFalseBoolean } from './utils.js';
|
import { isFalseBoolean } from './utils.js';
|
||||||
|
@ -40,7 +41,7 @@ function getLocalVariable(name, args = {}) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (localVariable === '' || isNaN(Number(localVariable))) ? (localVariable || '') : Number(localVariable);
|
return (localVariable?.trim?.() === '' || isNaN(Number(localVariable))) ? (localVariable || '') : Number(localVariable);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setLocalVariable(name, value, args = {}) {
|
function setLocalVariable(name, value, args = {}) {
|
||||||
|
@ -93,7 +94,7 @@ function getGlobalVariable(name, args = {}) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (globalVariable === '' || isNaN(Number(globalVariable))) ? (globalVariable || '') : Number(globalVariable);
|
return (globalVariable?.trim?.() === '' || isNaN(Number(globalVariable))) ? (globalVariable || '') : Number(globalVariable);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setGlobalVariable(name, value, args = {}) {
|
function setGlobalVariable(name, value, args = {}) {
|
||||||
|
@ -348,11 +349,13 @@ async function whileCallback(args, value) {
|
||||||
|
|
||||||
if (result && command) {
|
if (result && command) {
|
||||||
if (command instanceof SlashCommandClosure) {
|
if (command instanceof SlashCommandClosure) {
|
||||||
|
command.breakController = new SlashCommandBreakController();
|
||||||
commandResult = await command.execute();
|
commandResult = await command.execute();
|
||||||
} else {
|
} else {
|
||||||
commandResult = await executeSubCommands(command, args._scope, args._parserFlags, args._abortController);
|
commandResult = await executeSubCommands(command, args._scope, args._parserFlags, args._abortController);
|
||||||
}
|
}
|
||||||
if (commandResult.isAborted) break;
|
if (commandResult.isAborted) break;
|
||||||
|
if (commandResult.isBreak) break;
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -390,8 +393,8 @@ async function timesCallback(args, value) {
|
||||||
const iterations = Math.min(Number(repeats), isGuardOff ? Number.MAX_SAFE_INTEGER : MAX_LOOPS);
|
const iterations = Math.min(Number(repeats), isGuardOff ? Number.MAX_SAFE_INTEGER : MAX_LOOPS);
|
||||||
let result;
|
let result;
|
||||||
for (let i = 0; i < iterations; i++) {
|
for (let i = 0; i < iterations; i++) {
|
||||||
/**@type {SlashCommandClosureResult}*/
|
|
||||||
if (command instanceof SlashCommandClosure) {
|
if (command instanceof SlashCommandClosure) {
|
||||||
|
command.breakController = new SlashCommandBreakController();
|
||||||
command.scope.setMacro('timesIndex', i);
|
command.scope.setMacro('timesIndex', i);
|
||||||
result = await command.execute();
|
result = await command.execute();
|
||||||
}
|
}
|
||||||
|
@ -399,6 +402,7 @@ async function timesCallback(args, value) {
|
||||||
result = await executeSubCommands(command.replace(/\{\{timesIndex\}\}/g, i.toString()), args._scope, args._parserFlags, args._abortController);
|
result = await executeSubCommands(command.replace(/\{\{timesIndex\}\}/g, i.toString()), args._scope, args._parserFlags, args._abortController);
|
||||||
}
|
}
|
||||||
if (result.isAborted) break;
|
if (result.isAborted) break;
|
||||||
|
if (result.isBreak) break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result?.pipe ?? '';
|
return result?.pipe ?? '';
|
||||||
|
@ -461,7 +465,7 @@ function existsGlobalVariable(name) {
|
||||||
* @param {object} args Command arguments
|
* @param {object} args Command arguments
|
||||||
* @returns {{a: string | number, b: string | number, rule: string}} Boolean operands
|
* @returns {{a: string | number, b: string | number, rule: string}} Boolean operands
|
||||||
*/
|
*/
|
||||||
function parseBooleanOperands(args) {
|
export function parseBooleanOperands(args) {
|
||||||
// Resolution order: numeric literal, local variable, global variable, string literal
|
// Resolution order: numeric literal, local variable, global variable, string literal
|
||||||
/**
|
/**
|
||||||
* @param {string} operand Boolean operand candidate
|
* @param {string} operand Boolean operand candidate
|
||||||
|
@ -510,36 +514,15 @@ function parseBooleanOperands(args) {
|
||||||
* @param {string|number} b The right operand
|
* @param {string|number} b The right operand
|
||||||
* @returns {boolean} True if the rule yields true, false otherwise
|
* @returns {boolean} True if the rule yields true, false otherwise
|
||||||
*/
|
*/
|
||||||
function evalBoolean(rule, a, b) {
|
export function evalBoolean(rule, a, b) {
|
||||||
if (!rule) {
|
if (!rule) {
|
||||||
toastr.warning('The rule must be specified for the boolean comparison.', 'Invalid command');
|
toastr.warning('The rule must be specified for the boolean comparison.', 'Invalid command');
|
||||||
throw new Error('Invalid command.');
|
throw new Error('Invalid command.');
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = false;
|
let result = false;
|
||||||
|
if (typeof a === 'number' && typeof b === 'number') {
|
||||||
if (typeof a === 'string' && typeof b !== 'number') {
|
// only do numeric comparison if both operands are numbers
|
||||||
const aString = String(a).toLowerCase();
|
|
||||||
const bString = String(b).toLowerCase();
|
|
||||||
|
|
||||||
switch (rule) {
|
|
||||||
case 'in':
|
|
||||||
result = aString.includes(bString);
|
|
||||||
break;
|
|
||||||
case 'nin':
|
|
||||||
result = !aString.includes(bString);
|
|
||||||
break;
|
|
||||||
case 'eq':
|
|
||||||
result = aString === bString;
|
|
||||||
break;
|
|
||||||
case 'neq':
|
|
||||||
result = aString !== bString;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
toastr.error('Unknown boolean comparison rule for type string.', 'Invalid /if command');
|
|
||||||
throw new Error('Invalid command.');
|
|
||||||
}
|
|
||||||
} else if (typeof a === 'number') {
|
|
||||||
const aNumber = Number(a);
|
const aNumber = Number(a);
|
||||||
const bNumber = Number(b);
|
const bNumber = Number(b);
|
||||||
|
|
||||||
|
@ -569,6 +552,38 @@ function evalBoolean(rule, a, b) {
|
||||||
toastr.error('Unknown boolean comparison rule for type number.', 'Invalid command');
|
toastr.error('Unknown boolean comparison rule for type number.', 'Invalid command');
|
||||||
throw new Error('Invalid command.');
|
throw new Error('Invalid command.');
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// otherwise do case-insensitive string comparsion, stringify non-strings
|
||||||
|
let aString;
|
||||||
|
let bString;
|
||||||
|
if (typeof a == 'string') {
|
||||||
|
aString = a.toLowerCase();
|
||||||
|
} else {
|
||||||
|
aString = JSON.stringify(a).toLowerCase();
|
||||||
|
}
|
||||||
|
if (typeof b == 'string') {
|
||||||
|
bString = b.toLowerCase();
|
||||||
|
} else {
|
||||||
|
bString = JSON.stringify(b).toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (rule) {
|
||||||
|
case 'in':
|
||||||
|
result = aString.includes(bString);
|
||||||
|
break;
|
||||||
|
case 'nin':
|
||||||
|
result = !aString.includes(bString);
|
||||||
|
break;
|
||||||
|
case 'eq':
|
||||||
|
result = aString === bString;
|
||||||
|
break;
|
||||||
|
case 'neq':
|
||||||
|
result = aString !== bString;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
toastr.error('Unknown boolean comparison rule for type string.', 'Invalid /if command');
|
||||||
|
throw new Error('Invalid command.');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
@ -783,24 +798,29 @@ function randValuesCallback(from, to, args) {
|
||||||
* @returns The variable's value
|
* @returns The variable's value
|
||||||
*/
|
*/
|
||||||
function letCallback(args, value) {
|
function letCallback(args, value) {
|
||||||
if (Array.isArray(value)) {
|
if (!Array.isArray(value)) value = [value];
|
||||||
args._scope.letVariable(value[0], typeof value[1] == 'string' ? value.slice(1).join(' ') : value[1]);
|
|
||||||
return value[1];
|
|
||||||
}
|
|
||||||
if (args.key !== undefined) {
|
if (args.key !== undefined) {
|
||||||
const key = args.key;
|
const key = args.key;
|
||||||
const val = value;
|
if (typeof key != 'string') throw new Error('Key must be a string');
|
||||||
|
if (args._hasUnnamedArgument) {
|
||||||
|
const val = typeof value[0] == 'string' ? value.join(' ') : value[0];
|
||||||
args._scope.letVariable(key, val);
|
args._scope.letVariable(key, val);
|
||||||
return val;
|
return val;
|
||||||
|
} else {
|
||||||
|
args._scope.letVariable(key);
|
||||||
|
return '';
|
||||||
}
|
}
|
||||||
if (value instanceof SlashCommandClosure) throw new Error('/let unnamed argument does not support closures if no key is provided');
|
}
|
||||||
if (value.includes(' ')) {
|
const key = value.shift();
|
||||||
const key = value.split(' ')[0];
|
if (typeof key != 'string') throw new Error('Key must be a string');
|
||||||
const val = value.split(' ').slice(1).join(' ');
|
if (value.length > 0) {
|
||||||
|
const val = typeof value[0] == 'string' ? value.join(' ') : value[0];
|
||||||
args._scope.letVariable(key, val);
|
args._scope.letVariable(key, val);
|
||||||
return val;
|
return val;
|
||||||
|
} else {
|
||||||
|
args._scope.letVariable(key);
|
||||||
|
return '';
|
||||||
}
|
}
|
||||||
args._scope.letVariable(value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -813,8 +833,9 @@ function varCallback(args, value) {
|
||||||
if (!Array.isArray(value)) value = [value];
|
if (!Array.isArray(value)) value = [value];
|
||||||
if (args.key !== undefined) {
|
if (args.key !== undefined) {
|
||||||
const key = args.key;
|
const key = args.key;
|
||||||
|
if (typeof key != 'string') throw new Error('Key must be a string');
|
||||||
if (args._hasUnnamedArgument) {
|
if (args._hasUnnamedArgument) {
|
||||||
const val = value.join(' ');
|
const val = typeof value[0] == 'string' ? value.join(' ') : value[0];
|
||||||
args._scope.setVariable(key, val, args.index);
|
args._scope.setVariable(key, val, args.index);
|
||||||
return val;
|
return val;
|
||||||
} else {
|
} else {
|
||||||
|
@ -822,8 +843,9 @@ function varCallback(args, value) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const key = value.shift();
|
const key = value.shift();
|
||||||
|
if (typeof key != 'string') throw new Error('Key must be a string');
|
||||||
if (value.length > 0) {
|
if (value.length > 0) {
|
||||||
const val = value.join(' ');
|
const val = typeof value[0] == 'string' ? value.join(' ') : value[0];
|
||||||
args._scope.setVariable(key, val, args.index);
|
args._scope.setVariable(key, val, args.index);
|
||||||
return val;
|
return val;
|
||||||
} else {
|
} else {
|
||||||
|
@ -1382,6 +1404,7 @@ export function registerVariableCommands() {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
splitUnnamedArgument: true,
|
splitUnnamedArgument: true,
|
||||||
|
splitUnnamedArgumentCount: 1,
|
||||||
helpString: `
|
helpString: `
|
||||||
<div>
|
<div>
|
||||||
Execute any valid slash command enclosed in quotes <code>repeats</code> number of times.
|
Execute any valid slash command enclosed in quotes <code>repeats</code> number of times.
|
||||||
|
@ -1459,7 +1482,7 @@ export function registerVariableCommands() {
|
||||||
}));
|
}));
|
||||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||||
name: 'add',
|
name: 'add',
|
||||||
callback: addValuesCallback,
|
callback: (args, /**@type {string[]}*/value) => addValuesCallback(args, value.join(' ')),
|
||||||
returns: 'sum of the provided values',
|
returns: 'sum of the provided values',
|
||||||
unnamedArgumentList: [
|
unnamedArgumentList: [
|
||||||
SlashCommandArgument.fromProps({
|
SlashCommandArgument.fromProps({
|
||||||
|
@ -1467,10 +1490,32 @@ export function registerVariableCommands() {
|
||||||
typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
|
typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
|
||||||
isRequired: true,
|
isRequired: true,
|
||||||
acceptsMultiple: true,
|
acceptsMultiple: true,
|
||||||
enumProvider: commonEnumProviders.variables('all'),
|
enumProvider: (executor, scope)=>{
|
||||||
|
const vars = commonEnumProviders.variables('all')(executor, scope);
|
||||||
|
vars.push(
|
||||||
|
new SlashCommandEnumValue(
|
||||||
|
'any variable name',
|
||||||
|
null,
|
||||||
|
enumTypes.variable,
|
||||||
|
enumIcons.variable,
|
||||||
|
(input)=>/^\w*$/.test(input),
|
||||||
|
(input)=>input,
|
||||||
|
),
|
||||||
|
new SlashCommandEnumValue(
|
||||||
|
'any number',
|
||||||
|
null,
|
||||||
|
enumTypes.number,
|
||||||
|
enumIcons.number,
|
||||||
|
(input)=>input == '' || !Number.isNaN(Number(input)),
|
||||||
|
(input)=>input,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return vars;
|
||||||
|
},
|
||||||
forceEnum: false,
|
forceEnum: false,
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
splitUnnamedArgument: true,
|
||||||
helpString: `
|
helpString: `
|
||||||
<div>
|
<div>
|
||||||
Performs an addition of the set of values and passes the result down the pipe.
|
Performs an addition of the set of values and passes the result down the pipe.
|
||||||
|
@ -2001,6 +2046,7 @@ export function registerVariableCommands() {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
splitUnnamedArgument: true,
|
splitUnnamedArgument: true,
|
||||||
|
splitUnnamedArgumentCount: 1,
|
||||||
helpString: `
|
helpString: `
|
||||||
<div>
|
<div>
|
||||||
Get or set a variable.
|
Get or set a variable.
|
||||||
|
@ -2043,6 +2089,7 @@ export function registerVariableCommands() {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
splitUnnamedArgument: true,
|
splitUnnamedArgument: true,
|
||||||
|
splitUnnamedArgumentCount: 1,
|
||||||
helpString: `
|
helpString: `
|
||||||
<div>
|
<div>
|
||||||
Declares a new variable in the current scope.
|
Declares a new variable in the current scope.
|
||||||
|
|
123
public/style.css
123
public/style.css
|
@ -1383,6 +1383,8 @@ body[data-stscript-style="dark"] {
|
||||||
--ac-style-color-matchedText: rgba(108 171 251 / 1);
|
--ac-style-color-matchedText: rgba(108 171 251 / 1);
|
||||||
--ac-style-color-selectedBackground: rgba(32 57 92 / 1);
|
--ac-style-color-selectedBackground: rgba(32 57 92 / 1);
|
||||||
--ac-style-color-selectedText: rgba(255 255 255 / 1);
|
--ac-style-color-selectedText: rgba(255 255 255 / 1);
|
||||||
|
--ac-style-color-notSelectableBackground: rgb(65, 78, 95);
|
||||||
|
--ac-style-color-notSelectableText: rgba(255 255 255 / 1);
|
||||||
--ac-style-color-hoveredBackground: rgba(43 45 46 / 1);
|
--ac-style-color-hoveredBackground: rgba(43 45 46 / 1);
|
||||||
--ac-style-color-hoveredText: rgba(204 204 204 / 1);
|
--ac-style-color-hoveredText: rgba(204 204 204 / 1);
|
||||||
--ac-style-color-argName: rgba(171 209 239 / 1);
|
--ac-style-color-argName: rgba(171 209 239 / 1);
|
||||||
|
@ -1398,6 +1400,7 @@ body[data-stscript-style="dark"] {
|
||||||
--ac-style-color-punctuationL2: rgba(98 160 251 / 1);
|
--ac-style-color-punctuationL2: rgba(98 160 251 / 1);
|
||||||
--ac-style-color-currentParenthesis: rgba(195 118 210 / 1);
|
--ac-style-color-currentParenthesis: rgba(195 118 210 / 1);
|
||||||
--ac-style-color-comment: rgba(122 151 90 / 1);
|
--ac-style-color-comment: rgba(122 151 90 / 1);
|
||||||
|
--ac-style-color-keyword: rgba(182 137 190 / 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
body[data-stscript-style="light"] {
|
body[data-stscript-style="light"] {
|
||||||
|
@ -1408,6 +1411,8 @@ body[data-stscript-style="light"] {
|
||||||
--ac-style-color-matchedText: rgba(61 104 188 / 1);
|
--ac-style-color-matchedText: rgba(61 104 188 / 1);
|
||||||
--ac-style-color-selectedBackground: rgba(232 232 232 / 1);
|
--ac-style-color-selectedBackground: rgba(232 232 232 / 1);
|
||||||
--ac-style-color-selectedText: rgba(0 0 0 / 1);
|
--ac-style-color-selectedText: rgba(0 0 0 / 1);
|
||||||
|
--ac-style-color-notSelectableBackground: rgba(232 232 232 / 1);
|
||||||
|
--ac-style-color-notSelectableText: rgb(83, 83, 83);
|
||||||
--ac-style-color-hoveredBackground: rgba(242 242 242 / 1);
|
--ac-style-color-hoveredBackground: rgba(242 242 242 / 1);
|
||||||
--ac-style-color-hoveredText: rgba(59 59 59 / 1);
|
--ac-style-color-hoveredText: rgba(59 59 59 / 1);
|
||||||
--ac-style-color-argName: rgba(16 24 125 / 1);
|
--ac-style-color-argName: rgba(16 24 125 / 1);
|
||||||
|
@ -1419,6 +1424,7 @@ body[data-stscript-style="light"] {
|
||||||
--ac-style-color-variable: rgba(16 24 125 / 1);
|
--ac-style-color-variable: rgba(16 24 125 / 1);
|
||||||
--ac-style-color-currentParenthesis: rgba(195 118 210 / 1);
|
--ac-style-color-currentParenthesis: rgba(195 118 210 / 1);
|
||||||
--ac-style-color-comment: rgba(70 126 26 / 1);
|
--ac-style-color-comment: rgba(70 126 26 / 1);
|
||||||
|
--ac-style-color-keyword: rgba(182 137 190 / 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
body[data-stscript-style="theme"] {
|
body[data-stscript-style="theme"] {
|
||||||
|
@ -1429,6 +1435,8 @@ body[data-stscript-style="theme"] {
|
||||||
--ac-style-color-matchedText: var(--SmartThemeQuoteColor);
|
--ac-style-color-matchedText: var(--SmartThemeQuoteColor);
|
||||||
--ac-style-color-selectedBackground: color-mix(in srgb, rgb(128 128 128) 75%, var(--SmartThemeChatTintColor));
|
--ac-style-color-selectedBackground: color-mix(in srgb, rgb(128 128 128) 75%, var(--SmartThemeChatTintColor));
|
||||||
--ac-style-color-selectedText: var(--SmartThemeBodyColor);
|
--ac-style-color-selectedText: var(--SmartThemeBodyColor);
|
||||||
|
--ac-style-color-notSelectableBackground: color-mix(in srgb, rgb(128 128 128) 50%, var(--SmartThemeChatTintColor));
|
||||||
|
--ac-style-color-notSelectableText: var(--SmartThemeBodyColor);
|
||||||
--ac-style-color-hoveredBackground: color-mix(in srgb, rgb(128 128 128) 30%, var(--SmartThemeChatTintColor));
|
--ac-style-color-hoveredBackground: color-mix(in srgb, rgb(128 128 128) 30%, var(--SmartThemeChatTintColor));
|
||||||
--ac-style-color-hoveredText: var(--SmartThemeEmColor);
|
--ac-style-color-hoveredText: var(--SmartThemeEmColor);
|
||||||
--ac-style-color-argName: rgba(171 209 239 / 1);
|
--ac-style-color-argName: rgba(171 209 239 / 1);
|
||||||
|
@ -1439,6 +1447,7 @@ body[data-stscript-style="theme"] {
|
||||||
--ac-style-color-variable: rgba(131 193 252 / 1);
|
--ac-style-color-variable: rgba(131 193 252 / 1);
|
||||||
--ac-style-color-currentParenthesis: rgba(195 118 210 / 1);
|
--ac-style-color-currentParenthesis: rgba(195 118 210 / 1);
|
||||||
--ac-style-color-comment: rgba(122 151 90 / 1);
|
--ac-style-color-comment: rgba(122 151 90 / 1);
|
||||||
|
--ac-style-color-keyword: rgba(182 137 190 / 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
@ -1454,6 +1463,8 @@ body {
|
||||||
--ac-color-matchedText: var(--ac-style-color-matchedText, rgba(108 171 251 / 1));
|
--ac-color-matchedText: var(--ac-style-color-matchedText, rgba(108 171 251 / 1));
|
||||||
--ac-color-selectedBackground: var(--ac-style-color-selectedBackground, rgba(32 57 92 / 1));
|
--ac-color-selectedBackground: var(--ac-style-color-selectedBackground, rgba(32 57 92 / 1));
|
||||||
--ac-color-selectedText: var(--ac-style-color-selectedText, rgba(255 255 255 / 1));
|
--ac-color-selectedText: var(--ac-style-color-selectedText, rgba(255 255 255 / 1));
|
||||||
|
--ac-color-notSelectableBackground: var(--ac-style-color-notSelectableBackground, rgb(60, 73, 92));
|
||||||
|
--ac-color-notSelectableText: var(--ac-style-color-notSelectableText, rgba(255 255 255 / 1));
|
||||||
--ac-color-hoveredBackground: var(--ac-style-color-hoveredBackground, rgba(43 45 46 / 1));
|
--ac-color-hoveredBackground: var(--ac-style-color-hoveredBackground, rgba(43 45 46 / 1));
|
||||||
--ac-color-hoveredText: var(--ac-style-color-hoveredText, rgba(204 204 204 / 1));
|
--ac-color-hoveredText: var(--ac-style-color-hoveredText, rgba(204 204 204 / 1));
|
||||||
--ac-color-argName: var(--ac-style-color-argName, rgba(171 209 239 / 1));
|
--ac-color-argName: var(--ac-style-color-argName, rgba(171 209 239 / 1));
|
||||||
|
@ -1469,6 +1480,7 @@ body {
|
||||||
--ac-color-punctuationL2: var(--ac-style-color-punctuationL2, rgba(98 160 251 / 1));
|
--ac-color-punctuationL2: var(--ac-style-color-punctuationL2, rgba(98 160 251 / 1));
|
||||||
--ac-color-currentParenthesis: var(--ac-style-color-currentParenthesis, rgba(195 118 210 / 1));
|
--ac-color-currentParenthesis: var(--ac-style-color-currentParenthesis, rgba(195 118 210 / 1));
|
||||||
--ac-color-comment: var(--ac-style-color-comment, rgba(122 151 90 / 1));
|
--ac-color-comment: var(--ac-style-color-comment, rgba(122 151 90 / 1));
|
||||||
|
--ac-color-keyword: var(--ac-style-color-keyword, rgba(182 137 190 / 1));
|
||||||
|
|
||||||
font-size: calc(var(--ac-font-scale) * 1em);
|
font-size: calc(var(--ac-font-scale) * 1em);
|
||||||
|
|
||||||
|
@ -1569,16 +1581,25 @@ body[data-stscript-style] .hljs.language-stscript {
|
||||||
color: var(--ac-style-color-punctuation);
|
color: var(--ac-style-color-punctuation);
|
||||||
}
|
}
|
||||||
|
|
||||||
.hljs-keyword {
|
|
||||||
color: var(--ac-style-color-variableLanguage);
|
|
||||||
}
|
|
||||||
|
|
||||||
.hljs-comment {
|
.hljs-comment {
|
||||||
color: var(--ac-style-color-comment);
|
color: var(--ac-style-color-comment);
|
||||||
}
|
}
|
||||||
|
|
||||||
.hljs-abort {
|
.hljs-abort {
|
||||||
color: var(--ac-style-color-abort, #e38e23);
|
color: var(--ac-style-color-abort, #e38e23);
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-keyword {
|
||||||
|
color: var(--ac-style-color-keyword);
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-pipe {
|
||||||
|
color: var(--ac-style-color-punctuation);
|
||||||
|
}
|
||||||
|
.hljs-pipebreak {
|
||||||
|
color: var(--ac-style-color-type);
|
||||||
}
|
}
|
||||||
|
|
||||||
.hljs-closure {
|
.hljs-closure {
|
||||||
|
@ -1670,6 +1691,10 @@ body[data-stscript-style] .hljs.language-stscript {
|
||||||
background-color: var(--ac-color-selectedBackground);
|
background-color: var(--ac-color-selectedBackground);
|
||||||
color: var(--ac-color-selectedText);
|
color: var(--ac-color-selectedText);
|
||||||
}
|
}
|
||||||
|
&.selected.not-selectable>* {
|
||||||
|
background-color: var(--ac-color-notSelectableBackground);
|
||||||
|
color: var(--ac-color-notSelectableText);
|
||||||
|
}
|
||||||
|
|
||||||
>* {
|
>* {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
@ -1747,7 +1772,12 @@ body[data-stscript-style] .hljs.language-stscript {
|
||||||
padding: 0.25em 0.25em 0.5em 0.25em;
|
padding: 0.25em 0.25em 0.5em 0.25em;
|
||||||
border-bottom: 1px solid var(--ac-color-border);
|
border-bottom: 1px solid var(--ac-color-border);
|
||||||
|
|
||||||
>.name {
|
> .head {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5em;
|
||||||
|
}
|
||||||
|
> .head > .name, > .name {
|
||||||
|
flex: 1 1 auto;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: var(--ac-color-text);
|
color: var(--ac-color-text);
|
||||||
cursor: help;
|
cursor: help;
|
||||||
|
@ -1756,6 +1786,35 @@ body[data-stscript-style] .hljs.language-stscript {
|
||||||
text-decoration: 1px dotted underline;
|
text-decoration: 1px dotted underline;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
> .head > .source {
|
||||||
|
padding: 0 0.5em;
|
||||||
|
cursor: help;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5em;
|
||||||
|
&.isThirdParty.isExtension {
|
||||||
|
color: #F89406;
|
||||||
|
}
|
||||||
|
&.isCore {
|
||||||
|
color: transparent;
|
||||||
|
&.isExtension {
|
||||||
|
color: #51A351;
|
||||||
|
}
|
||||||
|
&:after {
|
||||||
|
content: '';
|
||||||
|
order: -1;
|
||||||
|
height: 14px;
|
||||||
|
aspect-ratio: 1 / 1;
|
||||||
|
background-image: url('/favicon.ico');
|
||||||
|
background-size: contain;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-decoration: 1px dotted underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
>.body {
|
>.body {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
@ -1843,7 +1902,8 @@ body[data-stscript-style] .hljs.language-stscript {
|
||||||
|
|
||||||
>code {
|
>code {
|
||||||
display: block;
|
display: block;
|
||||||
padding: 0;
|
padding: 1px;
|
||||||
|
tab-size: 4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3291,6 +3351,11 @@ grammarly-extension {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.menu_button.fa-fw {
|
||||||
|
/* Font Awesome icons that are a menu button and should be fixed width need a slight fix. 1.25em is their default, but we need to account for button spacing. */
|
||||||
|
min-width: calc(1.25em + 12px);
|
||||||
|
}
|
||||||
|
|
||||||
.avatar_div .menu_button,
|
.avatar_div .menu_button,
|
||||||
.form_create_bottom_buttons_block .menu_button {
|
.form_create_bottom_buttons_block .menu_button {
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
|
@ -5300,3 +5365,49 @@ body:not(.movingUI) .drawer-content.maximized {
|
||||||
.regex-highlight {
|
.regex-highlight {
|
||||||
color: #FAF8F6;
|
color: #FAF8F6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.popup:has(.faPicker) {
|
||||||
|
/* Fix height for fa picker popup, otherwise search is making it resize weirdly */
|
||||||
|
height: 70%;
|
||||||
|
.popup-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.faPicker-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;;
|
||||||
|
}
|
||||||
|
|
||||||
|
.faQuery-container {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.faPicker {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
overflow: auto;
|
||||||
|
gap: 1em;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(3.5em, 1fr));
|
||||||
|
|
||||||
|
.menu_button {
|
||||||
|
aspect-ratio: 1 / 1;
|
||||||
|
font-size: 2em;
|
||||||
|
height: 1lh;
|
||||||
|
line-height: 1.2;
|
||||||
|
padding: 0.25em;
|
||||||
|
width: unset;
|
||||||
|
box-sizing: content-box;
|
||||||
|
&.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.faPicker:not(:has(:not(.hidden)))::after {
|
||||||
|
content: 'No icons found';
|
||||||
|
color: var(--SmartThemeBodyColor);
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue