diff --git a/.codecov.yml b/.codecov.yml new file mode 100644 index 0000000..8622e3f --- /dev/null +++ b/.codecov.yml @@ -0,0 +1,39 @@ +coverage: + status: + default_rules: + # don't send status checks that don't have flag coverage uploaded + flag_coverage_not_uploaded_behavior: exclude + + # rules for overall project-wide coverage + project: + backend: + flags: + - backend + target: auto + threshold: 1% + frontend: + flags: + - frontend + target: auto + informational: true # don't fail because of frontend tests for now + + # rules based on the specific changes of the PR + patch: + backend: + flags: + - backend + target: 100% + threshold: 2% + frontend: + flags: + - frontend + target: auto + informational: true # don't fail because of frontend tests for now + +flags: + backend: + paths: + - src/classes/ + frontend: + paths: + - src/frontend/ diff --git a/.composer-require-checker.json b/.composer-require-checker.json new file mode 100644 index 0000000..b8eb236 --- /dev/null +++ b/.composer-require-checker.json @@ -0,0 +1,17 @@ +{ + "symbol-whitelist": [], + "php-core-extensions": [ + "Core", + "date", + "json", + "pcre", + "Phar", + "Reflection", + "SPL", + "standard" + ], + "scan-files": [ + "src/**/*.php", + "index.php" + ] +} diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..6e1d9a7 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,19 @@ +# This file is for unifying the coding style for different editors and IDEs +# editorconfig.org + +[*] +charset = utf-8 +end_of_line = lf +indent_style = tab +indent_size = 2 +trim_trailing_whitespace = true + +[*.php] +indent_size = 4 +insert_final_newline = true + +[*.yml] +indent_style = space + +[*.md,*.txt] +trim_trailing_whitespace = false diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..a80ac4d --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,24 @@ +module.exports = { + extends: [ + "eslint:recommended", + "plugin:vue/recommended", + "prettier" + ], + globals: { + "panel": true + }, + rules: { + "vue/attributes-order": "error", + "vue/component-definition-name-casing": "off", + "vue/html-closing-bracket-newline": [ + "error", + { + singleline: "never", + multiline: "always" + } + ], + "vue/multi-word-component-names": "off", + "vue/require-default-prop": "off", + "vue/require-prop-types": "error" + } +}; diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..832426c --- /dev/null +++ b/.gitattributes @@ -0,0 +1,30 @@ +# Git +.gitattributes export-ignore +.github/ export-ignore +.gitignore export-ignore + +# Source files +src/frontend/ export-ignore + +# Development files +.editorconfig export-ignore +.eslintrc.js export-ignore +.prettierrc.json export-ignore +composer.lock export-ignore +package-lock.json export-ignore +package.json export-ignore +rollup.config.js export-ignore + +# Screenshots +screenshot.png export-ignore + +# Tests +.codecov.yml export-ignore +.composer-require-checker.json export-ignore +.php-cs-fixer.dist.php export-ignore +phpmd.xml.dist export-ignore +phpunit.xml.dist export-ignore +psalm.xml.dist export-ignore +etc/ export-ignore +stubs/ export-ignore +tests/ export-ignore diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..dc41407 --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,75 @@ +# Contributing to the Roomle plugin + +👍️🎉 First off, thanks for taking the time to contribute! 🎉👍️ + +There are a few ways how you can contribute to the development of the Roomle plugin: + +## Report issues & suggest features + +While the Roomle plugin was originally created for a client project, the goal is to make it useful for other projects as well. For that I need to know if something isn't working or if something can be improved. + +It's already a huge help to receive detailed bug reports and thought-through feature requests. You can submit any of these by [creating a new issue](https://github.com/lukasbestle/kirby-roomle/issues/new) and using the issue templates, which are set up to guide you through providing all information. The more relevant information you give, the easier it is to find a solution for the bug or feature/enhancement request. + +## Help with translations + +The Panel interface of the Roomle plugin uses the configured user language for all the displayed text. Of course I don't speak all languages of the world, so I need your help. + +You can contribute additional translations by forking this repo and editing the following files: + +- Most importantly, add the translation to the block blueprint in [`src/config/blueprints/blocks/roomle-configurator.yml`](https://github.com/lukasbestle/kirby-roomle/blob/main/src/config/blueprints/blocks/roomle-configurator.yml). +- Add a new file in the [`src/config/i18n` directory](https://github.com/lukasbestle/kirby-roomle/tree/main/src/config/i18n). You can copy any of the existing translation files and use them as the base (however the English translation in `en.php` is considered the "original" and complete version). +- Please also add your translation to the list in [`src/config/translations.php`](https://github.com/lukasbestle/kirby-roomle/blob/main/src/config/translations.php). + +Once you are ready, send a pull request to this repo. + +If you want to improve existing translations, you can change the blueprint and translation files directly and send a pull request. + +Thank you for your help! ❤️ + +## Code contributions + +Pull requests for bug fixes or enhancements are always welcome! + +Please note that I can't guarantee that your pull request will be merged, especially if it's for a larger feature. If you want to make sure, please create an issue first (or comment in an existing issue, if one already exists for the task you want to work on) to discuss your idea. + +### How to set up the project for local development + +If you want to contribute, please first fork this repo. + +Now set up a local installation of one of the Kirby kits (I recommend the [Starterkit](https://github.com/getkirby/starterkit) or [Demokit](https://github.com/getkirby/demokit)). Create the directory `/site/plugins` if it doesn't already exist and then clone your fork of the Roomle plugin into `/site/plugins/roomle`. + +If you want to work on Panel code, please run the following commands inside `/site/plugins/roomle`: + +```sh +npm install +npm run dev +``` + +This will run the kirbyup bundler, which will listen for changes to the files inside the `src/frontend` directory. You can now open the Panel of your Kirby installation. + +If you want to work on the frontend code for the block, please run the following commands: + +```sh +npm install +npm run build:public +``` + +If you want to work on backend code and want to run the automated tests, you need the following command: + +```sh +composer install +``` + +The tests assume that you have PHPUnit installed globally. I also use a few other analysis tools that each have their commands listed in `/composer.json`. With `composer ci` you can run all tools at once (which assumes that all tools have been installed globally). If you don't want to install tools, don't worry – all tools will also be run automatically once you create your pull request. + +**Note:** Never commit the changes to the compiled dist files `/index.css`, `/index.js` and `/assets/*`. Including these files in your PR will lead to merge conflicts down the road. Instead, I will build the dist files for each plugin release. + +## Monetary support + +Most of my development time for this plugin has already been paid for, which is why I'm offering the plugin for free under the terms of the MIT license. For the same reason I do not sell licenses or accept donations. + +However you can support my work by commissioning the development of a feature of your choice: If your project requires a specific feature, you can pay me to build it for you. In exchange for your support you will get the feature more quickly and it will be designed with your requirements in mind. Additionally I can mention you as the sponsor of the feature in the Credits section of the plugin's `README`. + +Features developed in this way will also be published to this repo so that the community can benefit from them as well. This approach also enables future improvements and fixes to the feature. + +If you are interested, please get in touch directly via the [Kirby Forum](https://forum.getkirby.com/u/lukasbestle), [Discord](https://chat.getkirby.com) or [email](mailto:project-kirbyroomle@codesignd.de) to discuss the details. diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..fe9c475 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,28 @@ +--- +name: "🐛 Bug report" +about: "Something that isn't working as expected 🤬" +labels: "type: bug 🐛" +--- + +## Describe the bug + + +## Steps to reproduce + + +## Expected behavior + + +## Additional context + + +## Technical details + +- Plugin version: +- Kirby version: +- Browser: +- Server operating system: diff --git a/.github/ISSUE_TEMPLATE/enhancement.md b/.github/ISSUE_TEMPLATE/enhancement.md new file mode 100644 index 0000000..adf3555 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/enhancement.md @@ -0,0 +1,13 @@ +--- +name: "✨ Enhancement" +about: "It exists in the Roomle plugin, but you think it should be better 🤓" +labels: "type: enhancement ✨" +--- + +## Which aspect in the Roomle plugin bothers you and why? + + +## How would you improve it? + + +## How would this make the Roomle plugin better? diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..b2fafe4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,17 @@ +--- +name: "⭐️ Feature request" +about: "Something you would like to see added 🤩" +labels: "type: feature ⭐️" +--- + +## Is your feature request related to a problem? Please describe. + + +## Solution you'd like + + +## Potential alternatives + + +## Additional context + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..408c32b --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,15 @@ +## Describe the PR + + +## Related issues + + +- Fixes # + +## Ready? + + +- [ ] Added unit tests for fixed bug/feature +- [ ] Added in-code documentation (if needed) +- [ ] CI passes (runs automatically when the PR is created or run `composer ci` locally) + Running locally requires PHPUnit, PHP-CS-Fixer, Psalm, PHPCPD and PHPMD. diff --git a/.github/SECURITY.md b/.github/SECURITY.md new file mode 100644 index 0000000..570905a --- /dev/null +++ b/.github/SECURITY.md @@ -0,0 +1,14 @@ +# Security Policy + +I take the security of my open-source projects seriously. If you believe you found a security vulnerability, please report it to me as described below. + +## Reporting Security Issues + +**Please do not report security vulnerabilities through public GitHub issues.** + +Instead, please send me an email to . + +If you want to encrypt your message, you can use my PGP key: +[`D703 9FD2 4AAE 69E9 97A6 92FF 2E2A DD32 FE50 61C1`](https://lukasbestle.com/pgp.asc) + +I will get back to you as soon as possible. Please give me some time to fix the issue before publishing your findings (Coordinated Vulnerability Disclosure). diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..bfe35f5 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,249 @@ +name: CI +on: [push, pull_request] + +jobs: + tests: + name: PHP ${{ matrix.php }} (${{ matrix.dependencies }} deps) + + runs-on: ubuntu-latest + timeout-minutes: 5 + strategy: + matrix: + php: ["8.0", "8.1"] + dependencies: ["lowest", "locked"] + env: + extensions: mbstring, pcov + ini: pcov.directory=., "pcov.exclude=\"~(vendor|tests)~\"" + + steps: + - name: Checkout + uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # pin@v3 + + - name: Setup PHP cache environment + id: ext-cache + uses: shivammathur/cache-extensions@fc01a9cdc93341e96c2078d848f2e96240d83c17 # pin@v1 + with: + php-version: ${{ matrix.php }} + extensions: ${{ env.extensions }} + key: php-v1 + + - name: Cache PHP extensions + uses: actions/cache@c3f1317a9e7b1ef106c153ac8c0f00fed3ddbc0d # pin@v3 + with: + path: ${{ steps.ext-cache.outputs.dir }} + key: ${{ steps.ext-cache.outputs.key }} + restore-keys: ${{ steps.ext-cache.outputs.key }} + + - name: Setup PHP environment + uses: shivammathur/setup-php@3eda58347216592f618bb1dff277810b6698e4ca # pin@v2 + with: + php-version: ${{ matrix.php }} + extensions: ${{ env.extensions }} + ini-values: ${{ env.ini }} + coverage: pcov + tools: phpunit:9.5.13, psalm:4.11.2 + + - name: Setup problem matchers + run: | + echo "::add-matcher::${{ runner.tool_cache }}/php.json" + echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" + + - name: Get Composer cache directory + id: composerCache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache dependencies + uses: actions/cache@c3f1317a9e7b1ef106c153ac8c0f00fed3ddbc0d # pin@v3 + with: + path: ${{ steps.composerCache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ matrix.dependencies }}-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer-${{ matrix.dependencies }}- + + - name: Install dependencies + run: | + # install the lowest or highest versions based on the matrix config + command=$([ "${{ matrix.dependencies }}" == "lowest" ] && echo "update --prefer-lowest" || echo "install") + composer $command --prefer-dist + + - name: Cache analysis data + id: finishPrepare + uses: actions/cache@c3f1317a9e7b1ef106c153ac8c0f00fed3ddbc0d # pin@v3 + with: + path: ~/.cache/psalm + key: backend-analysis-${{ matrix.php }}-v2 + + - name: Run tests + if: always() && steps.finishPrepare.outcome == 'success' + run: phpunit --coverage-clover ${{ github.workspace }}/clover.xml + + - name: Statically analyze using Psalm + if: always() && steps.finishPrepare.outcome == 'success' + run: psalm --output-format=github --php-version=${{ matrix.php }} + + - name: Upload coverage results to Codecov + uses: codecov/codecov-action@66b3de25f6f91f65eb92c514d31d6b6f13d5ab18 # pin@v3 + with: + file: ${{ github.workspace }}/clover.xml + flags: backend + env_vars: PHP + env: + PHP: ${{ matrix.php }} + + analysis: + name: Analysis + + runs-on: ubuntu-latest + timeout-minutes: 5 + env: + php: "8.0" + extensions: mbstring + + steps: + - name: Checkout + uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # pin@v3 + + - name: Setup PHP cache environment + id: ext-cache + uses: shivammathur/cache-extensions@fc01a9cdc93341e96c2078d848f2e96240d83c17 # pin@v1 + with: + php-version: ${{ env.php }} + extensions: ${{ env.extensions }} + key: php-v1 + + - name: Cache PHP extensions + uses: actions/cache@c3f1317a9e7b1ef106c153ac8c0f00fed3ddbc0d # pin@v3 + with: + path: ${{ steps.ext-cache.outputs.dir }} + key: ${{ steps.ext-cache.outputs.key }} + restore-keys: ${{ steps.ext-cache.outputs.key }} + + - name: Setup PHP environment + id: finishPrepare + uses: shivammathur/setup-php@3eda58347216592f618bb1dff277810b6698e4ca # pin@v2 + with: + php-version: ${{ env.php }} + extensions: ${{ env.extensions }} + coverage: none + tools: | + composer:2.3.7, composer-normalize:2.28.0, + composer-unused:0.7.12, phpcpd:6.0.3, phpmd:2.12.0 + + - name: Validate composer.json/composer.lock + if: always() && steps.finishPrepare.outcome == 'success' + run: composer validate --strict --no-check-version --no-check-all + + - name: Ensure that composer.json is normalized + if: always() && steps.finishPrepare.outcome == 'success' + run: composer-normalize --dry-run + + - name: Get Composer cache directory + id: composerCache1 + if: always() && steps.finishPrepare.outcome == 'success' + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache dependencies + id: composerCache2 + if: always() && steps.composerCache1.outcome == 'success' + uses: actions/cache@c3f1317a9e7b1ef106c153ac8c0f00fed3ddbc0d # pin@v3 + with: + path: ${{ steps.composerCache1.outputs.dir }} + key: ${{ runner.os }}-composer-locked-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer-locked- + + - name: Install dependencies + id: composerInstall + if: always() && steps.composerCache2.outcome == 'success' + run: composer install --prefer-dist + + - name: Check for unused Composer dependencies + if: always() && steps.composerInstall.outcome == 'success' + run: composer-unused --no-progress + + - name: Check for duplicated code + if: always() && steps.composerInstall.outcome == 'success' + run: phpcpd --fuzzy --exclude tests --exclude vendor . + + - name: Statically analyze using PHPMD + if: always() && steps.composerInstall.outcome == 'success' + run: phpmd . github phpmd.xml.dist --exclude 'node_modules/*,stubs/*,tests/*,vendor/*' + + coding-style: + name: Coding Style & Frontend Analysis + + runs-on: ubuntu-latest + timeout-minutes: 5 + + steps: + - name: Checkout + uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # pin@v3 + + - name: Set up Node.js problem matchers and cache npm dependencies + uses: actions/setup-node@eeb10cff27034e7acf239c5d29f62154018672fd # pin@v3 + with: + cache: 'npm' + + - name: Install npm dependencies + run: npm ci + + - name: Setup PHP environment + uses: shivammathur/setup-php@3eda58347216592f618bb1dff277810b6698e4ca # pin@v2 + with: + coverage: none + tools: php-cs-fixer:3.8.0 + + - name: Cache analysis data + id: finishPrepare + uses: actions/cache@c3f1317a9e7b1ef106c153ac8c0f00fed3ddbc0d # pin@v3 + with: + path: ~/.php-cs-fixer + key: coding-style + + - name: Check for JavaScript coding style violations (ESLint) + if: always() && steps.finishPrepare.outcome == 'success' + # Use the --no-fix flag in push builds to get a failed CI status + run: > + npm run lint -- --max-warnings 0 --format stylish + ${{ github.event_name != 'pull_request' && '--no-fix' || '' }} + + - name: Create code suggestions from the coding style changes (on PR only) + if: > + always() && steps.finishPrepare.outcome == 'success' && + github.event_name == 'pull_request' + uses: reviewdog/action-suggester@ab82daa6ea9b84fe43db7747bb10fa087f34e1ab # pin@v1 + with: + tool_name: ESLint + fail_on_error: "true" + + - name: Check for JavaScript coding style violations (Prettier) + if: always() && steps.finishPrepare.outcome == 'success' + # Use the --check flag in push builds to get a failed CI status + run: > + npm run format -- + ${{ github.event_name != 'pull_request' && '--check' || '--write' }} + + - name: Create code suggestions from the coding style changes (on PR only) + if: > + always() && steps.finishPrepare.outcome == 'success' && + github.event_name == 'pull_request' + uses: reviewdog/action-suggester@ab82daa6ea9b84fe43db7747bb10fa087f34e1ab # pin@v1 + with: + tool_name: Prettier + fail_on_error: "true" + + - name: Check for PHP coding style violations + if: always() && steps.finishPrepare.outcome == 'success' + env: + PHP_CS_FIXER_IGNORE_ENV: 1 + # Use the --dry-run flag in push builds to get a failed CI status + run: > + php-cs-fixer fix --diff + ${{ github.event_name != 'pull_request' && '--dry-run' || '' }} + + - name: Create code suggestions from the coding style changes (on PR only) + if: > + always() && steps.finishPrepare.outcome == 'success' && + github.event_name == 'pull_request' + uses: reviewdog/action-suggester@ab82daa6ea9b84fe43db7747bb10fa087f34e1ab # pin@v1 + with: + tool_name: PHP-CS-Fixer + fail_on_error: "true" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8322fd4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +# OS files +.DS_Store + +# Vendor files +/node_modules +/vendor + +# Cache and temporary files +/.cache +/.php-cs-fixer.cache +/.phpunit.result.cache +/tests/coverage +/tests/*/tmp diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php new file mode 100644 index 0000000..87c0710 --- /dev/null +++ b/.php-cs-fixer.dist.php @@ -0,0 +1,58 @@ +exclude('node_modules') + ->in(__DIR__); + +$config = new PhpCsFixer\Config(); +return $config + ->setRules([ + '@PSR12' => true, + 'align_multiline_comment' => ['comment_type' => 'phpdocs_like'], + 'array_indentation' => true, + 'array_syntax' => ['syntax' => 'short'], + 'cast_spaces' => ['space' => 'none'], + 'combine_consecutive_issets' => true, + 'combine_consecutive_unsets' => true, + 'combine_nested_dirname' => true, + 'concat_space' => ['spacing' => 'one'], + 'declare_equal_normalize' => ['space' => 'single'], + 'dir_constant' => true, + 'function_typehint_space' => true, + 'include' => true, + 'logical_operators' => true, + 'lowercase_cast' => true, + 'lowercase_static_reference' => true, + 'magic_constant_casing' => true, + 'magic_method_casing' => true, + 'method_chaining_indentation' => true, + 'modernize_types_casting' => true, + 'multiline_comment_opening_closing' => true, + 'native_function_casing' => true, + 'native_function_type_declaration_casing' => true, + 'new_with_braces' => true, + 'no_blank_lines_after_class_opening' => true, + 'no_blank_lines_after_phpdoc' => true, + 'no_empty_comment' => true, + 'no_empty_phpdoc' => true, + 'no_empty_statement' => true, + 'no_leading_namespace_whitespace' => true, + 'no_mixed_echo_print' => ['use' => 'echo'], + 'no_unneeded_control_parentheses' => true, + 'no_unused_imports' => true, + 'no_useless_return' => true, + 'ordered_class_elements' => ['sort_algorithm' => 'alpha'], + 'ordered_imports' => ['sort_algorithm' => 'alpha'], + 'phpdoc_align' => ['align' => 'left'], + 'phpdoc_indent' => true, + 'phpdoc_scalar' => true, + 'phpdoc_trim' => true, + 'short_scalar_cast' => true, + 'single_line_comment_style' => true, + 'single_quote' => true, + 'ternary_to_null_coalescing' => true, + 'whitespace_after_comma_in_array' => true + ]) + ->setRiskyAllowed(true) + ->setIndent("\t") + ->setFinder($finder); diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..2e85942 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,4 @@ +{ + "trailingComma": "none", + "useTabs": true +} diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..4fd1414 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +# MIT License + +Copyright (c) 2022 Lukas Bestle + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..33ad800 --- /dev/null +++ b/README.md @@ -0,0 +1,51 @@ +# Roomle for Kirby 3 + +[![Kirby 3.7.0+](https://img.shields.io/badge/Kirby-3.7.0%2B-green)](https://getkirby.com) +[![MIT license](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE.md) +[![Release](https://img.shields.io/github/v/release/lukasbestle/kirby-roomle)](https://github.com/lukasbestle/kirby-roomle/releases/latest) +[![CI Status](https://img.shields.io/github/workflow/status/lukasbestle/kirby-roomle/CI?label=CI)](https://github.com/lukasbestle/kirby-roomle/actions?query=workflow%3ACI) +[![Coverage Status](https://img.shields.io/codecov/c/gh/lukasbestle/kirby-roomle?token=IBYEIB22SM)](https://codecov.io/gh/lukasbestle/kirby-roomle) + +> Block to embed the [Roomle 3D Configurator](https://www.roomle.com/en/configurator) into your Kirby site + +![Screenshot of the Roomle block in the Kirby Panel](screenshot.png) + +## Support my work + +> The Roomle plugin is completely free and published under the terms of the MIT license. I do not sell licenses or accept donations, but I'm available for contract work regarding feature development for this plugin. +> ➯ [Read more…](.github/CONTRIBUTING.md#monetary-support) + +## About this plugin + +Roomle Rubens is a 3D product configurator for furniture. Roomle provides the configurator as a hosted web service. + +This Kirby plugin allows you to easily embed the Rubens configurator into your Kirby site. To use the plugin with your own furniture products, you need paid account at [Roomle](https://www.roomle.com/en). + +## Features + +- Customizable variant switcher for different suggested configuration variants +- Automatic handling of the product configuration data for integration in contact forms or shops +- Deeplink generation to allow sharing/bookmarking a specific configuration draft +- Automatic language control based on the site language (only in multilang installations) +- Support for advanced customization of the configurator options + +## Requirements + +- Kirby 3.7.0+ +- PHP 8.0+ + +## Documentation + +The [plugin documentation](https://github.com/lukasbestle/kirby-roomle/wiki) will show you how to set up the plugin initially, how to configure the embedded configurator and how to use the plugin. + +## License + +[The MIT License](LICENSE.md) + +## Contributing & Monetary Support + +See [`CONTRIBUTING.md`](.github/CONTRIBUTING.md). + +## Credits + +- Author and developer: [Lukas Bestle](https://lukasbestle.com) diff --git a/assets/configurator.js b/assets/configurator.js new file mode 100644 index 0000000..df45012 --- /dev/null +++ b/assets/configurator.js @@ -0,0 +1,17 @@ +var m=(t,e,s)=>new Promise((r,i)=>{var o=c=>{try{a(s.next(c))}catch(l){i(l)}},n=c=>{try{a(s.throw(c))}catch(l){i(l)}},a=c=>c.done?r(c.value):Promise.resolve(c.value).then(o,n);a((s=s.apply(t,e)).next())});var y=class{constructor(e,s,r,i){this._outgoingMessageBus=null,this._execMessage=null,this._side=e,this._incomingMessageBus=s,this._outgoingMessageBus=r,this._execMessage=i,this._incomingMessageBus.addEventListener("message",this._handleMessage.bind(this))}setOutgoingMessageBus(e){this._outgoingMessageBus=e}setMessageExecution(e){this._execMessage=e}sendMessage(e,s=[]){return new Promise((r,i)=>{let o=new MessageChannel;o.port1.onmessage=a=>{if(!a||!a.data)return o.port1.close(),o.port2.close(),i(new Error(this._side+" received message but response can not be interpreted"));let c;try{c=JSON.parse(a.data)}catch(l){return o.port1.close(),o.port2.close(),this._prepareError(l),i(l)}c.error?i(c.error):c.result!==void 0?r(c.result):r(),o.port1.close(),o.port2.close()};let n="";try{n=JSON.stringify({message:e,args:s})}catch(a){return i(new Error(this._side+": can not create command because it is not JSON.stringify able"))}if(!this._outgoingMessageBus)return i(new Error(this._side+": outgoing bus not set yet"));this._outgoingMessageBus.postMessage(n,"*",[o.port2])})}_handleMessage(e){let s=e.ports&&Array.isArray(e.ports)&&e.ports.length>0?e.ports[0]:null;if(e.data&&s)try{let r=JSON.parse(e.data);if(!this._execMessage)return s.postMessage(JSON.stringify({error:this._side+" is not ready to handle messages"}));Array.isArray(r.args)||(r.args=[r.args]);let i=this._execMessage(r,e);if(i===void 0)return;i.then((o={})=>{let n,a;typeof o=="object"&&o!==null&&(n=o.error,a=o.result),n?s.postMessage(JSON.stringify({error:n})):a!==void 0?s.postMessage(JSON.stringify({result:a})):s.postMessage(JSON.stringify({result:o}))},o=>{s.postMessage(JSON.stringify({error:this._prepareError(o)}))})}catch(r){s.postMessage(JSON.stringify({error:this._prepareError(r)}))}}_prepareError(e){if(typeof e=="string"){let s=this._side+": "+e;return console.error(s),s}return e.message=this._side+": "+e.message,console.error(e),e.message}},w=".",I={REQUEST_BOOT:"requestBoot",SETUP:"setup",WEBSITE_READY:"websiteReady"},C=(t,e)=>m(void 0,null,function*(){if(typeof t!="string")throw new Error('Configurator ID is not a string type: "'+typeof t+'"');let s=e.customApiUrl?e.customApiUrl:"https://api.roomle.com/v2",r=e.overrideTenant||9,i=s+"/configurators/"+t,o="roomle_portal_v2",n="03-"+window.btoa(new Date().toISOString()+";anonymous;"+o),a=()=>{let h={apiKey:o,currentTenant:r,locale:"en",language:"en",device:1,token:n,platform:"web"};return new Headers(h)},c=new Request(i,{method:"GET",headers:a(),mode:"cors",cache:"default"}),l=yield fetch(c),{configurator:g}=yield l.json();return g}),U=()=>{try{return window.self!==window.top}catch(t){return!0}},D=["127.0.0.1","localhost","0.0.0.0"],F=()=>{let t=U(),e=window.location.href;if(t){if(!document.referrer)return null;e=document.referrer}let{hostname:s}=new URL(e);return s},H=t=>!!(D.includes(t)||t.endsWith("roomle.com")||t.endsWith("gitlab.io")||t.endsWith("gitlab.com")),k=(t,e)=>{let s=JSON.parse(JSON.stringify(t));return b(s,e)},b=(t,e)=>{for(let s in e)try{e[s].constructor===Object?t[s]=b(t[s],e[s]):t[s]=e[s]}catch(r){t[s]=e[s]}return t},A=["language","browserLanguage","userLanguage","systemLanguage"],B=(t=null)=>{let e=window.navigator;if(t)return t.substr(0,2);if(Array.isArray(e.languages)&&e.languages.length>0)return e.languages[0].substr(0,2);for(let s=0,r=A.length;s(O(t),t!=null&&t.customApiUrl&&(t.customApiUrl=decodeURIComponent(t.customApiUrl)),t.shareUrl&&(t.deeplink=t.shareUrl.replace(G,$)),t),O=t=>{if(!t)return;let e=Object.keys(t);for(let s of e){let r=t[s];if(!Array.isArray(r)&&typeof r=="object"&&r!==null)return O(r);if(Array.isArray(r)){for(let i of r)O(i);return}(r==="true"||r==="false")&&(t[s]=r==="true")}},J=(t,e)=>{e.configuratorId=t.id;let s=t.settings||{};return!e.overrideTenant&&t.tenant&&(e.overrideTenant=t.tenant),k(s,e)},V=()=>{let t={};t.locale||(t.locale=B()),t.id===x&&delete t.id;let e=F();return e&&H(e)&&(t.configuratorId="demoConfigurator"),t.customApiUrl="https://www.roomle.com/api/v2",t.emails=!1,t},G="",$="#CONFIGURATIONID#",v=()=>/(android)/i.test(navigator.userAgent),R=(t,e,s)=>{let r=null;Object.defineProperty(t,e,{get(){return r||s},set(i){i!=null&&i.mute?r=i.value:(console.warn("You override Roomle defined behaviour. To disalbe this warning pass in an object with the following properties"),console.warn("{ mute: true, value: () => void }"),r=i)}})},N=()=>window.innerHeight*.01+"px",L=t=>{!t||setTimeout(()=>t.style.setProperty(S,N()),0)},T="rml-styles",q=450,S="--rml-full-height",u={CONTAINER:"rml-container",FILL:"rml-fill",POSITION:"rml-pos",TRANSITION:"rml-transition",ANDROID_HEIGHT:"rml-android-height",OVERFLOW_HIDDEN:"rml-overflow-hidden"},E=new Map,_=class{constructor(e,s,r,i){if(this.ui={callbacks:null},this.extended={callbacks:null},this.analytics={callbacks:{}},this.global={callbacks:{}},this._initData={},!e||typeof e.id!="string")throw new Error("Please provide a correct configuratorId, you get the correct ID from your Roomle Contact Person");if(E.has(s))throw new Error("There is already an instance on this DOM element");if(!!!document.getElementById(T)){let a=r.zIndex||9999999,c=document.createElement("style");c.type="text/css",c.id=T;let l="transition:all ease-in-out "+q+"ms;",g=["-webkit-","-o-"].reduce((f,d)=>f+=d+l,"")+l,h=N();c.innerHTML=` + .${u.CONTAINER}{${S}:${h};} + .${u.POSITION}{position:fixed;top:0;left:0;z-index:${a};opacity:0} + .${u.TRANSITION}{${g}} + .${u.FILL}{width:100%;height:100%;opacity:1} + .${u.ANDROID_HEIGHT}{height:calc(var(${S},1vh)*100)} + .${u.OVERFLOW_HIDDEN}{overflow:hidden} + `,document.head.appendChild(c)}this._onResize=this._onResize.bind(this),v()&&window.addEventListener("resize",this._onResize),this._container=s,this._initData=r,this._configuratorSettings=e;let n=this._createIframe();this._onUseFullPage=this._onUseFullPage.bind(this),this._executeMessage=this._executeMessage.bind(this),this._onBackToWebsite=this._onBackToWebsite.bind(this),this._messageHandler=new y("website",window,null,this._executeMessage),this._waitForIframe=i,this._container.appendChild(n),this._iframe=n,E.set(s,!0)}static createPlanner(e,s,r){return this._create(e,s,r)}static createConfigurator(e,s,r){return this._create(e,s,r)}static create(e,s,r){return this._create(e,s,r)}static createViewer(e,s,r){return this._create(e,s,r)}static _create(e,s,r){return new Promise((i,o)=>m(this,null,function*(){try{let n=b(V(),W(r));n.featureFlags||(n.featureFlags={}),typeof n.featureFlags.realPartList!="boolean"&&(n.featureFlags.realPartList=!0),typeof n.featureFlags.globalCallbacks!="boolean"&&(n.featureFlags.globalCallbacks=!0);let a=yield C(e,n);return r=J(a,n),new this(a,s,r,i)}catch(n){return o(n)}}))}teardown(){this._container&&E.delete(this._container);let e=this._container.querySelector("iframe");e&&this._container.removeChild(e),window.removeEventListener("resize",this._onResize)}_createIframe(){var e;let s=document.createElement("iframe"),r=((e=this._configuratorSettings)===null||e===void 0?void 0:e.url)||"https://www.roomle.com/t/cp/";return this._initData.useLocalRoomle&&(r=location.href.replace("embedding.html","")),location.href.includes("roomle.gitlab.io")&&(r=location.href.replace("embedding.html","index.html")),this._initData.overrideServerUrl&&(r=this._initData.overrideServerUrl),s.src=r,s.classList.add(u.CONTAINER),s.classList.add(u.FILL),s}_onResize(){L(this._iframe)}_onUseFullPage(){this._iframe.classList.add(u.POSITION),document.documentElement.classList.add(u.OVERFLOW_HIDDEN),window.document.body.classList.add(u.OVERFLOW_HIDDEN),v()&&(L(this._iframe),this._iframe.classList.add(u.ANDROID_HEIGHT))}_onBackToWebsite(){this._iframe.classList.remove(u.POSITION),this._iframe.classList.remove(u.ANDROID_HEIGHT),document.documentElement.classList.remove(u.OVERFLOW_HIDDEN),window.document.body.classList.remove(u.OVERFLOW_HIDDEN)}_executeMessage({message:e,args:s},r){var i;if(!r.source||r.source!==((i=this._iframe)===null||i===void 0?void 0:i.contentWindow))return;if(e===I.REQUEST_BOOT)return this._messageHandler.setOutgoingMessageBus(r.source),Promise.resolve({result:this._initData});if(e===I.SETUP){let{methods:l,callbacks:g}=s[0];return l.forEach(h=>{let f=h.split(w),d=f[0],p=f[1];this[d]||(this[d]={}),this[d][p]=function(){return this._messageHandler.sendMessage(h,[...arguments])}.bind(this)}),g.forEach(h=>{let f=h.split(w),d=f[0],p=f[1],P=f[2];this[d]||(this[d]={}),this[d][p]||(this[d][p]={}),this[d][p][P]=()=>{}}),R(this.ui.callbacks,"onUseFullPage",this._onUseFullPage),R(this.ui.callbacks,"onBackToWebsite",this._onBackToWebsite),this._waitForIframe(this),setTimeout(()=>this._messageHandler.sendMessage(I.WEBSITE_READY),0),Promise.resolve({result:null})}let o=e.split(w),n=o[0],a=o[1],c=o.length===3?o[2]:null;if(c&&this[n][a][c]){let l=this[n][a][c](...s);return l instanceof Promise?l.then(g=>({result:g})):l!==void 0?Promise.resolve({result:l}):Promise.resolve({result:null})}return Promise.reject('Message "'+e+'" is unkown')}};var M=class{constructor(t){this.props=t,this.variantSelector="#"+t.htmlId+" .roomle-configurator-variants input",this.currentId=this.idFromUrl()||t.options.id}init(){return m(this,null,function*(){if(this.configurator)return;document.querySelectorAll(this.variantSelector).forEach(e=>{e.addEventListener("change",this.onVariantSelect.bind(this))}),window.addEventListener("popstate",this.onUrlChange.bind(this)),this.updateVariants(this.currentId);let t=this.props.options;t.id=this.currentId,this.configurator=yield _.createConfigurator(this.props.configuratorId,document.getElementById(this.props.htmlId+"-container"),t),this.currentId!==t.id&&this.load(this.currentId),this.props.targetUrl&&(this.configurator.ui.callbacks.onRequestProduct=this.onRequestProduct.bind(this))})}idFromUrl(){return new URLSearchParams(window.location.search).get(this.props.htmlId)}idToUrl(t,e=!1){let s=new URL(window.location.href);return s.searchParams.set(this.props.htmlId,t),e===!0&&(s.hash=this.props.htmlId),s}load(t){return m(this,null,function*(){return this.currentId=t,this.updateVariants(t),this.configurator?(yield this.configurator.ui.loadObject(t),!0):!1})}onRequestProduct(t,e,s,r,i,o){let n={catalog:o.catalog,configuratorUrl:this.idToUrl(t,!0),depth:o.depth,height:o.height,id:t,label:o.label,parts:s.fullList,perspectiveImage:o.perspectiveImage,rootComponentId:o.rootComponentId,topImage:o.topImage,width:o.width},a=document.createElement("form");a.action=this.props.targetUrl,a.method="POST",a.style.display="none";let c=document.createElement("input");c.name="roomle-configuration",c.value=JSON.stringify(n),a.appendChild(c),document.body.appendChild(a),a.submit()}onUrlChange(){let t=this.idFromUrl();t?this.load(t):this.load(this.props.options.id)}onVariantSelect(t){let e=this.idToUrl(t.target.value);window.history.pushState(null,"",e),this.load(t.target.value)}updateVariants(t){let e=document.querySelector(this.variantSelector+"[checked]"),s=document.querySelector(this.variantSelector+'[value="'+CSS.escape(t)+'"]');e&&(e.removeAttribute("checked"),e.checked=!1),s&&(s.checked=!0,s.setAttribute("checked","true"))}};export{M as default}; +/** + * Configurator + * + * @package Kirby Roomle Plugin + * @author Lukas Bestle + * @link https://github.com/lukasbestle/kirby-roomle + * @copyright Lukas Bestle + * @license https://opensource.org/licenses/MIT + */ diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..2b96ab9 --- /dev/null +++ b/composer.json @@ -0,0 +1,54 @@ +{ + "name": "lukasbestle/kirby-roomle", + "description": "Roomle Plugin for Kirby 3", + "license": "MIT", + "type": "kirby-plugin", + "version": "1.0.0-rc.1", + "authors": [ + { + "name": "Lukas Bestle", + "email": "project-kirbyroomle@lukasbestle.com" + } + ], + "require": { + "php": ">=8.0.0 <8.2.0", + "getkirby/cms": ">=3.7.0 <3.8.0", + "getkirby/composer-installer": "^1.1" + }, + "suggest": { + "ext-intl": "Support for locale-aware number formatting" + }, + "autoload-dev": { + "psr-4": { + "LukasBestle\\": "tests/" + } + }, + "config": { + "allow-plugins": { + "getkirby/composer-installer": true + } + }, + "extra": { + "installer-name": "roomle", + "kirby-cms-path": false + }, + "scripts": { + "analyze": [ + "@analyze:composer", + "@analyze:psalm", + "@analyze:phpcpd", + "@analyze:phpmd" + ], + "analyze:composer": "composer validate --strict --no-check-version --no-check-all", + "analyze:phpcpd": "phpcpd --fuzzy --exclude node_modules --exclude tests --exclude vendor .", + "analyze:phpmd": "phpmd . ansi phpmd.xml.dist --exclude 'node_modules/*,stubs/*,tests/*,vendor/*'", + "analyze:psalm": "psalm", + "ci": [ + "@fix", + "@analyze", + "@test" + ], + "fix": "php-cs-fixer fix", + "test": "phpunit --stderr --coverage-html=tests/coverage" + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..143d09a --- /dev/null +++ b/composer.lock @@ -0,0 +1,843 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "2a4fdad8181f27c0b7ec75571eb354d2", + "packages": [ + { + "name": "claviska/simpleimage", + "version": "3.7.0", + "source": { + "type": "git", + "url": "https://github.com/claviska/SimpleImage.git", + "reference": "abd15ced313c7b8041d7d73d8d2398b4f2510cf1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/claviska/SimpleImage/zipball/abd15ced313c7b8041d7d73d8d2398b4f2510cf1", + "reference": "abd15ced313c7b8041d7d73d8d2398b4f2510cf1", + "shasum": "" + }, + "require": { + "ext-gd": "*", + "league/color-extractor": "0.3.*", + "php": ">=5.6.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "claviska": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Cory LaViska", + "homepage": "http://www.abeautifulsite.net/", + "role": "Developer" + } + ], + "description": "A PHP class that makes working with images as simple as possible.", + "support": { + "issues": "https://github.com/claviska/SimpleImage/issues", + "source": "https://github.com/claviska/SimpleImage/tree/3.7.0" + }, + "funding": [ + { + "url": "https://github.com/claviska", + "type": "github" + } + ], + "time": "2022-07-05T13:18:44+00:00" + }, + { + "name": "filp/whoops", + "version": "2.14.5", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.5" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2022-01-07T12:00:00+00:00" + }, + { + "name": "getkirby/cms", + "version": "3.7.5", + "source": { + "type": "git", + "url": "https://github.com/getkirby/kirby.git", + "reference": "021561f7444896fc9917eccb52768a6e715e9a74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/getkirby/kirby/zipball/021561f7444896fc9917eccb52768a6e715e9a74", + "reference": "021561f7444896fc9917eccb52768a6e715e9a74", + "shasum": "" + }, + "require": { + "claviska/simpleimage": "3.7.0", + "ext-ctype": "*", + "ext-curl": "*", + "ext-dom": "*", + "ext-filter": "*", + "ext-hash": "*", + "ext-iconv": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-openssl": "*", + "ext-simplexml": "*", + "filp/whoops": "2.14.5", + "getkirby/composer-installer": "^1.2.1", + "laminas/laminas-escaper": "2.10.0", + "michelf/php-smartypants": "1.8.1", + "php": ">=7.4.0 <8.2.0", + "phpmailer/phpmailer": "6.6.4", + "symfony/polyfill-intl-idn": "1.26.0", + "symfony/polyfill-mbstring": "1.26.0" + }, + "replace": { + "symfony/polyfill-php72": "*" + }, + "suggest": { + "ext-PDO": "Support for using databases", + "ext-apcu": "Support for the Apcu cache driver", + "ext-exif": "Support for exif information from images", + "ext-fileinfo": "Improved mime type detection for files", + "ext-intl": "Improved i18n number formatting", + "ext-memcached": "Support for the Memcached cache driver", + "ext-zip": "Support for ZIP archive file functions", + "ext-zlib": "Sanitization and validation for svgz files" + }, + "type": "kirby-cms", + "extra": { + "unused": [ + "symfony/polyfill-intl-idn" + ] + }, + "autoload": { + "files": [ + "config/setup.php", + "config/helpers.php" + ], + "psr-4": { + "Kirby\\": "src/" + }, + "classmap": [ + "dependencies/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "proprietary" + ], + "authors": [ + { + "name": "Kirby Team", + "email": "support@getkirby.com", + "homepage": "https://getkirby.com" + } + ], + "description": "The Kirby 3 core", + "homepage": "https://getkirby.com", + "keywords": [ + "cms", + "core", + "kirby" + ], + "support": { + "email": "support@getkirby.com", + "forum": "https://forum.getkirby.com", + "issues": "https://github.com/getkirby/kirby/issues", + "source": "https://github.com/getkirby/kirby" + }, + "funding": [ + { + "url": "https://getkirby.com/buy", + "type": "custom" + } + ], + "time": "2022-08-30T18:27:48+00:00" + }, + { + "name": "getkirby/composer-installer", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/getkirby/composer-installer.git", + "reference": "c98ece30bfba45be7ce457e1102d1b169d922f3d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/getkirby/composer-installer/zipball/c98ece30bfba45be7ce457e1102d1b169d922f3d", + "reference": "c98ece30bfba45be7ce457e1102d1b169d922f3d", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0" + }, + "require-dev": { + "composer/composer": "^1.8 || ^2.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Kirby\\ComposerInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "Kirby\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Kirby's custom Composer installer for the Kirby CMS and for Kirby plugins", + "homepage": "https://getkirby.com", + "support": { + "issues": "https://github.com/getkirby/composer-installer/issues", + "source": "https://github.com/getkirby/composer-installer/tree/1.2.1" + }, + "funding": [ + { + "url": "https://getkirby.com/buy", + "type": "custom" + } + ], + "time": "2020-12-28T12:54:39+00:00" + }, + { + "name": "laminas/laminas-escaper", + "version": "2.10.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-escaper.git", + "reference": "58af67282db37d24e584a837a94ee55b9c7552be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-escaper/zipball/58af67282db37d24e584a837a94ee55b9c7552be", + "reference": "58af67282db37d24e584a837a94ee55b9c7552be", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-mbstring": "*", + "php": "^7.4 || ~8.0.0 || ~8.1.0" + }, + "conflict": { + "zendframework/zend-escaper": "*" + }, + "require-dev": { + "infection/infection": "^0.26.6", + "laminas/laminas-coding-standard": "~2.3.0", + "maglnet/composer-require-checker": "^3.8.0", + "phpunit/phpunit": "^9.5.18", + "psalm/plugin-phpunit": "^0.16.1", + "vimeo/psalm": "^4.22.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Laminas\\Escaper\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Securely and safely escape HTML, HTML attributes, JavaScript, CSS, and URLs", + "homepage": "https://laminas.dev", + "keywords": [ + "escaper", + "laminas" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-escaper/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-escaper/issues", + "rss": "https://github.com/laminas/laminas-escaper/releases.atom", + "source": "https://github.com/laminas/laminas-escaper" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2022-03-08T20:15:36+00:00" + }, + { + "name": "league/color-extractor", + "version": "0.3.2", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/color-extractor.git", + "reference": "837086ec60f50c84c611c613963e4ad2e2aec806" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/color-extractor/zipball/837086ec60f50c84c611c613963e4ad2e2aec806", + "reference": "837086ec60f50c84c611c613963e4ad2e2aec806", + "shasum": "" + }, + "require": { + "ext-gd": "*", + "php": ">=5.4.0" + }, + "replace": { + "matthecat/colorextractor": "*" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~2", + "phpunit/phpunit": "~5" + }, + "type": "library", + "autoload": { + "psr-4": { + "": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mathieu Lechat", + "email": "math.lechat@gmail.com", + "homepage": "http://matthecat.com", + "role": "Developer" + } + ], + "description": "Extract colors from an image as a human would do.", + "homepage": "https://github.com/thephpleague/color-extractor", + "keywords": [ + "color", + "extract", + "human", + "image", + "palette" + ], + "support": { + "issues": "https://github.com/thephpleague/color-extractor/issues", + "source": "https://github.com/thephpleague/color-extractor/tree/master" + }, + "time": "2016-12-15T09:30:02+00:00" + }, + { + "name": "michelf/php-smartypants", + "version": "1.8.1", + "source": { + "type": "git", + "url": "https://github.com/michelf/php-smartypants.git", + "reference": "47d17c90a4dfd0ccf1f87e25c65e6c8012415aad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/michelf/php-smartypants/zipball/47d17c90a4dfd0ccf1f87e25c65e6c8012415aad", + "reference": "47d17c90a4dfd0ccf1f87e25c65e6c8012415aad", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Michelf": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Michel Fortin", + "email": "michel.fortin@michelf.ca", + "homepage": "https://michelf.ca/", + "role": "Developer" + }, + { + "name": "John Gruber", + "homepage": "https://daringfireball.net/" + } + ], + "description": "PHP SmartyPants", + "homepage": "https://michelf.ca/projects/php-smartypants/", + "keywords": [ + "dashes", + "quotes", + "spaces", + "typographer", + "typography" + ], + "support": { + "issues": "https://github.com/michelf/php-smartypants/issues", + "source": "https://github.com/michelf/php-smartypants/tree/1.8.1" + }, + "time": "2016-12-13T01:01:17+00:00" + }, + { + "name": "phpmailer/phpmailer", + "version": "v6.6.4", + "source": { + "type": "git", + "url": "https://github.com/PHPMailer/PHPMailer.git", + "reference": "a94fdebaea6bd17f51be0c2373ab80d3d681269b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/a94fdebaea6bd17f51be0c2373ab80d3d681269b", + "reference": "a94fdebaea6bd17f51be0c2373ab80d3d681269b", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-filter": "*", + "ext-hash": "*", + "php": ">=5.5.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", + "doctrine/annotations": "^1.2", + "php-parallel-lint/php-console-highlighter": "^1.0.0", + "php-parallel-lint/php-parallel-lint": "^1.3.2", + "phpcompatibility/php-compatibility": "^9.3.5", + "roave/security-advisories": "dev-latest", + "squizlabs/php_codesniffer": "^3.6.2", + "yoast/phpunit-polyfills": "^1.0.0" + }, + "suggest": { + "ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses", + "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication", + "league/oauth2-google": "Needed for Google XOAUTH2 authentication", + "psr/log": "For optional PSR-3 debug logging", + "stevenmaguire/oauth2-microsoft": "Needed for Microsoft XOAUTH2 authentication", + "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPMailer\\PHPMailer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-only" + ], + "authors": [ + { + "name": "Marcus Bointon", + "email": "phpmailer@synchromedia.co.uk" + }, + { + "name": "Jim Jagielski", + "email": "jimjag@gmail.com" + }, + { + "name": "Andy Prevost", + "email": "codeworxtech@users.sourceforge.net" + }, + { + "name": "Brent R. Matzelle" + } + ], + "description": "PHPMailer is a full-featured email creation and transfer class for PHP", + "support": { + "issues": "https://github.com/PHPMailer/PHPMailer/issues", + "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.6.4" + }, + "funding": [ + { + "url": "https://github.com/Synchro", + "type": "github" + } + ], + "time": "2022-08-22T09:22:00+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + }, + { + "name": "symfony/polyfill-intl-idn", + "version": "v1.26.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "59a8d271f00dd0e4c2e518104cc7963f655a1aa8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/59a8d271f00dd0e4c2e518104cc7963f655a1aa8", + "reference": "59a8d271f00dd0e4c2e518104cc7963f655a1aa8", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "symfony/polyfill-intl-normalizer": "^1.10", + "symfony/polyfill-php72": "^1.10" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Idn\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Laurent Bassin", + "email": "laurent@bassin.info" + }, + { + "name": "Trevor Rowbotham", + "email": "trevor.rowbotham@pm.me" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "idn", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.26.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-24T11:49:31+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.26.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "219aa369ceff116e673852dce47c3a41794c14bd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/219aa369ceff116e673852dce47c3a41794c14bd", + "reference": "219aa369ceff116e673852dce47c3a41794c14bd", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.26.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-24T11:49:31+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.26.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", + "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.26.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-24T11:49:31+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=8.0.0 <8.2.0" + }, + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/etc/psalm-plugins/HelperFunctionUsePlugin.php b/etc/psalm-plugins/HelperFunctionUsePlugin.php new file mode 100644 index 0000000..7d69b74 --- /dev/null +++ b/etc/psalm-plugins/HelperFunctionUsePlugin.php @@ -0,0 +1,46 @@ +getExpr(); + if ($expr->name instanceof Expr) { + return; + } + + // find where the called function was defined + $functions = $event->getCodebase()->functions; + $statementsSource = $event->getStatementsSource(); + $storage = $functions->getStorage( + $statementsSource instanceof StatementsAnalyzer ?: null, + $event->getFunctionId() + ); + + // if the function is a Kirby helper, consider this function call an issue + if ($storage->location->file_path === dirname(__FILE__, 3) . '/vendor/getkirby/cms/config/helpers.php') { + IssueBuffer::accepts( + new HelperFunctionUse( + 'Use of user-overridable Kirby helper "' . $storage->cased_name . '"', + new CodeLocation($statementsSource, $expr->name) + ), + $statementsSource->getSuppressedIssues() + ); + } + } +} + +class HelperFunctionUse extends PluginIssue +{ +} diff --git a/index.css b/index.css new file mode 100644 index 0000000..b51dd55 --- /dev/null +++ b/index.css @@ -0,0 +1 @@ +.k-block-type-roomle-configurator-wrapper,.k-block-type-roomle-configurator-variants{display:grid;grid-template-columns:1fr 1fr;grid-gap:1rem}.k-block-type-roomle-configurator-wrapper{align-items:start}.k-block-type-roomle-configurator-variant{display:flex;flex-direction:column;justify-content:space-between}.k-block-type-roomle-configurator-main img,.k-block-type-roomle-configurator-variant img{width:100%}.k-block-type-roomle-configurator-main .k-empty{height:100%}.k-block-type-roomle-configurator-labels{margin-top:.5rem}.k-block-type-roomle-configurator-labels *{display:block}@media screen and (max-width: 40rem){.k-block-type-roomle-configurator-wrapper{display:block}} diff --git a/index.js b/index.js new file mode 100644 index 0000000..44d225e --- /dev/null +++ b/index.js @@ -0,0 +1,3 @@ +(function(){"use strict";const f=` + +`,w="";function l(t,e,r,s,i,c,u,I){var o=typeof t=="function"?t.options:t;e&&(o.render=e,o.staticRenderFns=r,o._compiled=!0),s&&(o.functional=!0),c&&(o._scopeId="data-v-"+c);var a;if(u?(a=function(n){n=n||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext,!n&&typeof __VUE_SSR_CONTEXT__<"u"&&(n=__VUE_SSR_CONTEXT__),i&&i.call(this,n),n&&n._registeredComponents&&n._registeredComponents.add(u)},o._ssrRegister=a):i&&(a=I?function(){i.call(this,(o.functional?this.parent:this).$root.$options.shadowRoot)}:i),a)if(o.functional){o._injectStyles=a;var T=o.render;o.render=function(U,m){return a.call(m),T(U,m)}}else{var _=o.beforeCreate;o.beforeCreate=_?[].concat(_,a):[a]}return{exports:t,options:o}}const p={computed:{mainUrl(){return this.idToImageUrl(this.content.mainproductid)}},methods:{idToImageUrl(t){return t?t.split(":").length>2?"https://uploads.roomle.com/configurations/"+t+"/perspectiveImage.png":"https://www.roomle.com/api/v2/items/"+t+"/perspectiveImageHD":null},variantToImageUrl(t){return t.image[0]?t.image[0].url:t.productid?this.idToImageUrl(t.productid):null}}};var d=function(){var e=this,r=e._self._c;return r("div",{staticClass:"k-block-type-roomle-configurator-wrapper",on:{dblclick:function(s){return e.$emit("open")}}},[r("k-aspect-ratio",{staticClass:"k-block-type-roomle-configurator-main",attrs:{ratio:"1/1"}},[r("lbro-lazy-image",{attrs:{src:e.mainUrl}},[r("k-empty",{attrs:{icon:"roomle",layout:"cardlets",text:e.$t("roomle.empty")}})],1)],1),r("ul",{staticClass:"k-block-type-roomle-configurator-variants"},e._l(e.content.variants,function(s,i){return r("li",{key:i,staticClass:"k-block-type-roomle-configurator-variant"},[r("lbro-lazy-image",{attrs:{src:e.variantToImageUrl(s)}},[r("k-empty",{attrs:{icon:"image",layout:"cardlets",text:e.$t("roomle.noRendering")}})],1),r("span",{staticClass:"k-block-type-roomle-configurator-labels"},[r("strong",[e._v(e._s(s.title))]),r("span",[e._v(e._s(s.subtitle))])])],1)}),0)],1)},v=[],g=l(p,d,v,!1,null,null,null,null);const h=g.exports,b={props:{alt:{type:String,default:""},src:String},data(){return{visible:!0}}};var y=function(){var e=this,r=e._self._c;return r("span",[e.src?r("img",{directives:[{name:"show",rawName:"v-show",value:e.visible,expression:"visible"}],attrs:{alt:e.alt,src:e.src},on:{error:function(s){e.visible=!1},load:function(s){e.visible=!0}}}):e._e(),!e.src||!e.visible?e._t("default"):e._e()],2)},k=[],C=l(b,y,k,!1,null,null,null,null);const $=C.exports;panel.plugin("lukasbestle/roomle",{blocks:{"roomle-configurator":h},components:{"lbro-lazy-image":$},icons:{roomle:f}})})(); diff --git a/index.php b/index.php new file mode 100644 index 0000000..e3c8759 --- /dev/null +++ b/index.php @@ -0,0 +1,63 @@ + + * @link https://github.com/lukasbestle/kirby-roomle + * @copyright Lukas Bestle + * @license https://opensource.org/licenses/MIT + */ + +// validate the Kirby version; the supported versions are +// updated manually when verified to work with the plugin +$kirbyVersion = App::version(); +if ( + $kirbyVersion !== null && + ( + version_compare($kirbyVersion, '3.7.0', '<') === true || + version_compare($kirbyVersion, '3.8.0-alpha', '>=') === true + ) +) { + throw new Exception( + 'The installed version of the Kirby Roomle plugin ' . + 'is not compatible with Kirby ' . $kirbyVersion + ); +} + +// autoload classes +F::loadClasses([ + 'LukasBestle\Roomle\Configuration' => __DIR__ . '/src/classes/Configuration.php', + 'LukasBestle\Roomle\ConfiguratorBlock' => __DIR__ . '/src/classes/ConfiguratorBlock.php', + 'LukasBestle\Roomle\ConfiguratorVariant' => __DIR__ . '/src/classes/ConfiguratorVariant.php', + 'LukasBestle\Roomle\Parameter' => __DIR__ . '/src/classes/Parameter.php', + 'LukasBestle\Roomle\Parameters' => __DIR__ . '/src/classes/Parameters.php', + 'LukasBestle\Roomle\Part' => __DIR__ . '/src/classes/Part.php', +]); + +// register the plugin +App::plugin('lukasbestle/roomle', [ + 'blockModels' => require __DIR__ . '/src/config/blockModels.php', + 'blueprints' => require __DIR__ . '/src/config/blueprints.php', + 'options' => require __DIR__ . '/src/config/options.php', + 'snippets' => require __DIR__ . '/src/config/snippets.php', + 'translations' => require __DIR__ . '/src/config/translations.php', +]); + +/** + * Returns the object for a product configuration + * + * @param array|string|null $data `null` to get the data from the request (`roomle-configuration` param) + */ +function roomleConfiguration(array|string|null $data = null): Configuration|null +{ + return Configuration::lazyInstance($data); +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..87e8998 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,4206 @@ +{ + "name": "roomle", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "dependencies": { + "@roomle/embedding-lib": "^4.41.0" + }, + "devDependencies": { + "esbuild": "^0.15.6", + "eslint": "^8.16.0", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-vue": "^9.0.1", + "kirbyup": "^2.0.0", + "prettier": "^2.6.2" + } + }, + "node_modules/@antfu/utils": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-0.5.2.tgz", + "integrity": "sha512-CQkeV+oJxUazwjlHD0/3ZD08QWKuGQkhnrKo3e6ly5pd48VUpXbb77q0xMU4+vc2CkJnDS02Eq/M9ugyX20XZA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@babel/parser": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.0.tgz", + "integrity": "sha512-74bEXKX2h+8rrfQUfsBfuZZHzsEs6Eql4pqy/T4Nn6Y9wNPggQOqD6z6pn5Bl8ZfysKouFZT/UXEH94ummEeQw==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.7.tgz", + "integrity": "sha512-IKznSJOsVUuyt7cDzzSZyqBEcZe+7WlBqTVXiF1OXP/4Nm387ToaXZ0fyLwI1iBlI/bzpxVq411QE2/Bt2XWWw==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.1.tgz", + "integrity": "sha512-OhSY22oQQdw3zgPOOwdoj01l/Dzl1Z+xyUP33tkSN+aqyEhymJCcPHyXt+ylW8FSe0TfRC2VG+ROQOapD0aZSQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.4.0", + "globals": "^13.15.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.4.tgz", + "integrity": "sha512-mXAIHxZT3Vcpg83opl1wGlVZ9xydbfZO3r5YfRSH6Gpp2J/PfdBP0wbDa2sO6/qRbcalpoevVyW6A/fI6LfeMw==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/gitignore-to-minimatch": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/gitignore-to-minimatch/-/gitignore-to-minimatch-1.0.2.tgz", + "integrity": "sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@roomle/embedding-lib": { + "version": "4.42.0", + "resolved": "https://registry.npmjs.org/@roomle/embedding-lib/-/embedding-lib-4.42.0.tgz", + "integrity": "sha512-jnYIwJjKrqZwqLFzo6BgEmZ/r/cZ8XTKyqsU8tH3t1XuMI4IwfHn/RfoeBZPJv29pDuEc6clMNk9DWG3KE7GCg==", + "dependencies": { + "@roomle/web-sdk": "2.38.0" + } + }, + "node_modules/@roomle/web-sdk": { + "version": "2.38.0", + "resolved": "https://registry.npmjs.org/@roomle/web-sdk/-/web-sdk-2.38.0.tgz", + "integrity": "sha512-1NQOjarzfq1DzmpidyjGRiMmJZSQ4UgHLQSSkVI8dierzx6A70NKo28+tHhFCuM1ZvVK3UeDhRIk02xC0NtoHw==", + "hasInstallScript": true + }, + "node_modules/@vue/compiler-sfc": { + "version": "2.7.10", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-2.7.10.tgz", + "integrity": "sha512-55Shns6WPxlYsz4WX7q9ZJBL77sKE1ZAYNYStLs6GbhIOMrNtjMvzcob6gu3cGlfpCR4bT7NXgyJ3tly2+Hx8Q==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.18.4", + "postcss": "^8.4.14", + "source-map": "^0.6.1" + } + }, + "node_modules/acorn": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", + "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/consola": { + "version": "2.15.3", + "resolved": "https://registry.npmjs.org/consola/-/consola-2.15.3.tgz", + "integrity": "sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz", + "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==", + "dev": true + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/defu": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.0.tgz", + "integrity": "sha512-pOFYRTIhoKujrmbTRhcW5lYQLBXw/dlTwfI8IguF1QCDJOcJzNH1w+YFjxqy6BAuJrClTy6MUE8q+oKJ2FLsIw==", + "dev": true + }, + "node_modules/detect-package-manager": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/detect-package-manager/-/detect-package-manager-2.0.1.tgz", + "integrity": "sha512-j/lJHyoLlWi6G1LDdLgvUtz60Zo5GEj+sVYtTVXnYLDPuzgC3llMxonXym9zIwhhUII8vjdw0LXxavpLqTbl1A==", + "dev": true, + "dependencies": { + "execa": "^5.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/esbuild": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.7.tgz", + "integrity": "sha512-7V8tzllIbAQV1M4QoE52ImKu8hT/NLGlGXkiDsbEU5PS6K8Mn09ZnYoS+dcmHxOS9CRsV4IRAMdT3I67IyUNXw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/linux-loong64": "0.15.7", + "esbuild-android-64": "0.15.7", + "esbuild-android-arm64": "0.15.7", + "esbuild-darwin-64": "0.15.7", + "esbuild-darwin-arm64": "0.15.7", + "esbuild-freebsd-64": "0.15.7", + "esbuild-freebsd-arm64": "0.15.7", + "esbuild-linux-32": "0.15.7", + "esbuild-linux-64": "0.15.7", + "esbuild-linux-arm": "0.15.7", + "esbuild-linux-arm64": "0.15.7", + "esbuild-linux-mips64le": "0.15.7", + "esbuild-linux-ppc64le": "0.15.7", + "esbuild-linux-riscv64": "0.15.7", + "esbuild-linux-s390x": "0.15.7", + "esbuild-netbsd-64": "0.15.7", + "esbuild-openbsd-64": "0.15.7", + "esbuild-sunos-64": "0.15.7", + "esbuild-windows-32": "0.15.7", + "esbuild-windows-64": "0.15.7", + "esbuild-windows-arm64": "0.15.7" + } + }, + "node_modules/esbuild-android-64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.7.tgz", + "integrity": "sha512-p7rCvdsldhxQr3YHxptf1Jcd86dlhvc3EQmQJaZzzuAxefO9PvcI0GLOa5nCWem1AJ8iMRu9w0r5TG8pHmbi9w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-android-arm64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.7.tgz", + "integrity": "sha512-L775l9ynJT7rVqRM5vo+9w5g2ysbOCfsdLV4CWanTZ1k/9Jb3IYlQ06VCI1edhcosTYJRECQFJa3eAvkx72eyQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-darwin-64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.7.tgz", + "integrity": "sha512-KGPt3r1c9ww009t2xLB6Vk0YyNOXh7hbjZ3EecHoVDxgtbUlYstMPDaReimKe6eOEfyY4hBEEeTvKwPsiH5WZg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-darwin-arm64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.7.tgz", + "integrity": "sha512-kBIHvtVqbSGajN88lYMnR3aIleH3ABZLLFLxwL2stiuIGAjGlQW741NxVTpUHQXUmPzxi6POqc9npkXa8AcSZQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-freebsd-64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.7.tgz", + "integrity": "sha512-hESZB91qDLV5MEwNxzMxPfbjAhOmtfsr9Wnuci7pY6TtEh4UDuevmGmkUIjX/b+e/k4tcNBMf7SRQ2mdNuK/HQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-freebsd-arm64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.7.tgz", + "integrity": "sha512-dLFR0ChH5t+b3J8w0fVKGvtwSLWCv7GYT2Y2jFGulF1L5HftQLzVGN+6pi1SivuiVSmTh28FwUhi9PwQicXI6Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-32": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.7.tgz", + "integrity": "sha512-v3gT/LsONGUZcjbt2swrMjwxo32NJzk+7sAgtxhGx1+ZmOFaTRXBAi1PPfgpeo/J//Un2jIKm/I+qqeo4caJvg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.7.tgz", + "integrity": "sha512-LxXEfLAKwOVmm1yecpMmWERBshl+Kv5YJ/1KnyAr6HRHFW8cxOEsEfisD3sVl/RvHyW//lhYUVSuy9jGEfIRAQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-arm": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.7.tgz", + "integrity": "sha512-JKgAHtMR5f75wJTeuNQbyznZZa+pjiUHV7sRZp42UNdyXC6TiUYMW/8z8yIBAr2Fpad8hM1royZKQisqPABPvQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-arm64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.7.tgz", + "integrity": "sha512-P3cfhudpzWDkglutWgXcT2S7Ft7o2e3YDMrP1n0z2dlbUZghUkKCyaWw0zhp4KxEEzt/E7lmrtRu/pGWnwb9vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-mips64le": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.7.tgz", + "integrity": "sha512-T7XKuxl0VpeFLCJXub6U+iybiqh0kM/bWOTb4qcPyDDwNVhLUiPcGdG2/0S7F93czUZOKP57YiLV8YQewgLHKw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-ppc64le": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.7.tgz", + "integrity": "sha512-6mGuC19WpFN7NYbecMIJjeQgvDb5aMuvyk0PDYBJrqAEMkTwg3Z98kEKuCm6THHRnrgsdr7bp4SruSAxEM4eJw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-riscv64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.7.tgz", + "integrity": "sha512-uUJsezbswAYo/X7OU/P+PuL/EI9WzxsEQXDekfwpQ23uGiooxqoLFAPmXPcRAt941vjlY9jtITEEikWMBr+F/g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-s390x": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.7.tgz", + "integrity": "sha512-+tO+xOyTNMc34rXlSxK7aCwJgvQyffqEM5MMdNDEeMU3ss0S6wKvbBOQfgd5jRPblfwJ6b+bKiz0g5nABpY0QQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-netbsd-64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.7.tgz", + "integrity": "sha512-yVc4Wz+Pu3cP5hzm5kIygNPrjar/v5WCSoRmIjCPWfBVJkZNb5brEGKUlf+0Y759D48BCWa0WHrWXaNy0DULTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-openbsd-64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.7.tgz", + "integrity": "sha512-GsimbwC4FSR4lN3wf8XmTQ+r8/0YSQo21rWDL0XFFhLHKlzEA4SsT1Tl8bPYu00IU6UWSJ+b3fG/8SB69rcuEQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-sunos-64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.7.tgz", + "integrity": "sha512-8CDI1aL/ts0mDGbWzjEOGKXnU7p3rDzggHSBtVryQzkSOsjCHRVe0iFYUuhczlxU1R3LN/E7HgUO4NXzGGP/Ag==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-32": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.7.tgz", + "integrity": "sha512-cOnKXUEPS8EGCzRSFa1x6NQjGhGsFlVgjhqGEbLTPsA7x4RRYiy2RKoArNUU4iR2vHmzqS5Gr84MEumO/wxYKA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.7.tgz", + "integrity": "sha512-7MI08Ec2sTIDv+zH6StNBKO+2hGUYIT42GmFyW6MBBWWtJhTcQLinKS6ldIN1d52MXIbiJ6nXyCJ+LpL4jBm3Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-arm64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.7.tgz", + "integrity": "sha512-R06nmqBlWjKHddhRJYlqDd3Fabx9LFdKcjoOy08YLimwmsswlFBJV4rXzZCxz/b7ZJXvrZgj8DDv1ewE9+StMw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.23.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.23.0.tgz", + "integrity": "sha512-pBG/XOn0MsJcKcTRLr27S5HpzQo4kLr+HjLQIyK4EiCsijDl/TB+h5uEuJU6bQ8Edvwz1XWOjpaP2qgnXGpTcA==", + "dev": true, + "dependencies": { + "@eslint/eslintrc": "^1.3.1", + "@humanwhocodes/config-array": "^0.10.4", + "@humanwhocodes/gitignore-to-minimatch": "^1.0.2", + "@humanwhocodes/module-importer": "^1.0.1", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.4.0", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^6.0.1", + "globals": "^13.15.0", + "globby": "^11.1.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-prettier": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz", + "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-vue": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.4.0.tgz", + "integrity": "sha512-Nzz2QIJ8FG+rtJaqT/7/ru5ie2XgT9KCudkbN0y3uFYhQ41nuHEaboLAiqwMcK006hZPQv/rVMRhUIwEGhIvfQ==", + "dev": true, + "dependencies": { + "eslint-utils": "^3.0.0", + "natural-compare": "^1.4.0", + "nth-check": "^2.0.1", + "postcss-selector-parser": "^6.0.9", + "semver": "^7.3.5", + "vue-eslint-parser": "^9.0.1", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/espree": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.0.tgz", + "integrity": "sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==", + "dev": true, + "dependencies": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", + "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "dev": true + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "13.17.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", + "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/immutable": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.1.0.tgz", + "integrity": "sha512-oNkuqVTA8jqG1Q6c+UglTOD1xhC1BtjKI7XkCXRkZHrN5m18/XsnUp8Q89GkQO/z+0WjonSvl0FLhDYftp46nQ==", + "dev": true + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", + "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/jiti": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.14.0.tgz", + "integrity": "sha512-4IwstlaKQc9vCTC+qUXLM1hajy2ImiL9KnLvVYiaHOtS/v3wRjhLlGl121AmgDgx/O43uKmxownJghS5XMya2A==", + "dev": true, + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/kirbyup": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/kirbyup/-/kirbyup-2.0.1.tgz", + "integrity": "sha512-Yhjakz09gIqTUKZHK4EmU+sYK5H0aV//+WYThwzIWPTSzvS5iKT319wpR2VEREfdBTW8SGDdYgU1Tu73h7a9fA==", + "dev": true, + "dependencies": { + "@vue/compiler-sfc": "^2.7.10", + "cac": "^6.7.12", + "chokidar": "^3.5.3", + "consola": "^2.15.3", + "detect-package-manager": "^2.0.1", + "magic-string": "^0.26.2", + "pathe": "^0.3.5", + "perfect-debounce": "^0.1.3", + "picocolors": "^1.0.0", + "postcss": "^8.4.16", + "postcss-dir-pseudo-class": "^6.0.5", + "postcss-load-config": "^4.0.1", + "postcss-logical": "^5.0.4", + "sass": "^1.54.5", + "unconfig": "^0.3.5", + "vite": "^3.0.9", + "vite-plugin-full-reload": "^1.0.4", + "vue": "^2.7.10" + }, + "bin": { + "kirbyup": "cli.mjs" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lilconfig": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.6.tgz", + "integrity": "sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/magic-string": { + "version": "0.26.3", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.26.3.tgz", + "integrity": "sha512-u1Po0NDyFcwdg2nzHT88wSK0+Rih0N1M+Ph1Sp08k8yvFFU3KR72wryS7e1qMPJypt99WB7fIFVCA92mQrMjrg==", + "dev": true, + "dependencies": { + "sourcemap-codec": "^1.4.8" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "dev": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-0.3.5.tgz", + "integrity": "sha512-grU/QeYP0ChuE5kjU2/k8VtAeODzbernHlue0gTa27+ayGIu3wqYBIPGfP9r5xSqgCgDd4nWrjKXEfxMillByg==", + "dev": true + }, + "node_modules/perfect-debounce": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-0.1.3.tgz", + "integrity": "sha512-NOT9AcKiDGpnV/HBhI22Str++XWcErO/bALvHCuhv33owZW/CjH8KAFLZDCmu3727sihe0wTxpDhyGc6M8qacQ==", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.4.16", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.16.tgz", + "integrity": "sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + } + ], + "dependencies": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-dir-pseudo-class": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-6.0.5.tgz", + "integrity": "sha512-eqn4m70P031PF7ZQIvSgy9RSJ5uI2171O/OO/zcRNYpJbvaeKFUlar1aJ7rmgiQtbm0FSPsRewjpdS0Oew7MPA==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-load-config": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.1.tgz", + "integrity": "sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==", + "dev": true, + "dependencies": { + "lilconfig": "^2.0.5", + "yaml": "^2.1.1" + }, + "engines": { + "node": ">= 14" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-logical": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-5.0.4.tgz", + "integrity": "sha512-RHXxplCeLh9VjinvMrZONq7im4wjWGlRJAqmAVLXyZaXwfDWP73/oq4NdIp+OZwhQUMj0zjqDfM5Fj7qby+B4g==", + "dev": true, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", + "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "2.78.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.78.1.tgz", + "integrity": "sha512-VeeCgtGi4P+o9hIg+xz4qQpRl6R401LWEXBmxYKOV4zlF82lyhgh2hTZnheFUbANE8l2A41F458iwj2vEYaXJg==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=10.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/sass": { + "version": "1.54.8", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.54.8.tgz", + "integrity": "sha512-ib4JhLRRgbg6QVy6bsv5uJxnJMTS2soVcCp9Y88Extyy13A8vV0G1fAwujOzmNkFQbR3LvedudAMbtuNRPbQww==", + "dev": true, + "dependencies": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "dev": true + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unconfig": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/unconfig/-/unconfig-0.3.6.tgz", + "integrity": "sha512-JWefWyjLrDAbzs30sFkzcE9YpvAhN9+UPMZBwnNUmaY9X7QhI+wCGP4hoEWfZDzvkP+WIaZDPcMUJjarpxFvKg==", + "dev": true, + "dependencies": { + "@antfu/utils": "^0.5.2", + "defu": "^6.1.0", + "jiti": "^1.14.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/vite": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-3.1.0.tgz", + "integrity": "sha512-YBg3dUicDpDWFCGttmvMbVyS9ydjntwEjwXRj2KBFwSB8SxmGcudo1yb8FW5+M/G86aS8x828ujnzUVdsLjs9g==", + "dev": true, + "dependencies": { + "esbuild": "^0.15.6", + "postcss": "^8.4.16", + "resolve": "^1.22.1", + "rollup": "~2.78.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "less": "*", + "sass": "*", + "stylus": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-plugin-full-reload": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/vite-plugin-full-reload/-/vite-plugin-full-reload-1.0.4.tgz", + "integrity": "sha512-9WejQII6zJ++m/YE173Zvl2jq4cqa404KNrVT+JDzDnqaGRq5UvOvA48fnsSWPIMXFV7S0dq5+sZqcSB+tKBgA==", + "dev": true, + "dependencies": { + "picocolors": "^1.0.0", + "picomatch": "^2.3.1" + }, + "peerDependencies": { + "vite": "^2 || ^3" + } + }, + "node_modules/vue": { + "version": "2.7.10", + "resolved": "https://registry.npmjs.org/vue/-/vue-2.7.10.tgz", + "integrity": "sha512-HmFC70qarSHPXcKtW8U8fgIkF6JGvjEmDiVInTkKZP0gIlEPhlVlcJJLkdGIDiNkIeA2zJPQTWJUI4iWe+AVfg==", + "dev": true, + "dependencies": { + "@vue/compiler-sfc": "2.7.10", + "csstype": "^3.1.0" + } + }, + "node_modules/vue-eslint-parser": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.0.3.tgz", + "integrity": "sha512-yL+ZDb+9T0ELG4VIFo/2anAOz8SvBdlqEnQnvJ3M7Scq56DvtjY0VY88bByRZB0D4J0u8olBcfrXTVONXsh4og==", + "dev": true, + "dependencies": { + "debug": "^4.3.4", + "eslint-scope": "^7.1.1", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.3.1", + "esquery": "^1.4.0", + "lodash": "^4.17.21", + "semver": "^7.3.6" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yaml": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.1.1.tgz", + "integrity": "sha512-o96x3OPo8GjWeSLF+wOAbrPfhFOGY0W00GNaxCDv+9hkcDJEnev1yh8S7pgHF0ik6zc8sQLuL8hjHjJULZp8bw==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + }, + "dependencies": { + "@antfu/utils": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-0.5.2.tgz", + "integrity": "sha512-CQkeV+oJxUazwjlHD0/3ZD08QWKuGQkhnrKo3e6ly5pd48VUpXbb77q0xMU4+vc2CkJnDS02Eq/M9ugyX20XZA==", + "dev": true + }, + "@babel/parser": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.0.tgz", + "integrity": "sha512-74bEXKX2h+8rrfQUfsBfuZZHzsEs6Eql4pqy/T4Nn6Y9wNPggQOqD6z6pn5Bl8ZfysKouFZT/UXEH94ummEeQw==", + "dev": true + }, + "@esbuild/linux-loong64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.7.tgz", + "integrity": "sha512-IKznSJOsVUuyt7cDzzSZyqBEcZe+7WlBqTVXiF1OXP/4Nm387ToaXZ0fyLwI1iBlI/bzpxVq411QE2/Bt2XWWw==", + "dev": true, + "optional": true + }, + "@eslint/eslintrc": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.1.tgz", + "integrity": "sha512-OhSY22oQQdw3zgPOOwdoj01l/Dzl1Z+xyUP33tkSN+aqyEhymJCcPHyXt+ylW8FSe0TfRC2VG+ROQOapD0aZSQ==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.4.0", + "globals": "^13.15.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + } + }, + "@humanwhocodes/config-array": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.4.tgz", + "integrity": "sha512-mXAIHxZT3Vcpg83opl1wGlVZ9xydbfZO3r5YfRSH6Gpp2J/PfdBP0wbDa2sO6/qRbcalpoevVyW6A/fI6LfeMw==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + } + }, + "@humanwhocodes/gitignore-to-minimatch": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/gitignore-to-minimatch/-/gitignore-to-minimatch-1.0.2.tgz", + "integrity": "sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA==", + "dev": true + }, + "@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true + }, + "@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@roomle/embedding-lib": { + "version": "4.42.0", + "resolved": "https://registry.npmjs.org/@roomle/embedding-lib/-/embedding-lib-4.42.0.tgz", + "integrity": "sha512-jnYIwJjKrqZwqLFzo6BgEmZ/r/cZ8XTKyqsU8tH3t1XuMI4IwfHn/RfoeBZPJv29pDuEc6clMNk9DWG3KE7GCg==", + "requires": { + "@roomle/web-sdk": "2.38.0" + } + }, + "@roomle/web-sdk": { + "version": "2.38.0", + "resolved": "https://registry.npmjs.org/@roomle/web-sdk/-/web-sdk-2.38.0.tgz", + "integrity": "sha512-1NQOjarzfq1DzmpidyjGRiMmJZSQ4UgHLQSSkVI8dierzx6A70NKo28+tHhFCuM1ZvVK3UeDhRIk02xC0NtoHw==" + }, + "@vue/compiler-sfc": { + "version": "2.7.10", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-2.7.10.tgz", + "integrity": "sha512-55Shns6WPxlYsz4WX7q9ZJBL77sKE1ZAYNYStLs6GbhIOMrNtjMvzcob6gu3cGlfpCR4bT7NXgyJ3tly2+Hx8Q==", + "dev": true, + "requires": { + "@babel/parser": "^7.18.4", + "postcss": "^8.4.14", + "source-map": "^0.6.1" + } + }, + "acorn": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", + "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", + "dev": true + }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "requires": {} + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "dependencies": { + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + } + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "consola": { + "version": "2.15.3", + "resolved": "https://registry.npmjs.org/consola/-/consola-2.15.3.tgz", + "integrity": "sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==", + "dev": true + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true + }, + "csstype": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz", + "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==", + "dev": true + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "defu": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.0.tgz", + "integrity": "sha512-pOFYRTIhoKujrmbTRhcW5lYQLBXw/dlTwfI8IguF1QCDJOcJzNH1w+YFjxqy6BAuJrClTy6MUE8q+oKJ2FLsIw==", + "dev": true + }, + "detect-package-manager": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/detect-package-manager/-/detect-package-manager-2.0.1.tgz", + "integrity": "sha512-j/lJHyoLlWi6G1LDdLgvUtz60Zo5GEj+sVYtTVXnYLDPuzgC3llMxonXym9zIwhhUII8vjdw0LXxavpLqTbl1A==", + "dev": true, + "requires": { + "execa": "^5.1.1" + } + }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + } + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "esbuild": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.7.tgz", + "integrity": "sha512-7V8tzllIbAQV1M4QoE52ImKu8hT/NLGlGXkiDsbEU5PS6K8Mn09ZnYoS+dcmHxOS9CRsV4IRAMdT3I67IyUNXw==", + "dev": true, + "requires": { + "@esbuild/linux-loong64": "0.15.7", + "esbuild-android-64": "0.15.7", + "esbuild-android-arm64": "0.15.7", + "esbuild-darwin-64": "0.15.7", + "esbuild-darwin-arm64": "0.15.7", + "esbuild-freebsd-64": "0.15.7", + "esbuild-freebsd-arm64": "0.15.7", + "esbuild-linux-32": "0.15.7", + "esbuild-linux-64": "0.15.7", + "esbuild-linux-arm": "0.15.7", + "esbuild-linux-arm64": "0.15.7", + "esbuild-linux-mips64le": "0.15.7", + "esbuild-linux-ppc64le": "0.15.7", + "esbuild-linux-riscv64": "0.15.7", + "esbuild-linux-s390x": "0.15.7", + "esbuild-netbsd-64": "0.15.7", + "esbuild-openbsd-64": "0.15.7", + "esbuild-sunos-64": "0.15.7", + "esbuild-windows-32": "0.15.7", + "esbuild-windows-64": "0.15.7", + "esbuild-windows-arm64": "0.15.7" + } + }, + "esbuild-android-64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.7.tgz", + "integrity": "sha512-p7rCvdsldhxQr3YHxptf1Jcd86dlhvc3EQmQJaZzzuAxefO9PvcI0GLOa5nCWem1AJ8iMRu9w0r5TG8pHmbi9w==", + "dev": true, + "optional": true + }, + "esbuild-android-arm64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.7.tgz", + "integrity": "sha512-L775l9ynJT7rVqRM5vo+9w5g2ysbOCfsdLV4CWanTZ1k/9Jb3IYlQ06VCI1edhcosTYJRECQFJa3eAvkx72eyQ==", + "dev": true, + "optional": true + }, + "esbuild-darwin-64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.7.tgz", + "integrity": "sha512-KGPt3r1c9ww009t2xLB6Vk0YyNOXh7hbjZ3EecHoVDxgtbUlYstMPDaReimKe6eOEfyY4hBEEeTvKwPsiH5WZg==", + "dev": true, + "optional": true + }, + "esbuild-darwin-arm64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.7.tgz", + "integrity": "sha512-kBIHvtVqbSGajN88lYMnR3aIleH3ABZLLFLxwL2stiuIGAjGlQW741NxVTpUHQXUmPzxi6POqc9npkXa8AcSZQ==", + "dev": true, + "optional": true + }, + "esbuild-freebsd-64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.7.tgz", + "integrity": "sha512-hESZB91qDLV5MEwNxzMxPfbjAhOmtfsr9Wnuci7pY6TtEh4UDuevmGmkUIjX/b+e/k4tcNBMf7SRQ2mdNuK/HQ==", + "dev": true, + "optional": true + }, + "esbuild-freebsd-arm64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.7.tgz", + "integrity": "sha512-dLFR0ChH5t+b3J8w0fVKGvtwSLWCv7GYT2Y2jFGulF1L5HftQLzVGN+6pi1SivuiVSmTh28FwUhi9PwQicXI6Q==", + "dev": true, + "optional": true + }, + "esbuild-linux-32": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.7.tgz", + "integrity": "sha512-v3gT/LsONGUZcjbt2swrMjwxo32NJzk+7sAgtxhGx1+ZmOFaTRXBAi1PPfgpeo/J//Un2jIKm/I+qqeo4caJvg==", + "dev": true, + "optional": true + }, + "esbuild-linux-64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.7.tgz", + "integrity": "sha512-LxXEfLAKwOVmm1yecpMmWERBshl+Kv5YJ/1KnyAr6HRHFW8cxOEsEfisD3sVl/RvHyW//lhYUVSuy9jGEfIRAQ==", + "dev": true, + "optional": true + }, + "esbuild-linux-arm": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.7.tgz", + "integrity": "sha512-JKgAHtMR5f75wJTeuNQbyznZZa+pjiUHV7sRZp42UNdyXC6TiUYMW/8z8yIBAr2Fpad8hM1royZKQisqPABPvQ==", + "dev": true, + "optional": true + }, + "esbuild-linux-arm64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.7.tgz", + "integrity": "sha512-P3cfhudpzWDkglutWgXcT2S7Ft7o2e3YDMrP1n0z2dlbUZghUkKCyaWw0zhp4KxEEzt/E7lmrtRu/pGWnwb9vw==", + "dev": true, + "optional": true + }, + "esbuild-linux-mips64le": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.7.tgz", + "integrity": "sha512-T7XKuxl0VpeFLCJXub6U+iybiqh0kM/bWOTb4qcPyDDwNVhLUiPcGdG2/0S7F93czUZOKP57YiLV8YQewgLHKw==", + "dev": true, + "optional": true + }, + "esbuild-linux-ppc64le": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.7.tgz", + "integrity": "sha512-6mGuC19WpFN7NYbecMIJjeQgvDb5aMuvyk0PDYBJrqAEMkTwg3Z98kEKuCm6THHRnrgsdr7bp4SruSAxEM4eJw==", + "dev": true, + "optional": true + }, + "esbuild-linux-riscv64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.7.tgz", + "integrity": "sha512-uUJsezbswAYo/X7OU/P+PuL/EI9WzxsEQXDekfwpQ23uGiooxqoLFAPmXPcRAt941vjlY9jtITEEikWMBr+F/g==", + "dev": true, + "optional": true + }, + "esbuild-linux-s390x": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.7.tgz", + "integrity": "sha512-+tO+xOyTNMc34rXlSxK7aCwJgvQyffqEM5MMdNDEeMU3ss0S6wKvbBOQfgd5jRPblfwJ6b+bKiz0g5nABpY0QQ==", + "dev": true, + "optional": true + }, + "esbuild-netbsd-64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.7.tgz", + "integrity": "sha512-yVc4Wz+Pu3cP5hzm5kIygNPrjar/v5WCSoRmIjCPWfBVJkZNb5brEGKUlf+0Y759D48BCWa0WHrWXaNy0DULTQ==", + "dev": true, + "optional": true + }, + "esbuild-openbsd-64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.7.tgz", + "integrity": "sha512-GsimbwC4FSR4lN3wf8XmTQ+r8/0YSQo21rWDL0XFFhLHKlzEA4SsT1Tl8bPYu00IU6UWSJ+b3fG/8SB69rcuEQ==", + "dev": true, + "optional": true + }, + "esbuild-sunos-64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.7.tgz", + "integrity": "sha512-8CDI1aL/ts0mDGbWzjEOGKXnU7p3rDzggHSBtVryQzkSOsjCHRVe0iFYUuhczlxU1R3LN/E7HgUO4NXzGGP/Ag==", + "dev": true, + "optional": true + }, + "esbuild-windows-32": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.7.tgz", + "integrity": "sha512-cOnKXUEPS8EGCzRSFa1x6NQjGhGsFlVgjhqGEbLTPsA7x4RRYiy2RKoArNUU4iR2vHmzqS5Gr84MEumO/wxYKA==", + "dev": true, + "optional": true + }, + "esbuild-windows-64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.7.tgz", + "integrity": "sha512-7MI08Ec2sTIDv+zH6StNBKO+2hGUYIT42GmFyW6MBBWWtJhTcQLinKS6ldIN1d52MXIbiJ6nXyCJ+LpL4jBm3Q==", + "dev": true, + "optional": true + }, + "esbuild-windows-arm64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.7.tgz", + "integrity": "sha512-R06nmqBlWjKHddhRJYlqDd3Fabx9LFdKcjoOy08YLimwmsswlFBJV4rXzZCxz/b7ZJXvrZgj8DDv1ewE9+StMw==", + "dev": true, + "optional": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "eslint": { + "version": "8.23.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.23.0.tgz", + "integrity": "sha512-pBG/XOn0MsJcKcTRLr27S5HpzQo4kLr+HjLQIyK4EiCsijDl/TB+h5uEuJU6bQ8Edvwz1XWOjpaP2qgnXGpTcA==", + "dev": true, + "requires": { + "@eslint/eslintrc": "^1.3.1", + "@humanwhocodes/config-array": "^0.10.4", + "@humanwhocodes/gitignore-to-minimatch": "^1.0.2", + "@humanwhocodes/module-importer": "^1.0.1", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.4.0", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^6.0.1", + "globals": "^13.15.0", + "globby": "^11.1.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + } + }, + "eslint-config-prettier": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz", + "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==", + "dev": true, + "requires": {} + }, + "eslint-plugin-vue": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.4.0.tgz", + "integrity": "sha512-Nzz2QIJ8FG+rtJaqT/7/ru5ie2XgT9KCudkbN0y3uFYhQ41nuHEaboLAiqwMcK006hZPQv/rVMRhUIwEGhIvfQ==", + "dev": true, + "requires": { + "eslint-utils": "^3.0.0", + "natural-compare": "^1.4.0", + "nth-check": "^2.0.1", + "postcss-selector-parser": "^6.0.9", + "semver": "^7.3.5", + "vue-eslint-parser": "^9.0.1", + "xml-name-validator": "^4.0.0" + } + }, + "eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + } + }, + "eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^2.0.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true + } + } + }, + "eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true + }, + "espree": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.0.tgz", + "integrity": "sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==", + "dev": true, + "requires": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + } + }, + "esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + } + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", + "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "dependencies": { + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + } + } + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "dev": true + }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + }, + "globals": { + "version": "13.17.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", + "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + } + }, + "grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true + }, + "ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true + }, + "immutable": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.1.0.tgz", + "integrity": "sha512-oNkuqVTA8jqG1Q6c+UglTOD1xhC1BtjKI7XkCXRkZHrN5m18/XsnUp8Q89GkQO/z+0WjonSvl0FLhDYftp46nQ==", + "dev": true + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-core-module": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", + "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "jiti": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.14.0.tgz", + "integrity": "sha512-4IwstlaKQc9vCTC+qUXLM1hajy2ImiL9KnLvVYiaHOtS/v3wRjhLlGl121AmgDgx/O43uKmxownJghS5XMya2A==", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "kirbyup": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/kirbyup/-/kirbyup-2.0.1.tgz", + "integrity": "sha512-Yhjakz09gIqTUKZHK4EmU+sYK5H0aV//+WYThwzIWPTSzvS5iKT319wpR2VEREfdBTW8SGDdYgU1Tu73h7a9fA==", + "dev": true, + "requires": { + "@vue/compiler-sfc": "^2.7.10", + "cac": "^6.7.12", + "chokidar": "^3.5.3", + "consola": "^2.15.3", + "detect-package-manager": "^2.0.1", + "magic-string": "^0.26.2", + "pathe": "^0.3.5", + "perfect-debounce": "^0.1.3", + "picocolors": "^1.0.0", + "postcss": "^8.4.16", + "postcss-dir-pseudo-class": "^6.0.5", + "postcss-load-config": "^4.0.1", + "postcss-logical": "^5.0.4", + "sass": "^1.54.5", + "unconfig": "^0.3.5", + "vite": "^3.0.9", + "vite-plugin-full-reload": "^1.0.4", + "vue": "^2.7.10" + } + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "lilconfig": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.6.tgz", + "integrity": "sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==", + "dev": true + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "magic-string": { + "version": "0.26.3", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.26.3.tgz", + "integrity": "sha512-u1Po0NDyFcwdg2nzHT88wSK0+Rih0N1M+Ph1Sp08k8yvFFU3KR72wryS7e1qMPJypt99WB7fIFVCA92mQrMjrg==", + "dev": true, + "requires": { + "sourcemap-codec": "^1.4.8" + } + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "nanoid": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "requires": { + "boolbase": "^1.0.0" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "pathe": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-0.3.5.tgz", + "integrity": "sha512-grU/QeYP0ChuE5kjU2/k8VtAeODzbernHlue0gTa27+ayGIu3wqYBIPGfP9r5xSqgCgDd4nWrjKXEfxMillByg==", + "dev": true + }, + "perfect-debounce": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-0.1.3.tgz", + "integrity": "sha512-NOT9AcKiDGpnV/HBhI22Str++XWcErO/bALvHCuhv33owZW/CjH8KAFLZDCmu3727sihe0wTxpDhyGc6M8qacQ==", + "dev": true + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "postcss": { + "version": "8.4.16", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.16.tgz", + "integrity": "sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==", + "dev": true, + "requires": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + } + }, + "postcss-dir-pseudo-class": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-6.0.5.tgz", + "integrity": "sha512-eqn4m70P031PF7ZQIvSgy9RSJ5uI2171O/OO/zcRNYpJbvaeKFUlar1aJ7rmgiQtbm0FSPsRewjpdS0Oew7MPA==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.10" + } + }, + "postcss-load-config": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.1.tgz", + "integrity": "sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==", + "dev": true, + "requires": { + "lilconfig": "^2.0.5", + "yaml": "^2.1.1" + } + }, + "postcss-logical": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-5.0.4.tgz", + "integrity": "sha512-RHXxplCeLh9VjinvMrZONq7im4wjWGlRJAqmAVLXyZaXwfDWP73/oq4NdIp+OZwhQUMj0zjqDfM5Fj7qby+B4g==", + "dev": true, + "requires": {} + }, + "postcss-selector-parser": { + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", + "dev": true, + "requires": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + } + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "prettier": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", + "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", + "dev": true + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true + }, + "resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "requires": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "rollup": { + "version": "2.78.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.78.1.tgz", + "integrity": "sha512-VeeCgtGi4P+o9hIg+xz4qQpRl6R401LWEXBmxYKOV4zlF82lyhgh2hTZnheFUbANE8l2A41F458iwj2vEYaXJg==", + "dev": true, + "requires": { + "fsevents": "~2.3.2" + } + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "sass": { + "version": "1.54.8", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.54.8.tgz", + "integrity": "sha512-ib4JhLRRgbg6QVy6bsv5uJxnJMTS2soVcCp9Y88Extyy13A8vV0G1fAwujOzmNkFQbR3LvedudAMbtuNRPbQww==", + "dev": true, + "requires": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + } + }, + "semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true + }, + "sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "dev": true + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + }, + "unconfig": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/unconfig/-/unconfig-0.3.6.tgz", + "integrity": "sha512-JWefWyjLrDAbzs30sFkzcE9YpvAhN9+UPMZBwnNUmaY9X7QhI+wCGP4hoEWfZDzvkP+WIaZDPcMUJjarpxFvKg==", + "dev": true, + "requires": { + "@antfu/utils": "^0.5.2", + "defu": "^6.1.0", + "jiti": "^1.14.0" + } + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "vite": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-3.1.0.tgz", + "integrity": "sha512-YBg3dUicDpDWFCGttmvMbVyS9ydjntwEjwXRj2KBFwSB8SxmGcudo1yb8FW5+M/G86aS8x828ujnzUVdsLjs9g==", + "dev": true, + "requires": { + "esbuild": "^0.15.6", + "fsevents": "~2.3.2", + "postcss": "^8.4.16", + "resolve": "^1.22.1", + "rollup": "~2.78.0" + } + }, + "vite-plugin-full-reload": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/vite-plugin-full-reload/-/vite-plugin-full-reload-1.0.4.tgz", + "integrity": "sha512-9WejQII6zJ++m/YE173Zvl2jq4cqa404KNrVT+JDzDnqaGRq5UvOvA48fnsSWPIMXFV7S0dq5+sZqcSB+tKBgA==", + "dev": true, + "requires": { + "picocolors": "^1.0.0", + "picomatch": "^2.3.1" + } + }, + "vue": { + "version": "2.7.10", + "resolved": "https://registry.npmjs.org/vue/-/vue-2.7.10.tgz", + "integrity": "sha512-HmFC70qarSHPXcKtW8U8fgIkF6JGvjEmDiVInTkKZP0gIlEPhlVlcJJLkdGIDiNkIeA2zJPQTWJUI4iWe+AVfg==", + "dev": true, + "requires": { + "@vue/compiler-sfc": "2.7.10", + "csstype": "^3.1.0" + } + }, + "vue-eslint-parser": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.0.3.tgz", + "integrity": "sha512-yL+ZDb+9T0ELG4VIFo/2anAOz8SvBdlqEnQnvJ3M7Scq56DvtjY0VY88bByRZB0D4J0u8olBcfrXTVONXsh4og==", + "dev": true, + "requires": { + "debug": "^4.3.4", + "eslint-scope": "^7.1.1", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.3.1", + "esquery": "^1.4.0", + "lodash": "^4.17.21", + "semver": "^7.3.6" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "yaml": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.1.1.tgz", + "integrity": "sha512-o96x3OPo8GjWeSLF+wOAbrPfhFOGY0W00GNaxCDv+9hkcDJEnev1yh8S7pgHF0ik6zc8sQLuL8hjHjJULZp8bw==", + "dev": true + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..3ae8b49 --- /dev/null +++ b/package.json @@ -0,0 +1,22 @@ +{ + "scripts": { + "build": "npm run build:panel && npm run build:public", + "build:panel": "kirbyup src/frontend/index.js", + "build:public": "esbuild src/frontend/public/*.js --outdir=assets --minify --bundle --splitting --format=esm --target=es6", + "dev:panel": "kirbyup src/frontend/index.js --watch", + "lint": "eslint --ext js,vue src/frontend", + "format": "prettier --write \"src/frontend/**/*.{js,vue}\"" + }, + "dependencies": { + "@roomle/embedding-lib": "^4.41.0" + }, + "devDependencies": { + "esbuild": "^0.15.6", + "eslint": "^8.16.0", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-vue": "^9.0.1", + "kirbyup": "^2.0.0", + "prettier": "^2.6.2" + }, + "browserslist": "> 2%" +} diff --git a/phpmd.xml.dist b/phpmd.xml.dist new file mode 100644 index 0000000..1f01864 --- /dev/null +++ b/phpmd.xml.dist @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..23ef219 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,24 @@ + + + + + + ./src/classes + + + + + + ./tests/ + + + diff --git a/psalm.xml.dist b/psalm.xml.dist new file mode 100644 index 0000000..296d7f3 --- /dev/null +++ b/psalm.xml.dist @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + diff --git a/screenshot.png b/screenshot.png new file mode 100644 index 0000000..a490511 Binary files /dev/null and b/screenshot.png differ diff --git a/src/classes/Configuration.php b/src/classes/Configuration.php new file mode 100644 index 0000000..327529f --- /dev/null +++ b/src/classes/Configuration.php @@ -0,0 +1,224 @@ + + * @link https://github.com/lukasbestle/kirby-roomle + * @copyright Lukas Bestle + * @license https://opensource.org/licenses/MIT + * + * @psalm-suppress PropertyNotSetInConstructor + */ +class Configuration extends Obj +{ + /** + * Identifier of the Roomle Catalog + * this configuration is part of + */ + public string $catalog; + + /** + * Deeplink to the configurator where + * this configuration was created + */ + public string $configuratorUrl; + + /** + * Depth of the configured product in mm + */ + public int $depth; + + /** + * Height of the configured product in mm + */ + public int $height; + + /** + * ID of the configuration + */ + public string $id; + + /** + * Product label + */ + public string $label; + + /** + * List of individual parts of the configuration + */ + public array $parts; + + /** + * Image URL of a perspective view of the configured product + */ + public string $perspectiveImage; + + /** + * Root component ID of the configuration + */ + public string $rootComponentId; + + /** + * Image URL of a top view of the configured product + */ + public string $topImage; + + /** + * Width of the configured product in mm + */ + public int $width; + + /** + * Constructor + * + * @param array|string|null $data `null` to get the data from the request (`roomle-configuration` param) + * + * @throws \Kirby\Exception\InvalidArgumentException if no configuration data was passed as argument or in the request + * @throws \Kirby\Exception\InvalidArgumentException if the JSON data from the request could not be parsed + */ + public function __construct(array|string|null $data = null) + { + if ($data === null) { + $data = App::instance()->request()->get('roomle-configuration'); + + if (!$data) { + throw new InvalidArgumentException('No configuration data passed or available in request'); + } + } + + if (is_string($data) === true) { + $data = Json::decode($data); + } + + parent::__construct($data); + } + + /** + * Returns a plain text representation of the configuration + * e.g. for use in contact forms + */ + public function __toString(): string + { + // TODO: Remove suppression comment when support for Kirby 3.7.x is dropped + /** @psalm-suppress InternalMethod */ + return App::instance()->snippet('roomle/configuration', ['configuration' => $this], true); + } + + /** + * Returns the depth of the configured product + * as a human-readable string in cm + */ + public function depthLabel(): string + { + return static::formatLength($this->depth); + } + + /** + * Formats a length in millimeters as a + * human-readable string in cm + * @internal + */ + public static function formatLength(int $millimeters): string + { + $centimeters = $millimeters / 10; + + if (class_exists(NumberFormatter::class) === true) { + $formatter = new NumberFormatter(locale_get_default(), NumberFormatter::DECIMAL); + return $formatter->format($centimeters) . ' cm'; + } + + return $centimeters . ' cm'; // @codeCoverageIgnore + } + + /** + * Returns the height of the configured product + * as a human-readable string in cm + */ + public function heightLabel(): string + { + return static::formatLength($this->height); + } + + /** + * Creates an instance if data is available + * (either from the argument or the request) + * + * @param array|string|null $data `null` to get the data from the request (`roomle-configuration` param) + */ + public static function lazyInstance(array|string|null $data = null): self|null + { + try { + return new self($data); + } catch (InvalidArgumentException) { + return null; + } + } + + /** + * Returns the list of individual parts + * of the configuration as an object structure + * + * @throws \Kirby\Exception\InvalidArgumentException if a part in the raw data is not an array + */ + public function parts(): Collection + { + $parts = []; + foreach ($this->parts as $num => $part) { + if (is_array($part) !== true) { + throw new InvalidArgumentException('Invalid part ' . $num); + } + + $part = new Part($part); + + // double-check uninitialized property before access (normally should not happen); + // the Psalm error is suppressed because Psalm assumes all props to be + // initialized in the `Part` constructor (which `Obj` does not ensure) + /** @psalm-suppress TypeDoesNotContainType */ + if (isset($part->componentId) !== true) { + throw new InvalidArgumentException('Part ' . $num . ' does not have a component ID'); + } + + $parts[$part->componentId] = $part; + } + + return new Collection($parts); + } + + /** + * Returns the image of a perspective view of the configured product + */ + public function perspectiveImage(): Image + { + return new Image(['url' => $this->perspectiveImage]); + } + + /** + * Returns the image of a top view of the configured product + */ + public function topImage(): Image + { + return new Image(['url' => $this->topImage]); + } + + /** + * Returns the width of the configured product + * as a human-readable string in cm + */ + public function widthLabel(): string + { + return static::formatLength($this->width); + } +} diff --git a/src/classes/ConfiguratorBlock.php b/src/classes/ConfiguratorBlock.php new file mode 100644 index 0000000..1a5a5b1 --- /dev/null +++ b/src/classes/ConfiguratorBlock.php @@ -0,0 +1,202 @@ + + * @link https://github.com/lukasbestle/kirby-roomle + * @copyright Lukas Bestle + * @license https://opensource.org/licenses/MIT + */ +class ConfiguratorBlock extends Block +{ + /** + * Returns the Roomle configurator ID + * + * @throws \Kirby\Exception\InvalidArgumentException if no configurator ID was configured + */ + public function configuratorId(): string + { + $id = match ($this->content()->useConfiguratorId()->value()) { + 'default' => $this->option('configuratorId'), + 'custom' => $this->content()->configuratorId()->value(), + default => null + }; + + if (is_string($id) !== true || !$id) { + throw new InvalidArgumentException('Missing or invalid configurator ID setting'); + } + + return $id; + } + + /** + * Returns the data that is necessary for the JS logic + * as JSON string + */ + public function frontendJson(): string + { + return json_encode($this->frontendProps()); + } + + /** + * Returns the data that is necessary for the JS logic + */ + public function frontendProps(): array + { + return [ + 'configuratorId' => $this->configuratorId(), + 'htmlId' => $this->htmlId(), + 'options' => $this->options(), + 'targetUrl' => $this->targetUrl(), + ]; + } + + /** + * Returns whether variants have been configured + */ + public function hasVariants(): bool + { + return $this->variants()->isNotEmpty(); + } + + /** + * Returns the block ID used for matching the + * JS logic to the HTML code and query string + */ + public function htmlId(): string + { + return 'roomle-' . $this->id(); + } + + /** + * Returns the URL to the `configurator.js` file + */ + public function jsUrl(): string + { + /** @var \Kirby\Cms\Plugin $plugin */ + $plugin = $this->kirby()->plugin('lukasbestle/roomle'); + $mediaUrl = $plugin->mediaUrl(); + + return $mediaUrl . '/configurator.js'; + } + + /** + * Returns the Roomle item or configuration ID of the + * product to display by default + */ + public function mainProductId(): string + { + $id = $this->content()->mainProductId()->value(); + + if (!$id) { + throw new InvalidArgumentException('Missing main product ID'); + } + + return $id; + } + + /** + * Returns the custom options for the Roomle configurator + */ + public function options(): array + { + $defaults = []; + + // URL template for the configurator URL; + // the #CONFIGURATIONID# placeholder is dynamically replaced by Roomle + $defaults['deeplink'] = ( + $this->parent()->url() . + '?' . $this->htmlId() . '=#CONFIGURATIONID#' . + '#' . $this->htmlId() + ); + + // locale settings from the site's language code + $language = $this->kirby()->language(); + if ($language !== null) { + $code = $language->code(); + + if (Str::contains($code, '-') === true) { + $defaults['locale'] = Str::before($code, '-'); + $defaults['overrideCountry'] = Str::after($code, '-'); + } else { + $defaults['locale'] = $code; + } + } + + // no point displaying the "request product" button + // if no target page was configured + if ($this->targetUrl() === null) { + $defaults['buttons']['requestproduct'] = false; + } + + // assemble the options from the dynamic defaults with + // overrides from the config and the block settings + $overrides = $this->content()->options()->yaml(); + $options = array_merge($defaults, $this->option('options'), $overrides); + + // always set the `id` property from block data + $options['id'] = $this->mainProductId(); + + return $options; + } + + /** + * Returns the page to redirect to when + * "request product" is clicked + */ + public function targetUrl(): string|null + { + $defaultTarget = $this->option('target'); + if ($defaultTarget !== null) { + $defaultTarget = Url::to($defaultTarget); + } + + return match ($this->content()->useTarget()->value()) { + 'default' => $defaultTarget, + 'custom' => $this->content()->target()->toPage()?->url(), + default => null + }; + } + + /** + * Returns the variants the visitor can switch between + */ + public function variants(): Structure + { + $field = $this->content()->variants(); + $structure = new Structure([], $field->parent()); + + foreach ($field->value() as $id => $props) { + $variant = new ConfiguratorVariant([ + 'content' => $props, + 'id' => $props['id'] ?? $id, + 'parent' => $field->parent(), + 'structure' => $structure + ]); + + $structure->set($variant->id(), $variant); + } + + return $structure; + } + + /** + * Returns a configured plugin option value + */ + protected function option(string $option): mixed + { + return $this->kirby()->option('lukasbestle.roomle.' . $option); + } +} diff --git a/src/classes/ConfiguratorVariant.php b/src/classes/ConfiguratorVariant.php new file mode 100644 index 0000000..e59a87f --- /dev/null +++ b/src/classes/ConfiguratorVariant.php @@ -0,0 +1,43 @@ + + * @link https://github.com/lukasbestle/kirby-roomle + * @copyright Lukas Bestle + * @license https://opensource.org/licenses/MIT + * + * @psalm-suppress PropertyNotSetInConstructor + */ +class ConfiguratorVariant extends StructureObject +{ + /** + * Returns an image object for the configured variant image + */ + public function image(): File|Image + { + $image = $this->content()->image()->toFile(); + if ($image !== null) { + return $image; + } + + // more than two colons = configuration, else item + $productId = $this->content()->productId()->value(); + if (count(explode(':', $productId)) > 2) { + $url = 'https://uploads.roomle.com/configurations/' . $productId . '/perspectiveImage.png'; + } else { + $url = 'https://www.roomle.com/api/v2/items/' . $productId . '/perspectiveImageHD'; + } + + return new Image(compact('url')); + } +} diff --git a/src/classes/Parameter.php b/src/classes/Parameter.php new file mode 100644 index 0000000..ec333ce --- /dev/null +++ b/src/classes/Parameter.php @@ -0,0 +1,107 @@ + + * @link https://github.com/lukasbestle/kirby-roomle + * @copyright Lukas Bestle + * @license https://opensource.org/licenses/MIT + * + * @psalm-suppress PropertyNotSetInConstructor + */ +class Parameter extends Obj +{ + /** + * Machine-readable key that + * identifies this parameter + */ + public string $key; + + /** + * Human-readable label that + * identifies this parameter + */ + public string|null $label; + + /** + * Data type of the `value` + * + * @var string 'Decimal'|'Integral'|'Material'|'String'|'Unknown' + */ + public string $type; + + /** + * Unit type for number values + * + * @var string|null 'angle'|'area'|'count'|'length'|null + */ + public string|null $unitType; + + /** + * Machine-readable value + */ + public string $value; + + /** + * Human-readable value + */ + public string|null $valueLabel; + + /** + * Returns a plain text representation of the parameter + * e.g. for use in contact forms + */ + public function __toString(): string + { + // TODO: Remove suppression comment when support for Kirby 3.7.x is dropped + /** @psalm-suppress InternalMethod */ + return App::instance()->snippet('roomle/parameter', ['parameter' => $this], true); + } + + /** + * Returns the human-readable label + * that identifies this parameter + * + * @return string + */ + public function label(): string + { + return $this->label ?? $this->key; + } + + /** + * Returns the machine-readable value + * depending on the `type` + */ + public function value(): string|float + { + if ($this->type === 'Decimal') { + return (float)$this->value; + } + + return $this->value; + } + + /** + * Returns the human-readable value + */ + public function valueLabel(): string + { + if ($this->type === 'Decimal' && $this->unitType === 'length') { + /** @var float $value */ + $value = $this->value(); + + return Configuration::formatLength((int)$value); + } + + return $this->valueLabel ?? $this->value; + } +} diff --git a/src/classes/Parameters.php b/src/classes/Parameters.php new file mode 100644 index 0000000..fcb6235 --- /dev/null +++ b/src/classes/Parameters.php @@ -0,0 +1,32 @@ + + * @link https://github.com/lukasbestle/kirby-roomle + * @copyright Lukas Bestle + * @license https://opensource.org/licenses/MIT + * + * @psalm-suppress PropertyNotSetInConstructor + */ +class Parameters extends Collection +{ + /** + * Returns a key-value array with the + * machine-readable parameter data + */ + public function rawData(): array + { + return array_map( + fn (Parameter $parameter): string|float => $parameter->value(), + $this->data + ); + } +} diff --git a/src/classes/Part.php b/src/classes/Part.php new file mode 100644 index 0000000..1f79dea --- /dev/null +++ b/src/classes/Part.php @@ -0,0 +1,112 @@ + + * @link https://github.com/lukasbestle/kirby-roomle + * @copyright Lukas Bestle + * @license https://opensource.org/licenses/MIT + * + * @psalm-suppress PropertyNotSetInConstructor + */ +class Part extends Obj +{ + /** + * Article number of the part + */ + public string $articleNr; + + /** + * ID of the component + */ + public string $componentId; + + /** + * Number of units of this part + * in the configuration + */ + public int $count; + + /** + * Currency symbol for `price` and `retailerPrice` + */ + public string|null $currencySymbol; + + /** + * Human-readable part name + */ + public string $label; + + /** + * How many parts are contained + * in one item of the article number + */ + public int $packageSize; + + /** + * Configured parameters of this part + * + * @var array + */ + public array $parameters; + + /** + * Price of the part + */ + public float|null $price; + + /** + * Retailer price of the part + */ + public float|null $retailerPrice; + + /** + * Returns a plain text representation of the part + * e.g. for use in contact forms + */ + public function __toString(): string + { + // TODO: Remove suppression comment when support for Kirby 3.7.x is dropped + /** @psalm-suppress InternalMethod */ + return App::instance()->snippet('roomle/part', ['part' => $this], true); + } + + /** + * Returns the configured parameters + * of this part as an object structure + * + * @throws \Kirby\Exception\InvalidArgumentException if a parameter in the raw data is not an array + */ + public function parameters(): Parameters + { + $parameters = []; + foreach ($this->parameters as $num => $parameter) { + if (is_array($parameter) !== true) { + throw new InvalidArgumentException('Invalid parameter ' . $num); + } + + $parameter = new Parameter($parameter); + + // double-check uninitialized property before access (normally should not happen); + // the Psalm error is suppressed because Psalm assumes all props to be + // initialized in the `Parameter` constructor (which `Obj` does not ensure) + /** @psalm-suppress TypeDoesNotContainType */ + if (isset($parameter->key) !== true) { + throw new InvalidArgumentException('Parameter ' . $num . ' does not have a key'); + } + + $parameters[$parameter->key] = $parameter; + } + + return new Parameters($parameters); + } +} diff --git a/src/config/blockModels.php b/src/config/blockModels.php new file mode 100644 index 0000000..1b49eb0 --- /dev/null +++ b/src/config/blockModels.php @@ -0,0 +1,7 @@ + ConfiguratorBlock::class +]; diff --git a/src/config/blueprints.php b/src/config/blueprints.php new file mode 100644 index 0000000..c8e680b --- /dev/null +++ b/src/config/blueprints.php @@ -0,0 +1,5 @@ + __DIR__ . '/blueprints/blocks/roomle-configurator.yml' +]; diff --git a/src/config/blueprints/blocks/roomle-configurator.yml b/src/config/blueprints/blocks/roomle-configurator.yml new file mode 100644 index 0000000..be0c3ba --- /dev/null +++ b/src/config/blueprints/blocks/roomle-configurator.yml @@ -0,0 +1,162 @@ +name: + en: Roomle Configurator + de: Roomle-Konfigurator +icon: roomle +label: + en: "{{ mainproductid }} · {{ variants.length }} variant(s)" + de: "{{ mainproductid }} · {{ variants.length }} Variante(n)" +tabs: + products: + label: + en: Products + de: Produkte + fields: + mainProductId: + label: + en: Main product ID + de: Hauptprodukt-ID + type: text + counter: false + required: true + variants: + label: + en: Variants + de: Varianten + type: structure + columns: + productId: + mobile: true + title: + mobile: true + subtitle: + image: + empty: + en: No variants yet + de: Noch keine Varianten + fields: + productId: + label: + en: Product ID + de: Produkt-ID + type: text + counter: false + required: true + title: + label: + en: Title + de: Titel + type: text + required: true + subtitle: + label: + en: Subtitle + de: Untertitel + type: text + image: + label: + en: Image + de: Bild + type: files + empty: + en: No custom image yet, uses rendering of the product + de: Noch kein eigenes Bild, verwendet Rendering des Produkts + multiple: false + query: page.images + settings: + label: + en: Settings + de: Einstellungen + fields: + info: + label: false + type: info + text: + en: These settings override the global default settings. Only change them if needed. + de: Diese Einstellungen haben Vorrang vor den globalen Standard-Einstellungen. Verändere sie nur wenn nötig. + useConfiguratorId: + label: + en: Configurator ID + de: Konfigurator-ID + type: toggles + default: default + grow: false + options: + - + text: + en: Default + de: Standard + value: default + - + text: + en: Custom + de: Eigene + value: custom + required: true + width: 1/3 + gap: + type: gap + when: + useConfiguratorId: default + width: 2/3 + configuratorId: + label: + en: Custom configurator ID + de: Eigene Konfigurator-ID + type: text + counter: false + required: true + when: + useConfiguratorId: custom + width: 2/3 + useTarget: + label: + en: Request target page + de: Anfrage-Zielseite + type: toggles + default: default + grow: false + options: + - + text: + en: Default + de: Standard + value: default + - + text: + en: Custom + de: Eigene + value: custom + - + icon: cancel + text: + en: None + de: Keine + value: none + required: true + width: 1/3 + target: + label: + en: Custom target page + de: Eigene Zielseite + type: pages + empty: + en: No custom target page yet + de: Noch keine eigene Zielseite + multiple: false + required: true + when: + useTarget: custom + width: 2/3 + options: + label: + en: Configurator options + de: Konfigurator-Optionen + type: textarea + buttons: false + counter: false + font: monospace + help: + en: Overrides for the Roomle configurator options in YAML syntax + de: Ergänzende Roomle Konfigurator-Optionen in YAML-Syntax + size: medium + spellcheck: false diff --git a/src/config/i18n/de.php b/src/config/i18n/de.php new file mode 100644 index 0000000..2d578d1 --- /dev/null +++ b/src/config/i18n/de.php @@ -0,0 +1,6 @@ + 'Noch kein gültiges Hauptprodukt', + 'roomle.noRendering' => 'Kein Rendering verfügbar', +]; diff --git a/src/config/i18n/en.php b/src/config/i18n/en.php new file mode 100644 index 0000000..342d878 --- /dev/null +++ b/src/config/i18n/en.php @@ -0,0 +1,6 @@ + 'No valid main product yet', + 'roomle.noRendering' => 'No rendering available', +]; diff --git a/src/config/options.php b/src/config/options.php new file mode 100644 index 0000000..a4399b6 --- /dev/null +++ b/src/config/options.php @@ -0,0 +1,16 @@ + null, + + // page to redirect to when "request product" is clicked; + // defaults to none (request product feature is disabled) + 'target' => null, + + // custom options for the Roomle configurator; + // see https://docs.roomle.com/web/embedding/api/interfaces/types.UiInitData.html + 'options' => [], +]; diff --git a/src/config/snippets.php b/src/config/snippets.php new file mode 100644 index 0000000..d2c0e2e --- /dev/null +++ b/src/config/snippets.php @@ -0,0 +1,8 @@ + __DIR__ . '/snippets/blocks/roomle-configurator.php', + 'roomle/configuration' => __DIR__ . '/snippets/roomle/configuration.php', + 'roomle/parameter' => __DIR__ . '/snippets/roomle/parameter.php', + 'roomle/part' => __DIR__ . '/snippets/roomle/part.php' +]; diff --git a/src/config/snippets/blocks/roomle-configurator.php b/src/config/snippets/blocks/roomle-configurator.php new file mode 100644 index 0000000..42e0c10 --- /dev/null +++ b/src/config/snippets/blocks/roomle-configurator.php @@ -0,0 +1,38 @@ + +
+ hasVariants() === true): ?> + + + +
+ + +
diff --git a/src/config/snippets/roomle/configuration.php b/src/config/snippets/roomle/configuration.php new file mode 100644 index 0000000..b11fc9e --- /dev/null +++ b/src/config/snippets/roomle/configuration.php @@ -0,0 +1,8 @@ + +label() ?> (id() ?>) +W widthLabel() ?> / H heightLabel() ?> / D depthLabel() . "\n" ?> +configuratorUrl() . "\n" ?> + +parts() as $part): ?> + + diff --git a/src/config/snippets/roomle/parameter.php b/src/config/snippets/roomle/parameter.php new file mode 100644 index 0000000..cf1b856 --- /dev/null +++ b/src/config/snippets/roomle/parameter.php @@ -0,0 +1,2 @@ + +label() ?>: valueLabel() ?> diff --git a/src/config/snippets/roomle/part.php b/src/config/snippets/roomle/part.php new file mode 100644 index 0000000..9580512 --- /dev/null +++ b/src/config/snippets/roomle/part.php @@ -0,0 +1,5 @@ + +count() ?>x label() ?> (articleNr() ?>) +parameters() as $parameter): ?> + + diff --git a/src/config/translations.php b/src/config/translations.php new file mode 100644 index 0000000..e4686e3 --- /dev/null +++ b/src/config/translations.php @@ -0,0 +1,9 @@ + require __DIR__ . '/i18n/en.php', + + // additional translations + 'de' => require __DIR__ . '/i18n/de.php', +]; diff --git a/src/frontend/components/Configurator.vue b/src/frontend/components/Configurator.vue new file mode 100644 index 0000000..402cc0c --- /dev/null +++ b/src/frontend/components/Configurator.vue @@ -0,0 +1,133 @@ + + + + + diff --git a/src/frontend/components/LazyImage.vue b/src/frontend/components/LazyImage.vue new file mode 100644 index 0000000..acabccf --- /dev/null +++ b/src/frontend/components/LazyImage.vue @@ -0,0 +1,30 @@ + + + diff --git a/src/frontend/icons/roomle.svg b/src/frontend/icons/roomle.svg new file mode 100644 index 0000000..ee893c6 --- /dev/null +++ b/src/frontend/icons/roomle.svg @@ -0,0 +1,2 @@ + + diff --git a/src/frontend/index.js b/src/frontend/index.js new file mode 100644 index 0000000..e4fce17 --- /dev/null +++ b/src/frontend/index.js @@ -0,0 +1,15 @@ +import roomleIcon from "./icons/roomle.svg?raw"; +import Configurator from "./components/Configurator.vue"; +import LazyImage from "./components/LazyImage.vue"; + +panel.plugin("lukasbestle/roomle", { + blocks: { + "roomle-configurator": Configurator + }, + components: { + "lbro-lazy-image": LazyImage + }, + icons: { + roomle: roomleIcon + } +}); diff --git a/src/frontend/public/configurator.js b/src/frontend/public/configurator.js new file mode 100644 index 0000000..554a0ac --- /dev/null +++ b/src/frontend/public/configurator.js @@ -0,0 +1,213 @@ +import RoomleConfiguratorApi from "@roomle/embedding-lib/roomle-configurator-api.es"; + +/** + * Configurator + * + * @package Kirby Roomle Plugin + * @author Lukas Bestle + * @link https://github.com/lukasbestle/kirby-roomle + * @copyright Lukas Bestle + * @license https://opensource.org/licenses/MIT + */ +export default class { + /** + * @param {Object} props From `$block->frontendJson()` + */ + constructor(props) { + this.props = props; + this.variantSelector = + "#" + props.htmlId + " .roomle-configurator-variants input"; + + // override the item from the query param if set + this.currentId = this.idFromUrl() || props.options.id; + } + + /** + * Loads the Roomle configurator and initializes event listeners + */ + async init() { + // only initialize once + if (this.configurator) { + return; + } + + // handle selecting a different variant + document.querySelectorAll(this.variantSelector).forEach((input) => { + input.addEventListener("change", this.onVariantSelect.bind(this)); + }); + + // handle browser history changes + window.addEventListener("popstate", this.onUrlChange.bind(this)); + + // initialize the variant selector + this.updateVariants(this.currentId); + + // override the item in case an ID was extracted from the URL + const options = this.props.options; + options.id = this.currentId; + + // initialize the configured or passed product + this.configurator = await RoomleConfiguratorApi.createConfigurator( + this.props.configuratorId, + document.getElementById(this.props.htmlId + "-container"), + options + ); + + // update the configurator if the variant was changed during loading + if (this.currentId !== options.id) { + this.load(this.currentId); + } + + // handle the "request product" interaction + if (this.props.targetUrl) { + this.configurator.ui.callbacks.onRequestProduct = + this.onRequestProduct.bind(this); + } + } + + /** + * Extracts the item/configuration from the current URL + * + * @returns {String|null} + */ + idFromUrl() { + const params = new URLSearchParams(window.location.search); + return params.get(this.props.htmlId); + } + + /** + * Builds a URL for a specific item or configuration in this block + * + * @param {String} id Item or configuration ID + * @param {Boolean} hash Whether to include the hash of the current block + * @returns {URL} + */ + idToUrl(id, hash = false) { + const url = new URL(window.location.href); + url.searchParams.set(this.props.htmlId, id); + + if (hash === true) { + url.hash = this.props.htmlId; + } + + return url; + } + + /** + * Loads a new item or configuration into the configurator + * + * @param {String} id Item or configuration ID + * @returns {Boolean} Whether the configurator was updated + */ + async load(id) { + this.currentId = id; + this.updateVariants(id); + + if (!this.configurator) { + // not yet initialized + return false; + } + + await this.configurator.ui.loadObject(id); + + return true; + } + + /** + * Handles the user clicking the "request product" button + * in the configurator + * + * @param {String} configurationId ID of the current configuration + * @param {Base64Image} image Image of the current configuration + * @param {KernelPartList} partlist The part list with all details, grouped, etc. + * @param {Price} price Price of the current configuration, either set via setPrice or from Roomle price service + * @param {Labels} labels The label of the catalog and the furniture system + * @param {RapiConfigurationEnhanced} configuration The data returned from the Roomle backend + */ + onRequestProduct( + configurationId, + image, + partlist, + price, + labels, + configuration + ) { + const data = { + catalog: configuration.catalog, + configuratorUrl: this.idToUrl(configurationId, true), + depth: configuration.depth, + height: configuration.height, + id: configurationId, + label: configuration.label, + parts: partlist.fullList, + perspectiveImage: configuration.perspectiveImage, + rootComponentId: configuration.rootComponentId, + topImage: configuration.topImage, + width: configuration.width + }; + + // submit the data as a POST request to the target + const form = document.createElement("form"); + form.action = this.props.targetUrl; + form.method = "POST"; + form.style.display = "none"; + + const input = document.createElement("input"); + input.name = "roomle-configuration"; + input.value = JSON.stringify(data); + form.appendChild(input); + + document.body.appendChild(form); + form.submit(); + } + + /** + * Handles a browser history change (e.g. back button) + */ + onUrlChange() { + const id = this.idFromUrl(); + + if (id) { + this.load(id); + } else { + // reset to the original product + this.load(this.props.options.id); + } + } + + /** + * Handles the user selecting a different variant + * + * @param {Event} event Browser `change` event + */ + onVariantSelect(event) { + // update the page URL + const url = this.idToUrl(event.target.value); + window.history.pushState(null, "", url); + + this.load(event.target.value); + } + + /** + * Updates the variants UI with a new ID + * if a variant for that ID exists + * + * @param {String} id Item or configuration ID + */ + updateVariants(id) { + const oldInput = document.querySelector(this.variantSelector + "[checked]"); + const newInput = document.querySelector( + this.variantSelector + '[value="' + CSS.escape(id) + '"]' + ); + + if (oldInput) { + oldInput.removeAttribute("checked"); + oldInput.checked = false; + } + + if (newInput) { + newInput.checked = true; + newInput.setAttribute("checked", "true"); + } + } +} diff --git a/stubs/App.php b/stubs/App.php new file mode 100644 index 0000000..9d7a90d --- /dev/null +++ b/stubs/App.php @@ -0,0 +1,22 @@ + [ + 'body' => [ + 'roomle-configuration' => '{"depth": 123}' + ] + ] + ]); + + locale_set_default('en_US'); + } + + /** + * @covers ::__construct + * @covers ::lazyInstance + */ + public function testConstruct_Array() + { + $configuration = new Configuration([ + 'depth' => 1337 + ]); + $this->assertSame(1337, $configuration->depth()); + + $configuration = Configuration::lazyInstance([ + 'depth' => 1337 + ]); + $this->assertSame(1337, $configuration->depth()); + } + + /** + * @covers ::__construct + * @covers ::lazyInstance + */ + public function testConstruct_Request() + { + $configuration = new Configuration(); + $this->assertSame(123, $configuration->depth()); + + $configuration = Configuration::lazyInstance(); + $this->assertSame(123, $configuration->depth()); + } + + /** + * @covers ::__construct + * @covers ::lazyInstance + */ + public function testConstruct_RequestInvalid() + { + // CMS instance without request data + new App(); + + $this->assertNull(Configuration::lazyInstance()); + + $this->expectException('Kirby\Exception\InvalidArgumentException'); + $this->expectExceptionMessage('No configuration data passed or available in request'); + + new Configuration(); + } + + /** + * @covers ::__construct + * @covers ::lazyInstance + */ + public function testConstruct_String() + { + $configuration = new Configuration('{"depth": 1337}'); + $this->assertSame(1337, $configuration->depth()); + + $configuration = Configuration::lazyInstance('{"depth": 1337}'); + $this->assertSame(1337, $configuration->depth()); + } + + /** + * @covers ::__construct + * @covers ::lazyInstance + */ + public function testConstruct_StringInvalid() + { + $this->assertNull(Configuration::lazyInstance('Definitely not JSON')); + + $this->expectException('Kirby\Exception\InvalidArgumentException'); + $this->expectExceptionMessage('JSON string is invalid'); + + new Configuration('Definitely not JSON'); + } + + /** + * @covers ::formatLength + */ + public function testFormatLength() + { + $this->assertSame('0.1 cm', Configuration::formatLength(1)); + $this->assertSame('1 cm', Configuration::formatLength(10)); + $this->assertSame('1.3 cm', Configuration::formatLength(13)); + $this->assertSame('123.4 cm', Configuration::formatLength(1234)); + $this->assertSame('1,234.5 cm', Configuration::formatLength(12345)); + } + + /** + * @covers ::depthLabel + * @covers ::heightLabel + * @covers ::widthLabel + */ + public function testLengthLabels() + { + $configuration = new Configuration([ + 'depth' => 12345, + 'height' => 34567, + 'width' => 56789 + ]); + + $this->assertSame('1,234.5 cm', $configuration->depthLabel()); + $this->assertSame('3,456.7 cm', $configuration->heightLabel()); + $this->assertSame('5,678.9 cm', $configuration->widthLabel()); + } + + /** + * @covers ::parts + */ + public function testParts() + { + $configuration = new Configuration([ + 'parts' => [ + [ + 'componentId' => 'some:component1', + 'label' => 'Some component 1' + ], + [ + 'componentId' => 'some:component2', + 'label' => 'Some component 2' + ] + ] + ]); + + $parts = $configuration->parts(); + + $this->assertSame(2, $parts->count()); + $this->assertSame(['some:component1', 'some:component2'], $parts->keys()); + $this->assertInstanceOf(Part::class, $parts->first()); + $this->assertSame('Some component 1', $parts->first()->label()); + } + + /** + * @covers ::parts + */ + public function testParts_Invalid1() + { + $this->expectException('Kirby\Exception\InvalidArgumentException'); + $this->expectExceptionMessage('Invalid part 0'); + + $configuration = new Configuration([ + 'parts' => [ + 'not an array' + ] + ]); + + $configuration->parts(); + } + + /** + * @covers ::parts + */ + public function testParts_Invalid2() + { + $this->expectException('Kirby\Exception\InvalidArgumentException'); + $this->expectExceptionMessage('Part 0 does not have a component ID'); + + $configuration = new Configuration([ + 'parts' => [ + [ + 'label' => 'Some part without component ID' + ] + ] + ]); + + $configuration->parts(); + } + + /** + * @covers ::perspectiveImage + */ + public function testPerspectiveImage() + { + $configuration = new Configuration([ + 'perspectiveImage' => $url = 'https://example.com/path/to/image.jpg' + ]); + + $image = $configuration->perspectiveImage(); + $this->assertNull($image->root()); + $this->assertSame($url, $image->url()); + $this->assertSame('', $image->html()); + } + + /** + * @covers ::topImage + */ + public function testTopImage() + { + $configuration = new Configuration([ + 'topImage' => $url = 'https://example.com/path/to/image.jpg' + ]); + + $image = $configuration->topImage(); + $this->assertNull($image->root()); + $this->assertSame($url, $image->url()); + $this->assertSame('', $image->html()); + } + + /** + * @covers ::__toString + */ + public function testToString() + { + $configuration = new Configuration([ + 'configuratorUrl' => 'https://example.com/configurator', + 'depth' => 12, + 'height' => 34, + 'id' => 'some:id', + 'label' => 'Some product', + 'width' => 56, + 'parts' => [ + [ + 'articleNr' => '123.456.789', + 'componentId' => 'some:component1', + 'count' => 2, + 'label' => 'Some part', + 'parameters' => [ + [ + 'key' => 'height', + 'label' => 'Height', + 'type' => 'Decimal', + 'unitType' => 'length', + 'value' => '123.0' + ], + [ + 'key' => 'width', + 'label' => 'Width', + 'type' => 'Decimal', + 'unitType' => 'length', + 'value' => '456.0' + ] + ] + ], + [ + 'articleNr' => '987.654.321', + 'componentId' => 'some:component2', + 'count' => 1, + 'label' => 'Some other part', + 'parameters' => [ + [ + 'key' => 'height', + 'label' => 'Height', + 'type' => 'Decimal', + 'unitType' => 'length', + 'value' => '789.0' + ] + ] + ] + ], + ]); + + $this->assertStringEqualsFile(__DIR__ . '/fixtures/configuration.txt', (string)$configuration); + } +} diff --git a/tests/Roomle/ConfiguratorBlockTest.php b/tests/Roomle/ConfiguratorBlockTest.php new file mode 100644 index 0000000..980ce42 --- /dev/null +++ b/tests/Roomle/ConfiguratorBlockTest.php @@ -0,0 +1,770 @@ +app = new App([ + 'options' => [ + 'lukasbestle.roomle' => [ + 'configuratorId' => 'defaultConfigurator', + 'target' => 'default-target', + 'options' => ['some' => 'default', 'other' => 'default'] + ] + ], + 'site' => [ + 'children' => [ + [ + 'slug' => 'test', + 'files' => [ + ['filename' => 'image.png'] + ] + ], + [ + 'slug' => 'custom-target' + ], + [ + 'slug' => 'default-target' + ] + ] + ], + 'urls' => [ + 'index' => 'https://example.com', + 'media' => 'https://media.example.com' + ] + ]); + } + + /** + * @covers ::configuratorId + */ + public function testConfiguratorId_Custom() + { + $block = new ConfiguratorBlock([ + 'content' => [ + 'configuratorid' => 'demoConfigurator', + 'useconfiguratorid' => 'custom', + ], + 'id' => '12345678-90ab-cdef-1234-567890abcdef', + 'isHidden' => false, + 'parent' => $this->app->page('test'), + 'type' => 'roomle-configurator' + ]); + + $this->assertSame('demoConfigurator', $block->configuratorId()); + } + + /** + * @covers ::configuratorId + * @covers ::option + */ + public function testConfiguratorId_Default() + { + $block = new ConfiguratorBlock([ + 'content' => [ + 'configuratorid' => 'demoConfigurator', + 'useconfiguratorid' => 'default', + ], + 'id' => '12345678-90ab-cdef-1234-567890abcdef', + 'isHidden' => false, + 'parent' => $this->app->page('test'), + 'type' => 'roomle-configurator' + ]); + + $this->assertSame('defaultConfigurator', $block->configuratorId()); + } + + /** + * @covers ::configuratorId + */ + public function testConfiguratorId_Invalid1() + { + $this->expectException('Kirby\Exception\InvalidArgumentException'); + $this->expectExceptionMessage('Missing or invalid configurator ID setting'); + + $block = new ConfiguratorBlock([ + 'content' => [ + 'configuratorid' => '', + 'useconfiguratorid' => 'custom', + ], + 'id' => '12345678-90ab-cdef-1234-567890abcdef', + 'isHidden' => false, + 'parent' => $this->app->page('test'), + 'type' => 'roomle-configurator' + ]); + + $block->configuratorId(); + } + + /** + * @covers ::configuratorId + * @covers ::option + */ + public function testConfiguratorId_Invalid2() + { + $this->expectException('Kirby\Exception\InvalidArgumentException'); + $this->expectExceptionMessage('Missing or invalid configurator ID setting'); + + $app = $this->app->clone([ + 'options' => [ + 'lukasbestle.roomle' => [ + 'configuratorId' => null + ] + ] + ]); + + $block = new ConfiguratorBlock([ + 'content' => [ + 'configuratorid' => 'demoConfigurator', + 'useconfiguratorid' => 'default', + ], + 'id' => '12345678-90ab-cdef-1234-567890abcdef', + 'isHidden' => false, + 'parent' => $app->page('test'), + 'type' => 'roomle-configurator' + ]); + + $block->configuratorId(); + } + + /** + * @covers ::configuratorId + */ + public function testConfiguratorId_Invalid3() + { + $this->expectException('Kirby\Exception\InvalidArgumentException'); + $this->expectExceptionMessage('Missing or invalid configurator ID setting'); + + $block = new ConfiguratorBlock([ + 'content' => [ + 'configuratorid' => 'demoConfigurator', + 'useconfiguratorid' => 'invalid', + ], + 'id' => '12345678-90ab-cdef-1234-567890abcdef', + 'isHidden' => false, + 'parent' => $this->app->page('test'), + 'type' => 'roomle-configurator' + ]); + + $block->configuratorId(); + } + + /** + * @coversNothing + */ + public function testFields() + { + $block = new ConfiguratorBlock([ + 'content' => [ + 'configuratorid' => $configuratorId = 'demoConfigurator', + 'mainproductid' => $mainProductId = 'some:product', + 'options' => $options = 'some: option', + 'target' => $target = ['default-target'], + 'useconfiguratorid' => $useConfiguratorId = 'custom', + 'usetarget' => $useTarget = 'custom', + 'variants' => $variants = [ + [ + 'productid' => 'some:variant', + 'title' => 'Some variant', + 'subtitle' => 'for cool people', + 'image' => ['image.png'] + ] + ] + ], + 'id' => $id = '12345678-90ab-cdef-1234-567890abcdef', + 'isHidden' => false, + 'parent' => $this->app->page('test'), + 'type' => 'roomle-configurator' + ]); + + $this->assertSame($configuratorId, $block->content()->configuratorId()->value()); + $this->assertSame($mainProductId, $block->content()->mainProductId()->value()); + $this->assertSame($options, $block->content()->options()->value()); + $this->assertSame($target, $block->content()->target()->value()); + $this->assertSame($useConfiguratorId, $block->content()->useConfiguratorId()->value()); + $this->assertSame($useTarget, $block->content()->useTarget()->value()); + $this->assertSame($variants, $block->content()->variants()->value()); + + $this->assertSame($id, $block->id()); + } + + /** + * @covers ::frontendJson + */ + public function testFrontendJson() + { + $block = new ConfiguratorBlock([ + 'content' => [ + 'mainproductid' => 'some:product', + 'options' => '', + 'useconfiguratorid' => 'default', + 'usetarget' => 'default' + ], + 'id' => '12345678-90ab-cdef-1234-567890abcdef', + 'isHidden' => false, + 'parent' => $this->app->page('test'), + 'type' => 'roomle-configurator' + ]); + + $this->assertSame(json_encode([ + 'configuratorId' => 'defaultConfigurator', + 'htmlId' => 'roomle-12345678-90ab-cdef-1234-567890abcdef', + 'options' => [ + 'deeplink' => ( + 'https://example.com/test' . + '?roomle-12345678-90ab-cdef-1234-567890abcdef=#CONFIGURATIONID#' . + '#roomle-12345678-90ab-cdef-1234-567890abcdef' + ), + 'some' => 'default', + 'other' => 'default', + 'id' => 'some:product', + ], + 'targetUrl' => 'https://example.com/default-target' + ]), $block->frontendJson()); + } + + /** + * @covers ::frontendProps + */ + public function testFrontendProps() + { + $block = new ConfiguratorBlock([ + 'content' => [ + 'mainproductid' => 'some:product', + 'options' => '', + 'useconfiguratorid' => 'default', + 'usetarget' => 'default' + ], + 'id' => '12345678-90ab-cdef-1234-567890abcdef', + 'isHidden' => false, + 'parent' => $this->app->page('test'), + 'type' => 'roomle-configurator' + ]); + + $this->assertSame([ + 'configuratorId' => 'defaultConfigurator', + 'htmlId' => 'roomle-12345678-90ab-cdef-1234-567890abcdef', + 'options' => [ + 'deeplink' => ( + 'https://example.com/test' . + '?roomle-12345678-90ab-cdef-1234-567890abcdef=#CONFIGURATIONID#' . + '#roomle-12345678-90ab-cdef-1234-567890abcdef' + ), + 'some' => 'default', + 'other' => 'default', + 'id' => 'some:product', + ], + 'targetUrl' => 'https://example.com/default-target' + ], $block->frontendProps()); + } + + /** + * @covers ::hasVariants + */ + public function testHasVariants_No() + { + $block = new ConfiguratorBlock([ + 'content' => [ + 'variants' => [] + ], + 'id' => '12345678-90ab-cdef-1234-567890abcdef', + 'isHidden' => false, + 'parent' => $this->app->page('test'), + 'type' => 'roomle-configurator' + ]); + + $this->assertFalse($block->hasVariants()); + } + + /** + * @covers ::hasVariants + */ + public function testHasVariants_Yes() + { + $block = new ConfiguratorBlock([ + 'content' => [ + 'variants' => [ + [ + 'productid' => 'some:variant', + 'title' => 'Some variant', + 'subtitle' => 'for cool people', + 'image' => ['image.png'] + ] + ] + ], + 'id' => '12345678-90ab-cdef-1234-567890abcdef', + 'isHidden' => false, + 'parent' => $this->app->page('test'), + 'type' => 'roomle-configurator' + ]); + + $this->assertTrue($block->hasVariants()); + } + + /** + * @covers ::htmlId + */ + public function testHtmlId() + { + $block = new ConfiguratorBlock([ + 'content' => [], + 'id' => '12345678-90ab-cdef-1234-567890abcdef', + 'isHidden' => false, + 'parent' => $this->app->page('test'), + 'type' => 'roomle-configurator' + ]); + + $this->assertSame('roomle-12345678-90ab-cdef-1234-567890abcdef', $block->htmlId()); + } + + /** + * @covers ::jsUrl + */ + public function testJsUrl() + { + $block = new ConfiguratorBlock([ + 'content' => [], + 'id' => '12345678-90ab-cdef-1234-567890abcdef', + 'isHidden' => false, + 'parent' => $this->app->page('test'), + 'type' => 'roomle-configurator' + ]); + + $this->assertSame('https://media.example.com/plugins/lukasbestle/roomle/configurator.js', $block->jsUrl()); + } + + /** + * @covers ::mainProductId + */ + public function testMainProductId() + { + $block = new ConfiguratorBlock([ + 'content' => [ + 'mainproductid' => 'some:product' + ], + 'id' => '12345678-90ab-cdef-1234-567890abcdef', + 'isHidden' => false, + 'parent' => $this->app->page('test'), + 'type' => 'roomle-configurator' + ]); + + $this->assertSame('some:product', $block->mainProductId()); + } + + /** + * @covers ::mainProductId + */ + public function testMainProductId_Invalid() + { + $this->expectException('Kirby\Exception\InvalidArgumentException'); + $this->expectExceptionMessage('Missing main product ID'); + + $block = new ConfiguratorBlock([ + 'content' => [ + 'mainproductid' => '' + ], + 'id' => '12345678-90ab-cdef-1234-567890abcdef', + 'isHidden' => false, + 'parent' => $this->app->page('test'), + 'type' => 'roomle-configurator' + ]); + + $block->mainProductId(); + } + + /** + * @covers ::options + * @covers ::option + */ + public function testOptions_Defaults() + { + $block = new ConfiguratorBlock([ + 'content' => [ + 'mainproductid' => 'some:product', + 'options' => '', + 'usetarget' => 'default' + ], + 'id' => '12345678-90ab-cdef-1234-567890abcdef', + 'isHidden' => false, + 'parent' => $this->app->page('test'), + 'type' => 'roomle-configurator' + ]); + + $this->assertSame([ + 'deeplink' => ( + 'https://example.com/test' . + '?roomle-12345678-90ab-cdef-1234-567890abcdef=#CONFIGURATIONID#' . + '#roomle-12345678-90ab-cdef-1234-567890abcdef' + ), + 'some' => 'default', + 'other' => 'default', + 'id' => 'some:product', + ], $block->options()); + } + + /** + * @covers ::options + * @covers ::option + */ + public function testOptions_Locale1() + { + $app = $this->app->clone([ + 'languages' => [ + ['code' => 'en'], + ['code' => 'de'] + ] + ]); + $app->setCurrentLanguage('de'); + + $block = new ConfiguratorBlock([ + 'content' => [ + 'mainproductid' => 'some:product', + 'options' => '', + 'usetarget' => 'default' + ], + 'id' => '12345678-90ab-cdef-1234-567890abcdef', + 'isHidden' => false, + 'parent' => $app->page('test'), + 'type' => 'roomle-configurator' + ]); + + $this->assertSame([ + 'deeplink' => ( + 'https://example.com/de/test' . + '?roomle-12345678-90ab-cdef-1234-567890abcdef=#CONFIGURATIONID#' . + '#roomle-12345678-90ab-cdef-1234-567890abcdef' + ), + 'locale' => 'de', + 'some' => 'default', + 'other' => 'default', + 'id' => 'some:product', + ], $block->options()); + } + + /** + * @covers ::options + * @covers ::option + */ + public function testOptions_Locale2() + { + $app = $this->app->clone([ + 'languages' => [ + ['code' => 'en-us'], + ['code' => 'de-at'] + ] + ]); + $app->setCurrentLanguage('de-at'); + + $block = new ConfiguratorBlock([ + 'content' => [ + 'mainproductid' => 'some:product', + 'options' => '', + 'usetarget' => 'default' + ], + 'id' => '12345678-90ab-cdef-1234-567890abcdef', + 'isHidden' => false, + 'parent' => $app->page('test'), + 'type' => 'roomle-configurator' + ]); + + $this->assertSame([ + 'deeplink' => ( + 'https://example.com/de-at/test' . + '?roomle-12345678-90ab-cdef-1234-567890abcdef=#CONFIGURATIONID#' . + '#roomle-12345678-90ab-cdef-1234-567890abcdef' + ), + 'locale' => 'de', + 'overrideCountry' => 'at', + 'some' => 'default', + 'other' => 'default', + 'id' => 'some:product', + ], $block->options()); + } + + /** + * @covers ::options + * @covers ::option + */ + public function testOptions_MissingProduct() + { + $this->expectException('Kirby\Exception\InvalidArgumentException'); + $this->expectExceptionMessage('Missing main product ID'); + + $block = new ConfiguratorBlock([ + 'content' => [ + 'options' => '', + 'usetarget' => 'default' + ], + 'id' => '12345678-90ab-cdef-1234-567890abcdef', + 'isHidden' => false, + 'parent' => $this->app->page('test'), + 'type' => 'roomle-configurator' + ]); + + $block->options(); + } + + /** + * @covers ::options + * @covers ::option + */ + public function testOptions_NoTarget() + { + $block = new ConfiguratorBlock([ + 'content' => [ + 'mainproductid' => 'some:product', + 'options' => '', + 'usetarget' => 'none' + ], + 'id' => '12345678-90ab-cdef-1234-567890abcdef', + 'isHidden' => false, + 'parent' => $this->app->page('test'), + 'type' => 'roomle-configurator' + ]); + + $this->assertSame([ + 'deeplink' => ( + 'https://example.com/test' . + '?roomle-12345678-90ab-cdef-1234-567890abcdef=#CONFIGURATIONID#' . + '#roomle-12345678-90ab-cdef-1234-567890abcdef' + ), + 'buttons' => [ + 'requestproduct' => false + ], + 'some' => 'default', + 'other' => 'default', + 'id' => 'some:product', + ], $block->options()); + } + + /** + * @covers ::options + * @covers ::option + */ + public function testOptions_Overrides() + { + $block = new ConfiguratorBlock([ + 'content' => [ + 'mainproductid' => 'some:product', + 'options' => "some: custom value\nskin:\n primary-color: '#123456'\nid: should not be used", + 'usetarget' => 'default' + ], + 'id' => '12345678-90ab-cdef-1234-567890abcdef', + 'isHidden' => false, + 'parent' => $this->app->page('test'), + 'type' => 'roomle-configurator' + ]); + + $this->assertSame([ + 'deeplink' => ( + 'https://example.com/test' . + '?roomle-12345678-90ab-cdef-1234-567890abcdef=#CONFIGURATIONID#' . + '#roomle-12345678-90ab-cdef-1234-567890abcdef' + ), + 'some' => 'custom value', + 'other' => 'default', + 'skin' => [ + 'primary-color' => '#123456' + ], + 'id' => 'some:product', + ], $block->options()); + } + + /** + * @covers ::targetUrl + */ + public function testTargetUrl_Custom() + { + $block = new ConfiguratorBlock([ + 'content' => [ + 'target' => ['custom-target'], + 'usetarget' => 'custom' + ], + 'id' => '12345678-90ab-cdef-1234-567890abcdef', + 'isHidden' => false, + 'parent' => $this->app->page('test'), + 'type' => 'roomle-configurator' + ]); + + $this->assertSame('https://example.com/custom-target', $block->targetUrl()); + } + + /** + * @covers ::targetUrl + */ + public function testTargetUrl_CustomInvalid() + { + $block = new ConfiguratorBlock([ + 'content' => [ + 'target' => ['does-not-exist'], + 'usetarget' => 'custom' + ], + 'id' => '12345678-90ab-cdef-1234-567890abcdef', + 'isHidden' => false, + 'parent' => $this->app->page('test'), + 'type' => 'roomle-configurator' + ]); + + $this->assertNull($block->targetUrl()); + } + + /** + * @covers ::targetUrl + * @covers ::option + */ + public function testTargetUrl_Default1() + { + $block = new ConfiguratorBlock([ + 'content' => [ + 'target' => ['custom-target'], + 'usetarget' => 'default' + ], + 'id' => '12345678-90ab-cdef-1234-567890abcdef', + 'isHidden' => false, + 'parent' => $this->app->page('test'), + 'type' => 'roomle-configurator' + ]); + + $this->assertSame('https://example.com/default-target', $block->targetUrl()); + } + + /** + * @covers ::targetUrl + * @covers ::option + */ + public function testTargetUrl_Default2() + { + $app = $this->app->clone([ + 'options' => [ + 'lukasbestle.roomle' => [ + 'target' => 'not-a-page' + ] + ] + ]); + + $block = new ConfiguratorBlock([ + 'content' => [ + 'target' => ['custom-target'], + 'usetarget' => 'default' + ], + 'id' => '12345678-90ab-cdef-1234-567890abcdef', + 'isHidden' => false, + 'parent' => $app->page('test'), + 'type' => 'roomle-configurator' + ]); + + $this->assertSame('https://example.com/not-a-page', $block->targetUrl()); + } + + /** + * @covers ::targetUrl + * @covers ::option + */ + public function testTargetUrl_Default3() + { + $app = $this->app->clone([ + 'options' => [ + 'lukasbestle.roomle' => [ + 'target' => 'https://external.com/target' + ] + ] + ]); + + $block = new ConfiguratorBlock([ + 'content' => [ + 'target' => ['custom-target'], + 'usetarget' => 'default' + ], + 'id' => '12345678-90ab-cdef-1234-567890abcdef', + 'isHidden' => false, + 'parent' => $app->page('test'), + 'type' => 'roomle-configurator' + ]); + + $this->assertSame('https://external.com/target', $block->targetUrl()); + } + + /** + * @covers ::targetUrl + * @covers ::option + */ + public function testTargetUrl_Default4() + { + $app = $this->app->clone([ + 'options' => [ + 'lukasbestle.roomle' => [ + 'target' => null + ] + ] + ]); + + $block = new ConfiguratorBlock([ + 'content' => [ + 'target' => ['custom-target'], + 'usetarget' => 'default' + ], + 'id' => '12345678-90ab-cdef-1234-567890abcdef', + 'isHidden' => false, + 'parent' => $app->page('test'), + 'type' => 'roomle-configurator' + ]); + + $this->assertNull($block->targetUrl()); + } + + /** + * @covers ::Variants + */ + public function testVariants() + { + $block = new ConfiguratorBlock([ + 'content' => [ + 'variants' => [ + [ + 'productid' => 'some:variant', + 'title' => 'Some variant', + 'subtitle' => 'for cool people', + 'image' => ['image.png'] + ], + [ + 'productid' => 'another:variant', + 'title' => 'Another variant', + 'subtitle' => 'for even cooler people', + 'image' => ['image.png'] + ] + ] + ], + 'id' => '12345678-90ab-cdef-1234-567890abcdef', + 'isHidden' => false, + 'parent' => $this->app->page('test'), + 'type' => 'roomle-configurator' + ]); + + $variants = $block->variants(); + + $this->assertSame(2, $variants->count()); + $this->assertInstanceOf(Page::class, $variants->parent()); + $this->assertSame($this->app->page('test'), $variants->parent()); + + $firstVariant = $variants->first(); + + $this->assertInstanceOf(ConfiguratorVariant::class, $firstVariant); + $this->assertSame('some:variant', $firstVariant->productId()->value()); + $this->assertInstanceOf(Page::class, $firstVariant->parent()); + $this->assertSame($this->app->page('test'), $firstVariant->parent()); + $this->assertInstanceOf(File::class, $firstVariant->image()); + $this->assertSame($this->app->file('test/image.png'), $firstVariant->image()); + } +} diff --git a/tests/Roomle/ConfiguratorVariantTest.php b/tests/Roomle/ConfiguratorVariantTest.php new file mode 100644 index 0000000..4494f7a --- /dev/null +++ b/tests/Roomle/ConfiguratorVariantTest.php @@ -0,0 +1,146 @@ +app = new App([ + 'site' => [ + 'children' => [ + [ + 'slug' => 'test', + 'files' => [ + ['filename' => 'image.png'] + ] + ] + ] + ] + ]); + } + + /** + * @coversNothing + */ + public function testConstruct() + { + $variant = new ConfiguratorVariant([ + 'content' => [ + 'productid' => $productId = 'some:variant', + 'title' => $title = 'Some variant', + 'subtitle' => $subtitle = 'for cool people', + 'image' => $image = ['image.png'] + ], + 'id' => 0, + 'parent' => $this->app->page('test') + ]); + + $this->assertSame($productId, $variant->content()->productId()->value()); + $this->assertSame($title, $variant->content()->title()->value()); + $this->assertSame($subtitle, $variant->content()->subtitle()->value()); + $this->assertSame($image, $variant->content()->image()->value()); + + $this->assertSame($productId, $variant->productId()->value()); + $this->assertSame($title, $variant->title()->value()); + $this->assertSame($subtitle, $variant->subtitle()->value()); + } + + /** + * @covers ::image + */ + public function testImage_Configuration() + { + $variant = new ConfiguratorVariant([ + 'content' => [ + 'productid' => 'some:variant:hash', + 'title' => 'Some variant', + 'subtitle' => 'for cool people', + 'image' => [] + ], + 'id' => 0, + 'parent' => $this->app->page('test') + ]); + + $image = $variant->image(); + + $this->assertInstanceOf(Image::class, $image); + $this->assertSame('https://uploads.roomle.com/configurations/some:variant:hash/perspectiveImage.png', $image->url()); + } + + /** + * @covers ::image + */ + public function testImage_File() + { + $variant = new ConfiguratorVariant([ + 'content' => [ + 'productid' => 'some:variant', + 'title' => 'Some variant', + 'subtitle' => 'for cool people', + 'image' => ['image.png'] + ], + 'id' => 0, + 'parent' => $this->app->page('test') + ]); + + $image = $variant->image(); + + $this->assertInstanceOf(File::class, $image); + $this->assertSame($this->app->file('test/image.png'), $image); + } + + /** + * @covers ::image + */ + public function testImage_FileInvalid() + { + $variant = new ConfiguratorVariant([ + 'content' => [ + 'productid' => 'some:variant', + 'title' => 'Some variant', + 'subtitle' => 'for cool people', + 'image' => ['does-not-exist.png'] + ], + 'id' => 0, + 'parent' => $this->app->page('test') + ]); + + $image = $variant->image(); + + $this->assertInstanceOf(Image::class, $image); + $this->assertSame('https://www.roomle.com/api/v2/items/some:variant/perspectiveImageHD', $image->url()); + } + + /** + * @covers ::image + */ + public function testImage_Item() + { + $variant = new ConfiguratorVariant([ + 'content' => [ + 'productid' => 'some:variant', + 'title' => 'Some variant', + 'subtitle' => 'for cool people', + 'image' => [] + ], + 'id' => 0, + 'parent' => $this->app->page('test') + ]); + + $image = $variant->image(); + + $this->assertInstanceOf(Image::class, $image); + $this->assertSame('https://www.roomle.com/api/v2/items/some:variant/perspectiveImageHD', $image->url()); + } +} diff --git a/tests/Roomle/ParameterTest.php b/tests/Roomle/ParameterTest.php new file mode 100644 index 0000000..a342f19 --- /dev/null +++ b/tests/Roomle/ParameterTest.php @@ -0,0 +1,133 @@ + $key = 'height', + 'type' => $type = 'Decimal', + 'unitType' => $unitType = 'length', + + // additional fields without documented properties + 'sort' => $sort = 0, + ]); + + $this->assertSame($key, $parameter->key()); + $this->assertSame($type, $parameter->type()); + $this->assertSame($unitType, $parameter->unitType()); + $this->assertSame($sort, $parameter->sort()); + } + + /** + * @covers ::label + */ + public function testLabel() + { + $parameter = new Parameter([ + 'key' => 'height', + 'label' => 'Height' + ]); + + $this->assertSame('Height', $parameter->label()); + } + + /** + * @covers ::label + */ + public function testLabel_Fallback() + { + $parameter = new Parameter([ + 'key' => 'height', + 'label' => null + ]); + + $this->assertSame('height', $parameter->label()); + } + + /** + * @covers ::__toString + */ + public function testToString() + { + $parameter = new Parameter([ + 'key' => 'height', + 'label' => 'Height', + 'type' => 'Decimal', + 'unitType' => 'length', + 'value' => '123.0' + ]); + + $this->assertStringEqualsFile(__DIR__ . '/fixtures/parameter.txt', (string)$parameter); + } + + /** + * @covers ::value + */ + public function testValue() + { + $parameter = new Parameter([ + 'type' => 'String', + 'value' => '123.0' + ]); + + $this->assertSame('123.0', $parameter->value()); + } + + /** + * @covers ::value + */ + public function testValue_Decimal() + { + $parameter = new Parameter([ + 'type' => 'Decimal', + 'value' => '123.0' + ]); + + $this->assertSame(123.0, $parameter->value()); + } + + /** + * @covers ::valueLabel + */ + public function testValueLabel() + { + $parameter = new Parameter([ + 'type' => 'String', + 'value' => '123.0', + 'valueLabel' => 'Some string' + ]); + + $this->assertSame('Some string', $parameter->valueLabel()); + } + + /** + * @covers ::valueLabel + */ + public function testValueLabel_Length() + { + $parameter = new Parameter([ + 'type' => 'Decimal', + 'unitType' => 'length', + 'value' => '123.0', + 'valueLabel' => 'should not be used' + ]); + + $this->assertSame('12.3 cm', $parameter->valueLabel()); + } +} diff --git a/tests/Roomle/ParametersTest.php b/tests/Roomle/ParametersTest.php new file mode 100644 index 0000000..3674357 --- /dev/null +++ b/tests/Roomle/ParametersTest.php @@ -0,0 +1,67 @@ + new Parameter([ + 'key' => 'height', + 'label' => 'Height', + 'type' => 'Decimal', + 'unitType' => 'length', + 'value' => '123.0' + ]), + 'width' => new Parameter([ + 'key' => 'width', + 'label' => 'Width', + 'type' => 'Decimal', + 'unitType' => 'length', + 'value' => '456.0' + ]) + ]); + + $this->assertSame(2, $parameters->count()); + $this->assertSame(['height', 'width'], $parameters->keys()); + $this->assertInstanceOf(Parameter::class, $parameters->first()); + $this->assertSame('12.3 cm', $parameters->first()->valueLabel()); + } + + /** + * @covers ::rawData + */ + public function testRawData() + { + $parameters = new Parameters([ + 'height' => new Parameter([ + 'key' => 'height', + 'label' => 'Height', + 'type' => 'Decimal', + 'unitType' => 'length', + 'value' => '123.0' + ]), + 'width' => new Parameter([ + 'key' => 'width', + 'label' => 'Width', + 'type' => 'Decimal', + 'unitType' => 'length', + 'value' => '456.0' + ]) + ]); + + $this->assertSame([ + 'height' => 123.0, + 'width' => 456.0 + ], $parameters->rawData()); + } +} diff --git a/tests/Roomle/PartTest.php b/tests/Roomle/PartTest.php new file mode 100644 index 0000000..7158eca --- /dev/null +++ b/tests/Roomle/PartTest.php @@ -0,0 +1,147 @@ + $articleNr = '123.456.789', + 'componentId' => $componentId = 'some:component', + 'count' => $count = 2, + 'currencySymbol' => $currencySymbol = '€', + 'label' => $label = 'Some component', + 'packageSize' => $packageSize = 1, + 'price' => $price = 123.45, + 'retailerPrice' => $retailerPrice = 234.56, + + // additional fields without documented properties + 'subpartId' => $subpartId = 12, + 'hasGeometry' => $hasGeometry = false, + ]); + + $this->assertSame($articleNr, $part->articleNr()); + $this->assertSame($componentId, $part->componentId()); + $this->assertSame($count, $part->count()); + $this->assertSame($currencySymbol, $part->currencySymbol()); + $this->assertSame($label, $part->label()); + $this->assertSame($packageSize, $part->packageSize()); + $this->assertSame($price, $part->price()); + $this->assertSame($retailerPrice, $part->retailerPrice()); + $this->assertSame($subpartId, $part->subpartId()); + $this->assertSame($hasGeometry, $part->hasGeometry()); + } + + /** + * @covers ::parameters + */ + public function testParameters() + { + $part = new Part([ + 'parameters' => [ + [ + 'key' => 'height', + 'label' => 'Height', + 'type' => 'Decimal', + 'unitType' => 'length', + 'value' => '123.0' + ], + [ + 'key' => 'width', + 'label' => 'Width', + 'type' => 'Decimal', + 'unitType' => 'length', + 'value' => '456.0' + ] + ] + ]); + + $parameters = $part->parameters(); + + $this->assertSame(2, $parameters->count()); + $this->assertSame(['height', 'width'], $parameters->keys()); + $this->assertInstanceOf(Parameter::class, $parameters->first()); + $this->assertSame('12.3 cm', $parameters->first()->valueLabel()); + } + + /** + * @covers ::parameters + */ + public function testParameters_Invalid1() + { + $this->expectException('Kirby\Exception\InvalidArgumentException'); + $this->expectExceptionMessage('Invalid parameter 0'); + + $part = new Part([ + 'parameters' => [ + 'not an array' + ] + ]); + + $part->parameters(); + } + + /** + * @covers ::parameters + */ + public function testParameters_Invalid2() + { + $this->expectException('Kirby\Exception\InvalidArgumentException'); + $this->expectExceptionMessage('Parameter 0 does not have a key'); + + $part = new Part([ + 'parameters' => [ + [ + 'label' => 'Some parameter without key' + ] + ] + ]); + + $part->parameters(); + } + + /** + * @covers ::__toString + */ + public function testToString() + { + $part = new Part([ + 'articleNr' => '123.456.789', + 'componentId' => 'some:component1', + 'count' => 2, + 'label' => 'Some part', + 'parameters' => [ + [ + 'key' => 'height', + 'label' => 'Height', + 'type' => 'Decimal', + 'unitType' => 'length', + 'value' => '123.0' + ], + [ + 'key' => 'width', + 'label' => 'Width', + 'type' => 'Decimal', + 'unitType' => 'length', + 'value' => '456.0' + ] + ] + ]); + + $this->assertStringEqualsFile(__DIR__ . '/fixtures/part.txt', (string)$part); + } +} diff --git a/tests/Roomle/fixtures/configuration.txt b/tests/Roomle/fixtures/configuration.txt new file mode 100644 index 0000000..04606fa --- /dev/null +++ b/tests/Roomle/fixtures/configuration.txt @@ -0,0 +1,11 @@ +Some product (some:id) +W 5.6 cm / H 3.4 cm / D 1.2 cm +https://example.com/configurator + +2x Some part (123.456.789) +Height: 12.3 cm +Width: 45.6 cm + +1x Some other part (987.654.321) +Height: 78.9 cm + diff --git a/tests/Roomle/fixtures/parameter.txt b/tests/Roomle/fixtures/parameter.txt new file mode 100644 index 0000000..c9511aa --- /dev/null +++ b/tests/Roomle/fixtures/parameter.txt @@ -0,0 +1 @@ +Height: 12.3 cm \ No newline at end of file diff --git a/tests/Roomle/fixtures/part.txt b/tests/Roomle/fixtures/part.txt new file mode 100644 index 0000000..e2c3277 --- /dev/null +++ b/tests/Roomle/fixtures/part.txt @@ -0,0 +1,3 @@ +2x Some part (123.456.789) +Height: 12.3 cm +Width: 45.6 cm diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..f9b4c49 --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,10 @@ +