diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 3bf2a12..0000000 --- a/.dockerignore +++ /dev/null @@ -1,3 +0,0 @@ -dist -*/node_modules -Dockerfile* diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs deleted file mode 100644 index 2300230..0000000 --- a/.git-blame-ignore-revs +++ /dev/null @@ -1,11 +0,0 @@ -# https://docs.github.com/en/repositories/working-with-files/using-files/viewing-a-file#ignore-commits-in-the-blame-view - -# Run prettier (https://github.com/binwiederhier/ntfy/pull/746) -6f6a2d1f693070bf72e89d86748080e4825c9164 -c87549e71a10bc789eac8036078228f06e515a8e -ca5d736a7169eb6b4b0d849e061d5bf9565dcc53 -2e27f58963feb9e4d1c573d4745d07770777fa7d - -# Run eslint (https://github.com/binwiederhier/ntfy/pull/748) -f558b4dbe9bb5b9e0e87fada1215de2558353173 -8319f1cf26113167fb29fe12edaff5db74caf35f diff --git a/.github/images/logo.png b/.github/images/logo.png deleted file mode 100644 index 351db4d..0000000 Binary files a/.github/images/logo.png and /dev/null differ diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 0076c0f..92816e6 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -4,21 +4,30 @@ jobs: build: runs-on: ubuntu-latest steps: - - - name: Checkout code - uses: actions/checkout@v3 - name: Install Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v2 with: go-version: '1.19.x' - name: Install node - uses: actions/setup-node@v3 + uses: actions/setup-node@v2 with: - node-version: '18' - cache: 'npm' - cache-dependency-path: './web/package-lock.json' + node-version: '17' + - + name: Checkout code + uses: actions/checkout@v2 + - + name: Cache Go and npm modules + uses: actions/cache@v3 + with: + path: | + ~/go/pkg/mod + ~/go/bin + ~/.npm + web/node_modules + key: ${{ runner.os }}-ntfy-${{ hashFiles('**/go.sum', '**/package.lock') }} + restore-keys: ${{ runner.os }}-ntfy- - name: Install dependencies run: make build-deps-ubuntu diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 6991dea..2ba9b9c 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -30,7 +30,7 @@ jobs: run: | cd build/ntfy-docs.github.io git config user.name "GitHub Actions Bot" - git config user.email "" + git config user.email "<>" git add docs/ git commit -m "Updated docs" git push origin main diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index f709332..be13b96 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -7,21 +7,30 @@ jobs: release: runs-on: ubuntu-latest steps: - - - name: Checkout code - uses: actions/checkout@v3 - name: Install Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v2 with: go-version: '1.19.x' - name: Install node - uses: actions/setup-node@v3 + uses: actions/setup-node@v2 with: - node-version: '18' - cache: 'npm' - cache-dependency-path: './web/package-lock.json' + node-version: '17' + - + name: Checkout code + uses: actions/checkout@v2 + - + name: Cache Go and npm modules + uses: actions/cache@v3 + with: + path: | + ~/go/pkg/mod + ~/go/bin + ~/.npm + web/node_modules + key: ${{ runner.os }}-ntfy-${{ hashFiles('**/go.sum', '**/package.lock') }} + restore-keys: ${{ runner.os }}-ntfy- - name: Docker login uses: docker/login-action@v2 diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 7473567..544857c 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -4,21 +4,30 @@ jobs: test: runs-on: ubuntu-latest steps: - - - name: Checkout code - uses: actions/checkout@v3 - name: Install Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v2 with: go-version: '1.19.x' - name: Install node - uses: actions/setup-node@v3 + uses: actions/setup-node@v2 with: - node-version: '18' - cache: 'npm' - cache-dependency-path: './web/package-lock.json' + node-version: '17' + - + name: Checkout code + uses: actions/checkout@v2 + - + name: Cache Go and npm modules + uses: actions/cache@v3 + with: + path: | + ~/go/pkg/mod + ~/go/bin + ~/.npm + web/node_modules + key: ${{ runner.os }}-ntfy-${{ hashFiles('**/go.sum', '**/package.lock') }} + restore-keys: ${{ runner.os }}-ntfy- - name: Install dependencies run: make build-deps-ubuntu diff --git a/.gitignore b/.gitignore index f695607..2b566b7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ dist/ -dev-dist/ build/ .idea/ .vscode/ @@ -12,4 +11,3 @@ secrets/ *.iml node_modules/ .DS_Store -__pycache__ diff --git a/.goreleaser.yml b/.goreleaser.yml index 131a302..9ba8bb4 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -97,7 +97,7 @@ nfpms: - dst: /var/lib/ntfy type: dir - dst: /usr/share/ntfy/logo.png - src: web/public/static/images/ntfy.png + src: web/public/static/img/ntfy.png scripts: preinstall: "scripts/preinst.sh" postinstall: "scripts/postinst.sh" diff --git a/Dockerfile b/Dockerfile index feb813f..7c2052e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM r.batts.cloud/debian:testing +FROM alpine LABEL org.opencontainers.image.authors="philipp.heckel@gmail.com" LABEL org.opencontainers.image.url="https://ntfy.sh/" diff --git a/Dockerfile-build b/Dockerfile-build deleted file mode 100644 index 9c6d1bc..0000000 --- a/Dockerfile-build +++ /dev/null @@ -1,54 +0,0 @@ -FROM r.batts.cloud/golang:1.19 as builder - -ARG VERSION=dev -ARG COMMIT=unknown - -RUN apt-get update -RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash -RUN apt-get install -y \ - build-essential \ - nodejs \ - python3-pip \ - python3-venv - -WORKDIR /app -ADD Makefile . - -# docs -ADD ./requirements.txt . -RUN make docs-deps -ADD ./mkdocs.yml . -ADD ./docs ./docs -RUN make docs-build - -# web -ADD ./web/package.json ./web/package-lock.json ./web/ -RUN make web-deps -ADD ./web ./web -RUN make web-build - -# cli & server -ADD go.mod go.sum main.go ./ -ADD ./client ./client -ADD ./cmd ./cmd -ADD ./log ./log -ADD ./server ./server -ADD ./user ./user -ADD ./util ./util -RUN make VERSION=$VERSION COMMIT=$COMMIT cli-linux-server - -FROM r.batts.cloud/debian:testing - -LABEL org.opencontainers.image.authors="philipp.heckel@gmail.com" -LABEL org.opencontainers.image.url="https://ntfy.sh/" -LABEL org.opencontainers.image.documentation="https://docs.ntfy.sh/" -LABEL org.opencontainers.image.source="https://github.com/binwiederhier/ntfy" -LABEL org.opencontainers.image.vendor="Philipp C. Heckel" -LABEL org.opencontainers.image.licenses="Apache-2.0, GPL-2.0" -LABEL org.opencontainers.image.title="ntfy" -LABEL org.opencontainers.image.description="Send push notifications to your phone or desktop using PUT/POST" - -COPY --from=builder /app/dist/ntfy_linux_server/ntfy /usr/bin/ntfy - -EXPOSE 80/tcp -ENTRYPOINT ["ntfy"] diff --git a/Makefile b/Makefile index 440bfa6..8ca86da 100644 --- a/Makefile +++ b/Makefile @@ -31,16 +31,10 @@ help: @echo " make cli-darwin-server - Build client & server (no GoReleaser, current arch, macOS)" @echo " make cli-client - Build client only (no GoReleaser, current arch, Linux/macOS/Windows)" @echo - @echo "Build dev Docker:" - @echo " make docker-dev - Build client & server for current architecture using Docker only" - @echo @echo "Build web app:" @echo " make web - Build the web app" @echo " make web-deps - Install web app dependencies (npm install the universe)" @echo " make web-build - Actually build the web app" - @echo " make web-lint - Run eslint on the web app" - @echo " make web-format - Run prettier on the web app" - @echo " make web-format-check - Run prettier on the web app, but don't change anything" @echo @echo "Build documentation:" @echo " make docs - Build the documentation" @@ -86,33 +80,23 @@ build: web docs cli update: web-deps-update cli-deps-update docs-deps-update docker pull alpine -docker-dev: - docker build \ - --file ./Dockerfile-build \ - --tag binwiederhier/ntfy:$(VERSION) \ - --tag binwiederhier/ntfy:dev \ - --build-arg VERSION=$(VERSION) \ - --build-arg COMMIT=$(COMMIT) \ - ./ - # Ubuntu-specific build-deps-ubuntu: - sudo apt-get update - sudo apt-get install -y \ + sudo apt update + sudo apt install -y \ curl \ gcc-aarch64-linux-gnu \ gcc-arm-linux-gnueabi \ jq - which pip3 || sudo apt-get install -y python3-pip + which pip3 || sudo apt install -y python3-pip # Documentation docs: docs-deps docs-build -docs-build: venv .PHONY - @. venv/bin/activate && \ - if ! /bin/echo -e "import sys\nif sys.version_info < (3,8):\n exit(1)" | python3; then \ +docs-build: .PHONY + @if ! /bin/echo -e "import sys\nif sys.version_info < (3,8):\n exit(1)" | python3; then \ if which python3.8; then \ echo "python3.8 $(shell which mkdocs) build"; \ python3.8 $(shell which mkdocs) build; \ @@ -125,15 +109,10 @@ docs-build: venv .PHONY mkdocs build; \ fi -venv: - python3 -m venv ./venv - -docs-deps: venv .PHONY - . venv/bin/activate && \ +docs-deps: .PHONY pip3 install -r requirements.txt -docs-deps-update: venv .PHONY - . venv/bin/activate && \ +docs-deps-update: .PHONY pip3 install -r requirements.txt --upgrade @@ -148,7 +127,8 @@ web-build: && rm -rf ../server/site \ && mv build ../server/site \ && rm \ - ../server/site/config.js + ../server/site/config.js \ + ../server/site/asset-manifest.json web-deps: cd web && npm install @@ -157,37 +137,29 @@ web-deps: web-deps-update: cd web && npm update -web-format: - cd web && npm run format - -web-format-check: - cd web && npm run format:check - -web-lint: - cd web && npm run lint # Main server/client build cli: cli-deps - goreleaser build --snapshot --clean + goreleaser build --snapshot --rm-dist cli-linux-amd64: cli-deps-static-sites - goreleaser build --snapshot --clean --id ntfy_linux_amd64 + goreleaser build --snapshot --rm-dist --id ntfy_linux_amd64 cli-linux-armv6: cli-deps-static-sites cli-deps-gcc-armv6-armv7 - goreleaser build --snapshot --clean --id ntfy_linux_armv6 + goreleaser build --snapshot --rm-dist --id ntfy_linux_armv6 cli-linux-armv7: cli-deps-static-sites cli-deps-gcc-armv6-armv7 - goreleaser build --snapshot --clean --id ntfy_linux_armv7 + goreleaser build --snapshot --rm-dist --id ntfy_linux_armv7 cli-linux-arm64: cli-deps-static-sites cli-deps-gcc-arm64 - goreleaser build --snapshot --clean --id ntfy_linux_arm64 + goreleaser build --snapshot --rm-dist --id ntfy_linux_arm64 cli-windows-amd64: cli-deps-static-sites - goreleaser build --snapshot --clean --id ntfy_windows_amd64 + goreleaser build --snapshot --rm-dist --id ntfy_windows_amd64 cli-darwin-all: cli-deps-static-sites - goreleaser build --snapshot --clean --id ntfy_darwin_all + goreleaser build --snapshot --rm-dist --id ntfy_darwin_all cli-linux-server: cli-deps-static-sites # This is a target to build the CLI (including the server) manually. @@ -254,7 +226,7 @@ cli-build-results: # Test/check targets -check: test web-format-check fmt-check vet web-lint lint staticcheck +check: test fmt-check vet lint staticcheck test: .PHONY go test $(shell go list ./... | grep -vE 'ntfy/(test|examples|tools)') @@ -305,11 +277,11 @@ staticcheck: .PHONY # Releasing targets -release: clean cli-deps release-checks docs web check - goreleaser release --clean +release: clean update cli-deps release-checks docs web check + goreleaser release --rm-dist -release-snapshot: clean cli-deps docs web check - goreleaser release --snapshot --skip-publish --clean +release-snapshot: clean update cli-deps docs web check + goreleaser release --snapshot --skip-publish --rm-dist release-checks: $(eval LATEST_TAG := $(shell git describe --abbrev=0 --tags | cut -c2-)) diff --git a/README.md b/README.md index cebf55b..c42bfd1 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![ntfy](web/public/static/images/ntfy.png) +![ntfy](web/public/static/img/ntfy.png) # ntfy.sh | Send push notifications to your phone or desktop via PUT/POST [![Release](https://img.shields.io/github/release/binwiederhier/ntfy.svg?color=success&style=flat-square)](https://github.com/binwiederhier/ntfy/releases/latest) @@ -23,16 +23,13 @@ available on [Google Play](https://play.google.com/store/apps/details?id=io.heck as well as an [open source iOS app](https://github.com/binwiederhier/ntfy-ios) available on the [App Store](https://apps.apple.com/us/app/ntfy/id1625396347).

- - - - - + + + + +

-## [ntfy Pro](https://ntfy.sh/app) 💸 🎉 -I now offer paid plans for [ntfy.sh](https://ntfy.sh/) if you don't want to self-host, or you want to support the development of ntfy (→ [Purchase via web app](https://ntfy.sh/app)). You can **buy a plan for as low as $3.33/month** (if you use promo code `MYTOPIC`, limited time only). You can also donate via [GitHub Sponsors](https://github.com/sponsors/binwiederhier), and [Liberapay](https://liberapay.com/ntfy). I would be very humbled by your sponsorship. ❤️ - ## **[Documentation](https://ntfy.sh/docs/)** [Getting started](https://ntfy.sh/docs/) | @@ -129,18 +126,6 @@ account costs. Even small donations are very much appreciated. A big fat **Thank - - - - - - - - - - - - I'd also like to thank JetBrains for providing their awesome [IntelliJ IDEA](https://www.jetbrains.com/idea/) to me for free, and [DigitalOcean](https://m.do.co/c/442b929528db) (*referral link*) for supporting the project: diff --git a/client/client.go b/client/client.go index 93cf7da..b744fa1 100644 --- a/client/client.go +++ b/client/client.go @@ -11,25 +11,23 @@ import ( "heckel.io/ntfy/util" "io" "net/http" - "regexp" "strings" "sync" "time" ) +// Event type constants const ( - // MessageEvent identifies a message event - MessageEvent = "message" + MessageEvent = "message" + KeepaliveEvent = "keepalive" + OpenEvent = "open" + PollRequestEvent = "poll_request" ) const ( maxResponseBytes = 4096 ) -var ( - topicRegex = regexp.MustCompile(`^[-_A-Za-z0-9]{1,64}$`) // Same as in server/server.go -) - // Client is the ntfy client that can be used to publish and subscribe to ntfy topics type Client struct { Messages chan *Message @@ -98,14 +96,8 @@ func (c *Client) Publish(topic, message string, options ...PublishOption) (*Mess // To pass title, priority and tags, check out WithTitle, WithPriority, WithTagsList, WithDelay, WithNoCache, // WithNoFirebase, and the generic WithHeader. func (c *Client) PublishReader(topic string, body io.Reader, options ...PublishOption) (*Message, error) { - topicURL, err := c.expandTopicURL(topic) - if err != nil { - return nil, err - } - req, err := http.NewRequest("POST", topicURL, body) - if err != nil { - return nil, err - } + topicURL := c.expandTopicURL(topic) + req, _ := http.NewRequest("POST", topicURL, body) for _, option := range options { if err := option(req); err != nil { return nil, err @@ -141,14 +133,11 @@ func (c *Client) PublishReader(topic string, body io.Reader, options ...PublishO // By default, all messages will be returned, but you can change this behavior using a SubscribeOption. // See WithSince, WithSinceAll, WithSinceUnixTime, WithScheduled, and the generic WithQueryParam. func (c *Client) Poll(topic string, options ...SubscribeOption) ([]*Message, error) { - topicURL, err := c.expandTopicURL(topic) - if err != nil { - return nil, err - } ctx := context.Background() messages := make([]*Message, 0) msgChan := make(chan *Message) errChan := make(chan error) + topicURL := c.expandTopicURL(topic) log.Debug("%s Polling from topic", util.ShortTopicURL(topicURL)) options = append(options, WithPoll()) go func() { @@ -177,18 +166,15 @@ func (c *Client) Poll(topic string, options ...SubscribeOption) ([]*Message, err // Example: // // c := client.New(client.NewConfig()) -// subscriptionID, _ := c.Subscribe("mytopic") +// subscriptionID := c.Subscribe("mytopic") // for m := range c.Messages { // fmt.Printf("New message: %s", m.Message) // } -func (c *Client) Subscribe(topic string, options ...SubscribeOption) (string, error) { - topicURL, err := c.expandTopicURL(topic) - if err != nil { - return "", err - } +func (c *Client) Subscribe(topic string, options ...SubscribeOption) string { c.mu.Lock() defer c.mu.Unlock() subscriptionID := util.RandomString(10) + topicURL := c.expandTopicURL(topic) log.Debug("%s Subscribing to topic", util.ShortTopicURL(topicURL)) ctx, cancel := context.WithCancel(context.Background()) c.subscriptions[subscriptionID] = &subscription{ @@ -197,7 +183,7 @@ func (c *Client) Subscribe(topic string, options ...SubscribeOption) (string, er cancel: cancel, } go handleSubscribeConnLoop(ctx, c.Messages, topicURL, subscriptionID, options...) - return subscriptionID, nil + return subscriptionID } // Unsubscribe unsubscribes from a topic that has been previously subscribed to using the unique @@ -213,16 +199,31 @@ func (c *Client) Unsubscribe(subscriptionID string) { sub.cancel() } -func (c *Client) expandTopicURL(topic string) (string, error) { +// UnsubscribeAll unsubscribes from a topic that has been previously subscribed with Subscribe. +// If there are multiple subscriptions matching the topic, all of them are unsubscribed from. +// +// A topic can be either a full URL (e.g. https://myhost.lan/mytopic), a short URL which is then prepended https:// +// (e.g. myhost.lan -> https://myhost.lan), or a short name which is expanded using the default host in the +// config (e.g. mytopic -> https://ntfy.sh/mytopic). +func (c *Client) UnsubscribeAll(topic string) { + c.mu.Lock() + defer c.mu.Unlock() + topicURL := c.expandTopicURL(topic) + for _, sub := range c.subscriptions { + if sub.topicURL == topicURL { + delete(c.subscriptions, sub.ID) + sub.cancel() + } + } +} + +func (c *Client) expandTopicURL(topic string) string { if strings.HasPrefix(topic, "http://") || strings.HasPrefix(topic, "https://") { - return topic, nil + return topic } else if strings.Contains(topic, "/") { - return fmt.Sprintf("https://%s", topic), nil + return fmt.Sprintf("https://%s", topic) } - if !topicRegex.MatchString(topic) { - return "", fmt.Errorf("invalid topic name: %s", topic) - } - return fmt.Sprintf("%s/%s", c.config.DefaultHost, topic), nil + return fmt.Sprintf("%s/%s", c.config.DefaultHost, topic) } func handleSubscribeConnLoop(ctx context.Context, msgChan chan *Message, topicURL, subcriptionID string, options ...SubscribeOption) { diff --git a/client/client.yml b/client/client.yml index 1b81b80..d3ba272 100644 --- a/client/client.yml +++ b/client/client.yml @@ -5,12 +5,10 @@ # # default-host: https://ntfy.sh -# Default credentials will be used with "ntfy publish" and "ntfy subscribe" if no other credentials are provided. -# You can set a default token to use or a default user:password combination, but not both. For an empty password, -# use empty double-quotes ("") - -# default-token: - +# Default username and password will be used with "ntfy publish" if no credentials are provided on command line +# Default username and password will be used with "ntfy subscribe" if no credentials are provided in subscription below +# For an empty password, use empty double-quotes ("") +# # default-user: # default-password: @@ -32,8 +30,6 @@ # command: 'notify-send "$m"' # user: phill # password: mypass -# - topic: token_topic -# token: tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2 # # Variables: # Variable Aliases Description diff --git a/client/client_test.go b/client/client_test.go index f0b15a3..a71ea5c 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -21,7 +21,7 @@ func TestClient_Publish_Subscribe(t *testing.T) { defer test.StopServer(t, s, port) c := client.New(newTestConfig(port)) - subscriptionID, _ := c.Subscribe("mytopic") + subscriptionID := c.Subscribe("mytopic") time.Sleep(time.Second) msg, err := c.Publish("mytopic", "some message") diff --git a/client/config.go b/client/config.go index d4337d4..b2efc1d 100644 --- a/client/config.go +++ b/client/config.go @@ -12,22 +12,17 @@ const ( // Config is the config struct for a Client type Config struct { - DefaultHost string `yaml:"default-host"` - DefaultUser string `yaml:"default-user"` - DefaultPassword *string `yaml:"default-password"` - DefaultToken string `yaml:"default-token"` - DefaultCommand string `yaml:"default-command"` - Subscribe []Subscribe `yaml:"subscribe"` -} - -// Subscribe is the struct for a Subscription within Config -type Subscribe struct { - Topic string `yaml:"topic"` - User string `yaml:"user"` - Password *string `yaml:"password"` - Token string `yaml:"token"` - Command string `yaml:"command"` - If map[string]string `yaml:"if"` + DefaultHost string `yaml:"default-host"` + DefaultUser string `yaml:"default-user"` + DefaultPassword *string `yaml:"default-password"` + DefaultCommand string `yaml:"default-command"` + Subscribe []struct { + Topic string `yaml:"topic"` + User string `yaml:"user"` + Password *string `yaml:"password"` + Command string `yaml:"command"` + If map[string]string `yaml:"if"` + } `yaml:"subscribe"` } // NewConfig creates a new Config struct for a Client @@ -36,7 +31,6 @@ func NewConfig() *Config { DefaultHost: DefaultBaseURL, DefaultUser: "", DefaultPassword: nil, - DefaultToken: "", DefaultCommand: "", Subscribe: nil, } diff --git a/client/config_test.go b/client/config_test.go index f22e6b2..0a71c3b 100644 --- a/client/config_test.go +++ b/client/config_test.go @@ -116,25 +116,3 @@ subscribe: require.Equal(t, "phil", conf.Subscribe[0].User) require.Nil(t, conf.Subscribe[0].Password) } - -func TestConfig_DefaultToken(t *testing.T) { - filename := filepath.Join(t.TempDir(), "client.yml") - require.Nil(t, os.WriteFile(filename, []byte(` -default-host: http://localhost -default-token: tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2 -subscribe: - - topic: mytopic -`), 0600)) - - conf, err := client.LoadConfig(filename) - require.Nil(t, err) - require.Equal(t, "http://localhost", conf.DefaultHost) - require.Equal(t, "", conf.DefaultUser) - require.Nil(t, conf.DefaultPassword) - require.Equal(t, "tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2", conf.DefaultToken) - require.Equal(t, 1, len(conf.Subscribe)) - require.Equal(t, "mytopic", conf.Subscribe[0].Topic) - require.Equal(t, "", conf.Subscribe[0].User) - require.Nil(t, conf.Subscribe[0].Password) - require.Equal(t, "", conf.Subscribe[0].Token) -} diff --git a/cmd/publish.go b/cmd/publish.go index 0179f9f..21578d3 100644 --- a/cmd/publish.go +++ b/cmd/publish.go @@ -154,7 +154,8 @@ func execPublish(c *cli.Context) error { } if token != "" { options = append(options, client.WithBearerAuth(token)) - } else if user != "" { + } + if user != "" { var pass string parts := strings.SplitN(user, ":", 2) if len(parts) == 2 { @@ -170,9 +171,7 @@ func execPublish(c *cli.Context) error { fmt.Fprintf(c.App.ErrWriter, "\r%s\r", strings.Repeat(" ", 20)) } options = append(options, client.WithBasicAuth(user, pass)) - } else if conf.DefaultToken != "" { - options = append(options, client.WithBearerAuth(conf.DefaultToken)) - } else if conf.DefaultUser != "" && conf.DefaultPassword != nil { + } else if token == "" && conf.DefaultUser != "" && conf.DefaultPassword != nil { options = append(options, client.WithBasicAuth(conf.DefaultUser, *conf.DefaultPassword)) } if pid > 0 { diff --git a/cmd/publish_test.go b/cmd/publish_test.go index a254f47..6fe2d00 100644 --- a/cmd/publish_test.go +++ b/cmd/publish_test.go @@ -5,11 +5,8 @@ import ( "github.com/stretchr/testify/require" "heckel.io/ntfy/test" "heckel.io/ntfy/util" - "net/http" - "net/http/httptest" "os" "os/exec" - "path/filepath" "strconv" "strings" "testing" @@ -133,7 +130,7 @@ func TestCLI_Publish_Wait_PID_And_Cmd(t *testing.T) { require.Equal(t, `command failed: does-not-exist-no-really "really though", error: exec: "does-not-exist-no-really": executable file not found in $PATH`, err.Error()) // Tests with NTFY_TOPIC set //// - t.Setenv("NTFY_TOPIC", topic) + require.Nil(t, os.Setenv("NTFY_TOPIC", topic)) // Test: Successful command with NTFY_TOPIC app, _, stdout, _ = newTestApp() @@ -150,151 +147,3 @@ func TestCLI_Publish_Wait_PID_And_Cmd(t *testing.T) { m = toMessage(t, stdout.String()) require.Regexp(t, `Process with PID \d+ exited after .+ms`, m.Message) } - -func TestCLI_Publish_Default_UserPass(t *testing.T) { - message := `{"id":"RXIQBFaieLVr","time":124,"expires":1124,"event":"message","topic":"mytopic","message":"triggered"}` - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, "/mytopic", r.URL.Path) - require.Equal(t, "Basic cGhpbGlwcDpteXBhc3M=", r.Header.Get("Authorization")) - - w.WriteHeader(http.StatusOK) - w.Write([]byte(message)) - })) - defer server.Close() - - filename := filepath.Join(t.TempDir(), "client.yml") - require.Nil(t, os.WriteFile(filename, []byte(fmt.Sprintf(` -default-host: %s -default-user: philipp -default-password: mypass -`, server.URL)), 0600)) - - app, _, stdout, _ := newTestApp() - require.Nil(t, app.Run([]string{"ntfy", "publish", "--config=" + filename, "mytopic", "triggered"})) - m := toMessage(t, stdout.String()) - require.Equal(t, "triggered", m.Message) -} - -func TestCLI_Publish_Default_Token(t *testing.T) { - message := `{"id":"RXIQBFaieLVr","time":124,"expires":1124,"event":"message","topic":"mytopic","message":"triggered"}` - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, "/mytopic", r.URL.Path) - require.Equal(t, "Bearer tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2", r.Header.Get("Authorization")) - - w.WriteHeader(http.StatusOK) - w.Write([]byte(message)) - })) - defer server.Close() - - filename := filepath.Join(t.TempDir(), "client.yml") - require.Nil(t, os.WriteFile(filename, []byte(fmt.Sprintf(` -default-host: %s -default-token: tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2 -`, server.URL)), 0600)) - - app, _, stdout, _ := newTestApp() - require.Nil(t, app.Run([]string{"ntfy", "publish", "--config=" + filename, "mytopic", "triggered"})) - m := toMessage(t, stdout.String()) - require.Equal(t, "triggered", m.Message) -} - -func TestCLI_Publish_Default_UserPass_CLI_Token(t *testing.T) { - message := `{"id":"RXIQBFaieLVr","time":124,"expires":1124,"event":"message","topic":"mytopic","message":"triggered"}` - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, "/mytopic", r.URL.Path) - require.Equal(t, "Bearer tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2", r.Header.Get("Authorization")) - - w.WriteHeader(http.StatusOK) - w.Write([]byte(message)) - })) - defer server.Close() - - filename := filepath.Join(t.TempDir(), "client.yml") - require.Nil(t, os.WriteFile(filename, []byte(fmt.Sprintf(` -default-host: %s -default-user: philipp -default-password: mypass -`, server.URL)), 0600)) - - app, _, stdout, _ := newTestApp() - require.Nil(t, app.Run([]string{"ntfy", "publish", "--config=" + filename, "--token", "tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2", "mytopic", "triggered"})) - m := toMessage(t, stdout.String()) - require.Equal(t, "triggered", m.Message) -} - -func TestCLI_Publish_Default_Token_CLI_UserPass(t *testing.T) { - message := `{"id":"RXIQBFaieLVr","time":124,"expires":1124,"event":"message","topic":"mytopic","message":"triggered"}` - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, "/mytopic", r.URL.Path) - require.Equal(t, "Basic cGhpbGlwcDpteXBhc3M=", r.Header.Get("Authorization")) - - w.WriteHeader(http.StatusOK) - w.Write([]byte(message)) - })) - defer server.Close() - - filename := filepath.Join(t.TempDir(), "client.yml") - require.Nil(t, os.WriteFile(filename, []byte(fmt.Sprintf(` -default-host: %s -default-token: tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2 -`, server.URL)), 0600)) - - app, _, stdout, _ := newTestApp() - require.Nil(t, app.Run([]string{"ntfy", "publish", "--config=" + filename, "--user", "philipp:mypass", "mytopic", "triggered"})) - m := toMessage(t, stdout.String()) - require.Equal(t, "triggered", m.Message) -} - -func TestCLI_Publish_Default_Token_CLI_Token(t *testing.T) { - message := `{"id":"RXIQBFaieLVr","time":124,"expires":1124,"event":"message","topic":"mytopic","message":"triggered"}` - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, "/mytopic", r.URL.Path) - require.Equal(t, "Bearer tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2", r.Header.Get("Authorization")) - - w.WriteHeader(http.StatusOK) - w.Write([]byte(message)) - })) - defer server.Close() - - filename := filepath.Join(t.TempDir(), "client.yml") - require.Nil(t, os.WriteFile(filename, []byte(fmt.Sprintf(` -default-host: %s -default-token: tk_FAKETOKEN01234567890FAKETOKEN -`, server.URL)), 0600)) - - app, _, stdout, _ := newTestApp() - require.Nil(t, app.Run([]string{"ntfy", "publish", "--config=" + filename, "--token", "tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2", "mytopic", "triggered"})) - m := toMessage(t, stdout.String()) - require.Equal(t, "triggered", m.Message) -} - -func TestCLI_Publish_Default_UserPass_CLI_UserPass(t *testing.T) { - message := `{"id":"RXIQBFaieLVr","time":124,"expires":1124,"event":"message","topic":"mytopic","message":"triggered"}` - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, "/mytopic", r.URL.Path) - require.Equal(t, "Basic cGhpbGlwcDpteXBhc3M=", r.Header.Get("Authorization")) - - w.WriteHeader(http.StatusOK) - w.Write([]byte(message)) - })) - defer server.Close() - - filename := filepath.Join(t.TempDir(), "client.yml") - require.Nil(t, os.WriteFile(filename, []byte(fmt.Sprintf(` -default-host: %s -default-user: philipp -default-password: fakepass -`, server.URL)), 0600)) - - app, _, stdout, _ := newTestApp() - require.Nil(t, app.Run([]string{"ntfy", "publish", "--config=" + filename, "--user", "philipp:mypass", "mytopic", "triggered"})) - m := toMessage(t, stdout.String()) - require.Equal(t, "triggered", m.Message) -} - -func TestCLI_Publish_Token_And_UserPass(t *testing.T) { - app, _, _, _ := newTestApp() - err := app.Run([]string{"ntfy", "publish", "--token", "tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2", "--user", "philipp:mypass", "mytopic", "triggered"}) - require.Error(t, err) - require.Equal(t, "cannot set both --user and --token", err.Error()) -} diff --git a/cmd/serve.go b/cmd/serve.go index 5d5381b..7883c8d 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -37,8 +37,8 @@ var flagsServe = append( append([]cli.Flag{}, flagsDefault...), &cli.StringFlag{Name: "config", Aliases: []string{"c"}, EnvVars: []string{"NTFY_CONFIG_FILE"}, Value: defaultServerConfigFile, DefaultText: defaultServerConfigFile, Usage: "config file"}, altsrc.NewStringFlag(&cli.StringFlag{Name: "base-url", Aliases: []string{"base_url", "B"}, EnvVars: []string{"NTFY_BASE_URL"}, Usage: "externally visible base URL for this host (e.g. https://ntfy.sh)"}), - altsrc.NewStringFlag(&cli.StringFlag{Name: "listen-http", Aliases: []string{"listen_http", "l"}, EnvVars: []string{"NTFY_LISTEN_HTTP"}, Value: server.DefaultListenHTTP, Usage: "ip:port used as HTTP listen address"}), - altsrc.NewStringFlag(&cli.StringFlag{Name: "listen-https", Aliases: []string{"listen_https", "L"}, EnvVars: []string{"NTFY_LISTEN_HTTPS"}, Usage: "ip:port used as HTTPS listen address"}), + altsrc.NewStringFlag(&cli.StringFlag{Name: "listen-http", Aliases: []string{"listen_http", "l"}, EnvVars: []string{"NTFY_LISTEN_HTTP"}, Value: server.DefaultListenHTTP, Usage: "ip:port used to as HTTP listen address"}), + altsrc.NewStringFlag(&cli.StringFlag{Name: "listen-https", Aliases: []string{"listen_https", "L"}, EnvVars: []string{"NTFY_LISTEN_HTTPS"}, Usage: "ip:port used to as HTTPS listen address"}), altsrc.NewStringFlag(&cli.StringFlag{Name: "listen-unix", Aliases: []string{"listen_unix", "U"}, EnvVars: []string{"NTFY_LISTEN_UNIX"}, Usage: "listen on unix socket path"}), altsrc.NewIntFlag(&cli.IntFlag{Name: "listen-unix-mode", Aliases: []string{"listen_unix_mode"}, EnvVars: []string{"NTFY_LISTEN_UNIX_MODE"}, DefaultText: "system default", Usage: "file permissions of unix socket, e.g. 0700"}), altsrc.NewStringFlag(&cli.StringFlag{Name: "key-file", Aliases: []string{"key_file", "K"}, EnvVars: []string{"NTFY_KEY_FILE"}, Usage: "private key file, if listen-https is set"}), @@ -59,12 +59,11 @@ var flagsServe = append( altsrc.NewDurationFlag(&cli.DurationFlag{Name: "keepalive-interval", Aliases: []string{"keepalive_interval", "k"}, EnvVars: []string{"NTFY_KEEPALIVE_INTERVAL"}, Value: server.DefaultKeepaliveInterval, Usage: "interval of keepalive messages"}), altsrc.NewDurationFlag(&cli.DurationFlag{Name: "manager-interval", Aliases: []string{"manager_interval", "m"}, EnvVars: []string{"NTFY_MANAGER_INTERVAL"}, Value: server.DefaultManagerInterval, Usage: "interval of for message pruning and stats printing"}), altsrc.NewStringSliceFlag(&cli.StringSliceFlag{Name: "disallowed-topics", Aliases: []string{"disallowed_topics"}, EnvVars: []string{"NTFY_DISALLOWED_TOPICS"}, Usage: "topics that are not allowed to be used"}), - altsrc.NewStringFlag(&cli.StringFlag{Name: "web-root", Aliases: []string{"web_root"}, EnvVars: []string{"NTFY_WEB_ROOT"}, Value: "/", Usage: "sets root of the web app (e.g. /, or /app), or disables it (disable)"}), + altsrc.NewStringFlag(&cli.StringFlag{Name: "web-root", Aliases: []string{"web_root"}, EnvVars: []string{"NTFY_WEB_ROOT"}, Value: "app", Usage: "sets web root to landing page (home), web app (app) or disabled (disable)"}), altsrc.NewBoolFlag(&cli.BoolFlag{Name: "enable-signup", Aliases: []string{"enable_signup"}, EnvVars: []string{"NTFY_ENABLE_SIGNUP"}, Value: false, Usage: "allows users to sign up via the web app, or API"}), altsrc.NewBoolFlag(&cli.BoolFlag{Name: "enable-login", Aliases: []string{"enable_login"}, EnvVars: []string{"NTFY_ENABLE_LOGIN"}, Value: false, Usage: "allows users to log in via the web app, or API"}), altsrc.NewBoolFlag(&cli.BoolFlag{Name: "enable-reservations", Aliases: []string{"enable_reservations"}, EnvVars: []string{"NTFY_ENABLE_RESERVATIONS"}, Value: false, Usage: "allows users to reserve topics (if their tier allows it)"}), altsrc.NewStringFlag(&cli.StringFlag{Name: "upstream-base-url", Aliases: []string{"upstream_base_url"}, EnvVars: []string{"NTFY_UPSTREAM_BASE_URL"}, Value: "", Usage: "forward poll request to an upstream server, this is needed for iOS push notifications for self-hosted servers"}), - altsrc.NewStringFlag(&cli.StringFlag{Name: "upstream-access-token", Aliases: []string{"upstream_access_token"}, EnvVars: []string{"NTFY_UPSTREAM_ACCESS_TOKEN"}, Value: "", Usage: "access token to use for the upstream server; needed only if upstream rate limits are exceeded or upstream server requires auth"}), altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-sender-addr", Aliases: []string{"smtp_sender_addr"}, EnvVars: []string{"NTFY_SMTP_SENDER_ADDR"}, Usage: "SMTP server address (host:port) for outgoing emails"}), altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-sender-user", Aliases: []string{"smtp_sender_user"}, EnvVars: []string{"NTFY_SMTP_SENDER_USER"}, Usage: "SMTP user (if e-mail sending is enabled)"}), altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-sender-pass", Aliases: []string{"smtp_sender_pass"}, EnvVars: []string{"NTFY_SMTP_SENDER_PASS"}, Usage: "SMTP password (if e-mail sending is enabled)"}), @@ -72,10 +71,6 @@ var flagsServe = append( altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-server-listen", Aliases: []string{"smtp_server_listen"}, EnvVars: []string{"NTFY_SMTP_SERVER_LISTEN"}, Usage: "SMTP server address (ip:port) for incoming emails, e.g. :25"}), altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-server-domain", Aliases: []string{"smtp_server_domain"}, EnvVars: []string{"NTFY_SMTP_SERVER_DOMAIN"}, Usage: "SMTP domain for incoming e-mail, e.g. ntfy.sh"}), altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-server-addr-prefix", Aliases: []string{"smtp_server_addr_prefix"}, EnvVars: []string{"NTFY_SMTP_SERVER_ADDR_PREFIX"}, Usage: "SMTP email address prefix for topics to prevent spam (e.g. 'ntfy-')"}), - altsrc.NewStringFlag(&cli.StringFlag{Name: "twilio-account", Aliases: []string{"twilio_account"}, EnvVars: []string{"NTFY_TWILIO_ACCOUNT"}, Usage: "Twilio account SID, used for phone calls, e.g. AC123..."}), - altsrc.NewStringFlag(&cli.StringFlag{Name: "twilio-auth-token", Aliases: []string{"twilio_auth_token"}, EnvVars: []string{"NTFY_TWILIO_AUTH_TOKEN"}, Usage: "Twilio auth token"}), - altsrc.NewStringFlag(&cli.StringFlag{Name: "twilio-phone-number", Aliases: []string{"twilio_phone_number"}, EnvVars: []string{"NTFY_TWILIO_PHONE_NUMBER"}, Usage: "Twilio number to use for outgoing calls"}), - altsrc.NewStringFlag(&cli.StringFlag{Name: "twilio-verify-service", Aliases: []string{"twilio_verify_service"}, EnvVars: []string{"NTFY_TWILIO_VERIFY_SERVICE"}, Usage: "Twilio Verify service ID, used for phone number verification"}), altsrc.NewIntFlag(&cli.IntFlag{Name: "global-topic-limit", Aliases: []string{"global_topic_limit", "T"}, EnvVars: []string{"NTFY_GLOBAL_TOPIC_LIMIT"}, Value: server.DefaultTotalTopicLimit, Usage: "total number of topics allowed"}), altsrc.NewIntFlag(&cli.IntFlag{Name: "visitor-subscription-limit", Aliases: []string{"visitor_subscription_limit"}, EnvVars: []string{"NTFY_VISITOR_SUBSCRIPTION_LIMIT"}, Value: server.DefaultVisitorSubscriptionLimit, Usage: "number of subscriptions per visitor"}), altsrc.NewStringFlag(&cli.StringFlag{Name: "visitor-attachment-total-size-limit", Aliases: []string{"visitor_attachment_total_size_limit"}, EnvVars: []string{"NTFY_VISITOR_ATTACHMENT_TOTAL_SIZE_LIMIT"}, Value: "100M", Usage: "total storage limit used for attachments per visitor"}), @@ -86,14 +81,11 @@ var flagsServe = append( altsrc.NewIntFlag(&cli.IntFlag{Name: "visitor-message-daily-limit", Aliases: []string{"visitor_message_daily_limit"}, EnvVars: []string{"NTFY_VISITOR_MESSAGE_DAILY_LIMIT"}, Value: server.DefaultVisitorMessageDailyLimit, Usage: "max messages per visitor per day, derived from request limit if unset"}), altsrc.NewIntFlag(&cli.IntFlag{Name: "visitor-email-limit-burst", Aliases: []string{"visitor_email_limit_burst"}, EnvVars: []string{"NTFY_VISITOR_EMAIL_LIMIT_BURST"}, Value: server.DefaultVisitorEmailLimitBurst, Usage: "initial limit of e-mails per visitor"}), altsrc.NewDurationFlag(&cli.DurationFlag{Name: "visitor-email-limit-replenish", Aliases: []string{"visitor_email_limit_replenish"}, EnvVars: []string{"NTFY_VISITOR_EMAIL_LIMIT_REPLENISH"}, Value: server.DefaultVisitorEmailLimitReplenish, Usage: "interval at which burst limit is replenished (one per x)"}), - altsrc.NewBoolFlag(&cli.BoolFlag{Name: "visitor-subscriber-rate-limiting", Aliases: []string{"visitor_subscriber_rate_limiting"}, EnvVars: []string{"NTFY_VISITOR_SUBSCRIBER_RATE_LIMITING"}, Value: false, Usage: "enables subscriber-based rate limiting"}), + altsrc.NewBoolFlag(&cli.BoolFlag{Name: "visitor-subscriber-rate-limiting", Aliases: []string{"enable_rate_visitor"}, EnvVars: []string{"NTFY_ENABLE_RATE_VISITOR"}, Value: false, Usage: "enables subscriber-based rate limiting for UnifiedPush topics"}), altsrc.NewBoolFlag(&cli.BoolFlag{Name: "behind-proxy", Aliases: []string{"behind_proxy", "P"}, EnvVars: []string{"NTFY_BEHIND_PROXY"}, Value: false, Usage: "if set, use X-Forwarded-For header to determine visitor IP address (for rate limiting)"}), altsrc.NewStringFlag(&cli.StringFlag{Name: "stripe-secret-key", Aliases: []string{"stripe_secret_key"}, EnvVars: []string{"NTFY_STRIPE_SECRET_KEY"}, Value: "", Usage: "key used for the Stripe API communication, this enables payments"}), altsrc.NewStringFlag(&cli.StringFlag{Name: "stripe-webhook-key", Aliases: []string{"stripe_webhook_key"}, EnvVars: []string{"NTFY_STRIPE_WEBHOOK_KEY"}, Value: "", Usage: "key required to validate the authenticity of incoming webhooks from Stripe"}), altsrc.NewStringFlag(&cli.StringFlag{Name: "billing-contact", Aliases: []string{"billing_contact"}, EnvVars: []string{"NTFY_BILLING_CONTACT"}, Value: "", Usage: "e-mail or website to display in upgrade dialog (only if payments are enabled)"}), - altsrc.NewBoolFlag(&cli.BoolFlag{Name: "enable-metrics", Aliases: []string{"enable_metrics"}, EnvVars: []string{"NTFY_ENABLE_METRICS"}, Value: false, Usage: "if set, Prometheus metrics are exposed via the /metrics endpoint"}), - altsrc.NewStringFlag(&cli.StringFlag{Name: "metrics-listen-http", Aliases: []string{"metrics_listen_http"}, EnvVars: []string{"NTFY_METRICS_LISTEN_HTTP"}, Usage: "ip:port used to expose the metrics endpoint (implicitly enables metrics)"}), - altsrc.NewStringFlag(&cli.StringFlag{Name: "profile-listen-http", Aliases: []string{"profile_listen_http"}, EnvVars: []string{"NTFY_PROFILE_LISTEN_HTTP"}, Usage: "ip:port used to expose the profiling endpoints (implicitly enables profiling)"}), ) var cmdServe = &cli.Command{ @@ -149,7 +141,6 @@ func execServe(c *cli.Context) error { enableLogin := c.Bool("enable-login") enableReservations := c.Bool("enable-reservations") upstreamBaseURL := c.String("upstream-base-url") - upstreamAccessToken := c.String("upstream-access-token") smtpSenderAddr := c.String("smtp-sender-addr") smtpSenderUser := c.String("smtp-sender-user") smtpSenderPass := c.String("smtp-sender-pass") @@ -157,10 +148,6 @@ func execServe(c *cli.Context) error { smtpServerListen := c.String("smtp-server-listen") smtpServerDomain := c.String("smtp-server-domain") smtpServerAddrPrefix := c.String("smtp-server-addr-prefix") - twilioAccount := c.String("twilio-account") - twilioAuthToken := c.String("twilio-auth-token") - twilioPhoneNumber := c.String("twilio-phone-number") - twilioVerifyService := c.String("twilio-verify-service") totalTopicLimit := c.Int("global-topic-limit") visitorSubscriptionLimit := c.Int("visitor-subscription-limit") visitorSubscriberRateLimiting := c.Bool("visitor-subscriber-rate-limiting") @@ -176,9 +163,6 @@ func execServe(c *cli.Context) error { stripeSecretKey := c.String("stripe-secret-key") stripeWebhookKey := c.String("stripe-webhook-key") billingContact := c.String("billing-contact") - metricsListenHTTP := c.String("metrics-listen-http") - enableMetrics := c.Bool("enable-metrics") || metricsListenHTTP != "" - profileListenHTTP := c.String("profile-listen-http") // Check values if firebaseKeyFile != "" && !util.FileExists(firebaseKeyFile) { @@ -205,6 +189,8 @@ func execServe(c *cli.Context) error { return errors.New("if set, base-url must start with http:// or https://") } else if baseURL != "" && strings.HasSuffix(baseURL, "/") { return errors.New("if set, base-url must not end with a slash (/)") + } else if !util.Contains([]string{"app", "home", "disable"}, webRoot) { + return errors.New("if set, web-root must be 'home' or 'app'") } else if upstreamBaseURL != "" && !strings.HasPrefix(upstreamBaseURL, "http://") && !strings.HasPrefix(upstreamBaseURL, "https://") { return errors.New("if set, upstream-base-url must start with http:// or https://") } else if upstreamBaseURL != "" && strings.HasSuffix(upstreamBaseURL, "/") { @@ -219,20 +205,10 @@ func execServe(c *cli.Context) error { return errors.New("cannot set enable-signup without also setting enable-login") } else if stripeSecretKey != "" && (stripeWebhookKey == "" || baseURL == "") { return errors.New("if stripe-secret-key is set, stripe-webhook-key and base-url must also be set") - } else if twilioAccount != "" && (twilioAuthToken == "" || twilioPhoneNumber == "" || twilioVerifyService == "" || baseURL == "" || authFile == "") { - return errors.New("if twilio-account is set, twilio-auth-token, twilio-phone-number, twilio-verify-service, base-url, and auth-file must also be set") } - // Backwards compatibility - if webRoot == "app" { - webRoot = "/" - } else if webRoot == "home" { - webRoot = "/app" - } else if webRoot == "disable" { - webRoot = "" - } else if !strings.HasPrefix(webRoot, "/") { - webRoot = "/" + webRoot - } + webRootIsApp := webRoot == "app" + enableWeb := webRoot != "disable" // Default auth permissions authDefault, err := user.ParsePermission(authDefaultAccess) @@ -311,9 +287,8 @@ func execServe(c *cli.Context) error { conf.KeepaliveInterval = keepaliveInterval conf.ManagerInterval = managerInterval conf.DisallowedTopics = disallowedTopics - conf.WebRoot = webRoot + conf.WebRootIsApp = webRootIsApp conf.UpstreamBaseURL = upstreamBaseURL - conf.UpstreamAccessToken = upstreamAccessToken conf.SMTPSenderAddr = smtpSenderAddr conf.SMTPSenderUser = smtpSenderUser conf.SMTPSenderPass = smtpSenderPass @@ -321,10 +296,6 @@ func execServe(c *cli.Context) error { conf.SMTPServerListen = smtpServerListen conf.SMTPServerDomain = smtpServerDomain conf.SMTPServerAddrPrefix = smtpServerAddrPrefix - conf.TwilioAccount = twilioAccount - conf.TwilioAuthToken = twilioAuthToken - conf.TwilioPhoneNumber = twilioPhoneNumber - conf.TwilioVerifyService = twilioVerifyService conf.TotalTopicLimit = totalTopicLimit conf.VisitorSubscriptionLimit = visitorSubscriptionLimit conf.VisitorAttachmentTotalSizeLimit = visitorAttachmentTotalSizeLimit @@ -340,12 +311,10 @@ func execServe(c *cli.Context) error { conf.StripeSecretKey = stripeSecretKey conf.StripeWebhookKey = stripeWebhookKey conf.BillingContact = billingContact + conf.EnableWeb = enableWeb conf.EnableSignup = enableSignup conf.EnableLogin = enableLogin conf.EnableReservations = enableReservations - conf.EnableMetrics = enableMetrics - conf.MetricsListenHTTP = metricsListenHTTP - conf.ProfileListenHTTP = profileListenHTTP conf.Version = c.App.Version // Set up hot-reloading of config diff --git a/cmd/subscribe.go b/cmd/subscribe.go index c85c468..bbc6fb3 100644 --- a/cmd/subscribe.go +++ b/cmd/subscribe.go @@ -30,7 +30,6 @@ var flagsSubscribe = append( &cli.StringFlag{Name: "config", Aliases: []string{"c"}, Usage: "client config file"}, &cli.StringFlag{Name: "since", Aliases: []string{"s"}, Usage: "return events since `SINCE` (Unix timestamp, or all)"}, &cli.StringFlag{Name: "user", Aliases: []string{"u"}, EnvVars: []string{"NTFY_USER"}, Usage: "username[:password] used to auth against the server"}, - &cli.StringFlag{Name: "token", Aliases: []string{"k"}, EnvVars: []string{"NTFY_TOKEN"}, Usage: "access token used to auth against the server"}, &cli.BoolFlag{Name: "from-config", Aliases: []string{"from_config", "C"}, Usage: "read subscriptions from config file (service mode)"}, &cli.BoolFlag{Name: "poll", Aliases: []string{"p"}, Usage: "return events and exit, do not listen for new events"}, &cli.BoolFlag{Name: "scheduled", Aliases: []string{"sched", "S"}, Usage: "also return scheduled/delayed events"}, @@ -72,7 +71,7 @@ ntfy subscribe TOPIC COMMAND $NTFY_TITLE $title, $t Message title $NTFY_PRIORITY $priority, $prio, $p Message priority (1=min, 5=max) $NTFY_TAGS $tags, $tag, $ta Message tags (comma separated list) - $NTFY_RAW $raw Raw JSON message + $NTFY_RAW $raw Raw JSON message Examples: ntfy sub mytopic 'notify-send "$m"' # Execute command for incoming messages @@ -98,18 +97,11 @@ func execSubscribe(c *cli.Context) error { cl := client.New(conf) since := c.String("since") user := c.String("user") - token := c.String("token") poll := c.Bool("poll") scheduled := c.Bool("scheduled") fromConfig := c.Bool("from-config") topic := c.Args().Get(0) command := c.Args().Get(1) - - // Checks - if user != "" && token != "" { - return errors.New("cannot set both --user and --token") - } - if !fromConfig { conf.Subscribe = nil // wipe if --from-config not passed } @@ -117,9 +109,7 @@ func execSubscribe(c *cli.Context) error { if since != "" { options = append(options, client.WithSince(since)) } - if token != "" { - options = append(options, client.WithBearerAuth(token)) - } else if user != "" { + if user != "" { var pass string parts := strings.SplitN(user, ":", 2) if len(parts) == 2 { @@ -135,10 +125,9 @@ func execSubscribe(c *cli.Context) error { fmt.Fprintf(c.App.ErrWriter, "\r%s\r", strings.Repeat(" ", 20)) } options = append(options, client.WithBasicAuth(user, pass)) - } else if conf.DefaultToken != "" { - options = append(options, client.WithBearerAuth(conf.DefaultToken)) - } else if conf.DefaultUser != "" && conf.DefaultPassword != nil { - options = append(options, client.WithBasicAuth(conf.DefaultUser, *conf.DefaultPassword)) + } + if poll { + options = append(options, client.WithPoll()) } if scheduled { options = append(options, client.WithScheduled()) @@ -156,9 +145,6 @@ func execSubscribe(c *cli.Context) error { func doPoll(c *cli.Context, cl *client.Client, conf *client.Config, topic, command string, options ...client.SubscribeOption) error { for _, s := range conf.Subscribe { // may be nil - if auth := maybeAddAuthHeader(s, conf); auth != nil { - options = append(options, auth) - } if err := doPollSingle(c, cl, s.Topic, s.Command, options...); err != nil { return err } @@ -189,15 +175,22 @@ func doSubscribe(c *cli.Context, cl *client.Client, conf *client.Config, topic, for filter, value := range s.If { topicOptions = append(topicOptions, client.WithFilter(filter, value)) } - - if auth := maybeAddAuthHeader(s, conf); auth != nil { - topicOptions = append(topicOptions, auth) + var user string + var password *string + if s.User != "" { + user = s.User + } else if conf.DefaultUser != "" { + user = conf.DefaultUser } - - subscriptionID, err := cl.Subscribe(s.Topic, topicOptions...) - if err != nil { - return err + if s.Password != nil { + password = s.Password + } else if conf.DefaultPassword != nil { + password = conf.DefaultPassword } + if user != "" && password != nil { + topicOptions = append(topicOptions, client.WithBasicAuth(user, *password)) + } + subscriptionID := cl.Subscribe(s.Topic, topicOptions...) if s.Command != "" { cmds[subscriptionID] = s.Command } else if conf.DefaultCommand != "" { @@ -207,10 +200,7 @@ func doSubscribe(c *cli.Context, cl *client.Client, conf *client.Config, topic, } } if topic != "" { - subscriptionID, err := cl.Subscribe(topic, options...) - if err != nil { - return err - } + subscriptionID := cl.Subscribe(topic, options...) cmds[subscriptionID] = command } for m := range cl.Messages { @@ -224,25 +214,6 @@ func doSubscribe(c *cli.Context, cl *client.Client, conf *client.Config, topic, return nil } -func maybeAddAuthHeader(s client.Subscribe, conf *client.Config) client.SubscribeOption { - // check for subscription token then subscription user:pass - if s.Token != "" { - return client.WithBearerAuth(s.Token) - } - if s.User != "" && s.Password != nil { - return client.WithBasicAuth(s.User, *s.Password) - } - - // if no subscription token nor subscription user:pass, check for default token then default user:pass - if conf.DefaultToken != "" { - return client.WithBearerAuth(conf.DefaultToken) - } - if conf.DefaultUser != "" && conf.DefaultPassword != nil { - return client.WithBasicAuth(conf.DefaultUser, *conf.DefaultPassword) - } - return nil -} - func printMessageOrRunCommand(c *cli.Context, m *client.Message, command string) { if command != "" { runCommand(c, command, m) diff --git a/cmd/subscribe_test.go b/cmd/subscribe_test.go deleted file mode 100644 index 0b3a0a4..0000000 --- a/cmd/subscribe_test.go +++ /dev/null @@ -1,361 +0,0 @@ -package cmd - -import ( - "fmt" - "github.com/stretchr/testify/require" - "net/http" - "net/http/httptest" - "os" - "path/filepath" - "strings" - "testing" -) - -func TestCLI_Subscribe_Default_UserPass_Subscription_Token(t *testing.T) { - message := `{"id":"RXIQBFaieLVr","time":124,"expires":1124,"event":"message","topic":"mytopic","message":"triggered"}` - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, "/mytopic/json", r.URL.Path) - require.Equal(t, "Bearer tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2", r.Header.Get("Authorization")) - - w.WriteHeader(http.StatusOK) - w.Write([]byte(message)) - })) - defer server.Close() - - filename := filepath.Join(t.TempDir(), "client.yml") - require.Nil(t, os.WriteFile(filename, []byte(fmt.Sprintf(` -default-host: %s -default-user: philipp -default-password: mypass -subscribe: - - topic: mytopic - token: tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2 -`, server.URL)), 0600)) - - app, _, stdout, _ := newTestApp() - - require.Nil(t, app.Run([]string{"ntfy", "subscribe", "--poll", "--from-config", "--config=" + filename})) - - require.Equal(t, message, strings.TrimSpace(stdout.String())) -} - -func TestCLI_Subscribe_Default_Token_Subscription_UserPass(t *testing.T) { - message := `{"id":"RXIQBFaieLVr","time":124,"expires":1124,"event":"message","topic":"mytopic","message":"triggered"}` - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, "/mytopic/json", r.URL.Path) - require.Equal(t, "Basic cGhpbGlwcDpteXBhc3M=", r.Header.Get("Authorization")) - - w.WriteHeader(http.StatusOK) - w.Write([]byte(message)) - })) - defer server.Close() - - filename := filepath.Join(t.TempDir(), "client.yml") - require.Nil(t, os.WriteFile(filename, []byte(fmt.Sprintf(` -default-host: %s -default-token: tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2 -subscribe: - - topic: mytopic - user: philipp - password: mypass -`, server.URL)), 0600)) - - app, _, stdout, _ := newTestApp() - - require.Nil(t, app.Run([]string{"ntfy", "subscribe", "--poll", "--from-config", "--config=" + filename})) - - require.Equal(t, message, strings.TrimSpace(stdout.String())) -} - -func TestCLI_Subscribe_Default_Token_Subscription_Token(t *testing.T) { - message := `{"id":"RXIQBFaieLVr","time":124,"expires":1124,"event":"message","topic":"mytopic","message":"triggered"}` - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, "/mytopic/json", r.URL.Path) - require.Equal(t, "Bearer tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2", r.Header.Get("Authorization")) - - w.WriteHeader(http.StatusOK) - w.Write([]byte(message)) - })) - defer server.Close() - - filename := filepath.Join(t.TempDir(), "client.yml") - require.Nil(t, os.WriteFile(filename, []byte(fmt.Sprintf(` -default-host: %s -default-token: tk_FAKETOKEN01234567890FAKETOKEN -subscribe: - - topic: mytopic - token: tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2 -`, server.URL)), 0600)) - - app, _, stdout, _ := newTestApp() - - require.Nil(t, app.Run([]string{"ntfy", "subscribe", "--poll", "--from-config", "--config=" + filename})) - - require.Equal(t, message, strings.TrimSpace(stdout.String())) -} - -func TestCLI_Subscribe_Default_UserPass_Subscription_UserPass(t *testing.T) { - message := `{"id":"RXIQBFaieLVr","time":124,"expires":1124,"event":"message","topic":"mytopic","message":"triggered"}` - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, "/mytopic/json", r.URL.Path) - require.Equal(t, "Basic cGhpbGlwcDpteXBhc3M=", r.Header.Get("Authorization")) - - w.WriteHeader(http.StatusOK) - w.Write([]byte(message)) - })) - defer server.Close() - - filename := filepath.Join(t.TempDir(), "client.yml") - require.Nil(t, os.WriteFile(filename, []byte(fmt.Sprintf(` -default-host: %s -default-user: fake -default-password: password -subscribe: - - topic: mytopic - user: philipp - password: mypass -`, server.URL)), 0600)) - - app, _, stdout, _ := newTestApp() - - require.Nil(t, app.Run([]string{"ntfy", "subscribe", "--poll", "--from-config", "--config=" + filename})) - - require.Equal(t, message, strings.TrimSpace(stdout.String())) -} - -func TestCLI_Subscribe_Default_Token_Subscription_Empty(t *testing.T) { - message := `{"id":"RXIQBFaieLVr","time":124,"expires":1124,"event":"message","topic":"mytopic","message":"triggered"}` - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, "/mytopic/json", r.URL.Path) - require.Equal(t, "Bearer tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2", r.Header.Get("Authorization")) - - w.WriteHeader(http.StatusOK) - w.Write([]byte(message)) - })) - defer server.Close() - - filename := filepath.Join(t.TempDir(), "client.yml") - require.Nil(t, os.WriteFile(filename, []byte(fmt.Sprintf(` -default-host: %s -default-token: tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2 -subscribe: - - topic: mytopic -`, server.URL)), 0600)) - - app, _, stdout, _ := newTestApp() - - require.Nil(t, app.Run([]string{"ntfy", "subscribe", "--poll", "--from-config", "--config=" + filename})) - - require.Equal(t, message, strings.TrimSpace(stdout.String())) -} - -func TestCLI_Subscribe_Default_UserPass_Subscription_Empty(t *testing.T) { - message := `{"id":"RXIQBFaieLVr","time":124,"expires":1124,"event":"message","topic":"mytopic","message":"triggered"}` - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, "/mytopic/json", r.URL.Path) - require.Equal(t, "Basic cGhpbGlwcDpteXBhc3M=", r.Header.Get("Authorization")) - - w.WriteHeader(http.StatusOK) - w.Write([]byte(message)) - })) - defer server.Close() - - filename := filepath.Join(t.TempDir(), "client.yml") - require.Nil(t, os.WriteFile(filename, []byte(fmt.Sprintf(` -default-host: %s -default-user: philipp -default-password: mypass -subscribe: - - topic: mytopic -`, server.URL)), 0600)) - - app, _, stdout, _ := newTestApp() - - require.Nil(t, app.Run([]string{"ntfy", "subscribe", "--poll", "--from-config", "--config=" + filename})) - - require.Equal(t, message, strings.TrimSpace(stdout.String())) -} - -func TestCLI_Subscribe_Default_Empty_Subscription_Token(t *testing.T) { - message := `{"id":"RXIQBFaieLVr","time":124,"expires":1124,"event":"message","topic":"mytopic","message":"triggered"}` - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, "/mytopic/json", r.URL.Path) - require.Equal(t, "Bearer tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2", r.Header.Get("Authorization")) - - w.WriteHeader(http.StatusOK) - w.Write([]byte(message)) - })) - defer server.Close() - - filename := filepath.Join(t.TempDir(), "client.yml") - require.Nil(t, os.WriteFile(filename, []byte(fmt.Sprintf(` -default-host: %s -subscribe: - - topic: mytopic - token: tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2 -`, server.URL)), 0600)) - - app, _, stdout, _ := newTestApp() - - require.Nil(t, app.Run([]string{"ntfy", "subscribe", "--poll", "--from-config", "--config=" + filename})) - - require.Equal(t, message, strings.TrimSpace(stdout.String())) -} - -func TestCLI_Subscribe_Default_Empty_Subscription_UserPass(t *testing.T) { - message := `{"id":"RXIQBFaieLVr","time":124,"expires":1124,"event":"message","topic":"mytopic","message":"triggered"}` - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, "/mytopic/json", r.URL.Path) - require.Equal(t, "Basic cGhpbGlwcDpteXBhc3M=", r.Header.Get("Authorization")) - - w.WriteHeader(http.StatusOK) - w.Write([]byte(message)) - })) - defer server.Close() - - filename := filepath.Join(t.TempDir(), "client.yml") - require.Nil(t, os.WriteFile(filename, []byte(fmt.Sprintf(` -default-host: %s -subscribe: - - topic: mytopic - user: philipp - password: mypass -`, server.URL)), 0600)) - - app, _, stdout, _ := newTestApp() - - require.Nil(t, app.Run([]string{"ntfy", "subscribe", "--poll", "--from-config", "--config=" + filename})) - - require.Equal(t, message, strings.TrimSpace(stdout.String())) -} - -func TestCLI_Subscribe_Default_Token_CLI_Token(t *testing.T) { - message := `{"id":"RXIQBFaieLVr","time":124,"expires":1124,"event":"message","topic":"mytopic","message":"triggered"}` - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, "/mytopic/json", r.URL.Path) - require.Equal(t, "Bearer tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2", r.Header.Get("Authorization")) - - w.WriteHeader(http.StatusOK) - w.Write([]byte(message)) - })) - defer server.Close() - - filename := filepath.Join(t.TempDir(), "client.yml") - require.Nil(t, os.WriteFile(filename, []byte(fmt.Sprintf(` -default-host: %s -default-token: tk_FAKETOKEN0123456789FAKETOKEN -`, server.URL)), 0600)) - - app, _, stdout, _ := newTestApp() - - require.Nil(t, app.Run([]string{"ntfy", "subscribe", "--poll", "--from-config", "--config=" + filename, "--token", "tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2", "mytopic"})) - - require.Equal(t, message, strings.TrimSpace(stdout.String())) -} - -func TestCLI_Subscribe_Default_Token_CLI_UserPass(t *testing.T) { - message := `{"id":"RXIQBFaieLVr","time":124,"expires":1124,"event":"message","topic":"mytopic","message":"triggered"}` - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, "/mytopic/json", r.URL.Path) - require.Equal(t, "Basic cGhpbGlwcDpteXBhc3M=", r.Header.Get("Authorization")) - - w.WriteHeader(http.StatusOK) - w.Write([]byte(message)) - })) - defer server.Close() - - filename := filepath.Join(t.TempDir(), "client.yml") - require.Nil(t, os.WriteFile(filename, []byte(fmt.Sprintf(` -default-host: %s -default-token: tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2 -`, server.URL)), 0600)) - - app, _, stdout, _ := newTestApp() - - require.Nil(t, app.Run([]string{"ntfy", "subscribe", "--poll", "--from-config", "--config=" + filename, "--user", "philipp:mypass", "mytopic"})) - - require.Equal(t, message, strings.TrimSpace(stdout.String())) -} - -func TestCLI_Subscribe_Default_Token_Subscription_Token_CLI_UserPass(t *testing.T) { - message := `{"id":"RXIQBFaieLVr","time":124,"expires":1124,"event":"message","topic":"mytopic","message":"triggered"}` - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, "/mytopic/json", r.URL.Path) - require.Equal(t, "Bearer tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2", r.Header.Get("Authorization")) - - w.WriteHeader(http.StatusOK) - w.Write([]byte(message)) - })) - defer server.Close() - - filename := filepath.Join(t.TempDir(), "client.yml") - require.Nil(t, os.WriteFile(filename, []byte(fmt.Sprintf(` -default-host: %s -default-token: tk_FAKETOKEN01234567890FAKETOKEN -subscribe: - - topic: mytopic - token: tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2 -`, server.URL)), 0600)) - - app, _, stdout, _ := newTestApp() - - require.Nil(t, app.Run([]string{"ntfy", "subscribe", "--poll", "--from-config", "--config=" + filename, "--user", "philipp:mypass"})) - - require.Equal(t, message, strings.TrimSpace(stdout.String())) -} - -func TestCLI_Subscribe_Token_And_UserPass(t *testing.T) { - app, _, _, _ := newTestApp() - err := app.Run([]string{"ntfy", "subscribe", "--poll", "--token", "tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2", "--user", "philipp:mypass", "mytopic", "triggered"}) - require.Error(t, err) - require.Equal(t, "cannot set both --user and --token", err.Error()) -} - -func TestCLI_Subscribe_Default_Token(t *testing.T) { - message := `{"id":"RXIQBFaieLVr","time":124,"expires":1124,"event":"message","topic":"mytopic","message":"triggered"}` - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, "/mytopic/json", r.URL.Path) - require.Equal(t, "Bearer tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2", r.Header.Get("Authorization")) - - w.WriteHeader(http.StatusOK) - w.Write([]byte(message)) - })) - defer server.Close() - - filename := filepath.Join(t.TempDir(), "client.yml") - require.Nil(t, os.WriteFile(filename, []byte(fmt.Sprintf(` -default-host: %s -default-token: tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2 -`, server.URL)), 0600)) - - app, _, stdout, _ := newTestApp() - - require.Nil(t, app.Run([]string{"ntfy", "subscribe", "--poll", "--config=" + filename, "mytopic"})) - - require.Equal(t, message, strings.TrimSpace(stdout.String())) -} - -func TestCLI_Subscribe_Default_UserPass(t *testing.T) { - message := `{"id":"RXIQBFaieLVr","time":124,"expires":1124,"event":"message","topic":"mytopic","message":"triggered"}` - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, "/mytopic/json", r.URL.Path) - require.Equal(t, "Basic cGhpbGlwcDpteXBhc3M=", r.Header.Get("Authorization")) - - w.WriteHeader(http.StatusOK) - w.Write([]byte(message)) - })) - defer server.Close() - - filename := filepath.Join(t.TempDir(), "client.yml") - require.Nil(t, os.WriteFile(filename, []byte(fmt.Sprintf(` -default-host: %s -default-user: philipp -default-password: mypass -`, server.URL)), 0600)) - - app, _, stdout, _ := newTestApp() - - require.Nil(t, app.Run([]string{"ntfy", "subscribe", "--poll", "--config=" + filename, "mytopic"})) - - require.Equal(t, message, strings.TrimSpace(stdout.String())) -} diff --git a/cmd/tier.go b/cmd/tier.go index f1c8ddc..c0b83d7 100644 --- a/cmd/tier.go +++ b/cmd/tier.go @@ -18,7 +18,6 @@ const ( defaultMessageLimit = 5000 defaultMessageExpiryDuration = "12h" defaultEmailLimit = 20 - defaultCallLimit = 0 defaultReservationLimit = 3 defaultAttachmentFileSizeLimit = "15M" defaultAttachmentTotalSizeLimit = "100M" @@ -49,7 +48,6 @@ var cmdTier = &cli.Command{ &cli.Int64Flag{Name: "message-limit", Value: defaultMessageLimit, Usage: "daily message limit"}, &cli.StringFlag{Name: "message-expiry-duration", Value: defaultMessageExpiryDuration, Usage: "duration after which messages are deleted"}, &cli.Int64Flag{Name: "email-limit", Value: defaultEmailLimit, Usage: "daily email limit"}, - &cli.Int64Flag{Name: "call-limit", Value: defaultCallLimit, Usage: "daily phone call limit"}, &cli.Int64Flag{Name: "reservation-limit", Value: defaultReservationLimit, Usage: "topic reservation limit"}, &cli.StringFlag{Name: "attachment-file-size-limit", Value: defaultAttachmentFileSizeLimit, Usage: "per-attachment file size limit"}, &cli.StringFlag{Name: "attachment-total-size-limit", Value: defaultAttachmentTotalSizeLimit, Usage: "total size limit of attachments for the user"}, @@ -93,7 +91,6 @@ Examples: &cli.Int64Flag{Name: "message-limit", Usage: "daily message limit"}, &cli.StringFlag{Name: "message-expiry-duration", Usage: "duration after which messages are deleted"}, &cli.Int64Flag{Name: "email-limit", Usage: "daily email limit"}, - &cli.Int64Flag{Name: "call-limit", Usage: "daily phone call limit"}, &cli.Int64Flag{Name: "reservation-limit", Usage: "topic reservation limit"}, &cli.StringFlag{Name: "attachment-file-size-limit", Usage: "per-attachment file size limit"}, &cli.StringFlag{Name: "attachment-total-size-limit", Usage: "total size limit of attachments for the user"}, @@ -218,7 +215,6 @@ func execTierAdd(c *cli.Context) error { MessageLimit: c.Int64("message-limit"), MessageExpiryDuration: messageExpiryDuration, EmailLimit: c.Int64("email-limit"), - CallLimit: c.Int64("call-limit"), ReservationLimit: c.Int64("reservation-limit"), AttachmentFileSizeLimit: attachmentFileSizeLimit, AttachmentTotalSizeLimit: attachmentTotalSizeLimit, @@ -271,9 +267,6 @@ func execTierChange(c *cli.Context) error { if c.IsSet("email-limit") { tier.EmailLimit = c.Int64("email-limit") } - if c.IsSet("call-limit") { - tier.CallLimit = c.Int64("call-limit") - } if c.IsSet("reservation-limit") { tier.ReservationLimit = c.Int64("reservation-limit") } @@ -364,7 +357,6 @@ func printTier(c *cli.Context, tier *user.Tier) { fmt.Fprintf(c.App.ErrWriter, "- Message limit: %d\n", tier.MessageLimit) fmt.Fprintf(c.App.ErrWriter, "- Message expiry duration: %s (%d seconds)\n", tier.MessageExpiryDuration.String(), int64(tier.MessageExpiryDuration.Seconds())) fmt.Fprintf(c.App.ErrWriter, "- Email limit: %d\n", tier.EmailLimit) - fmt.Fprintf(c.App.ErrWriter, "- Phone call limit: %d\n", tier.CallLimit) fmt.Fprintf(c.App.ErrWriter, "- Reservation limit: %d\n", tier.ReservationLimit) fmt.Fprintf(c.App.ErrWriter, "- Attachment file size limit: %s\n", util.FormatSize(tier.AttachmentFileSizeLimit)) fmt.Fprintf(c.App.ErrWriter, "- Attachment total size limit: %s\n", util.FormatSize(tier.AttachmentTotalSizeLimit)) diff --git a/docs/_overrides/main.html b/docs/_overrides/main.html index 52483eb..53a26fc 100644 --- a/docs/_overrides/main.html +++ b/docs/_overrides/main.html @@ -32,11 +32,11 @@ -If you like ntfy, please consider sponsoring me via GitHub Sponsors +If you like ntfy, please consider sponsoring it via GitHub Sponsors or Liberapay -, or subscribing to ntfy Pro. + - - - diff --git a/web/package-lock.json b/web/package-lock.json index b5754d9..28eb44d 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -8,8 +8,8 @@ "name": "ntfy", "version": "1.0.0", "dependencies": { - "@emotion/react": "^11.11.0", - "@emotion/styled": "^11.11.0", + "@emotion/react": "^11.8.2", + "@emotion/styled": "^11.8.1", "@mui/icons-material": "^5.4.2", "@mui/material": "latest", "dexie": "^3.2.1", @@ -24,29 +24,17 @@ "react-i18next": "^11.16.2", "react-infinite-scroll-component": "^6.1.0", "react-router-dom": "^6.2.2", + "react-scripts": "^5.0.0", "stacktrace-gps": "^3.0.4", "stacktrace-js": "^2.0.2" - }, - "devDependencies": { - "@vitejs/plugin-react": "^4.0.0", - "eslint": "^8.41.0", - "eslint-config-airbnb": "^19.0.4", - "eslint-config-prettier": "^8.8.0", - "eslint-plugin-import": "^2.27.5", - "eslint-plugin-jsx-a11y": "^6.7.1", - "eslint-plugin-react": "^7.32.2", - "eslint-plugin-react-hooks": "^4.6.0", - "prettier": "^2.8.8", - "vite": "^4.3.8" } }, "node_modules/@ampproject/remapping": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", - "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", - "dev": true, + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/gen-mapping": "^0.1.0", "@jridgewell/trace-mapping": "^0.3.9" }, "engines": { @@ -54,9 +42,9 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", - "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", "dependencies": { "@babel/highlight": "^7.18.6" }, @@ -65,30 +53,28 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.21.9", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.9.tgz", - "integrity": "sha512-FUGed8kfhyWvbYug/Un/VPJD41rDIgoVVcR+FuzhzOYyRz5uED+Gd3SLZml0Uw2l2aHFb7ZgdW5mGA3G2cCCnQ==", - "dev": true, + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.0.tgz", + "integrity": "sha512-gMuZsmsgxk/ENC3O/fRw5QY8A9/uxQbbCEypnLIiYYc/qVJtEV7ouxC3EllIIwNzMqAQee5tanFabWsUOutS7g==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.21.8", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.8.tgz", - "integrity": "sha512-YeM22Sondbo523Sz0+CirSPnbj9bG3P0CdHcBZdqUuaeOaYEFbOLoGU7lebvGP6P5J/WE9wOn7u7C4J9HvS1xQ==", - "dev": true, + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.0.tgz", + "integrity": "sha512-PuxUbxcW6ZYe656yL3EAhpy7qXKq0DmYsrJLpbB8XrsCP9Nm+XCg9XFMb5vIDliPD7+U/+M+QJlH17XOcB7eXA==", "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.21.4", - "@babel/generator": "^7.21.5", - "@babel/helper-compilation-targets": "^7.21.5", - "@babel/helper-module-transforms": "^7.21.5", - "@babel/helpers": "^7.21.5", - "@babel/parser": "^7.21.8", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.21.0", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-module-transforms": "^7.21.0", + "@babel/helpers": "^7.21.0", + "@babel/parser": "^7.21.0", "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.5", - "@babel/types": "^7.21.5", + "@babel/traverse": "^7.21.0", + "@babel/types": "^7.21.0", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -103,13 +89,53 @@ "url": "https://opencollective.com/babel" } }, - "node_modules/@babel/generator": { - "version": "7.21.9", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.9.tgz", - "integrity": "sha512-F3fZga2uv09wFdEjEQIJxXALXfz0+JaOb7SabvVMmjHxeVTuGW8wgE8Vp1Hd7O+zMTYtcfEISGRzPkeiaPPsvg==", - "dev": true, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/eslint-parser": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.19.1.tgz", + "integrity": "sha512-AqNf2QWt1rtu2/1rLswy6CDP7H9Oh3mMhk177Y67Rg8d7RD9WfOLLv8CGn6tisFvS2htm86yIe1yLF6I1UDaGQ==", "dependencies": { - "@babel/types": "^7.21.5", + "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", + "eslint-visitor-keys": "^2.1.0", + "semver": "^6.3.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || >=14.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.11.0", + "eslint": "^7.5.0 || ^8.0.0" + } + }, + "node_modules/@babel/eslint-parser/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "engines": { + "node": ">=10" + } + }, + "node_modules/@babel/eslint-parser/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.21.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.1.tgz", + "integrity": "sha512-1lT45bAYlQhFn/BHivJs43AiW2rg3/UbLyShGfF3C0KmHvO5fSghWd5kBJy30kpRRucGzXStvnnCFniCR2kXAA==", + "dependencies": { + "@babel/types": "^7.21.0", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -118,14 +144,49 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.21.5.tgz", - "integrity": "sha512-1RkbFGUKex4lvsB9yhIfWltJM5cZKUftB2eNajaDv3dCMEp49iBG0K14uH8NnX9IPux2+mK7JGEOB0jn48/J6w==", - "dev": true, + "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", "dependencies": { - "@babel/compat-data": "^7.21.5", - "@babel/helper-validator-option": "^7.21.0", + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", + "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz", + "integrity": "sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==", + "dependencies": { + "@babel/helper-explode-assignable-expression": "^7.18.6", + "@babel/types": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz", + "integrity": "sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==", + "dependencies": { + "@babel/compat-data": "^7.20.5", + "@babel/helper-validator-option": "^7.18.6", "browserslist": "^4.21.3", "lru-cache": "^5.1.1", "semver": "^6.3.0" @@ -137,11 +198,89 @@ "@babel/core": "^7.0.0" } }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.21.0.tgz", + "integrity": "sha512-Q8wNiMIdwsv5la5SPxNYzzkPnjgC0Sy0i7jLkVOCdllu/xcVNkr3TeZzbHBJrj+XXRqzX5uCyCoV9eu6xUG7KQ==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.21.0", + "@babel/helper-member-expression-to-functions": "^7.21.0", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-replace-supers": "^7.20.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", + "@babel/helper-split-export-declaration": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.21.0.tgz", + "integrity": "sha512-N+LaFW/auRSWdx7SHD/HiARwXQju1vXTW4fKr4u5SgBUTm51OKEjKgj+cs00ggW3kEvNqwErnlwuq7Y3xBe4eg==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "regexpu-core": "^5.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz", + "integrity": "sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==", + "dependencies": { + "@babel/helper-compilation-targets": "^7.17.7", + "@babel/helper-plugin-utils": "^7.16.7", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2", + "semver": "^6.1.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0-0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.21.5.tgz", - "integrity": "sha512-IYl4gZ3ETsWocUWgsFZLM5i1BYx9SoemminVEXadgLBa9TdeorzgLKm8wWLA6J1N/kT3Kch8XIk1laNzYoHKvQ==", - "dev": true, + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", + "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-explode-assignable-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz", + "integrity": "sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==", + "dependencies": { + "@babel/types": "^7.18.6" + }, "engines": { "node": ">=6.9.0" } @@ -150,7 +289,6 @@ "version": "7.21.0", "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", - "dev": true, "dependencies": { "@babel/template": "^7.20.7", "@babel/types": "^7.21.0" @@ -163,7 +301,6 @@ "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", - "dev": true, "dependencies": { "@babel/types": "^7.18.6" }, @@ -171,52 +308,115 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-module-imports": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.21.4.tgz", - "integrity": "sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg==", + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.21.0.tgz", + "integrity": "sha512-Muu8cdZwNN6mRRNG6lAYErJ5X3bRevgYR2O8wN0yn7jJSnGDu6eG59RfT29JHxGUovyfrh6Pj0XzmR7drNVL3Q==", "dependencies": { - "@babel/types": "^7.21.4" + "@babel/types": "^7.21.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "dependencies": { + "@babel/types": "^7.18.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.21.5.tgz", - "integrity": "sha512-bI2Z9zBGY2q5yMHoBvJ2a9iX3ZOAzJPm7Q8Yz6YeoUjU/Cvhmi2G4QyTNyPBqqXSgTjUxRg3L0xV45HvkNWWBw==", - "dev": true, + "version": "7.21.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.21.2.tgz", + "integrity": "sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ==", "dependencies": { - "@babel/helper-environment-visitor": "^7.21.5", - "@babel/helper-module-imports": "^7.21.4", - "@babel/helper-simple-access": "^7.21.5", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-simple-access": "^7.20.2", "@babel/helper-split-export-declaration": "^7.18.6", "@babel/helper-validator-identifier": "^7.19.1", "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.5", - "@babel/types": "^7.21.5" + "@babel/traverse": "^7.21.2", + "@babel/types": "^7.21.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", + "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", + "dependencies": { + "@babel/types": "^7.18.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.21.5.tgz", - "integrity": "sha512-0WDaIlXKOX/3KfBK/dwP1oQGiPh6rjMkT7HIRv7i5RR2VUMwrx5ZL0dwBkKx7+SW1zwNdgjHd34IMk5ZjTeHVg==", - "dev": true, + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz", + "integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz", + "integrity": "sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-wrap-function": "^7.18.9", + "@babel/types": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.20.7.tgz", + "integrity": "sha512-vujDMtB6LVfNW13jhlCrp48QNslK6JXi7lQG736HVbHz/mbf4Dc7tIRh1Xf5C0rF7BP8iiSxGMCmY6Ci1ven3A==", + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-member-expression-to-functions": "^7.20.7", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.20.7", + "@babel/types": "^7.20.7" + }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-simple-access": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.21.5.tgz", - "integrity": "sha512-ENPDAMC1wAjR0uaCUwliBdiSl1KBJAVnMTzXqi64c2MG8MPR6ii4qf7bSXDqSFbr4W6W028/rf5ivoHop5/mkg==", - "dev": true, + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz", + "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==", "dependencies": { - "@babel/types": "^7.21.5" + "@babel/types": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz", + "integrity": "sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==", + "dependencies": { + "@babel/types": "^7.20.0" }, "engines": { "node": ">=6.9.0" @@ -226,7 +426,6 @@ "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", - "dev": true, "dependencies": { "@babel/types": "^7.18.6" }, @@ -235,9 +434,9 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.21.5.tgz", - "integrity": "sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w==", + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", + "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", "engines": { "node": ">=6.9.0" } @@ -254,20 +453,32 @@ "version": "7.21.0", "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz", "integrity": "sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==", - "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.20.5.tgz", + "integrity": "sha512-bYMxIWK5mh+TgXGVqAtnu5Yn1un+v8DDZtqyzKRLUzrh70Eal2O3aZ7aPYiMADO4uKlkzOiRiZ6GX5q3qxvW9Q==", + "dependencies": { + "@babel/helper-function-name": "^7.19.0", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.20.5", + "@babel/types": "^7.20.5" + }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.21.5.tgz", - "integrity": "sha512-BSY+JSlHxOmGsPTydUkPf1MdMQ3M81x5xGCOVgWM3G8XH77sJ292Y2oqcp0CbbgxhqBuI46iUz1tT7hqP7EfgA==", - "dev": true, + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.21.0.tgz", + "integrity": "sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA==", "dependencies": { "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.5", - "@babel/types": "^7.21.5" + "@babel/traverse": "^7.21.0", + "@babel/types": "^7.21.0" }, "engines": { "node": ">=6.9.0" @@ -287,10 +498,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.21.9", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.9.tgz", - "integrity": "sha512-q5PNg/Bi1OpGgx5jYlvWZwAorZepEudDMCLtj967aeS7WMont7dUZI46M2XwcIQqvUlMxWfdLFu4S/qSxeUu5g==", - "dev": true, + "version": "7.21.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.2.tgz", + "integrity": "sha512-URpaIJQwEkEC2T9Kn+Ai6Xe/02iNaVCuT/PtoRz3GPVJVDpPd7mLo+VddTbhCRU9TXqW5mSrQfXZyi8kDKOVpQ==", "bin": { "parser": "bin/babel-parser.js" }, @@ -298,11 +508,339 @@ "node": ">=6.0.0" } }, - "node_modules/@babel/plugin-transform-react-jsx-self": { + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz", + "integrity": "sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.20.7.tgz", + "integrity": "sha512-sbr9+wNE5aXMBBFBICk01tt7sBf2Oc9ikRFEcem/ZORup9IMUdNhW7/wVLEbbtlWOsEubJet46mHAL2C8+2jKQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", + "@babel/plugin-proposal-optional-chaining": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-proposal-async-generator-functions": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.7.tgz", + "integrity": "sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==", + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-remap-async-to-generator": "^7.18.9", + "@babel/plugin-syntax-async-generators": "^7.8.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-class-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", + "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-class-static-block": { "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.21.0.tgz", - "integrity": "sha512-f/Eq+79JEu+KUANFks9UZCcvydOOGMgF7jBrcwjHa5jTZD8JivnhCJYvmlhR/WTXBWonDExPoW0eO/CR4QJirA==", - "dev": true, + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.21.0.tgz", + "integrity": "sha512-XP5G9MWNUskFuP30IfFSEFB0Z6HzLIUcjYM4bYOPHXl7eiJ9HFv8tWj6TXTN5QODiEhDZAeI4hLok2iHFFV4hw==", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.21.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-proposal-decorators": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.21.0.tgz", + "integrity": "sha512-MfgX49uRrFUTL/HvWtmx3zmpyzMMr4MTj3d527MLlr/4RTT9G/ytFFP7qet2uM2Ve03b+BkpWUpK+lRXnQ+v9w==", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.21.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-replace-supers": "^7.20.7", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/plugin-syntax-decorators": "^7.21.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-dynamic-import": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz", + "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-export-namespace-from": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", + "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-json-strings": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz", + "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-json-strings": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-logical-assignment-operators": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.20.7.tgz", + "integrity": "sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", + "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-numeric-separator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", + "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-object-rest-spread": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz", + "integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==", + "dependencies": { + "@babel/compat-data": "^7.20.5", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-catch-binding": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", + "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-chaining": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz", + "integrity": "sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-methods": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", + "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0.tgz", + "integrity": "sha512-ha4zfehbJjc5MmXBlHec1igel5TJXXLDDRbuJ4+XT2TJcyD9/V1919BA8gMvsdHcNMBy4WBUBiRb3nw/EQUtBw==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-create-class-features-plugin": "^7.21.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-unicode-property-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", + "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-decorators": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.21.0.tgz", + "integrity": "sha512-tIoPpGBR8UuM4++ccWN3gifhVvQu7ZizuR1fklhRJrd5ewgbkUS+0KVFeWWxELtn18NTLoW32XV7zyOgIAiz+w==", "dependencies": { "@babel/helper-plugin-utils": "^7.20.2" }, @@ -313,11 +851,46 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.19.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.19.6.tgz", - "integrity": "sha512-RpAi004QyMNisst/pvSanoRdJ4q+jMCWyk9zdw/CyLB9j8RXEahodR6l2GyttDRyEVWZtbN+TpLiHJ3t34LbsQ==", - "dev": true, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-flow": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.18.6.tgz", + "integrity": "sha512-LUbR+KNTBWCUAqRG9ex5Gnzu2IOkt8jRJbHHXFT9q+L9zm7M/QQbEqXyw1n1pohYvOyWC8CjeyjrSaIwiYjK7A==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.20.0.tgz", + "integrity": "sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ==", "dependencies": { "@babel/helper-plugin-utils": "^7.19.0" }, @@ -328,10 +901,913 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz", + "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.20.0.tgz", + "integrity": "sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.20.7.tgz", + "integrity": "sha512-3poA5E7dzDomxj9WXWwuD6A5F3kc7VXwIJO+E+J8qtDtS+pXPAhrgEyh+9GBwBgPq1Z+bB+/JD60lp5jsN7JPQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.20.7.tgz", + "integrity": "sha512-Uo5gwHPT9vgnSXQxqGtpdufUiWp96gk7yiP4Mp5bm1QMkEmLXBO7PAGYbKoJ6DhAwiNkcHFBol/x5zZZkL/t0Q==", + "dependencies": { + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-remap-async-to-generator": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz", + "integrity": "sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.21.0.tgz", + "integrity": "sha512-Mdrbunoh9SxwFZapeHVrwFmri16+oYotcZysSzhNIVDwIAb1UV+kvnxULSYq9J3/q5MDG+4X6w8QVgD1zhBXNQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.21.0.tgz", + "integrity": "sha512-RZhbYTCEUAe6ntPehC4hlslPWosNHDox+vAs4On/mCLRLfoDVHf6hVEd7kuxr1RnHwJmxFfUM3cZiZRmPxJPXQ==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.21.0", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-replace-supers": "^7.20.7", + "@babel/helper-split-export-declaration": "^7.18.6", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.20.7.tgz", + "integrity": "sha512-Lz7MvBK6DTjElHAmfu6bfANzKcxpyNPeYBGEafyA6E5HtRpjpZwU+u7Qrgz/2OR0z+5TvKYbPdphfSaAcZBrYQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/template": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.20.7.tgz", + "integrity": "sha512-Xwg403sRrZb81IVB79ZPqNQME23yhugYVqgTxAhT99h485F4f+GMELFhhOsscDUB7HCswepKeCKLn/GZvUKoBA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz", + "integrity": "sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz", + "integrity": "sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz", + "integrity": "sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==", + "dependencies": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-flow-strip-types": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.21.0.tgz", + "integrity": "sha512-FlFA2Mj87a6sDkW4gfGrQQqwY/dLlBAyJa2dJEZ+FHXUVHBflO2wyKvg+OOEzXfrKYIa4HWl0mgmbCzt0cMb7w==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-flow": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.21.0.tgz", + "integrity": "sha512-LlUYlydgDkKpIY7mcBWvyPPmMcOphEyYA27Ef4xpbh1IiDNLr0kZsos2nf92vz3IccvJI25QUwp86Eo5s6HmBQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz", + "integrity": "sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==", + "dependencies": { + "@babel/helper-compilation-targets": "^7.18.9", + "@babel/helper-function-name": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz", + "integrity": "sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz", + "integrity": "sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.20.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.20.11.tgz", + "integrity": "sha512-NuzCt5IIYOW0O30UvqktzHYR2ud5bOWbY0yaxWZ6G+aFzOMJvrs5YHNikrbdaT15+KNO31nPOy5Fim3ku6Zb5g==", + "dependencies": { + "@babel/helper-module-transforms": "^7.20.11", + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.21.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.21.2.tgz", + "integrity": "sha512-Cln+Yy04Gxua7iPdj6nOV96smLGjpElir5YwzF0LBPKoPlLDNJePNlrGGaybAJkd0zKRnOVXOgizSqPYMNYkzA==", + "dependencies": { + "@babel/helper-module-transforms": "^7.21.2", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-simple-access": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.20.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.20.11.tgz", + "integrity": "sha512-vVu5g9BPQKSFEmvt2TA4Da5N+QVS66EX21d8uoOihC+OCpUoGvzVsXeqFdtAEfVa5BILAeFt+U7yVmLbQnAJmw==", + "dependencies": { + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-module-transforms": "^7.20.11", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-validator-identifier": "^7.19.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz", + "integrity": "sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==", + "dependencies": { + "@babel/helper-module-transforms": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.20.5.tgz", + "integrity": "sha512-mOW4tTzi5iTLnw+78iEq3gr8Aoq4WNRGpmSlrogqaiCBoR1HFhpU4JkpQFOHfeYx3ReVIFWOQJS4aZBRvuZ6mA==", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.20.5", + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz", + "integrity": "sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz", + "integrity": "sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-replace-supers": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.20.7.tgz", + "integrity": "sha512-WiWBIkeHKVOSYPO0pWkxGPfKeWrCJyD3NJ53+Lrp/QMSZbsVPovrVl2aWZ19D/LTVnaDv5Ap7GJ/B2CTOZdrfA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz", + "integrity": "sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-constant-elements": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.20.2.tgz", + "integrity": "sha512-KS/G8YI8uwMGKErLFOHS/ekhqdHhpEloxs43NecQHVgo2QuQSyJhGIY1fL8UGl9wy5ItVwwoUL4YxVqsplGq2g==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-display-name": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.18.6.tgz", + "integrity": "sha512-TV4sQ+T013n61uMoygyMRm+xf04Bd5oqFpv2jAEQwSZ8NwQA7zeRPg1LMVg2PWi3zWBz+CLKD+v5bcpZ/BS0aA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.21.0.tgz", + "integrity": "sha512-6OAWljMvQrZjR2DaNhVfRz6dkCAVV+ymcLUmaf8bccGOHn2v5rHJK3tTpij0BuhdYWP4LLaqj5lwcdlpAAPuvg==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-jsx": "^7.18.6", + "@babel/types": "^7.21.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-development": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.18.6.tgz", + "integrity": "sha512-SA6HEjwYFKF7WDjWcMcMGUimmw/nhNRDWxr+KaLSCrkD/LMDBvWRmHAYgE1HDeF8KUuI8OAu+RT6EOtKxSW2qA==", + "dependencies": { + "@babel/plugin-transform-react-jsx": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-pure-annotations": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.18.6.tgz", + "integrity": "sha512-I8VfEPg9r2TRDdvnHgPepTKvuRomzA8+u+nhY7qSI1fR2hRNebasZEETLyM5mAUr0Ku56OkXJ0I7NHJnO6cJiQ==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.20.5.tgz", + "integrity": "sha512-kW/oO7HPBtntbsahzQ0qSE3tFvkFwnbozz3NWFhLGqH75vLEg+sCGngLlhVkePlCs3Jv0dBBHDzCHxNiFAQKCQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "regenerator-transform": "^0.15.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz", + "integrity": "sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.21.0.tgz", + "integrity": "sha512-ReY6pxwSzEU0b3r2/T/VhqMKg/AkceBT19X0UptA3/tYi5Pe2eXgEUH+NNMC5nok6c6XQz5tyVTUpuezRfSMSg==", + "dependencies": { + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.20.2", + "babel-plugin-polyfill-corejs2": "^0.3.3", + "babel-plugin-polyfill-corejs3": "^0.6.0", + "babel-plugin-polyfill-regenerator": "^0.4.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz", + "integrity": "sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.20.7.tgz", + "integrity": "sha512-ewBbHQ+1U/VnH1fxltbJqDeWBU1oNLG8Dj11uIv3xVf7nrQu0bPGe5Rf716r7K5Qz+SqtAOVswoVunoiBtGhxw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz", + "integrity": "sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz", + "integrity": "sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz", + "integrity": "sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.21.0.tgz", + "integrity": "sha512-xo///XTPp3mDzTtrqXoBlK9eiAYW3wv9JXglcn/u1bi60RW11dEUxIgA8cbnDhutS1zacjMRmAwxE0gMklLnZg==", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.21.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-typescript": "^7.20.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz", + "integrity": "sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz", + "integrity": "sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.20.2.tgz", + "integrity": "sha512-1G0efQEWR1EHkKvKHqbG+IN/QdgwfByUpM5V5QroDzGV2t3S/WXNQd693cHiHTlCFMpr9B6FkPFXDA2lQcKoDg==", + "dependencies": { + "@babel/compat-data": "^7.20.1", + "@babel/helper-compilation-targets": "^7.20.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-validator-option": "^7.18.6", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.18.9", + "@babel/plugin-proposal-async-generator-functions": "^7.20.1", + "@babel/plugin-proposal-class-properties": "^7.18.6", + "@babel/plugin-proposal-class-static-block": "^7.18.6", + "@babel/plugin-proposal-dynamic-import": "^7.18.6", + "@babel/plugin-proposal-export-namespace-from": "^7.18.9", + "@babel/plugin-proposal-json-strings": "^7.18.6", + "@babel/plugin-proposal-logical-assignment-operators": "^7.18.9", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", + "@babel/plugin-proposal-numeric-separator": "^7.18.6", + "@babel/plugin-proposal-object-rest-spread": "^7.20.2", + "@babel/plugin-proposal-optional-catch-binding": "^7.18.6", + "@babel/plugin-proposal-optional-chaining": "^7.18.9", + "@babel/plugin-proposal-private-methods": "^7.18.6", + "@babel/plugin-proposal-private-property-in-object": "^7.18.6", + "@babel/plugin-proposal-unicode-property-regex": "^7.18.6", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.20.0", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-transform-arrow-functions": "^7.18.6", + "@babel/plugin-transform-async-to-generator": "^7.18.6", + "@babel/plugin-transform-block-scoped-functions": "^7.18.6", + "@babel/plugin-transform-block-scoping": "^7.20.2", + "@babel/plugin-transform-classes": "^7.20.2", + "@babel/plugin-transform-computed-properties": "^7.18.9", + "@babel/plugin-transform-destructuring": "^7.20.2", + "@babel/plugin-transform-dotall-regex": "^7.18.6", + "@babel/plugin-transform-duplicate-keys": "^7.18.9", + "@babel/plugin-transform-exponentiation-operator": "^7.18.6", + "@babel/plugin-transform-for-of": "^7.18.8", + "@babel/plugin-transform-function-name": "^7.18.9", + "@babel/plugin-transform-literals": "^7.18.9", + "@babel/plugin-transform-member-expression-literals": "^7.18.6", + "@babel/plugin-transform-modules-amd": "^7.19.6", + "@babel/plugin-transform-modules-commonjs": "^7.19.6", + "@babel/plugin-transform-modules-systemjs": "^7.19.6", + "@babel/plugin-transform-modules-umd": "^7.18.6", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.19.1", + "@babel/plugin-transform-new-target": "^7.18.6", + "@babel/plugin-transform-object-super": "^7.18.6", + "@babel/plugin-transform-parameters": "^7.20.1", + "@babel/plugin-transform-property-literals": "^7.18.6", + "@babel/plugin-transform-regenerator": "^7.18.6", + "@babel/plugin-transform-reserved-words": "^7.18.6", + "@babel/plugin-transform-shorthand-properties": "^7.18.6", + "@babel/plugin-transform-spread": "^7.19.0", + "@babel/plugin-transform-sticky-regex": "^7.18.6", + "@babel/plugin-transform-template-literals": "^7.18.9", + "@babel/plugin-transform-typeof-symbol": "^7.18.9", + "@babel/plugin-transform-unicode-escapes": "^7.18.10", + "@babel/plugin-transform-unicode-regex": "^7.18.6", + "@babel/preset-modules": "^0.1.5", + "@babel/types": "^7.20.2", + "babel-plugin-polyfill-corejs2": "^0.3.3", + "babel-plugin-polyfill-corejs3": "^0.6.0", + "babel-plugin-polyfill-regenerator": "^0.4.1", + "core-js-compat": "^3.25.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", + "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-react": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.18.6.tgz", + "integrity": "sha512-zXr6atUmyYdiWRVLOZahakYmOBHtWc2WGCkP8PYTgZi0iJXDY2CN180TdrIW4OGOAdLc7TifzDIvtx6izaRIzg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-validator-option": "^7.18.6", + "@babel/plugin-transform-react-display-name": "^7.18.6", + "@babel/plugin-transform-react-jsx": "^7.18.6", + "@babel/plugin-transform-react-jsx-development": "^7.18.6", + "@babel/plugin-transform-react-pure-annotations": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.21.0.tgz", + "integrity": "sha512-myc9mpoVA5m1rF8K8DgLEatOYFDpwC+RkMkjZ0Du6uI62YvDe8uxIEYVs/VCdSJ097nlALiU/yBC7//3nI+hNg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-validator-option": "^7.21.0", + "@babel/plugin-transform-typescript": "^7.21.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==" + }, "node_modules/@babel/runtime": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.5.tgz", - "integrity": "sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==", + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz", + "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==", "dependencies": { "regenerator-runtime": "^0.13.11" }, @@ -340,33 +1816,31 @@ } }, "node_modules/@babel/template": { - "version": "7.21.9", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.21.9.tgz", - "integrity": "sha512-MK0X5k8NKOuWRamiEfc3KEJiHMTkGZNUjzMipqCGDDc6ijRl/B7RGSKVGncu4Ro/HdyzzY6cmoXuKI2Gffk7vQ==", - "dev": true, + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", + "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", "dependencies": { - "@babel/code-frame": "^7.21.4", - "@babel/parser": "^7.21.9", - "@babel/types": "^7.21.5" + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.5.tgz", - "integrity": "sha512-AhQoI3YjWi6u/y/ntv7k48mcrCXmus0t79J9qPNlk/lAsFlCiJ047RmbfMOawySTHtywXhbXgpx/8nXMYd+oFw==", - "dev": true, + "version": "7.21.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.2.tgz", + "integrity": "sha512-ts5FFU/dSUPS13tv8XiEObDu9K+iagEKME9kAbaP7r0Y9KtZJZ+NGndDvWoRAYNpeWafbpFeki3q9QoMD6gxyw==", "dependencies": { - "@babel/code-frame": "^7.21.4", - "@babel/generator": "^7.21.5", - "@babel/helper-environment-visitor": "^7.21.5", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.21.1", + "@babel/helper-environment-visitor": "^7.18.9", "@babel/helper-function-name": "^7.21.0", "@babel/helper-hoist-variables": "^7.18.6", "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.21.5", - "@babel/types": "^7.21.5", + "@babel/parser": "^7.21.2", + "@babel/types": "^7.21.2", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -375,11 +1849,11 @@ } }, "node_modules/@babel/types": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.5.tgz", - "integrity": "sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q==", + "version": "7.21.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.2.tgz", + "integrity": "sha512-3wRZSs7jiFaB8AjxiiD+VqN5DTG2iRvJGQ+qYFrs/654lg6kGTQWIOFjlBo5RaXuAZjBmP3+OQH4dmhqiiyYxw==", "dependencies": { - "@babel/helper-string-parser": "^7.21.5", + "@babel/helper-string-parser": "^7.19.4", "@babel/helper-validator-identifier": "^7.19.1", "to-fast-properties": "^2.0.0" }, @@ -387,66 +1861,342 @@ "node": ">=6.9.0" } }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==" + }, + "node_modules/@csstools/normalize.css": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-12.0.0.tgz", + "integrity": "sha512-M0qqxAcwCsIVfpFQSlGN5XjXWu8l5JDZN+fPt1LeW5SZexQTgnaEvgXAY+CeygRw0EeppWHi12JxESWiWrB0Sg==" + }, + "node_modules/@csstools/postcss-cascade-layers": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-1.1.1.tgz", + "integrity": "sha512-+KdYrpKC5TgomQr2DlZF4lDEpHcoxnj5IGddYYfBWJAKfj1JtuHUIqMa+E1pJJ+z3kvDViWMqyqPlG4Ja7amQA==", + "dependencies": { + "@csstools/selector-specificity": "^2.0.2", + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-color-function": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-1.1.1.tgz", + "integrity": "sha512-Bc0f62WmHdtRDjf5f3e2STwRAl89N2CLb+9iAwzrv4L2hncrbDwnQD9PCq0gtAt7pOI2leIV08HIBUd4jxD8cw==", + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-font-format-keywords": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-1.0.1.tgz", + "integrity": "sha512-ZgrlzuUAjXIOc2JueK0X5sZDjCtgimVp/O5CEqTcs5ShWBa6smhWYbS0x5cVc/+rycTDbjjzoP0KTDnUneZGOg==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-hwb-function": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-1.0.2.tgz", + "integrity": "sha512-YHdEru4o3Rsbjmu6vHy4UKOXZD+Rn2zmkAmLRfPet6+Jz4Ojw8cbWxe1n42VaXQhD3CQUXXTooIy8OkVbUcL+w==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-ic-unit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-1.0.1.tgz", + "integrity": "sha512-Ot1rcwRAaRHNKC9tAqoqNZhjdYBzKk1POgWfhN4uCOE47ebGcLRqXjKkApVDpjifL6u2/55ekkpnFcp+s/OZUw==", + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-is-pseudo-class": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-2.0.7.tgz", + "integrity": "sha512-7JPeVVZHd+jxYdULl87lvjgvWldYu+Bc62s9vD/ED6/QTGjy0jy0US/f6BG53sVMTBJ1lzKZFpYmofBN9eaRiA==", + "dependencies": { + "@csstools/selector-specificity": "^2.0.0", + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-nested-calc": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-nested-calc/-/postcss-nested-calc-1.0.0.tgz", + "integrity": "sha512-JCsQsw1wjYwv1bJmgjKSoZNvf7R6+wuHDAbi5f/7MbFhl2d/+v+TvBTU4BJH3G1X1H87dHl0mh6TfYogbT/dJQ==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-normalize-display-values": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-1.0.1.tgz", + "integrity": "sha512-jcOanIbv55OFKQ3sYeFD/T0Ti7AMXc9nM1hZWu8m/2722gOTxFg7xYu4RDLJLeZmPUVQlGzo4jhzvTUq3x4ZUw==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-oklab-function": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-1.1.1.tgz", + "integrity": "sha512-nJpJgsdA3dA9y5pgyb/UfEzE7W5Ka7u0CX0/HIMVBNWzWemdcTH3XwANECU6anWv/ao4vVNLTMxhiPNZsTK6iA==", + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-progressive-custom-properties": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-1.3.0.tgz", + "integrity": "sha512-ASA9W1aIy5ygskZYuWams4BzafD12ULvSypmaLJT2jvQ8G0M3I8PRQhC0h7mG0Z3LI05+agZjqSR9+K9yaQQjA==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.3" + } + }, + "node_modules/@csstools/postcss-stepped-value-functions": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-1.0.1.tgz", + "integrity": "sha512-dz0LNoo3ijpTOQqEJLY8nyaapl6umbmDcgj4AD0lgVQ572b2eqA1iGZYTTWhrcrHztWDDRAX2DGYyw2VBjvCvQ==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-text-decoration-shorthand": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-1.0.0.tgz", + "integrity": "sha512-c1XwKJ2eMIWrzQenN0XbcfzckOLLJiczqy+YvfGmzoVXd7pT9FfObiSEfzs84bpE/VqfpEuAZ9tCRbZkZxxbdw==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-trigonometric-functions": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-1.0.2.tgz", + "integrity": "sha512-woKaLO///4bb+zZC2s80l+7cm07M7268MsyG3M0ActXXEFi6SuhvriQYcb58iiKGbjwwIU7n45iRLEHypB47Og==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-unset-value": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-unset-value/-/postcss-unset-value-1.0.2.tgz", + "integrity": "sha512-c8J4roPBILnelAsdLr4XOAR/GsTm0GJi4XpcfvoWk3U6KiTCqiFYc63KhRMQQX35jYMp4Ao8Ij9+IZRgMfJp1g==", + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/selector-specificity": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.1.1.tgz", + "integrity": "sha512-jwx+WCqszn53YHOfvFMJJRd/B2GqkCBt+1MJSG6o5/s8+ytHMvDZXsJgUEWLk12UnLd7HYKac4BYU5i/Ron1Cw==", + "engines": { + "node": "^14 || ^16 || >=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.4", + "postcss-selector-parser": "^6.0.10" + } + }, "node_modules/@emotion/babel-plugin": { - "version": "11.11.0", - "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz", - "integrity": "sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==", + "version": "11.10.6", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.10.6.tgz", + "integrity": "sha512-p2dAqtVrkhSa7xz1u/m9eHYdLi+en8NowrmXeF/dKtJpU8lCWli8RUAati7NcSl0afsBott48pdnANuD0wh9QQ==", "dependencies": { "@babel/helper-module-imports": "^7.16.7", "@babel/runtime": "^7.18.3", - "@emotion/hash": "^0.9.1", - "@emotion/memoize": "^0.8.1", - "@emotion/serialize": "^1.1.2", + "@emotion/hash": "^0.9.0", + "@emotion/memoize": "^0.8.0", + "@emotion/serialize": "^1.1.1", "babel-plugin-macros": "^3.1.0", "convert-source-map": "^1.5.0", "escape-string-regexp": "^4.0.0", "find-root": "^1.1.0", "source-map": "^0.5.7", - "stylis": "4.2.0" + "stylis": "4.1.3" } }, "node_modules/@emotion/cache": { - "version": "11.11.0", - "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz", - "integrity": "sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==", + "version": "11.10.5", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.10.5.tgz", + "integrity": "sha512-dGYHWyzTdmK+f2+EnIGBpkz1lKc4Zbj2KHd4cX3Wi8/OWr5pKslNjc3yABKH4adRGCvSX4VDC0i04mrrq0aiRA==", "dependencies": { - "@emotion/memoize": "^0.8.1", - "@emotion/sheet": "^1.2.2", - "@emotion/utils": "^1.2.1", - "@emotion/weak-memoize": "^0.3.1", - "stylis": "4.2.0" + "@emotion/memoize": "^0.8.0", + "@emotion/sheet": "^1.2.1", + "@emotion/utils": "^1.2.0", + "@emotion/weak-memoize": "^0.3.0", + "stylis": "4.1.3" } }, "node_modules/@emotion/hash": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz", - "integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==" + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.0.tgz", + "integrity": "sha512-14FtKiHhy2QoPIzdTcvh//8OyBlknNs2nXRwIhG904opCby3l+9Xaf/wuPvICBF0rc1ZCNBd3nKe9cd2mecVkQ==" }, "node_modules/@emotion/is-prop-valid": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz", - "integrity": "sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz", + "integrity": "sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg==", "dependencies": { - "@emotion/memoize": "^0.8.1" + "@emotion/memoize": "^0.8.0" } }, "node_modules/@emotion/memoize": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", - "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz", + "integrity": "sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==" }, "node_modules/@emotion/react": { - "version": "11.11.0", - "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.0.tgz", - "integrity": "sha512-ZSK3ZJsNkwfjT3JpDAWJZlrGD81Z3ytNDsxw1LKq1o+xkmO5pnWfr6gmCC8gHEFf3nSSX/09YrG67jybNPxSUw==", + "version": "11.10.6", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.10.6.tgz", + "integrity": "sha512-6HT8jBmcSkfzO7mc+N1L9uwvOnlcGoix8Zn7srt+9ga0MjREo6lRpuVX0kzo6Jp6oTqDhREOFsygN6Ew4fEQbw==", "dependencies": { "@babel/runtime": "^7.18.3", - "@emotion/babel-plugin": "^11.11.0", - "@emotion/cache": "^11.11.0", - "@emotion/serialize": "^1.1.2", - "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", - "@emotion/utils": "^1.2.1", - "@emotion/weak-memoize": "^0.3.1", + "@emotion/babel-plugin": "^11.10.6", + "@emotion/cache": "^11.10.5", + "@emotion/serialize": "^1.1.1", + "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0", + "@emotion/utils": "^1.2.0", + "@emotion/weak-memoize": "^0.3.0", "hoist-non-react-statics": "^3.3.1" }, "peerDependencies": { @@ -459,33 +2209,33 @@ } }, "node_modules/@emotion/serialize": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.2.tgz", - "integrity": "sha512-zR6a/fkFP4EAcCMQtLOhIgpprZOwNmCldtpaISpvz348+DP4Mz8ZoKaGGCQpbzepNIUWbq4w6hNZkwDyKoS+HA==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.1.tgz", + "integrity": "sha512-Zl/0LFggN7+L1liljxXdsVSVlg6E/Z/olVWpfxUTxOAmi8NU7YoeWeLfi1RmnB2TATHoaWwIBRoL+FvAJiTUQA==", "dependencies": { - "@emotion/hash": "^0.9.1", - "@emotion/memoize": "^0.8.1", - "@emotion/unitless": "^0.8.1", - "@emotion/utils": "^1.2.1", + "@emotion/hash": "^0.9.0", + "@emotion/memoize": "^0.8.0", + "@emotion/unitless": "^0.8.0", + "@emotion/utils": "^1.2.0", "csstype": "^3.0.2" } }, "node_modules/@emotion/sheet": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz", - "integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==" + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.1.tgz", + "integrity": "sha512-zxRBwl93sHMsOj4zs+OslQKg/uhF38MB+OMKoCrVuS0nyTkqnau+BM3WGEoOptg9Oz45T/aIGs1qbVAsEFo3nA==" }, "node_modules/@emotion/styled": { - "version": "11.11.0", - "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.11.0.tgz", - "integrity": "sha512-hM5Nnvu9P3midq5aaXj4I+lnSfNi7Pmd4EWk1fOZ3pxookaQTNew6bp4JaCBYM4HVFZF9g7UjJmsUmC2JlxOng==", + "version": "11.10.6", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.10.6.tgz", + "integrity": "sha512-OXtBzOmDSJo5Q0AFemHCfl+bUueT8BIcPSxu0EGTpGk6DmI5dnhSzQANm1e1ze0YZL7TDyAyy6s/b/zmGOS3Og==", "dependencies": { "@babel/runtime": "^7.18.3", - "@emotion/babel-plugin": "^11.11.0", - "@emotion/is-prop-valid": "^1.2.1", - "@emotion/serialize": "^1.1.2", - "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", - "@emotion/utils": "^1.2.1" + "@emotion/babel-plugin": "^11.10.6", + "@emotion/is-prop-valid": "^1.2.0", + "@emotion/serialize": "^1.1.1", + "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0", + "@emotion/utils": "^1.2.0" }, "peerDependencies": { "@emotion/react": "^11.0.0-rc.0", @@ -498,413 +2248,36 @@ } }, "node_modules/@emotion/unitless": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", - "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==" + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.0.tgz", + "integrity": "sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw==" }, "node_modules/@emotion/use-insertion-effect-with-fallbacks": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz", - "integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.0.tgz", + "integrity": "sha512-1eEgUGmkaljiBnRMTdksDV1W4kUnmwgp7X9G8B++9GYwl1lUdqSndSriIrTJ0N7LQaoauY9JJ2yhiOYK5+NI4A==", "peerDependencies": { "react": ">=16.8.0" } }, "node_modules/@emotion/utils": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.1.tgz", - "integrity": "sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.0.tgz", + "integrity": "sha512-sn3WH53Kzpw8oQ5mgMmIzzyAaH2ZqFEbozVVBSYp538E06OSE6ytOp7pRAjNQR+Q/orwqdQYJSe2m3hCOeznkw==" }, "node_modules/@emotion/weak-memoize": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz", - "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==" - }, - "node_modules/@esbuild/android-arm": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz", - "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz", - "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz", - "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz", - "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz", - "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz", - "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz", - "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz", - "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz", - "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz", - "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz", - "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz", - "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz", - "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz", - "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz", - "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz", - "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz", - "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz", - "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz", - "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz", - "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz", - "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz", - "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.1.tgz", - "integrity": "sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==", - "dev": true, - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.0.tgz", + "integrity": "sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg==" }, "node_modules/@eslint/eslintrc": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.3.tgz", - "integrity": "sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==", - "dev": true, + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.0.tgz", + "integrity": "sha512-fluIaaV+GyV24CCu/ggiHdV+j4RNh85yQnAYS/G2mZODZgGmmlrgCydjUcV3YvxCm9x8nMAfThsqTni4KiXT4A==", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.5.2", + "espree": "^9.4.0", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", @@ -919,11 +2292,15 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@eslint/eslintrc/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, "node_modules/@eslint/eslintrc/node_modules/globals": { "version": "13.20.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", - "dev": true, "dependencies": { "type-fest": "^0.20.2" }, @@ -934,11 +2311,32 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@eslint/js": { - "version": "8.41.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.41.0.tgz", - "integrity": "sha512-LxcyMGxwmTh2lY9FwHPGWOHmYFCZvbrFCBZL4FzSSsxsRPuhrYUg/49/0KDfW8tnIEaEHtfmn6+NPN+1DqaNmA==", - "dev": true, + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.35.0.tgz", + "integrity": "sha512-JXdzbRiWclLVoD8sNUjR443VVlYqiYmDVT6rGUEIEHU5YJW0gaVZwV2xgM7D4arkvASqD0IlLUVjHiFuxaftRw==", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } @@ -947,7 +2345,6 @@ "version": "0.11.8", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", - "dev": true, "dependencies": { "@humanwhocodes/object-schema": "^1.2.1", "debug": "^4.1.1", @@ -961,7 +2358,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, "engines": { "node": ">=12.22" }, @@ -973,18 +2369,686 @@ "node_modules/@humanwhocodes/object-schema": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==" + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-27.5.1.tgz", + "integrity": "sha512-kZ/tNpS3NXn0mlXXXPNuDZnb4c0oZ20r4K5eemM2k30ZC3G0T02nXUvyhf5YdbXWHPEJLc9qGLxEZ216MdL+Zg==", + "dependencies": { + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^27.5.1", + "jest-util": "^27.5.1", + "slash": "^3.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/console/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/console/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/console/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/console/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/@jest/console/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/core": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-27.5.1.tgz", + "integrity": "sha512-AK6/UTrvQD0Cd24NSqmIA6rKsu0tKIxfiCducZvqxYdmMisOYAsdItspT+fQDQYARPf8XgjAFZi0ogW2agH5nQ==", + "dependencies": { + "@jest/console": "^27.5.1", + "@jest/reporters": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.8.1", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^27.5.1", + "jest-config": "^27.5.1", + "jest-haste-map": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-regex-util": "^27.5.1", + "jest-resolve": "^27.5.1", + "jest-resolve-dependencies": "^27.5.1", + "jest-runner": "^27.5.1", + "jest-runtime": "^27.5.1", + "jest-snapshot": "^27.5.1", + "jest-util": "^27.5.1", + "jest-validate": "^27.5.1", + "jest-watcher": "^27.5.1", + "micromatch": "^4.0.4", + "rimraf": "^3.0.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/core/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/core/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/core/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/core/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/@jest/core/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/core/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/environment": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.5.1.tgz", + "integrity": "sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA==", + "dependencies": { + "@jest/fake-timers": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "jest-mock": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.5.1.tgz", + "integrity": "sha512-/aPowoolwa07k7/oM3aASneNeBGCmGQsc3ugN4u6s4C/+s5M64MFo/+djTdiwcbQlRfFElGuDXWzaWj6QgKObQ==", + "dependencies": { + "@jest/types": "^27.5.1", + "@sinonjs/fake-timers": "^8.0.1", + "@types/node": "*", + "jest-message-util": "^27.5.1", + "jest-mock": "^27.5.1", + "jest-util": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-27.5.1.tgz", + "integrity": "sha512-ZEJNB41OBQQgGzgyInAv0UUfDDj3upmHydjieSxFvTRuZElrx7tXg/uVQ5hYVEwiXs3+aMsAeEc9X7xiSKCm4Q==", + "dependencies": { + "@jest/environment": "^27.5.1", + "@jest/types": "^27.5.1", + "expect": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-27.5.1.tgz", + "integrity": "sha512-cPXh9hWIlVJMQkVk84aIvXuBB4uQQmFqZiacloFuGiP3ah1sbCxCosidXFDfqG8+6fO1oR2dTJTlsOy4VFmUfw==", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.2", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-haste-map": "^27.5.1", + "jest-resolve": "^27.5.1", + "jest-util": "^27.5.1", + "jest-worker": "^27.5.1", + "slash": "^3.0.0", + "source-map": "^0.6.0", + "string-length": "^4.0.1", + "terminal-link": "^2.0.0", + "v8-to-istanbul": "^8.1.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/reporters/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/reporters/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/reporters/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/reporters/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/@jest/reporters/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/reporters/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==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@jest/reporters/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/schemas": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", + "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", + "dependencies": { + "@sinclair/typebox": "^0.24.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-27.5.1.tgz", + "integrity": "sha512-y9NIHUYF3PJRlHk98NdC/N1gl88BL08aQQgu4k4ZopQkCw9t9cV8mtl3TV8b/YCB8XaVTFrmUTAJvjsntDireg==", + "dependencies": { + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9", + "source-map": "^0.6.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/source-map/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==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@jest/test-result": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-27.5.1.tgz", + "integrity": "sha512-EW35l2RYFUcUQxFJz5Cv5MTOxlJIQs4I7gxzi2zVU7PJhOwfYq1MdC5nhSmYjX1gmMmLPvB3sIaC+BkcHRBfag==", + "dependencies": { + "@jest/console": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-27.5.1.tgz", + "integrity": "sha512-LCheJF7WB2+9JuCS7VB/EmGIdQuhtqjRNI9A43idHv3E4KltCTsPsLxvdaubFHSYwY/fNjMWjl6vNRhDiN7vpQ==", + "dependencies": { + "@jest/test-result": "^27.5.1", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^27.5.1", + "jest-runtime": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-27.5.1.tgz", + "integrity": "sha512-ipON6WtYgl/1329g5AIJVbUuEh0wZVbdpGwC99Jw4LwuoBNS95MVphU6zOeD9pDkon+LLbFL7lOQRapbB8SCHw==", + "dependencies": { + "@babel/core": "^7.1.0", + "@jest/types": "^27.5.1", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^27.5.1", + "jest-regex-util": "^27.5.1", + "jest-util": "^27.5.1", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "source-map": "^0.6.1", + "write-file-atomic": "^3.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/transform/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/transform/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/transform/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/transform/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/@jest/transform/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/transform/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==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@jest/transform/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/types/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/types/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/types/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/types/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/@jest/types/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/types/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", - "dev": true, + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" }, "engines": { "node": ">=6.0.0" @@ -994,7 +3058,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "dev": true, "engines": { "node": ">=6.0.0" } @@ -1003,43 +3066,61 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", + "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "node_modules/@jridgewell/source-map/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, "engines": { "node": ">=6.0.0" } }, "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==", - "dev": true + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.18", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", - "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", - "dev": true, + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", + "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", "dependencies": { "@jridgewell/resolve-uri": "3.1.0", "@jridgewell/sourcemap-codec": "1.4.14" } }, - "node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", + "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==" }, "node_modules/@mui/base": { - "version": "5.0.0-beta.2", - "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.2.tgz", - "integrity": "sha512-R9R+aqrl1QhZJaO05rhvooqxOaf7SKpQ+EjW80sbP3ticTVmLmrn4YBLQS7/ML+WXdrkrPtqSmKFdSE5Ik3gBQ==", + "version": "5.0.0-alpha.119", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.119.tgz", + "integrity": "sha512-XA5zhlYfXi67u613eIF0xRmktkatx6ERy3h+PwrMN5IcWFbgiL1guz8VpdXON+GWb8+G7B8t5oqTFIaCqaSAeA==", "dependencies": { "@babel/runtime": "^7.21.0", - "@emotion/is-prop-valid": "^1.2.1", - "@mui/types": "^7.2.4", - "@mui/utils": "^5.13.1", - "@popperjs/core": "^2.11.7", + "@emotion/is-prop-valid": "^1.2.0", + "@mui/types": "^7.2.3", + "@mui/utils": "^5.11.11", + "@popperjs/core": "^2.11.6", "clsx": "^1.2.1", "prop-types": "^15.8.1", "react-is": "^18.2.0" @@ -1063,18 +3144,18 @@ } }, "node_modules/@mui/core-downloads-tracker": { - "version": "5.13.2", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.13.2.tgz", - "integrity": "sha512-aOLCXMCySMFL2WmUhnz+DjF84AoFVu8rn35OsL759HXOZMz8zhEwVf5w/xxkWx7DycM2KXDTgAvYW48nTfqTLA==", + "version": "5.11.11", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.11.11.tgz", + "integrity": "sha512-0YK0K9GfW1ysw9z4ztWAjLW+bktf+nExMyn2+EQe1Ijb0kF2kz1kIOmb4+di0/PsXG70uCuw4DhEIdNd+JQkRA==", "funding": { "type": "opencollective", "url": "https://opencollective.com/mui" } }, "node_modules/@mui/icons-material": { - "version": "5.11.16", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.11.16.tgz", - "integrity": "sha512-oKkx9z9Kwg40NtcIajF9uOXhxiyTZrrm9nmIJ4UjkU2IdHpd4QVLbCc/5hZN/y0C6qzi2Zlxyr9TGddQx2vx2A==", + "version": "5.11.11", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.11.11.tgz", + "integrity": "sha512-Eell3ADmQVE8HOpt/LZ3zIma8JSvPh3XgnhwZLT0k5HRqZcd6F/QDHc7xsWtgz09t+UEFvOYJXjtrwKmLdwwpw==", "dependencies": { "@babel/runtime": "^7.21.0" }, @@ -1097,19 +3178,19 @@ } }, "node_modules/@mui/material": { - "version": "5.13.2", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.13.2.tgz", - "integrity": "sha512-Pfke1l0GG2OJb/Nr10aVr8huoBFcBTdWKV5iFSTEHqf9c2C1ZlyYMISn7ui6X3Gix8vr+hP5kVqH1LAWwQSb6w==", + "version": "5.11.11", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.11.11.tgz", + "integrity": "sha512-sSe0dmKjB1IGOYt32Pcha+cXV3IIrX5L5mFAF9LDRssp/x53bluhgLLbkc8eTiJvueVvo6HAyze6EkFEYLQRXQ==", "dependencies": { "@babel/runtime": "^7.21.0", - "@mui/base": "5.0.0-beta.2", - "@mui/core-downloads-tracker": "^5.13.2", - "@mui/system": "^5.13.2", - "@mui/types": "^7.2.4", - "@mui/utils": "^5.13.1", - "@types/react-transition-group": "^4.4.6", + "@mui/base": "5.0.0-alpha.119", + "@mui/core-downloads-tracker": "^5.11.11", + "@mui/system": "^5.11.11", + "@mui/types": "^7.2.3", + "@mui/utils": "^5.11.11", + "@types/react-transition-group": "^4.4.5", "clsx": "^1.2.1", - "csstype": "^3.1.2", + "csstype": "^3.1.1", "prop-types": "^15.8.1", "react-is": "^18.2.0", "react-transition-group": "^4.4.5" @@ -1141,12 +3222,12 @@ } }, "node_modules/@mui/private-theming": { - "version": "5.13.1", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.13.1.tgz", - "integrity": "sha512-HW4npLUD9BAkVppOUZHeO1FOKUJWAwbpy0VQoGe3McUYTlck1HezGHQCfBQ5S/Nszi7EViqiimECVl9xi+/WjQ==", + "version": "5.11.11", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.11.11.tgz", + "integrity": "sha512-yLgTkjNC1mpye2SOUkc+zQQczUpg8NvQAETvxwXTMzNgJK1pv4htL7IvBM5vmCKG7IHAB3hX26W2u6i7bxwF3A==", "dependencies": { "@babel/runtime": "^7.21.0", - "@mui/utils": "^5.13.1", + "@mui/utils": "^5.11.11", "prop-types": "^15.8.1" }, "engines": { @@ -1167,13 +3248,13 @@ } }, "node_modules/@mui/styled-engine": { - "version": "5.13.2", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.13.2.tgz", - "integrity": "sha512-VCYCU6xVtXOrIN8lcbuPmoG+u7FYuOERG++fpY74hPpEWkyFQG97F+/XfTQVYzlR2m7nPjnwVUgATcTCMEaMvw==", + "version": "5.11.11", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.11.11.tgz", + "integrity": "sha512-wV0UgW4lN5FkDBXefN8eTYeuE9sjyQdg5h94vtwZCUamGQEzmCOtir4AakgmbWMy0x8OLjdEUESn9wnf5J9MOg==", "dependencies": { "@babel/runtime": "^7.21.0", - "@emotion/cache": "^11.11.0", - "csstype": "^3.1.2", + "@emotion/cache": "^11.10.5", + "csstype": "^3.1.1", "prop-types": "^15.8.1" }, "engines": { @@ -1198,17 +3279,17 @@ } }, "node_modules/@mui/system": { - "version": "5.13.2", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.13.2.tgz", - "integrity": "sha512-TPyWmRJPt0JPVxacZISI4o070xEJ7ftxpVtu6LWuYVOUOINlhoGOclam4iV8PDT3EMQEHuUrwU49po34UdWLlw==", + "version": "5.11.11", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.11.11.tgz", + "integrity": "sha512-a9gaOAJBjpzypDfhbGZQ8HzdcxdxsKkFvbp1aAWZhFHBPdehEkARNh7mj851VfEhD/GdffYt85PFKFKdUta5Eg==", "dependencies": { "@babel/runtime": "^7.21.0", - "@mui/private-theming": "^5.13.1", - "@mui/styled-engine": "^5.13.2", - "@mui/types": "^7.2.4", - "@mui/utils": "^5.13.1", + "@mui/private-theming": "^5.11.11", + "@mui/styled-engine": "^5.11.11", + "@mui/types": "^7.2.3", + "@mui/utils": "^5.11.11", "clsx": "^1.2.1", - "csstype": "^3.1.2", + "csstype": "^3.1.1", "prop-types": "^15.8.1" }, "engines": { @@ -1237,9 +3318,9 @@ } }, "node_modules/@mui/types": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.4.tgz", - "integrity": "sha512-LBcwa8rN84bKF+f5sDyku42w1NTxaPgPyYKODsh01U1fVstTClbUoSA96oyRBnSNyEiAVjKm6Gwx9vjR+xyqHA==", + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.3.tgz", + "integrity": "sha512-tZ+CQggbe9Ol7e/Fs5RcKwg/woU+o8DCtOnccX6KmbBc7YrfqMYEYuaIcXHuhpT880QwNkZZ3wQwvtlDFA2yOw==", "peerDependencies": { "@types/react": "*" }, @@ -1250,13 +3331,13 @@ } }, "node_modules/@mui/utils": { - "version": "5.13.1", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.13.1.tgz", - "integrity": "sha512-6lXdWwmlUbEU2jUI8blw38Kt+3ly7xkmV9ljzY4Q20WhsJMWiNry9CX8M+TaP/HbtuyR8XKsdMgQW7h7MM3n3A==", + "version": "5.11.11", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.11.11.tgz", + "integrity": "sha512-neMM5rrEXYQrOrlxUfns/TGgX4viS8K2zb9pbQh11/oUUYFlGI32Tn+PHePQx7n6Fy/0zq6WxdBFC9VpnJ5JrQ==", "dependencies": { "@babel/runtime": "^7.21.0", "@types/prop-types": "^15.7.5", - "@types/react-is": "^18.2.0", + "@types/react-is": "^16.7.1 || ^17.0.0", "prop-types": "^15.8.1", "react-is": "^18.2.0" }, @@ -1271,11 +3352,38 @@ "react": "^17.0.0 || ^18.0.0" } }, + "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { + "version": "5.1.1-v1", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", + "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==", + "dependencies": { + "eslint-scope": "5.1.1" + } + }, + "node_modules/@nicolo-ribaudo/eslint-scope-5-internals/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@nicolo-ribaudo/eslint-scope-5-internals/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "engines": { + "node": ">=4.0" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -1288,7 +3396,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, "engines": { "node": ">= 8" } @@ -1297,7 +3404,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -1306,43 +3412,625 @@ "node": ">= 8" } }, + "node_modules/@pmmmwh/react-refresh-webpack-plugin": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.10.tgz", + "integrity": "sha512-j0Ya0hCFZPd4x40qLzbhGsh9TMtdb+CJQiso+WxLOPNasohq9cc5SNUcwsZaRH6++Xh91Xkm/xHCkuIiIu0LUA==", + "dependencies": { + "ansi-html-community": "^0.0.8", + "common-path-prefix": "^3.0.0", + "core-js-pure": "^3.23.3", + "error-stack-parser": "^2.0.6", + "find-up": "^5.0.0", + "html-entities": "^2.1.0", + "loader-utils": "^2.0.4", + "schema-utils": "^3.0.0", + "source-map": "^0.7.3" + }, + "engines": { + "node": ">= 10.13" + }, + "peerDependencies": { + "@types/webpack": "4.x || 5.x", + "react-refresh": ">=0.10.0 <1.0.0", + "sockjs-client": "^1.4.0", + "type-fest": ">=0.17.0 <4.0.0", + "webpack": ">=4.43.0 <6.0.0", + "webpack-dev-server": "3.x || 4.x", + "webpack-hot-middleware": "2.x", + "webpack-plugin-serve": "0.x || 1.x" + }, + "peerDependenciesMeta": { + "@types/webpack": { + "optional": true + }, + "sockjs-client": { + "optional": true + }, + "type-fest": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + }, + "webpack-hot-middleware": { + "optional": true + }, + "webpack-plugin-serve": { + "optional": true + } + } + }, + "node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "engines": { + "node": ">= 8" + } + }, "node_modules/@popperjs/core": { - "version": "2.11.7", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.7.tgz", - "integrity": "sha512-Cr4OjIkipTtcXKjAsm8agyleBuDHvxzeBoa1v543lbv1YaIwQjESsVcmjiWiPEbC1FIeHOG/Op9kdCmAmiS3Kw==", + "version": "2.11.6", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz", + "integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==", "funding": { "type": "opencollective", "url": "https://opencollective.com/popperjs" } }, "node_modules/@remix-run/router": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.6.2.tgz", - "integrity": "sha512-LzqpSrMK/3JBAVBI9u3NWtOhWNw5AMQfrUFYB0+bDHTSw17z++WJLsPsxAuK+oSddsxk4d7F/JcdDPM1M5YAhA==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.3.3.tgz", + "integrity": "sha512-YRHie1yQEj0kqqCTCJEfHqYSSNlZQ696QJG+MMiW4mxSl9I0ojz/eRhJS4fs88Z5i6D1SmoF9d3K99/QOhI8/w==", "engines": { "node": ">=14" } }, + "node_modules/@rollup/plugin-babel": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", + "integrity": "sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==", + "dependencies": { + "@babel/helper-module-imports": "^7.10.4", + "@rollup/pluginutils": "^3.1.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "@types/babel__core": "^7.1.9", + "rollup": "^1.20.0||^2.0.0" + }, + "peerDependenciesMeta": { + "@types/babel__core": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "11.2.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-11.2.1.tgz", + "integrity": "sha512-yc2n43jcqVyGE2sqV5/YCmocy9ArjVAP/BeXyTtADTBBX6V0e5UMqwO8CdQ0kzjb6zu5P1qMzsScCMRvE9OlVg==", + "dependencies": { + "@rollup/pluginutils": "^3.1.0", + "@types/resolve": "1.17.1", + "builtin-modules": "^3.1.0", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0" + } + }, + "node_modules/@rollup/plugin-replace": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-2.4.2.tgz", + "integrity": "sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg==", + "dependencies": { + "@rollup/pluginutils": "^3.1.0", + "magic-string": "^0.25.7" + }, + "peerDependencies": { + "rollup": "^1.20.0 || ^2.0.0" + } + }, + "node_modules/@rollup/pluginutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", + "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", + "dependencies": { + "@types/estree": "0.0.39", + "estree-walker": "^1.0.1", + "picomatch": "^2.2.2" + }, + "engines": { + "node": ">= 8.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0" + } + }, + "node_modules/@rollup/pluginutils/node_modules/@types/estree": { + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", + "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==" + }, + "node_modules/@rushstack/eslint-patch": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz", + "integrity": "sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==" + }, + "node_modules/@sinclair/typebox": { + "version": "0.24.51", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", + "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==" + }, + "node_modules/@sinonjs/commons": { + "version": "1.8.6", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", + "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz", + "integrity": "sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==", + "dependencies": { + "@sinonjs/commons": "^1.7.0" + } + }, + "node_modules/@surma/rollup-plugin-off-main-thread": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz", + "integrity": "sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==", + "dependencies": { + "ejs": "^3.1.6", + "json5": "^2.2.0", + "magic-string": "^0.25.0", + "string.prototype.matchall": "^4.0.6" + } + }, + "node_modules/@svgr/babel-plugin-add-jsx-attribute": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-5.4.0.tgz", + "integrity": "sha512-ZFf2gs/8/6B8PnSofI0inYXr2SDNTDScPXhN7k5EqD4aZ3gi6u+rbmZHVB8IM3wDyx8ntKACZbtXSm7oZGRqVg==", + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-attribute": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-5.4.0.tgz", + "integrity": "sha512-yaS4o2PgUtwLFGTKbsiAy6D0o3ugcUhWK0Z45umJ66EPWunAz9fuFw2gJuje6wqQvQWOTJvIahUwndOXb7QCPg==", + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-5.0.1.tgz", + "integrity": "sha512-LA72+88A11ND/yFIMzyuLRSMJ+tRKeYKeQ+mR3DcAZ5I4h5CPWN9AHyUzJbWSYp/u2u0xhmgOe0+E41+GjEueA==", + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-5.0.1.tgz", + "integrity": "sha512-PoiE6ZD2Eiy5mK+fjHqwGOS+IXX0wq/YDtNyIgOrc6ejFnxN4b13pRpiIPbtPwHEc+NT2KCjteAcq33/F1Y9KQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/babel-plugin-svg-dynamic-title": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-5.4.0.tgz", + "integrity": "sha512-zSOZH8PdZOpuG1ZVx/cLVePB2ibo3WPpqo7gFIjLV9a0QsuQAzJiwwqmuEdTaW2pegyBE17Uu15mOgOcgabQZg==", + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/babel-plugin-svg-em-dimensions": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-5.4.0.tgz", + "integrity": "sha512-cPzDbDA5oT/sPXDCUYoVXEmm3VIoAWAPT6mSPTJNbQaBNUuEKVKyGH93oDY4e42PYHRW67N5alJx/eEol20abw==", + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/babel-plugin-transform-react-native-svg": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-5.4.0.tgz", + "integrity": "sha512-3eYP/SaopZ41GHwXma7Rmxcv9uRslRDTY1estspeB1w1ueZWd/tPlMfEOoccYpEMZU3jD4OU7YitnXcF5hLW2Q==", + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/babel-plugin-transform-svg-component": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-5.5.0.tgz", + "integrity": "sha512-q4jSH1UUvbrsOtlo/tKcgSeiCHRSBdXoIoqX1pgcKK/aU3JD27wmMKwGtpB8qRYUYoyXvfGxUVKchLuR5pB3rQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/babel-preset": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-5.5.0.tgz", + "integrity": "sha512-4FiXBjvQ+z2j7yASeGPEi8VD/5rrGQk4Xrq3EdJmoZgz/tpqChpo5hgXDvmEauwtvOc52q8ghhZK4Oy7qph4ig==", + "dependencies": { + "@svgr/babel-plugin-add-jsx-attribute": "^5.4.0", + "@svgr/babel-plugin-remove-jsx-attribute": "^5.4.0", + "@svgr/babel-plugin-remove-jsx-empty-expression": "^5.0.1", + "@svgr/babel-plugin-replace-jsx-attribute-value": "^5.0.1", + "@svgr/babel-plugin-svg-dynamic-title": "^5.4.0", + "@svgr/babel-plugin-svg-em-dimensions": "^5.4.0", + "@svgr/babel-plugin-transform-react-native-svg": "^5.4.0", + "@svgr/babel-plugin-transform-svg-component": "^5.5.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/core": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/core/-/core-5.5.0.tgz", + "integrity": "sha512-q52VOcsJPvV3jO1wkPtzTuKlvX7Y3xIcWRpCMtBF3MrteZJtBfQw/+u0B1BHy5ColpQc1/YVTrPEtSYIMNZlrQ==", + "dependencies": { + "@svgr/plugin-jsx": "^5.5.0", + "camelcase": "^6.2.0", + "cosmiconfig": "^7.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/hast-util-to-babel-ast": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-5.5.0.tgz", + "integrity": "sha512-cAaR/CAiZRB8GP32N+1jocovUtvlj0+e65TB50/6Lcime+EA49m/8l+P2ko+XPJ4dw3xaPS3jOL4F2X4KWxoeQ==", + "dependencies": { + "@babel/types": "^7.12.6" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/plugin-jsx": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-5.5.0.tgz", + "integrity": "sha512-V/wVh33j12hGh05IDg8GpIUXbjAPnTdPTKuP4VNLggnwaHMPNQNae2pRnyTAILWCQdz5GyMqtO488g7CKM8CBA==", + "dependencies": { + "@babel/core": "^7.12.3", + "@svgr/babel-preset": "^5.5.0", + "@svgr/hast-util-to-babel-ast": "^5.5.0", + "svg-parser": "^2.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/plugin-svgo": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-5.5.0.tgz", + "integrity": "sha512-r5swKk46GuQl4RrVejVwpeeJaydoxkdwkM1mBKOgJLBUJPGaLci6ylg/IjhrRsREKDkr4kbMWdgOtbXEh0fyLQ==", + "dependencies": { + "cosmiconfig": "^7.0.0", + "deepmerge": "^4.2.2", + "svgo": "^1.2.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/webpack": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-5.5.0.tgz", + "integrity": "sha512-DOBOK255wfQxguUta2INKkzPj6AIS6iafZYiYmHn6W3pHlycSRRlvWKCfLDG10fXfLWqE3DJHgRUOyJYmARa7g==", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/plugin-transform-react-constant-elements": "^7.12.1", + "@babel/preset-env": "^7.12.1", + "@babel/preset-react": "^7.12.5", + "@svgr/core": "^5.5.0", + "@svgr/plugin-jsx": "^5.5.0", + "@svgr/plugin-svgo": "^5.5.0", + "loader-utils": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/@trysound/sax": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", + "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.0.tgz", + "integrity": "sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ==", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", + "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", + "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.3.tgz", + "integrity": "sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w==", + "dependencies": { + "@babel/types": "^7.3.0" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bonjour": { + "version": "3.5.10", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.10.tgz", + "integrity": "sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect-history-api-fallback": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz", + "integrity": "sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw==", + "dependencies": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "node_modules/@types/eslint": { + "version": "8.21.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.21.1.tgz", + "integrity": "sha512-rc9K8ZpVjNcLs8Fp0dkozd5Pt2Apk1glO4Vgz8ix1u6yFByxfqo5Yavpy65o+93TAe24jr7v+eSBtFLvOQtCRQ==", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", + "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz", + "integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==" + }, + "node_modules/@types/express": { + "version": "4.17.17", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz", + "integrity": "sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.33", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.33.tgz", + "integrity": "sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA==", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz", + "integrity": "sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==" + }, + "node_modules/@types/http-proxy": { + "version": "1.17.10", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.10.tgz", + "integrity": "sha512-Qs5aULi+zV1bwKAg5z1PWnDXWmsn+LxIvUGv6E2+OOMYhclZMO+OXd9pYVf2gLykf2I7IV2u7oTHwChPNsvJ7g==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==" + }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==" + }, + "node_modules/@types/mime": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", + "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==" + }, + "node_modules/@types/node": { + "version": "18.14.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.2.tgz", + "integrity": "sha512-1uEQxww3DaghA0RxqHx0O0ppVlo43pJhepY51OxuQIKHpjbnYLA7vcdwioNPzIqmC2u3I/dmylcqjlh0e7AyUA==" }, "node_modules/@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" }, + "node_modules/@types/prettier": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz", + "integrity": "sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==" + }, "node_modules/@types/prop-types": { "version": "15.7.5", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" }, + "node_modules/@types/q": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.5.tgz", + "integrity": "sha512-L28j2FcJfSZOnL1WBjDYp2vUHCeIFlyYI/53EwD/rKUBQ7MtUUfbQWiyKJGpcnv4/WgrhWsFKrcPstcAt/J0tQ==" + }, + "node_modules/@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" + }, + "node_modules/@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" + }, "node_modules/@types/react": { - "version": "18.2.7", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.7.tgz", - "integrity": "sha512-ojrXpSH2XFCmHm7Jy3q44nXDyN54+EYKP2lBhJ2bqfyPj6cIUW/FZW/Csdia34NQgq7KYcAlHi5184m4X88+yw==", + "version": "18.0.28", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.28.tgz", + "integrity": "sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew==", "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -1350,49 +4038,480 @@ } }, "node_modules/@types/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-1vz2yObaQkLL7YFe/pme2cpvDsCwI1WXIfL+5eLz0MI9gFG24Re16RzUsI8t9XZn9ZWvgLNDrJBmrqXJO7GNQQ==", + "version": "17.0.3", + "resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-17.0.3.tgz", + "integrity": "sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw==", "dependencies": { "@types/react": "*" } }, "node_modules/@types/react-transition-group": { - "version": "4.4.6", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.6.tgz", - "integrity": "sha512-VnCdSxfcm08KjsJVQcfBmhEQAPnLB8G08hAxn39azX1qYBQ/5RVQuoHuKIcfKOdncuaUvEpFKFzEvbtIMsfVew==", + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==", "dependencies": { "@types/react": "*" } }, - "node_modules/@types/scheduler": { - "version": "0.16.3", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", - "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==" - }, - "node_modules/@vitejs/plugin-react": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.0.0.tgz", - "integrity": "sha512-HX0XzMjL3hhOYm+0s95pb0Z7F8O81G7joUHgfDd/9J/ZZf5k4xX6QAMFkKsHFxaHlf6X7GD7+XuaZ66ULiJuhQ==", - "dev": true, + "node_modules/@types/resolve": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", + "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==", "dependencies": { - "@babel/core": "^7.21.4", - "@babel/plugin-transform-react-jsx-self": "^7.21.0", - "@babel/plugin-transform-react-jsx-source": "^7.19.6", - "react-refresh": "^0.14.0" + "@types/node": "*" + } + }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==" + }, + "node_modules/@types/scheduler": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" + }, + "node_modules/@types/semver": { + "version": "7.3.13", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", + "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==" + }, + "node_modules/@types/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg==", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.1.tgz", + "integrity": "sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ==", + "dependencies": { + "@types/mime": "*", + "@types/node": "*" + } + }, + "node_modules/@types/sockjs": { + "version": "0.3.33", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz", + "integrity": "sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", + "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==" + }, + "node_modules/@types/trusted-types": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.3.tgz", + "integrity": "sha512-NfQ4gyz38SL8sDNrSixxU2Os1a5xcdFxipAFxYEuLUlvU2uDwS4NUpsImcf1//SlWItCVMMLiylsxbmNMToV/g==" + }, + "node_modules/@types/ws": { + "version": "8.5.4", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz", + "integrity": "sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/yargs": { + "version": "16.0.5", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.5.tgz", + "integrity": "sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.54.0.tgz", + "integrity": "sha512-+hSN9BdSr629RF02d7mMtXhAJvDTyCbprNYJKrXETlul/Aml6YZwd90XioVbjejQeHbb3R8Dg0CkRgoJDxo8aw==", + "dependencies": { + "@typescript-eslint/scope-manager": "5.54.0", + "@typescript-eslint/type-utils": "5.54.0", + "@typescript-eslint/utils": "5.54.0", + "debug": "^4.3.4", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "regexpp": "^3.2.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" }, "engines": { - "node": "^14.18.0 || >=16.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "vite": "^4.2.0" + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/experimental-utils": { + "version": "5.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.54.0.tgz", + "integrity": "sha512-rRYECOTh5V3iWsrOzXi7h1jp3Bi9OkJHrb3wECi3DVqMGTilo9wAYmCbT+6cGdrzUY3MWcAa2mESM6FMik6tVw==", + "dependencies": { + "@typescript-eslint/utils": "5.54.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.54.0.tgz", + "integrity": "sha512-aAVL3Mu2qTi+h/r04WI/5PfNWvO6pdhpeMRWk9R7rEV4mwJNzoWf5CCU5vDKBsPIFQFjEq1xg7XBI2rjiMXQbQ==", + "dependencies": { + "@typescript-eslint/scope-manager": "5.54.0", + "@typescript-eslint/types": "5.54.0", + "@typescript-eslint/typescript-estree": "5.54.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.54.0.tgz", + "integrity": "sha512-VTPYNZ7vaWtYna9M4oD42zENOBrb+ZYyCNdFs949GcN8Miwn37b8b7eMj+EZaq7VK9fx0Jd+JhmkhjFhvnovhg==", + "dependencies": { + "@typescript-eslint/types": "5.54.0", + "@typescript-eslint/visitor-keys": "5.54.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.54.0.tgz", + "integrity": "sha512-WI+WMJ8+oS+LyflqsD4nlXMsVdzTMYTxl16myXPaCXnSgc7LWwMsjxQFZCK/rVmTZ3FN71Ct78ehO9bRC7erYQ==", + "dependencies": { + "@typescript-eslint/typescript-estree": "5.54.0", + "@typescript-eslint/utils": "5.54.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.54.0.tgz", + "integrity": "sha512-nExy+fDCBEgqblasfeE3aQ3NuafBUxZxgxXcYfzYRZFHdVvk5q60KhCSkG0noHgHRo/xQ/BOzURLZAafFpTkmQ==", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.54.0.tgz", + "integrity": "sha512-X2rJG97Wj/VRo5YxJ8Qx26Zqf0RRKsVHd4sav8NElhbZzhpBI8jU54i6hfo9eheumj4oO4dcRN1B/zIVEqR/MQ==", + "dependencies": { + "@typescript-eslint/types": "5.54.0", + "@typescript-eslint/visitor-keys": "5.54.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.54.0.tgz", + "integrity": "sha512-cuwm8D/Z/7AuyAeJ+T0r4WZmlnlxQ8wt7C7fLpFlKMR+dY6QO79Cq1WpJhvZbMA4ZeZGHiRWnht7ZJ8qkdAunw==", + "dependencies": { + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.54.0", + "@typescript-eslint/types": "5.54.0", + "@typescript-eslint/typescript-estree": "5.54.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.54.0.tgz", + "integrity": "sha512-xu4wT7aRCakGINTLGeyGqDn+78BwFlggwBjnHa1ar/KaGagnmwLYmlrXIrgAaQ3AE1Vd6nLfKASm7LrFHNbKGA==", + "dependencies": { + "@typescript-eslint/types": "5.54.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", + "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", + "dependencies": { + "@webassemblyjs/helper-numbers": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", + "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==" + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", + "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==" + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", + "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==" + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", + "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", + "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==" + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", + "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", + "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", + "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", + "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==" + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", + "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/helper-wasm-section": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-opt": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1", + "@webassemblyjs/wast-printer": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", + "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", + "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", + "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", + "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==" + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" + }, + "node_modules/abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" } }, "node_modules/acorn": { "version": "8.8.2", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", - "dev": true, "bin": { "acorn": "bin/acorn" }, @@ -1400,20 +4519,106 @@ "node": ">=0.4.0" } }, + "node_modules/acorn-globals": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", + "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", + "dependencies": { + "acorn": "^7.1.1", + "acorn-walk": "^7.1.1" + } + }, + "node_modules/acorn-globals/node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-assertions": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", + "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", + "peerDependencies": { + "acorn": "^8" + } + }, "node_modules/acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/acorn-node": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", + "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==", + "dependencies": { + "acorn": "^7.0.0", + "acorn-walk": "^7.0.0", + "xtend": "^4.0.2" + } + }, + "node_modules/acorn-node/node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/address": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/address/-/address-1.2.2.tgz", + "integrity": "sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/adjust-sourcemap-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz", + "integrity": "sha512-OXwN5b9pCUXNQHJpwwD2qP40byEmSgzj8B4ydSN0uMNYWiFmJ6x6KwUllMmfk8Rwu/HJDFR7U8ubsWBoN0Xp0A==", + "dependencies": { + "loader-utils": "^2.0.0", + "regex-parser": "^2.2.11" + }, + "engines": { + "node": ">=8.9" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -1425,11 +4630,79 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", + "engines": [ + "node >= 0.8.0" + ], + "bin": { + "ansi-html": "bin/ansi-html" + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "engines": { "node": ">=8" } @@ -1445,39 +4718,48 @@ "node": ">=4" } }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==" + }, "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dependencies": { + "sprintf-js": "~1.0.2" + } }, "node_modules/aria-query": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", - "dev": true, "dependencies": { "deep-equal": "^2.0.5" } }, - "node_modules/array-buffer-byte-length": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", - "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "is-array-buffer": "^3.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "node_modules/array-flatten": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", + "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==" }, "node_modules/array-includes": { "version": "3.1.6", "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -1492,11 +4774,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "engines": { + "node": ">=8" + } + }, "node_modules/array.prototype.flat": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -1514,7 +4803,6 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -1528,11 +4816,28 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/array.prototype.reduce": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/array.prototype.reduce/-/array.prototype.reduce-1.0.5.tgz", + "integrity": "sha512-kDdugMl7id9COE8R7MHF5jWk7Dqt/fs4Pv+JXoICnYwqpjjjbUurz6w5fT5IG6brLdJhv6/VoHB0H7oyIBXd+Q==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-array-method-boxes-properly": "^1.0.0", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array.prototype.tosorted": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.1.tgz", "integrity": "sha512-pZYPXPRl2PqWcsUs6LOMn+1f1532nEoPTYowBtqLwAW+W8vSVhkIGnmOX1t/UQjD6YGI0vcD2B1U7ZFGQH9jnQ==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -1541,17 +4846,70 @@ "get-intrinsic": "^1.1.3" } }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" + }, "node_modules/ast-types-flow": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", - "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==", - "dev": true + "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==" + }, + "node_modules/async": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.13", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.13.tgz", + "integrity": "sha512-49vKpMqcZYsJjwotvt4+h/BCjJVnhGwcLpDt5xkcaOG3eLrG/HUYLagrihYsQ+qrIBgIzX1Rw7a6L8I/ZA1Atg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + } + ], + "dependencies": { + "browserslist": "^4.21.4", + "caniuse-lite": "^1.0.30001426", + "fraction.js": "^4.2.0", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.0", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } }, "node_modules/available-typed-arrays": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -1560,10 +4918,9 @@ } }, "node_modules/axe-core": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.7.1.tgz", - "integrity": "sha512-sCXXUhA+cljomZ3ZAwb8i1p3oOlkABzPy08ZDAoGcYuvtBPlQ1Ytde129ArXyHWDhfeewq7rlx9F+cUx2SSlkg==", - "dev": true, + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.6.3.tgz", + "integrity": "sha512-/BQzOX780JhsxDnPpH4ZiyrJAzcd8AfzFPkv+89veFSr1rcMjuq2JDCwypKaPeB6ljHp9KjXhPpjgCvQlWYuqg==", "engines": { "node": ">=4" } @@ -1572,11 +4929,159 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.1.1.tgz", "integrity": "sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg==", - "dev": true, "dependencies": { "deep-equal": "^2.0.5" } }, + "node_modules/babel-jest": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz", + "integrity": "sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg==", + "dependencies": { + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^27.5.1", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-jest/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/babel-jest/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/babel-jest/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/babel-jest/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/babel-jest/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-jest/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-loader": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.3.0.tgz", + "integrity": "sha512-H8SvsMF+m9t15HNLMipppzkC+Y2Yq+v3SonZyU70RBL/h1gxPkH08Ot8pEE9Z4Kd+czyWJClmFS8qzIP9OZ04Q==", + "dependencies": { + "find-cache-dir": "^3.3.1", + "loader-utils": "^2.0.0", + "make-dir": "^3.1.0", + "schema-utils": "^2.6.5" + }, + "engines": { + "node": ">= 8.9" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "webpack": ">=2" + } + }, + "node_modules/babel-loader/node_modules/schema-utils": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "dependencies": { + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.5.1.tgz", + "integrity": "sha512-50wCwD5EMNW4aRpOwtqzyZHIewTYNxLA4nhB+09d8BIssfNfzBRhkBIHiaPv1Si226TQSvp8gxAJm2iY2qs2hQ==", + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.0.0", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, "node_modules/babel-plugin-macros": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", @@ -1591,27 +5096,268 @@ "npm": ">=6" } }, + "node_modules/babel-plugin-named-asset-import": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.8.tgz", + "integrity": "sha512-WXiAc++qo7XcJ1ZnTYGtLxmBCVbddAml3CEXgWaBzNzLNoxtQ8AiGEFDMOhot9XjTCQbvP5E77Fj9Gk924f00Q==", + "peerDependencies": { + "@babel/core": "^7.1.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz", + "integrity": "sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==", + "dependencies": { + "@babel/compat-data": "^7.17.7", + "@babel/helper-define-polyfill-provider": "^0.3.3", + "semver": "^6.1.1" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz", + "integrity": "sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.3.3", + "core-js-compat": "^3.25.1" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz", + "integrity": "sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.3.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-transform-react-remove-prop-types": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz", + "integrity": "sha512-eqj0hVcJUR57/Ug2zE1Yswsw4LhuqqHhD+8v120T1cl3kjg76QwtyBrdIk4WVwK+lAhBJVYCd/v+4nc4y+8JsA==" + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.5.1.tgz", + "integrity": "sha512-Nptf2FzlPCWYuJg41HBqXVT8ym6bXOevuCTbhxlUpjwtysGaIWFvDEjp4y+G7fl13FgOdjs7P/DmErqH7da0Ag==", + "dependencies": { + "babel-plugin-jest-hoist": "^27.5.1", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-react-app": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-react-app/-/babel-preset-react-app-10.0.1.tgz", + "integrity": "sha512-b0D9IZ1WhhCWkrTXyFuIIgqGzSkRIH5D5AmB0bXbzYAB1OBAwHcUeyWW2LorutLWF5btNo/N7r/cIdmvvKJlYg==", + "dependencies": { + "@babel/core": "^7.16.0", + "@babel/plugin-proposal-class-properties": "^7.16.0", + "@babel/plugin-proposal-decorators": "^7.16.4", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.0", + "@babel/plugin-proposal-numeric-separator": "^7.16.0", + "@babel/plugin-proposal-optional-chaining": "^7.16.0", + "@babel/plugin-proposal-private-methods": "^7.16.0", + "@babel/plugin-transform-flow-strip-types": "^7.16.0", + "@babel/plugin-transform-react-display-name": "^7.16.0", + "@babel/plugin-transform-runtime": "^7.16.4", + "@babel/preset-env": "^7.16.4", + "@babel/preset-react": "^7.16.0", + "@babel/preset-typescript": "^7.16.0", + "@babel/runtime": "^7.16.3", + "babel-plugin-macros": "^3.1.0", + "babel-plugin-transform-react-remove-prop-types": "^0.4.24" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==" + }, + "node_modules/bfj": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/bfj/-/bfj-7.0.2.tgz", + "integrity": "sha512-+e/UqUzwmzJamNF50tBV6tZPTORow7gQ96iFow+8b562OdMpEK0BcJEq2OSPEDmAbSMBQ7PKZ87ubFkgxpYWgw==", + "dependencies": { + "bluebird": "^3.5.5", + "check-types": "^11.1.1", + "hoopy": "^0.1.4", + "tryer": "^1.0.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + }, + "node_modules/body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/bonjour-service": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.1.0.tgz", + "integrity": "sha512-LVRinRB3k1/K0XzZ2p58COnWvkQknIY6sf0zF2rpErvcJXpMBttEPQSxK+HEXSS9VmpZlDoDnQWv8ftJT20B0Q==", + "dependencies": { + "array-flatten": "^2.1.2", + "dns-equal": "^1.0.0", + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-process-hrtime": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", + "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==" + }, "node_modules/browserslist": { "version": "4.21.5", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", - "dev": true, "funding": [ { "type": "opencollective", @@ -1635,11 +5381,42 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "node_modules/builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, "dependencies": { "function-bind": "^1.1.1", "get-intrinsic": "^1.0.2" @@ -1656,11 +5433,49 @@ "node": ">=6" } }, + "node_modules/camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "dependencies": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-api": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", + "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", + "dependencies": { + "browserslist": "^4.0.0", + "caniuse-lite": "^1.0.0", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + } + }, "node_modules/caniuse-lite": { - "version": "1.0.30001489", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001489.tgz", - "integrity": "sha512-x1mgZEXK8jHIfAxm+xgdpHpk50IN3z3q3zP261/WS+uvePxW8izXuCu6AHz0lkuYTlATDehiZ/tNyYBdSQsOUQ==", - "dev": true, + "version": "1.0.30001458", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001458.tgz", + "integrity": "sha512-lQ1VlUUq5q9ro9X+5gOEyH7i3vm+AYVT1WDCVB69XOZ17KZRhnZ9J0Sqz7wTHQaLBJccNCHq8/Ww5LlOIZbB0w==", "funding": [ { "type": "opencollective", @@ -1669,13 +5484,17 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" } ] }, + "node_modules/case-sensitive-paths-webpack-plugin": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz", + "integrity": "sha512-roIFONhcxog0JSSWbvVAh3OocukmSgpqOH6YpMkCvav/ySIV3JKg4Dc8vYtQjYi/UxpNE36r/9v+VqTQqgkYmw==", + "engines": { + "node": ">=4" + } + }, "node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -1697,6 +5516,112 @@ "node": ">=0.8.0" } }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "engines": { + "node": ">=10" + } + }, + "node_modules/check-types": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/check-types/-/check-types-11.2.2.tgz", + "integrity": "sha512-HBiYvXvn9Z70Z88XKjz3AEKd4HJhBXsa3j7xFnITAzoS8+q6eIGi8qDB8FKPBAjtuxjI/zFpwuiCb8oDtKOYrA==" + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/ci-info": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", + "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", + "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==" + }, + "node_modules/clean-css": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.2.tgz", + "integrity": "sha512-JVJbM+f3d3Q704rF4bqQ5UUyTtuJ0JRKNbTKVEeujCCBoMdkEi+V+e8oktO9qGQNSvHrFTM6JZRXrUvGR1czww==", + "dependencies": { + "source-map": "~0.6.0" + }, + "engines": { + "node": ">= 10.0" + } + }, + "node_modules/clean-css/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==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, "node_modules/clsx": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", @@ -1705,6 +5630,33 @@ "node": ">=6" } }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/coa": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz", + "integrity": "sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==", + "dependencies": { + "@types/q": "^1.5.1", + "chalk": "^2.4.1", + "q": "^1.1.2" + }, + "engines": { + "node": ">= 4.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", + "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==" + }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -1718,23 +5670,191 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" }, + "node_modules/colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==" + }, + "node_modules/colorette": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", + "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "engines": { + "node": ">= 12" + } + }, + "node_modules/common-path-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", + "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==" + }, + "node_modules/common-tags": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", + "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==" + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dependencies": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/compression/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "node_modules/confusing-browser-globals": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", - "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", - "dev": true + "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==" + }, + "node_modules/connect-history-api-fallback": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } }, "node_modules/convert-source-map": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "node_modules/core-js": { + "version": "3.29.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.29.0.tgz", + "integrity": "sha512-VG23vuEisJNkGl6XQmFJd3rEG/so/CNatqeE+7uZAwTSwFeB/qaO0be8xZYUNWprJ/GIwL8aMt9cj1kvbpTZhg==", + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-compat": { + "version": "3.29.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.29.0.tgz", + "integrity": "sha512-ScMn3uZNAFhK2DGoEfErguoiAHhV2Ju+oJo/jK08p7B3f3UhocUrCCkTvnZaiS+edl5nlIoiBXKcwMc6elv4KQ==", + "dependencies": { + "browserslist": "^4.21.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-pure": { + "version": "3.29.0", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.29.0.tgz", + "integrity": "sha512-v94gUjN5UTe1n0yN/opTihJ8QBWD2O8i19RfTZR7foONPWArnjB96QA/wk5ozu1mm6ja3udQCzOzwQXTxi3xOQ==", + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, "node_modules/cosmiconfig": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", @@ -1762,7 +5882,6 @@ "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -1772,22 +5891,444 @@ "node": ">= 8" } }, + "node_modules/crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/css-blank-pseudo": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-3.0.3.tgz", + "integrity": "sha512-VS90XWtsHGqoM0t4KpH053c4ehxZ2E6HtGI7x68YFV0pTo/QmkV/YFA+NnlvK8guxZVNWGQhVNJGC39Q8XF4OQ==", + "dependencies": { + "postcss-selector-parser": "^6.0.9" + }, + "bin": { + "css-blank-pseudo": "dist/cli.cjs" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/css-declaration-sorter": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.3.1.tgz", + "integrity": "sha512-fBffmak0bPAnyqc/HO8C3n2sHrp9wcqQz6ES9koRF2/mLOVAx9zIQ3Y7R29sYCteTPqMCwns4WYQoCX91Xl3+w==", + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.0.9" + } + }, + "node_modules/css-has-pseudo": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-3.0.4.tgz", + "integrity": "sha512-Vse0xpR1K9MNlp2j5w1pgWIJtm1a8qS0JwS9goFYcImjlHEmywP9VUF05aGBXzGpDJF86QXk4L0ypBmwPhGArw==", + "dependencies": { + "postcss-selector-parser": "^6.0.9" + }, + "bin": { + "css-has-pseudo": "dist/cli.cjs" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/css-loader": { + "version": "6.7.3", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.3.tgz", + "integrity": "sha512-qhOH1KlBMnZP8FzRO6YCH9UHXQhVMcEGLyNdb7Hv2cpcmJbW0YrddO+tG1ab5nT41KpHIYGsbeHqxB9xPu1pKQ==", + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.19", + "postcss-modules-extract-imports": "^3.0.0", + "postcss-modules-local-by-default": "^4.0.0", + "postcss-modules-scope": "^3.0.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.3.8" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/css-minimizer-webpack-plugin": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-3.4.1.tgz", + "integrity": "sha512-1u6D71zeIfgngN2XNRJefc/hY7Ybsxd74Jm4qngIXyUEk7fss3VUzuHxLAq/R8NAba4QU9OUSaMZlbpRc7bM4Q==", + "dependencies": { + "cssnano": "^5.0.6", + "jest-worker": "^27.0.2", + "postcss": "^8.3.5", + "schema-utils": "^4.0.0", + "serialize-javascript": "^6.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@parcel/css": { + "optional": true + }, + "clean-css": { + "optional": true + }, + "csso": { + "optional": true + }, + "esbuild": { + "optional": true + } + } + }, + "node_modules/css-minimizer-webpack-plugin/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/css-minimizer-webpack-plugin/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/css-minimizer-webpack-plugin/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "node_modules/css-minimizer-webpack-plugin/node_modules/schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/css-minimizer-webpack-plugin/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==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/css-prefers-color-scheme": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-6.0.3.tgz", + "integrity": "sha512-4BqMbZksRkJQx2zAjrokiGMd07RqOa2IxIrrN10lyBe9xhn9DEvjUK79J6jkeiv9D9hQFXKb6g1jwU62jziJZA==", + "bin": { + "css-prefers-color-scheme": "dist/cli.cjs" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-select-base-adapter": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", + "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==" + }, + "node_modules/css-tree": { + "version": "1.0.0-alpha.37", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", + "integrity": "sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg==", + "dependencies": { + "mdn-data": "2.0.4", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/css-tree/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==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssdb": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-7.4.1.tgz", + "integrity": "sha512-0Q8NOMpXJ3iTDDbUv9grcmQAfdDx4qz+fN/+Md2FGbevT+6+bJNQ2LjB2YIUlLbpBTM32idU1Sb+tb/uGt6/XQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssnano": { + "version": "5.1.15", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-5.1.15.tgz", + "integrity": "sha512-j+BKgDcLDQA+eDifLx0EO4XSA56b7uut3BQFH+wbSaSTuGLuiyTa/wbRYthUXX8LC9mLg+WWKe8h+qJuwTAbHw==", + "dependencies": { + "cssnano-preset-default": "^5.2.14", + "lilconfig": "^2.0.3", + "yaml": "^1.10.2" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/cssnano" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/cssnano-preset-default": { + "version": "5.2.14", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-5.2.14.tgz", + "integrity": "sha512-t0SFesj/ZV2OTylqQVOrFgEh5uanxbO6ZAdeCrNsUQ6fVuXwYTxJPNAGvGTxHbD68ldIJNec7PyYZDBrfDQ+6A==", + "dependencies": { + "css-declaration-sorter": "^6.3.1", + "cssnano-utils": "^3.1.0", + "postcss-calc": "^8.2.3", + "postcss-colormin": "^5.3.1", + "postcss-convert-values": "^5.1.3", + "postcss-discard-comments": "^5.1.2", + "postcss-discard-duplicates": "^5.1.0", + "postcss-discard-empty": "^5.1.1", + "postcss-discard-overridden": "^5.1.0", + "postcss-merge-longhand": "^5.1.7", + "postcss-merge-rules": "^5.1.4", + "postcss-minify-font-values": "^5.1.0", + "postcss-minify-gradients": "^5.1.1", + "postcss-minify-params": "^5.1.4", + "postcss-minify-selectors": "^5.2.1", + "postcss-normalize-charset": "^5.1.0", + "postcss-normalize-display-values": "^5.1.0", + "postcss-normalize-positions": "^5.1.1", + "postcss-normalize-repeat-style": "^5.1.1", + "postcss-normalize-string": "^5.1.0", + "postcss-normalize-timing-functions": "^5.1.0", + "postcss-normalize-unicode": "^5.1.1", + "postcss-normalize-url": "^5.1.0", + "postcss-normalize-whitespace": "^5.1.1", + "postcss-ordered-values": "^5.1.3", + "postcss-reduce-initial": "^5.1.2", + "postcss-reduce-transforms": "^5.1.0", + "postcss-svgo": "^5.1.0", + "postcss-unique-selectors": "^5.1.1" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/cssnano-utils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-3.1.0.tgz", + "integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/csso": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", + "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", + "dependencies": { + "css-tree": "^1.1.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/csso/node_modules/css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "dependencies": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==" + }, + "node_modules/csso/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==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/cssom": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", + "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==" + }, + "node_modules/cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dependencies": { + "cssom": "~0.3.6" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cssstyle/node_modules/cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==" + }, "node_modules/csstype": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", - "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", + "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==" }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", - "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", - "dev": true + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==" + }, + "node_modules/data-urls": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", + "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", + "dependencies": { + "abab": "^2.0.3", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/data-urls/node_modules/tr46": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", + "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/data-urls/node_modules/whatwg-url": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", + "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", + "dependencies": { + "lodash": "^4.7.0", + "tr46": "^2.1.0", + "webidl-conversions": "^6.1.0" + }, + "engines": { + "node": ">=10" + } }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -1800,18 +6341,26 @@ } } }, + "node_modules/decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==" + }, + "node_modules/dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==" + }, "node_modules/deep-equal": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.1.tgz", - "integrity": "sha512-lKdkdV6EOGoVn65XaOsPdH4rMxTZOnmFyuIkMjM1i5HHCbfjC97dawgTAy0deYNfuqUqW+Q5VrVaQYtUpSd6yQ==", - "dev": true, + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.0.tgz", + "integrity": "sha512-RdpzE0Hv4lhowpIUKKMJfeH6C1pXdtT1/it80ubgWqwI3qpuxUBpC1S4hnHg+zjnuOoDkzUtUCEEkG+XG5l3Mw==", "dependencies": { - "array-buffer-byte-length": "^1.0.0", "call-bind": "^1.0.2", - "es-get-iterator": "^1.1.3", - "get-intrinsic": "^1.2.0", + "es-get-iterator": "^1.1.2", + "get-intrinsic": "^1.1.3", "is-arguments": "^1.1.1", - "is-array-buffer": "^3.0.2", + "is-array-buffer": "^3.0.1", "is-date-object": "^1.0.5", "is-regex": "^1.1.4", "is-shared-array-buffer": "^1.0.2", @@ -1819,7 +6368,7 @@ "object-is": "^1.1.5", "object-keys": "^1.1.1", "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.5.0", + "regexp.prototype.flags": "^1.4.3", "side-channel": "^1.0.4", "which-boxed-primitive": "^1.0.2", "which-collection": "^1.0.1", @@ -1832,14 +6381,39 @@ "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" + }, + "node_modules/deepmerge": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.0.tgz", + "integrity": "sha512-z2wJZXrmeHdvYJp/Ux55wIjqo81G5Bp4c+oELTW+7ar6SogWHajt5a9gO3s3IDaGSAXjDk0vlQKN3rms8ab3og==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-gateway": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", + "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", + "dependencies": { + "execa": "^5.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "engines": { + "node": ">=8" + } }, "node_modules/define-properties": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", - "dev": true, "dependencies": { "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" @@ -1851,6 +6425,97 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/defined": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.1.tgz", + "integrity": "sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==" + }, + "node_modules/detect-port-alt": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/detect-port-alt/-/detect-port-alt-1.1.6.tgz", + "integrity": "sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q==", + "dependencies": { + "address": "^1.0.1", + "debug": "^2.6.0" + }, + "bin": { + "detect": "bin/detect-port", + "detect-port": "bin/detect-port" + }, + "engines": { + "node": ">= 4.2.1" + } + }, + "node_modules/detect-port-alt/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/detect-port-alt/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/detective": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.1.tgz", + "integrity": "sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==", + "dependencies": { + "acorn-node": "^1.8.2", + "defined": "^1.0.0", + "minimist": "^1.2.6" + }, + "bin": { + "detective": "bin/detective.js" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/dexie": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/dexie/-/dexie-3.2.3.tgz", @@ -1860,20 +6525,64 @@ } }, "node_modules/dexie-react-hooks": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/dexie-react-hooks/-/dexie-react-hooks-1.1.3.tgz", - "integrity": "sha512-bXXE1gfYtfuVYTNiOlyam+YVaO8KaqacgRuxFuP37YtpS6o/jxT6KOl5h+hhqY36s0UavlHWbL+HWJFMcQumIg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/dexie-react-hooks/-/dexie-react-hooks-1.1.1.tgz", + "integrity": "sha512-Cam5JP6PxHN564RvWEoe8cqLhosW0O4CAZ9XEVYeGHJBa6KEJlOpd9CUpV3kmU9dm2MrW97/lk7qkf1xpij7gA==", "peerDependencies": { "@types/react": ">=16", "dexie": ">=3.1.0-alpha.1 <5.0.0", "react": ">=16" } }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==" + }, + "node_modules/diff-sequences": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", + "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==", + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" + }, + "node_modules/dns-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", + "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==" + }, + "node_modules/dns-packet": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.4.0.tgz", + "integrity": "sha512-EgqGeaBB8hLiHLZtp/IbaDQTL8pZ0+IvwzSHA6d7VyMDM+B9hgddEMa9xjK5oYnw0ci0JQ6g2XCD7/f6cafU6g==", + "dependencies": { + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, "dependencies": { "esutils": "^2.0.2" }, @@ -1881,6 +6590,14 @@ "node": ">=6.0.0" } }, + "node_modules/dom-converter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", + "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", + "dependencies": { + "utila": "~0.4" + } + }, "node_modules/dom-helpers": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", @@ -1890,17 +6607,178 @@ "csstype": "^3.0.2" } }, + "node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domexception": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", + "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", + "dependencies": { + "webidl-conversions": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/domexception/node_modules/webidl-conversions": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", + "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/dotenv": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", + "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", + "engines": { + "node": ">=10" + } + }, + "node_modules/dotenv-expand": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz", + "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==" + }, + "node_modules/duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==" + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/ejs": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.8.tgz", + "integrity": "sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ==", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/electron-to-chromium": { - "version": "1.4.407", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.407.tgz", - "integrity": "sha512-5smEvFSFYMv90tICOzRVP7Opp98DAC4KW7RRipg3BuNpGbbV3N+x24Zh3sbLb1T5haGtOSy/hrBfXsWnIM9aCg==", - "dev": true + "version": "1.4.314", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.314.tgz", + "integrity": "sha512-+3RmNVx9hZLlc0gW//4yep0K5SYKmIvB5DXg1Yg6varsuAHlHwTeqeygfS8DWwLCsNOWrgj+p9qgM5WYjw1lXQ==" + }, + "node_modules/emittery": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz", + "integrity": "sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } }, "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.12.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz", + "integrity": "sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } }, "node_modules/error-ex": { "version": "1.3.2", @@ -1919,18 +6797,17 @@ } }, "node_modules/es-abstract": { - "version": "1.21.2", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz", - "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==", - "dev": true, + "version": "1.21.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.1.tgz", + "integrity": "sha512-QudMsPOz86xYz/1dG1OuGBKOELjCh99IIWHLzy5znUB6j8xG2yMA7bfTV86VSqKF+Y/H08vQPR+9jyXpuC6hfg==", "dependencies": { - "array-buffer-byte-length": "^1.0.0", "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", "es-set-tostringtag": "^2.0.1", "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", "function.prototype.name": "^1.1.5", - "get-intrinsic": "^1.2.0", + "get-intrinsic": "^1.1.3", "get-symbol-description": "^1.0.0", "globalthis": "^1.0.3", "gopd": "^1.0.1", @@ -1938,8 +6815,8 @@ "has-property-descriptors": "^1.0.0", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", - "internal-slot": "^1.0.5", - "is-array-buffer": "^3.0.2", + "internal-slot": "^1.0.4", + "is-array-buffer": "^3.0.1", "is-callable": "^1.2.7", "is-negative-zero": "^2.0.2", "is-regex": "^1.1.4", @@ -1947,12 +6824,11 @@ "is-string": "^1.0.7", "is-typed-array": "^1.1.10", "is-weakref": "^1.0.2", - "object-inspect": "^1.12.3", + "object-inspect": "^1.12.2", "object-keys": "^1.1.1", "object.assign": "^4.1.4", "regexp.prototype.flags": "^1.4.3", "safe-regex-test": "^1.0.0", - "string.prototype.trim": "^1.2.7", "string.prototype.trimend": "^1.0.6", "string.prototype.trimstart": "^1.0.6", "typed-array-length": "^1.0.4", @@ -1966,11 +6842,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-array-method-boxes-properly": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", + "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==" + }, "node_modules/es-get-iterator": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.1.3", @@ -1986,11 +6866,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-module-lexer": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", + "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==" + }, "node_modules/es-set-tostringtag": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", - "dev": true, "dependencies": { "get-intrinsic": "^1.1.3", "has": "^1.0.3", @@ -2004,7 +6888,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", - "dev": true, "dependencies": { "has": "^1.0.3" } @@ -2013,7 +6896,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, "dependencies": { "is-callable": "^1.1.4", "is-date-object": "^1.0.1", @@ -2026,52 +6908,19 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/esbuild": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz", - "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==", - "dev": true, - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/android-arm": "0.17.19", - "@esbuild/android-arm64": "0.17.19", - "@esbuild/android-x64": "0.17.19", - "@esbuild/darwin-arm64": "0.17.19", - "@esbuild/darwin-x64": "0.17.19", - "@esbuild/freebsd-arm64": "0.17.19", - "@esbuild/freebsd-x64": "0.17.19", - "@esbuild/linux-arm": "0.17.19", - "@esbuild/linux-arm64": "0.17.19", - "@esbuild/linux-ia32": "0.17.19", - "@esbuild/linux-loong64": "0.17.19", - "@esbuild/linux-mips64el": "0.17.19", - "@esbuild/linux-ppc64": "0.17.19", - "@esbuild/linux-riscv64": "0.17.19", - "@esbuild/linux-s390x": "0.17.19", - "@esbuild/linux-x64": "0.17.19", - "@esbuild/netbsd-x64": "0.17.19", - "@esbuild/openbsd-x64": "0.17.19", - "@esbuild/sunos-x64": "0.17.19", - "@esbuild/win32-arm64": "0.17.19", - "@esbuild/win32-ia32": "0.17.19", - "@esbuild/win32-x64": "0.17.19" - } - }, "node_modules/escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, "engines": { "node": ">=6" } }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -2083,16 +6932,90 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint": { - "version": "8.41.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.41.0.tgz", - "integrity": "sha512-WQDQpzGBOP5IrXPo4Hc0814r4/v2rrIsB0rhT7jtunIalgg6gYXWhRMOejVO8yH21T/FGaxjmFjBMNqcIlmH1Q==", - "dev": true, + "node_modules/escodegen": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", + "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.0.3", - "@eslint/js": "8.41.0", + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/escodegen/node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "dependencies": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dependencies": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/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==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/escodegen/node_modules/type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/eslint": { + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.35.0.tgz", + "integrity": "sha512-BxAf1fVL7w+JLRQhWl2pzGeSiGqbWumV4WNvc9Rhp6tiCtm4oHnyPBSEtMGZwrQgudFQ+otqzWoPB7x+hxoWsw==", + "dependencies": { + "@eslint/eslintrc": "^2.0.0", + "@eslint/js": "8.35.0", "@humanwhocodes/config-array": "^0.11.8", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -2102,9 +7025,10 @@ "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.0", - "eslint-visitor-keys": "^3.4.1", - "espree": "^9.5.2", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.4.0", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -2112,12 +7036,13 @@ "find-up": "^5.0.0", "glob-parent": "^6.0.2", "globals": "^13.19.0", - "graphemer": "^1.4.0", + "grapheme-splitter": "^1.0.4", "ignore": "^5.2.0", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", + "js-sdsl": "^4.1.4", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", @@ -2125,6 +7050,7 @@ "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.1", + "regexpp": "^3.2.0", "strip-ansi": "^6.0.1", "strip-json-comments": "^3.1.0", "text-table": "^0.2.0" @@ -2139,63 +7065,37 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint-config-airbnb": { - "version": "19.0.4", - "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-19.0.4.tgz", - "integrity": "sha512-T75QYQVQX57jiNgpF9r1KegMICE94VYwoFQyMGhrvc+lB8YF2E/M/PYDaQe1AJcWaEgqLE+ErXV1Og/+6Vyzew==", - "dev": true, + "node_modules/eslint-config-react-app": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-7.0.1.tgz", + "integrity": "sha512-K6rNzvkIeHaTd8m/QEh1Zko0KI7BACWkkneSs6s9cKZC/J27X3eZR6Upt1jkmZ/4FK+XUOPPxMEN7+lbUXfSlA==", "dependencies": { - "eslint-config-airbnb-base": "^15.0.0", - "object.assign": "^4.1.2", - "object.entries": "^1.1.5" - }, - "engines": { - "node": "^10.12.0 || ^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": "^7.32.0 || ^8.2.0", + "@babel/core": "^7.16.0", + "@babel/eslint-parser": "^7.16.3", + "@rushstack/eslint-patch": "^1.1.0", + "@typescript-eslint/eslint-plugin": "^5.5.0", + "@typescript-eslint/parser": "^5.5.0", + "babel-preset-react-app": "^10.0.1", + "confusing-browser-globals": "^1.0.11", + "eslint-plugin-flowtype": "^8.0.3", "eslint-plugin-import": "^2.25.3", + "eslint-plugin-jest": "^25.3.0", "eslint-plugin-jsx-a11y": "^6.5.1", - "eslint-plugin-react": "^7.28.0", - "eslint-plugin-react-hooks": "^4.3.0" - } - }, - "node_modules/eslint-config-airbnb-base": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz", - "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==", - "dev": true, - "dependencies": { - "confusing-browser-globals": "^1.0.10", - "object.assign": "^4.1.2", - "object.entries": "^1.1.5", - "semver": "^6.3.0" + "eslint-plugin-react": "^7.27.1", + "eslint-plugin-react-hooks": "^4.3.0", + "eslint-plugin-testing-library": "^5.0.1" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=14.0.0" }, "peerDependencies": { - "eslint": "^7.32.0 || ^8.2.0", - "eslint-plugin-import": "^2.25.2" - } - }, - "node_modules/eslint-config-prettier": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.8.0.tgz", - "integrity": "sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA==", - "dev": true, - "bin": { - "eslint-config-prettier": "bin/cli.js" - }, - "peerDependencies": { - "eslint": ">=7.0.0" + "eslint": "^8.0.0" } }, "node_modules/eslint-import-resolver-node": { "version": "0.3.7", "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz", "integrity": "sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==", - "dev": true, "dependencies": { "debug": "^3.2.7", "is-core-module": "^2.11.0", @@ -2206,16 +7106,14 @@ "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, "dependencies": { "ms": "^2.1.1" } }, "node_modules/eslint-module-utils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", - "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==", - "dev": true, + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz", + "integrity": "sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==", "dependencies": { "debug": "^3.2.7" }, @@ -2232,16 +7130,31 @@ "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, "dependencies": { "ms": "^2.1.1" } }, + "node_modules/eslint-plugin-flowtype": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-flowtype/-/eslint-plugin-flowtype-8.0.3.tgz", + "integrity": "sha512-dX8l6qUL6O+fYPtpNRideCFSpmWOUVx5QcaGLVqe/vlDiBSe4vYljDWDETwnyFzpl7By/WVIu6rcrniCgH9BqQ==", + "dependencies": { + "lodash": "^4.17.21", + "string-natural-compare": "^3.0.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@babel/plugin-syntax-flow": "^7.14.5", + "@babel/plugin-transform-react-jsx": "^7.14.9", + "eslint": "^8.1.0" + } + }, "node_modules/eslint-plugin-import": { "version": "2.27.5", "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz", "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==", - "dev": true, "dependencies": { "array-includes": "^3.1.6", "array.prototype.flat": "^1.3.1", @@ -2270,7 +7183,6 @@ "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, "dependencies": { "ms": "^2.1.1" } @@ -2279,7 +7191,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, "dependencies": { "esutils": "^2.0.2" }, @@ -2287,11 +7198,41 @@ "node": ">=0.10.0" } }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-jest": { + "version": "25.7.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-25.7.0.tgz", + "integrity": "sha512-PWLUEXeeF7C9QGKqvdSbzLOiLTx+bno7/HC9eefePfEb257QFHg7ye3dh80AZVkaa/RQsBB1Q/ORQvg2X7F0NQ==", + "dependencies": { + "@typescript-eslint/experimental-utils": "^5.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^4.0.0 || ^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + }, + "jest": { + "optional": true + } + } + }, "node_modules/eslint-plugin-jsx-a11y": { "version": "6.7.1", "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.7.1.tgz", "integrity": "sha512-63Bog4iIethyo8smBklORknVjB0T2dwB8Mr/hIC+fBS0uyHdYYpzM/Ed+YC8VxTjlXHEWFOdmgwcDn1U2L9VCA==", - "dev": true, "dependencies": { "@babel/runtime": "^7.20.7", "aria-query": "^5.1.3", @@ -2317,11 +7258,18 @@ "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" } }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/eslint-plugin-react": { "version": "7.32.2", "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.32.2.tgz", "integrity": "sha512-t2fBMa+XzonrrNkyVirzKlvn5RXzzPwRHtMvLAtVZrt8oxgnTQaYbU6SXTOO1mwQgp1y5+toMSKInnzGr0Knqg==", - "dev": true, "dependencies": { "array-includes": "^3.1.6", "array.prototype.flatmap": "^1.3.1", @@ -2350,7 +7298,6 @@ "version": "4.6.0", "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", - "dev": true, "engines": { "node": ">=10" }, @@ -2362,7 +7309,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, "dependencies": { "esutils": "^2.0.2" }, @@ -2374,7 +7320,6 @@ "version": "2.0.0-next.4", "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz", "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==", - "dev": true, "dependencies": { "is-core-module": "^2.9.0", "path-parse": "^1.0.7", @@ -2387,39 +7332,185 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-testing-library": { + "version": "5.10.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-testing-library/-/eslint-plugin-testing-library-5.10.2.tgz", + "integrity": "sha512-f1DmDWcz5SDM+IpCkEX0lbFqrrTs8HRsEElzDEqN/EBI0hpRj8Cns5+IVANXswE8/LeybIJqPAOQIFu2j5Y5sw==", + "dependencies": { + "@typescript-eslint/utils": "^5.43.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0", + "npm": ">=6" + }, + "peerDependencies": { + "eslint": "^7.5.0 || ^8.0.0" + } + }, "node_modules/eslint-scope": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", - "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", - "dev": true, + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "engines": { + "node": ">=10" } }, "node_modules/eslint-visitor-keys": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", - "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", - "dev": true, + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint-webpack-plugin": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/eslint-webpack-plugin/-/eslint-webpack-plugin-3.2.0.tgz", + "integrity": "sha512-avrKcGncpPbPSUHX6B3stNGzkKFto3eL+DKM4+VyMrVnhPc3vRczVlCq3uhuFOdRvDHTVXuzwk1ZKUrqDQHQ9w==", + "dependencies": { + "@types/eslint": "^7.29.0 || ^8.4.1", + "jest-worker": "^28.0.2", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 12.13.0" }, "funding": { - "url": "https://opencollective.com/eslint" + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0", + "webpack": "^5.0.0" + } + }, + "node_modules/eslint-webpack-plugin/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint-webpack-plugin/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/eslint-webpack-plugin/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint-webpack-plugin/node_modules/jest-worker": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-28.1.3.tgz", + "integrity": "sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g==", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/eslint-webpack-plugin/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "node_modules/eslint-webpack-plugin/node_modules/schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/eslint-webpack-plugin/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, "node_modules/eslint/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -2430,11 +7521,15 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/eslint/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, "node_modules/eslint/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -2450,7 +7545,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -2461,14 +7555,12 @@ "node_modules/eslint/node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "node_modules/eslint/node_modules/globals": { "version": "13.20.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", - "dev": true, "dependencies": { "type-fest": "^0.20.2" }, @@ -2483,16 +7575,25 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "engines": { "node": ">=8" } }, + "node_modules/eslint/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/eslint/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -2500,15 +7601,25 @@ "node": ">=8" } }, + "node_modules/eslint/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/espree": { - "version": "9.5.2", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz", - "integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==", - "dev": true, + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", + "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", "dependencies": { "acorn": "^8.8.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" + "eslint-visitor-keys": "^3.3.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -2517,11 +7628,22 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", - "dev": true, + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.2.tgz", + "integrity": "sha512-JVSoLdTlTDkmjFmab7H/9SL9qGSyjElT3myyKp7krqjVFQCDLmj1QFaCLRFBszBKI0XVZaiiXvuPIX3ZwHe1Ng==", "dependencies": { "estraverse": "^5.1.0" }, @@ -2533,7 +7655,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, "dependencies": { "estraverse": "^5.2.0" }, @@ -2545,52 +7666,219 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, "engines": { "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", + "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==" + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, "engines": { "node": ">=0.10.0" } }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/expect/-/expect-27.5.1.tgz", + "integrity": "sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw==", + "dependencies": { + "@jest/types": "^27.5.1", + "jest-get-type": "^27.5.1", + "jest-matcher-utils": "^27.5.1", + "jest-message-util": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" }, "node_modules/fastq": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, "dependencies": { "reusify": "^1.0.4" } }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dependencies": { + "bser": "2.1.1" + } + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, "dependencies": { "flat-cache": "^3.0.4" }, @@ -2598,6 +7886,117 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/file-loader": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz", + "integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==", + "dependencies": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/filesize": { + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-8.0.7.tgz", + "integrity": "sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, "node_modules/find-root": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", @@ -2607,7 +8006,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -2623,7 +8021,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, "dependencies": { "flatted": "^3.1.0", "rimraf": "^3.0.2" @@ -2635,29 +8032,259 @@ "node_modules/flatted": { "version": "3.2.7", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", - "dev": true + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==" + }, + "node_modules/follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dev": true, "dependencies": { "is-callable": "^1.1.3" } }, + "node_modules/fork-ts-checker-webpack-plugin": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.2.tgz", + "integrity": "sha512-m5cUmF30xkZ7h4tWUgTAcEaKmUW7tfyUyTqNNOz7OxWJ0v1VWKTcOvH8FWHUwSjlW/356Ijc9vi3XfcPstpQKA==", + "dependencies": { + "@babel/code-frame": "^7.8.3", + "@types/json-schema": "^7.0.5", + "chalk": "^4.1.0", + "chokidar": "^3.4.2", + "cosmiconfig": "^6.0.0", + "deepmerge": "^4.2.2", + "fs-extra": "^9.0.0", + "glob": "^7.1.6", + "memfs": "^3.1.2", + "minimatch": "^3.0.4", + "schema-utils": "2.7.0", + "semver": "^7.3.2", + "tapable": "^1.0.0" + }, + "engines": { + "node": ">=10", + "yarn": ">=1.0.0" + }, + "peerDependencies": { + "eslint": ">= 6", + "typescript": ">= 2.7", + "vue-template-compiler": "*", + "webpack": ">= 4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + }, + "vue-template-compiler": { + "optional": true + } + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/cosmiconfig": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", + "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.7.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/schema-utils": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", + "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", + "dependencies": { + "@types/json-schema": "^7.0.4", + "ajv": "^6.12.2", + "ajv-keywords": "^3.4.1" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/tapable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", + "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fraction.js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", + "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://www.patreon.com/infusion" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fs-monkey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.3.tgz", + "integrity": "sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q==" + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "node_modules/fsevents": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, "hasInstallScript": true, "optional": true, "os": [ @@ -2676,7 +8303,6 @@ "version": "1.1.5", "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.3", @@ -2694,7 +8320,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -2703,31 +8328,59 @@ "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, "engines": { "node": ">=6.9.0" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, "node_modules/get-intrinsic": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", - "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", - "dev": true, + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", + "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", "dependencies": { "function-bind": "^1.1.1", "has": "^1.0.3", - "has-proto": "^1.0.1", "has-symbols": "^1.0.3" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-own-enumerable-property-symbols": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", + "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==" + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-symbol-description": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.1.1" @@ -2743,7 +8396,6 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -2763,7 +8415,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, "dependencies": { "is-glob": "^4.0.3" }, @@ -2771,11 +8422,50 @@ "node": ">=10.13.0" } }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" + }, + "node_modules/global-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", + "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", + "dependencies": { + "global-prefix": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "dependencies": { + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/global-prefix/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, "node_modules/globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, "engines": { "node": ">=4" } @@ -2784,7 +8474,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", - "dev": true, "dependencies": { "define-properties": "^1.1.3" }, @@ -2795,11 +8484,29 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, "dependencies": { "get-intrinsic": "^1.1.3" }, @@ -2807,11 +8514,39 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true + "node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" + }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==" + }, + "node_modules/gzip-size": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", + "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", + "dependencies": { + "duplexer": "^0.1.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==" + }, + "node_modules/harmony-reflect": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/harmony-reflect/-/harmony-reflect-1.6.2.tgz", + "integrity": "sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g==" }, "node_modules/has": { "version": "1.0.3", @@ -2828,7 +8563,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -2845,7 +8579,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", - "dev": true, "dependencies": { "get-intrinsic": "^1.1.1" }, @@ -2857,7 +8590,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -2869,7 +8601,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -2881,7 +8612,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dev": true, "dependencies": { "has-symbols": "^1.0.2" }, @@ -2892,6 +8622,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "bin": { + "he": "bin/he" + } + }, "node_modules/hoist-non-react-statics": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", @@ -2905,6 +8643,98 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/hoopy": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", + "integrity": "sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ==", + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", + "dependencies": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "node_modules/hpack.js/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "node_modules/hpack.js/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/hpack.js/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/hpack.js/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", + "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", + "dependencies": { + "whatwg-encoding": "^1.0.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/html-entities": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz", + "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==" + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==" + }, + "node_modules/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", + "dependencies": { + "camel-case": "^4.1.2", + "clean-css": "^5.2.2", + "commander": "^8.3.0", + "he": "^1.2.0", + "param-case": "^3.0.4", + "relateurl": "^0.2.7", + "terser": "^5.10.0" + }, + "bin": { + "html-minifier-terser": "cli.js" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/html-parse-stringify": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", @@ -2913,6 +8743,140 @@ "void-elements": "3.1.0" } }, + "node_modules/html-webpack-plugin": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz", + "integrity": "sha512-sy88PC2cRTVxvETRgUHFrL4No3UxvcH8G1NepGhqaTT+GXN2kTamqasot0inS5hXeg1cMbFDt27zzo9p35lZVw==", + "dependencies": { + "@types/html-minifier-terser": "^6.0.0", + "html-minifier-terser": "^6.0.2", + "lodash": "^4.17.21", + "pretty-error": "^4.0.0", + "tapable": "^2.0.0" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/html-webpack-plugin" + }, + "peerDependencies": { + "webpack": "^5.20.0" + } + }, + "node_modules/htmlparser2": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", + "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "domutils": "^2.5.2", + "entities": "^2.0.0" + } + }, + "node_modules/http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==" + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==" + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "dependencies": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/http-proxy-middleware": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", + "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "dependencies": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "engines": { + "node": ">=10.17.0" + } + }, "node_modules/humanize-duration": { "version": "3.28.0", "resolved": "https://registry.npmjs.org/humanize-duration/-/humanize-duration-3.28.0.tgz", @@ -2956,15 +8920,61 @@ "cross-fetch": "3.1.5" } }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/idb": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", + "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==" + }, + "node_modules/identity-obj-proxy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz", + "integrity": "sha512-00n6YnVHKrinT9t0d9+5yZC6UBNJANpYEQvL2LlX6Ab9lnmxzIRcEmTPuyGScvl1+jKuCICX1Z0Ab1pPKKdikA==", + "dependencies": { + "harmony-reflect": "^1.4.6" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/ignore": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", - "dev": true, "engines": { "node": ">= 4" } }, + "node_modules/immer": { + "version": "9.0.19", + "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.19.tgz", + "integrity": "sha512-eY+Y0qcsB4TZKwgQzLaE/lqYMlKhv5J9dyd2RhhtGhNo2njPXDqU9XPfcNfa3MIDsdtZt5KlkIsirlo4dHsWdQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -2980,11 +8990,28 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, "engines": { "node": ">=0.8.19" } @@ -2993,7 +9020,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -3002,14 +9028,17 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, "node_modules/internal-slot": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", - "dev": true, "dependencies": { "get-intrinsic": "^1.2.0", "has": "^1.0.3", @@ -3019,11 +9048,18 @@ "node": ">= 0.4" } }, + "node_modules/ipaddr.js": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz", + "integrity": "sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==", + "engines": { + "node": ">= 10" + } + }, "node_modules/is-arguments": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -3036,13 +9072,12 @@ } }, "node_modules/is-array-buffer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", - "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", - "dev": true, + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.1.tgz", + "integrity": "sha512-ASfLknmY8Xa2XtB4wmbz13Wu202baeA18cJBCeCy0wXUHZF0IPyVEXqKEcd+t2fNSLLL1vC6k7lxZEojNbISXQ==", "dependencies": { "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.0", + "get-intrinsic": "^1.1.3", "is-typed-array": "^1.1.10" }, "funding": { @@ -3058,7 +9093,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, "dependencies": { "has-bigints": "^1.0.1" }, @@ -3066,11 +9100,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/is-boolean-object": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -3086,7 +9130,6 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -3095,9 +9138,9 @@ } }, "node_modules/is-core-module": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", - "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", "dependencies": { "has": "^1.0.3" }, @@ -3109,7 +9152,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dev": true, "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -3120,20 +9162,48 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, "engines": { "node": ">=0.10.0" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "engines": { + "node": ">=6" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "dependencies": { "is-extglob": "^2.1.1" }, @@ -3145,16 +9215,19 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==" + }, "node_modules/is-negative-zero": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -3162,11 +9235,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, "node_modules/is-number-object": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", - "dev": true, "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -3177,20 +9257,42 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-path-inside": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, "engines": { "node": ">=8" } }, + "node_modules/is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==" + }, "node_modules/is-regex": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -3202,11 +9304,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", + "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-root": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-root/-/is-root-2.1.0.tgz", + "integrity": "sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg==", + "engines": { + "node": ">=6" + } + }, "node_modules/is-set": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -3215,7 +9332,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2" }, @@ -3223,11 +9339,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-string": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -3242,7 +9368,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, "dependencies": { "has-symbols": "^1.0.2" }, @@ -3257,7 +9382,6 @@ "version": "1.1.10", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", - "dev": true, "dependencies": { "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", @@ -3272,11 +9396,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" + }, "node_modules/is-weakmap": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -3285,7 +9413,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", - "dev": true, "dependencies": { "call-bind": "^1.0.2" }, @@ -3297,7 +9424,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.1.1" @@ -3306,45 +9432,2180 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps/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==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", + "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jake": { + "version": "10.8.5", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.5.tgz", + "integrity": "sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==", + "dependencies": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.1", + "minimatch": "^3.0.4" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jake/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jake/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jake/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jake/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/jake/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jake/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest/-/jest-27.5.1.tgz", + "integrity": "sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ==", + "dependencies": { + "@jest/core": "^27.5.1", + "import-local": "^3.0.2", + "jest-cli": "^27.5.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.5.1.tgz", + "integrity": "sha512-buBLMiByfWGCoMsLLzGUUSpAmIAGnbR2KJoMN10ziLhOLvP4e0SlypHnAel8iqQXTrcbmfEY9sSqae5sgUsTvw==", + "dependencies": { + "@jest/types": "^27.5.1", + "execa": "^5.0.0", + "throat": "^6.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-circus": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-27.5.1.tgz", + "integrity": "sha512-D95R7x5UtlMA5iBYsOHFFbMD/GVA4R/Kdq15f7xYWUfWHBto9NYRsOvnSauTgdF+ogCpJ4tyKOXhUifxS65gdw==", + "dependencies": { + "@jest/environment": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^0.7.0", + "expect": "^27.5.1", + "is-generator-fn": "^2.0.0", + "jest-each": "^27.5.1", + "jest-matcher-utils": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-runtime": "^27.5.1", + "jest-snapshot": "^27.5.1", + "jest-util": "^27.5.1", + "pretty-format": "^27.5.1", + "slash": "^3.0.0", + "stack-utils": "^2.0.3", + "throat": "^6.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-circus/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-circus/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-circus/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-circus/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/jest-circus/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-circus/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-cli": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-27.5.1.tgz", + "integrity": "sha512-Hc6HOOwYq4/74/c62dEE3r5elx8wjYqxY0r0G/nFrLDPMFRu6RA/u8qINOIkvhxG7mMQ5EJsOGfRpI8L6eFUVw==", + "dependencies": { + "@jest/core": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/types": "^27.5.1", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "import-local": "^3.0.2", + "jest-config": "^27.5.1", + "jest-util": "^27.5.1", + "jest-validate": "^27.5.1", + "prompts": "^2.0.1", + "yargs": "^16.2.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-cli/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-cli/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-cli/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-cli/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/jest-cli/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-cli/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-config": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-27.5.1.tgz", + "integrity": "sha512-5sAsjm6tGdsVbW9ahcChPAFCk4IlkQUknH5AvKjuLTSlcO/wCZKyFdn7Rg0EkC+OGgWODEy2hDpWB1PgzH0JNA==", + "dependencies": { + "@babel/core": "^7.8.0", + "@jest/test-sequencer": "^27.5.1", + "@jest/types": "^27.5.1", + "babel-jest": "^27.5.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.1", + "graceful-fs": "^4.2.9", + "jest-circus": "^27.5.1", + "jest-environment-jsdom": "^27.5.1", + "jest-environment-node": "^27.5.1", + "jest-get-type": "^27.5.1", + "jest-jasmine2": "^27.5.1", + "jest-regex-util": "^27.5.1", + "jest-resolve": "^27.5.1", + "jest-runner": "^27.5.1", + "jest-util": "^27.5.1", + "jest-validate": "^27.5.1", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^27.5.1", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-config/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-config/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-config/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-config/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/jest-config/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-config/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-diff": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", + "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^27.5.1", + "jest-get-type": "^27.5.1", + "pretty-format": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-diff/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-diff/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/jest-diff/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-diff/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-docblock": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.5.1.tgz", + "integrity": "sha512-rl7hlABeTsRYxKiUfpHrQrG4e2obOiTQWfMEH3PxPjOtdsfLQO4ReWSZaQ7DETm4xu07rl4q/h4zcKXyU0/OzQ==", + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-each": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-27.5.1.tgz", + "integrity": "sha512-1Ff6p+FbhT/bXQnEouYy00bkNSY7OUpfIcmdl8vZ31A1UUaurOLPA8a8BbJOF2RDUElwJhmeaV7LnagI+5UwNQ==", + "dependencies": { + "@jest/types": "^27.5.1", + "chalk": "^4.0.0", + "jest-get-type": "^27.5.1", + "jest-util": "^27.5.1", + "pretty-format": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-each/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-each/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-each/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-each/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/jest-each/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-each/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-environment-jsdom": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.5.1.tgz", + "integrity": "sha512-TFBvkTC1Hnnnrka/fUb56atfDtJ9VMZ94JkjTbggl1PEpwrYtUBKMezB3inLmWqQsXYLcMwNoDQwoBTAvFfsfw==", + "dependencies": { + "@jest/environment": "^27.5.1", + "@jest/fake-timers": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "jest-mock": "^27.5.1", + "jest-util": "^27.5.1", + "jsdom": "^16.6.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-27.5.1.tgz", + "integrity": "sha512-Jt4ZUnxdOsTGwSRAfKEnE6BcwsSPNOijjwifq5sDFSA2kesnXTvNqKHYgM0hDq3549Uf/KzdXNYn4wMZJPlFLw==", + "dependencies": { + "@jest/environment": "^27.5.1", + "@jest/fake-timers": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "jest-mock": "^27.5.1", + "jest-util": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", + "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.5.1.tgz", + "integrity": "sha512-7GgkZ4Fw4NFbMSDSpZwXeBiIbx+t/46nJ2QitkOjvwPYyZmqttu2TDSimMHP1EkPOi4xUZAN1doE5Vd25H4Jng==", + "dependencies": { + "@jest/types": "^27.5.1", + "@types/graceful-fs": "^4.1.2", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^27.5.1", + "jest-serializer": "^27.5.1", + "jest-util": "^27.5.1", + "jest-worker": "^27.5.1", + "micromatch": "^4.0.4", + "walker": "^1.0.7" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-jasmine2": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.5.1.tgz", + "integrity": "sha512-jtq7VVyG8SqAorDpApwiJJImd0V2wv1xzdheGHRGyuT7gZm6gG47QEskOlzsN1PG/6WNaCo5pmwMHDf3AkG2pQ==", + "dependencies": { + "@jest/environment": "^27.5.1", + "@jest/source-map": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "expect": "^27.5.1", + "is-generator-fn": "^2.0.0", + "jest-each": "^27.5.1", + "jest-matcher-utils": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-runtime": "^27.5.1", + "jest-snapshot": "^27.5.1", + "jest-util": "^27.5.1", + "pretty-format": "^27.5.1", + "throat": "^6.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-jasmine2/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-jasmine2/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-jasmine2/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-jasmine2/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/jest-jasmine2/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-jasmine2/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-leak-detector": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.5.1.tgz", + "integrity": "sha512-POXfWAMvfU6WMUXftV4HolnJfnPOGEu10fscNCA76KBpRRhcMN2c8d3iT2pxQS3HLbA+5X4sOUPzYO2NUyIlHQ==", + "dependencies": { + "jest-get-type": "^27.5.1", + "pretty-format": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz", + "integrity": "sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^27.5.1", + "jest-get-type": "^27.5.1", + "pretty-format": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-matcher-utils/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-matcher-utils/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/jest-matcher-utils/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-matcher-utils/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-message-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", + "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^27.5.1", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^27.5.1", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-message-util/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-message-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-message-util/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-message-util/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/jest-message-util/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-message-util/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-mock": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.5.1.tgz", + "integrity": "sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og==", + "dependencies": { + "@jest/types": "^27.5.1", + "@types/node": "*" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.5.1.tgz", + "integrity": "sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg==", + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.5.1.tgz", + "integrity": "sha512-FFDy8/9E6CV83IMbDpcjOhumAQPDyETnU2KZ1O98DwTnz8AOBsW/Xv3GySr1mOZdItLR+zDZ7I/UdTFbgSOVCw==", + "dependencies": { + "@jest/types": "^27.5.1", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^27.5.1", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^27.5.1", + "jest-validate": "^27.5.1", + "resolve": "^1.20.0", + "resolve.exports": "^1.1.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-27.5.1.tgz", + "integrity": "sha512-QQOOdY4PE39iawDn5rzbIePNigfe5B9Z91GDD1ae/xNDlu9kaat8QQ5EKnNmVWPV54hUdxCVwwj6YMgR2O7IOg==", + "dependencies": { + "@jest/types": "^27.5.1", + "jest-regex-util": "^27.5.1", + "jest-snapshot": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-resolve/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-resolve/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-resolve/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-resolve/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/jest-resolve/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-resolve/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runner": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-27.5.1.tgz", + "integrity": "sha512-g4NPsM4mFCOwFKXO4p/H/kWGdJp9V8kURY2lX8Me2drgXqG7rrZAx5kv+5H7wtt/cdFIjhqYx1HrlqWHaOvDaQ==", + "dependencies": { + "@jest/console": "^27.5.1", + "@jest/environment": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.8.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^27.5.1", + "jest-environment-jsdom": "^27.5.1", + "jest-environment-node": "^27.5.1", + "jest-haste-map": "^27.5.1", + "jest-leak-detector": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-resolve": "^27.5.1", + "jest-runtime": "^27.5.1", + "jest-util": "^27.5.1", + "jest-worker": "^27.5.1", + "source-map-support": "^0.5.6", + "throat": "^6.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-runner/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-runner/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-runner/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-runner/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/jest-runner/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runner/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runtime": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.5.1.tgz", + "integrity": "sha512-o7gxw3Gf+H2IGt8fv0RiyE1+r83FJBRruoA+FXrlHw6xEyBsU8ugA6IPfTdVyA0w8HClpbK+DGJxH59UrNMx8A==", + "dependencies": { + "@jest/environment": "^27.5.1", + "@jest/fake-timers": "^27.5.1", + "@jest/globals": "^27.5.1", + "@jest/source-map": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "execa": "^5.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-mock": "^27.5.1", + "jest-regex-util": "^27.5.1", + "jest-resolve": "^27.5.1", + "jest-snapshot": "^27.5.1", + "jest-util": "^27.5.1", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-runtime/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-runtime/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-runtime/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-runtime/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/jest-runtime/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runtime/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-serializer": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.5.1.tgz", + "integrity": "sha512-jZCyo6iIxO1aqUxpuBlwTDMkzOAJS4a3eYz3YzgxxVQFwLeSA7Jfq5cbqCY+JLvTDrWirgusI/0KwxKMgrdf7w==", + "dependencies": { + "@types/node": "*", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.5.1.tgz", + "integrity": "sha512-yYykXI5a0I31xX67mgeLw1DZ0bJB+gpq5IpSuCAoyDi0+BhgU/RIrL+RTzDmkNTchvDFWKP8lp+w/42Z3us5sA==", + "dependencies": { + "@babel/core": "^7.7.2", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/traverse": "^7.7.2", + "@babel/types": "^7.0.0", + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/babel__traverse": "^7.0.4", + "@types/prettier": "^2.1.5", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^27.5.1", + "graceful-fs": "^4.2.9", + "jest-diff": "^27.5.1", + "jest-get-type": "^27.5.1", + "jest-haste-map": "^27.5.1", + "jest-matcher-utils": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-util": "^27.5.1", + "natural-compare": "^1.4.0", + "pretty-format": "^27.5.1", + "semver": "^7.3.2" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-snapshot/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-snapshot/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/jest-snapshot/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-snapshot/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", + "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", + "dependencies": { + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-util/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-util/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-util/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/jest-util/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-util/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-validate": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.5.1.tgz", + "integrity": "sha512-thkNli0LYTmOI1tDB3FI1S1RTp/Bqyd9pTarJwL87OIBFuqEb5Apv5EaApEudYg4g86e3CT6kM0RowkhtEnCBQ==", + "dependencies": { + "@jest/types": "^27.5.1", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^27.5.1", + "leven": "^3.1.0", + "pretty-format": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-validate/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-validate/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-validate/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-validate/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/jest-validate/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-validate/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-watch-typeahead": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jest-watch-typeahead/-/jest-watch-typeahead-1.1.0.tgz", + "integrity": "sha512-Va5nLSJTN7YFtC2jd+7wsoe1pNe5K4ShLux/E5iHEwlB9AxaxmggY7to9KUqKojhaJw3aXqt5WAb4jGPOolpEw==", + "dependencies": { + "ansi-escapes": "^4.3.1", + "chalk": "^4.0.0", + "jest-regex-util": "^28.0.0", + "jest-watcher": "^28.0.0", + "slash": "^4.0.0", + "string-length": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "jest": "^27.0.0 || ^28.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/@jest/console": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-28.1.3.tgz", + "integrity": "sha512-QPAkP5EwKdK/bxIr6C1I4Vs0rm2nHiANzj/Z5X2JQkrZo6IqvC4ldZ9K95tF0HdidhA8Bo6egxSzUFPYKcEXLw==", + "dependencies": { + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^28.1.3", + "jest-util": "^28.1.3", + "slash": "^3.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/@jest/console/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-watch-typeahead/node_modules/@jest/test-result": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-28.1.3.tgz", + "integrity": "sha512-kZAkxnSE+FqE8YjW8gNuoVkkC9I7S1qmenl8sGcDOLropASP+BkcGKwhXoyqQuGOGeYY0y/ixjrd/iERpEXHNg==", + "dependencies": { + "@jest/console": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/@jest/types": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", + "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", + "dependencies": { + "@jest/schemas": "^28.1.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/@types/yargs": { + "version": "17.0.22", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.22.tgz", + "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-watch-typeahead/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-watch-typeahead/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-watch-typeahead/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/jest-watch-typeahead/node_modules/emittery": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.10.2.tgz", + "integrity": "sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/jest-watch-typeahead/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-watch-typeahead/node_modules/jest-message-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", + "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^28.1.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^28.1.3", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/jest-message-util/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-watch-typeahead/node_modules/jest-regex-util": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz", + "integrity": "sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw==", + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/jest-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", + "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", + "dependencies": { + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/jest-watcher": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-28.1.3.tgz", + "integrity": "sha512-t4qcqj9hze+jviFPUN3YAtAEeFnr/azITXQEMARf5cMwKY2SMBRnCQTXLixTl20OR6mLh9KLMrgVJgJISym+1g==", + "dependencies": { + "@jest/test-result": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.10.2", + "jest-util": "^28.1.3", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/jest-watcher/node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-watch-typeahead/node_modules/jest-watcher/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-watch-typeahead/node_modules/pretty-format": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", + "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", + "dependencies": { + "@jest/schemas": "^28.1.3", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-watch-typeahead/node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watch-typeahead/node_modules/string-length": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-5.0.1.tgz", + "integrity": "sha512-9Ep08KAMUn0OadnVaBuRdE2l615CQ508kr0XMadjClfYpdCyvrbFp6Taebo8yyxokQ4viUd/xPPUA4FGgUa0ow==", + "dependencies": { + "char-regex": "^2.0.0", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watch-typeahead/node_modules/string-length/node_modules/char-regex": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-2.0.1.tgz", + "integrity": "sha512-oSvEeo6ZUD7NepqAat3RqoucZ5SeqLJgOvVIwkafu6IP3V0pO38s/ypdVUmDDK6qIIHNlYHJAKX9E7R7HoKElw==", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/jest-watch-typeahead/node_modules/strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/jest-watch-typeahead/node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/jest-watch-typeahead/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-watcher": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.5.1.tgz", + "integrity": "sha512-z676SuD6Z8o8qbmEGhoEUFOM1+jfEiL3DXHK/xgEiG2EyNYfFG60jluWcupY6dATjfEsKQuibReS1djInQnoVw==", + "dependencies": { + "@jest/test-result": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "jest-util": "^27.5.1", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-watcher/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-watcher/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-watcher/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-watcher/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/jest-watcher/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-watcher/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } }, "node_modules/js-base64": { "version": "3.7.5", "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.5.tgz", "integrity": "sha512-3MEt5DTINKqfScXKfJFrRbxkrnk2AxPWGBL/ycjz4dK8iqiSJ06UxD8jh8xuh6p10TX4t2+7FsBYVxxQbMg+qA==" }, + "node_modules/js-sdsl": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz", + "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "dependencies": { - "argparse": "^2.0.1" + "argparse": "^1.0.7", + "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsdom": { + "version": "16.7.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", + "integrity": "sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==", + "dependencies": { + "abab": "^2.0.5", + "acorn": "^8.2.4", + "acorn-globals": "^6.0.0", + "cssom": "^0.4.4", + "cssstyle": "^2.3.0", + "data-urls": "^2.0.0", + "decimal.js": "^10.2.1", + "domexception": "^2.0.1", + "escodegen": "^2.0.0", + "form-data": "^3.0.0", + "html-encoding-sniffer": "^2.0.1", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.0", + "parse5": "6.0.1", + "saxes": "^5.0.1", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.0.0", + "w3c-hr-time": "^1.0.2", + "w3c-xmlserializer": "^2.0.0", + "webidl-conversions": "^6.1.0", + "whatwg-encoding": "^1.0.5", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.5.0", + "ws": "^7.4.6", + "xml-name-validator": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/tr46": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", + "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jsdom/node_modules/whatwg-url": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", + "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", + "dependencies": { + "lodash": "^4.7.0", + "tr46": "^2.1.0", + "webidl-conversions": "^6.1.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true, "bin": { "jsesc": "bin/jsesc" }, @@ -3357,23 +11618,25 @@ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==" }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, "bin": { "json5": "lib/cli.js" }, @@ -3381,11 +11644,29 @@ "node": ">=6" } }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonpointer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", + "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/jsx-ast-utils": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz", "integrity": "sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==", - "dev": true, "dependencies": { "array-includes": "^3.1.5", "object.assign": "^4.1.3" @@ -3394,26 +11675,55 @@ "node": ">=4.0" } }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "engines": { + "node": ">=6" + } + }, + "node_modules/klona": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", + "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", + "engines": { + "node": ">= 8" + } + }, "node_modules/language-subtag-registry": { "version": "0.3.22", "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz", - "integrity": "sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==", - "dev": true + "integrity": "sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==" }, "node_modules/language-tags": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz", "integrity": "sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ==", - "dev": true, "dependencies": { "language-subtag-registry": "~0.3.2" } }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "engines": { + "node": ">=6" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -3422,16 +11732,44 @@ "node": ">= 0.8.0" } }, + "node_modules/lilconfig": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.6.tgz", + "integrity": "sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==", + "engines": { + "node": ">=10" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, "dependencies": { "p-locate": "^5.0.0" }, @@ -3442,11 +11780,35 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + }, + "node_modules/lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==" + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==" }, "node_modules/loose-envify": { "version": "1.4.0", @@ -3459,20 +11821,236 @@ "loose-envify": "cli.js" } }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dependencies": { + "tslib": "^2.0.3" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, "dependencies": { "yallist": "^3.0.2" } }, + "node_modules/magic-string": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", + "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", + "dependencies": { + "sourcemap-codec": "^1.4.8" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/mdn-data": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", + "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==" + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memfs": { + "version": "3.4.13", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.13.tgz", + "integrity": "sha512-omTM41g3Skpvx5dSYeZIbXKcXoAVc/AoMNwn9TKx++L/gaen/+4TTttmu8ZSch5vfVJ8uJvGbroTsIlslRg6lg==", + "dependencies": { + "fs-monkey": "^1.0.3" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/mini-css-extract-plugin": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.2.tgz", + "integrity": "sha512-EdlUizq13o0Pd+uCp+WO/JpkLvHRVGt97RqfeGhXqAcorYo1ypJSpkV+WDT0vY/kmh/p7wRdJNJtuyK540PXDw==", + "dependencies": { + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/mini-css-extract-plugin/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/mini-css-extract-plugin/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/mini-css-extract-plugin/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "node_modules/mini-css-extract-plugin/node_modules/schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -3484,28 +12062,42 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "dependencies": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + }, + "bin": { + "multicast-dns": "cli.js" + } }, "node_modules/nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -3516,8 +12108,34 @@ "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" + }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" + }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } }, "node_modules/node-fetch": { "version": "2.6.7", @@ -3538,11 +12156,77 @@ } } }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==" + }, "node_modules/node-releases": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.12.tgz", - "integrity": "sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ==", - "dev": true + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", + "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/nwsapi": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.2.tgz", + "integrity": "sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==" }, "node_modules/object-assign": { "version": "4.1.1", @@ -3552,11 +12236,18 @@ "node": ">=0.10.0" } }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "engines": { + "node": ">= 6" + } + }, "node_modules/object-inspect": { "version": "1.12.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -3565,7 +12256,6 @@ "version": "1.1.5", "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.3" @@ -3581,7 +12271,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, "engines": { "node": ">= 0.4" } @@ -3590,7 +12279,6 @@ "version": "4.1.4", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -3608,7 +12296,6 @@ "version": "1.1.6", "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.6.tgz", "integrity": "sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -3622,7 +12309,6 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.6.tgz", "integrity": "sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -3635,11 +12321,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/object.getownpropertydescriptors": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.5.tgz", + "integrity": "sha512-yDNzckpM6ntyQiGTik1fKV1DcVDRS+w8bvpWNCBanvH5LfRX9O8WTHqQzG4RZwRAM4I0oU7TV11Lj5v0g20ibw==", + "dependencies": { + "array.prototype.reduce": "^1.0.5", + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/object.hasown": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.2.tgz", "integrity": "sha512-B5UIT3J1W+WuWIU55h0mjlwaqxiE5vYENJXIXZ4VFe05pNYrkKuK0U/6aFcb0pKywYJh7IhfoqUfKVmrJJHZHw==", - "dev": true, "dependencies": { "define-properties": "^1.1.4", "es-abstract": "^1.20.4" @@ -3652,7 +12354,6 @@ "version": "1.1.6", "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -3665,20 +12366,72 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==" + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "dependencies": { "wrappy": "1" } }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/optionator": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", @@ -3695,7 +12448,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, "dependencies": { "yocto-queue": "^0.1.0" }, @@ -3710,7 +12462,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, "dependencies": { "p-limit": "^3.0.2" }, @@ -3721,6 +12472,35 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -3749,11 +12529,32 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, "engines": { "node": ">=8" } @@ -3762,7 +12563,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -3771,7 +12571,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "engines": { "node": ">=8" } @@ -3781,6 +12580,11 @@ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -3789,17 +12593,173 @@ "node": ">=8" } }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", + "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-up": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", + "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", + "dependencies": { + "find-up": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-up/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-up/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-up/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-up/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-up/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "engines": { + "node": ">=4" + } }, "node_modules/postcss": { - "version": "8.4.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.23.tgz", - "integrity": "sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==", - "dev": true, + "version": "8.4.21", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", + "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", "funding": [ { "type": "opencollective", @@ -3808,14 +12768,10 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" } ], "dependencies": { - "nanoid": "^3.3.6", + "nanoid": "^3.3.4", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" }, @@ -3823,28 +12779,1230 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-attribute-case-insensitive": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-5.0.2.tgz", + "integrity": "sha512-XIidXV8fDr0kKt28vqki84fRK8VW8eTuIa4PChv2MqKuT6C9UjmSKzen6KaWhWEoYvwxFCa7n/tC1SZ3tyq4SQ==", + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-browser-comments": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-browser-comments/-/postcss-browser-comments-4.0.0.tgz", + "integrity": "sha512-X9X9/WN3KIvY9+hNERUqX9gncsgBA25XaeR+jshHz2j8+sYyHktHw1JdKuMjeLpGktXidqDhA7b/qm1mrBDmgg==", + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "browserslist": ">=4", + "postcss": ">=8" + } + }, + "node_modules/postcss-calc": { + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-8.2.4.tgz", + "integrity": "sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==", + "dependencies": { + "postcss-selector-parser": "^6.0.9", + "postcss-value-parser": "^4.2.0" + }, + "peerDependencies": { + "postcss": "^8.2.2" + } + }, + "node_modules/postcss-clamp": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-clamp/-/postcss-clamp-4.1.0.tgz", + "integrity": "sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=7.6.0" + }, + "peerDependencies": { + "postcss": "^8.4.6" + } + }, + "node_modules/postcss-color-functional-notation": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-4.2.4.tgz", + "integrity": "sha512-2yrTAUZUab9s6CpxkxC4rVgFEVaR6/2Pipvi6qcgvnYiVqZcbDHEoBDhrXzyb7Efh2CCfHQNtcqWcIruDTIUeg==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-color-hex-alpha": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-8.0.4.tgz", + "integrity": "sha512-nLo2DCRC9eE4w2JmuKgVA3fGL3d01kGq752pVALF68qpGLmx2Qrk91QTKkdUqqp45T1K1XV8IhQpcu1hoAQflQ==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-color-rebeccapurple": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-7.1.1.tgz", + "integrity": "sha512-pGxkuVEInwLHgkNxUc4sdg4g3py7zUeCQ9sMfwyHAT+Ezk8a4OaaVZ8lIY5+oNqA/BXXgLyXv0+5wHP68R79hg==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-colormin": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-5.3.1.tgz", + "integrity": "sha512-UsWQG0AqTFQmpBegeLLc1+c3jIqBNB0zlDGRWR+dQ3pRKJL1oeMzyqmH3o2PIfn9MBdNrVPWhDbT769LxCTLJQ==", + "dependencies": { + "browserslist": "^4.21.4", + "caniuse-api": "^3.0.0", + "colord": "^2.9.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-convert-values": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-5.1.3.tgz", + "integrity": "sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA==", + "dependencies": { + "browserslist": "^4.21.4", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-custom-media": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-8.0.2.tgz", + "integrity": "sha512-7yi25vDAoHAkbhAzX9dHx2yc6ntS4jQvejrNcC+csQJAXjj15e7VcWfMgLqBNAbOvqi5uIa9huOVwdHbf+sKqg==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.3" + } + }, + "node_modules/postcss-custom-properties": { + "version": "12.1.11", + "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-12.1.11.tgz", + "integrity": "sha512-0IDJYhgU8xDv1KY6+VgUwuQkVtmYzRwu+dMjnmdMafXYv86SWqfxkc7qdDvWS38vsjaEtv8e0vGOUQrAiMBLpQ==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-custom-selectors": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-6.0.3.tgz", + "integrity": "sha512-fgVkmyiWDwmD3JbpCmB45SvvlCD6z9CG6Ie6Iere22W5aHea6oWa7EM2bpnv2Fj3I94L3VbtvX9KqwSi5aFzSg==", + "dependencies": { + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.3" + } + }, + "node_modules/postcss-dir-pseudo-class": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-6.0.5.tgz", + "integrity": "sha512-eqn4m70P031PF7ZQIvSgy9RSJ5uI2171O/OO/zcRNYpJbvaeKFUlar1aJ7rmgiQtbm0FSPsRewjpdS0Oew7MPA==", + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-discard-comments": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz", + "integrity": "sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-duplicates": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz", + "integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-empty": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz", + "integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-overridden": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz", + "integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-double-position-gradients": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-3.1.2.tgz", + "integrity": "sha512-GX+FuE/uBR6eskOK+4vkXgT6pDkexLokPaz/AbJna9s5Kzp/yl488pKPjhy0obB475ovfT1Wv8ho7U/cHNaRgQ==", + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-env-function": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/postcss-env-function/-/postcss-env-function-4.0.6.tgz", + "integrity": "sha512-kpA6FsLra+NqcFnL81TnsU+Z7orGtDTxcOhl6pwXeEq1yFPpRMkCDpHhrz8CFQDr/Wfm0jLiNQ1OsGGPjlqPwA==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-flexbugs-fixes": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-5.0.2.tgz", + "integrity": "sha512-18f9voByak7bTktR2QgDveglpn9DTbBWPUzSOe9g0N4WR/2eSt6Vrcbf0hmspvMI6YWGywz6B9f7jzpFNJJgnQ==", + "peerDependencies": { + "postcss": "^8.1.4" + } + }, + "node_modules/postcss-focus-visible": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-6.0.4.tgz", + "integrity": "sha512-QcKuUU/dgNsstIK6HELFRT5Y3lbrMLEOwG+A4s5cA+fx3A3y/JTq3X9LaOj3OC3ALH0XqyrgQIgey/MIZ8Wczw==", + "dependencies": { + "postcss-selector-parser": "^6.0.9" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-focus-within": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-5.0.4.tgz", + "integrity": "sha512-vvjDN++C0mu8jz4af5d52CB184ogg/sSxAFS+oUJQq2SuCe7T5U2iIsVJtsCp2d6R4j0jr5+q3rPkBVZkXD9fQ==", + "dependencies": { + "postcss-selector-parser": "^6.0.9" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-font-variant": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz", + "integrity": "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==", + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-gap-properties": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-3.0.5.tgz", + "integrity": "sha512-IuE6gKSdoUNcvkGIqdtjtcMtZIFyXZhmFd5RUlg97iVEvp1BZKV5ngsAjCjrVy+14uhGBQl9tzmi1Qwq4kqVOg==", + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-image-set-function": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-4.0.7.tgz", + "integrity": "sha512-9T2r9rsvYzm5ndsBE8WgtrMlIT7VbtTfE7b3BQnudUqnBcBo7L758oc+o+pdj/dUV0l5wjwSdjeOH2DZtfv8qw==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-import": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-14.1.0.tgz", + "integrity": "sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-initial": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-4.0.1.tgz", + "integrity": "sha512-0ueD7rPqX8Pn1xJIjay0AZeIuDoF+V+VvMt/uOnn+4ezUKhZM/NokDeP6DwMNyIoYByuN/94IQnt5FEkaN59xQ==", + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-lab-function": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-4.2.1.tgz", + "integrity": "sha512-xuXll4isR03CrQsmxyz92LJB2xX9n+pZJ5jE9JgcnmsCammLyKdlzrBin+25dy6wIjfhJpKBAN80gsTlCgRk2w==", + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-load-config": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz", + "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", + "dependencies": { + "lilconfig": "^2.0.5", + "yaml": "^1.10.2" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-loader": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-6.2.1.tgz", + "integrity": "sha512-WbbYpmAaKcux/P66bZ40bpWsBucjx/TTgVVzRZ9yUO8yQfVBlameJ0ZGVaPfH64hNSBh63a+ICP5nqOpBA0w+Q==", + "dependencies": { + "cosmiconfig": "^7.0.0", + "klona": "^2.0.5", + "semver": "^7.3.5" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "postcss": "^7.0.0 || ^8.0.1", + "webpack": "^5.0.0" + } + }, + "node_modules/postcss-logical": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-5.0.4.tgz", + "integrity": "sha512-RHXxplCeLh9VjinvMrZONq7im4wjWGlRJAqmAVLXyZaXwfDWP73/oq4NdIp+OZwhQUMj0zjqDfM5Fj7qby+B4g==", + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-media-minmax": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-5.0.0.tgz", + "integrity": "sha512-yDUvFf9QdFZTuCUg0g0uNSHVlJ5X1lSzDZjPSFaiCWvjgsvu8vEVxtahPrLMinIDEEGnx6cBe6iqdx5YWz08wQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-merge-longhand": { + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-5.1.7.tgz", + "integrity": "sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ==", + "dependencies": { + "postcss-value-parser": "^4.2.0", + "stylehacks": "^5.1.1" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-merge-rules": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-5.1.4.tgz", + "integrity": "sha512-0R2IuYpgU93y9lhVbO/OylTtKMVcHb67zjWIfCiKR9rWL3GUk1677LAqD/BcHizukdZEjT8Ru3oHRoAYoJy44g==", + "dependencies": { + "browserslist": "^4.21.4", + "caniuse-api": "^3.0.0", + "cssnano-utils": "^3.1.0", + "postcss-selector-parser": "^6.0.5" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-font-values": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz", + "integrity": "sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-gradients": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz", + "integrity": "sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==", + "dependencies": { + "colord": "^2.9.1", + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-params": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-5.1.4.tgz", + "integrity": "sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw==", + "dependencies": { + "browserslist": "^4.21.4", + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-selectors": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-5.2.1.tgz", + "integrity": "sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==", + "dependencies": { + "postcss-selector-parser": "^6.0.5" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", + "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", + "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==", + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", + "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", + "dependencies": { + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-nested": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.0.tgz", + "integrity": "sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==", + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": ">=12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-nesting": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-10.2.0.tgz", + "integrity": "sha512-EwMkYchxiDiKUhlJGzWsD9b2zvq/r2SSubcRrgP+jujMXFzqvANLt16lJANC+5uZ6hjI7lpRmI6O8JIl+8l1KA==", + "dependencies": { + "@csstools/selector-specificity": "^2.0.0", + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-normalize": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize/-/postcss-normalize-10.0.1.tgz", + "integrity": "sha512-+5w18/rDev5mqERcG3W5GZNMJa1eoYYNGo8gB7tEwaos0ajk3ZXAI4mHGcNT47NE+ZnZD1pEpUOFLvltIwmeJA==", + "dependencies": { + "@csstools/normalize.css": "*", + "postcss-browser-comments": "^4", + "sanitize.css": "*" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "browserslist": ">= 4", + "postcss": ">= 8" + } + }, + "node_modules/postcss-normalize-charset": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz", + "integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-display-values": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz", + "integrity": "sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-positions": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-5.1.1.tgz", + "integrity": "sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-repeat-style": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.1.tgz", + "integrity": "sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-string": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz", + "integrity": "sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-timing-functions": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz", + "integrity": "sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-unicode": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.1.tgz", + "integrity": "sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA==", + "dependencies": { + "browserslist": "^4.21.4", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-url": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz", + "integrity": "sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==", + "dependencies": { + "normalize-url": "^6.0.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-whitespace": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz", + "integrity": "sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-opacity-percentage": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/postcss-opacity-percentage/-/postcss-opacity-percentage-1.1.3.tgz", + "integrity": "sha512-An6Ba4pHBiDtyVpSLymUUERMo2cU7s+Obz6BTrS+gxkbnSBNKSuD0AVUc+CpBMrpVPKKfoVz0WQCX+Tnst0i4A==", + "funding": [ + { + "type": "kofi", + "url": "https://ko-fi.com/mrcgrtz" + }, + { + "type": "liberapay", + "url": "https://liberapay.com/mrcgrtz" + } + ], + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-ordered-values": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-5.1.3.tgz", + "integrity": "sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==", + "dependencies": { + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-overflow-shorthand": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-3.0.4.tgz", + "integrity": "sha512-otYl/ylHK8Y9bcBnPLo3foYFLL6a6Ak+3EQBPOTR7luMYCOsiVTUk1iLvNf6tVPNGXcoL9Hoz37kpfriRIFb4A==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-page-break": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-3.0.4.tgz", + "integrity": "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==", + "peerDependencies": { + "postcss": "^8" + } + }, + "node_modules/postcss-place": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-7.0.5.tgz", + "integrity": "sha512-wR8igaZROA6Z4pv0d+bvVrvGY4GVHihBCBQieXFY3kuSuMyOmEnnfFzHl/tQuqHZkfkIVBEbDvYcFfHmpSet9g==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-preset-env": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-7.8.3.tgz", + "integrity": "sha512-T1LgRm5uEVFSEF83vHZJV2z19lHg4yJuZ6gXZZkqVsqv63nlr6zabMH3l4Pc01FQCyfWVrh2GaUeCVy9Po+Aag==", + "dependencies": { + "@csstools/postcss-cascade-layers": "^1.1.1", + "@csstools/postcss-color-function": "^1.1.1", + "@csstools/postcss-font-format-keywords": "^1.0.1", + "@csstools/postcss-hwb-function": "^1.0.2", + "@csstools/postcss-ic-unit": "^1.0.1", + "@csstools/postcss-is-pseudo-class": "^2.0.7", + "@csstools/postcss-nested-calc": "^1.0.0", + "@csstools/postcss-normalize-display-values": "^1.0.1", + "@csstools/postcss-oklab-function": "^1.1.1", + "@csstools/postcss-progressive-custom-properties": "^1.3.0", + "@csstools/postcss-stepped-value-functions": "^1.0.1", + "@csstools/postcss-text-decoration-shorthand": "^1.0.0", + "@csstools/postcss-trigonometric-functions": "^1.0.2", + "@csstools/postcss-unset-value": "^1.0.2", + "autoprefixer": "^10.4.13", + "browserslist": "^4.21.4", + "css-blank-pseudo": "^3.0.3", + "css-has-pseudo": "^3.0.4", + "css-prefers-color-scheme": "^6.0.3", + "cssdb": "^7.1.0", + "postcss-attribute-case-insensitive": "^5.0.2", + "postcss-clamp": "^4.1.0", + "postcss-color-functional-notation": "^4.2.4", + "postcss-color-hex-alpha": "^8.0.4", + "postcss-color-rebeccapurple": "^7.1.1", + "postcss-custom-media": "^8.0.2", + "postcss-custom-properties": "^12.1.10", + "postcss-custom-selectors": "^6.0.3", + "postcss-dir-pseudo-class": "^6.0.5", + "postcss-double-position-gradients": "^3.1.2", + "postcss-env-function": "^4.0.6", + "postcss-focus-visible": "^6.0.4", + "postcss-focus-within": "^5.0.4", + "postcss-font-variant": "^5.0.0", + "postcss-gap-properties": "^3.0.5", + "postcss-image-set-function": "^4.0.7", + "postcss-initial": "^4.0.1", + "postcss-lab-function": "^4.2.1", + "postcss-logical": "^5.0.4", + "postcss-media-minmax": "^5.0.0", + "postcss-nesting": "^10.2.0", + "postcss-opacity-percentage": "^1.1.2", + "postcss-overflow-shorthand": "^3.0.4", + "postcss-page-break": "^3.0.4", + "postcss-place": "^7.0.5", + "postcss-pseudo-class-any-link": "^7.1.6", + "postcss-replace-overflow-wrap": "^4.0.0", + "postcss-selector-not": "^6.0.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-pseudo-class-any-link": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-7.1.6.tgz", + "integrity": "sha512-9sCtZkO6f/5ML9WcTLcIyV1yz9D1rf0tWc+ulKcvV30s0iZKS/ONyETvoWsr6vnrmW+X+KmuK3gV/w5EWnT37w==", + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-reduce-initial": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-5.1.2.tgz", + "integrity": "sha512-dE/y2XRaqAi6OvjzD22pjTUQ8eOfc6m/natGHgKFBK9DxFmIm69YmaRVQrGgFlEfc1HePIurY0TmDeROK05rIg==", + "dependencies": { + "browserslist": "^4.21.4", + "caniuse-api": "^3.0.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-reduce-transforms": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz", + "integrity": "sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-replace-overflow-wrap": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz", + "integrity": "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==", + "peerDependencies": { + "postcss": "^8.0.3" + } + }, + "node_modules/postcss-selector-not": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-6.0.1.tgz", + "integrity": "sha512-1i9affjAe9xu/y9uqWH+tD4r6/hDaXJruk8xn2x1vzxC2U3J3LKO3zJW4CyxlNhA56pADJ/djpEwpH1RClI2rQ==", + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.11", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz", + "integrity": "sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-svgo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-5.1.0.tgz", + "integrity": "sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==", + "dependencies": { + "postcss-value-parser": "^4.2.0", + "svgo": "^2.7.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-svgo/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/postcss-svgo/node_modules/css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "dependencies": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/postcss-svgo/node_modules/mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==" + }, + "node_modules/postcss-svgo/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==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postcss-svgo/node_modules/svgo": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz", + "integrity": "sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==", + "dependencies": { + "@trysound/sax": "0.2.0", + "commander": "^7.2.0", + "css-select": "^4.1.3", + "css-tree": "^1.1.3", + "csso": "^4.2.0", + "picocolors": "^1.0.0", + "stable": "^0.1.8" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/postcss-unique-selectors": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz", + "integrity": "sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==", + "dependencies": { + "postcss-selector-parser": "^6.0.5" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, "engines": { "node": ">= 0.8.0" } }, - "node_modules/prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", - "dev": true, - "bin": { - "prettier": "bin-prettier.js" - }, + "node_modules/pretty-bytes": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", + "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", "engines": { - "node": ">=10.13.0" + "node": ">=6" }, "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pretty-error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", + "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", + "dependencies": { + "lodash": "^4.17.20", + "renderkid": "^3.0.0" + } + }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/pretty-format/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "node_modules/promise": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", + "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==", + "dependencies": { + "asap": "~2.0.6" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" } }, "node_modules/prop-types": { @@ -3862,20 +14020,71 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-addr/node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" + }, "node_modules/punycode": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", - "dev": true, "engines": { "node": ">=6" } }, + "node_modules/q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", + "engines": { + "node": ">=0.6.0", + "teleport": ">=0.2.0" + } + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, "funding": [ { "type": "github", @@ -3891,6 +14100,74 @@ } ] }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/raf": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", + "dependencies": { + "performance-now": "^2.1.0" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/react": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", @@ -3902,6 +14179,128 @@ "node": ">=0.10.0" } }, + "node_modules/react-app-polyfill": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/react-app-polyfill/-/react-app-polyfill-3.0.0.tgz", + "integrity": "sha512-sZ41cxiU5llIB003yxxQBYrARBqe0repqPTTYBTmMqTz9szeBbE37BehCE891NZsmdZqqP+xWKdT3eo3vOzN8w==", + "dependencies": { + "core-js": "^3.19.2", + "object-assign": "^4.1.1", + "promise": "^8.1.0", + "raf": "^3.4.1", + "regenerator-runtime": "^0.13.9", + "whatwg-fetch": "^3.6.2" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/react-dev-utils": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz", + "integrity": "sha512-84Ivxmr17KjUupyqzFode6xKhjwuEJDROWKJy/BthkL7Wn6NJ8h4WE6k/exAv6ImS+0oZLRRW5j/aINMHyeGeQ==", + "dependencies": { + "@babel/code-frame": "^7.16.0", + "address": "^1.1.2", + "browserslist": "^4.18.1", + "chalk": "^4.1.2", + "cross-spawn": "^7.0.3", + "detect-port-alt": "^1.1.6", + "escape-string-regexp": "^4.0.0", + "filesize": "^8.0.6", + "find-up": "^5.0.0", + "fork-ts-checker-webpack-plugin": "^6.5.0", + "global-modules": "^2.0.0", + "globby": "^11.0.4", + "gzip-size": "^6.0.0", + "immer": "^9.0.7", + "is-root": "^2.1.0", + "loader-utils": "^3.2.0", + "open": "^8.4.0", + "pkg-up": "^3.1.0", + "prompts": "^2.4.2", + "react-error-overlay": "^6.0.11", + "recursive-readdir": "^2.2.2", + "shell-quote": "^1.7.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/react-dev-utils/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/react-dev-utils/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/react-dev-utils/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/react-dev-utils/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/react-dev-utils/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/react-dev-utils/node_modules/loader-utils": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.2.1.tgz", + "integrity": "sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw==", + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/react-dev-utils/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/react-dom": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", @@ -3914,6 +14313,11 @@ "react": "^18.2.0" } }, + "node_modules/react-error-overlay": { + "version": "6.0.11", + "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz", + "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==" + }, "node_modules/react-i18next": { "version": "11.18.6", "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-11.18.6.tgz", @@ -3952,20 +14356,19 @@ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" }, "node_modules/react-refresh": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", - "integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==", - "dev": true, + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", + "integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==", "engines": { "node": ">=0.10.0" } }, "node_modules/react-router": { - "version": "6.11.2", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.11.2.tgz", - "integrity": "sha512-74z9xUSaSX07t3LM+pS6Un0T55ibUE/79CzfZpy5wsPDZaea1F8QkrsiyRnA2YQ7LwE/umaydzXZV80iDCPkMg==", + "version": "6.8.2", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.8.2.tgz", + "integrity": "sha512-lF7S0UmXI5Pd8bmHvMdPKI4u4S5McxmHnzJhrYi9ZQ6wE+DA8JN5BzVC5EEBuduWWDaiJ8u6YhVOCmThBli+rw==", "dependencies": { - "@remix-run/router": "1.6.2" + "@remix-run/router": "1.3.3" }, "engines": { "node": ">=14" @@ -3975,12 +14378,12 @@ } }, "node_modules/react-router-dom": { - "version": "6.11.2", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.11.2.tgz", - "integrity": "sha512-JNbKtAeh1VSJQnH6RvBDNhxNwemRj7KxCzc5jb7zvDSKRnPWIFj9pO+eXqjM69gQJ0r46hSz1x4l9y0651DKWw==", + "version": "6.8.2", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.8.2.tgz", + "integrity": "sha512-N/oAF1Shd7g4tWy+75IIufCGsHBqT74tnzHQhbiUTYILYF0Blk65cg+HPZqwC+6SqEyx033nKqU7by38v3lBZg==", "dependencies": { - "@remix-run/router": "1.6.2", - "react-router": "6.11.2" + "@remix-run/router": "1.3.3", + "react-router": "6.8.2" }, "engines": { "node": ">=14" @@ -3990,6 +14393,78 @@ "react-dom": ">=16.8" } }, + "node_modules/react-scripts": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", + "integrity": "sha512-8VAmEm/ZAwQzJ+GOMLbBsTdDKOpuZh7RPs0UymvBR2vRk4iZWCskjbFnxqjrzoIvlNNRZ3QJFx6/qDSi6zSnaQ==", + "dependencies": { + "@babel/core": "^7.16.0", + "@pmmmwh/react-refresh-webpack-plugin": "^0.5.3", + "@svgr/webpack": "^5.5.0", + "babel-jest": "^27.4.2", + "babel-loader": "^8.2.3", + "babel-plugin-named-asset-import": "^0.3.8", + "babel-preset-react-app": "^10.0.1", + "bfj": "^7.0.2", + "browserslist": "^4.18.1", + "camelcase": "^6.2.1", + "case-sensitive-paths-webpack-plugin": "^2.4.0", + "css-loader": "^6.5.1", + "css-minimizer-webpack-plugin": "^3.2.0", + "dotenv": "^10.0.0", + "dotenv-expand": "^5.1.0", + "eslint": "^8.3.0", + "eslint-config-react-app": "^7.0.1", + "eslint-webpack-plugin": "^3.1.1", + "file-loader": "^6.2.0", + "fs-extra": "^10.0.0", + "html-webpack-plugin": "^5.5.0", + "identity-obj-proxy": "^3.0.0", + "jest": "^27.4.3", + "jest-resolve": "^27.4.2", + "jest-watch-typeahead": "^1.0.0", + "mini-css-extract-plugin": "^2.4.5", + "postcss": "^8.4.4", + "postcss-flexbugs-fixes": "^5.0.2", + "postcss-loader": "^6.2.1", + "postcss-normalize": "^10.0.1", + "postcss-preset-env": "^7.0.1", + "prompts": "^2.4.2", + "react-app-polyfill": "^3.0.0", + "react-dev-utils": "^12.0.1", + "react-refresh": "^0.11.0", + "resolve": "^1.20.0", + "resolve-url-loader": "^4.0.0", + "sass-loader": "^12.3.0", + "semver": "^7.3.5", + "source-map-loader": "^3.0.0", + "style-loader": "^3.3.1", + "tailwindcss": "^3.0.2", + "terser-webpack-plugin": "^5.2.5", + "webpack": "^5.64.4", + "webpack-dev-server": "^4.6.0", + "webpack-manifest-plugin": "^4.0.2", + "workbox-webpack-plugin": "^6.4.1" + }, + "bin": { + "react-scripts": "bin/react-scripts.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + }, + "peerDependencies": { + "react": ">= 16", + "typescript": "^3.2.1 || ^4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", @@ -4005,20 +14480,91 @@ "react-dom": ">=16.6.0" } }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.1.tgz", + "integrity": "sha512-+rQmrWMYGA90yenhTYsLWAsLsqVC8osOw6PKE1HDYiO0gdPeKe/xDHNzIAIn4C91YQ6oenEhfYqqc1883qHbjQ==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/recursive-readdir": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.3.tgz", + "integrity": "sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA==", + "dependencies": { + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==" + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", + "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==", + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/regenerator-runtime": { "version": "0.13.11", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" }, + "node_modules/regenerator-transform": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.1.tgz", + "integrity": "sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==", + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, + "node_modules/regex-parser": { + "version": "2.2.11", + "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.2.11.tgz", + "integrity": "sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q==" + }, "node_modules/regexp.prototype.flags": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", - "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", - "dev": true, + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "functions-have-names": "^1.2.3" + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" }, "engines": { "node": ">= 0.4" @@ -4027,12 +14573,99 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/resolve": { - "version": "1.22.2", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", - "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/regexpu-core": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.1.tgz", + "integrity": "sha512-nCOzW2V/X15XpLsK2rlgdwrysrBq+AauCn+omItIz4R1pIcmeot5zvjdmOBRLzEH/CkC6IxMJVmxDe3QcMuNVQ==", "dependencies": { - "is-core-module": "^2.11.0", + "@babel/regjsgen": "^0.8.0", + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.1.0", + "regjsparser": "^0.9.1", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsparser": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/renderkid": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", + "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", + "dependencies": { + "css-select": "^4.1.3", + "dom-converter": "^0.2.0", + "htmlparser2": "^6.1.0", + "lodash": "^4.17.21", + "strip-ansi": "^6.0.1" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + }, + "node_modules/resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dependencies": { + "is-core-module": "^2.9.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -4043,6 +14676,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "engines": { + "node": ">=8" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -4051,11 +14703,82 @@ "node": ">=4" } }, + "node_modules/resolve-url-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-4.0.0.tgz", + "integrity": "sha512-05VEMczVREcbtT7Bz+C+96eUO5HDNvdthIiMB34t7FcF8ehcu4wC0sSgPUubs3XW2Q3CNLJk/BJrCU9wVRymiA==", + "dependencies": { + "adjust-sourcemap-loader": "^4.0.0", + "convert-source-map": "^1.7.0", + "loader-utils": "^2.0.0", + "postcss": "^7.0.35", + "source-map": "0.6.1" + }, + "engines": { + "node": ">=8.9" + }, + "peerDependencies": { + "rework": "1.0.1", + "rework-visit": "1.0.0" + }, + "peerDependenciesMeta": { + "rework": { + "optional": true + }, + "rework-visit": { + "optional": true + } + } + }, + "node_modules/resolve-url-loader/node_modules/picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==" + }, + "node_modules/resolve-url-loader/node_modules/postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "dependencies": { + "picocolors": "^0.2.1", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + } + }, + "node_modules/resolve-url-loader/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==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve.exports": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.1.tgz", + "integrity": "sha512-/NtpHNDN7jWhAaQ9BvBUYZ6YTXsRBgfqWFWP7BZBaoMJO/I3G5OFzvTuWNlZC3aPjins1F+TNrLKsGbH4rfsRQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "engines": { + "node": ">= 4" + } + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" @@ -4065,7 +14788,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, "dependencies": { "glob": "^7.1.3" }, @@ -4077,26 +14799,78 @@ } }, "node_modules/rollup": { - "version": "3.23.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.23.0.tgz", - "integrity": "sha512-h31UlwEi7FHihLe1zbk+3Q7z1k/84rb9BSwmBSr/XjOCEaBJ2YyedQDuM0t/kfOS0IxM+vk1/zI9XxYj9V+NJQ==", - "dev": true, + "version": "2.79.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz", + "integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==", "bin": { "rollup": "dist/bin/rollup" }, "engines": { - "node": ">=14.18.0", - "npm": ">=8.0.0" + "node": ">=10.0.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, + "node_modules/rollup-plugin-terser": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz", + "integrity": "sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==", + "deprecated": "This package has been deprecated and is no longer maintained. Please use @rollup/plugin-terser", + "dependencies": { + "@babel/code-frame": "^7.10.4", + "jest-worker": "^26.2.1", + "serialize-javascript": "^4.0.0", + "terser": "^5.0.0" + }, + "peerDependencies": { + "rollup": "^2.0.0" + } + }, + "node_modules/rollup-plugin-terser/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/rollup-plugin-terser/node_modules/jest-worker": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", + "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/rollup-plugin-terser/node_modules/serialize-javascript": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", + "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/rollup-plugin-terser/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, "funding": [ { "type": "github", @@ -4115,11 +14889,29 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/safe-regex-test": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.1.3", @@ -4129,6 +14921,69 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/sanitize.css": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/sanitize.css/-/sanitize.css-13.0.0.tgz", + "integrity": "sha512-ZRwKbh/eQ6w9vmTjkuG0Ioi3HBwPFce0O+v//ve+aOq1oeCy7jMV2qzzAlpsNuqpqCBjjriM1lbtZbF/Q8jVyA==" + }, + "node_modules/sass-loader": { + "version": "12.6.0", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-12.6.0.tgz", + "integrity": "sha512-oLTaH0YCtX4cfnJZxKSLAyglED0naiYfNG1iXfU5w1LNZ+ukoA5DtyDIN5zmKVZwYNJP4KRc5Y3hkWga+7tYfA==", + "dependencies": { + "klona": "^2.0.4", + "neo-async": "^2.6.2" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "fibers": ">= 3.1.0", + "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0", + "sass": "^1.3.0", + "sass-embedded": "*", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "fibers": { + "optional": true + }, + "node-sass": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + } + } + }, + "node_modules/sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "node_modules/saxes": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/scheduler": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", @@ -4137,20 +14992,211 @@ "loose-envify": "^1.1.0" } }, + "node_modules/schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==" + }, + "node_modules/selfsigned": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.1.1.tgz", + "integrity": "sha512-GSL3aowiF7wa/WtSFwnUrludWFoNhftq8bUkH9pkzjpN2XSPOAYEgg6e0sS9s0rZwgJzJiQRPU18A6clnoW5wQ==", + "dependencies": { + "node-forge": "^1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dependencies": { + "lru-cache": "^6.0.0" + }, "bin": { "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, + "node_modules/semver/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==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/serialize-javascript": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", + "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", + "dependencies": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-index/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/serve-index/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==" + }, + "node_modules/serve-index/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/serve-index/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + }, + "node_modules/serve-index/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "dependencies": { "shebang-regex": "^3.0.0" }, @@ -4162,16 +15208,22 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "engines": { "node": ">=8" } }, + "node_modules/shell-quote": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.0.tgz", + "integrity": "sha512-QHsz8GgQIGKlRi24yFc6a6lN69Idnx634w49ay6+jA5yFh7a1UY+4Rp6HPx/L/1zcEDPEij8cIsiqR6bQsE5VQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, "dependencies": { "call-bind": "^1.0.0", "get-intrinsic": "^1.0.2", @@ -4181,6 +15233,39 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/sockjs": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "dependencies": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "node_modules/source-list-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", + "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==" + }, "node_modules/source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -4193,11 +15278,92 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "dev": true, "engines": { "node": ">=0.10.0" } }, + "node_modules/source-map-loader": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-3.0.2.tgz", + "integrity": "sha512-BokxPoLjyl3iOrgkWaakaxqnelAJSS+0V+De0kKIq6lyWrXuiPgYTGp6z3iHmqljKAaLXwZa+ctD8GccRJeVvg==", + "dependencies": { + "abab": "^2.0.5", + "iconv-lite": "^0.6.3", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/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==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "deprecated": "Please use @jridgewell/sourcemap-codec instead" + }, + "node_modules/spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "dependencies": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "dependencies": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" + }, + "node_modules/stable": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", + "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", + "deprecated": "Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility" + }, "node_modules/stack-generator": { "version": "2.0.10", "resolved": "https://registry.npmjs.org/stack-generator/-/stack-generator-2.0.10.tgz", @@ -4206,6 +15372,25 @@ "stackframe": "^1.3.4" } }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "engines": { + "node": ">=8" + } + }, "node_modules/stackframe": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", @@ -4238,11 +15423,18 @@ "stacktrace-gps": "^3.0.4" } }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/stop-iteration-iterator": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==", - "dev": true, "dependencies": { "internal-slot": "^1.0.4" }, @@ -4250,11 +15442,53 @@ "node": ">= 0.4" } }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-natural-compare": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/string-natural-compare/-/string-natural-compare-3.0.1.tgz", + "integrity": "sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==" + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, "node_modules/string.prototype.matchall": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz", "integrity": "sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -4269,28 +15503,10 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/string.prototype.trim": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", - "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/string.prototype.trimend": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -4304,7 +15520,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -4314,11 +15529,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/stringify-object": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", + "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", + "dependencies": { + "get-own-enumerable-property-symbols": "^3.0.0", + "is-obj": "^1.0.1", + "is-regexp": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -4327,19 +15554,33 @@ } }, "node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", "engines": { - "node": ">=4" + "node": ">=8" + } + }, + "node_modules/strip-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-comments/-/strip-comments-2.0.1.tgz", + "integrity": "sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==", + "engines": { + "node": ">=10" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "engines": { + "node": ">=6" } }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, "engines": { "node": ">=8" }, @@ -4347,10 +15588,40 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/style-loader": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.1.tgz", + "integrity": "sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ==", + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/stylehacks": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz", + "integrity": "sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==", + "dependencies": { + "browserslist": "^4.21.4", + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, "node_modules/stylis": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", - "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.1.3.tgz", + "integrity": "sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA==" }, "node_modules/supports-color": { "version": "5.5.0", @@ -4363,6 +15634,37 @@ "node": ">=4" } }, + "node_modules/supports-hyperlinks": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", + "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -4374,11 +15676,277 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/svg-parser": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", + "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==" + }, + "node_modules/svgo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", + "integrity": "sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw==", + "deprecated": "This SVGO version is no longer supported. Upgrade to v2.x.x.", + "dependencies": { + "chalk": "^2.4.1", + "coa": "^2.0.2", + "css-select": "^2.0.0", + "css-select-base-adapter": "^0.1.1", + "css-tree": "1.0.0-alpha.37", + "csso": "^4.0.2", + "js-yaml": "^3.13.1", + "mkdirp": "~0.5.1", + "object.values": "^1.1.0", + "sax": "~1.2.4", + "stable": "^0.1.8", + "unquote": "~1.1.1", + "util.promisify": "~1.0.0" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/svgo/node_modules/css-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz", + "integrity": "sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ==", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^3.2.1", + "domutils": "^1.7.0", + "nth-check": "^1.0.2" + } + }, + "node_modules/svgo/node_modules/css-what": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.4.2.tgz", + "integrity": "sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ==", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/svgo/node_modules/dom-serializer": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", + "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", + "dependencies": { + "domelementtype": "^2.0.1", + "entities": "^2.0.0" + } + }, + "node_modules/svgo/node_modules/domutils": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", + "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "dependencies": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "node_modules/svgo/node_modules/domutils/node_modules/domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" + }, + "node_modules/svgo/node_modules/nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "dependencies": { + "boolbase": "~1.0.0" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" + }, + "node_modules/tailwindcss": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.2.7.tgz", + "integrity": "sha512-B6DLqJzc21x7wntlH/GsZwEXTBttVSl1FtCzC8WP4oBc/NKef7kaax5jeihkkCEWc831/5NDJ9gRNDK6NEioQQ==", + "dependencies": { + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "color-name": "^1.1.4", + "detective": "^5.2.1", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.2.12", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "lilconfig": "^2.0.6", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.0.9", + "postcss-import": "^14.1.0", + "postcss-js": "^4.0.0", + "postcss-load-config": "^3.1.4", + "postcss-nested": "6.0.0", + "postcss-selector-parser": "^6.0.11", + "postcss-value-parser": "^4.2.0", + "quick-lru": "^5.1.1", + "resolve": "^1.22.1" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=12.13.0" + }, + "peerDependencies": { + "postcss": "^8.0.9" + } + }, + "node_modules/tailwindcss/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/temp-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", + "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/tempy": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tempy/-/tempy-0.6.0.tgz", + "integrity": "sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw==", + "dependencies": { + "is-stream": "^2.0.0", + "temp-dir": "^2.0.0", + "type-fest": "^0.16.0", + "unique-string": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tempy/node_modules/type-fest": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz", + "integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/terminal-link": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", + "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", + "dependencies": { + "ansi-escapes": "^4.2.1", + "supports-hyperlinks": "^2.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/terser": { + "version": "5.16.5", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.16.5.tgz", + "integrity": "sha512-qcwfg4+RZa3YvlFh0qjifnzBHjKGNbtDo9yivMqMFDy9Q6FSaQWSB/j1xKhsoUFJIqDOM3TsN6D5xbrMrFcHbg==", + "dependencies": { + "@jridgewell/source-map": "^0.3.2", + "acorn": "^8.5.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.6", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.6.tgz", + "integrity": "sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ==", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.14", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.0", + "terser": "^5.14.1" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" + }, + "node_modules/throat": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.2.tgz", + "integrity": "sha512-WKexMoJj3vEuK0yFEapj8y64V0A6xcuPuK9Gt1d0R+dzCSJc0lHqQytAbSB4cDAK0dWh4T0E2ETkoLE2WZ41OQ==" }, "node_modules/throttle-debounce": { "version": "2.3.0", @@ -4388,6 +15956,16 @@ "node": ">=8" } }, + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==" + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==" + }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -4396,16 +15974,61 @@ "node": ">=4" } }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tough-cookie": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", + "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tough-cookie/node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, + "node_modules/tryer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz", + "integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==" + }, "node_modules/tsconfig-paths": { "version": "3.14.2", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", - "dev": true, "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.2", @@ -4417,7 +16040,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, "dependencies": { "minimist": "^1.2.0" }, @@ -4425,11 +16047,42 @@ "json5": "lib/cli.js" } }, + "node_modules/tsconfig-paths/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, "dependencies": { "prelude-ls": "^1.2.1" }, @@ -4437,11 +16090,18 @@ "node": ">= 0.8.0" } }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "engines": { + "node": ">=4" + } + }, "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "engines": { "node": ">=10" }, @@ -4449,11 +16109,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/typed-array-length": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "for-each": "^0.3.3", @@ -4463,11 +16134,31 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, "node_modules/unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-bigints": "^1.0.2", @@ -4478,11 +16169,87 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "engines": { + "node": ">=4" + } + }, + "node_modules/unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "dependencies": { + "crypto-random-string": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/unquote": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz", + "integrity": "sha512-vRCqFv6UhXpWxZPyGDh/F3ZpNv8/qo7w6iufLpQg9aKnQ71qM4B5KiI7Mia9COcjEhrO9LueHpMYjYzsWH3OIg==" + }, + "node_modules/upath": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", + "engines": { + "node": ">=4", + "yarn": "*" + } + }, "node_modules/update-browserslist-db": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", - "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", - "dev": true, + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", + "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", "funding": [ { "type": "opencollective", @@ -4491,10 +16258,6 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" } ], "dependencies": { @@ -4502,7 +16265,7 @@ "picocolors": "^1.0.0" }, "bin": { - "update-browserslist-db": "cli.js" + "browserslist-lint": "cli.js" }, "peerDependencies": { "browserslist": ">= 4.21.0" @@ -4512,57 +16275,86 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, "dependencies": { "punycode": "^2.1.0" } }, - "node_modules/vite": { - "version": "4.3.8", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.3.8.tgz", - "integrity": "sha512-uYB8PwN7hbMrf4j1xzGDk/lqjsZvCDbt/JC5dyfxc19Pg8kRm14LinK/uq+HSLNswZEoKmweGdtpbnxRtrAXiQ==", - "dev": true, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", "dependencies": { - "esbuild": "^0.17.5", - "postcss": "^8.4.23", - "rollup": "^3.21.0" + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/util.promisify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.1.tgz", + "integrity": "sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA==", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.2", + "has-symbols": "^1.0.1", + "object.getownpropertydescriptors": "^2.1.0" }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/utila": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", + "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", "bin": { - "vite": "bin/vite.js" + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-to-istanbul": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz", + "integrity": "sha512-FGtKtv3xIpR6BYhvgH8MI/y78oT7d8Au3ww4QIxymrCtZEh5b8gCw2siywE+puhEmuWKDtmfrvF5UlB298ut3w==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0", + "source-map": "^0.7.3" }, "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - }, - "peerDependencies": { - "@types/node": ">= 14", - "less": "*", - "sass": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "sass": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - } + "node": ">=10.12.0" + } + }, + "node_modules/v8-to-istanbul/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" } }, "node_modules/void-elements": { @@ -4573,10 +16365,419 @@ "node": ">=0.10.0" } }, + "node_modules/w3c-hr-time": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", + "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", + "deprecated": "Use your platform's native performance.now() and performance.timeOrigin.", + "dependencies": { + "browser-process-hrtime": "^1.0.0" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", + "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", + "dependencies": { + "xml-name-validator": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/watchpack": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dependencies": { + "minimalistic-assert": "^1.0.0" + } + }, "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", + "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", + "engines": { + "node": ">=10.4" + } + }, + "node_modules/webpack": { + "version": "5.75.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.75.0.tgz", + "integrity": "sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ==", + "dependencies": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^0.0.51", + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/wasm-edit": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.7.6", + "browserslist": "^4.14.5", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.10.0", + "es-module-lexer": "^0.9.0", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.9", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.1.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.1.3", + "watchpack": "^2.4.0", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-dev-middleware": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz", + "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==", + "dependencies": { + "colorette": "^2.0.10", + "memfs": "^3.4.3", + "mime-types": "^2.1.31", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/webpack-dev-middleware/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/webpack-dev-middleware/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/webpack-dev-middleware/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "node_modules/webpack-dev-middleware/node_modules/schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/webpack-dev-server": { + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.11.1.tgz", + "integrity": "sha512-lILVz9tAUy1zGFwieuaQtYiadImb5M3d+H+L1zDYalYoDl0cksAB1UNyuE5MMWJrG6zR1tXkCP2fitl7yoUJiw==", + "dependencies": { + "@types/bonjour": "^3.5.9", + "@types/connect-history-api-fallback": "^1.3.5", + "@types/express": "^4.17.13", + "@types/serve-index": "^1.9.1", + "@types/serve-static": "^1.13.10", + "@types/sockjs": "^0.3.33", + "@types/ws": "^8.5.1", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.0.11", + "chokidar": "^3.5.3", + "colorette": "^2.0.10", + "compression": "^1.7.4", + "connect-history-api-fallback": "^2.0.0", + "default-gateway": "^6.0.3", + "express": "^4.17.3", + "graceful-fs": "^4.2.6", + "html-entities": "^2.3.2", + "http-proxy-middleware": "^2.0.3", + "ipaddr.js": "^2.0.1", + "open": "^8.0.9", + "p-retry": "^4.5.0", + "rimraf": "^3.0.2", + "schema-utils": "^4.0.0", + "selfsigned": "^2.1.1", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^5.3.1", + "ws": "^8.4.2" + }, + "bin": { + "webpack-dev-server": "bin/webpack-dev-server.js" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.37.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/webpack-dev-server/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/webpack-dev-server/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "node_modules/webpack-dev-server/node_modules/schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/webpack-dev-server/node_modules/ws": { + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.12.1.tgz", + "integrity": "sha512-1qo+M9Ba+xNhPB+YTWUlK6M17brTut5EXbcBaMRN5pH5dFrXz7lzz1ChFSUq3bOUl8yEvSenhHmYUNJxFzdJew==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/webpack-manifest-plugin": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/webpack-manifest-plugin/-/webpack-manifest-plugin-4.1.1.tgz", + "integrity": "sha512-YXUAwxtfKIJIKkhg03MKuiFAD72PlrqCiwdwO4VEXdRO5V0ORCNwaOwAZawPZalCbmH9kBDmXnNeQOw+BIEiow==", + "dependencies": { + "tapable": "^2.0.0", + "webpack-sources": "^2.2.0" + }, + "engines": { + "node": ">=12.22.0" + }, + "peerDependencies": { + "webpack": "^4.44.2 || ^5.47.0" + } + }, + "node_modules/webpack-manifest-plugin/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==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-manifest-plugin/node_modules/webpack-sources": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-2.3.1.tgz", + "integrity": "sha512-y9EI9AO42JjEcrTJFOYmVywVZdKVUfOvDUPsJea5GIr1JOEGFVqwlY2K098fFoIjOkDzHn2AjRvM8dsBZu+gCA==", + "dependencies": { + "source-list-map": "^2.0.1", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack/node_modules/@types/estree": { + "version": "0.0.51", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", + "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==" + }, + "node_modules/webpack/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/webpack/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/whatwg-encoding": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", + "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "dependencies": { + "iconv-lite": "0.4.24" + } + }, + "node_modules/whatwg-encoding/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/whatwg-fetch": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz", + "integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==" + }, + "node_modules/whatwg-mimetype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==" }, "node_modules/whatwg-url": { "version": "5.0.0", @@ -4587,11 +16788,15 @@ "webidl-conversions": "^3.0.0" } }, + "node_modules/whatwg-url/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "dependencies": { "isexe": "^2.0.0" }, @@ -4606,7 +16811,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, "dependencies": { "is-bigint": "^1.0.1", "is-boolean-object": "^1.1.0", @@ -4622,7 +16826,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", - "dev": true, "dependencies": { "is-map": "^2.0.1", "is-set": "^2.0.1", @@ -4637,7 +16840,6 @@ "version": "1.1.9", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", - "dev": true, "dependencies": { "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", @@ -4657,22 +16859,416 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true, "engines": { "node": ">=0.10.0" } }, + "node_modules/workbox-background-sync": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-6.5.4.tgz", + "integrity": "sha512-0r4INQZMyPky/lj4Ou98qxcThrETucOde+7mRGJl13MPJugQNKeZQOdIJe/1AchOP23cTqHcN/YVpD6r8E6I8g==", + "dependencies": { + "idb": "^7.0.1", + "workbox-core": "6.5.4" + } + }, + "node_modules/workbox-broadcast-update": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/workbox-broadcast-update/-/workbox-broadcast-update-6.5.4.tgz", + "integrity": "sha512-I/lBERoH1u3zyBosnpPEtcAVe5lwykx9Yg1k6f8/BGEPGaMMgZrwVrqL1uA9QZ1NGGFoyE6t9i7lBjOlDhFEEw==", + "dependencies": { + "workbox-core": "6.5.4" + } + }, + "node_modules/workbox-build": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/workbox-build/-/workbox-build-6.5.4.tgz", + "integrity": "sha512-kgRevLXEYvUW9WS4XoziYqZ8Q9j/2ziJYEtTrjdz5/L/cTUa2XfyMP2i7c3p34lgqJ03+mTiz13SdFef2POwbA==", + "dependencies": { + "@apideck/better-ajv-errors": "^0.3.1", + "@babel/core": "^7.11.1", + "@babel/preset-env": "^7.11.0", + "@babel/runtime": "^7.11.2", + "@rollup/plugin-babel": "^5.2.0", + "@rollup/plugin-node-resolve": "^11.2.1", + "@rollup/plugin-replace": "^2.4.1", + "@surma/rollup-plugin-off-main-thread": "^2.2.3", + "ajv": "^8.6.0", + "common-tags": "^1.8.0", + "fast-json-stable-stringify": "^2.1.0", + "fs-extra": "^9.0.1", + "glob": "^7.1.6", + "lodash": "^4.17.20", + "pretty-bytes": "^5.3.0", + "rollup": "^2.43.1", + "rollup-plugin-terser": "^7.0.0", + "source-map": "^0.8.0-beta.0", + "stringify-object": "^3.3.0", + "strip-comments": "^2.0.1", + "tempy": "^0.6.0", + "upath": "^1.2.0", + "workbox-background-sync": "6.5.4", + "workbox-broadcast-update": "6.5.4", + "workbox-cacheable-response": "6.5.4", + "workbox-core": "6.5.4", + "workbox-expiration": "6.5.4", + "workbox-google-analytics": "6.5.4", + "workbox-navigation-preload": "6.5.4", + "workbox-precaching": "6.5.4", + "workbox-range-requests": "6.5.4", + "workbox-recipes": "6.5.4", + "workbox-routing": "6.5.4", + "workbox-strategies": "6.5.4", + "workbox-streams": "6.5.4", + "workbox-sw": "6.5.4", + "workbox-window": "6.5.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/workbox-build/node_modules/@apideck/better-ajv-errors": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@apideck/better-ajv-errors/-/better-ajv-errors-0.3.6.tgz", + "integrity": "sha512-P+ZygBLZtkp0qqOAJJVX4oX/sFo5JR3eBWwwuqHHhK0GIgQOKWrAfiAaWX0aArHkRWHMuggFEgAZNxVPwPZYaA==", + "dependencies": { + "json-schema": "^0.4.0", + "jsonpointer": "^5.0.0", + "leven": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "ajv": ">=8" + } + }, + "node_modules/workbox-build/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/workbox-build/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/workbox-build/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "node_modules/workbox-build/node_modules/source-map": { + "version": "0.8.0-beta.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", + "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", + "dependencies": { + "whatwg-url": "^7.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/workbox-build/node_modules/tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/workbox-build/node_modules/webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==" + }, + "node_modules/workbox-build/node_modules/whatwg-url": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", + "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "dependencies": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, + "node_modules/workbox-cacheable-response": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-6.5.4.tgz", + "integrity": "sha512-DCR9uD0Fqj8oB2TSWQEm1hbFs/85hXXoayVwFKLVuIuxwJaihBsLsp4y7J9bvZbqtPJ1KlCkmYVGQKrBU4KAug==", + "dependencies": { + "workbox-core": "6.5.4" + } + }, + "node_modules/workbox-core": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-6.5.4.tgz", + "integrity": "sha512-OXYb+m9wZm8GrORlV2vBbE5EC1FKu71GGp0H4rjmxmF4/HLbMCoTFws87M3dFwgpmg0v00K++PImpNQ6J5NQ6Q==" + }, + "node_modules/workbox-expiration": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/workbox-expiration/-/workbox-expiration-6.5.4.tgz", + "integrity": "sha512-jUP5qPOpH1nXtjGGh1fRBa1wJL2QlIb5mGpct3NzepjGG2uFFBn4iiEBiI9GUmfAFR2ApuRhDydjcRmYXddiEQ==", + "dependencies": { + "idb": "^7.0.1", + "workbox-core": "6.5.4" + } + }, + "node_modules/workbox-google-analytics": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-6.5.4.tgz", + "integrity": "sha512-8AU1WuaXsD49249Wq0B2zn4a/vvFfHkpcFfqAFHNHwln3jK9QUYmzdkKXGIZl9wyKNP+RRX30vcgcyWMcZ9VAg==", + "dependencies": { + "workbox-background-sync": "6.5.4", + "workbox-core": "6.5.4", + "workbox-routing": "6.5.4", + "workbox-strategies": "6.5.4" + } + }, + "node_modules/workbox-navigation-preload": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/workbox-navigation-preload/-/workbox-navigation-preload-6.5.4.tgz", + "integrity": "sha512-IIwf80eO3cr8h6XSQJF+Hxj26rg2RPFVUmJLUlM0+A2GzB4HFbQyKkrgD5y2d84g2IbJzP4B4j5dPBRzamHrng==", + "dependencies": { + "workbox-core": "6.5.4" + } + }, + "node_modules/workbox-precaching": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-6.5.4.tgz", + "integrity": "sha512-hSMezMsW6btKnxHB4bFy2Qfwey/8SYdGWvVIKFaUm8vJ4E53JAY+U2JwLTRD8wbLWoP6OVUdFlXsTdKu9yoLTg==", + "dependencies": { + "workbox-core": "6.5.4", + "workbox-routing": "6.5.4", + "workbox-strategies": "6.5.4" + } + }, + "node_modules/workbox-range-requests": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/workbox-range-requests/-/workbox-range-requests-6.5.4.tgz", + "integrity": "sha512-Je2qR1NXCFC8xVJ/Lux6saH6IrQGhMpDrPXWZWWS8n/RD+WZfKa6dSZwU+/QksfEadJEr/NfY+aP/CXFFK5JFg==", + "dependencies": { + "workbox-core": "6.5.4" + } + }, + "node_modules/workbox-recipes": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/workbox-recipes/-/workbox-recipes-6.5.4.tgz", + "integrity": "sha512-QZNO8Ez708NNwzLNEXTG4QYSKQ1ochzEtRLGaq+mr2PyoEIC1xFW7MrWxrONUxBFOByksds9Z4//lKAX8tHyUA==", + "dependencies": { + "workbox-cacheable-response": "6.5.4", + "workbox-core": "6.5.4", + "workbox-expiration": "6.5.4", + "workbox-precaching": "6.5.4", + "workbox-routing": "6.5.4", + "workbox-strategies": "6.5.4" + } + }, + "node_modules/workbox-routing": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-6.5.4.tgz", + "integrity": "sha512-apQswLsbrrOsBUWtr9Lf80F+P1sHnQdYodRo32SjiByYi36IDyL2r7BH1lJtFX8fwNHDa1QOVY74WKLLS6o5Pg==", + "dependencies": { + "workbox-core": "6.5.4" + } + }, + "node_modules/workbox-strategies": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-6.5.4.tgz", + "integrity": "sha512-DEtsxhx0LIYWkJBTQolRxG4EI0setTJkqR4m7r4YpBdxtWJH1Mbg01Cj8ZjNOO8etqfA3IZaOPHUxCs8cBsKLw==", + "dependencies": { + "workbox-core": "6.5.4" + } + }, + "node_modules/workbox-streams": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/workbox-streams/-/workbox-streams-6.5.4.tgz", + "integrity": "sha512-FXKVh87d2RFXkliAIheBojBELIPnWbQdyDvsH3t74Cwhg0fDheL1T8BqSM86hZvC0ZESLsznSYWw+Va+KVbUzg==", + "dependencies": { + "workbox-core": "6.5.4", + "workbox-routing": "6.5.4" + } + }, + "node_modules/workbox-sw": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/workbox-sw/-/workbox-sw-6.5.4.tgz", + "integrity": "sha512-vo2RQo7DILVRoH5LjGqw3nphavEjK4Qk+FenXeUsknKn14eCNedHOXWbmnvP4ipKhlE35pvJ4yl4YYf6YsJArA==" + }, + "node_modules/workbox-webpack-plugin": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/workbox-webpack-plugin/-/workbox-webpack-plugin-6.5.4.tgz", + "integrity": "sha512-LmWm/zoaahe0EGmMTrSLUi+BjyR3cdGEfU3fS6PN1zKFYbqAKuQ+Oy/27e4VSXsyIwAw8+QDfk1XHNGtZu9nQg==", + "dependencies": { + "fast-json-stable-stringify": "^2.1.0", + "pretty-bytes": "^5.4.1", + "upath": "^1.2.0", + "webpack-sources": "^1.4.3", + "workbox-build": "6.5.4" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "webpack": "^4.4.0 || ^5.9.0" + } + }, + "node_modules/workbox-webpack-plugin/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==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/workbox-webpack-plugin/node_modules/webpack-sources": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", + "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", + "dependencies": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + } + }, + "node_modules/workbox-window": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-6.5.4.tgz", + "integrity": "sha512-HnLZJDwYBE+hpG25AQBO8RUWBJRaCsI9ksQJEp3aCOFCaG5kqaToAYXFRAHxzRluM2cQbGzdQF5rjKPWPA1fug==", + "dependencies": { + "@types/trusted-types": "^2.0.2", + "workbox-core": "6.5.4" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "node_modules/ws": { + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==" + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" }, "node_modules/yaml": { "version": "1.10.2", @@ -4682,11 +17278,35 @@ "node": ">= 6" } }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "engines": { + "node": ">=10" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, "engines": { "node": ">=10" }, diff --git a/web/package.json b/web/package.json index c00e8c6..9e919ef 100644 --- a/web/package.json +++ b/web/package.json @@ -3,16 +3,14 @@ "version": "1.0.0", "private": true, "scripts": { - "start": "NODE_OPTIONS=\"--enable-source-maps\" vite", - "build": "vite build", - "serve": "vite preview", - "format": "prettier . --write", - "format:check": "prettier . --check", - "lint": "eslint --report-unused-disable-directives --ext .js,.jsx ./src/" + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test", + "eject": "react-scripts eject" }, "dependencies": { - "@emotion/react": "^11.11.0", - "@emotion/styled": "^11.11.0", + "@emotion/react": "^11.8.2", + "@emotion/styled": "^11.8.1", "@mui/icons-material": "^5.4.2", "@mui/material": "latest", "dexie": "^3.2.1", @@ -27,21 +25,10 @@ "react-i18next": "^11.16.2", "react-infinite-scroll-component": "^6.1.0", "react-router-dom": "^6.2.2", + "react-scripts": "^5.0.0", "stacktrace-gps": "^3.0.4", "stacktrace-js": "^2.0.2" }, - "devDependencies": { - "@vitejs/plugin-react": "^4.0.0", - "eslint": "^8.41.0", - "eslint-config-airbnb": "^19.0.4", - "eslint-config-prettier": "^8.8.0", - "eslint-plugin-import": "^2.27.5", - "eslint-plugin-jsx-a11y": "^6.7.1", - "eslint-plugin-react": "^7.32.2", - "eslint-plugin-react-hooks": "^4.6.0", - "prettier": "^2.8.8", - "vite": "^4.3.8" - }, "browserslist": { "production": [ ">0.2%", @@ -53,8 +40,5 @@ "last 1 firefox version", "last 1 safari version" ] - }, - "prettier": { - "printWidth": 140 } } diff --git a/web/public/config.js b/web/public/config.js index 2f46d65..30da691 100644 --- a/web/public/config.js +++ b/web/public/config.js @@ -6,14 +6,12 @@ // During web development, you may change values here for rapid testing. var config = { - base_url: window.location.origin, // Change to test against a different server - app_root: "/app", - enable_login: true, - enable_signup: true, - enable_payments: false, - enable_reservations: true, - enable_emails: true, - enable_calls: true, - billing_contact: "", - disallowed_topics: ["docs", "static", "file", "app", "account", "settings", "signup", "login", "v1"], + base_url: window.location.origin, // Change to test against a different server + app_root: "/app", + enable_login: true, + enable_signup: true, + enable_payments: true, + enable_reservations: true, + billing_contact: "", + disallowed_topics: ["docs", "static", "file", "app", "account", "settings", "signup", "login", "v1"] }; diff --git a/web/public/home.html b/web/public/home.html new file mode 100644 index 0000000..43007ca --- /dev/null +++ b/web/public/home.html @@ -0,0 +1,182 @@ + + + + + + ntfy.sh | Send push notifications to your phone via PUT/POST + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

Send push notifications to your phone or desktop via PUT/POST

+

+ ntfy (pronounce: notify) is a simple HTTP-based pub-sub notification service. + It allows you to send notifications to your phone or desktop via scripts from any computer, + entirely without signup, cost or setup. It's also open source if you want to run your own. +

+
+ + + + + + + +
+ +

Publishing messages

+

+ Publishing messages can be done via PUT or POST. Topics are created on the fly by subscribing or publishing to them. + Because there is no sign-up, the topic is essentially a password, so pick something that's not easily guessable. +

+

+ Here's an example showing how to publish a message using a POST request (via curl -d): +

+ + curl -d "Backup successful 😀" ntfy.sh/mytopic + +

+ There are more features related to publishing messages: You can set a + notification priority, a title, + and tag messages. + Here's an example using some of them together: +

+ + curl \
+   -H "Title: Unauthorized access detected" \
+   -H "Priority: urgent" \
+   -H "Tags: warning,skull" \
+   -d "Remote access to $(hostname) detected. Act right away." \
+   ntfy.sh/mytopic +
+

+ Here's what that looks like in the Android app: +

+
+ +
Urgent notification with pop-over
+
+ +

Subscribe to a topic

+

+ You can create and subscribe to a topic either using your phone, + in this web UI, or in your own app by subscribing via the API. +

+ +

Subscribe from your phone

+

+ Simply get the app and start publishing messages. To learn more about the app, + check out the documentation. +

+

+ + + +

+

+ Here's a video showing the app in action: +

+
+ +
Sending push notifications to your Android phone
+
+ +

Subscribe via web app

+

+ Subscribe to topics in the web app and receive messages as desktop notification. + It is available at ntfy.sh/app. +

+
+ +
ntfy web app, available at ntfy.sh/app
+
+ +

Subscribe using the API

+

+ There's a super simple API that you can use to integrate your own app. You can consume + a JSON stream, + an SSE/EventSource stream, + a plain text stream, + or via WebSockets. +

+

+ Here's an example for JSON. The connection stays open, so you can retrieve messages as they come in: +

+ + $ curl -s ntfy.sh/mytopic/json
+ {"id":"SLiKI64DOt","time":1635528757,"event":"open","topic":"mytopic"}
+ {"id":"hwQ2YpKdmg","time":1635528741,"event":"message","topic":"mytopic","message":"Hi!"}
+ {"id":"DGUDShMCsc","time":1635528787,"event":"keepalive","topic":"mytopic"}
+ ... +
+

+ Here's a short video demonstrating it in action: +

+
+ +
Subscribing to the JSON stream with curl
+
+ +

Check out the docs!

+

+ ntfy has so many more features and you can learn about all of them in the documentation + (I tried my very best to make it the best docs ever 😉, not sure if I succeeded, hehe). +

+
+ +
Check out the documentation
+
+ +

100% open source & forever free

+

+ I love free software, and I'm doing this because it's fun. I have no bad intentions, and I will + never monetize or sell your information. This service will always stay + free and open. + You can read more in the FAQs and in the privacy policy. +

+ +
Made with ❤️ by Philipp C. Heckel
+
+ + + + diff --git a/web/public/index.html b/web/public/index.html new file mode 100644 index 0000000..4dd8ef2 --- /dev/null +++ b/web/public/index.html @@ -0,0 +1,44 @@ + + + + + ntfy web + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + diff --git a/web/public/static/css/app.css b/web/public/static/css/app.css index 213859c..12b105a 100644 --- a/web/public/static/css/app.css +++ b/web/public/static/css/app.css @@ -1,11 +1,10 @@ /* web app styling overrides */ -a, -a:visited { - color: #338574; +a, a:visited { + color: #338574; } a:hover { - text-decoration: none; - color: #317f6f; + text-decoration: none; + color: #317f6f; } diff --git a/web/public/static/css/fonts.css b/web/public/static/css/fonts.css index 2cf00a3..d14bad0 100644 --- a/web/public/static/css/fonts.css +++ b/web/public/static/css/fonts.css @@ -2,32 +2,40 @@ /* roboto-300 - latin */ @font-face { - font-family: "Roboto"; - font-style: normal; - font-weight: 300; - src: local(""), url("../fonts/roboto-v29-latin-300.woff2") format("woff2"); + font-family: 'Roboto'; + font-style: normal; + font-weight: 300; + src: local(''), + url('../fonts/roboto-v29-latin-300.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ + url('../fonts/roboto-v29-latin-300.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ } /* roboto-regular - latin */ @font-face { - font-family: "Roboto"; - font-style: normal; - font-weight: 400; - src: local(""), url("../fonts/roboto-v29-latin-regular.woff2") format("woff2"); + font-family: 'Roboto'; + font-style: normal; + font-weight: 400; + src: local(''), + url('../fonts/roboto-v29-latin-regular.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ + url('../fonts/roboto-v29-latin-regular.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ } /* roboto-500 - latin */ @font-face { - font-family: "Roboto"; - font-style: normal; - font-weight: 500; - src: local(""), url("../fonts/roboto-v29-latin-500.woff2") format("woff2"); + font-family: 'Roboto'; + font-style: normal; + font-weight: 500; + src: local(''), + url('../fonts/roboto-v29-latin-500.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ + url('../fonts/roboto-v29-latin-500.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ } /* roboto-700 - latin */ @font-face { - font-family: "Roboto"; - font-style: normal; - font-weight: 700; - src: local(""), url("../fonts/roboto-v29-latin-700.woff2") format("woff2"); + font-family: 'Roboto'; + font-style: normal; + font-weight: 700; + src: local(''), + url('../fonts/roboto-v29-latin-700.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ + url('../fonts/roboto-v29-latin-700.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ } diff --git a/web/public/static/css/home.css b/web/public/static/css/home.css new file mode 100644 index 0000000..feeaa7e --- /dev/null +++ b/web/public/static/css/home.css @@ -0,0 +1,280 @@ +/* general styling */ + +html, body { + font-family: 'Roboto', sans-serif; + font-weight: 400; + font-size: 1.1em; + color: #444; + margin: 0; + padding: 0; +} + +html { + /* prevent scrollbar from repositioning website: + * https://www.w3docs.com/snippets/css/how-to-prevent-scrollbar-from-repositioning-web-page.html */ + overflow-y: scroll; +} + +a, a:visited { + color: #338574; +} + +a:hover { + text-decoration: none; + color: #317f6f; +} + +h1 { + margin-top: 35px; + margin-bottom: 30px; + font-size: 2.5em; + word-wrap: break-word; /* For very long topics */ + padding-right: 40px; /* For the X on the detail page */ + font-weight: 300; + color: #666; +} + +h2 { + margin-top: 30px; + margin-bottom: 5px; + font-size: 1.8em; + font-weight: 300; + color: #333; +} + +h3 { + margin-top: 25px; + margin-bottom: 5px; + font-size: 1.3em; + font-weight: 300; + color: #333; +} + +p { + margin-top: 10px; + margin-bottom: 20px; + line-height: 160%; + font-weight: 400; +} + +p.smallMarginBottom { + margin-bottom: 10px; +} + +b { + font-weight: 500; +} + +tt { + background: #eee; + padding: 2px 7px; + border-radius: 3px; +} + +code { + display: block; + background: #eee; + font-family: monospace; + padding: 20px; + border-radius: 3px; + margin-top: 10px; + margin-bottom: 20px; + overflow-x: auto; + white-space: nowrap; +} + +/* Main page */ + +#main { + max-width: 900px; + margin: 0 auto 50px auto; + padding: 0 10px; +} + +#error { + color: darkred; + font-style: italic; +} + +#ironicCenterTagDontFreakOut { + color: #666; +} + +/* Anchors */ + +.anchor .anchorLink { + color: #ccc; + text-decoration: none; + padding: 0 5px; + visibility: hidden; +} + +.anchor:hover .anchorLink { + visibility: visible; +} + +.anchor .anchorLink:hover { + color: #338574; + visibility: visible; +} + +/* Figures */ + +figure { + text-align: center; +} + +figure img, figure video { + filter: drop-shadow(3px 3px 3px #ccc); + border-radius: 7px; + max-width: 100%; +} + +figure video { + width: 100%; + max-height: 450px; +} + +figcaption { + text-align: center; + font-style: italic; + padding-top: 10px; +} + +/* Screenshots */ + +#screenshots { + text-align: center; +} + +#screenshots img { + height: 190px; + margin: 3px; + border-radius: 5px; + filter: drop-shadow(2px 2px 2px #ddd); +} + +#screenshots .nowrap { + white-space: nowrap; +} + +/* Lightbox; thanks to https://yossiabramov.com/blog/vanilla-js-lightbox */ + +.lightbox { + opacity: 0; + visibility: hidden; + position: fixed; + left:0; + right: 0; + top: 0; + bottom: 0; + z-index: -1; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.15s ease-in; +} + +.lightbox.show { + background-color: rgba(0,0,0, 0.75); + opacity: 1; + visibility: visible; + z-index: 1000; +} + +.lightbox img { + max-width: 90%; + max-height: 90%; + filter: drop-shadow(5px 5px 10px #222); + border-radius: 5px; +} + +.lightbox .close-lightbox { + cursor: pointer; + position: absolute; + top: 30px; + right: 30px; + width: 20px; + height: 20px; +} + +.lightbox .close-lightbox::after, +.lightbox .close-lightbox::before { + content: ''; + width: 3px; + height: 20px; + background-color: #ddd; + position: absolute; + border-radius: 5px; + transform: rotate(45deg); +} + +.lightbox .close-lightbox::before { + transform: rotate(-45deg); +} + +.lightbox .close-lightbox:hover::after, +.lightbox .close-lightbox:hover::before { + background-color: #fff; +} + +/* Header */ + +#header { + background: #338574; + height: 130px; +} + +#header #headerBox { + max-width: 900px; + margin: 0 auto; + padding: 0 10px; +} + +#header #logo { + margin-top: 23px; + float: left; +} + +#header #name { + float: left; + color: white; + font-size: 2.6em; + font-weight: 300; + margin: 35px 0 0 20px; +} + +#header ol { + list-style-type: none; + float: right; + margin-top: 80px; +} + +#header ol li { + display: inline-block; + margin: 0 10px; + font-weight: 400; +} + +#header ol li a, nav ol li a:visited { + color: white; + text-decoration: none; +} + +#header ol li a:hover { + text-decoration: underline; +} + +li { + padding: 4px 0; + margin: 4px 0; + font-size: 0.9em; +} + + +/* Hide top menu SMALL SCREEN */ +@media only screen and (max-width: 780px) { + #header ol { + display: none; + } +} diff --git a/web/public/static/fonts/roboto-v29-latin-300.woff b/web/public/static/fonts/roboto-v29-latin-300.woff new file mode 100644 index 0000000..5565042 Binary files /dev/null and b/web/public/static/fonts/roboto-v29-latin-300.woff differ diff --git a/web/public/static/fonts/roboto-v29-latin-500.woff b/web/public/static/fonts/roboto-v29-latin-500.woff new file mode 100644 index 0000000..c9eb5ca Binary files /dev/null and b/web/public/static/fonts/roboto-v29-latin-500.woff differ diff --git a/web/public/static/fonts/roboto-v29-latin-700.woff b/web/public/static/fonts/roboto-v29-latin-700.woff new file mode 100644 index 0000000..a5d98fc Binary files /dev/null and b/web/public/static/fonts/roboto-v29-latin-700.woff differ diff --git a/web/public/static/fonts/roboto-v29-latin-regular.woff b/web/public/static/fonts/roboto-v29-latin-regular.woff new file mode 100644 index 0000000..86b3863 Binary files /dev/null and b/web/public/static/fonts/roboto-v29-latin-regular.woff differ diff --git a/web/public/static/images/favicon.ico b/web/public/static/images/favicon.ico deleted file mode 100644 index 857fa54..0000000 Binary files a/web/public/static/images/favicon.ico and /dev/null differ diff --git a/web/public/static/img/android-video-overview.mp4 b/web/public/static/img/android-video-overview.mp4 new file mode 100644 index 0000000..cf29509 Binary files /dev/null and b/web/public/static/img/android-video-overview.mp4 differ diff --git a/web/public/static/img/android-video-subscribe-api.mp4 b/web/public/static/img/android-video-subscribe-api.mp4 new file mode 100644 index 0000000..d73e5c6 Binary files /dev/null and b/web/public/static/img/android-video-subscribe-api.mp4 differ diff --git a/web/public/static/img/badge-appstore.png b/web/public/static/img/badge-appstore.png new file mode 100644 index 0000000..0b4ce1c Binary files /dev/null and b/web/public/static/img/badge-appstore.png differ diff --git a/web/public/static/img/badge-fdroid.png b/web/public/static/img/badge-fdroid.png new file mode 100644 index 0000000..9464d38 Binary files /dev/null and b/web/public/static/img/badge-fdroid.png differ diff --git a/web/public/static/img/badge-googleplay.png b/web/public/static/img/badge-googleplay.png new file mode 100644 index 0000000..36036d8 Binary files /dev/null and b/web/public/static/img/badge-googleplay.png differ diff --git a/web/public/static/img/favicon.png b/web/public/static/img/favicon.png new file mode 100644 index 0000000..92312fe Binary files /dev/null and b/web/public/static/img/favicon.png differ diff --git a/web/public/static/images/ntfy.png b/web/public/static/img/ntfy.png similarity index 100% rename from web/public/static/images/ntfy.png rename to web/public/static/img/ntfy.png diff --git a/.github/images/screenshot-curl.png b/web/public/static/img/screenshot-curl.png similarity index 100% rename from .github/images/screenshot-curl.png rename to web/public/static/img/screenshot-curl.png diff --git a/web/public/static/img/screenshot-docs.png b/web/public/static/img/screenshot-docs.png new file mode 100644 index 0000000..4345ded Binary files /dev/null and b/web/public/static/img/screenshot-docs.png differ diff --git a/web/public/static/img/screenshot-phone-add.jpg b/web/public/static/img/screenshot-phone-add.jpg new file mode 100644 index 0000000..f728ec9 Binary files /dev/null and b/web/public/static/img/screenshot-phone-add.jpg differ diff --git a/.github/images/screenshot-phone-detail.jpg b/web/public/static/img/screenshot-phone-detail.jpg similarity index 100% rename from .github/images/screenshot-phone-detail.jpg rename to web/public/static/img/screenshot-phone-detail.jpg diff --git a/.github/images/screenshot-phone-main.jpg b/web/public/static/img/screenshot-phone-main.jpg similarity index 100% rename from .github/images/screenshot-phone-main.jpg rename to web/public/static/img/screenshot-phone-main.jpg diff --git a/.github/images/screenshot-phone-notification.jpg b/web/public/static/img/screenshot-phone-notification.jpg similarity index 100% rename from .github/images/screenshot-phone-notification.jpg rename to web/public/static/img/screenshot-phone-notification.jpg diff --git a/web/public/static/img/screenshot-phone-popover.png b/web/public/static/img/screenshot-phone-popover.png new file mode 100644 index 0000000..31d1515 Binary files /dev/null and b/web/public/static/img/screenshot-phone-popover.png differ diff --git a/.github/images/screenshot-web-detail.png b/web/public/static/img/screenshot-web-detail.png similarity index 100% rename from .github/images/screenshot-web-detail.png rename to web/public/static/img/screenshot-web-detail.png diff --git a/web/public/static/js/home.js b/web/public/static/js/home.js new file mode 100644 index 0000000..80b1405 --- /dev/null +++ b/web/public/static/js/home.js @@ -0,0 +1,84 @@ + +/* All the things */ + +let currentUrl = window.location.hostname; +if (window.location.port) { + currentUrl += ':' + window.location.port +} + +/* Screenshots */ +const lightbox = document.getElementById("lightbox"); + +const showScreenshotOverlay = (e, el, index) => { + lightbox.classList.add('show'); + document.addEventListener('keydown', nextScreenshotKeyboardListener); + return showScreenshot(e, index); +}; + +const showScreenshot = (e, index) => { + const actualIndex = resolveScreenshotIndex(index); + lightbox.innerHTML = '
' + screenshots[actualIndex].innerHTML; + lightbox.querySelector('img').onclick = (e) => { return showScreenshot(e,actualIndex+1); }; + currentScreenshotIndex = actualIndex; + e.stopPropagation(); + return false; +}; + +const nextScreenshot = (e) => { + return showScreenshot(e, currentScreenshotIndex+1); +}; + +const previousScreenshot = (e) => { + return showScreenshot(e, currentScreenshotIndex-1); +}; + +const resolveScreenshotIndex = (index) => { + if (index < 0) { + return screenshots.length - 1; + } else if (index > screenshots.length - 1) { + return 0; + } + return index; +}; + +const hideScreenshotOverlay = (e) => { + lightbox.classList.remove('show'); + document.removeEventListener('keydown', nextScreenshotKeyboardListener); +}; + +const nextScreenshotKeyboardListener = (e) => { + switch (e.keyCode) { + case 37: + previousScreenshot(e); + break; + case 39: + nextScreenshot(e); + break; + } +}; + +let currentScreenshotIndex = 0; +const screenshots = [...document.querySelectorAll("#screenshots a")]; +screenshots.forEach((el, index) => { + el.onclick = (e) => { return showScreenshotOverlay(e, el, index); }; +}); + +lightbox.onclick = hideScreenshotOverlay; + +// Add anchor links +document.querySelectorAll('.anchor').forEach((el) => { + if (el.hasAttribute('id')) { + const id = el.getAttribute('id'); + const anchor = document.createElement('a'); + anchor.innerHTML = `#`; + el.appendChild(anchor); + } +}); + +// Change ntfy.sh url and protocol to match self-hosted one +document.querySelectorAll('.ntfyUrl').forEach((el) => { + el.innerHTML = currentUrl; +}); +document.querySelectorAll('.ntfyProtocol').forEach((el) => { + el.innerHTML = window.location.protocol + "//"; +}); diff --git a/web/public/static/langs/ar.json b/web/public/static/langs/ar.json index 0c9fcc7..79c9d2f 100644 --- a/web/public/static/langs/ar.json +++ b/web/public/static/langs/ar.json @@ -56,12 +56,12 @@ "publish_dialog_title_topic": "أنشُر إلى {{topic}}", "publish_dialog_title_no_topic": "انشُر الإشعار", "publish_dialog_emoji_picker_show": "اختر رمزًا تعبيريًا", - "publish_dialog_priority_min": "أولوية دنيا", + "publish_dialog_priority_min": "الحد الأدنى للأولوية", "publish_dialog_priority_low": "أولوية منخفضة", "publish_dialog_priority_default": "الأولوية الافتراضية", "publish_dialog_priority_high": "أولوية عالية", "publish_dialog_base_url_label": "الرابط التشعبي للخدمة", - "publish_dialog_priority_max": "أولوية قصوى", + "publish_dialog_priority_max": "الأولوية القصوى", "publish_dialog_topic_placeholder": "اسم الموضوع، على سبيل المثال phil_alerts", "publish_dialog_title_label": "العنوان", "publish_dialog_title_placeholder": "عنوان الإشعار، على سبيل المثال تنبيه مساحة القرص", @@ -152,9 +152,9 @@ "publish_dialog_chip_delay_label": "تأخير التسليم", "subscribe_dialog_login_description": "هذا الموضوع محمي بكلمة مرور. الرجاء إدخال اسم المستخدم وكلمة المرور للاشتراك.", "subscribe_dialog_subscribe_button_cancel": "إلغاء", - "common_back": "العودة", + "subscribe_dialog_login_button_back": "العودة", "prefs_notifications_sound_play": "تشغيل الصوت المحدد", - "prefs_notifications_min_priority_title": "أولوية دنيا", + "prefs_notifications_min_priority_title": "الحد الأدنى للأولوية", "prefs_notifications_min_priority_max_only": "الأولوية القصوى فقط", "notifications_no_subscriptions_description": "انقر فوق الرابط \"{{linktext}}\" لإنشاء موضوع أو الاشتراك فيه. بعد ذلك، يمكنك إرسال رسائل عبر PUT أو POST وستتلقى إشعارات هنا.", "publish_dialog_click_label": "الرابط التشعبي URL للنقر", @@ -214,8 +214,8 @@ "account_delete_description": "احذف حسابك نهائيا", "account_delete_dialog_label": "كلمة المرور", "account_upgrade_dialog_title": "تغيير فئة الحساب", - "account_upgrade_dialog_tier_features_messages_other": "{{messages}} رسائل يومية", - "account_upgrade_dialog_tier_features_emails_other": "{{emails}} من رسائل البريد الإلكتروني اليومية", + "account_upgrade_dialog_tier_features_messages": "{{messages}} رسائل يومية", + "account_upgrade_dialog_tier_features_emails": "{{emails}} من رسائل البريد الإلكتروني اليومية", "account_upgrade_dialog_button_cancel": "إلغاء", "account_upgrade_dialog_button_pay_now": "ادفع الآن واشترك", "account_upgrade_dialog_button_cancel_subscription": "إلغاء الاشتراك", @@ -225,7 +225,7 @@ "account_tokens_table_expires_header": "تنتهي مدة صلاحيته في", "account_tokens_table_never_expires": "لا تنتهي صلاحيتها أبدا", "account_tokens_table_current_session": "جلسة المتصفح الحالية", - "common_copy_to_clipboard": "انسخ إلى الحافظة", + "account_tokens_table_copy_to_clipboard": "انسخ إلى الحافظة", "account_tokens_table_cannot_delete_or_edit": "لا يمكن تحرير أو حذف الرمز المميز للجلسة الحالية", "account_tokens_table_create_token_button": "إنشاء رمز مميز للوصول", "account_tokens_table_last_origin_tooltip": "من عنوان IP {{ip}}، انقر للبحث", @@ -283,51 +283,5 @@ "publish_dialog_details_examples_description": "للحصول على أمثلة ووصف مُفصّل لجميع ميزات الإرسال، يرجى الاستناد إلى الدليل.", "subscribe_dialog_subscribe_description": "قد لا تكون الموضوعات محمية بكلمة سر لذا اختر اسمًا ليس من السهل تخمينه وبمجرد اشتراكك، يمكنك الحصول على إشعارات عبر \"PUT/POST\".", "prefs_notifications_sound_description_some": "تقوم الإشعارات بتشغيل صوت {{sound}} عند وصولها", - "notifications_none_for_topic_description": "لإرسال إشعارات إلى هذا الموضوع، ما عليك سوى PUT أو POST إلى عنوان URL الخاص بالموضوع.", - "priority_low": "منخفضة", - "signup_form_toggle_password_visibility": "تبديل رؤية كلمة المرور", - "account_usage_limits_reset_daily": "يعاد تحديد حدود الاستخدام يوميا في منتصف الليل (UTC)", - "account_tokens_table_label_header": "المُلصَقة", - "account_upgrade_dialog_button_redirect_signup": "تسجيل فوري", - "account_upgrade_dialog_tier_current_label": "الحالي", - "account_tokens_dialog_expires_x_days": "تنتهي صلاحية الرمز المميز في غضون {{days}} أيام", - "prefs_reservations_dialog_title_add": "حجز موضوع", - "prefs_reservations_description": "يمكنك حجز أسماء الموضوعات للاستخدام الشخصي هنا. يمنحك حجز موضوع ما ملكية الموضوع، ويسمح لك بتحديد تصريحات الوصول للمستخدمين الآخرين إلى الموضوع.", - "prefs_users_description_no_sync": "لا تتم مزامنة المستخدمين وكلمات المرور مع حسابك.", - "reservation_delete_dialog_action_delete_description": "سيتم حذف الرسائل والمرفقات المخزنة مؤقتا نهائيا. لا يمكن التراجع عن هذا الإجراء.", - "notifications_actions_http_request_title": "إرسال طلب HTTP {{method}} إلى {{url}}", - "notifications_none_for_any_description": "لإرسال إشعارات إلى موضوع ما، ما عليك سوى إرسال طلب PUT أو POST إلى الرابط التشعبي URL للموضوع. إليك مثال باستخدام أحد مواضيعك.", - "error_boundary_description": "من الواضح أن هذا لا ينبغي أن يحدث. آسف جدًا بشأن هذا.
إن كان لديك دقيقة، يرجى الإبلاغ عن ذلك على GitHub ، أو إعلامنا عبر Discord أو Matrix .", - "nav_button_muted": "الإشعارات المكتومة", - "priority_min": "دنيا", - "signup_error_username_taken": "تم حجز اسم المستخدم {{username}} مِن قَبلُ", - "action_bar_reservation_limit_reached": "بلغت الحد الأقصى", - "prefs_reservations_delete_button": "إعادة تعيين الوصول إلى الموضوع", - "prefs_reservations_edit_button": "تعديل الوصول إلى موضوع", - "prefs_reservations_limit_reached": "لقد بلغت الحد الأقصى من المواضيع المحجوزة.", - "reservation_delete_dialog_action_keep_description": "ستصبح الرسائل والمرفقات المخزنة مؤقتًا على الخادم مرئية للعموم وللأشخاص الذين لديهم معرفة باسم الموضوع.", - "reservation_delete_dialog_description": "تؤدي إزالة الحجز إلى التخلي عن ملكية الموضوع، مما يسمح للآخرين بحجزه. يمكنك الاحتفاظ بالرسائل والمرفقات الموجودة أو حذفها.", - "prefs_reservations_dialog_description": "يمنحك حجز موضوع ما ملكية الموضوع، ويسمح لك بتحديد تصريحات وصول المستخدمين الآخرين إليه.", - "account_upgrade_dialog_interval_yearly_discount_save_up_to": "توفير ما يصل إلى {{discount}}٪", - "account_upgrade_dialog_interval_monthly": "شهريا", - "account_upgrade_dialog_tier_features_attachment_total_size": "إجمالي مساحة التخزين {{totalsize}}", - "publish_dialog_progress_uploading_detail": "تحميل {{loaded}}/{{total}} ({{percent}}٪) …", - "account_basics_tier_interval_monthly": "شهريا", - "account_basics_tier_interval_yearly": "سنويا", - "account_upgrade_dialog_tier_features_reservations_other": "{{reservations}} مواضيع محجوزة", - "account_upgrade_dialog_billing_contact_website": "للأسئلة المتعلقة بالفوترة، يرجى الرجوع إلى موقعنا على الويب.", - "prefs_notifications_min_priority_description_x_or_higher": "إظهار الإشعارات إذا كانت الأولوية {{number}} ({{name}}) أو أعلى", - "account_upgrade_dialog_billing_contact_email": "للأسئلة المتعلقة بالفوترة، الرجاء الاتصال بنا مباشرة.", - "account_upgrade_dialog_tier_selected_label": "المحدد", - "account_upgrade_dialog_tier_features_attachment_file_size": "{{filesize}} لكل ملف", - "account_upgrade_dialog_interval_yearly": "سنويا", - "account_upgrade_dialog_tier_features_no_reservations": "لا توجد مواضيع محجوزة", - "account_upgrade_dialog_interval_yearly_discount_save": "وفر {{discount}}٪", - "publish_dialog_click_reset": "إزالة الرابط التشعبي URL للنقر", - "prefs_notifications_min_priority_description_max": "إظهار الإشعارات إذا كانت الأولوية 5 (كحد أقصى)", - "publish_dialog_attachment_limits_file_reached": "يتجاوز الحد الأقصى للملف {{fileSizeLimit}}", - "publish_dialog_attachment_limits_quota_reached": "يتجاوز الحصة، {{remainingBytes}} متبقية", - "account_basics_tier_paid_until": "تم دفع مبلغ الاشتراك إلى غاية {{date}}، وسيتم تجديده تِلْقائيًا", - "account_basics_tier_canceled_subscription": "تم إلغاء اشتراكك وسيتم إعادته إلى مستوى حساب مجاني بداية مِن {{date}}.", - "account_delete_dialog_billing_warning": "إلغاء حسابك أيضاً يلغي اشتراكك في الفوترة فوراً ولن تتمكن من الوصول إلى لوح الفوترة بعد الآن." + "notifications_none_for_topic_description": "لإرسال إشعارات إلى هذا الموضوع، ما عليك سوى PUT أو POST إلى عنوان URL الخاص بالموضوع." } diff --git a/web/public/static/langs/bg.json b/web/public/static/langs/bg.json index a040b01..11987f8 100644 --- a/web/public/static/langs/bg.json +++ b/web/public/static/langs/bg.json @@ -104,7 +104,7 @@ "subscribe_dialog_subscribe_topic_placeholder": "Име на темата, напр. phils_alerts", "subscribe_dialog_subscribe_use_another_label": "Използване на друг сървър", "subscribe_dialog_login_username_label": "Потребител, напр. phil", - "common_back": "Назад", + "subscribe_dialog_login_button_back": "Назад", "subscribe_dialog_subscribe_button_cancel": "Отказ", "subscribe_dialog_login_description": "Темата е защитена. За да се абонирате въведете потребител и парола.", "subscribe_dialog_subscribe_button_subscribe": "Абониране", @@ -228,64 +228,5 @@ "account_basics_username_description": "Хей, това сте вие ❤", "account_basics_username_admin_tooltip": "Вие сте администратор", "account_basics_password_title": "Парола", - "account_delete_dialog_label": "Парола", - "account_basics_password_dialog_title": "Смяна на парола", - "account_basics_password_dialog_current_password_label": "Текуща парола", - "account_basics_password_dialog_new_password_label": "Нова парола", - "account_basics_password_dialog_confirm_password_label": "Парола отново", - "account_basics_password_dialog_button_submit": "Смяна на парола", - "account_usage_title": "Употреба", - "account_usage_of_limit": "от {{limit}}", - "account_usage_unlimited": "Неограничено", - "account_usage_limits_reset_daily": "Ограниченията се нулират всеки ден в полунощ (UTC)", - "account_basics_tier_interval_monthly": "месечно", - "account_basics_tier_interval_yearly": "годишно", - "account_basics_password_description": "Промяна на паролата на профила", - "account_basics_tier_title": "Вид на профила", - "account_basics_tier_admin": "Администратор", - "account_basics_tier_admin_suffix_with_tier": "(с {{tier}} ниво)", - "account_basics_tier_admin_suffix_no_tier": "(без ниво)", - "account_basics_tier_free": "безплатен", - "account_basics_tier_basic": "базов", - "account_basics_tier_change_button": "Променяне", - "account_basics_tier_paid_until": "Абонаментът е платен до {{date}} и автоматично ще се поднови", - "account_usage_attachment_storage_title": "Хранилище за прикачени файлове", - "account_delete_dialog_button_cancel": "Отказ", - "account_upgrade_dialog_interval_monthly": "Месечно", - "account_upgrade_dialog_tier_features_reservations_other": "{{reservations}} резервирани теми", - "account_upgrade_dialog_tier_features_no_reservations": "Няма резервирани теми", - "account_tokens_dialog_button_cancel": "Отказ", - "account_delete_title": "Премахване на профила", - "account_upgrade_dialog_title": "Промяна нивото на профила", - "account_usage_emails_title": "Изпратени съобщения", - "account_usage_reservations_title": "Резервирани теми", - "account_usage_reservations_none": "Няма резервирани теми", - "account_usage_cannot_create_portal_session": "Порталът за разплащане не може да бъде отворен", - "account_upgrade_dialog_interval_yearly": "Годишно", - "account_delete_description": "Безвъзвратно премахване на профила", - "account_delete_dialog_button_submit": "Безвъзвратно премахване на профила", - "account_upgrade_dialog_interval_yearly_discount_save": "отстъпка {{discount}}%", - "account_upgrade_dialog_button_cancel": "Отказ", - "account_upgrade_dialog_button_redirect_signup": "Регистриране", - "account_tokens_table_label_header": "Етикет", - "prefs_reservations_edit_button": "Настройки на достъпа", - "prefs_reservations_table_topic_header": "Тема", - "prefs_reservations_table_access_header": "Достъп", - "prefs_reservations_dialog_topic_label": "Тема", - "prefs_reservations_dialog_access_label": "Достъп", - "account_basics_password_dialog_current_password_incorrect": "Грешна парола", - "account_basics_tier_description": "Ниво на профила", - "account_basics_tier_upgrade_button": "Надграждане до Pro", - "account_usage_messages_title": "Публикувани съобщения", - "account_tokens_table_last_access_header": "Последен достъп", - "account_basics_tier_payment_overdue": "Имате просрочено задължение. Обновете начина на плащане, защото в противен случай скоро профилът ви ще загуби предимствата на абонамента.", - "account_usage_basis_ip_description": "Статистиката и ограниченията на използване се отчитат по IP адрес, така че може да бъдат споделени с други потребители. Показаните по-горе ограничения са приблизителни и се основават на съществуващите ограничения на използване.", - "account_delete_dialog_description": "Това действие ще доведе до безвъзвратното изтриване на профила ви, включително на всички данни, които се съхраняват на сървъра. След изтриването потребителското ви име няма да бъде достъпно в продължение на 7 дни. Ако наистина искате да продължите, потвърдете с паролата си в полето по-долу.", - "account_upgrade_dialog_tier_features_reservations_one": "{{reservations}} резервирана тема", - "account_upgrade_dialog_interval_yearly_discount_save_up_to": "спестете до {{discount}}%", - "account_delete_dialog_billing_warning": "Изтриването на профила незабавно отменя и платения абонамент. Няма да имате достъп до таблото за плащания.", - "account_upgrade_dialog_cancel_warning": "Това действие ще прекрати абонамента и ще промени профила ви на неплатен на {{date}}. На тази дата резервираните теми, както и пазените на сървъра съобщения, ще бъдат премахнати.", - "account_upgrade_dialog_proration_info": "Преизчисляване на плащания: При надграждане между платени планове разликата в цената ще бъде начислена незабавно. При преминаване към по-евтин план надплатената сума ще бъде използвана за плащане за бъдещи периоди.", - "account_basics_tier_manage_billing_button": "Управление на плащанията", - "account_basics_tier_canceled_subscription": "Абонаментът е прекратен и профилът ще бъде променен на неплатен на {{date}}." + "account_delete_dialog_label": "Парола" } diff --git a/web/public/static/langs/cs.json b/web/public/static/langs/cs.json index 6b967c8..93e9abb 100644 --- a/web/public/static/langs/cs.json +++ b/web/public/static/langs/cs.json @@ -91,7 +91,7 @@ "subscribe_dialog_subscribe_button_subscribe": "Přihlásit odběr", "subscribe_dialog_login_username_label": "Uživatelské jméno, např. phil", "subscribe_dialog_login_password_label": "Heslo", - "common_back": "Zpět", + "subscribe_dialog_login_button_back": "Zpět", "subscribe_dialog_login_button_login": "Přihlásit se", "subscribe_dialog_error_user_not_authorized": "Uživatel {{username}} není autorizován", "subscribe_dialog_error_user_anonymous": "anonymně", @@ -285,11 +285,11 @@ "account_delete_dialog_button_submit": "Trvale odstranit účet", "account_delete_dialog_billing_warning": "Odstraněním účtu se také okamžitě zruší vaše předplatné. Nebudete již mít přístup k fakturačnímu panelu.", "account_upgrade_dialog_title": "Změna úrovně účtu", - "account_upgrade_dialog_proration_info": "Prohlášení: Při přechodu mezi placenými úrovněmi bude rozdíl v ceně zaúčtován okamžitě. Při přechodu na nižší úroveň se zůstatek použije na platbu za budoucí zúčtovací období.", + "account_upgrade_dialog_proration_info": "Prohlášení: Při přechodu mezi placenými úrovněmi bude rozdíl v ceně účtován nebo vrácen v následující faktuře. Další fakturu obdržíte až na konci dalšího zúčtovacího období.", "account_upgrade_dialog_reservations_warning_one": "Vybraná úroveň umožňuje méně rezervovaných témat než vaše aktuální úroveň. Než změníte svou úroveň, odstraňte alespoň jednu rezervaci. Rezervace můžete odstranit v Nastavení.", - "account_upgrade_dialog_tier_features_reservations_other": "{{reservations}} rezervovaných témat", - "account_upgrade_dialog_tier_features_messages_other": "{{messages}} denních zpráv", - "account_upgrade_dialog_tier_features_emails_other": "{{emails}} denních e-mailů", + "account_upgrade_dialog_tier_features_reservations": "{{reservations}} rezervovaných témat", + "account_upgrade_dialog_tier_features_messages": "{{messages}} denních zpráv", + "account_upgrade_dialog_tier_features_emails": "{{emails}} denních e-mailů", "account_upgrade_dialog_tier_features_attachment_file_size": "{{filesize}} na soubor", "account_upgrade_dialog_tier_features_attachment_total_size": "{{totalsize}} celkový úložný prostor", "account_upgrade_dialog_tier_selected_label": "Vybráno", @@ -305,7 +305,7 @@ "account_tokens_table_expires_header": "Vyprší", "account_tokens_table_never_expires": "Nikdy nevyprší", "account_tokens_table_current_session": "Současná relace prohlížeče", - "common_copy_to_clipboard": "Kopírování do schránky", + "account_tokens_table_copy_to_clipboard": "Kopírování do schránky", "account_tokens_table_label_header": "Popisek", "account_tokens_table_cannot_delete_or_edit": "Nelze upravit nebo odstranit aktuální token relace", "account_tokens_table_create_token_button": "Vytvořit přístupový token", @@ -340,30 +340,5 @@ "reservation_delete_dialog_action_keep_description": "Zprávy a přílohy, které jsou uloženy v mezipaměti serveru, se stanou veřejně viditelnými pro osoby, které znají název tématu.", "reservation_delete_dialog_action_delete_title": "Odstranění zpráv a příloh uložených v mezipaměti", "reservation_delete_dialog_action_delete_description": "Zprávy a přílohy uložené v mezipaměti budou trvale odstraněny. Tuto akci nelze vrátit zpět.", - "reservation_delete_dialog_submit_button": "Odstranit rezervaci", - "account_basics_tier_interval_yearly": "roční", - "account_upgrade_dialog_interval_yearly_discount_save": "ušetříte {{discount}}%", - "account_upgrade_dialog_tier_price_per_month": "měsíc", - "account_upgrade_dialog_tier_features_no_reservations": "Žádná rezervovaná témata", - "account_upgrade_dialog_interval_yearly_discount_save_up_to": "ušetříte až {{discount}}%", - "account_upgrade_dialog_tier_price_billed_yearly": "{{price}} účtováno ročně. Ušetříte {{save}}.", - "account_basics_tier_interval_monthly": "měsíční", - "account_upgrade_dialog_interval_monthly": "Měsíční", - "account_upgrade_dialog_interval_yearly": "Roční", - "account_upgrade_dialog_tier_price_billed_monthly": "{{price}} za rok. Účtuje se měsíčně.", - "account_upgrade_dialog_billing_contact_email": "V případě dotazů týkajících se fakturace nás prosím kontaktujte přímo.", - "account_upgrade_dialog_billing_contact_website": "Otázky týkající se fakturace naleznete na našich webových stránkách.", - "account_upgrade_dialog_tier_features_reservations_one": "{{reservations}} rezervované téma", - "account_upgrade_dialog_tier_features_messages_one": "{{messages}} denní zpráva", - "account_upgrade_dialog_tier_features_emails_one": "{{emails}} denní e-mail", - "publish_dialog_call_label": "Telefonát", - "publish_dialog_call_reset": "Odstranit telefonát", - "publish_dialog_chip_call_label": "Telefonát", - "account_basics_phone_numbers_title": "Telefonní čísla", - "account_basics_phone_numbers_dialog_description": "Pro oznámení prostřednictvím tel. hovoru, musíte přidat a ověřit alespoň jedno telefonní číslo. Ověření lze provést pomocí SMS nebo telefonátu.", - "account_basics_phone_numbers_description": "K oznámení telefonátem", - "account_basics_phone_numbers_no_phone_numbers_yet": "Zatím žádná telefonní čísla", - "account_basics_phone_numbers_copied_to_clipboard": "Telefonní číslo zkopírováno do schránky", - "publish_dialog_chip_call_no_verified_numbers_tooltip": "Žádná ověřená telefonní čísla", - "publish_dialog_call_item": "Vytočit číslo {{number}}" + "reservation_delete_dialog_submit_button": "Odstranit rezervaci" } diff --git a/web/public/static/langs/cy.json b/web/public/static/langs/cy.json deleted file mode 100644 index 68846b8..0000000 --- a/web/public/static/langs/cy.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "notifications_delete": "Dileu", - "action_bar_sign_in": "Mewngofnodi", - "notifications_copied_to_clipboard": "Wedi'i gopio i'r clipfwrdd", - "common_cancel": "Canslo", - "nav_button_account": "Cyfrif", - "common_save": "Arbed", - "common_add": "Ychwanegu", - "signup_title": "Creu cyfrif ntfy", - "signup_form_username": "Enw defnyddiwr", - "signup_form_password": "Cyfrinair", - "action_bar_logo_alt": "logo ntfy", - "action_bar_settings": "Gosodiadau", - "action_bar_profile_title": "Proffil", - "action_bar_profile_logout": "Allgofnodi", - "message_bar_publish": "Cyhoeddi neges", - "notifications_attachment_copy_url_button": "Copio URL", - "notifications_attachment_open_title": "Ewch i {{url}}", - "publish_dialog_base_url_label": "URL y Gwasanaeth", - "publish_dialog_priority_high": "Blaenoriaeth uchel", - "publish_dialog_title_label": "Teitl", - "publish_dialog_message_label": "Neges", - "publish_dialog_attach_label": "URL Atodiad", - "publish_dialog_filename_label": "Enw ffeil", - "publish_dialog_filename_placeholder": "Enw ffeil yr atodiad", - "action_bar_account": "Cyfrif", - "action_bar_unsubscribe": "Dad-danysgrifio", - "login_title": "Mewngofnodi i'ch cyfrif ntfy", - "login_form_button_submit": "Mewngofnodi", - "action_bar_change_display_name": "Newid enw arddangos", - "action_bar_profile_settings": "Gosodiadau", - "nav_button_settings": "Gosodiadau", - "nav_button_documentation": "Dogfennaeth", - "alert_not_supported_context_description": "Dim ond dros HTTPS y gellir derbyn cyhoeddiadau. Mae hyn yn gyfyngiad ar yr API Notifications.", - "notifications_attachment_open_button": "Agor atodiad", - "notifications_attachment_file_document": "dogfen arall", - "notifications_click_open_button": "Agor linc", - "publish_dialog_base_url_placeholder": "URL y Gwasanaeth, e.e. https://example.com", - "publish_dialog_attach_placeholder": "Atodi ffeil drwy URL, e.e. https://f-droid.org/F-Droid.apk", - "notifications_click_copy_url_button": "Copio linc", - "notifications_actions_open_url_title": "Ewch i {{url}}", - "publish_dialog_email_label": "Ebost" -} diff --git a/web/public/static/langs/da.json b/web/public/static/langs/da.json index c7477df..2f871f5 100644 --- a/web/public/static/langs/da.json +++ b/web/public/static/langs/da.json @@ -91,7 +91,7 @@ "publish_dialog_delay_label": "Forsinkelse", "publish_dialog_button_send": "Send", "subscribe_dialog_subscribe_button_subscribe": "Tilmeld", - "common_back": "Tilbage", + "subscribe_dialog_login_button_back": "Tilbage", "subscribe_dialog_login_username_label": "Brugernavn, f.eks. phil", "account_basics_title": "Konto", "subscribe_dialog_error_topic_already_reserved": "Emnet er allerede reserveret", @@ -201,18 +201,18 @@ "account_basics_password_dialog_current_password_label": "Nuværende kodeord", "account_basics_password_dialog_new_password_label": "Nyt kodeord", "notifications_loading": "Indlæser notifikationer…", - "account_upgrade_dialog_tier_features_emails_other": "{{emails}} daglige e-mails", + "account_upgrade_dialog_tier_features_emails": "{{emails}} daglige e-mails", "account_tokens_table_create_token_button": "Opret adgangstoken", "account_tokens_dialog_title_delete": "Slet adgangstoken", "publish_dialog_chip_email_label": "Videresend til e-mail", "account_upgrade_dialog_tier_features_attachment_total_size": "{{totalsize}} samlet lagerplads", "subscribe_dialog_subscribe_use_another_label": "Brug en anden server", "account_basics_tier_upgrade_button": "Opgrader til Pro", - "account_upgrade_dialog_tier_features_messages_other": "{{messages}} daglige beskeder", - "common_copy_to_clipboard": "Kopier til udklipsholder", + "account_upgrade_dialog_tier_features_messages": "{{messages}} daglige beskeder", + "account_tokens_table_copy_to_clipboard": "Kopier til udklipsholder", "prefs_reservations_edit_button": "Rediger emneadgang", "account_upgrade_dialog_title": "Skift kontoniveau", - "account_upgrade_dialog_tier_features_reservations_other": "{{reservations}} reserverede emner", + "account_upgrade_dialog_tier_features_reservations": "{{reservations}} reserverede emner", "account_tokens_dialog_expires_never": "Token udløber aldrig", "account_tokens_table_current_session": "Nuværende browsersession", "account_tokens_dialog_title_edit": "Rediger adgangstoken", @@ -221,63 +221,5 @@ "account_tokens_delete_dialog_submit_button": "Slet token permanent", "prefs_notifications_delete_after_one_month": "Efter en måned", "prefs_notifications_delete_after_one_week": "Efter en uge", - "prefs_users_dialog_username_label": "Brugernavn, f.eks. phil", - "prefs_notifications_delete_after_one_day_description": "Notifikationer slettes automatisk efter en dag", - "notifications_none_for_topic_description": "For at sende en notifikation til dette emne, skal du blot sende en PUT eller POST til emne-URL'en.", - "notifications_none_for_any_description": "For at sende en notifikation til et emne, skal du blot sende en PUT eller POST til emne-URL'en. Her er et eksempel med et af dine emner.", - "notifications_no_subscriptions_title": "Det ser ud til, at du ikke har nogen abonnementer endnu.", - "notifications_more_details": "For mere information, se webstedet eller dokumentationen.", - "display_name_dialog_description": "Angiv et alternativt navn for et emne, der vises på abonnementslisten. Dette gør det nemmere at identificere emner med komplicerede navne.", - "reserve_dialog_checkbox_label": "Reserver emne og konfigurer adgang", - "publish_dialog_attachment_limits_file_reached": "overskrider {{fileSizeLimit}} filgrænse", - "publish_dialog_attachment_limits_quota_reached": "overskrider kvote, {{remainingBytes}} tilbage", - "publish_dialog_topic_label": "Emnenavn", - "publish_dialog_topic_placeholder": "Emnenavn, f.eks. phil_alerts", - "publish_dialog_topic_reset": "Nulstil emne", - "publish_dialog_click_reset": "Fjern klik-URL", - "publish_dialog_delay_placeholder": "Forsink levering, f.eks. {{unixTimestamp}}, {{relativeTime}} eller \"{{naturalLanguage}}\" (kun på engelsk)", - "publish_dialog_other_features": "Andre funktioner:", - "publish_dialog_chip_attach_url_label": "Vedhæft fil via URL", - "publish_dialog_chip_attach_file_label": "Vedhæft lokal fil", - "publish_dialog_details_examples_description": "For eksempler og en detaljeret beskrivelse af alle afsendelsesfunktioner henvises til dokumentationen.", - "publish_dialog_button_cancel_sending": "Annuller afsendelse", - "publish_dialog_attached_file_title": "Vedhæftet fil:", - "emoji_picker_search_placeholder": "Søg emoji", - "emoji_picker_search_clear": "Ryd søgning", - "subscribe_dialog_subscribe_title": "Abonner på emne", - "subscribe_dialog_subscribe_topic_placeholder": "Emnenavn, f.eks. phil_alerts", - "subscribe_dialog_subscribe_button_generate_topic_name": "Generer navn", - "subscribe_dialog_login_title": "Login påkrævet", - "subscribe_dialog_login_description": "Dette emne er adgangskodebeskyttet. Indtast venligst brugernavn og adgangskode for at abonnere.", - "subscribe_dialog_error_user_not_authorized": "Brugeren {{username}} er ikke autoriseret", - "account_basics_password_description": "Skift adgangskoden til din konto", - "account_usage_limits_reset_daily": "Brugsgrænser nulstilles dagligt ved midnat (UTC)", - "account_basics_tier_paid_until": "Abonnementet er betalt indtil {{date}} og fornys automatisk", - "account_basics_tier_payment_overdue": "Din betaling er forfalden. Opdater venligst din betalingsmetode, ellers bliver din konto snart nedgraderet.", - "account_basics_tier_canceled_subscription": "Dit abonnement blev annulleret og vil blive nedgraderet til en gratis konto den {{date}}.", - "account_usage_cannot_create_portal_session": "Kan ikke åbne faktureringsportalen", - "account_delete_description": "Slet din konto permanent", - "account_delete_dialog_description": "Dette vil slette din konto permanent, inklusive alle data, der er gemt på serveren. Efter sletning vil dit brugernavn være utilgængeligt i 7 dage. Hvis du virkelig ønsker at fortsætte, bedes du bekræfte med dit kodeord i feltet nedenfor.", - "account_upgrade_dialog_button_pay_now": "Betal nu og abonner", - "account_tokens_table_last_origin_tooltip": "Fra IP-adresse {{ip}}, klik for at slå op", - "account_tokens_dialog_label": "Label, f.eks. radarmeddelelser", - "account_tokens_dialog_expires_label": "Adgangstoken udløber om", - "account_tokens_dialog_expires_unchanged": "Lad udløbsdatoen forblive uændret", - "account_tokens_dialog_expires_x_hours": "Token udløber om {{hours}} timer", - "account_tokens_dialog_expires_x_days": "Token udløber om {{days}} dage", - "prefs_notifications_sound_description_none": "Notifikationer afspiller ingen lyd, når de ankommer", - "prefs_notifications_sound_description_some": "Notifikationer afspiller {{sound}}-lyden, når de ankommer", - "prefs_notifications_min_priority_low_and_higher": "Lav prioritet og højere", - "prefs_notifications_min_priority_default_and_higher": "Standardprioritet og højere", - "prefs_notifications_min_priority_high_and_higher": "Høj prioritet og højere", - "prefs_notifications_delete_after_never_description": "Notifikationer slettes aldrig automatisk", - "prefs_notifications_delete_after_three_hours_description": "Notifikationer slettes automatisk efter tre timer", - "prefs_notifications_delete_after_one_week_description": "Notifikationer slettes automatisk efter en uge", - "prefs_notifications_delete_after_one_month_description": "Notifikationer slettes automatisk efter en måned", - "prefs_reservations_limit_reached": "Du har nået din grænse for reserverede emner.", - "prefs_reservations_table_click_to_subscribe": "Klik for at abonnere", - "reservation_delete_dialog_action_keep_title": "Behold cachelagrede meddelelser og vedhæftede filer", - "reservation_delete_dialog_action_delete_title": "Slet cachelagrede meddelelser og vedhæftede filer", - "error_boundary_title": "Oh nej, ntfy brød sammen", - "error_boundary_description": "Dette bør naturligvis ikke ske. Det beklager vi meget.
Hvis du har et øjeblik, bedes du rapportere dette på GitHub, eller give os besked via Discord eller Matrix." + "prefs_users_dialog_username_label": "Brugernavn, f.eks. phil" } diff --git a/web/public/static/langs/de.json b/web/public/static/langs/de.json index 6343dee..cf7e23a 100644 --- a/web/public/static/langs/de.json +++ b/web/public/static/langs/de.json @@ -82,7 +82,7 @@ "publish_dialog_attach_placeholder": "Datei von URL anhängen, z.B. https://f-droid.org/F-Droid.apk", "publish_dialog_filename_placeholder": "Dateiname des Anhangs", "publish_dialog_delay_label": "Verzögerung", - "publish_dialog_email_placeholder": "E-Mail-Adresse, an welche die Benachrichtigung gesendet werden soll, z. B. phil@example.com", + "publish_dialog_email_placeholder": "E-Mail-Adresse, an die die Benachrichtigung gesendet werden soll, z. B. phil@example.com", "publish_dialog_chip_click_label": "Klick-URL", "publish_dialog_button_cancel_sending": "Senden abbrechen", "publish_dialog_drop_file_here": "Datei hierher ziehen", @@ -94,7 +94,7 @@ "publish_dialog_delay_placeholder": "Auslieferung verzögern, z.B. {{unixTimestamp}}, {{relativeTime}}, oder \"{{naturalLanguage}}\" (nur Englisch)", "prefs_appearance_title": "Darstellung", "subscribe_dialog_login_password_label": "Kennwort", - "common_back": "Zurück", + "subscribe_dialog_login_button_back": "Zurück", "publish_dialog_chip_attach_url_label": "Datei von URL anhängen", "publish_dialog_chip_delay_label": "Auslieferung verzögern", "publish_dialog_chip_topic_label": "Thema ändern", @@ -261,12 +261,12 @@ "account_usage_basis_ip_description": "Nutzungsstatistiken und Limits für diesen Account basieren auf Deiner IP-Adresse, können also mit anderen Usern geteilt sein. Die oben gezeigten Limits sind Schätzungen basierend auf den bestehenden Limits.", "account_delete_dialog_billing_warning": "Das Löschen Deines Kontos storniert auch sofort Deine Zahlung. Du wirst dann keinen Zugang zum Abrechnungs-Dashboard haben.", "account_upgrade_dialog_title": "Konto-Level ändern", - "account_upgrade_dialog_proration_info": "Anrechnung: Wenn Du auf einen höheren kostenpflichtigen Level wechselst wird die Differenz sofort berechnet. Beim Wechsel auf ein kleineres Level verwenden wir Dein Guthaben für zukünftige Abrechnungsperioden.", + "account_upgrade_dialog_proration_info": "Anrechnung: Wenn Du zwischen kostenpflichtigen Leveln wechselst wir die Differenz bei der nächsten Abrechnung nachberechnet oder erstattet. Du erhältst bis zum Ende der Abrechnungsperiode keine neue Rechnung.", "account_upgrade_dialog_reservations_warning_one": "Das gewählte Level erlaubt weniger reservierte Themen als Dein aktueller Level. Bitte löschen vor dem Wechsel Deines Levels mindestens eine Reservierung. Du kannst Reservierungen in den Einstellungen löschen.", "account_upgrade_dialog_reservations_warning_other": "Das gewählte Level erlaubt weniger reservierte Themen als Dein aktueller Level. Bitte löschen vor dem Wechsel Deines Levels mindestens {{count}} Reservierungen. Du kannst Reservierungen in den Einstellungen löschen.", - "account_upgrade_dialog_tier_features_reservations_other": "{{reservations}} reservierte Themen", - "account_upgrade_dialog_tier_features_messages_other": "{{messages}} Nachrichten pro Tag", - "account_upgrade_dialog_tier_features_emails_other": "{{emails}} Emails pro Tag", + "account_upgrade_dialog_tier_features_reservations": "{{reservations}} reservierte Themen", + "account_upgrade_dialog_tier_features_messages": "{{messages}} Nachrichten pro Tag", + "account_upgrade_dialog_tier_features_emails": "{{emails}} Emails pro Tag", "account_upgrade_dialog_tier_features_attachment_file_size": "{{filesize}} pro Datei", "account_upgrade_dialog_tier_features_attachment_total_size": "{{totalsize}} gesamter Speicherplatz", "account_upgrade_dialog_tier_selected_label": "Ausgewählt", @@ -284,7 +284,7 @@ "account_tokens_table_expires_header": "Verfällt", "account_tokens_table_never_expires": "Verfällt nie", "account_tokens_table_current_session": "Aktuelle Browser-Sitzung", - "common_copy_to_clipboard": "In die Zwischenablage kopieren", + "account_tokens_table_copy_to_clipboard": "In die Zwischenablage kopieren", "account_tokens_table_copied_to_clipboard": "Access-Token kopiert", "account_tokens_table_cannot_delete_or_edit": "Aktuelles Token kann nicht bearbeitet oder gelöscht werden", "account_tokens_table_create_token_button": "Access-Token erzeugen", @@ -340,45 +340,5 @@ "nav_upgrade_banner_label": "Upgrade auf ntfy Pro", "alert_not_supported_context_description": "Benachrichtigungen werden nur über HTTPS unterstützt. Das ist eine Einschränkung der Notifications API.", "display_name_dialog_description": "Lege einen alternativen Namen für ein Thema fest, der in der Abo-Liste angezeigt wird. So kannst Du Themen mit komplizierten Namen leichter finden.", - "account_basics_username_admin_tooltip": "Du bist Admin", - "account_upgrade_dialog_interval_yearly_discount_save": "spare {{discount}}%", - "account_upgrade_dialog_interval_yearly_discount_save_up_to": "spare bis zu {{discount}}%", - "account_upgrade_dialog_tier_price_per_month": "Monat", - "account_upgrade_dialog_tier_price_billed_yearly": "{{price}} pro Jahr. Spare {{save}}.", - "account_upgrade_dialog_billing_contact_email": "Bei Fragen zur Abrechnung, kontaktiere uns bitte direkt.", - "account_upgrade_dialog_billing_contact_website": "Bei Fragen zur Abrechnung sieh bitte auf unserer Webseite nach.", - "account_upgrade_dialog_tier_features_no_reservations": "Keine reservierten Themen", - "account_basics_tier_interval_yearly": "jährlich", - "account_basics_tier_interval_monthly": "monatlich", - "account_upgrade_dialog_interval_monthly": "Monatlich", - "account_upgrade_dialog_tier_price_billed_monthly": "{{price}} pro Jahr. Monatlich abgerechnet.", - "account_upgrade_dialog_interval_yearly": "Jährlich", - "account_upgrade_dialog_tier_features_messages_one": "{{messages}} tägliche Nachricht", - "account_upgrade_dialog_tier_features_reservations_one": "{{reservations}} reserviertes Thema", - "account_upgrade_dialog_tier_features_emails_one": "{{emails}} tägliche E-Mail", - "publish_dialog_call_label": "Telefonanruf", - "publish_dialog_call_item": "Telefonnummer {{number}} anrufen", - "publish_dialog_chip_call_label": "Telefonanruf", - "publish_dialog_chip_call_no_verified_numbers_tooltip": "Keine verifizierten Telefonnummern", - "account_basics_phone_numbers_title": "Telefonnummern", - "account_basics_phone_numbers_copied_to_clipboard": "Telefonnummer wurde in die Zwischenablage kopiert", - "account_basics_phone_numbers_dialog_title": "Telefonnummer hinzufügen", - "account_upgrade_dialog_tier_features_calls_other": "{{calls}} Telefonanrufe pro Tag", - "account_upgrade_dialog_tier_features_no_calls": "Keine Telefonanrufe", - "publish_dialog_call_reset": "Telefonanruf entfernen", - "account_basics_phone_numbers_dialog_description": "Um die Benachrichtigung per Telefonanruf zu nutzen musst Du mindestens eine Telefonnummer hinzufügen und verifizieren. Die Verifizierung kann per SMS oder über einen Anruf erfolgen.", - "account_basics_phone_numbers_description": "Für Telefon-Benachrichtigungen", - "account_basics_phone_numbers_no_phone_numbers_yet": "Noch keine Telefonnummern", - "account_basics_phone_numbers_dialog_number_label": "Telefonnummer", - "account_basics_phone_numbers_dialog_channel_sms": "SMS", - "account_basics_phone_numbers_dialog_channel_call": "Anruf", - "account_basics_phone_numbers_dialog_number_placeholder": "z.B. +49123456789", - "account_basics_phone_numbers_dialog_verify_button_call": "Ruf mich an", - "account_basics_phone_numbers_dialog_verify_button_sms": "SMS senden", - "account_basics_phone_numbers_dialog_code_label": "Verifizierungs-Code", - "account_basics_phone_numbers_dialog_code_placeholder": "z.B. 123456", - "account_basics_phone_numbers_dialog_check_verification_button": "Code bestätigen", - "account_usage_calls_title": "Getätigte Anrufe", - "account_usage_calls_none": "Noch keine Anrufe mit diesem Account getätigt", - "account_upgrade_dialog_tier_features_calls_one": "{{calls}} Telefonanrufe pro Tag" + "account_basics_username_admin_tooltip": "Du bist Admin" } diff --git a/web/public/static/langs/en.json b/web/public/static/langs/en.json index 5d8a3a3..babdd1d 100644 --- a/web/public/static/langs/en.json +++ b/web/public/static/langs/en.json @@ -2,8 +2,6 @@ "common_cancel": "Cancel", "common_save": "Save", "common_add": "Add", - "common_back": "Back", - "common_copy_to_clipboard": "Copy to clipboard", "signup_title": "Create a ntfy account", "signup_form_username": "Username", "signup_form_password": "Password", @@ -129,9 +127,6 @@ "publish_dialog_email_label": "Email", "publish_dialog_email_placeholder": "Address to forward the notification to, e.g. phil@example.com", "publish_dialog_email_reset": "Remove email forward", - "publish_dialog_call_label": "Phone call", - "publish_dialog_call_item": "Call phone number {{number}}", - "publish_dialog_call_reset": "Remove phone call", "publish_dialog_attach_label": "Attachment URL", "publish_dialog_attach_placeholder": "Attach file by URL, e.g. https://f-droid.org/F-Droid.apk", "publish_dialog_attach_reset": "Remove attachment URL", @@ -143,8 +138,6 @@ "publish_dialog_other_features": "Other features:", "publish_dialog_chip_click_label": "Click URL", "publish_dialog_chip_email_label": "Forward to email", - "publish_dialog_chip_call_label": "Phone call", - "publish_dialog_chip_call_no_verified_numbers_tooltip": "No verified phone numbers", "publish_dialog_chip_attach_url_label": "Attach file by URL", "publish_dialog_chip_attach_file_label": "Attach local file", "publish_dialog_chip_delay_label": "Delay delivery", @@ -172,6 +165,7 @@ "subscribe_dialog_login_description": "This topic is password-protected. Please enter username and password to subscribe.", "subscribe_dialog_login_username_label": "Username, e.g. phil", "subscribe_dialog_login_password_label": "Password", + "subscribe_dialog_login_button_back": "Back", "subscribe_dialog_login_button_login": "Login", "subscribe_dialog_error_user_not_authorized": "User {{username}} not authorized", "subscribe_dialog_error_topic_already_reserved": "Topic already reserved", @@ -188,21 +182,6 @@ "account_basics_password_dialog_confirm_password_label": "Confirm password", "account_basics_password_dialog_button_submit": "Change password", "account_basics_password_dialog_current_password_incorrect": "Password incorrect", - "account_basics_phone_numbers_title": "Phone numbers", - "account_basics_phone_numbers_dialog_description": "To use the call notification feature, you need to add and verify at least one phone number. Verification can be done via SMS or a phone call.", - "account_basics_phone_numbers_description": "For phone call notifications", - "account_basics_phone_numbers_no_phone_numbers_yet": "No phone numbers yet", - "account_basics_phone_numbers_copied_to_clipboard": "Phone number copied to clipboard", - "account_basics_phone_numbers_dialog_title": "Add phone number", - "account_basics_phone_numbers_dialog_number_label": "Phone number", - "account_basics_phone_numbers_dialog_number_placeholder": "e.g. +1222333444", - "account_basics_phone_numbers_dialog_verify_button_sms": "Send SMS", - "account_basics_phone_numbers_dialog_verify_button_call": "Call me", - "account_basics_phone_numbers_dialog_code_label": "Verification code", - "account_basics_phone_numbers_dialog_code_placeholder": "e.g. 123456", - "account_basics_phone_numbers_dialog_check_verification_button": "Confirm code", - "account_basics_phone_numbers_dialog_channel_sms": "SMS", - "account_basics_phone_numbers_dialog_channel_call": "Call", "account_usage_title": "Usage", "account_usage_of_limit": "of {{limit}}", "account_usage_unlimited": "Unlimited", @@ -224,8 +203,6 @@ "account_basics_tier_manage_billing_button": "Manage billing", "account_usage_messages_title": "Published messages", "account_usage_emails_title": "Emails sent", - "account_usage_calls_title": "Phone calls made", - "account_usage_calls_none": "No phone calls can be made with this account", "account_usage_reservations_title": "Reserved topics", "account_usage_reservations_none": "No reserved topics for this account", "account_usage_attachment_storage_title": "Attachment storage", @@ -248,16 +225,10 @@ "account_upgrade_dialog_proration_info": "Proration: When upgrading between paid plans, the price difference will be charged immediately. When downgrading to a lower tier, the balance will be used to pay for future billing periods.", "account_upgrade_dialog_reservations_warning_one": "The selected tier allows fewer reserved topics than your current tier. Before changing your tier, please delete at least one reservation. You can remove reservations in the Settings.", "account_upgrade_dialog_reservations_warning_other": "The selected tier allows fewer reserved topics than your current tier. Before changing your tier, please delete at least {{count}} reservations. You can remove reservations in the Settings.", - "account_upgrade_dialog_tier_features_reservations_one": "{{reservations}} reserved topic", - "account_upgrade_dialog_tier_features_reservations_other": "{{reservations}} reserved topics", + "account_upgrade_dialog_tier_features_reservations": "{{reservations}} reserved topics", "account_upgrade_dialog_tier_features_no_reservations": "No reserved topics", - "account_upgrade_dialog_tier_features_messages_one": "{{messages}} daily message", - "account_upgrade_dialog_tier_features_messages_other": "{{messages}} daily messages", - "account_upgrade_dialog_tier_features_emails_one": "{{emails}} daily email", - "account_upgrade_dialog_tier_features_emails_other": "{{emails}} daily emails", - "account_upgrade_dialog_tier_features_calls_one": "{{calls}} daily phone calls", - "account_upgrade_dialog_tier_features_calls_other": "{{calls}} daily phone calls", - "account_upgrade_dialog_tier_features_no_calls": "No phone calls", + "account_upgrade_dialog_tier_features_messages": "{{messages}} daily messages", + "account_upgrade_dialog_tier_features_emails": "{{emails}} daily emails", "account_upgrade_dialog_tier_features_attachment_file_size": "{{filesize}} per file", "account_upgrade_dialog_tier_features_attachment_total_size": "{{totalsize}} total storage", "account_upgrade_dialog_tier_price_per_month": "month", @@ -280,6 +251,7 @@ "account_tokens_table_expires_header": "Expires", "account_tokens_table_never_expires": "Never expires", "account_tokens_table_current_session": "Current browser session", + "account_tokens_table_copy_to_clipboard": "Copy to clipboard", "account_tokens_table_copied_to_clipboard": "Access token copied", "account_tokens_table_cannot_delete_or_edit": "Cannot edit or delete current session token", "account_tokens_table_create_token_button": "Create access token", diff --git a/web/public/static/langs/es.json b/web/public/static/langs/es.json index 62ecdaf..dc5aaa4 100644 --- a/web/public/static/langs/es.json +++ b/web/public/static/langs/es.json @@ -81,7 +81,7 @@ "subscribe_dialog_login_description": "Este tópico está protegido por contraseña. Por favor, introduzca su nombre de usuario y contraseña para suscribirse.", "subscribe_dialog_login_username_label": "Nombre de usuario, ej. phil", "subscribe_dialog_login_password_label": "Contraseña", - "common_back": "Volver", + "subscribe_dialog_login_button_back": "Volver", "subscribe_dialog_login_button_login": "Iniciar sesión", "subscribe_dialog_error_user_not_authorized": "Usuario {{username}} no autorizado", "subscribe_dialog_error_user_anonymous": "anónimo", @@ -107,7 +107,7 @@ "prefs_appearance_language_title": "Idioma", "error_boundary_title": "Oh no, ntfy tuvo un error", "error_boundary_button_copy_stack_trace": "Copiar el stack trace", - "error_boundary_stack_trace": "Rastreo de pila", + "error_boundary_stack_trace": "Stack trace", "error_boundary_gathering_info": "Reunir más información …", "notifications_example": "Ejemplo", "prefs_notifications_min_priority_title": "Prioridad mínima", @@ -240,146 +240,5 @@ "account_basics_password_title": "Contraseña", "account_basics_password_dialog_title": "Cambiar contraseña", "account_basics_password_dialog_current_password_label": "Contraseña actual", - "account_basics_password_dialog_new_password_label": "Contraseña nueva", - "account_basics_tier_basic": "Básico", - "account_basics_tier_admin_suffix_with_tier": "(con nivel {{tier}})", - "account_basics_tier_admin_suffix_no_tier": "(sin nivel)", - "account_basics_tier_free": "Gratis", - "account_basics_tier_upgrade_button": "Actualizar a Pro", - "account_basics_tier_change_button": "Cambiar", - "account_basics_tier_paid_until": "Suscripción pagada hasta {{fecha}}, y se renovará automáticamente", - "account_basics_tier_manage_billing_button": "Administrar la facturación", - "account_basics_tier_title": "Tipo de cuenta", - "account_tokens_description": "Utilice tokens de acceso al publicar y suscribirse a través de la API de ntfy para no tener que enviar las credenciales de su cuenta. Consulte la documentación para obtener más información.", - "account_tokens_table_token_header": "Token", - "account_tokens_table_label_header": "Etiqueta", - "account_tokens_table_last_access_header": "Último acceso", - "account_tokens_table_expires_header": "Expira", - "account_tokens_table_never_expires": "Nunca expira", - "account_tokens_table_current_session": "Sesión del navegador actual", - "common_copy_to_clipboard": "Copiar al portapapeles", - "account_tokens_table_copied_to_clipboard": "Token de acceso copiado", - "account_tokens_table_cannot_delete_or_edit": "No se puede editar ni eliminar el token de sesión actual", - "account_tokens_table_create_token_button": "Crear token de acceso", - "account_tokens_table_last_origin_tooltip": "Desde la dirección IP {{ip}}, haga clic para buscar", - "account_tokens_dialog_title_create": "Crear token de acceso", - "account_tokens_dialog_title_edit": "Editar token de acceso", - "account_tokens_dialog_title_delete": "Eliminar token de acceso", - "account_tokens_dialog_label": "Etiqueta, por ejemplo, notificaciones de Radarr", - "account_tokens_dialog_button_create": "Crear token", - "prefs_reservations_table_everyone_write_only": "Puedo publicar y suscribirme, todo el mundo puede publicar", - "account_usage_messages_title": "Mensajes publicados", - "account_usage_reservations_title": "Tópicos reservados", - "account_usage_reservations_none": "No hay tópicos reservados para esta cuenta", - "account_usage_cannot_create_portal_session": "No se puede abrir el portal de facturación", - "account_upgrade_dialog_title": "Cambiar nivel de cuenta", - "account_basics_tier_payment_overdue": "Su pago ha vencido. Por favor actualice su método de pago o su cuenta será degradada en breve.", - "account_basics_tier_canceled_subscription": "Su suscripción fue cancelada y será degradada a una cuenta gratuita el {{date}}.", - "account_usage_emails_title": "Correos enviados", - "account_usage_attachment_storage_title": "Almacenamiento de archivos adjuntos", - "account_usage_attachment_storage_description": "{{filesize}} por archivo, eliminado después de {{expiry}}", - "account_usage_basis_ip_description": "Las estadísticas de uso y los límites de esta cuenta se basan en su dirección IP, por lo que podrían ser compartidos con otros usuarios. Los límites mostrados anteriormente son aproximados basados en los límites existentes.", - "account_delete_title": "Elimina cuenta", - "account_delete_dialog_button_cancel": "Cancelar", - "account_delete_dialog_billing_warning": "La eliminación de su cuenta también cancela su suscripción de facturación inmediatamente. Ya no tendrá acceso al panel de facturación.", - "account_upgrade_dialog_reservations_warning_one": "El nivel seleccionado permite menos tópicos reservados que su nivel actual. Antes de cambiar de nivel, por favor elimine al menos una reserva. Puede eliminar reservas en Configuración.", - "account_upgrade_dialog_tier_selected_label": "Seleccionado", - "account_upgrade_dialog_button_cancel": "Cancelar", - "account_upgrade_dialog_button_cancel_subscription": "Cancelar suscripción", - "account_tokens_title": "Tokens de acceso", - "account_delete_description": "Eliminar permanentemente su cuenta", - "account_delete_dialog_description": "Esto borrará permanentemente su cuenta, incluyendo todos los datos almacenados en el servidor. Tras la eliminación, su nombre de usuario no estará disponible durante 7 días. Si realmente desea continuar, por favor confirme su contraseña en la casilla de abajo.", - "account_delete_dialog_label": "Contraseña", - "account_delete_dialog_button_submit": "Eliminar permanentemente la cuenta", - "account_upgrade_dialog_tier_features_reservations_other": "{{reservations}} tópicos reservados", - "account_upgrade_dialog_cancel_warning": "Esto cancelará su suscripción y degradará su cuenta en {{date}}. En esa fecha, sus tópicos reservados y sus mensajes almacenados en caché en el servidor serán eliminados.", - "account_upgrade_dialog_proration_info": "Prorrateo: al actualizar entre planes pagos, la diferencia de precio se cobrará de inmediato. Al cambiar a un nivel inferior, el saldo se utilizará para pagar futuros períodos de facturación.", - "account_upgrade_dialog_reservations_warning_other": "El nivel seleccionado permite menos tópicos reservados que su nivel actual. Antes de cambiar de nivel, por favor elimine al menos {{count}} reservaciones. Puede eliminar reservaciones en Configuración.", - "account_upgrade_dialog_tier_features_messages_other": "{{messages}} mensajes diarios", - "account_upgrade_dialog_tier_features_emails_other": "{{emails}} correos diarios", - "account_upgrade_dialog_tier_features_attachment_file_size": "{{filesize}} por archivo", - "account_upgrade_dialog_tier_features_attachment_total_size": "{{totalsize}} almacenamiento total", - "account_upgrade_dialog_tier_current_label": "Actual", - "account_upgrade_dialog_button_redirect_signup": "Regístrese ahora", - "account_upgrade_dialog_button_pay_now": "Pague ahora y suscríbase", - "account_upgrade_dialog_button_update_subscription": "Actualizar suscripción", - "account_tokens_dialog_button_update": "Actualizar token", - "account_tokens_dialog_expires_label": "El token de acceso expira en", - "prefs_reservations_table": "Tabla de tópicos reservados", - "prefs_reservations_dialog_description": "Reservar un tópico le otorga la propiedad sobre el mismo y le permite definir permisos de acceso para otros usuarios sobre el tópico.", - "account_tokens_dialog_button_cancel": "Cancelar", - "account_tokens_dialog_expires_unchanged": "No modificar la fecha de expiración", - "prefs_reservations_add_button": "Agregar tópico reservado", - "prefs_reservations_table_access_header": "Acceso", - "reservation_delete_dialog_action_delete_description": "Los mensajes y archivos adjuntos almacenados en caché se eliminarán de forma permanente. Esta acción no se puede deshacer.", - "account_tokens_dialog_expires_x_hours": "El token expira en {{hours}} horas", - "account_tokens_delete_dialog_title": "Eliminar token de acceso", - "prefs_reservations_limit_reached": "Ha alcanzado su límite de tópicos reservados.", - "prefs_reservations_table_everyone_read_write": "Todo el mundo puede publicar y suscribirse", - "reservation_delete_dialog_action_keep_description": "Los mensajes y archivos adjuntos que se almacenen en caché en el servidor pasarán a ser visibles públicamente para las personas que conozcan el nombre del tópico.", - "account_tokens_dialog_expires_x_days": "El token expira en {{days}} días", - "account_tokens_dialog_expires_never": "El token nunca expira", - "account_tokens_delete_dialog_description": "Antes de eliminar un token de acceso, asegúrese de que ninguna aplicación o script lo está utilizando activamente. Esta acción no se puede deshacer.", - "prefs_users_table_cannot_delete_or_edit": "No se puede eliminar o editar el usuario conectado", - "prefs_reservations_title": "Tópicos reservados", - "prefs_reservations_edit_button": "Editar acceso al tópico", - "prefs_reservations_table_topic_header": "Tópico", - "prefs_reservations_table_everyone_read_only": "Puedo publicar y suscribirme, todo el mundo puede suscribirse", - "prefs_reservations_table_everyone_deny_all": "Sólo yo puedo publicar y suscribirme", - "prefs_reservations_table_click_to_subscribe": "Haga clic para suscribirse", - "prefs_reservations_dialog_title_edit": "Edita tópico reservado", - "account_tokens_delete_dialog_submit_button": "Eliminar permanentemente el token", - "prefs_reservations_description": "Aquí puede reservar nombres de tópicos para uso personal. Reservar un tópico le otorga la propiedad sobre el mismo y le permite definir permisos de acceso para otros usuarios sobre el tópico.", - "prefs_reservations_delete_button": "Restablecer acceso a tópico", - "prefs_reservations_table_not_subscribed": "No suscrito", - "prefs_reservations_dialog_title_add": "Reservar tópico", - "prefs_users_description_no_sync": "Los usuarios y las contraseñas no están sincronizados con su cuenta.", - "prefs_reservations_dialog_title_delete": "Borrar reserva de tópico", - "prefs_reservations_dialog_access_label": "Acceso", - "reservation_delete_dialog_action_keep_title": "Conservar mensajes y archivos adjuntos en caché", - "prefs_reservations_dialog_topic_label": "Tópico", - "reservation_delete_dialog_description": "Al eliminar una reserva se renuncia a la propiedad sobre el tópico y se permite que otros lo reserven. Puede conservar o eliminar los mensajes y archivos adjuntos existentes.", - "reservation_delete_dialog_action_delete_title": "Eliminar mensajes y archivos adjuntos en caché", - "reservation_delete_dialog_submit_button": "Eliminar reserva", - "account_basics_tier_interval_monthly": "mensualmente", - "account_basics_tier_interval_yearly": "anualmente", - "account_upgrade_dialog_interval_monthly": "Mensualmente", - "account_upgrade_dialog_interval_yearly": "Anualmente", - "account_upgrade_dialog_interval_yearly_discount_save": "ahorrar {{discount}}%", - "account_upgrade_dialog_interval_yearly_discount_save_up_to": "ahorra hasta un {{discount}}%", - "account_upgrade_dialog_tier_features_no_reservations": "Ningún tema reservado", - "account_upgrade_dialog_tier_price_per_month": "mes", - "account_upgrade_dialog_tier_price_billed_yearly": "{{price}} facturado anualmente. Guardar {{save}}.", - "account_upgrade_dialog_billing_contact_website": "Si tiene preguntas sobre facturación, consulte nuestra página web.", - "account_upgrade_dialog_tier_price_billed_monthly": "{{price}} al año. Facturación mensual.", - "account_upgrade_dialog_billing_contact_email": "Para preguntas sobre facturación, por favor contáctenos directamente.", - "account_upgrade_dialog_tier_features_messages_one": "{{messages}} mensaje diario", - "account_upgrade_dialog_tier_features_emails_one": "{{emails}} correo electrónico diario", - "account_upgrade_dialog_tier_features_reservations_one": "{{reservations}} tema reservado", - "publish_dialog_call_label": "Llamada telefónica", - "publish_dialog_call_placeholder": "Número de teléfono al cual llamar con el mensaje, por ejemplo +12223334444, o \"sí\"", - "publish_dialog_chip_call_label": "Llamada telefónica", - "account_basics_phone_numbers_title": "Números de teléfono", - "account_basics_phone_numbers_description": "Para notificaciones por llamada teléfonica", - "account_basics_phone_numbers_no_phone_numbers_yet": "Aún no hay números de teléfono", - "account_basics_phone_numbers_dialog_number_label": "Número de teléfono", - "account_basics_phone_numbers_dialog_number_placeholder": "p. ej. +1222333444", - "account_basics_phone_numbers_dialog_verify_button_sms": "Envía SMS", - "account_basics_phone_numbers_dialog_verify_button_call": "Llámame", - "account_basics_phone_numbers_dialog_code_label": "Código de verificación", - "account_basics_phone_numbers_dialog_channel_sms": "SMS", - "account_basics_phone_numbers_dialog_channel_call": "Llamar", - "account_usage_calls_title": "Llamadas telefónicas realizadas", - "account_usage_calls_none": "No se pueden hacer llamadas telefónicas con esta cuenta", - "account_upgrade_dialog_tier_features_calls_one": "{{llamadas}} llamadas telefónicas diarias", - "account_upgrade_dialog_tier_features_calls_other": "{{llamadas}} llamadas telefónicas diarias", - "account_upgrade_dialog_tier_features_no_calls": "No hay llamadas telefónicas", - "publish_dialog_call_reset": "Eliminar llamada telefónica", - "account_basics_phone_numbers_dialog_description": "Para utilizar la función de notificación de llamadas, tiene que añadir y verificar al menos un número de teléfono. La verificación puede realizarse mediante un SMS o una llamada telefónica.", - "account_basics_phone_numbers_copied_to_clipboard": "Número de teléfono copiado al portapapeles", - "account_basics_phone_numbers_dialog_check_verification_button": "Confirmar código", - "account_basics_phone_numbers_dialog_title": "Agregar número de teléfono", - "account_basics_phone_numbers_dialog_code_placeholder": "p.ej. 123456", - "publish_dialog_call_item": "Llamar al número de teléfono {{number}}", - "publish_dialog_chip_call_no_verified_numbers_tooltip": "No hay números de teléfono verificados" + "account_basics_password_dialog_new_password_label": "Contraseña nueva" } diff --git a/web/public/static/langs/fr.json b/web/public/static/langs/fr.json index cf4bb72..c279d02 100644 --- a/web/public/static/langs/fr.json +++ b/web/public/static/langs/fr.json @@ -106,7 +106,7 @@ "prefs_notifications_title": "Notifications", "prefs_notifications_delete_after_title": "Supprimer les notifications", "prefs_users_add_button": "Ajouter un utilisateur", - "common_back": "Retour", + "subscribe_dialog_login_button_back": "Retour", "subscribe_dialog_error_user_anonymous": "anonyme", "prefs_notifications_sound_no_sound": "Aucun son", "prefs_notifications_min_priority_title": "Priorité minimum", @@ -273,10 +273,10 @@ "account_delete_dialog_billing_warning": "Supprimer votre compte annule aussi immédiatement votre facturation. Vous n'aurez plus accès à votre tableau de bord de facturation.", "account_upgrade_dialog_title": "Changer le tarif du compte", "account_upgrade_dialog_proration_info": "Facturation : Lors d'un changement entre un plan payant et un autre, la différence de prix sera créditée ou remboursée sur la prochaine facture. Vous ne recevrez pas d'autre facture avant la fin de la prochaine période de facturation.", - "account_upgrade_dialog_reservations_warning_other": "Le tarif sélectionné autorise moins de sujets réservés que votre tarif actuel. Avant de changer de tarif, veuillez supprimer au moins {{count}} sujets réservés. Vous pouvez supprimer des sujets réservés dans les Paramètres.", - "account_upgrade_dialog_tier_features_reservations_other": "{{reservations}} sujets réservés", - "account_upgrade_dialog_tier_features_messages_other": "{{messages}} messages journaliers", - "account_upgrade_dialog_tier_features_emails_other": "{{emails}} emails journaliers", + "account_upgrade_dialog_reservations_warning_other": "Le tarif sélectionné autorise moins de sujets réservés que votre tarif actuel. Avant de changer de tarif, veuillez supprimer au moins {{count}} sujets réservés. Vous pouvez supprimer des sujets réservés dans les Settings.", + "account_upgrade_dialog_tier_features_reservations": "{{reservations}} sujets réservés", + "account_upgrade_dialog_tier_features_messages": "{{messages}} messages journaliers", + "account_upgrade_dialog_tier_features_emails": "{{emails}} emails journaliers", "account_upgrade_dialog_tier_features_attachment_file_size": "{{filesize}} par fichier", "account_upgrade_dialog_tier_features_attachment_total_size": "{{totalsize}} stockage total", "account_upgrade_dialog_tier_selected_label": "Sélectionné", @@ -293,7 +293,7 @@ "account_tokens_table_expires_header": "Expire", "account_tokens_table_never_expires": "N'expire jamais", "account_tokens_table_current_session": "Session de navigation actuelle", - "common_copy_to_clipboard": "Copier dans le presse-papier", + "account_tokens_table_copy_to_clipboard": "Copier dans le presse-papier", "account_tokens_table_copied_to_clipboard": "Jeton d'accès copié", "account_tokens_table_create_token_button": "Créer un jeton d'accès", "account_tokens_table_last_origin_tooltip": "Depuis l'adresse IP {{ip}}, cliquer pour rechercher", @@ -337,39 +337,8 @@ "alert_not_supported_context_description": "Les notifications ne sont supportées qu'en HTTPS. C'est une limitation de la Notifications API.", "account_basics_tier_payment_overdue": "Votre paiement est en retard. Veuillez mettre à jour votre méthode de paiement, ou votre compte va bientôt être rétrogradé.", "account_upgrade_dialog_cancel_warning": "Cela va annuler votre abonnement et rétrograder votre compte le {{date}}. Ce jour là, les sujets réservés ainsi que tous les messages dans le cache du serveur seront supprimés.", - "account_upgrade_dialog_reservations_warning_one": "Le tarif sélectionné autorise moins de sujets réservés que votre tarif actuel. Avant de changer de tarif, veuillez supprimer au moins un sujet réservé. Vous pouvez supprimer des sujets réservés dans les Paramètres.", + "account_upgrade_dialog_reservations_warning_one": "Le tarif sélectionné autorise moins de sujets réservés que votre tarif actuel. Avant de changer de tarif, veuillez supprimer au moins un sujet réservé. Vous pouvez supprimer des sujets réservés dans les Settings.", "account_tokens_description": "Utilisez des jetons d'accès lors de la publication ou de l'abonnement via l'API de ntfy, afin d'éviter d'envoyer vos identifiants de compte. Regardez la documentation pour en savoir plus.", "account_tokens_delete_dialog_description": "Avant de supprimer un jeton d'accès, assurez-vous qu'aucune application ou script ne soit en train de l'utiliser. Cette action ne peut pas être annulée.", - "prefs_reservations_description": "Vous pouvez réserver les noms de sujet à usage personnel ici. Réserver un sujet vous donne la propriété sur ce sujet et vous permet de définir les permissions d'accès à ce sujet pour d'autres utilisateurs.", - "account_basics_tier_interval_yearly": "annuel", - "account_upgrade_dialog_interval_yearly": "Annuel", - "account_upgrade_dialog_interval_yearly_discount_save": "économisez {{discount}}%", - "account_upgrade_dialog_tier_features_no_reservations": "Aucun sujet(s) réservé(s)", - "account_upgrade_dialog_tier_price_billed_monthly": "{{price}} par an. Prélevé mensuellement.", - "account_upgrade_dialog_billing_contact_website": "Pour des questions en rapport avec la facturation, se référer à notre site internet.", - "account_basics_tier_interval_monthly": "mensuel", - "account_upgrade_dialog_interval_monthly": "Mensuel", - "account_upgrade_dialog_interval_yearly_discount_save_up_to": "économisez jusqu'à {{discount}}%", - "account_upgrade_dialog_tier_price_per_month": "mois", - "account_upgrade_dialog_tier_price_billed_yearly": "{{price}} prélevé annuellement. Économisez {{save}}.", - "account_upgrade_dialog_billing_contact_email": "Pour des questions concernant la facturation, merci de nous contacter directement.", - "publish_dialog_call_label": "Appel téléphonique", - "account_basics_phone_numbers_title": "Numéros de téléphone", - "account_basics_phone_numbers_dialog_description": "Pour utiliser la fonctionnalité de notification par appels, vous devez ajouter et vérifier au moins un numéro de téléphone. La vérification peut se faire par SMS ou appel téléphonique.", - "account_basics_phone_numbers_description": "Pour des notifications par appel téléphoniques", - "account_basics_phone_numbers_no_phone_numbers_yet": "Pas encore de numéros de téléphone", - "account_basics_phone_numbers_copied_to_clipboard": "Numéro de téléphone copié dans le presse-papier", - "account_basics_phone_numbers_dialog_title": "Ajouter un numéro de téléphone", - "account_basics_phone_numbers_dialog_number_label": "Numéro de téléphone", - "account_basics_phone_numbers_dialog_number_placeholder": "Ex : +33701020304", - "account_basics_phone_numbers_dialog_verify_button_sms": "Envoyer un SMS", - "account_basics_phone_numbers_dialog_verify_button_call": "Appelez moi", - "account_basics_phone_numbers_dialog_code_label": "Code de vérification", - "account_basics_phone_numbers_dialog_code_placeholder": "Ex : 123456", - "account_basics_phone_numbers_dialog_check_verification_button": "Code de confirmarion", - "account_basics_phone_numbers_dialog_channel_sms": "SMS", - "account_basics_phone_numbers_dialog_channel_call": "Appel", - "account_usage_calls_none": "Aucun appels téléphoniques ne peut être fait avec ce compte", - "publish_dialog_call_reset": "Supprimer les appels téléphoniques", - "publish_dialog_chip_call_label": "Appel téléphonique" + "prefs_reservations_description": "Vous pouvez réserver les noms de sujet à usage personnel ici. Réserver un sujet vous donne la propriété sur ce sujet et vous permet de définir les permissions d'accès à ce sujet pour d'autres utilisateurs." } diff --git a/web/public/static/langs/gl.json b/web/public/static/langs/gl.json deleted file mode 100644 index 0967ef4..0000000 --- a/web/public/static/langs/gl.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/web/public/static/langs/hu.json b/web/public/static/langs/hu.json index b52e3a4..975d8d9 100644 --- a/web/public/static/langs/hu.json +++ b/web/public/static/langs/hu.json @@ -84,7 +84,7 @@ "subscribe_dialog_login_description": "Ez a téma jelszóval védett. Jelentkezz be a feliratkozáshoz.", "subscribe_dialog_login_username_label": "Felhasználónév, pl: jozsi", "subscribe_dialog_login_password_label": "Jelszó", - "common_back": "Vissza", + "subscribe_dialog_login_button_back": "Vissza", "subscribe_dialog_login_button_login": "Belépés", "subscribe_dialog_error_user_anonymous": "névtelen", "subscribe_dialog_error_user_not_authorized": "A(z) {{username}} felhasználónak nincs hozzáférése", diff --git a/web/public/static/langs/id.json b/web/public/static/langs/id.json index 48fcda0..c3b79b0 100644 --- a/web/public/static/langs/id.json +++ b/web/public/static/langs/id.json @@ -116,7 +116,7 @@ "common_save": "Simpan", "prefs_appearance_title": "Tampilan", "subscribe_dialog_login_password_label": "Kata sandi", - "common_back": "Kembali", + "subscribe_dialog_login_button_back": "Kembali", "prefs_notifications_sound_title": "Suara notifikasi", "prefs_notifications_min_priority_low_and_higher": "Prioritas rendah dan lebih tinggi", "prefs_notifications_min_priority_default_and_higher": "Prioritas bawaan dan lebih tinggi", @@ -256,11 +256,11 @@ "account_usage_cannot_create_portal_session": "Tidak dapat membuka portal tagihan", "account_delete_dialog_billing_warning": "Menghapus akun Anda juga membatalkan tagihan langganan dengan segera. Anda tidak akan memiliki akses lagi ke dasbor tagihan.", "account_upgrade_dialog_title": "Ubah peringkat akun", - "account_upgrade_dialog_proration_info": "Prorasi: Saat melakukan upgrade antar paket berbayar, selisih harga akan langsung dibebankan ke. Saat menurunkan ke tingkat yang lebih rendah, saldo akan digunakan untuk membayar periode penagihan di masa mendatang.", + "account_upgrade_dialog_proration_info": "Prorasi: Ketika mengubah rencana berbayar, perubahan harga akan ditagih atau dikembalikan di faktur berikutnya. Anda tidak akan menerima faktur lain sampai akhir periode tagihan.", "account_upgrade_dialog_reservations_warning_other": "Peringkat yang dipilih memperbolehkan lebih sedikit reservasi topik daripada peringkat Anda saat ini. Sebelum mengubah peringkat Anda, silakan menghapus setidaknya {{count}} reservasi. Anda dapat menghapus reservasi di Pengaturan.", - "account_upgrade_dialog_tier_features_reservations_other": "{{reservations}} topik yang telah direservasi", - "account_upgrade_dialog_tier_features_messages_other": "{{messages}} pesan harian", - "account_upgrade_dialog_tier_features_emails_other": "{{emails}} surel harian", + "account_upgrade_dialog_tier_features_reservations": "{{reservations}} topik yang telah direservasi", + "account_upgrade_dialog_tier_features_messages": "{{messages}} pesan harian", + "account_upgrade_dialog_tier_features_emails": "{{emails}} surel harian", "account_upgrade_dialog_tier_features_attachment_file_size": "{{filesize}} per berkas", "account_upgrade_dialog_tier_features_attachment_total_size": "{{totalsize}} jumlah penyimpanan", "account_upgrade_dialog_tier_selected_label": "Dipilih", @@ -278,7 +278,7 @@ "account_tokens_table_expires_header": "Kedaluwarsa", "account_tokens_table_never_expires": "Tidak pernah kedaluwarsa", "account_tokens_table_current_session": "Sesi peramban saat ini", - "common_copy_to_clipboard": "Salin ke papan klip", + "account_tokens_table_copy_to_clipboard": "Salin ke papan klip", "account_tokens_table_copied_to_clipboard": "Token akses disalin", "account_tokens_table_cannot_delete_or_edit": "Tidak dapat menyunting atau menghapus token sesi saat ini", "account_tokens_table_create_token_button": "Buat token akses", @@ -340,46 +340,5 @@ "prefs_reservations_dialog_description": "Mereservasikan sebuah topik memberikan Anda kemilikan pada topik, dan memungkinkan Anda untuk mendefinisikan perizinan akses untuk pengguna lain melalui topik.", "prefs_reservations_dialog_topic_label": "Topik", "prefs_reservations_dialog_access_label": "Akses", - "reservation_delete_dialog_description": "Menghapus sebuah reservasi menghapus kemilikan pada topik, dan memperbolehkan orang-orang lain untuk mereservasinya.", - "account_upgrade_dialog_interval_yearly": "Setiap tahun", - "account_upgrade_dialog_tier_price_billed_yearly": "Ditagih {{price}} setiap tahun. Hemat {{save}}.", - "account_upgrade_dialog_interval_yearly_discount_save": "hemat {{discount}}%", - "account_upgrade_dialog_interval_monthly": "Setiap bulan", - "account_basics_tier_interval_monthly": "setiap bulan", - "account_basics_tier_interval_yearly": "setiap tahun", - "account_upgrade_dialog_interval_yearly_discount_save_up_to": "hemat sampai {{discount}}%", - "account_upgrade_dialog_tier_features_no_reservations": "Tidak ada topik yang direservasi", - "account_upgrade_dialog_tier_price_per_month": "bulan", - "account_upgrade_dialog_tier_price_billed_monthly": "{{price}} per bulan. Ditagih setiap bulan.", - "account_upgrade_dialog_billing_contact_email": "Untuk pertanyaan penagihan, silakan hubungi kami secara langsung.", - "account_upgrade_dialog_billing_contact_website": "Untuk pertanyaan penagihan, silakan menuju ke situs web kami.", - "account_upgrade_dialog_tier_features_reservations_one": "{{reservations}} topik yang direservasi", - "account_upgrade_dialog_tier_features_emails_one": "{{emails}} surel harian", - "account_upgrade_dialog_tier_features_messages_one": "{{messages}} pesan harian", - "publish_dialog_call_label": "Panggilan telepon", - "publish_dialog_call_placeholder": "Nomor telepon untuk dipanggil dengan pesan, mis. +622223334444, atau 'yes'", - "account_basics_phone_numbers_title": "Nomor telepon", - "account_basics_phone_numbers_dialog_description": "Untuk menggunakan fitur notifikasi telepon, Anda perlu menambahkan dan memverifikasi setidaknya satu nomor telepon. Verifikasi dapat dilakukan melalui SMS atau panggilan telepon.", - "account_basics_phone_numbers_no_phone_numbers_yet": "Belum ada nomor telepon", - "account_basics_phone_numbers_dialog_title": "Tambahkan nomor telepon", - "account_basics_phone_numbers_dialog_number_label": "Nomor telepon", - "account_basics_phone_numbers_dialog_number_placeholder": "mis. +62222333444", - "account_basics_phone_numbers_dialog_verify_button_sms": "Kirim SMS", - "account_basics_phone_numbers_dialog_channel_call": "Panggil", - "account_usage_calls_title": "Panggilan telepon dilakukan", - "account_usage_calls_none": "Tidak ada panggilan telepon yang dapat dilakukan dengan akun ini", - "account_upgrade_dialog_tier_features_calls_other": "{{calls}} panggilan telepon harian", - "publish_dialog_call_reset": "Hapus panggilan telepon", - "account_basics_phone_numbers_description": "Untuk notifikasi panggilan telepon", - "account_basics_phone_numbers_copied_to_clipboard": "Nomor telepon disalin ke papan klip", - "publish_dialog_chip_call_label": "Panggilan telepon", - "account_basics_phone_numbers_dialog_verify_button_call": "Panggil saya", - "account_basics_phone_numbers_dialog_code_placeholder": "mis. 123456", - "account_basics_phone_numbers_dialog_check_verification_button": "Konfirmasi kode", - "account_basics_phone_numbers_dialog_channel_sms": "SMS", - "account_upgrade_dialog_tier_features_calls_one": "{{calls}} panggilan telepon harian", - "account_upgrade_dialog_tier_features_no_calls": "Tidak ada panggilan telepon", - "account_basics_phone_numbers_dialog_code_label": "Kode verifikasi", - "publish_dialog_call_item": "Panggil nomor telepon {{number}}", - "publish_dialog_chip_call_no_verified_numbers_tooltip": "Tidak ada nomor telepon terverifikasi" + "reservation_delete_dialog_description": "Menghapus sebuah reservasi menghapus kemilikan pada topik, dan memperbolehkan orang-orang lain untuk mereservasinya." } diff --git a/web/public/static/langs/it.json b/web/public/static/langs/it.json index 95c4b5b..3dc40d5 100644 --- a/web/public/static/langs/it.json +++ b/web/public/static/langs/it.json @@ -178,7 +178,7 @@ "prefs_notifications_sound_play": "Riproduci il suono selezionato", "prefs_notifications_min_priority_title": "Priorità minima", "subscribe_dialog_login_description": "Questo argomento è protetto da password. Per favore inserisci username e password per iscriverti.", - "common_back": "Indietro", + "subscribe_dialog_login_button_back": "Indietro", "subscribe_dialog_error_user_not_authorized": "Utente {{username}} non autorizzato", "prefs_notifications_title": "Notifiche", "prefs_notifications_delete_after_title": "Elimina le notifiche", @@ -187,77 +187,5 @@ "prefs_notifications_delete_after_one_week": "Dopo una settimana", "prefs_notifications_delete_after_one_month": "Dopo un mese", "prefs_notifications_delete_after_three_hours_description": "Le notifiche vengono eliminate automaticamente dopo tre ore", - "error_boundary_unsupported_indexeddb_description": "L'app web ntfy ha bisogno di IndexedDB per funzionare e il tuo browser non supporta IndexedDB in modalità di navigazione privata.

Anche se questo è un peccato, non ha molto senso usare il web ntfy app in modalità di navigazione privata comunque, perché tutto è archiviato nella memoria del browser. Puoi leggere di più a riguardo in questo numero di GitHub o parlarci su Discord o Matrix.", - "nav_upgrade_banner_label": "Passa alla versione Pro di ntfy", - "alert_not_supported_context_description": "Le Notificche sono supportate solo tramite HTTPS. Questa è una limitazione delle Notifications API.", - "account_basics_password_dialog_new_password_label": "Nuova password", - "action_bar_profile_logout": "Esci", - "account_basics_tier_interval_monthly": "mensile", - "account_basics_tier_interval_yearly": "annuale", - "account_basics_tier_upgrade_button": "Passa alla versione Pro", - "account_basics_tier_change_button": "Cambia", - "account_basics_tier_paid_until": "Abbonamento pagato fino a {{data}}, e si rinnoverà automaticamente", - "account_basics_tier_payment_overdue": "Il pagamento è scaduto. La preghiamo di aggiornare il suo metodo di pagamento, altrimenti il suo account verrà presto declassato.", - "account_basics_tier_canceled_subscription": "L'abbonamento è stato annullato e sarà declassato ad account gratuito a partire dalla {{data}}.", - "account_basics_tier_manage_billing_button": "Gestire la fatturazione", - "account_usage_messages_title": "Messaggi pubblicati", - "account_usage_reservations_title": "Argomenti riservati", - "account_usage_reservations_none": "Non ci sono argomenti riservati per questo account", - "signup_form_toggle_password_visibility": "Imposta la visibilità della password", - "signup_already_have_account": "Hai già un account? Accedi!", - "signup_disabled": "Registrazione disabilitata", - "signup_title": "Crea un account ntfy", - "signup_form_username": "Nome utente", - "signup_form_password": "Password", - "signup_form_confirm_password": "Conferma password", - "signup_form_button_submit": "Registrazione", - "signup_error_username_taken": "Il nome utente {{username}} è già utilizzato", - "signup_error_creation_limit_reached": "Il limite per la creazione di account è stato raggiunto", - "login_title": "Accedi al tuo account ntfy", - "login_form_button_submit": "Accedi", - "login_link_signup": "Registrati", - "login_disabled": "L'accesso è disabilitato", - "action_bar_account": "Account", - "action_bar_change_display_name": "Cambia il nome da visualizzare", - "action_bar_reservation_limit_reached": "Limite raggiunto", - "action_bar_profile_title": "Profilo", - "action_bar_profile_settings": "Impostazioni", - "action_bar_reservation_add": "Riserva un argomento", - "action_bar_reservation_edit": "Modifica l'argomento riservato", - "action_bar_reservation_delete": "Rimuovi l'argomento riservato", - "action_bar_sign_in": "Accedi", - "action_bar_sign_up": "Registrati", - "nav_button_account": "Account", - "nav_upgrade_banner_description": "Riserva argomenti, più messaggi ed e-mail e allegati più grandi", - "display_name_dialog_description": "Imposta un nome alternativo per un argomento che viene visualizzato nell'elenco delle sottoscrizioni. Questo aiuta a identificare più facilmente gli argomenti con nomi complicati.", - "display_name_dialog_title": "Cambia il nome visualizzato", - "display_name_dialog_placeholder": "Nome visualizzato", - "reserve_dialog_checkbox_label": "Riserva un argomento e configura l'accesso", - "subscribe_dialog_subscribe_button_generate_topic_name": "Genera un nome", - "subscribe_dialog_error_topic_already_reserved": "Argomento già in uso", - "account_basics_title": "Account", - "account_basics_username_title": "Nome utente", - "account_basics_username_admin_tooltip": "Sei Amministratore", - "account_basics_password_title": "Password", - "account_basics_password_description": "Cambia la password del tuo account", - "account_basics_password_dialog_title": "Cambia la password", - "account_basics_password_dialog_current_password_label": "Password attuale", - "account_basics_password_dialog_confirm_password_label": "Conferma la password", - "account_basics_password_dialog_button_submit": "Cambia la password", - "account_basics_password_dialog_current_password_incorrect": "Password errata", - "account_usage_title": "Utilizzo", - "account_usage_of_limit": "di {{limit}}", - "account_usage_unlimited": "Illimitato", - "account_usage_limits_reset_daily": "I limiti di utilizzo vengono azzerati ogni giorno a mezzanotte (orario UTC)", - "account_basics_tier_title": "Tipo di account", - "account_basics_tier_description": "Permessi del tuo account", - "account_basics_tier_admin": "Amministratore", - "account_basics_tier_admin_suffix_with_tier": "(con livello {{tier}})", - "account_basics_tier_admin_suffix_no_tier": "(nessun livello)", - "account_basics_tier_basic": "Base", - "account_basics_tier_free": "Gratuito", - "account_usage_emails_title": "Email inviate", - "account_usage_cannot_create_portal_session": "Impossibile aprire il portale di pagamento", - "account_delete_title": "Elimina account", - "account_basics_username_description": "Hey, sei tu ❤" + "error_boundary_unsupported_indexeddb_description": "L'app web ntfy ha bisogno di IndexedDB per funzionare e il tuo browser non supporta IndexedDB in modalità di navigazione privata.

Anche se questo è un peccato, non ha molto senso usare il web ntfy app in modalità di navigazione privata comunque, perché tutto è archiviato nella memoria del browser. Puoi leggere di più a riguardo in questo numero di GitHub o parlarci su Discord o Matrix." } diff --git a/web/public/static/langs/ja.json b/web/public/static/langs/ja.json index 9c68679..1e2ff37 100644 --- a/web/public/static/langs/ja.json +++ b/web/public/static/langs/ja.json @@ -20,7 +20,7 @@ "subscribe_dialog_login_description": "このトピックはログインする必要があります。ユーザー名とパスワードを入力してください。", "subscribe_dialog_login_username_label": "ユーザー名, 例) phil", "subscribe_dialog_login_password_label": "パスワード", - "common_back": "戻る", + "subscribe_dialog_login_button_back": "戻る", "subscribe_dialog_login_button_login": "ログイン", "prefs_notifications_min_priority_high_and_higher": "優先度高 およびそれ以上", "prefs_notifications_min_priority_max_only": "優先度最高のみ", @@ -219,142 +219,5 @@ "nav_upgrade_banner_description": "トピックを予約、より多くのメッセージとメール、より大きい添付ファイル", "signup_error_username_taken": "ユーザー名 {{username}} は既に使用されています", "action_bar_reservation_delete": "予約を削除する", - "display_name_dialog_description": "購読リストに表示されるトピックの別名を設定して、複雑な名前のトピックの識別を容易にします。", - "reserve_dialog_checkbox_label": "トピックを保存してアクセスを編集", - "subscribe_dialog_subscribe_button_generate_topic_name": "名前を生成", - "subscribe_dialog_error_topic_already_reserved": "このトピックは予約済みです", - "account_basics_title": "アカウント", - "account_basics_tier_description": "アカウントのパワーレベル", - "account_basics_tier_admin": "管理者", - "account_basics_tier_admin_suffix_with_tier": "(ティア {{tier}})", - "account_basics_tier_free": "無料", - "account_usage_attachment_storage_description": "1ファイルあたり{{filesize}}、{{expiry}}を過ぎると削除", - "account_usage_basis_ip_description": "アカウントの使用量統計および制限はあなたのIPアドレスに基づいているため、他のユーザーと共有される可能性があります。上記制限は既存のレート制限に基づく概算値です。", - "account_usage_cannot_create_portal_session": "支払いポータルを開けませんでした", - "account_delete_title": "アカウントを削除", - "account_delete_description": "アカウントを永久的に削除", - "account_delete_dialog_description": "サーバーに保存されている全てのデータを含むあなたのアカウント情報を削除します。削除後、あなたのユーザー名は7日間利用できません。もし本当に先に進めたい場合、下の入力欄にパスワードを入力して確認して下さい。", - "account_delete_dialog_label": "パスワード", - "account_delete_dialog_button_cancel": "キャンセル", - "account_delete_dialog_button_submit": "永久的にアカウントを削除", - "account_delete_dialog_billing_warning": "アカウントを削除するとサブスクリプション支払いも即時キャンセルされます。支払いダッシュボードにもアクセスできなくなります。", - "account_upgrade_dialog_title": "アカウントティアを変更", - "account_upgrade_dialog_cancel_warning": "これによりサブスクリプションをキャンセルし{{date}}にアカウントをダウングレードします。同日、トピック予約およびサーバーにキャッシュされたメッセージは削除されます。", - "account_upgrade_dialog_proration_info": "追記。有料プランをアップグレードする場合、価格差は即座に請求されます。ダウングレードする場合、差額は次の請求期間の支払いに利用されます。", - "account_upgrade_dialog_tier_features_reservations_other": "予約のトピック{{reservations}}件", - "account_upgrade_dialog_tier_features_emails_other": "日次メール{{emails}}件", - "account_upgrade_dialog_tier_features_messages_other": "日次メッセージ{{messages}}件", - "account_upgrade_dialog_tier_selected_label": "選択", - "account_upgrade_dialog_tier_current_label": "現在", - "account_upgrade_dialog_button_cancel": "キャンセル", - "account_upgrade_dialog_button_redirect_signup": "サインアップ", - "account_upgrade_dialog_button_pay_now": "支払いしてサブスクライブする", - "account_upgrade_dialog_button_cancel_subscription": "サブスクリプションをキャンセル", - "account_upgrade_dialog_button_update_subscription": "サブスクリプションを更新", - "account_tokens_description": "ntfy APIで発行または購読する際にアクセストークンを使うことで、アカウント認証情報を送信する必要がなくなります。詳細はドキュメントを確認して下さい。", - "account_tokens_table_token_header": "トークン", - "account_tokens_table_label_header": "ラベル", - "account_tokens_table_last_access_header": "最終アクセス", - "account_tokens_table_expires_header": "期限", - "account_tokens_table_never_expires": "無期限", - "account_tokens_table_current_session": "現在のブラウザセッション", - "common_copy_to_clipboard": "クリップボードにコピー", - "account_tokens_table_copied_to_clipboard": "アクセストークンをコピーしました", - "account_tokens_table_cannot_delete_or_edit": "現在のセッショントークンは編集または削除できません", - "account_tokens_table_create_token_button": "アクセストークンを生成", - "account_tokens_table_last_origin_tooltip": "IPアドレス {{ip}} から、クリックして参照", - "account_tokens_dialog_title_create": "アクセストークンを生成", - "account_tokens_dialog_title_edit": "アクセストークンを編集", - "account_tokens_dialog_title_delete": "アクセストークンを削除", - "account_tokens_dialog_label": "ラベル、例:Radarr通知", - "account_tokens_dialog_button_create": "トークンを生成", - "account_tokens_dialog_button_update": "トークンを更新", - "account_tokens_dialog_button_cancel": "キャンセル", - "account_tokens_dialog_expires_label": "アクセストークン有効期限", - "account_tokens_dialog_expires_unchanged": "有効期限を変更しない", - "account_tokens_dialog_expires_x_hours": "トークンは {{hours}} 時間後に失効します", - "account_tokens_dialog_expires_x_days": "トークンは {{days}} 日後に失効します", - "account_tokens_dialog_expires_never": "トークン失効なし", - "account_tokens_delete_dialog_title": "アクセストークンを削除", - "account_tokens_delete_dialog_submit_button": "トークンを永久削除", - "prefs_users_description_no_sync": "ユーザー名とパスワードはアカウントと同期されません。", - "prefs_users_table_cannot_delete_or_edit": "ログインしているユーザーは削除または編集できません", - "prefs_reservations_title": "予約されたトピック", - "prefs_reservations_description": "ここでトピック名を個人利用の為に予約する事ができます。トピックを予約する事でそのトピックの所有権が付与され、他のユーザーにアクセス権を付与する事ができるようになります。", - "prefs_reservations_add_button": "予約トピックを追加する", - "prefs_reservations_edit_button": "トピックへのアクセスを編集する", - "prefs_reservations_delete_button": "トピックへのアクセスをリセットする", - "prefs_reservations_table": "予約トピックの一覧", - "prefs_reservations_table_topic_header": "トピック", - "prefs_reservations_table_everyone_deny_all": "自分のみ発行と購読が可能", - "prefs_reservations_table_everyone_read_only": "自分は発行と購読が可能、誰でも購読可能", - "prefs_reservations_table_everyone_write_only": "自分は発行と購読可能、誰でも発行可能", - "prefs_reservations_table_everyone_read_write": "誰でも発行と購読が可能", - "prefs_reservations_table_not_subscribed": "購読されていません", - "prefs_reservations_table_click_to_subscribe": "クリックして購読", - "prefs_reservations_dialog_title_edit": "予約トピックを編集", - "prefs_reservations_dialog_title_delete": "トピック予約を削除", - "prefs_reservations_dialog_topic_label": "トピック", - "prefs_reservations_dialog_access_label": "アクセス", - "reservation_delete_dialog_action_keep_title": "キャッシュされたメッセージと添付ファイルを保持する", - "reservation_delete_dialog_action_keep_description": "サーバーにキャッシュされたメッセージと添付ファイルは公開されてトピック名を知っている人が閲覧できるようになります。", - "reservation_delete_dialog_action_delete_title": "キャッシュされたメッセージと添付ファイルを削除する", - "reservation_delete_dialog_action_delete_description": "キャッシュされたメッセージと添付ファイルは永久的に削除されます。この操作は元に戻せません。", - "account_basics_username_admin_tooltip": "あなたは管理者です", - "account_basics_password_title": "パスワード", - "account_basics_password_dialog_current_password_label": "現在のパスワード", - "account_usage_limits_reset_daily": "使用量制限は世界協定時 (UTC) の深夜に毎日リセットされます", - "account_basics_tier_basic": "ベーシック", - "account_basics_tier_paid_until": "サブスクリプションは{{date}}まで有効で、自動更新されます", - "account_basics_username_title": "ユーザー名", - "account_basics_username_description": "あなたのお名前です ❤", - "account_basics_password_description": "アカウントパスワードを変更", - "account_basics_password_dialog_title": "パスワード変更", - "account_basics_password_dialog_confirm_password_label": "パスワードを確認", - "account_basics_password_dialog_current_password_incorrect": "パスワードが異なります", - "account_usage_of_limit": ": {{limit}}", - "account_usage_unlimited": "無制限", - "account_basics_tier_upgrade_button": "プロにアップグレード", - "account_basics_tier_manage_billing_button": "支払い方法を管理", - "account_basics_password_dialog_new_password_label": "新しいパスワード", - "account_basics_password_dialog_button_submit": "パスワードを変更", - "account_usage_title": "使用量", - "account_basics_tier_title": "アカウントタイプ", - "account_basics_tier_admin_suffix_no_tier": "(ティアなし)", - "account_basics_tier_change_button": "変更", - "account_basics_tier_payment_overdue": "支払期限を過ぎています。支払い方法を更新しないと、近日中にアカウントはダウングレードされます。", - "account_basics_tier_canceled_subscription": "あなたのサブスクリプションはキャンセルされ{{date}}に無料アカウントにダウングレードされます。", - "account_usage_messages_title": "発行されたメッセージ", - "account_usage_reservations_none": "このアカウントで予約されたトピックはありません", - "account_usage_attachment_storage_title": "添付ストレージ", - "account_usage_emails_title": "送信済みメール", - "account_upgrade_dialog_reservations_warning_one": "選択されたティアは、現在のティアよりも少ない予約トピックを利用できます。ティアを変更する前に、少なくとも1つの予約を削除してください。予約の削除は、設定で行うことができます。", - "account_usage_reservations_title": "予約されたトピック", - "account_upgrade_dialog_reservations_warning_other": "選択されたティアは、現在のティアよりも少ない予約トピックを利用できます。ティアを変更する前に、少なくとも{{count}}個の予約を削除してください。予約の削除は、設定で行うことができます。", - "account_tokens_delete_dialog_description": "アクセストークンを削除する前に、アプリやスクリプトが利用中でないか確認して下さい。この操作は元に戻せません。", - "account_upgrade_dialog_tier_features_attachment_file_size": "1ファイルあたり{{filesize}}", - "account_upgrade_dialog_tier_features_attachment_total_size": "総ストレージ{{totalsize}}", - "account_tokens_title": "アクセストークン", - "prefs_reservations_limit_reached": "予約トピック数の上限に達しました。", - "prefs_reservations_table_access_header": "アクセス", - "prefs_reservations_dialog_title_add": "トピックを予約", - "prefs_reservations_dialog_description": "トピックを予約する事でそのトピックの所有権が付与され、他のユーザーにアクセス権を付与する事ができるようになります。", - "reservation_delete_dialog_description": "予約を削除するとトピックの所有権を失い、他の人が予約できるようになります。既存のメッセージや添付ファイルは保持または削除することができます。", - "reservation_delete_dialog_submit_button": "予約を削除", - "account_basics_tier_interval_monthly": "毎月", - "account_upgrade_dialog_interval_monthly": "毎月", - "account_upgrade_dialog_interval_yearly": "毎年", - "account_upgrade_dialog_interval_yearly_discount_save_up_to": "最大{{discount}}%節約", - "account_upgrade_dialog_tier_features_no_reservations": "予約トピックなし", - "account_upgrade_dialog_billing_contact_email": "支払いについての問い合わせは、直接お問い合わせください。", - "account_upgrade_dialog_interval_yearly_discount_save": "{{discount}}%節約", - "account_basics_tier_interval_yearly": "毎年", - "account_upgrade_dialog_tier_price_per_month": "月", - "account_upgrade_dialog_tier_price_billed_monthly": "年間{{price}}。月毎の支払い。", - "account_upgrade_dialog_tier_price_billed_yearly": "年間{{price}}の支払い。{{save}}節約。", - "account_upgrade_dialog_billing_contact_website": "支払いに関する質問は、ウェブサイトを参照して下さい。", - "account_upgrade_dialog_tier_features_messages_one": "毎日 {{messages}} メッセージ", - "account_upgrade_dialog_tier_features_reservations_one": "予約済みトピック {{reservations}} 件", - "account_upgrade_dialog_tier_features_emails_one": "毎日メール {{emails}} 件", - "publish_dialog_call_label": "電話" + "display_name_dialog_description": "購読リストに表示されるトピックの別名を設定して、複雑な名前のトピックの識別を容易にします。" } diff --git a/web/public/static/langs/ko.json b/web/public/static/langs/ko.json index 2e46c7a..67c3128 100644 --- a/web/public/static/langs/ko.json +++ b/web/public/static/langs/ko.json @@ -93,7 +93,7 @@ "subscribe_dialog_error_user_not_authorized": "사용자 {{username}} 은(는) 인증되지 않았습니다", "subscribe_dialog_login_username_label": "사용자 이름, 예를 들면 phil", "subscribe_dialog_login_password_label": "비밀번호", - "common_back": "뒤로가기", + "subscribe_dialog_login_button_back": "뒤로가기", "subscribe_dialog_login_button_login": "로그인", "prefs_notifications_title": "알림", "prefs_notifications_sound_title": "알림 효과음", diff --git a/web/public/static/langs/nb_NO.json b/web/public/static/langs/nb_NO.json index 0dd9571..312791d 100644 --- a/web/public/static/langs/nb_NO.json +++ b/web/public/static/langs/nb_NO.json @@ -113,7 +113,7 @@ "prefs_notifications_delete_after_one_week_description": "Merknader slettes automatisk etter én uke", "prefs_notifications_delete_after_one_month_description": "Merknader slettes automatisk etter én måned", "priority_min": "min.", - "common_back": "Tilbake", + "subscribe_dialog_login_button_back": "Tilbake", "prefs_notifications_delete_after_three_hours": "Etter tre timer", "prefs_users_table_base_url_header": "Tjeneste-nettadresse", "common_cancel": "Avbryt", diff --git a/web/public/static/langs/nl.json b/web/public/static/langs/nl.json index 8ccb629..3c7adb4 100644 --- a/web/public/static/langs/nl.json +++ b/web/public/static/langs/nl.json @@ -44,7 +44,7 @@ "notifications_mark_read": "Markeer als gelezen", "notifications_delete": "Verwijder", "notifications_copied_to_clipboard": "Gekopieerd naar klembord", - "notifications_tags": "Labels", + "notifications_tags": "Tags", "notifications_priority_x": "Prioriteit {{priority}}", "notifications_new_indicator": "Nieuwe notificatie", "notifications_attachment_image": "Afbeelding bijlage", @@ -140,7 +140,7 @@ "subscribe_dialog_subscribe_title": "Onderwerp abonneren", "subscribe_dialog_subscribe_description": "Onderwerpen zijn mogelijk niet beschermd met een wachtwoord, kies daarom een moeilijk te raden naam. Na abonneren kun je notificaties via PUT/POST sturen.", "subscribe_dialog_login_password_label": "Wachtwoord", - "common_back": "Terug", + "subscribe_dialog_login_button_back": "Terug", "subscribe_dialog_login_button_login": "Aanmelden", "subscribe_dialog_error_user_not_authorized": "Gebruiker {{username}} heeft geen toegang", "subscribe_dialog_error_user_anonymous": "anoniem", @@ -226,7 +226,7 @@ "account_usage_unlimited": "Onbeperkt", "account_basics_tier_title": "Account type", "account_basics_tier_admin": "Beheerder", - "account_basics_tier_admin_suffix_with_tier": "(met {{tier}} niveau)", + "account_basics_tier_admin_suffix_with_tier": "", "account_basics_tier_basic": "Basis", "account_basics_tier_free": "Gratis", "account_basics_tier_change_button": "Wijzig", @@ -248,137 +248,5 @@ "subscribe_dialog_error_topic_already_reserved": "Onderwerp al gereserveerd", "account_basics_password_dialog_title": "Wijzig wachtwoord", "account_usage_limits_reset_daily": "Gebruikslimieten worden dagelijks om middernacht (UTC) gereset", - "account_basics_tier_upgrade_button": "Upgrade naar Pro", - "account_upgrade_dialog_title": "Accountniveau wijzigen", - "account_upgrade_dialog_interval_yearly_discount_save": "bespaar {{discount}}%", - "account_upgrade_dialog_tier_price_billed_yearly": "{{price}} jaarlijks gefactureerd. Bespaar {{save}}.", - "account_upgrade_dialog_cancel_warning": "Hiermee wordt uw abonnement opgezegd en wordt uw account gedowngraded op {{date}}. Op die datum worden onderwerpreserveringen en berichten in de cache op de server verwijderd .", - "account_tokens_dialog_button_update": "Token bijwerken", - "account_upgrade_dialog_proration_info": "Pro rata: Bij een upgrade tussen betaalde abonnementen wordt het prijsverschil onmiddellijk in rekening gebracht. Wanneer u downgradet naar een lager niveau, wordt het saldo gebruikt om toekomstige factureringsperioden te betalen.", - "account_upgrade_dialog_reservations_warning_one": "Het geselecteerde niveau staat minder gereserveerde onderwerpen toe dan uw huidige niveau. Voordat u uw niveau wijzigt, , moet u ten minste één reservering verwijderen . U kunt reserveringen verwijderen in de Instellingen.", - "account_upgrade_dialog_reservations_warning_other": "Het geselecteerde niveau staat minder gereserveerde onderwerpen toe dan uw huidige niveau. Voordat u uw niveau wijzigt, moet u ten minste {{count}} reserveringen verwijderen. U kunt reserveringen verwijderen in de Instellingen.", - "account_upgrade_dialog_tier_features_reservations_other": "{{reservations}} gereserveerde onderwerpen", - "account_upgrade_dialog_billing_contact_email": "Neem voor vragen over facturering rechtstreeks contact met ons op.", - "account_tokens_table_token_header": "Token", - "account_tokens_table_never_expires": "Verloopt nooit", - "account_tokens_table_current_session": "Huidige browsersessie", - "prefs_reservations_table_everyone_read_only": "Ik kan publiceren en abonneren, iedereen kan zich abonneren", - "prefs_reservations_table_everyone_write_only": "Ik kan publiceren en abonneren, iedereen kan publiceren", - "account_usage_reservations_none": "Geen gereserveerde onderwerpen voor dit account", - "account_usage_attachment_storage_title": "Bijlage-opslag", - "account_usage_attachment_storage_description": "{{filesize}} per bestand, verwijderd na {{expiry}}", - "account_delete_dialog_description": "Hiermee wordt uw account definitief verwijderd, inclusief alle gegevens die op de server zijn opgeslagen. Na verwijdering is uw gebruikersnaam 7 dagen niet beschikbaar. Als u echt wilt doorgaan, bevestig dan met uw wachtwoord in het onderstaande vak.", - "account_delete_dialog_billing_warning": "Als u uw account verwijdert, wordt ook uw facturering onmiddellijk geannuleerd. U heeft dan geen toegang meer tot het factureringsdashboard.", - "account_tokens_dialog_button_cancel": "Annuleren", - "reservation_delete_dialog_submit_button": "Reservering verwijderen", - "prefs_reservations_table_everyone_deny_all": "Alleen ik kan publiceren en abonneren", - "reservation_delete_dialog_description": "Het verwijderen van een reservering geeft het eigendom van het onderwerp op en stelt anderen in staat het te reserveren. U kunt bestaande berichten en bijlagen behouden of verwijderen.", - "account_basics_tier_interval_monthly": "maandelijks", - "account_basics_tier_interval_yearly": "jaarlijks", - "account_usage_basis_ip_description": "Gebruiksstatistieken en -limieten voor dit account zijn gebaseerd op uw IP-adres en kunnen dus worden gedeeld met andere gebruikers. De hierboven weergegeven limieten zijn bij benadering gebaseerd op de bestaande limieten.", - "account_usage_cannot_create_portal_session": "Kan factureringsportaal niet openen", - "account_delete_title": "Account verwijderen", - "account_delete_description": "Verwijder uw account definitief", - "account_delete_dialog_label": "Wachtwoord", - "account_delete_dialog_button_cancel": "Annuleren", - "account_delete_dialog_button_submit": "Verwijder uw account definitief", - "account_upgrade_dialog_interval_monthly": "Maandelijks", - "account_upgrade_dialog_interval_yearly": "Jaarlijks", - "account_upgrade_dialog_interval_yearly_discount_save_up_to": "bespaar tot {{discount}}%", - "account_upgrade_dialog_tier_features_no_reservations": "Geen gereserveerde onderwerpen", - "account_upgrade_dialog_tier_features_attachment_total_size": "{{totalsize}} totale opslag", - "account_upgrade_dialog_tier_current_label": "Huidig", - "account_upgrade_dialog_button_update_subscription": "Abonnement bijwerken", - "account_tokens_title": "Toegangstokens", - "account_tokens_description": "Gebruik toegangstokens bij het publiceren en abonneren via de ntfy API, zodat u uw accountgegevens niet hoeft op te sturen. Bekijk de documentatie voor meer informatie.", - "account_tokens_table_label_header": "Label", - "account_tokens_table_cannot_delete_or_edit": "Kan huidige sessietoken niet bewerken of verwijderen", - "account_tokens_dialog_expires_label": "Toegangstoken verloopt over", - "account_tokens_dialog_expires_unchanged": "Vervaldatum ongewijzigd laten", - "account_tokens_dialog_expires_x_hours": "Token verloopt over {{hours}} uur", - "account_tokens_dialog_expires_x_days": "Token verloopt over {{days}} dagen", - "account_tokens_dialog_expires_never": "Token verloopt nooit", - "account_tokens_delete_dialog_title": "Toegangstoken verwijderen", - "account_tokens_delete_dialog_description": "Voordat u een toegangstoken verwijdert, moet u ervoor zorgen dat er geen toepassingen of scripts actief gebruik van maken. Deze actie kan niet ongedaan worden gemaakt.", - "prefs_users_table_cannot_delete_or_edit": "Kan ingelogde gebruiker niet verwijderen of bewerken", - "prefs_reservations_title": "Gereserveerde onderwerpen", - "prefs_reservations_description": "U kunt hier onderwerpnamen reserveren voor persoonlijk gebruik. Door een onderwerp te reserveren, wordt u eigenaar van het onderwerp en kunt u toegangsmachtigingen voor andere gebruikers voor het onderwerp definiëren.", - "prefs_reservations_limit_reached": "Je hebt je limiet voor gereserveerde onderwerpen bereikt.", - "prefs_reservations_add_button": "Gereserveerd onderwerp toevoegen", - "prefs_reservations_table_click_to_subscribe": "Klik om je te abonneren", - "prefs_reservations_dialog_title_add": "Onderwerp reserveren", - "prefs_reservations_dialog_title_edit": "Gereserveerd onderwerp bewerken", - "prefs_reservations_dialog_title_delete": "Onderwerpreservering verwijderen", - "prefs_reservations_dialog_description": "Door een onderwerp te reserveren, wordt u eigenaar van het onderwerp en kunt u toegangsmachtigingen voor andere gebruikers voor het onderwerp definiëren.", - "prefs_reservations_dialog_topic_label": "Onderwerp", - "prefs_reservations_dialog_access_label": "Toegang", - "reservation_delete_dialog_action_keep_title": "Bewaar in de cache opgeslagen berichten en bijlagen", - "reservation_delete_dialog_action_keep_description": "Berichten en bijlagen die in de cache op de server zijn opgeslagen, worden publiekelijk zichtbaar voor mensen die de onderwerpnaam kennen.", - "reservation_delete_dialog_action_delete_description": "Berichten en bijlagen in de cache worden permanent verwijderd. Deze actie kan niet ongedaan gemaakt worden.", - "account_upgrade_dialog_tier_features_reservations_one": "{{reservations}} gereserveerd onderwerp", - "account_upgrade_dialog_tier_features_messages_one": "{{messages}} dagelijks bericht", - "account_upgrade_dialog_tier_features_messages_other": "{{messages}} dagelijkse berichten", - "account_upgrade_dialog_tier_features_emails_one": "{{emails}} dagelijkse e-mail", - "account_upgrade_dialog_tier_features_emails_other": "{{emails}} dagelijkse e-mails", - "account_upgrade_dialog_tier_features_attachment_file_size": "{{filesize}} per bestand", - "account_upgrade_dialog_tier_price_per_month": "maand", - "account_upgrade_dialog_tier_price_billed_monthly": "{{price}} per jaar. Maandelijks gefactureerd.", - "account_upgrade_dialog_tier_selected_label": "Geselecteerd", - "account_upgrade_dialog_billing_contact_website": "Raadpleeg voor vragen over facturering onze website.", - "account_upgrade_dialog_button_cancel": "Annuleren", - "account_upgrade_dialog_button_redirect_signup": "Nu aanmelden", - "account_upgrade_dialog_button_pay_now": "Nu betalen en inschrijven", - "account_upgrade_dialog_button_cancel_subscription": "Abonnement opzeggen", - "account_tokens_table_last_access_header": "Laatste toegang", - "account_tokens_table_expires_header": "Verloopt op", - "common_copy_to_clipboard": "Kopieer naar klembord", - "account_tokens_table_copied_to_clipboard": "Toegangstoken gekopieerd", - "account_tokens_delete_dialog_submit_button": "Token definitief verwijderen", - "prefs_users_description_no_sync": "Gebruikers en wachtwoorden worden niet gesynchroniseerd met uw account.", - "reservation_delete_dialog_action_delete_title": "Verwijder in de cache opgeslagen berichten en bijlagen", - "account_basics_tier_description": "Het niveau van uw account", - "account_basics_tier_admin_suffix_no_tier": "(geen niveau)", - "account_basics_tier_manage_billing_button": "Facturering beheren", - "account_usage_messages_title": "Gepubliceerde berichten", - "account_usage_emails_title": "E-mails verzonden", - "account_usage_reservations_title": "Gereserveerde onderwerpen", - "account_tokens_table_create_token_button": "Toegangstoken maken", - "account_tokens_table_last_origin_tooltip": "Vanaf IP-adres {{ip}}, klik om op te zoeken", - "account_tokens_dialog_title_create": "Toegangstoken maken", - "account_tokens_dialog_title_edit": "Toegangstoken bewerken", - "account_tokens_dialog_title_delete": "Toegangstoken verwijderen", - "account_tokens_dialog_label": "Label, bijv. Radarr-meldingen", - "account_tokens_dialog_button_create": "Token maken", - "prefs_reservations_edit_button": "Onderwerptoegang bewerken", - "prefs_reservations_delete_button": "Toegang tot onderwerp resetten", - "prefs_reservations_table": "Tabel met gereserveerde onderwerpen", - "prefs_reservations_table_topic_header": "Onderwerp", - "prefs_reservations_table_access_header": "Toegang", - "prefs_reservations_table_everyone_read_write": "Iedereen kan publiceren en abonneren", - "prefs_reservations_table_not_subscribed": "Niet geabonneerd", - "publish_dialog_call_label": "Telefoongesprek", - "publish_dialog_call_reset": "Telefoongesprek verwijderen", - "publish_dialog_chip_call_label": "Telefoongesprek", - "account_basics_phone_numbers_title": "Telefoonnummers", - "account_basics_phone_numbers_description": "Voor meldingen via telefoongesprekken", - "account_basics_phone_numbers_no_phone_numbers_yet": "Nog geen telefoonnummers", - "account_basics_phone_numbers_dialog_verify_button_call": "Bel me", - "account_upgrade_dialog_tier_features_calls_one": "{{calls}} dagelijkse telefoontjes", - "account_basics_phone_numbers_copied_to_clipboard": "Telefoonnummer gekopieerd naar klembord", - "publish_dialog_call_item": "Bel telefoonnummer {{nummer}}", - "account_basics_phone_numbers_dialog_check_verification_button": "Bevestig code", - "publish_dialog_chip_call_no_verified_numbers_tooltip": "Geen geverifieerde telefoonnummers", - "account_basics_phone_numbers_dialog_channel_call": "Telefoongesprek", - "account_basics_phone_numbers_dialog_number_label": "Telefoonnummer", - "account_basics_phone_numbers_dialog_channel_sms": "SMS", - "account_basics_phone_numbers_dialog_code_placeholder": "bijv. 123456", - "account_upgrade_dialog_tier_features_calls_other": "{{calls}} dagelijkse telefoontjes", - "account_upgrade_dialog_tier_features_no_calls": "Geen telefoontjes", - "account_basics_phone_numbers_dialog_description": "Als u de functie voor oproepmeldingen wilt gebruiken, moet u ten minste één telefoonnummer toevoegen en verifiëren. Verificatie kan worden gedaan via sms of een telefoontje.", - "account_basics_phone_numbers_dialog_title": "Telefoonnummer toevoegen", - "account_basics_phone_numbers_dialog_number_placeholder": "bijv. +1222333444", - "account_basics_phone_numbers_dialog_verify_button_sms": "Stuur SMS", - "account_basics_phone_numbers_dialog_code_label": "Verificatiecode", - "account_usage_calls_title": "Aantal telefoontjes", - "account_usage_calls_none": "Met dit account kan niet worden gebeld" + "account_basics_tier_upgrade_button": "Upgrade naar Pro" } diff --git a/web/public/static/langs/pl.json b/web/public/static/langs/pl.json index 9dea2b8..36ce869 100644 --- a/web/public/static/langs/pl.json +++ b/web/public/static/langs/pl.json @@ -107,7 +107,7 @@ "subscribe_dialog_login_username_label": "Nazwa użytkownika, np. phil", "subscribe_dialog_login_password_label": "Hasło", "publish_dialog_button_cancel": "Anuluj", - "common_back": "Powrót", + "subscribe_dialog_login_button_back": "Powrót", "subscribe_dialog_login_button_login": "Zaloguj się", "subscribe_dialog_error_user_not_authorized": "Użytkownik {{username}} nie ma uprawnień", "subscribe_dialog_error_user_anonymous": "anonim", @@ -235,87 +235,5 @@ "account_usage_title": "Użycie", "account_usage_of_limit": "z {{limit}}", "account_usage_unlimited": "Bez limitu", - "account_usage_limits_reset_daily": "Limity są resetowane codziennie o północy (UTC)", - "account_delete_dialog_button_submit": "Nieodwracalnie usuń konto", - "account_upgrade_dialog_tier_features_no_reservations": "Brak rezerwacji tematów", - "account_upgrade_dialog_tier_features_attachment_file_size": "{{filesize}} na plik", - "account_upgrade_dialog_tier_features_attachment_total_size": "{{totalsize}} pamięci łącznie", - "account_upgrade_dialog_tier_price_per_month": "miesiąc", - "account_upgrade_dialog_tier_price_billed_monthly": "{{price}} na rok. Płatne miesięcznie.", - "account_upgrade_dialog_billing_contact_email": "W razie pytań dotyczących rozliczeń skontaktuj się z nami bezpośrednio.", - "account_upgrade_dialog_billing_contact_website": "W razie pytań dotyczących rozliczeń sprawdź naszą stronę.", - "account_upgrade_dialog_button_cancel_subscription": "Anuluj subskrypcję", - "account_upgrade_dialog_button_update_subscription": "Zmień subskrypcję", - "account_tokens_title": "Tokeny dostępowe", - "account_tokens_table_token_header": "Token", - "account_tokens_table_label_header": "Etykieta", - "account_tokens_table_last_access_header": "Ostatnie użycie", - "account_tokens_table_expires_header": "Termin ważności", - "account_tokens_table_never_expires": "Bezterminowy", - "account_tokens_table_current_session": "Aktualna sesja przeglądarki", - "common_copy_to_clipboard": "Kopiuj do schowka", - "account_tokens_table_copied_to_clipboard": "Token został skopiowany", - "account_tokens_table_cannot_delete_or_edit": "Nie można edytować ani usunąć tokenu aktualnej sesji", - "account_tokens_table_create_token_button": "Utwórz token dostępowy", - "account_tokens_dialog_label": "Etykieta, np. Powiadomienia Radarr", - "account_tokens_dialog_button_update": "Zmień token", - "account_basics_tier_interval_monthly": "miesięcznie", - "account_basics_tier_interval_yearly": "rocznie", - "account_upgrade_dialog_interval_monthly": "Miesięcznie", - "account_upgrade_dialog_title": "Zmień plan konta", - "account_delete_dialog_description": "Konto, wraz ze wszystkimi związanymi z nim danymi przechowywanymi na serwerze, będzie nieodwracalnie usunięte. Po usunięciu Twoja nazwa użytkownika będzie niedostępna jeszcze przez 7 dni. Jeśli chcesz kontynuować, potwierdź wpisując swoje hasło w polu poniżej.", - "account_delete_dialog_billing_warning": "Usunięcie konta powoduje natychmiastowe anulowanie subskrypcji. Nie będziesz już mieć dostępu do strony z rachunkami.", - "account_upgrade_dialog_interval_yearly": "Rocznie", - "account_upgrade_dialog_interval_yearly_discount_save": "taniej o {{discount}}%", - "account_upgrade_dialog_interval_yearly_discount_save_up_to": "nawet {{discount}}% taniej", - "account_upgrade_dialog_button_cancel": "Anuluj", - "account_tokens_description": "Używaj tokenów do publikowania wiadomości i subskrybowania tematów przez API ntfy, żeby uniknąć konieczności podawania danych do logowania. Szczegóły znajdziesz w dokumentacji.", - "account_tokens_dialog_title_create": "Utwórz token dostępowy", - "account_tokens_table_last_origin_tooltip": "Z adresu IP {{ip}}, kliknij żeby sprawdzić", - "account_upgrade_dialog_tier_price_billed_yearly": "{{price}} płatne jednorazowo. Oszczędzasz {{save}}.", - "account_tokens_dialog_title_edit": "Edytuj token dostępowy", - "account_tokens_dialog_title_delete": "Usuń token dostępowy", - "account_tokens_dialog_button_create": "Utwórz token", - "nav_upgrade_banner_label": "Przejdź na ntfy Pro", - "nav_upgrade_banner_description": "Rezerwuj tematy, więcej powiadomień i maili oraz większe załączniki", - "alert_not_supported_context_description": "Powiadomienia działają tylko przez HTTPS. To jest ograniczenie Notifications API.", - "account_basics_tier_canceled_subscription": "Twoja subskrypcja została anulowana i konto zostanie ograniczone do wersji darmowej w dniu {{date}}.", - "account_basics_tier_manage_billing_button": "Zarządzaj rachunkami", - "account_usage_messages_title": "Wysłane wiadomości", - "account_usage_emails_title": "Wysłane maile", - "account_basics_tier_title": "Rodzaj konta", - "account_basics_tier_description": "Mocarność Twojego konta", - "account_basics_tier_admin": "Administrator", - "account_basics_tier_admin_suffix_with_tier": "(plan {{tier}})", - "account_basics_tier_admin_suffix_no_tier": "(brak planu)", - "account_basics_tier_basic": "Podstawowe", - "account_basics_tier_free": "Darmowe", - "account_basics_tier_upgrade_button": "Przejdź na Pro", - "account_basics_tier_change_button": "Zmień", - "account_basics_tier_paid_until": "Subskrypcja opłacona do {{date}} i będzie odnowiona automatycznie", - "account_basics_tier_payment_overdue": "Minął termin płatności. Zaktualizuj metodę płatności, w przeciwnym razie Twoje konto wkrótce zostanie ograniczone.", - "account_usage_reservations_title": "Zarezerwowane tematy", - "account_usage_reservations_none": "Brak zarezerwowanych tematów na tym koncie", - "account_usage_attachment_storage_title": "Miejsce na załączniki", - "account_usage_attachment_storage_description": "{{filesize}} na każdy plik, przechowywane przez {{expiry}}", - "account_usage_basis_ip_description": "Statystyki i limity dla tego konta bazują na Twoim adresie IP, więc mogą być współdzielone z innymi użytkownikami. Limity pokazane powyżej to wartości przybliżone bazujące na rzeczywistych limitach.", - "account_usage_cannot_create_portal_session": "Nie można otworzyć portalu z rachunkami", - "account_delete_title": "Usuń konto", - "account_delete_description": "Usuń swoje konto nieodwracalnie", - "account_delete_dialog_label": "Hasło", - "account_delete_dialog_button_cancel": "Anuluj", - "account_upgrade_dialog_button_redirect_signup": "Załóż konto", - "account_upgrade_dialog_button_pay_now": "Zapłać i aktywuj subskrypcję", - "account_tokens_dialog_button_cancel": "Anuluj", - "account_tokens_dialog_expires_label": "Token dostępowy wygasa po", - "account_tokens_dialog_expires_unchanged": "Pozostaw termin ważności bez zmian", - "account_upgrade_dialog_tier_features_reservations_one": "{{reservations}} rezerwacja tematu", - "account_upgrade_dialog_tier_features_reservations_few": "{{reservations}} rezerwacje tematów", - "account_upgrade_dialog_tier_features_reservations_many": "{{reservations}} rezerwacji tematów", - "account_upgrade_dialog_tier_features_emails_one": "{{emails}} mail dziennie", - "account_upgrade_dialog_tier_features_emails_few": "{{emails}} maile dziennie", - "account_upgrade_dialog_tier_features_emails_many": "{{emails}} maili dziennie", - "account_upgrade_dialog_tier_features_messages_one": "{{messages}} wiadomość dziennie", - "account_upgrade_dialog_tier_features_messages_few": "{{messages}} wiadomości dziennie", - "account_upgrade_dialog_tier_features_messages_many": "{{messages}} wiadomości dziennie" + "account_usage_limits_reset_daily": "Limity są resetowane codziennie o północy (UTC)" } diff --git a/web/public/static/langs/pt.json b/web/public/static/langs/pt.json index 57d5656..61338db 100644 --- a/web/public/static/langs/pt.json +++ b/web/public/static/langs/pt.json @@ -31,7 +31,7 @@ "notifications_attachment_copy_url_title": "Copiar URL do anexo para a área de transferência", "notifications_attachment_copy_url_button": "Copiar URL", "notifications_attachment_open_title": "Ir para {{url}}", - "notifications_attachment_link_expired": "a ligação de descarga expirou", + "notifications_attachment_link_expired": "a ligação de transferência expirou", "notifications_attachment_open_button": "Abrir anexo", "notifications_attachment_link_expires": "a ligação expira em {{date}}", "notifications_attachment_file_image": "ficheiro de imagem", @@ -144,7 +144,7 @@ "subscribe_dialog_login_description": "Esse tópico é protegido por palavra-passe. Por favor insira um nome de utilizador e palavra-passe para subscrever.", "subscribe_dialog_login_username_label": "Nome, por exemplo: \"filipe\"", "subscribe_dialog_login_password_label": "Palavra-passe", - "common_back": "Voltar", + "subscribe_dialog_login_button_back": "Voltar", "subscribe_dialog_login_button_login": "Autenticar", "subscribe_dialog_error_user_anonymous": "anónimo", "prefs_notifications_title": "Notificações", @@ -214,17 +214,5 @@ "login_link_signup": "Registar", "action_bar_reservation_add": "Reservar tópico", "action_bar_sign_up": "Registar", - "nav_button_account": "Conta", - "common_copy_to_clipboard": "Copiar", - "nav_upgrade_banner_label": "Atualizar para ntfy Pro", - "alert_not_supported_context_description": "Notificações são suportadas apenas sobre HTTPS. Essa é uma limitação da API de Notificações.", - "display_name_dialog_title": "Alterar nome mostrado", - "display_name_dialog_description": "Configura um nome alternativo ao tópico que é mostrado na lista de assinaturas. Isto ajuda a identificar tópicos com nomes complicados mais facilmente.", - "display_name_dialog_placeholder": "Nome exibido", - "reserve_dialog_checkbox_label": "Reservar tópico e configurar acesso", - "publish_dialog_call_label": "Chamada telefônica", - "publish_dialog_call_placeholder": "Número de telefone para ligar com a mensagem, ex: +12223334444, ou 'Sim'", - "publish_dialog_call_reset": "Remover chamada telefônica", - "publish_dialog_chip_call_label": "Chamada telefônica", - "subscribe_dialog_subscribe_button_generate_topic_name": "Gerar nome" + "nav_button_account": "Conta" } diff --git a/web/public/static/langs/pt_BR.json b/web/public/static/langs/pt_BR.json index acf5bca..79622be 100644 --- a/web/public/static/langs/pt_BR.json +++ b/web/public/static/langs/pt_BR.json @@ -93,7 +93,7 @@ "prefs_notifications_min_priority_low_and_higher": "Baixa prioridade e acima", "prefs_notifications_min_priority_default_and_higher": "Prioridade padrão e acima", "subscribe_dialog_login_password_label": "Senha", - "common_back": "Voltar", + "subscribe_dialog_login_button_back": "Voltar", "prefs_notifications_min_priority_high_and_higher": "Alta prioridade e acima", "prefs_notifications_min_priority_max_only": "Apenas prioridade máxima", "prefs_notifications_delete_after_title": "Apagar notificações", diff --git a/web/public/static/langs/ru.json b/web/public/static/langs/ru.json index 9633d97..c629e52 100644 --- a/web/public/static/langs/ru.json +++ b/web/public/static/langs/ru.json @@ -1,30 +1,30 @@ { - "publish_dialog_priority_min": "Минимальный приоритет", + "publish_dialog_priority_min": "Мин. приоритет", "action_bar_settings": "Настройки", "action_bar_send_test_notification": "Отправить тестовое уведомление", "action_bar_clear_notifications": "Удалить все уведомления", "action_bar_unsubscribe": "Отписаться", "message_bar_type_message": "Введите сообщение здесь", - "notifications_none_for_topic_description": "Чтобы отправить уведомление на данную тему, просто сделаете PUT или POST-запрос на URL-адрес этой темы.", - "notifications_none_for_any_description": "Чтобы отправить уведомление на тему, просто сделаете PUT или POST-запрос на её URL-адрес. Вот пример с использованием одной из ваших тем.", - "notifications_no_subscriptions_title": "Похоже, что у вас ещё нет подписок.", + "notifications_none_for_topic_description": "Чтобы отправить уведомление на данную тему, просто отправьте PUT или POST на URL-адрес этой темы.", + "notifications_none_for_any_description": "Чтобы отправить уведомления на тему, просто отправьте PUT или POST на URL-адрес темы. Вот пример используя одну из ваших тем.", + "notifications_no_subscriptions_title": "Похоже у вас ещё нет подписок.", "alert_grant_description": "Разрешите браузеру показывать уведомления.", - "notifications_no_subscriptions_description": "Нажмите на ссылку \"{{linktext}}\", чтобы создать или подписаться на тему. После этого Вы сможете отправлять сообщения используя PUT или POST-запросы и получать уведомления здесь.", + "notifications_no_subscriptions_description": "Нажмите \"{{linktext}}\" ссылку, чтобы создать или подписаться на тему. После этого вы сможете отправлять сообщения используя PUT или POST, и вы будете получать здесь уведомления.", "notifications_example": "Пример", - "notifications_more_details": "Для более подробной информации, посетите наш сайт или документацию.", - "notifications_loading": "Идет загрузка уведомлений …", + "notifications_more_details": "Дополнительную информацию найдёте на сайте или в документации.", + "notifications_loading": "Загружаются уведомления …", "publish_dialog_title_topic": "Опубликовать в {{topic}}", "publish_dialog_title_no_topic": "Опубликовать уведомление", - "publish_dialog_progress_uploading": "Идет загрузка …", + "publish_dialog_progress_uploading": "Загружается …", "publish_dialog_progress_uploading_detail": "Загружается {{loaded}}/{{total}} ({{percent}}%) …", "publish_dialog_message_published": "Уведомление опубликовано", - "publish_dialog_attachment_limits_file_and_quota_reached": "превышает максимальный размер файла {{fileSizeLimit}} и квоту, осталось {{remainingBytes}}", - "publish_dialog_attachment_limits_file_reached": "превышает максимальный размер файла {{fileSizeLimit}}", - "publish_dialog_attachment_limits_quota_reached": "превышает квоту, осталось {{remainingBytes}}", + "publish_dialog_attachment_limits_file_and_quota_reached": "превышает {{fileSizeLimit}} размер файла, {{remainingBytes}} осталось", + "publish_dialog_attachment_limits_file_reached": "превышает {{fileSizeLimit}} размер файла", + "publish_dialog_attachment_limits_quota_reached": "превышает квоту, {{remainingBytes}} осталось", "publish_dialog_priority_low": "Низкий приоритет", - "publish_dialog_priority_default": "Стандартный приоритет", + "publish_dialog_priority_default": "Приоритет по умолчанию", "publish_dialog_priority_high": "Высокий приоритет", - "publish_dialog_priority_max": "Максимальный приоритет", + "publish_dialog_priority_max": "Макс. приоритет", "publish_dialog_base_url_label": "URL-адрес сервиса", "publish_dialog_base_url_placeholder": "URL-адрес сервиса, например https://example.com", "publish_dialog_topic_label": "Название темы", @@ -32,14 +32,14 @@ "publish_dialog_title_label": "Заголовок", "publish_dialog_title_placeholder": "Заголовок уведомления, например Disk space alert", "publish_dialog_message_label": "Сообщение", - "publish_dialog_message_placeholder": "Введите сообщение здесь", + "publish_dialog_message_placeholder": "Текст сообщения", "publish_dialog_tags_label": "Тэги", - "publish_dialog_tags_placeholder": "Список тэгов, разделённый запятой, например: warning, srv1-backup", + "publish_dialog_tags_placeholder": "Список тэгов, разделённый запятой, например warning, srv1-backup", "publish_dialog_priority_label": "Приоритет", - "publish_dialog_click_label": "Ссылка при открытии", - "publish_dialog_click_placeholder": "URL-адрес, который откроется при нажатии на уведомление", - "publish_dialog_email_label": "Электронная почта", - "message_bar_error_publishing": "Ошибка публикации уведомления", + "publish_dialog_click_label": "Нажмите на URL-адрес", + "publish_dialog_click_placeholder": "URL-адрес который откроется когда будет нажато уведомление", + "publish_dialog_email_label": "Эл. почта", + "message_bar_error_publishing": "Ошибка отправки уведомления", "alert_not_supported_title": "Уведомления не поддерживаются", "alert_not_supported_description": "Уведомления не поддерживаются вашим браузером.", "notifications_copied_to_clipboard": "Скопировано в буфер обмена", @@ -66,30 +66,30 @@ "notifications_click_open_button": "Открыть ссылку", "subscribe_dialog_subscribe_title": "Подписаться на тему", "publish_dialog_button_cancel": "Отмена", - "subscribe_dialog_subscribe_description": "Темы могут быть не защищены паролем, поэтому укажите сложное имя. После подписки Вы сможете отправлять уведомления используя PUT/POST-запросы.", + "subscribe_dialog_subscribe_description": "Темы могут быть не защищены паролем, поэтому укажите сложное имя. После подписки вы можете размещать/отправлять уведомления.", "prefs_users_description": "Добавляйте/удаляйте пользователей для защищенных тем. Обратите внимание, что имя пользователя и пароль хранятся в локальном хранилище браузера.", - "error_boundary_description": "Это не должно было случиться. Нам очень жаль.
Если Вы можете уделить минуту своего времени, пожалуйста сообщите об этом на GitHub, или дайте нам знать через Discord или Matrix.", + "error_boundary_description": "Этого, очевидно, не должно происходить. Очень сожалею об этом.
Если у вас есть минутка, пожалуйста сообщить об этом на GitHub, или сообщите нам через Discord или Matrix.", "publish_dialog_email_placeholder": "Адрес для пересылки уведомления. Например, phil@example.com", "publish_dialog_attach_placeholder": "Прикрепите файл по URL. Например, https://f-droid.org/F-Droid.apk", "publish_dialog_filename_label": "Имя файла", "publish_dialog_delay_label": "Задержка", - "publish_dialog_delay_placeholder": "Задержка доставки. Например, {{unixTimestamp}}, {{relativeTime}}, или \"{{naturalLanguage}}\" (только по-английски)", - "publish_dialog_chip_click_label": "URL-адрес при нажатии", + "publish_dialog_delay_placeholder": "Задержка доставки. Например, {{unixTimestamp}}, {{relativeTime}}, or \"{{naturalLanguage}}\" (English only)", + "publish_dialog_chip_click_label": "Адрес", "publish_dialog_chip_email_label": "Переслать на электронную почту", "publish_dialog_chip_attach_url_label": "Прикрепить файл по URL", "publish_dialog_chip_attach_file_label": "Прикрепить локальный файл", - "publish_dialog_chip_delay_label": "Задержать доставку", + "publish_dialog_chip_delay_label": "Задержка отправки", "publish_dialog_chip_topic_label": "Изменить тему", - "publish_dialog_details_examples_description": "Примеры и подробное описание всех функций смотрите в документации.", + "publish_dialog_details_examples_description": "Примеры и подробное описание всех функций см. в e документации.", "publish_dialog_attach_label": "URL-адрес вложения", "publish_dialog_filename_placeholder": "Имя файла вложения", "publish_dialog_other_features": "Другие возможности:", "publish_dialog_button_cancel_sending": "Отменить отправку", "publish_dialog_button_send": "Отправить", "publish_dialog_checkbox_publish_another": "Опубликовать еще", - "publish_dialog_attached_file_title": "Прикреплённый файл:", + "publish_dialog_attached_file_title": "Прикрепленный файл:", "publish_dialog_attached_file_filename_placeholder": "Имя прикреплённого файла", - "emoji_picker_search_placeholder": "Поиск смайликов", + "emoji_picker_search_placeholder": "Поиск эмодзи", "subscribe_dialog_subscribe_topic_placeholder": "Название темы. Например, phil_alerts", "subscribe_dialog_subscribe_use_another_label": "Использовать другой сервер", "subscribe_dialog_subscribe_button_cancel": "Отмена", @@ -98,26 +98,26 @@ "subscribe_dialog_login_description": "Эта тема защищена паролем. Пожалуйста, введите имя пользователя и пароль, чтобы подписаться.", "subscribe_dialog_login_username_label": "Имя пользователя. Например, phil", "subscribe_dialog_login_password_label": "Пароль", - "common_back": "Назад", + "subscribe_dialog_login_button_back": "Назад", "subscribe_dialog_login_button_login": "Войти", "subscribe_dialog_error_user_not_authorized": "Пользователь {{username}} не авторизован", - "subscribe_dialog_error_user_anonymous": "анонимный пользователь", + "subscribe_dialog_error_user_anonymous": "аноним", "prefs_notifications_title": "Уведомления", "prefs_notifications_sound_title": "Звук уведомления", "prefs_notifications_sound_description_none": "Уведомления не воспроизводят никаких звуков при получении", "prefs_notifications_sound_no_sound": "Без звука", "prefs_notifications_min_priority_title": "Минимальный приоритет", - "prefs_notifications_min_priority_description_any": "Показывать все уведомления, независимо от приоритета", + "prefs_notifications_min_priority_description_any": "Показать все уведомления, независимо от приоритета", "prefs_notifications_min_priority_description_x_or_higher": "Показывать уведомления, если приоритет {{number}} ({{name}}) или выше", - "prefs_notifications_min_priority_description_max": "Показывать уведомления, если приоритет равен 5 (максимальный)", + "prefs_notifications_min_priority_description_max": "Показывать уведомления, если приоритет равен 5 (максимум)", "prefs_notifications_min_priority_any": "Любой приоритет", - "prefs_notifications_min_priority_low_and_higher": "Низкий приоритет и выше", + "prefs_notifications_min_priority_low_and_higher": "Низкий и высокий приоритет", "prefs_notifications_min_priority_max_only": "Только максимальный приоритет", "prefs_notifications_delete_after_title": "Удалить уведомления", "prefs_notifications_delete_after_never": "Никогда", "prefs_notifications_delete_after_three_hours": "Через три часа", "prefs_notifications_sound_description_some": "Уведомления воспроизводят звук {{sound}}", - "prefs_notifications_min_priority_default_and_higher": "Стандартный приоритет и выше", + "prefs_notifications_min_priority_default_and_higher": "Приоритет по умолчанию и высокий", "prefs_notifications_delete_after_one_day": "Через день", "prefs_notifications_delete_after_one_week": "Через неделю", "prefs_notifications_delete_after_one_month": "Через месяц", @@ -129,10 +129,10 @@ "prefs_users_title": "Управление пользователями", "prefs_users_add_button": "Добавить пользователя", "prefs_users_table_user_header": "Пользователь", - "prefs_users_table_base_url_header": "URL сервера", + "prefs_users_table_base_url_header": "URL службы", "prefs_users_dialog_title_add": "Добавить пользователя", "prefs_users_dialog_title_edit": "Редактировать пользователя", - "prefs_users_dialog_base_url_label": "URL-адрес сервера. Например, https://ntfy.sh", + "prefs_users_dialog_base_url_label": "URL-адрес службы. Например, https://ntfy.sh", "prefs_users_dialog_username_label": "Имя пользователя. Например, phil", "prefs_users_dialog_password_label": "Пароль", "common_cancel": "Отмена", @@ -140,217 +140,19 @@ "common_save": "Сохранить", "prefs_appearance_title": "Внешний вид", "prefs_appearance_language_title": "Язык", - "priority_min": "минимальный", + "priority_min": "минимум", "priority_low": "низкий", - "priority_default": "стандартный", + "priority_default": "по умолчанию", "priority_high": "высокий", "priority_max": "максимальный", - "error_boundary_title": "О нет, ntfy сломался", - "error_boundary_button_copy_stack_trace": "Скопировать трассировку стека", + "error_boundary_title": "О нет, Ntfy сломался", + "error_boundary_button_copy_stack_trace": "Копирование трассировки стека", "error_boundary_stack_trace": "Трассировка стека", - "error_boundary_gathering_info": "Идет сбор дополнительной информации …", - "publish_dialog_drop_file_here": "Перетащите файл сюда", + "error_boundary_gathering_info": "Соберите больше информации …", + "publish_dialog_drop_file_here": "Перетащите файл юда", "prefs_notifications_min_priority_high_and_higher": "Высокий приоритет и выше", "action_bar_toggle_action_menu": "Открыть/закрыть меню", "action_bar_show_menu": "Показать меню", - "action_bar_logo_alt": "Логотип ntfy", - "emoji_picker_search_clear": "Сбросить поиск", - "account_upgrade_dialog_cancel_warning": "Это действие отменит Вашу подписку и переведет Вашую учетную запись на бесплатное обслуживание {{date}}. При наступлении этой даты, все резервирования и сообщения в кэше будут удалены.", - "account_tokens_table_create_token_button": "Создать токен доступа", - "account_tokens_table_last_origin_tooltip": "с IP-адреса {{ip}}, нажмите для подробностей", - "account_tokens_dialog_title_edit": "Изменить токен доступа", - "account_delete_dialog_button_cancel": "Отмена", - "account_delete_dialog_billing_warning": "Удаление учетной записи также отменяет все платные подписки. У Вас не будет доступа к порталу оплаты.", - "account_delete_dialog_description": "Это действие безвозвратно удалит Вашу учетную запись, включая все Ваши данные хранящиеся на сервере. После удаления, Ваше имя пользователя не будет доступно для регистрации в течении 7 дней. Если Вы действительно хотите продолжить, пожалуйста введите Ваш пароль ниже.", - "account_delete_dialog_label": "Пароль", - "reservation_delete_dialog_action_keep_description": "Сообщения и вложения которые находятся в кэше сервера станут доступны всем, кто знает имя темы.", - "prefs_reservations_table": "Список зарезервированных тем", - "prefs_reservations_table_access_header": "Доступ", - "prefs_reservations_table_everyone_write_only": "Я могу публиковать и подписываться, все остальные могут публиковать", - "prefs_reservations_dialog_description": "Резервирование дает Вам возможность управлять темой и настраивать правила доступа к ней для пользователей.", - "reservation_delete_dialog_action_delete_title": "Удалить сообщения в кэше и вложения", - "reservation_delete_dialog_action_delete_description": "Сообщения в кэше и вложения будут безвозвратно удалены. Это действие невозможно отменить.", - "prefs_reservations_table_not_subscribed": "Не подписан", - "prefs_reservations_table_everyone_deny_all": "Только я могу публиковать и подписываться", - "prefs_reservations_table_everyone_read_write": "Все могут публиковать и подписываться", - "prefs_reservations_table_click_to_subscribe": "Нажмите чтобы подписаться", - "prefs_reservations_dialog_title_add": "Зарезервировать тему", - "prefs_reservations_dialog_title_delete": "Удалить резервирование", - "prefs_reservations_dialog_title_edit": "Изменение резервированной темы", - "prefs_reservations_table_topic_header": "Тема", - "prefs_users_description_no_sync": "Пользователи и пароли не синхронизируются с Вашей учетной записью.", - "prefs_users_delete_button": "Удалить пользователя", - "prefs_users_table_cannot_delete_or_edit": "Невозможно удалить или редактировать залогиненного пользователя", - "account_upgrade_dialog_reservations_warning_one": "Выбранная подписка разрешает меньше зарезервированных тем, чем есть у Вас на данный момент. Перед сменой подписки, пожалуйста удалите хотя бы одну зарезервированную тему. Вы можете это сделать в Настройках.", - "account_upgrade_dialog_proration_info": "Пересчёт оплаты: при расширении подписки, разница в цене от текущей спишется сразу. При упрощении подписки, неиспользованные средства пойдут в оплату баланса по следующим счетам.", - "account_upgrade_dialog_tier_features_attachment_file_size": "{{filesize}} на файл", - "account_tokens_table_never_expires": "Никогда", - "account_tokens_table_copied_to_clipboard": "Токен доступа скопирован", - "account_tokens_table_cannot_delete_or_edit": "Невозможно изменить или удалить токен текущего сеанса", - "account_tokens_delete_dialog_description": "Перед удалением токена доступа, убедитесь что он не используется приложениями и скриптами. Это действие невозможно отменить.", - "error_boundary_unsupported_indexeddb_title": "Работа в приватном режиме не поддерживается", - "account_tokens_dialog_button_create": "Создать токен", - "account_tokens_delete_dialog_submit_button": "Безвозвратно удалить токен", - "account_upgrade_dialog_reservations_warning_other": "Выбранная подписка разрешает меньше зарезервированных тем, чем есть у Вас на данный момент. Перед сменой подписки, пожалуйста удалите хотя бы {{count}} зарезервированных тем. Вы можете это сделать в Настройках.", - "account_upgrade_dialog_tier_features_messages_other": "{{messages}} сообщений в день", - "account_upgrade_dialog_tier_features_attachment_total_size": "{{totalsize}} суммарный объем", - "account_upgrade_dialog_tier_selected_label": "Выбранная", - "account_tokens_table_current_session": "Текущий сеанс браузера", - "account_tokens_dialog_button_update": "Изменить токен", - "account_tokens_dialog_expires_label": "Токен доступа истекает", - "account_tokens_dialog_expires_x_hours": "Токен истекает через {{hours}} часов", - "account_tokens_dialog_expires_never": "Токен никогда не истекает", - "prefs_notifications_sound_play": "Воспроизводить выбранный звук", - "account_upgrade_dialog_tier_features_reservations_other": "{{reservations}} зарезервированных тем", - "account_upgrade_dialog_tier_features_emails_other": "{{emails}} эл. сообщений в день", - "account_basics_tier_free": "Бесплатный", - "account_tokens_dialog_title_create": "Создать токен доступа", - "account_tokens_dialog_title_delete": "Удалить токен доступа", - "common_copy_to_clipboard": "Скопировать в буфер обмена", - "account_tokens_dialog_button_cancel": "Отмена", - "account_tokens_dialog_expires_unchanged": "Оставить срок истечения без изменений", - "account_tokens_dialog_expires_x_days": "Токен истекает через {{days}} дней", - "account_tokens_delete_dialog_title": "Удалить токен доступа", - "prefs_users_table": "Список пользоваетелй", - "account_upgrade_dialog_tier_current_label": "Текущая", - "account_upgrade_dialog_button_cancel": "Отмена", - "prefs_users_edit_button": "Редактировать пользователя", - "account_basics_tier_upgrade_button": "Подписаться на Pro", - "account_basics_tier_paid_until": "Подписка оплачена до {{date}} и будет продляться автоматически", - "account_basics_tier_change_button": "Изменить", - "account_delete_dialog_button_submit": "Безвозвратно удалить учетную запись", - "account_upgrade_dialog_title": "Изменить уровень учетной записи", - "account_usage_basis_ip_description": "Статистика и ограничения на использование учитываются по IP-адресу, поэтому они могут совмещаться с другими пользователями. Уровни, указанные выше, примерно соответствуют текущим ограничениям.", - "publish_dialog_topic_reset": "Сбросить тему", - "account_basics_tier_admin_suffix_no_tier": "(без подписки)", - "prefs_reservations_dialog_topic_label": "Тема", - "signup_form_username": "Имя пользователя", - "signup_form_password": "Пароль", - "signup_form_confirm_password": "Подтвердите пароль", - "signup_form_button_submit": "Зарегистрироваться", - "signup_form_toggle_password_visibility": "Показать/скрыть пароль", - "signup_disabled": "Регистрация недоступна", - "signup_error_username_taken": "Имя пользователя {{username}} уже занято", - "signup_title": "Создать учетную запись ntfy", - "signup_already_have_account": "Уже есть учетная запись? Войдите!", - "signup_error_creation_limit_reached": "Лимит на создание учетных записей исчерпан", - "login_form_button_submit": "Вход", - "login_link_signup": "Регистрация", - "login_disabled": "Вход недоступен", - "action_bar_reservation_add": "Зарезервировать тему", - "action_bar_reservation_edit": "Изменить резервирование", - "action_bar_reservation_delete": "Удалить резервирование", - "action_bar_profile_title": "Профиль", - "action_bar_profile_settings": "Настройки", - "action_bar_profile_logout": "Выход", - "action_bar_sign_in": "Вход", - "action_bar_sign_up": "Регистрация", - "action_bar_change_display_name": "Изменить псевдоним", - "message_bar_publish": "Опубликовать сообщение", - "nav_button_muted": "Уведомления заглушены", - "nav_button_connecting": "установка соединения", - "action_bar_account": "Учетная запись", - "login_title": "Вход в Вашу учетную запись ntfy", - "action_bar_reservation_limit_reached": "Лимит исчерпан", - "action_bar_toggle_mute": "Заглушить/разрешить уведомления", - "nav_button_account": "Учетная запись", - "nav_upgrade_banner_label": "Подпишитесь на ntfy Pro", - "message_bar_show_dialog": "Открыть диалог публикации", - "notifications_list": "Список уведомлений", - "notifications_list_item": "Уведомление", - "notifications_mark_read": "Пометить как прочтенное", - "notifications_priority_x": "Приоритет {{priority}}", - "notifications_attachment_image": "Приложенное изображение", - "notifications_attachment_file_audio": "звуковой файл", - "notifications_attachment_file_video": "видео файл", - "notifications_attachment_file_image": "графический файл", - "notifications_attachment_file_app": "исполняемый файл Android", - "notifications_attachment_file_document": "другой тип файла", - "notifications_actions_not_supported": "Действие не поддерживается в веб-приложении", - "display_name_dialog_title": "Изменить псевдоним", - "display_name_dialog_description": "Создайте псевдоним для темы, который будет отображаться в списке Ваших подписок. Это помогает легче находить темы со сложными именами.", - "reserve_dialog_checkbox_label": "Зарезервировать тему и настроить доступ", - "publish_dialog_emoji_picker_show": "Выбрать смайлик", - "publish_dialog_click_reset": "Удалить ссылку", - "publish_dialog_email_reset": "Удалить адрес для пересылки", - "publish_dialog_attach_reset": "Удалить URL-адрес вложения", - "publish_dialog_delay_reset": "Удалить задержку доставки", - "publish_dialog_attached_file_remove": "Удалить прикреплённый файл", - "subscribe_dialog_subscribe_base_url_label": "URL-адрес сервера", - "subscribe_dialog_subscribe_button_generate_topic_name": "Сгенерировать случайное имя", - "subscribe_dialog_error_topic_already_reserved": "Тема уже зарезервирована", - "account_basics_title": "Учетная запись", - "account_basics_username_title": "Имя пользователя", - "account_basics_username_admin_tooltip": "Вы Администратор", - "account_basics_password_title": "Пароль", - "account_basics_username_description": "Это Вы! :)", - "account_basics_password_description": "Смена пароля учетной записи", - "account_basics_password_dialog_title": "Смена пароля", - "account_basics_password_dialog_current_password_label": "Текущий пароль", - "account_basics_password_dialog_current_password_incorrect": "Введен неверный пароль", - "account_usage_title": "Использование", - "account_usage_of_limit": "из {{limit}}", - "account_usage_unlimited": "Неограниченно", - "account_usage_limits_reset_daily": "Ограничения сбрасываются ежедневно в полночь (UTC)", - "account_basics_tier_description": "Уровень Вашей учетной записи", - "account_basics_tier_admin": "Администратор", - "account_basics_tier_admin_suffix_with_tier": "(с {{tier}} подпиской)", - "account_basics_tier_payment_overdue": "У Вас задолженность по оплате. Пожалуйста проверьте метод оплаты, иначе Вы скоро потеряете преимущества Вашей подписки.", - "account_basics_tier_canceled_subscription": "Ваша подписка была отменена; учетная запись перейдет на бесплатное обслуживание {{date}}.", - "account_basics_tier_manage_billing_button": "Управление оплатой", - "account_usage_messages_title": "Опубликованные сообщения", - "account_usage_emails_title": "Отправленные электронные сообщения", - "account_usage_reservations_title": "Зарезервированные темы", - "account_usage_reservations_none": "Нет зарезервированных тем", - "account_usage_attachment_storage_title": "Хранение вложений", - "account_usage_attachment_storage_description": "{{filesize}} за файл, удаляются спустя {{expiry}}", - "account_usage_cannot_create_portal_session": "Невозможно открыть портал оплаты", - "account_delete_title": "Удалить учетную запись", - "account_delete_description": "Безвозвратно удалить Вашу учетную запись", - "account_upgrade_dialog_button_redirect_signup": "Зарегистрироваться", - "account_upgrade_dialog_button_pay_now": "Оплатить и подписаться", - "account_upgrade_dialog_button_cancel_subscription": "Отменить подписку", - "account_upgrade_dialog_button_update_subscription": "Изменить подписку", - "account_tokens_title": "Токены доступа", - "account_tokens_description": "Используйте токены доступа для публикации и подписки через ntfy API чтобы не пересылать данные Вашей учетной записи. Смотрите документацию чтобы узнать больше.", - "account_tokens_table_token_header": "Токен", - "account_tokens_table_label_header": "Название", - "account_tokens_table_last_access_header": "Последний доступ", - "account_tokens_table_expires_header": "Истекает", - "account_tokens_dialog_label": "Название, например Radarr notifications", - "prefs_reservations_title": "Зарезервированные темы", - "prefs_reservations_description": "Здесь Вы можете резервировать темы для личного пользования. Резервирование дает Вам возможность управлять темой и настраивать правила доступа к ней для пользователей.", - "prefs_reservations_limit_reached": "Вы исчерпали Ваш лимит на количество зарезервированных тем.", - "prefs_reservations_add_button": "Добавить тему", - "prefs_reservations_edit_button": "Настройка доступа", - "prefs_reservations_delete_button": "Сбросить правила доступа", - "prefs_reservations_table_everyone_read_only": "Я могу публиковать и подписываться, все остальные могут подписываться", - "prefs_reservations_dialog_access_label": "Доступ", - "reservation_delete_dialog_description": "Удаление резервирования дает возможность зарезервировать эту тему другим. Вы можете оставить или удалить существующие сообщения и вложения.", - "reservation_delete_dialog_action_keep_title": "Сохранить сообщения в кэше и вложения", - "reservation_delete_dialog_submit_button": "Удалить резервирование", - "account_basics_tier_basic": "Базовый", - "nav_upgrade_banner_description": "Зарезервированные темы, больше сообщений и электронных писем, а также вложения большего размера", - "alert_not_supported_context_description": "Уведомления поддерживаются только по протоколу HTTPS. Это ограничение Notifications API.", - "notifications_delete": "Удалить", - "notifications_new_indicator": "Новое уведомление", - "notifications_actions_http_request_title": "Сделать HTTP {{method}}-запрос на {{url}}", - "display_name_dialog_placeholder": "Псевдоним", - "account_basics_password_dialog_new_password_label": "Новый пароль", - "account_basics_password_dialog_confirm_password_label": "Подтвердите пароль", - "account_basics_password_dialog_button_submit": "Сменить пароль", - "account_basics_tier_title": "Тип учетной записи", - "error_boundary_unsupported_indexeddb_description": "Веб-приложение ntfy использует IndexedDB, который не поддерживается Вашим браузером в приватном режиме.

Хотя это и не лучший вариант, использовать веб-приложение ntfy в приватном режиме не имеет особого смысла, так как все данные храняться в локальном хранилище браузера. Вы можете узнать больше в этом отчете на GitHub или связавшись с нами через Discord или Matrix.", - "account_basics_tier_interval_monthly": "ежемесячно", - "account_basics_tier_interval_yearly": "ежегодно", - "account_upgrade_dialog_interval_yearly": "Ежегодно", - "account_upgrade_dialog_interval_yearly_discount_save": "скидка {{discount}}%", - "account_upgrade_dialog_interval_monthly": "Ежемесячно", - "account_upgrade_dialog_interval_yearly_discount_save_up_to": "скидка до {{discount}}%", - "account_upgrade_dialog_tier_features_no_reservations": "Нет зарезервированных тем", - "account_upgrade_dialog_tier_price_per_month": "в месяц", - "account_upgrade_dialog_tier_price_billed_monthly": "{{price}} в год. Оплата помесячно.", - "account_upgrade_dialog_tier_price_billed_yearly": "{{price}} ежегодно. Сэкономьте {{save}}.", - "account_upgrade_dialog_billing_contact_email": "По вопросам оплаты, пожалуйста свяжитесь с нами.", - "account_upgrade_dialog_billing_contact_website": "По вопросам оплаты, пожалуйста обратитесь к нашему сайту." + "action_bar_logo_alt": "ntfy лого", + "emoji_picker_search_clear": "Очистить поиск" } diff --git a/web/public/static/langs/sv.json b/web/public/static/langs/sv.json index bc4a540..12939f7 100644 --- a/web/public/static/langs/sv.json +++ b/web/public/static/langs/sv.json @@ -14,7 +14,7 @@ "alert_grant_title": "Notiser är avstängda", "alert_grant_button": "Bevilja nu", "alert_not_supported_title": "Notiser stöds inte", - "notifications_list": "Notifieringslista", + "notifications_list": "Notis-lista", "notifications_list_item": "Notis", "notifications_delete": "Radera", "notifications_copied_to_clipboard": "Kopierat till urklipp", @@ -47,338 +47,5 @@ "notifications_actions_open_url_title": "Gå till {{url}}", "notifications_none_for_any_title": "Du har inte fått några notiser.", "notifications_example": "Exempel", - "notifications_loading": "Laddar notiser …", - "signup_title": "Skapa ett nytt konto", - "signup_form_confirm_password": "Bekräfta lösenord", - "signup_form_button_submit": "Skapa konto", - "login_title": "Logga in på ditt konto", - "login_form_button_submit": "Logga in", - "login_link_signup": "Registrera", - "login_disabled": "Inloggning är inaktiverat", - "action_bar_account": "Konto", - "action_bar_change_display_name": "Ändra visningsnamn", - "action_bar_reservation_add": "Reservera ämne", - "action_bar_reservation_edit": "Ändra reservation", - "action_bar_reservation_delete": "Ta bort reservation", - "action_bar_reservation_limit_reached": "Gräns nådd", - "action_bar_profile_title": "Profil", - "action_bar_profile_settings": "Inställningar", - "action_bar_profile_logout": "Logga ut", - "action_bar_sign_in": "Logga in", - "action_bar_sign_up": "Registrera", - "nav_button_account": "Konto", - "nav_upgrade_banner_label": "Uppgradera till Pro", - "common_add": "Lägg till", - "signup_form_password": "Lösenord", - "signup_form_toggle_password_visibility": "Visa/dölj lösenord", - "common_cancel": "Avbryt", - "common_save": "Spara", - "signup_form_username": "Användarnamn", - "signup_already_have_account": "Har du redan ett konto? Logga in!", - "signup_disabled": "Registrering är inaktiverad", - "signup_error_username_taken": "Användarnamn [[username]] används redan", - "notifications_attachment_file_document": "annat dokument", - "notifications_attachment_file_app": "Android app fil", - "notifications_click_copy_url_title": "Kopiera länk till urklipp", - "notifications_none_for_topic_title": "Du har inte fått några notiser för detta ämnet ännu.", - "notifications_none_for_topic_description": "För att kunna skicka notiser till detta ämnet, använd PUT eller POST till ämnets URL.", - "notifications_actions_http_request_title": "Skicka HTTP {{method}} till {{url}}", - "publish_dialog_progress_uploading": "Laddar upp …", - "nav_upgrade_banner_description": "Reservera ämnen, fler meddelanden och e-postmeddelanden och större bilagor", - "publish_dialog_attachment_limits_file_and_quota_reached": "överskrider {{fileSizeLimit}} filgräns och kvot, {{remainingBytes}} återstående", - "publish_dialog_attachment_limits_file_reached": "överskrider {{fileSizeLimit}} filgräns", - "publish_dialog_attachment_limits_quota_reached": "överskrider kvoten, {{remainingBytes}} återstår", - "publish_dialog_message_placeholder": "Skriv ett meddelande här", - "publish_dialog_checkbox_publish_another": "Publicera en till", - "subscribe_dialog_error_user_anonymous": "anonym", - "account_basics_password_dialog_confirm_password_label": "Bekräfta lösenord", - "publish_dialog_email_placeholder": "Adress att vidarebefordra meddelandet till, t.ex. phil@example.com", - "publish_dialog_details_examples_description": "Exempel och en detaljerad beskrivning av alla sändningsfunktioner finns i dokumentationen .", - "publish_dialog_button_send": "Skicka", - "common_back": "Tillbaka", - "account_basics_tier_free": "Gratis", - "account_upgrade_dialog_tier_features_reservations_one": "{{reservations}} reserverat ämne", - "account_delete_title": "Ta bort konto", - "account_upgrade_dialog_tier_features_messages_other": "{{messages}} dagliga meddelanden", - "account_upgrade_dialog_tier_features_emails_one": "{{emails}} dagligt e-postmeddelande", - "account_upgrade_dialog_button_cancel": "Avbryt", - "common_copy_to_clipboard": "Kopiera till urklipp", - "account_tokens_table_copied_to_clipboard": "Åtkomsttoken kopierat", - "account_tokens_description": "Använd åtkomsttoken när du publicerar och prenumererar via ntfy API, så att du inte behöver skicka dina kontouppgifter. Läs mer i dokumentationen.", - "account_tokens_table_create_token_button": "Skapa åtkomsttoken", - "prefs_users_description_no_sync": "Användare och lösenord synkroniseras inte till ditt konto.", - "error_boundary_unsupported_indexeddb_description": "ntfy-webbappen behöver IndexedDB för att fungera och din webbläsare har inte stöd för IndexedDB i privat surfläge.

Detta är beklagligt, men det är inte heller särskilt meningsfullt att använda ntfy-webbappen i privat surfläge, eftersom allt lagras i webbläsarens lagringsutrymme. Du kan läsa mer om det i detta GitHub-ärende, eller prata med oss på Discord eller Matrix.", - "account_basics_tier_interval_monthly": "månadsvis", - "account_basics_tier_interval_yearly": "årligen", - "account_basics_tier_canceled_subscription": "Din prenumeration avbröts och kommer att nedgraderas till ett gratis konto den {{date}}.", - "account_basics_tier_manage_billing_button": "Hantera fakturering", - "account_usage_messages_title": "Publicerade meddelande", - "account_usage_emails_title": "Skickade e-postmeddelanden", - "account_usage_reservations_title": "Reserverade ämnen", - "account_usage_reservations_none": "Inga reserverade ämnen för det här kontot", - "account_usage_attachment_storage_title": "Lagring av bilagor", - "account_usage_attachment_storage_description": "{{filesize}} per fil, raderas efter {{expiry}}", - "account_delete_description": "Ta bort ditt konto permanent", - "account_delete_dialog_description": "Detta kommer att radera ditt konto permanent, inklusive all data som lagras på servern. Efter raderingen kommer ditt användarnamn att vara otillgängligt i 7 dagar. Om du verkligen vill fortsätta, bekräfta med ditt lösenord i rutan nedan.", - "account_delete_dialog_label": "Lösenord", - "account_delete_dialog_button_cancel": "Avbryt", - "account_delete_dialog_button_submit": "Ta bort kontot permanent", - "account_delete_dialog_billing_warning": "Om du raderar ditt konto annulleras också din faktureringsprenumeration omedelbart. Du kommer inte längre att ha tillgång till instrumentpanelen för fakturering.", - "account_upgrade_dialog_title": "Ändra kontonivå", - "account_upgrade_dialog_interval_monthly": "Månadsvis", - "account_upgrade_dialog_interval_yearly": "Årligen", - "account_upgrade_dialog_interval_yearly_discount_save": "spara {{discount}}%", - "account_upgrade_dialog_interval_yearly_discount_save_up_to": "spara upp till {{discount}}%", - "account_upgrade_dialog_cancel_warning": "Detta kommer att säga upp din prenumeration och nedgradera ditt konto på {{date}}. På det datumet kommer ämnesreservationer och meddelanden som ligger i cacheminnet på servern att raderas.", - "account_upgrade_dialog_proration_info": "Deklaration: När du uppgraderar mellan betalda planer kommer prisskillnaden att debiteras omedelbart. Vid nedgradering till en lägre nivå kommer saldot att användas för att betala för framtida faktureringsperioder.", - "account_upgrade_dialog_reservations_warning_one": "Den valda nivån tillåter färre reserverade ämnen än din nuvarande nivå. Innan du ändrar nivå, bör du ta bort minst en reservation. Du kan ta bort reservationer i Inställningar.", - "account_upgrade_dialog_reservations_warning_other": "Den valda nivån tillåter färre reserverade ämnen än din nuvarande nivå. Innan du ändrar nivå, ta bort minst {{count}} reservationer. Du kan ta bort reservationer i Inställningar.", - "account_upgrade_dialog_tier_features_no_reservations": "Inga reserverade ämnen", - "account_upgrade_dialog_tier_features_attachment_file_size": "{{filesize}} per fil", - "account_upgrade_dialog_tier_features_attachment_total_size": "{{totalsize}} total lagring", - "account_upgrade_dialog_tier_price_per_month": "månad", - "account_upgrade_dialog_tier_selected_label": "Vald", - "account_tokens_table_token_header": "Token", - "account_tokens_dialog_title_create": "Skapa åtkomsttoken", - "account_tokens_dialog_title_delete": "Ta bort åtkomsttoken", - "account_tokens_dialog_label": "Etikett, t.ex. Radarr-meddelanden", - "account_tokens_dialog_title_edit": "Redigera åtkomsttoken", - "account_tokens_dialog_button_create": "Skapa token", - "account_tokens_dialog_button_update": "Uppdatera token", - "account_tokens_delete_dialog_submit_button": "Ta bort token permanent", - "prefs_notifications_delete_after_one_day": "Efter en dag", - "reservation_delete_dialog_action_delete_description": "Cachade meddelanden och bilagor raderas permanent. Denna åtgärd kan inte ångras.", - "error_boundary_gathering_info": "Samla mer information …", - "error_boundary_unsupported_indexeddb_title": "Privat surfning stöds inte", - "reservation_delete_dialog_submit_button": "Ta bort reservationen", - "priority_low": "låg", - "error_boundary_title": "Åh nej, ntfy kraschade", - "error_boundary_description": "Detta får naturligtvis inte ske. Vi beklagar verkligen detta.
Om du har tid, vänligen rapportera detta på GitHub, eller meddela oss via Discord eller Matrix.", - "notifications_no_subscriptions_title": "Det ser ut som om du inte har några prenumerationer ännu.", - "notifications_more_details": "Mer information finns på webbplatsen eller i dokumentationen .", - "publish_dialog_title_topic": "Publicera till {{topic}}", - "publish_dialog_message_published": "Meddelande publicerat", - "publish_dialog_emoji_picker_show": "Välj emoji", - "publish_dialog_base_url_placeholder": "Service-URL, t.ex. https://example.com", - "publish_dialog_topic_label": "Ämnesnamn", - "publish_dialog_topic_placeholder": "Ämnesnamn, t.ex. phils_alerts", - "publish_dialog_topic_reset": "Återställ ämne", - "publish_dialog_title_label": "Titel", - "publish_dialog_title_placeholder": "Meddelandets rubrik, t.ex. Varning för diskutrymme", - "publish_dialog_tags_label": "Taggar", - "publish_dialog_message_label": "Meddelande", - "publish_dialog_tags_placeholder": "Kommaseparerad lista med taggar, t.ex. warning, srv1-backup", - "publish_dialog_priority_label": "Prioritet", - "publish_dialog_click_label": "Klicka på URL", - "publish_dialog_click_placeholder": "URL som öppnas när man klickar på anmälan", - "publish_dialog_click_reset": "Ta bort klickbar URL", - "publish_dialog_email_reset": "Ta bort vidarebefordran av e-post", - "publish_dialog_attach_label": "URL för bifogade filer", - "publish_dialog_attach_placeholder": "Bifoga fil via URL, t.ex. https://f-droid.org/F-Droid.apk", - "publish_dialog_filename_label": "Filnamn", - "publish_dialog_delay_label": "Fördröjning", - "publish_dialog_filename_placeholder": "Filnamn för bifogad fil", - "publish_dialog_delay_placeholder": "Fördröj leverans, t.ex. {{unixTimestamp}}, {{relativeTime}} eller \"{{naturalLanguage}}\" (endast engelska)", - "publish_dialog_delay_reset": "Ta bort försenad leverans", - "publish_dialog_other_features": "Andra funktioner:", - "publish_dialog_chip_click_label": "Klicka på URL", - "publish_dialog_attached_file_title": "Bifogad fil:", - "publish_dialog_attached_file_filename_placeholder": "Filnamn för bifogad fil", - "emoji_picker_search_placeholder": "Sök emoji", - "subscribe_dialog_subscribe_button_cancel": "Avbryt", - "prefs_notifications_sound_description_some": "Meddelanden spelar upp ljudet {{sound}} när de anländer", - "prefs_notifications_sound_no_sound": "Inget ljud", - "prefs_notifications_min_priority_any": "Alla prioriteringar", - "prefs_notifications_min_priority_low_and_higher": "Låg prioritet och högre", - "prefs_notifications_delete_after_three_hours": "Efter tre timmar", - "prefs_notifications_delete_after_never": "Aldrig", - "prefs_users_table": "Användartabell", - "prefs_users_add_button": "Lägg till användare", - "prefs_users_edit_button": "Redigera användare", - "prefs_users_dialog_title_add": "Lägg till användare", - "prefs_users_dialog_title_edit": "Redigera användare", - "prefs_users_dialog_base_url_label": "Tjänstens URL, t.ex. https://ntfy.sh", - "prefs_users_dialog_password_label": "Lösenord", - "prefs_appearance_title": "Utseende", - "prefs_appearance_language_title": "Språk", - "priority_min": "min", - "priority_default": "standard", - "priority_high": "hög", - "priority_max": "max", - "error_boundary_button_copy_stack_trace": "Kopiera stackspårning", - "error_boundary_stack_trace": "Stackspårning", - "account_upgrade_dialog_tier_features_reservations_other": "{{reservations}} reserverade ämnen", - "account_upgrade_dialog_tier_features_messages_one": "{{messages}} dagligt meddelande", - "account_upgrade_dialog_tier_features_emails_other": "{{emails}} dagliga e-postmeddelanden", - "account_upgrade_dialog_tier_price_billed_monthly": "{{price}} per år. Faktureras månadsvis.", - "account_upgrade_dialog_tier_price_billed_yearly": "{{price}} faktureras årligen. Spara {{save}}.", - "account_upgrade_dialog_tier_current_label": "Aktuell", - "account_upgrade_dialog_billing_contact_email": "För faktureringsfrågor, vänligen kontakta oss direkt.", - "account_upgrade_dialog_billing_contact_website": "För frågor om fakturering hänvisar vi till vår webbplats.", - "account_upgrade_dialog_button_redirect_signup": "Registrera dig nu", - "account_upgrade_dialog_button_pay_now": "Betala nu och prenumerera", - "account_upgrade_dialog_button_cancel_subscription": "Avbryt prenumeration", - "account_upgrade_dialog_button_update_subscription": "Uppdatera prenumeration", - "account_tokens_table_label_header": "Etikett", - "account_tokens_table_last_access_header": "Sista åtkomst", - "account_tokens_table_expires_header": "Upphör", - "account_tokens_table_never_expires": "Upphör aldrig", - "account_tokens_table_current_session": "Nuvarande webbläsarsession", - "account_tokens_table_cannot_delete_or_edit": "Det går inte att redigera eller ta bort aktuell sessionstoken", - "account_tokens_table_last_origin_tooltip": "Från IP-adress {{ip}}, klicka för att söka upp", - "account_tokens_dialog_button_cancel": "Avbryt", - "account_tokens_dialog_expires_label": "Åtkomsttoken löper ut om", - "account_tokens_dialog_expires_unchanged": "Lämna utgångsdatumet oförändrat", - "account_tokens_dialog_expires_x_hours": "Token går ut om {{hours}} timmar", - "account_tokens_dialog_expires_x_days": "Token löper ut om {{days}} dagar", - "account_tokens_dialog_expires_never": "Token upphör aldrig att gälla", - "account_tokens_delete_dialog_title": "Ta bort åtkomsttoken", - "account_tokens_delete_dialog_description": "Innan du tar bort en åtkomsttoken bör du se till att inga program eller skript använder den aktivt. Den här åtgärden kan inte ångras.", - "prefs_notifications_title": "Notifieringar", - "prefs_notifications_sound_title": "Ljud för meddelanden", - "prefs_notifications_sound_description_none": "Meddelanden spelar inte upp något ljud när de kommer", - "prefs_notifications_sound_play": "Spela upp valt ljud", - "prefs_notifications_min_priority_title": "Lägsta prioritet", - "prefs_notifications_min_priority_description_any": "Visa alla meddelanden, oavsett prioritet", - "prefs_notifications_min_priority_description_x_or_higher": "Visa meddelanden om prioritet är {{number}} ({{name}}) eller högre", - "prefs_notifications_min_priority_description_max": "Visa notifieringar om prioritet är 5 (max)", - "prefs_notifications_min_priority_default_and_higher": "Standardprioritet och högre", - "prefs_notifications_min_priority_high_and_higher": "Hög prioritet och högre", - "prefs_notifications_min_priority_max_only": "Bara högsta prioritet", - "prefs_notifications_delete_after_title": "Radera meddelanden", - "prefs_notifications_delete_after_one_week": "Efter en vecka", - "prefs_notifications_delete_after_one_month": "Efter en månad", - "prefs_notifications_delete_after_never_description": "Meddelanden raderas aldrig automatiskt", - "prefs_notifications_delete_after_three_hours_description": "Meddelanden raderas automatiskt efter tre timmar", - "prefs_users_description": "Lägg till/ta bort användare för dina skyddade ämnen här. Observera att användarnamn och lösenord lagras i webbläsarens lokala lagring.", - "prefs_users_delete_button": "Ta bort användare", - "prefs_users_table_cannot_delete_or_edit": "Kan inte ta bort eller redigera inloggad användare", - "prefs_users_table_user_header": "Användare", - "prefs_users_table_base_url_header": "Service-URL", - "prefs_users_dialog_username_label": "Användarnamn, t.ex. phil", - "prefs_reservations_title": "Reserverade ämnen", - "prefs_reservations_description": "Du kan reservera ämnesnamn för personligt bruk här. Genom att reservera ett ämne får du äganderätt till ämnet och kan definiera åtkomstbehörigheter för andra användare till ämnet.", - "prefs_reservations_limit_reached": "Du har nått gränsen för reserverade ämnen.", - "prefs_reservations_add_button": "Lägg till reserverat ämne", - "prefs_reservations_dialog_title_edit": "Redigera reserverat ämne", - "prefs_reservations_dialog_title_delete": "Ta bort ämnesreservation", - "signup_error_creation_limit_reached": "Gränsen för skapande av konton har uppnåtts", - "alert_not_supported_context_description": "Meddelanden stöds endast via HTTPS. Detta är en begränsning av Notifications API.", - "notifications_actions_not_supported": "Åtgärd stöds inte i webbapplikationen", - "notifications_none_for_any_description": "För att skicka meddelanden till ett ämne är det bara att PUT eller POST till ämnets URL. Här är ett exempel med ett av dina ämnen.", - "notifications_no_subscriptions_description": "Klicka på länken \"{{linktext}}\" för att skapa eller prenumerera på ett ämne. Därefter kan du skicka meddelanden via PUT eller POST och du får meddelanden här.", - "display_name_dialog_title": "Ändra visningsnamn", - "display_name_dialog_description": "Ange ett alternativt namn för ett ämne som visas i prenumerationslistan. På så sätt kan du lättare identifiera ämnen med komplicerade namn.", - "display_name_dialog_placeholder": "Visningsnamn", - "reserve_dialog_checkbox_label": "Reservera ämne och konfigurera åtkomst", - "publish_dialog_title_no_topic": "Publicera meddelande", - "publish_dialog_progress_uploading_detail": "Laddar upp {{loaded}}/{{{total}} ({{procent}}}%) …", - "publish_dialog_priority_min": "Lägsta prioritet", - "publish_dialog_priority_low": "Låg prioritet", - "publish_dialog_priority_default": "Standard prioritet", - "publish_dialog_priority_high": "Hög prioritet", - "publish_dialog_priority_max": "Högsta prioritet", - "publish_dialog_base_url_label": "Service-URL", - "publish_dialog_email_label": "E-post", - "publish_dialog_attach_reset": "Ta bort URL för bifogade filer", - "publish_dialog_chip_email_label": "Vidarebefordra till e-post", - "publish_dialog_chip_attach_url_label": "Bifoga fil via URL", - "publish_dialog_chip_attach_file_label": "Bifoga lokal fil", - "publish_dialog_chip_delay_label": "Fördröj leveransen", - "publish_dialog_chip_topic_label": "Ändra ämne", - "publish_dialog_button_cancel_sending": "Avbryt sändning", - "publish_dialog_button_cancel": "Avbryt", - "publish_dialog_attached_file_remove": "Ta bort bifogad fil", - "publish_dialog_drop_file_here": "Släpp filen här", - "emoji_picker_search_clear": "Rensa sökning", - "subscribe_dialog_subscribe_title": "Prenumerera på ämnet", - "subscribe_dialog_subscribe_description": "Ämnen kanske inte är lösenordsskyddade, så välj ett namn som inte är lätt att gissa. När du har prenumererat kan du lägga in/lägga in meddelanden.", - "subscribe_dialog_subscribe_topic_placeholder": "Ämnesnamn, t.ex. phils_alerts", - "subscribe_dialog_subscribe_use_another_label": "Använd en annan server", - "subscribe_dialog_subscribe_base_url_label": "Service-URL", - "subscribe_dialog_subscribe_button_generate_topic_name": "Generera namn", - "subscribe_dialog_subscribe_button_subscribe": "Prenumerera", - "subscribe_dialog_login_title": "Inloggning krävs", - "subscribe_dialog_login_description": "Det här ämnet är lösenordsskyddat. Ange användarnamn och lösenord för att prenumerera.", - "subscribe_dialog_login_username_label": "Användarnamn, t.ex. phil", - "subscribe_dialog_login_password_label": "Lösenord", - "subscribe_dialog_login_button_login": "Logga in", - "subscribe_dialog_error_user_not_authorized": "Användaren {{användarnamn}} inte auktoriserad", - "subscribe_dialog_error_topic_already_reserved": "Ämnet är redan reserverat", - "account_basics_title": "Konto", - "account_basics_tier_paid_until": "Prenumerationen är betald fram till {{datum}}, och kommer att förnyas automatiskt", - "account_basics_username_title": "Användarnamn", - "account_basics_username_description": "Hej, det är du ❤", - "account_basics_username_admin_tooltip": "Du är admin", - "account_basics_password_title": "Lösenord", - "account_basics_password_description": "Ändra lösenordet till ditt konto", - "account_basics_tier_payment_overdue": "Din betalning är försenad. Vänligen uppdatera din betalningsmetod, annars kommer ditt konto att nedgraderas inom kort.", - "account_basics_password_dialog_title": "Byt lösenord", - "account_basics_password_dialog_current_password_label": "Aktuellt lösenord", - "account_basics_password_dialog_new_password_label": "Nytt lösenord", - "account_basics_password_dialog_button_submit": "Byt lösenord", - "account_basics_password_dialog_current_password_incorrect": "Felaktigt lösenord", - "account_usage_title": "Användning", - "account_usage_of_limit": "av {{limit}}", - "account_usage_unlimited": "Obegränsad", - "account_usage_limits_reset_daily": "Användningsgränserna återställs dagligen vid midnatt (UTC)", - "account_basics_tier_title": "Kontotyp", - "account_basics_tier_description": "Ditt kontos nivå", - "account_basics_tier_admin": "Admin", - "account_basics_tier_admin_suffix_with_tier": "(med {{tier}}} nivå)", - "account_basics_tier_admin_suffix_no_tier": "(ingen nivå)", - "account_basics_tier_basic": "Grundläggande", - "account_basics_tier_upgrade_button": "Uppgradera till Pro", - "account_basics_tier_change_button": "Ändra", - "account_usage_cannot_create_portal_session": "Det går inte att öppna faktureringsportalen", - "account_usage_basis_ip_description": "Användningsstatistik och begränsningar för det här kontot baseras på din IP-adress, så de kan delas med andra användare. De gränser som visas ovan är ungefärliga och baseras på befintliga gränser.", - "account_tokens_title": "Åtkomsttoken", - "prefs_notifications_delete_after_one_day_description": "Meddelanden raderas automatiskt efter en dag", - "prefs_notifications_delete_after_one_week_description": "Meddelanden raderas automatiskt efter en vecka", - "prefs_notifications_delete_after_one_month_description": "Meddelanden raderas automatiskt efter en månad", - "prefs_users_title": "Hantera användare", - "prefs_reservations_table_not_subscribed": "Prenumererar inte", - "prefs_reservations_table_click_to_subscribe": "Klicka för att prenumerera", - "prefs_reservations_edit_button": "Redigera ämnesåtkomst", - "prefs_reservations_delete_button": "Återställ ämnesåtkomst", - "prefs_reservations_table": "Tabell över reserverade ämnen", - "prefs_reservations_table_topic_header": "Ämne", - "prefs_reservations_table_access_header": "Tillgång", - "prefs_reservations_table_everyone_deny_all": "Endast jag kan publicera och prenumerera", - "prefs_reservations_table_everyone_read_only": "Jag kan publicera och prenumerera, alla kan prenumerera", - "prefs_reservations_table_everyone_write_only": "Jag kan publicera och prenumerera, alla kan publicera", - "prefs_reservations_table_everyone_read_write": "Alla kan publicera och prenumerera", - "prefs_reservations_dialog_title_add": "Reserverade ämnen", - "prefs_reservations_dialog_description": "Genom att reservera ett ämne får du äganderätt till ämnet och kan definiera åtkomstbehörigheter för andra användare till ämnet.", - "prefs_reservations_dialog_topic_label": "Ämne", - "prefs_reservations_dialog_access_label": "Tillgång", - "reservation_delete_dialog_action_keep_title": "Behåll cachade meddelanden och bilagor", - "reservation_delete_dialog_action_keep_description": "Meddelanden och bilagor som lagras på servern blir offentligt synliga för personer som känner till ämnesnamnet.", - "reservation_delete_dialog_action_delete_title": "Ta bort meddelanden och bilagor som sparats i cacheminnet", - "reservation_delete_dialog_description": "Om du tar bort en reservation ger du upp äganderätten till ämnet och låter andra reservera det. Du kan behålla eller radera befintliga meddelanden och bilagor.", - "publish_dialog_call_label": "Telefonsamtal", - "publish_dialog_call_reset": "Ta bort telefonsamtal", - "publish_dialog_chip_call_label": "Telefonsamtal", - "account_basics_phone_numbers_title": "Telefonnummer", - "account_basics_phone_numbers_description": "För notifieringar via telefonsamtal", - "account_basics_phone_numbers_no_phone_numbers_yet": "Inga telefonnummer ännu", - "account_basics_phone_numbers_copied_to_clipboard": "Telefonnummer kopierat till urklipp", - "account_basics_phone_numbers_dialog_title": "Lägga till telefonnummer", - "account_basics_phone_numbers_dialog_number_label": "Telefonnummer", - "account_basics_phone_numbers_dialog_number_placeholder": "t.ex. +1222333444", - "account_basics_phone_numbers_dialog_verify_button_sms": "Skicka SMS", - "account_basics_phone_numbers_dialog_verify_button_call": "Ring mig", - "account_basics_phone_numbers_dialog_code_label": "Verifieringskod", - "account_basics_phone_numbers_dialog_channel_call": "Ring", - "account_usage_calls_title": "Telefonsamtal som gjorts", - "account_usage_calls_none": "Inga telefonsamtal kan göras med detta konto", - "publish_dialog_call_item": "Ring telefonnummer {{number}}", - "publish_dialog_chip_call_no_verified_numbers_tooltip": "Inga verifierade telefonnummer", - "account_basics_phone_numbers_dialog_description": "För att använda funktionen för samtalsavisering måste du lägga till och verifiera minst ett telefonnummer. Verifieringen kan göras via SMS eller ett telefonsamtal.", - "account_basics_phone_numbers_dialog_code_placeholder": "t.ex. 123456", - "account_basics_phone_numbers_dialog_check_verification_button": "Bekräfta kod", - "account_basics_phone_numbers_dialog_channel_sms": "SMS", - "account_upgrade_dialog_tier_features_calls_other": "{{calls}} dagliga telefonsamtal", - "account_upgrade_dialog_tier_features_no_calls": "Inga telefonsamtal", - "account_upgrade_dialog_tier_features_calls_one": "{{calls}} dagliga telefonsamtal" + "notifications_loading": "Laddar notiser …" } diff --git a/web/public/static/langs/tr.json b/web/public/static/langs/tr.json index 3eccda8..4a74866 100644 --- a/web/public/static/langs/tr.json +++ b/web/public/static/langs/tr.json @@ -34,7 +34,7 @@ "subscribe_dialog_login_description": "Bu konu parola korumalı. Abone olmak için lütfen kullanıcı adı ve parola girin.", "subscribe_dialog_login_username_label": "Kullanıcı adı, örn. phil", "subscribe_dialog_login_password_label": "Parola", - "common_back": "Geri", + "subscribe_dialog_login_button_back": "Geri", "subscribe_dialog_login_button_login": "Oturum aç", "subscribe_dialog_error_user_not_authorized": "{{username}} kullanıcısı yetkili değil", "subscribe_dialog_error_user_anonymous": "anonim", @@ -251,11 +251,11 @@ "account_delete_dialog_button_submit": "Hesabı kalıcı olarak sil", "account_delete_dialog_billing_warning": "Hesabınızı silmek, faturalandırma aboneliğinizi de anında iptal eder. Artık faturalandırma sayfasına erişiminiz olmayacak.", "account_upgrade_dialog_title": "Hesap seviyesini değiştir", - "account_upgrade_dialog_proration_info": "Fiyatlandırma: Ücretli planlar arasında yükseltme yaparken, fiyat farkı hemen tahsil edilecektir. Daha düşük bir seviyeye inildiğinde, bakiye gelecek faturalandırma dönemleri için ödeme yapmak üzere kullanılacaktır.", + "account_upgrade_dialog_proration_info": "Ödeme oranı: Ücretli planlar arasında geçiş yaparken, fiyat farkı bir sonraki faturada tahsil edilecek veya iade edilecektir. Bir sonraki fatura döneminin sonuna kadar başka bir fatura almayacaksınız.", "account_upgrade_dialog_reservations_warning_other": "Seçilen seviye, geçerli seviyenizden daha az konu ayırtmaya izin veriyor. Seviyenizi değiştirmeden önce lütfen en az {{count}} ayırtmayı silin. Ayırtmaları Ayarlar sayfasından kaldırabilirsiniz.", - "account_upgrade_dialog_tier_features_reservations_other": "{{reservations}} konu ayırtıldı", - "account_upgrade_dialog_tier_features_messages_other": "{{messages}} günlük mesaj", - "account_upgrade_dialog_tier_features_emails_other": "{{emails}} günlük e-posta", + "account_upgrade_dialog_tier_features_reservations": "{{reservations}} konu ayırtıldı", + "account_upgrade_dialog_tier_features_messages": "{{messages}} günlük mesaj", + "account_upgrade_dialog_tier_features_emails": "{{emails}} günlük e-posta", "account_upgrade_dialog_tier_features_attachment_file_size": "dosya başına {{filesize}}", "account_upgrade_dialog_tier_features_attachment_total_size": "{{totalsize}} toplam depolama", "account_upgrade_dialog_tier_selected_label": "Seçilen", @@ -268,7 +268,7 @@ "account_tokens_table_token_header": "Belirteç", "account_tokens_table_label_header": "Etiket", "account_tokens_table_current_session": "Geçerli tarayıcı oturumu", - "common_copy_to_clipboard": "Panoya kopyala", + "account_tokens_table_copy_to_clipboard": "Panoya kopyala", "account_tokens_table_copied_to_clipboard": "Erişim belirteci kopyalandı", "account_tokens_table_cannot_delete_or_edit": "Geçerli oturum belirteci düzenlenemez veya silinemez", "account_tokens_table_create_token_button": "Erişim belirteci oluştur", @@ -340,20 +340,5 @@ "prefs_reservations_table_everyone_read_only": "Ben yayınlayabilir ve abone olabilirim, herkes abone olabilir", "prefs_reservations_table_not_subscribed": "Abone olunmadı", "prefs_reservations_table_everyone_read_write": "Herkes yayınlayabilir ve abone olabilir", - "reservation_delete_dialog_description": "Ayırtmanın kaldırılması, konu üzerindeki sahiplikten vazgeçer ve başkalarının onu ayırtmasına izin verir. Mevcut mesajları ve ekleri saklayabilir veya silebilirsiniz.", - "account_basics_tier_interval_yearly": "yıllık", - "account_upgrade_dialog_tier_features_no_reservations": "Ayırtılan konu yok", - "account_upgrade_dialog_tier_price_billed_monthly": "Yıllık {{price}}. Aylık faturalandırılır.", - "account_upgrade_dialog_tier_price_billed_yearly": "{{price}} yıllık olarak faturalandırılır. {{save}} tasarruf edin.", - "account_upgrade_dialog_interval_yearly": "Yıllık", - "account_upgrade_dialog_interval_yearly_discount_save": "%{{discount}} tasarruf edin", - "account_upgrade_dialog_tier_price_per_month": "ay", - "account_upgrade_dialog_billing_contact_email": "Faturalama ile ilgili sorularınız için lütfen doğrudan bizimle iletişime geçin.", - "account_upgrade_dialog_interval_yearly_discount_save_up_to": "%{{discount}} kadar tasarruf edin", - "account_upgrade_dialog_interval_monthly": "Aylık", - "account_basics_tier_interval_monthly": "aylık", - "account_upgrade_dialog_billing_contact_website": "Faturalama ile ilgili sorularınız için lütfen web sitemizi ziyaret edin.", - "account_upgrade_dialog_tier_features_reservations_one": "{{reservations}} ayırtılan konu", - "account_upgrade_dialog_tier_features_emails_one": "{{emails}} günlük e-posta", - "account_upgrade_dialog_tier_features_messages_one": "{{messages}} günlük mesaj" + "reservation_delete_dialog_description": "Ayırtmanın kaldırılması, konu üzerindeki sahiplikten vazgeçer ve başkalarının onu ayırtmasına izin verir. Mevcut mesajları ve ekleri saklayabilir veya silebilirsiniz." } diff --git a/web/public/static/langs/uk.json b/web/public/static/langs/uk.json index 32a3079..304bd9d 100644 --- a/web/public/static/langs/uk.json +++ b/web/public/static/langs/uk.json @@ -53,7 +53,7 @@ "subscribe_dialog_subscribe_use_another_label": "Використовувати інший сервер", "subscribe_dialog_subscribe_base_url_label": "URL служби", "subscribe_dialog_login_password_label": "Пароль", - "common_back": "Назад", + "subscribe_dialog_login_button_back": "Назад", "subscribe_dialog_error_user_not_authorized": "{{username}} користувач не авторизований", "prefs_notifications_sound_description_none": "Сповіщення не відтворюють жодного звуку при надходженні", "prefs_notifications_sound_description_some": "Сповіщення відтворюють звук {{sound}}", @@ -187,199 +187,5 @@ "priority_low": "низький", "error_boundary_stack_trace": "Трасування стека", "error_boundary_unsupported_indexeddb_title": "Приватний перегляд не підтримується", - "error_boundary_unsupported_indexeddb_description": "Веб-програма ntfy потребує IndexedDB для роботи, а ваш браузер не підтримує IndexedDB у режимі приватного перегляду.

На жаль, використання ntfy web не має сенсу у режимі приватного перегляду, оскільки все зберігається в пам’яті браузера. Ви можете прочитати більше про це у цьому випуску GitHub або поспілкуватися з нами на Discord або Matrix.", - "signup_title": "Створення облікового запису ntfy", - "signup_form_username": "Ім'я користувача", - "signup_form_password": "Пароль", - "signup_form_confirm_password": "Підтвердіть пароль", - "signup_form_button_submit": "Зареєструватися", - "signup_form_toggle_password_visibility": "Перемкнути видимість пароля", - "signup_already_have_account": "Вже маєте обліковий запис? Увійдіть!", - "signup_disabled": "Реєстрацію вимкнено", - "signup_error_username_taken": "Ім'я користувача {{username}} вже зайнято", - "signup_error_creation_limit_reached": "Досягнуто обмеження на створення облікового запису", - "login_title": "Увійдіть до свого облікового запису ntfy", - "login_form_button_submit": "Увійти", - "login_link_signup": "Зареєструватися", - "login_disabled": "Вхід вимкнено", - "action_bar_account": "Обліковий запис", - "action_bar_reservation_add": "Зарезервувати тему", - "action_bar_reservation_edit": "Змінити резервування", - "action_bar_reservation_delete": "Видалити резервування", - "action_bar_reservation_limit_reached": "Досягнуто ліміту", - "action_bar_change_display_name": "Змінити відображувану назву", - "action_bar_profile_title": "Профіль", - "action_bar_profile_settings": "Налаштування", - "action_bar_sign_up": "Зареєструватися", - "nav_button_account": "Обліковий запис", - "nav_upgrade_banner_description": "Резервування тем, більше повідомлень та імейлів, більші вкладення", - "alert_not_supported_context_description": "Сповіщення підтримуються лише через HTTPS. Це обмеження Notifications API.", - "display_name_dialog_title": "Змінити відображувану назву", - "reserve_dialog_checkbox_label": "Зарезервувати тему та налаштувати доступ", - "subscribe_dialog_subscribe_button_generate_topic_name": "Згенерувати назву", - "subscribe_dialog_error_topic_already_reserved": "Тема вже зарезервована", - "account_basics_title": "Обліковий запис", - "account_basics_username_title": "Ім'я користувача", - "account_basics_username_description": "Привіт, це ти ❤", - "account_basics_password_dialog_title": "Змінити пароль", - "account_basics_password_dialog_current_password_label": "Поточний пароль", - "account_basics_password_dialog_new_password_label": "Новий пароль", - "account_basics_password_dialog_confirm_password_label": "Підтвердіть пароль", - "account_basics_password_dialog_button_submit": "Змінити пароль", - "account_basics_password_dialog_current_password_incorrect": "Неправильний пароль", - "account_usage_title": "Використання", - "account_usage_limits_reset_daily": "Ліміти використання скидаються щодня опівночі (UTC)", - "account_basics_tier_title": "Тип облікового запису", - "account_basics_tier_admin": "Адміністратор", - "action_bar_sign_in": "Увійти", - "action_bar_profile_logout": "Вийти", - "nav_upgrade_banner_label": "Оновлення до ntfy Pro", - "display_name_dialog_description": "Задайте альтернативну назву для теми, яка відображатиметься у списку підписок. Це допоможе легше ідентифікувати теми зі складними назвами.", - "display_name_dialog_placeholder": "Відображуване ім'я", - "account_basics_password_title": "Пароль", - "account_basics_username_admin_tooltip": "Ви адміністратор", - "account_basics_tier_interval_monthly": "щомісяця", - "common_copy_to_clipboard": "Скопіювати в буфер обміну", - "account_basics_phone_numbers_title": "Номери телефонів", - "account_basics_phone_numbers_description": "Для сповіщень через телефонні дзвінки", - "account_basics_phone_numbers_no_phone_numbers_yet": "Поки що немає номерів телефонів", - "account_basics_phone_numbers_copied_to_clipboard": "Номер телефону скопійовано в буфер обміну", - "account_basics_phone_numbers_dialog_title": "Додати номер телефону", - "account_basics_phone_numbers_dialog_number_label": "Номер телефону", - "account_basics_phone_numbers_dialog_number_placeholder": "наприклад, +1222333444", - "account_basics_phone_numbers_dialog_verify_button_sms": "Надіслати SMS", - "account_basics_phone_numbers_dialog_verify_button_call": "Зателефонуйте мені", - "account_basics_phone_numbers_dialog_code_label": "Код підтвердження", - "account_basics_phone_numbers_dialog_code_placeholder": "наприклад, 123456", - "account_basics_phone_numbers_dialog_check_verification_button": "Підтвердити код", - "account_basics_phone_numbers_dialog_channel_sms": "SMS", - "account_basics_phone_numbers_dialog_channel_call": "Дзвінок", - "account_basics_tier_interval_yearly": "щороку", - "account_usage_calls_title": "Здійснені телефонні дзвінки", - "account_usage_calls_none": "З цього облікового запису не можна здійснювати телефонні дзвінки", - "account_usage_attachment_storage_title": "Зберігання вкладень", - "account_usage_attachment_storage_description": "{{filesize}} на файл, видаляється після {{expiry}}", - "account_usage_basis_ip_description": "Статистика використання та ліміти для цього облікового запису базуються на вашій IP-адресі, тому вони можуть бути доступні іншим користувачам. Ліміти, показані вище, є приблизними і базуються на існуючих лімітах тарифів.", - "account_usage_cannot_create_portal_session": "Не вдається відкрити білінговий портал", - "account_delete_title": "Видалення облікового запису", - "account_delete_description": "Назавжди видалити свій обліковий запис", - "account_delete_dialog_label": "Пароль", - "account_delete_dialog_button_cancel": "Скасувати", - "account_delete_dialog_button_submit": "Видалити обліковий запис назавжди", - "account_delete_dialog_billing_warning": "Видалення облікового запису також негайно скасовує вашу підписку. Ви більше не матимете доступу до білінгової панелі.", - "account_upgrade_dialog_title": "Зміна рівня облікового запису", - "account_upgrade_dialog_interval_monthly": "Щомісяця", - "account_upgrade_dialog_interval_yearly": "Щорічно", - "account_upgrade_dialog_interval_yearly_discount_save": "економія {{discount}}%", - "account_upgrade_dialog_interval_yearly_discount_save_up_to": "економія до {{discount}}%", - "publish_dialog_call_label": "Телефонний дзвінок", - "publish_dialog_call_placeholder": "Номер телефону, на який потрібно зателефонувати з повідомленням, наприклад, +12223334444 або \"yes\"", - "publish_dialog_chip_call_label": "Телефонний дзвінок", - "publish_dialog_call_reset": "Видалити телефонний дзвінок", - "account_basics_phone_numbers_dialog_description": "Щоб користуватися функцією сповіщення про дзвінки, потрібно додати та верифікувати принаймні один телефонний номер. Верифікацію можна здійснити за допомогою SMS або телефонного дзвінка.", - "account_delete_dialog_description": "Це призведе до остаточного видалення вашого облікового запису, включаючи всі дані, які зберігаються на сервері. Після видалення ваше ім'я користувача буде недоступне протягом 7 днів. Якщо ви дійсно хочете продовжити, будь ласка, підтвердьте свій пароль у полі нижче.", - "account_basics_tier_upgrade_button": "Оновлення до Pro", - "account_basics_password_description": "Зміна пароля облікового запису", - "account_usage_of_limit": "з {{limit}}", - "account_usage_unlimited": "Без обмежень", - "account_basics_tier_description": "Рівень потужності вашого облікового запису", - "account_basics_tier_admin_suffix_with_tier": "(з рівнем {{tier}})", - "account_basics_tier_admin_suffix_no_tier": "(без рівня)", - "account_basics_tier_basic": "Базовий", - "account_basics_tier_free": "Безкоштовний", - "account_basics_tier_change_button": "Змінити", - "account_basics_tier_paid_until": "Підписка оплачена до {{date}} і буде автоматично поновлюватися", - "account_basics_tier_payment_overdue": "Ваш платіж прострочено. Будь ласка, оновіть спосіб оплати, інакше ваш обліковий запис буде знижено до нижчого рівня.", - "account_basics_tier_canceled_subscription": "Вашу підписку було скасовано, і з {{date}} вона буде знижена до безкоштовного акаунта.", - "account_basics_tier_manage_billing_button": "Керувати рахунками", - "account_usage_messages_title": "Опубліковані повідомлення", - "account_usage_emails_title": "Надіслані електронні листи", - "account_usage_reservations_title": "Зарезервовані теми", - "account_usage_reservations_none": "Для цього облікового запису немає зарезервованих тем", - "account_upgrade_dialog_tier_features_attachment_file_size": "{{filesize}} на файл", - "account_upgrade_dialog_tier_features_attachment_total_size": "{{totalsize}} загальне сховище", - "account_upgrade_dialog_tier_current_label": "Поточний", - "account_upgrade_dialog_tier_selected_label": "Вибране", - "account_upgrade_dialog_cancel_warning": "Це скасує вашу підписку і знизить версію вашого облікового запису {{date}}. У цю дату резервування тем, а також повідомлення, кешовані на сервері , буде видалено.", - "account_upgrade_dialog_tier_features_reservations_other": "{{reservations}} зарезервовані теми", - "account_upgrade_dialog_tier_features_no_reservations": "Немає зарезервованих тем", - "account_upgrade_dialog_tier_features_messages_other": "{{messages}} повідомлень в день", - "account_upgrade_dialog_tier_features_emails_one": "{{emails}} електронний лист в день", - "account_upgrade_dialog_tier_features_emails_other": "{{emails}} електронних листів в день", - "account_upgrade_dialog_tier_features_calls_one": "{{calls}} телефонний дзвінок в день", - "account_upgrade_dialog_tier_features_calls_other": "{{дзвінки}} телефонних дзвінків в день", - "account_upgrade_dialog_tier_features_no_calls": "Без телефонних дзвінків", - "account_upgrade_dialog_tier_price_per_month": "місяць", - "account_upgrade_dialog_tier_price_billed_monthly": "{{price}} на рік. Рахунок виставляється щомісяця.", - "account_upgrade_dialog_tier_price_billed_yearly": "{{price}} виставляється щорічно. Збережіть {{save}}.", - "account_upgrade_dialog_billing_contact_email": "Якщо у вас виникли запитання щодо оплати, зв’яжіться з нами безпосередньо.", - "account_upgrade_dialog_billing_contact_website": "Якщо у вас виникли запитання щодо оплати, відвідайте наш веб-сайт.", - "account_upgrade_dialog_button_cancel_subscription": "Скасувати підписку", - "account_upgrade_dialog_button_update_subscription": "Оновити підписку", - "account_tokens_title": "Токени доступу", - "account_tokens_table_expires_header": "Термін дії закінчується", - "account_tokens_description": "Використовуйте токени доступу при публікації та підписці через ntfy API, щоб не надсилати свої облікові дані. Ознайомтеся з документацією, щоб дізнатися більше.", - "account_tokens_table_token_header": "Токен", - "account_tokens_table_never_expires": "Ніколи не закінчується", - "account_tokens_table_label_header": "Мітка", - "account_tokens_table_current_session": "Поточний сеанс браузера", - "account_tokens_table_last_access_header": "Останній доступ", - "account_tokens_table_copied_to_clipboard": "Токен доступу скопійовано", - "account_tokens_table_cannot_delete_or_edit": "Неможливо редагувати або видалити токен поточного сеансу", - "account_tokens_table_create_token_button": "Створити токен доступу", - "account_tokens_table_last_origin_tooltip": "З IP-адреси {{ip}} натисніть для пошуку", - "account_tokens_dialog_title_create": "Створити токен доступу", - "account_tokens_dialog_button_cancel": "Скасувати", - "account_tokens_dialog_title_edit": "Редагувати токен доступу", - "account_tokens_dialog_title_delete": "Видалити токен доступу", - "account_tokens_dialog_label": "Мітка, наприклад, сповіщення Radarr", - "account_tokens_dialog_button_create": "Створити токен", - "account_tokens_dialog_button_update": "Оновити токен", - "account_tokens_dialog_expires_label": "Термін дії токену доступу закінчується через", - "account_tokens_dialog_expires_x_hours": "Термін дії токена закінчується через {{hours}} годин", - "account_tokens_dialog_expires_x_days": "Термін дії токена закінчується через {{days}} днів", - "account_tokens_delete_dialog_description": "Перш ніж видалити токен доступу, переконайтеся, що жодна програма або скрипт не використовує його. Ця дія не може бути скасована.", - "prefs_users_description_no_sync": "Користувачі та паролі не синхронізуються з вашим акаунтом.", - "prefs_users_table_cannot_delete_or_edit": "Неможливо видалити або відредагувати користувача, який увійшов у систему", - "account_upgrade_dialog_tier_features_reservations_one": "{{reservations}} зарезервована тема", - "account_upgrade_dialog_tier_features_messages_one": "{{messages}} повідомлення в день", - "account_tokens_dialog_expires_unchanged": "Залишити термін придатності без змін", - "account_tokens_dialog_expires_never": "Термін дії токена ніколи не закінчується", - "account_tokens_delete_dialog_title": "Видалити токен доступу", - "account_tokens_delete_dialog_submit_button": "Видалити токен назавжди", - "account_upgrade_dialog_proration_info": "Пропорція: При переході з одного тарифного плану на інший різниця в ціні буде списана негайно. При переході на нижчий рівень залишок коштів буде використано для оплати майбутніх розрахункових періодів.", - "account_upgrade_dialog_reservations_warning_one": "Обраний рівень дозволяє менше зарезервованих тем, ніж ваш поточний рівень. Перш ніж змінити свій рівень, будь ласка, видаліть принаймні одне резервування. Ви можете видалити резервування в Налаштуваннях.", - "account_upgrade_dialog_reservations_warning_other": "Обраний рівень дозволяє менше зарезервованих тем, ніж ваш поточний рівень. Перш ніж змінити свій рівень, будь ласка, видаліть принаймні {{count}} резервувань. Ви можете видалити резервування в Налаштуваннях.", - "account_upgrade_dialog_button_cancel": "Скасувати", - "account_upgrade_dialog_button_redirect_signup": "Зареєструватися зараз", - "account_upgrade_dialog_button_pay_now": "Оплатити зараз і підписатися", - "prefs_reservations_add_button": "Додати зарезервовану тему", - "prefs_reservations_edit_button": "Редагувати доступ до теми", - "prefs_reservations_limit_reached": "Ви досягли ліміту зарезервованих тем.", - "prefs_reservations_table_click_to_subscribe": "Натисніть, щоб підписатися", - "prefs_reservations_table_topic_header": "Тема", - "prefs_reservations_description": "Тут ви можете зарезервувати назви тем для особистого користування. Резервування теми дає вам право власності на тему і дозволяє визначати права доступу до неї інших користувачів.", - "prefs_reservations_table": "Таблиця зарезервованих тем", - "prefs_reservations_table_access_header": "Доступ", - "prefs_reservations_table_everyone_deny_all": "Тільки я можу публікувати та підписуватись", - "prefs_reservations_table_everyone_read_only": "Я можу публікувати та підписуватись, кожен може підписатися", - "prefs_reservations_table_everyone_write_only": "Я можу публікувати і підписуватися, кожен може публікувати", - "prefs_reservations_table_everyone_read_write": "Кожен може публікувати та підписуватися", - "prefs_reservations_table_not_subscribed": "Не підписаний", - "prefs_reservations_dialog_title_add": "Зарезервувати тему", - "prefs_reservations_dialog_title_edit": "Редагувати зарезервовану тему", - "prefs_reservations_title": "Зарезервовані теми", - "prefs_reservations_delete_button": "Скинути доступ до теми", - "prefs_reservations_dialog_description": "Резервування теми дає вам право власності на цю тему і дозволяє визначати права доступу до неї інших користувачів.", - "prefs_reservations_dialog_topic_label": "Тема", - "prefs_reservations_dialog_access_label": "Доступ", - "reservation_delete_dialog_description": "Видалення резервування позбавляє вас права власності на тему і дозволяє іншим зарезервувати її. Ви можете зберегти або видалити існуючі повідомлення і вкладення.", - "reservation_delete_dialog_submit_button": "Видалити резервування", - "publish_dialog_call_item": "Телефонувати за номером {{номер}}", - "publish_dialog_chip_call_no_verified_numbers_tooltip": "Немає підтверджених номерів телефонів", - "prefs_reservations_dialog_title_delete": "Видалити резервування теми", - "reservation_delete_dialog_action_delete_title": "Видалення кешованих повідомлень і вкладень", - "reservation_delete_dialog_action_keep_title": "Збереження кешованих повідомлень і вкладень", - "reservation_delete_dialog_action_keep_description": "Повідомлення і вкладення, які кешуються на сервері, стають загальнодоступними для людей, які знають назву теми.", - "reservation_delete_dialog_action_delete_description": "Кешовані повідомлення та вкладення будуть видалені назавжди. Ця дія не може бути скасована." + "error_boundary_unsupported_indexeddb_description": "Веб-програма ntfy потребує IndexedDB для роботи, а ваш браузер не підтримує IndexedDB у режимі приватного перегляду.

На жаль, використання ntfy web не має сенсу у режимі приватного перегляду, оскільки все зберігається в пам’яті браузера. Ви можете прочитати більше про це у цьому випуску GitHub або поспілкуватися з нами на Discord або Matrix." } diff --git a/web/public/static/langs/zh_Hans.json b/web/public/static/langs/zh_Hans.json index 2db95f5..818aec0 100644 --- a/web/public/static/langs/zh_Hans.json +++ b/web/public/static/langs/zh_Hans.json @@ -103,7 +103,7 @@ "subscribe_dialog_login_description": "本主题受密码保护,请输入用户名和密码进行订阅。", "subscribe_dialog_login_username_label": "用户名,例如 phil", "subscribe_dialog_login_password_label": "密码", - "common_back": "返回", + "subscribe_dialog_login_button_back": "返回", "subscribe_dialog_login_button_login": "登录", "subscribe_dialog_error_user_not_authorized": "未授权 {{username}} 用户", "subscribe_dialog_error_user_anonymous": "匿名", @@ -293,12 +293,12 @@ "account_delete_dialog_billing_warning": "删除您的帐户也会立即取消您的计费订阅。您将无法再访问计费仪表板。", "account_upgrade_dialog_title": "更改帐户等级", "account_upgrade_dialog_cancel_warning": "这将取消您的订阅,并在 {{date}} 降级您的帐户。在那一天,主题保留以及缓存在服务器上的消息将被删除。", - "account_upgrade_dialog_proration_info": "按比例分配:在付费计划之间升级时,差价将被立刻收取。在降级到较低级别时,余额将被用于支付未来的账单周期。", + "account_upgrade_dialog_proration_info": "按比例分配:在付费计划之间切换时,差价将在下一次计费时收取或退还。在下一个计费周期结束之前,您不会收到另一张收据。", "account_upgrade_dialog_reservations_warning_one": "所选等级允许的保留主题少于当前等级。在更改您的等级之前,请至少删除 1 项保留。您可以在设置中删除保留。", "account_upgrade_dialog_reservations_warning_other": "所选等级允许的保留主题少于当前等级。在更改您的等级之前,请至少删除 {{count}} 项保留。您可以在设置中删除保留。", - "account_upgrade_dialog_tier_features_reservations_other": "{{reservations}} 条保留主题", - "account_upgrade_dialog_tier_features_messages_other": "{{messages}} 条每日消息", - "account_upgrade_dialog_tier_features_emails_other": "{{emails}} 条每日邮件", + "account_upgrade_dialog_tier_features_reservations": "{{reservations}} 条保留主题", + "account_upgrade_dialog_tier_features_messages": "{{messages}} 条每日消息", + "account_upgrade_dialog_tier_features_emails": "{{emails}} 条每日邮件", "account_upgrade_dialog_tier_features_attachment_file_size": "{{filesize}} 每个文件", "signup_form_confirm_password": "确认密码", "signup_form_button_submit": "注册", @@ -333,24 +333,12 @@ "account_tokens_table_expires_header": "过期", "account_tokens_table_never_expires": "永不过期", "account_tokens_table_current_session": "当前浏览器会话", - "common_copy_to_clipboard": "复制到剪贴板", + "account_tokens_table_copy_to_clipboard": "复制到剪贴板", "account_tokens_table_copied_to_clipboard": "已复制访问令牌", "account_tokens_table_cannot_delete_or_edit": "无法编辑或删除当前会话令牌", "account_tokens_table_create_token_button": "创建访问令牌", "account_tokens_table_last_origin_tooltip": "于IP地址 {{ip}},点击查找", "account_tokens_dialog_label": "标签,例如:Radarr 通知", "account_tokens_dialog_button_create": "创建令牌", - "account_tokens_dialog_button_update": "更新令牌", - "account_basics_tier_interval_monthly": "每月", - "account_basics_tier_interval_yearly": "每年", - "account_upgrade_dialog_interval_monthly": "每月", - "account_upgrade_dialog_interval_yearly": "每年", - "account_upgrade_dialog_interval_yearly_discount_save": "节省 {{discount}}%", - "account_upgrade_dialog_interval_yearly_discount_save_up_to": "节省高达 {{discount}}%", - "account_upgrade_dialog_tier_features_no_reservations": "无保留主题", - "account_upgrade_dialog_tier_price_per_month": "月", - "account_upgrade_dialog_tier_price_billed_monthly": "{{price}} 每年。按月计费。", - "account_upgrade_dialog_tier_price_billed_yearly": "{{价格}} 按年计费。节省 {{save}}。", - "account_upgrade_dialog_billing_contact_email": "有关账单问题,请直接联系我们 。", - "account_upgrade_dialog_billing_contact_website": "有关账单问题,请参考我们的网站 。" + "account_tokens_dialog_button_update": "更新令牌" } diff --git a/web/public/static/langs/zh_Hant.json b/web/public/static/langs/zh_Hant.json index aafc28e..c1b4de8 100644 --- a/web/public/static/langs/zh_Hant.json +++ b/web/public/static/langs/zh_Hant.json @@ -70,7 +70,7 @@ "subscribe_dialog_subscribe_button_subscribe": "訂閱", "emoji_picker_search_clear": "清除", "subscribe_dialog_login_password_label": "密碼", - "common_back": "返回", + "subscribe_dialog_login_button_back": "返回", "subscribe_dialog_login_button_login": "登入", "prefs_notifications_delete_after_never": "從不", "prefs_users_add_button": "新增使用者", diff --git a/web/src/app/AccountApi.js b/web/src/app/AccountApi.js index 9576c4e..243286b 100644 --- a/web/src/app/AccountApi.js +++ b/web/src/app/AccountApi.js @@ -1,430 +1,389 @@ -import i18n from "i18next"; import { - accountBillingPortalUrl, - accountBillingSubscriptionUrl, - accountPasswordUrl, - accountPhoneUrl, - accountPhoneVerifyUrl, - accountReservationSingleUrl, - accountReservationUrl, - accountSettingsUrl, - accountSubscriptionUrl, - accountTokenUrl, - accountUrl, - maybeWithBearerAuth, - tiersUrl, - withBasicAuth, - withBearerAuth, + accountBillingPortalUrl, + accountBillingSubscriptionUrl, + accountPasswordUrl, + accountReservationSingleUrl, + accountReservationUrl, + accountSettingsUrl, + accountSubscriptionSingleUrl, + accountSubscriptionUrl, + accountTokenUrl, + accountUrl, maybeWithBearerAuth, + tiersUrl, + withBasicAuth, + withBearerAuth } from "./utils"; import session from "./Session"; import subscriptionManager from "./SubscriptionManager"; +import i18n from "i18next"; import prefs from "./Prefs"; import routes from "../components/routes"; -import { fetchOrThrow, UnauthorizedError } from "./errors"; +import {fetchOrThrow, throwAppError, UnauthorizedError} from "./errors"; const delayMillis = 45000; // 45 seconds const intervalMillis = 900000; // 15 minutes class AccountApi { - constructor() { - this.timer = null; - this.listener = null; // Fired when account is fetched from remote - this.tiers = null; // Cached - } - - registerListener(listener) { - this.listener = listener; - } - - resetListener() { - this.listener = null; - } - - async login(user) { - const url = accountTokenUrl(config.base_url); - console.log(`[AccountApi] Checking auth for ${url}`); - const response = await fetchOrThrow(url, { - method: "POST", - headers: withBasicAuth({}, user.username, user.password), - }); - const json = await response.json(); // May throw SyntaxError - if (!json.token) { - throw new Error(`Unexpected server response: Cannot find token`); + constructor() { + this.timer = null; + this.listener = null; // Fired when account is fetched from remote + this.tiers = null; // Cached } - return json.token; - } - async logout() { - const url = accountTokenUrl(config.base_url); - console.log(`[AccountApi] Logging out from ${url} using token ${session.token()}`); - await fetchOrThrow(url, { - method: "DELETE", - headers: withBearerAuth({}, session.token()), - }); - } - - async create(username, password) { - const url = accountUrl(config.base_url); - const body = JSON.stringify({ - username, - password, - }); - console.log(`[AccountApi] Creating user account ${url}`); - await fetchOrThrow(url, { - method: "POST", - body, - }); - } - - async get() { - const url = accountUrl(config.base_url); - console.log(`[AccountApi] Fetching user account ${url}`); - const response = await fetchOrThrow(url, { - headers: maybeWithBearerAuth({}, session.token()), // GET /v1/account endpoint can be called by anonymous - }); - const account = await response.json(); // May throw SyntaxError - console.log(`[AccountApi] Account`, account); - if (this.listener) { - this.listener(account); + registerListener(listener) { + this.listener = listener; } - return account; - } - async delete(password) { - const url = accountUrl(config.base_url); - console.log(`[AccountApi] Deleting user account ${url}`); - await fetchOrThrow(url, { - method: "DELETE", - headers: withBearerAuth({}, session.token()), - body: JSON.stringify({ - password, - }), - }); - } - - async changePassword(currentPassword, newPassword) { - const url = accountPasswordUrl(config.base_url); - console.log(`[AccountApi] Changing account password ${url}`); - await fetchOrThrow(url, { - method: "POST", - headers: withBearerAuth({}, session.token()), - body: JSON.stringify({ - password: currentPassword, - new_password: newPassword, - }), - }); - } - - async createToken(label, expires) { - const url = accountTokenUrl(config.base_url); - const body = { - label, - expires: expires > 0 ? Math.floor(Date.now() / 1000) + expires : 0, - }; - console.log(`[AccountApi] Creating user access token ${url}`); - await fetchOrThrow(url, { - method: "POST", - headers: withBearerAuth({}, session.token()), - body: JSON.stringify(body), - }); - } - - async updateToken(token, label, expires) { - const url = accountTokenUrl(config.base_url); - const body = { - token, - label, - }; - if (expires > 0) { - body.expires = Math.floor(Date.now() / 1000) + expires; + resetListener() { + this.listener = null; } - console.log(`[AccountApi] Creating user access token ${url}`); - await fetchOrThrow(url, { - method: "PATCH", - headers: withBearerAuth({}, session.token()), - body: JSON.stringify(body), - }); - } - async extendToken() { - const url = accountTokenUrl(config.base_url); - console.log(`[AccountApi] Extending user access token ${url}`); - await fetchOrThrow(url, { - method: "PATCH", - headers: withBearerAuth({}, session.token()), - }); - } - - async deleteToken(token) { - const url = accountTokenUrl(config.base_url); - console.log(`[AccountApi] Deleting user access token ${url}`); - await fetchOrThrow(url, { - method: "DELETE", - headers: withBearerAuth({ "X-Token": token }, session.token()), - }); - } - - async updateSettings(payload) { - const url = accountSettingsUrl(config.base_url); - const body = JSON.stringify(payload); - console.log(`[AccountApi] Updating user account ${url}: ${body}`); - await fetchOrThrow(url, { - method: "PATCH", - headers: withBearerAuth({}, session.token()), - body, - }); - } - - async addSubscription(baseUrl, topic) { - const url = accountSubscriptionUrl(config.base_url); - const body = JSON.stringify({ - base_url: baseUrl, - topic, - }); - console.log(`[AccountApi] Adding user subscription ${url}: ${body}`); - const response = await fetchOrThrow(url, { - method: "POST", - headers: withBearerAuth({}, session.token()), - body, - }); - const subscription = await response.json(); // May throw SyntaxError - console.log(`[AccountApi] Subscription`, subscription); - return subscription; - } - - async updateSubscription(baseUrl, topic, payload) { - const url = accountSubscriptionUrl(config.base_url); - const body = JSON.stringify({ - base_url: baseUrl, - topic, - ...payload, - }); - console.log(`[AccountApi] Updating user subscription ${url}: ${body}`); - const response = await fetchOrThrow(url, { - method: "PATCH", - headers: withBearerAuth({}, session.token()), - body, - }); - const subscription = await response.json(); // May throw SyntaxError - console.log(`[AccountApi] Subscription`, subscription); - return subscription; - } - - async deleteSubscription(baseUrl, topic) { - const url = accountSubscriptionUrl(config.base_url); - console.log(`[AccountApi] Removing user subscription ${url}`); - const headers = { - "X-BaseURL": baseUrl, - "X-Topic": topic, - }; - await fetchOrThrow(url, { - method: "DELETE", - headers: withBearerAuth(headers, session.token()), - }); - } - - async upsertReservation(topic, everyone) { - const url = accountReservationUrl(config.base_url); - console.log(`[AccountApi] Upserting user access to topic ${topic}, everyone=${everyone}`); - await fetchOrThrow(url, { - method: "POST", - headers: withBearerAuth({}, session.token()), - body: JSON.stringify({ - topic, - everyone, - }), - }); - } - - async deleteReservation(topic, deleteMessages) { - const url = accountReservationSingleUrl(config.base_url, topic); - console.log(`[AccountApi] Removing topic reservation ${url}`); - const headers = { - "X-Delete-Messages": deleteMessages ? "true" : "false", - }; - await fetchOrThrow(url, { - method: "DELETE", - headers: withBearerAuth(headers, session.token()), - }); - } - - async billingTiers() { - if (this.tiers) { - return this.tiers; - } - const url = tiersUrl(config.base_url); - console.log(`[AccountApi] Fetching billing tiers`); - const response = await fetchOrThrow(url); // No auth needed! - this.tiers = await response.json(); // May throw SyntaxError - return this.tiers; - } - - async createBillingSubscription(tier, interval) { - console.log(`[AccountApi] Creating billing subscription with ${tier} and interval ${interval}`); - return this.upsertBillingSubscription("POST", tier, interval); - } - - async updateBillingSubscription(tier, interval) { - console.log(`[AccountApi] Updating billing subscription with ${tier} and interval ${interval}`); - return this.upsertBillingSubscription("PUT", tier, interval); - } - - async upsertBillingSubscription(method, tier, interval) { - const url = accountBillingSubscriptionUrl(config.base_url); - const response = await fetchOrThrow(url, { - method, - headers: withBearerAuth({}, session.token()), - body: JSON.stringify({ - tier, - interval, - }), - }); - return response.json(); // May throw SyntaxError - } - - async deleteBillingSubscription() { - const url = accountBillingSubscriptionUrl(config.base_url); - console.log(`[AccountApi] Cancelling billing subscription`); - await fetchOrThrow(url, { - method: "DELETE", - headers: withBearerAuth({}, session.token()), - }); - } - - async createBillingPortalSession() { - const url = accountBillingPortalUrl(config.base_url); - console.log(`[AccountApi] Creating billing portal session`); - const response = await fetchOrThrow(url, { - method: "POST", - headers: withBearerAuth({}, session.token()), - }); - return response.json(); // May throw SyntaxError - } - - async verifyPhoneNumber(phoneNumber, channel) { - const url = accountPhoneVerifyUrl(config.base_url); - console.log(`[AccountApi] Sending phone verification ${url}`); - await fetchOrThrow(url, { - method: "PUT", - headers: withBearerAuth({}, session.token()), - body: JSON.stringify({ - number: phoneNumber, - channel, - }), - }); - } - - async addPhoneNumber(phoneNumber, code) { - const url = accountPhoneUrl(config.base_url); - console.log(`[AccountApi] Adding phone number with verification code ${url}`); - await fetchOrThrow(url, { - method: "PUT", - headers: withBearerAuth({}, session.token()), - body: JSON.stringify({ - number: phoneNumber, - code, - }), - }); - } - - async deletePhoneNumber(phoneNumber) { - const url = accountPhoneUrl(config.base_url); - console.log(`[AccountApi] Deleting phone number ${url}`); - await fetchOrThrow(url, { - method: "DELETE", - headers: withBearerAuth({}, session.token()), - body: JSON.stringify({ - number: phoneNumber, - }), - }); - } - - async sync() { - try { - if (!session.token()) { - return null; - } - console.log(`[AccountApi] Syncing account`); - const account = await this.get(); - if (account.language) { - await i18n.changeLanguage(account.language); - } - if (account.notification) { - if (account.notification.sound) { - await prefs.setSound(account.notification.sound); + async login(user) { + const url = accountTokenUrl(config.base_url); + console.log(`[AccountApi] Checking auth for ${url}`); + const response = await fetchOrThrow(url, { + method: "POST", + headers: withBasicAuth({}, user.username, user.password) + }); + const json = await response.json(); // May throw SyntaxError + if (!json.token) { + throw new Error(`Unexpected server response: Cannot find token`); } - if (account.notification.delete_after) { - await prefs.setDeleteAfter(account.notification.delete_after); - } - if (account.notification.min_priority) { - await prefs.setMinPriority(account.notification.min_priority); - } - } - if (account.subscriptions) { - await subscriptionManager.syncFromRemote(account.subscriptions, account.reservations); - } - return account; - } catch (e) { - console.log(`[AccountApi] Error fetching account`, e); - if (e instanceof UnauthorizedError) { - session.resetAndRedirect(routes.login); - } - return undefined; + return json.token; } - } - startWorker() { - if (this.timer !== null) { - return; + async logout() { + const url = accountTokenUrl(config.base_url); + console.log(`[AccountApi] Logging out from ${url} using token ${session.token()}`); + await fetchOrThrow(url, { + method: "DELETE", + headers: withBearerAuth({}, session.token()) + }); } - console.log(`[AccountApi] Starting worker`); - this.timer = setInterval(() => this.runWorker(), intervalMillis); - setTimeout(() => this.runWorker(), delayMillis); - } - async runWorker() { - if (!session.token()) { - return; + async create(username, password) { + const url = accountUrl(config.base_url); + const body = JSON.stringify({ + username: username, + password: password + }); + console.log(`[AccountApi] Creating user account ${url}`); + await fetchOrThrow(url, { + method: "POST", + body: body + }); } - console.log(`[AccountApi] Extending user access token`); - try { - await this.extendToken(); - } catch (e) { - console.log(`[AccountApi] Error extending user access token`, e); + + async get() { + const url = accountUrl(config.base_url); + console.log(`[AccountApi] Fetching user account ${url}`); + const response = await fetchOrThrow(url, { + headers: maybeWithBearerAuth({}, session.token()) // GET /v1/account endpoint can be called by anonymous + }); + const account = await response.json(); // May throw SyntaxError + console.log(`[AccountApi] Account`, account); + if (this.listener) { + this.listener(account); + } + return account; + } + + async delete(password) { + const url = accountUrl(config.base_url); + console.log(`[AccountApi] Deleting user account ${url}`); + await fetchOrThrow(url, { + method: "DELETE", + headers: withBearerAuth({}, session.token()), + body: JSON.stringify({ + password: password + }) + }); + } + + async changePassword(currentPassword, newPassword) { + const url = accountPasswordUrl(config.base_url); + console.log(`[AccountApi] Changing account password ${url}`); + await fetchOrThrow(url, { + method: "POST", + headers: withBearerAuth({}, session.token()), + body: JSON.stringify({ + password: currentPassword, + new_password: newPassword + }) + }); + } + + async createToken(label, expires) { + const url = accountTokenUrl(config.base_url); + const body = { + label: label, + expires: (expires > 0) ? Math.floor(Date.now() / 1000) + expires : 0 + }; + console.log(`[AccountApi] Creating user access token ${url}`); + await fetchOrThrow(url, { + method: "POST", + headers: withBearerAuth({}, session.token()), + body: JSON.stringify(body) + }); + } + + async updateToken(token, label, expires) { + const url = accountTokenUrl(config.base_url); + const body = { + token: token, + label: label + }; + if (expires > 0) { + body.expires = Math.floor(Date.now() / 1000) + expires; + } + console.log(`[AccountApi] Creating user access token ${url}`); + await fetchOrThrow(url, { + method: "PATCH", + headers: withBearerAuth({}, session.token()), + body: JSON.stringify(body) + }); + } + + async extendToken() { + const url = accountTokenUrl(config.base_url); + console.log(`[AccountApi] Extending user access token ${url}`); + await fetchOrThrow(url, { + method: "PATCH", + headers: withBearerAuth({}, session.token()) + }); + } + + async deleteToken(token) { + const url = accountTokenUrl(config.base_url); + console.log(`[AccountApi] Deleting user access token ${url}`); + await fetchOrThrow(url, { + method: "DELETE", + headers: withBearerAuth({"X-Token": token}, session.token()) + }); + } + + async updateSettings(payload) { + const url = accountSettingsUrl(config.base_url); + const body = JSON.stringify(payload); + console.log(`[AccountApi] Updating user account ${url}: ${body}`); + await fetchOrThrow(url, { + method: "PATCH", + headers: withBearerAuth({}, session.token()), + body: body + }); + } + + async addSubscription(baseUrl, topic) { + const url = accountSubscriptionUrl(config.base_url); + const body = JSON.stringify({ + base_url: baseUrl, + topic: topic + }); + console.log(`[AccountApi] Adding user subscription ${url}: ${body}`); + const response = await fetchOrThrow(url, { + method: "POST", + headers: withBearerAuth({}, session.token()), + body: body + }); + const subscription = await response.json(); // May throw SyntaxError + console.log(`[AccountApi] Subscription`, subscription); + return subscription; + } + + async updateSubscription(baseUrl, topic, payload) { + const url = accountSubscriptionUrl(config.base_url); + const body = JSON.stringify({ + base_url: baseUrl, + topic: topic, + ...payload + }); + console.log(`[AccountApi] Updating user subscription ${url}: ${body}`); + const response = await fetchOrThrow(url, { + method: "PATCH", + headers: withBearerAuth({}, session.token()), + body: body + }); + const subscription = await response.json(); // May throw SyntaxError + console.log(`[AccountApi] Subscription`, subscription); + return subscription; + } + + async deleteSubscription(baseUrl, topic) { + const url = accountSubscriptionUrl(config.base_url); + console.log(`[AccountApi] Removing user subscription ${url}`); + const headers = { + "X-BaseURL": baseUrl, + "X-Topic": topic, + } + await fetchOrThrow(url, { + method: "DELETE", + headers: withBearerAuth(headers, session.token()), + }); + } + + async upsertReservation(topic, everyone) { + const url = accountReservationUrl(config.base_url); + console.log(`[AccountApi] Upserting user access to topic ${topic}, everyone=${everyone}`); + await fetchOrThrow(url, { + method: "POST", + headers: withBearerAuth({}, session.token()), + body: JSON.stringify({ + topic: topic, + everyone: everyone + }) + }); + } + + async deleteReservation(topic, deleteMessages) { + const url = accountReservationSingleUrl(config.base_url, topic); + console.log(`[AccountApi] Removing topic reservation ${url}`); + const headers = { + "X-Delete-Messages": deleteMessages ? "true" : "false" + } + await fetchOrThrow(url, { + method: "DELETE", + headers: withBearerAuth(headers, session.token()) + }); + } + + async billingTiers() { + if (this.tiers) { + return this.tiers; + } + const url = tiersUrl(config.base_url); + console.log(`[AccountApi] Fetching billing tiers`); + const response = await fetchOrThrow(url); // No auth needed! + this.tiers = await response.json(); // May throw SyntaxError + return this.tiers; + } + + async createBillingSubscription(tier, interval) { + console.log(`[AccountApi] Creating billing subscription with ${tier} and interval ${interval}`); + return await this.upsertBillingSubscription("POST", tier, interval) + } + + async updateBillingSubscription(tier, interval) { + console.log(`[AccountApi] Updating billing subscription with ${tier} and interval ${interval}`); + return await this.upsertBillingSubscription("PUT", tier, interval) + } + + async upsertBillingSubscription(method, tier, interval) { + const url = accountBillingSubscriptionUrl(config.base_url); + const response = await fetchOrThrow(url, { + method: method, + headers: withBearerAuth({}, session.token()), + body: JSON.stringify({ + tier: tier, + interval: interval + }) + }); + return await response.json(); // May throw SyntaxError + } + + async deleteBillingSubscription() { + const url = accountBillingSubscriptionUrl(config.base_url); + console.log(`[AccountApi] Cancelling billing subscription`); + await fetchOrThrow(url, { + method: "DELETE", + headers: withBearerAuth({}, session.token()) + }); + } + + async createBillingPortalSession() { + const url = accountBillingPortalUrl(config.base_url); + console.log(`[AccountApi] Creating billing portal session`); + const response = await fetchOrThrow(url, { + method: "POST", + headers: withBearerAuth({}, session.token()) + }); + return await response.json(); // May throw SyntaxError + } + + async sync() { + try { + if (!session.token()) { + return null; + } + console.log(`[AccountApi] Syncing account`); + const account = await this.get(); + if (account.language) { + await i18n.changeLanguage(account.language); + } + if (account.notification) { + if (account.notification.sound) { + await prefs.setSound(account.notification.sound); + } + if (account.notification.delete_after) { + await prefs.setDeleteAfter(account.notification.delete_after); + } + if (account.notification.min_priority) { + await prefs.setMinPriority(account.notification.min_priority); + } + } + if (account.subscriptions) { + await subscriptionManager.syncFromRemote(account.subscriptions, account.reservations); + } + return account; + } catch (e) { + console.log(`[AccountApi] Error fetching account`, e); + if (e instanceof UnauthorizedError) { + session.resetAndRedirect(routes.login); + } + } + } + + startWorker() { + if (this.timer !== null) { + return; + } + console.log(`[AccountApi] Starting worker`); + this.timer = setInterval(() => this.runWorker(), intervalMillis); + setTimeout(() => this.runWorker(), delayMillis); + } + + async runWorker() { + if (!session.token()) { + return; + } + console.log(`[AccountApi] Extending user access token`); + try { + await this.extendToken(); + } catch (e) { + console.log(`[AccountApi] Error extending user access token`, e); + } } - } } // Maps to user.Role in user/types.go export const Role = { - ADMIN: "admin", - USER: "user", + ADMIN: "admin", + USER: "user" }; // Maps to server.visitorLimitBasis in server/visitor.go export const LimitBasis = { - IP: "ip", - TIER: "tier", + IP: "ip", + TIER: "tier" }; // Maps to stripe.SubscriptionStatus export const SubscriptionStatus = { - ACTIVE: "active", - PAST_DUE: "past_due", + ACTIVE: "active", + PAST_DUE: "past_due" }; // Maps to stripe.PriceRecurringInterval export const SubscriptionInterval = { - MONTH: "month", - YEAR: "year", + MONTH: "month", + YEAR: "year" }; // Maps to user.Permission in user/types.go export const Permission = { - READ_WRITE: "read-write", - READ_ONLY: "read-only", - WRITE_ONLY: "write-only", - DENY_ALL: "deny-all", + READ_WRITE: "read-write", + READ_ONLY: "read-only", + WRITE_ONLY: "write-only", + DENY_ALL: "deny-all" }; const accountApi = new AccountApi(); diff --git a/web/src/app/Api.js b/web/src/app/Api.js index ba1cbe6..3d20d92 100644 --- a/web/src/app/Api.js +++ b/web/src/app/Api.js @@ -1,118 +1,115 @@ import { - fetchLinesIterator, - maybeWithAuth, - topicShortUrl, - topicUrl, - topicUrlAuth, - topicUrlJsonPoll, - topicUrlJsonPollWithSince, + fetchLinesIterator, + maybeWithAuth, + topicShortUrl, + topicUrl, + topicUrlAuth, + topicUrlJsonPoll, + topicUrlJsonPollWithSince } from "./utils"; import userManager from "./UserManager"; -import { fetchOrThrow } from "./errors"; +import {fetchOrThrow} from "./errors"; class Api { - async poll(baseUrl, topic, since) { - const user = await userManager.get(baseUrl); - const shortUrl = topicShortUrl(baseUrl, topic); - const url = since ? topicUrlJsonPollWithSince(baseUrl, topic, since) : topicUrlJsonPoll(baseUrl, topic); - const messages = []; - const headers = maybeWithAuth({}, user); - console.log(`[Api] Polling ${url}`); - for await (const line of fetchLinesIterator(url, headers)) { - const message = JSON.parse(line); - if (message.id) { - console.log(`[Api, ${shortUrl}] Received message ${line}`); - messages.push(message); - } - } - return messages; - } - - async publish(baseUrl, topic, message, options) { - const user = await userManager.get(baseUrl); - console.log(`[Api] Publishing message to ${topicUrl(baseUrl, topic)}`); - const headers = {}; - const body = { - topic, - message, - ...options, - }; - await fetchOrThrow(baseUrl, { - method: "PUT", - body: JSON.stringify(body), - headers: maybeWithAuth(headers, user), - }); - } - - /** - * Publishes to a topic using XMLHttpRequest (XHR), and returns a Promise with the active request. - * Unfortunately, fetch() does not support a progress hook, which is why XHR has to be used. - * - * Firefox XHR bug: - * Firefox has a bug(?), which returns 0 and "" for all fields of the XHR response in the case of an error, - * so we cannot determine the exact error. It also sometimes complains about CORS violations, even when the - * correct headers are clearly set. It's quite the odd behavior. - * - * There is an example, and the bug report here: - * - https://bugzilla.mozilla.org/show_bug.cgi?id=1733755 - * - https://gist.github.com/binwiederhier/627f146d1959799be207ad8c17a8f345 - */ - publishXHR(url, body, headers, onProgress) { - console.log(`[Api] Publishing message to ${url}`); - const xhr = new XMLHttpRequest(); - const send = new Promise((resolve, reject) => { - xhr.open("PUT", url); - if (body.type) { - xhr.overrideMimeType(body.type); - } - for (const [key, value] of Object.entries(headers)) { - xhr.setRequestHeader(key, value); - } - xhr.upload.addEventListener("progress", onProgress); - xhr.addEventListener("readystatechange", () => { - if (xhr.readyState === 4 && xhr.status >= 200 && xhr.status <= 299) { - console.log(`[Api] Publish successful (HTTP ${xhr.status})`, xhr.response); - resolve(xhr.response); - } else if (xhr.readyState === 4) { - // Firefox bug; see description above! - console.log(`[Api] Publish failed (HTTP ${xhr.status})`, xhr.responseText); - let errorText; - try { - const error = JSON.parse(xhr.responseText); - if (error.code && error.error) { - errorText = `Error ${error.code}: ${error.error}`; - } - } catch (e) { - // Nothing - } - xhr.abort(); - reject(errorText ?? "An error occurred"); + async poll(baseUrl, topic, since) { + const user = await userManager.get(baseUrl); + const shortUrl = topicShortUrl(baseUrl, topic); + const url = (since) + ? topicUrlJsonPollWithSince(baseUrl, topic, since) + : topicUrlJsonPoll(baseUrl, topic); + const messages = []; + const headers = maybeWithAuth({}, user); + console.log(`[Api] Polling ${url}`); + for await (let line of fetchLinesIterator(url, headers)) { + console.log(`[Api, ${shortUrl}] Received message ${line}`); + messages.push(JSON.parse(line)); } - }); - xhr.send(body); - }); - send.abort = () => { - console.log(`[Api] Publish aborted by user`); - xhr.abort(); - }; - return send; - } + return messages; + } - async topicAuth(baseUrl, topic, user) { - const url = topicUrlAuth(baseUrl, topic); - console.log(`[Api] Checking auth for ${url}`); - const response = await fetch(url, { - headers: maybeWithAuth({}, user), - }); - if (response.status >= 200 && response.status <= 299) { - return true; + async publish(baseUrl, topic, message, options) { + const user = await userManager.get(baseUrl); + console.log(`[Api] Publishing message to ${topicUrl(baseUrl, topic)}`); + const headers = {}; + const body = { + topic: topic, + message: message, + ...options + }; + await fetchOrThrow(baseUrl, { + method: 'PUT', + body: JSON.stringify(body), + headers: maybeWithAuth(headers, user) + }); } - if (response.status === 401 || response.status === 403) { - // See server/server.go - return false; + + /** + * Publishes to a topic using XMLHttpRequest (XHR), and returns a Promise with the active request. + * Unfortunately, fetch() does not support a progress hook, which is why XHR has to be used. + * + * Firefox XHR bug: + * Firefox has a bug(?), which returns 0 and "" for all fields of the XHR response in the case of an error, + * so we cannot determine the exact error. It also sometimes complains about CORS violations, even when the + * correct headers are clearly set. It's quite the odd behavior. + * + * There is an example, and the bug report here: + * - https://bugzilla.mozilla.org/show_bug.cgi?id=1733755 + * - https://gist.github.com/binwiederhier/627f146d1959799be207ad8c17a8f345 + */ + publishXHR(url, body, headers, onProgress) { + console.log(`[Api] Publishing message to ${url}`); + const xhr = new XMLHttpRequest(); + const send = new Promise(function (resolve, reject) { + xhr.open("PUT", url); + if (body.type) { + xhr.overrideMimeType(body.type); + } + for (const [key, value] of Object.entries(headers)) { + xhr.setRequestHeader(key, value); + } + xhr.upload.addEventListener("progress", onProgress); + xhr.addEventListener('readystatechange', () => { + if (xhr.readyState === 4 && xhr.status >= 200 && xhr.status <= 299) { + console.log(`[Api] Publish successful (HTTP ${xhr.status})`, xhr.response); + resolve(xhr.response); + } else if (xhr.readyState === 4) { + // Firefox bug; see description above! + console.log(`[Api] Publish failed (HTTP ${xhr.status})`, xhr.responseText); + let errorText; + try { + const error = JSON.parse(xhr.responseText); + if (error.code && error.error) { + errorText = `Error ${error.code}: ${error.error}`; + } + } catch (e) { + // Nothing + } + xhr.abort(); + reject(errorText ?? "An error occurred"); + } + }) + xhr.send(body); + }); + send.abort = () => { + console.log(`[Api] Publish aborted by user`); + xhr.abort(); + } + return send; + } + + async topicAuth(baseUrl, topic, user) { + const url = topicUrlAuth(baseUrl, topic); + console.log(`[Api] Checking auth for ${url}`); + const response = await fetch(url, { + headers: maybeWithAuth({}, user) + }); + if (response.status >= 200 && response.status <= 299) { + return true; + } else if (response.status === 401 || response.status === 403) { // See server/server.go + return false; + } + throw new Error(`Unexpected server response ${response.status}`); } - throw new Error(`Unexpected server response ${response.status}`); - } } const api = new Api(); diff --git a/web/src/app/Connection.js b/web/src/app/Connection.js index 5358cdd..8b79537 100644 --- a/web/src/app/Connection.js +++ b/web/src/app/Connection.js @@ -1,13 +1,6 @@ -/* eslint-disable max-classes-per-file */ -import { basicAuth, bearerAuth, encodeBase64Url, topicShortUrl, topicUrlWs } from "./utils"; +import {basicAuth, bearerAuth, encodeBase64Url, topicShortUrl, topicUrlWs} from "./utils"; -const retryBackoffSeconds = [5, 10, 20, 30, 60, 120]; - -export class ConnectionState { - static Connected = "connected"; - - static Connecting = "connecting"; -} +const retryBackoffSeconds = [5, 10, 15, 20, 30]; /** * A connection contains a single WebSocket connection for one topic. It handles its connection @@ -16,103 +9,110 @@ export class ConnectionState { * Incoming messages and state changes are forwarded via listeners. */ class Connection { - constructor(connectionId, subscriptionId, baseUrl, topic, user, since, onNotification, onStateChanged) { - this.connectionId = connectionId; - this.subscriptionId = subscriptionId; - this.baseUrl = baseUrl; - this.topic = topic; - this.user = user; - this.since = since; - this.shortUrl = topicShortUrl(baseUrl, topic); - this.onNotification = onNotification; - this.onStateChanged = onStateChanged; - this.ws = null; - this.retryCount = 0; - this.retryTimeout = null; - } - - start() { - // Don't fetch old messages; we do that as a poll() when adding a subscription; - // we don't want to re-trigger the main view re-render potentially hundreds of times. - - const wsUrl = this.wsUrl(); - console.log(`[Connection, ${this.shortUrl}, ${this.connectionId}] Opening connection to ${wsUrl}`); - - this.ws = new WebSocket(wsUrl); - this.ws.onopen = (event) => { - console.log(`[Connection, ${this.shortUrl}, ${this.connectionId}] Connection established`, event); - this.retryCount = 0; - this.onStateChanged(this.subscriptionId, ConnectionState.Connected); - }; - this.ws.onmessage = (event) => { - console.log(`[Connection, ${this.shortUrl}, ${this.connectionId}] Message received from server: ${event.data}`); - try { - const data = JSON.parse(event.data); - if (data.event === "open") { - return; - } - const relevantAndValid = data.event === "message" && "id" in data && "time" in data && "message" in data; - if (!relevantAndValid) { - console.log(`[Connection, ${this.shortUrl}, ${this.connectionId}] Unexpected message. Ignoring.`); - return; - } - this.since = data.id; - this.onNotification(this.subscriptionId, data); - } catch (e) { - console.log(`[Connection, ${this.shortUrl}, ${this.connectionId}] Error handling message: ${e}`); - } - }; - this.ws.onclose = (event) => { - if (event.wasClean) { - console.log( - `[Connection, ${this.shortUrl}, ${this.connectionId}] Connection closed cleanly, code=${event.code} reason=${event.reason}` - ); + constructor(connectionId, subscriptionId, baseUrl, topic, user, since, onNotification, onStateChanged) { + this.connectionId = connectionId; + this.subscriptionId = subscriptionId; + this.baseUrl = baseUrl; + this.topic = topic; + this.user = user; + this.since = since; + this.shortUrl = topicShortUrl(baseUrl, topic); + this.onNotification = onNotification; + this.onStateChanged = onStateChanged; this.ws = null; - } else { - const retrySeconds = retryBackoffSeconds[Math.min(this.retryCount, retryBackoffSeconds.length - 1)]; - this.retryCount += 1; - console.log(`[Connection, ${this.shortUrl}, ${this.connectionId}] Connection died, retrying in ${retrySeconds} seconds`); - this.retryTimeout = setTimeout(() => this.start(), retrySeconds * 1000); - this.onStateChanged(this.subscriptionId, ConnectionState.Connecting); - } - }; - this.ws.onerror = (event) => { - console.log(`[Connection, ${this.shortUrl}, ${this.connectionId}] Error occurred: ${event}`, event); - }; - } + this.retryCount = 0; + this.retryTimeout = null; + } - close() { - console.log(`[Connection, ${this.shortUrl}, ${this.connectionId}] Closing connection`); - const socket = this.ws; - const { retryTimeout } = this; - if (socket !== null) { - socket.close(); - } - if (retryTimeout !== null) { - clearTimeout(retryTimeout); - } - this.retryTimeout = null; - this.ws = null; - } + start() { + // Don't fetch old messages; we do that as a poll() when adding a subscription; + // we don't want to re-trigger the main view re-render potentially hundreds of times. - wsUrl() { - const params = []; - if (this.since) { - params.push(`since=${this.since}`); - } - if (this.user) { - params.push(`auth=${this.authParam()}`); - } - const wsUrl = topicUrlWs(this.baseUrl, this.topic); - return params.length === 0 ? wsUrl : `${wsUrl}?${params.join("&")}`; - } + const wsUrl = this.wsUrl(); + console.log(`[Connection, ${this.shortUrl}, ${this.connectionId}] Opening connection to ${wsUrl}`); - authParam() { - if (this.user.password) { - return encodeBase64Url(basicAuth(this.user.username, this.user.password)); + this.ws = new WebSocket(wsUrl); + this.ws.onopen = (event) => { + console.log(`[Connection, ${this.shortUrl}, ${this.connectionId}] Connection established`, event); + this.retryCount = 0; + this.onStateChanged(this.subscriptionId, ConnectionState.Connected); + } + this.ws.onmessage = (event) => { + console.log(`[Connection, ${this.shortUrl}, ${this.connectionId}] Message received from server: ${event.data}`); + try { + const data = JSON.parse(event.data); + if (data.event === 'open') { + return; + } + const relevantAndValid = + data.event === 'message' && + 'id' in data && + 'time' in data && + 'message' in data; + if (!relevantAndValid) { + console.log(`[Connection, ${this.shortUrl}, ${this.connectionId}] Unexpected message. Ignoring.`); + return; + } + this.since = data.id; + this.onNotification(this.subscriptionId, data); + } catch (e) { + console.log(`[Connection, ${this.shortUrl}, ${this.connectionId}] Error handling message: ${e}`); + } + }; + this.ws.onclose = (event) => { + if (event.wasClean) { + console.log(`[Connection, ${this.shortUrl}, ${this.connectionId}] Connection closed cleanly, code=${event.code} reason=${event.reason}`); + this.ws = null; + } else { + const retrySeconds = retryBackoffSeconds[Math.min(this.retryCount, retryBackoffSeconds.length-1)]; + this.retryCount++; + console.log(`[Connection, ${this.shortUrl}, ${this.connectionId}] Connection died, retrying in ${retrySeconds} seconds`); + this.retryTimeout = setTimeout(() => this.start(), retrySeconds * 1000); + this.onStateChanged(this.subscriptionId, ConnectionState.Connecting); + } + }; + this.ws.onerror = (event) => { + console.log(`[Connection, ${this.shortUrl}, ${this.connectionId}] Error occurred: ${event}`, event); + }; } - return encodeBase64Url(bearerAuth(this.user.token)); - } + + close() { + console.log(`[Connection, ${this.shortUrl}, ${this.connectionId}] Closing connection`); + const socket = this.ws; + const retryTimeout = this.retryTimeout; + if (socket !== null) { + socket.close(); + } + if (retryTimeout !== null) { + clearTimeout(retryTimeout); + } + this.retryTimeout = null; + this.ws = null; + } + + wsUrl() { + const params = []; + if (this.since) { + params.push(`since=${this.since}`); + } + if (this.user) { + params.push(`auth=${this.authParam()}`); + } + const wsUrl = topicUrlWs(this.baseUrl, this.topic); + return (params.length === 0) ? wsUrl : `${wsUrl}?${params.join('&')}`; + } + + authParam() { + if (this.user.password) { + return encodeBase64Url(basicAuth(this.user.username, this.user.password)); + } + return encodeBase64Url(bearerAuth(this.user.token)); + } +} + +export class ConnectionState { + static Connected = "connected"; + static Connecting = "connecting"; } export default Connection; diff --git a/web/src/app/ConnectionManager.js b/web/src/app/ConnectionManager.js index 2033cbe..1e805eb 100644 --- a/web/src/app/ConnectionManager.js +++ b/web/src/app/ConnectionManager.js @@ -1,8 +1,5 @@ import Connection from "./Connection"; -import { hashCode } from "./utils"; - -const makeConnectionId = async (subscription, user) => - user ? hashCode(`${subscription.id}|${user.username}|${user.password ?? ""}|${user.token ?? ""}`) : hashCode(`${subscription.id}`); +import {hashCode} from "./utils"; /** * The connection manager keeps track of active connections (WebSocket connections, see Connection). @@ -11,106 +8,109 @@ const makeConnectionId = async (subscription, user) => * as required. This is done pretty much exactly the same way as in the Android app. */ class ConnectionManager { - constructor() { - this.connections = new Map(); // ConnectionId -> Connection (hash, see below) - this.stateListener = null; // Fired when connection state changes - this.messageListener = null; // Fired when new notifications arrive - } - - registerStateListener(listener) { - this.stateListener = listener; - } - - resetStateListener() { - this.stateListener = null; - } - - registerMessageListener(listener) { - this.messageListener = listener; - } - - resetMessageListener() { - this.messageListener = null; - } - - /** - * This function figures out which websocket connections should be running by comparing the - * current state of the world (connections) with the target state (targetIds). - * - * It uses a "connectionId", which is sha256($subscriptionId|$username|$password) to identify - * connections. If any of them change, the connection is closed/replaced. - */ - async refresh(subscriptions, users) { - if (!subscriptions || !users) { - return; + constructor() { + this.connections = new Map(); // ConnectionId -> Connection (hash, see below) + this.stateListener = null; // Fired when connection state changes + this.messageListener = null; // Fired when new notifications arrive } - console.log(`[ConnectionManager] Refreshing connections`); - const subscriptionsWithUsersAndConnectionId = await Promise.all( - subscriptions.map(async (s) => { - const [user] = users.filter((u) => u.baseUrl === s.baseUrl); - const connectionId = await makeConnectionId(s, user); - return { ...s, user, connectionId }; - }) - ); - const targetIds = subscriptionsWithUsersAndConnectionId.map((s) => s.connectionId); - const deletedIds = Array.from(this.connections.keys()).filter((id) => !targetIds.includes(id)); - // Create and add new connections - subscriptionsWithUsersAndConnectionId.forEach((subscription) => { - const subscriptionId = subscription.id; - const { connectionId } = subscription; - const added = !this.connections.get(connectionId); - if (added) { - const { baseUrl, topic, user } = subscription; - const since = subscription.last; - const connection = new Connection( - connectionId, - subscriptionId, - baseUrl, - topic, - user, - since, - (subId, notification) => this.notificationReceived(subId, notification), - (subId, state) => this.stateChanged(subId, state) - ); - this.connections.set(connectionId, connection); - console.log( - `[ConnectionManager] Starting new connection ${connectionId} (subscription ${subscriptionId} with user ${ - user ? user.username : "anonymous" - })` - ); - connection.start(); - } - }); - - // Delete old connections - deletedIds.forEach((id) => { - console.log(`[ConnectionManager] Closing connection ${id}`); - const connection = this.connections.get(id); - this.connections.delete(id); - connection.close(); - }); - } - - stateChanged(subscriptionId, state) { - if (this.stateListener) { - try { - this.stateListener(subscriptionId, state); - } catch (e) { - console.error(`[ConnectionManager] Error updating state of ${subscriptionId} to ${state}`, e); - } + registerStateListener(listener) { + this.stateListener = listener; } - } - notificationReceived(subscriptionId, notification) { - if (this.messageListener) { - try { - this.messageListener(subscriptionId, notification); - } catch (e) { - console.error(`[ConnectionManager] Error handling notification for ${subscriptionId}`, e); - } + resetStateListener() { + this.stateListener = null; } - } + + registerMessageListener(listener) { + this.messageListener = listener; + } + + resetMessageListener() { + this.messageListener = null; + } + + /** + * This function figures out which websocket connections should be running by comparing the + * current state of the world (connections) with the target state (targetIds). + * + * It uses a "connectionId", which is sha256($subscriptionId|$username|$password) to identify + * connections. If any of them change, the connection is closed/replaced. + */ + async refresh(subscriptions, users) { + if (!subscriptions || !users) { + return; + } + console.log(`[ConnectionManager] Refreshing connections`); + const subscriptionsWithUsersAndConnectionId = await Promise.all(subscriptions + .map(async s => { + const [user] = users.filter(u => u.baseUrl === s.baseUrl); + const connectionId = await makeConnectionId(s, user); + return {...s, user, connectionId}; + })); + const targetIds = subscriptionsWithUsersAndConnectionId.map(s => s.connectionId); + const deletedIds = Array.from(this.connections.keys()).filter(id => !targetIds.includes(id)); + + // Create and add new connections + subscriptionsWithUsersAndConnectionId.forEach(subscription => { + const subscriptionId = subscription.id; + const connectionId = subscription.connectionId; + const added = !this.connections.get(connectionId) + if (added) { + const baseUrl = subscription.baseUrl; + const topic = subscription.topic; + const user = subscription.user; + const since = subscription.last; + const connection = new Connection( + connectionId, + subscriptionId, + baseUrl, + topic, + user, + since, + (subscriptionId, notification) => this.notificationReceived(subscriptionId, notification), + (subscriptionId, state) => this.stateChanged(subscriptionId, state) + ); + this.connections.set(connectionId, connection); + console.log(`[ConnectionManager] Starting new connection ${connectionId} (subscription ${subscriptionId} with user ${user ? user.username : "anonymous"})`); + connection.start(); + } + }); + + // Delete old connections + deletedIds.forEach(id => { + console.log(`[ConnectionManager] Closing connection ${id}`); + const connection = this.connections.get(id); + this.connections.delete(id); + connection.close(); + }); + } + + stateChanged(subscriptionId, state) { + if (this.stateListener) { + try { + this.stateListener(subscriptionId, state); + } catch (e) { + console.error(`[ConnectionManager] Error updating state of ${subscriptionId} to ${state}`, e); + } + } + } + + notificationReceived(subscriptionId, notification) { + if (this.messageListener) { + try { + this.messageListener(subscriptionId, notification); + } catch (e) { + console.error(`[ConnectionManager] Error handling notification for ${subscriptionId}`, e); + } + } + } +} + +const makeConnectionId = async (subscription, user) => { + return (user) + ? hashCode(`${subscription.id}|${user.username}|${user.password ?? ""}|${user.token ?? ""}`) + : hashCode(`${subscription.id}`); } const connectionManager = new ConnectionManager(); diff --git a/web/src/app/Notifier.js b/web/src/app/Notifier.js index 45792dc..613340c 100644 --- a/web/src/app/Notifier.js +++ b/web/src/app/Notifier.js @@ -1,4 +1,4 @@ -import { formatMessage, formatTitleWithDefault, openUrl, playSound, topicDisplayName, topicShortUrl } from "./utils"; +import {formatMessage, formatTitleWithDefault, openUrl, playSound, topicDisplayName, topicShortUrl} from "./utils"; import prefs from "./Prefs"; import subscriptionManager from "./SubscriptionManager"; import logo from "../img/ntfy.png"; @@ -8,87 +8,89 @@ import logo from "../img/ntfy.png"; * support this; most importantly, all iOS browsers do not support window.Notification. */ class Notifier { - async notify(subscriptionId, notification, onClickFallback) { - if (!this.supported()) { - return; - } - const subscription = await subscriptionManager.get(subscriptionId); - const shouldNotify = await this.shouldNotify(subscription, notification); - if (!shouldNotify) { - return; - } - const shortUrl = topicShortUrl(subscription.baseUrl, subscription.topic); - const displayName = topicDisplayName(subscription); - const message = formatMessage(notification); - const title = formatTitleWithDefault(notification, displayName); + async notify(subscriptionId, notification, onClickFallback) { + if (!this.supported()) { + return; + } + const subscription = await subscriptionManager.get(subscriptionId); + const shouldNotify = await this.shouldNotify(subscription, notification); + if (!shouldNotify) { + return; + } + const shortUrl = topicShortUrl(subscription.baseUrl, subscription.topic); + const displayName = topicDisplayName(subscription); + const message = formatMessage(notification); + const title = formatTitleWithDefault(notification, displayName); - // Show notification - console.log(`[Notifier, ${shortUrl}] Displaying notification ${notification.id}: ${message}`); - const n = new Notification(title, { - body: message, - icon: logo, - }); - if (notification.click) { - n.onclick = () => openUrl(notification.click); - } else { - n.onclick = () => onClickFallback(subscription); + // Show notification + console.log(`[Notifier, ${shortUrl}] Displaying notification ${notification.id}: ${message}`); + const n = new Notification(title, { + body: message, + icon: logo + }); + if (notification.click) { + n.onclick = (e) => openUrl(notification.click); + } else { + n.onclick = () => onClickFallback(subscription); + } + + // Play sound + const sound = await prefs.sound(); + if (sound && sound !== "none") { + try { + await playSound(sound); + } catch (e) { + console.log(`[Notifier, ${shortUrl}] Error playing audio`, e); + } + } } - // Play sound - const sound = await prefs.sound(); - if (sound && sound !== "none") { - try { - await playSound(sound); - } catch (e) { - console.log(`[Notifier, ${shortUrl}] Error playing audio`, e); - } + granted() { + return this.supported() && Notification.permission === 'granted'; } - } - granted() { - return this.supported() && Notification.permission === "granted"; - } - - maybeRequestPermission(cb) { - if (!this.supported()) { - cb(false); - return; + maybeRequestPermission(cb) { + if (!this.supported()) { + cb(false); + return; + } + if (!this.granted()) { + Notification.requestPermission().then((permission) => { + const granted = permission === 'granted'; + cb(granted); + }); + } } - if (!this.granted()) { - Notification.requestPermission().then((permission) => { - const granted = permission === "granted"; - cb(granted); - }); + + async shouldNotify(subscription, notification) { + if (subscription.mutedUntil === 1) { + return false; + } + const priority = (notification.priority) ? notification.priority : 3; + const minPriority = await prefs.minPriority(); + if (priority < minPriority) { + return false; + } + return true; } - } - async shouldNotify(subscription, notification) { - if (subscription.mutedUntil === 1) { - return false; + supported() { + return this.browserSupported() && this.contextSupported(); } - const priority = notification.priority ? notification.priority : 3; - const minPriority = await prefs.minPriority(); - if (priority < minPriority) { - return false; + + browserSupported() { + return 'Notification' in window; } - return true; - } - supported() { - return this.browserSupported() && this.contextSupported(); - } - - browserSupported() { - return "Notification" in window; - } - - /** - * Returns true if this is a HTTPS site, or served over localhost. Otherwise the Notification API - * is not supported, see https://developer.mozilla.org/en-US/docs/Web/API/notification - */ - contextSupported() { - return window.location.protocol === "https:" || window.location.hostname.match("^127.") || window.location.hostname === "localhost"; - } + /** + * Returns true if this is a HTTPS site, or served over localhost. Otherwise the Notification API + * is not supported, see https://developer.mozilla.org/en-US/docs/Web/API/notification + */ + contextSupported() { + return location.protocol === 'https:' + || location.hostname.match('^127.') + || location.hostname === 'localhost'; + } } const notifier = new Notifier(); diff --git a/web/src/app/Poller.js b/web/src/app/Poller.js index 372e46e..a7eed03 100644 --- a/web/src/app/Poller.js +++ b/web/src/app/Poller.js @@ -5,57 +5,54 @@ const delayMillis = 2000; // 2 seconds const intervalMillis = 300000; // 5 minutes class Poller { - constructor() { - this.timer = null; - } - - startWorker() { - if (this.timer !== null) { - return; + constructor() { + this.timer = null; } - console.log(`[Poller] Starting worker`); - this.timer = setInterval(() => this.pollAll(), intervalMillis); - setTimeout(() => this.pollAll(), delayMillis); - } - async pollAll() { - console.log(`[Poller] Polling all subscriptions`); - const subscriptions = await subscriptionManager.all(); - - await Promise.all( - subscriptions.map(async (s) => { - try { - await this.poll(s); - } catch (e) { - console.log(`[Poller] Error polling ${s.id}`, e); + startWorker() { + if (this.timer !== null) { + return; } - }) - ); - } - - async poll(subscription) { - console.log(`[Poller] Polling ${subscription.id}`); - - const since = subscription.last; - const notifications = await api.poll(subscription.baseUrl, subscription.topic, since); - if (!notifications || notifications.length === 0) { - console.log(`[Poller] No new notifications found for ${subscription.id}`); - return; + console.log(`[Poller] Starting worker`); + this.timer = setInterval(() => this.pollAll(), intervalMillis); + setTimeout(() => this.pollAll(), delayMillis); } - console.log(`[Poller] Adding ${notifications.length} notification(s) for ${subscription.id}`); - await subscriptionManager.addNotifications(subscription.id, notifications); - } - pollInBackground(subscription) { - const fn = async () => { - try { - await this.poll(subscription); - } catch (e) { - console.error(`[App] Error polling subscription ${subscription.id}`, e); - } - }; - setTimeout(() => fn(), 0); - } + async pollAll() { + console.log(`[Poller] Polling all subscriptions`); + const subscriptions = await subscriptionManager.all(); + for (const s of subscriptions) { + try { + await this.poll(s); + } catch (e) { + console.log(`[Poller] Error polling ${s.id}`, e); + } + } + } + + async poll(subscription) { + console.log(`[Poller] Polling ${subscription.id}`); + + const since = subscription.last; + const notifications = await api.poll(subscription.baseUrl, subscription.topic, since); + if (!notifications || notifications.length === 0) { + console.log(`[Poller] No new notifications found for ${subscription.id}`); + return; + } + console.log(`[Poller] Adding ${notifications.length} notification(s) for ${subscription.id}`); + await subscriptionManager.addNotifications(subscription.id, notifications); + } + + pollInBackground(subscription) { + const fn = async () => { + try { + await this.poll(subscription); + } catch (e) { + console.error(`[App] Error polling subscription ${subscription.id}`, e); + } + }; + setTimeout(() => fn(), 0); + } } const poller = new Poller(); diff --git a/web/src/app/Prefs.js b/web/src/app/Prefs.js index 8adc508..b444c6f 100644 --- a/web/src/app/Prefs.js +++ b/web/src/app/Prefs.js @@ -1,32 +1,32 @@ import db from "./db"; class Prefs { - async setSound(sound) { - db.prefs.put({ key: "sound", value: sound.toString() }); - } + async setSound(sound) { + db.prefs.put({key: 'sound', value: sound.toString()}); + } - async sound() { - const sound = await db.prefs.get("sound"); - return sound ? sound.value : "ding"; - } + async sound() { + const sound = await db.prefs.get('sound'); + return (sound) ? sound.value : "ding"; + } - async setMinPriority(minPriority) { - db.prefs.put({ key: "minPriority", value: minPriority.toString() }); - } + async setMinPriority(minPriority) { + db.prefs.put({key: 'minPriority', value: minPriority.toString()}); + } - async minPriority() { - const minPriority = await db.prefs.get("minPriority"); - return minPriority ? Number(minPriority.value) : 1; - } + async minPriority() { + const minPriority = await db.prefs.get('minPriority'); + return (minPriority) ? Number(minPriority.value) : 1; + } - async setDeleteAfter(deleteAfter) { - db.prefs.put({ key: "deleteAfter", value: deleteAfter.toString() }); - } + async setDeleteAfter(deleteAfter) { + db.prefs.put({key:'deleteAfter', value: deleteAfter.toString()}); + } - async deleteAfter() { - const deleteAfter = await db.prefs.get("deleteAfter"); - return deleteAfter ? Number(deleteAfter.value) : 604800; // Default is one week - } + async deleteAfter() { + const deleteAfter = await db.prefs.get('deleteAfter'); + return (deleteAfter) ? Number(deleteAfter.value) : 604800; // Default is one week + } } const prefs = new Prefs(); diff --git a/web/src/app/Pruner.js b/web/src/app/Pruner.js index 498c156..4594805 100644 --- a/web/src/app/Pruner.js +++ b/web/src/app/Pruner.js @@ -5,33 +5,33 @@ const delayMillis = 25000; // 25 seconds const intervalMillis = 1800000; // 30 minutes class Pruner { - constructor() { - this.timer = null; - } + constructor() { + this.timer = null; + } - startWorker() { - if (this.timer !== null) { - return; + startWorker() { + if (this.timer !== null) { + return; + } + console.log(`[Pruner] Starting worker`); + this.timer = setInterval(() => this.prune(), intervalMillis); + setTimeout(() => this.prune(), delayMillis); } - console.log(`[Pruner] Starting worker`); - this.timer = setInterval(() => this.prune(), intervalMillis); - setTimeout(() => this.prune(), delayMillis); - } - async prune() { - const deleteAfterSeconds = await prefs.deleteAfter(); - const pruneThresholdTimestamp = Math.round(Date.now() / 1000) - deleteAfterSeconds; - if (deleteAfterSeconds === 0) { - console.log(`[Pruner] Pruning is disabled. Skipping.`); - return; + async prune() { + const deleteAfterSeconds = await prefs.deleteAfter(); + const pruneThresholdTimestamp = Math.round(Date.now()/1000) - deleteAfterSeconds; + if (deleteAfterSeconds === 0) { + console.log(`[Pruner] Pruning is disabled. Skipping.`); + return; + } + console.log(`[Pruner] Pruning notifications older than ${deleteAfterSeconds}s (timestamp ${pruneThresholdTimestamp})`); + try { + await subscriptionManager.pruneNotifications(pruneThresholdTimestamp); + } catch (e) { + console.log(`[Pruner] Error pruning old subscriptions`, e); + } } - console.log(`[Pruner] Pruning notifications older than ${deleteAfterSeconds}s (timestamp ${pruneThresholdTimestamp})`); - try { - await subscriptionManager.pruneNotifications(pruneThresholdTimestamp); - } catch (e) { - console.log(`[Pruner] Error pruning old subscriptions`, e); - } - } } const pruner = new Pruner(); diff --git a/web/src/app/Session.js b/web/src/app/Session.js index 0b47f93..45f4842 100644 --- a/web/src/app/Session.js +++ b/web/src/app/Session.js @@ -1,30 +1,30 @@ class Session { - store(username, token) { - localStorage.setItem("user", username); - localStorage.setItem("token", token); - } + store(username, token) { + localStorage.setItem("user", username); + localStorage.setItem("token", token); + } - reset() { - localStorage.removeItem("user"); - localStorage.removeItem("token"); - } + reset() { + localStorage.removeItem("user"); + localStorage.removeItem("token"); + } - resetAndRedirect(url) { - this.reset(); - window.location.href = url; - } + resetAndRedirect(url) { + this.reset(); + window.location.href = url; + } - exists() { - return this.username() && this.token(); - } + exists() { + return this.username() && this.token(); + } - username() { - return localStorage.getItem("user"); - } + username() { + return localStorage.getItem("user"); + } - token() { - return localStorage.getItem("token"); - } + token() { + return localStorage.getItem("token"); + } } const session = new Session(); diff --git a/web/src/app/SubscriptionManager.js b/web/src/app/SubscriptionManager.js index ecbe4da..cdfe50e 100644 --- a/web/src/app/SubscriptionManager.js +++ b/web/src/app/SubscriptionManager.js @@ -1,189 +1,192 @@ import db from "./db"; -import { topicUrl } from "./utils"; +import {topicUrl} from "./utils"; class SubscriptionManager { - /** All subscriptions, including "new count"; this is a JOIN, see https://dexie.org/docs/API-Reference#joining */ - async all() { - const subscriptions = await db.subscriptions.toArray(); - return Promise.all( - subscriptions.map(async (s) => ({ - ...s, - new: await db.notifications.where({ subscriptionId: s.id, new: 1 }).count(), - })) - ); - } - - async get(subscriptionId) { - return db.subscriptions.get(subscriptionId); - } - - async add(baseUrl, topic, internal) { - const id = topicUrl(baseUrl, topic); - const existingSubscription = await this.get(id); - if (existingSubscription) { - return existingSubscription; + /** All subscriptions, including "new count"; this is a JOIN, see https://dexie.org/docs/API-Reference#joining */ + async all() { + const subscriptions = await db.subscriptions.toArray(); + await Promise.all(subscriptions.map(async s => { + s.new = await db.notifications + .where({ subscriptionId: s.id, new: 1 }) + .count(); + })); + return subscriptions; } - const subscription = { - id: topicUrl(baseUrl, topic), - baseUrl, - topic, - mutedUntil: 0, - last: null, - internal: internal || false, - }; - await db.subscriptions.put(subscription); - return subscription; - } - async syncFromRemote(remoteSubscriptions, remoteReservations) { - console.log(`[SubscriptionManager] Syncing subscriptions from remote`, remoteSubscriptions); + async get(subscriptionId) { + return await db.subscriptions.get(subscriptionId) + } - // Add remote subscriptions - const remoteIds = await Promise.all( - remoteSubscriptions.map(async (remote) => { - const local = await this.add(remote.base_url, remote.topic, false); - const reservation = remoteReservations?.find((r) => remote.base_url === config.base_url && remote.topic === r.topic) || null; - - await this.update(local.id, { - displayName: remote.display_name, // May be undefined - reservation, // May be null! - }); - - return local.id; - }) - ); - - // Remove local subscriptions that do not exist remotely - const localSubscriptions = await db.subscriptions.toArray(); - - await Promise.all( - localSubscriptions.map(async (local) => { - const remoteExists = remoteIds.includes(local.id); - if (!local.internal && !remoteExists) { - await this.remove(local.id); + async add(baseUrl, topic, internal) { + const id = topicUrl(baseUrl, topic); + const existingSubscription = await this.get(id); + if (existingSubscription) { + return existingSubscription; } - }) - ); - } - - async updateState(subscriptionId, state) { - db.subscriptions.update(subscriptionId, { state }); - } - - async remove(subscriptionId) { - await db.subscriptions.delete(subscriptionId); - await db.notifications.where({ subscriptionId }).delete(); - } - - async first() { - return db.subscriptions.toCollection().first(); // May be undefined - } - - async getNotifications(subscriptionId) { - // This is quite awkward, but it is the recommended approach as per the Dexie docs. - // It's actually fine, because the reading and filtering is quite fast. The rendering is what's - // killing performance. See https://dexie.org/docs/Collection/Collection.offset()#a-better-paging-approach - - return db.notifications - .orderBy("time") // Sort by time first - .filter((n) => n.subscriptionId === subscriptionId) - .reverse() - .toArray(); - } - - async getAllNotifications() { - return db.notifications - .orderBy("time") // Efficient, see docs - .reverse() - .toArray(); - } - - /** Adds notification, or returns false if it already exists */ - async addNotification(subscriptionId, notification) { - const exists = await db.notifications.get(notification.id); - if (exists) { - return false; + const subscription = { + id: topicUrl(baseUrl, topic), + baseUrl: baseUrl, + topic: topic, + mutedUntil: 0, + last: null, + internal: internal || false + }; + await db.subscriptions.put(subscription); + return subscription; } - try { - await db.notifications.add({ - ...notification, - subscriptionId, - // New marker (used for bubble indicator); cannot be boolean; Dexie index limitation - new: 1, - }); // FIXME consider put() for double tab - await db.subscriptions.update(subscriptionId, { - last: notification.id, - }); - } catch (e) { - console.error(`[SubscriptionManager] Error adding notification`, e); + + async syncFromRemote(remoteSubscriptions, remoteReservations) { + console.log(`[SubscriptionManager] Syncing subscriptions from remote`, remoteSubscriptions); + + // Add remote subscriptions + let remoteIds = []; // = topicUrl(baseUrl, topic) + for (let i = 0; i < remoteSubscriptions.length; i++) { + const remote = remoteSubscriptions[i]; + const local = await this.add(remote.base_url, remote.topic, false); + const reservation = remoteReservations?.find(r => remote.base_url === config.base_url && remote.topic === r.topic) || null; + await this.update(local.id, { + displayName: remote.display_name, // May be undefined + reservation: reservation // May be null! + }); + remoteIds.push(local.id); + } + + // Remove local subscriptions that do not exist remotely + const localSubscriptions = await db.subscriptions.toArray(); + for (let i = 0; i < localSubscriptions.length; i++) { + const local = localSubscriptions[i]; + const remoteExists = remoteIds.includes(local.id); + if (!local.internal && !remoteExists) { + await this.remove(local.id); + } + } } - return true; - } - /** Adds/replaces notifications, will not throw if they exist */ - async addNotifications(subscriptionId, notifications) { - const notificationsWithSubscriptionId = notifications.map((notification) => ({ ...notification, subscriptionId })); - const lastNotificationId = notifications.at(-1).id; - await db.notifications.bulkPut(notificationsWithSubscriptionId); - await db.subscriptions.update(subscriptionId, { - last: lastNotificationId, - }); - } - - async updateNotification(notification) { - const exists = await db.notifications.get(notification.id); - if (!exists) { - return false; + async updateState(subscriptionId, state) { + db.subscriptions.update(subscriptionId, { state: state }); } - try { - await db.notifications.put({ ...notification }); - } catch (e) { - console.error(`[SubscriptionManager] Error updating notification`, e); + + async remove(subscriptionId) { + await db.subscriptions.delete(subscriptionId); + await db.notifications + .where({subscriptionId: subscriptionId}) + .delete(); } - return true; - } - async deleteNotification(notificationId) { - await db.notifications.delete(notificationId); - } + async first() { + return db.subscriptions.toCollection().first(); // May be undefined + } - async deleteNotifications(subscriptionId) { - await db.notifications.where({ subscriptionId }).delete(); - } + async getNotifications(subscriptionId) { + // This is quite awkward, but it is the recommended approach as per the Dexie docs. + // It's actually fine, because the reading and filtering is quite fast. The rendering is what's + // killing performance. See https://dexie.org/docs/Collection/Collection.offset()#a-better-paging-approach - async markNotificationRead(notificationId) { - await db.notifications.where({ id: notificationId }).modify({ new: 0 }); - } + return db.notifications + .orderBy("time") // Sort by time first + .filter(n => n.subscriptionId === subscriptionId) + .reverse() + .toArray(); + } - async markNotificationsRead(subscriptionId) { - await db.notifications.where({ subscriptionId, new: 1 }).modify({ new: 0 }); - } + async getAllNotifications() { + return db.notifications + .orderBy("time") // Efficient, see docs + .reverse() + .toArray(); + } - async setMutedUntil(subscriptionId, mutedUntil) { - await db.subscriptions.update(subscriptionId, { - mutedUntil, - }); - } + /** Adds notification, or returns false if it already exists */ + async addNotification(subscriptionId, notification) { + const exists = await db.notifications.get(notification.id); + if (exists) { + return false; + } + try { + notification.new = 1; // New marker (used for bubble indicator); cannot be boolean; Dexie index limitation + await db.notifications.add({ ...notification, subscriptionId }); // FIXME consider put() for double tab + await db.subscriptions.update(subscriptionId, { + last: notification.id + }); + } catch (e) { + console.error(`[SubscriptionManager] Error adding notification`, e); + } + return true; + } - async setDisplayName(subscriptionId, displayName) { - await db.subscriptions.update(subscriptionId, { - displayName, - }); - } + /** Adds/replaces notifications, will not throw if they exist */ + async addNotifications(subscriptionId, notifications) { + const notificationsWithSubscriptionId = notifications + .map(notification => ({ ...notification, subscriptionId })); + const lastNotificationId = notifications.at(-1).id; + await db.notifications.bulkPut(notificationsWithSubscriptionId); + await db.subscriptions.update(subscriptionId, { + last: lastNotificationId + }); + } - async setReservation(subscriptionId, reservation) { - await db.subscriptions.update(subscriptionId, { - reservation, - }); - } + async updateNotification(notification) { + const exists = await db.notifications.get(notification.id); + if (!exists) { + return false; + } + try { + await db.notifications.put({ ...notification }); + } catch (e) { + console.error(`[SubscriptionManager] Error updating notification`, e); + } + return true; + } - async update(subscriptionId, params) { - await db.subscriptions.update(subscriptionId, params); - } + async deleteNotification(notificationId) { + await db.notifications.delete(notificationId); + } - async pruneNotifications(thresholdTimestamp) { - await db.notifications.where("time").below(thresholdTimestamp).delete(); - } + async deleteNotifications(subscriptionId) { + await db.notifications + .where({subscriptionId: subscriptionId}) + .delete(); + } + + async markNotificationRead(notificationId) { + await db.notifications + .where({id: notificationId}) + .modify({new: 0}); + } + + async markNotificationsRead(subscriptionId) { + await db.notifications + .where({subscriptionId: subscriptionId, new: 1}) + .modify({new: 0}); + } + + async setMutedUntil(subscriptionId, mutedUntil) { + await db.subscriptions.update(subscriptionId, { + mutedUntil: mutedUntil + }); + } + + async setDisplayName(subscriptionId, displayName) { + await db.subscriptions.update(subscriptionId, { + displayName: displayName + }); + } + + async setReservation(subscriptionId, reservation) { + await db.subscriptions.update(subscriptionId, { + reservation: reservation + }); + } + + async update(subscriptionId, params) { + await db.subscriptions.update(subscriptionId, params); + } + + async pruneNotifications(thresholdTimestamp) { + await db.notifications + .where("time").below(thresholdTimestamp) + .delete(); + } } const subscriptionManager = new SubscriptionManager(); diff --git a/web/src/app/UserManager.js b/web/src/app/UserManager.js index 2cdd544..1e54eb0 100644 --- a/web/src/app/UserManager.js +++ b/web/src/app/UserManager.js @@ -2,45 +2,45 @@ import db from "./db"; import session from "./Session"; class UserManager { - async all() { - const users = await db.users.toArray(); - if (session.exists()) { - users.unshift(this.localUser()); + async all() { + const users = await db.users.toArray(); + if (session.exists()) { + users.unshift(this.localUser()); + } + return users; } - return users; - } - async get(baseUrl) { - if (session.exists() && baseUrl === config.base_url) { - return this.localUser(); + async get(baseUrl) { + if (session.exists() && baseUrl === config.base_url) { + return this.localUser(); + } + return db.users.get(baseUrl); } - return db.users.get(baseUrl); - } - async save(user) { - if (session.exists() && user.baseUrl === config.base_url) { - return; + async save(user) { + if (session.exists() && user.baseUrl === config.base_url) { + return; + } + await db.users.put(user); } - await db.users.put(user); - } - async delete(baseUrl) { - if (session.exists() && baseUrl === config.base_url) { - return; + async delete(baseUrl) { + if (session.exists() && baseUrl === config.base_url) { + return; + } + await db.users.delete(baseUrl); } - await db.users.delete(baseUrl); - } - localUser() { - if (!session.exists()) { - return null; + localUser() { + if (!session.exists()) { + return null; + } + return { + baseUrl: config.base_url, + username: session.username(), + token: session.token() // Not "password"! + }; } - return { - baseUrl: config.base_url, - username: session.username(), - token: session.token(), // Not "password"! - }; - } } const userManager = new UserManager(); diff --git a/web/src/app/config.js b/web/src/app/config.js index 24e86f3..bdec53e 100644 --- a/web/src/app/config.js +++ b/web/src/app/config.js @@ -1,9 +1,9 @@ -const { config } = window; +const config = window.config; // The backend returns an empty base_url for the config struct, // so the frontend (hey, that's us!) can use the current location. if (!config.base_url || config.base_url === "") { - config.base_url = window.location.origin; + config.base_url = window.location.origin; } export default config; diff --git a/web/src/app/db.js b/web/src/app/db.js index 0e1a5e7..564ee1c 100644 --- a/web/src/app/db.js +++ b/web/src/app/db.js @@ -1,4 +1,4 @@ -import Dexie from "dexie"; +import Dexie from 'dexie'; import session from "./Session"; // Uses Dexie.js @@ -8,14 +8,14 @@ import session from "./Session"; // - As per docs, we only declare the indexable columns, not all columns // The IndexedDB database name is based on the logged-in user -const dbName = session.username() ? `ntfy-${session.username()}` : "ntfy"; +const dbName = (session.username()) ? `ntfy-${session.username()}` : "ntfy"; const db = new Dexie(dbName); db.version(1).stores({ - subscriptions: "&id,baseUrl", - notifications: "&id,subscriptionId,time,new,[subscriptionId+new]", // compound key for query performance - users: "&baseUrl,username", - prefs: "&key", + subscriptions: '&id,baseUrl', + notifications: '&id,subscriptionId,time,new,[subscriptionId+new]', // compound key for query performance + users: '&baseUrl,username', + prefs: '&key' }); export default db; diff --git a/web/src/app/emojis.js b/web/src/app/emojis.js index b7912c3..f6dac7b 100644 --- a/web/src/app/emojis.js +++ b/web/src/app/emojis.js @@ -1,14500 +1,3 @@ // This file is generated by scripts/emoji-convert.sh to reduce the size // Original data source: https://github.com/github/gemoji/blob/master/db/emoji.json -export const rawEmojis = [ - { - emoji: "😀", - aliases: ["grinning"], - tags: ["smile", "happy"], - category: "Smileys & Emotion", - description: "grinning face", - unicode_version: "6.1", - }, - { - emoji: "😃", - aliases: ["smiley"], - tags: ["happy", "joy", "haha"], - category: "Smileys & Emotion", - description: "grinning face with big eyes", - unicode_version: "6.0", - }, - { - emoji: "😄", - aliases: ["smile"], - tags: ["happy", "joy", "laugh", "pleased"], - category: "Smileys & Emotion", - description: "grinning face with smiling eyes", - unicode_version: "6.0", - }, - { - emoji: "😁", - aliases: ["grin"], - tags: [], - category: "Smileys & Emotion", - description: "beaming face with smiling eyes", - unicode_version: "6.0", - }, - { - emoji: "😆", - aliases: ["laughing", "satisfied"], - tags: ["happy", "haha"], - category: "Smileys & Emotion", - description: "grinning squinting face", - unicode_version: "6.0", - }, - { - emoji: "😅", - aliases: ["sweat_smile"], - tags: ["hot"], - category: "Smileys & Emotion", - description: "grinning face with sweat", - unicode_version: "6.0", - }, - { - emoji: "🤣", - aliases: ["rofl"], - tags: ["lol", "laughing"], - category: "Smileys & Emotion", - description: "rolling on the floor laughing", - unicode_version: "9.0", - }, - { - emoji: "😂", - aliases: ["joy"], - tags: ["tears"], - category: "Smileys & Emotion", - description: "face with tears of joy", - unicode_version: "6.0", - }, - { - emoji: "🙂", - aliases: ["slightly_smiling_face"], - tags: [], - category: "Smileys & Emotion", - description: "slightly smiling face", - unicode_version: "7.0", - }, - { - emoji: "🙃", - aliases: ["upside_down_face"], - tags: [], - category: "Smileys & Emotion", - description: "upside-down face", - unicode_version: "8.0", - }, - { - emoji: "😉", - aliases: ["wink"], - tags: ["flirt"], - category: "Smileys & Emotion", - description: "winking face", - unicode_version: "6.0", - }, - { - emoji: "😊", - aliases: ["blush"], - tags: ["proud"], - category: "Smileys & Emotion", - description: "smiling face with smiling eyes", - unicode_version: "6.0", - }, - { - emoji: "😇", - aliases: ["innocent"], - tags: ["angel"], - category: "Smileys & Emotion", - description: "smiling face with halo", - unicode_version: "6.0", - }, - { - emoji: "🥰", - aliases: ["smiling_face_with_three_hearts"], - tags: ["love"], - category: "Smileys & Emotion", - description: "smiling face with hearts", - unicode_version: "11.0", - }, - { - emoji: "😍", - aliases: ["heart_eyes"], - tags: ["love", "crush"], - category: "Smileys & Emotion", - description: "smiling face with heart-eyes", - unicode_version: "6.0", - }, - { - emoji: "🤩", - aliases: ["star_struck"], - tags: ["eyes"], - category: "Smileys & Emotion", - description: "star-struck", - unicode_version: "11.0", - }, - { - emoji: "😘", - aliases: ["kissing_heart"], - tags: ["flirt"], - category: "Smileys & Emotion", - description: "face blowing a kiss", - unicode_version: "6.0", - }, - { - emoji: "😗", - aliases: ["kissing"], - tags: [], - category: "Smileys & Emotion", - description: "kissing face", - unicode_version: "6.1", - }, - { - emoji: "☺️", - aliases: ["relaxed"], - tags: ["blush", "pleased"], - category: "Smileys & Emotion", - description: "smiling face", - unicode_version: "", - }, - { - emoji: "😚", - aliases: ["kissing_closed_eyes"], - tags: [], - category: "Smileys & Emotion", - description: "kissing face with closed eyes", - unicode_version: "6.0", - }, - { - emoji: "😙", - aliases: ["kissing_smiling_eyes"], - tags: [], - category: "Smileys & Emotion", - description: "kissing face with smiling eyes", - unicode_version: "6.1", - }, - { - emoji: "🥲", - aliases: ["smiling_face_with_tear"], - tags: [], - category: "Smileys & Emotion", - description: "smiling face with tear", - unicode_version: "13.0", - }, - { - emoji: "😋", - aliases: ["yum"], - tags: ["tongue", "lick"], - category: "Smileys & Emotion", - description: "face savoring food", - unicode_version: "6.0", - }, - { - emoji: "😛", - aliases: ["stuck_out_tongue"], - tags: [], - category: "Smileys & Emotion", - description: "face with tongue", - unicode_version: "6.1", - }, - { - emoji: "😜", - aliases: ["stuck_out_tongue_winking_eye"], - tags: ["prank", "silly"], - category: "Smileys & Emotion", - description: "winking face with tongue", - unicode_version: "6.0", - }, - { - emoji: "🤪", - aliases: ["zany_face"], - tags: ["goofy", "wacky"], - category: "Smileys & Emotion", - description: "zany face", - unicode_version: "11.0", - }, - { - emoji: "😝", - aliases: ["stuck_out_tongue_closed_eyes"], - tags: ["prank"], - category: "Smileys & Emotion", - description: "squinting face with tongue", - unicode_version: "6.0", - }, - { - emoji: "🤑", - aliases: ["money_mouth_face"], - tags: ["rich"], - category: "Smileys & Emotion", - description: "money-mouth face", - unicode_version: "8.0", - }, - { - emoji: "🤗", - aliases: ["hugs"], - tags: [], - category: "Smileys & Emotion", - description: "hugging face", - unicode_version: "8.0", - }, - { - emoji: "🤭", - aliases: ["hand_over_mouth"], - tags: ["quiet", "whoops"], - category: "Smileys & Emotion", - description: "face with hand over mouth", - unicode_version: "11.0", - }, - { - emoji: "🤫", - aliases: ["shushing_face"], - tags: ["silence", "quiet"], - category: "Smileys & Emotion", - description: "shushing face", - unicode_version: "11.0", - }, - { - emoji: "🤔", - aliases: ["thinking"], - tags: [], - category: "Smileys & Emotion", - description: "thinking face", - unicode_version: "8.0", - }, - { - emoji: "🤐", - aliases: ["zipper_mouth_face"], - tags: ["silence", "hush"], - category: "Smileys & Emotion", - description: "zipper-mouth face", - unicode_version: "8.0", - }, - { - emoji: "🤨", - aliases: ["raised_eyebrow"], - tags: ["suspicious"], - category: "Smileys & Emotion", - description: "face with raised eyebrow", - unicode_version: "11.0", - }, - { - emoji: "😐", - aliases: ["neutral_face"], - tags: ["meh"], - category: "Smileys & Emotion", - description: "neutral face", - unicode_version: "6.0", - }, - { - emoji: "😑", - aliases: ["expressionless"], - tags: [], - category: "Smileys & Emotion", - description: "expressionless face", - unicode_version: "6.1", - }, - { - emoji: "😶", - aliases: ["no_mouth"], - tags: ["mute", "silence"], - category: "Smileys & Emotion", - description: "face without mouth", - unicode_version: "6.0", - }, - { - emoji: "😶‍🌫️", - aliases: ["face_in_clouds"], - tags: [], - category: "Smileys & Emotion", - description: "face in clouds", - unicode_version: "13.1", - }, - { - emoji: "😏", - aliases: ["smirk"], - tags: ["smug"], - category: "Smileys & Emotion", - description: "smirking face", - unicode_version: "6.0", - }, - { - emoji: "😒", - aliases: ["unamused"], - tags: ["meh"], - category: "Smileys & Emotion", - description: "unamused face", - unicode_version: "6.0", - }, - { - emoji: "🙄", - aliases: ["roll_eyes"], - tags: [], - category: "Smileys & Emotion", - description: "face with rolling eyes", - unicode_version: "8.0", - }, - { - emoji: "😬", - aliases: ["grimacing"], - tags: [], - category: "Smileys & Emotion", - description: "grimacing face", - unicode_version: "6.1", - }, - { - emoji: "😮‍💨", - aliases: ["face_exhaling"], - tags: [], - category: "Smileys & Emotion", - description: "face exhaling", - unicode_version: "13.1", - }, - { - emoji: "🤥", - aliases: ["lying_face"], - tags: ["liar"], - category: "Smileys & Emotion", - description: "lying face", - unicode_version: "9.0", - }, - { - emoji: "😌", - aliases: ["relieved"], - tags: ["whew"], - category: "Smileys & Emotion", - description: "relieved face", - unicode_version: "6.0", - }, - { - emoji: "😔", - aliases: ["pensive"], - tags: [], - category: "Smileys & Emotion", - description: "pensive face", - unicode_version: "6.0", - }, - { - emoji: "😪", - aliases: ["sleepy"], - tags: ["tired"], - category: "Smileys & Emotion", - description: "sleepy face", - unicode_version: "6.0", - }, - { - emoji: "🤤", - aliases: ["drooling_face"], - tags: [], - category: "Smileys & Emotion", - description: "drooling face", - unicode_version: "9.0", - }, - { - emoji: "😴", - aliases: ["sleeping"], - tags: ["zzz"], - category: "Smileys & Emotion", - description: "sleeping face", - unicode_version: "6.1", - }, - { - emoji: "😷", - aliases: ["mask"], - tags: ["sick", "ill"], - category: "Smileys & Emotion", - description: "face with medical mask", - unicode_version: "6.0", - }, - { - emoji: "🤒", - aliases: ["face_with_thermometer"], - tags: ["sick"], - category: "Smileys & Emotion", - description: "face with thermometer", - unicode_version: "8.0", - }, - { - emoji: "🤕", - aliases: ["face_with_head_bandage"], - tags: ["hurt"], - category: "Smileys & Emotion", - description: "face with head-bandage", - unicode_version: "8.0", - }, - { - emoji: "🤢", - aliases: ["nauseated_face"], - tags: ["sick", "barf", "disgusted"], - category: "Smileys & Emotion", - description: "nauseated face", - unicode_version: "9.0", - }, - { - emoji: "🤮", - aliases: ["vomiting_face"], - tags: ["barf", "sick"], - category: "Smileys & Emotion", - description: "face vomiting", - unicode_version: "11.0", - }, - { - emoji: "🤧", - aliases: ["sneezing_face"], - tags: ["achoo", "sick"], - category: "Smileys & Emotion", - description: "sneezing face", - unicode_version: "9.0", - }, - { - emoji: "🥵", - aliases: ["hot_face"], - tags: ["heat", "sweating"], - category: "Smileys & Emotion", - description: "hot face", - unicode_version: "11.0", - }, - { - emoji: "🥶", - aliases: ["cold_face"], - tags: ["freezing", "ice"], - category: "Smileys & Emotion", - description: "cold face", - unicode_version: "11.0", - }, - { - emoji: "🥴", - aliases: ["woozy_face"], - tags: ["groggy"], - category: "Smileys & Emotion", - description: "woozy face", - unicode_version: "11.0", - }, - { - emoji: "😵", - aliases: ["dizzy_face"], - tags: [], - category: "Smileys & Emotion", - description: "knocked-out face", - unicode_version: "6.0", - }, - { - emoji: "😵‍💫", - aliases: ["face_with_spiral_eyes"], - tags: [], - category: "Smileys & Emotion", - description: "face with spiral eyes", - unicode_version: "13.1", - }, - { - emoji: "🤯", - aliases: ["exploding_head"], - tags: ["mind", "blown"], - category: "Smileys & Emotion", - description: "exploding head", - unicode_version: "11.0", - }, - { - emoji: "🤠", - aliases: ["cowboy_hat_face"], - tags: [], - category: "Smileys & Emotion", - description: "cowboy hat face", - unicode_version: "9.0", - }, - { - emoji: "🥳", - aliases: ["partying_face"], - tags: ["celebration", "birthday"], - category: "Smileys & Emotion", - description: "partying face", - unicode_version: "11.0", - }, - { - emoji: "🥸", - aliases: ["disguised_face"], - tags: [], - category: "Smileys & Emotion", - description: "disguised face", - unicode_version: "13.0", - }, - { - emoji: "😎", - aliases: ["sunglasses"], - tags: ["cool"], - category: "Smileys & Emotion", - description: "smiling face with sunglasses", - unicode_version: "6.0", - }, - { - emoji: "🤓", - aliases: ["nerd_face"], - tags: ["geek", "glasses"], - category: "Smileys & Emotion", - description: "nerd face", - unicode_version: "8.0", - }, - { - emoji: "🧐", - aliases: ["monocle_face"], - tags: [], - category: "Smileys & Emotion", - description: "face with monocle", - unicode_version: "11.0", - }, - { - emoji: "😕", - aliases: ["confused"], - tags: [], - category: "Smileys & Emotion", - description: "confused face", - unicode_version: "6.1", - }, - { - emoji: "😟", - aliases: ["worried"], - tags: ["nervous"], - category: "Smileys & Emotion", - description: "worried face", - unicode_version: "6.1", - }, - { - emoji: "🙁", - aliases: ["slightly_frowning_face"], - tags: [], - category: "Smileys & Emotion", - description: "slightly frowning face", - unicode_version: "7.0", - }, - { - emoji: "☹️", - aliases: ["frowning_face"], - tags: [], - category: "Smileys & Emotion", - description: "frowning face", - unicode_version: "", - }, - { - emoji: "😮", - aliases: ["open_mouth"], - tags: ["surprise", "impressed", "wow"], - category: "Smileys & Emotion", - description: "face with open mouth", - unicode_version: "6.1", - }, - { - emoji: "😯", - aliases: ["hushed"], - tags: ["silence", "speechless"], - category: "Smileys & Emotion", - description: "hushed face", - unicode_version: "6.1", - }, - { - emoji: "😲", - aliases: ["astonished"], - tags: ["amazed", "gasp"], - category: "Smileys & Emotion", - description: "astonished face", - unicode_version: "6.0", - }, - { - emoji: "😳", - aliases: ["flushed"], - tags: [], - category: "Smileys & Emotion", - description: "flushed face", - unicode_version: "6.0", - }, - { - emoji: "🥺", - aliases: ["pleading_face"], - tags: ["puppy", "eyes"], - category: "Smileys & Emotion", - description: "pleading face", - unicode_version: "11.0", - }, - { - emoji: "😦", - aliases: ["frowning"], - tags: [], - category: "Smileys & Emotion", - description: "frowning face with open mouth", - unicode_version: "6.1", - }, - { - emoji: "😧", - aliases: ["anguished"], - tags: ["stunned"], - category: "Smileys & Emotion", - description: "anguished face", - unicode_version: "6.1", - }, - { - emoji: "😨", - aliases: ["fearful"], - tags: ["scared", "shocked", "oops"], - category: "Smileys & Emotion", - description: "fearful face", - unicode_version: "6.0", - }, - { - emoji: "😰", - aliases: ["cold_sweat"], - tags: ["nervous"], - category: "Smileys & Emotion", - description: "anxious face with sweat", - unicode_version: "6.0", - }, - { - emoji: "😥", - aliases: ["disappointed_relieved"], - tags: ["phew", "sweat", "nervous"], - category: "Smileys & Emotion", - description: "sad but relieved face", - unicode_version: "6.0", - }, - { - emoji: "😢", - aliases: ["cry"], - tags: ["sad", "tear"], - category: "Smileys & Emotion", - description: "crying face", - unicode_version: "6.0", - }, - { - emoji: "😭", - aliases: ["sob"], - tags: ["sad", "cry", "bawling"], - category: "Smileys & Emotion", - description: "loudly crying face", - unicode_version: "6.0", - }, - { - emoji: "😱", - aliases: ["scream"], - tags: ["horror", "shocked"], - category: "Smileys & Emotion", - description: "face screaming in fear", - unicode_version: "6.0", - }, - { - emoji: "😖", - aliases: ["confounded"], - tags: [], - category: "Smileys & Emotion", - description: "confounded face", - unicode_version: "6.0", - }, - { - emoji: "😣", - aliases: ["persevere"], - tags: ["struggling"], - category: "Smileys & Emotion", - description: "persevering face", - unicode_version: "6.0", - }, - { - emoji: "😞", - aliases: ["disappointed"], - tags: ["sad"], - category: "Smileys & Emotion", - description: "disappointed face", - unicode_version: "6.0", - }, - { - emoji: "😓", - aliases: ["sweat"], - tags: [], - category: "Smileys & Emotion", - description: "downcast face with sweat", - unicode_version: "6.0", - }, - { - emoji: "😩", - aliases: ["weary"], - tags: ["tired"], - category: "Smileys & Emotion", - description: "weary face", - unicode_version: "6.0", - }, - { - emoji: "😫", - aliases: ["tired_face"], - tags: ["upset", "whine"], - category: "Smileys & Emotion", - description: "tired face", - unicode_version: "6.0", - }, - { - emoji: "🥱", - aliases: ["yawning_face"], - tags: [], - category: "Smileys & Emotion", - description: "yawning face", - unicode_version: "12.0", - }, - { - emoji: "😤", - aliases: ["triumph"], - tags: ["smug"], - category: "Smileys & Emotion", - description: "face with steam from nose", - unicode_version: "6.0", - }, - { - emoji: "😡", - aliases: ["rage", "pout"], - tags: ["angry"], - category: "Smileys & Emotion", - description: "pouting face", - unicode_version: "6.0", - }, - { - emoji: "😠", - aliases: ["angry"], - tags: ["mad", "annoyed"], - category: "Smileys & Emotion", - description: "angry face", - unicode_version: "6.0", - }, - { - emoji: "🤬", - aliases: ["cursing_face"], - tags: ["foul"], - category: "Smileys & Emotion", - description: "face with symbols on mouth", - unicode_version: "11.0", - }, - { - emoji: "😈", - aliases: ["smiling_imp"], - tags: ["devil", "evil", "horns"], - category: "Smileys & Emotion", - description: "smiling face with horns", - unicode_version: "6.0", - }, - { - emoji: "👿", - aliases: ["imp"], - tags: ["angry", "devil", "evil", "horns"], - category: "Smileys & Emotion", - description: "angry face with horns", - unicode_version: "6.0", - }, - { - emoji: "💀", - aliases: ["skull"], - tags: ["dead", "danger", "poison"], - category: "Smileys & Emotion", - description: "skull", - unicode_version: "6.0", - }, - { - emoji: "☠️", - aliases: ["skull_and_crossbones"], - tags: ["danger", "pirate"], - category: "Smileys & Emotion", - description: "skull and crossbones", - unicode_version: "", - }, - { - emoji: "💩", - aliases: ["hankey", "poop", "shit"], - tags: ["crap"], - category: "Smileys & Emotion", - description: "pile of poo", - unicode_version: "6.0", - }, - { - emoji: "🤡", - aliases: ["clown_face"], - tags: [], - category: "Smileys & Emotion", - description: "clown face", - unicode_version: "9.0", - }, - { - emoji: "👹", - aliases: ["japanese_ogre"], - tags: ["monster"], - category: "Smileys & Emotion", - description: "ogre", - unicode_version: "6.0", - }, - { - emoji: "👺", - aliases: ["japanese_goblin"], - tags: [], - category: "Smileys & Emotion", - description: "goblin", - unicode_version: "6.0", - }, - { - emoji: "👻", - aliases: ["ghost"], - tags: ["halloween"], - category: "Smileys & Emotion", - description: "ghost", - unicode_version: "6.0", - }, - { - emoji: "👽", - aliases: ["alien"], - tags: ["ufo"], - category: "Smileys & Emotion", - description: "alien", - unicode_version: "6.0", - }, - { - emoji: "👾", - aliases: ["space_invader"], - tags: ["game", "retro"], - category: "Smileys & Emotion", - description: "alien monster", - unicode_version: "6.0", - }, - { - emoji: "🤖", - aliases: ["robot"], - tags: [], - category: "Smileys & Emotion", - description: "robot", - unicode_version: "8.0", - }, - { - emoji: "😺", - aliases: ["smiley_cat"], - tags: [], - category: "Smileys & Emotion", - description: "grinning cat", - unicode_version: "6.0", - }, - { - emoji: "😸", - aliases: ["smile_cat"], - tags: [], - category: "Smileys & Emotion", - description: "grinning cat with smiling eyes", - unicode_version: "6.0", - }, - { - emoji: "😹", - aliases: ["joy_cat"], - tags: [], - category: "Smileys & Emotion", - description: "cat with tears of joy", - unicode_version: "6.0", - }, - { - emoji: "😻", - aliases: ["heart_eyes_cat"], - tags: [], - category: "Smileys & Emotion", - description: "smiling cat with heart-eyes", - unicode_version: "6.0", - }, - { - emoji: "😼", - aliases: ["smirk_cat"], - tags: [], - category: "Smileys & Emotion", - description: "cat with wry smile", - unicode_version: "6.0", - }, - { - emoji: "😽", - aliases: ["kissing_cat"], - tags: [], - category: "Smileys & Emotion", - description: "kissing cat", - unicode_version: "6.0", - }, - { - emoji: "🙀", - aliases: ["scream_cat"], - tags: ["horror"], - category: "Smileys & Emotion", - description: "weary cat", - unicode_version: "6.0", - }, - { - emoji: "😿", - aliases: ["crying_cat_face"], - tags: ["sad", "tear"], - category: "Smileys & Emotion", - description: "crying cat", - unicode_version: "6.0", - }, - { - emoji: "😾", - aliases: ["pouting_cat"], - tags: [], - category: "Smileys & Emotion", - description: "pouting cat", - unicode_version: "6.0", - }, - { - emoji: "🙈", - aliases: ["see_no_evil"], - tags: ["monkey", "blind", "ignore"], - category: "Smileys & Emotion", - description: "see-no-evil monkey", - unicode_version: "6.0", - }, - { - emoji: "🙉", - aliases: ["hear_no_evil"], - tags: ["monkey", "deaf"], - category: "Smileys & Emotion", - description: "hear-no-evil monkey", - unicode_version: "6.0", - }, - { - emoji: "🙊", - aliases: ["speak_no_evil"], - tags: ["monkey", "mute", "hush"], - category: "Smileys & Emotion", - description: "speak-no-evil monkey", - unicode_version: "6.0", - }, - { - emoji: "💋", - aliases: ["kiss"], - tags: ["lipstick"], - category: "Smileys & Emotion", - description: "kiss mark", - unicode_version: "6.0", - }, - { - emoji: "💌", - aliases: ["love_letter"], - tags: ["email", "envelope"], - category: "Smileys & Emotion", - description: "love letter", - unicode_version: "6.0", - }, - { - emoji: "💘", - aliases: ["cupid"], - tags: ["love", "heart"], - category: "Smileys & Emotion", - description: "heart with arrow", - unicode_version: "6.0", - }, - { - emoji: "💝", - aliases: ["gift_heart"], - tags: ["chocolates"], - category: "Smileys & Emotion", - description: "heart with ribbon", - unicode_version: "6.0", - }, - { - emoji: "💖", - aliases: ["sparkling_heart"], - tags: [], - category: "Smileys & Emotion", - description: "sparkling heart", - unicode_version: "6.0", - }, - { - emoji: "💗", - aliases: ["heartpulse"], - tags: [], - category: "Smileys & Emotion", - description: "growing heart", - unicode_version: "6.0", - }, - { - emoji: "💓", - aliases: ["heartbeat"], - tags: [], - category: "Smileys & Emotion", - description: "beating heart", - unicode_version: "6.0", - }, - { - emoji: "💞", - aliases: ["revolving_hearts"], - tags: [], - category: "Smileys & Emotion", - description: "revolving hearts", - unicode_version: "6.0", - }, - { - emoji: "💕", - aliases: ["two_hearts"], - tags: [], - category: "Smileys & Emotion", - description: "two hearts", - unicode_version: "6.0", - }, - { - emoji: "💟", - aliases: ["heart_decoration"], - tags: [], - category: "Smileys & Emotion", - description: "heart decoration", - unicode_version: "6.0", - }, - { - emoji: "❣️", - aliases: ["heavy_heart_exclamation"], - tags: [], - category: "Smileys & Emotion", - description: "heart exclamation", - unicode_version: "", - }, - { - emoji: "💔", - aliases: ["broken_heart"], - tags: [], - category: "Smileys & Emotion", - description: "broken heart", - unicode_version: "6.0", - }, - { - emoji: "❤️‍🔥", - aliases: ["heart_on_fire"], - tags: [], - category: "Smileys & Emotion", - description: "heart on fire", - unicode_version: "13.1", - }, - { - emoji: "❤️‍🩹", - aliases: ["mending_heart"], - tags: [], - category: "Smileys & Emotion", - description: "mending heart", - unicode_version: "13.1", - }, - { - emoji: "❤️", - aliases: ["heart"], - tags: ["love"], - category: "Smileys & Emotion", - description: "red heart", - unicode_version: "", - }, - { - emoji: "🧡", - aliases: ["orange_heart"], - tags: [], - category: "Smileys & Emotion", - description: "orange heart", - unicode_version: "11.0", - }, - { - emoji: "💛", - aliases: ["yellow_heart"], - tags: [], - category: "Smileys & Emotion", - description: "yellow heart", - unicode_version: "6.0", - }, - { - emoji: "💚", - aliases: ["green_heart"], - tags: [], - category: "Smileys & Emotion", - description: "green heart", - unicode_version: "6.0", - }, - { - emoji: "💙", - aliases: ["blue_heart"], - tags: [], - category: "Smileys & Emotion", - description: "blue heart", - unicode_version: "6.0", - }, - { - emoji: "💜", - aliases: ["purple_heart"], - tags: [], - category: "Smileys & Emotion", - description: "purple heart", - unicode_version: "6.0", - }, - { - emoji: "🤎", - aliases: ["brown_heart"], - tags: [], - category: "Smileys & Emotion", - description: "brown heart", - unicode_version: "12.0", - }, - { - emoji: "🖤", - aliases: ["black_heart"], - tags: [], - category: "Smileys & Emotion", - description: "black heart", - unicode_version: "9.0", - }, - { - emoji: "🤍", - aliases: ["white_heart"], - tags: [], - category: "Smileys & Emotion", - description: "white heart", - unicode_version: "12.0", - }, - { - emoji: "💯", - aliases: ["100"], - tags: ["score", "perfect"], - category: "Smileys & Emotion", - description: "hundred points", - unicode_version: "6.0", - }, - { - emoji: "💢", - aliases: ["anger"], - tags: ["angry"], - category: "Smileys & Emotion", - description: "anger symbol", - unicode_version: "6.0", - }, - { - emoji: "💥", - aliases: ["boom", "collision"], - tags: ["explode"], - category: "Smileys & Emotion", - description: "collision", - unicode_version: "6.0", - }, - { - emoji: "💫", - aliases: ["dizzy"], - tags: ["star"], - category: "Smileys & Emotion", - description: "dizzy", - unicode_version: "6.0", - }, - { - emoji: "💦", - aliases: ["sweat_drops"], - tags: ["water", "workout"], - category: "Smileys & Emotion", - description: "sweat droplets", - unicode_version: "6.0", - }, - { - emoji: "💨", - aliases: ["dash"], - tags: ["wind", "blow", "fast"], - category: "Smileys & Emotion", - description: "dashing away", - unicode_version: "6.0", - }, - { - emoji: "🕳️", - aliases: ["hole"], - tags: [], - category: "Smileys & Emotion", - description: "hole", - unicode_version: "7.0", - }, - { - emoji: "💣", - aliases: ["bomb"], - tags: ["boom"], - category: "Smileys & Emotion", - description: "bomb", - unicode_version: "6.0", - }, - { - emoji: "💬", - aliases: ["speech_balloon"], - tags: ["comment"], - category: "Smileys & Emotion", - description: "speech balloon", - unicode_version: "6.0", - }, - { - emoji: "👁️‍🗨️", - aliases: ["eye_speech_bubble"], - tags: [], - category: "Smileys & Emotion", - description: "eye in speech bubble", - unicode_version: "11.0", - }, - { - emoji: "🗨️", - aliases: ["left_speech_bubble"], - tags: [], - category: "Smileys & Emotion", - description: "left speech bubble", - unicode_version: "11.0", - }, - { - emoji: "🗯️", - aliases: ["right_anger_bubble"], - tags: [], - category: "Smileys & Emotion", - description: "right anger bubble", - unicode_version: "7.0", - }, - { - emoji: "💭", - aliases: ["thought_balloon"], - tags: ["thinking"], - category: "Smileys & Emotion", - description: "thought balloon", - unicode_version: "6.0", - }, - { - emoji: "💤", - aliases: ["zzz"], - tags: ["sleeping"], - category: "Smileys & Emotion", - description: "zzz", - unicode_version: "6.0", - }, - { - emoji: "👋", - aliases: ["wave"], - tags: ["goodbye"], - category: "People & Body", - description: "waving hand", - unicode_version: "6.0", - }, - { - emoji: "🤚", - aliases: ["raised_back_of_hand"], - tags: [], - category: "People & Body", - description: "raised back of hand", - unicode_version: "9.0", - }, - { - emoji: "🖐️", - aliases: ["raised_hand_with_fingers_splayed"], - tags: [], - category: "People & Body", - description: "hand with fingers splayed", - unicode_version: "7.0", - }, - { - emoji: "✋", - aliases: ["hand", "raised_hand"], - tags: ["highfive", "stop"], - category: "People & Body", - description: "raised hand", - unicode_version: "6.0", - }, - { - emoji: "🖖", - aliases: ["vulcan_salute"], - tags: ["prosper", "spock"], - category: "People & Body", - description: "vulcan salute", - unicode_version: "7.0", - }, - { - emoji: "👌", - aliases: ["ok_hand"], - tags: [], - category: "People & Body", - description: "OK hand", - unicode_version: "6.0", - }, - { - emoji: "🤌", - aliases: ["pinched_fingers"], - tags: [], - category: "People & Body", - description: "pinched fingers", - unicode_version: "13.0", - }, - { - emoji: "🤏", - aliases: ["pinching_hand"], - tags: [], - category: "People & Body", - description: "pinching hand", - unicode_version: "12.0", - }, - { - emoji: "✌️", - aliases: ["v"], - tags: ["victory", "peace"], - category: "People & Body", - description: "victory hand", - unicode_version: "", - }, - { - emoji: "🤞", - aliases: ["crossed_fingers"], - tags: ["luck", "hopeful"], - category: "People & Body", - description: "crossed fingers", - unicode_version: "9.0", - }, - { - emoji: "🤟", - aliases: ["love_you_gesture"], - tags: [], - category: "People & Body", - description: "love-you gesture", - unicode_version: "11.0", - }, - { - emoji: "🤘", - aliases: ["metal"], - tags: [], - category: "People & Body", - description: "sign of the horns", - unicode_version: "8.0", - }, - { - emoji: "🤙", - aliases: ["call_me_hand"], - tags: [], - category: "People & Body", - description: "call me hand", - unicode_version: "9.0", - }, - { - emoji: "👈", - aliases: ["point_left"], - tags: [], - category: "People & Body", - description: "backhand index pointing left", - unicode_version: "6.0", - }, - { - emoji: "👉", - aliases: ["point_right"], - tags: [], - category: "People & Body", - description: "backhand index pointing right", - unicode_version: "6.0", - }, - { - emoji: "👆", - aliases: ["point_up_2"], - tags: [], - category: "People & Body", - description: "backhand index pointing up", - unicode_version: "6.0", - }, - { - emoji: "🖕", - aliases: ["middle_finger", "fu"], - tags: [], - category: "People & Body", - description: "middle finger", - unicode_version: "7.0", - }, - { - emoji: "👇", - aliases: ["point_down"], - tags: [], - category: "People & Body", - description: "backhand index pointing down", - unicode_version: "6.0", - }, - { - emoji: "☝️", - aliases: ["point_up"], - tags: [], - category: "People & Body", - description: "index pointing up", - unicode_version: "", - }, - { - emoji: "👍", - aliases: ["+1", "thumbsup"], - tags: ["approve", "ok"], - category: "People & Body", - description: "thumbs up", - unicode_version: "6.0", - }, - { - emoji: "👎", - aliases: ["-1", "thumbsdown"], - tags: ["disapprove", "bury"], - category: "People & Body", - description: "thumbs down", - unicode_version: "6.0", - }, - { - emoji: "✊", - aliases: ["fist_raised", "fist"], - tags: ["power"], - category: "People & Body", - description: "raised fist", - unicode_version: "6.0", - }, - { - emoji: "👊", - aliases: ["fist_oncoming", "facepunch", "punch"], - tags: ["attack"], - category: "People & Body", - description: "oncoming fist", - unicode_version: "6.0", - }, - { - emoji: "🤛", - aliases: ["fist_left"], - tags: [], - category: "People & Body", - description: "left-facing fist", - unicode_version: "9.0", - }, - { - emoji: "🤜", - aliases: ["fist_right"], - tags: [], - category: "People & Body", - description: "right-facing fist", - unicode_version: "9.0", - }, - { - emoji: "👏", - aliases: ["clap"], - tags: ["praise", "applause"], - category: "People & Body", - description: "clapping hands", - unicode_version: "6.0", - }, - { - emoji: "🙌", - aliases: ["raised_hands"], - tags: ["hooray"], - category: "People & Body", - description: "raising hands", - unicode_version: "6.0", - }, - { - emoji: "👐", - aliases: ["open_hands"], - tags: [], - category: "People & Body", - description: "open hands", - unicode_version: "6.0", - }, - { - emoji: "🤲", - aliases: ["palms_up_together"], - tags: [], - category: "People & Body", - description: "palms up together", - unicode_version: "11.0", - }, - { - emoji: "🤝", - aliases: ["handshake"], - tags: ["deal"], - category: "People & Body", - description: "handshake", - unicode_version: "9.0", - }, - { - emoji: "🙏", - aliases: ["pray"], - tags: ["please", "hope", "wish"], - category: "People & Body", - description: "folded hands", - unicode_version: "6.0", - }, - { - emoji: "✍️", - aliases: ["writing_hand"], - tags: [], - category: "People & Body", - description: "writing hand", - unicode_version: "", - }, - { - emoji: "💅", - aliases: ["nail_care"], - tags: ["beauty", "manicure"], - category: "People & Body", - description: "nail polish", - unicode_version: "6.0", - }, - { - emoji: "🤳", - aliases: ["selfie"], - tags: [], - category: "People & Body", - description: "selfie", - unicode_version: "9.0", - }, - { - emoji: "💪", - aliases: ["muscle"], - tags: ["flex", "bicep", "strong", "workout"], - category: "People & Body", - description: "flexed biceps", - unicode_version: "6.0", - }, - { - emoji: "🦾", - aliases: ["mechanical_arm"], - tags: [], - category: "People & Body", - description: "mechanical arm", - unicode_version: "12.0", - }, - { - emoji: "🦿", - aliases: ["mechanical_leg"], - tags: [], - category: "People & Body", - description: "mechanical leg", - unicode_version: "12.0", - }, - { - emoji: "🦵", - aliases: ["leg"], - tags: [], - category: "People & Body", - description: "leg", - unicode_version: "11.0", - }, - { - emoji: "🦶", - aliases: ["foot"], - tags: [], - category: "People & Body", - description: "foot", - unicode_version: "11.0", - }, - { - emoji: "👂", - aliases: ["ear"], - tags: ["hear", "sound", "listen"], - category: "People & Body", - description: "ear", - unicode_version: "6.0", - }, - { - emoji: "🦻", - aliases: ["ear_with_hearing_aid"], - tags: [], - category: "People & Body", - description: "ear with hearing aid", - unicode_version: "12.0", - }, - { - emoji: "👃", - aliases: ["nose"], - tags: ["smell"], - category: "People & Body", - description: "nose", - unicode_version: "6.0", - }, - { - emoji: "🧠", - aliases: ["brain"], - tags: [], - category: "People & Body", - description: "brain", - unicode_version: "11.0", - }, - { - emoji: "🫀", - aliases: ["anatomical_heart"], - tags: [], - category: "People & Body", - description: "anatomical heart", - unicode_version: "13.0", - }, - { - emoji: "🫁", - aliases: ["lungs"], - tags: [], - category: "People & Body", - description: "lungs", - unicode_version: "13.0", - }, - { - emoji: "🦷", - aliases: ["tooth"], - tags: [], - category: "People & Body", - description: "tooth", - unicode_version: "11.0", - }, - { - emoji: "🦴", - aliases: ["bone"], - tags: [], - category: "People & Body", - description: "bone", - unicode_version: "11.0", - }, - { - emoji: "👀", - aliases: ["eyes"], - tags: ["look", "see", "watch"], - category: "People & Body", - description: "eyes", - unicode_version: "6.0", - }, - { - emoji: "👁️", - aliases: ["eye"], - tags: [], - category: "People & Body", - description: "eye", - unicode_version: "7.0", - }, - { - emoji: "👅", - aliases: ["tongue"], - tags: ["taste"], - category: "People & Body", - description: "tongue", - unicode_version: "6.0", - }, - { - emoji: "👄", - aliases: ["lips"], - tags: ["kiss"], - category: "People & Body", - description: "mouth", - unicode_version: "6.0", - }, - { - emoji: "👶", - aliases: ["baby"], - tags: ["child", "newborn"], - category: "People & Body", - description: "baby", - unicode_version: "6.0", - }, - { - emoji: "🧒", - aliases: ["child"], - tags: [], - category: "People & Body", - description: "child", - unicode_version: "11.0", - }, - { - emoji: "👦", - aliases: ["boy"], - tags: ["child"], - category: "People & Body", - description: "boy", - unicode_version: "6.0", - }, - { - emoji: "👧", - aliases: ["girl"], - tags: ["child"], - category: "People & Body", - description: "girl", - unicode_version: "6.0", - }, - { - emoji: "🧑", - aliases: ["adult"], - tags: [], - category: "People & Body", - description: "person", - unicode_version: "11.0", - }, - { - emoji: "👱", - aliases: ["blond_haired_person"], - tags: [], - category: "People & Body", - description: "person: blond hair", - unicode_version: "6.0", - }, - { - emoji: "👨", - aliases: ["man"], - tags: ["mustache", "father", "dad"], - category: "People & Body", - description: "man", - unicode_version: "6.0", - }, - { - emoji: "🧔", - aliases: ["bearded_person"], - tags: [], - category: "People & Body", - description: "person: beard", - unicode_version: "11.0", - }, - { - emoji: "🧔‍♂️", - aliases: ["man_beard"], - tags: [], - category: "People & Body", - description: "man: beard", - unicode_version: "13.1", - }, - { - emoji: "🧔‍♀️", - aliases: ["woman_beard"], - tags: [], - category: "People & Body", - description: "woman: beard", - unicode_version: "13.1", - }, - { - emoji: "👨‍🦰", - aliases: ["red_haired_man"], - tags: [], - category: "People & Body", - description: "man: red hair", - unicode_version: "11.0", - }, - { - emoji: "👨‍🦱", - aliases: ["curly_haired_man"], - tags: [], - category: "People & Body", - description: "man: curly hair", - unicode_version: "11.0", - }, - { - emoji: "👨‍🦳", - aliases: ["white_haired_man"], - tags: [], - category: "People & Body", - description: "man: white hair", - unicode_version: "11.0", - }, - { - emoji: "👨‍🦲", - aliases: ["bald_man"], - tags: [], - category: "People & Body", - description: "man: bald", - unicode_version: "11.0", - }, - { - emoji: "👩", - aliases: ["woman"], - tags: ["girls"], - category: "People & Body", - description: "woman", - unicode_version: "6.0", - }, - { - emoji: "👩‍🦰", - aliases: ["red_haired_woman"], - tags: [], - category: "People & Body", - description: "woman: red hair", - unicode_version: "11.0", - }, - { - emoji: "🧑‍🦰", - aliases: ["person_red_hair"], - tags: [], - category: "People & Body", - description: "person: red hair", - unicode_version: "12.1", - }, - { - emoji: "👩‍🦱", - aliases: ["curly_haired_woman"], - tags: [], - category: "People & Body", - description: "woman: curly hair", - unicode_version: "11.0", - }, - { - emoji: "🧑‍🦱", - aliases: ["person_curly_hair"], - tags: [], - category: "People & Body", - description: "person: curly hair", - unicode_version: "12.1", - }, - { - emoji: "👩‍🦳", - aliases: ["white_haired_woman"], - tags: [], - category: "People & Body", - description: "woman: white hair", - unicode_version: "11.0", - }, - { - emoji: "🧑‍🦳", - aliases: ["person_white_hair"], - tags: [], - category: "People & Body", - description: "person: white hair", - unicode_version: "12.1", - }, - { - emoji: "👩‍🦲", - aliases: ["bald_woman"], - tags: [], - category: "People & Body", - description: "woman: bald", - unicode_version: "11.0", - }, - { - emoji: "🧑‍🦲", - aliases: ["person_bald"], - tags: [], - category: "People & Body", - description: "person: bald", - unicode_version: "12.1", - }, - { - emoji: "👱‍♀️", - aliases: ["blond_haired_woman", "blonde_woman"], - tags: [], - category: "People & Body", - description: "woman: blond hair", - unicode_version: "6.0", - }, - { - emoji: "👱‍♂️", - aliases: ["blond_haired_man"], - tags: [], - category: "People & Body", - description: "man: blond hair", - unicode_version: "11.0", - }, - { - emoji: "🧓", - aliases: ["older_adult"], - tags: [], - category: "People & Body", - description: "older person", - unicode_version: "11.0", - }, - { - emoji: "👴", - aliases: ["older_man"], - tags: [], - category: "People & Body", - description: "old man", - unicode_version: "6.0", - }, - { - emoji: "👵", - aliases: ["older_woman"], - tags: [], - category: "People & Body", - description: "old woman", - unicode_version: "6.0", - }, - { - emoji: "🙍", - aliases: ["frowning_person"], - tags: [], - category: "People & Body", - description: "person frowning", - unicode_version: "6.0", - }, - { - emoji: "🙍‍♂️", - aliases: ["frowning_man"], - tags: [], - category: "People & Body", - description: "man frowning", - unicode_version: "6.0", - }, - { - emoji: "🙍‍♀️", - aliases: ["frowning_woman"], - tags: [], - category: "People & Body", - description: "woman frowning", - unicode_version: "11.0", - }, - { - emoji: "🙎", - aliases: ["pouting_face"], - tags: [], - category: "People & Body", - description: "person pouting", - unicode_version: "6.0", - }, - { - emoji: "🙎‍♂️", - aliases: ["pouting_man"], - tags: [], - category: "People & Body", - description: "man pouting", - unicode_version: "6.0", - }, - { - emoji: "🙎‍♀️", - aliases: ["pouting_woman"], - tags: [], - category: "People & Body", - description: "woman pouting", - unicode_version: "11.0", - }, - { - emoji: "🙅", - aliases: ["no_good"], - tags: ["stop", "halt", "denied"], - category: "People & Body", - description: "person gesturing NO", - unicode_version: "6.0", - }, - { - emoji: "🙅‍♂️", - aliases: ["no_good_man", "ng_man"], - tags: ["stop", "halt", "denied"], - category: "People & Body", - description: "man gesturing NO", - unicode_version: "6.0", - }, - { - emoji: "🙅‍♀️", - aliases: ["no_good_woman", "ng_woman"], - tags: ["stop", "halt", "denied"], - category: "People & Body", - description: "woman gesturing NO", - unicode_version: "11.0", - }, - { - emoji: "🙆", - aliases: ["ok_person"], - tags: [], - category: "People & Body", - description: "person gesturing OK", - unicode_version: "6.0", - }, - { - emoji: "🙆‍♂️", - aliases: ["ok_man"], - tags: [], - category: "People & Body", - description: "man gesturing OK", - unicode_version: "6.0", - }, - { - emoji: "🙆‍♀️", - aliases: ["ok_woman"], - tags: [], - category: "People & Body", - description: "woman gesturing OK", - unicode_version: "11.0", - }, - { - emoji: "💁", - aliases: ["tipping_hand_person", "information_desk_person"], - tags: [], - category: "People & Body", - description: "person tipping hand", - unicode_version: "6.0", - }, - { - emoji: "💁‍♂️", - aliases: ["tipping_hand_man", "sassy_man"], - tags: ["information"], - category: "People & Body", - description: "man tipping hand", - unicode_version: "6.0", - }, - { - emoji: "💁‍♀️", - aliases: ["tipping_hand_woman", "sassy_woman"], - tags: ["information"], - category: "People & Body", - description: "woman tipping hand", - unicode_version: "11.0", - }, - { - emoji: "🙋", - aliases: ["raising_hand"], - tags: [], - category: "People & Body", - description: "person raising hand", - unicode_version: "6.0", - }, - { - emoji: "🙋‍♂️", - aliases: ["raising_hand_man"], - tags: [], - category: "People & Body", - description: "man raising hand", - unicode_version: "6.0", - }, - { - emoji: "🙋‍♀️", - aliases: ["raising_hand_woman"], - tags: [], - category: "People & Body", - description: "woman raising hand", - unicode_version: "11.0", - }, - { - emoji: "🧏", - aliases: ["deaf_person"], - tags: [], - category: "People & Body", - description: "deaf person", - unicode_version: "12.0", - }, - { - emoji: "🧏‍♂️", - aliases: ["deaf_man"], - tags: [], - category: "People & Body", - description: "deaf man", - unicode_version: "12.0", - }, - { - emoji: "🧏‍♀️", - aliases: ["deaf_woman"], - tags: [], - category: "People & Body", - description: "deaf woman", - unicode_version: "12.0", - }, - { - emoji: "🙇", - aliases: ["bow"], - tags: ["respect", "thanks"], - category: "People & Body", - description: "person bowing", - unicode_version: "6.0", - }, - { - emoji: "🙇‍♂️", - aliases: ["bowing_man"], - tags: ["respect", "thanks"], - category: "People & Body", - description: "man bowing", - unicode_version: "11.0", - }, - { - emoji: "🙇‍♀️", - aliases: ["bowing_woman"], - tags: ["respect", "thanks"], - category: "People & Body", - description: "woman bowing", - unicode_version: "6.0", - }, - { - emoji: "🤦", - aliases: ["facepalm"], - tags: [], - category: "People & Body", - description: "person facepalming", - unicode_version: "11.0", - }, - { - emoji: "🤦‍♂️", - aliases: ["man_facepalming"], - tags: [], - category: "People & Body", - description: "man facepalming", - unicode_version: "9.0", - }, - { - emoji: "🤦‍♀️", - aliases: ["woman_facepalming"], - tags: [], - category: "People & Body", - description: "woman facepalming", - unicode_version: "9.0", - }, - { - emoji: "🤷", - aliases: ["shrug"], - tags: [], - category: "People & Body", - description: "person shrugging", - unicode_version: "11.0", - }, - { - emoji: "🤷‍♂️", - aliases: ["man_shrugging"], - tags: [], - category: "People & Body", - description: "man shrugging", - unicode_version: "9.0", - }, - { - emoji: "🤷‍♀️", - aliases: ["woman_shrugging"], - tags: [], - category: "People & Body", - description: "woman shrugging", - unicode_version: "9.0", - }, - { - emoji: "🧑‍⚕️", - aliases: ["health_worker"], - tags: [], - category: "People & Body", - description: "health worker", - unicode_version: "12.1", - }, - { - emoji: "👨‍⚕️", - aliases: ["man_health_worker"], - tags: ["doctor", "nurse"], - category: "People & Body", - description: "man health worker", - unicode_version: "", - }, - { - emoji: "👩‍⚕️", - aliases: ["woman_health_worker"], - tags: ["doctor", "nurse"], - category: "People & Body", - description: "woman health worker", - unicode_version: "", - }, - { - emoji: "🧑‍🎓", - aliases: ["student"], - tags: [], - category: "People & Body", - description: "student", - unicode_version: "12.1", - }, - { - emoji: "👨‍🎓", - aliases: ["man_student"], - tags: ["graduation"], - category: "People & Body", - description: "man student", - unicode_version: "", - }, - { - emoji: "👩‍🎓", - aliases: ["woman_student"], - tags: ["graduation"], - category: "People & Body", - description: "woman student", - unicode_version: "", - }, - { - emoji: "🧑‍🏫", - aliases: ["teacher"], - tags: [], - category: "People & Body", - description: "teacher", - unicode_version: "12.1", - }, - { - emoji: "👨‍🏫", - aliases: ["man_teacher"], - tags: ["school", "professor"], - category: "People & Body", - description: "man teacher", - unicode_version: "", - }, - { - emoji: "👩‍🏫", - aliases: ["woman_teacher"], - tags: ["school", "professor"], - category: "People & Body", - description: "woman teacher", - unicode_version: "", - }, - { - emoji: "🧑‍⚖️", - aliases: ["judge"], - tags: [], - category: "People & Body", - description: "judge", - unicode_version: "12.1", - }, - { - emoji: "👨‍⚖️", - aliases: ["man_judge"], - tags: ["justice"], - category: "People & Body", - description: "man judge", - unicode_version: "", - }, - { - emoji: "👩‍⚖️", - aliases: ["woman_judge"], - tags: ["justice"], - category: "People & Body", - description: "woman judge", - unicode_version: "", - }, - { - emoji: "🧑‍🌾", - aliases: ["farmer"], - tags: [], - category: "People & Body", - description: "farmer", - unicode_version: "12.1", - }, - { - emoji: "👨‍🌾", - aliases: ["man_farmer"], - tags: [], - category: "People & Body", - description: "man farmer", - unicode_version: "", - }, - { - emoji: "👩‍🌾", - aliases: ["woman_farmer"], - tags: [], - category: "People & Body", - description: "woman farmer", - unicode_version: "", - }, - { - emoji: "🧑‍🍳", - aliases: ["cook"], - tags: [], - category: "People & Body", - description: "cook", - unicode_version: "12.1", - }, - { - emoji: "👨‍🍳", - aliases: ["man_cook"], - tags: ["chef"], - category: "People & Body", - description: "man cook", - unicode_version: "", - }, - { - emoji: "👩‍🍳", - aliases: ["woman_cook"], - tags: ["chef"], - category: "People & Body", - description: "woman cook", - unicode_version: "", - }, - { - emoji: "🧑‍🔧", - aliases: ["mechanic"], - tags: [], - category: "People & Body", - description: "mechanic", - unicode_version: "12.1", - }, - { - emoji: "👨‍🔧", - aliases: ["man_mechanic"], - tags: [], - category: "People & Body", - description: "man mechanic", - unicode_version: "", - }, - { - emoji: "👩‍🔧", - aliases: ["woman_mechanic"], - tags: [], - category: "People & Body", - description: "woman mechanic", - unicode_version: "", - }, - { - emoji: "🧑‍🏭", - aliases: ["factory_worker"], - tags: [], - category: "People & Body", - description: "factory worker", - unicode_version: "12.1", - }, - { - emoji: "👨‍🏭", - aliases: ["man_factory_worker"], - tags: [], - category: "People & Body", - description: "man factory worker", - unicode_version: "", - }, - { - emoji: "👩‍🏭", - aliases: ["woman_factory_worker"], - tags: [], - category: "People & Body", - description: "woman factory worker", - unicode_version: "", - }, - { - emoji: "🧑‍💼", - aliases: ["office_worker"], - tags: [], - category: "People & Body", - description: "office worker", - unicode_version: "12.1", - }, - { - emoji: "👨‍💼", - aliases: ["man_office_worker"], - tags: ["business"], - category: "People & Body", - description: "man office worker", - unicode_version: "", - }, - { - emoji: "👩‍💼", - aliases: ["woman_office_worker"], - tags: ["business"], - category: "People & Body", - description: "woman office worker", - unicode_version: "", - }, - { - emoji: "🧑‍🔬", - aliases: ["scientist"], - tags: [], - category: "People & Body", - description: "scientist", - unicode_version: "12.1", - }, - { - emoji: "👨‍🔬", - aliases: ["man_scientist"], - tags: ["research"], - category: "People & Body", - description: "man scientist", - unicode_version: "", - }, - { - emoji: "👩‍🔬", - aliases: ["woman_scientist"], - tags: ["research"], - category: "People & Body", - description: "woman scientist", - unicode_version: "", - }, - { - emoji: "🧑‍💻", - aliases: ["technologist"], - tags: [], - category: "People & Body", - description: "technologist", - unicode_version: "12.1", - }, - { - emoji: "👨‍💻", - aliases: ["man_technologist"], - tags: ["coder"], - category: "People & Body", - description: "man technologist", - unicode_version: "", - }, - { - emoji: "👩‍💻", - aliases: ["woman_technologist"], - tags: ["coder"], - category: "People & Body", - description: "woman technologist", - unicode_version: "", - }, - { - emoji: "🧑‍🎤", - aliases: ["singer"], - tags: [], - category: "People & Body", - description: "singer", - unicode_version: "12.1", - }, - { - emoji: "👨‍🎤", - aliases: ["man_singer"], - tags: ["rockstar"], - category: "People & Body", - description: "man singer", - unicode_version: "", - }, - { - emoji: "👩‍🎤", - aliases: ["woman_singer"], - tags: ["rockstar"], - category: "People & Body", - description: "woman singer", - unicode_version: "", - }, - { - emoji: "🧑‍🎨", - aliases: ["artist"], - tags: [], - category: "People & Body", - description: "artist", - unicode_version: "12.1", - }, - { - emoji: "👨‍🎨", - aliases: ["man_artist"], - tags: ["painter"], - category: "People & Body", - description: "man artist", - unicode_version: "", - }, - { - emoji: "👩‍🎨", - aliases: ["woman_artist"], - tags: ["painter"], - category: "People & Body", - description: "woman artist", - unicode_version: "", - }, - { - emoji: "🧑‍✈️", - aliases: ["pilot"], - tags: [], - category: "People & Body", - description: "pilot", - unicode_version: "12.1", - }, - { - emoji: "👨‍✈️", - aliases: ["man_pilot"], - tags: [], - category: "People & Body", - description: "man pilot", - unicode_version: "", - }, - { - emoji: "👩‍✈️", - aliases: ["woman_pilot"], - tags: [], - category: "People & Body", - description: "woman pilot", - unicode_version: "", - }, - { - emoji: "🧑‍🚀", - aliases: ["astronaut"], - tags: [], - category: "People & Body", - description: "astronaut", - unicode_version: "12.1", - }, - { - emoji: "👨‍🚀", - aliases: ["man_astronaut"], - tags: ["space"], - category: "People & Body", - description: "man astronaut", - unicode_version: "", - }, - { - emoji: "👩‍🚀", - aliases: ["woman_astronaut"], - tags: ["space"], - category: "People & Body", - description: "woman astronaut", - unicode_version: "", - }, - { - emoji: "🧑‍🚒", - aliases: ["firefighter"], - tags: [], - category: "People & Body", - description: "firefighter", - unicode_version: "12.1", - }, - { - emoji: "👨‍🚒", - aliases: ["man_firefighter"], - tags: [], - category: "People & Body", - description: "man firefighter", - unicode_version: "", - }, - { - emoji: "👩‍🚒", - aliases: ["woman_firefighter"], - tags: [], - category: "People & Body", - description: "woman firefighter", - unicode_version: "", - }, - { - emoji: "👮", - aliases: ["police_officer", "cop"], - tags: ["law"], - category: "People & Body", - description: "police officer", - unicode_version: "6.0", - }, - { - emoji: "👮‍♂️", - aliases: ["policeman"], - tags: ["law", "cop"], - category: "People & Body", - description: "man police officer", - unicode_version: "11.0", - }, - { - emoji: "👮‍♀️", - aliases: ["policewoman"], - tags: ["law", "cop"], - category: "People & Body", - description: "woman police officer", - unicode_version: "6.0", - }, - { - emoji: "🕵️", - aliases: ["detective"], - tags: ["sleuth"], - category: "People & Body", - description: "detective", - unicode_version: "7.0", - }, - { - emoji: "🕵️‍♂️", - aliases: ["male_detective"], - tags: ["sleuth"], - category: "People & Body", - description: "man detective", - unicode_version: "11.0", - }, - { - emoji: "🕵️‍♀️", - aliases: ["female_detective"], - tags: ["sleuth"], - category: "People & Body", - description: "woman detective", - unicode_version: "6.0", - }, - { - emoji: "💂", - aliases: ["guard"], - tags: [], - category: "People & Body", - description: "guard", - unicode_version: "6.0", - }, - { - emoji: "💂‍♂️", - aliases: ["guardsman"], - tags: [], - category: "People & Body", - description: "man guard", - unicode_version: "11.0", - }, - { - emoji: "💂‍♀️", - aliases: ["guardswoman"], - tags: [], - category: "People & Body", - description: "woman guard", - unicode_version: "6.0", - }, - { - emoji: "🥷", - aliases: ["ninja"], - tags: [], - category: "People & Body", - description: "ninja", - unicode_version: "13.0", - }, - { - emoji: "👷", - aliases: ["construction_worker"], - tags: ["helmet"], - category: "People & Body", - description: "construction worker", - unicode_version: "6.0", - }, - { - emoji: "👷‍♂️", - aliases: ["construction_worker_man"], - tags: ["helmet"], - category: "People & Body", - description: "man construction worker", - unicode_version: "11.0", - }, - { - emoji: "👷‍♀️", - aliases: ["construction_worker_woman"], - tags: ["helmet"], - category: "People & Body", - description: "woman construction worker", - unicode_version: "6.0", - }, - { - emoji: "🤴", - aliases: ["prince"], - tags: ["crown", "royal"], - category: "People & Body", - description: "prince", - unicode_version: "9.0", - }, - { - emoji: "👸", - aliases: ["princess"], - tags: ["crown", "royal"], - category: "People & Body", - description: "princess", - unicode_version: "6.0", - }, - { - emoji: "👳", - aliases: ["person_with_turban"], - tags: [], - category: "People & Body", - description: "person wearing turban", - unicode_version: "6.0", - }, - { - emoji: "👳‍♂️", - aliases: ["man_with_turban"], - tags: [], - category: "People & Body", - description: "man wearing turban", - unicode_version: "11.0", - }, - { - emoji: "👳‍♀️", - aliases: ["woman_with_turban"], - tags: [], - category: "People & Body", - description: "woman wearing turban", - unicode_version: "6.0", - }, - { - emoji: "👲", - aliases: ["man_with_gua_pi_mao"], - tags: [], - category: "People & Body", - description: "person with skullcap", - unicode_version: "6.0", - }, - { - emoji: "🧕", - aliases: ["woman_with_headscarf"], - tags: ["hijab"], - category: "People & Body", - description: "woman with headscarf", - unicode_version: "11.0", - }, - { - emoji: "🤵", - aliases: ["person_in_tuxedo"], - tags: ["groom", "marriage", "wedding"], - category: "People & Body", - description: "person in tuxedo", - unicode_version: "9.0", - }, - { - emoji: "🤵‍♂️", - aliases: ["man_in_tuxedo"], - tags: [], - category: "People & Body", - description: "man in tuxedo", - unicode_version: "13.0", - }, - { - emoji: "🤵‍♀️", - aliases: ["woman_in_tuxedo"], - tags: [], - category: "People & Body", - description: "woman in tuxedo", - unicode_version: "13.0", - }, - { - emoji: "👰", - aliases: ["person_with_veil"], - tags: ["marriage", "wedding"], - category: "People & Body", - description: "person with veil", - unicode_version: "6.0", - }, - { - emoji: "👰‍♂️", - aliases: ["man_with_veil"], - tags: [], - category: "People & Body", - description: "man with veil", - unicode_version: "13.0", - }, - { - emoji: "👰‍♀️", - aliases: ["woman_with_veil", "bride_with_veil"], - tags: [], - category: "People & Body", - description: "woman with veil", - unicode_version: "13.0", - }, - { - emoji: "🤰", - aliases: ["pregnant_woman"], - tags: [], - category: "People & Body", - description: "pregnant woman", - unicode_version: "9.0", - }, - { - emoji: "🤱", - aliases: ["breast_feeding"], - tags: ["nursing"], - category: "People & Body", - description: "breast-feeding", - unicode_version: "11.0", - }, - { - emoji: "👩‍🍼", - aliases: ["woman_feeding_baby"], - tags: [], - category: "People & Body", - description: "woman feeding baby", - unicode_version: "13.0", - }, - { - emoji: "👨‍🍼", - aliases: ["man_feeding_baby"], - tags: [], - category: "People & Body", - description: "man feeding baby", - unicode_version: "13.0", - }, - { - emoji: "🧑‍🍼", - aliases: ["person_feeding_baby"], - tags: [], - category: "People & Body", - description: "person feeding baby", - unicode_version: "13.0", - }, - { - emoji: "👼", - aliases: ["angel"], - tags: [], - category: "People & Body", - description: "baby angel", - unicode_version: "6.0", - }, - { - emoji: "🎅", - aliases: ["santa"], - tags: ["christmas"], - category: "People & Body", - description: "Santa Claus", - unicode_version: "6.0", - }, - { - emoji: "🤶", - aliases: ["mrs_claus"], - tags: ["santa"], - category: "People & Body", - description: "Mrs. Claus", - unicode_version: "9.0", - }, - { - emoji: "🧑‍🎄", - aliases: ["mx_claus"], - tags: [], - category: "People & Body", - description: "mx claus", - unicode_version: "13.0", - }, - { - emoji: "🦸", - aliases: ["superhero"], - tags: [], - category: "People & Body", - description: "superhero", - unicode_version: "11.0", - }, - { - emoji: "🦸‍♂️", - aliases: ["superhero_man"], - tags: [], - category: "People & Body", - description: "man superhero", - unicode_version: "11.0", - }, - { - emoji: "🦸‍♀️", - aliases: ["superhero_woman"], - tags: [], - category: "People & Body", - description: "woman superhero", - unicode_version: "11.0", - }, - { - emoji: "🦹", - aliases: ["supervillain"], - tags: [], - category: "People & Body", - description: "supervillain", - unicode_version: "11.0", - }, - { - emoji: "🦹‍♂️", - aliases: ["supervillain_man"], - tags: [], - category: "People & Body", - description: "man supervillain", - unicode_version: "11.0", - }, - { - emoji: "🦹‍♀️", - aliases: ["supervillain_woman"], - tags: [], - category: "People & Body", - description: "woman supervillain", - unicode_version: "11.0", - }, - { - emoji: "🧙", - aliases: ["mage"], - tags: ["wizard"], - category: "People & Body", - description: "mage", - unicode_version: "11.0", - }, - { - emoji: "🧙‍♂️", - aliases: ["mage_man"], - tags: ["wizard"], - category: "People & Body", - description: "man mage", - unicode_version: "11.0", - }, - { - emoji: "🧙‍♀️", - aliases: ["mage_woman"], - tags: ["wizard"], - category: "People & Body", - description: "woman mage", - unicode_version: "11.0", - }, - { - emoji: "🧚", - aliases: ["fairy"], - tags: [], - category: "People & Body", - description: "fairy", - unicode_version: "11.0", - }, - { - emoji: "🧚‍♂️", - aliases: ["fairy_man"], - tags: [], - category: "People & Body", - description: "man fairy", - unicode_version: "11.0", - }, - { - emoji: "🧚‍♀️", - aliases: ["fairy_woman"], - tags: [], - category: "People & Body", - description: "woman fairy", - unicode_version: "11.0", - }, - { - emoji: "🧛", - aliases: ["vampire"], - tags: [], - category: "People & Body", - description: "vampire", - unicode_version: "11.0", - }, - { - emoji: "🧛‍♂️", - aliases: ["vampire_man"], - tags: [], - category: "People & Body", - description: "man vampire", - unicode_version: "11.0", - }, - { - emoji: "🧛‍♀️", - aliases: ["vampire_woman"], - tags: [], - category: "People & Body", - description: "woman vampire", - unicode_version: "11.0", - }, - { - emoji: "🧜", - aliases: ["merperson"], - tags: [], - category: "People & Body", - description: "merperson", - unicode_version: "11.0", - }, - { - emoji: "🧜‍♂️", - aliases: ["merman"], - tags: [], - category: "People & Body", - description: "merman", - unicode_version: "11.0", - }, - { - emoji: "🧜‍♀️", - aliases: ["mermaid"], - tags: [], - category: "People & Body", - description: "mermaid", - unicode_version: "11.0", - }, - { - emoji: "🧝", - aliases: ["elf"], - tags: [], - category: "People & Body", - description: "elf", - unicode_version: "11.0", - }, - { - emoji: "🧝‍♂️", - aliases: ["elf_man"], - tags: [], - category: "People & Body", - description: "man elf", - unicode_version: "11.0", - }, - { - emoji: "🧝‍♀️", - aliases: ["elf_woman"], - tags: [], - category: "People & Body", - description: "woman elf", - unicode_version: "11.0", - }, - { - emoji: "🧞", - aliases: ["genie"], - tags: [], - category: "People & Body", - description: "genie", - unicode_version: "11.0", - }, - { - emoji: "🧞‍♂️", - aliases: ["genie_man"], - tags: [], - category: "People & Body", - description: "man genie", - unicode_version: "11.0", - }, - { - emoji: "🧞‍♀️", - aliases: ["genie_woman"], - tags: [], - category: "People & Body", - description: "woman genie", - unicode_version: "11.0", - }, - { - emoji: "🧟", - aliases: ["zombie"], - tags: [], - category: "People & Body", - description: "zombie", - unicode_version: "11.0", - }, - { - emoji: "🧟‍♂️", - aliases: ["zombie_man"], - tags: [], - category: "People & Body", - description: "man zombie", - unicode_version: "11.0", - }, - { - emoji: "🧟‍♀️", - aliases: ["zombie_woman"], - tags: [], - category: "People & Body", - description: "woman zombie", - unicode_version: "11.0", - }, - { - emoji: "💆", - aliases: ["massage"], - tags: ["spa"], - category: "People & Body", - description: "person getting massage", - unicode_version: "6.0", - }, - { - emoji: "💆‍♂️", - aliases: ["massage_man"], - tags: ["spa"], - category: "People & Body", - description: "man getting massage", - unicode_version: "6.0", - }, - { - emoji: "💆‍♀️", - aliases: ["massage_woman"], - tags: ["spa"], - category: "People & Body", - description: "woman getting massage", - unicode_version: "11.0", - }, - { - emoji: "💇", - aliases: ["haircut"], - tags: ["beauty"], - category: "People & Body", - description: "person getting haircut", - unicode_version: "6.0", - }, - { - emoji: "💇‍♂️", - aliases: ["haircut_man"], - tags: [], - category: "People & Body", - description: "man getting haircut", - unicode_version: "6.0", - }, - { - emoji: "💇‍♀️", - aliases: ["haircut_woman"], - tags: [], - category: "People & Body", - description: "woman getting haircut", - unicode_version: "11.0", - }, - { - emoji: "🚶", - aliases: ["walking"], - tags: [], - category: "People & Body", - description: "person walking", - unicode_version: "6.0", - }, - { - emoji: "🚶‍♂️", - aliases: ["walking_man"], - tags: [], - category: "People & Body", - description: "man walking", - unicode_version: "11.0", - }, - { - emoji: "🚶‍♀️", - aliases: ["walking_woman"], - tags: [], - category: "People & Body", - description: "woman walking", - unicode_version: "6.0", - }, - { - emoji: "🧍", - aliases: ["standing_person"], - tags: [], - category: "People & Body", - description: "person standing", - unicode_version: "12.0", - }, - { - emoji: "🧍‍♂️", - aliases: ["standing_man"], - tags: [], - category: "People & Body", - description: "man standing", - unicode_version: "12.0", - }, - { - emoji: "🧍‍♀️", - aliases: ["standing_woman"], - tags: [], - category: "People & Body", - description: "woman standing", - unicode_version: "12.0", - }, - { - emoji: "🧎", - aliases: ["kneeling_person"], - tags: [], - category: "People & Body", - description: "person kneeling", - unicode_version: "12.0", - }, - { - emoji: "🧎‍♂️", - aliases: ["kneeling_man"], - tags: [], - category: "People & Body", - description: "man kneeling", - unicode_version: "12.0", - }, - { - emoji: "🧎‍♀️", - aliases: ["kneeling_woman"], - tags: [], - category: "People & Body", - description: "woman kneeling", - unicode_version: "12.0", - }, - { - emoji: "🧑‍🦯", - aliases: ["person_with_probing_cane"], - tags: [], - category: "People & Body", - description: "person with white cane", - unicode_version: "12.1", - }, - { - emoji: "👨‍🦯", - aliases: ["man_with_probing_cane"], - tags: [], - category: "People & Body", - description: "man with white cane", - unicode_version: "12.0", - }, - { - emoji: "👩‍🦯", - aliases: ["woman_with_probing_cane"], - tags: [], - category: "People & Body", - description: "woman with white cane", - unicode_version: "12.0", - }, - { - emoji: "🧑‍🦼", - aliases: ["person_in_motorized_wheelchair"], - tags: [], - category: "People & Body", - description: "person in motorized wheelchair", - unicode_version: "12.1", - }, - { - emoji: "👨‍🦼", - aliases: ["man_in_motorized_wheelchair"], - tags: [], - category: "People & Body", - description: "man in motorized wheelchair", - unicode_version: "12.0", - }, - { - emoji: "👩‍🦼", - aliases: ["woman_in_motorized_wheelchair"], - tags: [], - category: "People & Body", - description: "woman in motorized wheelchair", - unicode_version: "12.0", - }, - { - emoji: "🧑‍🦽", - aliases: ["person_in_manual_wheelchair"], - tags: [], - category: "People & Body", - description: "person in manual wheelchair", - unicode_version: "12.1", - }, - { - emoji: "👨‍🦽", - aliases: ["man_in_manual_wheelchair"], - tags: [], - category: "People & Body", - description: "man in manual wheelchair", - unicode_version: "12.0", - }, - { - emoji: "👩‍🦽", - aliases: ["woman_in_manual_wheelchair"], - tags: [], - category: "People & Body", - description: "woman in manual wheelchair", - unicode_version: "12.0", - }, - { - emoji: "🏃", - aliases: ["runner", "running"], - tags: ["exercise", "workout", "marathon"], - category: "People & Body", - description: "person running", - unicode_version: "6.0", - }, - { - emoji: "🏃‍♂️", - aliases: ["running_man"], - tags: ["exercise", "workout", "marathon"], - category: "People & Body", - description: "man running", - unicode_version: "11.0", - }, - { - emoji: "🏃‍♀️", - aliases: ["running_woman"], - tags: ["exercise", "workout", "marathon"], - category: "People & Body", - description: "woman running", - unicode_version: "6.0", - }, - { - emoji: "💃", - aliases: ["woman_dancing", "dancer"], - tags: ["dress"], - category: "People & Body", - description: "woman dancing", - unicode_version: "6.0", - }, - { - emoji: "🕺", - aliases: ["man_dancing"], - tags: ["dancer"], - category: "People & Body", - description: "man dancing", - unicode_version: "9.0", - }, - { - emoji: "🕴️", - aliases: ["business_suit_levitating"], - tags: [], - category: "People & Body", - description: "person in suit levitating", - unicode_version: "7.0", - }, - { - emoji: "👯", - aliases: ["dancers"], - tags: ["bunny"], - category: "People & Body", - description: "people with bunny ears", - unicode_version: "6.0", - }, - { - emoji: "👯‍♂️", - aliases: ["dancing_men"], - tags: ["bunny"], - category: "People & Body", - description: "men with bunny ears", - unicode_version: "6.0", - }, - { - emoji: "👯‍♀️", - aliases: ["dancing_women"], - tags: ["bunny"], - category: "People & Body", - description: "women with bunny ears", - unicode_version: "11.0", - }, - { - emoji: "🧖", - aliases: ["sauna_person"], - tags: ["steamy"], - category: "People & Body", - description: "person in steamy room", - unicode_version: "11.0", - }, - { - emoji: "🧖‍♂️", - aliases: ["sauna_man"], - tags: ["steamy"], - category: "People & Body", - description: "man in steamy room", - unicode_version: "11.0", - }, - { - emoji: "🧖‍♀️", - aliases: ["sauna_woman"], - tags: ["steamy"], - category: "People & Body", - description: "woman in steamy room", - unicode_version: "11.0", - }, - { - emoji: "🧗", - aliases: ["climbing"], - tags: ["bouldering"], - category: "People & Body", - description: "person climbing", - unicode_version: "11.0", - }, - { - emoji: "🧗‍♂️", - aliases: ["climbing_man"], - tags: ["bouldering"], - category: "People & Body", - description: "man climbing", - unicode_version: "11.0", - }, - { - emoji: "🧗‍♀️", - aliases: ["climbing_woman"], - tags: ["bouldering"], - category: "People & Body", - description: "woman climbing", - unicode_version: "11.0", - }, - { - emoji: "🤺", - aliases: ["person_fencing"], - tags: [], - category: "People & Body", - description: "person fencing", - unicode_version: "9.0", - }, - { - emoji: "🏇", - aliases: ["horse_racing"], - tags: [], - category: "People & Body", - description: "horse racing", - unicode_version: "6.0", - }, - { - emoji: "⛷️", - aliases: ["skier"], - tags: [], - category: "People & Body", - description: "skier", - unicode_version: "5.2", - }, - { - emoji: "🏂", - aliases: ["snowboarder"], - tags: [], - category: "People & Body", - description: "snowboarder", - unicode_version: "6.0", - }, - { - emoji: "🏌️", - aliases: ["golfing"], - tags: [], - category: "People & Body", - description: "person golfing", - unicode_version: "7.0", - }, - { - emoji: "🏌️‍♂️", - aliases: ["golfing_man"], - tags: [], - category: "People & Body", - description: "man golfing", - unicode_version: "11.0", - }, - { - emoji: "🏌️‍♀️", - aliases: ["golfing_woman"], - tags: [], - category: "People & Body", - description: "woman golfing", - unicode_version: "", - }, - { - emoji: "🏄", - aliases: ["surfer"], - tags: [], - category: "People & Body", - description: "person surfing", - unicode_version: "6.0", - }, - { - emoji: "🏄‍♂️", - aliases: ["surfing_man"], - tags: [], - category: "People & Body", - description: "man surfing", - unicode_version: "11.0", - }, - { - emoji: "🏄‍♀️", - aliases: ["surfing_woman"], - tags: [], - category: "People & Body", - description: "woman surfing", - unicode_version: "7.0", - }, - { - emoji: "🚣", - aliases: ["rowboat"], - tags: [], - category: "People & Body", - description: "person rowing boat", - unicode_version: "6.0", - }, - { - emoji: "🚣‍♂️", - aliases: ["rowing_man"], - tags: [], - category: "People & Body", - description: "man rowing boat", - unicode_version: "11.0", - }, - { - emoji: "🚣‍♀️", - aliases: ["rowing_woman"], - tags: [], - category: "People & Body", - description: "woman rowing boat", - unicode_version: "6.0", - }, - { - emoji: "🏊", - aliases: ["swimmer"], - tags: [], - category: "People & Body", - description: "person swimming", - unicode_version: "6.0", - }, - { - emoji: "🏊‍♂️", - aliases: ["swimming_man"], - tags: [], - category: "People & Body", - description: "man swimming", - unicode_version: "11.0", - }, - { - emoji: "🏊‍♀️", - aliases: ["swimming_woman"], - tags: [], - category: "People & Body", - description: "woman swimming", - unicode_version: "6.0", - }, - { - emoji: "⛹️", - aliases: ["bouncing_ball_person"], - tags: ["basketball"], - category: "People & Body", - description: "person bouncing ball", - unicode_version: "5.2", - }, - { - emoji: "⛹️‍♂️", - aliases: ["bouncing_ball_man", "basketball_man"], - tags: [], - category: "People & Body", - description: "man bouncing ball", - unicode_version: "11.0", - }, - { - emoji: "⛹️‍♀️", - aliases: ["bouncing_ball_woman", "basketball_woman"], - tags: [], - category: "People & Body", - description: "woman bouncing ball", - unicode_version: "7.0", - }, - { - emoji: "🏋️", - aliases: ["weight_lifting"], - tags: ["gym", "workout"], - category: "People & Body", - description: "person lifting weights", - unicode_version: "7.0", - }, - { - emoji: "🏋️‍♂️", - aliases: ["weight_lifting_man"], - tags: ["gym", "workout"], - category: "People & Body", - description: "man lifting weights", - unicode_version: "11.0", - }, - { - emoji: "🏋️‍♀️", - aliases: ["weight_lifting_woman"], - tags: ["gym", "workout"], - category: "People & Body", - description: "woman lifting weights", - unicode_version: "6.0", - }, - { - emoji: "🚴", - aliases: ["bicyclist"], - tags: [], - category: "People & Body", - description: "person biking", - unicode_version: "6.0", - }, - { - emoji: "🚴‍♂️", - aliases: ["biking_man"], - tags: [], - category: "People & Body", - description: "man biking", - unicode_version: "11.0", - }, - { - emoji: "🚴‍♀️", - aliases: ["biking_woman"], - tags: [], - category: "People & Body", - description: "woman biking", - unicode_version: "6.0", - }, - { - emoji: "🚵", - aliases: ["mountain_bicyclist"], - tags: [], - category: "People & Body", - description: "person mountain biking", - unicode_version: "6.0", - }, - { - emoji: "🚵‍♂️", - aliases: ["mountain_biking_man"], - tags: [], - category: "People & Body", - description: "man mountain biking", - unicode_version: "11.0", - }, - { - emoji: "🚵‍♀️", - aliases: ["mountain_biking_woman"], - tags: [], - category: "People & Body", - description: "woman mountain biking", - unicode_version: "6.0", - }, - { - emoji: "🤸", - aliases: ["cartwheeling"], - tags: [], - category: "People & Body", - description: "person cartwheeling", - unicode_version: "11.0", - }, - { - emoji: "🤸‍♂️", - aliases: ["man_cartwheeling"], - tags: [], - category: "People & Body", - description: "man cartwheeling", - unicode_version: "", - }, - { - emoji: "🤸‍♀️", - aliases: ["woman_cartwheeling"], - tags: [], - category: "People & Body", - description: "woman cartwheeling", - unicode_version: "", - }, - { - emoji: "🤼", - aliases: ["wrestling"], - tags: [], - category: "People & Body", - description: "people wrestling", - unicode_version: "11.0", - }, - { - emoji: "🤼‍♂️", - aliases: ["men_wrestling"], - tags: [], - category: "People & Body", - description: "men wrestling", - unicode_version: "9.0", - }, - { - emoji: "🤼‍♀️", - aliases: ["women_wrestling"], - tags: [], - category: "People & Body", - description: "women wrestling", - unicode_version: "9.0", - }, - { - emoji: "🤽", - aliases: ["water_polo"], - tags: [], - category: "People & Body", - description: "person playing water polo", - unicode_version: "11.0", - }, - { - emoji: "🤽‍♂️", - aliases: ["man_playing_water_polo"], - tags: [], - category: "People & Body", - description: "man playing water polo", - unicode_version: "9.0", - }, - { - emoji: "🤽‍♀️", - aliases: ["woman_playing_water_polo"], - tags: [], - category: "People & Body", - description: "woman playing water polo", - unicode_version: "9.0", - }, - { - emoji: "🤾", - aliases: ["handball_person"], - tags: [], - category: "People & Body", - description: "person playing handball", - unicode_version: "11.0", - }, - { - emoji: "🤾‍♂️", - aliases: ["man_playing_handball"], - tags: [], - category: "People & Body", - description: "man playing handball", - unicode_version: "9.0", - }, - { - emoji: "🤾‍♀️", - aliases: ["woman_playing_handball"], - tags: [], - category: "People & Body", - description: "woman playing handball", - unicode_version: "9.0", - }, - { - emoji: "🤹", - aliases: ["juggling_person"], - tags: [], - category: "People & Body", - description: "person juggling", - unicode_version: "11.0", - }, - { - emoji: "🤹‍♂️", - aliases: ["man_juggling"], - tags: [], - category: "People & Body", - description: "man juggling", - unicode_version: "9.0", - }, - { - emoji: "🤹‍♀️", - aliases: ["woman_juggling"], - tags: [], - category: "People & Body", - description: "woman juggling", - unicode_version: "9.0", - }, - { - emoji: "🧘", - aliases: ["lotus_position"], - tags: ["meditation"], - category: "People & Body", - description: "person in lotus position", - unicode_version: "11.0", - }, - { - emoji: "🧘‍♂️", - aliases: ["lotus_position_man"], - tags: ["meditation"], - category: "People & Body", - description: "man in lotus position", - unicode_version: "11.0", - }, - { - emoji: "🧘‍♀️", - aliases: ["lotus_position_woman"], - tags: ["meditation"], - category: "People & Body", - description: "woman in lotus position", - unicode_version: "11.0", - }, - { - emoji: "🛀", - aliases: ["bath"], - tags: ["shower"], - category: "People & Body", - description: "person taking bath", - unicode_version: "6.0", - }, - { - emoji: "🛌", - aliases: ["sleeping_bed"], - tags: [], - category: "People & Body", - description: "person in bed", - unicode_version: "7.0", - }, - { - emoji: "🧑‍🤝‍🧑", - aliases: ["people_holding_hands"], - tags: ["couple", "date"], - category: "People & Body", - description: "people holding hands", - unicode_version: "12.0", - }, - { - emoji: "👭", - aliases: ["two_women_holding_hands"], - tags: ["couple", "date"], - category: "People & Body", - description: "women holding hands", - unicode_version: "6.0", - }, - { - emoji: "👫", - aliases: ["couple"], - tags: ["date"], - category: "People & Body", - description: "woman and man holding hands", - unicode_version: "6.0", - }, - { - emoji: "👬", - aliases: ["two_men_holding_hands"], - tags: ["couple", "date"], - category: "People & Body", - description: "men holding hands", - unicode_version: "6.0", - }, - { - emoji: "💏", - aliases: ["couplekiss"], - tags: [], - category: "People & Body", - description: "kiss", - unicode_version: "6.0", - }, - { - emoji: "👩‍❤️‍💋‍👨", - aliases: ["couplekiss_man_woman"], - tags: [], - category: "People & Body", - description: "kiss: woman, man", - unicode_version: "11.0", - }, - { - emoji: "👨‍❤️‍💋‍👨", - aliases: ["couplekiss_man_man"], - tags: [], - category: "People & Body", - description: "kiss: man, man", - unicode_version: "6.0", - }, - { - emoji: "👩‍❤️‍💋‍👩", - aliases: ["couplekiss_woman_woman"], - tags: [], - category: "People & Body", - description: "kiss: woman, woman", - unicode_version: "6.0", - }, - { - emoji: "💑", - aliases: ["couple_with_heart"], - tags: [], - category: "People & Body", - description: "couple with heart", - unicode_version: "6.0", - }, - { - emoji: "👩‍❤️‍👨", - aliases: ["couple_with_heart_woman_man"], - tags: [], - category: "People & Body", - description: "couple with heart: woman, man", - unicode_version: "11.0", - }, - { - emoji: "👨‍❤️‍👨", - aliases: ["couple_with_heart_man_man"], - tags: [], - category: "People & Body", - description: "couple with heart: man, man", - unicode_version: "6.0", - }, - { - emoji: "👩‍❤️‍👩", - aliases: ["couple_with_heart_woman_woman"], - tags: [], - category: "People & Body", - description: "couple with heart: woman, woman", - unicode_version: "6.0", - }, - { - emoji: "👪", - aliases: ["family"], - tags: ["home", "parents", "child"], - category: "People & Body", - description: "family", - unicode_version: "6.0", - }, - { - emoji: "👨‍👩‍👦", - aliases: ["family_man_woman_boy"], - tags: [], - category: "People & Body", - description: "family: man, woman, boy", - unicode_version: "11.0", - }, - { - emoji: "👨‍👩‍👧", - aliases: ["family_man_woman_girl"], - tags: [], - category: "People & Body", - description: "family: man, woman, girl", - unicode_version: "6.0", - }, - { - emoji: "👨‍👩‍👧‍👦", - aliases: ["family_man_woman_girl_boy"], - tags: [], - category: "People & Body", - description: "family: man, woman, girl, boy", - unicode_version: "6.0", - }, - { - emoji: "👨‍👩‍👦‍👦", - aliases: ["family_man_woman_boy_boy"], - tags: [], - category: "People & Body", - description: "family: man, woman, boy, boy", - unicode_version: "6.0", - }, - { - emoji: "👨‍👩‍👧‍👧", - aliases: ["family_man_woman_girl_girl"], - tags: [], - category: "People & Body", - description: "family: man, woman, girl, girl", - unicode_version: "6.0", - }, - { - emoji: "👨‍👨‍👦", - aliases: ["family_man_man_boy"], - tags: [], - category: "People & Body", - description: "family: man, man, boy", - unicode_version: "6.0", - }, - { - emoji: "👨‍👨‍👧", - aliases: ["family_man_man_girl"], - tags: [], - category: "People & Body", - description: "family: man, man, girl", - unicode_version: "6.0", - }, - { - emoji: "👨‍👨‍👧‍👦", - aliases: ["family_man_man_girl_boy"], - tags: [], - category: "People & Body", - description: "family: man, man, girl, boy", - unicode_version: "6.0", - }, - { - emoji: "👨‍👨‍👦‍👦", - aliases: ["family_man_man_boy_boy"], - tags: [], - category: "People & Body", - description: "family: man, man, boy, boy", - unicode_version: "6.0", - }, - { - emoji: "👨‍👨‍👧‍👧", - aliases: ["family_man_man_girl_girl"], - tags: [], - category: "People & Body", - description: "family: man, man, girl, girl", - unicode_version: "6.0", - }, - { - emoji: "👩‍👩‍👦", - aliases: ["family_woman_woman_boy"], - tags: [], - category: "People & Body", - description: "family: woman, woman, boy", - unicode_version: "6.0", - }, - { - emoji: "👩‍👩‍👧", - aliases: ["family_woman_woman_girl"], - tags: [], - category: "People & Body", - description: "family: woman, woman, girl", - unicode_version: "6.0", - }, - { - emoji: "👩‍👩‍👧‍👦", - aliases: ["family_woman_woman_girl_boy"], - tags: [], - category: "People & Body", - description: "family: woman, woman, girl, boy", - unicode_version: "6.0", - }, - { - emoji: "👩‍👩‍👦‍👦", - aliases: ["family_woman_woman_boy_boy"], - tags: [], - category: "People & Body", - description: "family: woman, woman, boy, boy", - unicode_version: "6.0", - }, - { - emoji: "👩‍👩‍👧‍👧", - aliases: ["family_woman_woman_girl_girl"], - tags: [], - category: "People & Body", - description: "family: woman, woman, girl, girl", - unicode_version: "6.0", - }, - { - emoji: "👨‍👦", - aliases: ["family_man_boy"], - tags: [], - category: "People & Body", - description: "family: man, boy", - unicode_version: "6.0", - }, - { - emoji: "👨‍👦‍👦", - aliases: ["family_man_boy_boy"], - tags: [], - category: "People & Body", - description: "family: man, boy, boy", - unicode_version: "6.0", - }, - { - emoji: "👨‍👧", - aliases: ["family_man_girl"], - tags: [], - category: "People & Body", - description: "family: man, girl", - unicode_version: "6.0", - }, - { - emoji: "👨‍👧‍👦", - aliases: ["family_man_girl_boy"], - tags: [], - category: "People & Body", - description: "family: man, girl, boy", - unicode_version: "6.0", - }, - { - emoji: "👨‍👧‍👧", - aliases: ["family_man_girl_girl"], - tags: [], - category: "People & Body", - description: "family: man, girl, girl", - unicode_version: "6.0", - }, - { - emoji: "👩‍👦", - aliases: ["family_woman_boy"], - tags: [], - category: "People & Body", - description: "family: woman, boy", - unicode_version: "6.0", - }, - { - emoji: "👩‍👦‍👦", - aliases: ["family_woman_boy_boy"], - tags: [], - category: "People & Body", - description: "family: woman, boy, boy", - unicode_version: "6.0", - }, - { - emoji: "👩‍👧", - aliases: ["family_woman_girl"], - tags: [], - category: "People & Body", - description: "family: woman, girl", - unicode_version: "6.0", - }, - { - emoji: "👩‍👧‍👦", - aliases: ["family_woman_girl_boy"], - tags: [], - category: "People & Body", - description: "family: woman, girl, boy", - unicode_version: "6.0", - }, - { - emoji: "👩‍👧‍👧", - aliases: ["family_woman_girl_girl"], - tags: [], - category: "People & Body", - description: "family: woman, girl, girl", - unicode_version: "6.0", - }, - { - emoji: "🗣️", - aliases: ["speaking_head"], - tags: [], - category: "People & Body", - description: "speaking head", - unicode_version: "7.0", - }, - { - emoji: "👤", - aliases: ["bust_in_silhouette"], - tags: ["user"], - category: "People & Body", - description: "bust in silhouette", - unicode_version: "6.0", - }, - { - emoji: "👥", - aliases: ["busts_in_silhouette"], - tags: ["users", "group", "team"], - category: "People & Body", - description: "busts in silhouette", - unicode_version: "6.0", - }, - { - emoji: "🫂", - aliases: ["people_hugging"], - tags: [], - category: "People & Body", - description: "people hugging", - unicode_version: "13.0", - }, - { - emoji: "👣", - aliases: ["footprints"], - tags: ["feet", "tracks"], - category: "People & Body", - description: "footprints", - unicode_version: "6.0", - }, - { - emoji: "🐵", - aliases: ["monkey_face"], - tags: [], - category: "Animals & Nature", - description: "monkey face", - unicode_version: "6.0", - }, - { - emoji: "🐒", - aliases: ["monkey"], - tags: [], - category: "Animals & Nature", - description: "monkey", - unicode_version: "6.0", - }, - { - emoji: "🦍", - aliases: ["gorilla"], - tags: [], - category: "Animals & Nature", - description: "gorilla", - unicode_version: "9.0", - }, - { - emoji: "🦧", - aliases: ["orangutan"], - tags: [], - category: "Animals & Nature", - description: "orangutan", - unicode_version: "12.0", - }, - { - emoji: "🐶", - aliases: ["dog"], - tags: ["pet"], - category: "Animals & Nature", - description: "dog face", - unicode_version: "6.0", - }, - { - emoji: "🐕", - aliases: ["dog2"], - tags: [], - category: "Animals & Nature", - description: "dog", - unicode_version: "6.0", - }, - { - emoji: "🦮", - aliases: ["guide_dog"], - tags: [], - category: "Animals & Nature", - description: "guide dog", - unicode_version: "12.0", - }, - { - emoji: "🐕‍🦺", - aliases: ["service_dog"], - tags: [], - category: "Animals & Nature", - description: "service dog", - unicode_version: "12.0", - }, - { - emoji: "🐩", - aliases: ["poodle"], - tags: ["dog"], - category: "Animals & Nature", - description: "poodle", - unicode_version: "6.0", - }, - { - emoji: "🐺", - aliases: ["wolf"], - tags: [], - category: "Animals & Nature", - description: "wolf", - unicode_version: "6.0", - }, - { - emoji: "🦊", - aliases: ["fox_face"], - tags: [], - category: "Animals & Nature", - description: "fox", - unicode_version: "9.0", - }, - { - emoji: "🦝", - aliases: ["raccoon"], - tags: [], - category: "Animals & Nature", - description: "raccoon", - unicode_version: "11.0", - }, - { - emoji: "🐱", - aliases: ["cat"], - tags: ["pet"], - category: "Animals & Nature", - description: "cat face", - unicode_version: "6.0", - }, - { - emoji: "🐈", - aliases: ["cat2"], - tags: [], - category: "Animals & Nature", - description: "cat", - unicode_version: "6.0", - }, - { - emoji: "🐈‍⬛", - aliases: ["black_cat"], - tags: [], - category: "Animals & Nature", - description: "black cat", - unicode_version: "13.0", - }, - { - emoji: "🦁", - aliases: ["lion"], - tags: [], - category: "Animals & Nature", - description: "lion", - unicode_version: "8.0", - }, - { - emoji: "🐯", - aliases: ["tiger"], - tags: [], - category: "Animals & Nature", - description: "tiger face", - unicode_version: "6.0", - }, - { - emoji: "🐅", - aliases: ["tiger2"], - tags: [], - category: "Animals & Nature", - description: "tiger", - unicode_version: "6.0", - }, - { - emoji: "🐆", - aliases: ["leopard"], - tags: [], - category: "Animals & Nature", - description: "leopard", - unicode_version: "6.0", - }, - { - emoji: "🐴", - aliases: ["horse"], - tags: [], - category: "Animals & Nature", - description: "horse face", - unicode_version: "6.0", - }, - { - emoji: "🐎", - aliases: ["racehorse"], - tags: ["speed"], - category: "Animals & Nature", - description: "horse", - unicode_version: "6.0", - }, - { - emoji: "🦄", - aliases: ["unicorn"], - tags: [], - category: "Animals & Nature", - description: "unicorn", - unicode_version: "8.0", - }, - { - emoji: "🦓", - aliases: ["zebra"], - tags: [], - category: "Animals & Nature", - description: "zebra", - unicode_version: "11.0", - }, - { - emoji: "🦌", - aliases: ["deer"], - tags: [], - category: "Animals & Nature", - description: "deer", - unicode_version: "9.0", - }, - { - emoji: "🦬", - aliases: ["bison"], - tags: [], - category: "Animals & Nature", - description: "bison", - unicode_version: "13.0", - }, - { - emoji: "🐮", - aliases: ["cow"], - tags: [], - category: "Animals & Nature", - description: "cow face", - unicode_version: "6.0", - }, - { - emoji: "🐂", - aliases: ["ox"], - tags: [], - category: "Animals & Nature", - description: "ox", - unicode_version: "6.0", - }, - { - emoji: "🐃", - aliases: ["water_buffalo"], - tags: [], - category: "Animals & Nature", - description: "water buffalo", - unicode_version: "6.0", - }, - { - emoji: "🐄", - aliases: ["cow2"], - tags: [], - category: "Animals & Nature", - description: "cow", - unicode_version: "6.0", - }, - { - emoji: "🐷", - aliases: ["pig"], - tags: [], - category: "Animals & Nature", - description: "pig face", - unicode_version: "6.0", - }, - { - emoji: "🐖", - aliases: ["pig2"], - tags: [], - category: "Animals & Nature", - description: "pig", - unicode_version: "6.0", - }, - { - emoji: "🐗", - aliases: ["boar"], - tags: [], - category: "Animals & Nature", - description: "boar", - unicode_version: "6.0", - }, - { - emoji: "🐽", - aliases: ["pig_nose"], - tags: [], - category: "Animals & Nature", - description: "pig nose", - unicode_version: "6.0", - }, - { - emoji: "🐏", - aliases: ["ram"], - tags: [], - category: "Animals & Nature", - description: "ram", - unicode_version: "6.0", - }, - { - emoji: "🐑", - aliases: ["sheep"], - tags: [], - category: "Animals & Nature", - description: "ewe", - unicode_version: "6.0", - }, - { - emoji: "🐐", - aliases: ["goat"], - tags: [], - category: "Animals & Nature", - description: "goat", - unicode_version: "6.0", - }, - { - emoji: "🐪", - aliases: ["dromedary_camel"], - tags: ["desert"], - category: "Animals & Nature", - description: "camel", - unicode_version: "6.0", - }, - { - emoji: "🐫", - aliases: ["camel"], - tags: [], - category: "Animals & Nature", - description: "two-hump camel", - unicode_version: "6.0", - }, - { - emoji: "🦙", - aliases: ["llama"], - tags: [], - category: "Animals & Nature", - description: "llama", - unicode_version: "11.0", - }, - { - emoji: "🦒", - aliases: ["giraffe"], - tags: [], - category: "Animals & Nature", - description: "giraffe", - unicode_version: "11.0", - }, - { - emoji: "🐘", - aliases: ["elephant"], - tags: [], - category: "Animals & Nature", - description: "elephant", - unicode_version: "6.0", - }, - { - emoji: "🦣", - aliases: ["mammoth"], - tags: [], - category: "Animals & Nature", - description: "mammoth", - unicode_version: "13.0", - }, - { - emoji: "🦏", - aliases: ["rhinoceros"], - tags: [], - category: "Animals & Nature", - description: "rhinoceros", - unicode_version: "9.0", - }, - { - emoji: "🦛", - aliases: ["hippopotamus"], - tags: [], - category: "Animals & Nature", - description: "hippopotamus", - unicode_version: "11.0", - }, - { - emoji: "🐭", - aliases: ["mouse"], - tags: [], - category: "Animals & Nature", - description: "mouse face", - unicode_version: "6.0", - }, - { - emoji: "🐁", - aliases: ["mouse2"], - tags: [], - category: "Animals & Nature", - description: "mouse", - unicode_version: "6.0", - }, - { - emoji: "🐀", - aliases: ["rat"], - tags: [], - category: "Animals & Nature", - description: "rat", - unicode_version: "6.0", - }, - { - emoji: "🐹", - aliases: ["hamster"], - tags: ["pet"], - category: "Animals & Nature", - description: "hamster", - unicode_version: "6.0", - }, - { - emoji: "🐰", - aliases: ["rabbit"], - tags: ["bunny"], - category: "Animals & Nature", - description: "rabbit face", - unicode_version: "6.0", - }, - { - emoji: "🐇", - aliases: ["rabbit2"], - tags: [], - category: "Animals & Nature", - description: "rabbit", - unicode_version: "6.0", - }, - { - emoji: "🐿️", - aliases: ["chipmunk"], - tags: [], - category: "Animals & Nature", - description: "chipmunk", - unicode_version: "7.0", - }, - { - emoji: "🦫", - aliases: ["beaver"], - tags: [], - category: "Animals & Nature", - description: "beaver", - unicode_version: "13.0", - }, - { - emoji: "🦔", - aliases: ["hedgehog"], - tags: [], - category: "Animals & Nature", - description: "hedgehog", - unicode_version: "11.0", - }, - { - emoji: "🦇", - aliases: ["bat"], - tags: [], - category: "Animals & Nature", - description: "bat", - unicode_version: "9.0", - }, - { - emoji: "🐻", - aliases: ["bear"], - tags: [], - category: "Animals & Nature", - description: "bear", - unicode_version: "6.0", - }, - { - emoji: "🐻‍❄️", - aliases: ["polar_bear"], - tags: [], - category: "Animals & Nature", - description: "polar bear", - unicode_version: "13.0", - }, - { - emoji: "🐨", - aliases: ["koala"], - tags: [], - category: "Animals & Nature", - description: "koala", - unicode_version: "6.0", - }, - { - emoji: "🐼", - aliases: ["panda_face"], - tags: [], - category: "Animals & Nature", - description: "panda", - unicode_version: "6.0", - }, - { - emoji: "🦥", - aliases: ["sloth"], - tags: [], - category: "Animals & Nature", - description: "sloth", - unicode_version: "12.0", - }, - { - emoji: "🦦", - aliases: ["otter"], - tags: [], - category: "Animals & Nature", - description: "otter", - unicode_version: "12.0", - }, - { - emoji: "🦨", - aliases: ["skunk"], - tags: [], - category: "Animals & Nature", - description: "skunk", - unicode_version: "12.0", - }, - { - emoji: "🦘", - aliases: ["kangaroo"], - tags: [], - category: "Animals & Nature", - description: "kangaroo", - unicode_version: "11.0", - }, - { - emoji: "🦡", - aliases: ["badger"], - tags: [], - category: "Animals & Nature", - description: "badger", - unicode_version: "11.0", - }, - { - emoji: "🐾", - aliases: ["feet", "paw_prints"], - tags: [], - category: "Animals & Nature", - description: "paw prints", - unicode_version: "6.0", - }, - { - emoji: "🦃", - aliases: ["turkey"], - tags: ["thanksgiving"], - category: "Animals & Nature", - description: "turkey", - unicode_version: "8.0", - }, - { - emoji: "🐔", - aliases: ["chicken"], - tags: [], - category: "Animals & Nature", - description: "chicken", - unicode_version: "6.0", - }, - { - emoji: "🐓", - aliases: ["rooster"], - tags: [], - category: "Animals & Nature", - description: "rooster", - unicode_version: "6.0", - }, - { - emoji: "🐣", - aliases: ["hatching_chick"], - tags: [], - category: "Animals & Nature", - description: "hatching chick", - unicode_version: "6.0", - }, - { - emoji: "🐤", - aliases: ["baby_chick"], - tags: [], - category: "Animals & Nature", - description: "baby chick", - unicode_version: "6.0", - }, - { - emoji: "🐥", - aliases: ["hatched_chick"], - tags: [], - category: "Animals & Nature", - description: "front-facing baby chick", - unicode_version: "6.0", - }, - { - emoji: "🐦", - aliases: ["bird"], - tags: [], - category: "Animals & Nature", - description: "bird", - unicode_version: "6.0", - }, - { - emoji: "🐧", - aliases: ["penguin"], - tags: [], - category: "Animals & Nature", - description: "penguin", - unicode_version: "6.0", - }, - { - emoji: "🕊️", - aliases: ["dove"], - tags: ["peace"], - category: "Animals & Nature", - description: "dove", - unicode_version: "7.0", - }, - { - emoji: "🦅", - aliases: ["eagle"], - tags: [], - category: "Animals & Nature", - description: "eagle", - unicode_version: "9.0", - }, - { - emoji: "🦆", - aliases: ["duck"], - tags: [], - category: "Animals & Nature", - description: "duck", - unicode_version: "9.0", - }, - { - emoji: "🦢", - aliases: ["swan"], - tags: [], - category: "Animals & Nature", - description: "swan", - unicode_version: "11.0", - }, - { - emoji: "🦉", - aliases: ["owl"], - tags: [], - category: "Animals & Nature", - description: "owl", - unicode_version: "9.0", - }, - { - emoji: "🦤", - aliases: ["dodo"], - tags: [], - category: "Animals & Nature", - description: "dodo", - unicode_version: "13.0", - }, - { - emoji: "🪶", - aliases: ["feather"], - tags: [], - category: "Animals & Nature", - description: "feather", - unicode_version: "13.0", - }, - { - emoji: "🦩", - aliases: ["flamingo"], - tags: [], - category: "Animals & Nature", - description: "flamingo", - unicode_version: "12.0", - }, - { - emoji: "🦚", - aliases: ["peacock"], - tags: [], - category: "Animals & Nature", - description: "peacock", - unicode_version: "11.0", - }, - { - emoji: "🦜", - aliases: ["parrot"], - tags: [], - category: "Animals & Nature", - description: "parrot", - unicode_version: "11.0", - }, - { - emoji: "🐸", - aliases: ["frog"], - tags: [], - category: "Animals & Nature", - description: "frog", - unicode_version: "6.0", - }, - { - emoji: "🐊", - aliases: ["crocodile"], - tags: [], - category: "Animals & Nature", - description: "crocodile", - unicode_version: "6.0", - }, - { - emoji: "🐢", - aliases: ["turtle"], - tags: ["slow"], - category: "Animals & Nature", - description: "turtle", - unicode_version: "6.0", - }, - { - emoji: "🦎", - aliases: ["lizard"], - tags: [], - category: "Animals & Nature", - description: "lizard", - unicode_version: "9.0", - }, - { - emoji: "🐍", - aliases: ["snake"], - tags: [], - category: "Animals & Nature", - description: "snake", - unicode_version: "6.0", - }, - { - emoji: "🐲", - aliases: ["dragon_face"], - tags: [], - category: "Animals & Nature", - description: "dragon face", - unicode_version: "6.0", - }, - { - emoji: "🐉", - aliases: ["dragon"], - tags: [], - category: "Animals & Nature", - description: "dragon", - unicode_version: "6.0", - }, - { - emoji: "🦕", - aliases: ["sauropod"], - tags: ["dinosaur"], - category: "Animals & Nature", - description: "sauropod", - unicode_version: "11.0", - }, - { - emoji: "🦖", - aliases: ["t-rex"], - tags: ["dinosaur"], - category: "Animals & Nature", - description: "T-Rex", - unicode_version: "11.0", - }, - { - emoji: "🐳", - aliases: ["whale"], - tags: ["sea"], - category: "Animals & Nature", - description: "spouting whale", - unicode_version: "6.0", - }, - { - emoji: "🐋", - aliases: ["whale2"], - tags: [], - category: "Animals & Nature", - description: "whale", - unicode_version: "6.0", - }, - { - emoji: "🐬", - aliases: ["dolphin", "flipper"], - tags: [], - category: "Animals & Nature", - description: "dolphin", - unicode_version: "6.0", - }, - { - emoji: "🦭", - aliases: ["seal"], - tags: [], - category: "Animals & Nature", - description: "seal", - unicode_version: "13.0", - }, - { - emoji: "🐟", - aliases: ["fish"], - tags: [], - category: "Animals & Nature", - description: "fish", - unicode_version: "6.0", - }, - { - emoji: "🐠", - aliases: ["tropical_fish"], - tags: [], - category: "Animals & Nature", - description: "tropical fish", - unicode_version: "6.0", - }, - { - emoji: "🐡", - aliases: ["blowfish"], - tags: [], - category: "Animals & Nature", - description: "blowfish", - unicode_version: "6.0", - }, - { - emoji: "🦈", - aliases: ["shark"], - tags: [], - category: "Animals & Nature", - description: "shark", - unicode_version: "9.0", - }, - { - emoji: "🐙", - aliases: ["octopus"], - tags: [], - category: "Animals & Nature", - description: "octopus", - unicode_version: "6.0", - }, - { - emoji: "🐚", - aliases: ["shell"], - tags: ["sea", "beach"], - category: "Animals & Nature", - description: "spiral shell", - unicode_version: "6.0", - }, - { - emoji: "🐌", - aliases: ["snail"], - tags: ["slow"], - category: "Animals & Nature", - description: "snail", - unicode_version: "6.0", - }, - { - emoji: "🦋", - aliases: ["butterfly"], - tags: [], - category: "Animals & Nature", - description: "butterfly", - unicode_version: "9.0", - }, - { - emoji: "🐛", - aliases: ["bug"], - tags: [], - category: "Animals & Nature", - description: "bug", - unicode_version: "6.0", - }, - { - emoji: "🐜", - aliases: ["ant"], - tags: [], - category: "Animals & Nature", - description: "ant", - unicode_version: "6.0", - }, - { - emoji: "🐝", - aliases: ["bee", "honeybee"], - tags: [], - category: "Animals & Nature", - description: "honeybee", - unicode_version: "6.0", - }, - { - emoji: "🪲", - aliases: ["beetle"], - tags: [], - category: "Animals & Nature", - description: "beetle", - unicode_version: "13.0", - }, - { - emoji: "🐞", - aliases: ["lady_beetle"], - tags: ["bug"], - category: "Animals & Nature", - description: "lady beetle", - unicode_version: "6.0", - }, - { - emoji: "🦗", - aliases: ["cricket"], - tags: [], - category: "Animals & Nature", - description: "cricket", - unicode_version: "11.0", - }, - { - emoji: "🪳", - aliases: ["cockroach"], - tags: [], - category: "Animals & Nature", - description: "cockroach", - unicode_version: "13.0", - }, - { - emoji: "🕷️", - aliases: ["spider"], - tags: [], - category: "Animals & Nature", - description: "spider", - unicode_version: "7.0", - }, - { - emoji: "🕸️", - aliases: ["spider_web"], - tags: [], - category: "Animals & Nature", - description: "spider web", - unicode_version: "7.0", - }, - { - emoji: "🦂", - aliases: ["scorpion"], - tags: [], - category: "Animals & Nature", - description: "scorpion", - unicode_version: "8.0", - }, - { - emoji: "🦟", - aliases: ["mosquito"], - tags: [], - category: "Animals & Nature", - description: "mosquito", - unicode_version: "11.0", - }, - { - emoji: "🪰", - aliases: ["fly"], - tags: [], - category: "Animals & Nature", - description: "fly", - unicode_version: "13.0", - }, - { - emoji: "🪱", - aliases: ["worm"], - tags: [], - category: "Animals & Nature", - description: "worm", - unicode_version: "13.0", - }, - { - emoji: "🦠", - aliases: ["microbe"], - tags: ["germ"], - category: "Animals & Nature", - description: "microbe", - unicode_version: "11.0", - }, - { - emoji: "💐", - aliases: ["bouquet"], - tags: ["flowers"], - category: "Animals & Nature", - description: "bouquet", - unicode_version: "6.0", - }, - { - emoji: "🌸", - aliases: ["cherry_blossom"], - tags: ["flower", "spring"], - category: "Animals & Nature", - description: "cherry blossom", - unicode_version: "6.0", - }, - { - emoji: "💮", - aliases: ["white_flower"], - tags: [], - category: "Animals & Nature", - description: "white flower", - unicode_version: "6.0", - }, - { - emoji: "🏵️", - aliases: ["rosette"], - tags: [], - category: "Animals & Nature", - description: "rosette", - unicode_version: "7.0", - }, - { - emoji: "🌹", - aliases: ["rose"], - tags: ["flower"], - category: "Animals & Nature", - description: "rose", - unicode_version: "6.0", - }, - { - emoji: "🥀", - aliases: ["wilted_flower"], - tags: [], - category: "Animals & Nature", - description: "wilted flower", - unicode_version: "9.0", - }, - { - emoji: "🌺", - aliases: ["hibiscus"], - tags: [], - category: "Animals & Nature", - description: "hibiscus", - unicode_version: "6.0", - }, - { - emoji: "🌻", - aliases: ["sunflower"], - tags: [], - category: "Animals & Nature", - description: "sunflower", - unicode_version: "6.0", - }, - { - emoji: "🌼", - aliases: ["blossom"], - tags: [], - category: "Animals & Nature", - description: "blossom", - unicode_version: "6.0", - }, - { - emoji: "🌷", - aliases: ["tulip"], - tags: ["flower"], - category: "Animals & Nature", - description: "tulip", - unicode_version: "6.0", - }, - { - emoji: "🌱", - aliases: ["seedling"], - tags: ["plant"], - category: "Animals & Nature", - description: "seedling", - unicode_version: "6.0", - }, - { - emoji: "🪴", - aliases: ["potted_plant"], - tags: [], - category: "Animals & Nature", - description: "potted plant", - unicode_version: "13.0", - }, - { - emoji: "🌲", - aliases: ["evergreen_tree"], - tags: ["wood"], - category: "Animals & Nature", - description: "evergreen tree", - unicode_version: "6.0", - }, - { - emoji: "🌳", - aliases: ["deciduous_tree"], - tags: ["wood"], - category: "Animals & Nature", - description: "deciduous tree", - unicode_version: "6.0", - }, - { - emoji: "🌴", - aliases: ["palm_tree"], - tags: [], - category: "Animals & Nature", - description: "palm tree", - unicode_version: "6.0", - }, - { - emoji: "🌵", - aliases: ["cactus"], - tags: [], - category: "Animals & Nature", - description: "cactus", - unicode_version: "6.0", - }, - { - emoji: "🌾", - aliases: ["ear_of_rice"], - tags: [], - category: "Animals & Nature", - description: "sheaf of rice", - unicode_version: "6.0", - }, - { - emoji: "🌿", - aliases: ["herb"], - tags: [], - category: "Animals & Nature", - description: "herb", - unicode_version: "6.0", - }, - { - emoji: "☘️", - aliases: ["shamrock"], - tags: [], - category: "Animals & Nature", - description: "shamrock", - unicode_version: "4.1", - }, - { - emoji: "🍀", - aliases: ["four_leaf_clover"], - tags: ["luck"], - category: "Animals & Nature", - description: "four leaf clover", - unicode_version: "6.0", - }, - { - emoji: "🍁", - aliases: ["maple_leaf"], - tags: ["canada"], - category: "Animals & Nature", - description: "maple leaf", - unicode_version: "6.0", - }, - { - emoji: "🍂", - aliases: ["fallen_leaf"], - tags: ["autumn"], - category: "Animals & Nature", - description: "fallen leaf", - unicode_version: "6.0", - }, - { - emoji: "🍃", - aliases: ["leaves"], - tags: ["leaf"], - category: "Animals & Nature", - description: "leaf fluttering in wind", - unicode_version: "6.0", - }, - { - emoji: "🍇", - aliases: ["grapes"], - tags: [], - category: "Food & Drink", - description: "grapes", - unicode_version: "6.0", - }, - { - emoji: "🍈", - aliases: ["melon"], - tags: [], - category: "Food & Drink", - description: "melon", - unicode_version: "6.0", - }, - { - emoji: "🍉", - aliases: ["watermelon"], - tags: [], - category: "Food & Drink", - description: "watermelon", - unicode_version: "6.0", - }, - { - emoji: "🍊", - aliases: ["tangerine", "orange", "mandarin"], - tags: [], - category: "Food & Drink", - description: "tangerine", - unicode_version: "6.0", - }, - { - emoji: "🍋", - aliases: ["lemon"], - tags: [], - category: "Food & Drink", - description: "lemon", - unicode_version: "6.0", - }, - { - emoji: "🍌", - aliases: ["banana"], - tags: ["fruit"], - category: "Food & Drink", - description: "banana", - unicode_version: "6.0", - }, - { - emoji: "🍍", - aliases: ["pineapple"], - tags: [], - category: "Food & Drink", - description: "pineapple", - unicode_version: "6.0", - }, - { - emoji: "🥭", - aliases: ["mango"], - tags: [], - category: "Food & Drink", - description: "mango", - unicode_version: "11.0", - }, - { - emoji: "🍎", - aliases: ["apple"], - tags: [], - category: "Food & Drink", - description: "red apple", - unicode_version: "6.0", - }, - { - emoji: "🍏", - aliases: ["green_apple"], - tags: ["fruit"], - category: "Food & Drink", - description: "green apple", - unicode_version: "6.0", - }, - { - emoji: "🍐", - aliases: ["pear"], - tags: [], - category: "Food & Drink", - description: "pear", - unicode_version: "6.0", - }, - { - emoji: "🍑", - aliases: ["peach"], - tags: [], - category: "Food & Drink", - description: "peach", - unicode_version: "6.0", - }, - { - emoji: "🍒", - aliases: ["cherries"], - tags: ["fruit"], - category: "Food & Drink", - description: "cherries", - unicode_version: "6.0", - }, - { - emoji: "🍓", - aliases: ["strawberry"], - tags: ["fruit"], - category: "Food & Drink", - description: "strawberry", - unicode_version: "6.0", - }, - { - emoji: "🫐", - aliases: ["blueberries"], - tags: [], - category: "Food & Drink", - description: "blueberries", - unicode_version: "13.0", - }, - { - emoji: "🥝", - aliases: ["kiwi_fruit"], - tags: [], - category: "Food & Drink", - description: "kiwi fruit", - unicode_version: "9.0", - }, - { - emoji: "🍅", - aliases: ["tomato"], - tags: [], - category: "Food & Drink", - description: "tomato", - unicode_version: "6.0", - }, - { - emoji: "🫒", - aliases: ["olive"], - tags: [], - category: "Food & Drink", - description: "olive", - unicode_version: "13.0", - }, - { - emoji: "🥥", - aliases: ["coconut"], - tags: [], - category: "Food & Drink", - description: "coconut", - unicode_version: "11.0", - }, - { - emoji: "🥑", - aliases: ["avocado"], - tags: [], - category: "Food & Drink", - description: "avocado", - unicode_version: "9.0", - }, - { - emoji: "🍆", - aliases: ["eggplant"], - tags: ["aubergine"], - category: "Food & Drink", - description: "eggplant", - unicode_version: "6.0", - }, - { - emoji: "🥔", - aliases: ["potato"], - tags: [], - category: "Food & Drink", - description: "potato", - unicode_version: "9.0", - }, - { - emoji: "🥕", - aliases: ["carrot"], - tags: [], - category: "Food & Drink", - description: "carrot", - unicode_version: "9.0", - }, - { - emoji: "🌽", - aliases: ["corn"], - tags: [], - category: "Food & Drink", - description: "ear of corn", - unicode_version: "6.0", - }, - { - emoji: "🌶️", - aliases: ["hot_pepper"], - tags: ["spicy"], - category: "Food & Drink", - description: "hot pepper", - unicode_version: "7.0", - }, - { - emoji: "🫑", - aliases: ["bell_pepper"], - tags: [], - category: "Food & Drink", - description: "bell pepper", - unicode_version: "13.0", - }, - { - emoji: "🥒", - aliases: ["cucumber"], - tags: [], - category: "Food & Drink", - description: "cucumber", - unicode_version: "9.0", - }, - { - emoji: "🥬", - aliases: ["leafy_green"], - tags: [], - category: "Food & Drink", - description: "leafy green", - unicode_version: "11.0", - }, - { - emoji: "🥦", - aliases: ["broccoli"], - tags: [], - category: "Food & Drink", - description: "broccoli", - unicode_version: "11.0", - }, - { - emoji: "🧄", - aliases: ["garlic"], - tags: [], - category: "Food & Drink", - description: "garlic", - unicode_version: "12.0", - }, - { - emoji: "🧅", - aliases: ["onion"], - tags: [], - category: "Food & Drink", - description: "onion", - unicode_version: "12.0", - }, - { - emoji: "🍄", - aliases: ["mushroom"], - tags: [], - category: "Food & Drink", - description: "mushroom", - unicode_version: "6.0", - }, - { - emoji: "🥜", - aliases: ["peanuts"], - tags: [], - category: "Food & Drink", - description: "peanuts", - unicode_version: "9.0", - }, - { - emoji: "🌰", - aliases: ["chestnut"], - tags: [], - category: "Food & Drink", - description: "chestnut", - unicode_version: "6.0", - }, - { - emoji: "🍞", - aliases: ["bread"], - tags: ["toast"], - category: "Food & Drink", - description: "bread", - unicode_version: "6.0", - }, - { - emoji: "🥐", - aliases: ["croissant"], - tags: [], - category: "Food & Drink", - description: "croissant", - unicode_version: "9.0", - }, - { - emoji: "🥖", - aliases: ["baguette_bread"], - tags: [], - category: "Food & Drink", - description: "baguette bread", - unicode_version: "9.0", - }, - { - emoji: "🫓", - aliases: ["flatbread"], - tags: [], - category: "Food & Drink", - description: "flatbread", - unicode_version: "13.0", - }, - { - emoji: "🥨", - aliases: ["pretzel"], - tags: [], - category: "Food & Drink", - description: "pretzel", - unicode_version: "11.0", - }, - { - emoji: "🥯", - aliases: ["bagel"], - tags: [], - category: "Food & Drink", - description: "bagel", - unicode_version: "11.0", - }, - { - emoji: "🥞", - aliases: ["pancakes"], - tags: [], - category: "Food & Drink", - description: "pancakes", - unicode_version: "9.0", - }, - { - emoji: "🧇", - aliases: ["waffle"], - tags: [], - category: "Food & Drink", - description: "waffle", - unicode_version: "12.0", - }, - { - emoji: "🧀", - aliases: ["cheese"], - tags: [], - category: "Food & Drink", - description: "cheese wedge", - unicode_version: "8.0", - }, - { - emoji: "🍖", - aliases: ["meat_on_bone"], - tags: [], - category: "Food & Drink", - description: "meat on bone", - unicode_version: "6.0", - }, - { - emoji: "🍗", - aliases: ["poultry_leg"], - tags: ["meat", "chicken"], - category: "Food & Drink", - description: "poultry leg", - unicode_version: "6.0", - }, - { - emoji: "🥩", - aliases: ["cut_of_meat"], - tags: [], - category: "Food & Drink", - description: "cut of meat", - unicode_version: "11.0", - }, - { - emoji: "🥓", - aliases: ["bacon"], - tags: [], - category: "Food & Drink", - description: "bacon", - unicode_version: "9.0", - }, - { - emoji: "🍔", - aliases: ["hamburger"], - tags: ["burger"], - category: "Food & Drink", - description: "hamburger", - unicode_version: "6.0", - }, - { - emoji: "🍟", - aliases: ["fries"], - tags: [], - category: "Food & Drink", - description: "french fries", - unicode_version: "6.0", - }, - { - emoji: "🍕", - aliases: ["pizza"], - tags: [], - category: "Food & Drink", - description: "pizza", - unicode_version: "6.0", - }, - { - emoji: "🌭", - aliases: ["hotdog"], - tags: [], - category: "Food & Drink", - description: "hot dog", - unicode_version: "8.0", - }, - { - emoji: "🥪", - aliases: ["sandwich"], - tags: [], - category: "Food & Drink", - description: "sandwich", - unicode_version: "11.0", - }, - { - emoji: "🌮", - aliases: ["taco"], - tags: [], - category: "Food & Drink", - description: "taco", - unicode_version: "8.0", - }, - { - emoji: "🌯", - aliases: ["burrito"], - tags: [], - category: "Food & Drink", - description: "burrito", - unicode_version: "8.0", - }, - { - emoji: "🫔", - aliases: ["tamale"], - tags: [], - category: "Food & Drink", - description: "tamale", - unicode_version: "13.0", - }, - { - emoji: "🥙", - aliases: ["stuffed_flatbread"], - tags: [], - category: "Food & Drink", - description: "stuffed flatbread", - unicode_version: "9.0", - }, - { - emoji: "🧆", - aliases: ["falafel"], - tags: [], - category: "Food & Drink", - description: "falafel", - unicode_version: "12.0", - }, - { - emoji: "🥚", - aliases: ["egg"], - tags: [], - category: "Food & Drink", - description: "egg", - unicode_version: "9.0", - }, - { - emoji: "🍳", - aliases: ["fried_egg"], - tags: ["breakfast"], - category: "Food & Drink", - description: "cooking", - unicode_version: "6.0", - }, - { - emoji: "🥘", - aliases: ["shallow_pan_of_food"], - tags: ["paella", "curry"], - category: "Food & Drink", - description: "shallow pan of food", - unicode_version: "", - }, - { - emoji: "🍲", - aliases: ["stew"], - tags: [], - category: "Food & Drink", - description: "pot of food", - unicode_version: "6.0", - }, - { - emoji: "🫕", - aliases: ["fondue"], - tags: [], - category: "Food & Drink", - description: "fondue", - unicode_version: "13.0", - }, - { - emoji: "🥣", - aliases: ["bowl_with_spoon"], - tags: [], - category: "Food & Drink", - description: "bowl with spoon", - unicode_version: "11.0", - }, - { - emoji: "🥗", - aliases: ["green_salad"], - tags: [], - category: "Food & Drink", - description: "green salad", - unicode_version: "9.0", - }, - { - emoji: "🍿", - aliases: ["popcorn"], - tags: [], - category: "Food & Drink", - description: "popcorn", - unicode_version: "8.0", - }, - { - emoji: "🧈", - aliases: ["butter"], - tags: [], - category: "Food & Drink", - description: "butter", - unicode_version: "12.0", - }, - { - emoji: "🧂", - aliases: ["salt"], - tags: [], - category: "Food & Drink", - description: "salt", - unicode_version: "11.0", - }, - { - emoji: "🥫", - aliases: ["canned_food"], - tags: [], - category: "Food & Drink", - description: "canned food", - unicode_version: "11.0", - }, - { - emoji: "🍱", - aliases: ["bento"], - tags: [], - category: "Food & Drink", - description: "bento box", - unicode_version: "6.0", - }, - { - emoji: "🍘", - aliases: ["rice_cracker"], - tags: [], - category: "Food & Drink", - description: "rice cracker", - unicode_version: "6.0", - }, - { - emoji: "🍙", - aliases: ["rice_ball"], - tags: [], - category: "Food & Drink", - description: "rice ball", - unicode_version: "6.0", - }, - { - emoji: "🍚", - aliases: ["rice"], - tags: [], - category: "Food & Drink", - description: "cooked rice", - unicode_version: "6.0", - }, - { - emoji: "🍛", - aliases: ["curry"], - tags: [], - category: "Food & Drink", - description: "curry rice", - unicode_version: "6.0", - }, - { - emoji: "🍜", - aliases: ["ramen"], - tags: ["noodle"], - category: "Food & Drink", - description: "steaming bowl", - unicode_version: "6.0", - }, - { - emoji: "🍝", - aliases: ["spaghetti"], - tags: ["pasta"], - category: "Food & Drink", - description: "spaghetti", - unicode_version: "6.0", - }, - { - emoji: "🍠", - aliases: ["sweet_potato"], - tags: [], - category: "Food & Drink", - description: "roasted sweet potato", - unicode_version: "6.0", - }, - { - emoji: "🍢", - aliases: ["oden"], - tags: [], - category: "Food & Drink", - description: "oden", - unicode_version: "6.0", - }, - { - emoji: "🍣", - aliases: ["sushi"], - tags: [], - category: "Food & Drink", - description: "sushi", - unicode_version: "6.0", - }, - { - emoji: "🍤", - aliases: ["fried_shrimp"], - tags: ["tempura"], - category: "Food & Drink", - description: "fried shrimp", - unicode_version: "6.0", - }, - { - emoji: "🍥", - aliases: ["fish_cake"], - tags: [], - category: "Food & Drink", - description: "fish cake with swirl", - unicode_version: "6.0", - }, - { - emoji: "🥮", - aliases: ["moon_cake"], - tags: [], - category: "Food & Drink", - description: "moon cake", - unicode_version: "11.0", - }, - { - emoji: "🍡", - aliases: ["dango"], - tags: [], - category: "Food & Drink", - description: "dango", - unicode_version: "6.0", - }, - { - emoji: "🥟", - aliases: ["dumpling"], - tags: [], - category: "Food & Drink", - description: "dumpling", - unicode_version: "11.0", - }, - { - emoji: "🥠", - aliases: ["fortune_cookie"], - tags: [], - category: "Food & Drink", - description: "fortune cookie", - unicode_version: "11.0", - }, - { - emoji: "🥡", - aliases: ["takeout_box"], - tags: [], - category: "Food & Drink", - description: "takeout box", - unicode_version: "11.0", - }, - { - emoji: "🦀", - aliases: ["crab"], - tags: [], - category: "Food & Drink", - description: "crab", - unicode_version: "8.0", - }, - { - emoji: "🦞", - aliases: ["lobster"], - tags: [], - category: "Food & Drink", - description: "lobster", - unicode_version: "11.0", - }, - { - emoji: "🦐", - aliases: ["shrimp"], - tags: [], - category: "Food & Drink", - description: "shrimp", - unicode_version: "9.0", - }, - { - emoji: "🦑", - aliases: ["squid"], - tags: [], - category: "Food & Drink", - description: "squid", - unicode_version: "9.0", - }, - { - emoji: "🦪", - aliases: ["oyster"], - tags: [], - category: "Food & Drink", - description: "oyster", - unicode_version: "12.0", - }, - { - emoji: "🍦", - aliases: ["icecream"], - tags: [], - category: "Food & Drink", - description: "soft ice cream", - unicode_version: "6.0", - }, - { - emoji: "🍧", - aliases: ["shaved_ice"], - tags: [], - category: "Food & Drink", - description: "shaved ice", - unicode_version: "6.0", - }, - { - emoji: "🍨", - aliases: ["ice_cream"], - tags: [], - category: "Food & Drink", - description: "ice cream", - unicode_version: "6.0", - }, - { - emoji: "🍩", - aliases: ["doughnut"], - tags: [], - category: "Food & Drink", - description: "doughnut", - unicode_version: "6.0", - }, - { - emoji: "🍪", - aliases: ["cookie"], - tags: [], - category: "Food & Drink", - description: "cookie", - unicode_version: "6.0", - }, - { - emoji: "🎂", - aliases: ["birthday"], - tags: ["party"], - category: "Food & Drink", - description: "birthday cake", - unicode_version: "6.0", - }, - { - emoji: "🍰", - aliases: ["cake"], - tags: ["dessert"], - category: "Food & Drink", - description: "shortcake", - unicode_version: "6.0", - }, - { - emoji: "🧁", - aliases: ["cupcake"], - tags: [], - category: "Food & Drink", - description: "cupcake", - unicode_version: "11.0", - }, - { - emoji: "🥧", - aliases: ["pie"], - tags: [], - category: "Food & Drink", - description: "pie", - unicode_version: "11.0", - }, - { - emoji: "🍫", - aliases: ["chocolate_bar"], - tags: [], - category: "Food & Drink", - description: "chocolate bar", - unicode_version: "6.0", - }, - { - emoji: "🍬", - aliases: ["candy"], - tags: ["sweet"], - category: "Food & Drink", - description: "candy", - unicode_version: "6.0", - }, - { - emoji: "🍭", - aliases: ["lollipop"], - tags: [], - category: "Food & Drink", - description: "lollipop", - unicode_version: "6.0", - }, - { - emoji: "🍮", - aliases: ["custard"], - tags: [], - category: "Food & Drink", - description: "custard", - unicode_version: "6.0", - }, - { - emoji: "🍯", - aliases: ["honey_pot"], - tags: [], - category: "Food & Drink", - description: "honey pot", - unicode_version: "6.0", - }, - { - emoji: "🍼", - aliases: ["baby_bottle"], - tags: ["milk"], - category: "Food & Drink", - description: "baby bottle", - unicode_version: "6.0", - }, - { - emoji: "🥛", - aliases: ["milk_glass"], - tags: [], - category: "Food & Drink", - description: "glass of milk", - unicode_version: "9.0", - }, - { - emoji: "☕", - aliases: ["coffee"], - tags: ["cafe", "espresso"], - category: "Food & Drink", - description: "hot beverage", - unicode_version: "4.0", - }, - { - emoji: "🫖", - aliases: ["teapot"], - tags: [], - category: "Food & Drink", - description: "teapot", - unicode_version: "13.0", - }, - { - emoji: "🍵", - aliases: ["tea"], - tags: ["green", "breakfast"], - category: "Food & Drink", - description: "teacup without handle", - unicode_version: "6.0", - }, - { - emoji: "🍶", - aliases: ["sake"], - tags: [], - category: "Food & Drink", - description: "sake", - unicode_version: "6.0", - }, - { - emoji: "🍾", - aliases: ["champagne"], - tags: ["bottle", "bubbly", "celebration"], - category: "Food & Drink", - description: "bottle with popping cork", - unicode_version: "8.0", - }, - { - emoji: "🍷", - aliases: ["wine_glass"], - tags: [], - category: "Food & Drink", - description: "wine glass", - unicode_version: "6.0", - }, - { - emoji: "🍸", - aliases: ["cocktail"], - tags: ["drink"], - category: "Food & Drink", - description: "cocktail glass", - unicode_version: "6.0", - }, - { - emoji: "🍹", - aliases: ["tropical_drink"], - tags: ["summer", "vacation"], - category: "Food & Drink", - description: "tropical drink", - unicode_version: "6.0", - }, - { - emoji: "🍺", - aliases: ["beer"], - tags: ["drink"], - category: "Food & Drink", - description: "beer mug", - unicode_version: "6.0", - }, - { - emoji: "🍻", - aliases: ["beers"], - tags: ["drinks"], - category: "Food & Drink", - description: "clinking beer mugs", - unicode_version: "6.0", - }, - { - emoji: "🥂", - aliases: ["clinking_glasses"], - tags: ["cheers", "toast"], - category: "Food & Drink", - description: "clinking glasses", - unicode_version: "9.0", - }, - { - emoji: "🥃", - aliases: ["tumbler_glass"], - tags: ["whisky"], - category: "Food & Drink", - description: "tumbler glass", - unicode_version: "9.0", - }, - { - emoji: "🥤", - aliases: ["cup_with_straw"], - tags: [], - category: "Food & Drink", - description: "cup with straw", - unicode_version: "11.0", - }, - { - emoji: "🧋", - aliases: ["bubble_tea"], - tags: [], - category: "Food & Drink", - description: "bubble tea", - unicode_version: "13.0", - }, - { - emoji: "🧃", - aliases: ["beverage_box"], - tags: [], - category: "Food & Drink", - description: "beverage box", - unicode_version: "12.0", - }, - { - emoji: "🧉", - aliases: ["mate"], - tags: [], - category: "Food & Drink", - description: "mate", - unicode_version: "12.0", - }, - { - emoji: "🧊", - aliases: ["ice_cube"], - tags: [], - category: "Food & Drink", - description: "ice", - unicode_version: "12.0", - }, - { - emoji: "🥢", - aliases: ["chopsticks"], - tags: [], - category: "Food & Drink", - description: "chopsticks", - unicode_version: "11.0", - }, - { - emoji: "🍽️", - aliases: ["plate_with_cutlery"], - tags: ["dining", "dinner"], - category: "Food & Drink", - description: "fork and knife with plate", - unicode_version: "7.0", - }, - { - emoji: "🍴", - aliases: ["fork_and_knife"], - tags: ["cutlery"], - category: "Food & Drink", - description: "fork and knife", - unicode_version: "6.0", - }, - { - emoji: "🥄", - aliases: ["spoon"], - tags: [], - category: "Food & Drink", - description: "spoon", - unicode_version: "9.0", - }, - { - emoji: "🔪", - aliases: ["hocho", "knife"], - tags: ["cut", "chop"], - category: "Food & Drink", - description: "kitchen knife", - unicode_version: "6.0", - }, - { - emoji: "🏺", - aliases: ["amphora"], - tags: [], - category: "Food & Drink", - description: "amphora", - unicode_version: "8.0", - }, - { - emoji: "🌍", - aliases: ["earth_africa"], - tags: ["globe", "world", "international"], - category: "Travel & Places", - description: "globe showing Europe-Africa", - unicode_version: "6.0", - }, - { - emoji: "🌎", - aliases: ["earth_americas"], - tags: ["globe", "world", "international"], - category: "Travel & Places", - description: "globe showing Americas", - unicode_version: "6.0", - }, - { - emoji: "🌏", - aliases: ["earth_asia"], - tags: ["globe", "world", "international"], - category: "Travel & Places", - description: "globe showing Asia-Australia", - unicode_version: "6.0", - }, - { - emoji: "🌐", - aliases: ["globe_with_meridians"], - tags: ["world", "global", "international"], - category: "Travel & Places", - description: "globe with meridians", - unicode_version: "6.0", - }, - { - emoji: "🗺️", - aliases: ["world_map"], - tags: ["travel"], - category: "Travel & Places", - description: "world map", - unicode_version: "7.0", - }, - { - emoji: "🗾", - aliases: ["japan"], - tags: [], - category: "Travel & Places", - description: "map of Japan", - unicode_version: "6.0", - }, - { - emoji: "🧭", - aliases: ["compass"], - tags: [], - category: "Travel & Places", - description: "compass", - unicode_version: "11.0", - }, - { - emoji: "🏔️", - aliases: ["mountain_snow"], - tags: [], - category: "Travel & Places", - description: "snow-capped mountain", - unicode_version: "7.0", - }, - { - emoji: "⛰️", - aliases: ["mountain"], - tags: [], - category: "Travel & Places", - description: "mountain", - unicode_version: "5.2", - }, - { - emoji: "🌋", - aliases: ["volcano"], - tags: [], - category: "Travel & Places", - description: "volcano", - unicode_version: "6.0", - }, - { - emoji: "🗻", - aliases: ["mount_fuji"], - tags: [], - category: "Travel & Places", - description: "mount fuji", - unicode_version: "6.0", - }, - { - emoji: "🏕️", - aliases: ["camping"], - tags: [], - category: "Travel & Places", - description: "camping", - unicode_version: "7.0", - }, - { - emoji: "🏖️", - aliases: ["beach_umbrella"], - tags: [], - category: "Travel & Places", - description: "beach with umbrella", - unicode_version: "7.0", - }, - { - emoji: "🏜️", - aliases: ["desert"], - tags: [], - category: "Travel & Places", - description: "desert", - unicode_version: "7.0", - }, - { - emoji: "🏝️", - aliases: ["desert_island"], - tags: [], - category: "Travel & Places", - description: "desert island", - unicode_version: "7.0", - }, - { - emoji: "🏞️", - aliases: ["national_park"], - tags: [], - category: "Travel & Places", - description: "national park", - unicode_version: "7.0", - }, - { - emoji: "🏟️", - aliases: ["stadium"], - tags: [], - category: "Travel & Places", - description: "stadium", - unicode_version: "7.0", - }, - { - emoji: "🏛️", - aliases: ["classical_building"], - tags: [], - category: "Travel & Places", - description: "classical building", - unicode_version: "7.0", - }, - { - emoji: "🏗️", - aliases: ["building_construction"], - tags: [], - category: "Travel & Places", - description: "building construction", - unicode_version: "7.0", - }, - { - emoji: "🧱", - aliases: ["bricks"], - tags: [], - category: "Travel & Places", - description: "brick", - unicode_version: "11.0", - }, - { - emoji: "🪨", - aliases: ["rock"], - tags: [], - category: "Travel & Places", - description: "rock", - unicode_version: "13.0", - }, - { - emoji: "🪵", - aliases: ["wood"], - tags: [], - category: "Travel & Places", - description: "wood", - unicode_version: "13.0", - }, - { - emoji: "🛖", - aliases: ["hut"], - tags: [], - category: "Travel & Places", - description: "hut", - unicode_version: "13.0", - }, - { - emoji: "🏘️", - aliases: ["houses"], - tags: [], - category: "Travel & Places", - description: "houses", - unicode_version: "7.0", - }, - { - emoji: "🏚️", - aliases: ["derelict_house"], - tags: [], - category: "Travel & Places", - description: "derelict house", - unicode_version: "7.0", - }, - { - emoji: "🏠", - aliases: ["house"], - tags: [], - category: "Travel & Places", - description: "house", - unicode_version: "6.0", - }, - { - emoji: "🏡", - aliases: ["house_with_garden"], - tags: [], - category: "Travel & Places", - description: "house with garden", - unicode_version: "6.0", - }, - { - emoji: "🏢", - aliases: ["office"], - tags: [], - category: "Travel & Places", - description: "office building", - unicode_version: "6.0", - }, - { - emoji: "🏣", - aliases: ["post_office"], - tags: [], - category: "Travel & Places", - description: "Japanese post office", - unicode_version: "6.0", - }, - { - emoji: "🏤", - aliases: ["european_post_office"], - tags: [], - category: "Travel & Places", - description: "post office", - unicode_version: "6.0", - }, - { - emoji: "🏥", - aliases: ["hospital"], - tags: [], - category: "Travel & Places", - description: "hospital", - unicode_version: "6.0", - }, - { - emoji: "🏦", - aliases: ["bank"], - tags: [], - category: "Travel & Places", - description: "bank", - unicode_version: "6.0", - }, - { - emoji: "🏨", - aliases: ["hotel"], - tags: [], - category: "Travel & Places", - description: "hotel", - unicode_version: "6.0", - }, - { - emoji: "🏩", - aliases: ["love_hotel"], - tags: [], - category: "Travel & Places", - description: "love hotel", - unicode_version: "6.0", - }, - { - emoji: "🏪", - aliases: ["convenience_store"], - tags: [], - category: "Travel & Places", - description: "convenience store", - unicode_version: "6.0", - }, - { - emoji: "🏫", - aliases: ["school"], - tags: [], - category: "Travel & Places", - description: "school", - unicode_version: "6.0", - }, - { - emoji: "🏬", - aliases: ["department_store"], - tags: [], - category: "Travel & Places", - description: "department store", - unicode_version: "6.0", - }, - { - emoji: "🏭", - aliases: ["factory"], - tags: [], - category: "Travel & Places", - description: "factory", - unicode_version: "6.0", - }, - { - emoji: "🏯", - aliases: ["japanese_castle"], - tags: [], - category: "Travel & Places", - description: "Japanese castle", - unicode_version: "6.0", - }, - { - emoji: "🏰", - aliases: ["european_castle"], - tags: [], - category: "Travel & Places", - description: "castle", - unicode_version: "6.0", - }, - { - emoji: "💒", - aliases: ["wedding"], - tags: ["marriage"], - category: "Travel & Places", - description: "wedding", - unicode_version: "6.0", - }, - { - emoji: "🗼", - aliases: ["tokyo_tower"], - tags: [], - category: "Travel & Places", - description: "Tokyo tower", - unicode_version: "6.0", - }, - { - emoji: "🗽", - aliases: ["statue_of_liberty"], - tags: [], - category: "Travel & Places", - description: "Statue of Liberty", - unicode_version: "6.0", - }, - { - emoji: "⛪", - aliases: ["church"], - tags: [], - category: "Travel & Places", - description: "church", - unicode_version: "5.2", - }, - { - emoji: "🕌", - aliases: ["mosque"], - tags: [], - category: "Travel & Places", - description: "mosque", - unicode_version: "8.0", - }, - { - emoji: "🛕", - aliases: ["hindu_temple"], - tags: [], - category: "Travel & Places", - description: "hindu temple", - unicode_version: "12.0", - }, - { - emoji: "🕍", - aliases: ["synagogue"], - tags: [], - category: "Travel & Places", - description: "synagogue", - unicode_version: "8.0", - }, - { - emoji: "⛩️", - aliases: ["shinto_shrine"], - tags: [], - category: "Travel & Places", - description: "shinto shrine", - unicode_version: "5.2", - }, - { - emoji: "🕋", - aliases: ["kaaba"], - tags: [], - category: "Travel & Places", - description: "kaaba", - unicode_version: "8.0", - }, - { - emoji: "⛲", - aliases: ["fountain"], - tags: [], - category: "Travel & Places", - description: "fountain", - unicode_version: "5.2", - }, - { - emoji: "⛺", - aliases: ["tent"], - tags: ["camping"], - category: "Travel & Places", - description: "tent", - unicode_version: "5.2", - }, - { - emoji: "🌁", - aliases: ["foggy"], - tags: ["karl"], - category: "Travel & Places", - description: "foggy", - unicode_version: "6.0", - }, - { - emoji: "🌃", - aliases: ["night_with_stars"], - tags: [], - category: "Travel & Places", - description: "night with stars", - unicode_version: "6.0", - }, - { - emoji: "🏙️", - aliases: ["cityscape"], - tags: ["skyline"], - category: "Travel & Places", - description: "cityscape", - unicode_version: "7.0", - }, - { - emoji: "🌄", - aliases: ["sunrise_over_mountains"], - tags: [], - category: "Travel & Places", - description: "sunrise over mountains", - unicode_version: "6.0", - }, - { - emoji: "🌅", - aliases: ["sunrise"], - tags: [], - category: "Travel & Places", - description: "sunrise", - unicode_version: "6.0", - }, - { - emoji: "🌆", - aliases: ["city_sunset"], - tags: [], - category: "Travel & Places", - description: "cityscape at dusk", - unicode_version: "6.0", - }, - { - emoji: "🌇", - aliases: ["city_sunrise"], - tags: [], - category: "Travel & Places", - description: "sunset", - unicode_version: "6.0", - }, - { - emoji: "🌉", - aliases: ["bridge_at_night"], - tags: [], - category: "Travel & Places", - description: "bridge at night", - unicode_version: "6.0", - }, - { - emoji: "♨️", - aliases: ["hotsprings"], - tags: [], - category: "Travel & Places", - description: "hot springs", - unicode_version: "", - }, - { - emoji: "🎠", - aliases: ["carousel_horse"], - tags: [], - category: "Travel & Places", - description: "carousel horse", - unicode_version: "6.0", - }, - { - emoji: "🎡", - aliases: ["ferris_wheel"], - tags: [], - category: "Travel & Places", - description: "ferris wheel", - unicode_version: "6.0", - }, - { - emoji: "🎢", - aliases: ["roller_coaster"], - tags: [], - category: "Travel & Places", - description: "roller coaster", - unicode_version: "6.0", - }, - { - emoji: "💈", - aliases: ["barber"], - tags: [], - category: "Travel & Places", - description: "barber pole", - unicode_version: "6.0", - }, - { - emoji: "🎪", - aliases: ["circus_tent"], - tags: [], - category: "Travel & Places", - description: "circus tent", - unicode_version: "6.0", - }, - { - emoji: "🚂", - aliases: ["steam_locomotive"], - tags: ["train"], - category: "Travel & Places", - description: "locomotive", - unicode_version: "6.0", - }, - { - emoji: "🚃", - aliases: ["railway_car"], - tags: [], - category: "Travel & Places", - description: "railway car", - unicode_version: "6.0", - }, - { - emoji: "🚄", - aliases: ["bullettrain_side"], - tags: ["train"], - category: "Travel & Places", - description: "high-speed train", - unicode_version: "6.0", - }, - { - emoji: "🚅", - aliases: ["bullettrain_front"], - tags: ["train"], - category: "Travel & Places", - description: "bullet train", - unicode_version: "6.0", - }, - { - emoji: "🚆", - aliases: ["train2"], - tags: [], - category: "Travel & Places", - description: "train", - unicode_version: "6.0", - }, - { - emoji: "🚇", - aliases: ["metro"], - tags: [], - category: "Travel & Places", - description: "metro", - unicode_version: "6.0", - }, - { - emoji: "🚈", - aliases: ["light_rail"], - tags: [], - category: "Travel & Places", - description: "light rail", - unicode_version: "6.0", - }, - { - emoji: "🚉", - aliases: ["station"], - tags: [], - category: "Travel & Places", - description: "station", - unicode_version: "6.0", - }, - { - emoji: "🚊", - aliases: ["tram"], - tags: [], - category: "Travel & Places", - description: "tram", - unicode_version: "6.0", - }, - { - emoji: "🚝", - aliases: ["monorail"], - tags: [], - category: "Travel & Places", - description: "monorail", - unicode_version: "6.0", - }, - { - emoji: "🚞", - aliases: ["mountain_railway"], - tags: [], - category: "Travel & Places", - description: "mountain railway", - unicode_version: "6.0", - }, - { - emoji: "🚋", - aliases: ["train"], - tags: [], - category: "Travel & Places", - description: "tram car", - unicode_version: "6.0", - }, - { - emoji: "🚌", - aliases: ["bus"], - tags: [], - category: "Travel & Places", - description: "bus", - unicode_version: "6.0", - }, - { - emoji: "🚍", - aliases: ["oncoming_bus"], - tags: [], - category: "Travel & Places", - description: "oncoming bus", - unicode_version: "6.0", - }, - { - emoji: "🚎", - aliases: ["trolleybus"], - tags: [], - category: "Travel & Places", - description: "trolleybus", - unicode_version: "6.0", - }, - { - emoji: "🚐", - aliases: ["minibus"], - tags: [], - category: "Travel & Places", - description: "minibus", - unicode_version: "6.0", - }, - { - emoji: "🚑", - aliases: ["ambulance"], - tags: [], - category: "Travel & Places", - description: "ambulance", - unicode_version: "6.0", - }, - { - emoji: "🚒", - aliases: ["fire_engine"], - tags: [], - category: "Travel & Places", - description: "fire engine", - unicode_version: "6.0", - }, - { - emoji: "🚓", - aliases: ["police_car"], - tags: [], - category: "Travel & Places", - description: "police car", - unicode_version: "6.0", - }, - { - emoji: "🚔", - aliases: ["oncoming_police_car"], - tags: [], - category: "Travel & Places", - description: "oncoming police car", - unicode_version: "6.0", - }, - { - emoji: "🚕", - aliases: ["taxi"], - tags: [], - category: "Travel & Places", - description: "taxi", - unicode_version: "6.0", - }, - { - emoji: "🚖", - aliases: ["oncoming_taxi"], - tags: [], - category: "Travel & Places", - description: "oncoming taxi", - unicode_version: "6.0", - }, - { - emoji: "🚗", - aliases: ["car", "red_car"], - tags: [], - category: "Travel & Places", - description: "automobile", - unicode_version: "6.0", - }, - { - emoji: "🚘", - aliases: ["oncoming_automobile"], - tags: [], - category: "Travel & Places", - description: "oncoming automobile", - unicode_version: "6.0", - }, - { - emoji: "🚙", - aliases: ["blue_car"], - tags: [], - category: "Travel & Places", - description: "sport utility vehicle", - unicode_version: "6.0", - }, - { - emoji: "🛻", - aliases: ["pickup_truck"], - tags: [], - category: "Travel & Places", - description: "pickup truck", - unicode_version: "13.0", - }, - { - emoji: "🚚", - aliases: ["truck"], - tags: [], - category: "Travel & Places", - description: "delivery truck", - unicode_version: "6.0", - }, - { - emoji: "🚛", - aliases: ["articulated_lorry"], - tags: [], - category: "Travel & Places", - description: "articulated lorry", - unicode_version: "6.0", - }, - { - emoji: "🚜", - aliases: ["tractor"], - tags: [], - category: "Travel & Places", - description: "tractor", - unicode_version: "6.0", - }, - { - emoji: "🏎️", - aliases: ["racing_car"], - tags: [], - category: "Travel & Places", - description: "racing car", - unicode_version: "7.0", - }, - { - emoji: "🏍️", - aliases: ["motorcycle"], - tags: [], - category: "Travel & Places", - description: "motorcycle", - unicode_version: "7.0", - }, - { - emoji: "🛵", - aliases: ["motor_scooter"], - tags: [], - category: "Travel & Places", - description: "motor scooter", - unicode_version: "9.0", - }, - { - emoji: "🦽", - aliases: ["manual_wheelchair"], - tags: [], - category: "Travel & Places", - description: "manual wheelchair", - unicode_version: "12.0", - }, - { - emoji: "🦼", - aliases: ["motorized_wheelchair"], - tags: [], - category: "Travel & Places", - description: "motorized wheelchair", - unicode_version: "12.0", - }, - { - emoji: "🛺", - aliases: ["auto_rickshaw"], - tags: [], - category: "Travel & Places", - description: "auto rickshaw", - unicode_version: "12.0", - }, - { - emoji: "🚲", - aliases: ["bike"], - tags: ["bicycle"], - category: "Travel & Places", - description: "bicycle", - unicode_version: "6.0", - }, - { - emoji: "🛴", - aliases: ["kick_scooter"], - tags: [], - category: "Travel & Places", - description: "kick scooter", - unicode_version: "9.0", - }, - { - emoji: "🛹", - aliases: ["skateboard"], - tags: [], - category: "Travel & Places", - description: "skateboard", - unicode_version: "11.0", - }, - { - emoji: "🛼", - aliases: ["roller_skate"], - tags: [], - category: "Travel & Places", - description: "roller skate", - unicode_version: "13.0", - }, - { - emoji: "🚏", - aliases: ["busstop"], - tags: [], - category: "Travel & Places", - description: "bus stop", - unicode_version: "6.0", - }, - { - emoji: "🛣️", - aliases: ["motorway"], - tags: [], - category: "Travel & Places", - description: "motorway", - unicode_version: "7.0", - }, - { - emoji: "🛤️", - aliases: ["railway_track"], - tags: [], - category: "Travel & Places", - description: "railway track", - unicode_version: "7.0", - }, - { - emoji: "🛢️", - aliases: ["oil_drum"], - tags: [], - category: "Travel & Places", - description: "oil drum", - unicode_version: "7.0", - }, - { - emoji: "⛽", - aliases: ["fuelpump"], - tags: [], - category: "Travel & Places", - description: "fuel pump", - unicode_version: "5.2", - }, - { - emoji: "🚨", - aliases: ["rotating_light"], - tags: ["911", "emergency"], - category: "Travel & Places", - description: "police car light", - unicode_version: "6.0", - }, - { - emoji: "🚥", - aliases: ["traffic_light"], - tags: [], - category: "Travel & Places", - description: "horizontal traffic light", - unicode_version: "6.0", - }, - { - emoji: "🚦", - aliases: ["vertical_traffic_light"], - tags: ["semaphore"], - category: "Travel & Places", - description: "vertical traffic light", - unicode_version: "6.0", - }, - { - emoji: "🛑", - aliases: ["stop_sign"], - tags: [], - category: "Travel & Places", - description: "stop sign", - unicode_version: "9.0", - }, - { - emoji: "🚧", - aliases: ["construction"], - tags: ["wip"], - category: "Travel & Places", - description: "construction", - unicode_version: "6.0", - }, - { - emoji: "⚓", - aliases: ["anchor"], - tags: ["ship"], - category: "Travel & Places", - description: "anchor", - unicode_version: "4.1", - }, - { - emoji: "⛵", - aliases: ["boat", "sailboat"], - tags: [], - category: "Travel & Places", - description: "sailboat", - unicode_version: "5.2", - }, - { - emoji: "🛶", - aliases: ["canoe"], - tags: [], - category: "Travel & Places", - description: "canoe", - unicode_version: "9.0", - }, - { - emoji: "🚤", - aliases: ["speedboat"], - tags: ["ship"], - category: "Travel & Places", - description: "speedboat", - unicode_version: "6.0", - }, - { - emoji: "🛳️", - aliases: ["passenger_ship"], - tags: ["cruise"], - category: "Travel & Places", - description: "passenger ship", - unicode_version: "7.0", - }, - { - emoji: "⛴️", - aliases: ["ferry"], - tags: [], - category: "Travel & Places", - description: "ferry", - unicode_version: "5.2", - }, - { - emoji: "🛥️", - aliases: ["motor_boat"], - tags: [], - category: "Travel & Places", - description: "motor boat", - unicode_version: "7.0", - }, - { - emoji: "🚢", - aliases: ["ship"], - tags: [], - category: "Travel & Places", - description: "ship", - unicode_version: "6.0", - }, - { - emoji: "✈️", - aliases: ["airplane"], - tags: ["flight"], - category: "Travel & Places", - description: "airplane", - unicode_version: "", - }, - { - emoji: "🛩️", - aliases: ["small_airplane"], - tags: ["flight"], - category: "Travel & Places", - description: "small airplane", - unicode_version: "7.0", - }, - { - emoji: "🛫", - aliases: ["flight_departure"], - tags: [], - category: "Travel & Places", - description: "airplane departure", - unicode_version: "7.0", - }, - { - emoji: "🛬", - aliases: ["flight_arrival"], - tags: [], - category: "Travel & Places", - description: "airplane arrival", - unicode_version: "7.0", - }, - { - emoji: "🪂", - aliases: ["parachute"], - tags: [], - category: "Travel & Places", - description: "parachute", - unicode_version: "12.0", - }, - { - emoji: "💺", - aliases: ["seat"], - tags: [], - category: "Travel & Places", - description: "seat", - unicode_version: "6.0", - }, - { - emoji: "🚁", - aliases: ["helicopter"], - tags: [], - category: "Travel & Places", - description: "helicopter", - unicode_version: "6.0", - }, - { - emoji: "🚟", - aliases: ["suspension_railway"], - tags: [], - category: "Travel & Places", - description: "suspension railway", - unicode_version: "6.0", - }, - { - emoji: "🚠", - aliases: ["mountain_cableway"], - tags: [], - category: "Travel & Places", - description: "mountain cableway", - unicode_version: "6.0", - }, - { - emoji: "🚡", - aliases: ["aerial_tramway"], - tags: [], - category: "Travel & Places", - description: "aerial tramway", - unicode_version: "6.0", - }, - { - emoji: "🛰️", - aliases: ["artificial_satellite"], - tags: ["orbit", "space"], - category: "Travel & Places", - description: "satellite", - unicode_version: "7.0", - }, - { - emoji: "🚀", - aliases: ["rocket"], - tags: ["ship", "launch"], - category: "Travel & Places", - description: "rocket", - unicode_version: "6.0", - }, - { - emoji: "🛸", - aliases: ["flying_saucer"], - tags: ["ufo"], - category: "Travel & Places", - description: "flying saucer", - unicode_version: "11.0", - }, - { - emoji: "🛎️", - aliases: ["bellhop_bell"], - tags: [], - category: "Travel & Places", - description: "bellhop bell", - unicode_version: "7.0", - }, - { - emoji: "🧳", - aliases: ["luggage"], - tags: [], - category: "Travel & Places", - description: "luggage", - unicode_version: "11.0", - }, - { - emoji: "⌛", - aliases: ["hourglass"], - tags: ["time"], - category: "Travel & Places", - description: "hourglass done", - unicode_version: "", - }, - { - emoji: "⏳", - aliases: ["hourglass_flowing_sand"], - tags: ["time"], - category: "Travel & Places", - description: "hourglass not done", - unicode_version: "6.0", - }, - { - emoji: "⌚", - aliases: ["watch"], - tags: ["time"], - category: "Travel & Places", - description: "watch", - unicode_version: "", - }, - { - emoji: "⏰", - aliases: ["alarm_clock"], - tags: ["morning"], - category: "Travel & Places", - description: "alarm clock", - unicode_version: "6.0", - }, - { - emoji: "⏱️", - aliases: ["stopwatch"], - tags: [], - category: "Travel & Places", - description: "stopwatch", - unicode_version: "6.0", - }, - { - emoji: "⏲️", - aliases: ["timer_clock"], - tags: [], - category: "Travel & Places", - description: "timer clock", - unicode_version: "6.0", - }, - { - emoji: "🕰️", - aliases: ["mantelpiece_clock"], - tags: [], - category: "Travel & Places", - description: "mantelpiece clock", - unicode_version: "7.0", - }, - { - emoji: "🕛", - aliases: ["clock12"], - tags: [], - category: "Travel & Places", - description: "twelve o’clock", - unicode_version: "6.0", - }, - { - emoji: "🕧", - aliases: ["clock1230"], - tags: [], - category: "Travel & Places", - description: "twelve-thirty", - unicode_version: "6.0", - }, - { - emoji: "🕐", - aliases: ["clock1"], - tags: [], - category: "Travel & Places", - description: "one o’clock", - unicode_version: "6.0", - }, - { - emoji: "🕜", - aliases: ["clock130"], - tags: [], - category: "Travel & Places", - description: "one-thirty", - unicode_version: "6.0", - }, - { - emoji: "🕑", - aliases: ["clock2"], - tags: [], - category: "Travel & Places", - description: "two o’clock", - unicode_version: "6.0", - }, - { - emoji: "🕝", - aliases: ["clock230"], - tags: [], - category: "Travel & Places", - description: "two-thirty", - unicode_version: "6.0", - }, - { - emoji: "🕒", - aliases: ["clock3"], - tags: [], - category: "Travel & Places", - description: "three o’clock", - unicode_version: "6.0", - }, - { - emoji: "🕞", - aliases: ["clock330"], - tags: [], - category: "Travel & Places", - description: "three-thirty", - unicode_version: "6.0", - }, - { - emoji: "🕓", - aliases: ["clock4"], - tags: [], - category: "Travel & Places", - description: "four o’clock", - unicode_version: "6.0", - }, - { - emoji: "🕟", - aliases: ["clock430"], - tags: [], - category: "Travel & Places", - description: "four-thirty", - unicode_version: "6.0", - }, - { - emoji: "🕔", - aliases: ["clock5"], - tags: [], - category: "Travel & Places", - description: "five o’clock", - unicode_version: "6.0", - }, - { - emoji: "🕠", - aliases: ["clock530"], - tags: [], - category: "Travel & Places", - description: "five-thirty", - unicode_version: "6.0", - }, - { - emoji: "🕕", - aliases: ["clock6"], - tags: [], - category: "Travel & Places", - description: "six o’clock", - unicode_version: "6.0", - }, - { - emoji: "🕡", - aliases: ["clock630"], - tags: [], - category: "Travel & Places", - description: "six-thirty", - unicode_version: "6.0", - }, - { - emoji: "🕖", - aliases: ["clock7"], - tags: [], - category: "Travel & Places", - description: "seven o’clock", - unicode_version: "6.0", - }, - { - emoji: "🕢", - aliases: ["clock730"], - tags: [], - category: "Travel & Places", - description: "seven-thirty", - unicode_version: "6.0", - }, - { - emoji: "🕗", - aliases: ["clock8"], - tags: [], - category: "Travel & Places", - description: "eight o’clock", - unicode_version: "6.0", - }, - { - emoji: "🕣", - aliases: ["clock830"], - tags: [], - category: "Travel & Places", - description: "eight-thirty", - unicode_version: "6.0", - }, - { - emoji: "🕘", - aliases: ["clock9"], - tags: [], - category: "Travel & Places", - description: "nine o’clock", - unicode_version: "6.0", - }, - { - emoji: "🕤", - aliases: ["clock930"], - tags: [], - category: "Travel & Places", - description: "nine-thirty", - unicode_version: "6.0", - }, - { - emoji: "🕙", - aliases: ["clock10"], - tags: [], - category: "Travel & Places", - description: "ten o’clock", - unicode_version: "6.0", - }, - { - emoji: "🕥", - aliases: ["clock1030"], - tags: [], - category: "Travel & Places", - description: "ten-thirty", - unicode_version: "6.0", - }, - { - emoji: "🕚", - aliases: ["clock11"], - tags: [], - category: "Travel & Places", - description: "eleven o’clock", - unicode_version: "6.0", - }, - { - emoji: "🕦", - aliases: ["clock1130"], - tags: [], - category: "Travel & Places", - description: "eleven-thirty", - unicode_version: "6.0", - }, - { - emoji: "🌑", - aliases: ["new_moon"], - tags: [], - category: "Travel & Places", - description: "new moon", - unicode_version: "6.0", - }, - { - emoji: "🌒", - aliases: ["waxing_crescent_moon"], - tags: [], - category: "Travel & Places", - description: "waxing crescent moon", - unicode_version: "6.0", - }, - { - emoji: "🌓", - aliases: ["first_quarter_moon"], - tags: [], - category: "Travel & Places", - description: "first quarter moon", - unicode_version: "6.0", - }, - { - emoji: "🌔", - aliases: ["moon", "waxing_gibbous_moon"], - tags: [], - category: "Travel & Places", - description: "waxing gibbous moon", - unicode_version: "6.0", - }, - { - emoji: "🌕", - aliases: ["full_moon"], - tags: [], - category: "Travel & Places", - description: "full moon", - unicode_version: "6.0", - }, - { - emoji: "🌖", - aliases: ["waning_gibbous_moon"], - tags: [], - category: "Travel & Places", - description: "waning gibbous moon", - unicode_version: "6.0", - }, - { - emoji: "🌗", - aliases: ["last_quarter_moon"], - tags: [], - category: "Travel & Places", - description: "last quarter moon", - unicode_version: "6.0", - }, - { - emoji: "🌘", - aliases: ["waning_crescent_moon"], - tags: [], - category: "Travel & Places", - description: "waning crescent moon", - unicode_version: "6.0", - }, - { - emoji: "🌙", - aliases: ["crescent_moon"], - tags: ["night"], - category: "Travel & Places", - description: "crescent moon", - unicode_version: "6.0", - }, - { - emoji: "🌚", - aliases: ["new_moon_with_face"], - tags: [], - category: "Travel & Places", - description: "new moon face", - unicode_version: "6.0", - }, - { - emoji: "🌛", - aliases: ["first_quarter_moon_with_face"], - tags: [], - category: "Travel & Places", - description: "first quarter moon face", - unicode_version: "6.0", - }, - { - emoji: "🌜", - aliases: ["last_quarter_moon_with_face"], - tags: [], - category: "Travel & Places", - description: "last quarter moon face", - unicode_version: "6.0", - }, - { - emoji: "🌡️", - aliases: ["thermometer"], - tags: [], - category: "Travel & Places", - description: "thermometer", - unicode_version: "7.0", - }, - { - emoji: "☀️", - aliases: ["sunny"], - tags: ["weather"], - category: "Travel & Places", - description: "sun", - unicode_version: "", - }, - { - emoji: "🌝", - aliases: ["full_moon_with_face"], - tags: [], - category: "Travel & Places", - description: "full moon face", - unicode_version: "6.0", - }, - { - emoji: "🌞", - aliases: ["sun_with_face"], - tags: ["summer"], - category: "Travel & Places", - description: "sun with face", - unicode_version: "6.0", - }, - { - emoji: "🪐", - aliases: ["ringed_planet"], - tags: [], - category: "Travel & Places", - description: "ringed planet", - unicode_version: "12.0", - }, - { - emoji: "⭐", - aliases: ["star"], - tags: [], - category: "Travel & Places", - description: "star", - unicode_version: "5.1", - }, - { - emoji: "🌟", - aliases: ["star2"], - tags: [], - category: "Travel & Places", - description: "glowing star", - unicode_version: "6.0", - }, - { - emoji: "🌠", - aliases: ["stars"], - tags: [], - category: "Travel & Places", - description: "shooting star", - unicode_version: "6.0", - }, - { - emoji: "🌌", - aliases: ["milky_way"], - tags: [], - category: "Travel & Places", - description: "milky way", - unicode_version: "6.0", - }, - { - emoji: "☁️", - aliases: ["cloud"], - tags: [], - category: "Travel & Places", - description: "cloud", - unicode_version: "", - }, - { - emoji: "⛅", - aliases: ["partly_sunny"], - tags: ["weather", "cloud"], - category: "Travel & Places", - description: "sun behind cloud", - unicode_version: "5.2", - }, - { - emoji: "⛈️", - aliases: ["cloud_with_lightning_and_rain"], - tags: [], - category: "Travel & Places", - description: "cloud with lightning and rain", - unicode_version: "5.2", - }, - { - emoji: "🌤️", - aliases: ["sun_behind_small_cloud"], - tags: [], - category: "Travel & Places", - description: "sun behind small cloud", - unicode_version: "7.0", - }, - { - emoji: "🌥️", - aliases: ["sun_behind_large_cloud"], - tags: [], - category: "Travel & Places", - description: "sun behind large cloud", - unicode_version: "7.0", - }, - { - emoji: "🌦️", - aliases: ["sun_behind_rain_cloud"], - tags: [], - category: "Travel & Places", - description: "sun behind rain cloud", - unicode_version: "7.0", - }, - { - emoji: "🌧️", - aliases: ["cloud_with_rain"], - tags: [], - category: "Travel & Places", - description: "cloud with rain", - unicode_version: "7.0", - }, - { - emoji: "🌨️", - aliases: ["cloud_with_snow"], - tags: [], - category: "Travel & Places", - description: "cloud with snow", - unicode_version: "7.0", - }, - { - emoji: "🌩️", - aliases: ["cloud_with_lightning"], - tags: [], - category: "Travel & Places", - description: "cloud with lightning", - unicode_version: "7.0", - }, - { - emoji: "🌪️", - aliases: ["tornado"], - tags: [], - category: "Travel & Places", - description: "tornado", - unicode_version: "7.0", - }, - { - emoji: "🌫️", - aliases: ["fog"], - tags: [], - category: "Travel & Places", - description: "fog", - unicode_version: "7.0", - }, - { - emoji: "🌬️", - aliases: ["wind_face"], - tags: [], - category: "Travel & Places", - description: "wind face", - unicode_version: "7.0", - }, - { - emoji: "🌀", - aliases: ["cyclone"], - tags: ["swirl"], - category: "Travel & Places", - description: "cyclone", - unicode_version: "6.0", - }, - { - emoji: "🌈", - aliases: ["rainbow"], - tags: [], - category: "Travel & Places", - description: "rainbow", - unicode_version: "6.0", - }, - { - emoji: "🌂", - aliases: ["closed_umbrella"], - tags: ["weather", "rain"], - category: "Travel & Places", - description: "closed umbrella", - unicode_version: "6.0", - }, - { - emoji: "☂️", - aliases: ["open_umbrella"], - tags: [], - category: "Travel & Places", - description: "umbrella", - unicode_version: "", - }, - { - emoji: "☔", - aliases: ["umbrella"], - tags: ["rain", "weather"], - category: "Travel & Places", - description: "umbrella with rain drops", - unicode_version: "4.0", - }, - { - emoji: "⛱️", - aliases: ["parasol_on_ground"], - tags: ["beach_umbrella"], - category: "Travel & Places", - description: "umbrella on ground", - unicode_version: "5.2", - }, - { - emoji: "⚡", - aliases: ["zap"], - tags: ["lightning", "thunder"], - category: "Travel & Places", - description: "high voltage", - unicode_version: "4.0", - }, - { - emoji: "❄️", - aliases: ["snowflake"], - tags: ["winter", "cold", "weather"], - category: "Travel & Places", - description: "snowflake", - unicode_version: "", - }, - { - emoji: "☃️", - aliases: ["snowman_with_snow"], - tags: ["winter", "christmas"], - category: "Travel & Places", - description: "snowman", - unicode_version: "", - }, - { - emoji: "⛄", - aliases: ["snowman"], - tags: ["winter"], - category: "Travel & Places", - description: "snowman without snow", - unicode_version: "5.2", - }, - { - emoji: "☄️", - aliases: ["comet"], - tags: [], - category: "Travel & Places", - description: "comet", - unicode_version: "", - }, - { - emoji: "🔥", - aliases: ["fire"], - tags: ["burn"], - category: "Travel & Places", - description: "fire", - unicode_version: "6.0", - }, - { - emoji: "💧", - aliases: ["droplet"], - tags: ["water"], - category: "Travel & Places", - description: "droplet", - unicode_version: "6.0", - }, - { - emoji: "🌊", - aliases: ["ocean"], - tags: ["sea"], - category: "Travel & Places", - description: "water wave", - unicode_version: "6.0", - }, - { - emoji: "🎃", - aliases: ["jack_o_lantern"], - tags: ["halloween"], - category: "Activities", - description: "jack-o-lantern", - unicode_version: "6.0", - }, - { - emoji: "🎄", - aliases: ["christmas_tree"], - tags: [], - category: "Activities", - description: "Christmas tree", - unicode_version: "6.0", - }, - { - emoji: "🎆", - aliases: ["fireworks"], - tags: ["festival", "celebration"], - category: "Activities", - description: "fireworks", - unicode_version: "6.0", - }, - { - emoji: "🎇", - aliases: ["sparkler"], - tags: [], - category: "Activities", - description: "sparkler", - unicode_version: "6.0", - }, - { - emoji: "🧨", - aliases: ["firecracker"], - tags: [], - category: "Activities", - description: "firecracker", - unicode_version: "11.0", - }, - { - emoji: "✨", - aliases: ["sparkles"], - tags: ["shiny"], - category: "Activities", - description: "sparkles", - unicode_version: "6.0", - }, - { - emoji: "🎈", - aliases: ["balloon"], - tags: ["party", "birthday"], - category: "Activities", - description: "balloon", - unicode_version: "6.0", - }, - { - emoji: "🎉", - aliases: ["tada"], - tags: ["hooray", "party"], - category: "Activities", - description: "party popper", - unicode_version: "6.0", - }, - { - emoji: "🎊", - aliases: ["confetti_ball"], - tags: [], - category: "Activities", - description: "confetti ball", - unicode_version: "6.0", - }, - { - emoji: "🎋", - aliases: ["tanabata_tree"], - tags: [], - category: "Activities", - description: "tanabata tree", - unicode_version: "6.0", - }, - { - emoji: "🎍", - aliases: ["bamboo"], - tags: [], - category: "Activities", - description: "pine decoration", - unicode_version: "6.0", - }, - { - emoji: "🎎", - aliases: ["dolls"], - tags: [], - category: "Activities", - description: "Japanese dolls", - unicode_version: "6.0", - }, - { - emoji: "🎏", - aliases: ["flags"], - tags: [], - category: "Activities", - description: "carp streamer", - unicode_version: "6.0", - }, - { - emoji: "🎐", - aliases: ["wind_chime"], - tags: [], - category: "Activities", - description: "wind chime", - unicode_version: "6.0", - }, - { - emoji: "🎑", - aliases: ["rice_scene"], - tags: [], - category: "Activities", - description: "moon viewing ceremony", - unicode_version: "6.0", - }, - { - emoji: "🧧", - aliases: ["red_envelope"], - tags: [], - category: "Activities", - description: "red envelope", - unicode_version: "11.0", - }, - { - emoji: "🎀", - aliases: ["ribbon"], - tags: [], - category: "Activities", - description: "ribbon", - unicode_version: "6.0", - }, - { - emoji: "🎁", - aliases: ["gift"], - tags: ["present", "birthday", "christmas"], - category: "Activities", - description: "wrapped gift", - unicode_version: "6.0", - }, - { - emoji: "🎗️", - aliases: ["reminder_ribbon"], - tags: [], - category: "Activities", - description: "reminder ribbon", - unicode_version: "7.0", - }, - { - emoji: "🎟️", - aliases: ["tickets"], - tags: [], - category: "Activities", - description: "admission tickets", - unicode_version: "7.0", - }, - { - emoji: "🎫", - aliases: ["ticket"], - tags: [], - category: "Activities", - description: "ticket", - unicode_version: "6.0", - }, - { - emoji: "🎖️", - aliases: ["medal_military"], - tags: [], - category: "Activities", - description: "military medal", - unicode_version: "7.0", - }, - { - emoji: "🏆", - aliases: ["trophy"], - tags: ["award", "contest", "winner"], - category: "Activities", - description: "trophy", - unicode_version: "6.0", - }, - { - emoji: "🏅", - aliases: ["medal_sports"], - tags: ["gold", "winner"], - category: "Activities", - description: "sports medal", - unicode_version: "7.0", - }, - { - emoji: "🥇", - aliases: ["1st_place_medal"], - tags: ["gold"], - category: "Activities", - description: "1st place medal", - unicode_version: "9.0", - }, - { - emoji: "🥈", - aliases: ["2nd_place_medal"], - tags: ["silver"], - category: "Activities", - description: "2nd place medal", - unicode_version: "9.0", - }, - { - emoji: "🥉", - aliases: ["3rd_place_medal"], - tags: ["bronze"], - category: "Activities", - description: "3rd place medal", - unicode_version: "9.0", - }, - { - emoji: "⚽", - aliases: ["soccer"], - tags: ["sports"], - category: "Activities", - description: "soccer ball", - unicode_version: "5.2", - }, - { - emoji: "⚾", - aliases: ["baseball"], - tags: ["sports"], - category: "Activities", - description: "baseball", - unicode_version: "5.2", - }, - { - emoji: "🥎", - aliases: ["softball"], - tags: [], - category: "Activities", - description: "softball", - unicode_version: "11.0", - }, - { - emoji: "🏀", - aliases: ["basketball"], - tags: ["sports"], - category: "Activities", - description: "basketball", - unicode_version: "6.0", - }, - { - emoji: "🏐", - aliases: ["volleyball"], - tags: [], - category: "Activities", - description: "volleyball", - unicode_version: "8.0", - }, - { - emoji: "🏈", - aliases: ["football"], - tags: ["sports"], - category: "Activities", - description: "american football", - unicode_version: "6.0", - }, - { - emoji: "🏉", - aliases: ["rugby_football"], - tags: [], - category: "Activities", - description: "rugby football", - unicode_version: "6.0", - }, - { - emoji: "🎾", - aliases: ["tennis"], - tags: ["sports"], - category: "Activities", - description: "tennis", - unicode_version: "6.0", - }, - { - emoji: "🥏", - aliases: ["flying_disc"], - tags: [], - category: "Activities", - description: "flying disc", - unicode_version: "11.0", - }, - { - emoji: "🎳", - aliases: ["bowling"], - tags: [], - category: "Activities", - description: "bowling", - unicode_version: "6.0", - }, - { - emoji: "🏏", - aliases: ["cricket_game"], - tags: [], - category: "Activities", - description: "cricket game", - unicode_version: "8.0", - }, - { - emoji: "🏑", - aliases: ["field_hockey"], - tags: [], - category: "Activities", - description: "field hockey", - unicode_version: "8.0", - }, - { - emoji: "🏒", - aliases: ["ice_hockey"], - tags: [], - category: "Activities", - description: "ice hockey", - unicode_version: "8.0", - }, - { - emoji: "🥍", - aliases: ["lacrosse"], - tags: [], - category: "Activities", - description: "lacrosse", - unicode_version: "11.0", - }, - { - emoji: "🏓", - aliases: ["ping_pong"], - tags: [], - category: "Activities", - description: "ping pong", - unicode_version: "8.0", - }, - { - emoji: "🏸", - aliases: ["badminton"], - tags: [], - category: "Activities", - description: "badminton", - unicode_version: "8.0", - }, - { - emoji: "🥊", - aliases: ["boxing_glove"], - tags: [], - category: "Activities", - description: "boxing glove", - unicode_version: "9.0", - }, - { - emoji: "🥋", - aliases: ["martial_arts_uniform"], - tags: [], - category: "Activities", - description: "martial arts uniform", - unicode_version: "9.0", - }, - { - emoji: "🥅", - aliases: ["goal_net"], - tags: [], - category: "Activities", - description: "goal net", - unicode_version: "9.0", - }, - { - emoji: "⛳", - aliases: ["golf"], - tags: [], - category: "Activities", - description: "flag in hole", - unicode_version: "5.2", - }, - { - emoji: "⛸️", - aliases: ["ice_skate"], - tags: ["skating"], - category: "Activities", - description: "ice skate", - unicode_version: "5.2", - }, - { - emoji: "🎣", - aliases: ["fishing_pole_and_fish"], - tags: [], - category: "Activities", - description: "fishing pole", - unicode_version: "6.0", - }, - { - emoji: "🤿", - aliases: ["diving_mask"], - tags: [], - category: "Activities", - description: "diving mask", - unicode_version: "12.0", - }, - { - emoji: "🎽", - aliases: ["running_shirt_with_sash"], - tags: ["marathon"], - category: "Activities", - description: "running shirt", - unicode_version: "6.0", - }, - { - emoji: "🎿", - aliases: ["ski"], - tags: [], - category: "Activities", - description: "skis", - unicode_version: "6.0", - }, - { - emoji: "🛷", - aliases: ["sled"], - tags: [], - category: "Activities", - description: "sled", - unicode_version: "11.0", - }, - { - emoji: "🥌", - aliases: ["curling_stone"], - tags: [], - category: "Activities", - description: "curling stone", - unicode_version: "11.0", - }, - { - emoji: "🎯", - aliases: ["dart"], - tags: ["target"], - category: "Activities", - description: "bullseye", - unicode_version: "6.0", - }, - { - emoji: "🪀", - aliases: ["yo_yo"], - tags: [], - category: "Activities", - description: "yo-yo", - unicode_version: "12.0", - }, - { - emoji: "🪁", - aliases: ["kite"], - tags: [], - category: "Activities", - description: "kite", - unicode_version: "12.0", - }, - { - emoji: "🎱", - aliases: ["8ball"], - tags: ["pool", "billiards"], - category: "Activities", - description: "pool 8 ball", - unicode_version: "6.0", - }, - { - emoji: "🔮", - aliases: ["crystal_ball"], - tags: ["fortune"], - category: "Activities", - description: "crystal ball", - unicode_version: "6.0", - }, - { - emoji: "🪄", - aliases: ["magic_wand"], - tags: [], - category: "Activities", - description: "magic wand", - unicode_version: "13.0", - }, - { - emoji: "🧿", - aliases: ["nazar_amulet"], - tags: [], - category: "Activities", - description: "nazar amulet", - unicode_version: "11.0", - }, - { - emoji: "🎮", - aliases: ["video_game"], - tags: ["play", "controller", "console"], - category: "Activities", - description: "video game", - unicode_version: "6.0", - }, - { - emoji: "🕹️", - aliases: ["joystick"], - tags: [], - category: "Activities", - description: "joystick", - unicode_version: "7.0", - }, - { - emoji: "🎰", - aliases: ["slot_machine"], - tags: [], - category: "Activities", - description: "slot machine", - unicode_version: "6.0", - }, - { - emoji: "🎲", - aliases: ["game_die"], - tags: ["dice", "gambling"], - category: "Activities", - description: "game die", - unicode_version: "6.0", - }, - { - emoji: "🧩", - aliases: ["jigsaw"], - tags: [], - category: "Activities", - description: "puzzle piece", - unicode_version: "11.0", - }, - { - emoji: "🧸", - aliases: ["teddy_bear"], - tags: [], - category: "Activities", - description: "teddy bear", - unicode_version: "11.0", - }, - { - emoji: "🪅", - aliases: ["pinata"], - tags: [], - category: "Activities", - description: "piñata", - unicode_version: "13.0", - }, - { - emoji: "🪆", - aliases: ["nesting_dolls"], - tags: [], - category: "Activities", - description: "nesting dolls", - unicode_version: "13.0", - }, - { - emoji: "♠️", - aliases: ["spades"], - tags: [], - category: "Activities", - description: "spade suit", - unicode_version: "", - }, - { - emoji: "♥️", - aliases: ["hearts"], - tags: [], - category: "Activities", - description: "heart suit", - unicode_version: "", - }, - { - emoji: "♦️", - aliases: ["diamonds"], - tags: [], - category: "Activities", - description: "diamond suit", - unicode_version: "", - }, - { - emoji: "♣️", - aliases: ["clubs"], - tags: [], - category: "Activities", - description: "club suit", - unicode_version: "", - }, - { - emoji: "♟️", - aliases: ["chess_pawn"], - tags: [], - category: "Activities", - description: "chess pawn", - unicode_version: "11.0", - }, - { - emoji: "🃏", - aliases: ["black_joker"], - tags: [], - category: "Activities", - description: "joker", - unicode_version: "6.0", - }, - { - emoji: "🀄", - aliases: ["mahjong"], - tags: [], - category: "Activities", - description: "mahjong red dragon", - unicode_version: "", - }, - { - emoji: "🎴", - aliases: ["flower_playing_cards"], - tags: [], - category: "Activities", - description: "flower playing cards", - unicode_version: "6.0", - }, - { - emoji: "🎭", - aliases: ["performing_arts"], - tags: ["theater", "drama"], - category: "Activities", - description: "performing arts", - unicode_version: "6.0", - }, - { - emoji: "🖼️", - aliases: ["framed_picture"], - tags: [], - category: "Activities", - description: "framed picture", - unicode_version: "7.0", - }, - { - emoji: "🎨", - aliases: ["art"], - tags: ["design", "paint"], - category: "Activities", - description: "artist palette", - unicode_version: "6.0", - }, - { - emoji: "🧵", - aliases: ["thread"], - tags: [], - category: "Activities", - description: "thread", - unicode_version: "11.0", - }, - { - emoji: "🪡", - aliases: ["sewing_needle"], - tags: [], - category: "Activities", - description: "sewing needle", - unicode_version: "13.0", - }, - { - emoji: "🧶", - aliases: ["yarn"], - tags: [], - category: "Activities", - description: "yarn", - unicode_version: "11.0", - }, - { - emoji: "🪢", - aliases: ["knot"], - tags: [], - category: "Activities", - description: "knot", - unicode_version: "13.0", - }, - { - emoji: "👓", - aliases: ["eyeglasses"], - tags: ["glasses"], - category: "Objects", - description: "glasses", - unicode_version: "6.0", - }, - { - emoji: "🕶️", - aliases: ["dark_sunglasses"], - tags: [], - category: "Objects", - description: "sunglasses", - unicode_version: "7.0", - }, - { - emoji: "🥽", - aliases: ["goggles"], - tags: [], - category: "Objects", - description: "goggles", - unicode_version: "11.0", - }, - { - emoji: "🥼", - aliases: ["lab_coat"], - tags: [], - category: "Objects", - description: "lab coat", - unicode_version: "11.0", - }, - { - emoji: "🦺", - aliases: ["safety_vest"], - tags: [], - category: "Objects", - description: "safety vest", - unicode_version: "12.0", - }, - { - emoji: "👔", - aliases: ["necktie"], - tags: ["shirt", "formal"], - category: "Objects", - description: "necktie", - unicode_version: "6.0", - }, - { - emoji: "👕", - aliases: ["shirt", "tshirt"], - tags: [], - category: "Objects", - description: "t-shirt", - unicode_version: "6.0", - }, - { - emoji: "👖", - aliases: ["jeans"], - tags: ["pants"], - category: "Objects", - description: "jeans", - unicode_version: "6.0", - }, - { - emoji: "🧣", - aliases: ["scarf"], - tags: [], - category: "Objects", - description: "scarf", - unicode_version: "11.0", - }, - { - emoji: "🧤", - aliases: ["gloves"], - tags: [], - category: "Objects", - description: "gloves", - unicode_version: "11.0", - }, - { - emoji: "🧥", - aliases: ["coat"], - tags: [], - category: "Objects", - description: "coat", - unicode_version: "11.0", - }, - { - emoji: "🧦", - aliases: ["socks"], - tags: [], - category: "Objects", - description: "socks", - unicode_version: "11.0", - }, - { - emoji: "👗", - aliases: ["dress"], - tags: [], - category: "Objects", - description: "dress", - unicode_version: "6.0", - }, - { - emoji: "👘", - aliases: ["kimono"], - tags: [], - category: "Objects", - description: "kimono", - unicode_version: "6.0", - }, - { - emoji: "🥻", - aliases: ["sari"], - tags: [], - category: "Objects", - description: "sari", - unicode_version: "12.0", - }, - { - emoji: "🩱", - aliases: ["one_piece_swimsuit"], - tags: [], - category: "Objects", - description: "one-piece swimsuit", - unicode_version: "12.0", - }, - { - emoji: "🩲", - aliases: ["swim_brief"], - tags: [], - category: "Objects", - description: "briefs", - unicode_version: "12.0", - }, - { - emoji: "🩳", - aliases: ["shorts"], - tags: [], - category: "Objects", - description: "shorts", - unicode_version: "12.0", - }, - { - emoji: "👙", - aliases: ["bikini"], - tags: ["beach"], - category: "Objects", - description: "bikini", - unicode_version: "6.0", - }, - { - emoji: "👚", - aliases: ["womans_clothes"], - tags: [], - category: "Objects", - description: "woman’s clothes", - unicode_version: "6.0", - }, - { - emoji: "👛", - aliases: ["purse"], - tags: [], - category: "Objects", - description: "purse", - unicode_version: "6.0", - }, - { - emoji: "👜", - aliases: ["handbag"], - tags: ["bag"], - category: "Objects", - description: "handbag", - unicode_version: "6.0", - }, - { - emoji: "👝", - aliases: ["pouch"], - tags: ["bag"], - category: "Objects", - description: "clutch bag", - unicode_version: "6.0", - }, - { - emoji: "🛍️", - aliases: ["shopping"], - tags: ["bags"], - category: "Objects", - description: "shopping bags", - unicode_version: "7.0", - }, - { - emoji: "🎒", - aliases: ["school_satchel"], - tags: [], - category: "Objects", - description: "backpack", - unicode_version: "6.0", - }, - { - emoji: "🩴", - aliases: ["thong_sandal"], - tags: [], - category: "Objects", - description: "thong sandal", - unicode_version: "13.0", - }, - { - emoji: "👞", - aliases: ["mans_shoe", "shoe"], - tags: [], - category: "Objects", - description: "man’s shoe", - unicode_version: "6.0", - }, - { - emoji: "👟", - aliases: ["athletic_shoe"], - tags: ["sneaker", "sport", "running"], - category: "Objects", - description: "running shoe", - unicode_version: "6.0", - }, - { - emoji: "🥾", - aliases: ["hiking_boot"], - tags: [], - category: "Objects", - description: "hiking boot", - unicode_version: "11.0", - }, - { - emoji: "🥿", - aliases: ["flat_shoe"], - tags: [], - category: "Objects", - description: "flat shoe", - unicode_version: "11.0", - }, - { - emoji: "👠", - aliases: ["high_heel"], - tags: ["shoe"], - category: "Objects", - description: "high-heeled shoe", - unicode_version: "6.0", - }, - { - emoji: "👡", - aliases: ["sandal"], - tags: ["shoe"], - category: "Objects", - description: "woman’s sandal", - unicode_version: "6.0", - }, - { - emoji: "🩰", - aliases: ["ballet_shoes"], - tags: [], - category: "Objects", - description: "ballet shoes", - unicode_version: "12.0", - }, - { - emoji: "👢", - aliases: ["boot"], - tags: [], - category: "Objects", - description: "woman’s boot", - unicode_version: "6.0", - }, - { - emoji: "👑", - aliases: ["crown"], - tags: ["king", "queen", "royal"], - category: "Objects", - description: "crown", - unicode_version: "6.0", - }, - { - emoji: "👒", - aliases: ["womans_hat"], - tags: [], - category: "Objects", - description: "woman’s hat", - unicode_version: "6.0", - }, - { - emoji: "🎩", - aliases: ["tophat"], - tags: ["hat", "classy"], - category: "Objects", - description: "top hat", - unicode_version: "6.0", - }, - { - emoji: "🎓", - aliases: ["mortar_board"], - tags: ["education", "college", "university", "graduation"], - category: "Objects", - description: "graduation cap", - unicode_version: "6.0", - }, - { - emoji: "🧢", - aliases: ["billed_cap"], - tags: [], - category: "Objects", - description: "billed cap", - unicode_version: "11.0", - }, - { - emoji: "🪖", - aliases: ["military_helmet"], - tags: [], - category: "Objects", - description: "military helmet", - unicode_version: "13.0", - }, - { - emoji: "⛑️", - aliases: ["rescue_worker_helmet"], - tags: [], - category: "Objects", - description: "rescue worker’s helmet", - unicode_version: "5.2", - }, - { - emoji: "📿", - aliases: ["prayer_beads"], - tags: [], - category: "Objects", - description: "prayer beads", - unicode_version: "8.0", - }, - { - emoji: "💄", - aliases: ["lipstick"], - tags: ["makeup"], - category: "Objects", - description: "lipstick", - unicode_version: "6.0", - }, - { - emoji: "💍", - aliases: ["ring"], - tags: ["wedding", "marriage", "engaged"], - category: "Objects", - description: "ring", - unicode_version: "6.0", - }, - { - emoji: "💎", - aliases: ["gem"], - tags: ["diamond"], - category: "Objects", - description: "gem stone", - unicode_version: "6.0", - }, - { - emoji: "🔇", - aliases: ["mute"], - tags: ["sound", "volume"], - category: "Objects", - description: "muted speaker", - unicode_version: "6.0", - }, - { - emoji: "🔈", - aliases: ["speaker"], - tags: [], - category: "Objects", - description: "speaker low volume", - unicode_version: "6.0", - }, - { - emoji: "🔉", - aliases: ["sound"], - tags: ["volume"], - category: "Objects", - description: "speaker medium volume", - unicode_version: "6.0", - }, - { - emoji: "🔊", - aliases: ["loud_sound"], - tags: ["volume"], - category: "Objects", - description: "speaker high volume", - unicode_version: "6.0", - }, - { - emoji: "📢", - aliases: ["loudspeaker"], - tags: ["announcement"], - category: "Objects", - description: "loudspeaker", - unicode_version: "6.0", - }, - { - emoji: "📣", - aliases: ["mega"], - tags: [], - category: "Objects", - description: "megaphone", - unicode_version: "6.0", - }, - { - emoji: "📯", - aliases: ["postal_horn"], - tags: [], - category: "Objects", - description: "postal horn", - unicode_version: "6.0", - }, - { - emoji: "🔔", - aliases: ["bell"], - tags: ["sound", "notification"], - category: "Objects", - description: "bell", - unicode_version: "6.0", - }, - { - emoji: "🔕", - aliases: ["no_bell"], - tags: ["volume", "off"], - category: "Objects", - description: "bell with slash", - unicode_version: "6.0", - }, - { - emoji: "🎼", - aliases: ["musical_score"], - tags: [], - category: "Objects", - description: "musical score", - unicode_version: "6.0", - }, - { - emoji: "🎵", - aliases: ["musical_note"], - tags: [], - category: "Objects", - description: "musical note", - unicode_version: "6.0", - }, - { - emoji: "🎶", - aliases: ["notes"], - tags: ["music"], - category: "Objects", - description: "musical notes", - unicode_version: "6.0", - }, - { - emoji: "🎙️", - aliases: ["studio_microphone"], - tags: ["podcast"], - category: "Objects", - description: "studio microphone", - unicode_version: "7.0", - }, - { - emoji: "🎚️", - aliases: ["level_slider"], - tags: [], - category: "Objects", - description: "level slider", - unicode_version: "7.0", - }, - { - emoji: "🎛️", - aliases: ["control_knobs"], - tags: [], - category: "Objects", - description: "control knobs", - unicode_version: "7.0", - }, - { - emoji: "🎤", - aliases: ["microphone"], - tags: ["sing"], - category: "Objects", - description: "microphone", - unicode_version: "6.0", - }, - { - emoji: "🎧", - aliases: ["headphones"], - tags: ["music", "earphones"], - category: "Objects", - description: "headphone", - unicode_version: "6.0", - }, - { - emoji: "📻", - aliases: ["radio"], - tags: ["podcast"], - category: "Objects", - description: "radio", - unicode_version: "6.0", - }, - { - emoji: "🎷", - aliases: ["saxophone"], - tags: [], - category: "Objects", - description: "saxophone", - unicode_version: "6.0", - }, - { - emoji: "🪗", - aliases: ["accordion"], - tags: [], - category: "Objects", - description: "accordion", - unicode_version: "13.0", - }, - { - emoji: "🎸", - aliases: ["guitar"], - tags: ["rock"], - category: "Objects", - description: "guitar", - unicode_version: "6.0", - }, - { - emoji: "🎹", - aliases: ["musical_keyboard"], - tags: ["piano"], - category: "Objects", - description: "musical keyboard", - unicode_version: "6.0", - }, - { - emoji: "🎺", - aliases: ["trumpet"], - tags: [], - category: "Objects", - description: "trumpet", - unicode_version: "6.0", - }, - { - emoji: "🎻", - aliases: ["violin"], - tags: [], - category: "Objects", - description: "violin", - unicode_version: "6.0", - }, - { - emoji: "🪕", - aliases: ["banjo"], - tags: [], - category: "Objects", - description: "banjo", - unicode_version: "12.0", - }, - { - emoji: "🥁", - aliases: ["drum"], - tags: [], - category: "Objects", - description: "drum", - unicode_version: "", - }, - { - emoji: "🪘", - aliases: ["long_drum"], - tags: [], - category: "Objects", - description: "long drum", - unicode_version: "13.0", - }, - { - emoji: "📱", - aliases: ["iphone"], - tags: ["smartphone", "mobile"], - category: "Objects", - description: "mobile phone", - unicode_version: "6.0", - }, - { - emoji: "📲", - aliases: ["calling"], - tags: ["call", "incoming"], - category: "Objects", - description: "mobile phone with arrow", - unicode_version: "6.0", - }, - { - emoji: "☎️", - aliases: ["phone", "telephone"], - tags: [], - category: "Objects", - description: "telephone", - unicode_version: "", - }, - { - emoji: "📞", - aliases: ["telephone_receiver"], - tags: ["phone", "call"], - category: "Objects", - description: "telephone receiver", - unicode_version: "6.0", - }, - { - emoji: "📟", - aliases: ["pager"], - tags: [], - category: "Objects", - description: "pager", - unicode_version: "6.0", - }, - { - emoji: "📠", - aliases: ["fax"], - tags: [], - category: "Objects", - description: "fax machine", - unicode_version: "6.0", - }, - { - emoji: "🔋", - aliases: ["battery"], - tags: ["power"], - category: "Objects", - description: "battery", - unicode_version: "6.0", - }, - { - emoji: "🔌", - aliases: ["electric_plug"], - tags: [], - category: "Objects", - description: "electric plug", - unicode_version: "6.0", - }, - { - emoji: "💻", - aliases: ["computer"], - tags: ["desktop", "screen"], - category: "Objects", - description: "laptop", - unicode_version: "6.0", - }, - { - emoji: "🖥️", - aliases: ["desktop_computer"], - tags: [], - category: "Objects", - description: "desktop computer", - unicode_version: "7.0", - }, - { - emoji: "🖨️", - aliases: ["printer"], - tags: [], - category: "Objects", - description: "printer", - unicode_version: "7.0", - }, - { - emoji: "⌨️", - aliases: ["keyboard"], - tags: [], - category: "Objects", - description: "keyboard", - unicode_version: "", - }, - { - emoji: "🖱️", - aliases: ["computer_mouse"], - tags: [], - category: "Objects", - description: "computer mouse", - unicode_version: "7.0", - }, - { - emoji: "🖲️", - aliases: ["trackball"], - tags: [], - category: "Objects", - description: "trackball", - unicode_version: "7.0", - }, - { - emoji: "💽", - aliases: ["minidisc"], - tags: [], - category: "Objects", - description: "computer disk", - unicode_version: "6.0", - }, - { - emoji: "💾", - aliases: ["floppy_disk"], - tags: ["save"], - category: "Objects", - description: "floppy disk", - unicode_version: "6.0", - }, - { - emoji: "💿", - aliases: ["cd"], - tags: [], - category: "Objects", - description: "optical disk", - unicode_version: "6.0", - }, - { - emoji: "📀", - aliases: ["dvd"], - tags: [], - category: "Objects", - description: "dvd", - unicode_version: "6.0", - }, - { - emoji: "🧮", - aliases: ["abacus"], - tags: [], - category: "Objects", - description: "abacus", - unicode_version: "11.0", - }, - { - emoji: "🎥", - aliases: ["movie_camera"], - tags: ["film", "video"], - category: "Objects", - description: "movie camera", - unicode_version: "6.0", - }, - { - emoji: "🎞️", - aliases: ["film_strip"], - tags: [], - category: "Objects", - description: "film frames", - unicode_version: "7.0", - }, - { - emoji: "📽️", - aliases: ["film_projector"], - tags: [], - category: "Objects", - description: "film projector", - unicode_version: "7.0", - }, - { - emoji: "🎬", - aliases: ["clapper"], - tags: ["film"], - category: "Objects", - description: "clapper board", - unicode_version: "6.0", - }, - { - emoji: "📺", - aliases: ["tv"], - tags: [], - category: "Objects", - description: "television", - unicode_version: "6.0", - }, - { - emoji: "📷", - aliases: ["camera"], - tags: ["photo"], - category: "Objects", - description: "camera", - unicode_version: "6.0", - }, - { - emoji: "📸", - aliases: ["camera_flash"], - tags: ["photo"], - category: "Objects", - description: "camera with flash", - unicode_version: "7.0", - }, - { - emoji: "📹", - aliases: ["video_camera"], - tags: [], - category: "Objects", - description: "video camera", - unicode_version: "6.0", - }, - { - emoji: "📼", - aliases: ["vhs"], - tags: [], - category: "Objects", - description: "videocassette", - unicode_version: "6.0", - }, - { - emoji: "🔍", - aliases: ["mag"], - tags: ["search", "zoom"], - category: "Objects", - description: "magnifying glass tilted left", - unicode_version: "6.0", - }, - { - emoji: "🔎", - aliases: ["mag_right"], - tags: [], - category: "Objects", - description: "magnifying glass tilted right", - unicode_version: "6.0", - }, - { - emoji: "🕯️", - aliases: ["candle"], - tags: [], - category: "Objects", - description: "candle", - unicode_version: "7.0", - }, - { - emoji: "💡", - aliases: ["bulb"], - tags: ["idea", "light"], - category: "Objects", - description: "light bulb", - unicode_version: "6.0", - }, - { - emoji: "🔦", - aliases: ["flashlight"], - tags: [], - category: "Objects", - description: "flashlight", - unicode_version: "6.0", - }, - { - emoji: "🏮", - aliases: ["izakaya_lantern", "lantern"], - tags: [], - category: "Objects", - description: "red paper lantern", - unicode_version: "6.0", - }, - { - emoji: "🪔", - aliases: ["diya_lamp"], - tags: [], - category: "Objects", - description: "diya lamp", - unicode_version: "12.0", - }, - { - emoji: "📔", - aliases: ["notebook_with_decorative_cover"], - tags: [], - category: "Objects", - description: "notebook with decorative cover", - unicode_version: "6.0", - }, - { - emoji: "📕", - aliases: ["closed_book"], - tags: [], - category: "Objects", - description: "closed book", - unicode_version: "6.0", - }, - { - emoji: "📖", - aliases: ["book", "open_book"], - tags: [], - category: "Objects", - description: "open book", - unicode_version: "6.0", - }, - { - emoji: "📗", - aliases: ["green_book"], - tags: [], - category: "Objects", - description: "green book", - unicode_version: "6.0", - }, - { - emoji: "📘", - aliases: ["blue_book"], - tags: [], - category: "Objects", - description: "blue book", - unicode_version: "6.0", - }, - { - emoji: "📙", - aliases: ["orange_book"], - tags: [], - category: "Objects", - description: "orange book", - unicode_version: "6.0", - }, - { - emoji: "📚", - aliases: ["books"], - tags: ["library"], - category: "Objects", - description: "books", - unicode_version: "6.0", - }, - { - emoji: "📓", - aliases: ["notebook"], - tags: [], - category: "Objects", - description: "notebook", - unicode_version: "6.0", - }, - { - emoji: "📒", - aliases: ["ledger"], - tags: [], - category: "Objects", - description: "ledger", - unicode_version: "6.0", - }, - { - emoji: "📃", - aliases: ["page_with_curl"], - tags: [], - category: "Objects", - description: "page with curl", - unicode_version: "6.0", - }, - { - emoji: "📜", - aliases: ["scroll"], - tags: ["document"], - category: "Objects", - description: "scroll", - unicode_version: "6.0", - }, - { - emoji: "📄", - aliases: ["page_facing_up"], - tags: ["document"], - category: "Objects", - description: "page facing up", - unicode_version: "6.0", - }, - { - emoji: "📰", - aliases: ["newspaper"], - tags: ["press"], - category: "Objects", - description: "newspaper", - unicode_version: "6.0", - }, - { - emoji: "🗞️", - aliases: ["newspaper_roll"], - tags: ["press"], - category: "Objects", - description: "rolled-up newspaper", - unicode_version: "7.0", - }, - { - emoji: "📑", - aliases: ["bookmark_tabs"], - tags: [], - category: "Objects", - description: "bookmark tabs", - unicode_version: "6.0", - }, - { - emoji: "🔖", - aliases: ["bookmark"], - tags: [], - category: "Objects", - description: "bookmark", - unicode_version: "6.0", - }, - { - emoji: "🏷️", - aliases: ["label"], - tags: ["tag"], - category: "Objects", - description: "label", - unicode_version: "7.0", - }, - { - emoji: "💰", - aliases: ["moneybag"], - tags: ["dollar", "cream"], - category: "Objects", - description: "money bag", - unicode_version: "6.0", - }, - { - emoji: "🪙", - aliases: ["coin"], - tags: [], - category: "Objects", - description: "coin", - unicode_version: "13.0", - }, - { - emoji: "💴", - aliases: ["yen"], - tags: [], - category: "Objects", - description: "yen banknote", - unicode_version: "6.0", - }, - { - emoji: "💵", - aliases: ["dollar"], - tags: ["money"], - category: "Objects", - description: "dollar banknote", - unicode_version: "6.0", - }, - { - emoji: "💶", - aliases: ["euro"], - tags: [], - category: "Objects", - description: "euro banknote", - unicode_version: "6.0", - }, - { - emoji: "💷", - aliases: ["pound"], - tags: [], - category: "Objects", - description: "pound banknote", - unicode_version: "6.0", - }, - { - emoji: "💸", - aliases: ["money_with_wings"], - tags: ["dollar"], - category: "Objects", - description: "money with wings", - unicode_version: "6.0", - }, - { - emoji: "💳", - aliases: ["credit_card"], - tags: ["subscription"], - category: "Objects", - description: "credit card", - unicode_version: "6.0", - }, - { - emoji: "🧾", - aliases: ["receipt"], - tags: [], - category: "Objects", - description: "receipt", - unicode_version: "11.0", - }, - { - emoji: "💹", - aliases: ["chart"], - tags: [], - category: "Objects", - description: "chart increasing with yen", - unicode_version: "6.0", - }, - { - emoji: "✉️", - aliases: ["envelope"], - tags: ["letter", "email"], - category: "Objects", - description: "envelope", - unicode_version: "", - }, - { - emoji: "📧", - aliases: ["email", "e-mail"], - tags: [], - category: "Objects", - description: "e-mail", - unicode_version: "6.0", - }, - { - emoji: "📨", - aliases: ["incoming_envelope"], - tags: [], - category: "Objects", - description: "incoming envelope", - unicode_version: "6.0", - }, - { - emoji: "📩", - aliases: ["envelope_with_arrow"], - tags: [], - category: "Objects", - description: "envelope with arrow", - unicode_version: "6.0", - }, - { - emoji: "📤", - aliases: ["outbox_tray"], - tags: [], - category: "Objects", - description: "outbox tray", - unicode_version: "6.0", - }, - { - emoji: "📥", - aliases: ["inbox_tray"], - tags: [], - category: "Objects", - description: "inbox tray", - unicode_version: "6.0", - }, - { - emoji: "📦", - aliases: ["package"], - tags: ["shipping"], - category: "Objects", - description: "package", - unicode_version: "6.0", - }, - { - emoji: "📫", - aliases: ["mailbox"], - tags: [], - category: "Objects", - description: "closed mailbox with raised flag", - unicode_version: "6.0", - }, - { - emoji: "📪", - aliases: ["mailbox_closed"], - tags: [], - category: "Objects", - description: "closed mailbox with lowered flag", - unicode_version: "6.0", - }, - { - emoji: "📬", - aliases: ["mailbox_with_mail"], - tags: [], - category: "Objects", - description: "open mailbox with raised flag", - unicode_version: "6.0", - }, - { - emoji: "📭", - aliases: ["mailbox_with_no_mail"], - tags: [], - category: "Objects", - description: "open mailbox with lowered flag", - unicode_version: "6.0", - }, - { - emoji: "📮", - aliases: ["postbox"], - tags: [], - category: "Objects", - description: "postbox", - unicode_version: "6.0", - }, - { - emoji: "🗳️", - aliases: ["ballot_box"], - tags: [], - category: "Objects", - description: "ballot box with ballot", - unicode_version: "7.0", - }, - { - emoji: "✏️", - aliases: ["pencil2"], - tags: [], - category: "Objects", - description: "pencil", - unicode_version: "", - }, - { - emoji: "✒️", - aliases: ["black_nib"], - tags: [], - category: "Objects", - description: "black nib", - unicode_version: "", - }, - { - emoji: "🖋️", - aliases: ["fountain_pen"], - tags: [], - category: "Objects", - description: "fountain pen", - unicode_version: "7.0", - }, - { - emoji: "🖊️", - aliases: ["pen"], - tags: [], - category: "Objects", - description: "pen", - unicode_version: "7.0", - }, - { - emoji: "🖌️", - aliases: ["paintbrush"], - tags: [], - category: "Objects", - description: "paintbrush", - unicode_version: "7.0", - }, - { - emoji: "🖍️", - aliases: ["crayon"], - tags: [], - category: "Objects", - description: "crayon", - unicode_version: "7.0", - }, - { - emoji: "📝", - aliases: ["memo", "pencil"], - tags: ["document", "note"], - category: "Objects", - description: "memo", - unicode_version: "6.0", - }, - { - emoji: "💼", - aliases: ["briefcase"], - tags: ["business"], - category: "Objects", - description: "briefcase", - unicode_version: "6.0", - }, - { - emoji: "📁", - aliases: ["file_folder"], - tags: ["directory"], - category: "Objects", - description: "file folder", - unicode_version: "6.0", - }, - { - emoji: "📂", - aliases: ["open_file_folder"], - tags: [], - category: "Objects", - description: "open file folder", - unicode_version: "6.0", - }, - { - emoji: "🗂️", - aliases: ["card_index_dividers"], - tags: [], - category: "Objects", - description: "card index dividers", - unicode_version: "7.0", - }, - { - emoji: "📅", - aliases: ["date"], - tags: ["calendar", "schedule"], - category: "Objects", - description: "calendar", - unicode_version: "6.0", - }, - { - emoji: "📆", - aliases: ["calendar"], - tags: ["schedule"], - category: "Objects", - description: "tear-off calendar", - unicode_version: "6.0", - }, - { - emoji: "🗒️", - aliases: ["spiral_notepad"], - tags: [], - category: "Objects", - description: "spiral notepad", - unicode_version: "7.0", - }, - { - emoji: "🗓️", - aliases: ["spiral_calendar"], - tags: [], - category: "Objects", - description: "spiral calendar", - unicode_version: "7.0", - }, - { - emoji: "📇", - aliases: ["card_index"], - tags: [], - category: "Objects", - description: "card index", - unicode_version: "6.0", - }, - { - emoji: "📈", - aliases: ["chart_with_upwards_trend"], - tags: ["graph", "metrics"], - category: "Objects", - description: "chart increasing", - unicode_version: "6.0", - }, - { - emoji: "📉", - aliases: ["chart_with_downwards_trend"], - tags: ["graph", "metrics"], - category: "Objects", - description: "chart decreasing", - unicode_version: "6.0", - }, - { - emoji: "📊", - aliases: ["bar_chart"], - tags: ["stats", "metrics"], - category: "Objects", - description: "bar chart", - unicode_version: "6.0", - }, - { - emoji: "📋", - aliases: ["clipboard"], - tags: [], - category: "Objects", - description: "clipboard", - unicode_version: "6.0", - }, - { - emoji: "📌", - aliases: ["pushpin"], - tags: ["location"], - category: "Objects", - description: "pushpin", - unicode_version: "6.0", - }, - { - emoji: "📍", - aliases: ["round_pushpin"], - tags: ["location"], - category: "Objects", - description: "round pushpin", - unicode_version: "6.0", - }, - { - emoji: "📎", - aliases: ["paperclip"], - tags: [], - category: "Objects", - description: "paperclip", - unicode_version: "6.0", - }, - { - emoji: "🖇️", - aliases: ["paperclips"], - tags: [], - category: "Objects", - description: "linked paperclips", - unicode_version: "7.0", - }, - { - emoji: "📏", - aliases: ["straight_ruler"], - tags: [], - category: "Objects", - description: "straight ruler", - unicode_version: "6.0", - }, - { - emoji: "📐", - aliases: ["triangular_ruler"], - tags: [], - category: "Objects", - description: "triangular ruler", - unicode_version: "6.0", - }, - { - emoji: "✂️", - aliases: ["scissors"], - tags: ["cut"], - category: "Objects", - description: "scissors", - unicode_version: "", - }, - { - emoji: "🗃️", - aliases: ["card_file_box"], - tags: [], - category: "Objects", - description: "card file box", - unicode_version: "7.0", - }, - { - emoji: "🗄️", - aliases: ["file_cabinet"], - tags: [], - category: "Objects", - description: "file cabinet", - unicode_version: "7.0", - }, - { - emoji: "🗑️", - aliases: ["wastebasket"], - tags: ["trash"], - category: "Objects", - description: "wastebasket", - unicode_version: "7.0", - }, - { - emoji: "🔒", - aliases: ["lock"], - tags: ["security", "private"], - category: "Objects", - description: "locked", - unicode_version: "6.0", - }, - { - emoji: "🔓", - aliases: ["unlock"], - tags: ["security"], - category: "Objects", - description: "unlocked", - unicode_version: "6.0", - }, - { - emoji: "🔏", - aliases: ["lock_with_ink_pen"], - tags: [], - category: "Objects", - description: "locked with pen", - unicode_version: "6.0", - }, - { - emoji: "🔐", - aliases: ["closed_lock_with_key"], - tags: ["security"], - category: "Objects", - description: "locked with key", - unicode_version: "6.0", - }, - { - emoji: "🔑", - aliases: ["key"], - tags: ["lock", "password"], - category: "Objects", - description: "key", - unicode_version: "6.0", - }, - { - emoji: "🗝️", - aliases: ["old_key"], - tags: [], - category: "Objects", - description: "old key", - unicode_version: "7.0", - }, - { - emoji: "🔨", - aliases: ["hammer"], - tags: ["tool"], - category: "Objects", - description: "hammer", - unicode_version: "6.0", - }, - { - emoji: "🪓", - aliases: ["axe"], - tags: [], - category: "Objects", - description: "axe", - unicode_version: "12.0", - }, - { - emoji: "⛏️", - aliases: ["pick"], - tags: [], - category: "Objects", - description: "pick", - unicode_version: "5.2", - }, - { - emoji: "⚒️", - aliases: ["hammer_and_pick"], - tags: [], - category: "Objects", - description: "hammer and pick", - unicode_version: "4.1", - }, - { - emoji: "🛠️", - aliases: ["hammer_and_wrench"], - tags: [], - category: "Objects", - description: "hammer and wrench", - unicode_version: "7.0", - }, - { - emoji: "🗡️", - aliases: ["dagger"], - tags: [], - category: "Objects", - description: "dagger", - unicode_version: "7.0", - }, - { - emoji: "⚔️", - aliases: ["crossed_swords"], - tags: [], - category: "Objects", - description: "crossed swords", - unicode_version: "4.1", - }, - { - emoji: "🔫", - aliases: ["gun"], - tags: ["shoot", "weapon"], - category: "Objects", - description: "water pistol", - unicode_version: "6.0", - }, - { - emoji: "🪃", - aliases: ["boomerang"], - tags: [], - category: "Objects", - description: "boomerang", - unicode_version: "13.0", - }, - { - emoji: "🏹", - aliases: ["bow_and_arrow"], - tags: ["archery"], - category: "Objects", - description: "bow and arrow", - unicode_version: "8.0", - }, - { - emoji: "🛡️", - aliases: ["shield"], - tags: [], - category: "Objects", - description: "shield", - unicode_version: "7.0", - }, - { - emoji: "🪚", - aliases: ["carpentry_saw"], - tags: [], - category: "Objects", - description: "carpentry saw", - unicode_version: "13.0", - }, - { - emoji: "🔧", - aliases: ["wrench"], - tags: ["tool"], - category: "Objects", - description: "wrench", - unicode_version: "6.0", - }, - { - emoji: "🪛", - aliases: ["screwdriver"], - tags: [], - category: "Objects", - description: "screwdriver", - unicode_version: "13.0", - }, - { - emoji: "🔩", - aliases: ["nut_and_bolt"], - tags: [], - category: "Objects", - description: "nut and bolt", - unicode_version: "6.0", - }, - { - emoji: "⚙️", - aliases: ["gear"], - tags: [], - category: "Objects", - description: "gear", - unicode_version: "4.1", - }, - { - emoji: "🗜️", - aliases: ["clamp"], - tags: [], - category: "Objects", - description: "clamp", - unicode_version: "7.0", - }, - { - emoji: "⚖️", - aliases: ["balance_scale"], - tags: [], - category: "Objects", - description: "balance scale", - unicode_version: "4.1", - }, - { - emoji: "🦯", - aliases: ["probing_cane"], - tags: [], - category: "Objects", - description: "white cane", - unicode_version: "12.0", - }, - { - emoji: "🔗", - aliases: ["link"], - tags: [], - category: "Objects", - description: "link", - unicode_version: "6.0", - }, - { - emoji: "⛓️", - aliases: ["chains"], - tags: [], - category: "Objects", - description: "chains", - unicode_version: "5.2", - }, - { - emoji: "🪝", - aliases: ["hook"], - tags: [], - category: "Objects", - description: "hook", - unicode_version: "13.0", - }, - { - emoji: "🧰", - aliases: ["toolbox"], - tags: [], - category: "Objects", - description: "toolbox", - unicode_version: "11.0", - }, - { - emoji: "🧲", - aliases: ["magnet"], - tags: [], - category: "Objects", - description: "magnet", - unicode_version: "11.0", - }, - { - emoji: "🪜", - aliases: ["ladder"], - tags: [], - category: "Objects", - description: "ladder", - unicode_version: "13.0", - }, - { - emoji: "⚗️", - aliases: ["alembic"], - tags: [], - category: "Objects", - description: "alembic", - unicode_version: "4.1", - }, - { - emoji: "🧪", - aliases: ["test_tube"], - tags: [], - category: "Objects", - description: "test tube", - unicode_version: "11.0", - }, - { - emoji: "🧫", - aliases: ["petri_dish"], - tags: [], - category: "Objects", - description: "petri dish", - unicode_version: "11.0", - }, - { - emoji: "🧬", - aliases: ["dna"], - tags: [], - category: "Objects", - description: "dna", - unicode_version: "11.0", - }, - { - emoji: "🔬", - aliases: ["microscope"], - tags: ["science", "laboratory", "investigate"], - category: "Objects", - description: "microscope", - unicode_version: "6.0", - }, - { - emoji: "🔭", - aliases: ["telescope"], - tags: [], - category: "Objects", - description: "telescope", - unicode_version: "6.0", - }, - { - emoji: "📡", - aliases: ["satellite"], - tags: ["signal"], - category: "Objects", - description: "satellite antenna", - unicode_version: "6.0", - }, - { - emoji: "💉", - aliases: ["syringe"], - tags: ["health", "hospital", "needle"], - category: "Objects", - description: "syringe", - unicode_version: "6.0", - }, - { - emoji: "🩸", - aliases: ["drop_of_blood"], - tags: [], - category: "Objects", - description: "drop of blood", - unicode_version: "12.0", - }, - { - emoji: "💊", - aliases: ["pill"], - tags: ["health", "medicine"], - category: "Objects", - description: "pill", - unicode_version: "6.0", - }, - { - emoji: "🩹", - aliases: ["adhesive_bandage"], - tags: [], - category: "Objects", - description: "adhesive bandage", - unicode_version: "12.0", - }, - { - emoji: "🩺", - aliases: ["stethoscope"], - tags: [], - category: "Objects", - description: "stethoscope", - unicode_version: "12.0", - }, - { - emoji: "🚪", - aliases: ["door"], - tags: [], - category: "Objects", - description: "door", - unicode_version: "6.0", - }, - { - emoji: "🛗", - aliases: ["elevator"], - tags: [], - category: "Objects", - description: "elevator", - unicode_version: "13.0", - }, - { - emoji: "🪞", - aliases: ["mirror"], - tags: [], - category: "Objects", - description: "mirror", - unicode_version: "13.0", - }, - { - emoji: "🪟", - aliases: ["window"], - tags: [], - category: "Objects", - description: "window", - unicode_version: "13.0", - }, - { - emoji: "🛏️", - aliases: ["bed"], - tags: [], - category: "Objects", - description: "bed", - unicode_version: "7.0", - }, - { - emoji: "🛋️", - aliases: ["couch_and_lamp"], - tags: [], - category: "Objects", - description: "couch and lamp", - unicode_version: "7.0", - }, - { - emoji: "🪑", - aliases: ["chair"], - tags: [], - category: "Objects", - description: "chair", - unicode_version: "12.0", - }, - { - emoji: "🚽", - aliases: ["toilet"], - tags: ["wc"], - category: "Objects", - description: "toilet", - unicode_version: "6.0", - }, - { - emoji: "🪠", - aliases: ["plunger"], - tags: [], - category: "Objects", - description: "plunger", - unicode_version: "13.0", - }, - { - emoji: "🚿", - aliases: ["shower"], - tags: ["bath"], - category: "Objects", - description: "shower", - unicode_version: "6.0", - }, - { - emoji: "🛁", - aliases: ["bathtub"], - tags: [], - category: "Objects", - description: "bathtub", - unicode_version: "6.0", - }, - { - emoji: "🪤", - aliases: ["mouse_trap"], - tags: [], - category: "Objects", - description: "mouse trap", - unicode_version: "13.0", - }, - { - emoji: "🪒", - aliases: ["razor"], - tags: [], - category: "Objects", - description: "razor", - unicode_version: "12.0", - }, - { - emoji: "🧴", - aliases: ["lotion_bottle"], - tags: [], - category: "Objects", - description: "lotion bottle", - unicode_version: "11.0", - }, - { - emoji: "🧷", - aliases: ["safety_pin"], - tags: [], - category: "Objects", - description: "safety pin", - unicode_version: "11.0", - }, - { - emoji: "🧹", - aliases: ["broom"], - tags: [], - category: "Objects", - description: "broom", - unicode_version: "11.0", - }, - { - emoji: "🧺", - aliases: ["basket"], - tags: [], - category: "Objects", - description: "basket", - unicode_version: "11.0", - }, - { - emoji: "🧻", - aliases: ["roll_of_paper"], - tags: ["toilet"], - category: "Objects", - description: "roll of paper", - unicode_version: "11.0", - }, - { - emoji: "🪣", - aliases: ["bucket"], - tags: [], - category: "Objects", - description: "bucket", - unicode_version: "13.0", - }, - { - emoji: "🧼", - aliases: ["soap"], - tags: [], - category: "Objects", - description: "soap", - unicode_version: "11.0", - }, - { - emoji: "🪥", - aliases: ["toothbrush"], - tags: [], - category: "Objects", - description: "toothbrush", - unicode_version: "13.0", - }, - { - emoji: "🧽", - aliases: ["sponge"], - tags: [], - category: "Objects", - description: "sponge", - unicode_version: "11.0", - }, - { - emoji: "🧯", - aliases: ["fire_extinguisher"], - tags: [], - category: "Objects", - description: "fire extinguisher", - unicode_version: "11.0", - }, - { - emoji: "🛒", - aliases: ["shopping_cart"], - tags: [], - category: "Objects", - description: "shopping cart", - unicode_version: "9.0", - }, - { - emoji: "🚬", - aliases: ["smoking"], - tags: ["cigarette"], - category: "Objects", - description: "cigarette", - unicode_version: "6.0", - }, - { - emoji: "⚰️", - aliases: ["coffin"], - tags: ["funeral"], - category: "Objects", - description: "coffin", - unicode_version: "4.1", - }, - { - emoji: "🪦", - aliases: ["headstone"], - tags: [], - category: "Objects", - description: "headstone", - unicode_version: "13.0", - }, - { - emoji: "⚱️", - aliases: ["funeral_urn"], - tags: [], - category: "Objects", - description: "funeral urn", - unicode_version: "4.1", - }, - { - emoji: "🗿", - aliases: ["moyai"], - tags: ["stone"], - category: "Objects", - description: "moai", - unicode_version: "6.0", - }, - { - emoji: "🪧", - aliases: ["placard"], - tags: [], - category: "Objects", - description: "placard", - unicode_version: "13.0", - }, - { - emoji: "🏧", - aliases: ["atm"], - tags: [], - category: "Symbols", - description: "ATM sign", - unicode_version: "6.0", - }, - { - emoji: "🚮", - aliases: ["put_litter_in_its_place"], - tags: [], - category: "Symbols", - description: "litter in bin sign", - unicode_version: "6.0", - }, - { - emoji: "🚰", - aliases: ["potable_water"], - tags: [], - category: "Symbols", - description: "potable water", - unicode_version: "6.0", - }, - { - emoji: "♿", - aliases: ["wheelchair"], - tags: ["accessibility"], - category: "Symbols", - description: "wheelchair symbol", - unicode_version: "4.1", - }, - { - emoji: "🚹", - aliases: ["mens"], - tags: [], - category: "Symbols", - description: "men’s room", - unicode_version: "6.0", - }, - { - emoji: "🚺", - aliases: ["womens"], - tags: [], - category: "Symbols", - description: "women’s room", - unicode_version: "6.0", - }, - { - emoji: "🚻", - aliases: ["restroom"], - tags: ["toilet"], - category: "Symbols", - description: "restroom", - unicode_version: "6.0", - }, - { - emoji: "🚼", - aliases: ["baby_symbol"], - tags: [], - category: "Symbols", - description: "baby symbol", - unicode_version: "6.0", - }, - { - emoji: "🚾", - aliases: ["wc"], - tags: ["toilet", "restroom"], - category: "Symbols", - description: "water closet", - unicode_version: "6.0", - }, - { - emoji: "🛂", - aliases: ["passport_control"], - tags: [], - category: "Symbols", - description: "passport control", - unicode_version: "6.0", - }, - { - emoji: "🛃", - aliases: ["customs"], - tags: [], - category: "Symbols", - description: "customs", - unicode_version: "6.0", - }, - { - emoji: "🛄", - aliases: ["baggage_claim"], - tags: ["airport"], - category: "Symbols", - description: "baggage claim", - unicode_version: "6.0", - }, - { - emoji: "🛅", - aliases: ["left_luggage"], - tags: [], - category: "Symbols", - description: "left luggage", - unicode_version: "6.0", - }, - { - emoji: "⚠️", - aliases: ["warning"], - tags: ["wip"], - category: "Symbols", - description: "warning", - unicode_version: "4.0", - }, - { - emoji: "🚸", - aliases: ["children_crossing"], - tags: [], - category: "Symbols", - description: "children crossing", - unicode_version: "6.0", - }, - { - emoji: "⛔", - aliases: ["no_entry"], - tags: ["limit"], - category: "Symbols", - description: "no entry", - unicode_version: "5.2", - }, - { - emoji: "🚫", - aliases: ["no_entry_sign"], - tags: ["block", "forbidden"], - category: "Symbols", - description: "prohibited", - unicode_version: "6.0", - }, - { - emoji: "🚳", - aliases: ["no_bicycles"], - tags: [], - category: "Symbols", - description: "no bicycles", - unicode_version: "6.0", - }, - { - emoji: "🚭", - aliases: ["no_smoking"], - tags: [], - category: "Symbols", - description: "no smoking", - unicode_version: "6.0", - }, - { - emoji: "🚯", - aliases: ["do_not_litter"], - tags: [], - category: "Symbols", - description: "no littering", - unicode_version: "6.0", - }, - { - emoji: "🚱", - aliases: ["non-potable_water"], - tags: [], - category: "Symbols", - description: "non-potable water", - unicode_version: "6.0", - }, - { - emoji: "🚷", - aliases: ["no_pedestrians"], - tags: [], - category: "Symbols", - description: "no pedestrians", - unicode_version: "6.0", - }, - { - emoji: "📵", - aliases: ["no_mobile_phones"], - tags: [], - category: "Symbols", - description: "no mobile phones", - unicode_version: "6.0", - }, - { - emoji: "🔞", - aliases: ["underage"], - tags: [], - category: "Symbols", - description: "no one under eighteen", - unicode_version: "6.0", - }, - { - emoji: "☢️", - aliases: ["radioactive"], - tags: [], - category: "Symbols", - description: "radioactive", - unicode_version: "", - }, - { - emoji: "☣️", - aliases: ["biohazard"], - tags: [], - category: "Symbols", - description: "biohazard", - unicode_version: "", - }, - { - emoji: "⬆️", - aliases: ["arrow_up"], - tags: [], - category: "Symbols", - description: "up arrow", - unicode_version: "4.0", - }, - { - emoji: "↗️", - aliases: ["arrow_upper_right"], - tags: [], - category: "Symbols", - description: "up-right arrow", - unicode_version: "", - }, - { - emoji: "➡️", - aliases: ["arrow_right"], - tags: [], - category: "Symbols", - description: "right arrow", - unicode_version: "", - }, - { - emoji: "↘️", - aliases: ["arrow_lower_right"], - tags: [], - category: "Symbols", - description: "down-right arrow", - unicode_version: "", - }, - { - emoji: "⬇️", - aliases: ["arrow_down"], - tags: [], - category: "Symbols", - description: "down arrow", - unicode_version: "4.0", - }, - { - emoji: "↙️", - aliases: ["arrow_lower_left"], - tags: [], - category: "Symbols", - description: "down-left arrow", - unicode_version: "", - }, - { - emoji: "⬅️", - aliases: ["arrow_left"], - tags: [], - category: "Symbols", - description: "left arrow", - unicode_version: "4.0", - }, - { - emoji: "↖️", - aliases: ["arrow_upper_left"], - tags: [], - category: "Symbols", - description: "up-left arrow", - unicode_version: "", - }, - { - emoji: "↕️", - aliases: ["arrow_up_down"], - tags: [], - category: "Symbols", - description: "up-down arrow", - unicode_version: "", - }, - { - emoji: "↔️", - aliases: ["left_right_arrow"], - tags: [], - category: "Symbols", - description: "left-right arrow", - unicode_version: "", - }, - { - emoji: "↩️", - aliases: ["leftwards_arrow_with_hook"], - tags: ["return"], - category: "Symbols", - description: "right arrow curving left", - unicode_version: "", - }, - { - emoji: "↪️", - aliases: ["arrow_right_hook"], - tags: [], - category: "Symbols", - description: "left arrow curving right", - unicode_version: "", - }, - { - emoji: "⤴️", - aliases: ["arrow_heading_up"], - tags: [], - category: "Symbols", - description: "right arrow curving up", - unicode_version: "", - }, - { - emoji: "⤵️", - aliases: ["arrow_heading_down"], - tags: [], - category: "Symbols", - description: "right arrow curving down", - unicode_version: "", - }, - { - emoji: "🔃", - aliases: ["arrows_clockwise"], - tags: [], - category: "Symbols", - description: "clockwise vertical arrows", - unicode_version: "6.0", - }, - { - emoji: "🔄", - aliases: ["arrows_counterclockwise"], - tags: ["sync"], - category: "Symbols", - description: "counterclockwise arrows button", - unicode_version: "6.0", - }, - { - emoji: "🔙", - aliases: ["back"], - tags: [], - category: "Symbols", - description: "BACK arrow", - unicode_version: "6.0", - }, - { - emoji: "🔚", - aliases: ["end"], - tags: [], - category: "Symbols", - description: "END arrow", - unicode_version: "6.0", - }, - { - emoji: "🔛", - aliases: ["on"], - tags: [], - category: "Symbols", - description: "ON! arrow", - unicode_version: "6.0", - }, - { - emoji: "🔜", - aliases: ["soon"], - tags: [], - category: "Symbols", - description: "SOON arrow", - unicode_version: "6.0", - }, - { - emoji: "🔝", - aliases: ["top"], - tags: [], - category: "Symbols", - description: "TOP arrow", - unicode_version: "6.0", - }, - { - emoji: "🛐", - aliases: ["place_of_worship"], - tags: [], - category: "Symbols", - description: "place of worship", - unicode_version: "8.0", - }, - { - emoji: "⚛️", - aliases: ["atom_symbol"], - tags: [], - category: "Symbols", - description: "atom symbol", - unicode_version: "4.1", - }, - { - emoji: "🕉️", - aliases: ["om"], - tags: [], - category: "Symbols", - description: "om", - unicode_version: "7.0", - }, - { - emoji: "✡️", - aliases: ["star_of_david"], - tags: [], - category: "Symbols", - description: "star of David", - unicode_version: "", - }, - { - emoji: "☸️", - aliases: ["wheel_of_dharma"], - tags: [], - category: "Symbols", - description: "wheel of dharma", - unicode_version: "", - }, - { - emoji: "☯️", - aliases: ["yin_yang"], - tags: [], - category: "Symbols", - description: "yin yang", - unicode_version: "", - }, - { - emoji: "✝️", - aliases: ["latin_cross"], - tags: [], - category: "Symbols", - description: "latin cross", - unicode_version: "", - }, - { - emoji: "☦️", - aliases: ["orthodox_cross"], - tags: [], - category: "Symbols", - description: "orthodox cross", - unicode_version: "", - }, - { - emoji: "☪️", - aliases: ["star_and_crescent"], - tags: [], - category: "Symbols", - description: "star and crescent", - unicode_version: "", - }, - { - emoji: "☮️", - aliases: ["peace_symbol"], - tags: [], - category: "Symbols", - description: "peace symbol", - unicode_version: "", - }, - { - emoji: "🕎", - aliases: ["menorah"], - tags: [], - category: "Symbols", - description: "menorah", - unicode_version: "8.0", - }, - { - emoji: "🔯", - aliases: ["six_pointed_star"], - tags: [], - category: "Symbols", - description: "dotted six-pointed star", - unicode_version: "6.0", - }, - { - emoji: "♈", - aliases: ["aries"], - tags: [], - category: "Symbols", - description: "Aries", - unicode_version: "", - }, - { - emoji: "♉", - aliases: ["taurus"], - tags: [], - category: "Symbols", - description: "Taurus", - unicode_version: "", - }, - { - emoji: "♊", - aliases: ["gemini"], - tags: [], - category: "Symbols", - description: "Gemini", - unicode_version: "", - }, - { - emoji: "♋", - aliases: ["cancer"], - tags: [], - category: "Symbols", - description: "Cancer", - unicode_version: "", - }, - { - emoji: "♌", - aliases: ["leo"], - tags: [], - category: "Symbols", - description: "Leo", - unicode_version: "", - }, - { - emoji: "♍", - aliases: ["virgo"], - tags: [], - category: "Symbols", - description: "Virgo", - unicode_version: "", - }, - { - emoji: "♎", - aliases: ["libra"], - tags: [], - category: "Symbols", - description: "Libra", - unicode_version: "", - }, - { - emoji: "♏", - aliases: ["scorpius"], - tags: [], - category: "Symbols", - description: "Scorpio", - unicode_version: "", - }, - { - emoji: "♐", - aliases: ["sagittarius"], - tags: [], - category: "Symbols", - description: "Sagittarius", - unicode_version: "", - }, - { - emoji: "♑", - aliases: ["capricorn"], - tags: [], - category: "Symbols", - description: "Capricorn", - unicode_version: "", - }, - { - emoji: "♒", - aliases: ["aquarius"], - tags: [], - category: "Symbols", - description: "Aquarius", - unicode_version: "", - }, - { - emoji: "♓", - aliases: ["pisces"], - tags: [], - category: "Symbols", - description: "Pisces", - unicode_version: "", - }, - { - emoji: "⛎", - aliases: ["ophiuchus"], - tags: [], - category: "Symbols", - description: "Ophiuchus", - unicode_version: "6.0", - }, - { - emoji: "🔀", - aliases: ["twisted_rightwards_arrows"], - tags: ["shuffle"], - category: "Symbols", - description: "shuffle tracks button", - unicode_version: "6.0", - }, - { - emoji: "🔁", - aliases: ["repeat"], - tags: ["loop"], - category: "Symbols", - description: "repeat button", - unicode_version: "6.0", - }, - { - emoji: "🔂", - aliases: ["repeat_one"], - tags: [], - category: "Symbols", - description: "repeat single button", - unicode_version: "6.0", - }, - { - emoji: "▶️", - aliases: ["arrow_forward"], - tags: [], - category: "Symbols", - description: "play button", - unicode_version: "", - }, - { - emoji: "⏩", - aliases: ["fast_forward"], - tags: [], - category: "Symbols", - description: "fast-forward button", - unicode_version: "6.0", - }, - { - emoji: "⏭️", - aliases: ["next_track_button"], - tags: [], - category: "Symbols", - description: "next track button", - unicode_version: "6.0", - }, - { - emoji: "⏯️", - aliases: ["play_or_pause_button"], - tags: [], - category: "Symbols", - description: "play or pause button", - unicode_version: "6.0", - }, - { - emoji: "◀️", - aliases: ["arrow_backward"], - tags: [], - category: "Symbols", - description: "reverse button", - unicode_version: "", - }, - { - emoji: "⏪", - aliases: ["rewind"], - tags: [], - category: "Symbols", - description: "fast reverse button", - unicode_version: "6.0", - }, - { - emoji: "⏮️", - aliases: ["previous_track_button"], - tags: [], - category: "Symbols", - description: "last track button", - unicode_version: "6.0", - }, - { - emoji: "🔼", - aliases: ["arrow_up_small"], - tags: [], - category: "Symbols", - description: "upwards button", - unicode_version: "6.0", - }, - { - emoji: "⏫", - aliases: ["arrow_double_up"], - tags: [], - category: "Symbols", - description: "fast up button", - unicode_version: "6.0", - }, - { - emoji: "🔽", - aliases: ["arrow_down_small"], - tags: [], - category: "Symbols", - description: "downwards button", - unicode_version: "6.0", - }, - { - emoji: "⏬", - aliases: ["arrow_double_down"], - tags: [], - category: "Symbols", - description: "fast down button", - unicode_version: "6.0", - }, - { - emoji: "⏸️", - aliases: ["pause_button"], - tags: [], - category: "Symbols", - description: "pause button", - unicode_version: "7.0", - }, - { - emoji: "⏹️", - aliases: ["stop_button"], - tags: [], - category: "Symbols", - description: "stop button", - unicode_version: "7.0", - }, - { - emoji: "⏺️", - aliases: ["record_button"], - tags: [], - category: "Symbols", - description: "record button", - unicode_version: "7.0", - }, - { - emoji: "⏏️", - aliases: ["eject_button"], - tags: [], - category: "Symbols", - description: "eject button", - unicode_version: "11.0", - }, - { - emoji: "🎦", - aliases: ["cinema"], - tags: ["film", "movie"], - category: "Symbols", - description: "cinema", - unicode_version: "6.0", - }, - { - emoji: "🔅", - aliases: ["low_brightness"], - tags: [], - category: "Symbols", - description: "dim button", - unicode_version: "6.0", - }, - { - emoji: "🔆", - aliases: ["high_brightness"], - tags: [], - category: "Symbols", - description: "bright button", - unicode_version: "6.0", - }, - { - emoji: "📶", - aliases: ["signal_strength"], - tags: ["wifi"], - category: "Symbols", - description: "antenna bars", - unicode_version: "6.0", - }, - { - emoji: "📳", - aliases: ["vibration_mode"], - tags: [], - category: "Symbols", - description: "vibration mode", - unicode_version: "6.0", - }, - { - emoji: "📴", - aliases: ["mobile_phone_off"], - tags: ["mute", "off"], - category: "Symbols", - description: "mobile phone off", - unicode_version: "6.0", - }, - { - emoji: "♀️", - aliases: ["female_sign"], - tags: [], - category: "Symbols", - description: "female sign", - unicode_version: "11.0", - }, - { - emoji: "♂️", - aliases: ["male_sign"], - tags: [], - category: "Symbols", - description: "male sign", - unicode_version: "11.0", - }, - { - emoji: "⚧️", - aliases: ["transgender_symbol"], - tags: [], - category: "Symbols", - description: "transgender symbol", - unicode_version: "13.0", - }, - { - emoji: "✖️", - aliases: ["heavy_multiplication_x"], - tags: [], - category: "Symbols", - description: "multiply", - unicode_version: "", - }, - { - emoji: "➕", - aliases: ["heavy_plus_sign"], - tags: [], - category: "Symbols", - description: "plus", - unicode_version: "6.0", - }, - { - emoji: "➖", - aliases: ["heavy_minus_sign"], - tags: [], - category: "Symbols", - description: "minus", - unicode_version: "6.0", - }, - { - emoji: "➗", - aliases: ["heavy_division_sign"], - tags: [], - category: "Symbols", - description: "divide", - unicode_version: "6.0", - }, - { - emoji: "♾️", - aliases: ["infinity"], - tags: [], - category: "Symbols", - description: "infinity", - unicode_version: "11.0", - }, - { - emoji: "‼️", - aliases: ["bangbang"], - tags: [], - category: "Symbols", - description: "double exclamation mark", - unicode_version: "", - }, - { - emoji: "⁉️", - aliases: ["interrobang"], - tags: [], - category: "Symbols", - description: "exclamation question mark", - unicode_version: "3.0", - }, - { - emoji: "❓", - aliases: ["question"], - tags: ["confused"], - category: "Symbols", - description: "red question mark", - unicode_version: "6.0", - }, - { - emoji: "❔", - aliases: ["grey_question"], - tags: [], - category: "Symbols", - description: "white question mark", - unicode_version: "6.0", - }, - { - emoji: "❕", - aliases: ["grey_exclamation"], - tags: [], - category: "Symbols", - description: "white exclamation mark", - unicode_version: "6.0", - }, - { - emoji: "❗", - aliases: ["exclamation", "heavy_exclamation_mark"], - tags: ["bang"], - category: "Symbols", - description: "red exclamation mark", - unicode_version: "5.2", - }, - { - emoji: "〰️", - aliases: ["wavy_dash"], - tags: [], - category: "Symbols", - description: "wavy dash", - unicode_version: "", - }, - { - emoji: "💱", - aliases: ["currency_exchange"], - tags: [], - category: "Symbols", - description: "currency exchange", - unicode_version: "6.0", - }, - { - emoji: "💲", - aliases: ["heavy_dollar_sign"], - tags: [], - category: "Symbols", - description: "heavy dollar sign", - unicode_version: "6.0", - }, - { - emoji: "⚕️", - aliases: ["medical_symbol"], - tags: [], - category: "Symbols", - description: "medical symbol", - unicode_version: "11.0", - }, - { - emoji: "♻️", - aliases: ["recycle"], - tags: ["environment", "green"], - category: "Symbols", - description: "recycling symbol", - unicode_version: "3.2", - }, - { - emoji: "⚜️", - aliases: ["fleur_de_lis"], - tags: [], - category: "Symbols", - description: "fleur-de-lis", - unicode_version: "4.1", - }, - { - emoji: "🔱", - aliases: ["trident"], - tags: [], - category: "Symbols", - description: "trident emblem", - unicode_version: "6.0", - }, - { - emoji: "📛", - aliases: ["name_badge"], - tags: [], - category: "Symbols", - description: "name badge", - unicode_version: "6.0", - }, - { - emoji: "🔰", - aliases: ["beginner"], - tags: [], - category: "Symbols", - description: "Japanese symbol for beginner", - unicode_version: "6.0", - }, - { - emoji: "⭕", - aliases: ["o"], - tags: [], - category: "Symbols", - description: "hollow red circle", - unicode_version: "5.2", - }, - { - emoji: "✅", - aliases: ["white_check_mark"], - tags: [], - category: "Symbols", - description: "check mark button", - unicode_version: "6.0", - }, - { - emoji: "☑️", - aliases: ["ballot_box_with_check"], - tags: [], - category: "Symbols", - description: "check box with check", - unicode_version: "", - }, - { - emoji: "✔️", - aliases: ["heavy_check_mark"], - tags: [], - category: "Symbols", - description: "check mark", - unicode_version: "", - }, - { - emoji: "❌", - aliases: ["x"], - tags: [], - category: "Symbols", - description: "cross mark", - unicode_version: "6.0", - }, - { - emoji: "❎", - aliases: ["negative_squared_cross_mark"], - tags: [], - category: "Symbols", - description: "cross mark button", - unicode_version: "6.0", - }, - { - emoji: "➰", - aliases: ["curly_loop"], - tags: [], - category: "Symbols", - description: "curly loop", - unicode_version: "6.0", - }, - { - emoji: "➿", - aliases: ["loop"], - tags: [], - category: "Symbols", - description: "double curly loop", - unicode_version: "6.0", - }, - { - emoji: "〽️", - aliases: ["part_alternation_mark"], - tags: [], - category: "Symbols", - description: "part alternation mark", - unicode_version: "3.2", - }, - { - emoji: "✳️", - aliases: ["eight_spoked_asterisk"], - tags: [], - category: "Symbols", - description: "eight-spoked asterisk", - unicode_version: "", - }, - { - emoji: "✴️", - aliases: ["eight_pointed_black_star"], - tags: [], - category: "Symbols", - description: "eight-pointed star", - unicode_version: "", - }, - { - emoji: "❇️", - aliases: ["sparkle"], - tags: [], - category: "Symbols", - description: "sparkle", - unicode_version: "", - }, - { - emoji: "©️", - aliases: ["copyright"], - tags: [], - category: "Symbols", - description: "copyright", - unicode_version: "", - }, - { - emoji: "®️", - aliases: ["registered"], - tags: [], - category: "Symbols", - description: "registered", - unicode_version: "", - }, - { - emoji: "™️", - aliases: ["tm"], - tags: ["trademark"], - category: "Symbols", - description: "trade mark", - unicode_version: "", - }, - { - emoji: "#️⃣", - aliases: ["hash"], - tags: ["number"], - category: "Symbols", - description: "keycap: #", - unicode_version: "", - }, - { - emoji: "*️⃣", - aliases: ["asterisk"], - tags: [], - category: "Symbols", - description: "keycap: *", - unicode_version: "", - }, - { - emoji: "0️⃣", - aliases: ["zero"], - tags: [], - category: "Symbols", - description: "keycap: 0", - unicode_version: "", - }, - { - emoji: "1️⃣", - aliases: ["one"], - tags: [], - category: "Symbols", - description: "keycap: 1", - unicode_version: "", - }, - { - emoji: "2️⃣", - aliases: ["two"], - tags: [], - category: "Symbols", - description: "keycap: 2", - unicode_version: "", - }, - { - emoji: "3️⃣", - aliases: ["three"], - tags: [], - category: "Symbols", - description: "keycap: 3", - unicode_version: "", - }, - { - emoji: "4️⃣", - aliases: ["four"], - tags: [], - category: "Symbols", - description: "keycap: 4", - unicode_version: "", - }, - { - emoji: "5️⃣", - aliases: ["five"], - tags: [], - category: "Symbols", - description: "keycap: 5", - unicode_version: "", - }, - { - emoji: "6️⃣", - aliases: ["six"], - tags: [], - category: "Symbols", - description: "keycap: 6", - unicode_version: "", - }, - { - emoji: "7️⃣", - aliases: ["seven"], - tags: [], - category: "Symbols", - description: "keycap: 7", - unicode_version: "", - }, - { - emoji: "8️⃣", - aliases: ["eight"], - tags: [], - category: "Symbols", - description: "keycap: 8", - unicode_version: "", - }, - { - emoji: "9️⃣", - aliases: ["nine"], - tags: [], - category: "Symbols", - description: "keycap: 9", - unicode_version: "", - }, - { - emoji: "🔟", - aliases: ["keycap_ten"], - tags: [], - category: "Symbols", - description: "keycap: 10", - unicode_version: "6.0", - }, - { - emoji: "🔠", - aliases: ["capital_abcd"], - tags: ["letters"], - category: "Symbols", - description: "input latin uppercase", - unicode_version: "6.0", - }, - { - emoji: "🔡", - aliases: ["abcd"], - tags: [], - category: "Symbols", - description: "input latin lowercase", - unicode_version: "6.0", - }, - { - emoji: "🔢", - aliases: ["1234"], - tags: ["numbers"], - category: "Symbols", - description: "input numbers", - unicode_version: "6.0", - }, - { - emoji: "🔣", - aliases: ["symbols"], - tags: [], - category: "Symbols", - description: "input symbols", - unicode_version: "6.0", - }, - { - emoji: "🔤", - aliases: ["abc"], - tags: ["alphabet"], - category: "Symbols", - description: "input latin letters", - unicode_version: "6.0", - }, - { - emoji: "🅰️", - aliases: ["a"], - tags: [], - category: "Symbols", - description: "A button (blood type)", - unicode_version: "6.0", - }, - { - emoji: "🆎", - aliases: ["ab"], - tags: [], - category: "Symbols", - description: "AB button (blood type)", - unicode_version: "6.0", - }, - { - emoji: "🅱️", - aliases: ["b"], - tags: [], - category: "Symbols", - description: "B button (blood type)", - unicode_version: "6.0", - }, - { - emoji: "🆑", - aliases: ["cl"], - tags: [], - category: "Symbols", - description: "CL button", - unicode_version: "6.0", - }, - { - emoji: "🆒", - aliases: ["cool"], - tags: [], - category: "Symbols", - description: "COOL button", - unicode_version: "6.0", - }, - { - emoji: "🆓", - aliases: ["free"], - tags: [], - category: "Symbols", - description: "FREE button", - unicode_version: "6.0", - }, - { - emoji: "ℹ️", - aliases: ["information_source"], - tags: [], - category: "Symbols", - description: "information", - unicode_version: "3.0", - }, - { - emoji: "🆔", - aliases: ["id"], - tags: [], - category: "Symbols", - description: "ID button", - unicode_version: "6.0", - }, - { - emoji: "Ⓜ️", - aliases: ["m"], - tags: [], - category: "Symbols", - description: "circled M", - unicode_version: "", - }, - { - emoji: "🆕", - aliases: ["new"], - tags: ["fresh"], - category: "Symbols", - description: "NEW button", - unicode_version: "6.0", - }, - { - emoji: "🆖", - aliases: ["ng"], - tags: [], - category: "Symbols", - description: "NG button", - unicode_version: "6.0", - }, - { - emoji: "🅾️", - aliases: ["o2"], - tags: [], - category: "Symbols", - description: "O button (blood type)", - unicode_version: "6.0", - }, - { - emoji: "🆗", - aliases: ["ok"], - tags: ["yes"], - category: "Symbols", - description: "OK button", - unicode_version: "6.0", - }, - { - emoji: "🅿️", - aliases: ["parking"], - tags: [], - category: "Symbols", - description: "P button", - unicode_version: "5.2", - }, - { - emoji: "🆘", - aliases: ["sos"], - tags: ["help", "emergency"], - category: "Symbols", - description: "SOS button", - unicode_version: "6.0", - }, - { - emoji: "🆙", - aliases: ["up"], - tags: [], - category: "Symbols", - description: "UP! button", - unicode_version: "6.0", - }, - { - emoji: "🆚", - aliases: ["vs"], - tags: [], - category: "Symbols", - description: "VS button", - unicode_version: "6.0", - }, - { - emoji: "🈁", - aliases: ["koko"], - tags: [], - category: "Symbols", - description: "Japanese “here” button", - unicode_version: "6.0", - }, - { - emoji: "🈂️", - aliases: ["sa"], - tags: [], - category: "Symbols", - description: "Japanese “service charge” button", - unicode_version: "6.0", - }, - { - emoji: "🈷️", - aliases: ["u6708"], - tags: [], - category: "Symbols", - description: "Japanese “monthly amount” button", - unicode_version: "6.0", - }, - { - emoji: "🈶", - aliases: ["u6709"], - tags: [], - category: "Symbols", - description: "Japanese “not free of charge” button", - unicode_version: "6.0", - }, - { - emoji: "🈯", - aliases: ["u6307"], - tags: [], - category: "Symbols", - description: "Japanese “reserved” button", - unicode_version: "", - }, - { - emoji: "🉐", - aliases: ["ideograph_advantage"], - tags: [], - category: "Symbols", - description: "Japanese “bargain” button", - unicode_version: "6.0", - }, - { - emoji: "🈹", - aliases: ["u5272"], - tags: [], - category: "Symbols", - description: "Japanese “discount” button", - unicode_version: "6.0", - }, - { - emoji: "🈚", - aliases: ["u7121"], - tags: [], - category: "Symbols", - description: "Japanese “free of charge” button", - unicode_version: "", - }, - { - emoji: "🈲", - aliases: ["u7981"], - tags: [], - category: "Symbols", - description: "Japanese “prohibited” button", - unicode_version: "6.0", - }, - { - emoji: "🉑", - aliases: ["accept"], - tags: [], - category: "Symbols", - description: "Japanese “acceptable” button", - unicode_version: "6.0", - }, - { - emoji: "🈸", - aliases: ["u7533"], - tags: [], - category: "Symbols", - description: "Japanese “application” button", - unicode_version: "6.0", - }, - { - emoji: "🈴", - aliases: ["u5408"], - tags: [], - category: "Symbols", - description: "Japanese “passing grade” button", - unicode_version: "6.0", - }, - { - emoji: "🈳", - aliases: ["u7a7a"], - tags: [], - category: "Symbols", - description: "Japanese “vacancy” button", - unicode_version: "6.0", - }, - { - emoji: "㊗️", - aliases: ["congratulations"], - tags: [], - category: "Symbols", - description: "Japanese “congratulations” button", - unicode_version: "", - }, - { - emoji: "㊙️", - aliases: ["secret"], - tags: [], - category: "Symbols", - description: "Japanese “secret” button", - unicode_version: "", - }, - { - emoji: "🈺", - aliases: ["u55b6"], - tags: [], - category: "Symbols", - description: "Japanese “open for business” button", - unicode_version: "6.0", - }, - { - emoji: "🈵", - aliases: ["u6e80"], - tags: [], - category: "Symbols", - description: "Japanese “no vacancy” button", - unicode_version: "6.0", - }, - { - emoji: "🔴", - aliases: ["red_circle"], - tags: [], - category: "Symbols", - description: "red circle", - unicode_version: "6.0", - }, - { - emoji: "🟠", - aliases: ["orange_circle"], - tags: [], - category: "Symbols", - description: "orange circle", - unicode_version: "12.0", - }, - { - emoji: "🟡", - aliases: ["yellow_circle"], - tags: [], - category: "Symbols", - description: "yellow circle", - unicode_version: "12.0", - }, - { - emoji: "🟢", - aliases: ["green_circle"], - tags: [], - category: "Symbols", - description: "green circle", - unicode_version: "12.0", - }, - { - emoji: "🔵", - aliases: ["large_blue_circle"], - tags: [], - category: "Symbols", - description: "blue circle", - unicode_version: "6.0", - }, - { - emoji: "🟣", - aliases: ["purple_circle"], - tags: [], - category: "Symbols", - description: "purple circle", - unicode_version: "12.0", - }, - { - emoji: "🟤", - aliases: ["brown_circle"], - tags: [], - category: "Symbols", - description: "brown circle", - unicode_version: "12.0", - }, - { - emoji: "⚫", - aliases: ["black_circle"], - tags: [], - category: "Symbols", - description: "black circle", - unicode_version: "4.1", - }, - { - emoji: "⚪", - aliases: ["white_circle"], - tags: [], - category: "Symbols", - description: "white circle", - unicode_version: "4.1", - }, - { - emoji: "🟥", - aliases: ["red_square"], - tags: [], - category: "Symbols", - description: "red square", - unicode_version: "12.0", - }, - { - emoji: "🟧", - aliases: ["orange_square"], - tags: [], - category: "Symbols", - description: "orange square", - unicode_version: "12.0", - }, - { - emoji: "🟨", - aliases: ["yellow_square"], - tags: [], - category: "Symbols", - description: "yellow square", - unicode_version: "12.0", - }, - { - emoji: "🟩", - aliases: ["green_square"], - tags: [], - category: "Symbols", - description: "green square", - unicode_version: "12.0", - }, - { - emoji: "🟦", - aliases: ["blue_square"], - tags: [], - category: "Symbols", - description: "blue square", - unicode_version: "12.0", - }, - { - emoji: "🟪", - aliases: ["purple_square"], - tags: [], - category: "Symbols", - description: "purple square", - unicode_version: "12.0", - }, - { - emoji: "🟫", - aliases: ["brown_square"], - tags: [], - category: "Symbols", - description: "brown square", - unicode_version: "12.0", - }, - { - emoji: "⬛", - aliases: ["black_large_square"], - tags: [], - category: "Symbols", - description: "black large square", - unicode_version: "5.1", - }, - { - emoji: "⬜", - aliases: ["white_large_square"], - tags: [], - category: "Symbols", - description: "white large square", - unicode_version: "5.1", - }, - { - emoji: "◼️", - aliases: ["black_medium_square"], - tags: [], - category: "Symbols", - description: "black medium square", - unicode_version: "3.2", - }, - { - emoji: "◻️", - aliases: ["white_medium_square"], - tags: [], - category: "Symbols", - description: "white medium square", - unicode_version: "3.2", - }, - { - emoji: "◾", - aliases: ["black_medium_small_square"], - tags: [], - category: "Symbols", - description: "black medium-small square", - unicode_version: "3.2", - }, - { - emoji: "◽", - aliases: ["white_medium_small_square"], - tags: [], - category: "Symbols", - description: "white medium-small square", - unicode_version: "3.2", - }, - { - emoji: "▪️", - aliases: ["black_small_square"], - tags: [], - category: "Symbols", - description: "black small square", - unicode_version: "", - }, - { - emoji: "▫️", - aliases: ["white_small_square"], - tags: [], - category: "Symbols", - description: "white small square", - unicode_version: "", - }, - { - emoji: "🔶", - aliases: ["large_orange_diamond"], - tags: [], - category: "Symbols", - description: "large orange diamond", - unicode_version: "6.0", - }, - { - emoji: "🔷", - aliases: ["large_blue_diamond"], - tags: [], - category: "Symbols", - description: "large blue diamond", - unicode_version: "6.0", - }, - { - emoji: "🔸", - aliases: ["small_orange_diamond"], - tags: [], - category: "Symbols", - description: "small orange diamond", - unicode_version: "6.0", - }, - { - emoji: "🔹", - aliases: ["small_blue_diamond"], - tags: [], - category: "Symbols", - description: "small blue diamond", - unicode_version: "6.0", - }, - { - emoji: "🔺", - aliases: ["small_red_triangle"], - tags: [], - category: "Symbols", - description: "red triangle pointed up", - unicode_version: "6.0", - }, - { - emoji: "🔻", - aliases: ["small_red_triangle_down"], - tags: [], - category: "Symbols", - description: "red triangle pointed down", - unicode_version: "6.0", - }, - { - emoji: "💠", - aliases: ["diamond_shape_with_a_dot_inside"], - tags: [], - category: "Symbols", - description: "diamond with a dot", - unicode_version: "6.0", - }, - { - emoji: "🔘", - aliases: ["radio_button"], - tags: [], - category: "Symbols", - description: "radio button", - unicode_version: "6.0", - }, - { - emoji: "🔳", - aliases: ["white_square_button"], - tags: [], - category: "Symbols", - description: "white square button", - unicode_version: "6.0", - }, - { - emoji: "🔲", - aliases: ["black_square_button"], - tags: [], - category: "Symbols", - description: "black square button", - unicode_version: "6.0", - }, - { - emoji: "🏁", - aliases: ["checkered_flag"], - tags: ["milestone", "finish"], - category: "Flags", - description: "chequered flag", - unicode_version: "6.0", - }, - { - emoji: "🚩", - aliases: ["triangular_flag_on_post"], - tags: [], - category: "Flags", - description: "triangular flag", - unicode_version: "6.0", - }, - { - emoji: "🎌", - aliases: ["crossed_flags"], - tags: [], - category: "Flags", - description: "crossed flags", - unicode_version: "6.0", - }, - { - emoji: "🏴", - aliases: ["black_flag"], - tags: [], - category: "Flags", - description: "black flag", - unicode_version: "7.0", - }, - { - emoji: "🏳️", - aliases: ["white_flag"], - tags: [], - category: "Flags", - description: "white flag", - unicode_version: "7.0", - }, - { - emoji: "🏳️‍🌈", - aliases: ["rainbow_flag"], - tags: ["pride"], - category: "Flags", - description: "rainbow flag", - unicode_version: "6.0", - }, - { - emoji: "🏳️‍⚧️", - aliases: ["transgender_flag"], - tags: [], - category: "Flags", - description: "transgender flag", - unicode_version: "13.0", - }, - { - emoji: "🏴‍☠️", - aliases: ["pirate_flag"], - tags: [], - category: "Flags", - description: "pirate flag", - unicode_version: "11.0", - }, - { - emoji: "🇦🇨", - aliases: ["ascension_island"], - tags: [], - category: "Flags", - description: "flag: Ascension Island", - unicode_version: "11.0", - }, - { - emoji: "🇦🇩", - aliases: ["andorra"], - tags: [], - category: "Flags", - description: "flag: Andorra", - unicode_version: "6.0", - }, - { - emoji: "🇦🇪", - aliases: ["united_arab_emirates"], - tags: [], - category: "Flags", - description: "flag: United Arab Emirates", - unicode_version: "6.0", - }, - { - emoji: "🇦🇫", - aliases: ["afghanistan"], - tags: [], - category: "Flags", - description: "flag: Afghanistan", - unicode_version: "6.0", - }, - { - emoji: "🇦🇬", - aliases: ["antigua_barbuda"], - tags: [], - category: "Flags", - description: "flag: Antigua & Barbuda", - unicode_version: "6.0", - }, - { - emoji: "🇦🇮", - aliases: ["anguilla"], - tags: [], - category: "Flags", - description: "flag: Anguilla", - unicode_version: "6.0", - }, - { - emoji: "🇦🇱", - aliases: ["albania"], - tags: [], - category: "Flags", - description: "flag: Albania", - unicode_version: "6.0", - }, - { - emoji: "🇦🇲", - aliases: ["armenia"], - tags: [], - category: "Flags", - description: "flag: Armenia", - unicode_version: "6.0", - }, - { - emoji: "🇦🇴", - aliases: ["angola"], - tags: [], - category: "Flags", - description: "flag: Angola", - unicode_version: "6.0", - }, - { - emoji: "🇦🇶", - aliases: ["antarctica"], - tags: [], - category: "Flags", - description: "flag: Antarctica", - unicode_version: "6.0", - }, - { - emoji: "🇦🇷", - aliases: ["argentina"], - tags: [], - category: "Flags", - description: "flag: Argentina", - unicode_version: "6.0", - }, - { - emoji: "🇦🇸", - aliases: ["american_samoa"], - tags: [], - category: "Flags", - description: "flag: American Samoa", - unicode_version: "6.0", - }, - { - emoji: "🇦🇹", - aliases: ["austria"], - tags: [], - category: "Flags", - description: "flag: Austria", - unicode_version: "6.0", - }, - { - emoji: "🇦🇺", - aliases: ["australia"], - tags: [], - category: "Flags", - description: "flag: Australia", - unicode_version: "6.0", - }, - { - emoji: "🇦🇼", - aliases: ["aruba"], - tags: [], - category: "Flags", - description: "flag: Aruba", - unicode_version: "6.0", - }, - { - emoji: "🇦🇽", - aliases: ["aland_islands"], - tags: [], - category: "Flags", - description: "flag: Åland Islands", - unicode_version: "6.0", - }, - { - emoji: "🇦🇿", - aliases: ["azerbaijan"], - tags: [], - category: "Flags", - description: "flag: Azerbaijan", - unicode_version: "6.0", - }, - { - emoji: "🇧🇦", - aliases: ["bosnia_herzegovina"], - tags: [], - category: "Flags", - description: "flag: Bosnia & Herzegovina", - unicode_version: "6.0", - }, - { - emoji: "🇧🇧", - aliases: ["barbados"], - tags: [], - category: "Flags", - description: "flag: Barbados", - unicode_version: "6.0", - }, - { - emoji: "🇧🇩", - aliases: ["bangladesh"], - tags: [], - category: "Flags", - description: "flag: Bangladesh", - unicode_version: "6.0", - }, - { - emoji: "🇧🇪", - aliases: ["belgium"], - tags: [], - category: "Flags", - description: "flag: Belgium", - unicode_version: "6.0", - }, - { - emoji: "🇧🇫", - aliases: ["burkina_faso"], - tags: [], - category: "Flags", - description: "flag: Burkina Faso", - unicode_version: "6.0", - }, - { - emoji: "🇧🇬", - aliases: ["bulgaria"], - tags: [], - category: "Flags", - description: "flag: Bulgaria", - unicode_version: "6.0", - }, - { - emoji: "🇧🇭", - aliases: ["bahrain"], - tags: [], - category: "Flags", - description: "flag: Bahrain", - unicode_version: "6.0", - }, - { - emoji: "🇧🇮", - aliases: ["burundi"], - tags: [], - category: "Flags", - description: "flag: Burundi", - unicode_version: "6.0", - }, - { - emoji: "🇧🇯", - aliases: ["benin"], - tags: [], - category: "Flags", - description: "flag: Benin", - unicode_version: "6.0", - }, - { - emoji: "🇧🇱", - aliases: ["st_barthelemy"], - tags: [], - category: "Flags", - description: "flag: St. Barthélemy", - unicode_version: "6.0", - }, - { - emoji: "🇧🇲", - aliases: ["bermuda"], - tags: [], - category: "Flags", - description: "flag: Bermuda", - unicode_version: "6.0", - }, - { - emoji: "🇧🇳", - aliases: ["brunei"], - tags: [], - category: "Flags", - description: "flag: Brunei", - unicode_version: "6.0", - }, - { - emoji: "🇧🇴", - aliases: ["bolivia"], - tags: [], - category: "Flags", - description: "flag: Bolivia", - unicode_version: "6.0", - }, - { - emoji: "🇧🇶", - aliases: ["caribbean_netherlands"], - tags: [], - category: "Flags", - description: "flag: Caribbean Netherlands", - unicode_version: "6.0", - }, - { - emoji: "🇧🇷", - aliases: ["brazil"], - tags: [], - category: "Flags", - description: "flag: Brazil", - unicode_version: "6.0", - }, - { - emoji: "🇧🇸", - aliases: ["bahamas"], - tags: [], - category: "Flags", - description: "flag: Bahamas", - unicode_version: "6.0", - }, - { - emoji: "🇧🇹", - aliases: ["bhutan"], - tags: [], - category: "Flags", - description: "flag: Bhutan", - unicode_version: "6.0", - }, - { - emoji: "🇧🇻", - aliases: ["bouvet_island"], - tags: [], - category: "Flags", - description: "flag: Bouvet Island", - unicode_version: "11.0", - }, - { - emoji: "🇧🇼", - aliases: ["botswana"], - tags: [], - category: "Flags", - description: "flag: Botswana", - unicode_version: "6.0", - }, - { - emoji: "🇧🇾", - aliases: ["belarus"], - tags: [], - category: "Flags", - description: "flag: Belarus", - unicode_version: "6.0", - }, - { - emoji: "🇧🇿", - aliases: ["belize"], - tags: [], - category: "Flags", - description: "flag: Belize", - unicode_version: "6.0", - }, - { - emoji: "🇨🇦", - aliases: ["canada"], - tags: [], - category: "Flags", - description: "flag: Canada", - unicode_version: "6.0", - }, - { - emoji: "🇨🇨", - aliases: ["cocos_islands"], - tags: ["keeling"], - category: "Flags", - description: "flag: Cocos (Keeling) Islands", - unicode_version: "6.0", - }, - { - emoji: "🇨🇩", - aliases: ["congo_kinshasa"], - tags: [], - category: "Flags", - description: "flag: Congo - Kinshasa", - unicode_version: "6.0", - }, - { - emoji: "🇨🇫", - aliases: ["central_african_republic"], - tags: [], - category: "Flags", - description: "flag: Central African Republic", - unicode_version: "6.0", - }, - { - emoji: "🇨🇬", - aliases: ["congo_brazzaville"], - tags: [], - category: "Flags", - description: "flag: Congo - Brazzaville", - unicode_version: "6.0", - }, - { - emoji: "🇨🇭", - aliases: ["switzerland"], - tags: [], - category: "Flags", - description: "flag: Switzerland", - unicode_version: "6.0", - }, - { - emoji: "🇨🇮", - aliases: ["cote_divoire"], - tags: ["ivory"], - category: "Flags", - description: "flag: Côte d’Ivoire", - unicode_version: "6.0", - }, - { - emoji: "🇨🇰", - aliases: ["cook_islands"], - tags: [], - category: "Flags", - description: "flag: Cook Islands", - unicode_version: "6.0", - }, - { - emoji: "🇨🇱", - aliases: ["chile"], - tags: [], - category: "Flags", - description: "flag: Chile", - unicode_version: "6.0", - }, - { - emoji: "🇨🇲", - aliases: ["cameroon"], - tags: [], - category: "Flags", - description: "flag: Cameroon", - unicode_version: "6.0", - }, - { - emoji: "🇨🇳", - aliases: ["cn"], - tags: ["china"], - category: "Flags", - description: "flag: China", - unicode_version: "6.0", - }, - { - emoji: "🇨🇴", - aliases: ["colombia"], - tags: [], - category: "Flags", - description: "flag: Colombia", - unicode_version: "6.0", - }, - { - emoji: "🇨🇵", - aliases: ["clipperton_island"], - tags: [], - category: "Flags", - description: "flag: Clipperton Island", - unicode_version: "11.0", - }, - { - emoji: "🇨🇷", - aliases: ["costa_rica"], - tags: [], - category: "Flags", - description: "flag: Costa Rica", - unicode_version: "6.0", - }, - { - emoji: "🇨🇺", - aliases: ["cuba"], - tags: [], - category: "Flags", - description: "flag: Cuba", - unicode_version: "6.0", - }, - { - emoji: "🇨🇻", - aliases: ["cape_verde"], - tags: [], - category: "Flags", - description: "flag: Cape Verde", - unicode_version: "6.0", - }, - { - emoji: "🇨🇼", - aliases: ["curacao"], - tags: [], - category: "Flags", - description: "flag: Curaçao", - unicode_version: "6.0", - }, - { - emoji: "🇨🇽", - aliases: ["christmas_island"], - tags: [], - category: "Flags", - description: "flag: Christmas Island", - unicode_version: "6.0", - }, - { - emoji: "🇨🇾", - aliases: ["cyprus"], - tags: [], - category: "Flags", - description: "flag: Cyprus", - unicode_version: "6.0", - }, - { - emoji: "🇨🇿", - aliases: ["czech_republic"], - tags: [], - category: "Flags", - description: "flag: Czechia", - unicode_version: "6.0", - }, - { - emoji: "🇩🇪", - aliases: ["de"], - tags: ["flag", "germany"], - category: "Flags", - description: "flag: Germany", - unicode_version: "6.0", - }, - { - emoji: "🇩🇬", - aliases: ["diego_garcia"], - tags: [], - category: "Flags", - description: "flag: Diego Garcia", - unicode_version: "11.0", - }, - { - emoji: "🇩🇯", - aliases: ["djibouti"], - tags: [], - category: "Flags", - description: "flag: Djibouti", - unicode_version: "6.0", - }, - { - emoji: "🇩🇰", - aliases: ["denmark"], - tags: [], - category: "Flags", - description: "flag: Denmark", - unicode_version: "6.0", - }, - { - emoji: "🇩🇲", - aliases: ["dominica"], - tags: [], - category: "Flags", - description: "flag: Dominica", - unicode_version: "6.0", - }, - { - emoji: "🇩🇴", - aliases: ["dominican_republic"], - tags: [], - category: "Flags", - description: "flag: Dominican Republic", - unicode_version: "6.0", - }, - { - emoji: "🇩🇿", - aliases: ["algeria"], - tags: [], - category: "Flags", - description: "flag: Algeria", - unicode_version: "6.0", - }, - { - emoji: "🇪🇦", - aliases: ["ceuta_melilla"], - tags: [], - category: "Flags", - description: "flag: Ceuta & Melilla", - unicode_version: "11.0", - }, - { - emoji: "🇪🇨", - aliases: ["ecuador"], - tags: [], - category: "Flags", - description: "flag: Ecuador", - unicode_version: "6.0", - }, - { - emoji: "🇪🇪", - aliases: ["estonia"], - tags: [], - category: "Flags", - description: "flag: Estonia", - unicode_version: "6.0", - }, - { - emoji: "🇪🇬", - aliases: ["egypt"], - tags: [], - category: "Flags", - description: "flag: Egypt", - unicode_version: "6.0", - }, - { - emoji: "🇪🇭", - aliases: ["western_sahara"], - tags: [], - category: "Flags", - description: "flag: Western Sahara", - unicode_version: "6.0", - }, - { - emoji: "🇪🇷", - aliases: ["eritrea"], - tags: [], - category: "Flags", - description: "flag: Eritrea", - unicode_version: "6.0", - }, - { - emoji: "🇪🇸", - aliases: ["es"], - tags: ["spain"], - category: "Flags", - description: "flag: Spain", - unicode_version: "6.0", - }, - { - emoji: "🇪🇹", - aliases: ["ethiopia"], - tags: [], - category: "Flags", - description: "flag: Ethiopia", - unicode_version: "6.0", - }, - { - emoji: "🇪🇺", - aliases: ["eu", "european_union"], - tags: [], - category: "Flags", - description: "flag: European Union", - unicode_version: "6.0", - }, - { - emoji: "🇫🇮", - aliases: ["finland"], - tags: [], - category: "Flags", - description: "flag: Finland", - unicode_version: "6.0", - }, - { - emoji: "🇫🇯", - aliases: ["fiji"], - tags: [], - category: "Flags", - description: "flag: Fiji", - unicode_version: "6.0", - }, - { - emoji: "🇫🇰", - aliases: ["falkland_islands"], - tags: [], - category: "Flags", - description: "flag: Falkland Islands", - unicode_version: "6.0", - }, - { - emoji: "🇫🇲", - aliases: ["micronesia"], - tags: [], - category: "Flags", - description: "flag: Micronesia", - unicode_version: "6.0", - }, - { - emoji: "🇫🇴", - aliases: ["faroe_islands"], - tags: [], - category: "Flags", - description: "flag: Faroe Islands", - unicode_version: "6.0", - }, - { - emoji: "🇫🇷", - aliases: ["fr"], - tags: ["france", "french"], - category: "Flags", - description: "flag: France", - unicode_version: "6.0", - }, - { - emoji: "🇬🇦", - aliases: ["gabon"], - tags: [], - category: "Flags", - description: "flag: Gabon", - unicode_version: "6.0", - }, - { - emoji: "🇬🇧", - aliases: ["gb", "uk"], - tags: ["flag", "british"], - category: "Flags", - description: "flag: United Kingdom", - unicode_version: "6.0", - }, - { - emoji: "🇬🇩", - aliases: ["grenada"], - tags: [], - category: "Flags", - description: "flag: Grenada", - unicode_version: "6.0", - }, - { - emoji: "🇬🇪", - aliases: ["georgia"], - tags: [], - category: "Flags", - description: "flag: Georgia", - unicode_version: "6.0", - }, - { - emoji: "🇬🇫", - aliases: ["french_guiana"], - tags: [], - category: "Flags", - description: "flag: French Guiana", - unicode_version: "6.0", - }, - { - emoji: "🇬🇬", - aliases: ["guernsey"], - tags: [], - category: "Flags", - description: "flag: Guernsey", - unicode_version: "6.0", - }, - { - emoji: "🇬🇭", - aliases: ["ghana"], - tags: [], - category: "Flags", - description: "flag: Ghana", - unicode_version: "6.0", - }, - { - emoji: "🇬🇮", - aliases: ["gibraltar"], - tags: [], - category: "Flags", - description: "flag: Gibraltar", - unicode_version: "6.0", - }, - { - emoji: "🇬🇱", - aliases: ["greenland"], - tags: [], - category: "Flags", - description: "flag: Greenland", - unicode_version: "6.0", - }, - { - emoji: "🇬🇲", - aliases: ["gambia"], - tags: [], - category: "Flags", - description: "flag: Gambia", - unicode_version: "6.0", - }, - { - emoji: "🇬🇳", - aliases: ["guinea"], - tags: [], - category: "Flags", - description: "flag: Guinea", - unicode_version: "6.0", - }, - { - emoji: "🇬🇵", - aliases: ["guadeloupe"], - tags: [], - category: "Flags", - description: "flag: Guadeloupe", - unicode_version: "6.0", - }, - { - emoji: "🇬🇶", - aliases: ["equatorial_guinea"], - tags: [], - category: "Flags", - description: "flag: Equatorial Guinea", - unicode_version: "6.0", - }, - { - emoji: "🇬🇷", - aliases: ["greece"], - tags: [], - category: "Flags", - description: "flag: Greece", - unicode_version: "6.0", - }, - { - emoji: "🇬🇸", - aliases: ["south_georgia_south_sandwich_islands"], - tags: [], - category: "Flags", - description: "flag: South Georgia & South Sandwich Islands", - unicode_version: "6.0", - }, - { - emoji: "🇬🇹", - aliases: ["guatemala"], - tags: [], - category: "Flags", - description: "flag: Guatemala", - unicode_version: "6.0", - }, - { - emoji: "🇬🇺", - aliases: ["guam"], - tags: [], - category: "Flags", - description: "flag: Guam", - unicode_version: "6.0", - }, - { - emoji: "🇬🇼", - aliases: ["guinea_bissau"], - tags: [], - category: "Flags", - description: "flag: Guinea-Bissau", - unicode_version: "6.0", - }, - { - emoji: "🇬🇾", - aliases: ["guyana"], - tags: [], - category: "Flags", - description: "flag: Guyana", - unicode_version: "6.0", - }, - { - emoji: "🇭🇰", - aliases: ["hong_kong"], - tags: [], - category: "Flags", - description: "flag: Hong Kong SAR China", - unicode_version: "6.0", - }, - { - emoji: "🇭🇲", - aliases: ["heard_mcdonald_islands"], - tags: [], - category: "Flags", - description: "flag: Heard & McDonald Islands", - unicode_version: "11.0", - }, - { - emoji: "🇭🇳", - aliases: ["honduras"], - tags: [], - category: "Flags", - description: "flag: Honduras", - unicode_version: "6.0", - }, - { - emoji: "🇭🇷", - aliases: ["croatia"], - tags: [], - category: "Flags", - description: "flag: Croatia", - unicode_version: "6.0", - }, - { - emoji: "🇭🇹", - aliases: ["haiti"], - tags: [], - category: "Flags", - description: "flag: Haiti", - unicode_version: "6.0", - }, - { - emoji: "🇭🇺", - aliases: ["hungary"], - tags: [], - category: "Flags", - description: "flag: Hungary", - unicode_version: "6.0", - }, - { - emoji: "🇮🇨", - aliases: ["canary_islands"], - tags: [], - category: "Flags", - description: "flag: Canary Islands", - unicode_version: "6.0", - }, - { - emoji: "🇮🇩", - aliases: ["indonesia"], - tags: [], - category: "Flags", - description: "flag: Indonesia", - unicode_version: "6.0", - }, - { - emoji: "🇮🇪", - aliases: ["ireland"], - tags: [], - category: "Flags", - description: "flag: Ireland", - unicode_version: "6.0", - }, - { - emoji: "🇮🇱", - aliases: ["israel"], - tags: [], - category: "Flags", - description: "flag: Israel", - unicode_version: "6.0", - }, - { - emoji: "🇮🇲", - aliases: ["isle_of_man"], - tags: [], - category: "Flags", - description: "flag: Isle of Man", - unicode_version: "6.0", - }, - { - emoji: "🇮🇳", - aliases: ["india"], - tags: [], - category: "Flags", - description: "flag: India", - unicode_version: "6.0", - }, - { - emoji: "🇮🇴", - aliases: ["british_indian_ocean_territory"], - tags: [], - category: "Flags", - description: "flag: British Indian Ocean Territory", - unicode_version: "6.0", - }, - { - emoji: "🇮🇶", - aliases: ["iraq"], - tags: [], - category: "Flags", - description: "flag: Iraq", - unicode_version: "6.0", - }, - { - emoji: "🇮🇷", - aliases: ["iran"], - tags: [], - category: "Flags", - description: "flag: Iran", - unicode_version: "6.0", - }, - { - emoji: "🇮🇸", - aliases: ["iceland"], - tags: [], - category: "Flags", - description: "flag: Iceland", - unicode_version: "6.0", - }, - { - emoji: "🇮🇹", - aliases: ["it"], - tags: ["italy"], - category: "Flags", - description: "flag: Italy", - unicode_version: "6.0", - }, - { - emoji: "🇯🇪", - aliases: ["jersey"], - tags: [], - category: "Flags", - description: "flag: Jersey", - unicode_version: "6.0", - }, - { - emoji: "🇯🇲", - aliases: ["jamaica"], - tags: [], - category: "Flags", - description: "flag: Jamaica", - unicode_version: "6.0", - }, - { - emoji: "🇯🇴", - aliases: ["jordan"], - tags: [], - category: "Flags", - description: "flag: Jordan", - unicode_version: "6.0", - }, - { - emoji: "🇯🇵", - aliases: ["jp"], - tags: ["japan"], - category: "Flags", - description: "flag: Japan", - unicode_version: "6.0", - }, - { - emoji: "🇰🇪", - aliases: ["kenya"], - tags: [], - category: "Flags", - description: "flag: Kenya", - unicode_version: "6.0", - }, - { - emoji: "🇰🇬", - aliases: ["kyrgyzstan"], - tags: [], - category: "Flags", - description: "flag: Kyrgyzstan", - unicode_version: "6.0", - }, - { - emoji: "🇰🇭", - aliases: ["cambodia"], - tags: [], - category: "Flags", - description: "flag: Cambodia", - unicode_version: "6.0", - }, - { - emoji: "🇰🇮", - aliases: ["kiribati"], - tags: [], - category: "Flags", - description: "flag: Kiribati", - unicode_version: "6.0", - }, - { - emoji: "🇰🇲", - aliases: ["comoros"], - tags: [], - category: "Flags", - description: "flag: Comoros", - unicode_version: "6.0", - }, - { - emoji: "🇰🇳", - aliases: ["st_kitts_nevis"], - tags: [], - category: "Flags", - description: "flag: St. Kitts & Nevis", - unicode_version: "6.0", - }, - { - emoji: "🇰🇵", - aliases: ["north_korea"], - tags: [], - category: "Flags", - description: "flag: North Korea", - unicode_version: "6.0", - }, - { - emoji: "🇰🇷", - aliases: ["kr"], - tags: ["korea"], - category: "Flags", - description: "flag: South Korea", - unicode_version: "6.0", - }, - { - emoji: "🇰🇼", - aliases: ["kuwait"], - tags: [], - category: "Flags", - description: "flag: Kuwait", - unicode_version: "6.0", - }, - { - emoji: "🇰🇾", - aliases: ["cayman_islands"], - tags: [], - category: "Flags", - description: "flag: Cayman Islands", - unicode_version: "6.0", - }, - { - emoji: "🇰🇿", - aliases: ["kazakhstan"], - tags: [], - category: "Flags", - description: "flag: Kazakhstan", - unicode_version: "6.0", - }, - { - emoji: "🇱🇦", - aliases: ["laos"], - tags: [], - category: "Flags", - description: "flag: Laos", - unicode_version: "6.0", - }, - { - emoji: "🇱🇧", - aliases: ["lebanon"], - tags: [], - category: "Flags", - description: "flag: Lebanon", - unicode_version: "6.0", - }, - { - emoji: "🇱🇨", - aliases: ["st_lucia"], - tags: [], - category: "Flags", - description: "flag: St. Lucia", - unicode_version: "6.0", - }, - { - emoji: "🇱🇮", - aliases: ["liechtenstein"], - tags: [], - category: "Flags", - description: "flag: Liechtenstein", - unicode_version: "6.0", - }, - { - emoji: "🇱🇰", - aliases: ["sri_lanka"], - tags: [], - category: "Flags", - description: "flag: Sri Lanka", - unicode_version: "6.0", - }, - { - emoji: "🇱🇷", - aliases: ["liberia"], - tags: [], - category: "Flags", - description: "flag: Liberia", - unicode_version: "6.0", - }, - { - emoji: "🇱🇸", - aliases: ["lesotho"], - tags: [], - category: "Flags", - description: "flag: Lesotho", - unicode_version: "6.0", - }, - { - emoji: "🇱🇹", - aliases: ["lithuania"], - tags: [], - category: "Flags", - description: "flag: Lithuania", - unicode_version: "6.0", - }, - { - emoji: "🇱🇺", - aliases: ["luxembourg"], - tags: [], - category: "Flags", - description: "flag: Luxembourg", - unicode_version: "6.0", - }, - { - emoji: "🇱🇻", - aliases: ["latvia"], - tags: [], - category: "Flags", - description: "flag: Latvia", - unicode_version: "6.0", - }, - { - emoji: "🇱🇾", - aliases: ["libya"], - tags: [], - category: "Flags", - description: "flag: Libya", - unicode_version: "6.0", - }, - { - emoji: "🇲🇦", - aliases: ["morocco"], - tags: [], - category: "Flags", - description: "flag: Morocco", - unicode_version: "6.0", - }, - { - emoji: "🇲🇨", - aliases: ["monaco"], - tags: [], - category: "Flags", - description: "flag: Monaco", - unicode_version: "6.0", - }, - { - emoji: "🇲🇩", - aliases: ["moldova"], - tags: [], - category: "Flags", - description: "flag: Moldova", - unicode_version: "6.0", - }, - { - emoji: "🇲🇪", - aliases: ["montenegro"], - tags: [], - category: "Flags", - description: "flag: Montenegro", - unicode_version: "6.0", - }, - { - emoji: "🇲🇫", - aliases: ["st_martin"], - tags: [], - category: "Flags", - description: "flag: St. Martin", - unicode_version: "11.0", - }, - { - emoji: "🇲🇬", - aliases: ["madagascar"], - tags: [], - category: "Flags", - description: "flag: Madagascar", - unicode_version: "6.0", - }, - { - emoji: "🇲🇭", - aliases: ["marshall_islands"], - tags: [], - category: "Flags", - description: "flag: Marshall Islands", - unicode_version: "6.0", - }, - { - emoji: "🇲🇰", - aliases: ["macedonia"], - tags: [], - category: "Flags", - description: "flag: North Macedonia", - unicode_version: "6.0", - }, - { - emoji: "🇲🇱", - aliases: ["mali"], - tags: [], - category: "Flags", - description: "flag: Mali", - unicode_version: "6.0", - }, - { - emoji: "🇲🇲", - aliases: ["myanmar"], - tags: ["burma"], - category: "Flags", - description: "flag: Myanmar (Burma)", - unicode_version: "6.0", - }, - { - emoji: "🇲🇳", - aliases: ["mongolia"], - tags: [], - category: "Flags", - description: "flag: Mongolia", - unicode_version: "6.0", - }, - { - emoji: "🇲🇴", - aliases: ["macau"], - tags: [], - category: "Flags", - description: "flag: Macao SAR China", - unicode_version: "6.0", - }, - { - emoji: "🇲🇵", - aliases: ["northern_mariana_islands"], - tags: [], - category: "Flags", - description: "flag: Northern Mariana Islands", - unicode_version: "6.0", - }, - { - emoji: "🇲🇶", - aliases: ["martinique"], - tags: [], - category: "Flags", - description: "flag: Martinique", - unicode_version: "6.0", - }, - { - emoji: "🇲🇷", - aliases: ["mauritania"], - tags: [], - category: "Flags", - description: "flag: Mauritania", - unicode_version: "6.0", - }, - { - emoji: "🇲🇸", - aliases: ["montserrat"], - tags: [], - category: "Flags", - description: "flag: Montserrat", - unicode_version: "6.0", - }, - { - emoji: "🇲🇹", - aliases: ["malta"], - tags: [], - category: "Flags", - description: "flag: Malta", - unicode_version: "6.0", - }, - { - emoji: "🇲🇺", - aliases: ["mauritius"], - tags: [], - category: "Flags", - description: "flag: Mauritius", - unicode_version: "6.0", - }, - { - emoji: "🇲🇻", - aliases: ["maldives"], - tags: [], - category: "Flags", - description: "flag: Maldives", - unicode_version: "6.0", - }, - { - emoji: "🇲🇼", - aliases: ["malawi"], - tags: [], - category: "Flags", - description: "flag: Malawi", - unicode_version: "6.0", - }, - { - emoji: "🇲🇽", - aliases: ["mexico"], - tags: [], - category: "Flags", - description: "flag: Mexico", - unicode_version: "6.0", - }, - { - emoji: "🇲🇾", - aliases: ["malaysia"], - tags: [], - category: "Flags", - description: "flag: Malaysia", - unicode_version: "6.0", - }, - { - emoji: "🇲🇿", - aliases: ["mozambique"], - tags: [], - category: "Flags", - description: "flag: Mozambique", - unicode_version: "6.0", - }, - { - emoji: "🇳🇦", - aliases: ["namibia"], - tags: [], - category: "Flags", - description: "flag: Namibia", - unicode_version: "6.0", - }, - { - emoji: "🇳🇨", - aliases: ["new_caledonia"], - tags: [], - category: "Flags", - description: "flag: New Caledonia", - unicode_version: "6.0", - }, - { - emoji: "🇳🇪", - aliases: ["niger"], - tags: [], - category: "Flags", - description: "flag: Niger", - unicode_version: "6.0", - }, - { - emoji: "🇳🇫", - aliases: ["norfolk_island"], - tags: [], - category: "Flags", - description: "flag: Norfolk Island", - unicode_version: "6.0", - }, - { - emoji: "🇳🇬", - aliases: ["nigeria"], - tags: [], - category: "Flags", - description: "flag: Nigeria", - unicode_version: "6.0", - }, - { - emoji: "🇳🇮", - aliases: ["nicaragua"], - tags: [], - category: "Flags", - description: "flag: Nicaragua", - unicode_version: "6.0", - }, - { - emoji: "🇳🇱", - aliases: ["netherlands"], - tags: [], - category: "Flags", - description: "flag: Netherlands", - unicode_version: "6.0", - }, - { - emoji: "🇳🇴", - aliases: ["norway"], - tags: [], - category: "Flags", - description: "flag: Norway", - unicode_version: "6.0", - }, - { - emoji: "🇳🇵", - aliases: ["nepal"], - tags: [], - category: "Flags", - description: "flag: Nepal", - unicode_version: "6.0", - }, - { - emoji: "🇳🇷", - aliases: ["nauru"], - tags: [], - category: "Flags", - description: "flag: Nauru", - unicode_version: "6.0", - }, - { - emoji: "🇳🇺", - aliases: ["niue"], - tags: [], - category: "Flags", - description: "flag: Niue", - unicode_version: "6.0", - }, - { - emoji: "🇳🇿", - aliases: ["new_zealand"], - tags: [], - category: "Flags", - description: "flag: New Zealand", - unicode_version: "6.0", - }, - { - emoji: "🇴🇲", - aliases: ["oman"], - tags: [], - category: "Flags", - description: "flag: Oman", - unicode_version: "6.0", - }, - { - emoji: "🇵🇦", - aliases: ["panama"], - tags: [], - category: "Flags", - description: "flag: Panama", - unicode_version: "6.0", - }, - { - emoji: "🇵🇪", - aliases: ["peru"], - tags: [], - category: "Flags", - description: "flag: Peru", - unicode_version: "6.0", - }, - { - emoji: "🇵🇫", - aliases: ["french_polynesia"], - tags: [], - category: "Flags", - description: "flag: French Polynesia", - unicode_version: "6.0", - }, - { - emoji: "🇵🇬", - aliases: ["papua_new_guinea"], - tags: [], - category: "Flags", - description: "flag: Papua New Guinea", - unicode_version: "6.0", - }, - { - emoji: "🇵🇭", - aliases: ["philippines"], - tags: [], - category: "Flags", - description: "flag: Philippines", - unicode_version: "6.0", - }, - { - emoji: "🇵🇰", - aliases: ["pakistan"], - tags: [], - category: "Flags", - description: "flag: Pakistan", - unicode_version: "6.0", - }, - { - emoji: "🇵🇱", - aliases: ["poland"], - tags: [], - category: "Flags", - description: "flag: Poland", - unicode_version: "6.0", - }, - { - emoji: "🇵🇲", - aliases: ["st_pierre_miquelon"], - tags: [], - category: "Flags", - description: "flag: St. Pierre & Miquelon", - unicode_version: "6.0", - }, - { - emoji: "🇵🇳", - aliases: ["pitcairn_islands"], - tags: [], - category: "Flags", - description: "flag: Pitcairn Islands", - unicode_version: "6.0", - }, - { - emoji: "🇵🇷", - aliases: ["puerto_rico"], - tags: [], - category: "Flags", - description: "flag: Puerto Rico", - unicode_version: "6.0", - }, - { - emoji: "🇵🇸", - aliases: ["palestinian_territories"], - tags: [], - category: "Flags", - description: "flag: Palestinian Territories", - unicode_version: "6.0", - }, - { - emoji: "🇵🇹", - aliases: ["portugal"], - tags: [], - category: "Flags", - description: "flag: Portugal", - unicode_version: "6.0", - }, - { - emoji: "🇵🇼", - aliases: ["palau"], - tags: [], - category: "Flags", - description: "flag: Palau", - unicode_version: "6.0", - }, - { - emoji: "🇵🇾", - aliases: ["paraguay"], - tags: [], - category: "Flags", - description: "flag: Paraguay", - unicode_version: "6.0", - }, - { - emoji: "🇶🇦", - aliases: ["qatar"], - tags: [], - category: "Flags", - description: "flag: Qatar", - unicode_version: "6.0", - }, - { - emoji: "🇷🇪", - aliases: ["reunion"], - tags: [], - category: "Flags", - description: "flag: Réunion", - unicode_version: "6.0", - }, - { - emoji: "🇷🇴", - aliases: ["romania"], - tags: [], - category: "Flags", - description: "flag: Romania", - unicode_version: "6.0", - }, - { - emoji: "🇷🇸", - aliases: ["serbia"], - tags: [], - category: "Flags", - description: "flag: Serbia", - unicode_version: "6.0", - }, - { - emoji: "🇷🇺", - aliases: ["ru"], - tags: ["russia"], - category: "Flags", - description: "flag: Russia", - unicode_version: "6.0", - }, - { - emoji: "🇷🇼", - aliases: ["rwanda"], - tags: [], - category: "Flags", - description: "flag: Rwanda", - unicode_version: "6.0", - }, - { - emoji: "🇸🇦", - aliases: ["saudi_arabia"], - tags: [], - category: "Flags", - description: "flag: Saudi Arabia", - unicode_version: "6.0", - }, - { - emoji: "🇸🇧", - aliases: ["solomon_islands"], - tags: [], - category: "Flags", - description: "flag: Solomon Islands", - unicode_version: "6.0", - }, - { - emoji: "🇸🇨", - aliases: ["seychelles"], - tags: [], - category: "Flags", - description: "flag: Seychelles", - unicode_version: "6.0", - }, - { - emoji: "🇸🇩", - aliases: ["sudan"], - tags: [], - category: "Flags", - description: "flag: Sudan", - unicode_version: "6.0", - }, - { - emoji: "🇸🇪", - aliases: ["sweden"], - tags: [], - category: "Flags", - description: "flag: Sweden", - unicode_version: "6.0", - }, - { - emoji: "🇸🇬", - aliases: ["singapore"], - tags: [], - category: "Flags", - description: "flag: Singapore", - unicode_version: "6.0", - }, - { - emoji: "🇸🇭", - aliases: ["st_helena"], - tags: [], - category: "Flags", - description: "flag: St. Helena", - unicode_version: "6.0", - }, - { - emoji: "🇸🇮", - aliases: ["slovenia"], - tags: [], - category: "Flags", - description: "flag: Slovenia", - unicode_version: "6.0", - }, - { - emoji: "🇸🇯", - aliases: ["svalbard_jan_mayen"], - tags: [], - category: "Flags", - description: "flag: Svalbard & Jan Mayen", - unicode_version: "11.0", - }, - { - emoji: "🇸🇰", - aliases: ["slovakia"], - tags: [], - category: "Flags", - description: "flag: Slovakia", - unicode_version: "6.0", - }, - { - emoji: "🇸🇱", - aliases: ["sierra_leone"], - tags: [], - category: "Flags", - description: "flag: Sierra Leone", - unicode_version: "6.0", - }, - { - emoji: "🇸🇲", - aliases: ["san_marino"], - tags: [], - category: "Flags", - description: "flag: San Marino", - unicode_version: "6.0", - }, - { - emoji: "🇸🇳", - aliases: ["senegal"], - tags: [], - category: "Flags", - description: "flag: Senegal", - unicode_version: "6.0", - }, - { - emoji: "🇸🇴", - aliases: ["somalia"], - tags: [], - category: "Flags", - description: "flag: Somalia", - unicode_version: "6.0", - }, - { - emoji: "🇸🇷", - aliases: ["suriname"], - tags: [], - category: "Flags", - description: "flag: Suriname", - unicode_version: "6.0", - }, - { - emoji: "🇸🇸", - aliases: ["south_sudan"], - tags: [], - category: "Flags", - description: "flag: South Sudan", - unicode_version: "6.0", - }, - { - emoji: "🇸🇹", - aliases: ["sao_tome_principe"], - tags: [], - category: "Flags", - description: "flag: São Tomé & Príncipe", - unicode_version: "6.0", - }, - { - emoji: "🇸🇻", - aliases: ["el_salvador"], - tags: [], - category: "Flags", - description: "flag: El Salvador", - unicode_version: "6.0", - }, - { - emoji: "🇸🇽", - aliases: ["sint_maarten"], - tags: [], - category: "Flags", - description: "flag: Sint Maarten", - unicode_version: "6.0", - }, - { - emoji: "🇸🇾", - aliases: ["syria"], - tags: [], - category: "Flags", - description: "flag: Syria", - unicode_version: "6.0", - }, - { - emoji: "🇸🇿", - aliases: ["swaziland"], - tags: [], - category: "Flags", - description: "flag: Eswatini", - unicode_version: "6.0", - }, - { - emoji: "🇹🇦", - aliases: ["tristan_da_cunha"], - tags: [], - category: "Flags", - description: "flag: Tristan da Cunha", - unicode_version: "11.0", - }, - { - emoji: "🇹🇨", - aliases: ["turks_caicos_islands"], - tags: [], - category: "Flags", - description: "flag: Turks & Caicos Islands", - unicode_version: "6.0", - }, - { - emoji: "🇹🇩", - aliases: ["chad"], - tags: [], - category: "Flags", - description: "flag: Chad", - unicode_version: "6.0", - }, - { - emoji: "🇹🇫", - aliases: ["french_southern_territories"], - tags: [], - category: "Flags", - description: "flag: French Southern Territories", - unicode_version: "6.0", - }, - { - emoji: "🇹🇬", - aliases: ["togo"], - tags: [], - category: "Flags", - description: "flag: Togo", - unicode_version: "6.0", - }, - { - emoji: "🇹🇭", - aliases: ["thailand"], - tags: [], - category: "Flags", - description: "flag: Thailand", - unicode_version: "6.0", - }, - { - emoji: "🇹🇯", - aliases: ["tajikistan"], - tags: [], - category: "Flags", - description: "flag: Tajikistan", - unicode_version: "6.0", - }, - { - emoji: "🇹🇰", - aliases: ["tokelau"], - tags: [], - category: "Flags", - description: "flag: Tokelau", - unicode_version: "6.0", - }, - { - emoji: "🇹🇱", - aliases: ["timor_leste"], - tags: [], - category: "Flags", - description: "flag: Timor-Leste", - unicode_version: "6.0", - }, - { - emoji: "🇹🇲", - aliases: ["turkmenistan"], - tags: [], - category: "Flags", - description: "flag: Turkmenistan", - unicode_version: "6.0", - }, - { - emoji: "🇹🇳", - aliases: ["tunisia"], - tags: [], - category: "Flags", - description: "flag: Tunisia", - unicode_version: "6.0", - }, - { - emoji: "🇹🇴", - aliases: ["tonga"], - tags: [], - category: "Flags", - description: "flag: Tonga", - unicode_version: "6.0", - }, - { - emoji: "🇹🇷", - aliases: ["tr"], - tags: ["turkey"], - category: "Flags", - description: "flag: Turkey", - unicode_version: "8.0", - }, - { - emoji: "🇹🇹", - aliases: ["trinidad_tobago"], - tags: [], - category: "Flags", - description: "flag: Trinidad & Tobago", - unicode_version: "6.0", - }, - { - emoji: "🇹🇻", - aliases: ["tuvalu"], - tags: [], - category: "Flags", - description: "flag: Tuvalu", - unicode_version: "6.0", - }, - { - emoji: "🇹🇼", - aliases: ["taiwan"], - tags: [], - category: "Flags", - description: "flag: Taiwan", - unicode_version: "6.0", - }, - { - emoji: "🇹🇿", - aliases: ["tanzania"], - tags: [], - category: "Flags", - description: "flag: Tanzania", - unicode_version: "6.0", - }, - { - emoji: "🇺🇦", - aliases: ["ukraine"], - tags: [], - category: "Flags", - description: "flag: Ukraine", - unicode_version: "6.0", - }, - { - emoji: "🇺🇬", - aliases: ["uganda"], - tags: [], - category: "Flags", - description: "flag: Uganda", - unicode_version: "6.0", - }, - { - emoji: "🇺🇲", - aliases: ["us_outlying_islands"], - tags: [], - category: "Flags", - description: "flag: U.S. Outlying Islands", - unicode_version: "11.0", - }, - { - emoji: "🇺🇳", - aliases: ["united_nations"], - tags: [], - category: "Flags", - description: "flag: United Nations", - unicode_version: "11.0", - }, - { - emoji: "🇺🇸", - aliases: ["us"], - tags: ["flag", "united", "america"], - category: "Flags", - description: "flag: United States", - unicode_version: "6.0", - }, - { - emoji: "🇺🇾", - aliases: ["uruguay"], - tags: [], - category: "Flags", - description: "flag: Uruguay", - unicode_version: "6.0", - }, - { - emoji: "🇺🇿", - aliases: ["uzbekistan"], - tags: [], - category: "Flags", - description: "flag: Uzbekistan", - unicode_version: "6.0", - }, - { - emoji: "🇻🇦", - aliases: ["vatican_city"], - tags: [], - category: "Flags", - description: "flag: Vatican City", - unicode_version: "6.0", - }, - { - emoji: "🇻🇨", - aliases: ["st_vincent_grenadines"], - tags: [], - category: "Flags", - description: "flag: St. Vincent & Grenadines", - unicode_version: "6.0", - }, - { - emoji: "🇻🇪", - aliases: ["venezuela"], - tags: [], - category: "Flags", - description: "flag: Venezuela", - unicode_version: "6.0", - }, - { - emoji: "🇻🇬", - aliases: ["british_virgin_islands"], - tags: [], - category: "Flags", - description: "flag: British Virgin Islands", - unicode_version: "6.0", - }, - { - emoji: "🇻🇮", - aliases: ["us_virgin_islands"], - tags: [], - category: "Flags", - description: "flag: U.S. Virgin Islands", - unicode_version: "6.0", - }, - { - emoji: "🇻🇳", - aliases: ["vietnam"], - tags: [], - category: "Flags", - description: "flag: Vietnam", - unicode_version: "6.0", - }, - { - emoji: "🇻🇺", - aliases: ["vanuatu"], - tags: [], - category: "Flags", - description: "flag: Vanuatu", - unicode_version: "6.0", - }, - { - emoji: "🇼🇫", - aliases: ["wallis_futuna"], - tags: [], - category: "Flags", - description: "flag: Wallis & Futuna", - unicode_version: "6.0", - }, - { - emoji: "🇼🇸", - aliases: ["samoa"], - tags: [], - category: "Flags", - description: "flag: Samoa", - unicode_version: "6.0", - }, - { - emoji: "🇽🇰", - aliases: ["kosovo"], - tags: [], - category: "Flags", - description: "flag: Kosovo", - unicode_version: "6.0", - }, - { - emoji: "🇾🇪", - aliases: ["yemen"], - tags: [], - category: "Flags", - description: "flag: Yemen", - unicode_version: "6.0", - }, - { - emoji: "🇾🇹", - aliases: ["mayotte"], - tags: [], - category: "Flags", - description: "flag: Mayotte", - unicode_version: "6.0", - }, - { - emoji: "🇿🇦", - aliases: ["south_africa"], - tags: [], - category: "Flags", - description: "flag: South Africa", - unicode_version: "6.0", - }, - { - emoji: "🇿🇲", - aliases: ["zambia"], - tags: [], - category: "Flags", - description: "flag: Zambia", - unicode_version: "6.0", - }, - { - emoji: "🇿🇼", - aliases: ["zimbabwe"], - tags: [], - category: "Flags", - description: "flag: Zimbabwe", - unicode_version: "6.0", - }, - { - emoji: "🏴󠁧󠁢󠁥󠁮󠁧󠁿", - aliases: ["england"], - tags: [], - category: "Flags", - description: "flag: England", - unicode_version: "11.0", - }, - { - emoji: "🏴󠁧󠁢󠁳󠁣󠁴󠁿", - aliases: ["scotland"], - tags: [], - category: "Flags", - description: "flag: Scotland", - unicode_version: "11.0", - }, - { - emoji: "🏴󠁧󠁢󠁷󠁬󠁳󠁿", - aliases: ["wales"], - tags: [], - category: "Flags", - description: "flag: Wales", - unicode_version: "11.0", - }, -]; +export const rawEmojis = [{"emoji":"😀","aliases":["grinning"],"tags":["smile","happy"],"category":"Smileys & Emotion","description":"grinning face","unicode_version":"6.1"},{"emoji":"😃","aliases":["smiley"],"tags":["happy","joy","haha"],"category":"Smileys & Emotion","description":"grinning face with big eyes","unicode_version":"6.0"},{"emoji":"😄","aliases":["smile"],"tags":["happy","joy","laugh","pleased"],"category":"Smileys & Emotion","description":"grinning face with smiling eyes","unicode_version":"6.0"},{"emoji":"😁","aliases":["grin"],"tags":[],"category":"Smileys & Emotion","description":"beaming face with smiling eyes","unicode_version":"6.0"},{"emoji":"😆","aliases":["laughing","satisfied"],"tags":["happy","haha"],"category":"Smileys & Emotion","description":"grinning squinting face","unicode_version":"6.0"},{"emoji":"😅","aliases":["sweat_smile"],"tags":["hot"],"category":"Smileys & Emotion","description":"grinning face with sweat","unicode_version":"6.0"},{"emoji":"🤣","aliases":["rofl"],"tags":["lol","laughing"],"category":"Smileys & Emotion","description":"rolling on the floor laughing","unicode_version":"9.0"},{"emoji":"😂","aliases":["joy"],"tags":["tears"],"category":"Smileys & Emotion","description":"face with tears of joy","unicode_version":"6.0"},{"emoji":"🙂","aliases":["slightly_smiling_face"],"tags":[],"category":"Smileys & Emotion","description":"slightly smiling face","unicode_version":"7.0"},{"emoji":"🙃","aliases":["upside_down_face"],"tags":[],"category":"Smileys & Emotion","description":"upside-down face","unicode_version":"8.0"},{"emoji":"😉","aliases":["wink"],"tags":["flirt"],"category":"Smileys & Emotion","description":"winking face","unicode_version":"6.0"},{"emoji":"😊","aliases":["blush"],"tags":["proud"],"category":"Smileys & Emotion","description":"smiling face with smiling eyes","unicode_version":"6.0"},{"emoji":"😇","aliases":["innocent"],"tags":["angel"],"category":"Smileys & Emotion","description":"smiling face with halo","unicode_version":"6.0"},{"emoji":"🥰","aliases":["smiling_face_with_three_hearts"],"tags":["love"],"category":"Smileys & Emotion","description":"smiling face with hearts","unicode_version":"11.0"},{"emoji":"😍","aliases":["heart_eyes"],"tags":["love","crush"],"category":"Smileys & Emotion","description":"smiling face with heart-eyes","unicode_version":"6.0"},{"emoji":"🤩","aliases":["star_struck"],"tags":["eyes"],"category":"Smileys & Emotion","description":"star-struck","unicode_version":"11.0"},{"emoji":"😘","aliases":["kissing_heart"],"tags":["flirt"],"category":"Smileys & Emotion","description":"face blowing a kiss","unicode_version":"6.0"},{"emoji":"😗","aliases":["kissing"],"tags":[],"category":"Smileys & Emotion","description":"kissing face","unicode_version":"6.1"},{"emoji":"☺️","aliases":["relaxed"],"tags":["blush","pleased"],"category":"Smileys & Emotion","description":"smiling face","unicode_version":""},{"emoji":"😚","aliases":["kissing_closed_eyes"],"tags":[],"category":"Smileys & Emotion","description":"kissing face with closed eyes","unicode_version":"6.0"},{"emoji":"😙","aliases":["kissing_smiling_eyes"],"tags":[],"category":"Smileys & Emotion","description":"kissing face with smiling eyes","unicode_version":"6.1"},{"emoji":"🥲","aliases":["smiling_face_with_tear"],"tags":[],"category":"Smileys & Emotion","description":"smiling face with tear","unicode_version":"13.0"},{"emoji":"😋","aliases":["yum"],"tags":["tongue","lick"],"category":"Smileys & Emotion","description":"face savoring food","unicode_version":"6.0"},{"emoji":"😛","aliases":["stuck_out_tongue"],"tags":[],"category":"Smileys & Emotion","description":"face with tongue","unicode_version":"6.1"},{"emoji":"😜","aliases":["stuck_out_tongue_winking_eye"],"tags":["prank","silly"],"category":"Smileys & Emotion","description":"winking face with tongue","unicode_version":"6.0"},{"emoji":"🤪","aliases":["zany_face"],"tags":["goofy","wacky"],"category":"Smileys & Emotion","description":"zany face","unicode_version":"11.0"},{"emoji":"😝","aliases":["stuck_out_tongue_closed_eyes"],"tags":["prank"],"category":"Smileys & Emotion","description":"squinting face with tongue","unicode_version":"6.0"},{"emoji":"🤑","aliases":["money_mouth_face"],"tags":["rich"],"category":"Smileys & Emotion","description":"money-mouth face","unicode_version":"8.0"},{"emoji":"🤗","aliases":["hugs"],"tags":[],"category":"Smileys & Emotion","description":"hugging face","unicode_version":"8.0"},{"emoji":"🤭","aliases":["hand_over_mouth"],"tags":["quiet","whoops"],"category":"Smileys & Emotion","description":"face with hand over mouth","unicode_version":"11.0"},{"emoji":"🤫","aliases":["shushing_face"],"tags":["silence","quiet"],"category":"Smileys & Emotion","description":"shushing face","unicode_version":"11.0"},{"emoji":"🤔","aliases":["thinking"],"tags":[],"category":"Smileys & Emotion","description":"thinking face","unicode_version":"8.0"},{"emoji":"🤐","aliases":["zipper_mouth_face"],"tags":["silence","hush"],"category":"Smileys & Emotion","description":"zipper-mouth face","unicode_version":"8.0"},{"emoji":"🤨","aliases":["raised_eyebrow"],"tags":["suspicious"],"category":"Smileys & Emotion","description":"face with raised eyebrow","unicode_version":"11.0"},{"emoji":"😐","aliases":["neutral_face"],"tags":["meh"],"category":"Smileys & Emotion","description":"neutral face","unicode_version":"6.0"},{"emoji":"😑","aliases":["expressionless"],"tags":[],"category":"Smileys & Emotion","description":"expressionless face","unicode_version":"6.1"},{"emoji":"😶","aliases":["no_mouth"],"tags":["mute","silence"],"category":"Smileys & Emotion","description":"face without mouth","unicode_version":"6.0"},{"emoji":"😶‍🌫️","aliases":["face_in_clouds"],"tags":[],"category":"Smileys & Emotion","description":"face in clouds","unicode_version":"13.1"},{"emoji":"😏","aliases":["smirk"],"tags":["smug"],"category":"Smileys & Emotion","description":"smirking face","unicode_version":"6.0"},{"emoji":"😒","aliases":["unamused"],"tags":["meh"],"category":"Smileys & Emotion","description":"unamused face","unicode_version":"6.0"},{"emoji":"🙄","aliases":["roll_eyes"],"tags":[],"category":"Smileys & Emotion","description":"face with rolling eyes","unicode_version":"8.0"},{"emoji":"😬","aliases":["grimacing"],"tags":[],"category":"Smileys & Emotion","description":"grimacing face","unicode_version":"6.1"},{"emoji":"😮‍💨","aliases":["face_exhaling"],"tags":[],"category":"Smileys & Emotion","description":"face exhaling","unicode_version":"13.1"},{"emoji":"🤥","aliases":["lying_face"],"tags":["liar"],"category":"Smileys & Emotion","description":"lying face","unicode_version":"9.0"},{"emoji":"😌","aliases":["relieved"],"tags":["whew"],"category":"Smileys & Emotion","description":"relieved face","unicode_version":"6.0"},{"emoji":"😔","aliases":["pensive"],"tags":[],"category":"Smileys & Emotion","description":"pensive face","unicode_version":"6.0"},{"emoji":"😪","aliases":["sleepy"],"tags":["tired"],"category":"Smileys & Emotion","description":"sleepy face","unicode_version":"6.0"},{"emoji":"🤤","aliases":["drooling_face"],"tags":[],"category":"Smileys & Emotion","description":"drooling face","unicode_version":"9.0"},{"emoji":"😴","aliases":["sleeping"],"tags":["zzz"],"category":"Smileys & Emotion","description":"sleeping face","unicode_version":"6.1"},{"emoji":"😷","aliases":["mask"],"tags":["sick","ill"],"category":"Smileys & Emotion","description":"face with medical mask","unicode_version":"6.0"},{"emoji":"🤒","aliases":["face_with_thermometer"],"tags":["sick"],"category":"Smileys & Emotion","description":"face with thermometer","unicode_version":"8.0"},{"emoji":"🤕","aliases":["face_with_head_bandage"],"tags":["hurt"],"category":"Smileys & Emotion","description":"face with head-bandage","unicode_version":"8.0"},{"emoji":"🤢","aliases":["nauseated_face"],"tags":["sick","barf","disgusted"],"category":"Smileys & Emotion","description":"nauseated face","unicode_version":"9.0"},{"emoji":"🤮","aliases":["vomiting_face"],"tags":["barf","sick"],"category":"Smileys & Emotion","description":"face vomiting","unicode_version":"11.0"},{"emoji":"🤧","aliases":["sneezing_face"],"tags":["achoo","sick"],"category":"Smileys & Emotion","description":"sneezing face","unicode_version":"9.0"},{"emoji":"🥵","aliases":["hot_face"],"tags":["heat","sweating"],"category":"Smileys & Emotion","description":"hot face","unicode_version":"11.0"},{"emoji":"🥶","aliases":["cold_face"],"tags":["freezing","ice"],"category":"Smileys & Emotion","description":"cold face","unicode_version":"11.0"},{"emoji":"🥴","aliases":["woozy_face"],"tags":["groggy"],"category":"Smileys & Emotion","description":"woozy face","unicode_version":"11.0"},{"emoji":"😵","aliases":["dizzy_face"],"tags":[],"category":"Smileys & Emotion","description":"knocked-out face","unicode_version":"6.0"},{"emoji":"😵‍💫","aliases":["face_with_spiral_eyes"],"tags":[],"category":"Smileys & Emotion","description":"face with spiral eyes","unicode_version":"13.1"},{"emoji":"🤯","aliases":["exploding_head"],"tags":["mind","blown"],"category":"Smileys & Emotion","description":"exploding head","unicode_version":"11.0"},{"emoji":"🤠","aliases":["cowboy_hat_face"],"tags":[],"category":"Smileys & Emotion","description":"cowboy hat face","unicode_version":"9.0"},{"emoji":"🥳","aliases":["partying_face"],"tags":["celebration","birthday"],"category":"Smileys & Emotion","description":"partying face","unicode_version":"11.0"},{"emoji":"🥸","aliases":["disguised_face"],"tags":[],"category":"Smileys & Emotion","description":"disguised face","unicode_version":"13.0"},{"emoji":"😎","aliases":["sunglasses"],"tags":["cool"],"category":"Smileys & Emotion","description":"smiling face with sunglasses","unicode_version":"6.0"},{"emoji":"🤓","aliases":["nerd_face"],"tags":["geek","glasses"],"category":"Smileys & Emotion","description":"nerd face","unicode_version":"8.0"},{"emoji":"🧐","aliases":["monocle_face"],"tags":[],"category":"Smileys & Emotion","description":"face with monocle","unicode_version":"11.0"},{"emoji":"😕","aliases":["confused"],"tags":[],"category":"Smileys & Emotion","description":"confused face","unicode_version":"6.1"},{"emoji":"😟","aliases":["worried"],"tags":["nervous"],"category":"Smileys & Emotion","description":"worried face","unicode_version":"6.1"},{"emoji":"🙁","aliases":["slightly_frowning_face"],"tags":[],"category":"Smileys & Emotion","description":"slightly frowning face","unicode_version":"7.0"},{"emoji":"☹️","aliases":["frowning_face"],"tags":[],"category":"Smileys & Emotion","description":"frowning face","unicode_version":""},{"emoji":"😮","aliases":["open_mouth"],"tags":["surprise","impressed","wow"],"category":"Smileys & Emotion","description":"face with open mouth","unicode_version":"6.1"},{"emoji":"😯","aliases":["hushed"],"tags":["silence","speechless"],"category":"Smileys & Emotion","description":"hushed face","unicode_version":"6.1"},{"emoji":"😲","aliases":["astonished"],"tags":["amazed","gasp"],"category":"Smileys & Emotion","description":"astonished face","unicode_version":"6.0"},{"emoji":"😳","aliases":["flushed"],"tags":[],"category":"Smileys & Emotion","description":"flushed face","unicode_version":"6.0"},{"emoji":"🥺","aliases":["pleading_face"],"tags":["puppy","eyes"],"category":"Smileys & Emotion","description":"pleading face","unicode_version":"11.0"},{"emoji":"😦","aliases":["frowning"],"tags":[],"category":"Smileys & Emotion","description":"frowning face with open mouth","unicode_version":"6.1"},{"emoji":"😧","aliases":["anguished"],"tags":["stunned"],"category":"Smileys & Emotion","description":"anguished face","unicode_version":"6.1"},{"emoji":"😨","aliases":["fearful"],"tags":["scared","shocked","oops"],"category":"Smileys & Emotion","description":"fearful face","unicode_version":"6.0"},{"emoji":"😰","aliases":["cold_sweat"],"tags":["nervous"],"category":"Smileys & Emotion","description":"anxious face with sweat","unicode_version":"6.0"},{"emoji":"😥","aliases":["disappointed_relieved"],"tags":["phew","sweat","nervous"],"category":"Smileys & Emotion","description":"sad but relieved face","unicode_version":"6.0"},{"emoji":"😢","aliases":["cry"],"tags":["sad","tear"],"category":"Smileys & Emotion","description":"crying face","unicode_version":"6.0"},{"emoji":"😭","aliases":["sob"],"tags":["sad","cry","bawling"],"category":"Smileys & Emotion","description":"loudly crying face","unicode_version":"6.0"},{"emoji":"😱","aliases":["scream"],"tags":["horror","shocked"],"category":"Smileys & Emotion","description":"face screaming in fear","unicode_version":"6.0"},{"emoji":"😖","aliases":["confounded"],"tags":[],"category":"Smileys & Emotion","description":"confounded face","unicode_version":"6.0"},{"emoji":"😣","aliases":["persevere"],"tags":["struggling"],"category":"Smileys & Emotion","description":"persevering face","unicode_version":"6.0"},{"emoji":"😞","aliases":["disappointed"],"tags":["sad"],"category":"Smileys & Emotion","description":"disappointed face","unicode_version":"6.0"},{"emoji":"😓","aliases":["sweat"],"tags":[],"category":"Smileys & Emotion","description":"downcast face with sweat","unicode_version":"6.0"},{"emoji":"😩","aliases":["weary"],"tags":["tired"],"category":"Smileys & Emotion","description":"weary face","unicode_version":"6.0"},{"emoji":"😫","aliases":["tired_face"],"tags":["upset","whine"],"category":"Smileys & Emotion","description":"tired face","unicode_version":"6.0"},{"emoji":"🥱","aliases":["yawning_face"],"tags":[],"category":"Smileys & Emotion","description":"yawning face","unicode_version":"12.0"},{"emoji":"😤","aliases":["triumph"],"tags":["smug"],"category":"Smileys & Emotion","description":"face with steam from nose","unicode_version":"6.0"},{"emoji":"😡","aliases":["rage","pout"],"tags":["angry"],"category":"Smileys & Emotion","description":"pouting face","unicode_version":"6.0"},{"emoji":"😠","aliases":["angry"],"tags":["mad","annoyed"],"category":"Smileys & Emotion","description":"angry face","unicode_version":"6.0"},{"emoji":"🤬","aliases":["cursing_face"],"tags":["foul"],"category":"Smileys & Emotion","description":"face with symbols on mouth","unicode_version":"11.0"},{"emoji":"😈","aliases":["smiling_imp"],"tags":["devil","evil","horns"],"category":"Smileys & Emotion","description":"smiling face with horns","unicode_version":"6.0"},{"emoji":"👿","aliases":["imp"],"tags":["angry","devil","evil","horns"],"category":"Smileys & Emotion","description":"angry face with horns","unicode_version":"6.0"},{"emoji":"💀","aliases":["skull"],"tags":["dead","danger","poison"],"category":"Smileys & Emotion","description":"skull","unicode_version":"6.0"},{"emoji":"☠️","aliases":["skull_and_crossbones"],"tags":["danger","pirate"],"category":"Smileys & Emotion","description":"skull and crossbones","unicode_version":""},{"emoji":"💩","aliases":["hankey","poop","shit"],"tags":["crap"],"category":"Smileys & Emotion","description":"pile of poo","unicode_version":"6.0"},{"emoji":"🤡","aliases":["clown_face"],"tags":[],"category":"Smileys & Emotion","description":"clown face","unicode_version":"9.0"},{"emoji":"👹","aliases":["japanese_ogre"],"tags":["monster"],"category":"Smileys & Emotion","description":"ogre","unicode_version":"6.0"},{"emoji":"👺","aliases":["japanese_goblin"],"tags":[],"category":"Smileys & Emotion","description":"goblin","unicode_version":"6.0"},{"emoji":"👻","aliases":["ghost"],"tags":["halloween"],"category":"Smileys & Emotion","description":"ghost","unicode_version":"6.0"},{"emoji":"👽","aliases":["alien"],"tags":["ufo"],"category":"Smileys & Emotion","description":"alien","unicode_version":"6.0"},{"emoji":"👾","aliases":["space_invader"],"tags":["game","retro"],"category":"Smileys & Emotion","description":"alien monster","unicode_version":"6.0"},{"emoji":"🤖","aliases":["robot"],"tags":[],"category":"Smileys & Emotion","description":"robot","unicode_version":"8.0"},{"emoji":"😺","aliases":["smiley_cat"],"tags":[],"category":"Smileys & Emotion","description":"grinning cat","unicode_version":"6.0"},{"emoji":"😸","aliases":["smile_cat"],"tags":[],"category":"Smileys & Emotion","description":"grinning cat with smiling eyes","unicode_version":"6.0"},{"emoji":"😹","aliases":["joy_cat"],"tags":[],"category":"Smileys & Emotion","description":"cat with tears of joy","unicode_version":"6.0"},{"emoji":"😻","aliases":["heart_eyes_cat"],"tags":[],"category":"Smileys & Emotion","description":"smiling cat with heart-eyes","unicode_version":"6.0"},{"emoji":"😼","aliases":["smirk_cat"],"tags":[],"category":"Smileys & Emotion","description":"cat with wry smile","unicode_version":"6.0"},{"emoji":"😽","aliases":["kissing_cat"],"tags":[],"category":"Smileys & Emotion","description":"kissing cat","unicode_version":"6.0"},{"emoji":"🙀","aliases":["scream_cat"],"tags":["horror"],"category":"Smileys & Emotion","description":"weary cat","unicode_version":"6.0"},{"emoji":"😿","aliases":["crying_cat_face"],"tags":["sad","tear"],"category":"Smileys & Emotion","description":"crying cat","unicode_version":"6.0"},{"emoji":"😾","aliases":["pouting_cat"],"tags":[],"category":"Smileys & Emotion","description":"pouting cat","unicode_version":"6.0"},{"emoji":"🙈","aliases":["see_no_evil"],"tags":["monkey","blind","ignore"],"category":"Smileys & Emotion","description":"see-no-evil monkey","unicode_version":"6.0"},{"emoji":"🙉","aliases":["hear_no_evil"],"tags":["monkey","deaf"],"category":"Smileys & Emotion","description":"hear-no-evil monkey","unicode_version":"6.0"},{"emoji":"🙊","aliases":["speak_no_evil"],"tags":["monkey","mute","hush"],"category":"Smileys & Emotion","description":"speak-no-evil monkey","unicode_version":"6.0"},{"emoji":"💋","aliases":["kiss"],"tags":["lipstick"],"category":"Smileys & Emotion","description":"kiss mark","unicode_version":"6.0"},{"emoji":"💌","aliases":["love_letter"],"tags":["email","envelope"],"category":"Smileys & Emotion","description":"love letter","unicode_version":"6.0"},{"emoji":"💘","aliases":["cupid"],"tags":["love","heart"],"category":"Smileys & Emotion","description":"heart with arrow","unicode_version":"6.0"},{"emoji":"💝","aliases":["gift_heart"],"tags":["chocolates"],"category":"Smileys & Emotion","description":"heart with ribbon","unicode_version":"6.0"},{"emoji":"💖","aliases":["sparkling_heart"],"tags":[],"category":"Smileys & Emotion","description":"sparkling heart","unicode_version":"6.0"},{"emoji":"💗","aliases":["heartpulse"],"tags":[],"category":"Smileys & Emotion","description":"growing heart","unicode_version":"6.0"},{"emoji":"💓","aliases":["heartbeat"],"tags":[],"category":"Smileys & Emotion","description":"beating heart","unicode_version":"6.0"},{"emoji":"💞","aliases":["revolving_hearts"],"tags":[],"category":"Smileys & Emotion","description":"revolving hearts","unicode_version":"6.0"},{"emoji":"💕","aliases":["two_hearts"],"tags":[],"category":"Smileys & Emotion","description":"two hearts","unicode_version":"6.0"},{"emoji":"💟","aliases":["heart_decoration"],"tags":[],"category":"Smileys & Emotion","description":"heart decoration","unicode_version":"6.0"},{"emoji":"❣️","aliases":["heavy_heart_exclamation"],"tags":[],"category":"Smileys & Emotion","description":"heart exclamation","unicode_version":""},{"emoji":"💔","aliases":["broken_heart"],"tags":[],"category":"Smileys & Emotion","description":"broken heart","unicode_version":"6.0"},{"emoji":"❤️‍🔥","aliases":["heart_on_fire"],"tags":[],"category":"Smileys & Emotion","description":"heart on fire","unicode_version":"13.1"},{"emoji":"❤️‍🩹","aliases":["mending_heart"],"tags":[],"category":"Smileys & Emotion","description":"mending heart","unicode_version":"13.1"},{"emoji":"❤️","aliases":["heart"],"tags":["love"],"category":"Smileys & Emotion","description":"red heart","unicode_version":""},{"emoji":"🧡","aliases":["orange_heart"],"tags":[],"category":"Smileys & Emotion","description":"orange heart","unicode_version":"11.0"},{"emoji":"💛","aliases":["yellow_heart"],"tags":[],"category":"Smileys & Emotion","description":"yellow heart","unicode_version":"6.0"},{"emoji":"💚","aliases":["green_heart"],"tags":[],"category":"Smileys & Emotion","description":"green heart","unicode_version":"6.0"},{"emoji":"💙","aliases":["blue_heart"],"tags":[],"category":"Smileys & Emotion","description":"blue heart","unicode_version":"6.0"},{"emoji":"💜","aliases":["purple_heart"],"tags":[],"category":"Smileys & Emotion","description":"purple heart","unicode_version":"6.0"},{"emoji":"🤎","aliases":["brown_heart"],"tags":[],"category":"Smileys & Emotion","description":"brown heart","unicode_version":"12.0"},{"emoji":"🖤","aliases":["black_heart"],"tags":[],"category":"Smileys & Emotion","description":"black heart","unicode_version":"9.0"},{"emoji":"🤍","aliases":["white_heart"],"tags":[],"category":"Smileys & Emotion","description":"white heart","unicode_version":"12.0"},{"emoji":"💯","aliases":["100"],"tags":["score","perfect"],"category":"Smileys & Emotion","description":"hundred points","unicode_version":"6.0"},{"emoji":"💢","aliases":["anger"],"tags":["angry"],"category":"Smileys & Emotion","description":"anger symbol","unicode_version":"6.0"},{"emoji":"💥","aliases":["boom","collision"],"tags":["explode"],"category":"Smileys & Emotion","description":"collision","unicode_version":"6.0"},{"emoji":"💫","aliases":["dizzy"],"tags":["star"],"category":"Smileys & Emotion","description":"dizzy","unicode_version":"6.0"},{"emoji":"💦","aliases":["sweat_drops"],"tags":["water","workout"],"category":"Smileys & Emotion","description":"sweat droplets","unicode_version":"6.0"},{"emoji":"💨","aliases":["dash"],"tags":["wind","blow","fast"],"category":"Smileys & Emotion","description":"dashing away","unicode_version":"6.0"},{"emoji":"🕳️","aliases":["hole"],"tags":[],"category":"Smileys & Emotion","description":"hole","unicode_version":"7.0"},{"emoji":"💣","aliases":["bomb"],"tags":["boom"],"category":"Smileys & Emotion","description":"bomb","unicode_version":"6.0"},{"emoji":"💬","aliases":["speech_balloon"],"tags":["comment"],"category":"Smileys & Emotion","description":"speech balloon","unicode_version":"6.0"},{"emoji":"👁️‍🗨️","aliases":["eye_speech_bubble"],"tags":[],"category":"Smileys & Emotion","description":"eye in speech bubble","unicode_version":"11.0"},{"emoji":"🗨️","aliases":["left_speech_bubble"],"tags":[],"category":"Smileys & Emotion","description":"left speech bubble","unicode_version":"11.0"},{"emoji":"🗯️","aliases":["right_anger_bubble"],"tags":[],"category":"Smileys & Emotion","description":"right anger bubble","unicode_version":"7.0"},{"emoji":"💭","aliases":["thought_balloon"],"tags":["thinking"],"category":"Smileys & Emotion","description":"thought balloon","unicode_version":"6.0"},{"emoji":"💤","aliases":["zzz"],"tags":["sleeping"],"category":"Smileys & Emotion","description":"zzz","unicode_version":"6.0"},{"emoji":"👋","aliases":["wave"],"tags":["goodbye"],"category":"People & Body","description":"waving hand","unicode_version":"6.0"},{"emoji":"🤚","aliases":["raised_back_of_hand"],"tags":[],"category":"People & Body","description":"raised back of hand","unicode_version":"9.0"},{"emoji":"🖐️","aliases":["raised_hand_with_fingers_splayed"],"tags":[],"category":"People & Body","description":"hand with fingers splayed","unicode_version":"7.0"},{"emoji":"✋","aliases":["hand","raised_hand"],"tags":["highfive","stop"],"category":"People & Body","description":"raised hand","unicode_version":"6.0"},{"emoji":"🖖","aliases":["vulcan_salute"],"tags":["prosper","spock"],"category":"People & Body","description":"vulcan salute","unicode_version":"7.0"},{"emoji":"👌","aliases":["ok_hand"],"tags":[],"category":"People & Body","description":"OK hand","unicode_version":"6.0"},{"emoji":"🤌","aliases":["pinched_fingers"],"tags":[],"category":"People & Body","description":"pinched fingers","unicode_version":"13.0"},{"emoji":"🤏","aliases":["pinching_hand"],"tags":[],"category":"People & Body","description":"pinching hand","unicode_version":"12.0"},{"emoji":"✌️","aliases":["v"],"tags":["victory","peace"],"category":"People & Body","description":"victory hand","unicode_version":""},{"emoji":"🤞","aliases":["crossed_fingers"],"tags":["luck","hopeful"],"category":"People & Body","description":"crossed fingers","unicode_version":"9.0"},{"emoji":"🤟","aliases":["love_you_gesture"],"tags":[],"category":"People & Body","description":"love-you gesture","unicode_version":"11.0"},{"emoji":"🤘","aliases":["metal"],"tags":[],"category":"People & Body","description":"sign of the horns","unicode_version":"8.0"},{"emoji":"🤙","aliases":["call_me_hand"],"tags":[],"category":"People & Body","description":"call me hand","unicode_version":"9.0"},{"emoji":"👈","aliases":["point_left"],"tags":[],"category":"People & Body","description":"backhand index pointing left","unicode_version":"6.0"},{"emoji":"👉","aliases":["point_right"],"tags":[],"category":"People & Body","description":"backhand index pointing right","unicode_version":"6.0"},{"emoji":"👆","aliases":["point_up_2"],"tags":[],"category":"People & Body","description":"backhand index pointing up","unicode_version":"6.0"},{"emoji":"🖕","aliases":["middle_finger","fu"],"tags":[],"category":"People & Body","description":"middle finger","unicode_version":"7.0"},{"emoji":"👇","aliases":["point_down"],"tags":[],"category":"People & Body","description":"backhand index pointing down","unicode_version":"6.0"},{"emoji":"☝️","aliases":["point_up"],"tags":[],"category":"People & Body","description":"index pointing up","unicode_version":""},{"emoji":"👍","aliases":["+1","thumbsup"],"tags":["approve","ok"],"category":"People & Body","description":"thumbs up","unicode_version":"6.0"},{"emoji":"👎","aliases":["-1","thumbsdown"],"tags":["disapprove","bury"],"category":"People & Body","description":"thumbs down","unicode_version":"6.0"},{"emoji":"✊","aliases":["fist_raised","fist"],"tags":["power"],"category":"People & Body","description":"raised fist","unicode_version":"6.0"},{"emoji":"👊","aliases":["fist_oncoming","facepunch","punch"],"tags":["attack"],"category":"People & Body","description":"oncoming fist","unicode_version":"6.0"},{"emoji":"🤛","aliases":["fist_left"],"tags":[],"category":"People & Body","description":"left-facing fist","unicode_version":"9.0"},{"emoji":"🤜","aliases":["fist_right"],"tags":[],"category":"People & Body","description":"right-facing fist","unicode_version":"9.0"},{"emoji":"👏","aliases":["clap"],"tags":["praise","applause"],"category":"People & Body","description":"clapping hands","unicode_version":"6.0"},{"emoji":"🙌","aliases":["raised_hands"],"tags":["hooray"],"category":"People & Body","description":"raising hands","unicode_version":"6.0"},{"emoji":"👐","aliases":["open_hands"],"tags":[],"category":"People & Body","description":"open hands","unicode_version":"6.0"},{"emoji":"🤲","aliases":["palms_up_together"],"tags":[],"category":"People & Body","description":"palms up together","unicode_version":"11.0"},{"emoji":"🤝","aliases":["handshake"],"tags":["deal"],"category":"People & Body","description":"handshake","unicode_version":"9.0"},{"emoji":"🙏","aliases":["pray"],"tags":["please","hope","wish"],"category":"People & Body","description":"folded hands","unicode_version":"6.0"},{"emoji":"✍️","aliases":["writing_hand"],"tags":[],"category":"People & Body","description":"writing hand","unicode_version":""},{"emoji":"💅","aliases":["nail_care"],"tags":["beauty","manicure"],"category":"People & Body","description":"nail polish","unicode_version":"6.0"},{"emoji":"🤳","aliases":["selfie"],"tags":[],"category":"People & Body","description":"selfie","unicode_version":"9.0"},{"emoji":"💪","aliases":["muscle"],"tags":["flex","bicep","strong","workout"],"category":"People & Body","description":"flexed biceps","unicode_version":"6.0"},{"emoji":"🦾","aliases":["mechanical_arm"],"tags":[],"category":"People & Body","description":"mechanical arm","unicode_version":"12.0"},{"emoji":"🦿","aliases":["mechanical_leg"],"tags":[],"category":"People & Body","description":"mechanical leg","unicode_version":"12.0"},{"emoji":"🦵","aliases":["leg"],"tags":[],"category":"People & Body","description":"leg","unicode_version":"11.0"},{"emoji":"🦶","aliases":["foot"],"tags":[],"category":"People & Body","description":"foot","unicode_version":"11.0"},{"emoji":"👂","aliases":["ear"],"tags":["hear","sound","listen"],"category":"People & Body","description":"ear","unicode_version":"6.0"},{"emoji":"🦻","aliases":["ear_with_hearing_aid"],"tags":[],"category":"People & Body","description":"ear with hearing aid","unicode_version":"12.0"},{"emoji":"👃","aliases":["nose"],"tags":["smell"],"category":"People & Body","description":"nose","unicode_version":"6.0"},{"emoji":"🧠","aliases":["brain"],"tags":[],"category":"People & Body","description":"brain","unicode_version":"11.0"},{"emoji":"🫀","aliases":["anatomical_heart"],"tags":[],"category":"People & Body","description":"anatomical heart","unicode_version":"13.0"},{"emoji":"🫁","aliases":["lungs"],"tags":[],"category":"People & Body","description":"lungs","unicode_version":"13.0"},{"emoji":"🦷","aliases":["tooth"],"tags":[],"category":"People & Body","description":"tooth","unicode_version":"11.0"},{"emoji":"🦴","aliases":["bone"],"tags":[],"category":"People & Body","description":"bone","unicode_version":"11.0"},{"emoji":"👀","aliases":["eyes"],"tags":["look","see","watch"],"category":"People & Body","description":"eyes","unicode_version":"6.0"},{"emoji":"👁️","aliases":["eye"],"tags":[],"category":"People & Body","description":"eye","unicode_version":"7.0"},{"emoji":"👅","aliases":["tongue"],"tags":["taste"],"category":"People & Body","description":"tongue","unicode_version":"6.0"},{"emoji":"👄","aliases":["lips"],"tags":["kiss"],"category":"People & Body","description":"mouth","unicode_version":"6.0"},{"emoji":"👶","aliases":["baby"],"tags":["child","newborn"],"category":"People & Body","description":"baby","unicode_version":"6.0"},{"emoji":"🧒","aliases":["child"],"tags":[],"category":"People & Body","description":"child","unicode_version":"11.0"},{"emoji":"👦","aliases":["boy"],"tags":["child"],"category":"People & Body","description":"boy","unicode_version":"6.0"},{"emoji":"👧","aliases":["girl"],"tags":["child"],"category":"People & Body","description":"girl","unicode_version":"6.0"},{"emoji":"🧑","aliases":["adult"],"tags":[],"category":"People & Body","description":"person","unicode_version":"11.0"},{"emoji":"👱","aliases":["blond_haired_person"],"tags":[],"category":"People & Body","description":"person: blond hair","unicode_version":"6.0"},{"emoji":"👨","aliases":["man"],"tags":["mustache","father","dad"],"category":"People & Body","description":"man","unicode_version":"6.0"},{"emoji":"🧔","aliases":["bearded_person"],"tags":[],"category":"People & Body","description":"person: beard","unicode_version":"11.0"},{"emoji":"🧔‍♂️","aliases":["man_beard"],"tags":[],"category":"People & Body","description":"man: beard","unicode_version":"13.1"},{"emoji":"🧔‍♀️","aliases":["woman_beard"],"tags":[],"category":"People & Body","description":"woman: beard","unicode_version":"13.1"},{"emoji":"👨‍🦰","aliases":["red_haired_man"],"tags":[],"category":"People & Body","description":"man: red hair","unicode_version":"11.0"},{"emoji":"👨‍🦱","aliases":["curly_haired_man"],"tags":[],"category":"People & Body","description":"man: curly hair","unicode_version":"11.0"},{"emoji":"👨‍🦳","aliases":["white_haired_man"],"tags":[],"category":"People & Body","description":"man: white hair","unicode_version":"11.0"},{"emoji":"👨‍🦲","aliases":["bald_man"],"tags":[],"category":"People & Body","description":"man: bald","unicode_version":"11.0"},{"emoji":"👩","aliases":["woman"],"tags":["girls"],"category":"People & Body","description":"woman","unicode_version":"6.0"},{"emoji":"👩‍🦰","aliases":["red_haired_woman"],"tags":[],"category":"People & Body","description":"woman: red hair","unicode_version":"11.0"},{"emoji":"🧑‍🦰","aliases":["person_red_hair"],"tags":[],"category":"People & Body","description":"person: red hair","unicode_version":"12.1"},{"emoji":"👩‍🦱","aliases":["curly_haired_woman"],"tags":[],"category":"People & Body","description":"woman: curly hair","unicode_version":"11.0"},{"emoji":"🧑‍🦱","aliases":["person_curly_hair"],"tags":[],"category":"People & Body","description":"person: curly hair","unicode_version":"12.1"},{"emoji":"👩‍🦳","aliases":["white_haired_woman"],"tags":[],"category":"People & Body","description":"woman: white hair","unicode_version":"11.0"},{"emoji":"🧑‍🦳","aliases":["person_white_hair"],"tags":[],"category":"People & Body","description":"person: white hair","unicode_version":"12.1"},{"emoji":"👩‍🦲","aliases":["bald_woman"],"tags":[],"category":"People & Body","description":"woman: bald","unicode_version":"11.0"},{"emoji":"🧑‍🦲","aliases":["person_bald"],"tags":[],"category":"People & Body","description":"person: bald","unicode_version":"12.1"},{"emoji":"👱‍♀️","aliases":["blond_haired_woman","blonde_woman"],"tags":[],"category":"People & Body","description":"woman: blond hair","unicode_version":"6.0"},{"emoji":"👱‍♂️","aliases":["blond_haired_man"],"tags":[],"category":"People & Body","description":"man: blond hair","unicode_version":"11.0"},{"emoji":"🧓","aliases":["older_adult"],"tags":[],"category":"People & Body","description":"older person","unicode_version":"11.0"},{"emoji":"👴","aliases":["older_man"],"tags":[],"category":"People & Body","description":"old man","unicode_version":"6.0"},{"emoji":"👵","aliases":["older_woman"],"tags":[],"category":"People & Body","description":"old woman","unicode_version":"6.0"},{"emoji":"🙍","aliases":["frowning_person"],"tags":[],"category":"People & Body","description":"person frowning","unicode_version":"6.0"},{"emoji":"🙍‍♂️","aliases":["frowning_man"],"tags":[],"category":"People & Body","description":"man frowning","unicode_version":"6.0"},{"emoji":"🙍‍♀️","aliases":["frowning_woman"],"tags":[],"category":"People & Body","description":"woman frowning","unicode_version":"11.0"},{"emoji":"🙎","aliases":["pouting_face"],"tags":[],"category":"People & Body","description":"person pouting","unicode_version":"6.0"},{"emoji":"🙎‍♂️","aliases":["pouting_man"],"tags":[],"category":"People & Body","description":"man pouting","unicode_version":"6.0"},{"emoji":"🙎‍♀️","aliases":["pouting_woman"],"tags":[],"category":"People & Body","description":"woman pouting","unicode_version":"11.0"},{"emoji":"🙅","aliases":["no_good"],"tags":["stop","halt","denied"],"category":"People & Body","description":"person gesturing NO","unicode_version":"6.0"},{"emoji":"🙅‍♂️","aliases":["no_good_man","ng_man"],"tags":["stop","halt","denied"],"category":"People & Body","description":"man gesturing NO","unicode_version":"6.0"},{"emoji":"🙅‍♀️","aliases":["no_good_woman","ng_woman"],"tags":["stop","halt","denied"],"category":"People & Body","description":"woman gesturing NO","unicode_version":"11.0"},{"emoji":"🙆","aliases":["ok_person"],"tags":[],"category":"People & Body","description":"person gesturing OK","unicode_version":"6.0"},{"emoji":"🙆‍♂️","aliases":["ok_man"],"tags":[],"category":"People & Body","description":"man gesturing OK","unicode_version":"6.0"},{"emoji":"🙆‍♀️","aliases":["ok_woman"],"tags":[],"category":"People & Body","description":"woman gesturing OK","unicode_version":"11.0"},{"emoji":"💁","aliases":["tipping_hand_person","information_desk_person"],"tags":[],"category":"People & Body","description":"person tipping hand","unicode_version":"6.0"},{"emoji":"💁‍♂️","aliases":["tipping_hand_man","sassy_man"],"tags":["information"],"category":"People & Body","description":"man tipping hand","unicode_version":"6.0"},{"emoji":"💁‍♀️","aliases":["tipping_hand_woman","sassy_woman"],"tags":["information"],"category":"People & Body","description":"woman tipping hand","unicode_version":"11.0"},{"emoji":"🙋","aliases":["raising_hand"],"tags":[],"category":"People & Body","description":"person raising hand","unicode_version":"6.0"},{"emoji":"🙋‍♂️","aliases":["raising_hand_man"],"tags":[],"category":"People & Body","description":"man raising hand","unicode_version":"6.0"},{"emoji":"🙋‍♀️","aliases":["raising_hand_woman"],"tags":[],"category":"People & Body","description":"woman raising hand","unicode_version":"11.0"},{"emoji":"🧏","aliases":["deaf_person"],"tags":[],"category":"People & Body","description":"deaf person","unicode_version":"12.0"},{"emoji":"🧏‍♂️","aliases":["deaf_man"],"tags":[],"category":"People & Body","description":"deaf man","unicode_version":"12.0"},{"emoji":"🧏‍♀️","aliases":["deaf_woman"],"tags":[],"category":"People & Body","description":"deaf woman","unicode_version":"12.0"},{"emoji":"🙇","aliases":["bow"],"tags":["respect","thanks"],"category":"People & Body","description":"person bowing","unicode_version":"6.0"},{"emoji":"🙇‍♂️","aliases":["bowing_man"],"tags":["respect","thanks"],"category":"People & Body","description":"man bowing","unicode_version":"11.0"},{"emoji":"🙇‍♀️","aliases":["bowing_woman"],"tags":["respect","thanks"],"category":"People & Body","description":"woman bowing","unicode_version":"6.0"},{"emoji":"🤦","aliases":["facepalm"],"tags":[],"category":"People & Body","description":"person facepalming","unicode_version":"11.0"},{"emoji":"🤦‍♂️","aliases":["man_facepalming"],"tags":[],"category":"People & Body","description":"man facepalming","unicode_version":"9.0"},{"emoji":"🤦‍♀️","aliases":["woman_facepalming"],"tags":[],"category":"People & Body","description":"woman facepalming","unicode_version":"9.0"},{"emoji":"🤷","aliases":["shrug"],"tags":[],"category":"People & Body","description":"person shrugging","unicode_version":"11.0"},{"emoji":"🤷‍♂️","aliases":["man_shrugging"],"tags":[],"category":"People & Body","description":"man shrugging","unicode_version":"9.0"},{"emoji":"🤷‍♀️","aliases":["woman_shrugging"],"tags":[],"category":"People & Body","description":"woman shrugging","unicode_version":"9.0"},{"emoji":"🧑‍⚕️","aliases":["health_worker"],"tags":[],"category":"People & Body","description":"health worker","unicode_version":"12.1"},{"emoji":"👨‍⚕️","aliases":["man_health_worker"],"tags":["doctor","nurse"],"category":"People & Body","description":"man health worker","unicode_version":""},{"emoji":"👩‍⚕️","aliases":["woman_health_worker"],"tags":["doctor","nurse"],"category":"People & Body","description":"woman health worker","unicode_version":""},{"emoji":"🧑‍🎓","aliases":["student"],"tags":[],"category":"People & Body","description":"student","unicode_version":"12.1"},{"emoji":"👨‍🎓","aliases":["man_student"],"tags":["graduation"],"category":"People & Body","description":"man student","unicode_version":""},{"emoji":"👩‍🎓","aliases":["woman_student"],"tags":["graduation"],"category":"People & Body","description":"woman student","unicode_version":""},{"emoji":"🧑‍🏫","aliases":["teacher"],"tags":[],"category":"People & Body","description":"teacher","unicode_version":"12.1"},{"emoji":"👨‍🏫","aliases":["man_teacher"],"tags":["school","professor"],"category":"People & Body","description":"man teacher","unicode_version":""},{"emoji":"👩‍🏫","aliases":["woman_teacher"],"tags":["school","professor"],"category":"People & Body","description":"woman teacher","unicode_version":""},{"emoji":"🧑‍⚖️","aliases":["judge"],"tags":[],"category":"People & Body","description":"judge","unicode_version":"12.1"},{"emoji":"👨‍⚖️","aliases":["man_judge"],"tags":["justice"],"category":"People & Body","description":"man judge","unicode_version":""},{"emoji":"👩‍⚖️","aliases":["woman_judge"],"tags":["justice"],"category":"People & Body","description":"woman judge","unicode_version":""},{"emoji":"🧑‍🌾","aliases":["farmer"],"tags":[],"category":"People & Body","description":"farmer","unicode_version":"12.1"},{"emoji":"👨‍🌾","aliases":["man_farmer"],"tags":[],"category":"People & Body","description":"man farmer","unicode_version":""},{"emoji":"👩‍🌾","aliases":["woman_farmer"],"tags":[],"category":"People & Body","description":"woman farmer","unicode_version":""},{"emoji":"🧑‍🍳","aliases":["cook"],"tags":[],"category":"People & Body","description":"cook","unicode_version":"12.1"},{"emoji":"👨‍🍳","aliases":["man_cook"],"tags":["chef"],"category":"People & Body","description":"man cook","unicode_version":""},{"emoji":"👩‍🍳","aliases":["woman_cook"],"tags":["chef"],"category":"People & Body","description":"woman cook","unicode_version":""},{"emoji":"🧑‍🔧","aliases":["mechanic"],"tags":[],"category":"People & Body","description":"mechanic","unicode_version":"12.1"},{"emoji":"👨‍🔧","aliases":["man_mechanic"],"tags":[],"category":"People & Body","description":"man mechanic","unicode_version":""},{"emoji":"👩‍🔧","aliases":["woman_mechanic"],"tags":[],"category":"People & Body","description":"woman mechanic","unicode_version":""},{"emoji":"🧑‍🏭","aliases":["factory_worker"],"tags":[],"category":"People & Body","description":"factory worker","unicode_version":"12.1"},{"emoji":"👨‍🏭","aliases":["man_factory_worker"],"tags":[],"category":"People & Body","description":"man factory worker","unicode_version":""},{"emoji":"👩‍🏭","aliases":["woman_factory_worker"],"tags":[],"category":"People & Body","description":"woman factory worker","unicode_version":""},{"emoji":"🧑‍💼","aliases":["office_worker"],"tags":[],"category":"People & Body","description":"office worker","unicode_version":"12.1"},{"emoji":"👨‍💼","aliases":["man_office_worker"],"tags":["business"],"category":"People & Body","description":"man office worker","unicode_version":""},{"emoji":"👩‍💼","aliases":["woman_office_worker"],"tags":["business"],"category":"People & Body","description":"woman office worker","unicode_version":""},{"emoji":"🧑‍🔬","aliases":["scientist"],"tags":[],"category":"People & Body","description":"scientist","unicode_version":"12.1"},{"emoji":"👨‍🔬","aliases":["man_scientist"],"tags":["research"],"category":"People & Body","description":"man scientist","unicode_version":""},{"emoji":"👩‍🔬","aliases":["woman_scientist"],"tags":["research"],"category":"People & Body","description":"woman scientist","unicode_version":""},{"emoji":"🧑‍💻","aliases":["technologist"],"tags":[],"category":"People & Body","description":"technologist","unicode_version":"12.1"},{"emoji":"👨‍💻","aliases":["man_technologist"],"tags":["coder"],"category":"People & Body","description":"man technologist","unicode_version":""},{"emoji":"👩‍💻","aliases":["woman_technologist"],"tags":["coder"],"category":"People & Body","description":"woman technologist","unicode_version":""},{"emoji":"🧑‍🎤","aliases":["singer"],"tags":[],"category":"People & Body","description":"singer","unicode_version":"12.1"},{"emoji":"👨‍🎤","aliases":["man_singer"],"tags":["rockstar"],"category":"People & Body","description":"man singer","unicode_version":""},{"emoji":"👩‍🎤","aliases":["woman_singer"],"tags":["rockstar"],"category":"People & Body","description":"woman singer","unicode_version":""},{"emoji":"🧑‍🎨","aliases":["artist"],"tags":[],"category":"People & Body","description":"artist","unicode_version":"12.1"},{"emoji":"👨‍🎨","aliases":["man_artist"],"tags":["painter"],"category":"People & Body","description":"man artist","unicode_version":""},{"emoji":"👩‍🎨","aliases":["woman_artist"],"tags":["painter"],"category":"People & Body","description":"woman artist","unicode_version":""},{"emoji":"🧑‍✈️","aliases":["pilot"],"tags":[],"category":"People & Body","description":"pilot","unicode_version":"12.1"},{"emoji":"👨‍✈️","aliases":["man_pilot"],"tags":[],"category":"People & Body","description":"man pilot","unicode_version":""},{"emoji":"👩‍✈️","aliases":["woman_pilot"],"tags":[],"category":"People & Body","description":"woman pilot","unicode_version":""},{"emoji":"🧑‍🚀","aliases":["astronaut"],"tags":[],"category":"People & Body","description":"astronaut","unicode_version":"12.1"},{"emoji":"👨‍🚀","aliases":["man_astronaut"],"tags":["space"],"category":"People & Body","description":"man astronaut","unicode_version":""},{"emoji":"👩‍🚀","aliases":["woman_astronaut"],"tags":["space"],"category":"People & Body","description":"woman astronaut","unicode_version":""},{"emoji":"🧑‍🚒","aliases":["firefighter"],"tags":[],"category":"People & Body","description":"firefighter","unicode_version":"12.1"},{"emoji":"👨‍🚒","aliases":["man_firefighter"],"tags":[],"category":"People & Body","description":"man firefighter","unicode_version":""},{"emoji":"👩‍🚒","aliases":["woman_firefighter"],"tags":[],"category":"People & Body","description":"woman firefighter","unicode_version":""},{"emoji":"👮","aliases":["police_officer","cop"],"tags":["law"],"category":"People & Body","description":"police officer","unicode_version":"6.0"},{"emoji":"👮‍♂️","aliases":["policeman"],"tags":["law","cop"],"category":"People & Body","description":"man police officer","unicode_version":"11.0"},{"emoji":"👮‍♀️","aliases":["policewoman"],"tags":["law","cop"],"category":"People & Body","description":"woman police officer","unicode_version":"6.0"},{"emoji":"🕵️","aliases":["detective"],"tags":["sleuth"],"category":"People & Body","description":"detective","unicode_version":"7.0"},{"emoji":"🕵️‍♂️","aliases":["male_detective"],"tags":["sleuth"],"category":"People & Body","description":"man detective","unicode_version":"11.0"},{"emoji":"🕵️‍♀️","aliases":["female_detective"],"tags":["sleuth"],"category":"People & Body","description":"woman detective","unicode_version":"6.0"},{"emoji":"💂","aliases":["guard"],"tags":[],"category":"People & Body","description":"guard","unicode_version":"6.0"},{"emoji":"💂‍♂️","aliases":["guardsman"],"tags":[],"category":"People & Body","description":"man guard","unicode_version":"11.0"},{"emoji":"💂‍♀️","aliases":["guardswoman"],"tags":[],"category":"People & Body","description":"woman guard","unicode_version":"6.0"},{"emoji":"🥷","aliases":["ninja"],"tags":[],"category":"People & Body","description":"ninja","unicode_version":"13.0"},{"emoji":"👷","aliases":["construction_worker"],"tags":["helmet"],"category":"People & Body","description":"construction worker","unicode_version":"6.0"},{"emoji":"👷‍♂️","aliases":["construction_worker_man"],"tags":["helmet"],"category":"People & Body","description":"man construction worker","unicode_version":"11.0"},{"emoji":"👷‍♀️","aliases":["construction_worker_woman"],"tags":["helmet"],"category":"People & Body","description":"woman construction worker","unicode_version":"6.0"},{"emoji":"🤴","aliases":["prince"],"tags":["crown","royal"],"category":"People & Body","description":"prince","unicode_version":"9.0"},{"emoji":"👸","aliases":["princess"],"tags":["crown","royal"],"category":"People & Body","description":"princess","unicode_version":"6.0"},{"emoji":"👳","aliases":["person_with_turban"],"tags":[],"category":"People & Body","description":"person wearing turban","unicode_version":"6.0"},{"emoji":"👳‍♂️","aliases":["man_with_turban"],"tags":[],"category":"People & Body","description":"man wearing turban","unicode_version":"11.0"},{"emoji":"👳‍♀️","aliases":["woman_with_turban"],"tags":[],"category":"People & Body","description":"woman wearing turban","unicode_version":"6.0"},{"emoji":"👲","aliases":["man_with_gua_pi_mao"],"tags":[],"category":"People & Body","description":"person with skullcap","unicode_version":"6.0"},{"emoji":"🧕","aliases":["woman_with_headscarf"],"tags":["hijab"],"category":"People & Body","description":"woman with headscarf","unicode_version":"11.0"},{"emoji":"🤵","aliases":["person_in_tuxedo"],"tags":["groom","marriage","wedding"],"category":"People & Body","description":"person in tuxedo","unicode_version":"9.0"},{"emoji":"🤵‍♂️","aliases":["man_in_tuxedo"],"tags":[],"category":"People & Body","description":"man in tuxedo","unicode_version":"13.0"},{"emoji":"🤵‍♀️","aliases":["woman_in_tuxedo"],"tags":[],"category":"People & Body","description":"woman in tuxedo","unicode_version":"13.0"},{"emoji":"👰","aliases":["person_with_veil"],"tags":["marriage","wedding"],"category":"People & Body","description":"person with veil","unicode_version":"6.0"},{"emoji":"👰‍♂️","aliases":["man_with_veil"],"tags":[],"category":"People & Body","description":"man with veil","unicode_version":"13.0"},{"emoji":"👰‍♀️","aliases":["woman_with_veil","bride_with_veil"],"tags":[],"category":"People & Body","description":"woman with veil","unicode_version":"13.0"},{"emoji":"🤰","aliases":["pregnant_woman"],"tags":[],"category":"People & Body","description":"pregnant woman","unicode_version":"9.0"},{"emoji":"🤱","aliases":["breast_feeding"],"tags":["nursing"],"category":"People & Body","description":"breast-feeding","unicode_version":"11.0"},{"emoji":"👩‍🍼","aliases":["woman_feeding_baby"],"tags":[],"category":"People & Body","description":"woman feeding baby","unicode_version":"13.0"},{"emoji":"👨‍🍼","aliases":["man_feeding_baby"],"tags":[],"category":"People & Body","description":"man feeding baby","unicode_version":"13.0"},{"emoji":"🧑‍🍼","aliases":["person_feeding_baby"],"tags":[],"category":"People & Body","description":"person feeding baby","unicode_version":"13.0"},{"emoji":"👼","aliases":["angel"],"tags":[],"category":"People & Body","description":"baby angel","unicode_version":"6.0"},{"emoji":"🎅","aliases":["santa"],"tags":["christmas"],"category":"People & Body","description":"Santa Claus","unicode_version":"6.0"},{"emoji":"🤶","aliases":["mrs_claus"],"tags":["santa"],"category":"People & Body","description":"Mrs. Claus","unicode_version":"9.0"},{"emoji":"🧑‍🎄","aliases":["mx_claus"],"tags":[],"category":"People & Body","description":"mx claus","unicode_version":"13.0"},{"emoji":"🦸","aliases":["superhero"],"tags":[],"category":"People & Body","description":"superhero","unicode_version":"11.0"},{"emoji":"🦸‍♂️","aliases":["superhero_man"],"tags":[],"category":"People & Body","description":"man superhero","unicode_version":"11.0"},{"emoji":"🦸‍♀️","aliases":["superhero_woman"],"tags":[],"category":"People & Body","description":"woman superhero","unicode_version":"11.0"},{"emoji":"🦹","aliases":["supervillain"],"tags":[],"category":"People & Body","description":"supervillain","unicode_version":"11.0"},{"emoji":"🦹‍♂️","aliases":["supervillain_man"],"tags":[],"category":"People & Body","description":"man supervillain","unicode_version":"11.0"},{"emoji":"🦹‍♀️","aliases":["supervillain_woman"],"tags":[],"category":"People & Body","description":"woman supervillain","unicode_version":"11.0"},{"emoji":"🧙","aliases":["mage"],"tags":["wizard"],"category":"People & Body","description":"mage","unicode_version":"11.0"},{"emoji":"🧙‍♂️","aliases":["mage_man"],"tags":["wizard"],"category":"People & Body","description":"man mage","unicode_version":"11.0"},{"emoji":"🧙‍♀️","aliases":["mage_woman"],"tags":["wizard"],"category":"People & Body","description":"woman mage","unicode_version":"11.0"},{"emoji":"🧚","aliases":["fairy"],"tags":[],"category":"People & Body","description":"fairy","unicode_version":"11.0"},{"emoji":"🧚‍♂️","aliases":["fairy_man"],"tags":[],"category":"People & Body","description":"man fairy","unicode_version":"11.0"},{"emoji":"🧚‍♀️","aliases":["fairy_woman"],"tags":[],"category":"People & Body","description":"woman fairy","unicode_version":"11.0"},{"emoji":"🧛","aliases":["vampire"],"tags":[],"category":"People & Body","description":"vampire","unicode_version":"11.0"},{"emoji":"🧛‍♂️","aliases":["vampire_man"],"tags":[],"category":"People & Body","description":"man vampire","unicode_version":"11.0"},{"emoji":"🧛‍♀️","aliases":["vampire_woman"],"tags":[],"category":"People & Body","description":"woman vampire","unicode_version":"11.0"},{"emoji":"🧜","aliases":["merperson"],"tags":[],"category":"People & Body","description":"merperson","unicode_version":"11.0"},{"emoji":"🧜‍♂️","aliases":["merman"],"tags":[],"category":"People & Body","description":"merman","unicode_version":"11.0"},{"emoji":"🧜‍♀️","aliases":["mermaid"],"tags":[],"category":"People & Body","description":"mermaid","unicode_version":"11.0"},{"emoji":"🧝","aliases":["elf"],"tags":[],"category":"People & Body","description":"elf","unicode_version":"11.0"},{"emoji":"🧝‍♂️","aliases":["elf_man"],"tags":[],"category":"People & Body","description":"man elf","unicode_version":"11.0"},{"emoji":"🧝‍♀️","aliases":["elf_woman"],"tags":[],"category":"People & Body","description":"woman elf","unicode_version":"11.0"},{"emoji":"🧞","aliases":["genie"],"tags":[],"category":"People & Body","description":"genie","unicode_version":"11.0"},{"emoji":"🧞‍♂️","aliases":["genie_man"],"tags":[],"category":"People & Body","description":"man genie","unicode_version":"11.0"},{"emoji":"🧞‍♀️","aliases":["genie_woman"],"tags":[],"category":"People & Body","description":"woman genie","unicode_version":"11.0"},{"emoji":"🧟","aliases":["zombie"],"tags":[],"category":"People & Body","description":"zombie","unicode_version":"11.0"},{"emoji":"🧟‍♂️","aliases":["zombie_man"],"tags":[],"category":"People & Body","description":"man zombie","unicode_version":"11.0"},{"emoji":"🧟‍♀️","aliases":["zombie_woman"],"tags":[],"category":"People & Body","description":"woman zombie","unicode_version":"11.0"},{"emoji":"💆","aliases":["massage"],"tags":["spa"],"category":"People & Body","description":"person getting massage","unicode_version":"6.0"},{"emoji":"💆‍♂️","aliases":["massage_man"],"tags":["spa"],"category":"People & Body","description":"man getting massage","unicode_version":"6.0"},{"emoji":"💆‍♀️","aliases":["massage_woman"],"tags":["spa"],"category":"People & Body","description":"woman getting massage","unicode_version":"11.0"},{"emoji":"💇","aliases":["haircut"],"tags":["beauty"],"category":"People & Body","description":"person getting haircut","unicode_version":"6.0"},{"emoji":"💇‍♂️","aliases":["haircut_man"],"tags":[],"category":"People & Body","description":"man getting haircut","unicode_version":"6.0"},{"emoji":"💇‍♀️","aliases":["haircut_woman"],"tags":[],"category":"People & Body","description":"woman getting haircut","unicode_version":"11.0"},{"emoji":"🚶","aliases":["walking"],"tags":[],"category":"People & Body","description":"person walking","unicode_version":"6.0"},{"emoji":"🚶‍♂️","aliases":["walking_man"],"tags":[],"category":"People & Body","description":"man walking","unicode_version":"11.0"},{"emoji":"🚶‍♀️","aliases":["walking_woman"],"tags":[],"category":"People & Body","description":"woman walking","unicode_version":"6.0"},{"emoji":"🧍","aliases":["standing_person"],"tags":[],"category":"People & Body","description":"person standing","unicode_version":"12.0"},{"emoji":"🧍‍♂️","aliases":["standing_man"],"tags":[],"category":"People & Body","description":"man standing","unicode_version":"12.0"},{"emoji":"🧍‍♀️","aliases":["standing_woman"],"tags":[],"category":"People & Body","description":"woman standing","unicode_version":"12.0"},{"emoji":"🧎","aliases":["kneeling_person"],"tags":[],"category":"People & Body","description":"person kneeling","unicode_version":"12.0"},{"emoji":"🧎‍♂️","aliases":["kneeling_man"],"tags":[],"category":"People & Body","description":"man kneeling","unicode_version":"12.0"},{"emoji":"🧎‍♀️","aliases":["kneeling_woman"],"tags":[],"category":"People & Body","description":"woman kneeling","unicode_version":"12.0"},{"emoji":"🧑‍🦯","aliases":["person_with_probing_cane"],"tags":[],"category":"People & Body","description":"person with white cane","unicode_version":"12.1"},{"emoji":"👨‍🦯","aliases":["man_with_probing_cane"],"tags":[],"category":"People & Body","description":"man with white cane","unicode_version":"12.0"},{"emoji":"👩‍🦯","aliases":["woman_with_probing_cane"],"tags":[],"category":"People & Body","description":"woman with white cane","unicode_version":"12.0"},{"emoji":"🧑‍🦼","aliases":["person_in_motorized_wheelchair"],"tags":[],"category":"People & Body","description":"person in motorized wheelchair","unicode_version":"12.1"},{"emoji":"👨‍🦼","aliases":["man_in_motorized_wheelchair"],"tags":[],"category":"People & Body","description":"man in motorized wheelchair","unicode_version":"12.0"},{"emoji":"👩‍🦼","aliases":["woman_in_motorized_wheelchair"],"tags":[],"category":"People & Body","description":"woman in motorized wheelchair","unicode_version":"12.0"},{"emoji":"🧑‍🦽","aliases":["person_in_manual_wheelchair"],"tags":[],"category":"People & Body","description":"person in manual wheelchair","unicode_version":"12.1"},{"emoji":"👨‍🦽","aliases":["man_in_manual_wheelchair"],"tags":[],"category":"People & Body","description":"man in manual wheelchair","unicode_version":"12.0"},{"emoji":"👩‍🦽","aliases":["woman_in_manual_wheelchair"],"tags":[],"category":"People & Body","description":"woman in manual wheelchair","unicode_version":"12.0"},{"emoji":"🏃","aliases":["runner","running"],"tags":["exercise","workout","marathon"],"category":"People & Body","description":"person running","unicode_version":"6.0"},{"emoji":"🏃‍♂️","aliases":["running_man"],"tags":["exercise","workout","marathon"],"category":"People & Body","description":"man running","unicode_version":"11.0"},{"emoji":"🏃‍♀️","aliases":["running_woman"],"tags":["exercise","workout","marathon"],"category":"People & Body","description":"woman running","unicode_version":"6.0"},{"emoji":"💃","aliases":["woman_dancing","dancer"],"tags":["dress"],"category":"People & Body","description":"woman dancing","unicode_version":"6.0"},{"emoji":"🕺","aliases":["man_dancing"],"tags":["dancer"],"category":"People & Body","description":"man dancing","unicode_version":"9.0"},{"emoji":"🕴️","aliases":["business_suit_levitating"],"tags":[],"category":"People & Body","description":"person in suit levitating","unicode_version":"7.0"},{"emoji":"👯","aliases":["dancers"],"tags":["bunny"],"category":"People & Body","description":"people with bunny ears","unicode_version":"6.0"},{"emoji":"👯‍♂️","aliases":["dancing_men"],"tags":["bunny"],"category":"People & Body","description":"men with bunny ears","unicode_version":"6.0"},{"emoji":"👯‍♀️","aliases":["dancing_women"],"tags":["bunny"],"category":"People & Body","description":"women with bunny ears","unicode_version":"11.0"},{"emoji":"🧖","aliases":["sauna_person"],"tags":["steamy"],"category":"People & Body","description":"person in steamy room","unicode_version":"11.0"},{"emoji":"🧖‍♂️","aliases":["sauna_man"],"tags":["steamy"],"category":"People & Body","description":"man in steamy room","unicode_version":"11.0"},{"emoji":"🧖‍♀️","aliases":["sauna_woman"],"tags":["steamy"],"category":"People & Body","description":"woman in steamy room","unicode_version":"11.0"},{"emoji":"🧗","aliases":["climbing"],"tags":["bouldering"],"category":"People & Body","description":"person climbing","unicode_version":"11.0"},{"emoji":"🧗‍♂️","aliases":["climbing_man"],"tags":["bouldering"],"category":"People & Body","description":"man climbing","unicode_version":"11.0"},{"emoji":"🧗‍♀️","aliases":["climbing_woman"],"tags":["bouldering"],"category":"People & Body","description":"woman climbing","unicode_version":"11.0"},{"emoji":"🤺","aliases":["person_fencing"],"tags":[],"category":"People & Body","description":"person fencing","unicode_version":"9.0"},{"emoji":"🏇","aliases":["horse_racing"],"tags":[],"category":"People & Body","description":"horse racing","unicode_version":"6.0"},{"emoji":"⛷️","aliases":["skier"],"tags":[],"category":"People & Body","description":"skier","unicode_version":"5.2"},{"emoji":"🏂","aliases":["snowboarder"],"tags":[],"category":"People & Body","description":"snowboarder","unicode_version":"6.0"},{"emoji":"🏌️","aliases":["golfing"],"tags":[],"category":"People & Body","description":"person golfing","unicode_version":"7.0"},{"emoji":"🏌️‍♂️","aliases":["golfing_man"],"tags":[],"category":"People & Body","description":"man golfing","unicode_version":"11.0"},{"emoji":"🏌️‍♀️","aliases":["golfing_woman"],"tags":[],"category":"People & Body","description":"woman golfing","unicode_version":""},{"emoji":"🏄","aliases":["surfer"],"tags":[],"category":"People & Body","description":"person surfing","unicode_version":"6.0"},{"emoji":"🏄‍♂️","aliases":["surfing_man"],"tags":[],"category":"People & Body","description":"man surfing","unicode_version":"11.0"},{"emoji":"🏄‍♀️","aliases":["surfing_woman"],"tags":[],"category":"People & Body","description":"woman surfing","unicode_version":"7.0"},{"emoji":"🚣","aliases":["rowboat"],"tags":[],"category":"People & Body","description":"person rowing boat","unicode_version":"6.0"},{"emoji":"🚣‍♂️","aliases":["rowing_man"],"tags":[],"category":"People & Body","description":"man rowing boat","unicode_version":"11.0"},{"emoji":"🚣‍♀️","aliases":["rowing_woman"],"tags":[],"category":"People & Body","description":"woman rowing boat","unicode_version":"6.0"},{"emoji":"🏊","aliases":["swimmer"],"tags":[],"category":"People & Body","description":"person swimming","unicode_version":"6.0"},{"emoji":"🏊‍♂️","aliases":["swimming_man"],"tags":[],"category":"People & Body","description":"man swimming","unicode_version":"11.0"},{"emoji":"🏊‍♀️","aliases":["swimming_woman"],"tags":[],"category":"People & Body","description":"woman swimming","unicode_version":"6.0"},{"emoji":"⛹️","aliases":["bouncing_ball_person"],"tags":["basketball"],"category":"People & Body","description":"person bouncing ball","unicode_version":"5.2"},{"emoji":"⛹️‍♂️","aliases":["bouncing_ball_man","basketball_man"],"tags":[],"category":"People & Body","description":"man bouncing ball","unicode_version":"11.0"},{"emoji":"⛹️‍♀️","aliases":["bouncing_ball_woman","basketball_woman"],"tags":[],"category":"People & Body","description":"woman bouncing ball","unicode_version":"7.0"},{"emoji":"🏋️","aliases":["weight_lifting"],"tags":["gym","workout"],"category":"People & Body","description":"person lifting weights","unicode_version":"7.0"},{"emoji":"🏋️‍♂️","aliases":["weight_lifting_man"],"tags":["gym","workout"],"category":"People & Body","description":"man lifting weights","unicode_version":"11.0"},{"emoji":"🏋️‍♀️","aliases":["weight_lifting_woman"],"tags":["gym","workout"],"category":"People & Body","description":"woman lifting weights","unicode_version":"6.0"},{"emoji":"🚴","aliases":["bicyclist"],"tags":[],"category":"People & Body","description":"person biking","unicode_version":"6.0"},{"emoji":"🚴‍♂️","aliases":["biking_man"],"tags":[],"category":"People & Body","description":"man biking","unicode_version":"11.0"},{"emoji":"🚴‍♀️","aliases":["biking_woman"],"tags":[],"category":"People & Body","description":"woman biking","unicode_version":"6.0"},{"emoji":"🚵","aliases":["mountain_bicyclist"],"tags":[],"category":"People & Body","description":"person mountain biking","unicode_version":"6.0"},{"emoji":"🚵‍♂️","aliases":["mountain_biking_man"],"tags":[],"category":"People & Body","description":"man mountain biking","unicode_version":"11.0"},{"emoji":"🚵‍♀️","aliases":["mountain_biking_woman"],"tags":[],"category":"People & Body","description":"woman mountain biking","unicode_version":"6.0"},{"emoji":"🤸","aliases":["cartwheeling"],"tags":[],"category":"People & Body","description":"person cartwheeling","unicode_version":"11.0"},{"emoji":"🤸‍♂️","aliases":["man_cartwheeling"],"tags":[],"category":"People & Body","description":"man cartwheeling","unicode_version":""},{"emoji":"🤸‍♀️","aliases":["woman_cartwheeling"],"tags":[],"category":"People & Body","description":"woman cartwheeling","unicode_version":""},{"emoji":"🤼","aliases":["wrestling"],"tags":[],"category":"People & Body","description":"people wrestling","unicode_version":"11.0"},{"emoji":"🤼‍♂️","aliases":["men_wrestling"],"tags":[],"category":"People & Body","description":"men wrestling","unicode_version":"9.0"},{"emoji":"🤼‍♀️","aliases":["women_wrestling"],"tags":[],"category":"People & Body","description":"women wrestling","unicode_version":"9.0"},{"emoji":"🤽","aliases":["water_polo"],"tags":[],"category":"People & Body","description":"person playing water polo","unicode_version":"11.0"},{"emoji":"🤽‍♂️","aliases":["man_playing_water_polo"],"tags":[],"category":"People & Body","description":"man playing water polo","unicode_version":"9.0"},{"emoji":"🤽‍♀️","aliases":["woman_playing_water_polo"],"tags":[],"category":"People & Body","description":"woman playing water polo","unicode_version":"9.0"},{"emoji":"🤾","aliases":["handball_person"],"tags":[],"category":"People & Body","description":"person playing handball","unicode_version":"11.0"},{"emoji":"🤾‍♂️","aliases":["man_playing_handball"],"tags":[],"category":"People & Body","description":"man playing handball","unicode_version":"9.0"},{"emoji":"🤾‍♀️","aliases":["woman_playing_handball"],"tags":[],"category":"People & Body","description":"woman playing handball","unicode_version":"9.0"},{"emoji":"🤹","aliases":["juggling_person"],"tags":[],"category":"People & Body","description":"person juggling","unicode_version":"11.0"},{"emoji":"🤹‍♂️","aliases":["man_juggling"],"tags":[],"category":"People & Body","description":"man juggling","unicode_version":"9.0"},{"emoji":"🤹‍♀️","aliases":["woman_juggling"],"tags":[],"category":"People & Body","description":"woman juggling","unicode_version":"9.0"},{"emoji":"🧘","aliases":["lotus_position"],"tags":["meditation"],"category":"People & Body","description":"person in lotus position","unicode_version":"11.0"},{"emoji":"🧘‍♂️","aliases":["lotus_position_man"],"tags":["meditation"],"category":"People & Body","description":"man in lotus position","unicode_version":"11.0"},{"emoji":"🧘‍♀️","aliases":["lotus_position_woman"],"tags":["meditation"],"category":"People & Body","description":"woman in lotus position","unicode_version":"11.0"},{"emoji":"🛀","aliases":["bath"],"tags":["shower"],"category":"People & Body","description":"person taking bath","unicode_version":"6.0"},{"emoji":"🛌","aliases":["sleeping_bed"],"tags":[],"category":"People & Body","description":"person in bed","unicode_version":"7.0"},{"emoji":"🧑‍🤝‍🧑","aliases":["people_holding_hands"],"tags":["couple","date"],"category":"People & Body","description":"people holding hands","unicode_version":"12.0"},{"emoji":"👭","aliases":["two_women_holding_hands"],"tags":["couple","date"],"category":"People & Body","description":"women holding hands","unicode_version":"6.0"},{"emoji":"👫","aliases":["couple"],"tags":["date"],"category":"People & Body","description":"woman and man holding hands","unicode_version":"6.0"},{"emoji":"👬","aliases":["two_men_holding_hands"],"tags":["couple","date"],"category":"People & Body","description":"men holding hands","unicode_version":"6.0"},{"emoji":"💏","aliases":["couplekiss"],"tags":[],"category":"People & Body","description":"kiss","unicode_version":"6.0"},{"emoji":"👩‍❤️‍💋‍👨","aliases":["couplekiss_man_woman"],"tags":[],"category":"People & Body","description":"kiss: woman, man","unicode_version":"11.0"},{"emoji":"👨‍❤️‍💋‍👨","aliases":["couplekiss_man_man"],"tags":[],"category":"People & Body","description":"kiss: man, man","unicode_version":"6.0"},{"emoji":"👩‍❤️‍💋‍👩","aliases":["couplekiss_woman_woman"],"tags":[],"category":"People & Body","description":"kiss: woman, woman","unicode_version":"6.0"},{"emoji":"💑","aliases":["couple_with_heart"],"tags":[],"category":"People & Body","description":"couple with heart","unicode_version":"6.0"},{"emoji":"👩‍❤️‍👨","aliases":["couple_with_heart_woman_man"],"tags":[],"category":"People & Body","description":"couple with heart: woman, man","unicode_version":"11.0"},{"emoji":"👨‍❤️‍👨","aliases":["couple_with_heart_man_man"],"tags":[],"category":"People & Body","description":"couple with heart: man, man","unicode_version":"6.0"},{"emoji":"👩‍❤️‍👩","aliases":["couple_with_heart_woman_woman"],"tags":[],"category":"People & Body","description":"couple with heart: woman, woman","unicode_version":"6.0"},{"emoji":"👪","aliases":["family"],"tags":["home","parents","child"],"category":"People & Body","description":"family","unicode_version":"6.0"},{"emoji":"👨‍👩‍👦","aliases":["family_man_woman_boy"],"tags":[],"category":"People & Body","description":"family: man, woman, boy","unicode_version":"11.0"},{"emoji":"👨‍👩‍👧","aliases":["family_man_woman_girl"],"tags":[],"category":"People & Body","description":"family: man, woman, girl","unicode_version":"6.0"},{"emoji":"👨‍👩‍👧‍👦","aliases":["family_man_woman_girl_boy"],"tags":[],"category":"People & Body","description":"family: man, woman, girl, boy","unicode_version":"6.0"},{"emoji":"👨‍👩‍👦‍👦","aliases":["family_man_woman_boy_boy"],"tags":[],"category":"People & Body","description":"family: man, woman, boy, boy","unicode_version":"6.0"},{"emoji":"👨‍👩‍👧‍👧","aliases":["family_man_woman_girl_girl"],"tags":[],"category":"People & Body","description":"family: man, woman, girl, girl","unicode_version":"6.0"},{"emoji":"👨‍👨‍👦","aliases":["family_man_man_boy"],"tags":[],"category":"People & Body","description":"family: man, man, boy","unicode_version":"6.0"},{"emoji":"👨‍👨‍👧","aliases":["family_man_man_girl"],"tags":[],"category":"People & Body","description":"family: man, man, girl","unicode_version":"6.0"},{"emoji":"👨‍👨‍👧‍👦","aliases":["family_man_man_girl_boy"],"tags":[],"category":"People & Body","description":"family: man, man, girl, boy","unicode_version":"6.0"},{"emoji":"👨‍👨‍👦‍👦","aliases":["family_man_man_boy_boy"],"tags":[],"category":"People & Body","description":"family: man, man, boy, boy","unicode_version":"6.0"},{"emoji":"👨‍👨‍👧‍👧","aliases":["family_man_man_girl_girl"],"tags":[],"category":"People & Body","description":"family: man, man, girl, girl","unicode_version":"6.0"},{"emoji":"👩‍👩‍👦","aliases":["family_woman_woman_boy"],"tags":[],"category":"People & Body","description":"family: woman, woman, boy","unicode_version":"6.0"},{"emoji":"👩‍👩‍👧","aliases":["family_woman_woman_girl"],"tags":[],"category":"People & Body","description":"family: woman, woman, girl","unicode_version":"6.0"},{"emoji":"👩‍👩‍👧‍👦","aliases":["family_woman_woman_girl_boy"],"tags":[],"category":"People & Body","description":"family: woman, woman, girl, boy","unicode_version":"6.0"},{"emoji":"👩‍👩‍👦‍👦","aliases":["family_woman_woman_boy_boy"],"tags":[],"category":"People & Body","description":"family: woman, woman, boy, boy","unicode_version":"6.0"},{"emoji":"👩‍👩‍👧‍👧","aliases":["family_woman_woman_girl_girl"],"tags":[],"category":"People & Body","description":"family: woman, woman, girl, girl","unicode_version":"6.0"},{"emoji":"👨‍👦","aliases":["family_man_boy"],"tags":[],"category":"People & Body","description":"family: man, boy","unicode_version":"6.0"},{"emoji":"👨‍👦‍👦","aliases":["family_man_boy_boy"],"tags":[],"category":"People & Body","description":"family: man, boy, boy","unicode_version":"6.0"},{"emoji":"👨‍👧","aliases":["family_man_girl"],"tags":[],"category":"People & Body","description":"family: man, girl","unicode_version":"6.0"},{"emoji":"👨‍👧‍👦","aliases":["family_man_girl_boy"],"tags":[],"category":"People & Body","description":"family: man, girl, boy","unicode_version":"6.0"},{"emoji":"👨‍👧‍👧","aliases":["family_man_girl_girl"],"tags":[],"category":"People & Body","description":"family: man, girl, girl","unicode_version":"6.0"},{"emoji":"👩‍👦","aliases":["family_woman_boy"],"tags":[],"category":"People & Body","description":"family: woman, boy","unicode_version":"6.0"},{"emoji":"👩‍👦‍👦","aliases":["family_woman_boy_boy"],"tags":[],"category":"People & Body","description":"family: woman, boy, boy","unicode_version":"6.0"},{"emoji":"👩‍👧","aliases":["family_woman_girl"],"tags":[],"category":"People & Body","description":"family: woman, girl","unicode_version":"6.0"},{"emoji":"👩‍👧‍👦","aliases":["family_woman_girl_boy"],"tags":[],"category":"People & Body","description":"family: woman, girl, boy","unicode_version":"6.0"},{"emoji":"👩‍👧‍👧","aliases":["family_woman_girl_girl"],"tags":[],"category":"People & Body","description":"family: woman, girl, girl","unicode_version":"6.0"},{"emoji":"🗣️","aliases":["speaking_head"],"tags":[],"category":"People & Body","description":"speaking head","unicode_version":"7.0"},{"emoji":"👤","aliases":["bust_in_silhouette"],"tags":["user"],"category":"People & Body","description":"bust in silhouette","unicode_version":"6.0"},{"emoji":"👥","aliases":["busts_in_silhouette"],"tags":["users","group","team"],"category":"People & Body","description":"busts in silhouette","unicode_version":"6.0"},{"emoji":"🫂","aliases":["people_hugging"],"tags":[],"category":"People & Body","description":"people hugging","unicode_version":"13.0"},{"emoji":"👣","aliases":["footprints"],"tags":["feet","tracks"],"category":"People & Body","description":"footprints","unicode_version":"6.0"},{"emoji":"🐵","aliases":["monkey_face"],"tags":[],"category":"Animals & Nature","description":"monkey face","unicode_version":"6.0"},{"emoji":"🐒","aliases":["monkey"],"tags":[],"category":"Animals & Nature","description":"monkey","unicode_version":"6.0"},{"emoji":"🦍","aliases":["gorilla"],"tags":[],"category":"Animals & Nature","description":"gorilla","unicode_version":"9.0"},{"emoji":"🦧","aliases":["orangutan"],"tags":[],"category":"Animals & Nature","description":"orangutan","unicode_version":"12.0"},{"emoji":"🐶","aliases":["dog"],"tags":["pet"],"category":"Animals & Nature","description":"dog face","unicode_version":"6.0"},{"emoji":"🐕","aliases":["dog2"],"tags":[],"category":"Animals & Nature","description":"dog","unicode_version":"6.0"},{"emoji":"🦮","aliases":["guide_dog"],"tags":[],"category":"Animals & Nature","description":"guide dog","unicode_version":"12.0"},{"emoji":"🐕‍🦺","aliases":["service_dog"],"tags":[],"category":"Animals & Nature","description":"service dog","unicode_version":"12.0"},{"emoji":"🐩","aliases":["poodle"],"tags":["dog"],"category":"Animals & Nature","description":"poodle","unicode_version":"6.0"},{"emoji":"🐺","aliases":["wolf"],"tags":[],"category":"Animals & Nature","description":"wolf","unicode_version":"6.0"},{"emoji":"🦊","aliases":["fox_face"],"tags":[],"category":"Animals & Nature","description":"fox","unicode_version":"9.0"},{"emoji":"🦝","aliases":["raccoon"],"tags":[],"category":"Animals & Nature","description":"raccoon","unicode_version":"11.0"},{"emoji":"🐱","aliases":["cat"],"tags":["pet"],"category":"Animals & Nature","description":"cat face","unicode_version":"6.0"},{"emoji":"🐈","aliases":["cat2"],"tags":[],"category":"Animals & Nature","description":"cat","unicode_version":"6.0"},{"emoji":"🐈‍⬛","aliases":["black_cat"],"tags":[],"category":"Animals & Nature","description":"black cat","unicode_version":"13.0"},{"emoji":"🦁","aliases":["lion"],"tags":[],"category":"Animals & Nature","description":"lion","unicode_version":"8.0"},{"emoji":"🐯","aliases":["tiger"],"tags":[],"category":"Animals & Nature","description":"tiger face","unicode_version":"6.0"},{"emoji":"🐅","aliases":["tiger2"],"tags":[],"category":"Animals & Nature","description":"tiger","unicode_version":"6.0"},{"emoji":"🐆","aliases":["leopard"],"tags":[],"category":"Animals & Nature","description":"leopard","unicode_version":"6.0"},{"emoji":"🐴","aliases":["horse"],"tags":[],"category":"Animals & Nature","description":"horse face","unicode_version":"6.0"},{"emoji":"🐎","aliases":["racehorse"],"tags":["speed"],"category":"Animals & Nature","description":"horse","unicode_version":"6.0"},{"emoji":"🦄","aliases":["unicorn"],"tags":[],"category":"Animals & Nature","description":"unicorn","unicode_version":"8.0"},{"emoji":"🦓","aliases":["zebra"],"tags":[],"category":"Animals & Nature","description":"zebra","unicode_version":"11.0"},{"emoji":"🦌","aliases":["deer"],"tags":[],"category":"Animals & Nature","description":"deer","unicode_version":"9.0"},{"emoji":"🦬","aliases":["bison"],"tags":[],"category":"Animals & Nature","description":"bison","unicode_version":"13.0"},{"emoji":"🐮","aliases":["cow"],"tags":[],"category":"Animals & Nature","description":"cow face","unicode_version":"6.0"},{"emoji":"🐂","aliases":["ox"],"tags":[],"category":"Animals & Nature","description":"ox","unicode_version":"6.0"},{"emoji":"🐃","aliases":["water_buffalo"],"tags":[],"category":"Animals & Nature","description":"water buffalo","unicode_version":"6.0"},{"emoji":"🐄","aliases":["cow2"],"tags":[],"category":"Animals & Nature","description":"cow","unicode_version":"6.0"},{"emoji":"🐷","aliases":["pig"],"tags":[],"category":"Animals & Nature","description":"pig face","unicode_version":"6.0"},{"emoji":"🐖","aliases":["pig2"],"tags":[],"category":"Animals & Nature","description":"pig","unicode_version":"6.0"},{"emoji":"🐗","aliases":["boar"],"tags":[],"category":"Animals & Nature","description":"boar","unicode_version":"6.0"},{"emoji":"🐽","aliases":["pig_nose"],"tags":[],"category":"Animals & Nature","description":"pig nose","unicode_version":"6.0"},{"emoji":"🐏","aliases":["ram"],"tags":[],"category":"Animals & Nature","description":"ram","unicode_version":"6.0"},{"emoji":"🐑","aliases":["sheep"],"tags":[],"category":"Animals & Nature","description":"ewe","unicode_version":"6.0"},{"emoji":"🐐","aliases":["goat"],"tags":[],"category":"Animals & Nature","description":"goat","unicode_version":"6.0"},{"emoji":"🐪","aliases":["dromedary_camel"],"tags":["desert"],"category":"Animals & Nature","description":"camel","unicode_version":"6.0"},{"emoji":"🐫","aliases":["camel"],"tags":[],"category":"Animals & Nature","description":"two-hump camel","unicode_version":"6.0"},{"emoji":"🦙","aliases":["llama"],"tags":[],"category":"Animals & Nature","description":"llama","unicode_version":"11.0"},{"emoji":"🦒","aliases":["giraffe"],"tags":[],"category":"Animals & Nature","description":"giraffe","unicode_version":"11.0"},{"emoji":"🐘","aliases":["elephant"],"tags":[],"category":"Animals & Nature","description":"elephant","unicode_version":"6.0"},{"emoji":"🦣","aliases":["mammoth"],"tags":[],"category":"Animals & Nature","description":"mammoth","unicode_version":"13.0"},{"emoji":"🦏","aliases":["rhinoceros"],"tags":[],"category":"Animals & Nature","description":"rhinoceros","unicode_version":"9.0"},{"emoji":"🦛","aliases":["hippopotamus"],"tags":[],"category":"Animals & Nature","description":"hippopotamus","unicode_version":"11.0"},{"emoji":"🐭","aliases":["mouse"],"tags":[],"category":"Animals & Nature","description":"mouse face","unicode_version":"6.0"},{"emoji":"🐁","aliases":["mouse2"],"tags":[],"category":"Animals & Nature","description":"mouse","unicode_version":"6.0"},{"emoji":"🐀","aliases":["rat"],"tags":[],"category":"Animals & Nature","description":"rat","unicode_version":"6.0"},{"emoji":"🐹","aliases":["hamster"],"tags":["pet"],"category":"Animals & Nature","description":"hamster","unicode_version":"6.0"},{"emoji":"🐰","aliases":["rabbit"],"tags":["bunny"],"category":"Animals & Nature","description":"rabbit face","unicode_version":"6.0"},{"emoji":"🐇","aliases":["rabbit2"],"tags":[],"category":"Animals & Nature","description":"rabbit","unicode_version":"6.0"},{"emoji":"🐿️","aliases":["chipmunk"],"tags":[],"category":"Animals & Nature","description":"chipmunk","unicode_version":"7.0"},{"emoji":"🦫","aliases":["beaver"],"tags":[],"category":"Animals & Nature","description":"beaver","unicode_version":"13.0"},{"emoji":"🦔","aliases":["hedgehog"],"tags":[],"category":"Animals & Nature","description":"hedgehog","unicode_version":"11.0"},{"emoji":"🦇","aliases":["bat"],"tags":[],"category":"Animals & Nature","description":"bat","unicode_version":"9.0"},{"emoji":"🐻","aliases":["bear"],"tags":[],"category":"Animals & Nature","description":"bear","unicode_version":"6.0"},{"emoji":"🐻‍❄️","aliases":["polar_bear"],"tags":[],"category":"Animals & Nature","description":"polar bear","unicode_version":"13.0"},{"emoji":"🐨","aliases":["koala"],"tags":[],"category":"Animals & Nature","description":"koala","unicode_version":"6.0"},{"emoji":"🐼","aliases":["panda_face"],"tags":[],"category":"Animals & Nature","description":"panda","unicode_version":"6.0"},{"emoji":"🦥","aliases":["sloth"],"tags":[],"category":"Animals & Nature","description":"sloth","unicode_version":"12.0"},{"emoji":"🦦","aliases":["otter"],"tags":[],"category":"Animals & Nature","description":"otter","unicode_version":"12.0"},{"emoji":"🦨","aliases":["skunk"],"tags":[],"category":"Animals & Nature","description":"skunk","unicode_version":"12.0"},{"emoji":"🦘","aliases":["kangaroo"],"tags":[],"category":"Animals & Nature","description":"kangaroo","unicode_version":"11.0"},{"emoji":"🦡","aliases":["badger"],"tags":[],"category":"Animals & Nature","description":"badger","unicode_version":"11.0"},{"emoji":"🐾","aliases":["feet","paw_prints"],"tags":[],"category":"Animals & Nature","description":"paw prints","unicode_version":"6.0"},{"emoji":"🦃","aliases":["turkey"],"tags":["thanksgiving"],"category":"Animals & Nature","description":"turkey","unicode_version":"8.0"},{"emoji":"🐔","aliases":["chicken"],"tags":[],"category":"Animals & Nature","description":"chicken","unicode_version":"6.0"},{"emoji":"🐓","aliases":["rooster"],"tags":[],"category":"Animals & Nature","description":"rooster","unicode_version":"6.0"},{"emoji":"🐣","aliases":["hatching_chick"],"tags":[],"category":"Animals & Nature","description":"hatching chick","unicode_version":"6.0"},{"emoji":"🐤","aliases":["baby_chick"],"tags":[],"category":"Animals & Nature","description":"baby chick","unicode_version":"6.0"},{"emoji":"🐥","aliases":["hatched_chick"],"tags":[],"category":"Animals & Nature","description":"front-facing baby chick","unicode_version":"6.0"},{"emoji":"🐦","aliases":["bird"],"tags":[],"category":"Animals & Nature","description":"bird","unicode_version":"6.0"},{"emoji":"🐧","aliases":["penguin"],"tags":[],"category":"Animals & Nature","description":"penguin","unicode_version":"6.0"},{"emoji":"🕊️","aliases":["dove"],"tags":["peace"],"category":"Animals & Nature","description":"dove","unicode_version":"7.0"},{"emoji":"🦅","aliases":["eagle"],"tags":[],"category":"Animals & Nature","description":"eagle","unicode_version":"9.0"},{"emoji":"🦆","aliases":["duck"],"tags":[],"category":"Animals & Nature","description":"duck","unicode_version":"9.0"},{"emoji":"🦢","aliases":["swan"],"tags":[],"category":"Animals & Nature","description":"swan","unicode_version":"11.0"},{"emoji":"🦉","aliases":["owl"],"tags":[],"category":"Animals & Nature","description":"owl","unicode_version":"9.0"},{"emoji":"🦤","aliases":["dodo"],"tags":[],"category":"Animals & Nature","description":"dodo","unicode_version":"13.0"},{"emoji":"🪶","aliases":["feather"],"tags":[],"category":"Animals & Nature","description":"feather","unicode_version":"13.0"},{"emoji":"🦩","aliases":["flamingo"],"tags":[],"category":"Animals & Nature","description":"flamingo","unicode_version":"12.0"},{"emoji":"🦚","aliases":["peacock"],"tags":[],"category":"Animals & Nature","description":"peacock","unicode_version":"11.0"},{"emoji":"🦜","aliases":["parrot"],"tags":[],"category":"Animals & Nature","description":"parrot","unicode_version":"11.0"},{"emoji":"🐸","aliases":["frog"],"tags":[],"category":"Animals & Nature","description":"frog","unicode_version":"6.0"},{"emoji":"🐊","aliases":["crocodile"],"tags":[],"category":"Animals & Nature","description":"crocodile","unicode_version":"6.0"},{"emoji":"🐢","aliases":["turtle"],"tags":["slow"],"category":"Animals & Nature","description":"turtle","unicode_version":"6.0"},{"emoji":"🦎","aliases":["lizard"],"tags":[],"category":"Animals & Nature","description":"lizard","unicode_version":"9.0"},{"emoji":"🐍","aliases":["snake"],"tags":[],"category":"Animals & Nature","description":"snake","unicode_version":"6.0"},{"emoji":"🐲","aliases":["dragon_face"],"tags":[],"category":"Animals & Nature","description":"dragon face","unicode_version":"6.0"},{"emoji":"🐉","aliases":["dragon"],"tags":[],"category":"Animals & Nature","description":"dragon","unicode_version":"6.0"},{"emoji":"🦕","aliases":["sauropod"],"tags":["dinosaur"],"category":"Animals & Nature","description":"sauropod","unicode_version":"11.0"},{"emoji":"🦖","aliases":["t-rex"],"tags":["dinosaur"],"category":"Animals & Nature","description":"T-Rex","unicode_version":"11.0"},{"emoji":"🐳","aliases":["whale"],"tags":["sea"],"category":"Animals & Nature","description":"spouting whale","unicode_version":"6.0"},{"emoji":"🐋","aliases":["whale2"],"tags":[],"category":"Animals & Nature","description":"whale","unicode_version":"6.0"},{"emoji":"🐬","aliases":["dolphin","flipper"],"tags":[],"category":"Animals & Nature","description":"dolphin","unicode_version":"6.0"},{"emoji":"🦭","aliases":["seal"],"tags":[],"category":"Animals & Nature","description":"seal","unicode_version":"13.0"},{"emoji":"🐟","aliases":["fish"],"tags":[],"category":"Animals & Nature","description":"fish","unicode_version":"6.0"},{"emoji":"🐠","aliases":["tropical_fish"],"tags":[],"category":"Animals & Nature","description":"tropical fish","unicode_version":"6.0"},{"emoji":"🐡","aliases":["blowfish"],"tags":[],"category":"Animals & Nature","description":"blowfish","unicode_version":"6.0"},{"emoji":"🦈","aliases":["shark"],"tags":[],"category":"Animals & Nature","description":"shark","unicode_version":"9.0"},{"emoji":"🐙","aliases":["octopus"],"tags":[],"category":"Animals & Nature","description":"octopus","unicode_version":"6.0"},{"emoji":"🐚","aliases":["shell"],"tags":["sea","beach"],"category":"Animals & Nature","description":"spiral shell","unicode_version":"6.0"},{"emoji":"🐌","aliases":["snail"],"tags":["slow"],"category":"Animals & Nature","description":"snail","unicode_version":"6.0"},{"emoji":"🦋","aliases":["butterfly"],"tags":[],"category":"Animals & Nature","description":"butterfly","unicode_version":"9.0"},{"emoji":"🐛","aliases":["bug"],"tags":[],"category":"Animals & Nature","description":"bug","unicode_version":"6.0"},{"emoji":"🐜","aliases":["ant"],"tags":[],"category":"Animals & Nature","description":"ant","unicode_version":"6.0"},{"emoji":"🐝","aliases":["bee","honeybee"],"tags":[],"category":"Animals & Nature","description":"honeybee","unicode_version":"6.0"},{"emoji":"🪲","aliases":["beetle"],"tags":[],"category":"Animals & Nature","description":"beetle","unicode_version":"13.0"},{"emoji":"🐞","aliases":["lady_beetle"],"tags":["bug"],"category":"Animals & Nature","description":"lady beetle","unicode_version":"6.0"},{"emoji":"🦗","aliases":["cricket"],"tags":[],"category":"Animals & Nature","description":"cricket","unicode_version":"11.0"},{"emoji":"🪳","aliases":["cockroach"],"tags":[],"category":"Animals & Nature","description":"cockroach","unicode_version":"13.0"},{"emoji":"🕷️","aliases":["spider"],"tags":[],"category":"Animals & Nature","description":"spider","unicode_version":"7.0"},{"emoji":"🕸️","aliases":["spider_web"],"tags":[],"category":"Animals & Nature","description":"spider web","unicode_version":"7.0"},{"emoji":"🦂","aliases":["scorpion"],"tags":[],"category":"Animals & Nature","description":"scorpion","unicode_version":"8.0"},{"emoji":"🦟","aliases":["mosquito"],"tags":[],"category":"Animals & Nature","description":"mosquito","unicode_version":"11.0"},{"emoji":"🪰","aliases":["fly"],"tags":[],"category":"Animals & Nature","description":"fly","unicode_version":"13.0"},{"emoji":"🪱","aliases":["worm"],"tags":[],"category":"Animals & Nature","description":"worm","unicode_version":"13.0"},{"emoji":"🦠","aliases":["microbe"],"tags":["germ"],"category":"Animals & Nature","description":"microbe","unicode_version":"11.0"},{"emoji":"💐","aliases":["bouquet"],"tags":["flowers"],"category":"Animals & Nature","description":"bouquet","unicode_version":"6.0"},{"emoji":"🌸","aliases":["cherry_blossom"],"tags":["flower","spring"],"category":"Animals & Nature","description":"cherry blossom","unicode_version":"6.0"},{"emoji":"💮","aliases":["white_flower"],"tags":[],"category":"Animals & Nature","description":"white flower","unicode_version":"6.0"},{"emoji":"🏵️","aliases":["rosette"],"tags":[],"category":"Animals & Nature","description":"rosette","unicode_version":"7.0"},{"emoji":"🌹","aliases":["rose"],"tags":["flower"],"category":"Animals & Nature","description":"rose","unicode_version":"6.0"},{"emoji":"🥀","aliases":["wilted_flower"],"tags":[],"category":"Animals & Nature","description":"wilted flower","unicode_version":"9.0"},{"emoji":"🌺","aliases":["hibiscus"],"tags":[],"category":"Animals & Nature","description":"hibiscus","unicode_version":"6.0"},{"emoji":"🌻","aliases":["sunflower"],"tags":[],"category":"Animals & Nature","description":"sunflower","unicode_version":"6.0"},{"emoji":"🌼","aliases":["blossom"],"tags":[],"category":"Animals & Nature","description":"blossom","unicode_version":"6.0"},{"emoji":"🌷","aliases":["tulip"],"tags":["flower"],"category":"Animals & Nature","description":"tulip","unicode_version":"6.0"},{"emoji":"🌱","aliases":["seedling"],"tags":["plant"],"category":"Animals & Nature","description":"seedling","unicode_version":"6.0"},{"emoji":"🪴","aliases":["potted_plant"],"tags":[],"category":"Animals & Nature","description":"potted plant","unicode_version":"13.0"},{"emoji":"🌲","aliases":["evergreen_tree"],"tags":["wood"],"category":"Animals & Nature","description":"evergreen tree","unicode_version":"6.0"},{"emoji":"🌳","aliases":["deciduous_tree"],"tags":["wood"],"category":"Animals & Nature","description":"deciduous tree","unicode_version":"6.0"},{"emoji":"🌴","aliases":["palm_tree"],"tags":[],"category":"Animals & Nature","description":"palm tree","unicode_version":"6.0"},{"emoji":"🌵","aliases":["cactus"],"tags":[],"category":"Animals & Nature","description":"cactus","unicode_version":"6.0"},{"emoji":"🌾","aliases":["ear_of_rice"],"tags":[],"category":"Animals & Nature","description":"sheaf of rice","unicode_version":"6.0"},{"emoji":"🌿","aliases":["herb"],"tags":[],"category":"Animals & Nature","description":"herb","unicode_version":"6.0"},{"emoji":"☘️","aliases":["shamrock"],"tags":[],"category":"Animals & Nature","description":"shamrock","unicode_version":"4.1"},{"emoji":"🍀","aliases":["four_leaf_clover"],"tags":["luck"],"category":"Animals & Nature","description":"four leaf clover","unicode_version":"6.0"},{"emoji":"🍁","aliases":["maple_leaf"],"tags":["canada"],"category":"Animals & Nature","description":"maple leaf","unicode_version":"6.0"},{"emoji":"🍂","aliases":["fallen_leaf"],"tags":["autumn"],"category":"Animals & Nature","description":"fallen leaf","unicode_version":"6.0"},{"emoji":"🍃","aliases":["leaves"],"tags":["leaf"],"category":"Animals & Nature","description":"leaf fluttering in wind","unicode_version":"6.0"},{"emoji":"🍇","aliases":["grapes"],"tags":[],"category":"Food & Drink","description":"grapes","unicode_version":"6.0"},{"emoji":"🍈","aliases":["melon"],"tags":[],"category":"Food & Drink","description":"melon","unicode_version":"6.0"},{"emoji":"🍉","aliases":["watermelon"],"tags":[],"category":"Food & Drink","description":"watermelon","unicode_version":"6.0"},{"emoji":"🍊","aliases":["tangerine","orange","mandarin"],"tags":[],"category":"Food & Drink","description":"tangerine","unicode_version":"6.0"},{"emoji":"🍋","aliases":["lemon"],"tags":[],"category":"Food & Drink","description":"lemon","unicode_version":"6.0"},{"emoji":"🍌","aliases":["banana"],"tags":["fruit"],"category":"Food & Drink","description":"banana","unicode_version":"6.0"},{"emoji":"🍍","aliases":["pineapple"],"tags":[],"category":"Food & Drink","description":"pineapple","unicode_version":"6.0"},{"emoji":"🥭","aliases":["mango"],"tags":[],"category":"Food & Drink","description":"mango","unicode_version":"11.0"},{"emoji":"🍎","aliases":["apple"],"tags":[],"category":"Food & Drink","description":"red apple","unicode_version":"6.0"},{"emoji":"🍏","aliases":["green_apple"],"tags":["fruit"],"category":"Food & Drink","description":"green apple","unicode_version":"6.0"},{"emoji":"🍐","aliases":["pear"],"tags":[],"category":"Food & Drink","description":"pear","unicode_version":"6.0"},{"emoji":"🍑","aliases":["peach"],"tags":[],"category":"Food & Drink","description":"peach","unicode_version":"6.0"},{"emoji":"🍒","aliases":["cherries"],"tags":["fruit"],"category":"Food & Drink","description":"cherries","unicode_version":"6.0"},{"emoji":"🍓","aliases":["strawberry"],"tags":["fruit"],"category":"Food & Drink","description":"strawberry","unicode_version":"6.0"},{"emoji":"🫐","aliases":["blueberries"],"tags":[],"category":"Food & Drink","description":"blueberries","unicode_version":"13.0"},{"emoji":"🥝","aliases":["kiwi_fruit"],"tags":[],"category":"Food & Drink","description":"kiwi fruit","unicode_version":"9.0"},{"emoji":"🍅","aliases":["tomato"],"tags":[],"category":"Food & Drink","description":"tomato","unicode_version":"6.0"},{"emoji":"🫒","aliases":["olive"],"tags":[],"category":"Food & Drink","description":"olive","unicode_version":"13.0"},{"emoji":"🥥","aliases":["coconut"],"tags":[],"category":"Food & Drink","description":"coconut","unicode_version":"11.0"},{"emoji":"🥑","aliases":["avocado"],"tags":[],"category":"Food & Drink","description":"avocado","unicode_version":"9.0"},{"emoji":"🍆","aliases":["eggplant"],"tags":["aubergine"],"category":"Food & Drink","description":"eggplant","unicode_version":"6.0"},{"emoji":"🥔","aliases":["potato"],"tags":[],"category":"Food & Drink","description":"potato","unicode_version":"9.0"},{"emoji":"🥕","aliases":["carrot"],"tags":[],"category":"Food & Drink","description":"carrot","unicode_version":"9.0"},{"emoji":"🌽","aliases":["corn"],"tags":[],"category":"Food & Drink","description":"ear of corn","unicode_version":"6.0"},{"emoji":"🌶️","aliases":["hot_pepper"],"tags":["spicy"],"category":"Food & Drink","description":"hot pepper","unicode_version":"7.0"},{"emoji":"🫑","aliases":["bell_pepper"],"tags":[],"category":"Food & Drink","description":"bell pepper","unicode_version":"13.0"},{"emoji":"🥒","aliases":["cucumber"],"tags":[],"category":"Food & Drink","description":"cucumber","unicode_version":"9.0"},{"emoji":"🥬","aliases":["leafy_green"],"tags":[],"category":"Food & Drink","description":"leafy green","unicode_version":"11.0"},{"emoji":"🥦","aliases":["broccoli"],"tags":[],"category":"Food & Drink","description":"broccoli","unicode_version":"11.0"},{"emoji":"🧄","aliases":["garlic"],"tags":[],"category":"Food & Drink","description":"garlic","unicode_version":"12.0"},{"emoji":"🧅","aliases":["onion"],"tags":[],"category":"Food & Drink","description":"onion","unicode_version":"12.0"},{"emoji":"🍄","aliases":["mushroom"],"tags":[],"category":"Food & Drink","description":"mushroom","unicode_version":"6.0"},{"emoji":"🥜","aliases":["peanuts"],"tags":[],"category":"Food & Drink","description":"peanuts","unicode_version":"9.0"},{"emoji":"🌰","aliases":["chestnut"],"tags":[],"category":"Food & Drink","description":"chestnut","unicode_version":"6.0"},{"emoji":"🍞","aliases":["bread"],"tags":["toast"],"category":"Food & Drink","description":"bread","unicode_version":"6.0"},{"emoji":"🥐","aliases":["croissant"],"tags":[],"category":"Food & Drink","description":"croissant","unicode_version":"9.0"},{"emoji":"🥖","aliases":["baguette_bread"],"tags":[],"category":"Food & Drink","description":"baguette bread","unicode_version":"9.0"},{"emoji":"🫓","aliases":["flatbread"],"tags":[],"category":"Food & Drink","description":"flatbread","unicode_version":"13.0"},{"emoji":"🥨","aliases":["pretzel"],"tags":[],"category":"Food & Drink","description":"pretzel","unicode_version":"11.0"},{"emoji":"🥯","aliases":["bagel"],"tags":[],"category":"Food & Drink","description":"bagel","unicode_version":"11.0"},{"emoji":"🥞","aliases":["pancakes"],"tags":[],"category":"Food & Drink","description":"pancakes","unicode_version":"9.0"},{"emoji":"🧇","aliases":["waffle"],"tags":[],"category":"Food & Drink","description":"waffle","unicode_version":"12.0"},{"emoji":"🧀","aliases":["cheese"],"tags":[],"category":"Food & Drink","description":"cheese wedge","unicode_version":"8.0"},{"emoji":"🍖","aliases":["meat_on_bone"],"tags":[],"category":"Food & Drink","description":"meat on bone","unicode_version":"6.0"},{"emoji":"🍗","aliases":["poultry_leg"],"tags":["meat","chicken"],"category":"Food & Drink","description":"poultry leg","unicode_version":"6.0"},{"emoji":"🥩","aliases":["cut_of_meat"],"tags":[],"category":"Food & Drink","description":"cut of meat","unicode_version":"11.0"},{"emoji":"🥓","aliases":["bacon"],"tags":[],"category":"Food & Drink","description":"bacon","unicode_version":"9.0"},{"emoji":"🍔","aliases":["hamburger"],"tags":["burger"],"category":"Food & Drink","description":"hamburger","unicode_version":"6.0"},{"emoji":"🍟","aliases":["fries"],"tags":[],"category":"Food & Drink","description":"french fries","unicode_version":"6.0"},{"emoji":"🍕","aliases":["pizza"],"tags":[],"category":"Food & Drink","description":"pizza","unicode_version":"6.0"},{"emoji":"🌭","aliases":["hotdog"],"tags":[],"category":"Food & Drink","description":"hot dog","unicode_version":"8.0"},{"emoji":"🥪","aliases":["sandwich"],"tags":[],"category":"Food & Drink","description":"sandwich","unicode_version":"11.0"},{"emoji":"🌮","aliases":["taco"],"tags":[],"category":"Food & Drink","description":"taco","unicode_version":"8.0"},{"emoji":"🌯","aliases":["burrito"],"tags":[],"category":"Food & Drink","description":"burrito","unicode_version":"8.0"},{"emoji":"🫔","aliases":["tamale"],"tags":[],"category":"Food & Drink","description":"tamale","unicode_version":"13.0"},{"emoji":"🥙","aliases":["stuffed_flatbread"],"tags":[],"category":"Food & Drink","description":"stuffed flatbread","unicode_version":"9.0"},{"emoji":"🧆","aliases":["falafel"],"tags":[],"category":"Food & Drink","description":"falafel","unicode_version":"12.0"},{"emoji":"🥚","aliases":["egg"],"tags":[],"category":"Food & Drink","description":"egg","unicode_version":"9.0"},{"emoji":"🍳","aliases":["fried_egg"],"tags":["breakfast"],"category":"Food & Drink","description":"cooking","unicode_version":"6.0"},{"emoji":"🥘","aliases":["shallow_pan_of_food"],"tags":["paella","curry"],"category":"Food & Drink","description":"shallow pan of food","unicode_version":""},{"emoji":"🍲","aliases":["stew"],"tags":[],"category":"Food & Drink","description":"pot of food","unicode_version":"6.0"},{"emoji":"🫕","aliases":["fondue"],"tags":[],"category":"Food & Drink","description":"fondue","unicode_version":"13.0"},{"emoji":"🥣","aliases":["bowl_with_spoon"],"tags":[],"category":"Food & Drink","description":"bowl with spoon","unicode_version":"11.0"},{"emoji":"🥗","aliases":["green_salad"],"tags":[],"category":"Food & Drink","description":"green salad","unicode_version":"9.0"},{"emoji":"🍿","aliases":["popcorn"],"tags":[],"category":"Food & Drink","description":"popcorn","unicode_version":"8.0"},{"emoji":"🧈","aliases":["butter"],"tags":[],"category":"Food & Drink","description":"butter","unicode_version":"12.0"},{"emoji":"🧂","aliases":["salt"],"tags":[],"category":"Food & Drink","description":"salt","unicode_version":"11.0"},{"emoji":"🥫","aliases":["canned_food"],"tags":[],"category":"Food & Drink","description":"canned food","unicode_version":"11.0"},{"emoji":"🍱","aliases":["bento"],"tags":[],"category":"Food & Drink","description":"bento box","unicode_version":"6.0"},{"emoji":"🍘","aliases":["rice_cracker"],"tags":[],"category":"Food & Drink","description":"rice cracker","unicode_version":"6.0"},{"emoji":"🍙","aliases":["rice_ball"],"tags":[],"category":"Food & Drink","description":"rice ball","unicode_version":"6.0"},{"emoji":"🍚","aliases":["rice"],"tags":[],"category":"Food & Drink","description":"cooked rice","unicode_version":"6.0"},{"emoji":"🍛","aliases":["curry"],"tags":[],"category":"Food & Drink","description":"curry rice","unicode_version":"6.0"},{"emoji":"🍜","aliases":["ramen"],"tags":["noodle"],"category":"Food & Drink","description":"steaming bowl","unicode_version":"6.0"},{"emoji":"🍝","aliases":["spaghetti"],"tags":["pasta"],"category":"Food & Drink","description":"spaghetti","unicode_version":"6.0"},{"emoji":"🍠","aliases":["sweet_potato"],"tags":[],"category":"Food & Drink","description":"roasted sweet potato","unicode_version":"6.0"},{"emoji":"🍢","aliases":["oden"],"tags":[],"category":"Food & Drink","description":"oden","unicode_version":"6.0"},{"emoji":"🍣","aliases":["sushi"],"tags":[],"category":"Food & Drink","description":"sushi","unicode_version":"6.0"},{"emoji":"🍤","aliases":["fried_shrimp"],"tags":["tempura"],"category":"Food & Drink","description":"fried shrimp","unicode_version":"6.0"},{"emoji":"🍥","aliases":["fish_cake"],"tags":[],"category":"Food & Drink","description":"fish cake with swirl","unicode_version":"6.0"},{"emoji":"🥮","aliases":["moon_cake"],"tags":[],"category":"Food & Drink","description":"moon cake","unicode_version":"11.0"},{"emoji":"🍡","aliases":["dango"],"tags":[],"category":"Food & Drink","description":"dango","unicode_version":"6.0"},{"emoji":"🥟","aliases":["dumpling"],"tags":[],"category":"Food & Drink","description":"dumpling","unicode_version":"11.0"},{"emoji":"🥠","aliases":["fortune_cookie"],"tags":[],"category":"Food & Drink","description":"fortune cookie","unicode_version":"11.0"},{"emoji":"🥡","aliases":["takeout_box"],"tags":[],"category":"Food & Drink","description":"takeout box","unicode_version":"11.0"},{"emoji":"🦀","aliases":["crab"],"tags":[],"category":"Food & Drink","description":"crab","unicode_version":"8.0"},{"emoji":"🦞","aliases":["lobster"],"tags":[],"category":"Food & Drink","description":"lobster","unicode_version":"11.0"},{"emoji":"🦐","aliases":["shrimp"],"tags":[],"category":"Food & Drink","description":"shrimp","unicode_version":"9.0"},{"emoji":"🦑","aliases":["squid"],"tags":[],"category":"Food & Drink","description":"squid","unicode_version":"9.0"},{"emoji":"🦪","aliases":["oyster"],"tags":[],"category":"Food & Drink","description":"oyster","unicode_version":"12.0"},{"emoji":"🍦","aliases":["icecream"],"tags":[],"category":"Food & Drink","description":"soft ice cream","unicode_version":"6.0"},{"emoji":"🍧","aliases":["shaved_ice"],"tags":[],"category":"Food & Drink","description":"shaved ice","unicode_version":"6.0"},{"emoji":"🍨","aliases":["ice_cream"],"tags":[],"category":"Food & Drink","description":"ice cream","unicode_version":"6.0"},{"emoji":"🍩","aliases":["doughnut"],"tags":[],"category":"Food & Drink","description":"doughnut","unicode_version":"6.0"},{"emoji":"🍪","aliases":["cookie"],"tags":[],"category":"Food & Drink","description":"cookie","unicode_version":"6.0"},{"emoji":"🎂","aliases":["birthday"],"tags":["party"],"category":"Food & Drink","description":"birthday cake","unicode_version":"6.0"},{"emoji":"🍰","aliases":["cake"],"tags":["dessert"],"category":"Food & Drink","description":"shortcake","unicode_version":"6.0"},{"emoji":"🧁","aliases":["cupcake"],"tags":[],"category":"Food & Drink","description":"cupcake","unicode_version":"11.0"},{"emoji":"🥧","aliases":["pie"],"tags":[],"category":"Food & Drink","description":"pie","unicode_version":"11.0"},{"emoji":"🍫","aliases":["chocolate_bar"],"tags":[],"category":"Food & Drink","description":"chocolate bar","unicode_version":"6.0"},{"emoji":"🍬","aliases":["candy"],"tags":["sweet"],"category":"Food & Drink","description":"candy","unicode_version":"6.0"},{"emoji":"🍭","aliases":["lollipop"],"tags":[],"category":"Food & Drink","description":"lollipop","unicode_version":"6.0"},{"emoji":"🍮","aliases":["custard"],"tags":[],"category":"Food & Drink","description":"custard","unicode_version":"6.0"},{"emoji":"🍯","aliases":["honey_pot"],"tags":[],"category":"Food & Drink","description":"honey pot","unicode_version":"6.0"},{"emoji":"🍼","aliases":["baby_bottle"],"tags":["milk"],"category":"Food & Drink","description":"baby bottle","unicode_version":"6.0"},{"emoji":"🥛","aliases":["milk_glass"],"tags":[],"category":"Food & Drink","description":"glass of milk","unicode_version":"9.0"},{"emoji":"☕","aliases":["coffee"],"tags":["cafe","espresso"],"category":"Food & Drink","description":"hot beverage","unicode_version":"4.0"},{"emoji":"🫖","aliases":["teapot"],"tags":[],"category":"Food & Drink","description":"teapot","unicode_version":"13.0"},{"emoji":"🍵","aliases":["tea"],"tags":["green","breakfast"],"category":"Food & Drink","description":"teacup without handle","unicode_version":"6.0"},{"emoji":"🍶","aliases":["sake"],"tags":[],"category":"Food & Drink","description":"sake","unicode_version":"6.0"},{"emoji":"🍾","aliases":["champagne"],"tags":["bottle","bubbly","celebration"],"category":"Food & Drink","description":"bottle with popping cork","unicode_version":"8.0"},{"emoji":"🍷","aliases":["wine_glass"],"tags":[],"category":"Food & Drink","description":"wine glass","unicode_version":"6.0"},{"emoji":"🍸","aliases":["cocktail"],"tags":["drink"],"category":"Food & Drink","description":"cocktail glass","unicode_version":"6.0"},{"emoji":"🍹","aliases":["tropical_drink"],"tags":["summer","vacation"],"category":"Food & Drink","description":"tropical drink","unicode_version":"6.0"},{"emoji":"🍺","aliases":["beer"],"tags":["drink"],"category":"Food & Drink","description":"beer mug","unicode_version":"6.0"},{"emoji":"🍻","aliases":["beers"],"tags":["drinks"],"category":"Food & Drink","description":"clinking beer mugs","unicode_version":"6.0"},{"emoji":"🥂","aliases":["clinking_glasses"],"tags":["cheers","toast"],"category":"Food & Drink","description":"clinking glasses","unicode_version":"9.0"},{"emoji":"🥃","aliases":["tumbler_glass"],"tags":["whisky"],"category":"Food & Drink","description":"tumbler glass","unicode_version":"9.0"},{"emoji":"🥤","aliases":["cup_with_straw"],"tags":[],"category":"Food & Drink","description":"cup with straw","unicode_version":"11.0"},{"emoji":"🧋","aliases":["bubble_tea"],"tags":[],"category":"Food & Drink","description":"bubble tea","unicode_version":"13.0"},{"emoji":"🧃","aliases":["beverage_box"],"tags":[],"category":"Food & Drink","description":"beverage box","unicode_version":"12.0"},{"emoji":"🧉","aliases":["mate"],"tags":[],"category":"Food & Drink","description":"mate","unicode_version":"12.0"},{"emoji":"🧊","aliases":["ice_cube"],"tags":[],"category":"Food & Drink","description":"ice","unicode_version":"12.0"},{"emoji":"🥢","aliases":["chopsticks"],"tags":[],"category":"Food & Drink","description":"chopsticks","unicode_version":"11.0"},{"emoji":"🍽️","aliases":["plate_with_cutlery"],"tags":["dining","dinner"],"category":"Food & Drink","description":"fork and knife with plate","unicode_version":"7.0"},{"emoji":"🍴","aliases":["fork_and_knife"],"tags":["cutlery"],"category":"Food & Drink","description":"fork and knife","unicode_version":"6.0"},{"emoji":"🥄","aliases":["spoon"],"tags":[],"category":"Food & Drink","description":"spoon","unicode_version":"9.0"},{"emoji":"🔪","aliases":["hocho","knife"],"tags":["cut","chop"],"category":"Food & Drink","description":"kitchen knife","unicode_version":"6.0"},{"emoji":"🏺","aliases":["amphora"],"tags":[],"category":"Food & Drink","description":"amphora","unicode_version":"8.0"},{"emoji":"🌍","aliases":["earth_africa"],"tags":["globe","world","international"],"category":"Travel & Places","description":"globe showing Europe-Africa","unicode_version":"6.0"},{"emoji":"🌎","aliases":["earth_americas"],"tags":["globe","world","international"],"category":"Travel & Places","description":"globe showing Americas","unicode_version":"6.0"},{"emoji":"🌏","aliases":["earth_asia"],"tags":["globe","world","international"],"category":"Travel & Places","description":"globe showing Asia-Australia","unicode_version":"6.0"},{"emoji":"🌐","aliases":["globe_with_meridians"],"tags":["world","global","international"],"category":"Travel & Places","description":"globe with meridians","unicode_version":"6.0"},{"emoji":"🗺️","aliases":["world_map"],"tags":["travel"],"category":"Travel & Places","description":"world map","unicode_version":"7.0"},{"emoji":"🗾","aliases":["japan"],"tags":[],"category":"Travel & Places","description":"map of Japan","unicode_version":"6.0"},{"emoji":"🧭","aliases":["compass"],"tags":[],"category":"Travel & Places","description":"compass","unicode_version":"11.0"},{"emoji":"🏔️","aliases":["mountain_snow"],"tags":[],"category":"Travel & Places","description":"snow-capped mountain","unicode_version":"7.0"},{"emoji":"⛰️","aliases":["mountain"],"tags":[],"category":"Travel & Places","description":"mountain","unicode_version":"5.2"},{"emoji":"🌋","aliases":["volcano"],"tags":[],"category":"Travel & Places","description":"volcano","unicode_version":"6.0"},{"emoji":"🗻","aliases":["mount_fuji"],"tags":[],"category":"Travel & Places","description":"mount fuji","unicode_version":"6.0"},{"emoji":"🏕️","aliases":["camping"],"tags":[],"category":"Travel & Places","description":"camping","unicode_version":"7.0"},{"emoji":"🏖️","aliases":["beach_umbrella"],"tags":[],"category":"Travel & Places","description":"beach with umbrella","unicode_version":"7.0"},{"emoji":"🏜️","aliases":["desert"],"tags":[],"category":"Travel & Places","description":"desert","unicode_version":"7.0"},{"emoji":"🏝️","aliases":["desert_island"],"tags":[],"category":"Travel & Places","description":"desert island","unicode_version":"7.0"},{"emoji":"🏞️","aliases":["national_park"],"tags":[],"category":"Travel & Places","description":"national park","unicode_version":"7.0"},{"emoji":"🏟️","aliases":["stadium"],"tags":[],"category":"Travel & Places","description":"stadium","unicode_version":"7.0"},{"emoji":"🏛️","aliases":["classical_building"],"tags":[],"category":"Travel & Places","description":"classical building","unicode_version":"7.0"},{"emoji":"🏗️","aliases":["building_construction"],"tags":[],"category":"Travel & Places","description":"building construction","unicode_version":"7.0"},{"emoji":"🧱","aliases":["bricks"],"tags":[],"category":"Travel & Places","description":"brick","unicode_version":"11.0"},{"emoji":"🪨","aliases":["rock"],"tags":[],"category":"Travel & Places","description":"rock","unicode_version":"13.0"},{"emoji":"🪵","aliases":["wood"],"tags":[],"category":"Travel & Places","description":"wood","unicode_version":"13.0"},{"emoji":"🛖","aliases":["hut"],"tags":[],"category":"Travel & Places","description":"hut","unicode_version":"13.0"},{"emoji":"🏘️","aliases":["houses"],"tags":[],"category":"Travel & Places","description":"houses","unicode_version":"7.0"},{"emoji":"🏚️","aliases":["derelict_house"],"tags":[],"category":"Travel & Places","description":"derelict house","unicode_version":"7.0"},{"emoji":"🏠","aliases":["house"],"tags":[],"category":"Travel & Places","description":"house","unicode_version":"6.0"},{"emoji":"🏡","aliases":["house_with_garden"],"tags":[],"category":"Travel & Places","description":"house with garden","unicode_version":"6.0"},{"emoji":"🏢","aliases":["office"],"tags":[],"category":"Travel & Places","description":"office building","unicode_version":"6.0"},{"emoji":"🏣","aliases":["post_office"],"tags":[],"category":"Travel & Places","description":"Japanese post office","unicode_version":"6.0"},{"emoji":"🏤","aliases":["european_post_office"],"tags":[],"category":"Travel & Places","description":"post office","unicode_version":"6.0"},{"emoji":"🏥","aliases":["hospital"],"tags":[],"category":"Travel & Places","description":"hospital","unicode_version":"6.0"},{"emoji":"🏦","aliases":["bank"],"tags":[],"category":"Travel & Places","description":"bank","unicode_version":"6.0"},{"emoji":"🏨","aliases":["hotel"],"tags":[],"category":"Travel & Places","description":"hotel","unicode_version":"6.0"},{"emoji":"🏩","aliases":["love_hotel"],"tags":[],"category":"Travel & Places","description":"love hotel","unicode_version":"6.0"},{"emoji":"🏪","aliases":["convenience_store"],"tags":[],"category":"Travel & Places","description":"convenience store","unicode_version":"6.0"},{"emoji":"🏫","aliases":["school"],"tags":[],"category":"Travel & Places","description":"school","unicode_version":"6.0"},{"emoji":"🏬","aliases":["department_store"],"tags":[],"category":"Travel & Places","description":"department store","unicode_version":"6.0"},{"emoji":"🏭","aliases":["factory"],"tags":[],"category":"Travel & Places","description":"factory","unicode_version":"6.0"},{"emoji":"🏯","aliases":["japanese_castle"],"tags":[],"category":"Travel & Places","description":"Japanese castle","unicode_version":"6.0"},{"emoji":"🏰","aliases":["european_castle"],"tags":[],"category":"Travel & Places","description":"castle","unicode_version":"6.0"},{"emoji":"💒","aliases":["wedding"],"tags":["marriage"],"category":"Travel & Places","description":"wedding","unicode_version":"6.0"},{"emoji":"🗼","aliases":["tokyo_tower"],"tags":[],"category":"Travel & Places","description":"Tokyo tower","unicode_version":"6.0"},{"emoji":"🗽","aliases":["statue_of_liberty"],"tags":[],"category":"Travel & Places","description":"Statue of Liberty","unicode_version":"6.0"},{"emoji":"⛪","aliases":["church"],"tags":[],"category":"Travel & Places","description":"church","unicode_version":"5.2"},{"emoji":"🕌","aliases":["mosque"],"tags":[],"category":"Travel & Places","description":"mosque","unicode_version":"8.0"},{"emoji":"🛕","aliases":["hindu_temple"],"tags":[],"category":"Travel & Places","description":"hindu temple","unicode_version":"12.0"},{"emoji":"🕍","aliases":["synagogue"],"tags":[],"category":"Travel & Places","description":"synagogue","unicode_version":"8.0"},{"emoji":"⛩️","aliases":["shinto_shrine"],"tags":[],"category":"Travel & Places","description":"shinto shrine","unicode_version":"5.2"},{"emoji":"🕋","aliases":["kaaba"],"tags":[],"category":"Travel & Places","description":"kaaba","unicode_version":"8.0"},{"emoji":"⛲","aliases":["fountain"],"tags":[],"category":"Travel & Places","description":"fountain","unicode_version":"5.2"},{"emoji":"⛺","aliases":["tent"],"tags":["camping"],"category":"Travel & Places","description":"tent","unicode_version":"5.2"},{"emoji":"🌁","aliases":["foggy"],"tags":["karl"],"category":"Travel & Places","description":"foggy","unicode_version":"6.0"},{"emoji":"🌃","aliases":["night_with_stars"],"tags":[],"category":"Travel & Places","description":"night with stars","unicode_version":"6.0"},{"emoji":"🏙️","aliases":["cityscape"],"tags":["skyline"],"category":"Travel & Places","description":"cityscape","unicode_version":"7.0"},{"emoji":"🌄","aliases":["sunrise_over_mountains"],"tags":[],"category":"Travel & Places","description":"sunrise over mountains","unicode_version":"6.0"},{"emoji":"🌅","aliases":["sunrise"],"tags":[],"category":"Travel & Places","description":"sunrise","unicode_version":"6.0"},{"emoji":"🌆","aliases":["city_sunset"],"tags":[],"category":"Travel & Places","description":"cityscape at dusk","unicode_version":"6.0"},{"emoji":"🌇","aliases":["city_sunrise"],"tags":[],"category":"Travel & Places","description":"sunset","unicode_version":"6.0"},{"emoji":"🌉","aliases":["bridge_at_night"],"tags":[],"category":"Travel & Places","description":"bridge at night","unicode_version":"6.0"},{"emoji":"♨️","aliases":["hotsprings"],"tags":[],"category":"Travel & Places","description":"hot springs","unicode_version":""},{"emoji":"🎠","aliases":["carousel_horse"],"tags":[],"category":"Travel & Places","description":"carousel horse","unicode_version":"6.0"},{"emoji":"🎡","aliases":["ferris_wheel"],"tags":[],"category":"Travel & Places","description":"ferris wheel","unicode_version":"6.0"},{"emoji":"🎢","aliases":["roller_coaster"],"tags":[],"category":"Travel & Places","description":"roller coaster","unicode_version":"6.0"},{"emoji":"💈","aliases":["barber"],"tags":[],"category":"Travel & Places","description":"barber pole","unicode_version":"6.0"},{"emoji":"🎪","aliases":["circus_tent"],"tags":[],"category":"Travel & Places","description":"circus tent","unicode_version":"6.0"},{"emoji":"🚂","aliases":["steam_locomotive"],"tags":["train"],"category":"Travel & Places","description":"locomotive","unicode_version":"6.0"},{"emoji":"🚃","aliases":["railway_car"],"tags":[],"category":"Travel & Places","description":"railway car","unicode_version":"6.0"},{"emoji":"🚄","aliases":["bullettrain_side"],"tags":["train"],"category":"Travel & Places","description":"high-speed train","unicode_version":"6.0"},{"emoji":"🚅","aliases":["bullettrain_front"],"tags":["train"],"category":"Travel & Places","description":"bullet train","unicode_version":"6.0"},{"emoji":"🚆","aliases":["train2"],"tags":[],"category":"Travel & Places","description":"train","unicode_version":"6.0"},{"emoji":"🚇","aliases":["metro"],"tags":[],"category":"Travel & Places","description":"metro","unicode_version":"6.0"},{"emoji":"🚈","aliases":["light_rail"],"tags":[],"category":"Travel & Places","description":"light rail","unicode_version":"6.0"},{"emoji":"🚉","aliases":["station"],"tags":[],"category":"Travel & Places","description":"station","unicode_version":"6.0"},{"emoji":"🚊","aliases":["tram"],"tags":[],"category":"Travel & Places","description":"tram","unicode_version":"6.0"},{"emoji":"🚝","aliases":["monorail"],"tags":[],"category":"Travel & Places","description":"monorail","unicode_version":"6.0"},{"emoji":"🚞","aliases":["mountain_railway"],"tags":[],"category":"Travel & Places","description":"mountain railway","unicode_version":"6.0"},{"emoji":"🚋","aliases":["train"],"tags":[],"category":"Travel & Places","description":"tram car","unicode_version":"6.0"},{"emoji":"🚌","aliases":["bus"],"tags":[],"category":"Travel & Places","description":"bus","unicode_version":"6.0"},{"emoji":"🚍","aliases":["oncoming_bus"],"tags":[],"category":"Travel & Places","description":"oncoming bus","unicode_version":"6.0"},{"emoji":"🚎","aliases":["trolleybus"],"tags":[],"category":"Travel & Places","description":"trolleybus","unicode_version":"6.0"},{"emoji":"🚐","aliases":["minibus"],"tags":[],"category":"Travel & Places","description":"minibus","unicode_version":"6.0"},{"emoji":"🚑","aliases":["ambulance"],"tags":[],"category":"Travel & Places","description":"ambulance","unicode_version":"6.0"},{"emoji":"🚒","aliases":["fire_engine"],"tags":[],"category":"Travel & Places","description":"fire engine","unicode_version":"6.0"},{"emoji":"🚓","aliases":["police_car"],"tags":[],"category":"Travel & Places","description":"police car","unicode_version":"6.0"},{"emoji":"🚔","aliases":["oncoming_police_car"],"tags":[],"category":"Travel & Places","description":"oncoming police car","unicode_version":"6.0"},{"emoji":"🚕","aliases":["taxi"],"tags":[],"category":"Travel & Places","description":"taxi","unicode_version":"6.0"},{"emoji":"🚖","aliases":["oncoming_taxi"],"tags":[],"category":"Travel & Places","description":"oncoming taxi","unicode_version":"6.0"},{"emoji":"🚗","aliases":["car","red_car"],"tags":[],"category":"Travel & Places","description":"automobile","unicode_version":"6.0"},{"emoji":"🚘","aliases":["oncoming_automobile"],"tags":[],"category":"Travel & Places","description":"oncoming automobile","unicode_version":"6.0"},{"emoji":"🚙","aliases":["blue_car"],"tags":[],"category":"Travel & Places","description":"sport utility vehicle","unicode_version":"6.0"},{"emoji":"🛻","aliases":["pickup_truck"],"tags":[],"category":"Travel & Places","description":"pickup truck","unicode_version":"13.0"},{"emoji":"🚚","aliases":["truck"],"tags":[],"category":"Travel & Places","description":"delivery truck","unicode_version":"6.0"},{"emoji":"🚛","aliases":["articulated_lorry"],"tags":[],"category":"Travel & Places","description":"articulated lorry","unicode_version":"6.0"},{"emoji":"🚜","aliases":["tractor"],"tags":[],"category":"Travel & Places","description":"tractor","unicode_version":"6.0"},{"emoji":"🏎️","aliases":["racing_car"],"tags":[],"category":"Travel & Places","description":"racing car","unicode_version":"7.0"},{"emoji":"🏍️","aliases":["motorcycle"],"tags":[],"category":"Travel & Places","description":"motorcycle","unicode_version":"7.0"},{"emoji":"🛵","aliases":["motor_scooter"],"tags":[],"category":"Travel & Places","description":"motor scooter","unicode_version":"9.0"},{"emoji":"🦽","aliases":["manual_wheelchair"],"tags":[],"category":"Travel & Places","description":"manual wheelchair","unicode_version":"12.0"},{"emoji":"🦼","aliases":["motorized_wheelchair"],"tags":[],"category":"Travel & Places","description":"motorized wheelchair","unicode_version":"12.0"},{"emoji":"🛺","aliases":["auto_rickshaw"],"tags":[],"category":"Travel & Places","description":"auto rickshaw","unicode_version":"12.0"},{"emoji":"🚲","aliases":["bike"],"tags":["bicycle"],"category":"Travel & Places","description":"bicycle","unicode_version":"6.0"},{"emoji":"🛴","aliases":["kick_scooter"],"tags":[],"category":"Travel & Places","description":"kick scooter","unicode_version":"9.0"},{"emoji":"🛹","aliases":["skateboard"],"tags":[],"category":"Travel & Places","description":"skateboard","unicode_version":"11.0"},{"emoji":"🛼","aliases":["roller_skate"],"tags":[],"category":"Travel & Places","description":"roller skate","unicode_version":"13.0"},{"emoji":"🚏","aliases":["busstop"],"tags":[],"category":"Travel & Places","description":"bus stop","unicode_version":"6.0"},{"emoji":"🛣️","aliases":["motorway"],"tags":[],"category":"Travel & Places","description":"motorway","unicode_version":"7.0"},{"emoji":"🛤️","aliases":["railway_track"],"tags":[],"category":"Travel & Places","description":"railway track","unicode_version":"7.0"},{"emoji":"🛢️","aliases":["oil_drum"],"tags":[],"category":"Travel & Places","description":"oil drum","unicode_version":"7.0"},{"emoji":"⛽","aliases":["fuelpump"],"tags":[],"category":"Travel & Places","description":"fuel pump","unicode_version":"5.2"},{"emoji":"🚨","aliases":["rotating_light"],"tags":["911","emergency"],"category":"Travel & Places","description":"police car light","unicode_version":"6.0"},{"emoji":"🚥","aliases":["traffic_light"],"tags":[],"category":"Travel & Places","description":"horizontal traffic light","unicode_version":"6.0"},{"emoji":"🚦","aliases":["vertical_traffic_light"],"tags":["semaphore"],"category":"Travel & Places","description":"vertical traffic light","unicode_version":"6.0"},{"emoji":"🛑","aliases":["stop_sign"],"tags":[],"category":"Travel & Places","description":"stop sign","unicode_version":"9.0"},{"emoji":"🚧","aliases":["construction"],"tags":["wip"],"category":"Travel & Places","description":"construction","unicode_version":"6.0"},{"emoji":"⚓","aliases":["anchor"],"tags":["ship"],"category":"Travel & Places","description":"anchor","unicode_version":"4.1"},{"emoji":"⛵","aliases":["boat","sailboat"],"tags":[],"category":"Travel & Places","description":"sailboat","unicode_version":"5.2"},{"emoji":"🛶","aliases":["canoe"],"tags":[],"category":"Travel & Places","description":"canoe","unicode_version":"9.0"},{"emoji":"🚤","aliases":["speedboat"],"tags":["ship"],"category":"Travel & Places","description":"speedboat","unicode_version":"6.0"},{"emoji":"🛳️","aliases":["passenger_ship"],"tags":["cruise"],"category":"Travel & Places","description":"passenger ship","unicode_version":"7.0"},{"emoji":"⛴️","aliases":["ferry"],"tags":[],"category":"Travel & Places","description":"ferry","unicode_version":"5.2"},{"emoji":"🛥️","aliases":["motor_boat"],"tags":[],"category":"Travel & Places","description":"motor boat","unicode_version":"7.0"},{"emoji":"🚢","aliases":["ship"],"tags":[],"category":"Travel & Places","description":"ship","unicode_version":"6.0"},{"emoji":"✈️","aliases":["airplane"],"tags":["flight"],"category":"Travel & Places","description":"airplane","unicode_version":""},{"emoji":"🛩️","aliases":["small_airplane"],"tags":["flight"],"category":"Travel & Places","description":"small airplane","unicode_version":"7.0"},{"emoji":"🛫","aliases":["flight_departure"],"tags":[],"category":"Travel & Places","description":"airplane departure","unicode_version":"7.0"},{"emoji":"🛬","aliases":["flight_arrival"],"tags":[],"category":"Travel & Places","description":"airplane arrival","unicode_version":"7.0"},{"emoji":"🪂","aliases":["parachute"],"tags":[],"category":"Travel & Places","description":"parachute","unicode_version":"12.0"},{"emoji":"💺","aliases":["seat"],"tags":[],"category":"Travel & Places","description":"seat","unicode_version":"6.0"},{"emoji":"🚁","aliases":["helicopter"],"tags":[],"category":"Travel & Places","description":"helicopter","unicode_version":"6.0"},{"emoji":"🚟","aliases":["suspension_railway"],"tags":[],"category":"Travel & Places","description":"suspension railway","unicode_version":"6.0"},{"emoji":"🚠","aliases":["mountain_cableway"],"tags":[],"category":"Travel & Places","description":"mountain cableway","unicode_version":"6.0"},{"emoji":"🚡","aliases":["aerial_tramway"],"tags":[],"category":"Travel & Places","description":"aerial tramway","unicode_version":"6.0"},{"emoji":"🛰️","aliases":["artificial_satellite"],"tags":["orbit","space"],"category":"Travel & Places","description":"satellite","unicode_version":"7.0"},{"emoji":"🚀","aliases":["rocket"],"tags":["ship","launch"],"category":"Travel & Places","description":"rocket","unicode_version":"6.0"},{"emoji":"🛸","aliases":["flying_saucer"],"tags":["ufo"],"category":"Travel & Places","description":"flying saucer","unicode_version":"11.0"},{"emoji":"🛎️","aliases":["bellhop_bell"],"tags":[],"category":"Travel & Places","description":"bellhop bell","unicode_version":"7.0"},{"emoji":"🧳","aliases":["luggage"],"tags":[],"category":"Travel & Places","description":"luggage","unicode_version":"11.0"},{"emoji":"⌛","aliases":["hourglass"],"tags":["time"],"category":"Travel & Places","description":"hourglass done","unicode_version":""},{"emoji":"⏳","aliases":["hourglass_flowing_sand"],"tags":["time"],"category":"Travel & Places","description":"hourglass not done","unicode_version":"6.0"},{"emoji":"⌚","aliases":["watch"],"tags":["time"],"category":"Travel & Places","description":"watch","unicode_version":""},{"emoji":"⏰","aliases":["alarm_clock"],"tags":["morning"],"category":"Travel & Places","description":"alarm clock","unicode_version":"6.0"},{"emoji":"⏱️","aliases":["stopwatch"],"tags":[],"category":"Travel & Places","description":"stopwatch","unicode_version":"6.0"},{"emoji":"⏲️","aliases":["timer_clock"],"tags":[],"category":"Travel & Places","description":"timer clock","unicode_version":"6.0"},{"emoji":"🕰️","aliases":["mantelpiece_clock"],"tags":[],"category":"Travel & Places","description":"mantelpiece clock","unicode_version":"7.0"},{"emoji":"🕛","aliases":["clock12"],"tags":[],"category":"Travel & Places","description":"twelve o’clock","unicode_version":"6.0"},{"emoji":"🕧","aliases":["clock1230"],"tags":[],"category":"Travel & Places","description":"twelve-thirty","unicode_version":"6.0"},{"emoji":"🕐","aliases":["clock1"],"tags":[],"category":"Travel & Places","description":"one o’clock","unicode_version":"6.0"},{"emoji":"🕜","aliases":["clock130"],"tags":[],"category":"Travel & Places","description":"one-thirty","unicode_version":"6.0"},{"emoji":"🕑","aliases":["clock2"],"tags":[],"category":"Travel & Places","description":"two o’clock","unicode_version":"6.0"},{"emoji":"🕝","aliases":["clock230"],"tags":[],"category":"Travel & Places","description":"two-thirty","unicode_version":"6.0"},{"emoji":"🕒","aliases":["clock3"],"tags":[],"category":"Travel & Places","description":"three o’clock","unicode_version":"6.0"},{"emoji":"🕞","aliases":["clock330"],"tags":[],"category":"Travel & Places","description":"three-thirty","unicode_version":"6.0"},{"emoji":"🕓","aliases":["clock4"],"tags":[],"category":"Travel & Places","description":"four o’clock","unicode_version":"6.0"},{"emoji":"🕟","aliases":["clock430"],"tags":[],"category":"Travel & Places","description":"four-thirty","unicode_version":"6.0"},{"emoji":"🕔","aliases":["clock5"],"tags":[],"category":"Travel & Places","description":"five o’clock","unicode_version":"6.0"},{"emoji":"🕠","aliases":["clock530"],"tags":[],"category":"Travel & Places","description":"five-thirty","unicode_version":"6.0"},{"emoji":"🕕","aliases":["clock6"],"tags":[],"category":"Travel & Places","description":"six o’clock","unicode_version":"6.0"},{"emoji":"🕡","aliases":["clock630"],"tags":[],"category":"Travel & Places","description":"six-thirty","unicode_version":"6.0"},{"emoji":"🕖","aliases":["clock7"],"tags":[],"category":"Travel & Places","description":"seven o’clock","unicode_version":"6.0"},{"emoji":"🕢","aliases":["clock730"],"tags":[],"category":"Travel & Places","description":"seven-thirty","unicode_version":"6.0"},{"emoji":"🕗","aliases":["clock8"],"tags":[],"category":"Travel & Places","description":"eight o’clock","unicode_version":"6.0"},{"emoji":"🕣","aliases":["clock830"],"tags":[],"category":"Travel & Places","description":"eight-thirty","unicode_version":"6.0"},{"emoji":"🕘","aliases":["clock9"],"tags":[],"category":"Travel & Places","description":"nine o’clock","unicode_version":"6.0"},{"emoji":"🕤","aliases":["clock930"],"tags":[],"category":"Travel & Places","description":"nine-thirty","unicode_version":"6.0"},{"emoji":"🕙","aliases":["clock10"],"tags":[],"category":"Travel & Places","description":"ten o’clock","unicode_version":"6.0"},{"emoji":"🕥","aliases":["clock1030"],"tags":[],"category":"Travel & Places","description":"ten-thirty","unicode_version":"6.0"},{"emoji":"🕚","aliases":["clock11"],"tags":[],"category":"Travel & Places","description":"eleven o’clock","unicode_version":"6.0"},{"emoji":"🕦","aliases":["clock1130"],"tags":[],"category":"Travel & Places","description":"eleven-thirty","unicode_version":"6.0"},{"emoji":"🌑","aliases":["new_moon"],"tags":[],"category":"Travel & Places","description":"new moon","unicode_version":"6.0"},{"emoji":"🌒","aliases":["waxing_crescent_moon"],"tags":[],"category":"Travel & Places","description":"waxing crescent moon","unicode_version":"6.0"},{"emoji":"🌓","aliases":["first_quarter_moon"],"tags":[],"category":"Travel & Places","description":"first quarter moon","unicode_version":"6.0"},{"emoji":"🌔","aliases":["moon","waxing_gibbous_moon"],"tags":[],"category":"Travel & Places","description":"waxing gibbous moon","unicode_version":"6.0"},{"emoji":"🌕","aliases":["full_moon"],"tags":[],"category":"Travel & Places","description":"full moon","unicode_version":"6.0"},{"emoji":"🌖","aliases":["waning_gibbous_moon"],"tags":[],"category":"Travel & Places","description":"waning gibbous moon","unicode_version":"6.0"},{"emoji":"🌗","aliases":["last_quarter_moon"],"tags":[],"category":"Travel & Places","description":"last quarter moon","unicode_version":"6.0"},{"emoji":"🌘","aliases":["waning_crescent_moon"],"tags":[],"category":"Travel & Places","description":"waning crescent moon","unicode_version":"6.0"},{"emoji":"🌙","aliases":["crescent_moon"],"tags":["night"],"category":"Travel & Places","description":"crescent moon","unicode_version":"6.0"},{"emoji":"🌚","aliases":["new_moon_with_face"],"tags":[],"category":"Travel & Places","description":"new moon face","unicode_version":"6.0"},{"emoji":"🌛","aliases":["first_quarter_moon_with_face"],"tags":[],"category":"Travel & Places","description":"first quarter moon face","unicode_version":"6.0"},{"emoji":"🌜","aliases":["last_quarter_moon_with_face"],"tags":[],"category":"Travel & Places","description":"last quarter moon face","unicode_version":"6.0"},{"emoji":"🌡️","aliases":["thermometer"],"tags":[],"category":"Travel & Places","description":"thermometer","unicode_version":"7.0"},{"emoji":"☀️","aliases":["sunny"],"tags":["weather"],"category":"Travel & Places","description":"sun","unicode_version":""},{"emoji":"🌝","aliases":["full_moon_with_face"],"tags":[],"category":"Travel & Places","description":"full moon face","unicode_version":"6.0"},{"emoji":"🌞","aliases":["sun_with_face"],"tags":["summer"],"category":"Travel & Places","description":"sun with face","unicode_version":"6.0"},{"emoji":"🪐","aliases":["ringed_planet"],"tags":[],"category":"Travel & Places","description":"ringed planet","unicode_version":"12.0"},{"emoji":"⭐","aliases":["star"],"tags":[],"category":"Travel & Places","description":"star","unicode_version":"5.1"},{"emoji":"🌟","aliases":["star2"],"tags":[],"category":"Travel & Places","description":"glowing star","unicode_version":"6.0"},{"emoji":"🌠","aliases":["stars"],"tags":[],"category":"Travel & Places","description":"shooting star","unicode_version":"6.0"},{"emoji":"🌌","aliases":["milky_way"],"tags":[],"category":"Travel & Places","description":"milky way","unicode_version":"6.0"},{"emoji":"☁️","aliases":["cloud"],"tags":[],"category":"Travel & Places","description":"cloud","unicode_version":""},{"emoji":"⛅","aliases":["partly_sunny"],"tags":["weather","cloud"],"category":"Travel & Places","description":"sun behind cloud","unicode_version":"5.2"},{"emoji":"⛈️","aliases":["cloud_with_lightning_and_rain"],"tags":[],"category":"Travel & Places","description":"cloud with lightning and rain","unicode_version":"5.2"},{"emoji":"🌤️","aliases":["sun_behind_small_cloud"],"tags":[],"category":"Travel & Places","description":"sun behind small cloud","unicode_version":"7.0"},{"emoji":"🌥️","aliases":["sun_behind_large_cloud"],"tags":[],"category":"Travel & Places","description":"sun behind large cloud","unicode_version":"7.0"},{"emoji":"🌦️","aliases":["sun_behind_rain_cloud"],"tags":[],"category":"Travel & Places","description":"sun behind rain cloud","unicode_version":"7.0"},{"emoji":"🌧️","aliases":["cloud_with_rain"],"tags":[],"category":"Travel & Places","description":"cloud with rain","unicode_version":"7.0"},{"emoji":"🌨️","aliases":["cloud_with_snow"],"tags":[],"category":"Travel & Places","description":"cloud with snow","unicode_version":"7.0"},{"emoji":"🌩️","aliases":["cloud_with_lightning"],"tags":[],"category":"Travel & Places","description":"cloud with lightning","unicode_version":"7.0"},{"emoji":"🌪️","aliases":["tornado"],"tags":[],"category":"Travel & Places","description":"tornado","unicode_version":"7.0"},{"emoji":"🌫️","aliases":["fog"],"tags":[],"category":"Travel & Places","description":"fog","unicode_version":"7.0"},{"emoji":"🌬️","aliases":["wind_face"],"tags":[],"category":"Travel & Places","description":"wind face","unicode_version":"7.0"},{"emoji":"🌀","aliases":["cyclone"],"tags":["swirl"],"category":"Travel & Places","description":"cyclone","unicode_version":"6.0"},{"emoji":"🌈","aliases":["rainbow"],"tags":[],"category":"Travel & Places","description":"rainbow","unicode_version":"6.0"},{"emoji":"🌂","aliases":["closed_umbrella"],"tags":["weather","rain"],"category":"Travel & Places","description":"closed umbrella","unicode_version":"6.0"},{"emoji":"☂️","aliases":["open_umbrella"],"tags":[],"category":"Travel & Places","description":"umbrella","unicode_version":""},{"emoji":"☔","aliases":["umbrella"],"tags":["rain","weather"],"category":"Travel & Places","description":"umbrella with rain drops","unicode_version":"4.0"},{"emoji":"⛱️","aliases":["parasol_on_ground"],"tags":["beach_umbrella"],"category":"Travel & Places","description":"umbrella on ground","unicode_version":"5.2"},{"emoji":"⚡","aliases":["zap"],"tags":["lightning","thunder"],"category":"Travel & Places","description":"high voltage","unicode_version":"4.0"},{"emoji":"❄️","aliases":["snowflake"],"tags":["winter","cold","weather"],"category":"Travel & Places","description":"snowflake","unicode_version":""},{"emoji":"☃️","aliases":["snowman_with_snow"],"tags":["winter","christmas"],"category":"Travel & Places","description":"snowman","unicode_version":""},{"emoji":"⛄","aliases":["snowman"],"tags":["winter"],"category":"Travel & Places","description":"snowman without snow","unicode_version":"5.2"},{"emoji":"☄️","aliases":["comet"],"tags":[],"category":"Travel & Places","description":"comet","unicode_version":""},{"emoji":"🔥","aliases":["fire"],"tags":["burn"],"category":"Travel & Places","description":"fire","unicode_version":"6.0"},{"emoji":"💧","aliases":["droplet"],"tags":["water"],"category":"Travel & Places","description":"droplet","unicode_version":"6.0"},{"emoji":"🌊","aliases":["ocean"],"tags":["sea"],"category":"Travel & Places","description":"water wave","unicode_version":"6.0"},{"emoji":"🎃","aliases":["jack_o_lantern"],"tags":["halloween"],"category":"Activities","description":"jack-o-lantern","unicode_version":"6.0"},{"emoji":"🎄","aliases":["christmas_tree"],"tags":[],"category":"Activities","description":"Christmas tree","unicode_version":"6.0"},{"emoji":"🎆","aliases":["fireworks"],"tags":["festival","celebration"],"category":"Activities","description":"fireworks","unicode_version":"6.0"},{"emoji":"🎇","aliases":["sparkler"],"tags":[],"category":"Activities","description":"sparkler","unicode_version":"6.0"},{"emoji":"🧨","aliases":["firecracker"],"tags":[],"category":"Activities","description":"firecracker","unicode_version":"11.0"},{"emoji":"✨","aliases":["sparkles"],"tags":["shiny"],"category":"Activities","description":"sparkles","unicode_version":"6.0"},{"emoji":"🎈","aliases":["balloon"],"tags":["party","birthday"],"category":"Activities","description":"balloon","unicode_version":"6.0"},{"emoji":"🎉","aliases":["tada"],"tags":["hooray","party"],"category":"Activities","description":"party popper","unicode_version":"6.0"},{"emoji":"🎊","aliases":["confetti_ball"],"tags":[],"category":"Activities","description":"confetti ball","unicode_version":"6.0"},{"emoji":"🎋","aliases":["tanabata_tree"],"tags":[],"category":"Activities","description":"tanabata tree","unicode_version":"6.0"},{"emoji":"🎍","aliases":["bamboo"],"tags":[],"category":"Activities","description":"pine decoration","unicode_version":"6.0"},{"emoji":"🎎","aliases":["dolls"],"tags":[],"category":"Activities","description":"Japanese dolls","unicode_version":"6.0"},{"emoji":"🎏","aliases":["flags"],"tags":[],"category":"Activities","description":"carp streamer","unicode_version":"6.0"},{"emoji":"🎐","aliases":["wind_chime"],"tags":[],"category":"Activities","description":"wind chime","unicode_version":"6.0"},{"emoji":"🎑","aliases":["rice_scene"],"tags":[],"category":"Activities","description":"moon viewing ceremony","unicode_version":"6.0"},{"emoji":"🧧","aliases":["red_envelope"],"tags":[],"category":"Activities","description":"red envelope","unicode_version":"11.0"},{"emoji":"🎀","aliases":["ribbon"],"tags":[],"category":"Activities","description":"ribbon","unicode_version":"6.0"},{"emoji":"🎁","aliases":["gift"],"tags":["present","birthday","christmas"],"category":"Activities","description":"wrapped gift","unicode_version":"6.0"},{"emoji":"🎗️","aliases":["reminder_ribbon"],"tags":[],"category":"Activities","description":"reminder ribbon","unicode_version":"7.0"},{"emoji":"🎟️","aliases":["tickets"],"tags":[],"category":"Activities","description":"admission tickets","unicode_version":"7.0"},{"emoji":"🎫","aliases":["ticket"],"tags":[],"category":"Activities","description":"ticket","unicode_version":"6.0"},{"emoji":"🎖️","aliases":["medal_military"],"tags":[],"category":"Activities","description":"military medal","unicode_version":"7.0"},{"emoji":"🏆","aliases":["trophy"],"tags":["award","contest","winner"],"category":"Activities","description":"trophy","unicode_version":"6.0"},{"emoji":"🏅","aliases":["medal_sports"],"tags":["gold","winner"],"category":"Activities","description":"sports medal","unicode_version":"7.0"},{"emoji":"🥇","aliases":["1st_place_medal"],"tags":["gold"],"category":"Activities","description":"1st place medal","unicode_version":"9.0"},{"emoji":"🥈","aliases":["2nd_place_medal"],"tags":["silver"],"category":"Activities","description":"2nd place medal","unicode_version":"9.0"},{"emoji":"🥉","aliases":["3rd_place_medal"],"tags":["bronze"],"category":"Activities","description":"3rd place medal","unicode_version":"9.0"},{"emoji":"⚽","aliases":["soccer"],"tags":["sports"],"category":"Activities","description":"soccer ball","unicode_version":"5.2"},{"emoji":"⚾","aliases":["baseball"],"tags":["sports"],"category":"Activities","description":"baseball","unicode_version":"5.2"},{"emoji":"🥎","aliases":["softball"],"tags":[],"category":"Activities","description":"softball","unicode_version":"11.0"},{"emoji":"🏀","aliases":["basketball"],"tags":["sports"],"category":"Activities","description":"basketball","unicode_version":"6.0"},{"emoji":"🏐","aliases":["volleyball"],"tags":[],"category":"Activities","description":"volleyball","unicode_version":"8.0"},{"emoji":"🏈","aliases":["football"],"tags":["sports"],"category":"Activities","description":"american football","unicode_version":"6.0"},{"emoji":"🏉","aliases":["rugby_football"],"tags":[],"category":"Activities","description":"rugby football","unicode_version":"6.0"},{"emoji":"🎾","aliases":["tennis"],"tags":["sports"],"category":"Activities","description":"tennis","unicode_version":"6.0"},{"emoji":"🥏","aliases":["flying_disc"],"tags":[],"category":"Activities","description":"flying disc","unicode_version":"11.0"},{"emoji":"🎳","aliases":["bowling"],"tags":[],"category":"Activities","description":"bowling","unicode_version":"6.0"},{"emoji":"🏏","aliases":["cricket_game"],"tags":[],"category":"Activities","description":"cricket game","unicode_version":"8.0"},{"emoji":"🏑","aliases":["field_hockey"],"tags":[],"category":"Activities","description":"field hockey","unicode_version":"8.0"},{"emoji":"🏒","aliases":["ice_hockey"],"tags":[],"category":"Activities","description":"ice hockey","unicode_version":"8.0"},{"emoji":"🥍","aliases":["lacrosse"],"tags":[],"category":"Activities","description":"lacrosse","unicode_version":"11.0"},{"emoji":"🏓","aliases":["ping_pong"],"tags":[],"category":"Activities","description":"ping pong","unicode_version":"8.0"},{"emoji":"🏸","aliases":["badminton"],"tags":[],"category":"Activities","description":"badminton","unicode_version":"8.0"},{"emoji":"🥊","aliases":["boxing_glove"],"tags":[],"category":"Activities","description":"boxing glove","unicode_version":"9.0"},{"emoji":"🥋","aliases":["martial_arts_uniform"],"tags":[],"category":"Activities","description":"martial arts uniform","unicode_version":"9.0"},{"emoji":"🥅","aliases":["goal_net"],"tags":[],"category":"Activities","description":"goal net","unicode_version":"9.0"},{"emoji":"⛳","aliases":["golf"],"tags":[],"category":"Activities","description":"flag in hole","unicode_version":"5.2"},{"emoji":"⛸️","aliases":["ice_skate"],"tags":["skating"],"category":"Activities","description":"ice skate","unicode_version":"5.2"},{"emoji":"🎣","aliases":["fishing_pole_and_fish"],"tags":[],"category":"Activities","description":"fishing pole","unicode_version":"6.0"},{"emoji":"🤿","aliases":["diving_mask"],"tags":[],"category":"Activities","description":"diving mask","unicode_version":"12.0"},{"emoji":"🎽","aliases":["running_shirt_with_sash"],"tags":["marathon"],"category":"Activities","description":"running shirt","unicode_version":"6.0"},{"emoji":"🎿","aliases":["ski"],"tags":[],"category":"Activities","description":"skis","unicode_version":"6.0"},{"emoji":"🛷","aliases":["sled"],"tags":[],"category":"Activities","description":"sled","unicode_version":"11.0"},{"emoji":"🥌","aliases":["curling_stone"],"tags":[],"category":"Activities","description":"curling stone","unicode_version":"11.0"},{"emoji":"🎯","aliases":["dart"],"tags":["target"],"category":"Activities","description":"bullseye","unicode_version":"6.0"},{"emoji":"🪀","aliases":["yo_yo"],"tags":[],"category":"Activities","description":"yo-yo","unicode_version":"12.0"},{"emoji":"🪁","aliases":["kite"],"tags":[],"category":"Activities","description":"kite","unicode_version":"12.0"},{"emoji":"🎱","aliases":["8ball"],"tags":["pool","billiards"],"category":"Activities","description":"pool 8 ball","unicode_version":"6.0"},{"emoji":"🔮","aliases":["crystal_ball"],"tags":["fortune"],"category":"Activities","description":"crystal ball","unicode_version":"6.0"},{"emoji":"🪄","aliases":["magic_wand"],"tags":[],"category":"Activities","description":"magic wand","unicode_version":"13.0"},{"emoji":"🧿","aliases":["nazar_amulet"],"tags":[],"category":"Activities","description":"nazar amulet","unicode_version":"11.0"},{"emoji":"🎮","aliases":["video_game"],"tags":["play","controller","console"],"category":"Activities","description":"video game","unicode_version":"6.0"},{"emoji":"🕹️","aliases":["joystick"],"tags":[],"category":"Activities","description":"joystick","unicode_version":"7.0"},{"emoji":"🎰","aliases":["slot_machine"],"tags":[],"category":"Activities","description":"slot machine","unicode_version":"6.0"},{"emoji":"🎲","aliases":["game_die"],"tags":["dice","gambling"],"category":"Activities","description":"game die","unicode_version":"6.0"},{"emoji":"🧩","aliases":["jigsaw"],"tags":[],"category":"Activities","description":"puzzle piece","unicode_version":"11.0"},{"emoji":"🧸","aliases":["teddy_bear"],"tags":[],"category":"Activities","description":"teddy bear","unicode_version":"11.0"},{"emoji":"🪅","aliases":["pinata"],"tags":[],"category":"Activities","description":"piñata","unicode_version":"13.0"},{"emoji":"🪆","aliases":["nesting_dolls"],"tags":[],"category":"Activities","description":"nesting dolls","unicode_version":"13.0"},{"emoji":"♠️","aliases":["spades"],"tags":[],"category":"Activities","description":"spade suit","unicode_version":""},{"emoji":"♥️","aliases":["hearts"],"tags":[],"category":"Activities","description":"heart suit","unicode_version":""},{"emoji":"♦️","aliases":["diamonds"],"tags":[],"category":"Activities","description":"diamond suit","unicode_version":""},{"emoji":"♣️","aliases":["clubs"],"tags":[],"category":"Activities","description":"club suit","unicode_version":""},{"emoji":"♟️","aliases":["chess_pawn"],"tags":[],"category":"Activities","description":"chess pawn","unicode_version":"11.0"},{"emoji":"🃏","aliases":["black_joker"],"tags":[],"category":"Activities","description":"joker","unicode_version":"6.0"},{"emoji":"🀄","aliases":["mahjong"],"tags":[],"category":"Activities","description":"mahjong red dragon","unicode_version":""},{"emoji":"🎴","aliases":["flower_playing_cards"],"tags":[],"category":"Activities","description":"flower playing cards","unicode_version":"6.0"},{"emoji":"🎭","aliases":["performing_arts"],"tags":["theater","drama"],"category":"Activities","description":"performing arts","unicode_version":"6.0"},{"emoji":"🖼️","aliases":["framed_picture"],"tags":[],"category":"Activities","description":"framed picture","unicode_version":"7.0"},{"emoji":"🎨","aliases":["art"],"tags":["design","paint"],"category":"Activities","description":"artist palette","unicode_version":"6.0"},{"emoji":"🧵","aliases":["thread"],"tags":[],"category":"Activities","description":"thread","unicode_version":"11.0"},{"emoji":"🪡","aliases":["sewing_needle"],"tags":[],"category":"Activities","description":"sewing needle","unicode_version":"13.0"},{"emoji":"🧶","aliases":["yarn"],"tags":[],"category":"Activities","description":"yarn","unicode_version":"11.0"},{"emoji":"🪢","aliases":["knot"],"tags":[],"category":"Activities","description":"knot","unicode_version":"13.0"},{"emoji":"👓","aliases":["eyeglasses"],"tags":["glasses"],"category":"Objects","description":"glasses","unicode_version":"6.0"},{"emoji":"🕶️","aliases":["dark_sunglasses"],"tags":[],"category":"Objects","description":"sunglasses","unicode_version":"7.0"},{"emoji":"🥽","aliases":["goggles"],"tags":[],"category":"Objects","description":"goggles","unicode_version":"11.0"},{"emoji":"🥼","aliases":["lab_coat"],"tags":[],"category":"Objects","description":"lab coat","unicode_version":"11.0"},{"emoji":"🦺","aliases":["safety_vest"],"tags":[],"category":"Objects","description":"safety vest","unicode_version":"12.0"},{"emoji":"👔","aliases":["necktie"],"tags":["shirt","formal"],"category":"Objects","description":"necktie","unicode_version":"6.0"},{"emoji":"👕","aliases":["shirt","tshirt"],"tags":[],"category":"Objects","description":"t-shirt","unicode_version":"6.0"},{"emoji":"👖","aliases":["jeans"],"tags":["pants"],"category":"Objects","description":"jeans","unicode_version":"6.0"},{"emoji":"🧣","aliases":["scarf"],"tags":[],"category":"Objects","description":"scarf","unicode_version":"11.0"},{"emoji":"🧤","aliases":["gloves"],"tags":[],"category":"Objects","description":"gloves","unicode_version":"11.0"},{"emoji":"🧥","aliases":["coat"],"tags":[],"category":"Objects","description":"coat","unicode_version":"11.0"},{"emoji":"🧦","aliases":["socks"],"tags":[],"category":"Objects","description":"socks","unicode_version":"11.0"},{"emoji":"👗","aliases":["dress"],"tags":[],"category":"Objects","description":"dress","unicode_version":"6.0"},{"emoji":"👘","aliases":["kimono"],"tags":[],"category":"Objects","description":"kimono","unicode_version":"6.0"},{"emoji":"🥻","aliases":["sari"],"tags":[],"category":"Objects","description":"sari","unicode_version":"12.0"},{"emoji":"🩱","aliases":["one_piece_swimsuit"],"tags":[],"category":"Objects","description":"one-piece swimsuit","unicode_version":"12.0"},{"emoji":"🩲","aliases":["swim_brief"],"tags":[],"category":"Objects","description":"briefs","unicode_version":"12.0"},{"emoji":"🩳","aliases":["shorts"],"tags":[],"category":"Objects","description":"shorts","unicode_version":"12.0"},{"emoji":"👙","aliases":["bikini"],"tags":["beach"],"category":"Objects","description":"bikini","unicode_version":"6.0"},{"emoji":"👚","aliases":["womans_clothes"],"tags":[],"category":"Objects","description":"woman’s clothes","unicode_version":"6.0"},{"emoji":"👛","aliases":["purse"],"tags":[],"category":"Objects","description":"purse","unicode_version":"6.0"},{"emoji":"👜","aliases":["handbag"],"tags":["bag"],"category":"Objects","description":"handbag","unicode_version":"6.0"},{"emoji":"👝","aliases":["pouch"],"tags":["bag"],"category":"Objects","description":"clutch bag","unicode_version":"6.0"},{"emoji":"🛍️","aliases":["shopping"],"tags":["bags"],"category":"Objects","description":"shopping bags","unicode_version":"7.0"},{"emoji":"🎒","aliases":["school_satchel"],"tags":[],"category":"Objects","description":"backpack","unicode_version":"6.0"},{"emoji":"🩴","aliases":["thong_sandal"],"tags":[],"category":"Objects","description":"thong sandal","unicode_version":"13.0"},{"emoji":"👞","aliases":["mans_shoe","shoe"],"tags":[],"category":"Objects","description":"man’s shoe","unicode_version":"6.0"},{"emoji":"👟","aliases":["athletic_shoe"],"tags":["sneaker","sport","running"],"category":"Objects","description":"running shoe","unicode_version":"6.0"},{"emoji":"🥾","aliases":["hiking_boot"],"tags":[],"category":"Objects","description":"hiking boot","unicode_version":"11.0"},{"emoji":"🥿","aliases":["flat_shoe"],"tags":[],"category":"Objects","description":"flat shoe","unicode_version":"11.0"},{"emoji":"👠","aliases":["high_heel"],"tags":["shoe"],"category":"Objects","description":"high-heeled shoe","unicode_version":"6.0"},{"emoji":"👡","aliases":["sandal"],"tags":["shoe"],"category":"Objects","description":"woman’s sandal","unicode_version":"6.0"},{"emoji":"🩰","aliases":["ballet_shoes"],"tags":[],"category":"Objects","description":"ballet shoes","unicode_version":"12.0"},{"emoji":"👢","aliases":["boot"],"tags":[],"category":"Objects","description":"woman’s boot","unicode_version":"6.0"},{"emoji":"👑","aliases":["crown"],"tags":["king","queen","royal"],"category":"Objects","description":"crown","unicode_version":"6.0"},{"emoji":"👒","aliases":["womans_hat"],"tags":[],"category":"Objects","description":"woman’s hat","unicode_version":"6.0"},{"emoji":"🎩","aliases":["tophat"],"tags":["hat","classy"],"category":"Objects","description":"top hat","unicode_version":"6.0"},{"emoji":"🎓","aliases":["mortar_board"],"tags":["education","college","university","graduation"],"category":"Objects","description":"graduation cap","unicode_version":"6.0"},{"emoji":"🧢","aliases":["billed_cap"],"tags":[],"category":"Objects","description":"billed cap","unicode_version":"11.0"},{"emoji":"🪖","aliases":["military_helmet"],"tags":[],"category":"Objects","description":"military helmet","unicode_version":"13.0"},{"emoji":"⛑️","aliases":["rescue_worker_helmet"],"tags":[],"category":"Objects","description":"rescue worker’s helmet","unicode_version":"5.2"},{"emoji":"📿","aliases":["prayer_beads"],"tags":[],"category":"Objects","description":"prayer beads","unicode_version":"8.0"},{"emoji":"💄","aliases":["lipstick"],"tags":["makeup"],"category":"Objects","description":"lipstick","unicode_version":"6.0"},{"emoji":"💍","aliases":["ring"],"tags":["wedding","marriage","engaged"],"category":"Objects","description":"ring","unicode_version":"6.0"},{"emoji":"💎","aliases":["gem"],"tags":["diamond"],"category":"Objects","description":"gem stone","unicode_version":"6.0"},{"emoji":"🔇","aliases":["mute"],"tags":["sound","volume"],"category":"Objects","description":"muted speaker","unicode_version":"6.0"},{"emoji":"🔈","aliases":["speaker"],"tags":[],"category":"Objects","description":"speaker low volume","unicode_version":"6.0"},{"emoji":"🔉","aliases":["sound"],"tags":["volume"],"category":"Objects","description":"speaker medium volume","unicode_version":"6.0"},{"emoji":"🔊","aliases":["loud_sound"],"tags":["volume"],"category":"Objects","description":"speaker high volume","unicode_version":"6.0"},{"emoji":"📢","aliases":["loudspeaker"],"tags":["announcement"],"category":"Objects","description":"loudspeaker","unicode_version":"6.0"},{"emoji":"📣","aliases":["mega"],"tags":[],"category":"Objects","description":"megaphone","unicode_version":"6.0"},{"emoji":"📯","aliases":["postal_horn"],"tags":[],"category":"Objects","description":"postal horn","unicode_version":"6.0"},{"emoji":"🔔","aliases":["bell"],"tags":["sound","notification"],"category":"Objects","description":"bell","unicode_version":"6.0"},{"emoji":"🔕","aliases":["no_bell"],"tags":["volume","off"],"category":"Objects","description":"bell with slash","unicode_version":"6.0"},{"emoji":"🎼","aliases":["musical_score"],"tags":[],"category":"Objects","description":"musical score","unicode_version":"6.0"},{"emoji":"🎵","aliases":["musical_note"],"tags":[],"category":"Objects","description":"musical note","unicode_version":"6.0"},{"emoji":"🎶","aliases":["notes"],"tags":["music"],"category":"Objects","description":"musical notes","unicode_version":"6.0"},{"emoji":"🎙️","aliases":["studio_microphone"],"tags":["podcast"],"category":"Objects","description":"studio microphone","unicode_version":"7.0"},{"emoji":"🎚️","aliases":["level_slider"],"tags":[],"category":"Objects","description":"level slider","unicode_version":"7.0"},{"emoji":"🎛️","aliases":["control_knobs"],"tags":[],"category":"Objects","description":"control knobs","unicode_version":"7.0"},{"emoji":"🎤","aliases":["microphone"],"tags":["sing"],"category":"Objects","description":"microphone","unicode_version":"6.0"},{"emoji":"🎧","aliases":["headphones"],"tags":["music","earphones"],"category":"Objects","description":"headphone","unicode_version":"6.0"},{"emoji":"📻","aliases":["radio"],"tags":["podcast"],"category":"Objects","description":"radio","unicode_version":"6.0"},{"emoji":"🎷","aliases":["saxophone"],"tags":[],"category":"Objects","description":"saxophone","unicode_version":"6.0"},{"emoji":"🪗","aliases":["accordion"],"tags":[],"category":"Objects","description":"accordion","unicode_version":"13.0"},{"emoji":"🎸","aliases":["guitar"],"tags":["rock"],"category":"Objects","description":"guitar","unicode_version":"6.0"},{"emoji":"🎹","aliases":["musical_keyboard"],"tags":["piano"],"category":"Objects","description":"musical keyboard","unicode_version":"6.0"},{"emoji":"🎺","aliases":["trumpet"],"tags":[],"category":"Objects","description":"trumpet","unicode_version":"6.0"},{"emoji":"🎻","aliases":["violin"],"tags":[],"category":"Objects","description":"violin","unicode_version":"6.0"},{"emoji":"🪕","aliases":["banjo"],"tags":[],"category":"Objects","description":"banjo","unicode_version":"12.0"},{"emoji":"🥁","aliases":["drum"],"tags":[],"category":"Objects","description":"drum","unicode_version":""},{"emoji":"🪘","aliases":["long_drum"],"tags":[],"category":"Objects","description":"long drum","unicode_version":"13.0"},{"emoji":"📱","aliases":["iphone"],"tags":["smartphone","mobile"],"category":"Objects","description":"mobile phone","unicode_version":"6.0"},{"emoji":"📲","aliases":["calling"],"tags":["call","incoming"],"category":"Objects","description":"mobile phone with arrow","unicode_version":"6.0"},{"emoji":"☎️","aliases":["phone","telephone"],"tags":[],"category":"Objects","description":"telephone","unicode_version":""},{"emoji":"📞","aliases":["telephone_receiver"],"tags":["phone","call"],"category":"Objects","description":"telephone receiver","unicode_version":"6.0"},{"emoji":"📟","aliases":["pager"],"tags":[],"category":"Objects","description":"pager","unicode_version":"6.0"},{"emoji":"📠","aliases":["fax"],"tags":[],"category":"Objects","description":"fax machine","unicode_version":"6.0"},{"emoji":"🔋","aliases":["battery"],"tags":["power"],"category":"Objects","description":"battery","unicode_version":"6.0"},{"emoji":"🔌","aliases":["electric_plug"],"tags":[],"category":"Objects","description":"electric plug","unicode_version":"6.0"},{"emoji":"💻","aliases":["computer"],"tags":["desktop","screen"],"category":"Objects","description":"laptop","unicode_version":"6.0"},{"emoji":"🖥️","aliases":["desktop_computer"],"tags":[],"category":"Objects","description":"desktop computer","unicode_version":"7.0"},{"emoji":"🖨️","aliases":["printer"],"tags":[],"category":"Objects","description":"printer","unicode_version":"7.0"},{"emoji":"⌨️","aliases":["keyboard"],"tags":[],"category":"Objects","description":"keyboard","unicode_version":""},{"emoji":"🖱️","aliases":["computer_mouse"],"tags":[],"category":"Objects","description":"computer mouse","unicode_version":"7.0"},{"emoji":"🖲️","aliases":["trackball"],"tags":[],"category":"Objects","description":"trackball","unicode_version":"7.0"},{"emoji":"💽","aliases":["minidisc"],"tags":[],"category":"Objects","description":"computer disk","unicode_version":"6.0"},{"emoji":"💾","aliases":["floppy_disk"],"tags":["save"],"category":"Objects","description":"floppy disk","unicode_version":"6.0"},{"emoji":"💿","aliases":["cd"],"tags":[],"category":"Objects","description":"optical disk","unicode_version":"6.0"},{"emoji":"📀","aliases":["dvd"],"tags":[],"category":"Objects","description":"dvd","unicode_version":"6.0"},{"emoji":"🧮","aliases":["abacus"],"tags":[],"category":"Objects","description":"abacus","unicode_version":"11.0"},{"emoji":"🎥","aliases":["movie_camera"],"tags":["film","video"],"category":"Objects","description":"movie camera","unicode_version":"6.0"},{"emoji":"🎞️","aliases":["film_strip"],"tags":[],"category":"Objects","description":"film frames","unicode_version":"7.0"},{"emoji":"📽️","aliases":["film_projector"],"tags":[],"category":"Objects","description":"film projector","unicode_version":"7.0"},{"emoji":"🎬","aliases":["clapper"],"tags":["film"],"category":"Objects","description":"clapper board","unicode_version":"6.0"},{"emoji":"📺","aliases":["tv"],"tags":[],"category":"Objects","description":"television","unicode_version":"6.0"},{"emoji":"📷","aliases":["camera"],"tags":["photo"],"category":"Objects","description":"camera","unicode_version":"6.0"},{"emoji":"📸","aliases":["camera_flash"],"tags":["photo"],"category":"Objects","description":"camera with flash","unicode_version":"7.0"},{"emoji":"📹","aliases":["video_camera"],"tags":[],"category":"Objects","description":"video camera","unicode_version":"6.0"},{"emoji":"📼","aliases":["vhs"],"tags":[],"category":"Objects","description":"videocassette","unicode_version":"6.0"},{"emoji":"🔍","aliases":["mag"],"tags":["search","zoom"],"category":"Objects","description":"magnifying glass tilted left","unicode_version":"6.0"},{"emoji":"🔎","aliases":["mag_right"],"tags":[],"category":"Objects","description":"magnifying glass tilted right","unicode_version":"6.0"},{"emoji":"🕯️","aliases":["candle"],"tags":[],"category":"Objects","description":"candle","unicode_version":"7.0"},{"emoji":"💡","aliases":["bulb"],"tags":["idea","light"],"category":"Objects","description":"light bulb","unicode_version":"6.0"},{"emoji":"🔦","aliases":["flashlight"],"tags":[],"category":"Objects","description":"flashlight","unicode_version":"6.0"},{"emoji":"🏮","aliases":["izakaya_lantern","lantern"],"tags":[],"category":"Objects","description":"red paper lantern","unicode_version":"6.0"},{"emoji":"🪔","aliases":["diya_lamp"],"tags":[],"category":"Objects","description":"diya lamp","unicode_version":"12.0"},{"emoji":"📔","aliases":["notebook_with_decorative_cover"],"tags":[],"category":"Objects","description":"notebook with decorative cover","unicode_version":"6.0"},{"emoji":"📕","aliases":["closed_book"],"tags":[],"category":"Objects","description":"closed book","unicode_version":"6.0"},{"emoji":"📖","aliases":["book","open_book"],"tags":[],"category":"Objects","description":"open book","unicode_version":"6.0"},{"emoji":"📗","aliases":["green_book"],"tags":[],"category":"Objects","description":"green book","unicode_version":"6.0"},{"emoji":"📘","aliases":["blue_book"],"tags":[],"category":"Objects","description":"blue book","unicode_version":"6.0"},{"emoji":"📙","aliases":["orange_book"],"tags":[],"category":"Objects","description":"orange book","unicode_version":"6.0"},{"emoji":"📚","aliases":["books"],"tags":["library"],"category":"Objects","description":"books","unicode_version":"6.0"},{"emoji":"📓","aliases":["notebook"],"tags":[],"category":"Objects","description":"notebook","unicode_version":"6.0"},{"emoji":"📒","aliases":["ledger"],"tags":[],"category":"Objects","description":"ledger","unicode_version":"6.0"},{"emoji":"📃","aliases":["page_with_curl"],"tags":[],"category":"Objects","description":"page with curl","unicode_version":"6.0"},{"emoji":"📜","aliases":["scroll"],"tags":["document"],"category":"Objects","description":"scroll","unicode_version":"6.0"},{"emoji":"📄","aliases":["page_facing_up"],"tags":["document"],"category":"Objects","description":"page facing up","unicode_version":"6.0"},{"emoji":"📰","aliases":["newspaper"],"tags":["press"],"category":"Objects","description":"newspaper","unicode_version":"6.0"},{"emoji":"🗞️","aliases":["newspaper_roll"],"tags":["press"],"category":"Objects","description":"rolled-up newspaper","unicode_version":"7.0"},{"emoji":"📑","aliases":["bookmark_tabs"],"tags":[],"category":"Objects","description":"bookmark tabs","unicode_version":"6.0"},{"emoji":"🔖","aliases":["bookmark"],"tags":[],"category":"Objects","description":"bookmark","unicode_version":"6.0"},{"emoji":"🏷️","aliases":["label"],"tags":["tag"],"category":"Objects","description":"label","unicode_version":"7.0"},{"emoji":"💰","aliases":["moneybag"],"tags":["dollar","cream"],"category":"Objects","description":"money bag","unicode_version":"6.0"},{"emoji":"🪙","aliases":["coin"],"tags":[],"category":"Objects","description":"coin","unicode_version":"13.0"},{"emoji":"💴","aliases":["yen"],"tags":[],"category":"Objects","description":"yen banknote","unicode_version":"6.0"},{"emoji":"💵","aliases":["dollar"],"tags":["money"],"category":"Objects","description":"dollar banknote","unicode_version":"6.0"},{"emoji":"💶","aliases":["euro"],"tags":[],"category":"Objects","description":"euro banknote","unicode_version":"6.0"},{"emoji":"💷","aliases":["pound"],"tags":[],"category":"Objects","description":"pound banknote","unicode_version":"6.0"},{"emoji":"💸","aliases":["money_with_wings"],"tags":["dollar"],"category":"Objects","description":"money with wings","unicode_version":"6.0"},{"emoji":"💳","aliases":["credit_card"],"tags":["subscription"],"category":"Objects","description":"credit card","unicode_version":"6.0"},{"emoji":"🧾","aliases":["receipt"],"tags":[],"category":"Objects","description":"receipt","unicode_version":"11.0"},{"emoji":"💹","aliases":["chart"],"tags":[],"category":"Objects","description":"chart increasing with yen","unicode_version":"6.0"},{"emoji":"✉️","aliases":["envelope"],"tags":["letter","email"],"category":"Objects","description":"envelope","unicode_version":""},{"emoji":"📧","aliases":["email","e-mail"],"tags":[],"category":"Objects","description":"e-mail","unicode_version":"6.0"},{"emoji":"📨","aliases":["incoming_envelope"],"tags":[],"category":"Objects","description":"incoming envelope","unicode_version":"6.0"},{"emoji":"📩","aliases":["envelope_with_arrow"],"tags":[],"category":"Objects","description":"envelope with arrow","unicode_version":"6.0"},{"emoji":"📤","aliases":["outbox_tray"],"tags":[],"category":"Objects","description":"outbox tray","unicode_version":"6.0"},{"emoji":"📥","aliases":["inbox_tray"],"tags":[],"category":"Objects","description":"inbox tray","unicode_version":"6.0"},{"emoji":"📦","aliases":["package"],"tags":["shipping"],"category":"Objects","description":"package","unicode_version":"6.0"},{"emoji":"📫","aliases":["mailbox"],"tags":[],"category":"Objects","description":"closed mailbox with raised flag","unicode_version":"6.0"},{"emoji":"📪","aliases":["mailbox_closed"],"tags":[],"category":"Objects","description":"closed mailbox with lowered flag","unicode_version":"6.0"},{"emoji":"📬","aliases":["mailbox_with_mail"],"tags":[],"category":"Objects","description":"open mailbox with raised flag","unicode_version":"6.0"},{"emoji":"📭","aliases":["mailbox_with_no_mail"],"tags":[],"category":"Objects","description":"open mailbox with lowered flag","unicode_version":"6.0"},{"emoji":"📮","aliases":["postbox"],"tags":[],"category":"Objects","description":"postbox","unicode_version":"6.0"},{"emoji":"🗳️","aliases":["ballot_box"],"tags":[],"category":"Objects","description":"ballot box with ballot","unicode_version":"7.0"},{"emoji":"✏️","aliases":["pencil2"],"tags":[],"category":"Objects","description":"pencil","unicode_version":""},{"emoji":"✒️","aliases":["black_nib"],"tags":[],"category":"Objects","description":"black nib","unicode_version":""},{"emoji":"🖋️","aliases":["fountain_pen"],"tags":[],"category":"Objects","description":"fountain pen","unicode_version":"7.0"},{"emoji":"🖊️","aliases":["pen"],"tags":[],"category":"Objects","description":"pen","unicode_version":"7.0"},{"emoji":"🖌️","aliases":["paintbrush"],"tags":[],"category":"Objects","description":"paintbrush","unicode_version":"7.0"},{"emoji":"🖍️","aliases":["crayon"],"tags":[],"category":"Objects","description":"crayon","unicode_version":"7.0"},{"emoji":"📝","aliases":["memo","pencil"],"tags":["document","note"],"category":"Objects","description":"memo","unicode_version":"6.0"},{"emoji":"💼","aliases":["briefcase"],"tags":["business"],"category":"Objects","description":"briefcase","unicode_version":"6.0"},{"emoji":"📁","aliases":["file_folder"],"tags":["directory"],"category":"Objects","description":"file folder","unicode_version":"6.0"},{"emoji":"📂","aliases":["open_file_folder"],"tags":[],"category":"Objects","description":"open file folder","unicode_version":"6.0"},{"emoji":"🗂️","aliases":["card_index_dividers"],"tags":[],"category":"Objects","description":"card index dividers","unicode_version":"7.0"},{"emoji":"📅","aliases":["date"],"tags":["calendar","schedule"],"category":"Objects","description":"calendar","unicode_version":"6.0"},{"emoji":"📆","aliases":["calendar"],"tags":["schedule"],"category":"Objects","description":"tear-off calendar","unicode_version":"6.0"},{"emoji":"🗒️","aliases":["spiral_notepad"],"tags":[],"category":"Objects","description":"spiral notepad","unicode_version":"7.0"},{"emoji":"🗓️","aliases":["spiral_calendar"],"tags":[],"category":"Objects","description":"spiral calendar","unicode_version":"7.0"},{"emoji":"📇","aliases":["card_index"],"tags":[],"category":"Objects","description":"card index","unicode_version":"6.0"},{"emoji":"📈","aliases":["chart_with_upwards_trend"],"tags":["graph","metrics"],"category":"Objects","description":"chart increasing","unicode_version":"6.0"},{"emoji":"📉","aliases":["chart_with_downwards_trend"],"tags":["graph","metrics"],"category":"Objects","description":"chart decreasing","unicode_version":"6.0"},{"emoji":"📊","aliases":["bar_chart"],"tags":["stats","metrics"],"category":"Objects","description":"bar chart","unicode_version":"6.0"},{"emoji":"📋","aliases":["clipboard"],"tags":[],"category":"Objects","description":"clipboard","unicode_version":"6.0"},{"emoji":"📌","aliases":["pushpin"],"tags":["location"],"category":"Objects","description":"pushpin","unicode_version":"6.0"},{"emoji":"📍","aliases":["round_pushpin"],"tags":["location"],"category":"Objects","description":"round pushpin","unicode_version":"6.0"},{"emoji":"📎","aliases":["paperclip"],"tags":[],"category":"Objects","description":"paperclip","unicode_version":"6.0"},{"emoji":"🖇️","aliases":["paperclips"],"tags":[],"category":"Objects","description":"linked paperclips","unicode_version":"7.0"},{"emoji":"📏","aliases":["straight_ruler"],"tags":[],"category":"Objects","description":"straight ruler","unicode_version":"6.0"},{"emoji":"📐","aliases":["triangular_ruler"],"tags":[],"category":"Objects","description":"triangular ruler","unicode_version":"6.0"},{"emoji":"✂️","aliases":["scissors"],"tags":["cut"],"category":"Objects","description":"scissors","unicode_version":""},{"emoji":"🗃️","aliases":["card_file_box"],"tags":[],"category":"Objects","description":"card file box","unicode_version":"7.0"},{"emoji":"🗄️","aliases":["file_cabinet"],"tags":[],"category":"Objects","description":"file cabinet","unicode_version":"7.0"},{"emoji":"🗑️","aliases":["wastebasket"],"tags":["trash"],"category":"Objects","description":"wastebasket","unicode_version":"7.0"},{"emoji":"🔒","aliases":["lock"],"tags":["security","private"],"category":"Objects","description":"locked","unicode_version":"6.0"},{"emoji":"🔓","aliases":["unlock"],"tags":["security"],"category":"Objects","description":"unlocked","unicode_version":"6.0"},{"emoji":"🔏","aliases":["lock_with_ink_pen"],"tags":[],"category":"Objects","description":"locked with pen","unicode_version":"6.0"},{"emoji":"🔐","aliases":["closed_lock_with_key"],"tags":["security"],"category":"Objects","description":"locked with key","unicode_version":"6.0"},{"emoji":"🔑","aliases":["key"],"tags":["lock","password"],"category":"Objects","description":"key","unicode_version":"6.0"},{"emoji":"🗝️","aliases":["old_key"],"tags":[],"category":"Objects","description":"old key","unicode_version":"7.0"},{"emoji":"🔨","aliases":["hammer"],"tags":["tool"],"category":"Objects","description":"hammer","unicode_version":"6.0"},{"emoji":"🪓","aliases":["axe"],"tags":[],"category":"Objects","description":"axe","unicode_version":"12.0"},{"emoji":"⛏️","aliases":["pick"],"tags":[],"category":"Objects","description":"pick","unicode_version":"5.2"},{"emoji":"⚒️","aliases":["hammer_and_pick"],"tags":[],"category":"Objects","description":"hammer and pick","unicode_version":"4.1"},{"emoji":"🛠️","aliases":["hammer_and_wrench"],"tags":[],"category":"Objects","description":"hammer and wrench","unicode_version":"7.0"},{"emoji":"🗡️","aliases":["dagger"],"tags":[],"category":"Objects","description":"dagger","unicode_version":"7.0"},{"emoji":"⚔️","aliases":["crossed_swords"],"tags":[],"category":"Objects","description":"crossed swords","unicode_version":"4.1"},{"emoji":"🔫","aliases":["gun"],"tags":["shoot","weapon"],"category":"Objects","description":"water pistol","unicode_version":"6.0"},{"emoji":"🪃","aliases":["boomerang"],"tags":[],"category":"Objects","description":"boomerang","unicode_version":"13.0"},{"emoji":"🏹","aliases":["bow_and_arrow"],"tags":["archery"],"category":"Objects","description":"bow and arrow","unicode_version":"8.0"},{"emoji":"🛡️","aliases":["shield"],"tags":[],"category":"Objects","description":"shield","unicode_version":"7.0"},{"emoji":"🪚","aliases":["carpentry_saw"],"tags":[],"category":"Objects","description":"carpentry saw","unicode_version":"13.0"},{"emoji":"🔧","aliases":["wrench"],"tags":["tool"],"category":"Objects","description":"wrench","unicode_version":"6.0"},{"emoji":"🪛","aliases":["screwdriver"],"tags":[],"category":"Objects","description":"screwdriver","unicode_version":"13.0"},{"emoji":"🔩","aliases":["nut_and_bolt"],"tags":[],"category":"Objects","description":"nut and bolt","unicode_version":"6.0"},{"emoji":"⚙️","aliases":["gear"],"tags":[],"category":"Objects","description":"gear","unicode_version":"4.1"},{"emoji":"🗜️","aliases":["clamp"],"tags":[],"category":"Objects","description":"clamp","unicode_version":"7.0"},{"emoji":"⚖️","aliases":["balance_scale"],"tags":[],"category":"Objects","description":"balance scale","unicode_version":"4.1"},{"emoji":"🦯","aliases":["probing_cane"],"tags":[],"category":"Objects","description":"white cane","unicode_version":"12.0"},{"emoji":"🔗","aliases":["link"],"tags":[],"category":"Objects","description":"link","unicode_version":"6.0"},{"emoji":"⛓️","aliases":["chains"],"tags":[],"category":"Objects","description":"chains","unicode_version":"5.2"},{"emoji":"🪝","aliases":["hook"],"tags":[],"category":"Objects","description":"hook","unicode_version":"13.0"},{"emoji":"🧰","aliases":["toolbox"],"tags":[],"category":"Objects","description":"toolbox","unicode_version":"11.0"},{"emoji":"🧲","aliases":["magnet"],"tags":[],"category":"Objects","description":"magnet","unicode_version":"11.0"},{"emoji":"🪜","aliases":["ladder"],"tags":[],"category":"Objects","description":"ladder","unicode_version":"13.0"},{"emoji":"⚗️","aliases":["alembic"],"tags":[],"category":"Objects","description":"alembic","unicode_version":"4.1"},{"emoji":"🧪","aliases":["test_tube"],"tags":[],"category":"Objects","description":"test tube","unicode_version":"11.0"},{"emoji":"🧫","aliases":["petri_dish"],"tags":[],"category":"Objects","description":"petri dish","unicode_version":"11.0"},{"emoji":"🧬","aliases":["dna"],"tags":[],"category":"Objects","description":"dna","unicode_version":"11.0"},{"emoji":"🔬","aliases":["microscope"],"tags":["science","laboratory","investigate"],"category":"Objects","description":"microscope","unicode_version":"6.0"},{"emoji":"🔭","aliases":["telescope"],"tags":[],"category":"Objects","description":"telescope","unicode_version":"6.0"},{"emoji":"📡","aliases":["satellite"],"tags":["signal"],"category":"Objects","description":"satellite antenna","unicode_version":"6.0"},{"emoji":"💉","aliases":["syringe"],"tags":["health","hospital","needle"],"category":"Objects","description":"syringe","unicode_version":"6.0"},{"emoji":"🩸","aliases":["drop_of_blood"],"tags":[],"category":"Objects","description":"drop of blood","unicode_version":"12.0"},{"emoji":"💊","aliases":["pill"],"tags":["health","medicine"],"category":"Objects","description":"pill","unicode_version":"6.0"},{"emoji":"🩹","aliases":["adhesive_bandage"],"tags":[],"category":"Objects","description":"adhesive bandage","unicode_version":"12.0"},{"emoji":"🩺","aliases":["stethoscope"],"tags":[],"category":"Objects","description":"stethoscope","unicode_version":"12.0"},{"emoji":"🚪","aliases":["door"],"tags":[],"category":"Objects","description":"door","unicode_version":"6.0"},{"emoji":"🛗","aliases":["elevator"],"tags":[],"category":"Objects","description":"elevator","unicode_version":"13.0"},{"emoji":"🪞","aliases":["mirror"],"tags":[],"category":"Objects","description":"mirror","unicode_version":"13.0"},{"emoji":"🪟","aliases":["window"],"tags":[],"category":"Objects","description":"window","unicode_version":"13.0"},{"emoji":"🛏️","aliases":["bed"],"tags":[],"category":"Objects","description":"bed","unicode_version":"7.0"},{"emoji":"🛋️","aliases":["couch_and_lamp"],"tags":[],"category":"Objects","description":"couch and lamp","unicode_version":"7.0"},{"emoji":"🪑","aliases":["chair"],"tags":[],"category":"Objects","description":"chair","unicode_version":"12.0"},{"emoji":"🚽","aliases":["toilet"],"tags":["wc"],"category":"Objects","description":"toilet","unicode_version":"6.0"},{"emoji":"🪠","aliases":["plunger"],"tags":[],"category":"Objects","description":"plunger","unicode_version":"13.0"},{"emoji":"🚿","aliases":["shower"],"tags":["bath"],"category":"Objects","description":"shower","unicode_version":"6.0"},{"emoji":"🛁","aliases":["bathtub"],"tags":[],"category":"Objects","description":"bathtub","unicode_version":"6.0"},{"emoji":"🪤","aliases":["mouse_trap"],"tags":[],"category":"Objects","description":"mouse trap","unicode_version":"13.0"},{"emoji":"🪒","aliases":["razor"],"tags":[],"category":"Objects","description":"razor","unicode_version":"12.0"},{"emoji":"🧴","aliases":["lotion_bottle"],"tags":[],"category":"Objects","description":"lotion bottle","unicode_version":"11.0"},{"emoji":"🧷","aliases":["safety_pin"],"tags":[],"category":"Objects","description":"safety pin","unicode_version":"11.0"},{"emoji":"🧹","aliases":["broom"],"tags":[],"category":"Objects","description":"broom","unicode_version":"11.0"},{"emoji":"🧺","aliases":["basket"],"tags":[],"category":"Objects","description":"basket","unicode_version":"11.0"},{"emoji":"🧻","aliases":["roll_of_paper"],"tags":["toilet"],"category":"Objects","description":"roll of paper","unicode_version":"11.0"},{"emoji":"🪣","aliases":["bucket"],"tags":[],"category":"Objects","description":"bucket","unicode_version":"13.0"},{"emoji":"🧼","aliases":["soap"],"tags":[],"category":"Objects","description":"soap","unicode_version":"11.0"},{"emoji":"🪥","aliases":["toothbrush"],"tags":[],"category":"Objects","description":"toothbrush","unicode_version":"13.0"},{"emoji":"🧽","aliases":["sponge"],"tags":[],"category":"Objects","description":"sponge","unicode_version":"11.0"},{"emoji":"🧯","aliases":["fire_extinguisher"],"tags":[],"category":"Objects","description":"fire extinguisher","unicode_version":"11.0"},{"emoji":"🛒","aliases":["shopping_cart"],"tags":[],"category":"Objects","description":"shopping cart","unicode_version":"9.0"},{"emoji":"🚬","aliases":["smoking"],"tags":["cigarette"],"category":"Objects","description":"cigarette","unicode_version":"6.0"},{"emoji":"⚰️","aliases":["coffin"],"tags":["funeral"],"category":"Objects","description":"coffin","unicode_version":"4.1"},{"emoji":"🪦","aliases":["headstone"],"tags":[],"category":"Objects","description":"headstone","unicode_version":"13.0"},{"emoji":"⚱️","aliases":["funeral_urn"],"tags":[],"category":"Objects","description":"funeral urn","unicode_version":"4.1"},{"emoji":"🗿","aliases":["moyai"],"tags":["stone"],"category":"Objects","description":"moai","unicode_version":"6.0"},{"emoji":"🪧","aliases":["placard"],"tags":[],"category":"Objects","description":"placard","unicode_version":"13.0"},{"emoji":"🏧","aliases":["atm"],"tags":[],"category":"Symbols","description":"ATM sign","unicode_version":"6.0"},{"emoji":"🚮","aliases":["put_litter_in_its_place"],"tags":[],"category":"Symbols","description":"litter in bin sign","unicode_version":"6.0"},{"emoji":"🚰","aliases":["potable_water"],"tags":[],"category":"Symbols","description":"potable water","unicode_version":"6.0"},{"emoji":"♿","aliases":["wheelchair"],"tags":["accessibility"],"category":"Symbols","description":"wheelchair symbol","unicode_version":"4.1"},{"emoji":"🚹","aliases":["mens"],"tags":[],"category":"Symbols","description":"men’s room","unicode_version":"6.0"},{"emoji":"🚺","aliases":["womens"],"tags":[],"category":"Symbols","description":"women’s room","unicode_version":"6.0"},{"emoji":"🚻","aliases":["restroom"],"tags":["toilet"],"category":"Symbols","description":"restroom","unicode_version":"6.0"},{"emoji":"🚼","aliases":["baby_symbol"],"tags":[],"category":"Symbols","description":"baby symbol","unicode_version":"6.0"},{"emoji":"🚾","aliases":["wc"],"tags":["toilet","restroom"],"category":"Symbols","description":"water closet","unicode_version":"6.0"},{"emoji":"🛂","aliases":["passport_control"],"tags":[],"category":"Symbols","description":"passport control","unicode_version":"6.0"},{"emoji":"🛃","aliases":["customs"],"tags":[],"category":"Symbols","description":"customs","unicode_version":"6.0"},{"emoji":"🛄","aliases":["baggage_claim"],"tags":["airport"],"category":"Symbols","description":"baggage claim","unicode_version":"6.0"},{"emoji":"🛅","aliases":["left_luggage"],"tags":[],"category":"Symbols","description":"left luggage","unicode_version":"6.0"},{"emoji":"⚠️","aliases":["warning"],"tags":["wip"],"category":"Symbols","description":"warning","unicode_version":"4.0"},{"emoji":"🚸","aliases":["children_crossing"],"tags":[],"category":"Symbols","description":"children crossing","unicode_version":"6.0"},{"emoji":"⛔","aliases":["no_entry"],"tags":["limit"],"category":"Symbols","description":"no entry","unicode_version":"5.2"},{"emoji":"🚫","aliases":["no_entry_sign"],"tags":["block","forbidden"],"category":"Symbols","description":"prohibited","unicode_version":"6.0"},{"emoji":"🚳","aliases":["no_bicycles"],"tags":[],"category":"Symbols","description":"no bicycles","unicode_version":"6.0"},{"emoji":"🚭","aliases":["no_smoking"],"tags":[],"category":"Symbols","description":"no smoking","unicode_version":"6.0"},{"emoji":"🚯","aliases":["do_not_litter"],"tags":[],"category":"Symbols","description":"no littering","unicode_version":"6.0"},{"emoji":"🚱","aliases":["non-potable_water"],"tags":[],"category":"Symbols","description":"non-potable water","unicode_version":"6.0"},{"emoji":"🚷","aliases":["no_pedestrians"],"tags":[],"category":"Symbols","description":"no pedestrians","unicode_version":"6.0"},{"emoji":"📵","aliases":["no_mobile_phones"],"tags":[],"category":"Symbols","description":"no mobile phones","unicode_version":"6.0"},{"emoji":"🔞","aliases":["underage"],"tags":[],"category":"Symbols","description":"no one under eighteen","unicode_version":"6.0"},{"emoji":"☢️","aliases":["radioactive"],"tags":[],"category":"Symbols","description":"radioactive","unicode_version":""},{"emoji":"☣️","aliases":["biohazard"],"tags":[],"category":"Symbols","description":"biohazard","unicode_version":""},{"emoji":"⬆️","aliases":["arrow_up"],"tags":[],"category":"Symbols","description":"up arrow","unicode_version":"4.0"},{"emoji":"↗️","aliases":["arrow_upper_right"],"tags":[],"category":"Symbols","description":"up-right arrow","unicode_version":""},{"emoji":"➡️","aliases":["arrow_right"],"tags":[],"category":"Symbols","description":"right arrow","unicode_version":""},{"emoji":"↘️","aliases":["arrow_lower_right"],"tags":[],"category":"Symbols","description":"down-right arrow","unicode_version":""},{"emoji":"⬇️","aliases":["arrow_down"],"tags":[],"category":"Symbols","description":"down arrow","unicode_version":"4.0"},{"emoji":"↙️","aliases":["arrow_lower_left"],"tags":[],"category":"Symbols","description":"down-left arrow","unicode_version":""},{"emoji":"⬅️","aliases":["arrow_left"],"tags":[],"category":"Symbols","description":"left arrow","unicode_version":"4.0"},{"emoji":"↖️","aliases":["arrow_upper_left"],"tags":[],"category":"Symbols","description":"up-left arrow","unicode_version":""},{"emoji":"↕️","aliases":["arrow_up_down"],"tags":[],"category":"Symbols","description":"up-down arrow","unicode_version":""},{"emoji":"↔️","aliases":["left_right_arrow"],"tags":[],"category":"Symbols","description":"left-right arrow","unicode_version":""},{"emoji":"↩️","aliases":["leftwards_arrow_with_hook"],"tags":["return"],"category":"Symbols","description":"right arrow curving left","unicode_version":""},{"emoji":"↪️","aliases":["arrow_right_hook"],"tags":[],"category":"Symbols","description":"left arrow curving right","unicode_version":""},{"emoji":"⤴️","aliases":["arrow_heading_up"],"tags":[],"category":"Symbols","description":"right arrow curving up","unicode_version":""},{"emoji":"⤵️","aliases":["arrow_heading_down"],"tags":[],"category":"Symbols","description":"right arrow curving down","unicode_version":""},{"emoji":"🔃","aliases":["arrows_clockwise"],"tags":[],"category":"Symbols","description":"clockwise vertical arrows","unicode_version":"6.0"},{"emoji":"🔄","aliases":["arrows_counterclockwise"],"tags":["sync"],"category":"Symbols","description":"counterclockwise arrows button","unicode_version":"6.0"},{"emoji":"🔙","aliases":["back"],"tags":[],"category":"Symbols","description":"BACK arrow","unicode_version":"6.0"},{"emoji":"🔚","aliases":["end"],"tags":[],"category":"Symbols","description":"END arrow","unicode_version":"6.0"},{"emoji":"🔛","aliases":["on"],"tags":[],"category":"Symbols","description":"ON! arrow","unicode_version":"6.0"},{"emoji":"🔜","aliases":["soon"],"tags":[],"category":"Symbols","description":"SOON arrow","unicode_version":"6.0"},{"emoji":"🔝","aliases":["top"],"tags":[],"category":"Symbols","description":"TOP arrow","unicode_version":"6.0"},{"emoji":"🛐","aliases":["place_of_worship"],"tags":[],"category":"Symbols","description":"place of worship","unicode_version":"8.0"},{"emoji":"⚛️","aliases":["atom_symbol"],"tags":[],"category":"Symbols","description":"atom symbol","unicode_version":"4.1"},{"emoji":"🕉️","aliases":["om"],"tags":[],"category":"Symbols","description":"om","unicode_version":"7.0"},{"emoji":"✡️","aliases":["star_of_david"],"tags":[],"category":"Symbols","description":"star of David","unicode_version":""},{"emoji":"☸️","aliases":["wheel_of_dharma"],"tags":[],"category":"Symbols","description":"wheel of dharma","unicode_version":""},{"emoji":"☯️","aliases":["yin_yang"],"tags":[],"category":"Symbols","description":"yin yang","unicode_version":""},{"emoji":"✝️","aliases":["latin_cross"],"tags":[],"category":"Symbols","description":"latin cross","unicode_version":""},{"emoji":"☦️","aliases":["orthodox_cross"],"tags":[],"category":"Symbols","description":"orthodox cross","unicode_version":""},{"emoji":"☪️","aliases":["star_and_crescent"],"tags":[],"category":"Symbols","description":"star and crescent","unicode_version":""},{"emoji":"☮️","aliases":["peace_symbol"],"tags":[],"category":"Symbols","description":"peace symbol","unicode_version":""},{"emoji":"🕎","aliases":["menorah"],"tags":[],"category":"Symbols","description":"menorah","unicode_version":"8.0"},{"emoji":"🔯","aliases":["six_pointed_star"],"tags":[],"category":"Symbols","description":"dotted six-pointed star","unicode_version":"6.0"},{"emoji":"♈","aliases":["aries"],"tags":[],"category":"Symbols","description":"Aries","unicode_version":""},{"emoji":"♉","aliases":["taurus"],"tags":[],"category":"Symbols","description":"Taurus","unicode_version":""},{"emoji":"♊","aliases":["gemini"],"tags":[],"category":"Symbols","description":"Gemini","unicode_version":""},{"emoji":"♋","aliases":["cancer"],"tags":[],"category":"Symbols","description":"Cancer","unicode_version":""},{"emoji":"♌","aliases":["leo"],"tags":[],"category":"Symbols","description":"Leo","unicode_version":""},{"emoji":"♍","aliases":["virgo"],"tags":[],"category":"Symbols","description":"Virgo","unicode_version":""},{"emoji":"♎","aliases":["libra"],"tags":[],"category":"Symbols","description":"Libra","unicode_version":""},{"emoji":"♏","aliases":["scorpius"],"tags":[],"category":"Symbols","description":"Scorpio","unicode_version":""},{"emoji":"♐","aliases":["sagittarius"],"tags":[],"category":"Symbols","description":"Sagittarius","unicode_version":""},{"emoji":"♑","aliases":["capricorn"],"tags":[],"category":"Symbols","description":"Capricorn","unicode_version":""},{"emoji":"♒","aliases":["aquarius"],"tags":[],"category":"Symbols","description":"Aquarius","unicode_version":""},{"emoji":"♓","aliases":["pisces"],"tags":[],"category":"Symbols","description":"Pisces","unicode_version":""},{"emoji":"⛎","aliases":["ophiuchus"],"tags":[],"category":"Symbols","description":"Ophiuchus","unicode_version":"6.0"},{"emoji":"🔀","aliases":["twisted_rightwards_arrows"],"tags":["shuffle"],"category":"Symbols","description":"shuffle tracks button","unicode_version":"6.0"},{"emoji":"🔁","aliases":["repeat"],"tags":["loop"],"category":"Symbols","description":"repeat button","unicode_version":"6.0"},{"emoji":"🔂","aliases":["repeat_one"],"tags":[],"category":"Symbols","description":"repeat single button","unicode_version":"6.0"},{"emoji":"▶️","aliases":["arrow_forward"],"tags":[],"category":"Symbols","description":"play button","unicode_version":""},{"emoji":"⏩","aliases":["fast_forward"],"tags":[],"category":"Symbols","description":"fast-forward button","unicode_version":"6.0"},{"emoji":"⏭️","aliases":["next_track_button"],"tags":[],"category":"Symbols","description":"next track button","unicode_version":"6.0"},{"emoji":"⏯️","aliases":["play_or_pause_button"],"tags":[],"category":"Symbols","description":"play or pause button","unicode_version":"6.0"},{"emoji":"◀️","aliases":["arrow_backward"],"tags":[],"category":"Symbols","description":"reverse button","unicode_version":""},{"emoji":"⏪","aliases":["rewind"],"tags":[],"category":"Symbols","description":"fast reverse button","unicode_version":"6.0"},{"emoji":"⏮️","aliases":["previous_track_button"],"tags":[],"category":"Symbols","description":"last track button","unicode_version":"6.0"},{"emoji":"🔼","aliases":["arrow_up_small"],"tags":[],"category":"Symbols","description":"upwards button","unicode_version":"6.0"},{"emoji":"⏫","aliases":["arrow_double_up"],"tags":[],"category":"Symbols","description":"fast up button","unicode_version":"6.0"},{"emoji":"🔽","aliases":["arrow_down_small"],"tags":[],"category":"Symbols","description":"downwards button","unicode_version":"6.0"},{"emoji":"⏬","aliases":["arrow_double_down"],"tags":[],"category":"Symbols","description":"fast down button","unicode_version":"6.0"},{"emoji":"⏸️","aliases":["pause_button"],"tags":[],"category":"Symbols","description":"pause button","unicode_version":"7.0"},{"emoji":"⏹️","aliases":["stop_button"],"tags":[],"category":"Symbols","description":"stop button","unicode_version":"7.0"},{"emoji":"⏺️","aliases":["record_button"],"tags":[],"category":"Symbols","description":"record button","unicode_version":"7.0"},{"emoji":"⏏️","aliases":["eject_button"],"tags":[],"category":"Symbols","description":"eject button","unicode_version":"11.0"},{"emoji":"🎦","aliases":["cinema"],"tags":["film","movie"],"category":"Symbols","description":"cinema","unicode_version":"6.0"},{"emoji":"🔅","aliases":["low_brightness"],"tags":[],"category":"Symbols","description":"dim button","unicode_version":"6.0"},{"emoji":"🔆","aliases":["high_brightness"],"tags":[],"category":"Symbols","description":"bright button","unicode_version":"6.0"},{"emoji":"📶","aliases":["signal_strength"],"tags":["wifi"],"category":"Symbols","description":"antenna bars","unicode_version":"6.0"},{"emoji":"📳","aliases":["vibration_mode"],"tags":[],"category":"Symbols","description":"vibration mode","unicode_version":"6.0"},{"emoji":"📴","aliases":["mobile_phone_off"],"tags":["mute","off"],"category":"Symbols","description":"mobile phone off","unicode_version":"6.0"},{"emoji":"♀️","aliases":["female_sign"],"tags":[],"category":"Symbols","description":"female sign","unicode_version":"11.0"},{"emoji":"♂️","aliases":["male_sign"],"tags":[],"category":"Symbols","description":"male sign","unicode_version":"11.0"},{"emoji":"⚧️","aliases":["transgender_symbol"],"tags":[],"category":"Symbols","description":"transgender symbol","unicode_version":"13.0"},{"emoji":"✖️","aliases":["heavy_multiplication_x"],"tags":[],"category":"Symbols","description":"multiply","unicode_version":""},{"emoji":"➕","aliases":["heavy_plus_sign"],"tags":[],"category":"Symbols","description":"plus","unicode_version":"6.0"},{"emoji":"➖","aliases":["heavy_minus_sign"],"tags":[],"category":"Symbols","description":"minus","unicode_version":"6.0"},{"emoji":"➗","aliases":["heavy_division_sign"],"tags":[],"category":"Symbols","description":"divide","unicode_version":"6.0"},{"emoji":"♾️","aliases":["infinity"],"tags":[],"category":"Symbols","description":"infinity","unicode_version":"11.0"},{"emoji":"‼️","aliases":["bangbang"],"tags":[],"category":"Symbols","description":"double exclamation mark","unicode_version":""},{"emoji":"⁉️","aliases":["interrobang"],"tags":[],"category":"Symbols","description":"exclamation question mark","unicode_version":"3.0"},{"emoji":"❓","aliases":["question"],"tags":["confused"],"category":"Symbols","description":"red question mark","unicode_version":"6.0"},{"emoji":"❔","aliases":["grey_question"],"tags":[],"category":"Symbols","description":"white question mark","unicode_version":"6.0"},{"emoji":"❕","aliases":["grey_exclamation"],"tags":[],"category":"Symbols","description":"white exclamation mark","unicode_version":"6.0"},{"emoji":"❗","aliases":["exclamation","heavy_exclamation_mark"],"tags":["bang"],"category":"Symbols","description":"red exclamation mark","unicode_version":"5.2"},{"emoji":"〰️","aliases":["wavy_dash"],"tags":[],"category":"Symbols","description":"wavy dash","unicode_version":""},{"emoji":"💱","aliases":["currency_exchange"],"tags":[],"category":"Symbols","description":"currency exchange","unicode_version":"6.0"},{"emoji":"💲","aliases":["heavy_dollar_sign"],"tags":[],"category":"Symbols","description":"heavy dollar sign","unicode_version":"6.0"},{"emoji":"⚕️","aliases":["medical_symbol"],"tags":[],"category":"Symbols","description":"medical symbol","unicode_version":"11.0"},{"emoji":"♻️","aliases":["recycle"],"tags":["environment","green"],"category":"Symbols","description":"recycling symbol","unicode_version":"3.2"},{"emoji":"⚜️","aliases":["fleur_de_lis"],"tags":[],"category":"Symbols","description":"fleur-de-lis","unicode_version":"4.1"},{"emoji":"🔱","aliases":["trident"],"tags":[],"category":"Symbols","description":"trident emblem","unicode_version":"6.0"},{"emoji":"📛","aliases":["name_badge"],"tags":[],"category":"Symbols","description":"name badge","unicode_version":"6.0"},{"emoji":"🔰","aliases":["beginner"],"tags":[],"category":"Symbols","description":"Japanese symbol for beginner","unicode_version":"6.0"},{"emoji":"⭕","aliases":["o"],"tags":[],"category":"Symbols","description":"hollow red circle","unicode_version":"5.2"},{"emoji":"✅","aliases":["white_check_mark"],"tags":[],"category":"Symbols","description":"check mark button","unicode_version":"6.0"},{"emoji":"☑️","aliases":["ballot_box_with_check"],"tags":[],"category":"Symbols","description":"check box with check","unicode_version":""},{"emoji":"✔️","aliases":["heavy_check_mark"],"tags":[],"category":"Symbols","description":"check mark","unicode_version":""},{"emoji":"❌","aliases":["x"],"tags":[],"category":"Symbols","description":"cross mark","unicode_version":"6.0"},{"emoji":"❎","aliases":["negative_squared_cross_mark"],"tags":[],"category":"Symbols","description":"cross mark button","unicode_version":"6.0"},{"emoji":"➰","aliases":["curly_loop"],"tags":[],"category":"Symbols","description":"curly loop","unicode_version":"6.0"},{"emoji":"➿","aliases":["loop"],"tags":[],"category":"Symbols","description":"double curly loop","unicode_version":"6.0"},{"emoji":"〽️","aliases":["part_alternation_mark"],"tags":[],"category":"Symbols","description":"part alternation mark","unicode_version":"3.2"},{"emoji":"✳️","aliases":["eight_spoked_asterisk"],"tags":[],"category":"Symbols","description":"eight-spoked asterisk","unicode_version":""},{"emoji":"✴️","aliases":["eight_pointed_black_star"],"tags":[],"category":"Symbols","description":"eight-pointed star","unicode_version":""},{"emoji":"❇️","aliases":["sparkle"],"tags":[],"category":"Symbols","description":"sparkle","unicode_version":""},{"emoji":"©️","aliases":["copyright"],"tags":[],"category":"Symbols","description":"copyright","unicode_version":""},{"emoji":"®️","aliases":["registered"],"tags":[],"category":"Symbols","description":"registered","unicode_version":""},{"emoji":"™️","aliases":["tm"],"tags":["trademark"],"category":"Symbols","description":"trade mark","unicode_version":""},{"emoji":"#️⃣","aliases":["hash"],"tags":["number"],"category":"Symbols","description":"keycap: #","unicode_version":""},{"emoji":"*️⃣","aliases":["asterisk"],"tags":[],"category":"Symbols","description":"keycap: *","unicode_version":""},{"emoji":"0️⃣","aliases":["zero"],"tags":[],"category":"Symbols","description":"keycap: 0","unicode_version":""},{"emoji":"1️⃣","aliases":["one"],"tags":[],"category":"Symbols","description":"keycap: 1","unicode_version":""},{"emoji":"2️⃣","aliases":["two"],"tags":[],"category":"Symbols","description":"keycap: 2","unicode_version":""},{"emoji":"3️⃣","aliases":["three"],"tags":[],"category":"Symbols","description":"keycap: 3","unicode_version":""},{"emoji":"4️⃣","aliases":["four"],"tags":[],"category":"Symbols","description":"keycap: 4","unicode_version":""},{"emoji":"5️⃣","aliases":["five"],"tags":[],"category":"Symbols","description":"keycap: 5","unicode_version":""},{"emoji":"6️⃣","aliases":["six"],"tags":[],"category":"Symbols","description":"keycap: 6","unicode_version":""},{"emoji":"7️⃣","aliases":["seven"],"tags":[],"category":"Symbols","description":"keycap: 7","unicode_version":""},{"emoji":"8️⃣","aliases":["eight"],"tags":[],"category":"Symbols","description":"keycap: 8","unicode_version":""},{"emoji":"9️⃣","aliases":["nine"],"tags":[],"category":"Symbols","description":"keycap: 9","unicode_version":""},{"emoji":"🔟","aliases":["keycap_ten"],"tags":[],"category":"Symbols","description":"keycap: 10","unicode_version":"6.0"},{"emoji":"🔠","aliases":["capital_abcd"],"tags":["letters"],"category":"Symbols","description":"input latin uppercase","unicode_version":"6.0"},{"emoji":"🔡","aliases":["abcd"],"tags":[],"category":"Symbols","description":"input latin lowercase","unicode_version":"6.0"},{"emoji":"🔢","aliases":["1234"],"tags":["numbers"],"category":"Symbols","description":"input numbers","unicode_version":"6.0"},{"emoji":"🔣","aliases":["symbols"],"tags":[],"category":"Symbols","description":"input symbols","unicode_version":"6.0"},{"emoji":"🔤","aliases":["abc"],"tags":["alphabet"],"category":"Symbols","description":"input latin letters","unicode_version":"6.0"},{"emoji":"🅰️","aliases":["a"],"tags":[],"category":"Symbols","description":"A button (blood type)","unicode_version":"6.0"},{"emoji":"🆎","aliases":["ab"],"tags":[],"category":"Symbols","description":"AB button (blood type)","unicode_version":"6.0"},{"emoji":"🅱️","aliases":["b"],"tags":[],"category":"Symbols","description":"B button (blood type)","unicode_version":"6.0"},{"emoji":"🆑","aliases":["cl"],"tags":[],"category":"Symbols","description":"CL button","unicode_version":"6.0"},{"emoji":"🆒","aliases":["cool"],"tags":[],"category":"Symbols","description":"COOL button","unicode_version":"6.0"},{"emoji":"🆓","aliases":["free"],"tags":[],"category":"Symbols","description":"FREE button","unicode_version":"6.0"},{"emoji":"ℹ️","aliases":["information_source"],"tags":[],"category":"Symbols","description":"information","unicode_version":"3.0"},{"emoji":"🆔","aliases":["id"],"tags":[],"category":"Symbols","description":"ID button","unicode_version":"6.0"},{"emoji":"Ⓜ️","aliases":["m"],"tags":[],"category":"Symbols","description":"circled M","unicode_version":""},{"emoji":"🆕","aliases":["new"],"tags":["fresh"],"category":"Symbols","description":"NEW button","unicode_version":"6.0"},{"emoji":"🆖","aliases":["ng"],"tags":[],"category":"Symbols","description":"NG button","unicode_version":"6.0"},{"emoji":"🅾️","aliases":["o2"],"tags":[],"category":"Symbols","description":"O button (blood type)","unicode_version":"6.0"},{"emoji":"🆗","aliases":["ok"],"tags":["yes"],"category":"Symbols","description":"OK button","unicode_version":"6.0"},{"emoji":"🅿️","aliases":["parking"],"tags":[],"category":"Symbols","description":"P button","unicode_version":"5.2"},{"emoji":"🆘","aliases":["sos"],"tags":["help","emergency"],"category":"Symbols","description":"SOS button","unicode_version":"6.0"},{"emoji":"🆙","aliases":["up"],"tags":[],"category":"Symbols","description":"UP! button","unicode_version":"6.0"},{"emoji":"🆚","aliases":["vs"],"tags":[],"category":"Symbols","description":"VS button","unicode_version":"6.0"},{"emoji":"🈁","aliases":["koko"],"tags":[],"category":"Symbols","description":"Japanese “here” button","unicode_version":"6.0"},{"emoji":"🈂️","aliases":["sa"],"tags":[],"category":"Symbols","description":"Japanese “service charge” button","unicode_version":"6.0"},{"emoji":"🈷️","aliases":["u6708"],"tags":[],"category":"Symbols","description":"Japanese “monthly amount” button","unicode_version":"6.0"},{"emoji":"🈶","aliases":["u6709"],"tags":[],"category":"Symbols","description":"Japanese “not free of charge” button","unicode_version":"6.0"},{"emoji":"🈯","aliases":["u6307"],"tags":[],"category":"Symbols","description":"Japanese “reserved” button","unicode_version":""},{"emoji":"🉐","aliases":["ideograph_advantage"],"tags":[],"category":"Symbols","description":"Japanese “bargain” button","unicode_version":"6.0"},{"emoji":"🈹","aliases":["u5272"],"tags":[],"category":"Symbols","description":"Japanese “discount” button","unicode_version":"6.0"},{"emoji":"🈚","aliases":["u7121"],"tags":[],"category":"Symbols","description":"Japanese “free of charge” button","unicode_version":""},{"emoji":"🈲","aliases":["u7981"],"tags":[],"category":"Symbols","description":"Japanese “prohibited” button","unicode_version":"6.0"},{"emoji":"🉑","aliases":["accept"],"tags":[],"category":"Symbols","description":"Japanese “acceptable” button","unicode_version":"6.0"},{"emoji":"🈸","aliases":["u7533"],"tags":[],"category":"Symbols","description":"Japanese “application” button","unicode_version":"6.0"},{"emoji":"🈴","aliases":["u5408"],"tags":[],"category":"Symbols","description":"Japanese “passing grade” button","unicode_version":"6.0"},{"emoji":"🈳","aliases":["u7a7a"],"tags":[],"category":"Symbols","description":"Japanese “vacancy” button","unicode_version":"6.0"},{"emoji":"㊗️","aliases":["congratulations"],"tags":[],"category":"Symbols","description":"Japanese “congratulations” button","unicode_version":""},{"emoji":"㊙️","aliases":["secret"],"tags":[],"category":"Symbols","description":"Japanese “secret” button","unicode_version":""},{"emoji":"🈺","aliases":["u55b6"],"tags":[],"category":"Symbols","description":"Japanese “open for business” button","unicode_version":"6.0"},{"emoji":"🈵","aliases":["u6e80"],"tags":[],"category":"Symbols","description":"Japanese “no vacancy” button","unicode_version":"6.0"},{"emoji":"🔴","aliases":["red_circle"],"tags":[],"category":"Symbols","description":"red circle","unicode_version":"6.0"},{"emoji":"🟠","aliases":["orange_circle"],"tags":[],"category":"Symbols","description":"orange circle","unicode_version":"12.0"},{"emoji":"🟡","aliases":["yellow_circle"],"tags":[],"category":"Symbols","description":"yellow circle","unicode_version":"12.0"},{"emoji":"🟢","aliases":["green_circle"],"tags":[],"category":"Symbols","description":"green circle","unicode_version":"12.0"},{"emoji":"🔵","aliases":["large_blue_circle"],"tags":[],"category":"Symbols","description":"blue circle","unicode_version":"6.0"},{"emoji":"🟣","aliases":["purple_circle"],"tags":[],"category":"Symbols","description":"purple circle","unicode_version":"12.0"},{"emoji":"🟤","aliases":["brown_circle"],"tags":[],"category":"Symbols","description":"brown circle","unicode_version":"12.0"},{"emoji":"⚫","aliases":["black_circle"],"tags":[],"category":"Symbols","description":"black circle","unicode_version":"4.1"},{"emoji":"⚪","aliases":["white_circle"],"tags":[],"category":"Symbols","description":"white circle","unicode_version":"4.1"},{"emoji":"🟥","aliases":["red_square"],"tags":[],"category":"Symbols","description":"red square","unicode_version":"12.0"},{"emoji":"🟧","aliases":["orange_square"],"tags":[],"category":"Symbols","description":"orange square","unicode_version":"12.0"},{"emoji":"🟨","aliases":["yellow_square"],"tags":[],"category":"Symbols","description":"yellow square","unicode_version":"12.0"},{"emoji":"🟩","aliases":["green_square"],"tags":[],"category":"Symbols","description":"green square","unicode_version":"12.0"},{"emoji":"🟦","aliases":["blue_square"],"tags":[],"category":"Symbols","description":"blue square","unicode_version":"12.0"},{"emoji":"🟪","aliases":["purple_square"],"tags":[],"category":"Symbols","description":"purple square","unicode_version":"12.0"},{"emoji":"🟫","aliases":["brown_square"],"tags":[],"category":"Symbols","description":"brown square","unicode_version":"12.0"},{"emoji":"⬛","aliases":["black_large_square"],"tags":[],"category":"Symbols","description":"black large square","unicode_version":"5.1"},{"emoji":"⬜","aliases":["white_large_square"],"tags":[],"category":"Symbols","description":"white large square","unicode_version":"5.1"},{"emoji":"◼️","aliases":["black_medium_square"],"tags":[],"category":"Symbols","description":"black medium square","unicode_version":"3.2"},{"emoji":"◻️","aliases":["white_medium_square"],"tags":[],"category":"Symbols","description":"white medium square","unicode_version":"3.2"},{"emoji":"◾","aliases":["black_medium_small_square"],"tags":[],"category":"Symbols","description":"black medium-small square","unicode_version":"3.2"},{"emoji":"◽","aliases":["white_medium_small_square"],"tags":[],"category":"Symbols","description":"white medium-small square","unicode_version":"3.2"},{"emoji":"▪️","aliases":["black_small_square"],"tags":[],"category":"Symbols","description":"black small square","unicode_version":""},{"emoji":"▫️","aliases":["white_small_square"],"tags":[],"category":"Symbols","description":"white small square","unicode_version":""},{"emoji":"🔶","aliases":["large_orange_diamond"],"tags":[],"category":"Symbols","description":"large orange diamond","unicode_version":"6.0"},{"emoji":"🔷","aliases":["large_blue_diamond"],"tags":[],"category":"Symbols","description":"large blue diamond","unicode_version":"6.0"},{"emoji":"🔸","aliases":["small_orange_diamond"],"tags":[],"category":"Symbols","description":"small orange diamond","unicode_version":"6.0"},{"emoji":"🔹","aliases":["small_blue_diamond"],"tags":[],"category":"Symbols","description":"small blue diamond","unicode_version":"6.0"},{"emoji":"🔺","aliases":["small_red_triangle"],"tags":[],"category":"Symbols","description":"red triangle pointed up","unicode_version":"6.0"},{"emoji":"🔻","aliases":["small_red_triangle_down"],"tags":[],"category":"Symbols","description":"red triangle pointed down","unicode_version":"6.0"},{"emoji":"💠","aliases":["diamond_shape_with_a_dot_inside"],"tags":[],"category":"Symbols","description":"diamond with a dot","unicode_version":"6.0"},{"emoji":"🔘","aliases":["radio_button"],"tags":[],"category":"Symbols","description":"radio button","unicode_version":"6.0"},{"emoji":"🔳","aliases":["white_square_button"],"tags":[],"category":"Symbols","description":"white square button","unicode_version":"6.0"},{"emoji":"🔲","aliases":["black_square_button"],"tags":[],"category":"Symbols","description":"black square button","unicode_version":"6.0"},{"emoji":"🏁","aliases":["checkered_flag"],"tags":["milestone","finish"],"category":"Flags","description":"chequered flag","unicode_version":"6.0"},{"emoji":"🚩","aliases":["triangular_flag_on_post"],"tags":[],"category":"Flags","description":"triangular flag","unicode_version":"6.0"},{"emoji":"🎌","aliases":["crossed_flags"],"tags":[],"category":"Flags","description":"crossed flags","unicode_version":"6.0"},{"emoji":"🏴","aliases":["black_flag"],"tags":[],"category":"Flags","description":"black flag","unicode_version":"7.0"},{"emoji":"🏳️","aliases":["white_flag"],"tags":[],"category":"Flags","description":"white flag","unicode_version":"7.0"},{"emoji":"🏳️‍🌈","aliases":["rainbow_flag"],"tags":["pride"],"category":"Flags","description":"rainbow flag","unicode_version":"6.0"},{"emoji":"🏳️‍⚧️","aliases":["transgender_flag"],"tags":[],"category":"Flags","description":"transgender flag","unicode_version":"13.0"},{"emoji":"🏴‍☠️","aliases":["pirate_flag"],"tags":[],"category":"Flags","description":"pirate flag","unicode_version":"11.0"},{"emoji":"🇦🇨","aliases":["ascension_island"],"tags":[],"category":"Flags","description":"flag: Ascension Island","unicode_version":"11.0"},{"emoji":"🇦🇩","aliases":["andorra"],"tags":[],"category":"Flags","description":"flag: Andorra","unicode_version":"6.0"},{"emoji":"🇦🇪","aliases":["united_arab_emirates"],"tags":[],"category":"Flags","description":"flag: United Arab Emirates","unicode_version":"6.0"},{"emoji":"🇦🇫","aliases":["afghanistan"],"tags":[],"category":"Flags","description":"flag: Afghanistan","unicode_version":"6.0"},{"emoji":"🇦🇬","aliases":["antigua_barbuda"],"tags":[],"category":"Flags","description":"flag: Antigua & Barbuda","unicode_version":"6.0"},{"emoji":"🇦🇮","aliases":["anguilla"],"tags":[],"category":"Flags","description":"flag: Anguilla","unicode_version":"6.0"},{"emoji":"🇦🇱","aliases":["albania"],"tags":[],"category":"Flags","description":"flag: Albania","unicode_version":"6.0"},{"emoji":"🇦🇲","aliases":["armenia"],"tags":[],"category":"Flags","description":"flag: Armenia","unicode_version":"6.0"},{"emoji":"🇦🇴","aliases":["angola"],"tags":[],"category":"Flags","description":"flag: Angola","unicode_version":"6.0"},{"emoji":"🇦🇶","aliases":["antarctica"],"tags":[],"category":"Flags","description":"flag: Antarctica","unicode_version":"6.0"},{"emoji":"🇦🇷","aliases":["argentina"],"tags":[],"category":"Flags","description":"flag: Argentina","unicode_version":"6.0"},{"emoji":"🇦🇸","aliases":["american_samoa"],"tags":[],"category":"Flags","description":"flag: American Samoa","unicode_version":"6.0"},{"emoji":"🇦🇹","aliases":["austria"],"tags":[],"category":"Flags","description":"flag: Austria","unicode_version":"6.0"},{"emoji":"🇦🇺","aliases":["australia"],"tags":[],"category":"Flags","description":"flag: Australia","unicode_version":"6.0"},{"emoji":"🇦🇼","aliases":["aruba"],"tags":[],"category":"Flags","description":"flag: Aruba","unicode_version":"6.0"},{"emoji":"🇦🇽","aliases":["aland_islands"],"tags":[],"category":"Flags","description":"flag: Åland Islands","unicode_version":"6.0"},{"emoji":"🇦🇿","aliases":["azerbaijan"],"tags":[],"category":"Flags","description":"flag: Azerbaijan","unicode_version":"6.0"},{"emoji":"🇧🇦","aliases":["bosnia_herzegovina"],"tags":[],"category":"Flags","description":"flag: Bosnia & Herzegovina","unicode_version":"6.0"},{"emoji":"🇧🇧","aliases":["barbados"],"tags":[],"category":"Flags","description":"flag: Barbados","unicode_version":"6.0"},{"emoji":"🇧🇩","aliases":["bangladesh"],"tags":[],"category":"Flags","description":"flag: Bangladesh","unicode_version":"6.0"},{"emoji":"🇧🇪","aliases":["belgium"],"tags":[],"category":"Flags","description":"flag: Belgium","unicode_version":"6.0"},{"emoji":"🇧🇫","aliases":["burkina_faso"],"tags":[],"category":"Flags","description":"flag: Burkina Faso","unicode_version":"6.0"},{"emoji":"🇧🇬","aliases":["bulgaria"],"tags":[],"category":"Flags","description":"flag: Bulgaria","unicode_version":"6.0"},{"emoji":"🇧🇭","aliases":["bahrain"],"tags":[],"category":"Flags","description":"flag: Bahrain","unicode_version":"6.0"},{"emoji":"🇧🇮","aliases":["burundi"],"tags":[],"category":"Flags","description":"flag: Burundi","unicode_version":"6.0"},{"emoji":"🇧🇯","aliases":["benin"],"tags":[],"category":"Flags","description":"flag: Benin","unicode_version":"6.0"},{"emoji":"🇧🇱","aliases":["st_barthelemy"],"tags":[],"category":"Flags","description":"flag: St. Barthélemy","unicode_version":"6.0"},{"emoji":"🇧🇲","aliases":["bermuda"],"tags":[],"category":"Flags","description":"flag: Bermuda","unicode_version":"6.0"},{"emoji":"🇧🇳","aliases":["brunei"],"tags":[],"category":"Flags","description":"flag: Brunei","unicode_version":"6.0"},{"emoji":"🇧🇴","aliases":["bolivia"],"tags":[],"category":"Flags","description":"flag: Bolivia","unicode_version":"6.0"},{"emoji":"🇧🇶","aliases":["caribbean_netherlands"],"tags":[],"category":"Flags","description":"flag: Caribbean Netherlands","unicode_version":"6.0"},{"emoji":"🇧🇷","aliases":["brazil"],"tags":[],"category":"Flags","description":"flag: Brazil","unicode_version":"6.0"},{"emoji":"🇧🇸","aliases":["bahamas"],"tags":[],"category":"Flags","description":"flag: Bahamas","unicode_version":"6.0"},{"emoji":"🇧🇹","aliases":["bhutan"],"tags":[],"category":"Flags","description":"flag: Bhutan","unicode_version":"6.0"},{"emoji":"🇧🇻","aliases":["bouvet_island"],"tags":[],"category":"Flags","description":"flag: Bouvet Island","unicode_version":"11.0"},{"emoji":"🇧🇼","aliases":["botswana"],"tags":[],"category":"Flags","description":"flag: Botswana","unicode_version":"6.0"},{"emoji":"🇧🇾","aliases":["belarus"],"tags":[],"category":"Flags","description":"flag: Belarus","unicode_version":"6.0"},{"emoji":"🇧🇿","aliases":["belize"],"tags":[],"category":"Flags","description":"flag: Belize","unicode_version":"6.0"},{"emoji":"🇨🇦","aliases":["canada"],"tags":[],"category":"Flags","description":"flag: Canada","unicode_version":"6.0"},{"emoji":"🇨🇨","aliases":["cocos_islands"],"tags":["keeling"],"category":"Flags","description":"flag: Cocos (Keeling) Islands","unicode_version":"6.0"},{"emoji":"🇨🇩","aliases":["congo_kinshasa"],"tags":[],"category":"Flags","description":"flag: Congo - Kinshasa","unicode_version":"6.0"},{"emoji":"🇨🇫","aliases":["central_african_republic"],"tags":[],"category":"Flags","description":"flag: Central African Republic","unicode_version":"6.0"},{"emoji":"🇨🇬","aliases":["congo_brazzaville"],"tags":[],"category":"Flags","description":"flag: Congo - Brazzaville","unicode_version":"6.0"},{"emoji":"🇨🇭","aliases":["switzerland"],"tags":[],"category":"Flags","description":"flag: Switzerland","unicode_version":"6.0"},{"emoji":"🇨🇮","aliases":["cote_divoire"],"tags":["ivory"],"category":"Flags","description":"flag: Côte d’Ivoire","unicode_version":"6.0"},{"emoji":"🇨🇰","aliases":["cook_islands"],"tags":[],"category":"Flags","description":"flag: Cook Islands","unicode_version":"6.0"},{"emoji":"🇨🇱","aliases":["chile"],"tags":[],"category":"Flags","description":"flag: Chile","unicode_version":"6.0"},{"emoji":"🇨🇲","aliases":["cameroon"],"tags":[],"category":"Flags","description":"flag: Cameroon","unicode_version":"6.0"},{"emoji":"🇨🇳","aliases":["cn"],"tags":["china"],"category":"Flags","description":"flag: China","unicode_version":"6.0"},{"emoji":"🇨🇴","aliases":["colombia"],"tags":[],"category":"Flags","description":"flag: Colombia","unicode_version":"6.0"},{"emoji":"🇨🇵","aliases":["clipperton_island"],"tags":[],"category":"Flags","description":"flag: Clipperton Island","unicode_version":"11.0"},{"emoji":"🇨🇷","aliases":["costa_rica"],"tags":[],"category":"Flags","description":"flag: Costa Rica","unicode_version":"6.0"},{"emoji":"🇨🇺","aliases":["cuba"],"tags":[],"category":"Flags","description":"flag: Cuba","unicode_version":"6.0"},{"emoji":"🇨🇻","aliases":["cape_verde"],"tags":[],"category":"Flags","description":"flag: Cape Verde","unicode_version":"6.0"},{"emoji":"🇨🇼","aliases":["curacao"],"tags":[],"category":"Flags","description":"flag: Curaçao","unicode_version":"6.0"},{"emoji":"🇨🇽","aliases":["christmas_island"],"tags":[],"category":"Flags","description":"flag: Christmas Island","unicode_version":"6.0"},{"emoji":"🇨🇾","aliases":["cyprus"],"tags":[],"category":"Flags","description":"flag: Cyprus","unicode_version":"6.0"},{"emoji":"🇨🇿","aliases":["czech_republic"],"tags":[],"category":"Flags","description":"flag: Czechia","unicode_version":"6.0"},{"emoji":"🇩🇪","aliases":["de"],"tags":["flag","germany"],"category":"Flags","description":"flag: Germany","unicode_version":"6.0"},{"emoji":"🇩🇬","aliases":["diego_garcia"],"tags":[],"category":"Flags","description":"flag: Diego Garcia","unicode_version":"11.0"},{"emoji":"🇩🇯","aliases":["djibouti"],"tags":[],"category":"Flags","description":"flag: Djibouti","unicode_version":"6.0"},{"emoji":"🇩🇰","aliases":["denmark"],"tags":[],"category":"Flags","description":"flag: Denmark","unicode_version":"6.0"},{"emoji":"🇩🇲","aliases":["dominica"],"tags":[],"category":"Flags","description":"flag: Dominica","unicode_version":"6.0"},{"emoji":"🇩🇴","aliases":["dominican_republic"],"tags":[],"category":"Flags","description":"flag: Dominican Republic","unicode_version":"6.0"},{"emoji":"🇩🇿","aliases":["algeria"],"tags":[],"category":"Flags","description":"flag: Algeria","unicode_version":"6.0"},{"emoji":"🇪🇦","aliases":["ceuta_melilla"],"tags":[],"category":"Flags","description":"flag: Ceuta & Melilla","unicode_version":"11.0"},{"emoji":"🇪🇨","aliases":["ecuador"],"tags":[],"category":"Flags","description":"flag: Ecuador","unicode_version":"6.0"},{"emoji":"🇪🇪","aliases":["estonia"],"tags":[],"category":"Flags","description":"flag: Estonia","unicode_version":"6.0"},{"emoji":"🇪🇬","aliases":["egypt"],"tags":[],"category":"Flags","description":"flag: Egypt","unicode_version":"6.0"},{"emoji":"🇪🇭","aliases":["western_sahara"],"tags":[],"category":"Flags","description":"flag: Western Sahara","unicode_version":"6.0"},{"emoji":"🇪🇷","aliases":["eritrea"],"tags":[],"category":"Flags","description":"flag: Eritrea","unicode_version":"6.0"},{"emoji":"🇪🇸","aliases":["es"],"tags":["spain"],"category":"Flags","description":"flag: Spain","unicode_version":"6.0"},{"emoji":"🇪🇹","aliases":["ethiopia"],"tags":[],"category":"Flags","description":"flag: Ethiopia","unicode_version":"6.0"},{"emoji":"🇪🇺","aliases":["eu","european_union"],"tags":[],"category":"Flags","description":"flag: European Union","unicode_version":"6.0"},{"emoji":"🇫🇮","aliases":["finland"],"tags":[],"category":"Flags","description":"flag: Finland","unicode_version":"6.0"},{"emoji":"🇫🇯","aliases":["fiji"],"tags":[],"category":"Flags","description":"flag: Fiji","unicode_version":"6.0"},{"emoji":"🇫🇰","aliases":["falkland_islands"],"tags":[],"category":"Flags","description":"flag: Falkland Islands","unicode_version":"6.0"},{"emoji":"🇫🇲","aliases":["micronesia"],"tags":[],"category":"Flags","description":"flag: Micronesia","unicode_version":"6.0"},{"emoji":"🇫🇴","aliases":["faroe_islands"],"tags":[],"category":"Flags","description":"flag: Faroe Islands","unicode_version":"6.0"},{"emoji":"🇫🇷","aliases":["fr"],"tags":["france","french"],"category":"Flags","description":"flag: France","unicode_version":"6.0"},{"emoji":"🇬🇦","aliases":["gabon"],"tags":[],"category":"Flags","description":"flag: Gabon","unicode_version":"6.0"},{"emoji":"🇬🇧","aliases":["gb","uk"],"tags":["flag","british"],"category":"Flags","description":"flag: United Kingdom","unicode_version":"6.0"},{"emoji":"🇬🇩","aliases":["grenada"],"tags":[],"category":"Flags","description":"flag: Grenada","unicode_version":"6.0"},{"emoji":"🇬🇪","aliases":["georgia"],"tags":[],"category":"Flags","description":"flag: Georgia","unicode_version":"6.0"},{"emoji":"🇬🇫","aliases":["french_guiana"],"tags":[],"category":"Flags","description":"flag: French Guiana","unicode_version":"6.0"},{"emoji":"🇬🇬","aliases":["guernsey"],"tags":[],"category":"Flags","description":"flag: Guernsey","unicode_version":"6.0"},{"emoji":"🇬🇭","aliases":["ghana"],"tags":[],"category":"Flags","description":"flag: Ghana","unicode_version":"6.0"},{"emoji":"🇬🇮","aliases":["gibraltar"],"tags":[],"category":"Flags","description":"flag: Gibraltar","unicode_version":"6.0"},{"emoji":"🇬🇱","aliases":["greenland"],"tags":[],"category":"Flags","description":"flag: Greenland","unicode_version":"6.0"},{"emoji":"🇬🇲","aliases":["gambia"],"tags":[],"category":"Flags","description":"flag: Gambia","unicode_version":"6.0"},{"emoji":"🇬🇳","aliases":["guinea"],"tags":[],"category":"Flags","description":"flag: Guinea","unicode_version":"6.0"},{"emoji":"🇬🇵","aliases":["guadeloupe"],"tags":[],"category":"Flags","description":"flag: Guadeloupe","unicode_version":"6.0"},{"emoji":"🇬🇶","aliases":["equatorial_guinea"],"tags":[],"category":"Flags","description":"flag: Equatorial Guinea","unicode_version":"6.0"},{"emoji":"🇬🇷","aliases":["greece"],"tags":[],"category":"Flags","description":"flag: Greece","unicode_version":"6.0"},{"emoji":"🇬🇸","aliases":["south_georgia_south_sandwich_islands"],"tags":[],"category":"Flags","description":"flag: South Georgia & South Sandwich Islands","unicode_version":"6.0"},{"emoji":"🇬🇹","aliases":["guatemala"],"tags":[],"category":"Flags","description":"flag: Guatemala","unicode_version":"6.0"},{"emoji":"🇬🇺","aliases":["guam"],"tags":[],"category":"Flags","description":"flag: Guam","unicode_version":"6.0"},{"emoji":"🇬🇼","aliases":["guinea_bissau"],"tags":[],"category":"Flags","description":"flag: Guinea-Bissau","unicode_version":"6.0"},{"emoji":"🇬🇾","aliases":["guyana"],"tags":[],"category":"Flags","description":"flag: Guyana","unicode_version":"6.0"},{"emoji":"🇭🇰","aliases":["hong_kong"],"tags":[],"category":"Flags","description":"flag: Hong Kong SAR China","unicode_version":"6.0"},{"emoji":"🇭🇲","aliases":["heard_mcdonald_islands"],"tags":[],"category":"Flags","description":"flag: Heard & McDonald Islands","unicode_version":"11.0"},{"emoji":"🇭🇳","aliases":["honduras"],"tags":[],"category":"Flags","description":"flag: Honduras","unicode_version":"6.0"},{"emoji":"🇭🇷","aliases":["croatia"],"tags":[],"category":"Flags","description":"flag: Croatia","unicode_version":"6.0"},{"emoji":"🇭🇹","aliases":["haiti"],"tags":[],"category":"Flags","description":"flag: Haiti","unicode_version":"6.0"},{"emoji":"🇭🇺","aliases":["hungary"],"tags":[],"category":"Flags","description":"flag: Hungary","unicode_version":"6.0"},{"emoji":"🇮🇨","aliases":["canary_islands"],"tags":[],"category":"Flags","description":"flag: Canary Islands","unicode_version":"6.0"},{"emoji":"🇮🇩","aliases":["indonesia"],"tags":[],"category":"Flags","description":"flag: Indonesia","unicode_version":"6.0"},{"emoji":"🇮🇪","aliases":["ireland"],"tags":[],"category":"Flags","description":"flag: Ireland","unicode_version":"6.0"},{"emoji":"🇮🇱","aliases":["israel"],"tags":[],"category":"Flags","description":"flag: Israel","unicode_version":"6.0"},{"emoji":"🇮🇲","aliases":["isle_of_man"],"tags":[],"category":"Flags","description":"flag: Isle of Man","unicode_version":"6.0"},{"emoji":"🇮🇳","aliases":["india"],"tags":[],"category":"Flags","description":"flag: India","unicode_version":"6.0"},{"emoji":"🇮🇴","aliases":["british_indian_ocean_territory"],"tags":[],"category":"Flags","description":"flag: British Indian Ocean Territory","unicode_version":"6.0"},{"emoji":"🇮🇶","aliases":["iraq"],"tags":[],"category":"Flags","description":"flag: Iraq","unicode_version":"6.0"},{"emoji":"🇮🇷","aliases":["iran"],"tags":[],"category":"Flags","description":"flag: Iran","unicode_version":"6.0"},{"emoji":"🇮🇸","aliases":["iceland"],"tags":[],"category":"Flags","description":"flag: Iceland","unicode_version":"6.0"},{"emoji":"🇮🇹","aliases":["it"],"tags":["italy"],"category":"Flags","description":"flag: Italy","unicode_version":"6.0"},{"emoji":"🇯🇪","aliases":["jersey"],"tags":[],"category":"Flags","description":"flag: Jersey","unicode_version":"6.0"},{"emoji":"🇯🇲","aliases":["jamaica"],"tags":[],"category":"Flags","description":"flag: Jamaica","unicode_version":"6.0"},{"emoji":"🇯🇴","aliases":["jordan"],"tags":[],"category":"Flags","description":"flag: Jordan","unicode_version":"6.0"},{"emoji":"🇯🇵","aliases":["jp"],"tags":["japan"],"category":"Flags","description":"flag: Japan","unicode_version":"6.0"},{"emoji":"🇰🇪","aliases":["kenya"],"tags":[],"category":"Flags","description":"flag: Kenya","unicode_version":"6.0"},{"emoji":"🇰🇬","aliases":["kyrgyzstan"],"tags":[],"category":"Flags","description":"flag: Kyrgyzstan","unicode_version":"6.0"},{"emoji":"🇰🇭","aliases":["cambodia"],"tags":[],"category":"Flags","description":"flag: Cambodia","unicode_version":"6.0"},{"emoji":"🇰🇮","aliases":["kiribati"],"tags":[],"category":"Flags","description":"flag: Kiribati","unicode_version":"6.0"},{"emoji":"🇰🇲","aliases":["comoros"],"tags":[],"category":"Flags","description":"flag: Comoros","unicode_version":"6.0"},{"emoji":"🇰🇳","aliases":["st_kitts_nevis"],"tags":[],"category":"Flags","description":"flag: St. Kitts & Nevis","unicode_version":"6.0"},{"emoji":"🇰🇵","aliases":["north_korea"],"tags":[],"category":"Flags","description":"flag: North Korea","unicode_version":"6.0"},{"emoji":"🇰🇷","aliases":["kr"],"tags":["korea"],"category":"Flags","description":"flag: South Korea","unicode_version":"6.0"},{"emoji":"🇰🇼","aliases":["kuwait"],"tags":[],"category":"Flags","description":"flag: Kuwait","unicode_version":"6.0"},{"emoji":"🇰🇾","aliases":["cayman_islands"],"tags":[],"category":"Flags","description":"flag: Cayman Islands","unicode_version":"6.0"},{"emoji":"🇰🇿","aliases":["kazakhstan"],"tags":[],"category":"Flags","description":"flag: Kazakhstan","unicode_version":"6.0"},{"emoji":"🇱🇦","aliases":["laos"],"tags":[],"category":"Flags","description":"flag: Laos","unicode_version":"6.0"},{"emoji":"🇱🇧","aliases":["lebanon"],"tags":[],"category":"Flags","description":"flag: Lebanon","unicode_version":"6.0"},{"emoji":"🇱🇨","aliases":["st_lucia"],"tags":[],"category":"Flags","description":"flag: St. Lucia","unicode_version":"6.0"},{"emoji":"🇱🇮","aliases":["liechtenstein"],"tags":[],"category":"Flags","description":"flag: Liechtenstein","unicode_version":"6.0"},{"emoji":"🇱🇰","aliases":["sri_lanka"],"tags":[],"category":"Flags","description":"flag: Sri Lanka","unicode_version":"6.0"},{"emoji":"🇱🇷","aliases":["liberia"],"tags":[],"category":"Flags","description":"flag: Liberia","unicode_version":"6.0"},{"emoji":"🇱🇸","aliases":["lesotho"],"tags":[],"category":"Flags","description":"flag: Lesotho","unicode_version":"6.0"},{"emoji":"🇱🇹","aliases":["lithuania"],"tags":[],"category":"Flags","description":"flag: Lithuania","unicode_version":"6.0"},{"emoji":"🇱🇺","aliases":["luxembourg"],"tags":[],"category":"Flags","description":"flag: Luxembourg","unicode_version":"6.0"},{"emoji":"🇱🇻","aliases":["latvia"],"tags":[],"category":"Flags","description":"flag: Latvia","unicode_version":"6.0"},{"emoji":"🇱🇾","aliases":["libya"],"tags":[],"category":"Flags","description":"flag: Libya","unicode_version":"6.0"},{"emoji":"🇲🇦","aliases":["morocco"],"tags":[],"category":"Flags","description":"flag: Morocco","unicode_version":"6.0"},{"emoji":"🇲🇨","aliases":["monaco"],"tags":[],"category":"Flags","description":"flag: Monaco","unicode_version":"6.0"},{"emoji":"🇲🇩","aliases":["moldova"],"tags":[],"category":"Flags","description":"flag: Moldova","unicode_version":"6.0"},{"emoji":"🇲🇪","aliases":["montenegro"],"tags":[],"category":"Flags","description":"flag: Montenegro","unicode_version":"6.0"},{"emoji":"🇲🇫","aliases":["st_martin"],"tags":[],"category":"Flags","description":"flag: St. Martin","unicode_version":"11.0"},{"emoji":"🇲🇬","aliases":["madagascar"],"tags":[],"category":"Flags","description":"flag: Madagascar","unicode_version":"6.0"},{"emoji":"🇲🇭","aliases":["marshall_islands"],"tags":[],"category":"Flags","description":"flag: Marshall Islands","unicode_version":"6.0"},{"emoji":"🇲🇰","aliases":["macedonia"],"tags":[],"category":"Flags","description":"flag: North Macedonia","unicode_version":"6.0"},{"emoji":"🇲🇱","aliases":["mali"],"tags":[],"category":"Flags","description":"flag: Mali","unicode_version":"6.0"},{"emoji":"🇲🇲","aliases":["myanmar"],"tags":["burma"],"category":"Flags","description":"flag: Myanmar (Burma)","unicode_version":"6.0"},{"emoji":"🇲🇳","aliases":["mongolia"],"tags":[],"category":"Flags","description":"flag: Mongolia","unicode_version":"6.0"},{"emoji":"🇲🇴","aliases":["macau"],"tags":[],"category":"Flags","description":"flag: Macao SAR China","unicode_version":"6.0"},{"emoji":"🇲🇵","aliases":["northern_mariana_islands"],"tags":[],"category":"Flags","description":"flag: Northern Mariana Islands","unicode_version":"6.0"},{"emoji":"🇲🇶","aliases":["martinique"],"tags":[],"category":"Flags","description":"flag: Martinique","unicode_version":"6.0"},{"emoji":"🇲🇷","aliases":["mauritania"],"tags":[],"category":"Flags","description":"flag: Mauritania","unicode_version":"6.0"},{"emoji":"🇲🇸","aliases":["montserrat"],"tags":[],"category":"Flags","description":"flag: Montserrat","unicode_version":"6.0"},{"emoji":"🇲🇹","aliases":["malta"],"tags":[],"category":"Flags","description":"flag: Malta","unicode_version":"6.0"},{"emoji":"🇲🇺","aliases":["mauritius"],"tags":[],"category":"Flags","description":"flag: Mauritius","unicode_version":"6.0"},{"emoji":"🇲🇻","aliases":["maldives"],"tags":[],"category":"Flags","description":"flag: Maldives","unicode_version":"6.0"},{"emoji":"🇲🇼","aliases":["malawi"],"tags":[],"category":"Flags","description":"flag: Malawi","unicode_version":"6.0"},{"emoji":"🇲🇽","aliases":["mexico"],"tags":[],"category":"Flags","description":"flag: Mexico","unicode_version":"6.0"},{"emoji":"🇲🇾","aliases":["malaysia"],"tags":[],"category":"Flags","description":"flag: Malaysia","unicode_version":"6.0"},{"emoji":"🇲🇿","aliases":["mozambique"],"tags":[],"category":"Flags","description":"flag: Mozambique","unicode_version":"6.0"},{"emoji":"🇳🇦","aliases":["namibia"],"tags":[],"category":"Flags","description":"flag: Namibia","unicode_version":"6.0"},{"emoji":"🇳🇨","aliases":["new_caledonia"],"tags":[],"category":"Flags","description":"flag: New Caledonia","unicode_version":"6.0"},{"emoji":"🇳🇪","aliases":["niger"],"tags":[],"category":"Flags","description":"flag: Niger","unicode_version":"6.0"},{"emoji":"🇳🇫","aliases":["norfolk_island"],"tags":[],"category":"Flags","description":"flag: Norfolk Island","unicode_version":"6.0"},{"emoji":"🇳🇬","aliases":["nigeria"],"tags":[],"category":"Flags","description":"flag: Nigeria","unicode_version":"6.0"},{"emoji":"🇳🇮","aliases":["nicaragua"],"tags":[],"category":"Flags","description":"flag: Nicaragua","unicode_version":"6.0"},{"emoji":"🇳🇱","aliases":["netherlands"],"tags":[],"category":"Flags","description":"flag: Netherlands","unicode_version":"6.0"},{"emoji":"🇳🇴","aliases":["norway"],"tags":[],"category":"Flags","description":"flag: Norway","unicode_version":"6.0"},{"emoji":"🇳🇵","aliases":["nepal"],"tags":[],"category":"Flags","description":"flag: Nepal","unicode_version":"6.0"},{"emoji":"🇳🇷","aliases":["nauru"],"tags":[],"category":"Flags","description":"flag: Nauru","unicode_version":"6.0"},{"emoji":"🇳🇺","aliases":["niue"],"tags":[],"category":"Flags","description":"flag: Niue","unicode_version":"6.0"},{"emoji":"🇳🇿","aliases":["new_zealand"],"tags":[],"category":"Flags","description":"flag: New Zealand","unicode_version":"6.0"},{"emoji":"🇴🇲","aliases":["oman"],"tags":[],"category":"Flags","description":"flag: Oman","unicode_version":"6.0"},{"emoji":"🇵🇦","aliases":["panama"],"tags":[],"category":"Flags","description":"flag: Panama","unicode_version":"6.0"},{"emoji":"🇵🇪","aliases":["peru"],"tags":[],"category":"Flags","description":"flag: Peru","unicode_version":"6.0"},{"emoji":"🇵🇫","aliases":["french_polynesia"],"tags":[],"category":"Flags","description":"flag: French Polynesia","unicode_version":"6.0"},{"emoji":"🇵🇬","aliases":["papua_new_guinea"],"tags":[],"category":"Flags","description":"flag: Papua New Guinea","unicode_version":"6.0"},{"emoji":"🇵🇭","aliases":["philippines"],"tags":[],"category":"Flags","description":"flag: Philippines","unicode_version":"6.0"},{"emoji":"🇵🇰","aliases":["pakistan"],"tags":[],"category":"Flags","description":"flag: Pakistan","unicode_version":"6.0"},{"emoji":"🇵🇱","aliases":["poland"],"tags":[],"category":"Flags","description":"flag: Poland","unicode_version":"6.0"},{"emoji":"🇵🇲","aliases":["st_pierre_miquelon"],"tags":[],"category":"Flags","description":"flag: St. Pierre & Miquelon","unicode_version":"6.0"},{"emoji":"🇵🇳","aliases":["pitcairn_islands"],"tags":[],"category":"Flags","description":"flag: Pitcairn Islands","unicode_version":"6.0"},{"emoji":"🇵🇷","aliases":["puerto_rico"],"tags":[],"category":"Flags","description":"flag: Puerto Rico","unicode_version":"6.0"},{"emoji":"🇵🇸","aliases":["palestinian_territories"],"tags":[],"category":"Flags","description":"flag: Palestinian Territories","unicode_version":"6.0"},{"emoji":"🇵🇹","aliases":["portugal"],"tags":[],"category":"Flags","description":"flag: Portugal","unicode_version":"6.0"},{"emoji":"🇵🇼","aliases":["palau"],"tags":[],"category":"Flags","description":"flag: Palau","unicode_version":"6.0"},{"emoji":"🇵🇾","aliases":["paraguay"],"tags":[],"category":"Flags","description":"flag: Paraguay","unicode_version":"6.0"},{"emoji":"🇶🇦","aliases":["qatar"],"tags":[],"category":"Flags","description":"flag: Qatar","unicode_version":"6.0"},{"emoji":"🇷🇪","aliases":["reunion"],"tags":[],"category":"Flags","description":"flag: Réunion","unicode_version":"6.0"},{"emoji":"🇷🇴","aliases":["romania"],"tags":[],"category":"Flags","description":"flag: Romania","unicode_version":"6.0"},{"emoji":"🇷🇸","aliases":["serbia"],"tags":[],"category":"Flags","description":"flag: Serbia","unicode_version":"6.0"},{"emoji":"🇷🇺","aliases":["ru"],"tags":["russia"],"category":"Flags","description":"flag: Russia","unicode_version":"6.0"},{"emoji":"🇷🇼","aliases":["rwanda"],"tags":[],"category":"Flags","description":"flag: Rwanda","unicode_version":"6.0"},{"emoji":"🇸🇦","aliases":["saudi_arabia"],"tags":[],"category":"Flags","description":"flag: Saudi Arabia","unicode_version":"6.0"},{"emoji":"🇸🇧","aliases":["solomon_islands"],"tags":[],"category":"Flags","description":"flag: Solomon Islands","unicode_version":"6.0"},{"emoji":"🇸🇨","aliases":["seychelles"],"tags":[],"category":"Flags","description":"flag: Seychelles","unicode_version":"6.0"},{"emoji":"🇸🇩","aliases":["sudan"],"tags":[],"category":"Flags","description":"flag: Sudan","unicode_version":"6.0"},{"emoji":"🇸🇪","aliases":["sweden"],"tags":[],"category":"Flags","description":"flag: Sweden","unicode_version":"6.0"},{"emoji":"🇸🇬","aliases":["singapore"],"tags":[],"category":"Flags","description":"flag: Singapore","unicode_version":"6.0"},{"emoji":"🇸🇭","aliases":["st_helena"],"tags":[],"category":"Flags","description":"flag: St. Helena","unicode_version":"6.0"},{"emoji":"🇸🇮","aliases":["slovenia"],"tags":[],"category":"Flags","description":"flag: Slovenia","unicode_version":"6.0"},{"emoji":"🇸🇯","aliases":["svalbard_jan_mayen"],"tags":[],"category":"Flags","description":"flag: Svalbard & Jan Mayen","unicode_version":"11.0"},{"emoji":"🇸🇰","aliases":["slovakia"],"tags":[],"category":"Flags","description":"flag: Slovakia","unicode_version":"6.0"},{"emoji":"🇸🇱","aliases":["sierra_leone"],"tags":[],"category":"Flags","description":"flag: Sierra Leone","unicode_version":"6.0"},{"emoji":"🇸🇲","aliases":["san_marino"],"tags":[],"category":"Flags","description":"flag: San Marino","unicode_version":"6.0"},{"emoji":"🇸🇳","aliases":["senegal"],"tags":[],"category":"Flags","description":"flag: Senegal","unicode_version":"6.0"},{"emoji":"🇸🇴","aliases":["somalia"],"tags":[],"category":"Flags","description":"flag: Somalia","unicode_version":"6.0"},{"emoji":"🇸🇷","aliases":["suriname"],"tags":[],"category":"Flags","description":"flag: Suriname","unicode_version":"6.0"},{"emoji":"🇸🇸","aliases":["south_sudan"],"tags":[],"category":"Flags","description":"flag: South Sudan","unicode_version":"6.0"},{"emoji":"🇸🇹","aliases":["sao_tome_principe"],"tags":[],"category":"Flags","description":"flag: São Tomé & Príncipe","unicode_version":"6.0"},{"emoji":"🇸🇻","aliases":["el_salvador"],"tags":[],"category":"Flags","description":"flag: El Salvador","unicode_version":"6.0"},{"emoji":"🇸🇽","aliases":["sint_maarten"],"tags":[],"category":"Flags","description":"flag: Sint Maarten","unicode_version":"6.0"},{"emoji":"🇸🇾","aliases":["syria"],"tags":[],"category":"Flags","description":"flag: Syria","unicode_version":"6.0"},{"emoji":"🇸🇿","aliases":["swaziland"],"tags":[],"category":"Flags","description":"flag: Eswatini","unicode_version":"6.0"},{"emoji":"🇹🇦","aliases":["tristan_da_cunha"],"tags":[],"category":"Flags","description":"flag: Tristan da Cunha","unicode_version":"11.0"},{"emoji":"🇹🇨","aliases":["turks_caicos_islands"],"tags":[],"category":"Flags","description":"flag: Turks & Caicos Islands","unicode_version":"6.0"},{"emoji":"🇹🇩","aliases":["chad"],"tags":[],"category":"Flags","description":"flag: Chad","unicode_version":"6.0"},{"emoji":"🇹🇫","aliases":["french_southern_territories"],"tags":[],"category":"Flags","description":"flag: French Southern Territories","unicode_version":"6.0"},{"emoji":"🇹🇬","aliases":["togo"],"tags":[],"category":"Flags","description":"flag: Togo","unicode_version":"6.0"},{"emoji":"🇹🇭","aliases":["thailand"],"tags":[],"category":"Flags","description":"flag: Thailand","unicode_version":"6.0"},{"emoji":"🇹🇯","aliases":["tajikistan"],"tags":[],"category":"Flags","description":"flag: Tajikistan","unicode_version":"6.0"},{"emoji":"🇹🇰","aliases":["tokelau"],"tags":[],"category":"Flags","description":"flag: Tokelau","unicode_version":"6.0"},{"emoji":"🇹🇱","aliases":["timor_leste"],"tags":[],"category":"Flags","description":"flag: Timor-Leste","unicode_version":"6.0"},{"emoji":"🇹🇲","aliases":["turkmenistan"],"tags":[],"category":"Flags","description":"flag: Turkmenistan","unicode_version":"6.0"},{"emoji":"🇹🇳","aliases":["tunisia"],"tags":[],"category":"Flags","description":"flag: Tunisia","unicode_version":"6.0"},{"emoji":"🇹🇴","aliases":["tonga"],"tags":[],"category":"Flags","description":"flag: Tonga","unicode_version":"6.0"},{"emoji":"🇹🇷","aliases":["tr"],"tags":["turkey"],"category":"Flags","description":"flag: Turkey","unicode_version":"8.0"},{"emoji":"🇹🇹","aliases":["trinidad_tobago"],"tags":[],"category":"Flags","description":"flag: Trinidad & Tobago","unicode_version":"6.0"},{"emoji":"🇹🇻","aliases":["tuvalu"],"tags":[],"category":"Flags","description":"flag: Tuvalu","unicode_version":"6.0"},{"emoji":"🇹🇼","aliases":["taiwan"],"tags":[],"category":"Flags","description":"flag: Taiwan","unicode_version":"6.0"},{"emoji":"🇹🇿","aliases":["tanzania"],"tags":[],"category":"Flags","description":"flag: Tanzania","unicode_version":"6.0"},{"emoji":"🇺🇦","aliases":["ukraine"],"tags":[],"category":"Flags","description":"flag: Ukraine","unicode_version":"6.0"},{"emoji":"🇺🇬","aliases":["uganda"],"tags":[],"category":"Flags","description":"flag: Uganda","unicode_version":"6.0"},{"emoji":"🇺🇲","aliases":["us_outlying_islands"],"tags":[],"category":"Flags","description":"flag: U.S. Outlying Islands","unicode_version":"11.0"},{"emoji":"🇺🇳","aliases":["united_nations"],"tags":[],"category":"Flags","description":"flag: United Nations","unicode_version":"11.0"},{"emoji":"🇺🇸","aliases":["us"],"tags":["flag","united","america"],"category":"Flags","description":"flag: United States","unicode_version":"6.0"},{"emoji":"🇺🇾","aliases":["uruguay"],"tags":[],"category":"Flags","description":"flag: Uruguay","unicode_version":"6.0"},{"emoji":"🇺🇿","aliases":["uzbekistan"],"tags":[],"category":"Flags","description":"flag: Uzbekistan","unicode_version":"6.0"},{"emoji":"🇻🇦","aliases":["vatican_city"],"tags":[],"category":"Flags","description":"flag: Vatican City","unicode_version":"6.0"},{"emoji":"🇻🇨","aliases":["st_vincent_grenadines"],"tags":[],"category":"Flags","description":"flag: St. Vincent & Grenadines","unicode_version":"6.0"},{"emoji":"🇻🇪","aliases":["venezuela"],"tags":[],"category":"Flags","description":"flag: Venezuela","unicode_version":"6.0"},{"emoji":"🇻🇬","aliases":["british_virgin_islands"],"tags":[],"category":"Flags","description":"flag: British Virgin Islands","unicode_version":"6.0"},{"emoji":"🇻🇮","aliases":["us_virgin_islands"],"tags":[],"category":"Flags","description":"flag: U.S. Virgin Islands","unicode_version":"6.0"},{"emoji":"🇻🇳","aliases":["vietnam"],"tags":[],"category":"Flags","description":"flag: Vietnam","unicode_version":"6.0"},{"emoji":"🇻🇺","aliases":["vanuatu"],"tags":[],"category":"Flags","description":"flag: Vanuatu","unicode_version":"6.0"},{"emoji":"🇼🇫","aliases":["wallis_futuna"],"tags":[],"category":"Flags","description":"flag: Wallis & Futuna","unicode_version":"6.0"},{"emoji":"🇼🇸","aliases":["samoa"],"tags":[],"category":"Flags","description":"flag: Samoa","unicode_version":"6.0"},{"emoji":"🇽🇰","aliases":["kosovo"],"tags":[],"category":"Flags","description":"flag: Kosovo","unicode_version":"6.0"},{"emoji":"🇾🇪","aliases":["yemen"],"tags":[],"category":"Flags","description":"flag: Yemen","unicode_version":"6.0"},{"emoji":"🇾🇹","aliases":["mayotte"],"tags":[],"category":"Flags","description":"flag: Mayotte","unicode_version":"6.0"},{"emoji":"🇿🇦","aliases":["south_africa"],"tags":[],"category":"Flags","description":"flag: South Africa","unicode_version":"6.0"},{"emoji":"🇿🇲","aliases":["zambia"],"tags":[],"category":"Flags","description":"flag: Zambia","unicode_version":"6.0"},{"emoji":"🇿🇼","aliases":["zimbabwe"],"tags":[],"category":"Flags","description":"flag: Zimbabwe","unicode_version":"6.0"},{"emoji":"🏴󠁧󠁢󠁥󠁮󠁧󠁿","aliases":["england"],"tags":[],"category":"Flags","description":"flag: England","unicode_version":"11.0"},{"emoji":"🏴󠁧󠁢󠁳󠁣󠁴󠁿","aliases":["scotland"],"tags":[],"category":"Flags","description":"flag: Scotland","unicode_version":"11.0"},{"emoji":"🏴󠁧󠁢󠁷󠁬󠁳󠁿","aliases":["wales"],"tags":[],"category":"Flags","description":"flag: Wales","unicode_version":"11.0"}] diff --git a/web/src/app/errors.js b/web/src/app/errors.js index 28f49af..38165a2 100644 --- a/web/src/app/errors.js +++ b/web/src/app/errors.js @@ -1,80 +1,66 @@ -/* eslint-disable max-classes-per-file */ // This is a subset of, and the counterpart to errors.go -const maybeToJson = async (response) => { - try { - return await response.json(); - } catch (e) { - return null; - } +export const fetchOrThrow = async (url, options) => { + const response = await fetch(url, options); + if (response.status !== 200) { + await throwAppError(response); + } + return response; // Promise! }; +export const throwAppError = async (response) => { + if (response.status === 401 || response.status === 403) { + console.log(`[Error] HTTP ${response.status}`, response); + throw new UnauthorizedError(); + } + const error = await maybeToJson(response); + if (error?.code) { + console.log(`[Error] HTTP ${response.status}, ntfy error ${error.code}: ${error.error || ""}`, response); + if (error.code === UserExistsError.CODE) { + throw new UserExistsError(); + } else if (error.code === TopicReservedError.CODE) { + throw new TopicReservedError(); + } else if (error.code === AccountCreateLimitReachedError.CODE) { + throw new AccountCreateLimitReachedError(); + } else if (error.code === IncorrectPasswordError.CODE) { + throw new IncorrectPasswordError(); + } else if (error?.error) { + throw new Error(`Error ${error.code}: ${error.error}`); + } + } + console.log(`[Error] HTTP ${response.status}, not a ntfy error`, response); + throw new Error(`Unexpected response ${response.status}`); +}; + +const maybeToJson = async (response) => { + try { + return await response.json(); + } catch (e) { + return null; + } +} + export class UnauthorizedError extends Error { - constructor() { - super("Unauthorized"); - } + constructor() { super("Unauthorized"); } } export class UserExistsError extends Error { - static CODE = 40901; // errHTTPConflictUserExists - - constructor() { - super("Username already exists"); - } + static CODE = 40901; // errHTTPConflictUserExists + constructor() { super("Username already exists"); } } export class TopicReservedError extends Error { - static CODE = 40902; // errHTTPConflictTopicReserved - - constructor() { - super("Topic already reserved"); - } + static CODE = 40902; // errHTTPConflictTopicReserved + constructor() { super("Topic already reserved"); } } export class AccountCreateLimitReachedError extends Error { - static CODE = 42906; // errHTTPTooManyRequestsLimitAccountCreation - - constructor() { - super("Account creation limit reached"); - } + static CODE = 42906; // errHTTPTooManyRequestsLimitAccountCreation + constructor() { super("Account creation limit reached"); } } export class IncorrectPasswordError extends Error { - static CODE = 40026; // errHTTPBadRequestIncorrectPasswordConfirmation - - constructor() { - super("Password incorrect"); - } + static CODE = 40026; // errHTTPBadRequestIncorrectPasswordConfirmation + constructor() { super("Password incorrect"); } } -export const throwAppError = async (response) => { - if (response.status === 401 || response.status === 403) { - console.log(`[Error] HTTP ${response.status}`, response); - throw new UnauthorizedError(); - } - const error = await maybeToJson(response); - if (error?.code) { - console.log(`[Error] HTTP ${response.status}, ntfy error ${error.code}: ${error.error || ""}`, response); - if (error.code === UserExistsError.CODE) { - throw new UserExistsError(); - } else if (error.code === TopicReservedError.CODE) { - throw new TopicReservedError(); - } else if (error.code === AccountCreateLimitReachedError.CODE) { - throw new AccountCreateLimitReachedError(); - } else if (error.code === IncorrectPasswordError.CODE) { - throw new IncorrectPasswordError(); - } else if (error?.error) { - throw new Error(`Error ${error.code}: ${error.error}`); - } - } - console.log(`[Error] HTTP ${response.status}, not a ntfy error`, response); - throw new Error(`Unexpected response ${response.status}`); -}; - -export const fetchOrThrow = async (url, options) => { - const response = await fetch(url, options); - if (response.status !== 200) { - await throwAppError(response); - } - return response; // Promise! -}; diff --git a/web/src/app/utils.js b/web/src/app/utils.js index ab7551b..6eb4ac5 100644 --- a/web/src/app/utils.js +++ b/web/src/app/utils.js @@ -1,5 +1,4 @@ -import { Base64 } from "js-base64"; -import { rawEmojis } from "./emojis"; +import {rawEmojis} from "./emojis"; import beep from "../sounds/beep.mp3"; import juntos from "../sounds/juntos.mp3"; import pristine from "../sounds/pristine.mp3"; @@ -8,14 +7,12 @@ import dadum from "../sounds/dadum.mp3"; import pop from "../sounds/pop.mp3"; import popSwoosh from "../sounds/pop-swoosh.mp3"; import config from "./config"; +import {Base64} from 'js-base64'; -export const tiersUrl = (baseUrl) => `${baseUrl}/v1/tiers`; -export const shortUrl = (url) => url.replaceAll(/https?:\/\//g, ""); -export const expandUrl = (url) => [`https://${url}`, `http://${url}`]; -export const expandSecureUrl = (url) => `https://${url}`; export const topicUrl = (baseUrl, topic) => `${baseUrl}/${topic}`; -export const topicUrlWs = (baseUrl, topic) => - `${topicUrl(baseUrl, topic)}/ws`.replaceAll("https://", "wss://").replaceAll("http://", "ws://"); +export const topicUrlWs = (baseUrl, topic) => `${topicUrl(baseUrl, topic)}/ws` + .replaceAll("https://", "wss://") + .replaceAll("http://", "ws://"); export const topicUrlJson = (baseUrl, topic) => `${topicUrl(baseUrl, topic)}/json`; export const topicUrlJsonPoll = (baseUrl, topic) => `${topicUrlJson(baseUrl, topic)}?poll=1`; export const topicUrlJsonPollWithSince = (baseUrl, topic, since) => `${topicUrlJson(baseUrl, topic)}?poll=1&since=${since}`; @@ -30,261 +27,276 @@ export const accountReservationUrl = (baseUrl) => `${baseUrl}/v1/account/reserva export const accountReservationSingleUrl = (baseUrl, topic) => `${baseUrl}/v1/account/reservation/${topic}`; export const accountBillingSubscriptionUrl = (baseUrl) => `${baseUrl}/v1/account/billing/subscription`; export const accountBillingPortalUrl = (baseUrl) => `${baseUrl}/v1/account/billing/portal`; -export const accountPhoneUrl = (baseUrl) => `${baseUrl}/v1/account/phone`; -export const accountPhoneVerifyUrl = (baseUrl) => `${baseUrl}/v1/account/phone/verify`; +export const tiersUrl = (baseUrl) => `${baseUrl}/v1/tiers`; +export const shortUrl = (url) => url.replaceAll(/https?:\/\//g, ""); +export const expandUrl = (url) => [`https://${url}`, `http://${url}`]; +export const expandSecureUrl = (url) => `https://${url}`; -export const validUrl = (url) => url.match(/^https?:\/\/.+/); - -export const disallowedTopic = (topic) => config.disallowed_topics.includes(topic); +export const validUrl = (url) => { + return url.match(/^https?:\/\/.+/); +} export const validTopic = (topic) => { - if (disallowedTopic(topic)) { - return false; - } - return topic.match(/^([-_a-zA-Z0-9]{1,64})$/); // Regex must match Go & Android app! -}; + if (disallowedTopic(topic)) { + return false; + } + return topic.match(/^([-_a-zA-Z0-9]{1,64})$/); // Regex must match Go & Android app! +} + +export const disallowedTopic = (topic) => { + return config.disallowed_topics.includes(topic); +} export const topicDisplayName = (subscription) => { - if (subscription.displayName) { - return subscription.displayName; - } - if (subscription.baseUrl === config.base_url) { - return subscription.topic; - } - return topicShortUrl(subscription.baseUrl, subscription.topic); + if (subscription.displayName) { + return subscription.displayName; + } else if (subscription.baseUrl === config.base_url) { + return subscription.topic; + } + return topicShortUrl(subscription.baseUrl, subscription.topic); }; // Format emojis (see emoji.js) const emojis = {}; -rawEmojis.forEach((emoji) => { - emoji.aliases.forEach((alias) => { - emojis[alias] = emoji.emoji; - }); +rawEmojis.forEach(emoji => { + emoji.aliases.forEach(alias => { + emojis[alias] = emoji.emoji; + }); }); const toEmojis = (tags) => { - if (!tags) return []; - return tags.filter((tag) => tag in emojis).map((tag) => emojis[tag]); + if (!tags) return []; + else return tags.filter(tag => tag in emojis).map(tag => emojis[tag]); +} + +export const formatTitleWithDefault = (m, fallback) => { + if (m.title) { + return formatTitle(m); + } + return fallback; }; export const formatTitle = (m) => { - const emojiList = toEmojis(m.tags); - if (emojiList.length > 0) { - return `${emojiList.join(" ")} ${m.title}`; - } - return m.title; -}; - -export const formatTitleWithDefault = (m, fallback) => { - if (m.title) { - return formatTitle(m); - } - return fallback; + const emojiList = toEmojis(m.tags); + if (emojiList.length > 0) { + return `${emojiList.join(" ")} ${m.title}`; + } else { + return m.title; + } }; export const formatMessage = (m) => { - if (m.title) { - return m.message; - } - const emojiList = toEmojis(m.tags); - if (emojiList.length > 0) { - return `${emojiList.join(" ")} ${m.message}`; - } - return m.message; + if (m.title) { + return m.message; + } else { + const emojiList = toEmojis(m.tags); + if (emojiList.length > 0) { + return `${emojiList.join(" ")} ${m.message}`; + } else { + return m.message; + } + } }; export const unmatchedTags = (tags) => { - if (!tags) return []; - return tags.filter((tag) => !(tag in emojis)); -}; - -export const encodeBase64 = (s) => Base64.encode(s); - -export const encodeBase64Url = (s) => Base64.encodeURI(s); - -export const bearerAuth = (token) => `Bearer ${token}`; - -export const basicAuth = (username, password) => `Basic ${encodeBase64(`${username}:${password}`)}`; - -export const withBearerAuth = (headers, token) => ({ ...headers, Authorization: bearerAuth(token) }); - -export const maybeWithBearerAuth = (headers, token) => { - if (token) { - return withBearerAuth(headers, token); - } - return headers; -}; - -export const withBasicAuth = (headers, username, password) => ({ ...headers, Authorization: basicAuth(username, password) }); + if (!tags) return []; + else return tags.filter(tag => !(tag in emojis)); +} export const maybeWithAuth = (headers, user) => { - if (user?.password) { - return withBasicAuth(headers, user.username, user.password); - } - if (user?.token) { - return withBearerAuth(headers, user.token); - } - return headers; -}; + if (user && user.password) { + return withBasicAuth(headers, user.username, user.password); + } else if (user && user.token) { + return withBearerAuth(headers, user.token); + } + return headers; +} + +export const maybeWithBearerAuth = (headers, token) => { + if (token) { + return withBearerAuth(headers, token); + } + return headers; +} + +export const withBasicAuth = (headers, username, password) => { + headers['Authorization'] = basicAuth(username, password); + return headers; +} + +export const basicAuth = (username, password) => { + return `Basic ${encodeBase64(`${username}:${password}`)}`; +} + +export const withBearerAuth = (headers, token) => { + headers['Authorization'] = bearerAuth(token); + return headers; +} + +export const bearerAuth = (token) => { + return `Bearer ${token}`; +} + +export const encodeBase64 = (s) => { + return Base64.encode(s); +} + +export const encodeBase64Url = (s) => { + return Base64.encodeURI(s); +} export const maybeAppendActionErrors = (message, notification) => { - const actionErrors = (notification.actions ?? []) - .map((action) => action.error) - .filter((action) => !!action) - .join("\n"); - if (actionErrors.length === 0) { - return message; - } - return `${message}\n\n${actionErrors}`; -}; + const actionErrors = (notification.actions ?? []) + .map(action => action.error) + .filter(action => !!action) + .join("\n") + if (actionErrors.length === 0) { + return message; + } else { + return `${message}\n\n${actionErrors}`; + } +} export const shuffle = (arr) => { - const returnArr = [...arr]; + let j, x; + for (let index = arr.length - 1; index > 0; index--) { + j = Math.floor(Math.random() * (index + 1)); + x = arr[index]; + arr[index] = arr[j]; + arr[j] = x; + } + return arr; +} - for (let index = returnArr.length - 1; index > 0; index -= 1) { - const j = Math.floor(Math.random() * (index + 1)); - [returnArr[index], returnArr[j]] = [returnArr[j], returnArr[index]]; - } - - return returnArr; -}; - -export const splitNoEmpty = (s, delimiter) => - s - .split(delimiter) - .map((x) => x.trim()) - .filter((x) => x !== ""); +export const splitNoEmpty = (s, delimiter) => { + return s + .split(delimiter) + .map(x => x.trim()) + .filter(x => x !== ""); +} /** Non-cryptographic hash function, see https://stackoverflow.com/a/8831937/1440785 */ export const hashCode = async (s) => { - let hash = 0; - for (let i = 0; i < s.length; i += 1) { - const char = s.charCodeAt(i); - // eslint-disable-next-line no-bitwise - hash = (hash << 5) - hash + char; - // eslint-disable-next-line no-bitwise - hash &= hash; // Convert to 32bit integer - } - return hash; -}; + let hash = 0; + for (let i = 0; i < s.length; i++) { + const char = s.charCodeAt(i); + hash = ((hash<<5)-hash)+char; + hash = hash & hash; // Convert to 32bit integer + } + return hash; +} -export const formatShortDateTime = (timestamp) => - new Intl.DateTimeFormat("default", { - dateStyle: "short", - timeStyle: "short", - }).format(new Date(timestamp * 1000)); +export const formatShortDateTime = (timestamp) => { + return new Intl.DateTimeFormat('default', {dateStyle: 'short', timeStyle: 'short'}) + .format(new Date(timestamp * 1000)); +} -export const formatShortDate = (timestamp) => new Intl.DateTimeFormat("default", { dateStyle: "short" }).format(new Date(timestamp * 1000)); +export const formatShortDate = (timestamp) => { + return new Intl.DateTimeFormat('default', {dateStyle: 'short'}) + .format(new Date(timestamp * 1000)); +} export const formatBytes = (bytes, decimals = 2) => { - if (bytes === 0) return "0 bytes"; - const k = 1024; - const dm = decimals < 0 ? 0 : decimals; - const sizes = ["bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; - const i = Math.floor(Math.log(bytes) / Math.log(k)); - return `${parseFloat((bytes / k ** i).toFixed(dm))} ${sizes[i]}`; -}; + if (bytes === 0) return '0 bytes'; + const k = 1024; + const dm = decimals < 0 ? 0 : decimals; + const sizes = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; +} export const formatNumber = (n) => { - if (n === 0) { + if (n % 1000 === 0) { + return `${n/1000}k`; + } return n; - } - if (n % 1000 === 0) { - return `${n / 1000}k`; - } - return n.toLocaleString(); -}; +} export const formatPrice = (n) => { - if (n % 100 === 0) { - return `$${n / 100}`; - } - return `$${(n / 100).toPrecision(2)}`; -}; + if (n % 100 === 0) { + return `$${n/100}`; + } + return `$${(n/100).toPrecision(2)}`; +} export const openUrl = (url) => { - window.open(url, "_blank", "noopener,noreferrer"); + window.open(url, "_blank", "noopener,noreferrer"); }; export const sounds = { - ding: { - file: ding, - label: "Ding", - }, - juntos: { - file: juntos, - label: "Juntos", - }, - pristine: { - file: pristine, - label: "Pristine", - }, - dadum: { - file: dadum, - label: "Dadum", - }, - pop: { - file: pop, - label: "Pop", - }, - "pop-swoosh": { - file: popSwoosh, - label: "Pop swoosh", - }, - beep: { - file: beep, - label: "Beep", - }, + "ding": { + file: ding, + label: "Ding" + }, + "juntos": { + file: juntos, + label: "Juntos" + }, + "pristine": { + file: pristine, + label: "Pristine" + }, + "dadum": { + file: dadum, + label: "Dadum" + }, + "pop": { + file: pop, + label: "Pop" + }, + "pop-swoosh": { + file: popSwoosh, + label: "Pop swoosh" + }, + "beep": { + file: beep, + label: "Beep" + } }; export const playSound = async (id) => { - const audio = new Audio(sounds[id].file); - return audio.play(); + const audio = new Audio(sounds[id].file); + return audio.play(); }; // From: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch -// eslint-disable-next-line func-style export async function* fetchLinesIterator(fileURL, headers) { - const utf8Decoder = new TextDecoder("utf-8"); - const response = await fetch(fileURL, { - headers, - }); - const reader = response.body.getReader(); - let { value: chunk, done: readerDone } = await reader.read(); - chunk = chunk ? utf8Decoder.decode(chunk) : ""; + const utf8Decoder = new TextDecoder('utf-8'); + const response = await fetch(fileURL, { + headers: headers + }); + const reader = response.body.getReader(); + let { value: chunk, done: readerDone } = await reader.read(); + chunk = chunk ? utf8Decoder.decode(chunk) : ''; - const re = /\n|\r|\r\n/gm; - let startIndex = 0; + const re = /\n|\r|\r\n/gm; + let startIndex = 0; - for (;;) { - const result = re.exec(chunk); - if (!result) { - if (readerDone) { - break; - } - const remainder = chunk.substr(startIndex); - // eslint-disable-next-line no-await-in-loop - ({ value: chunk, done: readerDone } = await reader.read()); - chunk = remainder + (chunk ? utf8Decoder.decode(chunk) : ""); - startIndex = 0; - re.lastIndex = 0; - // eslint-disable-next-line no-continue - continue; + for (;;) { + let result = re.exec(chunk); + if (!result) { + if (readerDone) { + break; + } + let remainder = chunk.substr(startIndex); + ({ value: chunk, done: readerDone } = await reader.read()); + chunk = remainder + (chunk ? utf8Decoder.decode(chunk) : ''); + startIndex = re.lastIndex = 0; + continue; + } + yield chunk.substring(startIndex, result.index); + startIndex = re.lastIndex; + } + if (startIndex < chunk.length) { + yield chunk.substr(startIndex); // last line didn't end in a newline char } - yield chunk.substring(startIndex, result.index); - startIndex = re.lastIndex; - } - if (startIndex < chunk.length) { - yield chunk.substr(startIndex); // last line didn't end in a newline char - } } export const randomAlphanumericString = (len) => { - const alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; - let id = ""; - for (let i = 0; i < len; i += 1) { - // eslint-disable-next-line no-bitwise - id += alphabet[(Math.random() * alphabet.length) | 0]; - } - return id; -}; + const alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + let id = ""; + for (let i = 0; i < len; i++) { + id += alphabet[(Math.random() * alphabet.length) | 0]; + } + return id; +} diff --git a/web/src/components/Account.js b/web/src/components/Account.js new file mode 100644 index 0000000..e5b6007 --- /dev/null +++ b/web/src/components/Account.js @@ -0,0 +1,803 @@ +import * as React from 'react'; +import {useContext, useState} from 'react'; +import { + Alert, + CardActions, + CardContent, + FormControl, + LinearProgress, + Link, + Portal, + Select, + Snackbar, + Stack, + Table, + TableBody, + TableCell, + TableHead, + TableRow, + useMediaQuery +} from "@mui/material"; +import Tooltip from '@mui/material/Tooltip'; +import Typography from "@mui/material/Typography"; +import EditIcon from '@mui/icons-material/Edit'; +import Container from "@mui/material/Container"; +import Card from "@mui/material/Card"; +import Button from "@mui/material/Button"; +import {Trans, useTranslation} from "react-i18next"; +import session from "../app/Session"; +import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline'; +import theme from "./theme"; +import Dialog from "@mui/material/Dialog"; +import DialogTitle from "@mui/material/DialogTitle"; +import DialogContent from "@mui/material/DialogContent"; +import TextField from "@mui/material/TextField"; +import routes from "./routes"; +import IconButton from "@mui/material/IconButton"; +import {formatBytes, formatShortDate, formatShortDateTime, openUrl} from "../app/utils"; +import accountApi, {LimitBasis, Role, SubscriptionInterval, SubscriptionStatus} from "../app/AccountApi"; +import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'; +import {Pref, PrefGroup} from "./Pref"; +import db from "../app/db"; +import i18n from "i18next"; +import humanizeDuration from "humanize-duration"; +import UpgradeDialog from "./UpgradeDialog"; +import CelebrationIcon from "@mui/icons-material/Celebration"; +import {AccountContext} from "./App"; +import DialogFooter from "./DialogFooter"; +import {Paragraph} from "./styles"; +import CloseIcon from "@mui/icons-material/Close"; +import {ContentCopy, Public} from "@mui/icons-material"; +import MenuItem from "@mui/material/MenuItem"; +import DialogContentText from "@mui/material/DialogContentText"; +import {IncorrectPasswordError, UnauthorizedError} from "../app/errors"; + +const Account = () => { + if (!session.exists()) { + window.location.href = routes.app; + return <>; + } + return ( + + + + + + + + + ); +}; + +const Basics = () => { + const { t } = useTranslation(); + return ( + + + {t("account_basics_title")} + + + + + + + + ); +}; + +const Username = () => { + const { t } = useTranslation(); + const { account } = useContext(AccountContext); + const labelId = "prefUsername"; + + return ( + +
+ {session.username()} + {account?.role === Role.ADMIN + ? <>{" "}👑 + : ""} +
+
+ ) +}; + +const ChangePassword = () => { + const { t } = useTranslation(); + const [dialogKey, setDialogKey] = useState(0); + const [dialogOpen, setDialogOpen] = useState(false); + const labelId = "prefChangePassword"; + + const handleDialogOpen = () => { + setDialogKey(prev => prev+1); + setDialogOpen(true); + }; + + const handleDialogClose = () => { + setDialogOpen(false); + }; + + return ( + +
+ ⬤⬤⬤⬤⬤⬤⬤⬤⬤⬤ + + + +
+ +
+ ) +}; + +const ChangePasswordDialog = (props) => { + const { t } = useTranslation(); + const [error, setError] = useState(""); + const [currentPassword, setCurrentPassword] = useState(""); + const [newPassword, setNewPassword] = useState(""); + const [confirmPassword, setConfirmPassword] = useState(""); + const fullScreen = useMediaQuery(theme.breakpoints.down('sm')); + + const handleDialogSubmit = async () => { + try { + console.debug(`[Account] Changing password`); + await accountApi.changePassword(currentPassword, newPassword); + props.onClose(); + } catch (e) { + console.log(`[Account] Error changing password`, e); + if (e instanceof IncorrectPasswordError) { + setError(t("account_basics_password_dialog_current_password_incorrect")); + } else if (e instanceof UnauthorizedError) { + session.resetAndRedirect(routes.login); + } else { + setError(e.message); + } + } + }; + + return ( + + {t("account_basics_password_dialog_title")} + + setCurrentPassword(ev.target.value)} + fullWidth + variant="standard" + /> + setNewPassword(ev.target.value)} + fullWidth + variant="standard" + /> + setConfirmPassword(ev.target.value)} + fullWidth + variant="standard" + /> + + + + + + + ); +}; + +const AccountType = () => { + const { t } = useTranslation(); + const { account } = useContext(AccountContext); + const [upgradeDialogKey, setUpgradeDialogKey] = useState(0); + const [upgradeDialogOpen, setUpgradeDialogOpen] = useState(false); + const [showPortalError, setShowPortalError] = useState(false); + + if (!account) { + return <>; + } + + const handleUpgradeClick = () => { + setUpgradeDialogKey(k => k + 1); + setUpgradeDialogOpen(true); + } + + const handleManageBilling = async () => { + try { + const response = await accountApi.createBillingPortalSession(); + window.open(response.redirect_url, "billing_portal"); + } catch (e) { + console.log(`[Account] Error opening billing portal`, e); + if (e instanceof UnauthorizedError) { + session.resetAndRedirect(routes.login); + } else { + setShowPortalError(true); + } + } + }; + + let accountType; + if (account.role === Role.ADMIN) { + const tierSuffix = (account.tier) ? t("account_basics_tier_admin_suffix_with_tier", { tier: account.tier.name }) : t("account_basics_tier_admin_suffix_no_tier"); + accountType = `${t("account_basics_tier_admin")} ${tierSuffix}`; + } else if (!account.tier) { + accountType = (config.enable_payments) ? t("account_basics_tier_free") : t("account_basics_tier_basic"); + } else { + accountType = account.tier.name; + if (account.billing?.interval === SubscriptionInterval.MONTH) { + accountType += ` (${t("account_basics_tier_interval_monthly")})`; + } else if (account.billing?.interval === SubscriptionInterval.YEAR) { + accountType += ` (${t("account_basics_tier_interval_yearly")})`; + } + } + + return ( + 0} + title={t("account_basics_tier_title")} + description={t("account_basics_tier_description")} + > +
+ {accountType} + {account.billing?.paid_until && !account.billing?.cancel_at && + + + + } + {config.enable_payments && account.role === Role.USER && !account.billing?.subscription && + + } + {config.enable_payments && account.role === Role.USER && account.billing?.subscription && + + } + {config.enable_payments && account.role === Role.USER && account.billing?.customer && + + } + {config.enable_payments && + setUpgradeDialogOpen(false)} + /> + } +
+ {account.billing?.status === SubscriptionStatus.PAST_DUE && + {t("account_basics_tier_payment_overdue")} + } + {account.billing?.cancel_at > 0 && + {t("account_basics_tier_canceled_subscription", { date: formatShortDate(account.billing.cancel_at) })} + } + + setShowPortalError(false)} + message={t("account_usage_cannot_create_portal_session")} + /> + +
+ ) +}; + +const Stats = () => { + const { t } = useTranslation(); + const { account } = useContext(AccountContext); + + if (!account) { + return <>; + } + + const normalize = (value, max) => { + return Math.min(value / max * 100, 100); + }; + + return ( + + + {t("account_usage_title")} + + + + {(account.role === Role.ADMIN || account.limits.reservations > 0) && + <> +
+ {account.stats.reservations} + {account.role === Role.USER ? t("account_usage_of_limit", {limit: account.limits.reservations}) : t("account_usage_unlimited")} +
+ 0 ? normalize(account.stats.reservations, account.limits.reservations) : 100} + /> + + } + {account.role === Role.USER && account.limits.reservations === 0 && + {t("account_usage_reservations_none")} + } +
+ + {t("account_usage_messages_title")} + + + }> +
+ {account.stats.messages} + {account.role === Role.USER ? t("account_usage_of_limit", { limit: account.limits.messages }) : t("account_usage_unlimited")} +
+ +
+ + {t("account_usage_emails_title")} + + + }> +
+ {account.stats.emails} + {account.role === Role.USER ? t("account_usage_of_limit", { limit: account.limits.emails }) : t("account_usage_unlimited")} +
+ +
+ +
+ {formatBytes(account.stats.attachment_total_size)} + {account.role === Role.USER ? t("account_usage_of_limit", { limit: formatBytes(account.limits.attachment_total_size) }) : t("account_usage_unlimited")} +
+ +
+
+ {account.role === Role.USER && account.limits.basis === LimitBasis.IP && + + {t("account_usage_basis_ip_description")} + + } +
+ ); +}; + +const InfoIcon = () => { + return ( + + ); +} + + +const Tokens = () => { + const { t } = useTranslation(); + const { account } = useContext(AccountContext); + const [dialogKey, setDialogKey] = useState(0); + const [dialogOpen, setDialogOpen] = useState(false); + const tokens = account?.tokens || []; + + const handleCreateClick = () => { + setDialogKey(prev => prev+1); + setDialogOpen(true); + }; + + const handleDialogClose = () => { + setDialogOpen(false); + }; + + const handleDialogSubmit = async (user) => { + setDialogOpen(false); + // + }; + return ( + + + + {t("account_tokens_title")} + + + + }} + /> + + {tokens?.length > 0 && } + + + + + + + ); +}; + +const TokensTable = (props) => { + const { t } = useTranslation(); + const [snackOpen, setSnackOpen] = useState(false); + const [upsertDialogKey, setUpsertDialogKey] = useState(0); + const [upsertDialogOpen, setUpsertDialogOpen] = useState(false); + const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); + const [selectedToken, setSelectedToken] = useState(null); + + const tokens = (props.tokens || []) + .sort( (a, b) => { + if (a.token === session.token()) { + return -1; + } else if (b.token === session.token()) { + return 1; + } + return a.token.localeCompare(b.token); + }); + + const handleEditClick = (token) => { + setUpsertDialogKey(prev => prev+1); + setSelectedToken(token); + setUpsertDialogOpen(true); + }; + + const handleDialogClose = () => { + setUpsertDialogOpen(false); + setDeleteDialogOpen(false); + setSelectedToken(null); + }; + + const handleDeleteClick = async (token) => { + setSelectedToken(token); + setDeleteDialogOpen(true); + }; + + const handleCopy = async (token) => { + await navigator.clipboard.writeText(token); + setSnackOpen(true); + }; + + return ( + + + + {t("account_tokens_table_token_header")} + {t("account_tokens_table_label_header")} + {t("account_tokens_table_expires_header")} + {t("account_tokens_table_last_access_header")} + + + + + {tokens.map(token => ( + + + + {token.token.slice(0, 12)} + ... + + handleCopy(token.token)}> + + + + + {token.token === session.token() && {t("account_tokens_table_current_session")}} + {token.token !== session.token() && (token.label || "-")} + + + {token.expires ? formatShortDateTime(token.expires) : {t("account_tokens_table_never_expires")}} + + +
+ {formatShortDateTime(token.last_access)} + + openUrl(`https://whatismyipaddress.com/ip/${token.last_origin}`)}> + + + +
+
+ + {token.token !== session.token() && + <> + handleEditClick(token)} aria-label={t("account_tokens_dialog_title_edit")}> + + + handleDeleteClick(token)} aria-label={t("account_tokens_dialog_title_delete")}> + + + + } + {token.token === session.token() && + + + + + + + } + +
+ ))} +
+ + setSnackOpen(false)} + message={t("account_tokens_table_copied_to_clipboard")} + /> + + + +
+ ); +}; + +const TokenDialog = (props) => { + const { t } = useTranslation(); + const [error, setError] = useState(""); + const [label, setLabel] = useState(props.token?.label || ""); + const [expires, setExpires] = useState(props.token ? -1 : 0); + const fullScreen = useMediaQuery(theme.breakpoints.down('sm')); + const editMode = !!props.token; + + const handleSubmit = async () => { + try { + if (editMode) { + await accountApi.updateToken(props.token.token, label, expires); + } else { + await accountApi.createToken(label, expires); + } + props.onClose(); + } catch (e) { + console.log(`[Account] Error creating token`, e); + if (e instanceof UnauthorizedError) { + session.resetAndRedirect(routes.login); + } else { + setError(e.message); + } + } + }; + + return ( + + {editMode ? t("account_tokens_dialog_title_edit") : t("account_tokens_dialog_title_create")} + + setLabel(ev.target.value)} + fullWidth + variant="standard" + /> + + + + + + + + + + ); +}; + +const TokenDeleteDialog = (props) => { + const { t } = useTranslation(); + const [error, setError] = useState(""); + + const handleSubmit = async () => { + try { + await accountApi.deleteToken(props.token.token); + props.onClose(); + } catch (e) { + console.log(`[Account] Error deleting token`, e); + if (e instanceof UnauthorizedError) { + session.resetAndRedirect(routes.login); + } else { + setError(e.message); + } + } + }; + + return ( + + {t("account_tokens_delete_dialog_title")} + + + + + + + + + + + ); +} + + +const Delete = () => { + const { t } = useTranslation(); + return ( + + + {t("account_delete_title")} + + + + + + ); +}; + +const DeleteAccount = () => { + const { t } = useTranslation(); + const [dialogKey, setDialogKey] = useState(0); + const [dialogOpen, setDialogOpen] = useState(false); + + const handleDialogOpen = () => { + setDialogKey(prev => prev+1); + setDialogOpen(true); + }; + + const handleDialogClose = () => { + setDialogOpen(false); + }; + + return ( + +
+ +
+ +
+ ) +}; + +const DeleteAccountDialog = (props) => { + const { t } = useTranslation(); + const { account } = useContext(AccountContext); + const [error, setError] = useState(""); + const [password, setPassword] = useState(""); + const fullScreen = useMediaQuery(theme.breakpoints.down('sm')); + + const handleSubmit = async () => { + try { + await accountApi.delete(password); + await db.delete(); + console.debug(`[Account] Account deleted`); + session.resetAndRedirect(routes.app); + } catch (e) { + console.log(`[Account] Error deleting account`, e); + if (e instanceof IncorrectPasswordError) { + setError(t("account_basics_password_dialog_current_password_incorrect")); + } else if (e instanceof UnauthorizedError) { + session.resetAndRedirect(routes.login); + } else { + setError(e.message); + } + } + }; + + return ( + + {t("account_delete_title")} + + + {t("account_delete_dialog_description")} + + setPassword(ev.target.value)} + fullWidth + variant="standard" + /> + {account?.billing?.subscription && + {t("account_delete_dialog_billing_warning")} + } + + + + + + + ); +}; + +export default Account; diff --git a/web/src/components/Account.jsx b/web/src/components/Account.jsx deleted file mode 100644 index 541d4f8..0000000 --- a/web/src/components/Account.jsx +++ /dev/null @@ -1,1128 +0,0 @@ -import * as React from "react"; -import { useContext, useState } from "react"; -import { - Alert, - CardActions, - CardContent, - Chip, - FormControl, - FormControlLabel, - LinearProgress, - Link, - Portal, - Radio, - RadioGroup, - Select, - Snackbar, - Stack, - Table, - TableBody, - TableCell, - TableHead, - TableRow, - useMediaQuery, - Tooltip, - Typography, - Container, - Card, - Button, - Dialog, - DialogTitle, - DialogContent, - TextField, - IconButton, - MenuItem, - DialogContentText, -} from "@mui/material"; -import EditIcon from "@mui/icons-material/Edit"; -import { Trans, useTranslation } from "react-i18next"; -import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline"; -import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined"; -import i18n from "i18next"; -import humanizeDuration from "humanize-duration"; -import CelebrationIcon from "@mui/icons-material/Celebration"; -import CloseIcon from "@mui/icons-material/Close"; -import { ContentCopy, Public } from "@mui/icons-material"; -import AddIcon from "@mui/icons-material/Add"; -import routes from "./routes"; -import { formatBytes, formatShortDate, formatShortDateTime, openUrl } from "../app/utils"; -import accountApi, { LimitBasis, Role, SubscriptionInterval, SubscriptionStatus } from "../app/AccountApi"; -import { Pref, PrefGroup } from "./Pref"; -import db from "../app/db"; -import UpgradeDialog from "./UpgradeDialog"; -import { AccountContext } from "./App"; -import DialogFooter from "./DialogFooter"; -import { Paragraph } from "./styles"; -import { IncorrectPasswordError, UnauthorizedError } from "../app/errors"; -import { ProChip } from "./SubscriptionPopup"; -import theme from "./theme"; -import session from "../app/Session"; - -const Account = () => { - if (!session.exists()) { - window.location.href = routes.app; - return <>; - } - return ( - - - - - - - - - ); -}; - -const Basics = () => { - const { t } = useTranslation(); - return ( - - - {t("account_basics_title")} - - - - - - - - - ); -}; - -const Username = () => { - const { t } = useTranslation(); - const { account } = useContext(AccountContext); - const labelId = "prefUsername"; - - return ( - -
- {session.username()} - {account?.role === Role.ADMIN ? ( - <> - {" "} - - 👑 - - - ) : ( - "" - )} -
-
- ); -}; - -const ChangePassword = () => { - const { t } = useTranslation(); - const [dialogKey, setDialogKey] = useState(0); - const [dialogOpen, setDialogOpen] = useState(false); - const labelId = "prefChangePassword"; - - const handleDialogOpen = () => { - setDialogKey((prev) => prev + 1); - setDialogOpen(true); - }; - - const handleDialogClose = () => { - setDialogOpen(false); - }; - - return ( - -
- - ⬤⬤⬤⬤⬤⬤⬤⬤⬤⬤ - - - - -
- -
- ); -}; - -const ChangePasswordDialog = (props) => { - const { t } = useTranslation(); - const [error, setError] = useState(""); - const [currentPassword, setCurrentPassword] = useState(""); - const [newPassword, setNewPassword] = useState(""); - const [confirmPassword, setConfirmPassword] = useState(""); - const fullScreen = useMediaQuery(theme.breakpoints.down("sm")); - - const handleDialogSubmit = async () => { - try { - console.debug(`[Account] Changing password`); - await accountApi.changePassword(currentPassword, newPassword); - props.onClose(); - } catch (e) { - console.log(`[Account] Error changing password`, e); - if (e instanceof IncorrectPasswordError) { - setError(t("account_basics_password_dialog_current_password_incorrect")); - } else if (e instanceof UnauthorizedError) { - session.resetAndRedirect(routes.login); - } else { - setError(e.message); - } - } - }; - - return ( - - {t("account_basics_password_dialog_title")} - - setCurrentPassword(ev.target.value)} - fullWidth - variant="standard" - /> - setNewPassword(ev.target.value)} - fullWidth - variant="standard" - /> - setConfirmPassword(ev.target.value)} - fullWidth - variant="standard" - /> - - - - - - - ); -}; - -const AccountType = () => { - const { t } = useTranslation(); - const { account } = useContext(AccountContext); - const [upgradeDialogKey, setUpgradeDialogKey] = useState(0); - const [upgradeDialogOpen, setUpgradeDialogOpen] = useState(false); - const [showPortalError, setShowPortalError] = useState(false); - - if (!account) { - return <>; - } - - const handleUpgradeClick = () => { - setUpgradeDialogKey((k) => k + 1); - setUpgradeDialogOpen(true); - }; - - const handleManageBilling = async () => { - try { - const response = await accountApi.createBillingPortalSession(); - window.open(response.redirect_url, "billing_portal"); - } catch (e) { - console.log(`[Account] Error opening billing portal`, e); - if (e instanceof UnauthorizedError) { - session.resetAndRedirect(routes.login); - } else { - setShowPortalError(true); - } - } - }; - - let accountType; - if (account.role === Role.ADMIN) { - const tierSuffix = account.tier - ? t("account_basics_tier_admin_suffix_with_tier", { - tier: account.tier.name, - }) - : t("account_basics_tier_admin_suffix_no_tier"); - accountType = `${t("account_basics_tier_admin")} ${tierSuffix}`; - } else if (!account.tier) { - accountType = config.enable_payments ? t("account_basics_tier_free") : t("account_basics_tier_basic"); - } else { - accountType = account.tier.name; - if (account.billing?.interval === SubscriptionInterval.MONTH) { - accountType += ` (${t("account_basics_tier_interval_monthly")})`; - } else if (account.billing?.interval === SubscriptionInterval.YEAR) { - accountType += ` (${t("account_basics_tier_interval_yearly")})`; - } - } - - return ( - 0} - title={t("account_basics_tier_title")} - description={t("account_basics_tier_description")} - > -
- {accountType} - {account.billing?.paid_until && !account.billing?.cancel_at && ( - - - - - - )} - {config.enable_payments && account.role === Role.USER && !account.billing?.subscription && ( - - )} - {config.enable_payments && account.role === Role.USER && account.billing?.subscription && ( - - )} - {config.enable_payments && account.role === Role.USER && account.billing?.customer && ( - - )} - {config.enable_payments && ( - setUpgradeDialogOpen(false)} - /> - )} -
- {account.billing?.status === SubscriptionStatus.PAST_DUE && ( - - {t("account_basics_tier_payment_overdue")} - - )} - {account.billing?.cancel_at > 0 && ( - - {t("account_basics_tier_canceled_subscription", { - date: formatShortDate(account.billing.cancel_at), - })} - - )} - - setShowPortalError(false)} - message={t("account_usage_cannot_create_portal_session")} - /> - -
- ); -}; - -const PhoneNumbers = () => { - const { t } = useTranslation(); - const { account } = useContext(AccountContext); - const [dialogKey, setDialogKey] = useState(0); - const [dialogOpen, setDialogOpen] = useState(false); - const [snackOpen, setSnackOpen] = useState(false); - const labelId = "prefPhoneNumbers"; - - const handleDialogOpen = () => { - setDialogKey((prev) => prev + 1); - setDialogOpen(true); - }; - - const handleDialogClose = () => { - setDialogOpen(false); - }; - - const handleCopy = (phoneNumber) => { - navigator.clipboard.writeText(phoneNumber); - setSnackOpen(true); - }; - - const handleDelete = async (phoneNumber) => { - try { - await accountApi.deletePhoneNumber(phoneNumber); - } catch (e) { - console.log(`[Account] Error deleting phone number`, e); - if (e instanceof UnauthorizedError) { - session.resetAndRedirect(routes.login); - } - } - }; - - if (!config.enable_calls) { - return null; - } - - if (account?.limits.calls === 0) { - return ( - - {t("account_basics_phone_numbers_title")} - {config.enable_payments && } - - } - description={t("account_basics_phone_numbers_description")} - > - {t("account_usage_calls_none")} - - ); - } - - return ( - -
- {account?.phone_numbers?.map((phoneNumber) => ( - - {phoneNumber} - - } - variant="outlined" - onClick={() => handleCopy(phoneNumber)} - onDelete={() => handleDelete(phoneNumber)} - /> - ))} - {!account?.phone_numbers && {t("account_basics_phone_numbers_no_phone_numbers_yet")}} - - - -
- - - setSnackOpen(false)} - message={t("account_basics_phone_numbers_copied_to_clipboard")} - /> - -
- ); -}; - -const AddPhoneNumberDialog = (props) => { - const { t } = useTranslation(); - const [error, setError] = useState(""); - const [phoneNumber, setPhoneNumber] = useState(""); - const [channel, setChannel] = useState("sms"); - const [code, setCode] = useState(""); - const [sending, setSending] = useState(false); - const [verificationCodeSent, setVerificationCodeSent] = useState(false); - const fullScreen = useMediaQuery(theme.breakpoints.down("sm")); - - const verifyPhone = async () => { - try { - setSending(true); - await accountApi.verifyPhoneNumber(phoneNumber, channel); - setVerificationCodeSent(true); - } catch (e) { - console.log(`[Account] Error sending verification`, e); - if (e instanceof UnauthorizedError) { - session.resetAndRedirect(routes.login); - } else { - setError(e.message); - } - } finally { - setSending(false); - } - }; - - const checkVerifyPhone = async () => { - try { - setSending(true); - await accountApi.addPhoneNumber(phoneNumber, code); - props.onClose(); - } catch (e) { - console.log(`[Account] Error confirming verification`, e); - if (e instanceof UnauthorizedError) { - session.resetAndRedirect(routes.login); - } else { - setError(e.message); - } - } finally { - setSending(false); - } - }; - - const handleDialogSubmit = async () => { - if (!verificationCodeSent) { - await verifyPhone(); - } else { - await checkVerifyPhone(); - } - }; - - const handleCancel = () => { - if (verificationCodeSent) { - setVerificationCodeSent(false); - setCode(""); - } else { - props.onClose(); - } - }; - - return ( - - {t("account_basics_phone_numbers_dialog_title")} - - {t("account_basics_phone_numbers_dialog_description")} - {!verificationCodeSent && ( -
- setPhoneNumber(ev.target.value)} - inputProps={{ inputMode: "tel", pattern: "+[0-9]*" }} - variant="standard" - sx={{ flexGrow: 1 }} - /> - - - setChannel(e.target.value)} />} - label={t("account_basics_phone_numbers_dialog_channel_sms")} - /> - setChannel(e.target.value)} />} - label={t("account_basics_phone_numbers_dialog_channel_call")} - sx={{ marginRight: 0 }} - /> - - -
- )} - {verificationCodeSent && ( - setCode(ev.target.value)} - fullWidth - inputProps={{ inputMode: "numeric", pattern: "[0-9]*" }} - variant="standard" - /> - )} -
- - - - -
- ); -}; - -const Stats = () => { - const { t } = useTranslation(); - const { account } = useContext(AccountContext); - - if (!account) { - return <>; - } - - const normalize = (value, max) => Math.min((value / max) * 100, 100); - - return ( - - - {t("account_usage_title")} - - - {(account.role === Role.ADMIN || account.limits.reservations > 0) && ( - -
- - {account.stats.reservations.toLocaleString()} - - - {account.role === Role.USER - ? t("account_usage_of_limit", { - limit: account.limits.reservations.toLocaleString(), - }) - : t("account_usage_unlimited")} - -
- 0 - ? normalize(account.stats.reservations, account.limits.reservations) - : 100 - } - /> -
- )} - - {t("account_usage_messages_title")} - - - - - - - } - > -
- - {account.stats.messages.toLocaleString()} - - - {account.role === Role.USER - ? t("account_usage_of_limit", { - limit: account.limits.messages.toLocaleString(), - }) - : t("account_usage_unlimited")} - -
- -
- {config.enable_emails && ( - - {t("account_usage_emails_title")} - - - - - - - } - > -
- - {account.stats.emails.toLocaleString()} - - - {account.role === Role.USER - ? t("account_usage_of_limit", { - limit: account.limits.emails.toLocaleString(), - }) - : t("account_usage_unlimited")} - -
- -
- )} - {config.enable_calls && (account.role === Role.ADMIN || account.limits.calls > 0) && ( - - {t("account_usage_calls_title")} - - - - - - - } - > -
- - {account.stats.calls.toLocaleString()} - - - {account.role === Role.USER - ? t("account_usage_of_limit", { - limit: account.limits.calls.toLocaleString(), - }) - : t("account_usage_unlimited")} - -
- 0 ? normalize(account.stats.calls, account.limits.calls) : 100} - /> -
- )} - -
- - {formatBytes(account.stats.attachment_total_size)} - - - {account.role === Role.USER - ? t("account_usage_of_limit", { - limit: formatBytes(account.limits.attachment_total_size), - }) - : t("account_usage_unlimited")} - -
- -
- {config.enable_reservations && account.role === Role.USER && account.limits.reservations === 0 && ( - - {t("account_usage_reservations_title")} - {config.enable_payments && } - - } - > - {t("account_usage_reservations_none")} - - )} - {config.enable_calls && account.role === Role.USER && account.limits.calls === 0 && ( - - {t("account_usage_calls_title")} - {config.enable_payments && } - - } - > - {t("account_usage_calls_none")} - - )} -
- {account.role === Role.USER && account.limits.basis === LimitBasis.IP && ( - {t("account_usage_basis_ip_description")} - )} -
- ); -}; - -const InfoIcon = () => ( - -); - -const Tokens = () => { - const { t } = useTranslation(); - const { account } = useContext(AccountContext); - const [dialogKey, setDialogKey] = useState(0); - const [dialogOpen, setDialogOpen] = useState(false); - const tokens = account?.tokens || []; - - const handleCreateClick = () => { - setDialogKey((prev) => prev + 1); - setDialogOpen(true); - }; - - const handleDialogClose = () => { - setDialogOpen(false); - }; - - return ( - - - - {t("account_tokens_title")} - - - , - }} - /> - - {tokens?.length > 0 && } - - - - - - - ); -}; - -const TokensTable = (props) => { - const { t } = useTranslation(); - const [snackOpen, setSnackOpen] = useState(false); - const [upsertDialogKey, setUpsertDialogKey] = useState(0); - const [upsertDialogOpen, setUpsertDialogOpen] = useState(false); - const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); - const [selectedToken, setSelectedToken] = useState(null); - - const tokens = (props.tokens || []).sort((a, b) => { - if (a.token === session.token()) { - return -1; - } - if (b.token === session.token()) { - return 1; - } - return a.token.localeCompare(b.token); - }); - - const handleEditClick = (token) => { - setUpsertDialogKey((prev) => prev + 1); - setSelectedToken(token); - setUpsertDialogOpen(true); - }; - - const handleDialogClose = () => { - setUpsertDialogOpen(false); - setDeleteDialogOpen(false); - setSelectedToken(null); - }; - - const handleDeleteClick = async (token) => { - setSelectedToken(token); - setDeleteDialogOpen(true); - }; - - const handleCopy = async (token) => { - await navigator.clipboard.writeText(token); - setSnackOpen(true); - }; - - return ( - - - - {t("account_tokens_table_token_header")} - {t("account_tokens_table_label_header")} - {t("account_tokens_table_expires_header")} - {t("account_tokens_table_last_access_header")} - - - - - {tokens.map((token) => ( - - - - {token.token.slice(0, 12)} - ... - - handleCopy(token.token)}> - - - - - - - {token.token === session.token() && {t("account_tokens_table_current_session")}} - {token.token !== session.token() && (token.label || "-")} - - - {token.expires ? formatShortDateTime(token.expires) : {t("account_tokens_table_never_expires")}} - - -
- {formatShortDateTime(token.last_access)} - - openUrl(`https://whatismyipaddress.com/ip/${token.last_origin}`)}> - - - -
-
- - {token.token !== session.token() && ( - <> - handleEditClick(token)} aria-label={t("account_tokens_dialog_title_edit")}> - - - handleDeleteClick(token)} aria-label={t("account_tokens_dialog_title_delete")}> - - - - )} - {token.token === session.token() && ( - - - - - - - - - - - )} - -
- ))} -
- - setSnackOpen(false)} - message={t("account_tokens_table_copied_to_clipboard")} - /> - - - -
- ); -}; - -const TokenDialog = (props) => { - const { t } = useTranslation(); - const [error, setError] = useState(""); - const [label, setLabel] = useState(props.token?.label || ""); - const [expires, setExpires] = useState(props.token ? -1 : 0); - const fullScreen = useMediaQuery(theme.breakpoints.down("sm")); - const editMode = !!props.token; - - const handleSubmit = async () => { - try { - if (editMode) { - await accountApi.updateToken(props.token.token, label, expires); - } else { - await accountApi.createToken(label, expires); - } - props.onClose(); - } catch (e) { - console.log(`[Account] Error creating token`, e); - if (e instanceof UnauthorizedError) { - session.resetAndRedirect(routes.login); - } else { - setError(e.message); - } - } - }; - - return ( - - {editMode ? t("account_tokens_dialog_title_edit") : t("account_tokens_dialog_title_create")} - - setLabel(ev.target.value)} - fullWidth - variant="standard" - /> - - - - - - - - - - ); -}; - -const TokenDeleteDialog = (props) => { - const { t } = useTranslation(); - const [error, setError] = useState(""); - - const handleSubmit = async () => { - try { - await accountApi.deleteToken(props.token.token); - props.onClose(); - } catch (e) { - console.log(`[Account] Error deleting token`, e); - if (e instanceof UnauthorizedError) { - session.resetAndRedirect(routes.login); - } else { - setError(e.message); - } - } - }; - - return ( - - {t("account_tokens_delete_dialog_title")} - - - - - - - - - - - ); -}; - -const Delete = () => { - const { t } = useTranslation(); - return ( - - - {t("account_delete_title")} - - - - - - ); -}; - -const DeleteAccount = () => { - const { t } = useTranslation(); - const [dialogKey, setDialogKey] = useState(0); - const [dialogOpen, setDialogOpen] = useState(false); - - const handleDialogOpen = () => { - setDialogKey((prev) => prev + 1); - setDialogOpen(true); - }; - - const handleDialogClose = () => { - setDialogOpen(false); - }; - - return ( - -
- -
- -
- ); -}; - -const DeleteAccountDialog = (props) => { - const { t } = useTranslation(); - const { account } = useContext(AccountContext); - const [error, setError] = useState(""); - const [password, setPassword] = useState(""); - const fullScreen = useMediaQuery(theme.breakpoints.down("sm")); - - const handleSubmit = async () => { - try { - await accountApi.delete(password); - await db.delete(); - console.debug(`[Account] Account deleted`); - session.resetAndRedirect(routes.app); - } catch (e) { - console.log(`[Account] Error deleting account`, e); - if (e instanceof IncorrectPasswordError) { - setError(t("account_basics_password_dialog_current_password_incorrect")); - } else if (e instanceof UnauthorizedError) { - session.resetAndRedirect(routes.login); - } else { - setError(e.message); - } - } - }; - - return ( - - {t("account_delete_title")} - - {t("account_delete_dialog_description")} - setPassword(ev.target.value)} - fullWidth - variant="standard" - /> - {account?.billing?.subscription && ( - - {t("account_delete_dialog_billing_warning")} - - )} - - - - - - - ); -}; - -export default Account; diff --git a/web/src/components/ActionBar.js b/web/src/components/ActionBar.js new file mode 100644 index 0000000..189ae1c --- /dev/null +++ b/web/src/components/ActionBar.js @@ -0,0 +1,183 @@ +import AppBar from "@mui/material/AppBar"; +import Navigation from "./Navigation"; +import Toolbar from "@mui/material/Toolbar"; +import IconButton from "@mui/material/IconButton"; +import MenuIcon from "@mui/icons-material/Menu"; +import Typography from "@mui/material/Typography"; +import * as React from "react"; +import {useState} from "react"; +import Box from "@mui/material/Box"; +import {topicDisplayName} from "../app/utils"; +import db from "../app/db"; +import {useLocation, useNavigate} from "react-router-dom"; +import MenuItem from '@mui/material/MenuItem'; +import MoreVertIcon from "@mui/icons-material/MoreVert"; +import NotificationsIcon from '@mui/icons-material/Notifications'; +import NotificationsOffIcon from '@mui/icons-material/NotificationsOff'; +import routes from "./routes"; +import subscriptionManager from "../app/SubscriptionManager"; +import logo from "../img/ntfy.svg"; +import {useTranslation} from "react-i18next"; +import session from "../app/Session"; +import AccountCircleIcon from '@mui/icons-material/AccountCircle'; +import Button from "@mui/material/Button"; +import Divider from "@mui/material/Divider"; +import {Logout, Person, Settings} from "@mui/icons-material"; +import ListItemIcon from "@mui/material/ListItemIcon"; +import accountApi from "../app/AccountApi"; +import PopupMenu from "./PopupMenu"; +import { SubscriptionPopup } from "./SubscriptionPopup"; + +const ActionBar = (props) => { + const { t } = useTranslation(); + const location = useLocation(); + let title = "ntfy"; + if (props.selected) { + title = topicDisplayName(props.selected); + } else if (location.pathname === routes.settings) { + title = t("action_bar_settings"); + } else if (location.pathname === routes.account) { + title = t("action_bar_account"); + } + return ( + Navigation (1200), but < Dialog (1300) + ml: { sm: `${Navigation.width}px` } + }}> + + + + + + + {title} + + {props.selected && + } + + + + ); +}; + +const SettingsIcons = (props) => { + const { t } = useTranslation(); + const [anchorEl, setAnchorEl] = useState(null); + const subscription = props.subscription; + + const handleToggleMute = async () => { + const mutedUntil = (subscription.mutedUntil) ? 0 : 1; // Make this a timestamp in the future + await subscriptionManager.setMutedUntil(subscription.id, mutedUntil); + } + + return ( + <> + + {subscription.mutedUntil ? : } + + setAnchorEl(ev.currentTarget)} aria-label={t("action_bar_toggle_action_menu")}> + + + setAnchorEl(null)} + /> + + ); +}; + +const ProfileIcon = () => { + const { t } = useTranslation(); + const [anchorEl, setAnchorEl] = useState(null); + const open = Boolean(anchorEl); + const navigate = useNavigate(); + + const handleClick = (event) => { + setAnchorEl(event.currentTarget); + }; + + const handleClose = () => { + setAnchorEl(null); + }; + + const handleLogout = async () => { + try { + await accountApi.logout(); + await db.delete(); + } finally { + session.resetAndRedirect(routes.app); + } + }; + + return ( + <> + {session.exists() && + + + + } + {!session.exists() && config.enable_login && + + } + {!session.exists() && config.enable_signup && + + } + + navigate(routes.account)}> + + + + {session.username()} + + + navigate(routes.settings)}> + + + + {t("action_bar_profile_settings")} + + + + + + {t("action_bar_profile_logout")} + + + + ); +}; + +export default ActionBar; diff --git a/web/src/components/ActionBar.jsx b/web/src/components/ActionBar.jsx deleted file mode 100644 index 798efb4..0000000 --- a/web/src/components/ActionBar.jsx +++ /dev/null @@ -1,172 +0,0 @@ -import { AppBar, Toolbar, IconButton, Typography, Box, MenuItem, Button, Divider, ListItemIcon } from "@mui/material"; -import MenuIcon from "@mui/icons-material/Menu"; -import * as React from "react"; -import { useState } from "react"; -import { useLocation, useNavigate } from "react-router-dom"; -import MoreVertIcon from "@mui/icons-material/MoreVert"; -import NotificationsIcon from "@mui/icons-material/Notifications"; -import NotificationsOffIcon from "@mui/icons-material/NotificationsOff"; -import { useTranslation } from "react-i18next"; -import AccountCircleIcon from "@mui/icons-material/AccountCircle"; -import { Logout, Person, Settings } from "@mui/icons-material"; -import session from "../app/Session"; -import logo from "../img/ntfy.svg"; -import subscriptionManager from "../app/SubscriptionManager"; -import routes from "./routes"; -import db from "../app/db"; -import { topicDisplayName } from "../app/utils"; -import Navigation from "./Navigation"; -import accountApi from "../app/AccountApi"; -import PopupMenu from "./PopupMenu"; -import { SubscriptionPopup } from "./SubscriptionPopup"; - -const ActionBar = (props) => { - const { t } = useTranslation(); - const location = useLocation(); - let title = "ntfy"; - if (props.selected) { - title = topicDisplayName(props.selected); - } else if (location.pathname === routes.settings) { - title = t("action_bar_settings"); - } else if (location.pathname === routes.account) { - title = t("action_bar_account"); - } - return ( - Navigation (1200), but < Dialog (1300) - ml: { sm: `${Navigation.width}px` }, - }} - > - - - - - - - {title} - - {props.selected && } - - - - ); -}; - -const SettingsIcons = (props) => { - const { t } = useTranslation(); - const [anchorEl, setAnchorEl] = useState(null); - const { subscription } = props; - - const handleToggleMute = async () => { - const mutedUntil = subscription.mutedUntil ? 0 : 1; // Make this a timestamp in the future - await subscriptionManager.setMutedUntil(subscription.id, mutedUntil); - }; - - return ( - <> - - {subscription.mutedUntil ? : } - - setAnchorEl(ev.currentTarget)} - aria-label={t("action_bar_toggle_action_menu")} - > - - - setAnchorEl(null)} /> - - ); -}; - -const ProfileIcon = () => { - const { t } = useTranslation(); - const [anchorEl, setAnchorEl] = useState(null); - const open = Boolean(anchorEl); - const navigate = useNavigate(); - - const handleClick = (event) => { - setAnchorEl(event.currentTarget); - }; - - const handleClose = () => { - setAnchorEl(null); - }; - - const handleLogout = async () => { - try { - await accountApi.logout(); - await db.delete(); - } finally { - session.resetAndRedirect(routes.app); - } - }; - - return ( - <> - {session.exists() && ( - - - - )} - {!session.exists() && config.enable_login && ( - - )} - {!session.exists() && config.enable_signup && ( - - )} - - navigate(routes.account)}> - - - - {session.username()} - - - navigate(routes.settings)}> - - - - {t("action_bar_profile_settings")} - - - - - - {t("action_bar_profile_logout")} - - - - ); -}; - -export default ActionBar; diff --git a/web/src/components/App.js b/web/src/components/App.js new file mode 100644 index 0000000..861a370 --- /dev/null +++ b/web/src/components/App.js @@ -0,0 +1,147 @@ +import * as React from 'react'; +import {createContext, Suspense, useContext, useEffect, useState} from 'react'; +import Box from '@mui/material/Box'; +import {ThemeProvider} from '@mui/material/styles'; +import CssBaseline from '@mui/material/CssBaseline'; +import Toolbar from '@mui/material/Toolbar'; +import {AllSubscriptions, SingleSubscription} from "./Notifications"; +import theme from "./theme"; +import Navigation from "./Navigation"; +import ActionBar from "./ActionBar"; +import notifier from "../app/Notifier"; +import Preferences from "./Preferences"; +import {useLiveQuery} from "dexie-react-hooks"; +import subscriptionManager from "../app/SubscriptionManager"; +import userManager from "../app/UserManager"; +import {BrowserRouter, Outlet, Route, Routes, useParams} from "react-router-dom"; +import {expandUrl} from "../app/utils"; +import ErrorBoundary from "./ErrorBoundary"; +import routes from "./routes"; +import {useAccountListener, useBackgroundProcesses, useConnectionListeners} from "./hooks"; +import PublishDialog from "./PublishDialog"; +import Messaging from "./Messaging"; +import "./i18n"; // Translations! +import {Backdrop, CircularProgress} from "@mui/material"; +import Login from "./Login"; +import Signup from "./Signup"; +import Account from "./Account"; + +export const AccountContext = createContext(null); + +const App = () => { + const [account, setAccount] = useState(null); + return ( + }> + + + + + + + }/> + }/> + }> + }/> + }/> + }/> + }/> + }/> + + + + + + + + ); +} + +const Layout = () => { + const params = useParams(); + const { account, setAccount } = useContext(AccountContext); + const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false); + const [notificationsGranted, setNotificationsGranted] = useState(notifier.granted()); + const [sendDialogOpenMode, setSendDialogOpenMode] = useState(""); + const users = useLiveQuery(() => userManager.all()); + const subscriptions = useLiveQuery(() => subscriptionManager.all()); + const subscriptionsWithoutInternal = subscriptions?.filter(s => !s.internal); + const newNotificationsCount = subscriptionsWithoutInternal?.reduce((prev, cur) => prev + cur.new, 0) || 0; + const [selected] = (subscriptionsWithoutInternal || []).filter(s => { + return (params.baseUrl && expandUrl(params.baseUrl).includes(s.baseUrl) && params.topic === s.topic) + || (config.base_url === s.baseUrl && params.topic === s.topic) + }); + + useConnectionListeners(account, subscriptions, users); + useAccountListener(setAccount) + useBackgroundProcesses(); + useEffect(() => updateTitle(newNotificationsCount), [newNotificationsCount]); + + return ( + + setMobileDrawerOpen(!mobileDrawerOpen)} + /> + setMobileDrawerOpen(!mobileDrawerOpen)} + onNotificationGranted={setNotificationsGranted} + onPublishMessageClick={() => setSendDialogOpenMode(PublishDialog.OPEN_MODE_DEFAULT)} + /> +
+ + +
+ +
+ ); +} + +const Main = (props) => { + return ( + theme.palette.mode === 'light' ? theme.palette.grey[100] : theme.palette.grey[900] + }} + > + {props.children} + + ); +}; + +const Loader = () => ( + theme.palette.mode === 'light' ? theme.palette.grey[100] : theme.palette.grey[900] + }} + > + + +); + +const updateTitle = (newNotificationsCount) => { + document.title = (newNotificationsCount > 0) ? `(${newNotificationsCount}) ntfy` : "ntfy"; +} + +export default App; diff --git a/web/src/components/App.jsx b/web/src/components/App.jsx deleted file mode 100644 index 189235b..0000000 --- a/web/src/components/App.jsx +++ /dev/null @@ -1,140 +0,0 @@ -import * as React from "react"; -import { createContext, Suspense, useContext, useEffect, useState, useMemo } from "react"; -import { Box, Toolbar, CssBaseline, Backdrop, CircularProgress } from "@mui/material"; -import { ThemeProvider } from "@mui/material/styles"; -import { useLiveQuery } from "dexie-react-hooks"; -import { BrowserRouter, Outlet, Route, Routes, useParams } from "react-router-dom"; -import { AllSubscriptions, SingleSubscription } from "./Notifications"; -import theme from "./theme"; -import Navigation from "./Navigation"; -import ActionBar from "./ActionBar"; -import notifier from "../app/Notifier"; -import Preferences from "./Preferences"; -import subscriptionManager from "../app/SubscriptionManager"; -import userManager from "../app/UserManager"; -import { expandUrl } from "../app/utils"; -import ErrorBoundary from "./ErrorBoundary"; -import routes from "./routes"; -import { useAccountListener, useBackgroundProcesses, useConnectionListeners } from "./hooks"; -import PublishDialog from "./PublishDialog"; -import Messaging from "./Messaging"; -import "./i18n"; // Translations! -import Login from "./Login"; -import Signup from "./Signup"; -import Account from "./Account"; - -export const AccountContext = createContext(null); - -const App = () => { - const [account, setAccount] = useState(null); - const accountMemo = useMemo(() => ({ account, setAccount }), [account, setAccount]); - - return ( - }> - - - - - - - } /> - } /> - }> - } /> - } /> - } /> - } /> - } /> - - - - - - - - ); -}; - -const updateTitle = (newNotificationsCount) => { - document.title = newNotificationsCount > 0 ? `(${newNotificationsCount}) ntfy` : "ntfy"; -}; - -const Layout = () => { - const params = useParams(); - const { account, setAccount } = useContext(AccountContext); - const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false); - const [notificationsGranted, setNotificationsGranted] = useState(notifier.granted()); - const [sendDialogOpenMode, setSendDialogOpenMode] = useState(""); - const users = useLiveQuery(() => userManager.all()); - const subscriptions = useLiveQuery(() => subscriptionManager.all()); - const subscriptionsWithoutInternal = subscriptions?.filter((s) => !s.internal); - const newNotificationsCount = subscriptionsWithoutInternal?.reduce((prev, cur) => prev + cur.new, 0) || 0; - const [selected] = (subscriptionsWithoutInternal || []).filter( - (s) => - (params.baseUrl && expandUrl(params.baseUrl).includes(s.baseUrl) && params.topic === s.topic) || - (config.base_url === s.baseUrl && params.topic === s.topic) - ); - - useConnectionListeners(account, subscriptions, users); - useAccountListener(setAccount); - useBackgroundProcesses(); - useEffect(() => updateTitle(newNotificationsCount), [newNotificationsCount]); - - return ( - - setMobileDrawerOpen(!mobileDrawerOpen)} /> - setMobileDrawerOpen(!mobileDrawerOpen)} - onNotificationGranted={setNotificationsGranted} - onPublishMessageClick={() => setSendDialogOpenMode(PublishDialog.OPEN_MODE_DEFAULT)} - /> -
- - -
- -
- ); -}; - -const Main = (props) => ( - (palette.mode === "light" ? palette.grey[100] : palette.grey[900]), - }} - > - {props.children} - -); - -const Loader = () => ( - (palette.mode === "light" ? palette.grey[100] : palette.grey[900]), - }} - > - - -); - -export default App; diff --git a/web/src/components/AttachmentIcon.js b/web/src/components/AttachmentIcon.js new file mode 100644 index 0000000..337760b --- /dev/null +++ b/web/src/components/AttachmentIcon.js @@ -0,0 +1,47 @@ +import * as React from "react"; +import Box from "@mui/material/Box"; +import fileDocument from "../img/file-document.svg"; +import fileImage from "../img/file-image.svg"; +import fileVideo from "../img/file-video.svg"; +import fileAudio from "../img/file-audio.svg"; +import fileApp from "../img/file-app.svg"; +import {useTranslation} from "react-i18next"; + +const AttachmentIcon = (props) => { + const { t } = useTranslation(); + const type = props.type; + let imageFile, imageLabel; + if (!type) { + imageFile = fileDocument; + imageLabel = t("notifications_attachment_file_image"); + } else if (type.startsWith('image/')) { + imageFile = fileImage; + imageLabel = t("notifications_attachment_file_video"); + } else if (type.startsWith('video/')) { + imageFile = fileVideo; + imageLabel = t("notifications_attachment_file_video"); + } else if (type.startsWith('audio/')) { + imageFile = fileAudio; + imageLabel = t("notifications_attachment_file_audio"); + } else if (type === "application/vnd.android.package-archive") { + imageFile = fileApp; + imageLabel = t("notifications_attachment_file_app"); + } else { + imageFile = fileDocument; + imageLabel = t("notifications_attachment_file_document"); + } + return ( + + ); +} + +export default AttachmentIcon; diff --git a/web/src/components/AttachmentIcon.jsx b/web/src/components/AttachmentIcon.jsx deleted file mode 100644 index 9a2581e..0000000 --- a/web/src/components/AttachmentIcon.jsx +++ /dev/null @@ -1,48 +0,0 @@ -import * as React from "react"; -import { Box } from "@mui/material"; -import { useTranslation } from "react-i18next"; -import fileDocument from "../img/file-document.svg"; -import fileImage from "../img/file-image.svg"; -import fileVideo from "../img/file-video.svg"; -import fileAudio from "../img/file-audio.svg"; -import fileApp from "../img/file-app.svg"; - -const AttachmentIcon = (props) => { - const { t } = useTranslation(); - const { type } = props; - let imageFile; - let imageLabel; - if (!type) { - imageFile = fileDocument; - imageLabel = t("notifications_attachment_file_image"); - } else if (type.startsWith("image/")) { - imageFile = fileImage; - imageLabel = t("notifications_attachment_file_video"); - } else if (type.startsWith("video/")) { - imageFile = fileVideo; - imageLabel = t("notifications_attachment_file_video"); - } else if (type.startsWith("audio/")) { - imageFile = fileAudio; - imageLabel = t("notifications_attachment_file_audio"); - } else if (type === "application/vnd.android.package-archive") { - imageFile = fileApp; - imageLabel = t("notifications_attachment_file_app"); - } else { - imageFile = fileDocument; - imageLabel = t("notifications_attachment_file_document"); - } - return ( - - ); -}; - -export default AttachmentIcon; diff --git a/web/src/components/AvatarBox.js b/web/src/components/AvatarBox.js new file mode 100644 index 0000000..2278f60 --- /dev/null +++ b/web/src/components/AvatarBox.js @@ -0,0 +1,29 @@ +import * as React from 'react'; +import {Avatar} from "@mui/material"; +import Box from "@mui/material/Box"; +import logo from "../img/ntfy-filled.svg"; + +const AvatarBox = (props) => { + return ( + + + {props.children} + + ); +} + +export default AvatarBox; diff --git a/web/src/components/AvatarBox.jsx b/web/src/components/AvatarBox.jsx deleted file mode 100644 index 1037868..0000000 --- a/web/src/components/AvatarBox.jsx +++ /dev/null @@ -1,22 +0,0 @@ -import * as React from "react"; -import { Avatar, Box } from "@mui/material"; -import logo from "../img/ntfy-filled.svg"; - -const AvatarBox = (props) => ( - - - {props.children} - -); - -export default AvatarBox; diff --git a/web/src/components/DialogFooter.js b/web/src/components/DialogFooter.js new file mode 100644 index 0000000..68d17c7 --- /dev/null +++ b/web/src/components/DialogFooter.js @@ -0,0 +1,33 @@ +import * as React from "react"; +import Box from "@mui/material/Box"; +import DialogContentText from "@mui/material/DialogContentText"; +import DialogActions from "@mui/material/DialogActions"; + +const DialogFooter = (props) => { + return ( + + + {props.status} + + + {props.children} + + + ); +}; + +export default DialogFooter; diff --git a/web/src/components/DialogFooter.jsx b/web/src/components/DialogFooter.jsx deleted file mode 100644 index bcaf4cf..0000000 --- a/web/src/components/DialogFooter.jsx +++ /dev/null @@ -1,29 +0,0 @@ -import * as React from "react"; -import { Box, DialogContentText, DialogActions } from "@mui/material"; - -const DialogFooter = (props) => ( - - - {props.status} - - {props.children} - -); - -export default DialogFooter; diff --git a/web/src/components/EmojiPicker.js b/web/src/components/EmojiPicker.js new file mode 100644 index 0000000..9b29e8f --- /dev/null +++ b/web/src/components/EmojiPicker.js @@ -0,0 +1,179 @@ +import * as React from 'react'; +import {useRef, useState} from 'react'; +import Typography from '@mui/material/Typography'; +import {rawEmojis} from '../app/emojis'; +import Box from "@mui/material/Box"; +import TextField from "@mui/material/TextField"; +import {ClickAwayListener, Fade, InputAdornment, styled} from "@mui/material"; +import IconButton from "@mui/material/IconButton"; +import {Close} from "@mui/icons-material"; +import Popper from "@mui/material/Popper"; +import {splitNoEmpty} from "../app/utils"; +import {useTranslation} from "react-i18next"; + +// Create emoji list by category and create a search base (string with all search words) +// +// This also filters emojis that are not supported by Desktop Chrome. +// This is a hack, but on Ubuntu 18.04, with Chrome 99, only Emoji <= 11 are supported. + +const emojisByCategory = {}; +const isDesktopChrome = /Chrome/.test(navigator.userAgent) && !/Mobile/.test(navigator.userAgent); +const maxSupportedVersionForDesktopChrome = 11; +rawEmojis.forEach(emoji => { + if (!emojisByCategory[emoji.category]) { + emojisByCategory[emoji.category] = []; + } + try { + const unicodeVersion = parseFloat(emoji.unicode_version); + const supportedEmoji = unicodeVersion <= maxSupportedVersionForDesktopChrome || !isDesktopChrome; + if (supportedEmoji) { + const searchBase = `${emoji.description.toLowerCase()} ${emoji.aliases.join(" ")} ${emoji.tags.join(" ")}`; + const emojiWithSearchBase = { ...emoji, searchBase: searchBase }; + emojisByCategory[emoji.category].push(emojiWithSearchBase); + } + } catch (e) { + // Nothing. Ignore. + } +}); + +const EmojiPicker = (props) => { + const { t } = useTranslation(); + const open = Boolean(props.anchorEl); + const [search, setSearch] = useState(""); + const searchRef = useRef(null); + const searchFields = splitNoEmpty(search.toLowerCase(), " "); + + const handleSearchClear = () => { + setSearch(""); + searchRef.current?.focus(); + }; + + return ( + + {({ TransitionProps }) => ( + + + + setSearch(ev.target.value)} + type="text" + variant="standard" + fullWidth + sx={{ marginTop: 0, marginBottom: "12px", paddingRight: 2 }} + inputProps={{ + role: "searchbox", + "aria-label": t("emoji_picker_search_placeholder") + }} + InputProps={{ + endAdornment: + + + + + + }} + /> + + {Object.keys(emojisByCategory).map(category => + + )} + + + + + )} + + ); +}; + +const Category = (props) => { + const showTitle = props.search.length === 0; + return ( + <> + {showTitle && + + {props.title} + + } + {props.emojis.map(emoji => + props.onPick(emoji.aliases[0])} + /> + )} + + ); +}; + +const Emoji = (props) => { + const emoji = props.emoji; + const matches = emojiMatches(emoji, props.search); + const title = `${emoji.description} (${emoji.aliases[0]})`; + return ( + + {props.emoji.emoji} + + ); +}; + +const EmojiDiv = styled("div")({ + fontSize: "30px", + width: "30px", + height: "30px", + marginTop: "8px", + marginBottom: "8px", + marginRight: "8px", + lineHeight: "30px", + cursor: "pointer", + opacity: 0.85, + "&:hover": { + opacity: 1 + } +}); + +const emojiMatches = (emoji, words) => { + if (words.length === 0) { + return true; + } + for (const word of words) { + if (emoji.searchBase.indexOf(word) === -1) { + return false; + } + } + return true; +} + +export default EmojiPicker; diff --git a/web/src/components/EmojiPicker.jsx b/web/src/components/EmojiPicker.jsx deleted file mode 100644 index d1fb170..0000000 --- a/web/src/components/EmojiPicker.jsx +++ /dev/null @@ -1,158 +0,0 @@ -import * as React from "react"; -import { useRef, useState } from "react"; -import { Typography, Box, TextField, ClickAwayListener, Fade, InputAdornment, styled, IconButton, Popper } from "@mui/material"; -import { Close } from "@mui/icons-material"; -import { useTranslation } from "react-i18next"; -import { splitNoEmpty } from "../app/utils"; -import { rawEmojis } from "../app/emojis"; - -// Create emoji list by category and create a search base (string with all search words) -// -// This also filters emojis that are not supported by Desktop Chrome. -// This is a hack, but on Ubuntu 18.04, with Chrome 99, only Emoji <= 11 are supported. - -const emojisByCategory = {}; -const isDesktopChrome = /Chrome/.test(navigator.userAgent) && !/Mobile/.test(navigator.userAgent); -const maxSupportedVersionForDesktopChrome = 11; -rawEmojis.forEach((emoji) => { - if (!emojisByCategory[emoji.category]) { - emojisByCategory[emoji.category] = []; - } - try { - const unicodeVersion = parseFloat(emoji.unicode_version); - const supportedEmoji = unicodeVersion <= maxSupportedVersionForDesktopChrome || !isDesktopChrome; - if (supportedEmoji) { - const searchBase = `${emoji.description.toLowerCase()} ${emoji.aliases.join(" ")} ${emoji.tags.join(" ")}`; - const emojiWithSearchBase = { ...emoji, searchBase }; - emojisByCategory[emoji.category].push(emojiWithSearchBase); - } - } catch (e) { - // Nothing. Ignore. - } -}); - -const EmojiPicker = (props) => { - const { t } = useTranslation(); - const open = Boolean(props.anchorEl); - const [search, setSearch] = useState(""); - const searchRef = useRef(null); - const searchFields = splitNoEmpty(search.toLowerCase(), " "); - - const handleSearchClear = () => { - setSearch(""); - searchRef.current?.focus(); - }; - - return ( - - {({ TransitionProps }) => ( - - - - setSearch(ev.target.value)} - type="text" - variant="standard" - fullWidth - sx={{ marginTop: 0, marginBottom: "12px", paddingRight: 2 }} - inputProps={{ - role: "searchbox", - "aria-label": t("emoji_picker_search_placeholder"), - }} - InputProps={{ - endAdornment: ( - - - - - - ), - }} - /> - - {Object.keys(emojisByCategory).map((category) => ( - - ))} - - - - - )} - - ); -}; - -const Category = (props) => { - const showTitle = props.search.length === 0; - return ( - <> - {showTitle && ( - - {props.title} - - )} - {props.emojis.map((emoji) => ( - props.onPick(emoji.aliases[0])} /> - ))} - - ); -}; - -const emojiMatches = (emoji, words) => words.length === 0 || words.some((word) => emoji.searchBase.includes(word)); - -const Emoji = (props) => { - const { emoji } = props; - const matches = emojiMatches(emoji, props.search); - const title = `${emoji.description} (${emoji.aliases[0]})`; - return ( - - {props.emoji.emoji} - - ); -}; - -const EmojiDiv = styled("div")({ - fontSize: "30px", - width: "30px", - height: "30px", - marginTop: "8px", - marginBottom: "8px", - marginRight: "8px", - lineHeight: "30px", - cursor: "pointer", - opacity: 0.85, - "&:hover": { - opacity: 1, - }, -}); - -export default EmojiPicker; diff --git a/web/src/components/ErrorBoundary.js b/web/src/components/ErrorBoundary.js new file mode 100644 index 0000000..c6d789a --- /dev/null +++ b/web/src/components/ErrorBoundary.js @@ -0,0 +1,129 @@ +import * as React from "react"; +import StackTrace from "stacktrace-js"; +import {CircularProgress, Link} from "@mui/material"; +import Button from "@mui/material/Button"; +import {Trans, withTranslation} from "react-i18next"; + +class ErrorBoundaryImpl extends React.Component { + constructor(props) { + super(props); + this.state = { + error: false, + originalStack: null, + niceStack: null, + unsupportedIndexedDB: false + }; + } + + componentDidCatch(error, info) { + console.error("[ErrorBoundary] Error caught", error, info); + + // Special case for unsupported IndexedDB in Private Browsing mode (Firefox, Safari), see + // - https://github.com/dexie/Dexie.js/issues/312 + // - https://bugzilla.mozilla.org/show_bug.cgi?id=781982 + const isUnsupportedIndexedDB = error?.name === "InvalidStateError" || + (error?.name === "DatabaseClosedError" && error?.message?.indexOf("InvalidStateError") !== -1); + + if (isUnsupportedIndexedDB) { + this.handleUnsupportedIndexedDB(); + } else { + this.handleError(error, info); + } + } + + handleError(error, info) { + // Immediately render original stack trace + const prettierOriginalStack = info.componentStack + .trim() + .split("\n") + .map(line => ` at ${line}`) + .join("\n"); + this.setState({ + error: true, + originalStack: `${error.toString()}\n${prettierOriginalStack}` + }); + + // Fetch additional info and a better stack trace + StackTrace.fromError(error).then(stack => { + console.error("[ErrorBoundary] Stacktrace fetched", stack); + const niceStack = `${error.toString()}\n` + stack.map( el => ` at ${el.functionName} (${el.fileName}:${el.columnNumber}:${el.lineNumber})`).join("\n"); + this.setState({ niceStack }); + }); + } + + handleUnsupportedIndexedDB() { + this.setState({ + error: true, + unsupportedIndexedDB: true + }); + } + + copyStack() { + let stack = ""; + if (this.state.niceStack) { + stack += `${this.state.niceStack}\n\n`; + } + stack += `${this.state.originalStack}\n`; + navigator.clipboard.writeText(stack); + } + + render() { + if (this.state.error) { + if (this.state.unsupportedIndexedDB) { + return this.renderUnsupportedIndexedDB(); + } else { + return this.renderError(); + } + } + return this.props.children; + } + + renderUnsupportedIndexedDB() { + const { t } = this.props; + return ( +
+

{t("error_boundary_unsupported_indexeddb_title")} 😮

+

+ , + discordLink: , + matrixLink: + }} + /> +

+
+ ); + } + + renderError() { + const { t } = this.props; + return ( +
+

{t("error_boundary_title")} 😮

+

+ , + discordLink: , + matrixLink: + }} + /> +

+

+ +

+

{t("error_boundary_stack_trace")}

+ {this.state.niceStack + ?
{this.state.niceStack}
+ : <> {t("error_boundary_gathering_info")}} +
{this.state.originalStack}
+
+ ); + } +} + +const ErrorBoundary = withTranslation()(ErrorBoundaryImpl); // Adds props.t +export default ErrorBoundary; diff --git a/web/src/components/ErrorBoundary.jsx b/web/src/components/ErrorBoundary.jsx deleted file mode 100644 index 9715c0c..0000000 --- a/web/src/components/ErrorBoundary.jsx +++ /dev/null @@ -1,134 +0,0 @@ -import * as React from "react"; -import StackTrace from "stacktrace-js"; -import { CircularProgress, Link, Button } from "@mui/material"; -import { Trans, withTranslation } from "react-i18next"; - -class ErrorBoundaryImpl extends React.Component { - constructor(props) { - super(props); - this.state = { - error: false, - originalStack: null, - niceStack: null, - unsupportedIndexedDB: false, - }; - } - - componentDidCatch(error, info) { - console.error("[ErrorBoundary] Error caught", error, info); - - // Special case for unsupported IndexedDB in Private Browsing mode (Firefox, Safari), see - // - https://github.com/dexie/Dexie.js/issues/312 - // - https://bugzilla.mozilla.org/show_bug.cgi?id=781982 - const isUnsupportedIndexedDB = - error?.name === "InvalidStateError" || (error?.name === "DatabaseClosedError" && error?.message?.indexOf("InvalidStateError") !== -1); - - if (isUnsupportedIndexedDB) { - this.handleUnsupportedIndexedDB(); - } else { - this.handleError(error, info); - } - } - - handleError(error, info) { - // Immediately render original stack trace - const prettierOriginalStack = info.componentStack - .trim() - .split("\n") - .map((line) => ` at ${line}`) - .join("\n"); - this.setState({ - error: true, - originalStack: `${error.toString()}\n${prettierOriginalStack}`, - }); - - // Fetch additional info and a better stack trace - StackTrace.fromError(error).then((stack) => { - console.error("[ErrorBoundary] Stacktrace fetched", stack); - const stackString = stack.map((el) => ` at ${el.functionName} (${el.fileName}:${el.columnNumber}:${el.lineNumber})`).join("\n"); - const niceStack = `${error.toString()}\n${stackString}`; - this.setState({ niceStack }); - }); - } - - handleUnsupportedIndexedDB() { - this.setState({ - error: true, - unsupportedIndexedDB: true, - }); - } - - copyStack() { - let stack = ""; - if (this.state.niceStack) { - stack += `${this.state.niceStack}\n\n`; - } - stack += `${this.state.originalStack}\n`; - navigator.clipboard.writeText(stack); - } - - renderUnsupportedIndexedDB() { - const { t } = this.props; - return ( -
-

{t("error_boundary_unsupported_indexeddb_title")} 😮

-

- , - discordLink: , - matrixLink: , - }} - /> -

-
- ); - } - - renderError() { - const { t } = this.props; - return ( -
-

{t("error_boundary_title")} 😮

-

- , - discordLink: , - matrixLink: , - }} - /> -

-

- -

-

{t("error_boundary_stack_trace")}

- {this.state.niceStack ? ( -
{this.state.niceStack}
- ) : ( - <> - {t("error_boundary_gathering_info")} - - )} -
{this.state.originalStack}
-
- ); - } - - render() { - if (this.state.error) { - if (this.state.unsupportedIndexedDB) { - return this.renderUnsupportedIndexedDB(); - } - return this.renderError(); - } - return this.props.children; - } -} - -const ErrorBoundary = withTranslation()(ErrorBoundaryImpl); // Adds props.t -export default ErrorBoundary; diff --git a/web/src/components/Login.js b/web/src/components/Login.js new file mode 100644 index 0000000..8b14c53 --- /dev/null +++ b/web/src/components/Login.js @@ -0,0 +1,122 @@ +import * as React from 'react'; +import {useState} from 'react'; +import Typography from "@mui/material/Typography"; +import WarningAmberIcon from '@mui/icons-material/WarningAmber'; +import TextField from "@mui/material/TextField"; +import Button from "@mui/material/Button"; +import Box from "@mui/material/Box"; +import routes from "./routes"; +import session from "../app/Session"; +import {NavLink} from "react-router-dom"; +import AvatarBox from "./AvatarBox"; +import {useTranslation} from "react-i18next"; +import accountApi from "../app/AccountApi"; +import IconButton from "@mui/material/IconButton"; +import {InputAdornment} from "@mui/material"; +import {Visibility, VisibilityOff} from "@mui/icons-material"; +import {UnauthorizedError} from "../app/errors"; + +const Login = () => { + const { t } = useTranslation(); + const [error, setError] = useState(""); + const [username, setUsername] = useState(""); + const [password, setPassword] = useState(""); + const [showPassword, setShowPassword] = useState(false); + + const handleSubmit = async (event) => { + event.preventDefault(); + const user = { username, password }; + try { + const token = await accountApi.login(user); + console.log(`[Login] User auth for user ${user.username} successful, token is ${token}`); + session.store(user.username, token); + window.location.href = routes.app; + } catch (e) { + console.log(`[Login] User auth for user ${user.username} failed`, e); + if (e instanceof UnauthorizedError) { + setError(t("Login failed: Invalid username or password")); + } else { + setError(e.message); + } + } + }; + if (!config.enable_login) { + return ( + + {t("login_disabled")} + + ); + } + return ( + + + {t("login_title")} + + + setUsername(ev.target.value.trim())} + autoFocus + /> + setPassword(ev.target.value.trim())} + autoComplete="current-password" + InputProps={{ + endAdornment: ( + + setShowPassword(!showPassword)} + onMouseDown={(ev) => ev.preventDefault()} + edge="end" + > + {showPassword ? : } + + + ) + }} + /> + + {error && + + + {error} + + } + + {/* This is where the password reset link would go */} + {config.enable_signup &&
{t("login_link_signup")}
} +
+
+
+ ); +} + +export default Login; diff --git a/web/src/components/Login.jsx b/web/src/components/Login.jsx deleted file mode 100644 index 489eee0..0000000 --- a/web/src/components/Login.jsx +++ /dev/null @@ -1,117 +0,0 @@ -import * as React from "react"; -import { useState } from "react"; -import { Typography, TextField, Button, Box, IconButton, InputAdornment } from "@mui/material"; -import WarningAmberIcon from "@mui/icons-material/WarningAmber"; -import { NavLink } from "react-router-dom"; -import { useTranslation } from "react-i18next"; -import { Visibility, VisibilityOff } from "@mui/icons-material"; -import accountApi from "../app/AccountApi"; -import AvatarBox from "./AvatarBox"; -import session from "../app/Session"; -import routes from "./routes"; -import { UnauthorizedError } from "../app/errors"; - -const Login = () => { - const { t } = useTranslation(); - const [error, setError] = useState(""); - const [username, setUsername] = useState(""); - const [password, setPassword] = useState(""); - const [showPassword, setShowPassword] = useState(false); - - const handleSubmit = async (event) => { - event.preventDefault(); - const user = { username, password }; - try { - const token = await accountApi.login(user); - console.log(`[Login] User auth for user ${user.username} successful, token is ${token}`); - session.store(user.username, token); - window.location.href = routes.app; - } catch (e) { - console.log(`[Login] User auth for user ${user.username} failed`, e); - if (e instanceof UnauthorizedError) { - setError(t("Login failed: Invalid username or password")); - } else { - setError(e.message); - } - } - }; - if (!config.enable_login) { - return ( - - {t("login_disabled")} - - ); - } - return ( - - {t("login_title")} - - setUsername(ev.target.value.trim())} - autoFocus - /> - setPassword(ev.target.value.trim())} - autoComplete="current-password" - InputProps={{ - endAdornment: ( - - setShowPassword(!showPassword)} - onMouseDown={(ev) => ev.preventDefault()} - edge="end" - > - {showPassword ? : } - - - ), - }} - /> - - {error && ( - - - {error} - - )} - - {/* This is where the password reset link would go */} - {config.enable_signup && ( -
- - {t("login_link_signup")} - -
- )} -
-
-
- ); -}; - -export default Login; diff --git a/web/src/components/Messaging.js b/web/src/components/Messaging.js new file mode 100644 index 0000000..b1f11a9 --- /dev/null +++ b/web/src/components/Messaging.js @@ -0,0 +1,114 @@ +import * as React from 'react'; +import {useState} from 'react'; +import Navigation from "./Navigation"; +import Paper from "@mui/material/Paper"; +import IconButton from "@mui/material/IconButton"; +import TextField from "@mui/material/TextField"; +import SendIcon from "@mui/icons-material/Send"; +import api from "../app/Api"; +import PublishDialog from "./PublishDialog"; +import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp'; +import {Portal, Snackbar} from "@mui/material"; +import {useTranslation} from "react-i18next"; + +const Messaging = (props) => { + const [message, setMessage] = useState(""); + const [dialogKey, setDialogKey] = useState(0); + + const dialogOpenMode = props.dialogOpenMode; + const subscription = props.selected; + + const handleOpenDialogClick = () => { + props.onDialogOpenModeChange(PublishDialog.OPEN_MODE_DEFAULT); + }; + + const handleDialogClose = () => { + props.onDialogOpenModeChange(""); + setDialogKey(prev => prev+1); + }; + + return ( + <> + {subscription && } + props.onDialogOpenModeChange(prev => (prev) ? prev : PublishDialog.OPEN_MODE_DRAG)} // Only update if not already open + onResetOpenMode={() => props.onDialogOpenModeChange(PublishDialog.OPEN_MODE_DEFAULT)} + /> + + ); +} + +const MessageBar = (props) => { + const { t } = useTranslation(); + const subscription = props.subscription; + const [snackOpen, setSnackOpen] = useState(false); + const handleSendClick = async () => { + try { + await api.publish(subscription.baseUrl, subscription.topic, props.message); + } catch (e) { + console.log(`[MessageBar] Error publishing message`, e); + setSnackOpen(true); + } + props.onMessageChange(""); + }; + return ( + theme.palette.mode === 'light' ? theme.palette.grey[100] : theme.palette.grey[900] + }} + > + + + + props.onMessageChange(ev.target.value)} + onKeyPress={(ev) => { + if (ev.key === 'Enter') { + ev.preventDefault(); + handleSendClick(); + } + }} + /> + + + + + setSnackOpen(false)} + message={t("message_bar_error_publishing")} + /> + + + ); +}; + +export default Messaging; diff --git a/web/src/components/Messaging.jsx b/web/src/components/Messaging.jsx deleted file mode 100644 index 27e08dc..0000000 --- a/web/src/components/Messaging.jsx +++ /dev/null @@ -1,108 +0,0 @@ -import * as React from "react"; -import { useState } from "react"; -import { Paper, IconButton, TextField, Portal, Snackbar } from "@mui/material"; -import SendIcon from "@mui/icons-material/Send"; -import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp"; -import { useTranslation } from "react-i18next"; -import PublishDialog from "./PublishDialog"; -import api from "../app/Api"; -import Navigation from "./Navigation"; - -const Messaging = (props) => { - const [message, setMessage] = useState(""); - const [dialogKey, setDialogKey] = useState(0); - - const { dialogOpenMode } = props; - const subscription = props.selected; - - const handleOpenDialogClick = () => { - props.onDialogOpenModeChange(PublishDialog.OPEN_MODE_DEFAULT); - }; - - const handleDialogClose = () => { - props.onDialogOpenModeChange(""); - setDialogKey((prev) => prev + 1); - }; - - return ( - <> - {subscription && ( - - )} - props.onDialogOpenModeChange((prev) => prev || PublishDialog.OPEN_MODE_DRAG)} // Only update if not already open - onResetOpenMode={() => props.onDialogOpenModeChange(PublishDialog.OPEN_MODE_DEFAULT)} - /> - - ); -}; - -const MessageBar = (props) => { - const { t } = useTranslation(); - const { subscription } = props; - const [snackOpen, setSnackOpen] = useState(false); - const handleSendClick = async () => { - try { - await api.publish(subscription.baseUrl, subscription.topic, props.message); - } catch (e) { - console.log(`[MessageBar] Error publishing message`, e); - setSnackOpen(true); - } - props.onMessageChange(""); - }; - return ( - (theme.palette.mode === "light" ? theme.palette.grey[100] : theme.palette.grey[900]), - }} - > - - - - props.onMessageChange(ev.target.value)} - onKeyPress={(ev) => { - if (ev.key === "Enter") { - ev.preventDefault(); - handleSendClick(); - } - }} - /> - - - - - setSnackOpen(false)} - message={t("message_bar_error_publishing")} - /> - - - ); -}; - -export default Messaging; diff --git a/web/src/components/Navigation.js b/web/src/components/Navigation.js new file mode 100644 index 0000000..a7d0da0 --- /dev/null +++ b/web/src/components/Navigation.js @@ -0,0 +1,371 @@ +import Drawer from "@mui/material/Drawer"; +import * as React from "react"; +import {useContext, useState} from "react"; +import ListItemButton from "@mui/material/ListItemButton"; +import ListItemIcon from "@mui/material/ListItemIcon"; +import ChatBubbleOutlineIcon from "@mui/icons-material/ChatBubbleOutline"; +import Person from "@mui/icons-material/Person"; +import ListItemText from "@mui/material/ListItemText"; +import Toolbar from "@mui/material/Toolbar"; +import Divider from "@mui/material/Divider"; +import List from "@mui/material/List"; +import SettingsIcon from "@mui/icons-material/Settings"; +import AddIcon from "@mui/icons-material/Add"; +import SubscribeDialog from "./SubscribeDialog"; +import {Alert, AlertTitle, Badge, CircularProgress, Link, ListSubheader, Portal, Tooltip} from "@mui/material"; +import Button from "@mui/material/Button"; +import Typography from "@mui/material/Typography"; +import {openUrl, topicDisplayName, topicUrl} from "../app/utils"; +import routes from "./routes"; +import {ConnectionState} from "../app/Connection"; +import {useLocation, useNavigate} from "react-router-dom"; +import subscriptionManager from "../app/SubscriptionManager"; +import {ChatBubble, MoreVert, NotificationsOffOutlined, Send} from "@mui/icons-material"; +import Box from "@mui/material/Box"; +import notifier from "../app/Notifier"; +import config from "../app/config"; +import ArticleIcon from '@mui/icons-material/Article'; +import {Trans, useTranslation} from "react-i18next"; +import session from "../app/Session"; +import accountApi, {Permission, Role} from "../app/AccountApi"; +import CelebrationIcon from '@mui/icons-material/Celebration'; +import UpgradeDialog from "./UpgradeDialog"; +import {AccountContext} from "./App"; +import {PermissionDenyAll, PermissionRead, PermissionReadWrite, PermissionWrite} from "./ReserveIcons"; +import IconButton from "@mui/material/IconButton"; +import { SubscriptionPopup } from "./SubscriptionPopup"; + +const navWidth = 280; + +const Navigation = (props) => { + const navigationList = ; + return ( + + {/* Mobile drawer; only shown if menu icon clicked (mobile open) and display is small */} + + {navigationList} + + {/* Big screen drawer; persistent, shown if screen is big */} + + {navigationList} + + + ); +}; +Navigation.width = navWidth; + +const NavList = (props) => { + const { t } = useTranslation(); + const navigate = useNavigate(); + const location = useLocation(); + const { account } = useContext(AccountContext); + const [subscribeDialogKey, setSubscribeDialogKey] = useState(0); + const [subscribeDialogOpen, setSubscribeDialogOpen] = useState(false); + + const handleSubscribeReset = () => { + setSubscribeDialogOpen(false); + setSubscribeDialogKey(prev => prev+1); + } + + const handleSubscribeSubmit = (subscription) => { + console.log(`[Navigation] New subscription: ${subscription.id}`, subscription); + handleSubscribeReset(); + navigate(routes.forSubscription(subscription)); + handleRequestNotificationPermission(); + } + + const handleRequestNotificationPermission = () => { + notifier.maybeRequestPermission(granted => props.onNotificationGranted(granted)) + }; + + const handleAccountClick = () => { + accountApi.sync(); // Dangle! + navigate(routes.account); + }; + + const isAdmin = account?.role === Role.ADMIN; + const isPaid = account?.billing?.subscription; + const showUpgradeBanner = config.enable_payments && !isAdmin && !isPaid; + const showSubscriptionsList = props.subscriptions?.length > 0; + const showNotificationBrowserNotSupportedBox = !notifier.browserSupported(); + const showNotificationContextNotSupportedBox = notifier.browserSupported() && !notifier.contextSupported(); // Only show if notifications are generally supported in the browser + const showNotificationGrantBox = notifier.supported() && props.subscriptions?.length > 0 && !props.notificationsGranted; + const navListPadding = (showNotificationGrantBox || showNotificationBrowserNotSupportedBox || showNotificationContextNotSupportedBox) ? '0' : ''; + + return ( + <> + + + {showNotificationBrowserNotSupportedBox && } + {showNotificationContextNotSupportedBox && } + {showNotificationGrantBox && } + {!showSubscriptionsList && + navigate(routes.app)} selected={location.pathname === config.app_root}> + + + } + {showSubscriptionsList && + <> + {t("nav_topics_title")} + navigate(routes.app)} selected={location.pathname === config.app_root}> + + + + + + } + {session.exists() && + + + + + } + navigate(routes.settings)} selected={location.pathname === routes.settings}> + + + + openUrl("/docs")}> + + + + props.onPublishMessageClick()}> + + + + setSubscribeDialogOpen(true)}> + + + + {showUpgradeBanner && + + } + + + + ); +}; + +const UpgradeBanner = () => { + const { t } = useTranslation(); + const [dialogKey, setDialogKey] = useState(0); + const [dialogOpen, setDialogOpen] = useState(false); + + const handleClick = () => { + setDialogKey(k => k + 1); + setDialogOpen(true); + }; + + return ( + + + + + + + setDialogOpen(false)} + /> + + ); +}; + +const SubscriptionList = (props) => { + const sortedSubscriptions = props.subscriptions + .filter(s => !s.internal) + .sort((a, b) => { + return (topicUrl(a.baseUrl, a.topic) < topicUrl(b.baseUrl, b.topic)) ? -1 : 1; + }); + return ( + <> + {sortedSubscriptions.map(subscription => + )} + + ); +} + +const SubscriptionItem = (props) => { + const { t } = useTranslation(); + const navigate = useNavigate(); + const [menuAnchorEl, setMenuAnchorEl] = useState(null); + + const subscription = props.subscription; + const iconBadge = (subscription.new <= 99) ? subscription.new : "99+"; + const displayName = topicDisplayName(subscription); + const ariaLabel = (subscription.state === ConnectionState.Connecting) + ? `${displayName} (${t("nav_button_connecting")})` + : displayName; + const icon = (subscription.state === ConnectionState.Connecting) + ? + : ; + + const handleClick = async () => { + navigate(routes.forSubscription(subscription)); + await subscriptionManager.markNotificationsRead(subscription.id); + }; + + return ( + <> + + {icon} + + {subscription.reservation?.everyone && + + {subscription.reservation?.everyone === Permission.READ_WRITE && + + } + {subscription.reservation?.everyone === Permission.READ_ONLY && + + } + {subscription.reservation?.everyone === Permission.WRITE_ONLY && + + } + {subscription.reservation?.everyone === Permission.DENY_ALL && + + } + + } + {subscription.mutedUntil > 0 && + + + + } + + e.stopPropagation()} + onClick={(e) => { + e.stopPropagation(); + setMenuAnchorEl(e.currentTarget); + }} + > + + + + + + setMenuAnchorEl(null)} + /> + + + ); +}; + +const NotificationGrantAlert = (props) => { + const { t } = useTranslation(); + return ( + <> + + {t("alert_grant_title")} + {t("alert_grant_description")} + + + + + ); +}; + +const NotificationBrowserNotSupportedAlert = () => { + const { t } = useTranslation(); + return ( + <> + + {t("alert_not_supported_title")} + {t("alert_not_supported_description")} + + + + ); +}; + +const NotificationContextNotSupportedAlert = () => { + const { t } = useTranslation(); + return ( + <> + + {t("alert_not_supported_title")} + + + }} + /> + + + + + ); +}; + +export default Navigation; diff --git a/web/src/components/Navigation.jsx b/web/src/components/Navigation.jsx deleted file mode 100644 index 8cbefec..0000000 --- a/web/src/components/Navigation.jsx +++ /dev/null @@ -1,396 +0,0 @@ -import { - Drawer, - ListItemButton, - ListItemIcon, - ListItemText, - Toolbar, - Divider, - List, - Alert, - AlertTitle, - Badge, - CircularProgress, - Link, - ListSubheader, - Portal, - Tooltip, - Button, - Typography, - Box, - IconButton, -} from "@mui/material"; -import * as React from "react"; -import { useContext, useState } from "react"; -import ChatBubbleOutlineIcon from "@mui/icons-material/ChatBubbleOutline"; -import Person from "@mui/icons-material/Person"; -import SettingsIcon from "@mui/icons-material/Settings"; -import AddIcon from "@mui/icons-material/Add"; -import { useLocation, useNavigate } from "react-router-dom"; -import { ChatBubble, MoreVert, NotificationsOffOutlined, Send } from "@mui/icons-material"; -import ArticleIcon from "@mui/icons-material/Article"; -import { Trans, useTranslation } from "react-i18next"; -import CelebrationIcon from "@mui/icons-material/Celebration"; -import SubscribeDialog from "./SubscribeDialog"; -import { openUrl, topicDisplayName, topicUrl } from "../app/utils"; -import routes from "./routes"; -import { ConnectionState } from "../app/Connection"; -import subscriptionManager from "../app/SubscriptionManager"; -import notifier from "../app/Notifier"; -import config from "../app/config"; -import session from "../app/Session"; -import accountApi, { Permission, Role } from "../app/AccountApi"; -import UpgradeDialog from "./UpgradeDialog"; -import { AccountContext } from "./App"; -import { PermissionDenyAll, PermissionRead, PermissionReadWrite, PermissionWrite } from "./ReserveIcons"; -import { SubscriptionPopup } from "./SubscriptionPopup"; - -const navWidth = 280; - -const Navigation = (props) => { - const navigationList = ; - return ( - - {/* Mobile drawer; only shown if menu icon clicked (mobile open) and display is small */} - - {navigationList} - - {/* Big screen drawer; persistent, shown if screen is big */} - - {navigationList} - - - ); -}; -Navigation.width = navWidth; - -const NavList = (props) => { - const { t } = useTranslation(); - const navigate = useNavigate(); - const location = useLocation(); - const { account } = useContext(AccountContext); - const [subscribeDialogKey, setSubscribeDialogKey] = useState(0); - const [subscribeDialogOpen, setSubscribeDialogOpen] = useState(false); - - const handleSubscribeReset = () => { - setSubscribeDialogOpen(false); - setSubscribeDialogKey((prev) => prev + 1); - }; - - const handleRequestNotificationPermission = () => { - notifier.maybeRequestPermission((granted) => props.onNotificationGranted(granted)); - }; - - const handleSubscribeSubmit = (subscription) => { - console.log(`[Navigation] New subscription: ${subscription.id}`, subscription); - handleSubscribeReset(); - navigate(routes.forSubscription(subscription)); - handleRequestNotificationPermission(); - }; - - const handleAccountClick = () => { - accountApi.sync(); // Dangle! - navigate(routes.account); - }; - - const isAdmin = account?.role === Role.ADMIN; - const isPaid = account?.billing?.subscription; - const showUpgradeBanner = config.enable_payments && !isAdmin && !isPaid; - const showSubscriptionsList = props.subscriptions?.length > 0; - const showNotificationBrowserNotSupportedBox = !notifier.browserSupported(); - const showNotificationContextNotSupportedBox = notifier.browserSupported() && !notifier.contextSupported(); // Only show if notifications are generally supported in the browser - const showNotificationGrantBox = notifier.supported() && props.subscriptions?.length > 0 && !props.notificationsGranted; - const navListPadding = - showNotificationGrantBox || showNotificationBrowserNotSupportedBox || showNotificationContextNotSupportedBox ? "0" : ""; - - return ( - <> - - - {showNotificationBrowserNotSupportedBox && } - {showNotificationContextNotSupportedBox && } - {showNotificationGrantBox && } - {!showSubscriptionsList && ( - navigate(routes.app)} selected={location.pathname === config.app_root}> - - - - - - )} - {showSubscriptionsList && ( - <> - {t("nav_topics_title")} - navigate(routes.app)} selected={location.pathname === config.app_root}> - - - - - - - - - )} - {session.exists() && ( - - - - - - - )} - navigate(routes.settings)} selected={location.pathname === routes.settings}> - - - - - - openUrl("/docs")}> - - - - - - props.onPublishMessageClick()}> - - - - - - setSubscribeDialogOpen(true)}> - - - - - - {showUpgradeBanner && } - - - - ); -}; - -const UpgradeBanner = () => { - const { t } = useTranslation(); - const [dialogKey, setDialogKey] = useState(0); - const [dialogOpen, setDialogOpen] = useState(false); - - const handleClick = () => { - setDialogKey((k) => k + 1); - setDialogOpen(true); - }; - - return ( - - - - - - - - - setDialogOpen(false)} /> - - ); -}; - -const SubscriptionList = (props) => { - const sortedSubscriptions = props.subscriptions - .filter((s) => !s.internal) - .sort((a, b) => (topicUrl(a.baseUrl, a.topic) < topicUrl(b.baseUrl, b.topic) ? -1 : 1)); - return ( - <> - {sortedSubscriptions.map((subscription) => ( - - ))} - - ); -}; - -const SubscriptionItem = (props) => { - const { t } = useTranslation(); - const navigate = useNavigate(); - const [menuAnchorEl, setMenuAnchorEl] = useState(null); - - const { subscription } = props; - const iconBadge = subscription.new <= 99 ? subscription.new : "99+"; - const displayName = topicDisplayName(subscription); - const ariaLabel = subscription.state === ConnectionState.Connecting ? `${displayName} (${t("nav_button_connecting")})` : displayName; - const icon = - subscription.state === ConnectionState.Connecting ? ( - - ) : ( - - - - ); - - const handleClick = async () => { - navigate(routes.forSubscription(subscription)); - await subscriptionManager.markNotificationsRead(subscription.id); - }; - - return ( - <> - - {icon} - - {subscription.reservation?.everyone && ( - - {subscription.reservation?.everyone === Permission.READ_WRITE && ( - - - - )} - {subscription.reservation?.everyone === Permission.READ_ONLY && ( - - - - )} - {subscription.reservation?.everyone === Permission.WRITE_ONLY && ( - - - - )} - {subscription.reservation?.everyone === Permission.DENY_ALL && ( - - - - )} - - )} - {subscription.mutedUntil > 0 && ( - - - - - - )} - - e.stopPropagation()} - onClick={(e) => { - e.stopPropagation(); - setMenuAnchorEl(e.currentTarget); - }} - > - - - - - - setMenuAnchorEl(null)} /> - - - ); -}; - -const NotificationGrantAlert = (props) => { - const { t } = useTranslation(); - return ( - <> - - {t("alert_grant_title")} - {t("alert_grant_description")} - - - - - ); -}; - -const NotificationBrowserNotSupportedAlert = () => { - const { t } = useTranslation(); - return ( - <> - - {t("alert_not_supported_title")} - {t("alert_not_supported_description")} - - - - ); -}; - -const NotificationContextNotSupportedAlert = () => { - const { t } = useTranslation(); - return ( - <> - - {t("alert_not_supported_title")} - - , - }} - /> - - - - - ); -}; - -export default Navigation; diff --git a/web/src/components/Notifications.js b/web/src/components/Notifications.js new file mode 100644 index 0000000..10bcad8 --- /dev/null +++ b/web/src/components/Notifications.js @@ -0,0 +1,548 @@ +import Container from "@mui/material/Container"; +import { + ButtonBase, + CardActions, + CardContent, + CircularProgress, + Fade, + Link, + Modal, + Snackbar, + Stack, + Tooltip +} from "@mui/material"; +import Card from "@mui/material/Card"; +import Typography from "@mui/material/Typography"; +import * as React from "react"; +import {useEffect, useState} from "react"; +import { + formatBytes, + formatMessage, + formatShortDateTime, + formatTitle, + maybeAppendActionErrors, + openUrl, + shortUrl, + topicShortUrl, + unmatchedTags +} from "../app/utils"; +import IconButton from "@mui/material/IconButton"; +import CheckIcon from '@mui/icons-material/Check'; +import CloseIcon from '@mui/icons-material/Close'; +import {LightboxBackdrop, Paragraph, VerticallyCenteredContainer} from "./styles"; +import {useLiveQuery} from "dexie-react-hooks"; +import Box from "@mui/material/Box"; +import Button from "@mui/material/Button"; +import subscriptionManager from "../app/SubscriptionManager"; +import InfiniteScroll from "react-infinite-scroll-component"; +import priority1 from "../img/priority-1.svg"; +import priority2 from "../img/priority-2.svg"; +import priority4 from "../img/priority-4.svg"; +import priority5 from "../img/priority-5.svg"; +import logoOutline from "../img/ntfy-outline.svg"; +import AttachmentIcon from "./AttachmentIcon"; +import {Trans, useTranslation} from "react-i18next"; +import {useOutletContext} from "react-router-dom"; +import {useAutoSubscribe} from "./hooks"; + +export const AllSubscriptions = () => { + const { subscriptions } = useOutletContext(); + if (!subscriptions) { + return ; + } + return ; +}; + +export const SingleSubscription = () => { + const { subscriptions, selected } = useOutletContext(); + useAutoSubscribe(subscriptions, selected); + if (!selected) { + return ; + } + return ; +}; + +const AllSubscriptionsList = (props) => { + const subscriptions = props.subscriptions; + const notifications = useLiveQuery(() => subscriptionManager.getAllNotifications(), []); + if (notifications === null || notifications === undefined) { + return ; + } else if (subscriptions.length === 0) { + return ; + } else if (notifications.length === 0) { + return ; + } + return ; +} + +const SingleSubscriptionList = (props) => { + const subscription = props.subscription; + const notifications = useLiveQuery(() => subscriptionManager.getNotifications(subscription.id), [subscription]); + if (notifications === null || notifications === undefined) { + return ; + } else if (notifications.length === 0) { + return ; + } + return ; +} + +const NotificationList = (props) => { + const { t } = useTranslation(); + const pageSize = 20; + const notifications = props.notifications; + const [snackOpen, setSnackOpen] = useState(false); + const [maxCount, setMaxCount] = useState(pageSize); + const count = Math.min(notifications.length, maxCount); + + useEffect(() => { + return () => { + setMaxCount(pageSize); + const main = document.getElementById("main"); + if (main) { + main.scrollTo(0, 0); + } + } + }, [props.id]); + + return ( + setMaxCount(prev => prev + pageSize)} + hasMore={count < notifications.length} + loader={<>Loading ...} + scrollThreshold={0.7} + scrollableTarget="main" + > + + + {notifications.slice(0, count).map(notification => + setSnackOpen(true)} + />)} + setSnackOpen(false)} + message={t("notifications_copied_to_clipboard")} + /> + + + + ); +} + +const NotificationItem = (props) => { + const { t } = useTranslation(); + const notification = props.notification; + const attachment = notification.attachment; + const date = formatShortDateTime(notification.time); + const otherTags = unmatchedTags(notification.tags); + const tags = (otherTags.length > 0) ? otherTags.join(', ') : null; + const handleDelete = async () => { + console.log(`[Notifications] Deleting notification ${notification.id}`); + await subscriptionManager.deleteNotification(notification.id) + } + const handleMarkRead = async () => { + console.log(`[Notifications] Marking notification ${notification.id} as read`); + await subscriptionManager.markNotificationRead(notification.id) + } + const handleCopy = (s) => { + navigator.clipboard.writeText(s); + props.onShowSnack(); + }; + const expired = attachment && attachment.expires && attachment.expires < Date.now()/1000; + const hasAttachmentActions = attachment && !expired; + const hasClickAction = notification.click; + const hasUserActions = notification.actions && notification.actions.length > 0; + const showActions = hasAttachmentActions || hasClickAction || hasUserActions; + return ( + + + + + + + + {notification.new === 1 && + + + + + } + + {date} + {[1,2,4,5].includes(notification.priority) && + {t("notifications_priority_x",} + {notification.new === 1 && + + + } + + {notification.title && {formatTitle(notification)}} + + {autolink(maybeAppendActionErrors(formatMessage(notification), notification))} + + {attachment && } + {tags && {t("notifications_tags")}: {tags}} + + {showActions && + + {hasAttachmentActions && <> + + + + + + + } + {hasClickAction && <> + + + + + + + } + {hasUserActions && } + } + + ); +} + +/** + * Replace links with components; this is a combination of the genius function + * in [1] and the regex in [2]. + * + * [1] https://github.com/facebook/react/issues/3386#issuecomment-78605760 + * [2] https://github.com/bryanwoods/autolink-js/blob/master/autolink.js#L9 + */ +const autolink = (s) => { + const parts = s.split(/(\bhttps?:\/\/[\-A-Z0-9+\u0026\u2019@#\/%?=()~_|!:,.;]*[\-A-Z0-9+\u0026@#\/%=~()_|]\b)/gi); + for (let i = 1; i < parts.length; i += 2) { + parts[i] = {shortUrl(parts[i])}; + } + return <>{parts}; +}; + +const priorityFiles = { + 1: priority1, + 2: priority2, + 4: priority4, + 5: priority5 +}; + +const Attachment = (props) => { + const { t } = useTranslation(); + const attachment = props.attachment; + const expired = attachment.expires && attachment.expires < Date.now()/1000; + const expires = attachment.expires && attachment.expires > Date.now()/1000; + const displayableImage = !expired && attachment.type && attachment.type.startsWith("image/"); + + // Unexpired image + if (displayableImage) { + return ; + } + + // Anything else: Show box + const infos = []; + if (attachment.size) { + infos.push(formatBytes(attachment.size)); + } + if (expires) { + infos.push(t("notifications_attachment_link_expires", { date: formatShortDateTime(attachment.expires) })); + } + if (expired) { + infos.push(t("notifications_attachment_link_expired")); + } + const maybeInfoText = (infos.length > 0) ? <>
{infos.join(", ")} : null; + + // If expired, just show infos without click target + if (expired) { + return ( + + + + {attachment.name} + {maybeInfoText} + + + ); + } + + // Not expired + return ( + + + + + {attachment.name} + {maybeInfoText} + + + + ); +}; + +const Image = (props) => { + const { t } = useTranslation(); + const [open, setOpen] = useState(false); + return ( + <> + setOpen(true)} + sx={{ + marginTop: 2, + borderRadius: '4px', + boxShadow: 2, + width: 1, + maxHeight: '400px', + objectFit: 'cover', + cursor: 'pointer' + }} + /> + setOpen(false)} + BackdropComponent={LightboxBackdrop} + > + + + + + + ); +} + +const UserActions = (props) => { + return ( + <>{props.notification.actions.map(action => + )} + ); +}; + +const UserAction = (props) => { + const { t } = useTranslation(); + const notification = props.notification; + const action = props.action; + if (action.action === "broadcast") { + return ( + + + + ); + } else if (action.action === "view") { + return ( + + + + ); + } else if (action.action === "http") { + const method = action.method ?? "POST"; + const label = action.label + (ACTION_LABEL_SUFFIX[action.progress ?? 0] ?? ""); + return ( + + + + ); + } + return null; // Others +}; + +const performHttpAction = async (notification, action) => { + console.log(`[Notifications] Performing HTTP user action`, action); + try { + updateActionStatus(notification, action, ACTION_PROGRESS_ONGOING, null); + const response = await fetch(action.url, { + method: action.method ?? "POST", + headers: action.headers ?? {}, + // This must not null-coalesce to a non nullish value. Otherwise, the fetch API + // will reject it for "having a body" + body: action.body + }); + console.log(`[Notifications] HTTP user action response`, response); + const success = response.status >= 200 && response.status <= 299; + if (success) { + updateActionStatus(notification, action, ACTION_PROGRESS_SUCCESS, null); + } else { + updateActionStatus(notification, action, ACTION_PROGRESS_FAILED, `${action.label}: Unexpected response HTTP ${response.status}`); + } + } catch (e) { + console.log(`[Notifications] HTTP action failed`, e); + updateActionStatus(notification, action, ACTION_PROGRESS_FAILED, `${action.label}: ${e} Check developer console for details.`); + } +}; + +const updateActionStatus = (notification, action, progress, error) => { + notification.actions = notification.actions.map(a => { + if (a.id !== action.id) { + return a; + } + return { ...a, progress: progress, error: error }; + }); + subscriptionManager.updateNotification(notification); +} + +const ACTION_PROGRESS_ONGOING = 1; +const ACTION_PROGRESS_SUCCESS = 2; +const ACTION_PROGRESS_FAILED = 3; + +const ACTION_LABEL_SUFFIX = { + [ACTION_PROGRESS_ONGOING]: " …", + [ACTION_PROGRESS_SUCCESS]: " ✔", + [ACTION_PROGRESS_FAILED]: " ❌" +}; + +const NoNotifications = (props) => { + const { t } = useTranslation(); + const shortUrl = topicShortUrl(props.subscription.baseUrl, props.subscription.topic); + return ( + + + {t("action_bar_logo_alt")}/
+ {t("notifications_none_for_topic_title")} +
+ + {t("notifications_none_for_topic_description")} + + + {t("notifications_example")}:
+ + $ curl -d "Hi" {shortUrl} + +
+ + + +
+ ); +}; + +const NoNotificationsWithoutSubscription = (props) => { + const { t } = useTranslation(); + const subscription = props.subscriptions[0]; + const shortUrl = topicShortUrl(subscription.baseUrl, subscription.topic); + return ( + + + {t("action_bar_logo_alt")}/
+ {t("notifications_none_for_any_title")} +
+ + {t("notifications_none_for_any_description")} + + + {t("notifications_example")}:
+ + $ curl -d "Hi" {shortUrl} + +
+ + + +
+ ); +}; + +const NoSubscriptions = () => { + const { t } = useTranslation(); + return ( + + + {t("action_bar_logo_alt")}/
+ {t("notifications_no_subscriptions_title")} +
+ + {t("notifications_no_subscriptions_description", { + linktext: t("nav_button_subscribe") + })} + + + + +
+ ); +}; + +const ForMoreDetails = () => { + return ( + , + docsLink: + }} + /> + ); +}; + +const Loading = () => { + const { t } = useTranslation(); + return ( + + +
+ {t("notifications_loading")} +
+
+ ); +}; diff --git a/web/src/components/Notifications.jsx b/web/src/components/Notifications.jsx deleted file mode 100644 index 2faf2fd..0000000 --- a/web/src/components/Notifications.jsx +++ /dev/null @@ -1,616 +0,0 @@ -import { - Container, - ButtonBase, - CardActions, - CardContent, - CircularProgress, - Fade, - Link, - Modal, - Snackbar, - Stack, - Tooltip, - Card, - Typography, - IconButton, - Box, - Button, -} from "@mui/material"; -import * as React from "react"; -import { useEffect, useState } from "react"; -import CheckIcon from "@mui/icons-material/Check"; -import CloseIcon from "@mui/icons-material/Close"; -import { useLiveQuery } from "dexie-react-hooks"; -import InfiniteScroll from "react-infinite-scroll-component"; -import { Trans, useTranslation } from "react-i18next"; -import { useOutletContext } from "react-router-dom"; -import { - formatBytes, - formatMessage, - formatShortDateTime, - formatTitle, - maybeAppendActionErrors, - openUrl, - shortUrl, - topicShortUrl, - unmatchedTags, -} from "../app/utils"; -import { LightboxBackdrop, Paragraph, VerticallyCenteredContainer } from "./styles"; -import subscriptionManager from "../app/SubscriptionManager"; -import priority1 from "../img/priority-1.svg"; -import priority2 from "../img/priority-2.svg"; -import priority4 from "../img/priority-4.svg"; -import priority5 from "../img/priority-5.svg"; -import logoOutline from "../img/ntfy-outline.svg"; -import AttachmentIcon from "./AttachmentIcon"; -import { useAutoSubscribe } from "./hooks"; - -const priorityFiles = { - 1: priority1, - 2: priority2, - 4: priority4, - 5: priority5, -}; - -export const AllSubscriptions = () => { - const { subscriptions } = useOutletContext(); - if (!subscriptions) { - return ; - } - return ; -}; - -export const SingleSubscription = () => { - const { subscriptions, selected } = useOutletContext(); - useAutoSubscribe(subscriptions, selected); - if (!selected) { - return ; - } - return ; -}; - -const AllSubscriptionsList = (props) => { - const { subscriptions } = props; - const notifications = useLiveQuery(() => subscriptionManager.getAllNotifications(), []); - if (notifications === null || notifications === undefined) { - return ; - } - if (subscriptions.length === 0) { - return ; - } - if (notifications.length === 0) { - return ; - } - return ; -}; - -const SingleSubscriptionList = (props) => { - const { subscription } = props; - const notifications = useLiveQuery(() => subscriptionManager.getNotifications(subscription.id), [subscription]); - if (notifications === null || notifications === undefined) { - return ; - } - if (notifications.length === 0) { - return ; - } - return ; -}; - -const NotificationList = (props) => { - const { t } = useTranslation(); - const pageSize = 20; - const { notifications } = props; - const [snackOpen, setSnackOpen] = useState(false); - const [maxCount, setMaxCount] = useState(pageSize); - const count = Math.min(notifications.length, maxCount); - - useEffect( - () => () => { - setMaxCount(pageSize); - const main = document.getElementById("main"); - if (main) { - main.scrollTo(0, 0); - } - }, - [props.id] - ); - - return ( - setMaxCount((prev) => prev + pageSize)} - hasMore={count < notifications.length} - loader={<>Loading ...} - scrollThreshold={0.7} - scrollableTarget="main" - > - - - {notifications.slice(0, count).map((notification) => ( - setSnackOpen(true)} /> - ))} - setSnackOpen(false)} - message={t("notifications_copied_to_clipboard")} - /> - - - - ); -}; - -/** - * Replace links with components; this is a combination of the genius function - * in [1] and the regex in [2]. - * - * [1] https://github.com/facebook/react/issues/3386#issuecomment-78605760 - * [2] https://github.com/bryanwoods/autolink-js/blob/master/autolink.js#L9 - */ -const autolink = (s) => { - const parts = s.split(/(\bhttps?:\/\/[-A-Z0-9+\u0026\u2019@#/%?=()~_|!:,.;]*[-A-Z0-9+\u0026@#/%=~()_|]\b)/gi); - for (let i = 1; i < parts.length; i += 2) { - parts[i] = ( - - {shortUrl(parts[i])} - - ); - } - return <>{parts}; -}; - -const NotificationItem = (props) => { - const { t } = useTranslation(); - const { notification } = props; - const { attachment } = notification; - const date = formatShortDateTime(notification.time); - const otherTags = unmatchedTags(notification.tags); - const tags = otherTags.length > 0 ? otherTags.join(", ") : null; - const handleDelete = async () => { - console.log(`[Notifications] Deleting notification ${notification.id}`); - await subscriptionManager.deleteNotification(notification.id); - }; - const handleMarkRead = async () => { - console.log(`[Notifications] Marking notification ${notification.id} as read`); - await subscriptionManager.markNotificationRead(notification.id); - }; - const handleCopy = (s) => { - navigator.clipboard.writeText(s); - props.onShowSnack(); - }; - const expired = attachment && attachment.expires && attachment.expires < Date.now() / 1000; - const hasAttachmentActions = attachment && !expired; - const hasClickAction = notification.click; - const hasUserActions = notification.actions && notification.actions.length > 0; - const showActions = hasAttachmentActions || hasClickAction || hasUserActions; - return ( - - - - - - - - {notification.new === 1 && ( - - - - - - )} - - {date} - {[1, 2, 4, 5].includes(notification.priority) && ( - {t("notifications_priority_x", - )} - {notification.new === 1 && ( - - - - )} - - {notification.title && ( - - {formatTitle(notification)} - - )} - - {autolink(maybeAppendActionErrors(formatMessage(notification), notification))} - - {attachment && } - {tags && ( - - {t("notifications_tags")}: {tags} - - )} - - {showActions && ( - - {hasAttachmentActions && ( - <> - - - - - - - - )} - {hasClickAction && ( - <> - - - - - - - - )} - {hasUserActions && } - - )} - - ); -}; - -const Attachment = (props) => { - const { t } = useTranslation(); - const { attachment } = props; - const expired = attachment.expires && attachment.expires < Date.now() / 1000; - const expires = attachment.expires && attachment.expires > Date.now() / 1000; - const displayableImage = !expired && attachment.type && attachment.type.startsWith("image/"); - - // Unexpired image - if (displayableImage) { - return ; - } - - // Anything else: Show box - const infos = []; - if (attachment.size) { - infos.push(formatBytes(attachment.size)); - } - if (expires) { - infos.push( - t("notifications_attachment_link_expires", { - date: formatShortDateTime(attachment.expires), - }) - ); - } - if (expired) { - infos.push(t("notifications_attachment_link_expired")); - } - const maybeInfoText = - infos.length > 0 ? ( - <> -
- {infos.join(", ")} - - ) : null; - - // If expired, just show infos without click target - if (expired) { - return ( - - - - {attachment.name} - {maybeInfoText} - - - ); - } - - // Not expired - return ( - - - - - {attachment.name} - {maybeInfoText} - - - - ); -}; - -const Image = (props) => { - const { t } = useTranslation(); - const [open, setOpen] = useState(false); - return ( - <> - setOpen(true)} - sx={{ - marginTop: 2, - borderRadius: "4px", - boxShadow: 2, - width: 1, - maxHeight: "400px", - objectFit: "cover", - cursor: "pointer", - }} - /> - setOpen(false)} BackdropComponent={LightboxBackdrop}> - - - - - - ); -}; - -const UserActions = (props) => ( - <> - {props.notification.actions.map((action) => ( - - ))} - -); - -const ACTION_PROGRESS_ONGOING = 1; -const ACTION_PROGRESS_SUCCESS = 2; -const ACTION_PROGRESS_FAILED = 3; - -const ACTION_LABEL_SUFFIX = { - [ACTION_PROGRESS_ONGOING]: " …", - [ACTION_PROGRESS_SUCCESS]: " ✔", - [ACTION_PROGRESS_FAILED]: " ❌", -}; - -const updateActionStatus = (notification, action, progress, error) => { - subscriptionManager.updateNotification({ - ...notification, - actions: notification.actions.map((a) => (a.id === action.id ? { ...a, progress, error } : a)), - }); -}; - -const performHttpAction = async (notification, action) => { - console.log(`[Notifications] Performing HTTP user action`, action); - try { - updateActionStatus(notification, action, ACTION_PROGRESS_ONGOING, null); - const response = await fetch(action.url, { - method: action.method ?? "POST", - headers: action.headers ?? {}, - // This must not null-coalesce to a non nullish value. Otherwise, the fetch API - // will reject it for "having a body" - body: action.body, - }); - console.log(`[Notifications] HTTP user action response`, response); - const success = response.status >= 200 && response.status <= 299; - if (success) { - updateActionStatus(notification, action, ACTION_PROGRESS_SUCCESS, null); - } else { - updateActionStatus(notification, action, ACTION_PROGRESS_FAILED, `${action.label}: Unexpected response HTTP ${response.status}`); - } - } catch (e) { - console.log(`[Notifications] HTTP action failed`, e); - updateActionStatus(notification, action, ACTION_PROGRESS_FAILED, `${action.label}: ${e} Check developer console for details.`); - } -}; - -const UserAction = (props) => { - const { t } = useTranslation(); - const { notification } = props; - const { action } = props; - if (action.action === "broadcast") { - return ( - - - - - - ); - } - if (action.action === "view") { - return ( - - - - ); - } - if (action.action === "http") { - const method = action.method ?? "POST"; - const label = action.label + (ACTION_LABEL_SUFFIX[action.progress ?? 0] ?? ""); - return ( - - - - ); - } - return null; // Others -}; - -const NoNotifications = (props) => { - const { t } = useTranslation(); - const topicShortUrlResolved = topicShortUrl(props.subscription.baseUrl, props.subscription.topic); - return ( - - - {t("action_bar_logo_alt")} -
- {t("notifications_none_for_topic_title")} -
- {t("notifications_none_for_topic_description")} - - {t("notifications_example")}:
- - {'$ curl -d "Hi" '} - {topicShortUrlResolved} - -
- - - -
- ); -}; - -const NoNotificationsWithoutSubscription = (props) => { - const { t } = useTranslation(); - const subscription = props.subscriptions[0]; - const topicShortUrlResolved = topicShortUrl(subscription.baseUrl, subscription.topic); - return ( - - - {t("action_bar_logo_alt")} -
- {t("notifications_none_for_any_title")} -
- {t("notifications_none_for_any_description")} - - {t("notifications_example")}:
- - {'$ curl -d "Hi" '} - {topicShortUrlResolved} - -
- - - -
- ); -}; - -const NoSubscriptions = () => { - const { t } = useTranslation(); - return ( - - - {t("action_bar_logo_alt")} -
- {t("notifications_no_subscriptions_title")} -
- - {t("notifications_no_subscriptions_description", { - linktext: t("nav_button_subscribe"), - })} - - - - -
- ); -}; - -const ForMoreDetails = () => ( - , - docsLink: , - }} - /> -); - -const Loading = () => { - const { t } = useTranslation(); - return ( - - - -
- {t("notifications_loading")} -
-
- ); -}; diff --git a/web/src/components/PopupMenu.js b/web/src/components/PopupMenu.js new file mode 100644 index 0000000..4d22398 --- /dev/null +++ b/web/src/components/PopupMenu.js @@ -0,0 +1,48 @@ +import {Fade, Menu} from "@mui/material"; +import * as React from "react"; + +const PopupMenu = (props) => { + const horizontal = props.horizontal ?? "left"; + const arrow = (horizontal === "right") ? { right: 19 } : { left: 19 }; + return ( + + {props.children} + + ); +}; + +export default PopupMenu; diff --git a/web/src/components/PopupMenu.jsx b/web/src/components/PopupMenu.jsx deleted file mode 100644 index 89b2011..0000000 --- a/web/src/components/PopupMenu.jsx +++ /dev/null @@ -1,48 +0,0 @@ -import { Fade, Menu } from "@mui/material"; -import * as React from "react"; - -const PopupMenu = (props) => { - const horizontal = props.horizontal ?? "left"; - const arrow = horizontal === "right" ? { right: 19 } : { left: 19 }; - return ( - - {props.children} - - ); -}; - -export default PopupMenu; diff --git a/web/src/components/Pref.js b/web/src/components/Pref.js new file mode 100644 index 0000000..622d9bb --- /dev/null +++ b/web/src/components/Pref.js @@ -0,0 +1,51 @@ +import * as React from "react"; + +export const PrefGroup = (props) => { + return ( +
+ {props.children} +
+ ) +}; + +export const Pref = (props) => { + const justifyContent = (props.alignTop) ? "normal" : "center"; + return ( +
+
+
{props.title}{props.subtitle && ({props.subtitle})}
+ {props.description &&
{props.description}
} +
+
+ {props.children} +
+
+ ); +}; diff --git a/web/src/components/Pref.jsx b/web/src/components/Pref.jsx deleted file mode 100644 index a725d11..0000000 --- a/web/src/components/Pref.jsx +++ /dev/null @@ -1,52 +0,0 @@ -import * as React from "react"; - -export const PrefGroup = (props) =>
{props.children}
; - -export const Pref = (props) => { - const justifyContent = props.alignTop ? "normal" : "center"; - return ( -
-
-
- {props.title} - {props.subtitle && ({props.subtitle})} -
- {props.description && ( -
- {props.description} -
- )} -
-
- {props.children} -
-
- ); -}; diff --git a/web/src/components/Preferences.js b/web/src/components/Preferences.js new file mode 100644 index 0000000..3f6c1b3 --- /dev/null +++ b/web/src/components/Preferences.js @@ -0,0 +1,646 @@ +import * as React from 'react'; +import {useContext, useEffect, useState} from 'react'; +import { + Alert, + CardActions, + CardContent, + Chip, + FormControl, + Select, + Stack, + Table, + TableBody, + TableCell, + TableHead, + TableRow, + Tooltip, + useMediaQuery +} from "@mui/material"; +import Typography from "@mui/material/Typography"; +import prefs from "../app/Prefs"; +import {Paragraph} from "./styles"; +import EditIcon from '@mui/icons-material/Edit'; +import CloseIcon from "@mui/icons-material/Close"; +import IconButton from "@mui/material/IconButton"; +import PlayArrowIcon from '@mui/icons-material/PlayArrow'; +import Container from "@mui/material/Container"; +import TextField from "@mui/material/TextField"; +import MenuItem from "@mui/material/MenuItem"; +import Card from "@mui/material/Card"; +import Button from "@mui/material/Button"; +import {useLiveQuery} from "dexie-react-hooks"; +import theme from "./theme"; +import Dialog from "@mui/material/Dialog"; +import DialogTitle from "@mui/material/DialogTitle"; +import DialogContent from "@mui/material/DialogContent"; +import DialogActions from "@mui/material/DialogActions"; +import userManager from "../app/UserManager"; +import {playSound, shuffle, sounds, validUrl} from "../app/utils"; +import {useTranslation} from "react-i18next"; +import session from "../app/Session"; +import routes from "./routes"; +import accountApi, {Permission, Role} from "../app/AccountApi"; +import {Pref, PrefGroup} from "./Pref"; +import {Info} from "@mui/icons-material"; +import {AccountContext} from "./App"; +import {useOutletContext} from "react-router-dom"; +import {PermissionDenyAll, PermissionRead, PermissionReadWrite, PermissionWrite} from "./ReserveIcons"; +import {ReserveAddDialog, ReserveDeleteDialog, ReserveEditDialog} from "./ReserveDialogs"; +import {UnauthorizedError} from "../app/errors"; +import subscriptionManager from "../app/SubscriptionManager"; +import {subscribeTopic} from "./SubscribeDialog"; + +const Preferences = () => { + return ( + + + + + + + + + ); +}; + +const Notifications = () => { + const { t } = useTranslation(); + return ( + + + {t("prefs_notifications_title")} + + + + + + + + ); +}; + +const Sound = () => { + const { t } = useTranslation(); + const labelId = "prefSound"; + const sound = useLiveQuery(async () => prefs.sound()); + const handleChange = async (ev) => { + await prefs.setSound(ev.target.value); + await maybeUpdateAccountSettings({ + notification: { + sound: ev.target.value + } + }); + } + if (!sound) { + return null; // While loading + } + let description; + if (sound === "none") { + description = t("prefs_notifications_sound_description_none"); + } else { + description = t("prefs_notifications_sound_description_some", { sound: sounds[sound].label }); + } + return ( + +
+ + + + playSound(sound)} disabled={sound === "none"} aria-label={t("prefs_notifications_sound_play")}> + + +
+
+ ) +}; + +const MinPriority = () => { + const { t } = useTranslation(); + const labelId = "prefMinPriority"; + const minPriority = useLiveQuery(async () => prefs.minPriority()); + const handleChange = async (ev) => { + await prefs.setMinPriority(ev.target.value); + await maybeUpdateAccountSettings({ + notification: { + min_priority: ev.target.value + } + }); + } + if (!minPriority) { + return null; // While loading + } + const priorities = { + 1: t("priority_min"), + 2: t("priority_low"), + 3: t("priority_default"), + 4: t("priority_high"), + 5: t("priority_max") + } + let description; + if (minPriority === 1) { + description = t("prefs_notifications_min_priority_description_any"); + } else if (minPriority === 5) { + description = t("prefs_notifications_min_priority_description_max"); + } else { + description = t("prefs_notifications_min_priority_description_x_or_higher", { + number: minPriority, + name: priorities[minPriority] + }); + } + return ( + + + + + + ) +}; + +const DeleteAfter = () => { + const { t } = useTranslation(); + const labelId = "prefDeleteAfter"; + const deleteAfter = useLiveQuery(async () => prefs.deleteAfter()); + const handleChange = async (ev) => { + await prefs.setDeleteAfter(ev.target.value); + await maybeUpdateAccountSettings({ + notification: { + delete_after: ev.target.value + } + }); + } + if (deleteAfter === null || deleteAfter === undefined) { // !deleteAfter will not work with "0" + return null; // While loading + } + const description = (() => { + switch (deleteAfter) { + case 0: return t("prefs_notifications_delete_after_never_description"); + case 10800: return t("prefs_notifications_delete_after_three_hours_description"); + case 86400: return t("prefs_notifications_delete_after_one_day_description"); + case 604800: return t("prefs_notifications_delete_after_one_week_description"); + case 2592000: return t("prefs_notifications_delete_after_one_month_description"); + } + })(); + return ( + + + + + + ) +}; + +const Users = () => { + const { t } = useTranslation(); + const [dialogKey, setDialogKey] = useState(0); + const [dialogOpen, setDialogOpen] = useState(false); + const users = useLiveQuery(() => userManager.all()); + const handleAddClick = () => { + setDialogKey(prev => prev+1); + setDialogOpen(true); + }; + const handleDialogCancel = () => { + setDialogOpen(false); + }; + const handleDialogSubmit = async (user) => { + setDialogOpen(false); + try { + await userManager.save(user); + console.debug(`[Preferences] User ${user.username} for ${user.baseUrl} added`); + } catch (e) { + console.log(`[Preferences] Error adding user.`, e); + } + }; + return ( + + + + {t("prefs_users_title")} + + + {t("prefs_users_description")} + {session.exists() && <>{" " + t("prefs_users_description_no_sync")}} + + {users?.length > 0 && } + + + + + + + ); +}; + +const UserTable = (props) => { + const { t } = useTranslation(); + const [dialogKey, setDialogKey] = useState(0); + const [dialogOpen, setDialogOpen] = useState(false); + const [dialogUser, setDialogUser] = useState(null); + + const handleEditClick = (user) => { + setDialogKey(prev => prev+1); + setDialogUser(user); + setDialogOpen(true); + }; + + const handleDialogCancel = () => { + setDialogOpen(false); + }; + + const handleDialogSubmit = async (user) => { + setDialogOpen(false); + try { + await userManager.save(user); + console.debug(`[Preferences] User ${user.username} for ${user.baseUrl} updated`); + } catch (e) { + console.log(`[Preferences] Error updating user.`, e); + } + }; + + const handleDeleteClick = async (user) => { + try { + await userManager.delete(user.baseUrl); + console.debug(`[Preferences] User ${user.username} for ${user.baseUrl} deleted`); + } catch (e) { + console.error(`[Preferences] Error deleting user for ${user.baseUrl}`, e); + } + }; + + return ( + + + + {t("prefs_users_table_user_header")} + {t("prefs_users_table_base_url_header")} + + + + + {props.users?.map(user => ( + + {user.username} + {user.baseUrl} + + {(!session.exists() || user.baseUrl !== config.base_url) && + <> + handleEditClick(user)} aria-label={t("prefs_users_edit_button")}> + + + handleDeleteClick(user)} aria-label={t("prefs_users_delete_button")}> + + + + } + {session.exists() && user.baseUrl === config.base_url && + + + + + + + } + + + ))} + + +
+ ); +}; + +const UserDialog = (props) => { + const { t } = useTranslation(); + const [baseUrl, setBaseUrl] = useState(""); + const [username, setUsername] = useState(""); + const [password, setPassword] = useState(""); + const fullScreen = useMediaQuery(theme.breakpoints.down('sm')); + const editMode = props.user !== null; + const addButtonEnabled = (() => { + if (editMode) { + return username.length > 0 && password.length > 0; + } + const baseUrlValid = validUrl(baseUrl); + const baseUrlExists = props.users?.map(user => user.baseUrl).includes(baseUrl); + return baseUrlValid + && !baseUrlExists + && username.length > 0 + && password.length > 0; + })(); + const handleSubmit = async () => { + props.onSubmit({ + baseUrl: baseUrl, + username: username, + password: password + }) + }; + useEffect(() => { + if (editMode) { + setBaseUrl(props.user.baseUrl); + setUsername(props.user.username); + setPassword(props.user.password); + } + }, [editMode, props.user]); + return ( + + {editMode ? t("prefs_users_dialog_title_edit") : t("prefs_users_dialog_title_add")} + + {!editMode && setBaseUrl(ev.target.value)} + type="url" + fullWidth + variant="standard" + />} + setUsername(ev.target.value)} + type="text" + fullWidth + variant="standard" + /> + setPassword(ev.target.value)} + fullWidth + variant="standard" + /> + + + + + + + ); +}; + +const Appearance = () => { + const { t } = useTranslation(); + return ( + + + {t("prefs_appearance_title")} + + + + + + ); +}; + +const Language = () => { + const { t, i18n } = useTranslation(); + const labelId = "prefLanguage"; + const randomFlags = shuffle(["🇬🇧", "🇺🇸", "🇪🇸", "🇫🇷", "🇧🇬", "🇨🇿", "🇩🇪", "🇵🇱", "🇺🇦", "🇨🇳", "🇮🇹", "🇭🇺", "🇧🇷", "🇳🇱", "🇮🇩", "🇯🇵", "🇷🇺", "🇹🇷"]).slice(0, 3); + const title = t("prefs_appearance_language_title") + " " + randomFlags.join(" "); + const lang = i18n.language ?? "en"; + + const handleChange = async (ev) => { + await i18n.changeLanguage(ev.target.value); + await maybeUpdateAccountSettings({ + language: ev.target.value + }); + }; + + // Remember: Flags are not languages. Don't put flags next to the language in the list. + // Languages names from: https://www.omniglot.com/language/names.htm + // Better: Sidebar in Wikipedia: https://en.wikipedia.org/wiki/Bokm%C3%A5l + + return ( + + + + + + ) +}; + +const Reservations = () => { + const { t } = useTranslation(); + const { account } = useContext(AccountContext); + const [dialogKey, setDialogKey] = useState(0); + const [dialogOpen, setDialogOpen] = useState(false); + + if (!config.enable_reservations || !session.exists() || !account) { + return <>; + } + const reservations = account.reservations || []; + const limitReached = account.role === Role.USER && account.stats.reservations_remaining === 0; + + const handleAddClick = () => { + setDialogKey(prev => prev+1); + setDialogOpen(true); + }; + + return ( + + + + {t("prefs_reservations_title")} + + + {t("prefs_reservations_description")} + + {reservations.length > 0 && } + {limitReached && {t("prefs_reservations_limit_reached")}} + + + + setDialogOpen(false)} + /> + + + ); +}; + +const ReservationsTable = (props) => { + const { t } = useTranslation(); + const [dialogKey, setDialogKey] = useState(0); + const [dialogReservation, setDialogReservation] = useState(null); + const [editDialogOpen, setEditDialogOpen] = useState(false); + const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); + const { subscriptions } = useOutletContext(); + const localSubscriptions = (subscriptions?.length > 0) + ? Object.assign(...subscriptions.filter(s => s.baseUrl === config.base_url).map(s => ({[s.topic]: s}))) + : []; + + const handleEditClick = (reservation) => { + setDialogKey(prev => prev+1); + setDialogReservation(reservation); + setEditDialogOpen(true); + }; + + const handleDeleteClick = async (reservation) => { + setDialogKey(prev => prev+1); + setDialogReservation(reservation); + setDeleteDialogOpen(true); + }; + + const handleSubscribeClick = async (reservation) => { + await subscribeTopic(config.base_url, reservation.topic); + }; + + return ( + + + + {t("prefs_reservations_table_topic_header")} + {t("prefs_reservations_table_access_header")} + + + + + {props.reservations.map(reservation => ( + + + {reservation.topic} + + + {reservation.everyone === Permission.READ_WRITE && + <> + + {t("prefs_reservations_table_everyone_read_write")} + + } + {reservation.everyone === Permission.READ_ONLY && + <> + + {t("prefs_reservations_table_everyone_read_only")} + + } + {reservation.everyone === Permission.WRITE_ONLY && + <> + + {t("prefs_reservations_table_everyone_write_only")} + + } + {reservation.everyone === Permission.DENY_ALL && + <> + + {t("prefs_reservations_table_everyone_deny_all")} + + } + + + {!localSubscriptions[reservation.topic] && + + } onClick={() => handleSubscribeClick(reservation)} label={t("prefs_reservations_table_not_subscribed")} color="primary" variant="outlined"/> + + } + handleEditClick(reservation)} aria-label={t("prefs_reservations_edit_button")}> + + + handleDeleteClick(reservation)} aria-label={t("prefs_reservations_delete_button")}> + + + + + ))} + + setEditDialogOpen(false)} + /> + setDeleteDialogOpen(false)} + /> +
+ ); +}; + +const maybeUpdateAccountSettings = async (payload) => { + if (!session.exists()) { + return; + } + try { + await accountApi.updateSettings(payload); + } catch (e) { + console.log(`[Preferences] Error updating account settings`, e); + if (e instanceof UnauthorizedError) { + session.resetAndRedirect(routes.login); + } + } +}; + +export default Preferences; diff --git a/web/src/components/Preferences.jsx b/web/src/components/Preferences.jsx deleted file mode 100644 index 4afc0f8..0000000 --- a/web/src/components/Preferences.jsx +++ /dev/null @@ -1,695 +0,0 @@ -import * as React from "react"; -import { useContext, useEffect, useState } from "react"; -import { - Alert, - CardActions, - CardContent, - Chip, - FormControl, - Select, - Stack, - Table, - TableBody, - TableCell, - TableHead, - TableRow, - Tooltip, - useMediaQuery, - Typography, - IconButton, - Container, - TextField, - MenuItem, - Card, - Button, - Dialog, - DialogTitle, - DialogContent, - DialogActions, -} from "@mui/material"; -import EditIcon from "@mui/icons-material/Edit"; -import CloseIcon from "@mui/icons-material/Close"; -import PlayArrowIcon from "@mui/icons-material/PlayArrow"; -import { useLiveQuery } from "dexie-react-hooks"; -import { useTranslation } from "react-i18next"; -import { Info } from "@mui/icons-material"; -import { useOutletContext } from "react-router-dom"; -import theme from "./theme"; -import userManager from "../app/UserManager"; -import { playSound, shuffle, sounds, validUrl } from "../app/utils"; -import session from "../app/Session"; -import routes from "./routes"; -import accountApi, { Permission, Role } from "../app/AccountApi"; -import { Pref, PrefGroup } from "./Pref"; -import { AccountContext } from "./App"; -import { Paragraph } from "./styles"; -import prefs from "../app/Prefs"; -import { PermissionDenyAll, PermissionRead, PermissionReadWrite, PermissionWrite } from "./ReserveIcons"; -import { ReserveAddDialog, ReserveDeleteDialog, ReserveEditDialog } from "./ReserveDialogs"; -import { UnauthorizedError } from "../app/errors"; -import { subscribeTopic } from "./SubscribeDialog"; - -const maybeUpdateAccountSettings = async (payload) => { - if (!session.exists()) { - return; - } - try { - await accountApi.updateSettings(payload); - } catch (e) { - console.log(`[Preferences] Error updating account settings`, e); - if (e instanceof UnauthorizedError) { - session.resetAndRedirect(routes.login); - } - } -}; - -const Preferences = () => ( - - - - - - - - -); - -const Notifications = () => { - const { t } = useTranslation(); - return ( - - - {t("prefs_notifications_title")} - - - - - - - - ); -}; - -const Sound = () => { - const { t } = useTranslation(); - const labelId = "prefSound"; - const sound = useLiveQuery(async () => prefs.sound()); - const handleChange = async (ev) => { - await prefs.setSound(ev.target.value); - await maybeUpdateAccountSettings({ - notification: { - sound: ev.target.value, - }, - }); - }; - if (!sound) { - return null; // While loading - } - let description; - if (sound === "none") { - description = t("prefs_notifications_sound_description_none"); - } else { - description = t("prefs_notifications_sound_description_some", { - sound: sounds[sound].label, - }); - } - return ( - -
- - - - playSound(sound)} disabled={sound === "none"} aria-label={t("prefs_notifications_sound_play")}> - - -
-
- ); -}; - -const MinPriority = () => { - const { t } = useTranslation(); - const labelId = "prefMinPriority"; - const minPriority = useLiveQuery(async () => prefs.minPriority()); - const handleChange = async (ev) => { - await prefs.setMinPriority(ev.target.value); - await maybeUpdateAccountSettings({ - notification: { - min_priority: ev.target.value, - }, - }); - }; - if (!minPriority) { - return null; // While loading - } - const priorities = { - 1: t("priority_min"), - 2: t("priority_low"), - 3: t("priority_default"), - 4: t("priority_high"), - 5: t("priority_max"), - }; - let description; - if (minPriority === 1) { - description = t("prefs_notifications_min_priority_description_any"); - } else if (minPriority === 5) { - description = t("prefs_notifications_min_priority_description_max"); - } else { - description = t("prefs_notifications_min_priority_description_x_or_higher", { - number: minPriority, - name: priorities[minPriority], - }); - } - return ( - - - - - - ); -}; - -const DeleteAfter = () => { - const { t } = useTranslation(); - const labelId = "prefDeleteAfter"; - const deleteAfter = useLiveQuery(async () => prefs.deleteAfter()); - const handleChange = async (ev) => { - await prefs.setDeleteAfter(ev.target.value); - await maybeUpdateAccountSettings({ - notification: { - delete_after: ev.target.value, - }, - }); - }; - - if (deleteAfter === null || deleteAfter === undefined) { - // !deleteAfter will not work with "0" - return null; // While loading - } - - const description = (() => { - switch (deleteAfter) { - case 0: - return t("prefs_notifications_delete_after_never_description"); - case 10800: - return t("prefs_notifications_delete_after_three_hours_description"); - case 86400: - return t("prefs_notifications_delete_after_one_day_description"); - case 604800: - return t("prefs_notifications_delete_after_one_week_description"); - case 2592000: - return t("prefs_notifications_delete_after_one_month_description"); - default: - return ""; - } - })(); - - return ( - - - - - - ); -}; - -const Users = () => { - const { t } = useTranslation(); - const [dialogKey, setDialogKey] = useState(0); - const [dialogOpen, setDialogOpen] = useState(false); - const users = useLiveQuery(() => userManager.all()); - const handleAddClick = () => { - setDialogKey((prev) => prev + 1); - setDialogOpen(true); - }; - const handleDialogCancel = () => { - setDialogOpen(false); - }; - const handleDialogSubmit = async (user) => { - setDialogOpen(false); - try { - await userManager.save(user); - console.debug(`[Preferences] User ${user.username} for ${user.baseUrl} added`); - } catch (e) { - console.log(`[Preferences] Error adding user.`, e); - } - }; - return ( - - - - {t("prefs_users_title")} - - - {t("prefs_users_description")} - {session.exists() && <>{` ${t("prefs_users_description_no_sync")}`}} - - {users?.length > 0 && } - - - - - - - ); -}; - -const UserTable = (props) => { - const { t } = useTranslation(); - const [dialogKey, setDialogKey] = useState(0); - const [dialogOpen, setDialogOpen] = useState(false); - const [dialogUser, setDialogUser] = useState(null); - - const handleEditClick = (user) => { - setDialogKey((prev) => prev + 1); - setDialogUser(user); - setDialogOpen(true); - }; - - const handleDialogCancel = () => { - setDialogOpen(false); - }; - - const handleDialogSubmit = async (user) => { - setDialogOpen(false); - try { - await userManager.save(user); - console.debug(`[Preferences] User ${user.username} for ${user.baseUrl} updated`); - } catch (e) { - console.log(`[Preferences] Error updating user.`, e); - } - }; - - const handleDeleteClick = async (user) => { - try { - await userManager.delete(user.baseUrl); - console.debug(`[Preferences] User ${user.username} for ${user.baseUrl} deleted`); - } catch (e) { - console.error(`[Preferences] Error deleting user for ${user.baseUrl}`, e); - } - }; - - return ( - - - - {t("prefs_users_table_user_header")} - {t("prefs_users_table_base_url_header")} - - - - - {props.users?.map((user) => ( - - - {user.username} - - {user.baseUrl} - - {(!session.exists() || user.baseUrl !== config.base_url) && ( - <> - handleEditClick(user)} aria-label={t("prefs_users_edit_button")}> - - - handleDeleteClick(user)} aria-label={t("prefs_users_delete_button")}> - - - - )} - {session.exists() && user.baseUrl === config.base_url && ( - - - - - - - - - - - )} - - - ))} - - -
- ); -}; - -const UserDialog = (props) => { - const { t } = useTranslation(); - const [baseUrl, setBaseUrl] = useState(""); - const [username, setUsername] = useState(""); - const [password, setPassword] = useState(""); - const fullScreen = useMediaQuery(theme.breakpoints.down("sm")); - const editMode = props.user !== null; - const addButtonEnabled = (() => { - if (editMode) { - return username.length > 0 && password.length > 0; - } - const baseUrlValid = validUrl(baseUrl); - const baseUrlExists = props.users?.map((user) => user.baseUrl).includes(baseUrl); - return baseUrlValid && !baseUrlExists && username.length > 0 && password.length > 0; - })(); - const handleSubmit = async () => { - props.onSubmit({ - baseUrl, - username, - password, - }); - }; - useEffect(() => { - if (editMode) { - setBaseUrl(props.user.baseUrl); - setUsername(props.user.username); - setPassword(props.user.password); - } - }, [editMode, props.user]); - return ( - - {editMode ? t("prefs_users_dialog_title_edit") : t("prefs_users_dialog_title_add")} - - {!editMode && ( - setBaseUrl(ev.target.value)} - type="url" - fullWidth - variant="standard" - /> - )} - setUsername(ev.target.value)} - type="text" - fullWidth - variant="standard" - /> - setPassword(ev.target.value)} - fullWidth - variant="standard" - /> - - - - - - - ); -}; - -const Appearance = () => { - const { t } = useTranslation(); - return ( - - - {t("prefs_appearance_title")} - - - - - - ); -}; - -const Language = () => { - const { t, i18n } = useTranslation(); - const labelId = "prefLanguage"; - const lang = i18n.resolvedLanguage ?? "en"; - - // Country flags are displayed using emoji. Emoji rendering is handled by platform fonts. - // Windows in particular does not yet play nicely with flag emoji so for now, hide flags on Windows. - const randomFlags = shuffle([ - "🇬🇧", - "🇺🇸", - "🇪🇸", - "🇫🇷", - "🇧🇬", - "🇨🇿", - "🇩🇪", - "🇵🇱", - "🇺🇦", - "🇨🇳", - "🇮🇹", - "🇭🇺", - "🇧🇷", - "🇳🇱", - "🇮🇩", - "🇯🇵", - "🇷🇺", - "🇹🇷", - ]).slice(0, 3); - const showFlags = !navigator.userAgent.includes("Windows"); - let title = t("prefs_appearance_language_title"); - if (showFlags) { - title += ` ${randomFlags.join(" ")}`; - } - - const handleChange = async (ev) => { - await i18n.changeLanguage(ev.target.value); - await maybeUpdateAccountSettings({ - language: ev.target.value, - }); - }; - - // Remember: Flags are not languages. Don't put flags next to the language in the list. - // Languages names from: https://www.omniglot.com/language/names.htm - // Better: Sidebar in Wikipedia: https://en.wikipedia.org/wiki/Bokm%C3%A5l - - return ( - - - - - - ); -}; - -const Reservations = () => { - const { t } = useTranslation(); - const { account } = useContext(AccountContext); - const [dialogKey, setDialogKey] = useState(0); - const [dialogOpen, setDialogOpen] = useState(false); - - if (!config.enable_reservations || !session.exists() || !account) { - return <>; - } - const reservations = account.reservations || []; - const limitReached = account.role === Role.USER && account.stats.reservations_remaining === 0; - - const handleAddClick = () => { - setDialogKey((prev) => prev + 1); - setDialogOpen(true); - }; - - return ( - - - - {t("prefs_reservations_title")} - - {t("prefs_reservations_description")} - {reservations.length > 0 && } - {limitReached && {t("prefs_reservations_limit_reached")}} - - - - setDialogOpen(false)} - /> - - - ); -}; - -const ReservationsTable = (props) => { - const { t } = useTranslation(); - const [dialogKey, setDialogKey] = useState(0); - const [dialogReservation, setDialogReservation] = useState(null); - const [editDialogOpen, setEditDialogOpen] = useState(false); - const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); - const { subscriptions } = useOutletContext(); - const localSubscriptions = - subscriptions?.length > 0 - ? Object.assign({}, ...subscriptions.filter((s) => s.baseUrl === config.base_url).map((s) => ({ [s.topic]: s }))) - : {}; - - const handleEditClick = (reservation) => { - setDialogKey((prev) => prev + 1); - setDialogReservation(reservation); - setEditDialogOpen(true); - }; - - const handleDeleteClick = async (reservation) => { - setDialogKey((prev) => prev + 1); - setDialogReservation(reservation); - setDeleteDialogOpen(true); - }; - - const handleSubscribeClick = async (reservation) => { - await subscribeTopic(config.base_url, reservation.topic); - }; - - return ( - - - - {t("prefs_reservations_table_topic_header")} - {t("prefs_reservations_table_access_header")} - - - - - {props.reservations.map((reservation) => ( - - - {reservation.topic} - - - {reservation.everyone === Permission.READ_WRITE && ( - <> - - {t("prefs_reservations_table_everyone_read_write")} - - )} - {reservation.everyone === Permission.READ_ONLY && ( - <> - - {t("prefs_reservations_table_everyone_read_only")} - - )} - {reservation.everyone === Permission.WRITE_ONLY && ( - <> - - {t("prefs_reservations_table_everyone_write_only")} - - )} - {reservation.everyone === Permission.DENY_ALL && ( - <> - - {t("prefs_reservations_table_everyone_deny_all")} - - )} - - - {!localSubscriptions[reservation.topic] && ( - - } - onClick={() => handleSubscribeClick(reservation)} - label={t("prefs_reservations_table_not_subscribed")} - color="primary" - variant="outlined" - /> - - )} - handleEditClick(reservation)} aria-label={t("prefs_reservations_edit_button")}> - - - handleDeleteClick(reservation)} aria-label={t("prefs_reservations_delete_button")}> - - - - - ))} - - setEditDialogOpen(false)} - /> - setDeleteDialogOpen(false)} - /> -
- ); -}; - -export default Preferences; diff --git a/web/src/components/PublishDialog.js b/web/src/components/PublishDialog.js new file mode 100644 index 0000000..bdf6fb6 --- /dev/null +++ b/web/src/components/PublishDialog.js @@ -0,0 +1,740 @@ +import * as React from 'react'; +import {useEffect, useRef, useState} from 'react'; +import theme from "./theme"; +import {Checkbox, Chip, FormControl, FormControlLabel, InputLabel, Link, Select, useMediaQuery} from "@mui/material"; +import TextField from "@mui/material/TextField"; +import priority1 from "../img/priority-1.svg"; +import priority2 from "../img/priority-2.svg"; +import priority3 from "../img/priority-3.svg"; +import priority4 from "../img/priority-4.svg"; +import priority5 from "../img/priority-5.svg"; +import Dialog from "@mui/material/Dialog"; +import DialogTitle from "@mui/material/DialogTitle"; +import DialogContent from "@mui/material/DialogContent"; +import Button from "@mui/material/Button"; +import Typography from "@mui/material/Typography"; +import IconButton from "@mui/material/IconButton"; +import InsertEmoticonIcon from '@mui/icons-material/InsertEmoticon'; +import {Close} from "@mui/icons-material"; +import MenuItem from "@mui/material/MenuItem"; +import {formatBytes, maybeWithAuth, topicShortUrl, topicUrl, validTopic, validUrl} from "../app/utils"; +import Box from "@mui/material/Box"; +import AttachmentIcon from "./AttachmentIcon"; +import DialogFooter from "./DialogFooter"; +import api from "../app/Api"; +import userManager from "../app/UserManager"; +import EmojiPicker from "./EmojiPicker"; +import {Trans, useTranslation} from "react-i18next"; +import session from "../app/Session"; +import routes from "./routes"; +import accountApi from "../app/AccountApi"; +import {UnauthorizedError} from "../app/errors"; + +const PublishDialog = (props) => { + const { t } = useTranslation(); + const [baseUrl, setBaseUrl] = useState(""); + const [topic, setTopic] = useState(""); + const [message, setMessage] = useState(""); + const [messageFocused, setMessageFocused] = useState(true); + const [title, setTitle] = useState(""); + const [tags, setTags] = useState(""); + const [priority, setPriority] = useState(3); + const [clickUrl, setClickUrl] = useState(""); + const [attachUrl, setAttachUrl] = useState(""); + const [attachFile, setAttachFile] = useState(null); + const [filename, setFilename] = useState(""); + const [filenameEdited, setFilenameEdited] = useState(false); + const [email, setEmail] = useState(""); + const [delay, setDelay] = useState(""); + const [publishAnother, setPublishAnother] = useState(false); + + const [showTopicUrl, setShowTopicUrl] = useState(""); + const [showClickUrl, setShowClickUrl] = useState(false); + const [showAttachUrl, setShowAttachUrl] = useState(false); + const [showEmail, setShowEmail] = useState(false); + const [showDelay, setShowDelay] = useState(false); + + const showAttachFile = !!attachFile && !showAttachUrl; + const attachFileInput = useRef(); + const [attachFileError, setAttachFileError] = useState(""); + + const [activeRequest, setActiveRequest] = useState(null); + const [status, setStatus] = useState(""); + const disabled = !!activeRequest; + + const [emojiPickerAnchorEl, setEmojiPickerAnchorEl] = useState(null); + + const [dropZone, setDropZone] = useState(false); + const [sendButtonEnabled, setSendButtonEnabled] = useState(true); + + const open = !!props.openMode; + const fullScreen = useMediaQuery(theme.breakpoints.down('sm')); + + useEffect(() => { + window.addEventListener('dragenter', () => { + props.onDragEnter(); + setDropZone(true); + }); + }, []); + + useEffect(() => { + setBaseUrl(props.baseUrl); + setTopic(props.topic); + setShowTopicUrl(!props.baseUrl || !props.topic); + setMessageFocused(!!props.topic); // Focus message only if topic is set + }, [props.baseUrl, props.topic]); + + useEffect(() => { + const valid = validUrl(baseUrl) && validTopic(topic) && !attachFileError; + setSendButtonEnabled(valid); + }, [baseUrl, topic, attachFileError]); + + useEffect(() => { + setMessage(props.message); + }, [props.message]); + + const updateBaseUrl = (newVal) => { + if (validUrl(newVal)) { + setBaseUrl(newVal.replace(/\/$/, '')); // strip traililng slash after https?:// + } else { + setBaseUrl(newVal); + } + }; + + const handleSubmit = async () => { + const url = new URL(topicUrl(baseUrl, topic)); + if (title.trim()) { + url.searchParams.append("title", title.trim()); + } + if (tags.trim()) { + url.searchParams.append("tags", tags.trim()); + } + if (priority && priority !== 3) { + url.searchParams.append("priority", priority.toString()); + } + if (clickUrl.trim()) { + url.searchParams.append("click", clickUrl.trim()); + } + if (attachUrl.trim()) { + url.searchParams.append("attach", attachUrl.trim()); + } + if (filename.trim()) { + url.searchParams.append("filename", filename.trim()); + } + if (email.trim()) { + url.searchParams.append("email", email.trim()); + } + if (delay.trim()) { + url.searchParams.append("delay", delay.trim()); + } + if (attachFile && message.trim()) { + url.searchParams.append("message", message.replaceAll("\n", "\\n").trim()); + } + const body = (attachFile) ? attachFile : message; + try { + const user = await userManager.get(baseUrl); + const headers = maybeWithAuth({}, user); + const progressFn = (ev) => { + if (ev.loaded > 0 && ev.total > 0) { + setStatus(t("publish_dialog_progress_uploading_detail", { + loaded: formatBytes(ev.loaded), + total: formatBytes(ev.total), + percent: Math.round(ev.loaded * 100.0 / ev.total) + })); + } else { + setStatus(t("publish_dialog_progress_uploading")); + } + }; + const request = api.publishXHR(url, body, headers, progressFn); + setActiveRequest(request); + await request; + if (!publishAnother) { + props.onClose(); + } else { + setStatus(t("publish_dialog_message_published")); + setActiveRequest(null); + } + } catch (e) { + setStatus({e}); + setActiveRequest(null); + } + }; + + const checkAttachmentLimits = async (file) => { + try { + const account = await accountApi.get(); + const fileSizeLimit = account.limits.attachment_file_size ?? 0; + const remainingBytes = account.stats.attachment_total_size_remaining; + const fileSizeLimitReached = fileSizeLimit > 0 && file.size > fileSizeLimit; + const quotaReached = remainingBytes > 0 && file.size > remainingBytes; + if (fileSizeLimitReached && quotaReached) { + return setAttachFileError(t("publish_dialog_attachment_limits_file_and_quota_reached", { + fileSizeLimit: formatBytes(fileSizeLimit), + remainingBytes: formatBytes(remainingBytes) + })); + } else if (fileSizeLimitReached) { + return setAttachFileError(t("publish_dialog_attachment_limits_file_reached", { fileSizeLimit: formatBytes(fileSizeLimit) })); + } else if (quotaReached) { + return setAttachFileError(t("publish_dialog_attachment_limits_quota_reached", { remainingBytes: formatBytes(remainingBytes) })); + } + setAttachFileError(""); + } catch (e) { + console.log(`[PublishDialog] Retrieving attachment limits failed`, e); + if (e instanceof UnauthorizedError) { + session.resetAndRedirect(routes.login); + } else { + setAttachFileError(""); // Reset error (rely on server-side checking) + } + } + }; + + const handleAttachFileClick = () => { + attachFileInput.current.click(); + }; + + const handleAttachFileChanged = async (ev) => { + await updateAttachFile(ev.target.files[0]); + }; + + const handleAttachFileDrop = async (ev) => { + ev.preventDefault(); + setDropZone(false); + await updateAttachFile(ev.dataTransfer.files[0]); + }; + + const updateAttachFile = async (file) => { + setAttachFile(file); + setFilename(file.name); + props.onResetOpenMode(); + await checkAttachmentLimits(file); + }; + + const handleAttachFileDragLeave = () => { + setDropZone(false); + if (props.openMode === PublishDialog.OPEN_MODE_DRAG) { + props.onClose(); // Only close dialog if it was not open before dragging file in + } + }; + + const handleEmojiClick = (ev) => { + setEmojiPickerAnchorEl(ev.currentTarget); + }; + + const handleEmojiPick = (emoji) => { + setTags(tags => (tags.trim()) ? `${tags.trim()}, ${emoji}` : emoji); + }; + + const handleEmojiClose = () => { + setEmojiPickerAnchorEl(null); + }; + + const priorities = { + 1: { label: t("publish_dialog_priority_min"), file: priority1 }, + 2: { label: t("publish_dialog_priority_low"), file: priority2 }, + 3: { label: t("publish_dialog_priority_default"), file: priority3 }, + 4: { label: t("publish_dialog_priority_high"), file: priority4 }, + 5: { label: t("publish_dialog_priority_max"), file: priority5 } + }; + + return ( + <> + {dropZone && + } + + {(baseUrl && topic) ? t("publish_dialog_title_topic", { topic: topicShortUrl(baseUrl, topic) }) : t("publish_dialog_title_no_topic")} + + {dropZone && } + {showTopicUrl && + { + setBaseUrl(props.baseUrl); + setTopic(props.topic); + setShowTopicUrl(false); + }}> + updateBaseUrl(ev.target.value)} + disabled={disabled} + type="url" + variant="standard" + sx={{flexGrow: 1, marginRight: 1}} + inputProps={{ + "aria-label": t("publish_dialog_base_url_label") + }} + /> + setTopic(ev.target.value)} + disabled={disabled} + type="text" + variant="standard" + autoFocus={!messageFocused} + sx={{flexGrow: 1}} + inputProps={{ + "aria-label": t("publish_dialog_topic_label") + }} + /> + + } + setTitle(ev.target.value)} + disabled={disabled} + type="text" + fullWidth + variant="standard" + inputProps={{ + "aria-label": t("publish_dialog_title_label") + }} + /> + setMessage(ev.target.value)} + disabled={disabled} + type="text" + variant="standard" + rows={5} + autoFocus={messageFocused} + fullWidth + multiline + inputProps={{ + "aria-label": t("publish_dialog_message_label") + }} + /> +
+ + + + + setTags(ev.target.value)} + disabled={disabled} + type="text" + variant="standard" + sx={{flexGrow: 1, marginRight: 1}} + inputProps={{ + "aria-label": t("publish_dialog_tags_label") + }} + /> + + + + +
+ {showClickUrl && + { + setClickUrl(""); + setShowClickUrl(false); + }}> + setClickUrl(ev.target.value)} + disabled={disabled} + type="url" + fullWidth + variant="standard" + inputProps={{ + "aria-label": t("publish_dialog_click_label") + }} + /> + + } + {showEmail && + { + setEmail(""); + setShowEmail(false); + }}> + setEmail(ev.target.value)} + disabled={disabled} + type="email" + variant="standard" + fullWidth + inputProps={{ + "aria-label": t("publish_dialog_email_label") + }} + /> + + } + {showAttachUrl && + { + setAttachUrl(""); + setFilename(""); + setFilenameEdited(false); + setShowAttachUrl(false); + }}> + { + const url = ev.target.value; + setAttachUrl(url); + if (!filenameEdited) { + try { + const u = new URL(url); + const parts = u.pathname.split("/"); + if (parts.length > 0) { + setFilename(parts[parts.length-1]); + } + } catch (e) { + // Do nothing + } + } + }} + disabled={disabled} + type="url" + variant="standard" + sx={{flexGrow: 5, marginRight: 1}} + inputProps={{ + "aria-label": t("publish_dialog_attach_label") + }} + /> + { + setFilename(ev.target.value); + setFilenameEdited(true); + }} + disabled={disabled} + type="text" + variant="standard" + sx={{flexGrow: 1}} + inputProps={{ + "aria-label": t("publish_dialog_filename_label") + }} + /> + + } + + {showAttachFile && setFilename(f)} + onClose={() => { + setAttachFile(null); + setAttachFileError(""); + setFilename(""); + }} + />} + {showDelay && + { + setDelay(""); + setShowDelay(false); + }}> + setDelay(ev.target.value)} + disabled={disabled} + type="text" + variant="standard" + fullWidth + inputProps={{ + "aria-label": t("publish_dialog_delay_label") + }} + /> + + } + + {t("publish_dialog_other_features")} + +
+ {!showClickUrl && setShowClickUrl(true)} sx={{marginRight: 1, marginBottom: 1}}/>} + {!showEmail && setShowEmail(true)} sx={{marginRight: 1, marginBottom: 1}}/>} + {!showAttachUrl && !showAttachFile && setShowAttachUrl(true)} sx={{marginRight: 1, marginBottom: 1}}/>} + {!showAttachFile && !showAttachUrl && handleAttachFileClick()} sx={{marginRight: 1, marginBottom: 1}}/>} + {!showDelay && setShowDelay(true)} sx={{marginRight: 1, marginBottom: 1}}/>} + {!showTopicUrl && setShowTopicUrl(true)} sx={{marginRight: 1, marginBottom: 1}}/>} +
+ + + }} + /> + +
+ + {activeRequest && } + {!activeRequest && + <> + setPublishAnother(ev.target.checked)} + inputProps={{ + "aria-label": t("publish_dialog_checkbox_publish_another") + }} /> + } /> + + + + } + +
+ + ); +}; + +const Row = (props) => { + return ( +
+ {props.children} +
+ ); +}; + +const ClosableRow = (props) => { + const closable = (props.hasOwnProperty("closable")) ? props.closable : true; + return ( + + {props.children} + {closable && + + + + } + + ); +}; + +const DialogIconButton = (props) => { + const sx = props.sx || {}; + return ( + + {props.children} + + ); +}; + +const AttachmentBox = (props) => { + const { t } = useTranslation(); + const file = props.file; + return ( + <> + + {t("publish_dialog_attached_file_title")} + + + + + props.onChangeFilename(ev.target.value)} + disabled={props.disabled} + /> +
+ + {formatBytes(file.size)} + {props.error && + + {" "}({props.error}) + + } + +
+ + + +
+ + ); +}; + +const ExpandingTextField = (props) => { + const invisibleFieldRef = useRef(); + const [textWidth, setTextWidth] = useState(props.minWidth); + const determineTextWidth = () => { + const boundingRect = invisibleFieldRef?.current?.getBoundingClientRect(); + if (!boundingRect) { + return props.minWidth; + } + return (boundingRect.width >= props.minWidth) ? Math.round(boundingRect.width) : props.minWidth; + }; + useEffect(() => { + setTextWidth(determineTextWidth() + 5); + }, [props.value]); + return ( + <> + + {props.value} + + + + ) +}; + +const DropArea = (props) => { + const allowDrag = (ev) => { + // This is where we could disallow certain files to be dragged in. + // For now we allow all files. + + ev.dataTransfer.dropEffect = 'copy'; + ev.preventDefault(); + }; + + return ( + + ); +}; + +const DropBox = () => { + const { t } = useTranslation(); + return ( + + + {t("publish_dialog_drop_file_here")} + + + ); +} + +PublishDialog.OPEN_MODE_DEFAULT = "default"; +PublishDialog.OPEN_MODE_DRAG = "drag"; + +export default PublishDialog; diff --git a/web/src/components/PublishDialog.jsx b/web/src/components/PublishDialog.jsx deleted file mode 100644 index eb0af0d..0000000 --- a/web/src/components/PublishDialog.jsx +++ /dev/null @@ -1,913 +0,0 @@ -import * as React from "react"; -import { useContext, useEffect, useRef, useState } from "react"; -import { - Checkbox, - Chip, - FormControl, - FormControlLabel, - InputLabel, - Link, - Select, - Tooltip, - useMediaQuery, - TextField, - Dialog, - DialogTitle, - DialogContent, - Button, - Typography, - IconButton, - MenuItem, - Box, -} from "@mui/material"; -import InsertEmoticonIcon from "@mui/icons-material/InsertEmoticon"; -import { Close } from "@mui/icons-material"; -import { Trans, useTranslation } from "react-i18next"; -import priority1 from "../img/priority-1.svg"; -import priority2 from "../img/priority-2.svg"; -import priority3 from "../img/priority-3.svg"; -import priority4 from "../img/priority-4.svg"; -import priority5 from "../img/priority-5.svg"; -import { formatBytes, maybeWithAuth, topicShortUrl, topicUrl, validTopic, validUrl } from "../app/utils"; -import AttachmentIcon from "./AttachmentIcon"; -import DialogFooter from "./DialogFooter"; -import api from "../app/Api"; -import userManager from "../app/UserManager"; -import EmojiPicker from "./EmojiPicker"; -import theme from "./theme"; -import session from "../app/Session"; -import routes from "./routes"; -import accountApi from "../app/AccountApi"; -import { UnauthorizedError } from "../app/errors"; -import { AccountContext } from "./App"; - -const PublishDialog = (props) => { - const { t } = useTranslation(); - const { account } = useContext(AccountContext); - const [baseUrl, setBaseUrl] = useState(""); - const [topic, setTopic] = useState(""); - const [message, setMessage] = useState(""); - const [messageFocused, setMessageFocused] = useState(true); - const [title, setTitle] = useState(""); - const [tags, setTags] = useState(""); - const [priority, setPriority] = useState(3); - const [clickUrl, setClickUrl] = useState(""); - const [attachUrl, setAttachUrl] = useState(""); - const [attachFile, setAttachFile] = useState(null); - const [filename, setFilename] = useState(""); - const [filenameEdited, setFilenameEdited] = useState(false); - const [email, setEmail] = useState(""); - const [call, setCall] = useState(""); - const [delay, setDelay] = useState(""); - const [publishAnother, setPublishAnother] = useState(false); - - const [showTopicUrl, setShowTopicUrl] = useState(""); - const [showClickUrl, setShowClickUrl] = useState(false); - const [showAttachUrl, setShowAttachUrl] = useState(false); - const [showEmail, setShowEmail] = useState(false); - const [showCall, setShowCall] = useState(false); - const [showDelay, setShowDelay] = useState(false); - - const showAttachFile = !!attachFile && !showAttachUrl; - const attachFileInput = useRef(); - const [attachFileError, setAttachFileError] = useState(""); - - const [activeRequest, setActiveRequest] = useState(null); - const [status, setStatus] = useState(""); - const disabled = !!activeRequest; - - const [emojiPickerAnchorEl, setEmojiPickerAnchorEl] = useState(null); - - const [dropZone, setDropZone] = useState(false); - const [sendButtonEnabled, setSendButtonEnabled] = useState(true); - - const open = !!props.openMode; - const fullScreen = useMediaQuery(theme.breakpoints.down("sm")); - - useEffect(() => { - window.addEventListener("dragenter", () => { - props.onDragEnter(); - setDropZone(true); - }); - }, []); - - useEffect(() => { - setBaseUrl(props.baseUrl); - setTopic(props.topic); - setShowTopicUrl(!props.baseUrl || !props.topic); - setMessageFocused(!!props.topic); // Focus message only if topic is set - }, [props.baseUrl, props.topic]); - - useEffect(() => { - const valid = validUrl(baseUrl) && validTopic(topic) && !attachFileError; - setSendButtonEnabled(valid); - }, [baseUrl, topic, attachFileError]); - - useEffect(() => { - setMessage(props.message); - }, [props.message]); - - const updateBaseUrl = (newVal) => { - if (validUrl(newVal)) { - setBaseUrl(newVal.replace(/\/$/, "")); // strip traililng slash after https?:// - } else { - setBaseUrl(newVal); - } - }; - - const handleSubmit = async () => { - const url = new URL(topicUrl(baseUrl, topic)); - if (title.trim()) { - url.searchParams.append("title", title.trim()); - } - if (tags.trim()) { - url.searchParams.append("tags", tags.trim()); - } - if (priority && priority !== 3) { - url.searchParams.append("priority", priority.toString()); - } - if (clickUrl.trim()) { - url.searchParams.append("click", clickUrl.trim()); - } - if (attachUrl.trim()) { - url.searchParams.append("attach", attachUrl.trim()); - } - if (filename.trim()) { - url.searchParams.append("filename", filename.trim()); - } - if (email.trim()) { - url.searchParams.append("email", email.trim()); - } - if (call.trim()) { - url.searchParams.append("call", call.trim()); - } - if (delay.trim()) { - url.searchParams.append("delay", delay.trim()); - } - if (attachFile && message.trim()) { - url.searchParams.append("message", message.replaceAll("\n", "\\n").trim()); - } - const body = attachFile || message; - try { - const user = await userManager.get(baseUrl); - const headers = maybeWithAuth({}, user); - const progressFn = (ev) => { - if (ev.loaded > 0 && ev.total > 0) { - setStatus( - t("publish_dialog_progress_uploading_detail", { - loaded: formatBytes(ev.loaded), - total: formatBytes(ev.total), - percent: Math.round((ev.loaded * 100.0) / ev.total), - }) - ); - } else { - setStatus(t("publish_dialog_progress_uploading")); - } - }; - const request = api.publishXHR(url, body, headers, progressFn); - setActiveRequest(request); - await request; - if (!publishAnother) { - props.onClose(); - } else { - setStatus(t("publish_dialog_message_published")); - setActiveRequest(null); - } - } catch (e) { - setStatus({e}); - setActiveRequest(null); - } - }; - - const checkAttachmentLimits = async (file) => { - try { - const apiAccount = await accountApi.get(); - const fileSizeLimit = apiAccount.limits.attachment_file_size ?? 0; - const remainingBytes = apiAccount.stats.attachment_total_size_remaining; - const fileSizeLimitReached = fileSizeLimit > 0 && file.size > fileSizeLimit; - const quotaReached = remainingBytes > 0 && file.size > remainingBytes; - if (fileSizeLimitReached && quotaReached) { - setAttachFileError( - t("publish_dialog_attachment_limits_file_and_quota_reached", { - fileSizeLimit: formatBytes(fileSizeLimit), - remainingBytes: formatBytes(remainingBytes), - }) - ); - } else if (fileSizeLimitReached) { - setAttachFileError( - t("publish_dialog_attachment_limits_file_reached", { - fileSizeLimit: formatBytes(fileSizeLimit), - }) - ); - } else if (quotaReached) { - setAttachFileError( - t("publish_dialog_attachment_limits_quota_reached", { - remainingBytes: formatBytes(remainingBytes), - }) - ); - } else { - setAttachFileError(""); - } - } catch (e) { - console.log(`[PublishDialog] Retrieving attachment limits failed`, e); - if (e instanceof UnauthorizedError) { - session.resetAndRedirect(routes.login); - } else { - setAttachFileError(""); // Reset error (rely on server-side checking) - } - } - }; - - const handleAttachFileClick = () => { - attachFileInput.current.click(); - }; - - const updateAttachFile = async (file) => { - setAttachFile(file); - setFilename(file.name); - props.onResetOpenMode(); - await checkAttachmentLimits(file); - }; - - const handleAttachFileChanged = async (ev) => { - await updateAttachFile(ev.target.files[0]); - }; - - const handleAttachFileDrop = async (ev) => { - ev.preventDefault(); - setDropZone(false); - await updateAttachFile(ev.dataTransfer.files[0]); - }; - - const handleAttachFileDragLeave = () => { - setDropZone(false); - if (props.openMode === PublishDialog.OPEN_MODE_DRAG) { - props.onClose(); // Only close dialog if it was not open before dragging file in - } - }; - - const handleEmojiClick = (ev) => { - setEmojiPickerAnchorEl(ev.currentTarget); - }; - - const handleEmojiPick = (emoji) => { - setTags((prevTags) => (prevTags.trim() ? `${prevTags.trim()}, ${emoji}` : emoji)); - }; - - const handleEmojiClose = () => { - setEmojiPickerAnchorEl(null); - }; - - const priorities = { - 1: { label: t("publish_dialog_priority_min"), file: priority1 }, - 2: { label: t("publish_dialog_priority_low"), file: priority2 }, - 3: { label: t("publish_dialog_priority_default"), file: priority3 }, - 4: { label: t("publish_dialog_priority_high"), file: priority4 }, - 5: { label: t("publish_dialog_priority_max"), file: priority5 }, - }; - - return ( - <> - {dropZone && } - - - {baseUrl && topic - ? t("publish_dialog_title_topic", { - topic: topicShortUrl(baseUrl, topic), - }) - : t("publish_dialog_title_no_topic")} - - - {dropZone && } - {showTopicUrl && ( - { - setBaseUrl(props.baseUrl); - setTopic(props.topic); - setShowTopicUrl(false); - }} - > - updateBaseUrl(ev.target.value)} - disabled={disabled} - type="url" - variant="standard" - sx={{ flexGrow: 1, marginRight: 1 }} - inputProps={{ - "aria-label": t("publish_dialog_base_url_label"), - }} - /> - setTopic(ev.target.value)} - disabled={disabled} - type="text" - variant="standard" - autoFocus={!messageFocused} - sx={{ flexGrow: 1 }} - inputProps={{ - "aria-label": t("publish_dialog_topic_label"), - }} - /> - - )} - setTitle(ev.target.value)} - disabled={disabled} - type="text" - fullWidth - variant="standard" - inputProps={{ - "aria-label": t("publish_dialog_title_label"), - }} - /> - setMessage(ev.target.value)} - disabled={disabled} - type="text" - variant="standard" - rows={5} - autoFocus={messageFocused} - fullWidth - multiline - inputProps={{ - "aria-label": t("publish_dialog_message_label"), - }} - /> -
- - - - - setTags(ev.target.value)} - disabled={disabled} - type="text" - variant="standard" - sx={{ flexGrow: 1, marginRight: 1 }} - inputProps={{ - "aria-label": t("publish_dialog_tags_label"), - }} - /> - - - - -
- {showClickUrl && ( - { - setClickUrl(""); - setShowClickUrl(false); - }} - > - setClickUrl(ev.target.value)} - disabled={disabled} - type="url" - fullWidth - variant="standard" - inputProps={{ - "aria-label": t("publish_dialog_click_label"), - }} - /> - - )} - {showEmail && ( - { - setEmail(""); - setShowEmail(false); - }} - > - setEmail(ev.target.value)} - disabled={disabled} - type="email" - variant="standard" - fullWidth - inputProps={{ - "aria-label": t("publish_dialog_email_label"), - }} - /> - - )} - {showCall && ( - { - setCall(""); - setShowCall(false); - }} - > - - - - - - )} - {showAttachUrl && ( - { - setAttachUrl(""); - setFilename(""); - setFilenameEdited(false); - setShowAttachUrl(false); - }} - > - { - const url = ev.target.value; - setAttachUrl(url); - if (!filenameEdited) { - try { - const u = new URL(url); - const parts = u.pathname.split("/"); - if (parts.length > 0) { - setFilename(parts[parts.length - 1]); - } - } catch (e) { - // Do nothing - } - } - }} - disabled={disabled} - type="url" - variant="standard" - sx={{ flexGrow: 5, marginRight: 1 }} - inputProps={{ - "aria-label": t("publish_dialog_attach_label"), - }} - /> - { - setFilename(ev.target.value); - setFilenameEdited(true); - }} - disabled={disabled} - type="text" - variant="standard" - sx={{ flexGrow: 1 }} - inputProps={{ - "aria-label": t("publish_dialog_filename_label"), - }} - /> - - )} - - {showAttachFile && ( - setFilename(f)} - onClose={() => { - setAttachFile(null); - setAttachFileError(""); - setFilename(""); - }} - /> - )} - {showDelay && ( - { - setDelay(""); - setShowDelay(false); - }} - > - setDelay(ev.target.value)} - disabled={disabled} - type="text" - variant="standard" - fullWidth - inputProps={{ - "aria-label": t("publish_dialog_delay_label"), - }} - /> - - )} - - {t("publish_dialog_other_features")} - -
- {!showClickUrl && ( - setShowClickUrl(true)} - sx={{ marginRight: 1, marginBottom: 1 }} - /> - )} - {!showEmail && ( - setShowEmail(true)} - sx={{ marginRight: 1, marginBottom: 1 }} - /> - )} - {account?.phone_numbers?.length > 0 && !showCall && ( - { - setShowCall(true); - setCall(account.phone_numbers[0]); - }} - sx={{ marginRight: 1, marginBottom: 1 }} - /> - )} - {!showAttachUrl && !showAttachFile && ( - setShowAttachUrl(true)} - sx={{ marginRight: 1, marginBottom: 1 }} - /> - )} - {!showAttachFile && !showAttachUrl && ( - handleAttachFileClick()} - sx={{ marginRight: 1, marginBottom: 1 }} - /> - )} - {!showDelay && ( - setShowDelay(true)} - sx={{ marginRight: 1, marginBottom: 1 }} - /> - )} - {!showTopicUrl && ( - setShowTopicUrl(true)} - sx={{ marginRight: 1, marginBottom: 1 }} - /> - )} - {account && !account?.phone_numbers && ( - - - - - - )} -
- - , - }} - /> - -
- - {activeRequest && } - {!activeRequest && ( - <> - setPublishAnother(ev.target.checked)} - inputProps={{ - "aria-label": t("publish_dialog_checkbox_publish_another"), - }} - /> - } - /> - - - - )} - -
- - ); -}; - -const Row = (props) => ( -
- {props.children} -
-); - -const ClosableRow = (props) => { - const closable = props.closable !== undefined ? props.closable : true; - return ( - - {props.children} - {closable && ( - - - - )} - - ); -}; - -const DialogIconButton = (props) => { - const sx = props.sx || {}; - return ( - - {props.children} - - ); -}; - -const AttachmentBox = (props) => { - const { t } = useTranslation(); - const { file } = props; - return ( - <> - - {t("publish_dialog_attached_file_title")} - - - - - props.onChangeFilename(ev.target.value)} - disabled={props.disabled} - /> -
- - {formatBytes(file.size)} - {props.error && ( - - {" "} - ({props.error}) - - )} - -
- - - -
- - ); -}; - -const ExpandingTextField = (props) => { - const invisibleFieldRef = useRef(); - const [textWidth, setTextWidth] = useState(props.minWidth); - const determineTextWidth = () => { - const boundingRect = invisibleFieldRef?.current?.getBoundingClientRect(); - if (!boundingRect) { - return props.minWidth; - } - return boundingRect.width >= props.minWidth ? Math.round(boundingRect.width) : props.minWidth; - }; - useEffect(() => { - setTextWidth(determineTextWidth() + 5); - }, [props.value]); - return ( - <> - - {props.value} - - - - ); -}; - -const DropArea = (props) => { - const allowDrag = (ev) => { - // This is where we could disallow certain files to be dragged in. - // For now we allow all files. - - // eslint-disable-next-line no-param-reassign - ev.dataTransfer.dropEffect = "copy"; - ev.preventDefault(); - }; - - return ( - - ); -}; - -const DropBox = () => { - const { t } = useTranslation(); - return ( - - - {t("publish_dialog_drop_file_here")} - - - ); -}; - -PublishDialog.OPEN_MODE_DEFAULT = "default"; -PublishDialog.OPEN_MODE_DRAG = "drag"; - -export default PublishDialog; diff --git a/web/src/components/ReserveDialogs.js b/web/src/components/ReserveDialogs.js new file mode 100644 index 0000000..7a6a044 --- /dev/null +++ b/web/src/components/ReserveDialogs.js @@ -0,0 +1,199 @@ +import * as React from 'react'; +import {useState} from 'react'; +import Button from '@mui/material/Button'; +import TextField from '@mui/material/TextField'; +import Dialog from '@mui/material/Dialog'; +import DialogContent from '@mui/material/DialogContent'; +import DialogContentText from '@mui/material/DialogContentText'; +import DialogTitle from '@mui/material/DialogTitle'; +import {Alert, FormControl, Select, useMediaQuery} from "@mui/material"; +import theme from "./theme"; +import {validTopic} from "../app/utils"; +import DialogFooter from "./DialogFooter"; +import {useTranslation} from "react-i18next"; +import session from "../app/Session"; +import routes from "./routes"; +import accountApi, {Permission} from "../app/AccountApi"; +import ReserveTopicSelect from "./ReserveTopicSelect"; +import MenuItem from "@mui/material/MenuItem"; +import ListItemIcon from "@mui/material/ListItemIcon"; +import ListItemText from "@mui/material/ListItemText"; +import {Check, DeleteForever} from "@mui/icons-material"; +import {TopicReservedError, UnauthorizedError} from "../app/errors"; + +export const ReserveAddDialog = (props) => { + const { t } = useTranslation(); + const [error, setError] = useState(""); + const [topic, setTopic] = useState(props.topic || ""); + const [everyone, setEveryone] = useState(Permission.DENY_ALL); + const fullScreen = useMediaQuery(theme.breakpoints.down('sm')); + const allowTopicEdit = !props.topic; + const alreadyReserved = props.reservations.filter(r => r.topic === topic).length > 0; + const submitButtonEnabled = validTopic(topic) && !alreadyReserved; + + const handleSubmit = async () => { + try { + await accountApi.upsertReservation(topic, everyone); + console.debug(`[ReserveAddDialog] Added reservation for topic ${t}: ${everyone}`); + } catch (e) { + console.log(`[ReserveAddDialog] Error adding topic reservation.`, e); + if (e instanceof UnauthorizedError) { + session.resetAndRedirect(routes.login); + } else if (e instanceof TopicReservedError) { + setError(t("subscribe_dialog_error_topic_already_reserved")); + return; + } else { + setError(e.message); + return; + } + } + props.onClose(); + }; + + return ( + + {t("prefs_reservations_dialog_title_add")} + + + {t("prefs_reservations_dialog_description")} + + {allowTopicEdit && setTopic(ev.target.value)} + type="url" + fullWidth + variant="standard" + />} + + + + + + + + ); +}; + +export const ReserveEditDialog = (props) => { + const { t } = useTranslation(); + const [error, setError] = useState(""); + const [everyone, setEveryone] = useState(props.reservation?.everyone || Permission.DENY_ALL); + const fullScreen = useMediaQuery(theme.breakpoints.down('sm')); + + const handleSubmit = async () => { + try { + await accountApi.upsertReservation(props.reservation.topic, everyone); + console.debug(`[ReserveEditDialog] Updated reservation for topic ${t}: ${everyone}`); + } catch (e) { + console.log(`[ReserveEditDialog] Error updating topic reservation.`, e); + if (e instanceof UnauthorizedError) { + session.resetAndRedirect(routes.login); + } else { + setError(e.message); + return; + } + } + props.onClose(); + }; + + return ( + + {t("prefs_reservations_dialog_title_edit")} + + + {t("prefs_reservations_dialog_description")} + + + + + + + + + ); +}; + +export const ReserveDeleteDialog = (props) => { + const { t } = useTranslation(); + const [error, setError] = useState(""); + const [deleteMessages, setDeleteMessages] = useState(false); + const fullScreen = useMediaQuery(theme.breakpoints.down('sm')); + + const handleSubmit = async () => { + try { + await accountApi.deleteReservation(props.topic, deleteMessages); + console.debug(`[ReserveDeleteDialog] Deleted reservation for topic ${props.topic}`); + } catch (e) { + console.log(`[ReserveDeleteDialog] Error deleting topic reservation.`, e); + if (e instanceof UnauthorizedError) { + session.resetAndRedirect(routes.login); + } else { + setError(e.message); + return; + } + } + props.onClose(); + }; + + return ( + + {t("prefs_reservations_dialog_title_delete")} + + + {t("reservation_delete_dialog_description")} + + + + + {!deleteMessages && + + {t("reservation_delete_dialog_action_keep_description")} + + } + {deleteMessages && + + {t("reservation_delete_dialog_action_delete_description")} + + } + + + + + + + ); +}; + diff --git a/web/src/components/ReserveDialogs.jsx b/web/src/components/ReserveDialogs.jsx deleted file mode 100644 index 3dc370e..0000000 --- a/web/src/components/ReserveDialogs.jsx +++ /dev/null @@ -1,199 +0,0 @@ -import * as React from "react"; -import { useState } from "react"; -import { - Button, - TextField, - Dialog, - DialogContent, - DialogContentText, - DialogTitle, - Alert, - FormControl, - Select, - useMediaQuery, - MenuItem, - ListItemIcon, - ListItemText, -} from "@mui/material"; -import { useTranslation } from "react-i18next"; -import { Check, DeleteForever } from "@mui/icons-material"; -import theme from "./theme"; -import { validTopic } from "../app/utils"; -import DialogFooter from "./DialogFooter"; -import session from "../app/Session"; -import routes from "./routes"; -import accountApi, { Permission } from "../app/AccountApi"; -import ReserveTopicSelect from "./ReserveTopicSelect"; -import { TopicReservedError, UnauthorizedError } from "../app/errors"; - -export const ReserveAddDialog = (props) => { - const { t } = useTranslation(); - const [error, setError] = useState(""); - const [topic, setTopic] = useState(props.topic || ""); - const [everyone, setEveryone] = useState(Permission.DENY_ALL); - const fullScreen = useMediaQuery(theme.breakpoints.down("sm")); - const allowTopicEdit = !props.topic; - const alreadyReserved = props.reservations.filter((r) => r.topic === topic).length > 0; - const submitButtonEnabled = validTopic(topic) && !alreadyReserved; - - const handleSubmit = async () => { - try { - await accountApi.upsertReservation(topic, everyone); - console.debug(`[ReserveAddDialog] Added reservation for topic ${topic}: ${everyone}`); - } catch (e) { - console.log(`[ReserveAddDialog] Error adding topic reservation.`, e); - if (e instanceof UnauthorizedError) { - session.resetAndRedirect(routes.login); - } else if (e instanceof TopicReservedError) { - setError(t("subscribe_dialog_error_topic_already_reserved")); - return; - } else { - setError(e.message); - return; - } - } - props.onClose(); - }; - - return ( - - {t("prefs_reservations_dialog_title_add")} - - {t("prefs_reservations_dialog_description")} - {allowTopicEdit && ( - setTopic(ev.target.value)} - type="url" - fullWidth - variant="standard" - /> - )} - - - - - - - - ); -}; - -export const ReserveEditDialog = (props) => { - const { t } = useTranslation(); - const [error, setError] = useState(""); - const [everyone, setEveryone] = useState(props.reservation?.everyone || Permission.DENY_ALL); - const fullScreen = useMediaQuery(theme.breakpoints.down("sm")); - - const handleSubmit = async () => { - try { - await accountApi.upsertReservation(props.reservation.topic, everyone); - console.debug(`[ReserveEditDialog] Updated reservation for topic ${t}: ${everyone}`); - } catch (e) { - console.log(`[ReserveEditDialog] Error updating topic reservation.`, e); - if (e instanceof UnauthorizedError) { - session.resetAndRedirect(routes.login); - } else { - setError(e.message); - return; - } - } - props.onClose(); - }; - - return ( - - {t("prefs_reservations_dialog_title_edit")} - - {t("prefs_reservations_dialog_description")} - - - - - - - - ); -}; - -export const ReserveDeleteDialog = (props) => { - const { t } = useTranslation(); - const [error, setError] = useState(""); - const [deleteMessages, setDeleteMessages] = useState(false); - const fullScreen = useMediaQuery(theme.breakpoints.down("sm")); - - const handleSubmit = async () => { - try { - await accountApi.deleteReservation(props.topic, deleteMessages); - console.debug(`[ReserveDeleteDialog] Deleted reservation for topic ${props.topic}`); - } catch (e) { - console.log(`[ReserveDeleteDialog] Error deleting topic reservation.`, e); - if (e instanceof UnauthorizedError) { - session.resetAndRedirect(routes.login); - } else { - setError(e.message); - return; - } - } - props.onClose(); - }; - - return ( - - {t("prefs_reservations_dialog_title_delete")} - - {t("reservation_delete_dialog_description")} - - - - {!deleteMessages && ( - - {t("reservation_delete_dialog_action_keep_description")} - - )} - {deleteMessages && ( - - {t("reservation_delete_dialog_action_delete_description")} - - )} - - - - - - - ); -}; diff --git a/web/src/components/ReserveIcons.js b/web/src/components/ReserveIcons.js new file mode 100644 index 0000000..0d7b05b --- /dev/null +++ b/web/src/components/ReserveIcons.js @@ -0,0 +1,46 @@ +import * as React from 'react'; +import {Lock, Public} from "@mui/icons-material"; +import Box from "@mui/material/Box"; + +export const PermissionReadWrite = React.forwardRef((props, ref) => { + return ; +}); + +export const PermissionDenyAll = React.forwardRef((props, ref) => { + return ; +}); + +export const PermissionRead = React.forwardRef((props, ref) => { + return ; +}); + +export const PermissionWrite = React.forwardRef((props, ref) => { + return ; +}); + +const PermissionInternal = React.forwardRef((props, ref) => { + const size = props.size ?? "medium"; + const Icon = props.icon; + return ( + + + {props.text && + + {props.text} + + } + + ); +}); diff --git a/web/src/components/ReserveIcons.jsx b/web/src/components/ReserveIcons.jsx deleted file mode 100644 index 95f6f47..0000000 --- a/web/src/components/ReserveIcons.jsx +++ /dev/null @@ -1,47 +0,0 @@ -import * as React from "react"; -import { Lock, Public } from "@mui/icons-material"; -import { Box } from "@mui/material"; - -export const PermissionReadWrite = React.forwardRef((props, ref) => ); - -export const PermissionDenyAll = React.forwardRef((props, ref) => ); - -export const PermissionRead = React.forwardRef((props, ref) => ); - -export const PermissionWrite = React.forwardRef((props, ref) => ); - -const PermissionInternal = React.forwardRef((props, ref) => { - const size = props.size ?? "medium"; - const Icon = props.icon; - return ( - - - {props.text && ( - - {props.text} - - )} - - ); -}); diff --git a/web/src/components/ReserveTopicSelect.js b/web/src/components/ReserveTopicSelect.js new file mode 100644 index 0000000..e5daf69 --- /dev/null +++ b/web/src/components/ReserveTopicSelect.js @@ -0,0 +1,49 @@ +import * as React from 'react'; +import {FormControl, Select} from "@mui/material"; +import {useTranslation} from "react-i18next"; +import MenuItem from "@mui/material/MenuItem"; +import ListItemIcon from "@mui/material/ListItemIcon"; +import ListItemText from "@mui/material/ListItemText"; +import {PermissionDenyAll, PermissionRead, PermissionReadWrite, PermissionWrite} from "./ReserveIcons"; +import {Permission} from "../app/AccountApi"; + +const ReserveTopicSelect = (props) => { + const { t } = useTranslation(); + const sx = props.sx || {}; + return ( + + + + ); +}; + +export default ReserveTopicSelect; diff --git a/web/src/components/ReserveTopicSelect.jsx b/web/src/components/ReserveTopicSelect.jsx deleted file mode 100644 index 39ae5df..0000000 --- a/web/src/components/ReserveTopicSelect.jsx +++ /dev/null @@ -1,54 +0,0 @@ -import * as React from "react"; -import { FormControl, Select, MenuItem, ListItemIcon, ListItemText } from "@mui/material"; -import { useTranslation } from "react-i18next"; -import { PermissionDenyAll, PermissionRead, PermissionReadWrite, PermissionWrite } from "./ReserveIcons"; -import { Permission } from "../app/AccountApi"; - -const ReserveTopicSelect = (props) => { - const { t } = useTranslation(); - const sx = props.sx || {}; - return ( - - - - ); -}; - -export default ReserveTopicSelect; diff --git a/web/src/components/Signup.js b/web/src/components/Signup.js new file mode 100644 index 0000000..856ce8f --- /dev/null +++ b/web/src/components/Signup.js @@ -0,0 +1,158 @@ +import * as React from 'react'; +import {useState} from 'react'; +import TextField from "@mui/material/TextField"; +import Button from "@mui/material/Button"; +import Box from "@mui/material/Box"; +import routes from "./routes"; +import session from "../app/Session"; +import Typography from "@mui/material/Typography"; +import {NavLink} from "react-router-dom"; +import AvatarBox from "./AvatarBox"; +import {useTranslation} from "react-i18next"; +import WarningAmberIcon from "@mui/icons-material/WarningAmber"; +import accountApi from "../app/AccountApi"; +import {InputAdornment} from "@mui/material"; +import IconButton from "@mui/material/IconButton"; +import {Visibility, VisibilityOff} from "@mui/icons-material"; +import {AccountCreateLimitReachedError, UserExistsError} from "../app/errors"; + +const Signup = () => { + const { t } = useTranslation(); + const [error, setError] = useState(""); + const [username, setUsername] = useState(""); + const [password, setPassword] = useState(""); + const [confirm, setConfirm] = useState(""); + const [showPassword, setShowPassword] = useState(false); + const [showConfirm, setShowConfirm] = useState(false); + + const handleSubmit = async (event) => { + event.preventDefault(); + const user = { username, password }; + try { + await accountApi.create(user.username, user.password); + const token = await accountApi.login(user); + console.log(`[Signup] User signup for user ${user.username} successful, token is ${token}`); + session.store(user.username, token); + window.location.href = routes.app; + } catch (e) { + console.log(`[Signup] Signup for user ${user.username} failed`, e); + if (e instanceof UserExistsError) { + setError(t("signup_error_username_taken", { username: e.username })); + } else if ((e instanceof AccountCreateLimitReachedError)) { + setError(t("signup_error_creation_limit_reached")); + } else { + setError(e.message); + } + } + }; + + if (!config.enable_signup) { + return ( + + {t("signup_disabled")} + + ); + } + + return ( + + + {t("signup_title")} + + + setUsername(ev.target.value.trim())} + autoFocus + /> + setPassword(ev.target.value.trim())} + InputProps={{ + endAdornment: ( + + setShowPassword(!showPassword)} + onMouseDown={(ev) => ev.preventDefault()} + edge="end" + > + {showPassword ? : } + + + ) + }} + /> + setConfirm(ev.target.value.trim())} + InputProps={{ + endAdornment: ( + + setShowConfirm(!showConfirm)} + onMouseDown={(ev) => ev.preventDefault()} + edge="end" + > + {showConfirm ? : } + + + ) + }} + /> + + {error && + + + {error} + + } + + {config.enable_login && + + + {t("signup_already_have_account")} + + + } + + ); +} + +export default Signup; diff --git a/web/src/components/Signup.jsx b/web/src/components/Signup.jsx deleted file mode 100644 index 3b82cd6..0000000 --- a/web/src/components/Signup.jsx +++ /dev/null @@ -1,153 +0,0 @@ -import * as React from "react"; -import { useState } from "react"; -import { TextField, Button, Box, Typography, InputAdornment, IconButton } from "@mui/material"; -import { NavLink } from "react-router-dom"; -import { useTranslation } from "react-i18next"; -import WarningAmberIcon from "@mui/icons-material/WarningAmber"; -import { Visibility, VisibilityOff } from "@mui/icons-material"; -import accountApi from "../app/AccountApi"; -import AvatarBox from "./AvatarBox"; -import session from "../app/Session"; -import routes from "./routes"; -import { AccountCreateLimitReachedError, UserExistsError } from "../app/errors"; - -const Signup = () => { - const { t } = useTranslation(); - const [error, setError] = useState(""); - const [username, setUsername] = useState(""); - const [password, setPassword] = useState(""); - const [confirm, setConfirm] = useState(""); - const [showPassword, setShowPassword] = useState(false); - const [showConfirm, setShowConfirm] = useState(false); - - const handleSubmit = async (event) => { - event.preventDefault(); - const user = { username, password }; - try { - await accountApi.create(user.username, user.password); - const token = await accountApi.login(user); - console.log(`[Signup] User signup for user ${user.username} successful, token is ${token}`); - session.store(user.username, token); - window.location.href = routes.app; - } catch (e) { - console.log(`[Signup] Signup for user ${user.username} failed`, e); - if (e instanceof UserExistsError) { - setError(t("signup_error_username_taken", { username: e.username })); - } else if (e instanceof AccountCreateLimitReachedError) { - setError(t("signup_error_creation_limit_reached")); - } else { - setError(e.message); - } - } - }; - - if (!config.enable_signup) { - return ( - - {t("signup_disabled")} - - ); - } - - return ( - - {t("signup_title")} - - setUsername(ev.target.value.trim())} - autoFocus - /> - setPassword(ev.target.value.trim())} - InputProps={{ - endAdornment: ( - - setShowPassword(!showPassword)} - onMouseDown={(ev) => ev.preventDefault()} - edge="end" - > - {showPassword ? : } - - - ), - }} - /> - setConfirm(ev.target.value.trim())} - InputProps={{ - endAdornment: ( - - setShowConfirm(!showConfirm)} - onMouseDown={(ev) => ev.preventDefault()} - edge="end" - > - {showConfirm ? : } - - - ), - }} - /> - - {error && ( - - - {error} - - )} - - {config.enable_login && ( - - - {t("signup_already_have_account")} - - - )} - - ); -}; - -export default Signup; diff --git a/web/src/components/SubscribeDialog.js b/web/src/components/SubscribeDialog.js new file mode 100644 index 0000000..4fd4f8c --- /dev/null +++ b/web/src/components/SubscribeDialog.js @@ -0,0 +1,313 @@ +import * as React from 'react'; +import {useContext, useState} from 'react'; +import Button from '@mui/material/Button'; +import TextField from '@mui/material/TextField'; +import Dialog from '@mui/material/Dialog'; +import DialogContent from '@mui/material/DialogContent'; +import DialogContentText from '@mui/material/DialogContentText'; +import DialogTitle from '@mui/material/DialogTitle'; +import {Autocomplete, Checkbox, FormControlLabel, FormGroup, useMediaQuery} from "@mui/material"; +import theme from "./theme"; +import api from "../app/Api"; +import {randomAlphanumericString, topicUrl, validTopic, validUrl} from "../app/utils"; +import userManager from "../app/UserManager"; +import subscriptionManager from "../app/SubscriptionManager"; +import poller from "../app/Poller"; +import DialogFooter from "./DialogFooter"; +import {useTranslation} from "react-i18next"; +import session from "../app/Session"; +import routes from "./routes"; +import accountApi, {Permission, Role} from "../app/AccountApi"; +import ReserveTopicSelect from "./ReserveTopicSelect"; +import {AccountContext} from "./App"; +import {TopicReservedError, UnauthorizedError} from "../app/errors"; +import {ReserveLimitChip} from "./SubscriptionPopup"; + +const publicBaseUrl = "https://ntfy.sh"; + +const SubscribeDialog = (props) => { + const [baseUrl, setBaseUrl] = useState(""); + const [topic, setTopic] = useState(""); + const [showLoginPage, setShowLoginPage] = useState(false); + const fullScreen = useMediaQuery(theme.breakpoints.down('sm')); + + const handleSuccess = async () => { + console.log(`[SubscribeDialog] Subscribing to topic ${topic}`); + const actualBaseUrl = (baseUrl) ? baseUrl : config.base_url; + const subscription = await subscribeTopic(actualBaseUrl, topic); + poller.pollInBackground(subscription); // Dangle! + props.onSuccess(subscription); + } + + return ( + + {!showLoginPage && setShowLoginPage(true)} + onSuccess={handleSuccess} + />} + {showLoginPage && setShowLoginPage(false)} + onSuccess={handleSuccess} + />} + + ); +}; + +const SubscribePage = (props) => { + const { t } = useTranslation(); + const { account } = useContext(AccountContext); + const [error, setError] = useState(""); + const [reserveTopicVisible, setReserveTopicVisible] = useState(false); + const [anotherServerVisible, setAnotherServerVisible] = useState(false); + const [everyone, setEveryone] = useState(Permission.DENY_ALL); + const baseUrl = (anotherServerVisible) ? props.baseUrl : config.base_url; + const topic = props.topic; + const existingTopicUrls = props.subscriptions.map(s => topicUrl(s.baseUrl, s.topic)); + const existingBaseUrls = Array + .from(new Set([publicBaseUrl, ...props.subscriptions.map(s => s.baseUrl)])) + .filter(s => s !== config.base_url); + const showReserveTopicCheckbox = config.enable_reservations && !anotherServerVisible && (config.enable_payments || account); + const reserveTopicEnabled = session.exists() && (account?.role === Role.ADMIN || (account?.role === Role.USER && (account?.stats.reservations_remaining || 0) > 0)); + + const handleSubscribe = async () => { + const user = await userManager.get(baseUrl); // May be undefined + const username = (user) ? user.username : t("subscribe_dialog_error_user_anonymous"); + + // Check read access to topic + const success = await api.topicAuth(baseUrl, topic, user); + if (!success) { + console.log(`[SubscribeDialog] Login to ${topicUrl(baseUrl, topic)} failed for user ${username}`); + if (user) { + setError(t("subscribe_dialog_error_user_not_authorized", { username: username })); + return; + } else { + props.onNeedsLogin(); + return; + } + } + + // Reserve topic (if requested) + if (session.exists() && baseUrl === config.base_url && reserveTopicVisible) { + console.log(`[SubscribeDialog] Reserving topic ${topic} with everyone access ${everyone}`); + try { + await accountApi.upsertReservation(topic, everyone); + } catch (e) { + console.log(`[SubscribeDialog] Error reserving topic`, e); + if (e instanceof UnauthorizedError) { + session.resetAndRedirect(routes.login); + } else if (e instanceof TopicReservedError) { + setError(t("subscribe_dialog_error_topic_already_reserved")); + return; + } + } + } + + console.log(`[SubscribeDialog] Successful login to ${topicUrl(baseUrl, topic)} for user ${username}`); + props.onSuccess(); + }; + + const handleUseAnotherChanged = (e) => { + props.setBaseUrl(""); + setAnotherServerVisible(e.target.checked); + }; + + const subscribeButtonEnabled = (() => { + if (anotherServerVisible) { + const isExistingTopicUrl = existingTopicUrls.includes(topicUrl(baseUrl, topic)); + return validTopic(topic) && validUrl(baseUrl) && !isExistingTopicUrl; + } else { + const isExistingTopicUrl = existingTopicUrls.includes(topicUrl(config.base_url, topic)); + return validTopic(topic) && !isExistingTopicUrl; + } + })(); + + const updateBaseUrl = (ev, newVal) => { + if (validUrl(newVal)) { + props.setBaseUrl(newVal.replace(/\/$/, '')); // strip trailing slash after https?:// + } else { + props.setBaseUrl(newVal); + } + }; + + return ( + <> + {t("subscribe_dialog_subscribe_title")} + + + {t("subscribe_dialog_subscribe_description")} + +
+ props.setTopic(ev.target.value)} + type="text" + fullWidth + variant="standard" + inputProps={{ + maxLength: 64, + "aria-label": t("subscribe_dialog_subscribe_topic_placeholder") + }} + /> + +
+ {showReserveTopicCheckbox && + + setReserveTopicVisible(ev.target.checked)} + inputProps={{ + "aria-label": t("reserve_dialog_checkbox_label") + }} + /> + } + label={ + <> + {t("reserve_dialog_checkbox_label")} + + + } + /> + {reserveTopicVisible && + + } + + } + {!reserveTopicVisible && + + + } + label={t("subscribe_dialog_subscribe_use_another_label")}/> + {anotherServerVisible && + + } + />} + + } +
+ + + + + + ); +}; + +const LoginPage = (props) => { + const { t } = useTranslation(); + const [username, setUsername] = useState(""); + const [password, setPassword] = useState(""); + const [error, setError] = useState(""); + const baseUrl = (props.baseUrl) ? props.baseUrl : config.base_url; + const topic = props.topic; + + const handleLogin = async () => { + const user = {baseUrl, username, password}; + const success = await api.topicAuth(baseUrl, topic, user); + if (!success) { + console.log(`[SubscribeDialog] Login to ${topicUrl(baseUrl, topic)} failed for user ${username}`); + setError(t("subscribe_dialog_error_user_not_authorized", { username: username })); + return; + } + console.log(`[SubscribeDialog] Successful login to ${topicUrl(baseUrl, topic)} for user ${username}`); + await userManager.save(user); + props.onSuccess(); + }; + + return ( + <> + {t("subscribe_dialog_login_title")} + + + {t("subscribe_dialog_login_description")} + + setUsername(ev.target.value)} + type="text" + fullWidth + variant="standard" + inputProps={{ + "aria-label": t("subscribe_dialog_login_username_label") + }} + /> + setPassword(ev.target.value)} + fullWidth + variant="standard" + inputProps={{ + "aria-label": t("subscribe_dialog_login_password_label") + }} + /> + + + + + + + ); +}; + +export const subscribeTopic = async (baseUrl, topic) => { + const subscription = await subscriptionManager.add(baseUrl, topic); + if (session.exists()) { + try { + await accountApi.addSubscription(baseUrl, topic); + } catch (e) { + console.log(`[SubscribeDialog] Subscribing to topic ${topic} failed`, e); + if (e instanceof UnauthorizedError) { + session.resetAndRedirect(routes.login); + } + } + } + return subscription; +}; + +export default SubscribeDialog; diff --git a/web/src/components/SubscribeDialog.jsx b/web/src/components/SubscribeDialog.jsx deleted file mode 100644 index 0f1cec1..0000000 --- a/web/src/components/SubscribeDialog.jsx +++ /dev/null @@ -1,320 +0,0 @@ -import * as React from "react"; -import { useContext, useState } from "react"; -import { - Button, - TextField, - Dialog, - DialogContent, - DialogContentText, - DialogTitle, - Autocomplete, - Checkbox, - FormControlLabel, - FormGroup, - useMediaQuery, -} from "@mui/material"; -import { useTranslation } from "react-i18next"; -import theme from "./theme"; -import api from "../app/Api"; -import { randomAlphanumericString, topicUrl, validTopic, validUrl } from "../app/utils"; -import userManager from "../app/UserManager"; -import subscriptionManager from "../app/SubscriptionManager"; -import poller from "../app/Poller"; -import DialogFooter from "./DialogFooter"; -import session from "../app/Session"; -import routes from "./routes"; -import accountApi, { Permission, Role } from "../app/AccountApi"; -import ReserveTopicSelect from "./ReserveTopicSelect"; -import { AccountContext } from "./App"; -import { TopicReservedError, UnauthorizedError } from "../app/errors"; -import { ReserveLimitChip } from "./SubscriptionPopup"; - -const publicBaseUrl = "https://ntfy.sh"; - -export const subscribeTopic = async (baseUrl, topic) => { - const subscription = await subscriptionManager.add(baseUrl, topic); - if (session.exists()) { - try { - await accountApi.addSubscription(baseUrl, topic); - } catch (e) { - console.log(`[SubscribeDialog] Subscribing to topic ${topic} failed`, e); - if (e instanceof UnauthorizedError) { - session.resetAndRedirect(routes.login); - } - } - } - return subscription; -}; - -const SubscribeDialog = (props) => { - const [baseUrl, setBaseUrl] = useState(""); - const [topic, setTopic] = useState(""); - const [showLoginPage, setShowLoginPage] = useState(false); - const fullScreen = useMediaQuery(theme.breakpoints.down("sm")); - - const handleSuccess = async () => { - console.log(`[SubscribeDialog] Subscribing to topic ${topic}`); - const actualBaseUrl = baseUrl || config.base_url; - const subscription = await subscribeTopic(actualBaseUrl, topic); - poller.pollInBackground(subscription); // Dangle! - props.onSuccess(subscription); - }; - - return ( - - {!showLoginPage && ( - setShowLoginPage(true)} - onSuccess={handleSuccess} - /> - )} - {showLoginPage && setShowLoginPage(false)} onSuccess={handleSuccess} />} - - ); -}; - -const SubscribePage = (props) => { - const { t } = useTranslation(); - const { account } = useContext(AccountContext); - const [error, setError] = useState(""); - const [reserveTopicVisible, setReserveTopicVisible] = useState(false); - const [anotherServerVisible, setAnotherServerVisible] = useState(false); - const [everyone, setEveryone] = useState(Permission.DENY_ALL); - const baseUrl = anotherServerVisible ? props.baseUrl : config.base_url; - const { topic } = props; - const existingTopicUrls = props.subscriptions.map((s) => topicUrl(s.baseUrl, s.topic)); - const existingBaseUrls = Array.from(new Set([publicBaseUrl, ...props.subscriptions.map((s) => s.baseUrl)])).filter( - (s) => s !== config.base_url - ); - const showReserveTopicCheckbox = config.enable_reservations && !anotherServerVisible && (config.enable_payments || account); - const reserveTopicEnabled = - session.exists() && (account?.role === Role.ADMIN || (account?.role === Role.USER && (account?.stats.reservations_remaining || 0) > 0)); - - const handleSubscribe = async () => { - const user = await userManager.get(baseUrl); // May be undefined - const username = user ? user.username : t("subscribe_dialog_error_user_anonymous"); - - // Check read access to topic - const success = await api.topicAuth(baseUrl, topic, user); - if (!success) { - console.log(`[SubscribeDialog] Login to ${topicUrl(baseUrl, topic)} failed for user ${username}`); - if (user) { - setError( - t("subscribe_dialog_error_user_not_authorized", { - username, - }) - ); - return; - } - props.onNeedsLogin(); - return; - } - - // Reserve topic (if requested) - if (session.exists() && baseUrl === config.base_url && reserveTopicVisible) { - console.log(`[SubscribeDialog] Reserving topic ${topic} with everyone access ${everyone}`); - try { - await accountApi.upsertReservation(topic, everyone); - } catch (e) { - console.log(`[SubscribeDialog] Error reserving topic`, e); - if (e instanceof UnauthorizedError) { - session.resetAndRedirect(routes.login); - } else if (e instanceof TopicReservedError) { - setError(t("subscribe_dialog_error_topic_already_reserved")); - return; - } - } - } - - console.log(`[SubscribeDialog] Successful login to ${topicUrl(baseUrl, topic)} for user ${username}`); - props.onSuccess(); - }; - - const handleUseAnotherChanged = (e) => { - props.setBaseUrl(""); - setAnotherServerVisible(e.target.checked); - }; - - const subscribeButtonEnabled = (() => { - if (anotherServerVisible) { - const isExistingTopicUrl = existingTopicUrls.includes(topicUrl(baseUrl, topic)); - return validTopic(topic) && validUrl(baseUrl) && !isExistingTopicUrl; - } - const isExistingTopicUrl = existingTopicUrls.includes(topicUrl(config.base_url, topic)); - return validTopic(topic) && !isExistingTopicUrl; - })(); - - const updateBaseUrl = (ev, newVal) => { - if (validUrl(newVal)) { - props.setBaseUrl(newVal.replace(/\/$/, "")); // strip trailing slash after https?:// - } else { - props.setBaseUrl(newVal); - } - }; - - return ( - <> - {t("subscribe_dialog_subscribe_title")} - - {t("subscribe_dialog_subscribe_description")} -
- props.setTopic(ev.target.value)} - type="text" - fullWidth - variant="standard" - inputProps={{ - maxLength: 64, - "aria-label": t("subscribe_dialog_subscribe_topic_placeholder"), - }} - /> - -
- {showReserveTopicCheckbox && ( - - setReserveTopicVisible(ev.target.checked)} - inputProps={{ - "aria-label": t("reserve_dialog_checkbox_label"), - }} - /> - } - label={ - <> - {t("reserve_dialog_checkbox_label")} - - - } - /> - {reserveTopicVisible && } - - )} - {!reserveTopicVisible && ( - - - } - label={t("subscribe_dialog_subscribe_use_another_label")} - /> - {anotherServerVisible && ( - ( - - )} - /> - )} - - )} -
- - - - - - ); -}; - -const LoginPage = (props) => { - const { t } = useTranslation(); - const [username, setUsername] = useState(""); - const [password, setPassword] = useState(""); - const [error, setError] = useState(""); - const baseUrl = props.baseUrl ? props.baseUrl : config.base_url; - const { topic } = props; - - const handleLogin = async () => { - const user = { baseUrl, username, password }; - const success = await api.topicAuth(baseUrl, topic, user); - if (!success) { - console.log(`[SubscribeDialog] Login to ${topicUrl(baseUrl, topic)} failed for user ${username}`); - setError(t("subscribe_dialog_error_user_not_authorized", { username })); - return; - } - console.log(`[SubscribeDialog] Successful login to ${topicUrl(baseUrl, topic)} for user ${username}`); - await userManager.save(user); - props.onSuccess(); - }; - - return ( - <> - {t("subscribe_dialog_login_title")} - - {t("subscribe_dialog_login_description")} - setUsername(ev.target.value)} - type="text" - fullWidth - variant="standard" - inputProps={{ - "aria-label": t("subscribe_dialog_login_username_label"), - }} - /> - setPassword(ev.target.value)} - fullWidth - variant="standard" - inputProps={{ - "aria-label": t("subscribe_dialog_login_password_label"), - }} - /> - - - - - - - ); -}; - -export default SubscribeDialog; diff --git a/web/src/components/SubscriptionPopup.js b/web/src/components/SubscriptionPopup.js new file mode 100644 index 0000000..7655605 --- /dev/null +++ b/web/src/components/SubscriptionPopup.js @@ -0,0 +1,292 @@ +import * as React from 'react'; +import {useContext, useState} from 'react'; +import Button from '@mui/material/Button'; +import TextField from '@mui/material/TextField'; +import Dialog from '@mui/material/Dialog'; +import DialogContent from '@mui/material/DialogContent'; +import DialogContentText from '@mui/material/DialogContentText'; +import DialogTitle from '@mui/material/DialogTitle'; +import {Chip, InputAdornment, Portal, Snackbar, useMediaQuery} from "@mui/material"; +import theme from "./theme"; +import subscriptionManager from "../app/SubscriptionManager"; +import DialogFooter from "./DialogFooter"; +import {useTranslation} from "react-i18next"; +import accountApi, {Role} from "../app/AccountApi"; +import session from "../app/Session"; +import routes from "./routes"; +import MenuItem from "@mui/material/MenuItem"; +import PopupMenu from "./PopupMenu"; +import {formatShortDateTime, shuffle} from "../app/utils"; +import api from "../app/Api"; +import {useNavigate} from "react-router-dom"; +import IconButton from "@mui/material/IconButton"; +import {Clear} from "@mui/icons-material"; +import {AccountContext} from "./App"; +import {ReserveAddDialog, ReserveDeleteDialog, ReserveEditDialog} from "./ReserveDialogs"; +import {UnauthorizedError} from "../app/errors"; + +export const SubscriptionPopup = (props) => { + const { t } = useTranslation(); + const { account } = useContext(AccountContext); + const navigate = useNavigate(); + const [displayNameDialogOpen, setDisplayNameDialogOpen] = useState(false); + const [reserveAddDialogOpen, setReserveAddDialogOpen] = useState(false); + const [reserveEditDialogOpen, setReserveEditDialogOpen] = useState(false); + const [reserveDeleteDialogOpen, setReserveDeleteDialogOpen] = useState(false); + const [showPublishError, setShowPublishError] = useState(false); + const subscription = props.subscription; + const placement = props.placement ?? "left"; + const reservations = account?.reservations || []; + + const showReservationAdd = config.enable_reservations && !subscription?.reservation && account?.stats.reservations_remaining > 0; + const showReservationAddDisabled = !showReservationAdd && config.enable_reservations && !subscription?.reservation && (config.enable_payments || account?.stats.reservations_remaining === 0); + const showReservationEdit = config.enable_reservations && !!subscription?.reservation; + const showReservationDelete = config.enable_reservations && !!subscription?.reservation; + + const handleChangeDisplayName = async () => { + setDisplayNameDialogOpen(true); + } + + const handleReserveAdd = async () => { + setReserveAddDialogOpen(true); + } + + const handleReserveEdit = async () => { + setReserveEditDialogOpen(true); + } + + const handleReserveDelete = async () => { + setReserveDeleteDialogOpen(true); + } + + const handleSendTestMessage = async () => { + const baseUrl = props.subscription.baseUrl; + const topic = props.subscription.topic; + const tags = shuffle([ + "grinning", "octopus", "upside_down_face", "palm_tree", "maple_leaf", "apple", "skull", "warning", "jack_o_lantern", + "de-server-1", "backups", "cron-script", "script-error", "phils-automation", "mouse", "go-rocks", "hi-ben"]) + .slice(0, Math.round(Math.random() * 4)); + const priority = shuffle([1, 2, 3, 4, 5])[0]; + const title = shuffle([ + "", + "", + "", // Higher chance of no title + "Oh my, another test message?", + "Titles are optional, did you know that?", + "ntfy is open source, and will always be free. Cool, right?", + "I don't really like apples", + "My favorite TV show is The Wire. You should watch it!", + "You can attach files and URLs to messages too", + "You can delay messages up to 3 days" + ])[0]; + const nowSeconds = Math.round(Date.now()/1000); + const message = shuffle([ + `Hello friend, this is a test notification from ntfy web. It's ${formatShortDateTime(nowSeconds)} right now. Is that early or late?`, + `So I heard you like ntfy? If that's true, go to GitHub and star it, or to the Play store and rate it. Thanks! Oh yeah, this is a test notification.`, + `It's almost like you want to hear what I have to say. I'm not even a machine. I'm just a sentence that Phil typed on a random Thursday.`, + `Alright then, it's ${formatShortDateTime(nowSeconds)} already. Boy oh boy, where did the time go? I hope you're alright, friend.`, + `There are nine million bicycles in Beijing That's a fact; It's a thing we can't deny. I wonder if that's true ...`, + `I'm really excited that you're trying out ntfy. Did you know that there are a few public topics, such as ntfy.sh/stats and ntfy.sh/announcements.`, + `It's interesting to hear what people use ntfy for. I've heard people talk about using it for so many cool things. What do you use it for?` + ])[0]; + try { + await api.publish(baseUrl, topic, message, { + title: title, + priority: priority, + tags: tags + }); + } catch (e) { + console.log(`[SubscriptionPopup] Error publishing message`, e); + setShowPublishError(true); + } + } + + const handleClearAll = async () => { + console.log(`[SubscriptionPopup] Deleting all notifications from ${props.subscription.id}`); + await subscriptionManager.deleteNotifications(props.subscription.id); + }; + + const handleUnsubscribe = async () => { + console.log(`[SubscriptionPopup] Unsubscribing from ${props.subscription.id}`, props.subscription); + await subscriptionManager.remove(props.subscription.id); + if (session.exists() && !subscription.internal) { + try { + await accountApi.deleteSubscription(props.subscription.baseUrl, props.subscription.topic); + } catch (e) { + console.log(`[SubscriptionPopup] Error unsubscribing`, e); + if (e instanceof UnauthorizedError) { + session.resetAndRedirect(routes.login); + } + } + } + const newSelected = await subscriptionManager.first(); // May be undefined + if (newSelected && !newSelected.internal) { + navigate(routes.forSubscription(newSelected)); + } else { + navigate(routes.app); + } + }; + + return ( + <> + + {t("action_bar_change_display_name")} + {showReservationAdd && {t("action_bar_reservation_add")}} + {showReservationAddDisabled && + + {t("action_bar_reservation_add")} + + + } + {showReservationEdit && {t("action_bar_reservation_edit")}} + {showReservationDelete && {t("action_bar_reservation_delete")}} + {t("action_bar_send_test_notification")} + {t("action_bar_clear_notifications")} + {t("action_bar_unsubscribe")} + + + setShowPublishError(false)} + message={t("message_bar_error_publishing")} + /> + setDisplayNameDialogOpen(false)} + /> + {showReservationAdd && + setReserveAddDialogOpen(false)} + /> + } + {showReservationEdit && + setReserveEditDialogOpen(false)} + /> + } + {showReservationDelete && + setReserveDeleteDialogOpen(false)} + /> + } + + + ); +}; + +const DisplayNameDialog = (props) => { + const { t } = useTranslation(); + const subscription = props.subscription; + const [error, setError] = useState(""); + const [displayName, setDisplayName] = useState(subscription.displayName ?? ""); + const fullScreen = useMediaQuery(theme.breakpoints.down('sm')); + + const handleSave = async () => { + await subscriptionManager.setDisplayName(subscription.id, displayName); + if (session.exists() && !subscription.internal) { + try { + console.log(`[SubscriptionSettingsDialog] Updating subscription display name to ${displayName}`); + await accountApi.updateSubscription(subscription.baseUrl, subscription.topic, { display_name: displayName }); + } catch (e) { + console.log(`[SubscriptionSettingsDialog] Error updating subscription`, e); + if (e instanceof UnauthorizedError) { + session.resetAndRedirect(routes.login); + } else { + setError(e.message); + return; + } + } + } + props.onClose(); + } + + return ( + + {t("display_name_dialog_title")} + + + {t("display_name_dialog_description")} + + setDisplayName(ev.target.value)} + type="text" + fullWidth + variant="standard" + inputProps={{ + maxLength: 64, + "aria-label": t("display_name_dialog_placeholder") + }} + InputProps={{ + endAdornment: ( + + setDisplayName("")} edge="end"> + + + + ) + }} + /> + + + + + + + ); +}; + +export const ReserveLimitChip = () => { + const { account } = useContext(AccountContext); + if (account?.role === Role.ADMIN || account?.stats.reservations_remaining > 0) { + return <>; + } else if (config.enable_payments) { + return (account?.limits.reservations > 0) ? : ; + } else if (account) { + return ; + } + return <>; +}; + +const LimitReachedChip = () => { + const { t } = useTranslation(); + return ( + + ); +}; + +const ProChip = () => { + const { t } = useTranslation(); + return ( + + ); +}; + + diff --git a/web/src/components/SubscriptionPopup.jsx b/web/src/components/SubscriptionPopup.jsx deleted file mode 100644 index ee83a11..0000000 --- a/web/src/components/SubscriptionPopup.jsx +++ /dev/null @@ -1,314 +0,0 @@ -import * as React from "react"; -import { useContext, useState } from "react"; -import { - Button, - TextField, - Dialog, - DialogContent, - DialogContentText, - DialogTitle, - Chip, - InputAdornment, - Portal, - Snackbar, - useMediaQuery, - MenuItem, - IconButton, -} from "@mui/material"; -import { useTranslation } from "react-i18next"; -import { useNavigate } from "react-router-dom"; -import { Clear } from "@mui/icons-material"; -import theme from "./theme"; -import subscriptionManager from "../app/SubscriptionManager"; -import DialogFooter from "./DialogFooter"; -import accountApi, { Role } from "../app/AccountApi"; -import session from "../app/Session"; -import routes from "./routes"; -import PopupMenu from "./PopupMenu"; -import { formatShortDateTime, shuffle } from "../app/utils"; -import api from "../app/Api"; -import { AccountContext } from "./App"; -import { ReserveAddDialog, ReserveDeleteDialog, ReserveEditDialog } from "./ReserveDialogs"; -import { UnauthorizedError } from "../app/errors"; - -export const SubscriptionPopup = (props) => { - const { t } = useTranslation(); - const { account } = useContext(AccountContext); - const navigate = useNavigate(); - const [displayNameDialogOpen, setDisplayNameDialogOpen] = useState(false); - const [reserveAddDialogOpen, setReserveAddDialogOpen] = useState(false); - const [reserveEditDialogOpen, setReserveEditDialogOpen] = useState(false); - const [reserveDeleteDialogOpen, setReserveDeleteDialogOpen] = useState(false); - const [showPublishError, setShowPublishError] = useState(false); - const { subscription } = props; - const placement = props.placement ?? "left"; - const reservations = account?.reservations || []; - - const showReservationAdd = config.enable_reservations && !subscription?.reservation && account?.stats.reservations_remaining > 0; - const showReservationAddDisabled = - !showReservationAdd && - config.enable_reservations && - !subscription?.reservation && - (config.enable_payments || account?.stats.reservations_remaining === 0); - const showReservationEdit = config.enable_reservations && !!subscription?.reservation; - const showReservationDelete = config.enable_reservations && !!subscription?.reservation; - - const handleChangeDisplayName = async () => { - setDisplayNameDialogOpen(true); - }; - - const handleReserveAdd = async () => { - setReserveAddDialogOpen(true); - }; - - const handleReserveEdit = async () => { - setReserveEditDialogOpen(true); - }; - - const handleReserveDelete = async () => { - setReserveDeleteDialogOpen(true); - }; - - const handleSendTestMessage = async () => { - const { baseUrl } = props.subscription; - const { topic } = props.subscription; - const tags = shuffle([ - "grinning", - "octopus", - "upside_down_face", - "palm_tree", - "maple_leaf", - "apple", - "skull", - "warning", - "jack_o_lantern", - "de-server-1", - "backups", - "cron-script", - "script-error", - "phils-automation", - "mouse", - "go-rocks", - "hi-ben", - ]).slice(0, Math.round(Math.random() * 4)); - const priority = shuffle([1, 2, 3, 4, 5])[0]; - const title = shuffle([ - "", - "", - "", // Higher chance of no title - "Oh my, another test message?", - "Titles are optional, did you know that?", - "ntfy is open source, and will always be free. Cool, right?", - "I don't really like apples", - "My favorite TV show is The Wire. You should watch it!", - "You can attach files and URLs to messages too", - "You can delay messages up to 3 days", - ])[0]; - const nowSeconds = Math.round(Date.now() / 1000); - const message = shuffle([ - `Hello friend, this is a test notification from ntfy web. It's ${formatShortDateTime(nowSeconds)} right now. Is that early or late?`, - `So I heard you like ntfy? If that's true, go to GitHub and star it, or to the Play store and rate it. Thanks! Oh yeah, this is a test notification.`, - `It's almost like you want to hear what I have to say. I'm not even a machine. I'm just a sentence that Phil typed on a random Thursday.`, - `Alright then, it's ${formatShortDateTime(nowSeconds)} already. Boy oh boy, where did the time go? I hope you're alright, friend.`, - `There are nine million bicycles in Beijing That's a fact; It's a thing we can't deny. I wonder if that's true ...`, - `I'm really excited that you're trying out ntfy. Did you know that there are a few public topics, such as ntfy.sh/stats and ntfy.sh/announcements.`, - `It's interesting to hear what people use ntfy for. I've heard people talk about using it for so many cool things. What do you use it for?`, - ])[0]; - try { - await api.publish(baseUrl, topic, message, { - title, - priority, - tags, - }); - } catch (e) { - console.log(`[SubscriptionPopup] Error publishing message`, e); - setShowPublishError(true); - } - }; - - const handleClearAll = async () => { - console.log(`[SubscriptionPopup] Deleting all notifications from ${props.subscription.id}`); - await subscriptionManager.deleteNotifications(props.subscription.id); - }; - - const handleUnsubscribe = async () => { - console.log(`[SubscriptionPopup] Unsubscribing from ${props.subscription.id}`, props.subscription); - await subscriptionManager.remove(props.subscription.id); - if (session.exists() && !subscription.internal) { - try { - await accountApi.deleteSubscription(props.subscription.baseUrl, props.subscription.topic); - } catch (e) { - console.log(`[SubscriptionPopup] Error unsubscribing`, e); - if (e instanceof UnauthorizedError) { - session.resetAndRedirect(routes.login); - } - } - } - const newSelected = await subscriptionManager.first(); // May be undefined - if (newSelected && !newSelected.internal) { - navigate(routes.forSubscription(newSelected)); - } else { - navigate(routes.app); - } - }; - - return ( - <> - - {t("action_bar_change_display_name")} - {showReservationAdd && {t("action_bar_reservation_add")}} - {showReservationAddDisabled && ( - - {t("action_bar_reservation_add")} - - - )} - {showReservationEdit && {t("action_bar_reservation_edit")}} - {showReservationDelete && {t("action_bar_reservation_delete")}} - {t("action_bar_send_test_notification")} - {t("action_bar_clear_notifications")} - {t("action_bar_unsubscribe")} - - - setShowPublishError(false)} - message={t("message_bar_error_publishing")} - /> - setDisplayNameDialogOpen(false)} /> - {showReservationAdd && ( - setReserveAddDialogOpen(false)} - /> - )} - {showReservationEdit && ( - setReserveEditDialogOpen(false)} - /> - )} - {showReservationDelete && ( - setReserveDeleteDialogOpen(false)} - /> - )} - - - ); -}; - -const DisplayNameDialog = (props) => { - const { t } = useTranslation(); - const { subscription } = props; - const [error, setError] = useState(""); - const [displayName, setDisplayName] = useState(subscription.displayName ?? ""); - const fullScreen = useMediaQuery(theme.breakpoints.down("sm")); - - const handleSave = async () => { - await subscriptionManager.setDisplayName(subscription.id, displayName); - if (session.exists() && !subscription.internal) { - try { - console.log(`[SubscriptionSettingsDialog] Updating subscription display name to ${displayName}`); - await accountApi.updateSubscription(subscription.baseUrl, subscription.topic, { display_name: displayName }); - } catch (e) { - console.log(`[SubscriptionSettingsDialog] Error updating subscription`, e); - if (e instanceof UnauthorizedError) { - session.resetAndRedirect(routes.login); - } else { - setError(e.message); - return; - } - } - } - props.onClose(); - }; - - return ( - - {t("display_name_dialog_title")} - - {t("display_name_dialog_description")} - setDisplayName(ev.target.value)} - type="text" - fullWidth - variant="standard" - inputProps={{ - maxLength: 64, - "aria-label": t("display_name_dialog_placeholder"), - }} - InputProps={{ - endAdornment: ( - - setDisplayName("")} edge="end"> - - - - ), - }} - /> - - - - - - - ); -}; - -export const ReserveLimitChip = () => { - const { account } = useContext(AccountContext); - if (account?.role === Role.ADMIN || account?.stats.reservations_remaining > 0) { - return <>; - } - if (config.enable_payments) { - return account?.limits.reservations > 0 ? : ; - } - if (account) { - return ; - } - return <>; -}; - -const LimitReachedChip = () => { - const { t } = useTranslation(); - return ( - - ); -}; - -export const ProChip = () => ( - -); diff --git a/web/src/components/UpgradeDialog.js b/web/src/components/UpgradeDialog.js new file mode 100644 index 0000000..43be16f --- /dev/null +++ b/web/src/components/UpgradeDialog.js @@ -0,0 +1,366 @@ +import * as React from 'react'; +import {useContext, useEffect, useState} from 'react'; +import Dialog from '@mui/material/Dialog'; +import DialogContent from '@mui/material/DialogContent'; +import DialogTitle from '@mui/material/DialogTitle'; +import {Alert, CardActionArea, CardContent, Chip, Link, ListItem, Switch, useMediaQuery} from "@mui/material"; +import theme from "./theme"; +import Button from "@mui/material/Button"; +import accountApi, {SubscriptionInterval} from "../app/AccountApi"; +import session from "../app/Session"; +import routes from "./routes"; +import Card from "@mui/material/Card"; +import Typography from "@mui/material/Typography"; +import {AccountContext} from "./App"; +import {formatBytes, formatNumber, formatPrice, formatShortDate} from "../app/utils"; +import {Trans, useTranslation} from "react-i18next"; +import List from "@mui/material/List"; +import {Check, Close} from "@mui/icons-material"; +import ListItemIcon from "@mui/material/ListItemIcon"; +import ListItemText from "@mui/material/ListItemText"; +import Box from "@mui/material/Box"; +import {NavLink} from "react-router-dom"; +import {UnauthorizedError} from "../app/errors"; +import DialogContentText from "@mui/material/DialogContentText"; +import DialogActions from "@mui/material/DialogActions"; + +const UpgradeDialog = (props) => { + const { t } = useTranslation(); + const { account } = useContext(AccountContext); // May be undefined! + const [error, setError] = useState(""); + const [tiers, setTiers] = useState(null); + const [interval, setInterval] = useState(account?.billing?.interval || SubscriptionInterval.YEAR); + const [newTierCode, setNewTierCode] = useState(account?.tier?.code); // May be undefined + const [loading, setLoading] = useState(false); + const fullScreen = useMediaQuery(theme.breakpoints.down('sm')); + + useEffect(() => { + const fetchTiers = async () => { + setTiers(await accountApi.billingTiers()); + } + fetchTiers(); // Dangle + }, []); + + if (!tiers) { + return <>; + } + + const tiersMap = Object.assign(...tiers.map(tier => ({[tier.code]: tier}))); + const newTier = tiersMap[newTierCode]; // May be undefined + const currentTier = account?.tier; // May be undefined + const currentInterval = account?.billing?.interval; // May be undefined + const currentTierCode = currentTier?.code; // May be undefined + + // Figure out buttons, labels and the submit action + let submitAction, submitButtonLabel, banner; + if (!account) { + submitButtonLabel = t("account_upgrade_dialog_button_redirect_signup"); + submitAction = Action.REDIRECT_SIGNUP; + banner = null; + } else if (currentTierCode === newTierCode && (currentInterval === undefined || currentInterval === interval)) { + submitButtonLabel = t("account_upgrade_dialog_button_update_subscription"); + submitAction = null; + banner = (currentTierCode) ? Banner.PRORATION_INFO : null; + } else if (!currentTierCode) { + submitButtonLabel = t("account_upgrade_dialog_button_pay_now"); + submitAction = Action.CREATE_SUBSCRIPTION; + banner = null; + } else if (!newTierCode) { + submitButtonLabel = t("account_upgrade_dialog_button_cancel_subscription"); + submitAction = Action.CANCEL_SUBSCRIPTION; + banner = Banner.CANCEL_WARNING; + } else { + submitButtonLabel = t("account_upgrade_dialog_button_update_subscription"); + submitAction = Action.UPDATE_SUBSCRIPTION; + banner = Banner.PRORATION_INFO; + } + + // Exceptional conditions + if (loading) { + submitAction = null; + } else if (newTier?.code && account?.reservations?.length > newTier?.limits?.reservations) { + submitAction = null; + banner = Banner.RESERVATIONS_WARNING; + } + + const handleSubmit = async () => { + if (submitAction === Action.REDIRECT_SIGNUP) { + window.location.href = routes.signup; + return; + } + try { + setLoading(true); + if (submitAction === Action.CREATE_SUBSCRIPTION) { + const response = await accountApi.createBillingSubscription(newTierCode, interval); + window.location.href = response.redirect_url; + } else if (submitAction === Action.UPDATE_SUBSCRIPTION) { + await accountApi.updateBillingSubscription(newTierCode, interval); + } else if (submitAction === Action.CANCEL_SUBSCRIPTION) { + await accountApi.deleteBillingSubscription(); + } + props.onCancel(); + } catch (e) { + console.log(`[UpgradeDialog] Error changing billing subscription`, e); + if (e instanceof UnauthorizedError) { + session.resetAndRedirect(routes.login); + } else { + setError(e.message); + } + } finally { + setLoading(false); + } + } + + // Figure out discount + let discount = 0, upto = false; + if (newTier?.prices) { + discount = Math.round(((newTier.prices.month*12/newTier.prices.year)-1)*100); + } else { + let n = 0; + for (const t of tiers) { + if (t.prices) { + const tierDiscount = Math.round(((t.prices.month*12/t.prices.year)-1)*100); + if (tierDiscount > discount) { + discount = tierDiscount; + n++; + } + } + } + upto = n > 1; + } + + return ( + + +
+
{t("account_upgrade_dialog_title")}
+
+ {t("account_upgrade_dialog_interval_monthly")} + setInterval(ev.target.checked ? SubscriptionInterval.YEAR : SubscriptionInterval.MONTH)} + /> + {t("account_upgrade_dialog_interval_yearly")} + {discount > 0 && + + } +
+
+
+ +
+ {tiers.map(tier => + setNewTierCode(tier.code)} // tier.code may be undefined! + /> + )} +
+ {banner === Banner.CANCEL_WARNING && + + + + } + {banner === Banner.PRORATION_INFO && + + + + } + {banner === Banner.RESERVATIONS_WARNING && + + , + }} + /> + + } +
+ + + {config.billing_contact.indexOf('@') !== -1 && + <> }}/>{" "} + } + {config.billing_contact.match(`^http?s://`) && + <> }}/>{" "} + } + {error} + + + + + + +
+ ); +}; + +const TierCard = (props) => { + const { t } = useTranslation(); + const tier = props.tier; + + let cardStyle, labelStyle, labelText; + if (props.selected) { + cardStyle = { background: "#eee", border: "3px solid #338574" }; + labelStyle = { background: "#338574", color: "white" }; + labelText = t("account_upgrade_dialog_tier_selected_label"); + } else if (props.current) { + cardStyle = { border: "3px solid #eee" }; + labelStyle = { background: "#eee", color: "black" }; + labelText = t("account_upgrade_dialog_tier_current_label"); + } else { + cardStyle = { border: "3px solid transparent" }; + } + + let monthlyPrice; + if (!tier.prices) { + monthlyPrice = 0; + } else if (props.interval === SubscriptionInterval.YEAR) { + monthlyPrice = tier.prices.year/12; + } else if (props.interval === SubscriptionInterval.MONTH) { + monthlyPrice = tier.prices.month; + } + + return ( + + + + + {labelStyle && +
{labelText}
+ } + + {tier.name || t("account_basics_tier_free")} + +
+ {formatPrice(monthlyPrice)} + {monthlyPrice > 0 && <>/ {t("account_upgrade_dialog_tier_price_per_month")}} +
+ + {tier.limits.reservations > 0 && {t("account_upgrade_dialog_tier_features_reservations", { reservations: tier.limits.reservations })}} + {tier.limits.reservations === 0 && {t("account_upgrade_dialog_tier_features_no_reservations")}} + {t("account_upgrade_dialog_tier_features_messages", { messages: formatNumber(tier.limits.messages) })} + {t("account_upgrade_dialog_tier_features_emails", { emails: formatNumber(tier.limits.emails) })} + {t("account_upgrade_dialog_tier_features_attachment_file_size", { filesize: formatBytes(tier.limits.attachment_file_size, 0) })} + {t("account_upgrade_dialog_tier_features_attachment_total_size", { totalsize: formatBytes(tier.limits.attachment_total_size, 0) })} + + {tier.prices && props.interval === SubscriptionInterval.MONTH && + + {t("account_upgrade_dialog_tier_price_billed_monthly", { price: formatPrice(tier.prices.month*12) })} + + } + {tier.prices && props.interval === SubscriptionInterval.YEAR && + + {t("account_upgrade_dialog_tier_price_billed_yearly", { price: formatPrice(tier.prices.year), save: formatPrice(tier.prices.month*12-tier.prices.year) })} + + } +
+
+
+
+ + ); +} + +const Feature = (props) => { + return {props.children}; +} + +const NoFeature = (props) => { + return {props.children}; +} + +const FeatureItem = (props) => { + return ( + + + {props.feature && } + {!props.feature && } + + + {props.children} + + } + /> + + + ); +}; + +const Action = { + REDIRECT_SIGNUP: 1, + CREATE_SUBSCRIPTION: 2, + UPDATE_SUBSCRIPTION: 3, + CANCEL_SUBSCRIPTION: 4 +}; + +const Banner = { + CANCEL_WARNING: 1, + PRORATION_INFO: 2, + RESERVATIONS_WARNING: 3 +}; + +export default UpgradeDialog; diff --git a/web/src/components/UpgradeDialog.jsx b/web/src/components/UpgradeDialog.jsx deleted file mode 100644 index a554f1f..0000000 --- a/web/src/components/UpgradeDialog.jsx +++ /dev/null @@ -1,435 +0,0 @@ -import * as React from "react"; -import { useContext, useEffect, useState } from "react"; -import { - Dialog, - DialogContent, - DialogTitle, - Alert, - CardActionArea, - CardContent, - Chip, - Link, - ListItem, - Switch, - useMediaQuery, - Button, - Card, - Typography, - List, - ListItemIcon, - ListItemText, - Box, - DialogContentText, - DialogActions, -} from "@mui/material"; -import { Trans, useTranslation } from "react-i18next"; -import { Check, Close } from "@mui/icons-material"; -import { NavLink } from "react-router-dom"; -import { UnauthorizedError } from "../app/errors"; -import { formatBytes, formatNumber, formatPrice, formatShortDate } from "../app/utils"; -import { AccountContext } from "./App"; -import routes from "./routes"; -import session from "../app/Session"; -import accountApi, { SubscriptionInterval } from "../app/AccountApi"; -import theme from "./theme"; - -const Feature = (props) => {props.children}; - -const NoFeature = (props) => {props.children}; - -const FeatureItem = (props) => ( - - - {props.feature && } - {!props.feature && } - - {props.children}} /> - -); - -const Action = { - REDIRECT_SIGNUP: 1, - CREATE_SUBSCRIPTION: 2, - UPDATE_SUBSCRIPTION: 3, - CANCEL_SUBSCRIPTION: 4, -}; - -const Banner = { - CANCEL_WARNING: 1, - PRORATION_INFO: 2, - RESERVATIONS_WARNING: 3, -}; - -const UpgradeDialog = (props) => { - const { t } = useTranslation(); - const { account } = useContext(AccountContext); // May be undefined! - const [error, setError] = useState(""); - const [tiers, setTiers] = useState(null); - const [interval, setInterval] = useState(account?.billing?.interval || SubscriptionInterval.YEAR); - const [newTierCode, setNewTierCode] = useState(account?.tier?.code); // May be undefined - const [loading, setLoading] = useState(false); - const fullScreen = useMediaQuery(theme.breakpoints.down("sm")); - - useEffect(() => { - const fetchTiers = async () => { - setTiers(await accountApi.billingTiers()); - }; - fetchTiers(); // Dangle - }, []); - - if (!tiers) { - return <>; - } - - const tiersMap = Object.assign(...tiers.map((tier) => ({ [tier.code]: tier }))); - const newTier = tiersMap[newTierCode]; // May be undefined - const currentTier = account?.tier; // May be undefined - const currentInterval = account?.billing?.interval; // May be undefined - const currentTierCode = currentTier?.code; // May be undefined - - // Figure out buttons, labels and the submit action - let submitAction; - let submitButtonLabel; - let banner; - if (!account) { - submitButtonLabel = t("account_upgrade_dialog_button_redirect_signup"); - submitAction = Action.REDIRECT_SIGNUP; - banner = null; - } else if (currentTierCode === newTierCode && (currentInterval === undefined || currentInterval === interval)) { - submitButtonLabel = t("account_upgrade_dialog_button_update_subscription"); - submitAction = null; - banner = currentTierCode ? Banner.PRORATION_INFO : null; - } else if (!currentTierCode) { - submitButtonLabel = t("account_upgrade_dialog_button_pay_now"); - submitAction = Action.CREATE_SUBSCRIPTION; - banner = null; - } else if (!newTierCode) { - submitButtonLabel = t("account_upgrade_dialog_button_cancel_subscription"); - submitAction = Action.CANCEL_SUBSCRIPTION; - banner = Banner.CANCEL_WARNING; - } else { - submitButtonLabel = t("account_upgrade_dialog_button_update_subscription"); - submitAction = Action.UPDATE_SUBSCRIPTION; - banner = Banner.PRORATION_INFO; - } - - // Exceptional conditions - if (loading) { - submitAction = null; - } else if (newTier?.code && account?.reservations?.length > newTier?.limits?.reservations) { - submitAction = null; - banner = Banner.RESERVATIONS_WARNING; - } - - const handleSubmit = async () => { - if (submitAction === Action.REDIRECT_SIGNUP) { - window.location.href = routes.signup; - return; - } - try { - setLoading(true); - if (submitAction === Action.CREATE_SUBSCRIPTION) { - const response = await accountApi.createBillingSubscription(newTierCode, interval); - window.location.href = response.redirect_url; - } else if (submitAction === Action.UPDATE_SUBSCRIPTION) { - await accountApi.updateBillingSubscription(newTierCode, interval); - } else if (submitAction === Action.CANCEL_SUBSCRIPTION) { - await accountApi.deleteBillingSubscription(); - } - props.onCancel(); - } catch (e) { - console.log(`[UpgradeDialog] Error changing billing subscription`, e); - if (e instanceof UnauthorizedError) { - session.resetAndRedirect(routes.login); - } else { - setError(e.message); - } - } finally { - setLoading(false); - } - }; - - // Figure out discount - let discount = 0; - let upto = false; - if (newTier?.prices) { - discount = Math.round(((newTier.prices.month * 12) / newTier.prices.year - 1) * 100); - } else { - let n = 0; - for (const tier of tiers) { - if (tier.prices) { - const tierDiscount = Math.round(((tier.prices.month * 12) / tier.prices.year - 1) * 100); - if (tierDiscount > discount) { - discount = tierDiscount; - n += 1; - } - } - } - upto = n > 1; - } - - return ( - - -
-
{t("account_upgrade_dialog_title")}
-
- - {t("account_upgrade_dialog_interval_monthly")} - - setInterval(ev.target.checked ? SubscriptionInterval.YEAR : SubscriptionInterval.MONTH)} - /> - - {t("account_upgrade_dialog_interval_yearly")} - - {discount > 0 && ( - - )} -
-
-
- -
- {tiers.map((tier) => ( - setNewTierCode(tier.code)} // tier.code may be undefined! - /> - ))} -
- {banner === Banner.CANCEL_WARNING && ( - - - - )} - {banner === Banner.PRORATION_INFO && ( - - - - )} - {banner === Banner.RESERVATIONS_WARNING && ( - - , - }} - /> - - )} -
- - - {config.billing_contact.indexOf("@") !== -1 && ( - <> - , - }} - />{" "} - - )} - {config.billing_contact.match(`^http?s://`) && ( - <> - , - }} - />{" "} - - )} - {error} - - - - - - -
- ); -}; - -const TierCard = (props) => { - const { t } = useTranslation(); - const { tier } = props; - - let cardStyle; - let labelStyle; - let labelText; - if (props.selected) { - cardStyle = { background: "#eee", border: "3px solid #338574" }; - labelStyle = { background: "#338574", color: "white" }; - labelText = t("account_upgrade_dialog_tier_selected_label"); - } else if (props.current) { - cardStyle = { border: "3px solid #eee" }; - labelStyle = { background: "#eee", color: "black" }; - labelText = t("account_upgrade_dialog_tier_current_label"); - } else { - cardStyle = { border: "3px solid transparent" }; - } - - let monthlyPrice; - if (!tier.prices) { - monthlyPrice = 0; - } else if (props.interval === SubscriptionInterval.YEAR) { - monthlyPrice = tier.prices.year / 12; - } else if (props.interval === SubscriptionInterval.MONTH) { - monthlyPrice = tier.prices.month; - } - - return ( - - - - - {labelStyle && ( -
- {labelText} -
- )} - - {tier.name || t("account_basics_tier_free")} - -
- - {formatPrice(monthlyPrice)} - - {monthlyPrice > 0 && <>/ {t("account_upgrade_dialog_tier_price_per_month")}} -
- - {tier.limits.reservations > 0 && ( - - {t("account_upgrade_dialog_tier_features_reservations", { - reservations: tier.limits.reservations, - count: tier.limits.reservations, - })} - - )} - - {t("account_upgrade_dialog_tier_features_messages", { - messages: formatNumber(tier.limits.messages), - count: tier.limits.messages, - })} - - - {t("account_upgrade_dialog_tier_features_emails", { - emails: formatNumber(tier.limits.emails), - count: tier.limits.emails, - })} - - {tier.limits.calls > 0 && ( - - {t("account_upgrade_dialog_tier_features_calls", { - calls: formatNumber(tier.limits.calls), - count: tier.limits.calls, - })} - - )} - - {t("account_upgrade_dialog_tier_features_attachment_file_size", { - filesize: formatBytes(tier.limits.attachment_file_size, 0), - })} - - {tier.limits.reservations === 0 && {t("account_upgrade_dialog_tier_features_no_reservations")}} - {tier.limits.calls === 0 && {t("account_upgrade_dialog_tier_features_no_calls")}} - - {tier.prices && props.interval === SubscriptionInterval.MONTH && ( - - {t("account_upgrade_dialog_tier_price_billed_monthly", { - price: formatPrice(tier.prices.month * 12), - })} - - )} - {tier.prices && props.interval === SubscriptionInterval.YEAR && ( - - {t("account_upgrade_dialog_tier_price_billed_yearly", { - price: formatPrice(tier.prices.year), - save: formatPrice(tier.prices.month * 12 - tier.prices.year), - })} - - )} -
-
-
-
- ); -}; - -export default UpgradeDialog; diff --git a/web/src/components/hooks.js b/web/src/components/hooks.js index 6b68188..b1ce8ff 100644 --- a/web/src/components/hooks.js +++ b/web/src/components/hooks.js @@ -1,7 +1,7 @@ -import { useNavigate, useParams } from "react-router-dom"; -import { useEffect, useState } from "react"; +import {useNavigate, useParams} from "react-router-dom"; +import {useEffect, useState} from "react"; import subscriptionManager from "../app/SubscriptionManager"; -import { disallowedTopic, expandSecureUrl, topicUrl } from "../app/utils"; +import {disallowedTopic, expandSecureUrl, topicUrl} from "../app/utils"; import notifier from "../app/Notifier"; import routes from "./routes"; import connectionManager from "../app/ConnectionManager"; @@ -9,7 +9,7 @@ import poller from "../app/Poller"; import pruner from "../app/Pruner"; import session from "../app/Session"; import accountApi from "../app/AccountApi"; -import { UnauthorizedError } from "../app/errors"; +import {UnauthorizedError} from "../app/errors"; /** * Wire connectionManager and subscriptionManager so that subscriptions are updated when the connection @@ -17,75 +17,65 @@ import { UnauthorizedError } from "../app/errors"; * to the connection being re-established). */ export const useConnectionListeners = (account, subscriptions, users) => { - const navigate = useNavigate(); + const navigate = useNavigate(); - // Register listeners for incoming messages, and connection state changes - useEffect( - () => { - const handleInternalMessage = async (message) => { - console.log(`[ConnectionListener] Received message on sync topic`, message.message); - try { - const data = JSON.parse(message.message); - if (data.event === "sync") { - console.log(`[ConnectionListener] Triggering account sync`); - await accountApi.sync(); - } else { - console.log(`[ConnectionListener] Unknown message type. Doing nothing.`); - } - } catch (e) { - console.log(`[ConnectionListener] Error parsing sync topic message`, e); + // Register listeners for incoming messages, and connection state changes + useEffect(() => { + const handleMessage = async (subscriptionId, message) => { + const subscription = await subscriptionManager.get(subscriptionId); + if (subscription.internal) { + await handleInternalMessage(message); + } else { + await handleNotification(subscriptionId, message); + } + }; + + const handleInternalMessage = async (message) => { + console.log(`[ConnectionListener] Received message on sync topic`, message.message); + try { + const data = JSON.parse(message.message); + if (data.event === "sync") { + console.log(`[ConnectionListener] Triggering account sync`); + await accountApi.sync(); + } else { + console.log(`[ConnectionListener] Unknown message type. Doing nothing.`); + } + } catch (e) { + console.log(`[ConnectionListener] Error parsing sync topic message`, e); + } + }; + + const handleNotification = async (subscriptionId, notification) => { + const added = await subscriptionManager.addNotification(subscriptionId, notification); + if (added) { + const defaultClickAction = (subscription) => navigate(routes.forSubscription(subscription)); + await notifier.notify(subscriptionId, notification, defaultClickAction) + } + }; + connectionManager.registerStateListener(subscriptionManager.updateState); + connectionManager.registerMessageListener(handleMessage); + return () => { + connectionManager.resetStateListener(); + connectionManager.resetMessageListener(); + } + }, + // We have to disable dep checking for "navigate". This is fine, it never changes. + // eslint-disable-next-line + [] + ); + + // Sync topic listener: For accounts with sync_topic, subscribe to an internal topic + useEffect(() => { + if (!account || !account.sync_topic) { + return; } - }; + subscriptionManager.add(config.base_url, account.sync_topic, true); // Dangle! + }, [account]); - const handleNotification = async (subscriptionId, notification) => { - const added = await subscriptionManager.addNotification(subscriptionId, notification); - if (added) { - const defaultClickAction = (subscription) => navigate(routes.forSubscription(subscription)); - await notifier.notify(subscriptionId, notification, defaultClickAction); - } - }; - - const handleMessage = async (subscriptionId, message) => { - const subscription = await subscriptionManager.get(subscriptionId); - - // Race condition: sometimes the subscription is already unsubscribed from account - // sync before the message is handled - if (!subscription) { - return; - } - - if (subscription.internal) { - await handleInternalMessage(message); - } else { - await handleNotification(subscriptionId, message); - } - }; - - connectionManager.registerStateListener(subscriptionManager.updateState); - connectionManager.registerMessageListener(handleMessage); - - return () => { - connectionManager.resetStateListener(); - connectionManager.resetMessageListener(); - }; - }, - // We have to disable dep checking for "navigate". This is fine, it never changes. - - [] - ); - - // Sync topic listener: For accounts with sync_topic, subscribe to an internal topic - useEffect(() => { - if (!account || !account.sync_topic) { - return; - } - subscriptionManager.add(config.base_url, account.sync_topic, true); // Dangle! - }, [account]); - - // When subscriptions or users change, refresh the connections - useEffect(() => { - connectionManager.refresh(subscriptions, users); // Dangle - }, [subscriptions, users]); + // When subscriptions or users change, refresh the connections + useEffect(() => { + connectionManager.refresh(subscriptions, users); // Dangle + }, [subscriptions, users]); }; /** @@ -93,35 +83,35 @@ export const useConnectionListeners = (account, subscriptions, users) => { * This will only be run once after the initial page load. */ export const useAutoSubscribe = (subscriptions, selected) => { - const [hasRun, setHasRun] = useState(false); - const params = useParams(); + const [hasRun, setHasRun] = useState(false); + const params = useParams(); - useEffect(() => { - const loaded = subscriptions !== null && subscriptions !== undefined; - if (!loaded || hasRun) { - return; - } - setHasRun(true); - const eligible = params.topic && !selected && !disallowedTopic(params.topic); - if (eligible) { - const baseUrl = params.baseUrl ? expandSecureUrl(params.baseUrl) : config.base_url; - console.log(`[Hooks] Auto-subscribing to ${topicUrl(baseUrl, params.topic)}`); - (async () => { - const subscription = await subscriptionManager.add(baseUrl, params.topic); - if (session.exists()) { - try { - await accountApi.addSubscription(baseUrl, params.topic); - } catch (e) { - console.log(`[Hooks] Auto-subscribing failed`, e); - if (e instanceof UnauthorizedError) { - session.resetAndRedirect(routes.login); - } - } + useEffect(() => { + const loaded = subscriptions !== null && subscriptions !== undefined; + if (!loaded || hasRun) { + return; } - poller.pollInBackground(subscription); // Dangle! - })(); - } - }, [params, subscriptions, selected, hasRun]); + setHasRun(true); + const eligible = params.topic && !selected && !disallowedTopic(params.topic); + if (eligible) { + const baseUrl = (params.baseUrl) ? expandSecureUrl(params.baseUrl) : config.base_url; + console.log(`[Hooks] Auto-subscribing to ${topicUrl(baseUrl, params.topic)}`); + (async () => { + const subscription = await subscriptionManager.add(baseUrl, params.topic); + if (session.exists()) { + try { + await accountApi.addSubscription(baseUrl, params.topic); + } catch (e) { + console.log(`[Hooks] Auto-subscribing failed`, e); + if (e instanceof UnauthorizedError) { + session.resetAndRedirect(routes.login); + } + } + } + poller.pollInBackground(subscription); // Dangle! + })(); + } + }, [params, subscriptions, selected, hasRun]); }; /** @@ -130,19 +120,19 @@ export const useAutoSubscribe = (subscriptions, selected) => { * up "unused" imports. See https://github.com/binwiederhier/ntfy/issues/186. */ export const useBackgroundProcesses = () => { - useEffect(() => { - poller.startWorker(); - pruner.startWorker(); - accountApi.startWorker(); - }, []); -}; + useEffect(() => { + poller.startWorker(); + pruner.startWorker(); + accountApi.startWorker(); + }, []); +} export const useAccountListener = (setAccount) => { - useEffect(() => { - accountApi.registerListener(setAccount); - accountApi.sync(); // Dangle - return () => { - accountApi.resetListener(); - }; - }, []); -}; + useEffect(() => { + accountApi.registerListener(setAccount); + accountApi.sync(); // Dangle + return () => { + accountApi.resetListener(); + } + }, []); +} diff --git a/web/src/components/i18n.js b/web/src/components/i18n.js new file mode 100644 index 0000000..42eb572 --- /dev/null +++ b/web/src/components/i18n.js @@ -0,0 +1,29 @@ +import i18n from 'i18next'; +import Backend from 'i18next-http-backend'; +import LanguageDetector from 'i18next-browser-languagedetector'; +import { initReactI18next } from 'react-i18next'; + +// Translations using i18next +// - Options: https://www.i18next.com/overview/configuration-options +// - Browser Language Detector: https://github.com/i18next/i18next-browser-languageDetector +// - HTTP Backend (load files via fetch): https://github.com/i18next/i18next-http-backend +// +// See example project here: +// https://github.com/i18next/react-i18next/tree/master/example/react + +i18n + .use(Backend) + .use(LanguageDetector) + .use(initReactI18next) + .init({ + fallbackLng: 'en', + debug: true, + interpolation: { + escapeValue: false, // not needed for react as it escapes by default + }, + backend: { + loadPath: '/static/langs/{{lng}}.json', + } + }); + +export default i18n; diff --git a/web/src/components/i18n.jsx b/web/src/components/i18n.jsx deleted file mode 100644 index 2bc315c..0000000 --- a/web/src/components/i18n.jsx +++ /dev/null @@ -1,29 +0,0 @@ -import i18n from "i18next"; -import Backend from "i18next-http-backend"; -import LanguageDetector from "i18next-browser-languagedetector"; -import { initReactI18next } from "react-i18next"; - -// Translations using i18next -// - Options: https://www.i18next.com/overview/configuration-options -// - Browser Language Detector: https://github.com/i18next/i18next-browser-languageDetector -// - HTTP Backend (load files via fetch): https://github.com/i18next/i18next-http-backend -// -// See example project here: -// https://github.com/i18next/react-i18next/tree/master/example/react - -i18n - .use(Backend) - .use(LanguageDetector) - .use(initReactI18next) - .init({ - fallbackLng: "en", - debug: true, - interpolation: { - escapeValue: false, // not needed for react as it escapes by default - }, - backend: { - loadPath: "/static/langs/{{lng}}.json", - }, - }); - -export default i18n; diff --git a/web/src/components/routes.js b/web/src/components/routes.js index 17e0eac..d1db160 100644 --- a/web/src/components/routes.js +++ b/web/src/components/routes.js @@ -1,20 +1,20 @@ import config from "../app/config"; -import { shortUrl } from "../app/utils"; +import {shortUrl} from "../app/utils"; const routes = { - login: "/login", - signup: "/signup", - app: config.app_root, - account: "/account", - settings: "/settings", - subscription: "/:topic", - subscriptionExternal: "/:baseUrl/:topic", - forSubscription: (subscription) => { - if (subscription.baseUrl !== config.base_url) { - return `/${shortUrl(subscription.baseUrl)}/${subscription.topic}`; + login: "/login", + signup: "/signup", + app: config.app_root, + account: "/account", + settings: "/settings", + subscription: "/:topic", + subscriptionExternal: "/:baseUrl/:topic", + forSubscription: (subscription) => { + if (subscription.baseUrl !== config.base_url) { + return `/${shortUrl(subscription.baseUrl)}/${subscription.topic}`; + } + return `/${subscription.topic}`; } - return `/${subscription.topic}`; - }, }; export default routes; diff --git a/web/src/components/styles.js b/web/src/components/styles.js index edcfb46..d612794 100644 --- a/web/src/components/styles.js +++ b/web/src/components/styles.js @@ -1,5 +1,7 @@ -import { Typography, Container, Backdrop, styled } from "@mui/material"; +import Typography from "@mui/material/Typography"; import theme from "./theme"; +import Container from "@mui/material/Container"; +import {Backdrop, styled} from "@mui/material"; export const Paragraph = styled(Typography)({ paddingTop: 8, @@ -7,14 +9,14 @@ export const Paragraph = styled(Typography)({ }); export const VerticallyCenteredContainer = styled(Container)({ - display: "flex", + display: 'flex', flexGrow: 1, - flexDirection: "column", - justifyContent: "center", - alignContent: "center", - color: theme.palette.text.primary, + flexDirection: 'column', + justifyContent: 'center', + alignContent: 'center', + color: theme.palette.text.primary }); export const LightboxBackdrop = styled(Backdrop)({ - backgroundColor: "rgba(0, 0, 0, 0.8)", // was: 0.5 + backgroundColor: 'rgba(0, 0, 0, 0.8)' // was: 0.5 }); diff --git a/web/src/components/theme.js b/web/src/components/theme.js index ca77cdc..3fdafae 100644 --- a/web/src/components/theme.js +++ b/web/src/components/theme.js @@ -1,13 +1,13 @@ -import { red } from "@mui/material/colors"; -import { createTheme } from "@mui/material/styles"; +import { red } from '@mui/material/colors'; +import { createTheme } from '@mui/material/styles'; const theme = createTheme({ palette: { primary: { - main: "#338574", + main: '#338574', }, secondary: { - main: "#6cead0", + main: '#6cead0', }, error: { main: red.A400, @@ -17,19 +17,19 @@ const theme = createTheme({ MuiListItemIcon: { styleOverrides: { root: { - minWidth: "36px", + minWidth: '36px', }, }, }, MuiCardContent: { styleOverrides: { root: { - ":last-child": { - paddingBottom: "16px", - }, - }, - }, - }, + ':last-child': { + paddingBottom: '16px' + } + } + } + } }, }); diff --git a/web/src/index.js b/web/src/index.js new file mode 100644 index 0000000..659bcb8 --- /dev/null +++ b/web/src/index.js @@ -0,0 +1,6 @@ +import * as React from 'react'; +import { createRoot } from 'react-dom/client'; +import App from './components/App'; + +const root = createRoot(document.querySelector('#root')); +root.render(); diff --git a/web/src/index.jsx b/web/src/index.jsx deleted file mode 100644 index d60c05a..0000000 --- a/web/src/index.jsx +++ /dev/null @@ -1,6 +0,0 @@ -import * as React from "react"; -import { createRoot } from "react-dom/client"; -import App from "./components/App"; - -const root = createRoot(document.querySelector("#root")); -root.render(); diff --git a/web/vite.config.js b/web/vite.config.js deleted file mode 100644 index ffc80ab..0000000 --- a/web/vite.config.js +++ /dev/null @@ -1,14 +0,0 @@ -/* eslint-disable import/no-extraneous-dependencies */ -import { defineConfig } from "vite"; -import react from "@vitejs/plugin-react"; - -export default defineConfig(() => ({ - build: { - outDir: "build", - assetsDir: "static/media", - }, - server: { - port: 3000, - }, - plugins: [react()], -}));