Merge 646f77f9f8
into e59fb85db8
This commit is contained in:
commit
648a2aaa46
|
@ -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,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
|
||||
"
|
||||
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,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
|
||||
"
|
||||
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,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
|
||||
"
|
||||
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 |
|
@ -4246,6 +4246,16 @@
|
|||
</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.">
|
||||
<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">
|
||||
|
|
|
@ -16,8 +16,15 @@ export const AUTOCOMPLETE_WIDTH = {
|
|||
'FULL': 2,
|
||||
};
|
||||
|
||||
/**@readonly*/
|
||||
/**@enum {Number}*/
|
||||
export const AUTOCOMPLETE_SELECT_KEY = {
|
||||
'TAB': 1, // 2^0
|
||||
'ENTER': 2, // 2^1
|
||||
};
|
||||
|
||||
export class AutoComplete {
|
||||
/**@type {HTMLTextAreaElement}*/ textarea;
|
||||
/**@type {HTMLTextAreaElement|HTMLInputElement}*/ textarea;
|
||||
/**@type {boolean}*/ isFloating = false;
|
||||
/**@type {()=>boolean}*/ checkIfActivate;
|
||||
/**@type {(text:string, index:number) => Promise<AutoCompleteNameResult>}*/ getNameAt;
|
||||
|
@ -56,6 +63,8 @@ export class AutoComplete {
|
|||
/**@type {function}*/ updateDetailsPositionDebounced;
|
||||
/**@type {function}*/ updateFloatingPositionDebounced;
|
||||
|
||||
/**@type {(item:AutoCompleteOption)=>any}*/ onSelect;
|
||||
|
||||
get matchType() {
|
||||
return power_user.stscript.matching ?? 'fuzzy';
|
||||
}
|
||||
|
@ -102,10 +111,15 @@ export class AutoComplete {
|
|||
this.updateDetailsPositionDebounced = debounce(this.updateDetailsPosition.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('click', ()=>this.isActive ? this.show() : null);
|
||||
textarea.addEventListener('selectionchange', ()=>this.show());
|
||||
textarea.addEventListener('click', ()=>{
|
||||
this.selectionStart = this.textarea.selectionStart;
|
||||
if (this.isActive) this.show();
|
||||
});
|
||||
textarea.addEventListener('blur', ()=>this.hide());
|
||||
if (isFloating) {
|
||||
textarea.addEventListener('scroll', ()=>this.updateFloatingPositionDebounced());
|
||||
|
@ -189,6 +203,11 @@ export class AutoComplete {
|
|||
* @returns The 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);
|
||||
let start = null;
|
||||
let consecutive = [];
|
||||
|
@ -339,7 +358,7 @@ export class AutoComplete {
|
|||
|
||||
this.result = this.effectiveParserResult.optionList
|
||||
// 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
|
||||
.filter((it,idx,list) => list.findIndex(opt=>opt.value == it.value) == idx);
|
||||
|
||||
|
@ -357,10 +376,11 @@ export class AutoComplete {
|
|||
// build element
|
||||
option.dom = this.makeItem(option);
|
||||
// update replacer and add quotes if necessary
|
||||
const optionName = option.valueProvider ? option.valueProvider(this.name) : option.name;
|
||||
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 {
|
||||
option.replacer = option.name;
|
||||
option.replacer = optionName;
|
||||
}
|
||||
// calculate fuzzy score if matching is fuzzy
|
||||
if (this.matchType == 'fuzzy') this.fuzzyScore(option);
|
||||
|
@ -399,7 +419,7 @@ export class AutoComplete {
|
|||
,
|
||||
);
|
||||
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
|
||||
this.isReplaceable = false;
|
||||
this.isShowingDetails = false;
|
||||
|
@ -443,7 +463,7 @@ export class AutoComplete {
|
|||
}
|
||||
this.dom.append(frag);
|
||||
this.updatePosition();
|
||||
getTopmostModalLayer().append(this.domWrap);
|
||||
this.getLayer().append(this.domWrap);
|
||||
} else {
|
||||
this.domWrap.remove();
|
||||
}
|
||||
|
@ -458,10 +478,17 @@ export class AutoComplete {
|
|||
if (!this.isShowingDetails && this.isReplaceable) return this.detailsWrap.remove();
|
||||
this.detailsDom.innerHTML = '';
|
||||
this.detailsDom.append(this.selectedItem?.renderDetails() ?? 'NO ITEM');
|
||||
getTopmostModalLayer().append(this.detailsWrap);
|
||||
this.getLayer().append(this.detailsWrap);
|
||||
this.updateDetailsPositionDebounced();
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {HTMLElement} closest ancestor dialog or body
|
||||
*/
|
||||
getLayer() {
|
||||
return this.textarea.closest('dialog, body');
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
|
@ -474,7 +501,7 @@ export class AutoComplete {
|
|||
const rect = {};
|
||||
rect[AUTOCOMPLETE_WIDTH.INPUT] = this.textarea.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.dom.style.setProperty('--bottom', `${window.innerHeight - rect[AUTOCOMPLETE_WIDTH.INPUT].top}px`);
|
||||
this.domWrap.style.bottom = `${window.innerHeight - rect[AUTOCOMPLETE_WIDTH.INPUT].top}px`;
|
||||
|
@ -501,7 +528,7 @@ export class AutoComplete {
|
|||
const rect = {};
|
||||
rect[AUTOCOMPLETE_WIDTH.INPUT] = this.textarea.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) {
|
||||
this.detailsWrap.classList.remove('full');
|
||||
const selRect = this.selectedItem.dom.children[0].getBoundingClientRect();
|
||||
|
@ -527,32 +554,34 @@ export class AutoComplete {
|
|||
updateFloatingPosition() {
|
||||
const location = this.getCursorPosition();
|
||||
const rect = this.textarea.getBoundingClientRect();
|
||||
const layerRect = this.getLayer().getBoundingClientRect();
|
||||
// cursor is out of view -> hide
|
||||
if (location.bottom < rect.top || location.top > rect.bottom || location.left < rect.left || location.left > rect.right) {
|
||||
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}`);
|
||||
if (location.top <= window.innerHeight / 2) {
|
||||
// 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.maxHeight = `calc(${location.bottom}px - 1vh)`;
|
||||
this.domWrap.style.maxHeight = `calc(${location.bottom - layerRect.top}px - ${this.textarea.closest('dialog') ? '0' : '1vh'})`;
|
||||
} else {
|
||||
// if cursor is in upper half of window, show list below line
|
||||
this.domWrap.style.top = 'auto';
|
||||
this.domWrap.style.bottom = `calc(100vh - ${location.top}px)`;
|
||||
this.domWrap.style.maxHeight = `calc(${location.top}px - 1vh)`;
|
||||
this.domWrap.style.bottom = `calc(${layerRect.height}px - ${location.top - layerRect.top}px)`;
|
||||
this.domWrap.style.maxHeight = `calc(${location.top - layerRect.top}px - ${this.textarea.closest('dialog') ? '0' : '1vh'})`;
|
||||
}
|
||||
}
|
||||
|
||||
updateFloatingDetailsPosition(location = null) {
|
||||
if (!location) location = this.getCursorPosition();
|
||||
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) {
|
||||
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}`);
|
||||
if (this.isReplaceable) {
|
||||
this.detailsWrap.classList.remove('full');
|
||||
|
@ -572,14 +601,14 @@ export class AutoComplete {
|
|||
}
|
||||
if (location.top <= window.innerHeight / 2) {
|
||||
// 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.maxHeight = `calc(${location.bottom}px - 1vh)`;
|
||||
this.detailsWrap.style.maxHeight = `calc(${location.bottom - layerRect.top}px - ${this.textarea.closest('dialog') ? '0' : '1vh'})`;
|
||||
} else {
|
||||
// if cursor is in upper half of window, show list below line
|
||||
this.detailsWrap.style.top = 'auto';
|
||||
this.detailsWrap.style.bottom = `calc(100vh - ${location.top}px)`;
|
||||
this.detailsWrap.style.maxHeight = `calc(${location.top}px - 1vh)`;
|
||||
this.detailsWrap.style.bottom = `calc(${layerRect.height}px - ${location.top - layerRect.top}px)`;
|
||||
this.detailsWrap.style.maxHeight = `calc(${location.top - layerRect.top}px - ${this.textarea.closest('dialog') ? '0' : '1vh'})`;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -597,7 +626,7 @@ export class AutoComplete {
|
|||
}
|
||||
this.clone.style.position = 'fixed';
|
||||
this.clone.style.visibility = 'hidden';
|
||||
getTopmostModalLayer().append(this.clone);
|
||||
document.body.append(this.clone);
|
||||
const mo = new MutationObserver(muts=>{
|
||||
if (muts.find(it=>Array.from(it.removedNodes).includes(this.textarea))) {
|
||||
this.clone.remove();
|
||||
|
@ -656,6 +685,7 @@ export class AutoComplete {
|
|||
}
|
||||
this.wasForced = false;
|
||||
this.textarea.dispatchEvent(new Event('input', { bubbles:true }));
|
||||
this.onSelect?.(this.selectedItem);
|
||||
}
|
||||
|
||||
|
||||
|
@ -708,8 +738,10 @@ export class AutoComplete {
|
|||
}
|
||||
case 'Enter': {
|
||||
// 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 (this.selectedItem.name == this.name) break;
|
||||
if (!this.selectedItem.isSelectable) break;
|
||||
evt.preventDefault();
|
||||
evt.stopImmediatePropagation();
|
||||
this.select();
|
||||
|
@ -717,9 +749,11 @@ export class AutoComplete {
|
|||
}
|
||||
case 'Tab': {
|
||||
// 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;
|
||||
evt.preventDefault();
|
||||
evt.stopImmediatePropagation();
|
||||
if (!this.selectedItem.isSelectable) break;
|
||||
this.select();
|
||||
return;
|
||||
}
|
||||
|
@ -772,30 +806,16 @@ export class AutoComplete {
|
|||
// ignore keydown on modifier keys
|
||||
return;
|
||||
}
|
||||
switch (evt.key) {
|
||||
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;
|
||||
await new Promise(resolve=>{
|
||||
window.addEventListener('keyup', resolve, { once:true });
|
||||
});
|
||||
if (this.selectionStart != this.textarea.selectionStart) {
|
||||
this.selectionStart = this.textarea.selectionStart;
|
||||
this.show(this.isReplaceable || oldText != this.textarea.value);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
if (this.isActive) {
|
||||
this.text != this.textarea.value && this.show(this.isReplaceable);
|
||||
}
|
||||
break;
|
||||
}
|
||||
// await keyup to see if cursor position or text has changed
|
||||
const oldText = this.textarea.value;
|
||||
await new Promise(resolve=>{
|
||||
window.addEventListener('keyup', resolve, { once:true });
|
||||
});
|
||||
if (this.selectionStart != this.textarea.selectionStart) {
|
||||
this.selectionStart = this.textarea.selectionStart;
|
||||
this.show(this.isReplaceable || oldText != this.textarea.value);
|
||||
} else if (this.isActive) {
|
||||
this.text != this.textarea.value && this.show(this.isReplaceable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,36 +1,9 @@
|
|||
import { SlashCommandNamedArgumentAutoCompleteOption } from '../slash-commands/SlashCommandNamedArgumentAutoCompleteOption.js';
|
||||
import { AutoCompleteOption } from './AutoCompleteOption.js';
|
||||
// import { AutoCompleteSecondaryNameResult } from './AutoCompleteSecondaryNameResult.js';
|
||||
import { AutoCompleteNameResultBase } from './AutoCompleteNameResultBase.js';
|
||||
import { AutoCompleteSecondaryNameResult } from './AutoCompleteSecondaryNameResult.js';
|
||||
|
||||
|
||||
|
||||
export class AutoCompleteNameResult {
|
||||
/**@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;
|
||||
}
|
||||
|
||||
|
||||
export class AutoCompleteNameResult extends AutoCompleteNameResultBase {
|
||||
/**
|
||||
*
|
||||
* @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 {string}*/ replacer;
|
||||
/**@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;
|
||||
}
|
||||
|
||||
get isSelectable() {
|
||||
return this.makeSelectable || !this.valueProvider;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
*/
|
||||
constructor(name, typeIcon = ' ', type = '') {
|
||||
constructor(name, typeIcon = ' ', type = '', matchProvider = null, valueProvider = null, makeSelectable = false) {
|
||||
this.name = name;
|
||||
this.typeIcon = typeIcon;
|
||||
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}*/ 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.
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
getSetByName(name) {
|
||||
|
@ -36,13 +44,14 @@ export class QuickReplyApi {
|
|||
/**
|
||||
* 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} label label of the quick reply
|
||||
* @param {string} setName name of the quick reply set
|
||||
* @param {string|number} label label or numeric ID of the quick reply
|
||||
* @returns the quick reply, or undefined if not found
|
||||
*/
|
||||
getQrByLabel(setName, label) {
|
||||
const set = this.getSetByName(setName);
|
||||
if (!set) return;
|
||||
if (Number.isInteger(label)) return set.qrList.find(it=>it.id == label);
|
||||
return set.qrList.find(it=>it.label == label);
|
||||
}
|
||||
|
||||
|
@ -70,24 +79,25 @@ export class QuickReplyApi {
|
|||
/**
|
||||
* Executes an existing quick reply.
|
||||
*
|
||||
* @param {String} setName name of the existing quick reply set
|
||||
* @param {String} label label of the existing quick reply (text on the button)
|
||||
* @param {Object} [args] optional arguments
|
||||
* @param {string} setName name of the existing quick reply set
|
||||
* @param {string|number} label label of the existing quick reply (text on the button) or its numeric ID
|
||||
* @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);
|
||||
if (!qr) {
|
||||
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.
|
||||
*
|
||||
* @param {String} name the name of the set
|
||||
* @param {Boolean} isVisible whether to show the set's buttons or not
|
||||
* @param {string} name the name of the set
|
||||
* @param {boolean} isVisible whether to show the set's buttons or not
|
||||
*/
|
||||
toggleGlobalSet(name, isVisible = true) {
|
||||
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.
|
||||
*
|
||||
* @param {String} name the name of the set
|
||||
* @param {Boolean} isVisible whether to show the set's buttons or not
|
||||
* @param {string} name the name of the set
|
||||
* @param {boolean} isVisible whether to show the set's buttons or not
|
||||
*/
|
||||
addGlobalSet(name, isVisible = true) {
|
||||
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.
|
||||
*
|
||||
* @param {String} name the name of the set
|
||||
* @param {string} name the name of the set
|
||||
*/
|
||||
removeGlobalSet(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.
|
||||
*
|
||||
* @param {String} name the name of the set
|
||||
* @param {Boolean} isVisible whether to show the set's buttons or not
|
||||
* @param {string} name the name of the set
|
||||
* @param {boolean} isVisible whether to show the set's buttons or not
|
||||
*/
|
||||
toggleChatSet(name, isVisible = true) {
|
||||
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.
|
||||
*
|
||||
* @param {String} name the name of the set
|
||||
* @param {Boolean} isVisible whether to show the set's buttons or not
|
||||
* @param {string} name the name of the set
|
||||
* @param {boolean} isVisible whether to show the set's buttons or not
|
||||
*/
|
||||
addChatSet(name, isVisible = true) {
|
||||
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.
|
||||
*
|
||||
* @param {String} name the name of the set
|
||||
* @param {string} name the name of the set
|
||||
*/
|
||||
removeChatSet(name) {
|
||||
if (!this.settings.chatConfig) return;
|
||||
|
@ -181,21 +191,25 @@ export class QuickReplyApi {
|
|||
/**
|
||||
* 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} label label for the new quick reply (text on the button)
|
||||
* @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.title] the title / tooltip to be shown on the quick reply button
|
||||
* @param {Boolean} [props.isHidden] whether to hide or show the button
|
||||
* @param {Boolean} [props.executeOnStartup] whether to execute the quick reply when SillyTavern starts
|
||||
* @param {Boolean} [props.executeOnUser] whether to execute the quick reply after a user has sent a message
|
||||
* @param {Boolean} [props.executeOnAi] whether to execute the quick reply after the AI has sent a message
|
||||
* @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
|
||||
* @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 {object} [props]
|
||||
* @param {string} [props.icon] the icon to show on the QR button
|
||||
* @param {boolean} [props.showLabel] whether to show the label even when an icon is assigned
|
||||
* @param {string} [props.message] the message to be sent or slash command to be executed by the new quick reply
|
||||
* @param {string} [props.title] the title / tooltip to be shown on the quick reply button
|
||||
* @param {boolean} [props.isHidden] whether to hide or show the button
|
||||
* @param {boolean} [props.executeOnStartup] whether to execute the quick reply when SillyTavern starts
|
||||
* @param {boolean} [props.executeOnUser] whether to execute the quick reply after a user has sent a message
|
||||
* @param {boolean} [props.executeOnAi] whether to execute the quick reply after the AI has sent a message
|
||||
* @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
|
||||
*/
|
||||
createQuickReply(setName, label, {
|
||||
icon,
|
||||
showLabel,
|
||||
message,
|
||||
title,
|
||||
isHidden,
|
||||
|
@ -212,6 +226,8 @@ export class QuickReplyApi {
|
|||
}
|
||||
const qr = set.addQuickReply();
|
||||
qr.label = label ?? '';
|
||||
qr.icon = icon ?? '';
|
||||
qr.showLabel = showLabel ?? false;
|
||||
qr.message = message ?? '';
|
||||
qr.title = title ?? '';
|
||||
qr.isHidden = isHidden ?? false;
|
||||
|
@ -228,22 +244,26 @@ export class QuickReplyApi {
|
|||
/**
|
||||
* Updates an existing quick reply.
|
||||
*
|
||||
* @param {String} setName name of the existing quick reply set
|
||||
* @param {String} label label of the existing quick reply (text on the button)
|
||||
* @param {Object} [props]
|
||||
* @param {String} [props.newLabel] new label for quick reply (text on the button)
|
||||
* @param {String} [props.message] the message to be sent or slash command to be executed by the quick reply
|
||||
* @param {String} [props.title] the title / tooltip to be shown on the quick reply button
|
||||
* @param {Boolean} [props.isHidden] whether to hide or show the button
|
||||
* @param {Boolean} [props.executeOnStartup] whether to execute the quick reply when SillyTavern starts
|
||||
* @param {Boolean} [props.executeOnUser] whether to execute the quick reply after a user has sent a message
|
||||
* @param {Boolean} [props.executeOnAi] whether to execute the quick reply after the AI has sent a message
|
||||
* @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
|
||||
* @param {string} setName name of the existing quick reply set
|
||||
* @param {string|number} label label of the existing quick reply (text on the button) or its numeric ID
|
||||
* @param {object} [props]
|
||||
* @param {string} [props.icon] the icon to show on the QR button
|
||||
* @param {boolean} [props.showLabel] whether to show the label even when an icon is assigned
|
||||
* @param {string} [props.newLabel] new label for quick reply (text on the button)
|
||||
* @param {string} [props.message] the message to be sent or slash command to be executed by the quick reply
|
||||
* @param {string} [props.title] the title / tooltip to be shown on the quick reply button
|
||||
* @param {boolean} [props.isHidden] whether to hide or show the button
|
||||
* @param {boolean} [props.executeOnStartup] whether to execute the quick reply when SillyTavern starts
|
||||
* @param {boolean} [props.executeOnUser] whether to execute the quick reply after a user has sent a message
|
||||
* @param {boolean} [props.executeOnAi] whether to execute the quick reply after the AI has sent a message
|
||||
* @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
|
||||
*/
|
||||
updateQuickReply(setName, label, {
|
||||
icon,
|
||||
showLabel,
|
||||
newLabel,
|
||||
message,
|
||||
title,
|
||||
|
@ -259,6 +279,8 @@ export class QuickReplyApi {
|
|||
if (!qr) {
|
||||
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.updateMessage(message ?? qr.message);
|
||||
qr.updateTitle(title ?? qr.title);
|
||||
|
@ -276,8 +298,8 @@ export class QuickReplyApi {
|
|||
/**
|
||||
* Deletes an existing quick reply.
|
||||
*
|
||||
* @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} setName name of the existing quick reply set
|
||||
* @param {string|number} label label of the existing quick reply (text on the button) or its numeric ID
|
||||
*/
|
||||
deleteQuickReply(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.
|
||||
*
|
||||
* @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} 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 {string} setName name of the existing quick reply set containing the 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 {boolean} isChained whether or not to chain the context menu quick replies
|
||||
*/
|
||||
createContextItem(setName, label, contextSetName, isChained = false) {
|
||||
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.
|
||||
*
|
||||
* @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} contextSetName name of the existing quick reply set to be used as a context menu
|
||||
* @param {string} setName name of the existing quick reply set containing the 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
|
||||
*/
|
||||
deleteContextItem(setName, label, contextSetName) {
|
||||
const qr = this.getQrByLabel(setName, label);
|
||||
|
@ -333,8 +355,8 @@ export class QuickReplyApi {
|
|||
/**
|
||||
* 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} label label of the existing quick reply
|
||||
* @param {string} setName name of the existing quick reply set containing the quick reply
|
||||
* @param {string|number} label label of the existing quick reply or its numeric ID
|
||||
*/
|
||||
clearContextMenu(setName, label) {
|
||||
const qr = this.getQrByLabel(setName, label);
|
||||
|
@ -348,11 +370,11 @@ export class QuickReplyApi {
|
|||
/**
|
||||
* Create a new quick reply set.
|
||||
*
|
||||
* @param {String} name name of the new quick reply set
|
||||
* @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.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 {string} name name of the new quick reply set
|
||||
* @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.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
|
||||
* @returns {Promise<QuickReplySet>} the new quick reply set
|
||||
*/
|
||||
async createSet(name, {
|
||||
|
@ -384,11 +406,11 @@ export class QuickReplyApi {
|
|||
/**
|
||||
* Update an existing quick reply set.
|
||||
*
|
||||
* @param {String} name name of the existing quick reply set
|
||||
* @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.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 {string} name name of the existing quick reply set
|
||||
* @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.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
|
||||
* @returns {Promise<QuickReplySet>} the altered quick reply set
|
||||
*/
|
||||
async updateSet(name, {
|
||||
|
@ -411,7 +433,7 @@ export class QuickReplyApi {
|
|||
/**
|
||||
* 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) {
|
||||
const set = this.getSetByName(name);
|
||||
|
@ -451,7 +473,7 @@ export class QuickReplyApi {
|
|||
/**
|
||||
* 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
|
||||
*/
|
||||
listQuickReplies(setName) {
|
||||
|
|
|
@ -2,10 +2,23 @@
|
|||
<div id="qr--main">
|
||||
<h3 data-i18n="Labels and Message">Labels and Message</h3>
|
||||
<div class="qr--labels">
|
||||
<label>
|
||||
<span class="qr--labelText" data-i18n="Label">Label</span>
|
||||
<input type="text" class="text_pole" id="qr--modal-label">
|
||||
<label class="qr--fit">
|
||||
<span class="qr--labelText" data-i18n="Label">Icon</span>
|
||||
<small class="qr--labelHint"> </small>
|
||||
<div class="menu_button fa-fw" id="qr--modal-icon" title="Click to change icon"></div>
|
||||
</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>
|
||||
<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>
|
||||
|
@ -33,6 +46,8 @@
|
|||
<input type="checkbox" id="qr--modal-syntax">
|
||||
<span>Syntax highlight</span>
|
||||
</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 id="qr--modal-messageHolder">
|
||||
<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">
|
||||
<h3 data-i18n="Context Menu">Context Menu</h3>
|
||||
<div id="qr--ctxEditor">
|
||||
|
@ -117,11 +136,18 @@
|
|||
</div>
|
||||
</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-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" title="Chat input"></textarea>
|
||||
<div id="qr--modal-debugState"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -60,10 +60,20 @@
|
|||
<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>
|
||||
</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 id="qr--set-qrList" class="qr--qrList"></div>
|
||||
<div class="qr--set-qrListActions">
|
||||
<div class="qr--add menu_button menu_button_icon fa-solid fa-plus" id="qr--set-add" title="Add quick reply"></div>
|
||||
<div 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>
|
||||
|
|
|
@ -176,7 +176,7 @@ const init = async () => {
|
|||
buttons.show();
|
||||
settings.onSave = ()=>buttons.refresh();
|
||||
|
||||
window['executeQuickReplyByName'] = async(name, args = {}) => {
|
||||
window['executeQuickReplyByName'] = async(name, args = {}, options = {}) => {
|
||||
let qr = [...settings.config.setList, ...(settings.chatConfig?.setList ?? [])]
|
||||
.map(it=>it.set.qrList)
|
||||
.flat()
|
||||
|
@ -191,7 +191,7 @@ const init = async () => {
|
|||
}
|
||||
}
|
||||
if (qr && qr.onExecute) {
|
||||
return await qr.execute(args, false, true);
|
||||
return await qr.execute(args, false, true, options);
|
||||
} else {
|
||||
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}*/
|
||||
this.setListDom = root.querySelector('.qr--setList');
|
||||
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();
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import { getRequestHeaders, substituteParams } from '../../../../script.js';
|
||||
import { Popup, POPUP_RESULT, POPUP_TYPE } from '../../../popup.js';
|
||||
import { executeSlashCommands, executeSlashCommandsOnChatInput, executeSlashCommandsWithOptions } from '../../../slash-commands.js';
|
||||
import { SlashCommandParser } from '../../../slash-commands/SlashCommandParser.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';
|
||||
|
||||
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) {
|
||||
return this.list.find(it=>it.name == name);
|
||||
|
@ -25,17 +27,19 @@ export class QuickReplySet {
|
|||
|
||||
|
||||
|
||||
/**@type {String}*/ name;
|
||||
/**@type {Boolean}*/ disableSend = false;
|
||||
/**@type {Boolean}*/ placeBeforeInput = false;
|
||||
/**@type {Boolean}*/ injectInput = false;
|
||||
/**@type {string}*/ name;
|
||||
/**@type {boolean}*/ disableSend = false;
|
||||
/**@type {boolean}*/ placeBeforeInput = false;
|
||||
/**@type {boolean}*/ injectInput = false;
|
||||
/**@type {string}*/ color = 'transparent';
|
||||
/**@type {boolean}*/ onlyBorderColor = false;
|
||||
/**@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}*/ settingsDom;
|
||||
|
@ -64,6 +68,7 @@ export class QuickReplySet {
|
|||
const root = document.createElement('div'); {
|
||||
this.dom = root;
|
||||
root.classList.add('qr--buttons');
|
||||
this.updateColor();
|
||||
this.qrList.filter(qr=>!qr.isHidden).forEach(qr=>{
|
||||
root.append(qr.render());
|
||||
});
|
||||
|
@ -78,6 +83,22 @@ export class QuickReplySet {
|
|||
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;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param {QuickReply} qr
|
||||
* @param {number} idx
|
||||
*/
|
||||
renderSettingsItem(qr, 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.
|
||||
|
@ -109,6 +147,7 @@ export class QuickReplySet {
|
|||
* @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 {SlashCommandScope} [options.scope] (null) scope to be used when running the command
|
||||
* @param {import('../../../slash-commands.js').ExecuteSlashCommandsOptions} [options.executionOptions] ({}) further execution options
|
||||
* @returns
|
||||
*/
|
||||
async executeWithOptions(qr, options = {}) {
|
||||
|
@ -118,7 +157,9 @@ export class QuickReplySet {
|
|||
isEditor:false,
|
||||
isRun:false,
|
||||
scope:null,
|
||||
executionOptions:{},
|
||||
}, options);
|
||||
const execOptions = options.executionOptions;
|
||||
/**@type {HTMLTextAreaElement}*/
|
||||
const ta = document.querySelector('#send_textarea');
|
||||
const finalMessage = options.message ?? qr.message;
|
||||
|
@ -136,21 +177,24 @@ export class QuickReplySet {
|
|||
if (input[0] == '/' && !this.disableSend) {
|
||||
let result;
|
||||
if (options.isAutoExecute || options.isRun) {
|
||||
result = await executeSlashCommandsWithOptions(input, {
|
||||
result = await executeSlashCommandsWithOptions(input, Object.assign(execOptions, {
|
||||
handleParserErrors: true,
|
||||
scope: options.scope,
|
||||
});
|
||||
source: `${this.name}.${qr.label}`,
|
||||
}));
|
||||
} else if (options.isEditor) {
|
||||
result = await executeSlashCommandsWithOptions(input, {
|
||||
result = await executeSlashCommandsWithOptions(input, Object.assign(execOptions, {
|
||||
handleParserErrors: false,
|
||||
scope: options.scope,
|
||||
abortController: qr.abortController,
|
||||
source: `${this.name}.${qr.label}`,
|
||||
onProgress: (done, total) => qr.updateEditorProgress(done, total),
|
||||
});
|
||||
}));
|
||||
} else {
|
||||
result = await executeSlashCommandsOnChatInput(input, {
|
||||
result = await executeSlashCommandsOnChatInput(input, Object.assign(execOptions, {
|
||||
scope: options.scope,
|
||||
});
|
||||
source: `${this.name}.${qr.label}`,
|
||||
}));
|
||||
}
|
||||
return typeof result === 'object' ? result?.pipe : '';
|
||||
}
|
||||
|
@ -165,7 +209,7 @@ export class QuickReplySet {
|
|||
}
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
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;
|
||||
data.id =
|
||||
this.idIndex = id + 1;
|
||||
const qr = QuickReply.from({ id });
|
||||
const qr = QuickReply.from(data);
|
||||
this.qrList.push(qr);
|
||||
this.hookQuickReply(qr);
|
||||
if (this.settingsDom) {
|
||||
|
@ -194,11 +239,131 @@ export class QuickReplySet {
|
|||
this.save();
|
||||
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) {
|
||||
qr.onDebug = ()=>this.debug(qr);
|
||||
qr.onExecute = (_, options)=>this.executeWithOptions(qr, options);
|
||||
qr.onDelete = ()=>this.removeQuickReply(qr);
|
||||
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) {
|
||||
|
@ -214,6 +379,8 @@ export class QuickReplySet {
|
|||
disableSend: this.disableSend,
|
||||
placeBeforeInput: this.placeBeforeInput,
|
||||
injectInput: this.injectInput,
|
||||
color: this.color,
|
||||
onlyBorderColor: this.onlyBorderColor,
|
||||
qrList: this.qrList,
|
||||
idIndex: this.idIndex,
|
||||
};
|
||||
|
@ -245,8 +412,12 @@ export class QuickReplySet {
|
|||
if (response.ok) {
|
||||
this.unrender();
|
||||
const idx = QuickReplySet.list.indexOf(this);
|
||||
QuickReplySet.list.splice(idx, 1);
|
||||
this.isDeleted = true;
|
||||
if (idx > -1) {
|
||||
QuickReplySet.list.splice(idx, 1);
|
||||
this.isDeleted = true;
|
||||
} else {
|
||||
warn(`Deleted Quick Reply Set was not found in the list of sets: ${this.name}`);
|
||||
}
|
||||
} else {
|
||||
warn(`Failed to delete Quick Reply Set: ${this.name}`);
|
||||
}
|
||||
|
|
|
@ -45,7 +45,7 @@ export class QuickReplySetLink {
|
|||
this.set = QuickReplySet.get(set.value);
|
||||
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'); {
|
||||
opt.value = qrs.name;
|
||||
opt.textContent = qrs.name;
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
import { SlashCommand } from '../../../slash-commands/SlashCommand.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 { SlashCommandDebugController } from '../../../slash-commands/SlashCommandDebugController.js';
|
||||
import { SlashCommandEnumValue, enumTypes } from '../../../slash-commands/SlashCommandEnumValue.js';
|
||||
import { SlashCommandParser } from '../../../slash-commands/SlashCommandParser.js';
|
||||
import { SlashCommandScope } from '../../../slash-commands/SlashCommandScope.js';
|
||||
import { isTrueBoolean } from '../../../utils.js';
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import { QuickReplyApi } from '../api/QuickReplyApi.js';
|
||||
|
@ -47,6 +50,13 @@ export class SlashCommandHandler {
|
|||
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 */
|
||||
qrExecutables: () => {
|
||||
const globalSetList = this.api.settings.config.setList;
|
||||
|
@ -63,7 +73,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)),
|
||||
];
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
window['qrEnumProviderExecutables'] = localEnumProviders.qrExecutables;
|
||||
|
||||
|
@ -234,8 +244,20 @@ export class SlashCommandHandler {
|
|||
name: 'label',
|
||||
description: 'text on the button, e.g., label=MyButton',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: localEnumProviders.qrLabels,
|
||||
isRequired: false,
|
||||
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('startup', 'auto execute on app startup, e.g., startup=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'),
|
||||
|
@ -247,6 +269,13 @@ export class SlashCommandHandler {
|
|||
];
|
||||
const qrUpdateArgs = [
|
||||
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',
|
||||
|
@ -272,13 +301,61 @@ export class SlashCommandHandler {
|
|||
</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',
|
||||
callback: (args, message) => {
|
||||
this.updateQuickReply(args, message);
|
||||
return '';
|
||||
},
|
||||
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: [
|
||||
new SlashCommandArgument('command', [ARGUMENT_TYPE.STRING]),
|
||||
],
|
||||
|
@ -315,6 +392,12 @@ export class SlashCommandHandler {
|
|||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
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: [
|
||||
SlashCommandArgument.fromProps({
|
||||
|
@ -344,6 +427,12 @@ export class SlashCommandHandler {
|
|||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
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(
|
||||
'chain', 'boolean', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false',
|
||||
),
|
||||
|
@ -389,6 +478,12 @@ export class SlashCommandHandler {
|
|||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
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: [
|
||||
SlashCommandArgument.fromProps({
|
||||
|
@ -425,6 +520,12 @@ export class SlashCommandHandler {
|
|||
isRequired: true,
|
||||
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: [
|
||||
SlashCommandArgument.fromProps({
|
||||
|
@ -454,8 +555,8 @@ export class SlashCommandHandler {
|
|||
new SlashCommandNamedArgument('inject', 'inject user input automatically (if disabled use {{input}})', [ARGUMENT_TYPE.BOOLEAN], false),
|
||||
];
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set-create',
|
||||
callback: (args, name) => {
|
||||
this.createSet(name, args);
|
||||
callback: async (args, name) => {
|
||||
await this.createSet(name, args);
|
||||
return '';
|
||||
},
|
||||
aliases: ['qr-presetadd'],
|
||||
|
@ -485,8 +586,8 @@ export class SlashCommandHandler {
|
|||
}));
|
||||
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set-update',
|
||||
callback: (args, name) => {
|
||||
this.updateSet(name, args);
|
||||
callback: async (args, name) => {
|
||||
await this.updateSet(name, args);
|
||||
return '';
|
||||
},
|
||||
aliases: ['qr-presetupdate'],
|
||||
|
@ -510,8 +611,8 @@ export class SlashCommandHandler {
|
|||
`,
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set-delete',
|
||||
callback: (_, name) => {
|
||||
this.deleteSet(name);
|
||||
callback: async (_, name) => {
|
||||
await this.deleteSet(name);
|
||||
return '';
|
||||
},
|
||||
aliases: ['qr-presetdelete'],
|
||||
|
@ -533,6 +634,134 @@ export class SlashCommandHandler {
|
|||
</div>
|
||||
`,
|
||||
}));
|
||||
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-arg',
|
||||
callback: ({ _scope }, [key, value]) => {
|
||||
_scope.setMacro(`arg::${key}`, value, false);
|
||||
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, _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, [], null, 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 +847,8 @@ export class SlashCommandHandler {
|
|||
args.set ?? '',
|
||||
args.label ?? '',
|
||||
{
|
||||
icon: args.icon,
|
||||
showLabel: args.showlabel === undefined ? undefined : isTrueBoolean(args.showlabel),
|
||||
message: message ?? '',
|
||||
title: args.title,
|
||||
isHidden: isTrueBoolean(args.hidden),
|
||||
|
@ -633,12 +864,21 @@ export class SlashCommandHandler {
|
|||
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) {
|
||||
try {
|
||||
this.api.updateQuickReply(
|
||||
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,
|
||||
message: (message ?? '').trim().length > 0 ? message : undefined,
|
||||
title: args.title,
|
||||
|
@ -657,7 +897,7 @@ export class SlashCommandHandler {
|
|||
}
|
||||
deleteQuickReply(args, label) {
|
||||
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) {
|
||||
toastr.error(ex.message);
|
||||
}
|
||||
|
@ -692,9 +932,9 @@ export class SlashCommandHandler {
|
|||
}
|
||||
|
||||
|
||||
createSet(name, args) {
|
||||
async createSet(name, args) {
|
||||
try {
|
||||
this.api.createSet(
|
||||
await this.api.createSet(
|
||||
args.name ?? name ?? '',
|
||||
{
|
||||
disableSend: isTrueBoolean(args.nosend),
|
||||
|
@ -706,9 +946,9 @@ export class SlashCommandHandler {
|
|||
toastr.error(ex.message);
|
||||
}
|
||||
}
|
||||
updateSet(name, args) {
|
||||
async updateSet(name, args) {
|
||||
try {
|
||||
this.api.updateSet(
|
||||
await this.api.updateSet(
|
||||
args.name ?? name ?? '',
|
||||
{
|
||||
disableSend: args.nosend !== undefined ? isTrueBoolean(args.nosend) : undefined,
|
||||
|
@ -720,9 +960,9 @@ export class SlashCommandHandler {
|
|||
toastr.error(ex.message);
|
||||
}
|
||||
}
|
||||
deleteSet(name) {
|
||||
async deleteSet(name) {
|
||||
try {
|
||||
this.api.deleteSet(name ?? '');
|
||||
await this.api.deleteSet(name ?? '');
|
||||
} catch (ex) {
|
||||
toastr.error(ex.message);
|
||||
}
|
||||
|
|
|
@ -23,6 +23,8 @@ export class SettingsUi {
|
|||
/**@type {HTMLInputElement}*/ disableSend;
|
||||
/**@type {HTMLInputElement}*/ placeBeforeInput;
|
||||
/**@type {HTMLInputElement}*/ injectInput;
|
||||
/**@type {HTMLInputElement}*/ color;
|
||||
/**@type {HTMLInputElement}*/ onlyBorderColor;
|
||||
/**@type {HTMLSelectElement}*/ currentSet;
|
||||
|
||||
|
||||
|
@ -117,10 +119,29 @@ export class SettingsUi {
|
|||
this.dom.querySelector('#qr--set-add').addEventListener('click', async()=>{
|
||||
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.currentSet = this.dom.querySelector('#qr--set');
|
||||
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'); {
|
||||
opt.value = qrs.name;
|
||||
opt.textContent = qrs.name;
|
||||
|
@ -145,6 +166,34 @@ export class SettingsUi {
|
|||
qrs.injectInput = this.injectInput.checked;
|
||||
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();
|
||||
}
|
||||
onQrSetChange() {
|
||||
|
@ -152,6 +201,8 @@ export class SettingsUi {
|
|||
this.disableSend.checked = this.currentQrSet.disableSend;
|
||||
this.placeBeforeInput.checked = this.currentQrSet.placeBeforeInput;
|
||||
this.injectInput.checked = this.currentQrSet.injectInput;
|
||||
this.color.color = this.currentQrSet.color ?? 'transparent';
|
||||
this.onlyBorderColor.checked = this.currentQrSet.onlyBorderColor;
|
||||
this.qrList.innerHTML = '';
|
||||
const qrsDom = this.currentQrSet.renderSettings();
|
||||
this.qrList.append(qrsDom);
|
||||
|
@ -265,7 +316,7 @@ export class SettingsUi {
|
|||
const qrs = new QuickReplySet();
|
||||
qrs.name = name;
|
||||
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) {
|
||||
QuickReplySet.list.splice(idx, 0, qrs);
|
||||
} else {
|
||||
|
@ -321,7 +372,7 @@ export class SettingsUi {
|
|||
this.prepareChatSetList();
|
||||
}
|
||||
} 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) {
|
||||
QuickReplySet.list.splice(idx, 0, qrs);
|
||||
} else {
|
||||
|
|
|
@ -33,6 +33,8 @@ export class ContextMenu {
|
|||
*/
|
||||
build(qr, chainedMessage = null, hierarchy = [], labelHierarchy = []) {
|
||||
const tree = {
|
||||
icon: qr.icon,
|
||||
showLabel: qr.showLabel,
|
||||
label: qr.label,
|
||||
message: (chainedMessage && qr.message ? `${chainedMessage} | ` : '') + qr.message,
|
||||
children: [],
|
||||
|
@ -45,6 +47,8 @@ export class ContextMenu {
|
|||
cl.set.qrList.forEach(subQr => {
|
||||
const subTree = this.build(subQr, cl.isChained ? tree.message : null, nextHierarchy, nextLabelHierarchy);
|
||||
tree.children.push(new MenuItem(
|
||||
subTree.icon,
|
||||
subTree.showLabel,
|
||||
subTree.label,
|
||||
subTree.message,
|
||||
(evt) => {
|
||||
|
|
|
@ -2,7 +2,7 @@ import { MenuItem } from './MenuItem.js';
|
|||
|
||||
export class MenuHeader extends MenuItem {
|
||||
constructor(/**@type {String}*/label) {
|
||||
super(label, null, null);
|
||||
super(null, null, label, null, null);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,21 +1,34 @@
|
|||
import { SubMenu } from './SubMenu.js';
|
||||
|
||||
export class MenuItem {
|
||||
/**@type {String}*/ label;
|
||||
/**@type {Object}*/ value;
|
||||
/**@type {Function}*/ callback;
|
||||
/**@type {string}*/ icon;
|
||||
/**@type {boolean}*/ showLabel;
|
||||
/**@type {string}*/ label;
|
||||
/**@type {object}*/ value;
|
||||
/**@type {function}*/ callback;
|
||||
/**@type {MenuItem[]}*/ childList = [];
|
||||
/**@type {SubMenu}*/ subMenu;
|
||||
/**@type {Boolean}*/ isForceExpanded = false;
|
||||
/**@type {boolean}*/ isForceExpanded = false;
|
||||
|
||||
/**@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.value = value;
|
||||
this.callback = callback;
|
||||
|
@ -33,7 +46,21 @@ export class MenuItem {
|
|||
if (this.callback) {
|
||||
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) {
|
||||
item.classList.add('ctx-has-children');
|
||||
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 {
|
||||
outline: none;
|
||||
margin: 0;
|
||||
|
@ -41,6 +58,7 @@
|
|||
}
|
||||
#qr--bar > .qr--buttons,
|
||||
#qr--popout > .qr--body > .qr--buttons {
|
||||
--qr--color: transparent;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
|
@ -49,10 +67,44 @@
|
|||
gap: 5px;
|
||||
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--popout > .qr--body > .qr--buttons > .qr--buttons {
|
||||
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--popout > .qr--body > .qr--buttons .qr--button {
|
||||
color: var(--SmartThemeBodyColor);
|
||||
|
@ -66,11 +118,19 @@
|
|||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
}
|
||||
#qr--bar > .qr--buttons .qr--button:hover,
|
||||
#qr--popout > .qr--body > .qr--buttons .qr--button:hover {
|
||||
opacity: 1;
|
||||
filter: brightness(1.2);
|
||||
background-color: #4d4d4d;
|
||||
}
|
||||
#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--popout > .qr--body > .qr--buttons .qr--button > .qr--button-expander {
|
||||
|
@ -170,36 +230,80 @@
|
|||
#qr--settings #qr--set-qrList .qr--set-qrListContents {
|
||||
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;
|
||||
flex-direction: row;
|
||||
gap: 0.5em;
|
||||
align-items: baseline;
|
||||
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;
|
||||
}
|
||||
#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%;
|
||||
}
|
||||
#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;
|
||||
}
|
||||
#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%;
|
||||
}
|
||||
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item > :nth-child(5) {
|
||||
flex: 0 0 auto;
|
||||
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--content > :nth-child(5) {
|
||||
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;
|
||||
}
|
||||
#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--action {
|
||||
#qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--content .qr--set-itemLabelContainer {
|
||||
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;
|
||||
}
|
||||
#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;
|
||||
}
|
||||
#qr--settings .qr--set-qrListActions {
|
||||
|
@ -238,6 +342,72 @@
|
|||
.popup:has(#qr--modalEditor) {
|
||||
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 {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
@ -249,6 +419,47 @@
|
|||
gap: 1em;
|
||||
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;
|
||||
}
|
||||
.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--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 {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
|
@ -260,20 +471,114 @@
|
|||
display: flex;
|
||||
flex-direction: row;
|
||||
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;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
}
|
||||
.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 {
|
||||
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.qr--fit,
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label.qr--fit {
|
||||
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 {
|
||||
flex: 1 1 auto;
|
||||
|
@ -283,8 +588,9 @@
|
|||
}
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > .qr--modal-editorSettings {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex-direction: row;
|
||||
gap: 1em;
|
||||
column-gap: 1em;
|
||||
color: var(--grey70);
|
||||
font-size: smaller;
|
||||
align-items: baseline;
|
||||
|
@ -308,6 +614,11 @@
|
|||
background-color: var(--ac-style-color-background);
|
||||
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 {
|
||||
color: unset;
|
||||
background-color: rgba(108 171 251 / 0.25);
|
||||
|
@ -357,11 +668,15 @@
|
|||
font-family: var(--monoFontFamily);
|
||||
padding: 0.75em;
|
||||
margin: 0;
|
||||
border: none;
|
||||
resize: none;
|
||||
line-height: 1.2;
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
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 {
|
||||
display: flex;
|
||||
|
@ -410,6 +725,39 @@
|
|||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeButtons #qr--modal-stop {
|
||||
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):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-executeProgress {
|
||||
--prog: 0;
|
||||
--progColor: #92befc;
|
||||
|
@ -469,6 +817,7 @@
|
|||
overflow: auto;
|
||||
min-width: 100%;
|
||||
width: 0;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeResult.qr--hasResult {
|
||||
display: block;
|
||||
|
@ -476,6 +825,150 @@
|
|||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeResult:before {
|
||||
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 {
|
||||
0%,
|
||||
100% {
|
||||
|
@ -491,3 +984,48 @@
|
|||
.popup.qr--hide::backdrop {
|
||||
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 {
|
||||
outline: none;
|
||||
margin: 0;
|
||||
|
@ -50,6 +65,18 @@
|
|||
#qr--bar,
|
||||
#qr--popout>.qr--body {
|
||||
>.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;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
|
@ -60,6 +87,25 @@
|
|||
|
||||
>.qr--buttons {
|
||||
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 {
|
||||
|
@ -75,10 +121,17 @@
|
|||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
filter: brightness(1.2);
|
||||
background-color: rgb(30% 30% 30%);
|
||||
}
|
||||
|
||||
.qr--hidden {
|
||||
display: none;
|
||||
}
|
||||
.qr--button-icon {
|
||||
margin: 0 0.5em;
|
||||
}
|
||||
|
||||
>.qr--button-expander {
|
||||
|
@ -211,14 +264,41 @@
|
|||
.qr--set-qrListContents> {
|
||||
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;
|
||||
flex-direction: row;
|
||||
gap: 0.5em;
|
||||
align-items: baseline;
|
||||
padding: 0.25em 0;
|
||||
|
||||
> :nth-child(1) {
|
||||
> :nth-child(2) {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
|
@ -235,13 +315,29 @@
|
|||
}
|
||||
|
||||
> :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 {
|
||||
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--action {
|
||||
margin: 0;
|
||||
|
@ -251,6 +347,8 @@
|
|||
font-size: smaller;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -306,6 +404,78 @@
|
|||
.popup:has(#qr--modalEditor) {
|
||||
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 {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
@ -317,140 +487,242 @@
|
|||
gap: 1em;
|
||||
overflow: hidden;
|
||||
|
||||
>#qr--main {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
|
||||
>.qr--labels {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 0.5em;
|
||||
|
||||
>label {
|
||||
flex: 1 1 1px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
>.qr--labelText {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
>.qr--labelHint {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
>input {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
&.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;
|
||||
}
|
||||
}
|
||||
&.qr--isPaused #qr--modal-debugButtons {
|
||||
.menu_button:not(#qr--modal-minimize, #qr--modal-maximize) {
|
||||
cursor: pointer;
|
||||
opacity: 1;
|
||||
pointer-events: all;
|
||||
}
|
||||
}
|
||||
#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--modal-messageContainer {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
|
||||
>.qr--modal-editorSettings {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 1em;
|
||||
color: var(--grey70);
|
||||
font-size: smaller;
|
||||
align-items: baseline;
|
||||
|
||||
>.checkbox_label {
|
||||
white-space: nowrap;
|
||||
|
||||
>input {
|
||||
font-size: inherit;
|
||||
> #qr--main {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
> .qr--labels {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 0.5em;
|
||||
padding: 1px;
|
||||
> label, > .label {
|
||||
flex: 1 1 1px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
&.qr--fit {
|
||||
flex: 0 0 auto;
|
||||
justify-content: center;
|
||||
}
|
||||
.qr--inputGroup {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 0.5em;
|
||||
input {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
>#qr--modal-messageHolder {
|
||||
flex: 1 1 auto;
|
||||
display: grid;
|
||||
text-align: left;
|
||||
overflow: hidden;
|
||||
|
||||
&.qr--noSyntax {
|
||||
>#qr--modal-messageSyntax {
|
||||
display: none;
|
||||
}
|
||||
|
||||
>#qr--modal-message {
|
||||
background-color: var(--ac-style-color-background);
|
||||
color: var(--ac-style-color-text);
|
||||
|
||||
&::selection {
|
||||
color: unset;
|
||||
background-color: rgba(108 171 251 / 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);
|
||||
.qr--labelText {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
.qr--labelHint {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
input {
|
||||
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--modal-messageSyntax {
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: none;
|
||||
overflow: hidden;
|
||||
min-width: 100%;
|
||||
width: 0;
|
||||
|
||||
>#qr--modal-messageSyntaxInner {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
>#qr--modal-message {
|
||||
background-color: transparent;
|
||||
color: transparent;
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
caret-color: var(--ac-style-color-text);
|
||||
overflow: auto;
|
||||
|
||||
&::-webkit-scrollbar,
|
||||
&::-webkit-scrollbar-thumb {
|
||||
visibility: hidden;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
&::selection {
|
||||
color: transparent;
|
||||
background-color: rgba(108 171 251 / 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);
|
||||
.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-message,
|
||||
#qr--modal-messageSyntaxInner {
|
||||
font-family: var(--monoFontFamily);
|
||||
padding: 0.75em;
|
||||
margin: 0;
|
||||
border: none;
|
||||
resize: none;
|
||||
line-height: 1.2;
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
border-radius: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
> .qr--modal-messageContainer {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
> .qr--modal-editorSettings {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex-direction: row;
|
||||
column-gap: 1em;
|
||||
color: var(--grey70);
|
||||
font-size: smaller;
|
||||
align-items: baseline;
|
||||
> .checkbox_label {
|
||||
white-space: nowrap;
|
||||
> input {
|
||||
font-size: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
> #qr--modal-messageHolder {
|
||||
flex: 1 1 auto;
|
||||
display: grid;
|
||||
text-align: left;
|
||||
overflow: hidden;
|
||||
&.qr--noSyntax {
|
||||
> #qr--modal-messageSyntax {
|
||||
display: none;
|
||||
}
|
||||
> #qr--modal-message {
|
||||
background-color: var(--ac-style-color-background);
|
||||
color: var(--ac-style-color-text);
|
||||
&::-webkit-scrollbar, &::-webkit-scrollbar-thumb {
|
||||
visibility: visible;
|
||||
cursor: unset;
|
||||
}
|
||||
&::selection {
|
||||
color: unset;
|
||||
background-color: rgba(108 171 251 / 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
> #qr--modal-messageSyntax {
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: none;
|
||||
overflow: hidden;
|
||||
min-width: 100%;
|
||||
width: 0;
|
||||
> #qr--modal-messageSyntaxInner {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
> #qr--modal-message {
|
||||
background-color: transparent;
|
||||
color: transparent;
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
caret-color: var(--ac-style-color-text);
|
||||
overflow: auto;
|
||||
&::-webkit-scrollbar, &::-webkit-scrollbar-thumb {
|
||||
visibility: hidden;
|
||||
cursor: default;
|
||||
}
|
||||
&::selection {
|
||||
color: transparent;
|
||||
background-color: rgba(108 171 251 / 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
#qr--modal-message, #qr--modal-messageSyntaxInner {
|
||||
font-family: var(--monoFontFamily);
|
||||
padding: 0.75em;
|
||||
margin: 0;
|
||||
resize: none;
|
||||
line-height: 1.2;
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
border-radius: 5px;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#qr--modal-icon {
|
||||
height: 100%;
|
||||
aspect-ratio: 1 / 1;
|
||||
}
|
||||
|
||||
#qr--modal-executeButtons {
|
||||
display: flex;
|
||||
gap: 1em;
|
||||
|
@ -510,6 +782,39 @@
|
|||
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):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-executeProgress {
|
||||
--prog: 0;
|
||||
|
@ -588,6 +893,135 @@
|
|||
overflow: auto;
|
||||
min-width: 100%;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -606,9 +1040,63 @@
|
|||
}
|
||||
|
||||
.popup.qr--hide {
|
||||
opacity: 0 !important;
|
||||
opacity: 0 !important;
|
||||
&::backdrop {
|
||||
opacity: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.popup.qr--hide::backdrop {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -511,6 +511,15 @@ export class Popup {
|
|||
|
||||
return this.#promise;
|
||||
}
|
||||
completeAffirmative() {
|
||||
return this.complete(POPUP_RESULT.AFFIRMATIVE);
|
||||
}
|
||||
completeNegative() {
|
||||
return this.complete(POPUP_RESULT.NEGATIVE);
|
||||
}
|
||||
completeCancelled() {
|
||||
return this.complete(POPUP_RESULT.CANCELLED);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 { SlashCommand } from './slash-commands/SlashCommand.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 { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
|
||||
import { POPUP_TYPE, callGenericPopup } from './popup.js';
|
||||
|
@ -276,6 +276,7 @@ let power_user = {
|
|||
left: AUTOCOMPLETE_WIDTH.CHAT,
|
||||
right: AUTOCOMPLETE_WIDTH.CHAT,
|
||||
},
|
||||
select: AUTOCOMPLETE_SELECT_KEY.TAB + AUTOCOMPLETE_SELECT_KEY.ENTER,
|
||||
},
|
||||
parser: {
|
||||
/**@type {Object.<PARSER_FLAG,boolean>} */
|
||||
|
@ -1492,6 +1493,9 @@ function loadPowerUserSettings(settings, data) {
|
|||
if (power_user.stscript.autocomplete.style === undefined) {
|
||||
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) {
|
||||
power_user.stscript.parser = defaultStscript.parser;
|
||||
|
@ -1656,6 +1660,7 @@ function loadPowerUserSettings(settings, data) {
|
|||
$('#stscript_matching').val(power_user.stscript.matching ?? 'fuzzy');
|
||||
$('#stscript_autocomplete_style').val(power_user.stscript.autocomplete.style ?? 'theme');
|
||||
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_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);
|
||||
|
@ -3838,6 +3843,12 @@ $(document).ready(() => {
|
|||
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 () {
|
||||
const value = $(this).val();
|
||||
$('#stscript_autocomplete_font_scale_counter').val(value);
|
||||
|
|
|
@ -51,7 +51,7 @@ import { autoSelectPersona, retriggerFirstMessageOnEmptyChat, setPersonaLockStat
|
|||
import { addEphemeralStoppingString, chat_styles, flushEphemeralStoppingStrings, power_user } from './power-user.js';
|
||||
import { textgen_types, textgenerationwebui_settings } from './textgen-settings.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 { background_settings } from './backgrounds.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 { POPUP_TYPE, Popup, callGenericPopup } from './popup.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 {
|
||||
executeSlashCommands, executeSlashCommandsWithOptions, getSlashCommandsHelp, registerSlashCommand,
|
||||
};
|
||||
|
@ -1115,10 +1118,10 @@ export function initDefaultSlashCommands() {
|
|||
unnamedArgumentList: [
|
||||
SlashCommandArgument.fromProps({
|
||||
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,
|
||||
enumProvider: () => [
|
||||
...commonEnumProviders.variables('scope')(),
|
||||
enumProvider: (executor, scope) => [
|
||||
...commonEnumProviders.variables('scope')(executor, scope),
|
||||
...(typeof window['qrEnumProviderExecutables'] === 'function') ? window['qrEnumProviderExecutables']() : [],
|
||||
],
|
||||
}),
|
||||
|
@ -1472,6 +1475,21 @@ export function initDefaultSlashCommands() {
|
|||
],
|
||||
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();
|
||||
}
|
||||
|
@ -1815,6 +1833,11 @@ async function runCallback(args, name) {
|
|||
throw new Error('No name provided for /run command');
|
||||
}
|
||||
|
||||
if (name instanceof SlashCommandClosure) {
|
||||
name.breakController = new SlashCommandBreakController();
|
||||
return (await name.execute())?.pipe;
|
||||
}
|
||||
|
||||
/**@type {SlashCommandScope} */
|
||||
const scope = args._scope;
|
||||
if (scope.existsVariable(name)) {
|
||||
|
@ -1823,6 +1846,11 @@ async function runCallback(args, name) {
|
|||
throw new Error(`"${name}" is not callable.`);
|
||||
}
|
||||
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 => {
|
||||
if (Object.keys(args).includes(arg.name)) {
|
||||
const providedArg = new SlashCommandNamedArgumentAssignment();
|
||||
|
@ -1841,9 +1869,14 @@ async function runCallback(args, name) {
|
|||
|
||||
try {
|
||||
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) {
|
||||
throw new Error(`Error running Quick Reply "${name}": ${error.message}`, 'Error');
|
||||
throw new Error(`Error running Quick Reply "${name}": ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3417,7 +3450,9 @@ const clearCommandProgressDebounced = debounce(clearCommandProgress);
|
|||
* @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 {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 {string} [source] (null) String indicating where the code come from (e.g., QR name)
|
||||
*/
|
||||
|
||||
/**
|
||||
|
@ -3425,6 +3460,7 @@ const clearCommandProgressDebounced = debounce(clearCommandProgress);
|
|||
* @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 {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)
|
||||
*/
|
||||
|
||||
/**
|
||||
|
@ -3440,6 +3476,7 @@ export async function executeSlashCommandsOnChatInput(text, options = {}) {
|
|||
scope: null,
|
||||
parserFlags: null,
|
||||
clearChatInput: false,
|
||||
source: null,
|
||||
}, options);
|
||||
|
||||
isExecutingCommandsFromChatInput = true;
|
||||
|
@ -3462,13 +3499,21 @@ export async function executeSlashCommandsOnChatInput(text, options = {}) {
|
|||
|
||||
/**@type {SlashCommandClosureResult} */
|
||||
let result = null;
|
||||
let currentProgress = '';
|
||||
try {
|
||||
commandsFromChatInputAbortController = new SlashCommandAbortController();
|
||||
result = await executeSlashCommandsWithOptions(text, {
|
||||
abortController: commandsFromChatInputAbortController,
|
||||
onProgress: (done, total) => ta.style.setProperty('--prog', `${done / total * 100}%`),
|
||||
onProgress: (done, total) => {
|
||||
const newProgress = `${done / total * 100}%`;
|
||||
if (newProgress !== currentProgress) {
|
||||
currentProgress = newProgress;
|
||||
ta.style.setProperty('--prog', newProgress);
|
||||
}
|
||||
},
|
||||
parserFlags: options.parserFlags,
|
||||
scope: options.scope,
|
||||
source: options.source,
|
||||
});
|
||||
if (commandsFromChatInputAbortController.signal.aborted) {
|
||||
document.querySelector('#form_sheld').classList.add('script_aborted');
|
||||
|
@ -3481,7 +3526,23 @@ export async function executeSlashCommandsOnChatInput(text, options = {}) {
|
|||
result.isError = true;
|
||||
result.errorMessage = e.message || 'An unknown error occurred';
|
||||
if (e.cause !== 'abort') {
|
||||
toastr.error(result.errorMessage);
|
||||
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);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
delay(1000).then(() => clearCommandProgressDebounced());
|
||||
|
@ -3509,7 +3570,9 @@ async function executeSlashCommandsWithOptions(text, options = {}) {
|
|||
handleExecutionErrors: false,
|
||||
parserFlags: null,
|
||||
abortController: null,
|
||||
debugController: null,
|
||||
onProgress: null,
|
||||
source: null,
|
||||
}, options);
|
||||
|
||||
let closure;
|
||||
|
@ -3517,6 +3580,8 @@ async function executeSlashCommandsWithOptions(text, options = {}) {
|
|||
closure = parser.parse(text, true, options.parserFlags, options.abortController ?? new SlashCommandAbortController());
|
||||
closure.scope.parent = options.scope;
|
||||
closure.onProgress = options.onProgress;
|
||||
closure.debugController = options.debugController;
|
||||
closure.source = options.source;
|
||||
} catch (e) {
|
||||
if (options.handleParserErrors && e instanceof SlashCommandParserError) {
|
||||
/**@type {SlashCommandParserError}*/
|
||||
|
@ -3548,7 +3613,23 @@ async function executeSlashCommandsWithOptions(text, options = {}) {
|
|||
return result;
|
||||
} catch (e) {
|
||||
if (options.handleExecutionErrors) {
|
||||
toastr.error(e.message);
|
||||
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);
|
||||
}
|
||||
const result = new SlashCommandClosureResult();
|
||||
result.isError = true;
|
||||
result.errorMessage = e.message;
|
||||
|
@ -3585,6 +3666,7 @@ async function executeSlashCommands(text, handleParserErrors = true, scope = nul
|
|||
*
|
||||
* @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)
|
||||
* @returns {Promise<AutoComplete>}
|
||||
*/
|
||||
export async function setSlashCommandAutoComplete(textarea, isFloating = false) {
|
||||
function canUseNegativeLookbehind() {
|
||||
|
@ -3608,6 +3690,7 @@ export async function setSlashCommandAutoComplete(textarea, isFloating = false)
|
|||
async (text, index) => await parser.getNameAt(text, index),
|
||||
isFloating,
|
||||
);
|
||||
return ac;
|
||||
}
|
||||
/**@type {HTMLTextAreaElement} */
|
||||
const sendTextarea = document.querySelector('#send_textarea');
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { SlashCommandAbortController } from './SlashCommandAbortController.js';
|
||||
import { SlashCommandArgument, SlashCommandNamedArgument } from './SlashCommandArgument.js';
|
||||
import { SlashCommandClosure } from './SlashCommandClosure.js';
|
||||
import { SlashCommandDebugController } from './SlashCommandDebugController.js';
|
||||
import { PARSER_FLAG } from './SlashCommandParser.js';
|
||||
import { SlashCommandScope } from './SlashCommandScope.js';
|
||||
|
||||
|
@ -12,6 +13,7 @@ import { SlashCommandScope } from './SlashCommandScope.js';
|
|||
* _scope:SlashCommandScope,
|
||||
* _parserFlags:{[id:PARSER_FLAG]:boolean},
|
||||
* _abortController:SlashCommandAbortController,
|
||||
* _debugController:SlashCommandDebugController,
|
||||
* _hasUnnamedArgument:boolean,
|
||||
* [id:string]:string|SlashCommandClosure,
|
||||
* }} 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 {string} [props.helpString]
|
||||
* @param {boolean} [props.splitUnnamedArgument]
|
||||
* @param {Number} [props.splitUnnamedArgumentCount]
|
||||
* @param {string[]} [props.aliases]
|
||||
* @param {string} [props.returns]
|
||||
* @param {SlashCommandNamedArgument[]} [props.namedArgumentList]
|
||||
|
@ -50,9 +53,10 @@ export class SlashCommand {
|
|||
|
||||
|
||||
/**@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 {boolean}*/ splitUnnamedArgument = false;
|
||||
/**@type {Number}*/ splitUnnamedArgumentCount;
|
||||
/**@type {string[]}*/ aliases = [];
|
||||
/**@type {string}*/ returns;
|
||||
/**@type {SlashCommandNamedArgument[]}*/ namedArgumentList = [];
|
||||
|
@ -61,6 +65,10 @@ export class SlashCommand {
|
|||
/**@type {Object.<string, HTMLElement>}*/ helpCache = {};
|
||||
/**@type {Object.<string, DocumentFragment>}*/ helpDetailsCache = {};
|
||||
|
||||
/**@type {boolean}*/ isExtension = false;
|
||||
/**@type {boolean}*/ isThirdParty = false;
|
||||
/**@type {string}*/ source;
|
||||
|
||||
renderHelpItem(key = null) {
|
||||
key = key ?? this.name;
|
||||
if (!this.helpCache[key]) {
|
||||
|
@ -225,12 +233,35 @@ export class SlashCommand {
|
|||
const aliasList = [cmd.name, ...(cmd.aliases ?? [])].filter(it=>it != key);
|
||||
const specs = document.createElement('div'); {
|
||||
specs.classList.add('specs');
|
||||
const name = document.createElement('div'); {
|
||||
name.classList.add('name');
|
||||
name.classList.add('monospace');
|
||||
name.title = 'command name';
|
||||
name.textContent = `/${key}`;
|
||||
specs.append(name);
|
||||
const head = document.createElement('div'); {
|
||||
head.classList.add('head');
|
||||
const name = document.createElement('div'); {
|
||||
name.classList.add('name');
|
||||
name.classList.add('monospace');
|
||||
name.title = 'command name';
|
||||
name.textContent = `/${key}`;
|
||||
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'); {
|
||||
body.classList.add('body');
|
||||
|
@ -303,40 +334,52 @@ export class SlashCommand {
|
|||
for (const arg of unnamedArguments) {
|
||||
const listItem = document.createElement('li'); {
|
||||
listItem.classList.add('argumentItem');
|
||||
const argItem = document.createElement('div'); {
|
||||
argItem.classList.add('argument');
|
||||
argItem.classList.add('unnamedArgument');
|
||||
argItem.title = `${arg.isRequired ? '' : 'optional '}unnamed argument`;
|
||||
if (!arg.isRequired || (arg.defaultValue ?? false)) argItem.classList.add('optional');
|
||||
if (arg.acceptsMultiple) argItem.classList.add('multiple');
|
||||
if (arg.enumList.length > 0) {
|
||||
const enums = document.createElement('span'); {
|
||||
enums.classList.add('argument-enums');
|
||||
enums.title = `${argItem.title} - accepted values`;
|
||||
for (const e of arg.enumList) {
|
||||
const enumItem = document.createElement('span'); {
|
||||
enumItem.classList.add('argument-enum');
|
||||
enumItem.textContent = e.value;
|
||||
enums.append(enumItem);
|
||||
const argSpec = document.createElement('div'); {
|
||||
argSpec.classList.add('argumentSpec');
|
||||
const argItem = document.createElement('div'); {
|
||||
argItem.classList.add('argument');
|
||||
argItem.classList.add('unnamedArgument');
|
||||
argItem.title = `${arg.isRequired ? '' : 'optional '}unnamed argument`;
|
||||
if (!arg.isRequired || (arg.defaultValue ?? false)) argItem.classList.add('optional');
|
||||
if (arg.acceptsMultiple) argItem.classList.add('multiple');
|
||||
if (arg.enumList.length > 0) {
|
||||
const enums = document.createElement('span'); {
|
||||
enums.classList.add('argument-enums');
|
||||
enums.title = `${argItem.title} - accepted values`;
|
||||
for (const e of arg.enumList) {
|
||||
const enumItem = document.createElement('span'); {
|
||||
enumItem.classList.add('argument-enum');
|
||||
enumItem.textContent = e.value;
|
||||
enums.append(enumItem);
|
||||
}
|
||||
}
|
||||
argItem.append(enums);
|
||||
}
|
||||
} else {
|
||||
const types = document.createElement('span'); {
|
||||
types.classList.add('argument-types');
|
||||
types.title = `${argItem.title} - accepted types`;
|
||||
for (const t of arg.typeList) {
|
||||
const type = document.createElement('span'); {
|
||||
type.classList.add('argument-type');
|
||||
type.textContent = t;
|
||||
types.append(type);
|
||||
}
|
||||
}
|
||||
argItem.append(types);
|
||||
}
|
||||
argItem.append(enums);
|
||||
}
|
||||
} else {
|
||||
const types = document.createElement('span'); {
|
||||
types.classList.add('argument-types');
|
||||
types.title = `${argItem.title} - accepted types`;
|
||||
for (const t of arg.typeList) {
|
||||
const type = document.createElement('span'); {
|
||||
type.classList.add('argument-type');
|
||||
type.textContent = t;
|
||||
types.append(type);
|
||||
}
|
||||
}
|
||||
argItem.append(types);
|
||||
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(argItem);
|
||||
listItem.append(argSpec);
|
||||
}
|
||||
const desc = document.createElement('div'); {
|
||||
desc.classList.add('argument-description');
|
||||
|
|
|
@ -2,6 +2,9 @@ import { SlashCommandClosure } from './SlashCommandClosure.js';
|
|||
import { commonEnumProviders } from './SlashCommandCommonEnumsProvider.js';
|
||||
import { SlashCommandEnumValue } from './SlashCommandEnumValue.js';
|
||||
import { SlashCommandExecutor } from './SlashCommandExecutor.js';
|
||||
import { SlashCommandScope } from './SlashCommandScope.js';
|
||||
|
||||
|
||||
|
||||
/**@readonly*/
|
||||
/**@enum {string}*/
|
||||
|
@ -27,7 +30,7 @@ export class SlashCommandArgument {
|
|||
* @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|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
|
||||
*/
|
||||
static fromProps(props) {
|
||||
|
@ -49,7 +52,7 @@ export class SlashCommandArgument {
|
|||
/**@type {boolean}*/ acceptsMultiple = false;
|
||||
/**@type {string|SlashCommandClosure}*/ defaultValue;
|
||||
/**@type {SlashCommandEnumValue[]}*/ enumList = [];
|
||||
/**@type {(executor:SlashCommandExecutor)=>SlashCommandEnumValue[]}*/ enumProvider = null;
|
||||
/**@type {(executor:SlashCommandExecutor, scope:SlashCommandScope)=>SlashCommandEnumValue[]}*/ enumProvider = null;
|
||||
/**@type {boolean}*/ forceEnum = false;
|
||||
|
||||
/**
|
||||
|
@ -57,7 +60,7 @@ export class SlashCommandArgument {
|
|||
* @param {ARGUMENT_TYPE|ARGUMENT_TYPE[]} types
|
||||
* @param {string|SlashCommandClosure} defaultValue
|
||||
* @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) {
|
||||
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 {string|SlashCommandClosure} [props.defaultValue=null] default value if no value is provided
|
||||
* @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
|
||||
*/
|
||||
static fromProps(props) {
|
||||
|
@ -119,7 +122,7 @@ export class SlashCommandNamedArgument extends SlashCommandArgument {
|
|||
* @param {string|SlashCommandClosure} [defaultValue=null]
|
||||
* @param {string|SlashCommandEnumValue|(string|SlashCommandEnumValue)[]} [enums=[]]
|
||||
* @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]
|
||||
*/
|
||||
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 { SlashCommandExecutor } from './SlashCommandExecutor.js';
|
||||
import { SlashCommandNamedArgumentAutoCompleteOption } from './SlashCommandNamedArgumentAutoCompleteOption.js';
|
||||
import { SlashCommandScope } from './SlashCommandScope.js';
|
||||
|
||||
export class SlashCommandAutoCompleteNameResult extends AutoCompleteNameResult {
|
||||
/**@type {SlashCommandExecutor}*/ executor;
|
||||
/**@type {SlashCommandScope}*/ scope;
|
||||
|
||||
/**
|
||||
* @param {SlashCommandExecutor} executor
|
||||
* @param {SlashCommandScope} scope
|
||||
* @param {Object.<string,SlashCommand>} commands
|
||||
*/
|
||||
constructor(executor, commands) {
|
||||
constructor(executor, scope, commands) {
|
||||
super(
|
||||
executor.name,
|
||||
executor.start,
|
||||
|
@ -29,6 +32,7 @@ export class SlashCommandAutoCompleteNameResult extends AutoCompleteNameResult {
|
|||
()=>'No slash commands found!',
|
||||
);
|
||||
this.executor = executor;
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
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) {
|
||||
// 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 (typeof this.executor.unnamedArgumentList[0].value == 'string') {
|
||||
// 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 (index <= this.executor.startUnnamedArgs + this.executor.unnamedArgumentList[0].value.length) {
|
||||
name = this.executor.unnamedArgumentList[0].value.slice(0, index - this.executor.startUnnamedArgs);
|
||||
start = this.executor.startUnnamedArgs;
|
||||
|
@ -103,7 +107,7 @@ export class SlashCommandAutoCompleteNameResult extends AutoCompleteNameResult {
|
|||
|
||||
if (name.includes('=') && cmdArg) {
|
||||
// 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 (isSelect && enumList.find(it=>it.value == value) && argAssign && argAssign.end == index) {
|
||||
return null;
|
||||
|
@ -111,7 +115,7 @@ export class SlashCommandAutoCompleteNameResult extends AutoCompleteNameResult {
|
|||
const result = new AutoCompleteSecondaryNameResult(
|
||||
value,
|
||||
start + name.length,
|
||||
enumList.map(it=>new SlashCommandEnumAutoCompleteOption(this.executor.command, it)),
|
||||
enumList.map(it=>SlashCommandEnumAutoCompleteOption.from(this.executor.command, it)),
|
||||
true,
|
||||
);
|
||||
result.isRequired = true;
|
||||
|
@ -150,7 +154,10 @@ export class SlashCommandAutoCompleteNameResult extends AutoCompleteNameResult {
|
|||
if (idx > -1) {
|
||||
argAssign = this.executor.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) {
|
||||
value = argAssign.value.toString().slice(0, index - argAssign.start);
|
||||
start = argAssign.start;
|
||||
|
@ -161,23 +168,26 @@ export class SlashCommandAutoCompleteNameResult extends AutoCompleteNameResult {
|
|||
value = '';
|
||||
start = index;
|
||||
cmdArg = notProvidedArguments[0];
|
||||
if (cmdArg === undefined && this.executor.command.unnamedArgumentList.slice(-1)[0]?.acceptsMultiple) {
|
||||
cmdArg = this.executor.command.unnamedArgumentList.slice(-1)[0];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
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;
|
||||
|
||||
const result = new AutoCompleteSecondaryNameResult(
|
||||
value,
|
||||
start,
|
||||
enumList.map(it=>new SlashCommandEnumAutoCompleteOption(this.executor.command, it)),
|
||||
enumList.map(it=>SlashCommandEnumAutoCompleteOption.from(this.executor.command, it)),
|
||||
false,
|
||||
);
|
||||
const isCompleteValue = enumList.find(it=>it.value == value);
|
||||
const isSelectedValue = isSelect && isCompleteValue;
|
||||
result.isRequired = cmdArg.isRequired && !isSelectedValue && !isCompleteValue;
|
||||
result.isRequired = cmdArg.isRequired && !isSelectedValue;
|
||||
result.forceMatch = cmdArg.forceEnum;
|
||||
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 { delay, escapeRegex } from '../utils.js';
|
||||
import { delay, escapeRegex, uuidv4 } from '../utils.js';
|
||||
import { SlashCommand } from './SlashCommand.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 { SlashCommandDebugController } from './SlashCommandDebugController.js';
|
||||
import { SlashCommandExecutionError } from './SlashCommandExecutionError.js';
|
||||
import { SlashCommandExecutor } from './SlashCommandExecutor.js';
|
||||
import { SlashCommandNamedArgumentAssignment } from './SlashCommandNamedArgumentAssignment.js';
|
||||
import { SlashCommandScope } from './SlashCommandScope.js';
|
||||
|
@ -17,8 +21,20 @@ export class SlashCommandClosure {
|
|||
/**@type {SlashCommandNamedArgumentAssignment[]}*/ providedArgumentList = [];
|
||||
/**@type {SlashCommandExecutor[]}*/ executorList = [];
|
||||
/**@type {SlashCommandAbortController}*/ abortController;
|
||||
/**@type {SlashCommandBreakController}*/ breakController;
|
||||
/**@type {SlashCommandDebugController}*/ debugController;
|
||||
/**@type {(done:number, total:number)=>void}*/ onProgress;
|
||||
/**@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}*/
|
||||
get commandCount() {
|
||||
|
@ -30,7 +46,7 @@ export class SlashCommandClosure {
|
|||
}
|
||||
|
||||
toString() {
|
||||
return '[Closure]';
|
||||
return `[Closure]${this.executeNow ? '()' : ''}`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -43,16 +59,30 @@ export class SlashCommandClosure {
|
|||
let isList = false;
|
||||
let listValues = [];
|
||||
scope = scope ?? this.scope;
|
||||
const macros = scope.macroList.map(it=>escapeRegex(it.key)).join('|');
|
||||
const re = new RegExp(`({{pipe}})|(?:{{var::([^\\s]+?)(?:::((?!}}).+))?}})|(?:{{(${macros})}})`);
|
||||
const escapeMacro = (it)=>escapeRegex(it.key.replace(/\*/g, '~~~WILDCARD~~~'))
|
||||
.replaceAll('~~~WILDCARD~~~', '(?:(?:(?!(?:::|}})).)*)')
|
||||
;
|
||||
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;
|
||||
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 remaining = text;
|
||||
while (re.test(remaining)) {
|
||||
const match = re.exec(remaining);
|
||||
const before = substituteParams(remaining.slice(0, match.index));
|
||||
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)).test(match.groups.macro))?.value;
|
||||
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;
|
||||
if (match.index > 0) {
|
||||
listValues.push(before);
|
||||
|
@ -87,6 +117,12 @@ export class SlashCommandClosure {
|
|||
closure.providedArgumentList = this.providedArgumentList;
|
||||
closure.executorList = this.executorList;
|
||||
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;
|
||||
return closure;
|
||||
}
|
||||
|
@ -96,11 +132,22 @@ export class SlashCommandClosure {
|
|||
* @returns {Promise<SlashCommandClosureResult>}
|
||||
*/
|
||||
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();
|
||||
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
|
||||
for (const arg of this.argumentList) {
|
||||
let v = arg.value;
|
||||
|
@ -108,6 +155,7 @@ export class SlashCommandClosure {
|
|||
/**@type {SlashCommandClosure}*/
|
||||
const closure = v;
|
||||
closure.scope.parent = this.scope;
|
||||
closure.breakController = this.breakController;
|
||||
if (closure.executeNow) {
|
||||
v = (await closure.execute())?.pipe;
|
||||
} else {
|
||||
|
@ -131,6 +179,7 @@ export class SlashCommandClosure {
|
|||
/**@type {SlashCommandClosure}*/
|
||||
const closure = v;
|
||||
closure.scope.parent = this.scope;
|
||||
closure.breakController = this.breakController;
|
||||
if (closure.executeNow) {
|
||||
v = (await closure.execute())?.pipe;
|
||||
} else {
|
||||
|
@ -149,106 +198,145 @@ export class SlashCommandClosure {
|
|||
this.scope.setVariable(arg.name, v);
|
||||
}
|
||||
|
||||
let done = 0;
|
||||
if (this.executorList.length == 0) {
|
||||
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) {
|
||||
this.onProgress?.(done, this.commandCount);
|
||||
if (executor instanceof SlashCommandClosureExecutor) {
|
||||
const closure = this.scope.getVariable(executor.name);
|
||||
if (!closure || !(closure instanceof SlashCommandClosure)) throw new Error(`${executor.name} is not a closure.`);
|
||||
closure.scope.parent = this.scope;
|
||||
closure.providedArgumentList = executor.providedArgumentList;
|
||||
const result = await closure.execute();
|
||||
this.scope.pipe = result.pipe;
|
||||
if (this.debugController) {
|
||||
this.debugController.setExecutor(executor);
|
||||
this.debugController.namedArguments = undefined;
|
||||
this.debugController.unnamedArguments = undefined;
|
||||
}
|
||||
// yield before doing anything with this executor, the debugger might want to do
|
||||
// something with it (e.g., breakpoint, immediate closures that need resolving
|
||||
// or stepping into)
|
||||
yield executor;
|
||||
/**@type {import('./SlashCommand.js').NamedArguments} */
|
||||
// @ts-ignore
|
||||
let args = {
|
||||
_scope: this.scope,
|
||||
_parserFlags: executor.parserFlags,
|
||||
_abortController: this.abortController,
|
||||
_debugController: this.debugController,
|
||||
_hasUnnamedArgument: executor.unnamedArgumentList.length > 0,
|
||||
};
|
||||
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 {
|
||||
/**@type {import('./SlashCommand.js').NamedArguments} */
|
||||
let args = {
|
||||
_scope: this.scope,
|
||||
_parserFlags: executor.parserFlags,
|
||||
_abortController: this.abortController,
|
||||
_hasUnnamedArgument: executor.unnamedArgumentList.length > 0,
|
||||
};
|
||||
let value;
|
||||
// substitute named arguments
|
||||
for (const arg of executor.namedArgumentList) {
|
||||
if (arg.value instanceof SlashCommandClosure) {
|
||||
/**@type {SlashCommandClosure}*/
|
||||
const closure = arg.value;
|
||||
closure.scope.parent = this.scope;
|
||||
if (closure.executeNow) {
|
||||
args[arg.name] = (await closure.execute())?.pipe;
|
||||
} else {
|
||||
args[arg.name] = closure;
|
||||
}
|
||||
} else {
|
||||
args[arg.name] = this.substituteParams(arg.value);
|
||||
}
|
||||
// unescape named argument
|
||||
if (typeof args[arg.name] == 'string') {
|
||||
args[arg.name] = args[arg.name]
|
||||
?.replace(/\\\{/g, '{')
|
||||
?.replace(/\\\}/g, '}')
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
// substitute unnamed argument
|
||||
if (executor.unnamedArgumentList.length == 0) {
|
||||
if (executor.injectPipe) {
|
||||
value = this.scope.pipe;
|
||||
args._hasUnnamedArgument = this.scope.pipe !== null && this.scope.pipe !== undefined;
|
||||
}
|
||||
} else {
|
||||
value = [];
|
||||
for (let i = 0; i < executor.unnamedArgumentList.length; i++) {
|
||||
let v = executor.unnamedArgumentList[i].value;
|
||||
if (v instanceof SlashCommandClosure) {
|
||||
/**@type {SlashCommandClosure}*/
|
||||
const closure = v;
|
||||
closure.scope.parent = this.scope;
|
||||
if (closure.executeNow) {
|
||||
v = (await closure.execute())?.pipe;
|
||||
} else {
|
||||
v = closure;
|
||||
}
|
||||
} else {
|
||||
v = this.substituteParams(v);
|
||||
}
|
||||
value[i] = v;
|
||||
}
|
||||
if (!executor.command.splitUnnamedArgument) {
|
||||
if (value.length == 1) {
|
||||
value = value[0];
|
||||
} else if (!value.find(it=>it instanceof SlashCommandClosure)) {
|
||||
value = value.join('');
|
||||
}
|
||||
}
|
||||
}
|
||||
// unescape unnamed argument
|
||||
if (typeof value == 'string') {
|
||||
value = value
|
||||
?.replace(/\\\{/g, '{')
|
||||
?.replace(/\\\}/g, '}')
|
||||
;
|
||||
} else if (Array.isArray(value)) {
|
||||
value = value.map(v=>{
|
||||
if (typeof v == 'string') {
|
||||
return v
|
||||
?.replace(/\\\{/g, '{')
|
||||
?.replace(/\\\}/g, '}');
|
||||
}
|
||||
return v;
|
||||
});
|
||||
}
|
||||
// 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);
|
||||
this.scope.pipe = await executor.command.callback(args, value ?? '');
|
||||
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);
|
||||
|
@ -257,10 +345,10 @@ export class SlashCommandClosure {
|
|||
return abortResult;
|
||||
}
|
||||
}
|
||||
// finally, yield for "after exec"
|
||||
yield executor;
|
||||
isFirst = false;
|
||||
}
|
||||
/**@type {SlashCommandClosureResult} */
|
||||
const result = Object.assign(new SlashCommandClosureResult(), { pipe: this.scope.pipe });
|
||||
return result;
|
||||
}
|
||||
|
||||
async testPaused() {
|
||||
|
@ -279,14 +367,113 @@ export class SlashCommandClosure {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {SlashCommandExecutor} executor
|
||||
* @param {import('./SlashCommand.js').NamedArguments} args
|
||||
*/
|
||||
async substituteNamedArguments(executor, args) {
|
||||
// substitute named arguments
|
||||
for (const arg of executor.namedArgumentList) {
|
||||
if (arg.value instanceof SlashCommandClosure) {
|
||||
/**@type {SlashCommandClosure}*/
|
||||
const closure = arg.value;
|
||||
closure.scope.parent = this.scope;
|
||||
closure.breakController = this.breakController;
|
||||
if (this.debugController && !closure.debugController) {
|
||||
closure.debugController = this.debugController;
|
||||
}
|
||||
if (closure.executeNow) {
|
||||
args[arg.name] = (await closure.execute())?.pipe;
|
||||
} else {
|
||||
args[arg.name] = closure;
|
||||
}
|
||||
} else {
|
||||
args[arg.name] = this.substituteParams(arg.value);
|
||||
}
|
||||
// unescape named argument
|
||||
if (typeof args[arg.name] == 'string') {
|
||||
args[arg.name] = args[arg.name]
|
||||
?.replace(/\\\{/g, '{')
|
||||
?.replace(/\\\}/g, '}')
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
if (executor.unnamedArgumentList.length == 0) {
|
||||
if (!isFirst && executor.injectPipe) {
|
||||
value = this.scope.pipe;
|
||||
args._hasUnnamedArgument = this.scope.pipe !== null && this.scope.pipe !== undefined;
|
||||
}
|
||||
} else {
|
||||
value = [];
|
||||
for (let i = 0; i < executor.unnamedArgumentList.length; i++) {
|
||||
let v = executor.unnamedArgumentList[i].value;
|
||||
if (v instanceof SlashCommandClosure) {
|
||||
/**@type {SlashCommandClosure}*/
|
||||
const closure = v;
|
||||
closure.scope.parent = this.scope;
|
||||
closure.breakController = this.breakController;
|
||||
if (this.debugController && !closure.debugController) {
|
||||
closure.debugController = this.debugController;
|
||||
}
|
||||
if (closure.executeNow) {
|
||||
v = (await closure.execute())?.pipe;
|
||||
} else {
|
||||
v = closure;
|
||||
}
|
||||
} else {
|
||||
v = this.substituteParams(v);
|
||||
}
|
||||
value[i] = v;
|
||||
}
|
||||
if (!executor.command.splitUnnamedArgument) {
|
||||
if (value.length == 1) {
|
||||
value = value[0];
|
||||
} else if (!value.find(it=>it instanceof SlashCommandClosure)) {
|
||||
value = value.join('');
|
||||
}
|
||||
}
|
||||
}
|
||||
// unescape unnamed argument
|
||||
if (typeof value == 'string') {
|
||||
value = value
|
||||
?.replace(/\\\{/g, '{')
|
||||
?.replace(/\\\}/g, '}')
|
||||
;
|
||||
} else if (Array.isArray(value)) {
|
||||
value = value.map(v=>{
|
||||
if (typeof v == 'string') {
|
||||
return v
|
||||
?.replace(/\\\{/g, '{')
|
||||
?.replace(/\\\}/g, '}');
|
||||
}
|
||||
return v;
|
||||
});
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto-fixes the pipe if it is not a valid result for STscript.
|
||||
* @param {SlashCommand} command Command being executed
|
||||
*/
|
||||
#lintPipe(command) {
|
||||
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 = '';
|
||||
} 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 {
|
||||
/**@type {boolean}*/ interrupt = false;
|
||||
/**@type {string}*/ pipe;
|
||||
/**@type {boolean}*/ isBreak = false;
|
||||
/**@type {boolean}*/ isAborted = false;
|
||||
/**@type {boolean}*/ isQuietlyAborted = false;
|
||||
/**@type {string}*/ abortReason;
|
||||
|
|
|
@ -6,6 +6,7 @@ import { searchCharByName, getTagsList, tags } from '../tags.js';
|
|||
import { world_names } from '../world-info.js';
|
||||
import { SlashCommandClosure } from './SlashCommandClosure.js';
|
||||
import { SlashCommandEnumValue, enumTypes } from './SlashCommandEnumValue.js';
|
||||
import { SlashCommandScope } from "./SlashCommandScope.js";
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* @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 isAll = types.includes('all');
|
||||
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('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';
|
||||
|
||||
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 {SlashCommandEnumValue}*/ enumValue;
|
||||
|
||||
|
@ -13,7 +24,7 @@ export class SlashCommandEnumAutoCompleteOption extends AutoCompleteOption {
|
|||
* @param {SlashCommandEnumValue} 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.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
|
||||
|
@ -37,14 +40,17 @@ export const enumTypes = {
|
|||
getBasedOnIndex(index) {
|
||||
const keys = Object.keys(this);
|
||||
return this[keys[(index ?? 0) % keys.length]];
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export class SlashCommandEnumValue {
|
||||
/**@type {string}*/ value;
|
||||
/**@type {string}*/ description;
|
||||
/**@type {EnumType}*/ type = 'enum';
|
||||
/**@type {string}*/ typeIcon = '◊';
|
||||
/**@type {(input:string)=>boolean}*/ matchProvider;
|
||||
/**@type {(input:string)=>string}*/ valueProvider;
|
||||
/**@type {boolean}*/ makeSelectable = false;
|
||||
|
||||
/**
|
||||
* A constructor for creating a SlashCommandEnumValue instance.
|
||||
|
@ -52,13 +58,19 @@ export class SlashCommandEnumValue {
|
|||
* @param {string} value - The value
|
||||
* @param {string?} description - Optional description, displayed in a second line
|
||||
* @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.description = description;
|
||||
this.type = type ?? 'enum';
|
||||
this.typeIcon = typeIcon;
|
||||
this.matchProvider = matchProvider;
|
||||
this.valueProvider = valueProvider;
|
||||
this.makeSelectable = makeSelectable;
|
||||
}
|
||||
|
||||
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
|
||||
import { uuidv4 } from '../utils.js';
|
||||
import { SlashCommand } from './SlashCommand.js';
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import { SlashCommandClosure } from './SlashCommandClosure.js';
|
||||
|
@ -16,6 +17,17 @@ export class SlashCommandExecutor {
|
|||
/**@type {Number}*/ startUnnamedArgs;
|
||||
/**@type {Number}*/ endUnnamedArgs;
|
||||
/**@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;
|
||||
// @ts-ignore
|
||||
/**@type {SlashCommandNamedArgumentAssignment[]}*/ namedArgumentList = [];
|
||||
|
|
|
@ -17,7 +17,10 @@ import { SlashCommandAutoCompleteNameResult } from './SlashCommandAutoCompleteNa
|
|||
import { SlashCommandUnnamedArgumentAssignment } from './SlashCommandUnnamedArgumentAssignment.js';
|
||||
import { SlashCommandEnumValue } from './SlashCommandEnumValue.js';
|
||||
import { MacroAutoCompleteOption } from '../autocomplete/MacroAutoCompleteOption.js';
|
||||
import { SlashCommandBreakPoint } from './SlashCommandBreakPoint.js';
|
||||
import { SlashCommandDebugController } from './SlashCommandDebugController.js';
|
||||
import { commonEnumProviders } from './SlashCommandCommonEnumsProvider.js';
|
||||
import { SlashCommandBreak } from './SlashCommandBreak.js';
|
||||
|
||||
/** @typedef {import('./SlashCommand.js').NamedArgumentsCapture} NamedArgumentsCapture */
|
||||
/** @typedef {import('./SlashCommand.js').NamedArguments} NamedArguments */
|
||||
|
@ -53,7 +56,7 @@ export class SlashCommandParser {
|
|||
* @param {SlashCommand} command
|
||||
*/
|
||||
static addCommandObject(command) {
|
||||
const reserved = ['/', '#', ':', 'parser-flag'];
|
||||
const reserved = ['/', '#', ':', 'parser-flag', 'breakpoint'];
|
||||
for (const start of reserved) {
|
||||
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}".`);
|
||||
|
@ -70,6 +73,18 @@ export class SlashCommandParser {
|
|||
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;
|
||||
|
||||
if (Array.isArray(command.aliases)) {
|
||||
|
@ -89,6 +104,7 @@ export class SlashCommandParser {
|
|||
/**@type {string}*/ text;
|
||||
/**@type {number}*/ index;
|
||||
/**@type {SlashCommandAbortController}*/ abortController;
|
||||
/**@type {SlashCommandDebugController}*/ debugController;
|
||||
/**@type {SlashCommandScope}*/ scope;
|
||||
/**@type {SlashCommandClosure}*/ closure;
|
||||
|
||||
|
@ -101,6 +117,8 @@ export class SlashCommandParser {
|
|||
/**@type {SlashCommandExecutor[]}*/ commandIndex;
|
||||
/**@type {SlashCommandScope[]}*/ scopeIndex;
|
||||
|
||||
/**@type {string}*/ parserContext;
|
||||
|
||||
get userIndex() { return this.index; }
|
||||
|
||||
get ahead() {
|
||||
|
@ -154,6 +172,21 @@ export class SlashCommandParser {
|
|||
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
|
||||
this.registerLanguage();
|
||||
|
@ -191,13 +224,19 @@ export class SlashCommandParser {
|
|||
|
||||
function getQuotedRunRegex() {
|
||||
try {
|
||||
return new RegExp('(".+?(?<!\\\\)")|(\\S+?)');
|
||||
return new RegExp('(".+?(?<!\\\\)")|(\\S+?)(\\||$|\\s)');
|
||||
} catch {
|
||||
// fallback for browsers that don't support lookbehind
|
||||
return /(".+?")|(\S+?)/;
|
||||
return /(".+?")|(\S+?)(\||$|\s)/;
|
||||
}
|
||||
}
|
||||
|
||||
const BLOCK_COMMENT = {
|
||||
scope: 'comment',
|
||||
begin: /\/\*/,
|
||||
end: /\*\|/,
|
||||
contains: [],
|
||||
};
|
||||
const COMMENT = {
|
||||
scope: 'comment',
|
||||
begin: /\/[/#]/,
|
||||
|
@ -205,9 +244,29 @@ export class SlashCommandParser {
|
|||
contains: [],
|
||||
};
|
||||
const ABORT = {
|
||||
scope: 'abort',
|
||||
begin: /\/abort/,
|
||||
end: /\||$|:}/,
|
||||
begin: /\/(abort|breakpoint)/,
|
||||
beginScope: 'abort',
|
||||
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: [],
|
||||
};
|
||||
const LET = {
|
||||
|
@ -218,26 +277,31 @@ export class SlashCommandParser {
|
|||
1: 'variable',
|
||||
},
|
||||
end: /\||$|:}/,
|
||||
excludeEnd: false,
|
||||
returnEnd: true,
|
||||
contains: [],
|
||||
};
|
||||
const SETVAR = {
|
||||
begin: /\/(setvar|setglobalvar)\s+/,
|
||||
beginScope: 'variable',
|
||||
end: /\||$|:}/,
|
||||
excludeEnd: true,
|
||||
excludeEnd: false,
|
||||
returnEnd: true,
|
||||
contains: [],
|
||||
};
|
||||
const GETVAR = {
|
||||
begin: /\/(getvar|getglobalvar)\s+/,
|
||||
beginScope: 'variable',
|
||||
end: /\||$|:}/,
|
||||
excludeEnd: true,
|
||||
excludeEnd: false,
|
||||
returnEnd: true,
|
||||
contains: [],
|
||||
};
|
||||
const RUN = {
|
||||
match: [
|
||||
/\/:/,
|
||||
getQuotedRunRegex(),
|
||||
/\||$|(?=:})/,
|
||||
],
|
||||
className: {
|
||||
1: 'variable.language',
|
||||
|
@ -250,7 +314,8 @@ export class SlashCommandParser {
|
|||
begin: /\/\S+/,
|
||||
beginScope: 'title.function',
|
||||
end: /\||$|(?=:})/,
|
||||
excludeEnd: true,
|
||||
excludeEnd: false,
|
||||
returnEnd: true,
|
||||
contains: [], // defined later
|
||||
};
|
||||
const CLOSURE = {
|
||||
|
@ -271,6 +336,19 @@ export class SlashCommandParser {
|
|||
begin: /{{/,
|
||||
end: /}}/,
|
||||
};
|
||||
const PIPEBREAK = {
|
||||
beginScope: 'pipebreak',
|
||||
begin: /\|\|/,
|
||||
end: '',
|
||||
};
|
||||
const PIPE = {
|
||||
beginScope: 'pipe',
|
||||
begin: /\|/,
|
||||
end: '',
|
||||
};
|
||||
BLOCK_COMMENT.contains.push(
|
||||
BLOCK_COMMENT,
|
||||
);
|
||||
RUN.contains.push(
|
||||
hljs.BACKSLASH_ESCAPE,
|
||||
NAMED_ARG,
|
||||
|
@ -279,6 +357,22 @@ export class SlashCommandParser {
|
|||
MACRO,
|
||||
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(
|
||||
hljs.BACKSLASH_ESCAPE,
|
||||
NAMED_ARG,
|
||||
|
@ -303,6 +397,14 @@ export class SlashCommandParser {
|
|||
MACRO,
|
||||
CLOSURE,
|
||||
);
|
||||
ABORT.contains.push(
|
||||
hljs.BACKSLASH_ESCAPE,
|
||||
NAMED_ARG,
|
||||
NUMBER,
|
||||
MACRO,
|
||||
CLOSURE,
|
||||
hljs.QUOTE_STRING_MODE,
|
||||
);
|
||||
COMMAND.contains.push(
|
||||
hljs.BACKSLASH_ESCAPE,
|
||||
NAMED_ARG,
|
||||
|
@ -313,8 +415,11 @@ export class SlashCommandParser {
|
|||
);
|
||||
CLOSURE.contains.push(
|
||||
hljs.BACKSLASH_ESCAPE,
|
||||
BLOCK_COMMENT,
|
||||
COMMENT,
|
||||
ABORT,
|
||||
IMPORT,
|
||||
BREAK,
|
||||
NAMED_ARG,
|
||||
NUMBER,
|
||||
MACRO,
|
||||
|
@ -325,20 +430,27 @@ export class SlashCommandParser {
|
|||
COMMAND,
|
||||
'self',
|
||||
hljs.QUOTE_STRING_MODE,
|
||||
PIPEBREAK,
|
||||
PIPE,
|
||||
);
|
||||
hljs.registerLanguage('stscript', ()=>({
|
||||
case_insensitive: false,
|
||||
keywords: ['|'],
|
||||
keywords: [],
|
||||
contains: [
|
||||
hljs.BACKSLASH_ESCAPE,
|
||||
BLOCK_COMMENT,
|
||||
COMMENT,
|
||||
ABORT,
|
||||
IMPORT,
|
||||
BREAK,
|
||||
RUN,
|
||||
LET,
|
||||
GETVAR,
|
||||
SETVAR,
|
||||
COMMAND,
|
||||
CLOSURE,
|
||||
PIPEBREAK,
|
||||
PIPE,
|
||||
],
|
||||
}));
|
||||
}
|
||||
|
@ -415,7 +527,7 @@ export class SlashCommandParser {
|
|||
);
|
||||
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 null;
|
||||
|
@ -515,11 +627,14 @@ export class SlashCommandParser {
|
|||
}
|
||||
|
||||
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();
|
||||
const startIdx = this.index - value.length + idx;
|
||||
const endIdx = this.index - value.length + idx + match.length;
|
||||
// store pipe
|
||||
const pipeName = `_PARSER_${uuidv4()}`;
|
||||
const storePipe = new SlashCommandExecutor(null); {
|
||||
const pipeName = `_PARSER_PIPE_${uuidv4()}`;
|
||||
const storePipe = new SlashCommandExecutor(startIdx); {
|
||||
storePipe.end = endIdx;
|
||||
storePipe.command = this.commands['let'];
|
||||
storePipe.name = 'let';
|
||||
const nameAss = new SlashCommandUnnamedArgumentAssignment();
|
||||
|
@ -530,17 +645,19 @@ export class SlashCommandParser {
|
|||
this.closure.executorList.push(storePipe);
|
||||
}
|
||||
// getvar / getglobalvar
|
||||
const getvar = new SlashCommandExecutor(null); {
|
||||
const getvar = new SlashCommandExecutor(startIdx); {
|
||||
getvar.end = endIdx;
|
||||
getvar.command = this.commands[cmd];
|
||||
getvar.name = 'cmd';
|
||||
getvar.name = cmd;
|
||||
const nameAss = new SlashCommandUnnamedArgumentAssignment();
|
||||
nameAss.value = name;
|
||||
getvar.unnamedArgumentList = [nameAss];
|
||||
this.closure.executorList.push(getvar);
|
||||
}
|
||||
// set to temp scoped var
|
||||
const varName = `_PARSER_${uuidv4()}`;
|
||||
const setvar = new SlashCommandExecutor(null); {
|
||||
const varName = `_PARSER_VAR_${uuidv4()}`;
|
||||
const setvar = new SlashCommandExecutor(startIdx); {
|
||||
setvar.end = endIdx;
|
||||
setvar.command = this.commands['let'];
|
||||
setvar.name = 'let';
|
||||
const nameAss = new SlashCommandUnnamedArgumentAssignment();
|
||||
|
@ -551,7 +668,8 @@ export class SlashCommandParser {
|
|||
this.closure.executorList.push(setvar);
|
||||
}
|
||||
// return pipe
|
||||
const returnPipe = new SlashCommandExecutor(null); {
|
||||
const returnPipe = new SlashCommandExecutor(startIdx); {
|
||||
returnPipe.end = endIdx;
|
||||
returnPipe.command = this.commands['return'];
|
||||
returnPipe.name = 'return';
|
||||
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;
|
||||
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.abortController = abortController;
|
||||
this.debugController = debugController;
|
||||
this.text = text;
|
||||
this.index = 0;
|
||||
this.scope = null;
|
||||
|
@ -577,6 +696,7 @@ export class SlashCommandParser {
|
|||
this.commandIndex = [];
|
||||
this.scopeIndex = [];
|
||||
this.macroIndex = [];
|
||||
this.parserContext = uuidv4();
|
||||
const closure = this.parseClosure(true);
|
||||
return closure;
|
||||
}
|
||||
|
@ -604,8 +724,12 @@ export class SlashCommandParser {
|
|||
if (!isRoot) this.take(2); // discard opening {:
|
||||
const textStart = this.index;
|
||||
let closure = new SlashCommandClosure(this.scope);
|
||||
closure.parserContext = this.parserContext;
|
||||
closure.fullText = this.text;
|
||||
closure.abortController = this.abortController;
|
||||
closure.debugController = this.debugController;
|
||||
this.scope = closure.scope;
|
||||
const oldClosure = this.closure;
|
||||
this.closure = closure;
|
||||
this.discardWhitespace();
|
||||
while (this.testNamedArgument()) {
|
||||
|
@ -615,7 +739,9 @@ export class SlashCommandParser {
|
|||
this.discardWhitespace();
|
||||
}
|
||||
while (!this.testClosureEnd()) {
|
||||
if (this.testComment()) {
|
||||
if (this.testBlockComment()) {
|
||||
this.parseBlockComment();
|
||||
} else if (this.testComment()) {
|
||||
this.parseComment();
|
||||
} else if (this.testParserFlag()) {
|
||||
this.parseParserFlag();
|
||||
|
@ -623,6 +749,14 @@ export class SlashCommandParser {
|
|||
const cmd = this.parseRunShorthand();
|
||||
closure.executorList.push(cmd);
|
||||
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()) {
|
||||
const cmd = this.parseCommand();
|
||||
cmd.injectPipe = injectPipe;
|
||||
|
@ -651,14 +785,83 @@ export class SlashCommandParser {
|
|||
}
|
||||
closureIndexEntry.end = this.index - 1;
|
||||
this.scope = closure.scope.parent;
|
||||
this.closure = oldClosure ?? 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() {
|
||||
return this.testSymbol(/\/[/#]/);
|
||||
}
|
||||
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() {
|
||||
const start = this.index + 1;
|
||||
|
@ -761,10 +964,10 @@ export class SlashCommandParser {
|
|||
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;
|
||||
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;
|
||||
if (cmd.name == 'let') {
|
||||
const keyArg = cmd.namedArgumentList.find(it=>it.name == 'key');
|
||||
|
@ -773,6 +976,17 @@ export class SlashCommandParser {
|
|||
} else if (typeof cmd.unnamedArgumentList[0]?.value == 'string') {
|
||||
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()) {
|
||||
|
@ -813,24 +1027,60 @@ export class SlashCommandParser {
|
|||
testUnnamedArgumentEnd() {
|
||||
return this.testCommandEnd();
|
||||
}
|
||||
parseUnnamedArgument(split) {
|
||||
parseUnnamedArgument(split, splitCount = null) {
|
||||
const wasSplit = split;
|
||||
/**@type {SlashCommandClosure|String}*/
|
||||
let value = this.jumpedEscapeSequence ? this.take() : ''; // take the first, already tested, char if it is an escaped one
|
||||
let isList = split;
|
||||
let listValues = [];
|
||||
let listQuoted = []; // keep track of which listValues were quoted
|
||||
/**@type {SlashCommandUnnamedArgumentAssignment}*/
|
||||
let assignment = new SlashCommandUnnamedArgumentAssignment();
|
||||
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()) {
|
||||
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()) {
|
||||
isList = true;
|
||||
if (value.length > 0) {
|
||||
this.indexMacros(this.index - value.length, value);
|
||||
assignment.value = value;
|
||||
listValues.push(assignment);
|
||||
listQuoted.push(false);
|
||||
assignment = new SlashCommandUnnamedArgumentAssignment();
|
||||
assignment.start = this.index;
|
||||
value = '';
|
||||
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 = '';
|
||||
}
|
||||
}
|
||||
assignment.start = this.index;
|
||||
assignment.value = this.parseClosure();
|
||||
|
@ -845,18 +1095,21 @@ export class SlashCommandParser {
|
|||
assignment.value = this.parseQuotedValue();
|
||||
assignment.end = this.index;
|
||||
listValues.push(assignment);
|
||||
listQuoted.push(true);
|
||||
assignment = new SlashCommandUnnamedArgumentAssignment();
|
||||
} else if (this.testListValue()) {
|
||||
assignment.start = this.index;
|
||||
assignment.value = this.parseListValue();
|
||||
assignment.end = this.index;
|
||||
listValues.push(assignment);
|
||||
listQuoted.push(false);
|
||||
assignment = new SlashCommandUnnamedArgumentAssignment();
|
||||
} else if (this.testValue()) {
|
||||
assignment.start = this.index;
|
||||
assignment.value = this.parseValue();
|
||||
assignment.end = this.index;
|
||||
listValues.push(assignment);
|
||||
listQuoted.push(false);
|
||||
assignment = new SlashCommandUnnamedArgumentAssignment();
|
||||
} else {
|
||||
throw new SlashCommandParserError(`Unexpected end of unnamed argument at index ${this.userIndex}.`);
|
||||
|
@ -870,8 +1123,48 @@ export class SlashCommandParser {
|
|||
if (isList && value.length > 0) {
|
||||
assignment.value = value;
|
||||
listValues.push(assignment);
|
||||
listQuoted.push(false);
|
||||
}
|
||||
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;
|
||||
}
|
||||
this.indexMacros(this.index - value.length, value);
|
||||
|
|
|
@ -38,8 +38,10 @@ export class SlashCommandScope {
|
|||
}
|
||||
|
||||
|
||||
setMacro(key, value) {
|
||||
this.macros[key] = value;
|
||||
setMacro(key, value, overwrite = true) {
|
||||
if (overwrite || !this.macroList.find(it=>it.key == key)) {
|
||||
this.macros[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -95,7 +97,7 @@ export class SlashCommandScope {
|
|||
return v ?? '';
|
||||
} else {
|
||||
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) {
|
||||
|
|
|
@ -3,7 +3,7 @@ import { getRequestHeaders } from '../script.js';
|
|||
import { isMobile } from './RossAscends-mods.js';
|
||||
import { collapseNewlines } from './power-user.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.
|
||||
|
@ -1932,3 +1932,75 @@ export function getFreeName(name, list, numberFormatter = (n) => ` #${n}`) {
|
|||
}
|
||||
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 { SlashCommandAbortController } from './slash-commands/SlashCommandAbortController.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 { SlashCommandClosureResult } from './slash-commands/SlashCommandClosureResult.js';
|
||||
import { commonEnumProviders } from './slash-commands/SlashCommandCommonEnumsProvider.js';
|
||||
import { SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js';
|
||||
import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
|
||||
import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';
|
||||
import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js';
|
||||
import { SlashCommandScope } from './slash-commands/SlashCommandScope.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 = {}) {
|
||||
|
@ -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 = {}) {
|
||||
|
@ -348,11 +349,13 @@ async function whileCallback(args, value) {
|
|||
|
||||
if (result && command) {
|
||||
if (command instanceof SlashCommandClosure) {
|
||||
command.breakController = new SlashCommandBreakController();
|
||||
commandResult = await command.execute();
|
||||
} else {
|
||||
commandResult = await executeSubCommands(command, args._scope, args._parserFlags, args._abortController);
|
||||
}
|
||||
if (commandResult.isAborted) break;
|
||||
if (commandResult.isBreak) break;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
@ -390,8 +393,8 @@ async function timesCallback(args, value) {
|
|||
const iterations = Math.min(Number(repeats), isGuardOff ? Number.MAX_SAFE_INTEGER : MAX_LOOPS);
|
||||
let result;
|
||||
for (let i = 0; i < iterations; i++) {
|
||||
/**@type {SlashCommandClosureResult}*/
|
||||
if (command instanceof SlashCommandClosure) {
|
||||
command.breakController = new SlashCommandBreakController();
|
||||
command.scope.setMacro('timesIndex', i);
|
||||
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);
|
||||
}
|
||||
if (result.isAborted) break;
|
||||
if (result.isBreak) break;
|
||||
}
|
||||
|
||||
return result?.pipe ?? '';
|
||||
|
@ -461,7 +465,7 @@ function existsGlobalVariable(name) {
|
|||
* @param {object} args Command arguments
|
||||
* @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
|
||||
/**
|
||||
* @param {string} operand Boolean operand candidate
|
||||
|
@ -510,36 +514,15 @@ function parseBooleanOperands(args) {
|
|||
* @param {string|number} b The right operand
|
||||
* @returns {boolean} True if the rule yields true, false otherwise
|
||||
*/
|
||||
function evalBoolean(rule, a, b) {
|
||||
export function evalBoolean(rule, a, b) {
|
||||
if (!rule) {
|
||||
toastr.warning('The rule must be specified for the boolean comparison.', 'Invalid command');
|
||||
throw new Error('Invalid command.');
|
||||
}
|
||||
|
||||
let result = false;
|
||||
|
||||
if (typeof a === 'string' && typeof b !== 'number') {
|
||||
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') {
|
||||
if (typeof a === 'number' && typeof b === 'number') {
|
||||
// only do numeric comparison if both operands are numbers
|
||||
const aNumber = Number(a);
|
||||
const bNumber = Number(b);
|
||||
|
||||
|
@ -569,6 +552,38 @@ function evalBoolean(rule, a, b) {
|
|||
toastr.error('Unknown boolean comparison rule for type number.', '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;
|
||||
|
@ -783,24 +798,29 @@ function randValuesCallback(from, to, args) {
|
|||
* @returns The variable's value
|
||||
*/
|
||||
function letCallback(args, value) {
|
||||
if (Array.isArray(value)) {
|
||||
args._scope.letVariable(value[0], typeof value[1] == 'string' ? value.slice(1).join(' ') : value[1]);
|
||||
return value[1];
|
||||
}
|
||||
if (!Array.isArray(value)) value = [value];
|
||||
if (args.key !== undefined) {
|
||||
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);
|
||||
return val;
|
||||
} else {
|
||||
args._scope.letVariable(key);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
const key = value.shift();
|
||||
if (typeof key != 'string') throw new Error('Key must be a string');
|
||||
if (value.length > 0) {
|
||||
const val = typeof value[0] == 'string' ? value.join(' ') : value[0];
|
||||
args._scope.letVariable(key, 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.split(' ')[0];
|
||||
const val = value.split(' ').slice(1).join(' ');
|
||||
args._scope.letVariable(key, val);
|
||||
return val;
|
||||
}
|
||||
args._scope.letVariable(value);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -813,8 +833,9 @@ function varCallback(args, value) {
|
|||
if (!Array.isArray(value)) value = [value];
|
||||
if (args.key !== undefined) {
|
||||
const key = args.key;
|
||||
if (typeof key != 'string') throw new Error('Key must be a string');
|
||||
if (args._hasUnnamedArgument) {
|
||||
const val = value.join(' ');
|
||||
const val = typeof value[0] == 'string' ? value.join(' ') : value[0];
|
||||
args._scope.setVariable(key, val, args.index);
|
||||
return val;
|
||||
} else {
|
||||
|
@ -822,8 +843,9 @@ function varCallback(args, value) {
|
|||
}
|
||||
}
|
||||
const key = value.shift();
|
||||
if (typeof key != 'string') throw new Error('Key must be a string');
|
||||
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);
|
||||
return val;
|
||||
} else {
|
||||
|
@ -1382,6 +1404,7 @@ export function registerVariableCommands() {
|
|||
),
|
||||
],
|
||||
splitUnnamedArgument: true,
|
||||
splitUnnamedArgumentCount: 1,
|
||||
helpString: `
|
||||
<div>
|
||||
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({
|
||||
name: 'add',
|
||||
callback: addValuesCallback,
|
||||
callback: (args, /**@type {string[]}*/value) => addValuesCallback(args, value.join(' ')),
|
||||
returns: 'sum of the provided values',
|
||||
unnamedArgumentList: [
|
||||
SlashCommandArgument.fromProps({
|
||||
|
@ -1467,10 +1490,32 @@ export function registerVariableCommands() {
|
|||
typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
|
||||
isRequired: 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,
|
||||
}),
|
||||
],
|
||||
splitUnnamedArgument: true,
|
||||
helpString: `
|
||||
<div>
|
||||
Performs an addition of the set of values and passes the result down the pipe.
|
||||
|
@ -2001,6 +2046,7 @@ export function registerVariableCommands() {
|
|||
),
|
||||
],
|
||||
splitUnnamedArgument: true,
|
||||
splitUnnamedArgumentCount: 1,
|
||||
helpString: `
|
||||
<div>
|
||||
Get or set a variable.
|
||||
|
@ -2043,6 +2089,7 @@ export function registerVariableCommands() {
|
|||
),
|
||||
],
|
||||
splitUnnamedArgument: true,
|
||||
splitUnnamedArgumentCount: 1,
|
||||
helpString: `
|
||||
<div>
|
||||
Declares a new variable in the current scope.
|
||||
|
|
111
public/style.css
111
public/style.css
|
@ -1392,6 +1392,7 @@ body[data-stscript-style="dark"] {
|
|||
--ac-style-color-punctuationL2: rgba(98 160 251 / 1);
|
||||
--ac-style-color-currentParenthesis: rgba(195 118 210 / 1);
|
||||
--ac-style-color-comment: rgba(122 151 90 / 1);
|
||||
--ac-style-color-keyword: rgba(182 137 190 / 1);
|
||||
}
|
||||
|
||||
body[data-stscript-style="light"] {
|
||||
|
@ -1413,6 +1414,7 @@ body[data-stscript-style="light"] {
|
|||
--ac-style-color-variable: rgba(16 24 125 / 1);
|
||||
--ac-style-color-currentParenthesis: rgba(195 118 210 / 1);
|
||||
--ac-style-color-comment: rgba(70 126 26 / 1);
|
||||
--ac-style-color-keyword: rgba(182 137 190 / 1);
|
||||
}
|
||||
|
||||
body[data-stscript-style="theme"] {
|
||||
|
@ -1433,6 +1435,7 @@ body[data-stscript-style="theme"] {
|
|||
--ac-style-color-variable: rgba(131 193 252 / 1);
|
||||
--ac-style-color-currentParenthesis: rgba(195 118 210 / 1);
|
||||
--ac-style-color-comment: rgba(122 151 90 / 1);
|
||||
--ac-style-color-keyword: rgba(182 137 190 / 1);
|
||||
}
|
||||
|
||||
body {
|
||||
|
@ -1463,6 +1466,7 @@ body {
|
|||
--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-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);
|
||||
|
||||
|
@ -1563,16 +1567,25 @@ body[data-stscript-style] .hljs.language-stscript {
|
|||
color: var(--ac-style-color-punctuation);
|
||||
}
|
||||
|
||||
.hljs-keyword {
|
||||
color: var(--ac-style-color-variableLanguage);
|
||||
}
|
||||
|
||||
.hljs-comment {
|
||||
color: var(--ac-style-color-comment);
|
||||
}
|
||||
|
||||
.hljs-abort {
|
||||
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 {
|
||||
|
@ -1741,7 +1754,12 @@ body[data-stscript-style] .hljs.language-stscript {
|
|||
padding: 0.25em 0.25em 0.5em 0.25em;
|
||||
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;
|
||||
color: var(--ac-color-text);
|
||||
cursor: help;
|
||||
|
@ -1750,6 +1768,35 @@ body[data-stscript-style] .hljs.language-stscript {
|
|||
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 {
|
||||
flex-direction: column;
|
||||
|
@ -1837,7 +1884,8 @@ body[data-stscript-style] .hljs.language-stscript {
|
|||
|
||||
>code {
|
||||
display: block;
|
||||
padding: 0;
|
||||
padding: 1px;
|
||||
tab-size: 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3281,6 +3329,11 @@ grammarly-extension {
|
|||
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,
|
||||
.form_create_bottom_buttons_block .menu_button {
|
||||
padding: 5px;
|
||||
|
@ -5290,3 +5343,49 @@ body:not(.movingUI) .drawer-content.maximized {
|
|||
.regex-highlight {
|
||||
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