Compare commits
37 commits
Author | SHA1 | Date | |
---|---|---|---|
|
7d10842005 | ||
|
25ab08b982 | ||
|
3f316e18e7 | ||
|
c74a9f0dee | ||
|
bbf5bff023 | ||
|
58ed576b85 | ||
|
2961b87eb2 | ||
|
e799ca420a | ||
|
954eaad446 | ||
|
9bad471bf3 | ||
|
cf82f550b7 | ||
|
824d5311ec | ||
|
55ce44aebf | ||
|
43b73bbe5f | ||
|
c59e56fba7 | ||
|
dd3b797ec2 | ||
|
36bd59ef02 | ||
|
78ea484a71 | ||
|
23b8a9d6c3 | ||
|
6e759ea306 | ||
|
3959627a52 | ||
|
db117dab29 | ||
|
1c8d6db391 | ||
|
f45c77ba64 | ||
|
a199f2465f | ||
|
95e65cd005 | ||
|
2ec8bb4303 | ||
|
5ba1b0ae64 | ||
|
005af71c57 | ||
|
bc45e56262 | ||
|
85bcf00f09 | ||
|
2685d0f720 | ||
|
1a0ca50647 | ||
|
caa2c9f559 | ||
|
a7c0548b7e | ||
|
0dc94a7edd | ||
|
72bc29ba66 |
25 changed files with 919 additions and 461 deletions
40
CHANGELOG.md
40
CHANGELOG.md
|
@ -12,6 +12,46 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
|
||||
## [0.5.0] - 2023-09-29
|
||||
|
||||
### Changed
|
||||
- If the browser uses a dark theme, the default theme is also set to dark
|
||||
- Display the navigation bar on every view, except the creation of the first account
|
||||
|
||||
### Fixed
|
||||
- The account deletion view is now emphasized the same way it was before
|
||||
|
||||
|
||||
## [0.4.0] - 2023-09-26
|
||||
|
||||
### Added
|
||||
- Possibility to set a default account
|
||||
- Dark mode
|
||||
- Use the browser's preferred language if available
|
||||
|
||||
### Changed
|
||||
- The style has been entirely reworked using Bootstrap instead of Bulma
|
||||
- It is now impossible to include the separator in the dedicated name
|
||||
- When adding a new account, error messages are displayed alongside each affected elements whenever possible
|
||||
- By default, the new sub-address form reset button switches to the default account
|
||||
|
||||
## Fixed
|
||||
- Invalid preferences are now automatically corrected
|
||||
- It is now impossible to add the same account twice
|
||||
|
||||
|
||||
## [0.3.0] - 2023-08-25
|
||||
|
||||
### Added
|
||||
- The main view now includes a reset button
|
||||
- An error message is now displayed if JavaScript is disabled
|
||||
|
||||
### Changed
|
||||
- Secret keys are now restricted to 128 bits (16 bytes) or 256 bits (32 bytes)
|
||||
- The local part cannot contain the separator
|
||||
- The HTML lang attribute is now set to the appropriate language
|
||||
|
||||
|
||||
## [0.2.0] - 2023-08-11
|
||||
|
||||
### Added
|
||||
|
|
202
LICENSE-APACHE-2.0.txt
Normal file
202
LICENSE-APACHE-2.0.txt
Normal file
|
@ -0,0 +1,202 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
19
LICENSE-MIT.txt
Normal file
19
LICENSE-MIT.txt
Normal file
|
@ -0,0 +1,19 @@
|
|||
Copyright 2023 Rodolphe Bréard
|
||||
|
||||
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.
|
13
README.md
13
README.md
|
@ -1,6 +1,15 @@
|
|||
# Sub-Address KEy (SAKE) app
|
||||
|
||||
Web application that can be used to generate new sub-addresses as defined in the [Sub-Address KEy (SAKE) filter](https://github.com/breard-r/opensmtpd-filter-sake).
|
||||
Web application that can be used to generate new sub-addresses as defined in the [Sub-Address KEy (SAKE) filter](https://git.what.tf/rodolphe/opensmtpd-filter-sake).
|
||||
|
||||
## Install
|
||||
|
||||
Download the build from [the latest released version](https://git.what.tf/rodolphe/sake-app/releases). Extract the archive and configure your web server to serve those files.
|
||||
|
||||
That's it. The final build is plain HTML/CSS/JS with a few assets, therefore there is no back-end to configure.
|
||||
|
||||
Alternatively, on ArchLinux you can install the [sake-app](https://aur.archlinux.org/packages/sake-app) package from the AUR. You will find the files in the `/usr/share/webapps/sake-app` directory.
|
||||
|
||||
|
||||
## Project Setup
|
||||
|
||||
|
@ -25,7 +34,7 @@ npm run build
|
|||
|
||||
## Can I use this app without self-hosting it?
|
||||
|
||||
Unfortunately, although it would be ok, there is currently no known hosted version of this application. This should change in the future.
|
||||
Yes, you can use [https://sake.email/](https://sake.email/). This instance is kept up to date.
|
||||
|
||||
## Can I use this app completely offline?
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta charset="utf-8">
|
||||
<link rel="icon" href="favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Sub-Address KEy</title>
|
||||
|
@ -9,5 +9,8 @@
|
|||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="src/main.js"></script>
|
||||
<noscript>
|
||||
<p>This application's main purpose is to compute values on the client side, therefore JavaScript is required.</p>
|
||||
</noscript>
|
||||
</body>
|
||||
</html>
|
||||
|
|
508
package-lock.json
generated
508
package-lock.json
generated
|
@ -1,34 +1,36 @@
|
|||
{
|
||||
"name": "sake-app",
|
||||
"version": "0.1.0",
|
||||
"version": "0.5.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "sake-app",
|
||||
"version": "0.1.0",
|
||||
"version": "0.5.0",
|
||||
"license": "(MIT OR Apache-2.0)",
|
||||
"dependencies": {
|
||||
"@noble/hashes": "^1.3.1",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@vueuse/core": "^10.2.1",
|
||||
"base32-encode": "^2.0.0",
|
||||
"bulma": "^0.9.4",
|
||||
"bootstrap": "^5.3.2",
|
||||
"bootswatch": "^5.3.2",
|
||||
"vue": "^3.3.4",
|
||||
"vue-i18n": "^9.2.2",
|
||||
"vue-qrcode-reader": "^5.1.0",
|
||||
"vue-router": "^4.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@intlify/unplugin-vue-i18n": "^0.12.2",
|
||||
"@intlify/unplugin-vue-i18n": "^1.2.0",
|
||||
"@vitejs/plugin-vue": "^4.2.3",
|
||||
"sass": "^1.64.1",
|
||||
"vite": "^4.4.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.22.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.10.tgz",
|
||||
"integrity": "sha512-lNbdGsQb9ekfsnjFGhEiF4hfFqGgfOP3H3d27re3n+CGhNuTSUEQdfWk556sTLNTloczcdM5TYF2LhzmDQKyvQ==",
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz",
|
||||
"integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==",
|
||||
"bin": {
|
||||
"parser": "bin/babel-parser.js"
|
||||
},
|
||||
|
@ -389,21 +391,21 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@intlify/bundle-utils": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/bundle-utils/-/bundle-utils-7.0.2.tgz",
|
||||
"integrity": "sha512-8wbx9xhbawBFTE5LPTECiK26RRqrNS31jyWSur72ZXZZ4it5jiZTcG6eUJlNirr4+jXYio2DGY299JsGVT4cpw==",
|
||||
"version": "7.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/bundle-utils/-/bundle-utils-7.4.0.tgz",
|
||||
"integrity": "sha512-AQfjBe2HUxzyN8ignIk3WhhSuVcSuirgzOzkd17nb337rCbI4Gv/t1R60UUyIqFoFdviLb/wLcDUzTD/xXjv9w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@intlify/message-compiler": "9.3.0-beta.24",
|
||||
"@intlify/shared": "9.3.0-beta.24",
|
||||
"@intlify/message-compiler": "^9.4.0",
|
||||
"@intlify/shared": "^9.4.0",
|
||||
"acorn": "^8.8.2",
|
||||
"escodegen": "^2.0.0",
|
||||
"estree-walker": "^2.0.2",
|
||||
"jsonc-eslint-parser": "^1.0.1",
|
||||
"jsonc-eslint-parser": "^2.3.0",
|
||||
"magic-string": "^0.30.0",
|
||||
"mlly": "^1.2.0",
|
||||
"source-map-js": "^1.0.1",
|
||||
"yaml-eslint-parser": "^0.3.2"
|
||||
"yaml-eslint-parser": "^1.2.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14.16"
|
||||
|
@ -417,13 +419,27 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@intlify/bundle-utils/node_modules/@intlify/message-compiler": {
|
||||
"version": "9.3.0-beta.24",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.3.0-beta.24.tgz",
|
||||
"integrity": "sha512-prhHATkgp0mpPqoVgiAtLmUc1JMvs8fMH6w53AVEBn+VF87dLhzanfmWY5FoZWORG51ag54gBDBOoM/VFv3m3A==",
|
||||
"dev": true,
|
||||
"node_modules/@intlify/core-base": {
|
||||
"version": "9.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.5.0.tgz",
|
||||
"integrity": "sha512-y3ufM1RJbI/DSmJf3lYs9ACq3S/iRvaSsE3rPIk0MGH7fp+JxU6rdryv/EYcwfcr3Y1aHFlCBir6S391hRZ57w==",
|
||||
"dependencies": {
|
||||
"@intlify/shared": "9.3.0-beta.24",
|
||||
"@intlify/message-compiler": "9.5.0",
|
||||
"@intlify/shared": "9.5.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/kazupon"
|
||||
}
|
||||
},
|
||||
"node_modules/@intlify/message-compiler": {
|
||||
"version": "9.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.5.0.tgz",
|
||||
"integrity": "sha512-CAhVNfEZcOVFg0/5MNyt+OFjvs4J/ARjCj2b+54/FvFP0EDJI5lIqMTSDBE7k0atMROSP0SvWCkwu/AZ5xkK1g==",
|
||||
"dependencies": {
|
||||
"@intlify/shared": "9.5.0",
|
||||
"source-map-js": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
|
@ -433,11 +449,10 @@
|
|||
"url": "https://github.com/sponsors/kazupon"
|
||||
}
|
||||
},
|
||||
"node_modules/@intlify/bundle-utils/node_modules/@intlify/shared": {
|
||||
"version": "9.3.0-beta.24",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.3.0-beta.24.tgz",
|
||||
"integrity": "sha512-AKxJ8s7eKIQWkNaf4wyyoLRwf4puCuQgjSChlDJm5JBEt6T8HGgnYTJLRXu6LD/JACn3Qwu6hM/XRX1c9yvjmQ==",
|
||||
"dev": true,
|
||||
"node_modules/@intlify/shared": {
|
||||
"version": "9.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.5.0.tgz",
|
||||
"integrity": "sha512-tAxV14LMXZDZbu32XzLMTsowNlgJNmLwWHYzvMUl6L8gvQeoYiZONjY7AUsqZW8TOZDX9lfvF6adPkk9FSRdDA==",
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
},
|
||||
|
@ -445,59 +460,14 @@
|
|||
"url": "https://github.com/sponsors/kazupon"
|
||||
}
|
||||
},
|
||||
"node_modules/@intlify/core-base": {
|
||||
"version": "9.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.2.2.tgz",
|
||||
"integrity": "sha512-JjUpQtNfn+joMbrXvpR4hTF8iJQ2sEFzzK3KIESOx+f+uwIjgw20igOyaIdhfsVVBCds8ZM64MoeNSx+PHQMkA==",
|
||||
"dependencies": {
|
||||
"@intlify/devtools-if": "9.2.2",
|
||||
"@intlify/message-compiler": "9.2.2",
|
||||
"@intlify/shared": "9.2.2",
|
||||
"@intlify/vue-devtools": "9.2.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/@intlify/devtools-if": {
|
||||
"version": "9.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/devtools-if/-/devtools-if-9.2.2.tgz",
|
||||
"integrity": "sha512-4ttr/FNO29w+kBbU7HZ/U0Lzuh2cRDhP8UlWOtV9ERcjHzuyXVZmjyleESK6eVP60tGC9QtQW9yZE+JeRhDHkg==",
|
||||
"dependencies": {
|
||||
"@intlify/shared": "9.2.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/@intlify/message-compiler": {
|
||||
"version": "9.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.2.2.tgz",
|
||||
"integrity": "sha512-IUrQW7byAKN2fMBe8z6sK6riG1pue95e5jfokn8hA5Q3Bqy4MBJ5lJAofUsawQJYHeoPJ7svMDyBaVJ4d0GTtA==",
|
||||
"dependencies": {
|
||||
"@intlify/shared": "9.2.2",
|
||||
"source-map": "0.6.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/@intlify/shared": {
|
||||
"version": "9.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.2.2.tgz",
|
||||
"integrity": "sha512-wRwTpsslgZS5HNyM7uDQYZtxnbI12aGiBZURX3BTR9RFIKKRWpllTsgzHWvj3HKm3Y2Sh5LPC1r0PDCKEhVn9Q==",
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/@intlify/unplugin-vue-i18n": {
|
||||
"version": "0.12.2",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/unplugin-vue-i18n/-/unplugin-vue-i18n-0.12.2.tgz",
|
||||
"integrity": "sha512-IIgzLRSPUKZM1FBdUAZ9NwVPiLUr4ea5g/HLWe2lB7gNtPDz4FOfUNUllIT504hT+3pDoJmjaYJ6pyqT7F4Wuw==",
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/unplugin-vue-i18n/-/unplugin-vue-i18n-1.4.0.tgz",
|
||||
"integrity": "sha512-RGDchCRBlDTyVVFgPA1C1XC1uD4xYN81Ma+3EnU6GQ8pBEreraX/PWdPXXzOB6k9GWCQHuqii3atYXhcH3rpSg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@intlify/bundle-utils": "^7.0.2",
|
||||
"@intlify/shared": "9.3.0-beta.24",
|
||||
"@intlify/bundle-utils": "^7.4.0",
|
||||
"@intlify/shared": "^9.4.0",
|
||||
"@rollup/pluginutils": "^5.0.2",
|
||||
"@vue/compiler-sfc": "^3.2.47",
|
||||
"debug": "^4.3.3",
|
||||
|
@ -529,39 +499,15 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@intlify/unplugin-vue-i18n/node_modules/@intlify/shared": {
|
||||
"version": "9.3.0-beta.24",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.3.0-beta.24.tgz",
|
||||
"integrity": "sha512-AKxJ8s7eKIQWkNaf4wyyoLRwf4puCuQgjSChlDJm5JBEt6T8HGgnYTJLRXu6LD/JACn3Qwu6hM/XRX1c9yvjmQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/kazupon"
|
||||
}
|
||||
},
|
||||
"node_modules/@intlify/vue-devtools": {
|
||||
"version": "9.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/vue-devtools/-/vue-devtools-9.2.2.tgz",
|
||||
"integrity": "sha512-+dUyqyCHWHb/UcvY1MlIpO87munedm3Gn6E9WWYdWrMuYLcoIoOEVDWSS8xSwtlPU+kA+MEQTP6Q1iI/ocusJg==",
|
||||
"dependencies": {
|
||||
"@intlify/core-base": "9.2.2",
|
||||
"@intlify/shared": "9.2.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.4.15",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
|
||||
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
|
||||
},
|
||||
"node_modules/@noble/hashes": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz",
|
||||
"integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==",
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz",
|
||||
"integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==",
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
},
|
||||
|
@ -604,10 +550,19 @@
|
|||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/@popperjs/core": {
|
||||
"version": "2.11.8",
|
||||
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
|
||||
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/popperjs"
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/pluginutils": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.2.tgz",
|
||||
"integrity": "sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==",
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.4.tgz",
|
||||
"integrity": "sha512-0KJnIoRI8A+a1dqOYLxH8vBf8bphDmty5QvIm2hqm7oFCFYKCAZWWd2hXgMibaPsNDhI0AtpYfQZJG47pt/k4g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/estree": "^1.0.0",
|
||||
|
@ -626,32 +581,29 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@sec-ant/barcode-detector": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@sec-ant/barcode-detector/-/barcode-detector-1.1.2.tgz",
|
||||
"integrity": "sha512-9tI1RSpKtJlsBjkr5pQ1hZ2JQ+CEO12KRg7Pno11Tl5heb9YZeeaO/fq5uNDg/v1gn83HDpYOQsXG6cLgVrAKg==",
|
||||
"dependencies": {
|
||||
"@sec-ant/zxing-wasm": "^2.1.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@sec-ant/zxing-wasm": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@sec-ant/zxing-wasm/-/zxing-wasm-2.1.2.tgz",
|
||||
"integrity": "sha512-IFqipPQNL75RZwL4QkDDGYITGWTIdW967NQufCoblhz1KdLB6YsN+y+3JZAxPUBPw/6GUsnjhPJSeoQQc/Hx9w==",
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@sec-ant/zxing-wasm/-/zxing-wasm-2.1.5.tgz",
|
||||
"integrity": "sha512-U8/bq15/qytQ2GOfFeyoL9ItnBLLWlILpG3c0F03iZ3SlEZNJtUWd/DONjDAlbQ90TRzzOFFVw9AQhh75XlANQ==",
|
||||
"dependencies": {
|
||||
"@types/emscripten": "^1.39.7",
|
||||
"zustand": "^4.4.0"
|
||||
"zustand": "^4.4.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/dom-webcodecs": {
|
||||
"version": "0.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/dom-webcodecs/-/dom-webcodecs-0.1.8.tgz",
|
||||
"integrity": "sha512-KThTPaGQJLITk8Q0XkEkz+GqFdoWDyQfbyeJmfEUagB15TZQdNx5AqP2b7GP6vkVM6X/6T1Z8EHxA8RgHfY9BA=="
|
||||
},
|
||||
"node_modules/@types/emscripten": {
|
||||
"version": "1.39.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.39.7.tgz",
|
||||
"integrity": "sha512-tLqYV94vuqDrXh515F/FOGtBcRMTPGvVV1LzLbtYDcQmmhtpf/gLYf+hikBbQk8MzOHNz37wpFfJbYAuSn8HqA=="
|
||||
"version": "1.39.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.39.8.tgz",
|
||||
"integrity": "sha512-Rk0HKcMXFUuqT32k1kXHZWgxiMvsyYsmlnjp0rLKa0MMoqXLE3T9dogDBTRfuc3SAsXu97KD3k4SKR1lHqd57w=="
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz",
|
||||
"integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.2.tgz",
|
||||
"integrity": "sha512-VeiPZ9MMwXjO32/Xu7+OwflfmeoRwkE/qzndw42gGtgJwZopBnzy2gD//NN1+go1mADzkDcqf/KnFRSjTJ8xJA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/web-bluetooth": {
|
||||
|
@ -660,9 +612,9 @@
|
|||
"integrity": "sha512-4p9vcSmxAayx72yn70joFoL44c9MO/0+iVEBIQXe3v2h2SiAsEIo/G5v6ObFWvNKRFjbrVadNf9LqEEZeQPzdA=="
|
||||
},
|
||||
"node_modules/@vitejs/plugin-vue": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.2.3.tgz",
|
||||
"integrity": "sha512-R6JDUfiZbJA9cMiguQ7jxALsgiprjBeHL5ikpXfJCH62pPHtI+JdJ5xWj6Ev73yXSlYl86+blXn1kZHQ7uElxw==",
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.3.4.tgz",
|
||||
"integrity": "sha512-ciXNIHKPriERBisHFBvnTbfKa6r9SAesOYXeGDzgegcvy9Q4xdScSHAmKbNT0M3O0S9LKhIf5/G+UYG4NnnzYw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^14.18.0 || >=16.0.0"
|
||||
|
@ -780,13 +732,13 @@
|
|||
"integrity": "sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ=="
|
||||
},
|
||||
"node_modules/@vueuse/core": {
|
||||
"version": "10.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.3.0.tgz",
|
||||
"integrity": "sha512-BEM5yxcFKb5btFjTSAFjTu5jmwoW66fyV9uJIP4wUXXU8aR5Hl44gndaaXp7dC5HSObmgbnR2RN+Un1p68Mf5Q==",
|
||||
"version": "10.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.4.1.tgz",
|
||||
"integrity": "sha512-DkHIfMIoSIBjMgRRvdIvxsyboRZQmImofLyOHADqiVbQVilP8VVHDhBX2ZqoItOgu7dWa8oXiNnScOdPLhdEXg==",
|
||||
"dependencies": {
|
||||
"@types/web-bluetooth": "^0.0.17",
|
||||
"@vueuse/metadata": "10.3.0",
|
||||
"@vueuse/shared": "10.3.0",
|
||||
"@vueuse/metadata": "10.4.1",
|
||||
"@vueuse/shared": "10.4.1",
|
||||
"vue-demi": ">=0.14.5"
|
||||
},
|
||||
"funding": {
|
||||
|
@ -794,9 +746,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@vueuse/core/node_modules/vue-demi": {
|
||||
"version": "0.14.5",
|
||||
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.5.tgz",
|
||||
"integrity": "sha512-o9NUVpl/YlsGJ7t+xuqJKx8EBGf1quRhCiT6D/J0pfwmk9zUwYkC7yrF4SZCe6fETvSM3UNL2edcbYrSyc4QHA==",
|
||||
"version": "0.14.6",
|
||||
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz",
|
||||
"integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==",
|
||||
"hasInstallScript": true,
|
||||
"bin": {
|
||||
"vue-demi-fix": "bin/vue-demi-fix.js",
|
||||
|
@ -819,17 +771,17 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@vueuse/metadata": {
|
||||
"version": "10.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.3.0.tgz",
|
||||
"integrity": "sha512-Ema3YhNOa4swDsV0V7CEY5JXvK19JI/o1szFO1iWxdFg3vhdFtCtSTP26PCvbUpnUtNHBY2wx5y3WDXND5Pvnw==",
|
||||
"version": "10.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.4.1.tgz",
|
||||
"integrity": "sha512-2Sc8X+iVzeuMGHr6O2j4gv/zxvQGGOYETYXEc41h0iZXIRnRbJZGmY/QP8dvzqUelf8vg0p/yEA5VpCEu+WpZg==",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/@vueuse/shared": {
|
||||
"version": "10.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.3.0.tgz",
|
||||
"integrity": "sha512-kGqCTEuFPMK4+fNWy6dUOiYmxGcUbtznMwBZLC1PubidF4VZY05B+Oht7Jh7/6x4VOWGpvu3R37WHi81cKpiqg==",
|
||||
"version": "10.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.4.1.tgz",
|
||||
"integrity": "sha512-vz5hbAM4qA0lDKmcr2y3pPdU+2EVw/yzfRsBdu+6+USGa4PxqSQRYIUC9/NcT06y+ZgaTsyURw2I9qOFaaXHAg==",
|
||||
"dependencies": {
|
||||
"vue-demi": ">=0.14.5"
|
||||
},
|
||||
|
@ -838,9 +790,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@vueuse/shared/node_modules/vue-demi": {
|
||||
"version": "0.14.5",
|
||||
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.5.tgz",
|
||||
"integrity": "sha512-o9NUVpl/YlsGJ7t+xuqJKx8EBGf1quRhCiT6D/J0pfwmk9zUwYkC7yrF4SZCe6fETvSM3UNL2edcbYrSyc4QHA==",
|
||||
"version": "0.14.6",
|
||||
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz",
|
||||
"integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==",
|
||||
"hasInstallScript": true,
|
||||
"bin": {
|
||||
"vue-demi-fix": "bin/vue-demi-fix.js",
|
||||
|
@ -902,6 +854,15 @@
|
|||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/barcode-detector": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/barcode-detector/-/barcode-detector-2.0.3.tgz",
|
||||
"integrity": "sha512-4d6KAcnsL5SuXhxtz0Z+7qjKqPi6bqrgE2QdocKKWTA33MMR+s2Dx1+YfGEhQ++/sM/MrXT/hTBzgG8XL4dAgg==",
|
||||
"dependencies": {
|
||||
"@sec-ant/zxing-wasm": "2.1.5",
|
||||
"@types/dom-webcodecs": "^0.1.8"
|
||||
}
|
||||
},
|
||||
"node_modules/base32-encode": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/base32-encode/-/base32-encode-2.0.0.tgz",
|
||||
|
@ -922,6 +883,29 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/bootstrap": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.2.tgz",
|
||||
"integrity": "sha512-D32nmNWiQHo94BKHLmOrdjlL05q1c8oxbtBphQFb9Z5to6eGRDCm0QgeaZ4zFBHzfg2++rqa2JkqCcxDy0sH0g==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/twbs"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/bootstrap"
|
||||
}
|
||||
],
|
||||
"peerDependencies": {
|
||||
"@popperjs/core": "^2.11.8"
|
||||
}
|
||||
},
|
||||
"node_modules/bootswatch": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/bootswatch/-/bootswatch-5.3.2.tgz",
|
||||
"integrity": "sha512-r05xOSLSx7MJvjpk/uoU8wPYgkPHWLV+uenLaRsS7yBsqSUcWYPjeUkz+tmrRv6s1eFxkF08NvQfBSSPCTyYaA=="
|
||||
},
|
||||
"node_modules/braces": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
|
||||
|
@ -934,11 +918,6 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/bulma": {
|
||||
"version": "0.9.4",
|
||||
"resolved": "https://registry.npmjs.org/bulma/-/bulma-0.9.4.tgz",
|
||||
"integrity": "sha512-86FlT5+1GrsgKbPLRRY7cGDg8fsJiP/jzTqXXVqiUZZ2aZT8uemEOHlU1CDU+TxklPEZ11HZNNWclRBBecP4CQ=="
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
"version": "3.5.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
|
||||
|
@ -1046,54 +1025,33 @@
|
|||
"source-map": "~0.6.1"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-utils": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz",
|
||||
"integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==",
|
||||
"node_modules/eslint-visitor-keys": {
|
||||
"version": "3.4.3",
|
||||
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
|
||||
"integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"eslint-visitor-keys": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/mysticatea"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-visitor-keys": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz",
|
||||
"integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
"url": "https://opencollective.com/eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/espree": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz",
|
||||
"integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==",
|
||||
"version": "9.6.1",
|
||||
"resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
|
||||
"integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"acorn": "^7.1.1",
|
||||
"acorn-jsx": "^5.2.0",
|
||||
"eslint-visitor-keys": "^1.1.0"
|
||||
"acorn": "^8.9.0",
|
||||
"acorn-jsx": "^5.3.2",
|
||||
"eslint-visitor-keys": "^3.4.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/espree/node_modules/acorn": {
|
||||
"version": "7.4.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
|
||||
"integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/esprima": {
|
||||
|
@ -1170,9 +1128,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
|
@ -1196,9 +1154,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/immutable": {
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.2.tgz",
|
||||
"integrity": "sha512-oGXzbEDem9OOpDWZu88jGiYCvIsLHMvGw+8OXlpsvTFvIQplQbjg1B1cvKg8f7Hoch6+NGjpPsH1Fr+Mc2D1aA==",
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.4.tgz",
|
||||
"integrity": "sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/is-binary-path": {
|
||||
|
@ -1274,31 +1232,21 @@
|
|||
}
|
||||
},
|
||||
"node_modules/jsonc-eslint-parser": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/jsonc-eslint-parser/-/jsonc-eslint-parser-1.4.1.tgz",
|
||||
"integrity": "sha512-hXBrvsR1rdjmB2kQmUjf1rEIa+TqHBGMge8pwi++C+Si1ad7EjZrJcpgwym+QGK/pqTx+K7keFAtLlVNdLRJOg==",
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonc-eslint-parser/-/jsonc-eslint-parser-2.3.0.tgz",
|
||||
"integrity": "sha512-9xZPKVYp9DxnM3sd1yAsh/d59iIaswDkai8oTxbursfKYbg/ibjX0IzFt35+VZ8iEW453TVTXztnRvYUQlAfUQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"acorn": "^7.4.1",
|
||||
"eslint-utils": "^2.1.0",
|
||||
"eslint-visitor-keys": "^1.3.0",
|
||||
"espree": "^6.0.0",
|
||||
"semver": "^6.3.0"
|
||||
"acorn": "^8.5.0",
|
||||
"eslint-visitor-keys": "^3.0.0",
|
||||
"espree": "^9.0.0",
|
||||
"semver": "^7.3.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jsonc-eslint-parser/node_modules/acorn": {
|
||||
"version": "7.4.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
|
||||
"integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ota-meshi"
|
||||
}
|
||||
},
|
||||
"node_modules/jsonc-parser": {
|
||||
|
@ -1325,10 +1273,22 @@
|
|||
"loose-envify": "cli.js"
|
||||
}
|
||||
},
|
||||
"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.30.2",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.2.tgz",
|
||||
"integrity": "sha512-lNZdu7pewtq/ZvWUp9Wpf/x7WzMTsR26TWV03BRZrXFsv+BI6dy8RAiKgm1uM/kyR0rCfUcqvOlXKG66KhIGug==",
|
||||
"version": "0.30.4",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.4.tgz",
|
||||
"integrity": "sha512-Q/TKtsC5BPm0kGqgBIF9oXAs/xEf2vRKiIB4wCRQTJOQIByZ1d+NnUOotvJOvNpi5RNIgVOMC3pOuaP1ZTDlVg==",
|
||||
"dependencies": {
|
||||
"@jridgewell/sourcemap-codec": "^1.4.15"
|
||||
},
|
||||
|
@ -1359,15 +1319,15 @@
|
|||
}
|
||||
},
|
||||
"node_modules/mlly": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/mlly/-/mlly-1.4.0.tgz",
|
||||
"integrity": "sha512-ua8PAThnTwpprIaU47EPeZ/bPUVp2QYBbWMphUQpVdBI3Lgqzm5KZQ45Agm3YJedHXaIHl6pBGabaLSUPPSptg==",
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/mlly/-/mlly-1.4.2.tgz",
|
||||
"integrity": "sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"acorn": "^8.9.0",
|
||||
"acorn": "^8.10.0",
|
||||
"pathe": "^1.1.1",
|
||||
"pkg-types": "^1.0.3",
|
||||
"ufo": "^1.1.2"
|
||||
"ufo": "^1.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
|
@ -1437,9 +1397,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.27",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.27.tgz",
|
||||
"integrity": "sha512-gY/ACJtJPSmUFPDCHtX78+01fHa64FaU4zaaWfuh1MhGJISufJAH4cun6k/8fwsHYeK4UQmENQK+tRLCFJE8JQ==",
|
||||
"version": "8.4.31",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
||||
"integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
|
@ -1518,9 +1478,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "3.28.0",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.28.0.tgz",
|
||||
"integrity": "sha512-d7zhvo1OUY2SXSM6pfNjgD5+d0Nz87CUp4mt8l/GgVP3oBsPwzNvSzyu1me6BSG9JIgWNTVcafIXBIyM8yQ3yw==",
|
||||
"version": "3.29.4",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz",
|
||||
"integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"rollup": "dist/bin/rollup"
|
||||
|
@ -1557,9 +1517,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/sass": {
|
||||
"version": "1.65.1",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.65.1.tgz",
|
||||
"integrity": "sha512-9DINwtHmA41SEd36eVPQ9BJKpn7eKDQmUHmpI0y5Zv2Rcorrh0zS+cFrt050hdNbmmCNKTW3hV5mWfuegNRsEA==",
|
||||
"version": "1.68.0",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.68.0.tgz",
|
||||
"integrity": "sha512-Lmj9lM/fef0nQswm1J2HJcEsBUba4wgNx2fea6yJHODREoMFnwRpZydBnX/RjyXw2REIwdkbqE4hrTo4qfDBUA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"chokidar": ">=3.0.0 <4.0.0",
|
||||
|
@ -1579,18 +1539,26 @@
|
|||
"integrity": "sha512-d7wDPgDV3DDiqulJjKiV2865wKsJ34YI+NDREbm+FySq6WuKOikwyNQcm+doLAZ1O6ltdO0SeKle2xMpN3Brgw=="
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
||||
"version": "7.5.4",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
|
||||
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"lru-cache": "^6.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"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,
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
|
@ -1624,18 +1592,18 @@
|
|||
}
|
||||
},
|
||||
"node_modules/ufo": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/ufo/-/ufo-1.2.0.tgz",
|
||||
"integrity": "sha512-RsPyTbqORDNDxqAdQPQBpgqhWle1VcTSou/FraClYlHf6TZnQcGslpLcAphNR+sQW4q5lLWLbOsRlh9j24baQg==",
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/ufo/-/ufo-1.3.1.tgz",
|
||||
"integrity": "sha512-uY/99gMLIOlJPwATcMVYfqDSxUR9//AUcgZMzwfSTJPDKzA1S8mX4VLqa+fiAtveraQUBCz4FFcwVZBGbwBXIw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/unplugin": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.4.0.tgz",
|
||||
"integrity": "sha512-5x4eIEL6WgbzqGtF9UV8VEC/ehKptPXDS6L2b0mv4FRMkJxRtjaJfOWDd6a8+kYbqsjklix7yWP0N3SUepjXcg==",
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.5.0.tgz",
|
||||
"integrity": "sha512-9ZdRwbh/4gcm1JTOkp9lAkIDrtOyOxgHmY7cjuwI8L/2RTikMcVG25GsZwNAgRuap3iDw2jeq7eoqtAsz5rW3A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"acorn": "^8.9.0",
|
||||
"acorn": "^8.10.0",
|
||||
"chokidar": "^3.5.3",
|
||||
"webpack-sources": "^3.2.3",
|
||||
"webpack-virtual-modules": "^0.5.0"
|
||||
|
@ -1717,38 +1685,40 @@
|
|||
}
|
||||
},
|
||||
"node_modules/vue-i18n": {
|
||||
"version": "9.2.2",
|
||||
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.2.2.tgz",
|
||||
"integrity": "sha512-yswpwtj89rTBhegUAv9Mu37LNznyu3NpyLQmozF3i1hYOhwpG8RjcjIFIIfnu+2MDZJGSZPXaKWvnQA71Yv9TQ==",
|
||||
"version": "9.5.0",
|
||||
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.5.0.tgz",
|
||||
"integrity": "sha512-NiI3Ph1qMstNf7uhYh8trQBOBFLxeJgcOxBq51pCcZ28Vs18Y7BDS58r8HGDKCYgXdLUYqPDXdKatIF4bvBVZg==",
|
||||
"dependencies": {
|
||||
"@intlify/core-base": "9.2.2",
|
||||
"@intlify/shared": "9.2.2",
|
||||
"@intlify/vue-devtools": "9.2.2",
|
||||
"@vue/devtools-api": "^6.2.1"
|
||||
"@intlify/core-base": "9.5.0",
|
||||
"@intlify/shared": "9.5.0",
|
||||
"@vue/devtools-api": "^6.5.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
"node": ">= 16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/kazupon"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vue-qrcode-reader": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/vue-qrcode-reader/-/vue-qrcode-reader-5.2.0.tgz",
|
||||
"integrity": "sha512-0AWCttTFUCl53TpejVQYHFSh0tRMx2mlTcLqG93ueJ7jm4sav/qJiXZiat3wj4SJ0dthzW7i3c2wgRWqc6QzeQ==",
|
||||
"version": "5.3.6",
|
||||
"resolved": "https://registry.npmjs.org/vue-qrcode-reader/-/vue-qrcode-reader-5.3.6.tgz",
|
||||
"integrity": "sha512-LBhP3bgYUde6hRNFcIoTPGs26zUMDdOmE4bYolKk2SoiCxxg6icCKjv98i7pP2KcHBScAmZ11X2ha8Tovs+O+A==",
|
||||
"dependencies": {
|
||||
"@sec-ant/barcode-detector": "^1.1.2",
|
||||
"webrtc-adapter": "^8.2.2"
|
||||
"barcode-detector": "2.0.3",
|
||||
"webrtc-adapter": "^8.2.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vue-router": {
|
||||
"version": "4.2.4",
|
||||
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.2.4.tgz",
|
||||
"integrity": "sha512-9PISkmaCO02OzPVOMq2w82ilty6+xJmQrarYZDkjZBfl4RvYAlt4PKnEX21oW4KTtWfa9OuO/b3qk1Od3AEdCQ==",
|
||||
"version": "4.2.5",
|
||||
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.2.5.tgz",
|
||||
"integrity": "sha512-DIUpKcyg4+PTQKfFPX88UWhlagBEBEfJ5A8XDXRJLUnZOvcpMF8o/dnL90vpVkGaPbjvXazV/rC1qBKrZlFugw==",
|
||||
"dependencies": {
|
||||
"@vue/devtools-api": "^6.5.0"
|
||||
},
|
||||
|
@ -1786,24 +1756,36 @@
|
|||
"npm": ">=3.10.0"
|
||||
}
|
||||
},
|
||||
"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": "1.10.2",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
|
||||
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.2.tgz",
|
||||
"integrity": "sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/yaml-eslint-parser": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/yaml-eslint-parser/-/yaml-eslint-parser-0.3.2.tgz",
|
||||
"integrity": "sha512-32kYO6kJUuZzqte82t4M/gB6/+11WAuHiEnK7FreMo20xsCKPeFH5tDBU7iWxR7zeJpNnMXfJyXwne48D0hGrg==",
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/yaml-eslint-parser/-/yaml-eslint-parser-1.2.2.tgz",
|
||||
"integrity": "sha512-pEwzfsKbTrB8G3xc/sN7aw1v6A6c/pKxLAkjclnAyo5g5qOh6eL9WGu0o3cSDQZKrTNk4KL4lQSwZW+nBkANEg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"eslint-visitor-keys": "^1.3.0",
|
||||
"lodash": "^4.17.20",
|
||||
"yaml": "^1.10.0"
|
||||
"eslint-visitor-keys": "^3.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"yaml": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.17.0 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ota-meshi"
|
||||
}
|
||||
},
|
||||
"node_modules/zustand": {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "sake-app",
|
||||
"version": "0.2.0",
|
||||
"version": "0.5.0",
|
||||
"author": "Rodolphe Bréard <rodolphe@what.tf>",
|
||||
"license": "(MIT OR Apache-2.0)",
|
||||
"private": true,
|
||||
|
@ -15,16 +15,18 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@noble/hashes": "^1.3.1",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@vueuse/core": "^10.2.1",
|
||||
"base32-encode": "^2.0.0",
|
||||
"bulma": "^0.9.4",
|
||||
"bootstrap": "^5.3.2",
|
||||
"bootswatch": "^5.3.2",
|
||||
"vue": "^3.3.4",
|
||||
"vue-i18n": "^9.2.2",
|
||||
"vue-qrcode-reader": "^5.1.0",
|
||||
"vue-router": "^4.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@intlify/unplugin-vue-i18n": "^0.12.2",
|
||||
"@intlify/unplugin-vue-i18n": "^1.2.0",
|
||||
"@vitejs/plugin-vue": "^4.2.3",
|
||||
"sass": "^1.64.1",
|
||||
"vite": "^4.4.6"
|
||||
|
|
10
release.sh
10
release.sh
|
@ -27,6 +27,13 @@ update_app_version()
|
|||
jq ".version = \"${new_version}\"" "package.json" >"${tmp_file}"
|
||||
mv "${tmp_file}" "package.json"
|
||||
sed -i "s/## \[Unreleased\]/## \[${new_version}\] - ${current_date}/" "CHANGELOG.md"
|
||||
update_dependencies
|
||||
}
|
||||
|
||||
update_dependencies()
|
||||
{
|
||||
npm update
|
||||
npm outdated
|
||||
}
|
||||
|
||||
check_working_directory()
|
||||
|
@ -47,6 +54,8 @@ build_assets()
|
|||
|
||||
npm run --silent build >/dev/null
|
||||
cp -r "dist" "${asset_dir}"
|
||||
cp "LICENSE-APACHE-2.0.txt" "${asset_dir}"
|
||||
cp "LICENSE-MIT.txt" "${asset_dir}"
|
||||
rm -f "${asset_dir}/manifest.webmanifest" "${asset_dir}/sw.js"
|
||||
|
||||
tar czf "${asset_dir}.tar.gz" "${asset_dir}"
|
||||
|
@ -103,6 +112,7 @@ main()
|
|||
local new_version
|
||||
local confirm_release
|
||||
|
||||
update_dependencies
|
||||
check_working_directory
|
||||
|
||||
display_app_version
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
export function sortAccounts(accounts) {
|
||||
accounts.value.sort((a, b) => {
|
||||
accounts.sort((a, b) => {
|
||||
const va = `${a.localPart}@${a.domain}`;
|
||||
const vb = `${b.localPart}@${b.domain}`;
|
||||
return va.localeCompare(vb);
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
@charset "utf-8"
|
||||
|
||||
@import "node_modules/bulma/bulma"
|
||||
|
||||
a[href]
|
||||
text-decoration: underline
|
||||
|
||||
.navbar-menu a[class~="navbar-item"]
|
||||
text-decoration: none
|
||||
@import "bootswatch/dist/minty/variables"
|
||||
@import "variables.scss"
|
||||
@import "bootstrap/scss/bootstrap"
|
||||
@import "bootswatch/dist/minty/bootswatch"
|
||||
|
|
8
src/assets/variables.scss
Normal file
8
src/assets/variables.scss
Normal file
|
@ -0,0 +1,8 @@
|
|||
$container-max-widths: (
|
||||
sm: 600px,
|
||||
md: 700px,
|
||||
lg: 800px,
|
||||
xl: 820px,
|
||||
xxl: 840px
|
||||
);
|
||||
$web-font-path: false;
|
5
src/components/ButtonGroupComponent.vue
Normal file
5
src/components/ButtonGroupComponent.vue
Normal file
|
@ -0,0 +1,5 @@
|
|||
<template>
|
||||
<div class="d-grid gap-2 col-6 mx-auto">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
|
@ -1,11 +1,5 @@
|
|||
<template>
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="columns is-centered">
|
||||
<div class="column is-three-fifths">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<div class="container">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,35 +1,35 @@
|
|||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { RouterLink } from 'vue-router';
|
||||
|
||||
const menuActive = ref(false);
|
||||
const toggleBurger = () => {
|
||||
menuActive.value = !menuActive.value;
|
||||
};
|
||||
import LayoutComponent from '../components/LayoutComponent.vue';
|
||||
import { Popover } from 'bootstrap';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container">
|
||||
<div class="columns is-centered">
|
||||
<div class="column is-three-fifths">
|
||||
<nav class="navbar" role="navigation" aria-label="main navigation">
|
||||
<div class="navbar-brand">
|
||||
<a role="button" class="navbar-burger" :class="{ 'is-active': menuActive }" aria-label="menu" aria-expanded="false" @click="toggleBurger">
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="navbar-menu" :class="{ 'is-active': menuActive }">
|
||||
<div class="navbar-end">
|
||||
<RouterLink to="/add-account" class="navbar-item">{{ $t("navbar.addAccount") }}</RouterLink>
|
||||
<RouterLink to="/manage-accounts" class="navbar-item">{{ $t("navbar.manageAccounts") }}</RouterLink>
|
||||
<RouterLink to="/config" class="navbar-item">{{ $t("navbar.config") }}</RouterLink>
|
||||
<RouterLink to="/about" class="navbar-item">{{ $t("navbar.about") }}</RouterLink>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<nav class="navbar navbar-expand-lg bg-primary" data-bs-theme="dark">
|
||||
<div class="container-fluid">
|
||||
<span class="navbar-brand"></span>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#mainNavBar" aria-controls="mainNavBar" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="mainNavBar">
|
||||
<LayoutComponent>
|
||||
<ul class="navbar-nav justify-content-end flex-grow-1 pe-3">
|
||||
<li class="nav-item">
|
||||
<RouterLink to="/add-account" class="nav-link">{{ $t("navbar.addAccount") }}</RouterLink>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<RouterLink to="/manage-accounts" class="nav-link">{{ $t("navbar.manageAccounts") }}</RouterLink>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<RouterLink to="/config" class="nav-link">{{ $t("navbar.config") }}</RouterLink>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<RouterLink to="/about" class="nav-link">{{ $t("navbar.about") }}</RouterLink>
|
||||
</li>
|
||||
</ul>
|
||||
</LayoutComponent>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</template>
|
||||
|
|
9
src/const.js
Normal file
9
src/const.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
export const allowedColorModes = [
|
||||
'light',
|
||||
'dark',
|
||||
];
|
||||
export const allowedLocales = [
|
||||
'en',
|
||||
'fr',
|
||||
];
|
||||
export const resetToDefaultAccount = true;
|
|
@ -5,7 +5,8 @@
|
|||
"cancel": "Cancel",
|
||||
"close": "Close",
|
||||
"copy": "Copy",
|
||||
"delete": "Delete"
|
||||
"delete": "Delete",
|
||||
"reset": "Reset"
|
||||
}
|
||||
},
|
||||
"navbar": {
|
||||
|
@ -31,10 +32,13 @@
|
|||
"domainName": "Domain name",
|
||||
"privateKey": "Private key",
|
||||
"scan": "Scan",
|
||||
"scanTitle": "Scan a QR code",
|
||||
"addAccount": "Add account",
|
||||
"cancel": "@:invariants.controls.cancel",
|
||||
"error": {
|
||||
"accountAlreadyExists": "You already have an account on this domain that uses this local part.",
|
||||
"invalidBase64": "The key must be a valid base64 string.",
|
||||
"invalidKeyLength": "The key's length must be either 128 bits (16 bytes) or 256 bits (32 bytes).",
|
||||
"invalidSeparator": "The separator must be a single character.",
|
||||
"cameraNotAllowed": "Camera access permission was not granted.",
|
||||
"cameraNotFound": "No camera detected.",
|
||||
|
@ -42,12 +46,17 @@
|
|||
"cameraNotReadable": "Camera not accessible (potentially already in use).",
|
||||
"cameraOverconstrained": "Installed cameras are not suitable.",
|
||||
"cameraStreamApiNotSupported": "Stream API is not supported in this browser.",
|
||||
"localPartSeparator": "The local part cannot contain the separator.",
|
||||
"unknown": "Unknown error."
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"title": "Preferences",
|
||||
"language": "Language",
|
||||
"colorMode": "Theme",
|
||||
"resetToDefault": "Switch to the default account when the new sub-address form is reset",
|
||||
"lightTheme": "Light",
|
||||
"darkTheme": "Dark",
|
||||
"close": "@:invariants.controls.close"
|
||||
},
|
||||
"deleteAccount": {
|
||||
|
@ -63,10 +72,13 @@
|
|||
"name": "Name",
|
||||
"input": "Dedicated name",
|
||||
"address": "Address",
|
||||
"copy": "@:invariants.controls.copy"
|
||||
"copy": "@:invariants.controls.copy",
|
||||
"reset": "@:invariants.controls.reset"
|
||||
},
|
||||
"manageAccounts": {
|
||||
"title": "Accounts",
|
||||
"isDefault": "default",
|
||||
"setDefault": "Set default",
|
||||
"delete": "@:invariants.controls.delete",
|
||||
"close": "@:invariants.controls.close"
|
||||
}
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
"cancel": "Annuler",
|
||||
"close": "Fermer",
|
||||
"copy": "Copier",
|
||||
"delete": "Supprimer"
|
||||
"delete": "Supprimer",
|
||||
"reset": "Réinitialiser"
|
||||
}
|
||||
},
|
||||
"navbar": {
|
||||
|
@ -31,23 +32,31 @@
|
|||
"domainName": "Nom de domaine",
|
||||
"privateKey": "Clé privée",
|
||||
"scan": "Scanner",
|
||||
"scanTitle": "Scanner un QR code",
|
||||
"addAccount": "Ajouter",
|
||||
"cancel": "@:invariants.controls.cancel",
|
||||
"error": {
|
||||
"accountAlreadyExists": "Vous avez déjà un compte sur ce nom de domaine qui utilise cette partie locale.",
|
||||
"invalidBase64": "La clé doit être une chaîne de caractère en base64.",
|
||||
"invalidSeparator": "La séparateur doit être un unique caractère.",
|
||||
"invalidKeyLength": "La longueur de la clé doit être de 128 bits (16 bytes) ou de 256 bits (32 bytes).",
|
||||
"invalidSeparator": "Le séparateur doit être un unique caractère.",
|
||||
"cameraNotAllowed": "L'accès à la caméra n'a pas été autorisé.",
|
||||
"cameraNotFound": "Aucune caméra détectée.",
|
||||
"cameraInsecureContext": "Impossible d'accéder à la caméra depuis une liaison non-sécurisée.",
|
||||
"cameraNotReadable": "Caméra inaccessible (potentiellement déjà utilisée).",
|
||||
"cameraOverconstrained": "Les caméras détectées ne sont pas compatibles.",
|
||||
"cameraStreamApiNotSupported": "L'API du flux de données vidéo n'est pas supportée par ce navigateur.",
|
||||
"localPartSeparator": "La partie locale ne peut pas contenir le séparateur.",
|
||||
"unknown": "Erreur inconnue."
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"title": "Préférences",
|
||||
"language": "Langue",
|
||||
"colorMode": "Thème",
|
||||
"resetToDefault": "Basculer sur le compte par défaut lorsque le formulaire de sous-adresse est réinitialisé",
|
||||
"lightTheme": "Clair",
|
||||
"darkTheme": "Sombre",
|
||||
"close": "@:invariants.controls.close"
|
||||
},
|
||||
"deleteAccount": {
|
||||
|
@ -63,10 +72,13 @@
|
|||
"name": "Nom",
|
||||
"input": "Nom dédié",
|
||||
"address": "Adresse",
|
||||
"copy": "@:invariants.controls.copy"
|
||||
"copy": "@:invariants.controls.copy",
|
||||
"reset": "@:invariants.controls.reset"
|
||||
},
|
||||
"manageAccounts": {
|
||||
"title": "Comptes",
|
||||
"isDefault": "défaut",
|
||||
"setDefault": "Définir par défaut",
|
||||
"delete": "@:invariants.controls.delete",
|
||||
"close": "@:invariants.controls.close"
|
||||
}
|
||||
|
|
19
src/locales_utils.js
Normal file
19
src/locales_utils.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { allowedLocales } from './const';
|
||||
|
||||
const fallBackValue = 'en';
|
||||
|
||||
const getShortLanguageCode = (language) => {
|
||||
language = language.split('-')[0];
|
||||
language = language.split('_')[0];
|
||||
return language;
|
||||
};
|
||||
|
||||
export const getDefaultLocale = () => {
|
||||
for (const lang of navigator.languages) {
|
||||
const lang_short = getShortLanguageCode(lang);
|
||||
if (allowedLocales.includes(lang_short)) {
|
||||
return lang_short;
|
||||
}
|
||||
}
|
||||
return fallBackValue;
|
||||
};
|
31
src/main.js
31
src/main.js
|
@ -1,5 +1,7 @@
|
|||
import './assets/main.sass';
|
||||
|
||||
import { allowedColorModes, allowedLocales } from './const';
|
||||
import { getDefaultLocale } from './locales_utils';
|
||||
import { createApp } from 'vue';
|
||||
import { createI18n } from 'vue-i18n';
|
||||
import { useStorage } from '@vueuse/core';
|
||||
|
@ -7,16 +9,31 @@ import App from './App.vue';
|
|||
import router from './router';
|
||||
import messages from '@intlify/unplugin-vue-i18n/messages';
|
||||
|
||||
const default_locale = 'en';
|
||||
const stored_locale = useStorage('sake-locale', '');
|
||||
if (!stored_locale.value) {
|
||||
stored_locale.value = default_locale;
|
||||
}
|
||||
const setGlobalAttribute = (attrName, storageName, defaultValue, allowedValues) => {
|
||||
const stored_value = useStorage(storageName, '');
|
||||
if (!stored_value.value) {
|
||||
stored_value.value = defaultValue;
|
||||
}
|
||||
document.documentElement.setAttribute(attrName, stored_value.value);
|
||||
if (!allowedValues.includes(stored_value.value)) {
|
||||
stored_value.value = defaultValue;
|
||||
}
|
||||
return {
|
||||
'stored': stored_value,
|
||||
'defaultValue': defaultValue,
|
||||
};
|
||||
};
|
||||
const getDefaultColorMode = () => {
|
||||
const darkMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
return darkMediaQuery.matches ? 'dark' : 'light';
|
||||
};
|
||||
const locale = setGlobalAttribute('lang', 'sake-locale', getDefaultLocale(), allowedLocales);
|
||||
const colorMode = setGlobalAttribute('data-bs-theme', 'sake-color-mode', getDefaultColorMode(), allowedColorModes);
|
||||
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
locale: stored_locale.value,
|
||||
fallbackLocale: default_locale,
|
||||
locale: locale.stored.value,
|
||||
fallbackLocale: locale.defaultValue,
|
||||
messages,
|
||||
});
|
||||
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
<script setup>
|
||||
import { useRouter } from 'vue-router';
|
||||
import { version } from '../../package.json';
|
||||
import ButtonGroupComponent from '../components/ButtonGroupComponent.vue';
|
||||
import LayoutComponent from '../components/LayoutComponent.vue';
|
||||
import ExternalLinkComponent from '../components/ExternalLinkComponent.vue';
|
||||
import NavBarComponent from '../components/NavBarComponent.vue';
|
||||
|
||||
const router = useRouter();
|
||||
const repoUrl = 'https://github.com/breard-r/sake-app';
|
||||
const repoUrl = 'https://git.what.tf/rodolphe/sake-app';
|
||||
|
||||
const toMainView = () => {
|
||||
return router.push('/');
|
||||
|
@ -13,31 +15,30 @@ const toMainView = () => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<NavBarComponent />
|
||||
<LayoutComponent>
|
||||
<h1 class="title is-1">{{ $t("about.title") }}</h1>
|
||||
<h4 class="subtitle is-4">{{ $t("about.name") }}</h4>
|
||||
<div class="block">
|
||||
<i18n-t scope="global" keypath="about.version" tag="p">
|
||||
<template v-slot:version>{{ version }}</template>
|
||||
</i18n-t>
|
||||
<i18n-t scope="global" keypath="about.license" tag="p">
|
||||
<template v-slot:mit>
|
||||
<ExternalLinkComponent :url="$t('about.license_mit_url')" name="MIT" />
|
||||
</template>
|
||||
<template v-slot:apache>
|
||||
<ExternalLinkComponent :url="$t('about.license_apache_url')" name="Apache 2.0" />
|
||||
</template>
|
||||
</i18n-t>
|
||||
<i18n-t scope="global" keypath="about.repository" tag="p">
|
||||
<template v-slot:url>
|
||||
<ExternalLinkComponent :url="repoUrl" :name="repoUrl" />
|
||||
</template>
|
||||
</i18n-t>
|
||||
</div>
|
||||
<div class="block">
|
||||
<div class="buttons is-centered">
|
||||
<button class="button is-light" @click="toMainView">{{ $t("about.close") }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<h1>{{ $t("about.title") }}</h1>
|
||||
<h4>{{ $t("about.name") }}</h4>
|
||||
|
||||
<i18n-t scope="global" keypath="about.version" tag="p">
|
||||
<template v-slot:version>{{ version }}</template>
|
||||
</i18n-t>
|
||||
<i18n-t scope="global" keypath="about.license" tag="p">
|
||||
<template v-slot:mit>
|
||||
<ExternalLinkComponent :url="$t('about.license_mit_url')" name="MIT" />
|
||||
</template>
|
||||
<template v-slot:apache>
|
||||
<ExternalLinkComponent :url="$t('about.license_apache_url')" name="Apache 2.0" />
|
||||
</template>
|
||||
</i18n-t>
|
||||
<i18n-t scope="global" keypath="about.repository" tag="p">
|
||||
<template v-slot:url>
|
||||
<ExternalLinkComponent :url="repoUrl" :name="repoUrl" />
|
||||
</template>
|
||||
</i18n-t>
|
||||
|
||||
<ButtonGroupComponent>
|
||||
<button type="button" class="btn btn-secondary" @click="toMainView">{{ $t("about.close") }}</button>
|
||||
</ButtonGroupComponent>
|
||||
</LayoutComponent>
|
||||
</template>
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { ref, reactive, computed, onMounted } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useStorage } from '@vueuse/core';
|
||||
import { QrcodeStream, setZXingModuleOverrides } from 'vue-qrcode-reader';
|
||||
import { sha256 } from '@noble/hashes/sha256';
|
||||
import { Modal } from 'bootstrap';
|
||||
import base32Encode from 'base32-encode';
|
||||
import ButtonGroupComponent from '../components/ButtonGroupComponent.vue';
|
||||
import LayoutComponent from '../components/LayoutComponent.vue';
|
||||
import NavBarComponent from '../components/NavBarComponent.vue';
|
||||
import wasmFile from "../../node_modules/@sec-ant/zxing-wasm/dist/reader/zxing_reader.wasm?url";
|
||||
|
||||
const accounts = useStorage('sake-accounts', []);
|
||||
|
@ -14,7 +17,15 @@ const localPart = ref('');
|
|||
const separator = ref('+');
|
||||
const domainName = ref('');
|
||||
const privateKey = ref('');
|
||||
const authorizedKeyLengths = [16, 32];
|
||||
const state = reactive({
|
||||
QrCodeModal: null,
|
||||
});
|
||||
|
||||
const errorMessageId = ref('');
|
||||
const separatorErrorMessageId = ref('');
|
||||
const localPartErrorMessageId = ref('');
|
||||
const addrKeyErrorMessageId = ref('');
|
||||
|
||||
const base64Decode = (str_b64) => {
|
||||
try {
|
||||
|
@ -42,23 +53,48 @@ const addDisabled = computed(() => {
|
|||
});
|
||||
const addAccount = () => {
|
||||
if (!addDisabled.value) {
|
||||
resetErrorMessage();
|
||||
var hasError = false;
|
||||
var key = null;
|
||||
var accountId = null;
|
||||
if (separator.value.length != 1) {
|
||||
hasError = setErrorMessage('addAccount.error.invalidSeparator', separatorErrorMessageId);
|
||||
}
|
||||
try {
|
||||
if (separator.value.length != 1) {
|
||||
throw new Error('addAccount.error.invalidSeparator');
|
||||
if (localPart.value.includes(separator.value)) {
|
||||
throw new Error('addAccount.error.localPartSeparator');
|
||||
}
|
||||
const key = base64Decode(privateKey.value);
|
||||
const hash = sha256(`${localPart.value}@${domainName.value}`);
|
||||
accountId = `${localPart.value}@${domainName.value}`;
|
||||
for (const acc of accounts.value) {
|
||||
const comp = `${acc.localPart}@${acc.domain}`;
|
||||
if (accountId == comp) {
|
||||
throw new Error('addAccount.error.accountAlreadyExists');
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
hasError = setErrorMessage(e.message, localPartErrorMessageId);
|
||||
}
|
||||
try {
|
||||
key = base64Decode(privateKey.value);
|
||||
if (!authorizedKeyLengths.includes(key.length)) {
|
||||
throw new Error('addAccount.error.invalidKeyLength');
|
||||
}
|
||||
} catch (e) {
|
||||
hasError = setErrorMessage(e.message, addrKeyErrorMessageId);
|
||||
}
|
||||
if (!hasError && key && accountId) {
|
||||
const hash = sha256(accountId);
|
||||
const newAccount = {
|
||||
id: base32Encode(hash, 'RFC4648', { padding: false }).toLowerCase(),
|
||||
localPart: localPart.value,
|
||||
separator: separator.value,
|
||||
domain: domainName.value,
|
||||
key: key,
|
||||
isDefault: false,
|
||||
};
|
||||
accounts.value.push(newAccount);
|
||||
return toMainView();
|
||||
} catch (e) {
|
||||
errorMessageId.value = e.message;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -72,17 +108,26 @@ setZXingModuleOverrides({
|
|||
return prefix + path;
|
||||
},
|
||||
});
|
||||
onMounted(() => {
|
||||
state.QrCodeModal = new Modal('#qr-code-modal', {});
|
||||
});
|
||||
const scanQrCode = ref(false);
|
||||
const showQrCodeScanner = (data) => {
|
||||
state.QrCodeModal.show();
|
||||
scanQrCode.value = true;
|
||||
};
|
||||
const hideQrCodeScanner = () => {
|
||||
scanQrCode.value = false;
|
||||
state.QrCodeModal.hide();
|
||||
};
|
||||
const onQrCodeDetected = (result_list) => {
|
||||
if (result_list.length >= 1) {
|
||||
privateKey.value = result_list[0].rawValue;
|
||||
scanQrCode.value = false;
|
||||
hideQrCodeScanner();
|
||||
}
|
||||
};
|
||||
const onQrCodeError = (err) => {
|
||||
hideQrCodeScanner();
|
||||
if (err.name === 'NotAllowedError') {
|
||||
setErrorMessage('addAccount.error.cameraNotAllowed');
|
||||
} else if (err.name === 'NotFoundError') {
|
||||
|
@ -110,57 +155,76 @@ const toMainView = () => {
|
|||
};
|
||||
|
||||
// Error message
|
||||
const setErrorMessage = (messageId) => {
|
||||
if (messageId.startsWith('addAccount.error.')) {
|
||||
errorMessageId.value = messageId;
|
||||
const setErrorMessage = (messageId, messageType) => {
|
||||
const messageIdClean = messageId.startsWith('addAccount.error.') ? messageId : 'addAccount.error.unknown';
|
||||
if (messageType) {
|
||||
messageType.value = messageIdClean;
|
||||
} else {
|
||||
errorMessageId.value = 'addAccount.error.unknown';
|
||||
errorMessageId.value = messageIdClean;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
const resetFloatingErrorMessage = () => {
|
||||
errorMessageId.value = '';
|
||||
};
|
||||
const resetErrorMessage = () => {
|
||||
errorMessageId.value = '';
|
||||
resetFloatingErrorMessage();
|
||||
separatorErrorMessageId.value = '';
|
||||
localPartErrorMessageId.value = '';
|
||||
addrKeyErrorMessageId.value = '';
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NavBarComponent v-if="!cancellDisabled" />
|
||||
<LayoutComponent>
|
||||
<h1 class="title is-1">{{ $t("addAccount.title") }}</h1>
|
||||
<div class="notification is-danger is-light" v-if="errorMessageId">
|
||||
<button class="delete" @click="resetErrorMessage"></button>
|
||||
<h1>{{ $t("addAccount.title") }}</h1>
|
||||
|
||||
<div class="alert alert-danger alert-dismissible fade show" role="alert" v-if="errorMessageId">
|
||||
{{ $t(errorMessageId) }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close" @click="resetFloatingErrorMessage"></button>
|
||||
</div>
|
||||
<div class="container" id="new-account-error-msg-container"></div>
|
||||
<div class="field">
|
||||
<label class="label" for="new-addr-local-part">{{ $t("addAccount.localPart") }}</label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" id="new-addr-local-part" v-model="localPart">
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="new-addr-local-part">{{ $t("addAccount.localPart") }}</label>
|
||||
<input :class="{ 'form-control': true, 'is-invalid': localPartErrorMessageId}" type="text" id="new-addr-local-part" v-model="localPart">
|
||||
<div class="invalid-feedback" v-if="localPartErrorMessageId">{{ $t(localPartErrorMessageId) }}</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="new-addr-separator">{{ $t("addAccount.separator") }}</label>
|
||||
<input :class="{ 'form-control': true, 'is-invalid': separatorErrorMessageId}" type="text" id="new-addr-separator" v-model="separator">
|
||||
<div class="invalid-feedback" v-if="separatorErrorMessageId">{{ $t(separatorErrorMessageId) }}</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="new-addr-domain">{{ $t("addAccount.domainName") }}</label>
|
||||
<input class="form-control" type="text" id="new-addr-domain" placeholder="example.org" v-model="domainName">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="new-addr-key">{{ $t("addAccount.privateKey") }}</label>
|
||||
<div class="input-group">
|
||||
<input :class="{ 'form-control': true, 'is-invalid': addrKeyErrorMessageId}" type="text" id="new-addr-key" v-model="privateKey">
|
||||
<button class="btn btn-primary" type="button" @click="showQrCodeScanner">{{ $t("addAccount.scan") }}</button>
|
||||
<div class="invalid-feedback" v-if="addrKeyErrorMessageId">{{ $t(addrKeyErrorMessageId) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label" for="new-addr-separator">{{ $t("addAccount.separator") }}</label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" id="new-addr-separator" v-model="separator">
|
||||
|
||||
<div class="modal fade" id="qr-code-modal" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="staticBackdropLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title fs-5" id="staticBackdropLabel">{{ $t("addAccount.scanTitle") }}</h1>
|
||||
<button type="button" class="btn-close" aria-label="Close" @click="hideQrCodeScanner"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<qrcode-stream v-if="scanQrCode" @detect="onQrCodeDetected" @error="onQrCodeError"></qrcode-stream>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label" for="new-addr-domain">{{ $t("addAccount.domainName") }}</label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" id="new-addr-domain" placeholder="example.org" v-model="domainName">
|
||||
</div>
|
||||
</div>
|
||||
<label class="label" for="new-addr-key">{{ $t("addAccount.privateKey") }}</label>
|
||||
<div class="field has-addons">
|
||||
<div class="control is-expanded">
|
||||
<input class="input" type="text" id="new-addr-key" v-model="privateKey">
|
||||
</div>
|
||||
<p class="control">
|
||||
<a class="button is-primary" @click="showQrCodeScanner">{{ $t("addAccount.scan") }}</a>
|
||||
</p>
|
||||
</div>
|
||||
<qrcode-stream v-if="scanQrCode" @detect="onQrCodeDetected" @error="onQrCodeError"></qrcode-stream>
|
||||
<div class="buttons is-centered">
|
||||
<button class="button is-primary" :disabled="addDisabled" @click="addAccount">{{ $t("addAccount.addAccount") }}</button>
|
||||
<button class="button is-light" v-if="!cancellDisabled" @click="toMainView">{{ $t("addAccount.cancel") }}</button>
|
||||
</div>
|
||||
|
||||
<ButtonGroupComponent>
|
||||
<button type="button" class="btn btn-primary" :disabled="addDisabled" @click="addAccount">{{ $t("addAccount.addAccount") }}</button>
|
||||
<button type="button" class="btn btn-secondary" v-if="!cancellDisabled" @click="toMainView">{{ $t("addAccount.cancel") }}</button>
|
||||
</ButtonGroupComponent>
|
||||
</LayoutComponent>
|
||||
</template>
|
||||
|
|
|
@ -1,13 +1,18 @@
|
|||
<script setup>
|
||||
import { allowedColorModes, resetToDefaultAccount } from '../const';
|
||||
import { watch } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useStorage } from '@vueuse/core';
|
||||
import ButtonGroupComponent from '../components/ButtonGroupComponent.vue';
|
||||
import LayoutComponent from '../components/LayoutComponent.vue';
|
||||
import NavBarComponent from '../components/NavBarComponent.vue';
|
||||
|
||||
const router = useRouter();
|
||||
const stored_locale = useStorage('sake-locale', '');
|
||||
const { t, locale } = useI18n({ useScope: 'global' });
|
||||
const colorMode = useStorage('sake-color-mode');
|
||||
const resetToDefault = useStorage('sake-reset-to-default', resetToDefaultAccount);
|
||||
|
||||
const toMainView = () => {
|
||||
return router.push('/');
|
||||
|
@ -15,24 +20,40 @@ const toMainView = () => {
|
|||
|
||||
watch(locale, async (newLocale) => {
|
||||
stored_locale.value = newLocale;
|
||||
document.documentElement.setAttribute('lang', newLocale);
|
||||
});
|
||||
watch(colorMode, async (newColorMode) => {
|
||||
console.log(`new color mode: ${newColorMode}`);
|
||||
document.documentElement.setAttribute('data-bs-theme', newColorMode);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NavBarComponent />
|
||||
<LayoutComponent>
|
||||
<h1 class="title is-1">{{ $t("config.title") }}</h1>
|
||||
<div class="field">
|
||||
<label class="label" for="app-language">{{ $t("config.language") }}</label>
|
||||
<div class="control">
|
||||
<div class="select is-fullwidth">
|
||||
<select id="app-language" v-model="$i18n.locale">
|
||||
<option v-for="locale_id in $i18n.availableLocales" :key="`locale-${locale_id}`" :value="locale_id">{{ $t("locale_name", 1, { locale: locale_id}) }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<h1>{{ $t("config.title") }}</h1>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="app-language">{{ $t("config.language") }}</label>
|
||||
<select class="form-select" id="app-language" v-model="$i18n.locale">
|
||||
<option v-for="locale_id in $i18n.availableLocales" :key="`locale-${locale_id}`" :value="locale_id">{{ $t("locale_name", 1, { locale: locale_id}) }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="app-color-mode">{{ $t("config.colorMode") }}</label>
|
||||
<select class="form-select" id="app-color-mode" v-model="colorMode">
|
||||
<option v-for="mode in allowedColorModes" :key="mode" :value="mode">{{ $t(`config.${mode}Theme`) }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="app-reset-to-default" v-model="resetToDefault">
|
||||
<label class="form-check-label" for="app-reset-to-default">{{ $t("config.resetToDefault") }}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="buttons is-centered">
|
||||
<button class="button is-light" @click="toMainView">{{ $t("config.close") }}</button>
|
||||
</div>
|
||||
|
||||
<ButtonGroupComponent>
|
||||
<button type="button" class="btn btn-secondary" @click="toMainView">{{ $t("about.close") }}</button>
|
||||
</ButtonGroupComponent>
|
||||
</LayoutComponent>
|
||||
</template>
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
<script setup>
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { useStorage } from '@vueuse/core';
|
||||
import ButtonGroupComponent from '../components/ButtonGroupComponent.vue';
|
||||
import LayoutComponent from '../components/LayoutComponent.vue';
|
||||
import NavBarComponent from '../components/NavBarComponent.vue';
|
||||
|
||||
const accounts = useStorage('sake-accounts', []);
|
||||
const router = useRouter();
|
||||
|
@ -18,14 +20,17 @@ const toMainView = () => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<NavBarComponent />
|
||||
<LayoutComponent>
|
||||
<h1 class="title is-1">{{ $t("deleteAccount.title") }}</h1>
|
||||
<h1>{{ $t("deleteAccount.title") }}</h1>
|
||||
|
||||
<p>{{ $t("deleteAccount.account") }}</p>
|
||||
<p class="has-text-weight-semibold is-size-5">{{ account.localPart }}@{{ account.domain }}</p>
|
||||
<p class="fw-semibold fs-5">{{ account.localPart }}@{{ account.domain }}</p>
|
||||
<p>{{ $t("deleteAccount.confirm") }}</p>
|
||||
<div class="buttons is-centered">
|
||||
<button class="button is-danger" @click="deleteAccount">{{ $t("deleteAccount.delete") }}</button>
|
||||
<button class="button is-light" @click="toMainView">{{ $t("deleteAccount.cancel") }}</button>
|
||||
</div>
|
||||
|
||||
<ButtonGroupComponent>
|
||||
<button type="button" class="btn btn-danger" @click="deleteAccount">{{ $t("deleteAccount.delete") }}</button>
|
||||
<button type="button" class="btn btn-secondary" @click="toMainView">{{ $t("deleteAccount.cancel") }}</button>
|
||||
</ButtonGroupComponent>
|
||||
</LayoutComponent>
|
||||
</template>
|
||||
|
|
|
@ -1,17 +1,25 @@
|
|||
<script setup>
|
||||
import { sortAccounts } from '../accounts';
|
||||
import { resetToDefaultAccount } from '../const';
|
||||
import { ref, computed } from 'vue';
|
||||
import { RouterLink, useRouter } from 'vue-router';
|
||||
import { useStorage } from '@vueuse/core';
|
||||
import { hmac } from '@noble/hashes/hmac';
|
||||
import { sha256 } from '@noble/hashes/sha256';
|
||||
import base32Encode from 'base32-encode';
|
||||
import ButtonGroupComponent from '../components/ButtonGroupComponent.vue';
|
||||
import LayoutComponent from '../components/LayoutComponent.vue';
|
||||
import NavBarComponent from '../components/NavBarComponent.vue';
|
||||
|
||||
const router = useRouter();
|
||||
const accounts = sortAccounts(useStorage('sake-accounts', []));
|
||||
const selectedAccountId = ref(accounts.value[0].id);
|
||||
const accounts = useStorage('sake-accounts', []);
|
||||
const resetToDefault = useStorage('sake-reset-to-default', resetToDefaultAccount);
|
||||
const sortedAccounts = computed(() => sortAccounts(accounts.value));
|
||||
const getDefaultAccount = () => {
|
||||
const def = accounts.value.find((a) => a.isDefault);
|
||||
return def ? def.id : accounts.value[0].id;
|
||||
};
|
||||
const selectedAccountId = ref(getDefaultAccount());
|
||||
const subAddrName = ref('');
|
||||
|
||||
const fromRawAccount = (raw_account) => {
|
||||
|
@ -28,15 +36,20 @@ const generatedAddr = computed(() => {
|
|||
const raw_account = accounts.value.find((e) => e.id == selectedAccountId.value);
|
||||
if (raw_account) {
|
||||
const account = fromRawAccount(raw_account);
|
||||
var hasher = hmac.create(sha256, account.key);
|
||||
hasher.update(account.localPart);
|
||||
hasher.update(account.separator);
|
||||
hasher.update(subAddrName.value);
|
||||
const mac = hasher.digest();
|
||||
const offset = mac[mac.length - 1] & 0xf;
|
||||
const reduced_mac = mac.slice(offset, offset + 5);
|
||||
const code = base32Encode(reduced_mac, 'RFC4648', { padding: false }).toLowerCase();
|
||||
return `${account.localPart}${account.separator}${subAddrName.value}${account.separator}${code}@${account.domain}`;
|
||||
if (subAddrName.value.indexOf(account.separator) != -1) {
|
||||
subAddrName.value = subAddrName.value.replaceAll(account.separator, '');
|
||||
}
|
||||
if (subAddrName.value) {
|
||||
var hasher = hmac.create(sha256, account.key);
|
||||
hasher.update(account.localPart);
|
||||
hasher.update(account.separator);
|
||||
hasher.update(subAddrName.value);
|
||||
const mac = hasher.digest();
|
||||
const offset = mac[mac.length - 1] & 0xf;
|
||||
const reduced_mac = mac.slice(offset, offset + 5);
|
||||
const code = base32Encode(reduced_mac, 'RFC4648', { padding: false }).toLowerCase();
|
||||
return `${account.localPart}${account.separator}${subAddrName.value}${account.separator}${code}@${account.domain}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
return '';
|
||||
|
@ -44,36 +57,39 @@ const generatedAddr = computed(() => {
|
|||
const copyAddr = () => {
|
||||
navigator.clipboard.writeText(generatedAddr.value);
|
||||
};
|
||||
const resetForm = () => {
|
||||
subAddrName.value = '';
|
||||
if (resetToDefault.value) {
|
||||
selectedAccountId.value = getDefaultAccount();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NavBarComponent />
|
||||
<LayoutComponent>
|
||||
<h1 class="title is-1">{{ $t("main.title") }}</h1>
|
||||
<div class="field">
|
||||
<label class="label" for="account-name">{{ $t("main.account") }}</label>
|
||||
<div class="control">
|
||||
<div class="select is-fullwidth">
|
||||
<select id="account-name" v-model="selectedAccountId">
|
||||
<option v-for="account in accounts" :key="account.id" :value="account.id">{{ account.localPart }}@{{ account.domain }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<h1>{{ $t("main.title") }}</h1>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="account-name">{{ $t("main.account") }}</label>
|
||||
<select class="form-select" id="account-name" v-model="selectedAccountId">
|
||||
<option v-for="account in sortedAccounts" :key="account.id" :value="account.id">{{ account.localPart }}@{{ account.domain }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label" for="sub-addr-name">{{ $t("main.name") }}</label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" id="sub-addr-name" :placeholder="$t('main.input')" v-model="subAddrName">
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="sub-addr-name">{{ $t("main.name") }}</label>
|
||||
<input class="form-control" type="text" id="sub-addr-name" :placeholder="$t('main.input')" v-model="subAddrName">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label" for="generated-addr">{{ $t("main.address") }}</label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" id="generated-addr" v-model="generatedAddr" disabled>
|
||||
</div>
|
||||
</div>
|
||||
<div class="buttons is-centered">
|
||||
<button class="button is-primary" @click="copyAddr">{{ $t("main.copy") }}</button>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="generated-addr">{{ $t("main.address") }}</label>
|
||||
<input class="form-control" type="text" id="generated-addr" v-model="generatedAddr" disabled>
|
||||
</div>
|
||||
|
||||
<ButtonGroupComponent>
|
||||
<button type="button" class="btn btn-primary" @click="copyAddr">{{ $t("main.copy") }}</button>
|
||||
<button type="button" class="btn btn-secondary" @click="resetForm">{{ $t("main.reset") }}</button>
|
||||
</ButtonGroupComponent>
|
||||
</LayoutComponent>
|
||||
</template>
|
||||
|
|
|
@ -1,41 +1,52 @@
|
|||
<script setup>
|
||||
import { sortAccounts } from '../accounts';
|
||||
import { computed } from 'vue';
|
||||
import { RouterLink, useRouter } from 'vue-router';
|
||||
import { useStorage } from '@vueuse/core';
|
||||
import ButtonGroupComponent from '../components/ButtonGroupComponent.vue';
|
||||
import LayoutComponent from '../components/LayoutComponent.vue';
|
||||
import NavBarComponent from '../components/NavBarComponent.vue';
|
||||
|
||||
const router = useRouter();
|
||||
const accounts = sortAccounts(useStorage('sake-accounts', []));
|
||||
const accounts = useStorage('sake-accounts', []);
|
||||
const sortedAccounts = computed(() => sortAccounts(accounts.value));
|
||||
|
||||
const deleteAccount = (accountId) => {
|
||||
return router.push(`/delete-account/${accountId}`);
|
||||
};
|
||||
const setDefaultAccount = (accountId) => {
|
||||
accounts.value = accounts.value.map((a) => {
|
||||
a.isDefault = a.id === accountId;
|
||||
return a;
|
||||
});
|
||||
};
|
||||
const toMainView = () => {
|
||||
return router.push('/');
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NavBarComponent />
|
||||
<LayoutComponent>
|
||||
<h1 class="title is-1">{{ $t("manageAccounts.title") }}</h1>
|
||||
<div class="block">
|
||||
<table class="table is-fullwidth">
|
||||
<h1>{{ $t("manageAccounts.title") }}</h1>
|
||||
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr v-for="account in accounts">
|
||||
<th class="has-text-right is-vcentered">
|
||||
<tr v-for="account in sortedAccounts">
|
||||
<th class="text-end align-middle">
|
||||
<span class="badge text-bg-primary" v-if="account.isDefault">{{ $t("manageAccounts.isDefault") }}</span>
|
||||
{{ account.localPart }}@{{ account.domain }}
|
||||
</th>
|
||||
<th>
|
||||
<button class="button is-danger" @click="deleteAccount(account.id)">{{ $t("manageAccounts.delete") }}</button>
|
||||
<button type="button" class="btn btn-primary me-2" @click="setDefaultAccount(account.id)" v-if="!account.isDefault">{{ $t("manageAccounts.setDefault") }}</button>
|
||||
<button type="button" class="btn btn-danger" @click="deleteAccount(account.id)">{{ $t("manageAccounts.delete") }}</button>
|
||||
</th>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="block">
|
||||
<div class="buttons is-centered">
|
||||
<button class="button is-light" @click="toMainView">{{ $t("manageAccounts.close") }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ButtonGroupComponent>
|
||||
<button type="button" class="btn btn-secondary" @click="toMainView">{{ $t("manageAccounts.close") }}</button>
|
||||
</ButtonGroupComponent>
|
||||
</LayoutComponent>
|
||||
</template>
|
||||
|
|
Loading…
Reference in a new issue