Merge pull request #2415 from LenAnderson/parser-followup-2 Parser followup 2
Signed| @@ -0,0 +1,149 @@ | |||
| 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||
| 2 | <!-- Created with Inkscape (http://www.inkscape.org/) --> | ||
| 3 | |||
| 4 | <svg | ||
| 5 | width="48" | ||
| 6 | height="48" | ||
| 7 | viewBox="0 0 48 48" | ||
| 8 | version="1.1" | ||
| 9 | id="svg2120" | ||
| 10 | xml:space="preserve" | ||
| 11 | inkscape:version="1.2.2 (732a01da63, 2022-12-09)" | ||
| 12 | sodipodi:docname="step-into.svg" | ||
| 13 | xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | ||
| 14 | xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | ||
| 15 | xmlns:xlink="http://www.w3.org/1999/xlink" | ||
| 16 | xmlns="http://www.w3.org/2000/svg" | ||
| 17 | xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview | ||
| 18 | id="namedview2122" | ||
| 19 | pagecolor="#ffffff" | ||
| 20 | bordercolor="#000000" | ||
| 21 | borderopacity="0.25" | ||
| 22 | inkscape:showpageshadow="2" | ||
| 23 | inkscape:pageopacity="0.0" | ||
| 24 | inkscape:pagecheckerboard="0" | ||
| 25 | inkscape:deskcolor="#d1d1d1" | ||
| 26 | inkscape:document-units="px" | ||
| 27 | showgrid="false" | ||
| 28 | inkscape:zoom="11.313708" | ||
| 29 | inkscape:cx="58.910834" | ||
| 30 | inkscape:cy="25.323262" | ||
| 31 | inkscape:window-width="1920" | ||
| 32 | inkscape:window-height="992" | ||
| 33 | inkscape:window-x="-8" | ||
| 34 | inkscape:window-y="-8" | ||
| 35 | inkscape:window-maximized="1" | ||
| 36 | inkscape:current-layer="g2714" /><defs | ||
| 37 | id="defs2117"><inkscape:path-effect | ||
| 38 | effect="spiro" | ||
| 39 | id="path-effect2144" | ||
| 40 | is_visible="true" | ||
| 41 | lpeversion="1" /><inkscape:path-effect | ||
| 42 | effect="bspline" | ||
| 43 | id="path-effect2138" | ||
| 44 | is_visible="true" | ||
| 45 | lpeversion="1" | ||
| 46 | weight="33.333333" | ||
| 47 | steps="2" | ||
| 48 | helper_size="0" | ||
| 49 | apply_no_weight="true" | ||
| 50 | apply_with_weight="true" | ||
| 51 | only_selected="false" /></defs><g | ||
| 52 | inkscape:groupmode="layer" | ||
| 53 | id="layer4" | ||
| 54 | inkscape:label="img" | ||
| 55 | style="display:none"><image | ||
| 56 | width="305.68866" | ||
| 57 | height="70.374367" | ||
| 58 | preserveAspectRatio="none" | ||
| 59 | xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAARYAAABACAYAAADf7VgRAAAABHNCSVQICAgIfAhkiAAABdxJREFU | ||
| 60 | eJzt3T9vo0gYBvB3by+SsZRiKCI5SCkyJS6dzny3+26UpoSSFJFMOaTyUETaK3zDAQYbzGBgeH7S | ||
| 61 | SutN1kKj8cM7fxj/2u/3fwgAQKO/xr4AADAPggUAtEOwAIB2CBYA0A7BAgDaIVgAQDsECwBoh2AB | ||
| 62 | AO0QLACgHYIFALRDsACAdn+PfQFw5nkeWZZFRERSSvJ9f+Qrmg7XdSnLMorjeOxLgZYQLDBpruuS | ||
| 63 | 4zj5a4TLPGAoBJNVDRXOOXHOR7wiaAvBApNUDRUF4TIPCBaYnKZQURAu04dggUm5FSoKwmXaECww | ||
| 64 | GW1DRUG4TNfDV4VUx1mtViSEICKiLMtIStn4f4qdJ8syOh6Pw14kPFxdqAghyLKs0jI8EeWvif7v | ||
| 65 | G1gtmpbBg8V1XbJtm4jKHYKoHBhCCIrjuDZkqr+HYDFLU6gEQUCe55X+3ff90p4fIoTLFA0WLI7j | ||
| 66 | kOu6rX/ftm2ybZuEEBRF0dUKBsxxLVSaIFymT/scC2OMdrtdp1Apsm2bPM8jzvlFhQPmybKs9PpW | ||
| 67 | qCi+71/cfKrvZQrXdYkxNvZldKK1YmGM0cfHR+3PhBAkpaTv728iIjqdTmTbNq1WK7Jtu3aYxBij | ||
| 68 | KIp0XuLoXNelz8/PzhWZZVnkuq5x1ZyqMDjnrUNFKVYuURQZOUTe7XZ5NR+GIaVpOvYltaItWCzL | ||
| 69 | qg0VNXdS1yDFf2OM0Xa7LQWMbdt3Vz5TVOwkQRC0DgjLsvK5BlPD5d5Jed/3yXEcI0OlOj+53W5n | ||
| 70 | Ey5ahkLFjq9IKelwOFAQBK0aIk1T8n3/ooOohp27aifZ7XathnrVtjUtbJU+wWBiqBDRRWWrwqXv | ||
| 71 | sKi40jYULRVLtaP3eTo3iiLKssy4/QlJkpSGfCpcrlUudYFNhAnKpZBSUhAEpZvQvZWLGkoXb9RS | ||
| 72 | SkqSZJD+1Lti4ZxfVBVhGPZ6zziOjfvwpGlKYRhe3IGaKpemUDkcDrMohUEPFS59KhfOOXmed/E5 | ||
| 73 | tSwr/5nuCqZ3xfL6+lp63bXjq0naJVDhUpxLqgsXhAoU9alcGGM3q3/VB3WeAdSrYqmO1aSUnTs+ | ||
| 74 | Yyyf0Kz+MVFT5XILQmXZ7q1cmlZpqyzL6vQ4xS29KpZqtZIkSa+LWYq6yuUahIo56irRLqr95Vrl | ||
| 75 | 0nUksNlstE2EawsWKaVx8yJDahsuCBWzDLEa0xQu6/W68/vo0itYdIzJumyIMs2tcEGoQFt1/We1 | ||
| 76 | WvV+j3vhzNuRNYULQsVMfTc2Nn346/qLEKLTtg2d+4FGOzbhdDrlz3aYtIv0HtVwQaiYq0+V33W1 | ||
| 77 | UJ0U0LYS0fms1cOCpelQHjzNfKbCRf0dzqSUed9Ych+5ZwuClJLCMGy1MqQevdHl136//6Pt3Rqo | ||
| 78 | Z2SuMfUhMoC++u5runXSngognTe0wSuWup25dd7f3/MnoAHgTMdmyTiOKUmSh27pHzRY1Jbhtr/7 | ||
| 79 | +vqKJWuA/+jcga022FWP+RzKpFaFlrK1vw5jjNbrdX4W8K1zgJdiye1S9xR734n9R7XdoMFS3Zl7 | ||
| 80 | i6nb+K+pOxyLcz7IuHdO0C7necfi8GVOq4W/397e/hnqzdfrNb28vLT+fSklfX19DXU5k8M5p+12 | ||
| 81 | W/uzp6enfGl+Lp1JF7TL2c/PD6VpSs/Pz7ML00G/V0h9vUdbSylxidrPPy3t7F+0S5maG5lTqBAN | ||
| 82 | HCzFPQhtLGnitsspcCaeGNcE7WKGwb8Jse3ZrkKI2aVyH13mk+oOGzcV2sUMgweLmmy7Fi5dT2ef | ||
| 83 | O3wY6qFdzPGQ5WZ1ULY6LU4dEHU8HilJkkVVKvda0vxTF2iXaXroPpYlzaFco+ae2t6hl/LhQbuY | ||
| 84 | Y/ChENTrctrekk7mQ7uYAcEykjiOW91xl3YyH9rFDAiWEd1aMRNC9P4qlTlCu8zfQ45NgOscx6HN | ||
| 85 | ZlOa1M6ybPF3ZLTLfCFYAEA7DIUAQDsECwBoh2ABAO0QLACgHYIFALRDsACAdggWANAOwQIA2iFY | ||
| 86 | AEA7BAsAaIdgAQDt/gUoDXNStc/rMQAAAABJRU5ErkJggg== | ||
| 87 | " | ||
| 88 | id="image2132" | ||
| 89 | x="-82" | ||
| 90 | y="-11.9" /></g><g | ||
| 91 | inkscape:groupmode="layer" | ||
| 92 | id="layer5" | ||
| 93 | inkscape:label="dot" /><g | ||
| 94 | inkscape:label="over" | ||
| 95 | inkscape:groupmode="layer" | ||
| 96 | id="layer1" | ||
| 97 | style="display:none"><ellipse | ||
| 98 | style="fill:#000000;stroke:#000000;stroke-width:5.27982;stroke-dasharray:none" | ||
| 99 | id="path2637" | ||
| 100 | cx="24" | ||
| 101 | cy="33" | ||
| 102 | rx="4.3600898" | ||
| 103 | ry="4.3600893" /><path | ||
| 104 | style="fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none" | ||
| 105 | 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" | ||
| 106 | id="path2142" | ||
| 107 | inkscape:path-effect="#path-effect2144" | ||
| 108 | 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" | ||
| 109 | sodipodi:nodetypes="csc" /><path | ||
| 110 | style="fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none" | ||
| 111 | d="M 26,22 H 39 V 9" | ||
| 112 | id="path2626" | ||
| 113 | sodipodi:nodetypes="ccc" /></g><g | ||
| 114 | inkscape:groupmode="layer" | ||
| 115 | id="layer6" | ||
| 116 | inkscape:label="into" | ||
| 117 | style="display:inline"><ellipse | ||
| 118 | style="fill:#000000;stroke:#000000;stroke-width:5.27982;stroke-dasharray:none" | ||
| 119 | id="path2637-3" | ||
| 120 | cx="24" | ||
| 121 | cy="38.5" | ||
| 122 | rx="4.3600898" | ||
| 123 | ry="4.3600893" /><path | ||
| 124 | style="fill:#000000;stroke:#000000;stroke-width:5;stroke-dasharray:none" | ||
| 125 | d="M 24,2 V 24" | ||
| 126 | id="path2668" | ||
| 127 | sodipodi:nodetypes="cc" /><path | ||
| 128 | style="fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none" | ||
| 129 | d="M 14.807612,16.994936 24,26.187324 33.192388,16.994936" | ||
| 130 | id="path2626-8" | ||
| 131 | sodipodi:nodetypes="ccc" /></g><g | ||
| 132 | inkscape:groupmode="layer" | ||
| 133 | id="g2714" | ||
| 134 | inkscape:label="out" | ||
| 135 | style="display:none"><ellipse | ||
| 136 | style="fill:#000000;stroke:#000000;stroke-width:5.27982;stroke-dasharray:none" | ||
| 137 | id="ellipse2708" | ||
| 138 | cx="24" | ||
| 139 | cy="38.5" | ||
| 140 | rx="4.3600898" | ||
| 141 | ry="4.3600893" /><path | ||
| 142 | style="fill:#000000;stroke:#000000;stroke-width:5;stroke-dasharray:none" | ||
| 143 | d="M 24,29.722858 V 7.7228579" | ||
| 144 | id="path2710" | ||
| 145 | sodipodi:nodetypes="cc" /><path | ||
| 146 | style="display:inline;fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none" | ||
| 147 | d="M 33.192388,14.727922 24,5.5355339 14.807612,14.727922" | ||
| 148 | id="path2712" | ||
| 149 | sodipodi:nodetypes="ccc" /></g></svg> | ||
| @@ -0,0 +1,149 @@ | |||
| 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||
| 2 | <!-- Created with Inkscape (http://www.inkscape.org/) --> | ||
| 3 | |||
| 4 | <svg | ||
| 5 | width="48" | ||
| 6 | height="48" | ||
| 7 | viewBox="0 0 48 48" | ||
| 8 | version="1.1" | ||
| 9 | id="svg2120" | ||
| 10 | xml:space="preserve" | ||
| 11 | inkscape:version="1.2.2 (732a01da63, 2022-12-09)" | ||
| 12 | sodipodi:docname="step-out.svg" | ||
| 13 | xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | ||
| 14 | xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | ||
| 15 | xmlns:xlink="http://www.w3.org/1999/xlink" | ||
| 16 | xmlns="http://www.w3.org/2000/svg" | ||
| 17 | xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview | ||
| 18 | id="namedview2122" | ||
| 19 | pagecolor="#ffffff" | ||
| 20 | bordercolor="#000000" | ||
| 21 | borderopacity="0.25" | ||
| 22 | inkscape:showpageshadow="2" | ||
| 23 | inkscape:pageopacity="0.0" | ||
| 24 | inkscape:pagecheckerboard="0" | ||
| 25 | inkscape:deskcolor="#d1d1d1" | ||
| 26 | inkscape:document-units="px" | ||
| 27 | showgrid="false" | ||
| 28 | inkscape:zoom="11.313708" | ||
| 29 | inkscape:cx="58.910834" | ||
| 30 | inkscape:cy="25.323262" | ||
| 31 | inkscape:window-width="1920" | ||
| 32 | inkscape:window-height="992" | ||
| 33 | inkscape:window-x="-8" | ||
| 34 | inkscape:window-y="-8" | ||
| 35 | inkscape:window-maximized="1" | ||
| 36 | inkscape:current-layer="g2714" /><defs | ||
| 37 | id="defs2117"><inkscape:path-effect | ||
| 38 | effect="spiro" | ||
| 39 | id="path-effect2144" | ||
| 40 | is_visible="true" | ||
| 41 | lpeversion="1" /><inkscape:path-effect | ||
| 42 | effect="bspline" | ||
| 43 | id="path-effect2138" | ||
| 44 | is_visible="true" | ||
| 45 | lpeversion="1" | ||
| 46 | weight="33.333333" | ||
| 47 | steps="2" | ||
| 48 | helper_size="0" | ||
| 49 | apply_no_weight="true" | ||
| 50 | apply_with_weight="true" | ||
| 51 | only_selected="false" /></defs><g | ||
| 52 | inkscape:groupmode="layer" | ||
| 53 | id="layer4" | ||
| 54 | inkscape:label="img" | ||
| 55 | style="display:none"><image | ||
| 56 | width="305.68866" | ||
| 57 | height="70.374367" | ||
| 58 | preserveAspectRatio="none" | ||
| 59 | xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAARYAAABACAYAAADf7VgRAAAABHNCSVQICAgIfAhkiAAABdxJREFU | ||
| 60 | eJzt3T9vo0gYBvB3by+SsZRiKCI5SCkyJS6dzny3+26UpoSSFJFMOaTyUETaK3zDAQYbzGBgeH7S | ||
| 61 | SutN1kKj8cM7fxj/2u/3fwgAQKO/xr4AADAPggUAtEOwAIB2CBYA0A7BAgDaIVgAQDsECwBoh2AB | ||
| 62 | AO0QLACgHYIFALRDsACAdn+PfQFw5nkeWZZFRERSSvJ9f+Qrmg7XdSnLMorjeOxLgZYQLDBpruuS | ||
| 63 | 4zj5a4TLPGAoBJNVDRXOOXHOR7wiaAvBApNUDRUF4TIPCBaYnKZQURAu04dggUm5FSoKwmXaECww | ||
| 64 | GW1DRUG4TNfDV4VUx1mtViSEICKiLMtIStn4f4qdJ8syOh6Pw14kPFxdqAghyLKs0jI8EeWvif7v | ||
| 65 | G1gtmpbBg8V1XbJtm4jKHYKoHBhCCIrjuDZkqr+HYDFLU6gEQUCe55X+3ff90p4fIoTLFA0WLI7j | ||
| 66 | kOu6rX/ftm2ybZuEEBRF0dUKBsxxLVSaIFymT/scC2OMdrtdp1Apsm2bPM8jzvlFhQPmybKs9PpW | ||
| 67 | qCi+71/cfKrvZQrXdYkxNvZldKK1YmGM0cfHR+3PhBAkpaTv728iIjqdTmTbNq1WK7Jtu3aYxBij | ||
| 68 | KIp0XuLoXNelz8/PzhWZZVnkuq5x1ZyqMDjnrUNFKVYuURQZOUTe7XZ5NR+GIaVpOvYltaItWCzL | ||
| 69 | qg0VNXdS1yDFf2OM0Xa7LQWMbdt3Vz5TVOwkQRC0DgjLsvK5BlPD5d5Jed/3yXEcI0OlOj+53W5n | ||
| 70 | Ey5ahkLFjq9IKelwOFAQBK0aIk1T8n3/ooOohp27aifZ7XathnrVtjUtbJU+wWBiqBDRRWWrwqXv | ||
| 71 | sKi40jYULRVLtaP3eTo3iiLKssy4/QlJkpSGfCpcrlUudYFNhAnKpZBSUhAEpZvQvZWLGkoXb9RS | ||
| 72 | SkqSZJD+1Lti4ZxfVBVhGPZ6zziOjfvwpGlKYRhe3IGaKpemUDkcDrMohUEPFS59KhfOOXmed/E5 | ||
| 73 | tSwr/5nuCqZ3xfL6+lp63bXjq0naJVDhUpxLqgsXhAoU9alcGGM3q3/VB3WeAdSrYqmO1aSUnTs+ | ||
| 74 | Yyyf0Kz+MVFT5XILQmXZ7q1cmlZpqyzL6vQ4xS29KpZqtZIkSa+LWYq6yuUahIo56irRLqr95Vrl | ||
| 75 | 0nUksNlstE2EawsWKaVx8yJDahsuCBWzDLEa0xQu6/W68/vo0itYdIzJumyIMs2tcEGoQFt1/We1 | ||
| 76 | WvV+j3vhzNuRNYULQsVMfTc2Nn346/qLEKLTtg2d+4FGOzbhdDrlz3aYtIv0HtVwQaiYq0+V33W1 | ||
| 77 | UJ0U0LYS0fms1cOCpelQHjzNfKbCRf0dzqSUed9Ych+5ZwuClJLCMGy1MqQevdHl136//6Pt3Rqo | ||
| 78 | Z2SuMfUhMoC++u5runXSngognTe0wSuWup25dd7f3/MnoAHgTMdmyTiOKUmSh27pHzRY1Jbhtr/7 | ||
| 79 | +vqKJWuA/+jcga022FWP+RzKpFaFlrK1vw5jjNbrdX4W8K1zgJdiye1S9xR734n9R7XdoMFS3Zl7 | ||
| 80 | i6nb+K+pOxyLcz7IuHdO0C7necfi8GVOq4W/397e/hnqzdfrNb28vLT+fSklfX19DXU5k8M5p+12 | ||
| 81 | W/uzp6enfGl+Lp1JF7TL2c/PD6VpSs/Pz7ML00G/V0h9vUdbSylxidrPPy3t7F+0S5maG5lTqBAN | ||
| 82 | HCzFPQhtLGnitsspcCaeGNcE7WKGwb8Jse3ZrkKI2aVyH13mk+oOGzcV2sUMgweLmmy7Fi5dT2ef | ||
| 83 | O3wY6qFdzPGQ5WZ1ULY6LU4dEHU8HilJkkVVKvda0vxTF2iXaXroPpYlzaFco+ae2t6hl/LhQbuY | ||
| 84 | Y/ChENTrctrekk7mQ7uYAcEykjiOW91xl3YyH9rFDAiWEd1aMRNC9P4qlTlCu8zfQ45NgOscx6HN | ||
| 85 | ZlOa1M6ybPF3ZLTLfCFYAEA7DIUAQDsECwBoh2ABAO0QLACgHYIFALRDsACAdggWANAOwQIA2iFY | ||
| 86 | AEA7BAsAaIdgAQDt/gUoDXNStc/rMQAAAABJRU5ErkJggg== | ||
| 87 | " | ||
| 88 | id="image2132" | ||
| 89 | x="-82" | ||
| 90 | y="-11.9" /></g><g | ||
| 91 | inkscape:groupmode="layer" | ||
| 92 | id="layer5" | ||
| 93 | inkscape:label="dot" /><g | ||
| 94 | inkscape:label="over" | ||
| 95 | inkscape:groupmode="layer" | ||
| 96 | id="layer1" | ||
| 97 | style="display:none"><ellipse | ||
| 98 | style="fill:#000000;stroke:#000000;stroke-width:5.27982;stroke-dasharray:none" | ||
| 99 | id="path2637" | ||
| 100 | cx="24" | ||
| 101 | cy="33" | ||
| 102 | rx="4.3600898" | ||
| 103 | ry="4.3600893" /><path | ||
| 104 | style="fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none" | ||
| 105 | 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" | ||
| 106 | id="path2142" | ||
| 107 | inkscape:path-effect="#path-effect2144" | ||
| 108 | 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" | ||
| 109 | sodipodi:nodetypes="csc" /><path | ||
| 110 | style="fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none" | ||
| 111 | d="M 26,22 H 39 V 9" | ||
| 112 | id="path2626" | ||
| 113 | sodipodi:nodetypes="ccc" /></g><g | ||
| 114 | inkscape:groupmode="layer" | ||
| 115 | id="layer6" | ||
| 116 | inkscape:label="into" | ||
| 117 | style="display:none"><ellipse | ||
| 118 | style="fill:#000000;stroke:#000000;stroke-width:5.27982;stroke-dasharray:none" | ||
| 119 | id="path2637-3" | ||
| 120 | cx="24" | ||
| 121 | cy="38.5" | ||
| 122 | rx="4.3600898" | ||
| 123 | ry="4.3600893" /><path | ||
| 124 | style="fill:#000000;stroke:#000000;stroke-width:5;stroke-dasharray:none" | ||
| 125 | d="M 24,2 V 24" | ||
| 126 | id="path2668" | ||
| 127 | sodipodi:nodetypes="cc" /><path | ||
| 128 | style="fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none" | ||
| 129 | d="M 14.807612,16.994936 24,26.187324 33.192388,16.994936" | ||
| 130 | id="path2626-8" | ||
| 131 | sodipodi:nodetypes="ccc" /></g><g | ||
| 132 | inkscape:groupmode="layer" | ||
| 133 | id="g2714" | ||
| 134 | inkscape:label="out" | ||
| 135 | style="display:inline"><ellipse | ||
| 136 | style="fill:#000000;stroke:#000000;stroke-width:5.27982;stroke-dasharray:none" | ||
| 137 | id="ellipse2708" | ||
| 138 | cx="24" | ||
| 139 | cy="38.5" | ||
| 140 | rx="4.3600898" | ||
| 141 | ry="4.3600893" /><path | ||
| 142 | style="fill:#000000;stroke:#000000;stroke-width:5;stroke-dasharray:none" | ||
| 143 | d="M 24,29.722858 V 7.7228579" | ||
| 144 | id="path2710" | ||
| 145 | sodipodi:nodetypes="cc" /><path | ||
| 146 | style="display:inline;fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none" | ||
| 147 | d="M 33.192388,14.727922 24,5.5355339 14.807612,14.727922" | ||
| 148 | id="path2712" | ||
| 149 | sodipodi:nodetypes="ccc" /></g></svg> | ||
| @@ -0,0 +1,149 @@ | |||
| 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||
| 2 | <!-- Created with Inkscape (http://www.inkscape.org/) --> | ||
| 3 | |||
| 4 | <svg | ||
| 5 | width="48" | ||
| 6 | height="48" | ||
| 7 | viewBox="0 0 48 48" | ||
| 8 | version="1.1" | ||
| 9 | id="svg2120" | ||
| 10 | xml:space="preserve" | ||
| 11 | inkscape:version="1.2.2 (732a01da63, 2022-12-09)" | ||
| 12 | sodipodi:docname="step-over.svg" | ||
| 13 | xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | ||
| 14 | xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | ||
| 15 | xmlns:xlink="http://www.w3.org/1999/xlink" | ||
| 16 | xmlns="http://www.w3.org/2000/svg" | ||
| 17 | xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview | ||
| 18 | id="namedview2122" | ||
| 19 | pagecolor="#ffffff" | ||
| 20 | bordercolor="#000000" | ||
| 21 | borderopacity="0.25" | ||
| 22 | inkscape:showpageshadow="2" | ||
| 23 | inkscape:pageopacity="0.0" | ||
| 24 | inkscape:pagecheckerboard="0" | ||
| 25 | inkscape:deskcolor="#d1d1d1" | ||
| 26 | inkscape:document-units="px" | ||
| 27 | showgrid="false" | ||
| 28 | inkscape:zoom="11.313708" | ||
| 29 | inkscape:cx="58.910834" | ||
| 30 | inkscape:cy="25.323262" | ||
| 31 | inkscape:window-width="1920" | ||
| 32 | inkscape:window-height="992" | ||
| 33 | inkscape:window-x="-8" | ||
| 34 | inkscape:window-y="-8" | ||
| 35 | inkscape:window-maximized="1" | ||
| 36 | inkscape:current-layer="g2714" /><defs | ||
| 37 | id="defs2117"><inkscape:path-effect | ||
| 38 | effect="spiro" | ||
| 39 | id="path-effect2144" | ||
| 40 | is_visible="true" | ||
| 41 | lpeversion="1" /><inkscape:path-effect | ||
| 42 | effect="bspline" | ||
| 43 | id="path-effect2138" | ||
| 44 | is_visible="true" | ||
| 45 | lpeversion="1" | ||
| 46 | weight="33.333333" | ||
| 47 | steps="2" | ||
| 48 | helper_size="0" | ||
| 49 | apply_no_weight="true" | ||
| 50 | apply_with_weight="true" | ||
| 51 | only_selected="false" /></defs><g | ||
| 52 | inkscape:groupmode="layer" | ||
| 53 | id="layer4" | ||
| 54 | inkscape:label="img" | ||
| 55 | style="display:none"><image | ||
| 56 | width="305.68866" | ||
| 57 | height="70.374367" | ||
| 58 | preserveAspectRatio="none" | ||
| 59 | xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAARYAAABACAYAAADf7VgRAAAABHNCSVQICAgIfAhkiAAABdxJREFU | ||
| 60 | eJzt3T9vo0gYBvB3by+SsZRiKCI5SCkyJS6dzny3+26UpoSSFJFMOaTyUETaK3zDAQYbzGBgeH7S | ||
| 61 | SutN1kKj8cM7fxj/2u/3fwgAQKO/xr4AADAPggUAtEOwAIB2CBYA0A7BAgDaIVgAQDsECwBoh2AB | ||
| 62 | AO0QLACgHYIFALRDsACAdn+PfQFw5nkeWZZFRERSSvJ9f+Qrmg7XdSnLMorjeOxLgZYQLDBpruuS | ||
| 63 | 4zj5a4TLPGAoBJNVDRXOOXHOR7wiaAvBApNUDRUF4TIPCBaYnKZQURAu04dggUm5FSoKwmXaECww | ||
| 64 | GW1DRUG4TNfDV4VUx1mtViSEICKiLMtIStn4f4qdJ8syOh6Pw14kPFxdqAghyLKs0jI8EeWvif7v | ||
| 65 | G1gtmpbBg8V1XbJtm4jKHYKoHBhCCIrjuDZkqr+HYDFLU6gEQUCe55X+3ff90p4fIoTLFA0WLI7j | ||
| 66 | kOu6rX/ftm2ybZuEEBRF0dUKBsxxLVSaIFymT/scC2OMdrtdp1Apsm2bPM8jzvlFhQPmybKs9PpW | ||
| 67 | qCi+71/cfKrvZQrXdYkxNvZldKK1YmGM0cfHR+3PhBAkpaTv728iIjqdTmTbNq1WK7Jtu3aYxBij | ||
| 68 | KIp0XuLoXNelz8/PzhWZZVnkuq5x1ZyqMDjnrUNFKVYuURQZOUTe7XZ5NR+GIaVpOvYltaItWCzL | ||
| 69 | qg0VNXdS1yDFf2OM0Xa7LQWMbdt3Vz5TVOwkQRC0DgjLsvK5BlPD5d5Jed/3yXEcI0OlOj+53W5n | ||
| 70 | Ey5ahkLFjq9IKelwOFAQBK0aIk1T8n3/ooOohp27aifZ7XathnrVtjUtbJU+wWBiqBDRRWWrwqXv | ||
| 71 | sKi40jYULRVLtaP3eTo3iiLKssy4/QlJkpSGfCpcrlUudYFNhAnKpZBSUhAEpZvQvZWLGkoXb9RS | ||
| 72 | SkqSZJD+1Lti4ZxfVBVhGPZ6zziOjfvwpGlKYRhe3IGaKpemUDkcDrMohUEPFS59KhfOOXmed/E5 | ||
| 73 | tSwr/5nuCqZ3xfL6+lp63bXjq0naJVDhUpxLqgsXhAoU9alcGGM3q3/VB3WeAdSrYqmO1aSUnTs+ | ||
| 74 | Yyyf0Kz+MVFT5XILQmXZ7q1cmlZpqyzL6vQ4xS29KpZqtZIkSa+LWYq6yuUahIo56irRLqr95Vrl | ||
| 75 | 0nUksNlstE2EawsWKaVx8yJDahsuCBWzDLEa0xQu6/W68/vo0itYdIzJumyIMs2tcEGoQFt1/We1 | ||
| 76 | WvV+j3vhzNuRNYULQsVMfTc2Nn346/qLEKLTtg2d+4FGOzbhdDrlz3aYtIv0HtVwQaiYq0+V33W1 | ||
| 77 | UJ0U0LYS0fms1cOCpelQHjzNfKbCRf0dzqSUed9Ych+5ZwuClJLCMGy1MqQevdHl136//6Pt3Rqo | ||
| 78 | Z2SuMfUhMoC++u5runXSngognTe0wSuWup25dd7f3/MnoAHgTMdmyTiOKUmSh27pHzRY1Jbhtr/7 | ||
| 79 | +vqKJWuA/+jcga022FWP+RzKpFaFlrK1vw5jjNbrdX4W8K1zgJdiye1S9xR734n9R7XdoMFS3Zl7 | ||
| 80 | i6nb+K+pOxyLcz7IuHdO0C7necfi8GVOq4W/397e/hnqzdfrNb28vLT+fSklfX19DXU5k8M5p+12 | ||
| 81 | W/uzp6enfGl+Lp1JF7TL2c/PD6VpSs/Pz7ML00G/V0h9vUdbSylxidrPPy3t7F+0S5maG5lTqBAN | ||
| 82 | HCzFPQhtLGnitsspcCaeGNcE7WKGwb8Jse3ZrkKI2aVyH13mk+oOGzcV2sUMgweLmmy7Fi5dT2ef | ||
| 83 | O3wY6qFdzPGQ5WZ1ULY6LU4dEHU8HilJkkVVKvda0vxTF2iXaXroPpYlzaFco+ae2t6hl/LhQbuY | ||
| 84 | Y/ChENTrctrekk7mQ7uYAcEykjiOW91xl3YyH9rFDAiWEd1aMRNC9P4qlTlCu8zfQ45NgOscx6HN | ||
| 85 | ZlOa1M6ybPF3ZLTLfCFYAEA7DIUAQDsECwBoh2ABAO0QLACgHYIFALRDsACAdggWANAOwQIA2iFY | ||
| 86 | AEA7BAsAaIdgAQDt/gUoDXNStc/rMQAAAABJRU5ErkJggg== | ||
| 87 | " | ||
| 88 | id="image2132" | ||
| 89 | x="-82" | ||
| 90 | y="-11.9" /></g><g | ||
| 91 | inkscape:groupmode="layer" | ||
| 92 | id="layer5" | ||
| 93 | inkscape:label="dot" /><g | ||
| 94 | inkscape:label="over" | ||
| 95 | inkscape:groupmode="layer" | ||
| 96 | id="layer1" | ||
| 97 | style="display:inline"><ellipse | ||
| 98 | style="fill:#000000;stroke:#000000;stroke-width:5.27982;stroke-dasharray:none" | ||
| 99 | id="path2637" | ||
| 100 | cx="24" | ||
| 101 | cy="33" | ||
| 102 | rx="4.3600898" | ||
| 103 | ry="4.3600893" /><path | ||
| 104 | style="fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none" | ||
| 105 | 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" | ||
| 106 | id="path2142" | ||
| 107 | inkscape:path-effect="#path-effect2144" | ||
| 108 | 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" | ||
| 109 | sodipodi:nodetypes="csc" /><path | ||
| 110 | style="fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none" | ||
| 111 | d="M 26,22 H 39 V 9" | ||
| 112 | id="path2626" | ||
| 113 | sodipodi:nodetypes="ccc" /></g><g | ||
| 114 | inkscape:groupmode="layer" | ||
| 115 | id="layer6" | ||
| 116 | inkscape:label="into" | ||
| 117 | style="display:none"><ellipse | ||
| 118 | style="fill:#000000;stroke:#000000;stroke-width:5.27982;stroke-dasharray:none" | ||
| 119 | id="path2637-3" | ||
| 120 | cx="24" | ||
| 121 | cy="38.5" | ||
| 122 | rx="4.3600898" | ||
| 123 | ry="4.3600893" /><path | ||
| 124 | style="fill:#000000;stroke:#000000;stroke-width:5;stroke-dasharray:none" | ||
| 125 | d="M 24,2 V 24" | ||
| 126 | id="path2668" | ||
| 127 | sodipodi:nodetypes="cc" /><path | ||
| 128 | style="fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none" | ||
| 129 | d="M 14.807612,16.994936 24,26.187324 33.192388,16.994936" | ||
| 130 | id="path2626-8" | ||
| 131 | sodipodi:nodetypes="ccc" /></g><g | ||
| 132 | inkscape:groupmode="layer" | ||
| 133 | id="g2714" | ||
| 134 | inkscape:label="out" | ||
| 135 | style="display:none"><ellipse | ||
| 136 | style="fill:#000000;stroke:#000000;stroke-width:5.27982;stroke-dasharray:none" | ||
| 137 | id="ellipse2708" | ||
| 138 | cx="24" | ||
| 139 | cy="38.5" | ||
| 140 | rx="4.3600898" | ||
| 141 | ry="4.3600893" /><path | ||
| 142 | style="fill:#000000;stroke:#000000;stroke-width:5;stroke-dasharray:none" | ||
| 143 | d="M 24,29.722858 V 7.7228579" | ||
| 144 | id="path2710" | ||
| 145 | sodipodi:nodetypes="cc" /><path | ||
| 146 | style="display:inline;fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none" | ||
| 147 | d="M 33.192388,14.727922 24,5.5355339 14.807612,14.727922" | ||
| 148 | id="path2712" | ||
| 149 | sodipodi:nodetypes="ccc" /></g></svg> | ||
| @@ -0,0 +1,218 @@ | |||
| 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||
| 2 | <!-- Created with Inkscape (http://www.inkscape.org/) --> | ||
| 3 | |||
| 4 | <svg | ||
| 5 | width="48" | ||
| 6 | height="48" | ||
| 7 | viewBox="0 0 48 48" | ||
| 8 | version="1.1" | ||
| 9 | id="svg2120" | ||
| 10 | xml:space="preserve" | ||
| 11 | inkscape:version="1.2.2 (732a01da63, 2022-12-09)" | ||
| 12 | sodipodi:docname="step-resume.svg" | ||
| 13 | xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | ||
| 14 | xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | ||
| 15 | xmlns:xlink="http://www.w3.org/1999/xlink" | ||
| 16 | xmlns="http://www.w3.org/2000/svg" | ||
| 17 | xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview | ||
| 18 | id="namedview2122" | ||
| 19 | pagecolor="#ffffff" | ||
| 20 | bordercolor="#000000" | ||
| 21 | borderopacity="0.25" | ||
| 22 | inkscape:showpageshadow="2" | ||
| 23 | inkscape:pageopacity="0.0" | ||
| 24 | inkscape:pagecheckerboard="0" | ||
| 25 | inkscape:deskcolor="#d1d1d1" | ||
| 26 | inkscape:document-units="px" | ||
| 27 | showgrid="false" | ||
| 28 | inkscape:zoom="11.315" | ||
| 29 | inkscape:cx="4.7282369" | ||
| 30 | inkscape:cy="24.259832" | ||
| 31 | inkscape:window-width="1920" | ||
| 32 | inkscape:window-height="992" | ||
| 33 | inkscape:window-x="-8" | ||
| 34 | inkscape:window-y="-8" | ||
| 35 | inkscape:window-maximized="1" | ||
| 36 | inkscape:current-layer="layer8" /><defs | ||
| 37 | id="defs2117"><inkscape:path-effect | ||
| 38 | effect="spiro" | ||
| 39 | id="path-effect2144" | ||
| 40 | is_visible="true" | ||
| 41 | lpeversion="1" /><inkscape:path-effect | ||
| 42 | effect="bspline" | ||
| 43 | id="path-effect2138" | ||
| 44 | is_visible="true" | ||
| 45 | lpeversion="1" | ||
| 46 | weight="33.333333" | ||
| 47 | steps="2" | ||
| 48 | helper_size="0" | ||
| 49 | apply_no_weight="true" | ||
| 50 | apply_with_weight="true" | ||
| 51 | only_selected="false" /></defs><g | ||
| 52 | inkscape:groupmode="layer" | ||
| 53 | id="layer4" | ||
| 54 | inkscape:label="img" | ||
| 55 | style="display:none"><image | ||
| 56 | width="305.68866" | ||
| 57 | height="70.374367" | ||
| 58 | preserveAspectRatio="none" | ||
| 59 | xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAARYAAABACAYAAADf7VgRAAAABHNCSVQICAgIfAhkiAAABdxJREFU | ||
| 60 | eJzt3T9vo0gYBvB3by+SsZRiKCI5SCkyJS6dzny3+26UpoSSFJFMOaTyUETaK3zDAQYbzGBgeH7S | ||
| 61 | SutN1kKj8cM7fxj/2u/3fwgAQKO/xr4AADAPggUAtEOwAIB2CBYA0A7BAgDaIVgAQDsECwBoh2AB | ||
| 62 | AO0QLACgHYIFALRDsACAdn+PfQFw5nkeWZZFRERSSvJ9f+Qrmg7XdSnLMorjeOxLgZYQLDBpruuS | ||
| 63 | 4zj5a4TLPGAoBJNVDRXOOXHOR7wiaAvBApNUDRUF4TIPCBaYnKZQURAu04dggUm5FSoKwmXaECww | ||
| 64 | GW1DRUG4TNfDV4VUx1mtViSEICKiLMtIStn4f4qdJ8syOh6Pw14kPFxdqAghyLKs0jI8EeWvif7v | ||
| 65 | G1gtmpbBg8V1XbJtm4jKHYKoHBhCCIrjuDZkqr+HYDFLU6gEQUCe55X+3ff90p4fIoTLFA0WLI7j | ||
| 66 | kOu6rX/ftm2ybZuEEBRF0dUKBsxxLVSaIFymT/scC2OMdrtdp1Apsm2bPM8jzvlFhQPmybKs9PpW | ||
| 67 | qCi+71/cfKrvZQrXdYkxNvZldKK1YmGM0cfHR+3PhBAkpaTv728iIjqdTmTbNq1WK7Jtu3aYxBij | ||
| 68 | KIp0XuLoXNelz8/PzhWZZVnkuq5x1ZyqMDjnrUNFKVYuURQZOUTe7XZ5NR+GIaVpOvYltaItWCzL | ||
| 69 | qg0VNXdS1yDFf2OM0Xa7LQWMbdt3Vz5TVOwkQRC0DgjLsvK5BlPD5d5Jed/3yXEcI0OlOj+53W5n | ||
| 70 | Ey5ahkLFjq9IKelwOFAQBK0aIk1T8n3/ooOohp27aifZ7XathnrVtjUtbJU+wWBiqBDRRWWrwqXv | ||
| 71 | sKi40jYULRVLtaP3eTo3iiLKssy4/QlJkpSGfCpcrlUudYFNhAnKpZBSUhAEpZvQvZWLGkoXb9RS | ||
| 72 | SkqSZJD+1Lti4ZxfVBVhGPZ6zziOjfvwpGlKYRhe3IGaKpemUDkcDrMohUEPFS59KhfOOXmed/E5 | ||
| 73 | tSwr/5nuCqZ3xfL6+lp63bXjq0naJVDhUpxLqgsXhAoU9alcGGM3q3/VB3WeAdSrYqmO1aSUnTs+ | ||
| 74 | Yyyf0Kz+MVFT5XILQmXZ7q1cmlZpqyzL6vQ4xS29KpZqtZIkSa+LWYq6yuUahIo56irRLqr95Vrl | ||
| 75 | 0nUksNlstE2EawsWKaVx8yJDahsuCBWzDLEa0xQu6/W68/vo0itYdIzJumyIMs2tcEGoQFt1/We1 | ||
| 76 | WvV+j3vhzNuRNYULQsVMfTc2Nn346/qLEKLTtg2d+4FGOzbhdDrlz3aYtIv0HtVwQaiYq0+V33W1 | ||
| 77 | UJ0U0LYS0fms1cOCpelQHjzNfKbCRf0dzqSUed9Ych+5ZwuClJLCMGy1MqQevdHl136//6Pt3Rqo | ||
| 78 | Z2SuMfUhMoC++u5runXSngognTe0wSuWup25dd7f3/MnoAHgTMdmyTiOKUmSh27pHzRY1Jbhtr/7 | ||
| 79 | +vqKJWuA/+jcga022FWP+RzKpFaFlrK1vw5jjNbrdX4W8K1zgJdiye1S9xR734n9R7XdoMFS3Zl7 | ||
| 80 | i6nb+K+pOxyLcz7IuHdO0C7necfi8GVOq4W/397e/hnqzdfrNb28vLT+fSklfX19DXU5k8M5p+12 | ||
| 81 | W/uzp6enfGl+Lp1JF7TL2c/PD6VpSs/Pz7ML00G/V0h9vUdbSylxidrPPy3t7F+0S5maG5lTqBAN | ||
| 82 | HCzFPQhtLGnitsspcCaeGNcE7WKGwb8Jse3ZrkKI2aVyH13mk+oOGzcV2sUMgweLmmy7Fi5dT2ef | ||
| 83 | O3wY6qFdzPGQ5WZ1ULY6LU4dEHU8HilJkkVVKvda0vxTF2iXaXroPpYlzaFco+ae2t6hl/LhQbuY | ||
| 84 | Y/ChENTrctrekk7mQ7uYAcEykjiOW91xl3YyH9rFDAiWEd1aMRNC9P4qlTlCu8zfQ45NgOscx6HN | ||
| 85 | ZlOa1M6ybPF3ZLTLfCFYAEA7DIUAQDsECwBoh2ABAO0QLACgHYIFALRDsACAdggWANAOwQIA2iFY | ||
| 86 | AEA7BAsAaIdgAQDt/gUoDXNStc/rMQAAAABJRU5ErkJggg== | ||
| 87 | " | ||
| 88 | id="image2132" | ||
| 89 | x="-82" | ||
| 90 | y="-11.9" /><image | ||
| 91 | width="460" | ||
| 92 | height="71" | ||
| 93 | preserveAspectRatio="none" | ||
| 94 | xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAcwAAABHCAYAAACK23cpAAAABHNCSVQICAgIfAhkiAAACuZJREFU | ||
| 95 | eJzt3T9o3FgeB/Cv77aZBHInYZPD+JLF4iDxDl44ZNiAvVfNhC2vCEqxWQJpJn3MwlRXTeP0drNF | ||
| 96 | NtfIKa5cYnVxkQMLDoztbU6GgBkwDiPOEHuOK/aK7FM0Gs3M01gzkp6+HzBknPFEkSV93z/9NLO6 | ||
| 97 | uvoLiIiIaKjfZL0BRERERcDAJCIiksDAJCIiksDAJCIiksDAJCIiksDAJCIiksDAJCIiksDAJCIi | ||
| 98 | ksDAJCIiksDAJCIiksDAJCIiksDAJCIikvBZ1htAlIZmswlN0wAAvu+j1WplvEX5YVkWOp0OHMfJ | ||
| 99 | elMoBYuLi8Gfj4+PM9yS8mFgEinMsiyYphm8ZmgSjY9DskSKioZlvV5HrVbLcIuoyEzTDEZxyoqB | ||
| 100 | SaSgaFgKDE0aR6PRCI6pMocmA5NIMYPCUmBoUhKNRgOGYQD4eOyUOTQZmEQKGRWWAkOTZPm+3/O6 | ||
| 101 | zKHJwCRShGxYCgxNkmHbNlzX7fleWUNzqqtk5xaWMbuwHLx+d+Tg4vw0eH3txs2e19RPXBA1TQuW | ||
| 102 | lHc6nb5WYFj4ouj7ft/BT8UXF5ae50HX9Z7bbQD0XOTq9ToArp6l4WzbBoC+RWQA4Lru0OuPSqYa | ||
| 103 | mLMLy1i69yh4/f5kvycgv3nyI47evuwL0jKzLCuYPxjWmvM8D47jxIanOLDF+xiYahkUlltbW2g2 | ||
| 104 | mz3fb7VaPfesAgxNksPQzOGQ7NK9R/jLgw3MhXqiZWSaJjY2NoJhj1FDH4ZhBCvZyjZMUmbDwnKQ | ||
| 105 | VqsV26ji8CyNUvbh2VwWLrh24ya+frCBs5N9uK+fl6q3aRgGarVa0Ksc5+ebzSZ2dnZK0+ors06n | ||
| 106 | 0/N6VFgKcT1NVY8Vy7Lgui48z8t6Uwpr1HVpUE9T0zQ8ffp0rDAVo2F5GhHLXQ8zbG5hGd88+RG3 | ||
| 107 | l2q4duNm1pszcaKXGHdQioPHtm3Yto2tra2hoViv15XsbY77f9I0DY1GQ7n94TgOdnZ2AMiHpRDu | ||
| 108 | acb1HFTQaDRgmmbP1AYlI86dUfsvrqfp+z42NzfHaowZhgHLsnI18pHLHmaUef+Z8r1NcVBGibnJ | ||
| 109 | uNZx+Hvi4AofrOJ7qhAnrWEYiU5CTdOCuTzLsmDbtlK9Kcdxxl7M1Wq1YJqmkmEZnf8Xv3v2NJMJ | ||
| 110 | r4GQfW+4IS9Cc9yeZp7m2H9769atv03rH5tbWMbcH78MXkcX94QXBEVdv3ETf/rzXwEAF+en+N9/ | ||
| 111 | P0xuQ6csfEEXfN/HixcvgovhKL7vY3d3F7quY35+Pvi+rut97yvixdGyLFSrVQBApVJBtVrFwcEB | ||
| 112 | ut0uAGBtbQ2VSgUA0O12sbu7C6B/34r9U8R9MEy73Y79/qD9IvOzRddut1GtVoP/f6VSgWEYaLfb | ||
| 113 | V2owaZqGSqUSHHvTNu1h9MePH/e89jwPvu/3fIWvM4Zh4PLyEr7vB/uo2+3i4OAA8/PzfT876Cv6 | ||
| 114 | mb7vZ36s5raHeXF+ig/np32Lf5buPcLnX9Thvn6Os5P9jLYuXdFe4FWetmHbNjqdTqJWYRG4rgvD | ||
| 115 | MIKLhZgbGdbTjGuIAPloqdLkxfVsxu1pip8LD0v6vo+9vb1SHU+Dhv3j6hYD/T3NJFMG0Tl2y7KC | ||
| 116 | sM5KbucwP5yf4s2r9dhhWLEo6OsHG4Wf24ybSBfLt8cVntdShed5fUOpwxYUDArLra0tDsmVSNwc | ||
| 117 | Wlz4DVOr1dBsNvver2ka6vV634W9jCaxejauMTzusG5acjske3F+indHDv5zdox//+sfuP67P+D3 | ||
| 118 | c70HrBimFUO0RRymffjwYTBkBCS/oNdqteDADH/put43HAsUd0gWQDAkYxhGzzBbtVrtOYkqlQrW | ||
| 119 | 1tb6fr6MYSkzJKs6MRw4zvCszDoAcQxOa99Oe0g2PFo17PpxeHjYNyUUNzwra9DvLTodM0257WFG | ||
| 120 | ua+f482r9dhFP+b9ZzDvPytcbzN6f6Xv+4kv6GIRTNyXigb1NEcpY1jSJ+P2NOMW4sXRNC1RWUJV | ||
| 121 | pd3THPZ7y6Knmds5zDhnJ/v46YfvcPerb/H5F/WegBS3oBSpUlD0BNvb28toS4pFhKbsScOwVEfc | ||
| 122 | MHsS0eNl2Jxm0kanqquNk0q7IpCYK15ZWQl+f4ZhBPt7mnOahelhhv38z78PXPRTpEpBKysrwZ99 | ||
| 123 | 3y/V4oGriutpxmFYqkWMyoz7Negz43qaSXswcVMgZZV2T9NxnL4Ohfi8aSpUDzPsQwF6kKOMuxI2 | ||
| 124 | LMmqM9WM6mkyLEnWoIVjV/2MMptETzNq2o2UQgbm3a++HXjP5rsjB+7r51PeIsrKoNBkWKrpqsNv | ||
| 125 | g0It7ngRTwOSxeHYfmmFZtziK3HuT1OhAnNuYRl37z2KHW49O9nHz29fKnNv5iDiwPN9P6gjqlLV | ||
| 126 | mnFEQ5Nhqa6rjMokvdVIPPlHtucYretLH101NEXJ0LCkZSDTUpjANO8/w+2l+JqC7uvneHek9vyf | ||
| 127 | uH0kSnYuT3Xh1ibD8pNOp8OGFca7L9f3fdi2LbVSVpSwpHjjhmZcydCkBRDSlPvAvL1Ug3n/Wezf | ||
| 128 | qV5fVhhW+Fg8nUTV4tlJMCj7lXmOW7hKEQvP87CzszO0chYX7MlJGppiMVaYaMRkJbeBef3Xaj5x | ||
| 129 | w68X56dKlcYbRvZRX/V6PfOyUUR5k0bFJ8dx4LouS+OlIGloRhf1ZF08P7eBee3GzdhCBGVa1CNK | ||
| 130 | b8m+1zRNnrxEv0qzPKIYBhTzmWVqmIbncXVdly7mkITMU07ysDYht4EZVZZFPVdhGEZpA1MUZtc0 | ||
| 131 | DcfHx8GCjbIr836JK2l31YtuWfZdWKfT6SleP6nbZ4aFpmEYmYclUJDALMOinjhJb8pVtRzeMHEr | ||
| 132 | 6IBPcx15OMmywP2CYOW0OC/y0EMpItu2p1b0fFBo5mV9Rm6LrwMfe5VvXq3j/QR6lbOzs7i4uEj9 | ||
| 133 | c9Ok63rwDEgZ4pmYZVGr1QYWxq5UKkGDI+n9dEXH/fJRt9uF53mYn5/H9va2MmE57eLr3W4X7XYb | ||
| 134 | mqZhZmam52ERk3CVgu2Tlsse5qQX9czOzuL777/H+vr6RD4/LUlP8DLdByY7v1uv16debzJL3C+9 | ||
| 135 | srwFQSWe5020wSHzPM08yF0t2XdHDn764buJhuWTJ08m8tlpE08el1Wm+ctRj1wa971Fx/1CRTSJ | ||
| 136 | 52lOwlR7mO9P9nH09mXwOloP9s2r9Yku6hFhOTs7O7F/I23RJ8YPMukWYN4kma8VC1/y1FKdFO4X | ||
| 137 | Kqq0a89OwlQD8+xkf2ggMiz7iUUawx5llVWZqKzkqcWZJ9wvVHR5D81czmGmrahhKXieh1arFRQx | ||
| 138 | 0HUdmqbBdV24rluqnuW4sj7R8or7hfImz6GpfGAWPSzDHMcp1TzlIGJuV7ZHVZZQ4H4hVeQ1NHO3 | ||
| 139 | 6CdNKoUl9Yo+TDat9xYd9wupIo8LgZQNTIal2hzHkWpllq0wNvcLqWRQaGZVpEXJwGRYlsPm5ubQ | ||
| 140 | cMjiAbN5wP1CKomGpud5mVX+mWqln2lIEpZsYRdbt9vF7u4ufN9HpVIJqpC4rouDgwNsb2+Xcp6O | ||
| 141 | +0Vt0670kweHh4fQdR2Xl5eZ3hEws7q6+ktm/3rKkvYs817ph4iI8kOZVbKi3B0REdEkKNXDJCIi | ||
| 142 | mhQlF/0QERGljYFJREQkgYFJREQkgYFJREQkgYFJREQkgYFJREQkgYFJREQkgYFJREQkgYFJREQk | ||
| 143 | 4bPFxcWst4GIiCj32MMkIiKSMHPnzh3WkiUiIhqBPUwiIiIJDEwiIiIJDEwiIiIJ/weH8+Ed/xCz | ||
| 144 | fAAAAABJRU5ErkJggg== | ||
| 145 | " | ||
| 146 | id="image2845" | ||
| 147 | x="-15.489594" | ||
| 148 | y="-8.2945251" /></g><g | ||
| 149 | inkscape:groupmode="layer" | ||
| 150 | id="layer5" | ||
| 151 | inkscape:label="dot" /><g | ||
| 152 | inkscape:label="over" | ||
| 153 | inkscape:groupmode="layer" | ||
| 154 | id="layer1" | ||
| 155 | style="display:none"><ellipse | ||
| 156 | style="fill:#000000;stroke:#000000;stroke-width:5.27982;stroke-dasharray:none" | ||
| 157 | id="path2637" | ||
| 158 | cx="24" | ||
| 159 | cy="33" | ||
| 160 | rx="4.3600898" | ||
| 161 | ry="4.3600893" /><path | ||
| 162 | style="fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none" | ||
| 163 | 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" | ||
| 164 | id="path2142" | ||
| 165 | inkscape:path-effect="#path-effect2144" | ||
| 166 | 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" | ||
| 167 | sodipodi:nodetypes="csc" /><path | ||
| 168 | style="fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none" | ||
| 169 | d="M 26,22 H 39 V 9" | ||
| 170 | id="path2626" | ||
| 171 | sodipodi:nodetypes="ccc" /></g><g | ||
| 172 | inkscape:groupmode="layer" | ||
| 173 | id="layer6" | ||
| 174 | inkscape:label="into" | ||
| 175 | style="display:none"><ellipse | ||
| 176 | style="fill:#000000;stroke:#000000;stroke-width:5.27982;stroke-dasharray:none" | ||
| 177 | id="path2637-3" | ||
| 178 | cx="24" | ||
| 179 | cy="38.5" | ||
| 180 | rx="4.3600898" | ||
| 181 | ry="4.3600893" /><path | ||
| 182 | style="fill:#000000;stroke:#000000;stroke-width:5;stroke-dasharray:none" | ||
| 183 | d="M 24,2 V 24" | ||
| 184 | id="path2668" | ||
| 185 | sodipodi:nodetypes="cc" /><path | ||
| 186 | style="fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none" | ||
| 187 | d="M 14.807612,16.994936 24,26.187324 33.192388,16.994936" | ||
| 188 | id="path2626-8" | ||
| 189 | sodipodi:nodetypes="ccc" /></g><g | ||
| 190 | inkscape:groupmode="layer" | ||
| 191 | id="g2714" | ||
| 192 | inkscape:label="out" | ||
| 193 | style="display:none"><ellipse | ||
| 194 | style="fill:#000000;stroke:#000000;stroke-width:5.27982;stroke-dasharray:none" | ||
| 195 | id="ellipse2708" | ||
| 196 | cx="24" | ||
| 197 | cy="38.5" | ||
| 198 | rx="4.3600898" | ||
| 199 | ry="4.3600893" /><path | ||
| 200 | style="fill:#000000;stroke:#000000;stroke-width:5;stroke-dasharray:none" | ||
| 201 | d="M 24,29.722858 V 7.7228579" | ||
| 202 | id="path2710" | ||
| 203 | sodipodi:nodetypes="cc" /><path | ||
| 204 | style="display:inline;fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none" | ||
| 205 | d="M 33.192388,14.727922 24,5.5355339 14.807612,14.727922" | ||
| 206 | id="path2712" | ||
| 207 | sodipodi:nodetypes="ccc" /></g><g | ||
| 208 | inkscape:groupmode="layer" | ||
| 209 | id="layer8" | ||
| 210 | inkscape:label="resume"><path | ||
| 211 | style="fill:#000000;stroke:#000000;stroke-width:5;stroke-dasharray:none" | ||
| 212 | d="M 13,12 V 38" | ||
| 213 | id="path2850" | ||
| 214 | sodipodi:nodetypes="cc" /><path | ||
| 215 | style="fill:none;stroke:#000000;stroke-width:5;stroke-dasharray:none" | ||
| 216 | d="M 21,16 V 34 L 36.5,25 Z" | ||
| 217 | id="path2852" | ||
| 218 | sodipodi:nodetypes="cccc" /></g></svg> | ||
| @@ -4268,6 +4268,16 @@ | |||
| 4268 | </div> | 4268 | </div> |
| 4269 | </div> | 4269 | </div> |
| 4270 | </div> | 4270 | </div> |
| 4271 | <div title="Determines which keys select an item from the AutoComplete suggestions"> | ||
| 4272 | <label data-i18n="Keyboard"> | ||
| 4273 | <small>Keyboard:</small> | ||
| 4274 | </label> | ||
| 4275 | <select id="stscript_autocomplete_select"> | ||
| 4276 | <option value="3" data-i18n="Select with Tab or Enter">Select with Tab or Enter</option> | ||
| 4277 | <option value="1" data-i18n="Select with Tab">Select with Tab</option> | ||
| 4278 | <option value="2" data-i18n="Select with Enter">Select with Enter</option> | ||
| 4279 | </select> | ||
| 4280 | </div> | ||
| 4271 | <div class="flex-container flexFlowColumn gap0" title="Sets the font size of the autocomplete." data-i18n="[title]Sets the font size of the autocomplete."> | 4281 | <div class="flex-container flexFlowColumn gap0" title="Sets the font size of the autocomplete." data-i18n="[title]Sets the font size of the autocomplete."> |
| 4272 | <label for="stscript_autocomplete_font_scale"><small>Font Scale</small></label> | 4282 | <label for="stscript_autocomplete_font_scale"><small>Font Scale</small></label> |
| 4273 | <input class="neo-range-slider" type="range" id="stscript_autocomplete_font_scale" min="0.5" max="2" step="0.01"> | 4283 | <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 = { | |||
| 16 | 'FULL': 2, | 16 | 'FULL': 2, |
| 17 | }; | 17 | }; |
| 18 | 18 | ||
| 19 | /**@readonly*/ | ||
| 20 | /**@enum {Number}*/ | ||
| 21 | export const AUTOCOMPLETE_SELECT_KEY = { | ||
| 22 | 'TAB': 1, // 2^0 | ||
| 23 | 'ENTER': 2, // 2^1 | ||
| 24 | }; | ||
| 25 | |||
| 19 | export class AutoComplete { | 26 | export class AutoComplete { |
| 20 | /**@type {HTMLTextAreaElement}*/ textarea; | 27 | /**@type {HTMLTextAreaElement|HTMLInputElement}*/ textarea; |
| 21 | /**@type {boolean}*/ isFloating = false; | 28 | /**@type {boolean}*/ isFloating = false; |
| 22 | /**@type {()=>boolean}*/ checkIfActivate; | 29 | /**@type {()=>boolean}*/ checkIfActivate; |
| 23 | /**@type {(text:string, index:number) => Promise<AutoCompleteNameResult>}*/ getNameAt; | 30 | /**@type {(text:string, index:number) => Promise<AutoCompleteNameResult>}*/ getNameAt; |
| @@ -56,6 +63,8 @@ export class AutoComplete { | |||
| 56 | /**@type {function}*/ updateDetailsPositionDebounced; | 63 | /**@type {function}*/ updateDetailsPositionDebounced; |
| 57 | /**@type {function}*/ updateFloatingPositionDebounced; | 64 | /**@type {function}*/ updateFloatingPositionDebounced; |
| 58 | 65 | ||
| 66 | /**@type {(item:AutoCompleteOption)=>any}*/ onSelect; | ||
| 67 | |||
| 59 | get matchType() { | 68 | get matchType() { |
| 60 | return power_user.stscript.matching ?? 'fuzzy'; | 69 | return power_user.stscript.matching ?? 'fuzzy'; |
| 61 | } | 70 | } |
| @@ -68,7 +77,7 @@ export class AutoComplete { | |||
| 68 | 77 | ||
| 69 | 78 | ||
| 70 | /** | 79 | /** |
| 71 | * @param {HTMLTextAreaElement} textarea The textarea to receive autocomplete. | 80 | * @param {HTMLTextAreaElement|HTMLInputElement} textarea The textarea to receive autocomplete. |
| 72 | * @param {() => boolean} checkIfActivate Function should return true only if under the current conditions, autocomplete should display (e.g., for slash commands: autoComplete.text[0] == '/') | 81 | * @param {() => boolean} checkIfActivate Function should return true only if under the current conditions, autocomplete should display (e.g., for slash commands: autoComplete.text[0] == '/') |
| 73 | * @param {(text: string, index: number) => Promise<AutoCompleteNameResult>} getNameAt Function should return (unfiltered, matching against input is done in AutoComplete) information about name options at index in text. | 82 | * @param {(text: string, index: number) => Promise<AutoCompleteNameResult>} getNameAt Function should return (unfiltered, matching against input is done in AutoComplete) information about name options at index in text. |
| 74 | * @param {boolean} isFloating Whether autocomplete should float at the keyboard cursor. | 83 | * @param {boolean} isFloating Whether autocomplete should float at the keyboard cursor. |
| @@ -102,10 +111,15 @@ export class AutoComplete { | |||
| 102 | this.updateDetailsPositionDebounced = debounce(this.updateDetailsPosition.bind(this), 10); | 111 | this.updateDetailsPositionDebounced = debounce(this.updateDetailsPosition.bind(this), 10); |
| 103 | this.updateFloatingPositionDebounced = debounce(this.updateFloatingPosition.bind(this), 10); | 112 | this.updateFloatingPositionDebounced = debounce(this.updateFloatingPosition.bind(this), 10); |
| 104 | 113 | ||
| 105 | textarea.addEventListener('input', ()=>this.text != this.textarea.value && this.show(true, this.wasForced)); | 114 | textarea.addEventListener('input', ()=>{ |
| 115 | this.selectionStart = this.textarea.selectionStart; | ||
| 116 | if (this.text != this.textarea.value) this.show(true, this.wasForced); | ||
| 117 | }); | ||
| 106 | textarea.addEventListener('keydown', (evt)=>this.handleKeyDown(evt)); | 118 | textarea.addEventListener('keydown', (evt)=>this.handleKeyDown(evt)); |
| 107 | textarea.addEventListener('click', ()=>this.isActive ? this.show() : null); | 119 | textarea.addEventListener('click', ()=>{ |
| 108 | textarea.addEventListener('selectionchange', ()=>this.show()); | 120 | this.selectionStart = this.textarea.selectionStart; |
| 121 | if (this.isActive) this.show(); | ||
| 122 | }); | ||
| 109 | textarea.addEventListener('blur', ()=>this.hide()); | 123 | textarea.addEventListener('blur', ()=>this.hide()); |
| 110 | if (isFloating) { | 124 | if (isFloating) { |
| 111 | textarea.addEventListener('scroll', ()=>this.updateFloatingPositionDebounced()); | 125 | textarea.addEventListener('scroll', ()=>this.updateFloatingPositionDebounced()); |
| @@ -189,6 +203,11 @@ export class AutoComplete { | |||
| 189 | * @returns The option. | 203 | * @returns The option. |
| 190 | */ | 204 | */ |
| 191 | fuzzyScore(option) { | 205 | fuzzyScore(option) { |
| 206 | // might have been matched by the options matchProvider function instead | ||
| 207 | if (!this.fuzzyRegex.test(option.name)) { | ||
| 208 | option.score = new AutoCompleteFuzzyScore(Number.MAX_SAFE_INTEGER, -1); | ||
| 209 | return option; | ||
| 210 | } | ||
| 192 | const parts = this.fuzzyRegex.exec(option.name).slice(1, -1); | 211 | const parts = this.fuzzyRegex.exec(option.name).slice(1, -1); |
| 193 | let start = null; | 212 | let start = null; |
| 194 | let consecutive = []; | 213 | let consecutive = []; |
| @@ -339,7 +358,7 @@ export class AutoComplete { | |||
| 339 | 358 | ||
| 340 | this.result = this.effectiveParserResult.optionList | 359 | this.result = this.effectiveParserResult.optionList |
| 341 | // filter the list of options by the partial name according to the matching type | 360 | // filter the list of options by the partial name according to the matching type |
| 342 | .filter(it => this.isReplaceable || it.name == '' ? matchers[this.matchType](it.name) : it.name.toLowerCase() == this.name) | 361 | .filter(it => this.isReplaceable || it.name == '' ? (it.matchProvider ? it.matchProvider(this.name) : matchers[this.matchType](it.name)) : it.name.toLowerCase() == this.name) |
| 343 | // remove aliases | 362 | // remove aliases |
| 344 | .filter((it,idx,list) => list.findIndex(opt=>opt.value == it.value) == idx); | 363 | .filter((it,idx,list) => list.findIndex(opt=>opt.value == it.value) == idx); |
| 345 | 364 | ||
| @@ -357,10 +376,11 @@ export class AutoComplete { | |||
| 357 | // build element | 376 | // build element |
| 358 | option.dom = this.makeItem(option); | 377 | option.dom = this.makeItem(option); |
| 359 | // update replacer and add quotes if necessary | 378 | // update replacer and add quotes if necessary |
| 379 | const optionName = option.valueProvider ? option.valueProvider(this.name) : option.name; | ||
| 360 | if (this.effectiveParserResult.canBeQuoted) { | 380 | if (this.effectiveParserResult.canBeQuoted) { |
| 361 | option.replacer = option.name.includes(' ') || this.startQuote || this.endQuote ? `"${option.name}"` : `${option.name}`; | 381 | option.replacer = optionName.includes(' ') || this.startQuote || this.endQuote ? `"${optionName.replace(/"/g, '\\"')}"` : `${optionName}`; |
| 362 | } else { | 382 | } else { |
| 363 | option.replacer = option.name; | 383 | option.replacer = optionName; |
| 364 | } | 384 | } |
| 365 | // calculate fuzzy score if matching is fuzzy | 385 | // calculate fuzzy score if matching is fuzzy |
| 366 | if (this.matchType == 'fuzzy') this.fuzzyScore(option); | 386 | if (this.matchType == 'fuzzy') this.fuzzyScore(option); |
| @@ -399,7 +419,7 @@ export class AutoComplete { | |||
| 399 | , | 419 | , |
| 400 | ); | 420 | ); |
| 401 | this.result.push(option); | 421 | this.result.push(option); |
| 402 | } else if (this.result.length == 1 && this.effectiveParserResult && this.result[0].name == this.effectiveParserResult.name) { | 422 | } else if (this.result.length == 1 && this.effectiveParserResult && this.effectiveParserResult != this.secondaryParserResult && this.result[0].name == this.effectiveParserResult.name) { |
| 403 | // only one result that is exactly the current value? just show hint, no autocomplete | 423 | // only one result that is exactly the current value? just show hint, no autocomplete |
| 404 | this.isReplaceable = false; | 424 | this.isReplaceable = false; |
| 405 | this.isShowingDetails = false; | 425 | this.isShowingDetails = false; |
| @@ -439,11 +459,14 @@ export class AutoComplete { | |||
| 439 | } else { | 459 | } else { |
| 440 | item.dom.classList.remove('selected'); | 460 | item.dom.classList.remove('selected'); |
| 441 | } | 461 | } |
| 462 | if (!item.isSelectable) { | ||
| 463 | item.dom.classList.add('not-selectable'); | ||
| 464 | } | ||
| 442 | frag.append(item.dom); | 465 | frag.append(item.dom); |
| 443 | } | 466 | } |
| 444 | this.dom.append(frag); | 467 | this.dom.append(frag); |
| 445 | this.updatePosition(); | 468 | this.updatePosition(); |
| 446 | getTopmostModalLayer().append(this.domWrap); | 469 | this.getLayer().append(this.domWrap); |
| 447 | } else { | 470 | } else { |
| 448 | this.domWrap.remove(); | 471 | this.domWrap.remove(); |
| 449 | } | 472 | } |
| @@ -458,10 +481,17 @@ export class AutoComplete { | |||
| 458 | if (!this.isShowingDetails && this.isReplaceable) return this.detailsWrap.remove(); | 481 | if (!this.isShowingDetails && this.isReplaceable) return this.detailsWrap.remove(); |
| 459 | this.detailsDom.innerHTML = ''; | 482 | this.detailsDom.innerHTML = ''; |
| 460 | this.detailsDom.append(this.selectedItem?.renderDetails() ?? 'NO ITEM'); | 483 | this.detailsDom.append(this.selectedItem?.renderDetails() ?? 'NO ITEM'); |
| 461 | getTopmostModalLayer().append(this.detailsWrap); | 484 | this.getLayer().append(this.detailsWrap); |
| 462 | this.updateDetailsPositionDebounced(); | 485 | this.updateDetailsPositionDebounced(); |
| 463 | } | 486 | } |
| 464 | 487 | ||
| 488 | /** | ||
| 489 | * @returns {HTMLElement} closest ancestor dialog or body | ||
| 490 | */ | ||
| 491 | getLayer() { | ||
| 492 | return this.textarea.closest('dialog, body'); | ||
| 493 | } | ||
| 494 | |||
| 465 | 495 | ||
| 466 | 496 | ||
| 467 | /** | 497 | /** |
| @@ -474,7 +504,7 @@ export class AutoComplete { | |||
| 474 | const rect = {}; | 504 | const rect = {}; |
| 475 | rect[AUTOCOMPLETE_WIDTH.INPUT] = this.textarea.getBoundingClientRect(); | 505 | rect[AUTOCOMPLETE_WIDTH.INPUT] = this.textarea.getBoundingClientRect(); |
| 476 | rect[AUTOCOMPLETE_WIDTH.CHAT] = document.querySelector('#sheld').getBoundingClientRect(); | 506 | rect[AUTOCOMPLETE_WIDTH.CHAT] = document.querySelector('#sheld').getBoundingClientRect(); |
| 477 | rect[AUTOCOMPLETE_WIDTH.FULL] = getTopmostModalLayer().getBoundingClientRect(); | 507 | rect[AUTOCOMPLETE_WIDTH.FULL] = this.getLayer().getBoundingClientRect(); |
| 478 | this.domWrap.style.setProperty('--bottom', `${window.innerHeight - rect[AUTOCOMPLETE_WIDTH.INPUT].top}px`); | 508 | this.domWrap.style.setProperty('--bottom', `${window.innerHeight - rect[AUTOCOMPLETE_WIDTH.INPUT].top}px`); |
| 479 | this.dom.style.setProperty('--bottom', `${window.innerHeight - rect[AUTOCOMPLETE_WIDTH.INPUT].top}px`); | 509 | this.dom.style.setProperty('--bottom', `${window.innerHeight - rect[AUTOCOMPLETE_WIDTH.INPUT].top}px`); |
| 480 | this.domWrap.style.bottom = `${window.innerHeight - rect[AUTOCOMPLETE_WIDTH.INPUT].top}px`; | 510 | this.domWrap.style.bottom = `${window.innerHeight - rect[AUTOCOMPLETE_WIDTH.INPUT].top}px`; |
| @@ -501,7 +531,7 @@ export class AutoComplete { | |||
| 501 | const rect = {}; | 531 | const rect = {}; |
| 502 | rect[AUTOCOMPLETE_WIDTH.INPUT] = this.textarea.getBoundingClientRect(); | 532 | rect[AUTOCOMPLETE_WIDTH.INPUT] = this.textarea.getBoundingClientRect(); |
| 503 | rect[AUTOCOMPLETE_WIDTH.CHAT] = document.querySelector('#sheld').getBoundingClientRect(); | 533 | rect[AUTOCOMPLETE_WIDTH.CHAT] = document.querySelector('#sheld').getBoundingClientRect(); |
| 504 | rect[AUTOCOMPLETE_WIDTH.FULL] = getTopmostModalLayer().getBoundingClientRect(); | 534 | rect[AUTOCOMPLETE_WIDTH.FULL] = this.getLayer().getBoundingClientRect(); |
| 505 | if (this.isReplaceable) { | 535 | if (this.isReplaceable) { |
| 506 | this.detailsWrap.classList.remove('full'); | 536 | this.detailsWrap.classList.remove('full'); |
| 507 | const selRect = this.selectedItem.dom.children[0].getBoundingClientRect(); | 537 | const selRect = this.selectedItem.dom.children[0].getBoundingClientRect(); |
| @@ -527,32 +557,34 @@ export class AutoComplete { | |||
| 527 | updateFloatingPosition() { | 557 | updateFloatingPosition() { |
| 528 | const location = this.getCursorPosition(); | 558 | const location = this.getCursorPosition(); |
| 529 | const rect = this.textarea.getBoundingClientRect(); | 559 | const rect = this.textarea.getBoundingClientRect(); |
| 560 | const layerRect = this.getLayer().getBoundingClientRect(); | ||
| 530 | // cursor is out of view -> hide | 561 | // cursor is out of view -> hide |
| 531 | if (location.bottom < rect.top || location.top > rect.bottom || location.left < rect.left || location.left > rect.right) { | 562 | if (location.bottom < rect.top || location.top > rect.bottom || location.left < rect.left || location.left > rect.right) { |
| 532 | return this.hide(); | 563 | return this.hide(); |
| 533 | } | 564 | } |
| 534 | const left = Math.max(rect.left, location.left); | 565 | const left = Math.max(rect.left, location.left) - layerRect.left; |
| 535 | this.domWrap.style.setProperty('--targetOffset', `${left}`); | 566 | this.domWrap.style.setProperty('--targetOffset', `${left}`); |
| 536 | if (location.top <= window.innerHeight / 2) { | 567 | if (location.top <= window.innerHeight / 2) { |
| 537 | // if cursor is in lower half of window, show list above line | 568 | // if cursor is in lower half of window, show list above line |
| 538 | this.domWrap.style.top = `${location.bottom}px`; | 569 | this.domWrap.style.top = `${location.bottom - layerRect.top}px`; |
| 539 | this.domWrap.style.bottom = 'auto'; | 570 | this.domWrap.style.bottom = 'auto'; |
| 540 | this.domWrap.style.maxHeight = `calc(${location.bottom}px - 1vh)`; | 571 | this.domWrap.style.maxHeight = `calc(${location.bottom - layerRect.top}px - ${this.textarea.closest('dialog') ? '0' : '1vh'})`; |
| 541 | } else { | 572 | } else { |
| 542 | // if cursor is in upper half of window, show list below line | 573 | // if cursor is in upper half of window, show list below line |
| 543 | this.domWrap.style.top = 'auto'; | 574 | this.domWrap.style.top = 'auto'; |
| 544 | this.domWrap.style.bottom = `calc(100vh - ${location.top}px)`; | 575 | this.domWrap.style.bottom = `calc(${layerRect.height}px - ${location.top - layerRect.top}px)`; |
| 545 | this.domWrap.style.maxHeight = `calc(${location.top}px - 1vh)`; | 576 | this.domWrap.style.maxHeight = `calc(${location.top - layerRect.top}px - ${this.textarea.closest('dialog') ? '0' : '1vh'})`; |
| 546 | } | 577 | } |
| 547 | } | 578 | } |
| 548 | 579 | ||
| 549 | updateFloatingDetailsPosition(location = null) { | 580 | updateFloatingDetailsPosition(location = null) { |
| 550 | if (!location) location = this.getCursorPosition(); | 581 | if (!location) location = this.getCursorPosition(); |
| 551 | const rect = this.textarea.getBoundingClientRect(); | 582 | const rect = this.textarea.getBoundingClientRect(); |
| 583 | const layerRect = this.getLayer().getBoundingClientRect(); | ||
| 552 | if (location.bottom < rect.top || location.top > rect.bottom || location.left < rect.left || location.left > rect.right) { | 584 | if (location.bottom < rect.top || location.top > rect.bottom || location.left < rect.left || location.left > rect.right) { |
| 553 | return this.hide(); | 585 | return this.hide(); |
| 554 | } | 586 | } |
| 555 | const left = Math.max(rect.left, location.left); | 587 | const left = Math.max(rect.left, location.left) - layerRect.left; |
| 556 | this.detailsWrap.style.setProperty('--targetOffset', `${left}`); | 588 | this.detailsWrap.style.setProperty('--targetOffset', `${left}`); |
| 557 | if (this.isReplaceable) { | 589 | if (this.isReplaceable) { |
| 558 | this.detailsWrap.classList.remove('full'); | 590 | this.detailsWrap.classList.remove('full'); |
| @@ -572,14 +604,14 @@ export class AutoComplete { | |||
| 572 | } | 604 | } |
| 573 | if (location.top <= window.innerHeight / 2) { | 605 | if (location.top <= window.innerHeight / 2) { |
| 574 | // if cursor is in lower half of window, show list above line | 606 | // if cursor is in lower half of window, show list above line |
| 575 | this.detailsWrap.style.top = `${location.bottom}px`; | 607 | this.detailsWrap.style.top = `${location.bottom - layerRect.top}px`; |
| 576 | this.detailsWrap.style.bottom = 'auto'; | 608 | this.detailsWrap.style.bottom = 'auto'; |
| 577 | this.detailsWrap.style.maxHeight = `calc(${location.bottom}px - 1vh)`; | 609 | this.detailsWrap.style.maxHeight = `calc(${location.bottom - layerRect.top}px - ${this.textarea.closest('dialog') ? '0' : '1vh'})`; |
| 578 | } else { | 610 | } else { |
| 579 | // if cursor is in upper half of window, show list below line | 611 | // if cursor is in upper half of window, show list below line |
| 580 | this.detailsWrap.style.top = 'auto'; | 612 | this.detailsWrap.style.top = 'auto'; |
| 581 | this.detailsWrap.style.bottom = `calc(100vh - ${location.top}px)`; | 613 | this.detailsWrap.style.bottom = `calc(${layerRect.height}px - ${location.top - layerRect.top}px)`; |
| 582 | this.detailsWrap.style.maxHeight = `calc(${location.top}px - 1vh)`; | 614 | this.detailsWrap.style.maxHeight = `calc(${location.top - layerRect.top}px - ${this.textarea.closest('dialog') ? '0' : '1vh'})`; |
| 583 | } | 615 | } |
| 584 | } | 616 | } |
| 585 | 617 | ||
| @@ -597,7 +629,7 @@ export class AutoComplete { | |||
| 597 | } | 629 | } |
| 598 | this.clone.style.position = 'fixed'; | 630 | this.clone.style.position = 'fixed'; |
| 599 | this.clone.style.visibility = 'hidden'; | 631 | this.clone.style.visibility = 'hidden'; |
| 600 | getTopmostModalLayer().append(this.clone); | 632 | document.body.append(this.clone); |
| 601 | const mo = new MutationObserver(muts=>{ | 633 | const mo = new MutationObserver(muts=>{ |
| 602 | if (muts.find(it=>Array.from(it.removedNodes).includes(this.textarea))) { | 634 | if (muts.find(it=>Array.from(it.removedNodes).includes(this.textarea))) { |
| 603 | this.clone.remove(); | 635 | this.clone.remove(); |
| @@ -656,6 +688,7 @@ export class AutoComplete { | |||
| 656 | } | 688 | } |
| 657 | this.wasForced = false; | 689 | this.wasForced = false; |
| 658 | this.textarea.dispatchEvent(new Event('input', { bubbles:true })); | 690 | this.textarea.dispatchEvent(new Event('input', { bubbles:true })); |
| 691 | this.onSelect?.(this.selectedItem); | ||
| 659 | } | 692 | } |
| 660 | 693 | ||
| 661 | 694 | ||
| @@ -708,8 +741,10 @@ export class AutoComplete { | |||
| 708 | } | 741 | } |
| 709 | case 'Enter': { | 742 | case 'Enter': { |
| 710 | // pick the selected item to autocomplete | 743 | // pick the selected item to autocomplete |
| 744 | if ((power_user.stscript.autocomplete.select & AUTOCOMPLETE_SELECT_KEY.ENTER) != AUTOCOMPLETE_SELECT_KEY.ENTER) break; | ||
| 711 | if (evt.ctrlKey || evt.altKey || evt.shiftKey || this.selectedItem.value == '') break; | 745 | if (evt.ctrlKey || evt.altKey || evt.shiftKey || this.selectedItem.value == '') break; |
| 712 | if (this.selectedItem.name == this.name) break; | 746 | if (this.selectedItem.name == this.name) break; |
| 747 | if (!this.selectedItem.isSelectable) break; | ||
| 713 | evt.preventDefault(); | 748 | evt.preventDefault(); |
| 714 | evt.stopImmediatePropagation(); | 749 | evt.stopImmediatePropagation(); |
| 715 | this.select(); | 750 | this.select(); |
| @@ -717,9 +752,11 @@ export class AutoComplete { | |||
| 717 | } | 752 | } |
| 718 | case 'Tab': { | 753 | case 'Tab': { |
| 719 | // pick the selected item to autocomplete | 754 | // pick the selected item to autocomplete |
| 755 | if ((power_user.stscript.autocomplete.select & AUTOCOMPLETE_SELECT_KEY.TAB) != AUTOCOMPLETE_SELECT_KEY.TAB) break; | ||
| 720 | if (evt.ctrlKey || evt.altKey || evt.shiftKey || this.selectedItem.value == '') break; | 756 | if (evt.ctrlKey || evt.altKey || evt.shiftKey || this.selectedItem.value == '') break; |
| 721 | evt.preventDefault(); | 757 | evt.preventDefault(); |
| 722 | evt.stopImmediatePropagation(); | 758 | evt.stopImmediatePropagation(); |
| 759 | if (!this.selectedItem.isSelectable) break; | ||
| 723 | this.select(); | 760 | this.select(); |
| 724 | return; | 761 | return; |
| 725 | } | 762 | } |
| @@ -772,13 +809,7 @@ export class AutoComplete { | |||
| 772 | // ignore keydown on modifier keys | 809 | // ignore keydown on modifier keys |
| 773 | return; | 810 | return; |
| 774 | } | 811 | } |
| 775 | switch (evt.key) { | 812 | // await keyup to see if cursor position or text has changed |
| 776 | case 'ArrowUp': | ||
| 777 | case 'ArrowDown': | ||
| 778 | case 'ArrowRight': | ||
| 779 | case 'ArrowLeft': { | ||
| 780 | if (this.isActive) { | ||
| 781 | // keyboard navigation, wait for keyup to complete cursor move | ||
| 782 | const oldText = this.textarea.value; | 813 | const oldText = this.textarea.value; |
| 783 | await new Promise(resolve=>{ | 814 | await new Promise(resolve=>{ |
| 784 | window.addEventListener('keyup', resolve, { once:true }); | 815 | window.addEventListener('keyup', resolve, { once:true }); |
| @@ -786,16 +817,8 @@ export class AutoComplete { | |||
| 786 | if (this.selectionStart != this.textarea.selectionStart) { | 817 | if (this.selectionStart != this.textarea.selectionStart) { |
| 787 | this.selectionStart = this.textarea.selectionStart; | 818 | this.selectionStart = this.textarea.selectionStart; |
| 788 | this.show(this.isReplaceable || oldText != this.textarea.value); | 819 | this.show(this.isReplaceable || oldText != this.textarea.value); |
| 789 | } | 820 | } else if (this.isActive) { |
| 790 | } | ||
| 791 | break; | ||
| 792 | } | ||
| 793 | default: { | ||
| 794 | if (this.isActive) { | ||
| 795 | this.text != this.textarea.value && this.show(this.isReplaceable); | 821 | this.text != this.textarea.value && this.show(this.isReplaceable); |
| 796 | } | 822 | } |
| 797 | break; | ||
| 798 | } | ||
| 799 | } | ||
| 800 | } | 823 | } |
| 801 | } | 824 | } |
| @@ -1,36 +1,9 @@ | |||
| 1 | import { SlashCommandNamedArgumentAutoCompleteOption } from '../slash-commands/SlashCommandNamedArgumentAutoCompleteOption.js'; | 1 | import { AutoCompleteNameResultBase } from './AutoCompleteNameResultBase.js'; |
| 2 | import { AutoCompleteOption } from './AutoCompleteOption.js'; | 2 | import { AutoCompleteSecondaryNameResult } from './AutoCompleteSecondaryNameResult.js'; |
| 3 | // import { AutoCompleteSecondaryNameResult } from './AutoCompleteSecondaryNameResult.js'; | ||
| 4 | 3 | ||
| 5 | 4 | ||
| 6 | 5 | ||
| 7 | export class AutoCompleteNameResult { | 6 | export class AutoCompleteNameResult extends AutoCompleteNameResultBase { |
| 8 | /**@type {string} */ name; | ||
| 9 | /**@type {number} */ start; | ||
| 10 | /**@type {AutoCompleteOption[]} */ optionList = []; | ||
| 11 | /**@type {boolean} */ canBeQuoted = false; | ||
| 12 | /**@type {()=>string} */ makeNoMatchText = ()=>`No matches found for "${this.name}"`; | ||
| 13 | /**@type {()=>string} */ makeNoOptionsText = ()=>'No options'; | ||
| 14 | |||
| 15 | |||
| 16 | /** | ||
| 17 | * @param {string} name Name (potentially partial) of the name at the requested index. | ||
| 18 | * @param {number} start Index where the name starts. | ||
| 19 | * @param {AutoCompleteOption[]} optionList A list of autocomplete options found in the current scope. | ||
| 20 | * @param {boolean} canBeQuoted Whether the name can be inside quotes. | ||
| 21 | * @param {()=>string} makeNoMatchText Function that returns text to show when no matches where found. | ||
| 22 | * @param {()=>string} makeNoOptionsText Function that returns text to show when no options are available to match against. | ||
| 23 | */ | ||
| 24 | constructor(name, start, optionList = [], canBeQuoted = false, makeNoMatchText = null, makeNoOptionsText = null) { | ||
| 25 | this.name = name; | ||
| 26 | this.start = start; | ||
| 27 | this.optionList = optionList; | ||
| 28 | this.canBeQuoted = canBeQuoted; | ||
| 29 | this.noMatchText = makeNoMatchText ?? this.makeNoMatchText; | ||
| 30 | this.noOptionstext = makeNoOptionsText ?? this.makeNoOptionsText; | ||
| 31 | } | ||
| 32 | |||
| 33 | |||
| 34 | /** | 7 | /** |
| 35 | * | 8 | * |
| 36 | * @param {string} text The whole text | 9 | * @param {string} text The whole text |
| @@ -0,0 +1,31 @@ | |||
| 1 | import { SlashCommandNamedArgumentAutoCompleteOption } from '../slash-commands/SlashCommandNamedArgumentAutoCompleteOption.js'; | ||
| 2 | import { AutoCompleteOption } from './AutoCompleteOption.js'; | ||
| 3 | |||
| 4 | |||
| 5 | |||
| 6 | export class AutoCompleteNameResultBase { | ||
| 7 | /**@type {string} */ name; | ||
| 8 | /**@type {number} */ start; | ||
| 9 | /**@type {AutoCompleteOption[]} */ optionList = []; | ||
| 10 | /**@type {boolean} */ canBeQuoted = false; | ||
| 11 | /**@type {()=>string} */ makeNoMatchText = ()=>`No matches found for "${this.name}"`; | ||
| 12 | /**@type {()=>string} */ makeNoOptionsText = ()=>'No options'; | ||
| 13 | |||
| 14 | |||
| 15 | /** | ||
| 16 | * @param {string} name Name (potentially partial) of the name at the requested index. | ||
| 17 | * @param {number} start Index where the name starts. | ||
| 18 | * @param {AutoCompleteOption[]} optionList A list of autocomplete options found in the current scope. | ||
| 19 | * @param {boolean} canBeQuoted Whether the name can be inside quotes. | ||
| 20 | * @param {()=>string} makeNoMatchText Function that returns text to show when no matches where found. | ||
| 21 | * @param {()=>string} makeNoOptionsText Function that returns text to show when no options are available to match against. | ||
| 22 | */ | ||
| 23 | constructor(name, start, optionList = [], canBeQuoted = false, makeNoMatchText = null, makeNoOptionsText = null) { | ||
| 24 | this.name = name; | ||
| 25 | this.start = start; | ||
| 26 | this.optionList = optionList; | ||
| 27 | this.canBeQuoted = canBeQuoted; | ||
| 28 | this.noMatchText = makeNoMatchText ?? this.makeNoMatchText; | ||
| 29 | this.noOptionstext = makeNoOptionsText ?? this.makeNoOptionsText; | ||
| 30 | } | ||
| 31 | } | ||
| @@ -11,6 +11,9 @@ export class AutoCompleteOption { | |||
| 11 | /**@type {AutoCompleteFuzzyScore}*/ score; | 11 | /**@type {AutoCompleteFuzzyScore}*/ score; |
| 12 | /**@type {string}*/ replacer; | 12 | /**@type {string}*/ replacer; |
| 13 | /**@type {HTMLElement}*/ dom; | 13 | /**@type {HTMLElement}*/ dom; |
| 14 | /**@type {(input:string)=>boolean}*/ matchProvider; | ||
| 15 | /**@type {(input:string)=>string}*/ valueProvider; | ||
| 16 | /**@type {boolean}*/ makeSelectable = false; | ||
| 14 | 17 | ||
| 15 | 18 | ||
| 16 | /** | 19 | /** |
| @@ -21,14 +24,21 @@ export class AutoCompleteOption { | |||
| 21 | return this.name; | 24 | return this.name; |
| 22 | } | 25 | } |
| 23 | 26 | ||
| 27 | get isSelectable() { | ||
| 28 | return this.makeSelectable || !this.valueProvider; | ||
| 29 | } | ||
| 30 | |||
| 24 | 31 | ||
| 25 | /** | 32 | /** |
| 26 | * @param {string} name | 33 | * @param {string} name |
| 27 | */ | 34 | */ |
| 28 | constructor(name, typeIcon = ' ', type = '') { | 35 | constructor(name, typeIcon = ' ', type = '', matchProvider = null, valueProvider = null, makeSelectable = false) { |
| 29 | this.name = name; | 36 | this.name = name; |
| 30 | this.typeIcon = typeIcon; | 37 | this.typeIcon = typeIcon; |
| 31 | this.type = type; | 38 | this.type = type; |
| 39 | this.matchProvider = matchProvider; | ||
| 40 | this.valueProvider = valueProvider; | ||
| 41 | this.makeSelectable = makeSelectable; | ||
| 32 | } | 42 | } |
| 33 | 43 | ||
| 34 | 44 | ||
| @@ -1,6 +1,6 @@ | |||
| 1 | import { AutoCompleteNameResult } from './AutoCompleteNameResult.js'; | 1 | import { AutoCompleteNameResultBase } from './AutoCompleteNameResultBase.js'; |
| 2 | 2 | ||
| 3 | export class AutoCompleteSecondaryNameResult extends AutoCompleteNameResult { | 3 | export class AutoCompleteSecondaryNameResult extends AutoCompleteNameResultBase { |
| 4 | /**@type {boolean}*/ isRequired = false; | 4 | /**@type {boolean}*/ isRequired = false; |
| 5 | /**@type {boolean}*/ forceMatch = true; | 5 | /**@type {boolean}*/ forceMatch = true; |
| 6 | } | 6 | } |
| @@ -24,9 +24,17 @@ export class QuickReplyApi { | |||
| 24 | 24 | ||
| 25 | 25 | ||
| 26 | /** | 26 | /** |
| 27 | * @param {QuickReply} qr | ||
| 28 | * @returns {QuickReplySet} | ||
| 29 | */ | ||
| 30 | getSetByQr(qr) { | ||
| 31 | return QuickReplySet.list.find(it=>it.qrList.includes(qr)); | ||
| 32 | } | ||
| 33 | |||
| 34 | /** | ||
| 27 | * Finds and returns an existing Quick Reply Set by its name. | 35 | * Finds and returns an existing Quick Reply Set by its name. |
| 28 | * | 36 | * |
| 29 | * @param {String} name name of the quick reply set | 37 | * @param {string} name name of the quick reply set |
| 30 | * @returns the quick reply set, or undefined if not found | 38 | * @returns the quick reply set, or undefined if not found |
| 31 | */ | 39 | */ |
| 32 | getSetByName(name) { | 40 | getSetByName(name) { |
| @@ -36,13 +44,14 @@ export class QuickReplyApi { | |||
| 36 | /** | 44 | /** |
| 37 | * Finds and returns an existing Quick Reply by its set's name and its label. | 45 | * Finds and returns an existing Quick Reply by its set's name and its label. |
| 38 | * | 46 | * |
| 39 | * @param {String} setName name of the quick reply set | 47 | * @param {string} setName name of the quick reply set |
| 40 | * @param {String} label label of the quick reply | 48 | * @param {string|number} label label or numeric ID of the quick reply |
| 41 | * @returns the quick reply, or undefined if not found | 49 | * @returns the quick reply, or undefined if not found |
| 42 | */ | 50 | */ |
| 43 | getQrByLabel(setName, label) { | 51 | getQrByLabel(setName, label) { |
| 44 | const set = this.getSetByName(setName); | 52 | const set = this.getSetByName(setName); |
| 45 | if (!set) return; | 53 | if (!set) return; |
| 54 | if (Number.isInteger(label)) return set.qrList.find(it=>it.id == label); | ||
| 46 | return set.qrList.find(it=>it.label == label); | 55 | return set.qrList.find(it=>it.label == label); |
| 47 | } | 56 | } |
| 48 | 57 | ||
| @@ -70,24 +79,25 @@ export class QuickReplyApi { | |||
| 70 | /** | 79 | /** |
| 71 | * Executes an existing quick reply. | 80 | * Executes an existing quick reply. |
| 72 | * | 81 | * |
| 73 | * @param {String} setName name of the existing quick reply set | 82 | * @param {string} setName name of the existing quick reply set |
| 74 | * @param {String} label label of the existing quick reply (text on the button) | 83 | * @param {string|number} label label of the existing quick reply (text on the button) or its numeric ID |
| 75 | * @param {Object} [args] optional arguments | 84 | * @param {object} [args] optional arguments |
| 85 | * @param {import('../../../slash-commands.js').ExecuteSlashCommandsOptions} [options] optional execution options | ||
| 76 | */ | 86 | */ |
| 77 | async executeQuickReply(setName, label, args = {}) { | 87 | async executeQuickReply(setName, label, args = {}, options = {}) { |
| 78 | const qr = this.getQrByLabel(setName, label); | 88 | const qr = this.getQrByLabel(setName, label); |
| 79 | if (!qr) { | 89 | if (!qr) { |
| 80 | throw new Error(`No quick reply with label "${label}" in set "${setName}" found.`); | 90 | throw new Error(`No quick reply with label "${label}" in set "${setName}" found.`); |
| 81 | } | 91 | } |
| 82 | return await qr.execute(args); | 92 | return await qr.execute(args, false, false, options); |
| 83 | } | 93 | } |
| 84 | 94 | ||
| 85 | 95 | ||
| 86 | /** | 96 | /** |
| 87 | * Adds or removes a quick reply set to the list of globally active quick reply sets. | 97 | * Adds or removes a quick reply set to the list of globally active quick reply sets. |
| 88 | * | 98 | * |
| 89 | * @param {String} name the name of the set | 99 | * @param {string} name the name of the set |
| 90 | * @param {Boolean} isVisible whether to show the set's buttons or not | 100 | * @param {boolean} isVisible whether to show the set's buttons or not |
| 91 | */ | 101 | */ |
| 92 | toggleGlobalSet(name, isVisible = true) { | 102 | toggleGlobalSet(name, isVisible = true) { |
| 93 | const set = this.getSetByName(name); | 103 | const set = this.getSetByName(name); |
| @@ -104,8 +114,8 @@ export class QuickReplyApi { | |||
| 104 | /** | 114 | /** |
| 105 | * Adds a quick reply set to the list of globally active quick reply sets. | 115 | * Adds a quick reply set to the list of globally active quick reply sets. |
| 106 | * | 116 | * |
| 107 | * @param {String} name the name of the set | 117 | * @param {string} name the name of the set |
| 108 | * @param {Boolean} isVisible whether to show the set's buttons or not | 118 | * @param {boolean} isVisible whether to show the set's buttons or not |
| 109 | */ | 119 | */ |
| 110 | addGlobalSet(name, isVisible = true) { | 120 | addGlobalSet(name, isVisible = true) { |
| 111 | const set = this.getSetByName(name); | 121 | const set = this.getSetByName(name); |
| @@ -118,7 +128,7 @@ export class QuickReplyApi { | |||
| 118 | /** | 128 | /** |
| 119 | * Removes a quick reply set from the list of globally active quick reply sets. | 129 | * Removes a quick reply set from the list of globally active quick reply sets. |
| 120 | * | 130 | * |
| 121 | * @param {String} name the name of the set | 131 | * @param {string} name the name of the set |
| 122 | */ | 132 | */ |
| 123 | removeGlobalSet(name) { | 133 | removeGlobalSet(name) { |
| 124 | const set = this.getSetByName(name); | 134 | const set = this.getSetByName(name); |
| @@ -132,8 +142,8 @@ export class QuickReplyApi { | |||
| 132 | /** | 142 | /** |
| 133 | * Adds or removes a quick reply set to the list of the current chat's active quick reply sets. | 143 | * Adds or removes a quick reply set to the list of the current chat's active quick reply sets. |
| 134 | * | 144 | * |
| 135 | * @param {String} name the name of the set | 145 | * @param {string} name the name of the set |
| 136 | * @param {Boolean} isVisible whether to show the set's buttons or not | 146 | * @param {boolean} isVisible whether to show the set's buttons or not |
| 137 | */ | 147 | */ |
| 138 | toggleChatSet(name, isVisible = true) { | 148 | toggleChatSet(name, isVisible = true) { |
| 139 | if (!this.settings.chatConfig) return; | 149 | if (!this.settings.chatConfig) return; |
| @@ -151,8 +161,8 @@ export class QuickReplyApi { | |||
| 151 | /** | 161 | /** |
| 152 | * Adds a quick reply set to the list of the current chat's active quick reply sets. | 162 | * Adds a quick reply set to the list of the current chat's active quick reply sets. |
| 153 | * | 163 | * |
| 154 | * @param {String} name the name of the set | 164 | * @param {string} name the name of the set |
| 155 | * @param {Boolean} isVisible whether to show the set's buttons or not | 165 | * @param {boolean} isVisible whether to show the set's buttons or not |
| 156 | */ | 166 | */ |
| 157 | addChatSet(name, isVisible = true) { | 167 | addChatSet(name, isVisible = true) { |
| 158 | if (!this.settings.chatConfig) return; | 168 | if (!this.settings.chatConfig) return; |
| @@ -166,7 +176,7 @@ export class QuickReplyApi { | |||
| 166 | /** | 176 | /** |
| 167 | * Removes a quick reply set from the list of the current chat's active quick reply sets. | 177 | * Removes a quick reply set from the list of the current chat's active quick reply sets. |
| 168 | * | 178 | * |
| 169 | * @param {String} name the name of the set | 179 | * @param {string} name the name of the set |
| 170 | */ | 180 | */ |
| 171 | removeChatSet(name) { | 181 | removeChatSet(name) { |
| 172 | if (!this.settings.chatConfig) return; | 182 | if (!this.settings.chatConfig) return; |
| @@ -181,21 +191,25 @@ export class QuickReplyApi { | |||
| 181 | /** | 191 | /** |
| 182 | * Creates a new quick reply in an existing quick reply set. | 192 | * Creates a new quick reply in an existing quick reply set. |
| 183 | * | 193 | * |
| 184 | * @param {String} setName name of the quick reply set to insert the new quick reply into | 194 | * @param {string} setName name of the quick reply set to insert the new quick reply into |
| 185 | * @param {String} label label for the new quick reply (text on the button) | 195 | * @param {string} label label for the new quick reply (text on the button) |
| 186 | * @param {Object} [props] | 196 | * @param {object} [props] |
| 187 | * @param {String} [props.message] the message to be sent or slash command to be executed by the new quick reply | 197 | * @param {string} [props.icon] the icon to show on the QR button |
| 188 | * @param {String} [props.title] the title / tooltip to be shown on the quick reply button | 198 | * @param {boolean} [props.showLabel] whether to show the label even when an icon is assigned |
| 189 | * @param {Boolean} [props.isHidden] whether to hide or show the button | 199 | * @param {string} [props.message] the message to be sent or slash command to be executed by the new quick reply |
| 190 | * @param {Boolean} [props.executeOnStartup] whether to execute the quick reply when SillyTavern starts | 200 | * @param {string} [props.title] the title / tooltip to be shown on the quick reply button |
| 191 | * @param {Boolean} [props.executeOnUser] whether to execute the quick reply after a user has sent a message | 201 | * @param {boolean} [props.isHidden] whether to hide or show the button |
| 192 | * @param {Boolean} [props.executeOnAi] whether to execute the quick reply after the AI has sent a message | 202 | * @param {boolean} [props.executeOnStartup] whether to execute the quick reply when SillyTavern starts |
| 193 | * @param {Boolean} [props.executeOnChatChange] whether to execute the quick reply when a new chat is loaded | 203 | * @param {boolean} [props.executeOnUser] whether to execute the quick reply after a user has sent a message |
| 194 | * @param {Boolean} [props.executeOnGroupMemberDraft] whether to execute the quick reply when a group member is selected | 204 | * @param {boolean} [props.executeOnAi] whether to execute the quick reply after the AI has sent a message |
| 195 | * @param {String} [props.automationId] when not empty, the quick reply will be executed when the WI with the given automation ID is activated | 205 | * @param {boolean} [props.executeOnChatChange] whether to execute the quick reply when a new chat is loaded |
| 206 | * @param {boolean} [props.executeOnGroupMemberDraft] whether to execute the quick reply when a group member is selected | ||
| 207 | * @param {string} [props.automationId] when not empty, the quick reply will be executed when the WI with the given automation ID is activated | ||
| 196 | * @returns {QuickReply} the new quick reply | 208 | * @returns {QuickReply} the new quick reply |
| 197 | */ | 209 | */ |
| 198 | createQuickReply(setName, label, { | 210 | createQuickReply(setName, label, { |
| 211 | icon, | ||
| 212 | showLabel, | ||
| 199 | message, | 213 | message, |
| 200 | title, | 214 | title, |
| 201 | isHidden, | 215 | isHidden, |
| @@ -212,6 +226,8 @@ export class QuickReplyApi { | |||
| 212 | } | 226 | } |
| 213 | const qr = set.addQuickReply(); | 227 | const qr = set.addQuickReply(); |
| 214 | qr.label = label ?? ''; | 228 | qr.label = label ?? ''; |
| 229 | qr.icon = icon ?? ''; | ||
| 230 | qr.showLabel = showLabel ?? false; | ||
| 215 | qr.message = message ?? ''; | 231 | qr.message = message ?? ''; |
| 216 | qr.title = title ?? ''; | 232 | qr.title = title ?? ''; |
| 217 | qr.isHidden = isHidden ?? false; | 233 | qr.isHidden = isHidden ?? false; |
| @@ -228,22 +244,26 @@ export class QuickReplyApi { | |||
| 228 | /** | 244 | /** |
| 229 | * Updates an existing quick reply. | 245 | * Updates an existing quick reply. |
| 230 | * | 246 | * |
| 231 | * @param {String} setName name of the existing quick reply set | 247 | * @param {string} setName name of the existing quick reply set |
| 232 | * @param {String} label label of the existing quick reply (text on the button) | 248 | * @param {string|number} label label of the existing quick reply (text on the button) or its numeric ID |
| 233 | * @param {Object} [props] | 249 | * @param {object} [props] |
| 234 | * @param {String} [props.newLabel] new label for quick reply (text on the button) | 250 | * @param {string} [props.icon] the icon to show on the QR button |
| 235 | * @param {String} [props.message] the message to be sent or slash command to be executed by the quick reply | 251 | * @param {boolean} [props.showLabel] whether to show the label even when an icon is assigned |
| 236 | * @param {String} [props.title] the title / tooltip to be shown on the quick reply button | 252 | * @param {string} [props.newLabel] new label for quick reply (text on the button) |
| 237 | * @param {Boolean} [props.isHidden] whether to hide or show the button | 253 | * @param {string} [props.message] the message to be sent or slash command to be executed by the quick reply |
| 238 | * @param {Boolean} [props.executeOnStartup] whether to execute the quick reply when SillyTavern starts | 254 | * @param {string} [props.title] the title / tooltip to be shown on the quick reply button |
| 239 | * @param {Boolean} [props.executeOnUser] whether to execute the quick reply after a user has sent a message | 255 | * @param {boolean} [props.isHidden] whether to hide or show the button |
| 240 | * @param {Boolean} [props.executeOnAi] whether to execute the quick reply after the AI has sent a message | 256 | * @param {boolean} [props.executeOnStartup] whether to execute the quick reply when SillyTavern starts |
| 241 | * @param {Boolean} [props.executeOnChatChange] whether to execute the quick reply when a new chat is loaded | 257 | * @param {boolean} [props.executeOnUser] whether to execute the quick reply after a user has sent a message |
| 242 | * @param {Boolean} [props.executeOnGroupMemberDraft] whether to execute the quick reply when a group member is selected | 258 | * @param {boolean} [props.executeOnAi] whether to execute the quick reply after the AI has sent a message |
| 243 | * @param {String} [props.automationId] when not empty, the quick reply will be executed when the WI with the given automation ID is activated | 259 | * @param {boolean} [props.executeOnChatChange] whether to execute the quick reply when a new chat is loaded |
| 260 | * @param {boolean} [props.executeOnGroupMemberDraft] whether to execute the quick reply when a group member is selected | ||
| 261 | * @param {string} [props.automationId] when not empty, the quick reply will be executed when the WI with the given automation ID is activated | ||
| 244 | * @returns {QuickReply} the altered quick reply | 262 | * @returns {QuickReply} the altered quick reply |
| 245 | */ | 263 | */ |
| 246 | updateQuickReply(setName, label, { | 264 | updateQuickReply(setName, label, { |
| 265 | icon, | ||
| 266 | showLabel, | ||
| 247 | newLabel, | 267 | newLabel, |
| 248 | message, | 268 | message, |
| 249 | title, | 269 | title, |
| @@ -259,6 +279,8 @@ export class QuickReplyApi { | |||
| 259 | if (!qr) { | 279 | if (!qr) { |
| 260 | throw new Error(`No quick reply with label "${label}" in set "${setName}" found.`); | 280 | throw new Error(`No quick reply with label "${label}" in set "${setName}" found.`); |
| 261 | } | 281 | } |
| 282 | qr.updateIcon(icon ?? qr.icon); | ||
| 283 | qr.updateShowLabel(showLabel ?? qr.showLabel); | ||
| 262 | qr.updateLabel(newLabel ?? qr.label); | 284 | qr.updateLabel(newLabel ?? qr.label); |
| 263 | qr.updateMessage(message ?? qr.message); | 285 | qr.updateMessage(message ?? qr.message); |
| 264 | qr.updateTitle(title ?? qr.title); | 286 | qr.updateTitle(title ?? qr.title); |
| @@ -276,8 +298,8 @@ export class QuickReplyApi { | |||
| 276 | /** | 298 | /** |
| 277 | * Deletes an existing quick reply. | 299 | * Deletes an existing quick reply. |
| 278 | * | 300 | * |
| 279 | * @param {String} setName name of the existing quick reply set | 301 | * @param {string} setName name of the existing quick reply set |
| 280 | * @param {String} label label of the existing quick reply (text on the button) | 302 | * @param {string|number} label label of the existing quick reply (text on the button) or its numeric ID |
| 281 | */ | 303 | */ |
| 282 | deleteQuickReply(setName, label) { | 304 | deleteQuickReply(setName, label) { |
| 283 | const qr = this.getQrByLabel(setName, label); | 305 | const qr = this.getQrByLabel(setName, label); |
| @@ -291,10 +313,10 @@ export class QuickReplyApi { | |||
| 291 | /** | 313 | /** |
| 292 | * Adds an existing quick reply set as a context menu to an existing quick reply. | 314 | * Adds an existing quick reply set as a context menu to an existing quick reply. |
| 293 | * | 315 | * |
| 294 | * @param {String} setName name of the existing quick reply set containing the quick reply | 316 | * @param {string} setName name of the existing quick reply set containing the quick reply |
| 295 | * @param {String} label label of the existing quick reply | 317 | * @param {string|number} label label of the existing quick reply or its numeric ID |
| 296 | * @param {String} contextSetName name of the existing quick reply set to be used as a context menu | 318 | * @param {string} contextSetName name of the existing quick reply set to be used as a context menu |
| 297 | * @param {Boolean} isChained whether or not to chain the context menu quick replies | 319 | * @param {boolean} isChained whether or not to chain the context menu quick replies |
| 298 | */ | 320 | */ |
| 299 | createContextItem(setName, label, contextSetName, isChained = false) { | 321 | createContextItem(setName, label, contextSetName, isChained = false) { |
| 300 | const qr = this.getQrByLabel(setName, label); | 322 | const qr = this.getQrByLabel(setName, label); |
| @@ -314,9 +336,9 @@ export class QuickReplyApi { | |||
| 314 | /** | 336 | /** |
| 315 | * Removes a quick reply set from a quick reply's context menu. | 337 | * Removes a quick reply set from a quick reply's context menu. |
| 316 | * | 338 | * |
| 317 | * @param {String} setName name of the existing quick reply set containing the quick reply | 339 | * @param {string} setName name of the existing quick reply set containing the quick reply |
| 318 | * @param {String} label label of the existing quick reply | 340 | * @param {string|number} label label of the existing quick reply or its numeric ID |
| 319 | * @param {String} contextSetName name of the existing quick reply set to be used as a context menu | 341 | * @param {string} contextSetName name of the existing quick reply set to be used as a context menu |
| 320 | */ | 342 | */ |
| 321 | deleteContextItem(setName, label, contextSetName) { | 343 | deleteContextItem(setName, label, contextSetName) { |
| 322 | const qr = this.getQrByLabel(setName, label); | 344 | const qr = this.getQrByLabel(setName, label); |
| @@ -333,8 +355,8 @@ export class QuickReplyApi { | |||
| 333 | /** | 355 | /** |
| 334 | * Removes all entries from a quick reply's context menu. | 356 | * Removes all entries from a quick reply's context menu. |
| 335 | * | 357 | * |
| 336 | * @param {String} setName name of the existing quick reply set containing the quick reply | 358 | * @param {string} setName name of the existing quick reply set containing the quick reply |
| 337 | * @param {String} label label of the existing quick reply | 359 | * @param {string|number} label label of the existing quick reply or its numeric ID |
| 338 | */ | 360 | */ |
| 339 | clearContextMenu(setName, label) { | 361 | clearContextMenu(setName, label) { |
| 340 | const qr = this.getQrByLabel(setName, label); | 362 | const qr = this.getQrByLabel(setName, label); |
| @@ -348,11 +370,11 @@ export class QuickReplyApi { | |||
| 348 | /** | 370 | /** |
| 349 | * Create a new quick reply set. | 371 | * Create a new quick reply set. |
| 350 | * | 372 | * |
| 351 | * @param {String} name name of the new quick reply set | 373 | * @param {string} name name of the new quick reply set |
| 352 | * @param {Object} [props] | 374 | * @param {object} [props] |
| 353 | * @param {Boolean} [props.disableSend] whether or not to send the quick replies or put the message or slash command into the char input box | 375 | * @param {boolean} [props.disableSend] whether or not to send the quick replies or put the message or slash command into the char input box |
| 354 | * @param {Boolean} [props.placeBeforeInput] whether or not to place the quick reply contents before the existing user input | 376 | * @param {boolean} [props.placeBeforeInput] whether or not to place the quick reply contents before the existing user input |
| 355 | * @param {Boolean} [props.injectInput] whether or not to automatically inject the user input at the end of the quick reply | 377 | * @param {boolean} [props.injectInput] whether or not to automatically inject the user input at the end of the quick reply |
| 356 | * @returns {Promise<QuickReplySet>} the new quick reply set | 378 | * @returns {Promise<QuickReplySet>} the new quick reply set |
| 357 | */ | 379 | */ |
| 358 | async createSet(name, { | 380 | async createSet(name, { |
| @@ -384,11 +406,11 @@ export class QuickReplyApi { | |||
| 384 | /** | 406 | /** |
| 385 | * Update an existing quick reply set. | 407 | * Update an existing quick reply set. |
| 386 | * | 408 | * |
| 387 | * @param {String} name name of the existing quick reply set | 409 | * @param {string} name name of the existing quick reply set |
| 388 | * @param {Object} [props] | 410 | * @param {object} [props] |
| 389 | * @param {Boolean} [props.disableSend] whether or not to send the quick replies or put the message or slash command into the char input box | 411 | * @param {boolean} [props.disableSend] whether or not to send the quick replies or put the message or slash command into the char input box |
| 390 | * @param {Boolean} [props.placeBeforeInput] whether or not to place the quick reply contents before the existing user input | 412 | * @param {boolean} [props.placeBeforeInput] whether or not to place the quick reply contents before the existing user input |
| 391 | * @param {Boolean} [props.injectInput] whether or not to automatically inject the user input at the end of the quick reply | 413 | * @param {boolean} [props.injectInput] whether or not to automatically inject the user input at the end of the quick reply |
| 392 | * @returns {Promise<QuickReplySet>} the altered quick reply set | 414 | * @returns {Promise<QuickReplySet>} the altered quick reply set |
| 393 | */ | 415 | */ |
| 394 | async updateSet(name, { | 416 | async updateSet(name, { |
| @@ -411,7 +433,7 @@ export class QuickReplyApi { | |||
| 411 | /** | 433 | /** |
| 412 | * Delete an existing quick reply set. | 434 | * Delete an existing quick reply set. |
| 413 | * | 435 | * |
| 414 | * @param {String} name name of the existing quick reply set | 436 | * @param {string} name name of the existing quick reply set |
| 415 | */ | 437 | */ |
| 416 | async deleteSet(name) { | 438 | async deleteSet(name) { |
| 417 | const set = this.getSetByName(name); | 439 | const set = this.getSetByName(name); |
| @@ -451,7 +473,7 @@ export class QuickReplyApi { | |||
| 451 | /** | 473 | /** |
| 452 | * Gets a list of all quick replies in the quick reply set. | 474 | * Gets a list of all quick replies in the quick reply set. |
| 453 | * | 475 | * |
| 454 | * @param {String} setName name of the existing quick reply set | 476 | * @param {string} setName name of the existing quick reply set |
| 455 | * @returns array with the labels of this set's quick replies | 477 | * @returns array with the labels of this set's quick replies |
| 456 | */ | 478 | */ |
| 457 | listQuickReplies(setName) { | 479 | listQuickReplies(setName) { |
| @@ -2,10 +2,23 @@ | |||
| 2 | <div id="qr--main"> | 2 | <div id="qr--main"> |
| 3 | <h3 data-i18n="Labels and Message">Labels and Message</h3> | 3 | <h3 data-i18n="Labels and Message">Labels and Message</h3> |
| 4 | <div class="qr--labels"> | 4 | <div class="qr--labels"> |
| 5 | <label> | 5 | <label class="qr--fit"> |
| 6 | <span class="qr--labelText" data-i18n="Label">Icon</span> | ||
| 7 | <small class="qr--labelHint"> </small> | ||
| 8 | <div class="menu_button fa-fw" id="qr--modal-icon" title="Click to change icon"></div> | ||
| 9 | </label> | ||
| 10 | <div class="label"> | ||
| 6 | <span class="qr--labelText" data-i18n="Label">Label</span> | 11 | <span class="qr--labelText" data-i18n="Label">Label</span> |
| 7 | <input type="text" class="text_pole" id="qr--modal-label"> | 12 | <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> |
| 13 | <div class="qr--inputGroup"> | ||
| 14 | <label class="checkbox_label" title="Show label even if an icon is assigned"> | ||
| 15 | <input type="checkbox" id="qr--modal-showLabel"> | ||
| 16 | Show | ||
| 8 | </label> | 17 | </label> |
| 18 | <input type="text" class="text_pole" id="qr--modal-label"> | ||
| 19 | <div class="menu_button fa-fw fa-solid fa-chevron-down" id="qr--modal-switcher" title="Switch to another QR"></div> | ||
| 20 | </div> | ||
| 21 | </div> | ||
| 9 | <label> | 22 | <label> |
| 10 | <span class="qr--labelText" data-i18n="Title">Title</span> | 23 | <span class="qr--labelText" data-i18n="Title">Title</span> |
| 11 | <small class="qr--labelHint" data-i18n="(tooltip, leave empty to show message or /command)">(tooltip, leave empty to show message or /command)</small> | 24 | <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 @@ | |||
| 33 | <input type="checkbox" id="qr--modal-syntax"> | 46 | <input type="checkbox" id="qr--modal-syntax"> |
| 34 | <span>Syntax highlight</span> | 47 | <span>Syntax highlight</span> |
| 35 | </label> | 48 | </label> |
| 49 | <small>Ctrl+Alt+Click (or F9) to set / remove breakpoints</small> | ||
| 50 | <small>Ctrl+<span id="qr--modal-commentKey"></span> to toggle block comments</small> | ||
| 36 | </div> | 51 | </div> |
| 37 | <div id="qr--modal-messageHolder"> | 52 | <div id="qr--modal-messageHolder"> |
| 38 | <pre id="qr--modal-messageSyntax"><code id="qr--modal-messageSyntaxInner" class="hljs language-stscript"></code></pre> | 53 | <pre id="qr--modal-messageSyntax"><code id="qr--modal-messageSyntaxInner" class="hljs language-stscript"></code></pre> |
| @@ -43,6 +58,10 @@ | |||
| 43 | 58 | ||
| 44 | 59 | ||
| 45 | 60 | ||
| 61 | <div id="qr--resizeHandle"></div> | ||
| 62 | |||
| 63 | |||
| 64 | |||
| 46 | <div id="qr--qrOptions"> | 65 | <div id="qr--qrOptions"> |
| 47 | <h3 data-i18n="Context Menu">Context Menu</h3> | 66 | <h3 data-i18n="Context Menu">Context Menu</h3> |
| 48 | <div id="qr--ctxEditor"> | 67 | <div id="qr--ctxEditor"> |
| @@ -64,7 +83,7 @@ | |||
| 64 | 83 | ||
| 65 | 84 | ||
| 66 | <h3 data-i18n="Auto-Execute">Auto-Execute</h3> | 85 | <h3 data-i18n="Auto-Execute">Auto-Execute</h3> |
| 67 | <div class="flex-container flexFlowColumn"> | 86 | <div id="qr--autoExec" class="flex-container flexFlowColumn"> |
| 68 | <label class="checkbox_label" title="Prevent this quick reply from triggering other auto-executed quick replies while auto-executing (i.e., prevent recursive auto-execution)"> | 87 | <label class="checkbox_label" title="Prevent this quick reply from triggering other auto-executed quick replies while auto-executing (i.e., prevent recursive auto-execution)"> |
| 69 | <input type="checkbox" id="qr--preventAutoExecute" > | 88 | <input type="checkbox" id="qr--preventAutoExecute" > |
| 70 | <span><i class="fa-solid fa-fw fa-plane-slash"></i><span data-i18n="Don't trigger auto-execute">Don't trigger auto-execute</span></span> | 89 | <span><i class="fa-solid fa-fw fa-plane-slash"></i><span data-i18n="Don't trigger auto-execute">Don't trigger auto-execute</span></span> |
| @@ -117,11 +136,18 @@ | |||
| 117 | </div> | 136 | </div> |
| 118 | </div> | 137 | </div> |
| 119 | <div id="qr--modal-executeProgress"></div> | 138 | <div id="qr--modal-executeProgress"></div> |
| 120 | <label class="checkbox_label"> | ||
| 121 | <input type="checkbox" id="qr--modal-executeHide"> | ||
| 122 | <span title="Hide editor while executing"> Hide editor while executing</span> | ||
| 123 | </label> | ||
| 124 | <div id="qr--modal-executeErrors"></div> | 139 | <div id="qr--modal-executeErrors"></div> |
| 125 | <div id="qr--modal-executeResult"></div> | 140 | <div id="qr--modal-executeResult"></div> |
| 141 | |||
| 142 | <div id="qr--modal-debugButtons"> | ||
| 143 | <div title="Resume" id="qr--modal-resume" class="qr--modal-debugButton menu_button"></div> | ||
| 144 | <div title="Step Over" id="qr--modal-step" class="qr--modal-debugButton menu_button"></div> | ||
| 145 | <div title="Step Into" id="qr--modal-stepInto" class="qr--modal-debugButton menu_button"></div> | ||
| 146 | <div title="Step Out" id="qr--modal-stepOut" class="qr--modal-debugButton menu_button"></div> | ||
| 147 | <div title="Minimize" id="qr--modal-minimize" class="qr--modal-debugButton menu_button fa-solid fa-minimize"></div> | ||
| 148 | <div title="Maximize" id="qr--modal-maximize" class="qr--modal-debugButton menu_button fa-solid fa-maximize"></div> | ||
| 149 | </div> | ||
| 150 | <textarea rows="1" id="qr--modal-send_textarea" placeholder="Chat input for use with {{input}}" title="Chat input for use with {{input}}"></textarea> | ||
| 151 | <div id="qr--modal-debugState"></div> | ||
| 126 | </div> | 152 | </div> |
| 127 | </div> | 153 | </div> |
| @@ -60,10 +60,20 @@ | |||
| 60 | <label class="flex-container" id="qr--injectInputContainer"> | 60 | <label class="flex-container" id="qr--injectInputContainer"> |
| 61 | <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> | 61 | <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> |
| 62 | </label> | 62 | </label> |
| 63 | <div class="flex-container alignItemsCenter"> | ||
| 64 | <toolcool-color-picker id="qr--color"></toolcool-color-picker> | ||
| 65 | <div class="menu_button" id="qr--colorClear">Clear</div> | ||
| 66 | <span data-i18n="Color">Color</span> | ||
| 67 | </div> | ||
| 68 | <label class="flex-container" id="qr--onlyBorderColorContainer"> | ||
| 69 | <input type="checkbox" id="qr--onlyBorderColor"> <span data-i18n="Only apply color as accent">Only apply color as accent</span> | ||
| 70 | </label> | ||
| 63 | </div> | 71 | </div> |
| 64 | <div id="qr--set-qrList" class="qr--qrList"></div> | 72 | <div id="qr--set-qrList" class="qr--qrList"></div> |
| 65 | <div class="qr--set-qrListActions"> | 73 | <div class="qr--set-qrListActions"> |
| 66 | <div class="qr--add menu_button menu_button_icon fa-solid fa-plus" id="qr--set-add" title="Add quick reply"></div> | 74 | <div class="qr--add menu_button menu_button_icon fa-solid fa-plus" id="qr--set-add" title="Add quick reply"></div> |
| 75 | <div class="qr--paste menu_button menu_button_icon fa-solid fa-paste" id="qr--set-paste" title="Paste quick reply from clipboard"></div> | ||
| 76 | <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> | ||
| 67 | </div> | 77 | </div> |
| 68 | </div> | 78 | </div> |
| 69 | </div> | 79 | </div> |
| @@ -176,7 +176,7 @@ const init = async () => { | |||
| 176 | buttons.show(); | 176 | buttons.show(); |
| 177 | settings.onSave = ()=>buttons.refresh(); | 177 | settings.onSave = ()=>buttons.refresh(); |
| 178 | 178 | ||
| 179 | window['executeQuickReplyByName'] = async(name, args = {}) => { | 179 | window['executeQuickReplyByName'] = async(name, args = {}, options = {}) => { |
| 180 | let qr = [...settings.config.setList, ...(settings.chatConfig?.setList ?? [])] | 180 | let qr = [...settings.config.setList, ...(settings.chatConfig?.setList ?? [])] |
| 181 | .map(it=>it.set.qrList) | 181 | .map(it=>it.set.qrList) |
| 182 | .flat() | 182 | .flat() |
| @@ -191,7 +191,7 @@ const init = async () => { | |||
| 191 | } | 191 | } |
| 192 | } | 192 | } |
| 193 | if (qr && qr.onExecute) { | 193 | if (qr && qr.onExecute) { |
| 194 | return await qr.execute(args, false, true); | 194 | return await qr.execute(args, false, true, options); |
| 195 | } else { | 195 | } else { |
| 196 | throw new Error(`No Quick Reply found for "${name}".`); | 196 | throw new Error(`No Quick Reply found for "${name}".`); |
| 197 | } | 197 | } |
| @@ -0,0 +1,769 @@ | |||
| 1 | var DOCUMENT_FRAGMENT_NODE = 11; | ||
| 2 | |||
| 3 | function morphAttrs(fromNode, toNode) { | ||
| 4 | var toNodeAttrs = toNode.attributes; | ||
| 5 | var attr; | ||
| 6 | var attrName; | ||
| 7 | var attrNamespaceURI; | ||
| 8 | var attrValue; | ||
| 9 | var fromValue; | ||
| 10 | |||
| 11 | // document-fragments dont have attributes so lets not do anything | ||
| 12 | if (toNode.nodeType === DOCUMENT_FRAGMENT_NODE || fromNode.nodeType === DOCUMENT_FRAGMENT_NODE) { | ||
| 13 | return; | ||
| 14 | } | ||
| 15 | |||
| 16 | // update attributes on original DOM element | ||
| 17 | for (var i = toNodeAttrs.length - 1; i >= 0; i--) { | ||
| 18 | attr = toNodeAttrs[i]; | ||
| 19 | attrName = attr.name; | ||
| 20 | attrNamespaceURI = attr.namespaceURI; | ||
| 21 | attrValue = attr.value; | ||
| 22 | |||
| 23 | if (attrNamespaceURI) { | ||
| 24 | attrName = attr.localName || attrName; | ||
| 25 | fromValue = fromNode.getAttributeNS(attrNamespaceURI, attrName); | ||
| 26 | |||
| 27 | if (fromValue !== attrValue) { | ||
| 28 | if (attr.prefix === 'xmlns'){ | ||
| 29 | attrName = attr.name; // It's not allowed to set an attribute with the XMLNS namespace without specifying the `xmlns` prefix | ||
| 30 | } | ||
| 31 | fromNode.setAttributeNS(attrNamespaceURI, attrName, attrValue); | ||
| 32 | } | ||
| 33 | } else { | ||
| 34 | fromValue = fromNode.getAttribute(attrName); | ||
| 35 | |||
| 36 | if (fromValue !== attrValue) { | ||
| 37 | fromNode.setAttribute(attrName, attrValue); | ||
| 38 | } | ||
| 39 | } | ||
| 40 | } | ||
| 41 | |||
| 42 | // Remove any extra attributes found on the original DOM element that | ||
| 43 | // weren't found on the target element. | ||
| 44 | var fromNodeAttrs = fromNode.attributes; | ||
| 45 | |||
| 46 | for (var d = fromNodeAttrs.length - 1; d >= 0; d--) { | ||
| 47 | attr = fromNodeAttrs[d]; | ||
| 48 | attrName = attr.name; | ||
| 49 | attrNamespaceURI = attr.namespaceURI; | ||
| 50 | |||
| 51 | if (attrNamespaceURI) { | ||
| 52 | attrName = attr.localName || attrName; | ||
| 53 | |||
| 54 | if (!toNode.hasAttributeNS(attrNamespaceURI, attrName)) { | ||
| 55 | fromNode.removeAttributeNS(attrNamespaceURI, attrName); | ||
| 56 | } | ||
| 57 | } else { | ||
| 58 | if (!toNode.hasAttribute(attrName)) { | ||
| 59 | fromNode.removeAttribute(attrName); | ||
| 60 | } | ||
| 61 | } | ||
| 62 | } | ||
| 63 | } | ||
| 64 | |||
| 65 | var range; // Create a range object for efficently rendering strings to elements. | ||
| 66 | var NS_XHTML = 'http://www.w3.org/1999/xhtml'; | ||
| 67 | |||
| 68 | var doc = typeof document === 'undefined' ? undefined : document; | ||
| 69 | var HAS_TEMPLATE_SUPPORT = !!doc && 'content' in doc.createElement('template'); | ||
| 70 | var HAS_RANGE_SUPPORT = !!doc && doc.createRange && 'createContextualFragment' in doc.createRange(); | ||
| 71 | |||
| 72 | function createFragmentFromTemplate(str) { | ||
| 73 | var template = doc.createElement('template'); | ||
| 74 | template.innerHTML = str; | ||
| 75 | return template.content.childNodes[0]; | ||
| 76 | } | ||
| 77 | |||
| 78 | function createFragmentFromRange(str) { | ||
| 79 | if (!range) { | ||
| 80 | range = doc.createRange(); | ||
| 81 | range.selectNode(doc.body); | ||
| 82 | } | ||
| 83 | |||
| 84 | var fragment = range.createContextualFragment(str); | ||
| 85 | return fragment.childNodes[0]; | ||
| 86 | } | ||
| 87 | |||
| 88 | function createFragmentFromWrap(str) { | ||
| 89 | var fragment = doc.createElement('body'); | ||
| 90 | fragment.innerHTML = str; | ||
| 91 | return fragment.childNodes[0]; | ||
| 92 | } | ||
| 93 | |||
| 94 | /** | ||
| 95 | * This is about the same | ||
| 96 | * var html = new DOMParser().parseFromString(str, 'text/html'); | ||
| 97 | * return html.body.firstChild; | ||
| 98 | * | ||
| 99 | * @method toElement | ||
| 100 | * @param {String} str | ||
| 101 | */ | ||
| 102 | function toElement(str) { | ||
| 103 | str = str.trim(); | ||
| 104 | if (HAS_TEMPLATE_SUPPORT) { | ||
| 105 | // avoid restrictions on content for things like `<tr><th>Hi</th></tr>` which | ||
| 106 | // createContextualFragment doesn't support | ||
| 107 | // <template> support not available in IE | ||
| 108 | return createFragmentFromTemplate(str); | ||
| 109 | } else if (HAS_RANGE_SUPPORT) { | ||
| 110 | return createFragmentFromRange(str); | ||
| 111 | } | ||
| 112 | |||
| 113 | return createFragmentFromWrap(str); | ||
| 114 | } | ||
| 115 | |||
| 116 | /** | ||
| 117 | * Returns true if two node's names are the same. | ||
| 118 | * | ||
| 119 | * NOTE: We don't bother checking `namespaceURI` because you will never find two HTML elements with the same | ||
| 120 | * nodeName and different namespace URIs. | ||
| 121 | * | ||
| 122 | * @param {Element} a | ||
| 123 | * @param {Element} b The target element | ||
| 124 | * @return {boolean} | ||
| 125 | */ | ||
| 126 | function compareNodeNames(fromEl, toEl) { | ||
| 127 | var fromNodeName = fromEl.nodeName; | ||
| 128 | var toNodeName = toEl.nodeName; | ||
| 129 | var fromCodeStart, toCodeStart; | ||
| 130 | |||
| 131 | if (fromNodeName === toNodeName) { | ||
| 132 | return true; | ||
| 133 | } | ||
| 134 | |||
| 135 | fromCodeStart = fromNodeName.charCodeAt(0); | ||
| 136 | toCodeStart = toNodeName.charCodeAt(0); | ||
| 137 | |||
| 138 | // If the target element is a virtual DOM node or SVG node then we may | ||
| 139 | // need to normalize the tag name before comparing. Normal HTML elements that are | ||
| 140 | // in the "http://www.w3.org/1999/xhtml" | ||
| 141 | // are converted to upper case | ||
| 142 | if (fromCodeStart <= 90 && toCodeStart >= 97) { // from is upper and to is lower | ||
| 143 | return fromNodeName === toNodeName.toUpperCase(); | ||
| 144 | } else if (toCodeStart <= 90 && fromCodeStart >= 97) { // to is upper and from is lower | ||
| 145 | return toNodeName === fromNodeName.toUpperCase(); | ||
| 146 | } else { | ||
| 147 | return false; | ||
| 148 | } | ||
| 149 | } | ||
| 150 | |||
| 151 | /** | ||
| 152 | * Create an element, optionally with a known namespace URI. | ||
| 153 | * | ||
| 154 | * @param {string} name the element name, e.g. 'div' or 'svg' | ||
| 155 | * @param {string} [namespaceURI] the element's namespace URI, i.e. the value of | ||
| 156 | * its `xmlns` attribute or its inferred namespace. | ||
| 157 | * | ||
| 158 | * @return {Element} | ||
| 159 | */ | ||
| 160 | function createElementNS(name, namespaceURI) { | ||
| 161 | return !namespaceURI || namespaceURI === NS_XHTML ? | ||
| 162 | doc.createElement(name) : | ||
| 163 | doc.createElementNS(namespaceURI, name); | ||
| 164 | } | ||
| 165 | |||
| 166 | /** | ||
| 167 | * Copies the children of one DOM element to another DOM element | ||
| 168 | */ | ||
| 169 | function moveChildren(fromEl, toEl) { | ||
| 170 | var curChild = fromEl.firstChild; | ||
| 171 | while (curChild) { | ||
| 172 | var nextChild = curChild.nextSibling; | ||
| 173 | toEl.appendChild(curChild); | ||
| 174 | curChild = nextChild; | ||
| 175 | } | ||
| 176 | return toEl; | ||
| 177 | } | ||
| 178 | |||
| 179 | function syncBooleanAttrProp(fromEl, toEl, name) { | ||
| 180 | if (fromEl[name] !== toEl[name]) { | ||
| 181 | fromEl[name] = toEl[name]; | ||
| 182 | if (fromEl[name]) { | ||
| 183 | fromEl.setAttribute(name, ''); | ||
| 184 | } else { | ||
| 185 | fromEl.removeAttribute(name); | ||
| 186 | } | ||
| 187 | } | ||
| 188 | } | ||
| 189 | |||
| 190 | var specialElHandlers = { | ||
| 191 | OPTION: function(fromEl, toEl) { | ||
| 192 | var parentNode = fromEl.parentNode; | ||
| 193 | if (parentNode) { | ||
| 194 | var parentName = parentNode.nodeName.toUpperCase(); | ||
| 195 | if (parentName === 'OPTGROUP') { | ||
| 196 | parentNode = parentNode.parentNode; | ||
| 197 | parentName = parentNode && parentNode.nodeName.toUpperCase(); | ||
| 198 | } | ||
| 199 | if (parentName === 'SELECT' && !parentNode.hasAttribute('multiple')) { | ||
| 200 | if (fromEl.hasAttribute('selected') && !toEl.selected) { | ||
| 201 | // Workaround for MS Edge bug where the 'selected' attribute can only be | ||
| 202 | // removed if set to a non-empty value: | ||
| 203 | // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/12087679/ | ||
| 204 | fromEl.setAttribute('selected', 'selected'); | ||
| 205 | fromEl.removeAttribute('selected'); | ||
| 206 | } | ||
| 207 | // We have to reset select element's selectedIndex to -1, otherwise setting | ||
| 208 | // fromEl.selected using the syncBooleanAttrProp below has no effect. | ||
| 209 | // The correct selectedIndex will be set in the SELECT special handler below. | ||
| 210 | parentNode.selectedIndex = -1; | ||
| 211 | } | ||
| 212 | } | ||
| 213 | syncBooleanAttrProp(fromEl, toEl, 'selected'); | ||
| 214 | }, | ||
| 215 | /** | ||
| 216 | * The "value" attribute is special for the <input> element since it sets | ||
| 217 | * the initial value. Changing the "value" attribute without changing the | ||
| 218 | * "value" property will have no effect since it is only used to the set the | ||
| 219 | * initial value. Similar for the "checked" attribute, and "disabled". | ||
| 220 | */ | ||
| 221 | INPUT: function(fromEl, toEl) { | ||
| 222 | syncBooleanAttrProp(fromEl, toEl, 'checked'); | ||
| 223 | syncBooleanAttrProp(fromEl, toEl, 'disabled'); | ||
| 224 | |||
| 225 | if (fromEl.value !== toEl.value) { | ||
| 226 | fromEl.value = toEl.value; | ||
| 227 | } | ||
| 228 | |||
| 229 | if (!toEl.hasAttribute('value')) { | ||
| 230 | fromEl.removeAttribute('value'); | ||
| 231 | } | ||
| 232 | }, | ||
| 233 | |||
| 234 | TEXTAREA: function(fromEl, toEl) { | ||
| 235 | var newValue = toEl.value; | ||
| 236 | if (fromEl.value !== newValue) { | ||
| 237 | fromEl.value = newValue; | ||
| 238 | } | ||
| 239 | |||
| 240 | var firstChild = fromEl.firstChild; | ||
| 241 | if (firstChild) { | ||
| 242 | // Needed for IE. Apparently IE sets the placeholder as the | ||
| 243 | // node value and vise versa. This ignores an empty update. | ||
| 244 | var oldValue = firstChild.nodeValue; | ||
| 245 | |||
| 246 | if (oldValue == newValue || (!newValue && oldValue == fromEl.placeholder)) { | ||
| 247 | return; | ||
| 248 | } | ||
| 249 | |||
| 250 | firstChild.nodeValue = newValue; | ||
| 251 | } | ||
| 252 | }, | ||
| 253 | SELECT: function(fromEl, toEl) { | ||
| 254 | if (!toEl.hasAttribute('multiple')) { | ||
| 255 | var selectedIndex = -1; | ||
| 256 | var i = 0; | ||
| 257 | // We have to loop through children of fromEl, not toEl since nodes can be moved | ||
| 258 | // from toEl to fromEl directly when morphing. | ||
| 259 | // At the time this special handler is invoked, all children have already been morphed | ||
| 260 | // and appended to / removed from fromEl, so using fromEl here is safe and correct. | ||
| 261 | var curChild = fromEl.firstChild; | ||
| 262 | var optgroup; | ||
| 263 | var nodeName; | ||
| 264 | while(curChild) { | ||
| 265 | nodeName = curChild.nodeName && curChild.nodeName.toUpperCase(); | ||
| 266 | if (nodeName === 'OPTGROUP') { | ||
| 267 | optgroup = curChild; | ||
| 268 | curChild = optgroup.firstChild; | ||
| 269 | } else { | ||
| 270 | if (nodeName === 'OPTION') { | ||
| 271 | if (curChild.hasAttribute('selected')) { | ||
| 272 | selectedIndex = i; | ||
| 273 | break; | ||
| 274 | } | ||
| 275 | i++; | ||
| 276 | } | ||
| 277 | curChild = curChild.nextSibling; | ||
| 278 | if (!curChild && optgroup) { | ||
| 279 | curChild = optgroup.nextSibling; | ||
| 280 | optgroup = null; | ||
| 281 | } | ||
| 282 | } | ||
| 283 | } | ||
| 284 | |||
| 285 | fromEl.selectedIndex = selectedIndex; | ||
| 286 | } | ||
| 287 | } | ||
| 288 | }; | ||
| 289 | |||
| 290 | var ELEMENT_NODE = 1; | ||
| 291 | var DOCUMENT_FRAGMENT_NODE$1 = 11; | ||
| 292 | var TEXT_NODE = 3; | ||
| 293 | var COMMENT_NODE = 8; | ||
| 294 | |||
| 295 | function noop() {} | ||
| 296 | |||
| 297 | function defaultGetNodeKey(node) { | ||
| 298 | if (node) { | ||
| 299 | return (node.getAttribute && node.getAttribute('id')) || node.id; | ||
| 300 | } | ||
| 301 | } | ||
| 302 | |||
| 303 | function morphdomFactory(morphAttrs) { | ||
| 304 | |||
| 305 | return function morphdom(fromNode, toNode, options) { | ||
| 306 | if (!options) { | ||
| 307 | options = {}; | ||
| 308 | } | ||
| 309 | |||
| 310 | if (typeof toNode === 'string') { | ||
| 311 | if (fromNode.nodeName === '#document' || fromNode.nodeName === 'HTML' || fromNode.nodeName === 'BODY') { | ||
| 312 | var toNodeHtml = toNode; | ||
| 313 | toNode = doc.createElement('html'); | ||
| 314 | toNode.innerHTML = toNodeHtml; | ||
| 315 | } else { | ||
| 316 | toNode = toElement(toNode); | ||
| 317 | } | ||
| 318 | } else if (toNode.nodeType === DOCUMENT_FRAGMENT_NODE$1) { | ||
| 319 | toNode = toNode.firstElementChild; | ||
| 320 | } | ||
| 321 | |||
| 322 | var getNodeKey = options.getNodeKey || defaultGetNodeKey; | ||
| 323 | var onBeforeNodeAdded = options.onBeforeNodeAdded || noop; | ||
| 324 | var onNodeAdded = options.onNodeAdded || noop; | ||
| 325 | var onBeforeElUpdated = options.onBeforeElUpdated || noop; | ||
| 326 | var onElUpdated = options.onElUpdated || noop; | ||
| 327 | var onBeforeNodeDiscarded = options.onBeforeNodeDiscarded || noop; | ||
| 328 | var onNodeDiscarded = options.onNodeDiscarded || noop; | ||
| 329 | var onBeforeElChildrenUpdated = options.onBeforeElChildrenUpdated || noop; | ||
| 330 | var skipFromChildren = options.skipFromChildren || noop; | ||
| 331 | var addChild = options.addChild || function(parent, child){ return parent.appendChild(child); }; | ||
| 332 | var childrenOnly = options.childrenOnly === true; | ||
| 333 | |||
| 334 | // This object is used as a lookup to quickly find all keyed elements in the original DOM tree. | ||
| 335 | var fromNodesLookup = Object.create(null); | ||
| 336 | var keyedRemovalList = []; | ||
| 337 | |||
| 338 | function addKeyedRemoval(key) { | ||
| 339 | keyedRemovalList.push(key); | ||
| 340 | } | ||
| 341 | |||
| 342 | function walkDiscardedChildNodes(node, skipKeyedNodes) { | ||
| 343 | if (node.nodeType === ELEMENT_NODE) { | ||
| 344 | var curChild = node.firstChild; | ||
| 345 | while (curChild) { | ||
| 346 | |||
| 347 | var key = undefined; | ||
| 348 | |||
| 349 | if (skipKeyedNodes && (key = getNodeKey(curChild))) { | ||
| 350 | // If we are skipping keyed nodes then we add the key | ||
| 351 | // to a list so that it can be handled at the very end. | ||
| 352 | addKeyedRemoval(key); | ||
| 353 | } else { | ||
| 354 | // Only report the node as discarded if it is not keyed. We do this because | ||
| 355 | // at the end we loop through all keyed elements that were unmatched | ||
| 356 | // and then discard them in one final pass. | ||
| 357 | onNodeDiscarded(curChild); | ||
| 358 | if (curChild.firstChild) { | ||
| 359 | walkDiscardedChildNodes(curChild, skipKeyedNodes); | ||
| 360 | } | ||
| 361 | } | ||
| 362 | |||
| 363 | curChild = curChild.nextSibling; | ||
| 364 | } | ||
| 365 | } | ||
| 366 | } | ||
| 367 | |||
| 368 | /** | ||
| 369 | * Removes a DOM node out of the original DOM | ||
| 370 | * | ||
| 371 | * @param {Node} node The node to remove | ||
| 372 | * @param {Node} parentNode The nodes parent | ||
| 373 | * @param {Boolean} skipKeyedNodes If true then elements with keys will be skipped and not discarded. | ||
| 374 | * @return {undefined} | ||
| 375 | */ | ||
| 376 | function removeNode(node, parentNode, skipKeyedNodes) { | ||
| 377 | if (onBeforeNodeDiscarded(node) === false) { | ||
| 378 | return; | ||
| 379 | } | ||
| 380 | |||
| 381 | if (parentNode) { | ||
| 382 | parentNode.removeChild(node); | ||
| 383 | } | ||
| 384 | |||
| 385 | onNodeDiscarded(node); | ||
| 386 | walkDiscardedChildNodes(node, skipKeyedNodes); | ||
| 387 | } | ||
| 388 | |||
| 389 | // // TreeWalker implementation is no faster, but keeping this around in case this changes in the future | ||
| 390 | // function indexTree(root) { | ||
| 391 | // var treeWalker = document.createTreeWalker( | ||
| 392 | // root, | ||
| 393 | // NodeFilter.SHOW_ELEMENT); | ||
| 394 | // | ||
| 395 | // var el; | ||
| 396 | // while((el = treeWalker.nextNode())) { | ||
| 397 | // var key = getNodeKey(el); | ||
| 398 | // if (key) { | ||
| 399 | // fromNodesLookup[key] = el; | ||
| 400 | // } | ||
| 401 | // } | ||
| 402 | // } | ||
| 403 | |||
| 404 | // // NodeIterator implementation is no faster, but keeping this around in case this changes in the future | ||
| 405 | // | ||
| 406 | // function indexTree(node) { | ||
| 407 | // var nodeIterator = document.createNodeIterator(node, NodeFilter.SHOW_ELEMENT); | ||
| 408 | // var el; | ||
| 409 | // while((el = nodeIterator.nextNode())) { | ||
| 410 | // var key = getNodeKey(el); | ||
| 411 | // if (key) { | ||
| 412 | // fromNodesLookup[key] = el; | ||
| 413 | // } | ||
| 414 | // } | ||
| 415 | // } | ||
| 416 | |||
| 417 | function indexTree(node) { | ||
| 418 | if (node.nodeType === ELEMENT_NODE || node.nodeType === DOCUMENT_FRAGMENT_NODE$1) { | ||
| 419 | var curChild = node.firstChild; | ||
| 420 | while (curChild) { | ||
| 421 | var key = getNodeKey(curChild); | ||
| 422 | if (key) { | ||
| 423 | fromNodesLookup[key] = curChild; | ||
| 424 | } | ||
| 425 | |||
| 426 | // Walk recursively | ||
| 427 | indexTree(curChild); | ||
| 428 | |||
| 429 | curChild = curChild.nextSibling; | ||
| 430 | } | ||
| 431 | } | ||
| 432 | } | ||
| 433 | |||
| 434 | indexTree(fromNode); | ||
| 435 | |||
| 436 | function handleNodeAdded(el) { | ||
| 437 | onNodeAdded(el); | ||
| 438 | |||
| 439 | var curChild = el.firstChild; | ||
| 440 | while (curChild) { | ||
| 441 | var nextSibling = curChild.nextSibling; | ||
| 442 | |||
| 443 | var key = getNodeKey(curChild); | ||
| 444 | if (key) { | ||
| 445 | var unmatchedFromEl = fromNodesLookup[key]; | ||
| 446 | // if we find a duplicate #id node in cache, replace `el` with cache value | ||
| 447 | // and morph it to the child node. | ||
| 448 | if (unmatchedFromEl && compareNodeNames(curChild, unmatchedFromEl)) { | ||
| 449 | curChild.parentNode.replaceChild(unmatchedFromEl, curChild); | ||
| 450 | morphEl(unmatchedFromEl, curChild); | ||
| 451 | } else { | ||
| 452 | handleNodeAdded(curChild); | ||
| 453 | } | ||
| 454 | } else { | ||
| 455 | // recursively call for curChild and it's children to see if we find something in | ||
| 456 | // fromNodesLookup | ||
| 457 | handleNodeAdded(curChild); | ||
| 458 | } | ||
| 459 | |||
| 460 | curChild = nextSibling; | ||
| 461 | } | ||
| 462 | } | ||
| 463 | |||
| 464 | function cleanupFromEl(fromEl, curFromNodeChild, curFromNodeKey) { | ||
| 465 | // We have processed all of the "to nodes". If curFromNodeChild is | ||
| 466 | // non-null then we still have some from nodes left over that need | ||
| 467 | // to be removed | ||
| 468 | while (curFromNodeChild) { | ||
| 469 | var fromNextSibling = curFromNodeChild.nextSibling; | ||
| 470 | if ((curFromNodeKey = getNodeKey(curFromNodeChild))) { | ||
| 471 | // Since the node is keyed it might be matched up later so we defer | ||
| 472 | // the actual removal to later | ||
| 473 | addKeyedRemoval(curFromNodeKey); | ||
| 474 | } else { | ||
| 475 | // NOTE: we skip nested keyed nodes from being removed since there is | ||
| 476 | // still a chance they will be matched up later | ||
| 477 | removeNode(curFromNodeChild, fromEl, true /* skip keyed nodes */); | ||
| 478 | } | ||
| 479 | curFromNodeChild = fromNextSibling; | ||
| 480 | } | ||
| 481 | } | ||
| 482 | |||
| 483 | function morphEl(fromEl, toEl, childrenOnly) { | ||
| 484 | var toElKey = getNodeKey(toEl); | ||
| 485 | |||
| 486 | if (toElKey) { | ||
| 487 | // If an element with an ID is being morphed then it will be in the final | ||
| 488 | // DOM so clear it out of the saved elements collection | ||
| 489 | delete fromNodesLookup[toElKey]; | ||
| 490 | } | ||
| 491 | |||
| 492 | if (!childrenOnly) { | ||
| 493 | // optional | ||
| 494 | var beforeUpdateResult = onBeforeElUpdated(fromEl, toEl); | ||
| 495 | if (beforeUpdateResult === false) { | ||
| 496 | return; | ||
| 497 | } else if (beforeUpdateResult instanceof HTMLElement) { | ||
| 498 | fromEl = beforeUpdateResult; | ||
| 499 | // reindex the new fromEl in case it's not in the same | ||
| 500 | // tree as the original fromEl | ||
| 501 | // (Phoenix LiveView sometimes returns a cloned tree, | ||
| 502 | // but keyed lookups would still point to the original tree) | ||
| 503 | indexTree(fromEl); | ||
| 504 | } | ||
| 505 | |||
| 506 | // update attributes on original DOM element first | ||
| 507 | morphAttrs(fromEl, toEl); | ||
| 508 | // optional | ||
| 509 | onElUpdated(fromEl); | ||
| 510 | |||
| 511 | if (onBeforeElChildrenUpdated(fromEl, toEl) === false) { | ||
| 512 | return; | ||
| 513 | } | ||
| 514 | } | ||
| 515 | |||
| 516 | if (fromEl.nodeName !== 'TEXTAREA') { | ||
| 517 | morphChildren(fromEl, toEl); | ||
| 518 | } else { | ||
| 519 | specialElHandlers.TEXTAREA(fromEl, toEl); | ||
| 520 | } | ||
| 521 | } | ||
| 522 | |||
| 523 | function morphChildren(fromEl, toEl) { | ||
| 524 | var skipFrom = skipFromChildren(fromEl, toEl); | ||
| 525 | var curToNodeChild = toEl.firstChild; | ||
| 526 | var curFromNodeChild = fromEl.firstChild; | ||
| 527 | var curToNodeKey; | ||
| 528 | var curFromNodeKey; | ||
| 529 | |||
| 530 | var fromNextSibling; | ||
| 531 | var toNextSibling; | ||
| 532 | var matchingFromEl; | ||
| 533 | |||
| 534 | // walk the children | ||
| 535 | outer: while (curToNodeChild) { | ||
| 536 | toNextSibling = curToNodeChild.nextSibling; | ||
| 537 | curToNodeKey = getNodeKey(curToNodeChild); | ||
| 538 | |||
| 539 | // walk the fromNode children all the way through | ||
| 540 | while (!skipFrom && curFromNodeChild) { | ||
| 541 | fromNextSibling = curFromNodeChild.nextSibling; | ||
| 542 | |||
| 543 | if (curToNodeChild.isSameNode && curToNodeChild.isSameNode(curFromNodeChild)) { | ||
| 544 | curToNodeChild = toNextSibling; | ||
| 545 | curFromNodeChild = fromNextSibling; | ||
| 546 | continue outer; | ||
| 547 | } | ||
| 548 | |||
| 549 | curFromNodeKey = getNodeKey(curFromNodeChild); | ||
| 550 | |||
| 551 | var curFromNodeType = curFromNodeChild.nodeType; | ||
| 552 | |||
| 553 | // this means if the curFromNodeChild doesnt have a match with the curToNodeChild | ||
| 554 | var isCompatible = undefined; | ||
| 555 | |||
| 556 | if (curFromNodeType === curToNodeChild.nodeType) { | ||
| 557 | if (curFromNodeType === ELEMENT_NODE) { | ||
| 558 | // Both nodes being compared are Element nodes | ||
| 559 | |||
| 560 | if (curToNodeKey) { | ||
| 561 | // The target node has a key so we want to match it up with the correct element | ||
| 562 | // in the original DOM tree | ||
| 563 | if (curToNodeKey !== curFromNodeKey) { | ||
| 564 | // The current element in the original DOM tree does not have a matching key so | ||
| 565 | // let's check our lookup to see if there is a matching element in the original | ||
| 566 | // DOM tree | ||
| 567 | if ((matchingFromEl = fromNodesLookup[curToNodeKey])) { | ||
| 568 | if (fromNextSibling === matchingFromEl) { | ||
| 569 | // Special case for single element removals. To avoid removing the original | ||
| 570 | // DOM node out of the tree (since that can break CSS transitions, etc.), | ||
| 571 | // we will instead discard the current node and wait until the next | ||
| 572 | // iteration to properly match up the keyed target element with its matching | ||
| 573 | // element in the original tree | ||
| 574 | isCompatible = false; | ||
| 575 | } else { | ||
| 576 | // We found a matching keyed element somewhere in the original DOM tree. | ||
| 577 | // Let's move the original DOM node into the current position and morph | ||
| 578 | // it. | ||
| 579 | |||
| 580 | // NOTE: We use insertBefore instead of replaceChild because we want to go through | ||
| 581 | // the `removeNode()` function for the node that is being discarded so that | ||
| 582 | // all lifecycle hooks are correctly invoked | ||
| 583 | fromEl.insertBefore(matchingFromEl, curFromNodeChild); | ||
| 584 | |||
| 585 | // fromNextSibling = curFromNodeChild.nextSibling; | ||
| 586 | |||
| 587 | if (curFromNodeKey) { | ||
| 588 | // Since the node is keyed it might be matched up later so we defer | ||
| 589 | // the actual removal to later | ||
| 590 | addKeyedRemoval(curFromNodeKey); | ||
| 591 | } else { | ||
| 592 | // NOTE: we skip nested keyed nodes from being removed since there is | ||
| 593 | // still a chance they will be matched up later | ||
| 594 | removeNode(curFromNodeChild, fromEl, true /* skip keyed nodes */); | ||
| 595 | } | ||
| 596 | |||
| 597 | curFromNodeChild = matchingFromEl; | ||
| 598 | curFromNodeKey = getNodeKey(curFromNodeChild); | ||
| 599 | } | ||
| 600 | } else { | ||
| 601 | // The nodes are not compatible since the "to" node has a key and there | ||
| 602 | // is no matching keyed node in the source tree | ||
| 603 | isCompatible = false; | ||
| 604 | } | ||
| 605 | } | ||
| 606 | } else if (curFromNodeKey) { | ||
| 607 | // The original has a key | ||
| 608 | isCompatible = false; | ||
| 609 | } | ||
| 610 | |||
| 611 | isCompatible = isCompatible !== false && compareNodeNames(curFromNodeChild, curToNodeChild); | ||
| 612 | if (isCompatible) { | ||
| 613 | // We found compatible DOM elements so transform | ||
| 614 | // the current "from" node to match the current | ||
| 615 | // target DOM node. | ||
| 616 | // MORPH | ||
| 617 | morphEl(curFromNodeChild, curToNodeChild); | ||
| 618 | } | ||
| 619 | |||
| 620 | } else if (curFromNodeType === TEXT_NODE || curFromNodeType == COMMENT_NODE) { | ||
| 621 | // Both nodes being compared are Text or Comment nodes | ||
| 622 | isCompatible = true; | ||
| 623 | // Simply update nodeValue on the original node to | ||
| 624 | // change the text value | ||
| 625 | if (curFromNodeChild.nodeValue !== curToNodeChild.nodeValue) { | ||
| 626 | curFromNodeChild.nodeValue = curToNodeChild.nodeValue; | ||
| 627 | } | ||
| 628 | |||
| 629 | } | ||
| 630 | } | ||
| 631 | |||
| 632 | if (isCompatible) { | ||
| 633 | // Advance both the "to" child and the "from" child since we found a match | ||
| 634 | // Nothing else to do as we already recursively called morphChildren above | ||
| 635 | curToNodeChild = toNextSibling; | ||
| 636 | curFromNodeChild = fromNextSibling; | ||
| 637 | continue outer; | ||
| 638 | } | ||
| 639 | |||
| 640 | // No compatible match so remove the old node from the DOM and continue trying to find a | ||
| 641 | // match in the original DOM. However, we only do this if the from node is not keyed | ||
| 642 | // since it is possible that a keyed node might match up with a node somewhere else in the | ||
| 643 | // target tree and we don't want to discard it just yet since it still might find a | ||
| 644 | // home in the final DOM tree. After everything is done we will remove any keyed nodes | ||
| 645 | // that didn't find a home | ||
| 646 | if (curFromNodeKey) { | ||
| 647 | // Since the node is keyed it might be matched up later so we defer | ||
| 648 | // the actual removal to later | ||
| 649 | addKeyedRemoval(curFromNodeKey); | ||
| 650 | } else { | ||
| 651 | // NOTE: we skip nested keyed nodes from being removed since there is | ||
| 652 | // still a chance they will be matched up later | ||
| 653 | removeNode(curFromNodeChild, fromEl, true /* skip keyed nodes */); | ||
| 654 | } | ||
| 655 | |||
| 656 | curFromNodeChild = fromNextSibling; | ||
| 657 | } // END: while(curFromNodeChild) {} | ||
| 658 | |||
| 659 | // If we got this far then we did not find a candidate match for | ||
| 660 | // our "to node" and we exhausted all of the children "from" | ||
| 661 | // nodes. Therefore, we will just append the current "to" node | ||
| 662 | // to the end | ||
| 663 | if (curToNodeKey && (matchingFromEl = fromNodesLookup[curToNodeKey]) && compareNodeNames(matchingFromEl, curToNodeChild)) { | ||
| 664 | // MORPH | ||
| 665 | if(!skipFrom){ addChild(fromEl, matchingFromEl); } | ||
| 666 | morphEl(matchingFromEl, curToNodeChild); | ||
| 667 | } else { | ||
| 668 | var onBeforeNodeAddedResult = onBeforeNodeAdded(curToNodeChild); | ||
| 669 | if (onBeforeNodeAddedResult !== false) { | ||
| 670 | if (onBeforeNodeAddedResult) { | ||
| 671 | curToNodeChild = onBeforeNodeAddedResult; | ||
| 672 | } | ||
| 673 | |||
| 674 | if (curToNodeChild.actualize) { | ||
| 675 | curToNodeChild = curToNodeChild.actualize(fromEl.ownerDocument || doc); | ||
| 676 | } | ||
| 677 | addChild(fromEl, curToNodeChild); | ||
| 678 | handleNodeAdded(curToNodeChild); | ||
| 679 | } | ||
| 680 | } | ||
| 681 | |||
| 682 | curToNodeChild = toNextSibling; | ||
| 683 | curFromNodeChild = fromNextSibling; | ||
| 684 | } | ||
| 685 | |||
| 686 | cleanupFromEl(fromEl, curFromNodeChild, curFromNodeKey); | ||
| 687 | |||
| 688 | var specialElHandler = specialElHandlers[fromEl.nodeName]; | ||
| 689 | if (specialElHandler) { | ||
| 690 | specialElHandler(fromEl, toEl); | ||
| 691 | } | ||
| 692 | } // END: morphChildren(...) | ||
| 693 | |||
| 694 | var morphedNode = fromNode; | ||
| 695 | var morphedNodeType = morphedNode.nodeType; | ||
| 696 | var toNodeType = toNode.nodeType; | ||
| 697 | |||
| 698 | if (!childrenOnly) { | ||
| 699 | // Handle the case where we are given two DOM nodes that are not | ||
| 700 | // compatible (e.g. <div> --> <span> or <div> --> TEXT) | ||
| 701 | if (morphedNodeType === ELEMENT_NODE) { | ||
| 702 | if (toNodeType === ELEMENT_NODE) { | ||
| 703 | if (!compareNodeNames(fromNode, toNode)) { | ||
| 704 | onNodeDiscarded(fromNode); | ||
| 705 | morphedNode = moveChildren(fromNode, createElementNS(toNode.nodeName, toNode.namespaceURI)); | ||
| 706 | } | ||
| 707 | } else { | ||
| 708 | // Going from an element node to a text node | ||
| 709 | morphedNode = toNode; | ||
| 710 | } | ||
| 711 | } else if (morphedNodeType === TEXT_NODE || morphedNodeType === COMMENT_NODE) { // Text or comment node | ||
| 712 | if (toNodeType === morphedNodeType) { | ||
| 713 | if (morphedNode.nodeValue !== toNode.nodeValue) { | ||
| 714 | morphedNode.nodeValue = toNode.nodeValue; | ||
| 715 | } | ||
| 716 | |||
| 717 | return morphedNode; | ||
| 718 | } else { | ||
| 719 | // Text node to something else | ||
| 720 | morphedNode = toNode; | ||
| 721 | } | ||
| 722 | } | ||
| 723 | } | ||
| 724 | |||
| 725 | if (morphedNode === toNode) { | ||
| 726 | // The "to node" was not compatible with the "from node" so we had to | ||
| 727 | // toss out the "from node" and use the "to node" | ||
| 728 | onNodeDiscarded(fromNode); | ||
| 729 | } else { | ||
| 730 | if (toNode.isSameNode && toNode.isSameNode(morphedNode)) { | ||
| 731 | return; | ||
| 732 | } | ||
| 733 | |||
| 734 | morphEl(morphedNode, toNode, childrenOnly); | ||
| 735 | |||
| 736 | // We now need to loop over any keyed nodes that might need to be | ||
| 737 | // removed. We only do the removal if we know that the keyed node | ||
| 738 | // never found a match. When a keyed node is matched up we remove | ||
| 739 | // it out of fromNodesLookup and we use fromNodesLookup to determine | ||
| 740 | // if a keyed node has been matched up or not | ||
| 741 | if (keyedRemovalList) { | ||
| 742 | for (var i=0, len=keyedRemovalList.length; i<len; i++) { | ||
| 743 | var elToRemove = fromNodesLookup[keyedRemovalList[i]]; | ||
| 744 | if (elToRemove) { | ||
| 745 | removeNode(elToRemove, elToRemove.parentNode, false); | ||
| 746 | } | ||
| 747 | } | ||
| 748 | } | ||
| 749 | } | ||
| 750 | |||
| 751 | if (!childrenOnly && morphedNode !== fromNode && fromNode.parentNode) { | ||
| 752 | if (morphedNode.actualize) { | ||
| 753 | morphedNode = morphedNode.actualize(fromNode.ownerDocument || doc); | ||
| 754 | } | ||
| 755 | // If we had to swap out the from node with a new node because the old | ||
| 756 | // node was not compatible with the target node then we need to | ||
| 757 | // replace the old DOM node in the original DOM tree. This is only | ||
| 758 | // possible if the original DOM node was part of a DOM tree which | ||
| 759 | // we know is the case if it has a parent node. | ||
| 760 | fromNode.parentNode.replaceChild(morphedNode, fromNode); | ||
| 761 | } | ||
| 762 | |||
| 763 | return morphedNode; | ||
| 764 | }; | ||
| 765 | } | ||
| 766 | |||
| 767 | var morphdom = morphdomFactory(morphAttrs); | ||
| 768 | |||
| 769 | export default morphdom; | ||
| @@ -0,0 +1,21 @@ | |||
| 1 | The MIT License (MIT) | ||
| 2 | |||
| 3 | Copyright (c) Patrick Steele-Idem <pnidem@gmail.com> (psteeleidem.com) | ||
| 4 | |||
| 5 | Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| 6 | of this software and associated documentation files (the "Software"), to deal | ||
| 7 | in the Software without restriction, including without limitation the rights | ||
| 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| 9 | copies of the Software, and to permit persons to whom the Software is | ||
| 10 | furnished to do so, subject to the following conditions: | ||
| 11 | |||
| 12 | The above copyright notice and this permission notice shall be included in | ||
| 13 | all copies or substantial portions of the Software. | ||
| 14 | |||
| 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
| 21 | THE SOFTWARE. | ||
| \ No newline at end of file | 21 | \ No newline at end of file | |
| @@ -1,10 +1,17 @@ | |||
| 1 | import { POPUP_TYPE, Popup } from '../../../popup.js'; | 1 | import { POPUP_RESULT, POPUP_TYPE, Popup } from '../../../popup.js'; |
| 2 | import { setSlashCommandAutoComplete } from '../../../slash-commands.js'; | 2 | import { setSlashCommandAutoComplete } from '../../../slash-commands.js'; |
| 3 | import { SlashCommandAbortController } from '../../../slash-commands/SlashCommandAbortController.js'; | 3 | import { SlashCommandAbortController } from '../../../slash-commands/SlashCommandAbortController.js'; |
| 4 | import { SlashCommandBreakPoint } from '../../../slash-commands/SlashCommandBreakPoint.js'; | ||
| 5 | import { SlashCommandClosure } from '../../../slash-commands/SlashCommandClosure.js'; | ||
| 6 | import { SlashCommandClosureResult } from '../../../slash-commands/SlashCommandClosureResult.js'; | ||
| 7 | import { SlashCommandDebugController } from '../../../slash-commands/SlashCommandDebugController.js'; | ||
| 8 | import { SlashCommandExecutor } from '../../../slash-commands/SlashCommandExecutor.js'; | ||
| 9 | import { SlashCommandParser } from '../../../slash-commands/SlashCommandParser.js'; | ||
| 4 | import { SlashCommandParserError } from '../../../slash-commands/SlashCommandParserError.js'; | 10 | import { SlashCommandParserError } from '../../../slash-commands/SlashCommandParserError.js'; |
| 5 | import { SlashCommandScope } from '../../../slash-commands/SlashCommandScope.js'; | 11 | import { SlashCommandScope } from '../../../slash-commands/SlashCommandScope.js'; |
| 6 | import { debounce, getSortableDelay } from '../../../utils.js'; | 12 | import { debounce, delay, getSortableDelay, showFontAwesomePicker } from '../../../utils.js'; |
| 7 | import { log, warn } from '../index.js'; | 13 | import { log, quickReplyApi, warn } from '../index.js'; |
| 14 | import morphdom from '../lib/morphdom-esm.js'; | ||
| 8 | import { QuickReplyContextLink } from './QuickReplyContextLink.js'; | 15 | import { QuickReplyContextLink } from './QuickReplyContextLink.js'; |
| 9 | import { QuickReplySet } from './QuickReplySet.js'; | 16 | import { QuickReplySet } from './QuickReplySet.js'; |
| 10 | import { ContextMenu } from './ui/ctx/ContextMenu.js'; | 17 | import { ContextMenu } from './ui/ctx/ContextMenu.js'; |
| @@ -21,48 +28,61 @@ export class QuickReply { | |||
| 21 | 28 | ||
| 22 | 29 | ||
| 23 | 30 | ||
| 24 | /**@type {Number}*/ id; | 31 | /**@type {number}*/ id; |
| 25 | /**@type {String}*/ label = ''; | 32 | /**@type {string}*/ icon; |
| 26 | /**@type {String}*/ title = ''; | 33 | /**@type {string}*/ label = ''; |
| 27 | /**@type {String}*/ message = ''; | 34 | /**@type {boolean}*/ showLabel = false; |
| 35 | /**@type {string}*/ title = ''; | ||
| 36 | /**@type {string}*/ message = ''; | ||
| 28 | 37 | ||
| 29 | /**@type {QuickReplyContextLink[]}*/ contextList; | 38 | /**@type {QuickReplyContextLink[]}*/ contextList; |
| 30 | 39 | ||
| 31 | /**@type {Boolean}*/ preventAutoExecute = true; | 40 | /**@type {boolean}*/ preventAutoExecute = true; |
| 32 | /**@type {Boolean}*/ isHidden = false; | 41 | /**@type {boolean}*/ isHidden = false; |
| 33 | /**@type {Boolean}*/ executeOnStartup = false; | 42 | /**@type {boolean}*/ executeOnStartup = false; |
| 34 | /**@type {Boolean}*/ executeOnUser = false; | 43 | /**@type {boolean}*/ executeOnUser = false; |
| 35 | /**@type {Boolean}*/ executeOnAi = false; | 44 | /**@type {boolean}*/ executeOnAi = false; |
| 36 | /**@type {Boolean}*/ executeOnChatChange = false; | 45 | /**@type {boolean}*/ executeOnChatChange = false; |
| 37 | /**@type {Boolean}*/ executeOnGroupMemberDraft = false; | 46 | /**@type {boolean}*/ executeOnGroupMemberDraft = false; |
| 38 | /**@type {String}*/ automationId = ''; | 47 | /**@type {string}*/ automationId = ''; |
| 39 | 48 | ||
| 40 | /**@type {Function}*/ onExecute; | 49 | /**@type {function}*/ onExecute; |
| 41 | /**@type {Function}*/ onDelete; | 50 | /**@type {(qr:QuickReply)=>AsyncGenerator<SlashCommandClosureResult|{closure:SlashCommandClosure, executor:SlashCommandExecutor|SlashCommandClosureResult}, SlashCommandClosureResult, boolean>}*/ onDebug; |
| 42 | /**@type {Function}*/ onUpdate; | 51 | /**@type {function}*/ onDelete; |
| 52 | /**@type {function}*/ onUpdate; | ||
| 53 | /**@type {function}*/ onInsertBefore; | ||
| 54 | /**@type {function}*/ onTransfer; | ||
| 43 | 55 | ||
| 44 | 56 | ||
| 45 | /**@type {HTMLElement}*/ dom; | 57 | /**@type {HTMLElement}*/ dom; |
| 58 | /**@type {HTMLElement}*/ domIcon; | ||
| 46 | /**@type {HTMLElement}*/ domLabel; | 59 | /**@type {HTMLElement}*/ domLabel; |
| 47 | /**@type {HTMLElement}*/ settingsDom; | 60 | /**@type {HTMLElement}*/ settingsDom; |
| 61 | /**@type {HTMLElement}*/ settingsDomIcon; | ||
| 48 | /**@type {HTMLInputElement}*/ settingsDomLabel; | 62 | /**@type {HTMLInputElement}*/ settingsDomLabel; |
| 49 | /**@type {HTMLTextAreaElement}*/ settingsDomMessage; | 63 | /**@type {HTMLTextAreaElement}*/ settingsDomMessage; |
| 50 | 64 | ||
| 51 | /**@type {Popup}*/ editorPopup; | 65 | /**@type {Popup}*/ editorPopup; |
| 66 | /**@type {HTMLElement}*/ editorDom; | ||
| 52 | 67 | ||
| 68 | /**@type {HTMLTextAreaElement}*/ editorMessage; | ||
| 69 | /**@type {HTMLTextAreaElement}*/ editorMessageLabel; | ||
| 70 | /**@type {HTMLElement}*/ editorSyntax; | ||
| 53 | /**@type {HTMLElement}*/ editorExecuteBtn; | 71 | /**@type {HTMLElement}*/ editorExecuteBtn; |
| 54 | /**@type {HTMLElement}*/ editorExecuteBtnPause; | 72 | /**@type {HTMLElement}*/ editorExecuteBtnPause; |
| 55 | /**@type {HTMLElement}*/ editorExecuteBtnStop; | 73 | /**@type {HTMLElement}*/ editorExecuteBtnStop; |
| 56 | /**@type {HTMLElement}*/ editorExecuteProgress; | 74 | /**@type {HTMLElement}*/ editorExecuteProgress; |
| 57 | /**@type {HTMLElement}*/ editorExecuteErrors; | 75 | /**@type {HTMLElement}*/ editorExecuteErrors; |
| 58 | /**@type {HTMLElement}*/ editorExecuteResult; | 76 | /**@type {HTMLElement}*/ editorExecuteResult; |
| 59 | /**@type {HTMLInputElement}*/ editorExecuteHide; | 77 | /**@type {HTMLElement}*/ editorDebugState; |
| 60 | /**@type {Promise}*/ editorExecutePromise; | 78 | /**@type {Promise}*/ editorExecutePromise; |
| 79 | /**@type {boolean}*/ isExecuting; | ||
| 61 | /**@type {SlashCommandAbortController}*/ abortController; | 80 | /**@type {SlashCommandAbortController}*/ abortController; |
| 81 | /**@type {SlashCommandDebugController}*/ debugController; | ||
| 62 | 82 | ||
| 63 | 83 | ||
| 64 | get hasContext() { | 84 | get hasContext() { |
| 65 | return this.contextList && this.contextList.length > 0; | 85 | return this.contextList && this.contextList.filter(it => it.set).length > 0; |
| 66 | } | 86 | } |
| 67 | 87 | ||
| 68 | 88 | ||
| @@ -75,6 +95,14 @@ export class QuickReply { | |||
| 75 | updateRender() { | 95 | updateRender() { |
| 76 | if (!this.dom) return; | 96 | if (!this.dom) return; |
| 77 | this.dom.title = this.title || this.message; | 97 | this.dom.title = this.title || this.message; |
| 98 | if (this.icon) { | ||
| 99 | this.domIcon.classList.remove('qr--hidden'); | ||
| 100 | if (this.showLabel) this.domLabel.classList.remove('qr--hidden'); | ||
| 101 | else this.domLabel.classList.add('qr--hidden'); | ||
| 102 | } else { | ||
| 103 | this.domIcon.classList.add('qr--hidden'); | ||
| 104 | this.domLabel.classList.remove('qr--hidden'); | ||
| 105 | } | ||
| 78 | this.domLabel.textContent = this.label; | 106 | this.domLabel.textContent = this.label; |
| 79 | this.dom.classList[this.hasContext ? 'add' : 'remove']('qr--hasCtx'); | 107 | this.dom.classList[this.hasContext ? 'add' : 'remove']('qr--hasCtx'); |
| 80 | } | 108 | } |
| @@ -105,9 +133,18 @@ export class QuickReply { | |||
| 105 | } | 133 | } |
| 106 | this.execute(); | 134 | this.execute(); |
| 107 | }); | 135 | }); |
| 136 | const icon = document.createElement('div'); { | ||
| 137 | this.domIcon = icon; | ||
| 138 | icon.classList.add('qr--button-icon'); | ||
| 139 | icon.classList.add('fa-solid'); | ||
| 140 | if (!this.icon) icon.classList.add('qr--hidden'); | ||
| 141 | else icon.classList.add(this.icon); | ||
| 142 | root.append(icon); | ||
| 143 | } | ||
| 108 | const lbl = document.createElement('div'); { | 144 | const lbl = document.createElement('div'); { |
| 109 | this.domLabel = lbl; | 145 | this.domLabel = lbl; |
| 110 | lbl.classList.add('qr--button-label'); | 146 | lbl.classList.add('qr--button-label'); |
| 147 | if (this.icon && !this.showLabel) lbl.classList.add('qr--hidden'); | ||
| 111 | lbl.textContent = this.label; | 148 | lbl.textContent = this.label; |
| 112 | root.append(lbl); | 149 | root.append(lbl); |
| 113 | } | 150 | } |
| @@ -138,14 +175,90 @@ export class QuickReply { | |||
| 138 | item.classList.add('qr--set-item'); | 175 | item.classList.add('qr--set-item'); |
| 139 | item.setAttribute('data-order', String(idx)); | 176 | item.setAttribute('data-order', String(idx)); |
| 140 | item.setAttribute('data-id', String(this.id)); | 177 | item.setAttribute('data-id', String(this.id)); |
| 178 | const adder = document.createElement('div'); { | ||
| 179 | adder.classList.add('qr--set-itemAdder'); | ||
| 180 | const actions = document.createElement('div'); { | ||
| 181 | actions.classList.add('qr--actions'); | ||
| 182 | const addNew = document.createElement('div'); { | ||
| 183 | addNew.classList.add('qr--action'); | ||
| 184 | addNew.classList.add('qr--add'); | ||
| 185 | addNew.classList.add('menu_button'); | ||
| 186 | addNew.classList.add('menu_button_icon'); | ||
| 187 | addNew.classList.add('fa-solid'); | ||
| 188 | addNew.classList.add('fa-plus'); | ||
| 189 | addNew.title = 'Add quick reply'; | ||
| 190 | addNew.addEventListener('click', ()=>this.onInsertBefore()); | ||
| 191 | actions.append(addNew); | ||
| 192 | } | ||
| 193 | const paste = document.createElement('div'); { | ||
| 194 | paste.classList.add('qr--action'); | ||
| 195 | paste.classList.add('qr--paste'); | ||
| 196 | paste.classList.add('menu_button'); | ||
| 197 | paste.classList.add('menu_button_icon'); | ||
| 198 | paste.classList.add('fa-solid'); | ||
| 199 | paste.classList.add('fa-paste'); | ||
| 200 | paste.title = 'Add quick reply from clipboard'; | ||
| 201 | paste.addEventListener('click', async()=>{ | ||
| 202 | const text = await navigator.clipboard.readText(); | ||
| 203 | this.onInsertBefore(text); | ||
| 204 | }); | ||
| 205 | actions.append(paste); | ||
| 206 | } | ||
| 207 | const importFile = document.createElement('div'); { | ||
| 208 | importFile.classList.add('qr--action'); | ||
| 209 | importFile.classList.add('qr--importFile'); | ||
| 210 | importFile.classList.add('menu_button'); | ||
| 211 | importFile.classList.add('menu_button_icon'); | ||
| 212 | importFile.classList.add('fa-solid'); | ||
| 213 | importFile.classList.add('fa-file-import'); | ||
| 214 | importFile.title = 'Add quick reply from JSON file'; | ||
| 215 | importFile.addEventListener('click', async()=>{ | ||
| 216 | const inp = document.createElement('input'); { | ||
| 217 | inp.type = 'file'; | ||
| 218 | inp.accept = '.json'; | ||
| 219 | inp.addEventListener('change', async()=>{ | ||
| 220 | if (inp.files.length > 0) { | ||
| 221 | for (const file of inp.files) { | ||
| 222 | const text = await file.text(); | ||
| 223 | this.onInsertBefore(text); | ||
| 224 | } | ||
| 225 | } | ||
| 226 | }); | ||
| 227 | inp.click(); | ||
| 228 | } | ||
| 229 | }); | ||
| 230 | actions.append(importFile); | ||
| 231 | } | ||
| 232 | adder.append(actions); | ||
| 233 | } | ||
| 234 | item.append(adder); | ||
| 235 | } | ||
| 236 | const itemContent = document.createElement('div'); { | ||
| 237 | itemContent.classList.add('qr--content'); | ||
| 141 | const drag = document.createElement('div'); { | 238 | const drag = document.createElement('div'); { |
| 142 | drag.classList.add('drag-handle'); | 239 | drag.classList.add('drag-handle'); |
| 143 | drag.classList.add('ui-sortable-handle'); | 240 | drag.classList.add('ui-sortable-handle'); |
| 144 | drag.textContent = '☰'; | 241 | drag.textContent = '☰'; |
| 145 | item.append(drag); | 242 | itemContent.append(drag); |
| 146 | } | 243 | } |
| 147 | const lblContainer = document.createElement('div'); { | 244 | const lblContainer = document.createElement('div'); { |
| 148 | lblContainer.classList.add('qr--set-itemLabelContainer'); | 245 | lblContainer.classList.add('qr--set-itemLabelContainer'); |
| 246 | const icon = document.createElement('div'); { | ||
| 247 | this.settingsDomIcon = icon; | ||
| 248 | icon.title = 'Click to change icon'; | ||
| 249 | icon.classList.add('qr--set-itemIcon'); | ||
| 250 | icon.classList.add('menu_button'); | ||
| 251 | icon.classList.add('fa-fw'); | ||
| 252 | if (this.icon) { | ||
| 253 | icon.classList.add('fa-solid'); | ||
| 254 | icon.classList.add(this.icon); | ||
| 255 | } | ||
| 256 | icon.addEventListener('click', async()=>{ | ||
| 257 | let value = await showFontAwesomePicker(); | ||
| 258 | this.updateIcon(value); | ||
| 259 | }); | ||
| 260 | lblContainer.append(icon); | ||
| 261 | } | ||
| 149 | const lbl = document.createElement('input'); { | 262 | const lbl = document.createElement('input'); { |
| 150 | this.settingsDomLabel = lbl; | 263 | this.settingsDomLabel = lbl; |
| 151 | lbl.classList.add('qr--set-itemLabel'); | 264 | lbl.classList.add('qr--set-itemLabel'); |
| @@ -154,20 +267,23 @@ export class QuickReply { | |||
| 154 | lbl.addEventListener('input', ()=>this.updateLabel(lbl.value)); | 267 | lbl.addEventListener('input', ()=>this.updateLabel(lbl.value)); |
| 155 | lblContainer.append(lbl); | 268 | lblContainer.append(lbl); |
| 156 | } | 269 | } |
| 157 | item.append(lblContainer); | 270 | itemContent.append(lblContainer); |
| 271 | } | ||
| 272 | item.append(itemContent); | ||
| 158 | } | 273 | } |
| 159 | const optContainer = document.createElement('div'); { | 274 | const optContainer = document.createElement('div'); { |
| 160 | optContainer.classList.add('qr--set-optionsContainer'); | 275 | optContainer.classList.add('qr--set-optionsContainer'); |
| 161 | const opt = document.createElement('div'); { | 276 | const opt = document.createElement('div'); { |
| 162 | opt.classList.add('qr--action'); | 277 | opt.classList.add('qr--action'); |
| 163 | opt.classList.add('menu_button'); | 278 | opt.classList.add('menu_button'); |
| 279 | opt.classList.add('fa-fw'); | ||
| 164 | opt.classList.add('fa-solid'); | 280 | opt.classList.add('fa-solid'); |
| 165 | opt.textContent = '⁝'; | 281 | opt.textContent = '⁝'; |
| 166 | opt.title = 'Additional options:\n - large editor\n - context menu\n - auto-execution\n - tooltip'; | 282 | opt.title = 'Additional options:\n - large editor\n - context menu\n - auto-execution\n - tooltip'; |
| 167 | opt.addEventListener('click', ()=>this.showEditor()); | 283 | opt.addEventListener('click', ()=>this.showEditor()); |
| 168 | optContainer.append(opt); | 284 | optContainer.append(opt); |
| 169 | } | 285 | } |
| 170 | item.append(optContainer); | 286 | itemContent.append(optContainer); |
| 171 | } | 287 | } |
| 172 | const mes = document.createElement('textarea'); { | 288 | const mes = document.createElement('textarea'); { |
| 173 | this.settingsDomMessage = mes; | 289 | this.settingsDomMessage = mes; |
| @@ -176,22 +292,89 @@ export class QuickReply { | |||
| 176 | mes.value = this.message; | 292 | mes.value = this.message; |
| 177 | //HACK need to use jQuery to catch the triggered event from the expanded editor | 293 | //HACK need to use jQuery to catch the triggered event from the expanded editor |
| 178 | $(mes).on('input', ()=>this.updateMessage(mes.value)); | 294 | $(mes).on('input', ()=>this.updateMessage(mes.value)); |
| 179 | item.append(mes); | 295 | itemContent.append(mes); |
| 180 | } | 296 | } |
| 181 | const actions = document.createElement('div'); { | 297 | const actions = document.createElement('div'); { |
| 182 | actions.classList.add('qr--actions'); | 298 | actions.classList.add('qr--actions'); |
| 299 | const move = document.createElement('div'); { | ||
| 300 | move.classList.add('qr--action'); | ||
| 301 | move.classList.add('menu_button'); | ||
| 302 | move.classList.add('fa-fw'); | ||
| 303 | move.classList.add('fa-solid'); | ||
| 304 | move.classList.add('fa-truck-arrow-right'); | ||
| 305 | move.title = 'Move quick reply to other set'; | ||
| 306 | move.addEventListener('click', ()=>this.onTransfer(this)); | ||
| 307 | actions.append(move); | ||
| 308 | } | ||
| 309 | const copy = document.createElement('div'); { | ||
| 310 | copy.classList.add('qr--action'); | ||
| 311 | copy.classList.add('menu_button'); | ||
| 312 | copy.classList.add('fa-fw'); | ||
| 313 | copy.classList.add('fa-solid'); | ||
| 314 | copy.classList.add('fa-copy'); | ||
| 315 | copy.title = 'Copy quick reply to clipboard'; | ||
| 316 | copy.addEventListener('click', async()=>{ | ||
| 317 | await navigator.clipboard.writeText(JSON.stringify(this)); | ||
| 318 | copy.classList.add('qr--success'); | ||
| 319 | await delay(3010); | ||
| 320 | copy.classList.remove('qr--success'); | ||
| 321 | }); | ||
| 322 | actions.append(copy); | ||
| 323 | } | ||
| 324 | const cut = document.createElement('div'); { | ||
| 325 | cut.classList.add('qr--action'); | ||
| 326 | cut.classList.add('menu_button'); | ||
| 327 | cut.classList.add('fa-fw'); | ||
| 328 | cut.classList.add('fa-solid'); | ||
| 329 | cut.classList.add('fa-cut'); | ||
| 330 | cut.title = 'Cut quick reply to clipboard (copy and remove)'; | ||
| 331 | cut.addEventListener('click', async()=>{ | ||
| 332 | await navigator.clipboard.writeText(JSON.stringify(this)); | ||
| 333 | this.delete(); | ||
| 334 | }); | ||
| 335 | actions.append(cut); | ||
| 336 | } | ||
| 337 | const exp = document.createElement('div'); { | ||
| 338 | exp.classList.add('qr--action'); | ||
| 339 | exp.classList.add('menu_button'); | ||
| 340 | exp.classList.add('fa-fw'); | ||
| 341 | exp.classList.add('fa-solid'); | ||
| 342 | exp.classList.add('fa-file-export'); | ||
| 343 | exp.title = 'Export quick reply as file'; | ||
| 344 | exp.addEventListener('click', ()=>{ | ||
| 345 | const blob = new Blob([JSON.stringify(this)], { type:'text' }); | ||
| 346 | const url = URL.createObjectURL(blob); | ||
| 347 | const a = document.createElement('a'); { | ||
| 348 | a.href = url; | ||
| 349 | a.download = `${this.label}.qr.json`; | ||
| 350 | a.click(); | ||
| 351 | } | ||
| 352 | }); | ||
| 353 | actions.append(exp); | ||
| 354 | } | ||
| 183 | const del = document.createElement('div'); { | 355 | const del = document.createElement('div'); { |
| 184 | del.classList.add('qr--action'); | 356 | del.classList.add('qr--action'); |
| 185 | del.classList.add('menu_button'); | 357 | del.classList.add('menu_button'); |
| 186 | del.classList.add('menu_button_icon'); | 358 | del.classList.add('fa-fw'); |
| 187 | del.classList.add('fa-solid'); | 359 | del.classList.add('fa-solid'); |
| 188 | del.classList.add('fa-trash-can'); | 360 | del.classList.add('fa-trash-can'); |
| 189 | del.classList.add('redWarningBG'); | 361 | del.classList.add('redWarningBG'); |
| 190 | del.title = 'Remove quick reply'; | 362 | del.title = 'Remove Quick Reply\n---\nShit+Click to skip confirmation'; |
| 191 | del.addEventListener('click', ()=>this.delete()); | 363 | del.addEventListener('click', async(evt)=>{ |
| 364 | if (!evt.shiftKey) { | ||
| 365 | const result = await Popup.show.confirm( | ||
| 366 | 'Remove Quick Reply', | ||
| 367 | 'Are you sure you want to remove this Quick Reply?', | ||
| 368 | ); | ||
| 369 | if (result != POPUP_RESULT.AFFIRMATIVE) { | ||
| 370 | return; | ||
| 371 | } | ||
| 372 | } | ||
| 373 | this.delete(); | ||
| 374 | }); | ||
| 192 | actions.append(del); | 375 | actions.append(del); |
| 193 | } | 376 | } |
| 194 | item.append(actions); | 377 | itemContent.append(actions); |
| 195 | } | 378 | } |
| 196 | } | 379 | } |
| 197 | } | 380 | } |
| @@ -208,22 +391,156 @@ export class QuickReply { | |||
| 208 | /**@type {HTMLElement} */ | 391 | /**@type {HTMLElement} */ |
| 209 | // @ts-ignore | 392 | // @ts-ignore |
| 210 | const dom = this.template.cloneNode(true); | 393 | const dom = this.template.cloneNode(true); |
| 394 | this.editorDom = dom; | ||
| 211 | this.editorPopup = new Popup(dom, POPUP_TYPE.TEXT, undefined, { okButton: 'OK', wide: true, large: true, rows: 1 }); | 395 | this.editorPopup = new Popup(dom, POPUP_TYPE.TEXT, undefined, { okButton: 'OK', wide: true, large: true, rows: 1 }); |
| 212 | const popupResult = this.editorPopup.show(); | 396 | const popupResult = this.editorPopup.show(); |
| 213 | 397 | ||
| 214 | // basics | 398 | // basics |
| 399 | /**@type {HTMLElement}*/ | ||
| 400 | const icon = dom.querySelector('#qr--modal-icon'); | ||
| 401 | if (this.icon) { | ||
| 402 | icon.classList.add('fa-solid'); | ||
| 403 | icon.classList.add(this.icon); | ||
| 404 | } | ||
| 405 | else { | ||
| 406 | icon.textContent = '…'; | ||
| 407 | } | ||
| 408 | icon.addEventListener('click', async()=>{ | ||
| 409 | let value = await showFontAwesomePicker(); | ||
| 410 | if (value === null) return; | ||
| 411 | if (this.icon) icon.classList.remove(this.icon); | ||
| 412 | if (value == '') { | ||
| 413 | icon.classList.remove('fa-solid'); | ||
| 414 | icon.textContent = '…'; | ||
| 415 | } else { | ||
| 416 | icon.textContent = ''; | ||
| 417 | icon.classList.add('fa-solid'); | ||
| 418 | icon.classList.add(value); | ||
| 419 | } | ||
| 420 | this.updateIcon(value); | ||
| 421 | }); | ||
| 422 | /**@type {HTMLInputElement}*/ | ||
| 423 | const showLabel = dom.querySelector('#qr--modal-showLabel'); | ||
| 424 | showLabel.checked = this.showLabel; | ||
| 425 | showLabel.addEventListener('click', ()=>{ | ||
| 426 | this.updateShowLabel(showLabel.checked); | ||
| 427 | }); | ||
| 215 | /**@type {HTMLInputElement}*/ | 428 | /**@type {HTMLInputElement}*/ |
| 216 | const label = dom.querySelector('#qr--modal-label'); | 429 | const label = dom.querySelector('#qr--modal-label'); |
| 217 | label.value = this.label; | 430 | label.value = this.label; |
| 218 | label.addEventListener('input', ()=>{ | 431 | label.addEventListener('input', ()=>{ |
| 219 | this.updateLabel(label.value); | 432 | this.updateLabel(label.value); |
| 220 | }); | 433 | }); |
| 434 | let switcherList; | ||
| 435 | dom.querySelector('#qr--modal-switcher').addEventListener('click', (evt)=>{ | ||
| 436 | if (switcherList) { | ||
| 437 | switcherList.remove(); | ||
| 438 | switcherList = null; | ||
| 439 | return; | ||
| 440 | } | ||
| 441 | const list = document.createElement('ul'); { | ||
| 442 | switcherList = list; | ||
| 443 | list.classList.add('qr--modal-switcherList'); | ||
| 444 | const makeList = (qrs)=>{ | ||
| 445 | const setItem = document.createElement('li'); { | ||
| 446 | setItem.classList.add('qr--modal-switcherItem'); | ||
| 447 | setItem.addEventListener('click', ()=>{ | ||
| 448 | list.innerHTML = ''; | ||
| 449 | for (const qrs of quickReplyApi.listSets()) { | ||
| 450 | const item = document.createElement('li'); { | ||
| 451 | item.classList.add('qr--modal-switcherItem'); | ||
| 452 | item.addEventListener('click', ()=>{ | ||
| 453 | list.innerHTML = ''; | ||
| 454 | makeList(quickReplyApi.getSetByName(qrs)); | ||
| 455 | }); | ||
| 456 | const lbl = document.createElement('div'); { | ||
| 457 | lbl.classList.add('qr--label'); | ||
| 458 | lbl.textContent = qrs; | ||
| 459 | item.append(lbl); | ||
| 460 | } | ||
| 461 | list.append(item); | ||
| 462 | } | ||
| 463 | } | ||
| 464 | }); | ||
| 465 | const lbl = document.createElement('div'); { | ||
| 466 | lbl.classList.add('qr--label'); | ||
| 467 | const icon = document.createElement('i'); { | ||
| 468 | icon.classList.add('fa-solid'); | ||
| 469 | icon.classList.add('fa-arrow-alt-circle-right'); | ||
| 470 | icon.classList.add('menu_button'); | ||
| 471 | lbl.append(icon); | ||
| 472 | } | ||
| 473 | const text = document.createElement('span'); { | ||
| 474 | text.textContent = 'Switch QR Sets...'; | ||
| 475 | lbl.append(text); | ||
| 476 | } | ||
| 477 | setItem.append(lbl); | ||
| 478 | } | ||
| 479 | list.append(setItem); | ||
| 480 | } | ||
| 481 | const addItem = document.createElement('li'); { | ||
| 482 | addItem.classList.add('qr--modal-switcherItem'); | ||
| 483 | addItem.addEventListener('click', ()=>{ | ||
| 484 | const qr = quickReplyApi.getSetByQr(this).addQuickReply(); | ||
| 485 | this.editorPopup.completeAffirmative(); | ||
| 486 | qr.showEditor(); | ||
| 487 | }); | ||
| 488 | const lbl = document.createElement('div'); { | ||
| 489 | lbl.classList.add('qr--label'); | ||
| 490 | const icon = document.createElement('i'); { | ||
| 491 | icon.classList.add('fa-solid'); | ||
| 492 | icon.classList.add('fa-plus'); | ||
| 493 | icon.classList.add('menu_button'); | ||
| 494 | lbl.append(icon); | ||
| 495 | } | ||
| 496 | const text = document.createElement('span'); { | ||
| 497 | text.textContent = 'Add QR'; | ||
| 498 | lbl.append(text); | ||
| 499 | } | ||
| 500 | addItem.append(lbl); | ||
| 501 | } | ||
| 502 | list.append(addItem); | ||
| 503 | } | ||
| 504 | for (const qr of qrs.qrList.toSorted((a,b)=>a.label.toLowerCase().localeCompare(b.label.toLowerCase()))) { | ||
| 505 | const item = document.createElement('li'); { | ||
| 506 | item.classList.add('qr--modal-switcherItem'); | ||
| 507 | if (qr == this) item.classList.add('qr--current'); | ||
| 508 | else item.addEventListener('click', ()=>{ | ||
| 509 | this.editorPopup.completeAffirmative(); | ||
| 510 | qr.showEditor(); | ||
| 511 | }); | ||
| 512 | const lbl = document.createElement('div'); { | ||
| 513 | lbl.classList.add('qr--label'); | ||
| 514 | lbl.textContent = qr.label; | ||
| 515 | item.append(lbl); | ||
| 516 | } | ||
| 517 | const id = document.createElement('div'); { | ||
| 518 | id.classList.add('qr--id'); | ||
| 519 | id.textContent = qr.id.toString(); | ||
| 520 | item.append(id); | ||
| 521 | } | ||
| 522 | const mes = document.createElement('div'); { | ||
| 523 | mes.classList.add('qr--message'); | ||
| 524 | mes.textContent = qr.message; | ||
| 525 | item.append(mes); | ||
| 526 | } | ||
| 527 | list.append(item); | ||
| 528 | } | ||
| 529 | } | ||
| 530 | }; | ||
| 531 | makeList(quickReplyApi.getSetByQr(this)); | ||
| 532 | } | ||
| 533 | label.parentElement.append(list); | ||
| 534 | }); | ||
| 221 | /**@type {HTMLInputElement}*/ | 535 | /**@type {HTMLInputElement}*/ |
| 222 | const title = dom.querySelector('#qr--modal-title'); | 536 | const title = dom.querySelector('#qr--modal-title'); |
| 223 | title.value = this.title; | 537 | title.value = this.title; |
| 224 | title.addEventListener('input', () => { | 538 | title.addEventListener('input', () => { |
| 225 | this.updateTitle(title.value); | 539 | this.updateTitle(title.value); |
| 226 | }); | 540 | }); |
| 541 | /**@type {HTMLElement}*/ | ||
| 542 | const messageSyntaxInner = dom.querySelector('#qr--modal-messageSyntaxInner'); | ||
| 543 | this.editorSyntax = messageSyntaxInner; | ||
| 227 | /**@type {HTMLInputElement}*/ | 544 | /**@type {HTMLInputElement}*/ |
| 228 | const wrap = dom.querySelector('#qr--modal-wrap'); | 545 | const wrap = dom.querySelector('#qr--modal-wrap'); |
| 229 | wrap.checked = JSON.parse(localStorage.getItem('qr--wrap') ?? 'false'); | 546 | wrap.checked = JSON.parse(localStorage.getItem('qr--wrap') ?? 'false'); |
| @@ -235,9 +552,15 @@ export class QuickReply { | |||
| 235 | if (wrap.checked) { | 552 | if (wrap.checked) { |
| 236 | message.style.whiteSpace = 'pre-wrap'; | 553 | message.style.whiteSpace = 'pre-wrap'; |
| 237 | messageSyntaxInner.style.whiteSpace = 'pre-wrap'; | 554 | messageSyntaxInner.style.whiteSpace = 'pre-wrap'; |
| 555 | if (this.clone) { | ||
| 556 | this.clone.style.whiteSpace = 'pre-wrap'; | ||
| 557 | } | ||
| 238 | } else { | 558 | } else { |
| 239 | message.style.whiteSpace = 'pre'; | 559 | message.style.whiteSpace = 'pre'; |
| 240 | messageSyntaxInner.style.whiteSpace = 'pre'; | 560 | messageSyntaxInner.style.whiteSpace = 'pre'; |
| 561 | if (this.clone) { | ||
| 562 | this.clone.style.whiteSpace = 'pre'; | ||
| 563 | } | ||
| 241 | } | 564 | } |
| 242 | updateScrollDebounced(); | 565 | updateScrollDebounced(); |
| 243 | }; | 566 | }; |
| @@ -261,11 +584,8 @@ export class QuickReply { | |||
| 261 | }); | 584 | }); |
| 262 | }; | 585 | }; |
| 263 | const updateScrollDebounced = updateScroll; | 586 | const updateScrollDebounced = updateScroll; |
| 264 | const updateSyntax = ()=>{ | ||
| 265 | messageSyntaxInner.innerHTML = hljs.highlight(`${message.value}${message.value.slice(-1) == '\n' ? ' ' : ''}`, { language:'stscript', ignoreIllegals:true })?.value; | ||
| 266 | }; | ||
| 267 | const updateSyntaxEnabled = ()=>{ | 587 | const updateSyntaxEnabled = ()=>{ |
| 268 | if (JSON.parse(localStorage.getItem('qr--syntax'))) { | 588 | if (syntax.checked) { |
| 269 | dom.querySelector('#qr--modal-messageHolder').classList.remove('qr--noSyntax'); | 589 | dom.querySelector('#qr--modal-messageHolder').classList.remove('qr--noSyntax'); |
| 270 | } else { | 590 | } else { |
| 271 | dom.querySelector('#qr--modal-messageHolder').classList.add('qr--noSyntax'); | 591 | dom.querySelector('#qr--modal-messageHolder').classList.add('qr--noSyntax'); |
| @@ -296,48 +616,108 @@ export class QuickReply { | |||
| 296 | localStorage.setItem('qr--syntax', JSON.stringify(syntax.checked)); | 616 | localStorage.setItem('qr--syntax', JSON.stringify(syntax.checked)); |
| 297 | updateSyntaxEnabled(); | 617 | updateSyntaxEnabled(); |
| 298 | }); | 618 | }); |
| 619 | if (navigator.keyboard) { | ||
| 620 | navigator.keyboard.getLayoutMap().then(it=>dom.querySelector('#qr--modal-commentKey').textContent = it.get('Backslash')); | ||
| 621 | } else { | ||
| 622 | dom.querySelector('#qr--modal-commentKey').closest('small').remove(); | ||
| 623 | } | ||
| 624 | this.editorMessageLabel = dom.querySelector('label[for="qr--modal-message"]'); | ||
| 299 | /**@type {HTMLTextAreaElement}*/ | 625 | /**@type {HTMLTextAreaElement}*/ |
| 300 | const message = dom.querySelector('#qr--modal-message'); | 626 | const message = dom.querySelector('#qr--modal-message'); |
| 627 | this.editorMessage = message; | ||
| 301 | message.value = this.message; | 628 | message.value = this.message; |
| 629 | const updateMessageDebounced = debounce((value)=>this.updateMessage(value), 10); | ||
| 302 | message.addEventListener('input', () => { | 630 | message.addEventListener('input', () => { |
| 303 | updateSyntax(); | 631 | updateMessageDebounced(message.value); |
| 304 | this.updateMessage(message.value); | ||
| 305 | updateScrollDebounced(); | 632 | updateScrollDebounced(); |
| 306 | }); | 633 | }, { passive:true }); |
| 307 | setSlashCommandAutoComplete(message, true); | 634 | const getLineStart = ()=>{ |
| 308 | //TODO move tab support for textarea into its own helper(?) and use for both this and .editor_maximize | 635 | const start = message.selectionStart; |
| 636 | const end = message.selectionEnd; | ||
| 637 | let lineStart; | ||
| 638 | if (start == 0 || message.value[start - 1] == '\n') { | ||
| 639 | // cursor is already at beginning of line | ||
| 640 | // -> keep start | ||
| 641 | lineStart = start; | ||
| 642 | } else { | ||
| 643 | // cursor is at end of line or somewhere in the line | ||
| 644 | // -> find last newline before cursor and start after that | ||
| 645 | lineStart = message.value.lastIndexOf('\n', start - 1) + 1; | ||
| 646 | } | ||
| 647 | return lineStart; | ||
| 648 | }; | ||
| 309 | message.addEventListener('keydown', async(evt) => { | 649 | message.addEventListener('keydown', async(evt) => { |
| 650 | if (this.isExecuting) return; | ||
| 310 | if (evt.key == 'Tab' && !evt.shiftKey && !evt.ctrlKey && !evt.altKey) { | 651 | if (evt.key == 'Tab' && !evt.shiftKey && !evt.ctrlKey && !evt.altKey) { |
| 652 | // increase indent | ||
| 311 | evt.preventDefault(); | 653 | evt.preventDefault(); |
| 312 | const start = message.selectionStart; | 654 | const start = message.selectionStart; |
| 313 | const end = message.selectionEnd; | 655 | const end = message.selectionEnd; |
| 314 | if (end - start > 0 && message.value.substring(start, end).includes('\n')) { | 656 | if (end - start > 0 && message.value.substring(start, end).includes('\n')) { |
| 315 | const lineStart = message.value.lastIndexOf('\n', start); | 657 | evt.stopImmediatePropagation(); |
| 316 | const count = message.value.substring(lineStart, end).split('\n').length - 1; | 658 | evt.stopPropagation(); |
| 317 | message.value = `${message.value.substring(0, lineStart)}${message.value.substring(lineStart, end).replace(/\n/g, '\n\t')}${message.value.substring(end)}`; | 659 | const lineStart = getLineStart(); |
| 318 | message.selectionStart = start + 1; | 660 | message.selectionStart = lineStart; |
| 319 | message.selectionEnd = end + count; | 661 | const affectedLines = message.value.substring(lineStart, end).split('\n'); |
| 320 | updateSyntax(); | 662 | // document.execCommand is deprecated (and potentially buggy in some browsers) but the only way to retain undo-history |
| 321 | } else { | 663 | document.execCommand('insertText', false, `\t${affectedLines.join('\n\t')}`); |
| 322 | message.value = `${message.value.substring(0, start)}\t${message.value.substring(end)}`; | ||
| 323 | message.selectionStart = start + 1; | 664 | message.selectionStart = start + 1; |
| 324 | message.selectionEnd = end + 1; | 665 | message.selectionEnd = end + affectedLines.length; |
| 325 | updateSyntax(); | 666 | message.dispatchEvent(new Event('input', { bubbles:true })); |
| 667 | } else if (!(ac.isReplaceable && ac.isActive)) { | ||
| 668 | evt.stopImmediatePropagation(); | ||
| 669 | evt.stopPropagation(); | ||
| 670 | // document.execCommand is deprecated (and potentially buggy in some browsers) but the only way to retain undo-history | ||
| 671 | document.execCommand('insertText', false, '\t'); | ||
| 672 | message.dispatchEvent(new Event('input', { bubbles:true })); | ||
| 326 | } | 673 | } |
| 327 | } else if (evt.key == 'Tab' && evt.shiftKey && !evt.ctrlKey && !evt.altKey) { | 674 | } else if (evt.key == 'Tab' && evt.shiftKey && !evt.ctrlKey && !evt.altKey) { |
| 675 | // decrease indent | ||
| 328 | evt.preventDefault(); | 676 | evt.preventDefault(); |
| 677 | evt.stopImmediatePropagation(); | ||
| 678 | evt.stopPropagation(); | ||
| 329 | const start = message.selectionStart; | 679 | const start = message.selectionStart; |
| 330 | const end = message.selectionEnd; | 680 | const end = message.selectionEnd; |
| 331 | const lineStart = message.value.lastIndexOf('\n', start); | 681 | const lineStart = getLineStart(); |
| 332 | const count = message.value.substring(lineStart, end).split('\n\t').length - 1; | 682 | message.selectionStart = lineStart; |
| 333 | message.value = `${message.value.substring(0, lineStart)}${message.value.substring(lineStart, end).replace(/\n\t/g, '\n')}${message.value.substring(end)}`; | 683 | const affectedLines = message.value.substring(lineStart, end).split('\n'); |
| 334 | message.selectionStart = start - 1; | 684 | const newText = affectedLines.map(it=>it.replace(/^\t/, '')).join('\n'); |
| 335 | message.selectionEnd = end - count; | 685 | const delta = affectedLines.join('\n').length - newText.length; |
| 336 | updateSyntax(); | 686 | // document.execCommand is deprecated (and potentially buggy in some browsers) but the only way to retain undo-history |
| 337 | } else if (evt.key == 'Enter' && evt.ctrlKey && !evt.shiftKey && !evt.altKey) { | 687 | if (delta > 0) { |
| 688 | if (newText == '') { | ||
| 689 | document.execCommand('delete', false); | ||
| 690 | } else { | ||
| 691 | document.execCommand('insertText', false, newText); | ||
| 692 | } | ||
| 693 | message.selectionStart = start - (affectedLines[0].startsWith('\t') ? 1 : 0); | ||
| 694 | message.selectionEnd = end - delta; | ||
| 695 | message.dispatchEvent(new Event('input', { bubbles:true })); | ||
| 696 | } else { | ||
| 697 | message.selectionStart = start; | ||
| 698 | } | ||
| 699 | } else if (evt.key == 'Enter' && !evt.ctrlKey && !evt.shiftKey && !evt.altKey && !(ac.isReplaceable && ac.isActive)) { | ||
| 700 | // new line, keep indent | ||
| 701 | const start = message.selectionStart; | ||
| 702 | const end = message.selectionEnd; | ||
| 703 | let lineStart = getLineStart(); | ||
| 704 | const indent = /^([^\S\n]*)/.exec(message.value.slice(lineStart))[1] ?? ''; | ||
| 705 | if (indent.length) { | ||
| 706 | evt.stopImmediatePropagation(); | ||
| 338 | evt.stopPropagation(); | 707 | evt.stopPropagation(); |
| 339 | evt.preventDefault(); | 708 | evt.preventDefault(); |
| 709 | // document.execCommand is deprecated (and potentially buggy in some browsers) but the only way to retain undo-history | ||
| 710 | document.execCommand('insertText', false, `\n${indent}`); | ||
| 711 | message.selectionStart = start + 1 + indent.length; | ||
| 712 | message.selectionEnd = message.selectionStart; | ||
| 713 | message.dispatchEvent(new Event('input', { bubbles:true })); | ||
| 714 | } | ||
| 715 | } else if (evt.key == 'Enter' && evt.ctrlKey && !evt.shiftKey && !evt.altKey) { | ||
| 340 | if (executeShortcut.checked) { | 716 | if (executeShortcut.checked) { |
| 717 | // execute QR | ||
| 718 | evt.stopImmediatePropagation(); | ||
| 719 | evt.stopPropagation(); | ||
| 720 | evt.preventDefault(); | ||
| 341 | const selectionStart = message.selectionStart; | 721 | const selectionStart = message.selectionStart; |
| 342 | const selectionEnd = message.selectionEnd; | 722 | const selectionEnd = message.selectionEnd; |
| 343 | message.blur(); | 723 | message.blur(); |
| @@ -348,17 +728,175 @@ export class QuickReply { | |||
| 348 | message.selectionEnd = selectionEnd; | 728 | message.selectionEnd = selectionEnd; |
| 349 | } | 729 | } |
| 350 | } | 730 | } |
| 731 | } else if (evt.key == 'F9' && !evt.ctrlKey && !evt.shiftKey && !evt.altKey) { | ||
| 732 | // toggle breakpoint | ||
| 733 | evt.stopImmediatePropagation(); | ||
| 734 | evt.stopPropagation(); | ||
| 735 | evt.preventDefault(); | ||
| 736 | preBreakPointStart = message.selectionStart; | ||
| 737 | preBreakPointEnd = message.selectionEnd; | ||
| 738 | toggleBreakpoint(); | ||
| 739 | } else if (evt.code == 'Backslash' && evt.ctrlKey && !evt.shiftKey && !evt.altKey) { | ||
| 740 | // toggle block comment | ||
| 741 | // (evt.code will use the same physical key on the keyboard across different keyboard layouts) | ||
| 742 | evt.stopImmediatePropagation(); | ||
| 743 | evt.stopPropagation(); | ||
| 744 | evt.preventDefault(); | ||
| 745 | // check if we are inside a comment -> uncomment | ||
| 746 | const parser = new SlashCommandParser(); | ||
| 747 | parser.parse(message.value, false); | ||
| 748 | const start = message.selectionStart; | ||
| 749 | const end = message.selectionEnd; | ||
| 750 | const comment = parser.commandIndex.findLast(it=>it.name == '*' && (it.start <= start && it.end >= start || it.start <= end && it.end >= end)); | ||
| 751 | if (comment) { | ||
| 752 | // uncomment | ||
| 753 | let content = message.value.slice(comment.start + 1, comment.end - 1); | ||
| 754 | let len = content.length; | ||
| 755 | content = content.replace(/^ /, ''); | ||
| 756 | const offsetStart = len - content.length; | ||
| 757 | len = content.length; | ||
| 758 | content = content.replace(/ $/, ''); | ||
| 759 | const offsetEnd = len - content.length; | ||
| 760 | message.selectionStart = comment.start - 1; | ||
| 761 | message.selectionEnd = comment.end + 1; | ||
| 762 | // document.execCommand is deprecated (and potentially buggy in some browsers) but the only way to retain undo-history | ||
| 763 | document.execCommand('insertText', false, content); | ||
| 764 | message.selectionStart = start - (start >= comment.start ? 2 + offsetStart : 0); | ||
| 765 | message.selectionEnd = end - 2 - offsetStart - (end >= comment.end ? 2 + offsetEnd : 0); | ||
| 766 | } else { | ||
| 767 | // comment | ||
| 768 | const lineStart = getLineStart(); | ||
| 769 | const lineEnd = message.value.indexOf('\n', end); | ||
| 770 | message.selectionStart = lineStart; | ||
| 771 | message.selectionEnd = lineEnd; | ||
| 772 | // document.execCommand is deprecated (and potentially buggy in some browsers) but the only way to retain undo-history | ||
| 773 | document.execCommand('insertText', false, `/* ${message.value.slice(lineStart, lineEnd)} *|`); | ||
| 774 | message.selectionStart = start + 3; | ||
| 775 | message.selectionEnd = end + 3; | ||
| 776 | } | ||
| 777 | message.dispatchEvent(new Event('input', { bubbles:true })); | ||
| 351 | } | 778 | } |
| 352 | }); | 779 | }); |
| 780 | const ac = await setSlashCommandAutoComplete(message, true); | ||
| 353 | message.addEventListener('wheel', (evt)=>{ | 781 | message.addEventListener('wheel', (evt)=>{ |
| 354 | updateScrollDebounced(evt); | 782 | updateScrollDebounced(evt); |
| 355 | }); | 783 | }); |
| 356 | message.addEventListener('scroll', (evt)=>{ | 784 | message.addEventListener('scroll', (evt)=>{ |
| 357 | updateScrollDebounced(); | 785 | updateScrollDebounced(); |
| 358 | }); | 786 | }); |
| 787 | let preBreakPointStart; | ||
| 788 | let preBreakPointEnd; | ||
| 789 | /** | ||
| 790 | * @param {SlashCommandBreakPoint} bp | ||
| 791 | */ | ||
| 792 | const removeBreakpoint = (bp)=>{ | ||
| 793 | // start at -1 because "/" is not included in start-end | ||
| 794 | let start = bp.start - 1; | ||
| 795 | // step left until forward slash "/" | ||
| 796 | while (message.value[start] != '/') start--; | ||
| 797 | // step left while whitespace (except newline) before start | ||
| 798 | while (/[^\S\n]/.test(message.value[start - 1])) start--; | ||
| 799 | // if newline before indent, include the newline for removal | ||
| 800 | if (message.value[start - 1] == '\n') start--; | ||
| 801 | let end = bp.end; | ||
| 802 | // step right while whitespace | ||
| 803 | while (/\s/.test(message.value[end])) end++; | ||
| 804 | // if pipe after whitepace, include pipe for removal | ||
| 805 | if (message.value[end] == '|') end++; | ||
| 806 | message.selectionStart = start; | ||
| 807 | message.selectionEnd = end; | ||
| 808 | // document.execCommand is deprecated (and potentially buggy in some browsers) but the only way to retain undo-history | ||
| 809 | document.execCommand('insertText', false, ''); | ||
| 810 | message.dispatchEvent(new Event('input', { bubbles:true })); | ||
| 811 | let postStart = preBreakPointStart; | ||
| 812 | let postEnd = preBreakPointEnd; | ||
| 813 | // set caret back to where it was | ||
| 814 | if (preBreakPointStart <= start) { | ||
| 815 | // selection start was before breakpoint: do nothing | ||
| 816 | } else if (preBreakPointStart > start && preBreakPointEnd < end) { | ||
| 817 | // selection start was inside breakpoint: move to index before breakpoint | ||
| 818 | postStart = start; | ||
| 819 | } else if (preBreakPointStart >= end) { | ||
| 820 | // selection start was behind breakpoint: move back by length of removed string | ||
| 821 | postStart = preBreakPointStart - (end - start); | ||
| 822 | } | ||
| 823 | if (preBreakPointEnd <= start) { | ||
| 824 | // do nothing | ||
| 825 | } else if (preBreakPointEnd > start && preBreakPointEnd < end) { | ||
| 826 | // selection end was inside breakpoint: move to index before breakpoint | ||
| 827 | postEnd = start; | ||
| 828 | } else if (preBreakPointEnd >= end) { | ||
| 829 | // selection end was behind breakpoint: move back by length of removed string | ||
| 830 | postEnd = preBreakPointEnd - (end - start); | ||
| 831 | } | ||
| 832 | return { start:postStart, end:postEnd }; | ||
| 833 | }; | ||
| 834 | /** | ||
| 835 | * @param {SlashCommandExecutor} cmd | ||
| 836 | */ | ||
| 837 | const addBreakpoint = (cmd)=>{ | ||
| 838 | // start at -1 because "/" is not included in start-end | ||
| 839 | let start = cmd.start - 1; | ||
| 840 | let indent = ''; | ||
| 841 | // step left until forward slash "/" | ||
| 842 | while (message.value[start] != '/') start--; | ||
| 843 | // step left while whitespace (except newline) before start, collect the whitespace to help build indentation | ||
| 844 | while (/[^\S\n]/.test(message.value[start - 1])) { | ||
| 845 | start--; | ||
| 846 | indent += message.value[start]; | ||
| 847 | } | ||
| 848 | // if newline before indent, include the newline | ||
| 849 | if (message.value[start - 1] == '\n') { | ||
| 850 | start--; | ||
| 851 | indent = `\n${indent}`; | ||
| 852 | } | ||
| 853 | const breakpointText = `${indent}/breakpoint |`; | ||
| 854 | message.selectionStart = start; | ||
| 855 | message.selectionEnd = start; | ||
| 856 | // document.execCommand is deprecated (and potentially buggy in some browsers) but the only way to retain undo-history | ||
| 857 | document.execCommand('insertText', false, breakpointText); | ||
| 858 | message.dispatchEvent(new Event('input', { bubbles:true })); | ||
| 859 | return breakpointText.length; | ||
| 860 | }; | ||
| 861 | const toggleBreakpoint = ()=>{ | ||
| 862 | const idx = message.selectionStart; | ||
| 863 | let postStart = preBreakPointStart; | ||
| 864 | let postEnd = preBreakPointEnd; | ||
| 865 | const parser = new SlashCommandParser(); | ||
| 866 | parser.parse(message.value, false); | ||
| 867 | const cmdIdx = parser.commandIndex.findLastIndex(it=>it.start <= idx); | ||
| 868 | if (cmdIdx > -1) { | ||
| 869 | const cmd = parser.commandIndex[cmdIdx]; | ||
| 870 | if (cmd instanceof SlashCommandBreakPoint) { | ||
| 871 | const bp = cmd; | ||
| 872 | const { start, end } = removeBreakpoint(bp); | ||
| 873 | postStart = start; | ||
| 874 | postEnd = end; | ||
| 875 | } else if (parser.commandIndex[cmdIdx - 1] instanceof SlashCommandBreakPoint) { | ||
| 876 | const bp = parser.commandIndex[cmdIdx - 1]; | ||
| 877 | const { start, end } = removeBreakpoint(bp); | ||
| 878 | postStart = start; | ||
| 879 | postEnd = end; | ||
| 880 | } else { | ||
| 881 | const len = addBreakpoint(cmd); | ||
| 882 | postStart += len; | ||
| 883 | postEnd += len; | ||
| 884 | } | ||
| 885 | message.selectionStart = postStart; | ||
| 886 | message.selectionEnd = postEnd; | ||
| 887 | } | ||
| 888 | }; | ||
| 889 | message.addEventListener('pointerdown', (evt)=>{ | ||
| 890 | if (!evt.ctrlKey || !evt.altKey) return; | ||
| 891 | preBreakPointStart = message.selectionStart; | ||
| 892 | preBreakPointEnd = message.selectionEnd; | ||
| 893 | }); | ||
| 894 | message.addEventListener('pointerup', async(evt)=>{ | ||
| 895 | if (!evt.ctrlKey || !evt.altKey || message.selectionStart != message.selectionEnd) return; | ||
| 896 | toggleBreakpoint(); | ||
| 897 | }); | ||
| 359 | /** @type {any} */ | 898 | /** @type {any} */ |
| 360 | const resizeListener = debounce((evt) => { | 899 | const resizeListener = debounce((evt) => { |
| 361 | updateSyntax(); | ||
| 362 | updateScrollDebounced(evt); | 900 | updateScrollDebounced(evt); |
| 363 | if (document.activeElement == message) { | 901 | if (document.activeElement == message) { |
| 364 | message.blur(); | 902 | message.blur(); |
| @@ -367,10 +905,43 @@ export class QuickReply { | |||
| 367 | }); | 905 | }); |
| 368 | window.addEventListener('resize', resizeListener); | 906 | window.addEventListener('resize', resizeListener); |
| 369 | updateSyntaxEnabled(); | 907 | updateSyntaxEnabled(); |
| 370 | message.style.setProperty('text-shadow', 'none', 'important'); | 908 | const updateSyntax = ()=>{ |
| 371 | /**@type {HTMLElement}*/ | 909 | if (messageSyntaxInner && syntax.checked) { |
| 372 | const messageSyntaxInner = dom.querySelector('#qr--modal-messageSyntaxInner'); | 910 | morphdom( |
| 911 | messageSyntaxInner, | ||
| 912 | `<div>${hljs.highlight(`${message.value}${message.value.slice(-1) == '\n' ? ' ' : ''}`, { language:'stscript', ignoreIllegals:true })?.value}</div>`, | ||
| 913 | { childrenOnly: true }, | ||
| 914 | ); | ||
| 915 | updateScrollDebounced(); | ||
| 916 | } | ||
| 917 | }; | ||
| 918 | let lastSyntaxUpdate = 0; | ||
| 919 | const fpsTime = 1000 / 30; | ||
| 920 | let lastMessageValue = null; | ||
| 921 | let wasSyntax = null; | ||
| 922 | const updateSyntaxLoop = ()=>{ | ||
| 923 | const now = Date.now(); | ||
| 924 | // fps limit | ||
| 925 | if (now - lastSyntaxUpdate < fpsTime) return requestAnimationFrame(updateSyntaxLoop); | ||
| 926 | // elements don't exist (yet?) | ||
| 927 | if (!messageSyntaxInner || !message) return requestAnimationFrame(updateSyntaxLoop); | ||
| 928 | // elements no longer part of the document | ||
| 929 | if (!messageSyntaxInner.closest('body')) return; | ||
| 930 | // debugger is running | ||
| 931 | if (this.isExecuting) { | ||
| 932 | lastMessageValue = null; | ||
| 933 | return requestAnimationFrame(updateSyntaxLoop); | ||
| 934 | } | ||
| 935 | // value hasn't changed | ||
| 936 | if (wasSyntax == syntax.checked && lastMessageValue == message.value) return requestAnimationFrame(updateSyntaxLoop); | ||
| 937 | wasSyntax = syntax.checked; | ||
| 938 | lastSyntaxUpdate = now; | ||
| 939 | lastMessageValue = message.value; | ||
| 373 | updateSyntax(); | 940 | updateSyntax(); |
| 941 | requestAnimationFrame(updateSyntaxLoop); | ||
| 942 | }; | ||
| 943 | requestAnimationFrame(()=>updateSyntaxLoop()); | ||
| 944 | message.style.setProperty('text-shadow', 'none', 'important'); | ||
| 374 | updateWrap(); | 945 | updateWrap(); |
| 375 | updateTabSize(); | 946 | updateTabSize(); |
| 376 | 947 | ||
| @@ -379,7 +950,7 @@ export class QuickReply { | |||
| 379 | const tpl = dom.querySelector('#qr--ctxItem'); | 950 | const tpl = dom.querySelector('#qr--ctxItem'); |
| 380 | const linkList = dom.querySelector('#qr--ctxEditor'); | 951 | const linkList = dom.querySelector('#qr--ctxEditor'); |
| 381 | const fillQrSetSelect = (/**@type {HTMLSelectElement}*/select, /**@type {QuickReplyContextLink}*/ link) => { | 952 | const fillQrSetSelect = (/**@type {HTMLSelectElement}*/select, /**@type {QuickReplyContextLink}*/ link) => { |
| 382 | [{ name: 'Select a QR set' }, ...QuickReplySet.list].forEach(qrs => { | 953 | [{ name: 'Select a QR set' }, ...QuickReplySet.list.toSorted((a,b)=>a.name.toLowerCase().localeCompare(b.name.toLowerCase()))].forEach(qrs => { |
| 383 | const opt = document.createElement('option'); { | 954 | const opt = document.createElement('option'); { |
| 384 | opt.value = qrs.name; | 955 | opt.value = qrs.name; |
| 385 | opt.textContent = qrs.name; | 956 | opt.textContent = qrs.name; |
| @@ -388,7 +959,7 @@ export class QuickReply { | |||
| 388 | } | 959 | } |
| 389 | }); | 960 | }); |
| 390 | }; | 961 | }; |
| 391 | const addCtxItem = (/**@type {QuickReplyContextLink}*/link, /**@type {Number}*/idx) => { | 962 | const addCtxItem = (/**@type {QuickReplyContextLink}*/link, /**@type {number}*/idx) => { |
| 392 | /**@type {HTMLElement} */ | 963 | /**@type {HTMLElement} */ |
| 393 | // @ts-ignore | 964 | // @ts-ignore |
| 394 | const itemDom = tpl.content.querySelector('.qr--ctxItem').cloneNode(true); { | 965 | const itemDom = tpl.content.querySelector('.qr--ctxItem').cloneNode(true); { |
| @@ -506,9 +1077,9 @@ export class QuickReply { | |||
| 506 | /**@type {HTMLElement}*/ | 1077 | /**@type {HTMLElement}*/ |
| 507 | const executeResult = dom.querySelector('#qr--modal-executeResult'); | 1078 | const executeResult = dom.querySelector('#qr--modal-executeResult'); |
| 508 | this.editorExecuteResult = executeResult; | 1079 | this.editorExecuteResult = executeResult; |
| 509 | /**@type {HTMLInputElement}*/ | 1080 | /**@type {HTMLElement}*/ |
| 510 | const executeHide = dom.querySelector('#qr--modal-executeHide'); | 1081 | const debugState = dom.querySelector('#qr--modal-debugState'); |
| 511 | this.editorExecuteHide = executeHide; | 1082 | this.editorDebugState = debugState; |
| 512 | /**@type {HTMLElement}*/ | 1083 | /**@type {HTMLElement}*/ |
| 513 | const executeBtn = dom.querySelector('#qr--modal-execute'); | 1084 | const executeBtn = dom.querySelector('#qr--modal-execute'); |
| 514 | this.editorExecuteBtn = executeBtn; | 1085 | this.editorExecuteBtn = executeBtn; |
| @@ -536,6 +1107,76 @@ export class QuickReply { | |||
| 536 | this.abortController?.abort('Stop button clicked'); | 1107 | this.abortController?.abort('Stop button clicked'); |
| 537 | }); | 1108 | }); |
| 538 | 1109 | ||
| 1110 | /**@type {HTMLTextAreaElement} */ | ||
| 1111 | const inputOg = document.querySelector('#send_textarea'); | ||
| 1112 | const inputMirror = dom.querySelector('#qr--modal-send_textarea'); | ||
| 1113 | inputMirror.value = inputOg.value; | ||
| 1114 | const inputOgMo = new MutationObserver(muts=>{ | ||
| 1115 | if (muts.find(it=>[...it.removedNodes].includes(inputMirror) || [...it.removedNodes].find(n=>n.contains(inputMirror)))) { | ||
| 1116 | inputOg.removeEventListener('input', inputOgListener); | ||
| 1117 | } | ||
| 1118 | }); | ||
| 1119 | inputOgMo.observe(document.body, { childList:true }); | ||
| 1120 | const inputOgListener = ()=>{ | ||
| 1121 | inputMirror.value = inputOg.value; | ||
| 1122 | }; | ||
| 1123 | inputOg.addEventListener('input', inputOgListener); | ||
| 1124 | inputMirror.addEventListener('input', ()=>{ | ||
| 1125 | inputOg.value = inputMirror.value; | ||
| 1126 | }); | ||
| 1127 | |||
| 1128 | /**@type {HTMLElement}*/ | ||
| 1129 | const resumeBtn = dom.querySelector('#qr--modal-resume'); | ||
| 1130 | resumeBtn.addEventListener('click', ()=>{ | ||
| 1131 | this.debugController?.resume(); | ||
| 1132 | }); | ||
| 1133 | /**@type {HTMLElement}*/ | ||
| 1134 | const stepBtn = dom.querySelector('#qr--modal-step'); | ||
| 1135 | stepBtn.addEventListener('click', ()=>{ | ||
| 1136 | this.debugController?.step(); | ||
| 1137 | }); | ||
| 1138 | /**@type {HTMLElement}*/ | ||
| 1139 | const stepIntoBtn = dom.querySelector('#qr--modal-stepInto'); | ||
| 1140 | stepIntoBtn.addEventListener('click', ()=>{ | ||
| 1141 | this.debugController?.stepInto(); | ||
| 1142 | }); | ||
| 1143 | /**@type {HTMLElement}*/ | ||
| 1144 | const stepOutBtn = dom.querySelector('#qr--modal-stepOut'); | ||
| 1145 | stepOutBtn.addEventListener('click', ()=>{ | ||
| 1146 | this.debugController?.stepOut(); | ||
| 1147 | }); | ||
| 1148 | /**@type {HTMLElement}*/ | ||
| 1149 | const minimizeBtn = dom.querySelector('#qr--modal-minimize'); | ||
| 1150 | minimizeBtn.addEventListener('click', ()=>{ | ||
| 1151 | this.editorDom.classList.add('qr--minimized'); | ||
| 1152 | }); | ||
| 1153 | const maximizeBtn = dom.querySelector('#qr--modal-maximize'); | ||
| 1154 | maximizeBtn.addEventListener('click', ()=>{ | ||
| 1155 | this.editorDom.classList.remove('qr--minimized'); | ||
| 1156 | }); | ||
| 1157 | /**@type {boolean}*/ | ||
| 1158 | let isResizing = false; | ||
| 1159 | let resizeStart; | ||
| 1160 | let wStart; | ||
| 1161 | /**@type {HTMLElement}*/ | ||
| 1162 | const resizeHandle = dom.querySelector('#qr--resizeHandle'); | ||
| 1163 | resizeHandle.addEventListener('pointerdown', (evt)=>{ | ||
| 1164 | if (isResizing) return; | ||
| 1165 | isResizing = true; | ||
| 1166 | evt.preventDefault(); | ||
| 1167 | resizeStart = evt.x; | ||
| 1168 | wStart = dom.querySelector('#qr--qrOptions').offsetWidth; | ||
| 1169 | const dragListener = debounce((evt)=>{ | ||
| 1170 | const w = wStart + resizeStart - evt.x; | ||
| 1171 | dom.querySelector('#qr--qrOptions').style.setProperty('--width', `${w}px`); | ||
| 1172 | }, 5); | ||
| 1173 | window.addEventListener('pointerup', ()=>{ | ||
| 1174 | window.removeEventListener('pointermove', dragListener); | ||
| 1175 | isResizing = false; | ||
| 1176 | }, { once:true }); | ||
| 1177 | window.addEventListener('pointermove', dragListener); | ||
| 1178 | }); | ||
| 1179 | |||
| 539 | await popupResult; | 1180 | await popupResult; |
| 540 | 1181 | ||
| 541 | window.removeEventListener('resize', resizeListener); | 1182 | window.removeEventListener('resize', resizeListener); |
| @@ -544,8 +1185,62 @@ export class QuickReply { | |||
| 544 | } | 1185 | } |
| 545 | } | 1186 | } |
| 546 | 1187 | ||
| 1188 | getEditorPosition(start, end, message = null) { | ||
| 1189 | const inputRect = this.editorMessage.getBoundingClientRect(); | ||
| 1190 | const style = window.getComputedStyle(this.editorMessage); | ||
| 1191 | if (!this.clone) { | ||
| 1192 | this.clone = document.createElement('div'); | ||
| 1193 | for (const key of style) { | ||
| 1194 | this.clone.style[key] = style[key]; | ||
| 1195 | } | ||
| 1196 | this.clone.style.position = 'fixed'; | ||
| 1197 | this.clone.style.visibility = 'hidden'; | ||
| 1198 | const mo = new MutationObserver(muts=>{ | ||
| 1199 | if (muts.find(it=>[...it.removedNodes].includes(this.editorMessage) || [...it.removedNodes].find(n=>n.contains(this.editorMessage)))) { | ||
| 1200 | this.clone?.remove(); | ||
| 1201 | this.clone = null; | ||
| 1202 | } | ||
| 1203 | }); | ||
| 1204 | mo.observe(document.body, { childList:true }); | ||
| 1205 | } | ||
| 1206 | document.body.append(this.clone); | ||
| 1207 | this.clone.style.width = `${inputRect.width}px`; | ||
| 1208 | this.clone.style.height = `${inputRect.height}px`; | ||
| 1209 | this.clone.style.left = `${inputRect.left}px`; | ||
| 1210 | this.clone.style.top = `${inputRect.top}px`; | ||
| 1211 | this.clone.style.whiteSpace = style.whiteSpace; | ||
| 1212 | this.clone.style.tabSize = style.tabSize; | ||
| 1213 | const text = message ?? this.editorMessage.value; | ||
| 1214 | const before = text.slice(0, start); | ||
| 1215 | this.clone.textContent = before; | ||
| 1216 | const locator = document.createElement('span'); | ||
| 1217 | locator.textContent = text.slice(start, end); | ||
| 1218 | this.clone.append(locator); | ||
| 1219 | this.clone.append(text.slice(end)); | ||
| 1220 | this.clone.scrollTop = this.editorSyntax.scrollTop; | ||
| 1221 | this.clone.scrollLeft = this.editorSyntax.scrollLeft; | ||
| 1222 | const locatorRect = locator.getBoundingClientRect(); | ||
| 1223 | const bodyRect = document.body.getBoundingClientRect(); | ||
| 1224 | const location = { | ||
| 1225 | left: locatorRect.left - bodyRect.left, | ||
| 1226 | right: locatorRect.right - bodyRect.left, | ||
| 1227 | top: locatorRect.top - bodyRect.top, | ||
| 1228 | bottom: locatorRect.bottom - bodyRect.top, | ||
| 1229 | }; | ||
| 1230 | // this.clone.remove(); | ||
| 1231 | return location; | ||
| 1232 | } | ||
| 547 | async executeFromEditor() { | 1233 | async executeFromEditor() { |
| 548 | if (this.editorExecutePromise) return; | 1234 | if (this.isExecuting) return; |
| 1235 | this.editorPopup.onClosing = ()=>false; | ||
| 1236 | const uuidCheck = /^[0-9a-z]{8}(-[0-9a-z]{4}){3}-[0-9a-z]{12}$/; | ||
| 1237 | const oText = this.message; | ||
| 1238 | this.isExecuting = true; | ||
| 1239 | this.editorDom.classList.add('qr--isExecuting'); | ||
| 1240 | const noSyntax = this.editorDom.querySelector('#qr--modal-messageHolder').classList.contains('qr--noSyntax'); | ||
| 1241 | if (noSyntax) { | ||
| 1242 | this.editorDom.querySelector('#qr--modal-messageHolder').classList.remove('qr--noSyntax'); | ||
| 1243 | } | ||
| 549 | this.editorExecuteBtn.classList.add('qr--busy'); | 1244 | this.editorExecuteBtn.classList.add('qr--busy'); |
| 550 | this.editorExecuteProgress.style.setProperty('--prog', '0'); | 1245 | this.editorExecuteProgress.style.setProperty('--prog', '0'); |
| 551 | this.editorExecuteErrors.classList.remove('qr--hasErrors'); | 1246 | this.editorExecuteErrors.classList.remove('qr--hasErrors'); |
| @@ -556,12 +1251,451 @@ export class QuickReply { | |||
| 556 | this.editorExecuteProgress.classList.remove('qr--aborted'); | 1251 | this.editorExecuteProgress.classList.remove('qr--aborted'); |
| 557 | this.editorExecuteErrors.innerHTML = ''; | 1252 | this.editorExecuteErrors.innerHTML = ''; |
| 558 | this.editorExecuteResult.innerHTML = ''; | 1253 | this.editorExecuteResult.innerHTML = ''; |
| 559 | if (this.editorExecuteHide.checked) { | 1254 | const syntax = this.editorDom.querySelector('#qr--modal-messageSyntaxInner'); |
| 560 | this.editorPopup.dlg.classList.add('qr--hide'); | 1255 | const updateScroll = (evt) => { |
| 1256 | let left = syntax.scrollLeft; | ||
| 1257 | let top = syntax.scrollTop; | ||
| 1258 | if (evt) { | ||
| 1259 | evt.preventDefault(); | ||
| 1260 | left = syntax.scrollLeft + evt.deltaX; | ||
| 1261 | top = syntax.scrollTop + evt.deltaY; | ||
| 1262 | syntax.scrollTo({ | ||
| 1263 | behavior: 'instant', | ||
| 1264 | left, | ||
| 1265 | top, | ||
| 1266 | }); | ||
| 561 | } | 1267 | } |
| 1268 | this.editorMessage.scrollTo({ | ||
| 1269 | behavior: 'instant', | ||
| 1270 | left, | ||
| 1271 | top, | ||
| 1272 | }); | ||
| 1273 | }; | ||
| 1274 | const updateScrollDebounced = updateScroll; | ||
| 1275 | syntax.addEventListener('wheel', (evt)=>{ | ||
| 1276 | updateScrollDebounced(evt); | ||
| 1277 | }); | ||
| 1278 | syntax.addEventListener('scroll', (evt)=>{ | ||
| 1279 | updateScrollDebounced(); | ||
| 1280 | }); | ||
| 562 | try { | 1281 | try { |
| 563 | this.editorExecutePromise = this.execute({}, true); | 1282 | this.abortController = new SlashCommandAbortController(); |
| 564 | const result = await this.editorExecutePromise; | 1283 | this.debugController = new SlashCommandDebugController(); |
| 1284 | this.debugController.onBreakPoint = async(closure, executor)=>{ | ||
| 1285 | this.editorDom.classList.add('qr--isPaused'); | ||
| 1286 | syntax.innerHTML = hljs.highlight(`${closure.fullText}${closure.fullText.slice(-1) == '\n' ? ' ' : ''}`, { language:'stscript', ignoreIllegals:true })?.value; | ||
| 1287 | this.editorMessageLabel.innerHTML = ''; | ||
| 1288 | if (uuidCheck.test(closure.source)) { | ||
| 1289 | const p0 = document.createElement('span'); { | ||
| 1290 | p0.textContent = 'anonymous: '; | ||
| 1291 | this.editorMessageLabel.append(p0); | ||
| 1292 | } | ||
| 1293 | const p1 = document.createElement('strong'); { | ||
| 1294 | p1.textContent = executor.source.slice(0,5); | ||
| 1295 | this.editorMessageLabel.append(p1); | ||
| 1296 | } | ||
| 1297 | const p2 = document.createElement('span'); { | ||
| 1298 | p2.textContent = executor.source.slice(5, -5); | ||
| 1299 | this.editorMessageLabel.append(p2); | ||
| 1300 | } | ||
| 1301 | const p3 = document.createElement('strong'); { | ||
| 1302 | p3.textContent = executor.source.slice(-5); | ||
| 1303 | this.editorMessageLabel.append(p3); | ||
| 1304 | } | ||
| 1305 | } else { | ||
| 1306 | this.editorMessageLabel.textContent = executor.source; | ||
| 1307 | } | ||
| 1308 | const source = closure.source; | ||
| 1309 | this.editorDebugState.innerHTML = ''; | ||
| 1310 | let ci = -1; | ||
| 1311 | const varNames = []; | ||
| 1312 | const macroNames = []; | ||
| 1313 | /** | ||
| 1314 | * @param {SlashCommandScope} scope | ||
| 1315 | */ | ||
| 1316 | const buildVars = (scope, isCurrent = false)=>{ | ||
| 1317 | if (!isCurrent) { | ||
| 1318 | ci--; | ||
| 1319 | } | ||
| 1320 | const c = this.debugController.stack.slice(ci)[0]; | ||
| 1321 | const wrap = document.createElement('div'); { | ||
| 1322 | wrap.classList.add('qr--scope'); | ||
| 1323 | if (isCurrent) { | ||
| 1324 | const executor = this.debugController.cmdStack.slice(-1)[0]; | ||
| 1325 | { // named args | ||
| 1326 | const namedTitle = document.createElement('div'); { | ||
| 1327 | namedTitle.classList.add('qr--title'); | ||
| 1328 | namedTitle.textContent = `Named Args - /${executor.name}`; | ||
| 1329 | if (executor.command.name == 'run') { | ||
| 1330 | namedTitle.textContent += `${(executor.name == ':' ? '' : ' ')}${executor.unnamedArgumentList[0]?.value}`; | ||
| 1331 | } | ||
| 1332 | wrap.append(namedTitle); | ||
| 1333 | } | ||
| 1334 | const keys = new Set([...Object.keys(this.debugController.namedArguments ?? {}), ...(executor.namedArgumentList ?? []).map(it=>it.name)]); | ||
| 1335 | for (const key of keys) { | ||
| 1336 | if (key[0] == '_') continue; | ||
| 1337 | const item = document.createElement('div'); { | ||
| 1338 | item.classList.add('qr--var'); | ||
| 1339 | const k = document.createElement('div'); { | ||
| 1340 | k.classList.add('qr--key'); | ||
| 1341 | k.textContent = key; | ||
| 1342 | item.append(k); | ||
| 1343 | } | ||
| 1344 | const vUnresolved = document.createElement('div'); { | ||
| 1345 | vUnresolved.classList.add('qr--val'); | ||
| 1346 | vUnresolved.classList.add('qr--singleCol'); | ||
| 1347 | const val = executor.namedArgumentList.find(it=>it.name == key)?.value; | ||
| 1348 | if (val instanceof SlashCommandClosure) { | ||
| 1349 | vUnresolved.classList.add('qr--closure'); | ||
| 1350 | vUnresolved.title = val.rawText; | ||
| 1351 | vUnresolved.textContent = val.toString(); | ||
| 1352 | } else if (val === undefined) { | ||
| 1353 | vUnresolved.classList.add('qr--undefined'); | ||
| 1354 | vUnresolved.textContent = 'undefined'; | ||
| 1355 | } else { | ||
| 1356 | let jsonVal; | ||
| 1357 | try { jsonVal = JSON.parse(val); } catch { /* empty */ } | ||
| 1358 | if (jsonVal && typeof jsonVal == 'object') { | ||
| 1359 | vUnresolved.textContent = JSON.stringify(jsonVal, null, 2); | ||
| 1360 | } else { | ||
| 1361 | vUnresolved.textContent = val; | ||
| 1362 | vUnresolved.classList.add('qr--simple'); | ||
| 1363 | } | ||
| 1364 | } | ||
| 1365 | item.append(vUnresolved); | ||
| 1366 | } | ||
| 1367 | const vResolved = document.createElement('div'); { | ||
| 1368 | vResolved.classList.add('qr--val'); | ||
| 1369 | vResolved.classList.add('qr--singleCol'); | ||
| 1370 | if (this.debugController.namedArguments === undefined) { | ||
| 1371 | vResolved.classList.add('qr--unresolved'); | ||
| 1372 | } else { | ||
| 1373 | const val = this.debugController.namedArguments?.[key]; | ||
| 1374 | if (val instanceof SlashCommandClosure) { | ||
| 1375 | vResolved.classList.add('qr--closure'); | ||
| 1376 | vResolved.title = val.rawText; | ||
| 1377 | vResolved.textContent = val.toString(); | ||
| 1378 | } else if (val === undefined) { | ||
| 1379 | vResolved.classList.add('qr--undefined'); | ||
| 1380 | vResolved.textContent = 'undefined'; | ||
| 1381 | } else { | ||
| 1382 | let jsonVal; | ||
| 1383 | try { jsonVal = JSON.parse(val); } catch { /* empty */ } | ||
| 1384 | if (jsonVal && typeof jsonVal == 'object') { | ||
| 1385 | vResolved.textContent = JSON.stringify(jsonVal, null, 2); | ||
| 1386 | } else { | ||
| 1387 | vResolved.textContent = val; | ||
| 1388 | vResolved.classList.add('qr--simple'); | ||
| 1389 | } | ||
| 1390 | } | ||
| 1391 | } | ||
| 1392 | item.append(vResolved); | ||
| 1393 | } | ||
| 1394 | wrap.append(item); | ||
| 1395 | } | ||
| 1396 | } | ||
| 1397 | } | ||
| 1398 | { // unnamed args | ||
| 1399 | const unnamedTitle = document.createElement('div'); { | ||
| 1400 | unnamedTitle.classList.add('qr--title'); | ||
| 1401 | unnamedTitle.textContent = `Unnamed Args - /${executor.name}`; | ||
| 1402 | if (executor.command.name == 'run') { | ||
| 1403 | unnamedTitle.textContent += `${(executor.name == ':' ? '' : ' ')}${executor.unnamedArgumentList[0]?.value}`; | ||
| 1404 | } | ||
| 1405 | wrap.append(unnamedTitle); | ||
| 1406 | } | ||
| 1407 | let i = 0; | ||
| 1408 | let unnamed = this.debugController.unnamedArguments ?? []; | ||
| 1409 | if (!Array.isArray(unnamed)) unnamed = [unnamed]; | ||
| 1410 | while (unnamed.length < executor.unnamedArgumentList?.length ?? 0) unnamed.push(undefined); | ||
| 1411 | unnamed = unnamed.map((it,idx)=>[executor.unnamedArgumentList?.[idx], it]); | ||
| 1412 | for (const arg of unnamed) { | ||
| 1413 | i++; | ||
| 1414 | const item = document.createElement('div'); { | ||
| 1415 | item.classList.add('qr--var'); | ||
| 1416 | const k = document.createElement('div'); { | ||
| 1417 | k.classList.add('qr--key'); | ||
| 1418 | k.textContent = i.toString(); | ||
| 1419 | item.append(k); | ||
| 1420 | } | ||
| 1421 | const vUnresolved = document.createElement('div'); { | ||
| 1422 | vUnresolved.classList.add('qr--val'); | ||
| 1423 | vUnresolved.classList.add('qr--singleCol'); | ||
| 1424 | const val = arg[0]?.value; | ||
| 1425 | if (val instanceof SlashCommandClosure) { | ||
| 1426 | vUnresolved.classList.add('qr--closure'); | ||
| 1427 | vUnresolved.title = val.rawText; | ||
| 1428 | vUnresolved.textContent = val.toString(); | ||
| 1429 | } else if (val === undefined) { | ||
| 1430 | vUnresolved.classList.add('qr--undefined'); | ||
| 1431 | vUnresolved.textContent = 'undefined'; | ||
| 1432 | } else { | ||
| 1433 | let jsonVal; | ||
| 1434 | try { jsonVal = JSON.parse(val); } catch { /* empty */ } | ||
| 1435 | if (jsonVal && typeof jsonVal == 'object') { | ||
| 1436 | vUnresolved.textContent = JSON.stringify(jsonVal, null, 2); | ||
| 1437 | } else { | ||
| 1438 | vUnresolved.textContent = val; | ||
| 1439 | vUnresolved.classList.add('qr--simple'); | ||
| 1440 | } | ||
| 1441 | } | ||
| 1442 | item.append(vUnresolved); | ||
| 1443 | } | ||
| 1444 | const vResolved = document.createElement('div'); { | ||
| 1445 | vResolved.classList.add('qr--val'); | ||
| 1446 | vResolved.classList.add('qr--singleCol'); | ||
| 1447 | if (this.debugController.unnamedArguments === undefined) { | ||
| 1448 | vResolved.classList.add('qr--unresolved'); | ||
| 1449 | } else if ((Array.isArray(this.debugController.unnamedArguments) ? this.debugController.unnamedArguments : [this.debugController.unnamedArguments]).length < i) { | ||
| 1450 | // do nothing | ||
| 1451 | } else { | ||
| 1452 | const val = arg[1]; | ||
| 1453 | if (val instanceof SlashCommandClosure) { | ||
| 1454 | vResolved.classList.add('qr--closure'); | ||
| 1455 | vResolved.title = val.rawText; | ||
| 1456 | vResolved.textContent = val.toString(); | ||
| 1457 | } else if (val === undefined) { | ||
| 1458 | vResolved.classList.add('qr--undefined'); | ||
| 1459 | vResolved.textContent = 'undefined'; | ||
| 1460 | } else { | ||
| 1461 | let jsonVal; | ||
| 1462 | try { jsonVal = JSON.parse(val); } catch { /* empty */ } | ||
| 1463 | if (jsonVal && typeof jsonVal == 'object') { | ||
| 1464 | vResolved.textContent = JSON.stringify(jsonVal, null, 2); | ||
| 1465 | } else { | ||
| 1466 | vResolved.textContent = val; | ||
| 1467 | vResolved.classList.add('qr--simple'); | ||
| 1468 | } | ||
| 1469 | } | ||
| 1470 | } | ||
| 1471 | item.append(vResolved); | ||
| 1472 | } | ||
| 1473 | wrap.append(item); | ||
| 1474 | } | ||
| 1475 | } | ||
| 1476 | } | ||
| 1477 | } | ||
| 1478 | // current scope | ||
| 1479 | const title = document.createElement('div'); { | ||
| 1480 | title.classList.add('qr--title'); | ||
| 1481 | title.textContent = isCurrent ? 'Current Scope' : 'Parent Scope'; | ||
| 1482 | if (c.source == source) { | ||
| 1483 | let hi; | ||
| 1484 | title.addEventListener('pointerenter', ()=>{ | ||
| 1485 | const loc = this.getEditorPosition(Math.max(0, c.executorList[0].start - 1), c.executorList.slice(-1)[0].end, c.fullText); | ||
| 1486 | const layer = syntax.getBoundingClientRect(); | ||
| 1487 | hi = document.createElement('div'); | ||
| 1488 | hi.classList.add('qr--highlight-secondary'); | ||
| 1489 | hi.style.left = `${loc.left - layer.left}px`; | ||
| 1490 | hi.style.width = `${loc.right - loc.left}px`; | ||
| 1491 | hi.style.top = `${loc.top - layer.top + syntax.scrollTop}px`; | ||
| 1492 | hi.style.height = `${loc.bottom - loc.top}px`; | ||
| 1493 | syntax.append(hi); | ||
| 1494 | }); | ||
| 1495 | title.addEventListener('pointerleave', ()=>hi?.remove()); | ||
| 1496 | } | ||
| 1497 | wrap.append(title); | ||
| 1498 | } | ||
| 1499 | for (const key of Object.keys(scope.variables)) { | ||
| 1500 | const isHidden = varNames.includes(key); | ||
| 1501 | if (!isHidden) varNames.push(key); | ||
| 1502 | const item = document.createElement('div'); { | ||
| 1503 | item.classList.add('qr--var'); | ||
| 1504 | if (isHidden) item.classList.add('qr--isHidden'); | ||
| 1505 | const k = document.createElement('div'); { | ||
| 1506 | k.classList.add('qr--key'); | ||
| 1507 | k.textContent = key; | ||
| 1508 | item.append(k); | ||
| 1509 | } | ||
| 1510 | const v = document.createElement('div'); { | ||
| 1511 | v.classList.add('qr--val'); | ||
| 1512 | const val = scope.variables[key]; | ||
| 1513 | if (val instanceof SlashCommandClosure) { | ||
| 1514 | v.classList.add('qr--closure'); | ||
| 1515 | v.title = val.rawText; | ||
| 1516 | v.textContent = val.toString(); | ||
| 1517 | } else if (val === undefined) { | ||
| 1518 | v.classList.add('qr--undefined'); | ||
| 1519 | v.textContent = 'undefined'; | ||
| 1520 | } else { | ||
| 1521 | let jsonVal; | ||
| 1522 | try { jsonVal = JSON.parse(val); } catch { /* empty */ } | ||
| 1523 | if (jsonVal && typeof jsonVal == 'object') { | ||
| 1524 | v.textContent = JSON.stringify(jsonVal, null, 2); | ||
| 1525 | } else { | ||
| 1526 | v.textContent = val; | ||
| 1527 | v.classList.add('qr--simple'); | ||
| 1528 | } | ||
| 1529 | } | ||
| 1530 | item.append(v); | ||
| 1531 | } | ||
| 1532 | wrap.append(item); | ||
| 1533 | } | ||
| 1534 | } | ||
| 1535 | for (const key of Object.keys(scope.macros)) { | ||
| 1536 | const isHidden = macroNames.includes(key); | ||
| 1537 | if (!isHidden) macroNames.push(key); | ||
| 1538 | const item = document.createElement('div'); { | ||
| 1539 | item.classList.add('qr--macro'); | ||
| 1540 | if (isHidden) item.classList.add('qr--isHidden'); | ||
| 1541 | const k = document.createElement('div'); { | ||
| 1542 | k.classList.add('qr--key'); | ||
| 1543 | k.textContent = key; | ||
| 1544 | item.append(k); | ||
| 1545 | } | ||
| 1546 | const v = document.createElement('div'); { | ||
| 1547 | v.classList.add('qr--val'); | ||
| 1548 | const val = scope.macros[key]; | ||
| 1549 | if (val instanceof SlashCommandClosure) { | ||
| 1550 | v.classList.add('qr--closure'); | ||
| 1551 | v.title = val.rawText; | ||
| 1552 | v.textContent = val.toString(); | ||
| 1553 | } else if (val === undefined) { | ||
| 1554 | v.classList.add('qr--undefined'); | ||
| 1555 | v.textContent = 'undefined'; | ||
| 1556 | } else { | ||
| 1557 | let jsonVal; | ||
| 1558 | try { jsonVal = JSON.parse(val); } catch { /* empty */ } | ||
| 1559 | if (jsonVal && typeof jsonVal == 'object') { | ||
| 1560 | v.textContent = JSON.stringify(jsonVal, null, 2); | ||
| 1561 | } else { | ||
| 1562 | v.textContent = val; | ||
| 1563 | v.classList.add('qr--simple'); | ||
| 1564 | } | ||
| 1565 | } | ||
| 1566 | item.append(v); | ||
| 1567 | } | ||
| 1568 | wrap.append(item); | ||
| 1569 | } | ||
| 1570 | } | ||
| 1571 | const pipeItem = document.createElement('div'); { | ||
| 1572 | pipeItem.classList.add('qr--pipe'); | ||
| 1573 | const k = document.createElement('div'); { | ||
| 1574 | k.classList.add('qr--key'); | ||
| 1575 | k.textContent = 'pipe'; | ||
| 1576 | pipeItem.append(k); | ||
| 1577 | } | ||
| 1578 | const v = document.createElement('div'); { | ||
| 1579 | v.classList.add('qr--val'); | ||
| 1580 | const val = scope.pipe; | ||
| 1581 | if (val instanceof SlashCommandClosure) { | ||
| 1582 | v.classList.add('qr--closure'); | ||
| 1583 | v.title = val.rawText; | ||
| 1584 | v.textContent = val.toString(); | ||
| 1585 | } else if (val === undefined) { | ||
| 1586 | v.classList.add('qr--undefined'); | ||
| 1587 | v.textContent = 'undefined'; | ||
| 1588 | } else { | ||
| 1589 | let jsonVal; | ||
| 1590 | try { jsonVal = JSON.parse(val); } catch { /* empty */ } | ||
| 1591 | if (jsonVal && typeof jsonVal == 'object') { | ||
| 1592 | v.textContent = JSON.stringify(jsonVal, null, 2); | ||
| 1593 | } else { | ||
| 1594 | v.textContent = val; | ||
| 1595 | v.classList.add('qr--simple'); | ||
| 1596 | } | ||
| 1597 | } | ||
| 1598 | pipeItem.append(v); | ||
| 1599 | } | ||
| 1600 | wrap.append(pipeItem); | ||
| 1601 | } | ||
| 1602 | if (scope.parent) { | ||
| 1603 | wrap.append(buildVars(scope.parent)); | ||
| 1604 | } | ||
| 1605 | } | ||
| 1606 | return wrap; | ||
| 1607 | }; | ||
| 1608 | const buildStack = ()=>{ | ||
| 1609 | const wrap = document.createElement('div'); { | ||
| 1610 | wrap.classList.add('qr--stack'); | ||
| 1611 | const title = document.createElement('div'); { | ||
| 1612 | title.classList.add('qr--title'); | ||
| 1613 | title.textContent = 'Call Stack'; | ||
| 1614 | wrap.append(title); | ||
| 1615 | } | ||
| 1616 | let ei = -1; | ||
| 1617 | for (const executor of this.debugController.cmdStack.toReversed()) { | ||
| 1618 | ei++; | ||
| 1619 | const c = this.debugController.stack.toReversed()[ei]; | ||
| 1620 | const item = document.createElement('div'); { | ||
| 1621 | item.classList.add('qr--item'); | ||
| 1622 | if (executor.source == source) { | ||
| 1623 | let hi; | ||
| 1624 | item.addEventListener('pointerenter', ()=>{ | ||
| 1625 | const loc = this.getEditorPosition(Math.max(0, executor.start - 1), executor.end, c.fullText); | ||
| 1626 | const layer = syntax.getBoundingClientRect(); | ||
| 1627 | hi = document.createElement('div'); | ||
| 1628 | hi.classList.add('qr--highlight-secondary'); | ||
| 1629 | hi.style.left = `${loc.left - layer.left}px`; | ||
| 1630 | hi.style.width = `${loc.right - loc.left}px`; | ||
| 1631 | hi.style.top = `${loc.top - layer.top + syntax.scrollTop}px`; | ||
| 1632 | hi.style.height = `${loc.bottom - loc.top}px`; | ||
| 1633 | syntax.append(hi); | ||
| 1634 | }); | ||
| 1635 | item.addEventListener('pointerleave', ()=>hi?.remove()); | ||
| 1636 | } | ||
| 1637 | const cmd = document.createElement('div'); { | ||
| 1638 | cmd.classList.add('qr--cmd'); | ||
| 1639 | cmd.textContent = `/${executor.name}`; | ||
| 1640 | if (executor.command.name == 'run') { | ||
| 1641 | cmd.textContent += `${(executor.name == ':' ? '' : ' ')}${executor.unnamedArgumentList[0]?.value}`; | ||
| 1642 | } | ||
| 1643 | item.append(cmd); | ||
| 1644 | } | ||
| 1645 | const src = document.createElement('div'); { | ||
| 1646 | src.classList.add('qr--source'); | ||
| 1647 | const line = closure.fullText.slice(0, executor.start).split('\n').length; | ||
| 1648 | if (uuidCheck.test(executor.source)) { | ||
| 1649 | const p1 = document.createElement('span'); { | ||
| 1650 | p1.classList.add('qr--fixed'); | ||
| 1651 | p1.textContent = executor.source.slice(0,5); | ||
| 1652 | src.append(p1); | ||
| 1653 | } | ||
| 1654 | const p2 = document.createElement('span'); { | ||
| 1655 | p2.classList.add('qr--truncated'); | ||
| 1656 | p2.textContent = '…'; | ||
| 1657 | src.append(p2); | ||
| 1658 | } | ||
| 1659 | const p3 = document.createElement('span'); { | ||
| 1660 | p3.classList.add('qr--fixed'); | ||
| 1661 | p3.textContent = `${executor.source.slice(-5)}:${line}`; | ||
| 1662 | src.append(p3); | ||
| 1663 | } | ||
| 1664 | src.title = `anonymous: ${executor.source}`; | ||
| 1665 | } else { | ||
| 1666 | src.textContent = `${executor.source}:${line}`; | ||
| 1667 | } | ||
| 1668 | item.append(src); | ||
| 1669 | } | ||
| 1670 | wrap.append(item); | ||
| 1671 | } | ||
| 1672 | } | ||
| 1673 | } | ||
| 1674 | return wrap; | ||
| 1675 | }; | ||
| 1676 | this.editorDebugState.append(buildVars(closure.scope, true)); | ||
| 1677 | this.editorDebugState.append(buildStack()); | ||
| 1678 | this.editorDebugState.classList.add('qr--active'); | ||
| 1679 | const loc = this.getEditorPosition(Math.max(0, executor.start - 1), executor.end, closure.fullText); | ||
| 1680 | const layer = syntax.getBoundingClientRect(); | ||
| 1681 | const hi = document.createElement('div'); | ||
| 1682 | hi.classList.add('qr--highlight'); | ||
| 1683 | if (this.debugController.namedArguments === undefined) { | ||
| 1684 | hi.classList.add('qr--unresolved'); | ||
| 1685 | } | ||
| 1686 | hi.style.left = `${loc.left - layer.left}px`; | ||
| 1687 | hi.style.width = `${loc.right - loc.left}px`; | ||
| 1688 | hi.style.top = `${loc.top - layer.top + syntax.scrollTop}px`; | ||
| 1689 | hi.style.height = `${loc.bottom - loc.top}px`; | ||
| 1690 | syntax.append(hi); | ||
| 1691 | const isStepping = await this.debugController.awaitContinue(); | ||
| 1692 | hi.remove(); | ||
| 1693 | this.editorDebugState.textContent = ''; | ||
| 1694 | this.editorDebugState.classList.remove('qr--active'); | ||
| 1695 | this.editorDom.classList.remove('qr--isPaused'); | ||
| 1696 | return isStepping; | ||
| 1697 | }; | ||
| 1698 | const result = await this.onDebug(this); | ||
| 565 | if (this.abortController?.signal?.aborted) { | 1699 | if (this.abortController?.signal?.aborted) { |
| 566 | this.editorExecuteProgress.classList.add('qr--aborted'); | 1700 | this.editorExecuteProgress.classList.add('qr--aborted'); |
| 567 | } else { | 1701 | } else { |
| @@ -586,9 +1720,18 @@ export class QuickReply { | |||
| 586 | `; | 1720 | `; |
| 587 | } | 1721 | } |
| 588 | } | 1722 | } |
| 1723 | if (noSyntax) { | ||
| 1724 | this.editorDom.querySelector('#qr--modal-messageHolder').classList.add('qr--noSyntax'); | ||
| 1725 | } | ||
| 1726 | this.editorMessageLabel.innerHTML = ''; | ||
| 1727 | this.editorMessageLabel.textContent = 'Message / Command: '; | ||
| 1728 | this.editorMessage.value = oText; | ||
| 1729 | this.editorMessage.dispatchEvent(new Event('input', { bubbles:true })); | ||
| 589 | this.editorExecutePromise = null; | 1730 | this.editorExecutePromise = null; |
| 590 | this.editorExecuteBtn.classList.remove('qr--busy'); | 1731 | this.editorExecuteBtn.classList.remove('qr--busy'); |
| 591 | this.editorPopup.dlg.classList.remove('qr--hide'); | 1732 | this.editorDom.classList.remove('qr--isExecuting'); |
| 1733 | this.isExecuting = false; | ||
| 1734 | this.editorPopup.onClosing = null; | ||
| 592 | } | 1735 | } |
| 593 | 1736 | ||
| 594 | updateEditorProgress(done, total) { | 1737 | updateEditorProgress(done, total) { |
| @@ -623,6 +1766,47 @@ export class QuickReply { | |||
| 623 | /** | 1766 | /** |
| 624 | * @param {string} value | 1767 | * @param {string} value |
| 625 | */ | 1768 | */ |
| 1769 | updateIcon(value) { | ||
| 1770 | if (this.onUpdate) { | ||
| 1771 | if (value === null) return; | ||
| 1772 | if (this.settingsDomIcon) { | ||
| 1773 | if (this.icon != value) { | ||
| 1774 | if (value == '') { | ||
| 1775 | if (this.icon) { | ||
| 1776 | this.settingsDomIcon.classList.remove(this.icon); | ||
| 1777 | } | ||
| 1778 | this.settingsDomIcon.textContent = '…'; | ||
| 1779 | this.settingsDomIcon.classList.remove('fa-solid'); | ||
| 1780 | } else { | ||
| 1781 | if (this.icon) { | ||
| 1782 | this.settingsDomIcon.classList.remove(this.icon); | ||
| 1783 | } else { | ||
| 1784 | this.settingsDomIcon.classList.add('fa-solid'); | ||
| 1785 | } | ||
| 1786 | this.settingsDomIcon.classList.add(value); | ||
| 1787 | } | ||
| 1788 | } | ||
| 1789 | } | ||
| 1790 | this.icon = value; | ||
| 1791 | this.updateRender(); | ||
| 1792 | this.onUpdate(this); | ||
| 1793 | } | ||
| 1794 | } | ||
| 1795 | |||
| 1796 | /** | ||
| 1797 | * @param {boolean} value | ||
| 1798 | */ | ||
| 1799 | updateShowLabel(value) { | ||
| 1800 | if (this.onUpdate) { | ||
| 1801 | this.showLabel = value; | ||
| 1802 | this.updateRender(); | ||
| 1803 | this.onUpdate(this); | ||
| 1804 | } | ||
| 1805 | } | ||
| 1806 | |||
| 1807 | /** | ||
| 1808 | * @param {string} value | ||
| 1809 | */ | ||
| 626 | updateLabel(value) { | 1810 | updateLabel(value) { |
| 627 | if (this.onUpdate) { | 1811 | if (this.onUpdate) { |
| 628 | if (this.settingsDomLabel && this.settingsDomLabel.value != value) { | 1812 | if (this.settingsDomLabel && this.settingsDomLabel.value != value) { |
| @@ -670,12 +1854,15 @@ export class QuickReply { | |||
| 670 | } | 1854 | } |
| 671 | 1855 | ||
| 672 | 1856 | ||
| 673 | async execute(args = {}, isEditor = false, isRun = false) { | 1857 | async execute(args = {}, isEditor = false, isRun = false, options = {}) { |
| 674 | if (this.message?.length > 0 && this.onExecute) { | 1858 | if (this.message?.length > 0 && this.onExecute) { |
| 675 | const scope = new SlashCommandScope(); | 1859 | const scope = new SlashCommandScope(); |
| 676 | for (const key of Object.keys(args)) { | 1860 | for (const key of Object.keys(args)) { |
| 1861 | if (key[0] == '_') continue; | ||
| 1862 | if (key == 'isAutoExecute') continue; | ||
| 677 | scope.setMacro(`arg::${key}`, args[key]); | 1863 | scope.setMacro(`arg::${key}`, args[key]); |
| 678 | } | 1864 | } |
| 1865 | scope.setMacro('arg::*', ''); | ||
| 679 | if (isEditor) { | 1866 | if (isEditor) { |
| 680 | this.abortController = new SlashCommandAbortController(); | 1867 | this.abortController = new SlashCommandAbortController(); |
| 681 | } | 1868 | } |
| @@ -685,6 +1872,7 @@ export class QuickReply { | |||
| 685 | isEditor, | 1872 | isEditor, |
| 686 | isRun, | 1873 | isRun, |
| 687 | scope, | 1874 | scope, |
| 1875 | executionOptions: options, | ||
| 688 | }); | 1876 | }); |
| 689 | } | 1877 | } |
| 690 | } | 1878 | } |
| @@ -695,6 +1883,8 @@ export class QuickReply { | |||
| 695 | toJSON() { | 1883 | toJSON() { |
| 696 | return { | 1884 | return { |
| 697 | id: this.id, | 1885 | id: this.id, |
| 1886 | icon: this.icon, | ||
| 1887 | showLabel: this.showLabel, | ||
| 698 | label: this.label, | 1888 | label: this.label, |
| 699 | title: this.title, | 1889 | title: this.title, |
| 700 | message: this.message, | 1890 | message: this.message, |
| @@ -60,7 +60,12 @@ export class QuickReplyConfig { | |||
| 60 | /**@type {HTMLElement}*/ | 60 | /**@type {HTMLElement}*/ |
| 61 | this.setListDom = root.querySelector('.qr--setList'); | 61 | this.setListDom = root.querySelector('.qr--setList'); |
| 62 | root.querySelector('.qr--setListAdd').addEventListener('click', ()=>{ | 62 | root.querySelector('.qr--setListAdd').addEventListener('click', ()=>{ |
| 63 | this.addSet(QuickReplySet.list[0]); | 63 | const newSet = QuickReplySet.list.find(qr=>!this.setList.find(qrl=>qrl.set == qr)); |
| 64 | if (newSet) { | ||
| 65 | this.addSet(newSet); | ||
| 66 | } else { | ||
| 67 | toastr.warning('All existing QR Sets have already been added.'); | ||
| 68 | } | ||
| 64 | }); | 69 | }); |
| 65 | this.updateSetListDom(); | 70 | this.updateSetListDom(); |
| 66 | } | 71 | } |
| @@ -1,7 +1,9 @@ | |||
| 1 | import { getRequestHeaders, substituteParams } from '../../../../script.js'; | 1 | import { getRequestHeaders, substituteParams } from '../../../../script.js'; |
| 2 | import { Popup, POPUP_RESULT, POPUP_TYPE } from '../../../popup.js'; | ||
| 2 | import { executeSlashCommands, executeSlashCommandsOnChatInput, executeSlashCommandsWithOptions } from '../../../slash-commands.js'; | 3 | import { executeSlashCommands, executeSlashCommandsOnChatInput, executeSlashCommandsWithOptions } from '../../../slash-commands.js'; |
| 4 | import { SlashCommandParser } from '../../../slash-commands/SlashCommandParser.js'; | ||
| 3 | import { SlashCommandScope } from '../../../slash-commands/SlashCommandScope.js'; | 5 | import { SlashCommandScope } from '../../../slash-commands/SlashCommandScope.js'; |
| 4 | import { debounceAsync, warn } from '../index.js'; | 6 | import { debounceAsync, log, warn } from '../index.js'; |
| 5 | import { QuickReply } from './QuickReply.js'; | 7 | import { QuickReply } from './QuickReply.js'; |
| 6 | 8 | ||
| 7 | export class QuickReplySet { | 9 | export class QuickReplySet { |
| @@ -16,7 +18,7 @@ export class QuickReplySet { | |||
| 16 | } | 18 | } |
| 17 | 19 | ||
| 18 | /** | 20 | /** |
| 19 | * @param {String} name - name of the QuickReplySet | 21 | * @param {string} name - name of the QuickReplySet |
| 20 | */ | 22 | */ |
| 21 | static get(name) { | 23 | static get(name) { |
| 22 | return this.list.find(it=>it.name == name); | 24 | return this.list.find(it=>it.name == name); |
| @@ -25,17 +27,19 @@ export class QuickReplySet { | |||
| 25 | 27 | ||
| 26 | 28 | ||
| 27 | 29 | ||
| 28 | /**@type {String}*/ name; | 30 | /**@type {string}*/ name; |
| 29 | /**@type {Boolean}*/ disableSend = false; | 31 | /**@type {boolean}*/ disableSend = false; |
| 30 | /**@type {Boolean}*/ placeBeforeInput = false; | 32 | /**@type {boolean}*/ placeBeforeInput = false; |
| 31 | /**@type {Boolean}*/ injectInput = false; | 33 | /**@type {boolean}*/ injectInput = false; |
| 34 | /**@type {string}*/ color = 'transparent'; | ||
| 35 | /**@type {boolean}*/ onlyBorderColor = false; | ||
| 32 | /**@type {QuickReply[]}*/ qrList = []; | 36 | /**@type {QuickReply[]}*/ qrList = []; |
| 33 | 37 | ||
| 34 | /**@type {Number}*/ idIndex = 0; | 38 | /**@type {number}*/ idIndex = 0; |
| 35 | 39 | ||
| 36 | /**@type {Boolean}*/ isDeleted = false; | 40 | /**@type {boolean}*/ isDeleted = false; |
| 37 | 41 | ||
| 38 | /**@type {Function}*/ save; | 42 | /**@type {function}*/ save; |
| 39 | 43 | ||
| 40 | /**@type {HTMLElement}*/ dom; | 44 | /**@type {HTMLElement}*/ dom; |
| 41 | /**@type {HTMLElement}*/ settingsDom; | 45 | /**@type {HTMLElement}*/ settingsDom; |
| @@ -64,6 +68,7 @@ export class QuickReplySet { | |||
| 64 | const root = document.createElement('div'); { | 68 | const root = document.createElement('div'); { |
| 65 | this.dom = root; | 69 | this.dom = root; |
| 66 | root.classList.add('qr--buttons'); | 70 | root.classList.add('qr--buttons'); |
| 71 | this.updateColor(); | ||
| 67 | this.qrList.filter(qr=>!qr.isHidden).forEach(qr=>{ | 72 | this.qrList.filter(qr=>!qr.isHidden).forEach(qr=>{ |
| 68 | root.append(qr.render()); | 73 | root.append(qr.render()); |
| 69 | }); | 74 | }); |
| @@ -78,6 +83,22 @@ export class QuickReplySet { | |||
| 78 | this.dom.append(qr.render()); | 83 | this.dom.append(qr.render()); |
| 79 | }); | 84 | }); |
| 80 | } | 85 | } |
| 86 | updateColor() { | ||
| 87 | if (!this.dom) return; | ||
| 88 | if (this.color && this.color != 'transparent') { | ||
| 89 | this.dom.style.setProperty('--qr--color', this.color); | ||
| 90 | this.dom.classList.add('qr--color'); | ||
| 91 | if (this.onlyBorderColor) { | ||
| 92 | this.dom.classList.add('qr--borderColor'); | ||
| 93 | } else { | ||
| 94 | this.dom.classList.remove('qr--borderColor'); | ||
| 95 | } | ||
| 96 | } else { | ||
| 97 | this.dom.style.setProperty('--qr--color', 'transparent'); | ||
| 98 | this.dom.classList.remove('qr--color'); | ||
| 99 | this.dom.classList.remove('qr--borderColor'); | ||
| 100 | } | ||
| 101 | } | ||
| 81 | 102 | ||
| 82 | 103 | ||
| 83 | 104 | ||
| @@ -93,6 +114,11 @@ export class QuickReplySet { | |||
| 93 | } | 114 | } |
| 94 | return this.settingsDom; | 115 | return this.settingsDom; |
| 95 | } | 116 | } |
| 117 | /** | ||
| 118 | * | ||
| 119 | * @param {QuickReply} qr | ||
| 120 | * @param {number} idx | ||
| 121 | */ | ||
| 96 | renderSettingsItem(qr, idx) { | 122 | renderSettingsItem(qr, idx) { |
| 97 | this.settingsDom.append(qr.renderSettings(idx)); | 123 | this.settingsDom.append(qr.renderSettings(idx)); |
| 98 | } | 124 | } |
| @@ -102,6 +128,18 @@ export class QuickReplySet { | |||
| 102 | 128 | ||
| 103 | /** | 129 | /** |
| 104 | * | 130 | * |
| 131 | * @param {QuickReply} qr | ||
| 132 | */ | ||
| 133 | async debug(qr) { | ||
| 134 | const parser = new SlashCommandParser(); | ||
| 135 | const closure = parser.parse(qr.message, true, [], qr.abortController, qr.debugController); | ||
| 136 | closure.source = `${this.name}.${qr.label}`; | ||
| 137 | closure.onProgress = (done, total) => qr.updateEditorProgress(done, total); | ||
| 138 | closure.scope.setMacro('arg::*', ''); | ||
| 139 | return (await closure.execute())?.pipe; | ||
| 140 | } | ||
| 141 | /** | ||
| 142 | * | ||
| 105 | * @param {QuickReply} qr The QR to execute. | 143 | * @param {QuickReply} qr The QR to execute. |
| 106 | * @param {object} options | 144 | * @param {object} options |
| 107 | * @param {string} [options.message] (null) altered message to be used | 145 | * @param {string} [options.message] (null) altered message to be used |
| @@ -109,6 +147,7 @@ export class QuickReplySet { | |||
| 109 | * @param {boolean} [options.isEditor] (false) whether the execution is triggered by the QR editor | 147 | * @param {boolean} [options.isEditor] (false) whether the execution is triggered by the QR editor |
| 110 | * @param {boolean} [options.isRun] (false) whether the execution is triggered by /run or /: (window.executeQuickReplyByName) | 148 | * @param {boolean} [options.isRun] (false) whether the execution is triggered by /run or /: (window.executeQuickReplyByName) |
| 111 | * @param {SlashCommandScope} [options.scope] (null) scope to be used when running the command | 149 | * @param {SlashCommandScope} [options.scope] (null) scope to be used when running the command |
| 150 | * @param {import('../../../slash-commands.js').ExecuteSlashCommandsOptions} [options.executionOptions] ({}) further execution options | ||
| 112 | * @returns | 151 | * @returns |
| 113 | */ | 152 | */ |
| 114 | async executeWithOptions(qr, options = {}) { | 153 | async executeWithOptions(qr, options = {}) { |
| @@ -118,7 +157,9 @@ export class QuickReplySet { | |||
| 118 | isEditor:false, | 157 | isEditor:false, |
| 119 | isRun:false, | 158 | isRun:false, |
| 120 | scope:null, | 159 | scope:null, |
| 160 | executionOptions:{}, | ||
| 121 | }, options); | 161 | }, options); |
| 162 | const execOptions = options.executionOptions; | ||
| 122 | /**@type {HTMLTextAreaElement}*/ | 163 | /**@type {HTMLTextAreaElement}*/ |
| 123 | const ta = document.querySelector('#send_textarea'); | 164 | const ta = document.querySelector('#send_textarea'); |
| 124 | const finalMessage = options.message ?? qr.message; | 165 | const finalMessage = options.message ?? qr.message; |
| @@ -136,21 +177,24 @@ export class QuickReplySet { | |||
| 136 | if (input[0] == '/' && !this.disableSend) { | 177 | if (input[0] == '/' && !this.disableSend) { |
| 137 | let result; | 178 | let result; |
| 138 | if (options.isAutoExecute || options.isRun) { | 179 | if (options.isAutoExecute || options.isRun) { |
| 139 | result = await executeSlashCommandsWithOptions(input, { | 180 | result = await executeSlashCommandsWithOptions(input, Object.assign(execOptions, { |
| 140 | handleParserErrors: true, | 181 | handleParserErrors: true, |
| 141 | scope: options.scope, | 182 | scope: options.scope, |
| 142 | }); | 183 | source: `${this.name}.${qr.label}`, |
| 184 | })); | ||
| 143 | } else if (options.isEditor) { | 185 | } else if (options.isEditor) { |
| 144 | result = await executeSlashCommandsWithOptions(input, { | 186 | result = await executeSlashCommandsWithOptions(input, Object.assign(execOptions, { |
| 145 | handleParserErrors: false, | 187 | handleParserErrors: false, |
| 146 | scope: options.scope, | 188 | scope: options.scope, |
| 147 | abortController: qr.abortController, | 189 | abortController: qr.abortController, |
| 190 | source: `${this.name}.${qr.label}`, | ||
| 148 | onProgress: (done, total) => qr.updateEditorProgress(done, total), | 191 | onProgress: (done, total) => qr.updateEditorProgress(done, total), |
| 149 | }); | 192 | })); |
| 150 | } else { | 193 | } else { |
| 151 | result = await executeSlashCommandsOnChatInput(input, { | 194 | result = await executeSlashCommandsOnChatInput(input, Object.assign(execOptions, { |
| 152 | scope: options.scope, | 195 | scope: options.scope, |
| 153 | }); | 196 | source: `${this.name}.${qr.label}`, |
| 197 | })); | ||
| 154 | } | 198 | } |
| 155 | return typeof result === 'object' ? result?.pipe : ''; | 199 | return typeof result === 'object' ? result?.pipe : ''; |
| 156 | } | 200 | } |
| @@ -165,7 +209,7 @@ export class QuickReplySet { | |||
| 165 | } | 209 | } |
| 166 | /** | 210 | /** |
| 167 | * @param {QuickReply} qr | 211 | * @param {QuickReply} qr |
| 168 | * @param {String} [message] - optional altered message to be used | 212 | * @param {string} [message] - optional altered message to be used |
| 169 | * @param {SlashCommandScope} [scope] - optional scope to be used when running the command | 213 | * @param {SlashCommandScope} [scope] - optional scope to be used when running the command |
| 170 | */ | 214 | */ |
| 171 | async execute(qr, message = null, isAutoExecute = false, scope = null) { | 215 | async execute(qr, message = null, isAutoExecute = false, scope = null) { |
| @@ -179,10 +223,11 @@ export class QuickReplySet { | |||
| 179 | 223 | ||
| 180 | 224 | ||
| 181 | 225 | ||
| 182 | addQuickReply() { | 226 | addQuickReply(data = {}) { |
| 183 | const id = Math.max(this.idIndex, this.qrList.reduce((max,qr)=>Math.max(max,qr.id),0)) + 1; | 227 | const id = Math.max(this.idIndex, this.qrList.reduce((max,qr)=>Math.max(max,qr.id),0)) + 1; |
| 228 | data.id = | ||
| 184 | this.idIndex = id + 1; | 229 | this.idIndex = id + 1; |
| 185 | const qr = QuickReply.from({ id }); | 230 | const qr = QuickReply.from(data); |
| 186 | this.qrList.push(qr); | 231 | this.qrList.push(qr); |
| 187 | this.hookQuickReply(qr); | 232 | this.hookQuickReply(qr); |
| 188 | if (this.settingsDom) { | 233 | if (this.settingsDom) { |
| @@ -194,11 +239,131 @@ export class QuickReplySet { | |||
| 194 | this.save(); | 239 | this.save(); |
| 195 | return qr; | 240 | return qr; |
| 196 | } | 241 | } |
| 242 | addQuickReplyFromText(qrJson) { | ||
| 243 | let data; | ||
| 244 | if (qrJson) { | ||
| 245 | try { | ||
| 246 | data = JSON.parse(qrJson ?? '{}'); | ||
| 247 | delete data.id; | ||
| 248 | } catch { | ||
| 249 | // not JSON data | ||
| 250 | } | ||
| 251 | if (data) { | ||
| 252 | // JSON data | ||
| 253 | if (data.label === undefined || data.message === undefined) { | ||
| 254 | // not a QR | ||
| 255 | toastr.error('Not a QR.'); | ||
| 256 | return; | ||
| 257 | } | ||
| 258 | } else { | ||
| 259 | // no JSON, use plaintext as QR message | ||
| 260 | data = { message: qrJson }; | ||
| 261 | } | ||
| 262 | } else { | ||
| 263 | data = {}; | ||
| 264 | } | ||
| 265 | const newQr = this.addQuickReply(data); | ||
| 266 | return newQr; | ||
| 267 | } | ||
| 197 | 268 | ||
| 269 | /** | ||
| 270 | * | ||
| 271 | * @param {QuickReply} qr | ||
| 272 | */ | ||
| 198 | hookQuickReply(qr) { | 273 | hookQuickReply(qr) { |
| 274 | qr.onDebug = ()=>this.debug(qr); | ||
| 199 | qr.onExecute = (_, options)=>this.executeWithOptions(qr, options); | 275 | qr.onExecute = (_, options)=>this.executeWithOptions(qr, options); |
| 200 | qr.onDelete = ()=>this.removeQuickReply(qr); | 276 | qr.onDelete = ()=>this.removeQuickReply(qr); |
| 201 | qr.onUpdate = ()=>this.save(); | 277 | qr.onUpdate = ()=>this.save(); |
| 278 | qr.onInsertBefore = (qrJson)=>{ | ||
| 279 | this.addQuickReplyFromText(qrJson); | ||
| 280 | const newQr = this.qrList.pop(); | ||
| 281 | this.qrList.splice(this.qrList.indexOf(qr), 0, newQr); | ||
| 282 | if (qr.settingsDom) { | ||
| 283 | qr.settingsDom.insertAdjacentElement('beforebegin', newQr.settingsDom); | ||
| 284 | } | ||
| 285 | this.save(); | ||
| 286 | }; | ||
| 287 | qr.onTransfer = async()=>{ | ||
| 288 | /**@type {HTMLSelectElement} */ | ||
| 289 | let sel; | ||
| 290 | let isCopy = false; | ||
| 291 | const dom = document.createElement('div'); { | ||
| 292 | dom.classList.add('qr--transferModal'); | ||
| 293 | const title = document.createElement('h3'); { | ||
| 294 | title.textContent = 'Transfer Quick Reply'; | ||
| 295 | dom.append(title); | ||
| 296 | } | ||
| 297 | const subTitle = document.createElement('h4'); { | ||
| 298 | const entryName = qr.label; | ||
| 299 | const bookName = this.name; | ||
| 300 | subTitle.textContent = `${bookName}: ${entryName}`; | ||
| 301 | dom.append(subTitle); | ||
| 302 | } | ||
| 303 | sel = document.createElement('select'); { | ||
| 304 | sel.classList.add('qr--transferSelect'); | ||
| 305 | sel.setAttribute('autofocus', '1'); | ||
| 306 | const noOpt = document.createElement('option'); { | ||
| 307 | noOpt.value = ''; | ||
| 308 | noOpt.textContent = '-- Select QR Set --'; | ||
| 309 | sel.append(noOpt); | ||
| 310 | } | ||
| 311 | for (const qrs of QuickReplySet.list) { | ||
| 312 | const opt = document.createElement('option'); { | ||
| 313 | opt.value = qrs.name; | ||
| 314 | opt.textContent = qrs.name; | ||
| 315 | sel.append(opt); | ||
| 316 | } | ||
| 317 | } | ||
| 318 | sel.addEventListener('keyup', (evt)=>{ | ||
| 319 | if (evt.key == 'Shift') { | ||
| 320 | (dlg.dom ?? dlg.dlg).classList.remove('qr--isCopy'); | ||
| 321 | return; | ||
| 322 | } | ||
| 323 | }); | ||
| 324 | sel.addEventListener('keydown', (evt)=>{ | ||
| 325 | if (evt.key == 'Shift') { | ||
| 326 | (dlg.dom ?? dlg.dlg).classList.add('qr--isCopy'); | ||
| 327 | return; | ||
| 328 | } | ||
| 329 | if (!evt.ctrlKey && !evt.altKey && evt.key == 'Enter') { | ||
| 330 | evt.preventDefault(); | ||
| 331 | if (evt.shiftKey) isCopy = true; | ||
| 332 | dlg.completeAffirmative(); | ||
| 333 | } | ||
| 334 | }); | ||
| 335 | dom.append(sel); | ||
| 336 | } | ||
| 337 | const hintP = document.createElement('p'); { | ||
| 338 | const hint = document.createElement('small'); { | ||
| 339 | hint.textContent = 'Type or arrows to select QR Set. Enter to transfer. Shift+Enter to copy.'; | ||
| 340 | hintP.append(hint); | ||
| 341 | } | ||
| 342 | dom.append(hintP); | ||
| 343 | } | ||
| 344 | } | ||
| 345 | const dlg = new Popup(dom, POPUP_TYPE.CONFIRM, null, { okButton:'Transfer', cancelButton:'Cancel' }); | ||
| 346 | const copyBtn = document.createElement('div'); { | ||
| 347 | copyBtn.classList.add('qr--copy'); | ||
| 348 | copyBtn.classList.add('menu_button'); | ||
| 349 | copyBtn.textContent = 'Copy'; | ||
| 350 | copyBtn.addEventListener('click', ()=>{ | ||
| 351 | isCopy = true; | ||
| 352 | dlg.completeAffirmative(); | ||
| 353 | }); | ||
| 354 | (dlg.ok ?? dlg.okButton).insertAdjacentElement('afterend', copyBtn); | ||
| 355 | } | ||
| 356 | const prom = dlg.show(); | ||
| 357 | sel.focus(); | ||
| 358 | await prom; | ||
| 359 | if (dlg.result == POPUP_RESULT.AFFIRMATIVE) { | ||
| 360 | const qrs = QuickReplySet.list.find(it=>it.name == sel.value); | ||
| 361 | qrs.addQuickReply(qr.toJSON()); | ||
| 362 | if (!isCopy) { | ||
| 363 | qr.delete(); | ||
| 364 | } | ||
| 365 | } | ||
| 366 | }; | ||
| 202 | } | 367 | } |
| 203 | 368 | ||
| 204 | removeQuickReply(qr) { | 369 | removeQuickReply(qr) { |
| @@ -214,6 +379,8 @@ export class QuickReplySet { | |||
| 214 | disableSend: this.disableSend, | 379 | disableSend: this.disableSend, |
| 215 | placeBeforeInput: this.placeBeforeInput, | 380 | placeBeforeInput: this.placeBeforeInput, |
| 216 | injectInput: this.injectInput, | 381 | injectInput: this.injectInput, |
| 382 | color: this.color, | ||
| 383 | onlyBorderColor: this.onlyBorderColor, | ||
| 217 | qrList: this.qrList, | 384 | qrList: this.qrList, |
| 218 | idIndex: this.idIndex, | 385 | idIndex: this.idIndex, |
| 219 | }; | 386 | }; |
| @@ -245,9 +412,13 @@ export class QuickReplySet { | |||
| 245 | if (response.ok) { | 412 | if (response.ok) { |
| 246 | this.unrender(); | 413 | this.unrender(); |
| 247 | const idx = QuickReplySet.list.indexOf(this); | 414 | const idx = QuickReplySet.list.indexOf(this); |
| 415 | if (idx > -1) { | ||
| 248 | QuickReplySet.list.splice(idx, 1); | 416 | QuickReplySet.list.splice(idx, 1); |
| 249 | this.isDeleted = true; | 417 | this.isDeleted = true; |
| 250 | } else { | 418 | } else { |
| 419 | warn(`Deleted Quick Reply Set was not found in the list of sets: ${this.name}`); | ||
| 420 | } | ||
| 421 | } else { | ||
| 251 | warn(`Failed to delete Quick Reply Set: ${this.name}`); | 422 | warn(`Failed to delete Quick Reply Set: ${this.name}`); |
| 252 | } | 423 | } |
| 253 | } | 424 | } |
| @@ -45,7 +45,7 @@ export class QuickReplySetLink { | |||
| 45 | this.set = QuickReplySet.get(set.value); | 45 | this.set = QuickReplySet.get(set.value); |
| 46 | this.update(); | 46 | this.update(); |
| 47 | }); | 47 | }); |
| 48 | QuickReplySet.list.forEach(qrs=>{ | 48 | QuickReplySet.list.toSorted((a,b)=>a.name.toLowerCase().localeCompare(b.name.toLowerCase())).forEach(qrs=>{ |
| 49 | const opt = document.createElement('option'); { | 49 | const opt = document.createElement('option'); { |
| 50 | opt.value = qrs.name; | 50 | opt.value = qrs.name; |
| 51 | opt.textContent = qrs.name; | 51 | opt.textContent = qrs.name; |
| @@ -1,8 +1,12 @@ | |||
| 1 | import { SlashCommand } from '../../../slash-commands/SlashCommand.js'; | 1 | import { SlashCommand } from '../../../slash-commands/SlashCommand.js'; |
| 2 | import { SlashCommandAbortController } from '../../../slash-commands/SlashCommandAbortController.js'; | ||
| 2 | import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../../slash-commands/SlashCommandArgument.js'; | 3 | import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../../slash-commands/SlashCommandArgument.js'; |
| 4 | import { SlashCommandClosure } from '../../../slash-commands/SlashCommandClosure.js'; | ||
| 3 | import { enumIcons } from '../../../slash-commands/SlashCommandCommonEnumsProvider.js'; | 5 | import { enumIcons } from '../../../slash-commands/SlashCommandCommonEnumsProvider.js'; |
| 6 | import { SlashCommandDebugController } from '../../../slash-commands/SlashCommandDebugController.js'; | ||
| 4 | import { SlashCommandEnumValue, enumTypes } from '../../../slash-commands/SlashCommandEnumValue.js'; | 7 | import { SlashCommandEnumValue, enumTypes } from '../../../slash-commands/SlashCommandEnumValue.js'; |
| 5 | import { SlashCommandParser } from '../../../slash-commands/SlashCommandParser.js'; | 8 | import { SlashCommandParser } from '../../../slash-commands/SlashCommandParser.js'; |
| 9 | import { SlashCommandScope } from '../../../slash-commands/SlashCommandScope.js'; | ||
| 6 | import { isTrueBoolean } from '../../../utils.js'; | 10 | import { isTrueBoolean } from '../../../utils.js'; |
| 7 | // eslint-disable-next-line no-unused-vars | 11 | // eslint-disable-next-line no-unused-vars |
| 8 | import { QuickReplyApi } from '../api/QuickReplyApi.js'; | 12 | import { QuickReplyApi } from '../api/QuickReplyApi.js'; |
| @@ -47,6 +51,13 @@ export class SlashCommandHandler { | |||
| 47 | return new SlashCommandEnumValue(qr.label, message, enumTypes.enum, enumIcons.qr); | 51 | return new SlashCommandEnumValue(qr.label, message, enumTypes.enum, enumIcons.qr); |
| 48 | }) ?? [], | 52 | }) ?? [], |
| 49 | 53 | ||
| 54 | /** All QRs inside a set, utilizing the "set" named argument, returns the QR's ID */ | ||
| 55 | qrIds: (executor) => QuickReplySet.get(String(executor.namedArgumentList.find(x => x.name == 'set')?.value))?.qrList.map(qr => { | ||
| 56 | const icons = getExecutionIcons(qr); | ||
| 57 | const message = `${qr.automationId ? `[${qr.automationId}]` : ''}${icons ? `[auto: ${icons}]` : ''} ${qr.title || qr.message}`.trim(); | ||
| 58 | return new SlashCommandEnumValue(qr.label, message, enumTypes.enum, enumIcons.qr, null, ()=>qr.id.toString(), true); | ||
| 59 | }) ?? [], | ||
| 60 | |||
| 50 | /** All QRs as a set.name string, to be able to execute, for example via the /run command */ | 61 | /** All QRs as a set.name string, to be able to execute, for example via the /run command */ |
| 51 | qrExecutables: () => { | 62 | qrExecutables: () => { |
| 52 | const globalSetList = this.api.settings.config.setList; | 63 | const globalSetList = this.api.settings.config.setList; |
| @@ -63,7 +74,7 @@ export class SlashCommandHandler { | |||
| 63 | ...otherQrs.map(x => new SlashCommandEnumValue(`${x.set.name}.${x.qr.label}`, `${x.qr.title || x.qr.message}`, enumTypes.qr, enumIcons.qr)), | 74 | ...otherQrs.map(x => new SlashCommandEnumValue(`${x.set.name}.${x.qr.label}`, `${x.qr.title || x.qr.message}`, enumTypes.qr, enumIcons.qr)), |
| 64 | ]; | 75 | ]; |
| 65 | }, | 76 | }, |
| 66 | } | 77 | }; |
| 67 | 78 | ||
| 68 | window['qrEnumProviderExecutables'] = localEnumProviders.qrExecutables; | 79 | window['qrEnumProviderExecutables'] = localEnumProviders.qrExecutables; |
| 69 | 80 | ||
| @@ -234,8 +245,20 @@ export class SlashCommandHandler { | |||
| 234 | name: 'label', | 245 | name: 'label', |
| 235 | description: 'text on the button, e.g., label=MyButton', | 246 | description: 'text on the button, e.g., label=MyButton', |
| 236 | typeList: [ARGUMENT_TYPE.STRING], | 247 | typeList: [ARGUMENT_TYPE.STRING], |
| 237 | isRequired: true, | 248 | isRequired: false, |
| 238 | enumProvider: localEnumProviders.qrLabels, | 249 | enumProvider: localEnumProviders.qrEntries, |
| 250 | }), | ||
| 251 | SlashCommandNamedArgument.fromProps({ | ||
| 252 | name: 'icon', | ||
| 253 | description: 'icon to show on the button, e.g., icon=fa-pencil', | ||
| 254 | typeList: [ARGUMENT_TYPE.STRING], | ||
| 255 | isRequired: false, | ||
| 256 | }), | ||
| 257 | SlashCommandNamedArgument.fromProps({ | ||
| 258 | name: 'showlabel', | ||
| 259 | description: 'whether to show the label even when an icon is assigned, e.g., icon=fa-pencil showlabel=true', | ||
| 260 | typeList: [ARGUMENT_TYPE.BOOLEAN], | ||
| 261 | isRequired: false, | ||
| 239 | }), | 262 | }), |
| 240 | new SlashCommandNamedArgument('hidden', 'whether the button should be hidden, e.g., hidden=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'), | 263 | new SlashCommandNamedArgument('hidden', 'whether the button should be hidden, e.g., hidden=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'), |
| 241 | new SlashCommandNamedArgument('startup', 'auto execute on app startup, e.g., startup=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'), | 264 | new SlashCommandNamedArgument('startup', 'auto execute on app startup, e.g., startup=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'), |
| @@ -247,6 +270,13 @@ export class SlashCommandHandler { | |||
| 247 | ]; | 270 | ]; |
| 248 | const qrUpdateArgs = [ | 271 | const qrUpdateArgs = [ |
| 249 | new SlashCommandNamedArgument('newlabel', 'new text for the button', [ARGUMENT_TYPE.STRING], false), | 272 | new SlashCommandNamedArgument('newlabel', 'new text for the button', [ARGUMENT_TYPE.STRING], false), |
| 273 | SlashCommandNamedArgument.fromProps({ | ||
| 274 | name: 'id', | ||
| 275 | description: 'numeric ID of the QR, e.g., id=42', | ||
| 276 | typeList: [ARGUMENT_TYPE.NUMBER], | ||
| 277 | isRequired: false, | ||
| 278 | enumProvider: localEnumProviders.qrIds, | ||
| 279 | }), | ||
| 250 | ]; | 280 | ]; |
| 251 | 281 | ||
| 252 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-create', | 282 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-create', |
| @@ -272,13 +302,61 @@ export class SlashCommandHandler { | |||
| 272 | </div> | 302 | </div> |
| 273 | `, | 303 | `, |
| 274 | })); | 304 | })); |
| 305 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-get', | ||
| 306 | callback: (args, _) => { | ||
| 307 | return this.getQuickReply(args); | ||
| 308 | }, | ||
| 309 | namedArgumentList: [ | ||
| 310 | SlashCommandNamedArgument.fromProps({ | ||
| 311 | name: 'set', | ||
| 312 | description: 'name of the QR set, e.g., set=PresetName1', | ||
| 313 | typeList: [ARGUMENT_TYPE.STRING], | ||
| 314 | isRequired: true, | ||
| 315 | enumProvider: localEnumProviders.qrSets, | ||
| 316 | }), | ||
| 317 | SlashCommandNamedArgument.fromProps({ | ||
| 318 | name: 'label', | ||
| 319 | description: 'text on the button, e.g., label=MyButton', | ||
| 320 | typeList: [ARGUMENT_TYPE.STRING], | ||
| 321 | isRequired: false, | ||
| 322 | enumProvider: localEnumProviders.qrEntries, | ||
| 323 | }), | ||
| 324 | SlashCommandNamedArgument.fromProps({ | ||
| 325 | name: 'id', | ||
| 326 | description: 'numeric ID of the QR, e.g., id=42', | ||
| 327 | typeList: [ARGUMENT_TYPE.NUMBER], | ||
| 328 | isRequired: false, | ||
| 329 | enumProvider: localEnumProviders.qrIds, | ||
| 330 | }), | ||
| 331 | ], | ||
| 332 | returns: 'a dictionary with all the QR\'s properties', | ||
| 333 | helpString: ` | ||
| 334 | <div>Get a Quick Reply's properties.</div> | ||
| 335 | <div> | ||
| 336 | <strong>Examples:</strong> | ||
| 337 | <ul> | ||
| 338 | <li> | ||
| 339 | <pre><code>/qr-get set=MyPreset label=MyButton | /echo</code></pre> | ||
| 340 | <pre><code>/qr-get set=MyPreset id=42 | /echo</code></pre> | ||
| 341 | </li> | ||
| 342 | </ul> | ||
| 343 | </div> | ||
| 344 | `, | ||
| 345 | })); | ||
| 275 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-update', | 346 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-update', |
| 276 | callback: (args, message) => { | 347 | callback: (args, message) => { |
| 277 | this.updateQuickReply(args, message); | 348 | this.updateQuickReply(args, message); |
| 278 | return ''; | 349 | return ''; |
| 279 | }, | 350 | }, |
| 280 | returns: 'updated quick reply', | 351 | returns: 'updated quick reply', |
| 281 | namedArgumentList: [...qrUpdateArgs, ...qrArgs], | 352 | namedArgumentList: [...qrUpdateArgs, ...qrArgs.map(it=>{ |
| 353 | if (it.name == 'label') { | ||
| 354 | const clone = SlashCommandNamedArgument.fromProps(it); | ||
| 355 | clone.isRequired = false; | ||
| 356 | return clone; | ||
| 357 | } | ||
| 358 | return it; | ||
| 359 | })], | ||
| 282 | unnamedArgumentList: [ | 360 | unnamedArgumentList: [ |
| 283 | new SlashCommandArgument('command', [ARGUMENT_TYPE.STRING]), | 361 | new SlashCommandArgument('command', [ARGUMENT_TYPE.STRING]), |
| 284 | ], | 362 | ], |
| @@ -315,6 +393,12 @@ export class SlashCommandHandler { | |||
| 315 | typeList: [ARGUMENT_TYPE.STRING], | 393 | typeList: [ARGUMENT_TYPE.STRING], |
| 316 | enumProvider: localEnumProviders.qrEntries, | 394 | enumProvider: localEnumProviders.qrEntries, |
| 317 | }), | 395 | }), |
| 396 | SlashCommandNamedArgument.fromProps({ | ||
| 397 | name: 'id', | ||
| 398 | description: 'numeric ID of the QR, e.g., id=42', | ||
| 399 | typeList: [ARGUMENT_TYPE.NUMBER], | ||
| 400 | enumProvider: localEnumProviders.qrIds, | ||
| 401 | }), | ||
| 318 | ], | 402 | ], |
| 319 | unnamedArgumentList: [ | 403 | unnamedArgumentList: [ |
| 320 | SlashCommandArgument.fromProps({ | 404 | SlashCommandArgument.fromProps({ |
| @@ -344,6 +428,12 @@ export class SlashCommandHandler { | |||
| 344 | typeList: [ARGUMENT_TYPE.STRING], | 428 | typeList: [ARGUMENT_TYPE.STRING], |
| 345 | enumProvider: localEnumProviders.qrEntries, | 429 | enumProvider: localEnumProviders.qrEntries, |
| 346 | }), | 430 | }), |
| 431 | SlashCommandNamedArgument.fromProps({ | ||
| 432 | name: 'id', | ||
| 433 | description: 'numeric ID of the QR, e.g., id=42', | ||
| 434 | typeList: [ARGUMENT_TYPE.NUMBER], | ||
| 435 | enumProvider: localEnumProviders.qrIds, | ||
| 436 | }), | ||
| 347 | new SlashCommandNamedArgument( | 437 | new SlashCommandNamedArgument( |
| 348 | 'chain', 'boolean', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false', | 438 | 'chain', 'boolean', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false', |
| 349 | ), | 439 | ), |
| @@ -389,6 +479,12 @@ export class SlashCommandHandler { | |||
| 389 | typeList: [ARGUMENT_TYPE.STRING], | 479 | typeList: [ARGUMENT_TYPE.STRING], |
| 390 | enumProvider: localEnumProviders.qrEntries, | 480 | enumProvider: localEnumProviders.qrEntries, |
| 391 | }), | 481 | }), |
| 482 | SlashCommandNamedArgument.fromProps({ | ||
| 483 | name: 'id', | ||
| 484 | description: 'numeric ID of the QR, e.g., id=42', | ||
| 485 | typeList: [ARGUMENT_TYPE.NUMBER], | ||
| 486 | enumProvider: localEnumProviders.qrIds, | ||
| 487 | }), | ||
| 392 | ], | 488 | ], |
| 393 | unnamedArgumentList: [ | 489 | unnamedArgumentList: [ |
| 394 | SlashCommandArgument.fromProps({ | 490 | SlashCommandArgument.fromProps({ |
| @@ -425,6 +521,12 @@ export class SlashCommandHandler { | |||
| 425 | isRequired: true, | 521 | isRequired: true, |
| 426 | enumProvider: localEnumProviders.qrSets, | 522 | enumProvider: localEnumProviders.qrSets, |
| 427 | }), | 523 | }), |
| 524 | SlashCommandNamedArgument.fromProps({ | ||
| 525 | name: 'id', | ||
| 526 | description: 'numeric ID of the QR, e.g., id=42', | ||
| 527 | typeList: [ARGUMENT_TYPE.NUMBER], | ||
| 528 | enumProvider: localEnumProviders.qrIds, | ||
| 529 | }), | ||
| 428 | ], | 530 | ], |
| 429 | unnamedArgumentList: [ | 531 | unnamedArgumentList: [ |
| 430 | SlashCommandArgument.fromProps({ | 532 | SlashCommandArgument.fromProps({ |
| @@ -454,8 +556,8 @@ export class SlashCommandHandler { | |||
| 454 | new SlashCommandNamedArgument('inject', 'inject user input automatically (if disabled use {{input}})', [ARGUMENT_TYPE.BOOLEAN], false), | 556 | new SlashCommandNamedArgument('inject', 'inject user input automatically (if disabled use {{input}})', [ARGUMENT_TYPE.BOOLEAN], false), |
| 455 | ]; | 557 | ]; |
| 456 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set-create', | 558 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set-create', |
| 457 | callback: (args, name) => { | 559 | callback: async (args, name) => { |
| 458 | this.createSet(name, args); | 560 | await this.createSet(name, args); |
| 459 | return ''; | 561 | return ''; |
| 460 | }, | 562 | }, |
| 461 | aliases: ['qr-presetadd'], | 563 | aliases: ['qr-presetadd'], |
| @@ -485,8 +587,8 @@ export class SlashCommandHandler { | |||
| 485 | })); | 587 | })); |
| 486 | 588 | ||
| 487 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set-update', | 589 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set-update', |
| 488 | callback: (args, name) => { | 590 | callback: async (args, name) => { |
| 489 | this.updateSet(name, args); | 591 | await this.updateSet(name, args); |
| 490 | return ''; | 592 | return ''; |
| 491 | }, | 593 | }, |
| 492 | aliases: ['qr-presetupdate'], | 594 | aliases: ['qr-presetupdate'], |
| @@ -510,8 +612,8 @@ export class SlashCommandHandler { | |||
| 510 | `, | 612 | `, |
| 511 | })); | 613 | })); |
| 512 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set-delete', | 614 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set-delete', |
| 513 | callback: (_, name) => { | 615 | callback: async (_, name) => { |
| 514 | this.deleteSet(name); | 616 | await this.deleteSet(name); |
| 515 | return ''; | 617 | return ''; |
| 516 | }, | 618 | }, |
| 517 | aliases: ['qr-presetdelete'], | 619 | aliases: ['qr-presetdelete'], |
| @@ -533,6 +635,134 @@ export class SlashCommandHandler { | |||
| 533 | </div> | 635 | </div> |
| 534 | `, | 636 | `, |
| 535 | })); | 637 | })); |
| 638 | |||
| 639 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-arg', | ||
| 640 | callback: ({ _scope }, [key, value]) => { | ||
| 641 | _scope.setMacro(`arg::${key}`, value, key.includes('*')); | ||
| 642 | return ''; | ||
| 643 | }, | ||
| 644 | unnamedArgumentList: [ | ||
| 645 | SlashCommandArgument.fromProps({ description: 'argument name', | ||
| 646 | typeList: ARGUMENT_TYPE.STRING, | ||
| 647 | isRequired: true, | ||
| 648 | }), | ||
| 649 | SlashCommandArgument.fromProps({ description: 'argument value', | ||
| 650 | typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.BOOLEAN, ARGUMENT_TYPE.LIST, ARGUMENT_TYPE.DICTIONARY], | ||
| 651 | isRequired: true, | ||
| 652 | }), | ||
| 653 | ], | ||
| 654 | splitUnnamedArgument: true, | ||
| 655 | splitUnnamedArgumentCount: 2, | ||
| 656 | helpString: ` | ||
| 657 | <div> | ||
| 658 | Set a fallback value for a Quick Reply argument. | ||
| 659 | </div> | ||
| 660 | <div> | ||
| 661 | <strong>Example:</strong> | ||
| 662 | <pre><code>/qr-arg x foo |\n/echo {{arg::x}}</code></pre> | ||
| 663 | </div> | ||
| 664 | `, | ||
| 665 | })); | ||
| 666 | |||
| 667 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'import', | ||
| 668 | /** | ||
| 669 | * | ||
| 670 | * @param {{_scope:SlashCommandScope, _abortController:SlashCommandAbortController, _debugController:SlashCommandDebugController, from:string}} args | ||
| 671 | * @param {string} value | ||
| 672 | */ | ||
| 673 | callback: (args, value) => { | ||
| 674 | if (!args.from) throw new Error('/import requires from= to be set.'); | ||
| 675 | if (!value) throw new Error('/import requires the unnamed argument to be set.'); | ||
| 676 | let qr = [...this.api.listGlobalSets(), ...this.api.listChatSets()] | ||
| 677 | .map(it=>this.api.getSetByName(it)?.qrList ?? []) | ||
| 678 | .flat() | ||
| 679 | .find(it=>it.label == args.from) | ||
| 680 | ; | ||
| 681 | if (!qr) { | ||
| 682 | let [setName, ...qrNameParts] = args.from.split('.'); | ||
| 683 | let qrName = qrNameParts.join('.'); | ||
| 684 | let qrs = QuickReplySet.get(setName); | ||
| 685 | if (qrs) { | ||
| 686 | qr = qrs.qrList.find(it=>it.label == qrName); | ||
| 687 | } | ||
| 688 | } | ||
| 689 | if (qr) { | ||
| 690 | const parser = new SlashCommandParser(); | ||
| 691 | const closure = parser.parse(qr.message, true, [], args._abortController, args._debugController); | ||
| 692 | if (args._debugController) { | ||
| 693 | closure.source = args.from; | ||
| 694 | } | ||
| 695 | const testCandidates = (executor)=>{ | ||
| 696 | return ( | ||
| 697 | executor.namedArgumentList.find(arg=>arg.name == 'key') | ||
| 698 | && executor.unnamedArgumentList.length > 0 | ||
| 699 | && executor.unnamedArgumentList[0].value instanceof SlashCommandClosure | ||
| 700 | ) || ( | ||
| 701 | !executor.namedArgumentList.find(arg=>arg.name == 'key') | ||
| 702 | && executor.unnamedArgumentList.length > 1 | ||
| 703 | && executor.unnamedArgumentList[1].value instanceof SlashCommandClosure | ||
| 704 | ); | ||
| 705 | }; | ||
| 706 | const candidates = closure.executorList | ||
| 707 | .filter(executor=>['let', 'var'].includes(executor.command.name)) | ||
| 708 | .filter(testCandidates) | ||
| 709 | .map(executor=>({ | ||
| 710 | key: executor.namedArgumentList.find(arg=>arg.name == 'key')?.value ?? executor.unnamedArgumentList[0].value, | ||
| 711 | value: executor.unnamedArgumentList[executor.namedArgumentList.find(arg=>arg.name == 'key') ? 0 : 1].value, | ||
| 712 | })) | ||
| 713 | ; | ||
| 714 | for (let i = 0; i < value.length; i++) { | ||
| 715 | const srcName = value[i]; | ||
| 716 | let dstName = srcName; | ||
| 717 | if (i + 2 < value.length && value[i + 1] == 'as') { | ||
| 718 | dstName = value[i + 2]; | ||
| 719 | i += 2; | ||
| 720 | } | ||
| 721 | const pick = candidates.find(it=>it.key == srcName); | ||
| 722 | if (!pick) throw new Error(`No scoped closure named "${srcName}" found in "${args.from}"`); | ||
| 723 | if (args._scope.existsVariableInScope(dstName)) { | ||
| 724 | args._scope.setVariable(dstName, pick.value); | ||
| 725 | } else { | ||
| 726 | args._scope.letVariable(dstName, pick.value); | ||
| 727 | } | ||
| 728 | } | ||
| 729 | } else { | ||
| 730 | throw new Error(`No Quick Reply found for "${name}".`); | ||
| 731 | } | ||
| 732 | return ''; | ||
| 733 | }, | ||
| 734 | namedArgumentList: [ | ||
| 735 | SlashCommandNamedArgument.fromProps({ name: 'from', | ||
| 736 | description: 'Quick Reply to import from (QRSet.QRLabel)', | ||
| 737 | typeList: ARGUMENT_TYPE.STRING, | ||
| 738 | isRequired: true, | ||
| 739 | }), | ||
| 740 | ], | ||
| 741 | unnamedArgumentList: [ | ||
| 742 | SlashCommandArgument.fromProps({ description: 'what to import (x or x as y)', | ||
| 743 | acceptsMultiple: true, | ||
| 744 | typeList: ARGUMENT_TYPE.STRING, | ||
| 745 | isRequired: true, | ||
| 746 | }), | ||
| 747 | ], | ||
| 748 | splitUnnamedArgument: true, | ||
| 749 | helpString: ` | ||
| 750 | <div> | ||
| 751 | Import one or more closures from another Quick Reply. | ||
| 752 | </div> | ||
| 753 | <div> | ||
| 754 | Only imports closures that are directly assigned a scoped variable via <code>/let</code> or <code>/var</code>. | ||
| 755 | </div> | ||
| 756 | <div> | ||
| 757 | <strong>Examples:</strong> | ||
| 758 | <ul> | ||
| 759 | <li><pre><code>/import from=LibraryQrSet.FooBar foo |\n/:foo</code></pre></li> | ||
| 760 | <li><pre><code>/import from=LibraryQrSet.FooBar\n\tfoo\n\tbar\n|\n/:foo |\n/:bar</code></pre></li> | ||
| 761 | <li><pre><code>/import from=LibraryQrSet.FooBar\n\tfoo as x\n\tbar as y\n|\n/:x |\n/:y</code></pre></li> | ||
| 762 | </ul> | ||
| 763 | </div> | ||
| 764 | `, | ||
| 765 | })); | ||
| 536 | } | 766 | } |
| 537 | 767 | ||
| 538 | 768 | ||
| @@ -618,6 +848,8 @@ export class SlashCommandHandler { | |||
| 618 | args.set ?? '', | 848 | args.set ?? '', |
| 619 | args.label ?? '', | 849 | args.label ?? '', |
| 620 | { | 850 | { |
| 851 | icon: args.icon, | ||
| 852 | showLabel: args.showlabel === undefined ? undefined : isTrueBoolean(args.showlabel), | ||
| 621 | message: message ?? '', | 853 | message: message ?? '', |
| 622 | title: args.title, | 854 | title: args.title, |
| 623 | isHidden: isTrueBoolean(args.hidden), | 855 | isHidden: isTrueBoolean(args.hidden), |
| @@ -633,12 +865,21 @@ export class SlashCommandHandler { | |||
| 633 | toastr.error(ex.message); | 865 | toastr.error(ex.message); |
| 634 | } | 866 | } |
| 635 | } | 867 | } |
| 868 | getQuickReply(args) { | ||
| 869 | try { | ||
| 870 | return JSON.stringify(this.api.getQrByLabel(args.set, args.id !== undefined ? Number(args.id) : args.label)); | ||
| 871 | } catch (ex) { | ||
| 872 | toastr.error(ex.message); | ||
| 873 | } | ||
| 874 | } | ||
| 636 | updateQuickReply(args, message) { | 875 | updateQuickReply(args, message) { |
| 637 | try { | 876 | try { |
| 638 | this.api.updateQuickReply( | 877 | this.api.updateQuickReply( |
| 639 | args.set ?? '', | 878 | args.set ?? '', |
| 640 | args.label ?? '', | 879 | args.id !== undefined ? Number(args.id) : (args.label ?? ''), |
| 641 | { | 880 | { |
| 881 | icon: args.icon, | ||
| 882 | showLabel: args.showlabel === undefined ? undefined : isTrueBoolean(args.showlabel), | ||
| 642 | newLabel: args.newlabel, | 883 | newLabel: args.newlabel, |
| 643 | message: (message ?? '').trim().length > 0 ? message : undefined, | 884 | message: (message ?? '').trim().length > 0 ? message : undefined, |
| 644 | title: args.title, | 885 | title: args.title, |
| @@ -657,7 +898,7 @@ export class SlashCommandHandler { | |||
| 657 | } | 898 | } |
| 658 | deleteQuickReply(args, label) { | 899 | deleteQuickReply(args, label) { |
| 659 | try { | 900 | try { |
| 660 | this.api.deleteQuickReply(args.set, args.label ?? label); | 901 | this.api.deleteQuickReply(args.set, args.id !== undefined ? Number(args.id) : (args.label ?? label)); |
| 661 | } catch (ex) { | 902 | } catch (ex) { |
| 662 | toastr.error(ex.message); | 903 | toastr.error(ex.message); |
| 663 | } | 904 | } |
| @@ -692,9 +933,9 @@ export class SlashCommandHandler { | |||
| 692 | } | 933 | } |
| 693 | 934 | ||
| 694 | 935 | ||
| 695 | createSet(name, args) { | 936 | async createSet(name, args) { |
| 696 | try { | 937 | try { |
| 697 | this.api.createSet( | 938 | await this.api.createSet( |
| 698 | args.name ?? name ?? '', | 939 | args.name ?? name ?? '', |
| 699 | { | 940 | { |
| 700 | disableSend: isTrueBoolean(args.nosend), | 941 | disableSend: isTrueBoolean(args.nosend), |
| @@ -706,9 +947,9 @@ export class SlashCommandHandler { | |||
| 706 | toastr.error(ex.message); | 947 | toastr.error(ex.message); |
| 707 | } | 948 | } |
| 708 | } | 949 | } |
| 709 | updateSet(name, args) { | 950 | async updateSet(name, args) { |
| 710 | try { | 951 | try { |
| 711 | this.api.updateSet( | 952 | await this.api.updateSet( |
| 712 | args.name ?? name ?? '', | 953 | args.name ?? name ?? '', |
| 713 | { | 954 | { |
| 714 | disableSend: args.nosend !== undefined ? isTrueBoolean(args.nosend) : undefined, | 955 | disableSend: args.nosend !== undefined ? isTrueBoolean(args.nosend) : undefined, |
| @@ -720,9 +961,9 @@ export class SlashCommandHandler { | |||
| 720 | toastr.error(ex.message); | 961 | toastr.error(ex.message); |
| 721 | } | 962 | } |
| 722 | } | 963 | } |
| 723 | deleteSet(name) { | 964 | async deleteSet(name) { |
| 724 | try { | 965 | try { |
| 725 | this.api.deleteSet(name ?? ''); | 966 | await this.api.deleteSet(name ?? ''); |
| 726 | } catch (ex) { | 967 | } catch (ex) { |
| 727 | toastr.error(ex.message); | 968 | toastr.error(ex.message); |
| 728 | } | 969 | } |
| @@ -23,6 +23,8 @@ export class SettingsUi { | |||
| 23 | /**@type {HTMLInputElement}*/ disableSend; | 23 | /**@type {HTMLInputElement}*/ disableSend; |
| 24 | /**@type {HTMLInputElement}*/ placeBeforeInput; | 24 | /**@type {HTMLInputElement}*/ placeBeforeInput; |
| 25 | /**@type {HTMLInputElement}*/ injectInput; | 25 | /**@type {HTMLInputElement}*/ injectInput; |
| 26 | /**@type {HTMLInputElement}*/ color; | ||
| 27 | /**@type {HTMLInputElement}*/ onlyBorderColor; | ||
| 26 | /**@type {HTMLSelectElement}*/ currentSet; | 28 | /**@type {HTMLSelectElement}*/ currentSet; |
| 27 | 29 | ||
| 28 | 30 | ||
| @@ -117,10 +119,29 @@ export class SettingsUi { | |||
| 117 | this.dom.querySelector('#qr--set-add').addEventListener('click', async()=>{ | 119 | this.dom.querySelector('#qr--set-add').addEventListener('click', async()=>{ |
| 118 | this.currentQrSet.addQuickReply(); | 120 | this.currentQrSet.addQuickReply(); |
| 119 | }); | 121 | }); |
| 122 | this.dom.querySelector('#qr--set-paste').addEventListener('click', async()=>{ | ||
| 123 | const text = await navigator.clipboard.readText(); | ||
| 124 | this.currentQrSet.addQuickReplyFromText(text); | ||
| 125 | }); | ||
| 126 | this.dom.querySelector('#qr--set-importQr').addEventListener('click', async()=>{ | ||
| 127 | const inp = document.createElement('input'); { | ||
| 128 | inp.type = 'file'; | ||
| 129 | inp.accept = '.json'; | ||
| 130 | inp.addEventListener('change', async()=>{ | ||
| 131 | if (inp.files.length > 0) { | ||
| 132 | for (const file of inp.files) { | ||
| 133 | const text = await file.text(); | ||
| 134 | this.currentQrSet.addQuickReply(JSON.parse(text)); | ||
| 135 | } | ||
| 136 | } | ||
| 137 | }); | ||
| 138 | inp.click(); | ||
| 139 | } | ||
| 140 | }); | ||
| 120 | this.qrList = this.dom.querySelector('#qr--set-qrList'); | 141 | this.qrList = this.dom.querySelector('#qr--set-qrList'); |
| 121 | this.currentSet = this.dom.querySelector('#qr--set'); | 142 | this.currentSet = this.dom.querySelector('#qr--set'); |
| 122 | this.currentSet.addEventListener('change', ()=>this.onQrSetChange()); | 143 | this.currentSet.addEventListener('change', ()=>this.onQrSetChange()); |
| 123 | QuickReplySet.list.forEach(qrs=>{ | 144 | QuickReplySet.list.toSorted((a,b)=>a.name.toLowerCase().localeCompare(b.name.toLowerCase())).forEach(qrs=>{ |
| 124 | const opt = document.createElement('option'); { | 145 | const opt = document.createElement('option'); { |
| 125 | opt.value = qrs.name; | 146 | opt.value = qrs.name; |
| 126 | opt.textContent = qrs.name; | 147 | opt.textContent = qrs.name; |
| @@ -145,6 +166,34 @@ export class SettingsUi { | |||
| 145 | qrs.injectInput = this.injectInput.checked; | 166 | qrs.injectInput = this.injectInput.checked; |
| 146 | qrs.save(); | 167 | qrs.save(); |
| 147 | }); | 168 | }); |
| 169 | let initialColorChange = true; | ||
| 170 | this.color = this.dom.querySelector('#qr--color'); | ||
| 171 | this.color.color = this.currentQrSet?.color ?? 'transparent'; | ||
| 172 | this.color.addEventListener('change', (evt)=>{ | ||
| 173 | if (!this.dom.closest('body')) return; | ||
| 174 | const qrs = this.currentQrSet; | ||
| 175 | if (initialColorChange) { | ||
| 176 | initialColorChange = false; | ||
| 177 | this.color.color = qrs.color; | ||
| 178 | return; | ||
| 179 | } | ||
| 180 | qrs.color = evt.detail.rgb; | ||
| 181 | qrs.save(); | ||
| 182 | this.currentQrSet.updateColor(); | ||
| 183 | }); | ||
| 184 | this.dom.querySelector('#qr--colorClear').addEventListener('click', (evt)=>{ | ||
| 185 | const qrs = this.currentQrSet; | ||
| 186 | this.color.color = 'transparent'; | ||
| 187 | qrs.save(); | ||
| 188 | this.currentQrSet.updateColor(); | ||
| 189 | }); | ||
| 190 | this.onlyBorderColor = this.dom.querySelector('#qr--onlyBorderColor'); | ||
| 191 | this.onlyBorderColor.addEventListener('click', ()=>{ | ||
| 192 | const qrs = this.currentQrSet; | ||
| 193 | qrs.onlyBorderColor = this.onlyBorderColor.checked; | ||
| 194 | qrs.save(); | ||
| 195 | this.currentQrSet.updateColor(); | ||
| 196 | }); | ||
| 148 | this.onQrSetChange(); | 197 | this.onQrSetChange(); |
| 149 | } | 198 | } |
| 150 | onQrSetChange() { | 199 | onQrSetChange() { |
| @@ -152,6 +201,8 @@ export class SettingsUi { | |||
| 152 | this.disableSend.checked = this.currentQrSet.disableSend; | 201 | this.disableSend.checked = this.currentQrSet.disableSend; |
| 153 | this.placeBeforeInput.checked = this.currentQrSet.placeBeforeInput; | 202 | this.placeBeforeInput.checked = this.currentQrSet.placeBeforeInput; |
| 154 | this.injectInput.checked = this.currentQrSet.injectInput; | 203 | this.injectInput.checked = this.currentQrSet.injectInput; |
| 204 | this.color.color = this.currentQrSet.color ?? 'transparent'; | ||
| 205 | this.onlyBorderColor.checked = this.currentQrSet.onlyBorderColor; | ||
| 155 | this.qrList.innerHTML = ''; | 206 | this.qrList.innerHTML = ''; |
| 156 | const qrsDom = this.currentQrSet.renderSettings(); | 207 | const qrsDom = this.currentQrSet.renderSettings(); |
| 157 | this.qrList.append(qrsDom); | 208 | this.qrList.append(qrsDom); |
| @@ -265,7 +316,7 @@ export class SettingsUi { | |||
| 265 | const qrs = new QuickReplySet(); | 316 | const qrs = new QuickReplySet(); |
| 266 | qrs.name = name; | 317 | qrs.name = name; |
| 267 | qrs.addQuickReply(); | 318 | qrs.addQuickReply(); |
| 268 | const idx = QuickReplySet.list.findIndex(it=>it.name.localeCompare(name) == 1); | 319 | const idx = QuickReplySet.list.findIndex(it=>it.name.toLowerCase().localeCompare(name.toLowerCase()) == 1); |
| 269 | if (idx > -1) { | 320 | if (idx > -1) { |
| 270 | QuickReplySet.list.splice(idx, 0, qrs); | 321 | QuickReplySet.list.splice(idx, 0, qrs); |
| 271 | } else { | 322 | } else { |
| @@ -321,7 +372,7 @@ export class SettingsUi { | |||
| 321 | this.prepareChatSetList(); | 372 | this.prepareChatSetList(); |
| 322 | } | 373 | } |
| 323 | } else { | 374 | } else { |
| 324 | const idx = QuickReplySet.list.findIndex(it=>it.name.localeCompare(qrs.name) == 1); | 375 | const idx = QuickReplySet.list.findIndex(it=>it.name.toLowerCase().localeCompare(qrs.name.toLowerCase()) == 1); |
| 325 | if (idx > -1) { | 376 | if (idx > -1) { |
| 326 | QuickReplySet.list.splice(idx, 0, qrs); | 377 | QuickReplySet.list.splice(idx, 0, qrs); |
| 327 | } else { | 378 | } else { |
| @@ -33,11 +33,14 @@ export class ContextMenu { | |||
| 33 | */ | 33 | */ |
| 34 | build(qr, chainedMessage = null, hierarchy = [], labelHierarchy = []) { | 34 | build(qr, chainedMessage = null, hierarchy = [], labelHierarchy = []) { |
| 35 | const tree = { | 35 | const tree = { |
| 36 | icon: qr.icon, | ||
| 37 | showLabel: qr.showLabel, | ||
| 36 | label: qr.label, | 38 | label: qr.label, |
| 37 | message: (chainedMessage && qr.message ? `${chainedMessage} | ` : '') + qr.message, | 39 | message: (chainedMessage && qr.message ? `${chainedMessage} | ` : '') + qr.message, |
| 38 | children: [], | 40 | children: [], |
| 39 | }; | 41 | }; |
| 40 | qr.contextList.forEach((cl) => { | 42 | qr.contextList.forEach((cl) => { |
| 43 | if (!cl.set) return; | ||
| 41 | if (!hierarchy.includes(cl.set)) { | 44 | if (!hierarchy.includes(cl.set)) { |
| 42 | const nextHierarchy = [...hierarchy, cl.set]; | 45 | const nextHierarchy = [...hierarchy, cl.set]; |
| 43 | const nextLabelHierarchy = [...labelHierarchy, tree.label]; | 46 | const nextLabelHierarchy = [...labelHierarchy, tree.label]; |
| @@ -45,6 +48,8 @@ export class ContextMenu { | |||
| 45 | cl.set.qrList.forEach(subQr => { | 48 | cl.set.qrList.forEach(subQr => { |
| 46 | const subTree = this.build(subQr, cl.isChained ? tree.message : null, nextHierarchy, nextLabelHierarchy); | 49 | const subTree = this.build(subQr, cl.isChained ? tree.message : null, nextHierarchy, nextLabelHierarchy); |
| 47 | tree.children.push(new MenuItem( | 50 | tree.children.push(new MenuItem( |
| 51 | subTree.icon, | ||
| 52 | subTree.showLabel, | ||
| 48 | subTree.label, | 53 | subTree.label, |
| 49 | subTree.message, | 54 | subTree.message, |
| 50 | (evt) => { | 55 | (evt) => { |
| @@ -2,7 +2,7 @@ import { MenuItem } from './MenuItem.js'; | |||
| 2 | 2 | ||
| 3 | export class MenuHeader extends MenuItem { | 3 | export class MenuHeader extends MenuItem { |
| 4 | constructor(/**@type {String}*/label) { | 4 | constructor(/**@type {String}*/label) { |
| 5 | super(label, null, null); | 5 | super(null, null, label, null, null); |
| 6 | } | 6 | } |
| 7 | 7 | ||
| 8 | 8 | ||
| @@ -1,21 +1,34 @@ | |||
| 1 | import { SubMenu } from './SubMenu.js'; | 1 | import { SubMenu } from './SubMenu.js'; |
| 2 | 2 | ||
| 3 | export class MenuItem { | 3 | export class MenuItem { |
| 4 | /**@type {String}*/ label; | 4 | /**@type {string}*/ icon; |
| 5 | /**@type {Object}*/ value; | 5 | /**@type {boolean}*/ showLabel; |
| 6 | /**@type {Function}*/ callback; | 6 | /**@type {string}*/ label; |
| 7 | /**@type {object}*/ value; | ||
| 8 | /**@type {function}*/ callback; | ||
| 7 | /**@type {MenuItem[]}*/ childList = []; | 9 | /**@type {MenuItem[]}*/ childList = []; |
| 8 | /**@type {SubMenu}*/ subMenu; | 10 | /**@type {SubMenu}*/ subMenu; |
| 9 | /**@type {Boolean}*/ isForceExpanded = false; | 11 | /**@type {boolean}*/ isForceExpanded = false; |
| 10 | 12 | ||
| 11 | /**@type {HTMLElement}*/ root; | 13 | /**@type {HTMLElement}*/ root; |
| 12 | 14 | ||
| 13 | /**@type {Function}*/ onExpand; | 15 | /**@type {function}*/ onExpand; |
| 14 | 16 | ||
| 15 | 17 | ||
| 16 | 18 | ||
| 17 | 19 | ||
| 18 | constructor(/**@type {String}*/label, /**@type {Object}*/value, /**@type {function}*/callback, /**@type {MenuItem[]}*/children = []) { | 20 | /** |
| 21 | * | ||
| 22 | * @param {string} icon | ||
| 23 | * @param {boolean} showLabel | ||
| 24 | * @param {string} label | ||
| 25 | * @param {object} value | ||
| 26 | * @param {function} callback | ||
| 27 | * @param {MenuItem[]} children | ||
| 28 | */ | ||
| 29 | constructor(icon, showLabel, label, value, callback, children = []) { | ||
| 30 | this.icon = icon; | ||
| 31 | this.showLabel = showLabel; | ||
| 19 | this.label = label; | 32 | this.label = label; |
| 20 | this.value = value; | 33 | this.value = value; |
| 21 | this.callback = callback; | 34 | this.callback = callback; |
| @@ -33,7 +46,21 @@ export class MenuItem { | |||
| 33 | if (this.callback) { | 46 | if (this.callback) { |
| 34 | item.addEventListener('click', (evt) => this.callback(evt, this)); | 47 | item.addEventListener('click', (evt) => this.callback(evt, this)); |
| 35 | } | 48 | } |
| 36 | item.append(this.label); | 49 | const icon = document.createElement('div'); { |
| 50 | this.domIcon = icon; | ||
| 51 | icon.classList.add('qr--button-icon'); | ||
| 52 | icon.classList.add('fa-solid'); | ||
| 53 | if (!this.icon) icon.classList.add('qr--hidden'); | ||
| 54 | else icon.classList.add(this.icon); | ||
| 55 | item.append(icon); | ||
| 56 | } | ||
| 57 | const lbl = document.createElement('div'); { | ||
| 58 | this.domLabel = lbl; | ||
| 59 | lbl.classList.add('qr--button-label'); | ||
| 60 | if (this.icon && !this.showLabel) lbl.classList.add('qr--hidden'); | ||
| 61 | lbl.textContent = this.label; | ||
| 62 | item.append(lbl); | ||
| 63 | } | ||
| 37 | if (this.childList.length > 0) { | 64 | if (this.childList.length > 0) { |
| 38 | item.classList.add('ctx-has-children'); | 65 | item.classList.add('ctx-has-children'); |
| 39 | const sub = new SubMenu(this.childList); | 66 | const sub = new SubMenu(this.childList); |
| @@ -1,3 +1,20 @@ | |||
| 1 | @keyframes qr--success { | ||
| 2 | 0%, | ||
| 3 | 100% { | ||
| 4 | color: var(--SmartThemeBodyColor); | ||
| 5 | } | ||
| 6 | 25%, | ||
| 7 | 75% { | ||
| 8 | color: #51a351; | ||
| 9 | } | ||
| 10 | } | ||
| 11 | .qr--success { | ||
| 12 | animation-name: qr--success; | ||
| 13 | animation-duration: 3s; | ||
| 14 | animation-timing-function: linear; | ||
| 15 | animation-delay: 0s; | ||
| 16 | animation-iteration-count: 1; | ||
| 17 | } | ||
| 1 | #qr--bar { | 18 | #qr--bar { |
| 2 | outline: none; | 19 | outline: none; |
| 3 | margin: 0; | 20 | margin: 0; |
| @@ -41,6 +58,7 @@ | |||
| 41 | } | 58 | } |
| 42 | #qr--bar > .qr--buttons, | 59 | #qr--bar > .qr--buttons, |
| 43 | #qr--popout > .qr--body > .qr--buttons { | 60 | #qr--popout > .qr--body > .qr--buttons { |
| 61 | --qr--color: transparent; | ||
| 44 | margin: 0; | 62 | margin: 0; |
| 45 | padding: 0; | 63 | padding: 0; |
| 46 | display: flex; | 64 | display: flex; |
| @@ -49,10 +67,44 @@ | |||
| 49 | gap: 5px; | 67 | gap: 5px; |
| 50 | width: 100%; | 68 | width: 100%; |
| 51 | } | 69 | } |
| 70 | #qr--bar > .qr--buttons.qr--color, | ||
| 71 | #qr--popout > .qr--body > .qr--buttons.qr--color { | ||
| 72 | background-color: var(--qr--color); | ||
| 73 | } | ||
| 74 | #qr--bar > .qr--buttons.qr--borderColor, | ||
| 75 | #qr--popout > .qr--body > .qr--buttons.qr--borderColor { | ||
| 76 | background-color: transparent; | ||
| 77 | border-left: 5px solid var(--qr--color); | ||
| 78 | border-right: 5px solid var(--qr--color); | ||
| 79 | } | ||
| 80 | #qr--bar > .qr--buttons:has(.qr--buttons.qr--color), | ||
| 81 | #qr--popout > .qr--body > .qr--buttons:has(.qr--buttons.qr--color) { | ||
| 82 | margin: 5px; | ||
| 83 | } | ||
| 52 | #qr--bar > .qr--buttons > .qr--buttons, | 84 | #qr--bar > .qr--buttons > .qr--buttons, |
| 53 | #qr--popout > .qr--body > .qr--buttons > .qr--buttons { | 85 | #qr--popout > .qr--body > .qr--buttons > .qr--buttons { |
| 54 | display: contents; | 86 | display: contents; |
| 55 | } | 87 | } |
| 88 | #qr--bar > .qr--buttons > .qr--buttons.qr--color .qr--button:before, | ||
| 89 | #qr--popout > .qr--body > .qr--buttons > .qr--buttons.qr--color .qr--button:before { | ||
| 90 | content: ''; | ||
| 91 | background-color: var(--qr--color); | ||
| 92 | position: absolute; | ||
| 93 | inset: -5px; | ||
| 94 | z-index: -1; | ||
| 95 | } | ||
| 96 | #qr--bar > .qr--buttons > .qr--buttons.qr--color.qr--borderColor .qr--button:before, | ||
| 97 | #qr--popout > .qr--body > .qr--buttons > .qr--buttons.qr--color.qr--borderColor .qr--button:before { | ||
| 98 | display: none; | ||
| 99 | } | ||
| 100 | #qr--bar > .qr--buttons > .qr--buttons.qr--color.qr--borderColor:before, | ||
| 101 | #qr--popout > .qr--body > .qr--buttons > .qr--buttons.qr--color.qr--borderColor:before, | ||
| 102 | #qr--bar > .qr--buttons > .qr--buttons.qr--color.qr--borderColor:after, | ||
| 103 | #qr--popout > .qr--body > .qr--buttons > .qr--buttons.qr--color.qr--borderColor:after { | ||
| 104 | content: ''; | ||
| 105 | width: 5px; | ||
| 106 | background-color: var(--qr--color); | ||
| 107 | } | ||
| 56 | #qr--bar > .qr--buttons .qr--button, | 108 | #qr--bar > .qr--buttons .qr--button, |
| 57 | #qr--popout > .qr--body > .qr--buttons .qr--button { | 109 | #qr--popout > .qr--body > .qr--buttons .qr--button { |
| 58 | color: var(--SmartThemeBodyColor); | 110 | color: var(--SmartThemeBodyColor); |
| @@ -66,11 +118,19 @@ | |||
| 66 | align-items: center; | 118 | align-items: center; |
| 67 | justify-content: center; | 119 | justify-content: center; |
| 68 | text-align: center; | 120 | text-align: center; |
| 121 | position: relative; | ||
| 69 | } | 122 | } |
| 70 | #qr--bar > .qr--buttons .qr--button:hover, | 123 | #qr--bar > .qr--buttons .qr--button:hover, |
| 71 | #qr--popout > .qr--body > .qr--buttons .qr--button:hover { | 124 | #qr--popout > .qr--body > .qr--buttons .qr--button:hover { |
| 72 | opacity: 1; | 125 | background-color: #4d4d4d; |
| 73 | filter: brightness(1.2); | 126 | } |
| 127 | #qr--bar > .qr--buttons .qr--button .qr--hidden, | ||
| 128 | #qr--popout > .qr--body > .qr--buttons .qr--button .qr--hidden { | ||
| 129 | display: none; | ||
| 130 | } | ||
| 131 | #qr--bar > .qr--buttons .qr--button .qr--button-icon, | ||
| 132 | #qr--popout > .qr--body > .qr--buttons .qr--button .qr--button-icon { | ||
| 133 | margin: 0 0.5em; | ||
| 74 | } | 134 | } |
| 75 | #qr--bar > .qr--buttons .qr--button > .qr--button-expander, | 135 | #qr--bar > .qr--buttons .qr--button > .qr--button-expander, |
| 76 | #qr--popout > .qr--body > .qr--buttons .qr--button > .qr--button-expander { | 136 | #qr--popout > .qr--body > .qr--buttons .qr--button > .qr--button-expander { |
| @@ -170,36 +230,80 @@ | |||
| 170 | #qr--settings #qr--set-qrList .qr--set-qrListContents { | 230 | #qr--settings #qr--set-qrList .qr--set-qrListContents { |
| 171 | padding: 0 0.5em; | 231 | padding: 0 0.5em; |
| 172 | } | 232 | } |
| 173 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item { | 233 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--set-itemAdder { |
| 234 | display: flex; | ||
| 235 | align-items: center; | ||
| 236 | opacity: 0; | ||
| 237 | transition: 100ms; | ||
| 238 | margin: -2px 0 -11px 0; | ||
| 239 | position: relative; | ||
| 240 | } | ||
| 241 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--set-itemAdder .qr--actions { | ||
| 242 | display: flex; | ||
| 243 | gap: 0.25em; | ||
| 244 | flex: 0 0 auto; | ||
| 245 | } | ||
| 246 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--set-itemAdder .qr--actions .qr--action { | ||
| 247 | margin: 0; | ||
| 248 | } | ||
| 249 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--set-itemAdder:before, | ||
| 250 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--set-itemAdder:after { | ||
| 251 | content: ""; | ||
| 252 | display: block; | ||
| 253 | flex: 1 1 auto; | ||
| 254 | border: 1px solid; | ||
| 255 | margin: 0 1em; | ||
| 256 | height: 0; | ||
| 257 | } | ||
| 258 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--set-itemAdder:hover, | ||
| 259 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--set-itemAdder:focus-within { | ||
| 260 | opacity: 1; | ||
| 261 | } | ||
| 262 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--content { | ||
| 174 | display: flex; | 263 | display: flex; |
| 175 | flex-direction: row; | 264 | flex-direction: row; |
| 176 | gap: 0.5em; | 265 | gap: 0.5em; |
| 177 | align-items: baseline; | 266 | align-items: baseline; |
| 178 | padding: 0.25em 0; | 267 | padding: 0.25em 0; |
| 179 | } | 268 | } |
| 180 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item > :nth-child(1) { | 269 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--content > :nth-child(2) { |
| 181 | flex: 0 0 auto; | 270 | flex: 0 0 auto; |
| 182 | } | 271 | } |
| 183 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item > :nth-child(2) { | 272 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--content > :nth-child(2) { |
| 184 | flex: 1 1 25%; | 273 | flex: 1 1 25%; |
| 185 | } | 274 | } |
| 186 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item > :nth-child(3) { | 275 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--content > :nth-child(3) { |
| 187 | flex: 0 0 auto; | 276 | flex: 0 0 auto; |
| 188 | } | 277 | } |
| 189 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item > :nth-child(4) { | 278 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--content > :nth-child(4) { |
| 190 | flex: 1 1 75%; | 279 | flex: 1 1 75%; |
| 191 | } | 280 | } |
| 192 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item > :nth-child(5) { | 281 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--content > :nth-child(5) { |
| 193 | flex: 0 0 auto; | 282 | flex: 0 1 auto; |
| 283 | display: flex; | ||
| 284 | gap: 0.25em; | ||
| 285 | justify-content: flex-end; | ||
| 286 | flex-wrap: wrap; | ||
| 194 | } | 287 | } |
| 195 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item > .drag-handle { | 288 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--content > .drag-handle { |
| 196 | padding: 0.75em; | 289 | padding: 0.75em; |
| 197 | } | 290 | } |
| 198 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--set-itemLabel, | 291 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--content .qr--set-itemLabelContainer { |
| 199 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--action { | 292 | display: flex; |
| 293 | align-items: center; | ||
| 294 | gap: 0.5em; | ||
| 295 | } | ||
| 296 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--content .qr--set-itemLabelContainer .qr--set-itemIcon:not(.fa-solid) { | ||
| 297 | display: none; | ||
| 298 | } | ||
| 299 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--content .qr--set-itemLabelContainer .qr--set-itemLabel { | ||
| 300 | min-width: 24px; | ||
| 301 | } | ||
| 302 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--content .qr--set-itemLabel, | ||
| 303 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--content .qr--action { | ||
| 200 | margin: 0; | 304 | margin: 0; |
| 201 | } | 305 | } |
| 202 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--set-itemMessage { | 306 | #qr--settings #qr--set-qrList .qr--set-qrListContents > .qr--set-item .qr--content .qr--set-itemMessage { |
| 203 | font-size: smaller; | 307 | font-size: smaller; |
| 204 | } | 308 | } |
| 205 | #qr--settings .qr--set-qrListActions { | 309 | #qr--settings .qr--set-qrListActions { |
| @@ -212,6 +316,7 @@ | |||
| 212 | #qr--qrOptions { | 316 | #qr--qrOptions { |
| 213 | display: flex; | 317 | display: flex; |
| 214 | flex-direction: column; | 318 | flex-direction: column; |
| 319 | padding-right: 1px; | ||
| 215 | } | 320 | } |
| 216 | #qr--qrOptions > #qr--ctxEditor .qr--ctxItem { | 321 | #qr--qrOptions > #qr--ctxEditor .qr--ctxItem { |
| 217 | display: flex; | 322 | display: flex; |
| @@ -219,6 +324,12 @@ | |||
| 219 | gap: 0.5em; | 324 | gap: 0.5em; |
| 220 | align-items: baseline; | 325 | align-items: baseline; |
| 221 | } | 326 | } |
| 327 | #qr--qrOptions > #qr--autoExec .checkbox_label { | ||
| 328 | text-wrap: nowrap; | ||
| 329 | } | ||
| 330 | #qr--qrOptions > #qr--autoExec .checkbox_label .fa-fw { | ||
| 331 | margin-right: 2px; | ||
| 332 | } | ||
| 222 | @media screen and (max-width: 750px) { | 333 | @media screen and (max-width: 750px) { |
| 223 | body .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor { | 334 | body .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor { |
| 224 | flex-direction: column; | 335 | flex-direction: column; |
| @@ -238,6 +349,72 @@ | |||
| 238 | .popup:has(#qr--modalEditor) { | 349 | .popup:has(#qr--modalEditor) { |
| 239 | aspect-ratio: unset; | 350 | aspect-ratio: unset; |
| 240 | } | 351 | } |
| 352 | .popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized) { | ||
| 353 | min-width: unset; | ||
| 354 | min-height: unset; | ||
| 355 | height: auto !important; | ||
| 356 | width: min-content !important; | ||
| 357 | position: absolute; | ||
| 358 | right: 1em; | ||
| 359 | top: 1em; | ||
| 360 | left: unset; | ||
| 361 | bottom: unset; | ||
| 362 | margin: unset; | ||
| 363 | padding: 0; | ||
| 364 | } | ||
| 365 | .popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized)::backdrop { | ||
| 366 | backdrop-filter: unset; | ||
| 367 | background-color: transparent; | ||
| 368 | } | ||
| 369 | .popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized) .popup-body { | ||
| 370 | flex: 0 0 auto; | ||
| 371 | height: min-content; | ||
| 372 | width: min-content; | ||
| 373 | } | ||
| 374 | .popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized) .popup-content { | ||
| 375 | flex: 0 0 auto; | ||
| 376 | margin-top: 0; | ||
| 377 | } | ||
| 378 | .popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized) .popup-content > #qr--modalEditor { | ||
| 379 | max-height: 50vh; | ||
| 380 | } | ||
| 381 | .popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized) .popup-content > #qr--modalEditor > #qr--main, | ||
| 382 | .popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized) .popup-content > #qr--modalEditor > #qr--resizeHandle, | ||
| 383 | .popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized) .popup-content > #qr--modalEditor > #qr--qrOptions > h3, | ||
| 384 | .popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized) .popup-content > #qr--modalEditor > #qr--qrOptions > #qr--modal-executeButtons, | ||
| 385 | .popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized) .popup-content > #qr--modalEditor > #qr--qrOptions > #qr--modal-executeProgress { | ||
| 386 | display: none; | ||
| 387 | } | ||
| 388 | .popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized) .popup-content > #qr--modalEditor #qr--qrOptions { | ||
| 389 | width: auto; | ||
| 390 | } | ||
| 391 | .popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized) .popup-content > #qr--modalEditor #qr--modal-debugButtons .qr--modal-debugButton#qr--modal-maximize { | ||
| 392 | display: flex; | ||
| 393 | } | ||
| 394 | .popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized) .popup-content > #qr--modalEditor #qr--modal-debugButtons .qr--modal-debugButton#qr--modal-minimize { | ||
| 395 | display: none; | ||
| 396 | } | ||
| 397 | .popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized) .popup-content > #qr--modalEditor #qr--modal-debugState { | ||
| 398 | padding-top: 0; | ||
| 399 | } | ||
| 400 | .popup:has(#qr--modalEditor):has(.qr--isExecuting) .popup-controls { | ||
| 401 | display: none; | ||
| 402 | } | ||
| 403 | .popup:has(#qr--modalEditor):has(.qr--isExecuting) .qr--highlight { | ||
| 404 | position: absolute; | ||
| 405 | z-index: 50000; | ||
| 406 | pointer-events: none; | ||
| 407 | background-color: rgba(47, 150, 180, 0.5); | ||
| 408 | } | ||
| 409 | .popup:has(#qr--modalEditor):has(.qr--isExecuting) .qr--highlight.qr--unresolved { | ||
| 410 | background-color: rgba(255, 255, 0, 0.5); | ||
| 411 | } | ||
| 412 | .popup:has(#qr--modalEditor):has(.qr--isExecuting) .qr--highlight-secondary { | ||
| 413 | position: absolute; | ||
| 414 | z-index: 50000; | ||
| 415 | pointer-events: none; | ||
| 416 | border: 3px solid red; | ||
| 417 | } | ||
| 241 | .popup:has(#qr--modalEditor) .popup-content { | 418 | .popup:has(#qr--modalEditor) .popup-content { |
| 242 | display: flex; | 419 | display: flex; |
| 243 | flex-direction: column; | 420 | flex-direction: column; |
| @@ -249,6 +426,67 @@ | |||
| 249 | gap: 1em; | 426 | gap: 1em; |
| 250 | overflow: hidden; | 427 | overflow: hidden; |
| 251 | } | 428 | } |
| 429 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--main > h3:first-child, | ||
| 430 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--main > .qr--labels, | ||
| 431 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--main > .qr--modal-messageContainer > .qr--modal-editorSettings, | ||
| 432 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--qrOptions > h3:first-child, | ||
| 433 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--qrOptions > #qr--ctxEditor, | ||
| 434 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--qrOptions > .qr--ctxEditorActions, | ||
| 435 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--qrOptions > .qr--ctxEditorActions + h3, | ||
| 436 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--qrOptions > .qr--ctxEditorActions + h3 + div { | ||
| 437 | display: none; | ||
| 438 | } | ||
| 439 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder > #qr--modal-message { | ||
| 440 | visibility: hidden; | ||
| 441 | } | ||
| 442 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--modal-debugButtons { | ||
| 443 | display: flex; | ||
| 444 | } | ||
| 445 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--modal-debugButtons .menu_button:not(#qr--modal-minimize, #qr--modal-maximize) { | ||
| 446 | cursor: not-allowed; | ||
| 447 | opacity: 0.5; | ||
| 448 | pointer-events: none; | ||
| 449 | transition: 200ms; | ||
| 450 | border-color: transparent; | ||
| 451 | } | ||
| 452 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting.qr--isPaused #qr--modal-debugButtons .menu_button:not(#qr--modal-minimize, #qr--modal-maximize) { | ||
| 453 | cursor: pointer; | ||
| 454 | opacity: 1; | ||
| 455 | pointer-events: all; | ||
| 456 | } | ||
| 457 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting.qr--isPaused #qr--modal-debugButtons .menu_button:not(#qr--modal-minimize, #qr--modal-maximize)#qr--modal-resume { | ||
| 458 | animation-name: qr--debugPulse; | ||
| 459 | animation-duration: 1500ms; | ||
| 460 | animation-timing-function: ease-in-out; | ||
| 461 | animation-delay: 0s; | ||
| 462 | animation-iteration-count: infinite; | ||
| 463 | } | ||
| 464 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting.qr--isPaused #qr--modal-debugButtons .menu_button:not(#qr--modal-minimize, #qr--modal-maximize)#qr--modal-resume { | ||
| 465 | border-color: #51a351; | ||
| 466 | } | ||
| 467 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting.qr--isPaused #qr--modal-debugButtons .menu_button:not(#qr--modal-minimize, #qr--modal-maximize)#qr--modal-step { | ||
| 468 | border-color: var(--SmartThemeQuoteColor); | ||
| 469 | } | ||
| 470 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting.qr--isPaused #qr--modal-debugButtons .menu_button:not(#qr--modal-minimize, #qr--modal-maximize)#qr--modal-stepInto { | ||
| 471 | border-color: var(--SmartThemeQuoteColor); | ||
| 472 | } | ||
| 473 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting.qr--isPaused #qr--modal-debugButtons .menu_button:not(#qr--modal-minimize, #qr--modal-maximize)#qr--modal-stepOut { | ||
| 474 | border-color: var(--SmartThemeQuoteColor); | ||
| 475 | } | ||
| 476 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--resizeHandle { | ||
| 477 | width: 6px; | ||
| 478 | background-color: var(--SmartThemeBorderColor); | ||
| 479 | border: 2px solid var(--SmartThemeBlurTintColor); | ||
| 480 | transition: border-color 200ms, background-color 200ms; | ||
| 481 | cursor: w-resize; | ||
| 482 | } | ||
| 483 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--resizeHandle:hover { | ||
| 484 | background-color: var(--SmartThemeQuoteColor); | ||
| 485 | border-color: var(--SmartThemeQuoteColor); | ||
| 486 | } | ||
| 487 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--qrOptions { | ||
| 488 | width: var(--width, auto); | ||
| 489 | } | ||
| 252 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main { | 490 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main { |
| 253 | flex: 1 1 auto; | 491 | flex: 1 1 auto; |
| 254 | display: flex; | 492 | display: flex; |
| @@ -260,21 +498,115 @@ | |||
| 260 | display: flex; | 498 | display: flex; |
| 261 | flex-direction: row; | 499 | flex-direction: row; |
| 262 | gap: 0.5em; | 500 | gap: 0.5em; |
| 501 | padding: 1px; | ||
| 263 | } | 502 | } |
| 264 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label { | 503 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label, |
| 504 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label { | ||
| 265 | flex: 1 1 1px; | 505 | flex: 1 1 1px; |
| 266 | display: flex; | 506 | display: flex; |
| 267 | flex-direction: column; | 507 | flex-direction: column; |
| 508 | position: relative; | ||
| 509 | } | ||
| 510 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label.qr--fit, | ||
| 511 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label.qr--fit { | ||
| 512 | flex: 0 0 auto; | ||
| 513 | justify-content: center; | ||
| 514 | } | ||
| 515 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--inputGroup, | ||
| 516 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--inputGroup { | ||
| 517 | display: flex; | ||
| 518 | align-items: baseline; | ||
| 519 | gap: 0.5em; | ||
| 520 | } | ||
| 521 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--inputGroup input, | ||
| 522 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--inputGroup input { | ||
| 523 | flex: 1 1 auto; | ||
| 268 | } | 524 | } |
| 269 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label > .qr--labelText { | 525 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--labelText, |
| 526 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--labelText { | ||
| 270 | flex: 1 1 auto; | 527 | flex: 1 1 auto; |
| 271 | } | 528 | } |
| 272 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label > .qr--labelHint { | 529 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--labelHint, |
| 530 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--labelHint { | ||
| 273 | flex: 1 1 auto; | 531 | flex: 1 1 auto; |
| 274 | } | 532 | } |
| 275 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label > input { | 533 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label input, |
| 534 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label input { | ||
| 276 | flex: 0 0 auto; | 535 | flex: 0 0 auto; |
| 277 | } | 536 | } |
| 537 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--modal-switcherList, | ||
| 538 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--modal-switcherList { | ||
| 539 | background-color: var(--stcdx--bgColor); | ||
| 540 | border: 1px solid var(--SmartThemeBorderColor); | ||
| 541 | backdrop-filter: blur(var(--SmartThemeBlurStrength)); | ||
| 542 | border-radius: 10px; | ||
| 543 | font-size: smaller; | ||
| 544 | position: absolute; | ||
| 545 | top: 100%; | ||
| 546 | left: 0; | ||
| 547 | right: 0; | ||
| 548 | overflow: auto; | ||
| 549 | margin: 0; | ||
| 550 | padding: 0.5em; | ||
| 551 | max-height: 50vh; | ||
| 552 | list-style: none; | ||
| 553 | z-index: 40000; | ||
| 554 | max-width: 100%; | ||
| 555 | } | ||
| 556 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--modal-switcherList .qr--modal-switcherItem, | ||
| 557 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--modal-switcherList .qr--modal-switcherItem { | ||
| 558 | display: flex; | ||
| 559 | gap: 1em; | ||
| 560 | text-align: left; | ||
| 561 | opacity: 0.75; | ||
| 562 | transition: 200ms; | ||
| 563 | cursor: pointer; | ||
| 564 | } | ||
| 565 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--modal-switcherList .qr--modal-switcherItem:hover, | ||
| 566 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--modal-switcherList .qr--modal-switcherItem:hover { | ||
| 567 | opacity: 1; | ||
| 568 | } | ||
| 569 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--modal-switcherList .qr--modal-switcherItem.qr--current, | ||
| 570 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--modal-switcherList .qr--modal-switcherItem.qr--current { | ||
| 571 | opacity: 1; | ||
| 572 | } | ||
| 573 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--modal-switcherList .qr--modal-switcherItem.qr--current .qr--label, | ||
| 574 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--modal-switcherList .qr--modal-switcherItem.qr--current .qr--label, | ||
| 575 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--modal-switcherList .qr--modal-switcherItem.qr--current .qr--id, | ||
| 576 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--modal-switcherList .qr--modal-switcherItem.qr--current .qr--id { | ||
| 577 | font-weight: bold; | ||
| 578 | } | ||
| 579 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--modal-switcherList .qr--label, | ||
| 580 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--modal-switcherList .qr--label { | ||
| 581 | white-space: nowrap; | ||
| 582 | } | ||
| 583 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--modal-switcherList .qr--label .menu_button, | ||
| 584 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--modal-switcherList .qr--label .menu_button { | ||
| 585 | display: inline-block; | ||
| 586 | height: min-content; | ||
| 587 | width: min-content; | ||
| 588 | margin: 0 0.5em 0 0; | ||
| 589 | } | ||
| 590 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--modal-switcherList .qr--id, | ||
| 591 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--modal-switcherList .qr--id { | ||
| 592 | opacity: 0.5; | ||
| 593 | } | ||
| 594 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--modal-switcherList .qr--id:before, | ||
| 595 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--modal-switcherList .qr--id:before { | ||
| 596 | content: "["; | ||
| 597 | } | ||
| 598 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--modal-switcherList .qr--id:after, | ||
| 599 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--modal-switcherList .qr--id:after { | ||
| 600 | content: "]"; | ||
| 601 | } | ||
| 602 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--modal-switcherList .qr--message, | ||
| 603 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > .label .qr--modal-switcherList .qr--message { | ||
| 604 | height: 1lh; | ||
| 605 | overflow: hidden; | ||
| 606 | text-overflow: ellipsis; | ||
| 607 | white-space: nowrap; | ||
| 608 | opacity: 0.5; | ||
| 609 | } | ||
| 278 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer { | 610 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer { |
| 279 | flex: 1 1 auto; | 611 | flex: 1 1 auto; |
| 280 | display: flex; | 612 | display: flex; |
| @@ -283,8 +615,9 @@ | |||
| 283 | } | 615 | } |
| 284 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > .qr--modal-editorSettings { | 616 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > .qr--modal-editorSettings { |
| 285 | display: flex; | 617 | display: flex; |
| 618 | flex-wrap: wrap; | ||
| 286 | flex-direction: row; | 619 | flex-direction: row; |
| 287 | gap: 1em; | 620 | column-gap: 1em; |
| 288 | color: var(--grey70); | 621 | color: var(--grey70); |
| 289 | font-size: smaller; | 622 | font-size: smaller; |
| 290 | align-items: baseline; | 623 | align-items: baseline; |
| @@ -308,6 +641,11 @@ | |||
| 308 | background-color: var(--ac-style-color-background); | 641 | background-color: var(--ac-style-color-background); |
| 309 | color: var(--ac-style-color-text); | 642 | color: var(--ac-style-color-text); |
| 310 | } | 643 | } |
| 644 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder.qr--noSyntax > #qr--modal-message::-webkit-scrollbar, | ||
| 645 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder.qr--noSyntax > #qr--modal-message::-webkit-scrollbar-thumb { | ||
| 646 | visibility: visible; | ||
| 647 | cursor: unset; | ||
| 648 | } | ||
| 311 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder.qr--noSyntax > #qr--modal-message::selection { | 649 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder.qr--noSyntax > #qr--modal-message::selection { |
| 312 | color: unset; | 650 | color: unset; |
| 313 | background-color: rgba(108 171 251 / 0.25); | 651 | background-color: rgba(108 171 251 / 0.25); |
| @@ -357,11 +695,15 @@ | |||
| 357 | font-family: var(--monoFontFamily); | 695 | font-family: var(--monoFontFamily); |
| 358 | padding: 0.75em; | 696 | padding: 0.75em; |
| 359 | margin: 0; | 697 | margin: 0; |
| 360 | border: none; | ||
| 361 | resize: none; | 698 | resize: none; |
| 362 | line-height: 1.2; | 699 | line-height: 1.2; |
| 363 | border: 1px solid var(--SmartThemeBorderColor); | 700 | border: 1px solid var(--SmartThemeBorderColor); |
| 364 | border-radius: 5px; | 701 | border-radius: 5px; |
| 702 | position: relative; | ||
| 703 | } | ||
| 704 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-icon { | ||
| 705 | height: 100%; | ||
| 706 | aspect-ratio: 1 / 1; | ||
| 365 | } | 707 | } |
| 366 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeButtons { | 708 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeButtons { |
| 367 | display: flex; | 709 | display: flex; |
| @@ -410,6 +752,46 @@ | |||
| 410 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeButtons #qr--modal-stop { | 752 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeButtons #qr--modal-stop { |
| 411 | border-color: #d78872; | 753 | border-color: #d78872; |
| 412 | } | 754 | } |
| 755 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugButtons { | ||
| 756 | display: none; | ||
| 757 | gap: 1em; | ||
| 758 | } | ||
| 759 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugButtons .qr--modal-debugButton { | ||
| 760 | aspect-ratio: 1.25 / 1; | ||
| 761 | width: 2.25em; | ||
| 762 | position: relative; | ||
| 763 | } | ||
| 764 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugButtons .qr--modal-debugButton:not(.fa-solid) { | ||
| 765 | border-width: 1px; | ||
| 766 | border-style: solid; | ||
| 767 | } | ||
| 768 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugButtons .qr--modal-debugButton:not(.fa-solid):after { | ||
| 769 | content: ''; | ||
| 770 | position: absolute; | ||
| 771 | inset: 3px; | ||
| 772 | background-color: var(--SmartThemeBodyColor); | ||
| 773 | mask-size: contain; | ||
| 774 | mask-position: center; | ||
| 775 | mask-repeat: no-repeat; | ||
| 776 | } | ||
| 777 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugButtons .qr--modal-debugButton#qr--modal-resume:after { | ||
| 778 | mask-image: url('/img/step-resume.svg'); | ||
| 779 | } | ||
| 780 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugButtons .qr--modal-debugButton#qr--modal-step:after { | ||
| 781 | mask-image: url('/img/step-over.svg'); | ||
| 782 | } | ||
| 783 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugButtons .qr--modal-debugButton#qr--modal-stepInto:after { | ||
| 784 | mask-image: url('/img/step-into.svg'); | ||
| 785 | } | ||
| 786 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugButtons .qr--modal-debugButton#qr--modal-stepOut:after { | ||
| 787 | mask-image: url('/img/step-out.svg'); | ||
| 788 | } | ||
| 789 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugButtons .qr--modal-debugButton#qr--modal-maximize { | ||
| 790 | display: none; | ||
| 791 | } | ||
| 792 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-send_textarea { | ||
| 793 | flex: 0 0 auto; | ||
| 794 | } | ||
| 413 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeProgress { | 795 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeProgress { |
| 414 | --prog: 0; | 796 | --prog: 0; |
| 415 | --progColor: #92befc; | 797 | --progColor: #92befc; |
| @@ -417,6 +799,7 @@ | |||
| 417 | --progSuccessColor: #51a351; | 799 | --progSuccessColor: #51a351; |
| 418 | --progErrorColor: #bd362f; | 800 | --progErrorColor: #bd362f; |
| 419 | --progAbortedColor: #d78872; | 801 | --progAbortedColor: #d78872; |
| 802 | flex: 0 0 auto; | ||
| 420 | height: 0.5em; | 803 | height: 0.5em; |
| 421 | background-color: var(--black50a); | 804 | background-color: var(--black50a); |
| 422 | position: relative; | 805 | position: relative; |
| @@ -469,6 +852,7 @@ | |||
| 469 | overflow: auto; | 852 | overflow: auto; |
| 470 | min-width: 100%; | 853 | min-width: 100%; |
| 471 | width: 0; | 854 | width: 0; |
| 855 | white-space: pre-wrap; | ||
| 472 | } | 856 | } |
| 473 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeResult.qr--hasResult { | 857 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeResult.qr--hasResult { |
| 474 | display: block; | 858 | display: block; |
| @@ -476,6 +860,150 @@ | |||
| 476 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeResult:before { | 860 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeResult:before { |
| 477 | content: 'Result: '; | 861 | content: 'Result: '; |
| 478 | } | 862 | } |
| 863 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState { | ||
| 864 | display: none; | ||
| 865 | text-align: left; | ||
| 866 | font-size: smaller; | ||
| 867 | font-family: var(--monoFontFamily); | ||
| 868 | color: white; | ||
| 869 | padding: 0.5em 0; | ||
| 870 | overflow: auto; | ||
| 871 | min-width: 100%; | ||
| 872 | width: 0; | ||
| 873 | white-space: pre-wrap; | ||
| 874 | } | ||
| 875 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState.qr--active { | ||
| 876 | display: block; | ||
| 877 | } | ||
| 878 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope { | ||
| 879 | display: grid; | ||
| 880 | grid-template-columns: 0fr 1fr 1fr; | ||
| 881 | column-gap: 0em; | ||
| 882 | } | ||
| 883 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--title { | ||
| 884 | grid-column: 1 / 4; | ||
| 885 | font-weight: bold; | ||
| 886 | font-family: var(--mainFontFamily); | ||
| 887 | background-color: var(--black50a); | ||
| 888 | padding: 0.25em; | ||
| 889 | margin-top: 0.5em; | ||
| 890 | } | ||
| 891 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var, | ||
| 892 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro, | ||
| 893 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe { | ||
| 894 | display: contents; | ||
| 895 | } | ||
| 896 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var:nth-child(2n + 1) .qr--key, | ||
| 897 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro:nth-child(2n + 1) .qr--key, | ||
| 898 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe:nth-child(2n + 1) .qr--key, | ||
| 899 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var:nth-child(2n + 1) .qr--val, | ||
| 900 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro:nth-child(2n + 1) .qr--val, | ||
| 901 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe:nth-child(2n + 1) .qr--val { | ||
| 902 | background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.25); | ||
| 903 | } | ||
| 904 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var:nth-child(2n + 1) .qr--val:nth-child(2n), | ||
| 905 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro:nth-child(2n + 1) .qr--val:nth-child(2n), | ||
| 906 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe:nth-child(2n + 1) .qr--val:nth-child(2n) { | ||
| 907 | background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.125); | ||
| 908 | } | ||
| 909 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var:nth-child(2n + 1) .qr--val:hover, | ||
| 910 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro:nth-child(2n + 1) .qr--val:hover, | ||
| 911 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe:nth-child(2n + 1) .qr--val:hover { | ||
| 912 | background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.5); | ||
| 913 | } | ||
| 914 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var:nth-child(2n) .qr--val:nth-child(2n), | ||
| 915 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro:nth-child(2n) .qr--val:nth-child(2n), | ||
| 916 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe:nth-child(2n) .qr--val:nth-child(2n) { | ||
| 917 | background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.0625); | ||
| 918 | } | ||
| 919 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var:nth-child(2n) .qr--val:hover, | ||
| 920 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro:nth-child(2n) .qr--val:hover, | ||
| 921 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe:nth-child(2n) .qr--val:hover { | ||
| 922 | background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.5); | ||
| 923 | } | ||
| 924 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var.qr--isHidden .qr--key, | ||
| 925 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro.qr--isHidden .qr--key, | ||
| 926 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe.qr--isHidden .qr--key, | ||
| 927 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var.qr--isHidden .qr--val, | ||
| 928 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro.qr--isHidden .qr--val, | ||
| 929 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe.qr--isHidden .qr--val { | ||
| 930 | opacity: 0.5; | ||
| 931 | } | ||
| 932 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var .qr--val, | ||
| 933 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro .qr--val, | ||
| 934 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe .qr--val { | ||
| 935 | grid-column: 2 / 4; | ||
| 936 | } | ||
| 937 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var .qr--val.qr--singleCol, | ||
| 938 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro .qr--val.qr--singleCol, | ||
| 939 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe .qr--val.qr--singleCol { | ||
| 940 | grid-column: unset; | ||
| 941 | } | ||
| 942 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var .qr--val.qr--simple:before, | ||
| 943 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro .qr--val.qr--simple:before, | ||
| 944 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe .qr--val.qr--simple:before, | ||
| 945 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var .qr--val.qr--simple:after, | ||
| 946 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro .qr--val.qr--simple:after, | ||
| 947 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe .qr--val.qr--simple:after { | ||
| 948 | content: '"'; | ||
| 949 | color: var(--SmartThemeQuoteColor); | ||
| 950 | } | ||
| 951 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var .qr--val.qr--unresolved:after, | ||
| 952 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro .qr--val.qr--unresolved:after, | ||
| 953 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe .qr--val.qr--unresolved:after { | ||
| 954 | content: '-UNRESOLVED-'; | ||
| 955 | font-style: italic; | ||
| 956 | color: var(--SmartThemeQuoteColor); | ||
| 957 | } | ||
| 958 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--key { | ||
| 959 | margin-left: 0.5em; | ||
| 960 | padding-right: 1em; | ||
| 961 | } | ||
| 962 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--key:after { | ||
| 963 | content: ": "; | ||
| 964 | } | ||
| 965 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe > .qr--key:before, | ||
| 966 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro > .qr--key:before { | ||
| 967 | content: "{{"; | ||
| 968 | } | ||
| 969 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe > .qr--key:after, | ||
| 970 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro > .qr--key:after { | ||
| 971 | content: "}}: "; | ||
| 972 | } | ||
| 973 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--scope { | ||
| 974 | display: contents; | ||
| 975 | } | ||
| 976 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--scope .qr--pipe .qr--key, | ||
| 977 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--scope .qr--pipe .qr--val { | ||
| 978 | opacity: 0.5; | ||
| 979 | } | ||
| 980 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--stack { | ||
| 981 | display: grid; | ||
| 982 | grid-template-columns: 1fr 0fr; | ||
| 983 | } | ||
| 984 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--stack .qr--title { | ||
| 985 | grid-column: 1 / 3; | ||
| 986 | font-weight: bold; | ||
| 987 | font-family: var(--mainFontFamily); | ||
| 988 | background-color: var(--black50a); | ||
| 989 | padding: 0.25em; | ||
| 990 | margin-top: 1em; | ||
| 991 | } | ||
| 992 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--stack .qr--item { | ||
| 993 | display: contents; | ||
| 994 | } | ||
| 995 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--stack .qr--item:nth-child(2n + 1) .qr--name, | ||
| 996 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--stack .qr--item:nth-child(2n + 1) .qr--source { | ||
| 997 | background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.25); | ||
| 998 | } | ||
| 999 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--stack .qr--item .qr--name { | ||
| 1000 | margin-left: 0.5em; | ||
| 1001 | } | ||
| 1002 | .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--stack .qr--item .qr--source { | ||
| 1003 | opacity: 0.5; | ||
| 1004 | text-align: right; | ||
| 1005 | white-space: nowrap; | ||
| 1006 | } | ||
| 479 | @keyframes qr--progressPulse { | 1007 | @keyframes qr--progressPulse { |
| 480 | 0%, | 1008 | 0%, |
| 481 | 100% { | 1009 | 100% { |
| @@ -485,9 +1013,63 @@ | |||
| 485 | background-color: var(--progFlashColor); | 1013 | background-color: var(--progFlashColor); |
| 486 | } | 1014 | } |
| 487 | } | 1015 | } |
| 1016 | @keyframes qr--debugPulse { | ||
| 1017 | 0%, | ||
| 1018 | 100% { | ||
| 1019 | border-color: #51a351; | ||
| 1020 | } | ||
| 1021 | 50% { | ||
| 1022 | border-color: #92befc; | ||
| 1023 | } | ||
| 1024 | } | ||
| 488 | .popup.qr--hide { | 1025 | .popup.qr--hide { |
| 489 | opacity: 0 !important; | 1026 | opacity: 0 !important; |
| 490 | } | 1027 | } |
| 491 | .popup.qr--hide::backdrop { | 1028 | .popup.qr--hide::backdrop { |
| 492 | opacity: 0 !important; | 1029 | opacity: 0 !important; |
| 493 | } | 1030 | } |
| 1031 | .popup.qr--hide::backdrop { | ||
| 1032 | opacity: 0 !important; | ||
| 1033 | } | ||
| 1034 | .popup:has(.qr--transferModal) .popup-button-ok { | ||
| 1035 | display: flex; | ||
| 1036 | align-items: center; | ||
| 1037 | flex-direction: column; | ||
| 1038 | white-space: pre; | ||
| 1039 | font-weight: normal; | ||
| 1040 | box-shadow: 0 0 0; | ||
| 1041 | transition: 200ms; | ||
| 1042 | } | ||
| 1043 | .popup:has(.qr--transferModal) .popup-button-ok:after { | ||
| 1044 | content: 'Transfer'; | ||
| 1045 | height: 0; | ||
| 1046 | overflow: hidden; | ||
| 1047 | font-weight: bold; | ||
| 1048 | } | ||
| 1049 | .popup:has(.qr--transferModal) .qr--copy { | ||
| 1050 | display: flex; | ||
| 1051 | align-items: center; | ||
| 1052 | flex-direction: column; | ||
| 1053 | white-space: pre; | ||
| 1054 | font-weight: normal; | ||
| 1055 | box-shadow: 0 0 0; | ||
| 1056 | transition: 200ms; | ||
| 1057 | } | ||
| 1058 | .popup:has(.qr--transferModal) .qr--copy:after { | ||
| 1059 | content: 'Copy'; | ||
| 1060 | height: 0; | ||
| 1061 | overflow: hidden; | ||
| 1062 | font-weight: bold; | ||
| 1063 | } | ||
| 1064 | .popup:has(.qr--transferModal):has(.qr--transferSelect:focus) .popup-button-ok { | ||
| 1065 | font-weight: bold; | ||
| 1066 | box-shadow: 0 0 10px; | ||
| 1067 | } | ||
| 1068 | .popup:has(.qr--transferModal):has(.qr--transferSelect:focus).qr--isCopy .popup-button-ok { | ||
| 1069 | font-weight: normal; | ||
| 1070 | box-shadow: 0 0 0; | ||
| 1071 | } | ||
| 1072 | .popup:has(.qr--transferModal):has(.qr--transferSelect:focus).qr--isCopy .qr--copy { | ||
| 1073 | font-weight: bold; | ||
| 1074 | box-shadow: 0 0 10px; | ||
| 1075 | } | ||
| @@ -1,3 +1,18 @@ | |||
| 1 | @keyframes qr--success { | ||
| 2 | 0%, 100% { | ||
| 3 | color: var(--SmartThemeBodyColor); | ||
| 4 | } | ||
| 5 | 25%, 75% { | ||
| 6 | color: rgb(81, 163, 81); | ||
| 7 | } | ||
| 8 | } | ||
| 9 | &.qr--success { | ||
| 10 | animation-name: qr--success; | ||
| 11 | animation-duration: 3s; | ||
| 12 | animation-timing-function: linear; | ||
| 13 | animation-delay: 0s; | ||
| 14 | animation-iteration-count: 1; | ||
| 15 | } | ||
| 1 | #qr--bar { | 16 | #qr--bar { |
| 2 | outline: none; | 17 | outline: none; |
| 3 | margin: 0; | 18 | margin: 0; |
| @@ -50,6 +65,18 @@ | |||
| 50 | #qr--bar, | 65 | #qr--bar, |
| 51 | #qr--popout>.qr--body { | 66 | #qr--popout>.qr--body { |
| 52 | >.qr--buttons { | 67 | >.qr--buttons { |
| 68 | --qr--color: transparent; | ||
| 69 | &.qr--color { | ||
| 70 | background-color: var(--qr--color); | ||
| 71 | } | ||
| 72 | &.qr--borderColor { | ||
| 73 | background-color: transparent; | ||
| 74 | border-left: 5px solid var(--qr--color); | ||
| 75 | border-right: 5px solid var(--qr--color); | ||
| 76 | } | ||
| 77 | &:has(.qr--buttons.qr--color) { | ||
| 78 | margin: 5px; | ||
| 79 | } | ||
| 53 | margin: 0; | 80 | margin: 0; |
| 54 | padding: 0; | 81 | padding: 0; |
| 55 | display: flex; | 82 | display: flex; |
| @@ -60,6 +87,25 @@ | |||
| 60 | 87 | ||
| 61 | >.qr--buttons { | 88 | >.qr--buttons { |
| 62 | display: contents; | 89 | display: contents; |
| 90 | &.qr--color { | ||
| 91 | .qr--button:before { | ||
| 92 | content: ''; | ||
| 93 | background-color: var(--qr--color); | ||
| 94 | position: absolute; | ||
| 95 | inset: -5px; | ||
| 96 | z-index: -1; | ||
| 97 | } | ||
| 98 | &.qr--borderColor { | ||
| 99 | .qr--button:before { | ||
| 100 | display: none; | ||
| 101 | } | ||
| 102 | &:before, &:after { | ||
| 103 | content: ''; | ||
| 104 | width: 5px; | ||
| 105 | background-color: var(--qr--color); | ||
| 106 | } | ||
| 107 | } | ||
| 108 | } | ||
| 63 | } | 109 | } |
| 64 | 110 | ||
| 65 | .qr--button { | 111 | .qr--button { |
| @@ -75,10 +121,17 @@ | |||
| 75 | align-items: center; | 121 | align-items: center; |
| 76 | justify-content: center; | 122 | justify-content: center; |
| 77 | text-align: center; | 123 | text-align: center; |
| 124 | position: relative; | ||
| 78 | 125 | ||
| 79 | &:hover { | 126 | &:hover { |
| 80 | opacity: 1; | 127 | background-color: rgb(30% 30% 30%); |
| 81 | filter: brightness(1.2); | 128 | } |
| 129 | |||
| 130 | .qr--hidden { | ||
| 131 | display: none; | ||
| 132 | } | ||
| 133 | .qr--button-icon { | ||
| 134 | margin: 0 0.5em; | ||
| 82 | } | 135 | } |
| 83 | 136 | ||
| 84 | >.qr--button-expander { | 137 | >.qr--button-expander { |
| @@ -211,14 +264,41 @@ | |||
| 211 | .qr--set-qrListContents> { | 264 | .qr--set-qrListContents> { |
| 212 | padding: 0 0.5em; | 265 | padding: 0 0.5em; |
| 213 | 266 | ||
| 214 | >.qr--set-item { | 267 | >.qr--set-item .qr--set-itemAdder { |
| 268 | display: flex; | ||
| 269 | align-items: center; | ||
| 270 | opacity: 0; | ||
| 271 | transition: 100ms; | ||
| 272 | margin: -2px 0 -11px 0; | ||
| 273 | position: relative; | ||
| 274 | .qr--actions { | ||
| 275 | display: flex; | ||
| 276 | gap: 0.25em; | ||
| 277 | flex: 0 0 auto; | ||
| 278 | .qr--action { | ||
| 279 | margin: 0; | ||
| 280 | } | ||
| 281 | } | ||
| 282 | &:before, &:after { | ||
| 283 | content: ""; | ||
| 284 | display: block; | ||
| 285 | flex: 1 1 auto; | ||
| 286 | border: 1px solid; | ||
| 287 | margin: 0 1em; | ||
| 288 | height: 0; | ||
| 289 | } | ||
| 290 | &:hover, &:focus-within { | ||
| 291 | opacity: 1; | ||
| 292 | } | ||
| 293 | } | ||
| 294 | >.qr--set-item .qr--content { | ||
| 215 | display: flex; | 295 | display: flex; |
| 216 | flex-direction: row; | 296 | flex-direction: row; |
| 217 | gap: 0.5em; | 297 | gap: 0.5em; |
| 218 | align-items: baseline; | 298 | align-items: baseline; |
| 219 | padding: 0.25em 0; | 299 | padding: 0.25em 0; |
| 220 | 300 | ||
| 221 | > :nth-child(1) { | 301 | > :nth-child(2) { |
| 222 | flex: 0 0 auto; | 302 | flex: 0 0 auto; |
| 223 | } | 303 | } |
| 224 | 304 | ||
| @@ -235,13 +315,29 @@ | |||
| 235 | } | 315 | } |
| 236 | 316 | ||
| 237 | > :nth-child(5) { | 317 | > :nth-child(5) { |
| 238 | flex: 0 0 auto; | 318 | flex: 0 1 auto; |
| 319 | display: flex; | ||
| 320 | gap: 0.25em; | ||
| 321 | justify-content: flex-end; | ||
| 322 | flex-wrap: wrap; | ||
| 239 | } | 323 | } |
| 240 | 324 | ||
| 241 | >.drag-handle { | 325 | >.drag-handle { |
| 242 | padding: 0.75em; | 326 | padding: 0.75em; |
| 243 | } | 327 | } |
| 244 | 328 | ||
| 329 | .qr--set-itemLabelContainer { | ||
| 330 | display: flex; | ||
| 331 | align-items: center; | ||
| 332 | gap: 0.5em; | ||
| 333 | .qr--set-itemIcon:not(.fa-solid) { | ||
| 334 | display: none; | ||
| 335 | } | ||
| 336 | .qr--set-itemLabel { | ||
| 337 | min-width: 24px; | ||
| 338 | } | ||
| 339 | } | ||
| 340 | |||
| 245 | .qr--set-itemLabel, | 341 | .qr--set-itemLabel, |
| 246 | .qr--action { | 342 | .qr--action { |
| 247 | margin: 0; | 343 | margin: 0; |
| @@ -251,6 +347,8 @@ | |||
| 251 | font-size: smaller; | 347 | font-size: smaller; |
| 252 | } | 348 | } |
| 253 | } | 349 | } |
| 350 | |||
| 351 | |||
| 254 | } | 352 | } |
| 255 | } | 353 | } |
| 256 | 354 | ||
| @@ -270,6 +368,7 @@ | |||
| 270 | #qr--qrOptions { | 368 | #qr--qrOptions { |
| 271 | display: flex; | 369 | display: flex; |
| 272 | flex-direction: column; | 370 | flex-direction: column; |
| 371 | padding-right: 1px; | ||
| 273 | 372 | ||
| 274 | >#qr--ctxEditor { | 373 | >#qr--ctxEditor { |
| 275 | .qr--ctxItem { | 374 | .qr--ctxItem { |
| @@ -279,6 +378,15 @@ | |||
| 279 | align-items: baseline; | 378 | align-items: baseline; |
| 280 | } | 379 | } |
| 281 | } | 380 | } |
| 381 | >#qr--autoExec { | ||
| 382 | .checkbox_label { | ||
| 383 | text-wrap: nowrap; | ||
| 384 | |||
| 385 | .fa-fw { | ||
| 386 | margin-right: 2px; | ||
| 387 | } | ||
| 388 | } | ||
| 389 | } | ||
| 282 | } | 390 | } |
| 283 | 391 | ||
| 284 | 392 | ||
| @@ -306,6 +414,78 @@ | |||
| 306 | .popup:has(#qr--modalEditor) { | 414 | .popup:has(#qr--modalEditor) { |
| 307 | aspect-ratio: unset; | 415 | aspect-ratio: unset; |
| 308 | 416 | ||
| 417 | &:has(.qr--isExecuting.qr--minimized) { | ||
| 418 | min-width: unset; | ||
| 419 | min-height: unset; | ||
| 420 | height: auto !important; | ||
| 421 | width: min-content !important; | ||
| 422 | position: absolute; | ||
| 423 | right: 1em; | ||
| 424 | top: 1em; | ||
| 425 | left: unset; | ||
| 426 | bottom: unset; | ||
| 427 | margin: unset; | ||
| 428 | padding: 0; | ||
| 429 | &::backdrop { | ||
| 430 | backdrop-filter: unset; | ||
| 431 | background-color: transparent; | ||
| 432 | } | ||
| 433 | .popup-body { | ||
| 434 | flex: 0 0 auto; | ||
| 435 | height: min-content; | ||
| 436 | width: min-content; | ||
| 437 | } | ||
| 438 | .popup-content { | ||
| 439 | flex: 0 0 auto; | ||
| 440 | margin-top: 0; | ||
| 441 | |||
| 442 | > #qr--modalEditor { | ||
| 443 | max-height: 50vh; | ||
| 444 | > #qr--main, | ||
| 445 | > #qr--resizeHandle, | ||
| 446 | > #qr--qrOptions > h3, | ||
| 447 | > #qr--qrOptions > #qr--modal-executeButtons, | ||
| 448 | > #qr--qrOptions > #qr--modal-executeProgress | ||
| 449 | { | ||
| 450 | display: none; | ||
| 451 | } | ||
| 452 | #qr--qrOptions { | ||
| 453 | width: auto; | ||
| 454 | } | ||
| 455 | #qr--modal-debugButtons .qr--modal-debugButton#qr--modal-maximize { | ||
| 456 | display: flex; | ||
| 457 | } | ||
| 458 | #qr--modal-debugButtons .qr--modal-debugButton#qr--modal-minimize { | ||
| 459 | display: none; | ||
| 460 | } | ||
| 461 | #qr--modal-debugState { | ||
| 462 | padding-top: 0; | ||
| 463 | } | ||
| 464 | } | ||
| 465 | } | ||
| 466 | } | ||
| 467 | &:has(.qr--isExecuting) { | ||
| 468 | .popup-controls { | ||
| 469 | display: none; | ||
| 470 | } | ||
| 471 | |||
| 472 | .qr--highlight { | ||
| 473 | position: absolute; | ||
| 474 | z-index: 50000; | ||
| 475 | pointer-events: none; | ||
| 476 | background-color: rgb(47 150 180 / 0.5); | ||
| 477 | &.qr--unresolved { | ||
| 478 | background-color: rgb(255 255 0 / 0.5); | ||
| 479 | } | ||
| 480 | } | ||
| 481 | .qr--highlight-secondary { | ||
| 482 | position: absolute; | ||
| 483 | z-index: 50000; | ||
| 484 | pointer-events: none; | ||
| 485 | border: 3px solid red; | ||
| 486 | } | ||
| 487 | } | ||
| 488 | |||
| 309 | .popup-content { | 489 | .popup-content { |
| 310 | display: flex; | 490 | display: flex; |
| 311 | flex-direction: column; | 491 | flex-direction: column; |
| @@ -317,86 +497,213 @@ | |||
| 317 | gap: 1em; | 497 | gap: 1em; |
| 318 | overflow: hidden; | 498 | overflow: hidden; |
| 319 | 499 | ||
| 500 | &.qr--isExecuting { | ||
| 501 | #qr--main > h3:first-child, | ||
| 502 | #qr--main > .qr--labels, | ||
| 503 | #qr--main > .qr--modal-messageContainer > .qr--modal-editorSettings, | ||
| 504 | #qr--qrOptions > h3:first-child, | ||
| 505 | #qr--qrOptions > #qr--ctxEditor, | ||
| 506 | #qr--qrOptions > .qr--ctxEditorActions, | ||
| 507 | #qr--qrOptions > .qr--ctxEditorActions + h3, | ||
| 508 | #qr--qrOptions > .qr--ctxEditorActions + h3 + div | ||
| 509 | { | ||
| 510 | display: none; | ||
| 511 | } | ||
| 512 | #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder > #qr--modal-message { | ||
| 513 | visibility: hidden; | ||
| 514 | } | ||
| 515 | #qr--modal-debugButtons { | ||
| 516 | display: flex; | ||
| 517 | .menu_button:not(#qr--modal-minimize, #qr--modal-maximize) { | ||
| 518 | cursor: not-allowed; | ||
| 519 | opacity: 0.5; | ||
| 520 | pointer-events: none; | ||
| 521 | transition: 200ms; | ||
| 522 | border-color: transparent; | ||
| 523 | } | ||
| 524 | } | ||
| 525 | &.qr--isPaused #qr--modal-debugButtons { | ||
| 526 | .menu_button:not(#qr--modal-minimize, #qr--modal-maximize) { | ||
| 527 | cursor: pointer; | ||
| 528 | opacity: 1; | ||
| 529 | pointer-events: all; | ||
| 530 | &#qr--modal-resume { | ||
| 531 | animation-name: qr--debugPulse; | ||
| 532 | animation-duration: 1500ms; | ||
| 533 | animation-timing-function: ease-in-out; | ||
| 534 | animation-delay: 0s; | ||
| 535 | animation-iteration-count: infinite; | ||
| 536 | } | ||
| 537 | &#qr--modal-resume { | ||
| 538 | border-color: rgb(81, 163, 81); | ||
| 539 | } | ||
| 540 | &#qr--modal-step { | ||
| 541 | border-color: var(--SmartThemeQuoteColor); | ||
| 542 | } | ||
| 543 | &#qr--modal-stepInto { | ||
| 544 | border-color: var(--SmartThemeQuoteColor); | ||
| 545 | } | ||
| 546 | &#qr--modal-stepOut { | ||
| 547 | border-color: var(--SmartThemeQuoteColor); | ||
| 548 | } | ||
| 549 | } | ||
| 550 | } | ||
| 551 | #qr--resizeHandle { | ||
| 552 | width: 6px; | ||
| 553 | background-color: var(--SmartThemeBorderColor); | ||
| 554 | border: 2px solid var(--SmartThemeBlurTintColor); | ||
| 555 | transition: border-color 200ms, background-color 200ms; | ||
| 556 | cursor: w-resize; | ||
| 557 | &:hover { | ||
| 558 | background-color: var(--SmartThemeQuoteColor); | ||
| 559 | border-color: var(--SmartThemeQuoteColor); | ||
| 560 | } | ||
| 561 | } | ||
| 562 | #qr--qrOptions { | ||
| 563 | width: var(--width, auto); | ||
| 564 | } | ||
| 565 | } | ||
| 566 | |||
| 320 | > #qr--main { | 567 | > #qr--main { |
| 321 | flex: 1 1 auto; | 568 | flex: 1 1 auto; |
| 322 | display: flex; | 569 | display: flex; |
| 323 | flex-direction: column; | 570 | flex-direction: column; |
| 324 | overflow: hidden; | 571 | overflow: hidden; |
| 325 | |||
| 326 | > .qr--labels { | 572 | > .qr--labels { |
| 327 | flex: 0 0 auto; | 573 | flex: 0 0 auto; |
| 328 | display: flex; | 574 | display: flex; |
| 329 | flex-direction: row; | 575 | flex-direction: row; |
| 330 | gap: 0.5em; | 576 | gap: 0.5em; |
| 331 | 577 | padding: 1px; | |
| 332 | >label { | 578 | > label, > .label { |
| 333 | flex: 1 1 1px; | 579 | flex: 1 1 1px; |
| 334 | display: flex; | 580 | display: flex; |
| 335 | flex-direction: column; | 581 | flex-direction: column; |
| 336 | 582 | position: relative; | |
| 337 | >.qr--labelText { | 583 | &.qr--fit { |
| 584 | flex: 0 0 auto; | ||
| 585 | justify-content: center; | ||
| 586 | } | ||
| 587 | .qr--inputGroup { | ||
| 588 | display: flex; | ||
| 589 | align-items: baseline; | ||
| 590 | gap: 0.5em; | ||
| 591 | input { | ||
| 338 | flex: 1 1 auto; | 592 | flex: 1 1 auto; |
| 339 | } | 593 | } |
| 340 | 594 | } | |
| 341 | >.qr--labelHint { | 595 | .qr--labelText { |
| 342 | flex: 1 1 auto; | 596 | flex: 1 1 auto; |
| 343 | } | 597 | } |
| 344 | 598 | .qr--labelHint { | |
| 345 | >input { | 599 | flex: 1 1 auto; |
| 600 | } | ||
| 601 | input { | ||
| 346 | flex: 0 0 auto; | 602 | flex: 0 0 auto; |
| 347 | } | 603 | } |
| 604 | .qr--modal-switcherList { | ||
| 605 | background-color: var(--stcdx--bgColor); | ||
| 606 | border: 1px solid var(--SmartThemeBorderColor); | ||
| 607 | backdrop-filter: blur(var(--SmartThemeBlurStrength)); | ||
| 608 | border-radius: 10px; | ||
| 609 | font-size: smaller; | ||
| 610 | position: absolute; | ||
| 611 | top: 100%; | ||
| 612 | left: 0; | ||
| 613 | right: 0; | ||
| 614 | overflow: auto; | ||
| 615 | margin: 0; | ||
| 616 | padding: 0.5em; | ||
| 617 | max-height: 50vh; | ||
| 618 | list-style: none; | ||
| 619 | z-index: 40000; | ||
| 620 | max-width: 100%; | ||
| 621 | .qr--modal-switcherItem { | ||
| 622 | display: flex; | ||
| 623 | gap: 1em; | ||
| 624 | text-align: left; | ||
| 625 | opacity: 0.75; | ||
| 626 | transition: 200ms; | ||
| 627 | cursor: pointer; | ||
| 628 | &:hover { | ||
| 629 | opacity: 1; | ||
| 630 | } | ||
| 631 | &.qr--current { | ||
| 632 | opacity: 1; | ||
| 633 | .qr--label, .qr--id { | ||
| 634 | font-weight: bold; | ||
| 635 | } | ||
| 636 | } | ||
| 637 | } | ||
| 638 | .qr--label { | ||
| 639 | white-space: nowrap; | ||
| 640 | .menu_button { | ||
| 641 | display: inline-block; | ||
| 642 | height: min-content; | ||
| 643 | width: min-content; | ||
| 644 | margin: 0 0.5em 0 0; | ||
| 645 | } | ||
| 646 | } | ||
| 647 | .qr--id { | ||
| 648 | opacity: 0.5; | ||
| 649 | &:before { content: "["; } | ||
| 650 | &:after { content: "]"; } | ||
| 651 | } | ||
| 652 | .qr--message { | ||
| 653 | height: 1lh; | ||
| 654 | overflow: hidden; | ||
| 655 | text-overflow: ellipsis; | ||
| 656 | white-space: nowrap; | ||
| 657 | opacity: 0.5; | ||
| 658 | } | ||
| 659 | } | ||
| 348 | } | 660 | } |
| 349 | } | 661 | } |
| 350 | |||
| 351 | > .qr--modal-messageContainer { | 662 | > .qr--modal-messageContainer { |
| 352 | flex: 1 1 auto; | 663 | flex: 1 1 auto; |
| 353 | display: flex; | 664 | display: flex; |
| 354 | flex-direction: column; | 665 | flex-direction: column; |
| 355 | overflow: hidden; | 666 | overflow: hidden; |
| 356 | |||
| 357 | > .qr--modal-editorSettings { | 667 | > .qr--modal-editorSettings { |
| 358 | display: flex; | 668 | display: flex; |
| 669 | flex-wrap: wrap; | ||
| 359 | flex-direction: row; | 670 | flex-direction: row; |
| 360 | gap: 1em; | 671 | column-gap: 1em; |
| 361 | color: var(--grey70); | 672 | color: var(--grey70); |
| 362 | font-size: smaller; | 673 | font-size: smaller; |
| 363 | align-items: baseline; | 674 | align-items: baseline; |
| 364 | |||
| 365 | > .checkbox_label { | 675 | > .checkbox_label { |
| 366 | white-space: nowrap; | 676 | white-space: nowrap; |
| 367 | |||
| 368 | > input { | 677 | > input { |
| 369 | font-size: inherit; | 678 | font-size: inherit; |
| 370 | } | 679 | } |
| 371 | } | 680 | } |
| 372 | } | 681 | } |
| 373 | |||
| 374 | > #qr--modal-messageHolder { | 682 | > #qr--modal-messageHolder { |
| 375 | flex: 1 1 auto; | 683 | flex: 1 1 auto; |
| 376 | display: grid; | 684 | display: grid; |
| 377 | text-align: left; | 685 | text-align: left; |
| 378 | overflow: hidden; | 686 | overflow: hidden; |
| 379 | |||
| 380 | &.qr--noSyntax { | 687 | &.qr--noSyntax { |
| 381 | > #qr--modal-messageSyntax { | 688 | > #qr--modal-messageSyntax { |
| 382 | display: none; | 689 | display: none; |
| 383 | } | 690 | } |
| 384 | |||
| 385 | > #qr--modal-message { | 691 | > #qr--modal-message { |
| 386 | background-color: var(--ac-style-color-background); | 692 | background-color: var(--ac-style-color-background); |
| 387 | color: var(--ac-style-color-text); | 693 | color: var(--ac-style-color-text); |
| 388 | 694 | &::-webkit-scrollbar, &::-webkit-scrollbar-thumb { | |
| 695 | visibility: visible; | ||
| 696 | cursor: unset; | ||
| 697 | } | ||
| 389 | &::selection { | 698 | &::selection { |
| 390 | color: unset; | 699 | color: unset; |
| 391 | background-color: rgba(108 171 251 / 0.25); | 700 | background-color: rgba(108 171 251 / 0.25); |
| 392 | |||
| 393 | @supports (color: rgb(from white r g b / 0.25)) { | 701 | @supports (color: rgb(from white r g b / 0.25)) { |
| 394 | background-color: rgb(from var(--ac-style-color-matchedText) r g b / 0.25); | 702 | background-color: rgb(from var(--ac-style-color-matchedText) r g b / 0.25); |
| 395 | } | 703 | } |
| 396 | } | 704 | } |
| 397 | } | 705 | } |
| 398 | } | 706 | } |
| 399 | |||
| 400 | > #qr--modal-messageSyntax { | 707 | > #qr--modal-messageSyntax { |
| 401 | grid-column: 1; | 708 | grid-column: 1; |
| 402 | grid-row: 1; | 709 | grid-row: 1; |
| @@ -406,12 +713,10 @@ | |||
| 406 | overflow: hidden; | 713 | overflow: hidden; |
| 407 | min-width: 100%; | 714 | min-width: 100%; |
| 408 | width: 0; | 715 | width: 0; |
| 409 | |||
| 410 | > #qr--modal-messageSyntaxInner { | 716 | > #qr--modal-messageSyntaxInner { |
| 411 | height: 100%; | 717 | height: 100%; |
| 412 | } | 718 | } |
| 413 | } | 719 | } |
| 414 | |||
| 415 | > #qr--modal-message { | 720 | > #qr--modal-message { |
| 416 | background-color: transparent; | 721 | background-color: transparent; |
| 417 | color: transparent; | 722 | color: transparent; |
| @@ -419,38 +724,35 @@ | |||
| 419 | grid-row: 1; | 724 | grid-row: 1; |
| 420 | caret-color: var(--ac-style-color-text); | 725 | caret-color: var(--ac-style-color-text); |
| 421 | overflow: auto; | 726 | overflow: auto; |
| 422 | 727 | &::-webkit-scrollbar, &::-webkit-scrollbar-thumb { | |
| 423 | &::-webkit-scrollbar, | ||
| 424 | &::-webkit-scrollbar-thumb { | ||
| 425 | visibility: hidden; | 728 | visibility: hidden; |
| 426 | cursor: default; | 729 | cursor: default; |
| 427 | } | 730 | } |
| 428 | |||
| 429 | &::selection { | 731 | &::selection { |
| 430 | color: transparent; | 732 | color: transparent; |
| 431 | background-color: rgba(108 171 251 / 0.25); | 733 | background-color: rgba(108 171 251 / 0.25); |
| 432 | |||
| 433 | @supports (color: rgb(from white r g b / 0.25)) { | 734 | @supports (color: rgb(from white r g b / 0.25)) { |
| 434 | background-color: rgb(from var(--ac-style-color-matchedText) r g b / 0.25); | 735 | background-color: rgb(from var(--ac-style-color-matchedText) r g b / 0.25); |
| 435 | } | 736 | } |
| 436 | } | 737 | } |
| 437 | } | 738 | } |
| 438 | 739 | #qr--modal-message, #qr--modal-messageSyntaxInner { | |
| 439 | #qr--modal-message, | ||
| 440 | #qr--modal-messageSyntaxInner { | ||
| 441 | font-family: var(--monoFontFamily); | 740 | font-family: var(--monoFontFamily); |
| 442 | padding: 0.75em; | 741 | padding: 0.75em; |
| 443 | margin: 0; | 742 | margin: 0; |
| 444 | border: none; | ||
| 445 | resize: none; | 743 | resize: none; |
| 446 | line-height: 1.2; | 744 | line-height: 1.2; |
| 447 | border: 1px solid var(--SmartThemeBorderColor); | 745 | border: 1px solid var(--SmartThemeBorderColor); |
| 448 | border-radius: 5px; | 746 | border-radius: 5px; |
| 747 | position: relative; | ||
| 449 | } | 748 | } |
| 450 | } | 749 | } |
| 451 | } | 750 | } |
| 452 | } | 751 | } |
| 453 | 752 | #qr--modal-icon { | |
| 753 | height: 100%; | ||
| 754 | aspect-ratio: 1 / 1; | ||
| 755 | } | ||
| 454 | #qr--modal-executeButtons { | 756 | #qr--modal-executeButtons { |
| 455 | display: flex; | 757 | display: flex; |
| 456 | gap: 1em; | 758 | gap: 1em; |
| @@ -510,6 +812,47 @@ | |||
| 510 | border-color: rgb(215, 136, 114); | 812 | border-color: rgb(215, 136, 114); |
| 511 | } | 813 | } |
| 512 | } | 814 | } |
| 815 | #qr--modal-debugButtons { | ||
| 816 | display: none; | ||
| 817 | gap: 1em; | ||
| 818 | .qr--modal-debugButton { | ||
| 819 | aspect-ratio: 1.25 / 1; | ||
| 820 | width: 2.25em; | ||
| 821 | position: relative; | ||
| 822 | &:not(.fa-solid) { | ||
| 823 | border-width: 1px; | ||
| 824 | border-style: solid; | ||
| 825 | &:after { | ||
| 826 | content: ''; | ||
| 827 | position: absolute; | ||
| 828 | inset: 3px; | ||
| 829 | background-color: var(--SmartThemeBodyColor); | ||
| 830 | mask-size: contain; | ||
| 831 | mask-position: center; | ||
| 832 | mask-repeat: no-repeat; | ||
| 833 | } | ||
| 834 | } | ||
| 835 | &#qr--modal-resume:after { | ||
| 836 | mask-image: url('/img/step-resume.svg'); | ||
| 837 | } | ||
| 838 | &#qr--modal-step:after { | ||
| 839 | mask-image: url('/img/step-over.svg'); | ||
| 840 | } | ||
| 841 | &#qr--modal-stepInto:after { | ||
| 842 | mask-image: url('/img/step-into.svg'); | ||
| 843 | } | ||
| 844 | &#qr--modal-stepOut:after { | ||
| 845 | mask-image: url('/img/step-out.svg'); | ||
| 846 | } | ||
| 847 | &#qr--modal-maximize { | ||
| 848 | display: none; | ||
| 849 | } | ||
| 850 | } | ||
| 851 | } | ||
| 852 | |||
| 853 | #qr--modal-send_textarea { | ||
| 854 | flex: 0 0 auto; | ||
| 855 | } | ||
| 513 | 856 | ||
| 514 | #qr--modal-executeProgress { | 857 | #qr--modal-executeProgress { |
| 515 | --prog: 0; | 858 | --prog: 0; |
| @@ -518,6 +861,7 @@ | |||
| 518 | --progSuccessColor: rgb(81, 163, 81); | 861 | --progSuccessColor: rgb(81, 163, 81); |
| 519 | --progErrorColor: rgb(189, 54, 47); | 862 | --progErrorColor: rgb(189, 54, 47); |
| 520 | --progAbortedColor: rgb(215, 136, 114); | 863 | --progAbortedColor: rgb(215, 136, 114); |
| 864 | flex: 0 0 auto; | ||
| 521 | height: 0.5em; | 865 | height: 0.5em; |
| 522 | background-color: var(--black50a); | 866 | background-color: var(--black50a); |
| 523 | position: relative; | 867 | position: relative; |
| @@ -588,6 +932,135 @@ | |||
| 588 | overflow: auto; | 932 | overflow: auto; |
| 589 | min-width: 100%; | 933 | min-width: 100%; |
| 590 | width: 0; | 934 | width: 0; |
| 935 | white-space: pre-wrap; | ||
| 936 | } | ||
| 937 | #qr--modal-debugState { | ||
| 938 | display: none; | ||
| 939 | &.qr--active { | ||
| 940 | display: block; | ||
| 941 | } | ||
| 942 | text-align: left; | ||
| 943 | font-size: smaller; | ||
| 944 | font-family: var(--monoFontFamily); | ||
| 945 | // background-color: rgb(146, 190, 252); | ||
| 946 | color: white; | ||
| 947 | padding: 0.5em 0; | ||
| 948 | overflow: auto; | ||
| 949 | min-width: 100%; | ||
| 950 | width: 0; | ||
| 951 | white-space: pre-wrap; | ||
| 952 | |||
| 953 | .qr--scope { | ||
| 954 | display: grid; | ||
| 955 | grid-template-columns: 0fr 1fr 1fr; | ||
| 956 | column-gap: 0em; | ||
| 957 | .qr--title { | ||
| 958 | grid-column: 1 / 4; | ||
| 959 | font-weight: bold; | ||
| 960 | font-family: var(--mainFontFamily); | ||
| 961 | background-color: var(--black50a); | ||
| 962 | padding: 0.25em; | ||
| 963 | margin-top: 0.5em; | ||
| 964 | } | ||
| 965 | .qr--var, .qr--macro, .qr--pipe { | ||
| 966 | display: contents; | ||
| 967 | &:nth-child(2n + 1) { | ||
| 968 | .qr--key, .qr--val { | ||
| 969 | background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.25); | ||
| 970 | } | ||
| 971 | .qr--val { | ||
| 972 | &:nth-child(2n) { | ||
| 973 | background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.125); | ||
| 974 | } | ||
| 975 | &:hover { | ||
| 976 | background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.5); | ||
| 977 | } | ||
| 978 | } | ||
| 979 | } | ||
| 980 | &:nth-child(2n) { | ||
| 981 | .qr--val { | ||
| 982 | &:nth-child(2n) { | ||
| 983 | background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.0625); | ||
| 984 | } | ||
| 985 | &:hover { | ||
| 986 | background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.5); | ||
| 987 | } | ||
| 988 | } | ||
| 989 | } | ||
| 990 | &.qr--isHidden { | ||
| 991 | .qr--key, .qr--val { | ||
| 992 | opacity: 0.5; | ||
| 993 | } | ||
| 994 | } | ||
| 995 | .qr--val { | ||
| 996 | grid-column: 2 / 4; | ||
| 997 | &.qr--singleCol { | ||
| 998 | grid-column: unset; | ||
| 999 | } | ||
| 1000 | &.qr--simple { | ||
| 1001 | &:before, &:after { | ||
| 1002 | content: '"'; | ||
| 1003 | color: var(--SmartThemeQuoteColor); | ||
| 1004 | } | ||
| 1005 | } | ||
| 1006 | &.qr--unresolved { | ||
| 1007 | &:after { | ||
| 1008 | content: '-UNRESOLVED-'; | ||
| 1009 | font-style: italic; | ||
| 1010 | color: var(--SmartThemeQuoteColor); | ||
| 1011 | } | ||
| 1012 | } | ||
| 1013 | } | ||
| 1014 | } | ||
| 1015 | .qr--key { | ||
| 1016 | margin-left: 0.5em; | ||
| 1017 | padding-right: 1em; | ||
| 1018 | &:after { content: ": "; } | ||
| 1019 | } | ||
| 1020 | .qr--pipe, .qr--macro { | ||
| 1021 | > .qr--key { | ||
| 1022 | &:before { content: "{{"; } | ||
| 1023 | &:after { content: "}}: "; } | ||
| 1024 | } | ||
| 1025 | } | ||
| 1026 | .qr--scope { | ||
| 1027 | display: contents; | ||
| 1028 | .qr--pipe { | ||
| 1029 | .qr--key, .qr--val { | ||
| 1030 | opacity: 0.5; | ||
| 1031 | } | ||
| 1032 | } | ||
| 1033 | } | ||
| 1034 | } | ||
| 1035 | |||
| 1036 | .qr--stack { | ||
| 1037 | display: grid; | ||
| 1038 | grid-template-columns: 1fr 0fr; | ||
| 1039 | .qr--title { | ||
| 1040 | grid-column: 1 / 3; | ||
| 1041 | font-weight: bold; | ||
| 1042 | font-family: var(--mainFontFamily); | ||
| 1043 | background-color: var(--black50a); | ||
| 1044 | padding: 0.25em; | ||
| 1045 | margin-top: 1em; | ||
| 1046 | } | ||
| 1047 | .qr--item { | ||
| 1048 | display: contents; | ||
| 1049 | &:nth-child(2n + 1) { | ||
| 1050 | .qr--name, .qr--source { | ||
| 1051 | background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.25); | ||
| 1052 | } | ||
| 1053 | } | ||
| 1054 | .qr--name { | ||
| 1055 | margin-left: 0.5em; | ||
| 1056 | } | ||
| 1057 | .qr--source { | ||
| 1058 | opacity: 0.5; | ||
| 1059 | text-align: right; | ||
| 1060 | white-space: nowrap; | ||
| 1061 | } | ||
| 1062 | } | ||
| 1063 | } | ||
| 591 | } | 1064 | } |
| 592 | } | 1065 | } |
| 593 | } | 1066 | } |
| @@ -605,10 +1078,75 @@ | |||
| 605 | } | 1078 | } |
| 606 | } | 1079 | } |
| 607 | 1080 | ||
| 1081 | @keyframes qr--debugPulse { | ||
| 1082 | 0%, | ||
| 1083 | 100% { | ||
| 1084 | border-color: rgb(81, 163, 81); | ||
| 1085 | } | ||
| 1086 | |||
| 1087 | 50% { | ||
| 1088 | border-color: rgb(146, 190, 252); | ||
| 1089 | } | ||
| 1090 | } | ||
| 1091 | |||
| 608 | .popup.qr--hide { | 1092 | .popup.qr--hide { |
| 609 | opacity: 0 !important; | 1093 | opacity: 0 !important; |
| 1094 | &::backdrop { | ||
| 1095 | opacity: 0 !important; | ||
| 1096 | } | ||
| 610 | } | 1097 | } |
| 611 | 1098 | ||
| 612 | .popup.qr--hide::backdrop { | 1099 | .popup.qr--hide::backdrop { |
| 613 | opacity: 0 !important; | 1100 | opacity: 0 !important; |
| 614 | } | 1101 | } |
| 1102 | |||
| 1103 | |||
| 1104 | |||
| 1105 | .popup:has(.qr--transferModal) { | ||
| 1106 | .popup-button-ok { | ||
| 1107 | &:after { | ||
| 1108 | content: 'Transfer'; | ||
| 1109 | height: 0; | ||
| 1110 | overflow: hidden; | ||
| 1111 | font-weight: bold; | ||
| 1112 | } | ||
| 1113 | display: flex; | ||
| 1114 | align-items: center; | ||
| 1115 | flex-direction: column; | ||
| 1116 | white-space: pre; | ||
| 1117 | font-weight: normal; | ||
| 1118 | box-shadow: 0 0 0; | ||
| 1119 | transition: 200ms; | ||
| 1120 | } | ||
| 1121 | .qr--copy { | ||
| 1122 | &:after { | ||
| 1123 | content: 'Copy'; | ||
| 1124 | height: 0; | ||
| 1125 | overflow: hidden; | ||
| 1126 | font-weight: bold; | ||
| 1127 | } | ||
| 1128 | display: flex; | ||
| 1129 | align-items: center; | ||
| 1130 | flex-direction: column; | ||
| 1131 | white-space: pre; | ||
| 1132 | font-weight: normal; | ||
| 1133 | box-shadow: 0 0 0; | ||
| 1134 | transition: 200ms; | ||
| 1135 | } | ||
| 1136 | &:has(.qr--transferSelect:focus) { | ||
| 1137 | .popup-button-ok { | ||
| 1138 | font-weight: bold; | ||
| 1139 | box-shadow: 0 0 10px; | ||
| 1140 | } | ||
| 1141 | &.qr--isCopy { | ||
| 1142 | .popup-button-ok { | ||
| 1143 | font-weight: normal; | ||
| 1144 | box-shadow: 0 0 0; | ||
| 1145 | } | ||
| 1146 | .qr--copy { | ||
| 1147 | font-weight: bold; | ||
| 1148 | box-shadow: 0 0 10px; | ||
| 1149 | } | ||
| 1150 | } | ||
| 1151 | } | ||
| 1152 | } | ||
| @@ -526,6 +526,15 @@ export class Popup { | |||
| 526 | 526 | ||
| 527 | return this.#promise; | 527 | return this.#promise; |
| 528 | } | 528 | } |
| 529 | async completeAffirmative() { | ||
| 530 | return await this.complete(POPUP_RESULT.AFFIRMATIVE); | ||
| 531 | } | ||
| 532 | async completeNegative() { | ||
| 533 | return await this.complete(POPUP_RESULT.NEGATIVE); | ||
| 534 | } | ||
| 535 | async completeCancelled() { | ||
| 536 | return await this.complete(POPUP_RESULT.CANCELLED); | ||
| 537 | } | ||
| 529 | 538 | ||
| 530 | /** | 539 | /** |
| 531 | * Hides the popup, using the internal resolver to return the value to the original show promise | 540 | * 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'; | |||
| 45 | import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js'; | 45 | import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js'; |
| 46 | import { SlashCommand } from './slash-commands/SlashCommand.js'; | 46 | import { SlashCommand } from './slash-commands/SlashCommand.js'; |
| 47 | import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js'; | 47 | import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js'; |
| 48 | import { AUTOCOMPLETE_WIDTH } from './autocomplete/AutoComplete.js'; | 48 | import { AUTOCOMPLETE_SELECT_KEY, AUTOCOMPLETE_WIDTH } from './autocomplete/AutoComplete.js'; |
| 49 | import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js'; | 49 | import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js'; |
| 50 | import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js'; | 50 | import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js'; |
| 51 | import { POPUP_TYPE, callGenericPopup } from './popup.js'; | 51 | import { POPUP_TYPE, callGenericPopup } from './popup.js'; |
| @@ -277,6 +277,7 @@ let power_user = { | |||
| 277 | left: AUTOCOMPLETE_WIDTH.CHAT, | 277 | left: AUTOCOMPLETE_WIDTH.CHAT, |
| 278 | right: AUTOCOMPLETE_WIDTH.CHAT, | 278 | right: AUTOCOMPLETE_WIDTH.CHAT, |
| 279 | }, | 279 | }, |
| 280 | select: AUTOCOMPLETE_SELECT_KEY.TAB + AUTOCOMPLETE_SELECT_KEY.ENTER, | ||
| 280 | }, | 281 | }, |
| 281 | parser: { | 282 | parser: { |
| 282 | /**@type {Object.<PARSER_FLAG,boolean>} */ | 283 | /**@type {Object.<PARSER_FLAG,boolean>} */ |
| @@ -1494,6 +1495,9 @@ function loadPowerUserSettings(settings, data) { | |||
| 1494 | if (power_user.stscript.autocomplete.style === undefined) { | 1495 | if (power_user.stscript.autocomplete.style === undefined) { |
| 1495 | power_user.stscript.autocomplete.style = power_user.stscript.autocomplete_style || defaultStscript.autocomplete.style; | 1496 | power_user.stscript.autocomplete.style = power_user.stscript.autocomplete_style || defaultStscript.autocomplete.style; |
| 1496 | } | 1497 | } |
| 1498 | if (power_user.stscript.autocomplete.select === undefined) { | ||
| 1499 | power_user.stscript.autocomplete.select = defaultStscript.autocomplete.select; | ||
| 1500 | } | ||
| 1497 | } | 1501 | } |
| 1498 | if (power_user.stscript.parser === undefined) { | 1502 | if (power_user.stscript.parser === undefined) { |
| 1499 | power_user.stscript.parser = defaultStscript.parser; | 1503 | power_user.stscript.parser = defaultStscript.parser; |
| @@ -1658,6 +1662,7 @@ function loadPowerUserSettings(settings, data) { | |||
| 1658 | $('#stscript_matching').val(power_user.stscript.matching ?? 'fuzzy'); | 1662 | $('#stscript_matching').val(power_user.stscript.matching ?? 'fuzzy'); |
| 1659 | $('#stscript_autocomplete_style').val(power_user.stscript.autocomplete.style ?? 'theme'); | 1663 | $('#stscript_autocomplete_style').val(power_user.stscript.autocomplete.style ?? 'theme'); |
| 1660 | document.body.setAttribute('data-stscript-style', power_user.stscript.autocomplete.style); | 1664 | document.body.setAttribute('data-stscript-style', power_user.stscript.autocomplete.style); |
| 1665 | $('#stscript_autocomplete_select').val(power_user.stscript.autocomplete.select ?? (AUTOCOMPLETE_SELECT_KEY.TAB + AUTOCOMPLETE_SELECT_KEY.ENTER)); | ||
| 1661 | $('#stscript_parser_flag_strict_escaping').prop('checked', power_user.stscript.parser.flags[PARSER_FLAG.STRICT_ESCAPING] ?? false); | 1666 | $('#stscript_parser_flag_strict_escaping').prop('checked', power_user.stscript.parser.flags[PARSER_FLAG.STRICT_ESCAPING] ?? false); |
| 1662 | $('#stscript_parser_flag_replace_getvar').prop('checked', power_user.stscript.parser.flags[PARSER_FLAG.REPLACE_GETVAR] ?? false); | 1667 | $('#stscript_parser_flag_replace_getvar').prop('checked', power_user.stscript.parser.flags[PARSER_FLAG.REPLACE_GETVAR] ?? false); |
| 1663 | $('#stscript_autocomplete_font_scale').val(power_user.stscript.autocomplete.font.scale ?? defaultStscript.autocomplete.font.scale); | 1668 | $('#stscript_autocomplete_font_scale').val(power_user.stscript.autocomplete.font.scale ?? defaultStscript.autocomplete.font.scale); |
| @@ -3848,6 +3853,12 @@ $(document).ready(() => { | |||
| 3848 | saveSettingsDebounced(); | 3853 | saveSettingsDebounced(); |
| 3849 | }); | 3854 | }); |
| 3850 | 3855 | ||
| 3856 | $('#stscript_autocomplete_select').on('change', function () { | ||
| 3857 | const value = $(this).find(':selected').val(); | ||
| 3858 | power_user.stscript.autocomplete.select = parseInt(String(value)); | ||
| 3859 | saveSettingsDebounced(); | ||
| 3860 | }); | ||
| 3861 | |||
| 3851 | $('#stscript_autocomplete_font_scale').on('input', function () { | 3862 | $('#stscript_autocomplete_font_scale').on('input', function () { |
| 3852 | const value = $(this).val(); | 3863 | const value = $(this).val(); |
| 3853 | $('#stscript_autocomplete_font_scale_counter').val(value); | 3864 | $('#stscript_autocomplete_font_scale_counter').val(value); |
| @@ -4016,6 +4027,7 @@ $(document).ready(() => { | |||
| 4016 | ), | 4027 | ), |
| 4017 | ], | 4028 | ], |
| 4018 | helpString: 'Enter message deletion mode, and auto-deletes last N messages if numeric argument is provided.', | 4029 | helpString: 'Enter message deletion mode, and auto-deletes last N messages if numeric argument is provided.', |
| 4030 | returns: 'The text of the deleted messages.', | ||
| 4019 | })); | 4031 | })); |
| 4020 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | 4032 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ |
| 4021 | name: 'cut', | 4033 | name: 'cut', |
| @@ -51,7 +51,7 @@ import { autoSelectPersona, retriggerFirstMessageOnEmptyChat, setPersonaLockStat | |||
| 51 | import { addEphemeralStoppingString, chat_styles, flushEphemeralStoppingStrings, power_user } from './power-user.js'; | 51 | import { addEphemeralStoppingString, chat_styles, flushEphemeralStoppingStrings, power_user } from './power-user.js'; |
| 52 | import { textgen_types, textgenerationwebui_settings } from './textgen-settings.js'; | 52 | import { textgen_types, textgenerationwebui_settings } from './textgen-settings.js'; |
| 53 | import { decodeTextTokens, getFriendlyTokenizerName, getTextTokens, getTokenCountAsync } from './tokenizers.js'; | 53 | import { decodeTextTokens, getFriendlyTokenizerName, getTextTokens, getTokenCountAsync } from './tokenizers.js'; |
| 54 | import { debounce, delay, isFalseBoolean, isTrueBoolean, stringToRange, trimToEndSentence, trimToStartSentence, waitUntilCondition } from './utils.js'; | 54 | import { debounce, delay, isFalseBoolean, isTrueBoolean, showFontAwesomePicker, stringToRange, trimToEndSentence, trimToStartSentence, waitUntilCondition } from './utils.js'; |
| 55 | import { registerVariableCommands, resolveVariable } from './variables.js'; | 55 | import { registerVariableCommands, resolveVariable } from './variables.js'; |
| 56 | import { background_settings } from './backgrounds.js'; | 56 | import { background_settings } from './backgrounds.js'; |
| 57 | import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js'; | 57 | import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js'; |
| @@ -64,6 +64,9 @@ import { SlashCommandNamedArgumentAssignment } from './slash-commands/SlashComma | |||
| 64 | import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js'; | 64 | import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js'; |
| 65 | import { POPUP_TYPE, Popup, callGenericPopup } from './popup.js'; | 65 | import { POPUP_TYPE, Popup, callGenericPopup } from './popup.js'; |
| 66 | import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js'; | 66 | import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js'; |
| 67 | import { SlashCommandDebugController } from './slash-commands/SlashCommandDebugController.js'; | ||
| 68 | import { SlashCommandBreakController } from './slash-commands/SlashCommandBreakController.js'; | ||
| 69 | import { SlashCommandExecutionError } from './slash-commands/SlashCommandExecutionError.js'; | ||
| 67 | export { | 70 | export { |
| 68 | executeSlashCommands, executeSlashCommandsWithOptions, getSlashCommandsHelp, registerSlashCommand, | 71 | executeSlashCommands, executeSlashCommandsWithOptions, getSlashCommandsHelp, registerSlashCommand, |
| 69 | }; | 72 | }; |
| @@ -1120,10 +1123,10 @@ export function initDefaultSlashCommands() { | |||
| 1120 | unnamedArgumentList: [ | 1123 | unnamedArgumentList: [ |
| 1121 | SlashCommandArgument.fromProps({ | 1124 | SlashCommandArgument.fromProps({ |
| 1122 | description: 'scoped variable or qr label', | 1125 | description: 'scoped variable or qr label', |
| 1123 | typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING], | 1126 | typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.CLOSURE], |
| 1124 | isRequired: true, | 1127 | isRequired: true, |
| 1125 | enumProvider: () => [ | 1128 | enumProvider: (executor, scope) => [ |
| 1126 | ...commonEnumProviders.variables('scope')(), | 1129 | ...commonEnumProviders.variables('scope')(executor, scope), |
| 1127 | ...(typeof window['qrEnumProviderExecutables'] === 'function') ? window['qrEnumProviderExecutables']() : [], | 1130 | ...(typeof window['qrEnumProviderExecutables'] === 'function') ? window['qrEnumProviderExecutables']() : [], |
| 1128 | ], | 1131 | ], |
| 1129 | }), | 1132 | }), |
| @@ -1477,6 +1480,21 @@ export function initDefaultSlashCommands() { | |||
| 1477 | ], | 1480 | ], |
| 1478 | helpString: 'Sets the specified prompt manager entry/entries on or off.', | 1481 | helpString: 'Sets the specified prompt manager entry/entries on or off.', |
| 1479 | })); | 1482 | })); |
| 1483 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'pick-icon', | ||
| 1484 | callback: async()=>((await showFontAwesomePicker()) ?? false).toString(), | ||
| 1485 | returns: 'The chosen icon name or false if cancelled.', | ||
| 1486 | helpString: ` | ||
| 1487 | <div>Opens a popup with all the available Font Awesome icons and returns the selected icon's name.</div> | ||
| 1488 | <div> | ||
| 1489 | <strong>Example:</strong> | ||
| 1490 | <ul> | ||
| 1491 | <li> | ||
| 1492 | <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> | ||
| 1493 | </li> | ||
| 1494 | </ul> | ||
| 1495 | </div> | ||
| 1496 | `, | ||
| 1497 | })); | ||
| 1480 | 1498 | ||
| 1481 | registerVariableCommands(); | 1499 | registerVariableCommands(); |
| 1482 | } | 1500 | } |
| @@ -1821,6 +1839,11 @@ async function runCallback(args, name) { | |||
| 1821 | throw new Error('No name provided for /run command'); | 1839 | throw new Error('No name provided for /run command'); |
| 1822 | } | 1840 | } |
| 1823 | 1841 | ||
| 1842 | if (name instanceof SlashCommandClosure) { | ||
| 1843 | name.breakController = new SlashCommandBreakController(); | ||
| 1844 | return (await name.execute())?.pipe; | ||
| 1845 | } | ||
| 1846 | |||
| 1824 | /**@type {SlashCommandScope} */ | 1847 | /**@type {SlashCommandScope} */ |
| 1825 | const scope = args._scope; | 1848 | const scope = args._scope; |
| 1826 | if (scope.existsVariable(name)) { | 1849 | if (scope.existsVariable(name)) { |
| @@ -1829,6 +1852,11 @@ async function runCallback(args, name) { | |||
| 1829 | throw new Error(`"${name}" is not callable.`); | 1852 | throw new Error(`"${name}" is not callable.`); |
| 1830 | } | 1853 | } |
| 1831 | closure.scope.parent = scope; | 1854 | closure.scope.parent = scope; |
| 1855 | closure.breakController = new SlashCommandBreakController(); | ||
| 1856 | if (args._debugController && !closure.debugController) { | ||
| 1857 | closure.debugController = args._debugController; | ||
| 1858 | } | ||
| 1859 | while (closure.providedArgumentList.pop()); | ||
| 1832 | closure.argumentList.forEach(arg => { | 1860 | closure.argumentList.forEach(arg => { |
| 1833 | if (Object.keys(args).includes(arg.name)) { | 1861 | if (Object.keys(args).includes(arg.name)) { |
| 1834 | const providedArg = new SlashCommandNamedArgumentAssignment(); | 1862 | const providedArg = new SlashCommandNamedArgumentAssignment(); |
| @@ -1847,9 +1875,14 @@ async function runCallback(args, name) { | |||
| 1847 | 1875 | ||
| 1848 | try { | 1876 | try { |
| 1849 | name = name.trim(); | 1877 | name = name.trim(); |
| 1850 | return await window['executeQuickReplyByName'](name, args); | 1878 | /**@type {ExecuteSlashCommandsOptions} */ |
| 1879 | const options = { | ||
| 1880 | abortController: args._abortController, | ||
| 1881 | debugController: args._debugController, | ||
| 1882 | }; | ||
| 1883 | return await window['executeQuickReplyByName'](name, args, options); | ||
| 1851 | } catch (error) { | 1884 | } catch (error) { |
| 1852 | throw new Error(`Error running Quick Reply "${name}": ${error.message}`, 'Error'); | 1885 | throw new Error(`Error running Quick Reply "${name}": ${error.message}`); |
| 1853 | } | 1886 | } |
| 1854 | } | 1887 | } |
| 1855 | 1888 | ||
| @@ -3428,7 +3461,9 @@ const clearCommandProgressDebounced = debounce(clearCommandProgress); | |||
| 3428 | * @prop {boolean} [handleExecutionErrors] (false) Whether to handle execution errors (show toast on error) or throw | 3461 | * @prop {boolean} [handleExecutionErrors] (false) Whether to handle execution errors (show toast on error) or throw |
| 3429 | * @prop {{[id:PARSER_FLAG]:boolean}} [parserFlags] (null) Parser flags to apply | 3462 | * @prop {{[id:PARSER_FLAG]:boolean}} [parserFlags] (null) Parser flags to apply |
| 3430 | * @prop {SlashCommandAbortController} [abortController] (null) Controller used to abort or pause command execution | 3463 | * @prop {SlashCommandAbortController} [abortController] (null) Controller used to abort or pause command execution |
| 3464 | * @prop {SlashCommandDebugController} [debugController] (null) Controller used to control debug execution | ||
| 3431 | * @prop {(done:number, total:number)=>void} [onProgress] (null) Callback to handle progress events | 3465 | * @prop {(done:number, total:number)=>void} [onProgress] (null) Callback to handle progress events |
| 3466 | * @prop {string} [source] (null) String indicating where the code come from (e.g., QR name) | ||
| 3432 | */ | 3467 | */ |
| 3433 | 3468 | ||
| 3434 | /** | 3469 | /** |
| @@ -3436,6 +3471,7 @@ const clearCommandProgressDebounced = debounce(clearCommandProgress); | |||
| 3436 | * @prop {SlashCommandScope} [scope] (null) The scope to be used when executing the commands. | 3471 | * @prop {SlashCommandScope} [scope] (null) The scope to be used when executing the commands. |
| 3437 | * @prop {{[id:PARSER_FLAG]:boolean}} [parserFlags] (null) Parser flags to apply | 3472 | * @prop {{[id:PARSER_FLAG]:boolean}} [parserFlags] (null) Parser flags to apply |
| 3438 | * @prop {boolean} [clearChatInput] (false) Whether to clear the chat input textarea | 3473 | * @prop {boolean} [clearChatInput] (false) Whether to clear the chat input textarea |
| 3474 | * @prop {string} [source] (null) String indicating where the code come from (e.g., QR name) | ||
| 3439 | */ | 3475 | */ |
| 3440 | 3476 | ||
| 3441 | /** | 3477 | /** |
| @@ -3451,6 +3487,7 @@ export async function executeSlashCommandsOnChatInput(text, options = {}) { | |||
| 3451 | scope: null, | 3487 | scope: null, |
| 3452 | parserFlags: null, | 3488 | parserFlags: null, |
| 3453 | clearChatInput: false, | 3489 | clearChatInput: false, |
| 3490 | source: null, | ||
| 3454 | }, options); | 3491 | }, options); |
| 3455 | 3492 | ||
| 3456 | isExecutingCommandsFromChatInput = true; | 3493 | isExecutingCommandsFromChatInput = true; |
| @@ -3473,13 +3510,21 @@ export async function executeSlashCommandsOnChatInput(text, options = {}) { | |||
| 3473 | 3510 | ||
| 3474 | /**@type {SlashCommandClosureResult} */ | 3511 | /**@type {SlashCommandClosureResult} */ |
| 3475 | let result = null; | 3512 | let result = null; |
| 3513 | let currentProgress = 0; | ||
| 3476 | try { | 3514 | try { |
| 3477 | commandsFromChatInputAbortController = new SlashCommandAbortController(); | 3515 | commandsFromChatInputAbortController = new SlashCommandAbortController(); |
| 3478 | result = await executeSlashCommandsWithOptions(text, { | 3516 | result = await executeSlashCommandsWithOptions(text, { |
| 3479 | abortController: commandsFromChatInputAbortController, | 3517 | abortController: commandsFromChatInputAbortController, |
| 3480 | onProgress: (done, total) => ta.style.setProperty('--prog', `${done / total * 100}%`), | 3518 | onProgress: (done, total) => { |
| 3519 | const newProgress = done / total; | ||
| 3520 | if (newProgress > currentProgress) { | ||
| 3521 | currentProgress = newProgress; | ||
| 3522 | ta.style.setProperty('--prog', `${newProgress * 100}%`); | ||
| 3523 | } | ||
| 3524 | }, | ||
| 3481 | parserFlags: options.parserFlags, | 3525 | parserFlags: options.parserFlags, |
| 3482 | scope: options.scope, | 3526 | scope: options.scope, |
| 3527 | source: options.source, | ||
| 3483 | }); | 3528 | }); |
| 3484 | if (commandsFromChatInputAbortController.signal.aborted) { | 3529 | if (commandsFromChatInputAbortController.signal.aborted) { |
| 3485 | document.querySelector('#form_sheld').classList.add('script_aborted'); | 3530 | document.querySelector('#form_sheld').classList.add('script_aborted'); |
| @@ -3492,8 +3537,24 @@ export async function executeSlashCommandsOnChatInput(text, options = {}) { | |||
| 3492 | result.isError = true; | 3537 | result.isError = true; |
| 3493 | result.errorMessage = e.message || 'An unknown error occurred'; | 3538 | result.errorMessage = e.message || 'An unknown error occurred'; |
| 3494 | if (e.cause !== 'abort') { | 3539 | if (e.cause !== 'abort') { |
| 3540 | if (e instanceof SlashCommandExecutionError) { | ||
| 3541 | /**@type {SlashCommandExecutionError}*/ | ||
| 3542 | const ex = e; | ||
| 3543 | const toast = ` | ||
| 3544 | <div>${ex.message}</div> | ||
| 3545 | <div>Line: ${ex.line} Column: ${ex.column}</div> | ||
| 3546 | <pre style="text-align:left;">${ex.hint}</pre> | ||
| 3547 | `; | ||
| 3548 | const clickHint = '<p>Click to see details</p>'; | ||
| 3549 | toastr.error( | ||
| 3550 | `${toast}${clickHint}`, | ||
| 3551 | 'SlashCommandExecutionError', | ||
| 3552 | { escapeHtml: false, timeOut: 10000, onclick: () => callPopup(toast, 'text') }, | ||
| 3553 | ); | ||
| 3554 | } else { | ||
| 3495 | toastr.error(result.errorMessage); | 3555 | toastr.error(result.errorMessage); |
| 3496 | } | 3556 | } |
| 3557 | } | ||
| 3497 | } finally { | 3558 | } finally { |
| 3498 | delay(1000).then(() => clearCommandProgressDebounced()); | 3559 | delay(1000).then(() => clearCommandProgressDebounced()); |
| 3499 | 3560 | ||
| @@ -3520,7 +3581,9 @@ async function executeSlashCommandsWithOptions(text, options = {}) { | |||
| 3520 | handleExecutionErrors: false, | 3581 | handleExecutionErrors: false, |
| 3521 | parserFlags: null, | 3582 | parserFlags: null, |
| 3522 | abortController: null, | 3583 | abortController: null, |
| 3584 | debugController: null, | ||
| 3523 | onProgress: null, | 3585 | onProgress: null, |
| 3586 | source: null, | ||
| 3524 | }, options); | 3587 | }, options); |
| 3525 | 3588 | ||
| 3526 | let closure; | 3589 | let closure; |
| @@ -3528,6 +3591,8 @@ async function executeSlashCommandsWithOptions(text, options = {}) { | |||
| 3528 | closure = parser.parse(text, true, options.parserFlags, options.abortController ?? new SlashCommandAbortController()); | 3591 | closure = parser.parse(text, true, options.parserFlags, options.abortController ?? new SlashCommandAbortController()); |
| 3529 | closure.scope.parent = options.scope; | 3592 | closure.scope.parent = options.scope; |
| 3530 | closure.onProgress = options.onProgress; | 3593 | closure.onProgress = options.onProgress; |
| 3594 | closure.debugController = options.debugController; | ||
| 3595 | closure.source = options.source; | ||
| 3531 | } catch (e) { | 3596 | } catch (e) { |
| 3532 | if (options.handleParserErrors && e instanceof SlashCommandParserError) { | 3597 | if (options.handleParserErrors && e instanceof SlashCommandParserError) { |
| 3533 | /**@type {SlashCommandParserError}*/ | 3598 | /**@type {SlashCommandParserError}*/ |
| @@ -3559,7 +3624,23 @@ async function executeSlashCommandsWithOptions(text, options = {}) { | |||
| 3559 | return result; | 3624 | return result; |
| 3560 | } catch (e) { | 3625 | } catch (e) { |
| 3561 | if (options.handleExecutionErrors) { | 3626 | if (options.handleExecutionErrors) { |
| 3627 | if (e instanceof SlashCommandExecutionError) { | ||
| 3628 | /**@type {SlashCommandExecutionError}*/ | ||
| 3629 | const ex = e; | ||
| 3630 | const toast = ` | ||
| 3631 | <div>${ex.message}</div> | ||
| 3632 | <div>Line: ${ex.line} Column: ${ex.column}</div> | ||
| 3633 | <pre style="text-align:left;">${ex.hint}</pre> | ||
| 3634 | `; | ||
| 3635 | const clickHint = '<p>Click to see details</p>'; | ||
| 3636 | toastr.error( | ||
| 3637 | `${toast}${clickHint}`, | ||
| 3638 | 'SlashCommandExecutionError', | ||
| 3639 | { escapeHtml: false, timeOut: 10000, onclick: () => callPopup(toast, 'text') }, | ||
| 3640 | ); | ||
| 3641 | } else { | ||
| 3562 | toastr.error(e.message); | 3642 | toastr.error(e.message); |
| 3643 | } | ||
| 3563 | const result = new SlashCommandClosureResult(); | 3644 | const result = new SlashCommandClosureResult(); |
| 3564 | result.isError = true; | 3645 | result.isError = true; |
| 3565 | result.errorMessage = e.message; | 3646 | result.errorMessage = e.message; |
| @@ -3596,6 +3677,7 @@ async function executeSlashCommands(text, handleParserErrors = true, scope = nul | |||
| 3596 | * | 3677 | * |
| 3597 | * @param {HTMLTextAreaElement} textarea The textarea to receive autocomplete | 3678 | * @param {HTMLTextAreaElement} textarea The textarea to receive autocomplete |
| 3598 | * @param {Boolean} isFloating Whether to show the auto complete as a floating window (e.g., large QR editor) | 3679 | * @param {Boolean} isFloating Whether to show the auto complete as a floating window (e.g., large QR editor) |
| 3680 | * @returns {Promise<AutoComplete>} | ||
| 3599 | */ | 3681 | */ |
| 3600 | export async function setSlashCommandAutoComplete(textarea, isFloating = false) { | 3682 | export async function setSlashCommandAutoComplete(textarea, isFloating = false) { |
| 3601 | function canUseNegativeLookbehind() { | 3683 | function canUseNegativeLookbehind() { |
| @@ -3619,6 +3701,7 @@ export async function setSlashCommandAutoComplete(textarea, isFloating = false) | |||
| 3619 | async (text, index) => await parser.getNameAt(text, index), | 3701 | async (text, index) => await parser.getNameAt(text, index), |
| 3620 | isFloating, | 3702 | isFloating, |
| 3621 | ); | 3703 | ); |
| 3704 | return ac; | ||
| 3622 | } | 3705 | } |
| 3623 | /**@type {HTMLTextAreaElement} */ | 3706 | /**@type {HTMLTextAreaElement} */ |
| 3624 | const sendTextarea = document.querySelector('#send_textarea'); | 3707 | const sendTextarea = document.querySelector('#send_textarea'); |
| @@ -1,6 +1,7 @@ | |||
| 1 | import { SlashCommandAbortController } from './SlashCommandAbortController.js'; | 1 | import { SlashCommandAbortController } from './SlashCommandAbortController.js'; |
| 2 | import { SlashCommandArgument, SlashCommandNamedArgument } from './SlashCommandArgument.js'; | 2 | import { SlashCommandArgument, SlashCommandNamedArgument } from './SlashCommandArgument.js'; |
| 3 | import { SlashCommandClosure } from './SlashCommandClosure.js'; | 3 | import { SlashCommandClosure } from './SlashCommandClosure.js'; |
| 4 | import { SlashCommandDebugController } from './SlashCommandDebugController.js'; | ||
| 4 | import { PARSER_FLAG } from './SlashCommandParser.js'; | 5 | import { PARSER_FLAG } from './SlashCommandParser.js'; |
| 5 | import { SlashCommandScope } from './SlashCommandScope.js'; | 6 | import { SlashCommandScope } from './SlashCommandScope.js'; |
| 6 | 7 | ||
| @@ -12,6 +13,7 @@ import { SlashCommandScope } from './SlashCommandScope.js'; | |||
| 12 | * _scope:SlashCommandScope, | 13 | * _scope:SlashCommandScope, |
| 13 | * _parserFlags:{[id:PARSER_FLAG]:boolean}, | 14 | * _parserFlags:{[id:PARSER_FLAG]:boolean}, |
| 14 | * _abortController:SlashCommandAbortController, | 15 | * _abortController:SlashCommandAbortController, |
| 16 | * _debugController:SlashCommandDebugController, | ||
| 15 | * _hasUnnamedArgument:boolean, | 17 | * _hasUnnamedArgument:boolean, |
| 16 | * [id:string]:string|SlashCommandClosure, | 18 | * [id:string]:string|SlashCommandClosure, |
| 17 | * }} NamedArguments | 19 | * }} NamedArguments |
| @@ -36,6 +38,7 @@ export class SlashCommand { | |||
| 36 | * @param {(namedArguments:NamedArguments|NamedArgumentsCapture, unnamedArguments:string|SlashCommandClosure|(string|SlashCommandClosure)[])=>string|SlashCommandClosure|Promise<string|SlashCommandClosure>} [props.callback] | 38 | * @param {(namedArguments:NamedArguments|NamedArgumentsCapture, unnamedArguments:string|SlashCommandClosure|(string|SlashCommandClosure)[])=>string|SlashCommandClosure|Promise<string|SlashCommandClosure>} [props.callback] |
| 37 | * @param {string} [props.helpString] | 39 | * @param {string} [props.helpString] |
| 38 | * @param {boolean} [props.splitUnnamedArgument] | 40 | * @param {boolean} [props.splitUnnamedArgument] |
| 41 | * @param {Number} [props.splitUnnamedArgumentCount] | ||
| 39 | * @param {string[]} [props.aliases] | 42 | * @param {string[]} [props.aliases] |
| 40 | * @param {string} [props.returns] | 43 | * @param {string} [props.returns] |
| 41 | * @param {SlashCommandNamedArgument[]} [props.namedArgumentList] | 44 | * @param {SlashCommandNamedArgument[]} [props.namedArgumentList] |
| @@ -50,9 +53,10 @@ export class SlashCommand { | |||
| 50 | 53 | ||
| 51 | 54 | ||
| 52 | /**@type {string}*/ name; | 55 | /**@type {string}*/ name; |
| 53 | /**@type {(namedArguments:{_pipe:string|SlashCommandClosure, _scope:SlashCommandScope, _abortController:SlashCommandAbortController, [id:string]:string|SlashCommandClosure}, unnamedArguments:string|SlashCommandClosure|(string|SlashCommandClosure)[])=>string|SlashCommandClosure|Promise<string|SlashCommandClosure>}*/ callback; | 56 | /**@type {(namedArguments:{_scope:SlashCommandScope, _abortController:SlashCommandAbortController, [id:string]:string|SlashCommandClosure}, unnamedArguments:string|SlashCommandClosure|(string|SlashCommandClosure)[])=>string|SlashCommandClosure|Promise<string|SlashCommandClosure>}*/ callback; |
| 54 | /**@type {string}*/ helpString; | 57 | /**@type {string}*/ helpString; |
| 55 | /**@type {boolean}*/ splitUnnamedArgument = false; | 58 | /**@type {boolean}*/ splitUnnamedArgument = false; |
| 59 | /**@type {Number}*/ splitUnnamedArgumentCount; | ||
| 56 | /**@type {string[]}*/ aliases = []; | 60 | /**@type {string[]}*/ aliases = []; |
| 57 | /**@type {string}*/ returns; | 61 | /**@type {string}*/ returns; |
| 58 | /**@type {SlashCommandNamedArgument[]}*/ namedArgumentList = []; | 62 | /**@type {SlashCommandNamedArgument[]}*/ namedArgumentList = []; |
| @@ -61,6 +65,10 @@ export class SlashCommand { | |||
| 61 | /**@type {Object.<string, HTMLElement>}*/ helpCache = {}; | 65 | /**@type {Object.<string, HTMLElement>}*/ helpCache = {}; |
| 62 | /**@type {Object.<string, DocumentFragment>}*/ helpDetailsCache = {}; | 66 | /**@type {Object.<string, DocumentFragment>}*/ helpDetailsCache = {}; |
| 63 | 67 | ||
| 68 | /**@type {boolean}*/ isExtension = false; | ||
| 69 | /**@type {boolean}*/ isThirdParty = false; | ||
| 70 | /**@type {string}*/ source; | ||
| 71 | |||
| 64 | renderHelpItem(key = null) { | 72 | renderHelpItem(key = null) { |
| 65 | key = key ?? this.name; | 73 | key = key ?? this.name; |
| 66 | if (!this.helpCache[key]) { | 74 | if (!this.helpCache[key]) { |
| @@ -225,12 +233,35 @@ export class SlashCommand { | |||
| 225 | const aliasList = [cmd.name, ...(cmd.aliases ?? [])].filter(it=>it != key); | 233 | const aliasList = [cmd.name, ...(cmd.aliases ?? [])].filter(it=>it != key); |
| 226 | const specs = document.createElement('div'); { | 234 | const specs = document.createElement('div'); { |
| 227 | specs.classList.add('specs'); | 235 | specs.classList.add('specs'); |
| 236 | const head = document.createElement('div'); { | ||
| 237 | head.classList.add('head'); | ||
| 228 | const name = document.createElement('div'); { | 238 | const name = document.createElement('div'); { |
| 229 | name.classList.add('name'); | 239 | name.classList.add('name'); |
| 230 | name.classList.add('monospace'); | 240 | name.classList.add('monospace'); |
| 231 | name.title = 'command name'; | 241 | name.title = 'command name'; |
| 232 | name.textContent = `/${key}`; | 242 | name.textContent = `/${key}`; |
| 233 | specs.append(name); | 243 | head.append(name); |
| 244 | } | ||
| 245 | const src = document.createElement('div'); { | ||
| 246 | src.classList.add('source'); | ||
| 247 | src.classList.add('fa-solid'); | ||
| 248 | if (this.isExtension) { | ||
| 249 | src.classList.add('isExtension'); | ||
| 250 | src.classList.add('fa-cubes'); | ||
| 251 | if (this.isThirdParty) src.classList.add('isThirdParty'); | ||
| 252 | else src.classList.add('isCore'); | ||
| 253 | } else { | ||
| 254 | src.classList.add('isCore'); | ||
| 255 | src.classList.add('fa-star-of-life'); | ||
| 256 | } | ||
| 257 | src.title = [ | ||
| 258 | this.isExtension ? 'Extension' : 'Core', | ||
| 259 | this.isThirdParty ? 'Third Party' : (this.isExtension ? 'Core' : null), | ||
| 260 | this.source, | ||
| 261 | ].filter(it=>it).join('\n'); | ||
| 262 | head.append(src); | ||
| 263 | } | ||
| 264 | specs.append(head); | ||
| 234 | } | 265 | } |
| 235 | const body = document.createElement('div'); { | 266 | const body = document.createElement('div'); { |
| 236 | body.classList.add('body'); | 267 | body.classList.add('body'); |
| @@ -303,6 +334,8 @@ export class SlashCommand { | |||
| 303 | for (const arg of unnamedArguments) { | 334 | for (const arg of unnamedArguments) { |
| 304 | const listItem = document.createElement('li'); { | 335 | const listItem = document.createElement('li'); { |
| 305 | listItem.classList.add('argumentItem'); | 336 | listItem.classList.add('argumentItem'); |
| 337 | const argSpec = document.createElement('div'); { | ||
| 338 | argSpec.classList.add('argumentSpec'); | ||
| 306 | const argItem = document.createElement('div'); { | 339 | const argItem = document.createElement('div'); { |
| 307 | argItem.classList.add('argument'); | 340 | argItem.classList.add('argument'); |
| 308 | argItem.classList.add('unnamedArgument'); | 341 | argItem.classList.add('unnamedArgument'); |
| @@ -336,7 +369,17 @@ export class SlashCommand { | |||
| 336 | argItem.append(types); | 369 | argItem.append(types); |
| 337 | } | 370 | } |
| 338 | } | 371 | } |
| 339 | listItem.append(argItem); | 372 | argSpec.append(argItem); |
| 373 | } | ||
| 374 | if (arg.defaultValue !== null) { | ||
| 375 | const argDefault = document.createElement('div'); { | ||
| 376 | argDefault.classList.add('argument-default'); | ||
| 377 | argDefault.title = 'default value'; | ||
| 378 | argDefault.textContent = arg.defaultValue.toString(); | ||
| 379 | argSpec.append(argDefault); | ||
| 380 | } | ||
| 381 | } | ||
| 382 | listItem.append(argSpec); | ||
| 340 | } | 383 | } |
| 341 | const desc = document.createElement('div'); { | 384 | const desc = document.createElement('div'); { |
| 342 | desc.classList.add('argument-description'); | 385 | desc.classList.add('argument-description'); |
| @@ -2,6 +2,9 @@ import { SlashCommandClosure } from './SlashCommandClosure.js'; | |||
| 2 | import { commonEnumProviders } from './SlashCommandCommonEnumsProvider.js'; | 2 | import { commonEnumProviders } from './SlashCommandCommonEnumsProvider.js'; |
| 3 | import { SlashCommandEnumValue } from './SlashCommandEnumValue.js'; | 3 | import { SlashCommandEnumValue } from './SlashCommandEnumValue.js'; |
| 4 | import { SlashCommandExecutor } from './SlashCommandExecutor.js'; | 4 | import { SlashCommandExecutor } from './SlashCommandExecutor.js'; |
| 5 | import { SlashCommandScope } from './SlashCommandScope.js'; | ||
| 6 | |||
| 7 | |||
| 5 | 8 | ||
| 6 | /**@readonly*/ | 9 | /**@readonly*/ |
| 7 | /**@enum {string}*/ | 10 | /**@enum {string}*/ |
| @@ -27,7 +30,7 @@ export class SlashCommandArgument { | |||
| 27 | * @param {boolean} [props.acceptsMultiple=false] default: false - whether argument accepts multiple values | 30 | * @param {boolean} [props.acceptsMultiple=false] default: false - whether argument accepts multiple values |
| 28 | * @param {string|SlashCommandClosure} [props.defaultValue=null] default value if no value is provided | 31 | * @param {string|SlashCommandClosure} [props.defaultValue=null] default value if no value is provided |
| 29 | * @param {string|SlashCommandEnumValue|(string|SlashCommandEnumValue)[]} [props.enumList=[]] list of accepted values | 32 | * @param {string|SlashCommandEnumValue|(string|SlashCommandEnumValue)[]} [props.enumList=[]] list of accepted values |
| 30 | * @param {(executor:SlashCommandExecutor)=>SlashCommandEnumValue[]} [props.enumProvider=null] function that returns auto complete options | 33 | * @param {(executor:SlashCommandExecutor, scope:SlashCommandScope)=>SlashCommandEnumValue[]} [props.enumProvider=null] function that returns auto complete options |
| 31 | * @param {boolean} [props.forceEnum=false] default: false - whether the input must match one of the enum values | 34 | * @param {boolean} [props.forceEnum=false] default: false - whether the input must match one of the enum values |
| 32 | */ | 35 | */ |
| 33 | static fromProps(props) { | 36 | static fromProps(props) { |
| @@ -49,7 +52,7 @@ export class SlashCommandArgument { | |||
| 49 | /**@type {boolean}*/ acceptsMultiple = false; | 52 | /**@type {boolean}*/ acceptsMultiple = false; |
| 50 | /**@type {string|SlashCommandClosure}*/ defaultValue; | 53 | /**@type {string|SlashCommandClosure}*/ defaultValue; |
| 51 | /**@type {SlashCommandEnumValue[]}*/ enumList = []; | 54 | /**@type {SlashCommandEnumValue[]}*/ enumList = []; |
| 52 | /**@type {(executor:SlashCommandExecutor)=>SlashCommandEnumValue[]}*/ enumProvider = null; | 55 | /**@type {(executor:SlashCommandExecutor, scope:SlashCommandScope)=>SlashCommandEnumValue[]}*/ enumProvider = null; |
| 53 | /**@type {boolean}*/ forceEnum = false; | 56 | /**@type {boolean}*/ forceEnum = false; |
| 54 | 57 | ||
| 55 | /** | 58 | /** |
| @@ -57,7 +60,7 @@ export class SlashCommandArgument { | |||
| 57 | * @param {ARGUMENT_TYPE|ARGUMENT_TYPE[]} types | 60 | * @param {ARGUMENT_TYPE|ARGUMENT_TYPE[]} types |
| 58 | * @param {string|SlashCommandClosure} defaultValue | 61 | * @param {string|SlashCommandClosure} defaultValue |
| 59 | * @param {string|SlashCommandEnumValue|(string|SlashCommandEnumValue)[]} enums | 62 | * @param {string|SlashCommandEnumValue|(string|SlashCommandEnumValue)[]} enums |
| 60 | * @param {(executor:SlashCommandExecutor)=>SlashCommandEnumValue[]} enumProvider function that returns auto complete options | 63 | * @param {(executor:SlashCommandExecutor, scope:SlashCommandScope)=>SlashCommandEnumValue[]} enumProvider function that returns auto complete options |
| 61 | */ | 64 | */ |
| 62 | constructor(description, types, isRequired = false, acceptsMultiple = false, defaultValue = null, enums = [], enumProvider = null, forceEnum = false) { | 65 | constructor(description, types, isRequired = false, acceptsMultiple = false, defaultValue = null, enums = [], enumProvider = null, forceEnum = false) { |
| 63 | this.description = description; | 66 | this.description = description; |
| @@ -89,7 +92,7 @@ export class SlashCommandNamedArgument extends SlashCommandArgument { | |||
| 89 | * @param {boolean} [props.acceptsMultiple=false] default: false - whether argument accepts multiple values | 92 | * @param {boolean} [props.acceptsMultiple=false] default: false - whether argument accepts multiple values |
| 90 | * @param {string|SlashCommandClosure} [props.defaultValue=null] default value if no value is provided | 93 | * @param {string|SlashCommandClosure} [props.defaultValue=null] default value if no value is provided |
| 91 | * @param {string|SlashCommandEnumValue|(string|SlashCommandEnumValue)[]} [props.enumList=[]] list of accepted values | 94 | * @param {string|SlashCommandEnumValue|(string|SlashCommandEnumValue)[]} [props.enumList=[]] list of accepted values |
| 92 | * @param {(executor:SlashCommandExecutor)=>SlashCommandEnumValue[]} [props.enumProvider=null] function that returns auto complete options | 95 | * @param {(executor:SlashCommandExecutor, scope:SlashCommandScope)=>SlashCommandEnumValue[]} [props.enumProvider=null] function that returns auto complete options |
| 93 | * @param {boolean} [props.forceEnum=false] default: false - whether the input must match one of the enum values | 96 | * @param {boolean} [props.forceEnum=false] default: false - whether the input must match one of the enum values |
| 94 | */ | 97 | */ |
| 95 | static fromProps(props) { | 98 | static fromProps(props) { |
| @@ -119,7 +122,7 @@ export class SlashCommandNamedArgument extends SlashCommandArgument { | |||
| 119 | * @param {string|SlashCommandClosure} [defaultValue=null] | 122 | * @param {string|SlashCommandClosure} [defaultValue=null] |
| 120 | * @param {string|SlashCommandEnumValue|(string|SlashCommandEnumValue)[]} [enums=[]] | 123 | * @param {string|SlashCommandEnumValue|(string|SlashCommandEnumValue)[]} [enums=[]] |
| 121 | * @param {string[]} [aliases=[]] | 124 | * @param {string[]} [aliases=[]] |
| 122 | * @param {(executor:SlashCommandExecutor)=>SlashCommandEnumValue[]} [enumProvider=null] function that returns auto complete options | 125 | * @param {(executor:SlashCommandExecutor, scope:SlashCommandScope)=>SlashCommandEnumValue[]} [enumProvider=null] function that returns auto complete options |
| 123 | * @param {boolean} [forceEnum=false] | 126 | * @param {boolean} [forceEnum=false] |
| 124 | */ | 127 | */ |
| 125 | constructor(name, description, types, isRequired = false, acceptsMultiple = false, defaultValue = null, enums = [], aliases = [], enumProvider = null, forceEnum = false) { | 128 | constructor(name, description, types, isRequired = false, acceptsMultiple = false, defaultValue = null, enums = [], aliases = [], enumProvider = null, forceEnum = false) { |
| @@ -8,15 +8,18 @@ import { SlashCommandCommandAutoCompleteOption } from './SlashCommandCommandAuto | |||
| 8 | import { SlashCommandEnumAutoCompleteOption } from './SlashCommandEnumAutoCompleteOption.js'; | 8 | import { SlashCommandEnumAutoCompleteOption } from './SlashCommandEnumAutoCompleteOption.js'; |
| 9 | import { SlashCommandExecutor } from './SlashCommandExecutor.js'; | 9 | import { SlashCommandExecutor } from './SlashCommandExecutor.js'; |
| 10 | import { SlashCommandNamedArgumentAutoCompleteOption } from './SlashCommandNamedArgumentAutoCompleteOption.js'; | 10 | import { SlashCommandNamedArgumentAutoCompleteOption } from './SlashCommandNamedArgumentAutoCompleteOption.js'; |
| 11 | import { SlashCommandScope } from './SlashCommandScope.js'; | ||
| 11 | 12 | ||
| 12 | export class SlashCommandAutoCompleteNameResult extends AutoCompleteNameResult { | 13 | export class SlashCommandAutoCompleteNameResult extends AutoCompleteNameResult { |
| 13 | /**@type {SlashCommandExecutor}*/ executor; | 14 | /**@type {SlashCommandExecutor}*/ executor; |
| 15 | /**@type {SlashCommandScope}*/ scope; | ||
| 14 | 16 | ||
| 15 | /** | 17 | /** |
| 16 | * @param {SlashCommandExecutor} executor | 18 | * @param {SlashCommandExecutor} executor |
| 19 | * @param {SlashCommandScope} scope | ||
| 17 | * @param {Object.<string,SlashCommand>} commands | 20 | * @param {Object.<string,SlashCommand>} commands |
| 18 | */ | 21 | */ |
| 19 | constructor(executor, commands) { | 22 | constructor(executor, scope, commands) { |
| 20 | super( | 23 | super( |
| 21 | executor.name, | 24 | executor.name, |
| 22 | executor.start, | 25 | executor.start, |
| @@ -29,6 +32,7 @@ export class SlashCommandAutoCompleteNameResult extends AutoCompleteNameResult { | |||
| 29 | ()=>'No slash commands found!', | 32 | ()=>'No slash commands found!', |
| 30 | ); | 33 | ); |
| 31 | this.executor = executor; | 34 | this.executor = executor; |
| 35 | this.scope = scope; | ||
| 32 | } | 36 | } |
| 33 | 37 | ||
| 34 | getSecondaryNameAt(text, index, isSelect) { | 38 | getSecondaryNameAt(text, index, isSelect) { |
| @@ -86,8 +90,8 @@ export class SlashCommandAutoCompleteNameResult extends AutoCompleteNameResult { | |||
| 86 | } | 90 | } |
| 87 | } else if (unamedArgLength > 0 && index >= this.executor.startUnnamedArgs && index <= this.executor.endUnnamedArgs) { | 91 | } else if (unamedArgLength > 0 && index >= this.executor.startUnnamedArgs && index <= this.executor.endUnnamedArgs) { |
| 88 | // cursor is somewhere within the unnamed arguments | 92 | // cursor is somewhere within the unnamed arguments |
| 89 | //TODO if index is in first array item and that is a string, treat it as an unfinished named arg | 93 | // if index is in first array item and that is a string, treat it as an unfinished named arg |
| 90 | if (typeof this.executor.unnamedArgumentList[0].value == 'string') { | 94 | if (typeof this.executor.unnamedArgumentList[0]?.value == 'string') { |
| 91 | if (index <= this.executor.startUnnamedArgs + this.executor.unnamedArgumentList[0].value.length) { | 95 | if (index <= this.executor.startUnnamedArgs + this.executor.unnamedArgumentList[0].value.length) { |
| 92 | name = this.executor.unnamedArgumentList[0].value.slice(0, index - this.executor.startUnnamedArgs); | 96 | name = this.executor.unnamedArgumentList[0].value.slice(0, index - this.executor.startUnnamedArgs); |
| 93 | start = this.executor.startUnnamedArgs; | 97 | start = this.executor.startUnnamedArgs; |
| @@ -103,7 +107,7 @@ export class SlashCommandAutoCompleteNameResult extends AutoCompleteNameResult { | |||
| 103 | 107 | ||
| 104 | if (name.includes('=') && cmdArg) { | 108 | if (name.includes('=') && cmdArg) { |
| 105 | // if cursor is already behind "=" check for enums | 109 | // if cursor is already behind "=" check for enums |
| 106 | const enumList = cmdArg?.enumProvider?.(this.executor) ?? cmdArg?.enumList; | 110 | const enumList = cmdArg?.enumProvider?.(this.executor, this.scope) ?? cmdArg?.enumList; |
| 107 | if (cmdArg && enumList?.length) { | 111 | if (cmdArg && enumList?.length) { |
| 108 | if (isSelect && enumList.find(it=>it.value == value) && argAssign && argAssign.end == index) { | 112 | if (isSelect && enumList.find(it=>it.value == value) && argAssign && argAssign.end == index) { |
| 109 | return null; | 113 | return null; |
| @@ -111,7 +115,7 @@ export class SlashCommandAutoCompleteNameResult extends AutoCompleteNameResult { | |||
| 111 | const result = new AutoCompleteSecondaryNameResult( | 115 | const result = new AutoCompleteSecondaryNameResult( |
| 112 | value, | 116 | value, |
| 113 | start + name.length, | 117 | start + name.length, |
| 114 | enumList.map(it=>new SlashCommandEnumAutoCompleteOption(this.executor.command, it)), | 118 | enumList.map(it=>SlashCommandEnumAutoCompleteOption.from(this.executor.command, it)), |
| 115 | true, | 119 | true, |
| 116 | ); | 120 | ); |
| 117 | result.isRequired = true; | 121 | result.isRequired = true; |
| @@ -150,7 +154,10 @@ export class SlashCommandAutoCompleteNameResult extends AutoCompleteNameResult { | |||
| 150 | if (idx > -1) { | 154 | if (idx > -1) { |
| 151 | argAssign = this.executor.unnamedArgumentList[idx]; | 155 | argAssign = this.executor.unnamedArgumentList[idx]; |
| 152 | cmdArg = this.executor.command.unnamedArgumentList[idx]; | 156 | cmdArg = this.executor.command.unnamedArgumentList[idx]; |
| 153 | const enumList = cmdArg?.enumProvider?.(this.executor) ?? cmdArg?.enumList; | 157 | if (cmdArg === undefined && this.executor.command.unnamedArgumentList.slice(-1)[0]?.acceptsMultiple) { |
| 158 | cmdArg = this.executor.command.unnamedArgumentList.slice(-1)[0]; | ||
| 159 | } | ||
| 160 | const enumList = cmdArg?.enumProvider?.(this.executor, this.scope) ?? cmdArg?.enumList; | ||
| 154 | if (cmdArg && enumList.length > 0) { | 161 | if (cmdArg && enumList.length > 0) { |
| 155 | value = argAssign.value.toString().slice(0, index - argAssign.start); | 162 | value = argAssign.value.toString().slice(0, index - argAssign.start); |
| 156 | start = argAssign.start; | 163 | start = argAssign.start; |
| @@ -161,23 +168,26 @@ export class SlashCommandAutoCompleteNameResult extends AutoCompleteNameResult { | |||
| 161 | value = ''; | 168 | value = ''; |
| 162 | start = index; | 169 | start = index; |
| 163 | cmdArg = notProvidedArguments[0]; | 170 | cmdArg = notProvidedArguments[0]; |
| 171 | if (cmdArg === undefined && this.executor.command.unnamedArgumentList.slice(-1)[0]?.acceptsMultiple) { | ||
| 172 | cmdArg = this.executor.command.unnamedArgumentList.slice(-1)[0]; | ||
| 173 | } | ||
| 164 | } | 174 | } |
| 165 | } else { | 175 | } else { |
| 166 | return null; | 176 | return null; |
| 167 | } | 177 | } |
| 168 | 178 | ||
| 169 | const enumList = cmdArg?.enumProvider?.(this.executor) ?? cmdArg?.enumList; | 179 | const enumList = cmdArg?.enumProvider?.(this.executor, this.scope) ?? cmdArg?.enumList; |
| 170 | if (cmdArg == null || enumList.length == 0) return null; | 180 | if (cmdArg == null || enumList.length == 0) return null; |
| 171 | 181 | ||
| 172 | const result = new AutoCompleteSecondaryNameResult( | 182 | const result = new AutoCompleteSecondaryNameResult( |
| 173 | value, | 183 | value, |
| 174 | start, | 184 | start, |
| 175 | enumList.map(it=>new SlashCommandEnumAutoCompleteOption(this.executor.command, it)), | 185 | enumList.map(it=>SlashCommandEnumAutoCompleteOption.from(this.executor.command, it)), |
| 176 | false, | 186 | false, |
| 177 | ); | 187 | ); |
| 178 | const isCompleteValue = enumList.find(it=>it.value == value); | 188 | const isCompleteValue = enumList.find(it=>it.value == value); |
| 179 | const isSelectedValue = isSelect && isCompleteValue; | 189 | const isSelectedValue = isSelect && isCompleteValue; |
| 180 | result.isRequired = cmdArg.isRequired && !isSelectedValue && !isCompleteValue; | 190 | result.isRequired = cmdArg.isRequired && !isSelectedValue; |
| 181 | result.forceMatch = cmdArg.forceEnum; | 191 | result.forceMatch = cmdArg.forceEnum; |
| 182 | return result; | 192 | return result; |
| 183 | } | 193 | } |
| @@ -0,0 +1,7 @@ | |||
| 1 | import { SlashCommandExecutor } from './SlashCommandExecutor.js'; | ||
| 2 | |||
| 3 | export class SlashCommandBreak extends SlashCommandExecutor { | ||
| 4 | get value() { | ||
| 5 | return this.unnamedArgumentList[0]?.value; | ||
| 6 | } | ||
| 7 | } | ||
| @@ -0,0 +1,7 @@ | |||
| 1 | export class SlashCommandBreakController { | ||
| 2 | /**@type {boolean} */ isBreak = false; | ||
| 3 | |||
| 4 | break() { | ||
| 5 | this.isBreak = true; | ||
| 6 | } | ||
| 7 | } | ||
| @@ -0,0 +1,3 @@ | |||
| 1 | import { SlashCommandExecutor } from './SlashCommandExecutor.js'; | ||
| 2 | |||
| 3 | export class SlashCommandBreakPoint extends SlashCommandExecutor {} | ||
| @@ -1,9 +1,13 @@ | |||
| 1 | import { substituteParams } from '../../script.js'; | 1 | import { substituteParams } from '../../script.js'; |
| 2 | import { delay, escapeRegex } from '../utils.js'; | 2 | import { delay, escapeRegex, uuidv4 } from '../utils.js'; |
| 3 | import { SlashCommand } from './SlashCommand.js'; | 3 | import { SlashCommand } from './SlashCommand.js'; |
| 4 | import { SlashCommandAbortController } from './SlashCommandAbortController.js'; | 4 | import { SlashCommandAbortController } from './SlashCommandAbortController.js'; |
| 5 | import { SlashCommandClosureExecutor } from './SlashCommandClosureExecutor.js'; | 5 | import { SlashCommandBreak } from './SlashCommandBreak.js'; |
| 6 | import { SlashCommandBreakController } from './SlashCommandBreakController.js'; | ||
| 7 | import { SlashCommandBreakPoint } from './SlashCommandBreakPoint.js'; | ||
| 6 | import { SlashCommandClosureResult } from './SlashCommandClosureResult.js'; | 8 | import { SlashCommandClosureResult } from './SlashCommandClosureResult.js'; |
| 9 | import { SlashCommandDebugController } from './SlashCommandDebugController.js'; | ||
| 10 | import { SlashCommandExecutionError } from './SlashCommandExecutionError.js'; | ||
| 7 | import { SlashCommandExecutor } from './SlashCommandExecutor.js'; | 11 | import { SlashCommandExecutor } from './SlashCommandExecutor.js'; |
| 8 | import { SlashCommandNamedArgumentAssignment } from './SlashCommandNamedArgumentAssignment.js'; | 12 | import { SlashCommandNamedArgumentAssignment } from './SlashCommandNamedArgumentAssignment.js'; |
| 9 | import { SlashCommandScope } from './SlashCommandScope.js'; | 13 | import { SlashCommandScope } from './SlashCommandScope.js'; |
| @@ -17,8 +21,20 @@ export class SlashCommandClosure { | |||
| 17 | /**@type {SlashCommandNamedArgumentAssignment[]}*/ providedArgumentList = []; | 21 | /**@type {SlashCommandNamedArgumentAssignment[]}*/ providedArgumentList = []; |
| 18 | /**@type {SlashCommandExecutor[]}*/ executorList = []; | 22 | /**@type {SlashCommandExecutor[]}*/ executorList = []; |
| 19 | /**@type {SlashCommandAbortController}*/ abortController; | 23 | /**@type {SlashCommandAbortController}*/ abortController; |
| 24 | /**@type {SlashCommandBreakController}*/ breakController; | ||
| 25 | /**@type {SlashCommandDebugController}*/ debugController; | ||
| 20 | /**@type {(done:number, total:number)=>void}*/ onProgress; | 26 | /**@type {(done:number, total:number)=>void}*/ onProgress; |
| 21 | /**@type {string}*/ rawText; | 27 | /**@type {string}*/ rawText; |
| 28 | /**@type {string}*/ fullText; | ||
| 29 | /**@type {string}*/ parserContext; | ||
| 30 | /**@type {string}*/ #source = uuidv4(); | ||
| 31 | get source() { return this.#source; } | ||
| 32 | set source(value) { | ||
| 33 | this.#source = value; | ||
| 34 | for (const executor of this.executorList) { | ||
| 35 | executor.source = value; | ||
| 36 | } | ||
| 37 | } | ||
| 22 | 38 | ||
| 23 | /**@type {number}*/ | 39 | /**@type {number}*/ |
| 24 | get commandCount() { | 40 | get commandCount() { |
| @@ -30,7 +46,7 @@ export class SlashCommandClosure { | |||
| 30 | } | 46 | } |
| 31 | 47 | ||
| 32 | toString() { | 48 | toString() { |
| 33 | return '[Closure]'; | 49 | return `[Closure]${this.executeNow ? '()' : ''}`; |
| 34 | } | 50 | } |
| 35 | 51 | ||
| 36 | /** | 52 | /** |
| @@ -43,16 +59,37 @@ export class SlashCommandClosure { | |||
| 43 | let isList = false; | 59 | let isList = false; |
| 44 | let listValues = []; | 60 | let listValues = []; |
| 45 | scope = scope ?? this.scope; | 61 | scope = scope ?? this.scope; |
| 46 | const macros = scope.macroList.map(it=>escapeRegex(it.key)).join('|'); | 62 | const escapeMacro = (it, isAnchored = false)=>{ |
| 47 | const re = new RegExp(`({{pipe}})|(?:{{var::([^\\s]+?)(?:::((?!}}).+))?}})|(?:{{(${macros})}})`); | 63 | const regexText = escapeRegex(it.key.replace(/\*/g, '~~~WILDCARD~~~')) |
| 64 | .replaceAll('~~~WILDCARD~~~', '(?:(?:(?!(?:::|}})).)*)') | ||
| 65 | ; | ||
| 66 | if (isAnchored) { | ||
| 67 | return `^${regexText}$`; | ||
| 68 | } | ||
| 69 | return regexText; | ||
| 70 | }; | ||
| 71 | const macroList = scope.macroList.toSorted((a,b)=>{ | ||
| 72 | if (a.key.includes('*') && !b.key.includes('*')) return 1; | ||
| 73 | if (!a.key.includes('*') && b.key.includes('*')) return -1; | ||
| 74 | if (a.key.includes('*') && b.key.includes('*')) return b.key.indexOf('*') - a.key.indexOf('*'); | ||
| 75 | return 0; | ||
| 76 | }); | ||
| 77 | const macros = macroList.map(it=>escapeMacro(it)).join('|'); | ||
| 78 | const re = new RegExp(`(?<pipe>{{pipe}})|(?:{{var::(?<var>[^\\s]+?)(?:::(?<varIndex>(?!}}).+))?}})|(?:{{(?<macro>${macros})}})`); | ||
| 48 | let done = ''; | 79 | let done = ''; |
| 49 | let remaining = text; | 80 | let remaining = text; |
| 50 | while (re.test(remaining)) { | 81 | while (re.test(remaining)) { |
| 51 | const match = re.exec(remaining); | 82 | const match = re.exec(remaining); |
| 52 | const before = substituteParams(remaining.slice(0, match.index)); | 83 | const before = substituteParams(remaining.slice(0, match.index)); |
| 53 | const after = remaining.slice(match.index + match[0].length); | 84 | const after = remaining.slice(match.index + match[0].length); |
| 54 | const replacer = match[1] ? scope.pipe : match[2] ? scope.getVariable(match[2], match[3]) : scope.macroList.find(it=>it.key == match[4])?.value; | 85 | const replacer = match.groups.pipe ? scope.pipe : match.groups.var ? scope.getVariable(match.groups.var, match.groups.index) : macroList.find(it=>it.key == match.groups.macro || new RegExp(escapeMacro(it, true)).test(match.groups.macro))?.value; |
| 55 | if (replacer instanceof SlashCommandClosure) { | 86 | if (replacer instanceof SlashCommandClosure) { |
| 87 | replacer.abortController = this.abortController; | ||
| 88 | replacer.breakController = this.breakController; | ||
| 89 | replacer.scope.parent = this.scope; | ||
| 90 | if (this.debugController && !replacer.debugController) { | ||
| 91 | replacer.debugController = this.debugController; | ||
| 92 | } | ||
| 56 | isList = true; | 93 | isList = true; |
| 57 | if (match.index > 0) { | 94 | if (match.index > 0) { |
| 58 | listValues.push(before); | 95 | listValues.push(before); |
| @@ -87,6 +124,12 @@ export class SlashCommandClosure { | |||
| 87 | closure.providedArgumentList = this.providedArgumentList; | 124 | closure.providedArgumentList = this.providedArgumentList; |
| 88 | closure.executorList = this.executorList; | 125 | closure.executorList = this.executorList; |
| 89 | closure.abortController = this.abortController; | 126 | closure.abortController = this.abortController; |
| 127 | closure.breakController = this.breakController; | ||
| 128 | closure.debugController = this.debugController; | ||
| 129 | closure.rawText = this.rawText; | ||
| 130 | closure.fullText = this.fullText; | ||
| 131 | closure.parserContext = this.parserContext; | ||
| 132 | closure.source = this.source; | ||
| 90 | closure.onProgress = this.onProgress; | 133 | closure.onProgress = this.onProgress; |
| 91 | return closure; | 134 | return closure; |
| 92 | } | 135 | } |
| @@ -96,11 +139,22 @@ export class SlashCommandClosure { | |||
| 96 | * @returns {Promise<SlashCommandClosureResult>} | 139 | * @returns {Promise<SlashCommandClosureResult>} |
| 97 | */ | 140 | */ |
| 98 | async execute() { | 141 | async execute() { |
| 142 | // execute a copy of the closure to no taint it and its scope with the effects of its execution | ||
| 143 | // as this would affect the closure being called a second time (e.g., loop, multiple /run calls) | ||
| 99 | const closure = this.getCopy(); | 144 | const closure = this.getCopy(); |
| 100 | return await closure.executeDirect(); | 145 | const gen = closure.executeDirect(); |
| 146 | let step; | ||
| 147 | while (!step?.done) { | ||
| 148 | step = await gen.next(this.debugController?.testStepping(this) ?? false); | ||
| 149 | if (!(step.value instanceof SlashCommandClosureResult) && this.debugController) { | ||
| 150 | this.debugController.isStepping = await this.debugController.awaitBreakPoint(step.value.closure, step.value.executor); | ||
| 151 | } | ||
| 152 | } | ||
| 153 | return step.value; | ||
| 101 | } | 154 | } |
| 102 | 155 | ||
| 103 | async executeDirect() { | 156 | async * executeDirect() { |
| 157 | this.debugController?.down(this); | ||
| 104 | // closure arguments | 158 | // closure arguments |
| 105 | for (const arg of this.argumentList) { | 159 | for (const arg of this.argumentList) { |
| 106 | let v = arg.value; | 160 | let v = arg.value; |
| @@ -108,6 +162,7 @@ export class SlashCommandClosure { | |||
| 108 | /**@type {SlashCommandClosure}*/ | 162 | /**@type {SlashCommandClosure}*/ |
| 109 | const closure = v; | 163 | const closure = v; |
| 110 | closure.scope.parent = this.scope; | 164 | closure.scope.parent = this.scope; |
| 165 | closure.breakController = this.breakController; | ||
| 111 | if (closure.executeNow) { | 166 | if (closure.executeNow) { |
| 112 | v = (await closure.execute())?.pipe; | 167 | v = (await closure.execute())?.pipe; |
| 113 | } else { | 168 | } else { |
| @@ -131,6 +186,7 @@ export class SlashCommandClosure { | |||
| 131 | /**@type {SlashCommandClosure}*/ | 186 | /**@type {SlashCommandClosure}*/ |
| 132 | const closure = v; | 187 | const closure = v; |
| 133 | closure.scope.parent = this.scope; | 188 | closure.scope.parent = this.scope; |
| 189 | closure.breakController = this.breakController; | ||
| 134 | if (closure.executeNow) { | 190 | if (closure.executeNow) { |
| 135 | v = (await closure.execute())?.pipe; | 191 | v = (await closure.execute())?.pipe; |
| 136 | } else { | 192 | } else { |
| @@ -149,34 +205,190 @@ export class SlashCommandClosure { | |||
| 149 | this.scope.setVariable(arg.name, v); | 205 | this.scope.setVariable(arg.name, v); |
| 150 | } | 206 | } |
| 151 | 207 | ||
| 152 | let done = 0; | ||
| 153 | if (this.executorList.length == 0) { | 208 | if (this.executorList.length == 0) { |
| 154 | this.scope.pipe = ''; | 209 | this.scope.pipe = ''; |
| 155 | } | 210 | } |
| 211 | const stepper = this.executeStep(); | ||
| 212 | let step; | ||
| 213 | while (!step?.done && !this.breakController?.isBreak) { | ||
| 214 | // get executor before execution | ||
| 215 | step = await stepper.next(); | ||
| 216 | if (step.value instanceof SlashCommandBreakPoint) { | ||
| 217 | console.log('encountered SlashCommandBreakPoint'); | ||
| 218 | if (this.debugController) { | ||
| 219 | // resolve args | ||
| 220 | step = await stepper.next(); | ||
| 221 | // "execute" breakpoint | ||
| 222 | step = await stepper.next(); | ||
| 223 | // get next executor | ||
| 224 | step = await stepper.next(); | ||
| 225 | // breakpoint has to yield before arguments are resolved if one of the | ||
| 226 | // arguments is an immediate closure, otherwise you cannot step into the | ||
| 227 | // immediate closure | ||
| 228 | const hasImmediateClosureInNamedArgs = /**@type {SlashCommandExecutor}*/(step.value)?.namedArgumentList?.find(it=>it.value instanceof SlashCommandClosure && it.value.executeNow); | ||
| 229 | const hasImmediateClosureInUnnamedArgs = /**@type {SlashCommandExecutor}*/(step.value)?.unnamedArgumentList?.find(it=>it.value instanceof SlashCommandClosure && it.value.executeNow); | ||
| 230 | if (hasImmediateClosureInNamedArgs || hasImmediateClosureInUnnamedArgs) { | ||
| 231 | this.debugController.isStepping = yield { closure:this, executor:step.value }; | ||
| 232 | } else { | ||
| 233 | this.debugController.isStepping = true; | ||
| 234 | this.debugController.stepStack[this.debugController.stepStack.length - 1] = true; | ||
| 235 | } | ||
| 236 | } | ||
| 237 | } else if (!step.done && this.debugController?.testStepping(this)) { | ||
| 238 | this.debugController.isSteppingInto = false; | ||
| 239 | // if stepping, have to yield before arguments are resolved if one of the arguments | ||
| 240 | // is an immediate closure, otherwise you cannot step into the immediate closure | ||
| 241 | const hasImmediateClosureInNamedArgs = /**@type {SlashCommandExecutor}*/(step.value)?.namedArgumentList?.find(it=>it.value instanceof SlashCommandClosure && it.value.executeNow); | ||
| 242 | const hasImmediateClosureInUnnamedArgs = /**@type {SlashCommandExecutor}*/(step.value)?.unnamedArgumentList?.find(it=>it.value instanceof SlashCommandClosure && it.value.executeNow); | ||
| 243 | if (hasImmediateClosureInNamedArgs || hasImmediateClosureInUnnamedArgs) { | ||
| 244 | this.debugController.isStepping = yield { closure:this, executor:step.value }; | ||
| 245 | } | ||
| 246 | } | ||
| 247 | // resolve args | ||
| 248 | step = await stepper.next(); | ||
| 249 | if (step.value instanceof SlashCommandBreak) { | ||
| 250 | console.log('encountered SlashCommandBreak'); | ||
| 251 | if (this.breakController) { | ||
| 252 | this.breakController?.break(); | ||
| 253 | break; | ||
| 254 | } | ||
| 255 | } else if (!step.done && this.debugController?.testStepping(this)) { | ||
| 256 | this.debugController.isSteppingInto = false; | ||
| 257 | this.debugController.isStepping = yield { closure:this, executor:step.value }; | ||
| 258 | } | ||
| 259 | // execute executor | ||
| 260 | step = await stepper.next(); | ||
| 261 | } | ||
| 262 | |||
| 263 | // if execution has returned a closure result, return that (should only happen on abort) | ||
| 264 | if (step.value instanceof SlashCommandClosureResult) { | ||
| 265 | this.debugController?.up(); | ||
| 266 | return step.value; | ||
| 267 | } | ||
| 268 | /**@type {SlashCommandClosureResult} */ | ||
| 269 | const result = Object.assign(new SlashCommandClosureResult(), { pipe: this.scope.pipe, isBreak: this.breakController?.isBreak ?? false }); | ||
| 270 | this.debugController?.up(); | ||
| 271 | return result; | ||
| 272 | } | ||
| 273 | /** | ||
| 274 | * Generator that steps through the executor list. | ||
| 275 | * Every executor is split into three steps: | ||
| 276 | * - before arguments are resolved | ||
| 277 | * - after arguments are resolved | ||
| 278 | * - after execution | ||
| 279 | */ | ||
| 280 | async * executeStep() { | ||
| 281 | let done = 0; | ||
| 282 | let isFirst = true; | ||
| 156 | for (const executor of this.executorList) { | 283 | for (const executor of this.executorList) { |
| 157 | this.onProgress?.(done, this.commandCount); | 284 | this.onProgress?.(done, this.commandCount); |
| 158 | if (executor instanceof SlashCommandClosureExecutor) { | 285 | if (this.debugController) { |
| 159 | const closure = this.scope.getVariable(executor.name); | 286 | this.debugController.setExecutor(executor); |
| 160 | if (!closure || !(closure instanceof SlashCommandClosure)) throw new Error(`${executor.name} is not a closure.`); | 287 | this.debugController.namedArguments = undefined; |
| 161 | closure.scope.parent = this.scope; | 288 | this.debugController.unnamedArguments = undefined; |
| 162 | closure.providedArgumentList = executor.providedArgumentList; | 289 | } |
| 163 | const result = await closure.execute(); | 290 | // yield before doing anything with this executor, the debugger might want to do |
| 164 | this.scope.pipe = result.pipe; | 291 | // something with it (e.g., breakpoint, immediate closures that need resolving |
| 165 | } else { | 292 | // or stepping into) |
| 293 | yield executor; | ||
| 166 | /**@type {import('./SlashCommand.js').NamedArguments} */ | 294 | /**@type {import('./SlashCommand.js').NamedArguments} */ |
| 295 | // @ts-ignore | ||
| 167 | let args = { | 296 | let args = { |
| 168 | _scope: this.scope, | 297 | _scope: this.scope, |
| 169 | _parserFlags: executor.parserFlags, | 298 | _parserFlags: executor.parserFlags, |
| 170 | _abortController: this.abortController, | 299 | _abortController: this.abortController, |
| 300 | _debugController: this.debugController, | ||
| 171 | _hasUnnamedArgument: executor.unnamedArgumentList.length > 0, | 301 | _hasUnnamedArgument: executor.unnamedArgumentList.length > 0, |
| 172 | }; | 302 | }; |
| 173 | let value; | 303 | if (executor instanceof SlashCommandBreakPoint) { |
| 304 | // nothing to do for breakpoints, just raise counter and yield for "before exec" | ||
| 305 | done++; | ||
| 306 | yield executor; | ||
| 307 | isFirst = false; | ||
| 308 | } else if (executor instanceof SlashCommandBreak) { | ||
| 309 | // /break need to resolve the unnamed arg and put it into pipe, then yield | ||
| 310 | // for "before exec" | ||
| 311 | const value = await this.substituteUnnamedArgument(executor, isFirst, args); | ||
| 312 | done += this.executorList.length - this.executorList.indexOf(executor); | ||
| 313 | this.scope.pipe = value ?? this.scope.pipe; | ||
| 314 | yield executor; | ||
| 315 | isFirst = false; | ||
| 316 | } else { | ||
| 317 | // regular commands do all the argument resolving logic... | ||
| 318 | await this.substituteNamedArguments(executor, args); | ||
| 319 | let value = await this.substituteUnnamedArgument(executor, isFirst, args); | ||
| 320 | |||
| 321 | let abortResult = await this.testAbortController(); | ||
| 322 | if (abortResult) { | ||
| 323 | return abortResult; | ||
| 324 | } | ||
| 325 | if (this.debugController) { | ||
| 326 | this.debugController.namedArguments = args; | ||
| 327 | this.debugController.unnamedArguments = value ?? ''; | ||
| 328 | } | ||
| 329 | // then yield for "before exec" | ||
| 330 | yield executor; | ||
| 331 | // followed by command execution | ||
| 332 | executor.onProgress = (subDone, subTotal)=>this.onProgress?.(done + subDone, this.commandCount); | ||
| 333 | const isStepping = this.debugController?.testStepping(this); | ||
| 334 | if (this.debugController) { | ||
| 335 | this.debugController.isStepping = false || this.debugController.isSteppingInto; | ||
| 336 | } | ||
| 337 | try { | ||
| 338 | this.scope.pipe = await executor.command.callback(args, value ?? ''); | ||
| 339 | } catch (ex) { | ||
| 340 | throw new SlashCommandExecutionError(ex, ex.message, executor.name, executor.start, executor.end, this.fullText.slice(executor.start, executor.end), this.fullText); | ||
| 341 | } | ||
| 342 | if (this.debugController) { | ||
| 343 | this.debugController.namedArguments = undefined; | ||
| 344 | this.debugController.unnamedArguments = undefined; | ||
| 345 | this.debugController.isStepping = isStepping; | ||
| 346 | } | ||
| 347 | this.#lintPipe(executor.command); | ||
| 348 | done += executor.commandCount; | ||
| 349 | this.onProgress?.(done, this.commandCount); | ||
| 350 | abortResult = await this.testAbortController(); | ||
| 351 | if (abortResult) { | ||
| 352 | return abortResult; | ||
| 353 | } | ||
| 354 | } | ||
| 355 | // finally, yield for "after exec" | ||
| 356 | yield executor; | ||
| 357 | isFirst = false; | ||
| 358 | } | ||
| 359 | } | ||
| 360 | |||
| 361 | async testPaused() { | ||
| 362 | while (!this.abortController?.signal?.aborted && this.abortController?.signal?.paused) { | ||
| 363 | await delay(200); | ||
| 364 | } | ||
| 365 | } | ||
| 366 | async testAbortController() { | ||
| 367 | await this.testPaused(); | ||
| 368 | if (this.abortController?.signal?.aborted) { | ||
| 369 | const result = new SlashCommandClosureResult(); | ||
| 370 | result.isAborted = true; | ||
| 371 | result.isQuietlyAborted = this.abortController.signal.isQuiet; | ||
| 372 | result.abortReason = this.abortController.signal.reason.toString(); | ||
| 373 | return result; | ||
| 374 | } | ||
| 375 | } | ||
| 376 | |||
| 377 | /** | ||
| 378 | * @param {SlashCommandExecutor} executor | ||
| 379 | * @param {import('./SlashCommand.js').NamedArguments} args | ||
| 380 | */ | ||
| 381 | async substituteNamedArguments(executor, args) { | ||
| 174 | // substitute named arguments | 382 | // substitute named arguments |
| 175 | for (const arg of executor.namedArgumentList) { | 383 | for (const arg of executor.namedArgumentList) { |
| 176 | if (arg.value instanceof SlashCommandClosure) { | 384 | if (arg.value instanceof SlashCommandClosure) { |
| 177 | /**@type {SlashCommandClosure}*/ | 385 | /**@type {SlashCommandClosure}*/ |
| 178 | const closure = arg.value; | 386 | const closure = arg.value; |
| 179 | closure.scope.parent = this.scope; | 387 | closure.scope.parent = this.scope; |
| 388 | closure.breakController = this.breakController; | ||
| 389 | if (this.debugController && !closure.debugController) { | ||
| 390 | closure.debugController = this.debugController; | ||
| 391 | } | ||
| 180 | if (closure.executeNow) { | 392 | if (closure.executeNow) { |
| 181 | args[arg.name] = (await closure.execute())?.pipe; | 393 | args[arg.name] = (await closure.execute())?.pipe; |
| 182 | } else { | 394 | } else { |
| @@ -193,10 +405,19 @@ export class SlashCommandClosure { | |||
| 193 | ; | 405 | ; |
| 194 | } | 406 | } |
| 195 | } | 407 | } |
| 408 | } | ||
| 196 | 409 | ||
| 410 | /** | ||
| 411 | * @param {SlashCommandExecutor} executor | ||
| 412 | * @param {boolean} isFirst | ||
| 413 | * @param {import('./SlashCommand.js').NamedArguments} args | ||
| 414 | * @returns {Promise<string|SlashCommandClosure|(string|SlashCommandClosure)[]>} | ||
| 415 | */ | ||
| 416 | async substituteUnnamedArgument(executor, isFirst, args) { | ||
| 417 | let value; | ||
| 197 | // substitute unnamed argument | 418 | // substitute unnamed argument |
| 198 | if (executor.unnamedArgumentList.length == 0) { | 419 | if (executor.unnamedArgumentList.length == 0) { |
| 199 | if (executor.injectPipe) { | 420 | if (!isFirst && executor.injectPipe) { |
| 200 | value = this.scope.pipe; | 421 | value = this.scope.pipe; |
| 201 | args._hasUnnamedArgument = this.scope.pipe !== null && this.scope.pipe !== undefined; | 422 | args._hasUnnamedArgument = this.scope.pipe !== null && this.scope.pipe !== undefined; |
| 202 | } | 423 | } |
| @@ -208,6 +429,10 @@ export class SlashCommandClosure { | |||
| 208 | /**@type {SlashCommandClosure}*/ | 429 | /**@type {SlashCommandClosure}*/ |
| 209 | const closure = v; | 430 | const closure = v; |
| 210 | closure.scope.parent = this.scope; | 431 | closure.scope.parent = this.scope; |
| 432 | closure.breakController = this.breakController; | ||
| 433 | if (this.debugController && !closure.debugController) { | ||
| 434 | closure.debugController = this.debugController; | ||
| 435 | } | ||
| 211 | if (closure.executeNow) { | 436 | if (closure.executeNow) { |
| 212 | v = (await closure.execute())?.pipe; | 437 | v = (await closure.execute())?.pipe; |
| 213 | } else { | 438 | } else { |
| @@ -242,41 +467,7 @@ export class SlashCommandClosure { | |||
| 242 | return v; | 467 | return v; |
| 243 | }); | 468 | }); |
| 244 | } | 469 | } |
| 245 | 470 | return value; | |
| 246 | let abortResult = await this.testAbortController(); | ||
| 247 | if (abortResult) { | ||
| 248 | return abortResult; | ||
| 249 | } | ||
| 250 | executor.onProgress = (subDone, subTotal)=>this.onProgress?.(done + subDone, this.commandCount); | ||
| 251 | this.scope.pipe = await executor.command.callback(args, value ?? ''); | ||
| 252 | this.#lintPipe(executor.command); | ||
| 253 | done += executor.commandCount; | ||
| 254 | this.onProgress?.(done, this.commandCount); | ||
| 255 | abortResult = await this.testAbortController(); | ||
| 256 | if (abortResult) { | ||
| 257 | return abortResult; | ||
| 258 | } | ||
| 259 | } | ||
| 260 | } | ||
| 261 | /**@type {SlashCommandClosureResult} */ | ||
| 262 | const result = Object.assign(new SlashCommandClosureResult(), { pipe: this.scope.pipe }); | ||
| 263 | return result; | ||
| 264 | } | ||
| 265 | |||
| 266 | async testPaused() { | ||
| 267 | while (!this.abortController?.signal?.aborted && this.abortController?.signal?.paused) { | ||
| 268 | await delay(200); | ||
| 269 | } | ||
| 270 | } | ||
| 271 | async testAbortController() { | ||
| 272 | await this.testPaused(); | ||
| 273 | if (this.abortController?.signal?.aborted) { | ||
| 274 | const result = new SlashCommandClosureResult(); | ||
| 275 | result.isAborted = true; | ||
| 276 | result.isQuietlyAborted = this.abortController.signal.isQuiet; | ||
| 277 | result.abortReason = this.abortController.signal.reason.toString(); | ||
| 278 | return result; | ||
| 279 | } | ||
| 280 | } | 471 | } |
| 281 | 472 | ||
| 282 | /** | 473 | /** |
| @@ -285,8 +476,11 @@ export class SlashCommandClosure { | |||
| 285 | */ | 476 | */ |
| 286 | #lintPipe(command) { | 477 | #lintPipe(command) { |
| 287 | if (this.scope.pipe === undefined || this.scope.pipe === null) { | 478 | if (this.scope.pipe === undefined || this.scope.pipe === null) { |
| 288 | console.warn(`${command.name} returned undefined or null. Auto-fixing to empty string.`); | 479 | console.warn(`/${command.name} returned undefined or null. Auto-fixing to empty string.`); |
| 289 | this.scope.pipe = ''; | 480 | this.scope.pipe = ''; |
| 481 | } else if (!(typeof this.scope.pipe == 'string' || this.scope.pipe instanceof SlashCommandClosure)) { | ||
| 482 | console.warn(`/${command.name} returned illegal type (${typeof this.scope.pipe} - ${this.scope.pipe.constructor?.name ?? ''}). Auto-fixing to stringified JSON.`); | ||
| 483 | this.scope.pipe = JSON.stringify(this.scope.pipe) ?? ''; | ||
| 290 | } | 484 | } |
| 291 | } | 485 | } |
| 292 | } | 486 | } |
| @@ -1,7 +0,0 @@ | |||
| 1 | import { SlashCommandNamedArgumentAssignment } from './SlashCommandNamedArgumentAssignment.js'; | ||
| 2 | |||
| 3 | export class SlashCommandClosureExecutor { | ||
| 4 | /**@type {String}*/ name = ''; | ||
| 5 | // @ts-ignore | ||
| 6 | /**@type {SlashCommandNamedArgumentAssignment[]}*/ providedArgumentList = []; | ||
| 7 | } | ||
| @@ -1,6 +1,7 @@ | |||
| 1 | export class SlashCommandClosureResult { | 1 | export class SlashCommandClosureResult { |
| 2 | /**@type {boolean}*/ interrupt = false; | 2 | /**@type {boolean}*/ interrupt = false; |
| 3 | /**@type {string}*/ pipe; | 3 | /**@type {string}*/ pipe; |
| 4 | /**@type {boolean}*/ isBreak = false; | ||
| 4 | /**@type {boolean}*/ isAborted = false; | 5 | /**@type {boolean}*/ isAborted = false; |
| 5 | /**@type {boolean}*/ isQuietlyAborted = false; | 6 | /**@type {boolean}*/ isQuietlyAborted = false; |
| 6 | /**@type {string}*/ abortReason; | 7 | /**@type {string}*/ abortReason; |
| @@ -6,6 +6,7 @@ import { searchCharByName, getTagsList, tags } from '../tags.js'; | |||
| 6 | import { world_names } from '../world-info.js'; | 6 | import { world_names } from '../world-info.js'; |
| 7 | import { SlashCommandClosure } from './SlashCommandClosure.js'; | 7 | import { SlashCommandClosure } from './SlashCommandClosure.js'; |
| 8 | import { SlashCommandEnumValue, enumTypes } from './SlashCommandEnumValue.js'; | 8 | import { SlashCommandEnumValue, enumTypes } from './SlashCommandEnumValue.js'; |
| 9 | import { SlashCommandScope } from "./SlashCommandScope.js"; | ||
| 9 | 10 | ||
| 10 | /** | 11 | /** |
| 11 | * A collection of regularly used enum icons | 12 | * A collection of regularly used enum icons |
| @@ -134,16 +135,16 @@ export const commonEnumProviders = { | |||
| 134 | * Can be filtered by `type` to only show global or local variables | 135 | * Can be filtered by `type` to only show global or local variables |
| 135 | * | 136 | * |
| 136 | * @param {...('global'|'local'|'scope'|'all')} type - The type of variables to include in the array. Can be 'all', 'global', or 'local'. | 137 | * @param {...('global'|'local'|'scope'|'all')} type - The type of variables to include in the array. Can be 'all', 'global', or 'local'. |
| 137 | * @returns {() => SlashCommandEnumValue[]} | 138 | * @returns {(executor:SlashCommandExecutor, scope:SlashCommandScope) => SlashCommandEnumValue[]} |
| 138 | */ | 139 | */ |
| 139 | variables: (...type) => () => { | 140 | variables: (...type) => (executor, scope) => { |
| 140 | const types = type.flat(); | 141 | const types = type.flat(); |
| 141 | const isAll = types.includes('all'); | 142 | const isAll = types.includes('all'); |
| 142 | return [ | 143 | return [ |
| 143 | ...isAll || types.includes('global') ? Object.keys(extension_settings.variables.global ?? []).map(name => new SlashCommandEnumValue(name, null, enumTypes.macro, enumIcons.globalVariable)) : [], | 144 | ...isAll || types.includes('scope') ? scope.allVariableNames.map(name => new SlashCommandEnumValue(name, null, enumTypes.variable, enumIcons.scopeVariable)) : [], |
| 144 | ...isAll || types.includes('local') ? Object.keys(chat_metadata.variables ?? []).map(name => new SlashCommandEnumValue(name, null, enumTypes.name, enumIcons.localVariable)) : [], | 145 | ...isAll || types.includes('local') ? Object.keys(chat_metadata.variables ?? []).map(name => new SlashCommandEnumValue(name, null, enumTypes.name, enumIcons.localVariable)) : [], |
| 145 | ...isAll || types.includes('scope') ? [].map(name => new SlashCommandEnumValue(name, null, enumTypes.variable, enumIcons.scopeVariable)) : [], // TODO: Add scoped variables here, Lenny | 146 | ...isAll || types.includes('global') ? Object.keys(extension_settings.variables.global ?? []).map(name => new SlashCommandEnumValue(name, null, enumTypes.macro, enumIcons.globalVariable)) : [], |
| 146 | ]; | 147 | ].filter((item, idx, list)=>idx == list.findIndex(it=>it.value == item.value)); |
| 147 | }, | 148 | }, |
| 148 | 149 | ||
| 149 | /** | 150 | /** |
| @@ -0,0 +1,83 @@ | |||
| 1 | import { SlashCommandClosure } from './SlashCommandClosure.js'; | ||
| 2 | import { SlashCommandExecutor } from './SlashCommandExecutor.js'; | ||
| 3 | |||
| 4 | export class SlashCommandDebugController { | ||
| 5 | /**@type {SlashCommandClosure[]} */ stack = []; | ||
| 6 | /**@type {SlashCommandExecutor[]} */ cmdStack = []; | ||
| 7 | /**@type {boolean[]} */ stepStack = []; | ||
| 8 | /**@type {boolean} */ isStepping = false; | ||
| 9 | /**@type {boolean} */ isSteppingInto = false; | ||
| 10 | /**@type {boolean} */ isSteppingOut = false; | ||
| 11 | |||
| 12 | /**@type {object} */ namedArguments; | ||
| 13 | /**@type {string|SlashCommandClosure|(string|SlashCommandClosure)[]} */ unnamedArguments; | ||
| 14 | |||
| 15 | /**@type {Promise<boolean>} */ continuePromise; | ||
| 16 | /**@type {(boolean)=>void} */ continueResolver; | ||
| 17 | |||
| 18 | /**@type {(closure:SlashCommandClosure, executor:SlashCommandExecutor)=>Promise<boolean>} */ onBreakPoint; | ||
| 19 | |||
| 20 | |||
| 21 | |||
| 22 | |||
| 23 | testStepping(closure) { | ||
| 24 | return this.stepStack[this.stack.indexOf(closure)]; | ||
| 25 | } | ||
| 26 | |||
| 27 | |||
| 28 | |||
| 29 | |||
| 30 | down(closure) { | ||
| 31 | this.stack.push(closure); | ||
| 32 | if (this.stepStack.length < this.stack.length) { | ||
| 33 | this.stepStack.push(this.isSteppingInto); | ||
| 34 | } | ||
| 35 | } | ||
| 36 | up() { | ||
| 37 | this.stack.pop(); | ||
| 38 | while (this.cmdStack.length > this.stack.length) this.cmdStack.pop(); | ||
| 39 | this.stepStack.pop(); | ||
| 40 | } | ||
| 41 | |||
| 42 | setExecutor(executor) { | ||
| 43 | this.cmdStack[this.stack.length - 1] = executor; | ||
| 44 | } | ||
| 45 | |||
| 46 | |||
| 47 | |||
| 48 | resume() { | ||
| 49 | this.continueResolver?.(false); | ||
| 50 | this.continuePromise = null; | ||
| 51 | this.stepStack.forEach((_,idx)=>this.stepStack[idx] = false); | ||
| 52 | } | ||
| 53 | step() { | ||
| 54 | this.stepStack.forEach((_,idx)=>this.stepStack[idx] = true); | ||
| 55 | this.continueResolver?.(true); | ||
| 56 | this.continuePromise = null; | ||
| 57 | } | ||
| 58 | stepInto() { | ||
| 59 | this.isSteppingInto = true; | ||
| 60 | this.stepStack.forEach((_,idx)=>this.stepStack[idx] = true); | ||
| 61 | this.continueResolver?.(true); | ||
| 62 | this.continuePromise = null; | ||
| 63 | } | ||
| 64 | stepOut() { | ||
| 65 | this.isSteppingOut = true; | ||
| 66 | this.stepStack[this.stepStack.length - 1] = false; | ||
| 67 | this.continueResolver?.(false); | ||
| 68 | this.continuePromise = null; | ||
| 69 | } | ||
| 70 | |||
| 71 | async awaitContinue() { | ||
| 72 | this.continuePromise ??= new Promise(resolve=>{ | ||
| 73 | this.continueResolver = resolve; | ||
| 74 | }); | ||
| 75 | this.isStepping = await this.continuePromise; | ||
| 76 | return this.isStepping; | ||
| 77 | } | ||
| 78 | |||
| 79 | async awaitBreakPoint(closure, executor) { | ||
| 80 | this.isStepping = await this.onBreakPoint(closure, executor); | ||
| 81 | return this.isStepping; | ||
| 82 | } | ||
| 83 | } | ||
| @@ -3,6 +3,17 @@ import { SlashCommand } from './SlashCommand.js'; | |||
| 3 | import { SlashCommandEnumValue } from './SlashCommandEnumValue.js'; | 3 | import { SlashCommandEnumValue } from './SlashCommandEnumValue.js'; |
| 4 | 4 | ||
| 5 | export class SlashCommandEnumAutoCompleteOption extends AutoCompleteOption { | 5 | export class SlashCommandEnumAutoCompleteOption extends AutoCompleteOption { |
| 6 | /** | ||
| 7 | * @param {SlashCommand} cmd | ||
| 8 | * @param {SlashCommandEnumValue} enumValue | ||
| 9 | * @returns {SlashCommandEnumAutoCompleteOption} | ||
| 10 | */ | ||
| 11 | static from(cmd, enumValue) { | ||
| 12 | const mapped = this.valueToOptionMap.find(it=>enumValue instanceof it.value)?.option ?? this; | ||
| 13 | return new mapped(cmd, enumValue); | ||
| 14 | } | ||
| 15 | /**@type {{value:(typeof SlashCommandEnumValue), option:(typeof SlashCommandEnumAutoCompleteOption)}[]} */ | ||
| 16 | static valueToOptionMap = []; | ||
| 6 | /**@type {SlashCommand}*/ cmd; | 17 | /**@type {SlashCommand}*/ cmd; |
| 7 | /**@type {SlashCommandEnumValue}*/ enumValue; | 18 | /**@type {SlashCommandEnumValue}*/ enumValue; |
| 8 | 19 | ||
| @@ -13,7 +24,7 @@ export class SlashCommandEnumAutoCompleteOption extends AutoCompleteOption { | |||
| 13 | * @param {SlashCommandEnumValue} enumValue | 24 | * @param {SlashCommandEnumValue} enumValue |
| 14 | */ | 25 | */ |
| 15 | constructor(cmd, enumValue) { | 26 | constructor(cmd, enumValue) { |
| 16 | super(enumValue.value, enumValue.typeIcon, enumValue.type); | 27 | super(enumValue.value, enumValue.typeIcon, enumValue.type, enumValue.matchProvider, enumValue.valueProvider, enumValue.makeSelectable); |
| 17 | this.cmd = cmd; | 28 | this.cmd = cmd; |
| 18 | this.enumValue = enumValue; | 29 | this.enumValue = enumValue; |
| 19 | } | 30 | } |
| @@ -1,3 +1,6 @@ | |||
| 1 | import { SlashCommandExecutor } from './SlashCommandExecutor.js'; | ||
| 2 | import { SlashCommandScope } from './SlashCommandScope.js'; | ||
| 3 | |||
| 1 | 4 | ||
| 2 | /** | 5 | /** |
| 3 | * @typedef {'enum' | 'command' | 'namedArgument' | 'variable' | 'qr' | 'macro' | 'number' | 'name'} EnumType | 6 | * @typedef {'enum' | 'command' | 'namedArgument' | 'variable' | 'qr' | 'macro' | 'number' | 'name'} EnumType |
| @@ -37,14 +40,17 @@ export const enumTypes = { | |||
| 37 | getBasedOnIndex(index) { | 40 | getBasedOnIndex(index) { |
| 38 | const keys = Object.keys(this); | 41 | const keys = Object.keys(this); |
| 39 | return this[keys[(index ?? 0) % keys.length]]; | 42 | return this[keys[(index ?? 0) % keys.length]]; |
| 40 | } | 43 | }, |
| 41 | } | 44 | }; |
| 42 | 45 | ||
| 43 | export class SlashCommandEnumValue { | 46 | export class SlashCommandEnumValue { |
| 44 | /**@type {string}*/ value; | 47 | /**@type {string}*/ value; |
| 45 | /**@type {string}*/ description; | 48 | /**@type {string}*/ description; |
| 46 | /**@type {EnumType}*/ type = 'enum'; | 49 | /**@type {EnumType}*/ type = 'enum'; |
| 47 | /**@type {string}*/ typeIcon = '◊'; | 50 | /**@type {string}*/ typeIcon = '◊'; |
| 51 | /**@type {(input:string)=>boolean}*/ matchProvider; | ||
| 52 | /**@type {(input:string)=>string}*/ valueProvider; | ||
| 53 | /**@type {boolean}*/ makeSelectable = false; | ||
| 48 | 54 | ||
| 49 | /** | 55 | /** |
| 50 | * A constructor for creating a SlashCommandEnumValue instance. | 56 | * A constructor for creating a SlashCommandEnumValue instance. |
| @@ -52,13 +58,19 @@ export class SlashCommandEnumValue { | |||
| 52 | * @param {string} value - The value | 58 | * @param {string} value - The value |
| 53 | * @param {string?} description - Optional description, displayed in a second line | 59 | * @param {string?} description - Optional description, displayed in a second line |
| 54 | * @param {EnumType?} type - type of the enum (defining its color) | 60 | * @param {EnumType?} type - type of the enum (defining its color) |
| 55 | * @param {string} typeIcon - The icon to display (Can be pulled from `enumIcons` for common ones) | 61 | * @param {string?} typeIcon - The icon to display (Can be pulled from `enumIcons` for common ones) |
| 62 | * @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. | ||
| 63 | * @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). | ||
| 64 | * @param {boolean?} makeSelectable - Set to true to make the value selectable (through tab/enter) even though a valueProvider exists. | ||
| 56 | */ | 65 | */ |
| 57 | constructor(value, description = null, type = 'enum', typeIcon = '◊') { | 66 | constructor(value, description = null, type = 'enum', typeIcon = '◊', matchProvider = null, valueProvider = null, makeSelectable = false) { |
| 58 | this.value = value; | 67 | this.value = value; |
| 59 | this.description = description; | 68 | this.description = description; |
| 60 | this.type = type ?? 'enum'; | 69 | this.type = type ?? 'enum'; |
| 61 | this.typeIcon = typeIcon; | 70 | this.typeIcon = typeIcon; |
| 71 | this.matchProvider = matchProvider; | ||
| 72 | this.valueProvider = valueProvider; | ||
| 73 | this.makeSelectable = makeSelectable; | ||
| 62 | } | 74 | } |
| 63 | 75 | ||
| 64 | toString() { | 76 | toString() { |
| @@ -0,0 +1,60 @@ | |||
| 1 | export class SlashCommandExecutionError extends Error { | ||
| 2 | /**@type {string} */ commandName; | ||
| 3 | /**@type {number} */ start; | ||
| 4 | /**@type {number} */ end; | ||
| 5 | /**@type {string} */ commandText; | ||
| 6 | |||
| 7 | /**@type {string} */ text; | ||
| 8 | get index() { return this.start; } | ||
| 9 | |||
| 10 | get line() { | ||
| 11 | return this.text.slice(0, this.index).replace(/[^\n]/g, '').length; | ||
| 12 | } | ||
| 13 | get column() { | ||
| 14 | return this.text.slice(0, this.index).split('\n').pop().length; | ||
| 15 | } | ||
| 16 | get hint() { | ||
| 17 | let lineOffset = this.line.toString().length; | ||
| 18 | let lineStart = this.index; | ||
| 19 | let start = this.index; | ||
| 20 | let end = this.index; | ||
| 21 | let offset = 0; | ||
| 22 | let lineCount = 0; | ||
| 23 | while (offset < 10000 && lineCount < 3 && start >= 0) { | ||
| 24 | if (this.text[start] == '\n') lineCount++; | ||
| 25 | if (lineCount == 0) lineStart--; | ||
| 26 | offset++; | ||
| 27 | start--; | ||
| 28 | } | ||
| 29 | if (this.text[start + 1] == '\n') start++; | ||
| 30 | offset = 0; | ||
| 31 | while (offset < 10000 && this.text[end] != '\n') { | ||
| 32 | offset++; | ||
| 33 | end++; | ||
| 34 | } | ||
| 35 | let hint = []; | ||
| 36 | let lines = this.text.slice(start + 1, end - 1).split('\n'); | ||
| 37 | let lineNum = this.line - lines.length + 1; | ||
| 38 | let tabOffset = 0; | ||
| 39 | for (const line of lines) { | ||
| 40 | const num = `${' '.repeat(lineOffset - lineNum.toString().length)}${lineNum}`; | ||
| 41 | lineNum++; | ||
| 42 | const untabbedLine = line.replace(/\t/g, ' '.repeat(4)); | ||
| 43 | tabOffset = untabbedLine.length - line.length; | ||
| 44 | hint.push(`${num}: ${untabbedLine}`); | ||
| 45 | } | ||
| 46 | hint.push(`${' '.repeat(this.index - lineStart + lineOffset + 1 + tabOffset)}^^^^^`); | ||
| 47 | return hint.join('\n'); | ||
| 48 | } | ||
| 49 | |||
| 50 | |||
| 51 | |||
| 52 | constructor(cause, message, commandName, start, end, commandText, fullText) { | ||
| 53 | super(message, { cause }); | ||
| 54 | this.commandName = commandName; | ||
| 55 | this.start = start; | ||
| 56 | this.end = end; | ||
| 57 | this.commandText = commandText; | ||
| 58 | this.text = fullText; | ||
| 59 | } | ||
| 60 | } | ||
| @@ -1,4 +1,5 @@ | |||
| 1 | // eslint-disable-next-line no-unused-vars | 1 | // eslint-disable-next-line no-unused-vars |
| 2 | import { uuidv4 } from '../utils.js'; | ||
| 2 | import { SlashCommand } from './SlashCommand.js'; | 3 | import { SlashCommand } from './SlashCommand.js'; |
| 3 | // eslint-disable-next-line no-unused-vars | 4 | // eslint-disable-next-line no-unused-vars |
| 4 | import { SlashCommandClosure } from './SlashCommandClosure.js'; | 5 | import { SlashCommandClosure } from './SlashCommandClosure.js'; |
| @@ -16,6 +17,17 @@ export class SlashCommandExecutor { | |||
| 16 | /**@type {Number}*/ startUnnamedArgs; | 17 | /**@type {Number}*/ startUnnamedArgs; |
| 17 | /**@type {Number}*/ endUnnamedArgs; | 18 | /**@type {Number}*/ endUnnamedArgs; |
| 18 | /**@type {String}*/ name = ''; | 19 | /**@type {String}*/ name = ''; |
| 20 | /**@type {String}*/ #source = uuidv4(); | ||
| 21 | get source() { return this.#source; } | ||
| 22 | set source(value) { | ||
| 23 | this.#source = value; | ||
| 24 | for (const arg of this.namedArgumentList.filter(it=>it.value instanceof SlashCommandClosure)) { | ||
| 25 | arg.value.source = value; | ||
| 26 | } | ||
| 27 | for (const arg of this.unnamedArgumentList.filter(it=>it.value instanceof SlashCommandClosure)) { | ||
| 28 | arg.value.source = value; | ||
| 29 | } | ||
| 30 | } | ||
| 19 | /**@type {SlashCommand}*/ command; | 31 | /**@type {SlashCommand}*/ command; |
| 20 | // @ts-ignore | 32 | // @ts-ignore |
| 21 | /**@type {SlashCommandNamedArgumentAssignment[]}*/ namedArgumentList = []; | 33 | /**@type {SlashCommandNamedArgumentAssignment[]}*/ namedArgumentList = []; |
| @@ -17,7 +17,10 @@ import { SlashCommandAutoCompleteNameResult } from './SlashCommandAutoCompleteNa | |||
| 17 | import { SlashCommandUnnamedArgumentAssignment } from './SlashCommandUnnamedArgumentAssignment.js'; | 17 | import { SlashCommandUnnamedArgumentAssignment } from './SlashCommandUnnamedArgumentAssignment.js'; |
| 18 | import { SlashCommandEnumValue } from './SlashCommandEnumValue.js'; | 18 | import { SlashCommandEnumValue } from './SlashCommandEnumValue.js'; |
| 19 | import { MacroAutoCompleteOption } from '../autocomplete/MacroAutoCompleteOption.js'; | 19 | import { MacroAutoCompleteOption } from '../autocomplete/MacroAutoCompleteOption.js'; |
| 20 | import { SlashCommandBreakPoint } from './SlashCommandBreakPoint.js'; | ||
| 21 | import { SlashCommandDebugController } from './SlashCommandDebugController.js'; | ||
| 20 | import { commonEnumProviders } from './SlashCommandCommonEnumsProvider.js'; | 22 | import { commonEnumProviders } from './SlashCommandCommonEnumsProvider.js'; |
| 23 | import { SlashCommandBreak } from './SlashCommandBreak.js'; | ||
| 21 | 24 | ||
| 22 | /** @typedef {import('./SlashCommand.js').NamedArgumentsCapture} NamedArgumentsCapture */ | 25 | /** @typedef {import('./SlashCommand.js').NamedArgumentsCapture} NamedArgumentsCapture */ |
| 23 | /** @typedef {import('./SlashCommand.js').NamedArguments} NamedArguments */ | 26 | /** @typedef {import('./SlashCommand.js').NamedArguments} NamedArguments */ |
| @@ -53,7 +56,7 @@ export class SlashCommandParser { | |||
| 53 | * @param {SlashCommand} command | 56 | * @param {SlashCommand} command |
| 54 | */ | 57 | */ |
| 55 | static addCommandObject(command) { | 58 | static addCommandObject(command) { |
| 56 | const reserved = ['/', '#', ':', 'parser-flag']; | 59 | const reserved = ['/', '#', ':', 'parser-flag', 'breakpoint']; |
| 57 | for (const start of reserved) { | 60 | for (const start of reserved) { |
| 58 | if (command.name.toLowerCase().startsWith(start) || (command.aliases ?? []).find(a=>a.toLowerCase().startsWith(start))) { | 61 | if (command.name.toLowerCase().startsWith(start) || (command.aliases ?? []).find(a=>a.toLowerCase().startsWith(start))) { |
| 59 | throw new Error(`Illegal Name. Slash command name cannot begin with "${start}".`); | 62 | throw new Error(`Illegal Name. Slash command name cannot begin with "${start}".`); |
| @@ -70,6 +73,18 @@ export class SlashCommandParser { | |||
| 70 | console.trace('WARN: Duplicate slash command registered!', [command.name, ...command.aliases]); | 73 | console.trace('WARN: Duplicate slash command registered!', [command.name, ...command.aliases]); |
| 71 | } | 74 | } |
| 72 | 75 | ||
| 76 | const stack = new Error().stack.split('\n').map(it=>it.trim()); | ||
| 77 | command.isExtension = stack.find(it=>it.includes('/scripts/extensions/')) != null; | ||
| 78 | command.isThirdParty = stack.find(it=>it.includes('/scripts/extensions/third-party/')) != null; | ||
| 79 | if (command.isThirdParty) { | ||
| 80 | command.source = stack.find(it=>it.includes('/scripts/extensions/third-party/')).replace(/^.*?\/scripts\/extensions\/third-party\/([^/]+)\/.*$/, '$1'); | ||
| 81 | } else if (command.isExtension) { | ||
| 82 | command.source = stack.find(it=>it.includes('/scripts/extensions/')).replace(/^.*?\/scripts\/extensions\/([^/]+)\/.*$/, '$1'); | ||
| 83 | } else { | ||
| 84 | const idx = stack.findLastIndex(it=>it.includes('at SlashCommandParser.')) + 1; | ||
| 85 | command.source = stack[idx].replace(/^.*?\/((?:scripts\/)?(?:[^/]+)\.js).*$/, '$1'); | ||
| 86 | } | ||
| 87 | |||
| 73 | this.commands[command.name] = command; | 88 | this.commands[command.name] = command; |
| 74 | 89 | ||
| 75 | if (Array.isArray(command.aliases)) { | 90 | if (Array.isArray(command.aliases)) { |
| @@ -89,6 +104,7 @@ export class SlashCommandParser { | |||
| 89 | /**@type {string}*/ text; | 104 | /**@type {string}*/ text; |
| 90 | /**@type {number}*/ index; | 105 | /**@type {number}*/ index; |
| 91 | /**@type {SlashCommandAbortController}*/ abortController; | 106 | /**@type {SlashCommandAbortController}*/ abortController; |
| 107 | /**@type {SlashCommandDebugController}*/ debugController; | ||
| 92 | /**@type {SlashCommandScope}*/ scope; | 108 | /**@type {SlashCommandScope}*/ scope; |
| 93 | /**@type {SlashCommandClosure}*/ closure; | 109 | /**@type {SlashCommandClosure}*/ closure; |
| 94 | 110 | ||
| @@ -101,6 +117,8 @@ export class SlashCommandParser { | |||
| 101 | /**@type {SlashCommandExecutor[]}*/ commandIndex; | 117 | /**@type {SlashCommandExecutor[]}*/ commandIndex; |
| 102 | /**@type {SlashCommandScope[]}*/ scopeIndex; | 118 | /**@type {SlashCommandScope[]}*/ scopeIndex; |
| 103 | 119 | ||
| 120 | /**@type {string}*/ parserContext; | ||
| 121 | |||
| 104 | get userIndex() { return this.index; } | 122 | get userIndex() { return this.index; } |
| 105 | 123 | ||
| 106 | get ahead() { | 124 | get ahead() { |
| @@ -154,6 +172,21 @@ export class SlashCommandParser { | |||
| 154 | helpString: 'Write a comment.', | 172 | helpString: 'Write a comment.', |
| 155 | })); | 173 | })); |
| 156 | } | 174 | } |
| 175 | if (!Object.keys(this.commands).includes('breakpoint')) { | ||
| 176 | SlashCommandParser.addCommandObjectUnsafe(SlashCommand.fromProps({ name: 'breakpoint', | ||
| 177 | helpString: 'Set a breakpoint for debugging in the QR Editor.', | ||
| 178 | })); | ||
| 179 | } | ||
| 180 | if (!Object.keys(this.commands).includes('break')) { | ||
| 181 | SlashCommandParser.addCommandObjectUnsafe(SlashCommand.fromProps({ name: 'break', | ||
| 182 | helpString: 'Break out of a loop or closure executed through /run or /:', | ||
| 183 | unnamedArgumentList: [ | ||
| 184 | SlashCommandArgument.fromProps({ description: 'value to pass down the pipe instead of the current pipe value', | ||
| 185 | typeList: Object.values(ARGUMENT_TYPE), | ||
| 186 | }), | ||
| 187 | ], | ||
| 188 | })); | ||
| 189 | } | ||
| 157 | 190 | ||
| 158 | //TODO should not be re-registered from every instance | 191 | //TODO should not be re-registered from every instance |
| 159 | this.registerLanguage(); | 192 | this.registerLanguage(); |
| @@ -191,13 +224,19 @@ export class SlashCommandParser { | |||
| 191 | 224 | ||
| 192 | function getQuotedRunRegex() { | 225 | function getQuotedRunRegex() { |
| 193 | try { | 226 | try { |
| 194 | return new RegExp('(".+?(?<!\\\\)")|(\\S+?)'); | 227 | return new RegExp('(".+?(?<!\\\\)")|(\\S+?)(\\||$|\\s)'); |
| 195 | } catch { | 228 | } catch { |
| 196 | // fallback for browsers that don't support lookbehind | 229 | // fallback for browsers that don't support lookbehind |
| 197 | return /(".+?")|(\S+?)/; | 230 | return /(".+?")|(\S+?)(\||$|\s)/; |
| 198 | } | 231 | } |
| 199 | } | 232 | } |
| 200 | 233 | ||
| 234 | const BLOCK_COMMENT = { | ||
| 235 | scope: 'comment', | ||
| 236 | begin: /\/\*/, | ||
| 237 | end: /\*\|/, | ||
| 238 | contains: [], | ||
| 239 | }; | ||
| 201 | const COMMENT = { | 240 | const COMMENT = { |
| 202 | scope: 'comment', | 241 | scope: 'comment', |
| 203 | begin: /\/[/#]/, | 242 | begin: /\/[/#]/, |
| @@ -205,9 +244,29 @@ export class SlashCommandParser { | |||
| 205 | contains: [], | 244 | contains: [], |
| 206 | }; | 245 | }; |
| 207 | const ABORT = { | 246 | const ABORT = { |
| 208 | scope: 'abort', | 247 | begin: /\/(abort|breakpoint)/, |
| 209 | begin: /\/abort/, | 248 | beginScope: 'abort', |
| 210 | end: /\||$|:}/, | 249 | end: /\||$|(?=:})/, |
| 250 | excludeEnd: false, | ||
| 251 | returnEnd: true, | ||
| 252 | contains: [], | ||
| 253 | }; | ||
| 254 | const IMPORT = { | ||
| 255 | scope: 'command', | ||
| 256 | begin: /\/(import)/, | ||
| 257 | beginScope: 'keyword', | ||
| 258 | end: /\||$|(?=:})/, | ||
| 259 | excludeEnd: false, | ||
| 260 | returnEnd: true, | ||
| 261 | contains: [], | ||
| 262 | }; | ||
| 263 | const BREAK = { | ||
| 264 | scope: 'command', | ||
| 265 | begin: /\/(break)/, | ||
| 266 | beginScope: 'keyword', | ||
| 267 | end: /\||$|(?=:})/, | ||
| 268 | excludeEnd: false, | ||
| 269 | returnEnd: true, | ||
| 211 | contains: [], | 270 | contains: [], |
| 212 | }; | 271 | }; |
| 213 | const LET = { | 272 | const LET = { |
| @@ -218,26 +277,31 @@ export class SlashCommandParser { | |||
| 218 | 1: 'variable', | 277 | 1: 'variable', |
| 219 | }, | 278 | }, |
| 220 | end: /\||$|:}/, | 279 | end: /\||$|:}/, |
| 280 | excludeEnd: false, | ||
| 281 | returnEnd: true, | ||
| 221 | contains: [], | 282 | contains: [], |
| 222 | }; | 283 | }; |
| 223 | const SETVAR = { | 284 | const SETVAR = { |
| 224 | begin: /\/(setvar|setglobalvar)\s+/, | 285 | begin: /\/(setvar|setglobalvar)\s+/, |
| 225 | beginScope: 'variable', | 286 | beginScope: 'variable', |
| 226 | end: /\||$|:}/, | 287 | end: /\||$|:}/, |
| 227 | excludeEnd: true, | 288 | excludeEnd: false, |
| 289 | returnEnd: true, | ||
| 228 | contains: [], | 290 | contains: [], |
| 229 | }; | 291 | }; |
| 230 | const GETVAR = { | 292 | const GETVAR = { |
| 231 | begin: /\/(getvar|getglobalvar)\s+/, | 293 | begin: /\/(getvar|getglobalvar)\s+/, |
| 232 | beginScope: 'variable', | 294 | beginScope: 'variable', |
| 233 | end: /\||$|:}/, | 295 | end: /\||$|:}/, |
| 234 | excludeEnd: true, | 296 | excludeEnd: false, |
| 297 | returnEnd: true, | ||
| 235 | contains: [], | 298 | contains: [], |
| 236 | }; | 299 | }; |
| 237 | const RUN = { | 300 | const RUN = { |
| 238 | match: [ | 301 | match: [ |
| 239 | /\/:/, | 302 | /\/:/, |
| 240 | getQuotedRunRegex(), | 303 | getQuotedRunRegex(), |
| 304 | /\||$|(?=:})/, | ||
| 241 | ], | 305 | ], |
| 242 | className: { | 306 | className: { |
| 243 | 1: 'variable.language', | 307 | 1: 'variable.language', |
| @@ -250,7 +314,8 @@ export class SlashCommandParser { | |||
| 250 | begin: /\/\S+/, | 314 | begin: /\/\S+/, |
| 251 | beginScope: 'title.function', | 315 | beginScope: 'title.function', |
| 252 | end: /\||$|(?=:})/, | 316 | end: /\||$|(?=:})/, |
| 253 | excludeEnd: true, | 317 | excludeEnd: false, |
| 318 | returnEnd: true, | ||
| 254 | contains: [], // defined later | 319 | contains: [], // defined later |
| 255 | }; | 320 | }; |
| 256 | const CLOSURE = { | 321 | const CLOSURE = { |
| @@ -271,6 +336,19 @@ export class SlashCommandParser { | |||
| 271 | begin: /{{/, | 336 | begin: /{{/, |
| 272 | end: /}}/, | 337 | end: /}}/, |
| 273 | }; | 338 | }; |
| 339 | const PIPEBREAK = { | ||
| 340 | beginScope: 'pipebreak', | ||
| 341 | begin: /\|\|/, | ||
| 342 | end: '', | ||
| 343 | }; | ||
| 344 | const PIPE = { | ||
| 345 | beginScope: 'pipe', | ||
| 346 | begin: /\|/, | ||
| 347 | end: '', | ||
| 348 | }; | ||
| 349 | BLOCK_COMMENT.contains.push( | ||
| 350 | BLOCK_COMMENT, | ||
| 351 | ); | ||
| 274 | RUN.contains.push( | 352 | RUN.contains.push( |
| 275 | hljs.BACKSLASH_ESCAPE, | 353 | hljs.BACKSLASH_ESCAPE, |
| 276 | NAMED_ARG, | 354 | NAMED_ARG, |
| @@ -279,6 +357,22 @@ export class SlashCommandParser { | |||
| 279 | MACRO, | 357 | MACRO, |
| 280 | CLOSURE, | 358 | CLOSURE, |
| 281 | ); | 359 | ); |
| 360 | IMPORT.contains.push( | ||
| 361 | hljs.BACKSLASH_ESCAPE, | ||
| 362 | NAMED_ARG, | ||
| 363 | NUMBER, | ||
| 364 | MACRO, | ||
| 365 | CLOSURE, | ||
| 366 | hljs.QUOTE_STRING_MODE, | ||
| 367 | ); | ||
| 368 | BREAK.contains.push( | ||
| 369 | hljs.BACKSLASH_ESCAPE, | ||
| 370 | NAMED_ARG, | ||
| 371 | NUMBER, | ||
| 372 | MACRO, | ||
| 373 | CLOSURE, | ||
| 374 | hljs.QUOTE_STRING_MODE, | ||
| 375 | ); | ||
| 282 | LET.contains.push( | 376 | LET.contains.push( |
| 283 | hljs.BACKSLASH_ESCAPE, | 377 | hljs.BACKSLASH_ESCAPE, |
| 284 | NAMED_ARG, | 378 | NAMED_ARG, |
| @@ -303,6 +397,14 @@ export class SlashCommandParser { | |||
| 303 | MACRO, | 397 | MACRO, |
| 304 | CLOSURE, | 398 | CLOSURE, |
| 305 | ); | 399 | ); |
| 400 | ABORT.contains.push( | ||
| 401 | hljs.BACKSLASH_ESCAPE, | ||
| 402 | NAMED_ARG, | ||
| 403 | NUMBER, | ||
| 404 | MACRO, | ||
| 405 | CLOSURE, | ||
| 406 | hljs.QUOTE_STRING_MODE, | ||
| 407 | ); | ||
| 306 | COMMAND.contains.push( | 408 | COMMAND.contains.push( |
| 307 | hljs.BACKSLASH_ESCAPE, | 409 | hljs.BACKSLASH_ESCAPE, |
| 308 | NAMED_ARG, | 410 | NAMED_ARG, |
| @@ -313,8 +415,11 @@ export class SlashCommandParser { | |||
| 313 | ); | 415 | ); |
| 314 | CLOSURE.contains.push( | 416 | CLOSURE.contains.push( |
| 315 | hljs.BACKSLASH_ESCAPE, | 417 | hljs.BACKSLASH_ESCAPE, |
| 418 | BLOCK_COMMENT, | ||
| 316 | COMMENT, | 419 | COMMENT, |
| 317 | ABORT, | 420 | ABORT, |
| 421 | IMPORT, | ||
| 422 | BREAK, | ||
| 318 | NAMED_ARG, | 423 | NAMED_ARG, |
| 319 | NUMBER, | 424 | NUMBER, |
| 320 | MACRO, | 425 | MACRO, |
| @@ -325,20 +430,27 @@ export class SlashCommandParser { | |||
| 325 | COMMAND, | 430 | COMMAND, |
| 326 | 'self', | 431 | 'self', |
| 327 | hljs.QUOTE_STRING_MODE, | 432 | hljs.QUOTE_STRING_MODE, |
| 433 | PIPEBREAK, | ||
| 434 | PIPE, | ||
| 328 | ); | 435 | ); |
| 329 | hljs.registerLanguage('stscript', ()=>({ | 436 | hljs.registerLanguage('stscript', ()=>({ |
| 330 | case_insensitive: false, | 437 | case_insensitive: false, |
| 331 | keywords: ['|'], | 438 | keywords: [], |
| 332 | contains: [ | 439 | contains: [ |
| 333 | hljs.BACKSLASH_ESCAPE, | 440 | hljs.BACKSLASH_ESCAPE, |
| 441 | BLOCK_COMMENT, | ||
| 334 | COMMENT, | 442 | COMMENT, |
| 335 | ABORT, | 443 | ABORT, |
| 444 | IMPORT, | ||
| 445 | BREAK, | ||
| 336 | RUN, | 446 | RUN, |
| 337 | LET, | 447 | LET, |
| 338 | GETVAR, | 448 | GETVAR, |
| 339 | SETVAR, | 449 | SETVAR, |
| 340 | COMMAND, | 450 | COMMAND, |
| 341 | CLOSURE, | 451 | CLOSURE, |
| 452 | PIPEBREAK, | ||
| 453 | PIPE, | ||
| 342 | ], | 454 | ], |
| 343 | })); | 455 | })); |
| 344 | } | 456 | } |
| @@ -415,7 +527,7 @@ export class SlashCommandParser { | |||
| 415 | ); | 527 | ); |
| 416 | return result; | 528 | return result; |
| 417 | } | 529 | } |
| 418 | const result = new SlashCommandAutoCompleteNameResult(executor, this.commands); | 530 | const result = new SlashCommandAutoCompleteNameResult(executor, this.scopeIndex[this.commandIndex.indexOf(executor)], this.commands); |
| 419 | return result; | 531 | return result; |
| 420 | } | 532 | } |
| 421 | return null; | 533 | return null; |
| @@ -515,11 +627,14 @@ export class SlashCommandParser { | |||
| 515 | } | 627 | } |
| 516 | 628 | ||
| 517 | replaceGetvar(value) { | 629 | replaceGetvar(value) { |
| 518 | return value.replace(/{{(get(?:global)?var)::([^}]+)}}/gi, (_, cmd, name) => { | 630 | return value.replace(/{{(get(?:global)?var)::([^}]+)}}/gi, (match, cmd, name, idx) => { |
| 519 | name = name.trim(); | 631 | name = name.trim(); |
| 632 | const startIdx = this.index - value.length + idx; | ||
| 633 | const endIdx = this.index - value.length + idx + match.length; | ||
| 520 | // store pipe | 634 | // store pipe |
| 521 | const pipeName = `_PARSER_${uuidv4()}`; | 635 | const pipeName = `_PARSER_PIPE_${uuidv4()}`; |
| 522 | const storePipe = new SlashCommandExecutor(null); { | 636 | const storePipe = new SlashCommandExecutor(startIdx); { |
| 637 | storePipe.end = endIdx; | ||
| 523 | storePipe.command = this.commands['let']; | 638 | storePipe.command = this.commands['let']; |
| 524 | storePipe.name = 'let'; | 639 | storePipe.name = 'let'; |
| 525 | const nameAss = new SlashCommandUnnamedArgumentAssignment(); | 640 | const nameAss = new SlashCommandUnnamedArgumentAssignment(); |
| @@ -530,17 +645,19 @@ export class SlashCommandParser { | |||
| 530 | this.closure.executorList.push(storePipe); | 645 | this.closure.executorList.push(storePipe); |
| 531 | } | 646 | } |
| 532 | // getvar / getglobalvar | 647 | // getvar / getglobalvar |
| 533 | const getvar = new SlashCommandExecutor(null); { | 648 | const getvar = new SlashCommandExecutor(startIdx); { |
| 649 | getvar.end = endIdx; | ||
| 534 | getvar.command = this.commands[cmd]; | 650 | getvar.command = this.commands[cmd]; |
| 535 | getvar.name = 'cmd'; | 651 | getvar.name = cmd; |
| 536 | const nameAss = new SlashCommandUnnamedArgumentAssignment(); | 652 | const nameAss = new SlashCommandUnnamedArgumentAssignment(); |
| 537 | nameAss.value = name; | 653 | nameAss.value = name; |
| 538 | getvar.unnamedArgumentList = [nameAss]; | 654 | getvar.unnamedArgumentList = [nameAss]; |
| 539 | this.closure.executorList.push(getvar); | 655 | this.closure.executorList.push(getvar); |
| 540 | } | 656 | } |
| 541 | // set to temp scoped var | 657 | // set to temp scoped var |
| 542 | const varName = `_PARSER_${uuidv4()}`; | 658 | const varName = `_PARSER_VAR_${uuidv4()}`; |
| 543 | const setvar = new SlashCommandExecutor(null); { | 659 | const setvar = new SlashCommandExecutor(startIdx); { |
| 660 | setvar.end = endIdx; | ||
| 544 | setvar.command = this.commands['let']; | 661 | setvar.command = this.commands['let']; |
| 545 | setvar.name = 'let'; | 662 | setvar.name = 'let'; |
| 546 | const nameAss = new SlashCommandUnnamedArgumentAssignment(); | 663 | const nameAss = new SlashCommandUnnamedArgumentAssignment(); |
| @@ -551,7 +668,8 @@ export class SlashCommandParser { | |||
| 551 | this.closure.executorList.push(setvar); | 668 | this.closure.executorList.push(setvar); |
| 552 | } | 669 | } |
| 553 | // return pipe | 670 | // return pipe |
| 554 | const returnPipe = new SlashCommandExecutor(null); { | 671 | const returnPipe = new SlashCommandExecutor(startIdx); { |
| 672 | returnPipe.end = endIdx; | ||
| 555 | returnPipe.command = this.commands['return']; | 673 | returnPipe.command = this.commands['return']; |
| 556 | returnPipe.name = 'return'; | 674 | returnPipe.name = 'return'; |
| 557 | const varAss = new SlashCommandUnnamedArgumentAssignment(); | 675 | const varAss = new SlashCommandUnnamedArgumentAssignment(); |
| @@ -564,12 +682,13 @@ export class SlashCommandParser { | |||
| 564 | } | 682 | } |
| 565 | 683 | ||
| 566 | 684 | ||
| 567 | parse(text, verifyCommandNames = true, flags = null, abortController = null) { | 685 | parse(text, verifyCommandNames = true, flags = null, abortController = null, debugController = null) { |
| 568 | this.verifyCommandNames = verifyCommandNames; | 686 | this.verifyCommandNames = verifyCommandNames; |
| 569 | for (const key of Object.keys(PARSER_FLAG)) { | 687 | for (const key of Object.keys(PARSER_FLAG)) { |
| 570 | this.flags[PARSER_FLAG[key]] = flags?.[PARSER_FLAG[key]] ?? power_user.stscript.parser.flags[PARSER_FLAG[key]] ?? false; | 688 | this.flags[PARSER_FLAG[key]] = flags?.[PARSER_FLAG[key]] ?? power_user.stscript.parser.flags[PARSER_FLAG[key]] ?? false; |
| 571 | } | 689 | } |
| 572 | this.abortController = abortController; | 690 | this.abortController = abortController; |
| 691 | this.debugController = debugController; | ||
| 573 | this.text = text; | 692 | this.text = text; |
| 574 | this.index = 0; | 693 | this.index = 0; |
| 575 | this.scope = null; | 694 | this.scope = null; |
| @@ -577,6 +696,7 @@ export class SlashCommandParser { | |||
| 577 | this.commandIndex = []; | 696 | this.commandIndex = []; |
| 578 | this.scopeIndex = []; | 697 | this.scopeIndex = []; |
| 579 | this.macroIndex = []; | 698 | this.macroIndex = []; |
| 699 | this.parserContext = uuidv4(); | ||
| 580 | const closure = this.parseClosure(true); | 700 | const closure = this.parseClosure(true); |
| 581 | return closure; | 701 | return closure; |
| 582 | } | 702 | } |
| @@ -604,8 +724,12 @@ export class SlashCommandParser { | |||
| 604 | if (!isRoot) this.take(2); // discard opening {: | 724 | if (!isRoot) this.take(2); // discard opening {: |
| 605 | const textStart = this.index; | 725 | const textStart = this.index; |
| 606 | let closure = new SlashCommandClosure(this.scope); | 726 | let closure = new SlashCommandClosure(this.scope); |
| 727 | closure.parserContext = this.parserContext; | ||
| 728 | closure.fullText = this.text; | ||
| 607 | closure.abortController = this.abortController; | 729 | closure.abortController = this.abortController; |
| 730 | closure.debugController = this.debugController; | ||
| 608 | this.scope = closure.scope; | 731 | this.scope = closure.scope; |
| 732 | const oldClosure = this.closure; | ||
| 609 | this.closure = closure; | 733 | this.closure = closure; |
| 610 | this.discardWhitespace(); | 734 | this.discardWhitespace(); |
| 611 | while (this.testNamedArgument()) { | 735 | while (this.testNamedArgument()) { |
| @@ -615,7 +739,9 @@ export class SlashCommandParser { | |||
| 615 | this.discardWhitespace(); | 739 | this.discardWhitespace(); |
| 616 | } | 740 | } |
| 617 | while (!this.testClosureEnd()) { | 741 | while (!this.testClosureEnd()) { |
| 618 | if (this.testComment()) { | 742 | if (this.testBlockComment()) { |
| 743 | this.parseBlockComment(); | ||
| 744 | } else if (this.testComment()) { | ||
| 619 | this.parseComment(); | 745 | this.parseComment(); |
| 620 | } else if (this.testParserFlag()) { | 746 | } else if (this.testParserFlag()) { |
| 621 | this.parseParserFlag(); | 747 | this.parseParserFlag(); |
| @@ -623,6 +749,14 @@ export class SlashCommandParser { | |||
| 623 | const cmd = this.parseRunShorthand(); | 749 | const cmd = this.parseRunShorthand(); |
| 624 | closure.executorList.push(cmd); | 750 | closure.executorList.push(cmd); |
| 625 | injectPipe = true; | 751 | injectPipe = true; |
| 752 | } else if (this.testBreakPoint()) { | ||
| 753 | const bp = this.parseBreakPoint(); | ||
| 754 | if (this.debugController) { | ||
| 755 | closure.executorList.push(bp); | ||
| 756 | } | ||
| 757 | } else if (this.testBreak()) { | ||
| 758 | const b = this.parseBreak(); | ||
| 759 | closure.executorList.push(b); | ||
| 626 | } else if (this.testCommand()) { | 760 | } else if (this.testCommand()) { |
| 627 | const cmd = this.parseCommand(); | 761 | const cmd = this.parseCommand(); |
| 628 | cmd.injectPipe = injectPipe; | 762 | cmd.injectPipe = injectPipe; |
| @@ -651,14 +785,83 @@ export class SlashCommandParser { | |||
| 651 | } | 785 | } |
| 652 | closureIndexEntry.end = this.index - 1; | 786 | closureIndexEntry.end = this.index - 1; |
| 653 | this.scope = closure.scope.parent; | 787 | this.scope = closure.scope.parent; |
| 788 | this.closure = oldClosure ?? closure; | ||
| 654 | return closure; | 789 | return closure; |
| 655 | } | 790 | } |
| 656 | 791 | ||
| 792 | testBreakPoint() { | ||
| 793 | return this.testSymbol(/\/breakpoint\s*\|/); | ||
| 794 | } | ||
| 795 | parseBreakPoint() { | ||
| 796 | const cmd = new SlashCommandBreakPoint(); | ||
| 797 | cmd.name = 'breakpoint'; | ||
| 798 | cmd.command = this.commands['breakpoint']; | ||
| 799 | cmd.start = this.index + 1; | ||
| 800 | this.take('/breakpoint'.length); | ||
| 801 | cmd.end = this.index; | ||
| 802 | this.commandIndex.push(cmd); | ||
| 803 | this.scopeIndex.push(this.scope.getCopy()); | ||
| 804 | return cmd; | ||
| 805 | } | ||
| 806 | |||
| 807 | testBreak() { | ||
| 808 | return this.testSymbol(/\/break(\s|\||$)/); | ||
| 809 | } | ||
| 810 | parseBreak() { | ||
| 811 | const cmd = new SlashCommandBreak(); | ||
| 812 | cmd.name = 'break'; | ||
| 813 | cmd.command = this.commands['break']; | ||
| 814 | cmd.start = this.index + 1; | ||
| 815 | this.take('/break'.length); | ||
| 816 | this.discardWhitespace(); | ||
| 817 | if (this.testUnnamedArgument()) { | ||
| 818 | cmd.unnamedArgumentList.push(...this.parseUnnamedArgument()); | ||
| 819 | } | ||
| 820 | cmd.end = this.index; | ||
| 821 | this.commandIndex.push(cmd); | ||
| 822 | this.scopeIndex.push(this.scope.getCopy()); | ||
| 823 | return cmd; | ||
| 824 | } | ||
| 825 | |||
| 826 | testBlockComment() { | ||
| 827 | return this.testSymbol('/*'); | ||
| 828 | } | ||
| 829 | testBlockCommentEnd() { | ||
| 830 | if (!this.verifyCommandNames) { | ||
| 831 | if (this.index >= this.text.length) return true; | ||
| 832 | } else { | ||
| 833 | if (this.ahead.length < 1) throw new SlashCommandParserError(`Unclosed block comment at position ${this.userIndex}`, this.text, this.index); | ||
| 834 | } | ||
| 835 | return this.testSymbol('*|'); | ||
| 836 | } | ||
| 837 | parseBlockComment() { | ||
| 838 | const start = this.index + 1; | ||
| 839 | const cmd = new SlashCommandExecutor(start); | ||
| 840 | cmd.command = this.commands['*']; | ||
| 841 | this.commandIndex.push(cmd); | ||
| 842 | this.scopeIndex.push(this.scope.getCopy()); | ||
| 843 | this.take(); // discard "/" | ||
| 844 | cmd.name = this.take(); //set "*" as name | ||
| 845 | while (!this.testBlockCommentEnd()) { | ||
| 846 | if (this.testBlockComment()) { | ||
| 847 | this.parseBlockComment(); | ||
| 848 | } | ||
| 849 | this.take(); | ||
| 850 | } | ||
| 851 | this.take(2); // take closing "*|" | ||
| 852 | cmd.end = this.index - 1; | ||
| 853 | } | ||
| 854 | |||
| 657 | testComment() { | 855 | testComment() { |
| 658 | return this.testSymbol(/\/[/#]/); | 856 | return this.testSymbol(/\/[/#]/); |
| 659 | } | 857 | } |
| 660 | testCommentEnd() { | 858 | testCommentEnd() { |
| 661 | return this.testCommandEnd(); | 859 | if (!this.verifyCommandNames) { |
| 860 | if (this.index >= this.text.length) return true; | ||
| 861 | } else { | ||
| 862 | if (this.endOfText) throw new SlashCommandParserError(`Unclosed comment at position ${this.userIndex}`, this.text, this.index); | ||
| 863 | } | ||
| 864 | return this.testSymbol('|'); | ||
| 662 | } | 865 | } |
| 663 | parseComment() { | 866 | parseComment() { |
| 664 | const start = this.index + 1; | 867 | const start = this.index + 1; |
| @@ -719,11 +922,13 @@ export class SlashCommandParser { | |||
| 719 | else assignment.value = this.parseValue(); | 922 | else assignment.value = this.parseValue(); |
| 720 | cmd.unnamedArgumentList = [assignment]; | 923 | cmd.unnamedArgumentList = [assignment]; |
| 721 | this.discardWhitespace(); | 924 | this.discardWhitespace(); |
| 925 | cmd.startNamedArgs = this.index; | ||
| 722 | while (this.testNamedArgument()) { | 926 | while (this.testNamedArgument()) { |
| 723 | const arg = this.parseNamedArgument(); | 927 | const arg = this.parseNamedArgument(); |
| 724 | cmd.namedArgumentList.push(arg); | 928 | cmd.namedArgumentList.push(arg); |
| 725 | this.discardWhitespace(); | 929 | this.discardWhitespace(); |
| 726 | } | 930 | } |
| 931 | cmd.endNamedArgs = this.index; | ||
| 727 | this.discardWhitespace(); | 932 | this.discardWhitespace(); |
| 728 | // /run shorthand does not take unnamed arguments (the command name practically *is* the unnamed argument) | 933 | // /run shorthand does not take unnamed arguments (the command name practically *is* the unnamed argument) |
| 729 | if (this.testRunShorthandEnd()) { | 934 | if (this.testRunShorthandEnd()) { |
| @@ -761,10 +966,10 @@ export class SlashCommandParser { | |||
| 761 | this.discardWhitespace(); | 966 | this.discardWhitespace(); |
| 762 | } | 967 | } |
| 763 | this.discardWhitespace(); | 968 | this.discardWhitespace(); |
| 764 | cmd.startUnnamedArgs = this.index; | 969 | cmd.startUnnamedArgs = this.index - (/\s(\s*)$/s.exec(this.behind)?.[1]?.length ?? 0); |
| 765 | cmd.endUnnamedArgs = this.index; | 970 | cmd.endUnnamedArgs = this.index; |
| 766 | if (this.testUnnamedArgument()) { | 971 | if (this.testUnnamedArgument()) { |
| 767 | cmd.unnamedArgumentList = this.parseUnnamedArgument(cmd.command?.unnamedArgumentList?.length && cmd?.command?.splitUnnamedArgument); | 972 | cmd.unnamedArgumentList = this.parseUnnamedArgument(cmd.command?.unnamedArgumentList?.length && cmd?.command?.splitUnnamedArgument, cmd?.command?.splitUnnamedArgumentCount); |
| 768 | cmd.endUnnamedArgs = this.index; | 973 | cmd.endUnnamedArgs = this.index; |
| 769 | if (cmd.name == 'let') { | 974 | if (cmd.name == 'let') { |
| 770 | const keyArg = cmd.namedArgumentList.find(it=>it.name == 'key'); | 975 | const keyArg = cmd.namedArgumentList.find(it=>it.name == 'key'); |
| @@ -773,6 +978,17 @@ export class SlashCommandParser { | |||
| 773 | } else if (typeof cmd.unnamedArgumentList[0]?.value == 'string') { | 978 | } else if (typeof cmd.unnamedArgumentList[0]?.value == 'string') { |
| 774 | this.scope.variableNames.push(cmd.unnamedArgumentList[0].value); | 979 | this.scope.variableNames.push(cmd.unnamedArgumentList[0].value); |
| 775 | } | 980 | } |
| 981 | } else if (cmd.name == 'import') { | ||
| 982 | const value = /**@type {string[]}*/(cmd.unnamedArgumentList.map(it=>it.value)); | ||
| 983 | for (let i = 0; i < value.length; i++) { | ||
| 984 | const srcName = value[i]; | ||
| 985 | let dstName = srcName; | ||
| 986 | if (i + 2 < value.length && value[i + 1] == 'as') { | ||
| 987 | dstName = value[i + 2]; | ||
| 988 | i += 2; | ||
| 989 | } | ||
| 990 | this.scope.variableNames.push(dstName); | ||
| 991 | } | ||
| 776 | } | 992 | } |
| 777 | } | 993 | } |
| 778 | if (this.testCommandEnd()) { | 994 | if (this.testCommandEnd()) { |
| @@ -813,25 +1029,61 @@ export class SlashCommandParser { | |||
| 813 | testUnnamedArgumentEnd() { | 1029 | testUnnamedArgumentEnd() { |
| 814 | return this.testCommandEnd(); | 1030 | return this.testCommandEnd(); |
| 815 | } | 1031 | } |
| 816 | parseUnnamedArgument(split) { | 1032 | parseUnnamedArgument(split, splitCount = null) { |
| 1033 | const wasSplit = split; | ||
| 817 | /**@type {SlashCommandClosure|String}*/ | 1034 | /**@type {SlashCommandClosure|String}*/ |
| 818 | let value = this.jumpedEscapeSequence ? this.take() : ''; // take the first, already tested, char if it is an escaped one | 1035 | let value = this.jumpedEscapeSequence ? this.take() : ''; // take the first, already tested, char if it is an escaped one |
| 819 | let isList = split; | 1036 | let isList = split; |
| 820 | let listValues = []; | 1037 | let listValues = []; |
| 1038 | let listQuoted = []; // keep track of which listValues were quoted | ||
| 821 | /**@type {SlashCommandUnnamedArgumentAssignment}*/ | 1039 | /**@type {SlashCommandUnnamedArgumentAssignment}*/ |
| 822 | let assignment = new SlashCommandUnnamedArgumentAssignment(); | 1040 | let assignment = new SlashCommandUnnamedArgumentAssignment(); |
| 823 | assignment.start = this.index; | 1041 | assignment.start = this.index; |
| 1042 | if (!split && this.testQuotedValue()) { | ||
| 1043 | // if the next bit is a quoted value, take the whole value and gather contents as a list | ||
| 1044 | assignment.value = this.parseQuotedValue(); | ||
| 1045 | assignment.end = this.index; | ||
| 1046 | isList = true; | ||
| 1047 | listValues.push(assignment); | ||
| 1048 | listQuoted.push(true); | ||
| 1049 | assignment = new SlashCommandUnnamedArgumentAssignment(); | ||
| 1050 | assignment.start = this.index; | ||
| 1051 | } | ||
| 824 | while (!this.testUnnamedArgumentEnd()) { | 1052 | while (!this.testUnnamedArgumentEnd()) { |
| 1053 | if (split && splitCount && listValues.length >= splitCount) { | ||
| 1054 | // the split count has just been reached: stop splitting, the rest is one singular value | ||
| 1055 | split = false; | ||
| 1056 | if (this.testQuotedValue()) { | ||
| 1057 | // if the next bit is a quoted value, take the whole value | ||
| 1058 | assignment.value = this.parseQuotedValue(); | ||
| 1059 | assignment.end = this.index; | ||
| 1060 | listValues.push(assignment); | ||
| 1061 | listQuoted.push(true); | ||
| 1062 | assignment = new SlashCommandUnnamedArgumentAssignment(); | ||
| 1063 | assignment.start = this.index; | ||
| 1064 | } | ||
| 1065 | } | ||
| 825 | if (this.testClosure()) { | 1066 | if (this.testClosure()) { |
| 826 | isList = true; | 1067 | isList = true; |
| 827 | if (value.length > 0) { | 1068 | if (value.length > 0) { |
| 828 | this.indexMacros(this.index - value.length, value); | 1069 | this.indexMacros(this.index - value.length, value); |
| 829 | assignment.value = value; | 1070 | assignment.value = value; |
| 830 | listValues.push(assignment); | 1071 | listValues.push(assignment); |
| 1072 | listQuoted.push(false); | ||
| 1073 | assignment = new SlashCommandUnnamedArgumentAssignment(); | ||
| 1074 | assignment.start = this.index; | ||
| 1075 | if (!split && this.testQuotedValue()) { | ||
| 1076 | // if where currently not splitting and the next bit is a quoted value, take the whole value | ||
| 1077 | assignment.value = this.parseQuotedValue(); | ||
| 1078 | assignment.end = this.index; | ||
| 1079 | listValues.push(assignment); | ||
| 1080 | listQuoted.push(true); | ||
| 831 | assignment = new SlashCommandUnnamedArgumentAssignment(); | 1081 | assignment = new SlashCommandUnnamedArgumentAssignment(); |
| 832 | assignment.start = this.index; | 1082 | assignment.start = this.index; |
| 1083 | } else { | ||
| 833 | value = ''; | 1084 | value = ''; |
| 834 | } | 1085 | } |
| 1086 | } | ||
| 835 | assignment.start = this.index; | 1087 | assignment.start = this.index; |
| 836 | assignment.value = this.parseClosure(); | 1088 | assignment.value = this.parseClosure(); |
| 837 | assignment.end = this.index; | 1089 | assignment.end = this.index; |
| @@ -845,18 +1097,21 @@ export class SlashCommandParser { | |||
| 845 | assignment.value = this.parseQuotedValue(); | 1097 | assignment.value = this.parseQuotedValue(); |
| 846 | assignment.end = this.index; | 1098 | assignment.end = this.index; |
| 847 | listValues.push(assignment); | 1099 | listValues.push(assignment); |
| 1100 | listQuoted.push(true); | ||
| 848 | assignment = new SlashCommandUnnamedArgumentAssignment(); | 1101 | assignment = new SlashCommandUnnamedArgumentAssignment(); |
| 849 | } else if (this.testListValue()) { | 1102 | } else if (this.testListValue()) { |
| 850 | assignment.start = this.index; | 1103 | assignment.start = this.index; |
| 851 | assignment.value = this.parseListValue(); | 1104 | assignment.value = this.parseListValue(); |
| 852 | assignment.end = this.index; | 1105 | assignment.end = this.index; |
| 853 | listValues.push(assignment); | 1106 | listValues.push(assignment); |
| 1107 | listQuoted.push(false); | ||
| 854 | assignment = new SlashCommandUnnamedArgumentAssignment(); | 1108 | assignment = new SlashCommandUnnamedArgumentAssignment(); |
| 855 | } else if (this.testValue()) { | 1109 | } else if (this.testValue()) { |
| 856 | assignment.start = this.index; | 1110 | assignment.start = this.index; |
| 857 | assignment.value = this.parseValue(); | 1111 | assignment.value = this.parseValue(); |
| 858 | assignment.end = this.index; | 1112 | assignment.end = this.index; |
| 859 | listValues.push(assignment); | 1113 | listValues.push(assignment); |
| 1114 | listQuoted.push(false); | ||
| 860 | assignment = new SlashCommandUnnamedArgumentAssignment(); | 1115 | assignment = new SlashCommandUnnamedArgumentAssignment(); |
| 861 | } else { | 1116 | } else { |
| 862 | throw new SlashCommandParserError(`Unexpected end of unnamed argument at index ${this.userIndex}.`); | 1117 | throw new SlashCommandParserError(`Unexpected end of unnamed argument at index ${this.userIndex}.`); |
| @@ -870,8 +1125,48 @@ export class SlashCommandParser { | |||
| 870 | if (isList && value.length > 0) { | 1125 | if (isList && value.length > 0) { |
| 871 | assignment.value = value; | 1126 | assignment.value = value; |
| 872 | listValues.push(assignment); | 1127 | listValues.push(assignment); |
| 1128 | listQuoted.push(false); | ||
| 873 | } | 1129 | } |
| 874 | if (isList) { | 1130 | if (isList) { |
| 1131 | const firstVal = listValues[0]; | ||
| 1132 | if (typeof firstVal?.value == 'string') { | ||
| 1133 | if (!listQuoted[0]) { | ||
| 1134 | // only trim the first part if it wasn't quoted | ||
| 1135 | firstVal.value = firstVal.value.trimStart(); | ||
| 1136 | } | ||
| 1137 | if (firstVal.value.length == 0) { | ||
| 1138 | listValues.shift(); | ||
| 1139 | listQuoted.shift(); | ||
| 1140 | } | ||
| 1141 | } | ||
| 1142 | const lastVal = listValues.slice(-1)[0]; | ||
| 1143 | if (typeof lastVal?.value == 'string') { | ||
| 1144 | if (!listQuoted.slice(-1)[0]) { | ||
| 1145 | // only trim the last part if it wasn't quoted | ||
| 1146 | lastVal.value = lastVal.value.trimEnd(); | ||
| 1147 | } | ||
| 1148 | if (lastVal.value.length == 0) { | ||
| 1149 | listValues.pop(); | ||
| 1150 | listQuoted.pop(); | ||
| 1151 | } | ||
| 1152 | } | ||
| 1153 | if (wasSplit && splitCount && splitCount + 1 < listValues.length) { | ||
| 1154 | // if split with a split count and there are more values than expected | ||
| 1155 | // -> should be result of quoting + additional (non-whitespace) text | ||
| 1156 | // -> join the parts into one and restore quotes | ||
| 1157 | const joined = new SlashCommandUnnamedArgumentAssignment(); | ||
| 1158 | joined.start = listValues[splitCount].start; | ||
| 1159 | joined.end = listValues.slice(-1)[0].end; | ||
| 1160 | joined.value = ''; | ||
| 1161 | for (let i = splitCount; i < listValues.length; i++) { | ||
| 1162 | if (listQuoted[i]) joined.value += `"${listValues[i].value}"`; | ||
| 1163 | else joined.value += listValues[i].value; | ||
| 1164 | } | ||
| 1165 | listValues = [ | ||
| 1166 | ...listValues.slice(0, splitCount), | ||
| 1167 | joined, | ||
| 1168 | ]; | ||
| 1169 | } | ||
| 875 | return listValues; | 1170 | return listValues; |
| 876 | } | 1171 | } |
| 877 | this.indexMacros(this.index - value.length, value); | 1172 | this.indexMacros(this.index - value.length, value); |
| @@ -38,9 +38,11 @@ export class SlashCommandScope { | |||
| 38 | } | 38 | } |
| 39 | 39 | ||
| 40 | 40 | ||
| 41 | setMacro(key, value) { | 41 | setMacro(key, value, overwrite = true) { |
| 42 | if (overwrite || !this.macroList.find(it=>it.key == key)) { | ||
| 42 | this.macros[key] = value; | 43 | this.macros[key] = value; |
| 43 | } | 44 | } |
| 45 | } | ||
| 44 | 46 | ||
| 45 | 47 | ||
| 46 | existsVariableInScope(key) { | 48 | existsVariableInScope(key) { |
| @@ -95,7 +97,7 @@ export class SlashCommandScope { | |||
| 95 | return v ?? ''; | 97 | return v ?? ''; |
| 96 | } else { | 98 | } else { |
| 97 | const value = this.variables[key]; | 99 | const value = this.variables[key]; |
| 98 | return (value === '' || isNaN(Number(value))) ? (value || '') : Number(value); | 100 | return (value?.trim?.() === '' || isNaN(Number(value))) ? (value || '') : Number(value); |
| 99 | } | 101 | } |
| 100 | } | 102 | } |
| 101 | if (this.parent) { | 103 | if (this.parent) { |
| @@ -3,7 +3,7 @@ import { getRequestHeaders } from '../script.js'; | |||
| 3 | import { isMobile } from './RossAscends-mods.js'; | 3 | import { isMobile } from './RossAscends-mods.js'; |
| 4 | import { collapseNewlines } from './power-user.js'; | 4 | import { collapseNewlines } from './power-user.js'; |
| 5 | import { debounce_timeout } from './constants.js'; | 5 | import { debounce_timeout } from './constants.js'; |
| 6 | import { Popup } from './popup.js'; | 6 | import { Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js'; |
| 7 | 7 | ||
| 8 | /** | 8 | /** |
| 9 | * Pagination status string template. | 9 | * Pagination status string template. |
| @@ -1929,3 +1929,75 @@ export function getFreeName(name, list, numberFormatter = (n) => ` #${n}`) { | |||
| 1929 | } | 1929 | } |
| 1930 | return `${name}${numberFormatter(counter)}`; | 1930 | return `${name}${numberFormatter(counter)}`; |
| 1931 | } | 1931 | } |
| 1932 | |||
| 1933 | export async function fetchFaFile(name) { | ||
| 1934 | const style = document.createElement('style'); | ||
| 1935 | style.innerHTML = await (await fetch(`/css/${name}`)).text(); | ||
| 1936 | document.head.append(style); | ||
| 1937 | const sheet = style.sheet; | ||
| 1938 | style.remove(); | ||
| 1939 | return [...sheet.cssRules].filter(it=>it.style?.content).map(it=>it.selectorText.split('::').shift().slice(1)); | ||
| 1940 | } | ||
| 1941 | export async function fetchFa() { | ||
| 1942 | return [...new Set((await Promise.all([ | ||
| 1943 | fetchFaFile('fontawesome.min.css'), | ||
| 1944 | ])).flat())]; | ||
| 1945 | } | ||
| 1946 | /** | ||
| 1947 | * Opens a popup with all the available Font Awesome icons and returns the selected icon's name. | ||
| 1948 | * @prop {string[]} customList A custom list of Font Awesome icons to use instead of all available icons. | ||
| 1949 | * @returns {Promise<string>} The icon name (fa-pencil) or null if cancelled. | ||
| 1950 | */ | ||
| 1951 | export async function showFontAwesomePicker(customList = null) { | ||
| 1952 | const faList = customList ?? await fetchFa(); | ||
| 1953 | const fas = {}; | ||
| 1954 | const dom = document.createElement('div'); { | ||
| 1955 | dom.classList.add('faPicker-container'); | ||
| 1956 | const search = document.createElement('div'); { | ||
| 1957 | search.classList.add('faQuery-container'); | ||
| 1958 | const qry = document.createElement('input'); { | ||
| 1959 | qry.classList.add('text_pole'); | ||
| 1960 | qry.classList.add('faQuery'); | ||
| 1961 | qry.type = 'search'; | ||
| 1962 | qry.placeholder = 'Filter icons'; | ||
| 1963 | qry.autofocus = true; | ||
| 1964 | const qryDebounced = debounce(()=>{ | ||
| 1965 | const result = faList.filter(it=>it.includes(qry.value)); | ||
| 1966 | for (const fa of faList) { | ||
| 1967 | if (!result.includes(fa)) { | ||
| 1968 | fas[fa].classList.add('hidden'); | ||
| 1969 | } else { | ||
| 1970 | fas[fa].classList.remove('hidden'); | ||
| 1971 | } | ||
| 1972 | } | ||
| 1973 | }); | ||
| 1974 | qry.addEventListener('input', () => qryDebounced()); | ||
| 1975 | search.append(qry); | ||
| 1976 | } | ||
| 1977 | dom.append(search); | ||
| 1978 | } | ||
| 1979 | const grid = document.createElement('div'); { | ||
| 1980 | grid.classList.add('faPicker'); | ||
| 1981 | for (const fa of faList) { | ||
| 1982 | const opt = document.createElement('div'); { | ||
| 1983 | fas[fa] = opt; | ||
| 1984 | opt.classList.add('menu_button'); | ||
| 1985 | opt.classList.add('fa-solid'); | ||
| 1986 | opt.classList.add(fa); | ||
| 1987 | opt.title = fa.slice(3); | ||
| 1988 | opt.dataset.result = POPUP_RESULT.AFFIRMATIVE.toString(); | ||
| 1989 | opt.addEventListener('click', () => value = fa); | ||
| 1990 | grid.append(opt); | ||
| 1991 | } | ||
| 1992 | } | ||
| 1993 | dom.append(grid); | ||
| 1994 | } | ||
| 1995 | } | ||
| 1996 | let value = ''; | ||
| 1997 | const picker = new Popup(dom, POPUP_TYPE.TEXT, null, { allowVerticalScrolling: true, okButton: 'No Icon', cancelButton: 'Cancel' }); | ||
| 1998 | await picker.show(); | ||
| 1999 | if (picker.result == POPUP_RESULT.AFFIRMATIVE) { | ||
| 2000 | return value; | ||
| 2001 | } | ||
| 2002 | return null; | ||
| 2003 | } | ||
| @@ -4,10 +4,11 @@ import { executeSlashCommandsWithOptions } from './slash-commands.js'; | |||
| 4 | import { SlashCommand } from './slash-commands/SlashCommand.js'; | 4 | import { SlashCommand } from './slash-commands/SlashCommand.js'; |
| 5 | import { SlashCommandAbortController } from './slash-commands/SlashCommandAbortController.js'; | 5 | import { SlashCommandAbortController } from './slash-commands/SlashCommandAbortController.js'; |
| 6 | import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js'; | 6 | import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js'; |
| 7 | import { SlashCommandBreakController } from './slash-commands/SlashCommandBreakController.js'; | ||
| 7 | import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js'; | 8 | import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js'; |
| 8 | import { SlashCommandClosureResult } from './slash-commands/SlashCommandClosureResult.js'; | 9 | import { SlashCommandClosureResult } from './slash-commands/SlashCommandClosureResult.js'; |
| 9 | import { commonEnumProviders } from './slash-commands/SlashCommandCommonEnumsProvider.js'; | 10 | import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js'; |
| 10 | import { SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js'; | 11 | import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js'; |
| 11 | import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js'; | 12 | import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js'; |
| 12 | import { SlashCommandScope } from './slash-commands/SlashCommandScope.js'; | 13 | import { SlashCommandScope } from './slash-commands/SlashCommandScope.js'; |
| 13 | import { isFalseBoolean } from './utils.js'; | 14 | import { isFalseBoolean } from './utils.js'; |
| @@ -40,7 +41,7 @@ function getLocalVariable(name, args = {}) { | |||
| 40 | } | 41 | } |
| 41 | } | 42 | } |
| 42 | 43 | ||
| 43 | return (localVariable === '' || isNaN(Number(localVariable))) ? (localVariable || '') : Number(localVariable); | 44 | return (localVariable?.trim?.() === '' || isNaN(Number(localVariable))) ? (localVariable || '') : Number(localVariable); |
| 44 | } | 45 | } |
| 45 | 46 | ||
| 46 | function setLocalVariable(name, value, args = {}) { | 47 | function setLocalVariable(name, value, args = {}) { |
| @@ -93,7 +94,7 @@ function getGlobalVariable(name, args = {}) { | |||
| 93 | } | 94 | } |
| 94 | } | 95 | } |
| 95 | 96 | ||
| 96 | return (globalVariable === '' || isNaN(Number(globalVariable))) ? (globalVariable || '') : Number(globalVariable); | 97 | return (globalVariable?.trim?.() === '' || isNaN(Number(globalVariable))) ? (globalVariable || '') : Number(globalVariable); |
| 97 | } | 98 | } |
| 98 | 99 | ||
| 99 | function setGlobalVariable(name, value, args = {}) { | 100 | function setGlobalVariable(name, value, args = {}) { |
| @@ -348,11 +349,13 @@ async function whileCallback(args, value) { | |||
| 348 | 349 | ||
| 349 | if (result && command) { | 350 | if (result && command) { |
| 350 | if (command instanceof SlashCommandClosure) { | 351 | if (command instanceof SlashCommandClosure) { |
| 352 | command.breakController = new SlashCommandBreakController(); | ||
| 351 | commandResult = await command.execute(); | 353 | commandResult = await command.execute(); |
| 352 | } else { | 354 | } else { |
| 353 | commandResult = await executeSubCommands(command, args._scope, args._parserFlags, args._abortController); | 355 | commandResult = await executeSubCommands(command, args._scope, args._parserFlags, args._abortController); |
| 354 | } | 356 | } |
| 355 | if (commandResult.isAborted) break; | 357 | if (commandResult.isAborted) break; |
| 358 | if (commandResult.isBreak) break; | ||
| 356 | } else { | 359 | } else { |
| 357 | break; | 360 | break; |
| 358 | } | 361 | } |
| @@ -390,8 +393,8 @@ async function timesCallback(args, value) { | |||
| 390 | const iterations = Math.min(Number(repeats), isGuardOff ? Number.MAX_SAFE_INTEGER : MAX_LOOPS); | 393 | const iterations = Math.min(Number(repeats), isGuardOff ? Number.MAX_SAFE_INTEGER : MAX_LOOPS); |
| 391 | let result; | 394 | let result; |
| 392 | for (let i = 0; i < iterations; i++) { | 395 | for (let i = 0; i < iterations; i++) { |
| 393 | /**@type {SlashCommandClosureResult}*/ | ||
| 394 | if (command instanceof SlashCommandClosure) { | 396 | if (command instanceof SlashCommandClosure) { |
| 397 | command.breakController = new SlashCommandBreakController(); | ||
| 395 | command.scope.setMacro('timesIndex', i); | 398 | command.scope.setMacro('timesIndex', i); |
| 396 | result = await command.execute(); | 399 | result = await command.execute(); |
| 397 | } | 400 | } |
| @@ -399,6 +402,7 @@ async function timesCallback(args, value) { | |||
| 399 | result = await executeSubCommands(command.replace(/\{\{timesIndex\}\}/g, i.toString()), args._scope, args._parserFlags, args._abortController); | 402 | result = await executeSubCommands(command.replace(/\{\{timesIndex\}\}/g, i.toString()), args._scope, args._parserFlags, args._abortController); |
| 400 | } | 403 | } |
| 401 | if (result.isAborted) break; | 404 | if (result.isAborted) break; |
| 405 | if (result.isBreak) break; | ||
| 402 | } | 406 | } |
| 403 | 407 | ||
| 404 | return result?.pipe ?? ''; | 408 | return result?.pipe ?? ''; |
| @@ -461,7 +465,7 @@ function existsGlobalVariable(name) { | |||
| 461 | * @param {object} args Command arguments | 465 | * @param {object} args Command arguments |
| 462 | * @returns {{a: string | number, b: string | number, rule: string}} Boolean operands | 466 | * @returns {{a: string | number, b: string | number, rule: string}} Boolean operands |
| 463 | */ | 467 | */ |
| 464 | function parseBooleanOperands(args) { | 468 | export function parseBooleanOperands(args) { |
| 465 | // Resolution order: numeric literal, local variable, global variable, string literal | 469 | // Resolution order: numeric literal, local variable, global variable, string literal |
| 466 | /** | 470 | /** |
| 467 | * @param {string} operand Boolean operand candidate | 471 | * @param {string} operand Boolean operand candidate |
| @@ -510,36 +514,15 @@ function parseBooleanOperands(args) { | |||
| 510 | * @param {string|number} b The right operand | 514 | * @param {string|number} b The right operand |
| 511 | * @returns {boolean} True if the rule yields true, false otherwise | 515 | * @returns {boolean} True if the rule yields true, false otherwise |
| 512 | */ | 516 | */ |
| 513 | function evalBoolean(rule, a, b) { | 517 | export function evalBoolean(rule, a, b) { |
| 514 | if (!rule) { | 518 | if (!rule) { |
| 515 | toastr.warning('The rule must be specified for the boolean comparison.', 'Invalid command'); | 519 | toastr.warning('The rule must be specified for the boolean comparison.', 'Invalid command'); |
| 516 | throw new Error('Invalid command.'); | 520 | throw new Error('Invalid command.'); |
| 517 | } | 521 | } |
| 518 | 522 | ||
| 519 | let result = false; | 523 | let result = false; |
| 520 | 524 | if (typeof a === 'number' && typeof b === 'number') { | |
| 521 | if (typeof a === 'string' && typeof b !== 'number') { | 525 | // only do numeric comparison if both operands are numbers |
| 522 | const aString = String(a).toLowerCase(); | ||
| 523 | const bString = String(b).toLowerCase(); | ||
| 524 | |||
| 525 | switch (rule) { | ||
| 526 | case 'in': | ||
| 527 | result = aString.includes(bString); | ||
| 528 | break; | ||
| 529 | case 'nin': | ||
| 530 | result = !aString.includes(bString); | ||
| 531 | break; | ||
| 532 | case 'eq': | ||
| 533 | result = aString === bString; | ||
| 534 | break; | ||
| 535 | case 'neq': | ||
| 536 | result = aString !== bString; | ||
| 537 | break; | ||
| 538 | default: | ||
| 539 | toastr.error('Unknown boolean comparison rule for type string.', 'Invalid /if command'); | ||
| 540 | throw new Error('Invalid command.'); | ||
| 541 | } | ||
| 542 | } else if (typeof a === 'number') { | ||
| 543 | const aNumber = Number(a); | 526 | const aNumber = Number(a); |
| 544 | const bNumber = Number(b); | 527 | const bNumber = Number(b); |
| 545 | 528 | ||
| @@ -569,6 +552,38 @@ function evalBoolean(rule, a, b) { | |||
| 569 | toastr.error('Unknown boolean comparison rule for type number.', 'Invalid command'); | 552 | toastr.error('Unknown boolean comparison rule for type number.', 'Invalid command'); |
| 570 | throw new Error('Invalid command.'); | 553 | throw new Error('Invalid command.'); |
| 571 | } | 554 | } |
| 555 | } else { | ||
| 556 | // otherwise do case-insensitive string comparsion, stringify non-strings | ||
| 557 | let aString; | ||
| 558 | let bString; | ||
| 559 | if (typeof a == 'string') { | ||
| 560 | aString = a.toLowerCase(); | ||
| 561 | } else { | ||
| 562 | aString = JSON.stringify(a).toLowerCase(); | ||
| 563 | } | ||
| 564 | if (typeof b == 'string') { | ||
| 565 | bString = b.toLowerCase(); | ||
| 566 | } else { | ||
| 567 | bString = JSON.stringify(b).toLowerCase(); | ||
| 568 | } | ||
| 569 | |||
| 570 | switch (rule) { | ||
| 571 | case 'in': | ||
| 572 | result = aString.includes(bString); | ||
| 573 | break; | ||
| 574 | case 'nin': | ||
| 575 | result = !aString.includes(bString); | ||
| 576 | break; | ||
| 577 | case 'eq': | ||
| 578 | result = aString === bString; | ||
| 579 | break; | ||
| 580 | case 'neq': | ||
| 581 | result = aString !== bString; | ||
| 582 | break; | ||
| 583 | default: | ||
| 584 | toastr.error('Unknown boolean comparison rule for type string.', 'Invalid /if command'); | ||
| 585 | throw new Error('Invalid command.'); | ||
| 586 | } | ||
| 572 | } | 587 | } |
| 573 | 588 | ||
| 574 | return result; | 589 | return result; |
| @@ -783,24 +798,29 @@ function randValuesCallback(from, to, args) { | |||
| 783 | * @returns The variable's value | 798 | * @returns The variable's value |
| 784 | */ | 799 | */ |
| 785 | function letCallback(args, value) { | 800 | function letCallback(args, value) { |
| 786 | if (Array.isArray(value)) { | 801 | if (!Array.isArray(value)) value = [value]; |
| 787 | args._scope.letVariable(value[0], typeof value[1] == 'string' ? value.slice(1).join(' ') : value[1]); | ||
| 788 | return value[1]; | ||
| 789 | } | ||
| 790 | if (args.key !== undefined) { | 802 | if (args.key !== undefined) { |
| 791 | const key = args.key; | 803 | const key = args.key; |
| 792 | const val = value; | 804 | if (typeof key != 'string') throw new Error('Key must be a string'); |
| 805 | if (args._hasUnnamedArgument) { | ||
| 806 | const val = typeof value[0] == 'string' ? value.join(' ') : value[0]; | ||
| 793 | args._scope.letVariable(key, val); | 807 | args._scope.letVariable(key, val); |
| 794 | return val; | 808 | return val; |
| 809 | } else { | ||
| 810 | args._scope.letVariable(key); | ||
| 811 | return ''; | ||
| 812 | } | ||
| 795 | } | 813 | } |
| 796 | if (value instanceof SlashCommandClosure) throw new Error('/let unnamed argument does not support closures if no key is provided'); | 814 | const key = value.shift(); |
| 797 | if (value.includes(' ')) { | 815 | if (typeof key != 'string') throw new Error('Key must be a string'); |
| 798 | const key = value.split(' ')[0]; | 816 | if (value.length > 0) { |
| 799 | const val = value.split(' ').slice(1).join(' '); | 817 | const val = typeof value[0] == 'string' ? value.join(' ') : value[0]; |
| 800 | args._scope.letVariable(key, val); | 818 | args._scope.letVariable(key, val); |
| 801 | return val; | 819 | return val; |
| 820 | } else { | ||
| 821 | args._scope.letVariable(key); | ||
| 822 | return ''; | ||
| 802 | } | 823 | } |
| 803 | args._scope.letVariable(value); | ||
| 804 | } | 824 | } |
| 805 | 825 | ||
| 806 | /** | 826 | /** |
| @@ -813,8 +833,9 @@ function varCallback(args, value) { | |||
| 813 | if (!Array.isArray(value)) value = [value]; | 833 | if (!Array.isArray(value)) value = [value]; |
| 814 | if (args.key !== undefined) { | 834 | if (args.key !== undefined) { |
| 815 | const key = args.key; | 835 | const key = args.key; |
| 836 | if (typeof key != 'string') throw new Error('Key must be a string'); | ||
| 816 | if (args._hasUnnamedArgument) { | 837 | if (args._hasUnnamedArgument) { |
| 817 | const val = value.join(' '); | 838 | const val = typeof value[0] == 'string' ? value.join(' ') : value[0]; |
| 818 | args._scope.setVariable(key, val, args.index); | 839 | args._scope.setVariable(key, val, args.index); |
| 819 | return val; | 840 | return val; |
| 820 | } else { | 841 | } else { |
| @@ -822,8 +843,9 @@ function varCallback(args, value) { | |||
| 822 | } | 843 | } |
| 823 | } | 844 | } |
| 824 | const key = value.shift(); | 845 | const key = value.shift(); |
| 846 | if (typeof key != 'string') throw new Error('Key must be a string'); | ||
| 825 | if (value.length > 0) { | 847 | if (value.length > 0) { |
| 826 | const val = value.join(' '); | 848 | const val = typeof value[0] == 'string' ? value.join(' ') : value[0]; |
| 827 | args._scope.setVariable(key, val, args.index); | 849 | args._scope.setVariable(key, val, args.index); |
| 828 | return val; | 850 | return val; |
| 829 | } else { | 851 | } else { |
| @@ -1382,6 +1404,7 @@ export function registerVariableCommands() { | |||
| 1382 | ), | 1404 | ), |
| 1383 | ], | 1405 | ], |
| 1384 | splitUnnamedArgument: true, | 1406 | splitUnnamedArgument: true, |
| 1407 | splitUnnamedArgumentCount: 1, | ||
| 1385 | helpString: ` | 1408 | helpString: ` |
| 1386 | <div> | 1409 | <div> |
| 1387 | Execute any valid slash command enclosed in quotes <code>repeats</code> number of times. | 1410 | Execute any valid slash command enclosed in quotes <code>repeats</code> number of times. |
| @@ -1459,7 +1482,7 @@ export function registerVariableCommands() { | |||
| 1459 | })); | 1482 | })); |
| 1460 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | 1483 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ |
| 1461 | name: 'add', | 1484 | name: 'add', |
| 1462 | callback: addValuesCallback, | 1485 | callback: (args, /**@type {string[]}*/value) => addValuesCallback(args, value.join(' ')), |
| 1463 | returns: 'sum of the provided values', | 1486 | returns: 'sum of the provided values', |
| 1464 | unnamedArgumentList: [ | 1487 | unnamedArgumentList: [ |
| 1465 | SlashCommandArgument.fromProps({ | 1488 | SlashCommandArgument.fromProps({ |
| @@ -1467,10 +1490,32 @@ export function registerVariableCommands() { | |||
| 1467 | typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], | 1490 | typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], |
| 1468 | isRequired: true, | 1491 | isRequired: true, |
| 1469 | acceptsMultiple: true, | 1492 | acceptsMultiple: true, |
| 1470 | enumProvider: commonEnumProviders.variables('all'), | 1493 | enumProvider: (executor, scope)=>{ |
| 1494 | const vars = commonEnumProviders.variables('all')(executor, scope); | ||
| 1495 | vars.push( | ||
| 1496 | new SlashCommandEnumValue( | ||
| 1497 | 'any variable name', | ||
| 1498 | null, | ||
| 1499 | enumTypes.variable, | ||
| 1500 | enumIcons.variable, | ||
| 1501 | (input)=>/^\w*$/.test(input), | ||
| 1502 | (input)=>input, | ||
| 1503 | ), | ||
| 1504 | new SlashCommandEnumValue( | ||
| 1505 | 'any number', | ||
| 1506 | null, | ||
| 1507 | enumTypes.number, | ||
| 1508 | enumIcons.number, | ||
| 1509 | (input)=>input == '' || !Number.isNaN(Number(input)), | ||
| 1510 | (input)=>input, | ||
| 1511 | ), | ||
| 1512 | ); | ||
| 1513 | return vars; | ||
| 1514 | }, | ||
| 1471 | forceEnum: false, | 1515 | forceEnum: false, |
| 1472 | }), | 1516 | }), |
| 1473 | ], | 1517 | ], |
| 1518 | splitUnnamedArgument: true, | ||
| 1474 | helpString: ` | 1519 | helpString: ` |
| 1475 | <div> | 1520 | <div> |
| 1476 | Performs an addition of the set of values and passes the result down the pipe. | 1521 | Performs an addition of the set of values and passes the result down the pipe. |
| @@ -2001,6 +2046,7 @@ export function registerVariableCommands() { | |||
| 2001 | ), | 2046 | ), |
| 2002 | ], | 2047 | ], |
| 2003 | splitUnnamedArgument: true, | 2048 | splitUnnamedArgument: true, |
| 2049 | splitUnnamedArgumentCount: 1, | ||
| 2004 | helpString: ` | 2050 | helpString: ` |
| 2005 | <div> | 2051 | <div> |
| 2006 | Get or set a variable. | 2052 | Get or set a variable. |
| @@ -2043,6 +2089,7 @@ export function registerVariableCommands() { | |||
| 2043 | ), | 2089 | ), |
| 2044 | ], | 2090 | ], |
| 2045 | splitUnnamedArgument: true, | 2091 | splitUnnamedArgument: true, |
| 2092 | splitUnnamedArgumentCount: 1, | ||
| 2046 | helpString: ` | 2093 | helpString: ` |
| 2047 | <div> | 2094 | <div> |
| 2048 | Declares a new variable in the current scope. | 2095 | Declares a new variable in the current scope. |
| @@ -1383,6 +1383,8 @@ body[data-stscript-style="dark"] { | |||
| 1383 | --ac-style-color-matchedText: rgba(108 171 251 / 1); | 1383 | --ac-style-color-matchedText: rgba(108 171 251 / 1); |
| 1384 | --ac-style-color-selectedBackground: rgba(32 57 92 / 1); | 1384 | --ac-style-color-selectedBackground: rgba(32 57 92 / 1); |
| 1385 | --ac-style-color-selectedText: rgba(255 255 255 / 1); | 1385 | --ac-style-color-selectedText: rgba(255 255 255 / 1); |
| 1386 | --ac-style-color-notSelectableBackground: rgb(65, 78, 95); | ||
| 1387 | --ac-style-color-notSelectableText: rgba(255 255 255 / 1); | ||
| 1386 | --ac-style-color-hoveredBackground: rgba(43 45 46 / 1); | 1388 | --ac-style-color-hoveredBackground: rgba(43 45 46 / 1); |
| 1387 | --ac-style-color-hoveredText: rgba(204 204 204 / 1); | 1389 | --ac-style-color-hoveredText: rgba(204 204 204 / 1); |
| 1388 | --ac-style-color-argName: rgba(171 209 239 / 1); | 1390 | --ac-style-color-argName: rgba(171 209 239 / 1); |
| @@ -1398,6 +1400,7 @@ body[data-stscript-style="dark"] { | |||
| 1398 | --ac-style-color-punctuationL2: rgba(98 160 251 / 1); | 1400 | --ac-style-color-punctuationL2: rgba(98 160 251 / 1); |
| 1399 | --ac-style-color-currentParenthesis: rgba(195 118 210 / 1); | 1401 | --ac-style-color-currentParenthesis: rgba(195 118 210 / 1); |
| 1400 | --ac-style-color-comment: rgba(122 151 90 / 1); | 1402 | --ac-style-color-comment: rgba(122 151 90 / 1); |
| 1403 | --ac-style-color-keyword: rgba(182 137 190 / 1); | ||
| 1401 | } | 1404 | } |
| 1402 | 1405 | ||
| 1403 | body[data-stscript-style="light"] { | 1406 | body[data-stscript-style="light"] { |
| @@ -1408,6 +1411,8 @@ body[data-stscript-style="light"] { | |||
| 1408 | --ac-style-color-matchedText: rgba(61 104 188 / 1); | 1411 | --ac-style-color-matchedText: rgba(61 104 188 / 1); |
| 1409 | --ac-style-color-selectedBackground: rgba(232 232 232 / 1); | 1412 | --ac-style-color-selectedBackground: rgba(232 232 232 / 1); |
| 1410 | --ac-style-color-selectedText: rgba(0 0 0 / 1); | 1413 | --ac-style-color-selectedText: rgba(0 0 0 / 1); |
| 1414 | --ac-style-color-notSelectableBackground: rgba(232 232 232 / 1); | ||
| 1415 | --ac-style-color-notSelectableText: rgb(83, 83, 83); | ||
| 1411 | --ac-style-color-hoveredBackground: rgba(242 242 242 / 1); | 1416 | --ac-style-color-hoveredBackground: rgba(242 242 242 / 1); |
| 1412 | --ac-style-color-hoveredText: rgba(59 59 59 / 1); | 1417 | --ac-style-color-hoveredText: rgba(59 59 59 / 1); |
| 1413 | --ac-style-color-argName: rgba(16 24 125 / 1); | 1418 | --ac-style-color-argName: rgba(16 24 125 / 1); |
| @@ -1419,6 +1424,7 @@ body[data-stscript-style="light"] { | |||
| 1419 | --ac-style-color-variable: rgba(16 24 125 / 1); | 1424 | --ac-style-color-variable: rgba(16 24 125 / 1); |
| 1420 | --ac-style-color-currentParenthesis: rgba(195 118 210 / 1); | 1425 | --ac-style-color-currentParenthesis: rgba(195 118 210 / 1); |
| 1421 | --ac-style-color-comment: rgba(70 126 26 / 1); | 1426 | --ac-style-color-comment: rgba(70 126 26 / 1); |
| 1427 | --ac-style-color-keyword: rgba(182 137 190 / 1); | ||
| 1422 | } | 1428 | } |
| 1423 | 1429 | ||
| 1424 | body[data-stscript-style="theme"] { | 1430 | body[data-stscript-style="theme"] { |
| @@ -1429,6 +1435,8 @@ body[data-stscript-style="theme"] { | |||
| 1429 | --ac-style-color-matchedText: var(--SmartThemeQuoteColor); | 1435 | --ac-style-color-matchedText: var(--SmartThemeQuoteColor); |
| 1430 | --ac-style-color-selectedBackground: color-mix(in srgb, rgb(128 128 128) 75%, var(--SmartThemeChatTintColor)); | 1436 | --ac-style-color-selectedBackground: color-mix(in srgb, rgb(128 128 128) 75%, var(--SmartThemeChatTintColor)); |
| 1431 | --ac-style-color-selectedText: var(--SmartThemeBodyColor); | 1437 | --ac-style-color-selectedText: var(--SmartThemeBodyColor); |
| 1438 | --ac-style-color-notSelectableBackground: color-mix(in srgb, rgb(128 128 128) 50%, var(--SmartThemeChatTintColor)); | ||
| 1439 | --ac-style-color-notSelectableText: var(--SmartThemeBodyColor); | ||
| 1432 | --ac-style-color-hoveredBackground: color-mix(in srgb, rgb(128 128 128) 30%, var(--SmartThemeChatTintColor)); | 1440 | --ac-style-color-hoveredBackground: color-mix(in srgb, rgb(128 128 128) 30%, var(--SmartThemeChatTintColor)); |
| 1433 | --ac-style-color-hoveredText: var(--SmartThemeEmColor); | 1441 | --ac-style-color-hoveredText: var(--SmartThemeEmColor); |
| 1434 | --ac-style-color-argName: rgba(171 209 239 / 1); | 1442 | --ac-style-color-argName: rgba(171 209 239 / 1); |
| @@ -1439,6 +1447,7 @@ body[data-stscript-style="theme"] { | |||
| 1439 | --ac-style-color-variable: rgba(131 193 252 / 1); | 1447 | --ac-style-color-variable: rgba(131 193 252 / 1); |
| 1440 | --ac-style-color-currentParenthesis: rgba(195 118 210 / 1); | 1448 | --ac-style-color-currentParenthesis: rgba(195 118 210 / 1); |
| 1441 | --ac-style-color-comment: rgba(122 151 90 / 1); | 1449 | --ac-style-color-comment: rgba(122 151 90 / 1); |
| 1450 | --ac-style-color-keyword: rgba(182 137 190 / 1); | ||
| 1442 | } | 1451 | } |
| 1443 | 1452 | ||
| 1444 | body { | 1453 | body { |
| @@ -1454,6 +1463,8 @@ body { | |||
| 1454 | --ac-color-matchedText: var(--ac-style-color-matchedText, rgba(108 171 251 / 1)); | 1463 | --ac-color-matchedText: var(--ac-style-color-matchedText, rgba(108 171 251 / 1)); |
| 1455 | --ac-color-selectedBackground: var(--ac-style-color-selectedBackground, rgba(32 57 92 / 1)); | 1464 | --ac-color-selectedBackground: var(--ac-style-color-selectedBackground, rgba(32 57 92 / 1)); |
| 1456 | --ac-color-selectedText: var(--ac-style-color-selectedText, rgba(255 255 255 / 1)); | 1465 | --ac-color-selectedText: var(--ac-style-color-selectedText, rgba(255 255 255 / 1)); |
| 1466 | --ac-color-notSelectableBackground: var(--ac-style-color-notSelectableBackground, rgb(60, 73, 92)); | ||
| 1467 | --ac-color-notSelectableText: var(--ac-style-color-notSelectableText, rgba(255 255 255 / 1)); | ||
| 1457 | --ac-color-hoveredBackground: var(--ac-style-color-hoveredBackground, rgba(43 45 46 / 1)); | 1468 | --ac-color-hoveredBackground: var(--ac-style-color-hoveredBackground, rgba(43 45 46 / 1)); |
| 1458 | --ac-color-hoveredText: var(--ac-style-color-hoveredText, rgba(204 204 204 / 1)); | 1469 | --ac-color-hoveredText: var(--ac-style-color-hoveredText, rgba(204 204 204 / 1)); |
| 1459 | --ac-color-argName: var(--ac-style-color-argName, rgba(171 209 239 / 1)); | 1470 | --ac-color-argName: var(--ac-style-color-argName, rgba(171 209 239 / 1)); |
| @@ -1469,6 +1480,7 @@ body { | |||
| 1469 | --ac-color-punctuationL2: var(--ac-style-color-punctuationL2, rgba(98 160 251 / 1)); | 1480 | --ac-color-punctuationL2: var(--ac-style-color-punctuationL2, rgba(98 160 251 / 1)); |
| 1470 | --ac-color-currentParenthesis: var(--ac-style-color-currentParenthesis, rgba(195 118 210 / 1)); | 1481 | --ac-color-currentParenthesis: var(--ac-style-color-currentParenthesis, rgba(195 118 210 / 1)); |
| 1471 | --ac-color-comment: var(--ac-style-color-comment, rgba(122 151 90 / 1)); | 1482 | --ac-color-comment: var(--ac-style-color-comment, rgba(122 151 90 / 1)); |
| 1483 | --ac-color-keyword: var(--ac-style-color-keyword, rgba(182 137 190 / 1)); | ||
| 1472 | 1484 | ||
| 1473 | font-size: calc(var(--ac-font-scale) * 1em); | 1485 | font-size: calc(var(--ac-font-scale) * 1em); |
| 1474 | 1486 | ||
| @@ -1569,16 +1581,25 @@ body[data-stscript-style] .hljs.language-stscript { | |||
| 1569 | color: var(--ac-style-color-punctuation); | 1581 | color: var(--ac-style-color-punctuation); |
| 1570 | } | 1582 | } |
| 1571 | 1583 | ||
| 1572 | .hljs-keyword { | ||
| 1573 | color: var(--ac-style-color-variableLanguage); | ||
| 1574 | } | ||
| 1575 | |||
| 1576 | .hljs-comment { | 1584 | .hljs-comment { |
| 1577 | color: var(--ac-style-color-comment); | 1585 | color: var(--ac-style-color-comment); |
| 1578 | } | 1586 | } |
| 1579 | 1587 | ||
| 1580 | .hljs-abort { | 1588 | .hljs-abort { |
| 1581 | color: var(--ac-style-color-abort, #e38e23); | 1589 | color: var(--ac-style-color-abort, #e38e23); |
| 1590 | font-weight: bold; | ||
| 1591 | } | ||
| 1592 | |||
| 1593 | .hljs-keyword { | ||
| 1594 | color: var(--ac-style-color-keyword); | ||
| 1595 | font-weight: bold; | ||
| 1596 | } | ||
| 1597 | |||
| 1598 | .hljs-pipe { | ||
| 1599 | color: var(--ac-style-color-punctuation); | ||
| 1600 | } | ||
| 1601 | .hljs-pipebreak { | ||
| 1602 | color: var(--ac-style-color-type); | ||
| 1582 | } | 1603 | } |
| 1583 | 1604 | ||
| 1584 | .hljs-closure { | 1605 | .hljs-closure { |
| @@ -1670,6 +1691,10 @@ body[data-stscript-style] .hljs.language-stscript { | |||
| 1670 | background-color: var(--ac-color-selectedBackground); | 1691 | background-color: var(--ac-color-selectedBackground); |
| 1671 | color: var(--ac-color-selectedText); | 1692 | color: var(--ac-color-selectedText); |
| 1672 | } | 1693 | } |
| 1694 | &.selected.not-selectable>* { | ||
| 1695 | background-color: var(--ac-color-notSelectableBackground); | ||
| 1696 | color: var(--ac-color-notSelectableText); | ||
| 1697 | } | ||
| 1673 | 1698 | ||
| 1674 | >* { | 1699 | >* { |
| 1675 | height: 100%; | 1700 | height: 100%; |
| @@ -1747,7 +1772,12 @@ body[data-stscript-style] .hljs.language-stscript { | |||
| 1747 | padding: 0.25em 0.25em 0.5em 0.25em; | 1772 | padding: 0.25em 0.25em 0.5em 0.25em; |
| 1748 | border-bottom: 1px solid var(--ac-color-border); | 1773 | border-bottom: 1px solid var(--ac-color-border); |
| 1749 | 1774 | ||
| 1750 | >.name { | 1775 | > .head { |
| 1776 | display: flex; | ||
| 1777 | gap: 0.5em; | ||
| 1778 | } | ||
| 1779 | > .head > .name, > .name { | ||
| 1780 | flex: 1 1 auto; | ||
| 1751 | font-weight: bold; | 1781 | font-weight: bold; |
| 1752 | color: var(--ac-color-text); | 1782 | color: var(--ac-color-text); |
| 1753 | cursor: help; | 1783 | cursor: help; |
| @@ -1756,6 +1786,35 @@ body[data-stscript-style] .hljs.language-stscript { | |||
| 1756 | text-decoration: 1px dotted underline; | 1786 | text-decoration: 1px dotted underline; |
| 1757 | } | 1787 | } |
| 1758 | } | 1788 | } |
| 1789 | > .head > .source { | ||
| 1790 | padding: 0 0.5em; | ||
| 1791 | cursor: help; | ||
| 1792 | display: flex; | ||
| 1793 | align-items: center; | ||
| 1794 | gap: 0.5em; | ||
| 1795 | &.isThirdParty.isExtension { | ||
| 1796 | color: #F89406; | ||
| 1797 | } | ||
| 1798 | &.isCore { | ||
| 1799 | color: transparent; | ||
| 1800 | &.isExtension { | ||
| 1801 | color: #51A351; | ||
| 1802 | } | ||
| 1803 | &:after { | ||
| 1804 | content: ''; | ||
| 1805 | order: -1; | ||
| 1806 | height: 14px; | ||
| 1807 | aspect-ratio: 1 / 1; | ||
| 1808 | background-image: url('/favicon.ico'); | ||
| 1809 | background-size: contain; | ||
| 1810 | background-repeat: no-repeat; | ||
| 1811 | } | ||
| 1812 | } | ||
| 1813 | |||
| 1814 | &:hover { | ||
| 1815 | text-decoration: 1px dotted underline; | ||
| 1816 | } | ||
| 1817 | } | ||
| 1759 | 1818 | ||
| 1760 | >.body { | 1819 | >.body { |
| 1761 | flex-direction: column; | 1820 | flex-direction: column; |
| @@ -1843,7 +1902,8 @@ body[data-stscript-style] .hljs.language-stscript { | |||
| 1843 | 1902 | ||
| 1844 | >code { | 1903 | >code { |
| 1845 | display: block; | 1904 | display: block; |
| 1846 | padding: 0; | 1905 | padding: 1px; |
| 1906 | tab-size: 4; | ||
| 1847 | } | 1907 | } |
| 1848 | } | 1908 | } |
| 1849 | } | 1909 | } |
| @@ -3291,6 +3351,11 @@ grammarly-extension { | |||
| 3291 | text-align: center; | 3351 | text-align: center; |
| 3292 | } | 3352 | } |
| 3293 | 3353 | ||
| 3354 | .menu_button.fa-fw { | ||
| 3355 | /* 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. */ | ||
| 3356 | min-width: calc(1.25em + 12px); | ||
| 3357 | } | ||
| 3358 | |||
| 3294 | .avatar_div .menu_button, | 3359 | .avatar_div .menu_button, |
| 3295 | .form_create_bottom_buttons_block .menu_button { | 3360 | .form_create_bottom_buttons_block .menu_button { |
| 3296 | padding: 5px; | 3361 | padding: 5px; |
| @@ -5300,3 +5365,49 @@ body:not(.movingUI) .drawer-content.maximized { | |||
| 5300 | .regex-highlight { | 5365 | .regex-highlight { |
| 5301 | color: #FAF8F6; | 5366 | color: #FAF8F6; |
| 5302 | } | 5367 | } |
| 5368 | |||
| 5369 | .popup:has(.faPicker) { | ||
| 5370 | /* Fix height for fa picker popup, otherwise search is making it resize weirdly */ | ||
| 5371 | height: 70%; | ||
| 5372 | .popup-content { | ||
| 5373 | display: flex; | ||
| 5374 | flex-direction: column; | ||
| 5375 | } | ||
| 5376 | } | ||
| 5377 | |||
| 5378 | .faPicker-container { | ||
| 5379 | display: flex; | ||
| 5380 | flex-direction: column; | ||
| 5381 | overflow: hidden;; | ||
| 5382 | } | ||
| 5383 | |||
| 5384 | .faQuery-container { | ||
| 5385 | flex: 0 0 auto; | ||
| 5386 | } | ||
| 5387 | |||
| 5388 | .faPicker { | ||
| 5389 | flex: 1 1 auto; | ||
| 5390 | overflow: auto; | ||
| 5391 | gap: 1em; | ||
| 5392 | display: grid; | ||
| 5393 | grid-template-columns: repeat(auto-fill, minmax(3.5em, 1fr)); | ||
| 5394 | |||
| 5395 | .menu_button { | ||
| 5396 | aspect-ratio: 1 / 1; | ||
| 5397 | font-size: 2em; | ||
| 5398 | height: 1lh; | ||
| 5399 | line-height: 1.2; | ||
| 5400 | padding: 0.25em; | ||
| 5401 | width: unset; | ||
| 5402 | box-sizing: content-box; | ||
| 5403 | &.hidden { | ||
| 5404 | display: none; | ||
| 5405 | } | ||
| 5406 | } | ||
| 5407 | } | ||
| 5408 | |||
| 5409 | .faPicker:not(:has(:not(.hidden)))::after { | ||
| 5410 | content: 'No icons found'; | ||
| 5411 | color: var(--SmartThemeBodyColor); | ||
| 5412 | opacity: 0.7; | ||
| 5413 | } | ||