├── .github └── workflows │ └── deploy.yml ├── .gitignore ├── LICENCE ├── README.md ├── dist └── js │ ├── quick-request-module.js │ ├── quick-request-module.min.js │ ├── quick-request.js │ └── quick-request.min.js ├── docs ├── .vitepress │ └── config.mjs ├── contribute │ ├── contribution.md │ └── report-bugs.md ├── getting-started │ ├── changelog.md │ ├── install.md │ └── versions.md ├── index.md ├── introduction │ └── introduction.md ├── public │ ├── css │ │ └── style.css │ └── img │ │ ├── laravel-red.svg │ │ ├── laravel_black.svg │ │ ├── logo-github.jpg │ │ ├── quick-request-banner.png │ │ ├── quick-request.jpeg │ │ └── quick-request.png └── usage │ ├── blobs.md │ ├── examples.md │ ├── exceptions.md │ ├── general-structure.md │ └── laravel-errors.md ├── package-lock.json └── package.json /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy VitePress site to Pages 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | 7 | # Allows you to run this workflow manually from the Actions tab 8 | workflow_dispatch: 9 | 10 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 11 | permissions: 12 | contents: read 13 | pages: write 14 | id-token: write 15 | 16 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 17 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 18 | concurrency: 19 | group: pages 20 | cancel-in-progress: false 21 | 22 | jobs: 23 | # Build job 24 | build: 25 | runs-on: ubuntu-latest 26 | steps: 27 | - name: Checkout 28 | uses: actions/checkout@v3 29 | with: 30 | fetch-depth: 0 # Not needed if lastUpdated is not enabled 31 | # - uses: pnpm/action-setup@v2 # Uncomment this if you're using pnpm 32 | # - uses: oven-sh/setup-bun@v1 # Uncomment this if you're using Bun 33 | - name: Setup Node 34 | uses: actions/setup-node@v3 35 | with: 36 | node-version: 18 37 | cache: npm # or pnpm / yarn 38 | - name: Setup Pages 39 | uses: actions/configure-pages@v3 40 | - name: Install dependencies 41 | run: npm ci # or pnpm install / yarn install / bun install 42 | - name: Build with VitePress 43 | run: | 44 | npm run docs:build # or pnpm docs:build / yarn docs:build / bun run docs:build 45 | touch docs/.vitepress/dist/.nojekyll 46 | - name: Upload artifact 47 | uses: actions/upload-pages-artifact@v2 48 | with: 49 | path: docs/.vitepress/dist 50 | 51 | # Deployment job 52 | deploy: 53 | environment: 54 | name: github-pages 55 | url: ${{ steps.deployment.outputs.page_url }} 56 | needs: build 57 | runs-on: ubuntu-latest 58 | name: Deploy 59 | steps: 60 | - name: Deploy to GitHub Pages 61 | id: deployment 62 | uses: actions/deploy-pages@v2 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencias 2 | /node_modules 3 | 4 | # Archivos de construcción 5 | /build 6 | 7 | # Otros archivos generados 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | docs/.vitepress/cache 12 | docs/.vitepress/build -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 RAUL MAURICIO UÑATE CASTRO 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QuickRequest Laravel 2 | 3 | ![Banner de Quick Request](https://rmunate.github.io/Quick-Request-Laravel/img/quick-request-banner.png "Quick Request") 4 | 5 | 6 | ## Introduction 7 | 8 | **QuickRequest** is an ultra-lightweight tool designed for swift and efficient requests to the Laravel backend. 9 | 10 | By leveraging the "fetch" mechanism, this solution streamlines the execution of requests to Laravel controllers, eliminating the need for manual assignment of tokens, base URLs, hidden inputs, and other method-specific parameters. 11 | 12 | QuickRequest provides a clean and elegant coding experience, making it a pleasant shift for developers accustomed to older technologies like Ajax. 13 | 14 | Tailored to meet specific requirements, QuickRequest offers essential features for easily managing various request types in a standard Laravel application, including GET, POST, PUT, PATCH, and DELETE. 15 | 16 | Additionally, it offers a convenient approach for efficient file downloads by effectively handling Blobs. 17 | 18 | Forget about writing extra lines of code—experience the minimalist style of QuickRequest. 19 | 20 | ## Documentation 21 | [![📖📖📖 **FULL DOCUMENTATION** 📖📖📖](https://img.shields.io/badge/FULL%20DOCUMENTATION-Visit%20Here-blue?style=for-the-badge)](https://rmunate.github.io/Quick-Request-Laravel/) 22 | 23 | 24 | ### Installation 25 | 26 | In order to utilize this tool, you need to have the `csrf-token` meta tag in your main template, as per the official Laravel documentation on [X-CSRF-TOKEN](https://laravel.com/docs/11.x/csrf#csrf-x-csrf-token). This value will be independently read by the `QuickRequest` package, so you should NOT include the `@csrf` directives in your forms or create hidden inputs with this value, as **the library handles it for you**. 27 | 28 | ```html 29 | 30 | ``` 31 | 32 | ### CDN Usage 33 | 34 | If you're not using VITE in your project, you can install this solution simply by utilizing the CDN available for your use. 35 | 36 | It's as easy as adding the following line of code to the `` section of your main template. 37 | 38 | ```html 39 | 40 | 41 | 42 | 43 | 44 | ``` 45 | 46 | Alternatively, you could download the content from the aforementioned URL, place it in the `public` directory within a `js` folder, keeping the code locally in your project. However, this means you would maintain code that won't be updated with the adjustments or enhancements applied to the project. 47 | 48 | ```html 49 | 50 | 51 | 52 | 53 | 54 | ``` 55 | 56 | ### NPM Usage with VITE 57 | 58 | If you're developing your project with VITE, it will be much more convenient to install this solution using the following command. 59 | 60 | ```bash 61 | npm i quick-request-laravel 62 | ``` 63 | 64 | This way, the package will be readily available in your system; you just need to import it into your modules. 65 | 66 | ```javascript 67 | import { QuickRequest } from 'quick-request-laravel'; 68 | ``` 69 | 70 | ## License 71 | This project is under the [MIT License](https://choosealicense.com/licenses/mit/). 72 | 73 | 🌟 Support My Projects! 🚀 74 | 75 | [![Become a Sponsor](https://img.shields.io/badge/-Become%20a%20Sponsor-blue?style=for-the-badge&logo=github)](https://github.com/sponsors/rmunate) 76 | 77 | Make any contributions you see fit; the code is entirely yours. Together, we can do amazing things and improve the world of development. Your support is invaluable. ✨ 78 | 79 | If you have ideas, suggestions, or just want to collaborate, we are open to everything! Join our community and be part of our journey to success! 🌐👩‍💻👨‍💻 -------------------------------------------------------------------------------- /dist/js/quick-request-module.js: -------------------------------------------------------------------------------- 1 | /** 2 | * QuickRequest v1.1.0 3 | * Script 4 | * (c) Raul Mauricio Uñate Castro 5 | * https://github.com/rmunate 6 | * MIT 7 | */ 8 | 9 | /** 10 | * -------------------------- 11 | * Class for Throwing Errors to the Console. 12 | * -------------------------- 13 | */ 14 | class QuickRequestException { 15 | 16 | /** 17 | * Method to throw an exception to the console. 18 | * Halts execution with 'throw'. 19 | * @param {string} custom - Custom error message. 20 | */ 21 | constructor(custom){ 22 | 23 | // Create an error with the custom message 24 | const error = new Error(custom); 25 | 26 | // Log the error to the console and stop execution 27 | throw console.error("⚠ QuickRequestException ⚠ | " + error.toString(), { 28 | name: error.name, 29 | message: error.message, 30 | trace: error.stack.split("\n").map(function(element){ 31 | return element.trim().replaceAll("at ","🔍 At: "); 32 | }), 33 | }); 34 | } 35 | } 36 | 37 | /** 38 | * -------------------------- 39 | * Herlpers General - QuickRequest 40 | * -------------------------- 41 | */ 42 | const QuickRequestHelpers = { 43 | 44 | /** 45 | * Extracts the last segment from a given value based on a specified separator. 46 | * 47 | * @param {*} value - The value from which to extract the last segment. 48 | * @param {string} [separator="/"] - The separator used to split the value into segments. Default is "/". 49 | * @returns {string} - The last segment of the value. 50 | */ 51 | extractLastSegment: (value, separator = "/") => { 52 | 53 | // Check if the input value is not empty 54 | if (!QuickRequestHelpers.isValueEmpty(value)) { 55 | 56 | // Split the input string by "separator" and store the result in the 'segments' array 57 | const segments = value.split(separator); 58 | 59 | // Retrieve the last segment from the 'segments' array 60 | const lastSegment = segments[segments.length - 1]; 61 | 62 | // Return the last segment 63 | return lastSegment; 64 | } 65 | 66 | // If the input value is empty, return an empty string 67 | return ''; 68 | 69 | }, 70 | 71 | /** 72 | * Checks if a value is empty (null, undefined, empty string, empty array, or empty object) 73 | * 74 | * @param {*} value - The value to be checked 75 | * @returns {boolean} - True if the value is empty, false otherwise 76 | */ 77 | isValueEmpty: (value) => { 78 | if (value == null) { 79 | return true; 80 | } 81 | 82 | if (typeof value === 'string' && value.trim() === '') { 83 | return true; 84 | } 85 | 86 | if (Array.isArray(value) && value.length === 0) { 87 | return true; 88 | } 89 | 90 | if (typeof value === 'object' && Object.keys(value).length === 0) { 91 | return true; 92 | } 93 | 94 | return false; 95 | }, 96 | 97 | /** 98 | * Get Valid Token Laravel 99 | * @returns {string} - Token Laravel 100 | */ 101 | getTokenCSRF: () => { 102 | 103 | const csrfTokenMeta = document.querySelector('meta[name="csrf-token"]'); 104 | 105 | if (csrfTokenMeta) { 106 | return csrfTokenMeta.getAttribute('content'); 107 | } else { 108 | throw new QuickRequestException(`The meta tag "csrf-token" was not found. Please remember to add it in the head section: `); 109 | } 110 | }, 111 | 112 | }; 113 | 114 | /** 115 | * -------------------------- 116 | * Class for Validating the Presence of Required Values in Requests. 117 | * -------------------------- 118 | */ 119 | class QuickRequestValidate { 120 | 121 | /** 122 | * Validates that the mandatory values in the options are present. 123 | * @param {object} options - The request options to validate. 124 | * @returns {boolean} - True if all mandatory properties are present, false otherwise. 125 | */ 126 | constructor(options){ 127 | 128 | const required = ['url']; 129 | 130 | required.forEach(element => { 131 | let exist = options.hasOwnProperty(element) 132 | if (!exist) { 133 | throw new QuickRequestException("The property '" + element + "' is mandatory in the request options."); 134 | } 135 | }); 136 | 137 | } 138 | 139 | } 140 | 141 | /** 142 | * ----------------------------------- 143 | * Class for Validating HTML Elements 144 | * ----------------------------------- 145 | */ 146 | class QuickRequestElements { 147 | 148 | /** 149 | * Check if the element has a valid tag for extracting its value within FormData. 150 | * @param {HTMLElement} element - The HTML element to validate. 151 | * @returns {boolean} - True if the tag is valid, false otherwise. 152 | */ 153 | tagCheck(element){ 154 | const validTags = ['INPUT', 'TEXTAREA', 'SELECT']; 155 | return validTags.includes(element.tagName) 156 | } 157 | 158 | /** 159 | * Check if the input type is valid to be considered as a valid form control. 160 | * @param {HTMLInputElement} element - The input element to validate. 161 | * @returns {boolean} - True if the input type is valid, false otherwise. 162 | */ 163 | typeCheck(element){ 164 | const disabledTypes = ['submit','button'] 165 | return !disabledTypes.includes(element.type) 166 | } 167 | 168 | /** 169 | * Check if the input is of type "file". 170 | * @param {HTMLInputElement} element - The input element to validate. 171 | * @returns {boolean} - True if the input type is "file," false otherwise. 172 | */ 173 | typeFile(element){ 174 | return element.type === 'file'; 175 | } 176 | 177 | /** 178 | * ----------------------------------- 179 | * Check if it is a checkbox or a radio input 180 | * ----------------------------------- 181 | * Determine if the input is of type "checkbox" or "radio." 182 | * @param {HTMLInputElement} element - The input element to validate. 183 | * @returns {boolean} - True if the input type is "checkbox" or "radio," false otherwise. 184 | */ 185 | typeCheckboxOrRadio(element){ 186 | const validTypes = ['radio','checkbox'] 187 | return validTypes.includes(element.type) 188 | } 189 | 190 | } 191 | 192 | /** 193 | * -------------------------- 194 | * Class for preparing data for Fetch requests. 195 | * -------------------------- 196 | */ 197 | class QuickRequestMain { 198 | 199 | /* API request configuration. */ 200 | constructor(){ 201 | this.config = { 202 | expect: "JSON", 203 | confirm: false, 204 | activateEvent: false, 205 | eventListener: {}, 206 | preventDefault: true, 207 | method: 'GET', 208 | headers: { 209 | 'X-CSRF-TOKEN': QuickRequestHelpers.getTokenCSRF(), 210 | 'Accept': 'application/json', 211 | }, 212 | }; 213 | } 214 | 215 | /** 216 | * Set up an event listener for triggering the event. 217 | * @param {string} event - Event name to listen for. 218 | * @param {string} selectors - Selectors for event targets. 219 | */ 220 | eventListener(event, selectors){ 221 | 222 | if (QuickRequestHelpers.isValueEmpty(event)) { 223 | throw new QuickRequestException('You must define a valid event within the eventListener method(<>,<>)'); 224 | } 225 | 226 | if (QuickRequestHelpers.isValueEmpty(selectors)) { 227 | throw new QuickRequestException('You must define a valid selector within the eventListener method(<>,<>)'); 228 | } 229 | 230 | this.config.activateEvent = true; 231 | this.config.eventListener.event = event; 232 | this.config.eventListener.selectors = document.querySelectorAll(selectors); 233 | 234 | return this; 235 | } 236 | 237 | /** 238 | * Control whether to prevent the default behavior of the event. 239 | * @param {boolean} state - Set to true to prevent the default behavior, false otherwise. 240 | */ 241 | preventDefault(state = true){ 242 | 243 | if (state !== true && state !== false) { 244 | throw new QuickRequestException('The argument can only be <> or <>, or omit it to default to <>'); 245 | } 246 | 247 | this.config.preventDefault = state; 248 | 249 | return this; 250 | } 251 | 252 | /** 253 | * Add custom headers to the request. 254 | * @param {object} headersCustom - Custom headers to include. 255 | */ 256 | headers(headersCustom = {}){ 257 | 258 | this.config.headers = { 259 | ...this.config.headers, 260 | ...headersCustom 261 | }; 262 | 263 | return this; 264 | } 265 | 266 | /** 267 | * Confirm Pre-Request 268 | * @param {*} callback 269 | * @returns 270 | */ 271 | confirm(callback){ 272 | this.config.confirm = callback; 273 | return this; 274 | } 275 | 276 | /** 277 | * Make a POST request. 278 | * @param {object} options - Additional options for the request. 279 | */ 280 | post(options = {}){ 281 | 282 | this.config.method = 'POST'; 283 | 284 | if (options.hasOwnProperty('headers')) { 285 | this.headers(options.headers); 286 | delete options['headers']; 287 | } 288 | 289 | this.config.options = options; 290 | this.config.expect = options.expect || "JSON"; 291 | 292 | const manager = new QuickRequestEvents(this.config); 293 | return manager.handler; 294 | } 295 | 296 | /** 297 | * Make a GET request. 298 | * @param {object} options - Additional options for the request. 299 | */ 300 | get(options = {}){ 301 | 302 | this.config.method = 'GET'; 303 | 304 | if (options.hasOwnProperty('headers')) { 305 | this.headers(options.headers); 306 | delete options['headers']; 307 | } 308 | 309 | this.config.options = options; 310 | this.config.expect = options.expect || "JSON"; 311 | 312 | const manager = new QuickRequestEvents(this.config); 313 | return manager.handler; 314 | } 315 | 316 | /** 317 | * Make a PUT request. 318 | * @param {object} options - Additional options for the request. 319 | */ 320 | put(options = {}){ 321 | 322 | this.config.method = 'PUT'; 323 | 324 | if (options.hasOwnProperty('headers')) { 325 | this.headers(options.headers); 326 | delete options['headers']; 327 | } 328 | 329 | this.config.options = options; 330 | this.config.expect = options.expect || "JSON"; 331 | 332 | const manager = new QuickRequestEvents(this.config); 333 | return manager.handler; 334 | } 335 | 336 | /** 337 | * Make a PATCH request. 338 | * @param {object} options - Additional options for the request. 339 | */ 340 | patch(options = {}){ 341 | 342 | this.config.method = 'PATCH'; 343 | 344 | if (options.hasOwnProperty('headers')) { 345 | this.headers(options.headers); 346 | delete options['headers']; 347 | } 348 | 349 | this.config.options = options; 350 | this.config.expect = options.expect || "JSON"; 351 | 352 | const manager = new QuickRequestEvents(this.config); 353 | return manager.handler; 354 | } 355 | 356 | /** 357 | * Make a DELETE request. 358 | * @param {object} options - Additional options for the request. 359 | */ 360 | delete(options = {}){ 361 | 362 | this.config.method = 'DELETE'; 363 | 364 | if (options.hasOwnProperty('headers')) { 365 | this.headers(options.headers); 366 | delete options['headers']; 367 | } 368 | 369 | this.config.options = options; 370 | this.config.expect = options.expect || "JSON"; 371 | 372 | const manager = new QuickRequestEvents(this.config); 373 | return manager.handler; 374 | } 375 | } 376 | 377 | /** 378 | * -------------------------- 379 | * Class for Triggering Events. 380 | * -------------------------- 381 | */ 382 | class QuickRequestEvents { 383 | 384 | /** 385 | * Create an instance to execute a Fetch request. 386 | * @param {object} config - Request configuration. 387 | */ 388 | constructor(config){ 389 | 390 | // Configuration values for the request. 391 | this.config = config; 392 | this.config.callbacksEvents = []; 393 | 394 | // Perform validation of required values. 395 | new QuickRequestValidate(this.config.options); 396 | 397 | // Check whether to trigger the event. 398 | this.checkEvent(); 399 | } 400 | 401 | /** 402 | * Check if the event should be triggered. 403 | */ 404 | checkEvent(){ 405 | 406 | /* Determine whether to create an event to trigger the Fetch request */ 407 | if (this.config.activateEvent) { 408 | 409 | /* Create a reference to the main instance */ 410 | const self = this; 411 | 412 | /* Iterate through the selection to trigger the events */ 413 | this.config.eventListener.selectors.forEach(function(element) { 414 | 415 | const funcEvent = function(e) { 416 | if (self.config.preventDefault) { 417 | e.preventDefault(); 418 | } 419 | 420 | new QuickRequestFetch(self.config); 421 | }; 422 | 423 | /* Create the event for each matching selector */ 424 | element.addEventListener(self.config.eventListener.event, funcEvent); 425 | 426 | /* Save Event Handlers */ 427 | self.config.callbacksEvents.push({ 428 | element: element, 429 | event: self.config.eventListener.event, 430 | func: funcEvent 431 | }); 432 | }); 433 | 434 | self.handler = new QuickRequesHandler(self.config); 435 | 436 | } else { 437 | 438 | new QuickRequestFetch(this.config); 439 | 440 | this.handler = new QuickRequesHandler(this.config); 441 | } 442 | } 443 | } 444 | 445 | /** 446 | * -------------------------- 447 | * Class for Handling Fetch Requests. 448 | * -------------------------- 449 | */ 450 | class QuickRequestFetch { 451 | 452 | /** 453 | * Request configuration values. 454 | * @param {object} config - Request configuration. 455 | */ 456 | constructor(config){ 457 | 458 | /* Received and custom configuration values */ 459 | this.config = config; 460 | this.config.baseUrl = `${window.location.protocol}//${window.location.host}/`; 461 | this.config.formData = new FormData(); 462 | this.config.hasFile = false; 463 | this.custom = {} 464 | 465 | /* Execute the request sending process */ 466 | this.dispatch(); 467 | } 468 | 469 | /** 470 | * Create the final FormData for sending data to the backend. 471 | */ 472 | data(){ 473 | 474 | // Determine if form or data is being used 475 | const originForm = this.config.options.form || null; 476 | 477 | // En caso de que el origen sea un form. 478 | if (originForm !== null) { 479 | 480 | // Check input elements 481 | const inputsCheck = new QuickRequestElements(); 482 | 483 | //Validate Form 484 | let controls = []; 485 | const realElementForm = document.getElementById(originForm); 486 | if (realElementForm) { 487 | controls = Array.from(realElementForm.elements); 488 | } else { 489 | throw new QuickRequestException("Could not find any form with the id " + originForm); 490 | } 491 | 492 | controls.forEach(element => { 493 | 494 | if (inputsCheck.tagCheck(element) && inputsCheck.typeCheck(element)) { 495 | 496 | if (inputsCheck.typeFile(element)) { 497 | 498 | if (element.files.length > 0) { 499 | this.config.formData.append(element.name, element.files[0]); 500 | this.config.hasFile = true; 501 | } 502 | 503 | } else if(inputsCheck.typeCheckboxOrRadio(element)){ 504 | 505 | if (element.checked) { 506 | this.config.formData.append(element.name, element.value); 507 | } 508 | 509 | } else { 510 | 511 | if(!QuickRequestHelpers.isValueEmpty(element.value)){ 512 | this.config.formData.append(element.name, element.value); 513 | } 514 | 515 | } 516 | } 517 | }); 518 | 519 | // Check if additional data is available 520 | const originData = this.config.options.data || null; 521 | 522 | if (!QuickRequestHelpers.isValueEmpty(originData)) { 523 | 524 | for (const [key, value] of Object.entries(originData)) { 525 | this.config.formData.append(key, value); 526 | } 527 | 528 | } 529 | 530 | } else { 531 | 532 | let originData; 533 | 534 | //Validar si es un callback 535 | if (typeof this.config.options.data === 'function') { 536 | originData = this.config.options.data(this.custom) 537 | } else { 538 | originData = this.config.options.data || null; 539 | } 540 | 541 | if (!QuickRequestHelpers.isValueEmpty(originData)) { 542 | 543 | for (const [key, value] of Object.entries(originData)) { 544 | this.config.formData.append(key, value); 545 | } 546 | 547 | } 548 | } 549 | } 550 | 551 | /** 552 | * Define request body settings based on the method. 553 | */ 554 | method(){ 555 | 556 | /* Define URL */ 557 | let url = (this.config.baseUrl + this.config.options.url).replaceAll("//","/").replaceAll(":/","://"); 558 | 559 | /* Params Fetch */ 560 | let params = {}; 561 | 562 | if (['GET','HEAD'].includes(this.config.method)) { 563 | 564 | /* Validate This Request Has Files */ 565 | if (this.config.hasFile) { 566 | throw new QuickRequestException("File uploads are not allowed with " + this.config.method + " due to URL length limitations; use POST instead."); 567 | } 568 | 569 | /* Validate Query In URL */ 570 | if (url.includes("?")) { 571 | throw new QuickRequestException("The URL appears to already have a query, as it contains the '?' character in its structure. In case you need to send additional data, use the 'data' property."); 572 | } 573 | 574 | /* Create Query */ 575 | let query = new URLSearchParams(this.config.formData).toString(); 576 | url = `${url}?${query}`; 577 | params = { 578 | method: this.config.method, 579 | headers: this.config.headers, 580 | }; 581 | 582 | } else if(['PATCH','PUT','DELETE'].includes(this.config.method)){ 583 | 584 | /* Validate This Request Has Files */ 585 | if (this.config.hasFile) { 586 | throw new QuickRequestException("File uploads are not allowed with " + this.config.method + " as it uses JSON headers; use POST instead."); 587 | } 588 | 589 | /* Create JSON */ 590 | let jsonObject = {}; 591 | 592 | for (let [clave, valor] of this.config.formData.entries()) { 593 | 594 | let isArray = clave.includes("[]"); 595 | let index = clave.replace("[]", ""); 596 | 597 | if (jsonObject[index] === undefined) { 598 | if (isArray) { 599 | jsonObject[index] = [valor]; 600 | } else { 601 | jsonObject[index] = valor; 602 | } 603 | } else { 604 | try { 605 | jsonObject[index].push(valor); 606 | } catch (error) { 607 | throw new QuickRequestException("Two distinct inputs, excluding Checkboxes and Radios, have the same name '"+index+"' without the square brackets '[]', which prevents them from being treated as the same data when sent to the backend as an array."); 608 | } 609 | } 610 | } 611 | 612 | /* Add Header JSON */ 613 | this.config.headers['Content-Type'] = 'application/json'; 614 | 615 | params = { 616 | method: this.config.method, 617 | headers: this.config.headers, 618 | body: JSON.stringify(jsonObject), 619 | } 620 | 621 | } else { 622 | 623 | params = { 624 | method: this.config.method, 625 | headers: this.config.headers, 626 | body: this.config.formData, 627 | } 628 | } 629 | 630 | /* Validate Others Properties */ 631 | const propertiesToValidate = ["mode", "cache", "credentials", "redirect", "referrerPolicy"]; 632 | 633 | for (const property of propertiesToValidate) { 634 | if (this.config.hasOwnProperty(property)) { 635 | params[property] = this.config[property]; 636 | } 637 | } 638 | 639 | return { 640 | realUrl: url, 641 | realParams: params 642 | }; 643 | } 644 | 645 | /** 646 | * Dospatch Fetch 647 | */ 648 | dispatch(){ 649 | if (typeof this.config.confirm === 'function') { 650 | if (this.config.confirm(this.custom) === true) { 651 | this.send(); 652 | } 653 | } else { 654 | this.send(); 655 | } 656 | } 657 | 658 | /** 659 | * Send the request to the backend. 660 | */ 661 | async send(){ 662 | 663 | /* FormData */ 664 | this.data(); 665 | 666 | /* Execute Pre-request Function */ 667 | if (this.config.options.before && typeof this.config.options.before === "function") { 668 | this.config.options.before(this.custom); 669 | } 670 | 671 | /* Response values */ 672 | let custom_response; 673 | let custom_data; 674 | let custom_status; 675 | let errors; 676 | let responseJSON; 677 | 678 | /* Determine the method to send the request to the server */ 679 | const params = this.method(); 680 | 681 | /* Execute the Fetch request */ 682 | const response = await fetch(params.realUrl, params.realParams); 683 | 684 | if (!response.ok) { 685 | 686 | try { 687 | responseJSON = await response.json(); 688 | } catch (error) { 689 | responseJSON = response.statusText; 690 | } 691 | 692 | if(typeof responseJSON == 'string'){ 693 | 694 | errors = { 695 | "message" : responseJSON, 696 | "errors" : [] 697 | } 698 | 699 | } else { 700 | 701 | if (responseJSON.hasOwnProperty('exception') && responseJSON.hasOwnProperty('file') && responseJSON.hasOwnProperty('message')) { 702 | 703 | errors = { 704 | "message" : responseJSON.message, 705 | "errors" : [] 706 | } 707 | 708 | responseJSON.trace.forEach(element => { 709 | if(element.class){ 710 | errors.errors[`${element.class}:${element.line}`] = [`${element.file}${element.type}${element.function}`] 711 | } 712 | }); 713 | 714 | } else if (typeof responseJSON === 'object' && Object.keys(responseJSON).length > 0) { 715 | 716 | errors = { 717 | "message" : responseJSON.message || response.statusText, 718 | "errors" : responseJSON.errors || [] 719 | } 720 | } else { 721 | 722 | errors = { 723 | "message" : response.statusText, 724 | "errors" : [] 725 | } 726 | 727 | } 728 | 729 | custom_response = { 730 | data: errors, 731 | success: response.ok, 732 | status: response.status, 733 | statusText: response.statusText, 734 | headers: response.headers, 735 | url: response.url 736 | } 737 | 738 | custom_data = errors 739 | 740 | custom_status = response.status 741 | } 742 | 743 | if (this.config.options.error && typeof this.config.options.error === "function") { 744 | this.config.options.error(custom_response, custom_data, custom_status); 745 | } 746 | 747 | } else { 748 | 749 | try { 750 | 751 | if (this.config.expect.toLowerCase().trim() == "json") { 752 | 753 | try { 754 | responseJSON = await response.json(); 755 | } catch (error) { 756 | responseJSON = null 757 | } 758 | 759 | } else if (this.config.expect.toLowerCase().trim() == "text") { 760 | 761 | try { 762 | responseJSON = await response.text(); 763 | } catch (error) { 764 | responseJSON = null 765 | } 766 | 767 | } else if (this.config.expect.toLowerCase().trim() == "blob") { 768 | 769 | try { 770 | responseJSON = await response.blob(); 771 | } catch (error) { 772 | responseJSON = null 773 | } 774 | 775 | } else { 776 | 777 | throw new Error("Only the values 'json', 'blob' or 'text' are allowed for the 'expect' property."); 778 | 779 | } 780 | 781 | } catch (error) { 782 | 783 | throw new QuickRequestException(error.message) 784 | 785 | } 786 | 787 | custom_response = { 788 | data: responseJSON, 789 | success: response.ok, 790 | status: response.status, 791 | statusText: response.statusText, 792 | headers: response.headers, 793 | url: response.url 794 | } 795 | 796 | custom_data = responseJSON 797 | 798 | custom_status = response.status 799 | 800 | this.config.options.success(custom_response, custom_data, custom_status); 801 | } 802 | 803 | // Execute Post-request Function 804 | if (this.config.options.after && typeof this.config.options.after === "function") { 805 | this.config.options.after(custom_response, custom_data, custom_status); 806 | } 807 | } 808 | } 809 | 810 | /** 811 | * -------------------------- 812 | * Class for Handling PHP2JS Requests. 813 | * -------------------------- 814 | */ 815 | class QuickRequesHandler { 816 | /** 817 | * Constructor for QuickRequesHandler. 818 | * @param {object} config - Configuration object for PHP2JS requests. 819 | */ 820 | constructor({ callbacksEvents, headers, method, options, preventDefault }) { 821 | // Method to get the HTTP method. 822 | this.getMethod = function () { 823 | return method; 824 | }; 825 | 826 | // Method to get the endpoint URL. 827 | this.getEndPoint = function () { 828 | return options.url; 829 | }; 830 | 831 | // Method to get the form ID (if specified). 832 | this.getIdForm = function () { 833 | return options.form || null; 834 | } 835 | 836 | // Method to get the request data (if provided). 837 | this.getData = function () { 838 | return options.data || null; 839 | } 840 | 841 | // Method to get custom request headers. 842 | this.getCustomHeaders = function () { 843 | return headers; 844 | } 845 | 846 | // Method to get event-related information. 847 | this.getEvents = function () { 848 | return { 849 | preventDefault: !QuickRequestHelpers.isValueEmpty(callbacksEvents) ? preventDefault : null, 850 | events: callbacksEvents 851 | }; 852 | } 853 | 854 | // Method to get callback functions. 855 | this.call = function () { 856 | return { 857 | before: options.before, 858 | success: options.success, 859 | error: options.error, 860 | after: options.after, 861 | }; 862 | } 863 | 864 | // Method to get all request options. 865 | this.getParams = function () { 866 | return options; 867 | } 868 | 869 | // Method to remove event listeners. 870 | this.removeEventListener = function () { 871 | 872 | if (!QuickRequestHelpers.isValueEmpty(callbacksEvents)) { 873 | callbacksEvents.forEach(data => { 874 | data.element.removeEventListener(data.event, data.func) 875 | }); 876 | 877 | // Reset the events array to empty. 878 | this.getEvents = function () { 879 | return []; 880 | } 881 | 882 | return true; 883 | } 884 | 885 | return false; 886 | } 887 | } 888 | } 889 | 890 | /** 891 | * -------------------------- 892 | * Blob Manager. 893 | * -------------------------- 894 | */ 895 | export const QuickRequestBlobs = { 896 | 897 | config: { 898 | blob: null, 899 | name: null, 900 | extension: null, 901 | }, 902 | 903 | setBlob: function (blob) { 904 | this.config.blob = blob; 905 | return this; 906 | }, 907 | 908 | setName: function(name){ 909 | this.config.name = name; 910 | return this; 911 | }, 912 | 913 | setExtension: function(extension){ 914 | this.config.extension = extension; 915 | return this; 916 | }, 917 | 918 | download: function(){ 919 | 920 | let { blob, name, extension } = this.config; 921 | 922 | if (!blob) { 923 | throw new QuickRequestException('Blob is not set in QuickRequestBlobs.setBlob().'); 924 | } 925 | 926 | if (!name) { 927 | throw new QuickRequestException('Name is not set in QuickRequestBlobs.setName().'); 928 | } 929 | 930 | if (extension === null && blob.type) { 931 | 932 | const mimeTypeParts = blob.type.split('/'); 933 | if (mimeTypeParts.length >= 1) { 934 | extension = mimeTypeParts[1]; 935 | } 936 | } 937 | 938 | if (extension === null) { 939 | throw new QuickRequestException('Extension is not set in QuickRequestBlobs.setExtension().'); 940 | } 941 | 942 | const blobUrl = URL.createObjectURL(blob); 943 | const nameWithExtension = (name + "." + extension).replaceAll("..","."); 944 | 945 | // Create Element A 946 | const downloadLink = document.createElement('a'); 947 | downloadLink.href = blobUrl; 948 | downloadLink.download = nameWithExtension; 949 | downloadLink.textContent = name; 950 | 951 | // Emulate Click 952 | const event = new MouseEvent('click', { 953 | view: window, 954 | bubbles: true, 955 | cancelable: false, 956 | }); 957 | 958 | // Dispatch Event 959 | downloadLink.dispatchEvent(event); 960 | }, 961 | }; 962 | 963 | /** 964 | * -------------------------- 965 | * Accessor QuickRequestMain 966 | * -------------------------- 967 | */ 968 | export const QuickRequest = () => new QuickRequestMain(); -------------------------------------------------------------------------------- /dist/js/quick-request-module.min.js: -------------------------------------------------------------------------------- 1 | class QuickRequestException{constructor(custom){const error=new Error(custom);throw console.error("⚠ QuickRequestException ⚠ | "+error.toString(),{name:error.name,message:error.message,trace:error.stack.split("\n").map(function(element){return element.trim().replaceAll("at ","🔍 At: ")})})}}const QuickRequestHelpers={extractLastSegment:(value,separator="/")=>{if(!QuickRequestHelpers.isValueEmpty(value)){const segments=value.split(separator);const lastSegment=segments[segments.length-1];return lastSegment}return""},isValueEmpty:value=>{if(value==null){return true}if(typeof value==="string"&&value.trim()===""){return true}if(Array.isArray(value)&&value.length===0){return true}if(typeof value==="object"&&Object.keys(value).length===0){return true}return false},getTokenCSRF:()=>{const csrfTokenMeta=document.querySelector('meta[name="csrf-token"]');if(csrfTokenMeta){return csrfTokenMeta.getAttribute("content")}else{throw new QuickRequestException(`The meta tag "csrf-token" was not found. Please remember to add it in the head section: `)}}};class QuickRequestValidate{constructor(options){const required=["url"];required.forEach(element=>{let exist=options.hasOwnProperty(element);if(!exist){throw new QuickRequestException("The property '"+element+"' is mandatory in the request options.")}})}}class QuickRequestElements{tagCheck(element){const validTags=["INPUT","TEXTAREA","SELECT"];return validTags.includes(element.tagName)}typeCheck(element){const disabledTypes=["submit","button"];return!disabledTypes.includes(element.type)}typeFile(element){return element.type==="file"}typeCheckboxOrRadio(element){const validTypes=["radio","checkbox"];return validTypes.includes(element.type)}}class QuickRequestMain{constructor(){this.config={expect:"JSON",confirm:false,activateEvent:false,eventListener:{},preventDefault:true,method:"GET",headers:{"X-CSRF-TOKEN":QuickRequestHelpers.getTokenCSRF(),Accept:"application/json"}}}eventListener(event,selectors){if(QuickRequestHelpers.isValueEmpty(event)){throw new QuickRequestException("You must define a valid event within the eventListener method(<>,<>)")}if(QuickRequestHelpers.isValueEmpty(selectors)){throw new QuickRequestException("You must define a valid selector within the eventListener method(<>,<>)")}this.config.activateEvent=true;this.config.eventListener.event=event;this.config.eventListener.selectors=document.querySelectorAll(selectors);return this}preventDefault(state=true){if(state!==true&&state!==false){throw new QuickRequestException("The argument can only be <> or <>, or omit it to default to <>")}this.config.preventDefault=state;return this}headers(headersCustom={}){this.config.headers={...this.config.headers,...headersCustom};return this}confirm(callback){this.config.confirm=callback;return this}post(options={}){this.config.method="POST";if(options.hasOwnProperty("headers")){this.headers(options.headers);delete options["headers"]}this.config.options=options;this.config.expect=options.expect||"JSON";const manager=new QuickRequestEvents(this.config);return manager.handler}get(options={}){this.config.method="GET";if(options.hasOwnProperty("headers")){this.headers(options.headers);delete options["headers"]}this.config.options=options;this.config.expect=options.expect||"JSON";const manager=new QuickRequestEvents(this.config);return manager.handler}put(options={}){this.config.method="PUT";if(options.hasOwnProperty("headers")){this.headers(options.headers);delete options["headers"]}this.config.options=options;this.config.expect=options.expect||"JSON";const manager=new QuickRequestEvents(this.config);return manager.handler}patch(options={}){this.config.method="PATCH";if(options.hasOwnProperty("headers")){this.headers(options.headers);delete options["headers"]}this.config.options=options;this.config.expect=options.expect||"JSON";const manager=new QuickRequestEvents(this.config);return manager.handler}"delete"(options={}){this.config.method="DELETE";if(options.hasOwnProperty("headers")){this.headers(options.headers);delete options["headers"]}this.config.options=options;this.config.expect=options.expect||"JSON";const manager=new QuickRequestEvents(this.config);return manager.handler}}class QuickRequestEvents{constructor(config){this.config=config;this.config.callbacksEvents=[];new QuickRequestValidate(this.config.options);this.checkEvent()}checkEvent(){if(this.config.activateEvent){const self=this;this.config.eventListener.selectors.forEach(function(element){const funcEvent=function(e){if(self.config.preventDefault){e.preventDefault()}new QuickRequestFetch(self.config)};element.addEventListener(self.config.eventListener.event,funcEvent);self.config.callbacksEvents.push({element:element,event:self.config.eventListener.event,func:funcEvent})});self.handler=new QuickRequesHandler(self.config)}else{new QuickRequestFetch(this.config);this.handler=new QuickRequesHandler(this.config)}}}class QuickRequestFetch{constructor(config){this.config=config;this.config.baseUrl=`${window.location.protocol}//${window.location.host}/`;this.config.formData=new FormData;this.config.hasFile=false;this.custom={};this.dispatch()}data(){const originForm=this.config.options.form||null;if(originForm!==null){const inputsCheck=new QuickRequestElements;let controls=[];const realElementForm=document.getElementById(originForm);if(realElementForm){controls=Array.from(realElementForm.elements)}else{throw new QuickRequestException("Could not find any form with the id "+originForm)}controls.forEach(element=>{if(inputsCheck.tagCheck(element)&&inputsCheck.typeCheck(element)){if(inputsCheck.typeFile(element)){if(element.files.length>0){this.config.formData.append(element.name,element.files[0]);this.config.hasFile=true}}else if(inputsCheck.typeCheckboxOrRadio(element)){if(element.checked){this.config.formData.append(element.name,element.value)}}else{if(!QuickRequestHelpers.isValueEmpty(element.value)){this.config.formData.append(element.name,element.value)}}}});const originData=this.config.options.data||null;if(!QuickRequestHelpers.isValueEmpty(originData)){for(const[key,value]of Object.entries(originData)){this.config.formData.append(key,value)}}}else{let originData;if(typeof this.config.options.data==="function"){originData=this.config.options.data(this.custom)}else{originData=this.config.options.data||null}if(!QuickRequestHelpers.isValueEmpty(originData)){for(const[key,value]of Object.entries(originData)){this.config.formData.append(key,value)}}}}method(){let url=(this.config.baseUrl+this.config.options.url).replaceAll("//","/").replaceAll(":/","://");let params={};if(["GET","HEAD"].includes(this.config.method)){if(this.config.hasFile){throw new QuickRequestException("File uploads are not allowed with "+this.config.method+" due to URL length limitations; use POST instead.")}if(url.includes("?")){throw new QuickRequestException("The URL appears to already have a query, as it contains the '?' character in its structure. In case you need to send additional data, use the 'data' property.")}let query=new URLSearchParams(this.config.formData).toString();url=`${url}?${query}`;params={method:this.config.method,headers:this.config.headers}}else if(["PATCH","PUT","DELETE"].includes(this.config.method)){if(this.config.hasFile){throw new QuickRequestException("File uploads are not allowed with "+this.config.method+" as it uses JSON headers; use POST instead.")}let jsonObject={};for(let[clave,valor]of this.config.formData.entries()){let isArray=clave.includes("[]");let index=clave.replace("[]","");if(jsonObject[index]===undefined){if(isArray){jsonObject[index]=[valor]}else{jsonObject[index]=valor}}else{try{jsonObject[index].push(valor)}catch(error){throw new QuickRequestException("Two distinct inputs, excluding Checkboxes and Radios, have the same name '"+index+"' without the square brackets '[]', which prevents them from being treated as the same data when sent to the backend as an array.")}}}this.config.headers["Content-Type"]="application/json";params={method:this.config.method,headers:this.config.headers,body:JSON.stringify(jsonObject)}}else{params={method:this.config.method,headers:this.config.headers,body:this.config.formData}}const propertiesToValidate=["mode","cache","credentials","redirect","referrerPolicy"];for(const property of propertiesToValidate){if(this.config.hasOwnProperty(property)){params[property]=this.config[property]}}return{realUrl:url,realParams:params}}dispatch(){if(typeof this.config.confirm==="function"){if(this.config.confirm(this.custom)===true){this.send()}}else{this.send()}}async send(){this.data();if(this.config.options.before&&typeof this.config.options.before==="function"){this.config.options.before(this.custom)}let custom_response;let custom_data;let custom_status;let errors;let responseJSON;const params=this.method();const response=await fetch(params.realUrl,params.realParams);if(!response.ok){try{responseJSON=await response.json()}catch(error){responseJSON=response.statusText}if(typeof responseJSON=="string"){errors={message:responseJSON,errors:[]}}else{if(responseJSON.hasOwnProperty("exception")&&responseJSON.hasOwnProperty("file")&&responseJSON.hasOwnProperty("message")){errors={message:responseJSON.message,errors:[]};responseJSON.trace.forEach(element=>{if(element["class"]){errors.errors[`${element["class"]}:${element.line}`]=[`${element.file}${element.type}${element["function"]}`]}})}else if(typeof responseJSON==="object"&&Object.keys(responseJSON).length>0){errors={message:responseJSON.message||response.statusText,errors:responseJSON.errors||[]}}else{errors={message:response.statusText,errors:[]}}custom_response={data:errors,success:response.ok,status:response.status,statusText:response.statusText,headers:response.headers,url:response.url};custom_data=errors;custom_status=response.status}if(this.config.options.error&&typeof this.config.options.error==="function"){this.config.options.error(custom_response,custom_data,custom_status)}}else{try{if(this.config.expect.toLowerCase().trim()=="json"){try{responseJSON=await response.json()}catch(error){responseJSON=null}}else if(this.config.expect.toLowerCase().trim()=="text"){try{responseJSON=await response.text()}catch(error){responseJSON=null}}else if(this.config.expect.toLowerCase().trim()=="blob"){try{responseJSON=await response.blob()}catch(error){responseJSON=null}}else{throw new Error("Only the values 'json', 'blob' or 'text' are allowed for the 'expect' property.")}}catch(error){throw new QuickRequestException(error.message)}custom_response={data:responseJSON,success:response.ok,status:response.status,statusText:response.statusText,headers:response.headers,url:response.url};custom_data=responseJSON;custom_status=response.status;this.config.options.success(custom_response,custom_data,custom_status)}if(this.config.options.after&&typeof this.config.options.after==="function"){this.config.options.after(custom_response,custom_data,custom_status)}}}class QuickRequesHandler{constructor({callbacksEvents,headers,method,options,preventDefault}){this.getMethod=function(){return method};this.getEndPoint=function(){return options.url};this.getIdForm=function(){return options.form||null};this.getData=function(){return options.data||null};this.getCustomHeaders=function(){return headers};this.getEvents=function(){return{preventDefault:!QuickRequestHelpers.isValueEmpty(callbacksEvents)?preventDefault:null,events:callbacksEvents}};this.call=function(){return{before:options.before,success:options.success,error:options.error,after:options.after}};this.getParams=function(){return options};this.removeEventListener=function(){if(!QuickRequestHelpers.isValueEmpty(callbacksEvents)){callbacksEvents.forEach(data=>{data.element.removeEventListener(data.event,data.func)});this.getEvents=function(){return[]};return true}return false}}}export const QuickRequestBlobs={config:{blob:null,name:null,extension:null},setBlob:function(blob){this.config.blob=blob;return this},setName:function(name){this.config.name=name;return this},setExtension:function(extension){this.config.extension=extension;return this},download:function(){let{blob,name,extension}=this.config;if(!blob){throw new QuickRequestException("Blob is not set in QuickRequestBlobs.setBlob().")}if(!name){throw new QuickRequestException("Name is not set in QuickRequestBlobs.setName().")}if(extension===null&&blob.type){const mimeTypeParts=blob.type.split("/");if(mimeTypeParts.length>=1){extension=mimeTypeParts[1]}}if(extension===null){throw new QuickRequestException("Extension is not set in QuickRequestBlobs.setExtension().")}const blobUrl=URL.createObjectURL(blob);const nameWithExtension=(name+"."+extension).replaceAll("..",".");const downloadLink=document.createElement("a");downloadLink.href=blobUrl;downloadLink.download=nameWithExtension;downloadLink.textContent=name;const event=new MouseEvent("click",{view:window,bubbles:true,cancelable:false});downloadLink.dispatchEvent(event)}};export const QuickRequest=()=>new QuickRequestMain; -------------------------------------------------------------------------------- /dist/js/quick-request.js: -------------------------------------------------------------------------------- 1 | /** 2 | * QuickRequest v1.1.0 3 | * Script 4 | * (c) Raul Mauricio Uñate Castro 5 | * https://github.com/rmunate 6 | * MIT 7 | */ 8 | 9 | /** 10 | * -------------------------- 11 | * Class for Throwing Errors to the Console. 12 | * -------------------------- 13 | */ 14 | class QuickRequestException { 15 | 16 | /** 17 | * Method to throw an exception to the console. 18 | * Halts execution with 'throw'. 19 | * @param {string} custom - Custom error message. 20 | */ 21 | constructor(custom){ 22 | 23 | // Create an error with the custom message 24 | const error = new Error(custom); 25 | 26 | // Log the error to the console and stop execution 27 | throw console.error("⚠ QuickRequestException ⚠ | " + error.toString(), { 28 | name: error.name, 29 | message: error.message, 30 | trace: error.stack.split("\n").map(function(element){ 31 | return element.trim().replaceAll("at ","🔍 At: "); 32 | }), 33 | }); 34 | } 35 | } 36 | 37 | /** 38 | * -------------------------- 39 | * Herlpers General - QuickRequest 40 | * -------------------------- 41 | */ 42 | const QuickRequestHelpers = { 43 | 44 | /** 45 | * Extracts the last segment from a given value based on a specified separator. 46 | * 47 | * @param {*} value - The value from which to extract the last segment. 48 | * @param {string} [separator="/"] - The separator used to split the value into segments. Default is "/". 49 | * @returns {string} - The last segment of the value. 50 | */ 51 | extractLastSegment: (value, separator = "/") => { 52 | 53 | // Check if the input value is not empty 54 | if (!QuickRequestHelpers.isValueEmpty(value)) { 55 | 56 | // Split the input string by "separator" and store the result in the 'segments' array 57 | const segments = value.split(separator); 58 | 59 | // Retrieve the last segment from the 'segments' array 60 | const lastSegment = segments[segments.length - 1]; 61 | 62 | // Return the last segment 63 | return lastSegment; 64 | } 65 | 66 | // If the input value is empty, return an empty string 67 | return ''; 68 | 69 | }, 70 | 71 | /** 72 | * Checks if a value is empty (null, undefined, empty string, empty array, or empty object) 73 | * 74 | * @param {*} value - The value to be checked 75 | * @returns {boolean} - True if the value is empty, false otherwise 76 | */ 77 | isValueEmpty: (value) => { 78 | if (value == null) { 79 | return true; 80 | } 81 | 82 | if (typeof value === 'string' && value.trim() === '') { 83 | return true; 84 | } 85 | 86 | if (Array.isArray(value) && value.length === 0) { 87 | return true; 88 | } 89 | 90 | if (typeof value === 'object' && Object.keys(value).length === 0) { 91 | return true; 92 | } 93 | 94 | return false; 95 | }, 96 | 97 | /** 98 | * Get Valid Token Laravel 99 | * @returns {string} - Token Laravel 100 | */ 101 | getTokenCSRF: () => { 102 | 103 | const csrfTokenMeta = document.querySelector('meta[name="csrf-token"]'); 104 | 105 | if (csrfTokenMeta) { 106 | return csrfTokenMeta.getAttribute('content'); 107 | } else { 108 | throw new QuickRequestException(`The meta tag "csrf-token" was not found. Please remember to add it in the head section: `); 109 | } 110 | }, 111 | 112 | }; 113 | 114 | /** 115 | * -------------------------- 116 | * Class for Validating the Presence of Required Values in Requests. 117 | * -------------------------- 118 | */ 119 | class QuickRequestValidate { 120 | 121 | /** 122 | * Validates that the mandatory values in the options are present. 123 | * @param {object} options - The request options to validate. 124 | * @returns {boolean} - True if all mandatory properties are present, false otherwise. 125 | */ 126 | constructor(options){ 127 | 128 | const required = ['url']; 129 | 130 | required.forEach(element => { 131 | let exist = options.hasOwnProperty(element) 132 | if (!exist) { 133 | throw new QuickRequestException("The property '" + element + "' is mandatory in the request options."); 134 | } 135 | }); 136 | 137 | } 138 | 139 | } 140 | 141 | /** 142 | * ----------------------------------- 143 | * Class for Validating HTML Elements 144 | * ----------------------------------- 145 | */ 146 | class QuickRequestElements { 147 | 148 | /** 149 | * Check if the element has a valid tag for extracting its value within FormData. 150 | * @param {HTMLElement} element - The HTML element to validate. 151 | * @returns {boolean} - True if the tag is valid, false otherwise. 152 | */ 153 | tagCheck(element){ 154 | const validTags = ['INPUT', 'TEXTAREA', 'SELECT']; 155 | return validTags.includes(element.tagName) 156 | } 157 | 158 | /** 159 | * Check if the input type is valid to be considered as a valid form control. 160 | * @param {HTMLInputElement} element - The input element to validate. 161 | * @returns {boolean} - True if the input type is valid, false otherwise. 162 | */ 163 | typeCheck(element){ 164 | const disabledTypes = ['submit','button'] 165 | return !disabledTypes.includes(element.type) 166 | } 167 | 168 | /** 169 | * Check if the input is of type "file". 170 | * @param {HTMLInputElement} element - The input element to validate. 171 | * @returns {boolean} - True if the input type is "file," false otherwise. 172 | */ 173 | typeFile(element){ 174 | return element.type === 'file'; 175 | } 176 | 177 | /** 178 | * ----------------------------------- 179 | * Check if it is a checkbox or a radio input 180 | * ----------------------------------- 181 | * Determine if the input is of type "checkbox" or "radio." 182 | * @param {HTMLInputElement} element - The input element to validate. 183 | * @returns {boolean} - True if the input type is "checkbox" or "radio," false otherwise. 184 | */ 185 | typeCheckboxOrRadio(element){ 186 | const validTypes = ['radio','checkbox'] 187 | return validTypes.includes(element.type) 188 | } 189 | 190 | } 191 | 192 | /** 193 | * -------------------------- 194 | * Class for preparing data for Fetch requests. 195 | * -------------------------- 196 | */ 197 | class QuickRequestMain { 198 | 199 | /* API request configuration. */ 200 | constructor(){ 201 | this.config = { 202 | expect: "JSON", 203 | confirm: false, 204 | activateEvent: false, 205 | eventListener: {}, 206 | preventDefault: true, 207 | method: 'GET', 208 | headers: { 209 | 'X-CSRF-TOKEN': QuickRequestHelpers.getTokenCSRF(), 210 | 'Accept': 'application/json', 211 | }, 212 | }; 213 | } 214 | 215 | /** 216 | * Set up an event listener for triggering the event. 217 | * @param {string} event - Event name to listen for. 218 | * @param {string} selectors - Selectors for event targets. 219 | */ 220 | eventListener(event, selectors){ 221 | 222 | if (QuickRequestHelpers.isValueEmpty(event)) { 223 | throw new QuickRequestException('You must define a valid event within the eventListener method(<>,<>)'); 224 | } 225 | 226 | if (QuickRequestHelpers.isValueEmpty(selectors)) { 227 | throw new QuickRequestException('You must define a valid selector within the eventListener method(<>,<>)'); 228 | } 229 | 230 | this.config.activateEvent = true; 231 | this.config.eventListener.event = event; 232 | this.config.eventListener.selectors = document.querySelectorAll(selectors); 233 | 234 | return this; 235 | } 236 | 237 | /** 238 | * Control whether to prevent the default behavior of the event. 239 | * @param {boolean} state - Set to true to prevent the default behavior, false otherwise. 240 | */ 241 | preventDefault(state = true){ 242 | 243 | if (state !== true && state !== false) { 244 | throw new QuickRequestException('The argument can only be <> or <>, or omit it to default to <>'); 245 | } 246 | 247 | this.config.preventDefault = state; 248 | 249 | return this; 250 | } 251 | 252 | /** 253 | * Add custom headers to the request. 254 | * @param {object} headersCustom - Custom headers to include. 255 | */ 256 | headers(headersCustom = {}){ 257 | 258 | this.config.headers = { 259 | ...this.config.headers, 260 | ...headersCustom 261 | }; 262 | 263 | return this; 264 | } 265 | 266 | /** 267 | * Confirm Pre-Request 268 | * @param {*} callback 269 | * @returns 270 | */ 271 | confirm(callback){ 272 | this.config.confirm = callback; 273 | return this; 274 | } 275 | 276 | /** 277 | * Make a POST request. 278 | * @param {object} options - Additional options for the request. 279 | */ 280 | post(options = {}){ 281 | 282 | this.config.method = 'POST'; 283 | 284 | if (options.hasOwnProperty('headers')) { 285 | this.headers(options.headers); 286 | delete options['headers']; 287 | } 288 | 289 | this.config.options = options; 290 | this.config.expect = options.expect || "JSON"; 291 | 292 | const manager = new QuickRequestEvents(this.config); 293 | return manager.handler; 294 | } 295 | 296 | /** 297 | * Make a GET request. 298 | * @param {object} options - Additional options for the request. 299 | */ 300 | get(options = {}){ 301 | 302 | this.config.method = 'GET'; 303 | 304 | if (options.hasOwnProperty('headers')) { 305 | this.headers(options.headers); 306 | delete options['headers']; 307 | } 308 | 309 | this.config.options = options; 310 | this.config.expect = options.expect || "JSON"; 311 | 312 | const manager = new QuickRequestEvents(this.config); 313 | return manager.handler; 314 | } 315 | 316 | /** 317 | * Make a PUT request. 318 | * @param {object} options - Additional options for the request. 319 | */ 320 | put(options = {}){ 321 | 322 | this.config.method = 'PUT'; 323 | 324 | if (options.hasOwnProperty('headers')) { 325 | this.headers(options.headers); 326 | delete options['headers']; 327 | } 328 | 329 | this.config.options = options; 330 | this.config.expect = options.expect || "JSON"; 331 | 332 | const manager = new QuickRequestEvents(this.config); 333 | return manager.handler; 334 | } 335 | 336 | /** 337 | * Make a PATCH request. 338 | * @param {object} options - Additional options for the request. 339 | */ 340 | patch(options = {}){ 341 | 342 | this.config.method = 'PATCH'; 343 | 344 | if (options.hasOwnProperty('headers')) { 345 | this.headers(options.headers); 346 | delete options['headers']; 347 | } 348 | 349 | this.config.options = options; 350 | this.config.expect = options.expect || "JSON"; 351 | 352 | const manager = new QuickRequestEvents(this.config); 353 | return manager.handler; 354 | } 355 | 356 | /** 357 | * Make a DELETE request. 358 | * @param {object} options - Additional options for the request. 359 | */ 360 | delete(options = {}){ 361 | 362 | this.config.method = 'DELETE'; 363 | 364 | if (options.hasOwnProperty('headers')) { 365 | this.headers(options.headers); 366 | delete options['headers']; 367 | } 368 | 369 | this.config.options = options; 370 | this.config.expect = options.expect || "JSON"; 371 | 372 | const manager = new QuickRequestEvents(this.config); 373 | return manager.handler; 374 | } 375 | } 376 | 377 | /** 378 | * -------------------------- 379 | * Class for Triggering Events. 380 | * -------------------------- 381 | */ 382 | class QuickRequestEvents { 383 | 384 | /** 385 | * Create an instance to execute a Fetch request. 386 | * @param {object} config - Request configuration. 387 | */ 388 | constructor(config){ 389 | 390 | // Configuration values for the request. 391 | this.config = config; 392 | this.config.callbacksEvents = []; 393 | 394 | // Perform validation of required values. 395 | new QuickRequestValidate(this.config.options); 396 | 397 | // Check whether to trigger the event. 398 | this.checkEvent(); 399 | } 400 | 401 | /** 402 | * Check if the event should be triggered. 403 | */ 404 | checkEvent(){ 405 | 406 | /* Determine whether to create an event to trigger the Fetch request */ 407 | if (this.config.activateEvent) { 408 | 409 | /* Create a reference to the main instance */ 410 | const self = this; 411 | 412 | /* Iterate through the selection to trigger the events */ 413 | this.config.eventListener.selectors.forEach(function(element) { 414 | 415 | const funcEvent = function(e) { 416 | if (self.config.preventDefault) { 417 | e.preventDefault(); 418 | } 419 | 420 | new QuickRequestFetch(self.config); 421 | }; 422 | 423 | /* Create the event for each matching selector */ 424 | element.addEventListener(self.config.eventListener.event, funcEvent); 425 | 426 | /* Save Event Handlers */ 427 | self.config.callbacksEvents.push({ 428 | element: element, 429 | event: self.config.eventListener.event, 430 | func: funcEvent 431 | }); 432 | }); 433 | 434 | self.handler = new QuickRequesHandler(self.config); 435 | 436 | } else { 437 | 438 | new QuickRequestFetch(this.config); 439 | 440 | this.handler = new QuickRequesHandler(this.config); 441 | } 442 | } 443 | } 444 | 445 | /** 446 | * -------------------------- 447 | * Class for Handling Fetch Requests. 448 | * -------------------------- 449 | */ 450 | class QuickRequestFetch { 451 | 452 | /** 453 | * Request configuration values. 454 | * @param {object} config - Request configuration. 455 | */ 456 | constructor(config){ 457 | 458 | /* Received and custom configuration values */ 459 | this.config = config; 460 | this.config.baseUrl = `${window.location.protocol}//${window.location.host}/`; 461 | this.config.formData = new FormData(); 462 | this.config.hasFile = false; 463 | this.custom = {} 464 | 465 | /* Execute the request sending process */ 466 | this.dispatch(); 467 | } 468 | 469 | /** 470 | * Create the final FormData for sending data to the backend. 471 | */ 472 | data(){ 473 | 474 | // Determine if form or data is being used 475 | const originForm = this.config.options.form || null; 476 | 477 | // En caso de que el origen sea un form. 478 | if (originForm !== null) { 479 | 480 | // Check input elements 481 | const inputsCheck = new QuickRequestElements(); 482 | 483 | //Validate Form 484 | let controls = []; 485 | const realElementForm = document.getElementById(originForm); 486 | if (realElementForm) { 487 | controls = Array.from(realElementForm.elements); 488 | } else { 489 | throw new QuickRequestException("Could not find any form with the id " + originForm); 490 | } 491 | 492 | controls.forEach(element => { 493 | 494 | if (inputsCheck.tagCheck(element) && inputsCheck.typeCheck(element)) { 495 | 496 | if (inputsCheck.typeFile(element)) { 497 | 498 | if (element.files.length > 0) { 499 | this.config.formData.append(element.name, element.files[0]); 500 | this.config.hasFile = true; 501 | } 502 | 503 | } else if(inputsCheck.typeCheckboxOrRadio(element)){ 504 | 505 | if (element.checked) { 506 | this.config.formData.append(element.name, element.value); 507 | } 508 | 509 | } else { 510 | 511 | if(!QuickRequestHelpers.isValueEmpty(element.value)){ 512 | this.config.formData.append(element.name, element.value); 513 | } 514 | 515 | } 516 | } 517 | }); 518 | 519 | // Check if additional data is available 520 | const originData = this.config.options.data || null; 521 | 522 | if (!QuickRequestHelpers.isValueEmpty(originData)) { 523 | 524 | for (const [key, value] of Object.entries(originData)) { 525 | this.config.formData.append(key, value); 526 | } 527 | 528 | } 529 | 530 | } else { 531 | 532 | let originData; 533 | 534 | //Validar si es un callback 535 | if (typeof this.config.options.data === 'function') { 536 | originData = this.config.options.data(this.custom) 537 | } else { 538 | originData = this.config.options.data || null; 539 | } 540 | 541 | if (!QuickRequestHelpers.isValueEmpty(originData)) { 542 | 543 | for (const [key, value] of Object.entries(originData)) { 544 | this.config.formData.append(key, value); 545 | } 546 | 547 | } 548 | } 549 | } 550 | 551 | /** 552 | * Define request body settings based on the method. 553 | */ 554 | method(){ 555 | 556 | /* Define URL */ 557 | let url = (this.config.baseUrl + this.config.options.url).replaceAll("//","/").replaceAll(":/","://"); 558 | 559 | /* Params Fetch */ 560 | let params = {}; 561 | 562 | if (['GET','HEAD'].includes(this.config.method)) { 563 | 564 | /* Validate This Request Has Files */ 565 | if (this.config.hasFile) { 566 | throw new QuickRequestException("File uploads are not allowed with " + this.config.method + " due to URL length limitations; use POST instead."); 567 | } 568 | 569 | /* Validate Query In URL */ 570 | if (url.includes("?")) { 571 | throw new QuickRequestException("The URL appears to already have a query, as it contains the '?' character in its structure. In case you need to send additional data, use the 'data' property."); 572 | } 573 | 574 | /* Create Query */ 575 | let query = new URLSearchParams(this.config.formData).toString(); 576 | url = `${url}?${query}`; 577 | params = { 578 | method: this.config.method, 579 | headers: this.config.headers, 580 | }; 581 | 582 | } else if(['PATCH','PUT','DELETE'].includes(this.config.method)){ 583 | 584 | /* Validate This Request Has Files */ 585 | if (this.config.hasFile) { 586 | throw new QuickRequestException("File uploads are not allowed with " + this.config.method + " as it uses JSON headers; use POST instead."); 587 | } 588 | 589 | /* Create JSON */ 590 | let jsonObject = {}; 591 | 592 | for (let [clave, valor] of this.config.formData.entries()) { 593 | 594 | let isArray = clave.includes("[]"); 595 | let index = clave.replace("[]", ""); 596 | 597 | if (jsonObject[index] === undefined) { 598 | if (isArray) { 599 | jsonObject[index] = [valor]; 600 | } else { 601 | jsonObject[index] = valor; 602 | } 603 | } else { 604 | try { 605 | jsonObject[index].push(valor); 606 | } catch (error) { 607 | throw new QuickRequestException("Two distinct inputs, excluding Checkboxes and Radios, have the same name '"+index+"' without the square brackets '[]', which prevents them from being treated as the same data when sent to the backend as an array."); 608 | } 609 | } 610 | } 611 | 612 | /* Add Header JSON */ 613 | this.config.headers['Content-Type'] = 'application/json'; 614 | 615 | params = { 616 | method: this.config.method, 617 | headers: this.config.headers, 618 | body: JSON.stringify(jsonObject), 619 | } 620 | 621 | } else { 622 | 623 | params = { 624 | method: this.config.method, 625 | headers: this.config.headers, 626 | body: this.config.formData, 627 | } 628 | } 629 | 630 | /* Validate Others Properties */ 631 | const propertiesToValidate = ["mode", "cache", "credentials", "redirect", "referrerPolicy"]; 632 | 633 | for (const property of propertiesToValidate) { 634 | if (this.config.hasOwnProperty(property)) { 635 | params[property] = this.config[property]; 636 | } 637 | } 638 | 639 | return { 640 | realUrl: url, 641 | realParams: params 642 | }; 643 | } 644 | 645 | /** 646 | * Dospatch Fetch 647 | */ 648 | dispatch(){ 649 | if (typeof this.config.confirm === 'function') { 650 | if (this.config.confirm(this.custom) === true) { 651 | this.send(); 652 | } 653 | } else { 654 | this.send(); 655 | } 656 | } 657 | 658 | /** 659 | * Send the request to the backend. 660 | */ 661 | async send(){ 662 | 663 | /* FormData */ 664 | this.data(); 665 | 666 | /* Execute Pre-request Function */ 667 | if (this.config.options.before && typeof this.config.options.before === "function") { 668 | this.config.options.before(this.custom); 669 | } 670 | 671 | /* Response values */ 672 | let custom_response; 673 | let custom_data; 674 | let custom_status; 675 | let errors; 676 | let responseJSON; 677 | 678 | /* Determine the method to send the request to the server */ 679 | const params = this.method(); 680 | 681 | /* Execute the Fetch request */ 682 | const response = await fetch(params.realUrl, params.realParams); 683 | 684 | if (!response.ok) { 685 | 686 | try { 687 | responseJSON = await response.json(); 688 | } catch (error) { 689 | responseJSON = response.statusText; 690 | } 691 | 692 | if(typeof responseJSON == 'string'){ 693 | 694 | errors = { 695 | "message" : responseJSON, 696 | "errors" : [] 697 | } 698 | 699 | } else { 700 | 701 | if (responseJSON.hasOwnProperty('exception') && responseJSON.hasOwnProperty('file') && responseJSON.hasOwnProperty('message')) { 702 | 703 | errors = { 704 | "message" : responseJSON.message, 705 | "errors" : [] 706 | } 707 | 708 | responseJSON.trace.forEach(element => { 709 | if(element.class){ 710 | errors.errors[`${element.class}:${element.line}`] = [`${element.file}${element.type}${element.function}`] 711 | } 712 | }); 713 | 714 | } else if (typeof responseJSON === 'object' && Object.keys(responseJSON).length > 0) { 715 | 716 | errors = { 717 | "message" : responseJSON.message || response.statusText, 718 | "errors" : responseJSON.errors || [] 719 | } 720 | } else { 721 | 722 | errors = { 723 | "message" : response.statusText, 724 | "errors" : [] 725 | } 726 | 727 | } 728 | 729 | custom_response = { 730 | data: errors, 731 | success: response.ok, 732 | status: response.status, 733 | statusText: response.statusText, 734 | headers: response.headers, 735 | url: response.url 736 | } 737 | 738 | custom_data = errors 739 | 740 | custom_status = response.status 741 | } 742 | 743 | if (this.config.options.error && typeof this.config.options.error === "function") { 744 | this.config.options.error(custom_response, custom_data, custom_status); 745 | } 746 | 747 | } else { 748 | 749 | try { 750 | 751 | if (this.config.expect.toLowerCase().trim() == "json") { 752 | 753 | try { 754 | responseJSON = await response.json(); 755 | } catch (error) { 756 | responseJSON = null 757 | } 758 | 759 | } else if (this.config.expect.toLowerCase().trim() == "text") { 760 | 761 | try { 762 | responseJSON = await response.text(); 763 | } catch (error) { 764 | responseJSON = null 765 | } 766 | 767 | } else if (this.config.expect.toLowerCase().trim() == "blob") { 768 | 769 | try { 770 | responseJSON = await response.blob(); 771 | } catch (error) { 772 | responseJSON = null 773 | } 774 | 775 | } else { 776 | 777 | throw new Error("Only the values 'json', 'blob' or 'text' are allowed for the 'expect' property."); 778 | 779 | } 780 | 781 | } catch (error) { 782 | 783 | throw new QuickRequestException(error.message) 784 | 785 | } 786 | 787 | custom_response = { 788 | data: responseJSON, 789 | success: response.ok, 790 | status: response.status, 791 | statusText: response.statusText, 792 | headers: response.headers, 793 | url: response.url 794 | } 795 | 796 | custom_data = responseJSON 797 | 798 | custom_status = response.status 799 | 800 | this.config.options.success(custom_response, custom_data, custom_status); 801 | } 802 | 803 | // Execute Post-request Function 804 | if (this.config.options.after && typeof this.config.options.after === "function") { 805 | this.config.options.after(custom_response, custom_data, custom_status); 806 | } 807 | } 808 | } 809 | 810 | /** 811 | * -------------------------- 812 | * Class for Handling PHP2JS Requests. 813 | * -------------------------- 814 | */ 815 | class QuickRequesHandler { 816 | /** 817 | * Constructor for QuickRequesHandler. 818 | * @param {object} config - Configuration object for PHP2JS requests. 819 | */ 820 | constructor({ callbacksEvents, headers, method, options, preventDefault }) { 821 | // Method to get the HTTP method. 822 | this.getMethod = function () { 823 | return method; 824 | }; 825 | 826 | // Method to get the endpoint URL. 827 | this.getEndPoint = function () { 828 | return options.url; 829 | }; 830 | 831 | // Method to get the form ID (if specified). 832 | this.getIdForm = function () { 833 | return options.form || null; 834 | } 835 | 836 | // Method to get the request data (if provided). 837 | this.getData = function () { 838 | return options.data || null; 839 | } 840 | 841 | // Method to get custom request headers. 842 | this.getCustomHeaders = function () { 843 | return headers; 844 | } 845 | 846 | // Method to get event-related information. 847 | this.getEvents = function () { 848 | return { 849 | preventDefault: !QuickRequestHelpers.isValueEmpty(callbacksEvents) ? preventDefault : null, 850 | events: callbacksEvents 851 | }; 852 | } 853 | 854 | // Method to get callback functions. 855 | this.call = function () { 856 | return { 857 | before: options.before, 858 | success: options.success, 859 | error: options.error, 860 | after: options.after, 861 | }; 862 | } 863 | 864 | // Method to get all request options. 865 | this.getParams = function () { 866 | return options; 867 | } 868 | 869 | // Method to remove event listeners. 870 | this.removeEventListener = function () { 871 | 872 | if (!QuickRequestHelpers.isValueEmpty(callbacksEvents)) { 873 | callbacksEvents.forEach(data => { 874 | data.element.removeEventListener(data.event, data.func) 875 | }); 876 | 877 | // Reset the events array to empty. 878 | this.getEvents = function () { 879 | return []; 880 | } 881 | 882 | return true; 883 | } 884 | 885 | return false; 886 | } 887 | } 888 | } 889 | 890 | /** 891 | * -------------------------- 892 | * Blob Manager. 893 | * -------------------------- 894 | */ 895 | const QuickRequestBlobs = { 896 | 897 | config: { 898 | blob: null, 899 | name: null, 900 | extension: null, 901 | }, 902 | 903 | setBlob: function (blob) { 904 | this.config.blob = blob; 905 | return this; 906 | }, 907 | 908 | setName: function(name){ 909 | this.config.name = name; 910 | return this; 911 | }, 912 | 913 | setExtension: function(extension){ 914 | this.config.extension = extension; 915 | return this; 916 | }, 917 | 918 | download: function(){ 919 | 920 | let { blob, name, extension } = this.config; 921 | 922 | if (!blob) { 923 | throw new QuickRequestException('Blob is not set in QuickRequestBlobs.setBlob().'); 924 | } 925 | 926 | if (!name) { 927 | throw new QuickRequestException('Name is not set in QuickRequestBlobs.setName().'); 928 | } 929 | 930 | if (extension === null && blob.type) { 931 | 932 | const mimeTypeParts = blob.type.split('/'); 933 | if (mimeTypeParts.length >= 1) { 934 | extension = mimeTypeParts[1]; 935 | } 936 | } 937 | 938 | if (extension === null) { 939 | throw new QuickRequestException('Extension is not set in QuickRequestBlobs.setExtension().'); 940 | } 941 | 942 | const blobUrl = URL.createObjectURL(blob); 943 | const nameWithExtension = (name + "." + extension).replaceAll("..","."); 944 | 945 | // Create Element A 946 | const downloadLink = document.createElement('a'); 947 | downloadLink.href = blobUrl; 948 | downloadLink.download = nameWithExtension; 949 | downloadLink.textContent = name; 950 | 951 | // Emulate Click 952 | const event = new MouseEvent('click', { 953 | view: window, 954 | bubbles: true, 955 | cancelable: false, 956 | }); 957 | 958 | // Dispatch Event 959 | downloadLink.dispatchEvent(event); 960 | }, 961 | }; 962 | 963 | /** 964 | * -------------------------- 965 | * Accessor QuickRequestMain 966 | * -------------------------- 967 | */ 968 | const QuickRequest = () => new QuickRequestMain(); -------------------------------------------------------------------------------- /dist/js/quick-request.min.js: -------------------------------------------------------------------------------- 1 | class QuickRequestException{constructor(custom){const error=new Error(custom);throw console.error("⚠ QuickRequestException ⚠ | "+error.toString(),{name:error.name,message:error.message,trace:error.stack.split("\n").map(function(element){return element.trim().replaceAll("at ","🔍 At: ")})})}}const QuickRequestHelpers={extractLastSegment:(value,separator="/")=>{if(!QuickRequestHelpers.isValueEmpty(value)){const segments=value.split(separator);const lastSegment=segments[segments.length-1];return lastSegment}return""},isValueEmpty:value=>{if(value==null){return true}if(typeof value==="string"&&value.trim()===""){return true}if(Array.isArray(value)&&value.length===0){return true}if(typeof value==="object"&&Object.keys(value).length===0){return true}return false},getTokenCSRF:()=>{const csrfTokenMeta=document.querySelector('meta[name="csrf-token"]');if(csrfTokenMeta){return csrfTokenMeta.getAttribute("content")}else{throw new QuickRequestException(`The meta tag "csrf-token" was not found. Please remember to add it in the head section: `)}}};class QuickRequestValidate{constructor(options){const required=["url"];required.forEach(element=>{let exist=options.hasOwnProperty(element);if(!exist){throw new QuickRequestException("The property '"+element+"' is mandatory in the request options.")}})}}class QuickRequestElements{tagCheck(element){const validTags=["INPUT","TEXTAREA","SELECT"];return validTags.includes(element.tagName)}typeCheck(element){const disabledTypes=["submit","button"];return!disabledTypes.includes(element.type)}typeFile(element){return element.type==="file"}typeCheckboxOrRadio(element){const validTypes=["radio","checkbox"];return validTypes.includes(element.type)}}class QuickRequestMain{constructor(){this.config={expect:"JSON",confirm:false,activateEvent:false,eventListener:{},preventDefault:true,method:"GET",headers:{"X-CSRF-TOKEN":QuickRequestHelpers.getTokenCSRF(),Accept:"application/json"}}}eventListener(event,selectors){if(QuickRequestHelpers.isValueEmpty(event)){throw new QuickRequestException("You must define a valid event within the eventListener method(<>,<>)")}if(QuickRequestHelpers.isValueEmpty(selectors)){throw new QuickRequestException("You must define a valid selector within the eventListener method(<>,<>)")}this.config.activateEvent=true;this.config.eventListener.event=event;this.config.eventListener.selectors=document.querySelectorAll(selectors);return this}preventDefault(state=true){if(state!==true&&state!==false){throw new QuickRequestException("The argument can only be <> or <>, or omit it to default to <>")}this.config.preventDefault=state;return this}headers(headersCustom={}){this.config.headers={...this.config.headers,...headersCustom};return this}confirm(callback){this.config.confirm=callback;return this}post(options={}){this.config.method="POST";if(options.hasOwnProperty("headers")){this.headers(options.headers);delete options["headers"]}this.config.options=options;this.config.expect=options.expect||"JSON";const manager=new QuickRequestEvents(this.config);return manager.handler}get(options={}){this.config.method="GET";if(options.hasOwnProperty("headers")){this.headers(options.headers);delete options["headers"]}this.config.options=options;this.config.expect=options.expect||"JSON";const manager=new QuickRequestEvents(this.config);return manager.handler}put(options={}){this.config.method="PUT";if(options.hasOwnProperty("headers")){this.headers(options.headers);delete options["headers"]}this.config.options=options;this.config.expect=options.expect||"JSON";const manager=new QuickRequestEvents(this.config);return manager.handler}patch(options={}){this.config.method="PATCH";if(options.hasOwnProperty("headers")){this.headers(options.headers);delete options["headers"]}this.config.options=options;this.config.expect=options.expect||"JSON";const manager=new QuickRequestEvents(this.config);return manager.handler}"delete"(options={}){this.config.method="DELETE";if(options.hasOwnProperty("headers")){this.headers(options.headers);delete options["headers"]}this.config.options=options;this.config.expect=options.expect||"JSON";const manager=new QuickRequestEvents(this.config);return manager.handler}}class QuickRequestEvents{constructor(config){this.config=config;this.config.callbacksEvents=[];new QuickRequestValidate(this.config.options);this.checkEvent()}checkEvent(){if(this.config.activateEvent){const self=this;this.config.eventListener.selectors.forEach(function(element){const funcEvent=function(e){if(self.config.preventDefault){e.preventDefault()}new QuickRequestFetch(self.config)};element.addEventListener(self.config.eventListener.event,funcEvent);self.config.callbacksEvents.push({element:element,event:self.config.eventListener.event,func:funcEvent})});self.handler=new QuickRequesHandler(self.config)}else{new QuickRequestFetch(this.config);this.handler=new QuickRequesHandler(this.config)}}}class QuickRequestFetch{constructor(config){this.config=config;this.config.baseUrl=`${window.location.protocol}//${window.location.host}/`;this.config.formData=new FormData;this.config.hasFile=false;this.custom={};this.dispatch()}data(){const originForm=this.config.options.form||null;if(originForm!==null){const inputsCheck=new QuickRequestElements;let controls=[];const realElementForm=document.getElementById(originForm);if(realElementForm){controls=Array.from(realElementForm.elements)}else{throw new QuickRequestException("Could not find any form with the id "+originForm)}controls.forEach(element=>{if(inputsCheck.tagCheck(element)&&inputsCheck.typeCheck(element)){if(inputsCheck.typeFile(element)){if(element.files.length>0){this.config.formData.append(element.name,element.files[0]);this.config.hasFile=true}}else if(inputsCheck.typeCheckboxOrRadio(element)){if(element.checked){this.config.formData.append(element.name,element.value)}}else{if(!QuickRequestHelpers.isValueEmpty(element.value)){this.config.formData.append(element.name,element.value)}}}});const originData=this.config.options.data||null;if(!QuickRequestHelpers.isValueEmpty(originData)){for(const[key,value]of Object.entries(originData)){this.config.formData.append(key,value)}}}else{let originData;if(typeof this.config.options.data==="function"){originData=this.config.options.data(this.custom)}else{originData=this.config.options.data||null}if(!QuickRequestHelpers.isValueEmpty(originData)){for(const[key,value]of Object.entries(originData)){this.config.formData.append(key,value)}}}}method(){let url=(this.config.baseUrl+this.config.options.url).replaceAll("//","/").replaceAll(":/","://");let params={};if(["GET","HEAD"].includes(this.config.method)){if(this.config.hasFile){throw new QuickRequestException("File uploads are not allowed with "+this.config.method+" due to URL length limitations; use POST instead.")}if(url.includes("?")){throw new QuickRequestException("The URL appears to already have a query, as it contains the '?' character in its structure. In case you need to send additional data, use the 'data' property.")}let query=new URLSearchParams(this.config.formData).toString();url=`${url}?${query}`;params={method:this.config.method,headers:this.config.headers}}else if(["PATCH","PUT","DELETE"].includes(this.config.method)){if(this.config.hasFile){throw new QuickRequestException("File uploads are not allowed with "+this.config.method+" as it uses JSON headers; use POST instead.")}let jsonObject={};for(let[clave,valor]of this.config.formData.entries()){let isArray=clave.includes("[]");let index=clave.replace("[]","");if(jsonObject[index]===undefined){if(isArray){jsonObject[index]=[valor]}else{jsonObject[index]=valor}}else{try{jsonObject[index].push(valor)}catch(error){throw new QuickRequestException("Two distinct inputs, excluding Checkboxes and Radios, have the same name '"+index+"' without the square brackets '[]', which prevents them from being treated as the same data when sent to the backend as an array.")}}}this.config.headers["Content-Type"]="application/json";params={method:this.config.method,headers:this.config.headers,body:JSON.stringify(jsonObject)}}else{params={method:this.config.method,headers:this.config.headers,body:this.config.formData}}const propertiesToValidate=["mode","cache","credentials","redirect","referrerPolicy"];for(const property of propertiesToValidate){if(this.config.hasOwnProperty(property)){params[property]=this.config[property]}}return{realUrl:url,realParams:params}}dispatch(){if(typeof this.config.confirm==="function"){if(this.config.confirm(this.custom)===true){this.send()}}else{this.send()}}async send(){this.data();if(this.config.options.before&&typeof this.config.options.before==="function"){this.config.options.before(this.custom)}let custom_response;let custom_data;let custom_status;let errors;let responseJSON;const params=this.method();const response=await fetch(params.realUrl,params.realParams);if(!response.ok){try{responseJSON=await response.json()}catch(error){responseJSON=response.statusText}if(typeof responseJSON=="string"){errors={message:responseJSON,errors:[]}}else{if(responseJSON.hasOwnProperty("exception")&&responseJSON.hasOwnProperty("file")&&responseJSON.hasOwnProperty("message")){errors={message:responseJSON.message,errors:[]};responseJSON.trace.forEach(element=>{if(element["class"]){errors.errors[`${element["class"]}:${element.line}`]=[`${element.file}${element.type}${element["function"]}`]}})}else if(typeof responseJSON==="object"&&Object.keys(responseJSON).length>0){errors={message:responseJSON.message||response.statusText,errors:responseJSON.errors||[]}}else{errors={message:response.statusText,errors:[]}}custom_response={data:errors,success:response.ok,status:response.status,statusText:response.statusText,headers:response.headers,url:response.url};custom_data=errors;custom_status=response.status}if(this.config.options.error&&typeof this.config.options.error==="function"){this.config.options.error(custom_response,custom_data,custom_status)}}else{try{if(this.config.expect.toLowerCase().trim()=="json"){try{responseJSON=await response.json()}catch(error){responseJSON=null}}else if(this.config.expect.toLowerCase().trim()=="text"){try{responseJSON=await response.text()}catch(error){responseJSON=null}}else if(this.config.expect.toLowerCase().trim()=="blob"){try{responseJSON=await response.blob()}catch(error){responseJSON=null}}else{throw new Error("Only the values 'json', 'blob' or 'text' are allowed for the 'expect' property.")}}catch(error){throw new QuickRequestException(error.message)}custom_response={data:responseJSON,success:response.ok,status:response.status,statusText:response.statusText,headers:response.headers,url:response.url};custom_data=responseJSON;custom_status=response.status;this.config.options.success(custom_response,custom_data,custom_status)}if(this.config.options.after&&typeof this.config.options.after==="function"){this.config.options.after(custom_response,custom_data,custom_status)}}}class QuickRequesHandler{constructor({callbacksEvents,headers,method,options,preventDefault}){this.getMethod=function(){return method};this.getEndPoint=function(){return options.url};this.getIdForm=function(){return options.form||null};this.getData=function(){return options.data||null};this.getCustomHeaders=function(){return headers};this.getEvents=function(){return{preventDefault:!QuickRequestHelpers.isValueEmpty(callbacksEvents)?preventDefault:null,events:callbacksEvents}};this.call=function(){return{before:options.before,success:options.success,error:options.error,after:options.after}};this.getParams=function(){return options};this.removeEventListener=function(){if(!QuickRequestHelpers.isValueEmpty(callbacksEvents)){callbacksEvents.forEach(data=>{data.element.removeEventListener(data.event,data.func)});this.getEvents=function(){return[]};return true}return false}}}const QuickRequestBlobs={config:{blob:null,name:null,extension:null},setBlob:function(blob){this.config.blob=blob;return this},setName:function(name){this.config.name=name;return this},setExtension:function(extension){this.config.extension=extension;return this},download:function(){let{blob,name,extension}=this.config;if(!blob){throw new QuickRequestException("Blob is not set in QuickRequestBlobs.setBlob().")}if(!name){throw new QuickRequestException("Name is not set in QuickRequestBlobs.setName().")}if(extension===null&&blob.type){const mimeTypeParts=blob.type.split("/");if(mimeTypeParts.length>=1){extension=mimeTypeParts[1]}}if(extension===null){throw new QuickRequestException("Extension is not set in QuickRequestBlobs.setExtension().")}const blobUrl=URL.createObjectURL(blob);const nameWithExtension=(name+"."+extension).replaceAll("..",".");const downloadLink=document.createElement("a");downloadLink.href=blobUrl;downloadLink.download=nameWithExtension;downloadLink.textContent=name;const event=new MouseEvent("click",{view:window,bubbles:true,cancelable:false});downloadLink.dispatchEvent(event)}};const QuickRequest=()=>new QuickRequestMain; -------------------------------------------------------------------------------- /docs/.vitepress/config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitepress' 2 | 3 | export default defineConfig({ 4 | title: "Quick Request Laravel", 5 | description: "Easily consume your endpoints in Laravel. Using Ajax? Discover something even more exciting! 💻✨", 6 | lang: 'en-US', 7 | lastUpdated: true, 8 | base: '/Quick-Request-Laravel', 9 | themeConfig: { 10 | footer: { 11 | message: 'Released under the MIT License.', 12 | copyright: 'Copyright © 2021-2024 Raul Mauricio Uñate Castro' 13 | }, 14 | editLink: { 15 | pattern: 'https://github.com/rmunate/Quick-Request-Laravel/tree/main/docs/:path' 16 | }, 17 | logo: '/img/quick-request.png', 18 | nav: [ 19 | { text: 'v1.2.0', link: '/' } 20 | ], 21 | sidebar: [ 22 | { 23 | items: [ 24 | { text: 'What is QuickRequest?', link: '/introduction/introduction' } 25 | ] 26 | }, 27 | { 28 | text: 'Getting Started', 29 | collapsed: true, 30 | items: [ 31 | { text: 'Installation', link: '/getting-started/install' }, 32 | { text: 'Versions', link: '/getting-started/versions' }, 33 | { text: 'Release Notes', link: '/getting-started/changelog' }, 34 | ] 35 | }, 36 | { 37 | text: 'Usage', 38 | collapsed: true, 39 | items: [ 40 | { text: 'General Structure', link: '/usage/general-structure' }, 41 | { text: 'Examples', link: '/usage/examples' }, 42 | { text: 'BLOB', link: '/usage/blobs' }, 43 | { text: 'RunTime Exceptions', link: '/usage/exceptions' }, 44 | { text: 'Read Errors', link: '/usage/laravel-errors' }, 45 | ] 46 | },{ 47 | text: 'Contribute', 48 | collapsed: true, 49 | items: [ 50 | {text: 'Bug Report', link: '/contribute/report-bugs'}, 51 | {text: 'Contribution', link: '/contribute/contribution'} 52 | ] 53 | } 54 | ], 55 | socialLinks: [ 56 | { icon: 'github', link: 'https://github.com/rmunate/Quick-Request-Laravel' } 57 | ], 58 | search: { 59 | provider: 'local' 60 | } 61 | }, 62 | head: [ 63 | ['link', { 64 | rel: 'stylesheet', 65 | href: '/Quick-Request-Laravel/css/style.css' 66 | } 67 | ], 68 | ['link', { 69 | rel: 'icon', 70 | href: '/Quick-Request-Laravel/img/quick-request.png', 71 | type: 'image/png' 72 | } 73 | ], 74 | ['meta', { 75 | property: 'og:image', 76 | content: '/Quick-Request-Laravel/img/logo-github.jpg' 77 | } 78 | ], 79 | ['meta', { 80 | property: 'og:image:secure_url', 81 | content: '/Quick-Request-Laravel/img/logo-github.jpg' 82 | } 83 | ], 84 | ['meta', { 85 | property: 'og:image:width', 86 | content: '600' 87 | } 88 | ], 89 | ['meta', { 90 | property: 'og:image:height', 91 | content: '400' 92 | } 93 | ], 94 | ['meta', { 95 | property: 'og:title', 96 | content: 'Quick-Request-Laravel' 97 | } 98 | ], 99 | ['meta', { 100 | property: 'og:description', 101 | content: 'Developing Laravel monoliths has never been easier and more efficient! 💻✨' 102 | } 103 | ], 104 | ['meta', { 105 | property: 'og:url', 106 | content: 'https://rmunate.github.io/Quick-Request-Laravel/' 107 | } 108 | ], 109 | ['meta', { 110 | property: 'og:type', 111 | content: 'website' 112 | } 113 | ] 114 | ], 115 | }) 116 | -------------------------------------------------------------------------------- /docs/contribute/contribution.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Contributing 3 | editLink: true 4 | outline: deep 5 | --- 6 | 7 | 56 | 57 | # Contributing 58 | 59 | ## How to Contribute to this Solution? 60 | 61 | If you see ways in which we can improve, change how we handle information, adjust pre-built blocks, or add new features, or even develop them yourself and add them. 62 | 63 | We are eager to receive your pull requests! 64 | 65 | Simply fork the repository on GitHub and create pull requests (PRs) to the main branch once you have prepared your changes. 66 | 67 | It is essential to provide a clear justification for the changes or new features, accompanied by a usage manual. 68 | 69 | ::: tip TEST 70 | Remember to test your code before submitting pull requests. 71 | ::: 72 | 73 | ## Developers and Contributors 74 | 75 | 76 | 77 | ## License 78 | This project is under the [MIT License](https://choosealicense.com/licenses/mit/). 79 | 80 | 🌟 Support My Projects! 🚀 81 | 82 | [![Become a Sponsor](https://img.shields.io/badge/-Become%20a%20Sponsor-blue?style=for-the-badge&logo=github)](https://github.com/sponsors/rmunate) 83 | 84 | Make any contributions you see fit; the code is entirely yours. Together, we can do amazing things and improve the world of development. Your support is invaluable. ✨ 85 | 86 | If you have ideas, suggestions, or just want to collaborate, we are open to everything! Join our community and be part of our journey to success! 🌐👩‍💻👨‍💻 -------------------------------------------------------------------------------- /docs/contribute/report-bugs.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Bug Report 3 | editLink: true 4 | outline: deep 5 | --- 6 | 7 | # Bug Report 8 | 9 | If you find errors or opportunities within the package, you can create an incident that we will attend to in the shortest time possible. 10 | 11 | Here!: 12 | [https://github.com/rmunate/Quick-Request-Laravel/issues/new](https://github.com/rmunate/Quick-Request-Laravel/issues/new) 13 | 14 | Remember that you can also contribute as a collaborator of this solution. 15 | -------------------------------------------------------------------------------- /docs/getting-started/changelog.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Release Notes 3 | editLink: true 4 | outline: deep 5 | --- 6 | 7 | ::: warning We strongly recommend migrating to the current version 8 | If you have applications using previous versions, we highly recommend migrating to the current version. Keep in mind that the current version does not support functionalities from earlier versions. Its source code has been completely rewritten. 9 | ::: 10 | 11 | # Release Notes 12 | 13 | ## [1.0.0] - 2023-01-01 14 | 15 | - The first open-source version is launched. 16 | 17 | ## [1.0.3] - 2024-05-21 18 | 19 | - The NPM version is created to allow package installation via NPM. 20 | - Package documentation is adjusted. 21 | - Error reading is standardized regardless of whether they are unhandled exceptions, form requests, or others. 22 | -------------------------------------------------------------------------------- /docs/getting-started/install.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Installation 3 | editLink: true 4 | outline: deep 5 | --- 6 | 7 | ## Installation 8 | 9 | In order to utilize this tool, you need to have the `csrf-token` meta tag in your main template, as per the official Laravel documentation on [X-CSRF-TOKEN](https://laravel.com/docs/11.x/csrf#csrf-x-csrf-token). This value will be independently read by the `QuickRequest` package, so you should NOT include the `@csrf` directives in your forms or create hidden inputs with this value, as **the library handles it for you**. 10 | 11 | ```html 12 | 13 | ``` 14 | 15 | ### CDN Usage 16 | 17 | If you're not using VITE in your project, you can install this solution simply by utilizing the CDN available for your use. 18 | 19 | It's as easy as adding the following line of code to the `` section of your main template. 20 | 21 | ```html 22 | 23 | 24 | 25 | 26 | 27 | ``` 28 | 29 | Alternatively, you could download the content from the aforementioned URL, place it in the `public` directory within a `js` folder, keeping the code locally in your project. However, this means you would maintain code that won't be updated with the adjustments or enhancements applied to the project. 30 | 31 | ```html 32 | 33 | 34 | 35 | 36 | 37 | ``` 38 | 39 | ### NPM Usage with VITE 40 | 41 | If you're developing your project with VITE, it will be much more convenient to install this solution using the following command. 42 | 43 | ```bash 44 | npm i quick-request-laravel 45 | ``` 46 | 47 | This way, the package will be readily available in your system; you just need to import it into your modules. 48 | 49 | ```javascript 50 | import { QuickRequest } from 'quick-request-laravel'; 51 | ``` 52 | -------------------------------------------------------------------------------- /docs/getting-started/versions.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Versions 3 | editLink: true 4 | outline: deep 5 | --- 6 | 7 | ## Versions 8 | 9 | This solution has streamlined the creation of various monolithic applications with the Laravel framework. From its development and implementation in the internal systems of the company where it originated, its potential for standardizing backend responses and simplifying developer writing has been evident. 10 | 11 | Currently, these are the versions of this package. 12 | 13 | | Version | Release Date | End of Support Date | 14 | |--------------------------------------------------------|--------------|---------------------| 15 | | ^0.1   | 2021-10-21 | 2022-12-31 | 16 | | ^1.0   | 2023-01-01 | 2024-12-31 | 17 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: home 3 | 4 | hero: 5 | name: Laravel 6 | text: Quick Request 7 | tagline: Easily consume your endpoints in Laravel. Using Ajax? Discover something even more exciting! 💻✨ 8 | image: 9 | src: /img/quick-request.png 10 | alt: Quick Request 11 | actions: 12 | - theme: brand 13 | text: Get Started 14 | link: /introduction/introduction 15 | - theme: alt 16 | text: View on GitHub 17 | link: https://github.com/rmunate/QuickRequest-Laravel 18 | 19 | features: 20 | - icon: 🚀 21 | title: Fast and Elegant Requests 22 | details: Leverage the Fetch API wrapped in this solution to make requests with clean, elegant syntax and high efficiency. 23 | - icon: 🌐 24 | title: Forget About the Base URL 25 | details: This package integrates seamlessly with Laravel's structure, automatically recognizing the base URL for your requests. Just focus on providing the correct URI. 26 | - icon: 📢 27 | title: Security? 28 | details: With Ajax requests, it's common to be able to re-consume the service from browser consoles. Here, you can cover these opportunities and more. 29 | --- -------------------------------------------------------------------------------- /docs/introduction/introduction.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Introduction 3 | editLink: true 4 | outline: deep 5 | --- 6 | 7 | ![logo-spell-number](/img/quick-request-banner.png) 8 | 9 | ## Introduction 10 | 11 | **QuickRequest** is an ultra-lightweight tool designed for swift and efficient requests to the Laravel backend. 12 | 13 | By leveraging the "fetch" mechanism, this solution streamlines the execution of requests to Laravel controllers, eliminating the need for manual assignment of tokens, base URLs, hidden inputs, and other method-specific parameters. 14 | 15 | QuickRequest provides a clean and elegant coding experience, making it a pleasant shift for developers accustomed to older technologies like Ajax. 16 | 17 | Tailored to meet specific requirements, QuickRequest offers essential features for easily managing various request types in a standard Laravel application, including GET, POST, PUT, PATCH, and DELETE. 18 | 19 | Additionally, it offers a convenient approach for efficient file downloads by effectively handling Blobs. 20 | 21 | Forget about writing extra lines of code—experience the minimalist style of QuickRequest. -------------------------------------------------------------------------------- /docs/public/css/style.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --vp-home-hero-name-color: #26344f; 3 | --vp-button-brand-border: #fcdd00; 4 | --vp-button-brand-bg: #f0dc50; 5 | --vp-button-brand-text: #26344f; 6 | --vp-button-brand-hover-bg: #ffe20e; 7 | --vp-button-brand-hover-text: #000; 8 | --vp-c-brand-1: #26344f; 9 | } 10 | 11 | .tagline{ 12 | font-size: 22px !important; 13 | } -------------------------------------------------------------------------------- /docs/public/img/laravel-red.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/public/img/laravel_black.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/public/img/logo-github.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rmunate/Quick-Request-Laravel/fe1a4093edbeb150eb95da577beb9c10e9923082/docs/public/img/logo-github.jpg -------------------------------------------------------------------------------- /docs/public/img/quick-request-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rmunate/Quick-Request-Laravel/fe1a4093edbeb150eb95da577beb9c10e9923082/docs/public/img/quick-request-banner.png -------------------------------------------------------------------------------- /docs/public/img/quick-request.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rmunate/Quick-Request-Laravel/fe1a4093edbeb150eb95da577beb9c10e9923082/docs/public/img/quick-request.jpeg -------------------------------------------------------------------------------- /docs/public/img/quick-request.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rmunate/Quick-Request-Laravel/fe1a4093edbeb150eb95da577beb9c10e9923082/docs/public/img/quick-request.png -------------------------------------------------------------------------------- /docs/usage/blobs.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Blob 3 | editLink: true 4 | outline: deep 5 | --- 6 | 7 | # Blob 8 | 9 | Blob stands for "Binary Large Object," and it is a data type used to store binary data such as images, audio files, or videos. 10 | 11 | Blobs can be easily manipulated with QuickRequest, whether for upload or download. 12 | 13 | ## Upload File 14 | 15 | If you want to upload files through QuickRequest, use the POST method. It's important to define the form in your HTML with `enctype="multipart/form-data"`. 16 | 17 | - Routes in `web.php`: 18 | 19 | ```php 20 | Route::post('/upload-image', [YourController::class, 'uploadImage']); 21 | ``` 22 | 23 | - HTML Form: 24 | To create a file-upload form, structure it as follows: 25 | 26 | ```html 27 |
28 | 29 | 30 | 31 |
32 | ``` 33 | 34 | - QuickRequest Syntax: 35 | In this case, always use `form` as the data source for QuickRequest: 36 | 37 | ```javascript 38 | QuickRequest().post({ 39 | url: '/upload-image', 40 | form: 'uploadFile', 41 | success: function(response, data, code){ 42 | console.log("Successful Process"); 43 | }, 44 | error: function(response, data, code){ 45 | console.error("Error: " + data.message); 46 | } 47 | }); 48 | ``` 49 | 50 | This way, all values from your form, including files, will reach the backend. Forget about creating FormData and other validations to upload a file. 51 | 52 | ## Download File 53 | 54 | If you want to download a file, you can easily do it using the GET method. 55 | 56 | - Routes in `web.php`: 57 | 58 | ```php 59 | Route::get('/image/{name}', [YourController::class, 'findImage']); 60 | ``` 61 | 62 | - Method in `YourController.php`: 63 | 64 | ```php 65 | public function findImage($name) 66 | { 67 | /** 68 | * Execute actions. 69 | * In this example, search for an image 70 | * in the public folder. 71 | */ 72 | $pathToImage = public_path($name . ".jpeg"); 73 | $imageContents = file_get_contents($pathToImage); 74 | 75 | /** 76 | * Return the image as a "Binary Large Object" (Blob). 77 | * Important to define ->header('Content-Type', ?) 78 | */ 79 | return response($imageContents, 200)->header('Content-Type', 'image/jpeg'); 80 | } 81 | ``` 82 | 83 | - QuickRequest Request: 84 | 85 | Now that you are receiving an image as a "Binary Large Object," let's easily download it with `QuickRequestBlobs`: 86 | 87 | ```javascript 88 | const nameImage = 'LaravelLogo'; 89 | 90 | QuickRequest().get({ 91 | url: '/image/' + nameImage, 92 | expect: 'blob', // Mandatory 93 | success: function (response, data, code) { 94 | /** 95 | * To download the file, simply 96 | * Use the QuickRequestBlobs object that facilitates 97 | * the action. 98 | */ 99 | QuickRequestBlobs.setBlob(data) // Always arrives at this position the blob 100 | .setName(nameImage) // Preferably without spaces. 101 | .setExtension("jpeg") // Lowercase extension without the dot. 102 | .download(); 103 | }, 104 | error: function (response, data, code) { 105 | console.error("Error: " + data.message); 106 | } 107 | }); 108 | ``` 109 | 110 | ::: tip Reminder 111 | In controllers, always use the `->header('Content-Type', '?')` method, where `?` will be the corresponding value according to the type of file you want to download. 112 | ::: 113 | 114 | Here are some common headers: 115 | 116 | | File Type | Content-Type Header | 117 | |------------------------------|-------------------------------------------------------------------------------------------| 118 | | JPEG Image | `Content-Type: image/jpeg` | 119 | | PNG Image | `Content-Type: image/png` | 120 | | GIF Image | `Content-Type: image/gif` | 121 | | BMP Image | `Content-Type: image/bmp` | 122 | | WebP Image | `Content-Type: image/webp` | 123 | | SVG Image | `Content-Type: image/svg+xml` | 124 | | PDF Document | `Content-Type: application/pdf` | 125 | | Excel Spreadsheet (XLSX) | `Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet` | 126 | | Word Document (DOCX) | `Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document` | 127 | | PowerPoint (PPTX) | `Content-Type: application/vnd.openxmlformats-officedocument.presentationml.presentation` | 128 | | CSV (Comma-Separated Values) | `Content-Type: text/csv` | 129 | | XML | `Content-Type: application/xml` | 130 | | ZIP Archive | `Content-Type: application/zip` | -------------------------------------------------------------------------------- /docs/usage/examples.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Examples 3 | editLink: true 4 | outline: deep 5 | --- 6 | 7 | # Usage Examples 8 | 9 | Next, we will explore some usage examples of QuickRequest to address any uncertainties that may arise regarding the previous page outlining the general structure of the solution. 10 | ## GET Method 11 | 12 | Used to retrieve information from the server, it should be safe and idempotent. Data is sent through the URL (query parameters) and is suitable for read-only operations. 13 | 14 | In simple terms, it is used to query data, download files, etc. 15 | 16 | ### Query Data 17 | 18 | - Routes in `web.php`: 19 | 20 | ```php 21 | Route::get('/record/{id}', [YourController::class, 'find']); 22 | ``` 23 | 24 | - Method in `YourController.php`: 25 | 26 | ```php 27 | public function find($id) 28 | { 29 | /** 30 | * Execute the corresponding actions. 31 | * In this example, a simple database query. 32 | */ 33 | $record = Record::find($id); 34 | 35 | /** 36 | * Return the data as JSON. 37 | * Preferably use response()->json($data, 200); 38 | */ 39 | return response()->json($record, 200); 40 | } 41 | ``` 42 | 43 | - `QuickRequest` Request 44 | 45 | Remember that you can add the `eventListener` capability to QuickRequest to trigger the request only when an action is executed: 46 | 47 | ```javascript 48 | /** 49 | * Considering that this value is retrieved from 50 | * somewhere in a JS variable. 51 | */ 52 | const idRecord = 10; 53 | 54 | /** 55 | * Use the route structure created in web.php. 56 | */ 57 | QuickRequest().get({ 58 | url: '/record/' + idRecord, 59 | success: function (response, data, code) { 60 | console.log("Successful Process, Data: ", data); 61 | }, 62 | error: function (response, data, code) { 63 | console.error(data); 64 | } 65 | }); 66 | ``` 67 | 68 | ### Download File 69 | 70 | - Routes in `web.php`: 71 | 72 | ```php 73 | Route::get('/image/{name}', [YourController::class, 'findImage']); 74 | ``` 75 | 76 | - Method in `YourController.php`: 77 | 78 | ```php 79 | public function findImage($name) 80 | { 81 | /** 82 | * Execute actions. 83 | * In this example, search for an image 84 | * in the public folder. 85 | */ 86 | $pathToImage = public_path($name . ".jpeg"); 87 | 88 | /** 89 | * Return the image as a "Binary Large Object" (Blob). 90 | * Important to define ->header('Content-Type', ?) 91 | */ 92 | return response(file_get_contents($pathToImage), 200)->header('Content-Type', 'image/jpeg'); 93 | } 94 | ``` 95 | 96 | - `QuickRequest` Request 97 | 98 | Now that you are receiving an image as a "Binary Large Object," let's easily download it with `QuickRequestBlobs`: 99 | 100 | ```javascript 101 | const nameImage = 'LaravelLogo'; 102 | 103 | QuickRequest().get({ 104 | url: '/image/' + nameImage, 105 | expect: 'blob', // Mandatory 106 | success: function (response, data, code) { 107 | /** 108 | * To download the file, simply 109 | * Use the QuickRequestBlobs object that facilitates 110 | * the action. 111 | */ 112 | QuickRequestBlobs.setBlob(data) // Always arrives at this position the blob 113 | .setName(nameImage) // Preferably without spaces. 114 | .setExtension("jpeg") // Lowercase extension without the dot. 115 | .download(); 116 | }, 117 | error: function (response, data, code) { 118 | console.error(data); 119 | } 120 | }); 121 | ``` 122 | 123 | ::: tip Reminder 124 | In controllers, always use the `->header('Content-Type', '?')` method, where `?` will be the corresponding value according to the type of file you want to download. 125 | ::: 126 | 127 | ### Send Data 128 | 129 | Although with the GET method, you can send various values to the backend, remember that it should only be used to query information. Under no circumstances should it be used to insert data into the database or perform any other type of action. 130 | 131 | - Routes in `web.php`: 132 | 133 | ```php 134 | Route::get('/record/{type}', [YourController::class, 'groups']); 135 | ``` 136 | 137 | - Method in `YourController.php`: 138 | 139 | ```php 140 | public function groups($type, Request $request) 141 | { 142 | /** 143 | * Receive $type value from the URL 144 | * and $request from the URL Query. 145 | */ 146 | $groups = Record::where('type', $type) 147 | ->where("owner", $request->owner) 148 | ->groupBy($request->tag); 149 | 150 | /** 151 | * Return the data as JSON. 152 | * Preferably use response()->json($data, 200); 153 | */ 154 | return response()->json($groups, 200); 155 | } 156 | ``` 157 | 158 | - `QuickRequest` Request 159 | 160 | Remember that you can add the `eventListener` capability to QuickRequest to trigger the request only when an action is executed: 161 | 162 | ```javascript 163 | /** 164 | * Considering that this value is retrieved from 165 | * somewhere in a JS variable. 166 | */ 167 | const type = 'new'; 168 | 169 | /** 170 | * Use the route structure created in web.php. 171 | */ 172 | QuickRequest().get({ 173 | url: '/record/' + type, 174 | data: function () { 175 | return { 176 | owner: document.getElementById('owner').value, 177 | tag: document.getElementById('tag').value, 178 | }; 179 | }, 180 | success: function (response, data, code) { 181 | console.log("Successful Process, Data: ", data); 182 | }, 183 | error: function (response, data, code) { 184 | console.error("Error: " + data.message); 185 | } 186 | }); 187 | ``` 188 | 189 | ## POST Method 190 | 191 | Commonly used to send data to the server to create a new resource. It should not be idempotent. Data is sent in the message body and is suitable for operations that can have side effects or create resources. 192 | 193 | ### Create Records 194 | 195 | - Routes in `web.php`: 196 | 197 | ```php 198 | Route::post('/record', [YourController::class, 'store']); 199 | ``` 200 | 201 | - Method in `YourController.php`: 202 | 203 | ```php 204 | public function store(Request $request) 205 | { 206 | DB::beginTransaction(); 207 | 208 | try { 209 | 210 | $record = new Record(); 211 | $record->name = $request->name; 212 | $record->owner = $request->owner; 213 | $record->tag = $request->tag; 214 | $record->save(); 215 | 216 | DB::commit(); 217 | 218 | return response()->json([ 219 | "Success" => "Record Created Successfully", 220 | ], 201); 221 | 222 | } catch (\Throwable $th) { 223 | 224 | DB::rollback(); 225 | 226 | return response()->json([ 227 | "message" => $th->getMessage(), 228 | // "errors" => [ 229 | // "First Error" => [ 230 | // "First Description Error", 231 | // "Second Description Error", 232 | // ] 233 | // ], 234 | ], $th->getCode()); 235 | } 236 | } 237 | ``` 238 | 239 | - `QuickRequest` Request 240 | 241 | Remember that you can add the `eventListener` capability to QuickRequest to trigger the request only when an action is executed: 242 | 243 | ```javascript 244 | /** 245 | * Depending on the case, you should use 246 | * "form" when the data comes from a form 247 | * "data" when specific data needs to be sent. 248 | * 249 | * In this example, we will use "form". 250 | */ 251 | QuickRequest().post({ 252 | url: '/record', 253 | form: 'id_form', 254 | success: function(response, data, code){ 255 | console.log("Successful Process"); 256 | }, 257 | error: function(response, data, code){ 258 | console.error("Error: " + data.message); 259 | } 260 | }); 261 | ``` 262 | 263 | ::: tip Reminder 264 | When using the `form` property, it expects the ID assigned to the form in HTML. In this case, there is no need to use the # sign since it only works with IDs and not with classes. 265 | ::: 266 | 267 | ### Upload Files 268 | 269 | If you want to upload files through QuickRequest, you should use the POST method. It's important to define the form in your HTML with `enctype="multipart/form-data"`. 270 | 271 | To create a file-upload form, structure it as follows: 272 | 273 | ```html 274 |
275 | 276 | 277 | 278 |
279 | ``` 280 | 281 | In this case, always use `form` as the data source for QuickRequest: 282 | 283 | ```javascript 284 | QuickRequest().post({ 285 | url: '/record', 286 | form: 'uploadFile', 287 | success: function(response, data, code){ 288 | console.log("Successful Process"); 289 | }, 290 | error: function(response, data, code){ 291 | console.error("Error: " + data.message); 292 | } 293 | }); 294 | ``` 295 | 296 | This way, all values from your form, including files, will reach the backend. Forget about creating FormData and other validations to upload a file. 297 | 298 | ## PUT Method 299 | 300 | Used to update an existing resource or create it if it does not exist. It should be idempotent and requires the entire representation of the resource. 301 | 302 | - Routes in `web.php`: 303 | 304 | ```php 305 | Route::put('/record/{id}', [YourController::class, 'replace']); 306 | ``` 307 | 308 | - Method in `YourController.php`: 309 | 310 | ```php 311 | public function replace(Request $request, $id) 312 | { 313 | DB::beginTransaction(); 314 | 315 | try { 316 | 317 | $record = Record::find($id); 318 | $record->update($request->all()); 319 | 320 | DB::commit(); 321 | 322 | return response()->json([ 323 | "Success" => "Record Updated", 324 | ], 200); 325 | 326 | } catch (\Throwable $th) { 327 | 328 | DB::rollback(); 329 | 330 | return response()->json([ 331 | "message" => $th->getMessage(), 332 | // "errors" => [ 333 | // "First Error" => [ 334 | // "First Description Error", 335 | // "Second Description Error", 336 | // ] 337 | // ], 338 | ], $th->getCode()); 339 | } 340 | } 341 | ``` 342 | 343 | - `QuickRequest` Request 344 | 345 | Remember that you can add the `eventListener` capability to QuickRequest to trigger the request only when an action is executed: 346 | 347 | ```javascript 348 | /** 349 | * Depending on the case, you should use 350 | * "form" when the data comes from a form 351 | * "data" when specific data needs to be sent. 352 | * 353 | * In this example, we will use "data". 354 | */ 355 | 356 | const idRecord = 10; 357 | 358 | QuickRequest().put({ 359 | url: '/record/' + idRecord, 360 | data: function () { 361 | return { 362 | name: document.getElementById('name').value, 363 | owner: document.getElementById('owner').value, 364 | tag: document.getElementById('tag').value, 365 | }; 366 | }, 367 | success: function(response, data, code){ 368 | console.log("Successful Process"); 369 | }, 370 | error: function(response, data, code){ 371 | console.error("Error: " + data.message); 372 | } 373 | }); 374 | ``` 375 | 376 | ## PATCH Method 377 | 378 | Used to apply partial modifications to an existing resource. It should be idempotent and allows sending only the changes that need to be applied. 379 | 380 | - Routes in `web.php`: 381 | 382 | ```php 383 | Route::patch('/record/{id}', [YourController::class, 'update']); 384 | ``` 385 | 386 | - Method in `YourController.php`: 387 | 388 | ```php 389 | public function update(Request $request, $id) 390 | { 391 | DB::beginTransaction(); 392 | 393 | try { 394 | 395 | Record::find($id)->update($request->only(['tag'])); 396 | 397 | DB::commit(); 398 | 399 | return response()->json([ 400 | "Success" => "Record Updated", 401 | ], 200); 402 | 403 | } catch (\Throwable $th) { 404 | 405 | DB::rollback(); 406 | 407 | return response()->json([ 408 | "message" => $th->getMessage(), 409 | // "errors" => [ 410 | // "First Error" => [ 411 | // "First Description Error", 412 | // "Second Description Error", 413 | // ] 414 | // ], 415 | ], $th->getCode()); 416 | } 417 | } 418 | ``` 419 | 420 | - `QuickRequest` Request 421 | 422 | Remember that you can add the `eventListener` capability to QuickRequest to trigger the request only when an action is executed: 423 | 424 | ```javascript 425 | /** 426 | * Depending on the case, you should use 427 | * "form" when the data comes from a form 428 | * "data" when specific data needs to be sent. 429 | * 430 | * In this example, we will use "data". 431 | */ 432 | 433 | const idRecord = 10; 434 | 435 | QuickRequest().patch({ 436 | url: '/record/' + idRecord, 437 | data: function () { 438 | return { 439 | tag: document.getElementById('tag').value, 440 | }; 441 | }, 442 | success: function(response, data, code){ 443 | console.log("Successful Process"); 444 | }, 445 | error: function(response, data, code){ 446 | console.error("Error: " + data.message); 447 | } 448 | }); 449 | ``` 450 | 451 | ## DELETE Method 452 | 453 | Used to request the deletion of a resource. It should be idempotent and indicates that the resource identified by the URI should be deleted. 454 | 455 | - Routes in `web.php`: 456 | 457 | ```php 458 | Route::delete('/record/{id}', [YourController::class, 'destroy']); 459 | ``` 460 | 461 | - Method in `YourController.php`: 462 | 463 | ```php 464 | public function destroy($id) 465 | { 466 | DB::beginTransaction(); 467 | 468 | try { 469 | 470 | Record::find($id)->delete(); 471 | 472 | DB::commit(); 473 | 474 | return response()->json([ 475 | "Success" => "Record Deleted", 476 | ], 200); 477 | 478 | } catch (\Throwable $th) { 479 | 480 | DB::rollback(); 481 | 482 | return response()->json([ 483 | "message" => $th->getMessage(), 484 | // "errors" => [ 485 | // "First Error" => [ 486 | // "First Description Error", 487 | // "Second Description Error", 488 | // ] 489 | // ], 490 | ], $th->getCode()); 491 | } 492 | } 493 | ``` 494 | 495 | - `QuickRequest` Request 496 | 497 | Remember that you can add the `eventListener` capability to QuickRequest to trigger the request only when an action is executed: 498 | 499 | ```javascript 500 | /** 501 | * Depending on the case, you should use 502 | * "form" when the data comes from a form 503 | * "data" when specific data needs to be sent. 504 | * 505 | * In this example, we will use "data". 506 | */ 507 | 508 | const idRecord = 10; 509 | 510 | QuickRequest().delete({ 511 | url: '/record/' + idRecord, 512 | success: function(response, data, code){ 513 | console.log("Successful Process"); 514 | }, 515 | error: function(response, data, code){ 516 | console.error("Error: " + data.message); 517 | } 518 | }); 519 | ``` -------------------------------------------------------------------------------- /docs/usage/exceptions.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Exceptions 3 | editLink: true 4 | outline: deep 5 | --- 6 | 7 | # Exceptions: 8 | 9 | In case there is an error in the structure or processing of the data with QuickRequest, you will see an exception in the browser console like the following: 10 | 11 | ```shell 12 | QuickRequest.js:27 ⚠ QuickRequestException ⚠ | Error: Could not find any form with the id AnyForm 13 | { 14 | "name": "Error", 15 | "message": "Could not find any form with the id AnyForm", 16 | "trace": [ 17 | "Error: Could not find any form with the id AnyForm", 18 | "🔍 At: new QuickRequestException (http://laravel.com/QuickRequest.js:24:23)", 19 | "🔍 At: QuickRequestFetch.data (http://laravel.com/QuickRequest.js:489:23)", 20 | "🔍 At: QuickRequestFetch.send (http://laravel.com/QuickRequest.js:659:14)", 21 | "🔍 At: QuickRequestFetch.dispatch (http://laravel.com/QuickRequest.js:649:18)", 22 | "🔍 At: new QuickRequestFetch (http://laravel.com/QuickRequest.js:466:14)", 23 | "🔍 At: HTMLFormElement.funcEvent (http://laravel.com/QuickRequest.js:421:21)" 24 | ] 25 | } 26 | ``` 27 | 28 | It will be very easy to identify the error that occurred, as well as view the error trace. -------------------------------------------------------------------------------- /docs/usage/general-structure.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: General Structure 3 | editLink: true 4 | outline: deep 5 | --- 6 | 7 | # General Structure of QuickRequest 8 | 9 | QuickRequest features a clean and easy-to-implement syntax designed to simplify executing requests to the Laravel backend without complications, excessive configurations, or an abundance of code lines. 10 | 11 | Let's explore the simple structure of QuickRequest: 12 | 13 | ```javascript 14 | QuickRequest().get({ 15 | url: '/slug/1', 16 | expect: 'JSON', 17 | before: function(){ 18 | //... 19 | }, 20 | success: function(response, data, code){ 21 | //... 22 | }, 23 | error: function(response, data, code){ 24 | //... 25 | }, 26 | after: function(response, data, code){ 27 | //... 28 | }, 29 | }); 30 | ``` 31 | 32 | Now, let's provide a more detailed explanation of the different possible configurations in QuickRequest(). 33 | 34 | ## Methods 35 | 36 | To define the method to use, simply use the corresponding method name as the opening method of the request configuration data: 37 | 38 | ```javascript 39 | QuickRequest().get({...}); 40 | ``` 41 | 42 | ```javascript 43 | QuickRequest().post({...}); 44 | ``` 45 | 46 | ```javascript 47 | QuickRequest().patch({...}); 48 | ``` 49 | 50 | ```javascript 51 | QuickRequest().put({...}); 52 | ``` 53 | 54 | ```javascript 55 | QuickRequest().delete({...}); 56 | ``` 57 | 58 | ## Expect 59 | 60 | With QuickRequest, you can easily handle three types of expectations commonly used in Laravel applications. 61 | 62 | - **JSON** (Default): In this case, the backend (controller) is expected to return a valid JSON. 63 | 64 | ```javascript 65 | QuickRequest().get({ 66 | //... 67 | expect: "JSON" 68 | //... 69 | }); 70 | ``` 71 | 72 | In controllers, you should always use a return statement like the following: 73 | 74 | ```php 75 | return response()->json($data, 200); 76 | ``` 77 | 78 | - **TEXT**: There may be moments when you expect to return plain text such as HTML or something similar required in your JS. In that case, configure QuickRequest as follows. 79 | 80 | ```javascript 81 | QuickRequest().get({ 82 | //... 83 | expect: "TEXT" 84 | //... 85 | }); 86 | ``` 87 | 88 | In controllers, you should use a return statement like the following: 89 | 90 | ```php 91 | return response('Content'); 92 | 93 | //Or 94 | 95 | return response('Content', 200) 96 | ->header('Content-Type', 'text/html'); 97 | ``` 98 | 99 | Some other headers where TEXT should be expected. 100 | 101 | | File Type | Content-Type Header | 102 | |----------------- |---------------------------------| 103 | | Plain Text | `Content-Type: text/plain` | 104 | | HTML | `Content-Type: text/html` | 105 | | CSS | `Content-Type: text/css` | 106 | | JavaScript | `Content-Type: text/javascript` | 107 | 108 | - **BLOB**: It's common in Laravel applications to download files, images, Excel files, etc. For these scenarios, QuickRequest allows us to expect binaries and additionally provides an easy way to download files. 109 | 110 | ```javascript 111 | QuickRequest().get({ 112 | //... 113 | expect: "BLOB" 114 | success: function(response, data, code){ 115 | 116 | // Download File. 117 | QuickRequestBlobs.setBlob(data) 118 | .setName("Photo") 119 | .setExtension("jpeg") 120 | .download(); 121 | }, 122 | //... 123 | }); 124 | ``` 125 | 126 | In controllers, you should use a return statement where you specify the headers corresponding to the type of file being returned: 127 | 128 | ```php 129 | $pathToImage = public_path('image.jpeg'); 130 | 131 | return response(file_get_contents($pathToImage), 200)->header('Content-Type', 'image/jpeg'); 132 | ``` 133 | 134 | Here are some common headers: 135 | 136 | | File Type | Content-Type Header | 137 | |------------------------------|-------------------------------------------------------------------------------------------| 138 | | JPEG Image | `Content-Type: image/jpeg` | 139 | | PNG Image | `Content-Type: image/png` | 140 | | GIF Image | `Content-Type: image/gif` | 141 | | BMP Image | `Content-Type: image/bmp` | 142 | | WebP Image | `Content-Type: image/webp` | 143 | | SVG Image | `Content-Type: image/svg+xml` | 144 | | PDF Document | `Content-Type: application/pdf` | 145 | | Excel Spreadsheet (XLSX) | `Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet` | 146 | | Word Document (DOCX) | `Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document` | 147 | | PowerPoint (PPTX) | `Content-Type: application/vnd.openxmlformats-officedocument.presentationml.presentation` | 148 | | CSV (Comma-Separated Values) | `Content-Type: text/csv` | 149 | | XML | `Content-Type: application/xml` | 150 | | ZIP Archive | `Content-Type: application/zip` | 151 | 152 | ## URL 153 | 154 | QuickRequest expects the URL value to be what comes after the domain, meaning: 155 | 156 | ```javascript 157 | /** 158 | * When working with Ajax or Axios, you need to 159 | * pass the entire URL like this: 160 | */ 161 | const url = "https://sub.domain/custom-slug/10" 162 | 163 | /** 164 | * When working with QuickRequest, you only need to include 165 | * what comes after the domain 166 | * In this case, what comes after: 167 | * https://sub.domain/ 168 | */ 169 | const url = "/custom-slug/10" 170 | ``` 171 | 172 | In other words, the actual usage would be: 173 | 174 | ```javascript 175 | QuickRequest().get({ 176 | url: '/custom-slug/10', 177 | //... 178 | }); 179 | ``` 180 | 181 | ## Payload 182 | 183 | How to send values through QuickRequest(). 184 | 185 | You have two possible ways to do this, depending on whether you want to send form data or if you want to send an object with specific data to the backend. 186 | 187 | **Send a Form:** 188 | 189 | To send a form to the backend, you can use the following structure, where the value you put in the `form` property is the id of your form: 190 | 191 | Your form must have the `id` assigned: 192 | 193 | ```html 194 |
195 | 196 |
197 | ``` 198 | 199 | This `ID` should be the value of the `form` property: 200 | 201 | ```javascript 202 | QuickRequest().post({ 203 | //... 204 | form: 'id_form' 205 | //... 206 | }); 207 | ``` 208 | 209 | **Send Specific Data:** 210 | 211 | If you don't want to send form data but instead want to send specific data, then it is more convenient to use the `data` property, where you can simply relate the data you expect to receive in your controller through an object. 212 | 213 | ```javascript 214 | 215 | // Estatic Data (This is to take values that do not change in the context of use.) 216 | QuickRequest().post({ 217 | //... 218 | data: { 219 | key1: 'value1', 220 | key2: 'value2', 221 | } 222 | //... 223 | }); 224 | 225 | // Dinamic Data (This is used to obtain the value of the selector at the time QuickRequest is executed) 226 | QuickRequest().post({ 227 | //... 228 | data: function () { 229 | return { 230 | key1: document.getElementById('value1').value, 231 | key2: document.getElementById('value1').value, 232 | }; 233 | }, 234 | //... 235 | }); 236 | 237 | // Generate Previous Data (Values generated within the confirmation block "use later") 238 | QuickRequest().post({ 239 | //... 240 | data: function (me) { 241 | return { 242 | key1: me.value1, 243 | key2: me.value2 244 | }; 245 | }, 246 | //... 247 | }); 248 | ``` 249 | 250 | **Can I Send a Form and Additional Data?** 251 | 252 | Yes, if your case is that you need to add some values to the form data, then you can use both properties at the same time: 253 | 254 | ```javascript 255 | QuickRequest().post({ 256 | //... 257 | form: 'id_form', 258 | data: function () { 259 | return { 260 | key1: document.getElementById('value1').value, 261 | key2: document.getElementById('value1').value, 262 | }; 263 | }, 264 | //... 265 | }); 266 | ``` 267 | 268 | Ensure that values are not overwritten when using the same name for an input within the `data` property. 269 | 270 | ## Headers 271 | 272 | If you need to define specific headers in the request, you can easily do so using the `Headers` property. QuickRequest typically assigns headers automatically, but if you find it necessary, you can apply them manually. 273 | 274 | ```javascript 275 | QuickRequest().post({ 276 | //... 277 | headers: { 278 | 'Content-Type': 'application/json' 279 | } 280 | //... 281 | }); 282 | ``` 283 | 284 | This allows you to customize headers as needed for your specific requests. 285 | 286 | ## Before the Request 287 | 288 | If you need to perform any actions before sending the request to the backend, such as activating a spinner, displaying a preload on the web, showing notifications, or disabling buttons, you can easily achieve this with the `before` function: 289 | 290 | ```javascript 291 | QuickRequest().post({ 292 | //... 293 | before: function(){ 294 | const buttonSubmit = document.getElementById('btn-submit'); 295 | buttonSubmit.disabled = true; 296 | }, 297 | //... 298 | }); 299 | ``` 300 | This method is not mandatory. 301 | You can execute any type of action prior to sending the request. 302 | 303 | This method can also optionally receive the parameter me: `before: function(me){...` 304 | 305 | ## Successful Response 306 | 307 | If the response is successful, you can retrieve the values from the response using the `success` function. 308 | 309 | ```javascript 310 | QuickRequest().post({ 311 | //... 312 | success: function(response, data, code){ 313 | 314 | console.log(code) 315 | /* 200 - 201 ... */ 316 | 317 | console.log(data) 318 | /* {key: value} */ 319 | 320 | console.log(response) 321 | /* 322 | { 323 | data: {key: value}, 324 | success: true, 325 | status: 200, 326 | statusText: "OK", 327 | headers: {...}, 328 | url: 'http://...' 329 | } 330 | */ 331 | }, 332 | //... 333 | }); 334 | ``` 335 | This method is mandatory. 336 | Within this block, you can perform all the actions that correspond to a successful response. 337 | 338 | ## Error Response 339 | 340 | If an error occurs or the response is returned with an HTTP error code, then the response will be received through the `error` function. Here, you can access the list of errors generated and returned by the Laravel backend. 341 | 342 | It's important to note that regardless of how errors are returned from the backend, you will always receive them in the same format. This ensures standardized error handling in JavaScript and addresses the lack of a consistent error return standard from the framework. 343 | 344 | ```javascript 345 | QuickRequest().post({ 346 | //... 347 | error: function(response, data, code){ 348 | 349 | console.log(code) 350 | /* 400 - 500 ... */ 351 | 352 | console.log(data) 353 | /* 354 | { 355 | "message" : "Main error message." 356 | "errors" : { 357 | "First Error" : [ 358 | "First Description Error", 359 | "Second Description Error", 360 | ] 361 | } 362 | } 363 | */ 364 | 365 | console.log(response) 366 | /* 367 | { 368 | data: { 369 | "message" : "Main error message." 370 | "errors" : { 371 | "First Error" : [ 372 | "First Description Error", 373 | "Second Description Error", 374 | ] 375 | } 376 | }, 377 | success: false, 378 | status: 400, 379 | statusText: "Bad Request", 380 | headers: {...}, 381 | url: 'http://...' 382 | } 383 | */ 384 | }, 385 | //... 386 | }); 387 | ``` 388 | 389 | It's important but not mandatory that when returning errors from the Laravel backend, you always use the following indexed array format: 390 | 391 | ```php 392 | try { 393 | 394 | //... 395 | 396 | } catch (\Throwable $th) { 397 | 398 | return response()->json([ 399 | "message" => $th->getMessage(), 400 | // "errors" => [ 401 | // "First Error" => [ 402 | // "First Description Error", 403 | // "Second Description Error", 404 | // ] 405 | // ], 406 | ], $th->getCode()); 407 | 408 | } 409 | ``` 410 | 411 | ## After the Request 412 | 413 | Just as you have the ability to execute any action before sending the request to the backend, you can also perform any action after the request has been completed. In this function, you will have access to the response, whether it's successful or contains errors from the backend. This can be useful if you need these details to condition your actions. 414 | 415 | ```javascript 416 | QuickRequest().post({ 417 | //... 418 | after: function(response, data, code){ 419 | 420 | // - Here you will have access to the success or error data as the case may be. 421 | 422 | console.log(code) 423 | /* 200 - 201 ... */ 424 | 425 | console.log(data) 426 | /* {key: value} */ 427 | 428 | console.log(response) 429 | /* 430 | { 431 | data: {key: value}, 432 | success: true, 433 | status: 200, 434 | statusText: "OK", 435 | headers: {...}, 436 | url: 'http://...' 437 | } 438 | */ 439 | }, 440 | //... 441 | }); 442 | ``` 443 | 444 | This method is not mandatory; if you don't need it, you can simply omit it. 445 | 446 | ## Using an Event 447 | 448 | One interesting feature of QuickRequest is the ability to trigger a backend request based on the execution of an event. 449 | 450 | Below, you will find two possible ways to execute a backend request only when a specific event occurs. 451 | 452 | **Conventional Approach** 453 | 454 | You can trigger an event in the conventional way provided by JavaScript and simply specify that QuickRequest makes the request only if the event is fulfilled. 455 | 456 | ```javascript 457 | document.getElementById('id_form').addEventListener('submit', function(event) { 458 | 459 | event.preventDefault(); 460 | 461 | QuickRequest().get({ 462 | url: '/slug/1', 463 | expect: 'JSON', 464 | success: function(response, data, code){ 465 | //... 466 | }, 467 | error: function(response, data, code){ 468 | //... 469 | }, 470 | }); 471 | 472 | }); 473 | ``` 474 | 475 | **Simplified with QuickRequest** 476 | 477 | The previous approach can be easily streamlined using QuickRequest's own methods. Here is the same scenario but more compact and in the QuickRequest style: 478 | 479 | ```javascript 480 | QuickRequest().eventListener('submit','#id_form').preventDefault().get({ 481 | url: '/slug/1', 482 | expect: 'JSON', 483 | success: function(response, data, code){ 484 | //... 485 | }, 486 | error: function(response, data, code){ 487 | //... 488 | }, 489 | }); 490 | ``` 491 | 492 | You should use the `preventDefault()` method only in cases where you need to prevent the default behavior of the event. It is common to use it in `submit` events to interrupt the default browser-triggered event and execute the JavaScript action. 493 | 494 | ## Disabling Events 495 | 496 | How could I deactivate the events triggered by QuickRequest when needed? 497 | 498 | Well, for this purpose, it's as simple as storing the action within a variable and then using a QuickRequest method to disable the associated events. 499 | 500 | Here's how: 501 | 502 | ```javascript 503 | // Activate the event 504 | const submitForm = QuickRequest().eventListener('submit','#id_form').preventDefault().get({ 505 | url: '/slug/1', 506 | expect: 'JSON', 507 | success: function(response, data, code){ 508 | //... 509 | }, 510 | error: function(response, data, code){ 511 | //... 512 | }, 513 | }); 514 | 515 | // List events if needed 516 | submitForm.getEvents(); 517 | 518 | // Deactivate the event 519 | submitForm.removeEventListener(); 520 | ``` 521 | 522 | ## Requesting Confirmation 523 | 524 | If you, for example, want to display a confirmation window before sending the request to the backend, QuickRequest can provide this solution. Through the `confirm` method, you can create the logic you need to ask the user for confirmation of the action to be performed. If this method returns `true`, the action will be executed; otherwise, no action will be taken. 525 | 526 | ```javascript 527 | QuickRequest().eventListener('submit','#id_form').preventDefault().confirm(function(){ 528 | 529 | /** 530 | * You could, for example, use SweetAlert2 to request 531 | * confirmation or something similar. 532 | * 533 | * True execute the event, False stops it 534 | */ 535 | return true; 536 | 537 | }).get({ 538 | url: '/slug/1', 539 | expect: 'JSON', 540 | success: function(response, data, code){ 541 | //... 542 | }, 543 | error: function(response, data, code){ 544 | //... 545 | }, 546 | }); 547 | ``` 548 | 549 | There may be cases where you need to use values generated in this function, so you can use `me` 550 | 551 | ```javascript 552 | QuickRequest().eventListener('submit','#id_form').preventDefault().confirm(function(me){ 553 | 554 | /** 555 | * You could, for example, use SweetAlert2 to request 556 | * confirmation or something similar. 557 | * 558 | * True execute the event, False stops it 559 | */ 560 | me.name = 'example' 561 | 562 | return true; 563 | 564 | }).get({ 565 | url: '/slug/1', 566 | expect: 'JSON', 567 | data: function(me){ 568 | return { 569 | "name" : me.name 570 | } 571 | }, 572 | befere: function(me){ 573 | alert(me.name) 574 | }, 575 | success: function(response, data, code){ 576 | //... 577 | }, 578 | error: function(response, data, code){ 579 | //... 580 | }, 581 | }); 582 | ``` 583 | 584 | 585 | ## Other Options 586 | 587 | If needed, you also have the following options available: 588 | 589 | ```javascript 590 | QuickRequest().post({ 591 | //... 592 | mode: 'cors', // no-cors, *cors, same-origin 593 | cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached 594 | credentials: 'same-origin', // include, *same-origin, omit 595 | redirect: 'follow', // manual, *follow, error 596 | referrerPolicy: 'no-referrer', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url 597 | //... 598 | }); 599 | ``` 600 | 601 | These are not commonly used for typical requests in Laravel monoliths. However, if you require any specific actions, you can apply them as needed. -------------------------------------------------------------------------------- /docs/usage/laravel-errors.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Laravel Errors 3 | editLink: true 4 | outline: deep 5 | --- 6 | 7 | # Errors 8 | 9 | Now let's see how we can handle errors that occur during the processing of our request by the Laravel backend. Whether it's due to FormRequest, Validator, Exceptions, or any other source. 10 | 11 | It is essential to receive a standardized response for the errors that occur because it is not common to create customizations for each request to the backend for possible generated errors. 12 | 13 | For this, QuickRequest always returns the same error structure, regardless of whether errors are thrown in our code, caught in catch blocks, or thrown as exceptions in the framework. 14 | 15 | Below is the structure that the value passed to the `error` function looks like: 16 | 17 | ```javascript 18 | QuickRequest().get({ 19 | //... 20 | error: function(response, data, code){ 21 | console.log(data); 22 | }, 23 | //... 24 | }); 25 | ``` 26 | 27 | Browser Console Output: 28 | 29 | ```javascript 30 | { 31 | "message" : "Main error message." 32 | "errors" : { 33 | "First Error" : [ 34 | "First Description Error", 35 | "Second Description Error", 36 | ] 37 | } 38 | } 39 | ``` 40 | 41 | Where, 42 | - `code`: contains the error code such as `419`, `500`, etc. 43 | - `data`: contains the errors generated in the backend by a FormRequest, TryCatch block, or unhandled exceptions. 44 | - `response`: contains the entire response including Headers and more. 45 | 46 | The `data` property will always contain two properties: 47 | - `message`: This property contains the main error. 48 | - `errors`: contains an object where each property has an array of different errors related to the same issue. 49 | 50 | An important thing is how you should return errors from controllers to make reading from the front end more convenient: 51 | 52 | ```php 53 | try { 54 | 55 | //... 56 | 57 | } catch (\Throwable $th) { 58 | 59 | return response()->json([ 60 | "message" => $th->getMessage(), 61 | // "errors" => [ 62 | // "First Error" => [ 63 | // "First Description Error", 64 | // "Second Description Error", 65 | // ] 66 | // ], 67 | ], $th->getCode()); 68 | 69 | } 70 | ``` 71 | 72 | Simply, if you notice, it's an indexed array. 73 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "quick-request-laravel", 3 | "version": "1.1.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "quick-request-laravel", 9 | "version": "1.0.0", 10 | "license": "MIT", 11 | "devDependencies": { 12 | "vitepress": "^1.2.0" 13 | } 14 | }, 15 | "node_modules/@algolia/autocomplete-core": { 16 | "version": "1.9.3", 17 | "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.9.3.tgz", 18 | "integrity": "sha512-009HdfugtGCdC4JdXUbVJClA0q0zh24yyePn+KUGk3rP7j8FEe/m5Yo/z65gn6nP/cM39PxpzqKrL7A6fP6PPw==", 19 | "dev": true, 20 | "dependencies": { 21 | "@algolia/autocomplete-plugin-algolia-insights": "1.9.3", 22 | "@algolia/autocomplete-shared": "1.9.3" 23 | } 24 | }, 25 | "node_modules/@algolia/autocomplete-plugin-algolia-insights": { 26 | "version": "1.9.3", 27 | "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.9.3.tgz", 28 | "integrity": "sha512-a/yTUkcO/Vyy+JffmAnTWbr4/90cLzw+CC3bRbhnULr/EM0fGNvM13oQQ14f2moLMcVDyAx/leczLlAOovhSZg==", 29 | "dev": true, 30 | "dependencies": { 31 | "@algolia/autocomplete-shared": "1.9.3" 32 | }, 33 | "peerDependencies": { 34 | "search-insights": ">= 1 < 3" 35 | } 36 | }, 37 | "node_modules/@algolia/autocomplete-preset-algolia": { 38 | "version": "1.9.3", 39 | "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.9.3.tgz", 40 | "integrity": "sha512-d4qlt6YmrLMYy95n5TB52wtNDr6EgAIPH81dvvvW8UmuWRgxEtY0NJiPwl/h95JtG2vmRM804M0DSwMCNZlzRA==", 41 | "dev": true, 42 | "dependencies": { 43 | "@algolia/autocomplete-shared": "1.9.3" 44 | }, 45 | "peerDependencies": { 46 | "@algolia/client-search": ">= 4.9.1 < 6", 47 | "algoliasearch": ">= 4.9.1 < 6" 48 | } 49 | }, 50 | "node_modules/@algolia/autocomplete-shared": { 51 | "version": "1.9.3", 52 | "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.9.3.tgz", 53 | "integrity": "sha512-Wnm9E4Ye6Rl6sTTqjoymD+l8DjSTHsHboVRYrKgEt8Q7UHm9nYbqhN/i0fhUYA3OAEH7WA8x3jfpnmJm3rKvaQ==", 54 | "dev": true, 55 | "peerDependencies": { 56 | "@algolia/client-search": ">= 4.9.1 < 6", 57 | "algoliasearch": ">= 4.9.1 < 6" 58 | } 59 | }, 60 | "node_modules/@algolia/cache-browser-local-storage": { 61 | "version": "4.23.3", 62 | "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.23.3.tgz", 63 | "integrity": "sha512-vRHXYCpPlTDE7i6UOy2xE03zHF2C8MEFjPN2v7fRbqVpcOvAUQK81x3Kc21xyb5aSIpYCjWCZbYZuz8Glyzyyg==", 64 | "dev": true, 65 | "dependencies": { 66 | "@algolia/cache-common": "4.23.3" 67 | } 68 | }, 69 | "node_modules/@algolia/cache-common": { 70 | "version": "4.23.3", 71 | "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.23.3.tgz", 72 | "integrity": "sha512-h9XcNI6lxYStaw32pHpB1TMm0RuxphF+Ik4o7tcQiodEdpKK+wKufY6QXtba7t3k8eseirEMVB83uFFF3Nu54A==", 73 | "dev": true 74 | }, 75 | "node_modules/@algolia/cache-in-memory": { 76 | "version": "4.23.3", 77 | "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.23.3.tgz", 78 | "integrity": "sha512-yvpbuUXg/+0rbcagxNT7un0eo3czx2Uf0y4eiR4z4SD7SiptwYTpbuS0IHxcLHG3lq22ukx1T6Kjtk/rT+mqNg==", 79 | "dev": true, 80 | "dependencies": { 81 | "@algolia/cache-common": "4.23.3" 82 | } 83 | }, 84 | "node_modules/@algolia/client-account": { 85 | "version": "4.23.3", 86 | "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.23.3.tgz", 87 | "integrity": "sha512-hpa6S5d7iQmretHHF40QGq6hz0anWEHGlULcTIT9tbUssWUriN9AUXIFQ8Ei4w9azD0hc1rUok9/DeQQobhQMA==", 88 | "dev": true, 89 | "dependencies": { 90 | "@algolia/client-common": "4.23.3", 91 | "@algolia/client-search": "4.23.3", 92 | "@algolia/transporter": "4.23.3" 93 | } 94 | }, 95 | "node_modules/@algolia/client-analytics": { 96 | "version": "4.23.3", 97 | "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.23.3.tgz", 98 | "integrity": "sha512-LBsEARGS9cj8VkTAVEZphjxTjMVCci+zIIiRhpFun9jGDUlS1XmhCW7CTrnaWeIuCQS/2iPyRqSy1nXPjcBLRA==", 99 | "dev": true, 100 | "dependencies": { 101 | "@algolia/client-common": "4.23.3", 102 | "@algolia/client-search": "4.23.3", 103 | "@algolia/requester-common": "4.23.3", 104 | "@algolia/transporter": "4.23.3" 105 | } 106 | }, 107 | "node_modules/@algolia/client-common": { 108 | "version": "4.23.3", 109 | "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.23.3.tgz", 110 | "integrity": "sha512-l6EiPxdAlg8CYhroqS5ybfIczsGUIAC47slLPOMDeKSVXYG1n0qGiz4RjAHLw2aD0xzh2EXZ7aRguPfz7UKDKw==", 111 | "dev": true, 112 | "dependencies": { 113 | "@algolia/requester-common": "4.23.3", 114 | "@algolia/transporter": "4.23.3" 115 | } 116 | }, 117 | "node_modules/@algolia/client-personalization": { 118 | "version": "4.23.3", 119 | "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.23.3.tgz", 120 | "integrity": "sha512-3E3yF3Ocr1tB/xOZiuC3doHQBQ2zu2MPTYZ0d4lpfWads2WTKG7ZzmGnsHmm63RflvDeLK/UVx7j2b3QuwKQ2g==", 121 | "dev": true, 122 | "dependencies": { 123 | "@algolia/client-common": "4.23.3", 124 | "@algolia/requester-common": "4.23.3", 125 | "@algolia/transporter": "4.23.3" 126 | } 127 | }, 128 | "node_modules/@algolia/client-search": { 129 | "version": "4.23.3", 130 | "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.23.3.tgz", 131 | "integrity": "sha512-P4VAKFHqU0wx9O+q29Q8YVuaowaZ5EM77rxfmGnkHUJggh28useXQdopokgwMeYw2XUht49WX5RcTQ40rZIabw==", 132 | "dev": true, 133 | "dependencies": { 134 | "@algolia/client-common": "4.23.3", 135 | "@algolia/requester-common": "4.23.3", 136 | "@algolia/transporter": "4.23.3" 137 | } 138 | }, 139 | "node_modules/@algolia/logger-common": { 140 | "version": "4.23.3", 141 | "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.23.3.tgz", 142 | "integrity": "sha512-y9kBtmJwiZ9ZZ+1Ek66P0M68mHQzKRxkW5kAAXYN/rdzgDN0d2COsViEFufxJ0pb45K4FRcfC7+33YB4BLrZ+g==", 143 | "dev": true 144 | }, 145 | "node_modules/@algolia/logger-console": { 146 | "version": "4.23.3", 147 | "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.23.3.tgz", 148 | "integrity": "sha512-8xoiseoWDKuCVnWP8jHthgaeobDLolh00KJAdMe9XPrWPuf1by732jSpgy2BlsLTaT9m32pHI8CRfrOqQzHv3A==", 149 | "dev": true, 150 | "dependencies": { 151 | "@algolia/logger-common": "4.23.3" 152 | } 153 | }, 154 | "node_modules/@algolia/recommend": { 155 | "version": "4.23.3", 156 | "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-4.23.3.tgz", 157 | "integrity": "sha512-9fK4nXZF0bFkdcLBRDexsnGzVmu4TSYZqxdpgBW2tEyfuSSY54D4qSRkLmNkrrz4YFvdh2GM1gA8vSsnZPR73w==", 158 | "dev": true, 159 | "dependencies": { 160 | "@algolia/cache-browser-local-storage": "4.23.3", 161 | "@algolia/cache-common": "4.23.3", 162 | "@algolia/cache-in-memory": "4.23.3", 163 | "@algolia/client-common": "4.23.3", 164 | "@algolia/client-search": "4.23.3", 165 | "@algolia/logger-common": "4.23.3", 166 | "@algolia/logger-console": "4.23.3", 167 | "@algolia/requester-browser-xhr": "4.23.3", 168 | "@algolia/requester-common": "4.23.3", 169 | "@algolia/requester-node-http": "4.23.3", 170 | "@algolia/transporter": "4.23.3" 171 | } 172 | }, 173 | "node_modules/@algolia/requester-browser-xhr": { 174 | "version": "4.23.3", 175 | "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.23.3.tgz", 176 | "integrity": "sha512-jDWGIQ96BhXbmONAQsasIpTYWslyjkiGu0Quydjlowe+ciqySpiDUrJHERIRfELE5+wFc7hc1Q5hqjGoV7yghw==", 177 | "dev": true, 178 | "dependencies": { 179 | "@algolia/requester-common": "4.23.3" 180 | } 181 | }, 182 | "node_modules/@algolia/requester-common": { 183 | "version": "4.23.3", 184 | "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.23.3.tgz", 185 | "integrity": "sha512-xloIdr/bedtYEGcXCiF2muajyvRhwop4cMZo+K2qzNht0CMzlRkm8YsDdj5IaBhshqfgmBb3rTg4sL4/PpvLYw==", 186 | "dev": true 187 | }, 188 | "node_modules/@algolia/requester-node-http": { 189 | "version": "4.23.3", 190 | "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.23.3.tgz", 191 | "integrity": "sha512-zgu++8Uj03IWDEJM3fuNl34s746JnZOWn1Uz5taV1dFyJhVM/kTNw9Ik7YJWiUNHJQXcaD8IXD1eCb0nq/aByA==", 192 | "dev": true, 193 | "dependencies": { 194 | "@algolia/requester-common": "4.23.3" 195 | } 196 | }, 197 | "node_modules/@algolia/transporter": { 198 | "version": "4.23.3", 199 | "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.23.3.tgz", 200 | "integrity": "sha512-Wjl5gttqnf/gQKJA+dafnD0Y6Yw97yvfY8R9h0dQltX1GXTgNs1zWgvtWW0tHl1EgMdhAyw189uWiZMnL3QebQ==", 201 | "dev": true, 202 | "dependencies": { 203 | "@algolia/cache-common": "4.23.3", 204 | "@algolia/logger-common": "4.23.3", 205 | "@algolia/requester-common": "4.23.3" 206 | } 207 | }, 208 | "node_modules/@babel/parser": { 209 | "version": "7.24.5", 210 | "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.5.tgz", 211 | "integrity": "sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==", 212 | "dev": true, 213 | "bin": { 214 | "parser": "bin/babel-parser.js" 215 | }, 216 | "engines": { 217 | "node": ">=6.0.0" 218 | } 219 | }, 220 | "node_modules/@docsearch/css": { 221 | "version": "3.6.0", 222 | "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.6.0.tgz", 223 | "integrity": "sha512-+sbxb71sWre+PwDK7X2T8+bhS6clcVMLwBPznX45Qu6opJcgRjAp7gYSDzVFp187J+feSj5dNBN1mJoi6ckkUQ==", 224 | "dev": true 225 | }, 226 | "node_modules/@docsearch/js": { 227 | "version": "3.6.0", 228 | "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.6.0.tgz", 229 | "integrity": "sha512-QujhqINEElrkIfKwyyyTfbsfMAYCkylInLYMRqHy7PHc8xTBQCow73tlo/Kc7oIwBrCLf0P3YhjlOeV4v8hevQ==", 230 | "dev": true, 231 | "dependencies": { 232 | "@docsearch/react": "3.6.0", 233 | "preact": "^10.0.0" 234 | } 235 | }, 236 | "node_modules/@docsearch/react": { 237 | "version": "3.6.0", 238 | "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.6.0.tgz", 239 | "integrity": "sha512-HUFut4ztcVNmqy9gp/wxNbC7pTOHhgVVkHVGCACTuLhUKUhKAF9KYHJtMiLUJxEqiFLQiuri1fWF8zqwM/cu1w==", 240 | "dev": true, 241 | "dependencies": { 242 | "@algolia/autocomplete-core": "1.9.3", 243 | "@algolia/autocomplete-preset-algolia": "1.9.3", 244 | "@docsearch/css": "3.6.0", 245 | "algoliasearch": "^4.19.1" 246 | }, 247 | "peerDependencies": { 248 | "@types/react": ">= 16.8.0 < 19.0.0", 249 | "react": ">= 16.8.0 < 19.0.0", 250 | "react-dom": ">= 16.8.0 < 19.0.0", 251 | "search-insights": ">= 1 < 3" 252 | }, 253 | "peerDependenciesMeta": { 254 | "@types/react": { 255 | "optional": true 256 | }, 257 | "react": { 258 | "optional": true 259 | }, 260 | "react-dom": { 261 | "optional": true 262 | }, 263 | "search-insights": { 264 | "optional": true 265 | } 266 | } 267 | }, 268 | "node_modules/@esbuild/aix-ppc64": { 269 | "version": "0.20.2", 270 | "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", 271 | "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", 272 | "cpu": [ 273 | "ppc64" 274 | ], 275 | "dev": true, 276 | "optional": true, 277 | "os": [ 278 | "aix" 279 | ], 280 | "engines": { 281 | "node": ">=12" 282 | } 283 | }, 284 | "node_modules/@esbuild/android-arm": { 285 | "version": "0.20.2", 286 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", 287 | "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", 288 | "cpu": [ 289 | "arm" 290 | ], 291 | "dev": true, 292 | "optional": true, 293 | "os": [ 294 | "android" 295 | ], 296 | "engines": { 297 | "node": ">=12" 298 | } 299 | }, 300 | "node_modules/@esbuild/android-arm64": { 301 | "version": "0.20.2", 302 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", 303 | "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", 304 | "cpu": [ 305 | "arm64" 306 | ], 307 | "dev": true, 308 | "optional": true, 309 | "os": [ 310 | "android" 311 | ], 312 | "engines": { 313 | "node": ">=12" 314 | } 315 | }, 316 | "node_modules/@esbuild/android-x64": { 317 | "version": "0.20.2", 318 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", 319 | "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", 320 | "cpu": [ 321 | "x64" 322 | ], 323 | "dev": true, 324 | "optional": true, 325 | "os": [ 326 | "android" 327 | ], 328 | "engines": { 329 | "node": ">=12" 330 | } 331 | }, 332 | "node_modules/@esbuild/darwin-arm64": { 333 | "version": "0.20.2", 334 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", 335 | "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", 336 | "cpu": [ 337 | "arm64" 338 | ], 339 | "dev": true, 340 | "optional": true, 341 | "os": [ 342 | "darwin" 343 | ], 344 | "engines": { 345 | "node": ">=12" 346 | } 347 | }, 348 | "node_modules/@esbuild/darwin-x64": { 349 | "version": "0.20.2", 350 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", 351 | "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", 352 | "cpu": [ 353 | "x64" 354 | ], 355 | "dev": true, 356 | "optional": true, 357 | "os": [ 358 | "darwin" 359 | ], 360 | "engines": { 361 | "node": ">=12" 362 | } 363 | }, 364 | "node_modules/@esbuild/freebsd-arm64": { 365 | "version": "0.20.2", 366 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", 367 | "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", 368 | "cpu": [ 369 | "arm64" 370 | ], 371 | "dev": true, 372 | "optional": true, 373 | "os": [ 374 | "freebsd" 375 | ], 376 | "engines": { 377 | "node": ">=12" 378 | } 379 | }, 380 | "node_modules/@esbuild/freebsd-x64": { 381 | "version": "0.20.2", 382 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", 383 | "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", 384 | "cpu": [ 385 | "x64" 386 | ], 387 | "dev": true, 388 | "optional": true, 389 | "os": [ 390 | "freebsd" 391 | ], 392 | "engines": { 393 | "node": ">=12" 394 | } 395 | }, 396 | "node_modules/@esbuild/linux-arm": { 397 | "version": "0.20.2", 398 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", 399 | "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", 400 | "cpu": [ 401 | "arm" 402 | ], 403 | "dev": true, 404 | "optional": true, 405 | "os": [ 406 | "linux" 407 | ], 408 | "engines": { 409 | "node": ">=12" 410 | } 411 | }, 412 | "node_modules/@esbuild/linux-arm64": { 413 | "version": "0.20.2", 414 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", 415 | "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", 416 | "cpu": [ 417 | "arm64" 418 | ], 419 | "dev": true, 420 | "optional": true, 421 | "os": [ 422 | "linux" 423 | ], 424 | "engines": { 425 | "node": ">=12" 426 | } 427 | }, 428 | "node_modules/@esbuild/linux-ia32": { 429 | "version": "0.20.2", 430 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", 431 | "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", 432 | "cpu": [ 433 | "ia32" 434 | ], 435 | "dev": true, 436 | "optional": true, 437 | "os": [ 438 | "linux" 439 | ], 440 | "engines": { 441 | "node": ">=12" 442 | } 443 | }, 444 | "node_modules/@esbuild/linux-loong64": { 445 | "version": "0.20.2", 446 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", 447 | "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", 448 | "cpu": [ 449 | "loong64" 450 | ], 451 | "dev": true, 452 | "optional": true, 453 | "os": [ 454 | "linux" 455 | ], 456 | "engines": { 457 | "node": ">=12" 458 | } 459 | }, 460 | "node_modules/@esbuild/linux-mips64el": { 461 | "version": "0.20.2", 462 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", 463 | "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", 464 | "cpu": [ 465 | "mips64el" 466 | ], 467 | "dev": true, 468 | "optional": true, 469 | "os": [ 470 | "linux" 471 | ], 472 | "engines": { 473 | "node": ">=12" 474 | } 475 | }, 476 | "node_modules/@esbuild/linux-ppc64": { 477 | "version": "0.20.2", 478 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", 479 | "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", 480 | "cpu": [ 481 | "ppc64" 482 | ], 483 | "dev": true, 484 | "optional": true, 485 | "os": [ 486 | "linux" 487 | ], 488 | "engines": { 489 | "node": ">=12" 490 | } 491 | }, 492 | "node_modules/@esbuild/linux-riscv64": { 493 | "version": "0.20.2", 494 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", 495 | "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", 496 | "cpu": [ 497 | "riscv64" 498 | ], 499 | "dev": true, 500 | "optional": true, 501 | "os": [ 502 | "linux" 503 | ], 504 | "engines": { 505 | "node": ">=12" 506 | } 507 | }, 508 | "node_modules/@esbuild/linux-s390x": { 509 | "version": "0.20.2", 510 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", 511 | "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", 512 | "cpu": [ 513 | "s390x" 514 | ], 515 | "dev": true, 516 | "optional": true, 517 | "os": [ 518 | "linux" 519 | ], 520 | "engines": { 521 | "node": ">=12" 522 | } 523 | }, 524 | "node_modules/@esbuild/linux-x64": { 525 | "version": "0.20.2", 526 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", 527 | "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", 528 | "cpu": [ 529 | "x64" 530 | ], 531 | "dev": true, 532 | "optional": true, 533 | "os": [ 534 | "linux" 535 | ], 536 | "engines": { 537 | "node": ">=12" 538 | } 539 | }, 540 | "node_modules/@esbuild/netbsd-x64": { 541 | "version": "0.20.2", 542 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", 543 | "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", 544 | "cpu": [ 545 | "x64" 546 | ], 547 | "dev": true, 548 | "optional": true, 549 | "os": [ 550 | "netbsd" 551 | ], 552 | "engines": { 553 | "node": ">=12" 554 | } 555 | }, 556 | "node_modules/@esbuild/openbsd-x64": { 557 | "version": "0.20.2", 558 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", 559 | "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", 560 | "cpu": [ 561 | "x64" 562 | ], 563 | "dev": true, 564 | "optional": true, 565 | "os": [ 566 | "openbsd" 567 | ], 568 | "engines": { 569 | "node": ">=12" 570 | } 571 | }, 572 | "node_modules/@esbuild/sunos-x64": { 573 | "version": "0.20.2", 574 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", 575 | "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", 576 | "cpu": [ 577 | "x64" 578 | ], 579 | "dev": true, 580 | "optional": true, 581 | "os": [ 582 | "sunos" 583 | ], 584 | "engines": { 585 | "node": ">=12" 586 | } 587 | }, 588 | "node_modules/@esbuild/win32-arm64": { 589 | "version": "0.20.2", 590 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", 591 | "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", 592 | "cpu": [ 593 | "arm64" 594 | ], 595 | "dev": true, 596 | "optional": true, 597 | "os": [ 598 | "win32" 599 | ], 600 | "engines": { 601 | "node": ">=12" 602 | } 603 | }, 604 | "node_modules/@esbuild/win32-ia32": { 605 | "version": "0.20.2", 606 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", 607 | "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", 608 | "cpu": [ 609 | "ia32" 610 | ], 611 | "dev": true, 612 | "optional": true, 613 | "os": [ 614 | "win32" 615 | ], 616 | "engines": { 617 | "node": ">=12" 618 | } 619 | }, 620 | "node_modules/@esbuild/win32-x64": { 621 | "version": "0.20.2", 622 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", 623 | "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", 624 | "cpu": [ 625 | "x64" 626 | ], 627 | "dev": true, 628 | "optional": true, 629 | "os": [ 630 | "win32" 631 | ], 632 | "engines": { 633 | "node": ">=12" 634 | } 635 | }, 636 | "node_modules/@jridgewell/sourcemap-codec": { 637 | "version": "1.4.15", 638 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", 639 | "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", 640 | "dev": true 641 | }, 642 | "node_modules/@rollup/rollup-android-arm-eabi": { 643 | "version": "4.17.2", 644 | "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.17.2.tgz", 645 | "integrity": "sha512-NM0jFxY8bB8QLkoKxIQeObCaDlJKewVlIEkuyYKm5An1tdVZ966w2+MPQ2l8LBZLjR+SgyV+nRkTIunzOYBMLQ==", 646 | "cpu": [ 647 | "arm" 648 | ], 649 | "dev": true, 650 | "optional": true, 651 | "os": [ 652 | "android" 653 | ] 654 | }, 655 | "node_modules/@rollup/rollup-android-arm64": { 656 | "version": "4.17.2", 657 | "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.17.2.tgz", 658 | "integrity": "sha512-yeX/Usk7daNIVwkq2uGoq2BYJKZY1JfyLTaHO/jaiSwi/lsf8fTFoQW/n6IdAsx5tx+iotu2zCJwz8MxI6D/Bw==", 659 | "cpu": [ 660 | "arm64" 661 | ], 662 | "dev": true, 663 | "optional": true, 664 | "os": [ 665 | "android" 666 | ] 667 | }, 668 | "node_modules/@rollup/rollup-darwin-arm64": { 669 | "version": "4.17.2", 670 | "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.17.2.tgz", 671 | "integrity": "sha512-kcMLpE6uCwls023+kknm71ug7MZOrtXo+y5p/tsg6jltpDtgQY1Eq5sGfHcQfb+lfuKwhBmEURDga9N0ol4YPw==", 672 | "cpu": [ 673 | "arm64" 674 | ], 675 | "dev": true, 676 | "optional": true, 677 | "os": [ 678 | "darwin" 679 | ] 680 | }, 681 | "node_modules/@rollup/rollup-darwin-x64": { 682 | "version": "4.17.2", 683 | "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.17.2.tgz", 684 | "integrity": "sha512-AtKwD0VEx0zWkL0ZjixEkp5tbNLzX+FCqGG1SvOu993HnSz4qDI6S4kGzubrEJAljpVkhRSlg5bzpV//E6ysTQ==", 685 | "cpu": [ 686 | "x64" 687 | ], 688 | "dev": true, 689 | "optional": true, 690 | "os": [ 691 | "darwin" 692 | ] 693 | }, 694 | "node_modules/@rollup/rollup-linux-arm-gnueabihf": { 695 | "version": "4.17.2", 696 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.17.2.tgz", 697 | "integrity": "sha512-3reX2fUHqN7sffBNqmEyMQVj/CKhIHZd4y631duy0hZqI8Qoqf6lTtmAKvJFYa6bhU95B1D0WgzHkmTg33In0A==", 698 | "cpu": [ 699 | "arm" 700 | ], 701 | "dev": true, 702 | "optional": true, 703 | "os": [ 704 | "linux" 705 | ] 706 | }, 707 | "node_modules/@rollup/rollup-linux-arm-musleabihf": { 708 | "version": "4.17.2", 709 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.17.2.tgz", 710 | "integrity": "sha512-uSqpsp91mheRgw96xtyAGP9FW5ChctTFEoXP0r5FAzj/3ZRv3Uxjtc7taRQSaQM/q85KEKjKsZuiZM3GyUivRg==", 711 | "cpu": [ 712 | "arm" 713 | ], 714 | "dev": true, 715 | "optional": true, 716 | "os": [ 717 | "linux" 718 | ] 719 | }, 720 | "node_modules/@rollup/rollup-linux-arm64-gnu": { 721 | "version": "4.17.2", 722 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.17.2.tgz", 723 | "integrity": "sha512-EMMPHkiCRtE8Wdk3Qhtciq6BndLtstqZIroHiiGzB3C5LDJmIZcSzVtLRbwuXuUft1Cnv+9fxuDtDxz3k3EW2A==", 724 | "cpu": [ 725 | "arm64" 726 | ], 727 | "dev": true, 728 | "optional": true, 729 | "os": [ 730 | "linux" 731 | ] 732 | }, 733 | "node_modules/@rollup/rollup-linux-arm64-musl": { 734 | "version": "4.17.2", 735 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.17.2.tgz", 736 | "integrity": "sha512-NMPylUUZ1i0z/xJUIx6VUhISZDRT+uTWpBcjdv0/zkp7b/bQDF+NfnfdzuTiB1G6HTodgoFa93hp0O1xl+/UbA==", 737 | "cpu": [ 738 | "arm64" 739 | ], 740 | "dev": true, 741 | "optional": true, 742 | "os": [ 743 | "linux" 744 | ] 745 | }, 746 | "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { 747 | "version": "4.17.2", 748 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.17.2.tgz", 749 | "integrity": "sha512-T19My13y8uYXPw/L/k0JYaX1fJKFT/PWdXiHr8mTbXWxjVF1t+8Xl31DgBBvEKclw+1b00Chg0hxE2O7bTG7GQ==", 750 | "cpu": [ 751 | "ppc64" 752 | ], 753 | "dev": true, 754 | "optional": true, 755 | "os": [ 756 | "linux" 757 | ] 758 | }, 759 | "node_modules/@rollup/rollup-linux-riscv64-gnu": { 760 | "version": "4.17.2", 761 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.17.2.tgz", 762 | "integrity": "sha512-BOaNfthf3X3fOWAB+IJ9kxTgPmMqPPH5f5k2DcCsRrBIbWnaJCgX2ll77dV1TdSy9SaXTR5iDXRL8n7AnoP5cg==", 763 | "cpu": [ 764 | "riscv64" 765 | ], 766 | "dev": true, 767 | "optional": true, 768 | "os": [ 769 | "linux" 770 | ] 771 | }, 772 | "node_modules/@rollup/rollup-linux-s390x-gnu": { 773 | "version": "4.17.2", 774 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.17.2.tgz", 775 | "integrity": "sha512-W0UP/x7bnn3xN2eYMql2T/+wpASLE5SjObXILTMPUBDB/Fg/FxC+gX4nvCfPBCbNhz51C+HcqQp2qQ4u25ok6g==", 776 | "cpu": [ 777 | "s390x" 778 | ], 779 | "dev": true, 780 | "optional": true, 781 | "os": [ 782 | "linux" 783 | ] 784 | }, 785 | "node_modules/@rollup/rollup-linux-x64-gnu": { 786 | "version": "4.17.2", 787 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.17.2.tgz", 788 | "integrity": "sha512-Hy7pLwByUOuyaFC6mAr7m+oMC+V7qyifzs/nW2OJfC8H4hbCzOX07Ov0VFk/zP3kBsELWNFi7rJtgbKYsav9QQ==", 789 | "cpu": [ 790 | "x64" 791 | ], 792 | "dev": true, 793 | "optional": true, 794 | "os": [ 795 | "linux" 796 | ] 797 | }, 798 | "node_modules/@rollup/rollup-linux-x64-musl": { 799 | "version": "4.17.2", 800 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.17.2.tgz", 801 | "integrity": "sha512-h1+yTWeYbRdAyJ/jMiVw0l6fOOm/0D1vNLui9iPuqgRGnXA0u21gAqOyB5iHjlM9MMfNOm9RHCQ7zLIzT0x11Q==", 802 | "cpu": [ 803 | "x64" 804 | ], 805 | "dev": true, 806 | "optional": true, 807 | "os": [ 808 | "linux" 809 | ] 810 | }, 811 | "node_modules/@rollup/rollup-win32-arm64-msvc": { 812 | "version": "4.17.2", 813 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.17.2.tgz", 814 | "integrity": "sha512-tmdtXMfKAjy5+IQsVtDiCfqbynAQE/TQRpWdVataHmhMb9DCoJxp9vLcCBjEQWMiUYxO1QprH/HbY9ragCEFLA==", 815 | "cpu": [ 816 | "arm64" 817 | ], 818 | "dev": true, 819 | "optional": true, 820 | "os": [ 821 | "win32" 822 | ] 823 | }, 824 | "node_modules/@rollup/rollup-win32-ia32-msvc": { 825 | "version": "4.17.2", 826 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.17.2.tgz", 827 | "integrity": "sha512-7II/QCSTAHuE5vdZaQEwJq2ZACkBpQDOmQsE6D6XUbnBHW8IAhm4eTufL6msLJorzrHDFv3CF8oCA/hSIRuZeQ==", 828 | "cpu": [ 829 | "ia32" 830 | ], 831 | "dev": true, 832 | "optional": true, 833 | "os": [ 834 | "win32" 835 | ] 836 | }, 837 | "node_modules/@rollup/rollup-win32-x64-msvc": { 838 | "version": "4.17.2", 839 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.17.2.tgz", 840 | "integrity": "sha512-TGGO7v7qOq4CYmSBVEYpI1Y5xDuCEnbVC5Vth8mOsW0gDSzxNrVERPc790IGHsrT2dQSimgMr9Ub3Y1Jci5/8w==", 841 | "cpu": [ 842 | "x64" 843 | ], 844 | "dev": true, 845 | "optional": true, 846 | "os": [ 847 | "win32" 848 | ] 849 | }, 850 | "node_modules/@shikijs/core": { 851 | "version": "1.6.0", 852 | "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.6.0.tgz", 853 | "integrity": "sha512-NIEAi5U5R7BLkbW1pG/ZKu3eb1lzc3/+jD0lFsuxMT7zjaf9bbNwdNyMr7zh/Zl8EXQtQ+MYBAt5G+JLu+5DlA==", 854 | "dev": true 855 | }, 856 | "node_modules/@shikijs/transformers": { 857 | "version": "1.6.0", 858 | "resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-1.6.0.tgz", 859 | "integrity": "sha512-qGfHe1ECiqfE2STPWvfogIj/9Q0SK+MCRJdoITkW7AmFuB7DmbFnBT2US84+zklJOB51MzNO8RUXZiauWssJlQ==", 860 | "dev": true, 861 | "dependencies": { 862 | "shiki": "1.6.0" 863 | } 864 | }, 865 | "node_modules/@types/estree": { 866 | "version": "1.0.5", 867 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", 868 | "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", 869 | "dev": true 870 | }, 871 | "node_modules/@types/linkify-it": { 872 | "version": "5.0.0", 873 | "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", 874 | "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", 875 | "dev": true 876 | }, 877 | "node_modules/@types/markdown-it": { 878 | "version": "14.1.1", 879 | "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.1.tgz", 880 | "integrity": "sha512-4NpsnpYl2Gt1ljyBGrKMxFYAYvpqbnnkgP/i/g+NLpjEUa3obn1XJCur9YbEXKDAkaXqsR1LbDnGEJ0MmKFxfg==", 881 | "dev": true, 882 | "dependencies": { 883 | "@types/linkify-it": "^5", 884 | "@types/mdurl": "^2" 885 | } 886 | }, 887 | "node_modules/@types/mdurl": { 888 | "version": "2.0.0", 889 | "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", 890 | "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", 891 | "dev": true 892 | }, 893 | "node_modules/@types/web-bluetooth": { 894 | "version": "0.0.20", 895 | "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", 896 | "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==", 897 | "dev": true 898 | }, 899 | "node_modules/@vitejs/plugin-vue": { 900 | "version": "5.0.4", 901 | "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.0.4.tgz", 902 | "integrity": "sha512-WS3hevEszI6CEVEx28F8RjTX97k3KsrcY6kvTg7+Whm5y3oYvcqzVeGCU3hxSAn4uY2CLCkeokkGKpoctccilQ==", 903 | "dev": true, 904 | "engines": { 905 | "node": "^18.0.0 || >=20.0.0" 906 | }, 907 | "peerDependencies": { 908 | "vite": "^5.0.0", 909 | "vue": "^3.2.25" 910 | } 911 | }, 912 | "node_modules/@vue/compiler-core": { 913 | "version": "3.4.27", 914 | "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.27.tgz", 915 | "integrity": "sha512-E+RyqY24KnyDXsCuQrI+mlcdW3ALND6U7Gqa/+bVwbcpcR3BRRIckFoz7Qyd4TTlnugtwuI7YgjbvsLmxb+yvg==", 916 | "dev": true, 917 | "dependencies": { 918 | "@babel/parser": "^7.24.4", 919 | "@vue/shared": "3.4.27", 920 | "entities": "^4.5.0", 921 | "estree-walker": "^2.0.2", 922 | "source-map-js": "^1.2.0" 923 | } 924 | }, 925 | "node_modules/@vue/compiler-dom": { 926 | "version": "3.4.27", 927 | "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.27.tgz", 928 | "integrity": "sha512-kUTvochG/oVgE1w5ViSr3KUBh9X7CWirebA3bezTbB5ZKBQZwR2Mwj9uoSKRMFcz4gSMzzLXBPD6KpCLb9nvWw==", 929 | "dev": true, 930 | "dependencies": { 931 | "@vue/compiler-core": "3.4.27", 932 | "@vue/shared": "3.4.27" 933 | } 934 | }, 935 | "node_modules/@vue/compiler-sfc": { 936 | "version": "3.4.27", 937 | "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.27.tgz", 938 | "integrity": "sha512-nDwntUEADssW8e0rrmE0+OrONwmRlegDA1pD6QhVeXxjIytV03yDqTey9SBDiALsvAd5U4ZrEKbMyVXhX6mCGA==", 939 | "dev": true, 940 | "dependencies": { 941 | "@babel/parser": "^7.24.4", 942 | "@vue/compiler-core": "3.4.27", 943 | "@vue/compiler-dom": "3.4.27", 944 | "@vue/compiler-ssr": "3.4.27", 945 | "@vue/shared": "3.4.27", 946 | "estree-walker": "^2.0.2", 947 | "magic-string": "^0.30.10", 948 | "postcss": "^8.4.38", 949 | "source-map-js": "^1.2.0" 950 | } 951 | }, 952 | "node_modules/@vue/compiler-ssr": { 953 | "version": "3.4.27", 954 | "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.27.tgz", 955 | "integrity": "sha512-CVRzSJIltzMG5FcidsW0jKNQnNRYC8bT21VegyMMtHmhW3UOI7knmUehzswXLrExDLE6lQCZdrhD4ogI7c+vuw==", 956 | "dev": true, 957 | "dependencies": { 958 | "@vue/compiler-dom": "3.4.27", 959 | "@vue/shared": "3.4.27" 960 | } 961 | }, 962 | "node_modules/@vue/devtools-api": { 963 | "version": "7.2.1", 964 | "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.2.1.tgz", 965 | "integrity": "sha512-6oNCtyFOrNdqm6GUkFujsCgFlpbsHLnZqq7edeM/+cxAbMyCWvsaCsIMUaz7AiluKLccCGEM8fhOsjaKgBvb7g==", 966 | "dev": true, 967 | "dependencies": { 968 | "@vue/devtools-kit": "^7.2.1" 969 | } 970 | }, 971 | "node_modules/@vue/devtools-kit": { 972 | "version": "7.2.1", 973 | "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.2.1.tgz", 974 | "integrity": "sha512-Wak/fin1X0Q8LLIfCAHBrdaaB+R6IdpSXsDByPHbQ3BmkCP0/cIo/oEGp9i0U2+gEqD4L3V9RDjNf1S34DTzQQ==", 975 | "dev": true, 976 | "dependencies": { 977 | "@vue/devtools-shared": "^7.2.1", 978 | "hookable": "^5.5.3", 979 | "mitt": "^3.0.1", 980 | "perfect-debounce": "^1.0.0", 981 | "speakingurl": "^14.0.1" 982 | }, 983 | "peerDependencies": { 984 | "vue": "^3.0.0" 985 | } 986 | }, 987 | "node_modules/@vue/devtools-shared": { 988 | "version": "7.2.1", 989 | "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.2.1.tgz", 990 | "integrity": "sha512-PCJF4UknJmOal68+X9XHyVeQ+idv0LFujkTOIW30+GaMJqwFVN9LkQKX4gLqn61KkGMdJTzQ1bt7EJag3TI6AA==", 991 | "dev": true, 992 | "dependencies": { 993 | "rfdc": "^1.3.1" 994 | } 995 | }, 996 | "node_modules/@vue/reactivity": { 997 | "version": "3.4.27", 998 | "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.27.tgz", 999 | "integrity": "sha512-kK0g4NknW6JX2yySLpsm2jlunZJl2/RJGZ0H9ddHdfBVHcNzxmQ0sS0b09ipmBoQpY8JM2KmUw+a6sO8Zo+zIA==", 1000 | "dev": true, 1001 | "dependencies": { 1002 | "@vue/shared": "3.4.27" 1003 | } 1004 | }, 1005 | "node_modules/@vue/runtime-core": { 1006 | "version": "3.4.27", 1007 | "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.27.tgz", 1008 | "integrity": "sha512-7aYA9GEbOOdviqVvcuweTLe5Za4qBZkUY7SvET6vE8kyypxVgaT1ixHLg4urtOlrApdgcdgHoTZCUuTGap/5WA==", 1009 | "dev": true, 1010 | "dependencies": { 1011 | "@vue/reactivity": "3.4.27", 1012 | "@vue/shared": "3.4.27" 1013 | } 1014 | }, 1015 | "node_modules/@vue/runtime-dom": { 1016 | "version": "3.4.27", 1017 | "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.27.tgz", 1018 | "integrity": "sha512-ScOmP70/3NPM+TW9hvVAz6VWWtZJqkbdf7w6ySsws+EsqtHvkhxaWLecrTorFxsawelM5Ys9FnDEMt6BPBDS0Q==", 1019 | "dev": true, 1020 | "dependencies": { 1021 | "@vue/runtime-core": "3.4.27", 1022 | "@vue/shared": "3.4.27", 1023 | "csstype": "^3.1.3" 1024 | } 1025 | }, 1026 | "node_modules/@vue/server-renderer": { 1027 | "version": "3.4.27", 1028 | "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.27.tgz", 1029 | "integrity": "sha512-dlAMEuvmeA3rJsOMJ2J1kXU7o7pOxgsNHVr9K8hB3ImIkSuBrIdy0vF66h8gf8Tuinf1TK3mPAz2+2sqyf3KzA==", 1030 | "dev": true, 1031 | "dependencies": { 1032 | "@vue/compiler-ssr": "3.4.27", 1033 | "@vue/shared": "3.4.27" 1034 | }, 1035 | "peerDependencies": { 1036 | "vue": "3.4.27" 1037 | } 1038 | }, 1039 | "node_modules/@vue/shared": { 1040 | "version": "3.4.27", 1041 | "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.27.tgz", 1042 | "integrity": "sha512-DL3NmY2OFlqmYYrzp39yi3LDkKxa5vZVwxWdQ3rG0ekuWscHraeIbnI8t+aZK7qhYqEqWKTUdijadunb9pnrgA==", 1043 | "dev": true 1044 | }, 1045 | "node_modules/@vueuse/core": { 1046 | "version": "10.9.0", 1047 | "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.9.0.tgz", 1048 | "integrity": "sha512-/1vjTol8SXnx6xewDEKfS0Ra//ncg4Hb0DaZiwKf7drgfMsKFExQ+FnnENcN6efPen+1kIzhLQoGSy0eDUVOMg==", 1049 | "dev": true, 1050 | "dependencies": { 1051 | "@types/web-bluetooth": "^0.0.20", 1052 | "@vueuse/metadata": "10.9.0", 1053 | "@vueuse/shared": "10.9.0", 1054 | "vue-demi": ">=0.14.7" 1055 | }, 1056 | "funding": { 1057 | "url": "https://github.com/sponsors/antfu" 1058 | } 1059 | }, 1060 | "node_modules/@vueuse/core/node_modules/vue-demi": { 1061 | "version": "0.14.7", 1062 | "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz", 1063 | "integrity": "sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==", 1064 | "dev": true, 1065 | "hasInstallScript": true, 1066 | "bin": { 1067 | "vue-demi-fix": "bin/vue-demi-fix.js", 1068 | "vue-demi-switch": "bin/vue-demi-switch.js" 1069 | }, 1070 | "engines": { 1071 | "node": ">=12" 1072 | }, 1073 | "funding": { 1074 | "url": "https://github.com/sponsors/antfu" 1075 | }, 1076 | "peerDependencies": { 1077 | "@vue/composition-api": "^1.0.0-rc.1", 1078 | "vue": "^3.0.0-0 || ^2.6.0" 1079 | }, 1080 | "peerDependenciesMeta": { 1081 | "@vue/composition-api": { 1082 | "optional": true 1083 | } 1084 | } 1085 | }, 1086 | "node_modules/@vueuse/integrations": { 1087 | "version": "10.9.0", 1088 | "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-10.9.0.tgz", 1089 | "integrity": "sha512-acK+A01AYdWSvL4BZmCoJAcyHJ6EqhmkQEXbQLwev1MY7NBnS+hcEMx/BzVoR9zKI+UqEPMD9u6PsyAuiTRT4Q==", 1090 | "dev": true, 1091 | "dependencies": { 1092 | "@vueuse/core": "10.9.0", 1093 | "@vueuse/shared": "10.9.0", 1094 | "vue-demi": ">=0.14.7" 1095 | }, 1096 | "funding": { 1097 | "url": "https://github.com/sponsors/antfu" 1098 | }, 1099 | "peerDependencies": { 1100 | "async-validator": "*", 1101 | "axios": "*", 1102 | "change-case": "*", 1103 | "drauu": "*", 1104 | "focus-trap": "*", 1105 | "fuse.js": "*", 1106 | "idb-keyval": "*", 1107 | "jwt-decode": "*", 1108 | "nprogress": "*", 1109 | "qrcode": "*", 1110 | "sortablejs": "*", 1111 | "universal-cookie": "*" 1112 | }, 1113 | "peerDependenciesMeta": { 1114 | "async-validator": { 1115 | "optional": true 1116 | }, 1117 | "axios": { 1118 | "optional": true 1119 | }, 1120 | "change-case": { 1121 | "optional": true 1122 | }, 1123 | "drauu": { 1124 | "optional": true 1125 | }, 1126 | "focus-trap": { 1127 | "optional": true 1128 | }, 1129 | "fuse.js": { 1130 | "optional": true 1131 | }, 1132 | "idb-keyval": { 1133 | "optional": true 1134 | }, 1135 | "jwt-decode": { 1136 | "optional": true 1137 | }, 1138 | "nprogress": { 1139 | "optional": true 1140 | }, 1141 | "qrcode": { 1142 | "optional": true 1143 | }, 1144 | "sortablejs": { 1145 | "optional": true 1146 | }, 1147 | "universal-cookie": { 1148 | "optional": true 1149 | } 1150 | } 1151 | }, 1152 | "node_modules/@vueuse/integrations/node_modules/vue-demi": { 1153 | "version": "0.14.7", 1154 | "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz", 1155 | "integrity": "sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==", 1156 | "dev": true, 1157 | "hasInstallScript": true, 1158 | "bin": { 1159 | "vue-demi-fix": "bin/vue-demi-fix.js", 1160 | "vue-demi-switch": "bin/vue-demi-switch.js" 1161 | }, 1162 | "engines": { 1163 | "node": ">=12" 1164 | }, 1165 | "funding": { 1166 | "url": "https://github.com/sponsors/antfu" 1167 | }, 1168 | "peerDependencies": { 1169 | "@vue/composition-api": "^1.0.0-rc.1", 1170 | "vue": "^3.0.0-0 || ^2.6.0" 1171 | }, 1172 | "peerDependenciesMeta": { 1173 | "@vue/composition-api": { 1174 | "optional": true 1175 | } 1176 | } 1177 | }, 1178 | "node_modules/@vueuse/metadata": { 1179 | "version": "10.9.0", 1180 | "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.9.0.tgz", 1181 | "integrity": "sha512-iddNbg3yZM0X7qFY2sAotomgdHK7YJ6sKUvQqbvwnf7TmaVPxS4EJydcNsVejNdS8iWCtDk+fYXr7E32nyTnGA==", 1182 | "dev": true, 1183 | "funding": { 1184 | "url": "https://github.com/sponsors/antfu" 1185 | } 1186 | }, 1187 | "node_modules/@vueuse/shared": { 1188 | "version": "10.9.0", 1189 | "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.9.0.tgz", 1190 | "integrity": "sha512-Uud2IWncmAfJvRaFYzv5OHDli+FbOzxiVEQdLCKQKLyhz94PIyFC3CHcH7EDMwIn8NPtD06+PNbC/PiO0LGLtw==", 1191 | "dev": true, 1192 | "dependencies": { 1193 | "vue-demi": ">=0.14.7" 1194 | }, 1195 | "funding": { 1196 | "url": "https://github.com/sponsors/antfu" 1197 | } 1198 | }, 1199 | "node_modules/@vueuse/shared/node_modules/vue-demi": { 1200 | "version": "0.14.7", 1201 | "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz", 1202 | "integrity": "sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==", 1203 | "dev": true, 1204 | "hasInstallScript": true, 1205 | "bin": { 1206 | "vue-demi-fix": "bin/vue-demi-fix.js", 1207 | "vue-demi-switch": "bin/vue-demi-switch.js" 1208 | }, 1209 | "engines": { 1210 | "node": ">=12" 1211 | }, 1212 | "funding": { 1213 | "url": "https://github.com/sponsors/antfu" 1214 | }, 1215 | "peerDependencies": { 1216 | "@vue/composition-api": "^1.0.0-rc.1", 1217 | "vue": "^3.0.0-0 || ^2.6.0" 1218 | }, 1219 | "peerDependenciesMeta": { 1220 | "@vue/composition-api": { 1221 | "optional": true 1222 | } 1223 | } 1224 | }, 1225 | "node_modules/algoliasearch": { 1226 | "version": "4.23.3", 1227 | "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.23.3.tgz", 1228 | "integrity": "sha512-Le/3YgNvjW9zxIQMRhUHuhiUjAlKY/zsdZpfq4dlLqg6mEm0nL6yk+7f2hDOtLpxsgE4jSzDmvHL7nXdBp5feg==", 1229 | "dev": true, 1230 | "dependencies": { 1231 | "@algolia/cache-browser-local-storage": "4.23.3", 1232 | "@algolia/cache-common": "4.23.3", 1233 | "@algolia/cache-in-memory": "4.23.3", 1234 | "@algolia/client-account": "4.23.3", 1235 | "@algolia/client-analytics": "4.23.3", 1236 | "@algolia/client-common": "4.23.3", 1237 | "@algolia/client-personalization": "4.23.3", 1238 | "@algolia/client-search": "4.23.3", 1239 | "@algolia/logger-common": "4.23.3", 1240 | "@algolia/logger-console": "4.23.3", 1241 | "@algolia/recommend": "4.23.3", 1242 | "@algolia/requester-browser-xhr": "4.23.3", 1243 | "@algolia/requester-common": "4.23.3", 1244 | "@algolia/requester-node-http": "4.23.3", 1245 | "@algolia/transporter": "4.23.3" 1246 | } 1247 | }, 1248 | "node_modules/csstype": { 1249 | "version": "3.1.3", 1250 | "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", 1251 | "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", 1252 | "dev": true 1253 | }, 1254 | "node_modules/entities": { 1255 | "version": "4.5.0", 1256 | "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", 1257 | "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", 1258 | "dev": true, 1259 | "engines": { 1260 | "node": ">=0.12" 1261 | }, 1262 | "funding": { 1263 | "url": "https://github.com/fb55/entities?sponsor=1" 1264 | } 1265 | }, 1266 | "node_modules/esbuild": { 1267 | "version": "0.20.2", 1268 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", 1269 | "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", 1270 | "dev": true, 1271 | "hasInstallScript": true, 1272 | "bin": { 1273 | "esbuild": "bin/esbuild" 1274 | }, 1275 | "engines": { 1276 | "node": ">=12" 1277 | }, 1278 | "optionalDependencies": { 1279 | "@esbuild/aix-ppc64": "0.20.2", 1280 | "@esbuild/android-arm": "0.20.2", 1281 | "@esbuild/android-arm64": "0.20.2", 1282 | "@esbuild/android-x64": "0.20.2", 1283 | "@esbuild/darwin-arm64": "0.20.2", 1284 | "@esbuild/darwin-x64": "0.20.2", 1285 | "@esbuild/freebsd-arm64": "0.20.2", 1286 | "@esbuild/freebsd-x64": "0.20.2", 1287 | "@esbuild/linux-arm": "0.20.2", 1288 | "@esbuild/linux-arm64": "0.20.2", 1289 | "@esbuild/linux-ia32": "0.20.2", 1290 | "@esbuild/linux-loong64": "0.20.2", 1291 | "@esbuild/linux-mips64el": "0.20.2", 1292 | "@esbuild/linux-ppc64": "0.20.2", 1293 | "@esbuild/linux-riscv64": "0.20.2", 1294 | "@esbuild/linux-s390x": "0.20.2", 1295 | "@esbuild/linux-x64": "0.20.2", 1296 | "@esbuild/netbsd-x64": "0.20.2", 1297 | "@esbuild/openbsd-x64": "0.20.2", 1298 | "@esbuild/sunos-x64": "0.20.2", 1299 | "@esbuild/win32-arm64": "0.20.2", 1300 | "@esbuild/win32-ia32": "0.20.2", 1301 | "@esbuild/win32-x64": "0.20.2" 1302 | } 1303 | }, 1304 | "node_modules/estree-walker": { 1305 | "version": "2.0.2", 1306 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", 1307 | "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", 1308 | "dev": true 1309 | }, 1310 | "node_modules/focus-trap": { 1311 | "version": "7.5.4", 1312 | "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.5.4.tgz", 1313 | "integrity": "sha512-N7kHdlgsO/v+iD/dMoJKtsSqs5Dz/dXZVebRgJw23LDk+jMi/974zyiOYDziY2JPp8xivq9BmUGwIJMiuSBi7w==", 1314 | "dev": true, 1315 | "dependencies": { 1316 | "tabbable": "^6.2.0" 1317 | } 1318 | }, 1319 | "node_modules/fsevents": { 1320 | "version": "2.3.3", 1321 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 1322 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 1323 | "dev": true, 1324 | "hasInstallScript": true, 1325 | "optional": true, 1326 | "os": [ 1327 | "darwin" 1328 | ], 1329 | "engines": { 1330 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 1331 | } 1332 | }, 1333 | "node_modules/hookable": { 1334 | "version": "5.5.3", 1335 | "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", 1336 | "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", 1337 | "dev": true 1338 | }, 1339 | "node_modules/magic-string": { 1340 | "version": "0.30.10", 1341 | "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz", 1342 | "integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==", 1343 | "dev": true, 1344 | "dependencies": { 1345 | "@jridgewell/sourcemap-codec": "^1.4.15" 1346 | } 1347 | }, 1348 | "node_modules/mark.js": { 1349 | "version": "8.11.1", 1350 | "resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz", 1351 | "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==", 1352 | "dev": true 1353 | }, 1354 | "node_modules/minisearch": { 1355 | "version": "6.3.0", 1356 | "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-6.3.0.tgz", 1357 | "integrity": "sha512-ihFnidEeU8iXzcVHy74dhkxh/dn8Dc08ERl0xwoMMGqp4+LvRSCgicb+zGqWthVokQKvCSxITlh3P08OzdTYCQ==", 1358 | "dev": true 1359 | }, 1360 | "node_modules/mitt": { 1361 | "version": "3.0.1", 1362 | "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", 1363 | "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", 1364 | "dev": true 1365 | }, 1366 | "node_modules/nanoid": { 1367 | "version": "3.3.7", 1368 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", 1369 | "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", 1370 | "dev": true, 1371 | "funding": [ 1372 | { 1373 | "type": "github", 1374 | "url": "https://github.com/sponsors/ai" 1375 | } 1376 | ], 1377 | "bin": { 1378 | "nanoid": "bin/nanoid.cjs" 1379 | }, 1380 | "engines": { 1381 | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 1382 | } 1383 | }, 1384 | "node_modules/perfect-debounce": { 1385 | "version": "1.0.0", 1386 | "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", 1387 | "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", 1388 | "dev": true 1389 | }, 1390 | "node_modules/picocolors": { 1391 | "version": "1.0.1", 1392 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", 1393 | "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", 1394 | "dev": true 1395 | }, 1396 | "node_modules/postcss": { 1397 | "version": "8.4.38", 1398 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", 1399 | "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", 1400 | "dev": true, 1401 | "funding": [ 1402 | { 1403 | "type": "opencollective", 1404 | "url": "https://opencollective.com/postcss/" 1405 | }, 1406 | { 1407 | "type": "tidelift", 1408 | "url": "https://tidelift.com/funding/github/npm/postcss" 1409 | }, 1410 | { 1411 | "type": "github", 1412 | "url": "https://github.com/sponsors/ai" 1413 | } 1414 | ], 1415 | "dependencies": { 1416 | "nanoid": "^3.3.7", 1417 | "picocolors": "^1.0.0", 1418 | "source-map-js": "^1.2.0" 1419 | }, 1420 | "engines": { 1421 | "node": "^10 || ^12 || >=14" 1422 | } 1423 | }, 1424 | "node_modules/preact": { 1425 | "version": "10.22.0", 1426 | "resolved": "https://registry.npmjs.org/preact/-/preact-10.22.0.tgz", 1427 | "integrity": "sha512-RRurnSjJPj4rp5K6XoP45Ui33ncb7e4H7WiOHVpjbkvqvA3U+N8Z6Qbo0AE6leGYBV66n8EhEaFixvIu3SkxFw==", 1428 | "dev": true, 1429 | "funding": { 1430 | "type": "opencollective", 1431 | "url": "https://opencollective.com/preact" 1432 | } 1433 | }, 1434 | "node_modules/rfdc": { 1435 | "version": "1.3.1", 1436 | "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.1.tgz", 1437 | "integrity": "sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==", 1438 | "dev": true 1439 | }, 1440 | "node_modules/rollup": { 1441 | "version": "4.17.2", 1442 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.17.2.tgz", 1443 | "integrity": "sha512-/9ClTJPByC0U4zNLowV1tMBe8yMEAxewtR3cUNX5BoEpGH3dQEWpJLr6CLp0fPdYRF/fzVOgvDb1zXuakwF5kQ==", 1444 | "dev": true, 1445 | "dependencies": { 1446 | "@types/estree": "1.0.5" 1447 | }, 1448 | "bin": { 1449 | "rollup": "dist/bin/rollup" 1450 | }, 1451 | "engines": { 1452 | "node": ">=18.0.0", 1453 | "npm": ">=8.0.0" 1454 | }, 1455 | "optionalDependencies": { 1456 | "@rollup/rollup-android-arm-eabi": "4.17.2", 1457 | "@rollup/rollup-android-arm64": "4.17.2", 1458 | "@rollup/rollup-darwin-arm64": "4.17.2", 1459 | "@rollup/rollup-darwin-x64": "4.17.2", 1460 | "@rollup/rollup-linux-arm-gnueabihf": "4.17.2", 1461 | "@rollup/rollup-linux-arm-musleabihf": "4.17.2", 1462 | "@rollup/rollup-linux-arm64-gnu": "4.17.2", 1463 | "@rollup/rollup-linux-arm64-musl": "4.17.2", 1464 | "@rollup/rollup-linux-powerpc64le-gnu": "4.17.2", 1465 | "@rollup/rollup-linux-riscv64-gnu": "4.17.2", 1466 | "@rollup/rollup-linux-s390x-gnu": "4.17.2", 1467 | "@rollup/rollup-linux-x64-gnu": "4.17.2", 1468 | "@rollup/rollup-linux-x64-musl": "4.17.2", 1469 | "@rollup/rollup-win32-arm64-msvc": "4.17.2", 1470 | "@rollup/rollup-win32-ia32-msvc": "4.17.2", 1471 | "@rollup/rollup-win32-x64-msvc": "4.17.2", 1472 | "fsevents": "~2.3.2" 1473 | } 1474 | }, 1475 | "node_modules/search-insights": { 1476 | "version": "2.13.0", 1477 | "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.13.0.tgz", 1478 | "integrity": "sha512-Orrsjf9trHHxFRuo9/rzm0KIWmgzE8RMlZMzuhZOJ01Rnz3D0YBAe+V6473t6/H6c7irs6Lt48brULAiRWb3Vw==", 1479 | "dev": true, 1480 | "peer": true 1481 | }, 1482 | "node_modules/shiki": { 1483 | "version": "1.6.0", 1484 | "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.6.0.tgz", 1485 | "integrity": "sha512-P31ROeXcVgW/k3Z+vUUErcxoTah7ZRaimctOpzGuqAntqnnSmx1HOsvnbAB8Z2qfXPRhw61yptAzCsuKOhTHwQ==", 1486 | "dev": true, 1487 | "dependencies": { 1488 | "@shikijs/core": "1.6.0" 1489 | } 1490 | }, 1491 | "node_modules/source-map-js": { 1492 | "version": "1.2.0", 1493 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", 1494 | "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", 1495 | "dev": true, 1496 | "engines": { 1497 | "node": ">=0.10.0" 1498 | } 1499 | }, 1500 | "node_modules/speakingurl": { 1501 | "version": "14.0.1", 1502 | "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", 1503 | "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", 1504 | "dev": true, 1505 | "engines": { 1506 | "node": ">=0.10.0" 1507 | } 1508 | }, 1509 | "node_modules/tabbable": { 1510 | "version": "6.2.0", 1511 | "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", 1512 | "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", 1513 | "dev": true 1514 | }, 1515 | "node_modules/vite": { 1516 | "version": "5.2.11", 1517 | "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.11.tgz", 1518 | "integrity": "sha512-HndV31LWW05i1BLPMUCE1B9E9GFbOu1MbenhS58FuK6owSO5qHm7GiCotrNY1YE5rMeQSFBGmT5ZaLEjFizgiQ==", 1519 | "dev": true, 1520 | "dependencies": { 1521 | "esbuild": "^0.20.1", 1522 | "postcss": "^8.4.38", 1523 | "rollup": "^4.13.0" 1524 | }, 1525 | "bin": { 1526 | "vite": "bin/vite.js" 1527 | }, 1528 | "engines": { 1529 | "node": "^18.0.0 || >=20.0.0" 1530 | }, 1531 | "funding": { 1532 | "url": "https://github.com/vitejs/vite?sponsor=1" 1533 | }, 1534 | "optionalDependencies": { 1535 | "fsevents": "~2.3.3" 1536 | }, 1537 | "peerDependencies": { 1538 | "@types/node": "^18.0.0 || >=20.0.0", 1539 | "less": "*", 1540 | "lightningcss": "^1.21.0", 1541 | "sass": "*", 1542 | "stylus": "*", 1543 | "sugarss": "*", 1544 | "terser": "^5.4.0" 1545 | }, 1546 | "peerDependenciesMeta": { 1547 | "@types/node": { 1548 | "optional": true 1549 | }, 1550 | "less": { 1551 | "optional": true 1552 | }, 1553 | "lightningcss": { 1554 | "optional": true 1555 | }, 1556 | "sass": { 1557 | "optional": true 1558 | }, 1559 | "stylus": { 1560 | "optional": true 1561 | }, 1562 | "sugarss": { 1563 | "optional": true 1564 | }, 1565 | "terser": { 1566 | "optional": true 1567 | } 1568 | } 1569 | }, 1570 | "node_modules/vitepress": { 1571 | "version": "1.2.0", 1572 | "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.2.0.tgz", 1573 | "integrity": "sha512-m/4PAQVyPBvKHV7sFKwcmNmrsoSxdjnw/Eg40YyuBSaBHhrro9ubnfWk5GT0xGfE98LqjZkHCWKNJlR6G/7Ayg==", 1574 | "dev": true, 1575 | "dependencies": { 1576 | "@docsearch/css": "^3.6.0", 1577 | "@docsearch/js": "^3.6.0", 1578 | "@shikijs/core": "^1.5.2", 1579 | "@shikijs/transformers": "^1.5.2", 1580 | "@types/markdown-it": "^14.1.1", 1581 | "@vitejs/plugin-vue": "^5.0.4", 1582 | "@vue/devtools-api": "^7.2.0", 1583 | "@vue/shared": "^3.4.27", 1584 | "@vueuse/core": "^10.9.0", 1585 | "@vueuse/integrations": "^10.9.0", 1586 | "focus-trap": "^7.5.4", 1587 | "mark.js": "8.11.1", 1588 | "minisearch": "^6.3.0", 1589 | "shiki": "^1.5.2", 1590 | "vite": "^5.2.11", 1591 | "vue": "^3.4.27" 1592 | }, 1593 | "bin": { 1594 | "vitepress": "bin/vitepress.js" 1595 | }, 1596 | "peerDependencies": { 1597 | "markdown-it-mathjax3": "^4", 1598 | "postcss": "^8" 1599 | }, 1600 | "peerDependenciesMeta": { 1601 | "markdown-it-mathjax3": { 1602 | "optional": true 1603 | }, 1604 | "postcss": { 1605 | "optional": true 1606 | } 1607 | } 1608 | }, 1609 | "node_modules/vue": { 1610 | "version": "3.4.27", 1611 | "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.27.tgz", 1612 | "integrity": "sha512-8s/56uK6r01r1icG/aEOHqyMVxd1bkYcSe9j8HcKtr/xTOFWvnzIVTehNW+5Yt89f+DLBe4A569pnZLS5HzAMA==", 1613 | "dev": true, 1614 | "dependencies": { 1615 | "@vue/compiler-dom": "3.4.27", 1616 | "@vue/compiler-sfc": "3.4.27", 1617 | "@vue/runtime-dom": "3.4.27", 1618 | "@vue/server-renderer": "3.4.27", 1619 | "@vue/shared": "3.4.27" 1620 | }, 1621 | "peerDependencies": { 1622 | "typescript": "*" 1623 | }, 1624 | "peerDependenciesMeta": { 1625 | "typescript": { 1626 | "optional": true 1627 | } 1628 | } 1629 | } 1630 | } 1631 | } 1632 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "quick-request-laravel", 3 | "version": "1.2.0", 4 | "description": "Execute requests easily and quickly to the Laravel backend with QuickRequest, leave Ajax aside, now you have an easy, secure, and modern way of making requests.", 5 | "main": "dist/js/quick-request-module.min.js", 6 | "scripts": { 7 | "docs:dev": "vitepress dev docs", 8 | "docs:build": "vitepress build docs", 9 | "docs:preview": "vitepress preview docs" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git://github.com/rmunate/QuickRequest-Laravel.git" 14 | }, 15 | "keywords": [ 16 | "QuickRequest", 17 | "Laravel", 18 | "Ajax", 19 | "Axios", 20 | "PHP" 21 | ], 22 | "author": "Raul Mauricio Uñate", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/rmunate/QuickRequest-Laravel/issues" 26 | }, 27 | "homepage": "https://github.com/rmunate/QuickRequest-Laravel#readme", 28 | "devDependencies": { 29 | "vitepress": "^1.2.0" 30 | } 31 | } --------------------------------------------------------------------------------