gubes mirror. how does this work
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

squash

leah 1e3b8b6e

+14147
+52
.github/workflows/deploy.yml
··· 1 + # Simple workflow for deploying static content to GitHub Pages 2 + name: Build & Deploy 3 + 4 + on: 5 + # Runs on pushes targeting the default branch 6 + push: 7 + branches: ["tubes"] 8 + 9 + # Allows you to run this workflow manually from the Actions tab 10 + workflow_dispatch: 11 + 12 + # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 13 + permissions: 14 + contents: read 15 + pages: write 16 + id-token: write 17 + 18 + # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 19 + # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 20 + concurrency: 21 + group: "pages" 22 + cancel-in-progress: false 23 + 24 + jobs: 25 + # Single deploy job since we're just deploying 26 + deploy: 27 + environment: 28 + name: github-pages 29 + url: ${{ steps.deployment.outputs.page_url }} 30 + runs-on: ubuntu-latest 31 + steps: 32 + - name: Checkout 33 + uses: actions/checkout@v3 34 + - name: Setup Pages 35 + uses: actions/configure-pages@v3 36 + - name: Install Node.js 37 + uses: actions/setup-node@v3 38 + with: 39 + node-version: 18 40 + cache: npm 41 + - name: Install dependencies 42 + run: npm install 43 + - name: build 44 + run: npm run build 45 + - name: Upload artifact 46 + uses: actions/upload-pages-artifact@v2 47 + with: 48 + # Upload entire repository 49 + path: 'dist/' 50 + - name: Deploy to GitHub Pages 51 + id: deployment 52 + uses: actions/deploy-pages@v2
+31
.github/workflows/testcore.yml
··· 1 + name: Test Core 2 + 3 + on: 4 + push: 5 + branches: 6 + - tubes 7 + pull_request: 8 + branches: 9 + - tubes 10 + 11 + jobs: 12 + test: 13 + runs-on: ubuntu-latest 14 + 15 + steps: 16 + - name: Checkout code 17 + uses: actions/checkout@v3 18 + 19 + - name: Set up Node.js 20 + uses: actions/setup-node@v3 21 + with: 22 + node-version: 20 23 + cache: npm 24 + 25 + - name: Install dependencies 26 + run: npm ci 27 + working-directory: ./lib 28 + 29 + - name: Run Vitest 30 + run: npm run test 31 + working-directory: ./lib
+27
.gitignore
··· 1 + # Logs 2 + logs 3 + *.log 4 + npm-debug.log* 5 + yarn-debug.log* 6 + yarn-error.log* 7 + pnpm-debug.log* 8 + lerna-debug.log* 9 + 10 + node_modules 11 + dist 12 + dist-ssr 13 + core_dist 14 + *.local 15 + 16 + # Editor directories and files 17 + .vscode/* 18 + !.vscode/extensions.json 19 + .idea 20 + .DS_Store 21 + *.suo 22 + *.ntvs* 23 + *.njsproj 24 + *.sln 25 + *.sw? 26 + 27 + _site
+373
LICENCE
··· 1 + Mozilla Public License Version 2.0 2 + ================================== 3 + 4 + 1. Definitions 5 + -------------- 6 + 7 + 1.1. "Contributor" 8 + means each individual or legal entity that creates, contributes to 9 + the creation of, or owns Covered Software. 10 + 11 + 1.2. "Contributor Version" 12 + means the combination of the Contributions of others (if any) used 13 + by a Contributor and that particular Contributor's Contribution. 14 + 15 + 1.3. "Contribution" 16 + means Covered Software of a particular Contributor. 17 + 18 + 1.4. "Covered Software" 19 + means Source Code Form to which the initial Contributor has attached 20 + the notice in Exhibit A, the Executable Form of such Source Code 21 + Form, and Modifications of such Source Code Form, in each case 22 + including portions thereof. 23 + 24 + 1.5. "Incompatible With Secondary Licenses" 25 + means 26 + 27 + (a) that the initial Contributor has attached the notice described 28 + in Exhibit B to the Covered Software; or 29 + 30 + (b) that the Covered Software was made available under the terms of 31 + version 1.1 or earlier of the License, but not also under the 32 + terms of a Secondary License. 33 + 34 + 1.6. "Executable Form" 35 + means any form of the work other than Source Code Form. 36 + 37 + 1.7. "Larger Work" 38 + means a work that combines Covered Software with other material, in 39 + a separate file or files, that is not Covered Software. 40 + 41 + 1.8. "License" 42 + means this document. 43 + 44 + 1.9. "Licensable" 45 + means having the right to grant, to the maximum extent possible, 46 + whether at the time of the initial grant or subsequently, any and 47 + all of the rights conveyed by this License. 48 + 49 + 1.10. "Modifications" 50 + means any of the following: 51 + 52 + (a) any file in Source Code Form that results from an addition to, 53 + deletion from, or modification of the contents of Covered 54 + Software; or 55 + 56 + (b) any new file in Source Code Form that contains any Covered 57 + Software. 58 + 59 + 1.11. "Patent Claims" of a Contributor 60 + means any patent claim(s), including without limitation, method, 61 + process, and apparatus claims, in any patent Licensable by such 62 + Contributor that would be infringed, but for the grant of the 63 + License, by the making, using, selling, offering for sale, having 64 + made, import, or transfer of either its Contributions or its 65 + Contributor Version. 66 + 67 + 1.12. "Secondary License" 68 + means either the GNU General Public License, Version 2.0, the GNU 69 + Lesser General Public License, Version 2.1, the GNU Affero General 70 + Public License, Version 3.0, or any later versions of those 71 + licenses. 72 + 73 + 1.13. "Source Code Form" 74 + means the form of the work preferred for making modifications. 75 + 76 + 1.14. "You" (or "Your") 77 + means an individual or a legal entity exercising rights under this 78 + License. For legal entities, "You" includes any entity that 79 + controls, is controlled by, or is under common control with You. For 80 + purposes of this definition, "control" means (a) the power, direct 81 + or indirect, to cause the direction or management of such entity, 82 + whether by contract or otherwise, or (b) ownership of more than 83 + fifty percent (50%) of the outstanding shares or beneficial 84 + ownership of such entity. 85 + 86 + 2. License Grants and Conditions 87 + -------------------------------- 88 + 89 + 2.1. Grants 90 + 91 + Each Contributor hereby grants You a world-wide, royalty-free, 92 + non-exclusive license: 93 + 94 + (a) under intellectual property rights (other than patent or trademark) 95 + Licensable by such Contributor to use, reproduce, make available, 96 + modify, display, perform, distribute, and otherwise exploit its 97 + Contributions, either on an unmodified basis, with Modifications, or 98 + as part of a Larger Work; and 99 + 100 + (b) under Patent Claims of such Contributor to make, use, sell, offer 101 + for sale, have made, import, and otherwise transfer either its 102 + Contributions or its Contributor Version. 103 + 104 + 2.2. Effective Date 105 + 106 + The licenses granted in Section 2.1 with respect to any Contribution 107 + become effective for each Contribution on the date the Contributor first 108 + distributes such Contribution. 109 + 110 + 2.3. Limitations on Grant Scope 111 + 112 + The licenses granted in this Section 2 are the only rights granted under 113 + this License. No additional rights or licenses will be implied from the 114 + distribution or licensing of Covered Software under this License. 115 + Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 + Contributor: 117 + 118 + (a) for any code that a Contributor has removed from Covered Software; 119 + or 120 + 121 + (b) for infringements caused by: (i) Your and any other third party's 122 + modifications of Covered Software, or (ii) the combination of its 123 + Contributions with other software (except as part of its Contributor 124 + Version); or 125 + 126 + (c) under Patent Claims infringed by Covered Software in the absence of 127 + its Contributions. 128 + 129 + This License does not grant any rights in the trademarks, service marks, 130 + or logos of any Contributor (except as may be necessary to comply with 131 + the notice requirements in Section 3.4). 132 + 133 + 2.4. Subsequent Licenses 134 + 135 + No Contributor makes additional grants as a result of Your choice to 136 + distribute the Covered Software under a subsequent version of this 137 + License (see Section 10.2) or under the terms of a Secondary License (if 138 + permitted under the terms of Section 3.3). 139 + 140 + 2.5. Representation 141 + 142 + Each Contributor represents that the Contributor believes its 143 + Contributions are its original creation(s) or it has sufficient rights 144 + to grant the rights to its Contributions conveyed by this License. 145 + 146 + 2.6. Fair Use 147 + 148 + This License is not intended to limit any rights You have under 149 + applicable copyright doctrines of fair use, fair dealing, or other 150 + equivalents. 151 + 152 + 2.7. Conditions 153 + 154 + Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 + in Section 2.1. 156 + 157 + 3. Responsibilities 158 + ------------------- 159 + 160 + 3.1. Distribution of Source Form 161 + 162 + All distribution of Covered Software in Source Code Form, including any 163 + Modifications that You create or to which You contribute, must be under 164 + the terms of this License. You must inform recipients that the Source 165 + Code Form of the Covered Software is governed by the terms of this 166 + License, and how they can obtain a copy of this License. You may not 167 + attempt to alter or restrict the recipients' rights in the Source Code 168 + Form. 169 + 170 + 3.2. Distribution of Executable Form 171 + 172 + If You distribute Covered Software in Executable Form then: 173 + 174 + (a) such Covered Software must also be made available in Source Code 175 + Form, as described in Section 3.1, and You must inform recipients of 176 + the Executable Form how they can obtain a copy of such Source Code 177 + Form by reasonable means in a timely manner, at a charge no more 178 + than the cost of distribution to the recipient; and 179 + 180 + (b) You may distribute such Executable Form under the terms of this 181 + License, or sublicense it under different terms, provided that the 182 + license for the Executable Form does not attempt to limit or alter 183 + the recipients' rights in the Source Code Form under this License. 184 + 185 + 3.3. Distribution of a Larger Work 186 + 187 + You may create and distribute a Larger Work under terms of Your choice, 188 + provided that You also comply with the requirements of this License for 189 + the Covered Software. If the Larger Work is a combination of Covered 190 + Software with a work governed by one or more Secondary Licenses, and the 191 + Covered Software is not Incompatible With Secondary Licenses, this 192 + License permits You to additionally distribute such Covered Software 193 + under the terms of such Secondary License(s), so that the recipient of 194 + the Larger Work may, at their option, further distribute the Covered 195 + Software under the terms of either this License or such Secondary 196 + License(s). 197 + 198 + 3.4. Notices 199 + 200 + You may not remove or alter the substance of any license notices 201 + (including copyright notices, patent notices, disclaimers of warranty, 202 + or limitations of liability) contained within the Source Code Form of 203 + the Covered Software, except that You may alter any license notices to 204 + the extent required to remedy known factual inaccuracies. 205 + 206 + 3.5. Application of Additional Terms 207 + 208 + You may choose to offer, and to charge a fee for, warranty, support, 209 + indemnity or liability obligations to one or more recipients of Covered 210 + Software. However, You may do so only on Your own behalf, and not on 211 + behalf of any Contributor. You must make it absolutely clear that any 212 + such warranty, support, indemnity, or liability obligation is offered by 213 + You alone, and You hereby agree to indemnify every Contributor for any 214 + liability incurred by such Contributor as a result of warranty, support, 215 + indemnity or liability terms You offer. You may include additional 216 + disclaimers of warranty and limitations of liability specific to any 217 + jurisdiction. 218 + 219 + 4. Inability to Comply Due to Statute or Regulation 220 + --------------------------------------------------- 221 + 222 + If it is impossible for You to comply with any of the terms of this 223 + License with respect to some or all of the Covered Software due to 224 + statute, judicial order, or regulation then You must: (a) comply with 225 + the terms of this License to the maximum extent possible; and (b) 226 + describe the limitations and the code they affect. Such description must 227 + be placed in a text file included with all distributions of the Covered 228 + Software under this License. Except to the extent prohibited by statute 229 + or regulation, such description must be sufficiently detailed for a 230 + recipient of ordinary skill to be able to understand it. 231 + 232 + 5. Termination 233 + -------------- 234 + 235 + 5.1. The rights granted under this License will terminate automatically 236 + if You fail to comply with any of its terms. However, if You become 237 + compliant, then the rights granted under this License from a particular 238 + Contributor are reinstated (a) provisionally, unless and until such 239 + Contributor explicitly and finally terminates Your grants, and (b) on an 240 + ongoing basis, if such Contributor fails to notify You of the 241 + non-compliance by some reasonable means prior to 60 days after You have 242 + come back into compliance. Moreover, Your grants from a particular 243 + Contributor are reinstated on an ongoing basis if such Contributor 244 + notifies You of the non-compliance by some reasonable means, this is the 245 + first time You have received notice of non-compliance with this License 246 + from such Contributor, and You become compliant prior to 30 days after 247 + Your receipt of the notice. 248 + 249 + 5.2. If You initiate litigation against any entity by asserting a patent 250 + infringement claim (excluding declaratory judgment actions, 251 + counter-claims, and cross-claims) alleging that a Contributor Version 252 + directly or indirectly infringes any patent, then the rights granted to 253 + You by any and all Contributors for the Covered Software under Section 254 + 2.1 of this License shall terminate. 255 + 256 + 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 + end user license agreements (excluding distributors and resellers) which 258 + have been validly granted by You or Your distributors under this License 259 + prior to termination shall survive termination. 260 + 261 + ************************************************************************ 262 + * * 263 + * 6. Disclaimer of Warranty * 264 + * ------------------------- * 265 + * * 266 + * Covered Software is provided under this License on an "as is" * 267 + * basis, without warranty of any kind, either expressed, implied, or * 268 + * statutory, including, without limitation, warranties that the * 269 + * Covered Software is free of defects, merchantable, fit for a * 270 + * particular purpose or non-infringing. The entire risk as to the * 271 + * quality and performance of the Covered Software is with You. * 272 + * Should any Covered Software prove defective in any respect, You * 273 + * (not any Contributor) assume the cost of any necessary servicing, * 274 + * repair, or correction. This disclaimer of warranty constitutes an * 275 + * essential part of this License. No use of any Covered Software is * 276 + * authorized under this License except under this disclaimer. * 277 + * * 278 + ************************************************************************ 279 + 280 + ************************************************************************ 281 + * * 282 + * 7. Limitation of Liability * 283 + * -------------------------- * 284 + * * 285 + * Under no circumstances and under no legal theory, whether tort * 286 + * (including negligence), contract, or otherwise, shall any * 287 + * Contributor, or anyone who distributes Covered Software as * 288 + * permitted above, be liable to You for any direct, indirect, * 289 + * special, incidental, or consequential damages of any character * 290 + * including, without limitation, damages for lost profits, loss of * 291 + * goodwill, work stoppage, computer failure or malfunction, or any * 292 + * and all other commercial damages or losses, even if such party * 293 + * shall have been informed of the possibility of such damages. This * 294 + * limitation of liability shall not apply to liability for death or * 295 + * personal injury resulting from such party's negligence to the * 296 + * extent applicable law prohibits such limitation. Some * 297 + * jurisdictions do not allow the exclusion or limitation of * 298 + * incidental or consequential damages, so this exclusion and * 299 + * limitation may not apply to You. * 300 + * * 301 + ************************************************************************ 302 + 303 + 8. Litigation 304 + ------------- 305 + 306 + Any litigation relating to this License may be brought only in the 307 + courts of a jurisdiction where the defendant maintains its principal 308 + place of business and such litigation shall be governed by laws of that 309 + jurisdiction, without reference to its conflict-of-law provisions. 310 + Nothing in this Section shall prevent a party's ability to bring 311 + cross-claims or counter-claims. 312 + 313 + 9. Miscellaneous 314 + ---------------- 315 + 316 + This License represents the complete agreement concerning the subject 317 + matter hereof. If any provision of this License is held to be 318 + unenforceable, such provision shall be reformed only to the extent 319 + necessary to make it enforceable. Any law or regulation which provides 320 + that the language of a contract shall be construed against the drafter 321 + shall not be used to construe this License against a Contributor. 322 + 323 + 10. Versions of the License 324 + --------------------------- 325 + 326 + 10.1. New Versions 327 + 328 + Mozilla Foundation is the license steward. Except as provided in Section 329 + 10.3, no one other than the license steward has the right to modify or 330 + publish new versions of this License. Each version will be given a 331 + distinguishing version number. 332 + 333 + 10.2. Effect of New Versions 334 + 335 + You may distribute the Covered Software under the terms of the version 336 + of the License under which You originally received the Covered Software, 337 + or under the terms of any subsequent version published by the license 338 + steward. 339 + 340 + 10.3. Modified Versions 341 + 342 + If you create software not governed by this License, and you want to 343 + create a new license for such software, you may create and use a 344 + modified version of this License if you rename the license and remove 345 + any references to the name of the license steward (except to note that 346 + such modified license differs from this License). 347 + 348 + 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 + Licenses 350 + 351 + If You choose to distribute Source Code Form that is Incompatible With 352 + Secondary Licenses under the terms of this version of the License, the 353 + notice described in Exhibit B of this License must be attached. 354 + 355 + Exhibit A - Source Code Form License Notice 356 + ------------------------------------------- 357 + 358 + This Source Code Form is subject to the terms of the Mozilla Public 359 + License, v. 2.0. If a copy of the MPL was not distributed with this 360 + file, You can obtain one at https://mozilla.org/MPL/2.0/. 361 + 362 + If it is not possible or desirable to put the notice in a particular 363 + file, then You may include the notice in a location (such as a LICENSE 364 + file in a relevant directory) where a recipient would be likely to look 365 + for such a notice. 366 + 367 + You may add additional accurate notices of copyright ownership. 368 + 369 + Exhibit B - "Incompatible With Secondary Licenses" Notice 370 + --------------------------------------------------------- 371 + 372 + This Source Code Form is "Incompatible With Secondary Licenses", as 373 + defined by the Mozilla Public License, v. 2.0.
+51
README.md
··· 1 + **tubes** is a little irc client that goes in your web browser. 2 + 3 + ## interesting features 4 + 5 + - does the irc 6 + - looks nice 7 + - works nice also 8 + - supports many ircv3 features 9 + - built-in bouncer integration 10 + - uses a nice font 11 + - worse than discord 12 + - possibly better than matrix depending on your priorities 13 + 14 + ## build it 15 + 16 + this one has a build step (sorry) 17 + 18 + ### prerequisite(s) 19 + 20 + - own a computer 21 + - install node.js 22 + 23 + ### doing the thing 24 + 25 + ```bash 26 + cd neo 27 + npm install 28 + npm run build 29 + # move dist/ somewhere accessible xoxo 30 + ``` 31 + 32 + ## say hello 33 + 34 + it's all going down in [#tubes](ircs://irc.libera.chat/#tubes) on libera.chat 35 + 36 + ### contributing 37 + 38 + submit pull requests to our lovely [codeberg page][codeberg]. lots of things to 39 + do can be found on the [issue tracker][issues]. there's not much in the way of 40 + documentation at the moment, so if you get stuck and/or confused please do ping 41 + me! 42 + 43 + > [!NOTE] 44 + > This is a purely ideological project. Any changes that appear technically motivated will be rejected. 45 + 46 + [codeberg]: https://codeberg.org/smethwick/tubes 47 + [issues]: https://codeberg.org/smethwick/tubes/issues 48 + 49 + ## licence 50 + 51 + MPL 2.0, except tubes_core (inside lib/), which is ISC
+3
core/.npmignore
··· 1 + node_modules/ 2 + *.test.ts 3 + vitest.config.ts
+15
core/LICENCE
··· 1 + ISC License 2 + 3 + Copyright 2023 Leah Clark 4 + 5 + Permission to use, copy, modify, and/or distribute this software for any 6 + purpose with or without fee is hereby granted, provided that the above 7 + copyright notice and this permission notice appear in all copies. 8 + 9 + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 10 + REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 11 + FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 12 + INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 13 + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 14 + OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 15 + PERFORMANCE OF THIS SOFTWARE.
+88
core/README.md
··· 1 + # tubes core 2 + 3 + correct*, extensible & asynchronous IRCing for javascript 4 + *correctness coming soon 5 + 6 + (work in progress, don't bother using yet) 7 + 8 + ## lil example 9 + 10 + ```ts 11 + // import Connection from `ws`. ws, as the name implies, only lets you connect 12 + // over a websocket (so you can use it in browsers and the like). 13 + // if you need to connect over TCP, use `tubes_core/tcp` instead. 14 + import { Connection } from "tubes_core/ws"; 15 + 16 + // create the connection 17 + const conn = new Connection({ 18 + url: "wss://testnet.ergo.chat/webirc", 19 + nickname: "the_grungler", 20 + realname: "i'm the grungler", 21 + }); 22 + 23 + // start connecting 24 + await conn.connect(); 25 + // let the network know you're a bot by setting mode +B on your nick 26 + // (if you're writing a client, skip this) 27 + await conn.ima_bot(); 28 + 29 + // join a channel 30 + const channel = await conn.join_channel("#tubes"); 31 + // send a message. you can omit the `await` if you don't care whether the 32 + // message was sent successfully 33 + await channel.privmsg("hello world!"); 34 + 35 + // gracefully quit the network once all is said and done 36 + await conn.disconnect(); 37 + ``` 38 + 39 + for tcp: 40 + 41 + ```ts 42 + import { Connection } from "tubes_core/tcp"; 43 + 44 + const conn = new Connection({ 45 + url: "ircs://testnet.ergo.chat", 46 + nickname: "the_grungler", 47 + realname: "i'm the grungler", 48 + }); 49 + 50 + // same as above from there 51 + ``` 52 + 53 + listening out for messages: 54 + 55 + ```ts 56 + channel.subscribe((msg) => { 57 + // check if the message is a PRIVMSG (i.e. a normal message sent 58 + // to the channel) and if its content is "!ping". 59 + if (msg.is_privmsg() && msg.content() == "!ping") { 60 + // respond to the message. this uses the ircv3 replies thing if they're 61 + // available. 62 + await msg.reply("pong!"); 63 + } 64 + }); 65 + ``` 66 + 67 + ### bouncers 68 + 69 + tubes has an "adapter" system for connecting to irc bouncers. 70 + 71 + ### soju 72 + 73 + ```ts 74 + import { Adapter } from "tubes_core/soju"; 75 + 76 + const adp = new Adapter({ 77 + url: "wss://irc.example.org/socket", 78 + }); 79 + 80 + await adp.activate(); 81 + 82 + const conn = adp.connections[0] 83 + 84 + conn.send("PING hello"); 85 + ``` 86 + 87 + proper documentation coming soon xoxo 88 +
+37
core/adapter.ts
··· 1 + import { Signal } from "@preact/signals"; 2 + import { Connection, ConnectionConfig } from "./connection"; 3 + import { IrcProtocol } from "./support"; 4 + 5 + export abstract class Adapter<C = Connection> { 6 + abstract label: string; 7 + abstract protocols: IrcProtocol[]; 8 + 9 + get connections(): C[] { 10 + return this.$connections.peek(); 11 + } 12 + abstract $connections: Signal<C[]>; 13 + 14 + constructor( 15 + public id: string, 16 + ) { } 17 + 18 + abstract activate(): Promise<void>; 19 + abstract deactivate(): Promise<void>; 20 + 21 + abstract add_connection(config: ConnectionConfig): Promise<C>; 22 + 23 + /** 24 + * Get an idea of what caps this network supports. 25 + * Sends a CAP LS request, parses the response and then immedately quits. 26 + */ 27 + conduct_reconnaissance? :(url: string) => Promise<ReconData>; 28 + } 29 + 30 + export enum ReconError { 31 + Timeout = "Timed out", 32 + } 33 + 34 + export interface ReconData { 35 + url: string, 36 + supported_caps: Record<string, string | null> 37 + }
+54
core/capabilities.test.ts
··· 1 + import { expect, test } from "vitest"; 2 + import { parse_caps } from "./capabilities"; 3 + import { IrcMessage } from "./parser"; 4 + // import { MockConnection } from "./mock/connection"; 5 + 6 + test("parse caps", () => { 7 + const cap_msg = ":gaming.gov CAP * LS :account-notify extended-join multi-prefix sasl=PLAIN,EXTERNAL"; 8 + 9 + const expected = { 10 + "account-notify": null, 11 + "extended-join": null, 12 + "multi-prefix": null, 13 + "sasl": "PLAIN,EXTERNAL", 14 + }; 15 + 16 + const output = parse_caps([IrcMessage.parse(cap_msg)]); 17 + 18 + expect(output).toStrictEqual(expected); 19 + }); 20 + 21 + test("ls 302", () => { 22 + const msgs = [ 23 + ":gaming.gov CAP * LS * :account-notify extended-join multi-prefix sasl=PLAIN,EXTERNAL", 24 + ":gaming.gov CAP * LS :fake fake2 fake3=aaaaa", 25 + ].map(IrcMessage.parse); 26 + 27 + const expected = { 28 + "account-notify": null, 29 + "extended-join": null, 30 + "multi-prefix": null, 31 + "sasl": "PLAIN,EXTERNAL", 32 + "fake": null, 33 + "fake2": null, 34 + "fake3": "aaaaa", 35 + }; 36 + 37 + const output = parse_caps(msgs); 38 + 39 + expect(output).toStrictEqual(expected); 40 + }); 41 + 42 + // test("negoitate caps", async () => { 43 + // const conn = new MockConnection(); 44 + 45 + // negotiate_caps(conn, [ 46 + // IrcMessage.parse(":gaming.gov CAP * LS :message-tags"), 47 + // ]); 48 + 49 + // conn.recieve(":gaming.gov CAP * ACK :message-tags"); 50 + 51 + // expect(conn.messages).toStrictEqual([ 52 + // "CAP REQ :message-tags", 53 + // ]); 54 + // });
+78
core/capabilities.ts
··· 1 + import { Connection } from "./connection"; 2 + import { IrcMessage } from "./parser"; 3 + import { Matcher, Wildcard } from "./queue"; 4 + 5 + const supported_caps = [ 6 + "message-tags", 7 + "server-time", 8 + "batch", 9 + "sasl", 10 + "draft/chathistory", 11 + "draft/event-playback", 12 + "soju.im/bouncer-networks", 13 + ] as const; 14 + 15 + export async function collect_caps(conn: Connection): Promise<IrcMessage[]> { 16 + const cap_msgs: IrcMessage[] = []; 17 + 18 + // resolves when all cap msgs are received 19 + await new Promise((resolve) => { 20 + conn.queue.subscribe("cap listener", async (msg, unsub) => { 21 + if (msg.command == "CAP") { 22 + cap_msgs.push(msg); 23 + 24 + // https://ircv3.net/specs/extensions/capability-negotiation.html#multiline-replies-to-cap-ls-and-cap-list 25 + if (msg.params?.[2] != "*") { 26 + unsub(); 27 + resolve(null); 28 + } 29 + } 30 + }); 31 + }); 32 + 33 + return cap_msgs; 34 + } 35 + 36 + export function parse_caps(messages: IrcMessage[]): Map<string, string | null> { 37 + const result = messages.reduce((prev, obj) => { 38 + const caps_raw = obj.params?.at(-1); 39 + if (!caps_raw) return prev; 40 + 41 + const caps = caps_raw.split(" ").reduce((prev, obj) => { 42 + if (obj.includes("=")) { 43 + const [key, value] = obj.split("="); 44 + prev.set(key, value); 45 + } else { 46 + prev.set(obj, null); 47 + } 48 + 49 + return prev; 50 + }, new Map<string, string | null>()); 51 + 52 + return new Map([...prev, ...caps]); 53 + }, new Map<string, string | null>()); 54 + 55 + return result; 56 + } 57 + 58 + export async function negotiate_caps(conn: Connection, msgs: IrcMessage[]) { 59 + const server_caps = parse_caps(msgs); 60 + 61 + const supported = supported_caps.filter(cap => server_caps.has(cap)); 62 + 63 + if (supported.length > 0) { 64 + conn.send(`CAP REQ :${supported.join(" ")}`); 65 + // todo: send multiple when it stops fitting in one message 66 + } 67 + 68 + const res = await conn.expect("await cap ack", new Matcher("CAP", Wildcard.Any, "ACK")); 69 + const negotiated_caps = res.params?.[2].split(" "); 70 + 71 + const negotiated_caps_but_its_a_map 72 + = negotiated_caps?.reduce((acc, x) => acc.set(x, server_caps.get(x)), new Map) 73 + 74 + return { 75 + negotiated: negotiated_caps_but_its_a_map ?? new Map, 76 + available: server_caps 77 + }; 78 + }
+234
core/channel.ts
··· 1 + import type { Connection } from "./connection"; 2 + import { IrcMessage } from "./parser"; 3 + import { IrcChannelState } from "./support"; 4 + import { Mutex } from 'async-mutex'; 5 + import { signal, Signal, batch } from "@preact/signals"; 6 + import { FetchHistoryParams } from "./history"; 7 + 8 + const member_prefixes = [ 9 + "~", "&", "@", "%" 10 + ]; 11 + 12 + export abstract class ChatBuffer implements AsyncIterable<IrcMessage> { 13 + constructor(public name: string, public conn: Connection) { } 14 + 15 + [Symbol.asyncIterator] = this.history; 16 + 17 + /** iterate over the channel's history. 18 + * todo: improve documentation 19 + * @param [start_at="latest"] the timestamp to start fetching messages from. if the range 20 + * is `{ before: x }`, messages will be fetched in reverse order 21 + * (going forwards through time). 22 + * @param [chunk_size=50] 23 + */ 24 + async *history(start_at: FetchHistoryParams[1] = "latest", chunk_size = 50) { 25 + let next_range = start_at; 26 + 27 + while (true) { 28 + // fetch a page of history 29 + let page = await this.fetch_history_page(next_range, chunk_size); 30 + 31 + if (!page) return; 32 + 33 + // are we going backwards or forwards through history? 34 + let going_backwards 35 + = (typeof next_range != 'string' && 'after' in next_range) 36 + || next_range == "latest"; 37 + 38 + // console.log("next_range", next_range); 39 + // are we looking at a limited range of history? 40 + let is_limited = typeof next_range != 'string' && 'during' in next_range; 41 + 42 + console.log(page, page.at(-1)?.timestamp, page[0]?.timestamp) 43 + 44 + if (going_backwards) { 45 + // get the oldest message of the page 46 + let oldest = page.at(-1)?.timestamp; 47 + // if there's no oldest message, we must be out of messages to show. 48 + if (!oldest) break; 49 + 50 + // set the range for the next iteration 51 + next_range = { after: oldest }; 52 + 53 + page.reverse(); 54 + } else { 55 + // as above, but inverted 56 + let newest = page.at(0)?.timestamp; 57 + if (!newest) break; 58 + 59 + next_range = { before: newest }; 60 + } 61 + 62 + // yield messages from the page until we're done 63 + for (const msg of page) { 64 + yield msg; 65 + } 66 + 67 + // TODO: `during` logic 68 + if (is_limited) throw "todo"; 69 + } 70 + } 71 + 72 + async fetch_history_page(range: FetchHistoryParams[1], chunk_size = 50) { 73 + return this.conn.fetch_history?.( 74 + this.name, 75 + range, 76 + chunk_size 77 + ); 78 + } 79 + 80 + /** 81 + * Get messages pertaining to this channel. 82 + * @param callback Called on each message relevant to this channel 83 + * @returns A function to unsubscribe from the channel. 84 + * Call this after you're done with the subscription to prevent 85 + * memory leaks! 86 + */ 87 + subscribe(callback: (msg: IrcMessage) => void): () => void { 88 + const sub = this.conn.queue.subscribe(`${this.name} subscription`, (msg) => { 89 + if ( 90 + msg.command.toUpperCase() == ( 91 + "PRIVMSG" 92 + || "NOTICE" 93 + || "JOIN" 94 + || "PART" 95 + || "TAGMSG" 96 + || "MODE" 97 + || "TOPIC" 98 + ) && msg.params?.[0] == this.name 99 + ) { 100 + callback(msg); 101 + } 102 + }); 103 + return () => sub.unsubscribe(); 104 + } 105 + 106 + /** 107 + * Send a message to the channel. 108 + * @param message The text content of the message 109 + * @returns The resulting message 110 + */ 111 + privmsg(message: string): IrcMessage { 112 + const msg = new IrcMessage({ 113 + command: "PRIVMSG", 114 + params: [this.name, message], 115 + timestamp: new Date(Date.now()), 116 + }); 117 + this.conn.send(msg.toString()); 118 + 119 + // include the source on the returned message to avoid special 120 + // handling for clients. 121 + msg.source = { nick: this.conn.nickname } 122 + return msg; 123 + } 124 + } 125 + 126 + export class IrcChannel extends ChatBuffer { 127 + constructor(name: string, conn: Connection, initial_state = IrcChannelState.Pending) { 128 + super(name, conn); 129 + 130 + this.$state.value = initial_state; 131 + 132 + this.join_promise = new Promise((resolve, reject) => { 133 + this.join_resolve = resolve; 134 + this.join_reject = reject; 135 + 136 + setTimeout(() => { 137 + if (this.$state.value == IrcChannelState.Pending) { 138 + this.$state.value = IrcChannelState.Failed; 139 + this.$error.value = "Timed out"; 140 + this.join_reject("Timed out"); 141 + } 142 + }, 30000); 143 + }); 144 + } 145 + 146 + $state: Signal<IrcChannelState> = signal(IrcChannelState.Pending); 147 + $error: Signal<string | null> = signal(null) 148 + $topic: Signal<string | undefined> = signal(undefined); 149 + 150 + $members: Signal<string[]> = signal([]); 151 + $member_names: Signal<string[]> = signal([]); 152 + member_mutex = new Mutex(); 153 + 154 + join_promise: Promise<IrcChannel>; 155 + 156 + private join_resolve!: (value: IrcChannel) => void; 157 + private join_reject!: (reason?: any) => void; 158 + 159 + finish_join() { 160 + this.$state.value = IrcChannelState.Joined; 161 + this.join_resolve(this); 162 + } 163 + 164 + handle_join(message: IrcMessage) { 165 + const nick = message.source!.nick; 166 + this.member_mutex.runExclusive(() => { 167 + this.$members.value = [...this.$members.value, nick]; 168 + }) 169 + } 170 + 171 + handle_part(message: IrcMessage) { 172 + const nick = message.source!.nick; 173 + this.member_mutex.runExclusive(() => { 174 + this.$members.value = this.$members.value.filter(o => o != nick); 175 + }); 176 + } 177 + 178 + // #strip_nick_prefix(nick: string) { 179 + // const prefix = this.conn.isupport?.PREFIX 180 + // const prefixes = prefix?.slice(prefix.search(")")).split(""); 181 + // console.log(prefixes) 182 + // if (prefixes?.includes(nick[0])) { 183 + // return nick.slice(1); 184 + // } 185 + 186 + // return nick; 187 + // } 188 + 189 + // is set to true after rpl_endofnames. 190 + #has_all_names = false; 191 + 192 + handle_rpl_namreply(message: IrcMessage) { 193 + const nicks = message.params!.at(-1)!.split(" ").map(o => { 194 + if (member_prefixes.includes(o.charAt(0))) { 195 + return o.slice(1); 196 + } 197 + return o; 198 + }); 199 + 200 + this.member_mutex.runExclusive(() => { 201 + if (this.#has_all_names) { 202 + this.$members.value = []; 203 + } 204 + 205 + this.$members.value = [...this.$members.value, ...nicks] 206 + }) 207 + } 208 + 209 + handle_rpl_endofnames() { 210 + this.#has_all_names = true; 211 + } 212 + 213 + /** Join the channel after having {@link part}ed it. */ 214 + rejoin() { 215 + batch(() => { 216 + this.$members.value = []; 217 + this.$state.value = IrcChannelState.Pending; 218 + this.$error.value = null; 219 + }); 220 + this.conn.send(`JOIN ${this.name}`); 221 + } 222 + 223 + /** 224 + * Leave the channel. 225 + */ 226 + part() { 227 + this.conn.send(`PART ${this.name}`); 228 + batch(() => { 229 + this.$members.value = []; 230 + this.$state.value = IrcChannelState.Parted; 231 + }) 232 + } 233 + } 234 +
+412
core/connection.ts
··· 1 + import { Signal, signal } from "@preact/signals"; 2 + import { nanoid } from "nanoid"; 3 + import { ChatBuffer, IrcChannel } from "./channel"; 4 + import { MessageHandler, default_handler } from "./handler"; 5 + import { FetchHistoryParams } from "./history"; 6 + import { ISupport } from "./isupport"; 7 + import { IrcMessage } from "./parser"; 8 + import { Matcher, TaskQueue } from "./queue"; 9 + import { SaslConfig } from "./sasl"; 10 + import { IrcChannelState, Numeric, to_casemap_lowercase } from "./support"; 11 + import list_channels from "./list"; 12 + 13 + export interface ConnectionConfig { 14 + /** 15 + * a unique identifier for the connection. 16 + * 17 + * if not specified, a random id will be generated. 18 + */ 19 + id?: string, 20 + /** 21 + * the unique identifier of the adapeter hosting this connection, if it exists. 22 + */ 23 + adapter_id?: string, 24 + /** 25 + * a human-readable label for the connection. 26 + * defaults to the hostname of the connection URL. 27 + */ 28 + label?: string, 29 + /** 30 + * the URL of the IRC server to connect to. 31 + * 32 + * the protocol used varies depending on the type of connection, but 33 + * it's usually either `ws[s]://` for websocket connections or 34 + * `irc[s]://` for tcp connections. 35 + */ 36 + url: string | URL, 37 + /** 38 + * the user's nickname on the network. 39 + */ 40 + nickname: string, 41 + /** 42 + * the user's username (or ident). 43 + * defaults to the same value as {@link nickname}. 44 + */ 45 + username?: string, 46 + /** 47 + * the user's "real name" or "GECOS" field. contains general information 48 + * about the user. 49 + */ 50 + realname: string, 51 + /** 52 + * automatically connect when this connection is constructed. 53 + * @default false 54 + */ 55 + autoconnect?: boolean, 56 + /** 57 + * a list of channel names to join when the connection is established 58 + */ 59 + autojoin?: string[], 60 + 61 + sasl?: SaslConfig, 62 + } 63 + 64 + // todo: find some way to combine this with the other thing 65 + export interface ConnectionParameters { 66 + handler?: MessageHandler, 67 + history_fetcher?: (conn: Connection, ...params: FetchHistoryParams) => Promise<IrcMessage[]>, 68 + on_connect?: (conn: Connection) => void, 69 + } 70 + 71 + export enum ConnectionState { 72 + /** 73 + * the connection is simply chilling 74 + */ 75 + Disconnected, 76 + /** 77 + * the connection is in the middle of disconnecting 78 + */ 79 + Disconnecting, 80 + /** 81 + * the connection is connecting to the server 82 + */ 83 + Connecting, 84 + /** 85 + * the connection is trying to register 86 + */ 87 + Registering, 88 + /** 89 + * the connection is connected and ready to use 90 + */ 91 + Connected, 92 + /** 93 + * something went horribly wrong and the connection was severed. 94 + * the reason is typically stored in {@link Connection.$error} 95 + */ 96 + Failed, 97 + } 98 + 99 + export type ConnectionError = [code: ConnectionErrorCode, reason?: IrcMessage] 100 + 101 + export enum ConnectionErrorCode { 102 + NickTaken, 103 + SocketError, 104 + SaslFailed, 105 + SaslTooLong, 106 + SaslAborted, 107 + } 108 + 109 + const ping_interval = 30000; 110 + 111 + export abstract class Connection { 112 + constructor(public config: ConnectionConfig, opt?: ConnectionParameters) { 113 + this.#apply_config(config); 114 + 115 + if (opt) { 116 + if (opt.handler) this.handler = opt.handler; 117 + if (opt.history_fetcher) this.fetch_history = (...params) => opt.history_fetcher!(this, ...params); 118 + if (opt.on_connect) this.on_connect = opt.on_connect; 119 + } 120 + 121 + this.nickname = config.nickname; 122 + } 123 + 124 + id!: string; 125 + label!: string; 126 + url!: URL; 127 + nickname: string; 128 + username!: string; 129 + realname!: string; 130 + adapter_id?: string; 131 + hostname?: string; 132 + motd: string | null = null; 133 + system_msgs = <string[]>[] 134 + 135 + /** 136 + * Map of capabilities that have been negotiated and are able to be used. 137 + */ 138 + capabilities: Map<string, string | null> = new Map(); 139 + /** 140 + * Map of every capability the server advertised to us. 141 + * 142 + * These may or may not have been negotaited, check {@link capabilities} 143 + * if you want to know what capabilities the client can actually use. 144 + */ 145 + available_capabilities: Map<string, string | null> = new Map(); 146 + 147 + $buffers: Signal<ChatBuffer[]> = signal([]); 148 + $state = signal<ConnectionState>(ConnectionState.Disconnected); 149 + $error: Signal<[code: ConnectionErrorCode, reason?: IrcMessage] | null> = signal(null); 150 + 151 + abstract connect(): Promise<void>; 152 + abstract disconnect(state?: ConnectionState): Promise<void>; 153 + abstract send_raw(message: string): void; 154 + 155 + on_connect?: (conn: Connection) => void; 156 + 157 + send(message: string) { 158 + console.debug(`${this.id} ← SENT: ${message}`); 159 + this.send_raw(message); 160 + } 161 + 162 + queue: TaskQueue = new TaskQueue(this); 163 + 164 + // queue method aliases for brevity 165 + expect(...params: Parameters<TaskQueue["expect"]>) { 166 + return this.queue.expect(...params); 167 + } 168 + collect(...params: Parameters<TaskQueue["collect"]>) { 169 + return this.queue.collect(...params); 170 + } 171 + collect_batch(...params: Parameters<TaskQueue["collect_batch"]>) { 172 + return this.queue.collect_batch(...params); 173 + } 174 + 175 + is_connected = () => this.$state.value == ConnectionState.Connected; 176 + is_registering = () => this.$state.value == ConnectionState.Registering; 177 + 178 + isupport?: ISupport = {}; 179 + 180 + /** 181 + * the message handler for this connection. 182 + */ 183 + handler: MessageHandler = default_handler; 184 + 185 + masked_batches: string[] = []; 186 + 187 + protected async handle_incoming(message: string) { 188 + const parsed = IrcMessage.parse(message); 189 + 190 + const server_time = parsed.tags?.["time"]; 191 + const batch = parsed.tags?.['batch']; 192 + const masked = batch && this.masked_batches.includes(batch); 193 + 194 + parsed.timestamp = new Date(server_time ?? Date.now()); 195 + 196 + console.debug(`${this.id} → RECEIVED: ${message}`, parsed); 197 + 198 + // resolve pending tasks 199 + this.queue.resolve_tasks(parsed, { batch }); 200 + 201 + if (!masked) { 202 + this.handler(parsed, this); 203 + } 204 + } 205 + 206 + server_does_the_pinging = false; 207 + #ping_token = "tubes"; 208 + /** 209 + * begin pinging the server every {@link ping_interval} milliseconds. 210 + * 211 + * this is only used if the server doesn't bother pinging the client itself. 212 + */ 213 + protected start_pinging() { 214 + const token = crypto.randomUUID(); 215 + this.#ping_token = token; 216 + 217 + setTimeout(() => { 218 + if (this.server_does_the_pinging) { 219 + return; 220 + } 221 + 222 + if (this.$state.value != ConnectionState.Connected) { 223 + return; 224 + } 225 + 226 + // if the token changes, we can assume pinging has restarted 227 + // somewhere else and we can stop caring 228 + // this could be considered a bit hacky i suppose 229 + if (this.#ping_token != token) { 230 + return; 231 + } 232 + 233 + this.send(`PING ${this.#ping_token}`); 234 + this.start_pinging(); 235 + }, ping_interval); 236 + } 237 + 238 + /** 239 + * Dynamically update the connection's config whilst it's running. 240 + */ 241 + #apply_config(config: ConnectionConfig) { 242 + this.id = config.id ?? nanoid(); 243 + this.adapter_id = config.adapter_id; 244 + this.url = config.url instanceof URL ? config.url : new URL(config.url); 245 + this.label = config.label ?? this.url.hostname; 246 + this.username = config.username || config.nickname; 247 + this.realname = config.realname; 248 + } 249 + 250 + on_config_update?: (new_config: ConnectionConfig) => void; 251 + 252 + update_config(values_to_change: Partial<typeof this.config>) { 253 + if ( 254 + values_to_change.nickname 255 + && (values_to_change.nickname != this.nickname) 256 + && (this.is_connected() || this.is_registering()) 257 + ) { 258 + this.update_nick(values_to_change.nickname); 259 + } 260 + 261 + const new_config = { 262 + ...this.config, 263 + adapter_id: this.adapter_id, 264 + ...values_to_change, 265 + }; 266 + 267 + this.#apply_config(new_config); 268 + this.config = new_config; 269 + this.on_config_update?.(new_config); 270 + } 271 + 272 + /** 273 + * Request to change the nickname associated with the connection 274 + * @param new_nick The new nickname 275 + * @returns A promise that resolves when the change is acknowledged by the 276 + * server 277 + * @throws If the change is rejected by the server for whatever reason 278 + * (e.g., the nickname is already in use). 279 + */ 280 + async update_nick(new_nick: string) { 281 + let p = this.expect( 282 + "updated nick", 283 + new Matcher("NICK").require_source({ 284 + nick: this.nickname 285 + }), 286 + // todo: more rejections? 287 + new Matcher(Numeric.ERR_NICKNAMEINUSE) 288 + ); 289 + this.send(`NICK ${new_nick}`); 290 + 291 + let result = await p; 292 + this.nickname = result.params![0]; 293 + return this.nickname; 294 + } 295 + 296 + try_and_reconnect = false; 297 + retry_interval = 0; 298 + retry_count = 0; 299 + $recovering = signal(false); 300 + 301 + protected async recover_connection() { 302 + if (!this.try_and_reconnect) { 303 + return; 304 + } 305 + if (this.retry_count > 5) { 306 + this.try_and_reconnect = false; 307 + this.$recovering.value = false; 308 + this.retry_count = 0; 309 + this.retry_interval = 0; 310 + 311 + return; 312 + } 313 + 314 + setTimeout(() => { 315 + this.$recovering.value = true; 316 + 317 + this.connect(); 318 + this.retry_interval += 10000; 319 + }, this.retry_interval); 320 + } 321 + 322 + /** 323 + * join an IRC Channel 324 + * 325 + * if the channel is already in the list of joined channels, this will return it. 326 + * 327 + * @param channel The name of the channel. 328 + * @throws if the channel name doesn't match the network's supported channel prefixes 329 + * (see {@link ISupport.CHANTYPES}), is too long (see {@link ISupport.CHANNELLEN}), 330 + * or the server forbids it (e.g., if you're banned). 331 + * @returns A promise that resolves when the channel been joined. 332 + */ 333 + join_channel(channel: string): Promise<IrcChannel> { 334 + const supported_chan_prefixes = (this.isupport?.CHANTYPES ?? "#&").split(""); 335 + 336 + if (!supported_chan_prefixes.includes(channel.charAt(0))) { 337 + throw new Error( 338 + `Channels on this network must start with one of these: ` + 339 + supported_chan_prefixes.join(", ") 340 + ); 341 + } 342 + 343 + const max_length = Number(this.isupport?.CHANNELLEN) ?? 32; 344 + if (max_length < channel.length) { 345 + throw new Error( 346 + `Channel names cannot be more than ${max_length} characters long on ${this.label}` 347 + ) 348 + } 349 + 350 + const casemap = this.isupport?.CASEMAPPING ?? "rfc1459"; 351 + channel = to_casemap_lowercase(casemap, channel); 352 + 353 + // if we already have the channel, move along 354 + const existing_channel = this.get_channel(channel); 355 + if (existing_channel) return Promise.resolve(existing_channel); 356 + 357 + const new_channel = new IrcChannel(channel, this); 358 + // do the thing 359 + this.send(`JOIN ${channel}`); 360 + this.$buffers.value = [...this.$buffers.value, new_channel]; 361 + 362 + return new_channel.join_promise 363 + } 364 + 365 + /** 366 + * retrieve a channel from the connection's buffer list. 367 + * 368 + * @param name the name of the channel 369 + * @throws if the channel is actually a direct message 370 + * @returns the channel, or nothing 371 + */ 372 + get_channel(name: string): IrcChannel | undefined { 373 + const channel = this.$buffers.value.find(c => c.name == name); 374 + 375 + if (channel instanceof IrcChannel) { 376 + return channel 377 + } 378 + } 379 + 380 + /** called right after the connection has been closed. */ 381 + on_tidy?: () => void; 382 + 383 + protected tidy_up() { 384 + for (const buf of this.$buffers.value) { 385 + if (buf instanceof IrcChannel) { 386 + buf.$state.value = IrcChannelState.Parted 387 + } 388 + } 389 + 390 + if (this.on_tidy) this.on_tidy(); 391 + } 392 + 393 + /** 394 + * Fetch message history from a provided target. 395 + * @param target The channel or nick to retrieve history from. 396 + * @param range The range to retrieve history from. Can be an object with a 397 + * 'after' key or a 'before' key, or the string "latest". 398 + * @param limit The maximum number of messages to retrieve. 399 + */ 400 + fetch_history?(...params: FetchHistoryParams): Promise<IrcMessage[]>; 401 + 402 + list_channels = () => list_channels(this); 403 + 404 + /** 405 + * Convenience methods to check if something is supported by the connection. 406 + */ 407 + supports = { 408 + registration: () => this.capabilities.has("draft/account-registration"), 409 + sasl: () => this.capabilities.has("sasl"), 410 + batches: () => this.capabilities.has("batch"), 411 + } 412 + }
+11
core/formatting.ts
··· 1 + // export function irc_fmt_to_html(text: string) { 2 + // const tree = format(text); 3 + // } 4 + 5 + // type FmtNode = { 6 + // text: string, 7 + 8 + // }; 9 + 10 + // export function format(text: string) { 11 + // }
+206
core/handler.ts
··· 1 + import { batch } from "@preact/signals"; 2 + import { IrcChannel } from "./channel"; 3 + import { Connection, ConnectionErrorCode, ConnectionState } from "./connection"; 4 + import { parse_isupport } from "./isupport"; 5 + import { IrcMessage } from "./parser"; 6 + import { IrcChannelState, Numeric } from "./support"; 7 + 8 + export type MessageHandler = (message: IrcMessage, connection: Connection) => Promise<void>; 9 + 10 + export async function default_handler(message: IrcMessage, connection: Connection) { 11 + switch (message.command) { 12 + case Numeric.ERR_NICKNAMEINUSE: { 13 + if (connection.$state.value == ConnectionState.Registering) { 14 + connection.disconnect(ConnectionState.Failed); 15 + connection.$error.value = [ConnectionErrorCode.NickTaken, message]; 16 + } 17 + 18 + break; 19 + } 20 + 21 + case Numeric.RPL_WELCOME: { 22 + connection.hostname = message.source?.nick; 23 + 24 + const nick = message.params?.[0]; 25 + if (!nick) break; 26 + 27 + connection.nickname = nick; 28 + 29 + if (connection.config.nickname != nick) { 30 + const msg = 31 + `Your nickname was changed to ${nick} by the network.`; 32 + connection.system_msgs.push(msg); 33 + } 34 + 35 + break; 36 + } 37 + 38 + case Numeric.RPL_ISUPPORT: { 39 + connection.isupport = { ...connection.isupport, ...parse_isupport(message) }; 40 + break; 41 + } 42 + 43 + case "PING": { 44 + connection.send(`PONG ${message.params?.[0] ?? ""}`); 45 + connection.server_does_the_pinging = true; 46 + break; 47 + } 48 + 49 + case "JOIN": { 50 + const target = message.params?.[0]; 51 + const nick = message.source?.nick; 52 + if (!target) break; 53 + 54 + const chan = connection.get_channel(target); 55 + 56 + if (nick == connection.nickname) { 57 + // if we already have the channel, let it know it's finished joining 58 + if (chan) { 59 + chan.finish_join(); 60 + break; 61 + } else { 62 + // otherwise, add it to the buffer list 63 + let new_channel = new IrcChannel(target, connection, IrcChannelState.Joined); 64 + connection.$buffers.value = [...connection.$buffers.value, new_channel]; 65 + } 66 + } 67 + 68 + chan?.handle_join(message); 69 + 70 + break; 71 + } 72 + 73 + case "PART": { 74 + const target = message.params?.[0]; 75 + const nick = message.source?.nick; 76 + 77 + if (!target) break; 78 + const chan = connection.get_channel(target); 79 + 80 + if (target && nick == connection.nickname) { 81 + if (!chan) { 82 + break; 83 + } 84 + chan.$state.value = IrcChannelState.Parted; 85 + } 86 + 87 + chan?.handle_part(message); 88 + 89 + break; 90 + } 91 + 92 + case "QUIT": { 93 + connection.$buffers.value.forEach(channel => { 94 + channel instanceof IrcChannel 95 + ? channel.handle_part(message) 96 + : null; 97 + }); 98 + 99 + break; 100 + } 101 + 102 + case Numeric.RPL_TOPIC: { 103 + const channel = connection.get_channel(message.params![1]!); 104 + if (channel) { 105 + channel.$topic.value = message.params![2]; 106 + } 107 + break; 108 + } 109 + 110 + case "TOPIC": { 111 + const channel = connection.get_channel(message.params![0]!); 112 + if (channel) { 113 + channel.$topic.value = message.params![1]; 114 + } 115 + break; 116 + } 117 + 118 + // case "PRIVMSG": { 119 + // const target = message.params?.[0]; 120 + // if (!target) break; 121 + 122 + // break; 123 + // } 124 + 125 + case Numeric.RPL_NAMREPLY: { 126 + const target = message.params![2]!; 127 + const channel = connection.get_channel(target); 128 + channel?.handle_rpl_namreply(message); 129 + 130 + break; 131 + } 132 + 133 + case Numeric.RPL_ENDOFNAMES: { 134 + const target = message.params![1]!; 135 + const channel = connection.get_channel(target); 136 + channel?.handle_rpl_endofnames(); 137 + break; 138 + } 139 + 140 + case Numeric.ERR_NEEDMOREPARAMS: 141 + case Numeric.ERR_NOSUCHCHANNEL: 142 + case Numeric.ERR_TOOMANYCHANNELS: 143 + case Numeric.ERR_BADCHANNELKEY: 144 + case Numeric.ERR_BANNEDFROMCHAN: 145 + case Numeric.ERR_CHANNELISFULL: 146 + case Numeric.ERR_INVITEONLYCHAN: 147 + case Numeric.ERR_NONSTD_NO_REG: 148 + case Numeric.ERR_BADCHANMASK: 149 + case Numeric.ERR_NONSTD_ILLEGALNAME: { 150 + // special case for ERR_BADCHANMASK 151 + // which is formatted different because ?? 152 + const target = message.command == Numeric.ERR_BADCHANMASK 153 + ? message.params![0] 154 + : message.params![1]; 155 + const channel = connection.get_channel(target); 156 + 157 + if (!channel) { 158 + break; 159 + } 160 + 161 + if (channel.$state.value == IrcChannelState.Joined) { 162 + // this is unlikely to actually happen but it should 163 + // prevent you from getting locked out of a channel 164 + // if some errant part of the app sends out a join 165 + // message when it shouldn't 166 + break; 167 + } 168 + 169 + batch(() => { 170 + channel.$error.value = message.params!.at(-1)!; 171 + channel.$state.value = IrcChannelState.Failed; 172 + }) 173 + 174 + break; 175 + } 176 + 177 + case "NICK": { 178 + const old_nick = message.source!.nick; 179 + const new_nick = message.params![0]; 180 + 181 + if (old_nick == connection.nickname) { 182 + connection.nickname = new_nick; 183 + } 184 + 185 + for (const b of connection.$buffers.value) { 186 + if (b instanceof IrcChannel) { 187 + // update the members list of each channel 188 + let members = b.$members.peek(); 189 + const index = members.indexOf(old_nick); 190 + if (index > -1) { 191 + // remove the old nick 192 + members.splice(index, 1); 193 + } 194 + 195 + // add the new one 196 + members.push(new_nick); 197 + 198 + b.$members.value = members; 199 + } 200 + } 201 + 202 + break; 203 + } 204 + } 205 + } 206 +
+33
core/history.ts
··· 1 + import { Connection } from "."; 2 + import { IrcMessage } from "./parser"; 3 + 4 + export type FetchHistoryParams 5 + = [ 6 + target: string, 7 + range: { before: Date, } 8 + | { after: Date, } 9 + | { during: [Date, Date] } 10 + | "latest", 11 + limit: number 12 + ] 13 + 14 + async function chathistory(conn: Connection, ...[target, range, limit]: FetchHistoryParams): Promise<IrcMessage[]> { 15 + if (!conn.capabilities.has('draft/chathistory')) { 16 + return []; 17 + } 18 + switch (range) { 19 + case "latest": { 20 + conn.send(`CHATHISTORY LATEST ${target} * ${limit}`); 21 + break; 22 + } 23 + default: return []; 24 + } 25 + 26 + const msgs = await conn.collect_batch("chathistory", { mask: true }); 27 + console.log(msgs); 28 + return msgs.toReversed(); 29 + } 30 + 31 + export default class History { 32 + static chathistory = chathistory; 33 + }
+3
core/index.ts
··· 1 + import { Connection } from './connection' 2 + 3 + export { Connection }
+54
core/isupport.ts
··· 1 + import { IrcMessage } from "./parser"; 2 + 3 + /** 4 + * everything that can be included in isupport 5 + * as per the spec. 6 + * 7 + * https://modern.ircdocs.horse/#rplisupport-parameters 8 + */ 9 + export interface ISupport { 10 + AWAYLEN?: string, 11 + CASEMAPPING?: "ascii" | "rfc1459" | "strict-rfc1459" | "rfc7613", 12 + CHANLIMIT?: string, 13 + CHANMODES?: string, 14 + CHANNELLEN?: string, 15 + CHANTYPES?: string, 16 + ELIST?: string, 17 + EXCEPTS?: string, 18 + EXTBAN?: string, 19 + HOSTLEN?: string, 20 + INVEX?: string, 21 + KICKLEN?: string, 22 + MAXLIST?: string, 23 + MAXTARGETS?: string, 24 + MODES?: string, 25 + NETWORK?: string, 26 + NICKLEN?: string, 27 + PREFIX?: string, 28 + SAFELIST?: string, 29 + SILENCE?: string, 30 + STATUSMSG?: string, 31 + TARGMAX?: string, 32 + TOPICLEN?: string, 33 + USERLEN?: string, 34 + } 35 + 36 + /** 37 + * parse an RPL_ISUPPORT message into a strongly-typed object. 38 + */ 39 + export function parse_isupport(msg: IrcMessage): ISupport { 40 + const result: ISupport = {}; 41 + const params = msg.params!.slice(1, -1); 42 + 43 + for (const param of params) { 44 + if (param.includes("=")) { 45 + const [key, value] = param.split("="); 46 + // to any type-checking afficionados: don't worry about this. 47 + result[key as keyof ISupport] = value as any; 48 + } else { 49 + result[param as keyof ISupport] = param as any; 50 + } 51 + } 52 + 53 + return result; 54 + }
+27
core/list.ts
··· 1 + import { Connection } from "."; 2 + import { Matcher } from "./queue"; 3 + import { Numeric } from "./support"; 4 + 5 + export interface ChannelListItem { 6 + name: string, 7 + member_count: number, 8 + topic: string, 9 + } 10 + 11 + async function list_channels(conn: Connection): Promise<ChannelListItem[]> { 12 + conn.send("LIST"); 13 + 14 + const resp = await conn.collect("list response", { 15 + start: new Matcher(Numeric.RPL_LISTSTART), 16 + finish: new Matcher(Numeric.RPL_LISTEND), 17 + include: new Matcher(Numeric.RPL_LISTEND), 18 + }); 19 + 20 + return resp.map(x => ({ 21 + name: x.params![1], 22 + member_count: Number(x.params![2]), 23 + topic: x.params![3], 24 + })) 25 + } 26 + 27 + export default list_channels;
+35
core/mock/connection.ts
··· 1 + import { Connection, ConnectionState } from "../connection"; 2 + import { IrcMessage } from "../parser"; 3 + 4 + export class MockConnection extends Connection { 5 + constructor() { 6 + super({ 7 + id: "mock", 8 + label: "", 9 + url: "", 10 + nickname: "", 11 + username: "", 12 + realname: "", 13 + autoconnect: false, 14 + }); 15 + } 16 + 17 + async connect(): Promise<void> { } 18 + 19 + async disconnect(state?: ConnectionState): Promise<void> { 20 + this.$state.value = state ?? ConnectionState.Disconnected; 21 + } 22 + 23 + messages: IrcMessage[] = []; 24 + send_raw(message: string): void { 25 + this.messages.push(IrcMessage.parse(message)); 26 + } 27 + 28 + recieve(message: string) { 29 + this.handle_incoming(message); 30 + } 31 + 32 + fetch_history(target: string, range: "latest" | { before: Date; } | { after: Date; } | { during: [Date, Date]; }, limit: number): Promise<IrcMessage[]> { 33 + throw new Error("Method not implemented."); 34 + } 35 + }
core/modes.ts

This is a binary file and will not be displayed.

+26
core/motd.ts
··· 1 + import { Connection } from "."; 2 + import { IrcMessage } from "./parser"; 3 + import { Matcher } from "./queue"; 4 + import { Numeric } from "./support"; 5 + 6 + export async function collect_motd(conn: Connection) { 7 + try { 8 + const motd = await conn.collect("collect motd", { 9 + start: new Matcher(Numeric.RPL_MOTDSTART), 10 + include: new Matcher(Numeric.RPL_MOTD), 11 + finish: new Matcher(Numeric.RPL_ENDOFMOTD), 12 + reject_on: new Matcher(Numeric.ERR_NOMOTD), 13 + }); 14 + return motd 15 + .map(o => o.params!.at(-1)) 16 + .map(o => o?.startsWith("- ") ? o.substring(2) : o) 17 + .join("\n"); 18 + } catch (e) { 19 + if (e instanceof IrcMessage && e.command == Numeric.ERR_NOMOTD) { 20 + return null; 21 + } else { 22 + throw e; 23 + } 24 + } 25 + 26 + }
+1611
core/package-lock.json
··· 1 + { 2 + "name": "tubes_core", 3 + "version": "0.0.9", 4 + "lockfileVersion": 3, 5 + "requires": true, 6 + "packages": { 7 + "": { 8 + "name": "tubes_core", 9 + "version": "0.0.9", 10 + "license": "ISC", 11 + "dependencies": { 12 + "@preact/signals": "^1.3.0", 13 + "async-mutex": "^0.5.0", 14 + "isomorphic-ws": "^5.0.0", 15 + "nanoid": "^5.0.7", 16 + "ws": "^8.18.0" 17 + }, 18 + "devDependencies": { 19 + "@types/ws": "^8.5.12", 20 + "happy-dom": "14.12.3", 21 + "typescript": "^5.5.4", 22 + "vite": "^5.3.5", 23 + "vitest": "^2.0.4" 24 + } 25 + }, 26 + "node_modules/@ampproject/remapping": { 27 + "version": "2.3.0", 28 + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", 29 + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", 30 + "dev": true, 31 + "dependencies": { 32 + "@jridgewell/gen-mapping": "^0.3.5", 33 + "@jridgewell/trace-mapping": "^0.3.24" 34 + }, 35 + "engines": { 36 + "node": ">=6.0.0" 37 + } 38 + }, 39 + "node_modules/@esbuild/aix-ppc64": { 40 + "version": "0.21.5", 41 + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", 42 + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", 43 + "cpu": [ 44 + "ppc64" 45 + ], 46 + "dev": true, 47 + "optional": true, 48 + "os": [ 49 + "aix" 50 + ], 51 + "engines": { 52 + "node": ">=12" 53 + } 54 + }, 55 + "node_modules/@esbuild/android-arm": { 56 + "version": "0.21.5", 57 + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", 58 + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", 59 + "cpu": [ 60 + "arm" 61 + ], 62 + "dev": true, 63 + "optional": true, 64 + "os": [ 65 + "android" 66 + ], 67 + "engines": { 68 + "node": ">=12" 69 + } 70 + }, 71 + "node_modules/@esbuild/android-arm64": { 72 + "version": "0.21.5", 73 + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", 74 + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", 75 + "cpu": [ 76 + "arm64" 77 + ], 78 + "dev": true, 79 + "optional": true, 80 + "os": [ 81 + "android" 82 + ], 83 + "engines": { 84 + "node": ">=12" 85 + } 86 + }, 87 + "node_modules/@esbuild/android-x64": { 88 + "version": "0.21.5", 89 + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", 90 + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", 91 + "cpu": [ 92 + "x64" 93 + ], 94 + "dev": true, 95 + "optional": true, 96 + "os": [ 97 + "android" 98 + ], 99 + "engines": { 100 + "node": ">=12" 101 + } 102 + }, 103 + "node_modules/@esbuild/darwin-arm64": { 104 + "version": "0.21.5", 105 + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", 106 + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", 107 + "cpu": [ 108 + "arm64" 109 + ], 110 + "dev": true, 111 + "optional": true, 112 + "os": [ 113 + "darwin" 114 + ], 115 + "engines": { 116 + "node": ">=12" 117 + } 118 + }, 119 + "node_modules/@esbuild/darwin-x64": { 120 + "version": "0.21.5", 121 + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", 122 + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", 123 + "cpu": [ 124 + "x64" 125 + ], 126 + "dev": true, 127 + "optional": true, 128 + "os": [ 129 + "darwin" 130 + ], 131 + "engines": { 132 + "node": ">=12" 133 + } 134 + }, 135 + "node_modules/@esbuild/freebsd-arm64": { 136 + "version": "0.21.5", 137 + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", 138 + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", 139 + "cpu": [ 140 + "arm64" 141 + ], 142 + "dev": true, 143 + "optional": true, 144 + "os": [ 145 + "freebsd" 146 + ], 147 + "engines": { 148 + "node": ">=12" 149 + } 150 + }, 151 + "node_modules/@esbuild/freebsd-x64": { 152 + "version": "0.21.5", 153 + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", 154 + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", 155 + "cpu": [ 156 + "x64" 157 + ], 158 + "dev": true, 159 + "optional": true, 160 + "os": [ 161 + "freebsd" 162 + ], 163 + "engines": { 164 + "node": ">=12" 165 + } 166 + }, 167 + "node_modules/@esbuild/linux-arm": { 168 + "version": "0.21.5", 169 + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", 170 + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", 171 + "cpu": [ 172 + "arm" 173 + ], 174 + "dev": true, 175 + "optional": true, 176 + "os": [ 177 + "linux" 178 + ], 179 + "engines": { 180 + "node": ">=12" 181 + } 182 + }, 183 + "node_modules/@esbuild/linux-arm64": { 184 + "version": "0.21.5", 185 + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", 186 + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", 187 + "cpu": [ 188 + "arm64" 189 + ], 190 + "dev": true, 191 + "optional": true, 192 + "os": [ 193 + "linux" 194 + ], 195 + "engines": { 196 + "node": ">=12" 197 + } 198 + }, 199 + "node_modules/@esbuild/linux-ia32": { 200 + "version": "0.21.5", 201 + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", 202 + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", 203 + "cpu": [ 204 + "ia32" 205 + ], 206 + "dev": true, 207 + "optional": true, 208 + "os": [ 209 + "linux" 210 + ], 211 + "engines": { 212 + "node": ">=12" 213 + } 214 + }, 215 + "node_modules/@esbuild/linux-loong64": { 216 + "version": "0.21.5", 217 + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", 218 + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", 219 + "cpu": [ 220 + "loong64" 221 + ], 222 + "dev": true, 223 + "optional": true, 224 + "os": [ 225 + "linux" 226 + ], 227 + "engines": { 228 + "node": ">=12" 229 + } 230 + }, 231 + "node_modules/@esbuild/linux-mips64el": { 232 + "version": "0.21.5", 233 + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", 234 + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", 235 + "cpu": [ 236 + "mips64el" 237 + ], 238 + "dev": true, 239 + "optional": true, 240 + "os": [ 241 + "linux" 242 + ], 243 + "engines": { 244 + "node": ">=12" 245 + } 246 + }, 247 + "node_modules/@esbuild/linux-ppc64": { 248 + "version": "0.21.5", 249 + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", 250 + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", 251 + "cpu": [ 252 + "ppc64" 253 + ], 254 + "dev": true, 255 + "optional": true, 256 + "os": [ 257 + "linux" 258 + ], 259 + "engines": { 260 + "node": ">=12" 261 + } 262 + }, 263 + "node_modules/@esbuild/linux-riscv64": { 264 + "version": "0.21.5", 265 + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", 266 + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", 267 + "cpu": [ 268 + "riscv64" 269 + ], 270 + "dev": true, 271 + "optional": true, 272 + "os": [ 273 + "linux" 274 + ], 275 + "engines": { 276 + "node": ">=12" 277 + } 278 + }, 279 + "node_modules/@esbuild/linux-s390x": { 280 + "version": "0.21.5", 281 + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", 282 + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", 283 + "cpu": [ 284 + "s390x" 285 + ], 286 + "dev": true, 287 + "optional": true, 288 + "os": [ 289 + "linux" 290 + ], 291 + "engines": { 292 + "node": ">=12" 293 + } 294 + }, 295 + "node_modules/@esbuild/linux-x64": { 296 + "version": "0.21.5", 297 + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", 298 + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", 299 + "cpu": [ 300 + "x64" 301 + ], 302 + "dev": true, 303 + "optional": true, 304 + "os": [ 305 + "linux" 306 + ], 307 + "engines": { 308 + "node": ">=12" 309 + } 310 + }, 311 + "node_modules/@esbuild/netbsd-x64": { 312 + "version": "0.21.5", 313 + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", 314 + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", 315 + "cpu": [ 316 + "x64" 317 + ], 318 + "dev": true, 319 + "optional": true, 320 + "os": [ 321 + "netbsd" 322 + ], 323 + "engines": { 324 + "node": ">=12" 325 + } 326 + }, 327 + "node_modules/@esbuild/openbsd-x64": { 328 + "version": "0.21.5", 329 + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", 330 + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", 331 + "cpu": [ 332 + "x64" 333 + ], 334 + "dev": true, 335 + "optional": true, 336 + "os": [ 337 + "openbsd" 338 + ], 339 + "engines": { 340 + "node": ">=12" 341 + } 342 + }, 343 + "node_modules/@esbuild/sunos-x64": { 344 + "version": "0.21.5", 345 + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", 346 + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", 347 + "cpu": [ 348 + "x64" 349 + ], 350 + "dev": true, 351 + "optional": true, 352 + "os": [ 353 + "sunos" 354 + ], 355 + "engines": { 356 + "node": ">=12" 357 + } 358 + }, 359 + "node_modules/@esbuild/win32-arm64": { 360 + "version": "0.21.5", 361 + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", 362 + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", 363 + "cpu": [ 364 + "arm64" 365 + ], 366 + "dev": true, 367 + "optional": true, 368 + "os": [ 369 + "win32" 370 + ], 371 + "engines": { 372 + "node": ">=12" 373 + } 374 + }, 375 + "node_modules/@esbuild/win32-ia32": { 376 + "version": "0.21.5", 377 + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", 378 + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", 379 + "cpu": [ 380 + "ia32" 381 + ], 382 + "dev": true, 383 + "optional": true, 384 + "os": [ 385 + "win32" 386 + ], 387 + "engines": { 388 + "node": ">=12" 389 + } 390 + }, 391 + "node_modules/@esbuild/win32-x64": { 392 + "version": "0.21.5", 393 + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", 394 + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", 395 + "cpu": [ 396 + "x64" 397 + ], 398 + "dev": true, 399 + "optional": true, 400 + "os": [ 401 + "win32" 402 + ], 403 + "engines": { 404 + "node": ">=12" 405 + } 406 + }, 407 + "node_modules/@jridgewell/gen-mapping": { 408 + "version": "0.3.5", 409 + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", 410 + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", 411 + "dev": true, 412 + "dependencies": { 413 + "@jridgewell/set-array": "^1.2.1", 414 + "@jridgewell/sourcemap-codec": "^1.4.10", 415 + "@jridgewell/trace-mapping": "^0.3.24" 416 + }, 417 + "engines": { 418 + "node": ">=6.0.0" 419 + } 420 + }, 421 + "node_modules/@jridgewell/resolve-uri": { 422 + "version": "3.1.2", 423 + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", 424 + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", 425 + "dev": true, 426 + "engines": { 427 + "node": ">=6.0.0" 428 + } 429 + }, 430 + "node_modules/@jridgewell/set-array": { 431 + "version": "1.2.1", 432 + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", 433 + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", 434 + "dev": true, 435 + "engines": { 436 + "node": ">=6.0.0" 437 + } 438 + }, 439 + "node_modules/@jridgewell/sourcemap-codec": { 440 + "version": "1.5.0", 441 + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", 442 + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", 443 + "dev": true 444 + }, 445 + "node_modules/@jridgewell/trace-mapping": { 446 + "version": "0.3.25", 447 + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", 448 + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", 449 + "dev": true, 450 + "dependencies": { 451 + "@jridgewell/resolve-uri": "^3.1.0", 452 + "@jridgewell/sourcemap-codec": "^1.4.14" 453 + } 454 + }, 455 + "node_modules/@preact/signals": { 456 + "version": "1.3.0", 457 + "resolved": "https://registry.npmjs.org/@preact/signals/-/signals-1.3.0.tgz", 458 + "integrity": "sha512-EOMeg42SlLS72dhoq6Vjq08havnLseWmPQ8A0YsgIAqMgWgx7V1a39+Pxo6i7SY5NwJtH4849JogFq3M67AzWg==", 459 + "dependencies": { 460 + "@preact/signals-core": "^1.7.0" 461 + }, 462 + "funding": { 463 + "type": "opencollective", 464 + "url": "https://opencollective.com/preact" 465 + }, 466 + "peerDependencies": { 467 + "preact": "10.x" 468 + } 469 + }, 470 + "node_modules/@preact/signals-core": { 471 + "version": "1.7.0", 472 + "resolved": "https://registry.npmjs.org/@preact/signals-core/-/signals-core-1.7.0.tgz", 473 + "integrity": "sha512-bEZLgmJGSBVP5PUPDowhPW3bVdMmp9Tr5OEl+SQK+8Tv9T7UsIfyN905cfkmmeqw8z4xp8T6zrl4M1uj9+HAfg==", 474 + "funding": { 475 + "type": "opencollective", 476 + "url": "https://opencollective.com/preact" 477 + } 478 + }, 479 + "node_modules/@rollup/rollup-android-arm-eabi": { 480 + "version": "4.19.1", 481 + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.19.1.tgz", 482 + "integrity": "sha512-XzqSg714++M+FXhHfXpS1tDnNZNpgxxuGZWlRG/jSj+VEPmZ0yg6jV4E0AL3uyBKxO8mO3xtOsP5mQ+XLfrlww==", 483 + "cpu": [ 484 + "arm" 485 + ], 486 + "dev": true, 487 + "optional": true, 488 + "os": [ 489 + "android" 490 + ] 491 + }, 492 + "node_modules/@rollup/rollup-android-arm64": { 493 + "version": "4.19.1", 494 + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.19.1.tgz", 495 + "integrity": "sha512-thFUbkHteM20BGShD6P08aungq4irbIZKUNbG70LN8RkO7YztcGPiKTTGZS7Kw+x5h8hOXs0i4OaHwFxlpQN6A==", 496 + "cpu": [ 497 + "arm64" 498 + ], 499 + "dev": true, 500 + "optional": true, 501 + "os": [ 502 + "android" 503 + ] 504 + }, 505 + "node_modules/@rollup/rollup-darwin-arm64": { 506 + "version": "4.19.1", 507 + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.19.1.tgz", 508 + "integrity": "sha512-8o6eqeFZzVLia2hKPUZk4jdE3zW7LCcZr+MD18tXkgBBid3lssGVAYuox8x6YHoEPDdDa9ixTaStcmx88lio5Q==", 509 + "cpu": [ 510 + "arm64" 511 + ], 512 + "dev": true, 513 + "optional": true, 514 + "os": [ 515 + "darwin" 516 + ] 517 + }, 518 + "node_modules/@rollup/rollup-darwin-x64": { 519 + "version": "4.19.1", 520 + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.19.1.tgz", 521 + "integrity": "sha512-4T42heKsnbjkn7ovYiAdDVRRWZLU9Kmhdt6HafZxFcUdpjlBlxj4wDrt1yFWLk7G4+E+8p2C9tcmSu0KA6auGA==", 522 + "cpu": [ 523 + "x64" 524 + ], 525 + "dev": true, 526 + "optional": true, 527 + "os": [ 528 + "darwin" 529 + ] 530 + }, 531 + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { 532 + "version": "4.19.1", 533 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.19.1.tgz", 534 + "integrity": "sha512-MXg1xp+e5GhZ3Vit1gGEyoC+dyQUBy2JgVQ+3hUrD9wZMkUw/ywgkpK7oZgnB6kPpGrxJ41clkPPnsknuD6M2Q==", 535 + "cpu": [ 536 + "arm" 537 + ], 538 + "dev": true, 539 + "optional": true, 540 + "os": [ 541 + "linux" 542 + ] 543 + }, 544 + "node_modules/@rollup/rollup-linux-arm-musleabihf": { 545 + "version": "4.19.1", 546 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.19.1.tgz", 547 + "integrity": "sha512-DZNLwIY4ftPSRVkJEaxYkq7u2zel7aah57HESuNkUnz+3bZHxwkCUkrfS2IWC1sxK6F2QNIR0Qr/YXw7nkF3Pw==", 548 + "cpu": [ 549 + "arm" 550 + ], 551 + "dev": true, 552 + "optional": true, 553 + "os": [ 554 + "linux" 555 + ] 556 + }, 557 + "node_modules/@rollup/rollup-linux-arm64-gnu": { 558 + "version": "4.19.1", 559 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.19.1.tgz", 560 + "integrity": "sha512-C7evongnjyxdngSDRRSQv5GvyfISizgtk9RM+z2biV5kY6S/NF/wta7K+DanmktC5DkuaJQgoKGf7KUDmA7RUw==", 561 + "cpu": [ 562 + "arm64" 563 + ], 564 + "dev": true, 565 + "optional": true, 566 + "os": [ 567 + "linux" 568 + ] 569 + }, 570 + "node_modules/@rollup/rollup-linux-arm64-musl": { 571 + "version": "4.19.1", 572 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.19.1.tgz", 573 + "integrity": "sha512-89tFWqxfxLLHkAthAcrTs9etAoBFRduNfWdl2xUs/yLV+7XDrJ5yuXMHptNqf1Zw0UCA3cAutkAiAokYCkaPtw==", 574 + "cpu": [ 575 + "arm64" 576 + ], 577 + "dev": true, 578 + "optional": true, 579 + "os": [ 580 + "linux" 581 + ] 582 + }, 583 + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { 584 + "version": "4.19.1", 585 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.19.1.tgz", 586 + "integrity": "sha512-PromGeV50sq+YfaisG8W3fd+Cl6mnOOiNv2qKKqKCpiiEke2KiKVyDqG/Mb9GWKbYMHj5a01fq/qlUR28PFhCQ==", 587 + "cpu": [ 588 + "ppc64" 589 + ], 590 + "dev": true, 591 + "optional": true, 592 + "os": [ 593 + "linux" 594 + ] 595 + }, 596 + "node_modules/@rollup/rollup-linux-riscv64-gnu": { 597 + "version": "4.19.1", 598 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.19.1.tgz", 599 + "integrity": "sha512-/1BmHYh+iz0cNCP0oHCuF8CSiNj0JOGf0jRlSo3L/FAyZyG2rGBuKpkZVH9YF+x58r1jgWxvm1aRg3DHrLDt6A==", 600 + "cpu": [ 601 + "riscv64" 602 + ], 603 + "dev": true, 604 + "optional": true, 605 + "os": [ 606 + "linux" 607 + ] 608 + }, 609 + "node_modules/@rollup/rollup-linux-s390x-gnu": { 610 + "version": "4.19.1", 611 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.19.1.tgz", 612 + "integrity": "sha512-0cYP5rGkQWRZKy9/HtsWVStLXzCF3cCBTRI+qRL8Z+wkYlqN7zrSYm6FuY5Kd5ysS5aH0q5lVgb/WbG4jqXN1Q==", 613 + "cpu": [ 614 + "s390x" 615 + ], 616 + "dev": true, 617 + "optional": true, 618 + "os": [ 619 + "linux" 620 + ] 621 + }, 622 + "node_modules/@rollup/rollup-linux-x64-gnu": { 623 + "version": "4.19.1", 624 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.19.1.tgz", 625 + "integrity": "sha512-XUXeI9eM8rMP8aGvii/aOOiMvTs7xlCosq9xCjcqI9+5hBxtjDpD+7Abm1ZhVIFE1J2h2VIg0t2DX/gjespC2Q==", 626 + "cpu": [ 627 + "x64" 628 + ], 629 + "dev": true, 630 + "optional": true, 631 + "os": [ 632 + "linux" 633 + ] 634 + }, 635 + "node_modules/@rollup/rollup-linux-x64-musl": { 636 + "version": "4.19.1", 637 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.19.1.tgz", 638 + "integrity": "sha512-V7cBw/cKXMfEVhpSvVZhC+iGifD6U1zJ4tbibjjN+Xi3blSXaj/rJynAkCFFQfoG6VZrAiP7uGVzL440Q6Me2Q==", 639 + "cpu": [ 640 + "x64" 641 + ], 642 + "dev": true, 643 + "optional": true, 644 + "os": [ 645 + "linux" 646 + ] 647 + }, 648 + "node_modules/@rollup/rollup-win32-arm64-msvc": { 649 + "version": "4.19.1", 650 + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.19.1.tgz", 651 + "integrity": "sha512-88brja2vldW/76jWATlBqHEoGjJLRnP0WOEKAUbMcXaAZnemNhlAHSyj4jIwMoP2T750LE9lblvD4e2jXleZsA==", 652 + "cpu": [ 653 + "arm64" 654 + ], 655 + "dev": true, 656 + "optional": true, 657 + "os": [ 658 + "win32" 659 + ] 660 + }, 661 + "node_modules/@rollup/rollup-win32-ia32-msvc": { 662 + "version": "4.19.1", 663 + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.19.1.tgz", 664 + "integrity": "sha512-LdxxcqRVSXi6k6JUrTah1rHuaupoeuiv38du8Mt4r4IPer3kwlTo+RuvfE8KzZ/tL6BhaPlzJ3835i6CxrFIRQ==", 665 + "cpu": [ 666 + "ia32" 667 + ], 668 + "dev": true, 669 + "optional": true, 670 + "os": [ 671 + "win32" 672 + ] 673 + }, 674 + "node_modules/@rollup/rollup-win32-x64-msvc": { 675 + "version": "4.19.1", 676 + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.19.1.tgz", 677 + "integrity": "sha512-2bIrL28PcK3YCqD9anGxDxamxdiJAxA+l7fWIwM5o8UqNy1t3d1NdAweO2XhA0KTDJ5aH1FsuiT5+7VhtHliXg==", 678 + "cpu": [ 679 + "x64" 680 + ], 681 + "dev": true, 682 + "optional": true, 683 + "os": [ 684 + "win32" 685 + ] 686 + }, 687 + "node_modules/@types/estree": { 688 + "version": "1.0.5", 689 + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", 690 + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", 691 + "dev": true 692 + }, 693 + "node_modules/@types/node": { 694 + "version": "22.0.0", 695 + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.0.0.tgz", 696 + "integrity": "sha512-VT7KSYudcPOzP5Q0wfbowyNLaVR8QWUdw+088uFWwfvpY6uCWaXpqV6ieLAu9WBcnTa7H4Z5RLK8I5t2FuOcqw==", 697 + "dev": true, 698 + "dependencies": { 699 + "undici-types": "~6.11.1" 700 + } 701 + }, 702 + "node_modules/@types/ws": { 703 + "version": "8.5.12", 704 + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz", 705 + "integrity": "sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==", 706 + "dev": true, 707 + "dependencies": { 708 + "@types/node": "*" 709 + } 710 + }, 711 + "node_modules/@vitest/expect": { 712 + "version": "2.0.4", 713 + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.0.4.tgz", 714 + "integrity": "sha512-39jr5EguIoanChvBqe34I8m1hJFI4+jxvdOpD7gslZrVQBKhh8H9eD7J/LJX4zakrw23W+dITQTDqdt43xVcJw==", 715 + "dev": true, 716 + "dependencies": { 717 + "@vitest/spy": "2.0.4", 718 + "@vitest/utils": "2.0.4", 719 + "chai": "^5.1.1", 720 + "tinyrainbow": "^1.2.0" 721 + }, 722 + "funding": { 723 + "url": "https://opencollective.com/vitest" 724 + } 725 + }, 726 + "node_modules/@vitest/pretty-format": { 727 + "version": "2.0.4", 728 + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.0.4.tgz", 729 + "integrity": "sha512-RYZl31STbNGqf4l2eQM1nvKPXE0NhC6Eq0suTTePc4mtMQ1Fn8qZmjV4emZdEdG2NOWGKSCrHZjmTqDCDoeFBw==", 730 + "dev": true, 731 + "dependencies": { 732 + "tinyrainbow": "^1.2.0" 733 + }, 734 + "funding": { 735 + "url": "https://opencollective.com/vitest" 736 + } 737 + }, 738 + "node_modules/@vitest/runner": { 739 + "version": "2.0.4", 740 + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.0.4.tgz", 741 + "integrity": "sha512-Gk+9Su/2H2zNfNdeJR124gZckd5st4YoSuhF1Rebi37qTXKnqYyFCd9KP4vl2cQHbtuVKjfEKrNJxHHCW8thbQ==", 742 + "dev": true, 743 + "dependencies": { 744 + "@vitest/utils": "2.0.4", 745 + "pathe": "^1.1.2" 746 + }, 747 + "funding": { 748 + "url": "https://opencollective.com/vitest" 749 + } 750 + }, 751 + "node_modules/@vitest/snapshot": { 752 + "version": "2.0.4", 753 + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.0.4.tgz", 754 + "integrity": "sha512-or6Mzoz/pD7xTvuJMFYEtso1vJo1S5u6zBTinfl+7smGUhqybn6VjzCDMhmTyVOFWwkCMuNjmNNxnyXPgKDoPw==", 755 + "dev": true, 756 + "dependencies": { 757 + "@vitest/pretty-format": "2.0.4", 758 + "magic-string": "^0.30.10", 759 + "pathe": "^1.1.2" 760 + }, 761 + "funding": { 762 + "url": "https://opencollective.com/vitest" 763 + } 764 + }, 765 + "node_modules/@vitest/spy": { 766 + "version": "2.0.4", 767 + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.0.4.tgz", 768 + "integrity": "sha512-uTXU56TNoYrTohb+6CseP8IqNwlNdtPwEO0AWl+5j7NelS6x0xZZtP0bDWaLvOfUbaYwhhWp1guzXUxkC7mW7Q==", 769 + "dev": true, 770 + "dependencies": { 771 + "tinyspy": "^3.0.0" 772 + }, 773 + "funding": { 774 + "url": "https://opencollective.com/vitest" 775 + } 776 + }, 777 + "node_modules/@vitest/utils": { 778 + "version": "2.0.4", 779 + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.0.4.tgz", 780 + "integrity": "sha512-Zc75QuuoJhOBnlo99ZVUkJIuq4Oj0zAkrQ2VzCqNCx6wAwViHEh5Fnp4fiJTE9rA+sAoXRf00Z9xGgfEzV6fzQ==", 781 + "dev": true, 782 + "dependencies": { 783 + "@vitest/pretty-format": "2.0.4", 784 + "estree-walker": "^3.0.3", 785 + "loupe": "^3.1.1", 786 + "tinyrainbow": "^1.2.0" 787 + }, 788 + "funding": { 789 + "url": "https://opencollective.com/vitest" 790 + } 791 + }, 792 + "node_modules/assertion-error": { 793 + "version": "2.0.1", 794 + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", 795 + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", 796 + "dev": true, 797 + "engines": { 798 + "node": ">=12" 799 + } 800 + }, 801 + "node_modules/async-mutex": { 802 + "version": "0.5.0", 803 + "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.5.0.tgz", 804 + "integrity": "sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==", 805 + "dependencies": { 806 + "tslib": "^2.4.0" 807 + } 808 + }, 809 + "node_modules/cac": { 810 + "version": "6.7.14", 811 + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", 812 + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", 813 + "dev": true, 814 + "engines": { 815 + "node": ">=8" 816 + } 817 + }, 818 + "node_modules/chai": { 819 + "version": "5.1.1", 820 + "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz", 821 + "integrity": "sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==", 822 + "dev": true, 823 + "dependencies": { 824 + "assertion-error": "^2.0.1", 825 + "check-error": "^2.1.1", 826 + "deep-eql": "^5.0.1", 827 + "loupe": "^3.1.0", 828 + "pathval": "^2.0.0" 829 + }, 830 + "engines": { 831 + "node": ">=12" 832 + } 833 + }, 834 + "node_modules/check-error": { 835 + "version": "2.1.1", 836 + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", 837 + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", 838 + "dev": true, 839 + "engines": { 840 + "node": ">= 16" 841 + } 842 + }, 843 + "node_modules/cross-spawn": { 844 + "version": "7.0.3", 845 + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", 846 + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", 847 + "dev": true, 848 + "dependencies": { 849 + "path-key": "^3.1.0", 850 + "shebang-command": "^2.0.0", 851 + "which": "^2.0.1" 852 + }, 853 + "engines": { 854 + "node": ">= 8" 855 + } 856 + }, 857 + "node_modules/debug": { 858 + "version": "4.3.6", 859 + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", 860 + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", 861 + "dev": true, 862 + "dependencies": { 863 + "ms": "2.1.2" 864 + }, 865 + "engines": { 866 + "node": ">=6.0" 867 + }, 868 + "peerDependenciesMeta": { 869 + "supports-color": { 870 + "optional": true 871 + } 872 + } 873 + }, 874 + "node_modules/deep-eql": { 875 + "version": "5.0.2", 876 + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", 877 + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", 878 + "dev": true, 879 + "engines": { 880 + "node": ">=6" 881 + } 882 + }, 883 + "node_modules/entities": { 884 + "version": "4.5.0", 885 + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", 886 + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", 887 + "dev": true, 888 + "engines": { 889 + "node": ">=0.12" 890 + }, 891 + "funding": { 892 + "url": "https://github.com/fb55/entities?sponsor=1" 893 + } 894 + }, 895 + "node_modules/esbuild": { 896 + "version": "0.21.5", 897 + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", 898 + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", 899 + "dev": true, 900 + "hasInstallScript": true, 901 + "bin": { 902 + "esbuild": "bin/esbuild" 903 + }, 904 + "engines": { 905 + "node": ">=12" 906 + }, 907 + "optionalDependencies": { 908 + "@esbuild/aix-ppc64": "0.21.5", 909 + "@esbuild/android-arm": "0.21.5", 910 + "@esbuild/android-arm64": "0.21.5", 911 + "@esbuild/android-x64": "0.21.5", 912 + "@esbuild/darwin-arm64": "0.21.5", 913 + "@esbuild/darwin-x64": "0.21.5", 914 + "@esbuild/freebsd-arm64": "0.21.5", 915 + "@esbuild/freebsd-x64": "0.21.5", 916 + "@esbuild/linux-arm": "0.21.5", 917 + "@esbuild/linux-arm64": "0.21.5", 918 + "@esbuild/linux-ia32": "0.21.5", 919 + "@esbuild/linux-loong64": "0.21.5", 920 + "@esbuild/linux-mips64el": "0.21.5", 921 + "@esbuild/linux-ppc64": "0.21.5", 922 + "@esbuild/linux-riscv64": "0.21.5", 923 + "@esbuild/linux-s390x": "0.21.5", 924 + "@esbuild/linux-x64": "0.21.5", 925 + "@esbuild/netbsd-x64": "0.21.5", 926 + "@esbuild/openbsd-x64": "0.21.5", 927 + "@esbuild/sunos-x64": "0.21.5", 928 + "@esbuild/win32-arm64": "0.21.5", 929 + "@esbuild/win32-ia32": "0.21.5", 930 + "@esbuild/win32-x64": "0.21.5" 931 + } 932 + }, 933 + "node_modules/estree-walker": { 934 + "version": "3.0.3", 935 + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", 936 + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", 937 + "dev": true, 938 + "dependencies": { 939 + "@types/estree": "^1.0.0" 940 + } 941 + }, 942 + "node_modules/execa": { 943 + "version": "8.0.1", 944 + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", 945 + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", 946 + "dev": true, 947 + "dependencies": { 948 + "cross-spawn": "^7.0.3", 949 + "get-stream": "^8.0.1", 950 + "human-signals": "^5.0.0", 951 + "is-stream": "^3.0.0", 952 + "merge-stream": "^2.0.0", 953 + "npm-run-path": "^5.1.0", 954 + "onetime": "^6.0.0", 955 + "signal-exit": "^4.1.0", 956 + "strip-final-newline": "^3.0.0" 957 + }, 958 + "engines": { 959 + "node": ">=16.17" 960 + }, 961 + "funding": { 962 + "url": "https://github.com/sindresorhus/execa?sponsor=1" 963 + } 964 + }, 965 + "node_modules/fsevents": { 966 + "version": "2.3.3", 967 + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 968 + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 969 + "dev": true, 970 + "hasInstallScript": true, 971 + "optional": true, 972 + "os": [ 973 + "darwin" 974 + ], 975 + "engines": { 976 + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 977 + } 978 + }, 979 + "node_modules/get-func-name": { 980 + "version": "2.0.2", 981 + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", 982 + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", 983 + "dev": true, 984 + "engines": { 985 + "node": "*" 986 + } 987 + }, 988 + "node_modules/get-stream": { 989 + "version": "8.0.1", 990 + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", 991 + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", 992 + "dev": true, 993 + "engines": { 994 + "node": ">=16" 995 + }, 996 + "funding": { 997 + "url": "https://github.com/sponsors/sindresorhus" 998 + } 999 + }, 1000 + "node_modules/happy-dom": { 1001 + "version": "14.12.3", 1002 + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-14.12.3.tgz", 1003 + "integrity": "sha512-vsYlEs3E9gLwA1Hp+w3qzu+RUDFf4VTT8cyKqVICoZ2k7WM++Qyd2LwzyTi5bqMJFiIC/vNpTDYuxdreENRK/g==", 1004 + "dev": true, 1005 + "dependencies": { 1006 + "entities": "^4.5.0", 1007 + "webidl-conversions": "^7.0.0", 1008 + "whatwg-mimetype": "^3.0.0" 1009 + }, 1010 + "engines": { 1011 + "node": ">=16.0.0" 1012 + } 1013 + }, 1014 + "node_modules/human-signals": { 1015 + "version": "5.0.0", 1016 + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", 1017 + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", 1018 + "dev": true, 1019 + "engines": { 1020 + "node": ">=16.17.0" 1021 + } 1022 + }, 1023 + "node_modules/is-stream": { 1024 + "version": "3.0.0", 1025 + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", 1026 + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", 1027 + "dev": true, 1028 + "engines": { 1029 + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" 1030 + }, 1031 + "funding": { 1032 + "url": "https://github.com/sponsors/sindresorhus" 1033 + } 1034 + }, 1035 + "node_modules/isexe": { 1036 + "version": "2.0.0", 1037 + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 1038 + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", 1039 + "dev": true 1040 + }, 1041 + "node_modules/isomorphic-ws": { 1042 + "version": "5.0.0", 1043 + "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz", 1044 + "integrity": "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==", 1045 + "peerDependencies": { 1046 + "ws": "*" 1047 + } 1048 + }, 1049 + "node_modules/loupe": { 1050 + "version": "3.1.1", 1051 + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.1.tgz", 1052 + "integrity": "sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==", 1053 + "dev": true, 1054 + "dependencies": { 1055 + "get-func-name": "^2.0.1" 1056 + } 1057 + }, 1058 + "node_modules/magic-string": { 1059 + "version": "0.30.11", 1060 + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", 1061 + "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", 1062 + "dev": true, 1063 + "dependencies": { 1064 + "@jridgewell/sourcemap-codec": "^1.5.0" 1065 + } 1066 + }, 1067 + "node_modules/merge-stream": { 1068 + "version": "2.0.0", 1069 + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", 1070 + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", 1071 + "dev": true 1072 + }, 1073 + "node_modules/mimic-fn": { 1074 + "version": "4.0.0", 1075 + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", 1076 + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", 1077 + "dev": true, 1078 + "engines": { 1079 + "node": ">=12" 1080 + }, 1081 + "funding": { 1082 + "url": "https://github.com/sponsors/sindresorhus" 1083 + } 1084 + }, 1085 + "node_modules/ms": { 1086 + "version": "2.1.2", 1087 + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 1088 + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 1089 + "dev": true 1090 + }, 1091 + "node_modules/nanoid": { 1092 + "version": "5.0.7", 1093 + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.7.tgz", 1094 + "integrity": "sha512-oLxFY2gd2IqnjcYyOXD8XGCftpGtZP2AbHbOkthDkvRywH5ayNtPVy9YlOPcHckXzbLTCHpkb7FB+yuxKV13pQ==", 1095 + "funding": [ 1096 + { 1097 + "type": "github", 1098 + "url": "https://github.com/sponsors/ai" 1099 + } 1100 + ], 1101 + "bin": { 1102 + "nanoid": "bin/nanoid.js" 1103 + }, 1104 + "engines": { 1105 + "node": "^18 || >=20" 1106 + } 1107 + }, 1108 + "node_modules/npm-run-path": { 1109 + "version": "5.3.0", 1110 + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", 1111 + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", 1112 + "dev": true, 1113 + "dependencies": { 1114 + "path-key": "^4.0.0" 1115 + }, 1116 + "engines": { 1117 + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" 1118 + }, 1119 + "funding": { 1120 + "url": "https://github.com/sponsors/sindresorhus" 1121 + } 1122 + }, 1123 + "node_modules/npm-run-path/node_modules/path-key": { 1124 + "version": "4.0.0", 1125 + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", 1126 + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", 1127 + "dev": true, 1128 + "engines": { 1129 + "node": ">=12" 1130 + }, 1131 + "funding": { 1132 + "url": "https://github.com/sponsors/sindresorhus" 1133 + } 1134 + }, 1135 + "node_modules/onetime": { 1136 + "version": "6.0.0", 1137 + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", 1138 + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", 1139 + "dev": true, 1140 + "dependencies": { 1141 + "mimic-fn": "^4.0.0" 1142 + }, 1143 + "engines": { 1144 + "node": ">=12" 1145 + }, 1146 + "funding": { 1147 + "url": "https://github.com/sponsors/sindresorhus" 1148 + } 1149 + }, 1150 + "node_modules/path-key": { 1151 + "version": "3.1.1", 1152 + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 1153 + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 1154 + "dev": true, 1155 + "engines": { 1156 + "node": ">=8" 1157 + } 1158 + }, 1159 + "node_modules/pathe": { 1160 + "version": "1.1.2", 1161 + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", 1162 + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", 1163 + "dev": true 1164 + }, 1165 + "node_modules/pathval": { 1166 + "version": "2.0.0", 1167 + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", 1168 + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", 1169 + "dev": true, 1170 + "engines": { 1171 + "node": ">= 14.16" 1172 + } 1173 + }, 1174 + "node_modules/picocolors": { 1175 + "version": "1.0.1", 1176 + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", 1177 + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", 1178 + "dev": true 1179 + }, 1180 + "node_modules/postcss": { 1181 + "version": "8.4.40", 1182 + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.40.tgz", 1183 + "integrity": "sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q==", 1184 + "dev": true, 1185 + "funding": [ 1186 + { 1187 + "type": "opencollective", 1188 + "url": "https://opencollective.com/postcss/" 1189 + }, 1190 + { 1191 + "type": "tidelift", 1192 + "url": "https://tidelift.com/funding/github/npm/postcss" 1193 + }, 1194 + { 1195 + "type": "github", 1196 + "url": "https://github.com/sponsors/ai" 1197 + } 1198 + ], 1199 + "dependencies": { 1200 + "nanoid": "^3.3.7", 1201 + "picocolors": "^1.0.1", 1202 + "source-map-js": "^1.2.0" 1203 + }, 1204 + "engines": { 1205 + "node": "^10 || ^12 || >=14" 1206 + } 1207 + }, 1208 + "node_modules/postcss/node_modules/nanoid": { 1209 + "version": "3.3.7", 1210 + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", 1211 + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", 1212 + "dev": true, 1213 + "funding": [ 1214 + { 1215 + "type": "github", 1216 + "url": "https://github.com/sponsors/ai" 1217 + } 1218 + ], 1219 + "bin": { 1220 + "nanoid": "bin/nanoid.cjs" 1221 + }, 1222 + "engines": { 1223 + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 1224 + } 1225 + }, 1226 + "node_modules/preact": { 1227 + "version": "10.23.1", 1228 + "resolved": "https://registry.npmjs.org/preact/-/preact-10.23.1.tgz", 1229 + "integrity": "sha512-O5UdRsNh4vdZaTieWe3XOgSpdMAmkIYBCT3VhQDlKrzyCm8lUYsk0fmVEvoQQifoOjFRTaHZO69ylrzTW2BH+A==", 1230 + "peer": true, 1231 + "funding": { 1232 + "type": "opencollective", 1233 + "url": "https://opencollective.com/preact" 1234 + } 1235 + }, 1236 + "node_modules/rollup": { 1237 + "version": "4.19.1", 1238 + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.19.1.tgz", 1239 + "integrity": "sha512-K5vziVlg7hTpYfFBI+91zHBEMo6jafYXpkMlqZjg7/zhIG9iHqazBf4xz9AVdjS9BruRn280ROqLI7G3OFRIlw==", 1240 + "dev": true, 1241 + "dependencies": { 1242 + "@types/estree": "1.0.5" 1243 + }, 1244 + "bin": { 1245 + "rollup": "dist/bin/rollup" 1246 + }, 1247 + "engines": { 1248 + "node": ">=18.0.0", 1249 + "npm": ">=8.0.0" 1250 + }, 1251 + "optionalDependencies": { 1252 + "@rollup/rollup-android-arm-eabi": "4.19.1", 1253 + "@rollup/rollup-android-arm64": "4.19.1", 1254 + "@rollup/rollup-darwin-arm64": "4.19.1", 1255 + "@rollup/rollup-darwin-x64": "4.19.1", 1256 + "@rollup/rollup-linux-arm-gnueabihf": "4.19.1", 1257 + "@rollup/rollup-linux-arm-musleabihf": "4.19.1", 1258 + "@rollup/rollup-linux-arm64-gnu": "4.19.1", 1259 + "@rollup/rollup-linux-arm64-musl": "4.19.1", 1260 + "@rollup/rollup-linux-powerpc64le-gnu": "4.19.1", 1261 + "@rollup/rollup-linux-riscv64-gnu": "4.19.1", 1262 + "@rollup/rollup-linux-s390x-gnu": "4.19.1", 1263 + "@rollup/rollup-linux-x64-gnu": "4.19.1", 1264 + "@rollup/rollup-linux-x64-musl": "4.19.1", 1265 + "@rollup/rollup-win32-arm64-msvc": "4.19.1", 1266 + "@rollup/rollup-win32-ia32-msvc": "4.19.1", 1267 + "@rollup/rollup-win32-x64-msvc": "4.19.1", 1268 + "fsevents": "~2.3.2" 1269 + } 1270 + }, 1271 + "node_modules/shebang-command": { 1272 + "version": "2.0.0", 1273 + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 1274 + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 1275 + "dev": true, 1276 + "dependencies": { 1277 + "shebang-regex": "^3.0.0" 1278 + }, 1279 + "engines": { 1280 + "node": ">=8" 1281 + } 1282 + }, 1283 + "node_modules/shebang-regex": { 1284 + "version": "3.0.0", 1285 + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 1286 + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 1287 + "dev": true, 1288 + "engines": { 1289 + "node": ">=8" 1290 + } 1291 + }, 1292 + "node_modules/siginfo": { 1293 + "version": "2.0.0", 1294 + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", 1295 + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", 1296 + "dev": true 1297 + }, 1298 + "node_modules/signal-exit": { 1299 + "version": "4.1.0", 1300 + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", 1301 + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", 1302 + "dev": true, 1303 + "engines": { 1304 + "node": ">=14" 1305 + }, 1306 + "funding": { 1307 + "url": "https://github.com/sponsors/isaacs" 1308 + } 1309 + }, 1310 + "node_modules/source-map-js": { 1311 + "version": "1.2.0", 1312 + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", 1313 + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", 1314 + "dev": true, 1315 + "engines": { 1316 + "node": ">=0.10.0" 1317 + } 1318 + }, 1319 + "node_modules/stackback": { 1320 + "version": "0.0.2", 1321 + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", 1322 + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", 1323 + "dev": true 1324 + }, 1325 + "node_modules/std-env": { 1326 + "version": "3.7.0", 1327 + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", 1328 + "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==", 1329 + "dev": true 1330 + }, 1331 + "node_modules/strip-final-newline": { 1332 + "version": "3.0.0", 1333 + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", 1334 + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", 1335 + "dev": true, 1336 + "engines": { 1337 + "node": ">=12" 1338 + }, 1339 + "funding": { 1340 + "url": "https://github.com/sponsors/sindresorhus" 1341 + } 1342 + }, 1343 + "node_modules/tinybench": { 1344 + "version": "2.8.0", 1345 + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.8.0.tgz", 1346 + "integrity": "sha512-1/eK7zUnIklz4JUUlL+658n58XO2hHLQfSk1Zf2LKieUjxidN16eKFEoDEfjHc3ohofSSqK3X5yO6VGb6iW8Lw==", 1347 + "dev": true 1348 + }, 1349 + "node_modules/tinypool": { 1350 + "version": "1.0.0", 1351 + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.0.tgz", 1352 + "integrity": "sha512-KIKExllK7jp3uvrNtvRBYBWBOAXSX8ZvoaD8T+7KB/QHIuoJW3Pmr60zucywjAlMb5TeXUkcs/MWeWLu0qvuAQ==", 1353 + "dev": true, 1354 + "engines": { 1355 + "node": "^18.0.0 || >=20.0.0" 1356 + } 1357 + }, 1358 + "node_modules/tinyrainbow": { 1359 + "version": "1.2.0", 1360 + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", 1361 + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", 1362 + "dev": true, 1363 + "engines": { 1364 + "node": ">=14.0.0" 1365 + } 1366 + }, 1367 + "node_modules/tinyspy": { 1368 + "version": "3.0.0", 1369 + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.0.tgz", 1370 + "integrity": "sha512-q5nmENpTHgiPVd1cJDDc9cVoYN5x4vCvwT3FMilvKPKneCBZAxn2YWQjDF0UMcE9k0Cay1gBiDfTMU0g+mPMQA==", 1371 + "dev": true, 1372 + "engines": { 1373 + "node": ">=14.0.0" 1374 + } 1375 + }, 1376 + "node_modules/tslib": { 1377 + "version": "2.6.3", 1378 + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", 1379 + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==" 1380 + }, 1381 + "node_modules/typescript": { 1382 + "version": "5.5.4", 1383 + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", 1384 + "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", 1385 + "dev": true, 1386 + "bin": { 1387 + "tsc": "bin/tsc", 1388 + "tsserver": "bin/tsserver" 1389 + }, 1390 + "engines": { 1391 + "node": ">=14.17" 1392 + } 1393 + }, 1394 + "node_modules/undici-types": { 1395 + "version": "6.11.1", 1396 + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.11.1.tgz", 1397 + "integrity": "sha512-mIDEX2ek50x0OlRgxryxsenE5XaQD4on5U2inY7RApK3SOJpofyw7uW2AyfMKkhAxXIceo2DeWGVGwyvng1GNQ==", 1398 + "dev": true 1399 + }, 1400 + "node_modules/vite": { 1401 + "version": "5.3.5", 1402 + "resolved": "https://registry.npmjs.org/vite/-/vite-5.3.5.tgz", 1403 + "integrity": "sha512-MdjglKR6AQXQb9JGiS7Rc2wC6uMjcm7Go/NHNO63EwiJXfuk9PgqiP/n5IDJCziMkfw9n4Ubp7lttNwz+8ZVKA==", 1404 + "dev": true, 1405 + "dependencies": { 1406 + "esbuild": "^0.21.3", 1407 + "postcss": "^8.4.39", 1408 + "rollup": "^4.13.0" 1409 + }, 1410 + "bin": { 1411 + "vite": "bin/vite.js" 1412 + }, 1413 + "engines": { 1414 + "node": "^18.0.0 || >=20.0.0" 1415 + }, 1416 + "funding": { 1417 + "url": "https://github.com/vitejs/vite?sponsor=1" 1418 + }, 1419 + "optionalDependencies": { 1420 + "fsevents": "~2.3.3" 1421 + }, 1422 + "peerDependencies": { 1423 + "@types/node": "^18.0.0 || >=20.0.0", 1424 + "less": "*", 1425 + "lightningcss": "^1.21.0", 1426 + "sass": "*", 1427 + "stylus": "*", 1428 + "sugarss": "*", 1429 + "terser": "^5.4.0" 1430 + }, 1431 + "peerDependenciesMeta": { 1432 + "@types/node": { 1433 + "optional": true 1434 + }, 1435 + "less": { 1436 + "optional": true 1437 + }, 1438 + "lightningcss": { 1439 + "optional": true 1440 + }, 1441 + "sass": { 1442 + "optional": true 1443 + }, 1444 + "stylus": { 1445 + "optional": true 1446 + }, 1447 + "sugarss": { 1448 + "optional": true 1449 + }, 1450 + "terser": { 1451 + "optional": true 1452 + } 1453 + } 1454 + }, 1455 + "node_modules/vite-node": { 1456 + "version": "2.0.4", 1457 + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.0.4.tgz", 1458 + "integrity": "sha512-ZpJVkxcakYtig5iakNeL7N3trufe3M6vGuzYAr4GsbCTwobDeyPJpE4cjDhhPluv8OvQCFzu2LWp6GkoKRITXA==", 1459 + "dev": true, 1460 + "dependencies": { 1461 + "cac": "^6.7.14", 1462 + "debug": "^4.3.5", 1463 + "pathe": "^1.1.2", 1464 + "tinyrainbow": "^1.2.0", 1465 + "vite": "^5.0.0" 1466 + }, 1467 + "bin": { 1468 + "vite-node": "vite-node.mjs" 1469 + }, 1470 + "engines": { 1471 + "node": "^18.0.0 || >=20.0.0" 1472 + }, 1473 + "funding": { 1474 + "url": "https://opencollective.com/vitest" 1475 + } 1476 + }, 1477 + "node_modules/vitest": { 1478 + "version": "2.0.4", 1479 + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.0.4.tgz", 1480 + "integrity": "sha512-luNLDpfsnxw5QSW4bISPe6tkxVvv5wn2BBs/PuDRkhXZ319doZyLOBr1sjfB5yCEpTiU7xCAdViM8TNVGPwoog==", 1481 + "dev": true, 1482 + "dependencies": { 1483 + "@ampproject/remapping": "^2.3.0", 1484 + "@vitest/expect": "2.0.4", 1485 + "@vitest/pretty-format": "^2.0.4", 1486 + "@vitest/runner": "2.0.4", 1487 + "@vitest/snapshot": "2.0.4", 1488 + "@vitest/spy": "2.0.4", 1489 + "@vitest/utils": "2.0.4", 1490 + "chai": "^5.1.1", 1491 + "debug": "^4.3.5", 1492 + "execa": "^8.0.1", 1493 + "magic-string": "^0.30.10", 1494 + "pathe": "^1.1.2", 1495 + "std-env": "^3.7.0", 1496 + "tinybench": "^2.8.0", 1497 + "tinypool": "^1.0.0", 1498 + "tinyrainbow": "^1.2.0", 1499 + "vite": "^5.0.0", 1500 + "vite-node": "2.0.4", 1501 + "why-is-node-running": "^2.3.0" 1502 + }, 1503 + "bin": { 1504 + "vitest": "vitest.mjs" 1505 + }, 1506 + "engines": { 1507 + "node": "^18.0.0 || >=20.0.0" 1508 + }, 1509 + "funding": { 1510 + "url": "https://opencollective.com/vitest" 1511 + }, 1512 + "peerDependencies": { 1513 + "@edge-runtime/vm": "*", 1514 + "@types/node": "^18.0.0 || >=20.0.0", 1515 + "@vitest/browser": "2.0.4", 1516 + "@vitest/ui": "2.0.4", 1517 + "happy-dom": "*", 1518 + "jsdom": "*" 1519 + }, 1520 + "peerDependenciesMeta": { 1521 + "@edge-runtime/vm": { 1522 + "optional": true 1523 + }, 1524 + "@types/node": { 1525 + "optional": true 1526 + }, 1527 + "@vitest/browser": { 1528 + "optional": true 1529 + }, 1530 + "@vitest/ui": { 1531 + "optional": true 1532 + }, 1533 + "happy-dom": { 1534 + "optional": true 1535 + }, 1536 + "jsdom": { 1537 + "optional": true 1538 + } 1539 + } 1540 + }, 1541 + "node_modules/webidl-conversions": { 1542 + "version": "7.0.0", 1543 + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", 1544 + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", 1545 + "dev": true, 1546 + "engines": { 1547 + "node": ">=12" 1548 + } 1549 + }, 1550 + "node_modules/whatwg-mimetype": { 1551 + "version": "3.0.0", 1552 + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", 1553 + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", 1554 + "dev": true, 1555 + "engines": { 1556 + "node": ">=12" 1557 + } 1558 + }, 1559 + "node_modules/which": { 1560 + "version": "2.0.2", 1561 + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 1562 + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 1563 + "dev": true, 1564 + "dependencies": { 1565 + "isexe": "^2.0.0" 1566 + }, 1567 + "bin": { 1568 + "node-which": "bin/node-which" 1569 + }, 1570 + "engines": { 1571 + "node": ">= 8" 1572 + } 1573 + }, 1574 + "node_modules/why-is-node-running": { 1575 + "version": "2.3.0", 1576 + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", 1577 + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", 1578 + "dev": true, 1579 + "dependencies": { 1580 + "siginfo": "^2.0.0", 1581 + "stackback": "0.0.2" 1582 + }, 1583 + "bin": { 1584 + "why-is-node-running": "cli.js" 1585 + }, 1586 + "engines": { 1587 + "node": ">=8" 1588 + } 1589 + }, 1590 + "node_modules/ws": { 1591 + "version": "8.18.0", 1592 + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", 1593 + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", 1594 + "engines": { 1595 + "node": ">=10.0.0" 1596 + }, 1597 + "peerDependencies": { 1598 + "bufferutil": "^4.0.1", 1599 + "utf-8-validate": ">=5.0.2" 1600 + }, 1601 + "peerDependenciesMeta": { 1602 + "bufferutil": { 1603 + "optional": true 1604 + }, 1605 + "utf-8-validate": { 1606 + "optional": true 1607 + } 1608 + } 1609 + } 1610 + } 1611 + }
+33
core/package.json
··· 1 + { 2 + "name": "tubes_core", 3 + "version": "0.0.9", 4 + "description": "extensible irc clienting", 5 + "main": "./index.ts", 6 + "scripts": { 7 + "test": "vitest", 8 + "build": "tsc", 9 + "prepare": "tsc" 10 + }, 11 + "type": "module", 12 + "author": "Leah Clark", 13 + "license": "ISC", 14 + "keywords": [ 15 + "eye", 16 + "are", 17 + "see" 18 + ], 19 + "devDependencies": { 20 + "@types/ws": "^8.5.12", 21 + "happy-dom": "14.12.3", 22 + "typescript": "^5.5.4", 23 + "vite": "^5.3.5", 24 + "vitest": "^2.0.4" 25 + }, 26 + "dependencies": { 27 + "@preact/signals": "^1.3.0", 28 + "async-mutex": "^0.5.0", 29 + "isomorphic-ws": "^5.0.0", 30 + "nanoid": "^5.0.7", 31 + "ws": "^8.18.0" 32 + } 33 + }
+163
core/parser.test.ts
··· 1 + import { expect, test } from 'vitest'; 2 + import { IrcMessage } from './parser'; 3 + 4 + test('parse boring irc message', () => { 5 + const input = "PRIVMSG #chan hiya!"; 6 + const expected = new IrcMessage({ 7 + command: "PRIVMSG", 8 + params: ["#chan", "hiya!"], 9 + }); 10 + const output = IrcMessage.parse(input); 11 + expected.timestamp = output.timestamp; 12 + expect(output).toStrictEqual(expected); 13 + }); 14 + 15 + test('message with source', () => { 16 + const input = ":localhost PRIVMSG #chan :hiya!"; 17 + const expected = new IrcMessage({ 18 + source: { 19 + nick: "localhost", 20 + host: undefined, 21 + user: undefined, 22 + }, 23 + command: "PRIVMSG", 24 + params: ["#chan", "hiya!"], 25 + }); 26 + const output = IrcMessage.parse(input); 27 + expected.timestamp = output.timestamp; 28 + expect(output).toStrictEqual(expected); 29 + }); 30 + 31 + test('trailing params', () => { 32 + const input = "PRIVMSG #chan :these: colons are here: to trip:: the parser: up"; 33 + const expected = new IrcMessage({ 34 + command: "PRIVMSG", 35 + params: ["#chan", "these: colons are here: to trip:: the parser: up"], 36 + }); 37 + const output = IrcMessage.parse(input); 38 + expected.timestamp = output.timestamp; 39 + expect(output).toStrictEqual(expected); 40 + }); 41 + 42 + test('tags', () => { 43 + const input = "@a=b;c :localhost FOO bar"; 44 + const expected = new IrcMessage({ 45 + tags: { 46 + "a": "b", 47 + "c": null, 48 + }, 49 + source: { 50 + nick: "localhost", 51 + host: undefined, 52 + user: undefined, 53 + }, 54 + command: "FOO", 55 + params: ["bar"], 56 + }); 57 + const output = IrcMessage.parse(input); 58 + expected.timestamp = output.timestamp; 59 + expect(output).toStrictEqual(expected); 60 + }); 61 + 62 + test('source parsing', () => { 63 + const input = ":leah!tubes@gaming.org JOIN #chan"; 64 + const expected = new IrcMessage({ 65 + command: "JOIN", 66 + params: ["#chan"], 67 + source: { 68 + nick: "leah", 69 + user: "tubes", 70 + host: "gaming.org", 71 + }, 72 + }); 73 + const output = IrcMessage.parse(input); 74 + expected.timestamp = output.timestamp; 75 + expect(output).toStrictEqual(expected); 76 + }); 77 + 78 + test('tags with escaped values', () => { 79 + const input = "@semicolon=\\:;space=\\s;cr=\\r;lf=\\n;backslash=\\\\ FOO bar"; 80 + const expected = new IrcMessage({ 81 + tags: { 82 + "semicolon": ";", 83 + "space": " ", 84 + "cr": "\r", 85 + "lf": "\n", 86 + "backslash": "\\", 87 + }, 88 + command: "FOO", 89 + params: ["bar"], 90 + }); 91 + const output = IrcMessage.parse(input); 92 + expected.timestamp = output.timestamp; 93 + expect(output).toStrictEqual(expected); 94 + }); 95 + 96 + test("to string", () => { 97 + const input = ":leah!gaming@gaming PRIVMSG #chan :wowee !!"; 98 + const msg = IrcMessage.parse(input); 99 + const output = msg.toString(); 100 + expect(output).toEqual(input); 101 + }); 102 + 103 + test("to string with tags", () => { 104 + const input = "@a=b;c :leah!gaming@gaming PRIVMSG #chan :wowee !!"; 105 + const msg = IrcMessage.parse(input); 106 + const output = msg.toString(); 107 + expect(output).toEqual(input); 108 + }); 109 + 110 + test("gnarly to string", () => { 111 + const input = "@a=b;c :leah!gaming@gaming PRIVMSG #chan ::3"; 112 + const msg = IrcMessage.parse(input); 113 + const output = msg.toString(); 114 + expect(output).toEqual(input); 115 + }) 116 + 117 + // todo: broken 118 + // test("to string with escaped values", () => { 119 + // const input = "@semicolon=\\:;space=\\s;cr=\\r;lf=\\n;backslash=\\\\ FOO bar"; 120 + // const msg = IrcMessage.parse(input); 121 + // const output = msg.toString(); 122 + // expect(output).toEqual(input); 123 + // }); 124 + 125 + test("to server string", () => { 126 + const input = ":leah!gaming@gaming PRIVMSG #chan :wowee !!"; 127 + const msg = IrcMessage.parse(input); 128 + const output = msg.toString("server"); 129 + expect(output).toEqual("PRIVMSG #chan :wowee !!"); 130 + }); 131 + 132 + // test('tags with escaped values', () => { 133 + // const input = "@semicolon=\\:;space=\\s;cr=\\r;lf=\\n;backslash=\\\\ FOO bar"; 134 + // const expected = input 135 + // const output = IrcMessage.parse(input).toString(); 136 + // expect(output).toEqual(expected); 137 + // }); 138 + 139 + test("one that broke it before", () => { 140 + const input = ":the_grungler!~roKfNS@bgwdwj24.vczlrfwq.lu7rqx5t.ip JOIN :#tubes" 141 + const output = IrcMessage.parse(input); 142 + const expected = new IrcMessage({ 143 + source: { 144 + nick: "the_grungler", 145 + user: "~roKfNS", 146 + host: "bgwdwj24.vczlrfwq.lu7rqx5t.ip", 147 + }, 148 + command: "JOIN", 149 + params: ["#tubes"], 150 + }); 151 + expected.timestamp = output.timestamp; 152 + expect(output).toStrictEqual(expected); 153 + }); 154 + 155 + test("one that broke it before 2", () => { 156 + const input = "QUIT"; 157 + const output = IrcMessage.parse(input); 158 + const expected = new IrcMessage({ 159 + command: "QUIT", 160 + }); 161 + expected.timestamp = output.timestamp; 162 + expect(output).toStrictEqual(expected); 163 + });
+211
core/parser.ts
··· 1 + import { Numeric } from "./support"; 2 + 3 + type MessageParameters = { 4 + tags?: Record<string, string | null>, 5 + source?: MessageSource, 6 + command: Numeric | string, 7 + params?: string[]; 8 + timestamp?: Date; 9 + }; 10 + 11 + export type MessageSource = { 12 + nick: string, 13 + user?: string, 14 + host?: string, 15 + }; 16 + 17 + export class IrcMessage { 18 + constructor(opt: MessageParameters) { 19 + ({ 20 + tags: this.tags, 21 + source: this.source, 22 + command: this.command, 23 + params: this.params, 24 + timestamp: this.timestamp, 25 + } = opt); 26 + } 27 + 28 + tags?: Record<string, string | null>; 29 + source?: MessageSource; 30 + command: Numeric | string; 31 + params?: string[]; 32 + timestamp?; 33 + 34 + static parse(message: string) { 35 + let state = message; 36 + 37 + let tags, source, command, params; 38 + 39 + if (state.charAt(0) == "@") { 40 + ({ tags, state } = IrcMessage.parse_tags(tags, state)); 41 + } 42 + 43 + if (state.charAt(0) == ":") { 44 + const cutoff = state.search(" "); 45 + source = IrcMessage.parse_source(state.substring(1, cutoff)); 46 + state = state.substring(cutoff).trimStart(); 47 + } 48 + 49 + let cutoff = state.search(" "); 50 + if (cutoff == -1) cutoff = state.length; 51 + command = state.substring(0, cutoff); 52 + state = state.substring(cutoff).trimStart(); 53 + 54 + if (state) { 55 + ({ state, params } = IrcMessage.parse_params(state, params)); 56 + } 57 + 58 + return new IrcMessage({ tags, source, command, params }); 59 + } 60 + 61 + static hydrate(unhydrated: any): IrcMessage { 62 + if (unhydrated instanceof IrcMessage) return unhydrated; 63 + return new IrcMessage(unhydrated); 64 + } 65 + 66 + private static parse_params(state: string, params: any) { 67 + let trailing; 68 + 69 + if (state.startsWith(":")) { 70 + trailing = state.substring(1); 71 + state = ""; 72 + } 73 + 74 + if (state.includes(" :")) { 75 + let cutoff = state.search(" :"); 76 + trailing = state.substring(cutoff + 2); 77 + state = state.substring(0, cutoff); 78 + } 79 + 80 + params = state 81 + ? state.split(" ") 82 + : []; 83 + 84 + if (trailing) params.push(trailing); 85 + return { state, params }; 86 + } 87 + 88 + private static parse_tags(tags: any, state: string) { 89 + const cutoff = state.search(" "); 90 + const tag_string = state.substring(1, cutoff); 91 + const splitted_tags = tag_string.split(";"); 92 + 93 + tags = splitted_tags.reduce((prev, tag) => { 94 + if (tag.includes("=")) { 95 + let [key, value] = tag.split("="); 96 + // escape chars 97 + value = IrcMessage.unescape_tag_value(value); 98 + prev[key] = value; 99 + } else { 100 + prev[tag] = null; 101 + } 102 + 103 + return prev; 104 + }, <Record<string, string | null>>{}); 105 + 106 + state = state.substring(cutoff).trimStart(); 107 + return { tags, state }; 108 + } 109 + 110 + private static parse_source(source: string): MessageSource { 111 + let nick, user, host; 112 + 113 + if (source.includes("!")) { 114 + let rest; 115 + [nick, rest] = source.split("!"); 116 + [user, host] = rest.split("@"); 117 + } else { 118 + nick = source; 119 + } 120 + 121 + return { nick, user, host }; 122 + } 123 + 124 + static escape_chars = [ 125 + ["\\:", ";"], 126 + ["\\s", " "], 127 + ["\\\\", "\\"], 128 + ["\\r", "\r"], 129 + ["\\n", "\n"] 130 + ]; 131 + 132 + private static unescape_tag_value(value: string) { 133 + for (const r of this.escape_chars) { 134 + value = value.replaceAll(r[0], r[1]); 135 + }; 136 + return value; 137 + } 138 + 139 + // private static escape_tag_value(value: string) { 140 + // for (const r of this.escapable) { 141 + // value = value.replaceAll(r[1], r[0]); 142 + // console.log(value); 143 + // }; 144 + // return value; 145 + // } 146 + 147 + toString(recipient: "client" | "server" = "client"): string { 148 + let result = []; 149 + 150 + if (this.tags) { 151 + let tags = Object.keys(this.tags).reduce( 152 + (prev, key) => { 153 + const value = this.tags![key]; 154 + const tag_str = value ? `${key}=${value}` : key; 155 + return prev 156 + ? `${prev};${tag_str}` 157 + : `@${tag_str}`; 158 + }, 159 + ""); 160 + result.push(tags); 161 + } 162 + 163 + if (recipient == "client" && this.source) { 164 + const source_str = this.source.host 165 + ? `${this.source.nick}!${this.source.user}@${this.source.host}` 166 + : this.source.nick; 167 + result.push(`:${source_str}`); 168 + } 169 + 170 + result.push(this.command); 171 + 172 + if (this.params) { 173 + const params = this.params.reduce( 174 + (prev, param) => param.includes(" ") || param.includes(":") 175 + ? `${prev} :${param}` 176 + : `${prev ? `${prev} ` : ""}${param}`, 177 + "" 178 + ); 179 + result.push(params); 180 + } 181 + 182 + return result.join(" "); 183 + } 184 + 185 + get is_action() { 186 + let content = this.params?.at(-1); 187 + 188 + return content?.startsWith(ctcp_search) && content.endsWith("\x01"); 189 + } 190 + 191 + get content() { 192 + return extract_content(this); 193 + } 194 + } 195 + 196 + const ctcp_search = "\x01ACTION"; 197 + 198 + export function extract_content(msg: IrcMessage) { 199 + if (msg.command != ("PRIVMSG" || "NOTICE")) { 200 + return null; 201 + } 202 + 203 + let content = msg.params?.at(-1); 204 + 205 + // check for ctcp action magic 206 + if (content?.startsWith(ctcp_search) && content.endsWith("\x01")) { 207 + content = content.slice(ctcp_search.length, -1); 208 + } 209 + 210 + return content; 211 + }
+88
core/queue.test.ts
··· 1 + import { assert, expect, test } from "vitest"; 2 + import { Matcher, TaskQueue } from "./queue"; 3 + import { IrcMessage } from "./parser"; 4 + import { Numeric } from "./support"; 5 + 6 + test("matcher does the thing", () => { 7 + let matcher = new Matcher("PRIVMSG", "#test", "hi there!"); 8 + let message = IrcMessage.parse(":test!test@test PRIVMSG #test :hi there!"); 9 + 10 + assert(matcher.matches(message) === true); 11 + 12 + message = IrcMessage.parse(":test!test@test NOTPRIVMSG #test :hi there"); 13 + assert(matcher.matches(message) === false); 14 + 15 + message = IrcMessage.parse(":test!test@test PRIVMSG #nottest :hi there"); 16 + assert(matcher.matches(message) === false); 17 + }); 18 + 19 + // https://modern.ircdocs.horse/#numerics 20 + test("matcher doesn't fail on overflowing params", () => { 21 + const matcher = new Matcher("PRIVMSG", "#test"); 22 + const message = IrcMessage.parse(":test!test@test PRIVMSG #test :hello there!"); 23 + 24 + assert(matcher.matches(message) === true); 25 + }); 26 + 27 + test("expect does the thing", async () => { 28 + const m = new TaskQueue(); 29 + const matcher = new Matcher("PRIVMSG", "#test", "hi there!"); 30 + const promise = m.expect("match test", matcher); 31 + 32 + const message = IrcMessage.parse(":test!test@test PRIVMSG #test :hi there!"); 33 + m.resolve_tasks(message); 34 + 35 + assert((await promise) === message); 36 + }); 37 + 38 + test("expect rejects on reject_on", async () => { 39 + const m = new TaskQueue(); 40 + const matcher = new Matcher("PRIVMSG", "#test", "hi there"); 41 + const reject_on = new Matcher(Numeric.ERR_NOSUCHNICK); 42 + 43 + const promise = m.expect("reject test", matcher, reject_on); 44 + const message = IrcMessage.parse( 45 + `:test!test@test ${Numeric.ERR_NOSUCHNICK} #test` 46 + ); 47 + m.resolve_tasks(message); 48 + 49 + await expect(promise).rejects.toThrow(); 50 + }); 51 + 52 + test("collect collects", async () => { 53 + const m = new TaskQueue(); 54 + const messages = [ 55 + ":test!test@test 375 test :- test Message of the Day -", 56 + ":test!test@test 372 test : hi", 57 + ":test!test@test 372 test : hi", 58 + ":test!test@test 376 test :End of /MOTD command.", 59 + ].map(IrcMessage.parse); 60 + 61 + const promise = m.collect("collect test", { 62 + start: new Matcher(Numeric.RPL_MOTDSTART), 63 + include: new Matcher(Numeric.RPL_MOTD), 64 + finish: new Matcher(Numeric.RPL_ENDOFMOTD), 65 + }); 66 + 67 + messages.forEach((message) => m.resolve_tasks(message)); 68 + 69 + expect((await promise)).toStrictEqual(messages.slice(1, 3)); 70 + }); 71 + 72 + test("collect rejects", async () => { 73 + const m = new TaskQueue(); 74 + const messages = [ 75 + ":test!test@test 422 test : no motd lol", 76 + ].map(IrcMessage.parse); 77 + 78 + const promise = m.collect("collect test", { 79 + start: new Matcher(Numeric.RPL_MOTDSTART), 80 + include: new Matcher(Numeric.RPL_MOTD), 81 + finish: new Matcher(Numeric.RPL_ENDOFMOTD), 82 + reject_on: new Matcher(Numeric.ERR_NOMOTD), 83 + }); 84 + 85 + messages.forEach((message) => m.resolve_tasks(message)); 86 + 87 + await expect(promise).rejects.toThrow(); 88 + });
+281
core/queue.ts
··· 1 + import { Connection } from "."; 2 + import { IrcMessage, MessageSource } from "./parser"; 3 + import { Numeric } from "./support"; 4 + 5 + export interface Matchable { 6 + matches(message: IrcMessage): boolean; 7 + } 8 + 9 + export enum Wildcard { 10 + Any 11 + } 12 + 13 + export class Matcher implements Matchable { 14 + constructor(public command: string | Numeric, ...params: (string | Wildcard)[]) { 15 + this.params = params; 16 + } 17 + 18 + params: (string | Wildcard)[]; 19 + source?: MessageSource; 20 + 21 + require_source(source: MessageSource) { 22 + this.source = source; 23 + return this; 24 + } 25 + 26 + matches(message: IrcMessage) { 27 + if (message.command != this.command) return false; 28 + 29 + if (!this.match_source(message.source)) return false; 30 + 31 + for (let i = 0; i < this.params.length; i++) { 32 + if (this.params[i] == Wildcard.Any) return true; 33 + if (message.params?.[i] != this.params[i]) return false; 34 + } 35 + 36 + return true; 37 + } 38 + 39 + private match_source(source: MessageSource | undefined) { 40 + if (!this.source) return true; 41 + 42 + if (this.source.nick != source?.nick) return false; 43 + if (this.source.user && this.source.user != source?.user) return false; 44 + if (this.source.host && this.source.host != source?.host) return false; 45 + 46 + return true; 47 + } 48 + } 49 + 50 + export interface Resolvable { 51 + description: string; 52 + 53 + /** 54 + * @returns true if the task is resolved and should be removed from the queue. 55 + */ 56 + resolved(message: IrcMessage): boolean; 57 + } 58 + 59 + export class Deferred<T = void, E = void> { 60 + promise: Promise<T>; 61 + resolve!: (message: T) => void; 62 + reject!: (message: E) => void; 63 + 64 + constructor() { 65 + this.promise = new Promise((resolve, reject) => { 66 + this.resolve = resolve; 67 + this.reject = reject; 68 + }) 69 + } 70 + } 71 + 72 + export class TaskQueue { 73 + constructor(public conn: Connection) { } 74 + 75 + tasks: Resolvable[] = []; 76 + 77 + resolve_tasks(message: IrcMessage, opt?: { batch: string | null | undefined }) { 78 + const batch = opt?.batch; 79 + 80 + const before = this.tasks.length; 81 + 82 + if (batch && this.conn.masked_batches.includes(batch)) { 83 + this.tasks = this.tasks.filter( 84 + (task) => task instanceof BatchCollector && task.ref == batch 85 + ? !task.resolved(message) 86 + : true 87 + ) 88 + } else { 89 + this.tasks = this.tasks.filter((task) => !task.resolved(message)); 90 + } 91 + 92 + const after = this.tasks.length; 93 + 94 + if (before != after) { 95 + const resolved = before - after; 96 + console.debug(`${resolved} ${resolved == 1 ? "task" : "tasks"} resolved`); 97 + } 98 + } 99 + 100 + add_task(task: Resolvable) { 101 + this.tasks.push(task); 102 + } 103 + 104 + /** 105 + * wait for a message to be received. 106 + * @param description a human-readable description of the message 107 + * @param matcher a {@link Matchable} that will compared against incoming messages 108 + * @param reject_on an optional {@link Matchable} that will cause the promise to reject if matched 109 + * @returns a promise that resolves to the matched message when it is received 110 + */ 111 + expect(description: string, matcher: Matchable, reject_on?: Matchable): Promise<IrcMessage> { 112 + const expected = new ExpectedMessage(description, matcher, reject_on); 113 + this.add_task(expected); 114 + return expected.promise; 115 + } 116 + 117 + collect(description: string, { start, include, finish, reject_on, include_start_finish }: { 118 + start: Matchable, 119 + include: Matchable, 120 + finish: Matchable, 121 + reject_on?: Matchable, 122 + include_start_finish?: boolean, 123 + }): Promise<IrcMessage[]> { 124 + const collector = new Collector( 125 + description, 126 + start, 127 + include, 128 + finish, 129 + include_start_finish ?? false, 130 + reject_on 131 + ); 132 + this.add_task(collector); 133 + return collector.promise; 134 + } 135 + 136 + collect_batch(kind: string, opt?: { mask?: boolean }) { 137 + if (!this.conn.supports.batches) { 138 + throw new Error("Connection does not support batches."); 139 + } 140 + 141 + const c = opt?.mask 142 + ? new BatchCollector( 143 + kind, 144 + (ref) => this.conn.masked_batches.push(ref), 145 + (ref) => this.conn.masked_batches = this.conn.masked_batches.filter((x) => x != ref), 146 + ) 147 + : new BatchCollector(kind); 148 + 149 + this.add_task(c); 150 + return c.promise; 151 + } 152 + 153 + subscribe(description: string, callback: (msg: IrcMessage, unsubscribe: () => void) => void): Subscription { 154 + const sub = new Subscription(description, callback); 155 + this.add_task(sub); 156 + 157 + return sub 158 + } 159 + } 160 + 161 + class ExpectedMessage extends Deferred<IrcMessage, IrcMessage> implements Resolvable { 162 + constructor(public description: string, public matcher: Matchable, public reject_on?: Matchable) { 163 + super(); 164 + } 165 + 166 + resolved(message: IrcMessage) { 167 + if (this.reject_on?.matches(message)) { 168 + this.reject(message); 169 + return true; 170 + } 171 + 172 + if (this.matcher.matches(message)) { 173 + this.resolve(message); 174 + return true; 175 + } 176 + 177 + return false; 178 + } 179 + } 180 + 181 + class Collector extends Deferred<IrcMessage[], IrcMessage> implements Resolvable { 182 + constructor( 183 + public description: string, 184 + public start: Matchable, 185 + public include: Matchable, 186 + public finish: Matchable, 187 + public include_start_finish: boolean, 188 + public reject_on?: Matchable, 189 + ) { 190 + super(); 191 + } 192 + 193 + collected: IrcMessage[] = []; 194 + is_collecting = false; 195 + 196 + resolved(message: IrcMessage): boolean { 197 + if (this.reject_on?.matches(message)) { 198 + this.reject(message); 199 + return true; 200 + } 201 + 202 + if (this.start.matches(message)) { 203 + this.is_collecting = true; 204 + return false; 205 + } 206 + 207 + if (!this.is_collecting) return false; 208 + 209 + 210 + if (this.finish.matches(message)) { 211 + this.resolve(this.collected); 212 + return true; 213 + } 214 + 215 + if (this.include.matches(message)) { 216 + this.collected.push(message); 217 + } 218 + 219 + return false; 220 + } 221 + } 222 + 223 + class Subscription implements Resolvable { 224 + constructor( 225 + public description: string, 226 + public callback: (message: IrcMessage, unsubscribe: () => void) => void 227 + ) { } 228 + 229 + unsubscribe() { 230 + this.subscribed = false; 231 + } 232 + 233 + subscribed = true; 234 + 235 + resolved(message: IrcMessage): boolean { 236 + if (!this.subscribed) return true; 237 + this.callback(message, () => this.unsubscribe()); 238 + 239 + return false; 240 + } 241 + } 242 + 243 + class BatchCollector extends Deferred<IrcMessage[], void> implements Resolvable { 244 + constructor( 245 + public kind: string, 246 + public start_callback?: (ref: string) => void, 247 + public end_callback?: (ref: string) => void 248 + ) { 249 + super(); 250 + this.description = kind; 251 + } 252 + 253 + description; 254 + ref?: string; 255 + msgs: IrcMessage[] = []; 256 + 257 + resolved(message: IrcMessage): boolean { 258 + if (this.ref && message.tags?.["batch"] == this.ref) { 259 + this.msgs.push(message); 260 + return false; 261 + } 262 + 263 + if (message.command != "BATCH") return false; 264 + 265 + const first = message.params?.[0] ?? ""; 266 + 267 + if (first.startsWith("+")) { 268 + this.ref = first.slice(1); 269 + this.start_callback?.(this.ref); 270 + return false; 271 + } 272 + 273 + if (this.ref && first.startsWith("-" + this.ref)) { 274 + this.resolve(this.msgs); 275 + this.end_callback?.(this.ref); 276 + return true; 277 + } 278 + 279 + return false; 280 + } 281 + }
+96
core/sasl.ts
··· 1 + import { Connection } from "."; 2 + import { IrcMessage } from "./parser"; 3 + import { Matchable, Matcher } from "./queue"; 4 + import { Numeric } from "./support"; 5 + 6 + const secure_protocols = [ 7 + "wss:", 8 + "ircs:", 9 + ]; 10 + 11 + export interface SaslConfig { 12 + username: string; 13 + password?: string; 14 + cert?: string; 15 + } 16 + 17 + // we get a little object oriented with it 18 + class SaslSuccess implements Matchable { 19 + matches(message: IrcMessage): boolean { 20 + return message.command == Numeric.RPL_SASLSUCCESS; 21 + } 22 + } 23 + 24 + class SaslFailiure implements Matchable { 25 + static codes: string[] = [ 26 + Numeric.ERR_SASLFAIL, 27 + Numeric.ERR_SASLTOOLONG, 28 + Numeric.ERR_SASLABORTED, 29 + Numeric.ERR_SASLALREADY, 30 + ]; 31 + 32 + matches(message: IrcMessage): boolean { 33 + return SaslFailiure.codes.includes(message.command); 34 + } 35 + } 36 + 37 + export class Sasl { 38 + constructor(public conn: Connection, public stuff: SaslConfig) { 39 + } 40 + 41 + async authenticate() { 42 + this.check_security(); 43 + const mechs = this.get_mechs() 44 + 45 + let authed = false; 46 + 47 + if (this.stuff.password) { 48 + // password-based auth 49 + // if (!authed && mechs.includes("SCRAM-SHA-256")) throw "todo" 50 + if (!authed && mechs.includes("PLAIN")) { 51 + authed = await this.plain(this.stuff.username, this.stuff.password) 52 + } 53 + } else if (this.stuff.cert) { 54 + // password-cringe auth 55 + // if (!authed && mechs.includes("EXTERNAL")) throw "todo" 56 + } 57 + 58 + if (!authed) { 59 + // abort auth 60 + this.conn.send("AUTHENTICATE *"); 61 + } 62 + 63 + return authed; 64 + } 65 + 66 + private check_security() { 67 + const url = this.conn.url; 68 + if (!secure_protocols.includes(url.protocol)) { 69 + throw Error( 70 + "You cannot use SASL over an insecure connection\n" + 71 + `(protocol is ${url.protocol})` 72 + ); 73 + } 74 + } 75 + 76 + private get_mechs(): string[] { 77 + const mechs = this.conn.capabilities.get('sasl')?.split(","); 78 + 79 + return mechs ?? ["PLAIN", "EXTERNAL"] // seems like a reasonable guess 80 + } 81 + 82 + private async plain(nick: string, password: string): Promise<boolean> { 83 + const auth = `${nick}\u0000${nick}\u0000${password}`; 84 + 85 + this.conn.send("AUTHENTICATE PLAIN"); 86 + await this.conn.expect("expect ack", new Matcher("AUTHENTICATE", "+")); 87 + this.conn.send(`AUTHENTICATE ${btoa(auth)}`); 88 + await this.conn.expect( 89 + "expect success", 90 + new SaslSuccess, 91 + new SaslFailiure, 92 + ); 93 + 94 + return true 95 + } 96 + }
+80
core/soju/adapter.ts
··· 1 + import { Signal, signal } from "@preact/signals"; 2 + import { Adapter } from "../adapter"; 3 + import { ConnectionConfig, ConnectionParameters } from "../connection"; 4 + import { IrcProtocol } from "../support"; 5 + import { WsConnection } from "../ws/connection"; 6 + import { SojuConnection } from "./connection"; 7 + 8 + interface SojuAdapterConfig { 9 + id: string, 10 + url: URL | string; 11 + username: string; 12 + password: string; 13 + } 14 + 15 + export interface NetworkAttributes { 16 + name?: string, 17 + state?: "connected" | "connecting" | "disconnected", 18 + host: string, 19 + port?: string, 20 + tls?: "1" | "0", 21 + nickname?: string, 22 + username?: string, 23 + realname?: string, 24 + pass?: string, 25 + } 26 + 27 + export class SojuAdapter extends Adapter<SojuConnection> { 28 + protocols: IrcProtocol[] = [ 29 + IrcProtocol.Irc, 30 + IrcProtocol.SecureIrc, 31 + IrcProtocol.WebSocket, 32 + IrcProtocol.SecureWebSocket 33 + ]; 34 + label: string = "Soju"; 35 + $connections: Signal<SojuConnection[]> = signal([]); 36 + 37 + root_conn?: WsConnection; 38 + 39 + constructor(public config: SojuAdapterConfig, private opt?: ConnectionParameters) { 40 + super(config.id); 41 + } 42 + 43 + async activate(): Promise<void> { 44 + const { url, username, password } = this.config; 45 + this.root_conn = new WsConnection({ 46 + url, 47 + nickname: username, 48 + realname: username, 49 + sasl: { username, password }, 50 + }); 51 + 52 + await this.root_conn.connect(); 53 + 54 + this.root_conn.send("BOUNCER LISTNETWORKS"); 55 + let networks = await this.root_conn.collect_batch("soju.im/bouncer-networks"); 56 + 57 + for (const o of networks) { 58 + if (!o.params) throw new Error("invalid network list oughghhhgh"); 59 + let [, netid, raw_attrs] = o.params; 60 + const attrs = raw_attrs.split(";").reduce((acc, o) => { 61 + const [k, v] = o.split("="); 62 + acc[k] = v; 63 + return acc; 64 + }, <Record<string, string>>{}) as unknown as NetworkAttributes; 65 + 66 + const conn = new SojuConnection(netid, attrs.name!, this, this.opt); 67 + this.$connections.value = [...this.$connections.value, conn]; 68 + } 69 + 70 + await Promise.allSettled(this.$connections.value.map(x => x.connect())); 71 + } 72 + 73 + deactivate(): Promise<void> { 74 + throw new Error("Method not implemented."); 75 + } 76 + 77 + add_connection(_config: ConnectionConfig): Promise<SojuConnection> { 78 + throw new Error("Method not implemented."); 79 + } 80 + }
+95
core/soju/connection.ts
··· 1 + import WebSocket from "isomorphic-ws"; 2 + import { Connection } from ".."; 3 + import { ConnectionErrorCode, ConnectionParameters, ConnectionState } from "../connection"; 4 + import { IrcMessage } from "../parser"; 5 + import { SojuAdapter } from "./adapter"; 6 + import { Deferred } from "../queue"; 7 + import { collect_caps, negotiate_caps } from "../capabilities"; 8 + import { Sasl } from "../sasl"; 9 + import { Numeric } from "../support"; 10 + import { collect_motd } from "../motd"; 11 + 12 + export class SojuConnection extends Connection { 13 + constructor( 14 + bind_to: string, 15 + label: string, 16 + public adapter: SojuAdapter, 17 + opt?: ConnectionParameters 18 + ) { 19 + super({ 20 + id: bind_to, 21 + label, 22 + url: adapter.root_conn!.url, 23 + nickname: adapter.root_conn!.username, 24 + realname: "", 25 + adapter_id: adapter.id, 26 + }, opt); 27 + } 28 + 29 + ws?: WebSocket; 30 + 31 + connect(): Promise<void> { 32 + this.ws = new WebSocket(this.url); 33 + 34 + this.$state.value = ConnectionState.Connecting; 35 + const resolver_1966 = new Deferred(); 36 + 37 + this.ws.onopen = () => { this.#bind(resolver_1966) }; 38 + this.ws.onmessage = (ev) => this.handle_incoming(ev.data as string); 39 + // this.ws!.onclose = () => this.handle_disconnect(); 40 + // this.ws!.onerror = () => this.socket_error(); 41 + 42 + 43 + return resolver_1966.promise; 44 + } 45 + 46 + async #bind(resolver: Deferred) { 47 + this.send("CAP LS 302"); 48 + const caps_recvd = collect_caps(this); 49 + 50 + const { username, password } = this.adapter.config; 51 + 52 + this.send(`NICK ${username}`); 53 + this.send(`USER ${username} 0 0 :${username}`); 54 + 55 + const cap_msgs = await caps_recvd; 56 + const { available, negotiated } = await negotiate_caps(this, cap_msgs); 57 + this.capabilities = negotiated; 58 + this.available_capabilities = available; 59 + 60 + try { 61 + await new Sasl(this, { username, password }).authenticate(); 62 + } catch (e) { 63 + if (!(e instanceof IrcMessage)) throw e; 64 + 65 + this.$error.value = (() => { 66 + switch (e.command) { 67 + case Numeric.ERR_SASLFAIL: return [ConnectionErrorCode.SaslFailed, e]; 68 + case Numeric.ERR_SASLTOOLONG: return [ConnectionErrorCode.SaslTooLong, e]; 69 + case Numeric.ERR_SASLABORTED: return [ConnectionErrorCode.SaslAborted, e]; 70 + case Numeric.ERR_SASLALREADY: return null; 71 + default: throw e; 72 + } 73 + })(); 74 + } 75 + 76 + // todo: error handling :3 77 + this.send(`BOUNCER BIND ${this.id}`); 78 + this.send(`CAP END`); 79 + 80 + this.motd = await collect_motd(this); 81 + 82 + this.$state.value = ConnectionState.Connected; 83 + resolver.resolve(); 84 + this.on_connect?.(this); 85 + this.start_pinging(); 86 + } 87 + 88 + disconnect(_state?: ConnectionState | undefined): Promise<void> { 89 + throw new Error("Method not implemented."); 90 + } 91 + 92 + send_raw(message: string): void { 93 + this.ws?.send(message); 94 + } 95 + }
+28
core/support.test.ts
··· 1 + import { expect, test } from "vitest"; 2 + import { compare_names } from "./support"; 3 + 4 + test("ascii casemapping", () => { 5 + const input: [string, string] = ["Hello!", "hello!"]; 6 + const output = compare_names("ascii", ...input); 7 + 8 + expect(output).toEqual(true); 9 + }); 10 + 11 + test("rfc1459 casemapping", () => { 12 + let input: [string, string] = ["[Hello]\\~", "{hello}|^"]; 13 + let output = compare_names("rfc1459", ...input); 14 + 15 + expect(output).toEqual(true); 16 + }); 17 + 18 + test("strict-rfc1459 casemapping", () => { 19 + let input: [string, string] = ["[Hello]\\", "{hello}|"]; 20 + let output = compare_names("strict-rfc1459", ...input); 21 + 22 + expect(output).toEqual(true); 23 + 24 + input = ["[Hello]\\~", "{hello}|^"]; 25 + output = compare_names("strict-rfc1459", ...input); 26 + 27 + expect(output).toEqual(false); 28 + });
+234
core/support.ts
··· 1 + // file of various helpful functions and types 2 + 3 + import type { ISupport } from "./isupport"; 4 + 5 + /** 6 + * buncha numerics from the spec 7 + * https://modern.ircdocs.horse/#numerics 8 + */ 9 + export enum Numeric { 10 + RPL_WELCOME = "001", 11 + RPL_ISUPPORT = "005", 12 + 13 + ERR_NOSUCHNICK = "401", 14 + ERR_NONICKNAMEGIVEN = "431", 15 + ERR_ERRONEUSNICKNAME = "432", 16 + ERR_NICKNAMEINUSE = "433", 17 + ERR_NICKCOLLISION = "436", 18 + 19 + ERR_NOMOTD = "422", 20 + RPL_MOTDSTART = "375", 21 + RPL_MOTD = "372", 22 + RPL_ENDOFMOTD = "376", 23 + 24 + RPL_TOPIC = "332", 25 + 26 + RPL_NAMREPLY = "353", 27 + RPL_ENDOFNAMES = "366", 28 + 29 + ERR_NEEDMOREPARAMS = "461", 30 + ERR_NOSUCHCHANNEL = "403", 31 + ERR_TOOMANYCHANNELS = "405", 32 + ERR_BADCHANNELKEY = "475", 33 + ERR_BANNEDFROMCHAN = "474", 34 + ERR_CHANNELISFULL = "471", 35 + ERR_INVITEONLYCHAN = "473", 36 + ERR_BADCHANMASK = "476", 37 + RPL_TOPICWHOTIME = "333", 38 + 39 + // these seem to be in use on libera? 40 + ERR_NONSTD_ILLEGALNAME = "479", 41 + ERR_NONSTD_NO_REG = "477", 42 + 43 + RPL_YOURHOST = "002", 44 + RPL_CREATED = "003", 45 + RPL_MYINFO = "004", 46 + RPL_BOUNCE = "010", 47 + RPL_STATSCOMMANDS = "212", 48 + RPL_ENDOFSTATS = "219", 49 + RPL_UMODEIS = "221", 50 + RPL_STATSUPTIME = "242", 51 + RPL_LUSERCLIENT = "251", 52 + RPL_LUSEROP = "252", 53 + RPL_LUSERUNKNOWN = "253", 54 + RPL_LUSERCHANNELS = "254", 55 + RPL_LUSERME = "255", 56 + RPL_ADMINME = "256", 57 + RPL_ADMINLOC1 = "257", 58 + RPL_ADMINLOC2 = "258", 59 + RPL_ADMINEMAIL = "259", 60 + RPL_TRYAGAIN = "263", 61 + RPL_LOCALUSERS = "265", 62 + RPL_GLOBALUSERS = "266", 63 + RPL_WHOISCERTFP = "276", 64 + RPL_NONE = "300", 65 + RPL_AWAY = "301", 66 + RPL_USERHOST = "302", 67 + RPL_UNAWAY = "305", 68 + RPL_NOWAWAY = "306", 69 + RPL_WHOISREGNICK = "307", 70 + RPL_WHOISUSER = "311", 71 + RPL_WHOISSERVER = "312", 72 + RPL_WHOISOPERATOR = "313", 73 + RPL_WHOWASUSER = "314", 74 + RPL_ENDOFWHO = "315", 75 + RPL_WHOISIDLE = "317", 76 + RPL_ENDOFWHOIS = "318", 77 + RPL_WHOISCHANNELS = "319", 78 + RPL_WHOISSPECIAL = "320", 79 + RPL_LISTSTART = "321", 80 + RPL_LIST = "322", 81 + RPL_LISTEND = "323", 82 + RPL_CHANNELMODEIS = "324", 83 + RPL_CREATIONTIME = "329", 84 + RPL_WHOISACCOUNT = "330", 85 + RPL_NOTOPIC = "331", 86 + RPL_INVITELIST = "336", 87 + RPL_ENDOFINVITELIST = "337", 88 + RPL_WHOISACTUALLY = "338", 89 + RPL_INVITING = "341", 90 + RPL_INVEXLIST = "346", 91 + RPL_ENDOFINVEXLIST = "347", 92 + RPL_EXCEPTLIST = "348", 93 + RPL_ENDOFEXCEPTLIST = "349", 94 + RPL_VERSION = "351", 95 + RPL_WHOREPLY = "352", 96 + RPL_LINKS = "364", 97 + RPL_ENDOFLINKS = "365", 98 + RPL_BANLIST = "367", 99 + RPL_ENDOFBANLIST = "368", 100 + RPL_ENDOFWHOWAS = "369", 101 + RPL_INFO = "371", 102 + RPL_ENDOFINFO = "374", 103 + RPL_WHOISHOST = "378", 104 + RPL_WHOISMODES = "379", 105 + RPL_YOUREOPER = "381", 106 + RPL_REHASHING = "382", 107 + RPL_TIME = "391", 108 + ERR_UNKNOWNERROR = "400", 109 + ERR_NOSUCHSERVER = "402", 110 + ERR_CANNOTSENDTOCHAN = "404", 111 + ERR_WASNOSUCHNICK = "406", 112 + ERR_NOORIGIN = "409", 113 + ERR_NORECIPIENT = "411", 114 + ERR_NOTEXTTOSEND = "412", 115 + ERR_INPUTTOOLONG = "417", 116 + ERR_UNKNOWNCOMMAND = "421", 117 + ERR_USERNOTINCHANNEL = "441", 118 + ERR_NOTONCHANNEL = "442", 119 + ERR_USERONCHANNEL = "443", 120 + ERR_NOTREGISTERED = "451", 121 + ERR_ALREADYREGISTERED = "462", 122 + ERR_PASSWDMISMATCH = "464", 123 + ERR_YOUREBANNEDCREEP = "465", 124 + ERR_UNKNOWNMODE = "472", 125 + ERR_NOPRIVILEGES = "481", 126 + ERR_CHANOPRIVSNEEDED = "482", 127 + ERR_CANTKILLSERVER = "483", 128 + ERR_NOOPERHOST = "491", 129 + ERR_UMODEUNKNOWNFLAG = "501", 130 + ERR_USERSDONTMATCH = "502", 131 + ERR_HELPNOTFOUND = "524", 132 + ERR_INVALIDKEY = "525", 133 + RPL_STARTTLS = "670", 134 + RPL_WHOISSECURE = "671", 135 + ERR_STARTTLS = "691", 136 + ERR_INVALIDMODEPARAM = "696", 137 + RPL_HELPSTART = "704", 138 + RPL_HELPTXT = "705", 139 + RPL_ENDOFHELP = "706", 140 + ERR_NOPRIVS = "723", 141 + RPL_LOGGEDIN = "900", 142 + RPL_LOGGEDOUT = "901", 143 + ERR_NICKLOCKED = "902", 144 + RPL_SASLSUCCESS = "903", 145 + ERR_SASLFAIL = "904", 146 + ERR_SASLTOOLONG = "905", 147 + ERR_SASLABORTED = "906", 148 + ERR_SASLALREADY = "907", 149 + RPL_SASLMECHS = "908", 150 + } 151 + 152 + /** 153 + * these are ALL the states a channel could be in (EVER) 154 + */ 155 + // these are only here to prevent circular dependency problems. 156 + export enum IrcChannelState { 157 + /** 158 + * This channel has been joined. 159 + */ 160 + Joined = "joined", 161 + /** 162 + * This channel has been joined, but we're still waiting for 163 + * the server to acknowledge it. 164 + */ 165 + Pending = "pending", 166 + /** 167 + * This channel has been parted during this session. 168 + */ 169 + Parted = "parted", 170 + /** 171 + * The channel is not joined, but we have messages from it in 172 + * the logs. 173 + */ 174 + Historical = "historical", 175 + /** 176 + * A join was requested, but wasn't allowed for some reason 177 + */ 178 + Failed = "failed", 179 + } 180 + 181 + 182 + /** 183 + * compare two names according to the given casemapping. 184 + */ 185 + export function compare_names( 186 + casemapping: ISupport["CASEMAPPING"], 187 + a: string, 188 + b: string 189 + ): boolean { 190 + return to_casemap_lowercase(casemapping, a) == to_casemap_lowercase(casemapping, b); 191 + } 192 + 193 + export function to_casemap_lowercase( 194 + casemapping: ISupport["CASEMAPPING"], 195 + str: string 196 + ): string { 197 + let casemap = (() => { 198 + if (casemapping == "rfc1459") { 199 + return <Record<string, string>>{ 200 + "[": "{", 201 + "]": "}", 202 + "\\": "|", 203 + "~": "^", 204 + }; 205 + } 206 + 207 + if (casemapping == "strict-rfc1459") { 208 + return <Record<string, string>>{ 209 + "[": "{", 210 + "]": "}", 211 + "\\": "|", 212 + }; 213 + } 214 + })(); 215 + 216 + str = str.toLowerCase(); 217 + 218 + if (!casemap) { 219 + return str; 220 + } 221 + 222 + for (const [k, v] of Object.entries(casemap)) { 223 + str = str.replace(k, v); 224 + } 225 + 226 + return str; 227 + } 228 + 229 + export enum IrcProtocol { 230 + WebSocket = "ws", 231 + SecureWebSocket = "wss", 232 + Irc = "irc", 233 + SecureIrc = "ircs", 234 + }
+30
core/tsconfig.json
··· 1 + { 2 + "compilerOptions": { 3 + "target": "ES2020", 4 + "useDefineForClassFields": true, 5 + "module": "ESNext", 6 + "lib": ["ESNext", "DOM"], 7 + "skipLibCheck": true, 8 + "moduleResolution": "bundler", 9 + "resolveJsonModule": true, 10 + "isolatedModules": true, 11 + "allowSyntheticDefaultImports": true, 12 + "strict": true, 13 + "noFallthroughCasesInSwitch": true, 14 + "declaration": true, 15 + "sourceMap": true, 16 + "outDir": "./dist/", 17 + "declarationDir": "./dist/" 18 + }, 19 + "include": [ 20 + "**/*.ts" 21 + ], 22 + "exclude": [ 23 + "node_modules", 24 + "./**/*.test.ts", 25 + "./*.test.ts", 26 + "./**/*.d.ts", 27 + "../core_dist", 28 + "./dist" 29 + ] 30 + }
+7
core/vitest.config.ts
··· 1 + import { defineConfig } from 'vitest/config'; 2 + 3 + export default defineConfig({ 4 + test: { 5 + environment: 'happy-dom', 6 + } 7 + })
+165
core/ws/connection.ts
··· 1 + import WebSocket from "isomorphic-ws"; 2 + 3 + import { collect_caps, negotiate_caps } from "../capabilities"; 4 + import { IrcChannel } from "../channel"; 5 + import { 6 + Connection, 7 + ConnectionConfig, 8 + ConnectionErrorCode, 9 + ConnectionParameters, 10 + ConnectionState, 11 + } from "../connection"; 12 + import History, { FetchHistoryParams } from "../history"; 13 + import { collect_motd } from "../motd"; 14 + import { IrcMessage } from "../parser"; 15 + import { Deferred } from "../queue"; 16 + import { Sasl } from "../sasl"; 17 + import { Numeric } from "../support"; 18 + 19 + export interface WsConnectionConfig extends ConnectionConfig { 20 + } 21 + 22 + export class WsConnection extends Connection { 23 + constructor( 24 + public config: WsConnectionConfig, 25 + opt?: ConnectionParameters 26 + ) { 27 + super(config, opt); 28 + 29 + if (config.autoconnect) this.connect(); 30 + } 31 + 32 + ws?: WebSocket; 33 + 34 + async connect() { 35 + this.ws = new WebSocket(this.url); 36 + 37 + this.$state.value = ConnectionState.Connecting; 38 + 39 + const resolver = new Deferred<void, void>(); 40 + 41 + this.ws.onopen = () => { this.#register(resolver) }; 42 + this.ws.onmessage = (ev) => this.handle_incoming(ev.data as string); 43 + this.ws.onclose = () => this.handle_disconnect(); 44 + this.ws.onerror = () => this.socket_error(); 45 + 46 + return resolver.promise; 47 + } 48 + 49 + async #register(resolver: Deferred<void, void>) { 50 + this.$state.value = ConnectionState.Registering; 51 + this.send("CAP LS 302"); 52 + 53 + const caps_recvd = collect_caps(this); 54 + 55 + const { nickname, username, realname } = this; 56 + this.send(`NICK ${nickname}`); 57 + this.send(`USER ${username} 0 * :${realname || nickname}`); 58 + 59 + // wait until we've got all the caps 60 + const cap_msgs = await caps_recvd; 61 + const { available, negotiated } = await negotiate_caps(this, cap_msgs); 62 + this.capabilities = negotiated; 63 + this.available_capabilities = available; 64 + 65 + if (this.config.sasl?.username) { 66 + try { 67 + await new Sasl(this, this.config.sasl).authenticate(); 68 + } catch (e) { 69 + if (!(e instanceof IrcMessage)) throw e; 70 + 71 + switch (e.command) { 72 + case Numeric.ERR_SASLFAIL: 73 + this.$error.value = [ConnectionErrorCode.SaslFailed, e]; 74 + break; 75 + case Numeric.ERR_SASLTOOLONG: 76 + this.$error.value = [ConnectionErrorCode.SaslTooLong, e]; 77 + break; 78 + case Numeric.ERR_SASLABORTED: 79 + this.$error.value = [ConnectionErrorCode.SaslAborted, e]; 80 + break; 81 + case Numeric.ERR_SASLALREADY: break; 82 + default: 83 + throw e; 84 + } 85 + } 86 + } 87 + 88 + this.send("CAP END"); 89 + 90 + this.motd = await collect_motd(this); 91 + 92 + const autojoin = this.config.autojoin ?? []; 93 + 94 + if (autojoin && autojoin.length != 0) { 95 + this.send(`JOIN ${autojoin.join(",")}`); 96 + const channels = autojoin.map(name => new IrcChannel(name, this)); 97 + this.$buffers.value = channels; 98 + } 99 + 100 + // do this /after/ getting the motd to avoid it being missing 101 + // when the connection opens. 102 + // this feels incorrect, but who gives a shit 103 + this.$state.value = ConnectionState.Connected; 104 + 105 + this.on_connect?.(this); 106 + resolver.resolve(); 107 + 108 + this.start_pinging(); 109 + } 110 + 111 + private handle_disconnect() { 112 + this.ws = undefined; 113 + console.info(`${this.id} closed`) 114 + // retain the socket error message on disconnect 115 + if (this.$error.value?.[0] != ConnectionErrorCode.SocketError) { 116 + this.$state.value = ConnectionState.Disconnected; 117 + } 118 + } 119 + 120 + private socket_error() { 121 + console.log("hi") 122 + 123 + this.$state.value = ConnectionState.Failed; 124 + this.$error.value = [ConnectionErrorCode.SocketError]; 125 + 126 + // this.try_and_reconnect = true; 127 + // this.recover_connection(); 128 + } 129 + 130 + async disconnect(target_state: ConnectionState = ConnectionState.Disconnected) { 131 + this.$state.value = ConnectionState.Disconnecting; 132 + 133 + let old_ws = this.ws; 134 + this.ws = undefined; 135 + 136 + let done = false; 137 + 138 + const p = new Promise((resolve) => { 139 + old_ws?.send("QUIT"); 140 + 141 + setTimeout(() => { 142 + // kill the connection if it hasn't exited gracefully within 2 secs 143 + if (!done) old_ws?.close(); 144 + }, 2000); 145 + 146 + old_ws!.onclose = () => { 147 + done = true; 148 + resolve(null); 149 + }; 150 + }); 151 + 152 + await p; 153 + 154 + this.tidy_up(); 155 + this.$state.value = target_state; 156 + } 157 + 158 + send_raw(message: string) { 159 + this.ws!.send(message); 160 + } 161 + 162 + fetch_history = 163 + (...params: FetchHistoryParams): Promise<IrcMessage[]> => 164 + History.chathistory(this, ...params); 165 + }
+5
core/ws/index.ts
··· 1 + import { WsConnection } from "./connection"; 2 + 3 + export { 4 + WsConnection as Connection, 5 + }
docs/api/index.njk

This is a binary file and will not be displayed.

+26
docs/community.md
··· 1 + # tubes community 2 + 3 + tubes is a collaborative free software project. you are extremely welcome to 4 + participate if you want!! 5 + 6 + ## irc 7 + 8 + surprisingly enough, we operate an IRC channel on the 9 + [Libera.Chat](https://libera.chat) network. 10 + 11 + if you're looking to receive insights or wisdom from me (leah clark), please 12 + mention my nick (`thatcher`) in your message or else i will straight up not see 13 + it. 14 + 15 + ## elsewhere 16 + 17 + accounts for broadcasting tube news are available on the following platforms: 18 + 19 + * [mastodon and compatibles](https://h.pronounmail.com/@tubes) 20 + 21 + these are all managed by me, leah clark. 22 + 23 + ## conduct 24 + 25 + tldr: reactionaries banned on sight, don't get into slap fights, avoid being 26 + really fucking annoying, have fun.
+11
docs/index.njk
··· 1 + <!DOCTYPE html> 2 + <html lang="en"> 3 + <head> 4 + <meta charset="UTF-8"> 5 + <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 + <title>Tubes</title> 7 + </head> 8 + <body> 9 + 10 + </body> 11 + </html>
docs/manual/index.njk

This is a binary file and will not be displayed.

+2434
docs/package-lock.json
··· 1 + { 2 + "name": "docs", 3 + "lockfileVersion": 3, 4 + "requires": true, 5 + "packages": { 6 + "": { 7 + "dependencies": { 8 + "@fontsource-variable/roboto-serif": "^5.0.14" 9 + }, 10 + "devDependencies": { 11 + "@11ty/eleventy": "^2.0.1" 12 + } 13 + }, 14 + "node_modules/@11ty/dependency-tree": { 15 + "version": "2.0.1", 16 + "resolved": "https://registry.npmjs.org/@11ty/dependency-tree/-/dependency-tree-2.0.1.tgz", 17 + "integrity": "sha512-5R+DsT9LJ9tXiSQ4y+KLFppCkQyXhzAm1AIuBWE/sbU0hSXY5pkhoqQYEcPJQFg/nglL+wD55iv2j+7O96UAvg==", 18 + "dev": true 19 + }, 20 + "node_modules/@11ty/eleventy": { 21 + "version": "2.0.1", 22 + "resolved": "https://registry.npmjs.org/@11ty/eleventy/-/eleventy-2.0.1.tgz", 23 + "integrity": "sha512-t8XVUbCJByhVEa1RzO0zS2QzbL3wPY8ot1yUw9noqiSHxJWUwv6jiwm1/MZDPTYtkZH2ZHvdQIRQ5/SjG9XmLw==", 24 + "dev": true, 25 + "dependencies": { 26 + "@11ty/dependency-tree": "^2.0.1", 27 + "@11ty/eleventy-dev-server": "^1.0.4", 28 + "@11ty/eleventy-utils": "^1.0.1", 29 + "@11ty/lodash-custom": "^4.17.21", 30 + "@iarna/toml": "^2.2.5", 31 + "@sindresorhus/slugify": "^1.1.2", 32 + "bcp-47-normalize": "^1.1.1", 33 + "chokidar": "^3.5.3", 34 + "cross-spawn": "^7.0.3", 35 + "debug": "^4.3.4", 36 + "dependency-graph": "^0.11.0", 37 + "ejs": "^3.1.9", 38 + "fast-glob": "^3.2.12", 39 + "graceful-fs": "^4.2.11", 40 + "gray-matter": "^4.0.3", 41 + "hamljs": "^0.6.2", 42 + "handlebars": "^4.7.7", 43 + "is-glob": "^4.0.3", 44 + "iso-639-1": "^2.1.15", 45 + "kleur": "^4.1.5", 46 + "liquidjs": "^10.7.0", 47 + "luxon": "^3.3.0", 48 + "markdown-it": "^13.0.1", 49 + "micromatch": "^4.0.5", 50 + "minimist": "^1.2.8", 51 + "moo": "^0.5.2", 52 + "multimatch": "^5.0.0", 53 + "mustache": "^4.2.0", 54 + "normalize-path": "^3.0.0", 55 + "nunjucks": "^3.2.3", 56 + "path-to-regexp": "^6.2.1", 57 + "please-upgrade-node": "^3.2.0", 58 + "posthtml": "^0.16.6", 59 + "posthtml-urls": "^1.0.0", 60 + "pug": "^3.0.2", 61 + "recursive-copy": "^2.0.14", 62 + "semver": "^7.3.8", 63 + "slugify": "^1.6.6" 64 + }, 65 + "bin": { 66 + "eleventy": "cmd.js" 67 + }, 68 + "engines": { 69 + "node": ">=14" 70 + }, 71 + "funding": { 72 + "type": "opencollective", 73 + "url": "https://opencollective.com/11ty" 74 + } 75 + }, 76 + "node_modules/@11ty/eleventy-dev-server": { 77 + "version": "1.0.4", 78 + "resolved": "https://registry.npmjs.org/@11ty/eleventy-dev-server/-/eleventy-dev-server-1.0.4.tgz", 79 + "integrity": "sha512-qVBmV2G1KF/0o5B/3fITlrrDHy4bONUI2YuN3/WJ3BNw4NU1d/we8XhKrlgq13nNvHoBx5czYp3LZt8qRG53Fg==", 80 + "dev": true, 81 + "dependencies": { 82 + "@11ty/eleventy-utils": "^1.0.1", 83 + "chokidar": "^3.5.3", 84 + "debug": "^4.3.4", 85 + "dev-ip": "^1.0.1", 86 + "finalhandler": "^1.2.0", 87 + "mime": "^3.0.0", 88 + "minimist": "^1.2.8", 89 + "morphdom": "^2.7.0", 90 + "please-upgrade-node": "^3.2.0", 91 + "ssri": "^8.0.1", 92 + "ws": "^8.13.0" 93 + }, 94 + "bin": { 95 + "eleventy-dev-server": "cmd.js" 96 + }, 97 + "engines": { 98 + "node": ">=14" 99 + }, 100 + "funding": { 101 + "type": "opencollective", 102 + "url": "https://opencollective.com/11ty" 103 + } 104 + }, 105 + "node_modules/@11ty/eleventy-utils": { 106 + "version": "1.0.3", 107 + "resolved": "https://registry.npmjs.org/@11ty/eleventy-utils/-/eleventy-utils-1.0.3.tgz", 108 + "integrity": "sha512-nULO91om7vQw4Y/UBjM8i7nJ1xl+/nyK4rImZ41lFxiY2d+XUz7ChAj1CDYFjrLZeu0utAYJTZ45LlcHTkUG4g==", 109 + "dev": true, 110 + "dependencies": { 111 + "normalize-path": "^3.0.0" 112 + }, 113 + "engines": { 114 + "node": ">=12" 115 + }, 116 + "funding": { 117 + "type": "opencollective", 118 + "url": "https://opencollective.com/11ty" 119 + } 120 + }, 121 + "node_modules/@11ty/lodash-custom": { 122 + "version": "4.17.21", 123 + "resolved": "https://registry.npmjs.org/@11ty/lodash-custom/-/lodash-custom-4.17.21.tgz", 124 + "integrity": "sha512-Mqt6im1xpb1Ykn3nbcCovWXK3ggywRJa+IXIdoz4wIIK+cvozADH63lexcuPpGS/gJ6/m2JxyyXDyupkMr5DHw==", 125 + "dev": true, 126 + "engines": { 127 + "node": ">=14" 128 + }, 129 + "funding": { 130 + "type": "opencollective", 131 + "url": "https://opencollective.com/11ty" 132 + } 133 + }, 134 + "node_modules/@babel/helper-string-parser": { 135 + "version": "7.24.8", 136 + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", 137 + "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", 138 + "dev": true, 139 + "engines": { 140 + "node": ">=6.9.0" 141 + } 142 + }, 143 + "node_modules/@babel/helper-validator-identifier": { 144 + "version": "7.24.7", 145 + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", 146 + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", 147 + "dev": true, 148 + "engines": { 149 + "node": ">=6.9.0" 150 + } 151 + }, 152 + "node_modules/@babel/parser": { 153 + "version": "7.24.8", 154 + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.8.tgz", 155 + "integrity": "sha512-WzfbgXOkGzZiXXCqk43kKwZjzwx4oulxZi3nq2TYL9mOjQv6kYwul9mz6ID36njuL7Xkp6nJEfok848Zj10j/w==", 156 + "dev": true, 157 + "bin": { 158 + "parser": "bin/babel-parser.js" 159 + }, 160 + "engines": { 161 + "node": ">=6.0.0" 162 + } 163 + }, 164 + "node_modules/@babel/types": { 165 + "version": "7.24.8", 166 + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.8.tgz", 167 + "integrity": "sha512-SkSBEHwwJRU52QEVZBmMBnE5Ux2/6WU1grdYyOhpbCNxbmJrDuDCphBzKZSO3taf0zztp+qkWlymE5tVL5l0TA==", 168 + "dev": true, 169 + "dependencies": { 170 + "@babel/helper-string-parser": "^7.24.8", 171 + "@babel/helper-validator-identifier": "^7.24.7", 172 + "to-fast-properties": "^2.0.0" 173 + }, 174 + "engines": { 175 + "node": ">=6.9.0" 176 + } 177 + }, 178 + "node_modules/@fontsource-variable/roboto-serif": { 179 + "version": "5.0.14", 180 + "resolved": "https://registry.npmjs.org/@fontsource-variable/roboto-serif/-/roboto-serif-5.0.14.tgz", 181 + "integrity": "sha512-lzjPasdqDhuDaiavHn6OB2XIxptqHroXUNwnfoDUukU9whH7SlGFyt+s6MNFmVp5ijqv14uJdiDcd1SWTqwLKQ==" 182 + }, 183 + "node_modules/@iarna/toml": { 184 + "version": "2.2.5", 185 + "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz", 186 + "integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==", 187 + "dev": true 188 + }, 189 + "node_modules/@nodelib/fs.scandir": { 190 + "version": "2.1.5", 191 + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", 192 + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", 193 + "dev": true, 194 + "dependencies": { 195 + "@nodelib/fs.stat": "2.0.5", 196 + "run-parallel": "^1.1.9" 197 + }, 198 + "engines": { 199 + "node": ">= 8" 200 + } 201 + }, 202 + "node_modules/@nodelib/fs.stat": { 203 + "version": "2.0.5", 204 + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", 205 + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", 206 + "dev": true, 207 + "engines": { 208 + "node": ">= 8" 209 + } 210 + }, 211 + "node_modules/@nodelib/fs.walk": { 212 + "version": "1.2.8", 213 + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", 214 + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", 215 + "dev": true, 216 + "dependencies": { 217 + "@nodelib/fs.scandir": "2.1.5", 218 + "fastq": "^1.6.0" 219 + }, 220 + "engines": { 221 + "node": ">= 8" 222 + } 223 + }, 224 + "node_modules/@sindresorhus/slugify": { 225 + "version": "1.1.2", 226 + "resolved": "https://registry.npmjs.org/@sindresorhus/slugify/-/slugify-1.1.2.tgz", 227 + "integrity": "sha512-V9nR/W0Xd9TSGXpZ4iFUcFGhuOJtZX82Fzxj1YISlbSgKvIiNa7eLEZrT0vAraPOt++KHauIVNYgGRgjc13dXA==", 228 + "dev": true, 229 + "dependencies": { 230 + "@sindresorhus/transliterate": "^0.1.1", 231 + "escape-string-regexp": "^4.0.0" 232 + }, 233 + "engines": { 234 + "node": ">=10" 235 + }, 236 + "funding": { 237 + "url": "https://github.com/sponsors/sindresorhus" 238 + } 239 + }, 240 + "node_modules/@sindresorhus/transliterate": { 241 + "version": "0.1.2", 242 + "resolved": "https://registry.npmjs.org/@sindresorhus/transliterate/-/transliterate-0.1.2.tgz", 243 + "integrity": "sha512-5/kmIOY9FF32nicXH+5yLNTX4NJ4atl7jRgqAJuIn/iyDFXBktOKDxCvyGE/EzmF4ngSUvjXxQUQlQiZ5lfw+w==", 244 + "dev": true, 245 + "dependencies": { 246 + "escape-string-regexp": "^2.0.0", 247 + "lodash.deburr": "^4.1.0" 248 + }, 249 + "engines": { 250 + "node": ">=10" 251 + }, 252 + "funding": { 253 + "url": "https://github.com/sponsors/sindresorhus" 254 + } 255 + }, 256 + "node_modules/@sindresorhus/transliterate/node_modules/escape-string-regexp": { 257 + "version": "2.0.0", 258 + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", 259 + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", 260 + "dev": true, 261 + "engines": { 262 + "node": ">=8" 263 + } 264 + }, 265 + "node_modules/@types/minimatch": { 266 + "version": "3.0.5", 267 + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", 268 + "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==", 269 + "dev": true 270 + }, 271 + "node_modules/a-sync-waterfall": { 272 + "version": "1.0.1", 273 + "resolved": "https://registry.npmjs.org/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz", 274 + "integrity": "sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA==", 275 + "dev": true 276 + }, 277 + "node_modules/acorn": { 278 + "version": "7.4.1", 279 + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", 280 + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", 281 + "dev": true, 282 + "bin": { 283 + "acorn": "bin/acorn" 284 + }, 285 + "engines": { 286 + "node": ">=0.4.0" 287 + } 288 + }, 289 + "node_modules/ansi-styles": { 290 + "version": "4.3.0", 291 + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 292 + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 293 + "dev": true, 294 + "dependencies": { 295 + "color-convert": "^2.0.1" 296 + }, 297 + "engines": { 298 + "node": ">=8" 299 + }, 300 + "funding": { 301 + "url": "https://github.com/chalk/ansi-styles?sponsor=1" 302 + } 303 + }, 304 + "node_modules/any-promise": { 305 + "version": "0.1.0", 306 + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-0.1.0.tgz", 307 + "integrity": "sha512-lqzY9o+BbeGHRCOyxQkt/Tgvz0IZhTmQiA+LxQW8wSNpcTbj8K+0cZiSEvbpNZZP9/11Gy7dnLO3GNWUXO4d1g==", 308 + "dev": true 309 + }, 310 + "node_modules/anymatch": { 311 + "version": "3.1.3", 312 + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", 313 + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", 314 + "dev": true, 315 + "dependencies": { 316 + "normalize-path": "^3.0.0", 317 + "picomatch": "^2.0.4" 318 + }, 319 + "engines": { 320 + "node": ">= 8" 321 + } 322 + }, 323 + "node_modules/argparse": { 324 + "version": "1.0.10", 325 + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", 326 + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", 327 + "dev": true, 328 + "dependencies": { 329 + "sprintf-js": "~1.0.2" 330 + } 331 + }, 332 + "node_modules/array-differ": { 333 + "version": "3.0.0", 334 + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-3.0.0.tgz", 335 + "integrity": "sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==", 336 + "dev": true, 337 + "engines": { 338 + "node": ">=8" 339 + } 340 + }, 341 + "node_modules/array-union": { 342 + "version": "2.1.0", 343 + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", 344 + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", 345 + "dev": true, 346 + "engines": { 347 + "node": ">=8" 348 + } 349 + }, 350 + "node_modules/array-uniq": { 351 + "version": "1.0.3", 352 + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", 353 + "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", 354 + "dev": true, 355 + "engines": { 356 + "node": ">=0.10.0" 357 + } 358 + }, 359 + "node_modules/arrify": { 360 + "version": "2.0.1", 361 + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", 362 + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", 363 + "dev": true, 364 + "engines": { 365 + "node": ">=8" 366 + } 367 + }, 368 + "node_modules/asap": { 369 + "version": "2.0.6", 370 + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", 371 + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", 372 + "dev": true 373 + }, 374 + "node_modules/assert-never": { 375 + "version": "1.3.0", 376 + "resolved": "https://registry.npmjs.org/assert-never/-/assert-never-1.3.0.tgz", 377 + "integrity": "sha512-9Z3vxQ+berkL/JJo0dK+EY3Lp0s3NtSnP3VCLsh5HDcZPrh0M+KQRK5sWhUeyPPH+/RCxZqOxLMR+YC6vlviEQ==", 378 + "dev": true 379 + }, 380 + "node_modules/async": { 381 + "version": "3.2.5", 382 + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", 383 + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", 384 + "dev": true 385 + }, 386 + "node_modules/babel-walk": { 387 + "version": "3.0.0-canary-5", 388 + "resolved": "https://registry.npmjs.org/babel-walk/-/babel-walk-3.0.0-canary-5.tgz", 389 + "integrity": "sha512-GAwkz0AihzY5bkwIY5QDR+LvsRQgB/B+1foMPvi0FZPMl5fjD7ICiznUiBdLYMH1QYe6vqu4gWYytZOccLouFw==", 390 + "dev": true, 391 + "dependencies": { 392 + "@babel/types": "^7.9.6" 393 + }, 394 + "engines": { 395 + "node": ">= 10.0.0" 396 + } 397 + }, 398 + "node_modules/balanced-match": { 399 + "version": "1.0.2", 400 + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 401 + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 402 + "dev": true 403 + }, 404 + "node_modules/bcp-47": { 405 + "version": "1.0.8", 406 + "resolved": "https://registry.npmjs.org/bcp-47/-/bcp-47-1.0.8.tgz", 407 + "integrity": "sha512-Y9y1QNBBtYtv7hcmoX0tR+tUNSFZGZ6OL6vKPObq8BbOhkCoyayF6ogfLTgAli/KuAEbsYHYUNq2AQuY6IuLag==", 408 + "dev": true, 409 + "dependencies": { 410 + "is-alphabetical": "^1.0.0", 411 + "is-alphanumerical": "^1.0.0", 412 + "is-decimal": "^1.0.0" 413 + }, 414 + "funding": { 415 + "type": "github", 416 + "url": "https://github.com/sponsors/wooorm" 417 + } 418 + }, 419 + "node_modules/bcp-47-match": { 420 + "version": "1.0.3", 421 + "resolved": "https://registry.npmjs.org/bcp-47-match/-/bcp-47-match-1.0.3.tgz", 422 + "integrity": "sha512-LggQ4YTdjWQSKELZF5JwchnBa1u0pIQSZf5lSdOHEdbVP55h0qICA/FUp3+W99q0xqxYa1ZQizTUH87gecII5w==", 423 + "dev": true, 424 + "funding": { 425 + "type": "github", 426 + "url": "https://github.com/sponsors/wooorm" 427 + } 428 + }, 429 + "node_modules/bcp-47-normalize": { 430 + "version": "1.1.1", 431 + "resolved": "https://registry.npmjs.org/bcp-47-normalize/-/bcp-47-normalize-1.1.1.tgz", 432 + "integrity": "sha512-jWZ1Jdu3cs0EZdfCkS0UE9Gg01PtxnChjEBySeB+Zo6nkqtFfnvtoQQgP1qU1Oo4qgJgxhTI6Sf9y/pZIhPs0A==", 433 + "dev": true, 434 + "dependencies": { 435 + "bcp-47": "^1.0.0", 436 + "bcp-47-match": "^1.0.0" 437 + }, 438 + "funding": { 439 + "type": "github", 440 + "url": "https://github.com/sponsors/wooorm" 441 + } 442 + }, 443 + "node_modules/binary-extensions": { 444 + "version": "2.3.0", 445 + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", 446 + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", 447 + "dev": true, 448 + "engines": { 449 + "node": ">=8" 450 + }, 451 + "funding": { 452 + "url": "https://github.com/sponsors/sindresorhus" 453 + } 454 + }, 455 + "node_modules/brace-expansion": { 456 + "version": "1.1.11", 457 + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 458 + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 459 + "dev": true, 460 + "dependencies": { 461 + "balanced-match": "^1.0.0", 462 + "concat-map": "0.0.1" 463 + } 464 + }, 465 + "node_modules/braces": { 466 + "version": "3.0.3", 467 + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", 468 + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", 469 + "dev": true, 470 + "dependencies": { 471 + "fill-range": "^7.1.1" 472 + }, 473 + "engines": { 474 + "node": ">=8" 475 + } 476 + }, 477 + "node_modules/call-bind": { 478 + "version": "1.0.7", 479 + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", 480 + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", 481 + "dev": true, 482 + "dependencies": { 483 + "es-define-property": "^1.0.0", 484 + "es-errors": "^1.3.0", 485 + "function-bind": "^1.1.2", 486 + "get-intrinsic": "^1.2.4", 487 + "set-function-length": "^1.2.1" 488 + }, 489 + "engines": { 490 + "node": ">= 0.4" 491 + }, 492 + "funding": { 493 + "url": "https://github.com/sponsors/ljharb" 494 + } 495 + }, 496 + "node_modules/chalk": { 497 + "version": "4.1.2", 498 + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 499 + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 500 + "dev": true, 501 + "dependencies": { 502 + "ansi-styles": "^4.1.0", 503 + "supports-color": "^7.1.0" 504 + }, 505 + "engines": { 506 + "node": ">=10" 507 + }, 508 + "funding": { 509 + "url": "https://github.com/chalk/chalk?sponsor=1" 510 + } 511 + }, 512 + "node_modules/character-parser": { 513 + "version": "2.2.0", 514 + "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz", 515 + "integrity": "sha512-+UqJQjFEFaTAs3bNsF2j2kEN1baG/zghZbdqoYEDxGZtJo9LBzl1A+m0D4n3qKx8N2FNv8/Xp6yV9mQmBuptaw==", 516 + "dev": true, 517 + "dependencies": { 518 + "is-regex": "^1.0.3" 519 + } 520 + }, 521 + "node_modules/chokidar": { 522 + "version": "3.6.0", 523 + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", 524 + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", 525 + "dev": true, 526 + "dependencies": { 527 + "anymatch": "~3.1.2", 528 + "braces": "~3.0.2", 529 + "glob-parent": "~5.1.2", 530 + "is-binary-path": "~2.1.0", 531 + "is-glob": "~4.0.1", 532 + "normalize-path": "~3.0.0", 533 + "readdirp": "~3.6.0" 534 + }, 535 + "engines": { 536 + "node": ">= 8.10.0" 537 + }, 538 + "funding": { 539 + "url": "https://paulmillr.com/funding/" 540 + }, 541 + "optionalDependencies": { 542 + "fsevents": "~2.3.2" 543 + } 544 + }, 545 + "node_modules/color-convert": { 546 + "version": "2.0.1", 547 + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 548 + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 549 + "dev": true, 550 + "dependencies": { 551 + "color-name": "~1.1.4" 552 + }, 553 + "engines": { 554 + "node": ">=7.0.0" 555 + } 556 + }, 557 + "node_modules/color-name": { 558 + "version": "1.1.4", 559 + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 560 + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 561 + "dev": true 562 + }, 563 + "node_modules/commander": { 564 + "version": "10.0.1", 565 + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", 566 + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", 567 + "dev": true, 568 + "engines": { 569 + "node": ">=14" 570 + } 571 + }, 572 + "node_modules/concat-map": { 573 + "version": "0.0.1", 574 + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 575 + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", 576 + "dev": true 577 + }, 578 + "node_modules/constantinople": { 579 + "version": "4.0.1", 580 + "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-4.0.1.tgz", 581 + "integrity": "sha512-vCrqcSIq4//Gx74TXXCGnHpulY1dskqLTFGDmhrGxzeXL8lF8kvXv6mpNWlJj1uD4DW23D4ljAqbY4RRaaUZIw==", 582 + "dev": true, 583 + "dependencies": { 584 + "@babel/parser": "^7.6.0", 585 + "@babel/types": "^7.6.1" 586 + } 587 + }, 588 + "node_modules/cross-spawn": { 589 + "version": "7.0.3", 590 + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", 591 + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", 592 + "dev": true, 593 + "dependencies": { 594 + "path-key": "^3.1.0", 595 + "shebang-command": "^2.0.0", 596 + "which": "^2.0.1" 597 + }, 598 + "engines": { 599 + "node": ">= 8" 600 + } 601 + }, 602 + "node_modules/debug": { 603 + "version": "4.3.5", 604 + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", 605 + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", 606 + "dev": true, 607 + "dependencies": { 608 + "ms": "2.1.2" 609 + }, 610 + "engines": { 611 + "node": ">=6.0" 612 + }, 613 + "peerDependenciesMeta": { 614 + "supports-color": { 615 + "optional": true 616 + } 617 + } 618 + }, 619 + "node_modules/define-data-property": { 620 + "version": "1.1.4", 621 + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", 622 + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", 623 + "dev": true, 624 + "dependencies": { 625 + "es-define-property": "^1.0.0", 626 + "es-errors": "^1.3.0", 627 + "gopd": "^1.0.1" 628 + }, 629 + "engines": { 630 + "node": ">= 0.4" 631 + }, 632 + "funding": { 633 + "url": "https://github.com/sponsors/ljharb" 634 + } 635 + }, 636 + "node_modules/dependency-graph": { 637 + "version": "0.11.0", 638 + "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz", 639 + "integrity": "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==", 640 + "dev": true, 641 + "engines": { 642 + "node": ">= 0.6.0" 643 + } 644 + }, 645 + "node_modules/dev-ip": { 646 + "version": "1.0.1", 647 + "resolved": "https://registry.npmjs.org/dev-ip/-/dev-ip-1.0.1.tgz", 648 + "integrity": "sha512-LmVkry/oDShEgSZPNgqCIp2/TlqtExeGmymru3uCELnfyjY11IzpAproLYs+1X88fXO6DBoYP3ul2Xo2yz2j6A==", 649 + "dev": true, 650 + "bin": { 651 + "dev-ip": "lib/dev-ip.js" 652 + }, 653 + "engines": { 654 + "node": ">= 0.8.0" 655 + } 656 + }, 657 + "node_modules/doctypes": { 658 + "version": "1.1.0", 659 + "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz", 660 + "integrity": "sha512-LLBi6pEqS6Do3EKQ3J0NqHWV5hhb78Pi8vvESYwyOy2c31ZEZVdtitdzsQsKb7878PEERhzUk0ftqGhG6Mz+pQ==", 661 + "dev": true 662 + }, 663 + "node_modules/dom-serializer": { 664 + "version": "1.4.1", 665 + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", 666 + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", 667 + "dev": true, 668 + "dependencies": { 669 + "domelementtype": "^2.0.1", 670 + "domhandler": "^4.2.0", 671 + "entities": "^2.0.0" 672 + }, 673 + "funding": { 674 + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" 675 + } 676 + }, 677 + "node_modules/dom-serializer/node_modules/entities": { 678 + "version": "2.2.0", 679 + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", 680 + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", 681 + "dev": true, 682 + "funding": { 683 + "url": "https://github.com/fb55/entities?sponsor=1" 684 + } 685 + }, 686 + "node_modules/domelementtype": { 687 + "version": "2.3.0", 688 + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", 689 + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", 690 + "dev": true, 691 + "funding": [ 692 + { 693 + "type": "github", 694 + "url": "https://github.com/sponsors/fb55" 695 + } 696 + ] 697 + }, 698 + "node_modules/domhandler": { 699 + "version": "4.3.1", 700 + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", 701 + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", 702 + "dev": true, 703 + "dependencies": { 704 + "domelementtype": "^2.2.0" 705 + }, 706 + "engines": { 707 + "node": ">= 4" 708 + }, 709 + "funding": { 710 + "url": "https://github.com/fb55/domhandler?sponsor=1" 711 + } 712 + }, 713 + "node_modules/domutils": { 714 + "version": "2.8.0", 715 + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", 716 + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", 717 + "dev": true, 718 + "dependencies": { 719 + "dom-serializer": "^1.0.1", 720 + "domelementtype": "^2.2.0", 721 + "domhandler": "^4.2.0" 722 + }, 723 + "funding": { 724 + "url": "https://github.com/fb55/domutils?sponsor=1" 725 + } 726 + }, 727 + "node_modules/ee-first": { 728 + "version": "1.1.1", 729 + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 730 + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", 731 + "dev": true 732 + }, 733 + "node_modules/ejs": { 734 + "version": "3.1.10", 735 + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", 736 + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", 737 + "dev": true, 738 + "dependencies": { 739 + "jake": "^10.8.5" 740 + }, 741 + "bin": { 742 + "ejs": "bin/cli.js" 743 + }, 744 + "engines": { 745 + "node": ">=0.10.0" 746 + } 747 + }, 748 + "node_modules/encodeurl": { 749 + "version": "1.0.2", 750 + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 751 + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", 752 + "dev": true, 753 + "engines": { 754 + "node": ">= 0.8" 755 + } 756 + }, 757 + "node_modules/entities": { 758 + "version": "3.0.1", 759 + "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", 760 + "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", 761 + "dev": true, 762 + "engines": { 763 + "node": ">=0.12" 764 + }, 765 + "funding": { 766 + "url": "https://github.com/fb55/entities?sponsor=1" 767 + } 768 + }, 769 + "node_modules/errno": { 770 + "version": "0.1.8", 771 + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", 772 + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", 773 + "dev": true, 774 + "dependencies": { 775 + "prr": "~1.0.1" 776 + }, 777 + "bin": { 778 + "errno": "cli.js" 779 + } 780 + }, 781 + "node_modules/es-define-property": { 782 + "version": "1.0.0", 783 + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", 784 + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", 785 + "dev": true, 786 + "dependencies": { 787 + "get-intrinsic": "^1.2.4" 788 + }, 789 + "engines": { 790 + "node": ">= 0.4" 791 + } 792 + }, 793 + "node_modules/es-errors": { 794 + "version": "1.3.0", 795 + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", 796 + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", 797 + "dev": true, 798 + "engines": { 799 + "node": ">= 0.4" 800 + } 801 + }, 802 + "node_modules/escape-html": { 803 + "version": "1.0.3", 804 + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 805 + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", 806 + "dev": true 807 + }, 808 + "node_modules/escape-string-regexp": { 809 + "version": "4.0.0", 810 + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", 811 + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", 812 + "dev": true, 813 + "engines": { 814 + "node": ">=10" 815 + }, 816 + "funding": { 817 + "url": "https://github.com/sponsors/sindresorhus" 818 + } 819 + }, 820 + "node_modules/esprima": { 821 + "version": "4.0.1", 822 + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", 823 + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", 824 + "dev": true, 825 + "bin": { 826 + "esparse": "bin/esparse.js", 827 + "esvalidate": "bin/esvalidate.js" 828 + }, 829 + "engines": { 830 + "node": ">=4" 831 + } 832 + }, 833 + "node_modules/extend-shallow": { 834 + "version": "2.0.1", 835 + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", 836 + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", 837 + "dev": true, 838 + "dependencies": { 839 + "is-extendable": "^0.1.0" 840 + }, 841 + "engines": { 842 + "node": ">=0.10.0" 843 + } 844 + }, 845 + "node_modules/fast-glob": { 846 + "version": "3.3.2", 847 + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", 848 + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", 849 + "dev": true, 850 + "dependencies": { 851 + "@nodelib/fs.stat": "^2.0.2", 852 + "@nodelib/fs.walk": "^1.2.3", 853 + "glob-parent": "^5.1.2", 854 + "merge2": "^1.3.0", 855 + "micromatch": "^4.0.4" 856 + }, 857 + "engines": { 858 + "node": ">=8.6.0" 859 + } 860 + }, 861 + "node_modules/fastq": { 862 + "version": "1.17.1", 863 + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", 864 + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", 865 + "dev": true, 866 + "dependencies": { 867 + "reusify": "^1.0.4" 868 + } 869 + }, 870 + "node_modules/filelist": { 871 + "version": "1.0.4", 872 + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", 873 + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", 874 + "dev": true, 875 + "dependencies": { 876 + "minimatch": "^5.0.1" 877 + } 878 + }, 879 + "node_modules/filelist/node_modules/brace-expansion": { 880 + "version": "2.0.1", 881 + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", 882 + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", 883 + "dev": true, 884 + "dependencies": { 885 + "balanced-match": "^1.0.0" 886 + } 887 + }, 888 + "node_modules/filelist/node_modules/minimatch": { 889 + "version": "5.1.6", 890 + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", 891 + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", 892 + "dev": true, 893 + "dependencies": { 894 + "brace-expansion": "^2.0.1" 895 + }, 896 + "engines": { 897 + "node": ">=10" 898 + } 899 + }, 900 + "node_modules/fill-range": { 901 + "version": "7.1.1", 902 + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", 903 + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", 904 + "dev": true, 905 + "dependencies": { 906 + "to-regex-range": "^5.0.1" 907 + }, 908 + "engines": { 909 + "node": ">=8" 910 + } 911 + }, 912 + "node_modules/finalhandler": { 913 + "version": "1.2.0", 914 + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", 915 + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", 916 + "dev": true, 917 + "dependencies": { 918 + "debug": "2.6.9", 919 + "encodeurl": "~1.0.2", 920 + "escape-html": "~1.0.3", 921 + "on-finished": "2.4.1", 922 + "parseurl": "~1.3.3", 923 + "statuses": "2.0.1", 924 + "unpipe": "~1.0.0" 925 + }, 926 + "engines": { 927 + "node": ">= 0.8" 928 + } 929 + }, 930 + "node_modules/finalhandler/node_modules/debug": { 931 + "version": "2.6.9", 932 + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 933 + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 934 + "dev": true, 935 + "dependencies": { 936 + "ms": "2.0.0" 937 + } 938 + }, 939 + "node_modules/finalhandler/node_modules/ms": { 940 + "version": "2.0.0", 941 + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 942 + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", 943 + "dev": true 944 + }, 945 + "node_modules/fs.realpath": { 946 + "version": "1.0.0", 947 + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 948 + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", 949 + "dev": true 950 + }, 951 + "node_modules/fsevents": { 952 + "version": "2.3.3", 953 + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 954 + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 955 + "dev": true, 956 + "hasInstallScript": true, 957 + "optional": true, 958 + "os": [ 959 + "darwin" 960 + ], 961 + "engines": { 962 + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 963 + } 964 + }, 965 + "node_modules/function-bind": { 966 + "version": "1.1.2", 967 + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", 968 + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", 969 + "dev": true, 970 + "funding": { 971 + "url": "https://github.com/sponsors/ljharb" 972 + } 973 + }, 974 + "node_modules/get-intrinsic": { 975 + "version": "1.2.4", 976 + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", 977 + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", 978 + "dev": true, 979 + "dependencies": { 980 + "es-errors": "^1.3.0", 981 + "function-bind": "^1.1.2", 982 + "has-proto": "^1.0.1", 983 + "has-symbols": "^1.0.3", 984 + "hasown": "^2.0.0" 985 + }, 986 + "engines": { 987 + "node": ">= 0.4" 988 + }, 989 + "funding": { 990 + "url": "https://github.com/sponsors/ljharb" 991 + } 992 + }, 993 + "node_modules/glob": { 994 + "version": "7.2.3", 995 + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", 996 + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", 997 + "deprecated": "Glob versions prior to v9 are no longer supported", 998 + "dev": true, 999 + "dependencies": { 1000 + "fs.realpath": "^1.0.0", 1001 + "inflight": "^1.0.4", 1002 + "inherits": "2", 1003 + "minimatch": "^3.1.1", 1004 + "once": "^1.3.0", 1005 + "path-is-absolute": "^1.0.0" 1006 + }, 1007 + "engines": { 1008 + "node": "*" 1009 + }, 1010 + "funding": { 1011 + "url": "https://github.com/sponsors/isaacs" 1012 + } 1013 + }, 1014 + "node_modules/glob-parent": { 1015 + "version": "5.1.2", 1016 + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 1017 + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 1018 + "dev": true, 1019 + "dependencies": { 1020 + "is-glob": "^4.0.1" 1021 + }, 1022 + "engines": { 1023 + "node": ">= 6" 1024 + } 1025 + }, 1026 + "node_modules/gopd": { 1027 + "version": "1.0.1", 1028 + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", 1029 + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", 1030 + "dev": true, 1031 + "dependencies": { 1032 + "get-intrinsic": "^1.1.3" 1033 + }, 1034 + "funding": { 1035 + "url": "https://github.com/sponsors/ljharb" 1036 + } 1037 + }, 1038 + "node_modules/graceful-fs": { 1039 + "version": "4.2.11", 1040 + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", 1041 + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", 1042 + "dev": true 1043 + }, 1044 + "node_modules/gray-matter": { 1045 + "version": "4.0.3", 1046 + "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", 1047 + "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==", 1048 + "dev": true, 1049 + "dependencies": { 1050 + "js-yaml": "^3.13.1", 1051 + "kind-of": "^6.0.2", 1052 + "section-matter": "^1.0.0", 1053 + "strip-bom-string": "^1.0.0" 1054 + }, 1055 + "engines": { 1056 + "node": ">=6.0" 1057 + } 1058 + }, 1059 + "node_modules/hamljs": { 1060 + "version": "0.6.2", 1061 + "resolved": "https://registry.npmjs.org/hamljs/-/hamljs-0.6.2.tgz", 1062 + "integrity": "sha512-/chXRp4WpL47I+HX1vCCdSbEXAljEG2FBMmgO7Am0bYsqgnEjreeWzUdX1onXqwZtcfgxbCg5WtEYYvuZ5muBg==", 1063 + "dev": true 1064 + }, 1065 + "node_modules/handlebars": { 1066 + "version": "4.7.8", 1067 + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", 1068 + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", 1069 + "dev": true, 1070 + "dependencies": { 1071 + "minimist": "^1.2.5", 1072 + "neo-async": "^2.6.2", 1073 + "source-map": "^0.6.1", 1074 + "wordwrap": "^1.0.0" 1075 + }, 1076 + "bin": { 1077 + "handlebars": "bin/handlebars" 1078 + }, 1079 + "engines": { 1080 + "node": ">=0.4.7" 1081 + }, 1082 + "optionalDependencies": { 1083 + "uglify-js": "^3.1.4" 1084 + } 1085 + }, 1086 + "node_modules/has-flag": { 1087 + "version": "4.0.0", 1088 + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 1089 + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 1090 + "dev": true, 1091 + "engines": { 1092 + "node": ">=8" 1093 + } 1094 + }, 1095 + "node_modules/has-property-descriptors": { 1096 + "version": "1.0.2", 1097 + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", 1098 + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", 1099 + "dev": true, 1100 + "dependencies": { 1101 + "es-define-property": "^1.0.0" 1102 + }, 1103 + "funding": { 1104 + "url": "https://github.com/sponsors/ljharb" 1105 + } 1106 + }, 1107 + "node_modules/has-proto": { 1108 + "version": "1.0.3", 1109 + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", 1110 + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", 1111 + "dev": true, 1112 + "engines": { 1113 + "node": ">= 0.4" 1114 + }, 1115 + "funding": { 1116 + "url": "https://github.com/sponsors/ljharb" 1117 + } 1118 + }, 1119 + "node_modules/has-symbols": { 1120 + "version": "1.0.3", 1121 + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", 1122 + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", 1123 + "dev": true, 1124 + "engines": { 1125 + "node": ">= 0.4" 1126 + }, 1127 + "funding": { 1128 + "url": "https://github.com/sponsors/ljharb" 1129 + } 1130 + }, 1131 + "node_modules/has-tostringtag": { 1132 + "version": "1.0.2", 1133 + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", 1134 + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", 1135 + "dev": true, 1136 + "dependencies": { 1137 + "has-symbols": "^1.0.3" 1138 + }, 1139 + "engines": { 1140 + "node": ">= 0.4" 1141 + }, 1142 + "funding": { 1143 + "url": "https://github.com/sponsors/ljharb" 1144 + } 1145 + }, 1146 + "node_modules/hasown": { 1147 + "version": "2.0.2", 1148 + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", 1149 + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", 1150 + "dev": true, 1151 + "dependencies": { 1152 + "function-bind": "^1.1.2" 1153 + }, 1154 + "engines": { 1155 + "node": ">= 0.4" 1156 + } 1157 + }, 1158 + "node_modules/htmlparser2": { 1159 + "version": "7.2.0", 1160 + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-7.2.0.tgz", 1161 + "integrity": "sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog==", 1162 + "dev": true, 1163 + "funding": [ 1164 + "https://github.com/fb55/htmlparser2?sponsor=1", 1165 + { 1166 + "type": "github", 1167 + "url": "https://github.com/sponsors/fb55" 1168 + } 1169 + ], 1170 + "dependencies": { 1171 + "domelementtype": "^2.0.1", 1172 + "domhandler": "^4.2.2", 1173 + "domutils": "^2.8.0", 1174 + "entities": "^3.0.1" 1175 + } 1176 + }, 1177 + "node_modules/http-equiv-refresh": { 1178 + "version": "1.0.0", 1179 + "resolved": "https://registry.npmjs.org/http-equiv-refresh/-/http-equiv-refresh-1.0.0.tgz", 1180 + "integrity": "sha512-TScO04soylRN9i/QdOdgZyhydXg9z6XdaGzEyOgDKycePeDeTT4KvigjBcI+tgfTlieLWauGORMq5F1eIDa+1w==", 1181 + "dev": true, 1182 + "engines": { 1183 + "node": ">= 0.10" 1184 + } 1185 + }, 1186 + "node_modules/inflight": { 1187 + "version": "1.0.6", 1188 + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 1189 + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", 1190 + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", 1191 + "dev": true, 1192 + "dependencies": { 1193 + "once": "^1.3.0", 1194 + "wrappy": "1" 1195 + } 1196 + }, 1197 + "node_modules/inherits": { 1198 + "version": "2.0.4", 1199 + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 1200 + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 1201 + "dev": true 1202 + }, 1203 + "node_modules/is-alphabetical": { 1204 + "version": "1.0.4", 1205 + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", 1206 + "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", 1207 + "dev": true, 1208 + "funding": { 1209 + "type": "github", 1210 + "url": "https://github.com/sponsors/wooorm" 1211 + } 1212 + }, 1213 + "node_modules/is-alphanumerical": { 1214 + "version": "1.0.4", 1215 + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", 1216 + "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", 1217 + "dev": true, 1218 + "dependencies": { 1219 + "is-alphabetical": "^1.0.0", 1220 + "is-decimal": "^1.0.0" 1221 + }, 1222 + "funding": { 1223 + "type": "github", 1224 + "url": "https://github.com/sponsors/wooorm" 1225 + } 1226 + }, 1227 + "node_modules/is-binary-path": { 1228 + "version": "2.1.0", 1229 + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", 1230 + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", 1231 + "dev": true, 1232 + "dependencies": { 1233 + "binary-extensions": "^2.0.0" 1234 + }, 1235 + "engines": { 1236 + "node": ">=8" 1237 + } 1238 + }, 1239 + "node_modules/is-core-module": { 1240 + "version": "2.14.0", 1241 + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.14.0.tgz", 1242 + "integrity": "sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A==", 1243 + "dev": true, 1244 + "dependencies": { 1245 + "hasown": "^2.0.2" 1246 + }, 1247 + "engines": { 1248 + "node": ">= 0.4" 1249 + }, 1250 + "funding": { 1251 + "url": "https://github.com/sponsors/ljharb" 1252 + } 1253 + }, 1254 + "node_modules/is-decimal": { 1255 + "version": "1.0.4", 1256 + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", 1257 + "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", 1258 + "dev": true, 1259 + "funding": { 1260 + "type": "github", 1261 + "url": "https://github.com/sponsors/wooorm" 1262 + } 1263 + }, 1264 + "node_modules/is-expression": { 1265 + "version": "4.0.0", 1266 + "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-4.0.0.tgz", 1267 + "integrity": "sha512-zMIXX63sxzG3XrkHkrAPvm/OVZVSCPNkwMHU8oTX7/U3AL78I0QXCEICXUM13BIa8TYGZ68PiTKfQz3yaTNr4A==", 1268 + "dev": true, 1269 + "dependencies": { 1270 + "acorn": "^7.1.1", 1271 + "object-assign": "^4.1.1" 1272 + } 1273 + }, 1274 + "node_modules/is-extendable": { 1275 + "version": "0.1.1", 1276 + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", 1277 + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", 1278 + "dev": true, 1279 + "engines": { 1280 + "node": ">=0.10.0" 1281 + } 1282 + }, 1283 + "node_modules/is-extglob": { 1284 + "version": "2.1.1", 1285 + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 1286 + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 1287 + "dev": true, 1288 + "engines": { 1289 + "node": ">=0.10.0" 1290 + } 1291 + }, 1292 + "node_modules/is-glob": { 1293 + "version": "4.0.3", 1294 + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 1295 + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 1296 + "dev": true, 1297 + "dependencies": { 1298 + "is-extglob": "^2.1.1" 1299 + }, 1300 + "engines": { 1301 + "node": ">=0.10.0" 1302 + } 1303 + }, 1304 + "node_modules/is-json": { 1305 + "version": "2.0.1", 1306 + "resolved": "https://registry.npmjs.org/is-json/-/is-json-2.0.1.tgz", 1307 + "integrity": "sha512-6BEnpVn1rcf3ngfmViLM6vjUjGErbdrL4rwlv+u1NO1XO8kqT4YGL8+19Q+Z/bas8tY90BTWMk2+fW1g6hQjbA==", 1308 + "dev": true 1309 + }, 1310 + "node_modules/is-number": { 1311 + "version": "7.0.0", 1312 + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 1313 + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 1314 + "dev": true, 1315 + "engines": { 1316 + "node": ">=0.12.0" 1317 + } 1318 + }, 1319 + "node_modules/is-promise": { 1320 + "version": "2.2.2", 1321 + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", 1322 + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", 1323 + "dev": true 1324 + }, 1325 + "node_modules/is-regex": { 1326 + "version": "1.1.4", 1327 + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", 1328 + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", 1329 + "dev": true, 1330 + "dependencies": { 1331 + "call-bind": "^1.0.2", 1332 + "has-tostringtag": "^1.0.0" 1333 + }, 1334 + "engines": { 1335 + "node": ">= 0.4" 1336 + }, 1337 + "funding": { 1338 + "url": "https://github.com/sponsors/ljharb" 1339 + } 1340 + }, 1341 + "node_modules/isexe": { 1342 + "version": "2.0.0", 1343 + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 1344 + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", 1345 + "dev": true 1346 + }, 1347 + "node_modules/iso-639-1": { 1348 + "version": "2.1.15", 1349 + "resolved": "https://registry.npmjs.org/iso-639-1/-/iso-639-1-2.1.15.tgz", 1350 + "integrity": "sha512-7c7mBznZu2ktfvyT582E2msM+Udc1EjOyhVRE/0ZsjD9LBtWSm23h3PtiRh2a35XoUsTQQjJXaJzuLjXsOdFDg==", 1351 + "dev": true, 1352 + "engines": { 1353 + "node": ">=6.0" 1354 + } 1355 + }, 1356 + "node_modules/jake": { 1357 + "version": "10.9.1", 1358 + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.1.tgz", 1359 + "integrity": "sha512-61btcOHNnLnsOdtLgA5efqQWjnSi/vow5HbI7HMdKKWqvrKR1bLK3BPlJn9gcSaP2ewuamUSMB5XEy76KUIS2w==", 1360 + "dev": true, 1361 + "dependencies": { 1362 + "async": "^3.2.3", 1363 + "chalk": "^4.0.2", 1364 + "filelist": "^1.0.4", 1365 + "minimatch": "^3.1.2" 1366 + }, 1367 + "bin": { 1368 + "jake": "bin/cli.js" 1369 + }, 1370 + "engines": { 1371 + "node": ">=10" 1372 + } 1373 + }, 1374 + "node_modules/js-stringify": { 1375 + "version": "1.0.2", 1376 + "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz", 1377 + "integrity": "sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g==", 1378 + "dev": true 1379 + }, 1380 + "node_modules/js-yaml": { 1381 + "version": "3.14.1", 1382 + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", 1383 + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", 1384 + "dev": true, 1385 + "dependencies": { 1386 + "argparse": "^1.0.7", 1387 + "esprima": "^4.0.0" 1388 + }, 1389 + "bin": { 1390 + "js-yaml": "bin/js-yaml.js" 1391 + } 1392 + }, 1393 + "node_modules/jstransformer": { 1394 + "version": "1.0.0", 1395 + "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz", 1396 + "integrity": "sha512-C9YK3Rf8q6VAPDCCU9fnqo3mAfOH6vUGnMcP4AQAYIEpWtfGLpwOTmZ+igtdK5y+VvI2n3CyYSzy4Qh34eq24A==", 1397 + "dev": true, 1398 + "dependencies": { 1399 + "is-promise": "^2.0.0", 1400 + "promise": "^7.0.1" 1401 + } 1402 + }, 1403 + "node_modules/junk": { 1404 + "version": "1.0.3", 1405 + "resolved": "https://registry.npmjs.org/junk/-/junk-1.0.3.tgz", 1406 + "integrity": "sha512-3KF80UaaSSxo8jVnRYtMKNGFOoVPBdkkVPsw+Ad0y4oxKXPduS6G6iHkrf69yJVff/VAaYXkV42rtZ7daJxU3w==", 1407 + "dev": true, 1408 + "engines": { 1409 + "node": ">=0.10.0" 1410 + } 1411 + }, 1412 + "node_modules/kind-of": { 1413 + "version": "6.0.3", 1414 + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", 1415 + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", 1416 + "dev": true, 1417 + "engines": { 1418 + "node": ">=0.10.0" 1419 + } 1420 + }, 1421 + "node_modules/kleur": { 1422 + "version": "4.1.5", 1423 + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", 1424 + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", 1425 + "dev": true, 1426 + "engines": { 1427 + "node": ">=6" 1428 + } 1429 + }, 1430 + "node_modules/linkify-it": { 1431 + "version": "4.0.1", 1432 + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-4.0.1.tgz", 1433 + "integrity": "sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==", 1434 + "dev": true, 1435 + "dependencies": { 1436 + "uc.micro": "^1.0.1" 1437 + } 1438 + }, 1439 + "node_modules/liquidjs": { 1440 + "version": "10.15.0", 1441 + "resolved": "https://registry.npmjs.org/liquidjs/-/liquidjs-10.15.0.tgz", 1442 + "integrity": "sha512-u5lYWhW8ioT+O3FdCcp5U+hiPEGNO4xASCFlCHA+k5rMTJwDIa2c2KF111ZDKc2xGM7LXPvMoNRIrBfbLNpRBg==", 1443 + "dev": true, 1444 + "dependencies": { 1445 + "commander": "^10.0.0" 1446 + }, 1447 + "bin": { 1448 + "liquid": "bin/liquid.js", 1449 + "liquidjs": "bin/liquid.js" 1450 + }, 1451 + "engines": { 1452 + "node": ">=14" 1453 + }, 1454 + "funding": { 1455 + "type": "opencollective", 1456 + "url": "https://opencollective.com/liquidjs" 1457 + } 1458 + }, 1459 + "node_modules/list-to-array": { 1460 + "version": "1.1.0", 1461 + "resolved": "https://registry.npmjs.org/list-to-array/-/list-to-array-1.1.0.tgz", 1462 + "integrity": "sha512-+dAZZ2mM+/m+vY9ezfoueVvrgnHIGi5FvgSymbIgJOFwiznWyA59mav95L+Mc6xPtL3s9gm5eNTlNtxJLbNM1g==", 1463 + "dev": true 1464 + }, 1465 + "node_modules/lodash.deburr": { 1466 + "version": "4.1.0", 1467 + "resolved": "https://registry.npmjs.org/lodash.deburr/-/lodash.deburr-4.1.0.tgz", 1468 + "integrity": "sha512-m/M1U1f3ddMCs6Hq2tAsYThTBDaAKFDX3dwDo97GEYzamXi9SqUpjWi/Rrj/gf3X2n8ktwgZrlP1z6E3v/IExQ==", 1469 + "dev": true 1470 + }, 1471 + "node_modules/luxon": { 1472 + "version": "3.4.4", 1473 + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz", 1474 + "integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==", 1475 + "dev": true, 1476 + "engines": { 1477 + "node": ">=12" 1478 + } 1479 + }, 1480 + "node_modules/markdown-it": { 1481 + "version": "13.0.2", 1482 + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-13.0.2.tgz", 1483 + "integrity": "sha512-FtwnEuuK+2yVU7goGn/MJ0WBZMM9ZPgU9spqlFs7/A/pDIUNSOQZhUgOqYCficIuR2QaFnrt8LHqBWsbTAoI5w==", 1484 + "dev": true, 1485 + "dependencies": { 1486 + "argparse": "^2.0.1", 1487 + "entities": "~3.0.1", 1488 + "linkify-it": "^4.0.1", 1489 + "mdurl": "^1.0.1", 1490 + "uc.micro": "^1.0.5" 1491 + }, 1492 + "bin": { 1493 + "markdown-it": "bin/markdown-it.js" 1494 + } 1495 + }, 1496 + "node_modules/markdown-it/node_modules/argparse": { 1497 + "version": "2.0.1", 1498 + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", 1499 + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", 1500 + "dev": true 1501 + }, 1502 + "node_modules/maximatch": { 1503 + "version": "0.1.0", 1504 + "resolved": "https://registry.npmjs.org/maximatch/-/maximatch-0.1.0.tgz", 1505 + "integrity": "sha512-9ORVtDUFk4u/NFfo0vG/ND/z7UQCVZBL539YW0+U1I7H1BkZwizcPx5foFv7LCPcBnm2U6RjFnQOsIvN4/Vm2A==", 1506 + "dev": true, 1507 + "dependencies": { 1508 + "array-differ": "^1.0.0", 1509 + "array-union": "^1.0.1", 1510 + "arrify": "^1.0.0", 1511 + "minimatch": "^3.0.0" 1512 + }, 1513 + "engines": { 1514 + "node": ">=0.10.0" 1515 + } 1516 + }, 1517 + "node_modules/maximatch/node_modules/array-differ": { 1518 + "version": "1.0.0", 1519 + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", 1520 + "integrity": "sha512-LeZY+DZDRnvP7eMuQ6LHfCzUGxAAIViUBliK24P3hWXL6y4SortgR6Nim6xrkfSLlmH0+k+9NYNwVC2s53ZrYQ==", 1521 + "dev": true, 1522 + "engines": { 1523 + "node": ">=0.10.0" 1524 + } 1525 + }, 1526 + "node_modules/maximatch/node_modules/array-union": { 1527 + "version": "1.0.2", 1528 + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", 1529 + "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", 1530 + "dev": true, 1531 + "dependencies": { 1532 + "array-uniq": "^1.0.1" 1533 + }, 1534 + "engines": { 1535 + "node": ">=0.10.0" 1536 + } 1537 + }, 1538 + "node_modules/maximatch/node_modules/arrify": { 1539 + "version": "1.0.1", 1540 + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", 1541 + "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", 1542 + "dev": true, 1543 + "engines": { 1544 + "node": ">=0.10.0" 1545 + } 1546 + }, 1547 + "node_modules/mdurl": { 1548 + "version": "1.0.1", 1549 + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", 1550 + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", 1551 + "dev": true 1552 + }, 1553 + "node_modules/merge2": { 1554 + "version": "1.4.1", 1555 + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", 1556 + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", 1557 + "dev": true, 1558 + "engines": { 1559 + "node": ">= 8" 1560 + } 1561 + }, 1562 + "node_modules/micromatch": { 1563 + "version": "4.0.7", 1564 + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", 1565 + "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", 1566 + "dev": true, 1567 + "dependencies": { 1568 + "braces": "^3.0.3", 1569 + "picomatch": "^2.3.1" 1570 + }, 1571 + "engines": { 1572 + "node": ">=8.6" 1573 + } 1574 + }, 1575 + "node_modules/mime": { 1576 + "version": "3.0.0", 1577 + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", 1578 + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", 1579 + "dev": true, 1580 + "bin": { 1581 + "mime": "cli.js" 1582 + }, 1583 + "engines": { 1584 + "node": ">=10.0.0" 1585 + } 1586 + }, 1587 + "node_modules/minimatch": { 1588 + "version": "3.1.2", 1589 + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 1590 + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 1591 + "dev": true, 1592 + "dependencies": { 1593 + "brace-expansion": "^1.1.7" 1594 + }, 1595 + "engines": { 1596 + "node": "*" 1597 + } 1598 + }, 1599 + "node_modules/minimist": { 1600 + "version": "1.2.8", 1601 + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", 1602 + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", 1603 + "dev": true, 1604 + "funding": { 1605 + "url": "https://github.com/sponsors/ljharb" 1606 + } 1607 + }, 1608 + "node_modules/minipass": { 1609 + "version": "3.3.6", 1610 + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", 1611 + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", 1612 + "dev": true, 1613 + "dependencies": { 1614 + "yallist": "^4.0.0" 1615 + }, 1616 + "engines": { 1617 + "node": ">=8" 1618 + } 1619 + }, 1620 + "node_modules/mkdirp": { 1621 + "version": "0.5.6", 1622 + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", 1623 + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", 1624 + "dev": true, 1625 + "dependencies": { 1626 + "minimist": "^1.2.6" 1627 + }, 1628 + "bin": { 1629 + "mkdirp": "bin/cmd.js" 1630 + } 1631 + }, 1632 + "node_modules/moo": { 1633 + "version": "0.5.2", 1634 + "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.2.tgz", 1635 + "integrity": "sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==", 1636 + "dev": true 1637 + }, 1638 + "node_modules/morphdom": { 1639 + "version": "2.7.3", 1640 + "resolved": "https://registry.npmjs.org/morphdom/-/morphdom-2.7.3.tgz", 1641 + "integrity": "sha512-rvGK92GxSuPEZLY8D/JH07cG3BxyA+/F0Bxg32OoGAEFFhGWA3OqVpqPZlOgZTCR52clXrmz+z2pYSJ6gOig1w==", 1642 + "dev": true 1643 + }, 1644 + "node_modules/ms": { 1645 + "version": "2.1.2", 1646 + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 1647 + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 1648 + "dev": true 1649 + }, 1650 + "node_modules/multimatch": { 1651 + "version": "5.0.0", 1652 + "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-5.0.0.tgz", 1653 + "integrity": "sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA==", 1654 + "dev": true, 1655 + "dependencies": { 1656 + "@types/minimatch": "^3.0.3", 1657 + "array-differ": "^3.0.0", 1658 + "array-union": "^2.1.0", 1659 + "arrify": "^2.0.1", 1660 + "minimatch": "^3.0.4" 1661 + }, 1662 + "engines": { 1663 + "node": ">=10" 1664 + }, 1665 + "funding": { 1666 + "url": "https://github.com/sponsors/sindresorhus" 1667 + } 1668 + }, 1669 + "node_modules/mustache": { 1670 + "version": "4.2.0", 1671 + "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", 1672 + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", 1673 + "dev": true, 1674 + "bin": { 1675 + "mustache": "bin/mustache" 1676 + } 1677 + }, 1678 + "node_modules/neo-async": { 1679 + "version": "2.6.2", 1680 + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", 1681 + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", 1682 + "dev": true 1683 + }, 1684 + "node_modules/normalize-path": { 1685 + "version": "3.0.0", 1686 + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", 1687 + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", 1688 + "dev": true, 1689 + "engines": { 1690 + "node": ">=0.10.0" 1691 + } 1692 + }, 1693 + "node_modules/nunjucks": { 1694 + "version": "3.2.4", 1695 + "resolved": "https://registry.npmjs.org/nunjucks/-/nunjucks-3.2.4.tgz", 1696 + "integrity": "sha512-26XRV6BhkgK0VOxfbU5cQI+ICFUtMLixv1noZn1tGU38kQH5A5nmmbk/O45xdyBhD1esk47nKrY0mvQpZIhRjQ==", 1697 + "dev": true, 1698 + "dependencies": { 1699 + "a-sync-waterfall": "^1.0.0", 1700 + "asap": "^2.0.3", 1701 + "commander": "^5.1.0" 1702 + }, 1703 + "bin": { 1704 + "nunjucks-precompile": "bin/precompile" 1705 + }, 1706 + "engines": { 1707 + "node": ">= 6.9.0" 1708 + }, 1709 + "peerDependencies": { 1710 + "chokidar": "^3.3.0" 1711 + }, 1712 + "peerDependenciesMeta": { 1713 + "chokidar": { 1714 + "optional": true 1715 + } 1716 + } 1717 + }, 1718 + "node_modules/nunjucks/node_modules/commander": { 1719 + "version": "5.1.0", 1720 + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", 1721 + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", 1722 + "dev": true, 1723 + "engines": { 1724 + "node": ">= 6" 1725 + } 1726 + }, 1727 + "node_modules/object-assign": { 1728 + "version": "4.1.1", 1729 + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 1730 + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", 1731 + "dev": true, 1732 + "engines": { 1733 + "node": ">=0.10.0" 1734 + } 1735 + }, 1736 + "node_modules/on-finished": { 1737 + "version": "2.4.1", 1738 + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", 1739 + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", 1740 + "dev": true, 1741 + "dependencies": { 1742 + "ee-first": "1.1.1" 1743 + }, 1744 + "engines": { 1745 + "node": ">= 0.8" 1746 + } 1747 + }, 1748 + "node_modules/once": { 1749 + "version": "1.4.0", 1750 + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1751 + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 1752 + "dev": true, 1753 + "dependencies": { 1754 + "wrappy": "1" 1755 + } 1756 + }, 1757 + "node_modules/parse-srcset": { 1758 + "version": "1.0.2", 1759 + "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz", 1760 + "integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==", 1761 + "dev": true 1762 + }, 1763 + "node_modules/parseurl": { 1764 + "version": "1.3.3", 1765 + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 1766 + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", 1767 + "dev": true, 1768 + "engines": { 1769 + "node": ">= 0.8" 1770 + } 1771 + }, 1772 + "node_modules/path-is-absolute": { 1773 + "version": "1.0.1", 1774 + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 1775 + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", 1776 + "dev": true, 1777 + "engines": { 1778 + "node": ">=0.10.0" 1779 + } 1780 + }, 1781 + "node_modules/path-key": { 1782 + "version": "3.1.1", 1783 + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 1784 + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 1785 + "dev": true, 1786 + "engines": { 1787 + "node": ">=8" 1788 + } 1789 + }, 1790 + "node_modules/path-parse": { 1791 + "version": "1.0.7", 1792 + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", 1793 + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", 1794 + "dev": true 1795 + }, 1796 + "node_modules/path-to-regexp": { 1797 + "version": "6.2.2", 1798 + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.2.tgz", 1799 + "integrity": "sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==", 1800 + "dev": true 1801 + }, 1802 + "node_modules/picomatch": { 1803 + "version": "2.3.1", 1804 + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 1805 + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 1806 + "dev": true, 1807 + "engines": { 1808 + "node": ">=8.6" 1809 + }, 1810 + "funding": { 1811 + "url": "https://github.com/sponsors/jonschlinkert" 1812 + } 1813 + }, 1814 + "node_modules/pify": { 1815 + "version": "2.3.0", 1816 + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", 1817 + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", 1818 + "dev": true, 1819 + "engines": { 1820 + "node": ">=0.10.0" 1821 + } 1822 + }, 1823 + "node_modules/please-upgrade-node": { 1824 + "version": "3.2.0", 1825 + "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz", 1826 + "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==", 1827 + "dev": true, 1828 + "dependencies": { 1829 + "semver-compare": "^1.0.0" 1830 + } 1831 + }, 1832 + "node_modules/posthtml": { 1833 + "version": "0.16.6", 1834 + "resolved": "https://registry.npmjs.org/posthtml/-/posthtml-0.16.6.tgz", 1835 + "integrity": "sha512-JcEmHlyLK/o0uGAlj65vgg+7LIms0xKXe60lcDOTU7oVX/3LuEuLwrQpW3VJ7de5TaFKiW4kWkaIpJL42FEgxQ==", 1836 + "dev": true, 1837 + "dependencies": { 1838 + "posthtml-parser": "^0.11.0", 1839 + "posthtml-render": "^3.0.0" 1840 + }, 1841 + "engines": { 1842 + "node": ">=12.0.0" 1843 + } 1844 + }, 1845 + "node_modules/posthtml-parser": { 1846 + "version": "0.11.0", 1847 + "resolved": "https://registry.npmjs.org/posthtml-parser/-/posthtml-parser-0.11.0.tgz", 1848 + "integrity": "sha512-QecJtfLekJbWVo/dMAA+OSwY79wpRmbqS5TeXvXSX+f0c6pW4/SE6inzZ2qkU7oAMCPqIDkZDvd/bQsSFUnKyw==", 1849 + "dev": true, 1850 + "dependencies": { 1851 + "htmlparser2": "^7.1.1" 1852 + }, 1853 + "engines": { 1854 + "node": ">=12" 1855 + } 1856 + }, 1857 + "node_modules/posthtml-render": { 1858 + "version": "3.0.0", 1859 + "resolved": "https://registry.npmjs.org/posthtml-render/-/posthtml-render-3.0.0.tgz", 1860 + "integrity": "sha512-z+16RoxK3fUPgwaIgH9NGnK1HKY9XIDpydky5eQGgAFVXTCSezalv9U2jQuNV+Z9qV1fDWNzldcw4eK0SSbqKA==", 1861 + "dev": true, 1862 + "dependencies": { 1863 + "is-json": "^2.0.1" 1864 + }, 1865 + "engines": { 1866 + "node": ">=12" 1867 + } 1868 + }, 1869 + "node_modules/posthtml-urls": { 1870 + "version": "1.0.0", 1871 + "resolved": "https://registry.npmjs.org/posthtml-urls/-/posthtml-urls-1.0.0.tgz", 1872 + "integrity": "sha512-CMJ0L009sGQVUuYM/g6WJdscsq6ooAwhUuF6CDlYPMLxKp2rmCYVebEU+wZGxnQstGJhZPMvXsRhtqekILd5/w==", 1873 + "dev": true, 1874 + "dependencies": { 1875 + "http-equiv-refresh": "^1.0.0", 1876 + "list-to-array": "^1.1.0", 1877 + "parse-srcset": "^1.0.2", 1878 + "promise-each": "^2.2.0" 1879 + }, 1880 + "engines": { 1881 + "node": ">= 4" 1882 + } 1883 + }, 1884 + "node_modules/promise": { 1885 + "version": "7.3.1", 1886 + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", 1887 + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", 1888 + "dev": true, 1889 + "dependencies": { 1890 + "asap": "~2.0.3" 1891 + } 1892 + }, 1893 + "node_modules/promise-each": { 1894 + "version": "2.2.0", 1895 + "resolved": "https://registry.npmjs.org/promise-each/-/promise-each-2.2.0.tgz", 1896 + "integrity": "sha512-67roqt1k3QDA41DZ8xi0V+rF3GoaMiX7QilbXu0vXimut+9RcKBNZ/t60xCRgcsihmNUsEjh48xLfNqOrKblUg==", 1897 + "dev": true, 1898 + "dependencies": { 1899 + "any-promise": "^0.1.0" 1900 + } 1901 + }, 1902 + "node_modules/prr": { 1903 + "version": "1.0.1", 1904 + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", 1905 + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", 1906 + "dev": true 1907 + }, 1908 + "node_modules/pug": { 1909 + "version": "3.0.3", 1910 + "resolved": "https://registry.npmjs.org/pug/-/pug-3.0.3.tgz", 1911 + "integrity": "sha512-uBi6kmc9f3SZ3PXxqcHiUZLmIXgfgWooKWXcwSGwQd2Zi5Rb0bT14+8CJjJgI8AB+nndLaNgHGrcc6bPIB665g==", 1912 + "dev": true, 1913 + "dependencies": { 1914 + "pug-code-gen": "^3.0.3", 1915 + "pug-filters": "^4.0.0", 1916 + "pug-lexer": "^5.0.1", 1917 + "pug-linker": "^4.0.0", 1918 + "pug-load": "^3.0.0", 1919 + "pug-parser": "^6.0.0", 1920 + "pug-runtime": "^3.0.1", 1921 + "pug-strip-comments": "^2.0.0" 1922 + } 1923 + }, 1924 + "node_modules/pug-attrs": { 1925 + "version": "3.0.0", 1926 + "resolved": "https://registry.npmjs.org/pug-attrs/-/pug-attrs-3.0.0.tgz", 1927 + "integrity": "sha512-azINV9dUtzPMFQktvTXciNAfAuVh/L/JCl0vtPCwvOA21uZrC08K/UnmrL+SXGEVc1FwzjW62+xw5S/uaLj6cA==", 1928 + "dev": true, 1929 + "dependencies": { 1930 + "constantinople": "^4.0.1", 1931 + "js-stringify": "^1.0.2", 1932 + "pug-runtime": "^3.0.0" 1933 + } 1934 + }, 1935 + "node_modules/pug-code-gen": { 1936 + "version": "3.0.3", 1937 + "resolved": "https://registry.npmjs.org/pug-code-gen/-/pug-code-gen-3.0.3.tgz", 1938 + "integrity": "sha512-cYQg0JW0w32Ux+XTeZnBEeuWrAY7/HNE6TWnhiHGnnRYlCgyAUPoyh9KzCMa9WhcJlJ1AtQqpEYHc+vbCzA+Aw==", 1939 + "dev": true, 1940 + "dependencies": { 1941 + "constantinople": "^4.0.1", 1942 + "doctypes": "^1.1.0", 1943 + "js-stringify": "^1.0.2", 1944 + "pug-attrs": "^3.0.0", 1945 + "pug-error": "^2.1.0", 1946 + "pug-runtime": "^3.0.1", 1947 + "void-elements": "^3.1.0", 1948 + "with": "^7.0.0" 1949 + } 1950 + }, 1951 + "node_modules/pug-error": { 1952 + "version": "2.1.0", 1953 + "resolved": "https://registry.npmjs.org/pug-error/-/pug-error-2.1.0.tgz", 1954 + "integrity": "sha512-lv7sU9e5Jk8IeUheHata6/UThZ7RK2jnaaNztxfPYUY+VxZyk/ePVaNZ/vwmH8WqGvDz3LrNYt/+gA55NDg6Pg==", 1955 + "dev": true 1956 + }, 1957 + "node_modules/pug-filters": { 1958 + "version": "4.0.0", 1959 + "resolved": "https://registry.npmjs.org/pug-filters/-/pug-filters-4.0.0.tgz", 1960 + "integrity": "sha512-yeNFtq5Yxmfz0f9z2rMXGw/8/4i1cCFecw/Q7+D0V2DdtII5UvqE12VaZ2AY7ri6o5RNXiweGH79OCq+2RQU4A==", 1961 + "dev": true, 1962 + "dependencies": { 1963 + "constantinople": "^4.0.1", 1964 + "jstransformer": "1.0.0", 1965 + "pug-error": "^2.0.0", 1966 + "pug-walk": "^2.0.0", 1967 + "resolve": "^1.15.1" 1968 + } 1969 + }, 1970 + "node_modules/pug-lexer": { 1971 + "version": "5.0.1", 1972 + "resolved": "https://registry.npmjs.org/pug-lexer/-/pug-lexer-5.0.1.tgz", 1973 + "integrity": "sha512-0I6C62+keXlZPZkOJeVam9aBLVP2EnbeDw3An+k0/QlqdwH6rv8284nko14Na7c0TtqtogfWXcRoFE4O4Ff20w==", 1974 + "dev": true, 1975 + "dependencies": { 1976 + "character-parser": "^2.2.0", 1977 + "is-expression": "^4.0.0", 1978 + "pug-error": "^2.0.0" 1979 + } 1980 + }, 1981 + "node_modules/pug-linker": { 1982 + "version": "4.0.0", 1983 + "resolved": "https://registry.npmjs.org/pug-linker/-/pug-linker-4.0.0.tgz", 1984 + "integrity": "sha512-gjD1yzp0yxbQqnzBAdlhbgoJL5qIFJw78juN1NpTLt/mfPJ5VgC4BvkoD3G23qKzJtIIXBbcCt6FioLSFLOHdw==", 1985 + "dev": true, 1986 + "dependencies": { 1987 + "pug-error": "^2.0.0", 1988 + "pug-walk": "^2.0.0" 1989 + } 1990 + }, 1991 + "node_modules/pug-load": { 1992 + "version": "3.0.0", 1993 + "resolved": "https://registry.npmjs.org/pug-load/-/pug-load-3.0.0.tgz", 1994 + "integrity": "sha512-OCjTEnhLWZBvS4zni/WUMjH2YSUosnsmjGBB1An7CsKQarYSWQ0GCVyd4eQPMFJqZ8w9xgs01QdiZXKVjk92EQ==", 1995 + "dev": true, 1996 + "dependencies": { 1997 + "object-assign": "^4.1.1", 1998 + "pug-walk": "^2.0.0" 1999 + } 2000 + }, 2001 + "node_modules/pug-parser": { 2002 + "version": "6.0.0", 2003 + "resolved": "https://registry.npmjs.org/pug-parser/-/pug-parser-6.0.0.tgz", 2004 + "integrity": "sha512-ukiYM/9cH6Cml+AOl5kETtM9NR3WulyVP2y4HOU45DyMim1IeP/OOiyEWRr6qk5I5klpsBnbuHpwKmTx6WURnw==", 2005 + "dev": true, 2006 + "dependencies": { 2007 + "pug-error": "^2.0.0", 2008 + "token-stream": "1.0.0" 2009 + } 2010 + }, 2011 + "node_modules/pug-runtime": { 2012 + "version": "3.0.1", 2013 + "resolved": "https://registry.npmjs.org/pug-runtime/-/pug-runtime-3.0.1.tgz", 2014 + "integrity": "sha512-L50zbvrQ35TkpHwv0G6aLSuueDRwc/97XdY8kL3tOT0FmhgG7UypU3VztfV/LATAvmUfYi4wNxSajhSAeNN+Kg==", 2015 + "dev": true 2016 + }, 2017 + "node_modules/pug-strip-comments": { 2018 + "version": "2.0.0", 2019 + "resolved": "https://registry.npmjs.org/pug-strip-comments/-/pug-strip-comments-2.0.0.tgz", 2020 + "integrity": "sha512-zo8DsDpH7eTkPHCXFeAk1xZXJbyoTfdPlNR0bK7rpOMuhBYb0f5qUVCO1xlsitYd3w5FQTK7zpNVKb3rZoUrrQ==", 2021 + "dev": true, 2022 + "dependencies": { 2023 + "pug-error": "^2.0.0" 2024 + } 2025 + }, 2026 + "node_modules/pug-walk": { 2027 + "version": "2.0.0", 2028 + "resolved": "https://registry.npmjs.org/pug-walk/-/pug-walk-2.0.0.tgz", 2029 + "integrity": "sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ==", 2030 + "dev": true 2031 + }, 2032 + "node_modules/queue-microtask": { 2033 + "version": "1.2.3", 2034 + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", 2035 + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", 2036 + "dev": true, 2037 + "funding": [ 2038 + { 2039 + "type": "github", 2040 + "url": "https://github.com/sponsors/feross" 2041 + }, 2042 + { 2043 + "type": "patreon", 2044 + "url": "https://www.patreon.com/feross" 2045 + }, 2046 + { 2047 + "type": "consulting", 2048 + "url": "https://feross.org/support" 2049 + } 2050 + ] 2051 + }, 2052 + "node_modules/readdirp": { 2053 + "version": "3.6.0", 2054 + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", 2055 + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", 2056 + "dev": true, 2057 + "dependencies": { 2058 + "picomatch": "^2.2.1" 2059 + }, 2060 + "engines": { 2061 + "node": ">=8.10.0" 2062 + } 2063 + }, 2064 + "node_modules/recursive-copy": { 2065 + "version": "2.0.14", 2066 + "resolved": "https://registry.npmjs.org/recursive-copy/-/recursive-copy-2.0.14.tgz", 2067 + "integrity": "sha512-K8WNY8f8naTpfbA+RaXmkaQuD1IeW9EgNEfyGxSqqTQukpVtoOKros9jUqbpEsSw59YOmpd8nCBgtqJZy5nvog==", 2068 + "dev": true, 2069 + "dependencies": { 2070 + "errno": "^0.1.2", 2071 + "graceful-fs": "^4.1.4", 2072 + "junk": "^1.0.1", 2073 + "maximatch": "^0.1.0", 2074 + "mkdirp": "^0.5.1", 2075 + "pify": "^2.3.0", 2076 + "promise": "^7.0.1", 2077 + "rimraf": "^2.7.1", 2078 + "slash": "^1.0.0" 2079 + } 2080 + }, 2081 + "node_modules/resolve": { 2082 + "version": "1.22.8", 2083 + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", 2084 + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", 2085 + "dev": true, 2086 + "dependencies": { 2087 + "is-core-module": "^2.13.0", 2088 + "path-parse": "^1.0.7", 2089 + "supports-preserve-symlinks-flag": "^1.0.0" 2090 + }, 2091 + "bin": { 2092 + "resolve": "bin/resolve" 2093 + }, 2094 + "funding": { 2095 + "url": "https://github.com/sponsors/ljharb" 2096 + } 2097 + }, 2098 + "node_modules/reusify": { 2099 + "version": "1.0.4", 2100 + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", 2101 + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", 2102 + "dev": true, 2103 + "engines": { 2104 + "iojs": ">=1.0.0", 2105 + "node": ">=0.10.0" 2106 + } 2107 + }, 2108 + "node_modules/rimraf": { 2109 + "version": "2.7.1", 2110 + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", 2111 + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", 2112 + "deprecated": "Rimraf versions prior to v4 are no longer supported", 2113 + "dev": true, 2114 + "dependencies": { 2115 + "glob": "^7.1.3" 2116 + }, 2117 + "bin": { 2118 + "rimraf": "bin.js" 2119 + } 2120 + }, 2121 + "node_modules/run-parallel": { 2122 + "version": "1.2.0", 2123 + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", 2124 + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", 2125 + "dev": true, 2126 + "funding": [ 2127 + { 2128 + "type": "github", 2129 + "url": "https://github.com/sponsors/feross" 2130 + }, 2131 + { 2132 + "type": "patreon", 2133 + "url": "https://www.patreon.com/feross" 2134 + }, 2135 + { 2136 + "type": "consulting", 2137 + "url": "https://feross.org/support" 2138 + } 2139 + ], 2140 + "dependencies": { 2141 + "queue-microtask": "^1.2.2" 2142 + } 2143 + }, 2144 + "node_modules/section-matter": { 2145 + "version": "1.0.0", 2146 + "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", 2147 + "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", 2148 + "dev": true, 2149 + "dependencies": { 2150 + "extend-shallow": "^2.0.1", 2151 + "kind-of": "^6.0.0" 2152 + }, 2153 + "engines": { 2154 + "node": ">=4" 2155 + } 2156 + }, 2157 + "node_modules/semver": { 2158 + "version": "7.6.2", 2159 + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", 2160 + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", 2161 + "dev": true, 2162 + "bin": { 2163 + "semver": "bin/semver.js" 2164 + }, 2165 + "engines": { 2166 + "node": ">=10" 2167 + } 2168 + }, 2169 + "node_modules/semver-compare": { 2170 + "version": "1.0.0", 2171 + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", 2172 + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", 2173 + "dev": true 2174 + }, 2175 + "node_modules/set-function-length": { 2176 + "version": "1.2.2", 2177 + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", 2178 + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", 2179 + "dev": true, 2180 + "dependencies": { 2181 + "define-data-property": "^1.1.4", 2182 + "es-errors": "^1.3.0", 2183 + "function-bind": "^1.1.2", 2184 + "get-intrinsic": "^1.2.4", 2185 + "gopd": "^1.0.1", 2186 + "has-property-descriptors": "^1.0.2" 2187 + }, 2188 + "engines": { 2189 + "node": ">= 0.4" 2190 + } 2191 + }, 2192 + "node_modules/shebang-command": { 2193 + "version": "2.0.0", 2194 + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 2195 + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 2196 + "dev": true, 2197 + "dependencies": { 2198 + "shebang-regex": "^3.0.0" 2199 + }, 2200 + "engines": { 2201 + "node": ">=8" 2202 + } 2203 + }, 2204 + "node_modules/shebang-regex": { 2205 + "version": "3.0.0", 2206 + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 2207 + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 2208 + "dev": true, 2209 + "engines": { 2210 + "node": ">=8" 2211 + } 2212 + }, 2213 + "node_modules/slash": { 2214 + "version": "1.0.0", 2215 + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", 2216 + "integrity": "sha512-3TYDR7xWt4dIqV2JauJr+EJeW356RXijHeUlO+8djJ+uBXPn8/2dpzBc8yQhh583sVvc9CvFAeQVgijsH+PNNg==", 2217 + "dev": true, 2218 + "engines": { 2219 + "node": ">=0.10.0" 2220 + } 2221 + }, 2222 + "node_modules/slugify": { 2223 + "version": "1.6.6", 2224 + "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.6.tgz", 2225 + "integrity": "sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==", 2226 + "dev": true, 2227 + "engines": { 2228 + "node": ">=8.0.0" 2229 + } 2230 + }, 2231 + "node_modules/source-map": { 2232 + "version": "0.6.1", 2233 + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 2234 + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", 2235 + "dev": true, 2236 + "engines": { 2237 + "node": ">=0.10.0" 2238 + } 2239 + }, 2240 + "node_modules/sprintf-js": { 2241 + "version": "1.0.3", 2242 + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", 2243 + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", 2244 + "dev": true 2245 + }, 2246 + "node_modules/ssri": { 2247 + "version": "8.0.1", 2248 + "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", 2249 + "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", 2250 + "dev": true, 2251 + "dependencies": { 2252 + "minipass": "^3.1.1" 2253 + }, 2254 + "engines": { 2255 + "node": ">= 8" 2256 + } 2257 + }, 2258 + "node_modules/statuses": { 2259 + "version": "2.0.1", 2260 + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 2261 + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", 2262 + "dev": true, 2263 + "engines": { 2264 + "node": ">= 0.8" 2265 + } 2266 + }, 2267 + "node_modules/strip-bom-string": { 2268 + "version": "1.0.0", 2269 + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", 2270 + "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==", 2271 + "dev": true, 2272 + "engines": { 2273 + "node": ">=0.10.0" 2274 + } 2275 + }, 2276 + "node_modules/supports-color": { 2277 + "version": "7.2.0", 2278 + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 2279 + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 2280 + "dev": true, 2281 + "dependencies": { 2282 + "has-flag": "^4.0.0" 2283 + }, 2284 + "engines": { 2285 + "node": ">=8" 2286 + } 2287 + }, 2288 + "node_modules/supports-preserve-symlinks-flag": { 2289 + "version": "1.0.0", 2290 + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", 2291 + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", 2292 + "dev": true, 2293 + "engines": { 2294 + "node": ">= 0.4" 2295 + }, 2296 + "funding": { 2297 + "url": "https://github.com/sponsors/ljharb" 2298 + } 2299 + }, 2300 + "node_modules/to-fast-properties": { 2301 + "version": "2.0.0", 2302 + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", 2303 + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", 2304 + "dev": true, 2305 + "engines": { 2306 + "node": ">=4" 2307 + } 2308 + }, 2309 + "node_modules/to-regex-range": { 2310 + "version": "5.0.1", 2311 + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 2312 + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 2313 + "dev": true, 2314 + "dependencies": { 2315 + "is-number": "^7.0.0" 2316 + }, 2317 + "engines": { 2318 + "node": ">=8.0" 2319 + } 2320 + }, 2321 + "node_modules/token-stream": { 2322 + "version": "1.0.0", 2323 + "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-1.0.0.tgz", 2324 + "integrity": "sha512-VSsyNPPW74RpHwR8Fc21uubwHY7wMDeJLys2IX5zJNih+OnAnaifKHo+1LHT7DAdloQ7apeaaWg8l7qnf/TnEg==", 2325 + "dev": true 2326 + }, 2327 + "node_modules/uc.micro": { 2328 + "version": "1.0.6", 2329 + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", 2330 + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", 2331 + "dev": true 2332 + }, 2333 + "node_modules/uglify-js": { 2334 + "version": "3.18.0", 2335 + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.18.0.tgz", 2336 + "integrity": "sha512-SyVVbcNBCk0dzr9XL/R/ySrmYf0s372K6/hFklzgcp2lBFyXtw4I7BOdDjlLhE1aVqaI/SHWXWmYdlZxuyF38A==", 2337 + "dev": true, 2338 + "optional": true, 2339 + "bin": { 2340 + "uglifyjs": "bin/uglifyjs" 2341 + }, 2342 + "engines": { 2343 + "node": ">=0.8.0" 2344 + } 2345 + }, 2346 + "node_modules/unpipe": { 2347 + "version": "1.0.0", 2348 + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 2349 + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", 2350 + "dev": true, 2351 + "engines": { 2352 + "node": ">= 0.8" 2353 + } 2354 + }, 2355 + "node_modules/void-elements": { 2356 + "version": "3.1.0", 2357 + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", 2358 + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", 2359 + "dev": true, 2360 + "engines": { 2361 + "node": ">=0.10.0" 2362 + } 2363 + }, 2364 + "node_modules/which": { 2365 + "version": "2.0.2", 2366 + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 2367 + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 2368 + "dev": true, 2369 + "dependencies": { 2370 + "isexe": "^2.0.0" 2371 + }, 2372 + "bin": { 2373 + "node-which": "bin/node-which" 2374 + }, 2375 + "engines": { 2376 + "node": ">= 8" 2377 + } 2378 + }, 2379 + "node_modules/with": { 2380 + "version": "7.0.2", 2381 + "resolved": "https://registry.npmjs.org/with/-/with-7.0.2.tgz", 2382 + "integrity": "sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w==", 2383 + "dev": true, 2384 + "dependencies": { 2385 + "@babel/parser": "^7.9.6", 2386 + "@babel/types": "^7.9.6", 2387 + "assert-never": "^1.2.1", 2388 + "babel-walk": "3.0.0-canary-5" 2389 + }, 2390 + "engines": { 2391 + "node": ">= 10.0.0" 2392 + } 2393 + }, 2394 + "node_modules/wordwrap": { 2395 + "version": "1.0.0", 2396 + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", 2397 + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", 2398 + "dev": true 2399 + }, 2400 + "node_modules/wrappy": { 2401 + "version": "1.0.2", 2402 + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 2403 + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", 2404 + "dev": true 2405 + }, 2406 + "node_modules/ws": { 2407 + "version": "8.18.0", 2408 + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", 2409 + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", 2410 + "dev": true, 2411 + "engines": { 2412 + "node": ">=10.0.0" 2413 + }, 2414 + "peerDependencies": { 2415 + "bufferutil": "^4.0.1", 2416 + "utf-8-validate": ">=5.0.2" 2417 + }, 2418 + "peerDependenciesMeta": { 2419 + "bufferutil": { 2420 + "optional": true 2421 + }, 2422 + "utf-8-validate": { 2423 + "optional": true 2424 + } 2425 + } 2426 + }, 2427 + "node_modules/yallist": { 2428 + "version": "4.0.0", 2429 + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 2430 + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", 2431 + "dev": true 2432 + } 2433 + } 2434 + }
+8
docs/package.json
··· 1 + { 2 + "devDependencies": { 3 + "@11ty/eleventy": "^2.0.1" 4 + }, 5 + "dependencies": { 6 + "@fontsource-variable/roboto-serif": "^5.0.14" 7 + } 8 + }
+24
neo/.gitignore
··· 1 + # Logs 2 + logs 3 + *.log 4 + npm-debug.log* 5 + yarn-debug.log* 6 + yarn-error.log* 7 + pnpm-debug.log* 8 + lerna-debug.log* 9 + 10 + node_modules 11 + dist 12 + dist-ssr 13 + *.local 14 + 15 + # Editor directories and files 16 + .vscode/* 17 + !.vscode/extensions.json 18 + .idea 19 + .DS_Store 20 + *.suo 21 + *.ntvs* 22 + *.njsproj 23 + *.sln 24 + *.sw?
+13
neo/index.html
··· 1 + <!doctype html> 2 + <html lang="en"> 3 + <head> 4 + <meta charset="UTF-8" /> 5 + <link rel="icon" type="image/svg+xml" href="/vite.svg" /> 6 + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 7 + <title>Tubes</title> 8 + </head> 9 + <body> 10 + <div id="app"></div> 11 + <script type="module" src="/src/main.tsx"></script> 12 + </body> 13 + </html>
+3098
neo/package-lock.json
··· 1 + { 2 + "name": "tubes", 3 + "version": "0.0.0", 4 + "lockfileVersion": 3, 5 + "requires": true, 6 + "packages": { 7 + "": { 8 + "name": "tubes", 9 + "version": "0.0.0", 10 + "dependencies": { 11 + "@fontsource-variable/roboto-serif": "^5.0.14", 12 + "@preact/signals": "^1.3.0", 13 + "async-mutex": "^0.5.0", 14 + "dexie": "^4.0.8", 15 + "motion": "^10.18.0", 16 + "preact": "^10.23.1", 17 + "tubes_core": "file:../core", 18 + "wouter-preact": "^3.3.1" 19 + }, 20 + "devDependencies": { 21 + "@iconify-json/ph": "^1.1.13", 22 + "@preact/preset-vite": "^2.9.0", 23 + "@svgr/core": "^8.1.0", 24 + "@svgr/plugin-jsx": "^8.1.0", 25 + "@types/node": "^20.14.13", 26 + "typescript": "^5.5.4", 27 + "unplugin-icons": "^0.19.1", 28 + "vite": "^5.3.5" 29 + } 30 + }, 31 + "../core": { 32 + "name": "tubes_core", 33 + "version": "0.0.9", 34 + "license": "ISC", 35 + "dependencies": { 36 + "@preact/signals": "^1.3.0", 37 + "async-mutex": "^0.5.0", 38 + "isomorphic-ws": "^5.0.0", 39 + "nanoid": "^5.0.7", 40 + "ws": "^8.18.0" 41 + }, 42 + "devDependencies": { 43 + "@types/ws": "^8.5.12", 44 + "happy-dom": "14.12.3", 45 + "typescript": "^5.5.4", 46 + "vite": "^5.3.5", 47 + "vitest": "^2.0.4" 48 + } 49 + }, 50 + "node_modules/@ampproject/remapping": { 51 + "version": "2.3.0", 52 + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", 53 + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", 54 + "dev": true, 55 + "dependencies": { 56 + "@jridgewell/gen-mapping": "^0.3.5", 57 + "@jridgewell/trace-mapping": "^0.3.24" 58 + }, 59 + "engines": { 60 + "node": ">=6.0.0" 61 + } 62 + }, 63 + "node_modules/@antfu/install-pkg": { 64 + "version": "0.3.3", 65 + "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-0.3.3.tgz", 66 + "integrity": "sha512-nHHsk3NXQ6xkCfiRRC8Nfrg8pU5kkr3P3Y9s9dKqiuRmBD0Yap7fymNDjGFKeWhZQHqqbCS5CfeMy9wtExM24w==", 67 + "dev": true, 68 + "dependencies": { 69 + "@jsdevtools/ez-spawn": "^3.0.4" 70 + }, 71 + "funding": { 72 + "url": "https://github.com/sponsors/antfu" 73 + } 74 + }, 75 + "node_modules/@antfu/utils": { 76 + "version": "0.7.10", 77 + "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-0.7.10.tgz", 78 + "integrity": "sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==", 79 + "dev": true, 80 + "funding": { 81 + "url": "https://github.com/sponsors/antfu" 82 + } 83 + }, 84 + "node_modules/@babel/code-frame": { 85 + "version": "7.24.7", 86 + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", 87 + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", 88 + "dev": true, 89 + "dependencies": { 90 + "@babel/highlight": "^7.24.7", 91 + "picocolors": "^1.0.0" 92 + }, 93 + "engines": { 94 + "node": ">=6.9.0" 95 + } 96 + }, 97 + "node_modules/@babel/compat-data": { 98 + "version": "7.25.2", 99 + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.2.tgz", 100 + "integrity": "sha512-bYcppcpKBvX4znYaPEeFau03bp89ShqNMLs+rmdptMw+heSZh9+z84d2YG+K7cYLbWwzdjtDoW/uqZmPjulClQ==", 101 + "dev": true, 102 + "engines": { 103 + "node": ">=6.9.0" 104 + } 105 + }, 106 + "node_modules/@babel/core": { 107 + "version": "7.25.2", 108 + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", 109 + "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", 110 + "dev": true, 111 + "dependencies": { 112 + "@ampproject/remapping": "^2.2.0", 113 + "@babel/code-frame": "^7.24.7", 114 + "@babel/generator": "^7.25.0", 115 + "@babel/helper-compilation-targets": "^7.25.2", 116 + "@babel/helper-module-transforms": "^7.25.2", 117 + "@babel/helpers": "^7.25.0", 118 + "@babel/parser": "^7.25.0", 119 + "@babel/template": "^7.25.0", 120 + "@babel/traverse": "^7.25.2", 121 + "@babel/types": "^7.25.2", 122 + "convert-source-map": "^2.0.0", 123 + "debug": "^4.1.0", 124 + "gensync": "^1.0.0-beta.2", 125 + "json5": "^2.2.3", 126 + "semver": "^6.3.1" 127 + }, 128 + "engines": { 129 + "node": ">=6.9.0" 130 + }, 131 + "funding": { 132 + "type": "opencollective", 133 + "url": "https://opencollective.com/babel" 134 + } 135 + }, 136 + "node_modules/@babel/generator": { 137 + "version": "7.25.0", 138 + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.0.tgz", 139 + "integrity": "sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw==", 140 + "dev": true, 141 + "dependencies": { 142 + "@babel/types": "^7.25.0", 143 + "@jridgewell/gen-mapping": "^0.3.5", 144 + "@jridgewell/trace-mapping": "^0.3.25", 145 + "jsesc": "^2.5.1" 146 + }, 147 + "engines": { 148 + "node": ">=6.9.0" 149 + } 150 + }, 151 + "node_modules/@babel/helper-annotate-as-pure": { 152 + "version": "7.24.7", 153 + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz", 154 + "integrity": "sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==", 155 + "dev": true, 156 + "dependencies": { 157 + "@babel/types": "^7.24.7" 158 + }, 159 + "engines": { 160 + "node": ">=6.9.0" 161 + } 162 + }, 163 + "node_modules/@babel/helper-compilation-targets": { 164 + "version": "7.25.2", 165 + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", 166 + "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==", 167 + "dev": true, 168 + "dependencies": { 169 + "@babel/compat-data": "^7.25.2", 170 + "@babel/helper-validator-option": "^7.24.8", 171 + "browserslist": "^4.23.1", 172 + "lru-cache": "^5.1.1", 173 + "semver": "^6.3.1" 174 + }, 175 + "engines": { 176 + "node": ">=6.9.0" 177 + } 178 + }, 179 + "node_modules/@babel/helper-module-imports": { 180 + "version": "7.24.7", 181 + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", 182 + "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", 183 + "dev": true, 184 + "dependencies": { 185 + "@babel/traverse": "^7.24.7", 186 + "@babel/types": "^7.24.7" 187 + }, 188 + "engines": { 189 + "node": ">=6.9.0" 190 + } 191 + }, 192 + "node_modules/@babel/helper-module-transforms": { 193 + "version": "7.25.2", 194 + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz", 195 + "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==", 196 + "dev": true, 197 + "dependencies": { 198 + "@babel/helper-module-imports": "^7.24.7", 199 + "@babel/helper-simple-access": "^7.24.7", 200 + "@babel/helper-validator-identifier": "^7.24.7", 201 + "@babel/traverse": "^7.25.2" 202 + }, 203 + "engines": { 204 + "node": ">=6.9.0" 205 + }, 206 + "peerDependencies": { 207 + "@babel/core": "^7.0.0" 208 + } 209 + }, 210 + "node_modules/@babel/helper-plugin-utils": { 211 + "version": "7.24.8", 212 + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", 213 + "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", 214 + "dev": true, 215 + "engines": { 216 + "node": ">=6.9.0" 217 + } 218 + }, 219 + "node_modules/@babel/helper-simple-access": { 220 + "version": "7.24.7", 221 + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", 222 + "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", 223 + "dev": true, 224 + "dependencies": { 225 + "@babel/traverse": "^7.24.7", 226 + "@babel/types": "^7.24.7" 227 + }, 228 + "engines": { 229 + "node": ">=6.9.0" 230 + } 231 + }, 232 + "node_modules/@babel/helper-string-parser": { 233 + "version": "7.24.8", 234 + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", 235 + "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", 236 + "dev": true, 237 + "engines": { 238 + "node": ">=6.9.0" 239 + } 240 + }, 241 + "node_modules/@babel/helper-validator-identifier": { 242 + "version": "7.24.7", 243 + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", 244 + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", 245 + "dev": true, 246 + "engines": { 247 + "node": ">=6.9.0" 248 + } 249 + }, 250 + "node_modules/@babel/helper-validator-option": { 251 + "version": "7.24.8", 252 + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", 253 + "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", 254 + "dev": true, 255 + "engines": { 256 + "node": ">=6.9.0" 257 + } 258 + }, 259 + "node_modules/@babel/helpers": { 260 + "version": "7.25.0", 261 + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.0.tgz", 262 + "integrity": "sha512-MjgLZ42aCm0oGjJj8CtSM3DB8NOOf8h2l7DCTePJs29u+v7yO/RBX9nShlKMgFnRks/Q4tBAe7Hxnov9VkGwLw==", 263 + "dev": true, 264 + "dependencies": { 265 + "@babel/template": "^7.25.0", 266 + "@babel/types": "^7.25.0" 267 + }, 268 + "engines": { 269 + "node": ">=6.9.0" 270 + } 271 + }, 272 + "node_modules/@babel/highlight": { 273 + "version": "7.24.7", 274 + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", 275 + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", 276 + "dev": true, 277 + "dependencies": { 278 + "@babel/helper-validator-identifier": "^7.24.7", 279 + "chalk": "^2.4.2", 280 + "js-tokens": "^4.0.0", 281 + "picocolors": "^1.0.0" 282 + }, 283 + "engines": { 284 + "node": ">=6.9.0" 285 + } 286 + }, 287 + "node_modules/@babel/parser": { 288 + "version": "7.25.0", 289 + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.0.tgz", 290 + "integrity": "sha512-CzdIU9jdP0dg7HdyB+bHvDJGagUv+qtzZt5rYCWwW6tITNqV9odjp6Qu41gkG0ca5UfdDUWrKkiAnHHdGRnOrA==", 291 + "dev": true, 292 + "bin": { 293 + "parser": "bin/babel-parser.js" 294 + }, 295 + "engines": { 296 + "node": ">=6.0.0" 297 + } 298 + }, 299 + "node_modules/@babel/plugin-syntax-jsx": { 300 + "version": "7.24.7", 301 + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz", 302 + "integrity": "sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ==", 303 + "dev": true, 304 + "dependencies": { 305 + "@babel/helper-plugin-utils": "^7.24.7" 306 + }, 307 + "engines": { 308 + "node": ">=6.9.0" 309 + }, 310 + "peerDependencies": { 311 + "@babel/core": "^7.0.0-0" 312 + } 313 + }, 314 + "node_modules/@babel/plugin-transform-react-jsx": { 315 + "version": "7.25.2", 316 + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.25.2.tgz", 317 + "integrity": "sha512-KQsqEAVBpU82NM/B/N9j9WOdphom1SZH3R+2V7INrQUH+V9EBFwZsEJl8eBIVeQE62FxJCc70jzEZwqU7RcVqA==", 318 + "dev": true, 319 + "dependencies": { 320 + "@babel/helper-annotate-as-pure": "^7.24.7", 321 + "@babel/helper-module-imports": "^7.24.7", 322 + "@babel/helper-plugin-utils": "^7.24.8", 323 + "@babel/plugin-syntax-jsx": "^7.24.7", 324 + "@babel/types": "^7.25.2" 325 + }, 326 + "engines": { 327 + "node": ">=6.9.0" 328 + }, 329 + "peerDependencies": { 330 + "@babel/core": "^7.0.0-0" 331 + } 332 + }, 333 + "node_modules/@babel/plugin-transform-react-jsx-development": { 334 + "version": "7.24.7", 335 + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.24.7.tgz", 336 + "integrity": "sha512-QG9EnzoGn+Qar7rxuW+ZOsbWOt56FvvI93xInqsZDC5fsekx1AlIO4KIJ5M+D0p0SqSH156EpmZyXq630B8OlQ==", 337 + "dev": true, 338 + "dependencies": { 339 + "@babel/plugin-transform-react-jsx": "^7.24.7" 340 + }, 341 + "engines": { 342 + "node": ">=6.9.0" 343 + }, 344 + "peerDependencies": { 345 + "@babel/core": "^7.0.0-0" 346 + } 347 + }, 348 + "node_modules/@babel/template": { 349 + "version": "7.25.0", 350 + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", 351 + "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", 352 + "dev": true, 353 + "dependencies": { 354 + "@babel/code-frame": "^7.24.7", 355 + "@babel/parser": "^7.25.0", 356 + "@babel/types": "^7.25.0" 357 + }, 358 + "engines": { 359 + "node": ">=6.9.0" 360 + } 361 + }, 362 + "node_modules/@babel/traverse": { 363 + "version": "7.25.2", 364 + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.2.tgz", 365 + "integrity": "sha512-s4/r+a7xTnny2O6FcZzqgT6nE4/GHEdcqj4qAeglbUOh0TeglEfmNJFAd/OLoVtGd6ZhAO8GCVvCNUO5t/VJVQ==", 366 + "dev": true, 367 + "dependencies": { 368 + "@babel/code-frame": "^7.24.7", 369 + "@babel/generator": "^7.25.0", 370 + "@babel/parser": "^7.25.0", 371 + "@babel/template": "^7.25.0", 372 + "@babel/types": "^7.25.2", 373 + "debug": "^4.3.1", 374 + "globals": "^11.1.0" 375 + }, 376 + "engines": { 377 + "node": ">=6.9.0" 378 + } 379 + }, 380 + "node_modules/@babel/types": { 381 + "version": "7.25.2", 382 + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.2.tgz", 383 + "integrity": "sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==", 384 + "dev": true, 385 + "dependencies": { 386 + "@babel/helper-string-parser": "^7.24.8", 387 + "@babel/helper-validator-identifier": "^7.24.7", 388 + "to-fast-properties": "^2.0.0" 389 + }, 390 + "engines": { 391 + "node": ">=6.9.0" 392 + } 393 + }, 394 + "node_modules/@esbuild/aix-ppc64": { 395 + "version": "0.21.5", 396 + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", 397 + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", 398 + "cpu": [ 399 + "ppc64" 400 + ], 401 + "dev": true, 402 + "optional": true, 403 + "os": [ 404 + "aix" 405 + ], 406 + "engines": { 407 + "node": ">=12" 408 + } 409 + }, 410 + "node_modules/@esbuild/android-arm": { 411 + "version": "0.21.5", 412 + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", 413 + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", 414 + "cpu": [ 415 + "arm" 416 + ], 417 + "dev": true, 418 + "optional": true, 419 + "os": [ 420 + "android" 421 + ], 422 + "engines": { 423 + "node": ">=12" 424 + } 425 + }, 426 + "node_modules/@esbuild/android-arm64": { 427 + "version": "0.21.5", 428 + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", 429 + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", 430 + "cpu": [ 431 + "arm64" 432 + ], 433 + "dev": true, 434 + "optional": true, 435 + "os": [ 436 + "android" 437 + ], 438 + "engines": { 439 + "node": ">=12" 440 + } 441 + }, 442 + "node_modules/@esbuild/android-x64": { 443 + "version": "0.21.5", 444 + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", 445 + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", 446 + "cpu": [ 447 + "x64" 448 + ], 449 + "dev": true, 450 + "optional": true, 451 + "os": [ 452 + "android" 453 + ], 454 + "engines": { 455 + "node": ">=12" 456 + } 457 + }, 458 + "node_modules/@esbuild/darwin-arm64": { 459 + "version": "0.21.5", 460 + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", 461 + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", 462 + "cpu": [ 463 + "arm64" 464 + ], 465 + "dev": true, 466 + "optional": true, 467 + "os": [ 468 + "darwin" 469 + ], 470 + "engines": { 471 + "node": ">=12" 472 + } 473 + }, 474 + "node_modules/@esbuild/darwin-x64": { 475 + "version": "0.21.5", 476 + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", 477 + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", 478 + "cpu": [ 479 + "x64" 480 + ], 481 + "dev": true, 482 + "optional": true, 483 + "os": [ 484 + "darwin" 485 + ], 486 + "engines": { 487 + "node": ">=12" 488 + } 489 + }, 490 + "node_modules/@esbuild/freebsd-arm64": { 491 + "version": "0.21.5", 492 + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", 493 + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", 494 + "cpu": [ 495 + "arm64" 496 + ], 497 + "dev": true, 498 + "optional": true, 499 + "os": [ 500 + "freebsd" 501 + ], 502 + "engines": { 503 + "node": ">=12" 504 + } 505 + }, 506 + "node_modules/@esbuild/freebsd-x64": { 507 + "version": "0.21.5", 508 + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", 509 + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", 510 + "cpu": [ 511 + "x64" 512 + ], 513 + "dev": true, 514 + "optional": true, 515 + "os": [ 516 + "freebsd" 517 + ], 518 + "engines": { 519 + "node": ">=12" 520 + } 521 + }, 522 + "node_modules/@esbuild/linux-arm": { 523 + "version": "0.21.5", 524 + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", 525 + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", 526 + "cpu": [ 527 + "arm" 528 + ], 529 + "dev": true, 530 + "optional": true, 531 + "os": [ 532 + "linux" 533 + ], 534 + "engines": { 535 + "node": ">=12" 536 + } 537 + }, 538 + "node_modules/@esbuild/linux-arm64": { 539 + "version": "0.21.5", 540 + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", 541 + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", 542 + "cpu": [ 543 + "arm64" 544 + ], 545 + "dev": true, 546 + "optional": true, 547 + "os": [ 548 + "linux" 549 + ], 550 + "engines": { 551 + "node": ">=12" 552 + } 553 + }, 554 + "node_modules/@esbuild/linux-ia32": { 555 + "version": "0.21.5", 556 + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", 557 + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", 558 + "cpu": [ 559 + "ia32" 560 + ], 561 + "dev": true, 562 + "optional": true, 563 + "os": [ 564 + "linux" 565 + ], 566 + "engines": { 567 + "node": ">=12" 568 + } 569 + }, 570 + "node_modules/@esbuild/linux-loong64": { 571 + "version": "0.21.5", 572 + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", 573 + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", 574 + "cpu": [ 575 + "loong64" 576 + ], 577 + "dev": true, 578 + "optional": true, 579 + "os": [ 580 + "linux" 581 + ], 582 + "engines": { 583 + "node": ">=12" 584 + } 585 + }, 586 + "node_modules/@esbuild/linux-mips64el": { 587 + "version": "0.21.5", 588 + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", 589 + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", 590 + "cpu": [ 591 + "mips64el" 592 + ], 593 + "dev": true, 594 + "optional": true, 595 + "os": [ 596 + "linux" 597 + ], 598 + "engines": { 599 + "node": ">=12" 600 + } 601 + }, 602 + "node_modules/@esbuild/linux-ppc64": { 603 + "version": "0.21.5", 604 + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", 605 + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", 606 + "cpu": [ 607 + "ppc64" 608 + ], 609 + "dev": true, 610 + "optional": true, 611 + "os": [ 612 + "linux" 613 + ], 614 + "engines": { 615 + "node": ">=12" 616 + } 617 + }, 618 + "node_modules/@esbuild/linux-riscv64": { 619 + "version": "0.21.5", 620 + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", 621 + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", 622 + "cpu": [ 623 + "riscv64" 624 + ], 625 + "dev": true, 626 + "optional": true, 627 + "os": [ 628 + "linux" 629 + ], 630 + "engines": { 631 + "node": ">=12" 632 + } 633 + }, 634 + "node_modules/@esbuild/linux-s390x": { 635 + "version": "0.21.5", 636 + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", 637 + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", 638 + "cpu": [ 639 + "s390x" 640 + ], 641 + "dev": true, 642 + "optional": true, 643 + "os": [ 644 + "linux" 645 + ], 646 + "engines": { 647 + "node": ">=12" 648 + } 649 + }, 650 + "node_modules/@esbuild/linux-x64": { 651 + "version": "0.21.5", 652 + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", 653 + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", 654 + "cpu": [ 655 + "x64" 656 + ], 657 + "dev": true, 658 + "optional": true, 659 + "os": [ 660 + "linux" 661 + ], 662 + "engines": { 663 + "node": ">=12" 664 + } 665 + }, 666 + "node_modules/@esbuild/netbsd-x64": { 667 + "version": "0.21.5", 668 + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", 669 + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", 670 + "cpu": [ 671 + "x64" 672 + ], 673 + "dev": true, 674 + "optional": true, 675 + "os": [ 676 + "netbsd" 677 + ], 678 + "engines": { 679 + "node": ">=12" 680 + } 681 + }, 682 + "node_modules/@esbuild/openbsd-x64": { 683 + "version": "0.21.5", 684 + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", 685 + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", 686 + "cpu": [ 687 + "x64" 688 + ], 689 + "dev": true, 690 + "optional": true, 691 + "os": [ 692 + "openbsd" 693 + ], 694 + "engines": { 695 + "node": ">=12" 696 + } 697 + }, 698 + "node_modules/@esbuild/sunos-x64": { 699 + "version": "0.21.5", 700 + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", 701 + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", 702 + "cpu": [ 703 + "x64" 704 + ], 705 + "dev": true, 706 + "optional": true, 707 + "os": [ 708 + "sunos" 709 + ], 710 + "engines": { 711 + "node": ">=12" 712 + } 713 + }, 714 + "node_modules/@esbuild/win32-arm64": { 715 + "version": "0.21.5", 716 + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", 717 + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", 718 + "cpu": [ 719 + "arm64" 720 + ], 721 + "dev": true, 722 + "optional": true, 723 + "os": [ 724 + "win32" 725 + ], 726 + "engines": { 727 + "node": ">=12" 728 + } 729 + }, 730 + "node_modules/@esbuild/win32-ia32": { 731 + "version": "0.21.5", 732 + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", 733 + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", 734 + "cpu": [ 735 + "ia32" 736 + ], 737 + "dev": true, 738 + "optional": true, 739 + "os": [ 740 + "win32" 741 + ], 742 + "engines": { 743 + "node": ">=12" 744 + } 745 + }, 746 + "node_modules/@esbuild/win32-x64": { 747 + "version": "0.21.5", 748 + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", 749 + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", 750 + "cpu": [ 751 + "x64" 752 + ], 753 + "dev": true, 754 + "optional": true, 755 + "os": [ 756 + "win32" 757 + ], 758 + "engines": { 759 + "node": ">=12" 760 + } 761 + }, 762 + "node_modules/@fontsource-variable/roboto-serif": { 763 + "version": "5.0.14", 764 + "resolved": "https://registry.npmjs.org/@fontsource-variable/roboto-serif/-/roboto-serif-5.0.14.tgz", 765 + "integrity": "sha512-lzjPasdqDhuDaiavHn6OB2XIxptqHroXUNwnfoDUukU9whH7SlGFyt+s6MNFmVp5ijqv14uJdiDcd1SWTqwLKQ==" 766 + }, 767 + "node_modules/@iconify-json/ph": { 768 + "version": "1.1.13", 769 + "resolved": "https://registry.npmjs.org/@iconify-json/ph/-/ph-1.1.13.tgz", 770 + "integrity": "sha512-xtM4JJ63HCKj09WRqrBswXiHrpliBlqboWSZH8odcmqYXbvIFceU9/Til4V+MQr6+MoUC+KB72cxhky2+A6r/g==", 771 + "dev": true, 772 + "dependencies": { 773 + "@iconify/types": "*" 774 + } 775 + }, 776 + "node_modules/@iconify/types": { 777 + "version": "2.0.0", 778 + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", 779 + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", 780 + "dev": true 781 + }, 782 + "node_modules/@iconify/utils": { 783 + "version": "2.1.29", 784 + "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-2.1.29.tgz", 785 + "integrity": "sha512-wCcTsmlJvTi1VWBgcJ7HeuWlh7gLGWY7L9HmbgMfjOfsoo7DADemB2Nqnrw1KvCdEAxLL5wTMBAOP5BesFrtng==", 786 + "dev": true, 787 + "dependencies": { 788 + "@antfu/install-pkg": "^0.1.1", 789 + "@antfu/utils": "^0.7.10", 790 + "@iconify/types": "^2.0.0", 791 + "debug": "^4.3.5", 792 + "kolorist": "^1.8.0", 793 + "local-pkg": "^0.5.0", 794 + "mlly": "^1.7.1" 795 + } 796 + }, 797 + "node_modules/@iconify/utils/node_modules/@antfu/install-pkg": { 798 + "version": "0.1.1", 799 + "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-0.1.1.tgz", 800 + "integrity": "sha512-LyB/8+bSfa0DFGC06zpCEfs89/XoWZwws5ygEa5D+Xsm3OfI+aXQ86VgVG7Acyef+rSZ5HE7J8rrxzrQeM3PjQ==", 801 + "dev": true, 802 + "dependencies": { 803 + "execa": "^5.1.1", 804 + "find-up": "^5.0.0" 805 + }, 806 + "funding": { 807 + "url": "https://github.com/sponsors/antfu" 808 + } 809 + }, 810 + "node_modules/@jridgewell/gen-mapping": { 811 + "version": "0.3.5", 812 + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", 813 + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", 814 + "dev": true, 815 + "dependencies": { 816 + "@jridgewell/set-array": "^1.2.1", 817 + "@jridgewell/sourcemap-codec": "^1.4.10", 818 + "@jridgewell/trace-mapping": "^0.3.24" 819 + }, 820 + "engines": { 821 + "node": ">=6.0.0" 822 + } 823 + }, 824 + "node_modules/@jridgewell/resolve-uri": { 825 + "version": "3.1.2", 826 + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", 827 + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", 828 + "dev": true, 829 + "engines": { 830 + "node": ">=6.0.0" 831 + } 832 + }, 833 + "node_modules/@jridgewell/set-array": { 834 + "version": "1.2.1", 835 + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", 836 + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", 837 + "dev": true, 838 + "engines": { 839 + "node": ">=6.0.0" 840 + } 841 + }, 842 + "node_modules/@jridgewell/sourcemap-codec": { 843 + "version": "1.5.0", 844 + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", 845 + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", 846 + "dev": true 847 + }, 848 + "node_modules/@jridgewell/trace-mapping": { 849 + "version": "0.3.25", 850 + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", 851 + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", 852 + "dev": true, 853 + "dependencies": { 854 + "@jridgewell/resolve-uri": "^3.1.0", 855 + "@jridgewell/sourcemap-codec": "^1.4.14" 856 + } 857 + }, 858 + "node_modules/@jsdevtools/ez-spawn": { 859 + "version": "3.0.4", 860 + "resolved": "https://registry.npmjs.org/@jsdevtools/ez-spawn/-/ez-spawn-3.0.4.tgz", 861 + "integrity": "sha512-f5DRIOZf7wxogefH03RjMPMdBF7ADTWUMoOs9kaJo06EfwF+aFhMZMDZxHg/Xe12hptN9xoZjGso2fdjapBRIA==", 862 + "dev": true, 863 + "dependencies": { 864 + "call-me-maybe": "^1.0.1", 865 + "cross-spawn": "^7.0.3", 866 + "string-argv": "^0.3.1", 867 + "type-detect": "^4.0.8" 868 + }, 869 + "engines": { 870 + "node": ">=10" 871 + } 872 + }, 873 + "node_modules/@motionone/animation": { 874 + "version": "10.18.0", 875 + "resolved": "https://registry.npmjs.org/@motionone/animation/-/animation-10.18.0.tgz", 876 + "integrity": "sha512-9z2p5GFGCm0gBsZbi8rVMOAJCtw1WqBTIPw3ozk06gDvZInBPIsQcHgYogEJ4yuHJ+akuW8g1SEIOpTOvYs8hw==", 877 + "dependencies": { 878 + "@motionone/easing": "^10.18.0", 879 + "@motionone/types": "^10.17.1", 880 + "@motionone/utils": "^10.18.0", 881 + "tslib": "^2.3.1" 882 + } 883 + }, 884 + "node_modules/@motionone/dom": { 885 + "version": "10.18.0", 886 + "resolved": "https://registry.npmjs.org/@motionone/dom/-/dom-10.18.0.tgz", 887 + "integrity": "sha512-bKLP7E0eyO4B2UaHBBN55tnppwRnaE3KFfh3Ps9HhnAkar3Cb69kUCJY9as8LrccVYKgHA+JY5dOQqJLOPhF5A==", 888 + "dependencies": { 889 + "@motionone/animation": "^10.18.0", 890 + "@motionone/generators": "^10.18.0", 891 + "@motionone/types": "^10.17.1", 892 + "@motionone/utils": "^10.18.0", 893 + "hey-listen": "^1.0.8", 894 + "tslib": "^2.3.1" 895 + } 896 + }, 897 + "node_modules/@motionone/easing": { 898 + "version": "10.18.0", 899 + "resolved": "https://registry.npmjs.org/@motionone/easing/-/easing-10.18.0.tgz", 900 + "integrity": "sha512-VcjByo7XpdLS4o9T8t99JtgxkdMcNWD3yHU/n6CLEz3bkmKDRZyYQ/wmSf6daum8ZXqfUAgFeCZSpJZIMxaCzg==", 901 + "dependencies": { 902 + "@motionone/utils": "^10.18.0", 903 + "tslib": "^2.3.1" 904 + } 905 + }, 906 + "node_modules/@motionone/generators": { 907 + "version": "10.18.0", 908 + "resolved": "https://registry.npmjs.org/@motionone/generators/-/generators-10.18.0.tgz", 909 + "integrity": "sha512-+qfkC2DtkDj4tHPu+AFKVfR/C30O1vYdvsGYaR13W/1cczPrrcjdvYCj0VLFuRMN+lP1xvpNZHCRNM4fBzn1jg==", 910 + "dependencies": { 911 + "@motionone/types": "^10.17.1", 912 + "@motionone/utils": "^10.18.0", 913 + "tslib": "^2.3.1" 914 + } 915 + }, 916 + "node_modules/@motionone/types": { 917 + "version": "10.17.1", 918 + "resolved": "https://registry.npmjs.org/@motionone/types/-/types-10.17.1.tgz", 919 + "integrity": "sha512-KaC4kgiODDz8hswCrS0btrVrzyU2CSQKO7Ps90ibBVSQmjkrt2teqta6/sOG59v7+dPnKMAg13jyqtMKV2yJ7A==" 920 + }, 921 + "node_modules/@motionone/utils": { 922 + "version": "10.18.0", 923 + "resolved": "https://registry.npmjs.org/@motionone/utils/-/utils-10.18.0.tgz", 924 + "integrity": "sha512-3XVF7sgyTSI2KWvTf6uLlBJ5iAgRgmvp3bpuOiQJvInd4nZ19ET8lX5unn30SlmRH7hXbBbH+Gxd0m0klJ3Xtw==", 925 + "dependencies": { 926 + "@motionone/types": "^10.17.1", 927 + "hey-listen": "^1.0.8", 928 + "tslib": "^2.3.1" 929 + } 930 + }, 931 + "node_modules/@preact/preset-vite": { 932 + "version": "2.9.0", 933 + "resolved": "https://registry.npmjs.org/@preact/preset-vite/-/preset-vite-2.9.0.tgz", 934 + "integrity": "sha512-B9yVT7AkR6owrt84K3pLNyaKSvlioKdw65VqE/zMiR6HMovPekpsrwBNs5DJhBFEd5cvLMtCjHNHZ9P7Oblveg==", 935 + "dev": true, 936 + "dependencies": { 937 + "@babel/code-frame": "^7.22.13", 938 + "@babel/plugin-transform-react-jsx": "^7.22.15", 939 + "@babel/plugin-transform-react-jsx-development": "^7.22.5", 940 + "@prefresh/vite": "^2.4.1", 941 + "@rollup/pluginutils": "^4.1.1", 942 + "babel-plugin-transform-hook-names": "^1.0.2", 943 + "debug": "^4.3.4", 944 + "kolorist": "^1.8.0", 945 + "magic-string": "0.30.5", 946 + "node-html-parser": "^6.1.10", 947 + "resolve": "^1.22.8", 948 + "source-map": "^0.7.4", 949 + "stack-trace": "^1.0.0-pre2" 950 + }, 951 + "peerDependencies": { 952 + "@babel/core": "7.x", 953 + "vite": "2.x || 3.x || 4.x || 5.x" 954 + } 955 + }, 956 + "node_modules/@preact/signals": { 957 + "version": "1.3.0", 958 + "resolved": "https://registry.npmjs.org/@preact/signals/-/signals-1.3.0.tgz", 959 + "integrity": "sha512-EOMeg42SlLS72dhoq6Vjq08havnLseWmPQ8A0YsgIAqMgWgx7V1a39+Pxo6i7SY5NwJtH4849JogFq3M67AzWg==", 960 + "dependencies": { 961 + "@preact/signals-core": "^1.7.0" 962 + }, 963 + "funding": { 964 + "type": "opencollective", 965 + "url": "https://opencollective.com/preact" 966 + }, 967 + "peerDependencies": { 968 + "preact": "10.x" 969 + } 970 + }, 971 + "node_modules/@preact/signals-core": { 972 + "version": "1.7.0", 973 + "resolved": "https://registry.npmjs.org/@preact/signals-core/-/signals-core-1.7.0.tgz", 974 + "integrity": "sha512-bEZLgmJGSBVP5PUPDowhPW3bVdMmp9Tr5OEl+SQK+8Tv9T7UsIfyN905cfkmmeqw8z4xp8T6zrl4M1uj9+HAfg==", 975 + "funding": { 976 + "type": "opencollective", 977 + "url": "https://opencollective.com/preact" 978 + } 979 + }, 980 + "node_modules/@prefresh/babel-plugin": { 981 + "version": "0.5.1", 982 + "resolved": "https://registry.npmjs.org/@prefresh/babel-plugin/-/babel-plugin-0.5.1.tgz", 983 + "integrity": "sha512-uG3jGEAysxWoyG3XkYfjYHgaySFrSsaEb4GagLzYaxlydbuREtaX+FTxuIidp241RaLl85XoHg9Ej6E4+V1pcg==", 984 + "dev": true 985 + }, 986 + "node_modules/@prefresh/core": { 987 + "version": "1.5.2", 988 + "resolved": "https://registry.npmjs.org/@prefresh/core/-/core-1.5.2.tgz", 989 + "integrity": "sha512-A/08vkaM1FogrCII5PZKCrygxSsc11obExBScm3JF1CryK2uDS3ZXeni7FeKCx1nYdUkj4UcJxzPzc1WliMzZA==", 990 + "dev": true, 991 + "peerDependencies": { 992 + "preact": "^10.0.0" 993 + } 994 + }, 995 + "node_modules/@prefresh/utils": { 996 + "version": "1.2.0", 997 + "resolved": "https://registry.npmjs.org/@prefresh/utils/-/utils-1.2.0.tgz", 998 + "integrity": "sha512-KtC/fZw+oqtwOLUFM9UtiitB0JsVX0zLKNyRTA332sqREqSALIIQQxdUCS1P3xR/jT1e2e8/5rwH6gdcMLEmsQ==", 999 + "dev": true 1000 + }, 1001 + "node_modules/@prefresh/vite": { 1002 + "version": "2.4.6", 1003 + "resolved": "https://registry.npmjs.org/@prefresh/vite/-/vite-2.4.6.tgz", 1004 + "integrity": "sha512-miYbTl2J1YNaQJWyWHJzyIpNh7vKUuXC1qCDRzPeWjhQ+9bxeXkUBGDGd9I1f37R5GQYi1S65AN5oR0BR2WzvQ==", 1005 + "dev": true, 1006 + "dependencies": { 1007 + "@babel/core": "^7.22.1", 1008 + "@prefresh/babel-plugin": "0.5.1", 1009 + "@prefresh/core": "^1.5.1", 1010 + "@prefresh/utils": "^1.2.0", 1011 + "@rollup/pluginutils": "^4.2.1" 1012 + }, 1013 + "peerDependencies": { 1014 + "preact": "^10.4.0", 1015 + "vite": ">=2.0.0" 1016 + } 1017 + }, 1018 + "node_modules/@rollup/pluginutils": { 1019 + "version": "4.2.1", 1020 + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz", 1021 + "integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==", 1022 + "dev": true, 1023 + "dependencies": { 1024 + "estree-walker": "^2.0.1", 1025 + "picomatch": "^2.2.2" 1026 + }, 1027 + "engines": { 1028 + "node": ">= 8.0.0" 1029 + } 1030 + }, 1031 + "node_modules/@rollup/rollup-android-arm-eabi": { 1032 + "version": "4.19.1", 1033 + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.19.1.tgz", 1034 + "integrity": "sha512-XzqSg714++M+FXhHfXpS1tDnNZNpgxxuGZWlRG/jSj+VEPmZ0yg6jV4E0AL3uyBKxO8mO3xtOsP5mQ+XLfrlww==", 1035 + "cpu": [ 1036 + "arm" 1037 + ], 1038 + "dev": true, 1039 + "optional": true, 1040 + "os": [ 1041 + "android" 1042 + ] 1043 + }, 1044 + "node_modules/@rollup/rollup-android-arm64": { 1045 + "version": "4.19.1", 1046 + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.19.1.tgz", 1047 + "integrity": "sha512-thFUbkHteM20BGShD6P08aungq4irbIZKUNbG70LN8RkO7YztcGPiKTTGZS7Kw+x5h8hOXs0i4OaHwFxlpQN6A==", 1048 + "cpu": [ 1049 + "arm64" 1050 + ], 1051 + "dev": true, 1052 + "optional": true, 1053 + "os": [ 1054 + "android" 1055 + ] 1056 + }, 1057 + "node_modules/@rollup/rollup-darwin-arm64": { 1058 + "version": "4.19.1", 1059 + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.19.1.tgz", 1060 + "integrity": "sha512-8o6eqeFZzVLia2hKPUZk4jdE3zW7LCcZr+MD18tXkgBBid3lssGVAYuox8x6YHoEPDdDa9ixTaStcmx88lio5Q==", 1061 + "cpu": [ 1062 + "arm64" 1063 + ], 1064 + "dev": true, 1065 + "optional": true, 1066 + "os": [ 1067 + "darwin" 1068 + ] 1069 + }, 1070 + "node_modules/@rollup/rollup-darwin-x64": { 1071 + "version": "4.19.1", 1072 + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.19.1.tgz", 1073 + "integrity": "sha512-4T42heKsnbjkn7ovYiAdDVRRWZLU9Kmhdt6HafZxFcUdpjlBlxj4wDrt1yFWLk7G4+E+8p2C9tcmSu0KA6auGA==", 1074 + "cpu": [ 1075 + "x64" 1076 + ], 1077 + "dev": true, 1078 + "optional": true, 1079 + "os": [ 1080 + "darwin" 1081 + ] 1082 + }, 1083 + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { 1084 + "version": "4.19.1", 1085 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.19.1.tgz", 1086 + "integrity": "sha512-MXg1xp+e5GhZ3Vit1gGEyoC+dyQUBy2JgVQ+3hUrD9wZMkUw/ywgkpK7oZgnB6kPpGrxJ41clkPPnsknuD6M2Q==", 1087 + "cpu": [ 1088 + "arm" 1089 + ], 1090 + "dev": true, 1091 + "optional": true, 1092 + "os": [ 1093 + "linux" 1094 + ] 1095 + }, 1096 + "node_modules/@rollup/rollup-linux-arm-musleabihf": { 1097 + "version": "4.19.1", 1098 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.19.1.tgz", 1099 + "integrity": "sha512-DZNLwIY4ftPSRVkJEaxYkq7u2zel7aah57HESuNkUnz+3bZHxwkCUkrfS2IWC1sxK6F2QNIR0Qr/YXw7nkF3Pw==", 1100 + "cpu": [ 1101 + "arm" 1102 + ], 1103 + "dev": true, 1104 + "optional": true, 1105 + "os": [ 1106 + "linux" 1107 + ] 1108 + }, 1109 + "node_modules/@rollup/rollup-linux-arm64-gnu": { 1110 + "version": "4.19.1", 1111 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.19.1.tgz", 1112 + "integrity": "sha512-C7evongnjyxdngSDRRSQv5GvyfISizgtk9RM+z2biV5kY6S/NF/wta7K+DanmktC5DkuaJQgoKGf7KUDmA7RUw==", 1113 + "cpu": [ 1114 + "arm64" 1115 + ], 1116 + "dev": true, 1117 + "optional": true, 1118 + "os": [ 1119 + "linux" 1120 + ] 1121 + }, 1122 + "node_modules/@rollup/rollup-linux-arm64-musl": { 1123 + "version": "4.19.1", 1124 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.19.1.tgz", 1125 + "integrity": "sha512-89tFWqxfxLLHkAthAcrTs9etAoBFRduNfWdl2xUs/yLV+7XDrJ5yuXMHptNqf1Zw0UCA3cAutkAiAokYCkaPtw==", 1126 + "cpu": [ 1127 + "arm64" 1128 + ], 1129 + "dev": true, 1130 + "optional": true, 1131 + "os": [ 1132 + "linux" 1133 + ] 1134 + }, 1135 + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { 1136 + "version": "4.19.1", 1137 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.19.1.tgz", 1138 + "integrity": "sha512-PromGeV50sq+YfaisG8W3fd+Cl6mnOOiNv2qKKqKCpiiEke2KiKVyDqG/Mb9GWKbYMHj5a01fq/qlUR28PFhCQ==", 1139 + "cpu": [ 1140 + "ppc64" 1141 + ], 1142 + "dev": true, 1143 + "optional": true, 1144 + "os": [ 1145 + "linux" 1146 + ] 1147 + }, 1148 + "node_modules/@rollup/rollup-linux-riscv64-gnu": { 1149 + "version": "4.19.1", 1150 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.19.1.tgz", 1151 + "integrity": "sha512-/1BmHYh+iz0cNCP0oHCuF8CSiNj0JOGf0jRlSo3L/FAyZyG2rGBuKpkZVH9YF+x58r1jgWxvm1aRg3DHrLDt6A==", 1152 + "cpu": [ 1153 + "riscv64" 1154 + ], 1155 + "dev": true, 1156 + "optional": true, 1157 + "os": [ 1158 + "linux" 1159 + ] 1160 + }, 1161 + "node_modules/@rollup/rollup-linux-s390x-gnu": { 1162 + "version": "4.19.1", 1163 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.19.1.tgz", 1164 + "integrity": "sha512-0cYP5rGkQWRZKy9/HtsWVStLXzCF3cCBTRI+qRL8Z+wkYlqN7zrSYm6FuY5Kd5ysS5aH0q5lVgb/WbG4jqXN1Q==", 1165 + "cpu": [ 1166 + "s390x" 1167 + ], 1168 + "dev": true, 1169 + "optional": true, 1170 + "os": [ 1171 + "linux" 1172 + ] 1173 + }, 1174 + "node_modules/@rollup/rollup-linux-x64-gnu": { 1175 + "version": "4.19.1", 1176 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.19.1.tgz", 1177 + "integrity": "sha512-XUXeI9eM8rMP8aGvii/aOOiMvTs7xlCosq9xCjcqI9+5hBxtjDpD+7Abm1ZhVIFE1J2h2VIg0t2DX/gjespC2Q==", 1178 + "cpu": [ 1179 + "x64" 1180 + ], 1181 + "dev": true, 1182 + "optional": true, 1183 + "os": [ 1184 + "linux" 1185 + ] 1186 + }, 1187 + "node_modules/@rollup/rollup-linux-x64-musl": { 1188 + "version": "4.19.1", 1189 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.19.1.tgz", 1190 + "integrity": "sha512-V7cBw/cKXMfEVhpSvVZhC+iGifD6U1zJ4tbibjjN+Xi3blSXaj/rJynAkCFFQfoG6VZrAiP7uGVzL440Q6Me2Q==", 1191 + "cpu": [ 1192 + "x64" 1193 + ], 1194 + "dev": true, 1195 + "optional": true, 1196 + "os": [ 1197 + "linux" 1198 + ] 1199 + }, 1200 + "node_modules/@rollup/rollup-win32-arm64-msvc": { 1201 + "version": "4.19.1", 1202 + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.19.1.tgz", 1203 + "integrity": "sha512-88brja2vldW/76jWATlBqHEoGjJLRnP0WOEKAUbMcXaAZnemNhlAHSyj4jIwMoP2T750LE9lblvD4e2jXleZsA==", 1204 + "cpu": [ 1205 + "arm64" 1206 + ], 1207 + "dev": true, 1208 + "optional": true, 1209 + "os": [ 1210 + "win32" 1211 + ] 1212 + }, 1213 + "node_modules/@rollup/rollup-win32-ia32-msvc": { 1214 + "version": "4.19.1", 1215 + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.19.1.tgz", 1216 + "integrity": "sha512-LdxxcqRVSXi6k6JUrTah1rHuaupoeuiv38du8Mt4r4IPer3kwlTo+RuvfE8KzZ/tL6BhaPlzJ3835i6CxrFIRQ==", 1217 + "cpu": [ 1218 + "ia32" 1219 + ], 1220 + "dev": true, 1221 + "optional": true, 1222 + "os": [ 1223 + "win32" 1224 + ] 1225 + }, 1226 + "node_modules/@rollup/rollup-win32-x64-msvc": { 1227 + "version": "4.19.1", 1228 + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.19.1.tgz", 1229 + "integrity": "sha512-2bIrL28PcK3YCqD9anGxDxamxdiJAxA+l7fWIwM5o8UqNy1t3d1NdAweO2XhA0KTDJ5aH1FsuiT5+7VhtHliXg==", 1230 + "cpu": [ 1231 + "x64" 1232 + ], 1233 + "dev": true, 1234 + "optional": true, 1235 + "os": [ 1236 + "win32" 1237 + ] 1238 + }, 1239 + "node_modules/@svgr/babel-plugin-add-jsx-attribute": { 1240 + "version": "8.0.0", 1241 + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", 1242 + "integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==", 1243 + "dev": true, 1244 + "engines": { 1245 + "node": ">=14" 1246 + }, 1247 + "funding": { 1248 + "type": "github", 1249 + "url": "https://github.com/sponsors/gregberge" 1250 + }, 1251 + "peerDependencies": { 1252 + "@babel/core": "^7.0.0-0" 1253 + } 1254 + }, 1255 + "node_modules/@svgr/babel-plugin-remove-jsx-attribute": { 1256 + "version": "8.0.0", 1257 + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz", 1258 + "integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==", 1259 + "dev": true, 1260 + "engines": { 1261 + "node": ">=14" 1262 + }, 1263 + "funding": { 1264 + "type": "github", 1265 + "url": "https://github.com/sponsors/gregberge" 1266 + }, 1267 + "peerDependencies": { 1268 + "@babel/core": "^7.0.0-0" 1269 + } 1270 + }, 1271 + "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": { 1272 + "version": "8.0.0", 1273 + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz", 1274 + "integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==", 1275 + "dev": true, 1276 + "engines": { 1277 + "node": ">=14" 1278 + }, 1279 + "funding": { 1280 + "type": "github", 1281 + "url": "https://github.com/sponsors/gregberge" 1282 + }, 1283 + "peerDependencies": { 1284 + "@babel/core": "^7.0.0-0" 1285 + } 1286 + }, 1287 + "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { 1288 + "version": "8.0.0", 1289 + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz", 1290 + "integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==", 1291 + "dev": true, 1292 + "engines": { 1293 + "node": ">=14" 1294 + }, 1295 + "funding": { 1296 + "type": "github", 1297 + "url": "https://github.com/sponsors/gregberge" 1298 + }, 1299 + "peerDependencies": { 1300 + "@babel/core": "^7.0.0-0" 1301 + } 1302 + }, 1303 + "node_modules/@svgr/babel-plugin-svg-dynamic-title": { 1304 + "version": "8.0.0", 1305 + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz", 1306 + "integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==", 1307 + "dev": true, 1308 + "engines": { 1309 + "node": ">=14" 1310 + }, 1311 + "funding": { 1312 + "type": "github", 1313 + "url": "https://github.com/sponsors/gregberge" 1314 + }, 1315 + "peerDependencies": { 1316 + "@babel/core": "^7.0.0-0" 1317 + } 1318 + }, 1319 + "node_modules/@svgr/babel-plugin-svg-em-dimensions": { 1320 + "version": "8.0.0", 1321 + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz", 1322 + "integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==", 1323 + "dev": true, 1324 + "engines": { 1325 + "node": ">=14" 1326 + }, 1327 + "funding": { 1328 + "type": "github", 1329 + "url": "https://github.com/sponsors/gregberge" 1330 + }, 1331 + "peerDependencies": { 1332 + "@babel/core": "^7.0.0-0" 1333 + } 1334 + }, 1335 + "node_modules/@svgr/babel-plugin-transform-react-native-svg": { 1336 + "version": "8.1.0", 1337 + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz", 1338 + "integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==", 1339 + "dev": true, 1340 + "engines": { 1341 + "node": ">=14" 1342 + }, 1343 + "funding": { 1344 + "type": "github", 1345 + "url": "https://github.com/sponsors/gregberge" 1346 + }, 1347 + "peerDependencies": { 1348 + "@babel/core": "^7.0.0-0" 1349 + } 1350 + }, 1351 + "node_modules/@svgr/babel-plugin-transform-svg-component": { 1352 + "version": "8.0.0", 1353 + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz", 1354 + "integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==", 1355 + "dev": true, 1356 + "engines": { 1357 + "node": ">=12" 1358 + }, 1359 + "funding": { 1360 + "type": "github", 1361 + "url": "https://github.com/sponsors/gregberge" 1362 + }, 1363 + "peerDependencies": { 1364 + "@babel/core": "^7.0.0-0" 1365 + } 1366 + }, 1367 + "node_modules/@svgr/babel-preset": { 1368 + "version": "8.1.0", 1369 + "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz", 1370 + "integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==", 1371 + "dev": true, 1372 + "dependencies": { 1373 + "@svgr/babel-plugin-add-jsx-attribute": "8.0.0", 1374 + "@svgr/babel-plugin-remove-jsx-attribute": "8.0.0", 1375 + "@svgr/babel-plugin-remove-jsx-empty-expression": "8.0.0", 1376 + "@svgr/babel-plugin-replace-jsx-attribute-value": "8.0.0", 1377 + "@svgr/babel-plugin-svg-dynamic-title": "8.0.0", 1378 + "@svgr/babel-plugin-svg-em-dimensions": "8.0.0", 1379 + "@svgr/babel-plugin-transform-react-native-svg": "8.1.0", 1380 + "@svgr/babel-plugin-transform-svg-component": "8.0.0" 1381 + }, 1382 + "engines": { 1383 + "node": ">=14" 1384 + }, 1385 + "funding": { 1386 + "type": "github", 1387 + "url": "https://github.com/sponsors/gregberge" 1388 + }, 1389 + "peerDependencies": { 1390 + "@babel/core": "^7.0.0-0" 1391 + } 1392 + }, 1393 + "node_modules/@svgr/core": { 1394 + "version": "8.1.0", 1395 + "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", 1396 + "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", 1397 + "dev": true, 1398 + "dependencies": { 1399 + "@babel/core": "^7.21.3", 1400 + "@svgr/babel-preset": "8.1.0", 1401 + "camelcase": "^6.2.0", 1402 + "cosmiconfig": "^8.1.3", 1403 + "snake-case": "^3.0.4" 1404 + }, 1405 + "engines": { 1406 + "node": ">=14" 1407 + }, 1408 + "funding": { 1409 + "type": "github", 1410 + "url": "https://github.com/sponsors/gregberge" 1411 + } 1412 + }, 1413 + "node_modules/@svgr/hast-util-to-babel-ast": { 1414 + "version": "8.0.0", 1415 + "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz", 1416 + "integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==", 1417 + "dev": true, 1418 + "dependencies": { 1419 + "@babel/types": "^7.21.3", 1420 + "entities": "^4.4.0" 1421 + }, 1422 + "engines": { 1423 + "node": ">=14" 1424 + }, 1425 + "funding": { 1426 + "type": "github", 1427 + "url": "https://github.com/sponsors/gregberge" 1428 + } 1429 + }, 1430 + "node_modules/@svgr/plugin-jsx": { 1431 + "version": "8.1.0", 1432 + "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz", 1433 + "integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==", 1434 + "dev": true, 1435 + "dependencies": { 1436 + "@babel/core": "^7.21.3", 1437 + "@svgr/babel-preset": "8.1.0", 1438 + "@svgr/hast-util-to-babel-ast": "8.0.0", 1439 + "svg-parser": "^2.0.4" 1440 + }, 1441 + "engines": { 1442 + "node": ">=14" 1443 + }, 1444 + "funding": { 1445 + "type": "github", 1446 + "url": "https://github.com/sponsors/gregberge" 1447 + }, 1448 + "peerDependencies": { 1449 + "@svgr/core": "*" 1450 + } 1451 + }, 1452 + "node_modules/@types/estree": { 1453 + "version": "1.0.5", 1454 + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", 1455 + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", 1456 + "dev": true 1457 + }, 1458 + "node_modules/@types/node": { 1459 + "version": "20.14.13", 1460 + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.13.tgz", 1461 + "integrity": "sha512-+bHoGiZb8UiQ0+WEtmph2IWQCjIqg8MDZMAV+ppRRhUZnquF5mQkP/9vpSwJClEiSM/C7fZZExPzfU0vJTyp8w==", 1462 + "dev": true, 1463 + "dependencies": { 1464 + "undici-types": "~5.26.4" 1465 + } 1466 + }, 1467 + "node_modules/acorn": { 1468 + "version": "8.12.1", 1469 + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", 1470 + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", 1471 + "dev": true, 1472 + "bin": { 1473 + "acorn": "bin/acorn" 1474 + }, 1475 + "engines": { 1476 + "node": ">=0.4.0" 1477 + } 1478 + }, 1479 + "node_modules/ansi-styles": { 1480 + "version": "3.2.1", 1481 + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 1482 + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 1483 + "dev": true, 1484 + "dependencies": { 1485 + "color-convert": "^1.9.0" 1486 + }, 1487 + "engines": { 1488 + "node": ">=4" 1489 + } 1490 + }, 1491 + "node_modules/anymatch": { 1492 + "version": "3.1.3", 1493 + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", 1494 + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", 1495 + "dev": true, 1496 + "dependencies": { 1497 + "normalize-path": "^3.0.0", 1498 + "picomatch": "^2.0.4" 1499 + }, 1500 + "engines": { 1501 + "node": ">= 8" 1502 + } 1503 + }, 1504 + "node_modules/argparse": { 1505 + "version": "2.0.1", 1506 + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", 1507 + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", 1508 + "dev": true 1509 + }, 1510 + "node_modules/async-mutex": { 1511 + "version": "0.5.0", 1512 + "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.5.0.tgz", 1513 + "integrity": "sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==", 1514 + "dependencies": { 1515 + "tslib": "^2.4.0" 1516 + } 1517 + }, 1518 + "node_modules/babel-plugin-transform-hook-names": { 1519 + "version": "1.0.2", 1520 + "resolved": "https://registry.npmjs.org/babel-plugin-transform-hook-names/-/babel-plugin-transform-hook-names-1.0.2.tgz", 1521 + "integrity": "sha512-5gafyjyyBTTdX/tQQ0hRgu4AhNHG/hqWi0ZZmg2xvs2FgRkJXzDNKBZCyoYqgFkovfDrgM8OoKg8karoUvWeCw==", 1522 + "dev": true, 1523 + "peerDependencies": { 1524 + "@babel/core": "^7.12.10" 1525 + } 1526 + }, 1527 + "node_modules/binary-extensions": { 1528 + "version": "2.3.0", 1529 + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", 1530 + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", 1531 + "dev": true, 1532 + "engines": { 1533 + "node": ">=8" 1534 + }, 1535 + "funding": { 1536 + "url": "https://github.com/sponsors/sindresorhus" 1537 + } 1538 + }, 1539 + "node_modules/boolbase": { 1540 + "version": "1.0.0", 1541 + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", 1542 + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", 1543 + "dev": true 1544 + }, 1545 + "node_modules/braces": { 1546 + "version": "3.0.3", 1547 + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", 1548 + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", 1549 + "dev": true, 1550 + "dependencies": { 1551 + "fill-range": "^7.1.1" 1552 + }, 1553 + "engines": { 1554 + "node": ">=8" 1555 + } 1556 + }, 1557 + "node_modules/browserslist": { 1558 + "version": "4.23.2", 1559 + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.2.tgz", 1560 + "integrity": "sha512-qkqSyistMYdxAcw+CzbZwlBy8AGmS/eEWs+sEV5TnLRGDOL+C5M2EnH6tlZyg0YoAxGJAFKh61En9BR941GnHA==", 1561 + "dev": true, 1562 + "funding": [ 1563 + { 1564 + "type": "opencollective", 1565 + "url": "https://opencollective.com/browserslist" 1566 + }, 1567 + { 1568 + "type": "tidelift", 1569 + "url": "https://tidelift.com/funding/github/npm/browserslist" 1570 + }, 1571 + { 1572 + "type": "github", 1573 + "url": "https://github.com/sponsors/ai" 1574 + } 1575 + ], 1576 + "dependencies": { 1577 + "caniuse-lite": "^1.0.30001640", 1578 + "electron-to-chromium": "^1.4.820", 1579 + "node-releases": "^2.0.14", 1580 + "update-browserslist-db": "^1.1.0" 1581 + }, 1582 + "bin": { 1583 + "browserslist": "cli.js" 1584 + }, 1585 + "engines": { 1586 + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" 1587 + } 1588 + }, 1589 + "node_modules/call-me-maybe": { 1590 + "version": "1.0.2", 1591 + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", 1592 + "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", 1593 + "dev": true 1594 + }, 1595 + "node_modules/callsites": { 1596 + "version": "3.1.0", 1597 + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", 1598 + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", 1599 + "dev": true, 1600 + "engines": { 1601 + "node": ">=6" 1602 + } 1603 + }, 1604 + "node_modules/camelcase": { 1605 + "version": "6.3.0", 1606 + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", 1607 + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", 1608 + "dev": true, 1609 + "engines": { 1610 + "node": ">=10" 1611 + }, 1612 + "funding": { 1613 + "url": "https://github.com/sponsors/sindresorhus" 1614 + } 1615 + }, 1616 + "node_modules/caniuse-lite": { 1617 + "version": "1.0.30001644", 1618 + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001644.tgz", 1619 + "integrity": "sha512-YGvlOZB4QhZuiis+ETS0VXR+MExbFf4fZYYeMTEE0aTQd/RdIjkTyZjLrbYVKnHzppDvnOhritRVv+i7Go6mHw==", 1620 + "dev": true, 1621 + "funding": [ 1622 + { 1623 + "type": "opencollective", 1624 + "url": "https://opencollective.com/browserslist" 1625 + }, 1626 + { 1627 + "type": "tidelift", 1628 + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" 1629 + }, 1630 + { 1631 + "type": "github", 1632 + "url": "https://github.com/sponsors/ai" 1633 + } 1634 + ] 1635 + }, 1636 + "node_modules/chalk": { 1637 + "version": "2.4.2", 1638 + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 1639 + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 1640 + "dev": true, 1641 + "dependencies": { 1642 + "ansi-styles": "^3.2.1", 1643 + "escape-string-regexp": "^1.0.5", 1644 + "supports-color": "^5.3.0" 1645 + }, 1646 + "engines": { 1647 + "node": ">=4" 1648 + } 1649 + }, 1650 + "node_modules/chokidar": { 1651 + "version": "3.6.0", 1652 + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", 1653 + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", 1654 + "dev": true, 1655 + "dependencies": { 1656 + "anymatch": "~3.1.2", 1657 + "braces": "~3.0.2", 1658 + "glob-parent": "~5.1.2", 1659 + "is-binary-path": "~2.1.0", 1660 + "is-glob": "~4.0.1", 1661 + "normalize-path": "~3.0.0", 1662 + "readdirp": "~3.6.0" 1663 + }, 1664 + "engines": { 1665 + "node": ">= 8.10.0" 1666 + }, 1667 + "funding": { 1668 + "url": "https://paulmillr.com/funding/" 1669 + }, 1670 + "optionalDependencies": { 1671 + "fsevents": "~2.3.2" 1672 + } 1673 + }, 1674 + "node_modules/color-convert": { 1675 + "version": "1.9.3", 1676 + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 1677 + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 1678 + "dev": true, 1679 + "dependencies": { 1680 + "color-name": "1.1.3" 1681 + } 1682 + }, 1683 + "node_modules/color-name": { 1684 + "version": "1.1.3", 1685 + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 1686 + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", 1687 + "dev": true 1688 + }, 1689 + "node_modules/confbox": { 1690 + "version": "0.1.7", 1691 + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.7.tgz", 1692 + "integrity": "sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==", 1693 + "dev": true 1694 + }, 1695 + "node_modules/convert-source-map": { 1696 + "version": "2.0.0", 1697 + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", 1698 + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", 1699 + "dev": true 1700 + }, 1701 + "node_modules/cosmiconfig": { 1702 + "version": "8.3.6", 1703 + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", 1704 + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", 1705 + "dev": true, 1706 + "dependencies": { 1707 + "import-fresh": "^3.3.0", 1708 + "js-yaml": "^4.1.0", 1709 + "parse-json": "^5.2.0", 1710 + "path-type": "^4.0.0" 1711 + }, 1712 + "engines": { 1713 + "node": ">=14" 1714 + }, 1715 + "funding": { 1716 + "url": "https://github.com/sponsors/d-fischer" 1717 + }, 1718 + "peerDependencies": { 1719 + "typescript": ">=4.9.5" 1720 + }, 1721 + "peerDependenciesMeta": { 1722 + "typescript": { 1723 + "optional": true 1724 + } 1725 + } 1726 + }, 1727 + "node_modules/cross-spawn": { 1728 + "version": "7.0.3", 1729 + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", 1730 + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", 1731 + "dev": true, 1732 + "dependencies": { 1733 + "path-key": "^3.1.0", 1734 + "shebang-command": "^2.0.0", 1735 + "which": "^2.0.1" 1736 + }, 1737 + "engines": { 1738 + "node": ">= 8" 1739 + } 1740 + }, 1741 + "node_modules/css-select": { 1742 + "version": "5.1.0", 1743 + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", 1744 + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", 1745 + "dev": true, 1746 + "dependencies": { 1747 + "boolbase": "^1.0.0", 1748 + "css-what": "^6.1.0", 1749 + "domhandler": "^5.0.2", 1750 + "domutils": "^3.0.1", 1751 + "nth-check": "^2.0.1" 1752 + }, 1753 + "funding": { 1754 + "url": "https://github.com/sponsors/fb55" 1755 + } 1756 + }, 1757 + "node_modules/css-what": { 1758 + "version": "6.1.0", 1759 + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", 1760 + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", 1761 + "dev": true, 1762 + "engines": { 1763 + "node": ">= 6" 1764 + }, 1765 + "funding": { 1766 + "url": "https://github.com/sponsors/fb55" 1767 + } 1768 + }, 1769 + "node_modules/debug": { 1770 + "version": "4.3.6", 1771 + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", 1772 + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", 1773 + "dev": true, 1774 + "dependencies": { 1775 + "ms": "2.1.2" 1776 + }, 1777 + "engines": { 1778 + "node": ">=6.0" 1779 + }, 1780 + "peerDependenciesMeta": { 1781 + "supports-color": { 1782 + "optional": true 1783 + } 1784 + } 1785 + }, 1786 + "node_modules/dexie": { 1787 + "version": "4.0.8", 1788 + "resolved": "https://registry.npmjs.org/dexie/-/dexie-4.0.8.tgz", 1789 + "integrity": "sha512-1G6cJevS17KMDK847V3OHvK2zei899GwpDiqfEXHP1ASvme6eWJmAp9AU4s1son2TeGkWmC0g3y8ezOBPnalgQ==" 1790 + }, 1791 + "node_modules/dom-serializer": { 1792 + "version": "2.0.0", 1793 + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", 1794 + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", 1795 + "dev": true, 1796 + "dependencies": { 1797 + "domelementtype": "^2.3.0", 1798 + "domhandler": "^5.0.2", 1799 + "entities": "^4.2.0" 1800 + }, 1801 + "funding": { 1802 + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" 1803 + } 1804 + }, 1805 + "node_modules/domelementtype": { 1806 + "version": "2.3.0", 1807 + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", 1808 + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", 1809 + "dev": true, 1810 + "funding": [ 1811 + { 1812 + "type": "github", 1813 + "url": "https://github.com/sponsors/fb55" 1814 + } 1815 + ] 1816 + }, 1817 + "node_modules/domhandler": { 1818 + "version": "5.0.3", 1819 + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", 1820 + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", 1821 + "dev": true, 1822 + "dependencies": { 1823 + "domelementtype": "^2.3.0" 1824 + }, 1825 + "engines": { 1826 + "node": ">= 4" 1827 + }, 1828 + "funding": { 1829 + "url": "https://github.com/fb55/domhandler?sponsor=1" 1830 + } 1831 + }, 1832 + "node_modules/domutils": { 1833 + "version": "3.1.0", 1834 + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", 1835 + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", 1836 + "dev": true, 1837 + "dependencies": { 1838 + "dom-serializer": "^2.0.0", 1839 + "domelementtype": "^2.3.0", 1840 + "domhandler": "^5.0.3" 1841 + }, 1842 + "funding": { 1843 + "url": "https://github.com/fb55/domutils?sponsor=1" 1844 + } 1845 + }, 1846 + "node_modules/dot-case": { 1847 + "version": "3.0.4", 1848 + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", 1849 + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", 1850 + "dev": true, 1851 + "dependencies": { 1852 + "no-case": "^3.0.4", 1853 + "tslib": "^2.0.3" 1854 + } 1855 + }, 1856 + "node_modules/electron-to-chromium": { 1857 + "version": "1.5.3", 1858 + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.3.tgz", 1859 + "integrity": "sha512-QNdYSS5i8D9axWp/6XIezRObRHqaav/ur9z1VzCDUCH1XIFOr9WQk5xmgunhsTpjjgDy3oLxO/WMOVZlpUQrlA==", 1860 + "dev": true 1861 + }, 1862 + "node_modules/entities": { 1863 + "version": "4.5.0", 1864 + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", 1865 + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", 1866 + "dev": true, 1867 + "engines": { 1868 + "node": ">=0.12" 1869 + }, 1870 + "funding": { 1871 + "url": "https://github.com/fb55/entities?sponsor=1" 1872 + } 1873 + }, 1874 + "node_modules/error-ex": { 1875 + "version": "1.3.2", 1876 + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", 1877 + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", 1878 + "dev": true, 1879 + "dependencies": { 1880 + "is-arrayish": "^0.2.1" 1881 + } 1882 + }, 1883 + "node_modules/esbuild": { 1884 + "version": "0.21.5", 1885 + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", 1886 + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", 1887 + "dev": true, 1888 + "hasInstallScript": true, 1889 + "bin": { 1890 + "esbuild": "bin/esbuild" 1891 + }, 1892 + "engines": { 1893 + "node": ">=12" 1894 + }, 1895 + "optionalDependencies": { 1896 + "@esbuild/aix-ppc64": "0.21.5", 1897 + "@esbuild/android-arm": "0.21.5", 1898 + "@esbuild/android-arm64": "0.21.5", 1899 + "@esbuild/android-x64": "0.21.5", 1900 + "@esbuild/darwin-arm64": "0.21.5", 1901 + "@esbuild/darwin-x64": "0.21.5", 1902 + "@esbuild/freebsd-arm64": "0.21.5", 1903 + "@esbuild/freebsd-x64": "0.21.5", 1904 + "@esbuild/linux-arm": "0.21.5", 1905 + "@esbuild/linux-arm64": "0.21.5", 1906 + "@esbuild/linux-ia32": "0.21.5", 1907 + "@esbuild/linux-loong64": "0.21.5", 1908 + "@esbuild/linux-mips64el": "0.21.5", 1909 + "@esbuild/linux-ppc64": "0.21.5", 1910 + "@esbuild/linux-riscv64": "0.21.5", 1911 + "@esbuild/linux-s390x": "0.21.5", 1912 + "@esbuild/linux-x64": "0.21.5", 1913 + "@esbuild/netbsd-x64": "0.21.5", 1914 + "@esbuild/openbsd-x64": "0.21.5", 1915 + "@esbuild/sunos-x64": "0.21.5", 1916 + "@esbuild/win32-arm64": "0.21.5", 1917 + "@esbuild/win32-ia32": "0.21.5", 1918 + "@esbuild/win32-x64": "0.21.5" 1919 + } 1920 + }, 1921 + "node_modules/escalade": { 1922 + "version": "3.1.2", 1923 + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", 1924 + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", 1925 + "dev": true, 1926 + "engines": { 1927 + "node": ">=6" 1928 + } 1929 + }, 1930 + "node_modules/escape-string-regexp": { 1931 + "version": "1.0.5", 1932 + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 1933 + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", 1934 + "dev": true, 1935 + "engines": { 1936 + "node": ">=0.8.0" 1937 + } 1938 + }, 1939 + "node_modules/estree-walker": { 1940 + "version": "2.0.2", 1941 + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", 1942 + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", 1943 + "dev": true 1944 + }, 1945 + "node_modules/execa": { 1946 + "version": "5.1.1", 1947 + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", 1948 + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", 1949 + "dev": true, 1950 + "dependencies": { 1951 + "cross-spawn": "^7.0.3", 1952 + "get-stream": "^6.0.0", 1953 + "human-signals": "^2.1.0", 1954 + "is-stream": "^2.0.0", 1955 + "merge-stream": "^2.0.0", 1956 + "npm-run-path": "^4.0.1", 1957 + "onetime": "^5.1.2", 1958 + "signal-exit": "^3.0.3", 1959 + "strip-final-newline": "^2.0.0" 1960 + }, 1961 + "engines": { 1962 + "node": ">=10" 1963 + }, 1964 + "funding": { 1965 + "url": "https://github.com/sindresorhus/execa?sponsor=1" 1966 + } 1967 + }, 1968 + "node_modules/fill-range": { 1969 + "version": "7.1.1", 1970 + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", 1971 + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", 1972 + "dev": true, 1973 + "dependencies": { 1974 + "to-regex-range": "^5.0.1" 1975 + }, 1976 + "engines": { 1977 + "node": ">=8" 1978 + } 1979 + }, 1980 + "node_modules/find-up": { 1981 + "version": "5.0.0", 1982 + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", 1983 + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", 1984 + "dev": true, 1985 + "dependencies": { 1986 + "locate-path": "^6.0.0", 1987 + "path-exists": "^4.0.0" 1988 + }, 1989 + "engines": { 1990 + "node": ">=10" 1991 + }, 1992 + "funding": { 1993 + "url": "https://github.com/sponsors/sindresorhus" 1994 + } 1995 + }, 1996 + "node_modules/fsevents": { 1997 + "version": "2.3.3", 1998 + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 1999 + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 2000 + "dev": true, 2001 + "hasInstallScript": true, 2002 + "optional": true, 2003 + "os": [ 2004 + "darwin" 2005 + ], 2006 + "engines": { 2007 + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 2008 + } 2009 + }, 2010 + "node_modules/function-bind": { 2011 + "version": "1.1.2", 2012 + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", 2013 + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", 2014 + "dev": true, 2015 + "funding": { 2016 + "url": "https://github.com/sponsors/ljharb" 2017 + } 2018 + }, 2019 + "node_modules/gensync": { 2020 + "version": "1.0.0-beta.2", 2021 + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", 2022 + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", 2023 + "dev": true, 2024 + "engines": { 2025 + "node": ">=6.9.0" 2026 + } 2027 + }, 2028 + "node_modules/get-stream": { 2029 + "version": "6.0.1", 2030 + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", 2031 + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", 2032 + "dev": true, 2033 + "engines": { 2034 + "node": ">=10" 2035 + }, 2036 + "funding": { 2037 + "url": "https://github.com/sponsors/sindresorhus" 2038 + } 2039 + }, 2040 + "node_modules/glob-parent": { 2041 + "version": "5.1.2", 2042 + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 2043 + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 2044 + "dev": true, 2045 + "dependencies": { 2046 + "is-glob": "^4.0.1" 2047 + }, 2048 + "engines": { 2049 + "node": ">= 6" 2050 + } 2051 + }, 2052 + "node_modules/globals": { 2053 + "version": "11.12.0", 2054 + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", 2055 + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", 2056 + "dev": true, 2057 + "engines": { 2058 + "node": ">=4" 2059 + } 2060 + }, 2061 + "node_modules/has-flag": { 2062 + "version": "3.0.0", 2063 + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 2064 + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", 2065 + "dev": true, 2066 + "engines": { 2067 + "node": ">=4" 2068 + } 2069 + }, 2070 + "node_modules/hasown": { 2071 + "version": "2.0.2", 2072 + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", 2073 + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", 2074 + "dev": true, 2075 + "dependencies": { 2076 + "function-bind": "^1.1.2" 2077 + }, 2078 + "engines": { 2079 + "node": ">= 0.4" 2080 + } 2081 + }, 2082 + "node_modules/he": { 2083 + "version": "1.2.0", 2084 + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", 2085 + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", 2086 + "dev": true, 2087 + "bin": { 2088 + "he": "bin/he" 2089 + } 2090 + }, 2091 + "node_modules/hey-listen": { 2092 + "version": "1.0.8", 2093 + "resolved": "https://registry.npmjs.org/hey-listen/-/hey-listen-1.0.8.tgz", 2094 + "integrity": "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==" 2095 + }, 2096 + "node_modules/human-signals": { 2097 + "version": "2.1.0", 2098 + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", 2099 + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", 2100 + "dev": true, 2101 + "engines": { 2102 + "node": ">=10.17.0" 2103 + } 2104 + }, 2105 + "node_modules/import-fresh": { 2106 + "version": "3.3.0", 2107 + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", 2108 + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", 2109 + "dev": true, 2110 + "dependencies": { 2111 + "parent-module": "^1.0.0", 2112 + "resolve-from": "^4.0.0" 2113 + }, 2114 + "engines": { 2115 + "node": ">=6" 2116 + }, 2117 + "funding": { 2118 + "url": "https://github.com/sponsors/sindresorhus" 2119 + } 2120 + }, 2121 + "node_modules/is-arrayish": { 2122 + "version": "0.2.1", 2123 + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", 2124 + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", 2125 + "dev": true 2126 + }, 2127 + "node_modules/is-binary-path": { 2128 + "version": "2.1.0", 2129 + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", 2130 + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", 2131 + "dev": true, 2132 + "dependencies": { 2133 + "binary-extensions": "^2.0.0" 2134 + }, 2135 + "engines": { 2136 + "node": ">=8" 2137 + } 2138 + }, 2139 + "node_modules/is-core-module": { 2140 + "version": "2.15.0", 2141 + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.0.tgz", 2142 + "integrity": "sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==", 2143 + "dev": true, 2144 + "dependencies": { 2145 + "hasown": "^2.0.2" 2146 + }, 2147 + "engines": { 2148 + "node": ">= 0.4" 2149 + }, 2150 + "funding": { 2151 + "url": "https://github.com/sponsors/ljharb" 2152 + } 2153 + }, 2154 + "node_modules/is-extglob": { 2155 + "version": "2.1.1", 2156 + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 2157 + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 2158 + "dev": true, 2159 + "engines": { 2160 + "node": ">=0.10.0" 2161 + } 2162 + }, 2163 + "node_modules/is-glob": { 2164 + "version": "4.0.3", 2165 + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 2166 + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 2167 + "dev": true, 2168 + "dependencies": { 2169 + "is-extglob": "^2.1.1" 2170 + }, 2171 + "engines": { 2172 + "node": ">=0.10.0" 2173 + } 2174 + }, 2175 + "node_modules/is-number": { 2176 + "version": "7.0.0", 2177 + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 2178 + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 2179 + "dev": true, 2180 + "engines": { 2181 + "node": ">=0.12.0" 2182 + } 2183 + }, 2184 + "node_modules/is-stream": { 2185 + "version": "2.0.1", 2186 + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", 2187 + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", 2188 + "dev": true, 2189 + "engines": { 2190 + "node": ">=8" 2191 + }, 2192 + "funding": { 2193 + "url": "https://github.com/sponsors/sindresorhus" 2194 + } 2195 + }, 2196 + "node_modules/isexe": { 2197 + "version": "2.0.0", 2198 + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 2199 + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", 2200 + "dev": true 2201 + }, 2202 + "node_modules/js-tokens": { 2203 + "version": "4.0.0", 2204 + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 2205 + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", 2206 + "dev": true 2207 + }, 2208 + "node_modules/js-yaml": { 2209 + "version": "4.1.0", 2210 + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", 2211 + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", 2212 + "dev": true, 2213 + "dependencies": { 2214 + "argparse": "^2.0.1" 2215 + }, 2216 + "bin": { 2217 + "js-yaml": "bin/js-yaml.js" 2218 + } 2219 + }, 2220 + "node_modules/jsesc": { 2221 + "version": "2.5.2", 2222 + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", 2223 + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", 2224 + "dev": true, 2225 + "bin": { 2226 + "jsesc": "bin/jsesc" 2227 + }, 2228 + "engines": { 2229 + "node": ">=4" 2230 + } 2231 + }, 2232 + "node_modules/json-parse-even-better-errors": { 2233 + "version": "2.3.1", 2234 + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", 2235 + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", 2236 + "dev": true 2237 + }, 2238 + "node_modules/json5": { 2239 + "version": "2.2.3", 2240 + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", 2241 + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", 2242 + "dev": true, 2243 + "bin": { 2244 + "json5": "lib/cli.js" 2245 + }, 2246 + "engines": { 2247 + "node": ">=6" 2248 + } 2249 + }, 2250 + "node_modules/kolorist": { 2251 + "version": "1.8.0", 2252 + "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", 2253 + "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", 2254 + "dev": true 2255 + }, 2256 + "node_modules/lines-and-columns": { 2257 + "version": "1.2.4", 2258 + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", 2259 + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", 2260 + "dev": true 2261 + }, 2262 + "node_modules/local-pkg": { 2263 + "version": "0.5.0", 2264 + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.0.tgz", 2265 + "integrity": "sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==", 2266 + "dev": true, 2267 + "dependencies": { 2268 + "mlly": "^1.4.2", 2269 + "pkg-types": "^1.0.3" 2270 + }, 2271 + "engines": { 2272 + "node": ">=14" 2273 + }, 2274 + "funding": { 2275 + "url": "https://github.com/sponsors/antfu" 2276 + } 2277 + }, 2278 + "node_modules/locate-path": { 2279 + "version": "6.0.0", 2280 + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", 2281 + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", 2282 + "dev": true, 2283 + "dependencies": { 2284 + "p-locate": "^5.0.0" 2285 + }, 2286 + "engines": { 2287 + "node": ">=10" 2288 + }, 2289 + "funding": { 2290 + "url": "https://github.com/sponsors/sindresorhus" 2291 + } 2292 + }, 2293 + "node_modules/lower-case": { 2294 + "version": "2.0.2", 2295 + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", 2296 + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", 2297 + "dev": true, 2298 + "dependencies": { 2299 + "tslib": "^2.0.3" 2300 + } 2301 + }, 2302 + "node_modules/lru-cache": { 2303 + "version": "5.1.1", 2304 + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", 2305 + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", 2306 + "dev": true, 2307 + "dependencies": { 2308 + "yallist": "^3.0.2" 2309 + } 2310 + }, 2311 + "node_modules/magic-string": { 2312 + "version": "0.30.5", 2313 + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", 2314 + "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", 2315 + "dev": true, 2316 + "dependencies": { 2317 + "@jridgewell/sourcemap-codec": "^1.4.15" 2318 + }, 2319 + "engines": { 2320 + "node": ">=12" 2321 + } 2322 + }, 2323 + "node_modules/merge-stream": { 2324 + "version": "2.0.0", 2325 + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", 2326 + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", 2327 + "dev": true 2328 + }, 2329 + "node_modules/mimic-fn": { 2330 + "version": "2.1.0", 2331 + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", 2332 + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", 2333 + "dev": true, 2334 + "engines": { 2335 + "node": ">=6" 2336 + } 2337 + }, 2338 + "node_modules/mitt": { 2339 + "version": "3.0.1", 2340 + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", 2341 + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==" 2342 + }, 2343 + "node_modules/mlly": { 2344 + "version": "1.7.1", 2345 + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.1.tgz", 2346 + "integrity": "sha512-rrVRZRELyQzrIUAVMHxP97kv+G786pHmOKzuFII8zDYahFBS7qnHh2AlYSl1GAHhaMPCz6/oHjVMcfFYgFYHgA==", 2347 + "dev": true, 2348 + "dependencies": { 2349 + "acorn": "^8.11.3", 2350 + "pathe": "^1.1.2", 2351 + "pkg-types": "^1.1.1", 2352 + "ufo": "^1.5.3" 2353 + } 2354 + }, 2355 + "node_modules/motion": { 2356 + "version": "10.18.0", 2357 + "resolved": "https://registry.npmjs.org/motion/-/motion-10.18.0.tgz", 2358 + "integrity": "sha512-MVAZZmwM/cp77BrNe1TxTMldxRPjwBNHheU5aPToqT4rJdZxLiADk58H+a0al5jKLxkB0OdgNq6DiVn11cjvIQ==", 2359 + "dependencies": { 2360 + "@motionone/animation": "^10.18.0", 2361 + "@motionone/dom": "^10.18.0", 2362 + "@motionone/types": "^10.17.1", 2363 + "@motionone/utils": "^10.18.0" 2364 + } 2365 + }, 2366 + "node_modules/ms": { 2367 + "version": "2.1.2", 2368 + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 2369 + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 2370 + "dev": true 2371 + }, 2372 + "node_modules/nanoid": { 2373 + "version": "3.3.7", 2374 + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", 2375 + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", 2376 + "dev": true, 2377 + "funding": [ 2378 + { 2379 + "type": "github", 2380 + "url": "https://github.com/sponsors/ai" 2381 + } 2382 + ], 2383 + "bin": { 2384 + "nanoid": "bin/nanoid.cjs" 2385 + }, 2386 + "engines": { 2387 + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 2388 + } 2389 + }, 2390 + "node_modules/no-case": { 2391 + "version": "3.0.4", 2392 + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", 2393 + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", 2394 + "dev": true, 2395 + "dependencies": { 2396 + "lower-case": "^2.0.2", 2397 + "tslib": "^2.0.3" 2398 + } 2399 + }, 2400 + "node_modules/node-html-parser": { 2401 + "version": "6.1.13", 2402 + "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-6.1.13.tgz", 2403 + "integrity": "sha512-qIsTMOY4C/dAa5Q5vsobRpOOvPfC4pB61UVW2uSwZNUp0QU/jCekTal1vMmbO0DgdHeLUJpv/ARmDqErVxA3Sg==", 2404 + "dev": true, 2405 + "dependencies": { 2406 + "css-select": "^5.1.0", 2407 + "he": "1.2.0" 2408 + } 2409 + }, 2410 + "node_modules/node-releases": { 2411 + "version": "2.0.18", 2412 + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", 2413 + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", 2414 + "dev": true 2415 + }, 2416 + "node_modules/normalize-path": { 2417 + "version": "3.0.0", 2418 + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", 2419 + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", 2420 + "dev": true, 2421 + "engines": { 2422 + "node": ">=0.10.0" 2423 + } 2424 + }, 2425 + "node_modules/npm-run-path": { 2426 + "version": "4.0.1", 2427 + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", 2428 + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", 2429 + "dev": true, 2430 + "dependencies": { 2431 + "path-key": "^3.0.0" 2432 + }, 2433 + "engines": { 2434 + "node": ">=8" 2435 + } 2436 + }, 2437 + "node_modules/nth-check": { 2438 + "version": "2.1.1", 2439 + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", 2440 + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", 2441 + "dev": true, 2442 + "dependencies": { 2443 + "boolbase": "^1.0.0" 2444 + }, 2445 + "funding": { 2446 + "url": "https://github.com/fb55/nth-check?sponsor=1" 2447 + } 2448 + }, 2449 + "node_modules/onetime": { 2450 + "version": "5.1.2", 2451 + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", 2452 + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", 2453 + "dev": true, 2454 + "dependencies": { 2455 + "mimic-fn": "^2.1.0" 2456 + }, 2457 + "engines": { 2458 + "node": ">=6" 2459 + }, 2460 + "funding": { 2461 + "url": "https://github.com/sponsors/sindresorhus" 2462 + } 2463 + }, 2464 + "node_modules/p-limit": { 2465 + "version": "3.1.0", 2466 + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", 2467 + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", 2468 + "dev": true, 2469 + "dependencies": { 2470 + "yocto-queue": "^0.1.0" 2471 + }, 2472 + "engines": { 2473 + "node": ">=10" 2474 + }, 2475 + "funding": { 2476 + "url": "https://github.com/sponsors/sindresorhus" 2477 + } 2478 + }, 2479 + "node_modules/p-locate": { 2480 + "version": "5.0.0", 2481 + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", 2482 + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", 2483 + "dev": true, 2484 + "dependencies": { 2485 + "p-limit": "^3.0.2" 2486 + }, 2487 + "engines": { 2488 + "node": ">=10" 2489 + }, 2490 + "funding": { 2491 + "url": "https://github.com/sponsors/sindresorhus" 2492 + } 2493 + }, 2494 + "node_modules/parent-module": { 2495 + "version": "1.0.1", 2496 + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", 2497 + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", 2498 + "dev": true, 2499 + "dependencies": { 2500 + "callsites": "^3.0.0" 2501 + }, 2502 + "engines": { 2503 + "node": ">=6" 2504 + } 2505 + }, 2506 + "node_modules/parse-json": { 2507 + "version": "5.2.0", 2508 + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", 2509 + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", 2510 + "dev": true, 2511 + "dependencies": { 2512 + "@babel/code-frame": "^7.0.0", 2513 + "error-ex": "^1.3.1", 2514 + "json-parse-even-better-errors": "^2.3.0", 2515 + "lines-and-columns": "^1.1.6" 2516 + }, 2517 + "engines": { 2518 + "node": ">=8" 2519 + }, 2520 + "funding": { 2521 + "url": "https://github.com/sponsors/sindresorhus" 2522 + } 2523 + }, 2524 + "node_modules/path-exists": { 2525 + "version": "4.0.0", 2526 + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", 2527 + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", 2528 + "dev": true, 2529 + "engines": { 2530 + "node": ">=8" 2531 + } 2532 + }, 2533 + "node_modules/path-key": { 2534 + "version": "3.1.1", 2535 + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 2536 + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 2537 + "dev": true, 2538 + "engines": { 2539 + "node": ">=8" 2540 + } 2541 + }, 2542 + "node_modules/path-parse": { 2543 + "version": "1.0.7", 2544 + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", 2545 + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", 2546 + "dev": true 2547 + }, 2548 + "node_modules/path-type": { 2549 + "version": "4.0.0", 2550 + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", 2551 + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", 2552 + "dev": true, 2553 + "engines": { 2554 + "node": ">=8" 2555 + } 2556 + }, 2557 + "node_modules/pathe": { 2558 + "version": "1.1.2", 2559 + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", 2560 + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", 2561 + "dev": true 2562 + }, 2563 + "node_modules/picocolors": { 2564 + "version": "1.0.1", 2565 + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", 2566 + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", 2567 + "dev": true 2568 + }, 2569 + "node_modules/picomatch": { 2570 + "version": "2.3.1", 2571 + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 2572 + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 2573 + "dev": true, 2574 + "engines": { 2575 + "node": ">=8.6" 2576 + }, 2577 + "funding": { 2578 + "url": "https://github.com/sponsors/jonschlinkert" 2579 + } 2580 + }, 2581 + "node_modules/pkg-types": { 2582 + "version": "1.1.3", 2583 + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.1.3.tgz", 2584 + "integrity": "sha512-+JrgthZG6m3ckicaOB74TwQ+tBWsFl3qVQg7mN8ulwSOElJ7gBhKzj2VkCPnZ4NlF6kEquYU+RIYNVAvzd54UA==", 2585 + "dev": true, 2586 + "dependencies": { 2587 + "confbox": "^0.1.7", 2588 + "mlly": "^1.7.1", 2589 + "pathe": "^1.1.2" 2590 + } 2591 + }, 2592 + "node_modules/postcss": { 2593 + "version": "8.4.40", 2594 + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.40.tgz", 2595 + "integrity": "sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q==", 2596 + "dev": true, 2597 + "funding": [ 2598 + { 2599 + "type": "opencollective", 2600 + "url": "https://opencollective.com/postcss/" 2601 + }, 2602 + { 2603 + "type": "tidelift", 2604 + "url": "https://tidelift.com/funding/github/npm/postcss" 2605 + }, 2606 + { 2607 + "type": "github", 2608 + "url": "https://github.com/sponsors/ai" 2609 + } 2610 + ], 2611 + "dependencies": { 2612 + "nanoid": "^3.3.7", 2613 + "picocolors": "^1.0.1", 2614 + "source-map-js": "^1.2.0" 2615 + }, 2616 + "engines": { 2617 + "node": "^10 || ^12 || >=14" 2618 + } 2619 + }, 2620 + "node_modules/preact": { 2621 + "version": "10.23.1", 2622 + "resolved": "https://registry.npmjs.org/preact/-/preact-10.23.1.tgz", 2623 + "integrity": "sha512-O5UdRsNh4vdZaTieWe3XOgSpdMAmkIYBCT3VhQDlKrzyCm8lUYsk0fmVEvoQQifoOjFRTaHZO69ylrzTW2BH+A==", 2624 + "funding": { 2625 + "type": "opencollective", 2626 + "url": "https://opencollective.com/preact" 2627 + } 2628 + }, 2629 + "node_modules/readdirp": { 2630 + "version": "3.6.0", 2631 + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", 2632 + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", 2633 + "dev": true, 2634 + "dependencies": { 2635 + "picomatch": "^2.2.1" 2636 + }, 2637 + "engines": { 2638 + "node": ">=8.10.0" 2639 + } 2640 + }, 2641 + "node_modules/regexparam": { 2642 + "version": "3.0.0", 2643 + "resolved": "https://registry.npmjs.org/regexparam/-/regexparam-3.0.0.tgz", 2644 + "integrity": "sha512-RSYAtP31mvYLkAHrOlh25pCNQ5hWnT106VukGaaFfuJrZFkGRX5GhUAdPqpSDXxOhA2c4akmRuplv1mRqnBn6Q==", 2645 + "engines": { 2646 + "node": ">=8" 2647 + } 2648 + }, 2649 + "node_modules/resolve": { 2650 + "version": "1.22.8", 2651 + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", 2652 + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", 2653 + "dev": true, 2654 + "dependencies": { 2655 + "is-core-module": "^2.13.0", 2656 + "path-parse": "^1.0.7", 2657 + "supports-preserve-symlinks-flag": "^1.0.0" 2658 + }, 2659 + "bin": { 2660 + "resolve": "bin/resolve" 2661 + }, 2662 + "funding": { 2663 + "url": "https://github.com/sponsors/ljharb" 2664 + } 2665 + }, 2666 + "node_modules/resolve-from": { 2667 + "version": "4.0.0", 2668 + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", 2669 + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", 2670 + "dev": true, 2671 + "engines": { 2672 + "node": ">=4" 2673 + } 2674 + }, 2675 + "node_modules/rollup": { 2676 + "version": "4.19.1", 2677 + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.19.1.tgz", 2678 + "integrity": "sha512-K5vziVlg7hTpYfFBI+91zHBEMo6jafYXpkMlqZjg7/zhIG9iHqazBf4xz9AVdjS9BruRn280ROqLI7G3OFRIlw==", 2679 + "dev": true, 2680 + "dependencies": { 2681 + "@types/estree": "1.0.5" 2682 + }, 2683 + "bin": { 2684 + "rollup": "dist/bin/rollup" 2685 + }, 2686 + "engines": { 2687 + "node": ">=18.0.0", 2688 + "npm": ">=8.0.0" 2689 + }, 2690 + "optionalDependencies": { 2691 + "@rollup/rollup-android-arm-eabi": "4.19.1", 2692 + "@rollup/rollup-android-arm64": "4.19.1", 2693 + "@rollup/rollup-darwin-arm64": "4.19.1", 2694 + "@rollup/rollup-darwin-x64": "4.19.1", 2695 + "@rollup/rollup-linux-arm-gnueabihf": "4.19.1", 2696 + "@rollup/rollup-linux-arm-musleabihf": "4.19.1", 2697 + "@rollup/rollup-linux-arm64-gnu": "4.19.1", 2698 + "@rollup/rollup-linux-arm64-musl": "4.19.1", 2699 + "@rollup/rollup-linux-powerpc64le-gnu": "4.19.1", 2700 + "@rollup/rollup-linux-riscv64-gnu": "4.19.1", 2701 + "@rollup/rollup-linux-s390x-gnu": "4.19.1", 2702 + "@rollup/rollup-linux-x64-gnu": "4.19.1", 2703 + "@rollup/rollup-linux-x64-musl": "4.19.1", 2704 + "@rollup/rollup-win32-arm64-msvc": "4.19.1", 2705 + "@rollup/rollup-win32-ia32-msvc": "4.19.1", 2706 + "@rollup/rollup-win32-x64-msvc": "4.19.1", 2707 + "fsevents": "~2.3.2" 2708 + } 2709 + }, 2710 + "node_modules/semver": { 2711 + "version": "6.3.1", 2712 + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", 2713 + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", 2714 + "dev": true, 2715 + "bin": { 2716 + "semver": "bin/semver.js" 2717 + } 2718 + }, 2719 + "node_modules/shebang-command": { 2720 + "version": "2.0.0", 2721 + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 2722 + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 2723 + "dev": true, 2724 + "dependencies": { 2725 + "shebang-regex": "^3.0.0" 2726 + }, 2727 + "engines": { 2728 + "node": ">=8" 2729 + } 2730 + }, 2731 + "node_modules/shebang-regex": { 2732 + "version": "3.0.0", 2733 + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 2734 + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 2735 + "dev": true, 2736 + "engines": { 2737 + "node": ">=8" 2738 + } 2739 + }, 2740 + "node_modules/signal-exit": { 2741 + "version": "3.0.7", 2742 + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", 2743 + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", 2744 + "dev": true 2745 + }, 2746 + "node_modules/snake-case": { 2747 + "version": "3.0.4", 2748 + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", 2749 + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", 2750 + "dev": true, 2751 + "dependencies": { 2752 + "dot-case": "^3.0.4", 2753 + "tslib": "^2.0.3" 2754 + } 2755 + }, 2756 + "node_modules/source-map": { 2757 + "version": "0.7.4", 2758 + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", 2759 + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", 2760 + "dev": true, 2761 + "engines": { 2762 + "node": ">= 8" 2763 + } 2764 + }, 2765 + "node_modules/source-map-js": { 2766 + "version": "1.2.0", 2767 + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", 2768 + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", 2769 + "dev": true, 2770 + "engines": { 2771 + "node": ">=0.10.0" 2772 + } 2773 + }, 2774 + "node_modules/stack-trace": { 2775 + "version": "1.0.0-pre2", 2776 + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-1.0.0-pre2.tgz", 2777 + "integrity": "sha512-2ztBJRek8IVofG9DBJqdy2N5kulaacX30Nz7xmkYF6ale9WBVmIy6mFBchvGX7Vx/MyjBhx+Rcxqrj+dbOnQ6A==", 2778 + "dev": true, 2779 + "engines": { 2780 + "node": ">=16" 2781 + } 2782 + }, 2783 + "node_modules/string-argv": { 2784 + "version": "0.3.2", 2785 + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", 2786 + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", 2787 + "dev": true, 2788 + "engines": { 2789 + "node": ">=0.6.19" 2790 + } 2791 + }, 2792 + "node_modules/strip-final-newline": { 2793 + "version": "2.0.0", 2794 + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", 2795 + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", 2796 + "dev": true, 2797 + "engines": { 2798 + "node": ">=6" 2799 + } 2800 + }, 2801 + "node_modules/supports-color": { 2802 + "version": "5.5.0", 2803 + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 2804 + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 2805 + "dev": true, 2806 + "dependencies": { 2807 + "has-flag": "^3.0.0" 2808 + }, 2809 + "engines": { 2810 + "node": ">=4" 2811 + } 2812 + }, 2813 + "node_modules/supports-preserve-symlinks-flag": { 2814 + "version": "1.0.0", 2815 + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", 2816 + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", 2817 + "dev": true, 2818 + "engines": { 2819 + "node": ">= 0.4" 2820 + }, 2821 + "funding": { 2822 + "url": "https://github.com/sponsors/ljharb" 2823 + } 2824 + }, 2825 + "node_modules/svg-parser": { 2826 + "version": "2.0.4", 2827 + "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", 2828 + "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", 2829 + "dev": true 2830 + }, 2831 + "node_modules/to-fast-properties": { 2832 + "version": "2.0.0", 2833 + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", 2834 + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", 2835 + "dev": true, 2836 + "engines": { 2837 + "node": ">=4" 2838 + } 2839 + }, 2840 + "node_modules/to-regex-range": { 2841 + "version": "5.0.1", 2842 + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 2843 + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 2844 + "dev": true, 2845 + "dependencies": { 2846 + "is-number": "^7.0.0" 2847 + }, 2848 + "engines": { 2849 + "node": ">=8.0" 2850 + } 2851 + }, 2852 + "node_modules/tslib": { 2853 + "version": "2.6.3", 2854 + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", 2855 + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==" 2856 + }, 2857 + "node_modules/tubes_core": { 2858 + "resolved": "../core", 2859 + "link": true 2860 + }, 2861 + "node_modules/type-detect": { 2862 + "version": "4.1.0", 2863 + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", 2864 + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", 2865 + "dev": true, 2866 + "engines": { 2867 + "node": ">=4" 2868 + } 2869 + }, 2870 + "node_modules/typescript": { 2871 + "version": "5.5.4", 2872 + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", 2873 + "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", 2874 + "dev": true, 2875 + "bin": { 2876 + "tsc": "bin/tsc", 2877 + "tsserver": "bin/tsserver" 2878 + }, 2879 + "engines": { 2880 + "node": ">=14.17" 2881 + } 2882 + }, 2883 + "node_modules/ufo": { 2884 + "version": "1.5.4", 2885 + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz", 2886 + "integrity": "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==", 2887 + "dev": true 2888 + }, 2889 + "node_modules/undici-types": { 2890 + "version": "5.26.5", 2891 + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", 2892 + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", 2893 + "dev": true 2894 + }, 2895 + "node_modules/unplugin": { 2896 + "version": "1.12.0", 2897 + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.12.0.tgz", 2898 + "integrity": "sha512-KeczzHl2sATPQUx1gzo+EnUkmN4VmGBYRRVOZSGvGITE9rGHRDGqft6ONceP3vgXcyJ2XjX5axG5jMWUwNCYLw==", 2899 + "dev": true, 2900 + "dependencies": { 2901 + "acorn": "^8.12.1", 2902 + "chokidar": "^3.6.0", 2903 + "webpack-sources": "^3.2.3", 2904 + "webpack-virtual-modules": "^0.6.2" 2905 + }, 2906 + "engines": { 2907 + "node": ">=14.0.0" 2908 + } 2909 + }, 2910 + "node_modules/unplugin-icons": { 2911 + "version": "0.19.1", 2912 + "resolved": "https://registry.npmjs.org/unplugin-icons/-/unplugin-icons-0.19.1.tgz", 2913 + "integrity": "sha512-a5I+wSOO5lsgc4dB2nEFaSZ4eEgQvSSR8tSR2jT69nTKiVmcK+PPU633zn2FyRf9i6vLapUiQ28GQStfzGURdg==", 2914 + "dev": true, 2915 + "dependencies": { 2916 + "@antfu/install-pkg": "^0.3.3", 2917 + "@antfu/utils": "^0.7.10", 2918 + "@iconify/utils": "^2.1.29", 2919 + "debug": "^4.3.6", 2920 + "kolorist": "^1.8.0", 2921 + "local-pkg": "^0.5.0", 2922 + "unplugin": "^1.12.0" 2923 + }, 2924 + "funding": { 2925 + "url": "https://github.com/sponsors/antfu" 2926 + }, 2927 + "peerDependencies": { 2928 + "@svgr/core": ">=7.0.0", 2929 + "@svgx/core": "^1.0.1", 2930 + "@vue/compiler-sfc": "^3.0.2 || ^2.7.0", 2931 + "vue-template-compiler": "^2.6.12", 2932 + "vue-template-es2015-compiler": "^1.9.0" 2933 + }, 2934 + "peerDependenciesMeta": { 2935 + "@svgr/core": { 2936 + "optional": true 2937 + }, 2938 + "@svgx/core": { 2939 + "optional": true 2940 + }, 2941 + "@vue/compiler-sfc": { 2942 + "optional": true 2943 + }, 2944 + "vue-template-compiler": { 2945 + "optional": true 2946 + }, 2947 + "vue-template-es2015-compiler": { 2948 + "optional": true 2949 + } 2950 + } 2951 + }, 2952 + "node_modules/update-browserslist-db": { 2953 + "version": "1.1.0", 2954 + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", 2955 + "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", 2956 + "dev": true, 2957 + "funding": [ 2958 + { 2959 + "type": "opencollective", 2960 + "url": "https://opencollective.com/browserslist" 2961 + }, 2962 + { 2963 + "type": "tidelift", 2964 + "url": "https://tidelift.com/funding/github/npm/browserslist" 2965 + }, 2966 + { 2967 + "type": "github", 2968 + "url": "https://github.com/sponsors/ai" 2969 + } 2970 + ], 2971 + "dependencies": { 2972 + "escalade": "^3.1.2", 2973 + "picocolors": "^1.0.1" 2974 + }, 2975 + "bin": { 2976 + "update-browserslist-db": "cli.js" 2977 + }, 2978 + "peerDependencies": { 2979 + "browserslist": ">= 4.21.0" 2980 + } 2981 + }, 2982 + "node_modules/vite": { 2983 + "version": "5.3.5", 2984 + "resolved": "https://registry.npmjs.org/vite/-/vite-5.3.5.tgz", 2985 + "integrity": "sha512-MdjglKR6AQXQb9JGiS7Rc2wC6uMjcm7Go/NHNO63EwiJXfuk9PgqiP/n5IDJCziMkfw9n4Ubp7lttNwz+8ZVKA==", 2986 + "dev": true, 2987 + "dependencies": { 2988 + "esbuild": "^0.21.3", 2989 + "postcss": "^8.4.39", 2990 + "rollup": "^4.13.0" 2991 + }, 2992 + "bin": { 2993 + "vite": "bin/vite.js" 2994 + }, 2995 + "engines": { 2996 + "node": "^18.0.0 || >=20.0.0" 2997 + }, 2998 + "funding": { 2999 + "url": "https://github.com/vitejs/vite?sponsor=1" 3000 + }, 3001 + "optionalDependencies": { 3002 + "fsevents": "~2.3.3" 3003 + }, 3004 + "peerDependencies": { 3005 + "@types/node": "^18.0.0 || >=20.0.0", 3006 + "less": "*", 3007 + "lightningcss": "^1.21.0", 3008 + "sass": "*", 3009 + "stylus": "*", 3010 + "sugarss": "*", 3011 + "terser": "^5.4.0" 3012 + }, 3013 + "peerDependenciesMeta": { 3014 + "@types/node": { 3015 + "optional": true 3016 + }, 3017 + "less": { 3018 + "optional": true 3019 + }, 3020 + "lightningcss": { 3021 + "optional": true 3022 + }, 3023 + "sass": { 3024 + "optional": true 3025 + }, 3026 + "stylus": { 3027 + "optional": true 3028 + }, 3029 + "sugarss": { 3030 + "optional": true 3031 + }, 3032 + "terser": { 3033 + "optional": true 3034 + } 3035 + } 3036 + }, 3037 + "node_modules/webpack-sources": { 3038 + "version": "3.2.3", 3039 + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", 3040 + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", 3041 + "dev": true, 3042 + "engines": { 3043 + "node": ">=10.13.0" 3044 + } 3045 + }, 3046 + "node_modules/webpack-virtual-modules": { 3047 + "version": "0.6.2", 3048 + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", 3049 + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", 3050 + "dev": true 3051 + }, 3052 + "node_modules/which": { 3053 + "version": "2.0.2", 3054 + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 3055 + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 3056 + "dev": true, 3057 + "dependencies": { 3058 + "isexe": "^2.0.0" 3059 + }, 3060 + "bin": { 3061 + "node-which": "bin/node-which" 3062 + }, 3063 + "engines": { 3064 + "node": ">= 8" 3065 + } 3066 + }, 3067 + "node_modules/wouter-preact": { 3068 + "version": "3.3.1", 3069 + "resolved": "https://registry.npmjs.org/wouter-preact/-/wouter-preact-3.3.1.tgz", 3070 + "integrity": "sha512-X2Z8FcuRv7Gt7erEQ0hoVMUdOmM1HWF/6iy9YhyxVYluwqkoARP3cWV0TzfZVm8NcAegswW0y9In0ayoFCk5Ew==", 3071 + "dependencies": { 3072 + "mitt": "^3.0.1", 3073 + "regexparam": "^3.0.0" 3074 + }, 3075 + "peerDependencies": { 3076 + "preact": "^10.0.0" 3077 + } 3078 + }, 3079 + "node_modules/yallist": { 3080 + "version": "3.1.1", 3081 + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", 3082 + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", 3083 + "dev": true 3084 + }, 3085 + "node_modules/yocto-queue": { 3086 + "version": "0.1.0", 3087 + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", 3088 + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", 3089 + "dev": true, 3090 + "engines": { 3091 + "node": ">=10" 3092 + }, 3093 + "funding": { 3094 + "url": "https://github.com/sponsors/sindresorhus" 3095 + } 3096 + } 3097 + } 3098 + }
+31
neo/package.json
··· 1 + { 2 + "name": "tubes", 3 + "private": true, 4 + "version": "0.0.0", 5 + "type": "module", 6 + "scripts": { 7 + "dev": "vite", 8 + "build": "tsc && vite build", 9 + "preview": "vite preview" 10 + }, 11 + "dependencies": { 12 + "@fontsource-variable/roboto-serif": "^5.0.14", 13 + "@preact/signals": "^1.3.0", 14 + "async-mutex": "^0.5.0", 15 + "dexie": "^4.0.8", 16 + "motion": "^10.18.0", 17 + "preact": "^10.23.1", 18 + "tubes_core": "file:../core", 19 + "wouter-preact": "^3.3.1" 20 + }, 21 + "devDependencies": { 22 + "@iconify-json/ph": "^1.1.13", 23 + "@preact/preset-vite": "^2.9.0", 24 + "@svgr/core": "^8.1.0", 25 + "@svgr/plugin-jsx": "^8.1.0", 26 + "@types/node": "^20.14.13", 27 + "typescript": "^5.5.4", 28 + "unplugin-icons": "^0.19.1", 29 + "vite": "^5.3.5" 30 + } 31 + }
+26
neo/src/bits/buttons.tsx
··· 1 + import "@css/buttons.css"; 2 + import { FunctionalComponent } from "preact"; 3 + import { HTMLProps } from "preact/compat"; 4 + 5 + type Button = FunctionalComponent<HTMLProps<HTMLButtonElement>>; 6 + 7 + export const IconButton: Button = ({ children, ...rest }) => 8 + <button {...rest} class={`icon-button ${rest["class"] ?? ""}`}> 9 + {children} 10 + </button> 11 + 12 + export const TextButton: Button = ({ children, ...rest }) => 13 + <button {...rest} class={`text-button ${rest["class"] ?? ""}`}> 14 + {children} 15 + </button> 16 + 17 + export const PrimaryButton: Button = ({ children, ...rest }) => 18 + <button {...rest} class={`primary-button ${rest["class"] ?? ""}`}> 19 + {children} 20 + </button> 21 + 22 + export const SecondaryButton: Button = ({ children, ...rest }) => 23 + <button {...rest} class={`secondary-button ${rest["class"] ?? ""}`}> 24 + {children} 25 + </button> 26 +
+48
neo/src/bits/conn-config/index.tsx
··· 1 + import "@css/connection-info.css"; 2 + 3 + import { FunctionalComponent } from "preact"; 4 + import { Connection } from "tubes_core"; 5 + import { EmbiggenedSidebar } from "../sidebar/sidebar"; 6 + 7 + const ConnectionConfig 8 + : FunctionalComponent<{ conn: Connection }> 9 + = ({ conn }) => { 10 + const on_submit = (_e: SubmitEvent & { currentTarget: HTMLFormElement }) => { 11 + // const data = new FormData(e.currentTarget); 12 + } 13 + 14 + return <EmbiggenedSidebar class="connection-info"> 15 + <hgroup> 16 + <p>{conn.label}</p> 17 + <h1>Edit Connection</h1> 18 + </hgroup> 19 + <div class="sep" /> 20 + <form onSubmit={on_submit}> 21 + <h2>details</h2> 22 + <label class="form-field"> 23 + <span>label</span> 24 + <input type="text" name="label" value={conn.label} /> 25 + </label> 26 + <label class="form-field"> 27 + <span>url</span> 28 + <input type="url" name="url" value={conn.url.toString()} /> 29 + </label> 30 + <label class="form-field"> 31 + <span>nickname</span> 32 + <input type="url" name="url" value={conn.nickname} /> 33 + </label> 34 + <label class="form-field"> 35 + <span>realname</span> 36 + <input type="text" name="realname" value={conn.realname} /> 37 + </label> 38 + <label class="form-field"> 39 + <span>ident</span> 40 + <input type="text" name="username" value={conn.username} /> 41 + </label> 42 + 43 + <h2>account</h2> 44 + </form> 45 + </EmbiggenedSidebar>; 46 + } 47 + 48 + export default ConnectionConfig;
+35
neo/src/bits/conn-info/index.tsx
··· 1 + import "@css/connection-info.css"; 2 + 3 + import { FunctionalComponent } from "preact"; 4 + import { Connection } from "tubes_core"; 5 + import { EmbiggenedSidebar } from "../sidebar/sidebar"; 6 + import { ConnectionState } from "tubes_core/connection"; 7 + 8 + const ConnectionInfo 9 + : FunctionalComponent<{ conn: Connection }> 10 + = ({ conn }) => { 11 + const state = conn.$state.value; 12 + return <EmbiggenedSidebar class="connection-info"> 13 + <hgroup> 14 + <p>{(() => { 15 + switch (state) { 16 + case ConnectionState.Disconnected: return "disconnected"; 17 + case ConnectionState.Disconnecting: return "disconnecting from"; 18 + case ConnectionState.Connecting: 19 + case ConnectionState.Registering: return "connecting to"; 20 + case ConnectionState.Connected: return "connected to"; 21 + case ConnectionState.Failed: return "aaah!!!!!"; 22 + default: return ""; 23 + } 24 + })()}</p> 25 + <h1>{conn.label}</h1> 26 + </hgroup> 27 + <div class="sep" /> 28 + <h2>Message of the Day</h2> 29 + <pre> 30 + {conn.motd} 31 + </pre> 32 + </EmbiggenedSidebar>; 33 + } 34 + 35 + export default ConnectionInfo;
+83
neo/src/bits/dialog.tsx
··· 1 + import "@css/dialog.css"; 2 + 3 + import { useSignal } from "@preact/signals"; 4 + import { reduced_motion } from "@src/support"; 5 + import { spring, timeline } from "motion"; 6 + import { useLayoutEffect, useRef } from "preact/hooks"; 7 + 8 + export type DialogControls = { open: () => void, close: (mode?: CloseReason) => void } 9 + export type DialogInnards = (props: DialogControls) => any; 10 + 11 + export enum CloseReason { 12 + Cancel, 13 + Success, 14 + } 15 + 16 + export const create_dialog = ( 17 + Content: DialogInnards 18 + ) => { 19 + const is_open = useSignal(false); 20 + const dialog_ref = useRef<HTMLDialogElement>(null); 21 + 22 + const open = () => is_open.value = true; 23 + const close = (mode?: CloseReason) => { 24 + if (!dialog_ref.current) return; 25 + 26 + let transition = mode == CloseReason.Success 27 + ? trans_out 28 + : trans_out_cancel 29 + 30 + !reduced_motion() 31 + ? transition(dialog_ref.current) 32 + .finished 33 + .then(() => is_open.value = false) 34 + : is_open.value = false; 35 + }; 36 + 37 + const component = () => is_open.value 38 + ? <> 39 + <div class="dialog-scrim" aria-hidden /> 40 + <dialog ref={dialog_ref} class="fancy-dialog"> 41 + <Content open={open} close={close} /> 42 + </dialog> 43 + </> 44 + : <></> 45 + 46 + useLayoutEffect(() => { 47 + if (!dialog_ref.current) return; 48 + if (is_open.value == true) { 49 + dialog_ref.current.showModal(); 50 + dialog_ref.current.addEventListener("close", (e) => { 51 + e.preventDefault(); 52 + close() 53 + }); 54 + 55 + !reduced_motion() && trans_in(dialog_ref.current); 56 + } 57 + }, [is_open.value]); 58 + 59 + 60 + return { 61 + open, 62 + close, 63 + is_open, 64 + Component: component, 65 + } 66 + } 67 + 68 + const dialog_spring = spring({ stiffness: 750, damping: 45 }); 69 + 70 + const trans_in = (dialog: HTMLDialogElement) => timeline([ 71 + [dialog, { opacity: [0, 1], scale: [0.5, 1] }], 72 + [".dialog-scrim", { opacity: [0, 1] }, { at: 0 }], 73 + ], { defaultOptions: { easing: dialog_spring } }); 74 + 75 + const trans_out_cancel = (dialog: HTMLDialogElement) => timeline([ 76 + [dialog, { opacity: [1, 0, null], scale: [1, 0.75] }, { at: 0 }], 77 + [".dialog-scrim", { opacity: [1, 0] }, { at: 0 }], 78 + ], { defaultOptions: { easing: dialog_spring } }); 79 + 80 + const trans_out = (dialog: HTMLDialogElement) => timeline([ 81 + [dialog, { opacity: [1, 0, null], scale: [1, 1.25] }], 82 + [".dialog-scrim", { opacity: [1, 0] }, { at: 0 }], 83 + ], { defaultOptions: { easing: dialog_spring } });
+6
neo/src/bits/errors.tsx
··· 1 + import { FunctionalComponent } from "preact"; 2 + import { HTMLProps } from "preact/compat"; 3 + import ErrorIcon from "~icons/ph/diamond-fill"; 4 + 5 + export const ErrorMessage: FunctionalComponent<HTMLProps<HTMLParagraphElement>> = ({ children, ...rest }) => 6 + <p class="error-message" role="alert" aria-live="assertive" {...rest}><ErrorIcon /> {children}</p>
+14
neo/src/bits/form/form-field.tsx
··· 1 + import "@css/form.css"; 2 + import { FunctionalComponent } from "preact"; 3 + 4 + const FormField 5 + : FunctionalComponent<{ label: string, flavour_text?: string }> 6 + = ({ label, flavour_text, children }) => { 7 + return <label class="form-field"> 8 + <span>{label}</span> 9 + {children} 10 + <p>{flavour_text}</p> 11 + </label> 12 + } 13 + 14 + export default FormField;
+4
neo/src/bits/hud/hud.tsx
··· 1 + 2 + // const HUD: FunctionalComponent = () => { 3 + 4 + // }
neo/src/bits/menu/context-menu.tsx

This is a binary file and will not be displayed.

+21
neo/src/bits/menu/list-menu.tsx
··· 1 + import { FunctionalComponent } from "preact"; 2 + 3 + const ListMenu: FunctionalComponent = ({ children }) => { 4 + return <ul class="list-menu"> 5 + {children} 6 + </ul> 7 + } 8 + 9 + export const ListMenuItem: FunctionalComponent<{ 10 + icon?: any, 11 + destructive?: boolean, 12 + onClick?: (e: MouseEvent & { currentTarget: HTMLButtonElement }) => void, 13 + }> = ({ children, icon: Icon, destructive, onClick }) => { 14 + return <li> 15 + <button type="dialog" class={`${destructive ? "danger" : ""}`} onClick={onClick}> 16 + {Icon && <Icon aria-hidden />} {children} 17 + </button> 18 + </li>; 19 + }; 20 + 21 + export default ListMenu;
+3
neo/src/bits/menu/menu-item.tsx
··· 1 + import { ListMenuItem } from "./list-menu"; 2 + 3 + export default ListMenuItem;
+67
neo/src/bits/navigator.tsx
··· 1 + import { useSignal } from "@preact/signals"; 2 + import { reduced_motion } from "@src/support"; 3 + import { animate, spring, timeline } from "motion"; 4 + import { ComponentChild, FunctionalComponent } from "preact"; 5 + import { useLayoutEffect, useRef } from "preact/hooks"; 6 + 7 + type Page = (controls: NavigatorControls) => ComponentChild; 8 + 9 + export interface NavigatorControls { 10 + push: (page: Page) => void, 11 + pop: () => void, 12 + } 13 + 14 + const MiniNavigator 15 + : FunctionalComponent<{ children: Page, transitions?: boolean }> 16 + = ({ children, transitions }) => { 17 + const page_stack = useSignal<Page[]>([children]); 18 + 19 + const navigator_div = useRef<HTMLDivElement>(null); 20 + const first = navigator_div.current?.getBoundingClientRect(); 21 + const div_children = () => navigator_div.current!.children as unknown as Element[]; 22 + const animate_out = async () => transitions && !reduced_motion() && animate( 23 + div_children(), 24 + { opacity: 0 }, 25 + { duration: .1 } 26 + ).finished 27 + 28 + const controls: NavigatorControls = { 29 + push: (x) => animate_out().then(() => 30 + page_stack.value = [...page_stack.value, x] 31 + ), 32 + pop: () => animate_out().then(() => 33 + page_stack.value = page_stack.value.slice(0, -1) 34 + ), 35 + } 36 + 37 + useLayoutEffect(() => { 38 + if (!navigator_div.current || !first || !transitions || reduced_motion()) return; 39 + const last = navigator_div.current.getBoundingClientRect(); 40 + 41 + navigator_div.current.style.overflow = "hidden"; 42 + 43 + timeline([ 44 + [div_children(), { opacity: 0 }, { duration: 0 }], 45 + [ 46 + navigator_div.current, 47 + { 48 + width: [first.width + "px", last.width + "px"], 49 + height: [first.height + "px", last.height + "px"], 50 + }, 51 + { easing: spring({ stiffness: 750, damping: 40 }) } 52 + ], 53 + [div_children(), { opacity: 1 }, { easing: "ease-out", duration: 0.15, at: 0.1 }] 54 + ]).finished.then(() => { 55 + navigator_div.current!.removeAttribute("style"); 56 + navigator_div.current!.style.overflow = "unset"; 57 + }); 58 + 59 + console.log(first, last); 60 + }, [page_stack.value]) 61 + 62 + return <div class="navigator" ref={navigator_div}> 63 + {page_stack.value.at(-1)?.(controls)} 64 + </div> 65 + } 66 + 67 + export default MiniNavigator;
+8
neo/src/bits/placeholder.tsx
··· 1 + import "@css/placeholders.css"; 2 + 3 + const NoBufferPlaceholder = () => <article class="no-buffer"> 4 + <h2>nothing open</h2> 5 + <span>(select a thing on the left to open it here)</span> 6 + </article> 7 + 8 + export default NoBufferPlaceholder;
+75
neo/src/bits/popover.tsx
··· 1 + import "@css/popover.css"; 2 + 3 + import { Signal, useSignal } from "@preact/signals"; 4 + import { animate, spring } from "motion"; 5 + import { FunctionalComponent } from "preact"; 6 + import { MutableRef, useId, useLayoutEffect, useRef } from "preact/hooks"; 7 + 8 + export const create_popover = (content: any) => { 9 + const opened = useSignal(false); 10 + const ref = useRef<HTMLDialogElement>(); 11 + const rect = useSignal<DOMRect | null>(null) 12 + 13 + useLayoutEffect(() => { 14 + if (opened.value) { 15 + ref.current?.showModal(); 16 + 17 + ref.current?.addEventListener("close", () => { 18 + opened.value = false; 19 + }) 20 + } else { 21 + ref.current?.close(); 22 + } 23 + }, [opened.value]) 24 + 25 + let popover = () => opened.value ? <Popover trigger_rect={rect} dialog={ref}> 26 + {content} 27 + </Popover> : <></> 28 + 29 + return { 30 + open: (e?: MouseEvent & { currentTarget: HTMLButtonElement }) => { 31 + if (e) { 32 + rect.value = e.currentTarget.getBoundingClientRect(); 33 + } 34 + opened.value = true 35 + ref.current?.showModal(); 36 + }, 37 + close: () => opened.value = false, 38 + Popover: popover, 39 + } 40 + } 41 + 42 + export const Popover 43 + : FunctionalComponent<{ 44 + dialog: MutableRef<HTMLDialogElement | undefined>, 45 + trigger_rect: Signal<DOMRect | null>, 46 + }> 47 + = ({ children, dialog, trigger_rect }) => { 48 + const id = useId(); 49 + 50 + const r = trigger_rect.value; 51 + const x = r?.right; 52 + const y = r?.top; 53 + 54 + useLayoutEffect(() => { 55 + animate("#" + id, { 56 + scale: [0, 1], 57 + }, { easing: spring({ stiffness: 200, damping: 11, mass: 0.25 }) }); 58 + }); 59 + 60 + return <dialog 61 + class="popover" 62 + id={id} 63 + style={`--x: ${x}px; --y: ${y}px;`} 64 + //@ts-ignore 65 + ref={dialog} 66 + > 67 + {children} 68 + </dialog>; 69 + } 70 + 71 + export const PopoverInnards: FunctionalComponent = ({ children }) => { 72 + return <div class="popover-innards"> 73 + {children} 74 + </div> 75 + }
neo/src/bits/setup.tsx

This is a binary file and will not be displayed.

+174
neo/src/bits/sidebar/add-network.tsx
··· 1 + import "@css/add-network.css"; 2 + 3 + import { FunctionalComponent, FunctionComponent } from "preact"; 4 + import Plus from "~icons/ph/plus"; 5 + import { PrimaryButton, SecondaryButton, TextButton } from "../buttons"; 6 + import { CloseReason, create_dialog, DialogControls, DialogInnards } from "../dialog"; 7 + import FormField from "../form/form-field"; 8 + import MiniNavigator, { NavigatorControls } from "../navigator"; 9 + import { signal } from "@preact/signals"; 10 + import { ErrorMessage } from "../errors"; 11 + import Connections from "@src/chat/conns"; 12 + import { Adapter } from "tubes_core/adapter"; 13 + import { adapters } from "@src/chat/adapters"; 14 + 15 + export const AddNetworkButton = () => { 16 + const dialog = create_dialog(AddNetworkDialog); 17 + 18 + return <> 19 + <dialog.Component /> 20 + <TextButton class="add-network" onClick={() => { dialog.open() }}> 21 + <Plus width="15px" height="15px" /> 22 + Add Network 23 + </TextButton> 24 + </>; 25 + } 26 + 27 + const dialog_state = signal<{ 28 + url?: URL, 29 + nickname?: string, 30 + realname?: string, 31 + autoconnect?: boolean, 32 + }>({}); 33 + 34 + const AddNetworkDialog 35 + : DialogInnards 36 + = (props) => { 37 + const close = (x?: CloseReason) => { 38 + console.log("here") 39 + dialog_state.value = {}; 40 + props.close(x); 41 + } 42 + 43 + const error = signal(""); 44 + return <MiniNavigator transitions> 45 + {controls => <> 46 + <hgroup style="margin-bottom: 1rem"> 47 + <h2 class="heading">Add a Network</h2> 48 + <p class="body-small"> 49 + networks are where you do the talking 50 + </p> 51 + </hgroup> 52 + <form style="margin-top: 4rem" onSubmit={(e) => { 53 + error.value = ""; 54 + e.preventDefault(); 55 + 56 + try { 57 + const data = new FormData(e.currentTarget); 58 + const url_string = (data.get("url") as string).trim(); 59 + 60 + if (!url_string) { 61 + throw new Error("you, uh, need to put something here"); 62 + } 63 + 64 + let url; 65 + try { 66 + url = new URL(data.get("url") as string); 67 + } catch { 68 + throw new Error("that doesn't look like a valid URL"); 69 + } 70 + 71 + dialog_state.value["url"] = url!; 72 + 73 + controls.push(controls => <Stage2 navigator={controls} dialog={{ ...props, close }} />); 74 + } catch (err) { 75 + err instanceof Error 76 + ? error.value = err.message 77 + : null; 78 + } 79 + }}> 80 + <FormField 81 + label="where to?" 82 + flavour_text="enter the url of a network or search for a preset. i don't mind" 83 + > 84 + <input 85 + type="url" 86 + name="url" 87 + placeholder="e.g., wss://example.gov/irc" 88 + required 89 + value={dialog_state.value.url?.toString()} 90 + /> 91 + </FormField> 92 + 93 + {adapters.value.length == 0 94 + ? <></> 95 + : <AdapterSelector on_select={() => { }} />} 96 + 97 + {error.value && <ErrorMessage>{error.value}</ErrorMessage>} 98 + 99 + <div class="button-row" style="margin-top: 1rem"> 100 + <SecondaryButton type="button" onClick={() => close()}>nevermind</SecondaryButton> 101 + <PrimaryButton type="submit"> 102 + next 103 + </PrimaryButton> 104 + </div> 105 + </form> 106 + </>} 107 + </MiniNavigator> 108 + } 109 + 110 + const AdapterSelector 111 + : FunctionalComponent<{ on_select: (adapter: Adapter | null) => void }> 112 + = ({ on_select }) => { 113 + return <div class="adapter-selector"> 114 + <label> 115 + <span>connect from </span> 116 + <select name="adapter" onChange={(e) => { 117 + const value = e.currentTarget.value; 118 + if (!value) return null; 119 + 120 + const adapter = adapters.value.find(x => x.id == value); 121 + if (!adapter) return; 122 + 123 + on_select(adapter); 124 + }}> 125 + <option value="">This Computer</option> 126 + {adapters.value.map(x => <option value={x.id}>{x.label}</option>)} 127 + </select> 128 + </label> 129 + </div> 130 + } 131 + 132 + const Stage2 133 + : FunctionComponent<{ navigator: NavigatorControls, dialog: DialogControls }> 134 + = ({ navigator, dialog: { close } }) => { 135 + return <> 136 + <hgroup> 137 + <h2 class="heading">Adding {dialog_state.value.url!.hostname}</h2> 138 + <p class="body-small">(second page)</p> 139 + </hgroup> 140 + 141 + <form style="margin-top: 1rem" onSubmit={(e) => { 142 + e.preventDefault(); 143 + const data = new FormData(e.currentTarget); 144 + const nickname = data.get("nickname")! as string; 145 + const realname = data.get("nickname") as string ?? nickname; 146 + 147 + Connections.add({ 148 + url: dialog_state.value.url!.toString(), 149 + nickname, 150 + realname, 151 + autoconnect: true, 152 + }); 153 + 154 + close(CloseReason.Success); 155 + }}> 156 + <FormField label="nickname" flavour_text="this is the name that shows up next to your messages"> 157 + <input type="text" name="nickname" required /> 158 + </FormField> 159 + <FormField 160 + label='"real name"' 161 + flavour_text="your name or pronouns or what have you. doesn't actually have to be your real name" 162 + > 163 + <input type="text" /> 164 + </FormField> 165 + <div class="button-row" style="margin-top: 1.5rem"> 166 + <SecondaryButton type="button" onClick={() => close()}>nevermind</SecondaryButton> 167 + <SecondaryButton type="button" onClick={() => navigator.pop()}>back</SecondaryButton> 168 + <PrimaryButton type="submit" style="margin-left: auto;"> 169 + next 170 + </PrimaryButton> 171 + </div> 172 + </form> 173 + </> 174 + }
+280
neo/src/bits/sidebar/network-section.tsx
··· 1 + import { useComputed, useSignal } from "@preact/signals"; 2 + import Connections from "@src/chat/conns"; 3 + import { FunctionalComponent } from "preact"; 4 + import { Connection } from "tubes_core"; 5 + import { ChatBuffer } from "tubes_core/channel"; 6 + import { ConnectionErrorCode, ConnectionState } from "tubes_core/connection"; 7 + import DebugIcon from "~icons/ph/hammer"; 8 + import { IconButton, PrimaryButton, SecondaryButton, TextButton } from "../buttons"; 9 + import { ErrorMessage } from "../errors"; 10 + import FormField from "../form/form-field"; 11 + import ListMenu, { ListMenuItem } from "../menu/list-menu"; 12 + import { create_popover, PopoverInnards } from "../popover"; 13 + import { sidebar_view } from "./sidebar"; 14 + import { SidebarItem, SidebarLink } from "./sidebar-item"; 15 + import SidebarSection from "./sidebar-section"; 16 + 17 + import { useLocation } from "wouter-preact"; 18 + import ArchiveIcon from "~icons/ph/archive"; 19 + import ConnectionFailedIcon from "~icons/ph/diamond-fill"; 20 + import EtcIcon from "~icons/ph/dots-three-bold"; 21 + import ConnectingIcon from "~icons/ph/hourglass-simple-fill"; 22 + import RegisterIcon from "~icons/ph/identification-badge"; 23 + import InfoIcon from "~icons/ph/info"; 24 + import DisconnectedIcon from "~icons/ph/moon-fill"; 25 + import DMIcon from "~icons/ph/paper-plane-right"; 26 + import ConfIcon from "~icons/ph/pencil-simple"; 27 + import DisconnectIcon from "~icons/ph/plug"; 28 + import ConnectIcon from "~icons/ph/plugs-connected"; 29 + import JoinIcon from "~icons/ph/plus"; 30 + import LoginIcon from "~icons/ph/sign-in"; 31 + 32 + export const NetworkSection: FunctionalComponent<{ conn: Connection; }> = ({ conn }) => { 33 + const errored = useComputed(() => conn.$state.value == ConnectionState.Failed); 34 + const disconnected = useComputed(() => conn.$state.value == ConnectionState.Disconnected); 35 + 36 + const [, set_location] = useLocation() 37 + 38 + const icon = useComputed(() => { 39 + switch (conn.$state.value) { 40 + case ConnectionState.Disconnected: 41 + case ConnectionState.Disconnecting: return <DisconnectedIcon title="Disconnected" />; 42 + 43 + case ConnectionState.Connecting: 44 + case ConnectionState.Registering: return <ConnectingIcon title="Connecting" />; 45 + 46 + case ConnectionState.Failed: return <ConnectionFailedIcon title="Failed to Connect" />; 47 + 48 + case ConnectionState.Connected: return <></>; 49 + } 50 + }); 51 + 52 + const menu = create_popover(<ListMenu> 53 + <ListMenuItem icon={DMIcon}> 54 + New Direct Message 55 + </ListMenuItem> 56 + 57 + {conn.$state.value == ConnectionState.Disconnected 58 + ? <ListMenuItem 59 + onClick={() => conn.connect()} 60 + icon={ConnectIcon} 61 + > 62 + Connect 63 + </ListMenuItem> 64 + 65 + : <ListMenuItem 66 + onClick={() => conn.disconnect()} 67 + icon={DisconnectIcon} 68 + > 69 + Disconnect 70 + </ListMenuItem> 71 + } 72 + 73 + {/* debug menu */} 74 + {process.env.NODE_ENV == "development" && 75 + <ListMenuItem 76 + onClick={() => set_location( 77 + conn.adapter_id 78 + ? `/connection/${conn.adapter_id}/${conn.id}/debug` 79 + : `/connection/${conn.id}/debug` 80 + )} 81 + icon={DebugIcon} 82 + > 83 + Debug 84 + </ListMenuItem>} 85 + 86 + 87 + {conn.supports.sasl() && <ListMenuItem 88 + onClick={() => sidebar_view.value = ["conn-config", conn]} 89 + icon={LoginIcon} 90 + > 91 + Log In 92 + </ListMenuItem>} 93 + 94 + {conn.supports.registration() && <ListMenuItem 95 + onClick={() => sidebar_view.value = ["conn-config", conn]} 96 + icon={RegisterIcon} 97 + > 98 + Register 99 + </ListMenuItem>} 100 + 101 + <ListMenuItem 102 + onClick={() => sidebar_view.value = ["conn-info", conn]} 103 + icon={InfoIcon} 104 + > 105 + Details 106 + </ListMenuItem> 107 + 108 + <ListMenuItem 109 + onClick={() => sidebar_view.value = ["conn-config", conn]} 110 + icon={ConfIcon} 111 + > 112 + Configure 113 + </ListMenuItem> 114 + 115 + <ListMenuItem destructive icon={ArchiveIcon}>Archive</ListMenuItem> 116 + </ListMenu>); 117 + 118 + const join = create_popover(<JoinPopover conn={conn} />) 119 + 120 + return <SidebarSection> 121 + {/* da header */} 122 + <header class="sidebar-section-header"> 123 + {/* connection label */} 124 + <h2> 125 + <button 126 + class={`${disconnected.value ? "disconnected" : ""} ${errored.value ? "errored" : ""}`} 127 + onClick={() => sidebar_view.value = ["conn-info", conn]} 128 + > 129 + {icon.value} 130 + {conn.label} 131 + </button> 132 + </h2> 133 + 134 + {/* buttons */} 135 + {errored.value || disconnected.value 136 + ? <IconButton 137 + title="Connect" 138 + onClick={() => conn.connect()} 139 + > 140 + <ConnectIcon aria-hidden /> 141 + </IconButton> 142 + : <IconButton 143 + title="Connection Info" 144 + onClick={() => sidebar_view.value = ["conn-info", conn]} 145 + > 146 + <InfoIcon aria-hidden /> 147 + </IconButton> 148 + } 149 + <IconButton 150 + onClick={() => sidebar_view.value = ["conn-config", conn]} 151 + title="Configure" 152 + > 153 + <ConfIcon aria-hidden /> 154 + </IconButton> 155 + <IconButton title="More Stuff" onClick={(e) => menu.open(e)}><EtcIcon aria-hidden /></IconButton> 156 + 157 + <menu.Popover /> 158 + </header> 159 + 160 + {/* error message if there is one */} 161 + {errored.value && <div class="connection-error"><ConnErrorMessage conn={conn} /></div>} 162 + 163 + {/* list of buffers */} 164 + <ul class="sidebar-list"> 165 + {/* buffers */} 166 + {conn.$buffers.value.map(x => <BufferListItem buffer={x} />)} 167 + 168 + <join.Popover /> 169 + <SidebarItem onClick={join.open}> 170 + Join Channel <JoinIcon /> 171 + </SidebarItem> 172 + </ul> 173 + </SidebarSection>; 174 + }; 175 + 176 + const BufferListItem: FunctionalComponent<{ buffer: ChatBuffer; }> = ({ buffer }) => { 177 + return <SidebarLink 178 + to={buffer.conn.adapter_id 179 + ? `/connection/${buffer.conn.adapter_id}/${buffer.conn.id}/${buffer.name}` 180 + : `/connection/${buffer.conn.id}/${buffer.name}`} 181 + > 182 + {buffer.name} 183 + </SidebarLink>; 184 + }; 185 + 186 + const ConnErrorMessage: FunctionalComponent<{ conn: Connection }> = ({ conn }) => { 187 + switch (conn.$error.value?.[0]) { 188 + case ConnectionErrorCode.NickTaken: return <NickTaken conn={conn} /> 189 + case ConnectionErrorCode.SocketError: return <SocketError conn={conn} /> 190 + // case ConnectionError.SaslFailed: 191 + // case ConnectionError.SaslTooLong: 192 + // case ConnectionError.SaslAborted: 193 + default: return <></> 194 + } 195 + } 196 + 197 + const NickTaken: FunctionalComponent<{ conn: Connection }> = ({ conn }) => { 198 + const reason = conn.$error.value?.[1]; 199 + const reason_text = reason?.params?.at(-1); 200 + const update = create_popover(<> 201 + <p class="body-small low-emphasis"> 202 + Someone else on {conn.label} is already using the 203 + nickname <i>{conn.nickname}</i>. 204 + </p> 205 + </>); 206 + 207 + return <> 208 + <h3>Nickname Taken</h3> 209 + <p class="body-small low-emphasis"> 210 + Someone on {conn.label} is already using the 211 + nickname <i>{conn.nickname}</i>. 212 + </p> 213 + 214 + {reason && reason_text && <blockquote class="body-small low-emphasis"> 215 + <p>{reason_text}</p> 216 + <footer>—{reason.source?.nick ?? "Anonymous"}</footer> 217 + </blockquote>} 218 + 219 + 220 + <div class="buttons"> 221 + <TextButton onClick={() => conn.connect()}>Try Again</TextButton> 222 + <TextButton onClick={update.open}>Update Nickname</TextButton> 223 + <update.Popover /> 224 + </div> 225 + </>; 226 + } 227 + 228 + const SocketError: FunctionalComponent<{ conn: Connection }> = ({ conn }) => { 229 + return <> 230 + <h3>Couldn't Connect</h3> 231 + <p class="body-small low-emphasis"> 232 + Tubes could not connect to <i>{conn.config.url}</i>. <br /> 233 + Check if you're connected to the internet, the URL is correct 234 + and the network isn't experiencing any downtime. 235 + </p> 236 + 237 + <div class="buttons"> 238 + <TextButton onClick={() => conn.connect()}>Try Again</TextButton> 239 + <TextButton>Configure</TextButton> 240 + </div> 241 + </>; 242 + } 243 + 244 + const JoinPopover: FunctionalComponent<{ conn: Connection }> = ({ conn }) => { 245 + const error = useSignal(""); 246 + const on_submit = async (e: SubmitEvent & { currentTarget: HTMLFormElement }) => { 247 + e.preventDefault(); 248 + try { 249 + const data = new FormData(e.currentTarget); 250 + const channel_name = data.get("channel"); 251 + if (!channel_name || typeof channel_name != "string") { 252 + return; 253 + } 254 + Connections.add_autojoin(channel_name, conn); 255 + await conn.join_channel(channel_name); 256 + } catch (e) { 257 + if (e instanceof Error) { 258 + error.value = e.message; 259 + } 260 + } 261 + } 262 + 263 + return <PopoverInnards> 264 + <hgroup> 265 + <h2 class="heading">Join a Channel</h2> 266 + <p class="subtitle">channels are where the talking occurs.</p> 267 + </hgroup> 268 + <form onSubmit={on_submit} style="margin-top: 2rem;"> 269 + <FormField label="Name"> 270 + <input type="text" name="channel" placeholder="e.g. #example" /> 271 + </FormField> 272 + {error.value && <ErrorMessage>{error}</ErrorMessage>} 273 + <div class="button-row" style="margin-top: .75rem;"> 274 + <SecondaryButton onClick={() => { }} type="button">Nevermind</SecondaryButton> 275 + <PrimaryButton type="submit">Join</PrimaryButton> 276 + </div> 277 + </form> 278 + </PopoverInnards>; 279 + } 280 +
+35
neo/src/bits/sidebar/sidebar-footer.tsx
··· 1 + import { FunctionalComponent } from "preact"; 2 + import { Link } from "wouter-preact"; 3 + 4 + import SettingsIcon from "~icons/ph/faders"; 5 + 6 + const SidebarFooter 7 + : FunctionalComponent 8 + = () => <footer class="sidebar-footer"> 9 + <Link to="/settings"><SettingsIcon />Settings</Link> 10 + <ViewSwitcher /> 11 + </footer> 12 + 13 + import AllIcon from "~icons/ph/list"; 14 + import SplitIcon from "~icons/ph/rows"; 15 + import SeperatedIcon from "~icons/ph/square-split-horizontal"; 16 + 17 + const ViewSwitcher 18 + : FunctionalComponent 19 + = () => <fieldset class="view-switcher" aria-label="Switch Views"> 20 + <label title="Intermingled" > 21 + <input type="radio" name="view" value="all" /> 22 + <AllIcon aria-hidden /> 23 + </label> 24 + <label title="Split"> 25 + <input type="radio" name="view" value="by-network" checked /> 26 + <SplitIcon aria-hidden /> 27 + </label> 28 + <label title="Seperated" > 29 + <input type="radio" name="view" value="seperated" /> 30 + <SeperatedIcon aria-hidden /> 31 + </label> 32 + </fieldset> 33 + 34 + 35 + export default SidebarFooter
+6
neo/src/bits/sidebar/sidebar-header.tsx
··· 1 + import { FunctionalComponent } from "preact"; 2 + 3 + export const SidebarHeader: FunctionalComponent = () => 4 + <header class="sidebar-header"> 5 + <h1 class="heading">tubes!</h1> 6 + </header>
+29
neo/src/bits/sidebar/sidebar-item.tsx
··· 1 + import { FunctionalComponent } from "preact"; 2 + import { Link, useLocation } from "wouter-preact"; 3 + 4 + export const SidebarItem 5 + : FunctionalComponent<{ 6 + onClick?: (e: MouseEvent & { currentTarget: HTMLButtonElement }) => void, 7 + selected?: boolean 8 + }> 9 + = ({ children, onClick, selected }) => ( 10 + <li class={`sidebar-item ${selected ? "selected" : ""}`}> 11 + <button onClick={onClick}> 12 + {children} 13 + </button> 14 + </li> 15 + ) 16 + 17 + export const SidebarLink 18 + : FunctionalComponent<{ to: string, }> 19 + = ({ children, to }) => { 20 + const [location] = useLocation(); 21 + const selected = location == to; 22 + return ( 23 + <li class={`sidebar-item ${selected ? "selected" : ""}`}> 24 + <Link to={to}> 25 + {children} 26 + </Link> 27 + </li> 28 + ); 29 + }
+9
neo/src/bits/sidebar/sidebar-section.tsx
··· 1 + import { FunctionalComponent } from "preact"; 2 + 3 + const SidebarSection 4 + : FunctionalComponent 5 + = ({ children }) => <section class="sidebar-section"> 6 + {children} 7 + </section> 8 + 9 + export default SidebarSection;
+66
neo/src/bits/sidebar/sidebar.tsx
··· 1 + import { signal } from "@preact/signals"; 2 + import { FunctionalComponent } from "preact"; 3 + 4 + import Connections from "@src/chat/conns"; 5 + import { SidebarHeader } from "./sidebar-header"; 6 + 7 + import "@css/sidebar.css"; 8 + import { useEffect, useRef } from "preact/hooks"; 9 + import type { Connection } from "tubes_core"; 10 + import ConnectionConfig from "../conn-config"; 11 + import ConnectionInfo from "../conn-info"; 12 + import { NetworkSection } from "./network-section"; 13 + import { AddNetworkButton } from "./add-network"; 14 + import SidebarFooter from "./sidebar-footer"; 15 + 16 + type SidebarView 17 + = "main" 18 + | "add-network" 19 + | ["conn-info", Connection] 20 + | ["conn-config", Connection] 21 + 22 + export const sidebar_view = signal<SidebarView>("main") 23 + 24 + export const Sidebar: FunctionalComponent = ({ }) => { 25 + const view = sidebar_view.value; 26 + 27 + if (view instanceof Array && view[0] == "conn-info") { 28 + return <ConnectionInfo conn={view[1]} /> 29 + } 30 + 31 + if (view instanceof Array && view[0] == "conn-config") { 32 + return <ConnectionConfig conn={view[1]} /> 33 + } 34 + 35 + return <> 36 + <aside class="main-sidebar"> 37 + <SidebarHeader /> 38 + {Connections.connections.value.map(x => <NetworkSection conn={x} />)} 39 + <AddNetworkButton /> 40 + </aside> 41 + <SidebarFooter /> 42 + </> 43 + } 44 + 45 + export const EmbiggenedSidebar 46 + : FunctionalComponent<{ [k: string]: any }> 47 + = ({ children, ...rest }) => { 48 + const ref = useRef<HTMLDialogElement>(null); 49 + 50 + useEffect(() => { 51 + ref.current?.showModal(); 52 + // window.addEventListener('click', (e) => { 53 + // // todo: click outside 2 close 54 + // }); 55 + 56 + ref.current?.addEventListener("close", () => { 57 + sidebar_view.value = "main"; 58 + }) 59 + }); 60 + 61 + return <aside class="main-sidebar"> 62 + <dialog ref={ref} {...rest}> 63 + {children} 64 + </dialog> 65 + </aside> 66 + }
+45
neo/src/buffer/input.tsx
··· 1 + import { useComputed, useSignal } from "@preact/signals"; 2 + import { FunctionalComponent } from "preact"; 3 + import Add from "~icons/ph/plus"; 4 + import Emoji from "~icons/ph/smiley"; 5 + 6 + const MessageInput 7 + : FunctionalComponent<{ 8 + onSubmit: (msg: string, is_command: boolean) => void, 9 + is_scrolled: boolean 10 + }> 11 + = ({ onSubmit: onSend, is_scrolled }) => { 12 + const input = useSignal(""); 13 + const is_command = useComputed(() => input.value.charAt(0) == "/"); 14 + 15 + return <form 16 + class={` 17 + message-input 18 + ${is_scrolled ? "scrolled" : ""} 19 + ${is_command.value ? "command" : ""} 20 + `} 21 + onSubmit={(e) => { 22 + e.preventDefault(); 23 + onSend(input.value, is_command.value); 24 + input.value = ""; 25 + }} 26 + > 27 + <button class="prefix" type="button"> 28 + <Add width="17px" /> 29 + </button> 30 + <div class="input"> 31 + <input 32 + type="text" 33 + label="Message Input" 34 + placeholder="say something to #tubes" 35 + value={input.value} 36 + onInput={(e) => input.value = e.currentTarget.value} 37 + /> 38 + </div> 39 + <button class="suffix" type="button"> 40 + <Emoji width="18px" /> 41 + </button> 42 + </form>; 43 + } 44 + 45 + export default MessageInput;
+3
neo/src/buffer/squisher.ts
··· 1 + function squish_messages() { 2 + 3 + }
+216
neo/src/buffer/view.tsx
··· 1 + import "@css/messages.css"; 2 + import { useComputed, useSignal } from "@preact/signals"; 3 + import { pick_colour } from "@src/chat/colours"; 4 + import { FunctionalComponent, RefObject, createContext } from "preact"; 5 + import { useContext, useEffect, useMemo, useRef } from "preact/hooks"; 6 + import { ChatBuffer, IrcChannel } from "tubes_core/channel"; 7 + import { IrcMessage, extract_content } from "tubes_core/parser"; 8 + import MessageInput from "./input"; 9 + 10 + async function load_msgs(buffer: ChatBuffer, limit = 100): Promise<IrcMessage[]> { 11 + let count = 0; 12 + const acc = []; 13 + for await (const msg of buffer) { 14 + acc.push(msg); 15 + if (count++ >= limit) break; 16 + } 17 + 18 + return acc; 19 + } 20 + 21 + import { execute_command } from "@src/chat/commands"; 22 + import { store_message } from "@src/storage"; 23 + import Trangle from "~icons/ph/triangle-fill"; 24 + 25 + const StartOfHistory: FunctionalComponent<{ buffer: ChatBuffer }> = ({ buffer }) => { 26 + const chathistory = buffer.conn.capabilities.has("draft/chathistory"); 27 + return <div className="message-list-start"> 28 + <b>{buffer.name}</b> 29 + <p class="subtitle">this is the start of the channel's logs.</p> 30 + {!chathistory && 31 + <p class="chathistory-warning"> 32 + <Trangle aria-hidden /> Messages sent while Tubes was closed might not be shown. 33 + <a href="/">Learn More</a> 34 + </p>} 35 + 36 + </div> 37 + } 38 + 39 + const BufferView: FunctionalComponent<{ buffer: ChatBuffer }> = ({ buffer }) => { 40 + const msgs = useSignal<IrcMessage[]>([]); 41 + const is_scrolled = useSignal(false); 42 + const is_loading = useSignal(false); 43 + const finished_init = useSignal(false); 44 + const list_elem = useRef<HTMLUListElement>(null); 45 + 46 + const to_bottom = () => list_elem.current?.scroll({ top: list_elem.current?.scrollHeight }); 47 + 48 + // re-init the state when the buffer changes 49 + useMemo(() => { 50 + finished_init.value = false; 51 + is_scrolled.value = false; 52 + msgs.value = []; 53 + to_bottom(); 54 + 55 + load_msgs(buffer).then(x => { 56 + msgs.value = x; 57 + finished_init.value = true; 58 + to_bottom(); 59 + }); 60 + }, [buffer]); 61 + 62 + // append incoming messages into the msgs array 63 + useEffect(() => buffer.subscribe((msg) => { 64 + msgs.value = [...msgs.value, msg]; 65 + }), [buffer]); 66 + 67 + // when a new message comes in, scroll it into view 68 + useEffect(() => { 69 + if (!is_scrolled.value) to_bottom(); 70 + }, [msgs.value]); 71 + 72 + return <BufferContext.Provider value={buffer}> 73 + <div class={`message-list ${message_style.value}`}> 74 + {buffer instanceof IrcChannel ? <ChannelHeader channel={buffer} /> : <div />} 75 + {finished_init.value 76 + ? <ul class="messages" ref={list_elem} onScroll={e => { 77 + const elem = e.currentTarget; 78 + const padding = 5; 79 + 80 + is_scrolled.value = elem.scrollHeight > elem.scrollTop + elem.clientHeight + padding; 81 + }}> 82 + <StartOfHistory buffer={buffer} /> 83 + {is_loading.value && "loadin"} 84 + <LoadTrigger onIntersect={async () => { 85 + // if (is_loading.value == true) return; 86 + // is_loading.value = true; 87 + // const new_stuff = await load_msgs(buffer); 88 + // msgs.value = [...new_stuff, ...msgs.value]; 89 + }} parent={list_elem} /> 90 + {msgs.value.map(x => <Message msg={IrcMessage.hydrate(x)}></Message>)} 91 + </ul> 92 + : <SkeletonLoader ref2={list_elem} /> 93 + } 94 + <MessageInput 95 + is_scrolled={is_scrolled.value} 96 + onSubmit={(text, is_cmd) => { 97 + if (is_cmd) { 98 + return execute_command(text, buffer); 99 + } 100 + 101 + const msg = buffer.privmsg(text); 102 + store_message(buffer.conn, msg); 103 + msgs.value = [...msgs.value, msg]; 104 + }} 105 + /> 106 + </div> 107 + </BufferContext.Provider>; 108 + } 109 + 110 + const BufferContext = createContext<ChatBuffer | null>(null); 111 + 112 + const Message: FunctionalComponent<{ msg: IrcMessage }> = ({ msg }) => { 113 + const nick = msg.source?.nick; 114 + if (!nick) return <></>; 115 + 116 + const colour = pick_colour(nick.toString()); 117 + const style = ` 118 + --hover: var(--colour-${colour}-50); 119 + --selection: var(--colour-${colour}-700); 120 + `; 121 + 122 + if (msg.is_action) { 123 + return <li style={style}> 124 + <div class="left-bit"> 125 + <time datetime={msg.timestamp?.toDateString()} title={msg.timestamp?.toLocaleString()}> 126 + {msg.timestamp?.toLocaleTimeString(undefined, { timeStyle: "short" })} 127 + </time> 128 + <span class="name action">*</span> 129 + </div> 130 + <p><Nick nick={nick} colour={colour} /> {extract_content(msg)}</p> 131 + </li>; 132 + } 133 + 134 + switch (msg.command) { 135 + case "PRIVMSG": return <li style={style}> 136 + <div class="left-bit"> 137 + <time datetime={msg.timestamp?.toDateString()} title={msg.timestamp?.toLocaleString()}> 138 + {msg.timestamp?.toLocaleTimeString(undefined, { timeStyle: "short" })} 139 + </time> 140 + <Nick nick={nick} colour={colour} /> 141 + </div> 142 + <p>{msg.content}</p> 143 + </li>; 144 + 145 + // default: return <li><span class="name">⚠️</span> <p>{msg.toString()}</p></li> 146 + } 147 + return <></> 148 + } 149 + 150 + import { message_style } from "@src/support"; 151 + import GoneIcon from "~icons/ph/arrow-left"; 152 + 153 + const Nick 154 + : FunctionalComponent<{ colour?: string, nick: string }> 155 + = ({ colour, nick }) => { 156 + const buffer = useContext(BufferContext); 157 + const gone = useComputed(() => { 158 + return buffer instanceof IrcChannel 159 + && !buffer.$members.value.includes(nick) 160 + && nick != buffer.conn.nickname; 161 + }); 162 + 163 + return <> 164 + {gone.value && <GoneIcon width="12px" height="12px" />} 165 + 166 + <span 167 + class={` 168 + name 169 + ${gone.value ? "gone" : ""} 170 + ${nick.length > 10 ? "squish" : ""} 171 + `} 172 + style={`--colour: var(--colour-${colour}-700)`} 173 + title={nick} 174 + > 175 + {nick} 176 + </span> 177 + </> 178 + } 179 + 180 + const ChannelHeader 181 + : FunctionalComponent<{ channel: IrcChannel }> 182 + = ({ channel }) => <header class="channel-header"> 183 + <h1> 184 + {channel.name} 185 + </h1> 186 + <p> 187 + {channel.$topic.value} 188 + </p> 189 + </header> 190 + 191 + const SkeletonLoader 192 + : FunctionalComponent<{ ref2: RefObject<HTMLUListElement> }> 193 + = ({ ref2 }) => <ul ref={ref2} class="messages skeleton"> 194 + {Array.from({ length: 50 }, () => { 195 + return <li aria-hidden> 196 + <span class="name">{"h".repeat(Math.floor(Math.random() * 10))}</span> 197 + <p>{"h ".repeat(Math.floor(Math.random() * 75))}</p> 198 + </li> 199 + })} 200 + </ul> 201 + 202 + const LoadTrigger 203 + : FunctionalComponent<{ onIntersect: IntersectionObserverCallback, parent: RefObject<HTMLUListElement> }> 204 + = ({ onIntersect, parent }) => { 205 + const ref = useRef<HTMLDivElement>(null); 206 + useEffect(() => { 207 + const observer = new IntersectionObserver(onIntersect, { root: parent!.current }) 208 + observer.observe(ref.current!); 209 + 210 + return () => observer.disconnect(); 211 + }); 212 + 213 + return <div class="load-trigger" ref={ref} /> 214 + } 215 + 216 + export default BufferView;
+20
neo/src/chat/adapters.ts
··· 1 + import { signal } from "@preact/signals"; 2 + import type { Adapter } from "tubes_core/adapter"; 3 + import { SojuAdapter } from "tubes_core/soju/adapter"; 4 + import { store_message } from "@src/storage"; 5 + import History from "tubes_core/history"; 6 + 7 + export const adapters = signal<Adapter[]>([]); 8 + 9 + export const add_adapter = () => { 10 + const adapter = new SojuAdapter({ 11 + }, { 12 + history_fetcher: History.chathistory, 13 + on_connect(conn) { 14 + conn.queue.subscribe("tubes storage", (msg) => store_message(conn, msg)); 15 + }, 16 + }); 17 + 18 + adapters.value = [...adapters.value, adapter]; 19 + adapter.activate(); 20 + }
+17
neo/src/chat/colours.ts
··· 1 + const colours: string[] = [ 2 + "red", 3 + "orange", 4 + "yellow", 5 + "ectoplasm", 6 + "weezer", 7 + "aubergine", 8 + "bubblegum", 9 + ] 10 + 11 + export const pick_colour = (string: string): string => { 12 + const idx = string 13 + .split("") 14 + .reduce((acc, x) => x.charCodeAt(0) + acc, 0); 15 + 16 + return colours[idx % (colours.length)] 17 + }
+37
neo/src/chat/commands.ts
··· 1 + import { store_message } from "@src/storage"; 2 + import { ChatBuffer } from "tubes_core/channel"; 3 + 4 + export interface TubesCommand { 5 + description: string, 6 + activate: (params: string, buffer: ChatBuffer) => void, 7 + } 8 + 9 + export function execute_command(input: string, buffer: ChatBuffer) { 10 + if (input.charAt(0) == "/") { 11 + input = input.substring(1); 12 + } 13 + 14 + const cutoff = input.search(" "); 15 + const cmd_name = input.substring(0, cutoff == -1 ? 0 : cutoff); 16 + input = cutoff == -1 ? "" : input.substring(cutoff + 1); 17 + 18 + const cmd = commands[cmd_name]; 19 + 20 + if (!cmd) { 21 + throw new Error(`${cmd} is not a recognised command.`); 22 + } 23 + 24 + cmd.activate(input, buffer); 25 + } 26 + 27 + const commands = <Record<string, TubesCommand>>{ 28 + "me": { 29 + description: "", 30 + activate: (text, buffer) => { 31 + const msg = buffer.privmsg(`\x01ACTION ${text}\x01`) 32 + store_message(buffer.conn, msg); 33 + }, 34 + } 35 + }; 36 + 37 + export default commands;
+45
neo/src/chat/config.ts
··· 1 + import { Mutex } from "async-mutex" 2 + import { ConnectionConfig } from "tubes_core/connection"; 3 + 4 + const config_mutex = new Mutex(); 5 + 6 + const config_key = "tubes"; 7 + 8 + export interface TubesConfig { 9 + connections: ConnectionConfig[], 10 + adapters: { kind: string, [x: string]: any }[], 11 + } 12 + 13 + const write_config = async (new_config: any) => { 14 + localStorage.setItem(config_key, JSON.stringify(new_config)); 15 + } 16 + 17 + const open_config = async (cb: (config: TubesConfig, write: typeof write_config) => void) => { 18 + await config_mutex.runExclusive(() => { 19 + const config = fetch_config(); 20 + cb(config, write_config); 21 + }); 22 + } 23 + 24 + function fetch_config(): TubesConfig { 25 + const config_str = localStorage.getItem(config_key); 26 + if (!config_str) { 27 + init_config(); 28 + return fetch_config(); 29 + } 30 + 31 + const config = JSON.parse(config_str); 32 + // todo: validate 33 + 34 + return config; 35 + } 36 + 37 + function init_config() { 38 + write_config({ connections: [], adapters: [] }); 39 + } 40 + 41 + export default { 42 + read: fetch_config, 43 + open: open_config, 44 + key: config_key, 45 + }
+93
neo/src/chat/conns.ts
··· 1 + import type { Connection } from "tubes_core"; 2 + import { Connection as WsConnection } from "tubes_core/ws"; 3 + import { computed, signal, type Signal } from "@preact/signals"; 4 + import type { ConnectionConfig } from "tubes_core/connection"; 5 + import tubes_history from "./history"; 6 + import { store_message } from "@src/storage"; 7 + import { ChatBuffer } from "tubes_core/channel"; 8 + import { adapters } from "./adapters"; 9 + import Config from "./config"; 10 + 11 + const local_connections: Signal<Connection[]> = signal([]); 12 + export const connections: Signal<Connection[]> = computed(() => [ 13 + ...local_connections.value, 14 + ...adapters.value.flatMap(x => x.$connections.value) 15 + ]); 16 + 17 + let initialised = false; 18 + 19 + function initialise() { 20 + if (initialised) return; 21 + 22 + const config = Config.read(); 23 + console.log(config); 24 + 25 + local_connections.value = config.connections.map(x => init_connection(x)); 26 + 27 + initialised = true; 28 + } 29 + 30 + function init_connection(config: ConnectionConfig): WsConnection { 31 + const conn: WsConnection = new WsConnection(config, { 32 + history_fetcher: tubes_history, 33 + on_connect(conn) { 34 + conn.queue.subscribe("tubes storage", (msg) => store_message(conn, msg)); 35 + }, 36 + }); 37 + 38 + return conn; 39 + } 40 + 41 + function append_connection(conn: ConnectionConfig) { 42 + Config.open((config, write) => 43 + write({ ...config, connections: [...config.connections, conn] }) 44 + ); 45 + } 46 + 47 + function add_connection(config: ConnectionConfig) { 48 + const conn = init_connection(config); 49 + 50 + append_connection({ ...conn.config, id: conn.id }); 51 + local_connections.value = [...local_connections.value, conn]; 52 + conn.connect(); 53 + return conn.id; 54 + } 55 + 56 + function fetch_connection(id: string, adapter_id?: string): Connection | undefined { 57 + let connections = local_connections; 58 + if (adapter_id) { 59 + let adapter = adapters.value.find(x => x.id == adapter_id); 60 + if (!adapter) return; 61 + //@ts-ignore weird ass error that i don't think means anything. 62 + connections = adapter.$connections; 63 + } 64 + 65 + return connections.value.find(x => x.id == id); 66 + } 67 + 68 + function fetch_buffer(conn_id: string, name: string): ChatBuffer | undefined { 69 + return fetch_connection(conn_id)?.get_channel(name); 70 + } 71 + 72 + function add_autojoin(name: string, conn: Connection) { 73 + Config.open((config, write) => { 74 + const idx = config["connections"].findIndex(x => x.id == conn.id); 75 + if (idx == -1) return; 76 + 77 + config["connections"][idx].autojoin = [ 78 + ...config["connections"][idx]?.autojoin ?? [], 79 + name 80 + ]; 81 + 82 + write(config); 83 + }); 84 + } 85 + 86 + export default { 87 + connections, 88 + add: add_connection, 89 + initialise, 90 + fetch: fetch_connection, 91 + fetch_buffer, 92 + add_autojoin, 93 + }
+43
neo/src/chat/history.ts
··· 1 + import { db } from "@src/storage"; 2 + import { Connection } from "tubes_core"; 3 + import { FetchHistoryParams } from "tubes_core/history"; 4 + import { IrcMessage } from "tubes_core/parser"; 5 + import History from "tubes_core/history"; 6 + 7 + const fuzz = 1; 8 + 9 + export default async function tubes_history( 10 + conn: Connection, 11 + ...[target, range, limit]: FetchHistoryParams 12 + ): Promise<IrcMessage[]> { 13 + const res = await db.messages 14 + .where({ adapter_id: conn.adapter_id ?? "*", connection_id: conn.id, target }) 15 + .filter(x => { 16 + // todo: make unslow 17 + if (range == "latest") { 18 + return true; 19 + } 20 + 21 + if ("before" in range) { 22 + const res = x.timestamp.getTime() - range.before.getTime(); 23 + return res >= fuzz 24 + } 25 + 26 + if ("after" in range) { 27 + const res = range.after.getTime() - x.timestamp.getTime(); 28 + return res >= fuzz; 29 + } 30 + 31 + return false; 32 + }) 33 + .limit(limit) 34 + .sortBy('timestamp'); 35 + 36 + const msgs = res.map(x => x.message).toReversed(); 37 + 38 + if (msgs.length == 0) { 39 + return History.chathistory(conn, target, range, limit); 40 + } 41 + 42 + return msgs; 43 + }
+31
neo/src/css/add-network.css
··· 1 + div.adapter-selector { 2 + font-size: .8rem; 3 + padding: .25rem .5rem; 4 + border-radius: 4px; 5 + background-color: var(--colour-grey-50); 6 + color: var(--colour-grey-900); 7 + border: 1px solid var(--colour-grey-100); 8 + 9 + label { 10 + display: flex; 11 + align-items: baseline; 12 + gap: .25rem; 13 + } 14 + 15 + select { 16 + background-color: transparent; 17 + border: 0; 18 + font: inherit; 19 + font-weight: 500; 20 + padding: .1rem .25rem; 21 + border-radius: 4px; 22 + width: min-content; 23 + border: 1px solid var(--colour-grey-200); 24 + color: var(--colour-grey-900); 25 + background-color: white; 26 + 27 + &:hover { 28 + background-color: var(--colour-grey-100); 29 + } 30 + } 31 + }
+82
neo/src/css/buttons.css
··· 1 + .icon-button { 2 + background-color: transparent; 3 + border: none; 4 + padding: .15rem; 5 + width: min-content; 6 + aspect-ratio: 1 / 1; 7 + border-radius: 4px; 8 + color: inherit; 9 + cursor: pointer; 10 + 11 + &:hover { 12 + background-color: var(--colour-grey-100); 13 + } 14 + } 15 + 16 + .text-button { 17 + --hover: var(--hover-colour, var(--colour-grey-100)); 18 + --pressed: var(--pressed-colour, var(--colour-grey-200)); 19 + 20 + background-color: transparent; 21 + border: none; 22 + padding: .15rem .25rem; 23 + font-family: inherit; 24 + font-size: inherit; 25 + font-variation-settings: 'GRAD' 100; 26 + font-weight: 500; 27 + border-radius: 4px; 28 + 29 + display: flex; 30 + align-items: center; 31 + gap: .25rem; 32 + 33 + color: inherit; 34 + cursor: pointer; 35 + 36 + transition: background-color 40ms var(--easing-subtle-out); 37 + 38 + &:hover { 39 + background-color: var(--hover); 40 + } 41 + 42 + &:active { 43 + background-color: var(--pressed); 44 + } 45 + } 46 + 47 + .primary-button, .secondary-button { 48 + height: 2rem; 49 + padding: 0 .75rem; 50 + 51 + background-color: var(--colour-accent-200); 52 + border: 0; 53 + border-radius: 10000px; 54 + color: var(--colour-grey-900); 55 + 56 + font-family: inherit; 57 + font-size: .85rem; 58 + font-weight: 450; 59 + 60 + cursor: pointer; 61 + 62 + &.primary-button { 63 + background-color: var(--colour-accent-100); 64 + 65 + &:hover { 66 + background-color: var(--colour-accent-200); 67 + } 68 + } 69 + 70 + &.secondary-button { 71 + background-color: var(--colour-grey-100); 72 + &:hover { 73 + background-color: var(--colour-grey-200); 74 + } 75 + } 76 + } 77 + 78 + .button-row { 79 + display: flex; 80 + justify-content: space-between; 81 + gap: .5rem; 82 + }
+46
neo/src/css/connection-info.css
··· 1 + dialog.connection-info { 2 + display: grid; 3 + grid-template-columns: 1fr 1fr; 4 + grid-auto-rows: max-content; 5 + 6 + hgroup { 7 + height: 8rem; 8 + --width: 48rem; 9 + grid-column: 1 / -1; 10 + padding: 1.25rem calc((100% - var(--width)) / 2); 11 + 12 + display: flex; 13 + flex-direction: column; 14 + justify-content: end; 15 + gap: .25rem; 16 + 17 + p { 18 + font-size: .9rem; 19 + font-variation-settings: 'GRAD' 100; 20 + font-style: italic; 21 + color: var(--colour-grey-800); 22 + 23 + margin: 0; 24 + } 25 + } 26 + 27 + h1 { 28 + grid-column: 1 / -1; 29 + font-size: 1.5rem; 30 + font-weight: 500; 31 + /* font-style: italic; */ 32 + word-break: break-all; 33 + 34 + width: max-content; 35 + margin: 0; 36 + 37 + color: var(--colour-grey-950); 38 + } 39 + 40 + .sep { 41 + grid-column: 1 / -1; 42 + color: var(--colour-grey-200); 43 + border: none; 44 + border-top: 1px solid currentColor; 45 + } 46 + }
+82
neo/src/css/debug.css
··· 1 + article.debug { 2 + padding-bottom: 2rem; 3 + 4 + hgroup { 5 + display: flex; 6 + flex-direction: column; 7 + align-items: center; 8 + margin: 6vw auto; 9 + margin-bottom: 8vw; 10 + width: max-content; 11 + 12 + h1 { 13 + font-size: 12vw; 14 + font-weight: 100; 15 + font-style: italic; 16 + margin: 0; 17 + text-align: center; 18 + line-height: .7; 19 + user-select: none; 20 + } 21 + 22 + b { 23 + z-index: 1; 24 + font-weight: 900; 25 + font-variation-settings: 'GRAD' 100; 26 + } 27 + } 28 + 29 + div.sep { 30 + display: flex; 31 + white-space: nowrap; 32 + align-items: center; 33 + gap: 1rem; 34 + letter-spacing: .5rem; 35 + user-select: none; 36 + 37 + &::before, &::after { 38 + content: ''; 39 + width: 100%; 40 + height: 1px; 41 + background-color: var(--colour-grey-200); 42 + } 43 + } 44 + 45 + h2 { 46 + font-size: 6vw; 47 + font-weight: 200; 48 + font-style: italic; 49 + width: max-content; 50 + margin: 1rem; 51 + color: var(--colour-grey-200); 52 + margin-bottom: -0.5em; 53 + user-select: none; 54 + z-index: -1; 55 + } 56 + 57 + table { 58 + margin: auto; 59 + position: relative; 60 + z-index: 1; 61 + text-align: left; 62 + border-radius: 4px; 63 + border: 1px solid var(--colour-grey-200); 64 + border-spacing: 0; 65 + font-size: .9rem; 66 + 67 + th, td { 68 + padding: .5rem; 69 + } 70 + 71 + th { 72 + font-weight: 500; 73 + font-variation-settings: 'GRAD' 100; 74 + } 75 + 76 + thead { 77 + background-color: var(--colour-grey-100); 78 + border-bottom: 1px solid var(--colour-grey-200); 79 + font-style: italic; 80 + } 81 + } 82 + }
+23
neo/src/css/dialog.css
··· 1 + dialog.fancy-dialog { 2 + position: relative; 3 + z-index: 10; 4 + border: none; 5 + border-radius: 6px; 6 + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); 7 + padding: .75rem; 8 + width: 36rem; 9 + 10 + &::backdrop { 11 + display: none; 12 + } 13 + } 14 + 15 + .dialog-scrim { 16 + position: fixed; 17 + top: 0; 18 + left: 0; 19 + width: 100%; 20 + height: 100%; 21 + background-color: rgba(0, 0, 0, 0.25); 22 + z-index: 5; 23 + }
+48
neo/src/css/form.css
··· 1 + label.form-field { 2 + display: flex; 3 + flex-direction: column; 4 + gap: .25rem; 5 + 6 + --text-margin: .5rem; 7 + margin: .5rem 0; 8 + 9 + +label.form-field { 10 + margin-top: 1rem; 11 + } 12 + 13 + span { 14 + font-weight: 450; 15 + font-size: .85rem; 16 + font-variation-settings: 'GRAD' 100; 17 + margin: 0 var(--text-margin); 18 + margin-bottom: .1rem; 19 + line-height: 1.5; 20 + } 21 + 22 + &:has([required]) > span::after { 23 + content: ' *'; 24 + color: var(--colour-red-600); 25 + } 26 + 27 + p { 28 + font-size: .8rem; 29 + margin: 0 var(--text-margin); 30 + color: var(--colour-grey-700); 31 + line-height: 1.5; 32 + } 33 + 34 + input { 35 + padding: .5rem .5rem; 36 + font-family: inherit; 37 + font-size: .9rem; 38 + 39 + background-color: var(--colour-grey-50); 40 + border: 1px solid var(--colour-grey-200); 41 + border-radius: 6px; 42 + 43 + &:focus-visible { 44 + border-color: var(--colour-accent-300); 45 + outline: 3px solid var(--colour-accent-200); 46 + } 47 + } 48 + }
+168
neo/src/css/main.css
··· 1 + :root { 2 + --colour-ectoplasm-100: rgba(224, 247, 217, 1); 3 + --colour-ectoplasm-200: rgba(200, 242, 192, 1); 4 + --colour-ectoplasm-300: rgba(179, 241, 165, 1); 5 + --colour-ectoplasm-400: rgba(141, 219, 125, 1); 6 + --colour-ectoplasm-500: rgba(112, 202, 98, 1); 7 + --colour-ectoplasm-600: rgba(84, 180, 71, 1); 8 + --colour-ectoplasm-700: rgba(60, 126, 54, 1); 9 + --colour-ectoplasm-800: rgba(43, 92, 47, 1); 10 + --colour-ectoplasm-900: rgba(32, 74, 32, 1); 11 + --colour-ectoplasm-950: rgba(21, 53, 21, 1); 12 + --colour-ectoplasm-50: rgba(246, 253, 244, 1); 13 + --colour-aubergine-100: rgba(238, 229, 250, 1); 14 + --colour-aubergine-200: rgba(224, 207, 246, 1); 15 + --colour-aubergine-300: rgba(196, 158, 244, 1); 16 + --colour-aubergine-400: rgba(157, 97, 232, 1); 17 + --colour-aubergine-500: rgba(127, 48, 225, 1); 18 + --colour-aubergine-600: rgba(110, 42, 197, 1); 19 + --colour-aubergine-700: rgba(85, 30, 154, 1); 20 + --colour-aubergine-800: rgba(62, 22, 113, 1); 21 + --colour-aubergine-900: rgba(48, 13, 93, 1); 22 + --colour-aubergine-950: rgba(28, 4, 56, 1); 23 + --colour-aubergine-50: rgba(248, 244, 253, 1); 24 + --colour-weezer-100: rgba(216, 237, 245, 1); 25 + --colour-weezer-200: rgba(192, 226, 240, 1); 26 + --colour-weezer-300: rgba(153, 206, 227, 1); 27 + --colour-weezer-400: rgba(107, 186, 221, 1); 28 + --colour-weezer-500: rgba(75, 164, 212, 1); 29 + --colour-weezer-600: rgba(65, 146, 191, 1); 30 + --colour-weezer-700: rgba(51, 117, 160, 1); 31 + --colour-weezer-800: rgba(36, 84, 119, 1); 32 + --colour-weezer-900: rgba(25, 62, 86, 1); 33 + --colour-weezer-950: rgba(13, 39, 54, 1); 34 + --colour-weezer-50: rgba(244, 250, 253, 1); 35 + --colour-yellow-100: rgba(251, 244, 200, 1); 36 + --colour-yellow-200: rgba(247, 234, 156, 1); 37 + --colour-yellow-300: rgba(244, 227, 126, 1); 38 + --colour-yellow-400: rgba(241, 218, 91, 1); 39 + --colour-yellow-500: rgba(239, 202, 54, 1); 40 + --colour-yellow-600: rgba(225, 175, 15, 1); 41 + --colour-yellow-700: rgba(192, 139, 16, 1); 42 + --colour-yellow-800: rgba(128, 90, 34, 1); 43 + --colour-yellow-900: rgba(86, 63, 24, 1); 44 + --colour-yellow-950: rgba(58, 39, 9, 1); 45 + --colour-yellow-50: rgba(254, 252, 240, 1); 46 + --colour-orange-100: rgba(248, 225, 214, 1); 47 + --colour-orange-200: rgba(245, 207, 192, 1); 48 + --colour-orange-300: rgba(238, 175, 152, 1); 49 + --colour-orange-400: rgba(233, 144, 112, 1); 50 + --colour-orange-500: rgba(233, 124, 87, 1); 51 + --colour-orange-600: rgba(225, 87, 46, 1); 52 + --colour-orange-700: rgba(198, 69, 30, 1); 53 + --colour-orange-800: rgba(127, 49, 25, 1); 54 + --colour-orange-900: rgba(97, 36, 17, 1); 55 + --colour-orange-950: rgba(55, 20, 7, 1); 56 + --colour-orange-50: rgba(252, 244, 239, 1); 57 + --colour-red-100: rgba(244, 217, 220, 1); 58 + --colour-red-200: rgba(240, 187, 196, 1); 59 + --colour-red-300: rgba(237, 163, 171, 1); 60 + --colour-red-400: rgba(231, 136, 145, 1); 61 + --colour-red-500: rgba(218, 85, 96, 1); 62 + --colour-red-600: rgba(217, 57, 64, 1); 63 + --colour-red-700: rgba(189, 43, 46, 1); 64 + --colour-red-800: rgba(159, 36, 39, 1); 65 + --colour-red-900: rgba(123, 26, 29, 1); 66 + --colour-red-950: rgba(62, 10, 14, 1); 67 + --colour-red-50: rgba(251, 240, 241, 1); 68 + --colour-bubblegum-100: rgba(247, 219, 235, 1); 69 + --colour-bubblegum-200: rgba(238, 187, 216, 1); 70 + --colour-bubblegum-300: rgba(236, 163, 205, 1); 71 + --colour-bubblegum-400: rgba(234, 143, 196, 1); 72 + --colour-bubblegum-500: rgba(228, 108, 175, 1); 73 + --colour-bubblegum-600: rgba(223, 72, 153, 1); 74 + --colour-bubblegum-700: rgba(176, 46, 113, 1); 75 + --colour-bubblegum-800: rgba(131, 35, 87, 1); 76 + --colour-bubblegum-900: rgba(93, 20, 58, 1); 77 + --colour-bubblegum-950: rgba(56, 9, 35, 1); 78 + --colour-bubblegum-50: rgba(252, 239, 247, 1); 79 + --colour-grey-100: rgba(238, 238, 238, 1); 80 + --colour-grey-200: rgba(218, 218, 218, 1); 81 + --colour-grey-300: rgba(199, 198, 200, 1); 82 + --colour-grey-400: rgba(153, 148, 154, 1); 83 + --colour-grey-500: rgba(128, 124, 128, 1); 84 + --colour-grey-600: rgba(105, 101, 106, 1); 85 + --colour-grey-700: rgba(70, 67, 71, 1); 86 + --colour-grey-800: rgba(49, 46, 50, 1); 87 + --colour-grey-900: rgba(31, 29, 32, 1); 88 + --colour-grey-950: rgba(23, 21, 23, 1); 89 + --colour-grey-50: rgba(248, 248, 248, 1); 90 + --colour-brown-50: rgba(253, 248, 238, 1); 91 + --colour-brown-100: rgba(247, 238, 222, 1); 92 + --colour-brown-200: rgba(239, 210, 173, 1); 93 + --colour-brown-300: rgba(218, 169, 124, 1); 94 + --colour-brown-400: rgba(199, 142, 92, 1); 95 + --colour-brown-500: rgba(177, 111, 61, 1); 96 + --colour-brown-600: rgba(144, 86, 43, 1); 97 + --colour-brown-700: rgba(116, 69, 34, 1); 98 + --colour-brown-800: rgba(84, 47, 20, 1); 99 + --colour-brown-900: rgba(64, 36, 12, 1); 100 + --colour-brown-950: rgba(42, 24, 5, 1); 101 + 102 + --easing-subtle-out: cubic-bezier(0.165, 0.84, 0.44, 1); 103 + 104 + --font: 'Roboto Serif Variable', 'Roboto Serif', serif; 105 + font-family: var(--font); 106 + } 107 + 108 + ::selection { 109 + background-color: var(--colour-accent-700); 110 + color: white; 111 + } 112 + 113 + *, *::after, *::before { 114 + box-sizing: border-box; 115 + } 116 + 117 + input { 118 + accent-color: var(--colour-accent-700); 119 + } 120 + 121 + body, html { 122 + padding: 0; 123 + margin: 0; 124 + 125 + height: 100vh; 126 + } 127 + 128 + main.app { 129 + --sidebar-border: 1px solid var(--colour-grey-200); 130 + 131 + display: grid; 132 + grid-template-columns: max-content 1fr; 133 + grid-template-rows: 1fr min-content; 134 + height: 100vh; 135 + } 136 + 137 + main.app > :not(aside):not(footer) { 138 + height: 100vh; 139 + overflow: auto; 140 + grid-column: 2; 141 + grid-row: 1 / -1; 142 + } 143 + 144 + article.yeah { 145 + max-width: 56rem; 146 + width: 100%; 147 + padding: 0 1rem; 148 + margin: auto; 149 + 150 + h1.title { 151 + margin-top: 4rem; 152 + } 153 + } 154 + 155 + .error-message { 156 + display: flex; 157 + gap: .25rem; 158 + align-items: first baseline; 159 + color: var(--colour-red-700); 160 + font-size: .9rem; 161 + margin: 0; 162 + line-height: 1.5; 163 + 164 + svg { 165 + width: 12px; 166 + height: 12px; 167 + } 168 + }
+376
neo/src/css/messages.css
··· 1 + .message-list { 2 + display: grid; 3 + grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr)); 4 + grid-template-rows: auto 1fr auto; 5 + justify-content: center; 6 + max-height: 100vh; 7 + height: 100vh; 8 + gap: 0 .5rem; 9 + 10 + container-type: inline-size; 11 + 12 + > ul.messages { 13 + grid-column: 1 / -1; 14 + padding: .5rem 0; 15 + margin: 0; 16 + list-style: none; 17 + 18 + display: grid; 19 + grid-template-columns: subgrid; 20 + grid-template-rows: 1fr ; 21 + grid-auto-rows: max-content; 22 + font-size: .9rem; 23 + 24 + overflow: scroll; 25 + row-gap: .25rem; 26 + 27 + > li { 28 + display: grid; 29 + grid-template-columns: subgrid; 30 + align-items: baseline; 31 + grid-column: 1 / -1; 32 + 33 + padding: .125rem .75rem; 34 + 35 + @container (width < 42rem) { 36 + display: flex; 37 + flex-direction: column; 38 + padding: .25rem .75rem; 39 + 40 + span { 41 + line-height: 1.2; 42 + } 43 + } 44 + 45 + ::selection { 46 + background-color: var(--selection); 47 + color: white; 48 + } 49 + 50 + div.left-bit { 51 + display: inline-flex; 52 + gap: .25rem; 53 + align-items: baseline; 54 + grid-column: 1; 55 + } 56 + 57 + span.name { 58 + display: block; 59 + align-items: baseline; 60 + gap: .25rem; 61 + justify-self: end; 62 + font-weight: 500; 63 + font-variation-settings: 'GRAD' 100; 64 + color: var(--colour); 65 + 66 + width: max-content; 67 + overflow: hidden; 68 + white-space: nowrap; 69 + text-overflow: ellipsis; 70 + 71 + &.squish { 72 + font-stretch: ultra-condensed; 73 + 74 + &:hover { 75 + position: relative; 76 + z-index: 1; 77 + width: 1000%; 78 + overflow: visible; 79 + padding-right: .25rem; 80 + background-color: white; 81 + outline: 1px solid var(--colour-grey-100); 82 + font-stretch: normal; 83 + } 84 + } 85 + } 86 + 87 + span.action { 88 + display: block; 89 + line-height: 0; 90 + height: 0rem; 91 + margin-top: .6rem; 92 + align-self: center; 93 + font-size: 2rem; 94 + font-weight: 200; 95 + color: var(--colour-grey-700); 96 + user-select: none; 97 + } 98 + 99 + span.name.gone { 100 + font-variation-settings: 'GRAD' 0; 101 + font-weight: 400; 102 + filter: saturate(50%); 103 + 104 + > svg { 105 + align-self: center; 106 + color: var(--colour-grey-700); 107 + } 108 + } 109 + 110 + time { 111 + font-size: .7rem; 112 + font-variation-settings: 'GRAD' 100; 113 + font-variant-numeric: tabular-nums; 114 + color: var(--colour-grey-500); 115 + margin-right: auto; 116 + } 117 + 118 + > p { 119 + grid-column: 2 / -2; 120 + margin: 0; 121 + line-height: 1.6; 122 + color: var(--colour-grey-950); 123 + } 124 + 125 + &:hover { 126 + background-color: color-mix(in srgb, var(--hover), transparent 50%); 127 + } 128 + } 129 + } 130 + 131 + &.comfy { 132 + ul.messages > li { 133 + grid-column: 2 / -2; 134 + padding: 0 1rem; 135 + margin: 0 -1rem; 136 + border-radius: 4px; 137 + display: flex; 138 + flex-direction: column; 139 + } 140 + } 141 + } 142 + 143 + .message-input { 144 + grid-column: 1 / -1; 145 + 146 + display: grid; 147 + grid-template-columns: subgrid; 148 + gap: 0; 149 + 150 + height: 3.75rem; 151 + margin-top: 0; 152 + padding: .75rem 0; 153 + padding-top: .5rem; 154 + 155 + color: var(--colour-grey-900); 156 + border-top: 1px solid transparent; 157 + 158 + --radius: 8px; 159 + --bg: var(--colour-grey-50); 160 + --border: 1px solid var(--colour-grey-200); 161 + 162 + @container (width < 52rem) { 163 + display: flex; 164 + margin: 0 .75rem; 165 + 166 + .input { 167 + flex: 1; 168 + } 169 + } 170 + 171 + &.scrolled { 172 + border-color: var(--colour-grey-100); 173 + } 174 + 175 + .input { 176 + grid-column: 2 / -2; 177 + background-color: var(--bg); 178 + border: var(--border); 179 + border-left: none; 180 + border-right: none; 181 + } 182 + 183 + input { 184 + height: 100%; 185 + width: 100%; 186 + 187 + font: inherit; 188 + font-size: .85rem; 189 + 190 + background-color: transparent; 191 + border: none; 192 + padding-left: .25rem; 193 + 194 + &:focus-visible { 195 + outline: none; 196 + } 197 + } 198 + 199 + .input, .prefix, .suffix { 200 + position: relative; 201 + } 202 + 203 + button { 204 + font: inherit; 205 + 206 + &:hover { 207 + background-color: var(--colour-grey-100); 208 + cursor: pointer; 209 + } 210 + } 211 + 212 + /* can't use an outline bc it's 3 different elements styled to appear connected 213 + so, this */ 214 + &:has(:focus-visible) { 215 + --width: 4px; 216 + --border2: var(--width) solid var(--colour-accent-200); 217 + --radius2: calc(var(--radius) + var(--width) - 1px); 218 + --border: 1px solid var(--colour-accent-300); 219 + 220 + .input::before, .prefix::before, .suffix::before { 221 + content: ''; 222 + position: absolute; 223 + top: calc(var(--width) * -1); 224 + left: calc(var(--width) * -1); 225 + right: calc(var(--width) * -1); 226 + bottom: calc(var(--width) * -1); 227 + border: var(--border2); 228 + pointer-events: none; 229 + z-index: -1; 230 + } 231 + 232 + .input::before { 233 + border-right: none; 234 + border-left: none; 235 + border-radius: 0; 236 + } 237 + 238 + .prefix::before { 239 + border-right: none; 240 + border-radius: var(--radius2) 0 0 var(--radius2); 241 + } 242 + 243 + .suffix::before { 244 + border-left: none; 245 + border-radius: 0 var(--radius2) var(--radius2)0; 246 + } 247 + } 248 + 249 + .prefix, .suffix { 250 + color: var(--colour-grey-800); 251 + } 252 + 253 + .prefix { 254 + justify-self: end; 255 + height: 100%; 256 + padding-left: .25rem; 257 + 258 + border-radius: var(--radius) 0 0 var(--radius); 259 + background-color: var(--bg); 260 + border: var(--border); 261 + border-right: none; 262 + 263 + display: flex; 264 + width: auto; 265 + aspect-ratio: 1 / 1; 266 + 267 + svg { 268 + margin: auto; 269 + } 270 + } 271 + 272 + .suffix { 273 + justify-self: start; 274 + height: 100%; 275 + padding-left: .25rem; 276 + 277 + border-radius: 0 var(--radius) var(--radius) 0; 278 + background-color: var(--bg); 279 + border: var(--border); 280 + border-left: none; 281 + 282 + display: flex; 283 + width: auto; 284 + aspect-ratio: 1 / 1; 285 + 286 + svg { 287 + margin: auto; 288 + } 289 + } 290 + 291 + } 292 + 293 + .channel-header { 294 + grid-column: 1 / -1; 295 + border-bottom: 1px solid var(--colour-grey-100); 296 + display: flex; 297 + gap: .5rem; 298 + 299 + padding: .725rem 1rem; 300 + 301 + align-items: baseline; 302 + 303 + h1 { 304 + font-size: .95rem; 305 + margin: 0; 306 + font-weight: 450; 307 + font-variation-settings: 'GRAD' 100; 308 + color: var(--colour-grey-950); 309 + } 310 + 311 + p { 312 + margin: 0; 313 + font-size: .825rem; 314 + color: var(--colour-grey-800); 315 + white-space: nowrap; 316 + overflow: hidden; 317 + text-overflow: ellipsis; 318 + } 319 + } 320 + 321 + .message-list-start { 322 + display: grid; 323 + grid-template-columns: subgrid; 324 + grid-auto-rows: max-content; 325 + place-content: end start; 326 + grid-column: 1 / -1; 327 + height: 100%; 328 + padding-bottom: 1rem; 329 + padding-top: 8rem; 330 + 331 + > * { 332 + grid-column: 2 / -2; 333 + } 334 + 335 + b { 336 + font-size: 1rem; 337 + font-weight: 500; 338 + font-variation-settings: 'GRAD' 100; 339 + color: var(--colour-grey-800); 340 + } 341 + 342 + .subtitle { 343 + font-size: .85rem; 344 + margin-top: .5rem; 345 + color: var(--colour-grey-700); 346 + } 347 + 348 + .chathistory-warning { 349 + color: var(--colour-weezer-800); 350 + font-weight: 400; 351 + font-variation-settings: 'GRAD' 100; 352 + font-size: .8rem; 353 + display: flex; 354 + align-items: center; 355 + gap: .25rem; 356 + 357 + margin: 0; 358 + 359 + svg { 360 + width: 8px; 361 + height: 8px; 362 + } 363 + } 364 + } 365 + 366 + .messages.skeleton { 367 + li { 368 + span, p { 369 + display: inline; 370 + width: max-content; 371 + max-width: 100%; 372 + background-color: var(--colour-grey-100); 373 + color: transparent !important; 374 + } 375 + } 376 + }
+22
neo/src/css/placeholders.css
··· 1 + article.no-buffer { 2 + display: grid; 3 + justify-items: center; 4 + align-content: center; 5 + grid-auto-rows: min-content; 6 + gap: .75rem; 7 + 8 + h2 { 9 + font-size: 1rem; 10 + font-weight: 500; 11 + font-variation-settings: 'GRAD' 100; 12 + color: var(--colour-grey-800); 13 + margin: 0; 14 + } 15 + 16 + span { 17 + font-size: .9rem; 18 + color: var(--colour-grey-700); 19 + font-style: italic; 20 + margin: 0; 21 + } 22 + }
+92
neo/src/css/popover.css
··· 1 + .popover { 2 + margin: 0; 3 + padding: 0; 4 + border: 0; 5 + overflow: visible; 6 + background-color: transparent; 7 + 8 + position: absolute; 9 + left: var(--x); 10 + top: var(--y); 11 + 12 + transform-origin: top left; 13 + 14 + &::backdrop { 15 + background-color: rgba(0, 0, 0, 0.1); 16 + } 17 + 18 + &:has(.list-menu)::backdrop { 19 + background-color: transparent; 20 + } 21 + } 22 + 23 + .popover-innards { 24 + padding: .75rem; 25 + min-width: 18rem; 26 + width: 28rem; 27 + 28 + background-color: white; 29 + border: 1px solid var(--colour-grey-200); 30 + border-radius: 6px; 31 + box-shadow: 0px 2px 8px var(--colour-grey-200); 32 + } 33 + 34 + .list-menu { 35 + padding: .25rem; 36 + list-style: none; 37 + 38 + background-color: white; 39 + border: 1px solid var(--colour-grey-200); 40 + border-radius: 8px; 41 + box-shadow: 0px 2px 8px var(--colour-grey-200); 42 + 43 + display: flex; 44 + flex-direction: column; 45 + gap: .1rem; 46 + 47 + min-width: 12rem; 48 + 49 + > li > button { 50 + padding: .375rem; 51 + border-radius: 4px; 52 + padding-right: 1rem; 53 + width: 100%; 54 + 55 + background-color: transparent; 56 + border: none; 57 + 58 + display: flex; 59 + align-items: center; 60 + gap: .375rem; 61 + 62 + font: inherit; 63 + font-size: .85rem; 64 + font-variation-settings: 'GRAD' 100; 65 + font-weight: 400; 66 + color: var(--colour-grey-800); 67 + 68 + cursor: pointer; 69 + 70 + svg { 71 + width: 16px; 72 + height: 16px; 73 + } 74 + 75 + &.danger { 76 + color: var(--colour-red-700); 77 + 78 + &:hover { 79 + color: var(--colour-red-800); 80 + } 81 + } 82 + 83 + &:hover { 84 + background-color: var(--colour-grey-50); 85 + color: var(--colour-grey-950); 86 + } 87 + 88 + &:active { 89 + background-color: var(--colour-grey-100); 90 + } 91 + } 92 + }
+232
neo/src/css/settings.css
··· 1 + article.settings { 2 + padding-bottom: 2rem; 3 + container-type: inline-size; 4 + 5 + h1 { 6 + text-align: center; 7 + margin-top: .4em; 8 + margin-bottom: .5em; 9 + color: var(--colour-grey-900); 10 + } 11 + 12 + h2 { 13 + font-size: 6vw; 14 + font-weight: 200; 15 + font-style: italic; 16 + width: max-content; 17 + color: var(--colour-grey-200); 18 + margin: 1rem; 19 + margin-bottom: -0.4em; 20 + user-select: none; 21 + z-index: -1; 22 + letter-spacing: -0.025em; 23 + } 24 + 25 + div.sep { 26 + display: flex; 27 + white-space: nowrap; 28 + align-items: center; 29 + gap: 1rem; 30 + letter-spacing: .5rem; 31 + user-select: none; 32 + 33 + &::before, &::after { 34 + content: ''; 35 + width: 100%; 36 + height: 1px; 37 + background-color: var(--colour-grey-200); 38 + } 39 + } 40 + 41 + div.panel { 42 + position: relative; 43 + z-index: 1; 44 + max-width: 56rem; 45 + width: 100%; 46 + margin: auto; 47 + margin-bottom: 2rem; 48 + background-color: white; 49 + border-radius: 6px; 50 + padding: .5rem; 51 + border: 1px solid var(--colour-grey-200); 52 + 53 + display: grid; 54 + grid-template-columns: 1fr 1fr; 55 + gap: 1rem; 56 + 57 + @container (max-width: 48rem) { 58 + grid-template-columns: 1fr; 59 + } 60 + 61 + p.intro { 62 + grid-row: 1; 63 + grid-column: 1 / -1; 64 + margin: 0; 65 + color: var(--colour-grey-700); 66 + } 67 + } 68 + 69 + fieldset { 70 + legend { 71 + font-weight: 450; 72 + padding: 0; 73 + margin-bottom: .5rem; 74 + font-size: .95rem; 75 + font-variation-settings: 'GRAD' 100; 76 + } 77 + } 78 + } 79 + 80 + fieldset.colour-select { 81 + border: none; 82 + padding: 0; 83 + margin: 0; 84 + width: max-content; 85 + 86 + display: flex; 87 + gap: 2px; 88 + flex-wrap: wrap; 89 + 90 + label { 91 + position: relative; 92 + background-color: var(--colour); 93 + overflow: hidden; 94 + 95 + height: 2.5rem; 96 + aspect-ratio: 1 / 1; 97 + 98 + color: transparent; 99 + user-select: none; 100 + cursor: pointer; 101 + 102 + input { 103 + position: absolute; 104 + clip: rect(0, 0, 0, 0); 105 + } 106 + 107 + &:has(:focus-visible) { 108 + outline: 4px solid var(--colour-accent-400); 109 + } 110 + 111 + &:not(:has(:checked)):hover { 112 + background-color: var(--hover); 113 + } 114 + 115 + &:has(:checked) { 116 + z-index: 1; 117 + border-radius: 100%; 118 + } 119 + 120 + &:has(:checked)::after { 121 + position: absolute; 122 + top: 0; 123 + left: 0; 124 + right: 0; 125 + bottom: 0; 126 + margin: auto; 127 + height: 1rem; 128 + aspect-ratio: 1 / 1; 129 + border-radius: 100%; 130 + content: ''; 131 + margin: auto; 132 + background-color: white; 133 + } 134 + 135 + &:first-of-type:not(:has(:checked)) { 136 + border-radius: 6px 0 0 6px; 137 + } 138 + 139 + &:last-of-type:not(:has(:checked)) { 140 + border-radius: 0 6px 6px 0; 141 + } 142 + } 143 + } 144 + 145 + fieldset.comfy-compact-dichotomy { 146 + border: 0; 147 + padding: 0; 148 + margin: 0; 149 + 150 + display: grid; 151 + grid-template-columns: 1fr 1fr; 152 + margin-right: 1rem; 153 + gap: 1rem; 154 + 155 + label { 156 + cursor: pointer; 157 + 158 + &:hover:not(:has(:checked)) .preview { 159 + border-color: var(--colour-grey-300); 160 + } 161 + } 162 + 163 + input { 164 + position: absolute; 165 + clip: rect(0, 0, 0, 0); 166 + margin: 0; 167 + } 168 + 169 + figure { 170 + margin: 0; 171 + } 172 + 173 + figcaption { 174 + font-size: .85rem; 175 + font-weight: 500; 176 + margin: .25rem 0; 177 + text-align: center; 178 + color: var(--colour-grey-700); 179 + font-style: italic; 180 + } 181 + 182 + .preview { 183 + display: flex; 184 + gap: .25rem; 185 + 186 + width: 100%; 187 + height: 4rem; 188 + padding: .75rem; 189 + 190 + border-radius: 4px; 191 + border: 4px solid var(--colour-grey-200); 192 + 193 + font-size: .85rem; 194 + user-select: none; 195 + 196 + .name { 197 + font-variation-settings: 'GRAD' 100; 198 + font-weight: 500; 199 + color: var(--colour-accent-700); 200 + } 201 + 202 + &.compact { 203 + align-items: center; 204 + justify-content: center; 205 + } 206 + 207 + &.comfy { 208 + flex-direction: column; 209 + justify-content: center; 210 + } 211 + 212 + p { 213 + margin: 0; 214 + } 215 + 216 + } 217 + 218 + :has(:checked) { 219 + figcaption { 220 + font-variation-settings: 'GRAD' 100; 221 + color: var(--colour-grey-900); 222 + } 223 + 224 + .preview { 225 + border-color: var(--colour-accent-400); 226 + } 227 + } 228 + 229 + :has(:focus-visible) .preview { 230 + outline: 4px solid var(--colour-accent-200); 231 + } 232 + }
+360
neo/src/css/sidebar.css
··· 1 + aside.main-sidebar { 2 + border-right: var(--sidebar-border); 3 + width: 16rem; 4 + height: 100%; 5 + overflow: auto; 6 + padding-bottom: 1rem; 7 + 8 + --inner-padding: .75rem; 9 + } 10 + 11 + aside.main-sidebar > dialog.expanded { 12 + position: fixed; 13 + top: 0; 14 + left: 0; 15 + min-height: 100vh; 16 + height: 100vh; 17 + overflow: auto; 18 + width: 50vw; 19 + margin: 0; 20 + border: none; 21 + padding: 0; 22 + z-index: 10; 23 + border-right: 1px solid var(--colour-grey-200); 24 + } 25 + 26 + header.sidebar-header { 27 + display: flex; 28 + justify-content: space-between; 29 + align-items: center; 30 + padding: .6rem var(--inner-padding); 31 + background: linear-gradient(to bottom, var(--colour-accent-50), transparent); 32 + 33 + h1 { 34 + margin: 0; 35 + 36 + user-select: none; 37 + 38 + color: var(--colour-grey-600); 39 + font-size: .6rem; 40 + font-weight: 500; 41 + font-variation-settings: 'GRAD' 100; 42 + } 43 + 44 + + section.sidebar-section { 45 + margin-top: 0rem; 46 + } 47 + 48 + button { 49 + color: var(--colour-grey-800); 50 + /* just to account for the icon button having padding */ 51 + margin-right: -0.15rem; 52 + } 53 + } 54 + 55 + section.sidebar-section { 56 + margin-top: .75rem; 57 + 58 + > header > h2 { 59 + margin: 0; 60 + margin-right: auto; 61 + } 62 + 63 + > header > h2 > button { 64 + all: unset; 65 + cursor: pointer; 66 + 67 + margin: 0; 68 + margin-right: auto; 69 + 70 + font: inherit; 71 + font-size: .825rem; 72 + font-weight: 450; 73 + color: var(--colour-grey-950); 74 + font-variation-settings: 'GRAD' 100; 75 + 76 + display: flex; 77 + align-items: baseline; 78 + gap: .25rem; 79 + 80 + svg { 81 + width: 10px; 82 + height: 10px; 83 + } 84 + 85 + &.errored { 86 + color: var(--colour-red-800); 87 + font-weight: 550; 88 + } 89 + 90 + &.disconnected { 91 + color: var(--colour-grey-700); 92 + font-weight: 400; 93 + font-variation-settings: 'GRAD' 0; 94 + } 95 + 96 + &:hover { 97 + text-decoration: underline; 98 + } 99 + 100 + &:focus-visible { 101 + outline: 2px solid var(--colour-accent-500); 102 + outline-offset: 2px; 103 + } 104 + } 105 + 106 + &:has(+ .sidebar-section) { 107 + padding-bottom: .5rem; 108 + border-bottom: 1px solid var(--colour-grey-100); 109 + } 110 + 111 + > header { 112 + display: flex; 113 + align-items: center; 114 + gap: .25rem; 115 + margin: 0 var(--inner-padding); 116 + margin-bottom: .25rem; 117 + margin-right: .5rem; 118 + 119 + & > button { 120 + color: transparent; 121 + transition: color 75ms var(--easing-subtle-out); 122 + 123 + &:last-of-type { 124 + color: var(--colour-grey-400); 125 + } 126 + } 127 + } 128 + 129 + &:hover > header > button, 130 + &:has(:focus-visible) > header > button { 131 + color: var(--colour-grey-800); 132 + } 133 + } 134 + 135 + ul.sidebar-list { 136 + list-style: none; 137 + padding: 0; 138 + margin: 0; 139 + 140 + display: flex; 141 + flex-direction: column; 142 + gap: .15rem; 143 + 144 + > li.sidebar-item { 145 + display: flex; 146 + flex: 1; 147 + 148 + &:not(:has(> button, a)), 149 + > button, 150 + > a { 151 + display: flex; 152 + flex: 1; 153 + gap: .25rem; 154 + align-items: center; 155 + justify-content: space-between; 156 + 157 + height: 1.65rem; 158 + padding: 0 .5rem; 159 + margin: 0 .25rem; 160 + 161 + background-color: transparent; 162 + border: none; 163 + font: inherit; 164 + text-decoration: none; 165 + cursor: pointer; 166 + 167 + border-radius: 5px; 168 + 169 + font-size: 0.825rem; 170 + color: var(--colour-grey-900); 171 + line-height: 1; 172 + 173 + &:hover { 174 + background-color: var(--colour-grey-100); 175 + } 176 + } 177 + 178 + &.selected:not(:has(> button, a)), 179 + &.selected > button, 180 + &.selected > a { 181 + background-color: var(--colour-accent-100); 182 + color: var(--colour-grey-950); 183 + font-variation-settings: 'GRAD' 100; 184 + } 185 + } 186 + } 187 + 188 + .connection-error { 189 + background-color: var(--colour-red-100); 190 + border-left: 4px solid var(--colour-red-700); 191 + padding: .65rem; 192 + margin-bottom: .25rem; 193 + 194 + h3 { 195 + margin: .25rem 0; 196 + font-size: .85rem; 197 + font-weight: 400; 198 + font-variation-settings: 'GRAD' 100; 199 + } 200 + 201 + p { 202 + margin: 0; 203 + color: var(--colour-grey-800); 204 + } 205 + 206 + blockquote { 207 + position: relative; 208 + z-index: 1; 209 + margin: .5rem 0; 210 + text-align: right; 211 + 212 + &::before { 213 + position: absolute; 214 + top: 0; 215 + left: -0.25rem; 216 + font-size: 8rem; 217 + line-height: .75; 218 + z-index: -1; 219 + content: "“"; 220 + color: var(--colour-red-200); 221 + } 222 + 223 + p { 224 + color: var(--colour-grey-950); 225 + } 226 + 227 + footer { 228 + color: var(--colour-grey-600); 229 + } 230 + } 231 + 232 + .buttons { 233 + display: flex; 234 + justify-content: space-between; 235 + color: var(--colour-red-900); 236 + font-size: .8rem; 237 + margin: 0 -0.25rem; 238 + margin-top: .5rem; 239 + 240 + --hover-colour: var(--colour-red-200); 241 + --pressed-colour: var(--colour-red-300); 242 + } 243 + } 244 + 245 + footer.sidebar-footer { 246 + border-right: var(--sidebar-border); 247 + border-top: 1px solid var(--colour-grey-100); 248 + margin-top: auto; 249 + 250 + grid-row: 2; 251 + grid-column: 1; 252 + 253 + height: 3rem; 254 + display: flex; 255 + align-items: center; 256 + justify-content: space-between; 257 + padding: 0 .5rem; 258 + 259 + a { 260 + font-size: .8rem; 261 + font-weight: 400; 262 + padding: .25rem; 263 + color: var(--colour-grey-700); 264 + text-decoration: none; 265 + border-radius: 4px; 266 + font-variation-settings: 'GRAD' 100; 267 + 268 + display: flex; 269 + gap: .25rem; 270 + 271 + &:hover { 272 + background-color: var(--colour-grey-100); 273 + } 274 + } 275 + } 276 + 277 + fieldset.view-switcher { 278 + border: 1px solid var(--colour-grey-200); 279 + background-color: var(--colour-grey-100); 280 + border-radius: 6px; 281 + padding: .1rem; 282 + 283 + display: flex; 284 + align-items: center; 285 + gap: 1px; 286 + 287 + label { 288 + display: flex; 289 + align-items: center; 290 + padding: .15rem; 291 + border: 1px solid transparent; 292 + border-radius: 4px; 293 + cursor: pointer; 294 + 295 + color: var(--colour-grey-700); 296 + 297 + &:has(:checked) { 298 + border-color: var(--colour-grey-200); 299 + background-color: white; 300 + color: var(--colour-grey-950); 301 + } 302 + 303 + &:not(:has(:checked)):hover { 304 + background-color: var(--colour-grey-200); 305 + } 306 + 307 + input { 308 + position: absolute; 309 + clip: rect(0, 0, 0, 0); 310 + } 311 + 312 + svg { 313 + width: 17px; 314 + height: 17px; 315 + } 316 + } 317 + 318 + &:has(:focus-visible) label:has(:checked) { 319 + outline: 2px solid var(--colour-accent-400); 320 + } 321 + } 322 + 323 + @keyframes addbutton-rainbow { 324 + to { 325 + background-position: -200% 0 326 + } 327 + } 328 + 329 + button.add-network { 330 + font-size: .75rem; 331 + font-weight: 400; 332 + margin: 1rem auto; 333 + color: var(--colour-grey-600); 334 + font-variation-settings: 'GRAD' 0; 335 + transition: 100ms var(--easing-subtle-out); 336 + transition-property: color, font-variation-settings; 337 + padding: .4rem .5rem; 338 + 339 + @media screen and not (prefers-reduced-motion) { 340 + &:hover { 341 + animation: addbutton-rainbow 2s linear infinite; 342 + } 343 + } 344 + 345 + &:hover { 346 + background: 347 + linear-gradient(to right, 348 + var(--colour-red-200), 349 + var(--colour-orange-200), 350 + var(--colour-yellow-200), 351 + var(--colour-ectoplasm-200), 352 + var(--colour-weezer-200), 353 + var(--colour-aubergine-200), 354 + var(--colour-bubblegum-200), 355 + var(--colour-red-200)) 0 0/200% 100%; 356 + 357 + font-variation-settings: 'GRAD' 100; 358 + color: black; 359 + } 360 + }
+37
neo/src/css/type.css
··· 1 + /* .heading-1, h1.heading { 2 + } */ 3 + 4 + .body-small { 5 + font-size: 0.8rem; 6 + line-height: 1.5; 7 + } 8 + 9 + .title-1, h1.title { 10 + font-size: clamp(4rem, 10vw, 12rem); 11 + font-weight: 100; 12 + font-style: italic; 13 + letter-spacing: -0.025em; 14 + margin: 0; 15 + line-height: 1; 16 + user-select: none; 17 + } 18 + 19 + .heading-2, h2.heading { 20 + display: block; 21 + font: inherit; 22 + margin: .25em 0; 23 + font-size: .9rem; 24 + font-weight: 450; 25 + color: var(--colour-grey-950); 26 + font-variation-settings: 'GRAD' 100; 27 + 28 + hgroup > & { 29 + margin-bottom: 0; 30 + 31 + + p { 32 + font-size: .85rem; 33 + margin-top: .5em; 34 + color: var(--colour-grey-700); 35 + } 36 + } 37 + }
+81
neo/src/debug/index.tsx
··· 1 + import "@css/debug.css"; 2 + import { FunctionalComponent } from "preact"; 3 + import { Connection } from "tubes_core"; 4 + 5 + const DebugView: FunctionalComponent<{ conn: Connection }> = ({ conn }) => <article class="debug"> 6 + <hgroup> 7 + <b>{conn.label}</b> 8 + <h1 class="title">debugzone</h1> 9 + </hgroup> 10 + <div class="sep" aria-hidden>🪲🐛🐞</div> 11 + <section> 12 + <h2>negotiated</h2> 13 + <table> 14 + <thead> 15 + <tr> 16 + <th>capability</th> 17 + <th>value</th> 18 + </tr> 19 + </thead> 20 + <tbody> 21 + {Array.from(conn.capabilities).map(([k, v]) => <tr> 22 + <th>{k}</th> 23 + <td>{v}</td> 24 + </tr>)} 25 + </tbody> 26 + </table> 27 + </section> 28 + <section> 29 + <h2>available</h2> 30 + <table> 31 + <thead> 32 + <tr> 33 + <th>capability</th> 34 + <th>value</th> 35 + </tr> 36 + </thead> 37 + <tbody> 38 + {Array.from(conn.available_capabilities).map(([k, v]) => <tr> 39 + <th>{k}</th> 40 + <td>{v}</td> 41 + </tr>)} 42 + </tbody> 43 + </table> 44 + </section> 45 + <section> 46 + <h2>isupport</h2> 47 + <table> 48 + <thead> 49 + <tr> 50 + <th>token</th> 51 + <th>value</th> 52 + </tr> 53 + </thead> 54 + <tbody> 55 + {Object.entries(conn.isupport ?? {}).map(([k, v]) => <tr> 56 + <th>{k}</th> 57 + <td>{v}</td> 58 + </tr>)} 59 + </tbody> 60 + </table> 61 + </section> 62 + <section> 63 + <h2>queue</h2> 64 + <table> 65 + <thead> 66 + <tr> 67 + <th>description</th> 68 + <th>stringified</th> 69 + </tr> 70 + </thead> 71 + <tbody> 72 + {Object.values(conn.queue.tasks ?? {}).map((v) => <tr> 73 + <th>“{v.description}”</th> 74 + <td>{JSON.stringify(v)}</td> 75 + </tr>)} 76 + </tbody> 77 + </table> 78 + </section> 79 + </article> 80 + 81 + export default DebugView;
+52
neo/src/main.tsx
··· 1 + import '@css/main.css'; 2 + import '@css/type.css'; 3 + import "@fontsource-variable/roboto-serif/full-italic.css"; 4 + import "@fontsource-variable/roboto-serif/full.css"; 5 + 6 + import NoBufferPlaceholder from "@src/bits/placeholder"; 7 + import { Sidebar } from '@src/bits/sidebar/sidebar'; 8 + import BufferView from "@src/buffer/view"; 9 + import Connections from "@src/chat/conns"; 10 + import DebugView from "@src/debug"; 11 + import { render } from 'preact'; 12 + import { Route, Router, Switch } from 'wouter-preact'; 13 + import { useHashLocation } from "wouter-preact/use-hash-location"; 14 + import SettingsPage from './settings'; 15 + import { accent, build_accent_css } from './support'; 16 + 17 + Connections.initialise(); 18 + 19 + export function Tubes() { 20 + const css = build_accent_css(accent.value); 21 + 22 + return <Router hook={useHashLocation}> 23 + <main class="app" style={css}> 24 + <Sidebar /> 25 + <Switch> 26 + <Route path="/"> 27 + <NoBufferPlaceholder /> 28 + </Route> 29 + <Route path="/connection/:adapter?/:id/debug"> 30 + {({ adapter, id }) => { 31 + const conn = Connections.fetch(id, adapter); 32 + return conn ? <DebugView conn={conn} /> : "one sec"; 33 + }} 34 + </Route> 35 + <Route path="/connection/:adapter?/:id/:target"> 36 + {({ adapter, id, target }) => { 37 + const conn = Connections.fetch(id, adapter); 38 + const chan = conn?.get_channel(target); 39 + return chan 40 + ? <BufferView buffer={chan} /> 41 + : <p class="loader">(one sec)</p>; 42 + }} 43 + </Route> 44 + <Route path="/settings"> 45 + <SettingsPage /> 46 + </Route> 47 + </Switch> 48 + </main> 49 + </Router> 50 + } 51 + 52 + render(<Tubes />, document.getElementById('app')!);
+157
neo/src/settings/index.tsx
··· 1 + import "@css/settings.css"; 2 + import { add_adapter } from "@src/chat/adapters"; 3 + import { accent, message_style } from "@src/support"; 4 + import { FunctionalComponent } from "preact"; 5 + import { HTMLProps, TargetedEvent } from "preact/compat"; 6 + import { useEffect, useRef } from "preact/hooks"; 7 + 8 + // helper component that fires an event when a radio button in a fieldset is selected 9 + const FieldSetRadioButtonEventGroupThing 10 + : FunctionalComponent<{ 11 + onChange?: (e: TargetedEvent<HTMLInputElement>) => void 12 + } & Omit<HTMLProps<HTMLFieldSetElement>, "onChange">> 13 + = ({ children, onChange, ...rest }) => { 14 + let ref = useRef<HTMLFieldSetElement>(null); 15 + useEffect(() => { 16 + const items = ref.current!.querySelectorAll("input[type=radio]") as NodeListOf<HTMLInputElement>; 17 + const cb = (e: Event) => { 18 + onChange?.(e as TargetedEvent<HTMLInputElement>); 19 + }; 20 + 21 + for (const item of items) { 22 + item.addEventListener("change", cb); 23 + } 24 + 25 + return () => { 26 + for (const item of items) { 27 + item.removeEventListener("change", cb) 28 + } 29 + } 30 + }); 31 + return <fieldset ref={ref} {...rest}> 32 + {children} 33 + </fieldset> 34 + } 35 + 36 + const PickerItem: FunctionalComponent<{ colour: string }> = ({ colour }) => { 37 + return <label style={` 38 + --colour: var(--colour-${colour}-600); 39 + --hover: var(--colour-${colour}-500); 40 + `}> 41 + <input type="radio" name="accent-colour" value={colour} checked={accent.value == colour} /> {colour} 42 + </label> 43 + 44 + } 45 + 46 + const SettingsPage: FunctionalComponent = () => { 47 + return <article class="settings"> 48 + <h1 class="title">settingz</h1> 49 + 50 + <div class="sep">🔧🧰⚙️</div> 51 + 52 + <h2>bouncers</h2> 53 + <div class="panel"> 54 + <p class="intro"> 55 + bouncers 56 + </p> 57 + 58 + <div> 59 + <button onClick={() => { 60 + add_adapter(); 61 + }}> 62 + add soju adapter :3 63 + </button> 64 + </div> 65 + </div> 66 + <h2>appearance</h2> 67 + <div class="panel"> 68 + <p class="intro body-small">control the way tubes looks.</p> 69 + 70 + <FieldSetRadioButtonEventGroupThing 71 + onChange={(e) => { 72 + const colour = e.currentTarget.value; 73 + localStorage.setItem("colour", colour); 74 + accent.value = colour; 75 + }} 76 + class="colour-select" 77 + > 78 + <legend>favourite colour</legend> 79 + <PickerItem colour="aubergine"></PickerItem> 80 + <PickerItem colour="yellow"></PickerItem> 81 + <PickerItem colour="bubblegum"></PickerItem> 82 + <PickerItem colour="weezer"></PickerItem> 83 + <PickerItem colour="ectoplasm"></PickerItem> 84 + <PickerItem colour="orange"></PickerItem> 85 + <PickerItem colour="red"></PickerItem> 86 + <PickerItem colour="brown"></PickerItem> 87 + </FieldSetRadioButtonEventGroupThing> 88 + 89 + <FieldSetRadioButtonEventGroupThing class="comfy-compact-dichotomy" onChange={(e) => { 90 + const value = e.currentTarget.value; 91 + localStorage.setItem("message_style", value); 92 + message_style.value = value; 93 + }}> 94 + <legend>message list style</legend> 95 + <label> 96 + <input 97 + type="radio" 98 + name="message-style" 99 + value="comfy" 100 + checked={message_style.value == "comfy"} 101 + /> 102 + <figure> 103 + <div class="preview comfy"> 104 + <span class="name">the_grungler</span> 105 + <p class="content">hello!</p> 106 + </div> 107 + <figcaption>comfy</figcaption> 108 + </figure> 109 + </label> 110 + <label> 111 + <input 112 + type="radio" 113 + name="message-style" 114 + value="compact" 115 + checked={message_style.value == "compact"} 116 + /> 117 + <figure> 118 + <div class="preview compact"> 119 + <span class="name">the_grungler</span> 120 + <p class="content">hello!</p> 121 + </div> 122 + <figcaption>compact</figcaption> 123 + </figure> 124 + </label> 125 + </FieldSetRadioButtonEventGroupThing> 126 + 127 + <FieldSetRadioButtonEventGroupThing class="radio-group"> 128 + <legend>show when people join & leave a channel</legend> 129 + <label><input type="radio" name="events" value="no" /> hide all join & leave events</label> 130 + <label><input type="radio" name="events" value="folded" /> show events collapsed</label> 131 + <label><input type="radio" name="events" value="yes" /> show events expanded</label> 132 + </FieldSetRadioButtonEventGroupThing> 133 + </div> 134 + <h2>networks</h2> 135 + <div class="panel"> 136 + networks 137 + </div> 138 + <h2>notifications</h2> 139 + <div class="panel"> 140 + notifications 141 + </div> 142 + <h2>accessibility</h2> 143 + <div class="panel"> 144 + accessibility 145 + </div> 146 + <h2>other</h2> 147 + <div class="panel"> 148 + other 149 + </div> 150 + <h2>about</h2> 151 + <div class="panel"> 152 + about 153 + </div> 154 + </article> 155 + } 156 + 157 + export default SettingsPage;
+45
neo/src/storage.ts
··· 1 + import Dexie, { type EntityTable } from "dexie"; 2 + import { Connection } from "tubes_core"; 3 + import { IrcMessage } from "tubes_core/parser"; 4 + 5 + export interface StoredMessage { 6 + adapter_id: "*" | string & {}, 7 + connection_id: string, 8 + target: string, 9 + id: string, 10 + message: IrcMessage, 11 + timestamp: Date, 12 + } 13 + 14 + export const db = new Dexie('Tubes') as Dexie & { 15 + messages: EntityTable<StoredMessage>; 16 + }; 17 + 18 + db.version(1).stores({ 19 + messages: "id, [adapter_id+connection_id+target], adapter_id, connection_id, target, message, timestamp", 20 + }); 21 + 22 + export async function store_message(conn: Connection, message: IrcMessage) { 23 + const id = message.tags?.['msgid'] ?? crypto.randomUUID(); 24 + const timestamp = new Date(message.tags?.['time'] ?? Date.now()); 25 + 26 + switch (message.command) { 27 + case "PRIVMSG": 28 + case "NOTICE": { 29 + const target = message.params![0]; 30 + console.log(conn.id, conn.adapter_id, target, id, message, timestamp) 31 + await db.messages.add({ 32 + connection_id: conn.id, 33 + adapter_id: conn.adapter_id ?? "*", 34 + target, 35 + id, 36 + message, 37 + timestamp, 38 + }).catch(e => console.error(e)) ; 39 + console.log(`stored msg ${id}`, message) 40 + break; 41 + } 42 + 43 + default: return; 44 + } 45 + }
+20
neo/src/support.ts
··· 1 + import { signal } from "@preact/signals"; 2 + 3 + export const reduced_motion = () => window.matchMedia("(prefers-reduced-motion)").matches; 4 + 5 + export const accent = signal(localStorage.getItem("colour") ?? "aubergine"); 6 + export const message_style = signal(localStorage.getItem("message_style") ?? "compact"); 7 + 8 + export const build_accent_css = (colour: string) => ` 9 + --colour-accent-50: var(--colour-${colour}-50); 10 + --colour-accent-100: var(--colour-${colour}-100); 11 + --colour-accent-200: var(--colour-${colour}-200); 12 + --colour-accent-300: var(--colour-${colour}-300); 13 + --colour-accent-400: var(--colour-${colour}-400); 14 + --colour-accent-500: var(--colour-${colour}-500); 15 + --colour-accent-600: var(--colour-${colour}-600); 16 + --colour-accent-700: var(--colour-${colour}-700); 17 + --colour-accent-800: var(--colour-${colour}-800); 18 + --colour-accent-900: var(--colour-${colour}-900); 19 + --colour-accent-950: var(--colour-${colour}-950); 20 + `
+1
neo/src/vite-env.d.ts
··· 1 + /// <reference types="vite/client" />
+36
neo/tsconfig.json
··· 1 + { 2 + "compilerOptions": { 3 + "target": "ES2020", 4 + "useDefineForClassFields": true, 5 + "module": "ESNext", 6 + "lib": ["ESNext", "DOM", "DOM.Iterable"], 7 + "skipLibCheck": true, 8 + "paths": { 9 + "react": ["./node_modules/preact/compat/"], 10 + "react-dom": ["./node_modules/preact/compat/"], 11 + "@src/*": ["./src/*"], 12 + "@css/*": ["./src/css/*"] 13 + }, 14 + 15 + /* Bundler mode */ 16 + "moduleResolution": "bundler", 17 + "allowImportingTsExtensions": true, 18 + "resolveJsonModule": true, 19 + "isolatedModules": true, 20 + "noEmit": true, 21 + "jsx": "react-jsx", 22 + "jsxImportSource": "preact", 23 + 24 + /* Linting */ 25 + "strict": true, 26 + "noUnusedLocals": true, 27 + "noUnusedParameters": true, 28 + "noFallthroughCasesInSwitch": true, 29 + 30 + "types": [ 31 + "unplugin-icons/types/preact", 32 + ], 33 + }, 34 + "include": ["src"], 35 + "references": [{ "path": "./tsconfig.node.json" }] 36 + }
+11
neo/tsconfig.node.json
··· 1 + { 2 + "compilerOptions": { 3 + "composite": true, 4 + "skipLibCheck": true, 5 + "module": "ESNext", 6 + "moduleResolution": "bundler", 7 + "allowSyntheticDefaultImports": true, 8 + "strict": true 9 + }, 10 + "include": ["vite.config.ts"] 11 + }
+15
neo/vite.config.ts
··· 1 + import { defineConfig } from 'vite' 2 + import preact from '@preact/preset-vite' 3 + import icons from "unplugin-icons/vite"; 4 + import path from "node:path"; 5 + 6 + // https://vitejs.dev/config/ 7 + export default defineConfig({ 8 + plugins: [preact(), icons({ compiler: 'jsx', jsx: 'preact' })], 9 + resolve: { 10 + alias: { 11 + "@src": path.resolve(__dirname, "./src/"), 12 + "@css": path.resolve(__dirname, "./src/css"), 13 + } 14 + } 15 + })