diff --git a/.claude/skills/api-v2-routes/SKILL.md b/.claude/skills/api-v2-routes/SKILL.md new file mode 100644 index 000000000..2644b4338 --- /dev/null +++ b/.claude/skills/api-v2-routes/SKILL.md @@ -0,0 +1,186 @@ +--- +name: api-v2-routes +description: Use when adding or changing a resource on the Huma-backed /api/v2 API (new endpoints, porting a v1 resource, editing pkg/routes/api/v2/). Covers per-operation Huma handlers, the shared envelopes, error/auth bridging, REST verb conventions, and what's automatic. +user-invocable: true +--- + +# Adding /api/v2 routes for a CRUDable resource + +`/api/v2` is served by [Huma v2](https://github.com/danielgtaylor/huma) mounted on an Echo group via the vendored `pkg/modules/humaecho5` adapter. Unlike v1's generic `WebHandler`, each operation is a typed Huma handler registered explicitly. The handlers are thin: they pull auth off the context, call the same `pkg/web/handler.Do*` functions v1 uses, and translate domain errors into RFC 9457 responses. + +**Reference implementation:** `pkg/routes/api/v2/labels.go` is the canonical example — copy its shape. Shared envelopes live in `pkg/routes/api/v2/types.go`; the auth/error bridge in `pkg/routes/api/v2/errors.go`; config in `pkg/routes/api/v2/huma.go`. + +## Prerequisite: the model must be CRUDable + +v2 handlers call `handler.DoReadAll/DoReadOne/DoCreate/DoUpdate/DoDelete`, which invoke the model's `Can*` methods. If the model isn't already a working v1 resource, do the model work first — invoke the **`crudable`** skill. Permissions are enforced at the model level; **never** re-check them in a v2 handler. + +**Every exposed model field needs a `doc:` tag.** v2's schema is reflected from struct tags at runtime; Huma cannot read the Go doc comments swaggo uses for v1. A field without `doc:"..."` ships with no description in the spec. Add the tag alongside the existing comment (keep both — swaggo still reads the comment for v1, and they should stay in sync): + +```go +// The title of the label. You'll see this one on tasks associated with it. +Title string `json:"title" minLength:"1" maxLength:"250" doc:"The title of the label. You'll see this one on tasks associated with it."` +``` + +These model edits are safe for v1 — swaggo, XORM, and govalidator all ignore the `doc` tag. (Huma *does* read validation tags like `minLength`/`maxLength`/`enum`/`format`, so those carry over without a `doc` tag.) As with operations, a `doc` tag earns its place when it says something the field name and type don't: a format hint ("hex, 6 chars"), a read-only note ("set by the server; ignored on write"), units, or allowed values. "The label description." on a `Description` field is filler. See `pkg/models/label.go` for the reference. + +**Mark server-controlled fields `readOnly:"true"`.** Because the same model struct is the request body *and* the response, fields the client can never set — `id`, `created`, `updated`, `created_by`, and similar server-derived relations/IDs — should carry `readOnly:"true"`. Huma reflects this into the OpenAPI schema (`readOnly: true`), so docs and client generators present the field as response-only and drop it from request examples: + +```go +ID int64 `json:"id" readOnly:"true" doc:"The unique, numeric id of this label."` +CreatedBy *user.User `xorm:"-" json:"created_by" readOnly:"true" doc:"The user who created this label."` +Created time.Time `xorm:"created not null" json:"created" readOnly:"true" doc:"A timestamp when this label was created. You cannot change this value."` +``` + +The tag is **documentation only** — Huma does *not* reject these fields if a client sends them on create/update. Actual immutability still comes from the model layer (XORM-managed `created`/`updated`, `created_by` being `xorm:"-"` and set server-side). It's also harmless on v1 (swaggo/XORM/govalidator ignore it). Don't bother tagging fields that are already `json:"-"` (absent from the schema entirely), and skip it on response-only structs like the error model — there it's cosmetic since they never appear as a request body. See `pkg/models/label.go` and `pkg/user/user.go`. + +## Steps + +### 1. Create `pkg/routes/api/v2/.go` + +Define the list-response body, a `RegisterRoutes(api huma.API)` function, and one handler per operation. Mirror `labels.go` exactly: + +```go +// Element type matches what models..ReadAll returns; extra fields +// tagged json:"-" keep the wire shape identical to the plain model. +type fooListBody struct { + Body Paginated[*models.Foo] +} + +func RegisterFooRoutes(api huma.API) { + tags := []string{"foos"} + + Register(api, huma.Operation{ + OperationID: "foos-list", + Summary: "List foos", + Description: "Returns the foos the authenticated user has access to, paginated.", + Method: http.MethodGet, Path: "/foos", Tags: tags, + }, foosList) + Register(api, huma.Operation{OperationID: "foos-read", Summary: "Get a foo", Description: "...", Method: http.MethodGet, Path: "/foos/{id}", Tags: tags}, foosRead) + Register(api, huma.Operation{OperationID: "foos-create", Summary: "Create a foo", Description: "...", Method: http.MethodPost, Path: "/foos", Tags: tags}, foosCreate) + Register(api, huma.Operation{OperationID: "foos-update", Summary: "Update a foo", Description: "...", Method: http.MethodPut, Path: "/foos/{id}", Tags: tags}, foosUpdate) + Register(api, huma.Operation{OperationID: "foos-delete", Summary: "Delete a foo", Description: "...", Method: http.MethodDelete, Path: "/foos/{id}", Tags: tags}, foosDelete) +} +``` + +Use the package's `Register` wrapper, **not** `huma.Register` directly — it sets `DefaultStatus` from the verb (POST → 201, DELETE → 204). Don't spell out `DefaultStatus` unless you need a non-default code. Don't set `Security:` per operation — it's applied globally in `NewAPI`. + +**Every operation needs a `Summary` and `Description`.** v2's OpenAPI spec is generated from these `Operation` fields at runtime — unlike v1's swaggo, Huma cannot read Go doc comments, so anything you don't put in the `Operation` (or in a `doc:` tag, see below) is simply absent from the spec and the docs UI. An operation without them ships undocumented. + +**Make the description document the non-obvious — don't restate the verb+noun.** "Deletes a label" adds nothing over `DELETE /labels/{id}`. Spend the description on what a consumer *can't* infer from the method/path/schema: permission scope ("only the owner may delete it"; "returns only labels you can see, not a global list"), full-replace vs partial (PUT replaces, PATCH merges), read-only/conditional behavior (ETag → `If-None-Match` → 304), side effects (create sets ownership), non-obvious status codes. If the honest description is just the verb+noun, a short summary alone is fine — don't pad. See `labels.go` for the calibration. + +### 2. Write the handlers + +Every handler: pull auth with `authFromCtx(ctx)`, call the matching `handler.Do*`, wrap returned errors in `translateDomainError`. Use the shared envelopes from `types.go` (`singleBody`, `singleReadBody`, `emptyBody`, `ListParams`, `Paginated`/`NewPaginated`). + +- **List** takes `*ListParams` (gives you `page`/`per_page`/`q` for free, already `doc:`-tagged in `types.go` — no need to re-document them) and returns `*fooListBody`. **You must type-assert the `DoReadAll` result to the concrete slice** — `result` is `any`, and a blind cast or a generic wrapper silently serialises `[]` (the "generic-any silent-empty trap"). Return a hard error on mismatch: + ```go + items, ok := result.([]*models.Foo) + if !ok { + return nil, fmt.Errorf("foos.ReadAll returned unexpected type %T", result) + } + return &fooListBody{Body: NewPaginated(items, total, in.Page, in.PerPage)}, nil + ``` +- **Extra query params go *directly* on the handler's input struct — not in a shared/embedded helper.** Beyond `ListParams`, if an operation needs its own query params (`expand`, `order_by`, `include_public`, …), declare each as a direct field with its own `query:"…"` tag on that operation's input struct, then bind it onto the model. A shared or embedded struct of query fields silently **fails to bind** under Huma when combined with other query params/embeds — the field arrives empty (hit while implementing Project's `expand`). Flatten them into the input struct. +- **Read** embeds `conditional.Params` in its input. To surface the caller's permission, define a small per-resource response struct that **embeds the model by value** and adds the permission: `type fooReadBody struct { models.Foo; MaxPermission models.Permission \`json:"max_permission" readOnly:"true" doc:"..."\` }`. Go and Huma both promote the embedded model's fields, so the wire shape is flat (model fields + `max_permission`) with no custom marshaler and nothing added to the shared model struct. Capture `DoReadOne`'s returned max permission (it is `0`/`1`/`2` on success — **never discard it as `_`**), build the body, and `return conditionalReadResponse(&in.Params, body, foo.Updated, maxPermission)`. The shared helper (in `types.go`) folds the permission into the ETag (so a share/role change invalidates the cache), applies the conditional precondition (304/412), and returns `*singleReadBody[fooReadBody]`. See `labels.go`/`project_views.go`. (A generic `struct{ T; ... }` is impossible — Go forbids embedding a type parameter — so the per-resource struct is the price of a flat shape without a marshaler.) +- **Create / Update** return `*singleBody[Model]` and set the model's `ID` from the path (URL wins over body). **Update's request body must be the same `fooReadBody` the read returns, not the bare model** — AutoPatch's GET→PUT round trip echoes the read body (max_permission included) into the PUT, and because `max_permission` is a declared `readOnly` property of `fooReadBody`'s schema, Huma accepts and ignores it on write rather than rejecting it. Take `&in.Body.Foo` (the embedded model — value-embedded, so never nil) and ignore the embedded `MaxPermission`. Create stays a bare `Body Model` (AutoPatch only round-trips into PUT). +- **Delete** returns `*emptyBody`. + +### 3. Self-register the resource + +Resources self-register — **you do not edit `pkg/routes/routes.go`**. In your resource file, add an `init()` that hands your registrar to `AddRouteRegistrar`: + +```go +func init() { AddRouteRegistrar(RegisterFooRoutes) } + +func RegisterFooRoutes(api huma.API) { ... } +``` + +`registerAPIRoutesV2` in `routes.go` calls `apiv2.RegisterAll(api)`, which runs every registered registrar (in init/filename order — route order is irrelevant) and then `EnableAutoPatch`. New resources touch zero shared lines, so they never conflict on `routes.go`. + +Notes: + +- **Give each registrar a DISTINCT name.** They share package `apiv2`, so two resources both exporting `RegisterAvatarRoutes` collide and won't compile — that actually happened and the upload one had to be renamed (`RegisterAvatarRoutes` for the binary endpoint vs `RegisterAvatarUploadRoutes` for the upload). Name yours after the specific resource. +- **Config-gated resources check the flag inside the registrar.** `RegisterAll` runs at request-router-setup time, after config is loaded, so a `RegisterFooRoutes` may early-return (or skip individual `Register` calls) based on `config.FooEnabled.GetBool()`. Don't try to gate at `init()` time — config isn't loaded yet. +- **AutoPatch is automatic.** `RegisterAll` calls `EnableAutoPatch` after all registrars — don't call it yourself, and don't register a manual PATCH (see "What's automatic"). + +## REST verb conventions (v2 inverts v1) + +| Operation | v1 | v2 | +|---|---|---| +| create | PUT | **POST** | +| update | POST | **PUT** (and PATCH) | +| read / read-all / delete | GET / GET / DELETE | same | + +## Non-CRUDable / custom routes + +Not everything is plain CRUD — bulk operations, custom actions (`POST /tasks/{id}/duplicate`), sub-resource toggles, RPC-ish endpoints. These still go through Huma and reuse most of the machinery, but two responsibilities move **into your handler** because there's no `handler.Do*` doing them for you: + +1. **Permission enforcement is now yours.** This is the one place the "never check permissions in the handler" rule inverts. With no generic `Do*` to call the model's `Can*`, the handler must do it explicitly — load the relevant entity and call its permission method, then refuse on denial. Mirror the v1 custom-handler shape (`pkg/routes/api/v1/task_attachment.go`): + ```go + func tasksDuplicate(ctx context.Context, in *struct{ ID int64 `path:"id"` }) (*singleBody[models.Task], error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + s := db.NewSession() + defer s.Close() + + t := &models.Task{ID: in.ID} + can, err := t.CanUpdate(s, a) // or whichever Can* gates this action + if err != nil { + _ = s.Rollback() + return nil, translateDomainError(err) + } + if !can { + return nil, huma.Error403Forbidden("forbidden") + } + // ... do the work against s ... + if err := s.Commit(); err != nil { + return nil, translateDomainError(err) + } + return &singleBody[models.Task]{Body: t}, nil + } + ``` +2. **Session / transaction management is now yours.** The `Do*` helpers open and commit their own `xorm.Session`; custom handlers open one with `db.NewSession()`, `defer s.Close()`, and `Commit`/`Rollback` explicitly for anything that writes. + +Otherwise the same rules apply: register with the `Register` wrapper, pull auth via `authFromCtx`, route every error through `translateDomainError`, and reuse the `types.go` envelopes — or define a small body struct when none fits (don't bend a custom response into `singleBody` if it's awkward). + +**Verb choice:** pick by semantics, not the CRUD table. Non-idempotent actions are `POST`. AutoPatch only synthesises PATCH for GET+PUT *pairs*, so standalone custom routes are never touched. + +**Token permissions still automatic, but mind the derived name:** `collectRoutesForAPITokens` keys a route off its prefix-stripped path, so `POST /api/v2/tasks/{id}/duplicate` lands under the `tasks` group as a `duplicate` permission. Single-segment custom paths fall into the `other` group. Name the path so the derived `(group, permission)` reads sensibly — that string is what users grant tokens against. + +## What's automatic — do NOT hand-roll + +- **PATCH** — `EnableAutoPatch` synthesises a JSON-Merge-Patch PATCH for every GET+PUT pair. `RegisterAll` invokes it after all registrars, so it's automatic — don't call `EnableAutoPatch` and don't register PATCH yourself. +- **API token permissions** — `collectRoutesForAPITokens` walks the Echo router after registration, so your new routes land in the v2 token table automatically under the same `(group, permission)` keys as their v1 names. PATCH is intentionally not stored; `CanDoAPIRoute` accepts it as an alias for the stored PUT (see `pkg/models/api_routes.go`). +- **Security schemes** — `JWTKeyAuth` + `APITokenAuth` are declared globally in `NewAPI`. For a public endpoint, set `Security: []map[string][]string{}` on that operation and add its path to `unauthenticatedAPIPaths` in `routes.go`. +- **Error shape** — `translateDomainError` maps any `web.HTTPErrorProcessor` (e.g. `ErrFooDoesNotExist`) onto Huma's status error, producing RFC 9457 `application/problem+json`. Errors without HTTP semantics become 500. +- **OpenAPI spec / Scalar docs / `$schema` URLs** — handled in `huma.go`. Leave `Servers` alone (the relative entry must stay at index 0). + +## Anti-patterns (these get flagged) + +- Re-checking permissions in the handler instead of trusting `handler.Do*` → the model's `Can*`. +- Blind `result.([]*models.Foo)` without the `ok` check, or returning the `any` straight into the envelope — silent empty lists. +- `huma.Register` instead of the package `Register` wrapper (loses the verb-based status). +- Per-operation `Security:` lines (now global) or registering a manual PATCH (AutoPatch does it). +- Returning a raw model error instead of routing it through `translateDomainError` → leaks a 500 instead of the right code. +- Unquoted ETag in the response header. +- Operations without `Summary`/`Description`, or model fields without `doc:` tags — they ship undocumented because Huma can't read Go comments. +- Server-controlled fields (`id`, `created`, `updated`, `created_by`) on a shared input/output model left without `readOnly:"true"` — the docs then present them as writable request fields. + +## Tests (mandatory) + +Mirror the v1 webtest shape so v2 parity is readable side-by-side. Use the `webHandlerTestV2` harness in `pkg/webtests/integrations.go` — it takes the same `urlParams` map as v1's `webHandlerTest`. See `pkg/webtests/huma_label_test.go`: + +- One `Test` covering list/read/create/update/delete, positive + negative (forbidden, nonexistent), mirroring the v1 model test. +- v2-only behaviour (ETag/304, PATCH merge-patch) goes in separate top-level `Test_*` funcs using the `humaRequest`/`humaTokenFor` helpers in `pkg/webtests/huma_helpers_test.go`. +- The RFC 9457 error-body shape is asserted **once** globally in `TestHuma_ErrorShapeIsRFC9457` — don't re-assert the full problem+json shape per resource, just the status code. + +Run with `mage test:filter Test` while iterating. **Caveat:** `mage test:filter` injects `-short`, which makes `pkg/webtests` skip entirely (the suite short-circuits in short mode), so it silently reports success without running your webtest. To actually exercise a single webtest, run it directly: `go test -run '' ./pkg/webtests/`. Save output to a file per the project test-output rule. + +## Related + +- `crudable` skill — the model-layer prerequisite +- `pkg/routes/api/v2/labels.go` — reference resource +- `pkg/routes/api/v2/{types,errors,huma}.go` — shared envelopes, bridge, config +- `pkg/web/handler/core.go` — the `Do*` functions handlers call diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml new file mode 100644 index 000000000..fb012bfb4 --- /dev/null +++ b/.github/actionlint.yaml @@ -0,0 +1,6 @@ +self-hosted-runner: + # Custom labels from third-party runner providers used in our workflows. + # Listed here so actionlint doesn't flag them as unknown. + labels: + - namespace-profile-default + - blacksmith-8vcpu-ubuntu-2204 diff --git a/.github/actions/release-binaries/action.yml b/.github/actions/release-binaries/action.yml new file mode 100644 index 000000000..d75335ccd --- /dev/null +++ b/.github/actions/release-binaries/action.yml @@ -0,0 +1,189 @@ +name: Release binaries +description: | + Build, sign, and publish release binaries for a Vikunja sub-project. + + Derives every per-project path, cache key, artifact name, and S3 target + from the `project` input. Callers only need to provide the project name, + the raw `git describe` value, and pass through the GPG/S3 secrets as + inputs (composite actions can't read the `secrets` context directly). + +inputs: + project: + description: 'Which project to build: "vikunja" or "veans".' + required: true + release-version: + description: | + Raw git describe value (e.g. v1.2.3 or v2.3.0-408-ge053d317). Always + passed through to the build so the binary embeds the precise commit. + Filenames and the S3 directory use "unstable" instead whenever + github.ref_type isn't "tag". + required: true + # Secrets — composite actions can't read the `secrets` context directly, so + # the caller threads them through as inputs. + gpg-passphrase: + required: true + gpg-sign-key: + required: true + s3-access-key-id: + required: true + s3-secret-access-key: + required: true + s3-endpoint: + required: true + s3-bucket: + required: true + s3-region: + required: true + +runs: + using: composite + steps: + - name: Set project paths + shell: bash + env: + PROJECT: ${{ inputs.project }} + RELEASE_VERSION_INPUT: ${{ inputs.release-version }} + VERSION_OR_UNSTABLE: ${{ github.ref_type == 'tag' && inputs.release-version || 'unstable' }} + run: | + set -euo pipefail + + case "$PROJECT" in + vikunja|veans) ;; + *) + echo "::error::Unknown project '$PROJECT'. Expected 'vikunja' or 'veans'." >&2 + exit 1 + ;; + esac + + case "$PROJECT" in + vikunja) + output_dir="." + dist_prefix="dist" + ;; + veans) + output_dir="veans" + dist_prefix="veans/dist" + ;; + esac + + { + echo "PROJECT=$PROJECT" + echo "RELEASE_VERSION=$RELEASE_VERSION_INPUT" + echo "VERSION_OR_UNSTABLE=$VERSION_OR_UNSTABLE" + echo "XGO_OUT_NAME=${PROJECT}-${VERSION_OR_UNSTABLE}" + echo "OUTPUT_DIR=$output_dir" + echo "DIST_PREFIX=$dist_prefix" + echo "S3_TARGET_PATH=/${PROJECT}/${VERSION_OR_UNSTABLE}" + echo "ARTIFACT_BINARIES_NAME=${PROJECT}_bins" + echo "ARTIFACT_ZIPS_NAME=${PROJECT}_bin_packages" + } >> "$GITHUB_ENV" + + - name: Download Mage binary + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7 + with: + name: mage_bin + + - name: Make mage-static executable + shell: bash + run: chmod +x ./mage-static + + - name: Download frontend dist (vikunja only) + if: inputs.project == 'vikunja' + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7 + with: + name: frontend_dist + path: frontend/dist + + - name: Generate config.yml.sample (vikunja only) + if: inputs.project == 'vikunja' + shell: bash + run: ./mage-static generate:config-yaml 1 + + - name: Install upx + shell: bash + run: | + set -euo pipefail + wget -q https://github.com/upx/upx/releases/download/v5.0.0/upx-5.0.0-amd64_linux.tar.xz + echo 'b32abf118d721358a50f1aa60eacdbf3298df379c431c3a86f139173ab8289a1 upx-5.0.0-amd64_linux.tar.xz' > upx-5.0.0-amd64_linux.tar.xz.sha256 + sha256sum -c upx-5.0.0-amd64_linux.tar.xz.sha256 + tar xf upx-5.0.0-amd64_linux.tar.xz + sudo mv upx-5.0.0-amd64_linux/upx /usr/local/bin + + - name: Setup xgo cache + uses: useblacksmith/cache@71c7c918062ba3861252d84b07fe5ab2a6b467a6 # v5 + with: + path: /home/runner/.xgo-cache + key: xgo-${{ inputs.project }}-${{ hashFiles('**/go.sum') }} + restore-keys: | + xgo-${{ inputs.project }}- + + - name: Install mage for the build module + shell: bash + run: go install github.com/magefile/mage@v1.17.2 + + - name: Build release artifacts + shell: bash + env: + RELEASE_VERSION: ${{ env.RELEASE_VERSION }} + XGO_OUT_NAME: ${{ env.XGO_OUT_NAME }} + PROJECT: ${{ env.PROJECT }} + run: | + set -euo pipefail + export PATH="$PATH:$(go env GOPATH)/bin" + cd build && mage release:build "$PROJECT" + + - name: GPG setup + uses: kolaente/action-gpg@main + with: + gpg-passphrase: ${{ inputs.gpg-passphrase }} + gpg-sign-key: ${{ inputs.gpg-sign-key }} + + - name: Sign zips + shell: bash + env: + DIST_PREFIX: ${{ env.DIST_PREFIX }} + RELEASE_GPG_PASSPHRASE: ${{ inputs.gpg-passphrase }} + run: | + set -euo pipefail + zip_dir="${DIST_PREFIX}/zip" + echo "=== GPG agent status ===" + gpg-connect-agent 'keyinfo --list' /bye || true + echo "=== GPG secret keys ===" + gpg -K --with-keygrip + echo "=== GPG public keys ===" + gpg --list-keys + echo "=== Signing files in $zip_dir ===" + ls -hal "$zip_dir"/* + for file in "$zip_dir"/*; do + gpg -v \ + --default-key 7D061A4AA61436B40713D42EFF054DACD908493A \ + -b --batch --yes \ + --passphrase "$RELEASE_GPG_PASSPHRASE" \ + --pinentry-mode loopback \ + --sign "$file" + done + + - name: Upload zips to S3 + uses: kolaente/s3-action@main + with: + s3-access-key-id: ${{ inputs.s3-access-key-id }} + s3-secret-access-key: ${{ inputs.s3-secret-access-key }} + s3-endpoint: ${{ inputs.s3-endpoint }} + s3-bucket: ${{ inputs.s3-bucket }} + s3-region: ${{ inputs.s3-region }} + target-path: ${{ env.S3_TARGET_PATH }} + files: ${{ env.DIST_PREFIX }}/zip/* + strip-path-prefix: ${{ env.DIST_PREFIX }}/zip/ + + - name: Store binaries + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: ${{ env.ARTIFACT_BINARIES_NAME }} + path: ./${{ env.DIST_PREFIX }}/binaries/* + + - name: Store binary packages + if: github.ref_type == 'tag' + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: ${{ env.ARTIFACT_ZIPS_NAME }} + path: ./${{ env.DIST_PREFIX }}/zip/* diff --git a/.github/actions/release-os-package/action.yml b/.github/actions/release-os-package/action.yml new file mode 100644 index 000000000..8203babcc --- /dev/null +++ b/.github/actions/release-os-package/action.yml @@ -0,0 +1,204 @@ +name: Release OS package +description: > + Build a single deb/rpm/apk/archlinux package for the given project + arch + via nfpm, optionally GPG-sign it (archlinux is signed inline; rpm is signed + by nfpm itself), upload it to S3, and store it as a workflow artifact. + + Most paths and names are derived from `project`; the matrix only needs to + supply the per-arch and per-format inputs. + +inputs: + project: + description: 'Project name (vikunja | veans). Drives all derived paths.' + required: true + release-version: + description: | + RELEASE_VERSION env value — the same version that ended up in the + binaries artifact. Always embedded in the package metadata via + nfpm; filenames and the S3 directory use "unstable" instead + whenever github.ref_type isn't "tag". + required: true + packager: + description: 'nfpm packager: rpm | deb | apk | archlinux.' + required: true + nfpm-arch: + description: 'nfpm arch field (amd64 | arm64 | arm7).' + required: true + pkg-arch: + description: 'Package-format arch used in the output filename (x86_64 | aarch64 | armv7).' + required: true + go-name: + description: 'Go-style arch token used in the binary filename (linux-amd64 | linux-arm64 | linux-arm-7).' + required: true + # Secrets — composite actions can't read `${{ secrets.* }}` directly, so the + # caller threads them through as inputs. + gpg-passphrase: + required: true + gpg-sign-key: + required: true + s3-access-key-id: + required: true + s3-secret-access-key: + required: true + s3-endpoint: + required: true + s3-bucket: + required: true + s3-region: + required: true + +runs: + using: composite + steps: + - name: Set project paths + shell: bash + env: + PROJECT: ${{ inputs.project }} + RELEASE_VERSION: ${{ inputs.release-version }} + VERSION_OR_UNSTABLE: ${{ github.ref_type == 'tag' && inputs.release-version || 'unstable' }} + PACKAGER: ${{ inputs.packager }} + PKG_ARCH: ${{ inputs.pkg-arch }} + GO_NAME: ${{ inputs.go-name }} + run: | + case "$PROJECT" in + vikunja) + echo "BINARIES_DOWNLOAD_PATH=." >> "$GITHUB_ENV" + echo "STAGED_BINARY_PATH=./vikunja" >> "$GITHUB_ENV" + echo "NFPM_BIN_PATH=" >> "$GITHUB_ENV" + echo "NFPM_CONFIG_PATH=./nfpm.yaml" >> "$GITHUB_ENV" + # No leading "./" — the s3-action's strip-path-prefix must + # match the glob output exactly, and the glob doesn't emit it. + echo "PACKAGE_OUTPUT_DIR=dist/os-packages" >> "$GITHUB_ENV" + ;; + veans) + echo "BINARIES_DOWNLOAD_PATH=./veans-binaries" >> "$GITHUB_ENV" + echo "STAGED_BINARY_PATH=./veans/veans-bin" >> "$GITHUB_ENV" + echo "NFPM_BIN_PATH=./veans/veans-bin" >> "$GITHUB_ENV" + echo "NFPM_CONFIG_PATH=./veans/nfpm.yaml" >> "$GITHUB_ENV" + echo "PACKAGE_OUTPUT_DIR=veans/dist/os-packages" >> "$GITHUB_ENV" + ;; + *) + echo "::error::unknown project '$PROJECT' (expected vikunja|veans)" + exit 1 + ;; + esac + + echo "VERSION_OR_UNSTABLE=$VERSION_OR_UNSTABLE" >> "$GITHUB_ENV" + echo "BINARIES_ARTIFACT_NAME=${PROJECT}_bins" >> "$GITHUB_ENV" + echo "BINARY_GLOB=${PROJECT}-*-${GO_NAME}" >> "$GITHUB_ENV" + echo "PACKAGE_FILENAME=${PROJECT}-${VERSION_OR_UNSTABLE}-${PKG_ARCH}.${PACKAGER}" >> "$GITHUB_ENV" + echo "ARTIFACT_NAME=${PROJECT}_os_package_${PACKAGER}_${PKG_ARCH}" >> "$GITHUB_ENV" + echo "S3_TARGET_PATH=/${PROJECT}/${VERSION_OR_UNSTABLE}" >> "$GITHUB_ENV" + + - name: Download project binaries + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7 + with: + name: ${{ env.BINARIES_ARTIFACT_NAME }} + path: ${{ env.BINARIES_DOWNLOAD_PATH }} + + - uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6 + with: + go-version: stable + + - name: Install mage + shell: bash + run: go install github.com/magefile/mage@v1.17.2 + + - name: Generate config.yml.sample (vikunja only) + # vikunja's nfpm.yaml ships ./config.yml.sample as /etc/vikunja/config.yml. + # release-binaries generates it for the zip bundles, but this job runs on a + # fresh runner, so we regenerate it here before nfpm packs it. + if: inputs.project == 'vikunja' + shell: bash + run: | + export PATH=$PATH:$GOPATH/bin + mage generate:config-yaml 1 + + - name: Write GPG key for nfpm + if: inputs.packager == 'rpm' + shell: bash + env: + RELEASE_GPG_SIGN_KEY: ${{ inputs.gpg-sign-key }} + run: printf '%s' "$RELEASE_GPG_SIGN_KEY" > /tmp/nfpm-signing-key.gpg + + - name: GPG setup for archlinux signing + if: inputs.packager == 'archlinux' + uses: kolaente/action-gpg@main + with: + gpg-passphrase: ${{ inputs.gpg-passphrase }} + gpg-sign-key: ${{ inputs.gpg-sign-key }} + + - name: Prepare nfpm config + shell: bash + working-directory: build + env: + RELEASE_VERSION: ${{ inputs.release-version }} + NFPM_ARCH: ${{ inputs.nfpm-arch }} + NFPM_BIN_PATH: ${{ env.NFPM_BIN_PATH }} + PROJECT: ${{ inputs.project }} + run: | + export PATH=$PATH:$GOPATH/bin + mage release:prepare-nfpm-config "$PROJECT" "$NFPM_ARCH" + + - name: Stage binary + shell: bash + run: | + # Resolve the single matching binary and mv it into place. + matched=() + for f in $BINARIES_DOWNLOAD_PATH/$BINARY_GLOB; do + [ -e "$f" ] || continue + matched+=("$f") + done + if [ ${#matched[@]} -ne 1 ]; then + echo "::error::expected exactly 1 binary matching '$BINARIES_DOWNLOAD_PATH/$BINARY_GLOB', found ${#matched[@]}" + ls -la "$BINARIES_DOWNLOAD_PATH" || true + exit 1 + fi + mkdir -p "$(dirname "$STAGED_BINARY_PATH")" + mv "${matched[0]}" "$STAGED_BINARY_PATH" + chmod +x "$STAGED_BINARY_PATH" + + - name: Ensure package output dir exists + shell: bash + run: mkdir -p "$PACKAGE_OUTPUT_DIR" + + - name: Create package + uses: kolaente/action-gh-nfpm@master + with: + packager: ${{ inputs.packager }} + target: ${{ env.PACKAGE_OUTPUT_DIR }}/${{ env.PACKAGE_FILENAME }} + config: ${{ env.NFPM_CONFIG_PATH }} + env: + NFPM_GPG_KEY_FILE: ${{ inputs.packager == 'rpm' && '/tmp/nfpm-signing-key.gpg' || '' }} + NFPM_PASSPHRASE: ${{ inputs.packager == 'rpm' && inputs.gpg-passphrase || '' }} + + - name: Sign archlinux package + if: inputs.packager == 'archlinux' + shell: bash + env: + GPG_PASSPHRASE: ${{ inputs.gpg-passphrase }} + run: | + gpg --default-key 7D061A4AA61436B40713D42EFF054DACD908493A \ + --batch --yes \ + --passphrase "$GPG_PASSPHRASE" \ + --pinentry-mode loopback \ + --detach-sign \ + "$PACKAGE_OUTPUT_DIR/$PACKAGE_FILENAME" + + - name: Upload to S3 + uses: kolaente/s3-action@main + with: + s3-access-key-id: ${{ inputs.s3-access-key-id }} + s3-secret-access-key: ${{ inputs.s3-secret-access-key }} + s3-endpoint: ${{ inputs.s3-endpoint }} + s3-bucket: ${{ inputs.s3-bucket }} + s3-region: ${{ inputs.s3-region }} + target-path: ${{ env.S3_TARGET_PATH }} + files: ${{ env.PACKAGE_OUTPUT_DIR }}/* + strip-path-prefix: ${{ env.PACKAGE_OUTPUT_DIR }}/ + + - name: Store OS package + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: ${{ env.ARTIFACT_NAME }} + path: ${{ env.PACKAGE_OUTPUT_DIR }}/* diff --git a/.github/workflows/crowdin.yml b/.github/workflows/crowdin.yml index b364de00d..93d5bbf71 100644 --- a/.github/workflows/crowdin.yml +++ b/.github/workflows/crowdin.yml @@ -41,7 +41,7 @@ jobs: - name: Check for changes id: check_changes run: | - if git diff --quiet; then + if [ -z "$(git status --porcelain pkg/i18n/lang frontend/src/i18n/lang)" ]; then echo "changes_exist=0" >> "$GITHUB_OUTPUT" else echo "changes_exist=1" >> "$GITHUB_OUTPUT" @@ -51,7 +51,8 @@ jobs: run: | git config --local user.email "bot@vikunja.io" git config --local user.name "Frederick [Bot]" - git commit -am "chore(i18n): update translations via Crowdin" + git add pkg/i18n/lang frontend/src/i18n/lang + git commit -m "chore(i18n): update translations via Crowdin" - name: Push changes if: steps.check_changes.outputs.changes_exist != '0' uses: ad-m/github-push-action@master diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d86576881..910be41fd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,6 +4,40 @@ on: workflow_call: jobs: + build-mage: + runs-on: ubuntu-latest + name: prepare-build-mage + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 + - name: Set up Go + uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6 + with: + go-version: stable + - name: Cache build mage + id: cache-build-mage + uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5 + with: + key: ${{ runner.os }}-build-mage-build-${{ hashFiles('build/magefile.go') }} + path: | + ./build/build-mage-static + # Statically compile build/magefile.go so publish-repos can run repo + # metadata targets inside ubuntu/fedora/archlinux containers without + # needing a Go toolchain available there. + - name: Install mage + if: ${{ steps.cache-build-mage.outputs.cache-hit != 'true' }} + run: go install github.com/magefile/mage@v1.17.2 + - name: Compile build mage + if: ${{ steps.cache-build-mage.outputs.cache-hit != 'true' }} + working-directory: build + run: | + export PATH=$PATH:$GOPATH/bin + mage -compile ./build-mage-static + - name: Store build mage binary + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: build_mage_bin + path: ./build/build-mage-static + docker: runs-on: namespace-profile-default steps: @@ -63,83 +97,36 @@ jobs: - name: Git describe id: ghd uses: proudust/gh-describe@v2 - - uses: useblacksmith/setup-go@647ac649bd5b480f2a262e3e3e5f4d150ed452ad # v6 - with: - go-version: stable - - name: Download Mage Binary - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7 - with: - name: mage_bin - - name: get frontend - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7 - with: - name: frontend_dist - path: frontend/dist - - run: chmod +x ./mage-static - - name: install upx - run: | - wget https://github.com/upx/upx/releases/download/v5.0.0/upx-5.0.0-amd64_linux.tar.xz - echo 'b32abf118d721358a50f1aa60eacdbf3298df379c431c3a86f139173ab8289a1 upx-5.0.0-amd64_linux.tar.xz' > upx-5.0.0-amd64_linux.tar.xz.sha256 - sha256sum -c upx-5.0.0-amd64_linux.tar.xz.sha256 - tar xf upx-5.0.0-amd64_linux.tar.xz - mv upx-5.0.0-amd64_linux/upx /usr/local/bin - - name: setup xgo cache - uses: useblacksmith/cache@71c7c918062ba3861252d84b07fe5ab2a6b467a6 # v5 - with: - path: /home/runner/.xgo-cache - key: ${{ hashFiles('**/go.sum') }} - restore-keys: | - ${{ runner.os }}-go- - - name: build and release - env: - RELEASE_VERSION: ${{ steps.ghd.outputs.describe }} - XGO_OUT_NAME: vikunja-${{ github.ref_type == 'tag' && steps.ghd.outputs.describe || 'unstable' }} - run: | - export PATH=$PATH:$GOPATH/bin - ./mage-static release - - name: GPG setup - uses: kolaente/action-gpg@main - with: - gpg-passphrase: "${{ secrets.RELEASE_GPG_PASSPHRASE }}" - gpg-sign-key: "${{ secrets.RELEASE_GPG_SIGN_KEY }}" - - name: sign - run: | - echo "=== GPG agent status ===" - gpg-connect-agent 'keyinfo --list' /bye || true - echo "=== GPG secret keys ===" - gpg -K --with-keygrip - echo "=== GPG public keys ===" - gpg --list-keys - echo "=== GNUPG directory contents ===" - ls -la ~/.gnupg/ - ls -la ~/.gnupg/private-keys-v1.d/ || true - echo "=== Signing files ===" - ls -hal dist/zip/* - for file in dist/zip/*; do - gpg -v --default-key 7D061A4AA61436B40713D42EFF054DACD908493A -b --batch --yes --passphrase "${{ secrets.RELEASE_GPG_PASSPHRASE }}" --pinentry-mode loopback --sign "$file" - done - - name: Upload - uses: kolaente/s3-action@main + - uses: ./.github/actions/release-binaries with: + project: vikunja + release-version: ${{ steps.ghd.outputs.describe }} + gpg-passphrase: ${{ secrets.RELEASE_GPG_PASSPHRASE }} + gpg-sign-key: ${{ secrets.RELEASE_GPG_SIGN_KEY }} s3-access-key-id: ${{ secrets.S3_ACCESS_KEY }} s3-secret-access-key: ${{ secrets.S3_SECRET_KEY }} s3-endpoint: ${{ secrets.S3_ENDPOINT }} s3-bucket: ${{ secrets.S3_BUCKET }} s3-region: ${{ secrets.S3_REGION }} - target-path: /vikunja/${{ github.ref_type == 'tag' && steps.ghd.outputs.describe || 'unstable' }} - files: "dist/zip/*" - strip-path-prefix: dist/zip/ - - name: Store Binaries - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + + veans-binaries: + runs-on: blacksmith-8vcpu-ubuntu-2204 + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 + - name: Git describe + id: ghd + uses: proudust/gh-describe@v2 + - uses: ./.github/actions/release-binaries with: - name: vikunja_bins - path: ./dist/binaries/* - - name: Store Binary Packages - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 - if: ${{ github.ref_type == 'tag' }} - with: - name: vikunja_bin_packages - path: ./dist/zip/* + project: veans + release-version: ${{ steps.ghd.outputs.describe }} + gpg-passphrase: ${{ secrets.RELEASE_GPG_PASSPHRASE }} + gpg-sign-key: ${{ secrets.RELEASE_GPG_SIGN_KEY }} + s3-access-key-id: ${{ secrets.S3_ACCESS_KEY }} + s3-secret-access-key: ${{ secrets.S3_SECRET_KEY }} + s3-endpoint: ${{ secrets.S3_ENDPOINT }} + s3-bucket: ${{ secrets.S3_BUCKET }} + s3-region: ${{ secrets.S3_REGION }} os-package: runs-on: ubuntu-latest @@ -147,11 +134,7 @@ jobs: - binaries strategy: matrix: - package: - - rpm - - deb - - apk - - archlinux + package: [rpm, deb, apk, archlinux] arch: - go_name: linux-amd64 nfpm: amd64 @@ -165,76 +148,70 @@ jobs: steps: - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 - - name: Download Vikunja Binary - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7 - with: - name: vikunja_bins - name: Git describe id: ghd uses: proudust/gh-describe@v2 - - name: Download Mage Binary - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7 - with: - name: mage_bin - - name: Write GPG key for nfpm - if: matrix.package == 'rpm' - run: echo -n "${{ secrets.RELEASE_GPG_SIGN_KEY }}" > /tmp/nfpm-signing-key.gpg - - name: GPG setup for package signing - if: matrix.package == 'archlinux' - uses: kolaente/action-gpg@main - with: - gpg-passphrase: "${{ secrets.RELEASE_GPG_PASSPHRASE }}" - gpg-sign-key: "${{ secrets.RELEASE_GPG_SIGN_KEY }}" - - name: Prepare - env: - RELEASE_VERSION: ${{ steps.ghd.outputs.describe }} - NFPM_ARCH: ${{ matrix.arch.nfpm }} - run: | - chmod +x ./mage-static - ./mage-static release:prepare-nfpm-config - mkdir -p ./dist/os-packages - mv ./vikunja-*-${{ matrix.arch.go_name }} ./vikunja - chmod +x ./vikunja - - name: Create package - id: nfpm - uses: kolaente/action-gh-nfpm@master + - uses: ./.github/actions/release-os-package with: + project: vikunja + release-version: ${{ steps.ghd.outputs.describe }} packager: ${{ matrix.package }} - target: ./dist/os-packages/vikunja-${{ github.ref_type == 'tag' && steps.ghd.outputs.describe || 'unstable' }}-${{ matrix.arch.pkg }}.${{ matrix.package }} - config: ./nfpm.yaml - env: - NFPM_GPG_KEY_FILE: ${{ (matrix.package == 'rpm') && '/tmp/nfpm-signing-key.gpg' || '' }} - NFPM_PASSPHRASE: ${{ (matrix.package == 'rpm') && secrets.RELEASE_GPG_PASSPHRASE || '' }} - - name: Sign package - if: matrix.package == 'archlinux' - run: | - gpg --default-key 7D061A4AA61436B40713D42EFF054DACD908493A \ - --batch --yes \ - --passphrase "${{ secrets.RELEASE_GPG_PASSPHRASE }}" \ - --pinentry-mode loopback \ - --detach-sign \ - ./dist/os-packages/vikunja-${{ github.ref_type == 'tag' && steps.ghd.outputs.describe || 'unstable' }}-${{ matrix.arch.pkg }}.${{ matrix.package }} - - name: Upload - uses: kolaente/s3-action@main - with: + nfpm-arch: ${{ matrix.arch.nfpm }} + pkg-arch: ${{ matrix.arch.pkg }} + go-name: ${{ matrix.arch.go_name }} + gpg-passphrase: ${{ secrets.RELEASE_GPG_PASSPHRASE }} + gpg-sign-key: ${{ secrets.RELEASE_GPG_SIGN_KEY }} s3-access-key-id: ${{ secrets.S3_ACCESS_KEY }} s3-secret-access-key: ${{ secrets.S3_SECRET_KEY }} s3-endpoint: ${{ secrets.S3_ENDPOINT }} s3-bucket: ${{ secrets.S3_BUCKET }} s3-region: ${{ secrets.S3_REGION }} - target-path: /vikunja/${{ github.ref_type == 'tag' && steps.ghd.outputs.describe || 'unstable' }} - files: "dist/os-packages/*" - strip-path-prefix: dist/os-packages/ - - name: Store OS Packages - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + + veans-os-package: + runs-on: ubuntu-latest + needs: + - veans-binaries + strategy: + matrix: + package: [rpm, deb, apk, archlinux] + arch: + - go_name: linux-amd64 + nfpm: amd64 + pkg: x86_64 + - go_name: linux-arm64 + nfpm: arm64 + pkg: aarch64 + - go_name: linux-arm-7 + nfpm: arm7 + pkg: armv7 + + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 + - name: Git describe + id: ghd + uses: proudust/gh-describe@v2 + - uses: ./.github/actions/release-os-package with: - name: vikunja_os_package_${{ matrix.package }}_${{ matrix.arch.pkg }} - path: ./dist/os-packages/* + project: veans + release-version: ${{ steps.ghd.outputs.describe }} + packager: ${{ matrix.package }} + nfpm-arch: ${{ matrix.arch.nfpm }} + pkg-arch: ${{ matrix.arch.pkg }} + go-name: ${{ matrix.arch.go_name }} + gpg-passphrase: ${{ secrets.RELEASE_GPG_PASSPHRASE }} + gpg-sign-key: ${{ secrets.RELEASE_GPG_SIGN_KEY }} + s3-access-key-id: ${{ secrets.S3_ACCESS_KEY }} + s3-secret-access-key: ${{ secrets.S3_SECRET_KEY }} + s3-endpoint: ${{ secrets.S3_ENDPOINT }} + s3-bucket: ${{ secrets.S3_BUCKET }} + s3-region: ${{ secrets.S3_REGION }} publish-repos: runs-on: ubuntu-latest needs: + - build-mage - os-package + - veans-os-package - desktop strategy: fail-fast: false @@ -260,10 +237,14 @@ jobs: steps: - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 - - name: Download Mage Binary + - name: Download build mage binary + # Statically compiled in test.yml's build-mage job so it runs inside + # ubuntu/fedora/archlinux containers without a Go toolchain. + if: matrix.format != 'apk' uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7 with: - name: mage_bin + name: build_mage_bin + path: build - name: Download all server OS packages uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7 @@ -272,6 +253,16 @@ jobs: merge-multiple: true path: dist/repo-work/incoming + - name: Download all veans OS packages + # Merged into the same incoming dir so reprepro / createrepo_c / + # repo-add / the apk loop pick them up alongside vikunja's packages + # — same suite, same arch fan-out, no extra source entry for users. + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7 + with: + pattern: veans_os_package_* + merge-multiple: true + path: dist/repo-work/incoming + - name: Download desktop packages (Linux) uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7 with: @@ -338,12 +329,13 @@ jobs: - name: Generate repo metadata if: matrix.format != 'apk' + working-directory: build env: RELEASE_GPG_KEY: 7D061A4AA61436B40713D42EFF054DACD908493A RELEASE_GPG_PASSPHRASE: ${{ secrets.RELEASE_GPG_PASSPHRASE }} run: | - chmod +x ./mage-static - ./mage-static ${{ matrix.mage_target }} + chmod +x ./build-mage-static + ./build-mage-static ${{ matrix.mage_target }} - name: Generate APK repo metadata if: matrix.format == 'apk' @@ -538,6 +530,8 @@ jobs: needs: - binaries - os-package + - veans-binaries + - veans-os-package - desktop - publish-repos if: ${{ github.ref_type == 'tag' }} @@ -555,6 +549,17 @@ jobs: pattern: vikunja_os_package_* merge-multiple: true + - name: Download Veans Binaries + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7 + with: + name: veans_bin_packages + + - name: Download Veans OS Packages + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7 + with: + pattern: veans_os_package_* + merge-multiple: true + - name: Download Desktop Package Linux uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7 with: @@ -581,4 +586,9 @@ jobs: vikunja*.deb vikunja*.apk vikunja*.archlinux + veans*.zip + veans*.rpm + veans*.deb + veans*.apk + veans*.archlinux Vikunja Desktop* diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 557fbb57f..65e0bff12 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -78,6 +78,39 @@ jobs: with: version: v2.10.1 + veans-lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 + - uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6 + with: + go-version: stable + - name: golangci-lint + uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 # v9 + with: + version: v2.10.1 + working-directory: veans + + veans-test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 + - uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6 + with: + go-version: stable + - name: Install mage + # The cached mage-static artifact has the parent magefile compiled + # in — we need a generic mage binary to pick up veans/magefile.go. + run: go install github.com/magefile/mage@v1.17.2 + - name: Run unit tests + # `mage test` is the Aliases entry for Test.All which passes + # `-short` — the e2e package's TestMain skips under -short, + # mirroring the parent monorepo's pkg/webtests convention. The + # heavier test-veans-e2e job runs the full suite against the + # api-build artifact. + working-directory: veans + run: mage test + check-translations: runs-on: ubuntu-latest needs: mage @@ -404,6 +437,76 @@ jobs: name: frontend_dist path: ./frontend/dist + test-veans-e2e: + runs-on: ubuntu-latest + needs: + - api-build + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 + - name: Download Vikunja Binary + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7 + with: + name: vikunja_bin + - name: Set up Go + uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6 + with: + go-version: stable + - name: Install mage + # The cached mage-static artifact has the parent magefile compiled + # in — we need a generic mage binary to pick up veans/magefile.go. + run: go install github.com/magefile/mage@v1.17.2 + - run: chmod +x ./vikunja + - name: Run veans e2e against ephemeral Vikunja + env: + VIKUNJA_SERVICE_INTERFACE: ":3456" + VIKUNJA_SERVICE_PUBLICURL: "http://127.0.0.1:3456/" + VIKUNJA_SERVICE_JWTSECRET: "veans-e2e-jwt-secret-do-not-use-in-production" + # Enables PATCH /api/v1/test/{table} — the e2e suite seeds its + # own admin via this endpoint (see veans/e2e/helpers.go), same + # mechanism the playwright suite uses. + VIKUNJA_SERVICE_TESTINGTOKEN: averyLongSecretToSe33dtheDB + VIKUNJA_DATABASE_TYPE: sqlite + VIKUNJA_DATABASE_PATH: memory + VIKUNJA_LOG_LEVEL: WARNING + VIKUNJA_MAILER_ENABLED: "false" + VIKUNJA_REDIS_ENABLED: "false" + VIKUNJA_RATELIMIT_NOAUTHLIMIT: "1000" + VEANS_E2E_API_URL: http://127.0.0.1:3456 + # Same value as VIKUNJA_SERVICE_TESTINGTOKEN above — pass-through + # so the test harness can authenticate against /api/v1/test/. + VEANS_E2E_TESTING_TOKEN: averyLongSecretToSe33dtheDB + run: | + set -e + # Boot the prebuilt API and tests in one shell — backgrounded + # processes don't survive step boundaries on GH runners. + nohup ./vikunja web > /tmp/vikunja.log 2>&1 & + API_PID=$! + trap "kill $API_PID 2>/dev/null || true" EXIT + for i in $(seq 1 60); do + if curl -sf http://127.0.0.1:3456/api/v1/info >/dev/null 2>&1; then + echo "API ready after ${i}s" + break + fi + sleep 1 + done + if ! curl -sf http://127.0.0.1:3456/api/v1/info >/dev/null; then + echo "::error::API failed to start; log:" + cat /tmp/vikunja.log + exit 1 + fi + # `mage test:e2e` builds the binary once and exports VEANS_BINARY + # so each subtest reuses it (plain `mage test` would rebuild per + # test via buildOrLocate()). The suite seeds its own admin + # internally — no curl seeding here. + (cd veans && mage test:e2e) + - name: Upload API log on failure + if: failure() + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: veans-e2e-vikunja-log + path: /tmp/vikunja.log + retention-days: 7 + test-frontend-e2e-playwright: runs-on: ubuntu-latest needs: diff --git a/.gitignore b/.gitignore index 25d23cac0..469ce5eb9 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ docs/resources/ pkg/static/templates_vfsdata.go files/ !pkg/files/ +!pkg/web/files/ vikunja-dump* vendor/ os-packages/ diff --git a/.golangci.yml b/.golangci.yml index 6f1a759f2..19ee2f531 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -145,6 +145,13 @@ linters: - revive path: pkg/utils/* text: 'var-naming: avoid meaningless package names' + - linters: + - revive + path: pkg/routes/api/shared/* + text: 'var-naming: avoid meaningless package names' + - linters: + - contextcheck + path: pkg/routes/api/v2/backgrounds.go # the unsplash provider intentionally uses context.Background(); its interface is shared with v1 and can't take a context - linters: - revive text: 'var-naming: avoid package names that conflict with Go standard library package names' diff --git a/AGENTS.md b/AGENTS.md index 6cb251b1c..3524dfc60 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -11,12 +11,24 @@ The project consists of: - `desktop/` – Electron wrapper application - `docs/` – Documentation website +## API Version Policy — new work goes to /api/v2 + +**`/api/v1` is effectively deprecated and frozen.** It still runs and is fully supported for existing clients, but it should not grow. + +- **Every new route goes on `/api/v2`** (the Huma-backed API in `pkg/routes/api/v2/`). This includes new CRUDable entities, new custom/non-CRUD endpoints, and new actions on existing resources. +- **Before adding any v2 route, invoke the `api-v2-routes` skill** — it covers both CRUD and non-CRUD shapes. +- **Touch `/api/v1` only to:** fix a bug, or port an existing resource to v2. Do not add net-new functionality there. +- Models in `pkg/models/` are shared by both APIs — a new entity still gets its model + `Can*` methods (invoke `crudable`); only the HTTP surface differs (v2, not v1). + +If a task says "add an endpoint for X" without naming a version, it means v2. + ## Skills Before writing code in these areas, invoke the matching skill with the `Skill` tool. They are short checklists derived from recurring review feedback — loading them up front avoids rework. - Adding or modifying a model in `pkg/models/` (new CRUD, new or changed `Can*` methods, anything touching permissions): invoke `crudable`. - Creating or editing any file under `pkg/migration/`: invoke `migration`. +- Adding **any** new API route (new entity, custom action, or porting from v1) — all new routes go on the Huma-backed `/api/v2`, editing `pkg/routes/api/v2/`: invoke `api-v2-routes`. See the API Version Policy above. ## Plans and Worktrees @@ -172,11 +184,10 @@ Modern Vue 3 composition API application with TypeScript: ### Adding New Features **Backend Changes:** -1. Create/modify models in `pkg/models/` with proper CRUD and Permissions interfaces as required -2. Add database migration if needed: `mage dev:make-migration ` +1. Create/modify models in `pkg/models/` with proper CRUD and Permissions interfaces as required (invoke the `crudable` skill) +2. Add database migration if needed: `mage dev:make-migration ` (invoke the `migration` skill) 3. Create/update services in `pkg/services/` for complex business logic -4. Add API routes in `pkg/routes/api/v1/` following existing patterns -5. Update Swagger annotations +4. Add API routes on **`/api/v2`** in `pkg/routes/api/v2/` — invoke the `api-v2-routes` skill. Do **not** add new routes to `/api/v1`; it is frozen (see API Version Policy above) **Frontend Changes:** 1. Create TypeScript interfaces in `src/modelTypes/` matching backend models @@ -192,10 +203,11 @@ Modern Vue 3 composition API application with TypeScript: 4. Update TypeScript interfaces in frontend `src/modelTypes/` ### API Development -- All API endpoints follow RESTful conventions under `/api/v1/` -- Use generic web handlers in `pkg/web/handler/` for standard CRUD operations -- Implement proper permissions checking using the Permissions interface -- Add Swagger annotations for automatic documentation generation +- **New endpoints go on `/api/v2`** (Huma-backed, `pkg/routes/api/v2/`). `/api/v1` is frozen — see the API Version Policy near the top. Invoke the `api-v2-routes` skill before writing v2 routes. +- v2 verb conventions differ from v1: POST creates, PUT/PATCH update (v1 used PUT to create, POST to update). +- Both versions reuse the generic `pkg/web/handler/` `Do*` functions for standard CRUD, which enforce permissions via the model's `Can*` methods. +- Implement permission checks at the model level via the Permissions interface — never in the route handler (the exception: non-CRUD v2 actions must call `Can*` explicitly; the skill covers this). +- v2 generates its OpenAPI spec from Go types automatically — no Swagger annotations. v1's swaggo annotations stay as-is but no new ones are needed. ### Testing - Backend: Feature tests alongside source files, web tests in `pkg/webtests/` @@ -250,6 +262,8 @@ In the frontend, all translation strings live in `frontend/src/i18n/lang`. For t You only need to adjust the `en.json` file with the source string. The actual translation happens elsewhere. After adjusting the source string, you need to call the respective translation library with the key. Both are similar, check the existing code to figure it out. +**Do not add a new language from scratch or translate strings into other languages yourself.** Translations are managed through a dedicated workflow. If you are asked to add a new language, translate existing strings, or update translations for non-English locales, point the user to the translation guide instead: https://vikunja.io/docs/translations/ + ## Key Files and Conventions **Configuration:** @@ -261,12 +275,13 @@ After adjusting the source string, you need to call the respective translation l - Go: golangci-lint per `.golangci.yml`; use goimports; wrap errors with `fmt.Errorf("...: %w", err)`; enforce permissions checks in models; never log secrets; do not edit generated `pkg/swagger/*` - Vue: ESLint + TS; single quotes, trailing commas, no semicolons, tab indent; script setup + lang ts; keep services/models in sync with backend - Follow existing patterns for consistency +- **Comments: document the *why*, not the *what* — default to no comment.** Don't write comments that restate the code, a function/struct/field name, or a signature; they're noise the reader skips past (a comment that takes longer to read than the code it describes should be deleted). Only comment a genuinely non-obvious *why* — a gotcha, an invariant, a rejected alternative, a cross-file constraint — in one tight line. Be aggressive about cutting on the first pass, not just when asked. - Before creating a new file, function, or helper, search the codebase (`grep` / `rg`) for existing code that does the same thing. Prefer extending an existing helper over duplicating it. If logic overlaps an existing function significantly, reuse it. **Naming Conventions:** - Go: Standard Go conventions (PascalCase for exports, camelCase for private) - Vue: PascalCase for components, camelCase for composables -- API endpoints: kebab-case in URLs, camelCase in JSON +- API endpoints: kebab-case in URLs, snake_case in JSON **Permissions and Permissions:** - Always implement Permissions interface for new models diff --git a/Dockerfile b/Dockerfile index 560303236..ce2e4aec2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -28,7 +28,7 @@ ENV RELEASE_VERSION=$RELEASE_VERSION RUN export PATH=$PATH:$GOPATH/bin && \ mage build:clean && \ - mage release:xgo "${TARGETOS}/${TARGETARCH}/${TARGETVARIANT}" + (cd build && mage release:xgo vikunja "${TARGETOS}/${TARGETARCH}/${TARGETVARIANT}") RUN mkdir -p /tmp && chmod 1777 /tmp @@ -50,7 +50,7 @@ WORKDIR /app/vikunja ENTRYPOINT [ "/app/vikunja/vikunja" ] EXPOSE 3456 -COPY --from=apibuilder --chown=1000:1000 /tmp /tmp +COPY --from=apibuilder --chown=1000:1000 --chmod=1777 /tmp /tmp USER 1000 diff --git a/build/after-install-openrc.sh b/build/after-install-openrc.sh index c0e01cd03..8c3e9a638 100755 --- a/build/after-install-openrc.sh +++ b/build/after-install-openrc.sh @@ -2,7 +2,7 @@ rc-update add vikunja default # Fix the config to contain proper values -NEW_SECRET=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1) +NEW_SECRET=$(head -c 512 /dev/urandom | tr -dc 'a-zA-Z0-9' | head -c 32) sed -i "s//$NEW_SECRET/g" /etc/vikunja/config.yml sed -i "s//\/opt\/vikunja\//g" /etc/vikunja/config.yml sed -i "s/path: \"\.\/vikunja.db\"/path: \"\\/opt\/vikunja\/vikunja.db\"/g" /etc/vikunja/config.yml diff --git a/build/after-install.sh b/build/after-install.sh index a8f2840d7..1b6399a49 100644 --- a/build/after-install.sh +++ b/build/after-install.sh @@ -3,7 +3,7 @@ systemctl enable vikunja.service # Fix the config to contain proper values -NEW_SECRET=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1) +NEW_SECRET=$(head -c 512 /dev/urandom | tr -dc 'a-zA-Z0-9' | head -c 32) sed -i "s//$NEW_SECRET/g" /etc/vikunja/config.yml sed -i "s//\/opt\/vikunja\//g" /etc/vikunja/config.yml sed -i "s/path: \"\.\/vikunja.db\"/path: \"\\/opt\/vikunja\/vikunja.db\"/g" /etc/vikunja/config.yml diff --git a/build/go.mod b/build/go.mod new file mode 100644 index 000000000..17999956b --- /dev/null +++ b/build/go.mod @@ -0,0 +1,5 @@ +module code.vikunja.io/build + +go 1.25.0 + +require github.com/magefile/mage v1.17.2 diff --git a/build/go.sum b/build/go.sum new file mode 100644 index 000000000..a4324314b --- /dev/null +++ b/build/go.sum @@ -0,0 +1,2 @@ +github.com/magefile/mage v1.17.2 h1:fyXVu1eadI8Ap1HCCNgEhJ5McIWiYhLR8uol64ZZc40= +github.com/magefile/mage v1.17.2/go.mod h1:Yj51kqllmsgFpvvSzgrZPK9WtluG3kUhFaBUVLo4feA= diff --git a/build/magefile.go b/build/magefile.go new file mode 100644 index 000000000..d76990d32 --- /dev/null +++ b/build/magefile.go @@ -0,0 +1,757 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +//go:build mage + +// Centralized release pipeline for every Go binary in this monorepo. +// +// Both vikunja and veans cross-compile through the same code: xgo for the full +// OS/arch matrix, upx where the binary supports it, sha256 alongside each +// artifact, per-target zip bundle, and nfpm.yaml templating for deb/rpm/apk/ +// archlinux packaging. Repository-metadata targets (apt/rpm/pacman) consume +// the merged ../dist/repo-work/incoming/ tree the CI populates from both +// projects' packages. +// +// The module is intentionally separate from the project magefiles so the +// release tooling can evolve without touching them. The small filesystem +// helpers (copyFile, moveFile, sha256File) are duplicated rather than +// imported — this magefile depends on nothing but stdlib + mage. +package main + +import ( + "context" + "crypto/sha256" + "fmt" + "io" + "os" + "os/exec" + "path/filepath" + "strings" + "sync" + + "github.com/magefile/mage/mg" + "github.com/magefile/mage/sh" +) + +// ----------------------------------------------------------------------------- +// project definitions + +// project describes one releasable Go binary in this monorepo. Adding a new +// project means adding an entry to projectByName plus a constructor below. +type project struct { + // Name is the short identifier used on the CLI: `mage release:build `. + Name string + // Root is the project root, relative to this build/ directory. + Root string + // BuildPath is the Go package to build, relative to Root (e.g. "." or "./cmd/foo"). + BuildPath string + // Executable is the output binary name (sans -- suffix). + Executable string + // BuildTags are the base build tags applied to every cross-compile. + BuildTags string + // Ldflags returns the full -X flag string for the given version. + Ldflags func(version string) string + // NfpmConfigPath is the nfpm.yaml location, relative to Root. + NfpmConfigPath string + // NfpmBinPathDefault is the default substitution. Empty + // means use the Executable name as-is. + NfpmBinPathDefault string + // OsPackageExtras hook copies any extra files (LICENSE, sample config…) + // into each per-target bundle folder. Called once per binary. + OsPackageExtras func(folder string, p *project) error +} + +func projectByName(name string) (*project, error) { + switch name { + case "vikunja": + return vikunjaProject(), nil + case "veans": + return veansProject(), nil + default: + return nil, fmt.Errorf("unknown project %q (known: vikunja, veans)", name) + } +} + +func vikunjaProject() *project { + return &project{ + Name: "vikunja", + Root: "../", + BuildPath: ".", + Executable: "vikunja", + BuildTags: "osusergo netgo", + Ldflags: func(v string) string { + // Matches the parent magefile's pre-refactor ldflags. The + // main.Tags value is the literal build-tag string baked in + // for `vikunja info` to report. + return fmt.Sprintf(`-X "code.vikunja.io/api/pkg/version.Version=%s" -X "main.Tags=osusergo netgo"`, v) + }, + NfpmConfigPath: "nfpm.yaml", + NfpmBinPathDefault: "vikunja", + OsPackageExtras: func(folder string, p *project) error { + // config.yml.sample must be generated by the CI (or local dev) + // before this runs — we don't want to vendor the + // config-raw.json→YAML logic. The workflow does + // `mage generate:config-yaml 1` in the project root before + // invoking release:build. + if err := copyFile(filepath.Join(p.Root, "config.yml.sample"), filepath.Join(folder, "config.yml.sample")); err != nil { + return fmt.Errorf("copy config.yml.sample (run `mage generate:config-yaml 1` first): %w", err) + } + return copyFile(filepath.Join(p.Root, "LICENSE"), filepath.Join(folder, "LICENSE")) + }, + } +} + +func veansProject() *project { + return &project{ + Name: "veans", + Root: "../veans/", + BuildPath: "./cmd/veans", + Executable: "veans", + BuildTags: "osusergo netgo", + Ldflags: func(v string) string { + return fmt.Sprintf(`-X main.version=%s`, v) + }, + NfpmConfigPath: "nfpm.yaml", + NfpmBinPathDefault: "./veans", + OsPackageExtras: func(folder string, _ *project) error { + // veans intentionally doesn't carry its own LICENSE — the + // AGPLv3 at the repo root applies to both. + return copyFile("../LICENSE", filepath.Join(folder, "LICENSE")) + }, + } +} + +// ----------------------------------------------------------------------------- +// version resolution + +func releaseVersion(ctx context.Context) (string, error) { + if v := os.Getenv("RELEASE_VERSION"); v != "" { + return v, nil + } + out, err := exec.CommandContext(ctx, "git", "describe", "--tags", "--always", "--abbrev=10").Output() + if err != nil { + return "", fmt.Errorf("git describe: %w", err) + } + return strings.Replace(strings.TrimSpace(string(out)), "-g", "-", 1), nil +} + +func versionTagOrUnstable(v string) string { + switch v { + case "", "main": + return "unstable" + default: + return v + } +} + +// ----------------------------------------------------------------------------- +// Release namespace + +type Release mg.Namespace + +// Build runs the full release pipeline for the named project: dirs → xgo +// (windows/linux/darwin in parallel) → upx → copy → sha256 → per-target +// bundle dir → zip. +func (Release) Build(ctx context.Context, name string) error { + p, err := projectByName(name) + if err != nil { + return err + } + version, err := releaseVersion(ctx) + if err != nil { + return err + } + if err := releaseDirs(p); err != nil { + return err + } + if err := prepareXgo(ctx); err != nil { + return err + } + if err := xgoAllOS(ctx, p, version); err != nil { + return err + } + if err := compressBinaries(p); err != nil { + return err + } + if err := copyBinaries(p); err != nil { + return err + } + if err := writeChecksums(p); err != nil { + return err + } + if err := bundleOsPackages(p); err != nil { + return err + } + return zipBundles(ctx, p) +} + +// Xgo cross-compiles a single os/arch[/variant] target for the named project. +// Variant follows the parent magefile convention: `linux/arm/7` → arm-7. +// +// Unlike Release.Build, this skips prepareXgo on purpose: the only caller +// that hits this path in CI is the Dockerfile, which runs inside the xgo +// image (xgo binary already present, docker daemon not available). Local +// users invoking `mage release:xgo` need to install xgo themselves. +func (Release) Xgo(ctx context.Context, name, target string) error { + p, err := projectByName(name) + if err != nil { + return err + } + version, err := releaseVersion(ctx) + if err != nil { + return err + } + parts := strings.Split(target, "/") + if len(parts) < 2 { + return fmt.Errorf("invalid target %q (expected os/arch[/variant])", target) + } + variant := "" + if len(parts) > 2 && parts[2] != "" { + variant = "-" + strings.ReplaceAll(parts[2], "v", "") + } + return runXgo(ctx, p, version, parts[0]+"/"+parts[1]+variant) +} + +// PrepareNFPMConfig templates the named project's nfpm.yaml in place for the +// given nfpm arch (amd64|arm64|arm7|386). Destructive — CI checks out a fresh +// copy per matrix shard so the trampling is fine. +func (Release) PrepareNFPMConfig(ctx context.Context, name, arch string) error { + p, err := projectByName(name) + if err != nil { + return err + } + version, err := releaseVersion(ctx) + if err != nil { + return err + } + cfgPath := filepath.Join(p.Root, p.NfpmConfigPath) + raw, err := os.ReadFile(cfgPath) + if err != nil { + return err + } + binLocation := os.Getenv("NFPM_BIN_PATH") + if binLocation == "" { + binLocation = p.NfpmBinPathDefault + if binLocation == "" { + binLocation = p.Executable + } + } + out := strings.ReplaceAll(string(raw), "", version) + out = strings.ReplaceAll(out, "", arch) + out = strings.ReplaceAll(out, "", binLocation) + return os.WriteFile(cfgPath, []byte(out), 0o600) +} + +// ----------------------------------------------------------------------------- +// Repo-metadata targets — project-agnostic; operate on the merged tree at +// ../dist/repo-work/incoming and ../dist/repo-output. + +// RepoApt generates an APT repository (reprepro) for every .deb in the +// incoming tree. REPO_SUITE (stable|unstable) selects the target suite; +// RELEASE_GPG_KEY + RELEASE_GPG_PASSPHRASE drive the Release file signing. +func (Release) RepoApt(ctx context.Context) error { + suite := repoSuite() + incomingDir := filepath.Join(repoRootDist, "repo-work", "incoming") + outputBase := filepath.Join(repoRootDist, "repo-output", "apt") + confDir := filepath.Join(outputBase, "conf") + if err := os.MkdirAll(confDir, 0o755); err != nil { + return fmt.Errorf("creating reprepro conf dir: %w", err) + } + distConf, err := os.ReadFile("reprepro-dist-conf") + if err != nil { + return fmt.Errorf("reading reprepro-dist-conf: %w", err) + } + if err := os.WriteFile(filepath.Join(confDir, "distributions"), distConf, 0o600); err != nil { + return fmt.Errorf("writing distributions config: %w", err) + } + + debs, err := filepath.Glob(filepath.Join(incomingDir, "*.deb")) + if err != nil { + return err + } + for _, deb := range debs { + abs, _ := filepath.Abs(deb) + if err := sh.RunV("reprepro", "-b", outputBase, "includedeb", suite, abs); err != nil { + return fmt.Errorf("reprepro includedeb %s: %w", filepath.Base(deb), err) + } + } + + gpgKey := os.Getenv("RELEASE_GPG_KEY") + gpgPassphrase := os.Getenv("RELEASE_GPG_PASSPHRASE") + releaseFile := filepath.Join(outputBase, "dists", suite, "Release") + if _, err := os.Stat(releaseFile); err == nil { + if err := sh.RunV("gpg", + "--default-key", gpgKey, + "--batch", "--yes", + "--passphrase", gpgPassphrase, + "--pinentry-mode", "loopback", + "--detach-sign", "--armor", + "-o", releaseFile+".gpg", + releaseFile, + ); err != nil { + return fmt.Errorf("signing Release (detached): %w", err) + } + if err := sh.RunV("gpg", + "--default-key", gpgKey, + "--batch", "--yes", + "--passphrase", gpgPassphrase, + "--pinentry-mode", "loopback", + "--clearsign", + "-o", filepath.Join(filepath.Dir(releaseFile), "InRelease"), + releaseFile, + ); err != nil { + return fmt.Errorf("signing Release (clearsign): %w", err) + } + } + fmt.Println("APT repo metadata generated in", outputBase) + return nil +} + +// RepoRpm generates an RPM repository (createrepo_c) per arch in +// ../dist/repo-work/incoming/. +func (Release) RepoRpm(ctx context.Context) error { + suite := repoSuite() + incomingDir := filepath.Join(repoRootDist, "repo-work", "incoming") + outputBase := filepath.Join(repoRootDist, "repo-output", "rpm", suite) + gpgKey := os.Getenv("RELEASE_GPG_KEY") + gpgPassphrase := os.Getenv("RELEASE_GPG_PASSPHRASE") + + for _, arch := range []string{"x86_64", "aarch64", "armv7"} { + repoDir := filepath.Join(outputBase, arch) + if err := os.MkdirAll(repoDir, 0o755); err != nil { + return err + } + rpms, _ := filepath.Glob(filepath.Join(incomingDir, "*-"+arch+".rpm")) + if len(rpms) == 0 { + continue + } + for _, rpm := range rpms { + abs, _ := filepath.Abs(rpm) + dst := filepath.Join(repoDir, filepath.Base(rpm)) + _ = os.Remove(dst) + if err := os.Symlink(abs, dst); err != nil { + return err + } + } + args := []string{repoDir} + if _, err := os.Stat(filepath.Join(repoDir, "repodata")); err == nil { + args = []string{"--update", repoDir} + } + if err := sh.RunV("createrepo_c", args...); err != nil { + return fmt.Errorf("createrepo_c for %s: %w", arch, err) + } + if err := sh.RunV("gpg", + "--default-key", gpgKey, + "--batch", "--yes", + "--passphrase", gpgPassphrase, + "--pinentry-mode", "loopback", + "--detach-sign", "--armor", + "-o", filepath.Join(repoDir, "repodata", "repomd.xml.asc"), + filepath.Join(repoDir, "repodata", "repomd.xml"), + ); err != nil { + return fmt.Errorf("signing repomd.xml for %s: %w", arch, err) + } + } + fmt.Println("RPM repo metadata generated in", outputBase) + return nil +} + +// RepoPacman generates a Pacman repository (repo-add) per arch. +func (Release) RepoPacman(ctx context.Context) error { + suite := repoSuite() + incomingDir := filepath.Join(repoRootDist, "repo-work", "incoming") + outputBase := filepath.Join(repoRootDist, "repo-output", "pacman", suite) + gpgKey := os.Getenv("RELEASE_GPG_KEY") + gpgPassphrase := os.Getenv("RELEASE_GPG_PASSPHRASE") + + for _, arch := range []string{"x86_64", "aarch64", "armv7"} { + repoDir := filepath.Join(outputBase, arch) + if err := os.MkdirAll(repoDir, 0o755); err != nil { + return err + } + pkgs, _ := filepath.Glob(filepath.Join(incomingDir, "*-"+arch+".archlinux")) + if len(pkgs) == 0 { + continue + } + for _, pkg := range pkgs { + abs, _ := filepath.Abs(pkg) + dst := filepath.Join(repoDir, filepath.Base(pkg)) + _ = os.Remove(dst) + if err := os.Symlink(abs, dst); err != nil { + return err + } + } + dbPath := filepath.Join(repoDir, "vikunja.db.tar.gz") + repoPkgs, _ := filepath.Glob(filepath.Join(repoDir, "*.archlinux")) + repoAddArgs := append([]string{dbPath}, repoPkgs...) + if err := sh.RunV("repo-add", repoAddArgs...); err != nil { + return fmt.Errorf("repo-add for %s: %w", arch, err) + } + for _, name := range []string{"vikunja.db", "vikunja.files"} { + link := filepath.Join(repoDir, name) + _ = os.Remove(link) + if err := os.Symlink(name+".tar.gz", link); err != nil { + return fmt.Errorf("creating symlink %s: %w", name, err) + } + } + if err := sh.RunV("gpg", + "--default-key", gpgKey, + "--batch", "--yes", + "--passphrase", gpgPassphrase, + "--pinentry-mode", "loopback", + "--detach-sign", + "-o", filepath.Join(repoDir, "vikunja.db.sig"), + dbPath, + ); err != nil { + return fmt.Errorf("signing db for %s: %w", arch, err) + } + } + fmt.Println("Pacman repo metadata generated in", outputBase) + return nil +} + +// ----------------------------------------------------------------------------- +// pipeline internals + +const ( + distSubdir = "dist" + subBin = "binaries" + subRelease = "release" + subZip = "zip" + + // repoRootDist is where the repo-publish targets read and write — it's + // the dist/ directory at the repo root, not under build/. The CI + // populates dist/repo-work/incoming with packages from every project. + repoRootDist = "../dist" +) + +func projectDist(p *project, sub string) string { + return filepath.Join(p.Root, distSubdir, sub) +} + +func releaseDirs(p *project) error { + for _, d := range []string{subBin, subRelease, subZip} { + if err := os.MkdirAll(projectDist(p, d), 0o755); err != nil { + return err + } + } + return nil +} + +func prepareXgo(_ context.Context) error { + if _, err := exec.LookPath("xgo"); err != nil { + fmt.Println("xgo not found, installing src.techknowlogick.com/xgo...") + if err := sh.RunV("go", "install", "src.techknowlogick.com/xgo@latest"); err != nil { + return fmt.Errorf("installing xgo: %w", err) + } + } + fmt.Println("Pulling latest xgo docker image...") + return sh.RunV("docker", "pull", "ghcr.io/techknowlogick/xgo:latest") +} + +func xgoOutName(p *project, version string) string { + if v := os.Getenv("XGO_OUT_NAME"); v != "" { + return v + } + return p.Executable + "-" + versionTagOrUnstable(version) +} + +func runXgo(ctx context.Context, p *project, version, targets string) error { + extraLdflags := `-linkmode external -extldflags "-static" ` + // xgo's darwin builds can't use the static external linker. + if strings.HasPrefix(targets, "darwin") { + extraLdflags = "" + } + // xgo resolves its last arg as a Go package path. Running it from build/ + // with `../` confuses the module resolution (it tries to find a package + // inside this build module). Invoke xgo from the project root so we can + // pass p.BuildPath ("." or "./cmd/veans") just like the original + // per-project magefiles did. + absRoot, err := filepath.Abs(p.Root) + if err != nil { + return fmt.Errorf("resolve project root: %w", err) + } + absDest, err := filepath.Abs(projectDist(p, subBin)) + if err != nil { + return fmt.Errorf("resolve dest dir: %w", err) + } + //nolint:gosec // mage helper; args are derived from the static project table above. + cmd := exec.CommandContext(ctx, "xgo", + "-dest", absDest, + "-tags", p.BuildTags, + "-ldflags", extraLdflags+p.Ldflags(version), + "-targets", targets, + "-out", xgoOutName(p, version), + p.BuildPath, + ) + cmd.Dir = absRoot + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd.Run() +} + +func xgoAllOS(ctx context.Context, p *project, version string) error { + groups := []string{ + "windows/*", + strings.Join([]string{ + "linux/amd64", + "linux/arm-5", + "linux/arm-6", + "linux/arm-7", + "linux/arm64", + "linux/mips", + "linux/mipsle", + "linux/mips64", + "linux/mips64le", + "linux/riscv64", + }, ","), + "darwin-10.15/*", + } + var ( + wg sync.WaitGroup + mu sync.Mutex + firstErr error + ) + record := func(err error) { + if err == nil { + return + } + mu.Lock() + if firstErr == nil { + firstErr = err + } + mu.Unlock() + } + for _, targets := range groups { + wg.Add(1) + go func(t string) { + defer wg.Done() + record(runXgo(ctx, p, version, t)) + }(targets) + } + wg.Wait() + return firstErr +} + +// compressBinaries runs upx -9 over each binary that upx can handle. The skip +// list matches the parent magefile's behavior. +func compressBinaries(p *project) error { + var ( + wg sync.WaitGroup + mu sync.Mutex + firstErr error + ) + record := func(err error) { + if err == nil { + return + } + mu.Lock() + if firstErr == nil { + firstErr = err + } + mu.Unlock() + } + walkErr := filepath.Walk(projectDist(p, subBin), func(path string, info os.FileInfo, err error) error { + if err != nil || info.IsDir() { + return err + } + name := info.Name() + if !strings.Contains(name, p.Executable) { + return nil + } + if strings.Contains(name, "mips") || + strings.Contains(name, "s390x") || + strings.Contains(name, "riscv64") || + strings.Contains(name, "darwin") || + (strings.Contains(name, "windows") && strings.Contains(name, "arm64")) { + return nil + } + wg.Add(1) + go func(pp string) { + defer wg.Done() + if err := sh.RunV("chmod", "+x", pp); err != nil { + record(err) + return + } + record(sh.RunV("upx", "-9", pp)) + }(path) + return nil + }) + if walkErr != nil { + return walkErr + } + wg.Wait() + return firstErr +} + +func copyBinaries(p *project) error { + return filepath.Walk(projectDist(p, subBin), func(path string, info os.FileInfo, err error) error { + if err != nil || info.IsDir() { + return err + } + if !strings.Contains(info.Name(), p.Executable) { + return nil + } + return copyFile(path, filepath.Join(projectDist(p, subRelease), info.Name())) + }) +} + +func writeChecksums(p *project) error { + release := projectDist(p, subRelease) + return filepath.Walk(release, func(path string, info os.FileInfo, err error) error { + if err != nil || info.IsDir() { + return err + } + if strings.HasSuffix(info.Name(), ".sha256") { + return nil + } + sum, err := sha256File(path) + if err != nil { + return err + } + return os.WriteFile(path+".sha256", []byte(sum+" "+info.Name()+"\n"), 0o644) + }) +} + +func bundleOsPackages(p *project) error { + release := projectDist(p, subRelease) + bins := map[string]os.FileInfo{} + if err := filepath.Walk(release, func(path string, info os.FileInfo, err error) error { + if err != nil || info.IsDir() { + return err + } + if strings.HasSuffix(info.Name(), ".sha256") { + return nil + } + bins[path] = info + return nil + }); err != nil { + return err + } + for binPath, info := range bins { + folder := filepath.Join(release, info.Name()+"-full") + if err := os.MkdirAll(folder, 0o755); err != nil { + return err + } + if err := moveFile(binPath+".sha256", filepath.Join(folder, info.Name()+".sha256")); err != nil { + return err + } + if err := moveFile(binPath, filepath.Join(folder, info.Name())); err != nil { + return err + } + if p.OsPackageExtras != nil { + if err := p.OsPackageExtras(folder, p); err != nil { + return err + } + } + } + return nil +} + +func zipBundles(ctx context.Context, p *project) error { + zipDirAbs, err := filepath.Abs(projectDist(p, subZip)) + if err != nil { + return err + } + release := projectDist(p, subRelease) + return filepath.Walk(release, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if !info.IsDir() || filepath.Base(path) == subRelease { + return nil + } + fmt.Printf("Zipping %s...\n", info.Name()) + zipFile := filepath.Join(zipDirAbs, info.Name()+".zip") + //nolint:gosec // mage helper; args derive from the local filesystem walk above. + c := exec.CommandContext(ctx, "zip", "-r", zipFile, ".", "-i", "*") + c.Dir = path + c.Stdout, c.Stderr = os.Stdout, os.Stderr + return c.Run() + }) +} + +// repoSuite validates the REPO_SUITE env var; defaults to "stable". Limiting +// the values prevents path traversal via the suite name flowing into a +// filesystem path. +func repoSuite() string { + switch os.Getenv("REPO_SUITE") { + case "stable", "unstable": + return os.Getenv("REPO_SUITE") + default: + return "stable" + } +} + +// ----------------------------------------------------------------------------- +// helpers — duplicated from the project magefiles so this module depends on +// nothing but stdlib + mage. Don't import these from elsewhere; rewrite them +// here if they need to change. + +func copyFile(src, dst string) error { + in, err := os.Open(src) + if err != nil { + return err + } + defer in.Close() + out, err := os.Create(dst) + if err != nil { + return err + } + defer out.Close() + if _, err := io.Copy(out, in); err != nil { + return err + } + si, err := os.Stat(src) + if err != nil { + return err + } + if err := os.Chmod(dst, si.Mode()); err != nil { + return err + } + return out.Close() +} + +func moveFile(src, dst string) error { + if err := copyFile(src, dst); err != nil { + return err + } + return os.Remove(src) +} + +func sha256File(path string) (string, error) { + f, err := os.Open(path) + if err != nil { + return "", err + } + defer f.Close() + h := sha256.New() + if _, err := io.Copy(h, f); err != nil { + return "", err + } + return fmt.Sprintf("%x", h.Sum(nil)), nil +} + +// Aliases for kebab-case spelling at the CLI. +var Aliases = map[string]any{ + "release": Release.Build, + "release:build": Release.Build, + "release:xgo": Release.Xgo, + "release:prepare-nfpm-config": Release.PrepareNFPMConfig, + "release:repo-apt": Release.RepoApt, + "release:repo-rpm": Release.RepoRpm, + "release:repo-pacman": Release.RepoPacman, +} diff --git a/config-raw.json b/config-raw.json index dd395b768..641285994 100644 --- a/config-raw.json +++ b/config-raw.json @@ -997,6 +997,37 @@ } ] }, + { + "key": "audit", + "comment": "Audit logging writes structured JSONL records of authentication, authorization and data lifecycle events. Requires the licensed `audit_logs` feature — with `audit.enabled: true` but no active license, listeners are registered but nothing is written until a license with the feature becomes active.", + "children": [ + { + "key": "enabled", + "default_value": "false", + "comment": "Whether to enable audit logging." + }, + { + "key": "logfile", + "default_value": "", + "comment": "The file audit log entries are written to, one JSON object per line. If empty, defaults to `audit.log` in the configured log path." + }, + { + "key": "rotation", + "children": [ + { + "key": "maxsizemb", + "default_value": "100", + "comment": "Rotate the audit log file once it exceeds this size in megabytes. Set to 0 to disable size-based rotation." + }, + { + "key": "maxage", + "default_value": "30", + "comment": "Delete rotated audit log files older than this many days. This only applies to the local rotated files, it is not a retention policy. Set to 0 to keep rotated files forever." + } + ] + } + ] + }, { "key": "outgoingrequests", "children": [ diff --git a/desktop/icon.png b/desktop/icon.png new file mode 100644 index 000000000..21b9fc4a6 Binary files /dev/null and b/desktop/icon.png differ diff --git a/desktop/main.js b/desktop/main.js index f670f8fe9..ca84f4c1f 100644 --- a/desktop/main.js +++ b/desktop/main.js @@ -397,7 +397,11 @@ function toggleQuickEntry() { // ─── System tray ───────────────────────────────────────────────────── function setupTray() { if (!tray) { - const iconPath = path.join(__dirname, 'build', 'icon.png') + // NOTE: load the icon from the app root, not build/. The build/ directory is + // electron-builder's buildResources dir and is NOT packaged into the app, so + // referencing build/icon.png here works in dev but yields an empty tray icon + // in packaged releases (see issue #2668). + const iconPath = path.join(__dirname, 'icon.png') const icon = nativeImage.createFromPath(iconPath).resize({width: 16, height: 16}) tray = new Tray(icon) tray.setToolTip('Vikunja') diff --git a/desktop/package.json b/desktop/package.json index e60798ba7..1aaabf2c2 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -61,8 +61,8 @@ } }, "devDependencies": { - "electron": "40.9.3", - "electron-builder": "26.8.1", + "electron": "40.10.4", + "electron-builder": "26.15.3", "unzipper": "0.12.3" }, "dependencies": { @@ -74,9 +74,13 @@ ], "overrides": { "minimatch": "^10.2.3", - "tar": "^7.5.11", + "tar": ">=7.5.16", "@tootallnate/once": "^3.0.1", - "picomatch": ">=4.0.4" + "picomatch": ">=4.0.4", + "tmp": ">=0.2.7", + "ip-address": ">=10.1.1", + "form-data": ">=4.0.6", + "js-yaml": ">=4.2.0" } } } diff --git a/desktop/pnpm-lock.yaml b/desktop/pnpm-lock.yaml index 488330fa4..248ad6337 100644 --- a/desktop/pnpm-lock.yaml +++ b/desktop/pnpm-lock.yaml @@ -6,9 +6,13 @@ settings: overrides: minimatch: ^10.2.3 - tar: ^7.5.11 + tar: '>=7.5.16' '@tootallnate/once': ^3.0.1 picomatch: '>=4.0.4' + tmp: '>=0.2.7' + ip-address: '>=10.1.1' + form-data: '>=4.0.6' + js-yaml: '>=4.2.0' importers: @@ -19,11 +23,11 @@ importers: version: 5.2.1 devDependencies: electron: - specifier: 40.9.3 - version: 40.9.3 + specifier: 40.10.4 + version: 40.10.4 electron-builder: - specifier: 26.8.1 - version: 26.8.1(electron-builder-squirrel-windows@24.13.3) + specifier: 26.15.3 + version: 26.15.3(electron-builder-squirrel-windows@24.13.3) unzipper: specifier: 0.12.3 version: 0.12.3 @@ -37,6 +41,10 @@ packages: resolution: {integrity: sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig==} engines: {node: '>= 8.9.0'} + '@electron-internal/extract-zip@1.0.2': + resolution: {integrity: sha512-VJuNETNPEhrmQEZezeTZO5TZMV+dobBRyJ7zHjGJWIhMS7m7W1UeClt69u4hkUxv9ZZVxuli/E9Yvc4gDNHGsg==} + engines: {node: '>=22.12.0'} + '@electron/asar@3.4.1': resolution: {integrity: sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA==} engines: {node: '>=10.12.0'} @@ -46,14 +54,14 @@ packages: resolution: {integrity: sha512-zx0EIq78WlY/lBb1uXlziZmDZI4ubcCXIMJ4uGjXzZW0nS19TjSPeXPAjzzTmKQlJUZm0SbmZhPKP7tuQ1SsEw==} hasBin: true - '@electron/get@2.0.3': - resolution: {integrity: sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==} - engines: {node: '>=12'} - '@electron/get@3.1.0': resolution: {integrity: sha512-F+nKc0xW+kVbBRhFzaMgPy3KwmuNTYX1fx6+FxxoSnNgwYX6LD7AKBTWkU0MQ6IBoe7dz069CNkR673sPAgkCQ==} engines: {node: '>=14'} + '@electron/get@5.0.0': + resolution: {integrity: sha512-pjoBpru1KdEtcExBnuHAP1cAc/5faoedw0hzJkL3o4/IJp7HNF1+fbrdxT3gMYRX2oJfvnA/WXeCTVQpYYxyJA==} + engines: {node: '>=22.12.0'} + '@electron/notarize@2.2.1': resolution: {integrity: sha512-aL+bFMIkpR0cmmj5Zgy0LMKEpgy43/hw5zadEArgmAMWWlKc5buwFvFT9G/o/YJkvXAJm5q3iuTuLaiaXW39sg==} engines: {node: '>= 10.0.0'} @@ -72,8 +80,8 @@ packages: engines: {node: '>=12.0.0'} hasBin: true - '@electron/rebuild@4.0.3': - resolution: {integrity: sha512-u9vpTHRMkOYCs/1FLiSVAFZ7FbjsXK+bQuzviJZa+lG7BHZl1nz52/IcGvwa3sk80/fc3llutBkbCq10Vh8WQA==} + '@electron/rebuild@4.0.4': + resolution: {integrity: sha512-Rzc39XPdk/+/wBG8MfwAHohXflep0ITUfulb6Rgz3R0NeSB1noE+E9/M/cb8ftCAiyDD9PPhLuuWgE1GaInbKg==} engines: {node: '>=22.12.0'} hasBin: true @@ -105,13 +113,27 @@ packages: resolution: {integrity: sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==} engines: {node: '>= 10.0.0'} - '@npmcli/agent@3.0.0': - resolution: {integrity: sha512-S79NdEgDQd/NGCay6TCoVzXSj74skRZIKJcpJjC5lOq34SZzyI6MqtiiWoiVWoVrTcGjNeC4ipbh1VIHlpfF5Q==} - engines: {node: ^18.17.0 || >=20.5.0} + '@noble/hashes@1.4.0': + resolution: {integrity: sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==} + engines: {node: '>= 16'} - '@npmcli/fs@4.0.0': - resolution: {integrity: sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q==} - engines: {node: ^18.17.0 || >=20.5.0} + '@noble/hashes@2.2.0': + resolution: {integrity: sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==} + engines: {node: '>= 20.19.0'} + + '@peculiar/asn1-schema@2.7.0': + resolution: {integrity: sha512-W8ZfWzLmQnrcky+eh3tni4IozMdqBDiHWU0N+vve/UGjMaUs8c0L7A2oEdkBXS8rTpWDpK/aoI3DG/L/hxmxPg==} + + '@peculiar/json-schema@1.1.12': + resolution: {integrity: sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==} + engines: {node: '>=8.0.0'} + + '@peculiar/utils@2.0.3': + resolution: {integrity: sha512-+oL3HPFRIZ1St2K50lWCXiioIgSoxzz7R1J3uF6neO2yl1sgmpgY6XXJH4BdpoDkMWznQTeYF6oWNDZLCdQ4eQ==} + + '@peculiar/webcrypto@1.7.1': + resolution: {integrity: sha512-ODOov0sGMJMf3jPonOkgGqPknTsu+DdQ7kD++gz8aI+aFMOMHFbWAA2taqXXVTdP+OTOQR/znGvSpmkeI0WTYQ==} + engines: {node: '>=14.18.0'} '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} @@ -132,9 +154,6 @@ packages: '@types/cacheable-request@6.0.3': resolution: {integrity: sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==} - '@types/debug@4.1.12': - resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} - '@types/debug@4.1.13': resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} @@ -147,35 +166,26 @@ packages: '@types/keyv@3.1.4': resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} - '@types/ms@0.7.34': - resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} - '@types/ms@2.1.0': resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} '@types/node@24.10.9': resolution: {integrity: sha512-ne4A0IpG3+2ETuREInjPNhUGis1SFjv1d5asp8MzEAGtOZeTeHVDOYqOgqfhvseqg/iXty2hjBf1zAOb7RNiNw==} - '@types/plist@3.0.2': - resolution: {integrity: sha512-ULqvZNGMv0zRFvqn8/4LSPtnmN4MfhlPNtJCTpKuIIxGVGZ2rYWzFXrvEBoh9CVyqSE7D6YFRJ1hydLHI6kbWw==} - '@types/responselike@1.0.3': resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} - '@types/verror@1.10.4': - resolution: {integrity: sha512-OjJdqx6QlbyZw9LShPwRW+Kmiegeg3eWNI41MQQKaG3vjdU2L9SRElntM51HmHBY1cu7izxQJ1lMYioQh3XMBg==} - - '@types/yauzl@2.10.3': - resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} - - '@xmldom/xmldom@0.8.12': - resolution: {integrity: sha512-9k/gHF6n/pAi/9tqr3m3aqkuiNosYTurLLUtc7xQ9sxB/wm7WPygCv8GYa6mS0fLJEHhqMC1ATYhz++U/lRHqg==} + '@xmldom/xmldom@0.8.13': + resolution: {integrity: sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==} engines: {node: '>=10.0.0'} - deprecated: this version has critical issues, please update to the latest version - abbrev@3.0.1: - resolution: {integrity: sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==} - engines: {node: ^18.17.0 || >=20.5.0} + '@xmldom/xmldom@0.9.10': + resolution: {integrity: sha512-A9gOqLdi6cV4ibazAjcQufGj0B1y/vDqYrcuP6d/6x8P27gRS8643Dj9o1dEKtB6O7fwxb2FgBmJS2mX7gpvdw==} + engines: {node: '>=14.6'} + + abbrev@4.0.0: + resolution: {integrity: sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==} + engines: {node: ^20.17.0 || >=22.9.0} accepts@2.0.0: resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} @@ -197,6 +207,9 @@ packages: ajv@6.14.0: resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} + ajv@8.20.0: + resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -216,9 +229,6 @@ packages: app-builder-bin@4.0.0: resolution: {integrity: sha512-xwdG0FJPQMe0M0UA4Tz0zEB8rBJTRA5a476ZawAqiBkMv16GRK5xpXThOjMaEOFnZ6zabejjG4J3da0SXG63KA==} - app-builder-bin@5.0.0-alpha.12: - resolution: {integrity: sha512-j87o0j6LqPL3QRr8yid6c+Tt5gC7xNfYo6uQIQkorAC6MpeayVMZrEDzKmJJ/Hlv7EnOQpaRm53k6ktDYZyB6w==} - app-builder-lib@24.13.3: resolution: {integrity: sha512-FAzX6IBit2POXYGnTCT8YHFO/lr5AapAII6zzhQO3Rw4cEDOgK+t1xhLc5tNcKlicTHlo9zxIwnYCX9X2DLkig==} engines: {node: '>=14.0.0'} @@ -226,12 +236,12 @@ packages: dmg-builder: 24.13.3 electron-builder-squirrel-windows: 24.13.3 - app-builder-lib@26.8.1: - resolution: {integrity: sha512-p0Im/Dx5C4tmz8QEE1Yn4MkuPC8PrnlRneMhWJj7BBXQfNTJUshM/bp3lusdEsDbvvfJZpXWnYesgSLvwtM2Zw==} + app-builder-lib@26.15.3: + resolution: {integrity: sha512-2VnyWkqsP5v5XbBhL3tD5Syx8iNPBYsoU7kY4S2fz7wg8Rj/nztWKCUzGKaFRTv0Xwf3/H058CR1Kvtd/3lRow==} engines: {node: '>=14.0.0'} peerDependencies: - dmg-builder: 26.8.1 - electron-builder-squirrel-windows: 26.8.1 + dmg-builder: 26.15.3 + electron-builder-squirrel-windows: 26.15.3 archiver-utils@2.1.0: resolution: {integrity: sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==} @@ -248,13 +258,9 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - assert-plus@1.0.0: - resolution: {integrity: sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==} - engines: {node: '>=0.8'} - - astral-regex@2.0.0: - resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} - engines: {node: '>=8'} + asn1js@3.0.10: + resolution: {integrity: sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg==} + engines: {node: '>=12.0.0'} async-exit-hook@2.0.1: resolution: {integrity: sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==} @@ -270,6 +276,9 @@ packages: resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} engines: {node: '>= 4.0.0'} + aws4@1.13.2: + resolution: {integrity: sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==} + balanced-match@4.0.4: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} @@ -294,8 +303,8 @@ packages: resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==} deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. - brace-expansion@5.0.5: - resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} + brace-expansion@5.0.6: + resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} engines: {node: 18 || 20 || >=22} buffer-crc32@0.2.13: @@ -315,23 +324,24 @@ packages: resolution: {integrity: sha512-upp+biKpN/XZMLim7aguUyW8s0FUpDvOtK6sbanMFDAMBzpHDqdhgVYm6zc9HJ6nWo7u2Lxk60i2M6Jd3aiNrA==} engines: {node: '>=12.0.0'} - builder-util-runtime@9.5.1: - resolution: {integrity: sha512-qt41tMfgHTllhResqM5DcnHyDIWNgzHvuY2jDcYP9iaGpkWxTUzV6GQjDeLnlR1/DtdlcsWQbA7sByMpmJFTLQ==} + builder-util-runtime@9.7.0: + resolution: {integrity: sha512-g/kR520giAFYkSXTzcmF3kqQq7wi8F6N6SzeDgZrqTBN+VHdmgWOyTdD1yD7AATDId/yXLvuP34CxW46/BwCdw==} engines: {node: '>=12.0.0'} builder-util@24.13.1: resolution: {integrity: sha512-NhbCSIntruNDTOVI9fdXz0dihaqX2YuE1D6zZMrwiErzH4ELZHE6mdiB40wEgZNprDia+FghRFgKoAqMZRRjSA==} - builder-util@26.8.1: - resolution: {integrity: sha512-pm1lTYbGyc90DHgCDO7eo8Rl4EqKLciayNbZqGziqnH9jrlKe8ZANGdityLZU+pJh16dfzjAx2xQq9McuIPEtw==} + builder-util@26.15.3: + resolution: {integrity: sha512-q2hn7Mbo2nFNkVekPiHFx6Nfo3hURmES3tfBn+k5Pqxl2RkmP3QGqZUhH/q9Pch/4G05NRhPjDlVj1O8q4Txvw==} + engines: {node: '>=14.0.0'} bytes@3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} - cacache@19.0.1: - resolution: {integrity: sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ==} - engines: {node: ^18.17.0 || >=20.5.0} + bytestreamjs@2.0.1: + resolution: {integrity: sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ==} + engines: {node: '>=6.0.0'} cacheable-lookup@5.0.4: resolution: {integrity: sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==} @@ -368,18 +378,6 @@ packages: resolution: {integrity: sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==} engines: {node: '>=8'} - cli-cursor@3.1.0: - resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} - engines: {node: '>=8'} - - cli-spinners@2.9.2: - resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} - engines: {node: '>=6'} - - cli-truncate@2.1.0: - resolution: {integrity: sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==} - engines: {node: '>=8'} - cliui@8.0.1: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} @@ -387,10 +385,6 @@ packages: clone-response@1.0.3: resolution: {integrity: sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==} - clone@1.0.4: - resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} - engines: {node: '>=0.8'} - color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -433,9 +427,6 @@ packages: resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} engines: {node: '>= 0.6'} - core-util-is@1.0.2: - resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==} - core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} @@ -448,9 +439,6 @@ packages: resolution: {integrity: sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==} engines: {node: '>= 10'} - crc@3.8.0: - resolution: {integrity: sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==} - cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -468,9 +456,6 @@ packages: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} engines: {node: '>=10'} - defaults@1.0.4: - resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} - defer-to-connect@2.0.1: resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==} engines: {node: '>=10'} @@ -491,10 +476,6 @@ packages: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} - detect-libc@2.0.3: - resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} - engines: {node: '>=8'} - detect-node@2.1.0: resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} @@ -504,14 +485,8 @@ packages: dir-compare@4.2.0: resolution: {integrity: sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ==} - dmg-builder@26.8.1: - resolution: {integrity: sha512-glMJgnTreo8CFINujtAhCgN96QAqApDMZ8Vl1r8f0QT8QprvC1UCltV4CcWj20YoIyLZx6IUskaJZ0NV8fokcg==} - - dmg-license@1.0.11: - resolution: {integrity: sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q==} - engines: {node: '>=8'} - os: [darwin] - hasBin: true + dmg-builder@26.15.3: + resolution: {integrity: sha512-O3zJUFUYHJKgzPqioHxfxzBzlSC1eXCSr79gMSBKBP5AgjjpmrydMsMLotEg9fAJF36vdUncb+4ndRNxoPdlSQ==} dotenv-expand@11.0.6: resolution: {integrity: sha512-8NHi73otpWsZGBSZwwknTXS5pqMOrk9+Ssrna8xCaxkzEpU9OTf9R5ArQGVw03//Zmk9MOwLPng9WwndvpAJ5g==} @@ -549,20 +524,20 @@ packages: electron-builder-squirrel-windows@24.13.3: resolution: {integrity: sha512-oHkV0iogWfyK+ah9ZIvMDpei1m9ZRpdXcvde1wTpra2U8AFDNNpqJdnin5z+PM1GbQ5BoaKCWas2HSjtR0HwMg==} - electron-builder@26.8.1: - resolution: {integrity: sha512-uWhx1r74NGpCagG0ULs/P9Nqv2nsoo+7eo4fLUOB8L8MdWltq9odW/uuLXMFCDGnPafknYLZgjNX0ZIFRzOQAw==} + electron-builder@26.15.3: + resolution: {integrity: sha512-a1KM5heqS3gQCZzizXEI8RjJy3QVogULPdeSknt76uLDpBIW/HDGsMg/XgP0riP6PI9COsRvFITKKGDqA8fJxA==} engines: {node: '>=14.0.0'} hasBin: true electron-publish@24.13.1: resolution: {integrity: sha512-2ZgdEqJ8e9D17Hwp5LEq5mLQPjqU3lv/IALvgp+4W8VeNhryfGhYEQC/PgDPMrnWUp+l60Ou5SJLsu+k4mhQ8A==} - electron-publish@26.8.1: - resolution: {integrity: sha512-q+jrSTIh/Cv4eGZa7oVR+grEJo/FoLMYBAnSL5GCtqwUpr1T+VgKB/dn1pnzxIxqD8S/jP1yilT9VrwCqINR4w==} + electron-publish@26.15.3: + resolution: {integrity: sha512-g/2bn8YTavY4cuS5F+jOS7zmZbXXBV8KZ8yHKfJjFPoKtzBqrpCdNPxBd3tqdBwP7BVd0lGzf7Bk2s0KesWZ4Q==} - electron@40.9.3: - resolution: {integrity: sha512-rDcJOT6BBE689Ada+4jD3rVr05pMv9MZOgT0x/rIMVDF9c4ttx4RTb6lVARTyxZC7uqpirttCtcli1eg1DX5qg==} - engines: {node: '>= 12.20.55'} + electron@40.10.4: + resolution: {integrity: sha512-ouNZrXXmdPL/wiTQ+xzXpb7B/BHg+j7XARig0SE7azFO3bjbYUd6lFjIAAiDQ02Pl/Oj7MUk+4C0hdf9yFtA1A==} + engines: {node: '>= 22.12.0'} hasBin: true emoji-regex@8.0.0: @@ -575,9 +550,6 @@ packages: resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} engines: {node: '>= 0.8'} - encoding@0.1.13: - resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} - end-of-stream@1.4.5: resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} @@ -585,6 +557,10 @@ packages: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} engines: {node: '>=6'} + env-paths@3.0.0: + resolution: {integrity: sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + err-code@2.0.3: resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} @@ -629,23 +605,14 @@ packages: resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} engines: {node: '>= 18'} - extract-zip@2.0.1: - resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==} - engines: {node: '>= 10.17.0'} - hasBin: true - - extsprintf@1.4.0: - resolution: {integrity: sha512-6NW8DZ8pWBc5NbGYUiqqccj9dXnuSzilZYqprdKJBZsQodGH9IyUoFOGxIWVDcBzHMb8ET24aqx9p66tZEWZkA==} - engines: {'0': node >=0.6.0} - fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - fd-slicer@1.1.0: - resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} + fast-uri@3.1.2: + resolution: {integrity: sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==} fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} @@ -667,8 +634,8 @@ packages: resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} engines: {node: '>=14'} - form-data@4.0.5: - resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} + form-data@4.0.6: + resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==} engines: {node: '>= 6'} forwarded@0.2.0: @@ -702,10 +669,6 @@ packages: resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} engines: {node: '>=10'} - fs-minipass@3.0.3: - resolution: {integrity: sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} @@ -775,6 +738,10 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + hosted-git-info@4.1.0: resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} engines: {node: '>=10'} @@ -806,15 +773,6 @@ packages: resolution: {integrity: sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==} engines: {node: '>= 14'} - iconv-corefoundation@1.1.7: - resolution: {integrity: sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ==} - engines: {node: ^8.11.2 || >=10} - os: [darwin] - - iconv-lite@0.6.3: - resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} - engines: {node: '>=0.10.0'} - iconv-lite@0.7.0: resolution: {integrity: sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==} engines: {node: '>=0.10.0'} @@ -822,10 +780,6 @@ packages: ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - imurmurhash@0.1.4: - resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} - engines: {node: '>=0.8.19'} - inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. @@ -833,10 +787,6 @@ packages: inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - ip-address@10.1.0: - resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==} - engines: {node: '>= 12'} - ipaddr.js@1.9.1: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} @@ -849,17 +799,9 @@ packages: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} - is-interactive@1.0.0: - resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} - engines: {node: '>=8'} - is-promise@4.0.0: resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} - is-unicode-supported@0.1.0: - resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} - engines: {node: '>=10'} - isarray@1.0.0: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} @@ -878,6 +820,10 @@ packages: resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==} engines: {node: '>=16'} + isexe@4.0.0: + resolution: {integrity: sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==} + engines: {node: '>=20'} + jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} @@ -890,8 +836,8 @@ packages: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true - js-yaml@4.1.1: - resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + js-yaml@4.2.0: + resolution: {integrity: sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==} hasBin: true json-buffer@3.0.1: @@ -900,6 +846,9 @@ packages: json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + json-stringify-safe@5.0.1: resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} @@ -945,10 +894,6 @@ packages: lodash@4.18.1: resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} - log-symbols@4.1.0: - resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} - engines: {node: '>=10'} - lowercase-keys@2.0.0: resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} engines: {node: '>=8'} @@ -960,10 +905,6 @@ packages: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} - make-fetch-happen@14.0.3: - resolution: {integrity: sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ==} - engines: {node: ^18.17.0 || >=20.5.0} - matcher@3.0.0: resolution: {integrity: sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==} engines: {node: '>=10'} @@ -1001,10 +942,6 @@ packages: engines: {node: '>=4.0.0'} hasBin: true - mimic-fn@2.1.0: - resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} - engines: {node: '>=6'} - mimic-response@1.0.1: resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==} engines: {node: '>=4'} @@ -1013,10 +950,6 @@ packages: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} engines: {node: '>=10'} - minimatch@10.2.4: - resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==} - engines: {node: 18 || 20 || >=22} - minimatch@10.2.5: resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} engines: {node: 18 || 20 || >=22} @@ -1024,34 +957,6 @@ packages: minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - minipass-collect@2.0.1: - resolution: {integrity: sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==} - engines: {node: '>=16 || 14 >=14.17'} - - minipass-fetch@4.0.1: - resolution: {integrity: sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ==} - engines: {node: ^18.17.0 || >=20.5.0} - - minipass-flush@1.0.5: - resolution: {integrity: sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==} - engines: {node: '>= 8'} - - minipass-pipeline@1.2.4: - resolution: {integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==} - engines: {node: '>=8'} - - minipass-sized@1.0.3: - resolution: {integrity: sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==} - engines: {node: '>=8'} - - minipass@3.3.6: - resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} - engines: {node: '>=8'} - - minipass@7.1.2: - resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} - engines: {node: '>=16 || 14 >=14.17'} - minipass@7.1.3: resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} engines: {node: '>=16 || 14 >=14.17'} @@ -1071,23 +976,20 @@ packages: resolution: {integrity: sha512-u2EC1CeNe25uVtX3EZbdQ275c74zdZmmpzrHEQh2aIYqoVjlglfUpOX9YY85x1nlBydEKDVaSmMNhR7N82Qj8A==} engines: {node: '>=22.12.0'} - node-addon-api@1.7.2: - resolution: {integrity: sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==} - node-api-version@0.2.1: resolution: {integrity: sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q==} - node-gyp@11.5.0: - resolution: {integrity: sha512-ra7Kvlhxn5V9Slyus0ygMa2h+UqExPqUIkfk7Pc8QTLT956JLSy51uWFwHtIYy0vI8cB4BDhc/S03+880My/LQ==} - engines: {node: ^18.17.0 || >=20.5.0} + node-gyp@12.4.0: + resolution: {integrity: sha512-OMcPNvqTCFUnNaBlmdgq+lfNqY7gTiSmNRDjY3uAXRyudeKZEZxu3CLtjMQrx4zZxCX2b/mpNqTtwuCJgXhHkw==} + engines: {node: ^20.17.0 || >=22.9.0} hasBin: true node-int64@0.4.0: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} - nopt@8.1.0: - resolution: {integrity: sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==} - engines: {node: ^18.17.0 || >=20.5.0} + nopt@9.0.0: + resolution: {integrity: sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==} + engines: {node: ^20.17.0 || >=22.9.0} hasBin: true normalize-path@3.0.0: @@ -1113,14 +1015,6 @@ packages: once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - onetime@5.1.2: - resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} - engines: {node: '>=6'} - - ora@5.4.1: - resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} - engines: {node: '>=10'} - p-cancelable@2.1.1: resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==} engines: {node: '>=8'} @@ -1129,10 +1023,6 @@ packages: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} - p-map@7.0.4: - resolution: {integrity: sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==} - engines: {node: '>=18'} - package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} @@ -1159,20 +1049,25 @@ packages: resolution: {integrity: sha512-eRWB5LBz7PpDu4PUlwT0PhnQfTQJlDDdPa35urV4Osrm0t0AqQFGn+UIkU3klZvwJ8KPO3VbBFsXquA6p6kqZw==} engines: {node: '>=12', npm: '>=6'} - pend@1.2.0: - resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} - picomatch@4.0.4: resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} engines: {node: '>=12'} + pkijs@3.4.0: + resolution: {integrity: sha512-emEcLuomt2j03vxD54giVB4SxTjnsqkU692xZOZXHDVoYyypEm+b3jpiTcc+Cf+myooc+/Ly0z01jqeNHVgJGw==} + engines: {node: '>=16.0.0'} + plist@3.1.0: resolution: {integrity: sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==} engines: {node: '>=10.4.0'} - proc-log@5.0.0: - resolution: {integrity: sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==} - engines: {node: ^18.17.0 || >=20.5.0} + plist@3.1.1: + resolution: {integrity: sha512-ZIfcLJC+7E7FBFnDxm9MPmt7D+DidyQ26lewieO75AdhA2ayMtsJSES0iWzqJQbcVRSrTufQoy0DR94xHue0oA==} + engines: {node: '>=10.4.0'} + + proc-log@6.1.0: + resolution: {integrity: sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==} + engines: {node: ^20.17.0 || >=22.9.0} process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} @@ -1199,8 +1094,15 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - qs@6.15.0: - resolution: {integrity: sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==} + pvtsutils@1.3.6: + resolution: {integrity: sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==} + + pvutils@1.1.5: + resolution: {integrity: sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA==} + engines: {node: '>=16.0.0'} + + qs@6.15.2: + resolution: {integrity: sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==} engines: {node: '>=0.6'} quick-lru@5.1.1: @@ -1237,6 +1139,10 @@ packages: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + resedit@1.7.2: resolution: {integrity: sha512-vHjcY2MlAITJhC0eRD/Vv8Vlgmu9Sd3LX9zZvtGzU5ZImdTN3+d6e/4mnTyV8vEbyf1sgNIrWxhWlrys52OkEA==} engines: {node: '>=12', npm: '>=6'} @@ -1247,10 +1153,6 @@ packages: responselike@2.0.1: resolution: {integrity: sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==} - restore-cursor@3.1.0: - resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} - engines: {node: '>=8'} - retry@0.12.0: resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} engines: {node: '>= 4'} @@ -1272,16 +1174,9 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - sanitize-filename@1.6.3: - resolution: {integrity: sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==} - sanitize-filename@1.6.4: resolution: {integrity: sha512-9ZyI08PsvdQl2r/bBIGubpVdR3RR9sY6RDiWFPreA21C/EFlQhmgo20UZlNjZMMZNubusLhAQozkA0Od5J21Eg==} - sax@1.4.4: - resolution: {integrity: sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==} - engines: {node: '>=11.0.0'} - sax@1.6.0: resolution: {integrity: sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==} engines: {node: '>=11.0.0'} @@ -1302,6 +1197,11 @@ packages: engines: {node: '>=10'} hasBin: true + semver@7.8.1: + resolution: {integrity: sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==} + engines: {node: '>=10'} + hasBin: true + send@1.2.0: resolution: {integrity: sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==} engines: {node: '>= 18'} @@ -1352,22 +1252,6 @@ packages: resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==} engines: {node: '>=10'} - slice-ansi@3.0.0: - resolution: {integrity: sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==} - engines: {node: '>=8'} - - smart-buffer@4.2.0: - resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} - engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} - - socks-proxy-agent@8.0.5: - resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==} - engines: {node: '>= 14'} - - socks@2.8.7: - resolution: {integrity: sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==} - engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} - source-map-support@0.5.21: resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} @@ -1378,10 +1262,6 @@ packages: sprintf-js@1.1.3: resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} - ssri@12.0.0: - resolution: {integrity: sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ==} - engines: {node: ^18.17.0 || >=20.5.0} - stat-mode@1.0.0: resolution: {integrity: sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==} engines: {node: '>= 6'} @@ -1424,12 +1304,8 @@ packages: resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} engines: {node: '>=6'} - tar@7.5.11: - resolution: {integrity: sha512-ChjMH33/KetonMTAtpYdgUFr0tbz69Fp2v7zWxQfYZX4g5ZN2nOBXm1R2xyA+lMIKrLKIoKAwFj93jE/avX9cQ==} - engines: {node: '>=18'} - - tar@7.5.13: - resolution: {integrity: sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng==} + tar@7.5.16: + resolution: {integrity: sha512-56adEpPMouktRlBLXiaYFFzZ/3+JXa8P9n7WbR+ibIjtviN55mEaOkiysCnPnWm+7kkui1Dn8J9l+g6zV8731w==} engines: {node: '>=18'} temp-file@3.4.0: @@ -1445,8 +1321,8 @@ packages: tmp-promise@3.0.3: resolution: {integrity: sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==} - tmp@0.2.3: - resolution: {integrity: sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==} + tmp@0.2.7: + resolution: {integrity: sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==} engines: {node: '>=14.14'} toidentifier@1.0.1: @@ -1456,6 +1332,9 @@ packages: truncate-utf8-bytes@1.0.2: resolution: {integrity: sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==} + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + type-fest@0.13.1: resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==} engines: {node: '>=10'} @@ -1472,13 +1351,13 @@ packages: undici-types@7.16.0: resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} - unique-filename@4.0.0: - resolution: {integrity: sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ==} - engines: {node: ^18.17.0 || >=20.5.0} + undici@6.26.0: + resolution: {integrity: sha512-4yqz8a3n5HmGTlsbADNtr/dJlhkh/55Rq798G6ibiULcXbDtaLpTl1pvdqcbFfeoj3iSi52lePFM7h9H21cw/A==} + engines: {node: '>=18.17'} - unique-slug@5.0.0: - resolution: {integrity: sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg==} - engines: {node: ^18.17.0 || >=20.5.0} + undici@7.27.2: + resolution: {integrity: sha512-uZsKNuzQxDMUY6M3pIMvy5tvlGmtq8XJ2oLAkfRKGNu+1VQAIvLy2xIVG5ATZl5wDXl/tddByAWCizRbOme+TA==} + engines: {node: '>=20.18.1'} universalify@0.1.2: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} @@ -1508,12 +1387,8 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} - verror@1.10.0: - resolution: {integrity: sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==} - engines: {'0': node >=0.6.0} - - wcwidth@1.0.1: - resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} + webcrypto-core@1.9.2: + resolution: {integrity: sha512-gsXecm82UQNlTBURJGuqOWy1Ww08S3kZUcr3aOJS02Pk0xLtkfeUAVC0u0xhgdonFme80edSJUIJyuvL/7250Q==} which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} @@ -1525,6 +1400,11 @@ packages: engines: {node: ^18.17.0 || >=20.5.0} hasBin: true + which@6.0.1: + resolution: {integrity: sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==} + engines: {node: ^20.17.0 || >=22.9.0} + hasBin: true + wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -1559,9 +1439,6 @@ packages: resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} engines: {node: '>=12'} - yauzl@2.10.0: - resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} - yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -1579,11 +1456,13 @@ snapshots: ajv: 6.14.0 ajv-keywords: 3.5.2(ajv@6.14.0) + '@electron-internal/extract-zip@1.0.2': {} + '@electron/asar@3.4.1': dependencies: commander: 5.1.0 glob: 7.2.3 - minimatch: 10.2.4 + minimatch: 10.2.5 '@electron/fuses@1.8.0': dependencies: @@ -1591,7 +1470,7 @@ snapshots: fs-extra: 9.1.0 minimist: 1.2.8 - '@electron/get@2.0.3': + '@electron/get@3.1.0': dependencies: debug: 4.4.3 env-paths: 2.2.1 @@ -1605,17 +1484,16 @@ snapshots: transitivePeerDependencies: - supports-color - '@electron/get@3.1.0': + '@electron/get@5.0.0': dependencies: debug: 4.4.3 - env-paths: 2.2.1 - fs-extra: 8.1.0 - got: 11.8.6 + env-paths: 3.0.0 + graceful-fs: 4.2.11 progress: 2.0.3 - semver: 6.3.1 + semver: 7.8.1 sumchecker: 3.0.1 optionalDependencies: - global-agent: 3.0.0 + undici: 7.27.2 transitivePeerDependencies: - supports-color @@ -1642,7 +1520,7 @@ snapshots: fs-extra: 10.1.0 isbinaryfile: 4.0.10 minimist: 1.2.8 - plist: 3.1.0 + plist: 3.1.1 transitivePeerDependencies: - supports-color @@ -1653,25 +1531,18 @@ snapshots: fs-extra: 10.1.0 isbinaryfile: 4.0.10 minimist: 1.2.8 - plist: 3.1.0 + plist: 3.1.1 transitivePeerDependencies: - supports-color - '@electron/rebuild@4.0.3': + '@electron/rebuild@4.0.4': dependencies: '@malept/cross-spawn-promise': 2.0.0 debug: 4.4.3 - detect-libc: 2.0.3 - got: 11.8.6 - graceful-fs: 4.2.11 node-abi: 4.24.0 node-api-version: 0.2.1 - node-gyp: 11.5.0 - ora: 5.4.1 + node-gyp: 12.4.0 read-binary-file-arch: 1.0.6 - semver: 7.7.4 - tar: 7.5.11 - yargs: 17.7.2 transitivePeerDependencies: - supports-color @@ -1683,7 +1554,7 @@ snapshots: dir-compare: 3.3.0 fs-extra: 9.1.0 minimatch: 10.2.5 - plist: 3.1.0 + plist: 3.1.1 transitivePeerDependencies: - supports-color @@ -1694,8 +1565,8 @@ snapshots: debug: 4.4.3 dir-compare: 4.2.0 fs-extra: 11.3.1 - minimatch: 10.2.4 - plist: 3.1.0 + minimatch: 10.2.5 + plist: 3.1.1 transitivePeerDependencies: - supports-color @@ -1729,19 +1600,31 @@ snapshots: transitivePeerDependencies: - supports-color - '@npmcli/agent@3.0.0': - dependencies: - agent-base: 7.1.4 - http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.5 - lru-cache: 10.4.3 - socks-proxy-agent: 8.0.5 - transitivePeerDependencies: - - supports-color + '@noble/hashes@1.4.0': {} - '@npmcli/fs@4.0.0': + '@noble/hashes@2.2.0': {} + + '@peculiar/asn1-schema@2.7.0': dependencies: - semver: 7.7.4 + '@peculiar/utils': 2.0.3 + asn1js: 3.0.10 + tslib: 2.8.1 + + '@peculiar/json-schema@1.1.12': + dependencies: + tslib: 2.8.1 + + '@peculiar/utils@2.0.3': + dependencies: + tslib: 2.8.1 + + '@peculiar/webcrypto@1.7.1': + dependencies: + '@peculiar/asn1-schema': 2.7.0 + '@peculiar/json-schema': 1.1.12 + '@peculiar/utils': 2.0.3 + tslib: 2.8.1 + webcrypto-core: 1.9.2 '@pkgjs/parseargs@0.11.0': optional: true @@ -1761,10 +1644,6 @@ snapshots: '@types/node': 24.10.9 '@types/responselike': 1.0.3 - '@types/debug@4.1.12': - dependencies: - '@types/ms': 0.7.34 - '@types/debug@4.1.13': dependencies: '@types/ms': 2.1.0 @@ -1779,35 +1658,21 @@ snapshots: dependencies: '@types/node': 24.10.9 - '@types/ms@0.7.34': {} - '@types/ms@2.1.0': {} '@types/node@24.10.9': dependencies: undici-types: 7.16.0 - '@types/plist@3.0.2': - dependencies: - '@types/node': 24.10.9 - xmlbuilder: 15.1.1 - optional: true - '@types/responselike@1.0.3': dependencies: '@types/node': 24.10.9 - '@types/verror@1.10.4': - optional: true + '@xmldom/xmldom@0.8.13': {} - '@types/yauzl@2.10.3': - dependencies: - '@types/node': 24.10.9 - optional: true + '@xmldom/xmldom@0.9.10': {} - '@xmldom/xmldom@0.8.12': {} - - abbrev@3.0.1: {} + abbrev@4.0.0: {} accepts@2.0.0: dependencies: @@ -1833,6 +1698,13 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 + ajv@8.20.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.2 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + ansi-regex@5.0.1: {} ansi-regex@6.0.1: {} @@ -1845,9 +1717,7 @@ snapshots: app-builder-bin@4.0.0: {} - app-builder-bin@5.0.0-alpha.12: {} - - app-builder-lib@24.13.3(dmg-builder@26.8.1)(electron-builder-squirrel-windows@24.13.3): + app-builder-lib@24.13.3(dmg-builder@26.15.3)(electron-builder-squirrel-windows@24.13.3): dependencies: '@develar/schema-utils': 2.6.5 '@electron/notarize': 2.2.1 @@ -1861,65 +1731,70 @@ snapshots: builder-util-runtime: 9.2.4 chromium-pickle-js: 0.2.0 debug: 4.4.3 - dmg-builder: 26.8.1(electron-builder-squirrel-windows@24.13.3) + dmg-builder: 26.15.3(electron-builder-squirrel-windows@24.13.3) ejs: 3.1.10 - electron-builder-squirrel-windows: 24.13.3(dmg-builder@26.8.1) + electron-builder-squirrel-windows: 24.13.3(dmg-builder@26.15.3) electron-publish: 24.13.1 - form-data: 4.0.5 + form-data: 4.0.6 fs-extra: 10.1.0 hosted-git-info: 4.1.0 is-ci: 3.0.1 isbinaryfile: 5.0.7 - js-yaml: 4.1.1 + js-yaml: 4.2.0 lazy-val: 1.0.5 minimatch: 10.2.5 read-config-file: 6.3.2 sanitize-filename: 1.6.4 - semver: 7.7.4 - tar: 7.5.13 + semver: 7.8.1 + tar: 7.5.16 temp-file: 3.4.0 transitivePeerDependencies: - supports-color - app-builder-lib@26.8.1(dmg-builder@26.8.1)(electron-builder-squirrel-windows@24.13.3): + app-builder-lib@26.15.3(dmg-builder@26.15.3)(electron-builder-squirrel-windows@24.13.3): dependencies: - '@develar/schema-utils': 2.6.5 '@electron/asar': 3.4.1 '@electron/fuses': 1.8.0 '@electron/get': 3.1.0 '@electron/notarize': 2.5.0 '@electron/osx-sign': 1.3.3 - '@electron/rebuild': 4.0.3 + '@electron/rebuild': 4.0.4 '@electron/universal': 2.0.3 '@malept/flatpak-bundler': 0.4.0 + '@noble/hashes': 2.2.0 + '@peculiar/webcrypto': 1.7.1 '@types/fs-extra': 9.0.13 + ajv: 8.20.0 + asn1js: 3.0.10 async-exit-hook: 2.0.1 - builder-util: 26.8.1 - builder-util-runtime: 9.5.1 + builder-util: 26.15.3 + builder-util-runtime: 9.7.0 chromium-pickle-js: 0.2.0 ci-info: 4.3.1 debug: 4.4.3 - dmg-builder: 26.8.1(electron-builder-squirrel-windows@24.13.3) + dmg-builder: 26.15.3(electron-builder-squirrel-windows@24.13.3) dotenv: 16.4.5 dotenv-expand: 11.0.6 ejs: 3.1.10 - electron-builder-squirrel-windows: 24.13.3(dmg-builder@26.8.1) - electron-publish: 26.8.1 + electron-builder-squirrel-windows: 24.13.3(dmg-builder@26.15.3) + electron-publish: 26.15.3 fs-extra: 10.1.0 hosted-git-info: 4.1.0 isbinaryfile: 5.0.7 jiti: 2.6.1 - js-yaml: 4.1.1 + js-yaml: 4.2.0 json5: 2.2.3 lazy-val: 1.0.5 - minimatch: 10.2.4 + minimatch: 10.2.5 + pkijs: 3.4.0 plist: 3.1.0 proper-lockfile: 4.1.2 resedit: 1.7.2 semver: 7.7.4 - tar: 7.5.11 + tar: 7.5.16 temp-file: 3.4.0 tiny-async-pool: 1.3.0 + unzipper: 0.12.3 which: 5.0.0 transitivePeerDependencies: - supports-color @@ -1962,11 +1837,11 @@ snapshots: argparse@2.0.1: {} - assert-plus@1.0.0: - optional: true - - astral-regex@2.0.0: - optional: true + asn1js@3.0.10: + dependencies: + pvtsutils: 1.3.6 + pvutils: 1.1.5 + tslib: 2.8.1 async-exit-hook@2.0.1: {} @@ -1976,6 +1851,8 @@ snapshots: at-least-node@1.0.0: {} + aws4@1.13.2: {} + balanced-match@4.0.4: {} base64-js@1.5.1: {} @@ -2000,7 +1877,7 @@ snapshots: http-errors: 2.0.1 iconv-lite: 0.7.0 on-finished: 2.4.1 - qs: 6.15.0 + qs: 6.15.2 raw-body: 3.0.2 type-is: 2.0.1 transitivePeerDependencies: @@ -2009,7 +1886,7 @@ snapshots: boolean@3.2.0: optional: true - brace-expansion@5.0.5: + brace-expansion@5.0.6: dependencies: balanced-match: 4.0.4 @@ -2031,10 +1908,10 @@ snapshots: transitivePeerDependencies: - supports-color - builder-util-runtime@9.5.1: + builder-util-runtime@9.7.0: dependencies: debug: 4.4.3 - sax: 1.4.4 + sax: 1.6.0 transitivePeerDependencies: - supports-color @@ -2052,27 +1929,25 @@ snapshots: http-proxy-agent: 5.0.0 https-proxy-agent: 5.0.1 is-ci: 3.0.1 - js-yaml: 4.1.1 + js-yaml: 4.2.0 source-map-support: 0.5.21 stat-mode: 1.0.0 temp-file: 3.4.0 transitivePeerDependencies: - supports-color - builder-util@26.8.1: + builder-util@26.15.3: dependencies: - 7zip-bin: 5.2.0 - '@types/debug': 4.1.12 - app-builder-bin: 5.0.0-alpha.12 - builder-util-runtime: 9.5.1 + '@types/debug': 4.1.13 + builder-util-runtime: 9.7.0 chalk: 4.1.2 cross-spawn: 7.0.6 debug: 4.4.3 fs-extra: 10.1.0 http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.5 - js-yaml: 4.1.1 - sanitize-filename: 1.6.3 + js-yaml: 4.2.0 + sanitize-filename: 1.6.4 source-map-support: 0.5.21 stat-mode: 1.0.0 temp-file: 3.4.0 @@ -2082,20 +1957,7 @@ snapshots: bytes@3.1.2: {} - cacache@19.0.1: - dependencies: - '@npmcli/fs': 4.0.0 - fs-minipass: 3.0.3 - glob: 10.5.0 - lru-cache: 10.4.3 - minipass: 7.1.2 - minipass-collect: 2.0.1 - minipass-flush: 1.0.5 - minipass-pipeline: 1.2.4 - p-map: 7.0.4 - ssri: 12.0.0 - tar: 7.5.11 - unique-filename: 4.0.0 + bytestreamjs@2.0.1: {} cacheable-lookup@5.0.4: {} @@ -2132,18 +1994,6 @@ snapshots: ci-info@4.3.1: {} - cli-cursor@3.1.0: - dependencies: - restore-cursor: 3.1.0 - - cli-spinners@2.9.2: {} - - cli-truncate@2.1.0: - dependencies: - slice-ansi: 3.0.0 - string-width: 4.2.3 - optional: true - cliui@8.0.1: dependencies: string-width: 4.2.3 @@ -2154,8 +2004,6 @@ snapshots: dependencies: mimic-response: 1.0.1 - clone@1.0.4: {} - color-convert@2.0.1: dependencies: color-name: 1.1.4 @@ -2190,9 +2038,6 @@ snapshots: cookie@0.7.2: {} - core-util-is@1.0.2: - optional: true - core-util-is@1.0.3: {} crc-32@1.2.2: {} @@ -2202,11 +2047,6 @@ snapshots: crc-32: 1.2.2 readable-stream: 3.6.2 - crc@3.8.0: - dependencies: - buffer: 5.7.1 - optional: true - cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -2221,10 +2061,6 @@ snapshots: dependencies: mimic-response: 3.1.0 - defaults@1.0.4: - dependencies: - clone: 1.0.4 - defer-to-connect@2.0.1: {} define-data-property@1.1.4: @@ -2245,8 +2081,6 @@ snapshots: depd@2.0.0: {} - detect-libc@2.0.3: {} - detect-node@2.1.0: optional: true @@ -2257,34 +2091,19 @@ snapshots: dir-compare@4.2.0: dependencies: - minimatch: 10.2.4 + minimatch: 10.2.5 p-limit: 3.1.0 - dmg-builder@26.8.1(electron-builder-squirrel-windows@24.13.3): + dmg-builder@26.15.3(electron-builder-squirrel-windows@24.13.3): dependencies: - app-builder-lib: 26.8.1(dmg-builder@26.8.1)(electron-builder-squirrel-windows@24.13.3) - builder-util: 26.8.1 + app-builder-lib: 26.15.3(dmg-builder@26.15.3)(electron-builder-squirrel-windows@24.13.3) + builder-util: 26.15.3 fs-extra: 10.1.0 - iconv-lite: 0.6.3 - js-yaml: 4.1.1 - optionalDependencies: - dmg-license: 1.0.11 + js-yaml: 4.2.0 transitivePeerDependencies: - electron-builder-squirrel-windows - supports-color - dmg-license@1.0.11: - dependencies: - '@types/plist': 3.0.2 - '@types/verror': 1.10.4 - ajv: 6.14.0 - crc: 3.8.0 - iconv-corefoundation: 1.1.7 - plist: 3.1.0 - smart-buffer: 4.2.0 - verror: 1.10.0 - optional: true - dotenv-expand@11.0.6: dependencies: dotenv: 16.4.5 @@ -2313,9 +2132,9 @@ snapshots: dependencies: jake: 10.8.7 - electron-builder-squirrel-windows@24.13.3(dmg-builder@26.8.1): + electron-builder-squirrel-windows@24.13.3(dmg-builder@26.15.3): dependencies: - app-builder-lib: 24.13.3(dmg-builder@26.8.1)(electron-builder-squirrel-windows@24.13.3) + app-builder-lib: 24.13.3(dmg-builder@26.15.3)(electron-builder-squirrel-windows@24.13.3) archiver: 5.3.2 builder-util: 24.13.1 fs-extra: 10.1.0 @@ -2323,14 +2142,14 @@ snapshots: - dmg-builder - supports-color - electron-builder@26.8.1(electron-builder-squirrel-windows@24.13.3): + electron-builder@26.15.3(electron-builder-squirrel-windows@24.13.3): dependencies: - app-builder-lib: 26.8.1(dmg-builder@26.8.1)(electron-builder-squirrel-windows@24.13.3) - builder-util: 26.8.1 - builder-util-runtime: 9.5.1 + app-builder-lib: 26.15.3(dmg-builder@26.15.3)(electron-builder-squirrel-windows@24.13.3) + builder-util: 26.15.3 + builder-util-runtime: 9.7.0 chalk: 4.1.2 ci-info: 4.3.1 - dmg-builder: 26.8.1(electron-builder-squirrel-windows@24.13.3) + dmg-builder: 26.15.3(electron-builder-squirrel-windows@24.13.3) fs-extra: 10.1.0 lazy-val: 1.0.5 simple-update-notifier: 2.0.0 @@ -2351,24 +2170,25 @@ snapshots: transitivePeerDependencies: - supports-color - electron-publish@26.8.1: + electron-publish@26.15.3: dependencies: '@types/fs-extra': 9.0.13 - builder-util: 26.8.1 - builder-util-runtime: 9.5.1 + aws4: 1.13.2 + builder-util: 26.15.3 + builder-util-runtime: 9.7.0 chalk: 4.1.2 - form-data: 4.0.5 + form-data: 4.0.6 fs-extra: 10.1.0 lazy-val: 1.0.5 mime: 2.6.0 transitivePeerDependencies: - supports-color - electron@40.9.3: + electron@40.10.4: dependencies: - '@electron/get': 2.0.3 + '@electron-internal/extract-zip': 1.0.2 + '@electron/get': 5.0.0 '@types/node': 24.10.9 - extract-zip: 2.0.1 transitivePeerDependencies: - supports-color @@ -2378,17 +2198,14 @@ snapshots: encodeurl@2.0.0: {} - encoding@0.1.13: - dependencies: - iconv-lite: 0.6.3 - optional: true - end-of-stream@1.4.5: dependencies: once: 1.4.0 env-paths@2.2.1: {} + env-paths@3.0.0: {} + err-code@2.0.3: {} es-define-property@1.0.1: {} @@ -2404,7 +2221,7 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.3.0 has-tostringtag: 1.0.2 - hasown: 2.0.2 + hasown: 2.0.4 es6-error@4.1.1: optional: true @@ -2442,7 +2259,7 @@ snapshots: once: 1.4.0 parseurl: 1.3.3 proxy-addr: 2.0.7 - qs: 6.15.0 + qs: 6.15.2 range-parser: 1.2.1 router: 2.2.0 send: 1.2.0 @@ -2453,26 +2270,11 @@ snapshots: transitivePeerDependencies: - supports-color - extract-zip@2.0.1: - dependencies: - debug: 4.4.3 - get-stream: 5.2.0 - yauzl: 2.10.0 - optionalDependencies: - '@types/yauzl': 2.10.3 - transitivePeerDependencies: - - supports-color - - extsprintf@1.4.0: - optional: true - fast-deep-equal@3.1.3: {} fast-json-stable-stringify@2.1.0: {} - fd-slicer@1.1.0: - dependencies: - pend: 1.2.0 + fast-uri@3.1.2: {} fdir@6.5.0(picomatch@4.0.4): optionalDependencies: @@ -2480,7 +2282,7 @@ snapshots: filelist@1.0.4: dependencies: - minimatch: 10.2.4 + minimatch: 10.2.5 finalhandler@2.1.1: dependencies: @@ -2498,12 +2300,12 @@ snapshots: cross-spawn: 7.0.6 signal-exit: 4.1.0 - form-data@4.0.5: + form-data@4.0.6: dependencies: asynckit: 0.4.0 combined-stream: 1.0.8 es-set-tostringtag: 2.1.0 - hasown: 2.0.2 + hasown: 2.0.4 mime-types: 2.1.35 forwarded@0.2.0: {} @@ -2543,10 +2345,6 @@ snapshots: jsonfile: 6.2.0 universalify: 2.0.1 - fs-minipass@3.0.3: - dependencies: - minipass: 7.1.2 - fs.realpath@1.0.0: {} function-bind@1.1.2: {} @@ -2579,8 +2377,8 @@ snapshots: dependencies: foreground-child: 3.3.0 jackspeak: 3.4.3 - minimatch: 10.2.4 - minipass: 7.1.2 + minimatch: 10.2.5 + minipass: 7.1.3 package-json-from-dist: 1.0.1 path-scurry: 1.11.1 @@ -2589,7 +2387,7 @@ snapshots: fs.realpath: 1.0.0 inflight: 1.0.6 inherits: 2.0.4 - minimatch: 10.2.4 + minimatch: 10.2.5 once: 1.4.0 path-is-absolute: 1.0.1 @@ -2599,7 +2397,7 @@ snapshots: es6-error: 4.1.1 matcher: 3.0.0 roarr: 2.15.4 - semver: 7.7.4 + semver: 7.8.1 serialize-error: 7.0.1 optional: true @@ -2644,6 +2442,10 @@ snapshots: dependencies: function-bind: 1.1.2 + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + hosted-git-info@4.1.0: dependencies: lru-cache: 6.0.0 @@ -2692,24 +2494,12 @@ snapshots: transitivePeerDependencies: - supports-color - iconv-corefoundation@1.1.7: - dependencies: - cli-truncate: 2.1.0 - node-addon-api: 1.7.2 - optional: true - - iconv-lite@0.6.3: - dependencies: - safer-buffer: 2.1.2 - iconv-lite@0.7.0: dependencies: safer-buffer: 2.1.2 ieee754@1.2.1: {} - imurmurhash@0.1.4: {} - inflight@1.0.6: dependencies: once: 1.4.0 @@ -2717,8 +2507,6 @@ snapshots: inherits@2.0.4: {} - ip-address@10.1.0: {} - ipaddr.js@1.9.1: {} is-ci@3.0.1: @@ -2727,12 +2515,8 @@ snapshots: is-fullwidth-code-point@3.0.0: {} - is-interactive@1.0.0: {} - is-promise@4.0.0: {} - is-unicode-supported@0.1.0: {} - isarray@1.0.0: {} isbinaryfile@4.0.10: {} @@ -2743,6 +2527,8 @@ snapshots: isexe@3.1.1: {} + isexe@4.0.0: {} + jackspeak@3.4.3: dependencies: '@isaacs/cliui': 8.0.2 @@ -2754,11 +2540,11 @@ snapshots: async: 3.2.6 chalk: 4.1.2 filelist: 1.0.4 - minimatch: 10.2.4 + minimatch: 10.2.5 jiti@2.6.1: {} - js-yaml@4.1.1: + js-yaml@4.2.0: dependencies: argparse: 2.0.1 @@ -2766,6 +2552,8 @@ snapshots: json-schema-traverse@0.4.1: {} + json-schema-traverse@1.0.0: {} + json-stringify-safe@5.0.1: optional: true @@ -2809,11 +2597,6 @@ snapshots: lodash@4.18.1: {} - log-symbols@4.1.0: - dependencies: - chalk: 4.1.2 - is-unicode-supported: 0.1.0 - lowercase-keys@2.0.0: {} lru-cache@10.4.3: {} @@ -2822,22 +2605,6 @@ snapshots: dependencies: yallist: 4.0.0 - make-fetch-happen@14.0.3: - dependencies: - '@npmcli/agent': 3.0.0 - cacache: 19.0.1 - http-cache-semantics: 4.2.0 - minipass: 7.1.2 - minipass-fetch: 4.0.1 - minipass-flush: 1.0.5 - minipass-pipeline: 1.2.4 - negotiator: 1.0.0 - proc-log: 5.0.0 - promise-retry: 2.0.1 - ssri: 12.0.0 - transitivePeerDependencies: - - supports-color - matcher@3.0.0: dependencies: escape-string-regexp: 4.0.0 @@ -2863,52 +2630,16 @@ snapshots: mime@2.6.0: {} - mimic-fn@2.1.0: {} - mimic-response@1.0.1: {} mimic-response@3.1.0: {} - minimatch@10.2.4: - dependencies: - brace-expansion: 5.0.5 - minimatch@10.2.5: dependencies: - brace-expansion: 5.0.5 + brace-expansion: 5.0.6 minimist@1.2.8: {} - minipass-collect@2.0.1: - dependencies: - minipass: 7.1.2 - - minipass-fetch@4.0.1: - dependencies: - minipass: 7.1.2 - minipass-sized: 1.0.3 - minizlib: 3.1.0 - optionalDependencies: - encoding: 0.1.13 - - minipass-flush@1.0.5: - dependencies: - minipass: 3.3.6 - - minipass-pipeline@1.2.4: - dependencies: - minipass: 3.3.6 - - minipass-sized@1.0.3: - dependencies: - minipass: 3.3.6 - - minipass@3.3.6: - dependencies: - yallist: 4.0.0 - - minipass@7.1.2: {} - minipass@7.1.3: {} minizlib@3.1.0: @@ -2921,35 +2652,30 @@ snapshots: node-abi@4.24.0: dependencies: - semver: 7.7.4 - - node-addon-api@1.7.2: - optional: true + semver: 7.8.1 node-api-version@0.2.1: dependencies: - semver: 7.7.4 + semver: 7.8.1 - node-gyp@11.5.0: + node-gyp@12.4.0: dependencies: env-paths: 2.2.1 exponential-backoff: 3.1.3 graceful-fs: 4.2.11 - make-fetch-happen: 14.0.3 - nopt: 8.1.0 - proc-log: 5.0.0 - semver: 7.7.4 - tar: 7.5.11 + nopt: 9.0.0 + proc-log: 6.1.0 + semver: 7.8.1 + tar: 7.5.16 tinyglobby: 0.2.15 - which: 5.0.0 - transitivePeerDependencies: - - supports-color + undici: 6.26.0 + which: 6.0.1 node-int64@0.4.0: {} - nopt@8.1.0: + nopt@9.0.0: dependencies: - abbrev: 3.0.1 + abbrev: 4.0.0 normalize-path@3.0.0: {} @@ -2968,30 +2694,12 @@ snapshots: dependencies: wrappy: 1.0.2 - onetime@5.1.2: - dependencies: - mimic-fn: 2.1.0 - - ora@5.4.1: - dependencies: - bl: 4.1.0 - chalk: 4.1.2 - cli-cursor: 3.1.0 - cli-spinners: 2.9.2 - is-interactive: 1.0.0 - is-unicode-supported: 0.1.0 - log-symbols: 4.1.0 - strip-ansi: 6.0.1 - wcwidth: 1.0.1 - p-cancelable@2.1.1: {} p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 - p-map@7.0.4: {} - package-json-from-dist@1.0.1: {} parseurl@1.3.3: {} @@ -3003,23 +2711,36 @@ snapshots: path-scurry@1.11.1: dependencies: lru-cache: 10.4.3 - minipass: 7.1.2 + minipass: 7.1.3 path-to-regexp@8.4.1: {} pe-library@0.4.1: {} - pend@1.2.0: {} - picomatch@4.0.4: {} + pkijs@3.4.0: + dependencies: + '@noble/hashes': 1.4.0 + asn1js: 3.0.10 + bytestreamjs: 2.0.1 + pvtsutils: 1.3.6 + pvutils: 1.1.5 + tslib: 2.8.1 + plist@3.1.0: dependencies: - '@xmldom/xmldom': 0.8.12 + '@xmldom/xmldom': 0.8.13 base64-js: 1.5.1 xmlbuilder: 15.1.1 - proc-log@5.0.0: {} + plist@3.1.1: + dependencies: + '@xmldom/xmldom': 0.9.10 + base64-js: 1.5.1 + xmlbuilder: 15.1.1 + + proc-log@6.1.0: {} process-nextick-args@2.0.1: {} @@ -3048,7 +2769,13 @@ snapshots: punycode@2.3.1: {} - qs@6.15.0: + pvtsutils@1.3.6: + dependencies: + tslib: 2.8.1 + + pvutils@1.1.5: {} + + qs@6.15.2: dependencies: side-channel: 1.1.0 @@ -3074,7 +2801,7 @@ snapshots: config-file-ts: 0.2.6 dotenv: 9.0.2 dotenv-expand: 5.1.0 - js-yaml: 4.1.1 + js-yaml: 4.2.0 json5: 2.2.3 lazy-val: 1.0.5 @@ -3100,6 +2827,8 @@ snapshots: require-directory@2.1.1: {} + require-from-string@2.0.2: {} + resedit@1.7.2: dependencies: pe-library: 0.4.1 @@ -3110,11 +2839,6 @@ snapshots: dependencies: lowercase-keys: 2.0.0 - restore-cursor@3.1.0: - dependencies: - onetime: 5.1.2 - signal-exit: 3.0.7 - retry@0.12.0: {} roarr@2.15.4: @@ -3143,16 +2867,10 @@ snapshots: safer-buffer@2.1.2: {} - sanitize-filename@1.6.3: - dependencies: - truncate-utf8-bytes: 1.0.2 - sanitize-filename@1.6.4: dependencies: truncate-utf8-bytes: 1.0.2 - sax@1.4.4: {} - sax@1.6.0: {} semver-compare@1.0.0: @@ -3164,6 +2882,8 @@ snapshots: semver@7.7.4: {} + semver@7.8.1: {} + send@1.2.0: dependencies: debug: 4.4.3 @@ -3236,29 +2956,7 @@ snapshots: simple-update-notifier@2.0.0: dependencies: - semver: 7.7.4 - - slice-ansi@3.0.0: - dependencies: - ansi-styles: 4.3.0 - astral-regex: 2.0.0 - is-fullwidth-code-point: 3.0.0 - optional: true - - smart-buffer@4.2.0: {} - - socks-proxy-agent@8.0.5: - dependencies: - agent-base: 7.1.4 - debug: 4.4.3 - socks: 2.8.7 - transitivePeerDependencies: - - supports-color - - socks@2.8.7: - dependencies: - ip-address: 10.1.0 - smart-buffer: 4.2.0 + semver: 7.8.1 source-map-support@0.5.21: dependencies: @@ -3270,10 +2968,6 @@ snapshots: sprintf-js@1.1.3: optional: true - ssri@12.0.0: - dependencies: - minipass: 7.1.2 - stat-mode@1.0.0: {} statuses@2.0.2: {} @@ -3324,15 +3018,7 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 - tar@7.5.11: - dependencies: - '@isaacs/fs-minipass': 4.0.1 - chownr: 3.0.0 - minipass: 7.1.3 - minizlib: 3.1.0 - yallist: 5.0.0 - - tar@7.5.13: + tar@7.5.16: dependencies: '@isaacs/fs-minipass': 4.0.1 chownr: 3.0.0 @@ -3356,9 +3042,9 @@ snapshots: tmp-promise@3.0.3: dependencies: - tmp: 0.2.3 + tmp: 0.2.7 - tmp@0.2.3: {} + tmp@0.2.7: {} toidentifier@1.0.1: {} @@ -3366,6 +3052,8 @@ snapshots: dependencies: utf8-byte-length: 1.0.4 + tslib@2.8.1: {} + type-fest@0.13.1: optional: true @@ -3379,13 +3067,10 @@ snapshots: undici-types@7.16.0: {} - unique-filename@4.0.0: - dependencies: - unique-slug: 5.0.0 + undici@6.26.0: {} - unique-slug@5.0.0: - dependencies: - imurmurhash: 0.1.4 + undici@7.27.2: + optional: true universalify@0.1.2: {} @@ -3411,16 +3096,13 @@ snapshots: vary@1.1.2: {} - verror@1.10.0: + webcrypto-core@1.9.2: dependencies: - assert-plus: 1.0.0 - core-util-is: 1.0.2 - extsprintf: 1.4.0 - optional: true - - wcwidth@1.0.1: - dependencies: - defaults: 1.0.4 + '@peculiar/asn1-schema': 2.7.0 + '@peculiar/json-schema': 1.1.12 + '@peculiar/utils': 2.0.3 + asn1js: 3.0.10 + tslib: 2.8.1 which@2.0.2: dependencies: @@ -3430,6 +3112,10 @@ snapshots: dependencies: isexe: 3.1.1 + which@6.0.1: + dependencies: + isexe: 4.0.0 + wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 @@ -3464,11 +3150,6 @@ snapshots: y18n: 5.0.8 yargs-parser: 21.1.1 - yauzl@2.10.0: - dependencies: - buffer-crc32: 0.2.13 - fd-slicer: 1.1.0 - yocto-queue@0.1.0: {} zip-stream@4.1.1: diff --git a/devenv.lock b/devenv.lock index 6184f1dc8..6ed2e5f71 100644 --- a/devenv.lock +++ b/devenv.lock @@ -16,62 +16,6 @@ "type": "github" } }, - "flake-compat": { - "flake": false, - "locked": { - "lastModified": 1767039857, - "owner": "NixOS", - "repo": "flake-compat", - "rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab", - "type": "github" - }, - "original": { - "owner": "NixOS", - "repo": "flake-compat", - "type": "github" - } - }, - "git-hooks": { - "inputs": { - "flake-compat": "flake-compat", - "gitignore": "gitignore", - "nixpkgs": [ - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1772893680, - "owner": "cachix", - "repo": "git-hooks.nix", - "rev": "8baab586afc9c9b57645a734c820e4ac0a604af9", - "type": "github" - }, - "original": { - "owner": "cachix", - "repo": "git-hooks.nix", - "type": "github" - } - }, - "gitignore": { - "inputs": { - "nixpkgs": [ - "git-hooks", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1762808025, - "owner": "hercules-ci", - "repo": "gitignore.nix", - "rev": "cb5e3fdca1de58ccbc3ef53de65bd372b48f567c", - "type": "github" - }, - "original": { - "owner": "hercules-ci", - "repo": "gitignore.nix", - "type": "github" - } - }, "nixpkgs": { "inputs": { "nixpkgs-src": "nixpkgs-src" @@ -125,15 +69,11 @@ "root": { "inputs": { "devenv": "devenv", - "git-hooks": "git-hooks", "nixpkgs": "nixpkgs", - "nixpkgs-unstable": "nixpkgs-unstable", - "pre-commit-hooks": [ - "git-hooks" - ] + "nixpkgs-unstable": "nixpkgs-unstable" } } }, "root": "root", "version": 7 -} +} \ No newline at end of file diff --git a/frontend/index.html b/frontend/index.html index bf4670943..9ad084d61 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -23,7 +23,6 @@ // It has to be the full url, including the last /api/v1 part and port. // You can change this if your api is not reachable on the same port as the frontend. window.API_URL = '/api/v1' - window.ALLOW_ICON_CHANGES = true diff --git a/frontend/package.json b/frontend/package.json index 482dde9c9..3347deb8f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -60,6 +60,7 @@ "@kyvg/vue3-notification": "3.4.2", "@sentry/vue": "10.36.0", "@tiptap/core": "3.17.0", + "@tiptap/extension-blockquote": "3.17.0", "@tiptap/extension-code-block-lowlight": "3.17.0", "@tiptap/extension-hard-break": "3.17.0", "@tiptap/extension-image": "3.17.0", @@ -76,12 +77,12 @@ "@tiptap/vue-3": "3.17.0", "@vueuse/core": "14.1.0", "@vueuse/router": "14.1.0", - "axios": "1.15.2", + "axios": "1.16.0", "blurhash": "2.0.5", "bulma-css-variables": "0.9.33", "change-case": "5.4.4", "dayjs": "1.11.19", - "dompurify": "3.4.0", + "dompurify": "3.4.11", "fast-deep-equal": "3.1.3", "flatpickr": "4.6.13", "floating-vue": "5.2.2", @@ -104,60 +105,60 @@ "zhyswan-vuedraggable": "4.1.3" }, "devDependencies": { - "@faker-js/faker": "10.4.0", + "@faker-js/faker": "10.5.0", "@histoire/plugin-screenshot": "1.0.0-beta.1", "@histoire/plugin-vue": "1.0.0-beta.1", "@playwright/test": "1.58.2", "@sentry/vite-plugin": "3.6.1", - "@tailwindcss/vite": "4.3.0", + "@tailwindcss/vite": "4.3.1", "@tsconfig/node24": "24.0.4", "@types/codemirror": "5.60.17", "@types/is-touch-device": "1.0.3", - "@types/node": "24.12.3", + "@types/node": "24.13.2", "@types/sortablejs": "1.15.9", "@types/ws": "8.18.1", - "@typescript-eslint/eslint-plugin": "8.59.2", - "@typescript-eslint/parser": "8.59.2", - "@vitejs/plugin-vue": "6.0.6", - "@vue/eslint-config-typescript": "14.7.0", - "@vue/test-utils": "2.4.10", + "@typescript-eslint/eslint-plugin": "8.61.1", + "@typescript-eslint/parser": "8.61.1", + "@vitejs/plugin-vue": "6.0.7", + "@vue/eslint-config-typescript": "14.8.0", + "@vue/test-utils": "2.4.11", "@vue/tsconfig": "0.9.1", "@vueuse/shared": "14.3.0", "autoprefixer": "10.5.0", "browserslist": "4.28.2", - "caniuse-lite": "1.0.30001792", + "caniuse-lite": "1.0.30001799", "csstype": "3.2.3", - "esbuild": "0.28.0", + "esbuild": "0.28.1", "eslint": "9.39.4", "eslint-plugin-depend": "1.5.0", - "eslint-plugin-vue": "10.9.1", - "happy-dom": "20.9.0", + "eslint-plugin-vue": "10.9.2", + "happy-dom": "20.10.6", "histoire": "1.0.0-beta.1", "otplib": "12.0.1", - "postcss": "8.5.14", + "postcss": "8.5.15", "postcss-easing-gradients": "3.0.1", "postcss-html": "1.8.1", - "postcss-preset-env": "11.2.1", - "rollup": "4.60.3", + "postcss-preset-env": "11.3.1", + "rollup": "4.62.2", "rollup-plugin-visualizer": "6.0.11", - "sass-embedded": "1.99.0", - "stylelint": "17.11.0", + "sass-embedded": "1.100.0", + "stylelint": "17.13.0", "stylelint-config-property-sort-order-smacss": "10.0.0", "stylelint-config-recommended-vue": "1.6.1", "stylelint-config-standard-scss": "17.0.0", "stylelint-use-logical": "2.1.3", - "tailwindcss": "4.3.0", + "tailwindcss": "4.3.1", "typescript": "5.9.3", "unplugin-inject-preload": "3.0.0", - "vite": "7.3.3", + "vite": "7.3.5", "vite-plugin-pwa": "1.3.0", - "vite-plugin-vue-devtools": "8.1.2", + "vite-plugin-vue-devtools": "8.1.3", "vite-svg-loader": "5.1.1", - "vitest": "4.1.5", - "vue-tsc": "3.2.8", - "wait-on": "9.0.5", + "vitest": "4.1.9", + "vue-tsc": "3.3.5", + "wait-on": "9.0.10", "workbox-cli": "7.4.1", - "ws": "8.20.0" + "ws": "8.21.0" }, "pnpm": { "onlyBuiltDependencies": [ @@ -172,7 +173,16 @@ "rollup": "$rollup", "basic-ftp": ">=5.2.2", "serialize-javascript": "^7.0.5", - "flatted": "^3.4.1" + "flatted": "^3.4.1", + "ip-address": ">=10.1.1", + "postcss": ">=8.5.10", + "tmp": ">=0.2.7", + "esbuild": ">=0.28.1", + "form-data": ">=4.0.6", + "markdown-it": ">=14.2.0", + "launch-editor": ">=2.14.1", + "@babel/core": ">=7.29.6", + "js-yaml@4": ">=4.2.0" } } } diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 8ec59b699..b45f3e316 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -6,10 +6,19 @@ settings: overrides: minimatch: ^10.2.3 - rollup: 4.60.3 + rollup: 4.62.2 basic-ftp: '>=5.2.2' serialize-javascript: ^7.0.5 flatted: ^3.4.1 + ip-address: '>=10.1.1' + postcss: '>=8.5.10' + tmp: '>=0.2.7' + esbuild: '>=0.28.1' + form-data: '>=4.0.6' + markdown-it: '>=14.2.0' + launch-editor: '>=2.14.1' + '@babel/core': '>=7.29.6' + js-yaml@4: '>=4.2.0' importers: @@ -32,7 +41,7 @@ importers: version: 3.1.3(@fortawesome/fontawesome-svg-core@7.1.0)(vue@3.5.27(typescript@5.9.3)) '@intlify/unplugin-vue-i18n': specifier: 11.0.3 - version: 11.0.3(@vue/compiler-dom@3.5.27)(eslint@9.39.4(jiti@2.6.1))(rollup@4.60.3)(typescript@5.9.3)(vue-i18n@11.2.8(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3)) + version: 11.0.3(@vue/compiler-dom@3.5.27)(eslint@9.39.4(jiti@2.6.1))(rollup@4.62.2)(typescript@5.9.3)(vue-i18n@11.2.8(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3)) '@kyvg/vue3-notification': specifier: 3.4.2 version: 3.4.2(vue@3.5.27(typescript@5.9.3)) @@ -42,6 +51,9 @@ importers: '@tiptap/core': specifier: 3.17.0 version: 3.17.0(@tiptap/pm@3.17.0) + '@tiptap/extension-blockquote': + specifier: 3.17.0 + version: 3.17.0(@tiptap/core@3.17.0(@tiptap/pm@3.17.0)) '@tiptap/extension-code-block-lowlight': specifier: 3.17.0 version: 3.17.0(@tiptap/core@3.17.0(@tiptap/pm@3.17.0))(@tiptap/extension-code-block@3.17.1(@tiptap/core@3.17.0(@tiptap/pm@3.17.0))(@tiptap/pm@3.17.0))(@tiptap/pm@3.17.0)(highlight.js@11.11.1)(lowlight@3.3.0) @@ -91,8 +103,8 @@ importers: specifier: 14.1.0 version: 14.1.0(vue-router@4.6.4(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3)) axios: - specifier: 1.15.2 - version: 1.15.2 + specifier: 1.16.0 + version: 1.16.0 blurhash: specifier: 2.0.5 version: 2.0.5 @@ -106,8 +118,8 @@ importers: specifier: 1.11.19 version: 1.11.19 dompurify: - specifier: 3.4.0 - version: 3.4.0 + specifier: 3.4.11 + version: 3.4.11 fast-deep-equal: specifier: 3.1.3 version: 3.1.3 @@ -170,14 +182,14 @@ importers: version: 4.1.3(vue@3.5.27(typescript@5.9.3)) devDependencies: '@faker-js/faker': - specifier: 10.4.0 - version: 10.4.0 + specifier: 10.5.0 + version: 10.5.0 '@histoire/plugin-screenshot': specifier: 1.0.0-beta.1 - version: 1.0.0-beta.1(histoire@1.0.0-beta.1(@types/node@24.12.3)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(vite@7.3.3(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3))(yaml@2.8.3))(typescript@5.9.3) + version: 1.0.0-beta.1(histoire@1.0.0-beta.1(@types/node@24.13.2)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3))(yaml@2.8.3))(typescript@5.9.3) '@histoire/plugin-vue': specifier: 1.0.0-beta.1 - version: 1.0.0-beta.1(histoire@1.0.0-beta.1(@types/node@24.12.3)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(vite@7.3.3(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3))(yaml@2.8.3))(vite@7.3.3(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3))(vue@3.5.27(typescript@5.9.3)) + version: 1.0.0-beta.1(histoire@1.0.0-beta.1(@types/node@24.13.2)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3))(yaml@2.8.3))(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3))(vue@3.5.27(typescript@5.9.3)) '@playwright/test': specifier: 1.58.2 version: 1.58.2 @@ -185,8 +197,8 @@ importers: specifier: 3.6.1 version: 3.6.1 '@tailwindcss/vite': - specifier: 4.3.0 - version: 4.3.0(vite@7.3.3(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3)) + specifier: 4.3.1 + version: 4.3.1(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3)) '@tsconfig/node24': specifier: 24.0.4 version: 24.0.4 @@ -197,8 +209,8 @@ importers: specifier: 1.0.3 version: 1.0.3 '@types/node': - specifier: 24.12.3 - version: 24.12.3 + specifier: 24.13.2 + version: 24.13.2 '@types/sortablejs': specifier: 1.15.9 version: 1.15.9 @@ -206,20 +218,20 @@ importers: specifier: 8.18.1 version: 8.18.1 '@typescript-eslint/eslint-plugin': - specifier: 8.59.2 - version: 8.59.2(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + specifier: 8.61.1 + version: 8.61.1(@typescript-eslint/parser@8.61.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) '@typescript-eslint/parser': - specifier: 8.59.2 - version: 8.59.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + specifier: 8.61.1 + version: 8.61.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) '@vitejs/plugin-vue': - specifier: 6.0.6 - version: 6.0.6(vite@7.3.3(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3))(vue@3.5.27(typescript@5.9.3)) + specifier: 6.0.7 + version: 6.0.7(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3))(vue@3.5.27(typescript@5.9.3)) '@vue/eslint-config-typescript': - specifier: 14.7.0 - version: 14.7.0(eslint-plugin-vue@10.9.1(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(vue-eslint-parser@10.4.0(eslint@9.39.4(jiti@2.6.1))))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + specifier: 14.8.0 + version: 14.8.0(eslint-plugin-vue@10.9.2(@typescript-eslint/parser@8.61.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(vue-eslint-parser@10.4.0(eslint@9.39.4(jiti@2.6.1))))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) '@vue/test-utils': - specifier: 2.4.10 - version: 2.4.10(@vue/compiler-dom@3.5.27)(@vue/server-renderer@3.5.27(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3)) + specifier: 2.4.11 + version: 2.4.11(@vue/compiler-dom@3.5.27)(@vue/server-renderer@3.5.27(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3)) '@vue/tsconfig': specifier: 0.9.1 version: 0.9.1(typescript@5.9.3)(vue@3.5.27(typescript@5.9.3)) @@ -233,14 +245,14 @@ importers: specifier: 4.28.2 version: 4.28.2 caniuse-lite: - specifier: 1.0.30001792 - version: 1.0.30001792 + specifier: 1.0.30001799 + version: 1.0.30001799 csstype: specifier: 3.2.3 version: 3.2.3 esbuild: - specifier: 0.28.0 - version: 0.28.0 + specifier: '>=0.28.1' + version: 0.28.1 eslint: specifier: 9.39.4 version: 9.39.4(jiti@2.6.1) @@ -248,19 +260,19 @@ importers: specifier: 1.5.0 version: 1.5.0(eslint@9.39.4(jiti@2.6.1)) eslint-plugin-vue: - specifier: 10.9.1 - version: 10.9.1(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(vue-eslint-parser@10.4.0(eslint@9.39.4(jiti@2.6.1))) + specifier: 10.9.2 + version: 10.9.2(@typescript-eslint/parser@8.61.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(vue-eslint-parser@10.4.0(eslint@9.39.4(jiti@2.6.1))) happy-dom: - specifier: 20.9.0 - version: 20.9.0 + specifier: 20.10.6 + version: 20.10.6 histoire: specifier: 1.0.0-beta.1 - version: 1.0.0-beta.1(@types/node@24.12.3)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(vite@7.3.3(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3))(yaml@2.8.3) + version: 1.0.0-beta.1(@types/node@24.13.2)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3))(yaml@2.8.3) otplib: specifier: 12.0.1 version: 12.0.1 postcss: - specifier: 8.5.14 + specifier: '>=8.5.10' version: 8.5.14 postcss-easing-gradients: specifier: 3.0.1 @@ -269,35 +281,35 @@ importers: specifier: 1.8.1 version: 1.8.1 postcss-preset-env: - specifier: 11.2.1 - version: 11.2.1(postcss@8.5.14) + specifier: 11.3.1 + version: 11.3.1(postcss@8.5.14) rollup: - specifier: 4.60.3 - version: 4.60.3 + specifier: 4.62.2 + version: 4.62.2 rollup-plugin-visualizer: specifier: 6.0.11 - version: 6.0.11(rollup@4.60.3) + version: 6.0.11(rollup@4.62.2) sass-embedded: - specifier: 1.99.0 - version: 1.99.0 + specifier: 1.100.0 + version: 1.100.0 stylelint: - specifier: 17.11.0 - version: 17.11.0(typescript@5.9.3) + specifier: 17.13.0 + version: 17.13.0(typescript@5.9.3) stylelint-config-property-sort-order-smacss: specifier: 10.0.0 - version: 10.0.0(stylelint@17.11.0(typescript@5.9.3)) + version: 10.0.0(stylelint@17.13.0(typescript@5.9.3)) stylelint-config-recommended-vue: specifier: 1.6.1 - version: 1.6.1(postcss-html@1.8.1)(stylelint@17.11.0(typescript@5.9.3)) + version: 1.6.1(postcss-html@1.8.1)(stylelint@17.13.0(typescript@5.9.3)) stylelint-config-standard-scss: specifier: 17.0.0 - version: 17.0.0(postcss@8.5.14)(stylelint@17.11.0(typescript@5.9.3)) + version: 17.0.0(postcss@8.5.14)(stylelint@17.13.0(typescript@5.9.3)) stylelint-use-logical: specifier: 2.1.3 - version: 2.1.3(stylelint@17.11.0(typescript@5.9.3)) + version: 2.1.3(stylelint@17.13.0(typescript@5.9.3)) tailwindcss: - specifier: 4.3.0 - version: 4.3.0 + specifier: 4.3.1 + version: 4.3.1 typescript: specifier: 5.9.3 version: 5.9.3 @@ -305,32 +317,32 @@ importers: specifier: 3.0.0 version: 3.0.0 vite: - specifier: 7.3.3 - version: 7.3.3(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3) + specifier: 7.3.5 + version: 7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3) vite-plugin-pwa: specifier: 1.3.0 - version: 1.3.0(vite@7.3.3(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3))(workbox-build@7.4.1)(workbox-window@7.4.1) + version: 1.3.0(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3))(workbox-build@7.4.1)(workbox-window@7.4.1) vite-plugin-vue-devtools: - specifier: 8.1.2 - version: 8.1.2(vite@7.3.3(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3))(vue@3.5.27(typescript@5.9.3)) + specifier: 8.1.3 + version: 8.1.3(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3))(vue@3.5.27(typescript@5.9.3)) vite-svg-loader: specifier: 5.1.1 version: 5.1.1(vue@3.5.27(typescript@5.9.3)) vitest: - specifier: 4.1.5 - version: 4.1.5(@types/node@24.12.3)(happy-dom@20.9.0)(jsdom@27.4.0)(vite@7.3.3(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3)) + specifier: 4.1.9 + version: 4.1.9(@types/node@24.13.2)(happy-dom@20.10.6)(jsdom@27.4.0)(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3)) vue-tsc: - specifier: 3.2.8 - version: 3.2.8(typescript@5.9.3) + specifier: 3.3.5 + version: 3.3.5(typescript@5.9.3) wait-on: - specifier: 9.0.5 - version: 9.0.5 + specifier: 9.0.10 + version: 9.0.10 workbox-cli: specifier: 7.4.1 version: 7.4.1 ws: - specifier: 8.20.0 - version: 8.20.0 + specifier: 8.21.0 + version: 8.21.0 packages: @@ -341,10 +353,6 @@ packages: resolution: {integrity: sha512-nznEC1ZA/m3hQDEnrGQ4c5gkaa9pcaVnw4LFJyzBAaR7E3nfiAPEHS3otnSafpZouVnoKeITl5D+2LsnwlnK8g==} engines: {node: '>=14.0.0'} - '@ampproject/remapping@2.3.0': - resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} - engines: {node: '>=6.0.0'} - '@apideck/better-ajv-errors@0.3.6': resolution: {integrity: sha512-P+ZygBLZtkp0qqOAJJVX4oX/sFo5JR3eBWwwuqHHhK0GIgQOKWrAfiAaWX0aArHkRWHMuggFEgAZNxVPwPZYaA==} engines: {node: '>=10'} @@ -360,20 +368,28 @@ packages: '@asamuzakjp/nwsapi@2.3.9': resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==} - '@babel/code-frame@7.26.2': - resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} + engines: {node: '>=6.9.0'} + + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} engines: {node: '>=6.9.0'} '@babel/compat-data@7.26.0': resolution: {integrity: sha512-qETICbZSLe7uXv9VE8T/RWOdIE5qqyTucOt4zLYMafj2MRO271VGgLd4RACJMeBO37UPWhXiKMBk7YlJ0fOzQA==} engines: {node: '>=6.9.0'} - '@babel/core@7.26.0': - resolution: {integrity: sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==} + '@babel/compat-data@7.29.7': + resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} engines: {node: '>=6.9.0'} - '@babel/generator@7.26.0': - resolution: {integrity: sha512-/AIkAmInnWwgEAJGQr9vY0c66Mj6kjkE2ZPB1PurTRaRAh3U+J45sAQMjQDJdh4WbR3l0x5xkimXBKyBXXAu2w==} + '@babel/core@7.29.7': + resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.7': + resolution: {integrity: sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==} engines: {node: '>=6.9.0'} '@babel/helper-annotate-as-pure@7.25.9': @@ -388,22 +404,30 @@ packages: resolution: {integrity: sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==} engines: {node: '>=6.9.0'} + '@babel/helper-compilation-targets@7.29.7': + resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} + engines: {node: '>=6.9.0'} + '@babel/helper-create-class-features-plugin@7.25.9': resolution: {integrity: sha512-UTZQMvt0d/rSz6KI+qdu7GQze5TIajwTS++GUozlw8VBJDEOAqSXwm1WvmYEZwqdqSGQshRocPDqrt4HBZB3fQ==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0 + '@babel/core': '>=7.29.6' '@babel/helper-create-regexp-features-plugin@7.25.9': resolution: {integrity: sha512-ORPNZ3h6ZRkOyAa/SaHU+XsLZr0UQzRwuDQ0cczIA17nAzZ+85G5cVkOJIj7QavLZGSe8QXUmNFxSZzjcZF9bw==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0 + '@babel/core': '>=7.29.6' '@babel/helper-define-polyfill-provider@0.6.2': resolution: {integrity: sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ==} peerDependencies: - '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + '@babel/core': '>=7.29.6' + + '@babel/helper-globals@7.29.7': + resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} + engines: {node: '>=6.9.0'} '@babel/helper-member-expression-to-functions@7.25.9': resolution: {integrity: sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==} @@ -413,11 +437,15 @@ packages: resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} engines: {node: '>=6.9.0'} - '@babel/helper-module-transforms@7.26.0': - resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==} + '@babel/helper-module-imports@7.29.7': + resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.29.7': + resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0 + '@babel/core': '>=7.29.6' '@babel/helper-optimise-call-expression@7.25.9': resolution: {integrity: sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==} @@ -427,17 +455,21 @@ packages: resolution: {integrity: sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==} engines: {node: '>=6.9.0'} + '@babel/helper-plugin-utils@7.28.6': + resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} + engines: {node: '>=6.9.0'} + '@babel/helper-remap-async-to-generator@7.25.9': resolution: {integrity: sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0 + '@babel/core': '>=7.29.6' '@babel/helper-replace-supers@7.25.9': resolution: {integrity: sha512-IiDqTOTBQy0sWyeXyGSC5TBJpGFXBkRynjBeXsvbhQFKj2viwJC76Epz35YLU1fpe/Am6Vppb7W7zM4fPQzLsQ==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0 + '@babel/core': '>=7.29.6' '@babel/helper-simple-access@7.25.9': resolution: {integrity: sha512-c6WHXuiaRsJTyHYLJV75t9IqsmTbItYfdj99PnzYGQZkYKvan5/2jKJ7gu31J3/BJ/A18grImSPModuyG/Eo0Q==} @@ -451,20 +483,32 @@ packages: resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.28.5': resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-option@7.25.9': resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-option@7.29.7': + resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} + engines: {node: '>=6.9.0'} + '@babel/helper-wrap-function@7.25.9': resolution: {integrity: sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g==} engines: {node: '>=6.9.0'} - '@babel/helpers@7.26.10': - resolution: {integrity: sha512-UPYc3SauzZ3JGgj87GgZ89JVdC5dj0AoetR5Bw6wj4niittNyFh6+eOGonYvJ1ao6B8lEa3Q3klS7ADZ53bc5g==} + '@babel/helpers@7.29.7': + resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} engines: {node: '>=6.9.0'} '@babel/parser@7.28.5': @@ -472,430 +516,439 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + '@babel/parser@7.29.7': + resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} + engines: {node: '>=6.0.0'} + hasBin: true + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.9': resolution: {integrity: sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0 + '@babel/core': '>=7.29.6' '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.25.9': resolution: {integrity: sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0 + '@babel/core': '>=7.29.6' '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.25.9': resolution: {integrity: sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0 + '@babel/core': '>=7.29.6' '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.25.9': resolution: {integrity: sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.13.0 + '@babel/core': '>=7.29.6' '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.25.9': resolution: {integrity: sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0 + '@babel/core': '>=7.29.6' '@babel/plugin-proposal-decorators@7.25.9': resolution: {integrity: sha512-smkNLL/O1ezy9Nhy4CNosc4Va+1wo5w4gzSZeLe6y6dM4mmHfYOCPolXQPHQxonZCF+ZyebxN9vqOolkYrSn5g==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '>=7.29.6' '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2': resolution: {integrity: sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '>=7.29.6' '@babel/plugin-syntax-decorators@7.25.9': resolution: {integrity: sha512-ryzI0McXUPJnRCvMo4lumIKZUzhYUO/ScI+Mz4YVaTLt04DHNSjEUjKVvbzQjZFLuod/cYEc07mJWhzl6v4DPg==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '>=7.29.6' '@babel/plugin-syntax-import-assertions@7.26.0': resolution: {integrity: sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '>=7.29.6' '@babel/plugin-syntax-import-attributes@7.26.0': resolution: {integrity: sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '>=7.29.6' '@babel/plugin-syntax-import-meta@7.10.4': resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '>=7.29.6' '@babel/plugin-syntax-jsx@7.25.9': resolution: {integrity: sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '>=7.29.6' '@babel/plugin-syntax-typescript@7.25.9': resolution: {integrity: sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '>=7.29.6' '@babel/plugin-syntax-unicode-sets-regex@7.18.6': resolution: {integrity: sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0 + '@babel/core': '>=7.29.6' '@babel/plugin-transform-arrow-functions@7.25.9': resolution: {integrity: sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '>=7.29.6' '@babel/plugin-transform-async-generator-functions@7.25.9': resolution: {integrity: sha512-RXV6QAzTBbhDMO9fWwOmwwTuYaiPbggWQ9INdZqAYeSHyG7FzQ+nOZaUUjNwKv9pV3aE4WFqFm1Hnbci5tBCAw==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '>=7.29.6' '@babel/plugin-transform-async-to-generator@7.25.9': resolution: {integrity: sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '>=7.29.6' '@babel/plugin-transform-block-scoped-functions@7.25.9': resolution: {integrity: sha512-toHc9fzab0ZfenFpsyYinOX0J/5dgJVA2fm64xPewu7CoYHWEivIWKxkK2rMi4r3yQqLnVmheMXRdG+k239CgA==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '>=7.29.6' '@babel/plugin-transform-block-scoping@7.25.9': resolution: {integrity: sha512-1F05O7AYjymAtqbsFETboN1NvBdcnzMerO+zlMyJBEz6WkMdejvGWw9p05iTSjC85RLlBseHHQpYaM4gzJkBGg==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '>=7.29.6' '@babel/plugin-transform-class-properties@7.25.9': resolution: {integrity: sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '>=7.29.6' '@babel/plugin-transform-class-static-block@7.26.0': resolution: {integrity: sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.12.0 + '@babel/core': '>=7.29.6' '@babel/plugin-transform-classes@7.25.9': resolution: {integrity: sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '>=7.29.6' '@babel/plugin-transform-computed-properties@7.25.9': resolution: {integrity: sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '>=7.29.6' '@babel/plugin-transform-destructuring@7.25.9': resolution: {integrity: sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '>=7.29.6' '@babel/plugin-transform-dotall-regex@7.25.9': resolution: {integrity: sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '>=7.29.6' '@babel/plugin-transform-duplicate-keys@7.25.9': resolution: {integrity: sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '>=7.29.6' '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.25.9': resolution: {integrity: sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0 + '@babel/core': '>=7.29.6' '@babel/plugin-transform-dynamic-import@7.25.9': resolution: {integrity: sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '>=7.29.6' '@babel/plugin-transform-exponentiation-operator@7.25.9': resolution: {integrity: sha512-KRhdhlVk2nObA5AYa7QMgTMTVJdfHprfpAk4DjZVtllqRg9qarilstTKEhpVjyt+Npi8ThRyiV8176Am3CodPA==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '>=7.29.6' '@babel/plugin-transform-export-namespace-from@7.25.9': resolution: {integrity: sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '>=7.29.6' '@babel/plugin-transform-for-of@7.25.9': resolution: {integrity: sha512-LqHxduHoaGELJl2uhImHwRQudhCM50pT46rIBNvtT/Oql3nqiS3wOwP+5ten7NpYSXrrVLgtZU3DZmPtWZo16A==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '>=7.29.6' '@babel/plugin-transform-function-name@7.25.9': resolution: {integrity: sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '>=7.29.6' '@babel/plugin-transform-json-strings@7.25.9': resolution: {integrity: sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '>=7.29.6' '@babel/plugin-transform-literals@7.25.9': resolution: {integrity: sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '>=7.29.6' '@babel/plugin-transform-logical-assignment-operators@7.25.9': resolution: {integrity: sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '>=7.29.6' '@babel/plugin-transform-member-expression-literals@7.25.9': resolution: {integrity: sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '>=7.29.6' '@babel/plugin-transform-modules-amd@7.25.9': resolution: {integrity: sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '>=7.29.6' '@babel/plugin-transform-modules-commonjs@7.25.9': resolution: {integrity: sha512-dwh2Ol1jWwL2MgkCzUSOvfmKElqQcuswAZypBSUsScMXvgdT8Ekq5YA6TtqpTVWH+4903NmboMuH1o9i8Rxlyg==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '>=7.29.6' - '@babel/plugin-transform-modules-systemjs@7.25.9': - resolution: {integrity: sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA==} + '@babel/plugin-transform-modules-systemjs@7.29.4': + resolution: {integrity: sha512-N7QmZ0xRZfjHOfZeQLJjwgX2zS9pdGHSVl/cjSGlo4dXMqvurfxXDMKY4RqEKzPozV78VMcd0lxyG13mlbKc4w==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '>=7.29.6' '@babel/plugin-transform-modules-umd@7.25.9': resolution: {integrity: sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '>=7.29.6' '@babel/plugin-transform-named-capturing-groups-regex@7.25.9': resolution: {integrity: sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0 + '@babel/core': '>=7.29.6' '@babel/plugin-transform-new-target@7.25.9': resolution: {integrity: sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '>=7.29.6' '@babel/plugin-transform-nullish-coalescing-operator@7.25.9': resolution: {integrity: sha512-ENfftpLZw5EItALAD4WsY/KUWvhUlZndm5GC7G3evUsVeSJB6p0pBeLQUnRnBCBx7zV0RKQjR9kCuwrsIrjWog==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '>=7.29.6' '@babel/plugin-transform-numeric-separator@7.25.9': resolution: {integrity: sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '>=7.29.6' '@babel/plugin-transform-object-rest-spread@7.25.9': resolution: {integrity: sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '>=7.29.6' '@babel/plugin-transform-object-super@7.25.9': resolution: {integrity: sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '>=7.29.6' '@babel/plugin-transform-optional-catch-binding@7.25.9': resolution: {integrity: sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '>=7.29.6' '@babel/plugin-transform-optional-chaining@7.25.9': resolution: {integrity: sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '>=7.29.6' '@babel/plugin-transform-parameters@7.25.9': resolution: {integrity: sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '>=7.29.6' '@babel/plugin-transform-private-methods@7.25.9': resolution: {integrity: sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '>=7.29.6' '@babel/plugin-transform-private-property-in-object@7.25.9': resolution: {integrity: sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '>=7.29.6' '@babel/plugin-transform-property-literals@7.25.9': resolution: {integrity: sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '>=7.29.6' '@babel/plugin-transform-regenerator@7.25.9': resolution: {integrity: sha512-vwDcDNsgMPDGP0nMqzahDWE5/MLcX8sv96+wfX7as7LoF/kr97Bo/7fI00lXY4wUXYfVmwIIyG80fGZ1uvt2qg==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '>=7.29.6' '@babel/plugin-transform-regexp-modifiers@7.26.0': resolution: {integrity: sha512-vN6saax7lrA2yA/Pak3sCxuD6F5InBjn9IcrIKQPjpsLvuHYLVroTxjdlVRHjjBWxKOqIwpTXDkOssYT4BFdRw==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0 + '@babel/core': '>=7.29.6' '@babel/plugin-transform-reserved-words@7.25.9': resolution: {integrity: sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '>=7.29.6' '@babel/plugin-transform-shorthand-properties@7.25.9': resolution: {integrity: sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '>=7.29.6' '@babel/plugin-transform-spread@7.25.9': resolution: {integrity: sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '>=7.29.6' '@babel/plugin-transform-sticky-regex@7.25.9': resolution: {integrity: sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '>=7.29.6' '@babel/plugin-transform-template-literals@7.25.9': resolution: {integrity: sha512-o97AE4syN71M/lxrCtQByzphAdlYluKPDBzDVzMmfCobUjjhAryZV0AIpRPrxN0eAkxXO6ZLEScmt+PNhj2OTw==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '>=7.29.6' '@babel/plugin-transform-typeof-symbol@7.25.9': resolution: {integrity: sha512-v61XqUMiueJROUv66BVIOi0Fv/CUuZuZMl5NkRoCVxLAnMexZ0A3kMe7vvZ0nulxMuMp0Mk6S5hNh48yki08ZA==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '>=7.29.6' '@babel/plugin-transform-typescript@7.25.9': resolution: {integrity: sha512-7PbZQZP50tzv2KGGnhh82GSyMB01yKY9scIjf1a+GfZCtInOWqUH5+1EBU4t9fyR5Oykkkc9vFTs4OHrhHXljQ==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '>=7.29.6' '@babel/plugin-transform-unicode-escapes@7.25.9': resolution: {integrity: sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '>=7.29.6' '@babel/plugin-transform-unicode-property-regex@7.25.9': resolution: {integrity: sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '>=7.29.6' '@babel/plugin-transform-unicode-regex@7.25.9': resolution: {integrity: sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '>=7.29.6' '@babel/plugin-transform-unicode-sets-regex@7.25.9': resolution: {integrity: sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0 + '@babel/core': '>=7.29.6' '@babel/preset-env@7.26.0': resolution: {integrity: sha512-H84Fxq0CQJNdPFT2DrfnylZ3cf5K43rGfWK4LJGPpjKHiZlk0/RzwEus3PDDZZg+/Er7lCA03MVacueUuXdzfw==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '>=7.29.6' '@babel/preset-modules@0.1.6-no-external-plugins': resolution: {integrity: sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==} peerDependencies: - '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0 + '@babel/core': '>=7.29.6' '@babel/runtime@7.25.4': resolution: {integrity: sha512-DSgLeL/FNcpXuzav5wfYvHCGvynXkJbn3Zvc3823AEe9nPwW9IK4UoCSS5yGymmQzN0pCPvivtgS6/8U2kkm1w==} engines: {node: '>=6.9.0'} - '@babel/template@7.26.9': - resolution: {integrity: sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==} + '@babel/template@7.29.7': + resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.25.9': - resolution: {integrity: sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==} + '@babel/traverse@7.29.7': + resolution: {integrity: sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==} engines: {node: '>=6.9.0'} '@babel/types@7.28.5': resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} engines: {node: '>=6.9.0'} + '@babel/types@7.29.7': + resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} + engines: {node: '>=6.9.0'} + '@bufbuild/protobuf@2.5.2': resolution: {integrity: sha512-foZ7qr0IsUBjzWIq+SuBLfdQCpJ1j8cTuNNT4owngTHoN5KsJb8L9t65fzz7SCeSWzescoOil/0ldqiL041ABg==} - '@cacheable/memory@2.0.7': - resolution: {integrity: sha512-RbxnxAMf89Tp1dLhXMS7ceft/PGsDl1Ip7T20z5nZ+pwIAsQ1p2izPjVG69oCLv/jfQ7HDPHTWK0c9rcAWXN3A==} + '@cacheable/memory@2.0.9': + resolution: {integrity: sha512-HdMx6DoGywB30vacDbBsITbIX4pgFqj1zsrV58jZBUw3klzkNoXhj7qOqAgledhxG7YZI5rBSJg7Zp8/VG0DuA==} - '@cacheable/utils@2.3.4': - resolution: {integrity: sha512-knwKUJEYgIfwShABS1BX6JyJJTglAFcEU7EXqzTdiGCXur4voqkiJkdgZIQtWNFhynzDWERcTYv/sETMu3uJWA==} + '@cacheable/utils@2.4.1': + resolution: {integrity: sha512-eiFgzCbIneyMlLOmNG4g9xzF7Hv3Mga4LjxjcSC/ues6VYq2+gUbQI8JqNuw/ZM8tJIeIaBGpswAsqV2V7ApgA==} '@codemirror/commands@6.8.1': resolution: {integrity: sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw==} @@ -940,8 +993,8 @@ packages: '@csstools/css-parser-algorithms': ^3.0.5 '@csstools/css-tokenizer': ^3.0.4 - '@csstools/css-calc@3.2.0': - resolution: {integrity: sha512-bR9e6o2BDB12jzN/gIbjHa5wLJ4UjD1CB9pM7ehlc0ddk6EBz+yYS1EV2MF55/HUxrHcB/hehAyt5vhsA3hx7w==} + '@csstools/css-calc@3.2.1': + resolution: {integrity: sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg==} engines: {node: '>=20.19.0'} peerDependencies: '@csstools/css-parser-algorithms': ^4.0.0 @@ -954,8 +1007,8 @@ packages: '@csstools/css-parser-algorithms': ^3.0.5 '@csstools/css-tokenizer': ^3.0.4 - '@csstools/css-color-parser@4.1.0': - resolution: {integrity: sha512-U0KhLYmy2GVj6q4T3WaAe6NPuFYCPQoE3b0dRGxejWDgcPp8TP7S5rVdM5ZrFaqu4N67X8YaPBw14dQSYx3IyQ==} + '@csstools/css-color-parser@4.1.7': + resolution: {integrity: sha512-CmjJFQTFQx/U/xNJhSjCQ0ilpesPmNQ8+eOUeM/+kDOVW33qsIjeOXc27vrQDdWVkf83ZSWwtg7kXSUvKDJ8cQ==} engines: {node: '>=20.19.0'} peerDependencies: '@csstools/css-parser-algorithms': ^4.0.0 @@ -981,6 +1034,14 @@ packages: css-tree: optional: true + '@csstools/css-syntax-patches-for-csstree@1.1.5': + resolution: {integrity: sha512-oNjBvzLq2GPZtJphCjLqXow/cHySHSgtxvKZb7OqSZ/xHgw6NWNhfad+6AB9cLeVm6eA9d/qMll3JdEHjy6M+A==} + peerDependencies: + css-tree: ^3.2.1 + peerDependenciesMeta: + css-tree: + optional: true + '@csstools/css-tokenizer@3.0.4': resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} engines: {node: '>=18'} @@ -996,257 +1057,269 @@ packages: '@csstools/css-parser-algorithms': ^4.0.0 '@csstools/css-tokenizer': ^4.0.0 - '@csstools/postcss-alpha-function@2.0.4': - resolution: {integrity: sha512-fti7+GybzvfMrv5TSU6x8rWtXWOth5nLefT5w5AKJ3F3T0bZoxlRqajF0ZUgTtnytfMd4dQ8n5UiaNmsjFA65A==} + '@csstools/postcss-alpha-function@2.0.6': + resolution: {integrity: sha512-XaMnJJqqZv4veulLELvM+5caEMcLTsFyqTrkwGKPMF+UbiM7dlQoe4K46EnwfSJIvnm91K1ZXsZSd3OuJ04p9w==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' '@csstools/postcss-cascade-layers@6.0.0': resolution: {integrity: sha512-WhsECqmrEZQGqaPlBA7JkmF/CJ2/+wetL4fkL9sOPccKd32PQ1qToFM6gqSI5rkpmYqubvbxjEJhyMTHYK0vZQ==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' - '@csstools/postcss-color-function-display-p3-linear@2.0.3': - resolution: {integrity: sha512-u8QNV2TKOxG6cqK4ZrJkpctnxdrwdNTMrkyokmCi+iuLpJegOraA0cqC7HoxF2tHhxjuXc+BxwY/Qd62SwvanQ==} + '@csstools/postcss-color-function-display-p3-linear@2.0.5': + resolution: {integrity: sha512-YzY5qI0S/CsvqvMSiDn85ZyTCRLdnywxQn+6Fv8AU17aCE/fjcor54OSdVb/HlABBTcBq+d8NlWcLz11Bmo2mQ==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' - '@csstools/postcss-color-function@5.0.3': - resolution: {integrity: sha512-BiBukIeQ7rPjx9A//9+qgJugBjX6FY9eWiojbnfIJCPulWrl8J07rCgQbFkloTXena+a6Aw5xa25weU+3MA75A==} + '@csstools/postcss-color-function@5.0.5': + resolution: {integrity: sha512-s+9fU1+sZazUNk0WyKShlfmTLC0fosxNY5x7DiD637xXbZLX2lyce23QrdRhytP3Ja1G77qUk6cRD37N1gemdQ==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' - '@csstools/postcss-color-mix-function@4.0.3': - resolution: {integrity: sha512-M8ju3iqHRXtW1/5HYuOmi9WFR5rGGFgqkPh+kXkv/eG56oYK/WYtTeIwJgdcro7lRwjlo4Ut8xqbV3Iovkwfrw==} + '@csstools/postcss-color-mix-function@4.0.5': + resolution: {integrity: sha512-eBrrzTKudOlDl2XOJzW/pzHPIkC8tGkcGpNiFO/vmevb08U1huYEINhlxr8iz4OzSqs1GtiJx4d2v5iHFOZjNw==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' - '@csstools/postcss-color-mix-variadic-function-arguments@2.0.3': - resolution: {integrity: sha512-tL46UyFjIjz7mDywoPOe/JgOpvMic0rsTUfdMBB1OHrUcCtE8MQpBILzYl/cAOtinJGu+ZQLuDhqTgTBOoeg3g==} + '@csstools/postcss-color-mix-variadic-function-arguments@2.0.5': + resolution: {integrity: sha512-O4tE1hZXfEAbTP1IC2R857KjPCLNtpsFUqY2dqgycF/3M6GuFyJI20EWwkxVZzlSFvWdIcNppwRf9pxPFn0qnA==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' - '@csstools/postcss-content-alt-text@3.0.0': - resolution: {integrity: sha512-OHa+4aCcrJtHpPWB3zptScHwpS1TUbeLR4uO0ntIz0Su/zw9SoWkVu+tDMSySSAsNtNSI3kut4fTliFwIsrHxA==} + '@csstools/postcss-container-rule-prelude-list@1.0.1': + resolution: {integrity: sha512-c5qlevVGKHU+zDbVoUGSZl1Mw7Vl1gVRKv6cdIYnaoyM+9Ou23Ian0H5Gr2ZF+lsDWovPK03hOSAbkw6HS8aTg==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' - '@csstools/postcss-contrast-color-function@3.0.3': - resolution: {integrity: sha512-YcohXq+/hfYeobKirg3oXGivDaaTfOPv568bE3jYQCn9ILpFz+RgyJR/kF7ZWh5560TTlTjeCqF4ZmVsj2zwnw==} + '@csstools/postcss-content-alt-text@3.0.1': + resolution: {integrity: sha512-mK5lCgzgV/ZC+LgnFy4rAQVMcXR6HsnX3D1+4Q5gshSQsst5TtcvHbxTdzKy1XTv09sNZHJX8CO4CEQF9zA4ug==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' - '@csstools/postcss-exponential-functions@3.0.2': - resolution: {integrity: sha512-WDrfdFJXF4M67+wniEGr/5XVzsmn1rt2lL1YAlTfE7x7XDlRstTc5e+HuFoGv6jkiMWTwPsiADJaLwsnGC3UjQ==} + '@csstools/postcss-contrast-color-function@3.0.5': + resolution: {integrity: sha512-gfdTZ4a5ioL2zM/yN2FqExy6rql+6egkI5sDuK9MvrbfrVJMzB0OjiCkboT5UprU/P0JwfTiIutW1ZSyqK4Icw==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' + + '@csstools/postcss-exponential-functions@3.0.3': + resolution: {integrity: sha512-mB/NoeHLBHh0LZiVSrFdRDA/NxSfmg4tSN9117IJH9bdC2BzSTVgc82h3Gu/sdBXay6kDH2sA7fbkTigMiEi2A==} + engines: {node: '>=20.19.0'} + peerDependencies: + postcss: '>=8.5.10' '@csstools/postcss-font-format-keywords@5.0.0': resolution: {integrity: sha512-M1EjCe/J3u8fFhOZgRci74cQhJ7R0UFBX6T+WqoEvjrr8hVfMiV+HTYrzxLY5OW8YllvXYr5Q5t5OvJbsUSeDg==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' '@csstools/postcss-font-width-property@1.0.0': resolution: {integrity: sha512-AvmySApdijbjYQuXXh95tb7iVnqZBbJrv3oajO927ksE/mDmJBiszm+psW8orL2lRGR8j6ZU5Uv9/ou2Z5KRKA==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' - '@csstools/postcss-gamut-mapping@3.0.3': - resolution: {integrity: sha512-3v5ZvcVuynhFh5qCJX2LIJ9Iry8/SvxfOEj6vDngNxbH/3OKTZBFLgK+DgLuIbsP1DLA9LLH3Rn7jmRxXgEDLA==} + '@csstools/postcss-gamut-mapping@3.0.5': + resolution: {integrity: sha512-X6XkKkR9R8KyJey9n1ryEzzfX6WpihPz/JBsyIVvxAlztQcMjMA7I9mMybWVv3ZyRMC+0+H7RlIUe85vZkasNQ==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' - '@csstools/postcss-gradients-interpolation-method@6.0.3': - resolution: {integrity: sha512-wrRIaRv1dkq30a8nvYWtSAf41bwCl+sVzLBKGnqeOwk81aSktKN3NattJpkiPyoOtEoFqChisl3WH3Csj/rOsw==} + '@csstools/postcss-gradients-interpolation-method@6.0.5': + resolution: {integrity: sha512-wXiZI6bLRAGcw7XuzsqqPnTVNrHFkHTkcymK2su+ynJjemfCdpCD9HdG+ICikPqtQ782r6LSZdyC3cDhSQqF3Q==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' - '@csstools/postcss-hwb-function@5.0.3': - resolution: {integrity: sha512-bHz0uc/PBg2wJEAlGinUf494nMyuXsVKH/fExc2xGkvL6WHOKlxzx/lkn+2AVCQACtWBLVRCBDgDnkYr4RSC9w==} + '@csstools/postcss-hwb-function@5.0.5': + resolution: {integrity: sha512-HeJOXAMr1nYHZ7gJT1+6d899X9Y+5qJcpbLJ8WzhujQOIB4oqbzeP3769sd1xl3eH4qbasxtewxr4crs08SEQw==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' - '@csstools/postcss-ic-unit@5.0.0': - resolution: {integrity: sha512-/ws5d6c4uKqfM9zIL3ugcGI+3fvZEOOkJHNzAyTAGJIdZ+aSL9BVPNlHGV4QzmL0vqBSCOdU3+rhcMEj3+KzYw==} + '@csstools/postcss-ic-unit@5.0.1': + resolution: {integrity: sha512-jmsVLXPdMBTlaJAhiEijhIR3qL0j75MrlRfhJEs91DF1Wlt2kpJTDsbpXQpYFzn1nPFHZC/WEf+Mw0I/HXkHzQ==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' + + '@csstools/postcss-image-function@1.0.0': + resolution: {integrity: sha512-iuQztV6Cfeuc7NczazfickrzEhALOpxUS0yWgGkmRY1zZ0CKjBBFc/7WWSN9qupfpNAzHY7cPNcJCqUhtr+YMw==} + engines: {node: '>=20.19.0'} + peerDependencies: + postcss: '>=8.5.10' '@csstools/postcss-initial@3.0.0': resolution: {integrity: sha512-UVUrFmrTQyLomVepnjWlbBg7GoscLmXLwYFyjbcEnmpeGW7wde6lNpx5eM3eVwZI2M+7hCE3ykYnAsEPLcLa+Q==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' '@csstools/postcss-is-pseudo-class@6.0.0': resolution: {integrity: sha512-1Hdy/ykg9RDo8vU8RiM2o+RaXO39WpFPaIkHxlAEJFofle/lc33tdQMKhBk3jR/Fe+uZNLOs3HlowFafyFptVw==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' - '@csstools/postcss-light-dark-function@3.0.0': - resolution: {integrity: sha512-s++V5/hYazeRUCYIn2lsBVzUsxdeC46gtwpgW6lu5U/GlPOS5UTDT14kkEyPgXmFbCvaWLREqV7YTMJq1K3G6w==} + '@csstools/postcss-light-dark-function@3.0.1': + resolution: {integrity: sha512-tD2MMJmZ6XXCHgDythLHcXQDNi5z7KEEWPe7JeB3vPcw+YMuMabpW5ugRqndhIrui+vduhc0Md7f7yGPCmOErg==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' '@csstools/postcss-logical-float-and-clear@4.0.0': resolution: {integrity: sha512-NGzdIRVj/VxOa/TjVdkHeyiJoDihONV0+uB0csUdgWbFFr8xndtfqK8iIGP9IKJzco+w0hvBF2SSk2sDSTAnOQ==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' '@csstools/postcss-logical-overflow@3.0.0': resolution: {integrity: sha512-5cRg93QXVskM0MNepHpPcL0WLSf5Hncky0DrFDQY/4ozbH5lH7SX5ejayVpNTGSX7IpOvu7ykQDLOdMMGYzwpA==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' '@csstools/postcss-logical-overscroll-behavior@3.0.0': resolution: {integrity: sha512-82Jnl/5Wi5jb19nQE1XlBHrZcNL3PzOgcj268cDkfwf+xi10HBqufGo1Unwf5n8bbbEFhEKgyQW+vFsc9iY1jw==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' '@csstools/postcss-logical-resize@4.0.0': resolution: {integrity: sha512-L0T3q0gei/tGetCGZU0c7VN77VTivRpz1YZRNxjXYmW+85PKeI6U9YnSvDqLU2vBT2uN4kLEzfgZ0ThIZpN18A==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' '@csstools/postcss-logical-viewport-units@4.0.0': resolution: {integrity: sha512-TA3AqVN/1IH3dKRC2UUWvprvwyOs2IeD7FDZk5Hz20w4q33yIuSg0i0gjyTUkcn90g8A4n7QpyZ2AgBrnYPnnA==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' - '@csstools/postcss-media-minmax@3.0.2': - resolution: {integrity: sha512-+ABxs2ZhJDhy+B9PJg7pgkGq6/d3XPXsWl7+6yZfAk4b2ba6aQ1h2AiTn04XwS6rpMpZEF3tONli/ubfu4y8AQ==} + '@csstools/postcss-media-minmax@3.0.3': + resolution: {integrity: sha512-ch1tNS+1QayiHTGsyc53zv3AzrSd0zigjbkfLxoeuzzJyn32+P3V7em3u5vLVnqLMzBbEZK//GI13EVTIPRdDA==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' '@csstools/postcss-media-queries-aspect-ratio-number-values@4.0.0': resolution: {integrity: sha512-FDdC3lbrj8Vr0SkGIcSLTcRB7ApG6nlJFxOxkEF2C5hIZC1jtgjISFSGn/WjFdVkn8Dqe+Vx9QXI3axS2w1XHw==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' '@csstools/postcss-mixins@1.0.0': resolution: {integrity: sha512-rz6qjT2w9L3k65jGc2dX+3oGiSrYQ70EZPDrINSmSVoVys7lLBFH0tvEa8DW2sr9cbRVD/W+1sy8+7bfu0JUfg==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' '@csstools/postcss-nested-calc@5.0.0': resolution: {integrity: sha512-aPSw8P60e/i9BEfugauhikBqgjiwXcw3I9o4vXs+hktl4NSTgZRI0QHimxk9mst8N01A2TKDBxOln3mssRxiHQ==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' '@csstools/postcss-normalize-display-values@5.0.1': resolution: {integrity: sha512-FcbEmoxDEGYvm2W3rQzVzcuo66+dDJjzzVDs+QwRmZLHYofGmMGwIKPqzF86/YW+euMDa7sh1xjWDvz/fzByZQ==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' - '@csstools/postcss-oklab-function@5.0.3': - resolution: {integrity: sha512-vTMgJFMwMt9gnPvhKaDnMR7E/h9Nb+rPUv825SY5VUo4PWj+w0OH/N2NqgvjYeubaA3BVckbKDlvADATRpD4Hw==} + '@csstools/postcss-oklab-function@5.0.5': + resolution: {integrity: sha512-A+Nkzj2ODvQboM5FlqEcp0iqilyVo78f9FMx/3cHrRrEBqCymSXvf8sa1cTY54lJoUVI3Sn9XysgvYaVIAuIYg==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' '@csstools/postcss-position-area-property@2.0.0': resolution: {integrity: sha512-TeEfzsJGB23Syv7yCm8AHCD2XTFujdjr9YYu9ebH64vnfCEvY4BG319jXAYSlNlf3Yc9PNJ6WnkDkUF5XVgSKQ==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' - '@csstools/postcss-progressive-custom-properties@5.0.0': - resolution: {integrity: sha512-NsJoZ89rxmDrUsITf8QIk5w+lQZQ8Xw5K6cLFG+cfiffsLYHb3zcbOOrHLetGl1WIhjWWQ4Cr8MMrg46Q+oACg==} + '@csstools/postcss-progressive-custom-properties@5.1.0': + resolution: {integrity: sha512-lt/4yHy2GdKcGVpK4OGhBdSIq+z2PXynSusSRggn/T4y7uFurYAhdHqo/aYM+xI37vNb8rJlEKchqKKvVCXROQ==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' '@csstools/postcss-property-rule-prelude-list@2.0.0': resolution: {integrity: sha512-qcMAkc9AhpzHgmQCD8hoJgGYifcOAxd1exXjjxilMM6euwRE619xDa4UsKBCv/v4g+sS63sd6c29LPM8s2ylSQ==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' - '@csstools/postcss-random-function@3.0.2': - resolution: {integrity: sha512-iQ3vfX1LIqRXX7P1/ol45EpJ5CTWdQCAfdpTlHlsRPU4jMQeepmeNjQ0F60bj8RWTS1RkJ318fzzq4mUlyZ7hA==} + '@csstools/postcss-random-function@3.0.3': + resolution: {integrity: sha512-0EScyKxscGonwpi30Hj9DEAr0X8D2eDhOqqayQXE91gIqGli9UT+deLYqoogZLOy5GT+ncqltMqztc/q+0UkhA==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' - '@csstools/postcss-relative-color-syntax@4.0.3': - resolution: {integrity: sha512-SZSImz4KufmLi0dRwYivWXlza+7HF84SRApY8R48SyWgn+f0gDvmCn7D2Ie4CED7qU0JJK+YfCUC1HVlaQ10dg==} + '@csstools/postcss-relative-color-syntax@4.0.5': + resolution: {integrity: sha512-kBzf+LIm824cpjsZPhNtl/2N1KK+TXnxy8Kce4y+pEAQSrxhpX6WDUg54wjdHBGx2UZUXKBnlaUOsc71sSRDvg==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' '@csstools/postcss-scope-pseudo-class@5.0.0': resolution: {integrity: sha512-kBrBFJcAji3MSHS4qQIihPvJfJC5xCabXLbejqDMiQi+86HD4eMBiTayAo46Urg7tlEmZZQFymFiJt+GH6nvXw==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' - '@csstools/postcss-sign-functions@2.0.2': - resolution: {integrity: sha512-vOxkkMCMVnyaj7CW03uKR2R/zhJaCrptsXlm31HgI/dqC1lSIGnmu5W7N68x23XwcSgc8fE/fg0jKj4x1XFH4w==} + '@csstools/postcss-sign-functions@2.0.3': + resolution: {integrity: sha512-2BCPwlpeQweTC/8S8oQFYhYD5kxYkiroLf3AUJV2kVoKkSZ+4WM4rSwySXlKrqXL8HfCryAwVrJg7B0jr/RnOw==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' - '@csstools/postcss-stepped-value-functions@5.0.2': - resolution: {integrity: sha512-4PtqkRoBcMSxZG00gcDv+nq7cxVUua+Yd7TmG16qzJjdolyICHkx1RfhNL5mKSnWOLxUnk/IdxAoWN+KU7E/ng==} + '@csstools/postcss-stepped-value-functions@5.0.3': + resolution: {integrity: sha512-nXMFQBz5Pi2LLG02iqm2k+scrqwtqJT9ta/gN8S79oBZ23M0E7O3wDJ20//3z5Q6HU5e+K0n+SmmxN6iWtbm6w==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' '@csstools/postcss-syntax-descriptor-syntax-production@2.0.0': resolution: {integrity: sha512-elYcbdiBXAkPqvojB9kIBRuHY6htUhjSITtFQ+XiXnt6SvZCbNGxQmaaw6uZ7SPHu/+i/XVjzIt09/1k3SIerQ==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' '@csstools/postcss-system-ui-font-family@2.0.0': resolution: {integrity: sha512-FyGZCgchFImFyiHS2x3rD5trAqatf/x23veBLTIgbaqyFfna6RNBD+Qf8HRSjt6HGMXOLhAjxJ3OoZg0bbn7Qw==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' '@csstools/postcss-text-decoration-shorthand@5.0.3': resolution: {integrity: sha512-62fjggvIM1YYfDJPcErMUDkEZB6CByG8neTJqexnZe1hRBgCjD4dnXDLoCSSurjs1LzjBq6irFDpDaOvDZfrlw==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' - '@csstools/postcss-trigonometric-functions@5.0.2': - resolution: {integrity: sha512-hRansZmQk1HH11WGUNlWy8H/DCB9Wy6zDbRcyBfF2UUP+V2fubK+qwmq0q6LIDje5gRzxlKyWhgFYxPy1ohivA==} + '@csstools/postcss-trigonometric-functions@5.0.3': + resolution: {integrity: sha512-p9LTvLj+DFpl5RHbG/X9QGwg7BoMOBsRBZqsUAKKVvCw7MRCsk1P1llTUR/MW5nyZ4IsjFGDtDwTTj1reJjxvg==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' '@csstools/postcss-unset-value@5.0.0': resolution: {integrity: sha512-EoO54sS2KCIfesvHyFYAW99RtzwHdgaJzhl7cqKZSaMYKZv3fXSOehDjAQx8WZBKn1JrMd7xJJI1T1BxPF7/jA==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' '@csstools/selector-resolve-nested@4.0.0': resolution: {integrity: sha512-9vAPxmp+Dx3wQBIUwc1v7Mdisw1kbbaGqXUM8QLTgWg7SoPGYtXBsMXvsFs/0Bn5yoFhcktzxNZGNaUt0VjgjA==} @@ -1264,472 +1337,160 @@ packages: resolution: {integrity: sha512-etDqA/4jYvOGBM6yfKCOsEXfH96BKztZdgGmGqKi2xHnDe0ILIBraRspwgYatJH9JsCZ5HCGoCst8w18EKOAdg==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' - '@esbuild/aix-ppc64@0.25.12': - resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + '@esbuild/aix-ppc64@0.28.1': + resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/aix-ppc64@0.27.5': - resolution: {integrity: sha512-nGsF/4C7uzUj+Nj/4J+Zt0bYQ6bz33Phz8Lb2N80Mti1HjGclTJdXZ+9APC4kLvONbjxN1zfvYNd8FEcbBK/MQ==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - - '@esbuild/aix-ppc64@0.28.0': - resolution: {integrity: sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - - '@esbuild/android-arm64@0.25.12': - resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + '@esbuild/android-arm64@0.28.1': + resolution: {integrity: sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm64@0.27.5': - resolution: {integrity: sha512-Oeghq+XFgh1pUGd1YKs4DDoxzxkoUkvko+T/IVKwlghKLvvjbGFB3ek8VEDBmNvqhwuL0CQS3cExdzpmUyIrgA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm64@0.28.0': - resolution: {integrity: sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm@0.25.12': - resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + '@esbuild/android-arm@0.28.1': + resolution: {integrity: sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-arm@0.27.5': - resolution: {integrity: sha512-Cv781jd0Rfj/paoNrul1/r4G0HLvuFKYh7C9uHZ2Pl8YXstzvCyyeWENTFR9qFnRzNMCjXmsulZuvosDg10Mog==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - - '@esbuild/android-arm@0.28.0': - resolution: {integrity: sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - - '@esbuild/android-x64@0.25.12': - resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} + '@esbuild/android-x64@0.28.1': + resolution: {integrity: sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/android-x64@0.27.5': - resolution: {integrity: sha512-nQD7lspbzerlmtNOxYMFAGmhxgzn8Z7m9jgFkh6kpkjsAhZee1w8tJW3ZlW+N9iRePz0oPUDrYrXidCPSImD0Q==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - - '@esbuild/android-x64@0.28.0': - resolution: {integrity: sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - - '@esbuild/darwin-arm64@0.25.12': - resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + '@esbuild/darwin-arm64@0.28.1': + resolution: {integrity: sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-arm64@0.27.5': - resolution: {integrity: sha512-I+Ya/MgC6rr8oRWGRDF3BXDfP8K1BVUggHqN6VI2lUZLdDi1IM1v2cy0e3lCPbP+pVcK3Tv8cgUhHse1kaNZZw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-arm64@0.28.0': - resolution: {integrity: sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-x64@0.25.12': - resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} + '@esbuild/darwin-x64@0.28.1': + resolution: {integrity: sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/darwin-x64@0.27.5': - resolution: {integrity: sha512-MCjQUtC8wWJn/pIPM7vQaO69BFgwPD1jriEdqwTCKzWjGgkMbcg+M5HzrOhPhuYe1AJjXlHmD142KQf+jnYj8A==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - - '@esbuild/darwin-x64@0.28.0': - resolution: {integrity: sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - - '@esbuild/freebsd-arm64@0.25.12': - resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + '@esbuild/freebsd-arm64@0.28.1': + resolution: {integrity: sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-arm64@0.27.5': - resolution: {integrity: sha512-X6xVS+goSH0UelYXnuf4GHLwpOdc8rgK/zai+dKzBMnncw7BTQIwquOodE7EKvY2UVUetSqyAfyZC1D+oqLQtg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-arm64@0.28.0': - resolution: {integrity: sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.25.12': - resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} + '@esbuild/freebsd-x64@0.28.1': + resolution: {integrity: sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/freebsd-x64@0.27.5': - resolution: {integrity: sha512-233X1FGo3a8x1ekLB6XT69LfZ83vqz+9z3TSEQCTYfMNY880A97nr81KbPcAMl9rmOFp11wO0dP+eB18KU/Ucg==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.28.0': - resolution: {integrity: sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - - '@esbuild/linux-arm64@0.25.12': - resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + '@esbuild/linux-arm64@0.28.1': + resolution: {integrity: sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm64@0.27.5': - resolution: {integrity: sha512-euKkilsNOv7x/M1NKsx5znyprbpsRFIzTV6lWziqJch7yWYayfLtZzDxDTl+LSQDJYAjd9TVb/Kt5UKIrj2e4A==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm64@0.28.0': - resolution: {integrity: sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm@0.25.12': - resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + '@esbuild/linux-arm@0.28.1': + resolution: {integrity: sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-arm@0.27.5': - resolution: {integrity: sha512-0wkVrYHG4sdCCN/bcwQ7yYMXACkaHc3UFeaEOwSVW6e5RycMageYAFv+JS2bKLwHyeKVUvtoVH+5/RHq0fgeFw==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-arm@0.28.0': - resolution: {integrity: sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-ia32@0.25.12': - resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} + '@esbuild/linux-ia32@0.28.1': + resolution: {integrity: sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-ia32@0.27.5': - resolution: {integrity: sha512-hVRQX4+P3MS36NxOy24v/Cdsimy/5HYePw+tmPqnNN1fxV0bPrFWR6TMqwXPwoTM2VzbkA+4lbHWUKDd5ZDA/w==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-ia32@0.28.0': - resolution: {integrity: sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-loong64@0.25.12': - resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} + '@esbuild/linux-loong64@0.28.1': + resolution: {integrity: sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-loong64@0.27.5': - resolution: {integrity: sha512-mKqqRuOPALI8nDzhOBmIS0INvZOOFGGg5n1osGIXAx8oersceEbKd4t1ACNTHM3sJBXGFAlEgqM+svzjPot+ZQ==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-loong64@0.28.0': - resolution: {integrity: sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-mips64el@0.25.12': - resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + '@esbuild/linux-mips64el@0.28.1': + resolution: {integrity: sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-mips64el@0.27.5': - resolution: {integrity: sha512-EE/QXH9IyaAj1qeuIV5+/GZkBTipgGO782Ff7Um3vPS9cvLhJJeATy4Ggxikz2inZ46KByamMn6GqtqyVjhenA==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-mips64el@0.28.0': - resolution: {integrity: sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-ppc64@0.25.12': - resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} + '@esbuild/linux-ppc64@0.28.1': + resolution: {integrity: sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-ppc64@0.27.5': - resolution: {integrity: sha512-0V2iF1RGxBf1b7/BjurA5jfkl7PtySjom1r6xOK2q9KWw/XCpAdtB6KNMO+9xx69yYfSCRR9FE0TyKfHA2eQMw==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-ppc64@0.28.0': - resolution: {integrity: sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-riscv64@0.25.12': - resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + '@esbuild/linux-riscv64@0.28.1': + resolution: {integrity: sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-riscv64@0.27.5': - resolution: {integrity: sha512-rYxThBx6G9HN6tFNuvB/vykeLi4VDsm5hE5pVwzqbAjZEARQrWu3noZSfbEnPZ/CRXP3271GyFk/49up2W190g==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-riscv64@0.28.0': - resolution: {integrity: sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-s390x@0.25.12': - resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} + '@esbuild/linux-s390x@0.28.1': + resolution: {integrity: sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-s390x@0.27.5': - resolution: {integrity: sha512-uEP2q/4qgd8goEUc4QIdU/1P2NmEtZ/zX5u3OpLlCGhJIuBIv0s0wr7TB2nBrd3/A5XIdEkkS5ZLF0ULuvaaYQ==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-s390x@0.28.0': - resolution: {integrity: sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-x64@0.25.12': - resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + '@esbuild/linux-x64@0.28.1': + resolution: {integrity: sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/linux-x64@0.27.5': - resolution: {integrity: sha512-+Gq47Wqq6PLOOZuBzVSII2//9yyHNKZLuwfzCemqexqOQCSz0zy0O26kIzyp9EMNMK+nZ0tFHBZrCeVUuMs/ew==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - - '@esbuild/linux-x64@0.28.0': - resolution: {integrity: sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - - '@esbuild/netbsd-arm64@0.25.12': - resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + '@esbuild/netbsd-arm64@0.28.1': + resolution: {integrity: sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-arm64@0.27.5': - resolution: {integrity: sha512-3F/5EG8VHfN/I+W5cO1/SV2H9Q/5r7vcHabMnBqhHK2lTWOh3F8vixNzo8lqxrlmBtZVFpW8pmITHnq54+Tq4g==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - - '@esbuild/netbsd-arm64@0.28.0': - resolution: {integrity: sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - - '@esbuild/netbsd-x64@0.25.12': - resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} + '@esbuild/netbsd-x64@0.28.1': + resolution: {integrity: sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/netbsd-x64@0.27.5': - resolution: {integrity: sha512-28t+Sj3CPN8vkMOlZotOmDgilQwVvxWZl7b8rxpn73Tt/gCnvrHxQUMng4uu3itdFvrtba/1nHejvxqz8xgEMA==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - - '@esbuild/netbsd-x64@0.28.0': - resolution: {integrity: sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - - '@esbuild/openbsd-arm64@0.25.12': - resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + '@esbuild/openbsd-arm64@0.28.1': + resolution: {integrity: sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-arm64@0.27.5': - resolution: {integrity: sha512-Doz/hKtiuVAi9hMsBMpwBANhIZc8l238U2Onko3t2xUp8xtM0ZKdDYHMnm/qPFVthY8KtxkXaocwmMh6VolzMA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - - '@esbuild/openbsd-arm64@0.28.0': - resolution: {integrity: sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.25.12': - resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + '@esbuild/openbsd-x64@0.28.1': + resolution: {integrity: sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/openbsd-x64@0.27.5': - resolution: {integrity: sha512-WfGVaa1oz5A7+ZFPkERIbIhKT4olvGl1tyzTRaB5yoZRLqC0KwaO95FeZtOdQj/oKkjW57KcVF944m62/0GYtA==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.28.0': - resolution: {integrity: sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - - '@esbuild/openharmony-arm64@0.25.12': - resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + '@esbuild/openharmony-arm64@0.28.1': + resolution: {integrity: sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] - '@esbuild/openharmony-arm64@0.27.5': - resolution: {integrity: sha512-Xh+VRuh6OMh3uJ0JkCjI57l+DVe7VRGBYymen8rFPnTVgATBwA6nmToxM2OwTlSvrnWpPKkrQUj93+K9huYC6A==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] - - '@esbuild/openharmony-arm64@0.28.0': - resolution: {integrity: sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] - - '@esbuild/sunos-x64@0.25.12': - resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} + '@esbuild/sunos-x64@0.28.1': + resolution: {integrity: sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/sunos-x64@0.27.5': - resolution: {integrity: sha512-aC1gpJkkaUADHuAdQfuVTnqVUTLqqUNhAvEwHwVWcnVVZvNlDPGA0UveZsfXJJ9T6k9Po4eHi3c02gbdwO3g6w==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - - '@esbuild/sunos-x64@0.28.0': - resolution: {integrity: sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - - '@esbuild/win32-arm64@0.25.12': - resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + '@esbuild/win32-arm64@0.28.1': + resolution: {integrity: sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-arm64@0.27.5': - resolution: {integrity: sha512-0UNx2aavV0fk6UpZcwXFLztA2r/k9jTUa7OW7SAea1VYUhkug99MW1uZeXEnPn5+cHOd0n8myQay6TlFnBR07w==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-arm64@0.28.0': - resolution: {integrity: sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-ia32@0.25.12': - resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + '@esbuild/win32-ia32@0.28.1': + resolution: {integrity: sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-ia32@0.27.5': - resolution: {integrity: sha512-5nlJ3AeJWCTSzR7AEqVjT/faWyqKU86kCi1lLmxVqmNR+j4HrYdns+eTGjS/vmrzCIe8inGQckUadvS0+JkKdQ==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-ia32@0.28.0': - resolution: {integrity: sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-x64@0.25.12': - resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - - '@esbuild/win32-x64@0.27.5': - resolution: {integrity: sha512-PWypQR+d4FLfkhBIV+/kHsUELAnMpx1bRvvsn3p+/sAERbnCzFrtDRG2Xw5n+2zPxBK2+iaP+vetsRl4Ti7WgA==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - - '@esbuild/win32-x64@0.28.0': - resolution: {integrity: sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==} + '@esbuild/win32-x64@0.28.1': + resolution: {integrity: sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==} engines: {node: '>=18'} cpu: [x64] os: [win32] @@ -1781,8 +1542,8 @@ packages: '@exodus/crypto': optional: true - '@faker-js/faker@10.4.0': - resolution: {integrity: sha512-sDBWI3yLy8EcDzgobvJTWq1MJYzAkQdpjXuPukga9wXonhpMRvd1Izuo2Qgwey2OiEoRIBr35RMU9HJRoOHzpw==} + '@faker-js/faker@10.5.0': + resolution: {integrity: sha512-bsxD8WLS5lIj7aaoCx1YJkktqYj5vlBUE6HWzu2Q51ksrGJ0H737ECCKlFU7Yf8Br45z9t99frBp/J7kzbMPAg==} engines: {node: ^20.19.0 || ^22.13.0 || ^23.5.0 || >=24.0.0, npm: '>=10'} '@floating-ui/core@1.7.3': @@ -1957,9 +1718,8 @@ packages: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} - '@jridgewell/gen-mapping@0.3.5': - resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} - engines: {node: '>=6.0.0'} + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} '@jridgewell/remapping@2.3.5': resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} @@ -1968,24 +1728,20 @@ packages: resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} - '@jridgewell/set-array@1.2.1': - resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} - engines: {node: '>=6.0.0'} - '@jridgewell/source-map@0.3.6': resolution: {integrity: sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==} '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} - '@jridgewell/trace-mapping@0.3.25': - resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - '@keyv/bigmap@1.3.0': - resolution: {integrity: sha512-KT01GjzV6AQD5+IYrcpoYLkCu1Jod3nau1Z7EsEuViO3TZGRacSbO9MfHmbJ1WaOXFtWLxPVj169cn2WNKPkIg==} + '@keyv/bigmap@1.3.1': + resolution: {integrity: sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==} engines: {node: '>= 18'} peerDependencies: - keyv: ^5.5.4 + keyv: ^5.6.0 '@keyv/serialize@1.1.1': resolution: {integrity: sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==} @@ -2175,16 +1931,16 @@ packages: '@remusao/trie@1.5.0': resolution: {integrity: sha512-UX+3utJKgwCsg6sUozjxd38gNMVRXrY4TNX9VvCdSrlZBS1nZjRPi98ON3QjRAdf6KCguJFyQARRsulTeqQiPg==} - '@rolldown/pluginutils@1.0.0-rc.13': - resolution: {integrity: sha512-3ngTAv6F/Py35BsYbeeLeecvhMKdsKm4AoOETVhAA+Qc8nrA2I0kF7oa93mE9qnIurngOSpMnQ0x2nQY2FPviA==} + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} '@rollup/plugin-babel@6.1.0': resolution: {integrity: sha512-dFZNuFD2YRcoomP4oYf+DvQNSUA9ih+A3vUqopQx5EdtPGo3WBnQcI/S8pwpz91UsGfL0HsMSOlaMld8HrbubA==} engines: {node: '>=14.0.0'} peerDependencies: - '@babel/core': ^7.0.0 + '@babel/core': '>=7.29.6' '@types/babel__core': ^7.1.9 - rollup: 4.60.3 + rollup: 4.62.2 peerDependenciesMeta: '@types/babel__core': optional: true @@ -2195,7 +1951,7 @@ packages: resolution: {integrity: sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg==} engines: {node: '>=14.0.0'} peerDependencies: - rollup: 4.60.3 + rollup: 4.62.2 peerDependenciesMeta: rollup: optional: true @@ -2204,7 +1960,7 @@ packages: resolution: {integrity: sha512-J4RZarRvQAm5IF0/LwUUg+obsm+xZhYnbMXmXROyoSE1ATJe3oXSb9L5MMppdxP2ylNSjv6zFBwKYjcKMucVfA==} engines: {node: '>=14.0.0'} peerDependencies: - rollup: 4.60.3 + rollup: 4.62.2 peerDependenciesMeta: rollup: optional: true @@ -2213,7 +1969,7 @@ packages: resolution: {integrity: sha512-FnCxhTBx6bMOYQrar6C8h3scPt8/JwIzw3+AJ2K++6guogH5fYaIFia+zZuhqv0eo1RN7W1Pz630SyvLbDjhtQ==} engines: {node: '>=20.0.0'} peerDependencies: - rollup: 4.60.3 + rollup: 4.62.2 peerDependenciesMeta: rollup: optional: true @@ -2222,133 +1978,133 @@ packages: resolution: {integrity: sha512-Pnsb6f32CD2W3uCaLZIzDmeFyQ2b8UWMFI7xtwUezpcGBDVDW6y9XgAWIlARiGAo6eNF5FK5aQTr0LFyNyqq5A==} engines: {node: '>=14.0.0'} peerDependencies: - rollup: 4.60.3 + rollup: 4.62.2 peerDependenciesMeta: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.60.3': - resolution: {integrity: sha512-x35CNW/ANXG3hE/EZpRU8MXX1JDN86hBb2wMGAtltkz7pc6cxgjpy1OMMfDosOQ+2hWqIkag/fGok1Yady9nGw==} + '@rollup/rollup-android-arm-eabi@4.62.2': + resolution: {integrity: sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.60.3': - resolution: {integrity: sha512-xw3xtkDApIOGayehp2+Rz4zimfkaX65r4t47iy+ymQB2G4iJCBBfj0ogVg5jpvjpn8UWn/+q9tprxleYeNp3Hw==} + '@rollup/rollup-android-arm64@4.62.2': + resolution: {integrity: sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.60.3': - resolution: {integrity: sha512-vo6Y5Qfpx7/5EaamIwi0WqW2+zfiusVihKatLvtN1VFVy3D13uERk/6gZLU1UiHRL6fDXqj/ELIeVRGnvcTE1g==} + '@rollup/rollup-darwin-arm64@4.62.2': + resolution: {integrity: sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.60.3': - resolution: {integrity: sha512-D+0QGcZhBzTN82weOnsSlY7V7+RMmPuF1CkbxyMAGE8+ZHeUjyb76ZiWmBlCu//AQQONvxcqRbwZTajZKqjuOw==} + '@rollup/rollup-darwin-x64@4.62.2': + resolution: {integrity: sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.60.3': - resolution: {integrity: sha512-6HnvHCT7fDyj6R0Ph7A6x8dQS/S38MClRWeDLqc0MdfWkxjiu1HSDYrdPhqSILzjTIC/pnXbbJbo+ft+gy/9hQ==} + '@rollup/rollup-freebsd-arm64@4.62.2': + resolution: {integrity: sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.60.3': - resolution: {integrity: sha512-KHLgC3WKlUYW3ShFKnnosZDOJ0xjg9zp7au3sIm2bs/tGBeC2ipmvRh/N7JKi0t9Ue20C0dpEshi8WUubg+cnA==} + '@rollup/rollup-freebsd-x64@4.62.2': + resolution: {integrity: sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.60.3': - resolution: {integrity: sha512-DV6fJoxEYWJOvaZIsok7KrYl0tPvga5OZ2yvKHNNYyk/2roMLqQAbGhr78EQ5YhHpnhLKJD3S1WFusAkmUuV5g==} + '@rollup/rollup-linux-arm-gnueabihf@4.62.2': + resolution: {integrity: sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.60.3': - resolution: {integrity: sha512-mQKoJAzvuOs6F+TZybQO4GOTSMUu7v0WdxEk24krQ/uUxXoPTtHjuaUuPmFhtBcM4K0ons8nrE3JyhTuCFtT/w==} + '@rollup/rollup-linux-arm-musleabihf@4.62.2': + resolution: {integrity: sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.60.3': - resolution: {integrity: sha512-Whjj2qoiJ6+OOJMGptTYazaJvjOJm+iKHpXQM1P3LzGjt7Ff++Tp7nH4N8J/BUA7R9IHfDyx4DJIflifwnbmIA==} + '@rollup/rollup-linux-arm64-gnu@4.62.2': + resolution: {integrity: sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.60.3': - resolution: {integrity: sha512-4YTNHKqGng5+yiZt3mg77nmyuCfmNfX4fPmyUapBcIk+BdwSwmCWGXOUxhXbBEkFHtoN5boLj/5NON+u5QC9tg==} + '@rollup/rollup-linux-arm64-musl@4.62.2': + resolution: {integrity: sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loong64-gnu@4.60.3': - resolution: {integrity: sha512-SU3kNlhkpI4UqlUc2VXPGK9o886ZsSeGfMAX2ba2b8DKmMXq4AL7KUrkSWVbb7koVqx41Yczx6dx5PNargIrEA==} + '@rollup/rollup-linux-loong64-gnu@4.62.2': + resolution: {integrity: sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-loong64-musl@4.60.3': - resolution: {integrity: sha512-6lDLl5h4TXpB1mTf2rQWnAk/LcXrx9vBfu/DT5TIPhvMhRWaZ5MxkIc8u4lJAmBo6klTe1ywXIUHFjylW505sg==} + '@rollup/rollup-linux-loong64-musl@4.62.2': + resolution: {integrity: sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-ppc64-gnu@4.60.3': - resolution: {integrity: sha512-BMo8bOw8evlup/8G+cj5xWtPyp93xPdyoSN16Zy90Q2QZ0ZYRhCt6ZJSwbrRzG9HApFabjwj2p25TUPDWrhzqQ==} + '@rollup/rollup-linux-ppc64-gnu@4.62.2': + resolution: {integrity: sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-ppc64-musl@4.60.3': - resolution: {integrity: sha512-E0L8X1dZN1/Rph+5VPF6Xj2G7JJvMACVXtamTJIDrVI44Y3K+G8gQaMEAavbqCGTa16InptiVrX6eM6pmJ+7qA==} + '@rollup/rollup-linux-ppc64-musl@4.62.2': + resolution: {integrity: sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.60.3': - resolution: {integrity: sha512-oZJ/WHaVfHUiRAtmTAeo3DcevNsVvH8mbvodjZy7D5QKvCefO371SiKRpxoDcCxB3PTRTLayWBkvmDQKTcX/sw==} + '@rollup/rollup-linux-riscv64-gnu@4.62.2': + resolution: {integrity: sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-musl@4.60.3': - resolution: {integrity: sha512-Dhbyh7j9FybM3YaTgaHmVALwA8AkUwTPccyCQ79TG9AJUsMQqgN1DDEZNr4+QUfwiWvLDumW5vdwzoeUF+TNxQ==} + '@rollup/rollup-linux-riscv64-musl@4.62.2': + resolution: {integrity: sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.60.3': - resolution: {integrity: sha512-cJd1X5XhHHlltkaypz1UcWLA8AcoIi1aWhsvaWDskD1oz2eKCypnqvTQ8ykMNI0RSmm7NkTdSqSSD7zM0xa6Ig==} + '@rollup/rollup-linux-s390x-gnu@4.62.2': + resolution: {integrity: sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.60.3': - resolution: {integrity: sha512-DAZDBHQfG2oQuhY7mc6I3/qB4LU2fQCjRvxbDwd/Jdvb9fypP4IJ4qmtu6lNjes6B531AI8cg1aKC2di97bUxA==} + '@rollup/rollup-linux-x64-gnu@4.62.2': + resolution: {integrity: sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.60.3': - resolution: {integrity: sha512-cRxsE8c13mZOh3vP+wLDxpQBRrOHDIGOWyDL93Sy0Ga8y515fBcC2pjUfFwUe5T7tqvTvWbCpg1URM/AXdWIXA==} + '@rollup/rollup-linux-x64-musl@4.62.2': + resolution: {integrity: sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==} cpu: [x64] os: [linux] - '@rollup/rollup-openbsd-x64@4.60.3': - resolution: {integrity: sha512-QaWcIgRxqEdQdhJqW4DJctsH6HCmo5vHxY0krHSX4jMtOqfzC+dqDGuHM87bu4H8JBeibWx7jFz+h6/4C8wA5Q==} + '@rollup/rollup-openbsd-x64@4.62.2': + resolution: {integrity: sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==} cpu: [x64] os: [openbsd] - '@rollup/rollup-openharmony-arm64@4.60.3': - resolution: {integrity: sha512-AaXwSvUi3QIPtroAUw1t5yHGIyqKEXwH54WUocFolZhpGDruJcs8c+xPNDRn4XiQsS7MEwnYsHW2l0MBLDMkWg==} + '@rollup/rollup-openharmony-arm64@4.62.2': + resolution: {integrity: sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==} cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.60.3': - resolution: {integrity: sha512-65LAKM/bAWDqKNEelHlcHvm2V+Vfb8C6INFxQXRHCvaVN1rJfwr4NvdP4FyzUaLqWfaCGaadf6UbTm8xJeYfEg==} + '@rollup/rollup-win32-arm64-msvc@4.62.2': + resolution: {integrity: sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.60.3': - resolution: {integrity: sha512-EEM2gyhBF5MFnI6vMKdX1LAosE627RGBzIoGMdLloPZkXrUN0Ckqgr2Qi8+J3zip/8NVVro3/FjB+tjhZUgUHA==} + '@rollup/rollup-win32-ia32-msvc@4.62.2': + resolution: {integrity: sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-gnu@4.60.3': - resolution: {integrity: sha512-E5Eb5H/DpxaoXH++Qkv28RcUJboMopmdDUALBczvHMf7hNIxaDZqwY5lK12UK1BHacSmvupoEWGu+n993Z0y1A==} + '@rollup/rollup-win32-x64-gnu@4.62.2': + resolution: {integrity: sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.60.3': - resolution: {integrity: sha512-hPt/bgL5cE+Qp+/TPHBqptcAgPzgj46mPcg/16zNUmbQk0j+mOEQV/+Lqu8QRtDV3Ek95Q6FeFITpuhl6OTsAA==} + '@rollup/rollup-win32-x64-msvc@4.62.2': + resolution: {integrity: sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==} cpu: [x64] os: [win32] @@ -2485,65 +2241,65 @@ packages: '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} - '@tailwindcss/node@4.3.0': - resolution: {integrity: sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==} + '@tailwindcss/node@4.3.1': + resolution: {integrity: sha512-6NDaqRoAMSXD1mr/RXu0HBvNE9a2n5tHPsxu9XHLws8o4Twes5rBM2205SUUiJ9goAtadrN6xTGX0UDEwp/N4A==} - '@tailwindcss/oxide-android-arm64@4.3.0': - resolution: {integrity: sha512-TJPiq67tKlLuObP6RkwvVGDoxCMBVtDgKkLfa/uyj7/FyxvQwHS+UOnVrXXgbEsfUaMgiVvC4KbJnRr26ho4Ng==} + '@tailwindcss/oxide-android-arm64@4.3.1': + resolution: {integrity: sha512-SVlyf61g374l5cHyg8x9kf5xmLcOaxvOTsbsqDnSsDJaKOEFZ7GCvi84VAVGpxojYOs1+3K6M0UjXfqPU8vmOQ==} engines: {node: '>= 20'} cpu: [arm64] os: [android] - '@tailwindcss/oxide-darwin-arm64@4.3.0': - resolution: {integrity: sha512-oMN/WZRb+SO37BmUElEgeEWuU8E/HXRkiODxJxLe1UTHVXLrdVSgfaJV7pSlhRGMSOiXLuxTIjfsF3wYvz8cgQ==} + '@tailwindcss/oxide-darwin-arm64@4.3.1': + resolution: {integrity: sha512-hVnWLwv+e/l7c4WKyVtHVrIPvYdqWHjRB3MDIqARynzFtnQg85kmQEFCbV9Ja0VVx4xXTIiDWY60Y7iz/iNoDA==} engines: {node: '>= 20'} cpu: [arm64] os: [darwin] - '@tailwindcss/oxide-darwin-x64@4.3.0': - resolution: {integrity: sha512-N6CUmu4a6bKVADfw77p+iw6Yd9Q3OBhe0veaDX+QazfuVYlQsHfDgxBrsjQ/IW+zywL8mTrNd0SdJT/zgtvMdA==} + '@tailwindcss/oxide-darwin-x64@4.3.1': + resolution: {integrity: sha512-Cf7abu0WVgbhU7ANgPUnSAvm7nCvMweusHb8FnaHlLfv/Caq4GYaEZg7ZImzzmjx4lIAfuS8q+eLIS7A7IzxIg==} engines: {node: '>= 20'} cpu: [x64] os: [darwin] - '@tailwindcss/oxide-freebsd-x64@4.3.0': - resolution: {integrity: sha512-zDL5hBkQdH5C6MpqbK3gQAgP80tsMwSI26vjOzjJtNCMUo0lFgOItzHKBIupOZNQxt3ouPH7RPhvNhiTfCe5CQ==} + '@tailwindcss/oxide-freebsd-x64@4.3.1': + resolution: {integrity: sha512-ZZqzX2Y+GXtXXfqSfpJhDm60OoZfvLHLCgm+J7NVqgHHJjG/m9ugZI77RwTsVd4fnBJuCFP6Ae6kTJb71UdS8g==} engines: {node: '>= 20'} cpu: [x64] os: [freebsd] - '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.0': - resolution: {integrity: sha512-R06HdNi7A7OEoMsf6d4tjZ71RCWnZQPHj2mnotSFURjNLdBC+cIgXQ7l81CqeoiQftjf6OOblxXMInMgN2VzMA==} + '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.1': + resolution: {integrity: sha512-/Ah/xik0LaMYfv9DZ0S/t4pBlBNYOcqtRwusjgovHkvT8ixueWCLyJjsaF5kQIckjb4IT8Q6K6p/iPmZMixYgg==} engines: {node: '>= 20'} cpu: [arm] os: [linux] - '@tailwindcss/oxide-linux-arm64-gnu@4.3.0': - resolution: {integrity: sha512-qTJHELX8jetjhRQHCLilkVLmybpzNQAtaI/gaoVoidn/ufbNDbAo8KlK2J+yPoc8wQxvDxCmh/5lr8nC1+lTbg==} + '@tailwindcss/oxide-linux-arm64-gnu@4.3.1': + resolution: {integrity: sha512-gqdFoVJlw444GvpnheZLHmvTzSxI/cOUUh2KSNejQjTcYkW062SVD+En0rUgD+QV91bz1XGIGtt1HJd48xUGbQ==} engines: {node: '>= 20'} cpu: [arm64] os: [linux] - '@tailwindcss/oxide-linux-arm64-musl@4.3.0': - resolution: {integrity: sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ==} + '@tailwindcss/oxide-linux-arm64-musl@4.3.1': + resolution: {integrity: sha512-Bwv9KwOvE0VKa86xPFif9b9c3Y1NxOV1P0gLti/IYaWEsQYZXDlxfGEtA8mdDZ7SG3wyNXAWYT5SIn3giL57oA==} engines: {node: '>= 20'} cpu: [arm64] os: [linux] - '@tailwindcss/oxide-linux-x64-gnu@4.3.0': - resolution: {integrity: sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ==} + '@tailwindcss/oxide-linux-x64-gnu@4.3.1': + resolution: {integrity: sha512-Ymi8O8T15HYQdOUWUtTI6ldN0neHP85FC+Qz32xTcZ7iJXtem/x8ITev0o1e9e5rkqj4lONZfTRLvkmin1+tKg==} engines: {node: '>= 20'} cpu: [x64] os: [linux] - '@tailwindcss/oxide-linux-x64-musl@4.3.0': - resolution: {integrity: sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg==} + '@tailwindcss/oxide-linux-x64-musl@4.3.1': + resolution: {integrity: sha512-M+P/91qJ6uILLw4k2G93GMDRAXj61SMvFQYt39AqvUqYgExXpLL5aepfns7sj4HiAQeolirQF9E0lzRvdf4zPQ==} engines: {node: '>= 20'} cpu: [x64] os: [linux] - '@tailwindcss/oxide-wasm32-wasi@4.3.0': - resolution: {integrity: sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA==} + '@tailwindcss/oxide-wasm32-wasi@4.3.1': + resolution: {integrity: sha512-zsM8uOeqvVGHsAXsJxsT28ttosFahLJKCLOTUBqRAtKnVgGSRitds9T432QiT8b77Yga7JIBkulIRRlJPtYhRA==} engines: {node: '>=14.0.0'} cpu: [wasm32] bundledDependencies: @@ -2554,24 +2310,24 @@ packages: - '@emnapi/wasi-threads' - tslib - '@tailwindcss/oxide-win32-arm64-msvc@4.3.0': - resolution: {integrity: sha512-Pe+RPVTi1T+qymuuRpcdvwSVZjnll/f7n8gBxMMh3xLTctMDKqpdfGimbMyioqtLhUYZxdJ9wGNhV7MKHvgZsQ==} + '@tailwindcss/oxide-win32-arm64-msvc@4.3.1': + resolution: {integrity: sha512-aiNvSq9BsVk8V513lDKlrCFAgf8qBMPZTpgEhInL+NwQqs97mYmupVMrPrgBBSL8Pv/0zXu9MrMF9rMun1ZeNg==} engines: {node: '>= 20'} cpu: [arm64] os: [win32] - '@tailwindcss/oxide-win32-x64-msvc@4.3.0': - resolution: {integrity: sha512-Mvrf2kXW/yeW/OTezZlCGOirXRcUuLIBx/5Y12BaPM7wJoryG6dfS/NJL8aBPqtTEx/Vm4T4vKzFUcKDT+TKUA==} + '@tailwindcss/oxide-win32-x64-msvc@4.3.1': + resolution: {integrity: sha512-xDEyu1rg290472FEGaKHnzyDyh5QH+AlWvsU5hMoMtPpzmKlRI0jaYKCgSHDYtaQWZOYbMaduSyCwFwY4n1HmA==} engines: {node: '>= 20'} cpu: [x64] os: [win32] - '@tailwindcss/oxide@4.3.0': - resolution: {integrity: sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg==} + '@tailwindcss/oxide@4.3.1': + resolution: {integrity: sha512-yVPyo8RNkabVr3O2EhHEE0Rewu7YKzc1DhIqfL46LKveFrmu9XbDazNOJY7/GRuvw1h6u3utWnR29H/p5JPlgA==} engines: {node: '>= 20'} - '@tailwindcss/vite@4.3.0': - resolution: {integrity: sha512-t6J3OrB5Fc0ExuhohouH0fWUGMYL6PTLhW+E7zIk/pdbnJARZDCwjBznFnkh5ynRnIRSI4YjtTH0t6USjJISrw==} + '@tailwindcss/vite@4.3.1': + resolution: {integrity: sha512-hItDHuIIlEV61R+faXu66s1K36aTurO/Qw0e45Vskz57gXl9pWOT6eg3zmcEui6CZXddbN7zd41bwmvag4JGwQ==} peerDependencies: vite: ^5.2.0 || ^6 || ^7 || ^8 @@ -2580,10 +2336,10 @@ packages: peerDependencies: '@tiptap/pm': ^3.17.0 - '@tiptap/extension-blockquote@3.17.1': - resolution: {integrity: sha512-X4jU/fllJQ8QbjCHUafU4QIHBobyXP3yGBoOcXxUaKlWbLvUs0SQTREM3n6/86m2YyAxwTPG1cn3Xypf42DMAQ==} + '@tiptap/extension-blockquote@3.17.0': + resolution: {integrity: sha512-TVslb79JVoZUFO+O4lAHveu38asi1OEqNpLdnQr+SIijIi8WgvJv3VwQwZfkja91WUAHbOHGbnYN0QySOcVCtA==} peerDependencies: - '@tiptap/core': ^3.17.1 + '@tiptap/core': ^3.17.0 '@tiptap/extension-bold@3.17.1': resolution: {integrity: sha512-PZmrljcVBziJkQDXT/QJv4ESxVVQ0iRH+ruTzPda56Kk4h2310cSXGjI33W7rlCikGPoBAAjY/inujm46YB4bw==} @@ -2782,6 +2538,9 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + '@types/fs-extra@11.0.4': resolution: {integrity: sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==} @@ -2812,8 +2571,8 @@ packages: '@types/minimist@1.2.5': resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==} - '@types/node@24.12.3': - resolution: {integrity: sha512-8oljBDGun9cIsZRJR6fkihn0TSXJI0UDOOhncYaERq6M0JMDoPLxyscwruJcb4GKS6dvK/d8xebYBg27h/duaQ==} + '@types/node@24.13.2': + resolution: {integrity: sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA==} '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} @@ -2845,41 +2604,35 @@ packages: '@types/yauzl@2.10.3': resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} - '@typescript-eslint/eslint-plugin@8.56.0': - resolution: {integrity: sha512-lRyPDLzNCuae71A3t9NEINBiTn7swyOhvUj3MyUOxb8x6g6vPEFoOU+ZRmGMusNC3X3YMhqMIX7i8ShqhT74Pw==} + '@typescript-eslint/eslint-plugin@8.60.1': + resolution: {integrity: sha512-JQ4S5GB0tfjO8BuJ4fcX+HodkzJjYBV+7OJ+wLygaX7OGQ7FudyHL4NSCA6ob+w3Yn+5MkKIozOwQhXeM7opVg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.56.0 - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.0.0' - - '@typescript-eslint/eslint-plugin@8.59.2': - resolution: {integrity: sha512-j/bwmkBvHUtPNxzuWe5z6BEk3q54YRyGlBXkSsmfoih7zNrBvl5A9A98anlp/7JbyZcWIJ8KXo/3Tq/DjFLtuQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - '@typescript-eslint/parser': ^8.59.2 + '@typescript-eslint/parser': ^8.60.1 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/parser@8.56.0': - resolution: {integrity: sha512-IgSWvLobTDOjnaxAfDTIHaECbkNlAlKv2j5SjpB2v7QHKv1FIfjwMy8FsDbVfDX/KjmCmYICcw7uGaXLhtsLNg==} + '@typescript-eslint/eslint-plugin@8.61.1': + resolution: {integrity: sha512-ZPlVl3PB3et/59Ne0fv/sci6ZXz4T4Hp4nTJ56i/Y0gR89ARb+KphojTq6j+56E5PIezmOIOOWyY+aWQFd+IkQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: + '@typescript-eslint/parser': ^8.61.1 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.0.0' + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/parser@8.59.2': - resolution: {integrity: sha512-plR3pp6D+SSUn1HM7xvSkx12/DhoHInI2YF35KAcVFNZvlC0gtrWqx7Qq1oH2Ssgi0vlFRCTbP+DZc7B9+TtsQ==} + '@typescript-eslint/parser@8.60.1': + resolution: {integrity: sha512-A0M6ua6H252bVjPvvtSgl2QA4+ET9S5Mtkb2GDyTxIhH/C4qDItT7RQNO5PhMC6NXGYXOR9dIalcDDgBKT7oFA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/project-service@8.56.0': - resolution: {integrity: sha512-M3rnyL1vIQOMeWxTWIW096/TtVP+8W3p/XnaFflhmcFp+U4zlxUxWj4XwNs6HbDeTtN4yun0GNTTDBw/SvufKg==} + '@typescript-eslint/parser@8.61.1': + resolution: {integrity: sha512-PJ5vePq5/ognBbrIcoC5+SHO5dfpeLPzP9FpLkzWrguoYQEeeSjlJpVwOpo1JRSTEi7dRcwNy4h4dzV70PqHcg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <6.0.0' + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' '@typescript-eslint/project-service@8.58.0': resolution: {integrity: sha512-8Q/wBPWLQP1j16NxoPNIKpDZFMaxl7yWIoqXWYeWO+Bbd2mjgvoF0dxP2jKZg5+x49rgKdf7Ck473M8PC3V9lg==} @@ -2887,29 +2640,29 @@ packages: peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/project-service@8.59.2': - resolution: {integrity: sha512-+2hqvEkeyf/0FBor67duF0Ll7Ot8jyKzDQOSrxazF/danillRq2DwR9dLptsXpoZQqxE1UisSmoZewrlPas9Vw==} + '@typescript-eslint/project-service@8.60.1': + resolution: {integrity: sha512-eXkTH2bxmXlqD1RnOPmLZ9ZM9D3VwSx04JOwBnP9RQ+yUA5a2Mu7SfW8uaV2Aon53NJzZlZYuX7tn91Izf+xaw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/scope-manager@8.56.0': - resolution: {integrity: sha512-7UiO/XwMHquH+ZzfVCfUNkIXlp/yQjjnlYUyYz7pfvlK3/EyyN6BK+emDmGNyQLBtLGaYrTAI6KOw8tFucWL2w==} + '@typescript-eslint/project-service@8.61.1': + resolution: {integrity: sha512-PrC4JYGmR241lYnfhmKGTXkFqv8+ymbTFgSAY0fVXpY82/QkMw5TZPl+vGzuDDU2QYJk9fIDOBTntF+yDv9LEA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' '@typescript-eslint/scope-manager@8.58.0': resolution: {integrity: sha512-W1Lur1oF50FxSnNdGp3Vs6P+yBRSmZiw4IIjEeYxd8UQJwhUF0gDgDD/W/Tgmh73mxgEU3qX0Bzdl/NGuSPEpQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/scope-manager@8.59.2': - resolution: {integrity: sha512-JzfyEpEtOU89CcFSwyNS3mu4MLvLSXqnmX05+aKBDM+TdR5jzcGOEBwxwGNxrEQ7p/z6kK2WyioCGBf2zZBnvg==} + '@typescript-eslint/scope-manager@8.60.1': + resolution: {integrity: sha512-gvI5OQoptnxQnchOirukCuQ55svJSTuD/4k5+pC267xyBtYry748R9/c3tYUzb/iE6RZfllRz2lVulLCHkTm4w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.56.0': - resolution: {integrity: sha512-bSJoIIt4o3lKXD3xmDh9chZcjCz5Lk8xS7Rxn+6l5/pKrDpkCwtQNQQwZ2qRPk7TkUYhrq3WPIHXOXlbXP0itg==} + '@typescript-eslint/scope-manager@8.61.1': + resolution: {integrity: sha512-L2bdIeoQS8FlKAvONAr20w6OcLXeB+qiDKbAooS9A0Ben+iSIkBef0FxqwKWYqt5sa0i4KJtxVyVmhMylKzF5w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.0.0' '@typescript-eslint/tsconfig-utils@8.58.0': resolution: {integrity: sha512-doNSZEVJsWEu4htiVC+PR6NpM+pa+a4ClH9INRWOWCUzMst/VA9c4gXq92F8GUD1rwhNvRLkgjfYtFXegXQF7A==} @@ -2917,53 +2670,53 @@ packages: peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/tsconfig-utils@8.59.1': - resolution: {integrity: sha512-/0nEyPbX7gRsk0Uwfe4ALwwgxuA66d/l2mhRDNlAvaj4U3juhUtJNq0DsY8M2AYwwb9rEq2hrC3IcIcEt++iJA==} + '@typescript-eslint/tsconfig-utils@8.60.1': + resolution: {integrity: sha512-nh8w4qAteiKuZu3pSSzG/yGKpw0OlkrKnzFmbVRenKaD4qc+7i1GrmZaLVkr8rk4uipiPGMOW4YsM6WmKZ5CvA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/tsconfig-utils@8.59.2': - resolution: {integrity: sha512-BKK4alN7oi4C/zv4VqHQ+uRU+lTa6JGIZ7s1juw7b3RHo9OfKB+bKX3u0iVZetdsUCBBkSbdWbarJbmN0fTeSw==} + '@typescript-eslint/tsconfig-utils@8.61.0': + resolution: {integrity: sha512-O5Amvdv9ztMpxpf+vmFULGG78IE6Qwdr3bCGvqwG4nwc9H2qXkOYJJnRbRHyMkQTjv1d03olqwwwzHLMqpFePQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/type-utils@8.56.0': - resolution: {integrity: sha512-qX2L3HWOU2nuDs6GzglBeuFXviDODreS58tLY/BALPC7iu3Fa+J7EOTwnX9PdNBxUI7Uh0ntP0YWGnxCkXzmfA==} + '@typescript-eslint/tsconfig-utils@8.61.1': + resolution: {integrity: sha512-UN/H4di+OO7EWx2ovME+8t31YO+KVnK0RRKEHR3kOt21/Ay8BOq3M1OMvWs5vNiqcFCYGYoxK3MXPZzmMUE+yg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.0.0' + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/type-utils@8.59.2': - resolution: {integrity: sha512-nhqaj1nmTdVVl/BP5omXNRGO38jn5iosis2vbdmupF2txCf8ylWT8lx+JlvMYYVqzGVKtjojUFoQ3JRWK+mfzQ==} + '@typescript-eslint/type-utils@8.60.1': + resolution: {integrity: sha512-sdwTrpjosW7ANQYJ39ZBF1ZyEMEGVB2UsikrserVM/30a/F1dTLnu9bGxEdosugyu5caigjLrR2qiD11asjI1A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/types@8.56.0': - resolution: {integrity: sha512-DBsLPs3GsWhX5HylbP9HNG15U0bnwut55Lx12bHB9MpXxQ+R5GC8MwQe+N1UFXxAeQDvEsEDY6ZYwX03K7Z6HQ==} + '@typescript-eslint/type-utils@8.61.1': + resolution: {integrity: sha512-GYRicKmVK0C4fsKgaACaknOUAq9Oa2kwsjnpFhFcS/5p4Ht5IP9OVLbgIgcK4SRk92nVHFluurg1lumD9dBcLw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' '@typescript-eslint/types@8.58.0': resolution: {integrity: sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/types@8.59.1': - resolution: {integrity: sha512-ZDCjgccSdYPw5Bxh+my4Z0lJU96ZDN7jbBzvmEn0FZx3RtU1C7VWl6NbDx94bwY3V5YsgwRzJPOgeY2Q/nLG8A==} + '@typescript-eslint/types@8.60.1': + resolution: {integrity: sha512-4h0tY8ppCkdCzcrl2YM5M3my0xsE1Tf8om3owEu5oPWmXwkKRmk0j0LGDzYBGUcAlesEbxBhazqu/K4cu3Ug7w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/types@8.59.2': - resolution: {integrity: sha512-e82GVOE8Ps3E++Egvb6Y3Dw0S10u8NkQ9KXmtRhCWJJ8kDhOJTvtMAWnFL16kB1583goCWXsr0NieKCZMs2/0Q==} + '@typescript-eslint/types@8.61.0': + resolution: {integrity: sha512-9QTQpZ5Iin4CdIodfbDQFSeiSJKidgYJYug1P9CC2xWgUTvlmixViqDZNciMjwLBZyJnG4tGmPl97rVAFb1AJg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.56.0': - resolution: {integrity: sha512-ex1nTUMWrseMltXUHmR2GAQ4d+WjkZCT4f+4bVsps8QEdh0vlBsaCokKTPlnqBFqqGaxilDNJG7b8dolW2m43Q==} + '@typescript-eslint/types@8.61.1': + resolution: {integrity: sha512-G+CRlPqLv7Bz1IZVs03x5K59F1veqL0EJUROAdGhKsEq8qOiRiZbI+HUojPq5l0fEGOKModD9br6lObhB8zkoA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.0.0' '@typescript-eslint/typescript-estree@8.58.0': resolution: {integrity: sha512-7vv5UWbHqew/dvs+D3e1RvLv1v2eeZ9txRHPnEEBUgSNLx5ghdzjHa0sgLWYVKssH+lYmV0JaWdoubo0ncGYLA==} @@ -2971,61 +2724,60 @@ packages: peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/typescript-estree@8.59.2': - resolution: {integrity: sha512-o0XPGNwcWw+FIwStOWn+BwBuEmL6QXP0rsvAFg7ET1dey1Nr6Wb1ac8p5HEsK0ygO/6mUxlk+YWQD9xcb/nnXg==} + '@typescript-eslint/typescript-estree@8.60.1': + resolution: {integrity: sha512-alpRkfG8hlVE5kdJW2GkfgDgXxold3e8e4l6EnmhRmRLbekgAPCCGDVD++sABy9FcgPFroq+uFcCSM1vR57Cew==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@8.56.0': - resolution: {integrity: sha512-RZ3Qsmi2nFGsS+n+kjLAYDPVlrzf7UhTffrDIKr+h2yzAlYP/y5ZulU0yeDEPItos2Ph46JAL5P/On3pe7kDIQ==} + '@typescript-eslint/typescript-estree@8.61.1': + resolution: {integrity: sha512-u+oQD3BqYWPc8YV9Zab4vaJElJuwOLPRc10Jm1o/qS+6Qwen14HCWwx0Seo4LnSn2wxea2Ik8DxPt2/FHmuhrg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.0.0' + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@8.58.0': - resolution: {integrity: sha512-RfeSqcFeHMHlAWzt4TBjWOAtoW9lnsAGiP3GbaX9uVgTYYrMbVnGONEfUCiSss+xMHFl+eHZiipmA8WkQ7FuNA==} + '@typescript-eslint/utils@8.60.1': + resolution: {integrity: sha512-h2MPBLoNtjc3qZWfY3Tl51yPorQ2McHn8pJfcMNTcIvrrZrr90Ykffit0yjrPFWQcRcUxzH20+6OcVdW4yHtUg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@8.59.2': - resolution: {integrity: sha512-Juw3EinkXqjaffxz6roowvV7GZT/kET5vSKKZT6upl5TXdWkLkYmNPXwDDL2Vkt2DPn0nODIS4egC/0AGxKo/Q==} + '@typescript-eslint/utils@8.61.1': + resolution: {integrity: sha512-1+P/3Dj6jvtybE1q0HQ6yBt/gq+oKJyLdEv4HdnqasaEXRSYCAsD59mXEVQnM/ULNdQxbX77tdG4jPRjIS6knA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/visitor-keys@8.56.0': - resolution: {integrity: sha512-q+SL+b+05Ud6LbEE35qe4A99P+htKTKVbyiNEe45eCbJFyh/HVK9QXwlrbz+Q4L8SOW4roxSVwXYj4DMBT7Ieg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/visitor-keys@8.58.0': resolution: {integrity: sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/visitor-keys@8.59.2': - resolution: {integrity: sha512-NwjLUnGy8/Zfx23fl50tRC8rYaYnM52xNRYFAXvmiil9yh1+K6aRVQMnzW6gQB/1DLgWt977lYQn7C+wtgXZiA==} + '@typescript-eslint/visitor-keys@8.60.1': + resolution: {integrity: sha512-EbGRQg4FhrmwLodl+t3JNAnXHWVr9Vp+Zl1QBZVPY4ByfkzIT8cX3K6QWODHtkIZqqJVEWvhHSx3v5PDHsaQag==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/visitor-keys@8.61.1': + resolution: {integrity: sha512-6fJ9MHWtK14C1DSkiMlHUSOmrVebL7150xZJBlJiL62jjhIA4JmOq6flwBgDxIdBKKdoiZRel+dfPD5MLfny3w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} deprecated: Potential CWE-502 - Update to 1.3.1 or higher - '@vitejs/plugin-vue@6.0.6': - resolution: {integrity: sha512-u9HHgfrq3AjXlysn0eINFnWQOJQLO9WN6VprZ8FXl7A2bYisv3Hui9Ij+7QZ41F/WYWarHjwBbXtD7dKg3uxbg==} + '@vitejs/plugin-vue@6.0.7': + resolution: {integrity: sha512-km+p+XdSz9Sxm5rqUbqcSfZYaAniKxWBj1KURl+Jr7UaPvvX7BmaWMdP69I5rrFDeQGyxAG7NXdc57vz+snhWg==} engines: {node: ^20.19.0 || >=22.12.0} peerDependencies: vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 vue: ^3.2.25 - '@vitest/expect@4.1.5': - resolution: {integrity: sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw==} + '@vitest/expect@4.1.9': + resolution: {integrity: sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA==} - '@vitest/mocker@4.1.5': - resolution: {integrity: sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw==} + '@vitest/mocker@4.1.9': + resolution: {integrity: sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw==} peerDependencies: msw: ^2.4.9 vite: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -3035,20 +2787,20 @@ packages: vite: optional: true - '@vitest/pretty-format@4.1.5': - resolution: {integrity: sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==} + '@vitest/pretty-format@4.1.9': + resolution: {integrity: sha512-s0iufns3iIFitdgm+YR7g1whCAaGtXz459VS9/PqyKDEEFgYIhsHOQmXgIgDuYCt7DeQmiZT0Qe2OA2p4ZPu5A==} - '@vitest/runner@4.1.5': - resolution: {integrity: sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ==} + '@vitest/runner@4.1.9': + resolution: {integrity: sha512-KXLMDtc7oe70+3mJfGrPUWPesswH+3sTxAMAMl8DG7I8IUQT4XW718dY5ID3vPUcmlu27CcKfY4P3h3I29SLJg==} - '@vitest/snapshot@4.1.5': - resolution: {integrity: sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==} + '@vitest/snapshot@4.1.9': + resolution: {integrity: sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA==} - '@vitest/spy@4.1.5': - resolution: {integrity: sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==} + '@vitest/spy@4.1.9': + resolution: {integrity: sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA==} - '@vitest/utils@4.1.5': - resolution: {integrity: sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==} + '@vitest/utils@4.1.9': + resolution: {integrity: sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA==} '@volar/language-core@2.4.28': resolution: {integrity: sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==} @@ -3065,7 +2817,7 @@ packages: '@vue/babel-plugin-jsx@1.2.5': resolution: {integrity: sha512-zTrNmOd4939H9KsRIGmmzn3q2zvv1mjxkYZHgqHZgDrXz5B1Q3WyGEjO2f+JrmKghvl1JIRcvo63LgM1kH5zFg==} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '>=7.29.6' peerDependenciesMeta: '@babel/core': optional: true @@ -3073,7 +2825,7 @@ packages: '@vue/babel-plugin-resolve-type@1.2.5': resolution: {integrity: sha512-U/ibkQrf5sx0XXRnUZD1mo5F7PkpKyTbfXM3a3rC4YnUz6crHEz9Jg09jzzL6QYlXNto/9CePdOg/c87O4Nlfg==} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '>=7.29.6' '@vue/compiler-core@3.5.27': resolution: {integrity: sha512-gnSBQjZA+//qDZen+6a2EdHqJ68Z7uybrMf3SPjEGgG4dicklwDVmMC1AeIHxtLVPT7sn6sH1KOO+tS6gwOUeQ==} @@ -3093,25 +2845,25 @@ packages: '@vue/devtools-api@7.7.7': resolution: {integrity: sha512-lwOnNBH2e7x1fIIbVT7yF5D+YWhqELm55/4ZKf45R9T8r9dE2AIOy8HKjfqzGsoTHFbWbr337O4E0A0QADnjBg==} - '@vue/devtools-core@8.1.2': - resolution: {integrity: sha512-ZGGyaSBP4/+bN2Nd9ZHNYAVDRIzMw1rv2RyXWtyZlo6mQal+IDmTvKY4V+DjAEBhaXt30mHmsgYp1yXJ/2tIWg==} + '@vue/devtools-core@8.1.3': + resolution: {integrity: sha512-xezkv5/CPH/o5C8PE2Len9MnTJMsctYYQbKbbUiNOJpKd+fRHj27nKDb/sbtYI8NSQduegeQhCJGKRgAiOV6Uw==} peerDependencies: vue: ^3.0.0 '@vue/devtools-kit@7.7.7': resolution: {integrity: sha512-wgoZtxcTta65cnZ1Q6MbAfePVFxfM+gq0saaeytoph7nEa7yMXoi6sCPy4ufO111B9msnw0VOWjPEFCXuAKRHA==} - '@vue/devtools-kit@8.1.2': - resolution: {integrity: sha512-f75/upc+GCyjXErpgPGz4582ujS0L/adAltGy+tqXMGUJpgAcfGr6CxnnhpZY8BHuMYt6KpbF8uaFrrQG66rGQ==} + '@vue/devtools-kit@8.1.3': + resolution: {integrity: sha512-cRn7GXiCQkMYU2Z3h3pM4YO/ndbx9FY1yLDAqIqPLcmIq4H6zAOJHein6tvZU3AfPwgrodqLiPBEF+YQaS8AxA==} '@vue/devtools-shared@7.7.7': resolution: {integrity: sha512-+udSj47aRl5aKb0memBvcUG9koarqnxNM5yjuREvqwK6T3ap4mn3Zqqc17QrBFTqSMjr3HK1cvStEZpMDpfdyw==} - '@vue/devtools-shared@8.1.2': - resolution: {integrity: sha512-X9RyVFYAdkBe4IUf5v48TxBF/6QPmF8CmWrDAjXzfUHrgQ/HGfTC1A6TqgXqZ03ye66l3AD51BAGD69IvKM9sw==} + '@vue/devtools-shared@8.1.3': + resolution: {integrity: sha512-CM3uIPL+v+lrJUk33+pxspYo0MhuMWlCvf7zC9fybifvCPyM2jUbYRPwoYEJgYbwRqPikm5HozbUhp60MF2QuA==} - '@vue/eslint-config-typescript@14.7.0': - resolution: {integrity: sha512-iegbMINVc+seZ/QxtzWiOBozctrHiF2WvGedruu2EbLujg9VuU0FQiNcN2z1ycuaoKKpF4m2qzB5HDEMKbxtIg==} + '@vue/eslint-config-typescript@14.8.0': + resolution: {integrity: sha512-yIquzhXH7ZsrwSSm+rYvoGCRY6wcuF4qBi76e0l7hHLq7YU0f9aC+RcR5fL+XJNfmBZxgX5cVl4sppt4x7ZCBg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^9.10.0 || ^10.0.0 @@ -3121,8 +2873,8 @@ packages: typescript: optional: true - '@vue/language-core@3.2.8': - resolution: {integrity: sha512-9OiSPQFiAAWNVnXb0d2dcTmcKnFQamhuNES6ayyISrb/mwPWVgoGdAqSfCWqKhQpa3D5gDTcYD+w7ObiheZ81g==} + '@vue/language-core@3.3.5': + resolution: {integrity: sha512-UkKu5nhX89fg4VhlG/FOeI10G3cj/7radKT/cy9BT4Q9qJmJlSTAc/dP63Xqs29aypN4f39xUV6PsLNk/dcD6g==} '@vue/reactivity@3.5.27': resolution: {integrity: sha512-vvorxn2KXfJ0nBEnj4GYshSgsyMNFnIQah/wczXlsNXt+ijhugmW+PpJ2cNPe4V6jpnBcs0MhCODKllWG+nvoQ==} @@ -3141,8 +2893,8 @@ packages: '@vue/shared@3.5.27': resolution: {integrity: sha512-dXr/3CgqXsJkZ0n9F3I4elY8wM9jMJpP3pvRG52r6m0tu/MsAFIe6JpXVGeNMd/D9F4hQynWT8Rfuj0bdm9kFQ==} - '@vue/test-utils@2.4.10': - resolution: {integrity: sha512-SmoZ5EA1kYiAFs9NkYdiFFQF+cSnUwnvlYEbY+DogWQZUiqOm/Y29eSbc5T6yi75SgSF9863SBeXniIEoPajCA==} + '@vue/test-utils@2.4.11': + resolution: {integrity: sha512-GDqaqZsA6m2E5vNzej0aYiIb6BX8xV9pNSbbbXKOfEYwg7ZNblVX8suyqmUBThq8VIrgAJNxn+z72hVtUeiWHA==} peerDependencies: '@vue/compiler-dom': 3.x '@vue/server-renderer': 3.x @@ -3214,8 +2966,8 @@ packages: ajv@8.18.0: resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} - alien-signals@3.1.2: - resolution: {integrity: sha512-d9dYqZTS90WLiU0I5c6DHj/HcKkF8ZyGN3G5x8wSbslulz70KOxaqCT0hQCo9KOyhVqzqGojvNdJXoTumZOtcw==} + alien-signals@3.2.1: + resolution: {integrity: sha512-I8FjmltrfnDFoZedi5CG8DghVYNhzb/Ijluz7tCSJH0xpd0484Kowhbb1XDYOxfJpU1p5wnM2X54dA+IfGyD1g==} ansi-align@3.0.1: resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} @@ -3296,14 +3048,14 @@ packages: engines: {node: ^10 || ^12 || >=14} hasBin: true peerDependencies: - postcss: ^8.1.0 + postcss: '>=8.5.10' available-typed-arrays@1.0.7: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} - axios@1.15.2: - resolution: {integrity: sha512-wLrXxPtcrPTsNlJmKjkPnNPK2Ihe0hn0wGSaTEiHRPxwjvJwT3hKmXF4dpqxmPO9SoNb2FsYXj/xEo0gHN+D5A==} + axios@1.16.0: + resolution: {integrity: sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w==} b4a@1.6.7: resolution: {integrity: sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==} @@ -3311,17 +3063,17 @@ packages: babel-plugin-polyfill-corejs2@0.4.11: resolution: {integrity: sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q==} peerDependencies: - '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + '@babel/core': '>=7.29.6' babel-plugin-polyfill-corejs3@0.10.6: resolution: {integrity: sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==} peerDependencies: - '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + '@babel/core': '>=7.29.6' babel-plugin-polyfill-regenerator@0.6.2: resolution: {integrity: sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg==} peerDependencies: - '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + '@babel/core': '>=7.29.6' balanced-match@4.0.3: resolution: {integrity: sha512-1pHv8LX9CpKut1Zp4EXey7Z8OfH11ONNH6Dhi2WDUt31VVZFXZzKwXcysBgqSumFCmR+0dqjMK5v5JiFHzi0+g==} @@ -3395,8 +3147,8 @@ packages: resolution: {integrity: sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==} engines: {node: '>=18'} - brace-expansion@5.0.5: - resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} + brace-expansion@5.0.6: + resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} engines: {node: 18 || 20 || >=22} braces@3.0.3: @@ -3414,6 +3166,10 @@ packages: buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + buffer-image-size@0.6.4: + resolution: {integrity: sha512-nEh+kZOPY1w+gcCMobZ6ETUp9WfibndnosbpwB1iJk/8Gt5ZF2bhS6+B6bPYz424KtwsR6Rflc3tCz1/ghX2dQ==} + engines: {node: '>=4.0'} + buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} @@ -3429,8 +3185,8 @@ packages: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} - cacheable@2.3.2: - resolution: {integrity: sha512-w+ZuRNmex9c1TR9RcsxbfTKCjSL0rh1WA5SABbrWprIHeNBdmyQLSYonlDy9gpD+63XT8DgZ/wNh1Smvc9WnJA==} + cacheable@2.3.5: + resolution: {integrity: sha512-EQfaKe09tl615iNvq/TBRWTFf1AKJNXYQSsMx0Z3EI0nA+pVsVPS8wJhnRlkbdacKPh1d0qVIhwTc2zsQNFEEg==} call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} @@ -3460,8 +3216,8 @@ packages: resolution: {integrity: sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==} engines: {node: '>=16'} - caniuse-lite@1.0.30001792: - resolution: {integrity: sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==} + caniuse-lite@1.0.30001799: + resolution: {integrity: sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==} capture-website@4.2.0: resolution: {integrity: sha512-EmkSn36CXTC8tUsS6aNmvvsdpfVTYYkuRp7U5bV9gcJwcDbqqA5c0Op/iskYPKtDdOkuVp61mjn/LLywX0h7cw==} @@ -3502,6 +3258,10 @@ packages: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} + chokidar@5.0.0: + resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} + engines: {node: '>= 20.19.0'} + chroma-js@1.4.1: resolution: {integrity: sha512-jTwQiT859RTFN/vIf7s+Vl/Z2LcMrvMv3WUFmd/4u76AdlFC0NTNgqEEFPcRiHmAswPsMiQEDZLM8vX8qXpZNQ==} @@ -3617,7 +3377,7 @@ packages: resolution: {integrity: sha512-C5B2e5hCM4llrQkUms+KnWEMVW8K1n2XvX9G7ppfMZJQ7KAS/4rNnkP1Cs+HhWriOz1mWWTMFD4j1J7s31Dgug==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' css-functions-list@3.3.3: resolution: {integrity: sha512-8HFEBPKhOpJPEPu70wJJetjKta86Gw9+CCyCnB3sui2qQfOvRyqBy4IKLKKAwdMpWb2lHXWk9Wb4Z6AmaUT1Pg==} @@ -3627,13 +3387,13 @@ packages: resolution: {integrity: sha512-Uz/bsHRbOeir/5Oeuz85tq/yLJLxX+3dpoRdjNTshs6jjqwUg8XaEZGDd0ci3fw7l53Srw0EkJ8mYan0eW5uGQ==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' css-prefers-color-scheme@11.0.0: resolution: {integrity: sha512-fv0mgtwUhh2m9iio3Kxc2CkrogjIaRdMFaaqyzSFdii17JF4cfPyMNX72B15ZW2Nrr/NZUpxI4dec1VMHYJvdw==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' css-property-sort-order-smacss@2.2.0: resolution: {integrity: sha512-nXutswsivIEBOrPo/OZw2KQjFPLvtg68aovJf6Kqrm3L6FmTvvFPaeDrk83hh0+pRJGuP3PeKJwMS0E6DFipdQ==} @@ -3657,8 +3417,8 @@ packages: resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} engines: {node: '>= 6'} - cssdb@8.8.0: - resolution: {integrity: sha512-QbLeyz2Bgso1iRlh7IpWk6OKa3lLNGXsujVjDMPl9rOZpxKeiG69icLpbLCFxeURwmcdIfZqQyhlooKJYM4f8Q==} + cssdb@8.9.0: + resolution: {integrity: sha512-J8jOU/hLjaXcO1LldOLraJSQpfLXRKof0I7mtbRyOy2AAXgqst0x9rlgi2qXeD6d0ou3ZLqcPAMqYVbpCbrxEw==} cssesc@3.0.0: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} @@ -3811,8 +3571,8 @@ packages: resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} engines: {node: '>= 4'} - dompurify@3.4.0: - resolution: {integrity: sha512-nolgK9JcaUXMSmW+j1yaSvaEaoXYHwWyGJlkoCTghc97KgGDDSnpoU/PlEnw63Ah+TGKFOyY+X5LnxaWbCSfXg==} + dompurify@3.4.11: + resolution: {integrity: sha512-zhlUV12GsaRzMsf9q5M254YhA4+VuF0fG+QFqu6aYpoGlKtz+w8//jBcGVYBgQkR5GHjUomejY84AV+/uPbWdw==} domutils@3.2.2: resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} @@ -3880,8 +3640,8 @@ packages: end-of-stream@1.4.4: resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} - enhanced-resolve@5.21.3: - resolution: {integrity: sha512-QyL119InA+XXEkNLNTPCXPugSvOfhwv0JOlGNzvxs0hZaiHLNvXSpudUWsOlsXGWJh8G6ckCScEkVHfX3kw/2Q==} + enhanced-resolve@5.21.6: + resolution: {integrity: sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ==} engines: {node: '>=10.13.0'} entities@4.5.0: @@ -3928,6 +3688,10 @@ packages: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} + es-object-atoms@1.1.2: + resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} + engines: {node: '>= 0.4'} + es-set-tostringtag@2.1.0: resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} engines: {node: '>= 0.4'} @@ -3936,18 +3700,8 @@ packages: resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} engines: {node: '>= 0.4'} - esbuild@0.25.12: - resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} - engines: {node: '>=18'} - hasBin: true - - esbuild@0.27.5: - resolution: {integrity: sha512-zdQoHBjuDqKsvV5OPaWansOwfSQ0Js+Uj9J85TBvj3bFW1JjWTSULMRwdQAc8qMeIScbClxeMK0jlrtB9linhA==} - engines: {node: '>=18'} - hasBin: true - - esbuild@0.28.0: - resolution: {integrity: sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==} + esbuild@0.28.1: + resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} engines: {node: '>=18'} hasBin: true @@ -3980,8 +3734,8 @@ packages: peerDependencies: eslint: '>=8.40.0' - eslint-plugin-vue@10.9.1: - resolution: {integrity: sha512-cHB0Tf4Duvzwecwd/AqWzZvF/QszE13BhjVUpVXWCy9AeMR5GjkAjP3i85vqgLgOuTmkHR1OJ5oMeqLHtuw8zg==} + eslint-plugin-vue@10.9.2: + resolution: {integrity: sha512-4g7ZP3pYcuqd7Zp0pzUKcos0W+RkjBz4EGdhJ92FcYk6v03Ti/GK5NwjgsjxHK+98eXDbHeK7VtX1az7/8doZA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: '@stylistic/eslint-plugin': ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 @@ -4092,8 +3846,8 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - fast-uri@3.0.1: - resolution: {integrity: sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==} + fast-uri@3.1.2: + resolution: {integrity: sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==} fastest-levenshtein@1.0.16: resolution: {integrity: sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==} @@ -4118,8 +3872,8 @@ packages: resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} engines: {node: '>=8'} - file-entry-cache@11.1.2: - resolution: {integrity: sha512-N2WFfK12gmrK1c1GXOqiAJ1tc5YE+R53zvQ+t5P8S5XhnmKYVB5eZEiLNZKDSmoG8wqqbF9EXYBBW/nef19log==} + file-entry-cache@11.1.3: + resolution: {integrity: sha512-oMbq0PD6VIiIwMF6LIa7MEwd/l9huKwmqRKXqmrkqIZv8CvRbfowL+L0ryAl8h//HfAS0zS+4SbYoRyAoA6BJA==} file-entry-cache@8.0.0: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} @@ -4152,8 +3906,8 @@ packages: resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} engines: {node: '>=16'} - flat-cache@6.1.20: - resolution: {integrity: sha512-AhHYqwvN62NVLp4lObVXGVluiABTHapoB57EyegZVmazN+hhGhLTn3uZbOofoTw4DSDvVCadzzyChXhOAvy8uQ==} + flat-cache@6.1.22: + resolution: {integrity: sha512-N2dnzVJIphnNsjHcrxGW7DePckJ6haPrSFqpsBUhHYgwtKGVq4JrBGielEGD2fCVnsGm1zlBVZ8wGhkyuetgug==} flatpickr@4.6.13: resolution: {integrity: sha512-97PMG/aywoYpB4IvbvUJi0RQi8vearvU0oov1WW3k0WZPBMrTQVqekSX5CjSG/M4Q3i6A/0FKXC7RyAoAUUSPw==} @@ -4190,8 +3944,8 @@ packages: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} - form-data@4.0.5: - resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} + form-data@4.0.6: + resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==} engines: {node: '>= 6'} fraction.js@5.3.4: @@ -4344,8 +4098,8 @@ packages: resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==} engines: {node: '>=6.0'} - happy-dom@20.9.0: - resolution: {integrity: sha512-GZZ9mKe8r646NUAf/zemnGbjYh4Bt8/MqASJY+pSm5ZDtc3YQox+4gsLI7yi1hba6o+eCsGxpHn5+iEVn31/FQ==} + happy-dom@20.10.6: + resolution: {integrity: sha512-6QD0ilzDDt93tX44y8tbmZdAcdTRYDhUP+Asgi6pC8Pp5IA3cvaZGyoVN/EGtlq9ziT65iPuBBn3ASLr6hCgVw==} engines: {node: '>=20.0.0'} hard-rejection@2.1.0: @@ -4378,12 +4132,12 @@ packages: resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} engines: {node: '>= 0.4'} - hashery@1.4.0: - resolution: {integrity: sha512-Wn2i1In6XFxl8Az55kkgnFRiAlIAushzh26PTjL2AKtQcEfXrcLa7Hn5QOWGZEf3LU057P9TwwZjFyxfS1VuvQ==} + hashery@1.5.1: + resolution: {integrity: sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==} engines: {node: '>=20'} - hasown@2.0.3: - resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} engines: {node: '>= 0.4'} hast-util-to-html@9.0.5: @@ -4408,6 +4162,9 @@ packages: hookified@1.15.1: resolution: {integrity: sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==} + hookified@2.2.0: + resolution: {integrity: sha512-p/LgFzRN5FeoD3DLS6bkUapeye6E4SI6yJs6KetENd18S+FBthqYq2amJUWpt5z0EQwwHemidjY5OqJGEKm5uA==} + hosted-git-info@2.8.9: resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} @@ -4495,8 +4252,8 @@ packages: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} - ip-address@9.0.5: - resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==} + ip-address@10.2.0: + resolution: {integrity: sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==} engines: {node: '>= 12'} is-array-buffer@3.0.5: @@ -4725,8 +4482,12 @@ packages: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true - joi@18.1.2: - resolution: {integrity: sha512-rF5MAmps5esSlhCA+N1b6IYHDw9j/btzGaqfgie522jS02Ju/HXBxamlXVlKEHAxoMKQL77HWI8jlqWsFuekZA==} + jiti@2.7.0: + resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} + hasBin: true + + joi@18.2.1: + resolution: {integrity: sha512-2/OKlogiESf2Nh3TFCrRjrr9z1DRHeW0I+KReF67+4J0Ns+8hBtHRmoWAZ2OFU6I5+TWLEe6sVlSdXPjHm5UbQ==} engines: {node: '>= 20'} js-beautify@1.15.1: @@ -4734,9 +4495,9 @@ packages: engines: {node: '>=14'} hasBin: true - js-cookie@3.0.5: - resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==} - engines: {node: '>=14'} + js-cookie@3.0.7: + resolution: {integrity: sha512-z/wZZgDrkNV1eA0ULjM/F9/50Ya8fbzgKneSpoPsXSGd0KnpdtHfOZWK+GcwLk+EZbS4F9RBhU+K2RgzuDaItw==} + engines: {node: '>=20'} js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -4748,13 +4509,10 @@ packages: resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} hasBin: true - js-yaml@4.1.1: - resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + js-yaml@4.2.0: + resolution: {integrity: sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==} hasBin: true - jsbn@1.1.0: - resolution: {integrity: sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==} - jsdom@27.4.0: resolution: {integrity: sha512-mjzqwWRD9Y1J1KUi7W97Gja1bwOOM5Ug0EZ6UDK3xS7j7mndrkwozHtSblfomlzyB4NepioNt+B2sOSzczVgtQ==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} @@ -4769,6 +4527,11 @@ packages: engines: {node: '>=6'} hasBin: true + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} @@ -4831,8 +4594,8 @@ packages: resolution: {integrity: sha512-7W0vV3rqv5tokqkBAFV1LbR7HPOWzXQDpDgEuib/aJ1jsZZx6x3c2mBI+TJhJzOhkGeaLbCKEHXEXLfirtG2JA==} engines: {node: '>=18'} - launch-editor@2.10.0: - resolution: {integrity: sha512-D7dBRJo/qcGX9xlvt/6wUYzQxjh5G1RvZPgPv8vi4KRU99DVQL/oW7tnVOCCTm2HGeo3C5HvGE5Yrh6UBoZ0vA==} + launch-editor@2.14.1: + resolution: {integrity: sha512-QWBrQsMpH7gPr965dsKD/3cKWiNoTjpATQf++Xq63N6sKRGMwlVXz41O1IZTMfZQgBctD/K5Zt06+/I6pP6+HA==} leven@3.1.0: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} @@ -4915,8 +4678,8 @@ packages: lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - linkify-it@5.0.0: - resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} + linkify-it@5.0.1: + resolution: {integrity: sha512-wVoTjP4Q6R0NW5hiZkVJaFZPWgtXfoGF+6LucL3/FtiNjmcHhYjEr5f1Kqjirc1nBW07J/ZuRFumqr2oqccEWg==} linkifyjs@4.3.2: resolution: {integrity: sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==} @@ -4984,19 +4747,19 @@ packages: resolution: {integrity: sha512-sa2ErMQ6kKOA4l31gLGYliFQrMKkqSO0ZJgGhDHKijPf0pNFM9vghjAh3gn26pS4JDRs7Iwa9S36gxm3vgZTzg==} peerDependencies: '@types/markdown-it': '*' - markdown-it: '*' + markdown-it: '>=14.2.0' markdown-it-attrs@4.3.1: resolution: {integrity: sha512-/ko6cba+H6gdZ0DOw7BbNMZtfuJTRp9g/IrGIuz8lYc/EfnmWRpaR3CFPnNbVz0LDvF8Gf1hFGPqrQqq7De0rg==} engines: {node: '>=6'} peerDependencies: - markdown-it: '>= 9.0.0' + markdown-it: '>=14.2.0' markdown-it-emoji@3.0.0: resolution: {integrity: sha512-+rUD93bXHubA4arpEZO3q80so0qgoFJEKRkRbjKX8RTdca89v2kfyF+xR3i2sQTwql9tpPZPOQN5B+PunspXRg==} - markdown-it@14.1.1: - resolution: {integrity: sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==} + markdown-it@14.2.0: + resolution: {integrity: sha512-1TGiQiJVRQ3NPmZH6sx5Cfnmg6GQm9jvC1ch4TK511NjSJvjzKLzn5pPfZRNZkRPZP0HqCioSndqH8v2nRaWVQ==} hasBin: true marked@17.0.1: @@ -5219,10 +4982,6 @@ packages: orderedmap@2.1.1: resolution: {integrity: sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==} - os-tmpdir@1.0.2: - resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} - engines: {node: '>=0.10.0'} - otplib@12.0.1: resolution: {integrity: sha512-xDGvUOQjop7RDgxTQ+o4pOol0/3xSZzawTiPKRrHnQWAy0WjhNs/5HdIDJCrqC4MBynmjXgULc6YfioaxZeFgg==} @@ -5321,9 +5080,6 @@ packages: perfect-debounce@2.0.0: resolution: {integrity: sha512-fkEH/OBiKrqqI/yIgjR92lMfs2K8105zt/VT6+7eTjNwisrsh47CeIED9z58zI7DfKdH3uHAn25ziRZn3kgAow==} - picocolors@0.2.1: - resolution: {integrity: sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==} - picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -5362,61 +5118,61 @@ packages: resolution: {integrity: sha512-fovIPEV35c2JzVXdmP+sp2xirbBMt54J+upU8u6TSj410kUU5+axgEzvBBSAX8KCybze8CFCelzFAw/FfWg2TA==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' postcss-clamp@4.1.0: resolution: {integrity: sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow==} engines: {node: '>=7.6.0'} peerDependencies: - postcss: ^8.4.6 + postcss: '>=8.5.10' - postcss-color-functional-notation@8.0.3: - resolution: {integrity: sha512-MyaFK+3PusD7F2+qlMDP6+zfSgHWP17AtmvHQs44W3+Qbb39VptVDVRJ4Lf7gHSVffW5ekEy/XrsZ0S0t34hrA==} + postcss-color-functional-notation@8.0.5: + resolution: {integrity: sha512-Cxr97Vtt2VeJCGaex0JNSU5MViqYtjKmJLHKM+jI7d+qIs0J5xgHEVG6Q2bTCaFJ1yjcFz9s9VmWCibuzk3+MA==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' postcss-color-hex-alpha@11.0.0: resolution: {integrity: sha512-NCGa6vjIyrjosz9GqRxVKbONBklz5TeipYqTJp3IqbnBWlBq5e5EMtG6MaX4vqk9LzocPfMQkuRK9tfk+OQuKg==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' postcss-color-rebeccapurple@11.0.0: resolution: {integrity: sha512-g9561mx7cbdqx7XeO/L+lJzVlzu7bICyXr72efBVKZGxIhvBBJf9fGXn3Cb6U4Bwh3LbzQO2e9NWBLVYdX5Eag==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' postcss-custom-media@12.0.1: resolution: {integrity: sha512-66syE14+VeqkUf0rRX0bvbTCbNRJF132jD+ceo8th1dap2YJEAqpdh5uG98CE3IbgHT7m9XM0GIlOazNWqQdeA==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' postcss-custom-properties@15.0.1: resolution: {integrity: sha512-cuyq8sd8dLY0GLbelz1KB8IMIoDECo6RVXMeHeXY2Uw3Q05k/d1GVITdaKLsheqrHbnxlwxzSRZQQ5u+rNtbMg==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' postcss-custom-selectors@9.0.1: resolution: {integrity: sha512-2XBELy4DmdVKimChfaZ2id9u9CSGYQhiJ53SvlfBvMTzLMW2VxuMb9rHsMSQw9kRq/zSbhT5x13EaK8JSmK8KQ==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' postcss-dir-pseudo-class@10.0.0: resolution: {integrity: sha512-DmtIzULpyC8XaH4b5AaUgt4Jic4QmrECqidNCdR7u7naQFdnxX80YI06u238a+ZVRXwURDxVzy0s/UQnWmpVeg==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' - postcss-double-position-gradients@7.0.0: - resolution: {integrity: sha512-Msr/dxj8Os7KLJE5Hdhvprwm3K5Zrh1KTY0eFN3ngPKNkej/Usy4BM9JQmqE6CLAkDpHoQVsi4snbL72CPt6qg==} + postcss-double-position-gradients@7.0.1: + resolution: {integrity: sha512-M69I4EolEGwiYa0KmxKWg4zZp2DxhlNM0Bz12OvHCj930GXDVCvFhdWNGsRscz6BIijN6tFryzSFsy8kMLyD5Q==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' postcss-easing-gradients@3.0.1: resolution: {integrity: sha512-UrOKb4cenjGmMmrheETw7Cjnn/IKn3xgTvHs92b0sSwMhKgeZKxJpduGRjYZ8wgpu3zOzzgQpRwOLhhtMofayA==} @@ -5426,24 +5182,24 @@ packages: resolution: {integrity: sha512-VG1a9kBKizUBWS66t5xyB4uLONBnvZLCmZXxT40FALu8EF0QgVZBYy5ApC0KhmpHsv+pvHMJHB3agKHwmocWjw==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' postcss-focus-within@10.0.0: resolution: {integrity: sha512-dvql0fzUTG+gcJYp+KTbag5vAjuo94LDYZHkqDV1rnf5gPGer1v/SrmIZBdvKU8moep3HbcbujqGjzSb3DL53Q==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' postcss-font-variant@5.0.0: resolution: {integrity: sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==} peerDependencies: - postcss: ^8.1.0 + postcss: '>=8.5.10' postcss-gap-properties@7.0.0: resolution: {integrity: sha512-PSDF2QoZMRUbsINvXObQgxx4HExRP85QTT8qS/YN9fBsCPWCqUuwqAD6E6PNp0BqL/jU1eyWUBORaOK/J/9LDA==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' postcss-html@1.8.1: resolution: {integrity: sha512-OLF6P7qctfAWayOhLpcVnTGqVeJzu2W3WpIYelfz2+JV5oGxfkcEvweN9U4XpeqE0P98dcD9ssusGwlF0TK0uQ==} @@ -5453,19 +5209,19 @@ packages: resolution: {integrity: sha512-rEGNkOkNusf4+IuMmfEoIdLuVmvbExGbmG+MIsyV6jR5UaWSoyPcAYHV/PxzVDCmudyF+2Nh/o6Ub2saqUdnuA==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' - postcss-lab-function@8.0.3: - resolution: {integrity: sha512-rUa27RLVXjMn1aDkHEt5dRsK80+bAACPr8w5Ow0BkIlfH6gEk0Mh1I0REkYhtp4UhKFw1HLEk3AzvKBi6BGOqw==} + postcss-lab-function@8.0.5: + resolution: {integrity: sha512-ohQnYx1LloPkiLQhAjpt/Y9tAGCGOBOUaxgbcmO+1bDTFzUQCTfdpemOVh6oewI4V2K6q7+Vz8d3rP1glvK3uw==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' postcss-logical@9.0.0: resolution: {integrity: sha512-A4LNd9dk3q/juEUA9Gd8ALhBO3TeOeYurnyHLlf2aAToD94VHR8c5Uv7KNmf8YVRhTxvWsyug4c5fKtARzyIRQ==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' postcss-media-query-parser@0.2.3: resolution: {integrity: sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==} @@ -5474,47 +5230,47 @@ packages: resolution: {integrity: sha512-YGFOfVrjxYfeGTS5XctP1WCI5hu8Lr9SmntjfRC+iX5hCihEO+QZl9Ra+pkjqkgoVdDKvb2JccpElcowhZtzpw==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' postcss-opacity-percentage@3.0.0: resolution: {integrity: sha512-K6HGVzyxUxd/VgZdX04DCtdwWJ4NGLG212US4/LA1TLAbHgmAsTWVR86o+gGIbFtnTkfOpb9sCRBx8K7HO66qQ==} engines: {node: '>=18'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' postcss-overflow-shorthand@7.0.0: resolution: {integrity: sha512-9SLpjoUdGRoRrzoOdX66HbUs0+uDwfIAiXsRa7piKGOqPd6F4ZlON9oaDSP5r1Qpgmzw5L9Ht0undIK6igJPMA==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' postcss-page-break@3.0.4: resolution: {integrity: sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==} peerDependencies: - postcss: ^8 + postcss: '>=8.5.10' postcss-place@11.0.0: resolution: {integrity: sha512-fAifpyjQ+fuDRp2nmF95WbotqbpjdazebedahXdfBxy5sHembOLpBQ1cHveZD9ZmjK26tYM8tikeNaUlp/KfHA==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' - postcss-preset-env@11.2.1: - resolution: {integrity: sha512-dqL7WR5wg9yP/+6pTHIsIeIpK6XVghJDE4/r4ZSdr5ExrbLiN5x78gly0Xs0MLGbHy2oT3WWNfbxowmnw9BurQ==} + postcss-preset-env@11.3.1: + resolution: {integrity: sha512-ox2lu2L0fbuKXB0zRcUFCNii7koS9+fNLFqj+WOKaJ4DU/zZsYkFHOmz73lWNTKx8OHDqnV0R7Si98PIbJXLjQ==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' postcss-pseudo-class-any-link@11.0.0: resolution: {integrity: sha512-DNFZ4GMa3C3pU5dM+UCTG1CEeLtS1ZqV5DKSqCTJQMn1G5jnd/30fS8+A7H4o5bSD3MOcnx+VgI+xPE9Z5Wvig==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' postcss-replace-overflow-wrap@4.0.0: resolution: {integrity: sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==} peerDependencies: - postcss: ^8.0.3 + postcss: '>=8.5.10' postcss-resolve-nested-selector@0.1.6: resolution: {integrity: sha512-0sglIs9Wmkzbr8lQwEyIzlDOOC9bGmfVKcJTaxv3vMmd3uo4o4DerC3En0bnmgceeql9BfC8hRkp7cg0fjdVqw==} @@ -5523,25 +5279,25 @@ packages: resolution: {integrity: sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==} engines: {node: '>=12.0'} peerDependencies: - postcss: ^8.3.3 + postcss: '>=8.5.10' postcss-safe-parser@7.0.1: resolution: {integrity: sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==} engines: {node: '>=18.0'} peerDependencies: - postcss: ^8.4.31 + postcss: '>=8.5.10' postcss-scss@4.0.9: resolution: {integrity: sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==} engines: {node: '>=12.0'} peerDependencies: - postcss: ^8.4.29 + postcss: '>=8.5.10' postcss-selector-not@9.0.0: resolution: {integrity: sha512-xhAtTdHnVU2M/CrpYOPyRUvg3njhVlKmn2GNYXDaRJV9Ygx4d5OkSkc7NINzjUqnbDFtaKXlISOBeyMXU/zyFQ==} engines: {node: '>=20.19.0'} peerDependencies: - postcss: ^8.4 + postcss: '>=8.5.10' postcss-selector-parser@7.1.1: resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==} @@ -5550,7 +5306,7 @@ packages: postcss-sorting@8.0.2: resolution: {integrity: sha512-M9dkSrmU00t/jK7rF6BZSZauA5MAaBW4i5EnJXspMwt4iqTh/L9j6fgMnbElEOfyRyfLfVbIHj/R52zHzAPe1Q==} peerDependencies: - postcss: ^8.4.20 + postcss: '>=8.5.10' postcss-value-parser@3.3.1: resolution: {integrity: sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==} @@ -5558,10 +5314,6 @@ packages: postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - postcss@7.0.39: - resolution: {integrity: sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==} - engines: {node: '>=6.0.0'} - postcss@8.5.14: resolution: {integrity: sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==} engines: {node: ^10 || ^12 || >=14} @@ -5682,8 +5434,8 @@ packages: deprecated: < 24.15.0 is no longer supported hasBin: true - qified@0.6.0: - resolution: {integrity: sha512-tsSGN1x3h569ZSU1u6diwhltLyfUWDp3YbFHedapTmpBl0B3P6U3+Qptg7xu+v+1io1EwhdPyyRHYbEw0KN2FA==} + qified@0.10.1: + resolution: {integrity: sha512-+Owyggi9IxT1ePKGafcI87ubSmxol6smwJ+RAHDQlx9+9cPwFWDiKFFCPuWhr9ignlGpZ9vDQLw67N4dcTVFEA==} engines: {node: '>=20'} queue-microtask@1.2.3: @@ -5717,6 +5469,10 @@ packages: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} + readdirp@5.0.0: + resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} + engines: {node: '>= 20.19.0'} + redent@3.0.0: resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} engines: {node: '>=8'} @@ -5806,15 +5562,15 @@ packages: hasBin: true peerDependencies: rolldown: 1.x || ^1.0.0-beta - rollup: 4.60.3 + rollup: 4.62.2 peerDependenciesMeta: rolldown: optional: true rollup: optional: true - rollup@4.60.3: - resolution: {integrity: sha512-pAQK9HalE84QSm4Po3EmWIZPd3FnjkShVkiMlz1iligWYkWQ7wHYd1PF/T7QZ5TVSD6uSTon5gBVMSM4JfBV+A==} + rollup@4.62.2: + resolution: {integrity: sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -5861,118 +5617,118 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - sass-embedded-all-unknown@1.99.0: - resolution: {integrity: sha512-qPIRG8Uhjo6/OKyAKixTnwMliTz+t9K6Duk0mx5z+K7n0Ts38NSJz2sjDnc7cA/8V9Lb3q09H38dZ1CLwD+ssw==} + sass-embedded-all-unknown@1.100.0: + resolution: {integrity: sha512-auFtXY/kwYILmSVjtBDwyj0axcLbYYiffOKWoaXHnI5bsYwiRbBh3EneR1rpbX2ZIZCrwX93i5pxKLTZF/662Q==} cpu: ['!arm', '!arm64', '!riscv64', '!x64'] - sass-embedded-android-arm64@1.99.0: - resolution: {integrity: sha512-fNHhdnP23yqqieCbAdym4N47AleSwjbNt6OYIYx4DdACGdtERjQB4iOX/TaKsW034MupfF7SjnAAK8w7Ptldtg==} + sass-embedded-android-arm64@1.100.0: + resolution: {integrity: sha512-W+Ru9JwTnfU0UX3jSZcbqFdtKFMcYdfFwytc57h2DgnqCOIiAqI2E06mABZBZC+r3LwXCBuS5GbXAGeVgvVDkA==} engines: {node: '>=14.0.0'} cpu: [arm64] os: [android] - sass-embedded-android-arm@1.99.0: - resolution: {integrity: sha512-EHvJ0C7/VuP78Qr6f8gIUVUmCqIorEQpw2yp3cs3SMg02ZuumlhjXvkTcFBxHmFdFR23vTNk1WnhY6QSeV1nFQ==} + sass-embedded-android-arm@1.100.0: + resolution: {integrity: sha512-70f3HgX2pFNmzpGQ86n5e6QfWn2fP4QUQGfFQK0P1XH73ZLIzLo2YqygrGKGKeeqtc5eU2Wl1/xQzhzuKnO4kw==} engines: {node: '>=14.0.0'} cpu: [arm] os: [android] - sass-embedded-android-riscv64@1.99.0: - resolution: {integrity: sha512-4zqDFRvgGDTL5vTHuIhRxUpXFoh0Cy7Gm5Ywk19ASd8Settmd14YdPRZPmMxfgS1GH292PofV1fq1ifiSEJWBw==} + sass-embedded-android-riscv64@1.100.0: + resolution: {integrity: sha512-icU3o0V/uCSytSpf+tX5Lf51BvyQEbLzDUJfUi9etSauYBGHpPKkdtdZH0si4v98phq11Kl8rSV1SggksxF1Hg==} engines: {node: '>=14.0.0'} cpu: [riscv64] os: [android] - sass-embedded-android-x64@1.99.0: - resolution: {integrity: sha512-Uk53k/dGYt04RjOL4gFjZ0Z9DH9DKh8IA8WsXUkNqsxerAygoy3zqRBS2zngfE9K2jiOM87q+1R1p87ory9oQQ==} + sass-embedded-android-x64@1.100.0: + resolution: {integrity: sha512-mevF9VQk6gEYByy8+jusaHGmd7Usb2ytX/DsEOd0JtOGCtcf1kh575xJ6OUBDIcJ15uLnbau/0iy1eP6WVBvWA==} engines: {node: '>=14.0.0'} cpu: [x64] os: [android] - sass-embedded-darwin-arm64@1.99.0: - resolution: {integrity: sha512-u61/7U3IGLqoO6gL+AHeiAtlTPFwJK1+964U8gp45ZN0hzh1yrARf5O1mivXv8NnNgJvbG2wWJbiNZP0lG/lTg==} + sass-embedded-darwin-arm64@1.100.0: + resolution: {integrity: sha512-1PVlYi61POo93IT/FfrG1mc1tAHxeSTyUALF2aOFmXGWjVXr3bQzEQiBGCOvQbj/ix+5hNyXFXcEMEyKvtUJJA==} engines: {node: '>=14.0.0'} cpu: [arm64] os: [darwin] - sass-embedded-darwin-x64@1.99.0: - resolution: {integrity: sha512-j/kkk/NcXdIameLezSfXjgCiBkVcA+G60AXrX768/3g0miK1g7M9dj7xOhCb1i7/wQeiEI3rw2LLuO63xRIn4A==} + sass-embedded-darwin-x64@1.100.0: + resolution: {integrity: sha512-x97o3JnGyImZNCIVs9wQHJUE5QCvmVIKaH1cwrz/5dK7OT1FpeNiW+u9TUomP9hG6Ekjd8EL8NBHpxTfIhdjmg==} engines: {node: '>=14.0.0'} cpu: [x64] os: [darwin] - sass-embedded-linux-arm64@1.99.0: - resolution: {integrity: sha512-btNcFpItcB56L40n8hDeL7sRSMLDXQ56nB5h2deddJx1n60rpKSElJmkaDGHtpkrY+CTtDRV0FZDjHeTJddYew==} + sass-embedded-linux-arm64@1.100.0: + resolution: {integrity: sha512-Dwjmj8Z6VRy7rAi53JAdEwIyUjpfl7PhpSc2/LpQPQx+aO5Dp7Spaipkax0ufJl1SoDUdchCsM4y/88YaluorQ==} engines: {node: '>=14.0.0'} cpu: [arm64] os: [linux] - sass-embedded-linux-arm@1.99.0: - resolution: {integrity: sha512-d4IjJZrX2+AwB2YCy1JySwdptJECNP/WfAQLUl8txI3ka8/d3TUI155GtelnoZUkio211PwIeFvvAeZ9RXPQnw==} + sass-embedded-linux-arm@1.100.0: + resolution: {integrity: sha512-9Ul7O1eKrc5YlhwWjkp8tZPSe3UEwSZ1uwUZOQom1HL0pRlBA6F/IlGZYFTLwnHMIP1fc77MMNaBRfc05mKMpw==} engines: {node: '>=14.0.0'} cpu: [arm] os: [linux] - sass-embedded-linux-musl-arm64@1.99.0: - resolution: {integrity: sha512-Hi2bt/IrM5P4FBKz6EcHAlniwfpoz9mnTdvSd58y+avA3SANM76upIkAdSayA8ZGwyL3gZokru1AKDPF9lJDNw==} + sass-embedded-linux-musl-arm64@1.100.0: + resolution: {integrity: sha512-XpACJB2KjSLjf2e9uuvGVdOURsoNrFqgRiihhXyUHK9W0t3LIHb7z5MA/7XGPIT9bWSOO2zyw+rH/FHtDV/Yrg==} engines: {node: '>=14.0.0'} cpu: [arm64] os: [linux] - sass-embedded-linux-musl-arm@1.99.0: - resolution: {integrity: sha512-2gvHOupgIw3ytatXT4nFUow71LFbuOZPEwG+HUzcNQDH8ue4Ez8cr03vsv5MDv3lIjOKcXwDvWD980t18MwkoQ==} + sass-embedded-linux-musl-arm@1.100.0: + resolution: {integrity: sha512-sl0JgbGloPyJg66XXx5UDSDScZ0oU85DpMQU4JU/sCUCFj1Z8zZ69SJWKTCNE4/jwnce7WI2zPCV5AG+RHOZJw==} engines: {node: '>=14.0.0'} cpu: [arm] os: [linux] - sass-embedded-linux-musl-riscv64@1.99.0: - resolution: {integrity: sha512-mKqGvVaJ9rHMqyZsF0kikQe4NO0f4osb67+X6nLhBiVDKvyazQHJ3zJQreNefIE36yL2sjHIclSB//MprzaQDg==} + sass-embedded-linux-musl-riscv64@1.100.0: + resolution: {integrity: sha512-ShvI0Kx04mwoCARwZ0UjiT97isQvzO80tAt91zmFyHLN9kelc/IrQi940farSm2xQVPCKdeVyeG0ekBsokSpYQ==} engines: {node: '>=14.0.0'} cpu: [riscv64] os: [linux] - sass-embedded-linux-musl-x64@1.99.0: - resolution: {integrity: sha512-huhgOMmOc30r7CH7qbRbT9LerSEGSnWuS4CYNOskr9BvNeQp4dIneFufNRGZ7hkOAxUM8DglxIZJN/cyAT95Ew==} + sass-embedded-linux-musl-x64@1.100.0: + resolution: {integrity: sha512-TDBCRWNuS4RDLQXvRc1gjZlWiWTWaWGp0Bwu/IKwJxov81lsvrCs3TihTyNXtW7V5aoN4Ky3r0QOkNb3mwmBnA==} engines: {node: '>=14.0.0'} cpu: [x64] os: [linux] - sass-embedded-linux-riscv64@1.99.0: - resolution: {integrity: sha512-mevFPIFAVhrH90THifxLfOntFmHtcEKOcdWnep2gJ0X4DVva4AiVIRlQe/7w9JFx5+gnDRE1oaJJkzuFUuYZsA==} + sass-embedded-linux-riscv64@1.100.0: + resolution: {integrity: sha512-j4ENJGOheO+fm3j/yorLxCjBP6/XskrZx7dTLlT+lXYwN/qqCqoA/gsNLI0McS3DFM6GBwPiffzWsdWS8t6sEQ==} engines: {node: '>=14.0.0'} cpu: [riscv64] os: [linux] - sass-embedded-linux-x64@1.99.0: - resolution: {integrity: sha512-9k7IkULqIZdCIVt4Mboryt6vN8Mjmm3EhI1P3mClU5y5i3wLK5ExC3cbVWk047KsID/fvB1RLslqghXJx5BoxA==} + sass-embedded-linux-x64@1.100.0: + resolution: {integrity: sha512-0vUSN8j0WGtCJIOPh//EmUvYGHW0QOe5iul8qyhPk50MAcw49MA0r34AhftjDdx94ILPF6vApFs0gwHPQRlpVA==} engines: {node: '>=14.0.0'} cpu: [x64] os: [linux] - sass-embedded-unknown-all@1.99.0: - resolution: {integrity: sha512-P7MxiUtL/XzGo3PX0CaB8lNNEFLQWKikPA8pbKytx9ZCLZSDkt2NJcdAbblB/sqMs4AV3EK2NadV8rI/diq3xg==} + sass-embedded-unknown-all@1.100.0: + resolution: {integrity: sha512-c+naBgWId4MIpToXcI0DgqetjdAkwTTAxFAuOaBz7HUXLdyG1oZRrEvSsbe41nEdQOKH0vgofVFCeSQgoXOG9A==} os: ['!android', '!darwin', '!linux', '!win32'] - sass-embedded-win32-arm64@1.99.0: - resolution: {integrity: sha512-8whpsW7S+uO8QApKfQuc36m3P9EISzbVZOgC79goob4qGy09u8Gz/rYvw8h1prJDSjltpHGhOzBE6LDz7WvzVw==} + sass-embedded-win32-arm64@1.100.0: + resolution: {integrity: sha512-iE+yxj+hUXwwbqpHkXxgAWTzeRfcWxJ7SSTQEPMk48lwq3oCrWLlz5sQuWHbuTK/i0GKQfROdP+hOmPi89yjUg==} engines: {node: '>=14.0.0'} cpu: [arm64] os: [win32] - sass-embedded-win32-x64@1.99.0: - resolution: {integrity: sha512-ipuOv1R2K4MHeuCEAZGpuUbAgma4gb0sdacyrTjJtMOy/OY9UvWfVlwErdB09KIkp4fPDpQJDJfvYN6bC8jeNg==} + sass-embedded-win32-x64@1.100.0: + resolution: {integrity: sha512-qI4F8MI7/KYoy9NdjJfhSspG42WPkADSNDvwEV7qWvCSFC83koJssRsKO2/PfY+niZz6BG65Ic/D+A11h959hw==} engines: {node: '>=14.0.0'} cpu: [x64] os: [win32] - sass-embedded@1.99.0: - resolution: {integrity: sha512-gF/juR1aX02lZHkvwxdF80SapkQeg2fetoDF6gIQkNbSw5YEUFspMkyGTjPjgZSgIHuZpy+Wz4PlebKnLXMjdg==} + sass-embedded@1.100.0: + resolution: {integrity: sha512-Ut8wlQSk19tm7jMK6mz6cF1+e+E7tUnW2tM02zQDPnOTcVbV8qCQG8UWxZkkNlY50+hV3hqP24OOkUlMz8xBpw==} engines: {node: '>=16.0.0'} hasBin: true - sass@1.99.0: - resolution: {integrity: sha512-kgW13M54DUB7IsIRM5LvJkNlpH+WhMpooUcaWGFARkF1Tc82v9mIWkCbCYf+MBvpIUBSeSOTilpZjEPr2VYE6Q==} - engines: {node: '>=14.0.0'} + sass@1.100.0: + resolution: {integrity: sha512-B5j0rYMlinhhOo9tjQebMVVn0TfyXAF+wB3b2ggZUuJ/is/Y+7+JGjirAMxHZ9Z3hIP98NPfamlAkBHa1lAaXQ==} + engines: {node: '>=20.19.0'} hasBin: true sax@1.5.0: @@ -6024,8 +5780,9 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - shell-quote@1.8.1: - resolution: {integrity: sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==} + shell-quote@1.8.4: + resolution: {integrity: sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ==} + engines: {node: '>= 0.4'} shiki@3.2.1: resolution: {integrity: sha512-VML/2o1/KGYkEf/stJJ+s9Ypn7jUKQPomGLGYso4JJFMFxVDyPNsjsI3MB3KLjlMOeH44gyaPdXC6rik2WXvUQ==} @@ -6131,9 +5888,6 @@ packages: sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} - sprintf-js@1.1.3: - resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} - stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} @@ -6247,7 +6001,7 @@ packages: resolution: {integrity: sha512-VkVD9r7jfUT/dq3mA3/I1WXXk2U71rO5wvU2yIil9PW5o1g3UM7Xc82vHmuVJHV7Y8ok5K137fmW5u3HbhtTOA==} engines: {node: '>=20'} peerDependencies: - postcss: ^8.3.3 + postcss: '>=8.5.10' stylelint: ^17.0.0 peerDependenciesMeta: postcss: @@ -6270,7 +6024,7 @@ packages: resolution: {integrity: sha512-uLJS6xgOCBw5EMsDW7Ukji8l28qRoMnkRch15s0qwZpskXvWt9oPzMmcYM307m9GN4MxuWLsQh4I6hU9yI53cQ==} engines: {node: '>=20'} peerDependencies: - postcss: ^8.3.3 + postcss: '>=8.5.10' stylelint: ^17.0.0 peerDependenciesMeta: postcss: @@ -6299,8 +6053,8 @@ packages: peerDependencies: stylelint: '>= 11 < 18' - stylelint@17.11.0: - resolution: {integrity: sha512-/3czzmbF9XdGWvReDF3Ex4R23Ajolo7j8RB2bFNEqk6Ht356nlpVV+G5bG2Qt8AW1ofJzXztBRDnAtd7cgowWA==} + stylelint@17.13.0: + resolution: {integrity: sha512-G1WYzMerp7ihOaIe9VJCHLt12MoAD2QLf1AFerYP37+BCRBUK5UCpq8e/mN+zCIaJPKQcaxhE4WlPmqdiOx/gw==} engines: {node: '>=20.19.0'} hasBin: true @@ -6351,8 +6105,8 @@ packages: resolution: {integrity: sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==} engines: {node: '>=10.0.0'} - tailwindcss@4.3.0: - resolution: {integrity: sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==} + tailwindcss@4.3.1: + resolution: {integrity: sha512-hk+TB1m+K8CYNrP6rjQaq/Y+4Zylwpa87mLYBKCunwnnQ9p+fHb7kmSfGqyEJoxF/O6CDyABWVFEafNSYKll+Q==} tapable@2.3.3: resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} @@ -6419,9 +6173,9 @@ packages: resolution: {integrity: sha512-8PWx8tvC4jDB39BQw1m4x8y5MH1BcQ5xHeL2n7UVFulMPH/3Q0uiamahFJ3lXA0zO2SUyRXuVVbWSDmstlt9YA==} hasBin: true - tmp@0.0.33: - resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} - engines: {node: '>=0.6.0'} + tmp@0.2.7: + resolution: {integrity: sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==} + engines: {node: '>=14.14'} to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} @@ -6515,12 +6269,12 @@ packages: typed-query-selector@2.12.0: resolution: {integrity: sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==} - typescript-eslint@8.56.0: - resolution: {integrity: sha512-c7toRLrotJ9oixgdW7liukZpsnq5CZ7PuKztubGYlNppuTqhIoWfhgHo/7EU0v06gS2l/x0i2NEFK1qMIf0rIg==} + typescript-eslint@8.60.1: + resolution: {integrity: sha512-6m5hkkRAp8lKvhVpcprAIn5KkehQEh+47oHH2VGnExEh7dhNxXlg6GPAOIu6TxbVQxhebrJDvjl3020ooiWCMA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.0.0' + typescript: '>=4.8.4 <6.1.0' typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} @@ -6540,8 +6294,8 @@ packages: unbzip2-stream@1.4.3: resolution: {integrity: sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==} - undici-types@7.16.0: - resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + undici-types@7.18.2: + resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} unicode-canonical-property-names-ecmascript@2.0.0: resolution: {integrity: sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==} @@ -6691,8 +6445,8 @@ packages: '@vite-pwa/assets-generator': optional: true - vite-plugin-vue-devtools@8.1.2: - resolution: {integrity: sha512-gt5h1CNryR9Hy0tvhSbqY3j0F7aj0pGxBxWLa1lXSiZVkhdWDf0vbCOZyjh8ivFGE6FDHTGy3zkcZGlMZdVHig==} + vite-plugin-vue-devtools@8.1.3: + resolution: {integrity: sha512-KBTUhbTXvY+GsCdShnCHG4WdijEV74KIDxhF8erfSs5g5mS13g/cPRUf4mLpD10qr5FqHYosNt0j6rP5kpiS1Q==} engines: {node: '>=v14.21.3'} peerDependencies: vite: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -6707,8 +6461,8 @@ packages: peerDependencies: vue: '>=3.2.13' - vite@7.3.3: - resolution: {integrity: sha512-/4XH147Ui7OGTjg3HbdWe5arnZQSbfuRzdr9Ec7TQi5I7R+ir0Rlc9GIvD4v0XZurELqA035KVXJXpR61xhiTA==} + vite@7.3.5: + resolution: {integrity: sha512-KuOaNhcnGFN2zIPGA7wRmzF+lJA1sea7rHq17aiJ++9lzY1WWG6Jpwqwe1KNbRVPIqHmr8GLYx7jbrQcN/7/ww==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -6747,20 +6501,20 @@ packages: yaml: optional: true - vitest@4.1.5: - resolution: {integrity: sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==} + vitest@4.1.9: + resolution: {integrity: sha512-nE3/LEyc0z87uHYLZebqCUOaJr2hdtuPp7BQ4BosVFnfltxgAvMG08NyrSGlPpOUWvR27c5flSmYFTNr78L9GQ==} engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@opentelemetry/api': ^1.9.0 '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.1.5 - '@vitest/browser-preview': 4.1.5 - '@vitest/browser-webdriverio': 4.1.5 - '@vitest/coverage-istanbul': 4.1.5 - '@vitest/coverage-v8': 4.1.5 - '@vitest/ui': 4.1.5 + '@vitest/browser-playwright': 4.1.9 + '@vitest/browser-preview': 4.1.9 + '@vitest/browser-webdriverio': 4.1.9 + '@vitest/coverage-istanbul': 4.1.9 + '@vitest/coverage-v8': 4.1.9 + '@vitest/ui': 4.1.9 happy-dom: '*' jsdom: '*' vite: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -6839,8 +6593,8 @@ packages: peerDependencies: vue: ^3.5.0 - vue-tsc@3.2.8: - resolution: {integrity: sha512-27vTLJ6Q2370obOd0PFYoYoKnmXJ521uUIedrs3Zhhhg/8YG10VOCMmwt+JQslatpAMTDbnWiitLnoD5VlIvog==} + vue-tsc@3.3.5: + resolution: {integrity: sha512-Rzh/G2MmNlMSAMTiQEjDrsb4dgB/jbtEM47rVN2NtidF1dfb/q4w4QvpQBtW5+y3y5H27Hjh7deVwk+YB02fNg==} hasBin: true peerDependencies: typescript: '>=5.0.0' @@ -6869,8 +6623,8 @@ packages: resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} engines: {node: '>=18'} - wait-on@9.0.5: - resolution: {integrity: sha512-qgnbHDfDTRIp73ANEJNRW/7kn8CrDUcvZz18xotJQku/P4saTGkbIzvnMZebPmVvVNUiRq1qWAPyqCH+W4H8KA==} + wait-on@9.0.10: + resolution: {integrity: sha512-rCoJEhvMr0X6alHmwc9abbrA5ZrLZFKpFQVKPNFwl2h7DapXOGdmimIHDtLOWhT4PjhZhxFEtZoQgEXbkDWdZw==} engines: {node: '>=20.0.0'} hasBin: true @@ -7029,8 +6783,8 @@ packages: resolution: {integrity: sha512-OTIk8iR8/aCRWBqvxrzxR0hgxWpnYBblY1S5hDWBQfk/VFmJwzmJgQFN3WsoUKHISv2eAwe+PpbUzyL1CKTLXg==} engines: {node: ^20.17.0 || >=22.9.0} - ws@8.20.0: - resolution: {integrity: sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==} + ws@8.21.0: + resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -7112,11 +6866,6 @@ snapshots: '@akryum/tinypool@0.3.1': {} - '@ampproject/remapping@2.3.0': - dependencies: - '@jridgewell/gen-mapping': 0.3.5 - '@jridgewell/trace-mapping': 0.3.25 - '@apideck/better-ajv-errors@0.3.6(ajv@8.18.0)': dependencies: ajv: 8.18.0 @@ -7142,26 +6891,34 @@ snapshots: '@asamuzakjp/nwsapi@2.3.9': {} - '@babel/code-frame@7.26.2': + '@babel/code-frame@7.29.0': dependencies: '@babel/helper-validator-identifier': 7.28.5 js-tokens: 4.0.0 picocolors: 1.1.1 + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + '@babel/compat-data@7.26.0': {} - '@babel/core@7.26.0': + '@babel/compat-data@7.29.7': {} + + '@babel/core@7.29.7': dependencies: - '@ampproject/remapping': 2.3.0 - '@babel/code-frame': 7.26.2 - '@babel/generator': 7.26.0 - '@babel/helper-compilation-targets': 7.25.9 - '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) - '@babel/helpers': 7.26.10 - '@babel/parser': 7.28.5 - '@babel/template': 7.26.9 - '@babel/traverse': 7.25.9 - '@babel/types': 7.28.5 + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helpers': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 debug: 4.4.3 gensync: 1.0.0-beta.2 @@ -7170,667 +6927,696 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/generator@7.26.0': + '@babel/generator@7.29.7': dependencies: - '@babel/parser': 7.28.5 - '@babel/types': 7.28.5 - '@jridgewell/gen-mapping': 0.3.5 - '@jridgewell/trace-mapping': 0.3.25 - jsesc: 3.0.2 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 '@babel/helper-annotate-as-pure@7.25.9': dependencies: - '@babel/types': 7.28.5 + '@babel/types': 7.29.7 '@babel/helper-builder-binary-assignment-operator-visitor@7.25.9': dependencies: - '@babel/traverse': 7.25.9 - '@babel/types': 7.28.5 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 transitivePeerDependencies: - supports-color '@babel/helper-compilation-targets@7.25.9': dependencies: - '@babel/compat-data': 7.26.0 - '@babel/helper-validator-option': 7.25.9 + '@babel/compat-data': 7.29.7 + '@babel/helper-validator-option': 7.29.7 browserslist: 4.28.2 lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-create-class-features-plugin@7.25.9(@babel/core@7.26.0)': + '@babel/helper-compilation-targets@7.29.7': dependencies: - '@babel/core': 7.26.0 + '@babel/compat-data': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + browserslist: 4.28.2 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-create-class-features-plugin@7.25.9(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 '@babel/helper-annotate-as-pure': 7.25.9 '@babel/helper-member-expression-to-functions': 7.25.9 '@babel/helper-optimise-call-expression': 7.25.9 - '@babel/helper-replace-supers': 7.25.9(@babel/core@7.26.0) + '@babel/helper-replace-supers': 7.25.9(@babel/core@7.29.7) '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.29.7 semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/helper-create-regexp-features-plugin@7.25.9(@babel/core@7.26.0)': + '@babel/helper-create-regexp-features-plugin@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.29.7 '@babel/helper-annotate-as-pure': 7.25.9 regexpu-core: 6.1.1 semver: 6.3.1 - '@babel/helper-define-polyfill-provider@0.6.2(@babel/core@7.26.0)': + '@babel/helper-define-polyfill-provider@0.6.2(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-compilation-targets': 7.25.9 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 debug: 4.4.3 lodash.debounce: 4.0.8 resolve: 1.22.8 transitivePeerDependencies: - supports-color + '@babel/helper-globals@7.29.7': {} + '@babel/helper-member-expression-to-functions@7.25.9': dependencies: - '@babel/traverse': 7.25.9 - '@babel/types': 7.28.5 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 transitivePeerDependencies: - supports-color '@babel/helper-module-imports@7.25.9': dependencies: - '@babel/traverse': 7.25.9 - '@babel/types': 7.28.5 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.0)': + '@babel/helper-module-imports@7.29.7': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-module-imports': 7.25.9 - '@babel/helper-validator-identifier': 7.28.5 - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.7 transitivePeerDependencies: - supports-color '@babel/helper-optimise-call-expression@7.25.9': dependencies: - '@babel/types': 7.28.5 + '@babel/types': 7.29.7 '@babel/helper-plugin-utils@7.25.9': {} - '@babel/helper-remap-async-to-generator@7.25.9(@babel/core@7.26.0)': + '@babel/helper-plugin-utils@7.28.6': {} + + '@babel/helper-remap-async-to-generator@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.29.7 '@babel/helper-annotate-as-pure': 7.25.9 '@babel/helper-wrap-function': 7.25.9 - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.29.7 transitivePeerDependencies: - supports-color - '@babel/helper-replace-supers@7.25.9(@babel/core@7.26.0)': + '@babel/helper-replace-supers@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.29.7 '@babel/helper-member-expression-to-functions': 7.25.9 '@babel/helper-optimise-call-expression': 7.25.9 - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.29.7 transitivePeerDependencies: - supports-color '@babel/helper-simple-access@7.25.9': dependencies: - '@babel/traverse': 7.25.9 - '@babel/types': 7.28.5 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 transitivePeerDependencies: - supports-color '@babel/helper-skip-transparent-expression-wrappers@7.25.9': dependencies: - '@babel/traverse': 7.25.9 - '@babel/types': 7.28.5 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 transitivePeerDependencies: - supports-color '@babel/helper-string-parser@7.27.1': {} + '@babel/helper-string-parser@7.29.7': {} + '@babel/helper-validator-identifier@7.28.5': {} + '@babel/helper-validator-identifier@7.29.7': {} + '@babel/helper-validator-option@7.25.9': {} + '@babel/helper-validator-option@7.29.7': {} + '@babel/helper-wrap-function@7.25.9': dependencies: - '@babel/template': 7.26.9 - '@babel/traverse': 7.25.9 - '@babel/types': 7.28.5 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 transitivePeerDependencies: - supports-color - '@babel/helpers@7.26.10': + '@babel/helpers@7.29.7': dependencies: - '@babel/template': 7.26.9 - '@babel/types': 7.28.5 + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 '@babel/parser@7.28.5': dependencies: '@babel/types': 7.28.5 - '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.9(@babel/core@7.26.0)': + '@babel/parser@7.29.7': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 - '@babel/traverse': 7.25.9 + '@babel/types': 7.29.7 + + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.9(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/traverse': 7.29.7 transitivePeerDependencies: - supports-color - '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 - '@babel/plugin-transform-optional-chaining': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-optional-chaining': 7.25.9(@babel/core@7.29.7) transitivePeerDependencies: - supports-color - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 - '@babel/traverse': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/traverse': 7.29.7 transitivePeerDependencies: - supports-color - '@babel/plugin-proposal-decorators@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-proposal-decorators@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.26.0) - '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-syntax-decorators': 7.25.9(@babel/core@7.26.0) + '@babel/core': 7.29.7 + '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-decorators': 7.25.9(@babel/core@7.29.7) transitivePeerDependencies: - supports-color - '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.26.0)': + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.29.7 - '@babel/plugin-syntax-decorators@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-syntax-decorators@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-import-assertions@7.26.0(@babel/core@7.26.0)': + '@babel/plugin-syntax-import-assertions@7.26.0(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-import-attributes@7.26.0(@babel/core@7.26.0)': + '@babel/plugin-syntax-import-attributes@7.26.0(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.26.0)': + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-typescript@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-syntax-typescript@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.26.0)': + '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.26.0) - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-arrow-functions@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-arrow-functions@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-async-generator-functions@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-async-generator-functions@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 - '@babel/helper-remap-async-to-generator': 7.25.9(@babel/core@7.26.0) - '@babel/traverse': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-remap-async-to-generator': 7.25.9(@babel/core@7.29.7) + '@babel/traverse': 7.29.7 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-async-to-generator@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-async-to-generator@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-module-imports': 7.25.9 - '@babel/helper-plugin-utils': 7.25.9 - '@babel/helper-remap-async-to-generator': 7.25.9(@babel/core@7.26.0) + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-remap-async-to-generator': 7.25.9(@babel/core@7.29.7) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-block-scoped-functions@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-block-scoped-functions@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-block-scoping@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-block-scoping@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-class-properties@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-class-properties@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.26.0) - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-class-static-block@7.26.0(@babel/core@7.26.0)': + '@babel/plugin-transform-class-static-block@7.26.0(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.26.0) - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-classes@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-classes@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.29.7 '@babel/helper-annotate-as-pure': 7.25.9 - '@babel/helper-compilation-targets': 7.25.9 - '@babel/helper-plugin-utils': 7.25.9 - '@babel/helper-replace-supers': 7.25.9(@babel/core@7.26.0) - '@babel/traverse': 7.25.9 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-replace-supers': 7.25.9(@babel/core@7.29.7) + '@babel/traverse': 7.29.7 globals: 11.12.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-computed-properties@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-computed-properties@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 - '@babel/template': 7.26.9 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/template': 7.29.7 - '@babel/plugin-transform-destructuring@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-destructuring@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-dotall-regex@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-dotall-regex@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.26.0) - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-duplicate-keys@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-duplicate-keys@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.26.0) - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-dynamic-import@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-dynamic-import@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-exponentiation-operator@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-exponentiation-operator@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.29.7 '@babel/helper-builder-binary-assignment-operator-visitor': 7.25.9 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-export-namespace-from@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-export-namespace-from@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-for-of@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-for-of@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-function-name@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-function-name@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-compilation-targets': 7.25.9 - '@babel/helper-plugin-utils': 7.25.9 - '@babel/traverse': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/traverse': 7.29.7 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-json-strings@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-json-strings@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-literals@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-literals@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-logical-assignment-operators@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-logical-assignment-operators@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-member-expression-literals@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-member-expression-literals@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-modules-amd@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-modules-amd@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-commonjs@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-modules-commonjs@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-simple-access': 7.25.9 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-systemjs@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-modules-systemjs@7.29.4(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) - '@babel/helper-plugin-utils': 7.25.9 - '@babel/helper-validator-identifier': 7.28.5 - '@babel/traverse': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.7 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-umd@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-modules-umd@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-named-capturing-groups-regex@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-named-capturing-groups-regex@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.26.0) - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-new-target@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-new-target@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-nullish-coalescing-operator@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-nullish-coalescing-operator@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-numeric-separator@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-numeric-separator@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-object-rest-spread@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-object-rest-spread@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-compilation-targets': 7.25.9 - '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-parameters': 7.25.9(@babel/core@7.26.0) + '@babel/core': 7.29.7 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-transform-parameters': 7.25.9(@babel/core@7.29.7) - '@babel/plugin-transform-object-super@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-object-super@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 - '@babel/helper-replace-supers': 7.25.9(@babel/core@7.26.0) + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-replace-supers': 7.25.9(@babel/core@7.29.7) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-optional-catch-binding@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-optional-catch-binding@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-optional-chaining@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-optional-chaining@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-parameters@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-parameters@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-private-methods@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-private-methods@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.26.0) - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-private-property-in-object@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-private-property-in-object@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.29.7 '@babel/helper-annotate-as-pure': 7.25.9 - '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.26.0) - '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-property-literals@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-property-literals@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-regenerator@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-regenerator@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 regenerator-transform: 0.15.2 - '@babel/plugin-transform-regexp-modifiers@7.26.0(@babel/core@7.26.0)': + '@babel/plugin-transform-regexp-modifiers@7.26.0(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.26.0) - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-reserved-words@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-reserved-words@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-shorthand-properties@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-shorthand-properties@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-spread@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-spread@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-sticky-regex@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-sticky-regex@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-template-literals@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-template-literals@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-typeof-symbol@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-typeof-symbol@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-typescript@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-typescript@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.29.7 '@babel/helper-annotate-as-pure': 7.25.9 - '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.26.0) - '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 - '@babel/plugin-syntax-typescript': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-syntax-typescript': 7.25.9(@babel/core@7.29.7) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-unicode-escapes@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-unicode-escapes@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-unicode-property-regex@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-unicode-property-regex@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.26.0) - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-unicode-regex@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-unicode-regex@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.26.0) - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-unicode-sets-regex@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-unicode-sets-regex@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.26.0) - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.28.6 - '@babel/preset-env@7.26.0(@babel/core@7.26.0)': + '@babel/preset-env@7.26.0(@babel/core@7.29.7)': dependencies: '@babel/compat-data': 7.26.0 - '@babel/core': 7.26.0 + '@babel/core': 7.29.7 '@babel/helper-compilation-targets': 7.25.9 '@babel/helper-plugin-utils': 7.25.9 '@babel/helper-validator-option': 7.25.9 - '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.26.0) - '@babel/plugin-syntax-import-assertions': 7.26.0(@babel/core@7.26.0) - '@babel/plugin-syntax-import-attributes': 7.26.0(@babel/core@7.26.0) - '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.26.0) - '@babel/plugin-transform-arrow-functions': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-async-generator-functions': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-async-to-generator': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-block-scoped-functions': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-block-scoping': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-class-properties': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-class-static-block': 7.26.0(@babel/core@7.26.0) - '@babel/plugin-transform-classes': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-computed-properties': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-destructuring': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-dotall-regex': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-duplicate-keys': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-dynamic-import': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-exponentiation-operator': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-export-namespace-from': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-for-of': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-function-name': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-json-strings': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-literals': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-logical-assignment-operators': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-member-expression-literals': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-modules-amd': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-modules-commonjs': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-modules-systemjs': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-modules-umd': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-named-capturing-groups-regex': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-new-target': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-nullish-coalescing-operator': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-numeric-separator': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-object-rest-spread': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-object-super': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-optional-catch-binding': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-optional-chaining': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-parameters': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-private-methods': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-private-property-in-object': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-property-literals': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-regenerator': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-regexp-modifiers': 7.26.0(@babel/core@7.26.0) - '@babel/plugin-transform-reserved-words': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-shorthand-properties': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-spread': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-sticky-regex': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-template-literals': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-typeof-symbol': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-unicode-escapes': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-unicode-property-regex': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-unicode-regex': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-unicode-sets-regex': 7.25.9(@babel/core@7.26.0) - '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.26.0) - babel-plugin-polyfill-corejs2: 0.4.11(@babel/core@7.26.0) - babel-plugin-polyfill-corejs3: 0.10.6(@babel/core@7.26.0) - babel-plugin-polyfill-regenerator: 0.6.2(@babel/core@7.26.0) + '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.7) + '@babel/plugin-syntax-import-assertions': 7.26.0(@babel/core@7.29.7) + '@babel/plugin-syntax-import-attributes': 7.26.0(@babel/core@7.29.7) + '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.29.7) + '@babel/plugin-transform-arrow-functions': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-async-generator-functions': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-async-to-generator': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-block-scoped-functions': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-block-scoping': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-class-properties': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-class-static-block': 7.26.0(@babel/core@7.29.7) + '@babel/plugin-transform-classes': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-computed-properties': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-destructuring': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-dotall-regex': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-duplicate-keys': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-dynamic-import': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-exponentiation-operator': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-export-namespace-from': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-for-of': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-function-name': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-json-strings': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-literals': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-logical-assignment-operators': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-member-expression-literals': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-modules-amd': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-modules-commonjs': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-modules-systemjs': 7.29.4(@babel/core@7.29.7) + '@babel/plugin-transform-modules-umd': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-named-capturing-groups-regex': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-new-target': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-nullish-coalescing-operator': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-numeric-separator': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-object-rest-spread': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-object-super': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-optional-catch-binding': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-optional-chaining': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-parameters': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-private-methods': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-private-property-in-object': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-property-literals': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-regenerator': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-regexp-modifiers': 7.26.0(@babel/core@7.29.7) + '@babel/plugin-transform-reserved-words': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-shorthand-properties': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-spread': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-sticky-regex': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-template-literals': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-typeof-symbol': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-unicode-escapes': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-unicode-property-regex': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-unicode-regex': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-unicode-sets-regex': 7.25.9(@babel/core@7.29.7) + '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.29.7) + babel-plugin-polyfill-corejs2: 0.4.11(@babel/core@7.29.7) + babel-plugin-polyfill-corejs3: 0.10.6(@babel/core@7.29.7) + babel-plugin-polyfill-regenerator: 0.6.2(@babel/core@7.29.7) core-js-compat: 3.38.1 semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.26.0)': + '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 - '@babel/types': 7.28.5 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/types': 7.29.7 esutils: 2.0.3 '@babel/runtime@7.25.4': dependencies: regenerator-runtime: 0.14.1 - '@babel/template@7.26.9': + '@babel/template@7.29.7': dependencies: - '@babel/code-frame': 7.26.2 - '@babel/parser': 7.28.5 - '@babel/types': 7.28.5 + '@babel/code-frame': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 - '@babel/traverse@7.25.9': + '@babel/traverse@7.29.7': dependencies: - '@babel/code-frame': 7.26.2 - '@babel/generator': 7.26.0 - '@babel/parser': 7.28.5 - '@babel/template': 7.26.9 - '@babel/types': 7.28.5 + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/helper-globals': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 debug: 4.4.3 - globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -7839,18 +7625,23 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@babel/types@7.29.7': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@bufbuild/protobuf@2.5.2': {} - '@cacheable/memory@2.0.7': + '@cacheable/memory@2.0.9': dependencies: - '@cacheable/utils': 2.3.4 - '@keyv/bigmap': 1.3.0(keyv@5.6.0) + '@cacheable/utils': 2.4.1 + '@keyv/bigmap': 1.3.1(keyv@5.6.0) hookified: 1.15.1 keyv: 5.6.0 - '@cacheable/utils@2.3.4': + '@cacheable/utils@2.4.1': dependencies: - hashery: 1.4.0 + hashery: 1.5.1 keyv: 5.6.0 '@codemirror/commands@6.8.1': @@ -7911,7 +7702,7 @@ snapshots: '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/css-calc@3.2.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + '@csstools/css-calc@3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': dependencies: '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 @@ -7923,10 +7714,10 @@ snapshots: '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/css-color-parser@4.1.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + '@csstools/css-color-parser@4.1.7(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': dependencies: '@csstools/color-helpers': 6.0.2 - '@csstools/css-calc': 3.2.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 @@ -7942,6 +7733,10 @@ snapshots: optionalDependencies: css-tree: 3.2.1 + '@csstools/css-syntax-patches-for-csstree@1.1.5(css-tree@3.2.1)': + optionalDependencies: + css-tree: 3.2.1 + '@csstools/css-tokenizer@3.0.4': {} '@csstools/css-tokenizer@4.0.0': {} @@ -7951,12 +7746,12 @@ snapshots: '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 - '@csstools/postcss-alpha-function@2.0.4(postcss@8.5.14)': + '@csstools/postcss-alpha-function@2.0.6(postcss@8.5.14)': dependencies: - '@csstools/css-color-parser': 4.1.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-color-parser': 4.1.7(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 - '@csstools/postcss-progressive-custom-properties': 5.0.0(postcss@8.5.14) + '@csstools/postcss-progressive-custom-properties': 5.1.0(postcss@8.5.14) '@csstools/utilities': 3.0.0(postcss@8.5.14) postcss: 8.5.14 @@ -7966,62 +7761,68 @@ snapshots: postcss: 8.5.14 postcss-selector-parser: 7.1.1 - '@csstools/postcss-color-function-display-p3-linear@2.0.3(postcss@8.5.14)': + '@csstools/postcss-color-function-display-p3-linear@2.0.5(postcss@8.5.14)': dependencies: - '@csstools/css-color-parser': 4.1.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-color-parser': 4.1.7(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 - '@csstools/postcss-progressive-custom-properties': 5.0.0(postcss@8.5.14) + '@csstools/postcss-progressive-custom-properties': 5.1.0(postcss@8.5.14) '@csstools/utilities': 3.0.0(postcss@8.5.14) postcss: 8.5.14 - '@csstools/postcss-color-function@5.0.3(postcss@8.5.14)': + '@csstools/postcss-color-function@5.0.5(postcss@8.5.14)': dependencies: - '@csstools/css-color-parser': 4.1.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-color-parser': 4.1.7(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 - '@csstools/postcss-progressive-custom-properties': 5.0.0(postcss@8.5.14) + '@csstools/postcss-progressive-custom-properties': 5.1.0(postcss@8.5.14) '@csstools/utilities': 3.0.0(postcss@8.5.14) postcss: 8.5.14 - '@csstools/postcss-color-mix-function@4.0.3(postcss@8.5.14)': + '@csstools/postcss-color-mix-function@4.0.5(postcss@8.5.14)': dependencies: - '@csstools/css-color-parser': 4.1.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-color-parser': 4.1.7(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 - '@csstools/postcss-progressive-custom-properties': 5.0.0(postcss@8.5.14) + '@csstools/postcss-progressive-custom-properties': 5.1.0(postcss@8.5.14) '@csstools/utilities': 3.0.0(postcss@8.5.14) postcss: 8.5.14 - '@csstools/postcss-color-mix-variadic-function-arguments@2.0.3(postcss@8.5.14)': + '@csstools/postcss-color-mix-variadic-function-arguments@2.0.5(postcss@8.5.14)': dependencies: - '@csstools/css-color-parser': 4.1.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-color-parser': 4.1.7(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 - '@csstools/postcss-progressive-custom-properties': 5.0.0(postcss@8.5.14) + '@csstools/postcss-progressive-custom-properties': 5.1.0(postcss@8.5.14) '@csstools/utilities': 3.0.0(postcss@8.5.14) postcss: 8.5.14 - '@csstools/postcss-content-alt-text@3.0.0(postcss@8.5.14)': + '@csstools/postcss-container-rule-prelude-list@1.0.1(postcss@8.5.14)': dependencies: '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 - '@csstools/postcss-progressive-custom-properties': 5.0.0(postcss@8.5.14) - '@csstools/utilities': 3.0.0(postcss@8.5.14) postcss: 8.5.14 - '@csstools/postcss-contrast-color-function@3.0.3(postcss@8.5.14)': + '@csstools/postcss-content-alt-text@3.0.1(postcss@8.5.14)': dependencies: - '@csstools/css-color-parser': 4.1.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 - '@csstools/postcss-progressive-custom-properties': 5.0.0(postcss@8.5.14) + '@csstools/postcss-progressive-custom-properties': 5.1.0(postcss@8.5.14) '@csstools/utilities': 3.0.0(postcss@8.5.14) postcss: 8.5.14 - '@csstools/postcss-exponential-functions@3.0.2(postcss@8.5.14)': + '@csstools/postcss-contrast-color-function@3.0.5(postcss@8.5.14)': dependencies: - '@csstools/css-calc': 3.2.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-color-parser': 4.1.7(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + '@csstools/postcss-progressive-custom-properties': 5.1.0(postcss@8.5.14) + '@csstools/utilities': 3.0.0(postcss@8.5.14) + postcss: 8.5.14 + + '@csstools/postcss-exponential-functions@3.0.3(postcss@8.5.14)': + dependencies: + '@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 postcss: 8.5.14 @@ -8037,38 +7838,46 @@ snapshots: '@csstools/utilities': 3.0.0(postcss@8.5.14) postcss: 8.5.14 - '@csstools/postcss-gamut-mapping@3.0.3(postcss@8.5.14)': + '@csstools/postcss-gamut-mapping@3.0.5(postcss@8.5.14)': dependencies: - '@csstools/css-color-parser': 4.1.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-color-parser': 4.1.7(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 postcss: 8.5.14 - '@csstools/postcss-gradients-interpolation-method@6.0.3(postcss@8.5.14)': + '@csstools/postcss-gradients-interpolation-method@6.0.5(postcss@8.5.14)': dependencies: - '@csstools/css-color-parser': 4.1.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-color-parser': 4.1.7(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 - '@csstools/postcss-progressive-custom-properties': 5.0.0(postcss@8.5.14) + '@csstools/postcss-progressive-custom-properties': 5.1.0(postcss@8.5.14) '@csstools/utilities': 3.0.0(postcss@8.5.14) postcss: 8.5.14 - '@csstools/postcss-hwb-function@5.0.3(postcss@8.5.14)': + '@csstools/postcss-hwb-function@5.0.5(postcss@8.5.14)': dependencies: - '@csstools/css-color-parser': 4.1.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-color-parser': 4.1.7(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 - '@csstools/postcss-progressive-custom-properties': 5.0.0(postcss@8.5.14) + '@csstools/postcss-progressive-custom-properties': 5.1.0(postcss@8.5.14) '@csstools/utilities': 3.0.0(postcss@8.5.14) postcss: 8.5.14 - '@csstools/postcss-ic-unit@5.0.0(postcss@8.5.14)': + '@csstools/postcss-ic-unit@5.0.1(postcss@8.5.14)': dependencies: - '@csstools/postcss-progressive-custom-properties': 5.0.0(postcss@8.5.14) + '@csstools/postcss-progressive-custom-properties': 5.1.0(postcss@8.5.14) '@csstools/utilities': 3.0.0(postcss@8.5.14) postcss: 8.5.14 postcss-value-parser: 4.2.0 + '@csstools/postcss-image-function@1.0.0(postcss@8.5.14)': + dependencies: + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + '@csstools/postcss-progressive-custom-properties': 5.1.0(postcss@8.5.14) + '@csstools/utilities': 3.0.0(postcss@8.5.14) + postcss: 8.5.14 + '@csstools/postcss-initial@3.0.0(postcss@8.5.14)': dependencies: postcss: 8.5.14 @@ -8079,11 +7888,11 @@ snapshots: postcss: 8.5.14 postcss-selector-parser: 7.1.1 - '@csstools/postcss-light-dark-function@3.0.0(postcss@8.5.14)': + '@csstools/postcss-light-dark-function@3.0.1(postcss@8.5.14)': dependencies: '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 - '@csstools/postcss-progressive-custom-properties': 5.0.0(postcss@8.5.14) + '@csstools/postcss-progressive-custom-properties': 5.1.0(postcss@8.5.14) '@csstools/utilities': 3.0.0(postcss@8.5.14) postcss: 8.5.14 @@ -8110,9 +7919,9 @@ snapshots: '@csstools/utilities': 3.0.0(postcss@8.5.14) postcss: 8.5.14 - '@csstools/postcss-media-minmax@3.0.2(postcss@8.5.14)': + '@csstools/postcss-media-minmax@3.0.3(postcss@8.5.14)': dependencies: - '@csstools/css-calc': 3.2.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 '@csstools/media-query-list-parser': 5.0.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) @@ -8142,12 +7951,12 @@ snapshots: postcss: 8.5.14 postcss-value-parser: 4.2.0 - '@csstools/postcss-oklab-function@5.0.3(postcss@8.5.14)': + '@csstools/postcss-oklab-function@5.0.5(postcss@8.5.14)': dependencies: - '@csstools/css-color-parser': 4.1.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-color-parser': 4.1.7(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 - '@csstools/postcss-progressive-custom-properties': 5.0.0(postcss@8.5.14) + '@csstools/postcss-progressive-custom-properties': 5.1.0(postcss@8.5.14) '@csstools/utilities': 3.0.0(postcss@8.5.14) postcss: 8.5.14 @@ -8155,7 +7964,7 @@ snapshots: dependencies: postcss: 8.5.14 - '@csstools/postcss-progressive-custom-properties@5.0.0(postcss@8.5.14)': + '@csstools/postcss-progressive-custom-properties@5.1.0(postcss@8.5.14)': dependencies: postcss: 8.5.14 postcss-value-parser: 4.2.0 @@ -8166,19 +7975,19 @@ snapshots: '@csstools/css-tokenizer': 4.0.0 postcss: 8.5.14 - '@csstools/postcss-random-function@3.0.2(postcss@8.5.14)': + '@csstools/postcss-random-function@3.0.3(postcss@8.5.14)': dependencies: - '@csstools/css-calc': 3.2.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 postcss: 8.5.14 - '@csstools/postcss-relative-color-syntax@4.0.3(postcss@8.5.14)': + '@csstools/postcss-relative-color-syntax@4.0.5(postcss@8.5.14)': dependencies: - '@csstools/css-color-parser': 4.1.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-color-parser': 4.1.7(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 - '@csstools/postcss-progressive-custom-properties': 5.0.0(postcss@8.5.14) + '@csstools/postcss-progressive-custom-properties': 5.1.0(postcss@8.5.14) '@csstools/utilities': 3.0.0(postcss@8.5.14) postcss: 8.5.14 @@ -8187,16 +7996,16 @@ snapshots: postcss: 8.5.14 postcss-selector-parser: 7.1.1 - '@csstools/postcss-sign-functions@2.0.2(postcss@8.5.14)': + '@csstools/postcss-sign-functions@2.0.3(postcss@8.5.14)': dependencies: - '@csstools/css-calc': 3.2.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 postcss: 8.5.14 - '@csstools/postcss-stepped-value-functions@5.0.2(postcss@8.5.14)': + '@csstools/postcss-stepped-value-functions@5.0.3(postcss@8.5.14)': dependencies: - '@csstools/css-calc': 3.2.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 postcss: 8.5.14 @@ -8218,9 +8027,9 @@ snapshots: postcss: 8.5.14 postcss-value-parser: 4.2.0 - '@csstools/postcss-trigonometric-functions@5.0.2(postcss@8.5.14)': + '@csstools/postcss-trigonometric-functions@5.0.3(postcss@8.5.14)': dependencies: - '@csstools/css-calc': 3.2.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 postcss: 8.5.14 @@ -8241,238 +8050,82 @@ snapshots: dependencies: postcss: 8.5.14 - '@esbuild/aix-ppc64@0.25.12': + '@esbuild/aix-ppc64@0.28.1': optional: true - '@esbuild/aix-ppc64@0.27.5': + '@esbuild/android-arm64@0.28.1': optional: true - '@esbuild/aix-ppc64@0.28.0': + '@esbuild/android-arm@0.28.1': optional: true - '@esbuild/android-arm64@0.25.12': + '@esbuild/android-x64@0.28.1': optional: true - '@esbuild/android-arm64@0.27.5': + '@esbuild/darwin-arm64@0.28.1': optional: true - '@esbuild/android-arm64@0.28.0': + '@esbuild/darwin-x64@0.28.1': optional: true - '@esbuild/android-arm@0.25.12': + '@esbuild/freebsd-arm64@0.28.1': optional: true - '@esbuild/android-arm@0.27.5': + '@esbuild/freebsd-x64@0.28.1': optional: true - '@esbuild/android-arm@0.28.0': + '@esbuild/linux-arm64@0.28.1': optional: true - '@esbuild/android-x64@0.25.12': + '@esbuild/linux-arm@0.28.1': optional: true - '@esbuild/android-x64@0.27.5': + '@esbuild/linux-ia32@0.28.1': optional: true - '@esbuild/android-x64@0.28.0': + '@esbuild/linux-loong64@0.28.1': optional: true - '@esbuild/darwin-arm64@0.25.12': + '@esbuild/linux-mips64el@0.28.1': optional: true - '@esbuild/darwin-arm64@0.27.5': + '@esbuild/linux-ppc64@0.28.1': optional: true - '@esbuild/darwin-arm64@0.28.0': + '@esbuild/linux-riscv64@0.28.1': optional: true - '@esbuild/darwin-x64@0.25.12': + '@esbuild/linux-s390x@0.28.1': optional: true - '@esbuild/darwin-x64@0.27.5': + '@esbuild/linux-x64@0.28.1': optional: true - '@esbuild/darwin-x64@0.28.0': + '@esbuild/netbsd-arm64@0.28.1': optional: true - '@esbuild/freebsd-arm64@0.25.12': + '@esbuild/netbsd-x64@0.28.1': optional: true - '@esbuild/freebsd-arm64@0.27.5': + '@esbuild/openbsd-arm64@0.28.1': optional: true - '@esbuild/freebsd-arm64@0.28.0': + '@esbuild/openbsd-x64@0.28.1': optional: true - '@esbuild/freebsd-x64@0.25.12': + '@esbuild/openharmony-arm64@0.28.1': optional: true - '@esbuild/freebsd-x64@0.27.5': + '@esbuild/sunos-x64@0.28.1': optional: true - '@esbuild/freebsd-x64@0.28.0': + '@esbuild/win32-arm64@0.28.1': optional: true - '@esbuild/linux-arm64@0.25.12': + '@esbuild/win32-ia32@0.28.1': optional: true - '@esbuild/linux-arm64@0.27.5': - optional: true - - '@esbuild/linux-arm64@0.28.0': - optional: true - - '@esbuild/linux-arm@0.25.12': - optional: true - - '@esbuild/linux-arm@0.27.5': - optional: true - - '@esbuild/linux-arm@0.28.0': - optional: true - - '@esbuild/linux-ia32@0.25.12': - optional: true - - '@esbuild/linux-ia32@0.27.5': - optional: true - - '@esbuild/linux-ia32@0.28.0': - optional: true - - '@esbuild/linux-loong64@0.25.12': - optional: true - - '@esbuild/linux-loong64@0.27.5': - optional: true - - '@esbuild/linux-loong64@0.28.0': - optional: true - - '@esbuild/linux-mips64el@0.25.12': - optional: true - - '@esbuild/linux-mips64el@0.27.5': - optional: true - - '@esbuild/linux-mips64el@0.28.0': - optional: true - - '@esbuild/linux-ppc64@0.25.12': - optional: true - - '@esbuild/linux-ppc64@0.27.5': - optional: true - - '@esbuild/linux-ppc64@0.28.0': - optional: true - - '@esbuild/linux-riscv64@0.25.12': - optional: true - - '@esbuild/linux-riscv64@0.27.5': - optional: true - - '@esbuild/linux-riscv64@0.28.0': - optional: true - - '@esbuild/linux-s390x@0.25.12': - optional: true - - '@esbuild/linux-s390x@0.27.5': - optional: true - - '@esbuild/linux-s390x@0.28.0': - optional: true - - '@esbuild/linux-x64@0.25.12': - optional: true - - '@esbuild/linux-x64@0.27.5': - optional: true - - '@esbuild/linux-x64@0.28.0': - optional: true - - '@esbuild/netbsd-arm64@0.25.12': - optional: true - - '@esbuild/netbsd-arm64@0.27.5': - optional: true - - '@esbuild/netbsd-arm64@0.28.0': - optional: true - - '@esbuild/netbsd-x64@0.25.12': - optional: true - - '@esbuild/netbsd-x64@0.27.5': - optional: true - - '@esbuild/netbsd-x64@0.28.0': - optional: true - - '@esbuild/openbsd-arm64@0.25.12': - optional: true - - '@esbuild/openbsd-arm64@0.27.5': - optional: true - - '@esbuild/openbsd-arm64@0.28.0': - optional: true - - '@esbuild/openbsd-x64@0.25.12': - optional: true - - '@esbuild/openbsd-x64@0.27.5': - optional: true - - '@esbuild/openbsd-x64@0.28.0': - optional: true - - '@esbuild/openharmony-arm64@0.25.12': - optional: true - - '@esbuild/openharmony-arm64@0.27.5': - optional: true - - '@esbuild/openharmony-arm64@0.28.0': - optional: true - - '@esbuild/sunos-x64@0.25.12': - optional: true - - '@esbuild/sunos-x64@0.27.5': - optional: true - - '@esbuild/sunos-x64@0.28.0': - optional: true - - '@esbuild/win32-arm64@0.25.12': - optional: true - - '@esbuild/win32-arm64@0.27.5': - optional: true - - '@esbuild/win32-arm64@0.28.0': - optional: true - - '@esbuild/win32-ia32@0.25.12': - optional: true - - '@esbuild/win32-ia32@0.27.5': - optional: true - - '@esbuild/win32-ia32@0.28.0': - optional: true - - '@esbuild/win32-x64@0.25.12': - optional: true - - '@esbuild/win32-x64@0.27.5': - optional: true - - '@esbuild/win32-x64@0.28.0': + '@esbuild/win32-x64@0.28.1': optional: true '@eslint-community/eslint-utils@4.9.1(eslint@9.39.4(jiti@2.6.1))': @@ -8506,7 +8159,7 @@ snapshots: globals: 14.0.0 ignore: 5.3.2 import-fresh: 3.3.0 - js-yaml: 4.1.1 + js-yaml: 4.2.0 minimatch: 10.2.4 strip-json-comments: 3.1.1 transitivePeerDependencies: @@ -8523,7 +8176,7 @@ snapshots: '@exodus/bytes@1.8.0': {} - '@faker-js/faker@10.4.0': {} + '@faker-js/faker@10.5.0': {} '@floating-ui/core@1.7.3': dependencies: @@ -8597,17 +8250,17 @@ snapshots: dependencies: '@hapi/hoek': 11.0.7 - '@histoire/app@1.0.0-beta.1(vite@7.3.3(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3))': + '@histoire/app@1.0.0-beta.1(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3))': dependencies: - '@histoire/controls': 1.0.0-beta.1(vite@7.3.3(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3)) - '@histoire/shared': 1.0.0-beta.1(vite@7.3.3(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3)) + '@histoire/controls': 1.0.0-beta.1(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3)) + '@histoire/shared': 1.0.0-beta.1(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3)) '@histoire/vendors': 1.0.0-beta.1 fuse.js: 7.1.0 shiki: 3.2.1 transitivePeerDependencies: - vite - '@histoire/controls@1.0.0-beta.1(vite@7.3.3(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3))': + '@histoire/controls@1.0.0-beta.1(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3))': dependencies: '@codemirror/commands': 6.8.1 '@codemirror/lang-json': 6.0.1 @@ -8616,17 +8269,17 @@ snapshots: '@codemirror/state': 6.5.2 '@codemirror/theme-one-dark': 6.1.2 '@codemirror/view': 6.36.5 - '@histoire/shared': 1.0.0-beta.1(vite@7.3.3(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3)) + '@histoire/shared': 1.0.0-beta.1(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3)) '@histoire/vendors': 1.0.0-beta.1 transitivePeerDependencies: - vite - '@histoire/plugin-screenshot@1.0.0-beta.1(histoire@1.0.0-beta.1(@types/node@24.12.3)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(vite@7.3.3(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3))(yaml@2.8.3))(typescript@5.9.3)': + '@histoire/plugin-screenshot@1.0.0-beta.1(histoire@1.0.0-beta.1(@types/node@24.13.2)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3))(yaml@2.8.3))(typescript@5.9.3)': dependencies: capture-website: 4.2.0(typescript@5.9.3) defu: 6.1.7 fs-extra: 11.2.0 - histoire: 1.0.0-beta.1(@types/node@24.12.3)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(vite@7.3.3(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3))(yaml@2.8.3) + histoire: 1.0.0-beta.1(@types/node@24.13.2)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3))(yaml@2.8.3) pathe: 1.1.2 transitivePeerDependencies: - bare-buffer @@ -8635,21 +8288,21 @@ snapshots: - typescript - utf-8-validate - '@histoire/plugin-vue@1.0.0-beta.1(histoire@1.0.0-beta.1(@types/node@24.12.3)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(vite@7.3.3(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3))(yaml@2.8.3))(vite@7.3.3(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3))(vue@3.5.27(typescript@5.9.3))': + '@histoire/plugin-vue@1.0.0-beta.1(histoire@1.0.0-beta.1(@types/node@24.13.2)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3))(yaml@2.8.3))(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3))(vue@3.5.27(typescript@5.9.3))': dependencies: - '@histoire/controls': 1.0.0-beta.1(vite@7.3.3(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3)) - '@histoire/shared': 1.0.0-beta.1(vite@7.3.3(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3)) + '@histoire/controls': 1.0.0-beta.1(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3)) + '@histoire/shared': 1.0.0-beta.1(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3)) '@histoire/vendors': 1.0.0-beta.1 change-case: 5.4.4 globby: 14.1.0 - histoire: 1.0.0-beta.1(@types/node@24.12.3)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(vite@7.3.3(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3))(yaml@2.8.3) - launch-editor: 2.10.0 + histoire: 1.0.0-beta.1(@types/node@24.13.2)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3))(yaml@2.8.3) + launch-editor: 2.14.1 pathe: 1.1.2 vue: 3.5.27(typescript@5.9.3) transitivePeerDependencies: - vite - '@histoire/shared@1.0.0-beta.1(vite@7.3.3(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3))': + '@histoire/shared@1.0.0-beta.1(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3))': dependencies: '@histoire/vendors': 1.0.0-beta.1 '@types/fs-extra': 11.0.4 @@ -8657,7 +8310,7 @@ snapshots: chokidar: 4.0.3 pathe: 1.1.2 picocolors: 1.1.1 - vite: 7.3.3(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3) + vite: 7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3) '@histoire/vendors@1.0.0-beta.1': {} @@ -8679,7 +8332,7 @@ snapshots: '@intlify/message-compiler': 11.2.8 '@intlify/shared': 11.2.8 acorn: 8.15.0 - esbuild: 0.25.12 + esbuild: 0.28.1 escodegen: 2.1.0 estree-walker: 2.0.2 jsonc-eslint-parser: 2.4.0 @@ -8700,13 +8353,13 @@ snapshots: '@intlify/shared@11.2.8': {} - '@intlify/unplugin-vue-i18n@11.0.3(@vue/compiler-dom@3.5.27)(eslint@9.39.4(jiti@2.6.1))(rollup@4.60.3)(typescript@5.9.3)(vue-i18n@11.2.8(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3))': + '@intlify/unplugin-vue-i18n@11.0.3(@vue/compiler-dom@3.5.27)(eslint@9.39.4(jiti@2.6.1))(rollup@4.62.2)(typescript@5.9.3)(vue-i18n@11.2.8(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3))': dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.6.1)) '@intlify/bundle-utils': 11.0.3(vue-i18n@11.2.8(vue@3.5.27(typescript@5.9.3))) '@intlify/shared': 11.2.8 '@intlify/vue-i18n-extensions': 8.0.0(@intlify/shared@11.2.8)(@vue/compiler-dom@3.5.27)(vue-i18n@11.2.8(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3)) - '@rollup/pluginutils': 5.1.3(rollup@4.60.3) + '@rollup/pluginutils': 5.1.3(rollup@4.62.2) '@typescript-eslint/scope-manager': 8.58.0 '@typescript-eslint/typescript-estree': 8.58.0(typescript@5.9.3) debug: 4.4.3 @@ -8742,36 +8395,33 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 - '@jridgewell/gen-mapping@0.3.5': + '@jridgewell/gen-mapping@0.3.13': dependencies: - '@jridgewell/set-array': 1.2.1 '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/trace-mapping': 0.3.31 '@jridgewell/remapping@2.3.5': dependencies: - '@jridgewell/gen-mapping': 0.3.5 - '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 '@jridgewell/resolve-uri@3.1.2': {} - '@jridgewell/set-array@1.2.1': {} - '@jridgewell/source-map@0.3.6': dependencies: - '@jridgewell/gen-mapping': 0.3.5 - '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 '@jridgewell/sourcemap-codec@1.5.5': {} - '@jridgewell/trace-mapping@0.3.25': + '@jridgewell/trace-mapping@0.3.31': dependencies: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@keyv/bigmap@1.3.0(keyv@5.6.0)': + '@keyv/bigmap@1.3.1(keyv@5.6.0)': dependencies: - hashery: 1.4.0 + hashery: 1.5.1 hookified: 1.15.1 keyv: 5.6.0 @@ -8951,124 +8601,124 @@ snapshots: '@remusao/trie@1.5.0': {} - '@rolldown/pluginutils@1.0.0-rc.13': {} + '@rolldown/pluginutils@1.0.1': {} - '@rollup/plugin-babel@6.1.0(@babel/core@7.26.0)(rollup@4.60.3)': + '@rollup/plugin-babel@6.1.0(@babel/core@7.29.7)(rollup@4.62.2)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.29.7 '@babel/helper-module-imports': 7.25.9 - '@rollup/pluginutils': 5.1.3(rollup@4.60.3) + '@rollup/pluginutils': 5.1.3(rollup@4.62.2) optionalDependencies: - rollup: 4.60.3 + rollup: 4.62.2 transitivePeerDependencies: - supports-color - '@rollup/plugin-node-resolve@16.0.3(rollup@4.60.3)': + '@rollup/plugin-node-resolve@16.0.3(rollup@4.62.2)': dependencies: - '@rollup/pluginutils': 5.1.3(rollup@4.60.3) + '@rollup/pluginutils': 5.1.3(rollup@4.62.2) '@types/resolve': 1.20.2 deepmerge: 4.3.1 is-module: 1.0.0 resolve: 1.22.8 optionalDependencies: - rollup: 4.60.3 + rollup: 4.62.2 - '@rollup/plugin-replace@6.0.3(rollup@4.60.3)': + '@rollup/plugin-replace@6.0.3(rollup@4.62.2)': dependencies: - '@rollup/pluginutils': 5.1.3(rollup@4.60.3) + '@rollup/pluginutils': 5.1.3(rollup@4.62.2) magic-string: 0.30.21 optionalDependencies: - rollup: 4.60.3 + rollup: 4.62.2 - '@rollup/plugin-terser@1.0.0(rollup@4.60.3)': + '@rollup/plugin-terser@1.0.0(rollup@4.62.2)': dependencies: serialize-javascript: 7.0.5 smob: 1.5.0 terser: 5.31.6 optionalDependencies: - rollup: 4.60.3 + rollup: 4.62.2 - '@rollup/pluginutils@5.1.3(rollup@4.60.3)': + '@rollup/pluginutils@5.1.3(rollup@4.62.2)': dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 estree-walker: 2.0.2 picomatch: 4.0.4 optionalDependencies: - rollup: 4.60.3 + rollup: 4.62.2 - '@rollup/rollup-android-arm-eabi@4.60.3': + '@rollup/rollup-android-arm-eabi@4.62.2': optional: true - '@rollup/rollup-android-arm64@4.60.3': + '@rollup/rollup-android-arm64@4.62.2': optional: true - '@rollup/rollup-darwin-arm64@4.60.3': + '@rollup/rollup-darwin-arm64@4.62.2': optional: true - '@rollup/rollup-darwin-x64@4.60.3': + '@rollup/rollup-darwin-x64@4.62.2': optional: true - '@rollup/rollup-freebsd-arm64@4.60.3': + '@rollup/rollup-freebsd-arm64@4.62.2': optional: true - '@rollup/rollup-freebsd-x64@4.60.3': + '@rollup/rollup-freebsd-x64@4.62.2': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.60.3': + '@rollup/rollup-linux-arm-gnueabihf@4.62.2': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.60.3': + '@rollup/rollup-linux-arm-musleabihf@4.62.2': optional: true - '@rollup/rollup-linux-arm64-gnu@4.60.3': + '@rollup/rollup-linux-arm64-gnu@4.62.2': optional: true - '@rollup/rollup-linux-arm64-musl@4.60.3': + '@rollup/rollup-linux-arm64-musl@4.62.2': optional: true - '@rollup/rollup-linux-loong64-gnu@4.60.3': + '@rollup/rollup-linux-loong64-gnu@4.62.2': optional: true - '@rollup/rollup-linux-loong64-musl@4.60.3': + '@rollup/rollup-linux-loong64-musl@4.62.2': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.60.3': + '@rollup/rollup-linux-ppc64-gnu@4.62.2': optional: true - '@rollup/rollup-linux-ppc64-musl@4.60.3': + '@rollup/rollup-linux-ppc64-musl@4.62.2': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.60.3': + '@rollup/rollup-linux-riscv64-gnu@4.62.2': optional: true - '@rollup/rollup-linux-riscv64-musl@4.60.3': + '@rollup/rollup-linux-riscv64-musl@4.62.2': optional: true - '@rollup/rollup-linux-s390x-gnu@4.60.3': + '@rollup/rollup-linux-s390x-gnu@4.62.2': optional: true - '@rollup/rollup-linux-x64-gnu@4.60.3': + '@rollup/rollup-linux-x64-gnu@4.62.2': optional: true - '@rollup/rollup-linux-x64-musl@4.60.3': + '@rollup/rollup-linux-x64-musl@4.62.2': optional: true - '@rollup/rollup-openbsd-x64@4.60.3': + '@rollup/rollup-openbsd-x64@4.62.2': optional: true - '@rollup/rollup-openharmony-arm64@4.60.3': + '@rollup/rollup-openharmony-arm64@4.62.2': optional: true - '@rollup/rollup-win32-arm64-msvc@4.60.3': + '@rollup/rollup-win32-arm64-msvc@4.62.2': optional: true - '@rollup/rollup-win32-ia32-msvc@4.60.3': + '@rollup/rollup-win32-ia32-msvc@4.62.2': optional: true - '@rollup/rollup-win32-x64-gnu@4.60.3': + '@rollup/rollup-win32-x64-gnu@4.62.2': optional: true - '@rollup/rollup-win32-x64-msvc@4.60.3': + '@rollup/rollup-win32-x64-msvc@4.62.2': optional: true '@sentry-internal/browser-utils@10.36.0': @@ -9101,7 +8751,7 @@ snapshots: '@sentry/bundler-plugin-core@3.6.1': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.29.7 '@sentry/babel-plugin-component-annotate': 3.6.1 '@sentry/cli': 2.58.5 dotenv: 16.6.1 @@ -9214,79 +8864,79 @@ snapshots: '@standard-schema/spec@1.1.0': {} - '@tailwindcss/node@4.3.0': + '@tailwindcss/node@4.3.1': dependencies: '@jridgewell/remapping': 2.3.5 - enhanced-resolve: 5.21.3 - jiti: 2.6.1 + enhanced-resolve: 5.21.6 + jiti: 2.7.0 lightningcss: 1.32.0 magic-string: 0.30.21 source-map-js: 1.2.1 - tailwindcss: 4.3.0 + tailwindcss: 4.3.1 - '@tailwindcss/oxide-android-arm64@4.3.0': + '@tailwindcss/oxide-android-arm64@4.3.1': optional: true - '@tailwindcss/oxide-darwin-arm64@4.3.0': + '@tailwindcss/oxide-darwin-arm64@4.3.1': optional: true - '@tailwindcss/oxide-darwin-x64@4.3.0': + '@tailwindcss/oxide-darwin-x64@4.3.1': optional: true - '@tailwindcss/oxide-freebsd-x64@4.3.0': + '@tailwindcss/oxide-freebsd-x64@4.3.1': optional: true - '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.0': + '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.1': optional: true - '@tailwindcss/oxide-linux-arm64-gnu@4.3.0': + '@tailwindcss/oxide-linux-arm64-gnu@4.3.1': optional: true - '@tailwindcss/oxide-linux-arm64-musl@4.3.0': + '@tailwindcss/oxide-linux-arm64-musl@4.3.1': optional: true - '@tailwindcss/oxide-linux-x64-gnu@4.3.0': + '@tailwindcss/oxide-linux-x64-gnu@4.3.1': optional: true - '@tailwindcss/oxide-linux-x64-musl@4.3.0': + '@tailwindcss/oxide-linux-x64-musl@4.3.1': optional: true - '@tailwindcss/oxide-wasm32-wasi@4.3.0': + '@tailwindcss/oxide-wasm32-wasi@4.3.1': optional: true - '@tailwindcss/oxide-win32-arm64-msvc@4.3.0': + '@tailwindcss/oxide-win32-arm64-msvc@4.3.1': optional: true - '@tailwindcss/oxide-win32-x64-msvc@4.3.0': + '@tailwindcss/oxide-win32-x64-msvc@4.3.1': optional: true - '@tailwindcss/oxide@4.3.0': + '@tailwindcss/oxide@4.3.1': optionalDependencies: - '@tailwindcss/oxide-android-arm64': 4.3.0 - '@tailwindcss/oxide-darwin-arm64': 4.3.0 - '@tailwindcss/oxide-darwin-x64': 4.3.0 - '@tailwindcss/oxide-freebsd-x64': 4.3.0 - '@tailwindcss/oxide-linux-arm-gnueabihf': 4.3.0 - '@tailwindcss/oxide-linux-arm64-gnu': 4.3.0 - '@tailwindcss/oxide-linux-arm64-musl': 4.3.0 - '@tailwindcss/oxide-linux-x64-gnu': 4.3.0 - '@tailwindcss/oxide-linux-x64-musl': 4.3.0 - '@tailwindcss/oxide-wasm32-wasi': 4.3.0 - '@tailwindcss/oxide-win32-arm64-msvc': 4.3.0 - '@tailwindcss/oxide-win32-x64-msvc': 4.3.0 + '@tailwindcss/oxide-android-arm64': 4.3.1 + '@tailwindcss/oxide-darwin-arm64': 4.3.1 + '@tailwindcss/oxide-darwin-x64': 4.3.1 + '@tailwindcss/oxide-freebsd-x64': 4.3.1 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.3.1 + '@tailwindcss/oxide-linux-arm64-gnu': 4.3.1 + '@tailwindcss/oxide-linux-arm64-musl': 4.3.1 + '@tailwindcss/oxide-linux-x64-gnu': 4.3.1 + '@tailwindcss/oxide-linux-x64-musl': 4.3.1 + '@tailwindcss/oxide-wasm32-wasi': 4.3.1 + '@tailwindcss/oxide-win32-arm64-msvc': 4.3.1 + '@tailwindcss/oxide-win32-x64-msvc': 4.3.1 - '@tailwindcss/vite@4.3.0(vite@7.3.3(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3))': + '@tailwindcss/vite@4.3.1(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3))': dependencies: - '@tailwindcss/node': 4.3.0 - '@tailwindcss/oxide': 4.3.0 - tailwindcss: 4.3.0 - vite: 7.3.3(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3) + '@tailwindcss/node': 4.3.1 + '@tailwindcss/oxide': 4.3.1 + tailwindcss: 4.3.1 + vite: 7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3) '@tiptap/core@3.17.0(@tiptap/pm@3.17.0)': dependencies: '@tiptap/pm': 3.17.0 - '@tiptap/extension-blockquote@3.17.1(@tiptap/core@3.17.0(@tiptap/pm@3.17.0))': + '@tiptap/extension-blockquote@3.17.0(@tiptap/core@3.17.0(@tiptap/pm@3.17.0))': dependencies: '@tiptap/core': 3.17.0(@tiptap/pm@3.17.0) @@ -9445,7 +9095,7 @@ snapshots: '@tiptap/starter-kit@3.17.0': dependencies: '@tiptap/core': 3.17.0(@tiptap/pm@3.17.0) - '@tiptap/extension-blockquote': 3.17.1(@tiptap/core@3.17.0(@tiptap/pm@3.17.0)) + '@tiptap/extension-blockquote': 3.17.0(@tiptap/core@3.17.0(@tiptap/pm@3.17.0)) '@tiptap/extension-bold': 3.17.1(@tiptap/core@3.17.0(@tiptap/pm@3.17.0)) '@tiptap/extension-bullet-list': 3.17.1(@tiptap/extension-list@3.17.0(@tiptap/core@3.17.0(@tiptap/pm@3.17.0))(@tiptap/pm@3.17.0)) '@tiptap/extension-code': 3.17.1(@tiptap/core@3.17.0(@tiptap/pm@3.17.0)) @@ -9507,10 +9157,12 @@ snapshots: '@types/estree@1.0.8': {} + '@types/estree@1.0.9': {} + '@types/fs-extra@11.0.4': dependencies: '@types/jsonfile': 6.1.4 - '@types/node': 24.12.3 + '@types/node': 24.13.2 '@types/hast@3.0.4': dependencies: @@ -9522,7 +9174,7 @@ snapshots: '@types/jsonfile@6.1.4': dependencies: - '@types/node': 24.12.3 + '@types/node': 24.13.2 '@types/linkify-it@5.0.0': {} @@ -9539,9 +9191,9 @@ snapshots: '@types/minimist@1.2.5': {} - '@types/node@24.12.3': + '@types/node@24.13.2': dependencies: - undici-types: 7.16.0 + undici-types: 7.18.2 '@types/normalize-package-data@2.4.4': {} @@ -9551,7 +9203,7 @@ snapshots: '@types/tern@0.23.9': dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 '@types/trusted-types@2.0.7': {} @@ -9563,21 +9215,21 @@ snapshots: '@types/ws@8.18.1': dependencies: - '@types/node': 24.12.3 + '@types/node': 24.13.2 '@types/yauzl@2.10.3': dependencies: - '@types/node': 24.12.3 + '@types/node': 24.13.2 optional: true - '@typescript-eslint/eslint-plugin@8.56.0(@typescript-eslint/parser@8.56.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.60.1(@typescript-eslint/parser@8.60.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.56.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.56.0 - '@typescript-eslint/type-utils': 8.56.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/utils': 8.56.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.56.0 + '@typescript-eslint/parser': 8.60.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.60.1 + '@typescript-eslint/type-utils': 8.60.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/utils': 8.60.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.60.1 eslint: 9.39.4(jiti@2.6.1) ignore: 7.0.5 natural-compare: 1.4.0 @@ -9586,14 +9238,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/eslint-plugin@8.59.2(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.61.1(@typescript-eslint/parser@8.61.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.59.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.59.2 - '@typescript-eslint/type-utils': 8.59.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/utils': 8.59.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.59.2 + '@typescript-eslint/parser': 8.61.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.61.1 + '@typescript-eslint/type-utils': 8.61.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/utils': 8.61.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.61.1 eslint: 9.39.4(jiti@2.6.1) ignore: 7.0.5 natural-compare: 1.4.0 @@ -9602,93 +9254,93 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.56.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/parser@8.60.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 8.56.0 - '@typescript-eslint/types': 8.56.0 - '@typescript-eslint/typescript-estree': 8.56.0(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.56.0 + '@typescript-eslint/scope-manager': 8.60.1 + '@typescript-eslint/types': 8.60.1 + '@typescript-eslint/typescript-estree': 8.60.1(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.60.1 debug: 4.4.3 eslint: 9.39.4(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/parser@8.61.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 8.59.2 - '@typescript-eslint/types': 8.59.2 - '@typescript-eslint/typescript-estree': 8.59.2(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.59.2 + '@typescript-eslint/scope-manager': 8.61.1 + '@typescript-eslint/types': 8.61.1 + '@typescript-eslint/typescript-estree': 8.61.1(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.61.1 debug: 4.4.3 eslint: 9.39.4(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.56.0(typescript@5.9.3)': - dependencies: - '@typescript-eslint/tsconfig-utils': 8.59.1(typescript@5.9.3) - '@typescript-eslint/types': 8.59.1 - debug: 4.4.3 - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/project-service@8.58.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.59.1(typescript@5.9.3) - '@typescript-eslint/types': 8.59.1 + '@typescript-eslint/tsconfig-utils': 8.61.0(typescript@5.9.3) + '@typescript-eslint/types': 8.61.0 debug: 4.4.3 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.59.2(typescript@5.9.3)': + '@typescript-eslint/project-service@8.60.1(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.59.2(typescript@5.9.3) - '@typescript-eslint/types': 8.59.2 + '@typescript-eslint/tsconfig-utils': 8.61.0(typescript@5.9.3) + '@typescript-eslint/types': 8.61.0 debug: 4.4.3 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.56.0': + '@typescript-eslint/project-service@8.61.1(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.56.0 - '@typescript-eslint/visitor-keys': 8.56.0 + '@typescript-eslint/tsconfig-utils': 8.61.1(typescript@5.9.3) + '@typescript-eslint/types': 8.61.1 + debug: 4.4.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color '@typescript-eslint/scope-manager@8.58.0': dependencies: '@typescript-eslint/types': 8.58.0 '@typescript-eslint/visitor-keys': 8.58.0 - '@typescript-eslint/scope-manager@8.59.2': + '@typescript-eslint/scope-manager@8.60.1': dependencies: - '@typescript-eslint/types': 8.59.2 - '@typescript-eslint/visitor-keys': 8.59.2 + '@typescript-eslint/types': 8.60.1 + '@typescript-eslint/visitor-keys': 8.60.1 - '@typescript-eslint/tsconfig-utils@8.56.0(typescript@5.9.3)': + '@typescript-eslint/scope-manager@8.61.1': dependencies: - typescript: 5.9.3 + '@typescript-eslint/types': 8.61.1 + '@typescript-eslint/visitor-keys': 8.61.1 '@typescript-eslint/tsconfig-utils@8.58.0(typescript@5.9.3)': dependencies: typescript: 5.9.3 - '@typescript-eslint/tsconfig-utils@8.59.1(typescript@5.9.3)': + '@typescript-eslint/tsconfig-utils@8.60.1(typescript@5.9.3)': dependencies: typescript: 5.9.3 - '@typescript-eslint/tsconfig-utils@8.59.2(typescript@5.9.3)': + '@typescript-eslint/tsconfig-utils@8.61.0(typescript@5.9.3)': dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.56.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/tsconfig-utils@8.61.1(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.56.0 - '@typescript-eslint/typescript-estree': 8.56.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.56.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + typescript: 5.9.3 + + '@typescript-eslint/type-utils@8.60.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 8.60.1 + '@typescript-eslint/typescript-estree': 8.60.1(typescript@5.9.3) + '@typescript-eslint/utils': 8.60.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) debug: 4.4.3 eslint: 9.39.4(jiti@2.6.1) ts-api-utils: 2.5.0(typescript@5.9.3) @@ -9696,11 +9348,11 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/type-utils@8.59.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.61.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.59.2 - '@typescript-eslint/typescript-estree': 8.59.2(typescript@5.9.3) - '@typescript-eslint/utils': 8.59.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/types': 8.61.1 + '@typescript-eslint/typescript-estree': 8.61.1(typescript@5.9.3) + '@typescript-eslint/utils': 8.61.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) debug: 4.4.3 eslint: 9.39.4(jiti@2.6.1) ts-api-utils: 2.5.0(typescript@5.9.3) @@ -9708,28 +9360,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.56.0': {} - '@typescript-eslint/types@8.58.0': {} - '@typescript-eslint/types@8.59.1': {} + '@typescript-eslint/types@8.60.1': {} - '@typescript-eslint/types@8.59.2': {} + '@typescript-eslint/types@8.61.0': {} - '@typescript-eslint/typescript-estree@8.56.0(typescript@5.9.3)': - dependencies: - '@typescript-eslint/project-service': 8.56.0(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.56.0(typescript@5.9.3) - '@typescript-eslint/types': 8.56.0 - '@typescript-eslint/visitor-keys': 8.56.0 - debug: 4.4.3 - minimatch: 10.2.4 - semver: 7.7.3 - tinyglobby: 0.2.15 - ts-api-utils: 2.5.0(typescript@5.9.3) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color + '@typescript-eslint/types@8.61.1': {} '@typescript-eslint/typescript-estree@8.58.0(typescript@5.9.3)': dependencies: @@ -9746,12 +9383,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/typescript-estree@8.59.2(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.60.1(typescript@5.9.3)': dependencies: - '@typescript-eslint/project-service': 8.59.2(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.59.2(typescript@5.9.3) - '@typescript-eslint/types': 8.59.2 - '@typescript-eslint/visitor-keys': 8.59.2 + '@typescript-eslint/project-service': 8.60.1(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.60.1(typescript@5.9.3) + '@typescript-eslint/types': 8.60.1 + '@typescript-eslint/visitor-keys': 8.60.1 debug: 4.4.3 minimatch: 10.2.4 semver: 7.7.3 @@ -9761,100 +9398,104 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.56.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.61.1(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.61.1(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.61.1(typescript@5.9.3) + '@typescript-eslint/types': 8.61.1 + '@typescript-eslint/visitor-keys': 8.61.1 + debug: 4.4.3 + minimatch: 10.2.4 + semver: 7.7.3 + tinyglobby: 0.2.15 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.60.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.6.1)) - '@typescript-eslint/scope-manager': 8.56.0 - '@typescript-eslint/types': 8.56.0 - '@typescript-eslint/typescript-estree': 8.56.0(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.60.1 + '@typescript-eslint/types': 8.60.1 + '@typescript-eslint/typescript-estree': 8.60.1(typescript@5.9.3) eslint: 9.39.4(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/utils@8.61.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.6.1)) - '@typescript-eslint/scope-manager': 8.58.0 - '@typescript-eslint/types': 8.58.0 - '@typescript-eslint/typescript-estree': 8.58.0(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.61.1 + '@typescript-eslint/types': 8.61.1 + '@typescript-eslint/typescript-estree': 8.61.1(typescript@5.9.3) eslint: 9.39.4(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.59.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': - dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.6.1)) - '@typescript-eslint/scope-manager': 8.59.2 - '@typescript-eslint/types': 8.59.2 - '@typescript-eslint/typescript-estree': 8.59.2(typescript@5.9.3) - eslint: 9.39.4(jiti@2.6.1) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/visitor-keys@8.56.0': - dependencies: - '@typescript-eslint/types': 8.56.0 - eslint-visitor-keys: 5.0.0 - '@typescript-eslint/visitor-keys@8.58.0': dependencies: '@typescript-eslint/types': 8.58.0 eslint-visitor-keys: 5.0.0 - '@typescript-eslint/visitor-keys@8.59.2': + '@typescript-eslint/visitor-keys@8.60.1': dependencies: - '@typescript-eslint/types': 8.59.2 + '@typescript-eslint/types': 8.60.1 + eslint-visitor-keys: 5.0.0 + + '@typescript-eslint/visitor-keys@8.61.1': + dependencies: + '@typescript-eslint/types': 8.61.1 eslint-visitor-keys: 5.0.0 '@ungap/structured-clone@1.3.0': {} - '@vitejs/plugin-vue@6.0.6(vite@7.3.3(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3))(vue@3.5.27(typescript@5.9.3))': + '@vitejs/plugin-vue@6.0.7(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3))(vue@3.5.27(typescript@5.9.3))': dependencies: - '@rolldown/pluginutils': 1.0.0-rc.13 - vite: 7.3.3(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3) + '@rolldown/pluginutils': 1.0.1 + vite: 7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3) vue: 3.5.27(typescript@5.9.3) - '@vitest/expect@4.1.5': + '@vitest/expect@4.1.9': dependencies: '@standard-schema/spec': 1.1.0 '@types/chai': 5.2.2 - '@vitest/spy': 4.1.5 - '@vitest/utils': 4.1.5 + '@vitest/spy': 4.1.9 + '@vitest/utils': 4.1.9 chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.5(vite@7.3.3(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3))': + '@vitest/mocker@4.1.9(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3))': dependencies: - '@vitest/spy': 4.1.5 + '@vitest/spy': 4.1.9 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.3.3(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3) + vite: 7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3) - '@vitest/pretty-format@4.1.5': + '@vitest/pretty-format@4.1.9': dependencies: tinyrainbow: 3.1.0 - '@vitest/runner@4.1.5': + '@vitest/runner@4.1.9': dependencies: - '@vitest/utils': 4.1.5 + '@vitest/utils': 4.1.9 pathe: 2.0.3 - '@vitest/snapshot@4.1.5': + '@vitest/snapshot@4.1.9': dependencies: - '@vitest/pretty-format': 4.1.5 - '@vitest/utils': 4.1.5 + '@vitest/pretty-format': 4.1.9 + '@vitest/utils': 4.1.9 magic-string: 0.30.21 pathe: 2.0.3 - '@vitest/spy@4.1.5': {} + '@vitest/spy@4.1.9': {} - '@vitest/utils@4.1.5': + '@vitest/utils@4.1.9': dependencies: - '@vitest/pretty-format': 4.1.5 + '@vitest/pretty-format': 4.1.9 convert-source-map: 2.0.0 tinyrainbow: 3.1.0 @@ -9872,30 +9513,30 @@ snapshots: '@vue/babel-helper-vue-transform-on@1.2.5': {} - '@vue/babel-plugin-jsx@1.2.5(@babel/core@7.26.0)': + '@vue/babel-plugin-jsx@1.2.5(@babel/core@7.29.7)': dependencies: - '@babel/helper-module-imports': 7.25.9 - '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.0) - '@babel/template': 7.26.9 - '@babel/traverse': 7.25.9 - '@babel/types': 7.28.5 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.29.7) + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 '@vue/babel-helper-vue-transform-on': 1.2.5 - '@vue/babel-plugin-resolve-type': 1.2.5(@babel/core@7.26.0) + '@vue/babel-plugin-resolve-type': 1.2.5(@babel/core@7.29.7) html-tags: 3.3.1 svg-tags: 1.0.0 optionalDependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.29.7 transitivePeerDependencies: - supports-color - '@vue/babel-plugin-resolve-type@1.2.5(@babel/core@7.26.0)': + '@vue/babel-plugin-resolve-type@1.2.5(@babel/core@7.29.7)': dependencies: - '@babel/code-frame': 7.26.2 - '@babel/core': 7.26.0 - '@babel/helper-module-imports': 7.25.9 - '@babel/helper-plugin-utils': 7.25.9 - '@babel/parser': 7.28.5 + '@babel/code-frame': 7.29.7 + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/parser': 7.29.7 '@vue/compiler-sfc': 3.5.27 transitivePeerDependencies: - supports-color @@ -9936,10 +9577,10 @@ snapshots: dependencies: '@vue/devtools-kit': 7.7.7 - '@vue/devtools-core@8.1.2(vue@3.5.27(typescript@5.9.3))': + '@vue/devtools-core@8.1.3(vue@3.5.27(typescript@5.9.3))': dependencies: - '@vue/devtools-kit': 8.1.2 - '@vue/devtools-shared': 8.1.2 + '@vue/devtools-kit': 8.1.3 + '@vue/devtools-shared': 8.1.3 vue: 3.5.27(typescript@5.9.3) '@vue/devtools-kit@7.7.7': @@ -9952,9 +9593,9 @@ snapshots: speakingurl: 14.0.1 superjson: 2.2.2 - '@vue/devtools-kit@8.1.2': + '@vue/devtools-kit@8.1.3': dependencies: - '@vue/devtools-shared': 8.1.2 + '@vue/devtools-shared': 8.1.3 birpc: 2.6.1 hookable: 5.5.3 perfect-debounce: 2.0.0 @@ -9963,27 +9604,27 @@ snapshots: dependencies: rfdc: 1.4.1 - '@vue/devtools-shared@8.1.2': {} + '@vue/devtools-shared@8.1.3': {} - '@vue/eslint-config-typescript@14.7.0(eslint-plugin-vue@10.9.1(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(vue-eslint-parser@10.4.0(eslint@9.39.4(jiti@2.6.1))))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': + '@vue/eslint-config-typescript@14.8.0(eslint-plugin-vue@10.9.2(@typescript-eslint/parser@8.61.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(vue-eslint-parser@10.4.0(eslint@9.39.4(jiti@2.6.1))))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@typescript-eslint/utils': 8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/utils': 8.60.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) eslint: 9.39.4(jiti@2.6.1) - eslint-plugin-vue: 10.9.1(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(vue-eslint-parser@10.4.0(eslint@9.39.4(jiti@2.6.1))) + eslint-plugin-vue: 10.9.2(@typescript-eslint/parser@8.61.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(vue-eslint-parser@10.4.0(eslint@9.39.4(jiti@2.6.1))) fast-glob: 3.3.3 - typescript-eslint: 8.56.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + typescript-eslint: 8.60.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) vue-eslint-parser: 10.4.0(eslint@9.39.4(jiti@2.6.1)) optionalDependencies: typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@vue/language-core@3.2.8': + '@vue/language-core@3.3.5': dependencies: '@volar/language-core': 2.4.28 '@vue/compiler-dom': 3.5.27 '@vue/shared': 3.5.27 - alien-signals: 3.1.2 + alien-signals: 3.2.1 muggle-string: 0.4.1 path-browserify: 1.0.1 picomatch: 4.0.4 @@ -10012,7 +9653,7 @@ snapshots: '@vue/shared@3.5.27': {} - '@vue/test-utils@2.4.10(@vue/compiler-dom@3.5.27)(@vue/server-renderer@3.5.27(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3))': + '@vue/test-utils@2.4.11(@vue/compiler-dom@3.5.27)(@vue/server-renderer@3.5.27(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3))': dependencies: '@vue/compiler-dom': 3.5.27 js-beautify: 1.15.1 @@ -10075,11 +9716,11 @@ snapshots: ajv@8.18.0: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.0.1 + fast-uri: 3.1.2 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 - alien-signals@3.1.2: {} + alien-signals@3.2.1: {} ansi-align@3.0.1: dependencies: @@ -10151,7 +9792,7 @@ snapshots: autoprefixer@10.5.0(postcss@8.5.14): dependencies: browserslist: 4.28.2 - caniuse-lite: 1.0.30001792 + caniuse-lite: 1.0.30001799 fraction.js: 5.3.4 picocolors: 1.1.1 postcss: 8.5.14 @@ -10161,37 +9802,37 @@ snapshots: dependencies: possible-typed-array-names: 1.0.0 - axios@1.15.2: + axios@1.16.0: dependencies: follow-redirects: 1.16.0 - form-data: 4.0.5 + form-data: 4.0.6 proxy-from-env: 2.1.0 transitivePeerDependencies: - debug b4a@1.6.7: {} - babel-plugin-polyfill-corejs2@0.4.11(@babel/core@7.26.0): + babel-plugin-polyfill-corejs2@0.4.11(@babel/core@7.29.7): dependencies: - '@babel/compat-data': 7.26.0 - '@babel/core': 7.26.0 - '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.26.0) + '@babel/compat-data': 7.29.7 + '@babel/core': 7.29.7 + '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.29.7) semver: 6.3.1 transitivePeerDependencies: - supports-color - babel-plugin-polyfill-corejs3@0.10.6(@babel/core@7.26.0): + babel-plugin-polyfill-corejs3@0.10.6(@babel/core@7.29.7): dependencies: - '@babel/core': 7.26.0 - '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.26.0) + '@babel/core': 7.29.7 + '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.29.7) core-js-compat: 3.38.1 transitivePeerDependencies: - supports-color - babel-plugin-polyfill-regenerator@0.6.2(@babel/core@7.26.0): + babel-plugin-polyfill-regenerator@0.6.2(@babel/core@7.29.7): dependencies: - '@babel/core': 7.26.0 - '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.26.0) + '@babel/core': 7.29.7 + '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.29.7) transitivePeerDependencies: - supports-color @@ -10259,7 +9900,7 @@ snapshots: widest-line: 5.0.0 wrap-ansi: 9.0.2 - brace-expansion@5.0.5: + brace-expansion@5.0.6: dependencies: balanced-match: 4.0.3 @@ -10270,7 +9911,7 @@ snapshots: browserslist@4.28.2: dependencies: baseline-browser-mapping: 2.10.12 - caniuse-lite: 1.0.30001792 + caniuse-lite: 1.0.30001799 electron-to-chromium: 1.5.329 node-releases: 2.0.36 update-browserslist-db: 1.2.3(browserslist@4.28.2) @@ -10279,6 +9920,10 @@ snapshots: buffer-from@1.1.2: {} + buffer-image-size@0.6.4: + dependencies: + '@types/node': 24.13.2 + buffer@5.7.1: dependencies: base64-js: 1.5.1 @@ -10292,13 +9937,13 @@ snapshots: cac@6.7.14: {} - cacheable@2.3.2: + cacheable@2.3.5: dependencies: - '@cacheable/memory': 2.0.7 - '@cacheable/utils': 2.3.4 + '@cacheable/memory': 2.0.9 + '@cacheable/utils': 2.4.1 hookified: 1.15.1 keyv: 5.6.0 - qified: 0.6.0 + qified: 0.10.1 call-bind-apply-helpers@1.0.2: dependencies: @@ -10329,7 +9974,7 @@ snapshots: camelcase@8.0.0: {} - caniuse-lite@1.0.30001792: {} + caniuse-lite@1.0.30001799: {} capture-website@4.2.0(typescript@5.9.3): dependencies: @@ -10379,6 +10024,11 @@ snapshots: dependencies: readdirp: 4.1.2 + chokidar@5.0.0: + dependencies: + readdirp: 5.0.0 + optional: true + chroma-js@1.4.1: {} chromium-bidi@0.11.0(devtools-protocol@0.0.1367902): @@ -10466,7 +10116,7 @@ snapshots: dependencies: env-paths: 2.2.1 import-fresh: 3.3.0 - js-yaml: 4.1.1 + js-yaml: 4.2.0 parse-json: 5.2.0 optionalDependencies: typescript: 5.9.3 @@ -10526,7 +10176,7 @@ snapshots: css-what@6.1.0: {} - cssdb@8.8.0: {} + cssdb@8.9.0: {} cssesc@3.0.0: {} @@ -10659,7 +10309,7 @@ snapshots: dependencies: domelementtype: 2.3.0 - dompurify@3.4.0: + dompurify@3.4.11: optionalDependencies: '@types/trusted-types': 2.0.7 @@ -10720,7 +10370,7 @@ snapshots: dependencies: once: 1.4.0 - enhanced-resolve@5.21.3: + enhanced-resolve@5.21.6: dependencies: graceful-fs: 4.2.11 tapable: 2.3.3 @@ -10751,7 +10401,7 @@ snapshots: data-view-byte-offset: 1.0.1 es-define-property: 1.0.1 es-errors: 1.3.0 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 es-set-tostringtag: 2.1.0 es-to-primitive: 1.3.0 function.prototype.name: 1.1.8 @@ -10763,7 +10413,7 @@ snapshots: has-property-descriptors: 1.0.2 has-proto: 1.2.0 has-symbols: 1.1.0 - hasown: 2.0.3 + hasown: 2.0.4 internal-slot: 1.1.0 is-array-buffer: 3.0.5 is-callable: 1.2.7 @@ -10808,12 +10458,16 @@ snapshots: dependencies: es-errors: 1.3.0 + es-object-atoms@1.1.2: + dependencies: + es-errors: 1.3.0 + es-set-tostringtag@2.1.0: dependencies: es-errors: 1.3.0 get-intrinsic: 1.3.0 has-tostringtag: 1.0.2 - hasown: 2.0.3 + hasown: 2.0.4 es-to-primitive@1.3.0: dependencies: @@ -10821,92 +10475,34 @@ snapshots: is-date-object: 1.0.5 is-symbol: 1.0.4 - esbuild@0.25.12: + esbuild@0.28.1: optionalDependencies: - '@esbuild/aix-ppc64': 0.25.12 - '@esbuild/android-arm': 0.25.12 - '@esbuild/android-arm64': 0.25.12 - '@esbuild/android-x64': 0.25.12 - '@esbuild/darwin-arm64': 0.25.12 - '@esbuild/darwin-x64': 0.25.12 - '@esbuild/freebsd-arm64': 0.25.12 - '@esbuild/freebsd-x64': 0.25.12 - '@esbuild/linux-arm': 0.25.12 - '@esbuild/linux-arm64': 0.25.12 - '@esbuild/linux-ia32': 0.25.12 - '@esbuild/linux-loong64': 0.25.12 - '@esbuild/linux-mips64el': 0.25.12 - '@esbuild/linux-ppc64': 0.25.12 - '@esbuild/linux-riscv64': 0.25.12 - '@esbuild/linux-s390x': 0.25.12 - '@esbuild/linux-x64': 0.25.12 - '@esbuild/netbsd-arm64': 0.25.12 - '@esbuild/netbsd-x64': 0.25.12 - '@esbuild/openbsd-arm64': 0.25.12 - '@esbuild/openbsd-x64': 0.25.12 - '@esbuild/openharmony-arm64': 0.25.12 - '@esbuild/sunos-x64': 0.25.12 - '@esbuild/win32-arm64': 0.25.12 - '@esbuild/win32-ia32': 0.25.12 - '@esbuild/win32-x64': 0.25.12 - - esbuild@0.27.5: - optionalDependencies: - '@esbuild/aix-ppc64': 0.27.5 - '@esbuild/android-arm': 0.27.5 - '@esbuild/android-arm64': 0.27.5 - '@esbuild/android-x64': 0.27.5 - '@esbuild/darwin-arm64': 0.27.5 - '@esbuild/darwin-x64': 0.27.5 - '@esbuild/freebsd-arm64': 0.27.5 - '@esbuild/freebsd-x64': 0.27.5 - '@esbuild/linux-arm': 0.27.5 - '@esbuild/linux-arm64': 0.27.5 - '@esbuild/linux-ia32': 0.27.5 - '@esbuild/linux-loong64': 0.27.5 - '@esbuild/linux-mips64el': 0.27.5 - '@esbuild/linux-ppc64': 0.27.5 - '@esbuild/linux-riscv64': 0.27.5 - '@esbuild/linux-s390x': 0.27.5 - '@esbuild/linux-x64': 0.27.5 - '@esbuild/netbsd-arm64': 0.27.5 - '@esbuild/netbsd-x64': 0.27.5 - '@esbuild/openbsd-arm64': 0.27.5 - '@esbuild/openbsd-x64': 0.27.5 - '@esbuild/openharmony-arm64': 0.27.5 - '@esbuild/sunos-x64': 0.27.5 - '@esbuild/win32-arm64': 0.27.5 - '@esbuild/win32-ia32': 0.27.5 - '@esbuild/win32-x64': 0.27.5 - - esbuild@0.28.0: - optionalDependencies: - '@esbuild/aix-ppc64': 0.28.0 - '@esbuild/android-arm': 0.28.0 - '@esbuild/android-arm64': 0.28.0 - '@esbuild/android-x64': 0.28.0 - '@esbuild/darwin-arm64': 0.28.0 - '@esbuild/darwin-x64': 0.28.0 - '@esbuild/freebsd-arm64': 0.28.0 - '@esbuild/freebsd-x64': 0.28.0 - '@esbuild/linux-arm': 0.28.0 - '@esbuild/linux-arm64': 0.28.0 - '@esbuild/linux-ia32': 0.28.0 - '@esbuild/linux-loong64': 0.28.0 - '@esbuild/linux-mips64el': 0.28.0 - '@esbuild/linux-ppc64': 0.28.0 - '@esbuild/linux-riscv64': 0.28.0 - '@esbuild/linux-s390x': 0.28.0 - '@esbuild/linux-x64': 0.28.0 - '@esbuild/netbsd-arm64': 0.28.0 - '@esbuild/netbsd-x64': 0.28.0 - '@esbuild/openbsd-arm64': 0.28.0 - '@esbuild/openbsd-x64': 0.28.0 - '@esbuild/openharmony-arm64': 0.28.0 - '@esbuild/sunos-x64': 0.28.0 - '@esbuild/win32-arm64': 0.28.0 - '@esbuild/win32-ia32': 0.28.0 - '@esbuild/win32-x64': 0.28.0 + '@esbuild/aix-ppc64': 0.28.1 + '@esbuild/android-arm': 0.28.1 + '@esbuild/android-arm64': 0.28.1 + '@esbuild/android-x64': 0.28.1 + '@esbuild/darwin-arm64': 0.28.1 + '@esbuild/darwin-x64': 0.28.1 + '@esbuild/freebsd-arm64': 0.28.1 + '@esbuild/freebsd-x64': 0.28.1 + '@esbuild/linux-arm': 0.28.1 + '@esbuild/linux-arm64': 0.28.1 + '@esbuild/linux-ia32': 0.28.1 + '@esbuild/linux-loong64': 0.28.1 + '@esbuild/linux-mips64el': 0.28.1 + '@esbuild/linux-ppc64': 0.28.1 + '@esbuild/linux-riscv64': 0.28.1 + '@esbuild/linux-s390x': 0.28.1 + '@esbuild/linux-x64': 0.28.1 + '@esbuild/netbsd-arm64': 0.28.1 + '@esbuild/netbsd-x64': 0.28.1 + '@esbuild/openbsd-arm64': 0.28.1 + '@esbuild/openbsd-x64': 0.28.1 + '@esbuild/openharmony-arm64': 0.28.1 + '@esbuild/sunos-x64': 0.28.1 + '@esbuild/win32-arm64': 0.28.1 + '@esbuild/win32-ia32': 0.28.1 + '@esbuild/win32-x64': 0.28.1 escalade@3.2.0: {} @@ -10933,7 +10529,7 @@ snapshots: module-replacements: 2.11.0 semver: 7.7.3 - eslint-plugin-vue@10.9.1(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(vue-eslint-parser@10.4.0(eslint@9.39.4(jiti@2.6.1))): + eslint-plugin-vue@10.9.2(@typescript-eslint/parser@8.61.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(vue-eslint-parser@10.4.0(eslint@9.39.4(jiti@2.6.1))): dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.6.1)) eslint: 9.39.4(jiti@2.6.1) @@ -10944,7 +10540,7 @@ snapshots: vue-eslint-parser: 10.4.0(eslint@9.39.4(jiti@2.6.1)) xml-name-validator: 4.0.0 optionalDependencies: - '@typescript-eslint/parser': 8.59.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.61.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) eslint-scope@8.4.0: dependencies: @@ -11026,7 +10622,7 @@ snapshots: estree-walker@3.0.3: dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 esutils@2.0.3: {} @@ -11042,7 +10638,7 @@ snapshots: dependencies: chardet: 0.7.0 iconv-lite: 0.4.24 - tmp: 0.0.33 + tmp: 0.2.7 extract-zip@2.0.1: dependencies: @@ -11070,7 +10666,7 @@ snapshots: fast-levenshtein@2.0.6: {} - fast-uri@3.0.1: {} + fast-uri@3.1.2: {} fastest-levenshtein@1.0.16: {} @@ -11090,9 +10686,9 @@ snapshots: dependencies: escape-string-regexp: 1.0.5 - file-entry-cache@11.1.2: + file-entry-cache@11.1.3: dependencies: - flat-cache: 6.1.20 + flat-cache: 6.1.22 file-entry-cache@8.0.0: dependencies: @@ -11135,9 +10731,9 @@ snapshots: flatted: 3.4.2 keyv: 4.5.4 - flat-cache@6.1.20: + flat-cache@6.1.22: dependencies: - cacheable: 2.3.2 + cacheable: 2.3.5 flatted: 3.4.2 hookified: 1.15.1 @@ -11166,12 +10762,12 @@ snapshots: cross-spawn: 7.0.6 signal-exit: 4.1.0 - form-data@4.0.5: + form-data@4.0.6: dependencies: asynckit: 0.4.0 combined-stream: 1.0.8 es-set-tostringtag: 2.1.0 - hasown: 2.0.3 + hasown: 2.0.4 mime-types: 2.1.35 fraction.js@5.3.4: {} @@ -11205,7 +10801,7 @@ snapshots: call-bound: 1.0.4 define-properties: 1.2.1 functions-have-names: 1.2.3 - hasown: 2.0.3 + hasown: 2.0.4 is-callable: 1.2.7 functions-have-names@1.2.3: {} @@ -11225,12 +10821,12 @@ snapshots: call-bind-apply-helpers: 1.0.2 es-define-property: 1.0.1 es-errors: 1.3.0 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 function-bind: 1.1.2 get-proto: 1.0.1 gopd: 1.2.0 has-symbols: 1.1.0 - hasown: 2.0.3 + hasown: 2.0.4 math-intrinsics: 1.1.0 get-own-enumerable-property-symbols@3.0.2: {} @@ -11238,7 +10834,7 @@ snapshots: get-proto@1.0.1: dependencies: dunder-proto: 1.0.1 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 get-stream@5.2.0: dependencies: @@ -11347,14 +10943,15 @@ snapshots: section-matter: 1.0.0 strip-bom-string: 1.0.0 - happy-dom@20.9.0: + happy-dom@20.10.6: dependencies: - '@types/node': 24.12.3 + '@types/node': 24.13.2 '@types/whatwg-mimetype': 3.0.2 '@types/ws': 8.18.1 + buffer-image-size: 0.6.4 entities: 7.0.1 whatwg-mimetype: 3.0.0 - ws: 8.20.0 + ws: 8.21.0 transitivePeerDependencies: - bufferutil - utf-8-validate @@ -11381,11 +10978,11 @@ snapshots: dependencies: has-symbols: 1.1.0 - hashery@1.4.0: + hashery@1.5.1: dependencies: hookified: 1.15.1 - hasown@2.0.3: + hasown@2.0.4: dependencies: function-bind: 1.1.2 @@ -11409,12 +11006,12 @@ snapshots: highlight.js@11.11.1: {} - histoire@1.0.0-beta.1(@types/node@24.12.3)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(vite@7.3.3(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3))(yaml@2.8.3): + histoire@1.0.0-beta.1(@types/node@24.13.2)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3))(yaml@2.8.3): dependencies: '@akryum/tinypool': 0.3.1 - '@histoire/app': 1.0.0-beta.1(vite@7.3.3(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3)) - '@histoire/controls': 1.0.0-beta.1(vite@7.3.3(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3)) - '@histoire/shared': 1.0.0-beta.1(vite@7.3.3(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3)) + '@histoire/app': 1.0.0-beta.1(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3)) + '@histoire/controls': 1.0.0-beta.1(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3)) + '@histoire/shared': 1.0.0-beta.1(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3)) '@histoire/vendors': 1.0.0-beta.1 '@types/markdown-it': 14.1.2 birpc: 0.2.19 @@ -11428,9 +11025,9 @@ snapshots: gray-matter: 4.0.3 jiti: 2.6.1 jsdom: 27.4.0 - markdown-it: 14.1.1 - markdown-it-anchor: 9.2.0(@types/markdown-it@14.1.2)(markdown-it@14.1.1) - markdown-it-attrs: 4.3.1(markdown-it@14.1.1) + markdown-it: 14.2.0 + markdown-it-anchor: 9.2.0(@types/markdown-it@14.1.2)(markdown-it@14.2.0) + markdown-it-attrs: 4.3.1(markdown-it@14.2.0) markdown-it-emoji: 3.0.0 micromatch: 4.0.8 mrmime: 2.0.0 @@ -11439,8 +11036,8 @@ snapshots: sade: 1.8.1 shiki: 3.2.1 sirv: 3.0.2 - vite: 7.3.3(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3) - vite-node: 3.2.4(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3) + vite: 7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3) + vite-node: 3.2.4(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3) transitivePeerDependencies: - '@exodus/crypto' - '@types/node' @@ -11462,6 +11059,8 @@ snapshots: hookified@1.15.1: {} + hookified@2.2.0: {} + hosted-git-info@2.8.9: {} html-encoding-sniffer@6.0.0: @@ -11554,13 +11153,10 @@ snapshots: internal-slot@1.1.0: dependencies: es-errors: 1.3.0 - hasown: 2.0.3 + hasown: 2.0.4 side-channel: 1.1.0 - ip-address@9.0.5: - dependencies: - jsbn: 1.1.0 - sprintf-js: 1.1.3 + ip-address@10.2.0: {} is-array-buffer@3.0.5: dependencies: @@ -11595,7 +11191,7 @@ snapshots: is-core-module@2.15.1: dependencies: - hasown: 2.0.3 + hasown: 2.0.4 is-data-view@1.0.2: dependencies: @@ -11681,7 +11277,7 @@ snapshots: call-bound: 1.0.4 gopd: 1.2.0 has-tostringtag: 1.0.2 - hasown: 2.0.3 + hasown: 2.0.4 is-regexp@1.0.0: {} @@ -11760,7 +11356,9 @@ snapshots: jiti@2.6.1: {} - joi@18.1.2: + jiti@2.7.0: {} + + joi@18.2.1: dependencies: '@hapi/address': 5.1.1 '@hapi/formula': 3.0.2 @@ -11775,10 +11373,10 @@ snapshots: config-chain: 1.1.13 editorconfig: 1.0.4 glob: 10.5.0 - js-cookie: 3.0.5 + js-cookie: 3.0.7 nopt: 7.2.1 - js-cookie@3.0.5: {} + js-cookie@3.0.7: {} js-tokens@4.0.0: {} @@ -11789,12 +11387,10 @@ snapshots: argparse: 1.0.10 esprima: 4.0.1 - js-yaml@4.1.1: + js-yaml@4.2.0: dependencies: argparse: 2.0.1 - jsbn@1.1.0: {} - jsdom@27.4.0: dependencies: '@acemir/cssom': 0.9.30 @@ -11815,7 +11411,7 @@ snapshots: webidl-conversions: 8.0.1 whatwg-mimetype: 4.0.0 whatwg-url: 15.1.0 - ws: 8.20.0 + ws: 8.21.0 xml-name-validator: 5.0.0 transitivePeerDependencies: - '@exodus/crypto' @@ -11825,6 +11421,8 @@ snapshots: jsesc@3.0.2: {} + jsesc@3.1.0: {} + json-buffer@3.0.1: {} json-parse-even-better-errors@2.3.1: {} @@ -11876,10 +11474,10 @@ snapshots: dependencies: package-json: 10.0.1 - launch-editor@2.10.0: + launch-editor@2.14.1: dependencies: picocolors: 1.1.1 - shell-quote: 1.8.1 + shell-quote: 1.8.4 leven@3.1.0: {} @@ -11939,7 +11537,7 @@ snapshots: lines-and-columns@1.2.4: {} - linkify-it@5.0.0: + linkify-it@5.0.1: dependencies: uc.micro: 2.1.0 @@ -11996,22 +11594,22 @@ snapshots: map-obj@4.3.0: {} - markdown-it-anchor@9.2.0(@types/markdown-it@14.1.2)(markdown-it@14.1.1): + markdown-it-anchor@9.2.0(@types/markdown-it@14.1.2)(markdown-it@14.2.0): dependencies: '@types/markdown-it': 14.1.2 - markdown-it: 14.1.1 + markdown-it: 14.2.0 - markdown-it-attrs@4.3.1(markdown-it@14.1.1): + markdown-it-attrs@4.3.1(markdown-it@14.2.0): dependencies: - markdown-it: 14.1.1 + markdown-it: 14.2.0 markdown-it-emoji@3.0.0: {} - markdown-it@14.1.1: + markdown-it@14.2.0: dependencies: argparse: 2.0.1 entities: 4.5.0 - linkify-it: 5.0.0 + linkify-it: 5.0.1 mdurl: 2.0.0 punycode.js: 2.3.1 uc.micro: 2.1.0 @@ -12094,7 +11692,7 @@ snapshots: minimatch@10.2.4: dependencies: - brace-expansion: 5.0.5 + brace-expansion: 5.0.6 minimist-options@4.1.0: dependencies: @@ -12167,7 +11765,7 @@ snapshots: call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 has-symbols: 1.1.0 object-keys: 1.1.1 @@ -12232,8 +11830,6 @@ snapshots: orderedmap@2.1.1: {} - os-tmpdir@1.0.2: {} - otplib@12.0.1: dependencies: '@otplib/core': 12.0.1 @@ -12297,7 +11893,7 @@ snapshots: parse-json@5.2.0: dependencies: - '@babel/code-frame': 7.26.2 + '@babel/code-frame': 7.29.0 error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 @@ -12338,8 +11934,6 @@ snapshots: perfect-debounce@2.0.0: {} - picocolors@0.2.1: {} - picocolors@1.1.1: {} picomatch@2.3.2: {} @@ -12373,12 +11967,12 @@ snapshots: postcss: 8.5.14 postcss-value-parser: 4.2.0 - postcss-color-functional-notation@8.0.3(postcss@8.5.14): + postcss-color-functional-notation@8.0.5(postcss@8.5.14): dependencies: - '@csstools/css-color-parser': 4.1.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-color-parser': 4.1.7(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 - '@csstools/postcss-progressive-custom-properties': 5.0.0(postcss@8.5.14) + '@csstools/postcss-progressive-custom-properties': 5.1.0(postcss@8.5.14) '@csstools/utilities': 3.0.0(postcss@8.5.14) postcss: 8.5.14 @@ -12424,9 +12018,9 @@ snapshots: postcss: 8.5.14 postcss-selector-parser: 7.1.1 - postcss-double-position-gradients@7.0.0(postcss@8.5.14): + postcss-double-position-gradients@7.0.1(postcss@8.5.14): dependencies: - '@csstools/postcss-progressive-custom-properties': 5.0.0(postcss@8.5.14) + '@csstools/postcss-progressive-custom-properties': 5.1.0(postcss@8.5.14) '@csstools/utilities': 3.0.0(postcss@8.5.14) postcss: 8.5.14 postcss-value-parser: 4.2.0 @@ -12435,7 +12029,7 @@ snapshots: dependencies: chroma-js: 1.4.1 easing-coordinates: 2.0.2 - postcss: 7.0.39 + postcss: 8.5.14 postcss-value-parser: 3.3.1 postcss-focus-visible@11.0.0(postcss@8.5.14): @@ -12469,12 +12063,12 @@ snapshots: postcss: 8.5.14 postcss-value-parser: 4.2.0 - postcss-lab-function@8.0.3(postcss@8.5.14): + postcss-lab-function@8.0.5(postcss@8.5.14): dependencies: - '@csstools/css-color-parser': 4.1.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-color-parser': 4.1.7(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 - '@csstools/postcss-progressive-custom-properties': 5.0.0(postcss@8.5.14) + '@csstools/postcss-progressive-custom-properties': 5.1.0(postcss@8.5.14) '@csstools/utilities': 3.0.0(postcss@8.5.14) postcss: 8.5.14 @@ -12510,73 +12104,75 @@ snapshots: postcss: 8.5.14 postcss-value-parser: 4.2.0 - postcss-preset-env@11.2.1(postcss@8.5.14): + postcss-preset-env@11.3.1(postcss@8.5.14): dependencies: - '@csstools/postcss-alpha-function': 2.0.4(postcss@8.5.14) + '@csstools/postcss-alpha-function': 2.0.6(postcss@8.5.14) '@csstools/postcss-cascade-layers': 6.0.0(postcss@8.5.14) - '@csstools/postcss-color-function': 5.0.3(postcss@8.5.14) - '@csstools/postcss-color-function-display-p3-linear': 2.0.3(postcss@8.5.14) - '@csstools/postcss-color-mix-function': 4.0.3(postcss@8.5.14) - '@csstools/postcss-color-mix-variadic-function-arguments': 2.0.3(postcss@8.5.14) - '@csstools/postcss-content-alt-text': 3.0.0(postcss@8.5.14) - '@csstools/postcss-contrast-color-function': 3.0.3(postcss@8.5.14) - '@csstools/postcss-exponential-functions': 3.0.2(postcss@8.5.14) + '@csstools/postcss-color-function': 5.0.5(postcss@8.5.14) + '@csstools/postcss-color-function-display-p3-linear': 2.0.5(postcss@8.5.14) + '@csstools/postcss-color-mix-function': 4.0.5(postcss@8.5.14) + '@csstools/postcss-color-mix-variadic-function-arguments': 2.0.5(postcss@8.5.14) + '@csstools/postcss-container-rule-prelude-list': 1.0.1(postcss@8.5.14) + '@csstools/postcss-content-alt-text': 3.0.1(postcss@8.5.14) + '@csstools/postcss-contrast-color-function': 3.0.5(postcss@8.5.14) + '@csstools/postcss-exponential-functions': 3.0.3(postcss@8.5.14) '@csstools/postcss-font-format-keywords': 5.0.0(postcss@8.5.14) '@csstools/postcss-font-width-property': 1.0.0(postcss@8.5.14) - '@csstools/postcss-gamut-mapping': 3.0.3(postcss@8.5.14) - '@csstools/postcss-gradients-interpolation-method': 6.0.3(postcss@8.5.14) - '@csstools/postcss-hwb-function': 5.0.3(postcss@8.5.14) - '@csstools/postcss-ic-unit': 5.0.0(postcss@8.5.14) + '@csstools/postcss-gamut-mapping': 3.0.5(postcss@8.5.14) + '@csstools/postcss-gradients-interpolation-method': 6.0.5(postcss@8.5.14) + '@csstools/postcss-hwb-function': 5.0.5(postcss@8.5.14) + '@csstools/postcss-ic-unit': 5.0.1(postcss@8.5.14) + '@csstools/postcss-image-function': 1.0.0(postcss@8.5.14) '@csstools/postcss-initial': 3.0.0(postcss@8.5.14) '@csstools/postcss-is-pseudo-class': 6.0.0(postcss@8.5.14) - '@csstools/postcss-light-dark-function': 3.0.0(postcss@8.5.14) + '@csstools/postcss-light-dark-function': 3.0.1(postcss@8.5.14) '@csstools/postcss-logical-float-and-clear': 4.0.0(postcss@8.5.14) '@csstools/postcss-logical-overflow': 3.0.0(postcss@8.5.14) '@csstools/postcss-logical-overscroll-behavior': 3.0.0(postcss@8.5.14) '@csstools/postcss-logical-resize': 4.0.0(postcss@8.5.14) '@csstools/postcss-logical-viewport-units': 4.0.0(postcss@8.5.14) - '@csstools/postcss-media-minmax': 3.0.2(postcss@8.5.14) + '@csstools/postcss-media-minmax': 3.0.3(postcss@8.5.14) '@csstools/postcss-media-queries-aspect-ratio-number-values': 4.0.0(postcss@8.5.14) '@csstools/postcss-mixins': 1.0.0(postcss@8.5.14) '@csstools/postcss-nested-calc': 5.0.0(postcss@8.5.14) '@csstools/postcss-normalize-display-values': 5.0.1(postcss@8.5.14) - '@csstools/postcss-oklab-function': 5.0.3(postcss@8.5.14) + '@csstools/postcss-oklab-function': 5.0.5(postcss@8.5.14) '@csstools/postcss-position-area-property': 2.0.0(postcss@8.5.14) - '@csstools/postcss-progressive-custom-properties': 5.0.0(postcss@8.5.14) + '@csstools/postcss-progressive-custom-properties': 5.1.0(postcss@8.5.14) '@csstools/postcss-property-rule-prelude-list': 2.0.0(postcss@8.5.14) - '@csstools/postcss-random-function': 3.0.2(postcss@8.5.14) - '@csstools/postcss-relative-color-syntax': 4.0.3(postcss@8.5.14) + '@csstools/postcss-random-function': 3.0.3(postcss@8.5.14) + '@csstools/postcss-relative-color-syntax': 4.0.5(postcss@8.5.14) '@csstools/postcss-scope-pseudo-class': 5.0.0(postcss@8.5.14) - '@csstools/postcss-sign-functions': 2.0.2(postcss@8.5.14) - '@csstools/postcss-stepped-value-functions': 5.0.2(postcss@8.5.14) + '@csstools/postcss-sign-functions': 2.0.3(postcss@8.5.14) + '@csstools/postcss-stepped-value-functions': 5.0.3(postcss@8.5.14) '@csstools/postcss-syntax-descriptor-syntax-production': 2.0.0(postcss@8.5.14) '@csstools/postcss-system-ui-font-family': 2.0.0(postcss@8.5.14) '@csstools/postcss-text-decoration-shorthand': 5.0.3(postcss@8.5.14) - '@csstools/postcss-trigonometric-functions': 5.0.2(postcss@8.5.14) + '@csstools/postcss-trigonometric-functions': 5.0.3(postcss@8.5.14) '@csstools/postcss-unset-value': 5.0.0(postcss@8.5.14) autoprefixer: 10.5.0(postcss@8.5.14) browserslist: 4.28.2 css-blank-pseudo: 8.0.1(postcss@8.5.14) css-has-pseudo: 8.0.0(postcss@8.5.14) css-prefers-color-scheme: 11.0.0(postcss@8.5.14) - cssdb: 8.8.0 + cssdb: 8.9.0 postcss: 8.5.14 postcss-attribute-case-insensitive: 8.0.0(postcss@8.5.14) postcss-clamp: 4.1.0(postcss@8.5.14) - postcss-color-functional-notation: 8.0.3(postcss@8.5.14) + postcss-color-functional-notation: 8.0.5(postcss@8.5.14) postcss-color-hex-alpha: 11.0.0(postcss@8.5.14) postcss-color-rebeccapurple: 11.0.0(postcss@8.5.14) postcss-custom-media: 12.0.1(postcss@8.5.14) postcss-custom-properties: 15.0.1(postcss@8.5.14) postcss-custom-selectors: 9.0.1(postcss@8.5.14) postcss-dir-pseudo-class: 10.0.0(postcss@8.5.14) - postcss-double-position-gradients: 7.0.0(postcss@8.5.14) + postcss-double-position-gradients: 7.0.1(postcss@8.5.14) postcss-focus-visible: 11.0.0(postcss@8.5.14) postcss-focus-within: 10.0.0(postcss@8.5.14) postcss-font-variant: 5.0.0(postcss@8.5.14) postcss-gap-properties: 7.0.0(postcss@8.5.14) postcss-image-set-function: 8.0.0(postcss@8.5.14) - postcss-lab-function: 8.0.3(postcss@8.5.14) + postcss-lab-function: 8.0.5(postcss@8.5.14) postcss-logical: 9.0.0(postcss@8.5.14) postcss-nesting: 14.0.0(postcss@8.5.14) postcss-opacity-percentage: 3.0.0(postcss@8.5.14) @@ -12628,11 +12224,6 @@ snapshots: postcss-value-parser@4.2.0: {} - postcss@7.0.39: - dependencies: - picocolors: 0.2.1 - source-map: 0.6.1 - postcss@8.5.14: dependencies: nanoid: 3.3.11 @@ -12696,7 +12287,7 @@ snapshots: prosemirror-markdown@1.13.1: dependencies: '@types/markdown-it': 14.1.2 - markdown-it: 14.1.1 + markdown-it: 14.2.0 prosemirror-model: 1.25.0 prosemirror-menu@1.2.4: @@ -12791,7 +12382,7 @@ snapshots: debug: 4.4.3 devtools-protocol: 0.0.1367902 typed-query-selector: 2.12.0 - ws: 8.20.0 + ws: 8.21.0 transitivePeerDependencies: - bare-buffer - bufferutil @@ -12813,9 +12404,9 @@ snapshots: - typescript - utf-8-validate - qified@0.6.0: + qified@0.10.1: dependencies: - hookified: 1.15.1 + hookified: 2.2.0 queue-microtask@1.2.3: {} @@ -12853,6 +12444,9 @@ snapshots: readdirp@4.1.2: {} + readdirp@5.0.0: + optional: true + redent@3.0.0: dependencies: indent-string: 4.0.0 @@ -12864,7 +12458,7 @@ snapshots: define-properties: 1.2.1 es-abstract: 1.24.2 es-errors: 1.3.0 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 get-intrinsic: 1.3.0 get-proto: 1.0.1 which-builtin-type: 1.2.1 @@ -12946,44 +12540,44 @@ snapshots: rfdc@1.4.1: {} - rollup-plugin-visualizer@6.0.11(rollup@4.60.3): + rollup-plugin-visualizer@6.0.11(rollup@4.62.2): dependencies: open: 8.4.2 picomatch: 4.0.4 source-map: 0.7.4 yargs: 17.7.2 optionalDependencies: - rollup: 4.60.3 + rollup: 4.62.2 - rollup@4.60.3: + rollup@4.62.2: dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.60.3 - '@rollup/rollup-android-arm64': 4.60.3 - '@rollup/rollup-darwin-arm64': 4.60.3 - '@rollup/rollup-darwin-x64': 4.60.3 - '@rollup/rollup-freebsd-arm64': 4.60.3 - '@rollup/rollup-freebsd-x64': 4.60.3 - '@rollup/rollup-linux-arm-gnueabihf': 4.60.3 - '@rollup/rollup-linux-arm-musleabihf': 4.60.3 - '@rollup/rollup-linux-arm64-gnu': 4.60.3 - '@rollup/rollup-linux-arm64-musl': 4.60.3 - '@rollup/rollup-linux-loong64-gnu': 4.60.3 - '@rollup/rollup-linux-loong64-musl': 4.60.3 - '@rollup/rollup-linux-ppc64-gnu': 4.60.3 - '@rollup/rollup-linux-ppc64-musl': 4.60.3 - '@rollup/rollup-linux-riscv64-gnu': 4.60.3 - '@rollup/rollup-linux-riscv64-musl': 4.60.3 - '@rollup/rollup-linux-s390x-gnu': 4.60.3 - '@rollup/rollup-linux-x64-gnu': 4.60.3 - '@rollup/rollup-linux-x64-musl': 4.60.3 - '@rollup/rollup-openbsd-x64': 4.60.3 - '@rollup/rollup-openharmony-arm64': 4.60.3 - '@rollup/rollup-win32-arm64-msvc': 4.60.3 - '@rollup/rollup-win32-ia32-msvc': 4.60.3 - '@rollup/rollup-win32-x64-gnu': 4.60.3 - '@rollup/rollup-win32-x64-msvc': 4.60.3 + '@rollup/rollup-android-arm-eabi': 4.62.2 + '@rollup/rollup-android-arm64': 4.62.2 + '@rollup/rollup-darwin-arm64': 4.62.2 + '@rollup/rollup-darwin-x64': 4.62.2 + '@rollup/rollup-freebsd-arm64': 4.62.2 + '@rollup/rollup-freebsd-x64': 4.62.2 + '@rollup/rollup-linux-arm-gnueabihf': 4.62.2 + '@rollup/rollup-linux-arm-musleabihf': 4.62.2 + '@rollup/rollup-linux-arm64-gnu': 4.62.2 + '@rollup/rollup-linux-arm64-musl': 4.62.2 + '@rollup/rollup-linux-loong64-gnu': 4.62.2 + '@rollup/rollup-linux-loong64-musl': 4.62.2 + '@rollup/rollup-linux-ppc64-gnu': 4.62.2 + '@rollup/rollup-linux-ppc64-musl': 4.62.2 + '@rollup/rollup-linux-riscv64-gnu': 4.62.2 + '@rollup/rollup-linux-riscv64-musl': 4.62.2 + '@rollup/rollup-linux-s390x-gnu': 4.62.2 + '@rollup/rollup-linux-x64-gnu': 4.62.2 + '@rollup/rollup-linux-x64-musl': 4.62.2 + '@rollup/rollup-openbsd-x64': 4.62.2 + '@rollup/rollup-openharmony-arm64': 4.62.2 + '@rollup/rollup-win32-arm64-msvc': 4.62.2 + '@rollup/rollup-win32-ia32-msvc': 4.62.2 + '@rollup/rollup-win32-x64-gnu': 4.62.2 + '@rollup/rollup-win32-x64-msvc': 4.62.2 fsevents: 2.3.3 rope-sequence@1.3.4: {} @@ -13031,65 +12625,65 @@ snapshots: safer-buffer@2.1.2: {} - sass-embedded-all-unknown@1.99.0: + sass-embedded-all-unknown@1.100.0: dependencies: - sass: 1.99.0 + sass: 1.100.0 optional: true - sass-embedded-android-arm64@1.99.0: + sass-embedded-android-arm64@1.100.0: optional: true - sass-embedded-android-arm@1.99.0: + sass-embedded-android-arm@1.100.0: optional: true - sass-embedded-android-riscv64@1.99.0: + sass-embedded-android-riscv64@1.100.0: optional: true - sass-embedded-android-x64@1.99.0: + sass-embedded-android-x64@1.100.0: optional: true - sass-embedded-darwin-arm64@1.99.0: + sass-embedded-darwin-arm64@1.100.0: optional: true - sass-embedded-darwin-x64@1.99.0: + sass-embedded-darwin-x64@1.100.0: optional: true - sass-embedded-linux-arm64@1.99.0: + sass-embedded-linux-arm64@1.100.0: optional: true - sass-embedded-linux-arm@1.99.0: + sass-embedded-linux-arm@1.100.0: optional: true - sass-embedded-linux-musl-arm64@1.99.0: + sass-embedded-linux-musl-arm64@1.100.0: optional: true - sass-embedded-linux-musl-arm@1.99.0: + sass-embedded-linux-musl-arm@1.100.0: optional: true - sass-embedded-linux-musl-riscv64@1.99.0: + sass-embedded-linux-musl-riscv64@1.100.0: optional: true - sass-embedded-linux-musl-x64@1.99.0: + sass-embedded-linux-musl-x64@1.100.0: optional: true - sass-embedded-linux-riscv64@1.99.0: + sass-embedded-linux-riscv64@1.100.0: optional: true - sass-embedded-linux-x64@1.99.0: + sass-embedded-linux-x64@1.100.0: optional: true - sass-embedded-unknown-all@1.99.0: + sass-embedded-unknown-all@1.100.0: dependencies: - sass: 1.99.0 + sass: 1.100.0 optional: true - sass-embedded-win32-arm64@1.99.0: + sass-embedded-win32-arm64@1.100.0: optional: true - sass-embedded-win32-x64@1.99.0: + sass-embedded-win32-x64@1.100.0: optional: true - sass-embedded@1.99.0: + sass-embedded@1.100.0: dependencies: '@bufbuild/protobuf': 2.5.2 colorjs.io: 0.5.2 @@ -13099,28 +12693,28 @@ snapshots: sync-child-process: 1.0.2 varint: 6.0.0 optionalDependencies: - sass-embedded-all-unknown: 1.99.0 - sass-embedded-android-arm: 1.99.0 - sass-embedded-android-arm64: 1.99.0 - sass-embedded-android-riscv64: 1.99.0 - sass-embedded-android-x64: 1.99.0 - sass-embedded-darwin-arm64: 1.99.0 - sass-embedded-darwin-x64: 1.99.0 - sass-embedded-linux-arm: 1.99.0 - sass-embedded-linux-arm64: 1.99.0 - sass-embedded-linux-musl-arm: 1.99.0 - sass-embedded-linux-musl-arm64: 1.99.0 - sass-embedded-linux-musl-riscv64: 1.99.0 - sass-embedded-linux-musl-x64: 1.99.0 - sass-embedded-linux-riscv64: 1.99.0 - sass-embedded-linux-x64: 1.99.0 - sass-embedded-unknown-all: 1.99.0 - sass-embedded-win32-arm64: 1.99.0 - sass-embedded-win32-x64: 1.99.0 + sass-embedded-all-unknown: 1.100.0 + sass-embedded-android-arm: 1.100.0 + sass-embedded-android-arm64: 1.100.0 + sass-embedded-android-riscv64: 1.100.0 + sass-embedded-android-x64: 1.100.0 + sass-embedded-darwin-arm64: 1.100.0 + sass-embedded-darwin-x64: 1.100.0 + sass-embedded-linux-arm: 1.100.0 + sass-embedded-linux-arm64: 1.100.0 + sass-embedded-linux-musl-arm: 1.100.0 + sass-embedded-linux-musl-arm64: 1.100.0 + sass-embedded-linux-musl-riscv64: 1.100.0 + sass-embedded-linux-musl-x64: 1.100.0 + sass-embedded-linux-riscv64: 1.100.0 + sass-embedded-linux-x64: 1.100.0 + sass-embedded-unknown-all: 1.100.0 + sass-embedded-win32-arm64: 1.100.0 + sass-embedded-win32-x64: 1.100.0 - sass@1.99.0: + sass@1.100.0: dependencies: - chokidar: 4.0.3 + chokidar: 5.0.0 immutable: 5.1.5 source-map-js: 1.2.1 optionalDependencies: @@ -13166,7 +12760,7 @@ snapshots: dependencies: dunder-proto: 1.0.1 es-errors: 1.3.0 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 shebang-command@2.0.0: dependencies: @@ -13174,7 +12768,7 @@ snapshots: shebang-regex@3.0.0: {} - shell-quote@1.8.1: {} + shell-quote@1.8.4: {} shiki@3.2.1: dependencies: @@ -13249,7 +12843,7 @@ snapshots: socks@2.8.4: dependencies: - ip-address: 9.0.5 + ip-address: 10.2.0 smart-buffer: 4.2.0 sortablejs@1.14.0: {} @@ -13291,8 +12885,6 @@ snapshots: sprintf-js@1.0.3: {} - sprintf-js@1.1.3: {} - stackback@0.0.2: {} statuses@1.5.0: {} @@ -13357,7 +12949,7 @@ snapshots: define-data-property: 1.1.4 define-properties: 1.2.1 es-abstract: 1.24.2 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 has-property-descriptors: 1.0.2 string.prototype.trimend@1.0.9: @@ -13365,13 +12957,13 @@ snapshots: call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 string.prototype.trimstart@1.0.8: dependencies: call-bind: 1.0.9 define-properties: 1.2.1 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 string_decoder@1.3.0: dependencies: @@ -13416,58 +13008,58 @@ snapshots: style-mod@4.1.2: {} - stylelint-config-html@1.1.0(postcss-html@1.8.1)(stylelint@17.11.0(typescript@5.9.3)): + stylelint-config-html@1.1.0(postcss-html@1.8.1)(stylelint@17.13.0(typescript@5.9.3)): dependencies: postcss-html: 1.8.1 - stylelint: 17.11.0(typescript@5.9.3) + stylelint: 17.13.0(typescript@5.9.3) - stylelint-config-property-sort-order-smacss@10.0.0(stylelint@17.11.0(typescript@5.9.3)): + stylelint-config-property-sort-order-smacss@10.0.0(stylelint@17.13.0(typescript@5.9.3)): dependencies: css-property-sort-order-smacss: 2.2.0 - stylelint: 17.11.0(typescript@5.9.3) - stylelint-order: 6.0.4(stylelint@17.11.0(typescript@5.9.3)) + stylelint: 17.13.0(typescript@5.9.3) + stylelint-order: 6.0.4(stylelint@17.13.0(typescript@5.9.3)) - stylelint-config-recommended-scss@17.0.0(postcss@8.5.14)(stylelint@17.11.0(typescript@5.9.3)): + stylelint-config-recommended-scss@17.0.0(postcss@8.5.14)(stylelint@17.13.0(typescript@5.9.3)): dependencies: postcss-scss: 4.0.9(postcss@8.5.14) - stylelint: 17.11.0(typescript@5.9.3) - stylelint-config-recommended: 18.0.0(stylelint@17.11.0(typescript@5.9.3)) - stylelint-scss: 7.0.0(stylelint@17.11.0(typescript@5.9.3)) + stylelint: 17.13.0(typescript@5.9.3) + stylelint-config-recommended: 18.0.0(stylelint@17.13.0(typescript@5.9.3)) + stylelint-scss: 7.0.0(stylelint@17.13.0(typescript@5.9.3)) optionalDependencies: postcss: 8.5.14 - stylelint-config-recommended-vue@1.6.1(postcss-html@1.8.1)(stylelint@17.11.0(typescript@5.9.3)): + stylelint-config-recommended-vue@1.6.1(postcss-html@1.8.1)(stylelint@17.13.0(typescript@5.9.3)): dependencies: postcss-html: 1.8.1 semver: 7.7.3 - stylelint: 17.11.0(typescript@5.9.3) - stylelint-config-html: 1.1.0(postcss-html@1.8.1)(stylelint@17.11.0(typescript@5.9.3)) - stylelint-config-recommended: 18.0.0(stylelint@17.11.0(typescript@5.9.3)) + stylelint: 17.13.0(typescript@5.9.3) + stylelint-config-html: 1.1.0(postcss-html@1.8.1)(stylelint@17.13.0(typescript@5.9.3)) + stylelint-config-recommended: 18.0.0(stylelint@17.13.0(typescript@5.9.3)) - stylelint-config-recommended@18.0.0(stylelint@17.11.0(typescript@5.9.3)): + stylelint-config-recommended@18.0.0(stylelint@17.13.0(typescript@5.9.3)): dependencies: - stylelint: 17.11.0(typescript@5.9.3) + stylelint: 17.13.0(typescript@5.9.3) - stylelint-config-standard-scss@17.0.0(postcss@8.5.14)(stylelint@17.11.0(typescript@5.9.3)): + stylelint-config-standard-scss@17.0.0(postcss@8.5.14)(stylelint@17.13.0(typescript@5.9.3)): dependencies: - stylelint: 17.11.0(typescript@5.9.3) - stylelint-config-recommended-scss: 17.0.0(postcss@8.5.14)(stylelint@17.11.0(typescript@5.9.3)) - stylelint-config-standard: 40.0.0(stylelint@17.11.0(typescript@5.9.3)) + stylelint: 17.13.0(typescript@5.9.3) + stylelint-config-recommended-scss: 17.0.0(postcss@8.5.14)(stylelint@17.13.0(typescript@5.9.3)) + stylelint-config-standard: 40.0.0(stylelint@17.13.0(typescript@5.9.3)) optionalDependencies: postcss: 8.5.14 - stylelint-config-standard@40.0.0(stylelint@17.11.0(typescript@5.9.3)): + stylelint-config-standard@40.0.0(stylelint@17.13.0(typescript@5.9.3)): dependencies: - stylelint: 17.11.0(typescript@5.9.3) - stylelint-config-recommended: 18.0.0(stylelint@17.11.0(typescript@5.9.3)) + stylelint: 17.13.0(typescript@5.9.3) + stylelint-config-recommended: 18.0.0(stylelint@17.13.0(typescript@5.9.3)) - stylelint-order@6.0.4(stylelint@17.11.0(typescript@5.9.3)): + stylelint-order@6.0.4(stylelint@17.13.0(typescript@5.9.3)): dependencies: postcss: 8.5.14 postcss-sorting: 8.0.2(postcss@8.5.14) - stylelint: 17.11.0(typescript@5.9.3) + stylelint: 17.13.0(typescript@5.9.3) - stylelint-scss@7.0.0(stylelint@17.11.0(typescript@5.9.3)): + stylelint-scss@7.0.0(stylelint@17.13.0(typescript@5.9.3)): dependencies: css-tree: 3.2.1 is-plain-object: 5.0.0 @@ -13477,17 +13069,17 @@ snapshots: postcss-resolve-nested-selector: 0.1.6 postcss-selector-parser: 7.1.1 postcss-value-parser: 4.2.0 - stylelint: 17.11.0(typescript@5.9.3) + stylelint: 17.13.0(typescript@5.9.3) - stylelint-use-logical@2.1.3(stylelint@17.11.0(typescript@5.9.3)): + stylelint-use-logical@2.1.3(stylelint@17.13.0(typescript@5.9.3)): dependencies: - stylelint: 17.11.0(typescript@5.9.3) + stylelint: 17.13.0(typescript@5.9.3) - stylelint@17.11.0(typescript@5.9.3): + stylelint@17.13.0(typescript@5.9.3): dependencies: - '@csstools/css-calc': 3.2.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) - '@csstools/css-syntax-patches-for-csstree': 1.1.3(css-tree@3.2.1) + '@csstools/css-syntax-patches-for-csstree': 1.1.5(css-tree@3.2.1) '@csstools/css-tokenizer': 4.0.0 '@csstools/media-query-list-parser': 5.0.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/selector-resolve-nested': 4.0.0(postcss-selector-parser@7.1.1) @@ -13499,14 +13091,13 @@ snapshots: debug: 4.4.3 fast-glob: 3.3.3 fastest-levenshtein: 1.0.16 - file-entry-cache: 11.1.2 + file-entry-cache: 11.1.3 global-modules: 2.0.0 globby: 16.2.0 globjoin: 0.1.4 html-tags: 5.1.0 ignore: 7.0.5 import-meta-resolve: 4.2.0 - is-plain-object: 5.0.0 mathml-tag-names: 4.0.0 meow: 14.1.0 micromatch: 4.0.8 @@ -13574,7 +13165,7 @@ snapshots: string-width: 4.2.3 strip-ansi: 6.0.1 - tailwindcss@4.3.0: {} + tailwindcss@4.3.1: {} tapable@2.3.3: {} @@ -13645,9 +13236,7 @@ snapshots: dependencies: tldts-core: 7.0.19 - tmp@0.0.33: - dependencies: - os-tmpdir: 1.0.2 + tmp@0.2.7: {} to-regex-range@5.0.1: dependencies: @@ -13736,12 +13325,12 @@ snapshots: typed-query-selector@2.12.0: {} - typescript-eslint@8.56.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3): + typescript-eslint@8.60.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.56.0(@typescript-eslint/parser@8.56.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/parser': 8.56.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/typescript-estree': 8.56.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.56.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/eslint-plugin': 8.60.1(@typescript-eslint/parser@8.60.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.60.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.60.1(typescript@5.9.3) + '@typescript-eslint/utils': 8.60.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) eslint: 9.39.4(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: @@ -13765,7 +13354,7 @@ snapshots: buffer: 5.7.1 through: 2.3.8 - undici-types@7.16.0: {} + undici-types@7.18.2: {} unicode-canonical-property-names-ecmascript@2.0.0: {} @@ -13891,23 +13480,23 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.2 - vite-dev-rpc@1.1.0(vite@7.3.3(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3)): + vite-dev-rpc@1.1.0(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3)): dependencies: birpc: 2.6.1 - vite: 7.3.3(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3) - vite-hot-client: 2.1.0(vite@7.3.3(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3)) + vite: 7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3) + vite-hot-client: 2.1.0(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3)) - vite-hot-client@2.1.0(vite@7.3.3(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3)): + vite-hot-client@2.1.0(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3)): dependencies: - vite: 7.3.3(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3) + vite: 7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3) - vite-node@3.2.4(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3): + vite-node@3.2.4(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3): dependencies: cac: 6.7.14 debug: 4.4.3 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 7.3.3(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3) + vite: 7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3) transitivePeerDependencies: - '@types/node' - jiti @@ -13922,7 +13511,7 @@ snapshots: - tsx - yaml - vite-plugin-inspect@11.3.3(vite@7.3.3(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3)): + vite-plugin-inspect@11.3.3(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3)): dependencies: ansis: 4.1.0 debug: 4.4.3 @@ -13932,48 +13521,48 @@ snapshots: perfect-debounce: 2.0.0 sirv: 3.0.2 unplugin-utils: 0.3.0 - vite: 7.3.3(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3) - vite-dev-rpc: 1.1.0(vite@7.3.3(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3)) + vite: 7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3) + vite-dev-rpc: 1.1.0(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3)) transitivePeerDependencies: - supports-color - vite-plugin-pwa@1.3.0(vite@7.3.3(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3))(workbox-build@7.4.1)(workbox-window@7.4.1): + vite-plugin-pwa@1.3.0(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3))(workbox-build@7.4.1)(workbox-window@7.4.1): dependencies: debug: 4.4.3 pretty-bytes: 6.1.1 tinyglobby: 0.2.15 - vite: 7.3.3(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3) + vite: 7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3) workbox-build: 7.4.1 workbox-window: 7.4.1 transitivePeerDependencies: - supports-color - vite-plugin-vue-devtools@8.1.2(vite@7.3.3(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3))(vue@3.5.27(typescript@5.9.3)): + vite-plugin-vue-devtools@8.1.3(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3))(vue@3.5.27(typescript@5.9.3)): dependencies: - '@vue/devtools-core': 8.1.2(vue@3.5.27(typescript@5.9.3)) - '@vue/devtools-kit': 8.1.2 - '@vue/devtools-shared': 8.1.2 + '@vue/devtools-core': 8.1.3(vue@3.5.27(typescript@5.9.3)) + '@vue/devtools-kit': 8.1.3 + '@vue/devtools-shared': 8.1.3 sirv: 3.0.2 - vite: 7.3.3(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3) - vite-plugin-inspect: 11.3.3(vite@7.3.3(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3)) - vite-plugin-vue-inspector: 6.0.0(vite@7.3.3(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3)) + vite: 7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3) + vite-plugin-inspect: 11.3.3(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3)) + vite-plugin-vue-inspector: 6.0.0(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3)) transitivePeerDependencies: - '@nuxt/kit' - supports-color - vue - vite-plugin-vue-inspector@6.0.0(vite@7.3.3(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3)): + vite-plugin-vue-inspector@6.0.0(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3)): dependencies: - '@babel/core': 7.26.0 - '@babel/plugin-proposal-decorators': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-syntax-import-attributes': 7.26.0(@babel/core@7.26.0) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.26.0) - '@babel/plugin-transform-typescript': 7.25.9(@babel/core@7.26.0) - '@vue/babel-plugin-jsx': 1.2.5(@babel/core@7.26.0) + '@babel/core': 7.29.7 + '@babel/plugin-proposal-decorators': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-syntax-import-attributes': 7.26.0(@babel/core@7.29.7) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.29.7) + '@babel/plugin-transform-typescript': 7.25.9(@babel/core@7.29.7) + '@vue/babel-plugin-jsx': 1.2.5(@babel/core@7.29.7) '@vue/compiler-dom': 3.5.27 kolorist: 1.8.0 magic-string: 0.30.21 - vite: 7.3.3(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3) + vite: 7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3) transitivePeerDependencies: - supports-color @@ -13985,33 +13574,33 @@ snapshots: transitivePeerDependencies: - supports-color - vite@7.3.3(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3): + vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3): dependencies: - esbuild: 0.27.5 + esbuild: 0.28.1 fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 postcss: 8.5.14 - rollup: 4.60.3 + rollup: 4.62.2 tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 24.12.3 + '@types/node': 24.13.2 fsevents: 2.3.3 jiti: 2.6.1 lightningcss: 1.32.0 - sass: 1.99.0 - sass-embedded: 1.99.0 + sass: 1.100.0 + sass-embedded: 1.100.0 terser: 5.31.6 yaml: 2.8.3 - vitest@4.1.5(@types/node@24.12.3)(happy-dom@20.9.0)(jsdom@27.4.0)(vite@7.3.3(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3)): + vitest@4.1.9(@types/node@24.13.2)(happy-dom@20.10.6)(jsdom@27.4.0)(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3)): dependencies: - '@vitest/expect': 4.1.5 - '@vitest/mocker': 4.1.5(vite@7.3.3(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3)) - '@vitest/pretty-format': 4.1.5 - '@vitest/runner': 4.1.5 - '@vitest/snapshot': 4.1.5 - '@vitest/spy': 4.1.5 - '@vitest/utils': 4.1.5 + '@vitest/expect': 4.1.9 + '@vitest/mocker': 4.1.9(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3)) + '@vitest/pretty-format': 4.1.9 + '@vitest/runner': 4.1.9 + '@vitest/snapshot': 4.1.9 + '@vitest/spy': 4.1.9 + '@vitest/utils': 4.1.9 es-module-lexer: 2.0.0 expect-type: 1.3.0 magic-string: 0.30.21 @@ -14023,11 +13612,11 @@ snapshots: tinyexec: 1.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.1.0 - vite: 7.3.3(@types/node@24.12.3)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.31.6)(yaml@2.8.3) + vite: 7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 24.12.3 - happy-dom: 20.9.0 + '@types/node': 24.13.2 + happy-dom: 20.10.6 jsdom: 27.4.0 transitivePeerDependencies: - msw @@ -14080,10 +13669,10 @@ snapshots: '@vue/devtools-api': 6.6.4 vue: 3.5.27(typescript@5.9.3) - vue-tsc@3.2.8(typescript@5.9.3): + vue-tsc@3.3.5(typescript@5.9.3): dependencies: '@volar/typescript': 2.4.28 - '@vue/language-core': 3.2.8 + '@vue/language-core': 3.3.5 typescript: 5.9.3 vue@3.5.27(typescript@5.9.3): @@ -14108,10 +13697,10 @@ snapshots: dependencies: xml-name-validator: 5.0.0 - wait-on@9.0.5: + wait-on@9.0.10: dependencies: - axios: 1.15.2 - joi: 18.1.2 + axios: 1.16.0 + joi: 18.2.1 lodash: 4.18.1 minimist: 1.2.8 rxjs: 7.8.2 @@ -14228,13 +13817,13 @@ snapshots: workbox-build@7.4.1: dependencies: '@apideck/better-ajv-errors': 0.3.6(ajv@8.18.0) - '@babel/core': 7.26.0 - '@babel/preset-env': 7.26.0(@babel/core@7.26.0) + '@babel/core': 7.29.7 + '@babel/preset-env': 7.26.0(@babel/core@7.29.7) '@babel/runtime': 7.25.4 - '@rollup/plugin-babel': 6.1.0(@babel/core@7.26.0)(rollup@4.60.3) - '@rollup/plugin-node-resolve': 16.0.3(rollup@4.60.3) - '@rollup/plugin-replace': 6.0.3(rollup@4.60.3) - '@rollup/plugin-terser': 1.0.0(rollup@4.60.3) + '@rollup/plugin-babel': 6.1.0(@babel/core@7.29.7)(rollup@4.62.2) + '@rollup/plugin-node-resolve': 16.0.3(rollup@4.62.2) + '@rollup/plugin-replace': 6.0.3(rollup@4.62.2) + '@rollup/plugin-terser': 1.0.0(rollup@4.62.2) '@trickfilm400/rollup-plugin-off-main-thread': 3.0.0-pre1 ajv: 8.18.0 common-tags: 1.8.2 @@ -14243,7 +13832,7 @@ snapshots: fs-extra: 9.1.0 glob: 11.1.0 pretty-bytes: 5.6.0 - rollup: 4.60.3 + rollup: 4.62.2 source-map: 0.8.0-beta.0 stringify-object: 3.3.0 strip-comments: 2.0.1 @@ -14372,7 +13961,7 @@ snapshots: dependencies: signal-exit: 4.1.0 - ws@8.20.0: {} + ws@8.21.0: {} wsl-utils@0.1.0: dependencies: diff --git a/frontend/public/images/icons/favicon-tracking-32x32.png b/frontend/public/images/icons/favicon-tracking-32x32.png new file mode 100644 index 000000000..d3867376f Binary files /dev/null and b/frontend/public/images/icons/favicon-tracking-32x32.png differ diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 08688b1bb..760c18edc 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -61,6 +61,7 @@ import {useAuthStore} from '@/stores/auth' import {useBaseStore} from '@/stores/base' import {useColorScheme} from '@/composables/useColorScheme' +import {useTimeTrackingFavicon} from '@/composables/useTimeTrackingFavicon' import {useBodyClass} from '@/composables/useBodyClass' import QuickAddOverlay from '@/components/quick-actions/QuickAddOverlay.vue' import AddToHomeScreen from '@/components/home/AddToHomeScreen.vue' @@ -107,6 +108,7 @@ watch(accountDeletionConfirm, async (accountDeletionConfirm) => { setLanguage(authStore.settings.language ?? DEFAULT_LANGUAGE) useColorScheme() +useTimeTrackingFavicon() diff --git a/frontend/src/components/input/editor/TipTap.vue b/frontend/src/components/input/editor/TipTap.vue index d233b3e1a..3aef2de87 100644 --- a/frontend/src/components/input/editor/TipTap.vue +++ b/frontend/src/components/input/editor/TipTap.vue @@ -166,6 +166,7 @@ import Mention from '@tiptap/extension-mention' import {TaskList} from '@tiptap/extension-list' import {TaskItemWithId} from './taskItemWithId' +import {BlockquoteWithCommentId} from './blockquoteWithCommentId' import HardBreak from '@tiptap/extension-hard-break' import Commands from './commands' @@ -417,7 +418,9 @@ const extensions : Extensions = [ StarterKit.configure({ codeBlock: false, hardBreak: false, + blockquote: false, }), + BlockquoteWithCommentId, CodeBlockLowlight.configure({ lowlight: createLowlight(common), @@ -719,7 +722,7 @@ async function addImage(event: Event) { return } - const url = await inputPrompt(event.target.getBoundingClientRect()) + const url = await inputPrompt(event.target.getBoundingClientRect(), '', editor.value) if (url) { editor.value?.chain().focus().setImage({src: url}).run() @@ -775,6 +778,24 @@ function setModeAndValue(value: string) { }) } +// Replace the editor content with a reply draft (prefilled blockquote + empty +// paragraph) and enter edit mode immediately so the user can start typing. +// Returns synchronously after the next tick to let DOM updates settle. +async function setReplyContent(value: string) { + if (!editor.value) return + editor.value.commands.setContent(value, { + ...defaultSetContentOptions, + emitUpdate: false, + }) + internalMode.value = 'edit' + modelValue.value = editor.value.getHTML() + contentHasChanged.value = true + await nextTick() + editor.value.commands.focus('end') +} + +defineExpose({setReplyContent}) + // See https://github.com/github/hotkey/discussions/85#discussioncomment-5214660 function setFocusToEditor(event: KeyboardEvent) { diff --git a/frontend/src/components/input/editor/blockquoteWithCommentId.test.ts b/frontend/src/components/input/editor/blockquoteWithCommentId.test.ts new file mode 100644 index 000000000..fd7eda06a --- /dev/null +++ b/frontend/src/components/input/editor/blockquoteWithCommentId.test.ts @@ -0,0 +1,65 @@ +import {describe, it, expect} from 'vitest' +import {Editor} from '@tiptap/core' +import StarterKit from '@tiptap/starter-kit' +import {BlockquoteWithCommentId} from './blockquoteWithCommentId' + +describe('BlockquoteWithCommentId extension', () => { + const createEditor = (content: string = '') => { + return new Editor({ + extensions: [ + StarterKit.configure({blockquote: false}), + BlockquoteWithCommentId, + ], + content, + }) + } + + it('preserves data-comment-id through setContent → getHTML round-trip', () => { + const editor = createEditor('

hi

') + + const html = editor.getHTML() + expect(html).toContain('data-comment-id="42"') + + editor.destroy() + }) + + it('renders a plain blockquote (no attribute) unchanged', () => { + const editor = createEditor('

just a quote

') + + const html = editor.getHTML() + expect(html).toContain('
') + expect(html).not.toContain('data-comment-id') + + editor.destroy() + }) + + it('preserves nested rich content inside the blockquote', () => { + const editor = createEditor( + '

this is bold text

', + ) + + const html = editor.getHTML() + expect(html).toContain('data-comment-id="7"') + expect(html).toContain('bold') + + editor.destroy() + }) + + it('drops a malformed data-comment-id (non-integer)', () => { + const editor = createEditor('

x

') + + const html = editor.getHTML() + expect(html).not.toContain('data-comment-id') + + editor.destroy() + }) + + it('drops a non-positive data-comment-id', () => { + const editor = createEditor('

x

') + + const html = editor.getHTML() + expect(html).not.toContain('data-comment-id') + + editor.destroy() + }) +}) diff --git a/frontend/src/components/input/editor/blockquoteWithCommentId.ts b/frontend/src/components/input/editor/blockquoteWithCommentId.ts new file mode 100644 index 000000000..87e88ac28 --- /dev/null +++ b/frontend/src/components/input/editor/blockquoteWithCommentId.ts @@ -0,0 +1,50 @@ +import Blockquote from '@tiptap/extension-blockquote' +import {VueNodeViewRenderer} from '@tiptap/vue-3' + +import BlockquoteCommentView from './BlockquoteCommentView.vue' + +/** + * Blockquote extension that preserves `data-comment-id` across parse/serialize. + * Used as the canonical reply marker: a comment that quotes another comment + * stores the referenced comment's id on the wrapping blockquote, so both the + * backend (for implicit-mention notifications) and the frontend (for the + * jump-to-original chevron) can find it without a separate schema field. + * + * A Vue NodeView renders the in-app header + chevron when the surrounding + * component (Comments.vue) provides a `commentReplyContext`. Outside that + * context (task descriptions, etc.) the NodeView falls back to a plain + * blockquote. + */ +export const BlockquoteWithCommentId = Blockquote.extend({ + addAttributes() { + return { + ...this.parent?.(), + commentId: { + default: null, + parseHTML: (element: HTMLElement) => { + const raw = element.getAttribute('data-comment-id') + if (raw === null) { + return null + } + const id = Number(raw) + if (!Number.isInteger(id) || id <= 0) { + return null + } + return id + }, + renderHTML: (attributes) => { + if (attributes.commentId === null || attributes.commentId === undefined) { + return {} + } + return { + 'data-comment-id': String(attributes.commentId), + } + }, + }, + } + }, + + addNodeView() { + return VueNodeViewRenderer(BlockquoteCommentView) + }, +}) diff --git a/frontend/src/components/input/editor/emoji/emojiSuggestion.ts b/frontend/src/components/input/editor/emoji/emojiSuggestion.ts index 22dad82b2..b9fb7c961 100644 --- a/frontend/src/components/input/editor/emoji/emojiSuggestion.ts +++ b/frontend/src/components/input/editor/emoji/emojiSuggestion.ts @@ -5,6 +5,7 @@ import {PluginKey, type EditorState} from '@tiptap/pm/state' import EmojiList from './EmojiList.vue' import {loadEmojis, filterEmojis, type EmojiEntry} from './emojiData' +import {getPopupContainer} from '../popupContainer' export const EmojiSuggestionPluginKey = new PluginKey('emojiSuggestion') @@ -78,7 +79,7 @@ export default function emojiSuggestionSetup() { popupElement.style.left = '0' popupElement.style.zIndex = '4700' popupElement.appendChild(component.element!) - document.body.appendChild(popupElement) + getPopupContainer(props.editor).appendChild(popupElement) const rect = props.clientRect() if (!rect) { @@ -108,7 +109,7 @@ export default function emojiSuggestionSetup() { cleanupFloating = null } if (popupElement) { - document.body.removeChild(popupElement) + popupElement.remove() popupElement = null } component?.destroy() diff --git a/frontend/src/components/input/editor/setLinkInEditor.ts b/frontend/src/components/input/editor/setLinkInEditor.ts index f5f547ff4..b1fa4e88e 100644 --- a/frontend/src/components/input/editor/setLinkInEditor.ts +++ b/frontend/src/components/input/editor/setLinkInEditor.ts @@ -3,7 +3,7 @@ import inputPrompt from '@/helpers/inputPrompt' export async function setLinkInEditor(pos: DOMRect, editor: Editor | null | undefined) { const previousUrl = editor?.getAttributes('link').href || '' - const url = await inputPrompt(pos, previousUrl) + const url = await inputPrompt(pos, previousUrl, editor ?? undefined) // empty if (url === '') { diff --git a/frontend/src/components/input/filter/FilterCommandsList.vue b/frontend/src/components/input/filter/FilterCommandsList.vue index fe1348bd4..34c92887b 100644 --- a/frontend/src/components/input/filter/FilterCommandsList.vue +++ b/frontend/src/components/input/filter/FilterCommandsList.vue @@ -135,6 +135,7 @@ defineExpose({ inline-size: 100%; text-align: start; background: transparent; + color: inherit; border-radius: $radius; border: 0; padding: 0.375rem 0.5rem; diff --git a/frontend/src/components/misc/Icon.ts b/frontend/src/components/misc/Icon.ts index 558daeff5..c384c4d32 100644 --- a/frontend/src/components/misc/Icon.ts +++ b/frontend/src/components/misc/Icon.ts @@ -1,6 +1,7 @@ import {library} from '@fortawesome/fontawesome-svg-core' import { faAlignLeft, + faAngleLeft, faAngleRight, faAnglesUp, faArchive, @@ -58,6 +59,7 @@ import { faPlay, faPlus, faPowerOff, + faRss, faSearch, faShareAlt, faSignOutAlt, @@ -120,6 +122,7 @@ library.add(faCode) library.add(faQuoteRight) library.add(faListUl) library.add(faAlignLeft) +library.add(faAngleLeft) library.add(faAngleRight) library.add(faArchive) library.add(faArrowLeft) @@ -168,6 +171,7 @@ library.add(faPercent) library.add(faPlay) library.add(faPlus) library.add(faPowerOff) +library.add(faRss) library.add(faSave) library.add(faSearch) library.add(faShareAlt) diff --git a/frontend/src/components/misc/Modal.vue b/frontend/src/components/misc/Modal.vue index f33dd5aac..5450d0a99 100644 --- a/frontend/src/components/misc/Modal.vue +++ b/frontend/src/components/misc/Modal.vue @@ -17,7 +17,7 @@ > @@ -62,13 +62,13 @@ @@ -178,7 +211,13 @@ $modal-width: 1024px; // Reset UA dialog styles padding: 0; border: none; - background: transparent; + // The scrim lives on the dialog element, not on ::backdrop: Chromium + // intermittently stops painting a styled ::backdrop (e.g. after the + // dialog's subtree re-renders, or while display is transitioned) even + // though getComputedStyle still reports the color. The dialog fills the + // viewport anyway, and its opacity transition fades the scrim with it — + // same as the old div-based .modal-mask. + background: rgba(0, 0, 0, .8); color: #ffffff; // Fill viewport position: fixed; @@ -188,10 +227,12 @@ $modal-width: 1024px; max-inline-size: 100%; max-block-size: 100%; - // Transitions + // Transitions. No display/allow-discrete transition needed: the close + // fade runs while the dialog is still [open] (data-closing + timer in + // closeDialog), and transitioning display triggers the Chromium paint + // bug above. opacity: 0; - transition: opacity 150ms ease, - display 150ms ease allow-discrete; + transition: opacity 150ms ease; &[open]:not([data-closing]) { opacity: 1; @@ -203,16 +244,11 @@ $modal-width: 1024px; &::backdrop { background-color: rgba(0, 0, 0, 0); - transition: background-color 150ms ease, - display 150ms ease allow-discrete; } - &[open]:not([data-closing])::backdrop { - background-color: rgba(0, 0, 0, .8); - - @starting-style { - background-color: rgba(0, 0, 0, 0); - } + // in quick-add mode the Electron window itself is the overlay — no scrim + &:has(.is-quick-add-mode) { + background: transparent; } } @@ -228,13 +264,20 @@ $modal-width: 1024px; } .default .modal-content, -.hint-modal .modal-content { +.hint-modal .modal-content, +.top .modal-content { text-align: center; position: absolute; // fine to use top/left since we're only using this to position it centered inset-block-start: 50%; inset-inline-start: 50%; transform: translate(-50%, -50%); + // Cap centered content to the viewport and scroll inside it. Without this a + // taller-than-viewport modal centres its top edge above the viewport, where + // the container's overflow can't scroll to it (the .top variant overrides + // both values below). + max-block-size: calc(100dvh - 2rem); + overflow: auto; [dir="rtl"] & { transform: translate(50%, -50%); @@ -244,6 +287,9 @@ $modal-width: 1024px; margin: 0; position: static; transform: none; + // the fullscreen mobile layout flows and scrolls in .modal-container + max-block-size: none; + overflow: visible; } .modal-header { @@ -256,11 +302,31 @@ $modal-width: 1024px; } } +// anchored below the top edge instead of centered, used for QuickActions +.top .modal-content { + inset-block-start: 3rem; + transform: translate(-50%, 0); + max-block-size: calc(100dvh - 6rem); + overflow: auto; + + [dir="rtl"] & { + transform: translate(50%, 0); + } + + // the fullscreen mobile layout flows and scrolls in .modal-container + @media screen and (max-width: $tablet) { + transform: none; + max-block-size: none; + overflow: visible; + } +} + // Default width for centered modals. Scoped with :not(.is-wide) so the // `wide` prop can still expand the modal (the .is-wide rule below would // otherwise be outranked by .default .modal-content's specificity). .default .modal-content:not(.is-wide), -.hint-modal .modal-content:not(.is-wide) { +.hint-modal .modal-content:not(.is-wide), +.top .modal-content:not(.is-wide) { inline-size: calc(100% - 2rem); max-inline-size: 640px; @@ -361,6 +427,32 @@ $modal-width: 1024px; } } +// Unconstrain the native so the full modal content flows onto the +// printed page instead of being clipped to the viewport-sized top layer. +@media print { + .modal-dialog { + position: static; + inline-size: auto; + block-size: auto; + max-inline-size: none; + max-block-size: none; + background: transparent; + + &::backdrop { + display: none; + } + } + + .modal-container { + overflow: visible; + min-block-size: 0; + } + + :deep(.card) { + min-block-size: 0 !important; + } +} + .modal-content:has(.modal-header) { display: flex; flex-direction: column; diff --git a/frontend/src/components/misc/Notification.vue b/frontend/src/components/misc/Notification.vue index 3dc8bac52..a1173bf30 100644 --- a/frontend/src/components/misc/Notification.vue +++ b/frontend/src/components/misc/Notification.vue @@ -1,66 +1,96 @@ + + diff --git a/frontend/src/components/tasks/partials/Description.vue b/frontend/src/components/tasks/partials/Description.vue index 7b5ab3907..10bc3c9ce 100644 --- a/frontend/src/components/tasks/partials/Description.vue +++ b/frontend/src/components/tasks/partials/Description.vue @@ -1,5 +1,7 @@ - +
component is too much resulting in a diff --git a/frontend/src/components/tasks/partials/commentReplyContext.ts b/frontend/src/components/tasks/partials/commentReplyContext.ts new file mode 100644 index 000000000..01468cf30 --- /dev/null +++ b/frontend/src/components/tasks/partials/commentReplyContext.ts @@ -0,0 +1,26 @@ +import type {InjectionKey} from 'vue' +import type {ITaskComment} from '@/modelTypes/ITaskComment' + +export interface CommentReplyContext { + findComment: (id: number) => ITaskComment | undefined + scrollToComment: (id: number) => void +} + +export const commentReplyContextKey: InjectionKey = Symbol('commentReplyContext') + +const HIGHLIGHT_CLASS = 'comment-highlight' +const HIGHLIGHT_DURATION_MS = 1500 + +export function scrollAndHighlightComment(id: number): void { + const el = document.getElementById(`comment-${id}`) + if (!el) { + return + } + el.scrollIntoView({behavior: 'smooth', block: 'center', inline: 'nearest'}) + el.classList.remove(HIGHLIGHT_CLASS) + // Re-apply on next frame so the animation restarts even if already running. + requestAnimationFrame(() => { + el.classList.add(HIGHLIGHT_CLASS) + window.setTimeout(() => el.classList.remove(HIGHLIGHT_CLASS), HIGHLIGHT_DURATION_MS) + }) +} diff --git a/frontend/src/components/time-tracking/TaskTimeTracking.vue b/frontend/src/components/time-tracking/TaskTimeTracking.vue new file mode 100644 index 000000000..a05937ca2 --- /dev/null +++ b/frontend/src/components/time-tracking/TaskTimeTracking.vue @@ -0,0 +1,83 @@ + + + diff --git a/frontend/src/components/time-tracking/TimeEntryForm.vue b/frontend/src/components/time-tracking/TimeEntryForm.vue new file mode 100644 index 000000000..e8dd710bc --- /dev/null +++ b/frontend/src/components/time-tracking/TimeEntryForm.vue @@ -0,0 +1,353 @@ + + + + + diff --git a/frontend/src/components/time-tracking/TimeEntryList.vue b/frontend/src/components/time-tracking/TimeEntryList.vue new file mode 100644 index 000000000..7ef47b51b --- /dev/null +++ b/frontend/src/components/time-tracking/TimeEntryList.vue @@ -0,0 +1,247 @@ + + + + + diff --git a/frontend/src/components/time-tracking/TimerBadge.vue b/frontend/src/components/time-tracking/TimerBadge.vue new file mode 100644 index 000000000..9a52553cb --- /dev/null +++ b/frontend/src/components/time-tracking/TimerBadge.vue @@ -0,0 +1,113 @@ + + + + + diff --git a/frontend/src/composables/useGlobalNow.ts b/frontend/src/composables/useGlobalNow.ts index 83d9cf9ef..c5e3510e7 100644 --- a/frontend/src/composables/useGlobalNow.ts +++ b/frontend/src/composables/useGlobalNow.ts @@ -1,4 +1,4 @@ -import { ref } from 'vue' +import { getCurrentInstance, ref } from 'vue' import { createGlobalState, useIntervalFn } from '@vueuse/core' import { onBeforeRouteUpdate } from 'vue-router' @@ -18,10 +18,14 @@ export const useGlobalNow = createGlobalState(() => { useIntervalFn(update, GLOBAL_NOW_INTERVAL, { immediate: true }) - // ensure the now value is refreshed when the route changes - onBeforeRouteUpdate(() => { - update() - }) + // Now that this state can be initialised from a plain helper (formatDateSince), the + // first caller is not guaranteed to be a component — guard the route hook accordingly. + if (getCurrentInstance()) { + // ensure the now value is refreshed when the route changes + onBeforeRouteUpdate(() => { + update() + }) + } return { now, diff --git a/frontend/src/composables/useTaskList.test.ts b/frontend/src/composables/useTaskList.test.ts new file mode 100644 index 000000000..9703ab29a --- /dev/null +++ b/frontend/src/composables/useTaskList.test.ts @@ -0,0 +1,34 @@ +import {describe, it, expect} from 'vitest' +import {buildStoredQuery} from './useTaskList' + +describe('buildStoredQuery', () => { + it('includes sort when set', () => { + expect(buildStoredQuery({sort: 'due_date:asc', filter: undefined, s: undefined, page: 1})) + .toEqual({sort: 'due_date:asc'}) + }) + + it('includes filter and search when set', () => { + expect(buildStoredQuery({sort: undefined, filter: 'done = false', s: 'foo', page: 1})) + .toEqual({filter: 'done = false', s: 'foo'}) + }) + + it('omits page when it equals the default of 1', () => { + expect(buildStoredQuery({sort: 'id:desc', filter: undefined, s: undefined, page: 1})) + .toEqual({sort: 'id:desc'}) + }) + + it('includes page when greater than 1', () => { + expect(buildStoredQuery({sort: undefined, filter: undefined, s: undefined, page: 3})) + .toEqual({page: '3'}) + }) + + it('returns an empty object when nothing is set', () => { + expect(buildStoredQuery({sort: undefined, filter: undefined, s: undefined, page: 1})) + .toEqual({}) + }) + + it('skips empty strings', () => { + expect(buildStoredQuery({sort: '', filter: '', s: '', page: 1})) + .toEqual({}) + }) +}) diff --git a/frontend/src/composables/useTaskList.ts b/frontend/src/composables/useTaskList.ts index b595e5e09..0f4ac40c1 100644 --- a/frontend/src/composables/useTaskList.ts +++ b/frontend/src/composables/useTaskList.ts @@ -1,4 +1,6 @@ import {ref, shallowReactive, watch, computed, type ComputedGetter} from 'vue' +import {useRouter, isNavigationFailure} from 'vue-router' +import type {LocationQueryRaw} from 'vue-router' import {useRouteQuery} from '@vueuse/router' import TaskCollectionService, { @@ -10,6 +12,7 @@ import type {ITask} from '@/modelTypes/ITask' import {error} from '@/message' import type {IProject} from '@/modelTypes/IProject' import {useAuthStore} from '@/stores/auth' +import {useViewFiltersStore} from '@/stores/viewFilters' import type {IProjectView} from '@/modelTypes/IProjectView' export type Order = 'asc' | 'desc' | 'none' @@ -59,6 +62,22 @@ const SORT_BY_DEFAULT: SortBy = { id: 'desc', } +interface TaskListQueryState { + sort: string | undefined + filter: string | undefined + s: string | undefined + page: number +} + +export function buildStoredQuery(state: TaskListQueryState): LocationQueryRaw { + const query: LocationQueryRaw = {} + if (state.sort) query.sort = state.sort + if (state.filter) query.filter = state.filter + if (state.s) query.s = state.s + if (state.page > 1) query.page = String(state.page) + return query +} + // This makes sure an id sort order is always sorted last. // When tasks would be sorted first by id and then by whatever else was specified, the id sort takes // precedence over everything else, making any other sort columns pretty useless. @@ -94,6 +113,9 @@ export function useTaskList( const projectId = computed(() => projectIdGetter()) const projectViewId = computed(() => projectViewIdGetter()) + const router = useRouter() + const viewFiltersStore = useViewFiltersStore() + const params = ref({...getDefaultTaskFilterParams()}) const page = useRouteQuery('page', '1', { transform: Number }) @@ -119,6 +141,55 @@ export function useTaskList( }, }) + // Mirror the URL query bits this composable owns into the store so + // in-project tab switches and sidebar re-visits can restore them. + // + // `ProjectList`/`ProjectTable` are reused across project switches (no + // `:key` on them in ProjectView.vue), so setup runs only once. We track + // the last viewId we synced — on every viewId transition, if the URL has + // none of our params and the store has an entry, restore it via + // `router.replace` and skip writing back the empty state we'd otherwise + // clobber the saved entry with. + let lastSyncedViewId: number | undefined + watch( + [projectViewId, sortQuery, filter, s, page], + ([viewId, sortValue, filterValue, sValue, pageValue]) => { + const viewIdChanged = viewId !== lastSyncedViewId + lastSyncedViewId = viewId + + // An invalid `?page=` becomes NaN via `transform: Number`; treat it as + // the default so it neither blocks restoration nor wipes stored state. + const currentPage = Number.isInteger(pageValue) ? pageValue : 1 + const urlIsEmpty = !sortValue && !filterValue && !sValue && currentPage === 1 + if (viewIdChanged && urlIsEmpty) { + const storedQuery = viewFiltersStore.getViewQuery(viewId) + if (Object.keys(storedQuery).length > 0) { + // Merge so unrelated query params on the route survive the restore. + // Swallow navigation failures (e.g. aborted/duplicated) so the + // ignored promise can't surface as an unhandled rejection. + router.replace({query: {...router.currentRoute.value.query, ...storedQuery}}) + .catch(failure => { + if (!isNavigationFailure(failure)) throw failure + }) + return + } + } + + const query = buildStoredQuery({ + sort: sortValue as string | undefined, + filter: filterValue as string | undefined, + s: sValue as string | undefined, + page: currentPage, + }) + if (Object.keys(query).length > 0) { + viewFiltersStore.setViewQuery(viewId, query) + } else { + viewFiltersStore.clearViewQuery(viewId) + } + }, + {immediate: true}, + ) + const allParams = computed(() => { const loadParams = {...params.value} diff --git a/frontend/src/composables/useTimeTrackingFavicon.ts b/frontend/src/composables/useTimeTrackingFavicon.ts new file mode 100644 index 000000000..43f24b8a7 --- /dev/null +++ b/frontend/src/composables/useTimeTrackingFavicon.ts @@ -0,0 +1,32 @@ +import {watch} from 'vue' +import {createSharedComposable, tryOnMounted} from '@vueuse/core' +import {storeToRefs} from 'pinia' + +import {useTimeTrackingStore} from '@/stores/timeTracking' +import {getFullBaseUrl} from '@/helpers/getFullBaseUrl' + +const TRACKING_FAVICON = `${getFullBaseUrl()}images/icons/favicon-tracking-32x32.png` + +function getFaviconLink(): HTMLLinkElement | null { + return document.querySelector('link[rel="icon"]') +} + +// Swaps in a favicon with a small red dot in the lower left corner while a timer +// is running, so an active time tracking session is visible even when the tab +// isn't focused. +export const useTimeTrackingFavicon = createSharedComposable(() => { + const {hasActiveTimer} = storeToRefs(useTimeTrackingStore()) + + const originalHref = getFaviconLink()?.getAttribute('href') ?? '/favicon.ico' + + function update(active: boolean) { + const link = getFaviconLink() + if (link === null) { + return + } + link.href = active ? TRACKING_FAVICON : originalHref + } + + watch(hasActiveTimer, update, {flush: 'post'}) + tryOnMounted(() => update(hasActiveTimer.value)) +}) diff --git a/frontend/src/constants/proFeatures.ts b/frontend/src/constants/proFeatures.ts new file mode 100644 index 000000000..4e2af18ec --- /dev/null +++ b/frontend/src/constants/proFeatures.ts @@ -0,0 +1,8 @@ +// Licensed "pro" features the server may advertise via /info's enabled_pro_features. +// Use these instead of bare strings when calling configStore.isProFeatureEnabled. +export const PRO_FEATURE = { + ADMIN_PANEL: 'admin_panel', + TIME_TRACKING: 'time_tracking', +} as const + +export type ProFeature = typeof PRO_FEATURE[keyof typeof PRO_FEATURE] diff --git a/frontend/src/constants/redirectHash.ts b/frontend/src/constants/redirectHash.ts new file mode 100644 index 000000000..1dd648e58 --- /dev/null +++ b/frontend/src/constants/redirectHash.ts @@ -0,0 +1,12 @@ +/** + * Hash-fragment prefix used to carry a post-login destination in the URL. + * + * Unlike the localStorage redirect, this lives in the address bar so the URL + * stays copyable between browsers (needed for native OAuth clients that open + * /oauth/authorize, see #2654). It uses the hash – not a query param – so the + * embedded OAuth parameters never reach server or proxy access logs. + * + * Must stay distinct from LINK_SHARE_HASH_PREFIX, which router.beforeEach + * special-cases. + */ +export const REDIRECT_HASH_PREFIX = '#redirect=' diff --git a/frontend/src/directives/tooltip.ts b/frontend/src/directives/tooltip.ts new file mode 100644 index 000000000..4c76d6ed6 --- /dev/null +++ b/frontend/src/directives/tooltip.ts @@ -0,0 +1,49 @@ +import type {Directive, DirectiveBinding} from 'vue' +import {vTooltip} from 'floating-vue' + +// When a tooltip target lives inside a opened via showModal(), the +// dialog is in the browser's top layer. floating-vue teleports tooltips to +// by default, so they render *below* the dialog's ::backdrop and are +// not visible. Teleporting them into the dialog keeps them in the top layer. +function buildBinding(el: Element, binding: DirectiveBinding): DirectiveBinding { + const dialog = el.closest('dialog') + if (!dialog) { + return binding + } + + const value = binding.value + let normalized: Record + if (typeof value === 'string') { + normalized = {content: value} + } else if (value && typeof value === 'object') { + normalized = {...value as Record} + } else { + return binding + } + + if (normalized.container === undefined) { + normalized.container = dialog + } + + return {...binding, value: normalized} +} + +// Bind via `mounted` rather than `beforeMount` so the element is already +// attached to the DOM — otherwise `el.closest('dialog')` cannot find the +// dialog ancestor. +const tooltip: Directive = { + mounted(el, binding) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ;(vTooltip as any).beforeMount(el, buildBinding(el, binding)) + }, + updated(el, binding) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ;(vTooltip as any).updated(el, buildBinding(el, binding)) + }, + beforeUnmount(el) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ;(vTooltip as any).beforeUnmount(el) + }, +} + +export default tooltip diff --git a/frontend/src/helpers/inputPrompt.ts b/frontend/src/helpers/inputPrompt.ts index 11dd3325f..d8de526ca 100644 --- a/frontend/src/helpers/inputPrompt.ts +++ b/frontend/src/helpers/inputPrompt.ts @@ -2,10 +2,17 @@ import {createRandomID} from '@/helpers/randomId' import {computePosition, flip, shift, offset} from '@floating-ui/dom' import {nextTick} from 'vue' import {eventToShortcutString} from '@/helpers/shortcut' +import type {Editor} from '@tiptap/core' +import {getPopupContainer} from '@/components/input/editor/popupContainer' -export default function inputPrompt(pos: ClientRect, oldValue: string = ''): Promise { +export default function inputPrompt(pos: ClientRect, oldValue: string = '', editor?: Editor): Promise { return new Promise((resolve) => { const id = 'link-input-' + createRandomID() + // Append inside the open task (top-layer) when present, otherwise + // document.body. A body-level popup is painted behind a showModal() dialog + // and unfocusable through its focus trap, breaking the link prompt in the + // Kanban task popup (#2940). + const container = getPopupContainer(editor) // Create popup element const popupElement = document.createElement('div') @@ -26,7 +33,7 @@ export default function inputPrompt(pos: ClientRect, oldValue: string = ''): Pro inputElement.value = oldValue wrapperDiv.appendChild(inputElement) popupElement.appendChild(wrapperDiv) - document.body.appendChild(popupElement) + container.appendChild(popupElement) // Create a local mutable copy of the position for scroll tracking let currentRect = new DOMRect(pos.left, pos.top, pos.width, pos.height) @@ -82,15 +89,41 @@ export default function inputPrompt(pos: ClientRect, oldValue: string = ''): Pro nextTick(() => document.getElementById(id)?.focus()) + // The prompt is a sub-modal of the enclosing task . Native modal + // dialogs close themselves on Escape ("cancel"); swallow that while the + // prompt is open so Escape only dismisses the prompt, not the task dialog. + const dialog = container.closest('dialog') as HTMLDialogElement | null + const handleDialogCancel = (event: Event) => event.preventDefault() + dialog?.addEventListener('cancel', handleDialogCancel) + + const handleClickOutside = (event: MouseEvent) => { + if (!popupElement.contains(event.target as Node)) { + resolve('') + cleanup() + } + } + const cleanup = () => { window.removeEventListener('scroll', handleScroll, true) - if (document.body.contains(popupElement)) { - document.body.removeChild(popupElement) + document.removeEventListener('click', handleClickOutside) + dialog?.removeEventListener('cancel', handleDialogCancel) + if (container.contains(popupElement)) { + container.removeChild(popupElement) } } document.getElementById(id)?.addEventListener('keydown', event => { const shortcutString = eventToShortcutString(event) + + if (shortcutString === 'Escape') { + // Stop the native from closing on Escape; cancel the prompt only. + event.preventDefault() + event.stopPropagation() + resolve('') + cleanup() + return + } + if (shortcutString !== 'Enter') { return } @@ -105,15 +138,6 @@ export default function inputPrompt(pos: ClientRect, oldValue: string = ''): Pro cleanup() }) - // Close on click outside - const handleClickOutside = (event: MouseEvent) => { - if (!popupElement.contains(event.target as Node)) { - resolve('') - cleanup() - document.removeEventListener('click', handleClickOutside) - } - } - // Add slight delay to prevent immediate closing setTimeout(() => { document.addEventListener('click', handleClickOutside) diff --git a/frontend/src/helpers/time/formatDate.ts b/frontend/src/helpers/time/formatDate.ts index 4ff9a4da5..ed7f4a3d7 100644 --- a/frontend/src/helpers/time/formatDate.ts +++ b/frontend/src/helpers/time/formatDate.ts @@ -5,6 +5,7 @@ import {i18n} from '@/i18n' import {createSharedComposable} from '@vueuse/core' import {computed, toValue, type MaybeRefOrGetter} from 'vue' import {useDateDisplay} from '@/composables/useDateDisplay' +import {useGlobalNow} from '@/composables/useGlobalNow' import {useTimeFormat} from '@/composables/useTimeFormat' import {DATE_DISPLAY, type DateDisplay} from '@/constants/dateDisplay' import {TIME_FORMAT, type TimeFormat} from '@/constants/timeFormat' @@ -49,8 +50,13 @@ export const formatDateSince = (date: Date | string | null) => { const locale = DAYJS_LOCALE_MAPPING[i18n.global.locale.value.toLowerCase()] ?? 'en' + // Computing the relative string against the shared, ticking `now` (instead of fromNow's + // internal Date.now()) makes every reactive caller re-render on the 60s tick, so open views + // don't keep showing a stale "x minutes ago". + const {now} = useGlobalNow() + return date - ? dayjs(date).locale(locale).fromNow() + ? dayjs(date).locale(locale).from(now.value) : '' } diff --git a/frontend/src/helpers/time/smartFillStart.test.ts b/frontend/src/helpers/time/smartFillStart.test.ts new file mode 100644 index 000000000..e48caf130 --- /dev/null +++ b/frontend/src/helpers/time/smartFillStart.test.ts @@ -0,0 +1,57 @@ +import {describe, it, expect} from 'vitest' + +import {smartFillStart} from './smartFillStart' +import type {ITimeEntry} from '@/modelTypes/ITimeEntry' + +function entry(startTime: Date, endTime: Date | null): ITimeEntry { + return { + id: 1, + userId: 1, + taskId: 0, + projectId: 0, + startTime, + endTime, + comment: '', + created: startTime, + updated: startTime, + maxPermission: null, + } +} + +describe('smartFillStart', () => { + const now = new Date('2026-06-07T15:30:00') + + it('continues from the latest entry end time', () => { + const entries = [ + entry(new Date('2026-06-07T09:00:00'), new Date('2026-06-07T10:00:00')), + entry(new Date('2026-06-07T11:00:00'), new Date('2026-06-07T12:30:00')), + ] + expect(smartFillStart(entries, '09:00', now)).toEqual(new Date('2026-06-07T12:30:00')) + }) + + it('ignores still-running entries (no end) when picking the latest end', () => { + const entries = [ + entry(new Date('2026-06-07T09:00:00'), new Date('2026-06-07T10:00:00')), + entry(new Date('2026-06-07T13:00:00'), null), + ] + expect(smartFillStart(entries, '09:00', now)).toEqual(new Date('2026-06-07T10:00:00')) + }) + + it('falls back to the default start time on the current day when there are no entries', () => { + expect(smartFillStart([], '08:15', now)).toEqual(new Date('2026-06-07T08:15:00')) + }) + + it('falls back to 09:00 when no default is configured', () => { + expect(smartFillStart([], '', now)).toEqual(new Date('2026-06-07T09:00:00')) + }) + + it('caps the default start at now when it would be in the future (before 09:00)', () => { + const beforeNine = new Date('2026-06-07T07:30:00') + expect(smartFillStart([], '09:00', beforeNine)).toEqual(beforeNine) + }) + + it('caps a future last-entry end at now', () => { + const entries = [entry(new Date('2026-06-07T16:00:00'), new Date('2026-06-07T17:00:00'))] + expect(smartFillStart(entries, '09:00', now)).toEqual(now) + }) +}) diff --git a/frontend/src/helpers/time/smartFillStart.ts b/frontend/src/helpers/time/smartFillStart.ts new file mode 100644 index 000000000..0a84a7eea --- /dev/null +++ b/frontend/src/helpers/time/smartFillStart.ts @@ -0,0 +1,24 @@ +import type {ITimeEntry} from '@/modelTypes/ITimeEntry' + +// The smart-clock start time: continue from the most recent entry's end so +// consecutive entries don't overlap or leave gaps; with no completed entry to +// continue from, fall back to the user's configured default start (HH:MM) on +// the given day. +export function smartFillStart(recentEntries: ITimeEntry[], defaultStart: string, now: Date): Date { + // The filled range ends at now, so a start after now would be inverted (and + // rejected on save). Cap at now — e.g. the 09:00 fallback before 9am. + const cap = (start: Date) => (start.getTime() > now.getTime() ? new Date(now) : start) + + const lastEnd = recentEntries + .map(entry => entry.endTime) + .filter((end): end is Date => end !== null) + .sort((a, b) => b.getTime() - a.getTime())[0] + if (lastEnd !== undefined) { + return cap(new Date(lastEnd)) + } + + const [hours, minutes] = (defaultStart || '09:00').split(':').map(Number) + const start = new Date(now) + start.setHours(hours || 0, minutes || 0, 0, 0) + return cap(start) +} diff --git a/frontend/src/i18n/index.ts b/frontend/src/i18n/index.ts index 0990ca5df..d7dae0060 100644 --- a/frontend/src/i18n/index.ts +++ b/frontend/src/i18n/index.ts @@ -30,6 +30,7 @@ export const SUPPORTED_LOCALES = { 'ja-JP': '日本語', 'hu-HU': 'Magyar', 'ar-SA': 'اَلْعَرَبِيَّةُ', + 'fa-IR': 'فارسی', 'sl-SI': 'Slovenščina', 'pt-BR': 'Português Brasileiro', 'hr-HR': 'Hrvatski', @@ -41,6 +42,7 @@ export const SUPPORTED_LOCALES = { 'fi-FI': 'Suomi', 'he-IL': 'עִבְרִית', 'sv-SE': 'Svenska', + 'el-GR': 'Ελληνικά', // IMPORTANT: Also add new languages to useDayjsLanguageSync // IMPORTANT: Also add new languages to pkg/i18n/i18n.go } as const @@ -51,7 +53,7 @@ export const DEFAULT_LANGUAGE: SupportedLocale= 'en' export type ISOLanguage = string -const RTL_LANGUAGES = ['ar-SA', 'he-IL'] as const +const RTL_LANGUAGES = ['ar-SA', 'he-IL', 'fa-IR'] as const export function isRTLLanguage(locale: SupportedLocale): boolean { return RTL_LANGUAGES.includes(locale as typeof RTL_LANGUAGES[number]) diff --git a/frontend/src/i18n/lang/ar-SA.json b/frontend/src/i18n/lang/ar-SA.json index f9338b33a..26e0d5ef2 100644 --- a/frontend/src/i18n/lang/ar-SA.json +++ b/frontend/src/i18n/lang/ar-SA.json @@ -284,8 +284,7 @@ "default": "افتراضي", "month": "شهر", "day": "يوم", - "hour": "ساعة", - "range": "نطاق التاريخ" + "hour": "ساعة" }, "table": { "title": "جدول", @@ -294,7 +293,6 @@ "kanban": { "title": "Kanban", "limit": "الحد: {limit}", - "noLimit": "غير محدد", "doneBucket": "حافظة المهام المكتملة", "doneBucketHint": "سيتم تلقائياً وضع علامة مكتمل على جميع المهام التي تم نقلها إلى هذه الحافظة.", "doneBucketHintExtended": "سيتم وضع علامة مكتمل على جميع المهام التي تم نقلها إلى حافظة المهام المكتملة. كما سيتم نقل جميع المهام المكتملة من أماكن أخرى.", diff --git a/frontend/src/i18n/lang/bg-BG.json b/frontend/src/i18n/lang/bg-BG.json index 28b2c292f..0a4458c07 100644 --- a/frontend/src/i18n/lang/bg-BG.json +++ b/frontend/src/i18n/lang/bg-BG.json @@ -314,8 +314,7 @@ "default": "По подразбиране", "month": "Месец", "day": "Ден", - "hour": "Час", - "range": "Времеви диапазон" + "hour": "Час" }, "table": { "title": "Таблица", @@ -324,7 +323,6 @@ "kanban": { "title": "Канбан", "limit": "Лимит: {limit}", - "noLimit": "Не е зададен", "doneBucket": "Колона за завършени", "doneBucketHint": "Всички задачи, преместени в тази колона, автоматично ще бъдат маркирани като завършени.", "doneBucketHintExtended": "Всички задачи, преместени в колоната за завършени, ще бъдат автоматично маркирани като завършени. Всички задачи, маркирани като завършени от другаде, също ще бъдат преместени тук.", diff --git a/frontend/src/i18n/lang/cs-CZ.json b/frontend/src/i18n/lang/cs-CZ.json index b6ca3a20b..6a8d3c523 100644 --- a/frontend/src/i18n/lang/cs-CZ.json +++ b/frontend/src/i18n/lang/cs-CZ.json @@ -383,7 +383,6 @@ "month": "Měsíc", "day": "Den", "hour": "Hodina", - "range": "Časové období", "chartLabel": "Projektový Ganttův diagram", "taskBarsForRow": "Chlívky pro řádek {rowId}", "taskBarLabel": "Úkol: {task}. Od {startDate} do {endDate}. {dateType}. Klikněte pro úpravu, přetáhněte pro přesun.", @@ -412,7 +411,6 @@ "kanban": { "title": "Kanban", "limit": "Limit: {limit}", - "noLimit": "Nenastaveno", "doneBucket": "Sloupec \"Hotovo\"", "doneBucketHint": "Všechny úkoly přesunuté do tohoto sloupce budou automaticky označeny jako dokončené.", "doneBucketHintExtended": "Všechny úkoly přesunuté do sloupce \"Hotovo\" budou označeny jako dokončené automaticky. Všechny úkoly označené jako dokončené jinde sem budou přesunuty také.", diff --git a/frontend/src/i18n/lang/de-DE.json b/frontend/src/i18n/lang/de-DE.json index 4d88eaa68..a87ae711f 100644 --- a/frontend/src/i18n/lang/de-DE.json +++ b/frontend/src/i18n/lang/de-DE.json @@ -172,6 +172,7 @@ "yyyy/mm/dd": "JJJJ/MM/TT" }, "timeFormat": "Zeitformat", + "timeTrackingDefaultStart": "Startzeit für die Zeiterfassung", "timeFormatOptions": { "12h": "12 Stunden (AM/PM)", "24h": "24 Stunden (HH:mm)" @@ -219,6 +220,13 @@ "usernameIs": "Dein Anmeldename für CalDAV lautet: {0}", "apiTokenHint": "Du kannst auch ein API-Token mit CalDAV-Berechtigung verwenden. Erstelle eins unter {link}." }, + "feeds": { + "title": "Atom-Feed", + "howTo": "Du kannst deine Vikunja-Benachrichtigungen von jedem Atom-kompatiblen Feed-Reader abonnieren. Benutze die folgende URL:", + "usernameIs": "Dein Anmeldename für das Feed lautet: {0}", + "apiTokenHint": "Authentifiziere dich mit einem API-Token mit der {scope} Berechtigung. Erstellen eins unter {link}.", + "tokenTitle": "Atom-Feed" + }, "avatar": { "title": "Avatar", "initials": "Initialen", @@ -385,6 +393,7 @@ "title": "Dupliziere dieses Projekt", "label": "Duplizieren", "text": "Wähle ein übergeordnetes Projekt aus, welches das duplizierte Projekt enthalten soll:", + "shares": "Freigaben kopieren (Benutzer:innen, Teams und Linkfreigaben)", "success": "Das Projekt wurde erfolgreich dupliziert." }, "edit": { @@ -463,7 +472,6 @@ "month": "Monat", "day": "Tag", "hour": "Stunde", - "range": "Zeitraum", "chartLabel": "Projekt Gantt-Diagramm", "taskBarsForRow": "Aufgabenleisten für Zeile {rowId}", "taskBarLabel": "Aufgabe: {task}. Von {startDate} bis {endDate}. {dateType}. Klicke zum Bearbeiten, ziehe zum Verschieben.", @@ -492,7 +500,6 @@ "kanban": { "title": "Kanban", "limit": "Limit: {limit}", - "noLimit": "Nicht gesetzt", "doneBucket": "Erledigt Spalte", "doneBucketHint": "Alle Aufgaben, die in diese Spalte verschoben werden, werden automatisch als erledigt markiert.", "doneBucketHintExtended": "Alle Aufgaben, die in die Erledigt Spalte verschoben wurden, werden automatisch als erledigt markiert. Aufgaben, die in einer anderen Spalte als Erledigt markiert wurden, werden auch in diese Spalte verschoben.", @@ -776,7 +783,10 @@ "closeDialog": "Dialog schließen", "closeQuickActions": "Schnellaktionen schließen", "skipToContent": "Überspringen und zum Hauptinhalt gehen", - "sortBy": "Sortieren nach" + "sortBy": "Sortieren nach", + "dateRange": "Zeitraum", + "notSet": "Nicht festgelegt", + "user": "Benutzer:in" }, "input": { "projectColor": "Projektfarbe", @@ -855,6 +865,7 @@ "date": "Datum", "ranges": { "today": "Heute", + "tomorrow": "Morgen", "thisWeek": "Diese Woche", "restOfThisWeek": "Der Rest dieser Woche", "nextWeek": "Nächste Woche", @@ -985,6 +996,7 @@ "repeatAfter": "Wiederholung setzen", "percentDone": "Fortschritt einstellen", "attachments": "Anhänge hinzufügen", + "timeTracking": "Zeit erfassen", "relatedTasks": "Beziehung hinzufügen", "moveProject": "Verschieben", "duplicate": "Duplizieren", @@ -1058,7 +1070,10 @@ "addedSuccess": "Der Kommentar wurde erfolgreich hinzugefügt.", "permalink": "Permalink zu diesem Kommentar kopieren", "sortNewestFirst": "Neueste zuerst", - "sortOldestFirst": "Älteste zuerst" + "sortOldestFirst": "Älteste zuerst", + "reply": "Antworten", + "jumpToOriginal": "Zum ursprünglichen Kommentar springen", + "deletedComment": "gelöschter Kommentar" }, "mention": { "noUsersFound": "Keine Nutzer:innen gefunden" @@ -1323,7 +1338,8 @@ "none": "Du hast keine Benachrichtigungen. Einen schönen Tag noch!", "explainer": "Benachrichtigungen werden hier angezeigt, wenn Aktionen für Projekte oder Aufgaben, die du abonniert hast, ausgeführt werden.", "markAllRead": "Alle Benachrichtigungen als gelesen markieren", - "markAllReadSuccess": "Alle Benachrichtigungen erfolgreich als gelesen markiert." + "markAllReadSuccess": "Alle Benachrichtigungen erfolgreich als gelesen markiert.", + "subscribeFeed": "Benachrichtigungen über Atom-Feed abonnieren" }, "quickActions": { "notLoggedIn": "Bitte melde dich zuerst im Hauptfenster von Vikunja an.", @@ -1450,6 +1466,32 @@ "frontendVersion": "Frontend-Version: {version}", "apiVersion": "API-Version: {version}" }, + "timeTracking": { + "title": "Zeiterfassung", + "stop": "Timer stoppen", + "logTime": "Zeit buchen", + "editEntry": "Eintrag bearbeiten", + "form": { + "task": "Aufgabe", + "taskSearch": "Nach einer Aufgabe suchen…", + "commentPlaceholder": "Woran hast du gearbeitet?", + "save": "Speichern", + "startTimer": "Timer starten", + "update": "Eintrag aktualisieren", + "smartFill": "Vom letzten Eintrag ausfüllen" + }, + "list": { + "emptyTask": "Noch keine Zeit für diese Aufgabe getrackt.", + "emptyFiltered": "Keine Zeiterfassung innerhalb der ausgewählten Filter.", + "total": "Gesamt", + "time": "Uhrzeit", + "duration": "Dauer" + }, + "browse": { + "selectRange": "Bereich wählen", + "userSearch": "Nach einer:m Benutzer:in suchen…" + } + }, "time": { "units": { "seconds": "Sekunde|Sekunden", diff --git a/frontend/src/i18n/lang/de-swiss.json b/frontend/src/i18n/lang/de-swiss.json index c20af1643..b95b8ff20 100644 --- a/frontend/src/i18n/lang/de-swiss.json +++ b/frontend/src/i18n/lang/de-swiss.json @@ -172,6 +172,7 @@ "yyyy/mm/dd": "JJJJ/MM/TT" }, "timeFormat": "Zeitformat", + "timeTrackingDefaultStart": "Startzeit für die Zeiterfassung", "timeFormatOptions": { "12h": "12 Stunden (AM/PM)", "24h": "24 Stunden (HH:mm)" @@ -219,6 +220,13 @@ "usernameIs": "Dein Anmeldename für CalDAV lautet: {0}", "apiTokenHint": "Du kannst auch ein API-Token mit CalDAV-Berechtigung verwenden. Erstelle eins unter {link}." }, + "feeds": { + "title": "Atom-Feed", + "howTo": "Du kannst deine Vikunja-Benachrichtigungen von jedem Atom-kompatiblen Feed-Reader abonnieren. Benutze die folgende URL:", + "usernameIs": "Dein Anmeldename für das Feed lautet: {0}", + "apiTokenHint": "Authentifiziere dich mit einem API-Token mit der {scope} Berechtigung. Erstellen eins unter {link}.", + "tokenTitle": "Atom-Feed" + }, "avatar": { "title": "Herr Der Elemente", "initials": "Initialä", @@ -385,6 +393,7 @@ "title": "Dupliziere dieses Projekt", "label": "Duplizieren", "text": "Wähle ein übergeordnetes Projekt aus, welches das duplizierte Projekt enthalten soll:", + "shares": "Freigaben kopieren (Benutzer:innen, Teams und Linkfreigaben)", "success": "Das Projekt wurde erfolgreich dupliziert." }, "edit": { @@ -463,7 +472,6 @@ "month": "Monat", "day": "Tag", "hour": "Stunde", - "range": "Zeitraum", "chartLabel": "Projekt Gantt-Diagramm", "taskBarsForRow": "Aufgabenleisten für Zeile {rowId}", "taskBarLabel": "Aufgabe: {task}. Von {startDate} bis {endDate}. {dateType}. Klicke zum Bearbeiten, ziehe zum Verschieben.", @@ -492,7 +500,6 @@ "kanban": { "title": "Kanban", "limit": "Limit: {limit}", - "noLimit": "Nicht gesetzt", "doneBucket": "Erledigt Spalte", "doneBucketHint": "Alle Aufgaben, die in diese Spalte verschoben werden, werden automatisch als erledigt markiert.", "doneBucketHintExtended": "Alle Aufgaben, die in die Erledigt Spalte verschoben wurden, werden automatisch als erledigt markiert. Aufgaben, die in einer anderen Spalte als Erledigt markiert wurden, werden auch in diese Spalte verschoben.", @@ -776,7 +783,10 @@ "closeDialog": "Dialog schließen", "closeQuickActions": "Schnellaktionen schließen", "skipToContent": "Überspringen und zum Hauptinhalt gehen", - "sortBy": "Sortieren nach" + "sortBy": "Sortieren nach", + "dateRange": "Zeitraum", + "notSet": "Nicht festgelegt", + "user": "Benutzer:in" }, "input": { "projectColor": "Projektfarbe", @@ -855,6 +865,7 @@ "date": "Datum", "ranges": { "today": "Heute", + "tomorrow": "Morgen", "thisWeek": "Diese Woche", "restOfThisWeek": "Der Rest dieser Woche", "nextWeek": "Nächste Woche", @@ -985,6 +996,7 @@ "repeatAfter": "Wiederholung setzen", "percentDone": "Fortschritt einstellen", "attachments": "Anhänge hinzufügen", + "timeTracking": "Zeit erfassen", "relatedTasks": "Beziehung hinzufügen", "moveProject": "Verschieben", "duplicate": "Duplizieren", @@ -1058,7 +1070,10 @@ "addedSuccess": "Din Kommentar isch erfolgriich hinzuegfüegt worde.", "permalink": "Permalink zu diesem Kommentar kopieren", "sortNewestFirst": "Neueste zuerst", - "sortOldestFirst": "Älteste zuerst" + "sortOldestFirst": "Älteste zuerst", + "reply": "Antworten", + "jumpToOriginal": "Zum ursprünglichen Kommentar springen", + "deletedComment": "gelöschter Kommentar" }, "mention": { "noUsersFound": "Keine Nutzer:innen gefunden" @@ -1323,7 +1338,8 @@ "none": "Du hesch kei neui Benachrichtunge. Heb e schös Tägli!", "explainer": "Benachrichtigungen werden hier angezeigt, wenn Aktionen für Projekte oder Aufgaben, die du abonniert hast, ausgeführt werden.", "markAllRead": "Alle Benachrichtigungen als gelesen markieren", - "markAllReadSuccess": "Alle Benachrichtigungen erfolgreich als gelesen markiert." + "markAllReadSuccess": "Alle Benachrichtigungen erfolgreich als gelesen markiert.", + "subscribeFeed": "Benachrichtigungen über Atom-Feed abonnieren" }, "quickActions": { "notLoggedIn": "Bitte melde dich zuerst im Hauptfenster von Vikunja an.", @@ -1450,6 +1466,32 @@ "frontendVersion": "Frontend-Version: {version}", "apiVersion": "API-Version: {version}" }, + "timeTracking": { + "title": "Zeiterfassung", + "stop": "Timer stoppen", + "logTime": "Zeit buchen", + "editEntry": "Eintrag bearbeiten", + "form": { + "task": "Aufgabe", + "taskSearch": "Nach einer Aufgabe suchen…", + "commentPlaceholder": "Woran hast du gearbeitet?", + "save": "Speichern", + "startTimer": "Timer starten", + "update": "Eintrag aktualisieren", + "smartFill": "Vom letzten Eintrag ausfüllen" + }, + "list": { + "emptyTask": "Noch keine Zeit für diese Aufgabe getrackt.", + "emptyFiltered": "Keine Zeiterfassung innerhalb der ausgewählten Filter.", + "total": "Gesamt", + "time": "Uhrzeit", + "duration": "Dauer" + }, + "browse": { + "selectRange": "Bereich wählen", + "userSearch": "Nach einer:m Benutzer:in suchen…" + } + }, "time": { "units": { "seconds": "Sekunde|Sekunden", diff --git a/frontend/src/i18n/lang/el-GR.json b/frontend/src/i18n/lang/el-GR.json new file mode 100644 index 000000000..14837735b --- /dev/null +++ b/frontend/src/i18n/lang/el-GR.json @@ -0,0 +1,1565 @@ +{ + "404": { + "title": "Δε βρέθηκε", + "text": "Η σελίδα που ζητήσατε δεν υπάρχει." + }, + "home": { + "welcomeNight": "Καληνύχτα {username}!", + "welcomeNightOwl": "Γεια σου νυχτερινή κουκουβάγια {username}", + "welcomeNightBurning": "Κάνουμε υπερωρίες {username};", + "welcomeNightQuiet": "Ώρες ησυχίας, {username}", + "welcomeNightLate": "Είναι αργά, {username}", + "welcomeNightMoonlit": "Παράλληλη απασχόληση, {username};", + "welcomeMorning": "Καλημέρα {username}!", + "welcomeMorningHey": "Ε {username}, είσαι έτοιμος να ξεκινήσουμε;", + "welcomeMorningFresh": "Νέα αρχή, {username}", + "welcomeMorningCoffee": "Καφέ και εργασίες, {username};", + "welcomeMorningRise": "Ανεβάζουμε τα μανίκια, {username}", + "welcomeMorningBack": "Καλώς ήρθες και πάλι, {username}", + "welcomeMondayFresh": "Φρέσκια εβδομάδα, {username}", + "welcomeTuesday": "Χαρούμενη Τρίτη, {username}", + "welcomeWednesdayMid": "Ήδη είμαστε μεσοβδόμαδα, {username}", + "welcomeThursday": "Σχεδόν εκεί, {username}", + "welcomeFridayPush": "Γερά την Παρασκευή, {username};", + "welcomeSaturday": "Κατάσταση Σαββατοκύριακου, {username}", + "welcomeSundaySession": "Κυριακάτικη δουλίτσα, {username};", + "welcomeDay": "Γεια σου {username}!", + "welcomeDayBack": "Πίσω και πάλι, {username}", + "welcomeDayFocus": "Ας συγκεντρωθούμε, {username}", + "welcomeDayKeepGoing": "Συνέχισε, {username}", + "welcomeDayWhatsNext": "Τι έχει μετά, {username};", + "welcomeDayGood": "Καλό απόγευμα, {username}", + "welcomeEvening": "Καλησπέρα {username}!", + "welcomeEveningWind": "Χαλαρώνεις, {username};", + "welcomeEveningReturns": "Ο/Η {username} επιστρέφει", + "welcomeEveningWrap": "Ήρθε η ώρα να κλείσουμε, {username};", + "welcomeEveningOneMore": "Κάτι ακόμη, {username};", + "welcomeEveningStill": "Ακόμη σε αυτό, {username};", + "lastViewed": "Προβλήθηκε τελευταία", + "addToHomeScreen": "Προσθέστε την εφαρμογή στην αρχική οθόνη σας για ταχύτερη πρόσβαση και βελτιωμένη εμπειρία.", + "goToOverview": "Μετάβαση στην επισκόπηση", + "project": { + "importText": "Εισαγάγετε τα έργα και τις εργασίες σας από άλλες υπηρεσίες στο Vikunja:", + "import": "Εισαγωγή των δεδομένων σας στο Vikunja" + } + }, + "demo": { + "title": "Αυτή η εγκατάσταση βρίσκεται σε λειτουργία επίδειξης. Μην τη χρησιμοποιείτε με πραγματικά δεδομένα!", + "everythingWillBeDeleted": "Όλα θα διαγράφονται σε τακτά χρονικά διαστήματα!", + "accountWillBeDeleted": "Ο λογαριασμός σας θα διαγραφεί, συμπεριλαμβανομένων όλων των έργων, των εργασιών και των συνημμένων που ενδέχεται να είχατε δημιουργήσει." + }, + "ready": { + "loading": "Το Vikunja φορτώνει…", + "errorOccured": "Παρουσιάστηκε σφάλμα:", + "checkApiUrl": "Παρακαλώ ελέγξτε αν η διεύθυνση URL του API είναι σωστή.", + "noApiUrlConfigured": "Δεν έχει ρυθμιστεί διεύθυνση url για το API. Παρακαλώ ορίστε μία παρακάτω:" + }, + "offline": { + "title": "Είστε εκτός σύνδεσης.", + "text": "Ελέγξτε τη σύνδεση δικτύου σας και προσπαθήστε ξανά." + }, + "user": { + "auth": { + "username": "Όνομα χρήστη", + "usernameEmail": "Όνομα Χρήστη ή Διεύθυνση Email", + "usernamePlaceholder": "π.χ. Φρειδερίκος", + "email": "Διεύθυνση email", + "emailPlaceholder": "π.χ. freiderikos{'@'}vikunja.io", + "password": "Κωδικός πρόσβασης", + "passwordPlaceholder": "π.χ. •••••••••••", + "forgotPassword": "Ξεχάσατε τον κωδικό σας πρόσβασης;", + "resetPassword": "Επαναφορά του κωδικού σας πρόσβασης", + "resetPasswordAction": "Να μου σταλεί σύνδεσμος επαναφοράς κωδικού πρόσβασης", + "resetPasswordSuccess": "Ελέγξτε τα εισερχόμενά σας! Θα πρέπει να έχετε λάβει ένα email με οδηγίες για την επαναφορά του κωδικού σας πρόσβασης.", + "confirmEmailSuccess": "Επιβεβαιώσατε με επιτυχία το email σας! Μπορείτε τώρα να συνδεθείτε.", + "totpTitle": "Κωδικός Ελέγχου Ταυτοποίησης Δύο Παραγόντων", + "totpPlaceholder": "π.χ. 123456", + "login": "Είσοδος", + "createAccount": "Δημιουργία λογαριασμού", + "loginWith": "Σύνδεση μέσω {provider}", + "authenticating": "Ταυτοποίηση…", + "openIdStateError": "Η κατάσταση δεν ταιριάζει, αρνούμαι να συνεχίσω!", + "openIdGeneralError": "Παρουσιάστηκε σφάλμα κατά την ταυτοποίηση μέσω τρίτου παρόχου.", + "openIdTotpRequired": "Ο λογαριασμός σας απαιτεί ταυτοποίηση δύο παραγόντων. Εισαγάγετε τον κωδικό σας μίας χρήσης και κάνετε είσοδο εκ νέου.", + "openIdTotpSubmit": "Συνέχεια", + "oauthMissingParams": "Λείπουν οι απαιτούμενες παράμετροι OAuth: {params}", + "oauthRedirectedToApp": "Έχετε μεταβεί στην εφαρμογή. Μπορείτε να κλείσετε τώρα αυτή την καρτέλα.", + "desktopTryDemo": "Δοκιμάστε το demo", + "desktopCustomServer": "Προσαρμοσμένη Διεύθυνση URL Διακομιστή", + "desktopCustomServerDescription": "Εισαγάγετε τη διεύθυνση URL του διακομιστή σας Vikunja για να ξεκινήσετε.", + "desktopWaitingForAuth": "Αναμονή για ταυτοποίηση…", + "desktopOAuthError": "Η ταυτοποίηση απέτυχε: {error}", + "logout": "Έξοδος", + "emailInvalid": "Παρακαλώ εισαγάγετε μια έγκυρη διεύθυνση email.", + "usernameRequired": "Παρακαλώ εισαγάγετε ένα όνομα χρήστη.", + "usernameMustNotContainSpace": "Το όνομα χρήστη δεν πρέπει να περιέχει κενούς χαρακτήρες.", + "usernameMustNotLookLikeUrl": "Το όνομα χρήστη δεν πρέπει να μοιάζει με URL.", + "passwordRequired": "Παρακαλώ εισαγάγετε ένα κωδικό πρόσβασης.", + "passwordNotMin": "Ο κωδικός πρόσβασης πρέπει να περιέχει τουλάχιστον 8 χαρακτήρες.", + "passwordNotMax": "Ο κωδικός πρόσβασης πρέπει να περιέχει το πολύ 72 χαρακτήρες.", + "showPassword": "Προβολή κωδικού πρόσβασης", + "hidePassword": "Απόκρυψη κωδικού πρόσβασης", + "noAccountYet": "Δεν έχετε ακόμη λογαριασμό;", + "alreadyHaveAnAccount": "Έχετε ήδη λογαριασμό;", + "remember": "Μείνετε συνδεδεμένος/η", + "registrationDisabled": "Η εγγραφή είναι απενεργοποιημένη.", + "passwordResetTokenMissing": "Λείπει το τεκμήριο επαναφοράς κωδικού πρόσβασης.", + "registrationFailed": "Παρουσιάστηκε σφάλμα κατά την εγγραφή. Παρακαλώ ελέγξτε τι εισαγάγατε και προσπαθήστε εκ νέου." + }, + "settings": { + "bots": { + "title": "Χρήστες bot", + "description": "Οι χρήστες bot είναι μόνο API-χρήστες που εσείς κατέχετε. Μπορούν να προστεθούν σε έργα, ανατεθειμένες εργασίες και ταυτοποιούνται με τεκμήρια API. Δεν μπορούν να συνδεθούν διαδραστικά.", + "namePlaceholder": "Ο Βοηθός μου", + "create": "Δημιουργία bot", + "enable": "Ενεργοποίηση", + "badge": "Bot", + "delete": { + "header": "Διαγραφή αυτού του χρήστη bot", + "text1": "Είστε βέβαιοι ότι θέλετε να διαγράψετε τον χρήστη bot \"{username}\";", + "text2": "Αυτό είναι μη αναστρέψιμο. Οποιαδήποτε τεκμήρια API ανήκουν σε αυτό το bot θα ανακληθούν." + } + }, + "title": "Ρυθμίσεις", + "newPasswordTitle": "Ενημερώστε Τον Κωδικό σας Πρόσβασης", + "newPassword": "Νέος κωδικός πρόσβασης", + "currentPassword": "Τρέχων κωδικός πρόσβασης", + "currentPasswordPlaceholder": "Ο τρέχων κωδικός σας πρόσβασης", + "passwordUpdateSuccess": "Ο κωδικός πρόσβασης ενημερώθηκε με επιτυχία.", + "updateEmailTitle": "Ενημέρωση Της Διεύθυνσης Email σας", + "updateEmailNew": "Νέα διεύθυνση email", + "updateEmailSuccess": "Η διεύθυνση email σας ενημερώθηκε με επιτυχία. Έχει σταλεί ένας σύνδεσμος για να την επιβεβαιώσετε.", + "general": { + "title": "Γενικές Ρυθμίσεις", + "name": "Το Όνομά Μου", + "newName": "Το νέο όνομα", + "savedSuccess": "Οι ρυθμίσεις ενημερώθηκαν με επιτυχία.", + "emailReminders": "Να μου στέλνονται υπενθυμίσεις για τις εργασίες μέσω email", + "overdueReminders": "Να μου στέλνεται μια σύνοψη των καθυστερημένων και μη ολοκληρωμένων εργασιών κάθε μέρα", + "discoverableByName": "Να επιτρέπεται σε άλλους χρήστες να με προσθέτουν ως μέλος σε ομάδες ή έργα όταν ψάχνουν το όνομά μου", + "discoverableByEmail": "Να επιτρέπεται σε άλλους χρήστες να με προσθέτουν ως μέλος σε ομάδες ή έργα όταν ψάχνουν την πλήρη διεύθυνση email μου", + "playSoundWhenDone": "Να αναπαράγεται ένας ήχος όταν σημειώνονται οι εργασίες ως ολοκληρωμένες", + "allowIconChanges": "Να εμφανίζονται ειδικά λογότυπα σε συγκεκριμένες χρονικές περιόδους", + "alwaysShowBucketTaskCount": "Να εμφανίζεται πάντα ο αριθμός εργασιών στους κάδους Kanban", + "defaultTaskRelationType": "Προκαθορισμένος τύπος σχετιζόμενης εργασίας", + "weekStart": "Η εβδομάδα ξεκινάει την", + "weekStartSunday": "Κυριακή", + "weekStartMonday": "Δευτέρα", + "weekStartTuesday": "Τρίτη", + "weekStartWednesday": "Τετάρτη", + "weekStartThursday": "Πέμπτη", + "weekStartFriday": "Παρασκευή", + "weekStartSaturday": "Σάββατο", + "language": "Γλώσσα", + "defaultProject": "Προκαθορισμένο έργο", + "defaultView": "Προκαθορισμένη προβολή", + "timezone": "Ώρα ζώνης", + "overdueTasksRemindersTime": "Χρόνος υπενθύμισης με email εκπρόθεσμων εργασιών", + "quickAddDefaultReminders": "Προκαθορισμένες υπενθυμίσεις για γρήγορη προσθήκη", + "quickAddDefaultRemindersDescription": "Οι υπενθυμίσεις αυτές θα προστίθενται αυτόματα σε κάθε εργασία που δημιουργείται μέσω του quick add magic και περιέχει ημερομηνία παράδοσης.", + "quickAddDefaultRemindersHint": "Προσθέστε μία ή περισσότερες υπενθυμίσεις σχετικά με την ημερομηνία παράδοσης της εργασίας. Αφήστε κενό για απενεργοποίηση.", + "filterUsedOnOverview": "Αποθηκευμένο φίλτρο που χρησιμοποιείται στη σελίδα επισκόπησης", + "showLastViewed": "Εμφάνιση των τελευταία προβεβλημένων έργων στη σελίδα επισκόπησης", + "minimumPriority": "Ελάχιστη προτεραιότητα για ορατή εργασία", + "dateDisplay": "Μορφή εμφάνισης ημερομηνίας", + "dateDisplayOptions": { + "relative": "Σχετική (πχ. πριν από 3 ημέρες)", + "mm-dd-yyyy": "ΜΜ-ΗΗ-ΕΕΕΕ", + "dd-mm-yyyy": "ΗΗ-ΜΜ-ΕΕΕΕ", + "yyyy-mm-dd": "ΕΕΕΕ-ΜΜ-ΗΗ", + "mm/dd/yyyy": "ΜΜ/ΗΗ/ΕΕΕΕ", + "dd/mm/yyyy": "ΗΗ/ΜΜ/ΕΕΕΕ", + "yyyy/mm/dd": "ΕΕΕΕ/ΜΜ/ΗΗ" + }, + "timeFormat": "Μορφή ώρας", + "timeTrackingDefaultStart": "Ώρα έναρξης παρακολούθησης χρόνου με έξυπνο-γέμισμα", + "timeFormatOptions": { + "12h": "12 ώρες (ΠΜ/ΜΜ)", + "24h": "24 ώρες (ΩΩ:ΛΛ)" + }, + "externalUserNameChange": "Το όνομά σας διαχειρίζεται από τον πάροχό σας σύνδεσης ({provider}). Για να το αλλάξετε, ενημερώστε το εκεί." + }, + "sections": { + "personalInformation": "Προσωπικές Πληροφορίες", + "taskAndNotifications": "Έργα & Εργασίες", + "privacy": "Ιδιωτικότητα", + "localization": "Τοπική Προσαρμογή", + "appearance": "Εμφάνιση & Συμπεριφορά", + "desktop": "Εφαρμογή Υπολογιστή" + }, + "desktop": { + "quickEntryShortcut": "Συντόμευση για Γρήγορη Είσοδο", + "shortcutRecorderPlaceholder": "Κάντε κλικ για να ορίσετε συντόμευση", + "shortcutRecorderRecording": "Πατήστε ένα συνδυασμό πλήκτρων…" + }, + "totp": { + "title": "Ταυτοποίηση Δύο Παραγόντων", + "enroll": "Εγγραφή", + "finishSetupPart1": "Για να ολοκληρώσετε τις ρυθμίσεις σας, χρησιμοποιήστε αυτόν τον μυστικό κωδικό στην εφαρμογή TOTP (Google Authenticator ή παρόμοιο):", + "finishSetupPart2": "Μετά από αυτό, εισαγάγετε έναν κωδικό από την εφαρμογή σας παρακάτω.", + "scanQR": "Εναλλακτικά μπορείτε να σαρώσετε αυτόν τον κωδικό QR:", + "passcode": "Κωδικός", + "passcodePlaceholder": "Ένας κωδικός που δημιουργείται από την εφαρμογή σας TOTP", + "confirmNotice": "Μετά την ενεργοποίηση της ταυτοποίησης δύο παραγόντων, θα αποσυνδεθείτε από όλες τις συνεδρίες και θα πρέπει να κάνετε είσοδο εκ νέου.", + "setupSuccess": "Έχετε παραμετροποιήσει με επιτυχία τον έλεγχο ταυτοποίησης δύο παραγόντων!", + "enterPassword": "Παρακαλώ Εισαγάγετε Τον Κωδικό Σας", + "disable": "Απενεργοποίηση ταυτοποίησης δύο παραγόντων", + "confirmSuccess": "Έχετε ενεργοποιήσει με επιτυχία τον έλεγχο ταυτοποίησης δύο παραγόντων!", + "disableSuccess": "Ο έλεγχος ταυτοποίησης δύο παραγόντων απενεργοποιήθηκε με επιτυχία." + }, + "caldav": { + "title": "CalDAV", + "howTo": "Μπορείτε να συνδέσετε το Vikunja με πελάτες CalDAV για να βλέπετε και να διαχειρίζεστε όλες τις εργασίες σας από διαφορετικά προγράμματα πελάτες. Εισαγάγετε αυτήν τη διεύθυνση URL στο πρόγραμμα πελάτη σας:", + "more": "Περισσότερες πληροφορίες για το CalDAV στο Vikunja", + "tokens": "Τεκμήρια CalDAV", + "tokensHowTo": "Για ταυτοποίηση CalDAV μπορείτε να χρησιμοποιήσετε είτε τον κανονικό κωδικό πρόσβασής σας είτε ένα τεκμήριο CalDAV.", + "createToken": "Δημιουργία τεκμηρίου CalDAV", + "tokenCreated": "Αυτό είναι το νέο σας τεκμήριο: {token}", + "wontSeeItAgain": "Γράψτε το ή αποθηκεύστε το με ασφάλεια — δε θα είστε σε θέση να το ξαναδείτε.", + "mustUseToken": "Θα πρέπει να δημιουργήσετε ένα τεκμήριο CalDAV για να χρησιμοποιήσετε CalDAV με οποιοδήποτε τρίτο πρόγραμμα πελάτη. Εισαγάγετε το τεκμήριο στο πεδίο κωδικού πρόσβασης του προγράμματος πελάτη σας.", + "usernameIs": "Το όνομα χρήστη σας για το CalDAV είναι: {0}", + "apiTokenHint": "Μπορείτε επίσης να χρησιμοποιήσετε ένα τεκμήριο API με άδεια CalDAV. Δημιουργήστε ένα στο {link}." + }, + "feeds": { + "title": "Ροή Atom", + "howTo": "Μπορείτε να εγγραφείτε στις ειδοποιήσεις σας στο Vikunja από οποιονδήποτε αναγνώστη ροής συμβατό με Atom. Χρησιμοποιήστε την ακόλουθη διεύθυνση:", + "usernameIs": "Το όνομα χρήστη σας για τη ροή είναι: {0}", + "apiTokenHint": "Ταυτοποιηθείτε με τεκμήριο API που έχει την άδεια {scope}. Δημιουργήστε ένα στο {link}.", + "tokenTitle": "Ροή Atom" + }, + "avatar": { + "title": "Άβαταρ", + "initials": "Αρχικά", + "gravatar": "Gravatar", + "marble": "Marble", + "upload": "Ανέβασμα", + "uploadAvatar": "Ανέβασμα Άβαταρ", + "statusUpdateSuccess": "Η κατάσταση του άβαταρ ενημερώθηκε με επιτυχία!", + "setSuccess": "Το avatar ορίστηκε με επιτυχία!", + "ldap": "Το άβατάρ σας συγχρονίζεται αυτόματα από την υπηρεσία καταλόγου του οργανισμού σας (LDAP). Μπορείτε να ρωτήσετε την ομάδα IT σας για πληροφορίες σχετικά με το πώς να το αλλάξετε.", + "openid": "Το άβατάρ σας συγχρονίζεται αυτόματα από τον πάροχό σας σύνδεσης ({provider}). Για να το αλλάξετε, ενημερώστε το εκεί." + }, + "quickAddMagic": { + "title": "Κατάσταση Quick Add Magic", + "disabled": "Απενεργοποιημένο", + "todoist": "Todoist", + "vikunja": "Vikunja" + }, + "appearance": { + "title": "Συνδυασμός Χρωμάτων", + "colorScheme": { + "light": "Φωτεινό", + "system": "Συστήματος", + "dark": "Σκοτεινό" + } + }, + "backgroundBrightness": { + "title": "Φωτεινότητα φόντου" + }, + "webhooks": { + "title": "Ειδοποιήσεις Webhook", + "description": "Ρύθμιση των διευθύνσεων webhook URL για τη αποδοχή αιτήσεων τύπου POST όταν συμβαίνουν συμβάντα υπενθυμίσεων ή εκπρόθεσμων γεγονότων. Αυτά τα webhook θα λαμβάνουν συμβάντα από όλα τα έργα." + }, + "apiTokens": { + "title": "Τεκμήρια API", + "general": "Τα τεκμήρια API σας επιτρέπουν να χρησιμοποιείτε το API του Vikunja χωρίς χρήση διαπιστευτηρίων χρήστη.", + "apiDocs": "Ελέγξτε την τεκμηρίωση για το api", + "createAToken": "Δημιουργία ενός τεκμηρίου", + "createToken": "Δημιουργία τεκμηρίου", + "30d": "30 Ημέρες", + "60d": "60 Ημέρες", + "90d": "90 Ημέρες", + "permissionExplanation": "Οι άδειες σας επιτρέπουν να ορίσετε την εμβέλεια του τι επιτρέπεται να κάνει ένα τεκμήριο api.", + "titleRequired": "Ο τίτλος είναι απαιτούμενος", + "permissionRequired": "Παρακαλώ επιλέξτε τουλάχιστον μια άδεια από τη λίστα.", + "expired": "Το τεκμήριο έχει λήξει {ago}.", + "tokenCreatedSuccess": "Αυτό είναι το νέο σας τεκμήριο api: {token}", + "tokenCreatedNotSeeAgain": "Αποθηκεύστε το σε μια ασφαλή τοποθεσία, διαφορετικά δε θα το ξαναδείτε!", + "presets": { + "title": "Γρήγορες προεπιλογές", + "readOnly": "Μόνο για ανάγνωση", + "tasks": "Διαχείριση εργασιών", + "projects": "Διαχείριση έργου", + "fullAccess": "Πλήρης πρόσβαση" + }, + "delete": { + "header": "Διαγραφή αυτού του τεκμηρίου", + "text1": "Είστε σίγουροι ότι θέλετε να διαγράψετε το τεκμήριο\"{token}\";", + "text2": "Αυτό θα ανακαλέσει την πρόσβαση σε όλες τις εφαρμογές ή ενσωματώσεις που το χρησιμοποιούν. Δεν μπορείτε να το αναιρέσετε." + }, + "attributes": { + "title": "Τίτλος", + "titlePlaceholder": "Εισαγάγετε έναν τίτλο που θα αναγνωρίζετε στο μέλλον", + "expiresAt": "Λήγει στις", + "permissions": "Άδειες" + } + }, + "sessions": { + "title": "Συνεδρίες", + "description": "Αυτές είναι όλες οι συσκευές που είναι συνδεδεμένες στον λογαριασμό σας. Μπορείτε να ανακαλέσετε οποιαδήποτε συνεδρία για να αποσυνδεθεί η συσκευή. Ενδέχεται να χρειαστούν έως και 10 λεπτά ώσπου η ανάκληση να τεθεί σε πλήρη ισχύ.", + "deviceInfo": "Συσκευή", + "ipAddress": "Διεύθυνση IP", + "lastActive": "Τελευταία Δραστηριότητα", + "current": "Τρέχουσα συνεδρία", + "delete": { + "header": "Ανάκληση συνεδρίας", + "text": "Είστε βέβαιοι ότι θέλετε να ανακαλέσετε αυτή τη συνεδρία; Η συσκευή θα αποσυνδεθεί. Μπορεί να χρειαστούν έως και 10 λεπτά για να λήξει πλήρως η συνεδρία." + }, + "deleteSuccess": "Η συνεδρία ανακλήθηκε επιτυχώς. Μπορεί να χρειαστούν έως και 10 λεπτά για να λήξει πλήρως.", + "noOtherSessions": "Δεν υπάρχουν άλλες ενεργές συνεδρίες." + } + }, + "deletion": { + "title": "Διαγραφή του λογαριασμού σας Vikunja", + "text1": "Η διαγραφή του λογαριασμού σας είναι μόνιμη και δεν μπορεί να αναιρεθεί. Θα διαγράψουμε όλα τα έργα, τις εργασίες σας και όλα όσα σχετίζονται με αυτόν.", + "text2": "Για να συνεχίσετε, παρακαλώ εισαγάγετε τον κωδικό σας πρόσβασης. Θα λάβετε ένα email με περαιτέρω οδηγίες.", + "text3": "Για να συνεχίσετε, πατήστε το παρακάτω πλήκτρο. Θα λάβετε ένα email με περαιτέρω οδηγίες.", + "confirm": "Διαγραφή του λογαριασμού μου", + "requestSuccess": "Το αίτημα ήταν επιτυχές. Θα λάβετε ένα email με περαιτέρω οδηγίες.", + "passwordRequired": "Παρακαλώ εισαγάγετε τον κωδικό σας πρόσβασης.", + "confirmSuccess": "Έχετε επιβεβαιώσει με επιτυχία τη διαγραφή του λογαριασμού σας. Ο λογαριασμός σας θα διαγραφεί σε τρεις ημέρες.", + "scheduled": "Ο λογαριασμός σας στο Vikunja θα διαγραφεί στις {date} ({dateSince}).", + "scheduledCancel": "Για να ακυρώσετε τη διαγραφή του λογαριασμού σας, κάντε κλικ εδώ.", + "scheduledCancelText": "Για να ακυρώσετε τη διαγραφή του λογαριασμού σας, εισαγάγετε τον κωδικό σας πρόσβασης παρακάτω:", + "scheduledCancelButton": "Για να ακυρώσετε τη διαγραφή του λογαριασμού σας, πατήστε στο παρακάτω πλήκτρο:", + "scheduledCancelConfirm": "Ακύρωση της διαγραφής του λογαριασμού μου", + "scheduledCancelSuccess": "Δε θα διαγράψουμε τον λογαριασμό σας." + }, + "export": { + "title": "Εξαγωγή των δεδομένων σας από το Vikunja", + "description": "Μπορείτε να ζητήσετε αντίγραφο όλων των δεδομένων σας στο Vikunja. Αυτό περιλαμβάνει Έργα, Εργασίες και όλα όσα σχετίζονται με αυτά. Μπορείτε να εισαγάγετε αυτά τα δεδομένα σε οποιοδήποτε στιγμιότυπο Vikunja μέσω της λειτουργίας μετανάστευσης.", + "descriptionPasswordRequired": "Παρακαλώ εισαγάγετε τον κωδικό σας πρόσβασης για να συνεχίσετε:", + "request": "Ζητήστε αντίγραφο των δεδομένων σας στο Vikunja", + "success": "Έχετε αιτηθεί με επιτυχία τα δεδομένα σας στο Vikunja! Θα σας στείλουμε ένα email μόλις είναι έτοιμα για λήψη.", + "downloadTitle": "Κάντε λήψη των εξαχθέντων δεδομένων σας στο Vikunja", + "ready": "Η εξαγωγή σας είναι έτοιμη για λήψη. Μπορείτε να την κατεβάσετε μέχρι {0}.", + "requestNew": "Αίτηση για άλλη εξαγωγή" + } + }, + "project": { + "archivedMessage": "Το έργο είναι αρχειοθετημένο. Δεν είναι δυνατό να δημιουργήσετε νέες εργασίες ή να επεξεργαστείτε εργασίες σε αυτό.", + "archived": "Αρχειοθετημένα", + "showArchived": "Προβολή αρχειοθετημένων", + "title": "Τίτλος", + "color": "Χρώμα", + "projects": "Έργα", + "parent": "Γονικό έργο", + "search": "Πληκτρολογήστε για αναζήτηση έργου…", + "searchSelect": "Κάντε κλικ ή πατήστε Enter για να επιλέξετε αυτό το έργο", + "shared": "Κοινόχρηστα Έργα", + "noDescriptionAvailable": "Δεν υπάρχει διαθέσιμη περιγραφή έργου.", + "inboxTitle": "Εισερχόμενα", + "favorite": "Να σημειωθεί το έργο ως αγαπημένο", + "unfavorite": "Αφαίρεση του έργου από τα αγαπημένα", + "openSettingsMenu": "Άνοιγμα μενού ρυθμίσεων έργου", + "description": "Περιγραφή έργου", + "favoriteDescription": "Το έργο έχει όλες τις εργασίες σημειωμένες στα αγαπημένα.", + "create": { + "header": "Νέο έργο", + "titlePlaceholder": "Ο τίτλος του έργου πηγαίνει εδώ…", + "addTitleRequired": "Παρακαλώ καθορίστε έναν τίτλο.", + "createdSuccess": "Το έργο δημιουργήθηκε με επιτυχία.", + "addProjectRequired": "Παρακαλώ καθορίστε ένα έργο ή επιλέξτε ένα έργο ως προεπιλεγμένο στις ρυθμίσεις." + }, + "archive": { + "title": "Αρχειοθέτηση του \"{project}\"", + "archive": "Αρχειοθέτηση του έργου", + "unarchive": "Κατάργηση αρχειοθέτησης του έργου", + "unarchiveText": "Θα μπορείτε να δημιουργήσετε εργασίες ή να τις επεξεργαστείτε.", + "archiveText": "Δε θα μπορείτε να επεξεργαστείτε το έργο ή να δημιουργήσετε εργασίες μέχρι να αναιρέσετε την αρχειοθέτησή του.", + "success": "Το έργο αρχειοθετήθηκε με επιτυχία." + }, + "background": { + "title": "Ορισμός φόντου έργου", + "remove": "Αφαίρεση φόντου", + "upload": "Επιλέξτε ένα φόντο από τον υπολογιστή σας", + "searchPlaceholder": "Αναζήτηση για φόντο…", + "poweredByUnsplash": "Με τη δύναμη του Unsplash", + "loadMore": "Φόρτωση περισσότερων φωτογραφιών", + "success": "Το φόντο έχει οριστεί με επιτυχία!", + "removeSuccess": "Το φόντο αφαιρέθηκε με επιτυχία!" + }, + "delete": { + "title": "Διαγραφή του \"{project}\"", + "header": "Διαγραφή του έργου", + "text1": "Είστε σίγουροι ότι θέλετε να διαγράψετε το έργο και όλο τα περιεχόμενό του;", + "text2": "Αυτό περιλαμβάνει όλες τις εργασίες και ΔΕΝ ΕΙΝΑΙ ΔΥΝΑΤΗ Η ΑΝΑΙΡΕΣΗ!", + "success": "Το έργο διαγράφηκε με επιτυχία.", + "tasksToDelete": "Αυτό θα αφαιρέσει οριστικά περίπου {count} εργασίες.", + "tasksAndChildProjectsToDelete": "Αυτό θα αφαιρέσει οριστικά περίπου {tasks} εργασίες και {projects} έργα.", + "noTasksToDelete": "Το έργο δεν περιέχει εργασίες, είναι ασφαλές να διαγραφεί." + }, + "duplicate": { + "title": "Αντιγραφή του έργου", + "label": "Αντιγραφή", + "text": "Επιλέξτε ένα γονικό έργο που θα περιλαμβάνει το αντίγραφο του έργου:", + "success": "Το έργο αντιγράφηκε με επιτυχία." + }, + "edit": { + "header": "Επεξεργασία Έργου", + "title": "Επεξεργασία του \"{project}\"", + "titlePlaceholder": "Ο τίτλος του έργου πηγαίνει εδώ…", + "identifierTooltip": "Το αναγνωριστικό έργου μπορεί να χρησιμοποιηθεί για τον μοναδικό προσδιορισμό μιας εργασίας μέσα από όλα τα έργα. Μπορείτε να το αφήσετε κενό για να το απενεργοποιήσετε.", + "identifier": "Αναγνωριστικό Έργου", + "identifierPlaceholder": "Το αναγνωριστικό έργου πηγαίνει εδώ…", + "description": "Περιγραφή", + "descriptionPlaceholder": "Εισαγάγετε μια περιγραφή για το έργο, πατήστε '/' για περισσότερες επιλογές…", + "color": "Χρώμα", + "success": "Το έργο ενημερώθηκε με επιτυχία." + }, + "share": { + "header": "Διαμοιρασμός του έργου", + "title": "Διαμοιρασμός του \"{project}\"", + "share": "Διαμοιρασμός", + "links": { + "title": "Σύνδεσμοι Διαμοιρασμού", + "what": "Τι είναι ένας σύνδεσμος διαμοιρασμού;", + "explanation": "Οι Σύνδεσμοι Διαμοιρασμού σάς επιτρέπουν να μοιράζεστε εύκολα ένα έργο με άλλους χρήστες που δεν έχουν λογαριασμό στο Vikunja.", + "create": "Δημιουργία συνδέσμου διαμοιρασμού", + "name": "Όνομα (προαιρετικό)", + "namePlaceholder": "π.χ. Lorem Ipsum", + "nameExplanation": "Όλες οι ενέργειες που εκτελούνται από το σύνδεσμο θα εμφανίζονται με το όνομα.", + "password": "Κωδικός πρόσβασης (προαιρετικό)", + "passwordExplanation": "Κατά την είσοδο, ο χρήστης θα πρέπει να εισαγάγει αυτόν τον κωδικό πρόσβασης.", + "noName": "Δεν ορίστηκε όνομα", + "remove": "Αφαίρεση ενός συνδέσμου διαμοιρασμού", + "removeText": "Είστε βέβαιοι ότι θέλετε να αφαιρέσετε αυτό το σύνδεσμο διαμοιρασμού; Δε θα είναι πλέον δυνατή η πρόσβαση σε αυτό το έργο με αυτό το σύνδεσμο διαμοιρασμού. Αυτή η ενέργεια δεν μπορεί να αναιρεθεί!", + "createSuccess": "Ο σύνδεσμος διαμοιρασμού δημιουργήθηκε με επιτυχία.", + "deleteSuccess": "Ο σύνδεσμος διαμοιρασμού διαγράφηκε με επιτυχία", + "view": "Προβολή", + "sharedBy": "Διαμοιρασμένο από {0}" + }, + "userTeam": { + "typeUser": "χρήστης | χρήστες", + "typeTeam": "ομάδα | ομάδες", + "shared": "Διαμοιρασμένα με αυτά {type}", + "you": "Εσύ", + "notShared": "Χωρίς διαμοιρασμό ακόμη με οποιονδήποτε {type}.", + "removeHeader": "Αφαίρεση ενός {type} από {sharable}", + "removeText": "Είστε βέβαιοι ότι θέλετε να αφαιρέσετε αυτό το {sharable} από το {type}; Αυτό δεν μπορεί να αναιρεθεί!", + "removeSuccess": "Το {sharable} αφαιρέθηκε με επιτυχία από το {type}.", + "addedSuccess": "Το {type} προστέθηκε με επιτυχία.", + "updatedSuccess": "Το {type} προστέθηκε με επιτυχία." + }, + "permission": { + "title": "Άδεια", + "read": "Μόνο για ανάγνωση", + "readWrite": "Ανάγνωση & εγγραφή", + "admin": "Διαχείριση" + }, + "attributes": { + "link": "Σύνδεσμος", + "delete": "Διαγραφή" + } + }, + "first": { + "title": "Πρώτη Προβολή" + }, + "list": { + "title": "Λίστα", + "add": "Προσθήκη", + "addPlaceholder": "Προσθήκη εργασίας…", + "empty": "Το έργο είναι επί του παρόντος κενό.", + "newTaskCta": "Δημιουργήστε μια εργασία.", + "editTask": "Επεξεργασία Εργασίας", + "sort": "Ταξινόμηση" + }, + "gantt": { + "title": "Gantt", + "size": "Μέγεθος", + "default": "Προεπιλογή", + "month": "Μήνας", + "day": "Ημέρα", + "hour": "Ώρα", + "chartLabel": "Γράφημα Gantt Έργου", + "taskBarsForRow": "Μπάρες εργασίας για τη γραμμή {rowId}", + "taskBarLabel": "Εργασία: {task}. Από {startDate} έως {endDate}. {dateType}. Κάντε κλικ για επεξεργασία, σύρετε για μετακίνηση.", + "scheduledDates": "Προγραμματισμένες ημερομηνίες", + "estimatedDates": "Εκτιμώμενες ημερομηνίες", + "resizeStartDate": "Επανακαθορισμός ημερομηνίας έναρξης για την εργασία {task}", + "resizeEndDate": "Επανακαθορισμός ημερομηνίας λήξης για την εργασία {task}", + "timelineHeader": "Κεφαλίδα χρονοδιαγράμματος με μήνες και ημέρες", + "monthsRow": "Γραμμή μηνών", + "daysRow": "Γραμμή ημερών", + "monthLabel": "Μήνας: {month}", + "dayLabel": "Ημέρα: {date}, {weekday}", + "dayLabelToday": "Ημέρα: {date}, {weekday}", + "taskAriaLabel": "Εργασία: {task}", + "taskAriaLabelById": "Εργασία {id}", + "partialDatesStart": "Μόνο ημερομηνία έναρξης (ανοικτή λήξη)", + "partialDatesEnd": "Μόνο ημερομηνία λήξης (ανοικτή έναρξη)", + "expandGroup": "Ανάπτυξη της ομάδας: {task}", + "collapseGroup": "Σύμπτυξη της ομάδας: {task}", + "toggleRelationArrows": "Εναλλαγή βελών συσχέτισης" + }, + "table": { + "title": "Πίνακας", + "columns": "Στήλες" + }, + "kanban": { + "title": "Kanban", + "limit": "Όριο: {limit}", + "doneBucket": "Κάδος για ολοκληρωμένα", + "doneBucketHint": "Όλες οι εργασίες που μετακινούνται σε αυτόν τον κάδο θα σημειώνονται αυτόματα ως ολοκληρωμένες.", + "doneBucketHintExtended": "Όλες οι εργασίες που μετακινούνται στον κάδο των ολοκληρωμένων θα σημειώνονται αυτόματα ως ολοκληρωμένες. Όλες οι εργασίες που σημειώνονται ως ολοκληρωμένες από αλλού επίσης θα μετακινηθούν.", + "doneBucketSavedSuccess": "Ο κάδος για τα ολοκληρωμένα αποθηκεύτηκε με επιτυχία.", + "defaultBucket": "Προκαθορισμένος κάδος", + "defaultBucketHint": "Όταν δημιουργείτε εργασίες χωρίς να καθορίσετε κάδο, θα προστίθενται σε αυτόν τον κάδο.", + "defaultBucketSavedSuccess": "Ο προκαθορισμένος κάδος αποθηκεύτηκε με επιτυχία.", + "deleteLast": "Δεν μπορείτε να αφαιρέσετε τον τελευταίο κάδο.", + "addTaskPlaceholder": "Εισαγάγετε τον τίτλο της νέας εργασίας…", + "addTask": "Προσθήκη εργασίας", + "addAnotherTask": "Προσθήκη άλλης εργασίας", + "addBucket": "Δημιουργία κάδου", + "addBucketPlaceholder": "Εισαγάγετε τον τίτλο του νέου κάδου…", + "deleteHeaderBucket": "Διαγραφή του κάδου", + "deleteBucketText1": "Είστε σίγουροι ότι θέλετε να διαγράψετε τον κάδο;", + "deleteBucketText2": "Αυτό δε θα διαγράψει καμία εργασία, αλλά θα τις μετακινήσει στον προκαθορισμένο κάδο.", + "deleteBucketSuccess": "Ο κάδος διαγράφηκε με επιτυχία.", + "bucketTitleSavedSuccess": "Ο τίτλος του κάδου αποθηκεύτηκε με επιτυχία.", + "bucketLimitSavedSuccess": "Το όριο κάδου αποθηκεύτηκε με επιτυχία.", + "collapse": "Σύμπτυξη του κάδου", + "bucketLimitReached": "Έχετε φτάσει το όριο του κάδου. Αφαιρέστε εργασίες ή αυξήστε το όριο για να προσθέσετε νέες.", + "bucketOptions": "Επιλογές κάδου" + }, + "pseudo": { + "favorites": { + "title": "Αγαπημένα" + } + }, + "webhooks": { + "title": "Webhook", + "targetUrl": "URL προορισμού", + "targetUrlInvalid": "Παρακαλώ εισαγάγετε μια έγκυρη διεύθυνση URL.", + "events": "Συμβάντα", + "eventsHint": "Επιλέξτε όλα τα συμβάντα για τα οποία το webhook θα λαμβάνει ενημερώσεις (μέσα στο τρέχον έργο).", + "mustSelectEvents": "Πρέπει να επιλέξετε τουλάχιστον ένα συμβάν.", + "delete": "Διαγραφή του webhook", + "deleteText": "Είστε σίγουροι ότι θέλετε να διαγράψετε το webhook; Οι εξωτερικοί στόχοι δε θα λαμβάνουν πλέον ενημερώσεις για τα συμβάντα του.", + "deleteSuccess": "Το webhook διαγράφηκε με επιτυχία.", + "create": "Δημιουργία webhook", + "secret": "Μυστικό", + "basicauthuser": "Χρήστης για Βασική Ταυτοποίηση", + "basicauthpassword": "Κωδικός πρόσβασης για Βασική Ταυτοποίηση", + "basicauthlink": "Χρήση της Βασικής Ταυτοποίησης;", + "secretHint": "Αν δοθεί, όλες οι αιτήσεις προς τη διεύθυνση URL προορισμού του webhook θα υπογράφονται με χρήση του HMAC.", + "secretDocs": "Δείτε την τεκμηρίωση για περισσότερες πληροφορίες σχετικά με τη χρήση των μυστικών." + }, + "views": { + "header": "Επεξεργασία προβολών", + "title": "Τίτλος", + "actions": "Ενέργειες", + "kind": "Είδος", + "bucketConfigMode": "Λειτουργία διαμόρφωσης κάδου", + "bucketConfig": "Διαμόρφωση κάδου", + "bucketConfigManual": "Εγχειρίδιο", + "filter": "Φίλτρο", + "create": "Δημιουργία προβολής", + "createSuccess": "Η προβολή δημιουργήθηκε με επιτυχία.", + "titleRequired": "Παρακαλώ καθορίστε έναν τίτλο.", + "delete": "Διαγραφή της προβολής", + "deleteText": "Είστε σίγουροι ότι θέλετε να αφαιρέσετε την προβολή; Δε θα είναι πλέον δυνατό να τη χρησιμοποιήσετε για να δείτε εργασίες σε αυτό το έργο. Αυτή η ενέργεια δε θα διαγράψει καμία εργασία. Αυτή η ενέργεια είναι μη αναιρέσιμη!", + "deleteSuccess": "Η προβολή διαγράφηκε με επιτυχία.", + "onlyAdminsCanEdit": "Μόνο οι διαχειριστές του έργου μπορούν να επεξεργαστούν προβολές.", + "updateSuccess": "Η προβολή ενημερώθηκε με επιτυχία." + } + }, + "filters": { + "title": "Φίλτρα", + "clear": "Απαλοιφή Φίλτρων", + "showResults": "Εμφάνιση αποτελεσμάτων", + "noResults": "Δεν υπάρχουν αποτελέσματα", + "fromView": "Η τρέχουσα προβολή έχει επίσης ένα σύνολο φίλτρων:", + "fromViewBoth": "Θα χρησιμοποιηθεί σε συνδυασμό με αυτό που εισαγάγετε εδώ.", + "attributes": { + "title": "Τίτλος", + "titlePlaceholder": "Ο τίτλος για το αποθηκευμένο φίλτρο πηγαίνει εδώ…", + "description": "Περιγραφή", + "descriptionPlaceholder": "Προσθέστε μια περιγραφή για το φίλτρο, πατήστε '/' για περισσότερες επιλογές…", + "includeNulls": "Να συμπεριληφθούν Εργασίες που δεν έχουν καθορισμένη τιμή" + }, + "create": { + "title": "Νέο Αποθηκευμένο Φίλτρο", + "description": "Ένα αποθηκευμένο φίλτρο είναι ένα εικονικό έργο το οποίο δημιουργείται από ένα σύνολο φίλτρων κάθε φορά που το προσπελαύνει κανείς.", + "action": "Δημιουργία αποθηκευμένου φίλτρου", + "titleRequired": "Παρακαλώ εισαγάγετε έναν τίτλο για το φίλτρο." + }, + "delete": { + "header": "Διαγραφή του αποθηκευμένου φίλτρου", + "text": "Είστε σίγουροι ότι θέλετε να διαγράψετε το αποθηκευμένο φίλτρο;", + "success": "Το φίλτρο διαγράφηκε με επιτυχία." + }, + "edit": { + "title": "Επεξεργασία Του Αποθηκευμένου Φίλτρου", + "success": "Το φίλτρο αποθηκεύτηκε με επιτυχία." + }, + "query": { + "placeholder": "Πληκτρολογήστε για αναζήτηση ή φιλτράρισμα ερωτήματος…", + "help": { + "intro": "Για να φιλτράρετε εργασίες, μπορείτε να χρησιμοποιήσετε σύνταξη ερωτήματος παρόμοια με SQL. Τα διαθέσιμα πεδία για το φιλτράρισμα περιλαμβάνουν:", + "link": "Πώς λειτουργεί αυτό;", + "canUseDatemath": "Μπορείτε να χρησιμοποιήσετε μαθηματικά στις ημερομηνίες για να ορίσετε σχετικές ημερομηνίες. Κάντε κλικ στην τιμή της ημερομηνίας σε ένα ερώτημα για να μάθετε περισσότερα.", + "fields": { + "done": "Αν η εργασία έχει ολοκληρωθεί ή όχι", + "priority": "Το επίπεδο προτεραιότητας της εργασίας (1-5)", + "percentDone": "Το ποσοστό ολοκλήρωσης της εργασίας (0-100)", + "dueDate": "Η ημερομηνία παράδοσης της εργασίας", + "startDate": "Η ημερομηνία έναρξης της εργασίας", + "endDate": "Η ημερομηνία λήξης της εργασίας", + "doneAt": "Η ημερομηνία και η ώρα που ολοκληρώθηκε η εργασία", + "assignees": "Οι ανατιθέμενοι της εργασίας", + "labels": "Οι ετικέτες που σχετίζονται με την εργασία", + "project": "Το έργο στο οποίο ανήκει η εργασία (διαθέσιμο μόνο για αποθηκευμένα φίλτρα, όχι σε επίπεδο έργου)", + "reminders": "Οι υπενθυμίσεις της εργασίας ως πεδίο ημερομηνίας, θα επιστρέψουν όλες τις εργασίες με τουλάχιστον μία υπενθύμιση που ταιριάζει με το ερώτημα", + "created": "Η ώρα και ημερομηνία που δημιουργήθηκε η εργασία", + "updated": "Η ώρα και η ημερομηνία τελευταίας τροποποίησης μιας εργασίας" + }, + "operators": { + "intro": "Οι διαθέσιμοι τελεστές για το φιλτράρισμα περιλαμβάνουν:", + "notEqual": "Να μην είναι ίσο με", + "equal": "Ίσο με", + "greaterThan": "Μεγαλύτερο από", + "greaterThanOrEqual": "Μεγαλύτερο από ή ίσο με", + "lessThan": "Μικρότερο από", + "lessThanOrEqual": "Μικρότερο από ή ίσο με", + "like": "Ταιριάζει με μοτίβο (χρησιμοποιώντας το μπαλαντέρ %)", + "in": "Ταιριάζει με οποιαδήποτε τιμή από μια λίστα τιμών χωρισμένες με κόμμα", + "notIn": "Ταιριάζει με οποιαδήποτε τιμή που δεν υπάρχει σε μια λίστα τιμών χωρισμένες με κόμμα" + }, + "logicalOperators": { + "intro": "Για να συνδυάσετε πολλαπλές συνθήκες, μπορείτε να χρησιμοποιήσετε τους ακόλουθους λογικούς τελεστές:", + "and": "Τελεστής AND, ταιριάζει όλες τις συνθήκες που είναι αληθείς", + "or": "Τελεστής OR, ταιριάζει αν κάποια από τις συνθήκες είναι αληθής", + "parentheses": "Παρενθέσεις για ομαδοποίηση συνθηκών" + }, + "examples": { + "intro": "Παρακάτω υπάρχουν ορισμένα παραδείγματα ερωτημάτων φιλτραρίσματος:", + "priorityEqual": "Ταιριάζει με εργασίες με προτεραιτότητα επιπέδου 4", + "dueDatePast": "Βρίσκει εργασίες με ημερομηνία παράδοσης στο παρελθόν", + "undoneHighPriority": "Ταιριάζει με μη ολοκληρωμένες εργασίες με προτεραιότητα επιπέδου 3 ή μεγαλύτερο", + "assigneesIn": "Ταιριάζει με εργασίες που έχουν ανατεθεί είτε στο χρήστη \"user1\" ή στον \"user2\"", + "priorityOneOrTwoPastDue": "Βρίσκει εργασίες με επίπεδο προτεραιότητας 1 ή 2 και ημερομηνία παράδοσης στο παρελθόν" + } + } + } + }, + "sorting": { + "manually": "Χειροκίνητα", + "apply": "Εφαρμογή ταξινόμησης", + "description": "Επιλέξτε πως ταξινομούνται οι εργασίες σε αυτή τη λίστα. Κατά τη χειροκίνητη ταξινόμηση, μπορείτε να σύρετε και να αφήσετε εργασίες για να τις αλλάξετε τη σειρά.", + "options": { + "titleAsc": "Τίτλος (Α-Ω)", + "titleDesc": "Τίτλος (Ω-Α)", + "priorityDesc": "Προτεραιότητα (Υψηλότερη πρώτα)", + "priorityAsc": "Προτεραιότητα (Χαμηλότερη πρώτα)", + "dueDateAsc": "Ημερομηνία παράδοσης (Κοντινότερα πρώτα)", + "dueDateDesc": "Ημερομηνία παράδοσης (Αργότερα πρώτα)", + "startDateAsc": "Ημερομηνία έναρξης (Κοντινότερα πρώτα)", + "startDateDesc": "Ημερομηνία έναρξης (Αργότερα πρώτα)", + "endDateAsc": "Ημερομηνία λήξης (Κοντινότερα πρώτα)", + "endDateDesc": "Ημερομηνία λήξης (Αργότερα πρώτα)", + "percentDoneDesc": "% ολοκληρωμένα (Πρώτα τα πιο ολοκληρωμένα)", + "percentDoneAsc": "% ολοκληρωμένα (Πρώτα τα λιγότερο ολοκληρωμένα)", + "createdDesc": "Δημιουργήθηκε (Νεότερα πρώτα)", + "createdAsc": "Δημιουργήθηκε (Παλιότερα πρώτα)", + "updatedDesc": "Ενημερώθηκε (Νεότερα πρώτα)", + "updatedAsc": "Ενημερώθηκε (Παλιότερα πρώτα)" + } + }, + "migrate": { + "title": "Εισαγωγή από άλλες υπηρεσίες", + "titleService": "Εισαγωγή των δεδομένων σας από {name} στο Vikunja", + "description": "Κάντε κλικ στο λογότυπο μιας από τις παρακάτω υπηρεσίες τρίτων για να ξεκινήσετε.", + "descriptionDo": "Το Vikunja θα εισαγάγει όλες τις λίστες, εργασίες, σημειώσεις, υπενθυμίσεις και αρχεία που έχετε πρόσβαση.", + "authorize": "Για να επιτρέψετε στο Vikunja να προσπελάσει το {name} Λογαριασμό σας, κάντε κλικ στο παρακάτω πλήκτρο.", + "getStarted": "Ξεκινήστε", + "inProgress": "Εισαγωγή σε εξέλιξη…", + "alreadyMigrated1": "Φαίνεται ότι έχετε ήδη εισαγάγει τα στοιχεία σας από το {name} στο {date}.", + "alreadyMigrated2": "Η εισαγωγή είναι και πάλι δυνατή, αλλά μπορεί να δημιουργήσει διπλότυπα. Είστε σίγουροι;", + "confirm": "Είμαι σίγουρος, παρακαλώ ξεκινήστε τη μετάβαση τώρα!", + "importUpload": "Για εισαγωγή δεδομένων από το {name} στο Vikunja, κάντε κλικ στο παρακάτω κουμπί για να επιλέξετε ένα αρχείο.", + "upload": "Φόρτωση αρχείου", + "migrationStartedWillReciveEmail": "Το Vikunja θα εισάγει τώρα λίστες/έργα, εργασίες, σημειώσεις, υπενθυμίσεις και αρχεία από το {service}. Καθώς αυτό ενδέχεται να διαρκέσει λίγο χρόνο, θα σας σταλεί ένα email μόλις ολοκληρωθεί. Μπορείτε να κλείσετε τώρα το παράθυρο του προγράμματος πλοήγησης.", + "migrationInProgress": "Μια μετάβαση βρίσκεται σε εξέλιξη αυτήν τη στιγμή. Παρακαλώ περιμένετε μέχρι να ολοκληρωθεί.", + "csv": { + "description": "Εισαγωγή εργασιών από ένα αρχείο CSV με προσαρμοσμένη αντιστοίχιση στηλών.", + "uploadDescription": "Επιλέξτε ένα αρχείο CSV για εισαγωγή. Το αρχείο πρέπει να περιέχει τα δεδομένα των εργασιών με κεφαλίδες στην πρώτη γραμμή.", + "selectFile": "Επιλέξτε αρχείο CSV", + "columnMappingDescription": "Αντιστοίχιση κάθε στήλης στο αρχείο CSV με ιδιότητα εργασίας. Το Vikunja έχει εντοπίσει αυτόματα τις πιο πιθανές αντιστοιχίσεις. Η παρακάτω προεπισκόπηση θα ενημερωθεί αυτόματα όταν αλλάζετε τις ρυθμίσεις.", + "parsingOptions": "Επιλογές Ανάλυσης", + "delimiter": "Διαχωριστής", + "dateFormat": "Μορφή Ημερομηνίας", + "skipRows": "Παράλειψη Γραμμών", + "mapColumns": "Αντιστοίχιση Στηλών", + "example": "π.χ.", + "preview": "Προεπισκόπηση", + "previewDescription": "Εμφάνιση των πρώτων 5 από {count} εργασιών που θα εισαχθούν.", + "import": "Εισαγωγή Εργασιών", + "untitled": "Εργασία Χωρίς Τίτλο", + "ignore": "Παράβλεψη", + "delimiters": { + "comma": "Κόμμα (,)", + "semicolon": "Ελληνικό ερωτηματικό (;)", + "tab": "Στηλοθέτης", + "pipe": "Σωλήνας (|)" + } + } + }, + "label": { + "title": "Ετικέτες", + "manage": "Διαχείριση ετικετών", + "description": "Κάντε κλικ σε μια ετικέτα για να την επεξεργαστείτε. Μπορείτε να επεξεργαστείτε όλες τις ετικέτες που δημιουργήσατε, μπορείτε να χρησιμοποιήσετε όλες τις ετικέτες που σχετίζονται με μια εργασία σε έργο στο οποίο έχετε πρόσβαση.", + "newCTA": "Δεν έχετε αυτή τη στιγμή ετικέτες.", + "create": { + "header": "Νέα ετικέτα", + "title": "Δημιουργία ετικέτας", + "titleRequired": "Παρακαλώ καθορίστε έναν τίτλο.", + "success": "Η ετικέτα δημιουργήθηκε με επιτυχία." + }, + "edit": { + "header": "Επεξεργασία Ετικέτας", + "success": "Η ετικέτα ενημερώθηκε με επιτυχία." + }, + "deleteSuccess": "Η ετικέτα διαγράφηκε με επιτυχία.", + "attributes": { + "title": "Τίτλος", + "titlePlaceholder": "Ο τίτλος της ετικέτας πηγαίνει εδώ…", + "description": "Περιγραφή", + "color": "Χρώμα" + } + }, + "sharing": { + "authenticating": "Ταυτοποίηση…", + "passwordRequired": "Αυτό το διαμοιραζόμενο έργο απαιτεί έναν κωδικό πρόσβασης. Παρακαλώ εισάγετέ τον παρακάτω:", + "error": "Προέκυψε ένα σφάλμα.", + "invalidPassword": "Ο κωδικός πρόσβασης δεν είναι έγκυρος.", + "accessDenied": "Δεν επιτρέπεται η πρόσβαση. Παρακαλώ ελέγξτε τα δικαιώματά σας και προσπαθήστε ξανά.", + "serverError": "Παρουσιάστηκε σφάλμα διακομιστή. Παρακαλώ προσπαθήστε ξανά αργότερα.", + "projectLoadError": "Αποτυχία φόρτωσης πληροφοριών έργου.", + "retry": "Επανάληψη" + }, + "navigation": { + "overview": "Επισκόπηση", + "upcoming": "Επερχόμενα", + "settings": "Ρυθμίσεις", + "imprint": "Αποτύπωμα", + "privacy": "Πολιτική Ιδιωτικότητας", + "closeSidebar": "Κλείσιμο πλαϊνής μπάρας", + "home": "Αρχική σελίδα Vikunja" + }, + "misc": { + "loading": "Φόρτωση…", + "save": "Αποθήκευση", + "delete": "Διαγραφή", + "confirm": "Επιβεβαίωση", + "cancel": "Άκυρο", + "disable": "Απενεργοποίηση", + "copy": "Αντιγραφή στο πρόχειρο", + "copyError": "Η αντιγραφή στο πρόχειρο απέτυχε", + "searchPlaceholder": "Πληκτρολογήστε για αναζήτηση…", + "previous": "Προηγούμενο", + "next": "Επόμενο", + "poweredBy": "Με τη δύναμη του Vikunja", + "create": "Δημιουργία", + "doit": "Κάν'το!", + "saving": "Αποθήκευση…", + "saved": "Αποθηκεύτηκε!", + "default": "Προεπιλογή", + "close": "Κλείσιμο", + "download": "Λήψη", + "showMenu": "Προβολή του μενού", + "hideMenu": "Απόκρυψη του μενού", + "forExample": "Για παράδειγμα:", + "welcomeBack": "Καλώς Ήρθατε Πάλι!", + "custom": "Προσαρμογή", + "id": "Αναγνωριστικό", + "created": "Δημιουργήθηκε στις", + "createdBy": "Δημιουργήθηκε από {0}", + "actions": "Ενέργειες", + "cannotBeUndone": "Αυτό δεν μπορεί να αναιρεθεί!", + "avatarOfUser": "Εικόνα προφίλ {user}", + "closeBanner": "Κλείσιμο του banner", + "closeDialog": "Κλείσμο του διαλόγου", + "closeQuickActions": "Κλείσιμο των γρήγορων ενεργειών", + "skipToContent": "Μετάβαση στο κύριο περιεχόμενο", + "sortBy": "Ταξινόμηση ανά", + "dateRange": "Εύρος ημερομηνιών", + "notSet": "Μη ορισμένο", + "user": "Χρήστης" + }, + "input": { + "projectColor": "Χρώμα έργου", + "resetColor": "Επαναφορά Χρώματος", + "datepicker": { + "today": "Σήμερα", + "tomorrow": "Αύριο", + "nextMonday": "Επόμενη Δευτέρα", + "thisWeekend": "Αυτό το Σαββατοκύριακο", + "laterThisWeek": "Αργότερα μέσα στην εβδομάδα", + "nextWeek": "Επόμενη Εβδομάδα", + "chooseDate": "Επιλέξτε ημερομηνία" + }, + "editor": { + "edit": "Επεξεργασία", + "heading1": "Επικεφαλίδα 1", + "heading1Tooltip": "Μεγάλη κεφαλίδα τμήματος.", + "heading2": "Επικεφαλίδα 2", + "heading2Tooltip": "Μεσαία κεφαλίδα τμήματος.", + "heading3": "Επικεφαλίδα 3", + "heading3Tooltip": "Μικρότερη κεφαλίδα τμήματος.", + "bold": "Έντονα", + "italic": "Πλάγια", + "strikethrough": "Διακριτή διαγραφή", + "underline": "Υπογράμμιση", + "code": "Κώδικας", + "codeTooltip": "Καταγράψτε ένα απόσπασμα κώδικα.", + "quote": "Παράθεση", + "quoteTooltip": "Καταγράψτε μια φράση.", + "bulletList": "Λίστα με κουκκίδες", + "bulletListTooltip": "Δημιουργήστε μια απλή λίστα κουκκίδων.", + "orderedList": "Ταξινομημένη λίστα", + "orderedListTooltip": "Δημιουργήστε μια λίστα με αρίθμηση.", + "link": "Σύνδεσμος", + "image": "Εικόνα", + "imageTooltip": "Ανεβάστε μια εικόνα από τον υπολογιστή σας.", + "horizontalRule": "Οριζόντιος Κανόνας", + "horizontalRuleTooltip": "Διαίρεση ενότητας.", + "text": "Κείμενο", + "textTooltip": "Απλά αρχίστε να πληκτρολογείτε με απλό κείμενο.", + "taskList": "Λίστα εργασιών", + "taskListTooltip": "Παρακολουθήστε εργασίες με μια to-do λίστα.", + "undo": "Αναίρεση", + "redo": "Επανάληψη", + "placeholder": "Πληκτρολογήστε κάποιο κείμενο ή πατήστε '/' για να δείτε περισσότερες επιλογές…", + "table": { + "title": "Πίνακας", + "insert": "Εισαγωγή πίνακα", + "addColumnBefore": "Προσθήκη στήλης πριν", + "addColumnAfter": "Προσθήκη στήλης μετά", + "deleteColumn": "Διαγραφή στήλης", + "addRowBefore": "Προσθήκη γραμμής πριν", + "addRowAfter": "Προσθήκη γραμμής μετά", + "deleteRow": "Διαγραφή γραμμής", + "deleteTable": "Διαγραφή πίνακα", + "mergeCells": "Συγχώνευση κελιών", + "splitCell": "Διαίρεση κελιού", + "toggleHeaderColumn": "Εναλλαγή στήλης κεφαλίδας", + "toggleHeaderRow": "Εναλλαγή γραμμής κεφαλίδας", + "toggleHeaderCell": "Εναλλαγή κελιού κεφαλίδας", + "mergeOrSplit": "Συγχώνευση ή διαίρεση", + "fixTables": "Διόρθωση πινάκων" + }, + "emoji": { + "empty": "Δε βρέθηκαν emoji" + } + }, + "multiselect": { + "createPlaceholder": "Δημιουργία", + "selectPlaceholder": "Κάντε κλικ ή πατήστε enter για να επιλέξετε" + }, + "datepickerRange": { + "to": "Προς", + "from": "Από", + "fromto": "{from} προς {to}", + "date": "Ημερομηνία", + "ranges": { + "today": "Σήμερα", + "tomorrow": "Αύριο", + "thisWeek": "Αυτή η Εβδομάδα", + "restOfThisWeek": "Το υπόλοιπο της εβδομάδας", + "nextWeek": "Επόμενη Εβδομάδα", + "next7Days": "Επόμενες 7 Ημέρες", + "lastWeek": "Προηγούμενη Εβδομάδα", + "thisMonth": "Αυτός ο Μήνας", + "restOfThisMonth": "Το Υπόλοιπο Του Μήνα", + "nextMonth": "Επόμενος Μήνας", + "next30Days": "Επόμενες 30 Ημέρες", + "lastMonth": "Προηγούμενος Μήνας", + "thisYear": "Αυτό το Έτος", + "restOfThisYear": "Το Υπόλοιπο του Έτους" + }, + "values": { + "now": "Τώρα", + "startOfToday": "Έναρξη της ημέρας", + "endOfToday": "Τέλος της ημέρας", + "beginningOflastWeek": "Αρχή της προηγούμενης εβδομάδας", + "endOfLastWeek": "Τέλος της προηγούμενης εβδομάδας", + "beginningOfThisWeek": "Αρχή αυτής της εβδομάδας", + "endOfThisWeek": "Τέλος αυτής της εβδομάδας", + "startOfNextWeek": "Αρχή της επόμενης εβδομάδας", + "endOfNextWeek": "Τέλος της επόμενης εβδομάδας", + "in7Days": "Σε 7 ημέρες", + "beginningOfLastMonth": "Αρχή του προηγούμενου μήνα", + "endOfLastMonth": "Τέλος του προηγούμενου μήνα", + "startOfThisMonth": "Αρχή του μήνα", + "endOfThisMonth": "Τέλος του μήνα", + "startOfNextMonth": "Αρχή του επόμενου μήνα", + "endOfNextMonth": "Τέλος του επόμενου μήνα", + "in30Days": "Σε 30 ημέρες", + "startOfThisYear": "Αρχή του έτους", + "endOfThisYear": "Τέλος του έτους" + } + }, + "datemathHelp": { + "canuse": "Μπορείτε να χρησιμοποιήσετε μαθηματικά σε ημερομηνίες για σχετικές ημερομηνίες.", + "learnhow": "Δείτε πώς λειτουργεί", + "title": "Μαθηματικά Ημερομηνιών", + "intro": "Καθορίστε σχετικές ημερομηνίες που επιλύονται στη στιγμή από το Vikunja κατά την εφαρμογή του φίλτρου.", + "expression": "Κάθε μαθηματική έκφραση ημερομηνίας ξεκινά με μια ημερομηνία αγκύρωσης, η οποία μπορεί να είναι είτε {0}, ή μια συμβολοσειρά ημερομηνίας που τελειώνει σε {1}. Αυτή η ημερομηνία αγκύρωσης μπορεί προαιρετικά να ακολουθείται από μία ή περισσότερες εκφράσεις μαθηματικών.", + "similar": "Αυτές οι εκφράσεις είναι παρόμοιες με αυτές που παρέχονται από {0} και {1}.", + "add1Day": "Πρόσθεση μιας ημέρας", + "minus1Day": "Αφαίρεση μιας ημέρας", + "roundDay": "Στρογγυλοποίηση προς τα κάτω στην πλησιέστερη ημέρα", + "supportedUnits": "Υποστηριζόμενες μονάδες χρόνου", + "someExamples": "Παραδείγματα εκφράσεων χρόνου", + "units": { + "seconds": "Δευτερόλεπτα", + "minutes": "Λεπτά", + "hours": "Ώρες", + "days": "Ημέρες", + "weeks": "Εβδομάδες", + "months": "Μήνες", + "years": "Έτη" + }, + "examples": { + "now": "Ακριβώς τώρα", + "in24h": "Σε 24 Ώρες", + "today": "Σήμερα στις 00:00", + "beginningOfThisWeek": "Η αρχή της εβδομάδας στις 00:00", + "endOfThisWeek": "Τέλος της εβδομάδας", + "in30Days": "Σε 30 ημέρες", + "datePlusMonth": "{0} συν ένας μήνας στις 00:00 εκείνης της ημέρας" + } + } + }, + "task": { + "new": "Δημιουργήστε μια εργασία", + "createSuccess": "Η εργασία δημιουργήθηκε με επιτυχία.", + "addReminder": "Προσθήκη υπενθύμισης…", + "doneSuccess": "Η εργασία επισημάνθηκε με επιτυχία ως ολοκληρωμένη.", + "undoneSuccess": "Έγινε αναίρεση επισήμανσης της εργασίας ως ολοκληρωμένης.", + "readOnlyCheckbox": "Έχετε πρόσβαση μόνο για ανάγνωση για αυτή την εργασία και δεν μπορείτε να την επισημάνετε ως ολοκληρωμένη.", + "movedToProject": "Η εργασία μετακινήθηκε στο {project}.", + "undo": "Αναίρεση", + "checklistTotal": "{checked} από {total} εργασίες", + "checklistAllDone": "{total} εργασίες", + "show": { + "titleCurrent": "Τρέχουσες Εργασίες", + "noDates": "Προβολή εργασιών χωρίς ημερομηνία", + "overdue": "Προβολή εκπρόθεσμων εργασιών", + "fromuntil": "Εργασίες από {from} έως {until}", + "select": "Επιλέξτε εύρος ημερομηνίας", + "noTasks": "Δεν έχετε να κάνετε κάτι — Να έχετε μια όμορφη ημέρα!", + "filterByLabel": "Φιλτράρισμα ανά ετικέτα {label}", + "clearLabelFilter": "Καθαρισμός φίλτρου ετικέτας", + "savedFilterIgnored": "Το αποθηκευμένο φίλτρο αρχικής σελίδας δεν εφαρμόζεται κατά την προβολή εργασιών ανά ετικέτα." + }, + "detail": { + "chooseDueDate": "Κάντε κλικ εδώ για να καθορίσετε ημερομηνία παράδοσης", + "chooseStartDate": "Κάντε κλικ εδώ για να καθορίσετε ημερομηνία έναρξης", + "chooseEndDate": "Κάντε κλικ εδώ για να καθορίσετε ημερομηνία λήξης", + "move": "Μετακίνηση εργασίας σε διαφορετικό έργο", + "done": "Επισήμανση εργασίας ως ολοκληρωμένη!", + "undone": "Επισήμανση ως μη ολοκληρωμένης", + "created": "Δημιουργήθηκε από {0} στις {1}", + "updated": "Ενημερώθηκε {0}", + "doneAt": "Ολοκληρώθηκε {0}", + "updateSuccess": "Η εργασία αποθηκεύτηκε με επιτυχία.", + "deleteSuccess": "Η εργασία διαγράφηκε με επιτυχία.", + "duplicateSuccess": "Η εργασία αντιγράφηκε με επιτυχία.", + "noBucket": "Δεν υπάρχει κάδος", + "bucketChangedSuccess": "Ο κάδος εργασιών τροποποιήθηκε με επιτυχία.", + "belongsToProject": "Η εργασία ανήκει στο έργο '{project}'", + "back": "Επιστροφή στο έργο", + "due": "Παράδοση στις {at}", + "closeTaskDetail": "Κλείσιμο λεπτομερειών εργασίας", + "title": "Λεπτομέρειες εργασίας", + "markAsDone": "Επισήμανση '{task}' ως ολοκληρωμένης", + "scrollToBottom": "Κύλιση προς τα κάτω", + "organization": "Οργανισμός", + "management": "Διαχείριση", + "dateAndTime": "Ημερομηνία και ώρα", + "delete": { + "header": "Διαγραφή της εργασίας", + "text1": "Είστε σίγουροι ότι θέλετε να αφαιρέσετε την εργασία;", + "text2": "Αυτό θα αφαιρέσει επίσης όλα τα συνημμένα, υπενθυμίσεις και σχέσεις που σχετίζονται με την εργασία και δεν είναι δυνατή η αναίρεση!" + }, + "actions": { + "assign": "Ανάθεση σε Χρήστη", + "label": "Προσθήκη Ετικετών", + "priority": "Ορισμός Προτεραιότητας", + "dueDate": "Ορισμός Ημερομηνίας Παράδοσης", + "startDate": "Ορισμός Ημερομηνίας Έναρξης", + "endDate": "Ορισμός Ημερομηνίας Λήξης", + "reminders": "Ορισμός Υπενθυμίσεων", + "repeatAfter": "Ορισμός Επαναλαμβανόμενου Διαστήματος", + "percentDone": "Ορισμός Προόδου", + "attachments": "Προσθήκη Συνημμένων", + "timeTracking": "Χρόνος ίχνους", + "relatedTasks": "Προσθήκη Συσχέτισης", + "moveProject": "Μετακίνηση", + "duplicate": "Αντιγραφή", + "color": "Ορισμός Χρώματος", + "delete": "Διαγραφή", + "favorite": "Προσθήκη στα Αγαπημένα", + "unfavorite": "Αφαίρεση από τα Αγαπημένα" + } + }, + "attributes": { + "assignees": "Ανατιθέμενοι", + "color": "Χρώμα", + "created": "Δημιουργήθηκε", + "createdBy": "Δημιουργήθηκε Από", + "description": "Περιγραφή", + "done": "Ολοκληρώθηκε", + "dueDate": "Ημερομηνία Παράδοσης", + "endDate": "Ημερομηνία Λήξης", + "labels": "Ετικέτες", + "percentDone": "Πρόοδος", + "priority": "Προτεραιότητα", + "project": "Έργο", + "relatedTasks": "Σχετιζόμενες Εργασίες", + "reminders": "Υπενθυμίσεις", + "repeat": "Επανάληψη", + "comment": "{count} σχόλιο | {count} σχόλια", + "commentCount": "Αριθμός σχολίων", + "startDate": "Ημερομηνία Έναρξης", + "title": "Τίτλος", + "updated": "Ενημερώθηκε", + "doneAt": "Ολοκληρώθηκε Στις" + }, + "subscription": { + "subscribedTaskThroughParentProject": "Δεν μπορείτε να κάνετε απεγγραφή εδώ επειδή έχετε εγγραφεί σε αυτήν την εργασία μέσω του έργου της.", + "subscribedProject": "Αυτή τη στιγμή είστε εγγεγραμμένοι σε αυτό το έργο και θα λαμβάνετε ειδοποιήσεις για αλλαγές.", + "notSubscribedProject": "Δεν είστε εγγεγραμμένοι σε αυτό το έργο και δε θα λαμβάνετε ειδοποιήσεις για αλλαγές.", + "subscribedTask": "Αυτή τη στιγμή είστε εγγεγραμμένοι σε αυτήν την εργασία και θα λαμβάνετε ειδοποιήσεις για αλλαγές.", + "notSubscribedTask": "Δεν είστε εγγεγραμμένοι σε αυτή την εργασία και δε θα λαμβάνετε ειδοποιήσεις για αλλαγές.", + "subscribe": "Εγγραφή", + "unsubscribe": "Απεγγραφή", + "subscribeSuccessProject": "Είστε τώρα εγγεγραμμένοι στο έργο", + "unsubscribeSuccessProject": "Έχετε κάνει απεγγραφή από το έργο", + "subscribeSuccessTask": "Είστε τώρα εγγεγραμμένοι στην εργασία", + "unsubscribeSuccessTask": "Έχετε κάνει απεγγραφή από την εργασία" + }, + "attachment": { + "title": "Συνημμένα", + "createdBy": "δημιούργησε {0} από {1}", + "downloadTooltip": "Λήψη του συνημμένου", + "upload": "Φόρτωση συνημμένου", + "drop": "Σύρετε αρχεία εδώ για ανέβασμα", + "delete": "Διαγραφή συνημμένου", + "deleteTooltip": "Διαγραφή του συνημμένου", + "deleteText1": "Είστε σίγουροι ότι θέλετε να διαγράψετε το συνημμένο {filename};", + "copyUrlTooltip": "Αντιγραφή της διεύθυνσης url του συνημμένου για χρήση σε κείμενο", + "setAsCover": "Δημιουργία καλύμματος", + "unsetAsCover": "Αφαίρεση καλύμματος", + "successfullyChangedCoverImage": "Η εικόνα καλύμματος τροποποιήθηκε με επιτυχία.", + "usedAsCover": "Εικόνα εξώφυλλου" + }, + "comment": { + "title": "Σχόλια", + "loading": "Φόρτωση σχολίων…", + "edited": "ενημερώθηκε στις {date}", + "creating": "Δημιουργία σχολίου…", + "placeholder": "Προσθέστε το σχόλιό σας, πατήστε '/' για περισσότερες επιλογές…", + "comment": "Σχόλιο", + "delete": "Διαγραφή του σχολίου", + "deleteText1": "Είστε σίγουροι ότι θέλετε να διαγράψετε αυτό το σχόλιο;", + "deleteSuccess": "Το σχόλιο διαγράφηκε με επιτυχία.", + "addedSuccess": "Το σχόλιο προστέθηκε με επιτυχία.", + "permalink": "Αντιγραφή permalink για το σχόλιο", + "sortNewestFirst": "Νεότερα πρώτα", + "sortOldestFirst": "Παλαιότερα πρώτα", + "reply": "Απάντηση", + "jumpToOriginal": "Μετάβαση στο αρχικό σχόλιο", + "deletedComment": "διαγραμμένο σχόλιο" + }, + "mention": { + "noUsersFound": "Δε βρέθηκαν χρήστες" + }, + "deferDueDate": { + "title": "Αναβολή ημερομηνίας παράδοσης", + "1day": "1 ημέρα", + "3days": "3 ημέρες", + "1week": "1 εβδομάδα" + }, + "description": { + "placeholder": "Εισαγάγετε μια περιγραφή για το έργο, πατήστε '/' για περισσότερες επιλογές…" + }, + "assignee": { + "placeholder": "Πληκτρολογήστε για να αναθέσετε σε χρήστη…", + "selectPlaceholder": "Ανάθεση στο χρήστη", + "assignSuccess": "Έγινε η ανάθεση σε χρήστη με επιτυχία.", + "unassignSuccess": "Αφαιρέθηκε η ανάθεση από το χρήστη με επιτυχία." + }, + "label": { + "placeholder": "Πληκτρολογήστε για να προσθέσετε ετικέτα…", + "createPlaceholder": "Προσθέστε το ως νέα ετικέτα", + "addSuccess": "Η ετικέτα προστέθηκε με επιτυχία.", + "removeSuccess": "Η ετικέτα αφαιρέθηκε με επιτυχία.", + "addCreateSuccess": "Η ετικέτα δημιουργήθηκε και προστέθηκε με επιτυχία.", + "delete": { + "header": "Διαγραφή της ετικέτας", + "text1": "Είστε σίγουροι ότι θέλετε να διαγράψετε αυτή την ετικέτα;", + "text2": "Αυτό θα το αφαιρέσει από όλες τις εργασίες και δεν μπορεί να αναιρεθεί." + } + }, + "priority": { + "unset": "Μη ορισμένη", + "low": "Χαμηλή", + "medium": "Μεσαία", + "high": "Υψηλή", + "urgent": "Επείγον", + "doNow": "ΝΑ ΓΙΝΕΙ ΤΩΡΑ" + }, + "relation": { + "add": "Προσθήκη Νέας Συσχέτισης Εργασίας", + "new": "Νέα Συσχέτιση Εργασίας", + "searchPlaceholder": "Πληκτρολογήστε στην αναζήτηση μια εργασία για να την προσθέσετε ως σχετιζόμενη…", + "createPlaceholder": "Προσθέστε την ως σχετιζόμενη εργασία", + "differentProject": "Η εργασία ανήκει σε διαφορετικό έργο.", + "noneYet": "Δεν υπάρχουν ακόμη συσχετίσεις.", + "delete": "Διαγραφή Συσχέτισης Εργασίας", + "deleteText1": "Είστε σίγουροι ότι θέλετε να διαγράψετε τη συσχέτιση εργασίας;", + "select": "Επιλέξτε είδος σχέσης", + "taskRequired": "Παρακαλώ επιλέξτε μια εργασία ή εισαγάγετε ένα νέο τίτλο εργασίας.", + "kinds": { + "subtask": "Υποεργασία | Υποεργασίες", + "parenttask": "Γονική Εργασία | Γονικές Εργασίες", + "related": "Σχετιζόμενη Εργασία | Σχετιζόμενες Εργασίες", + "duplicateof": "Αντίγραφο του | Αντίγραφα του", + "duplicates": "Αντίγραφο | Αντίγραφα", + "blocking": "Μπλοκαρισμένο|Μπλοκαρισμένα", + "blocked": "Αποκλεισμένο Από | Αποκλεισμένα Από", + "precedes": "Προηγείται | Προηγούνται", + "follows": "Ακολουθεί | Ακολουθούν", + "copiedfrom": "Αντιγράφηκε Από | Αντιγράφηκαν Από", + "copiedto": "Αντιγράφηκε Σε | Αντιγράφηκαν Σε" + } + }, + "reminder": { + "before": "{amount} {unit} πριν {type}", + "after": "{amount} {unit} μετά {type}", + "beforeShort": "πριν", + "afterShort": "μετά", + "onDueDate": "Στην ημερομηνία παράδοσης", + "onStartDate": "Στην ημερομηνία έναρξης", + "onEndDate": "Στην ημερομηνία λήξης", + "custom": "Προσαρμογή", + "dateAndTime": "Ημερομηνία και ώρα" + }, + "repeat": { + "everyDay": "Κάθε Ημέρα", + "everyWeek": "Κάθε Εβδομάδα", + "every30d": "Κάθε 30 Ημέρες", + "mode": "Λειτουργία επανάληψης", + "monthly": "Μηνιαία", + "fromCurrentDate": "Από την ημερομηνία ολοκλήρωσης", + "each": "Κάθε", + "specifyAmount": "Καθορίστε μια τιμή…", + "hours": "Ώρες", + "days": "Ημέρες", + "weeks": "Εβδομάδες", + "invalidAmount": "Παρακαλώ εισαγάγετε μια τιμή μεγαλύτερη από το 0." + }, + "quickAddMagic": { + "hint": "Χρησιμοποιήστε μαγικά προθέματα για να ορίσετε τις ημερομηνίες παράδοσης, τους ανατιθέμενους και άλλες ιδιότητες των εργασιών.", + "quickEntryHint": "Χρήση μαγικών προθεμάτων για ημερομηνίες, ετικέτες & άλλα. Ανοίξτε την κύρια εφαρμογή Vikunja και ελέγξτε την υπόδειξη στην είσοδο εργασίας για περισσότερες λεπτομέρειες.", + "title": "Κατάσταση Quick Add Magic", + "intro": "Κατά τη δημιουργία μιας εργασίας, μπορείτε να χρησιμοποιήσετε ειδικές λέξεις-κλειδιά για να προσθέσετε άμεσα ιδιότητες στη νέα εργασία. Αυτό επιτρέπει την προσθήκη συνήθως χρησιμοποιούμενων ιδιοτήτων σε εργασίες πολύ πιο γρήγορα.", + "multiple": "Μπορείτε να το χρησιμοποιήσετε πολλές φορές.", + "label1": "Για να προσθέσετε μια ετικέτα, απλά προτάσσετε το όνομα της ετικέτας με το {prefix}.", + "label2": "Το Vikunja θα ελέγξει πρώτα αν η ετικέτα υπάρχει ήδη και θα τη δημιουργήσει αν δεν υπάρχει.", + "label3": "Για να χρησιμοποιήσετε κενά, απλά προσθέστε ένα \" ή ' γύρω από το όνομα της ετικέτας.", + "label4": "Για παράδειγμα: {prefix}\"Ετικέτα με κενά\".", + "priority1": "Για να ορίσετε την προτεραιότητα μιας εργασίας, προσθέστε τον αριθμό 1-5, με πρόθεμα ένα {prefix}.", + "priority2": "Όσο μεγαλύτερος ο αριθμός, τόσο υψηλότερη είναι η προτεραιότητα.", + "assignees": "Για να αναθέσετε άμεσα την εργασία σε έναν χρήστη, προσθέστε το όνομα χρήστη του με πρόθεμα {prefix} στην εργασία.", + "project1": "Για να ορίσετε ένα έργο στο οποίο θα εμφανίζεται η εργασία, πληκτρολογήστε το όνομα του με πρόθεμα {prefix}.", + "project2": "Αυτό θα επιστρέψει ένα σφάλμα αν το έργο δεν υπάρχει.", + "project3": "Για να χρησιμοποιήσετε κενά, απλά προσθέστε ένα \" ή ' γύρω από το όνομα του έργου.", + "project4": "Για παράδειγμα: {prefix}\"Έργο με κενά\".", + "dateAndTime": "Ημερομηνία και ώρα", + "date": "Οποιαδήποτε ημερομηνία θα χρησιμοποιηθεί ως ημερομηνία παράδοσης της νέας εργασίας. Μπορείτε να χρησιμοποιήσετε ημερομηνίες σε οποιαδήποτε από τις παρακάτω μορφές:", + "dateWeekday": "οποιαδήποτε καθημερινή, θα χρησιμοποιηθεί η επόμενη ημερομηνία μετά από εκείνη την ημέρα", + "dateCurrentYear": "θα χρησιμοποιηθεί το τρέχον έτος", + "dateNth": "θα χρησιμοποιηθεί η {day}η ημέρα του τρέχοντος μήνα", + "dateTime": "Συνδυάστε οποιαδήποτε από τις μορφές ημερομηνίας με το \"{time}\" (ή {timePM}) για να ορίσετε χρόνο.", + "repeats": "Επαναλαμβανόμενες εργασίες", + "repeatsDescription": "Για να ορίσετε μια εργασία ως επαναλαμβανόμενη σε ένα διάστημα, απλά προσθέστε το '{suffix}' στο κείμενο της εργασίας. Το ποσό πρέπει να Είναι αριθμός και μπορεί να παραληφθεί για να χρησιμοποιηθεί μόνο ο τύπος (δείτε παραδείγματα)." + } + }, + "team": { + "title": "Ομάδες", + "noTeams": "Αυτή τη στιγμή δεν είστε μέλος κάποιας ομάδας.", + "create": { + "title": "Δημιουργία ομάδας", + "success": "Η ομάδα δημιουργήθηκε με επιτυχία." + }, + "edit": { + "title": "Επεξεργασία Ομάδας \"{team}\"", + "members": "Μέλη Ομάδας", + "search": "Πληκτρολογήστε για να αναζητήσετε χρήστη…", + "addUser": "Προσθήκη στην ομάδα", + "makeMember": "Μετατροπή σε Μέλος", + "makeAdmin": "Μετατροπή σε Διαχειριστή", + "success": "Η ομάδα ενημερώθηκε με επιτυχία.", + "userAddedSuccess": "Το μέλος προστέθηκε στην ομάδα με επιτυχία.", + "madeMember": "Το μέλος της ομάδας μετατράπηκε με επιτυχία σε μέλος.", + "madeAdmin": "Το μέλος της ομάδας μετατράπηκε με επιτυχία σε διαχειριστή.", + "mustSelectUser": "Παρακαλώ επιλέξτε έναν χρήστη.", + "delete": { + "header": "Διαγραφή της ομάδας", + "text1": "Είστε σίγουροι ότι θέλετε να διαγράψετε την ομάδα και όλα τα μέλη της;", + "text2": "Όλα τα μέλη της ομάδας θα χάσουν την πρόσβαση σε έργα πους έχουν διαμοιραστεί. Αυτό ΔΕ ΓΙΝΕΤΑΙ ΝΑ ΑΝΑΙΡΕΘΕΙ!", + "success": "Η ομάδα διαγράφηκε με επιτυχία." + }, + "deleteUser": { + "header": "Αφαίρεση χρήστη από την ομάδα", + "text1": "Είστε σίγουροι ότι θέλετε να αφαιρέσετε το χρήστη από την ομάδα;", + "text2": "Θα χάσουν πρόσβαση σε όλα τα έργα που έχει πρόσβαση αυτή η ομάδα. Αυτό ΔΕ ΓΙΝΕΤΑΙ ΝΑ ΑΝΑΙΡΕΘΕΙ!", + "success": "Ο χρήστης διαγράφηκε από την ομάδα με επιτυχία." + }, + "leave": { + "title": "Αποχώρηση από την ομάδα", + "text1": "Είστε σίγουρα ότι θέλετε να αποχωρήσετε από την ομάδα;", + "text2": "Θα χάσετε την πρόσβαση σε όλα τα έργα στα οποία έχει πρόσβαση η ομάδα. Αν αλλάξετε γνώμη, θα χρειαστείτε έναν διαχειριστή ομάδας για να σας προσθέσει ξανά.", + "success": "Έχετε αποχωρήσει από την ομάδα με επιτυχία." + } + }, + "attributes": { + "name": "Όνομα Ομάδας", + "namePlaceholder": "Το όνομα της ομάδας πηγαίνει εδώ…", + "nameRequired": "Παρακαλώ καθορίστε ένα όνομα.", + "description": "Περιγραφή", + "descriptionPlaceholder": "Περιγράψτε εδώ την ομάδα, πατήστε το '/' για περισσότερες επιλογές…", + "admin": "Διαχειριστής", + "member": "Μέλος", + "isPublic": "Δημόσια Ομάδα", + "isPublicDescription": "Κάντε την ομάδα δημόσια αναζητήσιμη. Όταν αυτό είναι ενεργοποιημένο, ο καθένας μπορεί να διαμοιράζεται έργα με την ομάδα ακόμα και όταν δεν είναι άμεσο μέλος." + } + }, + "keyboardShortcuts": { + "title": "Συντομεύσεις Πληκτρολογίου", + "general": "Γενικά", + "allPages": "Αυτές οι συντομεύσεις λειτουργούν σε όλες τις σελίδες.", + "currentPageOnly": "Αυτές οι συντομεύσεις λειτουργούν μόνο στην τρέχουσα σελίδα.", + "somePagesOnly": "Αυτές οι συντομεύσεις λειτουργούν μόνο σε ορισμένες σελίδες.", + "toggleMenu": "Εναλλαγή Του Μενού", + "quickSearch": "Άνοιγμα της γραμμής αναζήτησης/γρήγορης δράσης", + "then": "έπειτα", + "task": { + "title": "Σελίδα Εργασίας", + "done": "Επισήμανση της εργασίας ως ολοκληρωμένη / μη ολοκληρωμένη", + "assign": "Ανάθεση της εργασίας σε έναν χρήστη", + "labels": "Προσθήκη ετικετών στην εργασία", + "dueDate": "Αλλαγή της ημερομηνίας παράδοσης της εργασίας", + "attachment": "Προσθήκη συνημμένου στην εργασία", + "related": "Τροποποίηση σχετιζόμενων εργασιών της εργασίας", + "color": "Αλλαγή του χρώματος της εργασίας", + "move": "Μετακίνηση της εργασίας σε άλλο έργο", + "reminder": "Διαχείριση υπενθυμίσεων της εργασίας", + "description": "Εναλλαγή επεξεργασίας της περιγραφής της εργασίας", + "delete": "Διαγραφή της εργασίας", + "priority": "Αλλαγή της προτεραιότητας της εργασίας", + "favorite": "Να σημειωθεί η εργασία ως αγαπημένη / μη αγαπημένη", + "openProject": "Άνοιγμα του έργου της εργασίας", + "save": "Αποθήκευση της τρέχουσας εργασίας", + "copyIdentifier": "Αντιγραφή του αναγνωριστικού της εργασίας στο πρόχειρο", + "copyIdentifierAndTitle": "Αντιγραφή του αναγνωριστικού και τίτλου της εργασίας στο πρόχειρο", + "copyIdentifierTitleAndUrl": "Αντιγραφή του αναγνωριστικού, τίτλου και διεύθυνσης URL της εργασίας στο πρόχειρο", + "copyUrl": "Αντιγραφή της διεύθυνσης URL της εργασίας στο πρόχειρο" + }, + "project": { + "title": "Προβολές Έργου", + "switchToListView": "Εναλλαγή σε προβολή λίστας", + "switchToGanttView": "Εναλλαγή σε προβολή gantt", + "switchToKanbanView": "Εναλλαγή σε προβολή kanban", + "switchToTableView": "Εναλλαγή σε προβολή πίνακα" + }, + "navigation": { + "title": "Πλοήγηση", + "overview": "Πλοηγηθείτε στην επισκόπηση", + "upcoming": "Πλοηγηθείτε στις επερχόμενες εργασίες", + "labels": "Πλοηγηθείτε στις ετικέτες", + "teams": "Πλοηγηθείτε στις ομάδες", + "projects": "Πλοηγηθείτε στα έργα" + }, + "list": { + "title": "Λίστα Εργασιών", + "navigateDown": "Επισήμανση επόμενης εργασίας", + "navigateUp": "Επισήμανση προηγούμενης εργασίας", + "open": "Άνοιγμα επισημασμένης εργασίας" + }, + "gantt": { + "title": "Διάγραμμα Gantt", + "moveTaskLeft": "Μετακίνηση εργασίας σε πρωθύστερη ημερομηνία", + "moveTaskRight": "Μετακίνηση εργασίας σε μεταγενέστερη ημερομηνία", + "expandTaskLeft": "Επέκταση ημερομηνίας έναρξης εργασίας νωρίτερα", + "expandTaskRight": "Επέκταση ημερομηνίας λήξης εργασίας αργότερα", + "shrinkTaskLeft": "Σμίκρυνση εργασίας από την ημερομηνία έναρξης", + "shrinkTaskRight": "Σμίκρυνση εργασίας από την ημερομηνία λήξης" + } + }, + "update": { + "available": "Υπάρχει διαθέσιμη ενημέρωση!", + "do": "Ενημέρωση Τώρα" + }, + "menu": { + "edit": "Επεξεργασία", + "archive": "Αρχειοθέτηση", + "duplicate": "Αντιγραφή", + "delete": "Διαγραφή", + "unarchive": "Αναίρεση Αρχειοθέτησης", + "setBackground": "Ρυθμίσεις παρασκηνίου", + "share": "Διαμοιρασμός", + "createProject": "Δημιουργία έργου", + "cantArchiveIsDefault": "Δεν μπορείτε να το αρχειοθετήσετε επειδή είναι το προκαθορισμένο σας έργο.", + "cantDeleteIsDefault": "Δεν μπορείτε να το διαγράψετε επειδή είναι το προκαθορισμένο σας έργο.", + "views": "Προβολές" + }, + "apiConfig": { + "url": "Vikunja URL", + "urlPlaceholder": "πχ. https://localhost:3456", + "change": "αλλαγή", + "use": "Χρήση της εγκατάστασης Vikunja στο {0}", + "error": "Αδυναμία εύρεσης ή χρήσης της εγκατάστασης Vikunja στο \"{domain}\". Παρακαλώ ελέγξτε αν η διεύθυνση url έχει τη σωστή μορφή και μπορείτε να το δείτε όταν το καλείτε απευθείας και προσπαθήστε ξανά.", + "success": "Χρήση της εγκατάστασης του Vikunja στο {domain}\".", + "urlRequired": "Απαιτείται μια διεύθυνση url." + }, + "loadingError": { + "failed": "Η φόρτωση απέτυχε, παρακαλώ {0}. Εάν το σφάλμα παραμένει, παρακαλώ {1}.", + "tryAgain": "δοκιμάστε ξανά", + "contact": "επικοινωνήστε μαζί μας" + }, + "notification": { + "title": "Ειδοποιήσεις", + "none": "Δεν έχετε καμία ειδοποίηση. Να έχετε μια όμορφη ημέρα!", + "explainer": "Οι ειδοποιήσεις θα εμφανίζονται εδώ όταν συμβούν ενέργειες, έργα ή εργασίες που έχετε κάνει εγγραφή.", + "markAllRead": "Επισήμανση όλων των ειδοποιήσεων ως αναγνωσμένες", + "markAllReadSuccess": "Όλες οι ειδοποιήσεις επισημάνθηκαν ως αναγνωσμένες με επιτυχία.", + "subscribeFeed": "Εγγραφή στις ειδοποιήσεις μέσω ροής Atom" + }, + "quickActions": { + "notLoggedIn": "Παρακαλώ συνδεθείτε πρώτα στο κύριο παράθυρο του Vikunja.", + "commands": "Εντολές", + "placeholder": "Πληκτρολογήστε εντολή ή κάντε αναζήτηση…", + "hint": "Μπορείτε να χρησιμοποιήσετε το {project} για να περιορίσετε την αναζήτηση μέσα σε ένα έργο. Συνδυάστε {project} ή {label} (ετικέτες) με ένα ερώτημα αναζήτησης για να αναζητήσετε μια εργασία με αυτές τις ετικέτες μέσα στο έργο. Χρησιμοποιήστε {assignee} για αναζήτηση μόνο για ομάδες.", + "tasks": "Εργασίες", + "projects": "Έργα", + "teams": "Ομάδες", + "labels": "Ετικέτες", + "newProject": "Εισαγάγετε τον τίτλο του νέου έργου…", + "newTask": "Εισαγάγετε τον τίτλο της νέας εργασίας…", + "newTeam": "Εισαγάγετε τον τίτλο της νέας ομάδας…", + "createTask": "Δημιουργία εργασίας στο τρέχον έργο ({title})", + "createProject": "Δημιουργία εργασίας", + "cmds": { + "newTask": "Νέα εργασία", + "newProject": "Νέο έργο", + "newTeam": "Νέα ομάδα" + } + }, + "date": { + "altFormatLong": "j M Y, H:i", + "altFormatShort": "j M Y" + }, + "reaction": { + "reactedWith": "Ο/Η {user} αντέδρασε με {value}", + "reactedWithAnd": "Οι {users} και ο/η {lastUser} αντέδρασαν με {value}", + "reactedWithAndMany": "Οι {users} και {num} περισσότεροι αντέδρασαν με {value}", + "add": "Προσθέστε την αντίδρασή σας" + }, + "error": { + "1001": "Υπάρχει ήδη χρήστης με αυτό το όνομα χρήστη.", + "1002": "Υπάρχει ήδη χρήστης με αυτήν τη διεύθυνση email.", + "1004": "Δε δόθηκαν όνομα χρήστη και κωδικός πρόσβασης.", + "1005": "Ο χρήστης αυτός δεν υπάρχει.", + "1006": "Δεν ήταν δυνατή η εύρεση του αναγνωριστικού χρήστη.", + "1008": "Δε δόθηκε τεκμήριο επαναφοράς κωδικού πρόσβασης.", + "1009": "Μη έγκυρο τεκμήριο επαναφοράς κωδικού πρόσβασης.", + "1010": "Μη έγκυρο τεκμήριο επιβεβαίωσης διεύθυνσης email.", + "1011": "Λάθος όνομα χρήστη ή κωδικός πρόσβασης.", + "1012": "Η διεύθυνση email του χρήστη δεν επιβεβαιώθηκε.", + "1013": "Ο νέος κωδικός πρόσβασης είναι κενός.", + "1014": "Ο παλιός κωδικός πρόσβασης είναι κενός.", + "1015": "Το TOTP είναι ήδη ενεργοποιημένο για αυτόν το χρήστη.", + "1016": "Το TOTP δεν είναι ενεργοποιημένο για αυτόν το χρήστη.", + "1017": "Ο κωδικός TOTP δεν είναι έγκυρος.", + "1018": "Η ρύθμιση τύπου άβαταρ χρήστη δεν είναι έγκυρη.", + "1019": "Δε δόθηκε διεύθυνση email από τον πάροχο OpenID. Παρακαλώ βεβαιωθείτε ότι ο πάροχος openid παρέχει δημόσια μια διεύθυνση email για το λογαριασμό σας.", + "1020": "Ο λογαριασμός είναι απενεργοποιημένος. Ελέγξτε τα email σας ή ρωτήστε το διαχειριστή.", + "1021": "Αυτός ο λογαριασμός διαχειρίζεται από τρίτο πάροχο ελέγχου ταυτοποίησης.", + "1022": "Το όνομα χρήστη δεν πρέπει να περιέχει κενούς χαρακτήρες.", + "1023": "Δεν μπορείτε να το κάνετε αυτό ως σύνδεσμο διαμοιρασμού.", + "1024": "Μη έγκυρα δεδομένα αξίωσης για το πεδίο {field} του τύπου {type}.", + "1025": "Η ζώνη ώρας '{timezone}' δεν είναι έγκυρη. Παρακαλώ επιλέξτε μια έγκυρη ζώνη ώρας από τη λίστα.", + "2001": "Το αναγνωριστικό δεν μπορεί να είναι κενό ή 0.", + "2002": "Κάποια από τα δεδομένα της αίτησης δεν ήταν έγκυρα.", + "2003": "Η ζώνη ώρας '{timezone}' δεν είναι έγκυρη.", + "3001": "Το έργο δεν υπάρχει.", + "3004": "Πρέπει να έχετε δικαιώματα ανάγνωσης στο έργο για την εκτέλεση αυτής της ενέργειας.", + "3005": "Ο τίτλος του έργου δεν μπορεί να είναι κενός.", + "3006": "Ο διαμοιρασμός του έργου δεν υπάρχει.", + "3007": "Υπάρχει ήδη ένα έργο με αυτό το αναγνωριστικό.", + "3008": "Το έργο είναι αρχειοθετημένο και επομένως μπορεί να προσπελαστεί μόνο για ανάγνωση. Αυτό ισχύει επίσης για όλες τις εργασίες που σχετίζονται με το έργο.", + "4001": "Ο τίτλος της εργασίας δεν μπορεί να είναι κενός.", + "4002": "Η εργασία δεν υπάρχει.", + "4003": "Όλες οι μαζικές εργασίες επεξεργασίας πρέπει να ανήκουν στο ίδιο έργο.", + "4004": "Χρειάζεται τουλάχιστον μία εργασία κατά τη μαζική επεξεργασία εργασιών.", + "4005": "Δεν έχετε το δικαίωμα να δείτε την εργασία.", + "4006": "Δεν μπορείτε να ορίσετε ως γονική εργασία την ίδια την εργασία.", + "4007": "Δεν μπορείτε να δημιουργήσετε μια σχέση εργασίας με ένα μη έγκυρο είδος σχέσης.", + "4008": "Δεν μπορείτε να δημιουργήσετε μια σχέση εργασίας που υπάρχει ήδη.", + "4009": "Η σχέση εργασίας δεν υπάρχει.", + "4010": "Δεν μπορεί να συσχετιστεί μια εργασία με τον εαυτό της.", + "4011": "Το συνημμένο της εργασίας δεν υπάρχει.", + "4012": "Το συνημμένο της εργασίας είναι πολύ μεγάλο.", + "4013": "Η παράμετρος ταξινόμησης εργασιών δεν είναι έγκυρη.", + "4014": "Η σειρά ταξινόμησης εργασιών δεν είναι έγκυρη.", + "4015": "Το σχόλιο της εργασίας δεν υπάρχει.", + "4016": "Μη έγκυρο πεδίο εργασίας.", + "4017": "Μη έγκυρος τελεστής σύγκρισης φίλτρου εργασιών.", + "4018": "Μη έγκυρος τελεστής σύγκρισης φίλτρου εργασιών.", + "4019": "Μη έγκυρη τιμή φίλτρου εργασιών.", + "4020": "Το συνημμένο δεν ανήκει σε αυτή την εργασία.", + "4021": "Ο χρήστης έχει ήδη ανατεθεί στην εργασία.", + "4022": "Παρακαλώ δώστε με τι είναι σχετική η ημερομηνία υπενθύμισης.", + "4023": "Δεν είναι δυνατή η δημιουργία κύκλου σχέσης εργασίας.", + "6001": "Το όνομα της ομάδας δεν μπορεί να είναι κενό.", + "6002": "Η ομάδα δεν υπάρχει.", + "6004": "Η ομάδα έχει ήδη πρόσβαση σε αυτό το έργο.", + "6005": "Ο χρήστης είναι ήδη μέλος αυτής της ομάδας.", + "6006": "Δεν είναι δυνατή η διαγραφή του τελευταίου μέλους της ομάδας.", + "6007": "Η ομάδα δεν έχει πρόσβαση στο έργο για να εκτελέσει αυτή την ενέργεια.", + "6008": "Δε βρέθηκε καμία ομάδα για το δεδομένο OIDC ID και εκδότη.", + "6009": "Δε βρέθηκαν ομάδες με ιδιότητα oidcId για το χρήστη.", + "7002": "Ο χρήστης έχει ήδη πρόσβαση στο έργο.", + "7003": "Δεν έχετε πρόσβαση στο έργο.", + "8001": "Η ετικέτα υπάρχει ήδη σε αυτή την εργασία.", + "8002": "Η ετικέτα δεν υπάρχει.", + "8003": "Δεν έχετε πρόσβαση στην ετικέτα.", + "9001": "Η άδεια δεν είναι έγκυρη.", + "10001": "Ο κάδος δεν υπάρχει.", + "10002": "Ο κάδος δεν ανήκει σε αυτό το έργο.", + "10003": "Δεν μπορείτε να αφαιρέσετε τον τελευταίο κάδο ενός έργου.", + "10004": "Δεν μπορείτε να προσθέσετε την εργασία σε αυτόν τον κάδο καθώς έχει ήδη υπερβεί το όριο των εργασιών που μπορεί να κρατήσει.", + "10005": "Μπορεί να υπάρχει μόνο ένας κάδος ολοκληρωμένων ανά έργο.", + "11001": "Το αποθηκευμένο φίλτρο δεν υπάρχει.", + "11002": "Τα αποθηκευμένα φίλτρα δεν είναι διαθέσιμα για τους συνδέσμους διαμοιρασμού.", + "12001": "Ο τύπος οντότητας συνδρομής δεν είναι έγκυρος.", + "12002": "Έχετε ήδη εγγραφεί στην ίδια την οντότητα ή σε μια γονική οντότητα.", + "12003": "Πρέπει να δώσετε ένα χρήστη για να συγκεντρώνει τις συνδρομές.", + "13001": "Ο σύνδεσμος διαμοιρασμού απαιτεί ένα κωδικό πρόσβασης για ταυτοποίηση, αλλά δε δόθηκε κάποιος.", + "13002": "Ο κωδικός πρόσβασης για το σύνδεσμο διαμοιρασμού που δόθηκε δεν είναι έγκυρος.", + "13003": "Το τεκμήριο για το σύνδεσμο διαμοιρασμού που δόθηκε δεν είναι έγκυρο.", + "14001": "Το τεκμήριο api που δόθηκε δεν είναι έγκυρο.", + "14002": "Το δικαίωμα {permission} της ομάδας {group} δεν είναι έγκυρο.", + "error": "Σφάλμα", + "success": "Επιτυχία", + "0001": "Δεν επιτρέπεται να το κάνετε αυτό." + }, + "about": { + "title": "Σχετικά", + "version": "Έκδοση: {version}", + "frontendVersion": "Έκδοση frontend: {version}", + "apiVersion": "Έκδοση API: {version}" + }, + "timeTracking": { + "title": "Ιχνηλάτηση χρόνου", + "stop": "Διακοπή χρονομέτρου", + "logTime": "Καταγραφή χρόνου", + "editEntry": "Επεξεργασία εγγραφής", + "form": { + "task": "Εργασία", + "taskSearch": "Αναζήτηση για μια εργασία…", + "commentPlaceholder": "Σε τι δουλέψατε;", + "save": "Αποθήκευση εγγραφής", + "startTimer": "Έναρξη χρονοµέτρου", + "update": "Ενημέρωση εγγραφής", + "smartFill": "Συμπλήρωση από την τελευταία καταχώριση" + }, + "list": { + "emptyTask": "Δεν καταγράφηκε ακόμη χρόνος για αυτήν την εργασία.", + "emptyFiltered": "Δεν καταγράφηκε χρόνος με βάση τα επιλεγμένα φίλτρα.", + "total": "Σύνολο", + "time": "Ώρα", + "duration": "Διάρκεια" + }, + "browse": { + "selectRange": "Επιλέξτε ένα εύρος", + "userSearch": "Αναζήτηση για ένα χρήστη…" + } + }, + "time": { + "units": { + "seconds": "δευτερόλεπτο|δευτερόλεπτα", + "minutes": "λεπτό|λεπτά", + "hours": "ώρα|ώρες", + "days": "ημέρα|ημέρες", + "weeks": "εβδομάδα|εβδομάδες", + "years": "έτος|έτη" + } + }, + "admin": { + "title": "Διαχείριση", + "labels": { + "users": "Χρήστες", + "tasks": "Εργασίες" + }, + "overview": { + "shares": "Διαμοιρασμοί", + "linkSharesShort": "σύνδεσμος", + "teamSharesShort": "ομάδα", + "userSharesShort": "χρήστης", + "version": "Έκδοση", + "license": "Άδεια", + "licenseValidUntil": "Έγκυρο μέχρι", + "licenseExpiresIn": "σε {days} ημέρες", + "licenseLastVerified": "Τελευταία επαλήθευση", + "licenseNever": "ποτέ", + "licenseLastCheckFailed": "ο τελευταίος έλεγχος απέτυχε", + "licenseFeatures": "Χαρακτηριστικά", + "licenseInstance": "Αναγνωριστικό Στιγμιότυπου", + "licenseManage": "Διαχείριση" + }, + "searchUsersPlaceholder": "Αναζήτηση με όνομα χρήστη ή email…", + "users": { + "status": "Κατάσταση", + "details": "Λεπτομέρειες", + "detailsTitle": "Χρήστης: {username}", + "issuer": "Εκδότης", + "issuerLocal": "Τοπικό", + "issuerUrl": "Url Εκδότη", + "subject": "Θέμα", + "statusActive": "Ενεργό", + "statusEmailConfirmation": "Απαιτείται επιβεβαίωση μέσω email", + "statusDisabled": "Απενεργοποιημένο", + "statusLocked": "Κλειδωμένος λογαριασμός", + "isAdminLabel": "Διαχειριστής", + "addUser": "Προσθήκη χρήστη", + "createTitle": "Δημιουργία χρήστη", + "nameLabel": "Όνομα", + "skipEmailConfirm": "Παράβλεψη επιβεβαίωσης email", + "createSubmit": "Δημιουργία χρήστη", + "saveButton": "Αποθήκευση αλλαγών", + "createdSuccess": "Ο χρήστης {username} δημιουργήθηκε.", + "updatedSuccess": "Ο χρήστης {username} ενημερώθηκε.", + "deletedSuccess": "Ο χρήστης {username} διαγράφηκε.", + "deleteScheduledSuccess": "Ο χρήστης {username} θα λάβει ένα email επιβεβαίωσης για να προγραμματιστεί η διαγραφή.", + "confirmDeleteTitle": "Διαγραφή του χρήστη;", + "confirmDeleteIntro": "Πώς θα διαγραφεί ο χρήστης {username};", + "deleteModeScheduled": "Προγραμματισμός διαγραφής", + "deleteModeScheduledHelp": "Ο προγραμματισμός διαγραφής θα στείλει στο χρήστη ένα email επιβεβαίωσης, αντικατοπτρίζοντας τη διαγραφή του χρήστη που ξεκίνησε ο ίδιος.", + "deleteModeNow": "Διαγραφή τώρα", + "deleteModeNowHelp": "Η διαγραφή τώρα αφαιρεί το χρήστη και όλα τα δεδομένα του αμέσως. Αυτό δεν μπορεί να αναιρεθεί." + }, + "projects": { + "ownerLabel": "Ιδιοκτήτης", + "reassignOwner": "Επανεκχώρηση ιδιοκτήτη", + "reassignTitle": "Επανεκχώρηση {title}", + "reassignedSuccess": "Ο ιδιοκτήτης του έργου επανεκχωρήθηκε.", + "newOwnerLabel": "Νέος ιδιοκτήτης" + } + } +} \ No newline at end of file diff --git a/frontend/src/i18n/lang/en.json b/frontend/src/i18n/lang/en.json index 23ba08757..1cc8212f5 100644 --- a/frontend/src/i18n/lang/en.json +++ b/frontend/src/i18n/lang/en.json @@ -172,6 +172,7 @@ "yyyy\/mm\/dd": "YYYY\/MM\/DD" }, "timeFormat": "Time format", + "timeTrackingDefaultStart": "Time tracking smart-fill start time", "timeFormatOptions": { "12h": "12-hour (AM/PM)", "24h": "24-hour (HH:mm)" @@ -219,6 +220,13 @@ "usernameIs": "Your username for CalDAV is: {0}", "apiTokenHint": "You can also use an API token with CalDAV permission. Create one in {link}." }, + "feeds": { + "title": "Atom Feed", + "howTo": "You can subscribe to your Vikunja notifications from any Atom-compatible feed reader. Use the following URL:", + "usernameIs": "Your username for the feed is: {0}", + "apiTokenHint": "Authenticate with an API token that has the {scope} permission. Create one in {link}.", + "tokenTitle": "Atom feed" + }, "avatar": { "title": "Avatar", "initials": "Initials", @@ -385,6 +393,7 @@ "title": "Duplicate this project", "label": "Duplicate", "text": "Select a parent project which should hold the duplicated project:", + "shares": "Copy shares (users, teams and link shares) to the duplicate", "success": "The project was successfully duplicated." }, "edit": { @@ -463,7 +472,6 @@ "month": "Month", "day": "Day", "hour": "Hour", - "range": "Date Range", "chartLabel": "Project Gantt Chart", "taskBarsForRow": "Task bars for row {rowId}", "taskBarLabel": "Task: {task}. From {startDate} to {endDate}. {dateType}. Click to edit, drag to move.", @@ -492,7 +500,6 @@ "kanban": { "title": "Kanban", "limit": "Limit: {limit}", - "noLimit": "Not Set", "doneBucket": "Done bucket", "doneBucketHint": "All tasks moved into this bucket will automatically marked as done.", "doneBucketHintExtended": "All tasks moved into the done bucket will be marked as done automatically. All tasks marked as done from elsewhere will be moved as well.", @@ -776,7 +783,10 @@ "closeDialog": "Close dialog", "closeQuickActions": "Close quick actions", "skipToContent": "Skip to main content", - "sortBy": "Sort by" + "sortBy": "Sort by", + "dateRange": "Date range", + "notSet": "Not set", + "user": "User" }, "input": { "projectColor": "Project color", @@ -855,6 +865,7 @@ "date": "Date", "ranges": { "today": "Today", + "tomorrow": "Tomorrow", "thisWeek": "This Week", "restOfThisWeek": "The Rest of This Week", "nextWeek": "Next Week", @@ -986,6 +997,7 @@ "repeatAfter": "Set Repeating Interval", "percentDone": "Set Progress", "attachments": "Add Attachments", + "timeTracking": "Track time", "relatedTasks": "Add Relation", "moveProject": "Move", "duplicate": "Duplicate", @@ -1059,7 +1071,10 @@ "addedSuccess": "The comment was added successfully.", "permalink": "Copy permalink to this comment", "sortNewestFirst": "Newest first", - "sortOldestFirst": "Oldest first" + "sortOldestFirst": "Oldest first", + "reply": "Reply", + "jumpToOriginal": "Jump to original comment", + "deletedComment": "deleted comment" }, "mention": { "noUsersFound": "No users found" @@ -1324,7 +1339,8 @@ "none": "You don't have any notifications. Have a nice day!", "explainer": "Notifications will appear here when actions, projects or tasks you subscribed to happen.", "markAllRead": "Mark all notifications as read", - "markAllReadSuccess": "Successfully marked all notifications as read." + "markAllReadSuccess": "Successfully marked all notifications as read.", + "subscribeFeed": "Subscribe to notifications via Atom feed" }, "quickActions": { "notLoggedIn": "Please log in to the main Vikunja window first.", @@ -1451,6 +1467,32 @@ "frontendVersion": "Frontend version: {version}", "apiVersion": "API version: {version}" }, + "timeTracking": { + "title": "Time tracking", + "stop": "Stop timer", + "logTime": "Log time", + "editEntry": "Edit entry", + "form": { + "task": "Task", + "taskSearch": "Search for a task…", + "commentPlaceholder": "What did you work on?", + "save": "Save entry", + "startTimer": "Start timer", + "update": "Update entry", + "smartFill": "Fill from last entry" + }, + "list": { + "emptyTask": "No time tracked for this task yet.", + "emptyFiltered": "No time tracked for the selected filters.", + "total": "Total", + "time": "Time", + "duration": "Duration" + }, + "browse": { + "selectRange": "Select a range", + "userSearch": "Search for a user…" + } + }, "time": { "units": { "seconds": "second|seconds", diff --git a/frontend/src/i18n/lang/es-ES.json b/frontend/src/i18n/lang/es-ES.json index 962beb5f2..98e9f78e6 100644 --- a/frontend/src/i18n/lang/es-ES.json +++ b/frontend/src/i18n/lang/es-ES.json @@ -251,8 +251,7 @@ "default": "Predeterminado", "month": "Mes", "day": "Día", - "hour": "Hora", - "range": "Rango de fechas" + "hour": "Hora" }, "table": { "title": "Tabla", @@ -261,7 +260,6 @@ "kanban": { "title": "Kanban", "limit": "Límite: {limit}", - "noLimit": "No Establecido", "doneBucket": "Contenedor completado", "doneBucketHint": "Todas las tareas movidas a este contenedor se marcarán automáticamente como finalizadas.", "doneBucketHintExtended": "Todas las tareas movidas al contenedor completado se marcarán como finalizadas automáticamente. Todas las tareas marcadas como finalizadas desde otro lugar también se moverán.", diff --git a/frontend/src/i18n/lang/fa-IR.json b/frontend/src/i18n/lang/fa-IR.json new file mode 100644 index 000000000..fdc0681c7 --- /dev/null +++ b/frontend/src/i18n/lang/fa-IR.json @@ -0,0 +1,1522 @@ +{ + "404": { + "title": "یافت نشد", + "text": "صفحه‌ای که درخواست کرده‌اید وجود ندارد." + }, + "home": { + "welcomeNight": "شب بخیر {username}!", + "welcomeNightOwl": "سلام جغد شب‌زنده‌دار {username}", + "welcomeNightBurning": "داری تا دیروقت کار می‌کنی، {username}؟", + "welcomeNightQuiet": "ساعات بی سروصدا، {username}", + "welcomeNightLate": "دیر وقته، {username}", + "welcomeNightMoonlit": "برنامه‌ریزی زیر نور ماه، {username}؟", + "welcomeMorning": "صبح بخیر {username}!", + "welcomeMorningHey": "سلام {username}، آماده‌ای شروع کنیم؟", + "welcomeMorningFresh": "شروعی تازه، {username}", + "welcomeMorningCoffee": "قهوه و کارها، {username}؟", + "welcomeMorningRise": "بلند شو و برنامه‌ریزی کن، {username}", + "welcomeMorningBack": "خوش برگشتی، {username}", + "welcomeMondayFresh": "هفته‌ای تازه، {username}", + "welcomeTuesday": "سه‌شنبه مبارک، {username}", + "welcomeWednesdayMid": "وسط هفته رسید، {username}", + "welcomeThursday": "تقریباً رسیدیم، {username}", + "welcomeFridayPush": "فشار آخر جمعه، {username}؟", + "welcomeSaturday": "حالت آخر هفته، {username}", + "welcomeSundaySession": "جلسه یکشنبه، {username}؟", + "welcomeDay": "سلام {username}!", + "welcomeDayBack": "دوباره برگشتی، {username}", + "welcomeDayFocus": "تمرکز کنیم، {username}", + "welcomeDayKeepGoing": "ادامه بده، {username}", + "welcomeDayWhatsNext": "بعدی چیست، {username}؟", + "welcomeDayGood": "بعد از ظهر بخیر، {username}", + "welcomeEvening": "عصر بخیر {username}!", + "welcomeEveningWind": "داری استراحت می‌کنی، {username}؟", + "welcomeEveningReturns": "{username} برگشت", + "welcomeEveningWrap": "وقت جمع‌بندی است، {username}؟", + "welcomeEveningOneMore": "فقط یک کار دیگر، {username}؟", + "welcomeEveningStill": "هنوز مشغولی، {username}؟", + "lastViewed": "آخرین بازدید", + "addToHomeScreen": "این برنامه را برای دسترسی سریع‌تر و تجربه بهتر به صفحه اصلی خود اضافه کنید.", + "goToOverview": "رفتن به داشبورد", + "project": { + "importText": "تسک ها و پروژه های خود را از سرویس دیگر به vikunja بیاورید:", + "import": "اطلاعات خود را به vikunja درون ریزی کنید" + } + }, + "demo": { + "title": "این نسخه در حالت آزمایشی اجرا می‌شود. از آن برای داده‌های واقعی استفاده نکنید!", + "everythingWillBeDeleted": "همه چیز در بازه‌های زمانی منظم حذف خواهد شد!", + "accountWillBeDeleted": "حساب کاربری شما حذف خواهد شد، از جمله تمام پروژه‌ها، وظایف و فایل‌های پیوستی که ممکن است ایجاد کرده باشید." + }, + "ready": { + "loading": "Vikunja در حال بارگذاری است…", + "errorOccured": "خطایی رخ داد:", + "checkApiUrl": "لطفاً بررسی کنید که آدرس api صحیح باشد.", + "noApiUrlConfigured": "هیچ آدرس API تنظیم نشده است. لطفاً یکی را در زیر وارد کنید:" + }, + "offline": { + "title": "شما آفلاین هستید.", + "text": "لطفاً اتصال شبکه خود را بررسی کرده و دوباره تلاش کنید." + }, + "user": { + "auth": { + "username": "نام کاربری", + "usernameEmail": "نام کاربری یا آدرس ایمیل", + "usernamePlaceholder": "مثلاً frederick", + "email": "آدرس ایمیل", + "emailPlaceholder": "مثلاً frederic{'@'}vikunja.io", + "password": "رمز عبور", + "passwordPlaceholder": "مثلاً •••••••••••", + "forgotPassword": "رمز عبور خود را فراموش کرده‌اید؟", + "resetPassword": "بازنشانی رمز عبور", + "resetPasswordAction": "لینک بازنشانی رمز عبور را برایم ارسال کن", + "resetPasswordSuccess": "صندوق ایمیل خود را بررسی کنید! باید ایمیلی حاوی دستورالعمل بازنشانی رمز عبور دریافت کرده باشید.", + "confirmEmailSuccess": "ایمیل شما با موفقیت تأیید شد! اکنون می‌توانید وارد شوید.", + "totpTitle": "کد احراز هویت دو مرحله‌ای", + "totpPlaceholder": "مثلاً 123456", + "login": "ورود", + "createAccount": "ایجاد حساب کاربری", + "loginWith": "ورود با {provider}", + "authenticating": "در حال احراز هویت…", + "openIdStateError": "وضعیت مطابقت ندارد، ادامه عملیات متوقف شد!", + "openIdGeneralError": "هنگام احراز هویت با سرویس شخص ثالث خطایی رخ داد.", + "openIdTotpRequired": "حساب شما نیاز به احراز هویت دو مرحله‌ای دارد. کد TOTP خود را وارد کرده و دوباره وارد شوید.", + "openIdTotpSubmit": "ادامه", + "oauthMissingParams": "پارامترهای ضروری OAuth موجود نیستند: {params}", + "oauthRedirectedToApp": "شما به برنامه هدایت شدید. اکنون می‌توانید این تب را ببندید.", + "desktopTryDemo": "امتحان نسخه آزمایشی", + "desktopCustomServer": "آدرس سرور سفارشی", + "desktopCustomServerDescription": "برای شروع، آدرس سرور Vikunja خود را وارد کنید.", + "desktopWaitingForAuth": "در انتظار احراز هویت…", + "desktopOAuthError": "احراز هویت ناموفق بود: {error}", + "logout": "خروج", + "emailInvalid": "لطفاً یک آدرس ایمیل معتبر وارد کنید.", + "usernameRequired": "لطفاً یک نام کاربری وارد کنید.", + "usernameMustNotContainSpace": "نام کاربری نباید شامل فاصله باشد.", + "usernameMustNotLookLikeUrl": "نام کاربری نباید شبیه URL باشد.", + "passwordRequired": "لطفاً رمز عبور وارد کنید.", + "passwordNotMin": "رمز عبور باید حداقل ۸ کاراکتر داشته باشد.", + "passwordNotMax": "رمز عبور باید حداکثر ۷۲ کاراکتر داشته باشد.", + "showPassword": "نمایش رمز عبور", + "hidePassword": "مخفی کردن رمز عبور", + "noAccountYet": "هنوز حساب کاربری ندارید؟", + "alreadyHaveAnAccount": "از قبل حساب کاربری دارید؟", + "remember": "مرا وارد نگه دار", + "registrationDisabled": "ثبت‌نام غیرفعال است.", + "passwordResetTokenMissing": "توکن بازنشانی رمز عبور موجود نیست.", + "registrationFailed": "هنگام ثبت‌نام خطایی رخ داد. لطفاً اطلاعات وارد شده را بررسی کرده و دوباره تلاش کنید." + }, + "settings": { + "bots": { + "title": "کاربران ربات", + "description": "کاربران ربات، کاربران فقط API هستند که متعلق به شما هستند. آنها می‌توانند به پروژه‌ها اضافه شوند، وظایف به آنها اختصاص داده شود و با توکن‌های API احراز هویت شوند. آنها نمی‌توانند به صورت تعاملی وارد شوند.", + "namePlaceholder": "دستیار من", + "create": "ایجاد ربات", + "enable": "فعال کردن", + "badge": "ربات", + "delete": { + "header": "حذف این کاربر ربات", + "text1": "آیا مطمئن هستید که می‌خواهید کاربر ربات \"{username}\" را حذف کنید؟", + "text2": "این کار غیرقابل برگشت است. هرگونه توکن API متعلق به این ربات باطل خواهد شد." + } + }, + "title": "تنظیمات", + "newPasswordTitle": "به‌روزرسانی رمز عبور شما", + "newPassword": "رمز عبور جدید", + "currentPassword": "رمز عبور فعلی", + "currentPasswordPlaceholder": "رمز عبور فعلی شما", + "passwordUpdateSuccess": "رمز عبور با موفقیت به‌روزرسانی شد.", + "updateEmailTitle": "به‌روزرسانی آدرس ایمیل شما", + "updateEmailNew": "آدرس ایمیل جدید", + "updateEmailSuccess": "آدرس ایمیل شما با موفقیت به‌روزرسانی شد. ما لینکی برای تأیید آن برای شما ارسال کرده‌ایم.", + "general": { + "title": "تنظیمات عمومی", + "name": "نام من", + "newName": "نام جدید", + "savedSuccess": "تنظیمات با موفقیت به‌روزرسانی شد.", + "emailReminders": "یادآوری وظایف را از طریق ایمیل برای من ارسال کن", + "overdueReminders": "هر روز خلاصه‌ای از وظایف انجام نشده و معوقه خود را برای من ارسال کن", + "discoverableByName": "به سایر کاربران اجازه بده تا با جستجوی نام من، مرا به عنوان عضو تیم‌ها یا پروژه‌ها اضافه کنند", + "discoverableByEmail": "به سایر کاربران اجازه بده تا با جستجوی ایمیل کامل من، مرا به عنوان عضو تیم‌ها یا پروژه‌ها اضافه کنند", + "playSoundWhenDone": "هنگام علامت‌گذاری وظایف به عنوان انجام شده، صدایی پخش کن", + "allowIconChanges": "نمایش لوگوهای ویژه در زمان‌های خاص", + "alwaysShowBucketTaskCount": "همیشه تعداد وظایف را در سطل‌های کانبان نمایش بده", + "defaultTaskRelationType": "نوع پیش‌فرض رابطه وظیفه", + "weekStart": "هفته از روز شروع می‌شود", + "weekStartSunday": "یکشنبه", + "weekStartMonday": "دوشنبه", + "weekStartTuesday": "سه‌شنبه", + "weekStartWednesday": "چهارشنبه", + "weekStartThursday": "پنج‌شنبه", + "weekStartFriday": "جمعه", + "weekStartSaturday": "شنبه", + "language": "زبان", + "defaultProject": "پروژه پیش‌فرض", + "defaultView": "نمای پیش‌فرض", + "timezone": "منطقه زمانی", + "overdueTasksRemindersTime": "زمان ارسال ایمیل یادآوری وظایف معوق", + "quickAddDefaultReminders": "یادآورهای پیش‌فرض برای افزودن سریع", + "quickAddDefaultRemindersDescription": "این یادآورها به طور خودکار به هر وظیفه‌ای که از طریق افزودن سریع جادویی ایجاد می‌شود و دارای تاریخ سررسید است، اضافه خواهند شد.", + "quickAddDefaultRemindersHint": "یک یا چند یادآور را نسبت به تاریخ سررسید وظیفه اضافه کنید. برای غیرفعال کردن، خالی بگذارید.", + "filterUsedOnOverview": "فیلتر ذخیره شده مورد استفاده در صفحه نمای کلی", + "showLastViewed": "نمایش آخرین پروژه‌های مشاهده شده در صفحه نمای کلی", + "minimumPriority": "حداقل اولویت وظیفه قابل مشاهده", + "dateDisplay": "قالب نمایش تاریخ", + "dateDisplayOptions": { + "relative": "نسبی (مثلاً ۳ روز پیش)", + "mm-dd-yyyy": "MM-DD-YYYY", + "dd-mm-yyyy": "DD-MM-YYYY", + "yyyy-mm-dd": "YYYY-MM-DD", + "mm/dd/yyyy": "MM/DD/YYYY", + "dd/mm/yyyy": "DD/MM/YYYY", + "yyyy/mm/dd": "YYYY/MM/DD" + }, + "timeFormat": "قالب زمان", + "timeFormatOptions": { + "12h": "۱۲ ساعته (AM/PM)", + "24h": "۲۴ ساعته (HH:mm)" + }, + "externalUserNameChange": "نام شما توسط ارائه‌دهنده ورود شما ({provider}) مدیریت می‌شود. برای تغییر آن، لطفاً در همانجا به‌روزرسانی کنید." + }, + "sections": { + "personalInformation": "اطلاعات شخصی", + "taskAndNotifications": "پروژه‌ها و وظایف", + "privacy": "حریم خصوصی", + "localization": "بومی‌سازی", + "appearance": "ظاهر و رفتار", + "desktop": "برنامه دسکتاپ" + }, + "desktop": { + "quickEntryShortcut": "میانبر ورود سریع", + "shortcutRecorderPlaceholder": "برای تنظیم میانبر کلیک کنید", + "shortcutRecorderRecording": "یک ترکیب کلید را فشار دهید…" + }, + "totp": { + "title": "احراز هویت دو عاملی", + "enroll": "ثبت‌نام", + "finishSetupPart1": "برای تکمیل راه‌اندازی خود، از این کلید مخفی در برنامه TOTP خود (مانند Google Authenticator) استفاده کنید:", + "finishSetupPart2": "پس از آن، یک کد از برنامه خود را در زیر وارد کنید.", + "scanQR": "به طور جایگزین می‌توانید این کد QR را اسکن کنید:", + "passcode": "گذرواژه", + "passcodePlaceholder": "کدی که توسط برنامه TOTP شما تولید شده است", + "confirmNotice": "پس از فعال کردن احراز هویت دو عاملی، از تمام جلسات خارج خواهید شد و باید دوباره وارد شوید.", + "setupSuccess": "شما با موفقیت احراز هویت دو عاملی را تنظیم کردید!", + "enterPassword": "لطفاً گذرواژه خود را وارد کنید", + "disable": "غیرفعال کردن احراز هویت دو عاملی", + "confirmSuccess": "شما با موفقیت احراز هویت دو عاملی را فعال کردید!", + "disableSuccess": "احراز هویت دو عاملی با موفقیت غیرفعال شد." + }, + "caldav": { + "title": "CalDAV", + "howTo": "شما می‌توانید Vikunja را به کلاینت‌های CalDAV متصل کنید تا تمام وظایف خود را از کلاینت‌های مختلف مشاهده و مدیریت کنید. این URL را در کلاینت خود وارد کنید:", + "more": "اطلاعات بیشتر درباره CalDAV در Vikunja", + "tokens": "توکن‌های CalDAV", + "tokensHowTo": "برای احراز هویت CalDAV می‌توانید از گذرواژه عادی حساب خود یا یک توکن اختصاصی CalDAV استفاده کنید.", + "createToken": "ایجاد یک توکن CalDAV", + "tokenCreated": "توکن جدید شما این است: {token}", + "wontSeeItAgain": "آن را یادداشت کنید یا در مکانی امن ذخیره کنید — دیگر قادر به دیدن آن نخواهید بود.", + "mustUseToken": "شما باید یک توکن CalDAV برای استفاده از CalDAV با هر کلاینت شخص ثالث ایجاد کنید. توکن را در فیلد گذرواژه کلاینت خود وارد کنید.", + "usernameIs": "نام کاربری شما برای CalDAV این است: {0}", + "apiTokenHint": "شما همچنین می‌توانید از یک توکن API با مجوز CalDAV استفاده کنید. یکی را در {link} ایجاد کنید." + }, + "avatar": { + "title": "آواتار", + "initials": "حروف اول", + "gravatar": "Gravatar", + "marble": "Marble", + "upload": "بارگذاری", + "uploadAvatar": "بارگذاری آواتار", + "statusUpdateSuccess": "وضعیت آواتار با موفقیت به‌روز شد!", + "setSuccess": "آواتار با موفقیت تنظیم شد!", + "ldap": "آواتار شما به طور خودکار از سرویس دایرکتوری سازمان شما (LDAP) همگام‌سازی می‌شود. برای اطلاعات بیشتر در مورد نحوه تغییر آن، می‌توانید از تیم IT خود بپرسید.", + "openid": "آواتار شما به طور خودکار از ارائه‌دهنده ورود شما ({provider}) همگام‌سازی می‌شود. برای تغییر آن، لطفاً در همانجا به‌روزرسانی کنید." + }, + "quickAddMagic": { + "title": "حالت جادویی افزودن سریع", + "disabled": "غیرفعال", + "todoist": "Todoist", + "vikunja": "Vikunja" + }, + "appearance": { + "title": "طرح رنگ", + "colorScheme": { + "light": "روشن", + "system": "سیستم", + "dark": "تیره" + } + }, + "backgroundBrightness": { + "title": "روشنایی پس‌زمینه" + }, + "webhooks": { + "title": "اعلان‌های وب‌هوک", + "description": "آدرس‌های URL وب‌هوک را پیکربندی کنید تا هنگام فعال شدن رویدادهای یادآوری یا معوق، درخواست‌های POST دریافت کنید. این وب‌هوک‌ها رویدادها را از تمام پروژه‌های شما دریافت می‌کنند." + }, + "apiTokens": { + "title": "توکن‌های API", + "general": "توکن‌های API به شما امکان می‌دهند بدون نیاز به اعتبارنامه کاربر از API Vikunja استفاده کنید.", + "apiDocs": "مستندات API را بررسی کنید", + "createAToken": "ایجاد یک توکن", + "createToken": "ایجاد توکن", + "30d": "۳۰ روز", + "60d": "۶۰ روز", + "90d": "۹۰ روز", + "permissionExplanation": "مجوزها به شما امکان می‌دهند دامنه کاری را که یک توکن API مجاز به انجام آن است، محدود کنید.", + "titleRequired": "عنوان الزامی است", + "permissionRequired": "لطفاً حداقل یک مجوز از لیست انتخاب کنید.", + "expired": "این توکن منقضی شده است {ago}.", + "tokenCreatedSuccess": "توکن API جدید شما این است: {token}", + "tokenCreatedNotSeeAgain": "آن را در مکانی امن ذخیره کنید، دیگر آن را نخواهید دید!", + "presets": { + "title": "پیش‌تنظیمات سریع", + "readOnly": "فقط خواندنی", + "tasks": "مدیریت وظایف", + "projects": "مدیریت پروژه", + "fullAccess": "دسترسی کامل" + }, + "delete": { + "header": "حذف این توکن", + "text1": "آیا مطمئن هستید که می‌خواهید توکن \"{token}\" را حذف کنید؟", + "text2": "این کار دسترسی تمام برنامه‌ها یا ادغام‌هایی را که از آن استفاده می‌کنند، لغو خواهد کرد. شما نمی‌توانید این کار را برگردانید." + }, + "attributes": { + "title": "عنوان", + "titlePlaceholder": "عنوانی را وارد کنید که بعداً آن را تشخیص دهید", + "expiresAt": "انقضا در", + "permissions": "مجوزها" + } + }, + "sessions": { + "title": "جلسات", + "description": "این‌ها تمام دستگاه‌هایی هستند که در حال حاضر به حساب شما وارد شده‌اند. می‌توانید هر جلسه را لغو کنید تا آن دستگاه خارج شود. ممکن است تا ۱۰ دقیقه طول بکشد تا لغو جلسه به طور کامل اعمال شود.", + "deviceInfo": "دستگاه", + "ipAddress": "آدرس IP", + "lastActive": "آخرین فعالیت", + "current": "جلسه فعلی", + "delete": { + "header": "لغو جلسه", + "text": "آیا مطمئن هستید که می‌خواهید این جلسه را لغو کنید؟ دستگاه از حساب خارج خواهد شد. ممکن است تا ۱۰ دقیقه طول بکشد تا جلسه به طور کامل منقضی شود." + }, + "deleteSuccess": "جلسه با موفقیت لغو شد. ممکن است تا ۱۰ دقیقه طول بکشد تا جلسه به طور کامل منقضی شود.", + "noOtherSessions": "هیچ جلسه فعال دیگری وجود ندارد." + } + }, + "deletion": { + "title": "حذف حساب Vikunja شما", + "text1": "حذف حساب شما دائمی است و قابل بازگشت نیست. ما تمام پروژه‌ها، وظایف و هر چیز مرتبط با آن را حذف خواهیم کرد.", + "text2": "برای ادامه، لطفاً رمز عبور خود را وارد کنید. ایمیلی حاوی دستورالعمل‌های بیشتر دریافت خواهید کرد.", + "text3": "برای ادامه، لطفاً دکمه زیر را فشار دهید. ایمیلی حاوی دستورالعمل‌های بیشتر دریافت خواهید کرد.", + "confirm": "حذف حساب من", + "requestSuccess": "درخواست با موفقیت انجام شد. ایمیلی حاوی دستورالعمل‌های بیشتر دریافت خواهید کرد.", + "passwordRequired": "لطفاً رمز عبور خود را وارد کنید.", + "confirmSuccess": "شما با موفقیت حذف حساب خود را تأیید کردید. حساب شما را در سه روز آینده حذف خواهیم کرد.", + "scheduled": "حساب Vikunja شما در {date} ({dateSince}) حذف خواهد شد.", + "scheduledCancel": "برای لغو حذف حساب خود، اینجا کلیک کنید.", + "scheduledCancelText": "برای لغو حذف حساب خود، لطفاً رمز عبور خود را در زیر وارد کنید:", + "scheduledCancelButton": "برای لغو حذف حساب خود، لطفاً دکمه زیر را فشار دهید:", + "scheduledCancelConfirm": "لغو حذف حساب من", + "scheduledCancelSuccess": "حساب شما حذف نخواهد شد." + }, + "export": { + "title": "صادر کردن داده‌های Vikunja شما", + "description": "می‌توانید درخواستی برای کپی تمام داده‌های Vikunja خود ارسال کنید. این شامل پروژه‌ها، وظایف و هر چیزی است که به آن‌ها مرتبط است. می‌توانید این داده‌ها را از طریق تابع مهاجرت در هر نمونه Vikunja وارد کنید.", + "descriptionPasswordRequired": "لطفاً برای ادامه رمز عبور خود را وارد کنید:", + "request": "درخواست کپی داده‌های Vikunja من", + "success": "شما با موفقیت داده‌های Vikunja خود را درخواست کردید! پس از آماده شدن برای دانلود، ایمیلی برای شما ارسال خواهیم کرد.", + "downloadTitle": "دانلود داده‌های صادر شده Vikunja شما", + "ready": "صادرات شما آماده دانلود است. می‌توانید آن را تا {0} دانلود کنید.", + "requestNew": "درخواست صادرات دیگر" + } + }, + "project": { + "archivedMessage": "این پروژه بایگانی شده است. امکان ایجاد وظایف جدید یا ویرایش وظایف برای آن وجود ندارد.", + "archived": "بایگانی شده", + "showArchived": "نمایش بایگانی شده‌ها", + "title": "عنوان", + "color": "رنگ", + "projects": "پروژه‌ها", + "parent": "پروژه والد", + "search": "برای جستجوی پروژه تایپ کنید…", + "searchSelect": "برای انتخاب این پروژه کلیک کنید یا Enter را فشار دهید", + "shared": "پروژه‌های اشتراک‌گذاری شده", + "noDescriptionAvailable": "هیچ توضیحی برای پروژه در دسترس نیست.", + "inboxTitle": "صندوق ورودی", + "favorite": "این پروژه را به عنوان مورد علاقه علامت‌گذاری کنید", + "unfavorite": "این پروژه را از موارد دلخواه حذف کنید", + "openSettingsMenu": "منوی تنظیمات پروژه را باز کنید", + "description": "توضیحات پروژه", + "favoriteDescription": "این پروژه تمام وظایف مورد علاقه را دارد.", + "create": { + "header": "پروژه جدید", + "titlePlaceholder": "عنوان پروژه در اینجا قرار می‌گیرد…", + "addTitleRequired": "لطفاً یک عنوان مشخص کنید.", + "createdSuccess": "پروژه با موفقیت ایجاد شد.", + "addProjectRequired": "لطفاً یک پروژه مشخص کنید یا یک پروژه پیش‌فرض در تنظیمات تعیین کنید." + }, + "archive": { + "title": "بایگانی \"{project}\"", + "archive": "این پروژه را بایگانی کنید", + "unarchive": "این پروژه را از حالت بایگانی خارج کنید", + "unarchiveText": "شما قادر به ایجاد وظایف یا ویرایش آن خواهید بود.", + "archiveText": "تا زمانی که آن را از حالت بایگانی خارج نکنید، قادر به ویرایش این پروژه یا ایجاد وظایف نخواهید بود.", + "success": "پروژه با موفقیت بایگانی شد." + }, + "background": { + "title": "تنظیم پس‌زمینه پروژه", + "remove": "حذف پس‌زمینه", + "upload": "یک پس‌زمینه از رایانه خود انتخاب کنید", + "searchPlaceholder": "جستجوی پس‌زمینه…", + "poweredByUnsplash": "ارائه شده توسط Unsplash", + "loadMore": "بارگذاری عکس‌های بیشتر", + "success": "پس‌زمینه با موفقیت تنظیم شد!", + "removeSuccess": "پس‌زمینه با موفقیت حذف شد!" + }, + "delete": { + "title": "حذف \"{project}\"", + "header": "این پروژه را حذف کنید", + "text1": "آیا مطمئن هستید که می‌خواهید این پروژه و تمام محتویات آن را حذف کنید؟", + "text2": "این شامل تمام وظایف است و قابل بازگشت نیست!", + "success": "پروژه با موفقیت حذف شد.", + "tasksToDelete": "این کار تقریباً {count} وظیفه را به طور برگشت‌ناپذیر حذف می‌کند.", + "tasksAndChildProjectsToDelete": "این کار تقریباً {tasks} وظیفه و {projects} پروژه را به طور برگشت‌ناپذیر حذف می‌کند.", + "noTasksToDelete": "این پروژه حاوی هیچ وظیفه‌ای نیست، حذف آن باید بی‌خطر باشد." + }, + "duplicate": { + "title": "تکثیر این پروژه", + "label": "تکثیر", + "text": "پروژه والد را انتخاب کنید که پروژه تکثیر شده را در خود نگه دارد:", + "success": "پروژه با موفقیت تکثیر شد." + }, + "edit": { + "header": "ویرایش این پروژه", + "title": "ویرایش \"{project}\"", + "titlePlaceholder": "عنوان پروژه در اینجا قرار می‌گیرد…", + "identifierTooltip": "شناسه پروژه می‌تواند برای شناسایی منحصر به فرد یک وظیفه در پروژه‌های مختلف استفاده شود. می‌توانید آن را خالی بگذارید تا غیرفعال شود.", + "identifier": "شناسه پروژه", + "identifierPlaceholder": "شناسه پروژه در اینجا قرار می‌گیرد…", + "description": "توضیحات", + "descriptionPlaceholder": "برای این پروژه توضیحی وارد کنید، برای گزینه‌های بیشتر '/' را بزنید…", + "color": "رنگ", + "success": "پروژه با موفقیت به‌روزرسانی شد." + }, + "share": { + "header": "اشتراک‌گذاری این پروژه", + "title": "اشتراک‌گذاری \"{project}\"", + "share": "اشتراک‌گذاری", + "links": { + "title": "لینک‌های اشتراک‌گذاری", + "what": "لینک اشتراک‌گذاری چیست؟", + "explanation": "لینک‌های اشتراک‌گذاری به شما امکان می‌دهند به راحتی یک پروژه را با کاربرانی که حساب کاربری در Vikunja ندارند، به اشتراک بگذارید.", + "create": "ایجاد یک لینک اشتراک‌گذاری", + "name": "نام (اختیاری)", + "namePlaceholder": "مثلاً لورم ایپسوم", + "nameExplanation": "تمام اقدامات انجام شده توسط این اشتراک‌گذاری لینک با نام نمایش داده می‌شود.", + "password": "رمز عبور (اختیاری)", + "passwordExplanation": "هنگام ورود، از کاربر خواسته می‌شود این رمز عبور را وارد کند.", + "noName": "نامی تنظیم نشده است", + "remove": "حذف اشتراک‌گذاری لینک", + "removeText": "آیا مطمئن هستید که می‌خواهید این اشتراک‌گذاری لینک را حذف کنید؟ دیگر امکان دسترسی به این پروژه با این اشتراک‌گذاری لینک وجود نخواهد داشت. این عمل قابل بازگشت نیست!", + "createSuccess": "اشتراک‌گذاری لینک با موفقیت ایجاد شد.", + "deleteSuccess": "اشتراک‌گذاری لینک با موفقیت حذف شد", + "view": "مشاهده", + "sharedBy": "اشتراک‌گذاری شده توسط {0}" + }, + "userTeam": { + "typeUser": "کاربر | کاربران", + "typeTeam": "تیم | تیم‌ها", + "shared": "اشتراک‌گذاری شده با این {type}", + "you": "شما", + "notShared": "هنوز با هیچ {type}ی اشتراک‌گذاری نشده است.", + "removeHeader": "حذف یک {type} از {sharable}", + "removeText": "آیا مطمئن هستید که می‌خواهید این {sharable} را از {type} حذف کنید؟ این عمل قابل بازگشت نیست!", + "removeSuccess": "{sharable} با موفقیت از {type} حذف شد.", + "addedSuccess": "{type} با موفقیت اضافه شد.", + "updatedSuccess": "{type} با موفقیت اضافه شد." + }, + "permission": { + "title": "مجوز", + "read": "فقط خواندنی", + "readWrite": "خواندن و نوشتن", + "admin": "مدیر" + }, + "attributes": { + "link": "لینک", + "delete": "حذف" + } + }, + "first": { + "title": "اولین مشاهده" + }, + "list": { + "title": "لیست", + "add": "افزودن", + "addPlaceholder": "افزودن یک وظیفه…", + "empty": "این پروژه در حال حاضر خالی است.", + "newTaskCta": "ایجاد یک وظیفه.", + "editTask": "ویرایش وظیفه", + "sort": "مرتب‌سازی" + }, + "gantt": { + "title": "گانت", + "size": "اندازه", + "default": "پیش‌فرض", + "month": "ماه", + "day": "روز", + "hour": "ساعت", + "chartLabel": "نمودار گانت پروژه", + "taskBarsForRow": "نوارهای وظیفه برای ردیف {rowId}", + "taskBarLabel": "وظیفه: {task}. از {startDate} تا {endDate}. {dateType}. برای ویرایش کلیک کنید، برای جابجایی بکشید.", + "scheduledDates": "تاریخ‌های زمان‌بندی شده", + "estimatedDates": "تاریخ‌های تخمینی", + "resizeStartDate": "تغییر اندازه تاریخ شروع برای وظیفه {task}", + "resizeEndDate": "تغییر اندازه تاریخ پایان برای وظیفه {task}", + "timelineHeader": "سربرگ جدول زمانی با ماه‌ها و روزها", + "monthsRow": "ردیف ماه‌ها", + "daysRow": "ردیف روزها", + "monthLabel": "ماه: {month}", + "dayLabel": "روز: {date}, {weekday}", + "dayLabelToday": "امروز: {date}, {weekday}", + "taskAriaLabel": "وظیفه: {task}", + "taskAriaLabelById": "وظیفه {id}", + "partialDatesStart": "فقط تاریخ شروع (بدون پایان)", + "partialDatesEnd": "فقط تاریخ پایان (بدون شروع)", + "expandGroup": "گسترش گروه: {task}", + "collapseGroup": "بستن گروه: {task}", + "toggleRelationArrows": "تغییر وضعیت نمایش فلش‌های ارتباط" + }, + "table": { + "title": "جدول", + "columns": "ستون‌ها" + }, + "kanban": { + "title": "کانبان", + "limit": "محدودیت: {limit}", + "doneBucket": "سطل انجام شده", + "doneBucketHint": "تمام وظایفی که به این سطل منتقل شوند به طور خودکار به عنوان انجام شده علامت‌گذاری می‌شوند.", + "doneBucketHintExtended": "تمام وظایفی که به سطل انجام شده منتقل شوند به طور خودکار علامت‌گذاری می‌شوند. همچنین تمام وظایفی که از جای دیگر به عنوان انجام شده علامت‌گذاری شوند نیز به اینجا منتقل خواهند شد.", + "doneBucketSavedSuccess": "سطل انجام شده با موفقیت ذخیره شد.", + "defaultBucket": "سطل پیش‌فرض", + "defaultBucketHint": "هنگام ایجاد وظایف بدون تعیین سطل، آن‌ها به این سطل اضافه خواهند شد.", + "defaultBucketSavedSuccess": "سطل پیش‌فرض با موفقیت ذخیره شد.", + "deleteLast": "شما نمی‌توانید آخرین سطل را حذف کنید.", + "addTaskPlaceholder": "عنوان وظیفه جدید را وارد کنید…", + "addTask": "افزودن وظیفه", + "addAnotherTask": "افزودن وظیفه دیگر", + "addBucket": "ایجاد سطل", + "addBucketPlaceholder": "عنوان سطل جدید را وارد کنید…", + "deleteHeaderBucket": "حذف سطل", + "deleteBucketText1": "آیا مطمئن هستید که می‌خواهید این سطل را حذف کنید؟", + "deleteBucketText2": "این کار وظایف را حذف نمی‌کند، بلکه آن‌ها را به سطل پیش‌فرض منتقل می‌کند.", + "deleteBucketSuccess": "سطل با موفقیت حذف شد.", + "bucketTitleSavedSuccess": "عنوان سطل با موفقیت ذخیره شد.", + "bucketLimitSavedSuccess": "محدودیت سطل با موفقیت ذخیره شد.", + "collapse": "بستن این سطل", + "bucketLimitReached": "شما به محدودیت سطل رسیده‌اید. برای افزودن وظایف جدید، وظایف را حذف کنید یا محدودیت را افزایش دهید.", + "bucketOptions": "گزینه‌های سطل" + }, + "pseudo": { + "favorites": { + "title": "موارد دلخواه" + } + }, + "webhooks": { + "title": "وب‌هوک‌ها", + "targetUrl": "URL هدف", + "targetUrlInvalid": "لطفاً یک URL معتبر ارائه دهید.", + "events": "رویدادها", + "eventsHint": "همه رویدادهایی که این وب‌هوک باید برای آن‌ها به‌روزرسانی دریافت کند را انتخاب کنید (در پروژه فعلی).", + "mustSelectEvents": "شما باید حداقل یک رویداد را انتخاب کنید.", + "delete": "حذف این وب‌هوک", + "deleteText": "آیا مطمئن هستید که می‌خواهید این وب‌هوک را حذف کنید؟ اهداف خارجی دیگر از رویدادهای آن مطلع نخواهند شد.", + "deleteSuccess": "وب‌هوک با موفقیت حذف شد.", + "create": "ایجاد وب‌هوک", + "secret": "کلید مخفی", + "basicauthuser": "کاربر احراز هویت پایه", + "basicauthpassword": "رمز عبور احراز هویت پایه", + "basicauthlink": "استفاده از احراز هویت پایه؟", + "secretHint": "در صورت ارائه، تمام درخواست‌ها به URL هدف وب‌هوک با استفاده از HMAC امضا خواهند شد.", + "secretDocs": "برای جزئیات بیشتر در مورد نحوه استفاده از کلیدهای مخفی، مستندات را بررسی کنید." + }, + "views": { + "header": "ویرایش نماها", + "title": "عنوان", + "actions": "اقدامات", + "kind": "نوع", + "bucketConfigMode": "حالت پیکربندی سطل", + "bucketConfig": "پیکربندی سطل", + "bucketConfigManual": "دستی", + "filter": "فیلتر", + "create": "ایجاد نما", + "createSuccess": "نما با موفقیت ایجاد شد.", + "titleRequired": "لطفاً یک عنوان ارائه دهید.", + "delete": "حذف این نما", + "deleteText": "آیا مطمئن هستید که می‌خواهید این نما را حذف کنید؟ دیگر امکان استفاده از آن برای مشاهده وظایف در این پروژه وجود نخواهد داشت. این عمل هیچ وظیفه‌ای را حذف نمی‌کند. این قابل بازگشت نیست!", + "deleteSuccess": "نما با موفقیت حذف شد.", + "onlyAdminsCanEdit": "فقط مدیران پروژه می‌توانند نماها را ویرایش کنند.", + "updateSuccess": "نما با موفقیت به‌روزرسانی شد." + } + }, + "filters": { + "title": "فیلترها", + "clear": "پاک کردن فیلترها", + "showResults": "نمایش نتایج", + "noResults": "نتیجه‌ای یافت نشد", + "fromView": "نمای فعلی نیز دارای فیلتر تنظیم شده است:", + "fromViewBoth": "این با آنچه در اینجا وارد می‌کنید ترکیب می‌شود.", + "attributes": { + "title": "عنوان", + "titlePlaceholder": "عنوان فیلتر ذخیره شده در اینجا قرار می‌گیرد...", + "description": "توضیحات", + "descriptionPlaceholder": "یک توضیح برای این فیلتر در اینجا اضافه کنید، برای گزینه‌های بیشتر '/' را بزنید...", + "includeNulls": "شامل وظایفی که مقداری برای آن‌ها تنظیم نشده است" + }, + "create": { + "title": "فیلتر ذخیره شده جدید", + "description": "یک فیلتر ذخیره شده یک پروژه مجازی است که هر بار که به آن دسترسی پیدا می‌شود، از مجموعه‌ای از فیلترها محاسبه می‌شود.", + "action": "ایجاد فیلتر ذخیره شده", + "titleRequired": "لطفاً یک عنوان برای فیلتر ارائه دهید." + }, + "delete": { + "header": "حذف این فیلتر ذخیره شده", + "text": "آیا مطمئن هستید که می‌خواهید این فیلتر ذخیره شده را حذف کنید؟", + "success": "فیلتر با موفقیت حذف شد." + }, + "edit": { + "title": "ویرایش این فیلتر ذخیره شده", + "success": "فیلتر با موفقیت ذخیره شد." + }, + "query": { + "placeholder": "یک کوئری جستجو یا فیلتر را تایپ کنید...", + "help": { + "intro": "برای فیلتر کردن وظایف، می‌توانید از سینتکس کوئری مشابه SQL استفاده کنید. فیلدهای موجود برای فیلتر کردن عبارتند از:", + "link": "این چگونه کار می‌کند؟", + "canUseDatemath": "می‌توانید از محاسبات تاریخ برای تنظیم تاریخ‌های نسبی استفاده کنید. برای اطلاعات بیشتر روی مقدار تاریخ در کوئری کلیک کنید.", + "fields": { + "done": "اینکه آیا وظیفه تکمیل شده است یا خیر", + "priority": "سطح اولویت وظیفه (۱-۵)", + "percentDone": "درصد تکمیل وظیفه (۰-۱۰۰)", + "dueDate": "تاریخ سررسید وظیفه", + "startDate": "تاریخ شروع وظیفه", + "endDate": "تاریخ پایان وظیفه", + "doneAt": "تاریخ و زمان تکمیل وظیفه", + "assignees": "مسئولین وظیفه", + "labels": "برچسب‌های مرتبط با وظیفه", + "project": "پروژه‌ای که وظیفه به آن تعلق دارد (فقط برای فیلترهای ذخیره شده موجود است، در سطح پروژه نیست)", + "reminders": "یادآورهای وظیفه به عنوان یک فیلد تاریخ، تمام وظایفی را که حداقل یک یادآور مطابق با کوئری دارند برمی‌گرداند", + "created": "زمان و تاریخ ایجاد وظیفه", + "updated": "زمان و تاریخ آخرین تغییر وظیفه" + }, + "operators": { + "intro": "عملگرهای موجود برای فیلتر کردن عبارتند از:", + "notEqual": "برابر نیست با", + "equal": "برابر است با", + "greaterThan": "بزرگتر از", + "greaterThanOrEqual": "بزرگتر یا مساوی با", + "lessThan": "کوچکتر از", + "lessThanOrEqual": "کوچکتر یا مساوی با", + "like": "مطابق با الگو (با استفاده از کاراکتر جانشین %)", + "in": "مطابق با هر مقداری در یک لیست مقادیر جدا شده با کاما", + "notIn": "مطابق با هر مقداری که در لیست مقادیر جدا شده با کاما وجود ندارد" + }, + "logicalOperators": { + "intro": "برای ترکیب چندین شرط، می‌توانید از عملگرهای منطقی زیر استفاده کنید:", + "and": "عملگر AND، در صورتی که همه شروط درست باشند مطابقت دارد", + "or": "عملگر OR، در صورتی که هر یک از شروط درست باشند مطابقت دارد", + "parentheses": "پرانتز برای گروه‌بندی شروط" + }, + "examples": { + "intro": "در اینجا چند مثال از کوئری‌های فیلتر آورده شده است:", + "priorityEqual": "وظایفی با سطح اولویت ۴ را مطابقت می‌دهد", + "dueDatePast": "وظایفی با تاریخ سررسید در گذشته را مطابقت می‌دهد", + "undoneHighPriority": "وظایف انجام نشده با اولویت ۳ یا بالاتر را مطابقت می‌دهد", + "assigneesIn": "وظایفی که به \"user1\" یا \"user2\" اختصاص داده شده‌اند را مطابقت می‌دهد", + "priorityOneOrTwoPastDue": "وظایفی با اولویت ۱ یا ۲ و تاریخ سررسید در گذشته را مطابقت می‌دهد" + } + } + } + }, + "sorting": { + "manually": "دستی", + "apply": "اعمال مرتب‌سازی", + "description": "نحوه مرتب‌سازی وظایف در این لیست را انتخاب کنید. هنگام مرتب‌سازی دستی، می‌توانید وظایف را با کشیدن و رها کردن مجدداً مرتب کنید.", + "options": { + "titleAsc": "عنوان (الف تا ی)", + "titleDesc": "عنوان (ی تا الف)", + "priorityDesc": "اولویت (بالاترین اول اول)", + "priorityAsc": "اولویت (پایین‌ترین اول اول)", + "dueDateAsc": "تاریخ سررسید (زودترین اول اول)", + "dueDateDesc": "تاریخ سررسید (دیرترین اول اول)", + "startDateAsc": "تاریخ شروع (زودترین اول اول)", + "startDateDesc": "تاریخ شروع (دیرترین اول اول)", + "endDateAsc": "تاریخ پایان (زودترین اول اول)", + "endDateDesc": "تاریخ پایان (دیرترین اول اول)", + "percentDoneDesc": "درصد تکمیل (بیشترین اول اول)", + "percentDoneAsc": "درصد تکمیل (کمترین اول اول)", + "createdDesc": "ایجاد شده (جدیدترین اول اول)", + "createdAsc": "ایجاد شده (قدیمی‌ترین اول اول)", + "updatedDesc": "به‌روز شده (جدیدترین اول اول)", + "updatedAsc": "به‌روز شده (قدیمی‌ترین اول اول)" + } + }, + "migrate": { + "title": "وارد کردن از سرویس‌های دیگر", + "titleService": "داده‌های خود را از {name} به Vikunja وارد کنید", + "description": "برای شروع روی لوگوی یکی از سرویس‌های شخص ثالث زیر کلیک کنید.", + "descriptionDo": "Vikunja تمام لیست‌ها، وظایف، یادداشت‌ها، یادآورها و فایل‌هایی که به آن‌ها دسترسی دارید را وارد خواهد کرد.", + "authorize": "برای اعطای مجوز به Vikunja برای دسترسی به حساب {name} خود، روی دکمه زیر کلیک کنید.", + "getStarted": "شروع کنید", + "inProgress": "در حال وارد کردن...", + "alreadyMigrated1": "به نظر می‌رسد شما قبلاً موارد خود را از {name} در {date} وارد کرده‌اید.", + "alreadyMigrated2": "وارد کردن مجدد امکان‌پذیر است، اما ممکن است باعث ایجاد موارد تکراری شود. آیا مطمئن هستید؟", + "confirm": "مطمئن هستم، لطفاً مهاجرت را شروع کنید!", + "importUpload": "برای وارد کردن داده‌ها از {name} به Vikunja، روی دکمه زیر کلیک کنید تا یک فایل انتخاب کنید.", + "upload": "آپلود فایل", + "migrationStartedWillReciveEmail": "Vikunja اکنون لیست‌ها/پروژه‌ها، وظایف، یادداشت‌ها، یادآورها و فایل‌های شما را از {service} وارد خواهد کرد. از آنجایی که این کار مدتی طول می‌کشد، پس از اتمام به شما ایمیل خواهیم فرستاد. اکنون می‌توانید این پنجره را ببندید.", + "migrationInProgress": "مهاجرت در حال حاضر در حال انجام است. لطفاً تا اتمام آن صبر کنید.", + "csv": { + "description": "وارد کردن وظایف از یک فایل CSV با نگاشت ستون سفارشی.", + "uploadDescription": "یک فایل CSV برای وارد کردن انتخاب کنید. فایل باید حاوی داده‌های وظیفه با سرصفحه‌ها در ردیف اول باشد.", + "selectFile": "انتخاب فایل CSV", + "columnMappingDescription": "هر ستون در فایل CSV خود را به یک ویژگی وظیفه نگاشت کنید. Vikunja محتمل‌ترین نگاشت‌ها را به‌طور خودکار تشخیص داده است. پیش‌نمایش زیر با تغییر تنظیمات به‌طور خودکار به‌روز می‌شود.", + "parsingOptions": "گزینه‌های تجزیه", + "delimiter": "جداکننده", + "dateFormat": "قالب تاریخ", + "skipRows": "ردیف‌های پرش", + "mapColumns": "نگاشت ستون‌ها", + "example": "مثلاً", + "preview": "پیش‌نمایش", + "previewDescription": "نمایش ۵ مورد اول از {count} وظیفه‌ای که وارد خواهند شد.", + "import": "وارد کردن وظایف", + "untitled": "وظیفه بدون عنوان", + "ignore": "نادیده گرفتن", + "delimiters": { + "comma": "ویرگول (،)", + "semicolon": "نقطه ویرگول (؛)", + "tab": "زبانه", + "pipe": "خط عمودی (|)" + } + } + }, + "label": { + "title": "برچسب‌ها", + "manage": "مدیریت برچسب‌ها", + "description": "روی یک برچسب کلیک کنید تا آن را ویرایش کنید. شما می‌توانید تمام برچسب‌هایی را که ایجاد کرده‌اید ویرایش کنید، همچنین می‌توانید از تمام برچسب‌هایی که با یک وظیفه مرتبط هستند و شما به پروژه آن دسترسی دارید استفاده کنید.", + "newCTA": "شما در حال حاضر هیچ برچسبی ندارید.", + "create": { + "header": "برچسب جدید", + "title": "ایجاد برچسب", + "titleRequired": "لطفاً یک عنوان مشخص کنید.", + "success": "برچسب با موفقیت ایجاد شد." + }, + "edit": { + "header": "ویرایش برچسب", + "success": "برچسب با موفقیت به‌روزرسانی شد." + }, + "deleteSuccess": "برچسب با موفقیت حذف شد.", + "attributes": { + "title": "عنوان", + "titlePlaceholder": "عنوان برچسب در اینجا قرار می‌گیرد…", + "description": "توضیحات", + "color": "رنگ" + } + }, + "sharing": { + "authenticating": "در حال احراز هویت…", + "passwordRequired": "این پروژه اشتراکی نیاز به رمز عبور دارد. لطفاً آن را در زیر وارد کنید:", + "error": "خطایی رخ داد.", + "invalidPassword": "رمز عبور نامعتبر است.", + "accessDenied": "دسترسی رد شد. لطفاً مجوزهای خود را بررسی کرده و دوباره امتحان کنید.", + "serverError": "خطای سرور رخ داد. لطفاً بعداً دوباره امتحان کنید.", + "projectLoadError": "بارگیری اطلاعات پروژه ناموفق بود.", + "retry": "تلاش مجدد" + }, + "navigation": { + "overview": "مرور کلی", + "upcoming": "به‌زودی", + "settings": "تنظیمات", + "imprint": "درباره ما", + "privacy": "سیاست حفظ حریم خصوصی", + "closeSidebar": "بستن نوار کناری", + "home": "صفحه اصلی vikunja" + }, + "misc": { + "loading": "در حال بارگیری…", + "save": "ذخیره", + "delete": "حذف", + "confirm": "تأیید", + "cancel": "لغو", + "disable": "غیرفعال کردن", + "copy": "کپی در کلیپ‌بورد", + "copyError": "کپی در کلیپ‌بورد ناموفق بود", + "searchPlaceholder": "برای جستجو تایپ کنید…", + "previous": "قبلی", + "next": "بعدی", + "poweredBy": "قدرت گرفته از vikunja", + "create": "ایجاد", + "doit": "انجام بده!", + "saving": "در حال ذخیره…", + "saved": "ذخیره شد!", + "default": "پیش‌فرض", + "close": "بستن", + "download": "دانلود", + "showMenu": "نمایش منو", + "hideMenu": "پنهان کردن منو", + "forExample": "مثلاً:", + "welcomeBack": "خوش برگشتید!", + "custom": "سفارشی", + "id": "شناسه", + "created": "ایجاد شده در", + "createdBy": "ایجاد شده توسط {0}", + "actions": "عملیات", + "cannotBeUndone": "این عمل قابل بازگشت نیست!", + "avatarOfUser": "تصویر پروفایل {user}", + "closeBanner": "بستن بنر", + "closeDialog": "بستن پنجره", + "closeQuickActions": "بستن اقدامات سریع", + "skipToContent": "پرش به محتوای اصلی", + "sortBy": "مرتب‌سازی بر اساس" + }, + "input": { + "projectColor": "رنگ پروژه", + "resetColor": "بازنشانی رنگ", + "datepicker": { + "today": "امروز", + "tomorrow": "فردا", + "nextMonday": "دوشنبه آینده", + "thisWeekend": "این آخر هفته", + "laterThisWeek": "اواخر این هفته", + "nextWeek": "هفته آینده", + "chooseDate": "انتخاب تاریخ" + }, + "editor": { + "edit": "ویرایش", + "heading1": "عنوان ۱", + "heading1Tooltip": "عنوان بخش بزرگ.", + "heading2": "عنوان ۲", + "heading2Tooltip": "عنوان بخش متوسط.", + "heading3": "عنوان ۳", + "heading3Tooltip": "عنوان بخش کوچک‌تر.", + "bold": "ضخیم", + "italic": "کج", + "strikethrough": "خط خورده", + "underline": "زیرخط", + "code": "کد", + "codeTooltip": "یک قطعه کد را ثبت کنید.", + "quote": "نقل قول", + "quoteTooltip": "یک نقل قول را ثبت کنید.", + "bulletList": "لیست نقطه‌ای", + "bulletListTooltip": "یک لیست ساده نقطه‌ای ایجاد کنید.", + "orderedList": "لیست شماره‌دار", + "orderedListTooltip": "یک لیست با شماره‌گذاری ایجاد کنید.", + "link": "لینک", + "image": "تصویر", + "imageTooltip": "یک تصویر را از رایانه خود بارگذاری کنید.", + "horizontalRule": "خط افقی", + "horizontalRuleTooltip": "یک بخش را جدا کنید.", + "text": "متن", + "textTooltip": "فقط شروع به تایپ با متن ساده کنید.", + "taskList": "لیست وظایف", + "taskListTooltip": "وظایف را با یک لیست انجام دادنی پیگیری کنید.", + "undo": "بازگردانی", + "redo": "بازپخش", + "placeholder": "متنی تایپ کنید یا '/' را بزنید تا گزینه‌های بیشتری را ببینید…", + "table": { + "title": "جدول", + "insert": "درج جدول", + "addColumnBefore": "افزودن ستون قبل", + "addColumnAfter": "افزودن ستون بعد", + "deleteColumn": "حذف ستون", + "addRowBefore": "افزودن ردیف قبل", + "addRowAfter": "افزودن ردیف بعد", + "deleteRow": "حذف ردیف", + "deleteTable": "حذف جدول", + "mergeCells": "ادغام سلول‌ها", + "splitCell": "تقسیم سلول", + "toggleHeaderColumn": "تغییر ستون سربرگ", + "toggleHeaderRow": "تغییر ردیف سربرگ", + "toggleHeaderCell": "تغییر سلول سربرگ", + "mergeOrSplit": "ادغام یا تقسیم", + "fixTables": "رفع جداول" + }, + "emoji": { + "empty": "ایموجی یافت نشد" + } + }, + "multiselect": { + "createPlaceholder": "ایجاد", + "selectPlaceholder": "برای انتخاب کلیک کنید یا Enter را بزنید" + }, + "datepickerRange": { + "to": "به", + "from": "از", + "fromto": "{from} تا {to}", + "date": "تاریخ", + "ranges": { + "today": "امروز", + "thisWeek": "این هفته", + "restOfThisWeek": "بقیه این هفته", + "nextWeek": "هفته آینده", + "next7Days": "۷ روز آینده", + "lastWeek": "هفته گذشته", + "thisMonth": "این ماه", + "restOfThisMonth": "بقیه این ماه", + "nextMonth": "ماه آینده", + "next30Days": "۳۰ روز آینده", + "lastMonth": "ماه گذشته", + "thisYear": "امسال", + "restOfThisYear": "بقیه امسال" + }, + "values": { + "now": "اکنون", + "startOfToday": "ابتدای امروز", + "endOfToday": "پایان امروز", + "beginningOflastWeek": "ابتدای هفته گذشته", + "endOfLastWeek": "پایان هفته گذشته", + "beginningOfThisWeek": "ابتدای این هفته", + "endOfThisWeek": "پایان این هفته", + "startOfNextWeek": "شروع هفته آینده", + "endOfNextWeek": "پایان هفته آینده", + "in7Days": "در ۷ روز", + "beginningOfLastMonth": "ابتدای ماه گذشته", + "endOfLastMonth": "پایان ماه گذشته", + "startOfThisMonth": "شروع این ماه", + "endOfThisMonth": "پایان این ماه", + "startOfNextMonth": "شروع ماه آینده", + "endOfNextMonth": "پایان ماه آینده", + "in30Days": "در ۳۰ روز", + "startOfThisYear": "ابتدای امسال", + "endOfThisYear": "پایان امسال" + } + }, + "datemathHelp": { + "canuse": "شما می‌توانید از محاسبات تاریخ برای فیلتر کردن تاریخ‌های نسبی استفاده کنید.", + "learnhow": "نحوه کار آن را بررسی کنید", + "title": "محاسبات تاریخ", + "intro": "تاریخ‌های نسبی را مشخص کنید که هنگام اعمال فیلتر توسط Vikunja به صورت پویا تجزیه می‌شوند.", + "expression": "هر عبارت تاریخ ریاضی با یک تاریخ لنگر شروع می‌شود که می‌تواند {0} باشد، یا یک رشته تاریخ که با {1} پایان می‌یابد. این تاریخ لنگر می‌تواند به صورت اختیاری با یک یا چند عبارت ریاضی دنبال شود.", + "similar": "این عبارات شبیه به مواردی هستند که توسط {0} و {1} ارائه شده‌اند.", + "add1Day": "یک روز اضافه کن", + "minus1Day": "یک روز کم کن", + "roundDay": "به نزدیک‌ترین روز گرد کن (به پایین)", + "supportedUnits": "واحدهای زمانی پشتیبانی شده", + "someExamples": "نمونه‌هایی از عبارات زمانی", + "units": { + "seconds": "ثانیه‌ها", + "minutes": "دقیقه‌ها", + "hours": "ساعت‌ها", + "days": "روزها", + "weeks": "هفته‌ها", + "months": "ماه‌ها", + "years": "سال‌ها" + }, + "examples": { + "now": "همین الان", + "in24h": "در ۲۴ ساعت آینده", + "today": "امروز ساعت ۰۰:۰۰", + "beginningOfThisWeek": "ابتدای این هفته ساعت ۰۰:۰۰", + "endOfThisWeek": "پایان این هفته", + "in30Days": "در ۳۰ روز آینده", + "datePlusMonth": "{0} به اضافه یک ماه در ساعت ۰۰:۰۰ آن روز" + } + } + }, + "task": { + "new": "ایجاد وظیفه", + "createSuccess": "وظیفه با موفقیت ایجاد شد.", + "addReminder": "یادآور را اضافه کنید…", + "doneSuccess": "وظیفه با موفقیت به عنوان انجام شده علامت‌گذاری شد.", + "undoneSuccess": "وظیفه با موفقیت به عنوان انجام نشده علامت‌گذاری شد.", + "readOnlyCheckbox": "شما فقط دسترسی خواندن به این وظیفه دارید و نمی‌توانید آن را به عنوان انجام شده علامت‌گذاری کنید.", + "movedToProject": "وظیفه به {project} منتقل شد.", + "undo": "بازگرداندن", + "checklistTotal": "{checked} از {total} وظیفه", + "checklistAllDone": "{total} وظیفه", + "show": { + "titleCurrent": "وظایف فعلی", + "noDates": "نمایش وظایف بدون تاریخ", + "overdue": "نمایش وظایف معوق", + "fromuntil": "وظایف از {from} تا {until}", + "select": "انتخاب محدوده تاریخ", + "noTasks": "کاری برای انجام نیست — روز خوبی داشته باشید!", + "filterByLabel": "فیلتر بر اساس برچسب {label}", + "clearLabelFilter": "پاک کردن فیلتر برچسب", + "savedFilterIgnored": "فیلتر صفحه اصلی ذخیره شده شما هنگام مشاهده وظایف بر اساس برچسب اعمال نمی‌شود." + }, + "detail": { + "chooseDueDate": "برای تعیین تاریخ سررسید اینجا کلیک کنید", + "chooseStartDate": "برای تعیین تاریخ شروع اینجا کلیک کنید", + "chooseEndDate": "برای تعیین تاریخ پایان اینجا کلیک کنید", + "move": "انتقال وظیفه به پروژه دیگر", + "done": "وظیفه را انجام شده علامت بزن!", + "undone": "به عنوان انجام نشده علامت بزن", + "created": "ایجاد شده در {0} توسط {1}", + "updated": "به‌روزرسانی شده در {0}", + "doneAt": "انجام شده در {0}", + "updateSuccess": "وظیفه با موفقیت ذخیره شد.", + "deleteSuccess": "وظیفه با موفقیت حذف شد.", + "duplicateSuccess": "وظیفه با موفقیت تکرار شد.", + "noBucket": "بدون سطل", + "bucketChangedSuccess": "سطل وظیفه با موفقیت تغییر کرد.", + "belongsToProject": "این وظیفه متعلق به پروژه '{project}' است", + "back": "بازگشت به پروژه", + "due": "سررسید {at}", + "closeTaskDetail": "بستن جزئیات وظیفه", + "title": "جزئیات وظیفه", + "markAsDone": "علامت‌گذاری '{task}' به عنوان انجام شده", + "scrollToBottom": "اسکرول به پایین", + "organization": "سازمان", + "management": "مدیریت", + "dateAndTime": "تاریخ و زمان", + "delete": { + "header": "حذف این وظیفه", + "text1": "آیا مطمئن هستید که می‌خواهید این وظیفه را حذف کنید؟", + "text2": "این کار همچنین تمام پیوست‌ها، یادآورها و روابط مرتبط با این وظیفه را حذف می‌کند و قابل بازگشت نیست!" + }, + "actions": { + "assign": "اختصاص به کاربر", + "label": "افزودن برچسب‌ها", + "priority": "تعیین اولویت", + "dueDate": "تعیین تاریخ سررسید", + "startDate": "تعیین تاریخ شروع", + "endDate": "تعیین تاریخ پایان", + "reminders": "تعیین یادآورها", + "repeatAfter": "تعیین فاصله تکرار", + "percentDone": "تعیین پیشرفت", + "attachments": "افزودن پیوست‌ها", + "relatedTasks": "افزودن رابطه", + "moveProject": "انتقال", + "duplicate": "تکرار", + "color": "تعیین رنگ", + "delete": "حذف", + "favorite": "افزودن به علاقه‌مندی‌ها", + "unfavorite": "حذف از علاقه‌مندی‌ها" + } + }, + "attributes": { + "assignees": "اختصاص‌یافتگان", + "color": "رنگ", + "created": "ایجاد شده", + "createdBy": "ایجاد شده توسط", + "description": "توضیحات", + "done": "انجام شده", + "dueDate": "تاریخ سررسید", + "endDate": "تاریخ پایان", + "labels": "برچسب‌ها", + "percentDone": "پیشرفت", + "priority": "اولویت", + "project": "پروژه", + "relatedTasks": "وظایف مرتبط", + "reminders": "یادآورها", + "repeat": "تکرار", + "comment": "{count} نظر | {count} نظر", + "commentCount": "تعداد نظرات", + "startDate": "تاریخ شروع", + "title": "عنوان", + "updated": "به‌روزرسانی شده", + "doneAt": "تاریخ انجام" + }, + "subscription": { + "subscribedTaskThroughParentProject": "شما نمی‌توانید در اینجا لغو اشتراک کنید زیرا از طریق پروژه خود در این وظیفه مشترک شده‌اید.", + "subscribedProject": "شما در حال حاضر در این پروژه مشترک هستید و اعلان‌های مربوط به تغییرات را دریافت خواهید کرد.", + "notSubscribedProject": "شما در این پروژه مشترک نیستید و اعلان‌های مربوط به تغییرات را دریافت نخواهید کرد.", + "subscribedTask": "شما در حال حاضر در این وظیفه مشترک هستید و اعلان‌های مربوط به تغییرات را دریافت خواهید کرد.", + "notSubscribedTask": "شما در این وظیفه مشترک نیستید و اعلان‌های مربوط به تغییرات را دریافت نخواهید کرد.", + "subscribe": "اشتراک", + "unsubscribe": "لغو اشتراک", + "subscribeSuccessProject": "شما اکنون در این پروژه مشترک شده‌اید", + "unsubscribeSuccessProject": "شما اکنون از این پروژه لغو اشتراک کرده‌اید", + "subscribeSuccessTask": "شما اکنون در این وظیفه مشترک شده‌اید", + "unsubscribeSuccessTask": "شما اکنون از این وظیفه لغو اشتراک کرده‌اید" + }, + "attachment": { + "title": "پیوست‌ها", + "createdBy": "ایجاد شده در {0} توسط {1}", + "downloadTooltip": "دانلود این پیوست", + "upload": "آپلود پیوست", + "drop": "فایل‌ها را برای آپلود اینجا رها کنید", + "delete": "حذف پیوست", + "deleteTooltip": "حذف این پیوست", + "deleteText1": "آیا مطمئن هستید که می‌خواهید پیوست {filename} را حذف کنید؟", + "copyUrlTooltip": "برای استفاده در متن، آدرس این پیوست را کپی کنید", + "setAsCover": "به عنوان کاور تنظیم کن", + "unsetAsCover": "حذف کاور", + "successfullyChangedCoverImage": "تصویر کاور با موفقیت تغییر کرد.", + "usedAsCover": "تصویر کاور" + }, + "comment": { + "title": "نظرات", + "loading": "در حال بارگذاری نظرات…", + "edited": "ویرایش شده در {date}", + "creating": "در حال ایجاد نظر…", + "placeholder": "نظر خود را اضافه کنید، برای گزینه‌های بیشتر '/' را بزنید…", + "comment": "نظر", + "delete": "حذف این نظر", + "deleteText1": "آیا از حذف این نظر مطمئن هستید؟", + "deleteSuccess": "نظر با موفقیت حذف شد.", + "addedSuccess": "نظر با موفقیت اضافه شد.", + "permalink": "کپی کردن پیوند ثابت این نظر", + "sortNewestFirst": "جدیدترین اول", + "sortOldestFirst": "قدیمی‌ترین اول" + }, + "mention": { + "noUsersFound": "کاربری یافت نشد" + }, + "deferDueDate": { + "title": "تعویق تاریخ سررسید", + "1day": "۱ روز", + "3days": "۳ روز", + "1week": "۱ هفته" + }, + "description": { + "placeholder": "یک توضیحات وارد کنید، برای گزینه‌های بیشتر '/' را بزنید…" + }, + "assignee": { + "placeholder": "برای تخصیص کاربر تایپ کنید…", + "selectPlaceholder": "تخصیص این کاربر", + "assignSuccess": "کاربر با موفقیت تخصیص داده شد.", + "unassignSuccess": "کاربر با موفقیت از تخصیص خارج شد." + }, + "label": { + "placeholder": "برای افزودن برچسب تایپ کنید…", + "createPlaceholder": "افزودن این به عنوان برچسب جدید", + "addSuccess": "برچسب با موفقیت اضافه شد.", + "removeSuccess": "برچسب با موفقیت حذف شد.", + "addCreateSuccess": "برچسب با موفقیت ایجاد و اضافه شد.", + "delete": { + "header": "حذف این برچسب", + "text1": "آیا از حذف این برچسب مطمئن هستید؟", + "text2": "این کار برچسب را از تمام وظایف حذف می‌کند و قابل بازیابی نیست." + } + }, + "priority": { + "unset": "لغو تنظیم", + "low": "کم", + "medium": "متوسط", + "high": "زیاد", + "urgent": "فوری", + "doNow": "همین الان انجام بده" + }, + "relation": { + "add": "افزودن ارتباط وظیفه جدید", + "new": "ارتباط وظیفه جدید", + "searchPlaceholder": "برای افزودن وظیفه مرتبط جستجو کنید…", + "createPlaceholder": "افزودن این به عنوان وظیفه مرتبط", + "differentProject": "این وظیفه متعلق به پروژه دیگری است.", + "noneYet": "هنوز ارتباط وظیفه‌ای وجود ندارد.", + "delete": "حذف ارتباط وظیفه", + "deleteText1": "آیا از حذف این ارتباط وظیفه مطمئن هستید؟", + "select": "نوع ارتباط را انتخاب کنید", + "taskRequired": "لطفاً یک وظیفه انتخاب کنید یا عنوان وظیفه جدیدی را وارد کنید.", + "kinds": { + "subtask": "وظیفه فرعی | وظایف فرعی", + "parenttask": "وظیفه والد | وظایف والد", + "related": "وظیفه مرتبط | وظایف مرتبط", + "duplicateof": "کپی شده از | کپی شده از", + "duplicates": "کپی‌ها | کپی‌ها", + "blocking": "مسدود کننده | مسدود کننده", + "blocked": "مسدود شده توسط | مسدود شده توسط", + "precedes": "پیش از | پیش از", + "follows": "پس از | پس از", + "copiedfrom": "کپی شده از | کپی شده از", + "copiedto": "کپی شده به | کپی شده به" + } + }, + "reminder": { + "before": "{amount} {unit} قبل از {type}", + "after": "{amount} {unit} بعد از {type}", + "beforeShort": "قبل", + "afterShort": "بعد", + "onDueDate": "در تاریخ سررسید", + "onStartDate": "در تاریخ شروع", + "onEndDate": "در تاریخ پایان", + "custom": "سفارشی", + "dateAndTime": "تاریخ و زمان" + }, + "repeat": { + "everyDay": "هر روز", + "everyWeek": "هر هفته", + "every30d": "هر ۳۰ روز", + "mode": "حالت تکرار", + "monthly": "ماهانه", + "fromCurrentDate": "از تاریخ تکمیل", + "each": "هر", + "specifyAmount": "مقدار را مشخص کنید…", + "hours": "ساعت", + "days": "روز", + "weeks": "هفته", + "invalidAmount": "لطفاً بیش از ۰ وارد کنید." + }, + "quickAddMagic": { + "hint": "از پیشوندهای جادویی برای تعیین تاریخ سررسید، مسئولین و سایر خصوصیات وظیفه استفاده کنید.", + "quickEntryHint": "از پیشوندهای جادویی برای تاریخ‌ها، برچسب‌ها و موارد دیگر استفاده کنید. برنامه اصلی vikunja را باز کنید و برای جزئیات بیشتر، راهنمای ابزار را در فیلد ورودی وظیفه بررسی کنید.", + "title": "افزودن سریع جادویی", + "intro": "هنگام ایجاد یک وظیفه، می‌توانید از کلمات کلیدی خاصی برای افزودن مستقیم خصوصیات به وظیفه تازه ایجاد شده استفاده کنید. این امکان را می‌دهد که خصوصیات پرکاربرد را خیلی سریع‌تر به وظایف اضافه کنید.", + "multiple": "می‌توانید این را چندین بار استفاده کنید.", + "label1": "برای افزودن یک برچسب، کافی است نام برچسب را با {prefix} پیشوند دهید.", + "label2": "vikunja ابتدا بررسی می‌کند که آیا برچسب از قبل وجود دارد یا خیر و در صورت عدم وجود آن را ایجاد می‌کند.", + "label3": "برای استفاده از فاصله‌ها، کافی است نام برچسب را در \" یا ' قرار دهید.", + "label4": "به عنوان مثال: {prefix}\"برچسب با فاصله‌ها\".", + "priority1": "برای تعیین اولویت وظیفه، عددی از ۱ تا ۵ را با پیشوند {prefix} اضافه کنید.", + "priority2": "هرچه عدد بالاتر باشد، اولویت بالاتر است.", + "assignees": "برای تخصیص مستقیم وظیفه به یک کاربر، نام کاربری او را با پیشوند {prefix} به وظیفه اضافه کنید.", + "project1": "برای تعیین پروژه‌ای که وظیفه در آن ظاهر شود، نام آن را با پیشوند {prefix} وارد کنید.", + "project2": "این کار در صورت عدم وجود پروژه، خطا برمی‌گرداند.", + "project3": "برای استفاده از فاصله‌ها، کافی است نام پروژه را در \" یا ' قرار دهید.", + "project4": "به عنوان مثال: {prefix}\"پروژه با فاصله‌ها\".", + "dateAndTime": "تاریخ و زمان", + "date": "هر تاریخی به عنوان تاریخ سررسید وظیفه جدید استفاده خواهد شد. می‌توانید از تاریخ‌ها در هر یک از این فرمت‌ها استفاده کنید:", + "dateWeekday": "هر روز هفته، از تاریخ بعدی با آن روز استفاده خواهد شد", + "dateCurrentYear": "سال جاری استفاده خواهد شد", + "dateNth": "روز {day} از ماه جاری استفاده خواهد شد", + "dateTime": "هر یک از فرمت‌های تاریخ را با \"{time}\" (یا {timePM}) ترکیب کنید تا زمان را تنظیم کنید.", + "repeats": "وظایف تکراری", + "repeatsDescription": "برای تنظیم یک وظیفه به عنوان تکراری در یک بازه زمانی، کافی است '{suffix}' را به متن وظیفه اضافه کنید. مقدار باید یک عدد باشد و در صورت استفاده فقط از نوع (به مثال‌ها مراجعه کنید) می‌توان آن را حذف کرد." + } + }, + "team": { + "title": "تیم‌ها", + "noTeams": "شما در حال حاضر عضو هیچ تیمی نیستید.", + "create": { + "title": "ایجاد تیم", + "success": "تیم با موفقیت ایجاد شد." + }, + "edit": { + "title": "ویرایش تیم \"{team}\"", + "members": "اعضای تیم", + "search": "برای جستجوی کاربر تایپ کنید…", + "addUser": "افزودن به تیم", + "makeMember": "تبدیل به عضو", + "makeAdmin": "تبدیل به مدیر", + "success": "تیم با موفقیت به‌روزرسانی شد.", + "userAddedSuccess": "عضو تیم با موفقیت اضافه شد.", + "madeMember": "عضو تیم با موفقیت به عنوان عضو تعیین شد.", + "madeAdmin": "عضو تیم با موفقیت مدیر شد.", + "mustSelectUser": "لطفاً یک کاربر انتخاب کنید.", + "delete": { + "header": "حذف تیم", + "text1": "آیا مطمئن هستید که می‌خواهید این تیم و تمام اعضای آن را حذف کنید؟", + "text2": "تمام اعضای تیم دسترسی خود را به پروژه‌های به‌اشتراک‌گذاشته‌شده از دست خواهند داد. این عمل غیرقابل بازگشت است!", + "success": "تیم با موفقیت حذف شد." + }, + "deleteUser": { + "header": "حذف کاربر از تیم", + "text1": "آیا مطمئن هستید که می‌خواهید این کاربر را از تیم حذف کنید؟", + "text2": "او دسترسی خود را به تمام پروژه‌های این تیم از دست خواهد داد. این عمل غیرقابل بازگشت است!", + "success": "کاربر با موفقیت از تیم حذف شد." + }, + "leave": { + "title": "ترک تیم", + "text1": "آیا مطمئن هستید که می‌خواهید تیم را ترک کنید؟", + "text2": "شما دسترسی خود را به تمام پروژه‌های این تیم از دست خواهید داد. برای بازگشت باید دوباره توسط مدیر تیم اضافه شوید.", + "success": "شما با موفقیت تیم را ترک کردید." + } + }, + "attributes": { + "name": "نام تیم", + "namePlaceholder": "نام تیم اینجا وارد می‌شود…", + "nameRequired": "لطفاً یک نام وارد کنید.", + "description": "توضیحات", + "descriptionPlaceholder": "توضیحات تیم را اینجا بنویسید… برای گزینه‌های بیشتر '/' را فشار دهید…", + "admin": "مدیر", + "member": "عضو", + "isPublic": "تیم عمومی", + "isPublicDescription": "تیم را قابل جستجوی عمومی کنید. در این حالت، هر کسی می‌تواند حتی بدون عضویت مستقیم، پروژه‌ها را با این تیم به اشتراک بگذارد." + } + }, + "keyboardShortcuts": { + "title": "میانبرهای صفحه‌کلید", + "general": "عمومی", + "allPages": "این میانبرها در همه صفحات کار می‌کنند.", + "currentPageOnly": "این میانبرها فقط در صفحه فعلی کار می‌کنند.", + "somePagesOnly": "این میانبرها فقط در برخی صفحات کار می‌کنند.", + "toggleMenu": "نمایش/مخفی کردن منو", + "quickSearch": "باز کردن نوار جستجو/عملیات سریع", + "then": "سپس", + "task": { + "title": "صفحه وظایف", + "done": "تغییر وضعیت انجام‌شده/انجام‌نشده", + "assign": "اختصاص این وظیفه به کاربر", + "labels": "افزودن برچسب به وظیفه", + "dueDate": "تغییر تاریخ سررسید", + "attachment": "افزودن پیوست", + "related": "ویرایش وظایف مرتبط", + "color": "تغییر رنگ وظیفه", + "move": "انتقال وظیفه به پروژه دیگر", + "reminder": "مدیریت یادآورها", + "description": "ویرایش توضیحات وظیفه", + "delete": "حذف وظیفه", + "priority": "تغییر اولویت وظیفه", + "favorite": "افزودن/حذف از علاقه‌مندی‌ها", + "openProject": "باز کردن پروژه وظیفه", + "save": "ذخیره وظیفه", + "copyIdentifier": "کپی شناسه وظیفه", + "copyIdentifierAndTitle": "کپی شناسه و عنوان وظیفه", + "copyIdentifierTitleAndUrl": "کپی شناسه، عنوان و لینک وظیفه", + "copyUrl": "کپی لینک وظیفه" + }, + "project": { + "title": "نماهای پروژه", + "switchToListView": "تغییر به لیست", + "switchToGanttView": "تغییر به گانت", + "switchToKanbanView": "تغییر به کانبان", + "switchToTableView": "تغییر به جدول" + }, + "navigation": { + "title": "ناوبری", + "overview": "رفتن به نمای کلی", + "upcoming": "رفتن به وظایف آینده", + "labels": "رفتن به برچسب‌ها", + "teams": "رفتن به تیم‌ها", + "projects": "رفتن به پروژه‌ها" + }, + "list": { + "title": "لیست وظایف", + "navigateDown": "انتخاب وظیفه بعدی", + "navigateUp": "انتخاب وظیفه قبلی", + "open": "باز کردن وظیفه انتخاب‌شده" + }, + "gantt": { + "title": "نمودار گانت", + "moveTaskLeft": "انتقال وظیفه به تاریخ زودتر", + "moveTaskRight": "انتقال وظیفه به تاریخ دیرتر", + "expandTaskLeft": "گسترش شروع وظیفه", + "expandTaskRight": "گسترش پایان وظیفه", + "shrinkTaskLeft": "کاهش از سمت شروع", + "shrinkTaskRight": "کاهش از سمت پایان" + } + }, + "update": { + "available": "به‌روزرسانی جدید موجود است!", + "do": "به‌روزرسانی" + }, + "menu": { + "edit": "ویرایش", + "archive": "بایگانی", + "duplicate": "تکثیر", + "delete": "حذف", + "unarchive": "خارج کردن از بایگانی", + "setBackground": "تنظیمات پس‌زمینه", + "share": "اشتراک‌گذاری", + "createProject": "ایجاد پروژه", + "cantArchiveIsDefault": "نمی‌توانید این را بایگانی کنید چون پروژه پیش‌فرض شماست.", + "cantDeleteIsDefault": "نمی‌توانید این را حذف کنید چون پروژه پیش‌فرض شماست.", + "views": "نماها" + }, + "apiConfig": { + "url": "آدرس Vikunja", + "urlPlaceholder": "مثلاً https://localhost:3456", + "change": "تغییر", + "use": "در حال استفاده از نصب Vikunja در {0}", + "error": "نمی‌توان نصب Vikunja را در \"{domain}\" پیدا یا استفاده کرد. لطفاً بررسی کنید آدرس صحیح است و از طریق مرورگر قابل دسترسی است.", + "success": "در حال استفاده از نصب Vikunja در \"{domain}\".", + "urlRequired": "وارد کردن آدرس الزامی است." + }, + "loadingError": { + "failed": "بارگذاری ناموفق بود، لطفاً {0}. اگر خطا ادامه داشت، لطفاً {1}.", + "tryAgain": "دوباره تلاش کنید", + "contact": "با ما تماس بگیرید" + }, + "notification": { + "title": "اعلان‌ها", + "none": "هیچ اعلانی ندارید. روز خوبی داشته باشید!", + "explainer": "اعلان‌ها زمانی نمایش داده می‌شوند که اقداماتی روی پروژه‌ها یا وظایف دنبال‌شده شما انجام شود.", + "markAllRead": "علامت‌گذاری همه به‌عنوان خوانده‌شده", + "markAllReadSuccess": "همه اعلان‌ها به‌عنوان خوانده‌شده علامت‌گذاری شدند." + }, + "quickActions": { + "notLoggedIn": "لطفاً ابتدا وارد پنجره اصلی Vikunja شوید.", + "commands": "دستورات", + "placeholder": "یک دستور یا جستجو تایپ کنید…", + "hint": "می‌توانید از {project} برای محدود کردن جستجو به یک پروژه استفاده کنید. همچنین {label} و {assignee} قابل استفاده هستند.", + "tasks": "وظایف", + "projects": "پروژه‌ها", + "teams": "تیم‌ها", + "labels": "برچسب‌ها", + "newProject": "عنوان پروژه جدید را وارد کنید…", + "newTask": "عنوان وظیفه جدید را وارد کنید…", + "newTeam": "نام تیم جدید را وارد کنید…", + "createTask": "ایجاد وظیفه در پروژه فعلی ({title})", + "createProject": "ایجاد پروژه", + "cmds": { + "newTask": "وظیفه جدید", + "newProject": "پروژه جدید", + "newTeam": "تیم جدید" + } + }, + "date": { + "altFormatLong": "j M Y, H:i", + "altFormatShort": "j M Y" + }, + "reaction": { + "reactedWith": "{user} با {value} واکنش نشان داد", + "reactedWithAnd": "{users} و {lastUser} با {value} واکنش نشان دادند", + "reactedWithAndMany": "{users} و {num} نفر دیگر با {value} واکنش نشان دادند", + "add": "افزودن واکنش" + }, + "error": { + "1001": "کاربری با این نام کاربری وجود دارد.", + "1002": "کاربری با این ایمیل وجود دارد.", + "1004": "نام کاربری و رمز عبور مشخص نشده است.", + "1005": "کاربر وجود ندارد.", + "1006": "دریافت شناسه کاربر ممکن نیست.", + "1008": "توکن بازنشانی رمز عبور ارائه نشده است.", + "1009": "توکن بازنشانی نامعتبر است.", + "1010": "توکن تأیید ایمیل نامعتبر است.", + "1011": "نام کاربری یا رمز عبور اشتباه است.", + "1012": "ایمیل کاربر تأیید نشده است.", + "1013": "رمز عبور جدید خالی است.", + "1014": "رمز عبور قدیمی خالی است.", + "1015": "TOTP برای این کاربر فعال است.", + "1016": "TOTP برای این کاربر فعال نیست.", + "1017": "کد TOTP نامعتبر است.", + "1018": "نوع آواتار نامعتبر است.", + "1019": "هیچ ایمیلی از ارائه‌دهنده OpenID دریافت نشد.", + "1020": "این حساب غیرفعال شده است.", + "1021": "این حساب توسط ارائه‌دهنده احراز هویت مدیریت می‌شود.", + "1022": "نام کاربری نباید شامل فاصله باشد.", + "1023": "نمی‌توانید به عنوان لینک اشتراکی این کار را انجام دهید.", + "1024": "داده نامعتبر برای فیلد {field} از نوع {type}.", + "1025": "منطقه زمانی '{timezone}' نامعتبر است.", + "2001": "شناسه نمی‌تواند خالی یا صفر باشد.", + "2002": "برخی از داده‌های درخواست نامعتبر بودند.", + "2003": "منطقه زمانی '{timezone}' نامعتبر است.", + "3001": "پروژه وجود ندارد.", + "3004": "برای انجام این عملیات باید دسترسی خواندن به پروژه داشته باشید.", + "3005": "عنوان پروژه نمی‌تواند خالی باشد.", + "3006": "اشتراک پروژه وجود ندارد.", + "3007": "پروژه‌ای با این شناسه وجود دارد.", + "3008": "این پروژه بایگانی شده است و فقط خواندنی است.", + "4001": "عنوان وظیفه نمی‌تواند خالی باشد.", + "4002": "وظیفه وجود ندارد.", + "4003": "ویرایش گروهی باید مربوط به یک پروژه باشد.", + "4004": "برای ویرایش گروهی حداقل یک وظیفه لازم است.", + "4005": "اجازه مشاهده این وظیفه را ندارید.", + "4006": "نمی‌توانید یک وظیفه والد را به خودش اختصاص دهید.", + "4007": "نوع رابطه وظیفه نامعتبر است.", + "4008": "این رابطه از قبل وجود دارد.", + "4009": "رابطه وظیفه وجود ندارد.", + "4010": "نمی‌توان یک وظیفه را به خودش مرتبط کرد.", + "4011": "پیوست وظیفه وجود ندارد.", + "4012": "حجم پیوست بیش از حد مجاز است.", + "4013": "پارامتر مرتب‌سازی نامعتبر است.", + "4014": "ترتیب مرتب‌سازی نامعتبر است.", + "4015": "کامنت وظیفه وجود ندارد.", + "4016": "فیلد وظیفه نامعتبر است.", + "4017": "فیلتر نامعتبر است.", + "4018": "ترکیب فیلتر نامعتبر است.", + "4019": "مقدار فیلتر نامعتبر است.", + "4020": "این پیوست متعلق به این وظیفه نیست.", + "4021": "این کاربر از قبل به این وظیفه اختصاص داده شده است.", + "4022": "لطفاً مشخص کنید یادآور نسبت به چه چیزی است.", + "4023": "نمی‌توان چرخه رابطه وظیفه ایجاد کرد.", + "6001": "نام تیم نمی‌تواند خالی باشد.", + "6002": "تیم وجود ندارد.", + "6004": "این تیم قبلاً به این پروژه دسترسی دارد.", + "6005": "این کاربر قبلاً عضو تیم است.", + "6006": "نمی‌توان آخرین عضو تیم را حذف کرد.", + "6007": "تیم به پروژه دسترسی ندارد.", + "6008": "هیچ تیمی با OIDC مشخص پیدا نشد.", + "6009": "هیچ تیمی با این ویژگی برای کاربر پیدا نشد.", + "7002": "کاربر قبلاً به این پروژه دسترسی دارد.", + "7003": "به این پروژه دسترسی ندارید.", + "8001": "این برچسب از قبل روی وظیفه وجود دارد.", + "8002": "برچسب وجود ندارد.", + "8003": "به این برچسب دسترسی ندارید.", + "9001": "سطح دسترسی نامعتبر است.", + "10001": "سطل وجود ندارد.", + "10002": "سطل متعلق به این پروژه نیست.", + "10003": "نمی‌توانید آخرین سطل را حذف کنید.", + "10004": "ظرفیت این سطل پر شده است.", + "10005": "فقط یک سطل انجام‌شده می‌تواند وجود داشته باشد.", + "11001": "فیلتر ذخیره‌شده وجود ندارد.", + "11002": "فیلتر ذخیره‌شده برای لینک اشتراکی در دسترس نیست.", + "12001": "نوع اشتراک نامعتبر است.", + "12002": "شما قبلاً مشترک هستید.", + "12003": "برای دریافت اشتراک باید کاربر مشخص شود.", + "13001": "این لینک نیاز به رمز دارد.", + "13002": "رمز لینک اشتراک نامعتبر است.", + "13003": "توکن لینک اشتراک نامعتبر است.", + "14001": "توکن API نامعتبر است.", + "14002": "سطح دسترسی گروه نامعتبر است.", + "error": "خطا", + "success": "موفقیت", + "0001": "شما اجازه انجام این کار را ندارید." + }, + "about": { + "title": "درباره", + "version": "نسخه: {version}", + "frontendVersion": "نسخه فرانت‌اند: {version}", + "apiVersion": "نسخه API: {version}" + }, + "time": { + "units": { + "seconds": "ثانیه|ثانیه‌ها", + "minutes": "دقیقه|دقیقه‌ها", + "hours": "ساعت|ساعت‌ها", + "days": "روز|روزها", + "weeks": "هفته|هفته‌ها", + "years": "سال|سال‌ها" + } + }, + "admin": { + "title": "مدیریت", + "labels": { + "users": "کاربران", + "tasks": "وظایف" + }, + "overview": { + "shares": "اشتراک‌ها", + "linkSharesShort": "لینک", + "teamSharesShort": "تیم", + "userSharesShort": "کاربر", + "version": "نسخه", + "license": "مجوز", + "licenseValidUntil": "معتبر تا", + "licenseExpiresIn": "در {days} روز", + "licenseLastVerified": "آخرین بررسی", + "licenseNever": "هرگز", + "licenseLastCheckFailed": "آخرین بررسی ناموفق بود", + "licenseFeatures": "ویژگی‌ها", + "licenseInstance": "شناسه نمونه", + "licenseManage": "مدیریت" + }, + "searchUsersPlaceholder": "جستجو بر اساس نام کاربری یا ایمیل…", + "users": { + "status": "وضعیت", + "details": "جزئیات", + "detailsTitle": "کاربر: {username}", + "issuer": "صادرکننده", + "issuerLocal": "محلی", + "issuerUrl": "آدرس صادرکننده", + "subject": "موضوع", + "statusActive": "فعال", + "statusEmailConfirmation": "نیاز به تأیید ایمیل", + "statusDisabled": "غیرفعال", + "statusLocked": "قفل‌شده", + "isAdminLabel": "مدیر", + "addUser": "افزودن کاربر", + "createTitle": "ایجاد کاربر", + "nameLabel": "نام", + "skipEmailConfirm": "رد کردن تأیید ایمیل", + "createSubmit": "ایجاد کاربر", + "saveButton": "ذخیره تغییرات", + "createdSuccess": "کاربر {username} ایجاد شد.", + "updatedSuccess": "کاربر {username} به‌روزرسانی شد.", + "deletedSuccess": "کاربر {username} حذف شد.", + "deleteScheduledSuccess": "ایمیل حذف برای کاربر {username} ارسال شد.", + "confirmDeleteTitle": "حذف کاربر؟", + "confirmDeleteIntro": "چگونه کاربر {username} حذف شود؟", + "deleteModeScheduled": "حذف زمان‌بندی‌شده", + "deleteModeScheduledHelp": "حذف زمان‌بندی‌شده ایمیل تأیید ارسال می‌کند.", + "deleteModeNow": "حذف فوری", + "deleteModeNowHelp": "حذف فوری تمام داده‌ها را پاک می‌کند و غیرقابل بازگشت است." + }, + "projects": { + "ownerLabel": "مالک", + "reassignOwner": "تغییر مالک", + "reassignTitle": "تغییر مالک {title}", + "reassignedSuccess": "مالک پروژه تغییر کرد.", + "newOwnerLabel": "مالک جدید" + } + } +} \ No newline at end of file diff --git a/frontend/src/i18n/lang/fi-FI.json b/frontend/src/i18n/lang/fi-FI.json index 5ceb0be11..90e1e99c6 100644 --- a/frontend/src/i18n/lang/fi-FI.json +++ b/frontend/src/i18n/lang/fi-FI.json @@ -347,8 +347,7 @@ "default": "Oletus", "month": "Kuukausi", "day": "Päivä", - "hour": "Tunti", - "range": "Ajanjakso" + "hour": "Tunti" }, "table": { "title": "Taulukko", @@ -357,7 +356,6 @@ "kanban": { "title": "Kanban", "limit": "Raja: {limit}", - "noLimit": "Ei Asetettu", "doneBucket": "Valmiit sarake", "doneBucketHint": "Kaikki tähän sarakkeeseen lisätyt tehtävät merkitään automaattisesti valmiiksi.", "doneBucketHintExtended": "Kaikki tähän sarakkeeseen lisätyt tehtävät merkitään automaattisesti valmiiksi. Muualla valmiiksi merkityt tehtävät siirretään myös.", diff --git a/frontend/src/i18n/lang/fr-FR.json b/frontend/src/i18n/lang/fr-FR.json index 7bcb472c7..fe9357062 100644 --- a/frontend/src/i18n/lang/fr-FR.json +++ b/frontend/src/i18n/lang/fr-FR.json @@ -346,7 +346,6 @@ "month": "Mois", "day": "Jour", "hour": "Heure", - "range": "Intervalle", "chartLabel": "Diagramme de Gantt du projet", "taskBarsForRow": "Barres de tâches pour la ligne {rowId}", "taskBarLabel": "Tâche : {task}. De {startDate} à {endDate}. {dateType}. Cliquez pour modifier, faites glisser pour déplacer.", @@ -370,7 +369,6 @@ "kanban": { "title": "Kanban", "limit": "Limite : {limit}", - "noLimit": "Non défini", "doneBucket": "Colonne des tâches terminées", "doneBucketHint": "Toute tâche déplacée dans cette colonne sera automatiquement marquée comme terminée.", "doneBucketHintExtended": "Toute tâche déplacée dans cette colonne sera automatiquement marquée comme terminée. Toute tâche marquée comme terminée ailleurs sera également déplacée.", diff --git a/frontend/src/i18n/lang/he-IL.json b/frontend/src/i18n/lang/he-IL.json index c8abc236a..0477ecffa 100644 --- a/frontend/src/i18n/lang/he-IL.json +++ b/frontend/src/i18n/lang/he-IL.json @@ -318,8 +318,7 @@ "default": "ברירת מחדל", "month": "חודש", "day": "יום", - "hour": "שעה", - "range": "טווח תאריכים" + "hour": "שעה" }, "table": { "title": "טבלה", @@ -328,7 +327,6 @@ "kanban": { "title": "קאנבאן", "limit": "הגבלה: {limit}", - "noLimit": "לא נקבע", "doneBucket": "דלי גמורים", "doneBucketHint": "דלי גמורים נשמר בהצלחה.", "doneBucketHintExtended": "כל המטלות המוכנסות לדלי הגמורים יסומנו אוטומטית כגמורים. כל המטלות המסומנות כגמורים מבחוץ יוזזו גם.", diff --git a/frontend/src/i18n/lang/hr-HR.json b/frontend/src/i18n/lang/hr-HR.json index 3f5f32bf6..77797bbe8 100644 --- a/frontend/src/i18n/lang/hr-HR.json +++ b/frontend/src/i18n/lang/hr-HR.json @@ -289,16 +289,14 @@ "default": "Zadano", "month": "Mjesec", "day": "Dan", - "hour": "Sat", - "range": "Raspon datuma" + "hour": "Sat" }, "table": { "title": "Tablica", "columns": "Stupci" }, "kanban": { - "title": "Kanban", - "noLimit": "Nije postavljeno" + "title": "Kanban" }, "pseudo": { "favorites": { diff --git a/frontend/src/i18n/lang/hu-HU.json b/frontend/src/i18n/lang/hu-HU.json index 178c0bf4a..1cace9336 100644 --- a/frontend/src/i18n/lang/hu-HU.json +++ b/frontend/src/i18n/lang/hu-HU.json @@ -290,8 +290,7 @@ "default": "Alapértelmezett", "month": "Hónap", "day": "Nap", - "hour": "Óra", - "range": "Időintervallum" + "hour": "Óra" }, "table": { "title": "Táblázat", @@ -300,7 +299,6 @@ "kanban": { "title": "Kanban", "limit": "Korlát: {limit}", - "noLimit": "Nincs beállítva", "doneBucket": "Kész vödör", "doneBucketHint": "Az ebbe a csoportba helyezett összes feladat automatikusan készként lesz megjelölve.", "doneBucketHintExtended": "A kész csoportba áthelyezett összes feladat automatikusan készként lesz megjelölve. A máshonnan elvégzettként megjelölt összes feladat is átkerül.", diff --git a/frontend/src/i18n/lang/it-IT.json b/frontend/src/i18n/lang/it-IT.json index ec402b64b..985be6830 100644 --- a/frontend/src/i18n/lang/it-IT.json +++ b/frontend/src/i18n/lang/it-IT.json @@ -362,7 +362,6 @@ "month": "Mese", "day": "Giorno", "hour": "Ora", - "range": "Intervallo di date", "chartLabel": "Progetto diagramma di Gantt", "taskBarsForRow": "Barre delle attività per riga {rowId}", "taskBarLabel": "Attività: {task}. Da {startDate} a {endDate}. {dateType}. Clicca per modificare, trascina per spostare.", @@ -386,7 +385,6 @@ "kanban": { "title": "Kanban", "limit": "Limite: {limit}", - "noLimit": "Non Impostato", "doneBucket": "Colonna attività completate", "doneBucketHint": "Tutte le attività spostate in questa colonna verranno automaticamente contrassegnate come completate.", "doneBucketHintExtended": "Tutte le attività spostate nella colonna attività completate saranno contrassegnate automaticamente come completate. Anche tutte le attività contrassegnate come completate altrove verranno spostate.", diff --git a/frontend/src/i18n/lang/ja-JP.json b/frontend/src/i18n/lang/ja-JP.json index d20972955..2462d9f4e 100644 --- a/frontend/src/i18n/lang/ja-JP.json +++ b/frontend/src/i18n/lang/ja-JP.json @@ -5,9 +5,36 @@ }, "home": { "welcomeNight": "おやすみなさい、{username}さん", + "welcomeNightOwl": "夜更かしですか、{username}さん", + "welcomeNightBurning": "夜更かししていますね、{username}さん?", + "welcomeNightQuiet": "静かな時間ですね、{username}さん", + "welcomeNightLate": "もう遅い時間ですよ、{username}さん", + "welcomeNightMoonlit": "月明かりの下で計画ですか、{username}さん?", "welcomeMorning": "おはようございます、{username}さん", + "welcomeMorningHey": "やあ {username}さん、準備はいいですか?", + "welcomeMorningFresh": "さわやかな朝ですね、{username}さん", + "welcomeMorningCoffee": "コーヒーとタスクはいかがですか、{username}さん?", + "welcomeMorningRise": "さあ、計画を立てましょう、{username}さん", + "welcomeMorningBack": "おかえりなさい、{username}さん", + "welcomeMondayFresh": "新しい一週間の始まりです、{username}さん", + "welcomeTuesday": "よい火曜日を、{username}さん", + "welcomeWednesdayMid": "もう週の半ばですね、{username}さん", + "welcomeThursday": "あと少しです、{username}さん", + "welcomeFridayPush": "金曜日、もうひと踏ん張りですね、{username}さん?", + "welcomeSaturday": "週末モードですね、{username}さん", + "welcomeSundaySession": "日曜日の作業ですか、{username}さん?", "welcomeDay": "こんにちは、{username}さん", + "welcomeDayBack": "作業再開ですね、{username}さん", + "welcomeDayFocus": "集中していきましょう、{username}さん", + "welcomeDayKeepGoing": "その調子、{username}さん", + "welcomeDayWhatsNext": "次は何ですか、{username}さん?", + "welcomeDayGood": "こんにちは、{username}さん", "welcomeEvening": "こんばんは、{username}さん", + "welcomeEveningWind": "そろそろ一段落ですか、{username}さん?", + "welcomeEveningReturns": "{username}さんのお戻りですね", + "welcomeEveningWrap": "そろそろ終わりにしませんか、{username}さん?", + "welcomeEveningOneMore": "あと一つだけいかがですか、{username}さん?", + "welcomeEveningStill": "まだ頑張っていますね、{username}さん?", "lastViewed": "最近の表示", "addToHomeScreen": "ホーム画面に追加すると、すぐにアクセスできて使いやすくなります。", "goToOverview": "概要に移動", @@ -53,6 +80,15 @@ "authenticating": "認証中…", "openIdStateError": "stateパラメータが一致しないため処理を中断しました。", "openIdGeneralError": "認証中にエラーが発生しました。", + "openIdTotpRequired": "このアカウントには2要素認証が必要です。TOTPコードを入力して再度サインインしてください。", + "openIdTotpSubmit": "続ける", + "oauthMissingParams": "必要なOAuthパラメータがありません: {params}", + "oauthRedirectedToApp": "アプリにリダイレクトされました。このタブは閉じて構いません。", + "desktopTryDemo": "デモを試す", + "desktopCustomServer": "カスタムサーバーURL", + "desktopCustomServerDescription": "使用するVikunjaサーバーのURLを入力して始めましょう。", + "desktopWaitingForAuth": "認証を待機中…", + "desktopOAuthError": "認証に失敗しました: {error}", "logout": "ログアウト", "emailInvalid": "有効なメールアドレスを入力してください。", "usernameRequired": "ユーザー名を入力してください。", @@ -71,6 +107,19 @@ "registrationFailed": "登録中にエラーが発生しました。入力内容を確認して、もう一度お試しください。" }, "settings": { + "bots": { + "title": "ボットユーザー", + "description": "ボットユーザーは、あなたが所有する API 専用のユーザーです。プロジェクトに追加したり、タスクを割り当てたり、API トークンで認証したりできます。対話的にログインすることはできません。", + "namePlaceholder": "マイアシスタント", + "create": "ボットを作成", + "enable": "有効にする", + "badge": "ボット", + "delete": { + "header": "このボットユーザーを削除", + "text1": "ボットユーザー「{username}」を削除してもよろしいですか?", + "text2": "この操作は取り消せません。このボットに紐付く API トークンはすべて失効します。" + } + }, "title": "設定", "newPasswordTitle": "パスワードの更新", "newPassword": "新しいパスワード", @@ -96,12 +145,21 @@ "weekStart": "週の始まり", "weekStartSunday": "日曜日", "weekStartMonday": "月曜日", + "weekStartTuesday": "火曜日", + "weekStartWednesday": "水曜日", + "weekStartThursday": "木曜日", + "weekStartFriday": "金曜日", + "weekStartSaturday": "土曜日", "language": "言語", "defaultProject": "デフォルトのプロジェクト", "defaultView": "デフォルトのビュー", "timezone": "タイムゾーン", "overdueTasksRemindersTime": "期限切れタスクのリマインダー送信時間", + "quickAddDefaultReminders": "クイック追加のデフォルトリマインダー", + "quickAddDefaultRemindersDescription": "期限日を持つクイック追加マジックで作成されたすべてのタスクに、これらのリマインダーが自動的に追加されます。", + "quickAddDefaultRemindersHint": "タスクの期限日を基準としたリマインダーを1つ以上追加してください。空にすると無効化されます。", "filterUsedOnOverview": "概要ページの絞り込み条件", + "showLastViewed": "概要ページに最近表示したプロジェクトを表示", "minimumPriority": "表示タスク優先度の最小値", "dateDisplay": "日付表示形式", "dateDisplayOptions": { @@ -125,7 +183,13 @@ "taskAndNotifications": "プロジェクトとタスク", "privacy": "プライバシー設定", "localization": "ローカライズ", - "appearance": "外観と動作" + "appearance": "外観と動作", + "desktop": "デスクトップアプリ" + }, + "desktop": { + "quickEntryShortcut": "クイック入力ショートカット", + "shortcutRecorderPlaceholder": "クリックしてショートカットを設定", + "shortcutRecorderRecording": "キーの組み合わせを押してください…" }, "totp": { "title": "2要素認証", @@ -135,15 +199,32 @@ "scanQR": "あるいは、TOTPアプリでこのQRコードをスキャンして認証コードを入力してください:", "passcode": "認証コード", "passcodePlaceholder": "TOTPアプリで生成された認証コード", + "confirmNotice": "2要素認証を有効化すると、すべてのセッションからログアウトされ、再度ログインが必要になります。", "setupSuccess": "2要素認証は正常に設定されました。", "enterPassword": "パスワードを入力してください", "disable": "2要素認証の無効化", + "confirmSuccess": "2要素認証を有効化しました!", "disableSuccess": "2要素認証は無効化されました。" }, "caldav": { "title": "CalDAV", + "howTo": "VikunjaをCalDAVクライアントに接続すると、さまざまなクライアントからすべてのタスクを表示・管理できます。以下のURLをクライアントに入力してください:", "more": "VikunjaのCalDAVに関する詳細情報", - "tokens": "CalDAVトークン" + "tokens": "CalDAVトークン", + "tokensHowTo": "CalDAV認証には、通常のアカウントパスワードかCalDAV専用トークンのいずれかを利用できます。", + "createToken": "CalDAVトークンを作成", + "tokenCreated": "新しいトークンはこちらです: {token}", + "wontSeeItAgain": "書き留めるか安全に保管してください — 再度表示することはできません。", + "mustUseToken": "サードパーティ製クライアントでCalDAVを利用するには、CalDAVトークンを作成する必要があります。作成したトークンをクライアントのパスワード欄に入力してください。", + "usernameIs": "CalDAV用のユーザー名: {0}", + "apiTokenHint": "CalDAV権限を持つAPIトークンも利用できます。{link} で作成してください。" + }, + "feeds": { + "title": "Atom フィード", + "howTo": "Atom 対応のフィードリーダーから Vikunja の通知を購読できます。以下の URL を使用してください:", + "usernameIs": "フィード用のユーザー名: {0}", + "apiTokenHint": "{scope} 権限を持つ API トークンで認証してください。{link} で作成できます。", + "tokenTitle": "Atom フィード" }, "avatar": { "title": "プロフィール画像", @@ -174,6 +255,10 @@ "backgroundBrightness": { "title": "背景の明るさ" }, + "webhooks": { + "title": "Webhook通知", + "description": "リマインダーや期限切れイベント発生時にPOSTリクエストを受信するWebhook URLを設定します。これらのWebhookはすべてのプロジェクトからイベントを受信します。" + }, "apiTokens": { "title": "APIトークン", "general": "APIトークンを使うとログインせずにVikunjaのAPIを利用できます。", @@ -189,6 +274,13 @@ "expired": "このトークンは {ago} に期限切れしています。", "tokenCreatedSuccess": "新しい API トークンはこちらです: {token}", "tokenCreatedNotSeeAgain": "このトークンは二度と表示されません。安全な場所に保管してください。", + "presets": { + "title": "クイックプリセット", + "readOnly": "読み取り専用", + "tasks": "タスク管理", + "projects": "プロジェクト管理", + "fullAccess": "フルアクセス" + }, "delete": { "header": "トークンの削除", "text1": "トークン \"{token}\" を削除してよろしいですか?", @@ -368,7 +460,8 @@ "addPlaceholder": "タスクを追加…", "empty": "このプロジェクトにはタスクが存在しません。", "newTaskCta": "タスクを作成してください。", - "editTask": "タスクの編集" + "editTask": "タスクの編集", + "sort": "並べ替え" }, "gantt": { "title": "ガント", @@ -377,7 +470,6 @@ "month": "月", "day": "日", "hour": "時間", - "range": "期間", "chartLabel": "プロジェクトガントチャート", "taskBarsForRow": "行 {rowId} のタスクバー", "taskBarLabel": "タスク: {task}。{startDate} から {endDate} まで。{dateType}。クリックして編集、ドラッグして移動。", @@ -406,7 +498,6 @@ "kanban": { "title": "カンバン", "limit": "上限: {limit}", - "noLimit": "未設定", "doneBucket": "バケットを完了", "doneBucketHint": "このバケットに移動されたすべてのタスクは自動的に完了としてマークされます。", "doneBucketHintExtended": "完了バケットに移動されたすべてのタスクは自動的に完了としてマークされます。他の場所にあるタスクも完了としてマークされるとこのバケットに移動されます。", @@ -427,7 +518,8 @@ "bucketTitleSavedSuccess": "バケットのタイトルは正常に保存されました。", "bucketLimitSavedSuccess": "バケットの上限は正常に保存されました。", "collapse": "このバケットを折りたたむ", - "bucketLimitReached": "バケットの上限に達しました。新しいタスクを追加するには、既存のタスクを削除するか、上限を緩和してください。" + "bucketLimitReached": "バケットの上限に達しました。新しいタスクを追加するには、既存のタスクを削除するか、上限を緩和してください。", + "bucketOptions": "バケットオプション" }, "pseudo": { "favorites": { @@ -550,6 +642,29 @@ } } }, + "sorting": { + "manually": "手動", + "apply": "並べ替えを適用", + "description": "このリスト内のタスクの並び順を選択します。手動並び替えを選ぶと、ドラッグ&ドロップでタスクの順序を変更できます。", + "options": { + "titleAsc": "タイトル (A–Z)", + "titleDesc": "タイトル (Z–A)", + "priorityDesc": "優先度 (高い順)", + "priorityAsc": "優先度 (低い順)", + "dueDateAsc": "期限日 (早い順)", + "dueDateDesc": "期限日 (遅い順)", + "startDateAsc": "開始日 (早い順)", + "startDateDesc": "開始日 (遅い順)", + "endDateAsc": "終了日 (早い順)", + "endDateDesc": "終了日 (遅い順)", + "percentDoneDesc": "完了率 (高い順)", + "percentDoneAsc": "完了率 (低い順)", + "createdDesc": "作成日時 (新しい順)", + "createdAsc": "作成日時 (古い順)", + "updatedDesc": "更新日時 (新しい順)", + "updatedAsc": "更新日時 (古い順)" + } + }, "migrate": { "title": "他のサービスからのインポート", "titleService": "{name}からVikunjaへのデータのインポート", @@ -564,7 +679,30 @@ "importUpload": "{name}からVikunjaにデータをインポートするには、以下のボタンをクリックしてファイルを選択してください。", "upload": "ファイルのアップロード", "migrationStartedWillReciveEmail": "{service}のリスト、タスク、メモ、リマインダー、ファイルをすべてVikunjaにインポートします。完了までしばらくお待ちください。メールでお知らせします。このウィンドウは閉じても構いません。", - "migrationInProgress": "現在移行中です。完了するまでしばらくお待ちください。" + "migrationInProgress": "現在移行中です。完了するまでしばらくお待ちください。", + "csv": { + "description": "カスタム列マッピングでCSVファイルからタスクをインポートします。", + "uploadDescription": "インポートするCSVファイルを選択してください。ファイルの1行目はヘッダーで、タスクデータを含んでいる必要があります。", + "selectFile": "CSVファイルを選択", + "columnMappingDescription": "CSVファイルの各列をタスク属性にマッピングします。Vikunjaが最も可能性の高いマッピングを自動検出しています。設定を変更すると、下部のプレビューが自動更新されます。", + "parsingOptions": "解析オプション", + "delimiter": "区切り文字", + "dateFormat": "日付形式", + "skipRows": "スキップする行数", + "mapColumns": "列のマッピング", + "example": "例:", + "preview": "プレビュー", + "previewDescription": "インポートされる {count} 件のタスクのうち先頭5件を表示しています。", + "import": "タスクをインポート", + "untitled": "無題のタスク", + "ignore": "無視", + "delimiters": { + "comma": "カンマ (,)", + "semicolon": "セミコロン (;)", + "tab": "タブ", + "pipe": "パイプ (|)" + } + } }, "label": { "title": "ラベル", @@ -604,7 +742,9 @@ "upcoming": "今後の予定", "settings": "設定", "imprint": "運営情報", - "privacy": "プライバシーポリシー" + "privacy": "プライバシーポリシー", + "closeSidebar": "サイドバーを閉じる", + "home": "Vikunja ホーム" }, "misc": { "loading": "読み込み中…", @@ -636,9 +776,15 @@ "createdBy": "{0} によって作成", "actions": "アクション", "cannotBeUndone": "この操作は元に戻せません!", - "avatarOfUser": "{user} のプロフィール画像" + "avatarOfUser": "{user} のプロフィール画像", + "closeBanner": "バナーを閉じる", + "closeDialog": "ダイアログを閉じる", + "closeQuickActions": "クイックアクションを閉じる", + "skipToContent": "メインコンテンツへスキップ", + "sortBy": "並び替え" }, "input": { + "projectColor": "プロジェクトの色", "resetColor": "色のリセット", "datepicker": { "today": "今日", @@ -698,6 +844,9 @@ "toggleHeaderCell": "選択中のセルのタイトル セル指定の有無の切り替え", "mergeOrSplit": "結合または分割", "fixTables": "テーブルの修正" + }, + "emoji": { + "empty": "絵文字が見つかりません" } }, "multiselect": { @@ -711,6 +860,7 @@ "date": "日付", "ranges": { "today": "今日", + "tomorrow": "明日", "thisWeek": "今週", "restOfThisWeek": "今から週末まで", "nextWeek": "来週", @@ -784,6 +934,7 @@ "addReminder": "リマイダーを作成…", "doneSuccess": "タスクを完了にしました。", "undoneSuccess": "タスクを未完了に戻しました。", + "readOnlyCheckbox": "このタスクには読み取り権限しかないため、完了にすることはできません。", "movedToProject": "タスクは {project} に移動しました。", "undo": "元に戻す", "checklistTotal": "{total}件中{checked}件のタスク", @@ -796,7 +947,8 @@ "select": "期間の選択", "noTasks": "タスクはありません — よい一日を!", "filterByLabel": "ラベル {label} での絞り込み", - "clearLabelFilter": "ラベルでの絞り込みの解除" + "clearLabelFilter": "ラベルでの絞り込みの解除", + "savedFilterIgnored": "ラベルごとのタスク表示中は、保存されたホーム画面のフィルターは適用されません。" }, "detail": { "chooseDueDate": "期日を設定…", @@ -811,9 +963,14 @@ "updateSuccess": "タスクは正常に保存されました。", "deleteSuccess": "タスクは正常に削除されました。", "duplicateSuccess": "タスクは正常に複製されました。", + "noBucket": "バケットなし", + "bucketChangedSuccess": "タスクのバケットを変更しました。", "belongsToProject": "このタスクはプロジェクト「{project}」に含まれています。", "back": "プロジェクトに戻る", "due": "期限: {at}", + "closeTaskDetail": "タスク詳細を閉じる", + "title": "タスクの詳細", + "markAsDone": "「{task}」を完了にする", "scrollToBottom": "一番下まで移動", "organization": "組織", "management": "管理", @@ -907,7 +1064,10 @@ "addedSuccess": "コメントは正常に追加されました。", "permalink": "コメントへのリンクをコピー", "sortNewestFirst": "新しい順", - "sortOldestFirst": "古い順" + "sortOldestFirst": "古い順", + "reply": "返信", + "jumpToOriginal": "元のコメントへ移動", + "deletedComment": "削除されたコメント" }, "mention": { "noUsersFound": "ユーザーが見つかりません" @@ -990,6 +1150,7 @@ "mode": "繰り返しモード", "monthly": "毎月", "fromCurrentDate": "完了からの間隔", + "each": "毎", "specifyAmount": "数字を入力…", "hours": "時間ごと", "days": "日ごと", @@ -998,6 +1159,7 @@ }, "quickAddMagic": { "hint": "期日、担当者、その他の項目を追加するキーワードが使用できます。", + "quickEntryHint": "日付やラベルなどに使えるマジックプレフィックスを利用できます。詳細はVikunja本体のアプリを開き、タスク入力欄のツールチップをご確認ください。", "title": "クイック追加", "intro": "タスクを作成する際に特定のキーワードを使うことで項目を直接追加できます。よく使う項目とともにタスクをすぐ追加できます。", "multiple": "複数使用できます。", @@ -1170,9 +1332,11 @@ "none": "通知はありません。よい一日を!", "explainer": "購読中のアクション、プロジェクト、タスクへの変更が発生すると、通知がここに表示されます。", "markAllRead": "通知をすべて既読にする", - "markAllReadSuccess": "通知をすべて既読にしました。" + "markAllReadSuccess": "通知をすべて既読にしました。", + "subscribeFeed": "Atom フィードで通知を購読" }, "quickActions": { + "notLoggedIn": "まずVikunja本体のウィンドウにログインしてください。", "commands": "コマンド", "placeholder": "コマンドまたはキーワードを入力…", "hint": "{project} を使うとプロジェクトを検索対象にできます。{project} または {label} (ラベル) を検索条件と組み合わせて使うとプロジェクト内のタスクやラベルの付いたタスクを検索できます。{assignee} を使うとチームを検索対象にできます。", @@ -1305,5 +1469,66 @@ "weeks": "週間", "years": "年" } + }, + "admin": { + "title": "管理", + "labels": { + "users": "ユーザー", + "tasks": "タスク" + }, + "overview": { + "shares": "共有", + "linkSharesShort": "リンク", + "teamSharesShort": "チーム", + "userSharesShort": "ユーザー", + "version": "バージョン", + "license": "ライセンス", + "licenseValidUntil": "有効期限", + "licenseExpiresIn": "あと {days} 日", + "licenseLastVerified": "最終検証日", + "licenseNever": "なし", + "licenseLastCheckFailed": "直近のチェックに失敗しました", + "licenseFeatures": "機能", + "licenseInstance": "インスタンスID", + "licenseManage": "管理" + }, + "searchUsersPlaceholder": "ユーザー名またはメールアドレスで検索…", + "users": { + "status": "ステータス", + "details": "詳細", + "detailsTitle": "ユーザー: {username}", + "issuer": "発行者", + "issuerLocal": "ローカル", + "issuerUrl": "発行者URL", + "subject": "サブジェクト", + "statusActive": "有効", + "statusEmailConfirmation": "メール確認待ち", + "statusDisabled": "無効化済み", + "statusLocked": "アカウントロック中", + "isAdminLabel": "管理者", + "addUser": "ユーザーを追加", + "createTitle": "ユーザーを作成", + "nameLabel": "名前", + "skipEmailConfirm": "メール確認をスキップ", + "createSubmit": "ユーザーを作成", + "saveButton": "変更を保存", + "createdSuccess": "ユーザー {username} を作成しました。", + "updatedSuccess": "ユーザー {username} を更新しました。", + "deletedSuccess": "ユーザー {username} を削除しました。", + "deleteScheduledSuccess": "ユーザー {username} に削除スケジュール確認メールが送信されます。", + "confirmDeleteTitle": "ユーザーを削除しますか?", + "confirmDeleteIntro": "ユーザー {username} をどのように削除しますか?", + "deleteModeScheduled": "削除をスケジュール", + "deleteModeScheduledHelp": "削除のスケジュールでは、ユーザー自身によるアカウント削除と同様に、確認メールをユーザーに送信します。", + "deleteModeNow": "今すぐ削除", + "deleteModeNowHelp": "今すぐ削除は、ユーザーとそのすべてのデータを即時に削除します。この操作は取り消せません。" + }, + "projects": { + "ownerLabel": "所有者", + "reassignOwner": "所有者を再割り当て", + "reassignTitle": "{title} を再割り当て", + "reassignedSuccess": "プロジェクトの所有者を再割り当てしました。", + "newOwnerLabel": "新しい所有者" + } } } \ No newline at end of file diff --git a/frontend/src/i18n/lang/ko-KR.json b/frontend/src/i18n/lang/ko-KR.json index f6136e700..3f541c33c 100644 --- a/frontend/src/i18n/lang/ko-KR.json +++ b/frontend/src/i18n/lang/ko-KR.json @@ -323,8 +323,7 @@ "default": "기본값", "month": "월", "day": "일", - "hour": "시", - "range": "날짜 범위" + "hour": "시" }, "table": { "title": "테이블", @@ -333,7 +332,6 @@ "kanban": { "title": "칸반", "limit": "제한: {limit}", - "noLimit": "설정 안함", "doneBucket": "완료 버킷", "doneBucketHint": "이 버킷으로 이동한 모든 할 일은 자동으로 완료로 표시됩니다.", "doneBucketHintExtended": "완료 버킷으로 이동된 모든 할 일은 자동으로 완료로 표시됩니다. 다른 곳에서 완료로 표시된 모든 할 일도 함께 이동됩니다.", diff --git a/frontend/src/i18n/lang/lt-LT.json b/frontend/src/i18n/lang/lt-LT.json index 98bd7dbbe..a27b89c9c 100644 --- a/frontend/src/i18n/lang/lt-LT.json +++ b/frontend/src/i18n/lang/lt-LT.json @@ -320,8 +320,7 @@ "default": "Numatytasis", "month": "Mėnuo", "day": "Diena", - "hour": "Valanda", - "range": "Datos intervalas" + "hour": "Valanda" }, "table": { "title": "Lentelė", @@ -330,7 +329,6 @@ "kanban": { "title": "Kanbanas", "limit": "Limitas: {limit}", - "noLimit": "Nenustatytas", "doneBucket": "Atliktųjų telkinys", "doneBucketHint": "Visos užduotys nukreiptos į šį telkinį bus automatiškai pažymėtos kaip atliktos.", "doneBucketHintExtended": "Visos užduotys perkeltos į atliktą telkiny bus automatiškai pažymėtos kaip atliktos. Visos užduotys pažymėtos kaip atliktos iš kitur bus perkeltos taip pat.", diff --git a/frontend/src/i18n/lang/nl-NL.json b/frontend/src/i18n/lang/nl-NL.json index 85b6bfd0e..a645a5d6d 100644 --- a/frontend/src/i18n/lang/nl-NL.json +++ b/frontend/src/i18n/lang/nl-NL.json @@ -5,9 +5,36 @@ }, "home": { "welcomeNight": "Goedenacht {username}!", + "welcomeNightOwl": "Hey {username}, nachtuil", + "welcomeNightBurning": "Tot diep in de nacht doorwerken {username}?", + "welcomeNightQuiet": "Stille uurtjes, {username}", + "welcomeNightLate": "Het is laat, {username}", + "welcomeNightMoonlit": "Maanverlichte planning, {username}?", "welcomeMorning": "Goedemorgen {username}!", + "welcomeMorningHey": "Hey {username}, klaar om te gaan?", + "welcomeMorningFresh": "Verse start, {username}", + "welcomeMorningCoffee": "Koffie en taken, {username}?", + "welcomeMorningRise": "Opstaan en plannen, {username}", + "welcomeMorningBack": "Welkom terug, {username}", + "welcomeMondayFresh": "Verse week, {username}", + "welcomeTuesday": "Fijne dinsdag, {username}", + "welcomeWednesdayMid": "Midden van de week alweer, {username}", + "welcomeThursday": "Bijna klaar, {username}", + "welcomeFridayPush": "Vrijdag nog even door, {username}?", + "welcomeSaturday": "Weekendstand, {username}", + "welcomeSundaySession": "Zondagse sessie, {username}?", "welcomeDay": "Hallo {username}!", + "welcomeDayBack": "Weer aan de slag, {username}", + "welcomeDayFocus": "Nu met focus, {username}", + "welcomeDayKeepGoing": "Blijf doorgaan, {username}", + "welcomeDayWhatsNext": "Wat komt er nu, {username}?", + "welcomeDayGood": "Goedemiddag {username}", "welcomeEvening": "Goedenavond {username}!", + "welcomeEveningWind": "Nu tot rust komen, {username}?", + "welcomeEveningReturns": "{username} keert terug", + "welcomeEveningWrap": "Tijd om af te ronden, {username}?", + "welcomeEveningOneMore": "Nog één ding, {username}?", + "welcomeEveningStill": "Nog steeds bezig, {username}?", "lastViewed": "Laatst bekeken", "addToHomeScreen": "Voeg deze app toe aan je startscherm voor snellere toegang en verbeterde ervaring.", "goToOverview": "Ga naar overzicht", @@ -53,6 +80,15 @@ "authenticating": "Authenticeren…", "openIdStateError": "Status komt niet overeen, weigert door te gaan!", "openIdGeneralError": "Er was een fout tijdens het authenticeren bij de externe applicatie.", + "openIdTotpRequired": "Je account vereist tweestapsverificatie. Voer je TOTP-code in en log opnieuw in.", + "openIdTotpSubmit": "Doorgaan", + "oauthMissingParams": "Ontbrekende OAuth parameters: {params}", + "oauthRedirectedToApp": "Je bent doorgestuurd naar de app. Je kunt dit tabblad nu sluiten.", + "desktopTryDemo": "Probeer de demo", + "desktopCustomServer": "Aangepaste server-URL", + "desktopCustomServerDescription": "Voer de URL in van je Vikunja server om te beginnen.", + "desktopWaitingForAuth": "Wachten op authenticatie…", + "desktopOAuthError": "Authenticatie mislukt: {error}", "logout": "Uitloggen", "emailInvalid": "Vul een geldig e-mailadres in.", "usernameRequired": "Geef een gebruikersnaam op.", @@ -67,9 +103,23 @@ "alreadyHaveAnAccount": "Heb je al een account?", "remember": "Ingelogd blijven", "registrationDisabled": "Registratie is uitgeschakeld.", - "passwordResetTokenMissing": "Wachtwoord reset token ontbreekt." + "passwordResetTokenMissing": "Wachtwoord reset token ontbreekt.", + "registrationFailed": "Er is een fout opgetreden tijdens de registratie. Controleer je invoer en probeer opnieuw." }, "settings": { + "bots": { + "title": "Bot-gebruikers", + "description": "Bot-gebruikers zijn API-only gebruikers waar jij eigenaar van bent. Ze kunnen worden toegevoegd aan projecten, taken krijgen en authenticeren met API-tokens. Ze kunnen niet interactief inloggen.", + "namePlaceholder": "Mijn Assistent", + "create": "Bot aanmaken", + "enable": "Inschakelen", + "badge": "Bot", + "delete": { + "header": "Verwijder deze bot-gebruiker", + "text1": "Weet je zeker dat je bot-gebruiker \"{username}\" wilt verwijderen?", + "text2": "Dit is onomkeerbaar. Alle API-tokens die bij deze bot horen, worden ingetrokken." + } + }, "title": "Instellingen", "newPasswordTitle": "Je wachtwoord bijwerken", "newPassword": "Nieuw wachtwoord", @@ -95,12 +145,21 @@ "weekStart": "Week begint op", "weekStartSunday": "Zondag", "weekStartMonday": "Maandag", + "weekStartTuesday": "Dinsdag", + "weekStartWednesday": "Woensdag", + "weekStartThursday": "Donderdag", + "weekStartFriday": "Vrijdag", + "weekStartSaturday": "Zaterdag", "language": "Taal", "defaultProject": "Standaardproject", "defaultView": "Standaardweergave", "timezone": "Tijdzone", "overdueTasksRemindersTime": "Tijdstip herinneringsmail voor achterstallige taken", + "quickAddDefaultReminders": "Standaardherinneringen voor Snel Toevoegen", + "quickAddDefaultRemindersDescription": "Deze herinneringen worden automatisch toegevoegd aan elke taak die is gemaakt via Magisch Snel Toevoegen met een vervaldatum.", + "quickAddDefaultRemindersHint": "Voeg herinnering(en) toe ten opzichte van de vervaldatum van de taak. Laat leeg om uit te schakelen.", "filterUsedOnOverview": "Opgeslagen filter toegepast op de overzichtspagina", + "showLastViewed": "Toon laatst bekeken projecten op de overzichtspagina", "minimumPriority": "Minimale zichtbare taakprioriteit", "dateDisplay": "Datumweergave", "dateDisplayOptions": { @@ -124,7 +183,13 @@ "taskAndNotifications": "Projecten & taken", "privacy": "Privacy", "localization": "Lokalisatie", - "appearance": "Uiterlijk & gedrag" + "appearance": "Uiterlijk & gedrag", + "desktop": "Desktop app" + }, + "desktop": { + "quickEntryShortcut": "Sneltoets voor snelle invoer", + "shortcutRecorderPlaceholder": "Klik om sneltoets in te stellen", + "shortcutRecorderRecording": "Druk op een toetscombinatie…" }, "totp": { "title": "Tweestapsverificatie", @@ -134,15 +199,32 @@ "scanQR": "Als alternatief kan je ook deze QR code scannen:", "passcode": "Je toegangscode", "passcodePlaceholder": "Een code gegenereerd door je TOTP-app", + "confirmNotice": "Na het inschakelen van tweestapsverificatie, wordt je uitgelogd uit alle sessies en moet je opnieuw inloggen.", "setupSuccess": "Je hebt tweestapsverificatie succesvol ingesteld!", "enterPassword": "Voer alsjeblieft je wachtwoord in", "disable": "Tweestapsverificatie uitschakelen", + "confirmSuccess": "Je hebt tweestapsverificatie succesvol ingeschakeld!", "disableSuccess": "Uitschakelen tweestapsverificatie is geslaagd." }, "caldav": { "title": "CalDAV", + "howTo": "Je kunt Vikunja verbinden met CalDAV-clients om taken te bekijken en beheren vanuit verschillende clients. Voer deze url in bij je client:", "more": "Meer informatie over CalDAV in Vikunja", - "tokens": "CalDAV tokens" + "tokens": "CalDAV tokens", + "tokensHowTo": "Voor CalDAV-authenticatie gebruik je jouw normale accountwachtwoord of een speciaal CalDAV-token.", + "createToken": "Maak een CalDAV-token", + "tokenCreated": "Hier is je nieuwe token: {token}", + "wontSeeItAgain": "Schrijf het op of bewaar het veilig - je kunt het hierna niet meer inzien.", + "mustUseToken": "Je moet een CalDAV-token aanmaken om CalDAV te gebruiken met een externe client. Gebruik het token in het wachtwoordveld van uw client.", + "usernameIs": "Je gebruikersnaam voor CalDAV is: {0}", + "apiTokenHint": "Je kunt ook een API-token gebruiken met CalDAV-permissie. Maak er een aan in {link}." + }, + "feeds": { + "title": "Atom Feed", + "howTo": "Je kunt je abonneren op je Vikunja meldingen met elke Atom-compatibele feedlezer. Gebruik deze URL:", + "usernameIs": "Je gebruikersnaam voor de feed is: {0}", + "apiTokenHint": "Authenticeer met een API-token die {scope} permissies heeft. Maak er een aan in {link}.", + "tokenTitle": "Atom feed" }, "avatar": { "title": "Avatar", @@ -173,6 +255,10 @@ "backgroundBrightness": { "title": "Achtergrond helderheid" }, + "webhooks": { + "title": "Webhook notificaties", + "description": "Configureer webhook-URL's om POST aanvragen te ontvangen wanneer herinneringen of achterstallige gebeurtenissen worden geactiveerd. Deze webhooks ontvangen gebeurtenissen van al je projecten." + }, "apiTokens": { "title": "API tokens", "general": "Met API tokens kun je Vikunja's API gebruiken zonder gebruikersnaam en wachtwoord.", @@ -188,6 +274,13 @@ "expired": "Dit token is verlopen {ago}.", "tokenCreatedSuccess": "Hier is je API-token: {token}", "tokenCreatedNotSeeAgain": "Bewaar het op een veilige locatie, het wordt slechts één keer getoond!", + "presets": { + "title": "Snelle voorkeursinstellingen", + "readOnly": "Alleen-lezen", + "tasks": "Taakbeheer", + "projects": "Projectmanagement", + "fullAccess": "Volledige toegang" + }, "delete": { "header": "Dit token verwijderen", "text1": "Weet je zeker dat je token \"{token}\" wilt verwijderen?", @@ -199,6 +292,20 @@ "expiresAt": "Verloopt op", "permissions": "Machtigingen" } + }, + "sessions": { + "title": "Sessies", + "description": "Dit zijn alle apparaten die momenteel zijn ingelogd op je account. Je kunt elke sessie intrekken om dat apparaat uit te loggen. Het kan tot 10 minuten duren voordat de intrekking volledig van kracht is.", + "deviceInfo": "Apparaat", + "ipAddress": "IP-adres", + "lastActive": "Laatst actief", + "current": "Huidige sessie", + "delete": { + "header": "Sessie intrekken", + "text": "Weet je zeker dat je deze sessie wilt intrekken? Het apparaat wordt uitgelogd. Het kan tot 10 minuten duren voordat de sessie volledig is verlopen." + }, + "deleteSuccess": "De sessie is ingetrokken. Het kan tot 10 minuten duren voordat de sessie volledig is verlopen.", + "noOtherSessions": "Geen andere actieve sessies." } }, "deletion": { @@ -353,7 +460,8 @@ "addPlaceholder": "Taak toevoegen…", "empty": "Dit project is momenteel leeg.", "newTaskCta": "Taak aanmaken.", - "editTask": "Taak bewerken" + "editTask": "Taak bewerken", + "sort": "Sorteren" }, "gantt": { "title": "Gantt", @@ -362,7 +470,6 @@ "month": "Maand", "day": "Dag", "hour": "Uur", - "range": "Datumbereik", "chartLabel": "Project Gantt-diagram", "taskBarsForRow": "Taakbalken voor rij {rowId}", "taskBarLabel": "Taak: {task}. Van {startDate} tot {endDate}. {dateType}. Klik om te bewerken, sleep om te verplaatsen.", @@ -379,7 +486,10 @@ "taskAriaLabel": "Taak: {task}", "taskAriaLabelById": "Taak {id}", "partialDatesStart": "Alleen startdatum (open-einde)", - "partialDatesEnd": "Alleen einddatum (open-einde)" + "partialDatesEnd": "Alleen einddatum (open-einde)", + "expandGroup": "Groep uitklappen: {task}", + "collapseGroup": "Groep inklappen: {task}", + "toggleRelationArrows": "Relatiepijlen wisselen" }, "table": { "title": "Tabel", @@ -388,7 +498,6 @@ "kanban": { "title": "Kanban", "limit": "Limiet: {limit}", - "noLimit": "Niet ingesteld", "doneBucket": "Categorie 'voltooid'", "doneBucketHint": "Alle taken die je naar deze categorie verplaatst worden automatisch als 'voltooid' gemarkeerd.", "doneBucketHintExtended": "Taken die je verplaatst naar de categorie 'voltooid' worden automatisch gemarkeerd als voltooid. Ook taken die je elders als voltooid aanmerkt, worden hierheen verplaatst.", @@ -409,7 +518,8 @@ "bucketTitleSavedSuccess": "De categorietitel is succesvol opgeslagen.", "bucketLimitSavedSuccess": "De categorielimiet is succesvol opgeslagen.", "collapse": "Deze categorie inklappen", - "bucketLimitReached": "U heeft de categorielimiet bereikt. Verwijder taken of verhoog de limiet om nieuwe taken toe te voegen." + "bucketLimitReached": "U heeft de categorielimiet bereikt. Verwijder taken of verhoog de limiet om nieuwe taken toe te voegen.", + "bucketOptions": "Categorie-opties" }, "pseudo": { "favorites": { @@ -532,6 +642,29 @@ } } }, + "sorting": { + "manually": "Handmatig", + "apply": "Sortering toepassen", + "description": "Kies hoe taken in deze lijst worden gesorteerd. Bij handmatig sorteren kun je taken verslepen om ze anders te ordenen.", + "options": { + "titleAsc": "Titel (A–Z)", + "titleDesc": "Titel (Z–A)", + "priorityDesc": "Prioriteit (hoogste eerst)", + "priorityAsc": "Prioriteit (laagste eerst)", + "dueDateAsc": "Vervaldatum (vroegste eerst)", + "dueDateDesc": "Vervaldatum (laatste eerst)", + "startDateAsc": "Startdatum (vroegste eerst)", + "startDateDesc": "Startdatum (laatste eerst)", + "endDateAsc": "Einddatum (vroegste eerst)", + "endDateDesc": "Einddatum (laatste eerst)", + "percentDoneDesc": "% gereed (meest gereed eerst)", + "percentDoneAsc": "% gereed (minst gereed eerst)", + "createdDesc": "Aangemaakt (nieuwste eerst)", + "createdAsc": "Aangemaakt (oudste eerst)", + "updatedDesc": "Bijgewerkt (nieuwste eerst)", + "updatedAsc": "Bijgewerkt (oudste eerst)" + } + }, "migrate": { "title": "Importeer vanuit een andere dienst", "titleService": "Importeer je gegevens van {name} naar Vikunja", @@ -546,7 +679,30 @@ "importUpload": "Om gegevens van {name} te importeren in Vikunja, klik je op de knop hieronder om een bestand te kiezen.", "upload": "Bestand uploaden", "migrationStartedWillReciveEmail": "Vikunja gaat nu je lijsten/projecten, taken, notities, herinneringen en bestanden van {service} importeren. Omdat dit een tijdje zal duren, sturen we je een e-mail zodra het klaar is. Je kunt dit venster nu sluiten.", - "migrationInProgress": "Er is momenteel een migratie aan de gang. Wacht tot dit voltooid is." + "migrationInProgress": "Er is momenteel een migratie aan de gang. Wacht tot dit voltooid is.", + "csv": { + "description": "Importeer taken uit een CSV-bestand met aangepaste kolomtoewijzing.", + "uploadDescription": "Kies een CSV-bestand om te importeren. Het bestand moet taakgegevens bevatten met kolomkoppen in de eerste rij.", + "selectFile": "Kies CSV-bestand", + "columnMappingDescription": "Wijs elke kolom in je CSV-bestand toe aan een taakattribuut. Vikunja heeft de meest waarschijnlijke toewijzingen automatisch gedetecteerd. Het voorbeeld hieronder wordt automatisch bijgewerkt wanneer je de instellingen aanpast.", + "parsingOptions": "Opties voor verwerking", + "delimiter": "Scheidingsteken", + "dateFormat": "Datumnotatie", + "skipRows": "Rijen overslaan", + "mapColumns": "Kolommen toewijzen", + "example": "bijv.", + "preview": "Voorbeeld", + "previewDescription": "Toont de eerste 5 van {count} taken die zullen worden geïmporteerd.", + "import": "Taken importeren", + "untitled": "Naamloze taak", + "ignore": "Negeren", + "delimiters": { + "comma": "Komma (,)", + "semicolon": "Puntkomma (;)", + "tab": "Tab", + "pipe": "Pijp (|)" + } + } }, "label": { "title": "Labels", @@ -586,7 +742,9 @@ "upcoming": "Aankomend", "settings": "Instellingen", "imprint": "Imprint", - "privacy": "Privacybeleid" + "privacy": "Privacybeleid", + "closeSidebar": "Zijbalk sluiten", + "home": "Vikunja home" }, "misc": { "loading": "Bezig met laden…", @@ -618,9 +776,15 @@ "createdBy": "Aangemaakt door {0}", "actions": "Acties", "cannotBeUndone": "Dit kan niet ongedaan gemaakt worden!", - "avatarOfUser": "{user}'s profielfoto" + "avatarOfUser": "{user}'s profielfoto", + "closeBanner": "Banner sluiten", + "closeDialog": "Dialoogvenster sluiten", + "closeQuickActions": "Snelle acties sluiten", + "skipToContent": "Direct naar hoofdinhoud", + "sortBy": "Sorteren op" }, "input": { + "projectColor": "Projectkleur", "resetColor": "Kleur resetten", "datepicker": { "today": "Vandaag", @@ -680,6 +844,9 @@ "toggleHeaderCell": "Celkopteksten in-/uitschakelen", "mergeOrSplit": "Samenvoegen of splitsen", "fixTables": "Tabellen repareren" + }, + "emoji": { + "empty": "Geen emoji gevonden" } }, "multiselect": { @@ -693,6 +860,7 @@ "date": "Datum", "ranges": { "today": "Vandaag", + "tomorrow": "Morgen", "thisWeek": "Deze week", "restOfThisWeek": "De rest van deze week", "nextWeek": "Volgende week", @@ -766,6 +934,7 @@ "addReminder": "Herinnering toevoegen…", "doneSuccess": "De taak is succesvol aangemerkt als voltooid.", "undoneSuccess": "Het voltooien van de taak is succesvol teruggedraaid.", + "readOnlyCheckbox": "Je hebt alleen leestoegang tot deze taak en kunt deze niet als voltooid markeren.", "movedToProject": "De taak werd verplaatst naar {project}.", "undo": "Ongedaan maken", "checklistTotal": "{checked} van {total} taken", @@ -778,7 +947,8 @@ "select": "Selecteer datumbereik", "noTasks": "Niets te doen - fijne dag!", "filterByLabel": "Gefilterd op label {label}", - "clearLabelFilter": "Wis labelfilter" + "clearLabelFilter": "Wis labelfilter", + "savedFilterIgnored": "Je opgeslagen startpagina-filter wordt niet toegepast als je taken bekijkt per label." }, "detail": { "chooseDueDate": "Klik hier om een vervaldatum in te stellen", @@ -792,9 +962,15 @@ "doneAt": "{0} voltooid", "updateSuccess": "De taak is succesvol opgeslagen.", "deleteSuccess": "De taak is succesvol verwijderd.", + "duplicateSuccess": "De taak is succesvol gedupliceerd.", + "noBucket": "Geen categorie", + "bucketChangedSuccess": "De taakcategorie is succesvol gewijzigd.", "belongsToProject": "Deze taak hoort bij project '{project}'", "back": "Terug naar project", "due": "Vervalt {at}", + "closeTaskDetail": "Sluit taakdetails", + "title": "Taakdetails", + "markAsDone": "Markeer '{task}' als gereed", "scrollToBottom": "Scroll naar beneden", "organization": "Organisatie", "management": "Beheer", @@ -817,6 +993,7 @@ "attachments": "Bijlagen toevoegen", "relatedTasks": "Relatie toevoegen", "moveProject": "Verplaatsen", + "duplicate": "Dupliceer", "color": "Kleur instellen", "delete": "Verwijder", "favorite": "Toevoegen aan favorieten", @@ -839,8 +1016,8 @@ "relatedTasks": "Verwante Taken", "reminders": "Herinneringen", "repeat": "Herhalen", - "comment": "{count} opmerking | {count} opmerkingen", - "commentCount": "Aantal opmerkingen", + "comment": "{count} reactie | {count} reacties", + "commentCount": "Aantal reacties", "startDate": "Begindatum", "title": "Titel", "updated": "Bijgewerkt", @@ -876,7 +1053,7 @@ }, "comment": { "title": "Reacties", - "loading": "Bezig met laden van reacties…", + "loading": "Reacties laden…", "edited": "bewerkt op {date}", "creating": "Opmerking maken…", "placeholder": "Voeg je reactie toe, druk op '/' voor meer opties…", @@ -887,7 +1064,10 @@ "addedSuccess": "De reactie is succesvol toegevoegd.", "permalink": "Kopieer permalink naar deze reactie", "sortNewestFirst": "Nieuwste eerst", - "sortOldestFirst": "Oudste eerst" + "sortOldestFirst": "Oudste eerst", + "reply": "Beantwoorden", + "jumpToOriginal": "Ga naar originele reactie", + "deletedComment": "verwijderde reactie" }, "mention": { "noUsersFound": "Geen gebruikers gevonden" @@ -979,6 +1159,7 @@ }, "quickAddMagic": { "hint": "Gebruik magische prefixes om vervaldata, toegewezen personen en andere taakeigenschappen te definiëren.", + "quickEntryHint": "Gebruik magische voorvoegsels voor datums, labels en meer. Open de Vikunja hoofd-app en bekijk de tooltip op de taakinvoer voor meer details.", "title": "Snel-toevoegen magie", "intro": "Bij het aanmaken van een taak kun je speciale trefwoorden gebruiken om direct kenmerken toe te voegen aan de nieuwe taak. Hiermee kun je veelgebruikte kenmerken veel sneller toevoegen aan taken.", "multiple": "Je kan dit meerdere keren gebruiken.", @@ -1151,9 +1332,11 @@ "none": "Je hebt geen meldingen. Fijne dag!", "explainer": "Hier verschijnen meldingen wanneer acties, projecten of taken gebeuren waarop u bent geabonneerd.", "markAllRead": "Markeer alle meldingen als gelezen", - "markAllReadSuccess": "Alle meldingen zijn als gelezen gemarkeerd." + "markAllReadSuccess": "Alle meldingen zijn als gelezen gemarkeerd.", + "subscribeFeed": "Abonneren op meldingen via Atom feed" }, "quickActions": { + "notLoggedIn": "Log eerst in op het Vikunja hoofdscherm.", "commands": "Opdrachten", "placeholder": "Typ een opdracht of zoek…", "hint": "Je kunt {project} gebruiken om het zoeken te beperken tot een project. Combineer {project} of {label} (labels) met een zoekopdracht om te zoeken naar een taak met deze labels of op dat project. Gebruik {assignee} om alleen te zoeken naar teams.", @@ -1286,5 +1469,66 @@ "weeks": "week|weken", "years": "jaar|jaren" } + }, + "admin": { + "title": "Beheer", + "labels": { + "users": "Gebruikers", + "tasks": "Taken" + }, + "overview": { + "shares": "Deelbare koppelingen", + "linkSharesShort": "link", + "teamSharesShort": "team", + "userSharesShort": "gebruiker", + "version": "Versie", + "license": "Licentie", + "licenseValidUntil": "Geldig tot", + "licenseExpiresIn": "over {days} dagen", + "licenseLastVerified": "Laatst geverifieerd", + "licenseNever": "nooit", + "licenseLastCheckFailed": "laatste controle mislukt", + "licenseFeatures": "Functionaliteiten", + "licenseInstance": "Instance ID", + "licenseManage": "Beheren" + }, + "searchUsersPlaceholder": "Zoek op gebruikersnaam of e-mail…", + "users": { + "status": "Status", + "details": "Details", + "detailsTitle": "Gebruiker: {username}", + "issuer": "Uitgever", + "issuerLocal": "Lokaal", + "issuerUrl": "Uitgever URL", + "subject": "Onderwerp", + "statusActive": "Actief", + "statusEmailConfirmation": "E-mailbevestiging vereist", + "statusDisabled": "Uitgeschakeld", + "statusLocked": "Account vergrendeld", + "isAdminLabel": "Beheerder", + "addUser": "Gebruiker toevoegen", + "createTitle": "Gebruiker aanmaken", + "nameLabel": "Naam", + "skipEmailConfirm": "E-mailbevestiging overslaan", + "createSubmit": "Gebruiker aanmaken", + "saveButton": "Wijzigingen opslaan", + "createdSuccess": "Gebruiker {username} aangemaakt.", + "updatedSuccess": "Gebruiker {username} bijgewerkt.", + "deletedSuccess": "Gebruiker {username} verwijderd.", + "deleteScheduledSuccess": "Gebruiker {username} ontvangt een bevestigingsmail om de verwijdering te plannen.", + "confirmDeleteTitle": "Gebruiker verwijderen?", + "confirmDeleteIntro": "Hoe moet gebruiker {username} worden verwijderd?", + "deleteModeScheduled": "Verwijdering plannen", + "deleteModeScheduledHelp": "Bij 'verwijdering plannen' ontvangt de gebruiker een bevestigingsmail, lijkend op een zelfgestarte accountverwijdering.", + "deleteModeNow": "Nu verwijderen", + "deleteModeNowHelp": "'Nu verwijderen' verwijdert de gebruiker en hun data onmiddellijk. Dit kan niet ongedaan worden gemaakt." + }, + "projects": { + "ownerLabel": "Eigenaar", + "reassignOwner": "Nieuwe eigenaar toewijzen", + "reassignTitle": "Opnieuw toewijzen {title}", + "reassignedSuccess": "Projecteigenaar opnieuw toegewezen.", + "newOwnerLabel": "Nieuwe eigenaar" + } } } \ No newline at end of file diff --git a/frontend/src/i18n/lang/no-NO.json b/frontend/src/i18n/lang/no-NO.json index a32189bd4..6761a82dd 100644 --- a/frontend/src/i18n/lang/no-NO.json +++ b/frontend/src/i18n/lang/no-NO.json @@ -353,7 +353,6 @@ "month": "Måned", "day": "Dag", "hour": "Time", - "range": "Datointervall", "chartLabel": "Gantt-kart for prosjekt", "taskBarsForRow": "Oppgavelinjer for rad {rowId}", "taskBarLabel": "Oppgave: {task}. Fra {startDate} til {endDate}. {dateType}. Klikk for å redigere, dra for å flytte.", @@ -377,7 +376,6 @@ "kanban": { "title": "Kanban", "limit": "Begrens: {limit}", - "noLimit": "Ikke angitt", "doneBucket": "Ferdigkurv", "doneBucketHint": "Alle oppgaver som flyttes til denne kurven vil automatisk bli markert som ferdige.", "doneBucketHintExtended": "Alle oppgaver som er flyttet til ferdigkurven, vil automatisk bli markert som ferdige. Alle oppgaver markert som ferdige fra andre steder vil også bli flyttet.", diff --git a/frontend/src/i18n/lang/pl-PL.json b/frontend/src/i18n/lang/pl-PL.json index 796e5e37e..1e3590223 100644 --- a/frontend/src/i18n/lang/pl-PL.json +++ b/frontend/src/i18n/lang/pl-PL.json @@ -300,8 +300,7 @@ "default": "Domyślnie", "month": "Miesiąc", "day": "Dzień", - "hour": "Godzina", - "range": "Zakres dat" + "hour": "Godzina" }, "table": { "title": "Tabela", @@ -310,7 +309,6 @@ "kanban": { "title": "Kanban", "limit": "Limit: {limit}", - "noLimit": "Nie ustawiony", "doneBucket": "Zakończone zadania", "doneBucketHint": "Wszystkie zadania przeniesione do tej kolumny zostaną automatycznie oznaczone jako zakończone.", "doneBucketHintExtended": "Wszystkie zadania przeniesione do kolumny zakończonych zostaną automatycznie oznaczone jako zakończone. Wszystkie zadania oznaczone jako zakończone z innych miejsc zostaną przeniesione.", diff --git a/frontend/src/i18n/lang/pt-BR.json b/frontend/src/i18n/lang/pt-BR.json index e5c91a043..9a2a0f815 100644 --- a/frontend/src/i18n/lang/pt-BR.json +++ b/frontend/src/i18n/lang/pt-BR.json @@ -286,8 +286,7 @@ "default": "Padrão", "month": "Mês", "day": "Dia", - "hour": "Hora", - "range": "Período" + "hour": "Hora" }, "table": { "title": "Tabela", @@ -296,7 +295,6 @@ "kanban": { "title": "Kanban", "limit": "Limite: {limit}", - "noLimit": "Não definido", "doneBucket": "Bucket concluído", "doneBucketHint": "Todas as tarefas movidas para este bucket serão marcadas automaticamente como concluídas.", "doneBucketHintExtended": "Todas as tarefas movidas para o bucket concluído serão automaticamente concluídas também. Todas as tarefas concluídas de outro lugar serão movidas também.", diff --git a/frontend/src/i18n/lang/pt-PT.json b/frontend/src/i18n/lang/pt-PT.json index 58c6e6aca..e986fe4ca 100644 --- a/frontend/src/i18n/lang/pt-PT.json +++ b/frontend/src/i18n/lang/pt-PT.json @@ -362,7 +362,6 @@ "month": "Mês", "day": "Dia", "hour": "Hora", - "range": "Intervalo de Datas", "chartLabel": "Gráfico de Gantt do projeto", "taskBarsForRow": "Barras de tarefas para a linha {rowId}", "taskBarLabel": "Tarefa: {task}. De {startDate} a {endDate}. {dateType}. Clica para editar, arrasta para mover.", @@ -386,7 +385,6 @@ "kanban": { "title": "Kanban", "limit": "Limite: {limit}", - "noLimit": "Não Definido", "doneBucket": "Conjunto concluído", "doneBucketHint": "Todas as tarefas movidas para este conjunto serão automaticamente marcadas como concluídas.", "doneBucketHintExtended": "Todas as tarefas movidas para o conjunto concluído serão marcadas automaticamente como concluídas. Todas as tarefas marcadas como concluídas em outro lugar também serão movidas.", diff --git a/frontend/src/i18n/lang/ru-RU.json b/frontend/src/i18n/lang/ru-RU.json index 9f4163bbf..8559e34e8 100644 --- a/frontend/src/i18n/lang/ru-RU.json +++ b/frontend/src/i18n/lang/ru-RU.json @@ -407,7 +407,6 @@ "month": "Месяц", "day": "День", "hour": "Час", - "range": "Диапазон", "chartLabel": "Диаграмма Ганта", "taskBarsForRow": "Задачи в строке {rowId}", "taskBarLabel": "Задача: {task}. С {startDate} по {endDate}. {dateType}. Нажмите для изменения, потяните для перемещения.", @@ -435,7 +434,6 @@ "kanban": { "title": "Канбан", "limit": "Лимит: {limit}", - "noLimit": "не установлен", "doneBucket": "Колонка завершённых", "doneBucketHint": "Все задачи, помещённые в эту колонку, автоматически отмечаются как завершённые.", "doneBucketHintExtended": "Все задачи, перенесённые в колонку завершённых, будут помечены как завершённые. Все задачи, помеченные как завершённые, также будут перемещены в эту колонку.", diff --git a/frontend/src/i18n/lang/sl-SI.json b/frontend/src/i18n/lang/sl-SI.json index faaf474af..d00e67842 100644 --- a/frontend/src/i18n/lang/sl-SI.json +++ b/frontend/src/i18n/lang/sl-SI.json @@ -314,8 +314,7 @@ "default": "Privzeto", "month": "Mesec", "day": "Dan", - "hour": "Ura", - "range": "Datumski obseg" + "hour": "Ura" }, "table": { "title": "Tabela", @@ -324,7 +323,6 @@ "kanban": { "title": "Kanban", "limit": "Omejitev: {limit}", - "noLimit": "Ni nastavljeno", "doneBucket": "Vedro končanih nalog", "doneBucketHint": "Vse naloge, premaknjene v to vedro, bodo samodejno označene kot opravljene.", "doneBucketHintExtended": "Vse naloge, premaknjene v vedro končanih nalog, bodo samodejno označene kot opravljene. Premaknjene bodo tudi vse naloge, ki so od drugje označena kot opravljene.", diff --git a/frontend/src/i18n/lang/sv-SE.json b/frontend/src/i18n/lang/sv-SE.json index 0bbe11cd7..8d7f7e72d 100644 --- a/frontend/src/i18n/lang/sv-SE.json +++ b/frontend/src/i18n/lang/sv-SE.json @@ -362,7 +362,6 @@ "month": "Månad", "day": "Dag", "hour": "Timme", - "range": "Datumintervall", "chartLabel": "Projektets Gantt-schema", "taskBarsForRow": "Uppgiftsstaplar för rad {rowId}", "taskBarLabel": "Uppgift: {task}. Från {startDate} till {endDate}. {dateType}. Klicka för att redigera, dra för att flytta.", @@ -386,7 +385,6 @@ "kanban": { "title": "Kanban", "limit": "Gräns: {limit}", - "noLimit": "Ej inställt", "doneBucket": "Färdigkolumn", "doneBucketHint": "Alla uppgifter som flyttas till den här kolumnen markeras automatiskt som klara.", "doneBucketHintExtended": "Alla uppgifter som flyttas till färdigkolumnen markeras som färdiga automatiskt. Alla uppgifter som markerats som färdiga från andra håll kommer också att flyttas.", diff --git a/frontend/src/i18n/lang/th-TH.json b/frontend/src/i18n/lang/th-TH.json new file mode 100644 index 000000000..9e26dfeeb --- /dev/null +++ b/frontend/src/i18n/lang/th-TH.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/frontend/src/i18n/lang/tr-TR.json b/frontend/src/i18n/lang/tr-TR.json index 40a33632b..bcd0bd6f6 100644 --- a/frontend/src/i18n/lang/tr-TR.json +++ b/frontend/src/i18n/lang/tr-TR.json @@ -362,7 +362,6 @@ "month": "Ay", "day": "Gün", "hour": "Saat", - "range": "Tarih Aralığı", "chartLabel": "Proje Gantt Şeması", "taskBarsForRow": "{rowId} satırı için görev çubukları", "taskBarLabel": "Görev: {task}. {startDate} tarihinden {endDate} tarihine. {dateType}. Düzenlemek için tıklayın, taşımak için sürükleyin.", @@ -386,7 +385,6 @@ "kanban": { "title": "Kanban", "limit": "Sınır: {limit}", - "noLimit": "Belirlenmedi", "doneBucket": "Tamamlananlar kutusu", "doneBucketHint": "Bu kutuya taşınan tüm görevler otomatik olarak tamamlandı olarak işaretlenir.", "doneBucketHintExtended": "Tamamlananlar kutusuna taşınan tüm görevler otomatik olarak tamamlandı olarak işaretlenir. Başka bir yerden tamamlandı olarak işaretlenen görevler de buraya taşınır.", diff --git a/frontend/src/i18n/lang/uk-UA.json b/frontend/src/i18n/lang/uk-UA.json index 87820eb34..9c95bc7f1 100644 --- a/frontend/src/i18n/lang/uk-UA.json +++ b/frontend/src/i18n/lang/uk-UA.json @@ -45,7 +45,7 @@ }, "demo": { "title": "Цей екземпляр працює в демонстраційному режимі. Не використовуйте його для реальних даних!", - "everythingWillBeDeleted": "Все буде видалено з регулярними інтервалами!", + "everythingWillBeDeleted": "Усі дані регулярно видаляться!", "accountWillBeDeleted": "Обліковий запис буде вилучено включно з усіма проєктами, завданнями та вкладеннями, які ви створили." }, "ready": { @@ -172,6 +172,7 @@ "yyyy/mm/dd": "YYYY/MM/DD" }, "timeFormat": "Формат часу", + "timeTrackingDefaultStart": "Початковий час для автоматичного заповнення обліку часу", "timeFormatOptions": { "12h": "12-годинний (AM/PM)", "24h": "24-годинний (HH:mm)" @@ -219,6 +220,13 @@ "usernameIs": "Ваше ім'я користувача в CalDAV є: {0}", "apiTokenHint": "Також можна використовувати токен API з дозволом CalDAV. Створіть його в {link}." }, + "feeds": { + "title": "Стрічка Atom", + "howTo": "Ви можете підписатися на сповіщення Vikunja у будь-якому читачі стрічок, сумісному з Atom. Використайте таку URL-адресу:", + "usernameIs": "Ваше ім'я користувача для стрічки: {0}", + "apiTokenHint": "Автентифікуйтеся за допомогою API-токена з дозволом {scope}. Створіть його в {link}.", + "tokenTitle": "Стрічка Atom" + }, "avatar": { "title": "Зображення обліковки", "initials": "Ініціали", @@ -463,7 +471,6 @@ "month": "Місяць", "day": "День", "hour": "Година", - "range": "Проміжок днів", "chartLabel": "Діаграма Ганта", "taskBarsForRow": "Смуги завдань для рядка {rowId}", "taskBarLabel": "Завдання: {task}. З {startDate} по {endDate}. {dateType}. Натисніть, щоб редагувати, перетягніть, щоб перемістити.", @@ -492,7 +499,6 @@ "kanban": { "title": "Дошка", "limit": "Межа: {limit}", - "noLimit": "Немає", "doneBucket": "Колонка «Виконано»", "doneBucketHint": "Всі завдання, переміщені в цю колонку, будуть автоматично позначені як виконані.", "doneBucketHintExtended": "Всі завдання, що потрапляють у колонку «Виконано», автоматично відмічаються як виконані. Завдання, позначені як виконані в інших місцях, також перемістяться сюди.", @@ -758,7 +764,7 @@ "doit": "Зробити!", "saving": "Зберігаю…", "saved": "Збережено!", - "default": "Припис", + "default": "Стандартний", "close": "Закрити", "download": "Витягти", "showMenu": "Показати список", @@ -776,7 +782,10 @@ "closeDialog": "Закрити діалог", "closeQuickActions": "Закрити швидкі дії", "skipToContent": "Перейти до основного вмісту", - "sortBy": "Сортувати за" + "sortBy": "Сортувати за", + "dateRange": "Діапазон дат", + "notSet": "Не встановлено", + "user": "Користувач" }, "input": { "projectColor": "Колір проєкту", @@ -804,8 +813,8 @@ "underline": "Підчеркнутий", "code": "Код", "codeTooltip": "Уривок коду.", - "quote": "Переповідь", - "quoteTooltip": "Дослівний уривок.", + "quote": "Цитата", + "quoteTooltip": "Блок цитати.", "bulletList": "Список з мітками", "bulletListTooltip": "Створює список з мітками.", "orderedList": "Список з числами", @@ -813,7 +822,7 @@ "link": "Посилання", "image": "Зображення", "imageTooltip": "Додає зображення.", - "horizontalRule": "Черть", + "horizontalRule": "Горизонтальна лінія", "horizontalRuleTooltip": "Ділить розділ.", "text": "Введення", "textTooltip": "Просто для введення чогось.", @@ -855,6 +864,7 @@ "date": "День", "ranges": { "today": "Сьогодні", + "tomorrow": "Завтра", "thisWeek": "Цей тиждень", "restOfThisWeek": "Залишок цього тижня", "nextWeek": "Наступний тиждень", @@ -917,7 +927,7 @@ "today": "Сьогодні о 00:00", "beginningOfThisWeek": "Початок цього тижня о 00:00", "endOfThisWeek": "Кінець цього тижня", - "in30Days": "Встяж 30 днів", + "in30Days": "Упродовж 30 днів", "datePlusMonth": "{0} додати один місяць о 00:00 цього дня" } } @@ -961,7 +971,7 @@ "bucketChangedSuccess": "Стрічку завдання успішно змінено.", "belongsToProject": "Завдання міститься у проєкті '{project}'", "back": "Назад до проєкту", - "due": "Виконати до {at}", + "due": "Виконати {at}", "closeTaskDetail": "Закрити деталі завдання", "title": "Деталі завдання", "markAsDone": "Позначити '{task}' як виконане", @@ -981,10 +991,11 @@ "dueDate": "Встановити термін", "startDate": "Почати", "endDate": "Встановити дату завершення", - "reminders": "Нагадувати", + "reminders": "Нагадування", "repeatAfter": "Повторювати", "percentDone": "Встановити прогрес", "attachments": "Вкласти", + "timeTracking": "Відстежити час", "relatedTasks": "Пов'язати", "moveProject": "Перемістити", "duplicate": "Дублювати", @@ -1001,7 +1012,7 @@ "createdBy": "Створювач", "description": "Опис", "done": "Закінчено", - "dueDate": "Строк", + "dueDate": "Кінцева дата", "endDate": "День закінчення", "labels": "Позначки", "percentDone": "Прогрес", @@ -1051,14 +1062,17 @@ "edited": "змінено: {date}", "creating": "Створюю коментар…", "placeholder": "Введіть коментар, натисніть '/' для додаткових опцій…", - "comment": "Залишити", - "delete": "Вилучається приписка", + "comment": "Зберегти коментар", + "delete": "Видалити коментар", "deleteText1": "Справді впровадити?", "deleteSuccess": "Коментар успішно видалено.", "addedSuccess": "Коментар успішно додано.", "permalink": "Одержати посилання", "sortNewestFirst": "Спочатку новіші", - "sortOldestFirst": "Спочатку старіші" + "sortOldestFirst": "Спочатку старіші", + "reply": "Відповідь", + "jumpToOriginal": "Перейти до початкового коментаря", + "deletedComment": "видалений коментар" }, "mention": { "noUsersFound": "Користувачів не знайдено" @@ -1079,7 +1093,7 @@ "unassignSuccess": "З вживача знято дорученість." }, "label": { - "placeholder": "Введіть щось, щоб знайти позначку…", + "placeholder": "Введіть назву мітки…", "createPlaceholder": "Додати це як нову позначку", "addSuccess": "Позначку додано.", "removeSuccess": "Позначку вилучено.", @@ -1137,13 +1151,13 @@ "repeat": { "everyDay": "Щодня", "everyWeek": "Щотижня", - "every30d": "Щомісяця", + "every30d": "Кожні 30 днів", "mode": "Спосіб", "monthly": "Щомісяця", - "fromCurrentDate": "Щодень закінчення", - "each": "Що", + "fromCurrentDate": "З дня закінчення", + "each": "Кожен", "specifyAmount": "Вкажіть величину…", - "hours": "Годин", + "hours": "Години", "days": "День", "weeks": "Тижнів", "invalidAmount": "Будь ласка, введіть більше нуля." @@ -1207,8 +1221,8 @@ "success": "Вживача успішно видалено зі спільноти." }, "leave": { - "title": "Покинути спільноту", - "text1": "Справді покинути?", + "title": "Залишити спільноту", + "text1": "Ви впевнені, що хочете залишити цю спільноту?", "text2": "Ви втратите доступ до всіх проєктів, до яких має доступ ця команда. Якщо передумаєте, вам знадобиться адміністратор команди, щоб додати вас знову.", "success": "Ви покинули спільноту." } @@ -1323,7 +1337,8 @@ "none": "У вас немає сповіщень. Гарного дня!", "explainer": "Сповіщення з'являтимуться тут, коли відбуватимуться якісь дії з проєктами або завданнями, на які ви підписані.", "markAllRead": "Позначити всі сповіщення як прочитані", - "markAllReadSuccess": "Всі сповіщення позначено прочитаними." + "markAllReadSuccess": "Всі сповіщення позначено прочитаними.", + "subscribeFeed": "Підписатися на сповіщення через стрічку Atom" }, "quickActions": { "notLoggedIn": "Будь ласка, спочатку авторизуйтесь в головному вікні Vikunja.", @@ -1446,18 +1461,44 @@ }, "about": { "title": "Про програму", - "version": "Відміна: {version}", + "version": "Версія: {version}", "frontendVersion": "Версія інтерфейсу: {version}", "apiVersion": "API версія: {version}" }, + "timeTracking": { + "title": "Відстеження часу", + "stop": "Зупинити таймер", + "logTime": "Записати час", + "editEntry": "Редагувати запис", + "form": { + "task": "Завдання", + "taskSearch": "Знайти завдання…", + "commentPlaceholder": "Над чим ви працювали?", + "save": "Зберегти запис", + "startTimer": "Запустити таймер", + "update": "Оновити запис", + "smartFill": "Заповнити з останнього запису" + }, + "list": { + "emptyTask": "Для цього завдання ще немає записів обліку часу.", + "emptyFiltered": "Немає записів обліку часу для вибраних фільтрів.", + "total": "Загалом", + "time": "Час", + "duration": "Тривалість" + }, + "browse": { + "selectRange": "Обрати діапазон", + "userSearch": "Знайти користувача…" + } + }, "time": { "units": { - "seconds": "секунда|секунди|секунд", - "minutes": "хвилина|хвилини|хвилин", - "hours": "година|години|годин", - "days": "день|дні|днів", - "weeks": "тиждень|тижні|тижнів", - "years": "рік|роки|років" + "seconds": "секунда|секунд(и)", + "minutes": "хвилина|хвилин(и)", + "hours": "година|годин(и)", + "days": "день|дні", + "weeks": "тиждень|тижні", + "years": "рік|роки" } }, "admin": { diff --git a/frontend/src/i18n/lang/vi-VN.json b/frontend/src/i18n/lang/vi-VN.json index ad559767d..9f845a319 100644 --- a/frontend/src/i18n/lang/vi-VN.json +++ b/frontend/src/i18n/lang/vi-VN.json @@ -319,8 +319,7 @@ "default": "Mặc định", "month": "Tháng", "day": "Ngày", - "hour": "Giờ", - "range": "Khoảng thời gian" + "hour": "Giờ" }, "table": { "title": "Bảng", @@ -329,7 +328,6 @@ "kanban": { "title": "Kanban", "limit": "Giới hạn: {limit}", - "noLimit": "Không giới hạn", "doneBucket": "Cột hoàn thành", "doneBucketHint": "Tất cả các tác vụ được chuyển đến cột này sẽ được tự động đánh dấu là hoàn thành.", "doneBucketHintExtended": "Tất cả các tác vụ được đưa đến cột hoàn thành sẽ được tự động đánh dấu là hoàn thành. Tất cả các tác vụ hoàn thành từ các phần khác cũng sẽ được chuyển tới đây.", diff --git a/frontend/src/i18n/lang/zh-CN.json b/frontend/src/i18n/lang/zh-CN.json index 329eb42ff..8e7ce7888 100644 --- a/frontend/src/i18n/lang/zh-CN.json +++ b/frontend/src/i18n/lang/zh-CN.json @@ -338,7 +338,6 @@ "month": "月", "day": "日", "hour": "时", - "range": "日期范围", "chartLabel": "项目甘特图", "scheduledDates": "预定日期", "estimatedDates": "估计日期" @@ -350,7 +349,6 @@ "kanban": { "title": "看板", "limit": "限制: {limit}", - "noLimit": "未设置", "doneBucket": "已完成的桶数", "doneBucketHint": "移入此存储桶的所有任务将自动标记为已完成。", "doneBucketHintExtended": "所有移入已完成存储桶的任务都将自动标记为已完成。 从其他位置标记为已完成的任务也将被移动。", diff --git a/frontend/src/i18n/lang/zh-TW.json b/frontend/src/i18n/lang/zh-TW.json index b55b8b4fb..594fb17c5 100644 --- a/frontend/src/i18n/lang/zh-TW.json +++ b/frontend/src/i18n/lang/zh-TW.json @@ -362,7 +362,6 @@ "month": "月", "day": "日", "hour": "時", - "range": "日期範圍", "chartLabel": "專案甘特圖", "taskBarsForRow": "第 {rowId} 列的任務列", "taskBarLabel": "任務:{task}。從 {startDate} 到 {endDate}。{dateType}。點擊編輯,拖曳移動。", @@ -386,7 +385,6 @@ "kanban": { "title": "看板", "limit": "限制: {limit}", - "noLimit": "未設定", "doneBucket": "已完成類別", "doneBucketHint": "移入此類別的任務將自動標記為已完成。", "doneBucketHintExtended": "所有移入已完成類別的任務將自動標記為已完成。 其他位置標記為已完成的任務也將被移動。", diff --git a/frontend/src/i18n/useDayjsLanguageSync.ts b/frontend/src/i18n/useDayjsLanguageSync.ts index a2c862fbb..792a284cd 100644 --- a/frontend/src/i18n/useDayjsLanguageSync.ts +++ b/frontend/src/i18n/useDayjsLanguageSync.ts @@ -22,6 +22,7 @@ export const DAYJS_LOCALE_MAPPING = { 'ja-jp': 'ja', 'hu-hu': 'hu', 'ar-sa': 'ar-sa', + 'fa-ir': 'fa', 'sl-si': 'sl', 'pt-br': 'pt', 'hr-hr': 'hr', @@ -33,6 +34,7 @@ export const DAYJS_LOCALE_MAPPING = { 'fi-fi': 'fi', 'he-il': 'he', 'sv-se': 'sv', + 'el-gr': 'el', } as Record export const DAYJS_LANGUAGE_IMPORTS = { @@ -54,6 +56,7 @@ export const DAYJS_LANGUAGE_IMPORTS = { 'ja-jp': () => import('dayjs/locale/ja'), 'hu-hu': () => import('dayjs/locale/hu'), 'ar-sa': () => import('dayjs/locale/ar-sa'), + 'fa-ir': () => import('dayjs/locale/fa'), 'sl-si': () => import('dayjs/locale/sl'), 'pt-br': () => import('dayjs/locale/pt-br'), 'hr-hr': () => import('dayjs/locale/hr'), @@ -65,6 +68,7 @@ export const DAYJS_LANGUAGE_IMPORTS = { 'fi-fi': () => import('dayjs/locale/fi'), 'he-il': () => import('dayjs/locale/he'), 'sv-se': () => import('dayjs/locale/sv'), + 'el-gr': () => import('dayjs/locale/el'), } as Record Promise> export async function loadDayJsLocale(language: SupportedLocale) { diff --git a/frontend/src/main.ts b/frontend/src/main.ts index 1a81de2a2..63a895a4d 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -19,7 +19,6 @@ declare global { API_URL: string; SENTRY_ENABLED?: boolean; SENTRY_DSN?: string; - ALLOW_ICON_CHANGES: boolean; CUSTOM_LOGO_URL?: string; CUSTOM_LOGO_URL_DARK?: string; } @@ -38,7 +37,7 @@ if (window.API_URL.endsWith('/')) { // directives import focus from '@/directives/focus' -import {vTooltip} from 'floating-vue' +import tooltip from '@/directives/tooltip' import 'floating-vue/dist/style.css' import shortcut from '@/directives/shortcut' import testid from '@/directives/testid' @@ -66,7 +65,7 @@ setLanguage(browserLanguage).then(() => { app.use(Notifications) app.directive('focus', focus) - app.directive('tooltip', vTooltip) + app.directive('tooltip', tooltip) app.directive('shortcut', shortcut) app.directive('cy', testid) diff --git a/frontend/src/modelTypes/IProjectDuplicate.ts b/frontend/src/modelTypes/IProjectDuplicate.ts index a24efa58c..cf9ba9167 100644 --- a/frontend/src/modelTypes/IProjectDuplicate.ts +++ b/frontend/src/modelTypes/IProjectDuplicate.ts @@ -5,4 +5,5 @@ export interface IProjectDuplicate extends IAbstract { projectId: number duplicatedProject: IProject | null parentProjectId: IProject['id'] + duplicateShares: boolean } diff --git a/frontend/src/modelTypes/ITask.ts b/frontend/src/modelTypes/ITask.ts index f8504c30a..45b6de45a 100644 --- a/frontend/src/modelTypes/ITask.ts +++ b/frontend/src/modelTypes/ITask.ts @@ -51,6 +51,7 @@ export interface ITask extends IAbstract { reactions: IReactionPerEntity comments: ITaskComment[] commentCount?: number + timeEntriesCount?: number createdBy: IUser created: Date diff --git a/frontend/src/modelTypes/ITimeEntry.ts b/frontend/src/modelTypes/ITimeEntry.ts new file mode 100644 index 000000000..249ce2129 --- /dev/null +++ b/frontend/src/modelTypes/ITimeEntry.ts @@ -0,0 +1,16 @@ +import type {IAbstract} from './IAbstract' + +export interface ITimeEntry extends IAbstract { + id: number + userId: number + // Exactly one of taskId / projectId is set (0 means unset). + taskId: number + projectId: number + startTime: Date + // null while the live timer is running. + endTime: Date | null + comment: string + + created: Date + updated: Date +} diff --git a/frontend/src/modelTypes/IUserSettings.ts b/frontend/src/modelTypes/IUserSettings.ts index 3cc6faa0d..ffd75e5ac 100644 --- a/frontend/src/modelTypes/IUserSettings.ts +++ b/frontend/src/modelTypes/IUserSettings.ts @@ -28,6 +28,7 @@ export interface IFrontendSettings { commentSortOrder: 'asc' | 'desc' desktopQuickEntryShortcut: string quickAddDefaultReminders: ITaskReminder[] + timeTrackingDefaultStart?: string } export interface IExtraSettingsLink { diff --git a/frontend/src/models/projectDuplicateModel.ts b/frontend/src/models/projectDuplicateModel.ts index ac137714d..53af125ba 100644 --- a/frontend/src/models/projectDuplicateModel.ts +++ b/frontend/src/models/projectDuplicateModel.ts @@ -8,6 +8,7 @@ export default class ProjectDuplicateModel extends AbstractModel) { super() diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index 6d48058af..02e5ad580 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -6,7 +6,9 @@ import {getProjectViewId} from '@/helpers/projectView' import {parseDateOrString} from '@/helpers/time/parseDateOrString' import {getNextWeekDate} from '@/helpers/time/getNextWeekDate' import {LINK_SHARE_HASH_PREFIX} from '@/constants/linkShareHash' +import {REDIRECT_HASH_PREFIX} from '@/constants/redirectHash' import {AUTH_ROUTE_NAMES} from '@/constants/authRouteNames' +import {PRO_FEATURE} from '@/constants/proFeatures' import {useAuthStore} from '@/stores/auth' import {useBaseStore} from '@/stores/base' @@ -29,7 +31,7 @@ const router = createRouter({ } // Scroll to anchor should still work - if (to.hash && !to.hash.startsWith(LINK_SHARE_HASH_PREFIX)) { + if (to.hash && !to.hash.startsWith(LINK_SHARE_HASH_PREFIX) && !to.hash.startsWith(REDIRECT_HASH_PREFIX)) { return {el: to.hash} } @@ -117,6 +119,11 @@ const router = createRouter({ name: 'user.settings.data-export', component: () => import('@/views/user/settings/DataExport.vue'), }, + { + path: '/user/settings/feeds', + name: 'user.settings.feeds', + component: () => import('@/views/user/settings/AtomFeed.vue'), + }, { path: '/user/settings/deletion', name: 'user.settings.deletion', @@ -428,6 +435,15 @@ const router = createRouter({ name: 'about', component: () => import('@/views/About.vue'), }, + { + path: '/time-tracking', + name: 'time-tracking', + component: () => import('@/views/time-tracking/TimeTracking.vue'), + meta: { + requiresTimeTracking: true, + title: 'timeTracking.title', + }, + }, { path: '/admin', component: () => import('@/views/admin/AdminShell.vue'), @@ -457,10 +473,22 @@ const router = createRouter({ }) export async function getAuthForRoute(to: RouteLocation, authStore) { + // vue-router already decoded to.hash once, so slicing off the prefix yields the original + // fullPath (e.g. /oauth/authorize?...) losslessly — no extra decodeURIComponent needed. + const redirectDest = to.name === 'user.login' && to.hash.startsWith(REDIRECT_HASH_PREFIX) + ? to.hash.slice(REDIRECT_HASH_PREFIX.length) + : '' + if (authStore.authUser || authStore.authLinkShare) { + // An already-signed-in browser that opens a copied /login#redirect= URL + // must run the OAuth flow with its existing session instead of short-circuiting to home. + // The destination has no redirect hash, so the second guard pass just early-returns (#2654). + if (redirectDest) { + return redirectDest + } return } - + // Check if password reset token is in query params const resetToken = to.query.userPasswordReset as string | undefined @@ -484,15 +512,35 @@ export async function getAuthForRoute(to: RouteLocation, authStore) { } } + // Keep the destination in the address bar (not just per-browser localStorage) so a native + // client's /oauth/authorize URL stays copyable into another browser. Hash, not query, so the + // embedded OAuth params never reach access logs (#2654). Pass fullPath raw: vue-router encodes + // the hash itself, so an extra encodeURIComponent here would be double-encoded in the URL. + if (to.name === 'oauth.authorize') { + return { + name: 'user.login', + hash: REDIRECT_HASH_PREFIX + to.fullPath, + } + } + + // Fold the hash destination into localStorage: it's the only bridge that survives the + // external OIDC round-trip out of the SPA, so redirectIfSaved() works after any auth method. + // vue-router already decoded to.hash once, so it equals the fullPath we wrote above as-is. + if (to.hash.startsWith(REDIRECT_HASH_PREFIX)) { + const destination = to.hash.slice(REDIRECT_HASH_PREFIX.length) + const resolved = router.resolve(destination) + saveLastVisited(resolved.name as string, resolved.params, resolved.query) + } + // Check if the route the user wants to go to is a route which needs authentication. We use this to // redirect the user after successful login. const isValidUserAppRoute = !AUTH_ROUTE_NAMES.has(to.name as string) && localStorage.getItem('emailConfirmToken') === null - + if (isValidUserAppRoute) { saveLastVisited(to.name as string, to.params, to.query) } - + if (isValidUserAppRoute) { return {name: 'user.login'} } @@ -514,7 +562,7 @@ router.beforeEach(async (to, from) => { const baseStore = useBaseStore() await baseStore.appReady const configStore = useConfigStore() - const featureOn = configStore.isProFeatureEnabled('admin_panel') + const featureOn = configStore.isProFeatureEnabled(PRO_FEATURE.ADMIN_PANEL) // isAdmin comes from /user, not the JWT; force-fetch in case checkAuth() was debounced. if (authStore.info?.isAdmin === undefined) { await authStore.refreshUserInfo() @@ -525,6 +573,15 @@ router.beforeEach(async (to, from) => { } } + if (to.meta?.requiresTimeTracking) { + const baseStore = useBaseStore() + await baseStore.appReady + const configStore = useConfigStore() + if (!configStore.isProFeatureEnabled(PRO_FEATURE.TIME_TRACKING)) { + return {name: 'not-found'} + } + } + if(from.hash && from.hash.startsWith(LINK_SHARE_HASH_PREFIX)) { to.hash = from.hash } @@ -541,12 +598,25 @@ router.beforeEach(async (to, from) => { const newRoute = await getAuthForRoute(to, authStore) if(newRoute) { + // A string target (the decoded redirect destination for an authed browser) already + // carries its own query/path and no redirect hash, so navigate to it verbatim — don't + // re-attach to.hash or it would re-enter the redirect loop. + if (typeof newRoute === 'string') { + return newRoute + } return { - ...newRoute, hash: to.hash, + ...newRoute, } } - + + // to.fullPath keeps the redirect hash url-encoded while to.hash is decoded, so the endsWith + // check below never matches and would re-append the hash forever. The hash is already on the + // URL here, so skip the re-attach (#2654). + if (to.hash.startsWith(REDIRECT_HASH_PREFIX)) { + return + } + if(!to.fullPath.endsWith(to.hash)) { return to.fullPath + to.hash } diff --git a/frontend/src/services/timeEntry.test.ts b/frontend/src/services/timeEntry.test.ts new file mode 100644 index 000000000..faa68bcc8 --- /dev/null +++ b/frontend/src/services/timeEntry.test.ts @@ -0,0 +1,33 @@ +import {describe, it, expect} from 'vitest' + +import {parseTimeEntry} from './timeEntry' + +describe('parseTimeEntry', () => { + it('maps snake_case keys and coerces dates', () => { + const e = parseTimeEntry({ + id: 1, + user_id: 2, + task_id: 3, + project_id: 0, + start_time: '2020-01-01T09:00:00Z', + end_time: '2020-01-01T10:00:00Z', + comment: 'work', + }) + expect(e.userId).toBe(2) + expect(e.taskId).toBe(3) + expect(e.comment).toBe('work') + expect(e.startTime).toBeInstanceOf(Date) + expect(e.endTime).toBeInstanceOf(Date) + }) + + it('treats a null end time as a running timer', () => { + const e = parseTimeEntry({ + id: 1, + user_id: 1, + task_id: 1, + start_time: '2020-01-01T09:00:00Z', + end_time: null, + }) + expect(e.endTime).toBeNull() + }) +}) diff --git a/frontend/src/services/timeEntry.ts b/frontend/src/services/timeEntry.ts new file mode 100644 index 000000000..b76a3d11a --- /dev/null +++ b/frontend/src/services/timeEntry.ts @@ -0,0 +1,91 @@ +import {AuthenticatedHTTPFactory, getApiBaseUrl} from '@/helpers/fetcher' +import {objectToCamelCase, objectToSnakeCase} from '@/helpers/case' + +import type {ITimeEntry} from '@/modelTypes/ITimeEntry' + +// Time tracking is the first frontend feature on /api/v2, while the shared +// AuthenticatedHTTPFactory pins baseURL to /api/v1. We hand axios absolute v2 +// URLs to bypass that. Bespoke and intentionally a bit dirty — to be folded +// into the proper service layer once the frontend moves fully onto v2. +function v2Url(path: string): string { + const v2Base = getApiBaseUrl().replace(/\/api\/v1\/$/, '/api/v2/') + return new URL(v2Base + path, window.location.origin).toString() +} + +export function parseTimeEntry(raw: Record): ITimeEntry { + const e = objectToCamelCase(raw) + const end = e.endTime as string | null | undefined + return { + id: e.id, + userId: e.userId, + taskId: e.taskId ?? 0, + projectId: e.projectId ?? 0, + startTime: new Date(e.startTime), + // null end_time = a running timer. + endTime: end ? new Date(end) : null, + comment: e.comment ?? '', + created: new Date(e.created), + updated: new Date(e.updated), + maxPermission: e.maxPermission ?? null, + } +} + +export interface TimeEntryListParams { + filter?: string + filterTimezone?: string + q?: string + page?: number + perPage?: number +} + +export interface TimeEntryListResult { + items: ITimeEntry[] + total: number + page: number + perPage: number + totalPages: number +} + +export function useTimeEntryService() { + const http = AuthenticatedHTTPFactory() + + async function getAll(params: TimeEntryListParams = {}): Promise { + const {data} = await http.get(v2Url('time-entries'), { + params: { + filter: params.filter, + filter_timezone: params.filterTimezone, + q: params.q, + page: params.page, + per_page: params.perPage, + }, + }) + return { + items: (data.items ?? []).map(parseTimeEntry), + total: data.total, + page: data.page, + perPage: data.per_page, + totalPages: data.total_pages, + } + } + + async function create(entry: Partial): Promise { + const {data} = await http.post(v2Url('time-entries'), objectToSnakeCase(entry)) + return parseTimeEntry(data) + } + + async function update(entry: Partial & {id: number}): Promise { + const {data} = await http.put(v2Url(`time-entries/${entry.id}`), objectToSnakeCase(entry)) + return parseTimeEntry(data) + } + + async function remove(id: number): Promise { + await http.delete(v2Url(`time-entries/${id}`)) + } + + async function stopTimer(): Promise { + const {data} = await http.post(v2Url('time-entries/timer/stop')) + return parseTimeEntry(data) + } + + return {getAll, create, update, remove, stopTimer} +} diff --git a/frontend/src/stores/auth.ts b/frontend/src/stores/auth.ts index e2576760f..e9a7c8ee0 100644 --- a/frontend/src/stores/auth.ts +++ b/frontend/src/stores/auth.ts @@ -533,9 +533,11 @@ export const useAuthStore = defineStore('auth', () => { // Revoke the server session so the refresh token can't be reused. // Best-effort: if the network call fails, still clean up locally. + let oidcLogoutUrl = '' try { const HTTP = AuthenticatedHTTPFactory() - await HTTP.post('user/logout') + const {data} = await HTTP.post('user/logout') + oidcLogoutUrl = data?.oidc_logout_url ?? '' } catch (_e) { // Ignore — session will expire naturally } @@ -547,7 +549,12 @@ export const useAuthStore = defineStore('auth', () => { await router.push({name: 'user.login'}) await checkAuth() - // if configured, redirect to OIDC Provider on logout + // Redirect to the OIDC provider to end its session too. Prefer the + // server-built RP-Initiated Logout URL, falling back to the static one. + if (oidcLogoutUrl) { + window.location.href = oidcLogoutUrl + return + } const fullProvider: IProvider|undefined = configStore.auth.openidConnect.providers?.find((p: IProvider) => p.key === loggedInVia) if (fullProvider) { redirectToProviderOnLogout(fullProvider) diff --git a/frontend/src/stores/config.ts b/frontend/src/stores/config.ts index 6ca087ff4..89b57bcf1 100644 --- a/frontend/src/stores/config.ts +++ b/frontend/src/stores/config.ts @@ -7,6 +7,7 @@ import {objectToCamelCase} from '@/helpers/case' import type {IProvider} from '@/types/IProvider' import type {MIGRATORS} from '@/views/migrate/migrators' +import type {ProFeature} from '@/constants/proFeatures' import {InvalidApiUrlProvidedError} from '@/helpers/checkAndSetApiUrl' export interface ConfigState { @@ -44,7 +45,9 @@ export interface ConfigState { }, }, publicTeamsEnabled: boolean, + allowIconChanges: boolean, enabledProFeatures: string[], + concurrentWrites: boolean, } export const useConfigStore = defineStore('config', () => { @@ -84,7 +87,9 @@ export const useConfigStore = defineStore('config', () => { }, }, publicTeamsEnabled: false, + allowIconChanges: true, enabledProFeatures: [], + concurrentWrites: false, }) const migratorsEnabled = computed(() => state.availableMigrators?.length > 0) @@ -102,7 +107,7 @@ export const useConfigStore = defineStore('config', () => { Object.assign(state, config) } - function isProFeatureEnabled(name: string): boolean { + function isProFeatureEnabled(name: ProFeature): boolean { return state.enabledProFeatures?.includes(name) ?? false } diff --git a/frontend/src/stores/projects.ts b/frontend/src/stores/projects.ts index 37110100e..18a3dfad2 100644 --- a/frontend/src/stores/projects.ts +++ b/frontend/src/stores/projects.ts @@ -380,10 +380,11 @@ export function useProject(projectId: MaybeRefOrGetter) { success({message: t('project.edit.success')}) } - async function duplicateProject(parentProjectId: IProject['id']) { + async function duplicateProject(parentProjectId: IProject['id'], duplicateShares: boolean = false) { const projectDuplicate = new ProjectDuplicateModel({ projectId: Number(toValue(projectId)), parentProjectId, + duplicateShares, }) const duplicate = await projectDuplicateService.create(projectDuplicate) diff --git a/frontend/src/stores/tasks.test.ts b/frontend/src/stores/tasks.test.ts index 4d7a60af6..644d6e6bb 100644 --- a/frontend/src/stores/tasks.test.ts +++ b/frontend/src/stores/tasks.test.ts @@ -1,5 +1,5 @@ import {describe, expect, it} from 'vitest' -import {buildDefaultRemindersForQuickAdd} from './tasks' +import {buildDefaultRemindersForQuickAdd, runWrites} from './tasks' import {REMINDER_PERIOD_RELATIVE_TO_TYPES} from '@/types/IReminderPeriodRelativeTo' import type {ITaskReminder} from '@/modelTypes/ITaskReminder' @@ -42,3 +42,39 @@ describe('buildDefaultRemindersForQuickAdd', () => { expect(result[0].relativeTo).toBe(REMINDER_PERIOD_RELATIVE_TO_TYPES.DUEDATE) }) }) + +describe('runWrites', () => { + function deferredWrite() { + const inFlight: string[] = [] + let maxConcurrent = 0 + const completed: string[] = [] + const write = async (item: string) => { + inFlight.push(item) + maxConcurrent = Math.max(maxConcurrent, inFlight.length) + await Promise.resolve() + inFlight.splice(inFlight.indexOf(item), 1) + completed.push(item) + } + return {write, completed, getMaxConcurrent: () => maxConcurrent} + } + + it('runs all writes in parallel when concurrent', async () => { + const {write, completed, getMaxConcurrent} = deferredWrite() + await runWrites(['a', 'b', 'c'], write, true) + expect(completed).toHaveLength(3) + expect(getMaxConcurrent()).toBeGreaterThan(1) + }) + + it('runs writes one at a time when not concurrent', async () => { + const {write, completed, getMaxConcurrent} = deferredWrite() + await runWrites(['a', 'b', 'c'], write, false) + expect(completed).toEqual(['a', 'b', 'c']) + expect(getMaxConcurrent()).toBe(1) + }) + + it('does nothing for an empty list', async () => { + const {write, completed} = deferredWrite() + await runWrites([], write, false) + expect(completed).toHaveLength(0) + }) +}) diff --git a/frontend/src/stores/tasks.ts b/frontend/src/stores/tasks.ts index 3eb99d1ef..ac27ff0d2 100644 --- a/frontend/src/stores/tasks.ts +++ b/frontend/src/stores/tasks.ts @@ -27,6 +27,7 @@ import type {IProject} from '@/modelTypes/IProject' import {REMINDER_PERIOD_RELATIVE_TO_TYPES} from '@/types/IReminderPeriodRelativeTo' import {setModuleLoading} from '@/stores/helper' +import {useConfigStore} from '@/stores/config' import {useLabelStore} from '@/stores/labels' import {useProjectStore} from '@/stores/projects' import {useKanbanStore} from '@/stores/kanban' @@ -59,6 +60,22 @@ export function buildDefaultRemindersForQuickAdd( })) } +// runWrites applies a write to each item. SQLite deadlocks on concurrent writes +// (read-then-write upgrade conflict), so callers pass concurrent=false to serialize. +export async function runWrites( + items: readonly T[], + write: (item: T) => Promise, + concurrent: boolean, +): Promise { + if (concurrent) { + await Promise.all(items.map(item => write(item))) + return + } + for (const item of items) { + await write(item) + } +} + // IDEA: maybe use a small fuzzy search here to prevent errors function findPropertyByValue(object, key, value, fuzzy = false) { return Object.values(object).find(l => { @@ -131,6 +148,7 @@ export const useTaskStore = defineStore('task', () => { const labelStore = useLabelStore() const projectStore = useProjectStore() const authStore = useAuthStore() + const configStore = useConfigStore() const tasks = ref<{ [id: ITask['id']]: ITask }>({}) // TODO: or is this ITask[] const isLoading = ref(false) @@ -395,10 +413,7 @@ export const useTaskStore = defineStore('task', () => { } const labels = await ensureLabelsExist(parsedLabels) - const labelAddsToWaitFor = labels.map(async l => addLabelToTask(task, l)) - - // This waits until all labels are created and added to the task - await Promise.all(labelAddsToWaitFor) + await runWrites(labels, l => addLabelToTask(task, l), configStore.concurrentWrites) return task } diff --git a/frontend/src/stores/timeTracking.test.ts b/frontend/src/stores/timeTracking.test.ts new file mode 100644 index 000000000..62d3c7d09 --- /dev/null +++ b/frontend/src/stores/timeTracking.test.ts @@ -0,0 +1,139 @@ +import {describe, it, expect, beforeEach, vi} from 'vitest' +import {setActivePinia, createPinia} from 'pinia' + +import {useTimeTrackingStore} from './timeTracking' +import type {ITimeEntry} from '@/modelTypes/ITimeEntry' + +const {getAllMock, removeMock, authInfo} = vi.hoisted(() => ({ + getAllMock: vi.fn(), + removeMock: vi.fn(), + authInfo: {value: {id: 7} as {id: number} | null}, +})) + +vi.mock('@/services/timeEntry', async importOriginal => { + const actual = await importOriginal() + return { + ...actual, + useTimeEntryService: () => ({ + getAll: getAllMock, + remove: removeMock, + }), + } +}) + +vi.mock('@/stores/auth', () => ({ + useAuthStore: () => ({ + info: authInfo.value, + }), +})) + +function entry(id: number, endTime: Date | null): ITimeEntry { + return { + id, + userId: 1, + taskId: 1, + projectId: 0, + startTime: new Date(), + endTime, + comment: '', + created: new Date(), + updated: new Date(), + maxPermission: null, + } +} + +describe('timeTracking store', () => { + beforeEach(() => { + setActivePinia(createPinia()) + getAllMock.mockReset() + removeMock.mockReset() + authInfo.value = {id: 7} + }) + + it('a running entry becomes the active timer', () => { + const store = useTimeTrackingStore() + store.applyTimerEvent(entry(4, null)) + expect(store.activeTimer?.id).toBe(4) + expect(store.hasActiveTimer).toBe(true) + }) + + it('a stopped entry clears the matching active timer', () => { + const store = useTimeTrackingStore() + store.applyTimerEvent(entry(4, null)) + store.applyTimerEvent(entry(4, new Date())) + expect(store.activeTimer).toBeNull() + }) + + it('a stop for a different timer leaves the active one alone', () => { + const store = useTimeTrackingStore() + store.applyTimerEvent(entry(4, null)) + store.applyTimerEvent(entry(5, new Date())) + expect(store.activeTimer?.id).toBe(4) + }) + + it('patches a stopped entry in the loaded list', () => { + const store = useTimeTrackingStore() + store.browsedEntries = [entry(4, null), entry(5, null)] + const stopped = entry(4, new Date('2026-01-01T10:00:00Z')) + store.applyTimerEvent(stopped) + expect(store.browsedEntries.find((e: ITimeEntry) => e.id === 4)?.endTime).toEqual(stopped.endTime) + expect(store.browsedEntries).toHaveLength(2) + }) + + it('does not insert an unknown entry into the loaded list', () => { + const store = useTimeTrackingStore() + store.browsedEntries = [entry(4, null)] + store.applyTimerEvent(entry(9, new Date())) + expect(store.browsedEntries).toHaveLength(1) + expect(store.browsedEntries.find((e: ITimeEntry) => e.id === 9)).toBeUndefined() + }) + + it('hydrates the active timer scoped to the current user', async () => { + getAllMock.mockResolvedValue({items: [entry(4, null)]}) + + const store = useTimeTrackingStore() + await store.hydrateActiveTimer() + + expect(getAllMock).toHaveBeenCalledWith({ + filter: 'user_id = 7 && end_time = null', + perPage: 1, + }) + expect(store.activeTimer?.id).toBe(4) + }) + + it('clears the active timer when deleting the running entry', async () => { + removeMock.mockResolvedValue(undefined) + + const store = useTimeTrackingStore() + store.browsedEntries = [entry(4, null), entry(5, new Date())] + store.applyTimerEvent(entry(4, null)) + + await store.removeEntry(4) + + expect(removeMock).toHaveBeenCalledWith(4) + expect(store.browsedEntries.map((e: ITimeEntry) => e.id)).toEqual([5]) + expect(store.activeTimer).toBeNull() + }) + + it('applyTimerDeletion drops the entry and clears the matching active timer', () => { + const store = useTimeTrackingStore() + store.browsedEntries = [entry(4, null), entry(5, new Date())] + store.applyTimerEvent(entry(4, null)) + + store.applyTimerDeletion(4) + + expect(store.browsedEntries.map((e: ITimeEntry) => e.id)).toEqual([5]) + expect(store.activeTimer).toBeNull() + }) + + it('applyTimerDeletion of another entry leaves the active timer alone', () => { + const store = useTimeTrackingStore() + store.browsedEntries = [entry(4, null), entry(5, new Date())] + store.applyTimerEvent(entry(4, null)) + + store.applyTimerDeletion(5) + + expect(store.browsedEntries.map((e: ITimeEntry) => e.id)).toEqual([4]) + expect(store.activeTimer?.id).toBe(4) + }) +}) diff --git a/frontend/src/stores/timeTracking.ts b/frontend/src/stores/timeTracking.ts new file mode 100644 index 000000000..b384c0538 --- /dev/null +++ b/frontend/src/stores/timeTracking.ts @@ -0,0 +1,142 @@ +import {ref, computed} from 'vue' +import {acceptHMRUpdate, defineStore} from 'pinia' + +import {useWebSocket} from '@/composables/useWebSocket' +import {useTimeEntryService, parseTimeEntry} from '@/services/timeEntry' +import {useAuthStore} from '@/stores/auth' + +import type {ITimeEntry} from '@/modelTypes/ITimeEntry' + +export const useTimeTrackingStore = defineStore('timeTracking', () => { + const activeTimer = ref(null) + const browsedEntries = ref([]) + + const hasActiveTimer = computed(() => activeTimer.value !== null) + + async function browseEntries(filter: string) { + const {items} = await useTimeEntryService().getAll({ + filter, + filterTimezone: Intl.DateTimeFormat().resolvedOptions().timeZone, + perPage: 250, + }) + browsedEntries.value = items + } + + // Drop a deleted entry from the list and clear the active timer if it was it. + // Shared by the local delete and the cross-tab WebSocket "timer.deleted". + function applyTimerDeletion(id: number) { + browsedEntries.value = browsedEntries.value.filter(entry => entry.id !== id) + if (activeTimer.value?.id === id) { + activeTimer.value = null + } + } + + async function removeEntry(id: number) { + await useTimeEntryService().remove(id) + applyTimerDeletion(id) + } + + // Replace an already-loaded entry in place so a stop (or any update) is + // reflected without a refetch. Never inserts — an event for an entry that + // isn't in the current filter shouldn't appear in the list. + function patchInList(entry: ITimeEntry) { + const index = browsedEntries.value.findIndex(existing => existing.id === entry.id) + if (index !== -1) { + browsedEntries.value.splice(index, 1, entry) + } + } + + // Reconcile the active timer from a timer event (WebSocket) or a local + // action: an entry with an end time is a stop — clear it if it's the one we + // track; otherwise it is the running timer. + function applyTimerEvent(entry: ITimeEntry) { + patchInList(entry) + if (entry.endTime !== null) { + if (activeTimer.value?.id === entry.id) { + activeTimer.value = null + } + return + } + activeTimer.value = entry + } + + // Source of truth on (re)connect: the caller's own running timer, if any. + async function hydrateActiveTimer() { + const userId = useAuthStore().info?.id + if (userId === undefined) { + activeTimer.value = null + return + } + + const {items} = await useTimeEntryService().getAll({ + filter: `user_id = ${userId} && end_time = null`, + perPage: 1, + }) + activeTimer.value = items[0] ?? null + } + + // Create any entry (manual, with an end time, or a running timer when end is + // omitted) and reconcile the active timer from the result. + async function createEntry(payload: Partial) { + const entry = await useTimeEntryService().create(payload) + applyTimerEvent(entry) + return entry + } + + async function updateEntry(payload: Partial & {id: number}) { + const entry = await useTimeEntryService().update(payload) + applyTimerEvent(entry) + return entry + } + + async function stopTimer() { + const entry = await useTimeEntryService().stopTimer() + applyTimerEvent(entry) + return entry + } + + let unsubscribers: Array<() => void> = [] + function subscribeToTimerEvents() { + const {subscribe} = useWebSocket() + // Ignore messages without a payload (e.g. subscribe acknowledgements). + const onEvent = (msg: {data?: unknown}) => { + if (msg.data == null) { + return + } + applyTimerEvent(parseTimeEntry(msg.data as Record)) + } + const onDelete = (msg: {data?: unknown}) => { + if (msg.data == null) { + return + } + applyTimerDeletion(parseTimeEntry(msg.data as Record).id) + } + unsubscribers.push(subscribe('timer.created', onEvent)) + unsubscribers.push(subscribe('timer.updated', onEvent)) + unsubscribers.push(subscribe('timer.deleted', onDelete)) + } + function unsubscribeFromTimerEvents() { + unsubscribers.forEach(unsubscribe => unsubscribe()) + unsubscribers = [] + } + + return { + activeTimer, + browsedEntries, + hasActiveTimer, + applyTimerEvent, + applyTimerDeletion, + hydrateActiveTimer, + browseEntries, + createEntry, + updateEntry, + stopTimer, + removeEntry, + subscribeToTimerEvents, + unsubscribeFromTimerEvents, + } +}) + +if (import.meta.hot) { + import.meta.hot.accept(acceptHMRUpdate(useTimeTrackingStore, import.meta.hot)) +} diff --git a/frontend/src/styles/theme/helpers.scss b/frontend/src/styles/theme/helpers.scss index b2238a00b..5e8685da2 100644 --- a/frontend/src/styles/theme/helpers.scss +++ b/frontend/src/styles/theme/helpers.scss @@ -4,6 +4,10 @@ } } -.is-pulled-right { +.is-pulled-end { float: right !important; } + +[dir="rtl"] .is-pulled-end { + float: left !important; +} diff --git a/frontend/src/views/labels/ListLabels.vue b/frontend/src/views/labels/ListLabels.vue index 30ae58645..b86dedd1d 100644 --- a/frontend/src/views/labels/ListLabels.vue +++ b/frontend/src/views/labels/ListLabels.vue @@ -5,7 +5,7 @@ > {{ $t('label.create.header') }} diff --git a/frontend/src/views/project/settings/ProjectSettingsDuplicate.vue b/frontend/src/views/project/settings/ProjectSettingsDuplicate.vue index 764a44803..ebfc61003 100644 --- a/frontend/src/views/project/settings/ProjectSettingsDuplicate.vue +++ b/frontend/src/views/project/settings/ProjectSettingsDuplicate.vue @@ -8,6 +8,12 @@ >

{{ $t('project.duplicate.text') }}

+ + {{ $t('project.duplicate.shares') }} + @@ -18,6 +24,7 @@ import {useI18n} from 'vue-i18n' import CreateEdit from '@/components/misc/CreateEdit.vue' import ProjectSearch from '@/components/tasks/partials/ProjectSearch.vue' +import FancyCheckbox from '@/components/input/FancyCheckbox.vue' import {success} from '@/message' import {useTitle} from '@/composables/useTitle' @@ -33,6 +40,7 @@ const projectStore = useProjectStore() const {project, isLoading, duplicateProject} = useProject(route.params.projectId) const parentProject = ref(null) +const duplicateShares = ref(true) const isDuplicating = ref(false) const loadingModel = computed({ @@ -53,7 +61,7 @@ async function duplicate() { isDuplicating.value = true try { - await duplicateProject(parentProject.value?.id ?? 0) + await duplicateProject(parentProject.value?.id ?? 0, duplicateShares.value) success({message: t('project.duplicate.success')}) } finally { isDuplicating.value = false diff --git a/frontend/src/views/tasks/TaskDetailView.vue b/frontend/src/views/tasks/TaskDetailView.vue index 157789c31..5396bc797 100644 --- a/frontend/src/views/tasks/TaskDetailView.vue +++ b/frontend/src/views/tasks/TaskDetailView.vue @@ -344,11 +344,11 @@
- @@ -366,6 +366,15 @@ /> + +
+ +
+
{{ $t('task.detail.dateAndTime') }} + + {{ $t('task.detail.actions.timeTracking') }} + + configStore.isProFeatureEnabled(PRO_FEATURE.TIME_TRACKING)) const kanbanStore = useKanbanStore() const authStore = useAuthStore() const baseStore = useBaseStore() @@ -923,7 +947,12 @@ watch( } try { - const loaded = await taskService.get({id}, {expand: ['reactions', 'comments', 'is_unread', 'buckets']}) + const expand = ['reactions', 'comments', 'is_unread', 'buckets'] + if (timeTrackingEnabled.value) { + // Only request the (server-computed) count when the feature is on. + expand.push('time_entries_count') + } + const loaded = await taskService.get({id}, {expand}) Object.assign(task.value, loaded) taskColor.value = task.value.hexColor setActiveFields() @@ -967,6 +996,7 @@ type FieldType = | 'reminders' | 'repeatAfter' | 'startDate' + | 'timeTracking' const activeFields: { [type in FieldType]: boolean } = reactive({ assignees: false, @@ -982,6 +1012,7 @@ const activeFields: { [type in FieldType]: boolean } = reactive({ reminders: false, repeatAfter: false, startDate: false, + timeTracking: false, }) function setActiveFields() { @@ -992,6 +1023,7 @@ function setActiveFields() { // Set all active fields based on values in the model activeFields.assignees = task.value.assignees.length > 0 activeFields.attachments = task.value.attachments.length > 0 + activeFields.timeTracking = (task.value.timeEntriesCount ?? 0) > 0 activeFields.dueDate = task.value.dueDate !== null activeFields.endDate = task.value.endDate !== null activeFields.labels = task.value.labels.length > 0 diff --git a/frontend/src/views/teams/EditTeam.vue b/frontend/src/views/teams/EditTeam.vue index f1079f9df..b1174f463 100644 --- a/frontend/src/views/teams/EditTeam.vue +++ b/frontend/src/views/teams/EditTeam.vue @@ -113,7 +113,7 @@
@@ -243,6 +243,7 @@ import FormField from '@/components/input/FormField.vue' import Multiselect from '@/components/input/Multiselect.vue' import User from '@/components/misc/User.vue' +import {getDisplayName} from '@/models/user' import TeamService from '@/services/team' import TeamMemberService from '@/services/teamMember' import UserService from '@/services/user' @@ -273,6 +274,12 @@ const userIsAdmin = computed(() => { }) const userInfo = computed(() => authStore.info) +const sortedMembers = computed(() => { + return [...(team.value?.members ?? [])].sort((a, b) => + getDisplayName(a).localeCompare(getDisplayName(b), undefined, {sensitivity: 'base'}), + ) +}) + const teamService = ref(new TeamService()) const teamMemberService = ref(new TeamMemberService()) const userService = ref(new UserService()) diff --git a/frontend/src/views/teams/ListTeams.vue b/frontend/src/views/teams/ListTeams.vue index 5b5d8077d..00c31703a 100644 --- a/frontend/src/views/teams/ListTeams.vue +++ b/frontend/src/views/teams/ListTeams.vue @@ -5,7 +5,7 @@ > {{ $t('team.create.title') }} diff --git a/frontend/src/views/time-tracking/TimeTracking.vue b/frontend/src/views/time-tracking/TimeTracking.vue new file mode 100644 index 000000000..b785fe9c3 --- /dev/null +++ b/frontend/src/views/time-tracking/TimeTracking.vue @@ -0,0 +1,396 @@ + + + + + diff --git a/frontend/src/views/user/OpenIdAuth.vue b/frontend/src/views/user/OpenIdAuth.vue index b163ba4e5..3d41c4f0f 100644 --- a/frontend/src/views/user/OpenIdAuth.vue +++ b/frontend/src/views/user/OpenIdAuth.vue @@ -90,25 +90,11 @@ function findProvider(providerKey: string): IProvider | undefined { } async function authenticateWithCode() { - // This component gets mounted twice: The first time when the actual auth request hits the frontend, - // the second time after that auth request succeeded and the outer component "content-no-auth" isn't used - // but instead the "content-auth" component is used. Because this component is just a route and thus - // gets mounted as part of a which both the content-auth and content-no-auth components have, - // this re-mounts the component, even if the user is already authenticated. - // To make sure we only try to authenticate the user once, we set this "authenticating" lock in localStorage - // which ensures only one auth request is done at a time. We don't simply check if the user is already - // authenticated to not prevent the whole authentication if some user is already logged in. - if (localStorage.getItem('authenticating')) { - return - } - localStorage.setItem('authenticating', 'true') - errorMessage.value = '' const providerKey = route.params.provider as string if (typeof route.query.error !== 'undefined') { - localStorage.removeItem('authenticating') sessionStorage.removeItem(pendingTotpKey(providerKey)) errorMessage.value = typeof route.query.message !== 'undefined' ? route.query.message as string @@ -118,7 +104,6 @@ async function authenticateWithCode() { const state = localStorage.getItem('state') if (typeof route.query.state === 'undefined' || route.query.state !== state) { - localStorage.removeItem('authenticating') sessionStorage.removeItem(pendingTotpKey(providerKey)) errorMessage.value = t('user.auth.openIdStateError') return @@ -145,8 +130,6 @@ async function authenticateWithCode() { return } errorMessage.value = getErrorText(e) - } finally { - localStorage.removeItem('authenticating') } } diff --git a/frontend/src/views/user/Settings.vue b/frontend/src/views/user/Settings.vue index aa1b92350..4879cf617 100644 --- a/frontend/src/views/user/Settings.vue +++ b/frontend/src/views/user/Settings.vue @@ -67,6 +67,10 @@ const navigationItems = computed(() => { routeName: 'user.settings.caldav', condition: caldavEnabled.value, }, + { + title: t('user.settings.feeds.title'), + routeName: 'user.settings.feeds', + }, { title: t('user.settings.apiTokens.title'), routeName: 'user.settings.apiTokens', diff --git a/frontend/src/views/user/settings/AtomFeed.vue b/frontend/src/views/user/settings/AtomFeed.vue new file mode 100644 index 000000000..0a1e29f29 --- /dev/null +++ b/frontend/src/views/user/settings/AtomFeed.vue @@ -0,0 +1,75 @@ + + + diff --git a/frontend/src/views/user/settings/General.vue b/frontend/src/views/user/settings/General.vue index 1d80c46ac..85506797b 100644 --- a/frontend/src/views/user/settings/General.vue +++ b/frontend/src/views/user/settings/General.vue @@ -151,6 +151,16 @@ :options="timeFormatOptions" /> + + + @@ -306,12 +316,14 @@ import {useTitle} from '@/composables/useTitle' import {useProjectStore} from '@/stores/projects' import {useAuthStore} from '@/stores/auth' +import {useConfigStore} from '@/stores/config' import type {IUserSettings} from '@/modelTypes/IUserSettings' import {isSavedFilter} from '@/services/savedFilter' import {DEFAULT_PROJECT_VIEW_SETTINGS} from '@/modelTypes/IProjectView' import {PRIORITIES} from '@/constants/priorities' import {DATE_DISPLAY} from '@/constants/dateDisplay' import {TIME_FORMAT} from '@/constants/timeFormat' +import {PRO_FEATURE} from '@/constants/proFeatures' import {RELATION_KINDS} from '@/types/IRelationKind' import {isDesktopApp} from '@/helpers/desktopAuth' import ShortcutRecorder from '@/components/misc/ShortcutRecorder.vue' @@ -396,6 +408,8 @@ const languageOptions = computed(() => ) const authStore = useAuthStore() +const configStore = useConfigStore() +const timeTrackingEnabled = computed(() => configStore.isProFeatureEnabled(PRO_FEATURE.TIME_TRACKING)) const settings = ref({ ...authStore.settings, @@ -416,6 +430,7 @@ const settings = ref({ defaultTaskRelationType: authStore.settings.frontendSettings.defaultTaskRelationType ?? 'related', // Clone to escape the store's readonly array type. quickAddDefaultReminders: [...(authStore.settings.frontendSettings.quickAddDefaultReminders ?? [])], + timeTrackingDefaultStart: authStore.settings.frontendSettings.timeTrackingDefaultStart ?? '09:00', }, }) diff --git a/frontend/tests/e2e/editor/link-prompt-kanban-popup.spec.ts b/frontend/tests/e2e/editor/link-prompt-kanban-popup.spec.ts new file mode 100644 index 000000000..26e15ae0f --- /dev/null +++ b/frontend/tests/e2e/editor/link-prompt-kanban-popup.spec.ts @@ -0,0 +1,124 @@ +import {test, expect} from '../../support/fixtures' +import {ProjectFactory} from '../../factories/project' +import {ProjectViewFactory} from '../../factories/project_view' +import {BucketFactory} from '../../factories/bucket' +import {TaskFactory} from '../../factories/task' +import {TaskBucketFactory} from '../../factories/task_buckets' + +// Regression test for #2940: in the Kanban task popup the description editor is +// rendered inside a native opened via showModal() (browser top-layer). +// The link prompt used to be appended to document.body, so it was painted behind +// the dialog and unfocusable through its focus trap, making "set link" a no-op. +test.describe('Editor link prompt inside the Kanban task popup', () => { + test('creates a link in the description when opened as the Kanban popup', async ({authenticatedPage: page}) => { + const projects = await ProjectFactory.create(1) + const views = await ProjectViewFactory.create(1, { + id: 1, + project_id: projects[0].id, + view_kind: 3, + bucket_configuration_mode: 1, + }) + const buckets = await BucketFactory.create(1, { + project_view_id: views[0].id, + }) + const tasks = await TaskFactory.create(1, { + project_id: projects[0].id, + description: 'link me', + index: 1, + }) + await TaskBucketFactory.create(1, { + task_id: tasks[0].id, + bucket_id: buckets[0].id, + project_view_id: views[0].id, + }) + + await page.goto(`/projects/${projects[0].id}/${views[0].id}`) + + const card = page.locator('.kanban .bucket .tasks .task').filter({hasText: tasks[0].title}) + await expect(card).toBeVisible() + await card.click() + + // The task popup must be a native in the top layer. + const dialog = page.locator('dialog[open]') + await expect(dialog).toBeVisible() + await expect(dialog.locator('.task-view')).toBeVisible() + + const editButton = dialog.locator('.details.content.description .tiptap button.done-edit').filter({hasText: 'Edit'}) + await expect(editButton).toBeVisible({timeout: 10000}) + await editButton.click() + + const description = dialog.locator('.details.content.description') + const editor = description.locator('[contenteditable="true"]').first() + await expect(editor).toBeVisible({timeout: 10000}) + await editor.click() + await page.keyboard.press('ControlOrMeta+a') + + await description.locator('.editor-toolbar__button').filter({hasText: 'Link'}).click() + + const urlInput = dialog.locator('input.input[placeholder="URL"]') + await expect(urlInput).toBeVisible() + await urlInput.fill('https://vikunja.io') + await urlInput.press('Enter') + + const link = editor.locator('a[href="https://vikunja.io"]') + await expect(link).toBeVisible() + await expect(link).toHaveText('link me') + }) + + // The link prompt is a sub-modal of the task : pressing Escape while + // it is open must cancel only the prompt and leave the task dialog open, + // instead of falling through to the native 's Escape-to-close. + test('Escape cancels the link prompt without closing the task dialog', async ({authenticatedPage: page}) => { + const projects = await ProjectFactory.create(1) + const views = await ProjectViewFactory.create(1, { + id: 1, + project_id: projects[0].id, + view_kind: 3, + bucket_configuration_mode: 1, + }) + const buckets = await BucketFactory.create(1, { + project_view_id: views[0].id, + }) + const tasks = await TaskFactory.create(1, { + project_id: projects[0].id, + description: 'link me', + index: 1, + }) + await TaskBucketFactory.create(1, { + task_id: tasks[0].id, + bucket_id: buckets[0].id, + project_view_id: views[0].id, + }) + + await page.goto(`/projects/${projects[0].id}/${views[0].id}`) + + const card = page.locator('.kanban .bucket .tasks .task').filter({hasText: tasks[0].title}) + await expect(card).toBeVisible() + await card.click() + + const dialog = page.locator('dialog[open]') + await expect(dialog).toBeVisible() + await expect(dialog.locator('.task-view')).toBeVisible() + + const editButton = dialog.locator('.details.content.description .tiptap button.done-edit').filter({hasText: 'Edit'}) + await expect(editButton).toBeVisible({timeout: 10000}) + await editButton.click() + + const description = dialog.locator('.details.content.description') + const editor = description.locator('[contenteditable="true"]').first() + await expect(editor).toBeVisible({timeout: 10000}) + await editor.click() + await page.keyboard.press('ControlOrMeta+a') + + await description.locator('.editor-toolbar__button').filter({hasText: 'Link'}).click() + + const urlInput = dialog.locator('input.input[placeholder="URL"]') + await expect(urlInput).toBeVisible() + await urlInput.press('Escape') + + // The prompt is gone, but the task dialog stays open. + await expect(urlInput).toBeHidden() + await expect(dialog).toBeVisible() + await expect(dialog.locator('.task-view')).toBeVisible() + }) +}) diff --git a/frontend/tests/e2e/project/sort-persistence.spec.ts b/frontend/tests/e2e/project/sort-persistence.spec.ts new file mode 100644 index 000000000..4aaeed3dc --- /dev/null +++ b/frontend/tests/e2e/project/sort-persistence.spec.ts @@ -0,0 +1,55 @@ +import {type Page} from '@playwright/test' +import {test, expect} from '../../support/fixtures' +import {TaskFactory} from '../../factories/task' +import {createProjects} from './prepareProjects' + +async function selectSortInList(page: Page, optionLabel: string) { + await page.locator('.filter-container').getByRole('button', {name: 'Sort', exact: true}).click() + await page.getByLabel('Sort by').selectOption({label: optionLabel}) + await page.getByRole('button', {name: 'Apply sort'}).click() +} + +async function navigateViaSidebar(page: Page, projectTitle: string) { + await page.locator('.menu-list .list-menu-link', { + has: page.locator('.project-menu-title', {hasText: new RegExp(`^${projectTitle}$`)}), + }).first().click() +} + +test.describe('Sort persistence across sidebar navigation (#2753)', () => { + test('List view: sort persists after navigating to another project and back', async ({authenticatedPage: page}) => { + const projects = await createProjects(2) + const [projectA, projectB] = projects + await TaskFactory.create(3, { + id: '{increment}', + project_id: projectA.id, + title: 'Task {increment}', + }) + + const listViewA = projectA.views[0].id + await page.goto(`/projects/${projectA.id}/${listViewA}`) + await expect(page).not.toHaveURL(/sort=/) + + await selectSortInList(page, 'Due date (Earliest first)') + await expect(page).toHaveURL(/sort=due_date:asc/) + + await navigateViaSidebar(page, projectB.title) + await expect(page).toHaveURL(new RegExp(`/projects/${projectB.id}/`)) + + await navigateViaSidebar(page, projectA.title) + await expect(page).toHaveURL(new RegExp(`/projects/${projectA.id}/`)) + await expect(page).toHaveURL(/sort=due_date:asc/) + }) + + test('List view: explicit URL sort wins over stored sort', async ({authenticatedPage: page}) => { + const projects = await createProjects(1) + const listView = projects[0].views[0].id + + // Seed the store with one sort by visiting with it set. + await page.goto(`/projects/${projects[0].id}/${listView}?sort=due_date:asc`) + await expect(page).toHaveURL(/sort=due_date:asc/) + + // Visit a URL that explicitly sets a different sort — that should win. + await page.goto(`/projects/${projects[0].id}/${listView}?sort=priority:desc`) + await expect(page).toHaveURL(/sort=priority:desc/) + }) +}) diff --git a/frontend/tests/e2e/task/comment-reply.spec.ts b/frontend/tests/e2e/task/comment-reply.spec.ts new file mode 100644 index 000000000..231b2c3f7 --- /dev/null +++ b/frontend/tests/e2e/task/comment-reply.spec.ts @@ -0,0 +1,56 @@ +import {test, expect} from '../../support/fixtures' +import {ProjectFactory} from '../../factories/project' +import {TaskFactory} from '../../factories/task' +import {TaskCommentFactory} from '../../factories/task_comment' +import {createDefaultViews} from '../project/prepareProjects' + +test.describe('Reply to a task comment', () => { + test.beforeEach(async ({authenticatedPage: page, currentUser}) => { + await ProjectFactory.create(1, {owner_id: currentUser.id}) + await createDefaultViews(1) + await TaskFactory.create(1, {id: 1, created_by_id: currentUser.id}) + }) + + test('Reply action prefills the editor with a quoted blockquote and the saved reply renders an author header + chevron that jumps to the original', async ({authenticatedPage: page, currentUser}) => { + await TaskCommentFactory.create(1, { + id: 1, + task_id: 1, + author_id: currentUser.id, + comment: 'Original message that we will quote.', + }) + + await page.goto('/tasks/1') + await page.waitForLoadState('networkidle') + + const originalComment = page.locator('#comment-1') + await expect(originalComment).toBeVisible({timeout: 10000}) + + // The Reply action lives in the per-comment bottom-actions list. + await originalComment.getByRole('button', {name: 'Reply', exact: true}).click() + + // The new-comment editor (the contenteditable one) should now contain + // the prefilled blockquote pointing back at comment 1. + const newCommentEditor = page.locator('.task-view .comments .media.comment .tiptap__editor .tiptap.ProseMirror[contenteditable="true"]').last() + await expect(newCommentEditor).toBeVisible() + await expect(newCommentEditor.locator('blockquote[data-comment-id="1"]')).toBeVisible() + await expect(newCommentEditor.locator('blockquote[data-comment-id="1"]')).toContainText('Original message that we will quote.') + + // Append a reply body after the auto-inserted paragraph. + await newCommentEditor.click() + await page.keyboard.press('End') + await page.keyboard.type('Thanks for that!') + + await page.getByRole('button', {name: 'Comment', exact: true}).click() + + // The newly-rendered reply should carry the quote header + chevron. + const reply = page.locator('.task-view .comments .media.comment').nth(1) + await expect(reply).toBeVisible() + const quote = reply.locator('blockquote.comment-quote[data-comment-id="1"]') + await expect(quote).toBeVisible() + await expect(quote.locator('.comment-quote__jump')).toBeVisible() + + // Clicking the chevron scrolls to and briefly highlights the original. + await quote.locator('.comment-quote__jump').click() + await expect(originalComment).toHaveClass(/comment-highlight/, {timeout: 2000}) + }) +}) diff --git a/frontend/tests/e2e/time-tracking/time-tracking.spec.ts b/frontend/tests/e2e/time-tracking/time-tracking.spec.ts new file mode 100644 index 000000000..43831b87f --- /dev/null +++ b/frontend/tests/e2e/time-tracking/time-tracking.spec.ts @@ -0,0 +1,323 @@ +import {test, expect} from '../../support/fixtures' +import type {Page, Locator} from '@playwright/test' + +import {ProjectFactory} from '../../factories/project' +import {TaskFactory} from '../../factories/task' +import {TimeEntryFactory} from '../../factories/time_entry' +import {LicenseFactory} from '../../factories/license' +import {UserFactory} from '../../factories/user' +import {UserProjectFactory} from '../../factories/users_project' + +// Pick a project in the form's project picker. Waits for the project store to +// hydrate (the sidebar shows it) before searching so the result is there. +async function selectProject(page: Page, form: Locator, title: string) { + await expect(page.locator('.menu-container').getByText(title)).toBeVisible() + const input = form.locator('.multiselect').first().locator('input') + await input.click() + // pressSequentially (not fill) so the multiselect's @keyup search fires. + await input.pressSequentially(title, {delay: 30}) + await form.locator('.search-result-button').filter({hasText: title}).first().click() +} + +// Pick a task in the form's task picker (the second multiselect, after project). +async function selectTask(form: Locator, title: string) { + const input = form.locator('.multiselect').nth(1).locator('input') + await input.click() + await input.pressSequentially(title, {delay: 30}) + await form.locator('.search-result-button').filter({hasText: title}).first().click() +} + +// Open the time-tracking section on a task detail page. +async function openTaskTimeTracking(page: Page, taskId: number): Promise { + await page.goto(`/tasks/${taskId}`) + await page.locator('[data-cy="taskTrackTimeAction"]').click() + const section = page.locator('.task-time-tracking') + await expect(section).toBeVisible() + return section +} + +test.describe('Time tracking', () => { + test.describe('with the feature licensed', () => { + test.beforeEach(async () => { + await LicenseFactory.enable(['time_tracking']) + }) + + test.afterEach(async () => { + await LicenseFactory.disable() + }) + + test('shows the page and the sidebar entry', async ({authenticatedPage: page}) => { + await page.goto('/') + await expect(page.locator('.menu-container').getByRole('link', {name: 'Time tracking'})).toBeVisible() + + await page.goto('/time-tracking') + await expect(page.locator('[data-cy="addTimeEntry"]')).toBeVisible() + }) + + test('logs a manual time entry', async ({authenticatedPage: page}) => { + await ProjectFactory.create(1, {title: 'E2E tracked project'}, false) + + await page.goto('/time-tracking') + await page.locator('[data-cy="addTimeEntry"]').click() + + const form = page.locator('[data-cy="timeEntryForm"]') + await expect(form).toBeVisible() + + await selectProject(page, form, 'E2E tracked project') + // Smart-fill populates both from and to, so the entry is complete. + await form.locator('[data-cy="smartFill"]').click() + await form.locator('[data-cy="saveTimeEntry"]').click() + + await expect(page.locator('[data-cy="timeEntry"]').filter({hasText: 'E2E tracked project'})).toBeVisible() + }) + + test('saving with an empty To logs a completed entry, not a running timer', async ({authenticatedPage: page}) => { + await ProjectFactory.create(1, {title: 'E2E save project'}, false) + + await page.goto('/time-tracking') + await page.locator('[data-cy="addTimeEntry"]').click() + const form = page.locator('[data-cy="timeEntryForm"]') + await selectProject(page, form, 'E2E save project') + // No smart-fill: leave "To" empty, then Save. + await form.locator('[data-cy="saveTimeEntry"]').click() + + // The entry is completed (no open-ended "…") and no timer started. + const entries = page.locator('[data-cy="timeEntry"]') + await expect(entries).toHaveCount(1) + await expect(entries.first()).not.toContainText('…') + await expect(page.locator('[data-cy="timerBadge"]')).not.toBeVisible() + }) + + test('switching from a task to a project logs against the project', async ({authenticatedPage: page}) => { + await ProjectFactory.create(1, {id: 1, title: 'XOR project'}, false) + await TaskFactory.create(1, {id: 1, title: 'XOR task', project_id: 1}, false) + + await page.goto('/time-tracking') + await page.locator('[data-cy="addTimeEntry"]').click() + const form = page.locator('[data-cy="timeEntryForm"]') + + // Pick a task, then change your mind to a project — the task must be cleared. + await selectTask(form, 'XOR task') + await selectProject(page, form, 'XOR project') + + await form.locator('[data-cy="smartFill"]').click() + await form.locator('[data-cy="saveTimeEntry"]').click() + + const entry = page.locator('[data-cy="timeEntry"]').first() + await expect(entry).toContainText('XOR project') + await expect(entry).not.toContainText('XOR task') + }) + + test('starts a timer and stopping it updates the same entry in the list', async ({authenticatedPage: page}) => { + await ProjectFactory.create(1, {title: 'E2E timer project'}, false) + + await page.goto('/time-tracking') + await page.locator('[data-cy="addTimeEntry"]').click() + + const form = page.locator('[data-cy="timeEntryForm"]') + await selectProject(page, form, 'E2E timer project') + await form.locator('[data-cy="startTimer"]').click() + + const badge = page.locator('[data-cy="timerBadge"]') + await expect(badge).toBeVisible() + + // The running entry is in the list with an open-ended time range. + const entries = page.locator('[data-cy="timeEntry"]') + await expect(entries).toHaveCount(1) + await expect(entries.first()).toContainText('…') + + await badge.locator('[data-cy="stopTimer"]').click() + await expect(badge).not.toBeVisible() + + // The same entry is updated in place — end time set, no longer open-ended. + await expect(entries).toHaveCount(1) + await expect(entries.first()).not.toContainText('…') + }) + + test('does not show another user\'s readable running timer in the header', async ({ + authenticatedPage: page, + currentUser, + }) => { + const [timerOwner] = await UserFactory.create(1, {id: currentUser.id + 100}, false) + const [sharedProject] = await ProjectFactory.create(1, { + id: 1001, + title: 'Shared active timer project', + owner_id: timerOwner.id, + }, false) + await UserProjectFactory.create(1, { + project_id: sharedProject.id, + user_id: currentUser.id, + permission: 0, + }, false) + await TimeEntryFactory.create(1, { + project_id: sharedProject.id, + user_id: timerOwner.id, + end_time: null, + comment: 'other user running timer', + }, false) + + const activeTimerHydrated = page.waitForResponse(response => + response.request().method() === 'GET' && + response.url().includes('/api/v2/time-entries') && + response.url().includes('per_page=1'), + ) + await page.goto('/time-tracking') + await activeTimerHydrated + + await expect(page.locator('[data-cy="timeEntry"]').filter({hasText: 'other user running timer'})).toBeVisible() + await expect(page.locator('[data-cy="timerBadge"]')).not.toBeVisible() + }) + + test('hides edit/delete on entries owned by another user', async ({authenticatedPage: page, currentUser}) => { + const [other] = await UserFactory.create(1, {id: currentUser.id + 100}, false) + const [shared] = await ProjectFactory.create(1, {id: 2001, title: 'Shared log project', owner_id: other.id}, false) + await UserProjectFactory.create(1, {project_id: shared.id, user_id: currentUser.id, permission: 0}, false) + await TimeEntryFactory.create(1, {id: 10, project_id: shared.id, user_id: other.id, comment: 'theirs'}, false) + await TimeEntryFactory.create(1, {id: 11, project_id: shared.id, user_id: currentUser.id, comment: 'mine'}, false) + + await page.goto('/time-tracking') + const theirs = page.locator('[data-cy="timeEntry"]').filter({hasText: 'theirs'}) + const mine = page.locator('[data-cy="timeEntry"]').filter({hasText: 'mine'}) + await expect(theirs).toBeVisible() + await expect(mine).toBeVisible() + + // The current user keeps the controls on their own entry, but not the other's. + await expect(mine.locator('[data-cy="editTimeEntry"]')).toBeVisible() + await expect(theirs.locator('[data-cy="editTimeEntry"]')).toHaveCount(0) + await expect(theirs.locator('[data-cy="deleteTimeEntry"]')).toHaveCount(0) + }) + + test('task detail: logs an entry and toggles the form with the + button', async ({authenticatedPage: page}) => { + await ProjectFactory.create(1, {title: 'P'}, false) + await TaskFactory.create(1, {title: 'Tracked task', project_id: 1}, false) + + const section = await openTaskTimeTracking(page, 1) + const form = section.locator('[data-cy="timeEntryForm"]') + + // No entries yet → the form is shown implicitly. + await expect(form).toBeVisible() + + await form.locator('[data-cy="smartFill"]').click() + await form.locator('[data-cy="saveTimeEntry"]').click() + await expect(section.locator('[data-cy="timeEntry"]')).toHaveCount(1) + + // With an entry, the form collapses behind the + button. + await expect(form).not.toBeVisible() + const addButton = section.locator('[data-cy="addTaskTimeEntry"]') + await expect(addButton).toBeVisible() + await addButton.click() + await expect(form).toBeVisible() + }) + + test('task detail: stopping a timer updates the entry in the list', async ({authenticatedPage: page}) => { + await ProjectFactory.create(1, {title: 'P'}, false) + await TaskFactory.create(1, {title: 'Timed task', project_id: 1}, false) + + const section = await openTaskTimeTracking(page, 1) + await section.locator('[data-cy="timeEntryForm"] [data-cy="startTimer"]').click() + + const badge = page.locator('[data-cy="timerBadge"]') + await expect(badge).toBeVisible() + + const entries = section.locator('[data-cy="timeEntry"]') + await expect(entries).toHaveCount(1) + await expect(entries.first()).toContainText('…') + + await badge.locator('[data-cy="stopTimer"]').click() + await expect(badge).not.toBeVisible() + + await expect(entries).toHaveCount(1) + await expect(entries.first()).not.toContainText('…') + }) + + test('edits an entry from the list', async ({authenticatedPage: page}) => { + await ProjectFactory.create(1, {id: 1, title: 'Edit project'}, false) + await TimeEntryFactory.create(1, {id: 1, project_id: 1, comment: 'original comment'}, false) + + await page.goto('/time-tracking') + const entries = page.locator('[data-cy="timeEntry"]') + await expect(entries).toHaveCount(1) + await expect(entries.first()).toContainText('original comment') + + await entries.first().locator('[data-cy="editTimeEntry"]').click() + const form = page.locator('[data-cy="timeEntryForm"]') + const comment = form.locator('[data-cy="timeEntryComment"]') + await expect(comment).toHaveValue('original comment') + await comment.fill('edited comment') + await form.locator('[data-cy="updateTimeEntry"]').click() + + await expect(entries).toHaveCount(1) + await expect(entries.first()).toContainText('edited comment') + await expect(entries.first()).not.toContainText('original comment') + }) + + test('deletes an entry from the list', async ({authenticatedPage: page}) => { + await ProjectFactory.create(1, {id: 1, title: 'Delete project'}, false) + await TimeEntryFactory.create(1, {id: 1, project_id: 1, comment: 'to be deleted'}, false) + + await page.goto('/time-tracking') + const entries = page.locator('[data-cy="timeEntry"]') + await expect(entries).toHaveCount(1) + + await entries.first().locator('[data-cy="deleteTimeEntry"]').click() + await expect(entries).toHaveCount(0) + }) + + test('filters by project, reflected in the url and restored on reload', async ({authenticatedPage: page}) => { + await ProjectFactory.create(1, {id: 1, title: 'Alpha'}, false) + await ProjectFactory.create(1, {id: 2, title: 'Beta'}, false) + await TimeEntryFactory.create(1, {id: 1, project_id: 1, comment: 'alpha entry'}, false) + await TimeEntryFactory.create(1, {id: 2, project_id: 2, comment: 'beta entry'}, false) + + await page.goto('/time-tracking') + const entries = page.locator('[data-cy="timeEntry"]') + await expect(entries).toHaveCount(2) + + // Narrow to project Alpha in the filter modal. + await page.locator('[data-cy="openTimeTrackingFilters"]').click() + const dialog = page.locator('dialog[open]') + const projectInput = dialog.locator('.multiselect').first().locator('input') + await projectInput.click() + await projectInput.pressSequentially('Alpha', {delay: 30}) + await dialog.locator('.search-result-button').filter({hasText: 'Alpha'}).first().click() + + // The filter is written to the url. + await expect(page).toHaveURL(/[?&]project=1\b/) + + // ...and survives a reload (restored from the url): only Alpha's entry. + await page.reload() + await expect(entries).toHaveCount(1) + await expect(entries.first()).toContainText('Alpha') + await expect(page).toHaveURL(/[?&]project=1\b/) + }) + + test('clearing the date range does not crash the page', async ({authenticatedPage: page}) => { + await page.goto('/time-tracking') + // The default range surfaces as "Today" in the toolbar label. + await expect(page.locator('.time-tracking__range')).toHaveText('Today') + + await page.locator('[data-cy="openTimeTrackingFilters"]').click() + // Open the range popup (its trigger is the first button in the picker) and clear via Custom. + await page.locator('dialog[open] .datepicker-with-range-container').getByRole('button').first().click() + await page.getByRole('button', {name: 'Custom', exact: true}).click() + + // rangeLabel must not call getFullYear on a null date — the page stays alive. + await expect(page.locator('.time-tracking__range')).toHaveText('Select a range') + await expect(page.locator('[data-cy="addTimeEntry"]')).toBeVisible() + }) + }) + + test.describe('without the feature licensed', () => { + test.beforeEach(async () => { + await LicenseFactory.disable() + }) + + test('hides the sidebar entry and blocks the route', async ({authenticatedPage: page}) => { + await page.goto('/') + await expect(page.locator('.menu-container').getByRole('link', {name: 'Time tracking'})).toHaveCount(0) + + await page.goto('/time-tracking') + await expect(page.locator('[data-cy="addTimeEntry"]')).not.toBeVisible() + }) + }) +}) diff --git a/frontend/tests/e2e/user/oauth-authorize.spec.ts b/frontend/tests/e2e/user/oauth-authorize.spec.ts index 908e9ae3f..572802e46 100644 --- a/frontend/tests/e2e/user/oauth-authorize.spec.ts +++ b/frontend/tests/e2e/user/oauth-authorize.spec.ts @@ -32,10 +32,20 @@ test.describe('OAuth 2.0 Authorization Flow', () => { }) // Navigate to the OAuth authorize frontend route. - // The user is not logged in, so the router guard saves the route - // and redirects to /login. + // The user is not logged in, so the router guard redirects to /login while + // carrying the authorize destination in a copyable #redirect= hash (not a + // query param, to keep the OAuth params out of access logs). await page.goto(`/oauth/authorize?${authorizeParams}`) - await expect(page).toHaveURL(/\/login/) + await expect(page).toHaveURL(/\/login#redirect=/) + + // The decoded #redirect= destination must carry the full authorize URL, including the + // OAuth params — checking only for the path would pass even if the query were dropped. + const redirectHash = decodeURIComponent(new URL(page.url()).hash) + expect(redirectHash).toContain('/oauth/authorize') + expect(redirectHash).toContain('response_type=code') + expect(redirectHash).toContain('client_id=vikunja') + expect(redirectHash).toContain(`code_challenge=${codeChallenge}`) + expect(redirectHash).toContain(`state=${state}`) // Register the response listener BEFORE clicking Login, because after // login redirectIfSaved() navigates back to /oauth/authorize and the @@ -77,4 +87,70 @@ test.describe('OAuth 2.0 Authorization Flow', () => { expect(tokenBody.token_type).toBe('bearer') expect(tokenBody.expires_in).toBeGreaterThan(0) }) + + // The primary #2654 scenario: the native client opened a different default browser that is + // already signed in to Vikunja. Opening the copied /login#redirect= URL must + // run the OAuth flow with the existing session instead of short-circuiting to home. + test('Already-authenticated browser opening the copied login redirect runs the authorize flow', async ({authenticatedPage, apiContext, currentUser}) => { + const page = authenticatedPage + + const codeVerifier = randomBytes(32).toString('base64url') + const codeChallenge = createHash('sha256').update(codeVerifier).digest('base64url') + const state = randomBytes(16).toString('base64url') + + const authorizeParams = new URLSearchParams({ + response_type: 'code', + client_id: 'vikunja', + redirect_uri: 'vikunja-flutter://callback', + code_challenge: codeChallenge, + code_challenge_method: 'S256', + state, + }) + + // The component POSTs as soon as it mounts with the existing session, so register the + // listener before navigating. + const authorizeResponsePromise = page.waitForResponse( + response => response.url().includes('/api/v1/oauth/authorize') && response.request().method() === 'POST', + {timeout: 15000}, + ) + + // Open the copyable login URL exactly as it would be pasted from another browser + // (#redirect= is REDIRECT_HASH_PREFIX from @/constants/redirectHash, inlined here because + // the e2e runner has no @ alias). + const redirectDestination = `/oauth/authorize?${authorizeParams}` + await page.goto(`/login#redirect=${encodeURIComponent(redirectDestination)}`) + + // The authed guard must send us straight to /oauth/authorize, not home. + await expect(page).toHaveURL(/\/oauth\/authorize/) + const landed = new URL(page.url()) + expect(landed.pathname).toBe('/oauth/authorize') + expect(landed.searchParams.get('response_type')).toBe('code') + expect(landed.searchParams.get('client_id')).toBe('vikunja') + expect(landed.searchParams.get('code_challenge')).toBe(codeChallenge) + expect(landed.searchParams.get('state')).toBe(state) + + // The PKCE flow completes with the existing session — no second login. + const authorizeResponse = await authorizeResponsePromise + const authorizeBody = await authorizeResponse.json() + expect(authorizeBody.code).toBeTruthy() + expect(authorizeBody.redirect_uri).toBe('vikunja-flutter://callback') + expect(authorizeBody.state).toBe(state) + + const tokenResponse = await apiContext.post('oauth/token', { + data: { + grant_type: 'authorization_code', + code: authorizeBody.code, + client_id: 'vikunja', + redirect_uri: 'vikunja-flutter://callback', + code_verifier: codeVerifier, + }, + }) + + expect(tokenResponse.ok()).toBe(true) + const tokenBody = await tokenResponse.json() + expect(tokenBody.access_token).toBeTruthy() + expect(tokenBody.refresh_token).toBeTruthy() + expect(tokenBody.token_type).toBe('bearer') + expect(tokenBody.expires_in).toBeGreaterThan(0) + }) }) diff --git a/frontend/tests/factories/time_entry.ts b/frontend/tests/factories/time_entry.ts new file mode 100644 index 000000000..0125d2917 --- /dev/null +++ b/frontend/tests/factories/time_entry.ts @@ -0,0 +1,31 @@ +import {Factory} from '../support/factory' + +// Local "YYYY-MM-DD HH:MM:SS" (the format the DB fixtures use), not ISO-with-Z. +// start_time is filtered with datemath day windows that resolve to local time, +// and the comparison is lexical — a UTC-stamped value falls outside "today" +// near midnight. +function sqlDateTime(d: Date): string { + const pad = (n: number) => String(n).padStart(2, '0') + return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}` +} + +export class TimeEntryFactory extends Factory { + static table = 'time_entries' + + static factory() { + const now = sqlDateTime(new Date()) + + return { + id: '{increment}', + user_id: 1, + task_id: 0, + project_id: 0, + // Completed by default (end set), within today so the default filter shows it. + start_time: now, + end_time: now, + comment: '', + created: now, + updated: now, + } + } +} diff --git a/go.mod b/go.mod index f759db0c5..b57df5446 100644 --- a/go.mod +++ b/go.mod @@ -35,10 +35,11 @@ require ( github.com/coder/websocket v1.8.14 github.com/coreos/go-oidc/v3 v3.17.0 github.com/d4l3k/messagediff v1.2.1 + github.com/danielgtaylor/huma/v2 v2.37.3 github.com/disintegration/imaging v1.6.2 github.com/dustinkirkland/golang-petname v0.0.0-20240422154211-76c06c4bde6b github.com/fatih/color v1.18.0 - github.com/gabriel-vasile/mimetype v1.4.12 + github.com/gabriel-vasile/mimetype v1.4.13 github.com/ganigeorgiev/fexpr v0.5.0 github.com/getsentry/sentry-go v0.41.0 github.com/go-ldap/ldap/v3 v3.4.12 @@ -47,6 +48,7 @@ require ( github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a github.com/golang-jwt/jwt/v5 v5.3.0 github.com/google/uuid v1.6.0 + github.com/gorilla/feeds v1.2.0 github.com/hashicorp/go-version v1.8.0 github.com/hhsnopek/etag v0.0.0-20171206181245-aea95f647346 github.com/huandu/go-clone/generic v1.7.3 @@ -117,21 +119,24 @@ require ( github.com/boombuler/barcode v1.0.1 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/clipperhouse/displaywidth v0.6.2 // indirect - github.com/clipperhouse/stringish v0.1.1 // indirect - github.com/clipperhouse/uax29/v2 v2.3.0 // indirect + github.com/clipperhouse/displaywidth v0.11.0 // indirect + github.com/clipperhouse/uax29/v2 v2.7.0 // indirect github.com/containerd/errdefs v1.0.0 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect + github.com/danielgtaylor/mexpr v1.9.1 // indirect + github.com/danielgtaylor/shorthand/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/go-connections v0.6.0 // indirect github.com/docker/go-units v0.5.0 // indirect + github.com/evanphx/json-patch/v5 v5.9.11 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect - github.com/go-chi/chi/v5 v5.2.2 // indirect + github.com/go-chi/chi/v5 v5.2.5 // indirect github.com/go-jose/go-jose/v4 v4.1.4 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect @@ -141,7 +146,7 @@ require ( github.com/go-openapi/swag v0.23.0 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/goccy/go-json v0.10.5 // indirect - github.com/goccy/go-yaml v1.18.0 // indirect + github.com/goccy/go-yaml v1.19.2 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/gorilla/css v1.0.1 // indirect github.com/huandu/go-clone v1.7.3 // indirect @@ -152,7 +157,7 @@ require ( github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-runewidth v0.0.19 // indirect + github.com/mattn/go-runewidth v0.0.20 // indirect github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/moby/api v1.53.0 // indirect @@ -185,6 +190,7 @@ require ( github.com/syndtr/goleveldb v1.0.0 // indirect github.com/tj/assert v0.0.3 // indirect github.com/urfave/cli/v2 v2.3.0 // indirect + github.com/x448/float16 v0.8.4 // indirect github.com/yosssi/gohtml v0.0.0-20201013000340-ee4748c638f4 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 // indirect @@ -197,7 +203,7 @@ require ( golang.org/x/mod v0.33.0 // indirect golang.org/x/time v0.14.0 // indirect golang.org/x/tools v0.42.0 // indirect - google.golang.org/protobuf v1.36.8 // indirect + google.golang.org/protobuf v1.36.11 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect sigs.k8s.io/yaml v1.3.0 // indirect diff --git a/go.sum b/go.sum index fe3bdbab8..4804ee5ee 100644 --- a/go.sum +++ b/go.sum @@ -96,12 +96,10 @@ github.com/chengxilo/virtualterm v1.0.4/go.mod h1:DyxxBZz/x1iqJjFxTFcr6/x+jSpqN0 github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY= github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic= github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/clipperhouse/displaywidth v0.6.2 h1:ZDpTkFfpHOKte4RG5O/BOyf3ysnvFswpyYrV7z2uAKo= -github.com/clipperhouse/displaywidth v0.6.2/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o= -github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs= -github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA= -github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4= -github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= +github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8= +github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0= +github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk= +github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g= github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg= @@ -122,6 +120,12 @@ github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= github.com/d4l3k/messagediff v1.2.1 h1:ZcAIMYsUg0EAp9X+tt8/enBE/Q8Yd5kzPynLyKptt9U= github.com/d4l3k/messagediff v1.2.1/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkEQxENCrlLo= +github.com/danielgtaylor/huma/v2 v2.37.3 h1:6Av0Vj45Vk5lDxRVfoO2iPlEdvCvwLc7pl5nbqGOkYM= +github.com/danielgtaylor/huma/v2 v2.37.3/go.mod h1:OeHHtCEAaNiuVbAVdYu4IQ0UOmnb4x3yMUOShNlZ53g= +github.com/danielgtaylor/mexpr v1.9.1 h1:nA9bsGRmNlJeVCPFgGf7WhrLuKag/+iWfOaJ03iKFPI= +github.com/danielgtaylor/mexpr v1.9.1/go.mod h1:kAivYNRnBeE/IJinqBvVFvLrX54xX//9zFYwADo4Bc8= +github.com/danielgtaylor/shorthand/v2 v2.2.0 h1:hVsemdRq6v3JocP6YRTfu9rOoghZI9PFmkngdKqzAVQ= +github.com/danielgtaylor/shorthand/v2 v2.2.0/go.mod h1:t5QfaNf7DPru9ZLIIhPQSO7Gyvajm3euw7LxB/MTUqE= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -144,6 +148,8 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/dustinkirkland/golang-petname v0.0.0-20240422154211-76c06c4bde6b h1:+0Xqob+onh+4l9TSWmFyZ4JHqGUiCy5P1muyH8Evfpw= github.com/dustinkirkland/golang-petname v0.0.0-20240422154211-76c06c4bde6b/go.mod h1:8AuBTZBRSFqEYBPYULd+NN474/zZBLP+6WeT5S9xlAc= +github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= +github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= @@ -154,16 +160,18 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw= -github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM= +github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/ganigeorgiev/fexpr v0.5.0 h1:XA9JxtTE/Xm+g/JFI6RfZEHSiQlk+1glLvRK1Lpv/Tk= github.com/ganigeorgiev/fexpr v0.5.0/go.mod h1:RyGiGqmeXhEQ6+mlGdnUleLHgtzzu/VGO2WtJkF5drE= github.com/getsentry/sentry-go v0.41.0 h1:q/dQZOlEIb4lhxQSjJhQqtRr3vwrJ6Ahe1C9zv+ryRo= github.com/getsentry/sentry-go v0.41.0/go.mod h1:eRXCoh3uvmjQLY6qu63BjUZnaBu5L5WhMV1RwYO8W5s= github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo= github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= -github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618= -github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= +github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug= +github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= @@ -205,8 +213,8 @@ github.com/goccy/go-json v0.8.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGF github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= -github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= -github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= +github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= @@ -245,6 +253,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= +github.com/gorilla/feeds v1.2.0 h1:O6pBiXJ5JHhPvqy53NsjKOThq+dNFm8+DFrxBEdzSCc= +github.com/gorilla/feeds v1.2.0/go.mod h1:WMib8uJP3BbY+X8Szd1rA5Pzhdfh+HCCAYT2z7Fza6Y= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4= @@ -328,8 +338,8 @@ github.com/jszwedko/go-datemath v0.1.1-0.20230526204004-640a500621d6/go.mod h1:W github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= -github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c= +github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= github.com/kolaente/caldav-go v3.0.1-0.20260326091743-a55d55891017+incompatible h1:81Hr6g9bunxXhRv4AZv0anKcS1WwHLMgo6wbBjamJlY= github.com/kolaente/caldav-go v3.0.1-0.20260326091743-a55d55891017+incompatible/go.mod h1:y1UhTNI4g0hVymJrI6yJ5/ohy09hNBeU8iJEZjgdDOw= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -378,8 +388,8 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= -github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= +github.com/mattn/go-runewidth v0.0.20 h1:WcT52H91ZUAwy8+HUkdM3THM6gXqXuLJi9O3rjcQQaQ= +github.com/mattn/go-runewidth v0.0.20/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/go-sqlite3 v1.14.33 h1:A5blZ5ulQo2AtayQ9/limgHEkFreKj1Dv226a1K73s0= @@ -532,6 +542,8 @@ github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/wneessen/go-mail v0.7.2 h1:xxPnhZ6IZLSgxShebmZ6DPKh1b6OJcoHfzy7UjOkzS8= github.com/wneessen/go-mail v0.7.2/go.mod h1:+TkW6QP3EVkgTEqHtVmnAE/1MRhmzb8Y9/W3pweuS+k= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yosssi/gohtml v0.0.0-20201013000340-ee4748c638f4 h1:0sw0nJM544SpsihWx1bkXdYLQDlzRflMgFJQ4Yih9ts= github.com/yosssi/gohtml v0.0.0-20201013000340-ee4748c638f4/go.mod h1:+ccdNT0xMY1dtc5XBxumbYfOUhmduiGudqaDgD2rVRE= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -698,8 +710,8 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= -google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/magefile.go b/magefile.go index 5996ee8a7..f0b614b72 100644 --- a/magefile.go +++ b/magefile.go @@ -43,7 +43,6 @@ import ( "github.com/iancoleman/strcase" "github.com/magefile/mage/mg" - "golang.org/x/sync/errgroup" ) const ( @@ -62,27 +61,21 @@ var ( // Aliases are mage aliases of targets Aliases = map[string]any{ - "build": Build.Build, - "check:got-swag": Check.GotSwag, - "release": Release.Release, - "release:os-package": Release.OsPackage, - "release:prepare-nfpm-config": Release.PrepareNFPMConfig, - "release:repo-apt": Release.RepoApt, - "release:repo-rpm": Release.RepoRpm, - "release:repo-pacman": Release.RepoPacman, - "dev:make-migration": Dev.MakeMigration, - "dev:make-event": Dev.MakeEvent, - "dev:make-listener": Dev.MakeListener, - "dev:make-notification": Dev.MakeNotification, - "dev:prepare-worktree": Dev.PrepareWorktree, - "dev:tag-release": Dev.TagRelease, - "test:e2e": Test.E2E, - "test:e2e-api": Test.E2EApi, - "plugins:build": Plugins.Build, - "lint": Check.Golangci, - "lint:fix": Check.GolangciFix, - "generate:config-yaml": Generate.ConfigYAML, - "generate:swagger-docs": Generate.SwaggerDocs, + "build": Build.Build, + "check:got-swag": Check.GotSwag, + "dev:make-migration": Dev.MakeMigration, + "dev:make-event": Dev.MakeEvent, + "dev:make-listener": Dev.MakeListener, + "dev:make-notification": Dev.MakeNotification, + "dev:prepare-worktree": Dev.PrepareWorktree, + "dev:tag-release": Dev.TagRelease, + "test:e2e": Test.E2E, + "test:e2e-api": Test.E2EApi, + "plugins:build": Plugins.Build, + "lint": Check.Golangci, + "lint:fix": Check.GolangciFix, + "generate:config-yaml": Generate.ConfigYAML, + "generate:swagger-docs": Generate.SwaggerDocs, } ) @@ -268,45 +261,6 @@ func copyFile(src, dst string) error { return out.Close() } -// os.Rename has issues with moving files between docker volumes. -// Because of this limitation, it fails in drone. -// Source: https://gist.github.com/var23rav/23ae5d0d4d830aff886c3c970b8f6c6b -func moveFile(src, dst string) error { - inputFile, err := os.Open(src) - if err != nil { - return fmt.Errorf("couldn't open source file: %w", err) - } - defer inputFile.Close() - - outputFile, err := os.Create(dst) - if err != nil { - return fmt.Errorf("couldn't open dest file: %w", err) - } - defer outputFile.Close() - - _, err = io.Copy(outputFile, inputFile) - if err != nil { - return fmt.Errorf("writing to output file failed: %w", err) - } - - // Make sure to copy copy the permissions of the original file as well - si, err := os.Stat(src) - if err != nil { - return err - } - - if err := os.Chmod(dst, si.Mode()); err != nil { - return err - } - - // The copy was successful, so now delete the original file - err = os.Remove(src) - if err != nil { - return fmt.Errorf("failed removing original file: %w", err) - } - return nil -} - func appendToFile(filename, content string) error { f, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0o600) if err != nil { @@ -1180,624 +1134,6 @@ func (Build) SaveVersionToFile() error { return nil } -type Release mg.Namespace - -// Release runs all steps in the right order to create release packages for various platforms -func (Release) Release(ctx context.Context) error { - mg.Deps(initVars) - mg.Deps(Release.Dirs, prepareXgo) - - // Run compiling in parallel to speed it up - errs, _ := errgroup.WithContext(ctx) - errgroupGoWithContext(ctx, errs, (Release{}).Windows) - errgroupGoWithContext(ctx, errs, (Release{}).Linux) - errgroupGoWithContext(ctx, errs, (Release{}).Darwin) - if err := errs.Wait(); err != nil { - return err - } - - if err := (Release{}).Compress(ctx); err != nil { - return err - } - if err := (Release{}).Copy(); err != nil { - return err - } - if err := (Release{}).Check(); err != nil { - return err - } - if err := (Release{}).OsPackage(); err != nil { - return err - } - if err := (Release{}).Zip(ctx); err != nil { - return err - } - - return nil -} - -func errgroupGoWithContext(ctx context.Context, errs *errgroup.Group, do func(context.Context) error) { - errs.Go(func() error { - return do(ctx) - }) -} - -// Dirs creates all directories needed to release vikunja -func (Release) Dirs() error { - for _, d := range []string{"binaries", "release", "zip"} { - if err := os.MkdirAll("./"+DIST+"/"+d, 0o755); err != nil { - return err - } - } - return nil -} - -func prepareXgo(ctx context.Context) error { - mg.Deps(initVars) - if err := checkAndInstallGoTool(ctx, "xgo", "src.techknowlogick.com/xgo"); err != nil { - return err - } - - fmt.Println("Pulling latest xgo docker image...") - return runAndStreamOutput(ctx, "docker", "pull", "ghcr.io/techknowlogick/xgo:latest") -} - -func runXgo(ctx context.Context, targets string) error { - mg.Deps(initVars) - if err := checkAndInstallGoTool(ctx, "xgo", "src.techknowlogick.com/xgo"); err != nil { - return err - } - - extraLdflags := `-linkmode external -extldflags "-static" ` - - // See https://github.com/techknowlogick/xgo/issues/79 - if strings.HasPrefix(targets, "darwin") { - extraLdflags = "" - } - outName := os.Getenv("XGO_OUT_NAME") - if outName == "" { - outName = Executable + "-" + Version - } - - if err := runAndStreamOutput(ctx, "xgo", - "-dest", "./"+DIST+"/binaries", - "-tags", "netgo "+Tags, - "-ldflags", extraLdflags+Ldflags, - "-targets", targets, - "-out", outName, - "."); err != nil { - return err - } - if os.Getenv("DRONE_WORKSPACE") != "" { - return filepath.Walk("/build/", func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - // Skip directories - if info.IsDir() { - return nil - } - - return moveFile(path, "./"+DIST+"/binaries/"+info.Name()) - }) - } - return nil -} - -// Windows builds binaries for windows -func (Release) Windows(ctx context.Context) error { - return runXgo(ctx, "windows/*") -} - -// Linux builds binaries for linux -func (Release) Linux(ctx context.Context) error { - targets := []string{ - "linux/amd64", - "linux/arm-5", - "linux/arm-6", - "linux/arm-7", - "linux/arm64", - "linux/mips", - "linux/mipsle", - "linux/mips64", - "linux/mips64le", - "linux/riscv64", - } - return runXgo(ctx, strings.Join(targets, ",")) -} - -// Darwin builds binaries for darwin -func (Release) Darwin(ctx context.Context) error { - return runXgo(ctx, "darwin-10.15/*") -} - -func (Release) Xgo(ctx context.Context, target string) error { - parts := strings.Split(target, "/") - if len(parts) < 2 { - return fmt.Errorf("invalid target") - } - - variant := "" - if len(parts) > 2 && parts[2] != "" { - variant = "-" + strings.ReplaceAll(parts[2], "v", "") - } - - return runXgo(ctx, parts[0]+"/"+parts[1]+variant) -} - -// Compress compresses the built binaries in dist/binaries/ to reduce their filesize -func (Release) Compress(ctx context.Context) error { - // $(foreach file,$(filter-out $(wildcard $(wildcard $(DIST)/binaries/$(EXECUTABLE)-*mips*)),$(wildcard $(DIST)/binaries/$(EXECUTABLE)-*)), upx -9 $(file);) - - errs, _ := errgroup.WithContext(ctx) - - walkErr := filepath.Walk("./"+DIST+"/binaries/", func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - // Only executable files - if !strings.Contains(info.Name(), Executable) { - return nil - } - if strings.Contains(info.Name(), "mips") || - strings.Contains(info.Name(), "s390x") || - strings.Contains(info.Name(), "riscv64") || - strings.Contains(info.Name(), "darwin") || - (strings.Contains(info.Name(), "windows") && strings.Contains(info.Name(), "arm64")) { - // not supported by upx - return nil - } - - // Runs compressing in parallel since upx is single-threaded - errs.Go(func() error { - if err := runAndStreamOutput(ctx, "chmod", "+x", path); err != nil { // Make sure all binaries are executable. Sometimes the CI does weird things and they're not. - return err - } - return runAndStreamOutput(ctx, "upx", "-9", path) - }) - - return nil - }) - if walkErr != nil { - return walkErr - } - return errs.Wait() -} - -// Copy copies all built binaries to dist/release/ in preparation for creating the os packages -func (Release) Copy() error { - return filepath.Walk("./"+DIST+"/binaries/", func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - // Only executable files - if !strings.Contains(info.Name(), Executable) { - return nil - } - - return copyFile(path, "./"+DIST+"/release/"+info.Name()) - }) -} - -// Check creates sha256 checksum files for each binary in dist/release/ -func (Release) Check() error { - p := "./" + DIST + "/release/" - return filepath.Walk(p, func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - if info.IsDir() { - return nil - } - - f, err := os.Create(p + info.Name() + ".sha256") - if err != nil { - return err - } - - hash, err := calculateSha256FileHash(path) - if err != nil { - return err - } - - _, err = f.WriteString(hash + " " + info.Name()) - if err != nil { - return err - } - - return f.Close() - }) -} - -// OsPackage creates a folder for each -func (Release) OsPackage() error { - p := "./" + DIST + "/release/" - - // We first put all files in a map to then iterate over it since the walk function would otherwise also iterate - // over the newly created files, creating some kind of endless loop. - bins := make(map[string]os.FileInfo) - if err := filepath.Walk(p, func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - if strings.Contains(info.Name(), ".sha256") || info.IsDir() { - return nil - } - bins[path] = info - return nil - }); err != nil { - return err - } - - generateConfigYAMLFromJSON("./"+DefaultConfigYAMLSamplePath, true) - - for path, info := range bins { - folder := p + info.Name() + "-full/" - if err := os.Mkdir(folder, 0o755); err != nil { - return err - } - if err := moveFile(p+info.Name()+".sha256", folder+info.Name()+".sha256"); err != nil { - return err - } - if err := moveFile(path, folder+info.Name()); err != nil { - return err - } - if err := copyFile("./"+DefaultConfigYAMLSamplePath, folder+DefaultConfigYAMLSamplePath); err != nil { - return err - } - if err := copyFile("./LICENSE", folder+"LICENSE"); err != nil { - return err - } - } - return nil -} - -// Zip creates a zip file from all os-package folders in dist/release -func (Release) Zip(ctx context.Context) error { - rootDir, err := os.Getwd() - if err != nil { - return fmt.Errorf("could not get working directory: %w", err) - } - - p := "./" + DIST + "/release/" - if err := filepath.Walk(p, func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - if !info.IsDir() || info.Name() == "release" { - return nil - } - - fmt.Printf("Zipping %s...\n", info.Name()) - - zipFile := filepath.Join(rootDir, DIST, "zip", info.Name()+".zip") - c := exec.CommandContext(ctx, "zip", "-r", zipFile, ".", "-i", "*") //nolint:gosec // This mage task creates zips of every directory recursively, it must use the directory name in the resulting file path to distinguish output files. - c.Dir = path - out, err := c.Output() - fmt.Print(string(out)) - return err - }); err != nil { - return err - } - - return nil -} - -// repoSuite returns a validated suite name from the REPO_SUITE env var. -// Only "stable" and "unstable" are allowed to prevent path traversal. -func repoSuite() string { - suite := os.Getenv("REPO_SUITE") - switch suite { - case "stable", "unstable": - return suite - default: - return "stable" - } -} - -// RepoApt generates APT repository metadata using reprepro. -// It expects .deb files in /repo-work/incoming/ and outputs to /repo-output/apt/. -// The reprepro config is read from build/reprepro-dist-conf. -// Signing is done manually after reprepro finishes to avoid gpgme pinentry issues in CI. -// Environment: REPO_SUITE controls the target suite (default: "stable"). -// Environment: RELEASE_GPG_KEY, RELEASE_GPG_PASSPHRASE must be set for signing. -func (Release) RepoApt(ctx context.Context) error { - mg.Deps(initVars) - - suite := repoSuite() - - incomingDir := filepath.Join(DIST, "repo-work", "incoming") - outputBase := filepath.Join(DIST, "repo-output", "apt") - - // Set up reprepro conf directory - confDir := filepath.Join(outputBase, "conf") - if err := os.MkdirAll(confDir, 0o755); err != nil { - return fmt.Errorf("creating reprepro conf dir: %w", err) - } - - // Copy distributions config - distConf, err := os.ReadFile("build/reprepro-dist-conf") - if err != nil { - return fmt.Errorf("reading reprepro-dist-conf: %w", err) - } - if err := os.WriteFile(filepath.Join(confDir, "distributions"), distConf, 0o600); err != nil { - return fmt.Errorf("writing distributions config: %w", err) - } - - // Include all .deb files into the target suite - debs, err := filepath.Glob(filepath.Join(incomingDir, "*.deb")) - if err != nil { - return err - } - for _, deb := range debs { - abs, _ := filepath.Abs(deb) - if err := runAndStreamOutput(ctx, "reprepro", - "-b", outputBase, - "includedeb", suite, - abs, - ); err != nil { - return fmt.Errorf("reprepro includedeb %s: %w", filepath.Base(deb), err) - } - } - - // Sign Release files manually (reprepro's gpgme signing doesn't work in CI) - gpgKey := os.Getenv("RELEASE_GPG_KEY") - gpgPassphrase := os.Getenv("RELEASE_GPG_PASSPHRASE") - - releaseFile := filepath.Join(outputBase, "dists", suite, "Release") - if _, err := os.Stat(releaseFile); err == nil { - // Generate Release.gpg (detached signature) - if err := runAndStreamOutput(ctx, "gpg", - "--default-key", gpgKey, - "--batch", "--yes", - "--passphrase", gpgPassphrase, - "--pinentry-mode", "loopback", - "--detach-sign", "--armor", - "-o", releaseFile+".gpg", - releaseFile, - ); err != nil { - return fmt.Errorf("signing Release (detached): %w", err) - } - - // Generate InRelease (clearsigned) - if err := runAndStreamOutput(ctx, "gpg", - "--default-key", gpgKey, - "--batch", "--yes", - "--passphrase", gpgPassphrase, - "--pinentry-mode", "loopback", - "--clearsign", - "-o", filepath.Join(filepath.Dir(releaseFile), "InRelease"), - releaseFile, - ); err != nil { - return fmt.Errorf("signing Release (clearsign): %w", err) - } - } - - fmt.Println("APT repo metadata generated in", outputBase) - return nil -} - -// RepoRpm generates RPM repository metadata for all .rpm files in the work directory. -// Expects .rpm files in /repo-work/incoming/ and outputs to /repo-output/rpm//. -// Environment: RELEASE_GPG_KEY, RELEASE_GPG_PASSPHRASE must be set for signing. -// Environment: REPO_SUITE controls the target suite (default: "stable"). -func (Release) RepoRpm(ctx context.Context) error { - mg.Deps(initVars) - - suite := repoSuite() - - incomingDir := filepath.Join(DIST, "repo-work", "incoming") - outputBase := filepath.Join(DIST, "repo-output", "rpm", suite) - - archMap := map[string]string{ - "x86_64": "x86_64", - "aarch64": "aarch64", - "armv7": "armv7", - } - - gpgKey := os.Getenv("RELEASE_GPG_KEY") - gpgPassphrase := os.Getenv("RELEASE_GPG_PASSPHRASE") - - for pkgArch, repoArch := range archMap { - repoDir := filepath.Join(outputBase, repoArch) - if err := os.MkdirAll(repoDir, 0o755); err != nil { - return err - } - - // Symlink matching RPMs - pattern := filepath.Join(incomingDir, "*-"+pkgArch+".rpm") - rpms, _ := filepath.Glob(pattern) - if len(rpms) == 0 { - continue - } - for _, rpm := range rpms { - abs, _ := filepath.Abs(rpm) - dst := filepath.Join(repoDir, filepath.Base(rpm)) - os.Remove(dst) - if err := os.Symlink(abs, dst); err != nil { - return err - } - } - - // createrepo_c (--update if repodata already exists) - args := []string{repoDir} - if _, err := os.Stat(filepath.Join(repoDir, "repodata")); err == nil { - args = []string{"--update", repoDir} - } - if err := runAndStreamOutput(ctx, "createrepo_c", args...); err != nil { - return fmt.Errorf("createrepo_c for %s: %w", repoArch, err) - } - - // Sign repomd.xml - if err := runAndStreamOutput(ctx, "gpg", - "--default-key", gpgKey, - "--batch", "--yes", - "--passphrase", gpgPassphrase, - "--pinentry-mode", "loopback", - "--detach-sign", "--armor", - "-o", filepath.Join(repoDir, "repodata", "repomd.xml.asc"), - filepath.Join(repoDir, "repodata", "repomd.xml"), - ); err != nil { - return fmt.Errorf("signing repomd.xml for %s: %w", repoArch, err) - } - } - - fmt.Println("RPM repo metadata generated in", outputBase) - return nil -} - -// RepoPacman generates Pacman repository database for all .archlinux files in the work directory. -// Expects .archlinux files in /repo-work/incoming/ and outputs to /repo-output/pacman//. -// Environment: RELEASE_GPG_KEY, RELEASE_GPG_PASSPHRASE must be set for signing. -// Environment: REPO_SUITE controls the target suite (default: "stable"). -func (Release) RepoPacman(ctx context.Context) error { - mg.Deps(initVars) - - suite := repoSuite() - - incomingDir := filepath.Join(DIST, "repo-work", "incoming") - outputBase := filepath.Join(DIST, "repo-output", "pacman", suite) - - archMap := map[string]string{ - "x86_64": "x86_64", - "aarch64": "aarch64", - "armv7": "armv7", - } - - gpgKey := os.Getenv("RELEASE_GPG_KEY") - gpgPassphrase := os.Getenv("RELEASE_GPG_PASSPHRASE") - - for pkgArch, repoArch := range archMap { - repoDir := filepath.Join(outputBase, repoArch) - if err := os.MkdirAll(repoDir, 0o755); err != nil { - return err - } - - pattern := filepath.Join(incomingDir, "*-"+pkgArch+".archlinux") - pkgs, _ := filepath.Glob(pattern) - if len(pkgs) == 0 { - continue - } - for _, pkg := range pkgs { - abs, _ := filepath.Abs(pkg) - dst := filepath.Join(repoDir, filepath.Base(pkg)) - os.Remove(dst) - if err := os.Symlink(abs, dst); err != nil { - return err - } - } - - // repo-add creates vikunja.db.tar.gz and vikunja.files.tar.gz - dbPath := filepath.Join(repoDir, "vikunja.db.tar.gz") - repoPkgs, _ := filepath.Glob(filepath.Join(repoDir, "*.archlinux")) - repoAddArgs := append([]string{dbPath}, repoPkgs...) - if err := runAndStreamOutput(ctx, "repo-add", repoAddArgs...); err != nil { - return fmt.Errorf("repo-add for %s: %w", repoArch, err) - } - - // Create conventional symlinks (vikunja.db -> vikunja.db.tar.gz) - for _, name := range []string{"vikunja.db", "vikunja.files"} { - link := filepath.Join(repoDir, name) - os.Remove(link) - if err := os.Symlink(name+".tar.gz", link); err != nil { - return fmt.Errorf("creating symlink %s: %w", name, err) - } - } - - // Sign the database - if err := runAndStreamOutput(ctx, "gpg", - "--default-key", gpgKey, - "--batch", "--yes", - "--passphrase", gpgPassphrase, - "--pinentry-mode", "loopback", - "--detach-sign", - "-o", filepath.Join(repoDir, "vikunja.db.sig"), - dbPath, - ); err != nil { - return fmt.Errorf("signing db for %s: %w", repoArch, err) - } - } - - fmt.Println("Pacman repo metadata generated in", outputBase) - return nil -} - -// PrepareNFPMConfig prepares the nfpm config -func (Release) PrepareNFPMConfig() error { - mg.Deps(initVars) - var err error - - // Because nfpm does not support templating, we replace the values in the config file and restore it after running - nfpmConfigPath := "./nfpm.yaml" - nfpmconfig, err := os.ReadFile(nfpmConfigPath) - if err != nil { - return err - } - - var nfpmArch string - switch os.Getenv("NFPM_ARCH") { - case "arm64": - nfpmArch = "arm64" - case "arm7": - nfpmArch = "arm7" - case "386": - nfpmArch = "386" - default: - nfpmArch = "amd64" - } - - fixedConfig := strings.ReplaceAll(string(nfpmconfig), "", VersionNumber) - fixedConfig = strings.ReplaceAll(fixedConfig, "", BinLocation) - fixedConfig = strings.ReplaceAll(fixedConfig, "", nfpmArch) - if err := os.WriteFile(nfpmConfigPath, []byte(fixedConfig), 0); err != nil { - return err - } - - generateConfigYAMLFromJSON(DefaultConfigYAMLSamplePath, true) - - return nil -} - -// Packages creates deb, rpm and apk packages -func (Release) Packages(ctx context.Context) error { - mg.Deps(initVars) - - var err error - binpath := os.Getenv("NFPM_BIN_PATH") - if binpath == "" { - binpath = "nfpm" - } - err = exec.CommandContext(ctx, binpath).Run() - if err != nil && strings.Contains(err.Error(), "executable file not found") { - binpath = "/usr/bin/nfpm" - err = exec.CommandContext(ctx, binpath).Run() - } - if err != nil && strings.Contains(err.Error(), "executable file not found") { - return fmt.Errorf("executable %s not found: please manually install nfpm by running the command: curl -sfL https://install.goreleaser.com/github.com/goreleaser/nfpm.sh | sh -s -- -b $(go env GOPATH)/bin", binpath) - } - - err = (Release{}).PrepareNFPMConfig() - if err != nil { - return err - } - - releasePath := "./" + DIST + "/os-packages/" - if err := os.MkdirAll(releasePath, 0o755); err != nil { - return err - } - - if err := runAndStreamOutput(ctx, binpath, "pkg", "--packager", "deb", "--target", releasePath); err != nil { - return err - } - if err := runAndStreamOutput(ctx, binpath, "pkg", "--packager", "rpm", "--target", releasePath); err != nil { - return err - } - if err := runAndStreamOutput(ctx, binpath, "pkg", "--packager", "apk", "--target", releasePath); err != nil { - return err - } - - return nil -} - type Dev mg.Namespace // MakeMigration creates a new bare db migration skeleton in pkg/migration. @@ -2174,6 +1510,46 @@ func (Generate) ConfigYAML(commented bool) { generateConfigYAMLFromJSON(DefaultConfigYAMLSamplePath, commented) } +// ScalarBundle downloads the Scalar API reference standalone JS bundle into +// pkg/routes/api/v2/scalar/. Version is pinned to match the Scalar version +// used in Huma's internal docs at the time of last update. +func (Generate) ScalarBundle() error { + const ( + version = "1.44.20" + dest = "pkg/routes/api/v2/scalar/scalar.standalone.js" + ) + url := fmt.Sprintf("https://unpkg.com/@scalar/api-reference@%s/dist/browser/standalone.js", version) + + fmt.Printf("Downloading Scalar bundle %s from %s\n", version, url) + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) + defer cancel() + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) //nolint:gosec // This is a dev-only mage task and the URL is hard-coded above. + if err != nil { + return fmt.Errorf("build scalar bundle request: %w", err) + } + resp, err := http.DefaultClient.Do(req) + if err != nil { + return fmt.Errorf("download scalar bundle: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("download scalar bundle: unexpected status %s", resp.Status) + } + + var buf bytes.Buffer + if _, err := io.Copy(&buf, resp.Body); err != nil { + return fmt.Errorf("read scalar bundle body: %w", err) + } + + if err := os.WriteFile(dest, buf.Bytes(), 0o600); err != nil { + return fmt.Errorf("write %s: %w", dest, err) + } + + fmt.Printf("Wrote %d bytes to %s\n", buf.Len(), dest) + return nil +} + func localBranchExists(ctx context.Context, name string) bool { return exec.CommandContext(ctx, "git", "show-ref", "--verify", "--quiet", "refs/heads/"+name).Run() == nil //nolint:gosec // This is a dev-only mage task and the branch name is supplied by the developer running it. } diff --git a/mise.toml b/mise.toml new file mode 100644 index 000000000..2148b7af6 --- /dev/null +++ b/mise.toml @@ -0,0 +1,4 @@ +[tools] +node = "24.13.0" # keep in sync with frontend/.nvmrc +pnpm = "10.28.1" # keep in sync with frontend/package.json#packageManager +go = "1.25.7" # keep in sync with go.mod diff --git a/pkg/audit/audit_test.go b/pkg/audit/audit_test.go new file mode 100644 index 000000000..897ebe93c --- /dev/null +++ b/pkg/audit/audit_test.go @@ -0,0 +1,254 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package audit_test + +import ( + "context" + "encoding/json" + "os" + "path/filepath" + "strings" + "sync" + "testing" + "time" + + "code.vikunja.io/api/pkg/audit" + "code.vikunja.io/api/pkg/config" + "code.vikunja.io/api/pkg/events" + "code.vikunja.io/api/pkg/license" + "code.vikunja.io/api/pkg/log" + "code.vikunja.io/api/pkg/modules/keyvalue" + + "github.com/ThreeDotsLabs/watermill/message" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestMain(m *testing.M) { + log.InitLogger() + config.InitDefaultConfig() + keyvalue.InitStorage() // license.SetForTests persists state through keyvalue + os.Exit(m.Run()) +} + +// One event type per test so each topic has exactly the listeners the test registered. +type pipelineEvent struct { + TaskID int64 `json:"task_id"` + DoerID int64 `json:"doer_id"` +} + +func (e *pipelineEvent) Name() string { return "test.audit.pipeline" } + +type licenseGateEvent struct { + Marker string `json:"marker"` +} + +func (e *licenseGateEvent) Name() string { return "test.audit.licensegate" } + +type rotationEvent struct { + Filler string `json:"filler"` +} + +func (e *rotationEvent) Name() string { return "test.audit.rotation" } + +// otherListener is a second, non-audit listener on the same topic. +type otherListener struct { + called chan struct{} +} + +func (l *otherListener) Handle(_ *message.Message) error { + select { + case l.called <- struct{}{}: + default: + } + return nil +} + +func (l *otherListener) Name() string { return "other" } + +var ( + registerTestEventsOnce sync.Once + other = &otherListener{called: make(chan struct{}, 16)} +) + +// The listener registry is global and watermill rejects duplicate handler +// names, so register once per process (relevant for -count > 1). +func registerTestEvents() { + registerTestEventsOnce.Do(func() { + audit.RegisterEventForAudit(func(e *pipelineEvent) *audit.Entry { + return &audit.Entry{ + Action: "task.created", + Actor: audit.UserActor(e.DoerID), + Target: audit.TaskTarget(e.TaskID), + } + }) + events.RegisterListener((&pipelineEvent{}).Name(), other) + + audit.RegisterEventForAudit(func(e *licenseGateEvent) *audit.Entry { + return &audit.Entry{ + Action: "task.created", + Actor: audit.SystemActor(), + Target: audit.TaskTarget(1), + Metadata: map[string]any{"marker": e.Marker}, + } + }) + + audit.RegisterEventForAudit(func(e *rotationEvent) *audit.Entry { + return &audit.Entry{ + Action: "task.created", + Actor: audit.SystemActor(), + Target: audit.TaskTarget(1), + Metadata: map[string]any{"filler": e.Filler}, + } + }) + }) +} + +func setupAuditFile(t *testing.T) string { + t.Helper() + logfile := filepath.Join(t.TempDir(), "audit.log") + config.AuditLogfile.Set(logfile) + require.NoError(t, audit.Init()) + t.Cleanup(audit.Close) + return logfile +} + +func startEventRouter(t *testing.T) { + t.Helper() + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + ready, err := events.InitEventsForTesting(ctx) + require.NoError(t, err) + <-ready +} + +func waitForLines(t *testing.T, logfile string) []string { + t.Helper() + var lines []string + require.Eventually(t, func() bool { + content, err := os.ReadFile(logfile) + if err != nil { + return false + } + lines = strings.Split(strings.TrimSpace(string(content)), "\n") + if len(lines) == 1 && lines[0] == "" { + lines = nil + } + return len(lines) >= 1 + }, 5*time.Second, 10*time.Millisecond, "expected at least one audit log line") + return lines +} + +func TestAuditPipeline(t *testing.T) { + logfile := setupAuditFile(t) + license.SetForTests([]license.Feature{license.FeatureAuditLogs}) + t.Cleanup(license.ResetForTests) + + registerTestEvents() + startEventRouter(t) + + ctx := events.WithRequestMeta(context.Background(), &events.RequestMeta{ + IP: "192.0.2.42", + UserAgent: "test-agent/1.0", + RequestID: "req-123", + }) + require.NoError(t, events.DispatchWithContext(ctx, &pipelineEvent{TaskID: 99, DoerID: 7})) + + waitForLines(t, logfile) + select { + case <-other.called: + case <-time.After(5 * time.Second): + t.Fatal("other listener on the same topic was not called") + } + // A topic with multiple listeners must produce exactly one audit entry. + events.WaitForPendingHandlers() + lines := waitForLines(t, logfile) + require.Len(t, lines, 1) + + var entry audit.Entry + require.NoError(t, json.Unmarshal([]byte(lines[0]), &entry)) + assert.NotEmpty(t, entry.EventID) + assert.False(t, entry.Timestamp.IsZero()) + assert.Equal(t, "task.created", entry.Action) + assert.Equal(t, audit.UserActor(7), entry.Actor) + assert.Equal(t, audit.TaskTarget(99), entry.Target) + assert.Equal(t, audit.OutcomeSuccess, entry.Outcome) + assert.Equal(t, "192.0.2.42", entry.Source.IP) + assert.Equal(t, "test-agent/1.0", entry.Source.UserAgent) + assert.Equal(t, audit.SourceHTTP, entry.Source.Type) + assert.Equal(t, "req-123", entry.RequestID) +} + +func TestAuditLicenseGating(t *testing.T) { + logfile := setupAuditFile(t) + + registerTestEvents() + startEventRouter(t) + + // Without the licensed feature nothing must be written. The license check + // happens per event at handle time, so give the async handler a settle + // window before flipping the license back on. + license.ResetForTests() + require.NoError(t, events.Dispatch(&licenseGateEvent{Marker: "unlicensed"})) + require.Never(t, func() bool { + content, err := os.ReadFile(logfile) + return err == nil && len(content) > 0 + }, 500*time.Millisecond, 10*time.Millisecond, "unlicensed event must not be written") + events.WaitForPendingHandlers() + + license.SetForTests([]license.Feature{license.FeatureAuditLogs}) + t.Cleanup(license.ResetForTests) + require.NoError(t, events.Dispatch(&licenseGateEvent{Marker: "licensed"})) + + lines := waitForLines(t, logfile) + require.Len(t, lines, 1) + assert.Contains(t, lines[0], `"marker":"licensed"`) + assert.NotContains(t, lines[0], "unlicensed") + assert.Contains(t, lines[0], `"type":"system"`) +} + +func TestAuditRotation(t *testing.T) { + logfile := setupAuditFile(t) + license.SetForTests([]license.Feature{license.FeatureAuditLogs}) + t.Cleanup(license.ResetForTests) + + registerTestEvents() + startEventRouter(t) + + // Default max size is 100MB and config values are MB-granular, so two + // entries of ~600KB cross the limit with maxsizemb set to 1. + config.AuditRotationMaxSizeMB.Set("1") + t.Cleanup(func() { config.AuditRotationMaxSizeMB.Set("100") }) + require.NoError(t, audit.Init()) + + filler := strings.Repeat("x", 600*1024) + require.NoError(t, events.Dispatch(&rotationEvent{Filler: filler})) + waitForLines(t, logfile) + require.NoError(t, events.Dispatch(&rotationEvent{Filler: filler})) + waitForLines(t, logfile) + + require.Eventually(t, func() bool { + rotated, err := filepath.Glob(strings.TrimSuffix(logfile, ".log") + "-*.log") + return err == nil && len(rotated) == 1 + }, 5*time.Second, 10*time.Millisecond, "expected one rotated audit log file") +} + +func TestWriteAuditEventNotInitialized(t *testing.T) { + audit.Close() + err := audit.WriteAuditEvent(&audit.Entry{Action: "task.created"}) + require.Error(t, err) +} diff --git a/pkg/audit/entry.go b/pkg/audit/entry.go new file mode 100644 index 000000000..bb7f98493 --- /dev/null +++ b/pkg/audit/entry.go @@ -0,0 +1,154 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +// Package audit persists an audit trail of authentication, authorization and +// data lifecycle events as JSONL. +// +// Events opt in via RegisterEventForAudit, which subscribes one audit +// listener per event on the existing watermill bus; the event→Entry mapping +// is a closure passed at registration. The catalog of audited events lives in +// registerEventsForAuditLogging in pkg/models/listeners.go. +// +// Entries reference actors and targets by opaque ID only — deleting a user +// row orphans their audit references, which satisfies GDPR erasure without +// log redaction. +// +// Audit logging is gated twice: registration on the audit.enabled config key, +// and each write on the licensed audit_logs feature. The license is checked +// per event because it can change at runtime; enabled-but-unlicensed means +// listeners run and write nothing. +// +// Request attribution (IP, user agent, request id) flows from an Echo +// middleware through the request context onto message metadata — see +// pkg/events.RequestMeta. Events dispatched outside a request get +// source type "system" instead. +// +// A failed file write is returned to the router for retry. Tamper evidence +// comes from filesystem permissions (the file is created 0600) plus shipping +// the file to an external system, not from hash chains or signatures. +// Rotation is size-based with age-based cleanup of rotated files; retention +// is the operator's concern. +package audit + +import "time" + +// Entry is one audit log record. It only references actors and targets by +// opaque ID — no names, emails or content — so GDPR erasure is satisfied by +// deleting the referenced row. +type Entry struct { + EventID string `json:"event_id"` // UUIDv7 + Timestamp time.Time `json:"timestamp"` + Actor Actor `json:"actor"` + Source Source `json:"source"` + Action string `json:"action"` + Target Target `json:"target"` + Outcome string `json:"outcome"` + Reason string `json:"reason,omitempty"` + RequestID string `json:"request_id,omitempty"` + Metadata map[string]any `json:"metadata,omitempty"` +} + +type actorType string +type targetType string + +// Actor is the principal which performed the audited action. +type Actor struct { + Type actorType `json:"type"` + ID int64 `json:"id,omitempty"` +} + +// Source describes where the action originated from. +type Source struct { + Type string `json:"type"` + IP string `json:"ip,omitempty"` + UserAgent string `json:"user_agent,omitempty"` +} + +// Target is the resource the audited action was performed on. +type Target struct { + Type targetType `json:"type"` + ID int64 `json:"id,omitempty"` +} + +// Outcome values for an Entry. +const ( + OutcomeSuccess = "success" + OutcomeFailure = "failure" +) + +// Source types for an Entry. +const ( + SourceHTTP = "http" + SourceSystem = "system" +) + +// The action catalog. Every audited action is listed here. +const ( + ActionLoginSucceeded = "auth.login.succeeded" + ActionLoginFailed = "auth.login.failed" + ActionLogout = "auth.logout" + ActionAPITokenIssued = "auth.api_token.issued" // #nosec G101 -- action identifier, not a credential + ActionAPITokenRevoked = "auth.api_token.revoked" // #nosec G101 + ActionAPITokenUsed = "auth.api_token.used" // #nosec G101 + + ActionUserCreated = "user.created" + + ActionTaskCreated = "task.created" + ActionTaskUpdated = "task.updated" + ActionTaskDeleted = "task.deleted" + ActionTaskAssigneeAdded = "task.assignee.added" + ActionTaskAssigneeRemoved = "task.assignee.removed" + ActionTaskCommentCreated = "task.comment.created" + ActionTaskCommentUpdated = "task.comment.updated" + ActionTaskCommentDeleted = "task.comment.deleted" + ActionTaskAttachmentCreated = "task.attachment.created" + ActionTaskAttachmentDeleted = "task.attachment.deleted" + ActionTaskRelationCreated = "task.relation.created" + ActionTaskRelationDeleted = "task.relation.deleted" + + ActionProjectCreated = "project.created" + ActionProjectUpdated = "project.updated" + ActionProjectDeleted = "project.deleted" + ActionProjectSharedWithUser = "project.shared.user" + ActionProjectSharedWithTeam = "project.shared.team" + + ActionTeamCreated = "team.created" + ActionTeamDeleted = "team.deleted" + ActionTeamMemberAdded = "team.member.added" + ActionTeamMemberRemoved = "team.member.removed" +) + +// The type strings are unexported; these constructors are the only way to +// build an Actor or Target, so a mismatched type/ID pair can't be expressed. + +func UserActor(id int64) Actor { return Actor{Type: "user", ID: id} } +func LinkShareActor(id int64) Actor { return Actor{Type: "link_share", ID: id} } +func SystemActor() Actor { return Actor{Type: "system"} } + +// ActorFromDoerID maps a doer ID to an actor. Link shares are disguised as +// users with negative IDs throughout the event payloads. +func ActorFromDoerID(id int64) Actor { + if id < 0 { + return LinkShareActor(-id) + } + return UserActor(id) +} + +func TaskTarget(id int64) Target { return Target{Type: "task", ID: id} } +func ProjectTarget(id int64) Target { return Target{Type: "project", ID: id} } +func UserTarget(id int64) Target { return Target{Type: "user", ID: id} } +func TeamTarget(id int64) Target { return Target{Type: "team", ID: id} } +func APITokenTarget(id int64) Target { return Target{Type: "api_token", ID: id} } diff --git a/pkg/audit/listener.go b/pkg/audit/listener.go new file mode 100644 index 000000000..599a9b385 --- /dev/null +++ b/pkg/audit/listener.go @@ -0,0 +1,76 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package audit + +import ( + "encoding/json" + + "code.vikunja.io/api/pkg/events" + "code.vikunja.io/api/pkg/license" + + "github.com/ThreeDotsLabs/watermill/message" +) + +type auditListener struct { + handle func(msg *message.Message) error +} + +func (l *auditListener) Handle(msg *message.Message) error { + return l.handle(msg) +} + +func (l *auditListener) Name() string { + return "audit" +} + +// RegisterEventForAudit opts an event into audit logging. The event→Entry +// mapping is passed at registration, so opting in and defining the mapping +// are one unit and can't drift apart. Returning a nil Entry skips the event. +func RegisterEventForAudit[T any, PT interface { + *T + events.Event +}](toEntry func(PT) *Entry) { + name := PT(new(T)).Name() + events.RegisterListener(name, &auditListener{handle: func(msg *message.Message) error { + if !license.IsFeatureEnabled(license.FeatureAuditLogs) { + return nil // license is runtime-mutable — checked per event, not at registration + } + e := PT(new(T)) // fresh instance per message — handlers run concurrently + if err := json.Unmarshal(msg.Payload, e); err != nil { + return err + } + entry := toEntry(e) + if entry == nil { + return nil + } + enrichFromMetadata(entry, msg.Metadata) + return WriteAuditEvent(entry) + }}) +} + +func enrichFromMetadata(entry *Entry, meta message.Metadata) { + entry.Source.IP = meta.Get(events.MetadataKeyIP) + entry.Source.UserAgent = meta.Get(events.MetadataKeyUserAgent) + entry.RequestID = meta.Get(events.MetadataKeyRequestID) + if entry.Source.Type == "" { + if entry.Source.IP != "" || entry.Source.UserAgent != "" { + entry.Source.Type = SourceHTTP + } else { + entry.Source.Type = SourceSystem + } + } +} diff --git a/pkg/audit/writer.go b/pkg/audit/writer.go new file mode 100644 index 000000000..feccdb6f3 --- /dev/null +++ b/pkg/audit/writer.go @@ -0,0 +1,211 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package audit + +import ( + "encoding/json" + "errors" + "fmt" + "os" + "path/filepath" + "strings" + "sync" + "time" + + "code.vikunja.io/api/pkg/config" + "code.vikunja.io/api/pkg/log" + + "github.com/google/uuid" +) + +var ( + mu sync.Mutex + initialized bool + logFile *os.File + logfilePath string + currentSize int64 + maxSizeBytes int64 + maxAge time.Duration + lastSync time.Time +) + +// Init opens the audit log file. +// Safe to call again to re-read the config (used by tests). +func Init() error { + mu.Lock() + defer mu.Unlock() + + closeLocked() + + logfilePath = config.AuditLogfile.GetString() + if logfilePath == "" { + logfilePath = filepath.Join(config.LogPath.GetString(), "audit.log") + } + maxSizeBytes = config.AuditRotationMaxSizeMB.GetInt64() * 1024 * 1024 + maxAge = time.Duration(config.AuditRotationMaxAge.GetInt64()) * 24 * time.Hour + + if err := os.MkdirAll(filepath.Dir(logfilePath), 0750); err != nil { + return fmt.Errorf("could not create audit log directory: %w", err) + } + if err := openLogFileLocked(); err != nil { + return err + } + + initialized = true + return nil +} + +// Close closes the audit log file. Used by tests. +func Close() { + mu.Lock() + defer mu.Unlock() + closeLocked() +} + +func closeLocked() { + if logFile != nil { + _ = logFile.Sync() + _ = logFile.Close() + logFile = nil + } + initialized = false +} + +func openLogFileLocked() error { + f, err := os.OpenFile(logfilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600) + if err != nil { + return fmt.Errorf("could not open audit log file %s: %w", logfilePath, err) + } + info, err := f.Stat() + if err != nil { + _ = f.Close() + return fmt.Errorf("could not stat audit log file %s: %w", logfilePath, err) + } + logFile = f + currentSize = info.Size() + return nil +} + +// WriteAuditEvent writes one entry to the local audit log. A failed write is +// returned so the event router retries it. +func WriteAuditEvent(entry *Entry) error { + if entry.EventID == "" { + id, err := uuid.NewV7() + if err != nil { + return fmt.Errorf("could not generate audit event id: %w", err) + } + entry.EventID = id.String() + } + if entry.Timestamp.IsZero() { + entry.Timestamp = time.Now().UTC() + } + if entry.Outcome == "" { + entry.Outcome = OutcomeSuccess + } + + line, err := json.Marshal(entry) + if err != nil { + return fmt.Errorf("could not marshal audit entry: %w", err) + } + + mu.Lock() + if !initialized { + mu.Unlock() + return fmt.Errorf("audit log not initialized") + } + + if err := rotateIfNeededLocked(int64(len(line)) + 1); err != nil { + mu.Unlock() + return err + } + + // A failed rotation can leave us without an open file — retry the open + // here so writes self-heal via the router's retries instead of panicking. + if logFile == nil { + if err := openLogFileLocked(); err != nil { + mu.Unlock() + return err + } + } + + written, err := logFile.Write(append(line, '\n')) + currentSize += int64(written) + if err == nil && time.Since(lastSync) > time.Second { + err = logFile.Sync() + lastSync = time.Now() + } + mu.Unlock() + + if err != nil { + return fmt.Errorf("could not write audit entry: %w", err) + } + + return nil +} + +func rotateIfNeededLocked(addition int64) error { + if maxSizeBytes <= 0 || currentSize+addition <= maxSizeBytes { + return nil + } + + _ = logFile.Sync() + _ = logFile.Close() + logFile = nil + + rotatedPath := rotatedFileName(logfilePath, time.Now().UTC()) + if err := os.Rename(logfilePath, rotatedPath); err != nil { + // Reopen the original so logging continues even if rotation failed. + if openErr := openLogFileLocked(); openErr != nil { + return errors.Join(fmt.Errorf("could not rotate audit log: %w", err), openErr) + } + return fmt.Errorf("could not rotate audit log: %w", err) + } + + cleanupRotatedFiles() + + return openLogFileLocked() +} + +func rotatedFileName(path string, now time.Time) string { + ext := filepath.Ext(path) + return strings.TrimSuffix(path, ext) + "-" + now.Format("20060102T150405.000") + ext +} + +func cleanupRotatedFiles() { + if maxAge <= 0 { + return + } + + ext := filepath.Ext(logfilePath) + pattern := strings.TrimSuffix(logfilePath, ext) + "-*" + ext + matches, err := filepath.Glob(pattern) + if err != nil { + log.Errorf("Could not list rotated audit log files: %s", err) + return + } + + cutoff := time.Now().Add(-maxAge) + for _, match := range matches { + info, err := os.Stat(match) + if err != nil || info.ModTime().After(cutoff) { + continue + } + if err := os.Remove(match); err != nil { + log.Errorf("Could not remove old audit log file %s: %s", match, err) + } + } +} diff --git a/pkg/config/config.go b/pkg/config/config.go index 1941f7f0b..2443cb627 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -220,6 +220,11 @@ const ( WebhooksProxyPassword Key = `webhooks.proxypassword` WebhooksAllowNonRoutableIPs Key = `webhooks.allownonroutableips` + AuditEnabled Key = `audit.enabled` + AuditLogfile Key = `audit.logfile` + AuditRotationMaxSizeMB Key = `audit.rotation.maxsizemb` + AuditRotationMaxAge Key = `audit.rotation.maxage` + OutgoingRequestsAllowNonRoutableIPs Key = `outgoingrequests.allownonroutableips` OutgoingRequestsProxyURL Key = `outgoingrequests.proxyurl` OutgoingRequestsProxyPassword Key = `outgoingrequests.proxypassword` @@ -483,6 +488,11 @@ func InitDefaultConfig() { WebhooksEnabled.setDefault(true) WebhooksTimeoutSeconds.setDefault(30) WebhooksAllowNonRoutableIPs.setDefault(false) + // Audit + AuditEnabled.setDefault(false) + AuditLogfile.setDefault("") // empty means /audit.log, resolved at init + AuditRotationMaxSizeMB.setDefault(100) + AuditRotationMaxAge.setDefault(30) // Outgoing Requests OutgoingRequestsAllowNonRoutableIPs.setDefault(false) OutgoingRequestsTimeoutSeconds.setDefault(30) diff --git a/pkg/db/db.go b/pkg/db/db.go index 829dcd997..108ad4245 100644 --- a/pkg/db/db.go +++ b/pkg/db/db.go @@ -525,5 +525,17 @@ func CreateParadeDBIndexes() error { return fmt.Errorf("could not ensure paradedb project index: %w", err) } + // Create ParadeDB index for time_entries (comment search via MultiFieldSearch) + timeEntriesIndexSQL := `CREATE INDEX IF NOT EXISTS idx_time_entries_paradedb ON time_entries USING bm25 (id, comment) + WITH ( + key_field='id', + text_fields='{ + "comment": {"fast": true, "record": "freq"} + }' + )` + if _, err := x.Exec(timeEntriesIndexSQL); err != nil { + return fmt.Errorf("could not ensure paradedb time entry index: %w", err) + } + return nil } diff --git a/pkg/db/dump.go b/pkg/db/dump.go index 1d9f803b9..be345e173 100644 --- a/pkg/db/dump.go +++ b/pkg/db/dump.go @@ -127,7 +127,7 @@ func RestoreAndTruncate(table string, contents []map[string]interface{}) (err er return err } } else { - if _, err := x.Query("TRUNCATE TABLE ?", table); err != nil { + if _, err := x.Query("TRUNCATE TABLE " + x.Quote(table)); err != nil { return err } } @@ -148,7 +148,7 @@ func TruncateAllTables() error { return err } } else { - if _, err := x.Query("TRUNCATE TABLE ?", name); err != nil { + if _, err := x.Query("TRUNCATE TABLE " + x.Quote(name)); err != nil { return err } } diff --git a/pkg/db/fixtures/api_tokens.yml b/pkg/db/fixtures/api_tokens.yml index 2ff417bff..46a97e7f5 100644 --- a/pkg/db/fixtures/api_tokens.yml +++ b/pkg/db/fixtures/api_tokens.yml @@ -68,3 +68,13 @@ owner_id: 15 created: 2024-01-01 00:00:00 # token in plaintext is tk_nocaldav_token_test_000000005678efab +- id: 8 + title: 'feeds access token for user 13' + token_salt: fEdRTk9sR2 + token_hash: c1231ac23940702dcbdf20ae4c125a904780788b091f6d6c56f94f3620a634ec00aac5288659e04174a69a60b20ea86cdfa5 + token_last_eight: feed0013 + permissions: '{"feeds":["access"]}' + expires_at: 2099-01-01 00:00:00 + owner_id: 13 + created: 2024-01-01 00:00:00 + # token in plaintext is tk_feeds_access_token_user_0013_feed0013 diff --git a/pkg/db/fixtures/labels.yml b/pkg/db/fixtures/labels.yml index d685d6392..c84663d34 100644 --- a/pkg/db/fixtures/labels.yml +++ b/pkg/db/fixtures/labels.yml @@ -43,3 +43,11 @@ created_by_id: 1 updated: 2018-12-02 15:13:12 created: 2018-12-01 15:13:12 +# Covers the bot-owner branch: created by bot 23, whose owner is user 21. +# User 21 should be able to read/update/delete it; user 22 (who owns bot 24) +# should not. +- id: 9 + title: 'Label #9 - created by bot 23 owned by user 21' + created_by_id: 23 + updated: 2018-12-02 15:13:12 + created: 2018-12-01 15:13:12 diff --git a/pkg/db/fixtures/notifications.yml b/pkg/db/fixtures/notifications.yml new file mode 100644 index 000000000..bb4f90062 --- /dev/null +++ b/pkg/db/fixtures/notifications.yml @@ -0,0 +1,21 @@ +- id: 1 + notifiable_id: 1 + notification: '{"test":"notification one"}' + name: test.notification + subject_id: 1 + read_at: null + created: 2022-01-01 00:00:00 +- id: 2 + notifiable_id: 1 + notification: '{"test":"notification two"}' + name: test.notification + subject_id: 2 + read_at: null + created: 2022-01-02 00:00:00 +- id: 3 + notifiable_id: 2 + notification: '{"test":"other user"}' + name: test.notification + subject_id: 3 + read_at: null + created: 2022-01-03 00:00:00 diff --git a/pkg/db/fixtures/projects.yml b/pkg/db/fixtures/projects.yml index 86d2cba24..5ab8d6536 100644 --- a/pkg/db/fixtures/projects.yml +++ b/pkg/db/fixtures/projects.yml @@ -2,7 +2,7 @@ id: 1 title: Test1 description: Lorem Ipsum - identifier: test1 + identifier: TEST1 owner_id: 1 position: 3 updated: 2018-12-02 15:13:12 @@ -11,7 +11,7 @@ id: 2 title: Test2 description: Lorem Ipsum - identifier: test2 + identifier: TEST2 owner_id: 3 position: 2 updated: 2018-12-02 15:13:12 @@ -20,7 +20,7 @@ id: 3 title: Test3 description: Lorem Ipsum - identifier: test3 + identifier: TEST3 owner_id: 3 position: 1 updated: 2018-12-02 15:13:12 @@ -29,7 +29,7 @@ id: 4 title: Test4 description: Lorem Ipsum - identifier: test4 + identifier: TEST4 owner_id: 3 position: 4 updated: 2018-12-02 15:13:12 @@ -38,7 +38,7 @@ id: 5 title: Test5 description: Lorem Ipsum - identifier: test5 + identifier: TEST5 owner_id: 5 position: 5 updated: 2018-12-02 15:13:12 @@ -47,7 +47,7 @@ id: 6 title: Test6 description: Lorem Ipsum - identifier: test6 + identifier: TEST6 owner_id: 6 position: 6 updated: 2018-12-02 15:13:12 @@ -56,7 +56,7 @@ id: 7 title: Test7 description: Lorem Ipsum - identifier: test7 + identifier: TEST7 owner_id: 6 position: 7 updated: 2018-12-02 15:13:12 @@ -65,7 +65,7 @@ id: 8 title: Test8 description: Lorem Ipsum - identifier: test8 + identifier: TEST8 owner_id: 6 position: 8 updated: 2018-12-02 15:13:12 @@ -74,7 +74,7 @@ id: 9 title: Test9 description: Lorem Ipsum - identifier: test9 + identifier: TEST9 owner_id: 6 position: 9 updated: 2018-12-02 15:13:12 @@ -83,7 +83,7 @@ id: 10 title: Test10 description: Lorem Ipsum - identifier: test10 + identifier: TEST10 owner_id: 6 position: 10 updated: 2018-12-02 15:13:12 @@ -92,7 +92,7 @@ id: 11 title: Test11 description: Lorem Ipsum - identifier: test11 + identifier: TEST11 owner_id: 6 position: 11 updated: 2018-12-02 15:13:12 @@ -101,7 +101,7 @@ id: 12 title: Test12 description: Lorem Ipsum - identifier: test12 + identifier: TEST12 owner_id: 6 position: 12 parent_project_id: 27 @@ -111,7 +111,7 @@ id: 13 title: Test13 description: Lorem Ipsum - identifier: test13 + identifier: TEST13 owner_id: 6 position: 13 parent_project_id: 28 @@ -121,7 +121,7 @@ id: 14 title: Test14 description: Lorem Ipsum - identifier: test14 + identifier: TEST14 owner_id: 6 position: 14 parent_project_id: 29 @@ -131,7 +131,7 @@ id: 15 title: Test15 description: Lorem Ipsum - identifier: test15 + identifier: TEST15 owner_id: 6 position: 15 parent_project_id: 32 @@ -141,7 +141,7 @@ id: 16 title: Test16 description: Lorem Ipsum - identifier: test16 + identifier: TEST16 owner_id: 6 position: 16 parent_project_id: 33 @@ -151,7 +151,7 @@ id: 17 title: Test17 description: Lorem Ipsum - identifier: test17 + identifier: TEST17 owner_id: 6 position: 17 parent_project_id: 34 @@ -163,7 +163,7 @@ id: 18 title: Test18 description: Lorem Ipsum - identifier: test18 + identifier: TEST18 owner_id: 7 position: 18 updated: 2018-12-02 15:13:12 @@ -172,7 +172,7 @@ id: 19 title: Test19 description: Lorem Ipsum - identifier: test19 + identifier: TEST19 owner_id: 7 position: 19 parent_project_id: 29 @@ -183,7 +183,7 @@ id: 20 title: Test20 description: Lorem Ipsum - identifier: test20 + identifier: TEST20 owner_id: 13 position: 20 updated: 2018-12-02 15:13:12 @@ -192,7 +192,7 @@ id: 21 title: Test21 archived through parent list description: Lorem Ipsum - identifier: test21 + identifier: TEST21 owner_id: 1 position: 21 parent_project_id: 22 @@ -202,7 +202,7 @@ id: 22 title: Test22 archived individually description: Lorem Ipsum - identifier: test22 + identifier: TEST22 owner_id: 1 is_archived: 1 position: 22 @@ -212,7 +212,7 @@ id: 23 title: Test23 description: Lorem Ipsum - identifier: test23 + identifier: TEST23 owner_id: 12 position: 23 updated: 2018-12-02 15:13:12 @@ -221,7 +221,7 @@ id: 24 title: Test24 description: Lorem Ipsum - identifier: test6 + identifier: TEST6 owner_id: 6 position: 7 updated: 2018-12-02 15:13:12 @@ -302,7 +302,7 @@ id: 35 title: Test35 with background description: Lorem Ipsum - identifier: test6 + identifier: TEST6 owner_id: 6 background_file_id: 1 position: 8 @@ -312,7 +312,7 @@ id: 36 title: Project 36 for Caldav tests description: Lorem Ipsum - identifier: test36 + identifier: TEST36 owner_id: 15 position: 1 updated: 2018-12-02 15:13:12 @@ -321,7 +321,7 @@ id: 37 title: Project 37 description: Lorem Ipsum - identifier: test37 + identifier: TEST37 owner_id: 16 position: 1 updated: 2018-12-02 15:13:12 @@ -330,7 +330,7 @@ id: 38 title: Project 38 for Caldav tests description: Lorem Ipsum - identifier: test38 + identifier: TEST38 owner_id: 15 position: 2 updated: 2018-12-02 15:13:12 @@ -341,7 +341,7 @@ id: 39 title: Orphaned project with deleted parent description: This project has a parent_project_id pointing to a non-existent project - identifier: orph1 + identifier: ORPH1 owner_id: 1 parent_project_id: 999999 is_archived: 1 @@ -354,7 +354,7 @@ id: 40 title: Test40 child archived individually description: Lorem Ipsum - identifier: test40 + identifier: TEST40 owner_id: 1 parent_project_id: 3 is_archived: 1 @@ -366,7 +366,7 @@ id: 41 title: HierarchyParent description: Parent project for subtask permission hierarchy test - identifier: hier1 + identifier: HIER1 owner_id: 6 position: 41 updated: 2018-12-02 15:13:12 @@ -376,7 +376,7 @@ id: 42 title: HierarchyChild description: Child project for subtask permission hierarchy test - identifier: hier2 + identifier: HIER2 owner_id: 6 parent_project_id: 41 position: 42 diff --git a/pkg/db/fixtures/time_entries.yml b/pkg/db/fixtures/time_entries.yml new file mode 100644 index 000000000..a3a0112fb --- /dev/null +++ b/pkg/db/fixtures/time_entries.yml @@ -0,0 +1,36 @@ +- id: 1 + user_id: 1 + task_id: 1 + project_id: 0 + start_time: 2018-12-01 10:00:00 + end_time: 2018-12-01 11:00:00 + comment: Time entry on task 1 + created: 2018-12-01 15:13:12 + updated: 2018-12-02 15:13:12 +- id: 2 + user_id: 1 + task_id: 0 + project_id: 1 + start_time: 2018-12-01 12:00:00 + end_time: 2018-12-01 13:00:00 + comment: Standalone entry on project 1 + created: 2018-12-01 15:13:12 + updated: 2018-12-02 15:13:12 +- id: 3 + user_id: 3 + task_id: 0 + project_id: 3 + start_time: 2018-12-01 12:00:00 + end_time: 2018-12-01 13:00:00 + comment: Standalone entry on project 3 by user3 + created: 2018-12-01 15:13:12 + updated: 2018-12-02 15:13:12 +# Running timer (no end_time) on task 1 by user1 +- id: 4 + user_id: 1 + task_id: 1 + project_id: 0 + start_time: 2018-12-01 14:00:00 + comment: Running timer + created: 2018-12-01 15:13:12 + updated: 2018-12-02 15:13:12 diff --git a/pkg/db/fixtures/webhooks.yml b/pkg/db/fixtures/webhooks.yml index c203cf04a..983a03aff 100644 --- a/pkg/db/fixtures/webhooks.yml +++ b/pkg/db/fixtures/webhooks.yml @@ -8,3 +8,74 @@ created_by_id: 1 created: 2024-01-01 00:00:00 updated: 2024-01-01 00:00:00 +# Webhooks 2-4 back the v2 permission matrix: project 9 is shared to user1 +# read-only, 10 write, 11 admin. Update/Delete gate on Project.CanWrite, so the +# read-share webhook (#2) must be forbidden while the write/admin ones pass. +- id: 2 + target_url: "https://example.com/webhook-read-share" + events: '["task.updated"]' + project_id: 9 + created_by_id: 6 + created: 2024-01-01 00:00:00 + updated: 2024-01-01 00:00:00 +- id: 3 + target_url: "https://example.com/webhook-write-share" + events: '["task.updated"]' + project_id: 10 + created_by_id: 6 + created: 2024-01-01 00:00:00 + updated: 2024-01-01 00:00:00 +- id: 4 + target_url: "https://example.com/webhook-admin-share" + events: '["task.updated"]' + project_id: 11 + created_by_id: 6 + created: 2024-01-01 00:00:00 + updated: 2024-01-01 00:00:00 +# Webhook #5 lives in project 2 (owned by user3, not shared to user1) so the +# fully-forbidden update/delete path can be exercised under its real parent. +- id: 5 + target_url: "https://example.com/webhook-forbidden" + events: '["task.updated"]' + project_id: 2 + created_by_id: 3 + created: 2024-01-01 00:00:00 + updated: 2024-01-01 00:00:00 +# Webhooks 6-8 are user-level (project_id null, user_id set) and back the v2 +# user-webhook tests. #6/#7 belong to user6; #6 carries credentials so masking +# can be asserted. #8 belongs to user1 so the owner-isolation check (user6 must +# not see or mutate another user's webhook) has a target. +# +# Event choice matters because the pkg/e2etests user-webhook suite shares these +# fixtures and dispatches real events. The WebhookListener fans a fired event out +# to ALL of the event-user's webhooks, asynchronously; a user-level fixture +# subscribed to a user-directed event the suite dispatches for its owner fires a +# real (failing) delivery to example.com, and that in-flight write then races the +# next test's fixture reload ("database table is locked: webhooks"). The suite +# dispatches user-directed events only for user1, so #6/#7 are owned by user6, and +# #8 (owned by user1) subscribes to task.updated — a project-only event the +# listener never matches for user webhooks. None of the three can fire there. +- id: 6 + target_url: "https://example.com/user-webhook-fixture" + events: '["task.reminder.fired"]' + user_id: 6 + secret: "uwh-secret-fixture" + basic_auth_user: "uwh-basicauth-user" + basic_auth_password: "uwh-basicauth-pass" + created_by_id: 6 + created: 2024-01-01 00:00:00 + updated: 2024-01-01 00:00:00 +- id: 7 + target_url: "https://example.com/user-webhook-second" + events: '["task.reminder.fired"]' + user_id: 6 + created_by_id: 6 + created: 2024-01-01 00:00:00 + updated: 2024-01-01 00:00:00 +- id: 8 + target_url: "https://example.com/user-webhook-other" + events: '["task.updated"]' + user_id: 1 + created_by_id: 1 + created: 2024-01-01 00:00:00 + updated: 2024-01-01 00:00:00 diff --git a/pkg/events/events.go b/pkg/events/events.go index 30c26ea99..5973b132d 100644 --- a/pkg/events/events.go +++ b/pkg/events/events.go @@ -201,6 +201,13 @@ func InitEventsForTesting(ctx context.Context) (<-chan struct{}, error) { // Dispatch dispatches an event func Dispatch(event Event) error { + return DispatchWithContext(context.Background(), event) +} + +// DispatchWithContext dispatches an event and copies request metadata from the +// context (see WithRequestMeta) onto the message metadata, so listeners can +// attribute the event to the originating HTTP request. +func DispatchWithContext(ctx context.Context, event Event) error { if isUnderTest { dispatchedTestEvents = append(dispatchedTestEvents, event) return nil @@ -216,6 +223,17 @@ func Dispatch(event Event) error { } msg := message.NewMessage(watermill.NewUUID(), content) + if meta := RequestMetaFromContext(ctx); meta != nil { + if meta.IP != "" { + msg.Metadata.Set(MetadataKeyIP, meta.IP) + } + if meta.UserAgent != "" { + msg.Metadata.Set(MetadataKeyUserAgent, meta.UserAgent) + } + if meta.RequestID != "" { + msg.Metadata.Set(MetadataKeyRequestID, meta.RequestID) + } + } return pubsub.Publish(event.Name(), msg) } @@ -241,8 +259,9 @@ func DispatchOnCommit(key any, event Event) { // DispatchPending dispatches all events accumulated for the given key and removes them. // Call this after s.Commit(). Safe to call even if no events were registered. +// Request metadata on the context (see WithRequestMeta) is copied onto each message. // If any event fails to dispatch, the error is logged but remaining events are still dispatched. -func DispatchPending(key any) { +func DispatchPending(ctx context.Context, key any) { val, ok := pendingEvents.LoadAndDelete(key) if !ok { return @@ -251,7 +270,7 @@ func DispatchPending(key any) { // No need to lock here since we've already removed it from the map // and this key won't receive new events for _, event := range queue.events { - if err := Dispatch(event); err != nil { + if err := DispatchWithContext(ctx, event); err != nil { log.Errorf("Failed to dispatch event %s: %v", event.Name(), err) } } diff --git a/pkg/events/events_test.go b/pkg/events/events_test.go index f78396a50..186d12f4a 100644 --- a/pkg/events/events_test.go +++ b/pkg/events/events_test.go @@ -17,6 +17,7 @@ package events import ( + "context" "testing" "github.com/stretchr/testify/assert" @@ -40,7 +41,7 @@ func TestDispatchOnCommit(t *testing.T) { assert.Equal(t, 0, CountDispatchedEvents("test.event")) // Simulate post-commit dispatch - DispatchPending(key) + DispatchPending(context.Background(), key) // Now it should be dispatched assert.Equal(t, 1, CountDispatchedEvents("test.event")) @@ -57,7 +58,7 @@ func TestDispatchOnCommitMultipleEvents(t *testing.T) { assert.Equal(t, 0, CountDispatchedEvents("test.event")) - DispatchPending(key) + DispatchPending(context.Background(), key) assert.Equal(t, 3, CountDispatchedEvents("test.event")) } @@ -74,7 +75,7 @@ func TestCleanupPending(t *testing.T) { CleanupPending(key) // Dispatching after cleanup should be a no-op - DispatchPending(key) + DispatchPending(context.Background(), key) assert.Equal(t, 0, CountDispatchedEvents("test.event")) } @@ -85,7 +86,7 @@ func TestDispatchPendingNoEvents(t *testing.T) { key := new(int) // Should be a no-op - DispatchPending(key) + DispatchPending(context.Background(), key) // Verify no events were dispatched assert.Equal(t, 0, CountDispatchedEvents("test.event")) diff --git a/pkg/events/request_meta.go b/pkg/events/request_meta.go new file mode 100644 index 000000000..796c7b7e9 --- /dev/null +++ b/pkg/events/request_meta.go @@ -0,0 +1,55 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package events + +import "context" + +// RequestMeta carries information about the originating HTTP request. It is +// stashed on the request context by a middleware and copied onto message +// metadata at publish time, so listeners (e.g. audit) can attribute an event +// to a request without every dispatch site changing its signature. +type RequestMeta struct { + IP string + UserAgent string + RequestID string +} + +// Message metadata keys holding request information. +const ( + MetadataKeyIP = "request_ip" + MetadataKeyUserAgent = "request_user_agent" + MetadataKeyRequestID = "request_id" +) + +type requestMetaKeyType struct{} + +var requestMetaKey requestMetaKeyType + +// WithRequestMeta returns a context carrying the given request metadata. +func WithRequestMeta(ctx context.Context, meta *RequestMeta) context.Context { + return context.WithValue(ctx, requestMetaKey, meta) +} + +// RequestMetaFromContext returns the request metadata stored on the context, +// or nil if there is none. +func RequestMetaFromContext(ctx context.Context) *RequestMeta { + if ctx == nil { + return nil + } + meta, _ := ctx.Value(requestMetaKey).(*RequestMeta) + return meta +} diff --git a/pkg/events/testing.go b/pkg/events/testing.go index 2c969f057..302886747 100644 --- a/pkg/events/testing.go +++ b/pkg/events/testing.go @@ -76,6 +76,18 @@ func ClearDispatchedEvents() { dispatchedTestEvents = nil } +// GetDispatchedEvents returns all dispatched test events matching the given name, letting tests +// assert on the event payload (not just that it was dispatched). +func GetDispatchedEvents(eventName string) []Event { + var events []Event + for _, testEvent := range dispatchedTestEvents { + if testEvent.Name() == eventName { + events = append(events, testEvent) + } + } + return events +} + // CountDispatchedEvents counts how many events of a specific type have been dispatched. func CountDispatchedEvents(eventName string) int { count := 0 diff --git a/pkg/files/files.go b/pkg/files/files.go index a103a5237..5542a5f5f 100644 --- a/pkg/files/files.go +++ b/pkg/files/files.go @@ -28,8 +28,6 @@ import ( "code.vikunja.io/api/pkg/config" "code.vikunja.io/api/pkg/db" "code.vikunja.io/api/pkg/log" - "code.vikunja.io/api/pkg/metrics" - "code.vikunja.io/api/pkg/modules/keyvalue" "code.vikunja.io/api/pkg/web" "github.com/c2h5oh/datasize" @@ -39,12 +37,12 @@ import ( // File holds all information about a file type File struct { - ID int64 `xorm:"bigint autoincr not null unique pk" json:"id"` - Name string `xorm:"text not null" json:"name"` - Mime string `xorm:"text null" json:"mime"` - Size uint64 `xorm:"bigint not null" json:"size"` + ID int64 `xorm:"bigint autoincr not null unique pk" json:"id" readOnly:"true" doc:"The unique, numeric id of this file."` + Name string `xorm:"text not null" json:"name" readOnly:"true" doc:"The original name of the uploaded file."` + Mime string `xorm:"text null" json:"mime" readOnly:"true" doc:"The detected mime type of the file."` + Size uint64 `xorm:"bigint not null" json:"size" readOnly:"true" doc:"The size of the file in bytes."` - Created time.Time `xorm:"created" json:"created"` + Created time.Time `xorm:"created" json:"created" readOnly:"true" doc:"A timestamp when this file was uploaded."` CreatedByID int64 `xorm:"bigint not null" json:"-"` File io.ReadCloser `xorm:"-" json:"-"` @@ -205,7 +203,7 @@ func (f *File) Delete(s *xorm.Session) (err error) { return err } - return keyvalue.DecrBy(metrics.FilesCountKey, 1) + return nil } // Save saves a file to storage @@ -214,5 +212,5 @@ func (f *File) Save(fcontent io.ReadSeeker) error { if err != nil { return fmt.Errorf("failed to save file: %w", err) } - return keyvalue.IncrBy(metrics.FilesCountKey, 1) + return nil } diff --git a/pkg/i18n/i18n.go b/pkg/i18n/i18n.go index dea187bf7..78b878ed7 100644 --- a/pkg/i18n/i18n.go +++ b/pkg/i18n/i18n.go @@ -77,6 +77,7 @@ var availableLanguages = map[string]bool{ "fi-FI": true, "he-IL": true, "sv-SE": true, + "el-GR": true, // IMPORTANT: Also add new languages to the frontend } diff --git a/pkg/i18n/lang/de-DE.json b/pkg/i18n/lang/de-DE.json index 64762d377..c446214de 100644 --- a/pkg/i18n/lang/de-DE.json +++ b/pkg/i18n/lang/de-DE.json @@ -173,5 +173,10 @@ "since_hours": "einer Stunde|%[1]d Stunden", "since_minutes": "einer Minute|%[1]d Minuten", "list_last_separator": "und" + }, + "feeds": { + "notifications": { + "title": "Vikunja Benachrichtigungen für %[1]s" + } } } \ No newline at end of file diff --git a/pkg/i18n/lang/de-swiss.json b/pkg/i18n/lang/de-swiss.json index 64762d377..c446214de 100644 --- a/pkg/i18n/lang/de-swiss.json +++ b/pkg/i18n/lang/de-swiss.json @@ -173,5 +173,10 @@ "since_hours": "einer Stunde|%[1]d Stunden", "since_minutes": "einer Minute|%[1]d Minuten", "list_last_separator": "und" + }, + "feeds": { + "notifications": { + "title": "Vikunja Benachrichtigungen für %[1]s" + } } } \ No newline at end of file diff --git a/pkg/i18n/lang/el-GR.json b/pkg/i18n/lang/el-GR.json new file mode 100644 index 000000000..d14c2664d --- /dev/null +++ b/pkg/i18n/lang/el-GR.json @@ -0,0 +1,182 @@ +{ + "notifications": { + "greeting": "Γεια σου %[1]s,", + "email_confirm": { + "subject": "%[1]s, παρακαλώ επιβεβαίωσε τη διεύθυνση email σου στο Vikunja", + "subject_new": "%[1]s + Vikunja = <3", + "welcome": "Καλωσορίσατε στο Vikunja!", + "confirm": "Για να επιβεβαιώσεις τη διεύθυνση email σου, κάνε κλικ στον παρακάτω σύνδεσμο:" + }, + "password": { + "changed": { + "subject": "Ο κωδικός σου πρόσβασης στο Vikunja άλλαξε", + "success": "Ο κωδικός πρόσβασης του λογαριασμού σου άλλαξε με επιτυχία.", + "warning": "Αν δεν ήσουν εσύ, αυτό θα μπορούσε να σημαίνει ότι κάποιος παραβίασε το λογαριασμό σου. Σε αυτή την περίπτωση επικοινώνησε με το διαχειριστή του διακομιστή σου." + }, + "reset": { + "subject": "Επανάφερε τον κωδικό σου στο Vikunja", + "instructions": "Για να επαναφέρεις τον κωδικό σου, πάτησε στον παρακάτω σύνδεσμο:", + "valid_duration": "Ο σύνδεσμος θα ισχύει για 24 ώρες." + } + }, + "totp": { + "invalid": { + "subject": "Κάποιος μόλις προσπάθησε να εισέλθει στο λογαριασμό σου στο Vikunja, αλλά απέτυχε", + "message": "Κάποιος μόλις προσπάθησε να συνδεθεί στο λογαριασμό σου με το σωστό όνομα χρήστη και κωδικό πρόσβασης, αλλά με λάθος κωδικό TOTP.", + "warning": "**Εάν δεν ήσουν εσύ, κάποιος άλλος γνωρίζει τον κωδικό σου πρόσβασης. Θα πρέπει να ορίσεις ένα νέο αμέσως!**" + }, + "account_locked": { + "subject": "Έχουμε απενεργοποιήσει το λογαριασμό σου στο Vikunja", + "message": "Κάποιος προσπάθησε να συνδεθεί με τα διαπιστευτήριά σας αλλά απέτυχε να δώσει έγκυρο κωδικό πρόσβασης TOTP.", + "disabled": "Μετά από 10 αποτυχημένες προσπάθειες, έχουμε απενεργοποιήσει το λογαριασμό σας και επαναφέραμε τον κωδικό σας πρόσβασης. Για να ορίσετε ένα νέο, ακολουθήστε τις οδηγίες στο email επαναφοράς που μόλις σας στείλαμε.", + "reset_instructions": "Εάν δε λάβατε ένα email με οδηγίες επαναφοράς, μπορείτε πάντα να ζητήσετε ένα νέο στο [%[1]s](%[2]s)." + } + }, + "login": { + "failed": { + "subject": "Κάποιος μόλις προσπάθησε να εισέλθει στο λογαριασμό σας στο Vikunja, αλλά απέτυχε να δώσει σωστό κωδικό πρόσβασης", + "message": "Κάποιος μόλις προσπάθησε να συνδεθεί στο λογαριασμό σας με λάθος κωδικό πρόσβασης τρεις φορές συνεχόμενα.", + "warning": "Αν δεν ήσασταν εσείς, θα μπορούσε να είναι κάποιος άλλος που προσπαθεί να εισέλθει κακόβουλα στο λογαριασμό σας.", + "enhance_security": "Για να ενισχύσετε την ασφάλεια του λογαριασμού σας μπορείτε να ορίσετε έναν ισχυρότερο κωδικό πρόσβασης ή να ενεργοποιήσετε τον έλεγχο ταυτοποίησης TOTP στις ρυθμίσεις:" + } + }, + "account": { + "deletion": { + "confirm": { + "subject": "Παρακαλώ επιβεβαιώστε τη διαγραφή του λογαριασμού σας στο Vikunja", + "request": "Ζητήσατε τη διαγραφή του λογαριασμού σας. Για να επιβεβαιώσετε την ενέργεια, κάντε κλικ στον παρακάτω σύνδεσμο:", + "valid_duration": "Ο σύνδεσμος θα ισχύει για 24 ώρες.", + "schedule_info": "Μόλις επιβεβαιώσετε τη διαγραφή, θα προγραμματίσουμε τη διαγραφή του λογαριασμού σας σε τρεις ημέρες και θα σας στείλουμε ένα άλλο email μέχρι τότε.", + "consequences": "Αν προχωρήσετε με τη διαγραφή του λογαριασμού σας, θα καταργήσουμε όλα τα έργα και τις εργασίες που δημιουργήσατε. Η κυριότητα όλων όσων μοιραστήκατε με άλλο χρήστη ή ομάδα θα μεταφερθεί σε αυτούς.", + "changed_mind": "Αν δεν αιτηθήκατε τη διαγραφή ή αλλάξατε γνώμη, μπορείτε απλά να αγνοήσετε αυτό το email." + }, + "scheduled": { + "subject_days": "Ο λογαριασμός σας στο Vikunja θα διαγραφεί σε %[1]ημέρες", + "subject_tomorrow": "Ο λογαριασμός σας στο Vikunja θα διαγραφεί αύριο", + "request_reminder": "Αιτηθήκατε πρόσφατα τη διαγραφή του λογαριασμού σας στο Vikunja.", + "deletion_time_days": "Θα διαγράψουμε τον λογαριασμό σας σε %[1]s ημέρες.", + "deletion_time_tomorrow": "Θα διαγράψουμε τον λογαριασμό σας αύριο.", + "changed_mind": "Αν αλλάξατε γνώμη, απλά κάντε κλικ στον παρακάτω σύνδεσμο για να ακυρώσετε τη διαγραφή και ακολουθήστε τις οδηγίες:" + }, + "completed": { + "subject": "Ο λογαριασμός σας στο Vikunja έχει διαγραφεί", + "confirmation": "Όπως ζητήθηκε, έχουμε διαγράψει το λογαριασμό σας στο Vikunja.", + "permanent": "Η διαγραφή είναι μόνιμη. Αν δε δημιουργήσατε αντίγραφο ασφαλείας και χρειάζεστε τα δεδομένα σας αυτή τη στιγμή, επικοινωνήστε με το διαχειριστή σας." + } + } + }, + "task": { + "reminder": { + "subject": "Υπενθύμιση για \"%[1]s\" (%[2]s)", + "message": "Αυτή είναι μια φιλική υπενθύμιση για την εργασία \"%[1]s\" (%[2]s)." + }, + "comment": { + "subject": "Σχ: %[1]s (%[2]s)", + "mentioned_subject": "Ο/Η %[1]s σας ανέφερε σε ένα σχόλιο στο \"%[2]s\" (%[3]s)" + }, + "assigned": { + "subject_to_assignee": "Σας έχει ανατεθεί το \"%[1]s\" (%[2]s)", + "message_to_assignee": "Ο/Η %[1]s σας έχει αναθέσει το \"%[2]s\".", + "subject_to_others": "Το \"%[1]s\" (%[2]s) έχει ανατεθεί στον/στην %[3]s", + "message_to_others": "Ο/Η %[1]s έχει αναθέσει την εργασία στον/στην %[2]s.", + "subject_to_others_self": "Το \"%[1]s\" (%[2]s) έχει ανατεθεί από τον/την %[3]s στον εαυτό τους", + "message_to_others_self": "Ο/Η %[1]s έχει αναθέσει την εργασία στον εαυτό τους." + }, + "deleted": { + "subject": "Το \"%[1]s\" (%[2]s) έχει διαγραφεί", + "message": "Ο/Η %[1]s έχει διαγράψει την εργασία \"%[2]s\" (%[3]s)" + }, + "mentioned": { + "subject_new": "Ο/Η %[1]s σας ανέφερε σε μια νέα εργασία \"%[2]s\" (%[3]s)", + "subject": "Ο/Η %[1]s σας ανέφερε σε μια εργασία \"%[2]s\" (%[3]s)" + }, + "overdue": { + "subject": "Η εργασία \"%[1]s\" (%[2]s) είναι ληξιπρόθεσμη", + "message": "Αυτή είναι μια φιλική υπενθύμιση για την εργασία \"%[1]s\" (%[2]s) που είναι ληξιπρόθεσμη %[3]s και δεν έχει ακόμη παραδοθεί.", + "multiple_subject": "Οι εκπρόθεσμες εργασίες σας", + "multiple_message": "Έχετε τις παρακάτω εκπρόθεσμες εργασίες:", + "overdue_since": "από %[1]s", + "overdue_now": "τώρα", + "overdue": "εκπρόθεσμη %[1]s" + } + }, + "project": { + "created": "Ο/Η %[1]s δημιούργησε το έργο \"%[2]s\"" + }, + "team": { + "member_added": { + "subject": "Ο/Η %[1]s σας πρόσθεσε στην ομάδα \"%[2]s\" στο Vikunja", + "message": "Ο/Η %[1]s σας πρόσθεσε στην ομάδα %[2]s στο Vikunja." + } + }, + "data_export": { + "ready": { + "subject": "Η εξαγωγή δεδομένων σας από το Vikunja είναι έτοιμη", + "message": "Η εξαγωγή δεδομένων σας από το Vikunja είναι έτοιμη για λήψη. Κάντε κλικ στο πλήκτρο παρακάτω για τα κατεβάσετε:", + "availability": "Η λήψη θα είναι διαθέσιμη για τις επόμενες 7 ημέρες." + } + }, + "migration": { + "done": { + "subject": "Η μετάβαση από το %[1]s στο Vikunja ολοκληρώθηκε", + "imported": "Το Vikunja έχει εισαγάγει όλες τις λίστες / έργα, εργασίες, σημειώσεις, υπενθυμίσεις και αρχεία από το %[1]s που έχετε πρόσβαση.", + "have_fun": "Καλή διασκέδαση με τα νέα (παλιά) έργα σας!" + }, + "failed": { + "subject": "Η μετάβαση από το %[1]s στο Vikunja απέτυχε", + "message": "Φαίνεται ότι η μετάβαση από το %[1]s δεν πήγε όπως θέλαμε αυτή τη φορά.", + "retry": "Μην ανησυχείτε, όμως! Απλά δώστε μια ακόμη ευκαιρία ξεκινώντας με τον ίδιο τρόπο όπως και πριν. Μερικές φορές, αυτές οι αναποδιές συμβαίνουν λόγω προβλημάτων από την πλευρά του %[1]s και δοκιμάζοντας ξανά πολλές φορές λύνει το πρόβλημα.", + "error": "Εντοπίσαμε ένα μικρό σφάλμα στην πορεία: `%[2]s`.", + "report": "Παρακαλώ αφήστε μια σημείωση σχετικά με αυτό [στο φόρουμ](https://community.vikunja.io/) ή σε οποιοδήποτε από τα συνηθισμένα μέρη, έτσι ώστε να μπορούμε να ρίξουμε μια ματιά στο γιατί απέτυχε.", + "working_on_it": "Έχουμε το μήνυμα σφάλματος στο ραντάρ μας και είμαστε έτοιμοι να το τακτοποιήσουμε σύντομα." + } + }, + "api_token": { + "expiring": { + "week": { + "subject": "Το API τεκμήριό σας \"%[1]s\" λήγει σύντομα", + "message": "Το τεκμήριό σας API \"%[1]s\" θα λήξει στις %[2]s. Αν εξακολουθείτε να το χρειάζεστε, παρακαλώ δημιουργήστε ένα νέο προτού λήξει." + }, + "day": { + "subject": "Το API τεκμήριό σας \"%[1]s\" λήγει αύριο", + "message": "Το τεκμήριό σας API \"%[1]s\" θα λήξει στις %[2]s. Αν εξακολουθείτε να το χρειάζεστε, παρακαλώ δημιουργήστε ένα νέο προτού λήξει." + }, + "action": "Διαχείριση Τεκμηρίων API" + } + }, + "common": { + "have_nice_day": "Να έχεις μια όμορφη μέρα!", + "copy_url": "Αν το παραπάνω κουμπί δε λειτουργεί, αντιγράψτε το παρακάτω url και επικολλήστε το στη γραμμή διευθύνσεων του προγράμματός σας πλοήγησης:", + "actions": { + "open_task": "Άνοιγμα Εργασίας στο Vikunja", + "open_vikunja": "Άνοιγμα του Vikunja", + "open_project": "Άνοιγμα Έργου", + "open_team": "Άνοιγμα Ομάδας", + "download": "Λήψη", + "reset_password": "Επαναφορά του κωδικού σας πρόσβασης", + "go_to_settings": "Μετάβαση στις ρυθμίσεις", + "confirm_email": "Επιβεβαιώστε τη διεύθυνση email σας", + "abort_deletion": "Ματαίωση της διαγραφής", + "confirm_account_deletion": "Επιβεβαίωση της διαγραφής του λογαριασμού μου", + "change_notification_settings_link": "Μπορείτε να αλλάξετε τις ρυθμίσεις σας ειδοποίησης [here](%[1]s).", + "left_comment": "Ο/Η %[1]s άφησε ένα σχόλιο", + "mentioned_you_comment": "Ο/Η %[1]s σας ανέφερε σε ένα σχόλιο", + "mentioned_you": "Ο/Η %[1]s σας ανέφερε", + "mentioned_you_new_task": "Ο/Η %[1]s σας ανέφερε σε μια νέα εργασία" + } + } + }, + "time": { + "since_years": "ένα έτος|%[1]d έτη", + "since_weeks": "μία εβδομάδα|%[1]d εβδομάδες", + "since_days": "μία ημέρα|%[1]d ημέρες", + "since_hours": "μία ώρα|%[1]d ώρες", + "since_minutes": "ένα λεπτό|%[1]d λεπτά", + "list_last_separator": "και" + }, + "feeds": { + "notifications": { + "title": "Ειδοποιήσεις του Vikunja για %[1]s" + } + } +} \ No newline at end of file diff --git a/pkg/i18n/lang/en.json b/pkg/i18n/lang/en.json index e8b05efb4..b9040d628 100644 --- a/pkg/i18n/lang/en.json +++ b/pkg/i18n/lang/en.json @@ -173,5 +173,10 @@ "since_hours": "one hour|%[1]d hours", "since_minutes": "one minute|%[1]d minutes", "list_last_separator": "and" + }, + "feeds": { + "notifications": { + "title": "Vikunja notifications for %[1]s" + } } } diff --git a/pkg/i18n/lang/fa-IR.json b/pkg/i18n/lang/fa-IR.json new file mode 100644 index 000000000..9e26dfeeb --- /dev/null +++ b/pkg/i18n/lang/fa-IR.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/pkg/i18n/lang/ja-JP.json b/pkg/i18n/lang/ja-JP.json index c0745bade..d0fe3d7a4 100644 --- a/pkg/i18n/lang/ja-JP.json +++ b/pkg/i18n/lang/ja-JP.json @@ -173,5 +173,10 @@ "since_hours": "%[1]d 時間", "since_minutes": "%[1]d 分", "list_last_separator": ", " + }, + "feeds": { + "notifications": { + "title": "%[1]s の Vikunja 通知" + } } } \ No newline at end of file diff --git a/pkg/i18n/lang/th-TH.json b/pkg/i18n/lang/th-TH.json new file mode 100644 index 000000000..9e26dfeeb --- /dev/null +++ b/pkg/i18n/lang/th-TH.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/pkg/i18n/lang/uk-UA.json b/pkg/i18n/lang/uk-UA.json index 2976f0d13..3911cc547 100644 --- a/pkg/i18n/lang/uk-UA.json +++ b/pkg/i18n/lang/uk-UA.json @@ -173,5 +173,10 @@ "since_hours": "одна година|%[1]d години|%[1]d годин", "since_minutes": "одна хвилина|%[1]d хвилини|%[1]d хвилин", "list_last_separator": "і" + }, + "feeds": { + "notifications": { + "title": "Vikunja сповіщення для %[1]s" + } } } \ No newline at end of file diff --git a/pkg/initialize/init.go b/pkg/initialize/init.go index df11ef01f..7210feb1e 100644 --- a/pkg/initialize/init.go +++ b/pkg/initialize/init.go @@ -19,6 +19,7 @@ package initialize import ( "time" + "code.vikunja.io/api/pkg/audit" "code.vikunja.io/api/pkg/config" "code.vikunja.io/api/pkg/cron" "code.vikunja.io/api/pkg/db" @@ -98,6 +99,12 @@ func FullInitWithoutAsync() { // See the package comment in pkg/license/license.go before removing. license.Init() + if config.AuditEnabled.GetBool() { + if err := audit.Init(); err != nil { + log.Fatalf("Could not initialize audit logging: %s", err) + } + } + // Start the mail daemon mail.StartMailDaemon() @@ -145,7 +152,6 @@ func FullInit() { // Start processing events go func() { models.RegisterListeners() - user.RegisterListeners() migrationHandler.RegisterListeners() ws.RegisterListeners() err := events.InitEvents() diff --git a/pkg/mail/testing.go b/pkg/mail/testing.go index 02a047586..b7e3f4bc1 100644 --- a/pkg/mail/testing.go +++ b/pkg/mail/testing.go @@ -46,3 +46,17 @@ func AssertSent(t *testing.T, opts *Opts) { assert.True(t, found, "Failed to assert mail '%v' has been sent.", opts) } + +// LastSent returns the most recently captured mail when running under Fake(), +// or nil if no mail has been sent. Intended for tests. +func LastSent() *Opts { + if len(sentMails) == 0 { + return nil + } + return sentMails[len(sentMails)-1] +} + +// ResetSent clears the captured mail buffer. Intended for tests. +func ResetSent() { + sentMails = nil +} diff --git a/pkg/metrics/metrics.go b/pkg/metrics/metrics.go index 00dce43eb..2ff7c1c6d 100644 --- a/pkg/metrics/metrics.go +++ b/pkg/metrics/metrics.go @@ -17,8 +17,9 @@ package metrics import ( - "strconv" + "time" + "code.vikunja.io/api/pkg/db" "code.vikunja.io/api/pkg/log" "code.vikunja.io/api/pkg/modules/keyvalue" @@ -36,6 +37,22 @@ const ( AttachmentsCountKey = `attachments_count` ) +// countCacheTTL is how long a cached entity count is served before it is recomputed +// from the database. The counts are inherently approximate (Prometheus samples them), +// so a short staleness window is fine and keeps the cache self-healing — a missed +// InvalidateCount call costs at most this much staleness, never a permanent drift. +const countCacheTTL = 30 * time.Second + +// countTables maps each count metric key to the database table it counts. +var countTables = map[string]string{ + ProjectCountKey: "projects", + UserCountKey: "users", + TaskCountKey: "tasks", + TeamCountKey: "teams", + FilesCountKey: "files", + AttachmentsCountKey: "task_attachments", +} + var registry *prometheus.Registry func GetRegistry() *prometheus.Registry { @@ -53,7 +70,10 @@ func registerPromMetric(key, description string) { Name: "vikunja_" + key, Help: description, }, func() float64 { - count, _ := GetCount(key) + count, err := GetCount(key) + if err != nil { + log.Errorf("Could not get count for metric %s: %s", key, err) + } return float64(count) })) if err != nil { @@ -65,8 +85,8 @@ func registerPromMetric(key, description string) { func InitMetrics() { GetRegistry() - registerPromMetric(ProjectCountKey, "The number of projects on this instance") - registerPromMetric(UserCountKey, "The total number of shares on this instance") + registerPromMetric(ProjectCountKey, "The total number of projects on this instance") + registerPromMetric(UserCountKey, "The total number of users on this instance") registerPromMetric(TaskCountKey, "The total number of tasks on this instance") registerPromMetric(TeamCountKey, "The total number of teams on this instance") registerPromMetric(FilesCountKey, "The total number of files on this instance") @@ -76,26 +96,31 @@ func InitMetrics() { setupActiveLinkSharesMetric() } -// GetCount returns the current count from keyvalue -func GetCount(key string) (count int64, err error) { - cnt, exists, err := keyvalue.Get(key) - if err != nil { - return 0, err - } - if !exists { +// GetCount returns the current count for the given metric key. The value is counted +// directly from the database and cached for countCacheTTL, so repeated scrapes don't +// hit the database on every request. +func GetCount(key string) (int64, error) { + return keyvalue.RememberFor(key, countCacheTTL, func() (int64, error) { + return countFromDatabase(key) + }) +} + +// countFromDatabase runs a COUNT(*) for the table backing the given metric key. +func countFromDatabase(key string) (int64, error) { + table, has := countTables[key] + if !has { return 0, nil } - if s, is := cnt.(string); is { - count, err = strconv.ParseInt(s, 10, 64) - } else { - count = cnt.(int64) - } + s := db.NewSession() + defer s.Close() - return + return s.Table(table).Count() } -// SetCount sets the project count to a given value -func SetCount(count int64, key string) error { - return keyvalue.Put(key, count) +// InvalidateCount drops the cached count for a key so the next read recomputes it from +// the database. Use it where instant freshness is worth the extra COUNT(*); everywhere +// else the countCacheTTL keeps the value reasonably up to date on its own. +func InvalidateCount(key string) error { + return keyvalue.Del(key) } diff --git a/pkg/migration/20260519120000.go b/pkg/migration/20260519120000.go new file mode 100644 index 000000000..cafb50a40 --- /dev/null +++ b/pkg/migration/20260519120000.go @@ -0,0 +1,108 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package migration + +import ( + "fmt" + + "code.vikunja.io/api/pkg/log" + + "src.techknowlogick.com/xormigrate" + "xorm.io/xorm" +) + +func init() { + migrations = append(migrations, &xormigrate.Migration{ + ID: "20260519120000", + Description: "uppercase existing project identifiers", + Migrate: func(tx *xorm.Engine) error { + s := tx.NewSession() + defer s.Close() + + if err := s.Begin(); err != nil { + return err + } + + // Postgres/SQLite default to case-sensitive comparisons, so + // projects like "foo" and "FOO" may coexist today. Uppercasing + // them blindly would create duplicate identifiers and break the + // invariant that task identifiers built from them are unique. + // Detect each colliding group, keep the oldest project's + // identifier and clear the rest so the operator can re-assign + // them after the migration runs. + type collidingGroup struct { + UpperIdentifier string `xorm:"upper_identifier"` + } + var groups []collidingGroup + err := s.SQL(` + SELECT UPPER(identifier) AS upper_identifier FROM projects + WHERE identifier IS NOT NULL AND identifier <> '' + GROUP BY UPPER(identifier) + HAVING COUNT(*) > 1 + `).Find(&groups) + if err != nil { + _ = s.Rollback() + return fmt.Errorf("failed to scan for colliding project identifiers: %w", err) + } + + for _, g := range groups { + type projectRow struct { + ID int64 + Identifier string + } + var rows []projectRow + err := s.SQL( + "SELECT id, identifier FROM projects WHERE UPPER(identifier) = ? ORDER BY id ASC", + g.UpperIdentifier, + ).Find(&rows) + if err != nil { + _ = s.Rollback() + return err + } + if len(rows) < 2 { + continue + } + + kept := rows[0] + for i := 1; i < len(rows); i++ { + log.Warningf( + "Project identifier collision during uppercase migration: clearing identifier %q on project %d (kept %q on project %d). Re-assign a unique identifier after the migration.", + rows[i].Identifier, rows[i].ID, kept.Identifier, kept.ID, + ) + if _, err := s.Exec( + "UPDATE projects SET identifier = ? WHERE id = ?", + "", rows[i].ID, + ); err != nil { + _ = s.Rollback() + return err + } + } + } + + // UPPER() is supported by MySQL, PostgreSQL and SQLite. + if _, err := s.Exec("UPDATE projects SET identifier = UPPER(identifier) WHERE identifier IS NOT NULL AND identifier <> UPPER(identifier)"); err != nil { + _ = s.Rollback() + return err + } + + return s.Commit() + }, + Rollback: func(_ *xorm.Engine) error { + return nil + }, + }) +} diff --git a/pkg/migration/20260607132257.go b/pkg/migration/20260607132257.go new file mode 100644 index 000000000..f2a7e76b3 --- /dev/null +++ b/pkg/migration/20260607132257.go @@ -0,0 +1,55 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package migration + +import ( + "time" + + "src.techknowlogick.com/xormigrate" + "xorm.io/xorm" +) + +// Mirrors models.TimeEntry. No partial unique index for the single-active-timer +// rule — MySQL has no filtered indexes; it's enforced in the model instead. +type TimeEntry20260607132257 struct { + ID int64 `xorm:"bigint autoincr not null unique pk"` + UserID int64 `xorm:"bigint not null INDEX"` + TaskID int64 `xorm:"bigint null INDEX"` + ProjectID int64 `xorm:"bigint null INDEX"` + StartTime time.Time `xorm:"not null INDEX"` + EndTime *time.Time `xorm:"null"` + Comment string `xorm:"text null"` + Created time.Time `xorm:"created not null"` + Updated time.Time `xorm:"updated not null"` +} + +func (TimeEntry20260607132257) TableName() string { + return "time_entries" +} + +func init() { + migrations = append(migrations, &xormigrate.Migration{ + ID: "20260607132257", + Description: "Add time_entries table", + Migrate: func(tx *xorm.Engine) error { + return tx.Sync(TimeEntry20260607132257{}) + }, + Rollback: func(tx *xorm.Engine) error { + return tx.DropTables(TimeEntry20260607132257{}) + }, + }) +} diff --git a/pkg/migration/20260617153629.go b/pkg/migration/20260617153629.go new file mode 100644 index 000000000..d5ae3d772 --- /dev/null +++ b/pkg/migration/20260617153629.go @@ -0,0 +1,127 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package migration + +import ( + "fmt" + + "src.techknowlogick.com/xormigrate" + "xorm.io/xorm" + "xorm.io/xorm/schemas" +) + +type taskPosition20260617153629 struct { + TaskID int64 `xorm:"bigint not null index"` + ProjectViewID int64 `xorm:"bigint not null index"` + Position float64 `xorm:"double not null"` +} + +func (taskPosition20260617153629) TableName() string { + return "task_positions" +} + +func init() { + migrations = append(migrations, &xormigrate.Migration{ + ID: "20260617153629", + Description: "deduplicate task positions and add a unique index on task_id + project_view_id", + Migrate: func(tx *xorm.Engine) error { + + s := tx.NewSession() + defer s.Close() + + err := s.Begin() + if err != nil { + return err + } + + // First remove all duplicate entries. A task may only ever have a + // single position per view; rapid task creation could race and + // insert more than one row before this constraint existed. + duplicates := []taskPosition20260617153629{} + err = s. + Select("task_id, project_view_id"). + GroupBy("task_id, project_view_id"). + Having("count(*) > 1"). + Find(&duplicates) + if err != nil { + _ = s.Rollback() + return err + } + + // Keep the lowest position of each group so the result is + // deterministic across databases. + kept := []taskPosition20260617153629{} + for _, dup := range duplicates { + row := taskPosition20260617153629{} + has, err := s. + Where("task_id = ? AND project_view_id = ?", dup.TaskID, dup.ProjectViewID). + OrderBy("position ASC"). + Get(&row) + if err != nil { + _ = s.Rollback() + return err + } + if !has { + // The pair was just reported as duplicated by the GroupBy above, + // so a row must exist. If it doesn't, fail instead of continuing — + // the delete loop below would otherwise drop every row for the pair + // without re-inserting one. + _ = s.Rollback() + return fmt.Errorf("no task_positions row found for task %d and project view %d while deduplicating positions", dup.TaskID, dup.ProjectViewID) + } + kept = append(kept, row) + } + + for _, dup := range duplicates { + _, err = s. + Where("task_id = ? AND project_view_id = ?", dup.TaskID, dup.ProjectViewID). + Delete(&taskPosition20260617153629{}) + if err != nil { + _ = s.Rollback() + return err + } + } + + for _, position := range kept { + _, err = s.Insert(&position) + if err != nil { + _ = s.Rollback() + return err + } + } + + err = s.Commit() + if err != nil { + return err + } + + // Then create the unique index + var query string + switch tx.Dialect().URI().DBType { + case schemas.MYSQL: + query = "CREATE UNIQUE INDEX UQE_task_positions_task_project_view ON task_positions (task_id, project_view_id)" + default: + query = "CREATE UNIQUE INDEX IF NOT EXISTS UQE_task_positions_task_project_view ON task_positions (task_id, project_view_id)" + } + _, err = tx.Exec(query) + return err + }, + Rollback: func(_ *xorm.Engine) error { + return nil + }, + }) +} diff --git a/pkg/migration/20260619155410.go b/pkg/migration/20260619155410.go new file mode 100644 index 000000000..97f91bb5e --- /dev/null +++ b/pkg/migration/20260619155410.go @@ -0,0 +1,55 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package migration + +import ( + "time" + + "src.techknowlogick.com/xormigrate" + "xorm.io/xorm" +) + +// Mirrors models.Session; adds the two columns RP-Initiated Logout needs. +type sessionOIDCLogout20260619155410 struct { + ID string `xorm:"varchar(36) not null unique pk"` + UserID int64 `xorm:"bigint not null index"` + TokenHash string `xorm:"varchar(64) not null unique index"` + DeviceInfo string `xorm:"text"` + IPAddress string `xorm:"varchar(100)"` + IsLongSession bool `xorm:"not null default false"` + OIDCIDToken string `xorm:"text"` + OIDCProviderKey string `xorm:"varchar(250)"` + LastActive time.Time `xorm:"not null"` + Created time.Time `xorm:"created not null"` +} + +func (sessionOIDCLogout20260619155410) TableName() string { + return "sessions" +} + +func init() { + migrations = append(migrations, &xormigrate.Migration{ + ID: "20260619155410", + Description: "Add oidc_id_token and oidc_provider_key columns to sessions for RP-Initiated Logout", + Migrate: func(tx *xorm.Engine) error { + return tx.Sync(sessionOIDCLogout20260619155410{}) + }, + Rollback: func(tx *xorm.Engine) error { + return nil + }, + }) +} diff --git a/pkg/models/admin_overview.go b/pkg/models/admin_overview.go new file mode 100644 index 000000000..082d6c81d --- /dev/null +++ b/pkg/models/admin_overview.go @@ -0,0 +1,83 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package models + +import ( + "code.vikunja.io/api/pkg/license" + + "xorm.io/xorm" +) + +type ShareCounts struct { + LinkShares int64 `json:"link_shares" readOnly:"true" doc:"Number of link shares across all projects."` + TeamShares int64 `json:"team_shares" readOnly:"true" doc:"Number of team-project shares."` + UserShares int64 `json:"user_shares" readOnly:"true" doc:"Number of user-project shares."` +} + +type Overview struct { + Users int64 `json:"users" readOnly:"true" doc:"Total number of user accounts."` + Projects int64 `json:"projects" readOnly:"true" doc:"Total number of projects."` + Tasks int64 `json:"tasks" readOnly:"true" doc:"Total number of tasks."` + Teams int64 `json:"teams" readOnly:"true" doc:"Total number of teams."` + Shares ShareCounts `json:"shares" readOnly:"true" doc:"Aggregate share counts."` + License license.Info `json:"license" readOnly:"true" doc:"Snapshot of the instance license state."` +} + +// BuildOverview returns aggregate instance counts plus the current license snapshot. +func BuildOverview(s *xorm.Session) (*Overview, error) { + users, err := s.Table("users").Count() + if err != nil { + return nil, err + } + projects, err := s.Table("projects").Count() + if err != nil { + return nil, err + } + tasks, err := s.Table("tasks").Count() + if err != nil { + return nil, err + } + teams, err := s.Table("teams").Count() + if err != nil { + return nil, err + } + linkShares, err := s.Table("link_shares").Count() + if err != nil { + return nil, err + } + teamShares, err := s.Table("team_projects").Count() + if err != nil { + return nil, err + } + userShares, err := s.Table("users_projects").Count() + if err != nil { + return nil, err + } + + return &Overview{ + Users: users, + Projects: projects, + Tasks: tasks, + Teams: teams, + Shares: ShareCounts{ + LinkShares: linkShares, + TeamShares: teamShares, + UserShares: userShares, + }, + License: license.CurrentInfo(), + }, nil +} diff --git a/pkg/models/admin_user_actions.go b/pkg/models/admin_user_actions.go new file mode 100644 index 000000000..9918eaafb --- /dev/null +++ b/pkg/models/admin_user_actions.go @@ -0,0 +1,106 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package models + +import ( + "code.vikunja.io/api/pkg/user" + + "xorm.io/xorm" +) + +// loadAdminTargetUser fetches a user by ID for the admin actions, returning +// ErrUserDoesNotExist for an invalid ID or a missing row. +func loadAdminTargetUser(s *xorm.Session, id int64) (*user.User, error) { + if id < 1 { + return nil, user.ErrUserDoesNotExist{UserID: id} + } + target := &user.User{ID: id} + has, err := s.Get(target) + if err != nil { + return nil, err + } + if !has { + return nil, user.ErrUserDoesNotExist{UserID: id} + } + return target, nil +} + +// SetUserAdminFlag sets a user's instance-admin flag. Demoting the last +// reachable admin is refused via GuardLastAdmin. It does not commit; the caller +// owns the transaction. +func SetUserAdminFlag(s *xorm.Session, id int64, isAdmin bool) (*user.User, error) { + target, err := loadAdminTargetUser(s, id) + if err != nil { + return nil, err + } + + if !isAdmin { + if err := user.GuardLastAdmin(s, target); err != nil { + return nil, err + } + } + + target.IsAdmin = isAdmin + if _, err := s.ID(target.ID).Cols("is_admin").Update(target); err != nil { + return nil, err + } + return target, nil +} + +// SetUserStatusAsAdmin sets a user's account status. Moving the last reachable +// admin out of Active is refused via GuardLastAdmin (any non-Active status +// blocks login, so it is equivalent to demotion). It does not commit; the caller +// owns the transaction. +func SetUserStatusAsAdmin(s *xorm.Session, id int64, status user.Status) (*user.User, error) { + target, err := loadAdminTargetUser(s, id) + if err != nil { + return nil, err + } + + if target.IsAdmin && status != user.StatusActive { + if err := user.GuardLastAdmin(s, target); err != nil { + return nil, err + } + } + + if err := user.SetUserStatus(s, target, status); err != nil { + return nil, err + } + // Reflect the change on the returned struct; GetUserByID refuses disabled accounts. + target.Status = status + return target, nil +} + +// DeleteUserAsAdmin removes a user. mode "now" deletes immediately; any other +// value triggers the email-confirmation self-deletion flow. Deleting the last +// reachable admin is refused via GuardLastAdmin. It does not commit; the caller +// owns the transaction. +func DeleteUserAsAdmin(s *xorm.Session, id int64, mode string) error { + target, err := loadAdminTargetUser(s, id) + if err != nil { + return err + } + + if err := user.GuardLastAdmin(s, target); err != nil { + return err + } + + if mode == "now" { + return DeleteUser(s, target) + } + return user.RequestDeletion(s, target) +} diff --git a/pkg/models/admin_user_create.go b/pkg/models/admin_user_create.go new file mode 100644 index 000000000..a54d328a0 --- /dev/null +++ b/pkg/models/admin_user_create.go @@ -0,0 +1,80 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package models + +import ( + "code.vikunja.io/api/pkg/config" + "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/user" + + "xorm.io/xorm" +) + +// CreateUserBody wraps user.APIUserPassword with admin-only fields. +type CreateUserBody struct { + // The full name of the new user. Optional. + Name string `json:"name" doc:"The full name of the new user. Optional."` + // The language of the new user. Must be a valid IETF BCP 47 language code and exist in Vikunja. + Language string `json:"language" valid:"language" doc:"IETF BCP 47 language code; must exist in Vikunja."` + user.APIUserPassword + // Mark the new user as an instance admin. + IsAdmin bool `json:"is_admin" doc:"Mark the new user as an instance admin."` + // Activate the new user immediately without email confirmation. + SkipEmailConfirm bool `json:"skip_email_confirm" doc:"Activate the new user immediately, skipping email confirmation."` +} + +// CreateUserAsAdmin provisions a new local account on behalf of an instance admin, +// honouring the admin-only is_admin and skip_email_confirm fields and bypassing the +// public-registration toggle. It commits s and returns the persisted user reloaded +// so the status reflects what was actually stored. +func CreateUserAsAdmin(s *xorm.Session, body *CreateUserBody) (*user.User, error) { + newUser, err := RegisterUser(s, &user.User{ + Username: body.Username, + Password: body.Password, + Email: body.Email, + Name: body.Name, + Language: body.Language, + }) + if err != nil { + return nil, err + } + + if body.IsAdmin { + if _, err := s.ID(newUser.ID).Cols("is_admin").Update(&user.User{IsAdmin: true}); err != nil { + return nil, err + } + newUser.IsAdmin = true + } + + // Force Active when the admin asked to skip, or when no mailer exists to send the confirmation. + if body.SkipEmailConfirm || !config.MailerEnabled.GetBool() { + if err := user.SetUserStatus(s, newUser, user.StatusActive); err != nil { + return nil, err + } + newUser.Status = user.StatusActive + } + + if err := s.Commit(); err != nil { + return nil, err + } + + // Reload on a fresh session so the returned status reflects what was actually + // persisted (e.g. StatusEmailConfirmationRequired on mail-enabled instances). + rs := db.NewSession() + defer rs.Close() + return user.GetUserByID(rs, newUser.ID) +} diff --git a/pkg/models/api_routes.go b/pkg/models/api_routes.go index 0f8fbcbeb..24a3c747b 100644 --- a/pkg/models/api_routes.go +++ b/pkg/models/api_routes.go @@ -27,14 +27,27 @@ import ( var apiTokenRoutes = map[string]APITokenRoute{} +// apiTokenRoutesV2 holds /api/v2 routes under the same (group, permission) +// keys as v1, so a token granted e.g. labels.read_one authorises both +// versions. CanDoAPIRoute consults both tables; GetAPITokenRoutes (the /routes +// exposure the frontend reads) merges v2-only groups so they're discoverable. +var apiTokenRoutesV2 = map[string]APITokenRoute{} + func init() { apiTokenRoutes = make(map[string]APITokenRoute) + apiTokenRoutesV2 = make(map[string]APITokenRoute) apiTokenRoutes["caldav"] = APITokenRoute{ "access": &RouteDetail{ Path: "/dav/*", Method: "ANY", }, } + apiTokenRoutes["feeds"] = APITokenRoute{ + "access": &RouteDetail{ + Path: "/feeds/*", + Method: "GET", + }, + } } type APITokenRoute map[string]*RouteDetail @@ -44,8 +57,25 @@ type RouteDetail struct { Method string `json:"method"` } +// isV2Path reports whether the given route path lives under /api/v2. +func isV2Path(path string) bool { + return strings.HasPrefix(path, "/api/v2/") || path == "/api/v2" +} + +// stripAPIVersion removes the /api/v1/ or /api/v2/ prefix so both +// versions normalise to the same token-permission group name. +func stripAPIVersion(path string) string { + if stripped := strings.TrimPrefix(path, "/api/v1/"); stripped != path { + return stripped + } + if stripped := strings.TrimPrefix(path, "/api/v2/"); stripped != path { + return stripped + } + return path +} + func getRouteGroupName(path string) (finalName string, filteredParts []string) { - parts := strings.Split(strings.TrimPrefix(path, "/api/v1/"), "/") + parts := strings.Split(stripAPIVersion(path), "/") filteredParts = []string{} for _, part := range parts { if strings.HasPrefix(part, ":") { @@ -69,6 +99,9 @@ func getRouteGroupName(path string) (finalName string, filteredParts []string) { // getRouteDetail determines the API permission type from the route's HTTP method and path. // In Echo v5, route.Name is auto-generated as METHOD:PATH, so we derive permissions from // the HTTP method and path structure instead of the handler function name. +// +// v1 and v2 have inverted create/update verbs: v1 uses PUT for create and POST +// for update, v2 follows REST conventions (POST create, PUT/PATCH update). func getRouteDetail(route echo.RouteInfo) (method string, detail *RouteDetail) { detail = &RouteDetail{ Path: route.Path, @@ -82,6 +115,7 @@ func getRouteDetail(route echo.RouteInfo) (method string, detail *RouteDetail) { lastPart = pathParts[len(pathParts)-1] } endsWithParam := strings.HasPrefix(lastPart, ":") + v2 := isV2Path(route.Path) switch route.Method { case http.MethodGet: @@ -90,10 +124,21 @@ func getRouteDetail(route echo.RouteInfo) (method string, detail *RouteDetail) { } return "read_all", detail case http.MethodPut: - // PUT is used for creating resources in this codebase + if v2 { + // v2: PUT replaces an existing resource → update. + return "update", detail + } + // v1: PUT is used for creating resources. return "create", detail case http.MethodPost: - // POST is used for updating resources + if v2 { + // v2: POST creates a new resource on the collection. + return "create", detail + } + // v1: POST is used for updating resources. + return "update", detail + case http.MethodPatch: + // Both versions use PATCH for partial updates. return "update", detail case http.MethodDelete: return "delete", detail @@ -102,9 +147,9 @@ func getRouteDetail(route echo.RouteInfo) (method string, detail *RouteDetail) { return "", detail } -func ensureAPITokenRoutesGroup(group string) { - if _, has := apiTokenRoutes[group]; !has { - apiTokenRoutes[group] = make(APITokenRoute) +func ensureAPITokenRoutesGroup(target map[string]APITokenRoute, group string) { + if _, has := target[group]; !has { + target[group] = make(APITokenRoute) } } @@ -138,6 +183,7 @@ func isStandardCRUDRoute(routeGroupName string, routeParts []string, _ string) b "comments": true, "relations": true, "attachments": true, + "time-entries": true, "projects_views": true, "projects_teams": true, "projects_users": true, @@ -177,8 +223,10 @@ func isStandardCRUDRoute(routeGroupName string, routeParts []string, _ string) b return false } -// CollectRoutesForAPITokenUsage gets called for every added APITokenRoute and builds a list of all routes we can use for the api tokens. -// The requiresJWT parameter indicates if this route is protected by JWT authentication. +// CollectRoutesForAPITokenUsage records a route for token authorisation. +// v1 and v2 share group/permission keys derived from the prefix-stripped +// path; v2 entries land in apiTokenRoutesV2 so the v1-only frontend UI is +// unchanged while CanDoAPIRoute consults both tables. func CollectRoutesForAPITokenUsage(route echo.RouteInfo, requiresJWT bool) { if route.Method == "echo_route_not_found" { @@ -199,6 +247,17 @@ func CollectRoutesForAPITokenUsage(route echo.RouteInfo, requiresJWT bool) { return } + target := apiTokenRoutes + if isV2Path(route.Path) { + target = apiTokenRoutesV2 + // AutoPatch's synthesised PATCH and the original PUT both derive the + // "update" permission and would clobber each other on the map. Store + // only PUT; CanDoAPIRoute accepts PATCH as its alias on the same path. + if route.Method == http.MethodPatch { + return + } + } + // Check if this is a standard CRUD route using path-based heuristics // In Echo v5, we can no longer rely on route.Name containing "(*WebHandler)" isCRUD := isStandardCRUDRoute(routeGroupName, routeParts, route.Method) @@ -218,67 +277,67 @@ func CollectRoutesForAPITokenUsage(route echo.RouteInfo, requiresJWT bool) { // Otherwise, we add it to the "other" key. if len(routeParts) == 1 { if routeGroupName == "notifications" && route.Method == http.MethodPost { - ensureAPITokenRoutesGroup("notifications") + ensureAPITokenRoutesGroup(target, "notifications") - apiTokenRoutes["notifications"]["mark_all_as_read"] = routeDetail + target["notifications"]["mark_all_as_read"] = routeDetail return } - ensureAPITokenRoutesGroup("other") + ensureAPITokenRoutesGroup(target, "other") - _, exists := apiTokenRoutes["other"][routeGroupName] + _, exists := target["other"][routeGroupName] if exists { routeGroupName += "_" + strings.ToLower(route.Method) } - apiTokenRoutes["other"][routeGroupName] = routeDetail + target["other"][routeGroupName] = routeDetail return } subkey := strings.Join(routeParts[1:], "_") - if _, has := apiTokenRoutes[routeParts[0]]; !has { - apiTokenRoutes[routeParts[0]] = make(APITokenRoute) + if _, has := target[routeParts[0]]; !has { + target[routeParts[0]] = make(APITokenRoute) } - if _, has := apiTokenRoutes[routeParts[0]][subkey]; has { + if _, has := target[routeParts[0]][subkey]; has { subkey += "_" + strings.ToLower(route.Method) } - apiTokenRoutes[routeParts[0]][subkey] = routeDetail + target[routeParts[0]][subkey] = routeDetail return } if strings.HasSuffix(routeGroupName, "_bulk") { parent := strings.TrimSuffix(routeGroupName, "_bulk") - ensureAPITokenRoutesGroup(parent) + ensureAPITokenRoutesGroup(target, parent) method, routeDetail := getRouteDetail(route) - apiTokenRoutes[parent][method+"_bulk"] = routeDetail + target[parent][method+"_bulk"] = routeDetail return } - _, has := apiTokenRoutes[routeGroupName] + _, has := target[routeGroupName] if !has { - apiTokenRoutes[routeGroupName] = make(APITokenRoute) + target[routeGroupName] = make(APITokenRoute) } method, routeDetail := getRouteDetail(route) if method != "" { - apiTokenRoutes[routeGroupName][method] = routeDetail + target[routeGroupName][method] = routeDetail } // Handle task attachments specially - they use custom handlers not WebHandler if routeGroupName == "tasks_attachments" { // PUT is upload (create), GET with :attachment param is download (read_one) if route.Method == http.MethodPut { - apiTokenRoutes[routeGroupName]["create"] = &RouteDetail{ + target[routeGroupName]["create"] = &RouteDetail{ Path: route.Path, Method: route.Method, } } if route.Method == http.MethodGet && strings.HasSuffix(route.Path, ":attachment") { - apiTokenRoutes[routeGroupName]["read_one"] = &RouteDetail{ + target[routeGroupName]["read_one"] = &RouteDetail{ Path: route.Path, Method: route.Method, } @@ -287,10 +346,30 @@ func CollectRoutesForAPITokenUsage(route echo.RouteInfo, requiresJWT bool) { } -// GetAPITokenRoutes exposes the registered scoped-token routes so tests -// and the /api/v1/routes handler share a single source of truth. +// GetAPITokenRoutes exposes the registered scoped-token routes for the /routes +// handler and tests. v1 is the base; v2-only groups and permissions (a v2-only +// resource like time-entries has no v1 counterpart) are merged in so tokens can +// discover and grant them. Shared (group, permission) keys keep their v1 entry — +// CanDoAPIRoute authorises both versions off the same key regardless. func GetAPITokenRoutes() map[string]APITokenRoute { - return apiTokenRoutes + merged := make(map[string]APITokenRoute, len(apiTokenRoutes)) + for group, perms := range apiTokenRoutes { + merged[group] = make(APITokenRoute, len(perms)) + for perm, rd := range perms { + merged[group][perm] = rd + } + } + for group, perms := range apiTokenRoutesV2 { + if merged[group] == nil { + merged[group] = make(APITokenRoute) + } + for perm, rd := range perms { + if merged[group][perm] == nil { + merged[group][perm] = rd + } + } + } + return merged } // GetAvailableAPIRoutesForToken returns a list of all API routes which are available for token usage. @@ -311,6 +390,10 @@ func GetAvailableAPIRoutesForToken(c *echo.Context) error { // stored (Path, Method) for that permission matches exactly. This closes // GHSA-v479-vf79-mg83 and the wider method/sub-resource confusion it // enabled. The one exception is the tasks.read_all quirk handled below. +// One (group, permission) pair can legitimately match both v1 and v2 +// routes; we walk apiTokenRoutes and apiTokenRoutesV2 in turn. On v2, +// PATCH is accepted as an alias for the stored PUT on the same path +// (AutoPatch collapses both onto the "update" permission). func CanDoAPIRoute(c *echo.Context, token *APIToken) (can bool) { path := c.Path() if path == "" { @@ -321,23 +404,32 @@ func CanDoAPIRoute(c *echo.Context, token *APIToken) (can bool) { method := c.Request().Method for group, perms := range token.APIPermissions { - routes, has := apiTokenRoutes[group] - if !has { - continue - } - for _, p := range perms { - rd := routes[p] - if rd == nil { + tables := []APITokenRoute{apiTokenRoutes[group], apiTokenRoutesV2[group]} + for _, routes := range tables { + if routes == nil { continue } - if rd.Method == method && rd.Path == path { - return true - } - // Two list endpoints share tasks.read_all but only one - // survives collection, so allow either explicitly. - if group == "tasks" && p == "read_all" && method == http.MethodGet && - (path == "/api/v1/tasks" || path == "/api/v1/projects/:project/tasks") { - return true + for _, p := range perms { + rd := routes[p] + if rd == nil { + continue + } + if rd.Method == method && rd.Path == path { + return true + } + // v2: AutoPatch mirrors every PUT as a PATCH on the same + // path. PATCH isn't stored (it would clobber PUT under + // the same "update" key), so accept it as an alias here. + if isV2Path(rd.Path) && rd.Method == http.MethodPut && + method == http.MethodPatch && rd.Path == path { + return true + } + // Two list endpoints share tasks.read_all but only one + // survives collection, so allow either explicitly. + if group == "tasks" && p == "read_all" && method == http.MethodGet && + (path == "/api/v1/tasks" || path == "/api/v1/projects/:project/tasks") { + return true + } } } } @@ -351,15 +443,20 @@ func CanDoAPIRoute(c *echo.Context, token *APIToken) (can bool) { func PermissionsAreValid(permissions APIPermissions) (err error) { for key, methods := range permissions { - routes, has := apiTokenRoutes[key] - if !has { + // A permission is valid if the group exists in either table. v2-only + // resources (no v1 counterpart) live solely in apiTokenRoutesV2, so + // validating against the union lets tokens grant them. CanDoAPIRoute + // already consults both tables when authorising. + v1Routes := apiTokenRoutes[key] + v2Routes := apiTokenRoutesV2[key] + if v1Routes == nil && v2Routes == nil { return &ErrInvalidAPITokenPermission{ Group: key, } } for _, method := range methods { - if routes[method] == nil { + if v1Routes[method] == nil && v2Routes[method] == nil { return &ErrInvalidAPITokenPermission{ Group: key, Permission: method, diff --git a/pkg/models/api_routes_test.go b/pkg/models/api_routes_test.go index 3dd9e7d99..5cc510a98 100644 --- a/pkg/models/api_routes_test.go +++ b/pkg/models/api_routes_test.go @@ -17,6 +17,7 @@ package models import ( + "net/http/httptest" "testing" "github.com/labstack/echo/v5" @@ -54,3 +55,197 @@ func TestCanDoAPIRoute_BulkLabelTask(t *testing.T) { assert.Equal(t, "/api/v1/tasks/:projecttask/labels/bulk", bulkRoute.Path) assert.Equal(t, "POST", bulkRoute.Method) } + +func TestIsV2Path(t *testing.T) { + cases := map[string]bool{ + "/api/v2": true, + "/api/v2/": true, + "/api/v2/labels": true, + "/api/v1/labels": false, + "/api/v1/api/v2": false, // prefix is authoritative + "": false, + "/api/v20/labels": false, // only exact /api/v2 prefix counts + "/api/v2labels": false, + } + for path, want := range cases { + t.Run(path, func(t *testing.T) { + assert.Equal(t, want, isV2Path(path)) + }) + } +} + +func TestStripAPIVersion(t *testing.T) { + cases := map[string]string{ + "/api/v1/labels": "labels", + "/api/v2/labels": "labels", + "/api/v2/labels/42": "labels/42", + "/api/v1/tasks/bulk": "tasks/bulk", + "/api/v3/labels": "/api/v3/labels", // unknown versions pass through + "/labels": "/labels", + "": "", + } + for path, want := range cases { + t.Run(path, func(t *testing.T) { + assert.Equal(t, want, stripAPIVersion(path)) + }) + } +} + +// TestCollectRoutesV2 verifies that /api/v2 routes are stored in the v2 +// shadow table under the same (group, permission) keys their v1 counterparts +// would use. This is what lets a token scoped on `labels.read_one` authorise +// both /api/v1/labels/{id} and /api/v2/labels/{id}. +func TestCollectRoutesV2(t *testing.T) { + apiTokenRoutes = make(map[string]APITokenRoute) + apiTokenRoutesV2 = make(map[string]APITokenRoute) + + CollectRoutesForAPITokenUsage(echo.RouteInfo{Method: "GET", Path: "/api/v2/labels"}, true) + CollectRoutesForAPITokenUsage(echo.RouteInfo{Method: "GET", Path: "/api/v2/labels/:id"}, true) + CollectRoutesForAPITokenUsage(echo.RouteInfo{Method: "POST", Path: "/api/v2/labels"}, true) + CollectRoutesForAPITokenUsage(echo.RouteInfo{Method: "PUT", Path: "/api/v2/labels/:id"}, true) + CollectRoutesForAPITokenUsage(echo.RouteInfo{Method: "DELETE", Path: "/api/v2/labels/:id"}, true) + CollectRoutesForAPITokenUsage(echo.RouteInfo{Method: "PATCH", Path: "/api/v2/labels/:id"}, true) + + // v1 map stays untouched. + assert.Empty(t, apiTokenRoutes, "v2 routes must not land in the v1 table") + + labels, has := apiTokenRoutesV2["labels"] + require.True(t, has, "labels group should exist in v2 table") + assert.Equal(t, "GET", labels["read_all"].Method) + assert.Equal(t, "/api/v2/labels", labels["read_all"].Path) + assert.Equal(t, "GET", labels["read_one"].Method) + assert.Equal(t, "POST", labels["create"].Method) + // PUT is the authoritative update verb for API tokens — PATCH is + // skipped during collection so it doesn't clobber PUT. + assert.Equal(t, "PUT", labels["update"].Method) + assert.Equal(t, "DELETE", labels["delete"].Method) +} + +// TestCollectRoutes_TimeEntriesV2 verifies the v2-only time-entries resource +// lands under a clean "time-entries" group rather than the "other" catch-all, +// so its scopes read sensibly for token clients. +func TestCollectRoutes_TimeEntriesV2(t *testing.T) { + apiTokenRoutes = make(map[string]APITokenRoute) + apiTokenRoutesV2 = make(map[string]APITokenRoute) + + CollectRoutesForAPITokenUsage(echo.RouteInfo{Method: "GET", Path: "/api/v2/time-entries"}, true) + CollectRoutesForAPITokenUsage(echo.RouteInfo{Method: "GET", Path: "/api/v2/time-entries/:id"}, true) + CollectRoutesForAPITokenUsage(echo.RouteInfo{Method: "POST", Path: "/api/v2/time-entries"}, true) + CollectRoutesForAPITokenUsage(echo.RouteInfo{Method: "PUT", Path: "/api/v2/time-entries/:id"}, true) + CollectRoutesForAPITokenUsage(echo.RouteInfo{Method: "DELETE", Path: "/api/v2/time-entries/:id"}, true) + + _, isOther := apiTokenRoutesV2["other"] + assert.False(t, isOther, "time-entries CRUD must not fall into the 'other' bucket") + + te, has := apiTokenRoutesV2["time-entries"] + require.True(t, has, "time-entries group should exist in the v2 table") + assert.Equal(t, "GET", te["read_all"].Method) + assert.Equal(t, "/api/v2/time-entries", te["read_all"].Path) + assert.Equal(t, "GET", te["read_one"].Method) + assert.Equal(t, "POST", te["create"].Method) + assert.Equal(t, "PUT", te["update"].Method) + assert.Equal(t, "DELETE", te["delete"].Method) +} + +// TestGetAPITokenRoutes_ExposesV2Only verifies the /routes payload merges +// v2-only groups (time-entries has no v1 counterpart) so token clients can +// discover and grant them, without mutating the v1 table itself. +func TestGetAPITokenRoutes_ExposesV2Only(t *testing.T) { + apiTokenRoutes = make(map[string]APITokenRoute) + apiTokenRoutesV2 = make(map[string]APITokenRoute) + + CollectRoutesForAPITokenUsage(echo.RouteInfo{Method: "GET", Path: "/api/v1/labels"}, true) + CollectRoutesForAPITokenUsage(echo.RouteInfo{Method: "GET", Path: "/api/v2/time-entries"}, true) + + routes := GetAPITokenRoutes() + + _, hasLabels := routes["labels"] + assert.True(t, hasLabels, "v1 groups stay exposed") + + te, hasTE := routes["time-entries"] + require.True(t, hasTE, "v2-only time-entries must be exposed via /routes") + assert.Equal(t, "GET", te["read_all"].Method) + + _, v1HasTE := apiTokenRoutes["time-entries"] + assert.False(t, v1HasTE, "the merge must not mutate the v1 table") +} + +// TestGetRouteDetail_V2Verbs verifies the v2 verb mapping: POST→create, +// PUT/PATCH→update. v1 inverts POST and PUT so we need a separate mapping +// path. +func TestGetRouteDetail_V2Verbs(t *testing.T) { + cases := []struct { + method, path, wantPerm string + }{ + {"GET", "/api/v2/labels", "read_all"}, + {"GET", "/api/v2/labels/:id", "read_one"}, + {"POST", "/api/v2/labels", "create"}, + {"PUT", "/api/v2/labels/:id", "update"}, + {"PATCH", "/api/v2/labels/:id", "update"}, + {"DELETE", "/api/v2/labels/:id", "delete"}, + } + for _, c := range cases { + t.Run(c.method+" "+c.path, func(t *testing.T) { + perm, _ := getRouteDetail(echo.RouteInfo{Method: c.method, Path: c.path}) + assert.Equal(t, c.wantPerm, perm) + }) + } +} + +// TestCanDoAPIRoute_V2PatchAliasesPut verifies that a token granted the +// "update" permission on a v2 resource can issue PATCH requests against +// the same path as the stored PUT route. Huma's AutoPatch synthesises +// PATCH for every PUT — the matcher accepts it as an alias so token +// holders aren't forced to use PUT exclusively. +func TestCanDoAPIRoute_V2PatchAliasesPut(t *testing.T) { + apiTokenRoutes = make(map[string]APITokenRoute) + apiTokenRoutesV2 = make(map[string]APITokenRoute) + apiTokenRoutes["caldav"] = APITokenRoute{ + "access": &RouteDetail{Path: "/dav/*", Method: "ANY"}, + } + + CollectRoutesForAPITokenUsage(echo.RouteInfo{Method: "PUT", Path: "/api/v2/labels/:id"}, true) + CollectRoutesForAPITokenUsage(echo.RouteInfo{Method: "PATCH", Path: "/api/v2/labels/:id"}, true) + + token := &APIToken{ + APIPermissions: APIPermissions{"labels": []string{"update"}}, + } + + e := echo.New() + + t.Run("PUT is allowed (stored verb)", func(t *testing.T) { + req := httptest.NewRequest("PUT", "/api/v2/labels/:id", nil) + c := e.NewContext(req, httptest.NewRecorder()) + assert.True(t, CanDoAPIRoute(c, token)) + }) + + t.Run("PATCH is allowed via alias", func(t *testing.T) { + req := httptest.NewRequest("PATCH", "/api/v2/labels/:id", nil) + c := e.NewContext(req, httptest.NewRecorder()) + assert.True(t, CanDoAPIRoute(c, token)) + }) + + t.Run("PATCH on a different path is rejected", func(t *testing.T) { + req := httptest.NewRequest("PATCH", "/api/v2/projects/:id", nil) + c := e.NewContext(req, httptest.NewRecorder()) + assert.False(t, CanDoAPIRoute(c, token)) + }) + + t.Run("v1 PATCH stays rejected", func(t *testing.T) { + // The alias must not bleed onto v1 — v1 has no AutoPatch and + // never registers PATCH on update routes. + apiTokenRoutes["labels"] = APITokenRoute{ + "update": &RouteDetail{Path: "/api/v1/labels/:id", Method: "POST"}, + } + v1Token := &APIToken{ + APIPermissions: APIPermissions{"labels": []string{"update"}}, + } + req := httptest.NewRequest("PATCH", "/api/v1/labels/:id", nil) + c := e.NewContext(req, httptest.NewRecorder()) + assert.False(t, CanDoAPIRoute(c, v1Token)) + }) +} + +// End-to-end CanDoAPIRoute coverage for /api/v2 is provided by the Label +// integration test in pkg/webtests/huma_label_test.go (see the token-auth +// scenarios in that file) which exercises the full auth pipeline. diff --git a/pkg/models/api_tokens.go b/pkg/models/api_tokens.go index 3664f1b3a..7739184fb 100644 --- a/pkg/models/api_tokens.go +++ b/pkg/models/api_tokens.go @@ -24,6 +24,7 @@ import ( "time" "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/events" "code.vikunja.io/api/pkg/user" "code.vikunja.io/api/pkg/utils" "code.vikunja.io/api/pkg/web" @@ -37,26 +38,26 @@ type APIPermissions map[string][]string type APIToken struct { // The unique, numeric id of this api key. - ID int64 `xorm:"bigint autoincr not null unique pk" json:"id" param:"token"` + ID int64 `xorm:"bigint autoincr not null unique pk" json:"id" param:"token" readOnly:"true" doc:"The unique, numeric id of this api key."` // A human-readable name for this token - Title string `xorm:"not null" json:"title" valid:"required"` + Title string `xorm:"not null" json:"title" valid:"required" minLength:"1" doc:"A human-readable name for this token."` // The actual api key. Only visible after creation. - Token string `xorm:"-" json:"token,omitempty"` + Token string `xorm:"-" json:"token,omitempty" readOnly:"true" doc:"The cleartext api key. Returned only once, in the response to creating the token; never readable again."` TokenSalt string `xorm:"not null" json:"-"` TokenHash string `xorm:"not null unique" json:"-"` TokenLastEight string `xorm:"not null index varchar(8)" json:"-"` // The permissions this token has. Possible values are available via the /routes endpoint and consist of the keys of the list from that endpoint. For example, if the token should be able to read all tasks as well as update existing tasks, you should add `{"tasks":["read_all","update"]}`. - APIPermissions APIPermissions `xorm:"json not null permissions" json:"permissions" valid:"required"` + APIPermissions APIPermissions `xorm:"json not null permissions" json:"permissions" valid:"required" doc:"The permissions this token has. Possible values are available via the /routes endpoint and consist of the keys of the list from that endpoint. For example, if the token should be able to read all tasks as well as update existing tasks, you should add {\"tasks\":[\"read_all\",\"update\"]}."` // The date when this key expires. - ExpiresAt time.Time `xorm:"not null" json:"expires_at" valid:"required"` + ExpiresAt time.Time `xorm:"not null" json:"expires_at" valid:"required" doc:"The date when this key expires."` // A timestamp when this api key was created. You cannot change this value. - Created time.Time `xorm:"created not null" json:"created"` + Created time.Time `xorm:"created not null" json:"created" readOnly:"true" doc:"A timestamp when this api key was created. You cannot change this value."` // The user ID of the token owner. When creating a token for a bot user, set this // to the bot's ID. If omitted, defaults to the authenticated user. - OwnerID int64 `xorm:"bigint not null" json:"owner_id,omitempty" query:"owner_id"` + OwnerID int64 `xorm:"bigint not null" json:"owner_id,omitempty" query:"owner_id" doc:"The user ID of the token owner. When creating a token for a bot user, set this to the bot's ID; the bot must be owned by the authenticated user. If omitted, defaults to the authenticated user."` web.Permissions `xorm:"-" json:"-"` web.CRUDable `xorm:"-" json:"-"` @@ -121,7 +122,17 @@ func (t *APIToken) Create(s *xorm.Session, a web.Auth) (err error) { } _, err = s.Insert(t) - return err + if err != nil { + return err + } + + events.DispatchOnCommit(s, &APITokenIssuedEvent{ + TokenID: t.ID, + DoerID: a.GetID(), + OwnerID: t.OwnerID, + }) + + return nil } func HashToken(token, salt string) string { @@ -192,10 +203,19 @@ func (t *APIToken) ReadAll(s *xorm.Session, a web.Auth, search string, page int, // @Failure 404 {object} web.HTTPError "The token does not exist." // @Failure 500 {object} models.Message "Internal error" // @Router /tokens/{tokenID} [delete] -func (t *APIToken) Delete(s *xorm.Session, _ web.Auth) (err error) { +func (t *APIToken) Delete(s *xorm.Session, a web.Auth) (err error) { // Ownership is verified in CanDelete; delete by ID only. _, err = s.Where("id = ?", t.ID).Delete(&APIToken{}) - return err + if err != nil { + return err + } + + events.DispatchOnCommit(s, &APITokenRevokedEvent{ + TokenID: t.ID, + DoerID: a.GetID(), + }) + + return nil } // HasCaldavAccess checks whether the token has the caldav access permission. @@ -207,6 +227,15 @@ func (t *APIToken) HasCaldavAccess() bool { return slices.Contains(perms, "access") } +// HasFeedsAccess checks whether the token has the feeds access permission. +func (t *APIToken) HasFeedsAccess() bool { + perms, has := t.APIPermissions["feeds"] + if !has { + return false + } + return slices.Contains(perms, "access") +} + // GetTokenFromTokenString returns the full token object from the original token string. func GetTokenFromTokenString(s *xorm.Session, token string) (apiToken *APIToken, err error) { lastEight := token[len(token)-8:] diff --git a/pkg/models/api_tokens_expiry_notification.go b/pkg/models/api_tokens_expiry_notification.go index 8a84d90eb..f99ef435f 100644 --- a/pkg/models/api_tokens_expiry_notification.go +++ b/pkg/models/api_tokens_expiry_notification.go @@ -23,15 +23,23 @@ import ( "code.vikunja.io/api/pkg/user" ) +func init() { + notifications.Register(func() notifications.Notification { return &APITokenExpiringWeekNotification{} }) + notifications.Register(func() notifications.Notification { return &APITokenExpiringDayNotification{} }) +} + // APITokenExpiringWeekNotification is sent 7 days before an API token expires. type APITokenExpiringWeekNotification struct { User *user.User `json:"user"` Token *APIToken `json:"api_token"` } +func (n *APITokenExpiringWeekNotification) ToTitle(lang string) string { + return i18n.T(lang, "notifications.api_token.expiring.week.subject", n.Token.Title) +} + func (n *APITokenExpiringWeekNotification) ToMail(lang string) *notifications.Mail { return notifications.NewMail(). - Subject(i18n.T(lang, "notifications.api_token.expiring.week.subject", n.Token.Title)). Greeting(i18n.T(lang, "notifications.greeting", n.User.GetName())). Line(i18n.T(lang, "notifications.api_token.expiring.week.message", notifications.EscapeMarkdown(n.Token.Title), n.Token.ExpiresAt.Format("2006-01-02"))). Action(i18n.T(lang, "notifications.api_token.expiring.action"), config.ServicePublicURL.GetString()+"user/settings/api-tokens"). @@ -56,9 +64,12 @@ type APITokenExpiringDayNotification struct { Token *APIToken `json:"api_token"` } +func (n *APITokenExpiringDayNotification) ToTitle(lang string) string { + return i18n.T(lang, "notifications.api_token.expiring.day.subject", n.Token.Title) +} + func (n *APITokenExpiringDayNotification) ToMail(lang string) *notifications.Mail { return notifications.NewMail(). - Subject(i18n.T(lang, "notifications.api_token.expiring.day.subject", n.Token.Title)). Greeting(i18n.T(lang, "notifications.greeting", n.User.GetName())). Line(i18n.T(lang, "notifications.api_token.expiring.day.message", notifications.EscapeMarkdown(n.Token.Title), n.Token.ExpiresAt.Format("2006-01-02"))). Action(i18n.T(lang, "notifications.api_token.expiring.action"), config.ServicePublicURL.GetString()+"user/settings/api-tokens"). diff --git a/pkg/models/api_tokens_test.go b/pkg/models/api_tokens_test.go index 6677c09fa..261de26bd 100644 --- a/pkg/models/api_tokens_test.go +++ b/pkg/models/api_tokens_test.go @@ -125,6 +125,36 @@ func TestAPIToken_HasCaldavAccess(t *testing.T) { }) } +func TestAPIToken_HasFeedsAccess(t *testing.T) { + t.Run("has feeds access", func(t *testing.T) { + token := &APIToken{ + APIPermissions: APIPermissions{"feeds": {"access"}}, + } + assert.True(t, token.HasFeedsAccess()) + }) + t.Run("no feeds group", func(t *testing.T) { + token := &APIToken{ + APIPermissions: APIPermissions{"tasks": {"read_all"}}, + } + assert.False(t, token.HasFeedsAccess()) + }) + t.Run("feeds group but wrong permission", func(t *testing.T) { + token := &APIToken{ + APIPermissions: APIPermissions{"feeds": {"read_all"}}, + } + assert.False(t, token.HasFeedsAccess()) + }) + t.Run("feeds access among other permissions", func(t *testing.T) { + token := &APIToken{ + APIPermissions: APIPermissions{ + "tasks": {"read_all", "update"}, + "feeds": {"access"}, + }, + } + assert.True(t, token.HasFeedsAccess()) + }) +} + func TestAPIToken_GetTokenFromTokenString(t *testing.T) { t.Run("valid token", func(t *testing.T) { s := db.NewSession() diff --git a/pkg/models/bot_users.go b/pkg/models/bot_users.go index efc544987..d2d22ed11 100644 --- a/pkg/models/bot_users.go +++ b/pkg/models/bot_users.go @@ -31,7 +31,7 @@ import ( type BotUser struct { // Status shadows user.User.Status so it is included in JSON responses // (the original has json:"-"). - Status user.Status `xorm:"-" json:"status"` + Status user.Status `xorm:"-" json:"status" doc:"The bot's status: 0=active, 2=disabled. Set to 2 to disable the bot, 0 to re-enable it."` user.User `xorm:"extends"` diff --git a/pkg/models/bulk_task.go b/pkg/models/bulk_task.go index c103bc713..a0e294422 100644 --- a/pkg/models/bulk_task.go +++ b/pkg/models/bulk_task.go @@ -24,10 +24,10 @@ import ( // BulkTask represents a bulk task update payload. type BulkTask struct { - TaskIDs []int64 `json:"task_ids"` - Fields []string `json:"fields"` - Values *Task `json:"values"` - Tasks []*Task `json:"tasks,omitempty"` + TaskIDs []int64 `json:"task_ids" doc:"The ids of the tasks to update. The user needs write access to every project these tasks belong to, or the whole request is rejected."` + Fields []string `json:"fields" doc:"The names of the task fields to apply from values; only these fields are written, the rest of each task is left untouched."` + Values *Task `json:"values" doc:"The task carrying the values to set. Only the fields named in fields are read from it and applied to every task."` + Tasks []*Task `json:"tasks,omitempty" readOnly:"true" doc:"The updated tasks, returned in the response."` web.CRUDable `xorm:"-" json:"-"` web.Permissions `xorm:"-" json:"-"` diff --git a/pkg/models/comment_quotes.go b/pkg/models/comment_quotes.go new file mode 100644 index 000000000..808895b0c --- /dev/null +++ b/pkg/models/comment_quotes.go @@ -0,0 +1,124 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package models + +import ( + "strconv" + "strings" + + "code.vikunja.io/api/pkg/user" + + "golang.org/x/net/html" + "xorm.io/xorm" +) + +// extractQuotedCommentIDs parses HTML and returns the set of positive integer +// comment ids referenced by
nodes anywhere in +// the document. Malformed values and zero/negative ids are silently skipped. +func extractQuotedCommentIDs(htmlText string) []int64 { + if htmlText == "" { + return nil + } + + doc, err := html.Parse(strings.NewReader(htmlText)) + if err != nil { + return nil + } + + ids := []int64{} + seen := make(map[int64]bool) + + var traverse func(*html.Node) + traverse = func(n *html.Node) { + if n.Type == html.ElementNode && n.Data == "blockquote" { + for _, attr := range n.Attr { + if attr.Key != "data-comment-id" { + continue + } + id, err := strconv.ParseInt(strings.TrimSpace(attr.Val), 10, 64) + if err == nil && id > 0 && !seen[id] { + ids = append(ids, id) + seen[id] = true + } + break + } + } + for c := n.FirstChild; c != nil; c = c.NextSibling { + traverse(c) + } + } + traverse(doc) + return ids +} + +// findQuotedCommentAuthors resolves the authors of comments referenced via +//
within text, restricted to comments on the +// given task and excluding the doer themselves. Missing or wrong-task +// references are silently skipped. Link-share authors (negative ids) are +// also skipped since they cannot receive notifications. +func findQuotedCommentAuthors(s *xorm.Session, taskID, doerID int64, text string) (map[int64]*user.User, error) { + ids := extractQuotedCommentIDs(text) + if len(ids) == 0 { + return nil, nil + } + + rows := []*TaskComment{} + err := s. + In("id", ids). + And("task_id = ?", taskID). + Cols("id", "author_id", "task_id"). + Find(&rows) + if err != nil { + return nil, err + } + + if len(rows) == 0 { + return nil, nil + } + + dedup := map[int64]bool{} + wanted := []int64{} + for _, r := range rows { + id := r.AuthorID + if id == doerID { + continue + } + if id <= 0 { + continue + } + if dedup[id] { + continue + } + dedup[id] = true + wanted = append(wanted, id) + } + if len(wanted) == 0 { + return nil, nil + } + + list := []*user.User{} + err = s.In("id", wanted).Find(&list) + if err != nil { + return nil, err + } + + users := make(map[int64]*user.User, len(list)) + for _, u := range list { + users[u.ID] = u + } + return users, nil +} diff --git a/pkg/models/comment_quotes_test.go b/pkg/models/comment_quotes_test.go new file mode 100644 index 000000000..e45068e61 --- /dev/null +++ b/pkg/models/comment_quotes_test.go @@ -0,0 +1,344 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package models + +import ( + "fmt" + "testing" + + "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/events" + "code.vikunja.io/api/pkg/user" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "xorm.io/builder" +) + +func TestExtractQuotedCommentIDs(t *testing.T) { + cases := []struct { + name string + in string + out []int64 + }{ + {"empty", "", nil}, + {"no blockquote", `

hello

`, []int64{}}, + {"plain blockquote without attr", `
hi
`, []int64{}}, + {"single attributed quote", `
hi
`, []int64{42}}, + { + "nested inside paragraph", + `

hi

`, + []int64{7}, + }, + { + "two quotes - deduped order preserved", + `
a
b
a again
`, + []int64{3, 5}, + }, + {"malformed - non-numeric", `
hi
`, []int64{}}, + {"malformed - negative", `
hi
`, []int64{}}, + {"malformed - zero", `
hi
`, []int64{}}, + {"malformed - empty", `
hi
`, []int64{}}, + { + "nested blockquote inside blockquote", + `
inner
`, + []int64{9, 8}, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := extractQuotedCommentIDs(c.in) + if c.out == nil { + assert.Nil(t, got) + return + } + assert.Equal(t, c.out, got) + }) + } +} + +// notifQuery builds a where clause matching a TaskCommentNotification for a +// given subject + recipient. +func notifQuery(subjectID, userID int64) builder.Cond { + return builder.And( + builder.Eq{"subject_id": subjectID}, + builder.Eq{"notifiable_id": userID}, + builder.Eq{"name": (&TaskCommentNotification{}).Name()}, + ) +} + +func TestTaskComment_CommentReplies_Notifications(t *testing.T) { + doer := &user.User{ID: 1} + + // task 32 is owned by user 1 (the doer) on project 3. + // user 2 has access to project 3 (this is exercised by the existing + // "should send notifications for comment mentions" test). + + t.Run("blockquote pointing at a same-task comment authored by another user notifies that user once", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + + // existing comment by user 2 on task 32 + parent := &TaskComment{Comment: "original", TaskID: 32, AuthorID: 2} + _, err := s.Insert(parent) + require.NoError(t, err) + + task, err := GetTaskByIDSimple(s, 32) + require.NoError(t, err) + + tc := &TaskComment{ + Comment: fmt.Sprintf(`
original

thanks!

`, parent.ID), + TaskID: 32, + } + err = tc.Create(s, doer) + require.NoError(t, err) + require.NoError(t, s.Commit()) + + events.TestListener(t, &TaskCommentCreatedEvent{ + Task: &task, + Doer: doer, + Comment: tc, + }, &SendTaskCommentNotification{}) + + db.AssertCount(t, "notifications", notifQuery(tc.ID, 2), 1) + }) + + t.Run("blockquote and @mention referring to the same user still result in exactly one notification", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + + parent := &TaskComment{Comment: "original", TaskID: 32, AuthorID: 2} + _, err := s.Insert(parent) + require.NoError(t, err) + + task, err := GetTaskByIDSimple(s, 32) + require.NoError(t, err) + + tc := &TaskComment{ + Comment: fmt.Sprintf( + `

@user2

original
`, + parent.ID, + ), + TaskID: 32, + } + err = tc.Create(s, doer) + require.NoError(t, err) + require.NoError(t, s.Commit()) + + events.TestListener(t, &TaskCommentCreatedEvent{ + Task: &task, + Doer: doer, + Comment: tc, + }, &SendTaskCommentNotification{}) + + db.AssertCount(t, "notifications", notifQuery(tc.ID, 2), 1) + }) + + t.Run("two blockquotes pointing at comments by two different users each notify their author once", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + + parentB := &TaskComment{Comment: "by B", TaskID: 32, AuthorID: 2} + _, err := s.Insert(parentB) + require.NoError(t, err) + parentC := &TaskComment{Comment: "by C", TaskID: 32, AuthorID: 3} + _, err = s.Insert(parentC) + require.NoError(t, err) + + task, err := GetTaskByIDSimple(s, 32) + require.NoError(t, err) + + tc := &TaskComment{ + Comment: fmt.Sprintf( + `
by B
by C
`, + parentB.ID, parentC.ID, + ), + TaskID: 32, + } + err = tc.Create(s, doer) + require.NoError(t, err) + require.NoError(t, s.Commit()) + + events.TestListener(t, &TaskCommentCreatedEvent{ + Task: &task, + Doer: doer, + Comment: tc, + }, &SendTaskCommentNotification{}) + + db.AssertCount(t, "notifications", notifQuery(tc.ID, 2), 1) + db.AssertCount(t, "notifications", notifQuery(tc.ID, 3), 1) + }) + + t.Run("blockquote pointing at a comment on a different task contributes nothing", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + + // user 2 authored a comment on a different task (task 1, owned by user 1). + offTask := &TaskComment{Comment: "elsewhere", TaskID: 1, AuthorID: 2} + _, err := s.Insert(offTask) + require.NoError(t, err) + + task, err := GetTaskByIDSimple(s, 32) + require.NoError(t, err) + + tc := &TaskComment{ + Comment: fmt.Sprintf(`
elsewhere
`, offTask.ID), + TaskID: 32, + } + err = tc.Create(s, doer) + require.NoError(t, err) + require.NoError(t, s.Commit()) + + events.TestListener(t, &TaskCommentCreatedEvent{ + Task: &task, + Doer: doer, + Comment: tc, + }, &SendTaskCommentNotification{}) + + db.AssertMissing(t, "notifications", map[string]interface{}{ + "subject_id": tc.ID, + "notifiable_id": 2, + "name": (&TaskCommentNotification{}).Name(), + }) + }) + + t.Run("blockquote pointing at a missing comment is silently ignored", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + + task, err := GetTaskByIDSimple(s, 32) + require.NoError(t, err) + + tc := &TaskComment{ + Comment: `
missing
`, + TaskID: 32, + } + err = tc.Create(s, doer) + require.NoError(t, err) + require.NoError(t, s.Commit()) + + events.TestListener(t, &TaskCommentCreatedEvent{ + Task: &task, + Doer: doer, + Comment: tc, + }, &SendTaskCommentNotification{}) + + db.AssertMissing(t, "notifications", map[string]interface{}{ + "subject_id": tc.ID, + "name": (&TaskCommentNotification{}).Name(), + }) + }) + + t.Run("blockquote pointing at the replier's own comment does not self-notify", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + + mine := &TaskComment{Comment: "by me", TaskID: 32, AuthorID: 1} + _, err := s.Insert(mine) + require.NoError(t, err) + + task, err := GetTaskByIDSimple(s, 32) + require.NoError(t, err) + + tc := &TaskComment{ + Comment: fmt.Sprintf(`
by me
`, mine.ID), + TaskID: 32, + } + err = tc.Create(s, doer) + require.NoError(t, err) + require.NoError(t, s.Commit()) + + events.TestListener(t, &TaskCommentCreatedEvent{ + Task: &task, + Doer: doer, + Comment: tc, + }, &SendTaskCommentNotification{}) + + db.AssertMissing(t, "notifications", map[string]interface{}{ + "subject_id": tc.ID, + "notifiable_id": 1, + "name": (&TaskCommentNotification{}).Name(), + }) + }) + + t.Run("blockquote with non-integer data-comment-id triggers no DB lookup and no notification", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + + task, err := GetTaskByIDSimple(s, 32) + require.NoError(t, err) + + tc := &TaskComment{ + Comment: `
malformed
`, + TaskID: 32, + } + err = tc.Create(s, doer) + require.NoError(t, err) + require.NoError(t, s.Commit()) + + events.TestListener(t, &TaskCommentCreatedEvent{ + Task: &task, + Doer: doer, + Comment: tc, + }, &SendTaskCommentNotification{}) + + db.AssertMissing(t, "notifications", map[string]interface{}{ + "subject_id": tc.ID, + "name": (&TaskCommentNotification{}).Name(), + }) + }) + + t.Run("blockquote nested deeper in the document is still counted", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + + parent := &TaskComment{Comment: "original", TaskID: 32, AuthorID: 2} + _, err := s.Insert(parent) + require.NoError(t, err) + + task, err := GetTaskByIDSimple(s, 32) + require.NoError(t, err) + + tc := &TaskComment{ + Comment: fmt.Sprintf( + `
deep
`, + parent.ID, + ), + TaskID: 32, + } + err = tc.Create(s, doer) + require.NoError(t, err) + require.NoError(t, s.Commit()) + + events.TestListener(t, &TaskCommentCreatedEvent{ + Task: &task, + Doer: doer, + Comment: tc, + }, &SendTaskCommentNotification{}) + + db.AssertCount(t, "notifications", notifQuery(tc.ID, 2), 1) + }) +} diff --git a/pkg/models/error.go b/pkg/models/error.go index 92c11fd5f..8f1a47553 100644 --- a/pkg/models/error.go +++ b/pkg/models/error.go @@ -131,6 +131,13 @@ func (err ValidationHTTPError) GetHTTPCode() int { return err.HTTPCode } +// GetCode returns Vikunja's numeric domain error code. v2's translateDomainError +// reads it to keep the v1 `code` body contract, since this type does not +// implement web.HTTPErrorProcessor (the embedded field shadows the method name). +func (err ValidationHTTPError) GetCode() int { + return err.Code +} + func InvalidFieldError(fields []string) error { return InvalidFieldErrorWithMessage(fields, "Invalid Data") } @@ -528,6 +535,34 @@ func (err *ErrProjectViewDoesNotExist) HTTPError() web.HTTPError { } } +// ErrProjectHasNoBackground represents an error where a project has no background set. +type ErrProjectHasNoBackground struct { + ProjectID int64 +} + +// IsErrProjectHasNoBackground checks if an error is ErrProjectHasNoBackground. +func IsErrProjectHasNoBackground(err error) bool { + _, ok := err.(*ErrProjectHasNoBackground) + return ok +} + +func (err *ErrProjectHasNoBackground) Error() string { + return fmt.Sprintf("Project has no background [ProjectID: %d]", err.ProjectID) +} + +// ErrCodeProjectHasNoBackground holds the unique world-error code of this error +const ErrCodeProjectHasNoBackground = 3015 + +// HTTPError holds the http error description +func (err *ErrProjectHasNoBackground) HTTPError() web.HTTPError { + return web.HTTPError{ + HTTPCode: http.StatusNotFound, + Code: ErrCodeProjectHasNoBackground, + // Message kept verbatim from v1's inline handler error so the wire body is unchanged. + Message: "Project background not found", + } +} + // ============== // Task errors // ============== @@ -2391,3 +2426,230 @@ func (err *ErrOAuthInvalidGrantType) HTTPError() web.HTTPError { Message: "The grant_type is not supported. Use 'authorization_code' or 'refresh_token'.", } } + +// ==================== +// Time Tracking Errors +// ==================== + +// ErrTimeEntryDoesNotExist represents an error where a time entry does not exist +type ErrTimeEntryDoesNotExist struct { + TimeEntryID int64 +} + +// IsErrTimeEntryDoesNotExist checks if an error is ErrTimeEntryDoesNotExist. +func IsErrTimeEntryDoesNotExist(err error) bool { + _, ok := err.(ErrTimeEntryDoesNotExist) + return ok +} + +func (err ErrTimeEntryDoesNotExist) Error() string { + return fmt.Sprintf("Time entry does not exist [TimeEntryID: %v]", err.TimeEntryID) +} + +// ErrCodeTimeEntryDoesNotExist holds the unique world-error code of this error +const ErrCodeTimeEntryDoesNotExist = 18001 + +// HTTPError holds the http error description +func (err ErrTimeEntryDoesNotExist) HTTPError() web.HTTPError { + return web.HTTPError{ + HTTPCode: http.StatusNotFound, + Code: ErrCodeTimeEntryDoesNotExist, + Message: "This time entry does not exist.", + } +} + +// ErrTimeEntryInvalidContainer represents an error where a time entry is attached +// to both a task and a project, or to neither (violating the XOR invariant). +type ErrTimeEntryInvalidContainer struct { + TaskID int64 + ProjectID int64 +} + +// IsErrTimeEntryInvalidContainer checks if an error is ErrTimeEntryInvalidContainer. +func IsErrTimeEntryInvalidContainer(err error) bool { + _, ok := err.(ErrTimeEntryInvalidContainer) + return ok +} + +func (err ErrTimeEntryInvalidContainer) Error() string { + return fmt.Sprintf("Time entry must be attached to exactly one of task or project [TaskID: %v, ProjectID: %v]", err.TaskID, err.ProjectID) +} + +// ErrCodeTimeEntryInvalidContainer holds the unique world-error code of this error +const ErrCodeTimeEntryInvalidContainer = 18002 + +// HTTPError holds the http error description +func (err ErrTimeEntryInvalidContainer) HTTPError() web.HTTPError { + return web.HTTPError{ + HTTPCode: http.StatusBadRequest, + Code: ErrCodeTimeEntryInvalidContainer, + Message: "A time entry must be attached to exactly one of a task or a project.", + } +} + +// ErrInvalidTimeEntryFilterField represents an error where a time entry filter references a non-filterable field +type ErrInvalidTimeEntryFilterField struct { + Field string +} + +// IsErrInvalidTimeEntryFilterField checks if an error is ErrInvalidTimeEntryFilterField. +func IsErrInvalidTimeEntryFilterField(err error) bool { + _, ok := err.(ErrInvalidTimeEntryFilterField) + return ok +} + +func (err ErrInvalidTimeEntryFilterField) Error() string { + return fmt.Sprintf("Time entry filter field is invalid [Field: %s]", err.Field) +} + +// ErrCodeInvalidTimeEntryFilterField holds the unique world-error code of this error +const ErrCodeInvalidTimeEntryFilterField = 18003 + +// HTTPError holds the http error description +func (err ErrInvalidTimeEntryFilterField) HTTPError() web.HTTPError { + return web.HTTPError{ + HTTPCode: http.StatusBadRequest, + Code: ErrCodeInvalidTimeEntryFilterField, + Message: fmt.Sprintf("The time entry filter field '%s' is invalid. Filterable fields are user_id, task_id, project_id, start_time and end_time.", err.Field), + } +} + +// ErrInvalidTimeEntryFilterValue represents an error where a time entry filter value cannot be parsed for its field +type ErrInvalidTimeEntryFilterValue struct { + Field string + Value string +} + +// IsErrInvalidTimeEntryFilterValue checks if an error is ErrInvalidTimeEntryFilterValue. +func IsErrInvalidTimeEntryFilterValue(err error) bool { + _, ok := err.(ErrInvalidTimeEntryFilterValue) + return ok +} + +func (err ErrInvalidTimeEntryFilterValue) Error() string { + return fmt.Sprintf("Time entry filter value is invalid [Field: %s, Value: %s]", err.Field, err.Value) +} + +// ErrCodeInvalidTimeEntryFilterValue holds the unique world-error code of this error +const ErrCodeInvalidTimeEntryFilterValue = 18004 + +// HTTPError holds the http error description +func (err ErrInvalidTimeEntryFilterValue) HTTPError() web.HTTPError { + return web.HTTPError{ + HTTPCode: http.StatusBadRequest, + Code: ErrCodeInvalidTimeEntryFilterValue, + Message: fmt.Sprintf("The value '%s' is not valid for the time entry filter field '%s'.", err.Value, err.Field), + } +} + +// ErrNoRunningTimer represents an error where a user has no running timer to act on +type ErrNoRunningTimer struct { + UserID int64 +} + +// IsErrNoRunningTimer checks if an error is ErrNoRunningTimer. +func IsErrNoRunningTimer(err error) bool { + _, ok := err.(ErrNoRunningTimer) + return ok +} + +func (err ErrNoRunningTimer) Error() string { + return fmt.Sprintf("No running timer [UserID: %d]", err.UserID) +} + +// ErrCodeNoRunningTimer holds the unique world-error code of this error +const ErrCodeNoRunningTimer = 18005 + +// HTTPError holds the http error description +func (err ErrNoRunningTimer) HTTPError() web.HTTPError { + return web.HTTPError{ + HTTPCode: http.StatusNotFound, + Code: ErrCodeNoRunningTimer, + Message: "You do not have a running timer.", + } +} + +// ErrTimeEntryAlreadyEnded represents an error where an update tries to clear the +// end time of an entry that has already ended (reopening it as a running timer). +type ErrTimeEntryAlreadyEnded struct { + TimeEntryID int64 +} + +// IsErrTimeEntryAlreadyEnded checks if an error is ErrTimeEntryAlreadyEnded. +func IsErrTimeEntryAlreadyEnded(err error) bool { + _, ok := err.(ErrTimeEntryAlreadyEnded) + return ok +} + +func (err ErrTimeEntryAlreadyEnded) Error() string { + return fmt.Sprintf("Time entry has already ended and cannot be reopened [TimeEntryID: %v]", err.TimeEntryID) +} + +// ErrCodeTimeEntryAlreadyEnded holds the unique world-error code of this error +const ErrCodeTimeEntryAlreadyEnded = 18006 + +// HTTPError holds the http error description +func (err ErrTimeEntryAlreadyEnded) HTTPError() web.HTTPError { + return web.HTTPError{ + HTTPCode: http.StatusBadRequest, + Code: ErrCodeTimeEntryAlreadyEnded, + Message: "A time entry that has already ended cannot be reopened into a running timer. Start a new timer instead.", + } +} + +// ErrTimeEntryEndBeforeStart represents an error where a time entry's end time +// precedes its start time, which would persist a negative interval. +type ErrTimeEntryEndBeforeStart struct { + TimeEntryID int64 +} + +// IsErrTimeEntryEndBeforeStart checks if an error is ErrTimeEntryEndBeforeStart. +func IsErrTimeEntryEndBeforeStart(err error) bool { + _, ok := err.(ErrTimeEntryEndBeforeStart) + return ok +} + +func (err ErrTimeEntryEndBeforeStart) Error() string { + return fmt.Sprintf("Time entry end time is before its start time [TimeEntryID: %v]", err.TimeEntryID) +} + +// ErrCodeTimeEntryEndBeforeStart holds the unique world-error code of this error +const ErrCodeTimeEntryEndBeforeStart = 18007 + +// HTTPError holds the http error description +func (err ErrTimeEntryEndBeforeStart) HTTPError() web.HTTPError { + return web.HTTPError{ + HTTPCode: http.StatusBadRequest, + Code: ErrCodeTimeEntryEndBeforeStart, + Message: "A time entry's end time cannot be before its start time.", + } +} + +// ================= +// User export errors +// ================= + +// ErrUserDataExportDoesNotExist represents an error where a user has no ready data export to download. +type ErrUserDataExportDoesNotExist struct{} + +// IsErrUserDataExportDoesNotExist checks if an error is ErrUserDataExportDoesNotExist. +func IsErrUserDataExportDoesNotExist(err error) bool { + _, ok := err.(ErrUserDataExportDoesNotExist) + return ok +} + +func (err ErrUserDataExportDoesNotExist) Error() string { + return "No user data export found" +} + +// ErrCodeUserDataExportDoesNotExist holds the unique world-error code of this error +const ErrCodeUserDataExportDoesNotExist = 19001 + +// HTTPError holds the http error description +func (err ErrUserDataExportDoesNotExist) HTTPError() web.HTTPError { + return web.HTTPError{ + HTTPCode: http.StatusNotFound, + Code: ErrCodeUserDataExportDoesNotExist, + Message: "No user data export found.", + } +} diff --git a/pkg/models/events.go b/pkg/models/events.go index 937524a1a..b938345f4 100644 --- a/pkg/models/events.go +++ b/pkg/models/events.go @@ -18,7 +18,6 @@ package models import ( "code.vikunja.io/api/pkg/user" - "code.vikunja.io/api/pkg/web" ) ///////////////// @@ -230,8 +229,8 @@ func (l *ProjectCreatedEvent) Name() string { // ProjectUpdatedEvent represents an event where a project has been updated type ProjectUpdatedEvent struct { - Project *Project `json:"project"` - Doer web.Auth `json:"doer"` + Project *Project `json:"project"` + Doer *user.User `json:"doer"` } // Name defines the name for ProjectUpdatedEvent @@ -241,8 +240,8 @@ func (p *ProjectUpdatedEvent) Name() string { // ProjectDeletedEvent represents an event where a project has been deleted type ProjectDeletedEvent struct { - Project *Project `json:"project"` - Doer web.Auth `json:"doer"` + Project *Project `json:"project"` + Doer *user.User `json:"doer"` } // Name defines the name for ProjectDeletedEvent @@ -258,7 +257,7 @@ func (p *ProjectDeletedEvent) Name() string { type ProjectSharedWithUserEvent struct { Project *Project `json:"project"` User *user.User `json:"user"` - Doer web.Auth `json:"doer"` + Doer *user.User `json:"doer"` } // Name defines the name for ProjectSharedWithUserEvent @@ -268,9 +267,9 @@ func (p *ProjectSharedWithUserEvent) Name() string { // ProjectSharedWithTeamEvent represents an event where a project has been shared with a team type ProjectSharedWithTeamEvent struct { - Project *Project `json:"project"` - Team *Team `json:"team"` - Doer web.Auth `json:"doer"` + Project *Project `json:"project"` + Team *Team `json:"team"` + Doer *user.User `json:"doer"` } // Name defines the name for ProjectSharedWithTeamEvent @@ -308,8 +307,8 @@ func (t *TeamMemberRemovedEvent) Name() string { // TeamCreatedEvent represents a TeamCreatedEvent event type TeamCreatedEvent struct { - Team *Team `json:"team"` - Doer web.Auth `json:"doer"` + Team *Team `json:"team"` + Doer *user.User `json:"doer"` } // Name defines the name for TeamCreatedEvent @@ -319,8 +318,8 @@ func (t *TeamCreatedEvent) Name() string { // TeamDeletedEvent represents a TeamDeletedEvent event type TeamDeletedEvent struct { - Team *Team `json:"team"` - Doer web.Auth `json:"doer"` + Team *Team `json:"team"` + Doer *user.User `json:"doer"` } // Name defines the name for TeamDeletedEvent @@ -362,3 +361,77 @@ type WebhookDeliveryEvent struct { func (w *WebhookDeliveryEvent) Name() string { return "webhook.delivery" } + +// TimeEntryCreatedEvent represents a time entry being created +type TimeEntryCreatedEvent struct { + TimeEntry *TimeEntry `json:"time_entry"` + Doer *user.User `json:"doer"` +} + +// Name defines the name for TimeEntryCreatedEvent +func (e *TimeEntryCreatedEvent) Name() string { + return "time-entry.created" +} + +// TimeEntryUpdatedEvent represents a time entry being updated (including a timer being stopped) +type TimeEntryUpdatedEvent struct { + TimeEntry *TimeEntry `json:"time_entry"` + Doer *user.User `json:"doer"` +} + +// Name defines the name for TimeEntryUpdatedEvent +func (e *TimeEntryUpdatedEvent) Name() string { + return "time-entry.updated" +} + +// TimeEntryDeletedEvent represents a time entry being deleted +type TimeEntryDeletedEvent struct { + TimeEntry *TimeEntry `json:"time_entry"` + Doer *user.User `json:"doer"` +} + +// Name defines the name for TimeEntryDeletedEvent +func (e *TimeEntryDeletedEvent) Name() string { + return "time-entry.deleted" +} + +//////////////////// +// API Token Events + +// API token events carry IDs only: the freshly created token struct holds the +// raw token string, which must never end up in a message payload (the poison +// queue logs payloads on handler failure). + +// APITokenIssuedEvent represents an API token being created +type APITokenIssuedEvent struct { + TokenID int64 `json:"token_id"` + DoerID int64 `json:"doer_id"` + OwnerID int64 `json:"owner_id"` +} + +// Name defines the name for APITokenIssuedEvent +func (e *APITokenIssuedEvent) Name() string { + return "api-token.issued" +} + +// APITokenRevokedEvent represents an API token being deleted +type APITokenRevokedEvent struct { + TokenID int64 `json:"token_id"` + DoerID int64 `json:"doer_id"` +} + +// Name defines the name for APITokenRevokedEvent +func (e *APITokenRevokedEvent) Name() string { + return "api-token.revoked" +} + +// APITokenUsedEvent represents an API token authenticating a request +type APITokenUsedEvent struct { + TokenID int64 `json:"token_id"` + OwnerID int64 `json:"owner_id"` +} + +// Name defines the name for APITokenUsedEvent +func (e *APITokenUsedEvent) Name() string { + return "api-token.used" +} diff --git a/pkg/models/export.go b/pkg/models/export.go index 4772fa2d5..2d9b57651 100644 --- a/pkg/models/export.go +++ b/pkg/models/export.go @@ -404,6 +404,64 @@ func exportProjectBackgrounds(s *xorm.Session, u *user.User, wr *zip.Writer) (er return utils.WriteFilesToZip(backgroundFiles, wr) } +// GetUserDataExportFile loads the user's ready data export with its bytes open for +// reading. It returns ErrUserDataExportDoesNotExist when the user never requested an +// export or the underlying file is gone. The caller must close the returned reader. +func GetUserDataExportFile(u *user.User) (*files.File, error) { + if u.ExportFileID == 0 { + return nil, ErrUserDataExportDoesNotExist{} + } + + exportFile := &files.File{ID: u.ExportFileID} + if err := exportFile.LoadFileMetaByID(); err != nil { + if files.IsErrFileDoesNotExist(err) { + return nil, ErrUserDataExportDoesNotExist{} + } + return nil, err + } + if err := exportFile.LoadFileByID(); err != nil { + if os.IsNotExist(err) { + return nil, ErrUserDataExportDoesNotExist{} + } + return nil, err + } + + return exportFile, nil +} + +// GetUserDataExportStatus returns metadata about the user's current data export, or +// nil when none exists. The expiry mirrors the cleanup cron's 7-day retention. +func GetUserDataExportStatus(u *user.User) (*UserExportStatus, error) { + if u.ExportFileID == 0 { + return nil, nil + } + + exportFile := &files.File{ID: u.ExportFileID} + if err := exportFile.LoadFileMetaByID(); err != nil { + // A missing meta row means there is no export — mirror the download path + // (404 there) instead of surfacing a 500. + if files.IsErrFileDoesNotExist(err) { + return nil, nil + } + return nil, err + } + + return &UserExportStatus{ + ID: exportFile.ID, + Size: exportFile.Size, + Created: exportFile.Created, + Expires: exportFile.Created.Add(7 * 24 * time.Hour), + }, nil +} + +// UserExportStatus is the metadata returned for a user's current data export. +type UserExportStatus struct { + ID int64 `json:"id" readOnly:"true" doc:"The id of the export file."` + Size uint64 `json:"size" readOnly:"true" doc:"The size of the export file in bytes."` + Created time.Time `json:"created" readOnly:"true" doc:"When the export was created."` + Expires time.Time `json:"expires" readOnly:"true" doc:"When the export will be automatically deleted (7 days after creation)."` +} + func RegisterOldExportCleanupCron() { const logPrefix = "[User Export Cleanup Cron] " diff --git a/pkg/models/export_test.go b/pkg/models/export_test.go new file mode 100644 index 000000000..f2d0fa2fb --- /dev/null +++ b/pkg/models/export_test.go @@ -0,0 +1,53 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package models + +import ( + "testing" + + "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/user" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGetUserDataExportStatus(t *testing.T) { + t.Run("no export", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + status, err := GetUserDataExportStatus(&user.User{ID: 15}) + require.NoError(t, err) + assert.Nil(t, status) + }) + + t.Run("with export", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + status, err := GetUserDataExportStatus(&user.User{ID: 1, ExportFileID: 1}) + require.NoError(t, err) + require.NotNil(t, status) + assert.Equal(t, int64(1), status.ID) + }) + + t.Run("export points at a missing file", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + // A dangling ExportFileID must read as "no export" rather than erroring, + // matching the download path which 404s the same case. + status, err := GetUserDataExportStatus(&user.User{ID: 15, ExportFileID: 9999}) + require.NoError(t, err) + assert.Nil(t, status) + }) +} diff --git a/pkg/models/kanban_task_bucket.go b/pkg/models/kanban_task_bucket.go index 0b2b06eaf..cd58e6928 100644 --- a/pkg/models/kanban_task_bucket.go +++ b/pkg/models/kanban_task_bucket.go @@ -21,25 +21,26 @@ import ( "code.vikunja.io/api/pkg/db" "code.vikunja.io/api/pkg/events" - "code.vikunja.io/api/pkg/user" "code.vikunja.io/api/pkg/web" + "xorm.io/xorm" + "xorm.io/xorm/schemas" ) // TaskBucket represents the relation between a task and a kanban bucket. // A task can only appear once per project view which is ensured by a // unique index on the combination of task_id and project_view_id. type TaskBucket struct { - BucketID int64 `xorm:"bigint not null index" json:"bucket_id" param:"bucket"` - Bucket *Bucket `xorm:"-" json:"bucket"` + BucketID int64 `xorm:"bigint not null index" json:"bucket_id" param:"bucket" doc:"The bucket to move the task into. On /api/v2 this is taken from the URL; a value in the body is ignored."` + Bucket *Bucket `xorm:"-" json:"bucket" readOnly:"true" doc:"The resolved target bucket, including its updated task count."` // The task which belongs to the bucket. Together with ProjectViewID // this field is part of a unique index to prevent duplicates. - TaskID int64 `xorm:"bigint not null index unique(task_view)" json:"task_id"` + TaskID int64 `xorm:"bigint not null index unique(task_view)" json:"task_id" doc:"The id of the task to place in the bucket."` // The view this bucket belongs to. Combined with TaskID this forms a // unique index. - ProjectViewID int64 `xorm:"bigint not null index unique(task_view)" json:"project_view_id" param:"view"` + ProjectViewID int64 `xorm:"bigint not null index unique(task_view)" json:"project_view_id" param:"view" doc:"The view the bucket belongs to. On /api/v2 this is taken from the URL; a value in the body is ignored."` ProjectID int64 `xorm:"-" json:"-" param:"project"` - Task *Task `xorm:"-" json:"task"` + Task *Task `xorm:"-" json:"task" readOnly:"true" doc:"The task as it stands after the move, reflecting any done-state change."` web.Permissions `xorm:"-" json:"-"` web.CRUDable `xorm:"-" json:"-"` @@ -59,27 +60,19 @@ func (b *TaskBucket) CanUpdate(s *xorm.Session, a web.Auth) (bool, error) { } func (b *TaskBucket) upsert(s *xorm.Session) (err error) { - count, err := s.Where("task_id = ? AND project_view_id = ?", b.TaskID, b.ProjectViewID). - Cols("bucket_id"). - Update(b) - if err != nil { - return - } - - if count == 0 { - _, err = s.Insert(b) - if err != nil { - // Check if this is a unique constraint violation for the task_buckets table - if db.IsUniqueConstraintError(err, "UQE_task_buckets_task_project_view") { - return ErrTaskAlreadyExistsInBucket{ - TaskID: b.TaskID, - ProjectViewID: b.ProjectViewID, - } - } - return - } + // A native upsert moves the task in one atomic statement, without + // depending on the affected-row count (MySQL/MariaDB report 0 affected + // rows for an unchanged value). + onConflict := "ON CONFLICT (task_id, project_view_id) DO UPDATE SET bucket_id = excluded.bucket_id" + if db.Type() == schemas.MYSQL { + onConflict = "ON DUPLICATE KEY UPDATE bucket_id = VALUES(bucket_id)" } + // Raw SQL bypasses xorm's bean-based table-name handling, so qualify the + // table ourselves to honor a configured postgres schema (database.schema). + table := s.Engine().TableName(b, true) + query := "INSERT INTO " + table + " (task_id, project_view_id, bucket_id) VALUES (?, ?, ?) " + onConflict + _, err = s.Exec(query, b.TaskID, b.ProjectViewID, b.BucketID) return } @@ -152,10 +145,8 @@ func updateTaskBucket(s *xorm.Session, a web.Auth, b *TaskBucket) (err error) { if err != nil { return err } - // If the task is already in the default bucket, skip the - // upsert — MySQL's UPDATE returns 0 affected rows when - // the value is unchanged, which would make upsert fall - // through to INSERT and hit the unique constraint. + // The task is already in the default bucket, so there is + // nothing to move and no count to bump. if b.BucketID == oldTaskBucket.BucketID { updateBucket = false } @@ -252,10 +243,9 @@ func (b *TaskBucket) Update(s *xorm.Session, a web.Auth) (err error) { } if b.Task != nil { - doer, _ := user.GetFromAuth(a) events.DispatchOnCommit(s, &TaskUpdatedEvent{ Task: b.Task, - Doer: doer, + Doer: doerFromAuth(s, a), }) } return nil diff --git a/pkg/models/kanban_task_bucket_test.go b/pkg/models/kanban_task_bucket_test.go index 6d1eb2f24..bf5c42ac3 100644 --- a/pkg/models/kanban_task_bucket_test.go +++ b/pkg/models/kanban_task_bucket_test.go @@ -226,6 +226,125 @@ func TestTaskBucket_Update(t *testing.T) { }) }) + t.Run("done task already in another view's done bucket", func(t *testing.T) { + // Regression test: marking a task done syncs it into the done bucket + // of every kanban view in the project. When the task already sits in + // such a view's done bucket the sync is a no-op update, but on + // MySQL/MariaDB an UPDATE that doesn't change the value reports 0 + // affected rows. The upsert then mistook that for "row missing" and + // inserted, hitting the unique index with ErrTaskAlreadyExistsInBucket. + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + + // A second manual kanban view on project 1. Creating it auto-generates + // the To-Do/Doing/Done buckets and sets its done bucket. + secondView := &ProjectView{ + Title: "Second Kanban", + ProjectID: 1, + ViewKind: ProjectViewKindKanban, + BucketConfigurationMode: BucketConfigurationModeManual, + } + err := secondView.Create(s, u) + require.NoError(t, err) + require.NotZero(t, secondView.DoneBucketID) + + // Pre-place task 1 in the second view's done bucket without going + // through the done-sync, so the task itself is still open and view 4 + // still has it in its default bucket. + _, err = s.Where("task_id = ? AND project_view_id = ?", 1, secondView.ID). + Cols("bucket_id"). + Update(&TaskBucket{BucketID: secondView.DoneBucketID}) + require.NoError(t, err) + + // Moving task 1 into view 4's done bucket marks it done and triggers + // the cross-view sync into the second view's done bucket, where it + // already lives. This must succeed rather than error. + tb := &TaskBucket{ + TaskID: 1, + BucketID: 3, // done bucket on view 4 + ProjectViewID: 4, + ProjectID: 1, + } + err = tb.Update(s, u) + require.NoError(t, err) + err = s.Commit() + require.NoError(t, err) + assert.True(t, tb.Task.Done) + + db.AssertExists(t, "task_buckets", map[string]interface{}{ + "task_id": 1, + "bucket_id": 3, + }, false) + db.AssertExists(t, "task_buckets", map[string]interface{}{ + "task_id": 1, + "project_view_id": secondView.ID, + "bucket_id": secondView.DoneBucketID, + }, false) + }) + + t.Run("saved filter: first task into empty limited bucket is allowed", func(t *testing.T) { + // Regression test for #2672: on a saved-filter kanban view the bucket + // limit was checked against the total number of tasks matching the + // filter instead of the number of tasks actually in the target bucket, + // so adding the first task to an empty limited bucket was wrongly + // rejected with ErrBucketLimitExceeded. + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + + // A saved filter matching many tasks; the filter total is well above + // the bucket limit we set below. + sf := &SavedFilter{ + Title: "limit-filter", + Filters: &TaskCollection{Filter: "done = false"}, + } + err := sf.Create(s, u) + require.NoError(t, err) + + filterProjectID := getProjectIDFromSavedFilterID(sf.ID) + + view := &ProjectView{} + exists, err := s.Where("project_id = ? AND view_kind = ?", filterProjectID, ProjectViewKindKanban).Get(view) + require.NoError(t, err) + require.True(t, exists) + + // All matching tasks are placed in the default bucket on creation; + // pick three of them to move into a fresh, empty bucket. + var defaultTasks []*TaskBucket + err = s.Where("project_view_id = ?", view.ID).Find(&defaultTasks) + require.NoError(t, err) + require.GreaterOrEqual(t, len(defaultTasks), 3, "filter must match enough tasks to exceed the bucket limit") + + limitedBucket := &Bucket{ + Title: "limited", + ProjectViewID: view.ID, + ProjectID: filterProjectID, + Limit: 2, + } + err = limitedBucket.Create(s, u) + require.NoError(t, err) + + moveTaskToBucket := func(taskID int64) error { + tb := &TaskBucket{ + TaskID: taskID, + BucketID: limitedBucket.ID, + ProjectViewID: view.ID, + ProjectID: filterProjectID, + } + return tb.Update(s, u) + } + + // Moving the FIRST task into the empty bucket must succeed (0/2 -> 1/2). + require.NoError(t, moveTaskToBucket(defaultTasks[0].TaskID)) + // The second one fills the bucket up to the limit (1/2 -> 2/2). + require.NoError(t, moveTaskToBucket(defaultTasks[1].TaskID)) + // The third one would exceed the limit and must be rejected. + err = moveTaskToBucket(defaultTasks[2].TaskID) + require.Error(t, err) + assert.True(t, IsErrBucketLimitExceeded(err)) + }) + t.Run("keep done timestamp when moving task between projects", func(t *testing.T) { db.LoadAndAssertFixtures(t) u := &user.User{ID: 1} diff --git a/pkg/models/label.go b/pkg/models/label.go index 3df013f73..b39e6ae83 100644 --- a/pkg/models/label.go +++ b/pkg/models/label.go @@ -29,22 +29,22 @@ import ( // Label represents a label type Label struct { // The unique, numeric id of this label. - ID int64 `xorm:"bigint autoincr not null unique pk" json:"id" param:"label"` + ID int64 `xorm:"bigint autoincr not null unique pk" json:"id" param:"label" readOnly:"true" doc:"The unique, numeric id of this label."` // The title of the label. You'll see this one on tasks associated with it. - Title string `xorm:"varchar(250) not null" json:"title" valid:"runelength(1|250)" minLength:"1" maxLength:"250"` + Title string `xorm:"varchar(250) not null" json:"title" valid:"runelength(1|250)" minLength:"1" maxLength:"250" doc:"The title of the label. You'll see this one on tasks associated with it."` // The label description. - Description string `xorm:"longtext null" json:"description"` + Description string `xorm:"longtext null" json:"description" doc:"The label description."` // The color this label has in hex format. - HexColor string `xorm:"varchar(6) null" json:"hex_color" valid:"runelength(0|7)" maxLength:"7"` + HexColor string `xorm:"varchar(6) null" json:"hex_color" valid:"runelength(0|7)" maxLength:"7" doc:"The color this label has in hex format."` CreatedByID int64 `xorm:"bigint not null" json:"-"` // The user who created this label - CreatedBy *user.User `xorm:"-" json:"created_by"` + CreatedBy *user.User `xorm:"-" json:"created_by" readOnly:"true" doc:"The user who created this label."` // A timestamp when this label was created. You cannot change this value. - Created time.Time `xorm:"created not null" json:"created"` + Created time.Time `xorm:"created not null" json:"created" readOnly:"true" doc:"A timestamp when this label was created. You cannot change this value."` // A timestamp when this label was last updated. You cannot change this value. - Updated time.Time `xorm:"updated not null" json:"updated"` + Updated time.Time `xorm:"updated not null" json:"updated" readOnly:"true" doc:"A timestamp when this label was last updated. You cannot change this value."` web.CRUDable `xorm:"-" json:"-"` web.Permissions `xorm:"-" json:"-"` diff --git a/pkg/models/label_permissions.go b/pkg/models/label_permissions.go index 1c5df95c1..2854cbf7b 100644 --- a/pkg/models/label_permissions.go +++ b/pkg/models/label_permissions.go @@ -17,6 +17,7 @@ package models import ( + "code.vikunja.io/api/pkg/user" "code.vikunja.io/api/pkg/web" "xorm.io/builder" "xorm.io/xorm" @@ -57,7 +58,19 @@ func (l *Label) isLabelOwner(s *xorm.Session, a web.Auth) (bool, error) { if err != nil { return false, err } - return lorig.CreatedByID == a.GetID(), nil + if lorig.CreatedByID == a.GetID() { + return true, nil + } + + // A bot owner inherits write/delete access to labels their bots created. + creator, err := user.GetUserByID(s, lorig.CreatedByID) + if err != nil { + if user.IsErrUserDoesNotExist(err) { + return false, nil + } + return false, err + } + return creator.IsBot() && creator.BotOwnerID == a.GetID(), nil } // hasAccessToLabel reports whether the caller can read a label and, if so, @@ -91,7 +104,12 @@ func (l *Label) hasAccessToLabel(s *xorm.Session, a web.Auth) (has bool, maxPerm accessBranches := []builder.Cond{labelAttachedToAccessibleTask} if !isLinkShare { - accessBranches = append(accessBranches, builder.Eq{"labels.created_by_id": a.GetID()}) + accessBranches = append(accessBranches, + builder.Eq{"labels.created_by_id": a.GetID()}, + builder.In("labels.created_by_id", + builder.Select("id").From("users").Where(builder.Eq{"bot_owner_id": a.GetID()}), + ), + ) } cond := builder.And( @@ -107,38 +125,15 @@ func (l *Label) hasAccessToLabel(s *xorm.Session, a web.Auth) (has bool, maxPerm return } - // maxPermission is derived only from label_tasks rows whose task is - // actually accessible. The pre-fix code used Get(ll) against the - // unrestricted LEFT JOIN, so it could return an inaccessible row and - // yield a wrong (or errored) permission. - accessibleTaskIDs := []int64{} - err = s.Table("label_tasks"). - Join("INNER", "tasks", "tasks.id = label_tasks.task_id"). - Where(builder.And( - builder.Eq{"label_tasks.label_id": l.ID}, - accessibleProjects, - )). - Cols("label_tasks.task_id"). - Find(&accessibleTaskIDs) + // Writes and deletes are owner-only (CanUpdate/CanDelete), so the caller's + // max permission is admin for the owner and read for anyone else who can see it. + owner, err := l.isLabelOwner(s, a) if err != nil { return } - - for _, taskID := range accessibleTaskIDs { - t := &Task{ID: taskID} - _, taskPermission, tErr := t.CanRead(s, a) - if tErr != nil { - err = tErr - return - } - if taskPermission > maxPermission { - maxPermission = taskPermission - } - } - - // Creator-branch fallback: access came from created_by_id with no - // accessible task to derive a permission from. - if len(accessibleTaskIDs) == 0 { + if owner { + maxPermission = int(PermissionAdmin) + } else { maxPermission = int(PermissionRead) } diff --git a/pkg/models/label_task.go b/pkg/models/label_task.go index 8a09a3c01..024b0e039 100644 --- a/pkg/models/label_task.go +++ b/pkg/models/label_task.go @@ -36,9 +36,9 @@ type LabelTask struct { ID int64 `xorm:"bigint autoincr not null unique pk" json:"-"` TaskID int64 `xorm:"bigint INDEX not null" json:"-" param:"projecttask"` // The label id you want to associate with a task. - LabelID int64 `xorm:"bigint INDEX not null" json:"label_id" param:"label"` + LabelID int64 `xorm:"bigint INDEX not null" json:"label_id" param:"label" doc:"The id of the label to associate with the task."` // A timestamp when this task was created. You cannot change this value. - Created time.Time `xorm:"created not null" json:"created"` + Created time.Time `xorm:"created not null" json:"created" readOnly:"true" doc:"A timestamp when this label was added to the task. You cannot change this value."` web.CRUDable `xorm:"-" json:"-"` web.Permissions `xorm:"-" json:"-"` @@ -212,7 +212,12 @@ func GetLabelsByTaskIDs(s *xorm.Session, opts *LabelByTaskIDsOptions) (ls []*Lab ), cond) } if opts.GetUnusedLabels && !isLinkShareAuth { - cond = builder.Or(cond, builder.Eq{"labels.created_by_id": opts.User.GetID()}) + cond = builder.Or(cond, + builder.Eq{"labels.created_by_id": opts.User.GetID()}, + builder.In("labels.created_by_id", + builder.Select("id").From("users").Where(builder.Eq{"bot_owner_id": opts.User.GetID()}), + ), + ) } ids := []int64{} @@ -410,7 +415,7 @@ func (t *Task) UpdateTaskLabels(s *xorm.Session, creator web.Auth, labels []*Lab // LabelTaskBulk is a helper struct to update a bunch of labels at once type LabelTaskBulk struct { // All labels you want to update at once. - Labels []*Label `json:"labels"` + Labels []*Label `json:"labels" doc:"The complete set of labels the task should have after the call. Any label currently on the task that is not in this list is removed; any label in the list that is not yet on the task is added. You must be able to see every label you attach."` TaskID int64 `json:"-" param:"projecttask"` web.CRUDable `json:"-"` diff --git a/pkg/models/label_test.go b/pkg/models/label_test.go index aa73ae647..02bff9f06 100644 --- a/pkg/models/label_test.go +++ b/pkg/models/label_test.go @@ -228,7 +228,7 @@ func TestLabel_ReadOne(t *testing.T) { }, auth: &user.User{ID: 1}, assertMaxPermission: true, - wantMaxPermission: int(PermissionRead), + wantMaxPermission: int(PermissionAdmin), }, { name: "Get nonexistant label", @@ -249,8 +249,8 @@ func TestLabel_ReadOne(t *testing.T) { auth: &user.User{ID: 1}, }, { - // Label 4 is attached to tasks in project 1 (user 1 is admin), - // so the accessible-tasks iteration must yield PermissionAdmin. + // Label 4 is owned by user 2; user 1 can read it via a shared task + // but is not the owner, so max permission is read. name: "Get label #4 - other user", fields: fields{ ID: 4, @@ -276,7 +276,7 @@ func TestLabel_ReadOne(t *testing.T) { }, auth: &user.User{ID: 1}, assertMaxPermission: true, - wantMaxPermission: int(PermissionAdmin), + wantMaxPermission: int(PermissionRead), }, { // PoC for GHSA-hj5c-mhh2-g7jq: label 6 is reachable only via task @@ -304,12 +304,12 @@ func TestLabel_ReadOne(t *testing.T) { }, auth: &user.User{ID: 1}, assertMaxPermission: true, - wantMaxPermission: int(PermissionRead), + wantMaxPermission: int(PermissionAdmin), }, { - // Label 8's only label_tasks row points at inaccessible task 34, - // so access must come from the creator branch and the - // maxPermission fallback to PermissionRead must kick in. + // Label 8's only label_tasks row points at inaccessible task 34, so + // access comes from the creator branch; as the owner, user 1's max + // permission is admin. name: "creator can read own label only attached to inaccessible task", fields: fields{ ID: 8, @@ -324,7 +324,7 @@ func TestLabel_ReadOne(t *testing.T) { }, auth: &user.User{ID: 1}, assertMaxPermission: true, - wantMaxPermission: int(PermissionRead), + wantMaxPermission: int(PermissionAdmin), }, { // Non-creator must not be able to read an unattached label owned @@ -336,6 +336,46 @@ func TestLabel_ReadOne(t *testing.T) { wantForbidden: true, auth: &user.User{ID: 1}, }, + { + // Label 9 was created by bot 23, whose owner is user 21. The + // bot owner inherits admin-level access. + name: "bot owner can read label created by their bot", + fields: fields{ + ID: 9, + }, + want: &Label{ + ID: 9, + Title: "Label #9 - created by bot 23 owned by user 21", + CreatedByID: 23, + CreatedBy: &user.User{ + ID: 23, + Name: "Owner A Assistant", + Username: "bot-owner-a-assistant", + Issuer: "local", + BotOwnerID: 21, + EmailRemindersEnabled: true, + OverdueTasksRemindersEnabled: true, + OverdueTasksRemindersTime: "09:00", + Created: testCreatedTime, + Updated: testUpdatedTime, + }, + Created: testCreatedTime, + Updated: testUpdatedTime, + }, + auth: &user.User{ID: 21}, + assertMaxPermission: true, + wantMaxPermission: int(PermissionAdmin), + }, + { + // User 22 owns a different bot and must not see another owner's + // bot's label. + name: "non-owner cannot read label created by someone else's bot", + fields: fields{ + ID: 9, + }, + wantForbidden: true, + auth: &user.User{ID: 22}, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -507,6 +547,27 @@ func TestLabel_Update(t *testing.T) { auth: &user.User{ID: 1}, wantForbidden: true, }, + { + // Label 9 was created by bot 23 (owned by user 21). The bot's + // owner inherits update permission. + name: "bot owner can update label created by their bot", + fields: fields{ + ID: 9, + Title: "new and better", + }, + auth: &user.User{ID: 21}, + }, + { + // User 22 owns a different bot and must not be able to update + // another owner's bot's label. + name: "non-owner cannot update label created by someone else's bot", + fields: fields{ + ID: 9, + Title: "new and better", + }, + auth: &user.User{ID: 22}, + wantForbidden: true, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -594,6 +655,25 @@ func TestLabel_Delete(t *testing.T) { auth: &user.User{ID: 1}, wantForbidden: true, }, + { + // Label 9 was created by bot 23 (owned by user 21). The bot's + // owner inherits delete permission. + name: "bot owner can delete label created by their bot", + fields: fields{ + ID: 9, + }, + auth: &user.User{ID: 21}, + }, + { + // User 22 owns a different bot and must not be able to delete + // another owner's bot's label. + name: "non-owner cannot delete label created by someone else's bot", + fields: fields{ + ID: 9, + }, + auth: &user.User{ID: 22}, + wantForbidden: true, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/pkg/models/link_sharing.go b/pkg/models/link_sharing.go index 23a3fd876..b05121be6 100644 --- a/pkg/models/link_sharing.go +++ b/pkg/models/link_sharing.go @@ -45,30 +45,30 @@ const ( // LinkSharing represents a shared project type LinkSharing struct { // The ID of the shared thing - ID int64 `xorm:"bigint autoincr not null unique pk" json:"id" param:"share"` + ID int64 `xorm:"bigint autoincr not null unique pk" json:"id" param:"share" readOnly:"true" doc:"The unique, numeric id of this link share."` // The public id to get this shared project - Hash string `xorm:"varchar(40) not null unique" json:"hash" param:"hash"` + Hash string `xorm:"varchar(40) not null unique" json:"hash" param:"hash" readOnly:"true" doc:"The public hash used to access the shared project. Generated by the server; ignored on write."` // The name of this link share. All actions someone takes while being authenticated with that link will appear with that name. - Name string `xorm:"text null" json:"name"` + Name string `xorm:"text null" json:"name" doc:"The name of this link share. All actions someone takes while authenticated through this link will appear under this name."` // The ID of the shared project ProjectID int64 `xorm:"bigint not null" json:"-" param:"project"` // The permission this project is shared with. 0 = Read only, 1 = Read & Write, 2 = Admin. See the docs for more details. - Permission Permission `xorm:"bigint INDEX not null default 0" json:"permission" valid:"length(0|2)" maximum:"2" default:"0"` + Permission Permission `xorm:"bigint INDEX not null default 0" json:"permission" valid:"length(0|2)" maximum:"2" default:"0" doc:"The permission this project is shared with: 0 = read only, 1 = read & write, 2 = admin."` // The kind of this link. 0 = undefined, 1 = without password, 2 = with password. - SharingType SharingType `xorm:"bigint INDEX not null default 0" json:"sharing_type" valid:"length(0|2)" maximum:"2" default:"0"` + SharingType SharingType `xorm:"bigint INDEX not null default 0" json:"sharing_type" valid:"length(0|2)" maximum:"2" default:"0" readOnly:"true" doc:"The kind of this link, derived from whether a password was set: 0 = undefined, 1 = without password, 2 = with password."` // The password of this link share. You can only set it, not retrieve it after the link share has been created. - Password string `xorm:"text null" json:"password"` + Password string `xorm:"text null" json:"password" writeOnly:"true" doc:"The password protecting this link share. Write-only: it can be set on create but is never returned."` // The user who shared this project - SharedBy *user.User `xorm:"-" json:"shared_by"` + SharedBy *user.User `xorm:"-" json:"shared_by" readOnly:"true" doc:"The user who created this link share."` SharedByID int64 `xorm:"bigint INDEX not null" json:"-"` // A timestamp when this project was shared. You cannot change this value. - Created time.Time `xorm:"created not null" json:"created"` + Created time.Time `xorm:"created not null" json:"created" readOnly:"true" doc:"A timestamp when this share was created. You cannot change this value."` // A timestamp when this share was last updated. You cannot change this value. - Updated time.Time `xorm:"updated not null" json:"updated"` + Updated time.Time `xorm:"updated not null" json:"updated" readOnly:"true" doc:"A timestamp when this share was last updated. You cannot change this value."` web.CRUDable `xorm:"-" json:"-"` web.Permissions `xorm:"-" json:"-"` diff --git a/pkg/models/link_sharing_permissions.go b/pkg/models/link_sharing_permissions.go index 359be43d7..8027583ab 100644 --- a/pkg/models/link_sharing_permissions.go +++ b/pkg/models/link_sharing_permissions.go @@ -28,11 +28,24 @@ func (share *LinkSharing) CanRead(s *xorm.Session, a web.Auth) (bool, int, error return false, 0, nil } - l, err := GetProjectByShareHash(s, share.Hash) - if err != nil { - return false, 0, err + // A by-id read carries the parent project but no hash, so resolve the + // project from ProjectID; only fall back to the hash lookup when ProjectID + // is absent (e.g. resolving a share purely by its public hash). + var project *Project + if share.ProjectID != 0 { + var err error + project, err = GetProjectSimpleByID(s, share.ProjectID) + if err != nil { + return false, 0, err + } + } else { + var err error + project, err = GetProjectByShareHash(s, share.Hash) + if err != nil { + return false, 0, err + } } - return l.CanRead(s, a) + return project.CanRead(s, a) } // CanDelete implements the delete permission check for a link share diff --git a/pkg/models/listeners.go b/pkg/models/listeners.go index 86707c94e..e50631ae1 100644 --- a/pkg/models/listeners.go +++ b/pkg/models/listeners.go @@ -22,12 +22,11 @@ import ( "strconv" "time" + "code.vikunja.io/api/pkg/audit" "code.vikunja.io/api/pkg/config" "code.vikunja.io/api/pkg/db" "code.vikunja.io/api/pkg/events" "code.vikunja.io/api/pkg/log" - "code.vikunja.io/api/pkg/metrics" - "code.vikunja.io/api/pkg/modules/keyvalue" "code.vikunja.io/api/pkg/notifications" "code.vikunja.io/api/pkg/user" @@ -38,16 +37,6 @@ import ( // RegisterListeners registers all event listeners func RegisterListeners() { - if config.MetricsEnabled.GetBool() { - events.RegisterListener((&ProjectCreatedEvent{}).Name(), &IncreaseProjectCounter{}) - events.RegisterListener((&ProjectDeletedEvent{}).Name(), &DecreaseProjectCounter{}) - events.RegisterListener((&TaskCreatedEvent{}).Name(), &IncreaseTaskCounter{}) - events.RegisterListener((&TaskDeletedEvent{}).Name(), &DecreaseTaskCounter{}) - events.RegisterListener((&TeamDeletedEvent{}).Name(), &DecreaseTeamCounter{}) - events.RegisterListener((&TeamCreatedEvent{}).Name(), &IncreaseTeamCounter{}) - events.RegisterListener((&TaskAttachmentCreatedEvent{}).Name(), &IncreaseAttachmentCounter{}) - events.RegisterListener((&TaskAttachmentDeletedEvent{}).Name(), &DecreaseAttachmentCounter{}) - } events.RegisterListener((&TaskCommentCreatedEvent{}).Name(), &SendTaskCommentNotification{}) events.RegisterListener((&TaskAssigneeCreatedEvent{}).Name(), &SendTaskAssignedNotification{}) events.RegisterListener((&TaskDeletedEvent{}).Name(), &SendTaskDeletedNotification{}) @@ -94,39 +83,254 @@ func RegisterListeners() { // Internal delivery listener — one message per webhook with its own retry lifecycle events.RegisterListener((&WebhookDeliveryEvent{}).Name(), &WebhookDeliveryListener{}) } + if config.AuditEnabled.GetBool() { + registerEventsForAuditLogging() + } +} + +func auditActorFromUser(u *user.User) audit.Actor { + if u == nil { + return audit.SystemActor() + } + return audit.ActorFromDoerID(u.ID) +} + +// registerEventsForAuditLogging opts events into audit logging. This block is +// the catalog of the entire audited surface — an event without a registration +// here is not audited. +func registerEventsForAuditLogging() { + // Auth boundary + audit.RegisterEventForAudit(func(e *user.LoginSucceededEvent) *audit.Entry { + return &audit.Entry{ + Action: audit.ActionLoginSucceeded, + Actor: audit.UserActor(e.User.ID), + Target: audit.UserTarget(e.User.ID), + } + }) + audit.RegisterEventForAudit(func(e *user.LoginFailedEvent) *audit.Entry { + return &audit.Entry{ + Action: audit.ActionLoginFailed, + Actor: audit.UserActor(e.User.ID), + Target: audit.UserTarget(e.User.ID), + Outcome: audit.OutcomeFailure, + Reason: "wrong password", + } + }) + audit.RegisterEventForAudit(func(e *user.LogoutEvent) *audit.Entry { + return &audit.Entry{ + Action: audit.ActionLogout, + Actor: audit.UserActor(e.UserID), + Target: audit.UserTarget(e.UserID), + } + }) + audit.RegisterEventForAudit(func(e *APITokenIssuedEvent) *audit.Entry { + return &audit.Entry{ + Action: audit.ActionAPITokenIssued, + Actor: audit.UserActor(e.DoerID), + Target: audit.APITokenTarget(e.TokenID), + Metadata: map[string]any{"owner_id": e.OwnerID}, + } + }) + audit.RegisterEventForAudit(func(e *APITokenRevokedEvent) *audit.Entry { + return &audit.Entry{ + Action: audit.ActionAPITokenRevoked, + Actor: audit.UserActor(e.DoerID), + Target: audit.APITokenTarget(e.TokenID), + } + }) + audit.RegisterEventForAudit(func(e *APITokenUsedEvent) *audit.Entry { + return &audit.Entry{ + Action: audit.ActionAPITokenUsed, + Actor: audit.UserActor(e.OwnerID), + Target: audit.APITokenTarget(e.TokenID), + } + }) + + // Users + audit.RegisterEventForAudit(func(e *user.CreatedEvent) *audit.Entry { + return &audit.Entry{ + Action: audit.ActionUserCreated, + Actor: audit.UserActor(e.User.ID), + Target: audit.UserTarget(e.User.ID), + } + }) + + // Tasks + audit.RegisterEventForAudit(func(e *TaskCreatedEvent) *audit.Entry { + return &audit.Entry{ + Action: audit.ActionTaskCreated, + Actor: auditActorFromUser(e.Doer), + Target: audit.TaskTarget(e.Task.ID), + } + }) + audit.RegisterEventForAudit(func(e *TaskUpdatedEvent) *audit.Entry { + return &audit.Entry{ + Action: audit.ActionTaskUpdated, + Actor: auditActorFromUser(e.Doer), + Target: audit.TaskTarget(e.Task.ID), + } + }) + audit.RegisterEventForAudit(func(e *TaskDeletedEvent) *audit.Entry { + return &audit.Entry{ + Action: audit.ActionTaskDeleted, + Actor: auditActorFromUser(e.Doer), + Target: audit.TaskTarget(e.Task.ID), + } + }) + audit.RegisterEventForAudit(func(e *TaskAssigneeCreatedEvent) *audit.Entry { + return &audit.Entry{ + Action: audit.ActionTaskAssigneeAdded, + Actor: auditActorFromUser(e.Doer), + Target: audit.TaskTarget(e.Task.ID), + Metadata: map[string]any{"assignee_id": e.Assignee.ID}, + } + }) + audit.RegisterEventForAudit(func(e *TaskAssigneeDeletedEvent) *audit.Entry { + return &audit.Entry{ + Action: audit.ActionTaskAssigneeRemoved, + Actor: auditActorFromUser(e.Doer), + Target: audit.TaskTarget(e.Task.ID), + Metadata: map[string]any{"assignee_id": e.Assignee.ID}, + } + }) + audit.RegisterEventForAudit(func(e *TaskCommentCreatedEvent) *audit.Entry { + return &audit.Entry{ + Action: audit.ActionTaskCommentCreated, + Actor: auditActorFromUser(e.Doer), + Target: audit.TaskTarget(e.Task.ID), + Metadata: map[string]any{"comment_id": e.Comment.ID}, + } + }) + audit.RegisterEventForAudit(func(e *TaskCommentUpdatedEvent) *audit.Entry { + return &audit.Entry{ + Action: audit.ActionTaskCommentUpdated, + Actor: auditActorFromUser(e.Doer), + Target: audit.TaskTarget(e.Task.ID), + Metadata: map[string]any{"comment_id": e.Comment.ID}, + } + }) + audit.RegisterEventForAudit(func(e *TaskCommentDeletedEvent) *audit.Entry { + return &audit.Entry{ + Action: audit.ActionTaskCommentDeleted, + Actor: auditActorFromUser(e.Doer), + Target: audit.TaskTarget(e.Task.ID), + Metadata: map[string]any{"comment_id": e.Comment.ID}, + } + }) + audit.RegisterEventForAudit(func(e *TaskAttachmentCreatedEvent) *audit.Entry { + return &audit.Entry{ + Action: audit.ActionTaskAttachmentCreated, + Actor: auditActorFromUser(e.Doer), + Target: audit.TaskTarget(e.Task.ID), + Metadata: map[string]any{"attachment_id": e.Attachment.ID}, + } + }) + audit.RegisterEventForAudit(func(e *TaskAttachmentDeletedEvent) *audit.Entry { + return &audit.Entry{ + Action: audit.ActionTaskAttachmentDeleted, + Actor: auditActorFromUser(e.Doer), + Target: audit.TaskTarget(e.Task.ID), + Metadata: map[string]any{"attachment_id": e.Attachment.ID}, + } + }) + audit.RegisterEventForAudit(func(e *TaskRelationCreatedEvent) *audit.Entry { + return &audit.Entry{ + Action: audit.ActionTaskRelationCreated, + Actor: auditActorFromUser(e.Doer), + Target: audit.TaskTarget(e.Task.ID), + Metadata: map[string]any{ + "other_task_id": e.Relation.OtherTaskID, + "relation_kind": e.Relation.RelationKind, + }, + } + }) + audit.RegisterEventForAudit(func(e *TaskRelationDeletedEvent) *audit.Entry { + return &audit.Entry{ + Action: audit.ActionTaskRelationDeleted, + Actor: auditActorFromUser(e.Doer), + Target: audit.TaskTarget(e.Task.ID), + Metadata: map[string]any{ + "other_task_id": e.Relation.OtherTaskID, + "relation_kind": e.Relation.RelationKind, + }, + } + }) + + // Projects + audit.RegisterEventForAudit(func(e *ProjectCreatedEvent) *audit.Entry { + return &audit.Entry{ + Action: audit.ActionProjectCreated, + Actor: auditActorFromUser(e.Doer), + Target: audit.ProjectTarget(e.Project.ID), + } + }) + audit.RegisterEventForAudit(func(e *ProjectUpdatedEvent) *audit.Entry { + return &audit.Entry{ + Action: audit.ActionProjectUpdated, + Actor: auditActorFromUser(e.Doer), + Target: audit.ProjectTarget(e.Project.ID), + } + }) + audit.RegisterEventForAudit(func(e *ProjectDeletedEvent) *audit.Entry { + return &audit.Entry{ + Action: audit.ActionProjectDeleted, + Actor: auditActorFromUser(e.Doer), + Target: audit.ProjectTarget(e.Project.ID), + } + }) + audit.RegisterEventForAudit(func(e *ProjectSharedWithUserEvent) *audit.Entry { + return &audit.Entry{ + Action: audit.ActionProjectSharedWithUser, + Actor: auditActorFromUser(e.Doer), + Target: audit.ProjectTarget(e.Project.ID), + Metadata: map[string]any{"user_id": e.User.ID}, + } + }) + audit.RegisterEventForAudit(func(e *ProjectSharedWithTeamEvent) *audit.Entry { + return &audit.Entry{ + Action: audit.ActionProjectSharedWithTeam, + Actor: auditActorFromUser(e.Doer), + Target: audit.ProjectTarget(e.Project.ID), + Metadata: map[string]any{"team_id": e.Team.ID}, + } + }) + + // Teams + audit.RegisterEventForAudit(func(e *TeamCreatedEvent) *audit.Entry { + return &audit.Entry{ + Action: audit.ActionTeamCreated, + Actor: auditActorFromUser(e.Doer), + Target: audit.TeamTarget(e.Team.ID), + } + }) + audit.RegisterEventForAudit(func(e *TeamDeletedEvent) *audit.Entry { + return &audit.Entry{ + Action: audit.ActionTeamDeleted, + Actor: auditActorFromUser(e.Doer), + Target: audit.TeamTarget(e.Team.ID), + } + }) + audit.RegisterEventForAudit(func(e *TeamMemberAddedEvent) *audit.Entry { + return &audit.Entry{ + Action: audit.ActionTeamMemberAdded, + Actor: auditActorFromUser(e.Doer), + Target: audit.TeamTarget(e.Team.ID), + Metadata: map[string]any{"member_id": e.Member.ID}, + } + }) + audit.RegisterEventForAudit(func(e *TeamMemberRemovedEvent) *audit.Entry { + return &audit.Entry{ + Action: audit.ActionTeamMemberRemoved, + Actor: auditActorFromUser(e.Doer), + Target: audit.TeamTarget(e.Team.ID), + Metadata: map[string]any{"member_id": e.Member.ID}, + } + }) } ////// // Task Events -// IncreaseTaskCounter represents a listener -type IncreaseTaskCounter struct { -} - -// Name defines the name for the IncreaseTaskCounter listener -func (s *IncreaseTaskCounter) Name() string { - return "task.counter.increase" -} - -// Handle is executed when the event IncreaseTaskCounter listens on is fired -func (s *IncreaseTaskCounter) Handle(_ *message.Message) (err error) { - return keyvalue.IncrBy(metrics.TaskCountKey, 1) -} - -// DecreaseTaskCounter represents a listener -type DecreaseTaskCounter struct { -} - -// Name defines the name for the DecreaseTaskCounter listener -func (s *DecreaseTaskCounter) Name() string { - return "task.counter.decrease" -} - -// Handle is executed when the event DecreaseTaskCounter listens on is fired -func (s *DecreaseTaskCounter) Handle(_ *message.Message) (err error) { - return keyvalue.DecrBy(metrics.TaskCountKey, 1) -} - func notifyMentionedUsers(sess *xorm.Session, task *Task, text string, n notifications.NotificationWithSubject) (users map[int64]*user.User, err error) { users, err = FindMentionedUsersInText(sess, text) if err != nil { @@ -209,6 +413,45 @@ func (s *SendTaskCommentNotification) Handle(msg *message.Message) (err error) { return err } + // Authors of comments quoted via
are + // treated as implicit mentions, sharing the same notification, dedup, + // permission and subscription logic. + quotedAuthors, err := findQuotedCommentAuthors(sess, event.Task.ID, event.Doer.ID, event.Comment.Comment) + if err != nil { + return err + } + for _, u := range quotedAuthors { + if _, has := mentionedUsers[u.ID]; has { + continue + } + + can, _, err := event.Task.CanRead(sess, u) + if err != nil { + return err + } + if !can { + continue + } + + dbn, err := notifications.GetNotificationsForNameAndUser(sess, u.ID, n.Name(), n.SubjectID()) + if err != nil { + return err + } + if len(dbn) > 0 { + continue + } + + err = notifications.Notify(u, n, sess) + if err != nil { + return err + } + + if mentionedUsers == nil { + mentionedUsers = make(map[int64]*user.User) + } + mentionedUsers[u.ID] = u + } + subscribers, err := GetSubscriptionsForEntity(sess, SubscriptionEntityTask, event.Task.ID) if err != nil { return err @@ -544,34 +787,6 @@ func (s *HandleTaskUpdateLastUpdated) Handle(msg *message.Message) (err error) { return sess.Commit() } -// IncreaseAttachmentCounter represents a listener -type IncreaseAttachmentCounter struct { -} - -// Name defines the name for the IncreaseAttachmentCounter listener -func (s *IncreaseAttachmentCounter) Name() string { - return "increase.attachment.counter" -} - -// Handle is executed when the event IncreaseAttachmentCounter listens on is fired -func (s *IncreaseAttachmentCounter) Handle(_ *message.Message) (err error) { - return keyvalue.IncrBy(metrics.AttachmentsCountKey, 1) -} - -// DecreaseAttachmentCounter represents a listener -type DecreaseAttachmentCounter struct { -} - -// Name defines the name for the DecreaseAttachmentCounter listener -func (s *DecreaseAttachmentCounter) Name() string { - return "decrease.attachment.counter" -} - -// Handle is executed when the event DecreaseAttachmentCounter listens on is fired -func (s *DecreaseAttachmentCounter) Handle(_ *message.Message) (err error) { - return keyvalue.DecrBy(metrics.AttachmentsCountKey, 1) -} - // UpdateTaskInSavedFilterViews represents a listener type UpdateTaskInSavedFilterViews struct { } @@ -699,28 +914,6 @@ func (l *UpdateTaskInSavedFilterViews) Handle(msg *message.Message) (err error) /////// // Project Event Listeners -type IncreaseProjectCounter struct { -} - -func (s *IncreaseProjectCounter) Name() string { - return "project.counter.increase" -} - -func (s *IncreaseProjectCounter) Handle(_ *message.Message) (err error) { - return keyvalue.IncrBy(metrics.ProjectCountKey, 1) -} - -type DecreaseProjectCounter struct { -} - -func (s *DecreaseProjectCounter) Name() string { - return "project.counter.decrease" -} - -func (s *DecreaseProjectCounter) Handle(_ *message.Message) (err error) { - return keyvalue.DecrBy(metrics.ProjectCountKey, 1) -} - // SendProjectCreatedNotification represents a listener type SendProjectCreatedNotification struct { } @@ -1220,34 +1413,6 @@ func (wl *WebhookListener) Handle(msg *message.Message) (err error) { /////// // Team Events -// IncreaseTeamCounter represents a listener -type IncreaseTeamCounter struct { -} - -// Name defines the name for the IncreaseTeamCounter listener -func (s *IncreaseTeamCounter) Name() string { - return "team.counter.increase" -} - -// Handle is executed when the event IncreaseTeamCounter listens on is fired -func (s *IncreaseTeamCounter) Handle(_ *message.Message) (err error) { - return keyvalue.IncrBy(metrics.TeamCountKey, 1) -} - -// DecreaseTeamCounter represents a listener -type DecreaseTeamCounter struct { -} - -// Name defines the name for the DecreaseTeamCounter listener -func (s *DecreaseTeamCounter) Name() string { - return "team.counter.decrease" -} - -// Handle is executed when the event DecreaseTeamCounter listens on is fired -func (s *DecreaseTeamCounter) Handle(_ *message.Message) (err error) { - return keyvalue.DecrBy(metrics.TeamCountKey, 1) -} - // CleanupTaskAssignmentsAfterTeamRemoval represents a listener type CleanupTaskAssignmentsAfterTeamRemoval struct{} diff --git a/pkg/models/message.go b/pkg/models/message.go index ef51cfc18..48ca786db 100644 --- a/pkg/models/message.go +++ b/pkg/models/message.go @@ -19,5 +19,5 @@ package models // Message is a standard message type Message struct { // A standard message. - Message string `json:"message"` + Message string `json:"message" readOnly:"true" doc:"A human-readable status message returned by the server."` } diff --git a/pkg/models/metrics_count_test.go b/pkg/models/metrics_count_test.go new file mode 100644 index 000000000..c8f64bac6 --- /dev/null +++ b/pkg/models/metrics_count_test.go @@ -0,0 +1,62 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package models + +import ( + "testing" + + "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/metrics" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestMetricsCountFromDatabase verifies that each metric key counts the right table +// straight from the database. This guards the count key -> table name mapping; the +// caching/expiry/invalidation behaviour itself is covered by the keyvalue RememberFor +// tests. +func TestMetricsCountFromDatabase(t *testing.T) { + cases := map[string]string{ + metrics.UserCountKey: "users", + metrics.ProjectCountKey: "projects", + metrics.TaskCountKey: "tasks", + metrics.TeamCountKey: "teams", + metrics.FilesCountKey: "files", + metrics.AttachmentsCountKey: "task_attachments", + } + + db.LoadAndAssertFixtures(t) + + s := db.NewSession() + defer s.Close() + + for key, table := range cases { + t.Run(table, func(t *testing.T) { + // Drop any value cached by a previous test so we recompute from the DB. + require.NoError(t, metrics.InvalidateCount(key)) + + expected, err := s.Table(table).Count() + require.NoError(t, err) + + count, err := metrics.GetCount(key) + require.NoError(t, err) + assert.Equal(t, expected, count) + assert.Positive(t, count, "fixtures should contain at least one %s", table) + }) + } +} diff --git a/pkg/models/models.go b/pkg/models/models.go index df562ef90..88d98231c 100644 --- a/pkg/models/models.go +++ b/pkg/models/models.go @@ -71,6 +71,7 @@ func GetTables() []interface{} { &TaskUnreadStatus{}, &Session{}, &OAuthCode{}, + &TimeEntry{}, } } diff --git a/pkg/models/notifications.go b/pkg/models/notifications.go index 3b1e4680a..4f27d04f4 100644 --- a/pkg/models/notifications.go +++ b/pkg/models/notifications.go @@ -32,6 +32,16 @@ import ( "code.vikunja.io/api/pkg/utils" ) +func init() { + notifications.Register(func() notifications.Notification { return &ReminderDueNotification{} }) + notifications.Register(func() notifications.Notification { return &TaskCommentNotification{} }) + notifications.Register(func() notifications.Notification { return &TaskAssignedNotification{} }) + notifications.Register(func() notifications.Notification { return &TaskDeletedNotification{} }) + notifications.Register(func() notifications.Notification { return &ProjectCreatedNotification{} }) + notifications.Register(func() notifications.Notification { return &TeamMemberAddedNotification{} }) + notifications.Register(func() notifications.Notification { return &UserMentionedInTaskNotification{} }) +} + // getDoerAvatarDataURI returns the avatar data URI for a user, for use in email headers. func getDoerAvatarDataURI(doer *user.User) string { provider := avatar.GetProvider(doer) @@ -55,12 +65,16 @@ type ReminderDueNotification struct { TaskReminder *TaskReminder `json:"reminder"` } +// ToTitle returns the translated one-line title for ReminderDueNotification +func (n *ReminderDueNotification) ToTitle(lang string) string { + return i18n.T(lang, "notifications.task.reminder.subject", n.Task.Title, n.Project.Title) +} + // ToMail returns the mail notification for ReminderDueNotification func (n *ReminderDueNotification) ToMail(lang string) *notifications.Mail { return notifications.NewMail(). IncludeLinkToSettings(lang). To(n.User.Email). - Subject(i18n.T(lang, "notifications.task.reminder.subject", n.Task.Title, n.Project.Title)). Greeting(i18n.T(lang, "notifications.greeting", n.User.GetName())). Line(i18n.T(lang, "notifications.task.reminder.message", notifications.EscapeMarkdown(n.Task.Title), notifications.EscapeMarkdown(n.Project.Title))). Action(i18n.T(lang, "notifications.common.actions.open_task"), config.ServicePublicURL.GetString()+"tasks/"+strconv.FormatInt(n.Task.ID, 10)). @@ -98,6 +112,14 @@ func (n *TaskCommentNotification) SubjectID() int64 { return n.Comment.ID } +// ToTitle returns the translated one-line title for TaskCommentNotification +func (n *TaskCommentNotification) ToTitle(lang string) string { + if n.Mentioned { + return i18n.T(lang, "notifications.task.comment.mentioned_subject", n.Doer.GetName(), n.Task.Title, n.Task.GetFullIdentifier()) + } + return i18n.T(lang, "notifications.task.comment.subject", n.Task.Title, n.Task.GetFullIdentifier()) +} + // ToMail returns the mail notification for TaskCommentNotification func (n *TaskCommentNotification) ToMail(lang string) *notifications.Mail { s := db.NewSession() @@ -106,14 +128,12 @@ func (n *TaskCommentNotification) ToMail(lang string) *notifications.Mail { mail := notifications.NewMail(). Conversational(). - From(n.Doer.GetNameAndFromEmail()). - Subject(i18n.T(lang, "notifications.task.comment.subject", n.Task.Title, n.Task.GetFullIdentifier())) + From(n.Doer.GetNameAndFromEmail()) // Add header line action := i18n.T(lang, "notifications.common.actions.left_comment", n.Doer.GetName()) if n.Mentioned { action = i18n.T(lang, "notifications.common.actions.mentioned_you_comment", n.Doer.GetName()) - mail.Subject(i18n.T(lang, "notifications.task.comment.mentioned_subject", n.Doer.GetName(), n.Task.Title, n.Task.GetFullIdentifier())) } headerLine := notifications.CreateConversationalHeader( @@ -158,13 +178,23 @@ type TaskAssignedNotification struct { Project *Project `json:"project"` } +// ToTitle returns the translated one-line title for TaskAssignedNotification +func (n *TaskAssignedNotification) ToTitle(lang string) string { + if n.Target.ID == n.Assignee.ID { + return i18n.T(lang, "notifications.task.assigned.subject_to_assignee", n.Task.Title, n.Task.GetFullIdentifier()) + } + if n.Doer.ID == n.Assignee.ID { + return i18n.T(lang, "notifications.task.assigned.subject_to_others_self", n.Task.Title, n.Task.GetFullIdentifier(), n.Doer.GetName()) + } + return i18n.T(lang, "notifications.task.assigned.subject_to_others", n.Task.Title, n.Task.GetFullIdentifier(), n.Assignee.GetName()) +} + // ToMail returns the mail notification for TaskAssignedNotification func (n *TaskAssignedNotification) ToMail(lang string) *notifications.Mail { if n.Target.ID == n.Assignee.ID { // Notification to the assignee return notifications.NewMail(). From(n.Doer.GetNameAndFromEmail()). - Subject(i18n.T(lang, "notifications.task.assigned.subject_to_assignee", n.Task.Title, n.Task.GetFullIdentifier())). Greeting(i18n.T(lang, "notifications.greeting", n.Target.GetName())). Line(i18n.T(lang, "notifications.task.assigned.message_to_assignee", notifications.EscapeMarkdown(n.Doer.GetName()), notifications.EscapeMarkdown(n.Task.Title))). Action(i18n.T(lang, "notifications.common.actions.open_task"), n.Task.GetFrontendURL()). @@ -175,7 +205,6 @@ func (n *TaskAssignedNotification) ToMail(lang string) *notifications.Mail { if n.Doer.ID == n.Assignee.ID { return notifications.NewMail(). From(n.Doer.GetNameAndFromEmail()). - Subject(i18n.T(lang, "notifications.task.assigned.subject_to_others_self", n.Task.Title, n.Task.GetFullIdentifier(), n.Doer.GetName())). Greeting(i18n.T(lang, "notifications.greeting", n.Target.GetName())). Line(i18n.T(lang, "notifications.task.assigned.message_to_others_self", notifications.EscapeMarkdown(n.Doer.GetName()))). Action(i18n.T(lang, "notifications.common.actions.open_task"), n.Task.GetFrontendURL()). @@ -185,7 +214,6 @@ func (n *TaskAssignedNotification) ToMail(lang string) *notifications.Mail { // Notification to others about assignment return notifications.NewMail(). From(n.Doer.GetNameAndFromEmail()). - Subject(i18n.T(lang, "notifications.task.assigned.subject_to_others", n.Task.Title, n.Task.GetFullIdentifier(), n.Assignee.GetName())). Greeting(i18n.T(lang, "notifications.greeting", n.Target.GetName())). Line(i18n.T(lang, "notifications.task.assigned.message_to_others", notifications.EscapeMarkdown(n.Doer.GetName()), notifications.EscapeMarkdown(n.Assignee.GetName()))). Action(i18n.T(lang, "notifications.common.actions.open_task"), n.Task.GetFrontendURL()). @@ -213,10 +241,14 @@ type TaskDeletedNotification struct { Task *Task `json:"task"` } +// ToTitle returns the translated one-line title for TaskDeletedNotification +func (n *TaskDeletedNotification) ToTitle(lang string) string { + return i18n.T(lang, "notifications.task.deleted.subject", n.Task.Title, n.Task.GetFullIdentifier()) +} + // ToMail returns the mail notification for TaskDeletedNotification func (n *TaskDeletedNotification) ToMail(lang string) *notifications.Mail { return notifications.NewMail(). - Subject(i18n.T(lang, "notifications.task.deleted.subject", n.Task.Title, n.Task.GetFullIdentifier())). Line(i18n.T(lang, "notifications.task.deleted.message", notifications.EscapeMarkdown(n.Doer.GetName()), notifications.EscapeMarkdown(n.Task.Title), notifications.EscapeMarkdown(n.Task.GetFullIdentifier()))) } @@ -241,10 +273,14 @@ type ProjectCreatedNotification struct { Project *Project `json:"project"` } +// ToTitle returns the translated one-line title for ProjectCreatedNotification +func (n *ProjectCreatedNotification) ToTitle(lang string) string { + return i18n.T(lang, "notifications.project.created", n.Doer.GetName(), n.Project.Title) +} + // ToMail returns the mail notification for ProjectCreatedNotification func (n *ProjectCreatedNotification) ToMail(lang string) *notifications.Mail { return notifications.NewMail(). - Subject(i18n.T(lang, "notifications.project.created", n.Doer.GetName(), n.Project.Title)). Line(i18n.T(lang, "notifications.project.created", notifications.EscapeMarkdown(n.Doer.GetName()), notifications.EscapeMarkdown(n.Project.Title))). Action(i18n.T(lang, "notifications.common.actions.open_project"), config.ServicePublicURL.GetString()+"projects/") } @@ -266,10 +302,14 @@ type TeamMemberAddedNotification struct { Team *Team `json:"team"` } +// ToTitle returns the translated one-line title for TeamMemberAddedNotification +func (n *TeamMemberAddedNotification) ToTitle(lang string) string { + return i18n.T(lang, "notifications.team.member_added.subject", n.Doer.GetName(), n.Team.Name) +} + // ToMail returns the mail notification for TeamMemberAddedNotification func (n *TeamMemberAddedNotification) ToMail(lang string) *notifications.Mail { return notifications.NewMail(). - Subject(i18n.T(lang, "notifications.team.member_added.subject", n.Doer.GetName(), n.Team.Name)). From(n.Doer.GetNameAndFromEmail()). Greeting(i18n.T(lang, "notifications.greeting", n.Member.GetName())). Line(i18n.T(lang, "notifications.team.member_added.message", notifications.EscapeMarkdown(n.Doer.GetName()), notifications.EscapeMarkdown(n.Team.Name))). @@ -385,23 +425,23 @@ func (n *UserMentionedInTaskNotification) SubjectID() int64 { return n.Task.ID } +// ToTitle returns the translated one-line title for UserMentionedInTaskNotification +func (n *UserMentionedInTaskNotification) ToTitle(lang string) string { + if n.IsNew { + return i18n.T(lang, "notifications.task.mentioned.subject_new", n.Doer.GetName(), n.Task.Title, n.Task.GetFullIdentifier()) + } + return i18n.T(lang, "notifications.task.mentioned.subject", n.Doer.GetName(), n.Task.Title, n.Task.GetFullIdentifier()) +} + // ToMail returns the mail notification for UserMentionedInTaskNotification func (n *UserMentionedInTaskNotification) ToMail(lang string) *notifications.Mail { s := db.NewSession() defer s.Close() formattedDescription := formatMentionsForEmail(s, n.Task.Description) - var subject string - if n.IsNew { - subject = i18n.T(lang, "notifications.task.mentioned.subject_new", n.Doer.GetName(), n.Task.Title, n.Task.GetFullIdentifier()) - } else { - subject = i18n.T(lang, "notifications.task.mentioned.subject", n.Doer.GetName(), n.Task.Title, n.Task.GetFullIdentifier()) - } - mail := notifications.NewMail(). Conversational(). - From(n.Doer.GetNameAndFromEmail()). - Subject(subject) + From(n.Doer.GetNameAndFromEmail()) // Add header line action := i18n.T(lang, "notifications.common.actions.mentioned_you", n.Doer.GetName()) diff --git a/pkg/models/notifications_database.go b/pkg/models/notifications_database.go index 3d430b971..75beef580 100644 --- a/pkg/models/notifications_database.go +++ b/pkg/models/notifications_database.go @@ -28,7 +28,7 @@ type DatabaseNotifications struct { // Whether or not to mark this notification as read or unread. // True is read, false is unread. - Read bool `xorm:"-" json:"read"` + Read bool `xorm:"-" json:"read" doc:"Set true to mark the notification read, false to mark it unread."` web.CRUDable `xorm:"-" json:"-"` web.Permissions `xorm:"-" json:"-"` @@ -53,7 +53,13 @@ func (d *DatabaseNotifications) ReadAll(s *xorm.Session, a web.Auth, _ string, p } limit, start := getLimitFromPageIndex(page, perPage) - return notifications.GetNotificationsForUser(s, a.GetID(), limit, start) + ns, resultCount, total, err := notifications.GetNotificationsForUser(s, a.GetID(), limit, start) + if err != nil { + return nil, 0, 0, err + } + + refreshNotificationsUsers(s, ns) + return ns, resultCount, total, nil } // CanUpdate checks if a user can mark a notification as read. diff --git a/pkg/models/notifications_refresh.go b/pkg/models/notifications_refresh.go new file mode 100644 index 000000000..cc119aeae --- /dev/null +++ b/pkg/models/notifications_refresh.go @@ -0,0 +1,110 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package models + +import ( + "encoding/json" + + "code.vikunja.io/api/pkg/log" + "code.vikunja.io/api/pkg/notifications" + "code.vikunja.io/api/pkg/user" + + "xorm.io/xorm" +) + +// refreshNotificationsUsers reloads each notification's embedded users from the +// database. Notifications serialized before the acting user was resolved with +// its full profile (#2720) stored only id+username, so without this they keep +// rendering the auto-generated username instead of the display name. It runs at +// read time and is not persisted; one cache is shared across the batch so a +// user recurring across notifications is fetched only once. +func refreshNotificationsUsers(s *xorm.Session, dbNotifications []*notifications.DatabaseNotification) { + cache := make(map[int64]*user.User) + for _, dbn := range dbNotifications { + refreshNotificationUsers(s, dbn, cache) + } +} + +func refreshNotificationUsers(s *xorm.Session, dbn *notifications.DatabaseNotification, cache map[int64]*user.User) { + typed, ok := notifications.Lookup(dbn.Name) + if !ok { + return + } + + raw, err := json.Marshal(dbn.Notification) + if err != nil { + log.Errorf("Could not marshal notification %d to refresh its users: %v", dbn.ID, err) + return + } + if err := json.Unmarshal(raw, typed); err != nil { + log.Errorf("Could not unmarshal notification %d to refresh its users: %v", dbn.ID, err) + return + } + + for _, u := range notificationUsers(typed) { + refreshUser(s, u, cache) + } + dbn.Notification = typed +} + +// notificationUsers returns the user fields a stored notification renders, so +// they can be reloaded. New notification types carrying a user belong here. +func notificationUsers(n notifications.Notification) []*user.User { + switch n := n.(type) { + case *TaskCommentNotification: + return []*user.User{n.Doer} + case *TaskAssignedNotification: + return []*user.User{n.Doer, n.Assignee} + case *TaskDeletedNotification: + return []*user.User{n.Doer} + case *ProjectCreatedNotification: + return []*user.User{n.Doer} + case *TeamMemberAddedNotification: + return []*user.User{n.Doer, n.Member} + case *UserMentionedInTaskNotification: + return []*user.User{n.Doer} + default: + return nil + } +} + +// refreshUser overwrites the user in place with its current database row. A +// disabled or locked account is still returned fully populated, so only a +// missing user or a real database error leaves the stored value untouched. +func refreshUser(s *xorm.Session, u *user.User, cache map[int64]*user.User) { + if u == nil || u.ID == 0 { + return + } + + fresh, cached := cache[u.ID] + if !cached { + loaded, err := user.GetUserByID(s, u.ID) + if err != nil && !user.IsErrUserStatusError(err) { + if !user.IsErrUserDoesNotExist(err) { + log.Errorf("Could not refresh user %d for a notification: %v", u.ID, err) + } + cache[u.ID] = nil + return + } + fresh = loaded + cache[u.ID] = fresh + } + + if fresh != nil { + *u = *fresh + } +} diff --git a/pkg/models/notifications_refresh_test.go b/pkg/models/notifications_refresh_test.go new file mode 100644 index 000000000..b0cef2a43 --- /dev/null +++ b/pkg/models/notifications_refresh_test.go @@ -0,0 +1,109 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package models + +import ( + "encoding/json" + "testing" + + "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/notifications" + "code.vikunja.io/api/pkg/user" + + "github.com/stretchr/testify/require" + "xorm.io/xorm" +) + +// TestDatabaseNotifications_ReadAll_RefreshesUsers guards #2720 for notifications +// already in the database: those were serialized with a partial doer (id + +// username, no display Name), so reading them must reload the embedded users so +// the display name is shown. The fix in the dispatch path only helps new +// notifications; old rows are healed here at read time. +func TestDatabaseNotifications_ReadAll_RefreshesUsers(t *testing.T) { + t.Run("fills in the display name from the database", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + + // user12 has the display name "Name with spaces" in the fixtures. + insertStoredNotification(t, s, 1, &TaskAssignedNotification{ + Doer: &user.User{ID: 12, Username: "user12"}, + Assignee: &user.User{ID: 12, Username: "user12"}, + Task: &Task{ID: 1}, + }) + + got := readAssignedNotification(t, s, 1) + require.Equal(t, "Name with spaces", got.Doer.GetName()) + require.Equal(t, "Name with spaces", got.Assignee.GetName()) + }) + + t.Run("keeps the stored value when the user no longer exists", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + + insertStoredNotification(t, s, 1, &TaskAssignedNotification{ + Doer: &user.User{ID: 999999, Username: "ghost"}, + Task: &Task{ID: 1}, + }) + + got := readAssignedNotification(t, s, 1) + require.Equal(t, "ghost", got.Doer.Username) + }) + + t.Run("refreshes a disabled user", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + + // user17 is disabled in the fixtures; the reload must still win over the + // stale stored value. + insertStoredNotification(t, s, 1, &TaskAssignedNotification{ + Doer: &user.User{ID: 17, Username: "stale"}, + Task: &Task{ID: 1}, + }) + + got := readAssignedNotification(t, s, 1) + require.Equal(t, "user17", got.Doer.Username) + }) +} + +func insertStoredNotification(t *testing.T, s *xorm.Session, notifiableID int64, n notifications.Notification) { + t.Helper() + content, err := json.Marshal(n) + require.NoError(t, err) + _, err = s.Insert(¬ifications.DatabaseNotification{ + NotifiableID: notifiableID, + Notification: json.RawMessage(content), + Name: n.Name(), + }) + require.NoError(t, err) +} + +func readAssignedNotification(t *testing.T, s *xorm.Session, notifiableID int64) *TaskAssignedNotification { + t.Helper() + result, _, _, err := (&DatabaseNotifications{}).ReadAll(s, &user.User{ID: notifiableID}, "", 1, 50) + require.NoError(t, err) + + for _, dbn := range result.([]*notifications.DatabaseNotification) { + if n, is := dbn.Notification.(*TaskAssignedNotification); is { + return n + } + } + t.Fatal("no task.assigned notification was returned") + return nil +} diff --git a/pkg/models/notifications_test.go b/pkg/models/notifications_test.go index 9e777d896..1aac48239 100644 --- a/pkg/models/notifications_test.go +++ b/pkg/models/notifications_test.go @@ -142,6 +142,90 @@ func TestUndoneTasksOverdueNotification_TitleIsMarkdownEscaped(t *testing.T) { "malicious title must render as literal text") } +func TestTaskCommentNotification_ToTitle(t *testing.T) { + doer := &user.User{ID: 1, Name: "alice", Username: "alice"} + task := &Task{ID: 42, Title: "Take out trash", Index: 7} + + t.Run("regular comment", func(t *testing.T) { + n := &TaskCommentNotification{Doer: doer, Task: task, Mentioned: false} + title := n.ToTitle("en") + assert.Contains(t, title, "Take out trash") + assert.NotContains(t, title, "alice", "regular comment title should not mention the doer") + }) + + t.Run("mention switches title", func(t *testing.T) { + n := &TaskCommentNotification{Doer: doer, Task: task, Mentioned: true} + title := n.ToTitle("en") + assert.Contains(t, title, "alice", "mentioned title should mention the doer") + assert.Contains(t, title, "Take out trash") + }) + + t.Run("regular and mentioned produce different titles", func(t *testing.T) { + regular := (&TaskCommentNotification{Doer: doer, Task: task, Mentioned: false}).ToTitle("en") + mentioned := (&TaskCommentNotification{Doer: doer, Task: task, Mentioned: true}).ToTitle("en") + assert.NotEqual(t, regular, mentioned) + }) +} + +func TestTaskAssignedNotification_ToTitle(t *testing.T) { + doer := &user.User{ID: 1, Name: "alice", Username: "alice"} + assignee := &user.User{ID: 2, Name: "bob", Username: "bob"} + third := &user.User{ID: 3, Name: "carol", Username: "carol"} + task := &Task{ID: 42, Title: "Take out trash", Index: 7} + + t.Run("to assignee themself", func(t *testing.T) { + n := &TaskAssignedNotification{Doer: doer, Task: task, Assignee: assignee, Target: assignee} + title := n.ToTitle("en") + assert.Contains(t, title, "Take out trash") + }) + + t.Run("doer assigned to themself", func(t *testing.T) { + n := &TaskAssignedNotification{Doer: doer, Task: task, Assignee: doer, Target: third} + title := n.ToTitle("en") + assert.Contains(t, title, "alice") + }) + + t.Run("doer assigned someone else, target is third party", func(t *testing.T) { + n := &TaskAssignedNotification{Doer: doer, Task: task, Assignee: assignee, Target: third} + title := n.ToTitle("en") + assert.Contains(t, title, "bob") + }) + + t.Run("three branches produce three distinct titles", func(t *testing.T) { + a := (&TaskAssignedNotification{Doer: doer, Task: task, Assignee: assignee, Target: assignee}).ToTitle("en") + b := (&TaskAssignedNotification{Doer: doer, Task: task, Assignee: doer, Target: third}).ToTitle("en") + c := (&TaskAssignedNotification{Doer: doer, Task: task, Assignee: assignee, Target: third}).ToTitle("en") + assert.NotEqual(t, a, b) + assert.NotEqual(t, a, c) + assert.NotEqual(t, b, c) + }) +} + +func TestUserMentionedInTaskNotification_ToTitle(t *testing.T) { + doer := &user.User{ID: 1, Name: "alice", Username: "alice"} + task := &Task{ID: 42, Title: "Take out trash", Index: 7} + + t.Run("existing task", func(t *testing.T) { + n := &UserMentionedInTaskNotification{Doer: doer, Task: task, IsNew: false} + title := n.ToTitle("en") + assert.Contains(t, title, "alice") + assert.Contains(t, title, "Take out trash") + }) + + t.Run("new task", func(t *testing.T) { + n := &UserMentionedInTaskNotification{Doer: doer, Task: task, IsNew: true} + title := n.ToTitle("en") + assert.Contains(t, title, "alice") + assert.Contains(t, title, "Take out trash") + }) + + t.Run("new task and existing task produce different titles", func(t *testing.T) { + existing := (&UserMentionedInTaskNotification{Doer: doer, Task: task, IsNew: false}).ToTitle("en") + newOne := (&UserMentionedInTaskNotification{Doer: doer, Task: task, IsNew: true}).ToTitle("en") + assert.NotEqual(t, existing, newOne) + }) +} + func TestReminderDueNotification_TitleIsMarkdownEscaped(t *testing.T) { originalPublicURL := config.ServicePublicURL.GetString() t.Cleanup(func() { config.ServicePublicURL.Set(originalPublicURL) }) diff --git a/pkg/models/project.go b/pkg/models/project.go index 55a297b26..b0ad5ee4c 100644 --- a/pkg/models/project.go +++ b/pkg/models/project.go @@ -38,52 +38,52 @@ import ( // Project represents a project of tasks type Project struct { // The unique, numeric id of this project. - ID int64 `xorm:"bigint autoincr not null unique pk" json:"id" param:"project"` + ID int64 `xorm:"bigint autoincr not null unique pk" json:"id" param:"project" readOnly:"true" doc:"The unique, numeric id of this project."` // The title of the project. You'll see this in the overview. - Title string `xorm:"varchar(250) not null" json:"title" valid:"required,runelength(1|250)" minLength:"1" maxLength:"250"` + Title string `xorm:"varchar(250) not null" json:"title" valid:"required,runelength(1|250)" minLength:"1" maxLength:"250" doc:"The title of the project. You'll see this in the overview."` // The description of the project. - Description string `xorm:"longtext null" json:"description"` + Description string `xorm:"longtext null" json:"description" doc:"The description of the project."` // The unique project short identifier. Used to build task identifiers. - Identifier string `xorm:"varchar(10) null" json:"identifier" valid:"runelength(0|10)" minLength:"0" maxLength:"10"` + Identifier string `xorm:"varchar(10) null" json:"identifier" valid:"runelength(0|10)" minLength:"0" maxLength:"10" doc:"The unique project short identifier. Used to build task identifiers (e.g. PROJ-123)."` // The hex color of this project - HexColor string `xorm:"varchar(6) null" json:"hex_color" valid:"runelength(0|7)" maxLength:"7"` + HexColor string `xorm:"varchar(6) null" json:"hex_color" valid:"runelength(0|7)" maxLength:"7" doc:"The hex color of this project, without the leading #."` OwnerID int64 `xorm:"bigint INDEX not null" json:"-"` - ParentProjectID int64 `xorm:"bigint INDEX null" json:"parent_project_id"` + ParentProjectID int64 `xorm:"bigint INDEX null" json:"parent_project_id" doc:"The id of the parent project. 0 if this is a top-level project."` ParentProject *Project `xorm:"-" json:"-"` // The user who created this project. - Owner *user.User `xorm:"-" json:"owner" valid:"-"` + Owner *user.User `xorm:"-" json:"owner" valid:"-" readOnly:"true" doc:"The user who owns this project. Set by the server; ignored on write."` // Whether a project is archived. - IsArchived bool `xorm:"not null default false" json:"is_archived" query:"is_archived"` + IsArchived bool `xorm:"not null default false" json:"is_archived" query:"is_archived" doc:"Whether the project is archived. Archived projects are read-only."` // The id of the file this project has set as background BackgroundFileID int64 `xorm:"null" json:"-"` // Holds extra information about the background set since some background providers require attribution or similar. If not null, the background can be accessed at /projects/{projectID}/background - BackgroundInformation interface{} `xorm:"-" json:"background_information"` + BackgroundInformation interface{} `xorm:"-" json:"background_information" readOnly:"true" doc:"Extra information about the background (e.g. attribution). When not null, the background is available at /projects/{projectID}/background."` // Contains a very small version of the project background to use as a blurry preview until the actual background is loaded. Check out https://blurha.sh/ to learn how it works. - BackgroundBlurHash string `xorm:"varchar(50) null" json:"background_blur_hash"` + BackgroundBlurHash string `xorm:"varchar(50) null" json:"background_blur_hash" readOnly:"true" doc:"A small BlurHash preview of the project background, shown until the real background loads. See https://blurha.sh/."` // True if a project is a favorite. Favorite projects show up in a separate parent project. This value depends on the user making the call to the api. - IsFavorite bool `xorm:"-" json:"is_favorite"` + IsFavorite bool `xorm:"-" json:"is_favorite" doc:"Whether the project is a favorite of the requesting user. This value is per-user and depends on who makes the call."` // The subscription status for the user reading this project. You can only read this property, use the subscription endpoints to modify it. // Will only returned when retreiving one project. - Subscription *Subscription `xorm:"-" json:"subscription,omitempty"` + Subscription *Subscription `xorm:"-" json:"subscription,omitempty" readOnly:"true" doc:"The requesting user's subscription status for this project. Read-only here; use the subscription endpoints to change it. Only returned when retrieving a single project."` // The position this project has when querying all projects. See the tasks.position property on how to use this. - Position float64 `xorm:"double null" json:"position"` + Position float64 `xorm:"double null" json:"position" doc:"The position of this project when listing all projects. See the tasks.position property for how positions work."` - Views []*ProjectView `xorm:"-" json:"views"` + Views []*ProjectView `xorm:"-" json:"views" readOnly:"true" doc:"The views configured for this project. Managed through the project view endpoints."` Expand ProjectExpandable `xorm:"-" json:"-" query:"expand"` - MaxPermission Permission `xorm:"-" json:"max_permission"` + MaxPermission Permission `xorm:"-" json:"max_permission" readOnly:"true" doc:"The maximum permission the requesting user has on this project (0 = read, 1 = read/write, 2 = admin)."` // A timestamp when this project was created. You cannot change this value. - Created time.Time `xorm:"created not null" json:"created"` + Created time.Time `xorm:"created not null" json:"created" readOnly:"true" doc:"A timestamp when this project was created. You cannot change this value."` // A timestamp when this project was last updated. You cannot change this value. - Updated time.Time `xorm:"updated not null" json:"updated"` + Updated time.Time `xorm:"updated not null" json:"updated" readOnly:"true" doc:"A timestamp when this project was last updated. You cannot change this value."` web.CRUDable `xorm:"-" json:"-"` web.Permissions `xorm:"-" json:"-"` @@ -448,6 +448,20 @@ func GetProjectSimpleByID(s *xorm.Session, projectID int64) (project *Project, e return } +// GetProjectSimpleByIdentifier gets a project by its textual identifier (e.g. "PROJ"). +// Identifiers are stored uppercase, so the lookup normalizes the input. +func GetProjectSimpleByIdentifier(s *xorm.Session, identifier string) (project *Project, err error) { + project, exists, err := getProjectSimple(s, builder.Eq{"identifier": strings.ToUpper(identifier)}) + if err != nil { + return nil, err + } + if !exists { + return nil, ErrProjectDoesNotExist{} + } + + return +} + func getProjectSimple(s *xorm.Session, cond builder.Cond) (project *Project, exists bool, err error) { project = &Project{} exists, err = s. @@ -989,6 +1003,11 @@ func checkProjectBeforeUpdateOrDelete(s *xorm.Session, project *Project) (err er } } + // Identifiers are stored uppercase so lookups and the uniqueness check + // below behave consistently across DBs (Postgres/SQLite are + // case-sensitive by default, MySQL is not). + project.Identifier = strings.ToUpper(project.Identifier) + // Check if the identifier is unique and not empty if project.Identifier != "" { exists, err := s. @@ -1053,7 +1072,7 @@ func CreateProject(s *xorm.Session, project *Project, auth web.Auth, createBackl events.DispatchOnCommit(s, &ProjectCreatedEvent{ Project: project, - Doer: doer, + Doer: doerFromAuth(s, auth), }) return nil } @@ -1200,7 +1219,7 @@ func UpdateProject(s *xorm.Session, project *Project, auth web.Auth, updateProje events.DispatchOnCommit(s, &ProjectUpdatedEvent{ Project: project, - Doer: auth, + Doer: doerFromAuth(s, auth), }) l, err := GetProjectSimpleByID(s, project.ID) @@ -1431,7 +1450,7 @@ func (p *Project) Delete(s *xorm.Session, a web.Auth) (err error) { events.DispatchOnCommit(s, &ProjectDeletedEvent{ Project: fullProject, - Doer: a, + Doer: doerFromAuth(s, a), }) childProjects := []*Project{} diff --git a/pkg/models/project_duplicate.go b/pkg/models/project_duplicate.go index 2ad0857ec..6b7c2f87d 100644 --- a/pkg/models/project_duplicate.go +++ b/pkg/models/project_duplicate.go @@ -33,10 +33,12 @@ type ProjectDuplicate struct { // The project id of the project to duplicate ProjectID int64 `json:"-" param:"projectid"` // The target parent project - ParentProjectID int64 `json:"parent_project_id,omitempty"` + ParentProjectID int64 `json:"parent_project_id,omitempty" doc:"The id of the project under which the duplicate should be created. Omit or 0 to place the copy at the top level; you need write access to the parent."` + // Whether to copy the project's shares to the duplicate + DuplicateShares bool `json:"duplicate_shares,omitempty" doc:"Whether to copy the project's user, team and link shares to the duplicate. Defaults to false."` // The copied project - Project *Project `json:"duplicated_project,omitempty"` + Project *Project `json:"duplicated_project,omitempty" readOnly:"true" doc:"The newly created duplicate project, populated by the server in the response."` web.Permissions `json:"-"` web.CRUDable `json:"-"` @@ -62,7 +64,7 @@ func (pd *ProjectDuplicate) CanCreate(s *xorm.Session, a web.Auth) (canCreate bo // Create duplicates a project // @Summary Duplicate an existing project -// @Description Copies the project, tasks, files, kanban data, assignees, comments, attachments, labels, relations, backgrounds, user/team permissions and link shares from one project to a new one. The user needs read access in the project and write access in the parent of the new project. +// @Description Copies the project, tasks, files, kanban data, assignees, comments, attachments, labels, relations and backgrounds from one project to a new one. User/team permissions and link shares are only copied when duplicate_shares is set to true. The user needs read access in the project and write access in the parent of the new project. // @tags project // @Accept json // @Produce json @@ -117,56 +119,58 @@ func (pd *ProjectDuplicate) Create(s *xorm.Session, doer web.Auth) (err error) { return } - // Permissions / Shares - // To keep it simple(r) we will only copy permissions which are directly used with the project, not the parent - users := []*ProjectUser{} - err = s.Where("project_id = ?", pd.ProjectID).Find(&users) - if err != nil { - return - } - for _, u := range users { - u.ID = 0 - u.ProjectID = pd.Project.ID - if _, err := s.Insert(u); err != nil { - return err - } - } - - log.Debugf("Duplicated user shares from project %d into %d", pd.ProjectID, pd.Project.ID) - - teams := []*TeamProject{} - err = s.Where("project_id = ?", pd.ProjectID).Find(&teams) - if err != nil { - return - } - for _, t := range teams { - t.ID = 0 - t.ProjectID = pd.Project.ID - if _, err := s.Insert(t); err != nil { - return err - } - } - - // Generate new link shares if any are available - linkShares := []*LinkSharing{} - err = s.Where("project_id = ?", pd.ProjectID).Find(&linkShares) - if err != nil { - return - } - for _, share := range linkShares { - share.ID = 0 - share.ProjectID = pd.Project.ID - hash, err := utils.CryptoRandomString(40) + if pd.DuplicateShares { + // Permissions / Shares + // To keep it simple(r) we will only copy permissions which are directly used with the project, not the parent + users := []*ProjectUser{} + err = s.Where("project_id = ?", pd.ProjectID).Find(&users) if err != nil { - return err + return } - share.Hash = hash - if _, err := s.Insert(share); err != nil { - return err + for _, u := range users { + u.ID = 0 + u.ProjectID = pd.Project.ID + if _, err := s.Insert(u); err != nil { + return err + } } - } - log.Debugf("Duplicated all link shares from project %d into %d", pd.ProjectID, pd.Project.ID) + log.Debugf("Duplicated user shares from project %d into %d", pd.ProjectID, pd.Project.ID) + + teams := []*TeamProject{} + err = s.Where("project_id = ?", pd.ProjectID).Find(&teams) + if err != nil { + return + } + for _, t := range teams { + t.ID = 0 + t.ProjectID = pd.Project.ID + if _, err := s.Insert(t); err != nil { + return err + } + } + + // Generate new link shares if any are available + linkShares := []*LinkSharing{} + err = s.Where("project_id = ?", pd.ProjectID).Find(&linkShares) + if err != nil { + return + } + for _, share := range linkShares { + share.ID = 0 + share.ProjectID = pd.Project.ID + hash, err := utils.CryptoRandomString(40) + if err != nil { + return err + } + share.Hash = hash + if _, err := s.Insert(share); err != nil { + return err + } + } + + log.Debugf("Duplicated all link shares from project %d into %d", pd.ProjectID, pd.Project.ID) + } err = pd.Project.ReadOne(s, doer) return diff --git a/pkg/models/project_duplicate_test.go b/pkg/models/project_duplicate_test.go index a89342edb..5e3360a1b 100644 --- a/pkg/models/project_duplicate_test.go +++ b/pkg/models/project_duplicate_test.go @@ -25,6 +25,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "xorm.io/xorm" ) func TestProjectDuplicate(t *testing.T) { @@ -38,6 +39,54 @@ func TestProjectDuplicate(t *testing.T) { // (non-Unsplash) background would fail with an internal server error testProjectDuplicate(t, 35, 6) }) + + t.Run("shares are not copied by default", func(t *testing.T) { + files.InitTestFileFixtures(t) + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + + // Project 3 has user, team and link shares + u := &user.User{ID: 3} + l := &ProjectDuplicate{ProjectID: 3} + can, err := l.CanCreate(s, u) + require.NoError(t, err) + assert.True(t, can) + require.NoError(t, l.Create(s, u)) + + assertShareCount(t, s, l.Project.ID, 0, 0, 0) + }) + + t.Run("shares are copied when duplicate_shares is set", func(t *testing.T) { + files.InitTestFileFixtures(t) + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + + // Project 3 has 2 user shares, 1 team share and 1 link share + u := &user.User{ID: 3} + l := &ProjectDuplicate{ProjectID: 3, DuplicateShares: true} + can, err := l.CanCreate(s, u) + require.NoError(t, err) + assert.True(t, can) + require.NoError(t, l.Create(s, u)) + + assertShareCount(t, s, l.Project.ID, 2, 1, 1) + }) +} + +func assertShareCount(t *testing.T, s *xorm.Session, projectID, users, teams, links int64) { + userCount, err := s.Where("project_id = ?", projectID).Count(&ProjectUser{}) + require.NoError(t, err) + assert.Equal(t, users, userCount, "unexpected number of user shares") + + teamCount, err := s.Where("project_id = ?", projectID).Count(&TeamProject{}) + require.NoError(t, err) + assert.Equal(t, teams, teamCount, "unexpected number of team shares") + + linkCount, err := s.Where("project_id = ?", projectID).Count(&LinkSharing{}) + require.NoError(t, err) + assert.Equal(t, links, linkCount, "unexpected number of link shares") } func testProjectDuplicate(t *testing.T, projectID int64, userID int64) { @@ -51,7 +100,8 @@ func testProjectDuplicate(t *testing.T, projectID int64, userID int64) { } l := &ProjectDuplicate{ - ProjectID: projectID, + ProjectID: projectID, + DuplicateShares: true, } can, err := l.CanCreate(s, u) require.NoError(t, err) diff --git a/pkg/models/project_team.go b/pkg/models/project_team.go index f67a1fb26..e6fd75f96 100644 --- a/pkg/models/project_team.go +++ b/pkg/models/project_team.go @@ -29,18 +29,18 @@ import ( // TeamProject defines the relation between a team and a project type TeamProject struct { // The unique, numeric id of this project <-> team relation. - ID int64 `xorm:"bigint autoincr not null unique pk" json:"id"` + ID int64 `xorm:"bigint autoincr not null unique pk" json:"id" readOnly:"true" doc:"The unique, numeric id of this project <-> team relation."` // The team id. - TeamID int64 `xorm:"bigint not null INDEX" json:"team_id" param:"team"` + TeamID int64 `xorm:"bigint not null INDEX" json:"team_id" param:"team" doc:"The id of the team that gets access to the project."` // The project id. ProjectID int64 `xorm:"bigint not null INDEX" json:"-" param:"project"` // The permission this team has. 0 = Read only, 1 = Read & Write, 2 = Admin. See the docs for more details. - Permission Permission `xorm:"bigint INDEX not null default 0" json:"permission" valid:"length(0|2)" maximum:"2" default:"0"` + Permission Permission `xorm:"bigint INDEX not null default 0" json:"permission" valid:"length(0|2)" maximum:"2" default:"0" doc:"The permission this team has on the project: 0 = Read only, 1 = Read & Write, 2 = Admin."` // A timestamp when this relation was created. You cannot change this value. - Created time.Time `xorm:"created not null" json:"created"` + Created time.Time `xorm:"created not null" json:"created" readOnly:"true" doc:"A timestamp when this relation was created. You cannot change this value."` // A timestamp when this relation was last updated. You cannot change this value. - Updated time.Time `xorm:"updated not null" json:"updated"` + Updated time.Time `xorm:"updated not null" json:"updated" readOnly:"true" doc:"A timestamp when this relation was last updated. You cannot change this value."` web.CRUDable `xorm:"-" json:"-"` web.Permissions `xorm:"-" json:"-"` @@ -54,7 +54,7 @@ func (*TeamProject) TableName() string { // TeamWithPermission represents a team, combined with permissions. type TeamWithPermission struct { Team `xorm:"extends"` - Permission Permission `json:"permission"` + Permission Permission `json:"permission" readOnly:"true" doc:"The permission this team has on the project: 0 = Read only, 1 = Read & Write, 2 = Admin."` } // Create creates a new team <-> project relation @@ -112,7 +112,7 @@ func (tl *TeamProject) Create(s *xorm.Session, a web.Auth) (err error) { events.DispatchOnCommit(s, &ProjectSharedWithTeamEvent{ Project: l, Team: team, - Doer: a, + Doer: doerFromAuth(s, a), }) err = updateProjectLastUpdated(s, l) diff --git a/pkg/models/project_users.go b/pkg/models/project_users.go index e57dcd6e9..0789220ce 100644 --- a/pkg/models/project_users.go +++ b/pkg/models/project_users.go @@ -30,20 +30,20 @@ import ( // ProjectUser represents a project <-> user relation type ProjectUser struct { // The unique, numeric id of this project <-> user relation. - ID int64 `xorm:"bigint autoincr not null unique pk" json:"id"` + ID int64 `xorm:"bigint autoincr not null unique pk" json:"id" readOnly:"true" doc:"The unique, numeric id of this project <-> user relation."` // The username. - Username string `xorm:"-" json:"username" param:"user"` + Username string `xorm:"-" json:"username" param:"user" doc:"The username of the user to share with. On update and delete this comes from the URL path, not the body."` // Used internally to reference the user UserID int64 `xorm:"bigint not null INDEX" json:"-"` // The project id. ProjectID int64 `xorm:"bigint not null INDEX" json:"-" param:"project"` // The permission this user has. 0 = Read only, 1 = Read & Write, 2 = Admin. See the docs for more details. - Permission Permission `xorm:"bigint INDEX not null default 0" json:"permission" valid:"length(0|2)" maximum:"2" default:"0"` + Permission Permission `xorm:"bigint INDEX not null default 0" json:"permission" valid:"length(0|2)" maximum:"2" default:"0" doc:"The permission this user has on the project. 0 = Read only, 1 = Read & Write, 2 = Admin."` // A timestamp when this relation was created. You cannot change this value. - Created time.Time `xorm:"created not null" json:"created"` + Created time.Time `xorm:"created not null" json:"created" readOnly:"true" doc:"A timestamp when this relation was created. You cannot change this value."` // A timestamp when this relation was last updated. You cannot change this value. - Updated time.Time `xorm:"updated not null" json:"updated"` + Updated time.Time `xorm:"updated not null" json:"updated" readOnly:"true" doc:"A timestamp when this relation was last updated. You cannot change this value."` web.CRUDable `xorm:"-" json:"-"` web.Permissions `xorm:"-" json:"-"` @@ -57,7 +57,7 @@ func (*ProjectUser) TableName() string { // UserWithPermission represents a user in combination with the permission it can have on a project type UserWithPermission struct { user.User `xorm:"extends"` - Permission Permission `json:"permission"` + Permission Permission `json:"permission" readOnly:"true" doc:"The permission this user has on the project. 0 = Read only, 1 = Read & Write, 2 = Admin."` } // Create creates a new project <-> user relation @@ -118,7 +118,7 @@ func (lu *ProjectUser) Create(s *xorm.Session, a web.Auth) (err error) { events.DispatchOnCommit(s, &ProjectSharedWithUserEvent{ Project: l, User: u, - Doer: a, + Doer: doerFromAuth(s, a), }) err = updateProjectLastUpdated(s, l) diff --git a/pkg/models/project_view.go b/pkg/models/project_view.go index fce6798de..50e20756b 100644 --- a/pkg/models/project_view.go +++ b/pkg/models/project_view.go @@ -23,6 +23,7 @@ import ( "code.vikunja.io/api/pkg/web" + "github.com/danielgtaylor/huma/v2" "xorm.io/xorm" ) @@ -66,6 +67,17 @@ func (p *ProjectViewKind) UnmarshalJSON(bytes []byte) error { return nil } +// Schema lets Huma (/api/v2) reflect this type as a string enum. The custom +// Marshal/UnmarshalJSON above serialize it as a string, but the underlying Go +// type is an int — without this, Huma would generate an integer schema and +// reject the string form clients actually send. +func (*ProjectViewKind) Schema(_ huma.Registry) *huma.Schema { + return &huma.Schema{ + Type: "string", + Enum: []any{"list", "gantt", "table", "kanban"}, + } +} + // NOTE: When adding or changing enum values for ProjectViewKind, // make sure to update the corresponding `enums` tag in the ProjectView struct // to keep the OpenAPI documentation in sync. @@ -123,39 +135,48 @@ func (p *BucketConfigurationModeKind) UnmarshalJSON(bytes []byte) error { return nil } +// Schema lets Huma (/api/v2) reflect this type as a string enum; see the note +// on ProjectViewKind.Schema for why this is needed. +func (*BucketConfigurationModeKind) Schema(_ huma.Registry) *huma.Schema { + return &huma.Schema{ + Type: "string", + Enum: []any{"none", "manual", "filter"}, + } +} + type ProjectViewBucketConfiguration struct { - Title string `json:"title"` - Filter *TaskCollection `json:"filter"` + Title string `json:"title" doc:"The title of the bucket this configuration creates."` + Filter *TaskCollection `json:"filter" doc:"The filter query that decides which tasks land in this bucket. See https://vikunja.io/docs/filters."` } type ProjectView struct { // The unique numeric id of this view - ID int64 `xorm:"autoincr not null unique pk" json:"id" param:"view"` + ID int64 `xorm:"autoincr not null unique pk" json:"id" param:"view" readOnly:"true" doc:"The unique, numeric id of this view. Set by the server."` // The title of this view - Title string `xorm:"varchar(255) not null" json:"title" valid:"required,runelength(1|250)"` + Title string `xorm:"varchar(255) not null" json:"title" valid:"required,runelength(1|250)" minLength:"1" maxLength:"250" doc:"The title of this view."` // The project this view belongs to - ProjectID int64 `xorm:"not null index" json:"project_id" param:"project"` + ProjectID int64 `xorm:"not null index" json:"project_id" param:"project" readOnly:"true" doc:"The project this view belongs to. Taken from the URL path; ignored on write."` // The kind of this view. Can be `list`, `gantt`, `table` or `kanban`. - ViewKind ProjectViewKind `xorm:"not null" json:"view_kind" swaggertype:"string" enums:"list,gantt,table,kanban"` + ViewKind ProjectViewKind `xorm:"not null" json:"view_kind" swaggertype:"string" enums:"list,gantt,table,kanban" doc:"The kind of this view. One of list, gantt, table or kanban."` // The filter query to match tasks by. Check out https://vikunja.io/docs/filters for a full explanation. - Filter *TaskCollection `xorm:"json null default null" query:"filter" json:"filter"` + Filter *TaskCollection `xorm:"json null default null" query:"filter" json:"filter" doc:"The filter query used to match tasks shown in this view. See https://vikunja.io/docs/filters."` // The position of this view in the list. The list of all views will be sorted by this parameter. - Position float64 `xorm:"double null" json:"position"` + Position float64 `xorm:"double null" json:"position" doc:"The position of this view in the project's list of views. Views are sorted ascending by this value."` // The bucket configuration mode. Can be `none`, `manual` or `filter`. `manual` allows to move tasks between buckets as you normally would. `filter` creates buckets based on a filter for each bucket. - BucketConfigurationMode BucketConfigurationModeKind `xorm:"default 0" json:"bucket_configuration_mode" swaggertype:"string" enums:"none,manual,filter,manual"` + BucketConfigurationMode BucketConfigurationModeKind `xorm:"default 0" json:"bucket_configuration_mode" swaggertype:"string" enums:"none,manual,filter" doc:"The bucket configuration mode. One of none, manual or filter. manual lets you move tasks between buckets; filter creates a bucket per filter."` // When the bucket configuration mode is not `manual`, this field holds the options of that configuration. - BucketConfiguration []*ProjectViewBucketConfiguration `xorm:"json" json:"bucket_configuration"` + BucketConfiguration []*ProjectViewBucketConfiguration `xorm:"json" json:"bucket_configuration" doc:"When the bucket configuration mode is filter, holds the title and filter of each bucket."` // The ID of the bucket where new tasks without a bucket are added to. By default, this is the leftmost bucket in a view. - DefaultBucketID int64 `xorm:"bigint INDEX null" json:"default_bucket_id"` + DefaultBucketID int64 `xorm:"bigint INDEX null" json:"default_bucket_id" doc:"The id of the bucket new tasks without a bucket are added to. Defaults to the leftmost bucket."` // If tasks are moved to the done bucket, they are marked as done. If they are marked as done individually, they are moved into the done bucket. - DoneBucketID int64 `xorm:"bigint INDEX null" json:"done_bucket_id"` + DoneBucketID int64 `xorm:"bigint INDEX null" json:"done_bucket_id" doc:"The id of the done bucket. Tasks moved here are marked done, and tasks marked done are moved here."` // A timestamp when this view was updated. You cannot change this value. - Updated time.Time `xorm:"updated not null" json:"updated"` + Updated time.Time `xorm:"updated not null" json:"updated" readOnly:"true" doc:"A timestamp when this view was last updated. You cannot change this value."` // A timestamp when this reaction was created. You cannot change this value. - Created time.Time `xorm:"created not null" json:"created"` + Created time.Time `xorm:"created not null" json:"created" readOnly:"true" doc:"A timestamp when this view was created. You cannot change this value."` web.CRUDable `xorm:"-" json:"-"` web.Permissions `xorm:"-" json:"-"` @@ -248,6 +269,13 @@ func (pv *ProjectView) ReadOne(s *xorm.Session, _ web.Auth) (err error) { // @Failure 500 {object} models.Message "Internal error" // @Router /projects/{project}/views/{id} [delete] func (pv *ProjectView) Delete(s *xorm.Session, _ web.Auth) (err error) { + // Resolve the view under the path project first: the buckets/positions below are deleted by + // view id alone, so without this guard a delete scoped to the wrong parent project would still + // wipe another project's buckets and positions while matching zero project_views rows. + if _, err = GetProjectViewByIDAndProject(s, pv.ID, pv.ProjectID); err != nil { + return err + } + _, err = s. Where("id = ? AND project_id = ?", pv.ID, pv.ProjectID). Delete(&ProjectView{}) @@ -291,6 +319,9 @@ func createProjectView(s *xorm.Session, p *ProjectView, a web.Auth, createBacklo if p.BucketConfigurationMode == BucketConfigurationModeFilter { for _, configuration := range p.BucketConfiguration { + if configuration == nil { + continue + } if configuration.Filter != nil && configuration.Filter.Filter != "" { _, err = getTaskFiltersFromFilterString(configuration.Filter.Filter, configuration.Filter.FilterTimezone) if err != nil { diff --git a/pkg/models/reaction.go b/pkg/models/reaction.go index 712e144a2..9567cfbc9 100644 --- a/pkg/models/reaction.go +++ b/pkg/models/reaction.go @@ -38,7 +38,7 @@ type Reaction struct { ID int64 `xorm:"autoincr not null unique pk" json:"-" param:"reaction"` // The user who reacted - User *user.User `xorm:"-" json:"user" valid:"-"` + User *user.User `xorm:"-" json:"user" valid:"-" readOnly:"true" doc:"The user who reacted. Set by the server from the authenticated user; ignored on write."` UserID int64 `xorm:"bigint not null INDEX" json:"-"` // The id of the entity you're reacting to @@ -48,10 +48,10 @@ type Reaction struct { EntityKindString string `xorm:"-" json:"-" param:"entitykind"` // The actual reaction. This can be any valid utf character or text, up to a length of 20. - Value string `xorm:"varchar(20) not null INDEX" json:"value" valid:"required"` + Value string `xorm:"varchar(20) not null INDEX" json:"value" valid:"required" maxLength:"20" doc:"The reaction itself: any UTF text up to 20 characters, e.g. an emoji."` // A timestamp when this reaction was created. You cannot change this value. - Created time.Time `xorm:"created not null" json:"created"` + Created time.Time `xorm:"created not null" json:"created" readOnly:"true" doc:"A timestamp when this reaction was created. You cannot change this value."` web.CRUDable `xorm:"-" json:"-"` web.Permissions `xorm:"-" json:"-"` diff --git a/pkg/models/saved_filter_positions_test.go b/pkg/models/saved_filter_positions_test.go index d233460d2..91bf09d9c 100644 --- a/pkg/models/saved_filter_positions_test.go +++ b/pkg/models/saved_filter_positions_test.go @@ -79,8 +79,17 @@ func TestCronInsertsNonZeroPosition(t *testing.T) { require.NoError(t, err) require.True(t, exists) + // Force the task to a zero position in this view to simulate the unhealed + // state. A task only ever has one position row per view, so update it if it + // already exists (e.g. created with the filter) instead of inserting a duplicate. tp := &TaskPosition{TaskID: task.ID, ProjectViewID: view.ID, Position: 0} - _, err = s.Insert(tp) + hasPosition, err := s.Where("task_id = ? AND project_view_id = ?", task.ID, view.ID).Exist(&TaskPosition{}) + require.NoError(t, err) + if hasPosition { + _, err = s.Where("task_id = ? AND project_view_id = ?", task.ID, view.ID).Cols("position").Update(tp) + } else { + _, err = s.Insert(tp) + } require.NoError(t, err) _, err = calculateNewPositionForTask(s, u, task, view) diff --git a/pkg/models/saved_filters.go b/pkg/models/saved_filters.go index 26f99ed48..09f28b518 100644 --- a/pkg/models/saved_filters.go +++ b/pkg/models/saved_filters.go @@ -32,25 +32,25 @@ import ( // SavedFilter represents a saved bunch of filters type SavedFilter struct { // The unique numeric id of this saved filter - ID int64 `xorm:"autoincr not null unique pk" json:"id" param:"filter"` + ID int64 `xorm:"autoincr not null unique pk" json:"id" param:"filter" readOnly:"true" doc:"The unique, numeric id of this saved filter."` // The actual filters this filter contains - Filters *TaskCollection `xorm:"JSON not null" json:"filters" valid:"required"` + Filters *TaskCollection `xorm:"JSON not null" json:"filters" valid:"required" doc:"The task filter query and collection options this saved filter wraps."` // The title of the filter. - Title string `xorm:"varchar(250) not null" json:"title" valid:"required,runelength(1|250)" minLength:"1" maxLength:"250"` + Title string `xorm:"varchar(250) not null" json:"title" valid:"required,runelength(1|250)" minLength:"1" maxLength:"250" doc:"The title of the filter."` // The description of the filter - Description string `xorm:"longtext null" json:"description"` + Description string `xorm:"longtext null" json:"description" doc:"The description of the filter."` OwnerID int64 `xorm:"bigint not null INDEX" json:"-"` // The user who owns this filter - Owner *user.User `xorm:"-" json:"owner" valid:"-"` + Owner *user.User `xorm:"-" json:"owner" valid:"-" readOnly:"true" doc:"The user who owns this filter; set by the server."` // True if the filter is a favorite. Favorite filters show up in a separate parent project together with favorite projects. - IsFavorite bool `xorm:"default false" json:"is_favorite"` + IsFavorite bool `xorm:"default false" json:"is_favorite" doc:"If true, the filter shows up in the Favorites pseudo-project alongside favorite projects."` // A timestamp when this filter was created. You cannot change this value. - Created time.Time `xorm:"created not null" json:"created"` + Created time.Time `xorm:"created not null" json:"created" readOnly:"true" doc:"A timestamp when this filter was created. You cannot change this value."` // A timestamp when this filter was last updated. You cannot change this value. - Updated time.Time `xorm:"updated not null" json:"updated"` + Updated time.Time `xorm:"updated not null" json:"updated" readOnly:"true" doc:"A timestamp when this filter was last updated. You cannot change this value."` web.CRUDable `xorm:"-" json:"-"` web.Permissions `xorm:"-" json:"-"` diff --git a/pkg/models/sessions.go b/pkg/models/sessions.go index 06914279c..9824cf0c3 100644 --- a/pkg/models/sessions.go +++ b/pkg/models/sessions.go @@ -36,23 +36,27 @@ import ( // Session represents an active user session with a refresh token. type Session struct { // The session UUID. Embedded in JWTs as the `sid` claim. - ID string `xorm:"varchar(36) not null unique pk" json:"id" param:"session"` + ID string `xorm:"varchar(36) not null unique pk" json:"id" param:"session" readOnly:"true" doc:"The session UUID; embedded in JWTs as the sid claim."` // The owning user. UserID int64 `xorm:"bigint not null index" json:"-"` // SHA-256 hash of the refresh token. Used for lookup on refresh. TokenHash string `xorm:"varchar(64) not null unique index" json:"-"` // The cleartext refresh token. Only populated on session creation, never stored. - RefreshToken string `xorm:"-" json:"refresh_token,omitempty"` + RefreshToken string `xorm:"-" json:"refresh_token,omitempty" readOnly:"true" doc:"The cleartext refresh token; returned only once by the login flow, never on listing."` // User-Agent string from the login request. - DeviceInfo string `xorm:"text" json:"device_info"` + DeviceInfo string `xorm:"text" json:"device_info" readOnly:"true" doc:"User-Agent string captured from the login request."` // IP address from the login request. - IPAddress string `xorm:"varchar(100)" json:"ip_address"` + IPAddress string `xorm:"varchar(100)" json:"ip_address" readOnly:"true" doc:"IP address captured from the login request."` // Whether this is a "remember me" session (controls max refresh lifetime). IsLongSession bool `xorm:"not null default false" json:"-"` + // Raw OIDC ID token, kept so logout can replay it as id_token_hint. Empty for non-OIDC sessions. + OIDCIDToken string `xorm:"text" json:"-"` + // OIDC provider that created this session, used to find its end-session endpoint at logout. + OIDCProviderKey string `xorm:"varchar(250)" json:"-"` // When this session was last refreshed. - LastActive time.Time `xorm:"not null" json:"last_active"` + LastActive time.Time `xorm:"not null" json:"last_active" readOnly:"true" doc:"When this session was last refreshed."` // When this session was created (login time). - Created time.Time `xorm:"created not null" json:"created"` + Created time.Time `xorm:"created not null" json:"created" readOnly:"true" doc:"When this session was created (login time)."` web.Permissions `xorm:"-" json:"-"` web.CRUDable `xorm:"-" json:"-"` @@ -81,9 +85,17 @@ func generateHashedToken() (rawToken, hash string, err error) { return rawToken, HashSessionToken(rawToken), nil } +// SessionOIDCData carries the OIDC metadata persisted on a session so an +// RP-Initiated Logout request can be built later. Nil for non-OIDC logins. +type SessionOIDCData struct { + IDToken string + ProviderKey string +} + // CreateSession creates a new session record and generates a refresh token. // Returns the session with RefreshToken populated (cleartext, shown only once). -func CreateSession(s *xorm.Session, userID int64, deviceInfo, ipAddress string, isLongSession bool) (*Session, error) { +// Pass oidc for OpenID Connect logins to persist the logout data; nil otherwise. +func CreateSession(s *xorm.Session, userID int64, deviceInfo, ipAddress string, isLongSession bool, oidc *SessionOIDCData) (*Session, error) { rawToken, hash, err := generateHashedToken() if err != nil { return nil, err @@ -98,6 +110,10 @@ func CreateSession(s *xorm.Session, userID int64, deviceInfo, ipAddress string, IsLongSession: isLongSession, LastActive: time.Now(), } + if oidc != nil { + session.OIDCIDToken = oidc.IDToken + session.OIDCProviderKey = oidc.ProviderKey + } _, err = s.Insert(session) if err != nil { diff --git a/pkg/models/setup_tests.go b/pkg/models/setup_tests.go index 90b31927a..15b056faa 100644 --- a/pkg/models/setup_tests.go +++ b/pkg/models/setup_tests.go @@ -59,6 +59,7 @@ func SetupTests() { "task_relations", "task_reminders", "tasks", + "time_entries", "team_projects", "team_members", "teams", @@ -78,6 +79,7 @@ func SetupTests() { "webhooks", "totp", "oauth_codes", + "notifications", ) if err != nil { log.Fatal(err) diff --git a/pkg/models/subscription.go b/pkg/models/subscription.go index ad664c23f..4afb59ea3 100644 --- a/pkg/models/subscription.go +++ b/pkg/models/subscription.go @@ -96,18 +96,18 @@ const ( // Subscription represents a subscription for an entity type Subscription struct { // The numeric ID of the subscription - ID int64 `xorm:"autoincr not null unique pk" json:"id"` + ID int64 `xorm:"autoincr not null unique pk" json:"id" readOnly:"true" doc:"The numeric id of the subscription."` - EntityType SubscriptionEntityType `xorm:"index not null" json:"entity"` + EntityType SubscriptionEntityType `xorm:"index not null" json:"entity" readOnly:"true" doc:"The kind of entity this subscription is for. Either project or task; derived server-side from the request path."` Entity string `xorm:"-" json:"-" param:"entity"` // The id of the entity to subscribe to. - EntityID int64 `xorm:"bigint index not null" json:"entity_id" param:"entityID"` + EntityID int64 `xorm:"bigint index not null" json:"entity_id" param:"entityID" readOnly:"true" doc:"The numeric id of the subscribed entity; taken from the request path."` // The user who made this subscription UserID int64 `xorm:"bigint index not null" json:"-"` // A timestamp when this subscription was created. You cannot change this value. - Created time.Time `xorm:"created not null" json:"created"` + Created time.Time `xorm:"created not null" json:"created" readOnly:"true" doc:"A timestamp when this subscription was created. You cannot change this value."` web.CRUDable `xorm:"-" json:"-"` web.Permissions `xorm:"-" json:"-"` diff --git a/pkg/models/task_assignees.go b/pkg/models/task_assignees.go index c2cb20483..662973e80 100644 --- a/pkg/models/task_assignees.go +++ b/pkg/models/task_assignees.go @@ -32,8 +32,8 @@ import ( type TaskAssginee struct { ID int64 `xorm:"bigint autoincr not null unique pk" json:"-"` TaskID int64 `xorm:"bigint INDEX not null" json:"-" param:"projecttask"` - UserID int64 `xorm:"bigint INDEX not null" json:"user_id" param:"user"` - Created time.Time `xorm:"created not null"` + UserID int64 `xorm:"bigint INDEX not null" json:"user_id" param:"user" doc:"The id of the user to assign to the task. The user must have access to the task's project."` + Created time.Time `xorm:"created not null" json:"created" readOnly:"true" doc:"A timestamp when this assignment was created. You cannot change this value."` web.CRUDable `xorm:"-" json:"-"` web.Permissions `xorm:"-" json:"-"` @@ -181,7 +181,7 @@ func (la *TaskAssginee) Delete(s *xorm.Session, a web.Auth) (err error) { return err } - doer, _ := user.GetFromAuth(a) + doer := doerFromAuth(s, a) task, err := GetTaskByIDSimple(s, la.TaskID) if err != nil { return err @@ -270,7 +270,7 @@ func (t *Task) addNewAssigneeByID(s *xorm.Session, newAssigneeID int64, project return err } - doer, _ := user.GetFromAuth(auth) + doer := doerFromAuth(s, auth) task, err := GetTaskSimple(s, &Task{ID: t.ID}) if err != nil { return err @@ -334,17 +334,19 @@ func (la *TaskAssginee) ReadAll(s *xorm.Session, a web.Auth, search string, page } numberOfTotalItems, err = s.Table("task_assignees"). - Select("users.*"). Join("INNER", "users", "task_assignees.user_id = users.id"). - Where("task_id = ? AND users.username LIKE ?", la.TaskID, "%"+search+"%"). - Count(&user.User{}) + Where(builder.And( + builder.Eq{"task_id": la.TaskID}, + db.ILIKE("users.username", search), + )). + Count(&TaskAssginee{}) return taskAssignees, len(taskAssignees), numberOfTotalItems, err } // BulkAssignees is a helper struct used to update multiple assignees at once. type BulkAssignees struct { // A project with all assignees - Assignees []*user.User `json:"assignees"` + Assignees []*user.User `json:"assignees" doc:"The full set of users to assign to the task. This replaces the task's current assignees: users not in this list are unassigned. Pass an empty array to unassign everyone. Each user must have access to the task's project."` TaskID int64 `json:"-" param:"projecttask"` web.CRUDable `json:"-"` diff --git a/pkg/models/task_assignees_test.go b/pkg/models/task_assignees_test.go new file mode 100644 index 000000000..415913d1c --- /dev/null +++ b/pkg/models/task_assignees_test.go @@ -0,0 +1,80 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package models + +import ( + "context" + "testing" + + "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/events" + "code.vikunja.io/api/pkg/user" + + "github.com/stretchr/testify/require" +) + +// TestTaskAssignee_DoerHasDisplayName guards against the regression in #2720: the doer attached to +// notification events was built straight from the JWT (id + username only), so notifications and +// emails rendered the auto-generated username instead of the user's display Name. The dispatch sites +// now resolve the full user from the database, so the doer must carry the display Name even when the +// acting auth object only has id + username (as GetUserFromClaims produces). +func TestTaskAssignee_DoerHasDisplayName(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + + // Mimics the partial user GetUserFromClaims builds from a JWT: id + username, no Name. + // user12 has the display name "Name with spaces" in the fixtures and owns project 23. + doer := &user.User{ID: 12, Username: "user12"} + require.Equal(t, "user12", doer.GetName(), "the auth doer must start without a display name") + + task := &Task{Title: "assign me", ProjectID: 23} + require.NoError(t, task.Create(s, doer)) + + events.ClearDispatchedEvents() + + ta := &TaskAssginee{TaskID: task.ID, UserID: 12} + require.NoError(t, ta.Create(s, doer)) + require.NoError(t, s.Commit()) + + events.DispatchPending(context.Background(), s) + + dispatched := events.GetDispatchedEvents((&TaskAssigneeCreatedEvent{}).Name()) + require.Len(t, dispatched, 1) + ev := dispatched[0].(*TaskAssigneeCreatedEvent) + require.NotNil(t, ev.Doer) + require.Equal(t, "Name with spaces", ev.Doer.GetName(), + "notification doer must carry the display Name, not the username") +} + +// TestDoerFromAuth_DisabledUser ensures resolving the event doer keeps working when acting on behalf +// of a disabled account (e.g. user deletion deletes that user's tasks). The full user is still +// returned with its display name, the disabled status error is swallowed. +func TestDoerFromAuth_DisabledUser(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + + // user17 is disabled in the fixtures. + _, err := user.GetUserByID(s, 17) + require.Error(t, err, "fixture user17 is expected to be disabled") + require.True(t, user.IsErrAccountDisabled(err)) + + doer := doerFromAuth(s, &user.User{ID: 17, Username: "user17"}) + require.NotNil(t, doer) + require.Equal(t, int64(17), doer.ID) +} diff --git a/pkg/models/task_attachment.go b/pkg/models/task_attachment.go index 59d8d7594..327e2311a 100644 --- a/pkg/models/task_attachment.go +++ b/pkg/models/task_attachment.go @@ -23,6 +23,7 @@ import ( "image/png" "io" "strconv" + "strings" "time" "code.vikunja.io/api/pkg/events" @@ -38,16 +39,16 @@ import ( // TaskAttachment is the definition of a task attachment type TaskAttachment struct { - ID int64 `xorm:"bigint autoincr not null unique pk" json:"id" param:"attachment"` - TaskID int64 `xorm:"bigint not null" json:"task_id" param:"task"` + ID int64 `xorm:"bigint autoincr not null unique pk" json:"id" param:"attachment" readOnly:"true" doc:"The unique, numeric id of this attachment."` + TaskID int64 `xorm:"bigint not null" json:"task_id" param:"task" readOnly:"true" doc:"The id of the task this attachment belongs to. Taken from the URL, not the body."` FileID int64 `xorm:"bigint not null" json:"-"` CreatedByID int64 `xorm:"bigint not null" json:"-"` - CreatedBy *user.User `xorm:"-" json:"created_by"` + CreatedBy *user.User `xorm:"-" json:"created_by" readOnly:"true" doc:"The user who uploaded this attachment."` - File *files.File `xorm:"-" json:"file"` + File *files.File `xorm:"-" json:"file" readOnly:"true" doc:"Metadata of the uploaded file (name, mime type, size). The bytes are fetched from the download endpoint, not this field."` - Created time.Time `xorm:"created" json:"created"` + Created time.Time `xorm:"created" json:"created" readOnly:"true" doc:"A timestamp when this attachment was uploaded. You cannot change this value."` web.CRUDable `xorm:"-" json:"-"` web.Permissions `xorm:"-" json:"-"` @@ -106,6 +107,74 @@ func (ta *TaskAttachment) NewAttachment(s *xorm.Session, f io.ReadSeeker, realna return nil } +// AttachmentToUpload is a transport-neutral file to attach, so the upload logic +// can be shared by the multipart v1 handler and the Huma v2 handler. +type AttachmentToUpload struct { + Reader io.ReadSeeker + Filename string + Size uint64 +} + +// UploadTaskAttachments checks create access to the task, then stores each file, +// collecting per-file failures rather than aborting. The caller owns the session +// and the commit. A returned err means the request as a whole failed (e.g. +// forbidden); per-file failures come back in failures instead. +func UploadTaskAttachments(s *xorm.Session, a web.Auth, taskID int64, uploads []*AttachmentToUpload) (success []*TaskAttachment, failures []error, err error) { + ta := &TaskAttachment{TaskID: taskID} + can, err := ta.CanCreate(s, a) + if err != nil { + return nil, nil, err + } + if !can { + return nil, nil, ErrGenericForbidden{} + } + + for _, upload := range uploads { + attachment := &TaskAttachment{TaskID: taskID} + if err := attachment.NewAttachment(s, upload.Reader, upload.Filename, upload.Size, a); err != nil { + failures = append(failures, err) + continue + } + success = append(success, attachment) + } + return success, failures, nil +} + +// LoadTaskAttachmentForDownload checks read access, loads the attachment with its +// open file, and resolves a preview if previewSize is set and the file is an image. +// It returns the loaded attachment and, when applicable, the preview bytes (the +// caller serves those instead of the file). The caller owns the session, the +// commit, and writing the response. Returns ErrGenericForbidden on denied access. +func LoadTaskAttachmentForDownload(s *xorm.Session, a web.Auth, taskID, attachmentID int64, previewSize PreviewSize) (ta *TaskAttachment, preview []byte, err error) { + ta = &TaskAttachment{ID: attachmentID, TaskID: taskID} + can, _, err := ta.CanRead(s, a) + if err != nil { + return nil, nil, err + } + if !can { + return nil, nil, ErrGenericForbidden{} + } + + if err := ta.ReadOne(s, a); err != nil { + return nil, nil, err + } + if err := ta.File.LoadFileByID(); err != nil { + return nil, nil, err + } + + if previewSize != PreviewSizeUnknown && strings.HasPrefix(ta.File.Mime, "image") { + preview = ta.GetPreview(previewSize) + // GetPreview consumes the file reader; re-open it for the non-preview fallback. + if preview == nil { + if err := ta.File.LoadFileByID(); err != nil { + return nil, nil, err + } + } + } + + return ta, preview, nil +} + // ReadOne returns a task attachment func (ta *TaskAttachment) ReadOne(s *xorm.Session, _ web.Auth) (err error) { query := s.Where("id = ?", ta.ID).NoAutoCondition() diff --git a/pkg/models/task_collection.go b/pkg/models/task_collection.go index 4651f72bc..bc217f7ca 100644 --- a/pkg/models/task_collection.go +++ b/pkg/models/task_collection.go @@ -32,22 +32,20 @@ type TaskCollection struct { ProjectID int64 `param:"project" json:"-"` ProjectViewID int64 `param:"view" json:"-"` - Search string `query:"s" json:"s"` + Search string `query:"s" json:"s" doc:"A search term to match tasks by their title."` // The query parameter to sort by. This is for ex. done, priority, etc. - SortBy []string `query:"sort_by" json:"sort_by"` - SortByArr []string `query:"sort_by[]" json:"-"` + SortBy []string `query:"sort_by" json:"sort_by" doc:"The fields to sort by, for example done or priority."` // The query parameter to order the items by. This can be either asc or desc, with asc being the default. - OrderBy []string `query:"order_by" json:"order_by"` - OrderByArr []string `query:"order_by[]" json:"-"` + OrderBy []string `query:"order_by" json:"order_by" doc:"The order for each sort_by field, either asc or desc. Defaults to asc."` // The filter query to match tasks by. Check out https://vikunja.io/docs/filters for a full explanation. - Filter string `query:"filter" json:"filter"` + Filter string `query:"filter" json:"filter" doc:"The filter query to match tasks by. See https://vikunja.io/docs/filters."` // The time zone which should be used for date match (statements like "now" resolve to different actual times) FilterTimezone string `query:"filter_timezone" json:"-"` // If set to true, the result will also include null values - FilterIncludeNulls bool `query:"filter_include_nulls" json:"filter_include_nulls"` + FilterIncludeNulls bool `query:"filter_include_nulls" json:"filter_include_nulls" doc:"If true, the result also includes tasks whose filtered field is null."` // If set to `subtasks`, Vikunja will fetch only tasks which do not have subtasks and then in a // second step, will fetch all of these subtasks. This may result in more tasks than the @@ -56,11 +54,16 @@ type TaskCollection struct { // If set to `reactions`, the reactions of each task will be present in the response. // If set to `comments`, the first 50 comments of each task will be present in the response. // You can set this multiple times with different values. - Expand []TaskCollectionExpandable `query:"expand" json:"-"` - ExpandArr []TaskCollectionExpandable `query:"expand[]" json:"-"` + Expand []TaskCollectionExpandable `query:"expand" json:"-"` isSavedFilter bool + // forceFlatTasks makes ReadAll always return []*Task, never []*Bucket, even + // for a kanban view. v1's single tasks endpoint is polymorphic; v2 splits it + // into a flat-tasks endpoint and a separate buckets-with-tasks one, and the + // former sets this so a kanban view path still yields tasks. + forceFlatTasks bool + web.CRUDable `xorm:"-" json:"-"` web.Permissions `xorm:"-" json:"-"` } @@ -72,6 +75,7 @@ const TaskCollectionExpandBuckets TaskCollectionExpandable = `buckets` const TaskCollectionExpandReactions TaskCollectionExpandable = `reactions` const TaskCollectionExpandComments TaskCollectionExpandable = `comments` const TaskCollectionExpandCommentCount TaskCollectionExpandable = `comment_count` +const TaskCollectionExpandTimeEntriesCount TaskCollectionExpandable = `time_entries_count` const TaskCollectionExpandIsUnread TaskCollectionExpandable = `is_unread` // Validate validates if the TaskCollectionExpandable value is valid. @@ -87,11 +91,13 @@ func (t TaskCollectionExpandable) Validate() error { return nil case TaskCollectionExpandCommentCount: return nil + case TaskCollectionExpandTimeEntriesCount: + return nil case TaskCollectionExpandIsUnread: return nil } - return InvalidFieldErrorWithMessage([]string{"expand"}, "Expand must be one of the following values: subtasks, buckets, reactions, comments, comment_count, is_unread") + return InvalidFieldErrorWithMessage([]string{"expand"}, "Expand must be one of the following values: subtasks, buckets, reactions, comments, comment_count, time_entries_count, is_unread") } func validateTaskField(fieldName string) error { @@ -107,18 +113,6 @@ func validateTaskField(fieldName string) error { } func getTaskFilterOptsFromCollection(tf *TaskCollection, projectView *ProjectView) (opts *taskSearchOptions, err error) { - if len(tf.SortByArr) > 0 { - tf.SortBy = append(tf.SortBy, tf.SortByArr...) - } - - if len(tf.OrderByArr) > 0 { - tf.OrderBy = append(tf.OrderBy, tf.OrderByArr...) - } - - if len(tf.ExpandArr) > 0 { - tf.Expand = append(tf.Expand, tf.ExpandArr...) - } - var sort = make([]*sortParam, 0, len(tf.SortBy)) for i, s := range tf.SortBy { param := &sortParam{ @@ -161,8 +155,14 @@ func getTaskFilterOptsFromCollection(tf *TaskCollection, projectView *ProjectVie return opts, err } -func getTaskOrTasksInBuckets(s *xorm.Session, a web.Auth, projects []*Project, view *ProjectView, opts *taskSearchOptions, filteringForBucket bool) (tasks interface{}, resultCount int, totalItems int64, err error) { - if filteringForBucket { +// SetForceFlatTasks makes ReadAll return a flat []*Task even for a kanban view. +// The v2 tasks endpoint uses it; v1 leaves it unset for the polymorphic shape. +func (tf *TaskCollection) SetForceFlatTasks() { + tf.forceFlatTasks = true +} + +func getTaskOrTasksInBuckets(s *xorm.Session, a web.Auth, projects []*Project, view *ProjectView, opts *taskSearchOptions, filteringForBucket, forceFlatTasks bool) (tasks interface{}, resultCount int, totalItems int64, err error) { + if filteringForBucket || forceFlatTasks { return getTasksForProjects(s, projects, a, opts, view) } @@ -272,18 +272,12 @@ func (tf *TaskCollection) ReadAll(s *xorm.Session, a web.Auth, search string, pa // By prepending sort options before the saved ones from the filter, we make sure the supplied sort // options via query take precedence over the rest. - sortby := append(tf.SortBy, tf.SortByArr...) - sortby = append(sortby, sf.Filters.SortBy...) - sortby = append(sortby, sf.Filters.SortByArr...) + sortby := append(tf.SortBy, sf.Filters.SortBy...) - orderby := append(tf.OrderBy, tf.OrderByArr...) - orderby = append(orderby, sf.Filters.OrderBy...) - orderby = append(orderby, sf.Filters.OrderByArr...) + orderby := append(tf.OrderBy, sf.Filters.OrderBy...) sf.Filters.SortBy = sortby - sf.Filters.SortByArr = nil sf.Filters.OrderBy = orderby - sf.Filters.OrderByArr = nil if sf.Filters.FilterTimezone == "" { u, err := user.GetUserByID(s, a.GetID()) @@ -297,8 +291,8 @@ func (tf *TaskCollection) ReadAll(s *xorm.Session, a web.Auth, search string, pa tc.ProjectViewID = tf.ProjectViewID tc.ProjectID = tf.ProjectID tc.isSavedFilter = true - tc.Expand = append(tf.Expand, tf.ExpandArr...) - tc.ExpandArr = nil + tc.Expand = tf.Expand + tc.forceFlatTasks = tf.forceFlatTasks if tf.Filter != "" { if tc.Filter != "" { @@ -391,7 +385,7 @@ func (tf *TaskCollection) ReadAll(s *xorm.Session, a web.Auth, search string, pa if err != nil { return nil, 0, 0, err } - return getTaskOrTasksInBuckets(s, a, []*Project{project}, view, opts, filteringForBucket) + return getTaskOrTasksInBuckets(s, a, []*Project{project}, view, opts, filteringForBucket, tf.forceFlatTasks) } projects, err := getRelevantProjectsFromCollection(s, a, tf) @@ -399,5 +393,5 @@ func (tf *TaskCollection) ReadAll(s *xorm.Session, a web.Auth, search string, pa return nil, 0, 0, err } - return getTaskOrTasksInBuckets(s, a, projects, view, opts, filteringForBucket) + return getTaskOrTasksInBuckets(s, a, projects, view, opts, filteringForBucket, tf.forceFlatTasks) } diff --git a/pkg/models/task_collection_filter.go b/pkg/models/task_collection_filter.go index 7d2970277..6828b0e07 100644 --- a/pkg/models/task_collection_filter.go +++ b/pkg/models/task_collection_filter.go @@ -170,20 +170,16 @@ func parseFilterFromExpression(f fexpr.ExprGroup, loc *time.Location) (filter *t return filter, nil } -func getTaskFiltersFromFilterString(filter string, filterTimezone string) (filters []*taskFilter, err error) { - - if filter == "" { - return - } - +// preprocessFilterString rewrites the human filter syntax (in / not in / like) +// into fexpr sigils and quotes bare values so fexpr.Parse accepts them. Shared +// by every entity that filters with the task grammar. +func preprocessFilterString(filter string) string { filter = strings.ReplaceAll(filter, " not in ", " "+string(fexpr.SignAnyNeq)+" ") filter = strings.ReplaceAll(filter, " in ", " ?= ") filter = strings.ReplaceAll(filter, " like ", " ~ ") - // Regex pattern to match filter expressions re := regexp.MustCompile(`(\w+)\s*(>=|<=|!=|~|\?=|\?!=|=|>|<)\s*([^&|()]+)`) - - filter = re.ReplaceAllStringFunc(filter, func(match string) string { + return re.ReplaceAllStringFunc(filter, func(match string) string { parts := re.FindStringSubmatch(match) if len(parts) != 4 { return match @@ -193,16 +189,24 @@ func getTaskFiltersFromFilterString(filter string, filterTimezone string) (filte comparator := parts[2] value := strings.TrimSpace(parts[3]) - // Check if the value is already quoted + // Already quoted — leave as-is if (strings.HasPrefix(value, "'") && strings.HasSuffix(value, "'")) || (strings.HasPrefix(value, "\"") && strings.HasSuffix(value, "\"")) { return field + " " + comparator + " " + value } - // Quote the value quotedValue := "'" + strings.ReplaceAll(value, "'", "\\'") + "'" return field + " " + comparator + " " + quotedValue }) +} + +func getTaskFiltersFromFilterString(filter string, filterTimezone string) (filters []*taskFilter, err error) { + + if filter == "" { + return + } + + filter = preprocessFilterString(filter) parsedFilter, err := fexpr.Parse(filter) if err != nil { diff --git a/pkg/models/task_collection_test.go b/pkg/models/task_collection_test.go index 943181f17..e66d945b2 100644 --- a/pkg/models/task_collection_test.go +++ b/pkg/models/task_collection_test.go @@ -95,7 +95,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { ID: 1, Title: "task #1", Description: "Lorem Ipsum", - Identifier: "test1-1", + Identifier: "TEST1-1", Index: 1, CreatedByID: 1, CreatedBy: user1, @@ -168,7 +168,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task2 := &Task{ ID: 2, Title: "task #2 done", - Identifier: "test1-2", + Identifier: "TEST1-2", Index: 2, Done: true, CreatedByID: 1, @@ -198,7 +198,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task3 := &Task{ ID: 3, Title: "task #3 high prio", - Identifier: "test1-3", + Identifier: "TEST1-3", Index: 3, CreatedByID: 1, CreatedBy: user1, @@ -211,7 +211,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task4 := &Task{ ID: 4, Title: "task #4 low prio", - Identifier: "test1-4", + Identifier: "TEST1-4", Index: 4, CreatedByID: 1, CreatedBy: user1, @@ -224,7 +224,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task5 := &Task{ ID: 5, Title: "task #5 higher due date", - Identifier: "test1-5", + Identifier: "TEST1-5", Index: 5, CreatedByID: 1, CreatedBy: user1, @@ -238,7 +238,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { ID: 6, Title: "task #6 lower due date", Description: "This has something unique", - Identifier: "test1-6", + Identifier: "TEST1-6", Index: 6, CreatedByID: 1, CreatedBy: user1, @@ -251,7 +251,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task7 := &Task{ ID: 7, Title: "task #7 with start date", - Identifier: "test1-7", + Identifier: "TEST1-7", Index: 7, CreatedByID: 1, CreatedBy: user1, @@ -264,7 +264,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task8 := &Task{ ID: 8, Title: "task #8 with end date", - Identifier: "test1-8", + Identifier: "TEST1-8", Index: 8, CreatedByID: 1, CreatedBy: user1, @@ -277,7 +277,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task9 := &Task{ ID: 9, Title: "task #9 with start and end date", - Identifier: "test1-9", + Identifier: "TEST1-9", Index: 9, CreatedByID: 1, CreatedBy: user1, @@ -291,7 +291,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task10 := &Task{ ID: 10, Title: "task #10 basic", - Identifier: "test1-10", + Identifier: "TEST1-10", Index: 10, CreatedByID: 1, CreatedBy: user1, @@ -303,7 +303,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task11 := &Task{ ID: 11, Title: "task #11 basic", - Identifier: "test1-11", + Identifier: "TEST1-11", Index: 11, CreatedByID: 1, CreatedBy: user1, @@ -315,7 +315,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task12 := &Task{ ID: 12, Title: "task #12 basic", - Identifier: "test1-12", + Identifier: "TEST1-12", Index: 12, CreatedByID: 1, CreatedBy: user1, @@ -327,7 +327,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task15 := &Task{ ID: 15, Title: "task #15", - Identifier: "test6-1", + Identifier: "TEST6-1", Index: 1, CreatedByID: 6, CreatedBy: user6, @@ -340,7 +340,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task16 := &Task{ ID: 16, Title: "task #16", - Identifier: "test7-1", + Identifier: "TEST7-1", Index: 1, CreatedByID: 6, CreatedBy: user6, @@ -352,7 +352,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task17 := &Task{ ID: 17, Title: "task #17", - Identifier: "test8-1", + Identifier: "TEST8-1", Index: 1, CreatedByID: 6, CreatedBy: user6, @@ -364,7 +364,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task18 := &Task{ ID: 18, Title: "task #18", - Identifier: "test9-1", + Identifier: "TEST9-1", Index: 1, CreatedByID: 6, CreatedBy: user6, @@ -376,7 +376,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task19 := &Task{ ID: 19, Title: "task #19", - Identifier: "test10-1", + Identifier: "TEST10-1", Index: 1, CreatedByID: 6, CreatedBy: user6, @@ -388,7 +388,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task20 := &Task{ ID: 20, Title: "task #20", - Identifier: "test11-1", + Identifier: "TEST11-1", Index: 1, CreatedByID: 6, CreatedBy: user6, @@ -436,7 +436,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task24 := &Task{ ID: 24, Title: "task #24", - Identifier: "test15-1", + Identifier: "TEST15-1", Index: 1, CreatedByID: 6, CreatedBy: user6, @@ -448,7 +448,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task25 := &Task{ ID: 25, Title: "task #25", - Identifier: "test16-1", + Identifier: "TEST16-1", Index: 1, CreatedByID: 6, CreatedBy: user6, @@ -460,7 +460,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task26 := &Task{ ID: 26, Title: "task #26", - Identifier: "test17-1", + Identifier: "TEST17-1", Index: 1, CreatedByID: 6, CreatedBy: user6, @@ -472,7 +472,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task27 := &Task{ ID: 27, Title: "task #27 with reminders and start_date", - Identifier: "test1-18", + Identifier: "TEST1-18", Index: 18, CreatedByID: 1, CreatedBy: user1, @@ -501,7 +501,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task28 := &Task{ ID: 28, Title: "task #28 with repeat after, start_date, end_date and due_date", - Identifier: "test1-13", + Identifier: "TEST1-13", Index: 13, CreatedByID: 1, CreatedBy: user1, @@ -517,7 +517,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task29 := &Task{ ID: 29, Title: "task #29 with parent task (1)", - Identifier: "test1-14", + Identifier: "TEST1-14", Index: 14, CreatedByID: 1, CreatedBy: user1, @@ -543,7 +543,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task30 := &Task{ ID: 30, Title: "task #30 with assignees", - Identifier: "test1-15", + Identifier: "TEST1-15", Index: 15, CreatedByID: 1, CreatedBy: user1, @@ -559,7 +559,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task31 := &Task{ ID: 31, Title: "task #31 with color", - Identifier: "test1-16", + Identifier: "TEST1-16", Index: 16, HexColor: "f0f0f0", CreatedByID: 1, @@ -572,7 +572,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task32 := &Task{ ID: 32, Title: "task #32", - Identifier: "test3-1", + Identifier: "TEST3-1", Index: 1, CreatedByID: 1, CreatedBy: user1, @@ -584,7 +584,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task33 := &Task{ ID: 33, Title: "task #33 with percent done", - Identifier: "test1-17", + Identifier: "TEST1-17", Index: 17, CreatedByID: 1, CreatedBy: user1, @@ -608,7 +608,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task47 := &Task{ ID: 47, Title: "task #47 with reminders outside window", - Identifier: "test1-32", + Identifier: "TEST1-32", Index: 32, CreatedByID: 1, CreatedBy: user1, @@ -635,7 +635,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { ID: 48, Title: "Landingpages update", Description: "Update all landingpages with new branding", - Identifier: "test1-33", + Identifier: "TEST1-33", Index: 33, CreatedByID: 1, CreatedBy: user1, diff --git a/pkg/models/task_comments.go b/pkg/models/task_comments.go index e079f54e0..37cbf7e87 100644 --- a/pkg/models/task_comments.go +++ b/pkg/models/task_comments.go @@ -30,18 +30,18 @@ import ( // TaskComment represents a task comment type TaskComment struct { - ID int64 `xorm:"autoincr pk unique not null" json:"id" param:"commentid"` - Comment string `xorm:"text not null" json:"comment" valid:"dbtext,required"` + ID int64 `xorm:"autoincr pk unique not null" json:"id" param:"commentid" readOnly:"true" doc:"The unique, numeric id of this comment."` + Comment string `xorm:"text not null" json:"comment" valid:"dbtext,required" doc:"The comment text. May contain HTML; mentions are parsed and notify the mentioned users."` AuthorID int64 `xorm:"not null" json:"-"` - Author *user.User `xorm:"-" json:"author"` + Author *user.User `xorm:"-" json:"author" readOnly:"true" doc:"The user who wrote the comment. Set from the authenticated user on create; ignored on write."` TaskID int64 `xorm:"index not null" json:"-" param:"task"` - Reactions ReactionMap `xorm:"-" json:"reactions"` + Reactions ReactionMap `xorm:"-" json:"reactions" readOnly:"true" doc:"The reactions on this comment, keyed by reaction value. Managed through the reactions endpoints, not by writing here."` OrderBy string `xorm:"-" json:"-" query:"order_by"` - Created time.Time `xorm:"created" json:"created"` - Updated time.Time `xorm:"updated" json:"updated"` + Created time.Time `xorm:"created" json:"created" readOnly:"true" doc:"A timestamp when this comment was created. You cannot change this value."` + Updated time.Time `xorm:"updated" json:"updated" readOnly:"true" doc:"A timestamp when this comment was last updated. You cannot change this value."` web.CRUDable `xorm:"-" json:"-"` web.Permissions `xorm:"-" json:"-"` @@ -167,7 +167,7 @@ func (tc *TaskComment) Delete(s *xorm.Session, a web.Auth) error { // @Failure 404 {object} web.HTTPError "The task comment was not found." // @Failure 500 {object} models.Message "Internal error" // @Router /tasks/{taskID}/comments/{commentID} [post] -func (tc *TaskComment) Update(s *xorm.Session, _ web.Auth) error { +func (tc *TaskComment) Update(s *xorm.Session, a web.Auth) error { updated, err := s. ID(tc.ID). Cols("comment"). @@ -185,10 +185,17 @@ func (tc *TaskComment) Update(s *xorm.Session, _ web.Auth) error { return err } + // Resolve the doer from the session, not from tc.Author: the latter is bound + // from the request body and could be omitted (nil) or spoofed. + doer, err := GetUserOrLinkShareUser(s, a) + if err != nil { + return err + } + events.DispatchOnCommit(s, &TaskCommentUpdatedEvent{ Task: &task, Comment: tc, - Doer: tc.Author, + Doer: doer, }) return nil } diff --git a/pkg/models/task_comments_test.go b/pkg/models/task_comments_test.go index 61f8f6dc4..988dc4f27 100644 --- a/pkg/models/task_comments_test.go +++ b/pkg/models/task_comments_test.go @@ -17,6 +17,7 @@ package models import ( + "context" "fmt" "testing" @@ -45,7 +46,7 @@ func TestTaskComment_Create(t *testing.T) { assert.Equal(t, int64(1), tc.Author.ID) err = s.Commit() require.NoError(t, err) - events.DispatchPending(s) + events.DispatchPending(context.Background(), s) events.AssertDispatched(t, &TaskCommentCreatedEvent{}) db.AssertExists(t, "task_comments", map[string]interface{}{ diff --git a/pkg/models/task_duplicate.go b/pkg/models/task_duplicate.go index 175834c7a..ed737a507 100644 --- a/pkg/models/task_duplicate.go +++ b/pkg/models/task_duplicate.go @@ -32,7 +32,7 @@ type TaskDuplicate struct { TaskID int64 `json:"-" param:"projecttask"` // The duplicated task - Task *Task `json:"duplicated_task,omitempty"` + Task *Task `json:"duplicated_task,omitempty" readOnly:"true" doc:"The newly created duplicate task, populated by the server in the response."` web.Permissions `json:"-"` web.CRUDable `json:"-"` diff --git a/pkg/models/task_position.go b/pkg/models/task_position.go index 325033207..bcc09884b 100644 --- a/pkg/models/task_position.go +++ b/pkg/models/task_position.go @@ -33,9 +33,9 @@ const MinPositionSpacing = 0.01 type TaskPosition struct { // The ID of the task this position is for - TaskID int64 `xorm:"bigint not null index" json:"task_id" param:"task"` + TaskID int64 `xorm:"bigint not null index unique(task_view)" json:"task_id" param:"task" readOnly:"true" doc:"The numeric id of the task this position belongs to. Taken from the URL; ignored in the request body."` // The project view this task is related to - ProjectViewID int64 `xorm:"bigint not null index" json:"project_view_id"` + ProjectViewID int64 `xorm:"bigint not null index unique(task_view)" json:"project_view_id" doc:"The id of the project view this position applies to. Positions are stored per view, so the same task has an independent position in each of its project's views."` // The position of the task - any task project can be sorted as usual by this parameter. // When accessing tasks via kanban buckets, this is primarily used to sort them based on a range // We're using a float64 here to make it possible to put any task within any two other tasks (by changing the number). @@ -44,7 +44,7 @@ type TaskPosition struct { // which also leaves a lot of room for rearranging and sorting later. // Positions are always saved per view. They will automatically be set if you request the tasks through a view // endpoint, otherwise they will always be 0. To update them, take a look at the Task Position endpoint. - Position float64 `xorm:"double not null" json:"position"` + Position float64 `xorm:"double not null" json:"position" doc:"The task's sort position within the view, as a float so a task can be placed between any two others. To drop a task between two neighbours, set this to their midpoint. Values below the minimum spacing trigger a server-side recalculation of all positions in the view, so the stored value may differ from what you sent."` web.CRUDable `xorm:"-" json:"-"` web.Permissions `xorm:"-" json:"-"` @@ -341,6 +341,57 @@ func calculateNewPositionForTask(s *xorm.Session, a web.Auth, t *Task, view *Pro }, nil } +type taskPositionKey struct { + taskID int64 + viewID int64 +} + +// filterNewTaskPositions returns the positions whose (task_id, project_view_id) +// row does not exist yet, also deduplicating within the slice. Position creation +// during task creation can trigger a full recalculation (calculateNewPositionForTask +// or moveTaskToDoneBuckets) that already persists rows for the new task, so inserting +// the queued positions unconditionally would violate the unique index on +// (task_id, project_view_id). +func filterNewTaskPositions(s *xorm.Session, positions []*TaskPosition) ([]*TaskPosition, error) { + if len(positions) == 0 { + return positions, nil + } + + taskIDs := make([]int64, 0, len(positions)) + seenTask := make(map[int64]bool, len(positions)) + for _, p := range positions { + if seenTask[p.TaskID] { + continue + } + seenTask[p.TaskID] = true + taskIDs = append(taskIDs, p.TaskID) + } + + // Fetch all existing rows for the involved tasks in one query so this stays + // cheap when createTask runs in a loop (bulk import, project duplication). + existing := []*TaskPosition{} + err := s.In("task_id", taskIDs).Find(&existing) + if err != nil { + return nil, err + } + + seen := make(map[taskPositionKey]bool, len(positions)+len(existing)) + for _, e := range existing { + seen[taskPositionKey{taskID: e.TaskID, viewID: e.ProjectViewID}] = true + } + + filtered := make([]*TaskPosition, 0, len(positions)) + for _, p := range positions { + key := taskPositionKey{taskID: p.TaskID, viewID: p.ProjectViewID} + if seen[key] { + continue + } + seen[key] = true + filtered = append(filtered, p) + } + return filtered, nil +} + // DeleteOrphanedTaskPositions removes task position records that reference // tasks or project views that no longer exist. // If dryRun is true, it counts the orphaned records without deleting them. diff --git a/pkg/models/task_relation.go b/pkg/models/task_relation.go index 58280c5ee..392ea2ef4 100644 --- a/pkg/models/task_relation.go +++ b/pkg/models/task_relation.go @@ -81,18 +81,19 @@ type TaskRelation struct { // The unique, numeric id of this relation. ID int64 `xorm:"bigint autoincr not null unique pk" json:"-"` // The ID of the "base" task, the task which has a relation to another. - TaskID int64 `xorm:"bigint not null" json:"task_id" param:"task"` + TaskID int64 `xorm:"bigint not null" json:"task_id" param:"task" readOnly:"true" doc:"The id of the base task. Set from the URL path; ignored in the request body."` // The ID of the other task, the task which is being related. - OtherTaskID int64 `xorm:"bigint not null" json:"other_task_id" param:"otherTask"` + OtherTaskID int64 `xorm:"bigint not null" json:"other_task_id" param:"otherTask" doc:"The id of the other task this relation points to."` // The kind of the relation. - RelationKind RelationKind `xorm:"varchar(50) not null" json:"relation_kind" param:"relationKind"` + // The enum list must stay in sync with RelationKind.isValid() (RelationKindUnknown excluded); the v2 delete route param repeats it. + RelationKind RelationKind `xorm:"varchar(50) not null" json:"relation_kind" param:"relationKind" enum:"subtask,parenttask,related,duplicateof,duplicates,blocking,blocked,precedes,follows,copiedfrom,copiedto" doc:"The kind of relation, describing the direction from the base task to the other task (e.g. subtask, blocking, related). The inverse relation is created automatically."` CreatedByID int64 `xorm:"bigint not null" json:"-"` // The user who created this relation - CreatedBy *user.User `xorm:"-" json:"created_by"` + CreatedBy *user.User `xorm:"-" json:"created_by" readOnly:"true" doc:"The user who created this relation."` // A timestamp when this label was created. You cannot change this value. - Created time.Time `xorm:"created not null" json:"created"` + Created time.Time `xorm:"created not null" json:"created" readOnly:"true" doc:"A timestamp when this relation was created. You cannot change this value."` web.CRUDable `xorm:"-" json:"-"` web.Permissions `xorm:"-" json:"-"` diff --git a/pkg/models/tasks.go b/pkg/models/tasks.go index b7515b40a..978a0f850 100644 --- a/pkg/models/tasks.go +++ b/pkg/models/tasks.go @@ -62,25 +62,25 @@ func validateRepeatAfter(repeatAfter int64) error { // Task represents a task in a project type Task struct { // The unique, numeric id of this task. - ID int64 `xorm:"bigint autoincr not null unique pk" json:"id" param:"projecttask"` + ID int64 `xorm:"bigint autoincr not null unique pk" json:"id" param:"projecttask" readOnly:"true" doc:"The unique, numeric id of this task."` // The task text. This is what you'll see in the project. - Title string `xorm:"TEXT not null" json:"title" valid:"minstringlength(1)" minLength:"1"` + Title string `xorm:"TEXT not null" json:"title" valid:"minstringlength(1)" minLength:"1" doc:"The task title. This is what you'll see in the project."` // The task description. Description string `xorm:"longtext null" json:"description"` // Whether a task is done or not. Done bool `xorm:"INDEX null" json:"done"` // The time when a task was marked as done. This field is system-controlled and cannot be set via API. - DoneAt time.Time `xorm:"INDEX null 'done_at'" json:"done_at"` + DoneAt time.Time `xorm:"INDEX null 'done_at'" json:"done_at" readOnly:"true" doc:"When the task was marked as done. Set by the server; ignored on write."` // The time when the task is due. DueDate time.Time `xorm:"DATETIME INDEX null 'due_date'" json:"due_date"` // An array of reminders that are associated with this task. Reminders []*TaskReminder `xorm:"-" json:"reminders"` // The project this task belongs to. - ProjectID int64 `xorm:"bigint INDEX not null unique(tasks_project_index)" json:"project_id" param:"project"` + ProjectID int64 `xorm:"bigint INDEX not null unique(tasks_project_index)" json:"project_id" param:"project" doc:"The id of the project this task belongs to. On create it is taken from the URL; on update, setting it to a different project moves the task (requires write access to the target project)."` // An amount in seconds this task repeats itself. If this is set, when marking the task as done, it will mark itself as "undone" and then increase all remindes and the due date by its amount. - RepeatAfter int64 `xorm:"bigint INDEX null" json:"repeat_after" valid:"range(0|9223372036854775807)"` + RepeatAfter int64 `xorm:"bigint INDEX null" json:"repeat_after" valid:"range(0|9223372036854775807)" doc:"The interval in seconds this task repeats. When set, marking the task done re-opens it and bumps its reminders and due date by this amount."` // Can have three possible values which will trigger when the task is marked as done: 0 = repeats after the amount specified in repeat_after, 1 = repeats all dates each months (ignoring repeat_after), 3 = repeats from the current date rather than the last set date. - RepeatMode TaskRepeatMode `xorm:"not null default 0" json:"repeat_mode"` + RepeatMode TaskRepeatMode `xorm:"not null default 0" json:"repeat_mode" doc:"How the task repeats when marked done: 0 = after repeat_after seconds, 1 = monthly (ignores repeat_after), 2 = from the current date rather than the last set date."` // The task priority. Can be anything you want, it is possible to sort by this later. Priority int64 `xorm:"bigint null" json:"priority"` // When this task starts. @@ -88,73 +88,75 @@ type Task struct { // When this task ends. EndDate time.Time `xorm:"DATETIME INDEX null 'end_date'" json:"end_date" query:"-"` // An array of users who are assigned to this task - Assignees []*user.User `xorm:"-" json:"assignees"` + Assignees []*user.User `xorm:"-" json:"assignees" readOnly:"true" doc:"The users assigned to this task. Read-only here; use the task-assignee endpoints to change assignments."` // An array of labels which are associated with this task. This property is read-only, you must use the separate endpoint to add labels to a task. - Labels []*Label `xorm:"-" json:"labels"` + Labels []*Label `xorm:"-" json:"labels" readOnly:"true" doc:"The labels on this task. Read-only here; use the label-task endpoints to add or remove labels."` // The task color in hex - HexColor string `xorm:"varchar(6) null" json:"hex_color" valid:"runelength(0|7)" maxLength:"7"` + HexColor string `xorm:"varchar(6) null" json:"hex_color" valid:"runelength(0|7)" maxLength:"7" doc:"The task color as a hex string without the leading '#'."` // Determines how far a task is left from being done - PercentDone float64 `xorm:"DOUBLE null" json:"percent_done"` + PercentDone float64 `xorm:"DOUBLE null" json:"percent_done" doc:"How far the task is from done, between 0 and 1."` // The task identifier, based on the project identifier and the task's index - Identifier string `xorm:"-" json:"identifier"` + Identifier string `xorm:"-" json:"identifier" readOnly:"true" doc:"The textual task identifier, derived from the project identifier and the task index (e.g. \"PROJ-12\")."` // The task index, calculated per project - Index int64 `xorm:"bigint not null default 0 unique(tasks_project_index)" json:"index" param:"index"` + Index int64 `xorm:"bigint not null default 0 unique(tasks_project_index)" json:"index" param:"index" readOnly:"true" doc:"The per-project task index, assigned by the server."` // The UID is currently not used for anything other than CalDAV, which is why we don't expose it over json UID string `xorm:"varchar(250) null" json:"-"` // All related tasks, grouped by their relation kind - RelatedTasks RelatedTaskMap `xorm:"-" json:"related_tasks"` + RelatedTasks RelatedTaskMap `xorm:"-" json:"related_tasks" readOnly:"true" doc:"Related tasks grouped by relation kind. Read-only here; use the task-relation endpoints to change relations."` // All attachments this task has. This property is read-onlym, you must use the separate endpoint to add attachments to a task. - Attachments []*TaskAttachment `xorm:"-" json:"attachments"` + Attachments []*TaskAttachment `xorm:"-" json:"attachments" readOnly:"true" doc:"The task's attachments. Read-only here; use the attachment endpoints to add or remove them."` // If this task has a cover image, the field will return the id of the attachment that is the cover image. - CoverImageAttachmentID int64 `xorm:"bigint default 0" json:"cover_image_attachment_id"` + CoverImageAttachmentID int64 `xorm:"bigint default 0" json:"cover_image_attachment_id" doc:"The id of the attachment used as this task's cover image, or 0 for none."` // True if a task is a favorite task. Favorite tasks show up in a separate "Important" project. This value depends on the user making the call to the api. - IsFavorite bool `xorm:"-" json:"is_favorite"` + IsFavorite bool `xorm:"-" json:"is_favorite" doc:"Whether the requesting user has favorited this task. Per-user, so it differs between callers."` - IsUnread *bool `xorm:"-" json:"is_unread,omitempty"` + IsUnread *bool `xorm:"-" json:"is_unread,omitempty" readOnly:"true" doc:"Whether the task is unread for the requesting user. Only present when requested via the is_unread expand option."` // The subscription status for the user reading this task. You can only read this property, use the subscription endpoints to modify it. // Will only returned when retrieving one task. - Subscription *Subscription `xorm:"-" json:"subscription,omitempty"` + Subscription *Subscription `xorm:"-" json:"subscription,omitempty" readOnly:"true" doc:"The requesting user's subscription to this task. Read-only here; use the subscription endpoints to change it. Only present when reading a single task."` // A timestamp when this task was created. You cannot change this value. - Created time.Time `xorm:"created not null" json:"created"` + Created time.Time `xorm:"created not null" json:"created" readOnly:"true" doc:"When this task was created. Set by the server; ignored on write."` // A timestamp when this task was last updated. You cannot change this value. - Updated time.Time `xorm:"updated not null" json:"updated"` + Updated time.Time `xorm:"updated not null" json:"updated" readOnly:"true" doc:"When this task was last updated. Set by the server; ignored on write."` // The bucket id. Will only be populated when the task is accessed via a view with buckets. // Can be used to move a task between buckets. In that case, the new bucket must be in the same view as the old one. - BucketID int64 `xorm:"-" json:"bucket_id"` + BucketID int64 `xorm:"-" json:"bucket_id" doc:"The bucket the task is in. Only populated when the task is accessed via a view with buckets. To move a task between buckets, the new bucket must be in the same view as the old one."` // All buckets across all views this task is part of. Only present when fetching tasks with the `expand` parameter set to `buckets`. - Buckets []*Bucket `xorm:"-" json:"buckets,omitempty"` + Buckets []*Bucket `xorm:"-" json:"buckets,omitempty" readOnly:"true" doc:"The task's buckets across all views. Only present when requested via the buckets expand option."` // All comments of this task. Only present when fetching tasks with the `expand` parameter set to `comments`. - Comments []*TaskComment `xorm:"-" json:"comments,omitempty"` + Comments []*TaskComment `xorm:"-" json:"comments,omitempty" readOnly:"true" doc:"The task's first 50 comments. Only present when requested via the comments expand option."` // Comment count of this task. Only present when fetching tasks with the `expand` parameter set to `comment_count`. - CommentCount *int64 `xorm:"-" json:"comment_count,omitempty"` + CommentCount *int64 `xorm:"-" json:"comment_count,omitempty" readOnly:"true" doc:"The number of comments on this task. Only present when requested via the comment_count expand option."` + + // Time entry count of this task. Only present when fetching tasks with the `expand` parameter set to `time_entries_count`. + TimeEntriesCount *int64 `xorm:"-" json:"time_entries_count,omitempty" readOnly:"true" doc:"The number of time entries on this task. Only present when requested via the time_entries_count expand option."` // Behaves exactly the same as with the TaskCollection.Expand parameter - Expand []TaskCollectionExpandable `xorm:"-" json:"-" query:"expand"` - ExpandArr []TaskCollectionExpandable `xorm:"-" json:"-" query:"expand[]"` + Expand []TaskCollectionExpandable `xorm:"-" json:"-" query:"expand"` // The position of the task - any task project can be sorted as usual by this parameter. // When accessing tasks via views with buckets, this is primarily used to sort them based on a range. // Positions are always saved per view. They will automatically be set if you request the tasks through a view // endpoint, otherwise they will always be 0. To update them, take a look at the Task Position endpoint. - Position float64 `xorm:"-" json:"position"` + Position float64 `xorm:"-" json:"position" readOnly:"true" doc:"The task's position, saved per view. Only non-zero when the task is fetched through a view endpoint; use the task-position endpoint to change it."` // Reactions on that task. - Reactions ReactionMap `xorm:"-" json:"reactions"` + Reactions ReactionMap `xorm:"-" json:"reactions" readOnly:"true" doc:"Reactions on this task. Only present when requested via the reactions expand option."` // The user who initially created the task. - CreatedBy *user.User `xorm:"-" json:"created_by" valid:"-"` + CreatedBy *user.User `xorm:"-" json:"created_by" valid:"-" readOnly:"true" doc:"The user who created this task. Set by the server."` CreatedByID int64 `xorm:"bigint not null" json:"-"` // ID of the user who put that task on the project web.CRUDable `xorm:"-" json:"-"` @@ -768,6 +770,11 @@ func addMoreInfoToTasks(s *xorm.Session, taskMap map[int64]*Task, a web.Auth, vi if err != nil { return err } + case TaskCollectionExpandTimeEntriesCount: + err = addTimeEntriesCountToTasks(s, a, taskIDs, taskMap) + if err != nil { + return err + } case TaskCollectionExpandIsUnread: err = addIsUnreadToTasks(s, taskIDs, taskMap, a) if err != nil { @@ -823,9 +830,15 @@ func checkBucketLimit(s *xorm.Session, a web.Auth, t *Task, bucket *Bucket) (tas } if view.ProjectID < 0 || (view.Filter != nil && view.Filter.Filter != "") { + // For saved filters or views with a filter, the count must be scoped to + // this bucket *and* the filter: raw task_buckets rows can include tasks + // that no longer match the filter (#355), while the unscoped filter total + // counts tasks across all buckets, not just this one (#2672). ReadAll + // combines the bucket_id condition with the saved-filter / view filter. tc := &TaskCollection{ ProjectID: view.ProjectID, ProjectViewID: bucket.ProjectViewID, + Filter: "bucket_id = " + strconv.FormatInt(bucket.ID, 10), } _, _, taskCount, err = tc.ReadAll(s, a, "", 1, 1) @@ -971,6 +984,13 @@ func createTask(s *xorm.Session, t *Task, a web.Auth, updateAssignees bool, setB return err } + if len(positions) > 0 { + positions, err = filterNewTaskPositions(s, positions) + if err != nil { + return err + } + } + if len(positions) > 0 { _, err = s.Insert(&positions) if err != nil { @@ -1442,10 +1462,9 @@ func (t *Task) updateSingleTask(s *xorm.Session, a web.Auth, fields []string) (e } t.Updated = nt.Updated - doer, _ := user.GetFromAuth(a) events.DispatchOnCommit(s, &TaskUpdatedEvent{ Task: t, - Doer: doer, + Doer: doerFromAuth(s, a), }) return updateProjectLastUpdated(s, &Project{ID: t.ProjectID}) @@ -1728,6 +1747,20 @@ func setTaskDatesFromCurrentDateRepeat(oldTask, newTask *Task) { newTask.Done = false } +var ( + checklistTiptapCheckedRegex = regexp.MustCompile(`(data-checked=")true(")`) + checklistInputCheckedRegex = regexp.MustCompile(`(]*type=["']checkbox["'][^>]*?)\s+checked(?:=["'][^"']*["'])?`) +) + +// resetDescriptionChecklist unchecks every checklist item in a TipTap HTML description +// (descriptions are always stored as HTML, never markdown) without touching other content, +// so a recurring task's next occurrence does not inherit checked items. +func resetDescriptionChecklist(description string) string { + description = checklistTiptapCheckedRegex.ReplaceAllString(description, "${1}false${2}") + description = checklistInputCheckedRegex.ReplaceAllString(description, "$1") + return description +} + // This helper function updates the reminders, doneAt, start, end and due dates of the *old* task // and saves the new values in the newTask object. // We make a few assumptions here: @@ -1747,6 +1780,11 @@ func updateDone(oldTask *Task, newTask *Task) (updateDoneAt bool) { setTaskDatesDefault(oldTask, newTask) } + // A recurring task reopens for its next occurrence, so its checklist starts fresh. + if oldTask.isRepeating() && !newTask.Done { + newTask.Description = resetDescriptionChecklist(newTask.Description) + } + newTask.DoneAt = time.Now() } @@ -1941,10 +1979,9 @@ func (t *Task) Delete(s *xorm.Session, a web.Auth) (err error) { return err } - doer, _ := user.GetFromAuth(a) events.DispatchOnCommit(s, &TaskDeletedEvent{ Task: fullTask, - Doer: doer, + Doer: doerFromAuth(s, a), }) err = updateProjectLastUpdated(s, &Project{ID: t.ProjectID}) @@ -1966,7 +2003,6 @@ func (t *Task) Delete(s *xorm.Session, a web.Auth) (err error) { // @Router /tasks/{id} [get] func (t *Task) ReadOne(s *xorm.Session, a web.Auth) (err error) { - t.Expand = append(t.Expand, t.ExpandArr...) expand := t.Expand if err = t.resolveIDFromProjectAndIndex(s); err != nil { return @@ -2013,10 +2049,9 @@ func triggerTaskUpdatedEventForTaskID(s *xorm.Session, auth web.Auth, taskID int return err } - doer, _ := user.GetFromAuth(auth) events.DispatchOnCommit(s, &TaskUpdatedEvent{ Task: &t, - Doer: doer, + Doer: doerFromAuth(s, auth), }) return nil } diff --git a/pkg/models/tasks_permissions.go b/pkg/models/tasks_permissions.go index 71459b13f..c4f07aaa3 100644 --- a/pkg/models/tasks_permissions.go +++ b/pkg/models/tasks_permissions.go @@ -40,7 +40,6 @@ func (t *Task) CanCreate(s *xorm.Session, a web.Auth) (bool, error) { // CanRead determines if a user can read a task func (t *Task) CanRead(s *xorm.Session, a web.Auth) (canRead bool, maxPermission int, err error) { - t.Expand = append(t.Expand, t.ExpandArr...) expand := t.Expand if err = t.resolveIDFromProjectAndIndex(s); err != nil { return diff --git a/pkg/models/tasks_test.go b/pkg/models/tasks_test.go index caa897740..433bee18a 100644 --- a/pkg/models/tasks_test.go +++ b/pkg/models/tasks_test.go @@ -17,6 +17,7 @@ package models import ( + "context" "testing" "time" @@ -70,7 +71,7 @@ func TestTask_Create(t *testing.T) { "bucket_id": 1, }, false) - events.DispatchPending(s) + events.DispatchPending(context.Background(), s) events.AssertDispatched(t, &TaskCreatedEvent{}) }) t.Run("with reminders", func(t *testing.T) { @@ -280,7 +281,7 @@ func TestTask_Update(t *testing.T) { err = s.Commit() require.NoError(t, err) - events.DispatchPending(s) + events.DispatchPending(context.Background(), s) // Verify exactly ONE task.updated event was dispatched count := events.CountDispatchedEvents("task.updated") assert.Equal(t, 1, count, "Expected exactly 1 task.updated event, got %d", count) @@ -985,6 +986,45 @@ func TestUpdateDone(t *testing.T) { assert.False(t, newTask.Done) }) }) + t.Run("reset checklist on recurrence", func(t *testing.T) { + const checked = `before
  • Item

after` + const unchecked = `before
  • Item

after` + + oldTask := &Task{ + Done: false, + RepeatAfter: 8600, + DueDate: time.Unix(1550000000, 0), + } + newTask := &Task{ + Done: true, + Description: checked, + } + + updateDone(oldTask, newTask) + + assert.False(t, newTask.Done) + assert.True(t, newTask.DueDate.After(oldTask.DueDate)) + assert.Equal(t, unchecked, newTask.Description) + }) + t.Run("non-recurring description untouched", func(t *testing.T) { + const checked = `before
  • Item

after` + + oldTask := &Task{ + Done: false, + RepeatAfter: 0, + RepeatMode: TaskRepeatModeDefault, + DueDate: time.Unix(1550000000, 0), + } + newTask := &Task{ + Done: true, + Description: checked, + } + + updateDone(oldTask, newTask) + + assert.True(t, newTask.Done) + assert.Equal(t, checked, newTask.Description) + }) }) } diff --git a/pkg/models/team_members.go b/pkg/models/team_members.go index 8dd95d7bb..b31929277 100644 --- a/pkg/models/team_members.go +++ b/pkg/models/team_members.go @@ -69,11 +69,10 @@ func (tm *TeamMember) Create(s *xorm.Session, a web.Auth) (err error) { return err } - doer, _ := user2.GetFromAuth(a) events.DispatchOnCommit(s, &TeamMemberAddedEvent{ Team: team, Member: member, - Doer: doer, + Doer: doerFromAuth(s, a), }) return nil } @@ -169,6 +168,10 @@ func (tm *TeamMember) Update(s *xorm.Session, _ web.Auth) (err error) { Where("team_id = ? AND user_id = ?", tm.TeamID, tm.UserID). Cols("admin"). Update(ttm) - tm.Admin = ttm.Admin // Since we're returning the updated permissions object + + // Carry the persisted row back onto tm so the response has id/created, keeping Username (xorm:"-"). + username := tm.Username + *tm = *ttm + tm.Username = username return } diff --git a/pkg/models/teams.go b/pkg/models/teams.go index 719459569..ab0a80846 100644 --- a/pkg/models/teams.go +++ b/pkg/models/teams.go @@ -17,6 +17,7 @@ package models import ( + "sort" "time" "code.vikunja.io/api/pkg/config" @@ -32,32 +33,35 @@ import ( // Team holds a team object type Team struct { // The unique, numeric id of this team. - ID int64 `xorm:"bigint autoincr not null unique pk" json:"id" param:"team"` + ID int64 `xorm:"bigint autoincr not null unique pk" json:"id" param:"team" readOnly:"true" doc:"The unique, numeric id of this team."` // The name of this team. - Name string `xorm:"varchar(250) not null" json:"name" valid:"required,runelength(1|250)" minLength:"1" maxLength:"250"` + Name string `xorm:"varchar(250) not null" json:"name" valid:"required,runelength(1|250)" minLength:"1" maxLength:"250" doc:"The name of this team."` // The team's description. Description string `xorm:"longtext null" json:"description"` CreatedByID int64 `xorm:"bigint not null INDEX" json:"-"` // The team's external id provided by the openid or ldap provider - ExternalID string `xorm:"varchar(250) null" maxLength:"250" json:"external_id"` + ExternalID string `xorm:"varchar(250) null" maxLength:"250" json:"external_id" readOnly:"true" doc:"The team's external id, set by the openid or ldap provider that created it. Read-only for clients."` // Contains the issuer extracted from the vikunja_groups claim if this team was created through oidc Issuer string `xorm:"text null" json:"-"` // The user who created this team. - CreatedBy *user.User `xorm:"-" json:"created_by"` + CreatedBy *user.User `xorm:"-" json:"created_by" readOnly:"true" doc:"The user who created this team. Set by the server."` // An array of all members in this team. - Members []*TeamUser `xorm:"-" json:"members"` + Members []*TeamUser `xorm:"-" json:"members" readOnly:"true" doc:"All members of this team. Managed through the team members endpoints, not by writing to this field."` // A timestamp when this relation was created. You cannot change this value. - Created time.Time `xorm:"created" json:"created"` + Created time.Time `xorm:"created" json:"created" readOnly:"true" doc:"A timestamp when this team was created. You cannot change this value."` // A timestamp when this relation was last updated. You cannot change this value. - Updated time.Time `xorm:"updated" json:"updated"` + Updated time.Time `xorm:"updated" json:"updated" readOnly:"true" doc:"A timestamp when this team was last updated. You cannot change this value."` // Defines wether the team should be publicly discoverable when sharing a project - IsPublic bool `xorm:"not null default false" json:"is_public"` + IsPublic bool `xorm:"not null default false" json:"is_public" doc:"Whether the team should be publicly discoverable when sharing a project. Only effective if public teams are enabled on the instance."` - // Query parameter controlling whether to include public projects or not - IncludePublic bool `xorm:"-" query:"include_public" json:"include_public"` + // Query-only flag controlling whether public teams the user is not a member + // of are included when listing. It is never part of the request or response + // body (json:"-") — v1 binds it from the query string via the query tag, and + // the v2 list handler takes it as a dedicated query field and sets it here. + IncludePublic bool `xorm:"-" query:"include_public" json:"-"` web.CRUDable `xorm:"-" json:"-"` web.Permissions `xorm:"-" json:"-"` @@ -71,18 +75,18 @@ func (*Team) TableName() string { // TeamMember defines the relationship between a user and a team type TeamMember struct { // The unique, numeric id of this team member relation. - ID int64 `xorm:"bigint autoincr not null unique pk" json:"id"` + ID int64 `xorm:"bigint autoincr not null unique pk" json:"id" readOnly:"true" doc:"The unique, numeric id of this team member relation. Set by the server."` // The team id. TeamID int64 `xorm:"bigint not null INDEX" json:"-" param:"team"` // The username of the member. We use this to prevent automated user id entering. - Username string `xorm:"-" json:"username" param:"user"` + Username string `xorm:"-" json:"username" param:"user" valid:"required" minLength:"1" doc:"The username of the member."` // Used under the hood to manage team members UserID int64 `xorm:"bigint not null INDEX" json:"-"` // Whether or not the member is an admin of the team. See the docs for more about what a team admin can do - Admin bool `xorm:"null" json:"admin"` + Admin bool `xorm:"null" json:"admin" doc:"Whether the member is an admin of the team. Team admins can add and remove members and toggle other members' admin status."` // A timestamp when this relation was created. You cannot change this value. - Created time.Time `xorm:"created not null" json:"created"` + Created time.Time `xorm:"created not null" json:"created" readOnly:"true" doc:"A timestamp when this member was added to the team. You cannot change this value."` web.CRUDable `xorm:"-" json:"-"` web.Permissions `xorm:"-" json:"-"` @@ -178,6 +182,9 @@ func addMoreInfoToTeams(s *xorm.Session, teams []*Team) (err error) { if teamUser, has := users[team.CreatedByID]; has { team.CreatedBy = &teamUser.User } + sort.Slice(team.Members, func(i, j int) bool { + return team.Members[i].ID < team.Members[j].ID + }) } return } @@ -215,7 +222,7 @@ func (t *Team) CreateNewTeam(s *xorm.Session, a web.Auth, firstUserShouldBeAdmin events.DispatchOnCommit(s, &TeamCreatedEvent{ Team: t, - Doer: a, + Doer: doer, }) return nil } @@ -355,7 +362,7 @@ func (t *Team) Delete(s *xorm.Session, a web.Auth) (err error) { events.DispatchOnCommit(s, &TeamDeletedEvent{ Team: t, - Doer: a, + Doer: doerFromAuth(s, a), }) return nil } diff --git a/pkg/models/time_tracking.go b/pkg/models/time_tracking.go new file mode 100644 index 000000000..fc9ee3251 --- /dev/null +++ b/pkg/models/time_tracking.go @@ -0,0 +1,441 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package models + +import ( + "time" + + "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/events" + "code.vikunja.io/api/pkg/license" + "code.vikunja.io/api/pkg/user" + "code.vikunja.io/api/pkg/web" + "xorm.io/builder" + "xorm.io/xorm" +) + +// TimeEntry is a single tracked time span attached to either a task or a +// project — exactly one of TaskID / ProjectID is set (XOR). A running live +// timer is just an entry whose EndTime is still null. +// +// v2-only: doc: tags are the schema's source of truth (no v1 swaggo), and it +// implements CRUDable + Permissions because the shared handler.Do* pipeline needs them. +type TimeEntry struct { + ID int64 `xorm:"bigint autoincr not null unique pk" json:"id" param:"timeentry" readOnly:"true" doc:"The unique, numeric id of this time entry."` + + UserID int64 `xorm:"bigint not null INDEX" json:"user_id" readOnly:"true" doc:"The id of the user who logged this time entry. Set by the server."` + + TaskID int64 `xorm:"bigint null INDEX" json:"task_id" doc:"The task this entry is attached to. Exactly one of task_id / project_id must be set."` + ProjectID int64 `xorm:"bigint null INDEX" json:"project_id" doc:"The project this entry is attached to directly. Exactly one of task_id / project_id must be set."` + + StartTime time.Time `xorm:"not null INDEX" json:"start_time" doc:"When the tracked time started."` + EndTime *time.Time `xorm:"null" json:"end_time" doc:"When the tracked time ended. Null means a live timer is still running."` + + Comment string `xorm:"text null" json:"comment" doc:"An optional comment describing the logged time."` + + Created time.Time `xorm:"created not null" json:"created" readOnly:"true" doc:"A timestamp when this time entry was created. You cannot change this value."` + Updated time.Time `xorm:"updated not null" json:"updated" readOnly:"true" doc:"A timestamp when this time entry was last updated. You cannot change this value."` + + // Filter-only fields (not persisted): set by the v2 list route, read by ReadAll. + Filter string `xorm:"-" json:"-"` + FilterTimezone string `xorm:"-" json:"-"` + + web.CRUDable `xorm:"-" json:"-"` + web.Permissions `xorm:"-" json:"-"` +} + +// TableName is time_entries, not the xorm-default time_entry. +func (*TimeEntry) TableName() string { + return "time_entries" +} + +// --- CRUDable --- + +func (te *TimeEntry) Create(s *xorm.Session, a web.Auth) (err error) { + te.UserID = a.GetID() + + // Starting a new running timer auto-stops the previous one; a completed + // manual entry (EndTime set) must leave the running timer alone. + if te.EndTime == nil { + if _, err = stopRunningTimerForUser(s, te.UserID); err != nil { + return err + } + } + + if te.StartTime.IsZero() { + te.StartTime = time.Now() + } + + if err = te.validateTimes(); err != nil { + return err + } + + if _, err = s.Insert(te); err != nil { + return err + } + + doer, err := user.GetFromAuth(a) + if err != nil { + return err + } + events.DispatchOnCommit(s, &TimeEntryCreatedEvent{TimeEntry: te, Doer: doer}) + return nil +} + +func (te *TimeEntry) ReadOne(_ *xorm.Session, _ web.Auth) (err error) { + // entry got already fetched in CanRead, nothing left to do here + return nil +} + +// stopRunningTimerForUser stops the user's active timer (end_time = now) and +// returns it, or nil if no timer is running. +func stopRunningTimerForUser(s *xorm.Session, userID int64) (*TimeEntry, error) { + running := &TimeEntry{} + exists, err := s.Where("user_id = ? AND end_time IS NULL", userID).Get(running) + if err != nil { + return nil, err + } + if !exists { + return nil, nil + } + if err := running.stop(s); err != nil { + return nil, err + } + + doer, err := user.GetUserByID(s, userID) + if err != nil { + return nil, err + } + events.DispatchOnCommit(s, &TimeEntryUpdatedEvent{TimeEntry: running, Doer: doer}) + return running, nil +} + +// StopRunningTimer stops the authenticated user's active timer and returns it, +// or ErrNoRunningTimer when none is running. The stop time is the server's now. +func StopRunningTimer(s *xorm.Session, a web.Auth) (*TimeEntry, error) { + // Link shares have no time tracking (mirrors the Can* methods). Their id is a + // share id, not a user id, so without this a share whose id collides with a + // user's would stop and read that user's running timer. + if _, isShare := a.(*LinkSharing); isShare { + return nil, ErrGenericForbidden{} + } + + running, err := stopRunningTimerForUser(s, a.GetID()) + if err != nil { + return nil, err + } + if running == nil { + return nil, ErrNoRunningTimer{UserID: a.GetID()} + } + return running, nil +} + +// readableTimeEntriesCond restricts a query to entries the auth can read: a +// standalone entry on an accessible project, or one on a task in such a project. +func readableTimeEntriesCond(a web.Auth) builder.Cond { + return entriesForProjectCond(accessibleProjectIDsSubquery(a, "project_id")) +} + +func (te *TimeEntry) ReadAll(s *xorm.Session, a web.Auth, search string, page int, perPage int) (result any, resultCount int, numberOfTotalItems int64, err error) { + // Link shares have no time-tracking access (mirrors the Can* methods); + // DoReadAll skips the permission check, so it must be guarded here too. + if _, isShareAuth := a.(*LinkSharing); isShareAuth { + return []*TimeEntry{}, 0, 0, nil + } + + cond := readableTimeEntriesCond(a) + if te.TaskID > 0 { + cond = cond.And(builder.Eq{"task_id": te.TaskID}) + } + if te.ProjectID > 0 { + cond = cond.And(entriesForProjectCond(builder.Eq{"project_id": te.ProjectID})) + } + + filterCond, err := timeEntryFilterCond(te.Filter, te.FilterTimezone) + if err != nil { + return nil, 0, 0, err + } + if filterCond != nil { + cond = cond.And(filterCond) + } + + if search != "" { + cond = cond.And(db.MultiFieldSearch([]string{"comment"}, search)) + } + + total, err := s.Where(cond). + Count(&TimeEntry{}) + if err != nil { + return nil, 0, 0, err + } + + entries := []*TimeEntry{} + err = s.Where(cond). + OrderBy("start_time ASC"). + Limit(getLimitFromPageIndex(page, perPage)). + Find(&entries) + return entries, len(entries), total, err +} + +func (te *TimeEntry) Update(s *xorm.Session, a web.Auth) (err error) { + // A completed entry can't be reopened into a running timer via update — that + // would sidestep Create's single-active-timer rule; start a new one instead. + existing, err := getTimeEntryByID(s, te.ID) + if err != nil { + return err + } + if existing.EndTime != nil && te.EndTime == nil { + return ErrTimeEntryAlreadyEnded{TimeEntryID: te.ID} + } + + if err = te.validateTimes(); err != nil { + return err + } + + // task_id / project_id are listed so a reassignment (and the zero value of + // the side being cleared) is written; the XOR was validated in CanUpdate. + _, err = s. + Where("id = ?", te.ID). + Cols("task_id", "project_id", "start_time", "end_time", "comment"). + Update(te) + if err != nil { + return err + } + + // reload: Update wrote only the editable columns + updated, err := getTimeEntryByID(s, te.ID) + if err != nil { + return err + } + *te = *updated + + doer, err := user.GetFromAuth(a) + if err != nil { + return err + } + events.DispatchOnCommit(s, &TimeEntryUpdatedEvent{TimeEntry: te, Doer: doer}) + return nil +} + +func (te *TimeEntry) Delete(s *xorm.Session, a web.Auth) (err error) { + entry, err := getTimeEntryByID(s, te.ID) + if err != nil { + return err + } + if _, err = s.Where("id = ?", te.ID).Delete(&TimeEntry{}); err != nil { + return err + } + + doer, err := user.GetFromAuth(a) + if err != nil { + return err + } + events.DispatchOnCommit(s, &TimeEntryDeletedEvent{TimeEntry: entry, Doer: doer}) + return nil +} + +func getTimeEntryByID(s *xorm.Session, id int64) (*TimeEntry, error) { + entry := &TimeEntry{} + exists, err := s.Where("id = ?", id).Get(entry) + if err != nil { + return nil, err + } + if !exists { + return nil, ErrTimeEntryDoesNotExist{TimeEntryID: id} + } + return entry, nil +} + +func (te *TimeEntry) stop(s *xorm.Session) (err error) { + now := time.Now() + te.EndTime = &now + _, err = s.ID(te.ID).Update(te) + return err +} + +// --- Permissions --- + +// Returns the loaded entry rather than mutating te, so Update keeps its payload. +func (te *TimeEntry) canDoTimeEntry(s *xorm.Session, a web.Auth, fetch bool) (*TimeEntry, bool, int, error) { + entry := &TimeEntry{TaskID: te.TaskID, ProjectID: te.ProjectID} + if fetch { + var err error + entry, err = getTimeEntryByID(s, te.ID) + if err != nil { + return nil, false, -1, err + } + } + + switch { + case entry.TaskID != 0: + task, err := GetTaskByIDSimple(s, entry.TaskID) + if err != nil { + return entry, false, -1, err + } + can, maxPerm, err := task.CanRead(s, a) + return entry, can, maxPerm, err + case entry.ProjectID != 0: + project, _, err := getProjectSimple(s, builder.Eq{"id": entry.ProjectID}) + if err != nil { + return entry, false, -1, err + } + can, maxPerm, err := project.CanRead(s, a) + return entry, can, maxPerm, err + default: + return entry, false, 0, nil + } +} + +func (te *TimeEntry) CanRead(s *xorm.Session, a web.Auth) (bool, int, error) { + if _, isShareAuth := a.(*LinkSharing); isShareAuth { + return false, 0, nil + } + + entry, can, maxPerm, err := te.canDoTimeEntry(s, a, true) + if err != nil { + return false, maxPerm, err + } + *te = *entry // ReadOne is a no-op; populate te here + return can, maxPerm, nil +} + +// validateContainer enforces the XOR invariant: exactly one of task or project. +func (te *TimeEntry) validateContainer() error { + if (te.TaskID == 0) == (te.ProjectID == 0) { + return ErrTimeEntryInvalidContainer{TaskID: te.TaskID, ProjectID: te.ProjectID} + } + return nil +} + +// validateTimes rejects a completed entry whose end precedes its start (a +// negative interval). A null end is a running timer and is always valid; an end +// equal to the start is allowed (a zero-length entry). +func (te *TimeEntry) validateTimes() error { + if te.EndTime != nil && te.EndTime.Before(te.StartTime) { + return ErrTimeEntryEndBeforeStart{TimeEntryID: te.ID} + } + return nil +} + +func (te *TimeEntry) CanCreate(s *xorm.Session, a web.Auth) (bool, error) { + if _, isShareAuth := a.(*LinkSharing); isShareAuth { + return false, nil + } + + if err := te.validateContainer(); err != nil { + return false, err + } + + _, can, _, err := te.canDoTimeEntry(s, a, false) + return can, err +} + +// CanUpdate allows the author to edit their entry, including moving it between +// task / project: on top of the author check it validates the (possibly new) +// container (XOR) and requires read access to it, mirroring create. +func (te *TimeEntry) CanUpdate(s *xorm.Session, a web.Auth) (bool, error) { + if _, isShareAuth := a.(*LinkSharing); isShareAuth { + return false, nil + } + + existing, err := getTimeEntryByID(s, te.ID) + if err != nil { + return false, err + } + if existing.UserID != a.GetID() { + return false, nil + } + + // A request that omits the container keeps the existing one — an entry + // always has exactly one, so "clearing" it is never valid. + if te.TaskID == 0 && te.ProjectID == 0 { + te.TaskID = existing.TaskID + te.ProjectID = existing.ProjectID + } + if err := te.validateContainer(); err != nil { + return false, err + } + + _, canReadContainer, _, err := te.canDoTimeEntry(s, a, false) + return canReadContainer, err +} + +func (te *TimeEntry) CanDelete(s *xorm.Session, a web.Auth) (bool, error) { + return te.canModify(s, a) +} + +// canModify gates delete: read access to the container plus being the author. +func (te *TimeEntry) canModify(s *xorm.Session, a web.Auth) (bool, error) { + if _, isShareAuth := a.(*LinkSharing); isShareAuth { + return false, nil + } + + entry, canRead, _, err := te.canDoTimeEntry(s, a, true) + if err != nil { + return false, err + } + if !canRead { + return false, nil + } + return entry.UserID == a.GetID(), nil +} + +// addTimeEntriesCountToTasks attaches each task's time-entry count for the +// `time_entries_count` expand. Mirrors addCommentCountToTasks, but follows the +// same gates as the time-entry endpoints: the count is left unset (absent) for +// link shares or when the feature is unlicensed, so it can't leak that way. +func addTimeEntriesCountToTasks(s *xorm.Session, a web.Auth, taskIDs []int64, taskMap map[int64]*Task) error { + if _, isShare := a.(*LinkSharing); isShare { + return nil + } + if !license.IsFeatureEnabled(license.FeatureTimeTracking) { + return nil + } + if len(taskIDs) == 0 { + return nil + } + + zero := int64(0) + for _, taskID := range taskIDs { + if task, ok := taskMap[taskID]; ok { + task.TimeEntriesCount = &zero + } + } + + type timeEntriesCount struct { + TaskID int64 `xorm:"task_id"` + Count int64 `xorm:"count"` + } + + counts := []timeEntriesCount{} + if err := s. + Select("task_id, COUNT(*) as count"). + Where(builder.In("task_id", taskIDs)). + GroupBy("task_id"). + Table("time_entries"). + Find(&counts); err != nil { + return err + } + + for _, c := range counts { + if task, ok := taskMap[c.TaskID]; ok { + task.TimeEntriesCount = &c.Count + } + } + + return nil +} diff --git a/pkg/models/time_tracking_filter.go b/pkg/models/time_tracking_filter.go new file mode 100644 index 000000000..ec4c7b68e --- /dev/null +++ b/pkg/models/time_tracking_filter.go @@ -0,0 +1,204 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package models + +import ( + "strconv" + "strings" + "time" + + "code.vikunja.io/api/pkg/config" + + "github.com/ganigeorgiev/fexpr" + "github.com/jszwedko/go-datemath" + "xorm.io/builder" +) + +// entriesForProjectCond matches time entries belonging to a project given a +// predicate over a project_id column: standalone entries whose own project_id +// matches, plus task-attached entries whose task currently lives in a matching +// project. Tasks move between projects, so the project is resolved via the task +// at query time rather than denormalized. Used for both permission scoping and +// the project_id filter. +func entriesForProjectCond(projectIDCond builder.Cond) builder.Cond { + return builder.Or( + projectIDCond, + builder.In("task_id", + builder.Select("id").From("tasks").Where(projectIDCond), + ), + ) +} + +// timeEntryFilterCond parses a task-style filter string into a condition over +// the time_entries table, or nil for an empty filter. Filterable fields: +// user_id, task_id, project_id (ints / in-lists), start_time, end_time (dates, +// datemath, or the literal null for running timers). comment is deliberately +// not filterable — text matching belongs to search. +func timeEntryFilterCond(filter, filterTimezone string) (builder.Cond, error) { + if filter == "" { + return nil, nil + } + + parsed, err := fexpr.Parse(preprocessFilterString(filter)) + if err != nil { + return nil, &ErrInvalidFilterExpression{Expression: filter, ExpressionError: err} + } + + loc := config.GetTimeZone() + if filterTimezone != "" { + loc, err = time.LoadLocation(filterTimezone) + if err != nil { + return nil, &ErrInvalidTimezone{Name: filterTimezone, LoadError: err} + } + } + + return buildTimeEntryFilterCond(parsed, loc) +} + +func buildTimeEntryFilterCond(groups []fexpr.ExprGroup, loc *time.Location) (builder.Cond, error) { + conds := make([]builder.Cond, 0, len(groups)) + joins := make([]taskFilterConcatinator, 0, len(groups)) + + for _, g := range groups { + join := filterConcatAnd + if g.Join == fexpr.JoinOr { + join = filterConcatOr + } + + var ( + cond builder.Cond + err error + ) + switch item := g.Item.(type) { + case []fexpr.ExprGroup: // a parenthesized sub-expression + cond, err = buildTimeEntryFilterCond(item, loc) + case fexpr.Expr: + var comparator taskFilterComparator + comparator, err = getFilterComparatorFromOp(item.Op) + if err == nil { + cond, err = resolveTimeEntryFilter(item.Left.Literal, comparator, item.Right.Literal, loc) + } + } + if err != nil { + return nil, err + } + conds = append(conds, cond) + joins = append(joins, join) + } + + if len(conds) == 0 { + return nil, nil + } + result := conds[0] + for i := 1; i < len(conds); i++ { + if joins[i] == filterConcatOr { + result = builder.Or(result, conds[i]) + continue + } + result = builder.And(result, conds[i]) + } + return result, nil +} + +func resolveTimeEntryFilter(field string, comparator taskFilterComparator, raw string, loc *time.Location) (builder.Cond, error) { + switch field { + case "user_id", "task_id": + value, err := timeEntryIntFilterValue(raw, comparator) + if err != nil { + return nil, ErrInvalidTimeEntryFilterValue{Field: field, Value: raw} + } + return getFilterCond(&taskFilter{field: field, value: value, comparator: comparator, isNumeric: true}, false) + + case "project", "project_id": + value, err := timeEntryIntFilterValue(raw, comparator) + if err != nil { + return nil, ErrInvalidTimeEntryFilterValue{Field: "project_id", Value: raw} + } + // Build membership positively (standalone-in-project OR task-in-project) + // and negate the whole set for != / not in. Negating project_id alone would + // wrongly match task-attached entries, whose own project_id is 0. + positive, negate := comparator, false + if comparator == taskFilterComparatorNotEquals { + positive, negate = taskFilterComparatorEquals, true + } + if comparator == taskFilterComparatorNotIn { + positive, negate = taskFilterComparatorIn, true + } + inner, err := getFilterCond(&taskFilter{field: "project_id", value: value, comparator: positive, isNumeric: true}, false) + if err != nil { + return nil, err + } + cond := entriesForProjectCond(inner) + if negate { + cond = builder.Not{cond} + } + return cond, nil + + case "start_time", "end_time": + if raw == "null" { + return nullTimeFilterCond(field, comparator) + } + value, err := timeEntryTimeFilterValue(raw, loc) + if err != nil { + return nil, ErrInvalidTimeEntryFilterValue{Field: field, Value: raw} + } + return getFilterCond(&taskFilter{field: field, value: value, comparator: comparator}, false) + + default: + return nil, ErrInvalidTimeEntryFilterField{Field: field} + } +} + +// nullTimeFilterCond handles `end_time = null` (running timers) and its negation. +func nullTimeFilterCond(field string, comparator taskFilterComparator) (builder.Cond, error) { + if comparator == taskFilterComparatorEquals { + return &builder.IsNull{field}, nil + } + if comparator == taskFilterComparatorNotEquals { + return &builder.NotNull{field}, nil + } + return nil, ErrInvalidTimeEntryFilterValue{Field: field, Value: "null"} +} + +func timeEntryIntFilterValue(raw string, comparator taskFilterComparator) (any, error) { + if comparator == taskFilterComparatorIn || comparator == taskFilterComparatorNotIn { + parts := strings.Split(raw, ",") + values := make([]int64, 0, len(parts)) + for _, part := range parts { + v, err := strconv.ParseInt(strings.TrimSpace(part), 10, 64) + if err != nil { + return nil, err + } + values = append(values, v) + } + return values, nil + } + return strconv.ParseInt(strings.TrimSpace(raw), 10, 64) +} + +// timeEntryTimeFilterValue mirrors the task filter's date handling: datemath +// (now, now-7d) first, then explicit date formats. +func timeEntryTimeFilterValue(raw string, loc *time.Location) (time.Time, error) { + if loc == nil { + loc = config.GetTimeZone() + } + if expr, err := safeDatemathParse(raw); err == nil { + t := expr.Time(datemath.WithLocation(loc)).In(config.GetTimeZone()) + return adjustDateForMysql(t), nil + } + return parseTimeFromUserInput(raw, loc) +} diff --git a/pkg/models/time_tracking_test.go b/pkg/models/time_tracking_test.go new file mode 100644 index 000000000..6e5391d51 --- /dev/null +++ b/pkg/models/time_tracking_test.go @@ -0,0 +1,730 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package models + +import ( + "context" + "encoding/json" + "testing" + "time" + + "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/events" + "code.vikunja.io/api/pkg/license" + "code.vikunja.io/api/pkg/user" + "code.vikunja.io/api/pkg/web" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func timePtr(t time.Time) *time.Time { return &t } + +// Fixture access graph (pkg/db/fixtures): project 1 is owned by user1 only +// (everyone else a stranger); task 1 lives in project 1. Project 3 is owned by +// user3, with user1 and user2 granted read. user4 has access to neither. +// Entries: 1 = user1 on task 1, 2 = user1 on project 1, 3 = user3 on project 3. + +func TestTimeEntry_CanRead(t *testing.T) { + tests := []struct { + name string + entryID int64 + auth web.Auth + wantCan bool + wantErr func(error) bool + }{ + {"owner reads task entry", 1, &user.User{ID: 1}, true, nil}, + {"owner reads project entry", 2, &user.User{ID: 1}, true, nil}, + {"reader reads other user's entry on a shared project", 3, &user.User{ID: 1}, true, nil}, + {"stranger denied on owned project", 1, &user.User{ID: 4}, false, nil}, + {"stranger denied on shared project", 3, &user.User{ID: 4}, false, nil}, + {"link share denied", 1, &LinkSharing{ID: 1, ProjectID: 1}, false, nil}, + {"missing entry is a 404", 999, &user.User{ID: 1}, false, IsErrTimeEntryDoesNotExist}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + + can, _, err := (&TimeEntry{ID: tt.entryID}).CanRead(s, tt.auth) + if tt.wantErr != nil { + require.Error(t, err) + assert.True(t, tt.wantErr(err), "unexpected error type: %v", err) + return + } + require.NoError(t, err) + assert.Equal(t, tt.wantCan, can) + }) + } +} + +func TestTimeEntry_CanCreate(t *testing.T) { + tests := []struct { + name string + entry *TimeEntry + auth web.Auth + wantCan bool + wantErr func(error) bool + }{ + {"on a task in an owned project", &TimeEntry{TaskID: 1}, &user.User{ID: 1}, true, nil}, + {"on an owned project", &TimeEntry{ProjectID: 1}, &user.User{ID: 1}, true, nil}, + {"on a readable project", &TimeEntry{ProjectID: 3}, &user.User{ID: 1}, true, nil}, + {"stranger denied", &TimeEntry{ProjectID: 1}, &user.User{ID: 4}, false, nil}, + {"both task and project is invalid", &TimeEntry{TaskID: 1, ProjectID: 1}, &user.User{ID: 1}, false, IsErrTimeEntryInvalidContainer}, + {"neither task nor project is invalid", &TimeEntry{}, &user.User{ID: 1}, false, IsErrTimeEntryInvalidContainer}, + {"link share denied", &TimeEntry{ProjectID: 1}, &LinkSharing{ID: 1, ProjectID: 1}, false, nil}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + + can, err := tt.entry.CanCreate(s, tt.auth) + if tt.wantErr != nil { + require.Error(t, err) + assert.True(t, tt.wantErr(err), "unexpected error type: %v", err) + return + } + require.NoError(t, err) + assert.Equal(t, tt.wantCan, can) + }) + } +} + +// Entry 3 is authored by user3; user1 can read project 3 but is not the author, +// so it can read but not modify. +func TestTimeEntry_CanModify(t *testing.T) { + tests := []struct { + name string + entryID int64 + auth web.Auth + wantCan bool + }{ + {"author modifies own entry", 1, &user.User{ID: 1}, true}, + {"author modifies own entry on shared project", 3, &user.User{ID: 3}, true}, + {"reader who is not author cannot modify", 3, &user.User{ID: 1}, false}, + {"stranger cannot modify", 3, &user.User{ID: 4}, false}, + {"link share cannot modify", 1, &LinkSharing{ID: 1, ProjectID: 1}, false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + + canUpdate, err := (&TimeEntry{ID: tt.entryID}).CanUpdate(s, tt.auth) + require.NoError(t, err) + assert.Equal(t, tt.wantCan, canUpdate, "CanUpdate") + + canDelete, err := (&TimeEntry{ID: tt.entryID}).CanDelete(s, tt.auth) + require.NoError(t, err) + assert.Equal(t, tt.wantCan, canDelete, "CanDelete") + }) + } +} + +// Guards the data leak: ReadAll must return only entries on tasks/projects the +// caller can read, since DoReadAll runs no permission check. +func TestTimeEntry_ReadAll(t *testing.T) { + tests := []struct { + name string + auth web.Auth + wantIDs []int64 + }{ + {"user sees every readable entry", &user.User{ID: 1}, []int64{1, 2, 3, 4}}, + {"user sees only entries on projects they can read", &user.User{ID: 2}, []int64{3}}, + {"stranger sees nothing", &user.User{ID: 4}, []int64{}}, + {"link share sees nothing", &LinkSharing{ID: 1, ProjectID: 1}, []int64{}}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + + result, count, total, err := (&TimeEntry{}).ReadAll(s, tt.auth, "", 1, 50) + require.NoError(t, err) + entries, ok := result.([]*TimeEntry) + require.True(t, ok) + + gotIDs := make([]int64, 0, len(entries)) + for _, e := range entries { + gotIDs = append(gotIDs, e.ID) + } + assert.ElementsMatch(t, tt.wantIDs, gotIDs) + assert.Equal(t, len(tt.wantIDs), count) + assert.Equal(t, int64(len(tt.wantIDs)), total) + }) + } +} + +// Filtering reuses the task filter grammar. user1 can read entries 1,2,4 +// (project 1) and 3 (project 3, shared) — the filter only narrows that set. +func TestTimeEntry_ReadAll_Filter(t *testing.T) { + tests := []struct { + name string + filter string + wantIDs []int64 + wantErr bool + }{ + {"by user", "user_id = 3", []int64{3}, false}, + {"by task", "task_id = 1", []int64{1, 4}, false}, + {"by project unions task-attached entries", "project_id = 1", []int64{1, 2, 4}, false}, + {"by project negated", "project_id != 1", []int64{3}, false}, + {"by start time", "start_time > '2018-12-01T11:00:00+00:00'", []int64{2, 3, 4}, false}, + {"running timers via null end_time", "end_time = null", []int64{4}, false}, + {"compound and", "user_id = 1 && end_time = null", []int64{4}, false}, + {"compound or", "user_id = 3 || task_id = 1", []int64{1, 3, 4}, false}, + {"in list", "user_id in 1,3", []int64{1, 2, 3, 4}, false}, + {"comment is not filterable", "comment = whatever", nil, true}, + {"unknown field errors", "bogus = 1", nil, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + + te := &TimeEntry{Filter: tt.filter} + result, _, _, err := te.ReadAll(s, &user.User{ID: 1}, "", 1, 50) + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + entries, ok := result.([]*TimeEntry) + require.True(t, ok) + gotIDs := make([]int64, 0, len(entries)) + for _, e := range entries { + gotIDs = append(gotIDs, e.ID) + } + assert.ElementsMatch(t, tt.wantIDs, gotIDs) + }) + } +} + +// Search matches the entry comment. Comments: 1="Time entry on task 1", +// 2/3 contain "Standalone", 4="Running timer". +func TestTimeEntry_ReadAll_Search(t *testing.T) { + tests := []struct { + name string + search string + wantIDs []int64 + }{ + {"matches a comment", "Running", []int64{4}}, + {"is case-insensitive", "running", []int64{4}}, + {"matches several", "Standalone", []int64{2, 3}}, + {"no match", "nothing matches this", []int64{}}, + {"empty search returns all readable", "", []int64{1, 2, 3, 4}}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + + result, _, _, err := (&TimeEntry{}).ReadAll(s, &user.User{ID: 1}, tt.search, 1, 50) + require.NoError(t, err) + entries, ok := result.([]*TimeEntry) + require.True(t, ok) + gotIDs := make([]int64, 0, len(entries)) + for _, e := range entries { + gotIDs = append(gotIDs, e.ID) + } + assert.ElementsMatch(t, tt.wantIDs, gotIDs) + }) + } +} + +func TestTimeEntry_Create(t *testing.T) { + t.Run("manual entry keeps its start time and is owned by the caller", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + + start := time.Date(2020, 1, 1, 9, 0, 0, 0, time.UTC) + end := time.Date(2020, 1, 1, 10, 0, 0, 0, time.UTC) + te := &TimeEntry{TaskID: 1, StartTime: start, EndTime: &end, Comment: "work"} + require.NoError(t, te.Create(s, &user.User{ID: 1})) + require.NoError(t, s.Commit()) + + assert.Equal(t, int64(1), te.UserID) + assert.True(t, te.StartTime.Equal(start)) + db.AssertExists(t, "time_entries", map[string]interface{}{ + "id": te.ID, + "user_id": 1, + "task_id": 1, + "comment": "work", + }, false) + }) + + t.Run("defaults the start time to now when none is given", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + + te := &TimeEntry{TaskID: 1} + require.NoError(t, te.Create(s, &user.User{ID: 1})) + assert.False(t, te.StartTime.IsZero()) + }) + + t.Run("a completed manual entry leaves a running timer alone", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + + // entry 4 is user1's running timer + manual := &TimeEntry{ + TaskID: 1, + StartTime: time.Date(2020, 1, 1, 9, 0, 0, 0, time.UTC), + EndTime: timePtr(time.Date(2020, 1, 1, 10, 0, 0, 0, time.UTC)), + } + require.NoError(t, manual.Create(s, &user.User{ID: 1})) + require.NoError(t, s.Commit()) + + running := &TimeEntry{} + exists, err := s.Where("id = ?", 4).Get(running) + require.NoError(t, err) + require.True(t, exists) + assert.Nil(t, running.EndTime, "a manual entry must not stop the running timer") + }) + + t.Run("auto-stops the caller's running timer", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + a := &user.User{ID: 1} + + first := &TimeEntry{TaskID: 1} + require.NoError(t, first.Create(s, a)) + require.Nil(t, first.EndTime, "first timer should be running") + + second := &TimeEntry{TaskID: 1} + require.NoError(t, second.Create(s, a)) + require.NoError(t, s.Commit()) + + reloaded := &TimeEntry{} + exists, err := s.Where("id = ?", first.ID).Get(reloaded) + require.NoError(t, err) + require.True(t, exists) + assert.NotNil(t, reloaded.EndTime, "first timer should have been auto-stopped") + assert.Nil(t, second.EndTime, "second timer should still be running") + }) +} + +// A running timer (no end) must round-trip as a NULL end_time: found by the +// null filter and serialized as JSON null, never the 0001-01-01 zero sentinel. +func TestTimeEntry_RunningTimerEndTimeIsNull(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + a := &user.User{ID: 1} + + te := &TimeEntry{TaskID: 1, StartTime: time.Date(2020, 1, 1, 9, 0, 0, 0, time.UTC)} + require.NoError(t, te.Create(s, a)) + require.NoError(t, s.Commit()) + + reloaded, err := getTimeEntryByID(s, te.ID) + require.NoError(t, err) + + marshalled, err := json.Marshal(reloaded) + require.NoError(t, err) + assert.Contains(t, string(marshalled), `"end_time":null`) + assert.NotContains(t, string(marshalled), "0001-01-01") + + // Stored as NULL, so the null filter matches it (not just the fixtures). + found := &TimeEntry{Filter: "end_time = null"} + result, _, _, err := found.ReadAll(s, a, "", 1, 50) + require.NoError(t, err) + ids := []int64{} + for _, e := range result.([]*TimeEntry) { + ids = append(ids, e.ID) + } + assert.Contains(t, ids, te.ID) +} + +// Regression guard: the permission check must not clobber the update payload. +func TestTimeEntry_Update(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + a := &user.User{ID: 1} + + te := &TimeEntry{ + ID: 1, + TaskID: 1, + StartTime: time.Date(2020, 1, 1, 9, 0, 0, 0, time.UTC), + EndTime: timePtr(time.Date(2020, 1, 1, 10, 0, 0, 0, time.UTC)), + Comment: "updated comment", + } + + can, err := te.CanUpdate(s, a) // the handler calls this before Update + require.NoError(t, err) + require.True(t, can) + require.NoError(t, te.Update(s, a)) + require.NoError(t, s.Commit()) + + assert.Equal(t, "updated comment", te.Comment) + db.AssertExists(t, "time_entries", map[string]interface{}{ + "id": 1, + "comment": "updated comment", + }, false) +} + +func TestTimeEntry_UpdateReassignsContainer(t *testing.T) { + validTimes := func(te *TimeEntry) { + te.StartTime = time.Date(2020, 1, 1, 9, 0, 0, 0, time.UTC) + te.EndTime = timePtr(time.Date(2020, 1, 1, 10, 0, 0, 0, time.UTC)) + } + + t.Run("moves an entry from a task to a project", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + a := &user.User{ID: 1} + + // Entry 1 is on task 1; move it onto project 1 directly. + te := &TimeEntry{ID: 1, ProjectID: 1} + validTimes(te) + + can, err := te.CanUpdate(s, a) + require.NoError(t, err) + require.True(t, can) + require.NoError(t, te.Update(s, a)) + require.NoError(t, s.Commit()) + + db.AssertExists(t, "time_entries", map[string]interface{}{ + "id": 1, + "task_id": 0, + "project_id": 1, + }, false) + }) + + t.Run("rejects an update that sets both task and project", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + + _, err := (&TimeEntry{ID: 1, TaskID: 1, ProjectID: 1}).CanUpdate(s, &user.User{ID: 1}) + require.Error(t, err) + assert.True(t, IsErrTimeEntryInvalidContainer(err)) + }) + + t.Run("an omitted container keeps the existing one", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + a := &user.User{ID: 1} + + // Entry 1 is on task 1; update only the comment, no container set. + te := &TimeEntry{ID: 1, Comment: "kept on task"} + validTimes(te) + + can, err := te.CanUpdate(s, a) + require.NoError(t, err) + require.True(t, can) + require.NoError(t, te.Update(s, a)) + require.NoError(t, s.Commit()) + + db.AssertExists(t, "time_entries", map[string]interface{}{ + "id": 1, + "task_id": 1, + "project_id": 0, + "comment": "kept on task", + }, false) + }) +} + +func TestTimeEntry_UpdateReopenGuard(t *testing.T) { + a := &user.User{ID: 1} + someStart := time.Date(2020, 1, 1, 9, 0, 0, 0, time.UTC) + + t.Run("rejects clearing the end of a completed entry", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + + // Entry 1 is completed; a nil end would reopen it as a running timer. + te := &TimeEntry{ID: 1, TaskID: 1, StartTime: someStart} // EndTime nil + can, err := te.CanUpdate(s, a) + require.NoError(t, err) + require.True(t, can) + + err = te.Update(s, a) + require.Error(t, err) + assert.True(t, IsErrTimeEntryAlreadyEnded(err), "unexpected error type: %v", err) + }) + + t.Run("allows editing a running entry while it stays running", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + + // Entry 4 is user1's running timer; keeping it running (nil end) is fine. + te := &TimeEntry{ID: 4, TaskID: 1, StartTime: someStart, Comment: "edited"} // EndTime nil + can, err := te.CanUpdate(s, a) + require.NoError(t, err) + require.True(t, can) + require.NoError(t, te.Update(s, a)) + }) +} + +func TestTimeEntry_RejectsInvertedInterval(t *testing.T) { + a := &user.User{ID: 1} + start := time.Date(2020, 1, 1, 10, 0, 0, 0, time.UTC) + before := time.Date(2020, 1, 1, 9, 0, 0, 0, time.UTC) + + t.Run("create rejects an end before the start", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + + te := &TimeEntry{TaskID: 1, StartTime: start, EndTime: timePtr(before)} + err := te.Create(s, a) + require.Error(t, err) + assert.True(t, IsErrTimeEntryEndBeforeStart(err), "unexpected error type: %v", err) + }) + + t.Run("create allows an end equal to the start", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + + te := &TimeEntry{TaskID: 1, StartTime: start, EndTime: timePtr(start)} + require.NoError(t, te.Create(s, a)) + }) + + t.Run("create allows a running timer with no end", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + + te := &TimeEntry{TaskID: 1, StartTime: start} // EndTime nil + require.NoError(t, te.Create(s, a)) + }) + + t.Run("update rejects an end before the start", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + + // Entry 1 is user1's completed entry. + te := &TimeEntry{ID: 1, TaskID: 1, StartTime: start, EndTime: timePtr(before)} + can, err := te.CanUpdate(s, a) + require.NoError(t, err) + require.True(t, can) + + err = te.Update(s, a) + require.Error(t, err) + assert.True(t, IsErrTimeEntryEndBeforeStart(err), "unexpected error type: %v", err) + }) +} + +func TestTimeEntry_StopRunningTimer(t *testing.T) { + t.Run("stops the caller's running timer and returns it", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + + entry, err := StopRunningTimer(s, &user.User{ID: 1}) // entry 4 + require.NoError(t, err) + require.NoError(t, s.Commit()) + + assert.Equal(t, int64(4), entry.ID) + assert.NotNil(t, entry.EndTime) + + reloaded := &TimeEntry{} + _, err = s.Where("id = ?", 4).Get(reloaded) + require.NoError(t, err) + assert.NotNil(t, reloaded.EndTime, "end time should be persisted") + }) + + t.Run("errors when no timer is running", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + + _, err := StopRunningTimer(s, &user.User{ID: 2}) // user2 has no entries + require.Error(t, err) + assert.True(t, IsErrNoRunningTimer(err), "unexpected error type: %v", err) + }) + + t.Run("denies a link share and leaves the matching user's timer running", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + + // Share id 1 collides with user 1, whose entry 4 is a running timer. + _, err := StopRunningTimer(s, &LinkSharing{ID: 1, ProjectID: 1}) + require.Error(t, err) + assert.True(t, IsErrGenericForbidden(err), "unexpected error type: %v", err) + + running := &TimeEntry{} + exists, err := s.Where("id = ?", 4).Get(running) + require.NoError(t, err) + require.True(t, exists) + assert.Nil(t, running.EndTime, "the user's timer must not have been stopped by a link share") + }) +} + +func TestTimeEntry_Events(t *testing.T) { + u := &user.User{ID: 1} + someStart := time.Date(2020, 1, 1, 9, 0, 0, 0, time.UTC) + someEnd := timePtr(time.Date(2020, 1, 1, 10, 0, 0, 0, time.UTC)) + + t.Run("create dispatches created", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + events.ClearDispatchedEvents() + s := db.NewSession() + defer s.Close() + + te := &TimeEntry{TaskID: 1, StartTime: someStart, EndTime: someEnd} + require.NoError(t, te.Create(s, u)) + require.NoError(t, s.Commit()) + events.DispatchPending(context.Background(), s) + events.AssertDispatched(t, &TimeEntryCreatedEvent{}) + }) + + t.Run("update dispatches updated", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + events.ClearDispatchedEvents() + s := db.NewSession() + defer s.Close() + + te := &TimeEntry{ID: 1, TaskID: 1, StartTime: someStart, EndTime: someEnd, Comment: "edited"} + can, err := te.CanUpdate(s, u) + require.NoError(t, err) + require.True(t, can) + require.NoError(t, te.Update(s, u)) + require.NoError(t, s.Commit()) + events.DispatchPending(context.Background(), s) + events.AssertDispatched(t, &TimeEntryUpdatedEvent{}) + }) + + t.Run("delete dispatches deleted", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + events.ClearDispatchedEvents() + s := db.NewSession() + defer s.Close() + + require.NoError(t, (&TimeEntry{ID: 1}).Delete(s, u)) + require.NoError(t, s.Commit()) + events.DispatchPending(context.Background(), s) + events.AssertDispatched(t, &TimeEntryDeletedEvent{}) + }) + + t.Run("starting a timer dispatches created plus updated for the auto-stopped entry", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + events.ClearDispatchedEvents() + s := db.NewSession() + defer s.Close() + + // entry 4 is user1's running timer; a new running timer auto-stops it + require.NoError(t, (&TimeEntry{TaskID: 1}).Create(s, u)) + require.NoError(t, s.Commit()) + events.DispatchPending(context.Background(), s) + events.AssertDispatched(t, &TimeEntryCreatedEvent{}) + events.AssertDispatched(t, &TimeEntryUpdatedEvent{}) + }) + + t.Run("a completed manual entry dispatches only created", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + events.ClearDispatchedEvents() + s := db.NewSession() + defer s.Close() + + te := &TimeEntry{TaskID: 1, StartTime: someStart, EndTime: someEnd} + require.NoError(t, te.Create(s, u)) + require.NoError(t, s.Commit()) + events.DispatchPending(context.Background(), s) + assert.Equal(t, 1, events.CountDispatchedEvents((&TimeEntryCreatedEvent{}).Name())) + assert.Equal(t, 0, events.CountDispatchedEvents((&TimeEntryUpdatedEvent{}).Name()), "a completed manual entry must not auto-stop") + }) + + t.Run("StopRunningTimer dispatches updated", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + events.ClearDispatchedEvents() + s := db.NewSession() + defer s.Close() + + _, err := StopRunningTimer(s, u) + require.NoError(t, err) + require.NoError(t, s.Commit()) + events.DispatchPending(context.Background(), s) + events.AssertDispatched(t, &TimeEntryUpdatedEvent{}) + }) +} + +func TestTimeEntry_Delete(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + + require.NoError(t, (&TimeEntry{ID: 1}).Delete(s, &user.User{ID: 1})) + require.NoError(t, s.Commit()) + db.AssertMissing(t, "time_entries", map[string]interface{}{"id": 1}) +} + +func TestTimeEntry_TaskCount(t *testing.T) { + u := &user.User{ID: 1} + + t.Run("attaches counts for a licensed, non-share caller", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + license.SetForTests([]license.Feature{license.FeatureTimeTracking}) + defer license.ResetForTests() + + task1 := &Task{ID: 1} // fixtures: time entries 1 and 4 are attached to task 1 + task2 := &Task{ID: 2} // no time entries + taskMap := map[int64]*Task{1: task1, 2: task2} + + require.NoError(t, addTimeEntriesCountToTasks(s, u, []int64{1, 2}, taskMap)) + + require.NotNil(t, task1.TimeEntriesCount) + assert.Equal(t, int64(2), *task1.TimeEntriesCount) + require.NotNil(t, task2.TimeEntriesCount) + assert.Equal(t, int64(0), *task2.TimeEntriesCount) + }) + + t.Run("leaves the count unset for a link share", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + license.SetForTests([]license.Feature{license.FeatureTimeTracking}) + defer license.ResetForTests() + + task1 := &Task{ID: 1} + taskMap := map[int64]*Task{1: task1} + require.NoError(t, addTimeEntriesCountToTasks(s, &LinkSharing{ID: 1}, []int64{1}, taskMap)) + assert.Nil(t, task1.TimeEntriesCount, "link shares must not learn time-entry counts") + }) + + t.Run("leaves the count unset when the feature is unlicensed", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + license.ResetForTests() // feature disabled + + task1 := &Task{ID: 1} + taskMap := map[int64]*Task{1: task1} + require.NoError(t, addTimeEntriesCountToTasks(s, u, []int64{1}, taskMap)) + assert.Nil(t, task1.TimeEntriesCount, "an unlicensed instance must not expose counts") + }) +} diff --git a/pkg/models/user_project.go b/pkg/models/user_project.go index 34ea85abe..7fe98c772 100644 --- a/pkg/models/user_project.go +++ b/pkg/models/user_project.go @@ -18,10 +18,30 @@ package models import ( "code.vikunja.io/api/pkg/user" + "code.vikunja.io/api/pkg/web" "xorm.io/builder" "xorm.io/xorm" ) +// SearchUsersForProject performs the per-project user search shared by both API +// versions: it checks the caller can read the project, then lists the users +// with access to it. canRead is false (with no error) when the caller lacks +// read access, so each handler can map that to its own forbidden response. +func SearchUsersForProject(s *xorm.Session, project *Project, a web.Auth, currentUser *user.User, search string) (users []*user.User, canRead bool, err error) { + canRead, _, err = project.CanRead(s, a) + if err != nil { + return nil, false, err + } + if !canRead { + return nil, false, nil + } + users, err = ListUsersFromProject(s, project, currentUser, search) + if err != nil { + return nil, true, err + } + return users, true, nil +} + // ProjectUIDs hold all kinds of user IDs from accounts who have access to a project type ProjectUIDs struct { ProjectOwnerID int64 `xorm:"projectOwner"` diff --git a/pkg/models/user_settings.go b/pkg/models/user_settings.go new file mode 100644 index 000000000..0d905cd1a --- /dev/null +++ b/pkg/models/user_settings.go @@ -0,0 +1,130 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package models + +import ( + "context" + + "code.vikunja.io/api/pkg/modules/avatar" + "code.vikunja.io/api/pkg/user" + + "xorm.io/xorm" +) + +// UserGeneralSettings is the single user-settings wire struct shared by v1 and +// v2 — both the update request body and the nested settings on GET /user. A +// dedicated struct (not user.User) is required: user.User's settings fields are +// json:"-" so they don't leak when it is embedded in other responses +// (assignees, created_by, members …). +type UserGeneralSettings struct { + Name string `json:"name" doc:"The full name of the user."` + EmailRemindersEnabled bool `json:"email_reminders_enabled" doc:"If enabled, sends email reminders of tasks to the user."` + DiscoverableByName bool `json:"discoverable_by_name" doc:"If true, this user can be found by their name or parts of it when searching."` + DiscoverableByEmail bool `json:"discoverable_by_email" doc:"If true, the user can be found when searching for their exact email."` + OverdueTasksRemindersEnabled bool `json:"overdue_tasks_reminders_enabled" doc:"If enabled, the user gets an email for their overdue tasks each morning."` + OverdueTasksRemindersTime string `json:"overdue_tasks_reminders_time" valid:"time,required" doc:"The time the daily overdue-tasks summary is sent, as HH:MM."` + DefaultProjectID int64 `json:"default_project_id" doc:"Project a task is filed under when created without an explicit project."` + WeekStart int `json:"week_start" valid:"range(0|6)" minimum:"0" maximum:"6" doc:"The day the week starts on: 0=sunday, 1=monday, … 6=saturday."` + Language string `json:"language" doc:"The user's language."` + Timezone string `json:"timezone" doc:"The user's time zone, used to send task reminders in their local time."` + FrontendSettings any `json:"frontend_settings" doc:"Arbitrary settings used only by the frontend. Any JSON value; stored and returned verbatim."` + // Server/OpenID-provided; populated on read, ignored on write. + ExtraSettingsLinks map[string]any `json:"extra_settings_links" readOnly:"true" doc:"Additional settings links provided by the OpenID provider. Server-controlled."` +} + +// NewUserGeneralSettings projects a user's stored settings into the shared wire +// struct for GET /user. Used by both the v1 and v2 user-show handlers. +func NewUserGeneralSettings(u *user.User) *UserGeneralSettings { + return &UserGeneralSettings{ + Name: u.Name, + EmailRemindersEnabled: u.EmailRemindersEnabled, + DiscoverableByName: u.DiscoverableByName, + DiscoverableByEmail: u.DiscoverableByEmail, + OverdueTasksRemindersEnabled: u.OverdueTasksRemindersEnabled, + OverdueTasksRemindersTime: u.OverdueTasksRemindersTime, + DefaultProjectID: u.DefaultProjectID, + WeekStart: u.WeekStart, + Language: u.Language, + Timezone: u.Timezone, + FrontendSettings: u.FrontendSettings, + ExtraSettingsLinks: u.ExtraSettingsLinks, + } +} + +// ChangeUserPassword verifies the old password, sets the new one, and +// invalidates all of the user's sessions. Lives here (not in pkg/user) because +// it needs DeleteAllUserSessions, which pkg/user cannot import. +func ChangeUserPassword(ctx context.Context, s *xorm.Session, u *user.User, oldPassword, newPassword string) error { + if oldPassword == "" { + return user.ErrEmptyOldPassword{} + } + + if _, err := user.CheckUserCredentials(ctx, s, &user.Login{Username: u.Username, Password: oldPassword}); err != nil { + return err + } + + if err := user.UpdateUserPassword(s, u, newPassword); err != nil { + return err + } + + return DeleteAllUserSessions(s, u.ID) +} + +// UpdateUserGeneralSettings copies the general settings onto the user, persists +// them, and flushes the avatar cache when an initials avatar's name changed. +// Lives here (not in pkg/user) because the avatar flush needs pkg/modules/avatar, +// which pkg/user cannot import. +func UpdateUserGeneralSettings(s *xorm.Session, u *user.User, settings *UserGeneralSettings) error { + invalidateAvatar := u.AvatarProvider == "initials" && u.Name != settings.Name + + u.Name = settings.Name + u.EmailRemindersEnabled = settings.EmailRemindersEnabled + u.DiscoverableByEmail = settings.DiscoverableByEmail + u.DiscoverableByName = settings.DiscoverableByName + u.OverdueTasksRemindersEnabled = settings.OverdueTasksRemindersEnabled + u.DefaultProjectID = settings.DefaultProjectID + u.WeekStart = settings.WeekStart + u.Language = settings.Language + u.Timezone = settings.Timezone + u.OverdueTasksRemindersTime = settings.OverdueTasksRemindersTime + u.FrontendSettings = settings.FrontendSettings + + if _, err := user.UpdateUser(s, u, true); err != nil { + return err + } + + if invalidateAvatar { + avatar.FlushAllCaches(u) + } + return nil +} + +// UpdateUserAvatarProvider sets the user's avatar provider, persists it, and +// flushes the avatar cache when the provider changes (or is set to initials). +func UpdateUserAvatarProvider(s *xorm.Session, u *user.User, provider string) error { + oldProvider := u.AvatarProvider + u.AvatarProvider = provider + + if _, err := user.UpdateUser(s, u, false); err != nil { + return err + } + + if u.AvatarProvider == "initials" || oldProvider != u.AvatarProvider { + avatar.FlushAllCaches(u) + } + return nil +} diff --git a/pkg/models/users.go b/pkg/models/users.go index da2b7af97..b5a79f510 100644 --- a/pkg/models/users.go +++ b/pkg/models/users.go @@ -22,6 +22,33 @@ import ( "xorm.io/xorm" ) +// doerFromAuth resolves the authenticated principal into a full user for event payloads. The JWT +// only carries id + username, so without a re-fetch notifications and emails render the +// auto-generated username instead of the display name (#2720). Status errors (disabled/locked) are +// swallowed because their user is still populated and some flows act on behalf of such accounts +// (e.g. user deletion deletes that user's tasks); the partial principal is used as a last resort. +func doerFromAuth(s *xorm.Session, a web.Auth) *user.User { + if a == nil { + return nil + } + + doer, err := GetUserOrLinkShareUser(s, a) + if err != nil && !user.IsErrUserStatusError(err) { + doer = nil + } + if doer != nil && doer.ID != 0 { + return doer + } + + if u, is := a.(*user.User); is { + return u + } + if share, is := a.(*LinkSharing); is { + return share.toUser() + } + return &user.User{ID: a.GetID()} +} + // GetUserOrLinkShareUser returns either a user or a link share disguised as a user. func GetUserOrLinkShareUser(s *xorm.Session, a web.Auth) (uu *user.User, err error) { if u, is := a.(*user.User); is { diff --git a/pkg/models/webhooks.go b/pkg/models/webhooks.go index d7e3787d4..b4038bf6c 100644 --- a/pkg/models/webhooks.go +++ b/pkg/models/webhooks.go @@ -40,6 +40,7 @@ import ( "code.vikunja.io/api/pkg/version" "code.vikunja.io/api/pkg/web" + "xorm.io/builder" "xorm.io/xorm" ) @@ -47,29 +48,29 @@ var webhookClient *http.Client type Webhook struct { // The generated ID of this webhook target - ID int64 `xorm:"bigint autoincr not null unique pk" json:"id" param:"webhook"` + ID int64 `xorm:"bigint autoincr not null unique pk" json:"id" param:"webhook" readOnly:"true" doc:"The generated ID of this webhook target."` // The target URL where the POST request with the webhook payload will be made - TargetURL string `xorm:"not null" valid:"required,url" json:"target_url"` + TargetURL string `xorm:"not null" valid:"required,url" json:"target_url" doc:"The target URL where the POST request with the webhook payload will be made."` // The webhook events which should fire this webhook target - Events []string `xorm:"JSON not null" valid:"required" json:"events"` + Events []string `xorm:"JSON not null" valid:"required" json:"events" doc:"The webhook events which should fire this webhook target. Get the available events from /api/v1/webhooks/events."` // The project ID of the project this webhook target belongs to - ProjectID int64 `xorm:"bigint null index" json:"project_id" param:"project"` + ProjectID int64 `xorm:"bigint null index" json:"project_id" param:"project" readOnly:"true" doc:"The id of the project this webhook target belongs to. Set from the URL, not the body."` // The user ID if this is a user-level webhook (mutually exclusive with ProjectID) - UserID int64 `xorm:"bigint null index" json:"user_id"` + UserID int64 `xorm:"bigint null index" json:"user_id" readOnly:"true" doc:"The id of the user if this is a user-level webhook (mutually exclusive with project_id)."` // If provided, webhook requests will be signed using HMAC. Check out the docs about how to use this: https://vikunja.io/docs/webhooks/#signing - Secret string `xorm:"null" json:"secret"` + Secret string `xorm:"null" json:"secret" writeOnly:"true" doc:"If provided, webhook requests will be signed using HMAC. See https://vikunja.io/docs/webhooks/#signing. Write-only: never returned in responses."` // If provided, webhook requests will be sent with a Basic Auth header. - BasicAuthUser string `xorm:"null" json:"basic_auth_user"` - BasicAuthPassword string `xorm:"null" json:"basic_auth_password"` + BasicAuthUser string `xorm:"null" json:"basic_auth_user" writeOnly:"true" doc:"If provided together with basic_auth_password, webhook requests will be sent with a Basic Auth header. Write-only: never returned in responses."` + BasicAuthPassword string `xorm:"null" json:"basic_auth_password" writeOnly:"true" doc:"The password for the Basic Auth header. Write-only: never returned in responses."` // The user who initially created the webhook target. - CreatedBy *user.User `xorm:"-" json:"created_by" valid:"-"` + CreatedBy *user.User `xorm:"-" json:"created_by" valid:"-" readOnly:"true" doc:"The user who initially created the webhook target."` CreatedByID int64 `xorm:"bigint not null" json:"-"` // A timestamp when this webhook target was created. You cannot change this value. - Created time.Time `xorm:"created not null" json:"created"` + Created time.Time `xorm:"created not null" json:"created" readOnly:"true" doc:"A timestamp when this webhook target was created. You cannot change this value."` // A timestamp when this webhook target was last updated. You cannot change this value. - Updated time.Time `xorm:"updated not null" json:"updated"` + Updated time.Time `xorm:"updated not null" json:"updated" readOnly:"true" doc:"A timestamp when this webhook target was last updated. You cannot change this value."` web.CRUDable `xorm:"-" json:"-"` web.Permissions `xorm:"-" json:"-"` @@ -79,6 +80,17 @@ func (w *Webhook) TableName() string { return "webhooks" } +// maskCredentials clears the write-only secret and basic-auth fields so they are +// never echoed back in a response. The client already submitted these values and +// the DB row keeps them (outgoing deliveries reload and sign from the DB copy); +// only the in-memory struct returned to the caller is cleared. Always call this +// after the DB write, never before. +func (w *Webhook) maskCredentials() { + w.Secret = "" + w.BasicAuthUser = "" + w.BasicAuthPassword = "" +} + var availableWebhookEvents map[string]bool var availableWebhookEventsLock *sync.Mutex var userDirectedWebhookEvents map[string]bool @@ -183,6 +195,11 @@ func (w *Webhook) Create(s *xorm.Session, a web.Auth) (err error) { } w.CreatedBy, err = user.GetUserByID(s, a.GetID()) + if err != nil { + return err + } + + w.maskCredentials() return } @@ -200,24 +217,36 @@ func (w *Webhook) Create(s *xorm.Session, a web.Auth) (err error) { // @Failure 500 {object} models.Message "Internal server error" // @Router /projects/{id}/webhooks [get] func (w *Webhook) ReadAll(s *xorm.Session, a web.Auth, _ string, page int, perPage int) (result interface{}, resultCount int, numberOfTotalItems int64, err error) { - p := &Project{ID: w.ProjectID} - can, _, err := p.CanRead(s, a) - if err != nil { - return nil, 0, 0, err - } - if !can { - return nil, 0, 0, ErrGenericForbidden{} + // w.UserID set selects the user-level list: a user may only see their own + // webhooks. The project list (w.UserID == 0) delegates to the project's read + // permission instead. + var listCond builder.Cond + if w.UserID > 0 { + if _, isShareAuth := a.(*LinkSharing); isShareAuth || w.UserID != a.GetID() { + return nil, 0, 0, ErrGenericForbidden{} + } + listCond = builder.Eq{"user_id": w.UserID} + } else { + p := &Project{ID: w.ProjectID} + can, _, cerr := p.CanRead(s, a) + if cerr != nil { + return nil, 0, 0, cerr + } + if !can { + return nil, 0, 0, ErrGenericForbidden{} + } + listCond = builder.Eq{"project_id": w.ProjectID} } ws := []*Webhook{} - err = s.Where("project_id = ?", w.ProjectID). + err = s.Where(listCond). Limit(getLimitFromPageIndex(page, perPage)). Find(&ws) if err != nil { return } - total, err := s.Where("project_id = ?", w.ProjectID). + total, err := s.Where(listCond). Count(&Webhook{}) if err != nil { return @@ -234,9 +263,7 @@ func (w *Webhook) ReadAll(s *xorm.Session, a web.Auth, _ string, page int, perPa } for _, webhook := range ws { - webhook.Secret = "" - webhook.BasicAuthUser = "" - webhook.BasicAuthPassword = "" + webhook.maskCredentials() if createdBy, has := users[webhook.CreatedByID]; has { webhook.CreatedBy = createdBy } @@ -268,6 +295,11 @@ func (w *Webhook) Update(s *xorm.Session, _ web.Auth) (err error) { _, err = s.Where("id = ?", w.ID). Cols("events"). Update(w) + if err != nil { + return err + } + + w.maskCredentials() return } diff --git a/pkg/modules/auth/auth.go b/pkg/modules/auth/auth.go index 540e85d04..87c89e5aa 100644 --- a/pkg/modules/auth/auth.go +++ b/pkg/modules/auth/auth.go @@ -17,6 +17,7 @@ package auth import ( + "context" "fmt" "net/http" "net/url" @@ -25,7 +26,10 @@ import ( "code.vikunja.io/api/pkg/config" "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/events" + "code.vikunja.io/api/pkg/log" "code.vikunja.io/api/pkg/models" + "code.vikunja.io/api/pkg/modules/humaecho5" "code.vikunja.io/api/pkg/user" "code.vikunja.io/api/pkg/web" @@ -96,42 +100,77 @@ func ClearRefreshTokenCookie(c *echo.Context) { SetRefreshTokenCookie(c, "", -1) } -// NewUserAuthTokenResponse creates a new user auth token response from a user object. -func NewUserAuthTokenResponse(u *user.User, c *echo.Context, long bool) error { +// IssuedUserToken bundles a freshly minted access token with the matching +// refresh token and the cookie max-age both v1 and v2 use to set the +// HttpOnly refresh cookie. +type IssuedUserToken struct { + AccessToken string + RefreshToken string + CookieMaxAge int +} + +// IssueUserToken creates a session for the user and mints a JWT access token plus +// a refresh token for it. It is the transport-agnostic core both v1 (which writes +// the echo response) and v2 (Huma) call; callers set the refresh cookie and the +// Cache-Control header themselves via WriteUserAuthCookies. Pass oidc for +// OpenID Connect logins to store the logout data; nil otherwise. +func IssueUserToken(ctx context.Context, u *user.User, deviceInfo, ipAddress string, long bool, oidc *models.SessionOIDCData) (*IssuedUserToken, error) { s := db.NewSession() defer s.Close() - deviceInfo := c.Request().UserAgent() - ipAddress := c.RealIP() - - session, err := models.CreateSession(s, u.ID, deviceInfo, ipAddress, long) + session, err := models.CreateSession(s, u.ID, deviceInfo, ipAddress, long, oidc) if err != nil { _ = s.Rollback() - return err + return nil, err } t, err := NewUserJWTAuthtoken(u, session.ID) if err != nil { _ = s.Rollback() - return err + return nil, err } if err := s.Commit(); err != nil { _ = s.Rollback() - return err + return nil, err + } + + if err := events.DispatchWithContext(ctx, &user.LoginSucceededEvent{User: u}); err != nil { + log.Errorf("Could not dispatch login succeeded event: %s", err) } - // Set the refresh token as an HttpOnly cookie. The cookie is path-scoped - // to the refresh endpoint, so the browser only sends it there. JavaScript - // never sees the refresh token — this protects it from XSS. cookieMaxAge := int(config.ServiceJWTTTL.GetInt64()) if long { cookieMaxAge = int(config.ServiceJWTTTLLong.GetInt64()) } - SetRefreshTokenCookie(c, session.RefreshToken, cookieMaxAge) + return &IssuedUserToken{ + AccessToken: t, + RefreshToken: session.RefreshToken, + CookieMaxAge: cookieMaxAge, + }, nil +} + +// WriteUserAuthCookies sets the HttpOnly refresh-token cookie and the +// Cache-Control: no-store header on a response. The cookie is path-scoped to the +// refresh endpoint, so the browser only sends it there; JavaScript never sees the +// refresh token, which protects it from XSS. Shared by the v1 echo handlers and +// the v2 Huma handlers (which reach the echo context via humaecho5.Unwrap). +func WriteUserAuthCookies(c *echo.Context, token *IssuedUserToken) { + SetRefreshTokenCookie(c, token.RefreshToken, token.CookieMaxAge) c.Response().Header().Set("Cache-Control", "no-store") - return c.JSON(http.StatusOK, Token{Token: t}) +} + +// NewUserAuthTokenResponse creates a new user auth token response from a user object. +// Pass oidc for OpenID Connect logins to store the logout data; nil otherwise. +func NewUserAuthTokenResponse(u *user.User, c *echo.Context, long bool, oidc *models.SessionOIDCData) error { + token, err := IssueUserToken(c.Request().Context(), u, c.Request().UserAgent(), c.RealIP(), long, oidc) + if err != nil { + return err + } + + WriteUserAuthCookies(c, token) + return c.JSON(http.StatusOK, Token{Token: token.AccessToken}) } // NewUserJWTAuthtoken generates and signs a new short-lived jwt token for a user. @@ -383,3 +422,34 @@ func RefreshSession(rawRefreshToken string) (*RefreshResult, error) { SessionID: session.ID, }, nil } + +// SessionIDFromContext reads the session id (the `sid` claim) off the user JWT +// in the echo context. It returns "" when there is no user JWT or no sid claim +// (API tokens and link shares carry no session), which callers treat as a no-op. +func SessionIDFromContext(c *echo.Context) string { + raw := c.Get("user") + if raw == nil { + return "" + } + jwtinf, ok := raw.(*jwt.Token) + if !ok { + return "" + } + claims, ok := jwtinf.Claims.(jwt.MapClaims) + if !ok { + return "" + } + sid, _ := claims["sid"].(string) + return sid +} + +// GetAuthFromContext retrieves the authenticated web.Auth from a plain +// context.Context, bridging Huma handlers to Vikunja's echo JWT flow. The +// humaecho5 adapter stashes the *echo.Context under EchoContextKey first. +func GetAuthFromContext(ctx context.Context) (web.Auth, error) { + ec, ok := ctx.Value(humaecho5.EchoContextKey).(*echo.Context) + if !ok { + return nil, fmt.Errorf("no echo.Context on request context; are you calling GetAuthFromContext from a Huma handler dispatched by humaecho5?") + } + return GetAuthFromClaims(ec) +} diff --git a/pkg/modules/auth/auth_test.go b/pkg/modules/auth/auth_test.go new file mode 100644 index 000000000..b51b2da37 --- /dev/null +++ b/pkg/modules/auth/auth_test.go @@ -0,0 +1,29 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package auth + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGetAuthFromContext_NoEchoContext(t *testing.T) { + _, err := GetAuthFromContext(context.Background()) + assert.Error(t, err, "should fail when echo.Context isn't stashed on ctx") +} diff --git a/pkg/modules/auth/oauth2server/authorize.go b/pkg/modules/auth/oauth2server/authorize.go index 873c00900..96afbbad7 100644 --- a/pkg/modules/auth/oauth2server/authorize.go +++ b/pkg/modules/auth/oauth2server/authorize.go @@ -26,8 +26,8 @@ import ( "github.com/labstack/echo/v5" ) -// authorizeRequest represents the JSON body for the authorize endpoint. -type authorizeRequest struct { +// AuthorizeRequest represents the body for the authorize endpoint. +type AuthorizeRequest struct { ResponseType string `json:"response_type"` ClientID string `json:"client_id"` RedirectURI string `json:"redirect_uri"` @@ -47,54 +47,66 @@ type AuthorizeResponse struct { // It validates the OAuth parameters, creates an authorization code, and // returns it as JSON. Authentication is handled by the token middleware. func HandleAuthorize(c *echo.Context) error { - var req authorizeRequest + var req AuthorizeRequest if err := c.Bind(&req); err != nil { return echo.NewHTTPError(http.StatusBadRequest, "Invalid request body") } - // Validate response_type - if req.ResponseType != "code" { - return echo.NewHTTPError(http.StatusBadRequest, "response_type must be 'code'") - } - - // Validate redirect_uri - if !ValidateRedirectURI(req.RedirectURI) { - return &models.ErrOAuthInvalidRedirectURI{} - } - - // Validate PKCE (required) - if req.CodeChallenge == "" || req.CodeChallengeMethod != "S256" { - return &models.ErrOAuthMissingPKCE{} - } - // Get the authenticated user from the middleware u, err := user.GetCurrentUser(c) if err != nil { return err } + resp, err := Authorize(&req, u.ID) + if err != nil { + return err + } + + return c.JSON(http.StatusOK, resp) +} + +// Authorize validates the OAuth authorization parameters for the given +// authenticated user and creates a single-use authorization code, independent +// of the HTTP layer. Callers own request binding and resolving the user. +func Authorize(req *AuthorizeRequest, userID int64) (*AuthorizeResponse, error) { + // Validate response_type + if req.ResponseType != "code" { + return nil, echo.NewHTTPError(http.StatusBadRequest, "response_type must be 'code'") + } + + // Validate redirect_uri + if !ValidateRedirectURI(req.RedirectURI) { + return nil, &models.ErrOAuthInvalidRedirectURI{} + } + + // Validate PKCE (required) + if req.CodeChallenge == "" || req.CodeChallengeMethod != "S256" { + return nil, &models.ErrOAuthMissingPKCE{} + } + s := db.NewSession() defer s.Close() - fullUser, err := user.GetUserByID(s, u.ID) + fullUser, err := user.GetUserByID(s, userID) if err != nil { _ = s.Rollback() - return err + return nil, err } code, err := models.CreateOAuthCode(s, fullUser.ID, req.ClientID, req.RedirectURI, req.CodeChallenge, req.CodeChallengeMethod) if err != nil { _ = s.Rollback() - return err + return nil, err } if err := s.Commit(); err != nil { - return err + return nil, err } - return c.JSON(http.StatusOK, AuthorizeResponse{ + return &AuthorizeResponse{ Code: code, RedirectURI: req.RedirectURI, State: req.State, - }) + }, nil } diff --git a/pkg/modules/auth/oauth2server/token.go b/pkg/modules/auth/oauth2server/token.go index 2725b988d..97978c0bf 100644 --- a/pkg/modules/auth/oauth2server/token.go +++ b/pkg/modules/auth/oauth2server/token.go @@ -17,10 +17,14 @@ package oauth2server import ( + "context" + "net/http" "code.vikunja.io/api/pkg/config" "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/events" + "code.vikunja.io/api/pkg/log" "code.vikunja.io/api/pkg/models" "code.vikunja.io/api/pkg/modules/auth" "code.vikunja.io/api/pkg/user" @@ -36,35 +40,51 @@ type TokenResponse struct { RefreshToken string `json:"refresh_token"` } -// tokenRequest holds the JSON body of a POST /oauth/token request. -type tokenRequest struct { - GrantType string `json:"grant_type"` - Code string `json:"code"` - ClientID string `json:"client_id"` - RedirectURI string `json:"redirect_uri"` - CodeVerifier string `json:"code_verifier"` - RefreshToken string `json:"refresh_token"` +// TokenRequest holds the parameters of a POST /oauth/token request. v1 binds it +// from JSON; v2 accepts spec-compliant application/x-www-form-urlencoded as well +// (form tags mirror the json names). +type TokenRequest struct { + GrantType string `json:"grant_type" form:"grant_type"` + Code string `json:"code" form:"code"` + ClientID string `json:"client_id" form:"client_id"` + RedirectURI string `json:"redirect_uri" form:"redirect_uri"` + CodeVerifier string `json:"code_verifier" form:"code_verifier"` + RefreshToken string `json:"refresh_token" form:"refresh_token"` } // HandleToken handles POST /oauth/token. // Supports grant_type=authorization_code and grant_type=refresh_token. func HandleToken(c *echo.Context) error { - var req tokenRequest + var req TokenRequest if err := c.Bind(&req); err != nil { return echo.NewHTTPError(http.StatusBadRequest, "Invalid request body") } + resp, err := ExchangeToken(c.Request().Context(), &req, c.Request().UserAgent(), c.RealIP()) + if err != nil { + return err + } + + c.Response().Header().Set("Cache-Control", "no-store") + return c.JSON(http.StatusOK, resp) +} + +// ExchangeToken runs the grant-type dispatch and token issuance for the OAuth +// token endpoint, independent of the HTTP layer. Callers own request binding and +// the Cache-Control: no-store response header. deviceInfo/ipAddress are recorded +// on the session created for the authorization_code grant. +func ExchangeToken(ctx context.Context, req *TokenRequest, deviceInfo, ipAddress string) (*TokenResponse, error) { switch req.GrantType { case "authorization_code": - return handleAuthorizationCodeGrant(c, &req) + return exchangeAuthorizationCode(ctx, req, deviceInfo, ipAddress) case "refresh_token": - return handleRefreshTokenGrant(c, &req) + return exchangeRefreshToken(req) default: - return &models.ErrOAuthInvalidGrantType{} + return nil, &models.ErrOAuthInvalidGrantType{} } } -func handleAuthorizationCodeGrant(c *echo.Context, req *tokenRequest) error { +func exchangeAuthorizationCode(ctx context.Context, req *TokenRequest, deviceInfo, ipAddress string) (*TokenResponse, error) { s := db.NewSession() defer s.Close() @@ -72,73 +92,75 @@ func handleAuthorizationCodeGrant(c *echo.Context, req *tokenRequest) error { oauthCode, err := models.GetAndDeleteOAuthCode(s, req.Code) if err != nil { _ = s.Rollback() - return err + return nil, err } // Validate client_id matches if oauthCode.ClientID != req.ClientID { _ = s.Rollback() - return &models.ErrOAuthClientNotFound{} + return nil, &models.ErrOAuthClientNotFound{} } // Validate redirect_uri matches if oauthCode.RedirectURI != req.RedirectURI { _ = s.Rollback() - return &models.ErrOAuthInvalidRedirectURI{} + return nil, &models.ErrOAuthInvalidRedirectURI{} } // Verify PKCE if !VerifyPKCE(req.CodeVerifier, oauthCode.CodeChallenge, oauthCode.CodeChallengeMethod) { _ = s.Rollback() - return &models.ErrOAuthPKCEVerifyFailed{} + return nil, &models.ErrOAuthPKCEVerifyFailed{} } // Create a session (reuses existing session infrastructure) - deviceInfo := c.Request().UserAgent() - ipAddress := c.RealIP() - session, err := models.CreateSession(s, oauthCode.UserID, deviceInfo, ipAddress, false) + session, err := models.CreateSession(s, oauthCode.UserID, deviceInfo, ipAddress, false, nil) if err != nil { _ = s.Rollback() - return err + return nil, err } u, err := user.GetUserByID(s, oauthCode.UserID) if err != nil { _ = s.Rollback() - return err + return nil, err } // Generate JWT accessToken, err := auth.NewUserJWTAuthtoken(u, session.ID) if err != nil { _ = s.Rollback() - return err + return nil, err } if err := s.Commit(); err != nil { - return err + return nil, err } - c.Response().Header().Set("Cache-Control", "no-store") - return c.JSON(http.StatusOK, TokenResponse{ + // The code exchange mints a fresh session, so it is a login for the + // audit trail, same as NewUserAuthTokenResponse. + if err := events.DispatchWithContext(ctx, &user.LoginSucceededEvent{User: u}); err != nil { + log.Errorf("Could not dispatch login succeeded event: %s", err) + } + + return &TokenResponse{ AccessToken: accessToken, TokenType: "bearer", ExpiresIn: config.ServiceJWTTTLShort.GetInt64(), RefreshToken: session.RefreshToken, - }) + }, nil } -func handleRefreshTokenGrant(c *echo.Context, req *tokenRequest) error { +func exchangeRefreshToken(req *TokenRequest) (*TokenResponse, error) { result, err := auth.RefreshSession(req.RefreshToken) if err != nil { - return err + return nil, err } - c.Response().Header().Set("Cache-Control", "no-store") - return c.JSON(http.StatusOK, TokenResponse{ + return &TokenResponse{ AccessToken: result.AccessToken, TokenType: "bearer", ExpiresIn: result.ExpiresIn, RefreshToken: result.NewRefreshToken, - }) + }, nil } diff --git a/pkg/modules/auth/openid/logout.go b/pkg/modules/auth/openid/logout.go new file mode 100644 index 000000000..958ea8765 --- /dev/null +++ b/pkg/modules/auth/openid/logout.go @@ -0,0 +1,110 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package openid + +import ( + "net/url" + + "code.vikunja.io/api/pkg/config" + "code.vikunja.io/api/pkg/log" + "code.vikunja.io/api/pkg/models" +) + +// EndSessionEndpoint returns the provider's RP-Initiated Logout endpoint +// (discovery's end_session_endpoint, cached at init), falling back to the static +// logouturl. Never triggers discovery so logout stays responsive when the OP is +// unreachable. +func (p *Provider) EndSessionEndpoint() string { + if p.EndSessionURL != "" { + return p.EndSessionURL + } + return p.LogoutURL +} + +// discoveredEndSessionEndpoint reads end_session_endpoint from the discovery +// document already cached on the *oidc.Provider, so Claims unmarshals in memory +// without a request. +func (p *Provider) discoveredEndSessionEndpoint() string { + if p.openIDProvider == nil { + return "" + } + + var meta struct { + EndSessionEndpoint string `json:"end_session_endpoint"` + } + if err := p.openIDProvider.Claims(&meta); err != nil { + log.Debugf("Could not read end_session_endpoint for provider %s: %v", p.Key, err) + return "" + } + return meta.EndSessionEndpoint +} + +// BuildEndSessionURL builds an OpenID Connect RP-Initiated Logout 1.0 request URL +// (id_token_hint + post_logout_redirect_uri + client_id; see RP-Initiated Logout +// 1.0 §2). post_logout_redirect_uri defaults to service.publicurl, and the OP +// only honors it when id_token_hint is present. Returns "" when neither an +// end_session_endpoint nor a static logouturl is configured. +func BuildEndSessionURL(providerKey string, oidc *models.SessionOIDCData) (string, error) { + // GetProvider would trigger OIDC discovery (a live HTTP GET that blocks when + // the OP is down); the cached static fields are all logout needs. + provider, err := getCachedProvider(providerKey) + if err != nil { + return "", err + } + if provider == nil { + return "", nil + } + + idToken := "" + if oidc != nil { + idToken = oidc.IDToken + } + + return buildEndSessionURL( + provider.EndSessionEndpoint(), + provider.ClientID, + idToken, + config.ServicePublicURL.GetString(), + ) +} + +// buildEndSessionURL appends the logout query params onto endpoint, omitting +// empty ones, and returns "" for an empty endpoint. +func buildEndSessionURL(endpoint, clientID, idToken, postLogoutRedirectURI string) (string, error) { + if endpoint == "" { + return "", nil + } + + u, err := url.Parse(endpoint) + if err != nil { + return "", err + } + + q := u.Query() + if clientID != "" { + q.Set("client_id", clientID) + } + if idToken != "" { + q.Set("id_token_hint", idToken) + } + if postLogoutRedirectURI != "" { + q.Set("post_logout_redirect_uri", postLogoutRedirectURI) + } + u.RawQuery = q.Encode() + + return u.String(), nil +} diff --git a/pkg/modules/auth/openid/logout_test.go b/pkg/modules/auth/openid/logout_test.go new file mode 100644 index 000000000..57e3ea6a2 --- /dev/null +++ b/pkg/modules/auth/openid/logout_test.go @@ -0,0 +1,234 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package openid + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "net/url" + "testing" + + "code.vikunja.io/api/pkg/config" + "code.vikunja.io/api/pkg/models" + "code.vikunja.io/api/pkg/modules/keyvalue" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// newMockOIDCServerWithEndSession publishes a discovery document with an +// end_session_endpoint. +func newMockOIDCServerWithEndSession() *httptest.Server { + var server *httptest.Server + mux := http.NewServeMux() + mux.HandleFunc("/.well-known/openid-configuration", func(w http.ResponseWriter, _ *http.Request) { + discovery := map[string]interface{}{ + "issuer": server.URL, + "authorization_endpoint": server.URL + "/auth", + "token_endpoint": server.URL + "/token", + "jwks_uri": server.URL + "/jwks", + "end_session_endpoint": server.URL + "/logout", + } + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(discovery) + }) + server = httptest.NewServer(mux) + return server +} + +func TestBuildEndSessionURLAssembly(t *testing.T) { + t.Run("all params", func(t *testing.T) { + got, err := buildEndSessionURL("https://op.example.com/logout", "my-client", "the-id-token", "https://vikunja.example.com/") + require.NoError(t, err) + + u, err := url.Parse(got) + require.NoError(t, err) + q := u.Query() + assert.Equal(t, "https", u.Scheme) + assert.Equal(t, "op.example.com", u.Host) + assert.Equal(t, "/logout", u.Path) + assert.Equal(t, "the-id-token", q.Get("id_token_hint")) + assert.Equal(t, "https://vikunja.example.com/", q.Get("post_logout_redirect_uri")) + assert.Equal(t, "my-client", q.Get("client_id")) + }) + + t.Run("preserves existing endpoint query params", func(t *testing.T) { + got, err := buildEndSessionURL("https://op.example.com/logout?foo=bar", "my-client", "the-id-token", "https://vikunja.example.com/") + require.NoError(t, err) + + u, err := url.Parse(got) + require.NoError(t, err) + q := u.Query() + assert.Equal(t, "bar", q.Get("foo")) + assert.Equal(t, "the-id-token", q.Get("id_token_hint")) + }) + + t.Run("omits id_token_hint when no token", func(t *testing.T) { + got, err := buildEndSessionURL("https://op.example.com/logout", "my-client", "", "https://vikunja.example.com/") + require.NoError(t, err) + + u, err := url.Parse(got) + require.NoError(t, err) + q := u.Query() + assert.False(t, q.Has("id_token_hint")) + assert.Equal(t, "https://vikunja.example.com/", q.Get("post_logout_redirect_uri")) + assert.Equal(t, "my-client", q.Get("client_id")) + }) + + t.Run("empty endpoint returns empty", func(t *testing.T) { + got, err := buildEndSessionURL("", "my-client", "the-id-token", "https://vikunja.example.com/") + require.NoError(t, err) + assert.Empty(t, got) + }) +} + +func TestBuildEndSessionURLFromDiscovery(t *testing.T) { + defer CleanupSavedOpenIDProviders() + + server := newMockOIDCServerWithEndSession() + defer server.Close() + + config.AuthOpenIDEnabled.Set(true) + config.ServicePublicURL.Set("https://vikunja.example.com/") + config.AuthOpenIDProviders.Set(map[string]interface{}{ + "provider1": map[string]interface{}{ + "name": "Provider One", + "authurl": server.URL, + "clientid": "client1", + "clientsecret": "secret1", + }, + }) + _ = keyvalue.Del("openid_providers") + _ = keyvalue.Del("openid_provider_provider1") + + got, err := BuildEndSessionURL("provider1", &models.SessionOIDCData{ + IDToken: "raw-id-token", + ProviderKey: "provider1", + }) + require.NoError(t, err) + + u, err := url.Parse(got) + require.NoError(t, err) + q := u.Query() + assert.Equal(t, server.URL+"/logout", u.Scheme+"://"+u.Host+u.Path) + assert.Equal(t, "raw-id-token", q.Get("id_token_hint")) + assert.Equal(t, "https://vikunja.example.com/", q.Get("post_logout_redirect_uri")) + assert.Equal(t, "client1", q.Get("client_id")) +} + +func TestBuildEndSessionURLFromCachedProviderWithoutLiveObject(t *testing.T) { + defer CleanupSavedOpenIDProviders() + + config.AuthOpenIDEnabled.Set(true) + config.ServicePublicURL.Set("https://vikunja.example.com/") + + // Seed only the cached static fields (no live openIDProvider), mimicking a + // provider restored from keyvalue whose OP is unreachable. + _ = keyvalue.Del("openid_providers") + require.NoError(t, keyvalue.Put("openid_provider_provider1", &Provider{ + Key: "provider1", + ClientID: "client1", + EndSessionURL: "https://op.example.com/end-session", + })) + + got, err := BuildEndSessionURL("provider1", &models.SessionOIDCData{ + IDToken: "raw-id-token", + ProviderKey: "provider1", + }) + require.NoError(t, err) + + u, err := url.Parse(got) + require.NoError(t, err) + q := u.Query() + assert.Equal(t, "https://op.example.com/end-session", u.Scheme+"://"+u.Host+u.Path) + assert.Equal(t, "raw-id-token", q.Get("id_token_hint")) + assert.Equal(t, "https://vikunja.example.com/", q.Get("post_logout_redirect_uri")) + assert.Equal(t, "client1", q.Get("client_id")) +} + +func TestEndSessionEndpointUsesCachedURLWithoutDiscovery(t *testing.T) { + // A nil openIDProvider models a provider restored from cache (or an + // unreachable OP): EndSessionEndpoint must answer from the cached URL. + p := &Provider{ + Key: "provider1", + LogoutURL: "https://op.example.com/static-logout", + EndSessionURL: "https://op.example.com/end-session", + } + assert.Equal(t, "https://op.example.com/end-session", p.EndSessionEndpoint()) +} + +func TestEndSessionEndpointFallsBackToLogoutURLWhenNotCached(t *testing.T) { + p := &Provider{ + Key: "provider1", + LogoutURL: "https://op.example.com/static-logout", + } + assert.Equal(t, "https://op.example.com/static-logout", p.EndSessionEndpoint()) +} + +func TestEndSessionEndpointCachedFromDiscoveryOnInit(t *testing.T) { + defer CleanupSavedOpenIDProviders() + + server := newMockOIDCServerWithEndSession() + defer server.Close() + + config.AuthOpenIDEnabled.Set(true) + config.AuthOpenIDProviders.Set(map[string]interface{}{ + "provider1": map[string]interface{}{ + "name": "Provider One", + "authurl": server.URL, + "clientid": "client1", + "clientsecret": "secret1", + }, + }) + _ = keyvalue.Del("openid_providers") + _ = keyvalue.Del("openid_provider_provider1") + + provider, err := GetProvider("provider1") + require.NoError(t, err) + require.NotNil(t, provider) + + assert.Equal(t, server.URL+"/logout", provider.EndSessionURL) + assert.Equal(t, server.URL+"/logout", provider.EndSessionEndpoint()) +} + +func TestEndSessionEndpointFallsBackToStaticLogoutURL(t *testing.T) { + defer CleanupSavedOpenIDProviders() + + // newMockOIDCServer publishes no end_session_endpoint, forcing the logouturl fallback. + server := newMockOIDCServer() + defer server.Close() + + config.AuthOpenIDEnabled.Set(true) + config.AuthOpenIDProviders.Set(map[string]interface{}{ + "provider1": map[string]interface{}{ + "name": "Provider One", + "authurl": server.URL, + "clientid": "client1", + "clientsecret": "secret1", + "logouturl": "https://op.example.com/static-logout", + }, + }) + _ = keyvalue.Del("openid_providers") + _ = keyvalue.Del("openid_provider_provider1") + + provider, err := GetProvider("provider1") + require.NoError(t, err) + require.NotNil(t, provider) + + assert.Equal(t, "https://op.example.com/static-logout", provider.EndSessionEndpoint()) +} diff --git a/pkg/modules/auth/openid/openid.go b/pkg/modules/auth/openid/openid.go index 381570f42..3fd7a6cfa 100644 --- a/pkg/modules/auth/openid/openid.go +++ b/pkg/modules/auth/openid/openid.go @@ -27,6 +27,7 @@ import ( "strings" "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/events" "code.vikunja.io/api/pkg/log" "code.vikunja.io/api/pkg/models" "code.vikunja.io/api/pkg/modules/auth" @@ -68,8 +69,12 @@ type Provider struct { ForceUserInfo bool `json:"force_user_info"` RequireAvailability bool `json:"-"` ClientSecret string `json:"-"` - openIDProvider *oidc.Provider - Oauth2Config *oauth2.Config `json:"-"` + // RP-Initiated Logout endpoint, cached at init so logout never fetches. + // Exported so it survives the gob keyvalue round-trip (gob skips unexported + // fields like openIDProvider); json:"-" keeps it out of /info. + EndSessionURL string `json:"-"` + openIDProvider *oidc.Provider + Oauth2Config *oauth2.Config `json:"-"` } type claims struct { @@ -167,8 +172,12 @@ func enforceTOTPIfRequired(s *xorm.Session, u *user.User, totpPasscode string) e // @Failure 500 {object} models.Message "Internal error" // @Router /auth/openid/{provider}/callback [post] func HandleCallback(c *echo.Context) error { + cb := &Callback{} + if err := c.Bind(cb); err != nil { + return &models.ErrOpenIDBadRequest{Message: "Bad data"} + } - provider, cb, oauthToken, idToken, err := getProviderAndOidcTokens(c) + u, oidcData, err := AuthenticateCallback(c.Request().Context(), cb, c.Param("provider")) if err != nil { var detailedErr *models.ErrOpenIDBadRequestWithDetails if errors.As(err, &detailedErr) { @@ -180,29 +189,58 @@ func HandleCallback(c *echo.Context) error { return err } - cl, err := getClaims(provider, oauthToken, idToken) + // Create token + return auth.NewUserAuthTokenResponse(u, c, false, oidcData) +} + +// AuthenticateCallback resolves an OpenID Connect callback to an authenticated +// user: it exchanges the auth code, verifies the ID token, creates or updates the +// matching local user, enforces the account-status and TOTP gates, and syncs the +// user's external teams. It is the transport-agnostic core shared by the v1 echo +// handler and the v2 Huma handler; the caller issues the auth token. The +// ErrOpenIDBadRequestWithDetails error keeps its provider detail so v1 can render +// its bespoke body and v2 can map it to RFC 9457. +func AuthenticateCallback(ctx context.Context, cb *Callback, providerKey string) (*user.User, *models.SessionOIDCData, error) { + // ctx is threaded through only to dispatch the login event; the OIDC token + // exchange, claim verification and user/avatar sync run on their own + // background contexts, exactly as the v1 callback always did. + provider, oauthToken, idToken, rawIDToken, err := exchangeOidcTokens(cb, providerKey) //nolint:contextcheck if err != nil { - return err + return nil, nil, err + } + + // Stored so logout can replay it as id_token_hint in an RP-Initiated Logout. + oidcData := &models.SessionOIDCData{ + IDToken: rawIDToken, + ProviderKey: providerKey, + } + + cl, err := getClaims(provider, oauthToken, idToken) //nolint:contextcheck + if err != nil { + return nil, nil, err } s := db.NewSession() defer s.Close() + // Discards events queued during a rolled-back transaction (e.g. user + // creation); a no-op once DispatchPending has run. + defer events.CleanupPending(s) // Check if we have seen this user before - u, err := getOrCreateUser(s, cl, provider, idToken) + u, err := getOrCreateUser(s, cl, provider, idToken) //nolint:contextcheck if err != nil { _ = s.Rollback() log.Errorf("Error creating new user for provider %s: %v", provider.Name, err) - return err + return nil, nil, err } if u.Status == user.StatusDisabled { _ = s.Rollback() - return &user.ErrAccountDisabled{UserID: u.ID} + return nil, nil, &user.ErrAccountDisabled{UserID: u.ID} } if u.Status == user.StatusAccountLocked { _ = s.Rollback() - return &user.ErrAccountLocked{UserID: u.ID} + return nil, nil, &user.ErrAccountLocked{UserID: u.ID} } // Must run before team sync so a failed 2FA attempt cannot mutate team @@ -212,29 +250,33 @@ func HandleCallback(c *echo.Context) error { if err := enforceTOTPIfRequired(s, u, cb.TOTPPasscode); err != nil { if commitErr := s.Commit(); commitErr != nil { log.Errorf("Error committing session after failed OIDC TOTP attempt for user %d: %v", u.ID, commitErr) + } else { + // The user creation above was committed, so its events are real. + events.DispatchPending(ctx, s) } if user.IsErrInvalidTOTPPasscode(err) { user.HandleFailedTOTPAuth(u) } - return err + return nil, nil, err } teamData := getTeamDataFromToken(cl.VikunjaGroups, provider) err = models.SyncExternalTeamsForUser(s, u, teamData, idToken.Issuer, provider.Name) if err != nil { - return err + return nil, nil, err } err = s.Commit() if err != nil { _ = s.Rollback() log.Errorf("Error creating new team for provider %s: %v", provider.Name, err) - return err + return nil, nil, err } - // Create token - return auth.NewUserAuthTokenResponse(u, c, false) + events.DispatchPending(ctx, s) + + return u, oidcData, nil } func getTeamDataFromToken(groups []map[string]interface{}, provider *Provider) (teamData []*models.Team) { @@ -335,6 +377,46 @@ func syncUserAvatarFromOpenID(s *xorm.Session, u *user.User, pictureURL string) return nil } +// fallbackSearchUsers builds the ordered list of local-user lookups used to link an OIDC +// login to an existing account when the provider has email and/or username fallback enabled. +// GetUserWithEmail ANDs all non-zero fields, so the email (when set) is combined with each +// username candidate. +func fallbackSearchUsers(cl *claims, provider *Provider, idToken *oidc.IDToken) []*user.User { + fallbackEmail := "" + if provider.EmailFallback { + // Used alone, allow for someone to connect from various provider to the same account. + // Discouraged for untrusted providers where someone can set email without verification. + // Note: mapping on email prevents auto-updating the user email. + fallbackEmail = cl.Email + } + + // Try the subject first (keeps working for IdPs where sub == username), then the + // preferred_username. The latter lets providers with an opaque sub (e.g. a random + // UUID, like PocketID) still link to an existing local account. + var searches []*user.User + if provider.UsernameFallback { + // Skip empty username candidates: GetUserWithEmail ANDs only non-zero fields, so a + // {Issuer, Username:"", Email:""} would degenerate to an issuer-only lookup and link + // an arbitrary local user. idToken.Subject is non-empty per OIDC, but guard anyway. + if idToken.Subject != "" { + searches = append(searches, &user.User{Issuer: user.IssuerLocal, Username: idToken.Subject, Email: fallbackEmail}) + } + preferred := strings.ReplaceAll(cl.PreferredUsername, " ", "-") + if preferred != "" && preferred != idToken.Subject { + searches = append(searches, &user.User{Issuer: user.IssuerLocal, Username: preferred, Email: fallbackEmail}) + } + } + // EmailFallback without UsernameFallback: a single email-only lookup (the caller only + // runs this when at least one fallback is enabled, so EmailFallback is guaranteed here). + // Only add it when there is a real email — an empty email would degenerate to an + // issuer-only lookup and link an arbitrary local user. + if len(searches) == 0 && cl.Email != "" { + searches = append(searches, &user.User{Issuer: user.IssuerLocal, Email: cl.Email}) + } + + return searches +} + func getOrCreateUser(s *xorm.Session, cl *claims, provider *Provider, idToken *oidc.IDToken) (u *user.User, err error) { // set defaults @@ -360,33 +442,21 @@ func getOrCreateUser(s *xorm.Session, cl *claims, provider *Provider, idToken *o if !alreadyCreatedFromIssuer && (provider.EmailFallback || provider.UsernameFallback) { - // try finding the user on fallback mappingproperties + // try finding the user on fallback mapping properties + for _, searchUser := range fallbackSearchUsers(cl, provider, idToken) { + u, err = user.GetUserWithEmail(s, searchUser) + if err != nil && !user.IsErrUserDoesNotExist(err) && !user.IsErrUserStatusError(err) { + return nil, err + } + fallbackMatchFound = err == nil || user.IsErrUserStatusError(err) - searchUser := &user.User{ - Issuer: user.IssuerLocal, - } - if provider.UsernameFallback { - // Match oidc subject on username as each is unique identifier in its own referential - // Discouraged if multiple account providers are used. - searchUser.Username = idToken.Subject - } - if provider.EmailFallback { - // Used alone, allow for someone to connect from various provider to the same account - // Discouraged for untrusted provider where someone can set email without verification - // Note : mapping on email prevent from auto-updating user email - searchUser.Email = cl.Email - } - - // Check if the user exists for the given fallback matching options - u, err = user.GetUserWithEmail(s, searchUser) - if err != nil && !user.IsErrUserDoesNotExist(err) && !user.IsErrUserStatusError(err) { - return nil, err - } - fallbackMatchFound = err == nil || user.IsErrUserStatusError(err) - - // Same as above: disabled/locked user found via fallback — return early. - if fallbackMatchFound && user.IsErrUserStatusError(err) { - return u, nil + // Same as above: disabled/locked user found via fallback — return early. + if fallbackMatchFound && user.IsErrUserStatusError(err) { + return u, nil + } + if fallbackMatchFound { + break + } } } @@ -507,21 +577,17 @@ func getClaims(provider *Provider, oauth2Token *oauth2.Token, idToken *oidc.IDTo return cl, nil } -func getProviderAndOidcTokens(c *echo.Context) (*Provider, *Callback, *oauth2.Token, *oidc.IDToken, error) { - - cb := &Callback{} - if err := c.Bind(cb); err != nil { - return nil, nil, nil, nil, &models.ErrOpenIDBadRequest{Message: "Bad data"} - } - - // Check if the provider exists - providerKey := c.Param("provider") +// exchangeOidcTokens resolves the provider, exchanges the callback's auth code, +// and verifies the returned ID token. It takes an already-bound Callback so it +// can be shared by the v1 echo handler (which binds from the request) and the v2 +// Huma handler (which binds via its typed body). +func exchangeOidcTokens(cb *Callback, providerKey string) (*Provider, *oauth2.Token, *oidc.IDToken, string, error) { provider, err := GetProvider(providerKey) if err != nil { - return nil, cb, nil, nil, err + return nil, nil, nil, "", err } if provider == nil { - return nil, cb, nil, nil, &models.ErrOpenIDBadRequest{Message: "Provider does not exist"} + return nil, nil, nil, "", &models.ErrOpenIDBadRequest{Message: "Provider does not exist"} } log.Debugf("Trying to authenticate user using provider: %s", provider.Key) @@ -537,25 +603,25 @@ func getProviderAndOidcTokens(c *echo.Context) (*Provider, *Callback, *oauth2.To if err := json.Unmarshal(rerr.Body, &details); err != nil { log.Errorf("Error unmarshalling token for provider %s: %v", provider.Name, err) log.Debugf("Raw token value is %s", rerr.Body) - return nil, cb, nil, nil, err + return nil, nil, nil, "", err } log.Errorf("Error retrieving token: %s", err) log.Debugf("Raw token value is %s", rerr.Body) - return nil, cb, nil, nil, &models.ErrOpenIDBadRequestWithDetails{ + return nil, nil, nil, "", &models.ErrOpenIDBadRequestWithDetails{ Message: "Could not authenticate against third party.", Details: details, } } - return nil, cb, nil, nil, err + return nil, nil, nil, "", err } // Extract the ID Token from OAuth2 token. rawIDToken, ok := oauth2Token.Extra("id_token").(string) if !ok { log.Debugf("Could not get id_token, raw token is %v", oauth2Token) - return nil, cb, nil, nil, &models.ErrOpenIDBadRequest{Message: "Missing token"} + return nil, nil, nil, "", &models.ErrOpenIDBadRequest{Message: "Missing token"} } verifier := provider.openIDProvider.Verifier(&oidc.Config{ClientID: provider.ClientID}) @@ -564,8 +630,8 @@ func getProviderAndOidcTokens(c *echo.Context) (*Provider, *Callback, *oauth2.To idToken, err := verifier.Verify(context.Background(), rawIDToken) if err != nil { log.Errorf("Error verifying token for provider %s: %v", provider.Name, err) - return nil, cb, nil, nil, err + return nil, nil, nil, "", err } - return provider, cb, oauth2Token, idToken, nil + return provider, oauth2Token, idToken, rawIDToken, nil } diff --git a/pkg/modules/auth/openid/openid_test.go b/pkg/modules/auth/openid/openid_test.go index 05ade6745..35bb27547 100644 --- a/pkg/modules/auth/openid/openid_test.go +++ b/pkg/modules/auth/openid/openid_test.go @@ -254,11 +254,61 @@ func TestGetOrCreateUser(t *testing.T) { assert.Equal(t, user.IssuerLocal, u.Issuer, "User should be a local one") assert.Equal(t, 11, int(u.ID), "user id 11 expected") }) + t.Run("ProviderFallback: Match to existing local user on preferred_username when sub differs", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + + cl := &claims{ + PreferredUsername: "user11", + } + provider := &Provider{ + UsernameFallback: true, + } + // PocketID-style: the subject is an opaque UUID that does not match any local username. + idToken := &oidc.IDToken{Issuer: "https://some.issuer", Subject: "c0ffee00-dead-beef-cafe-000000000011"} + + u, err := getOrCreateUser(s, cl, provider, idToken) + require.NoError(t, err) + err = s.Commit() + require.NoError(t, err) + + assert.Equal(t, "user11", u.Username, "should link to the local user matching preferred_username") + assert.Equal(t, user.IssuerLocal, u.Issuer, "User should be a local one") + assert.Equal(t, 11, int(u.ID), "user id 11 expected") + + // No duplicate user must be created for the opaque subject. + db.AssertMissing(t, "users", map[string]interface{}{ + "subject": idToken.Subject, + }) + }) + t.Run("ProviderFallback: Falls back to sub when preferred_username is empty", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + + cl := &claims{ + PreferredUsername: "", + } + provider := &Provider{ + UsernameFallback: true, + } + idToken := &oidc.IDToken{Issuer: "https://some.issuer", Subject: "user11"} + + u, err := getOrCreateUser(s, cl, provider, idToken) + require.NoError(t, err) + assert.Equal(t, idToken.Subject, u.Username, "subject should match username") + assert.Equal(t, user.IssuerLocal, u.Issuer, "User should be a local one") + assert.Equal(t, 11, int(u.ID), "user id 11 expected") + }) t.Run("ProviderFallback: Match to existing local user on email", func(t *testing.T) { db.LoadAndAssertFixtures(t) s := db.NewSession() defer s.Close() + usersBefore, err := s.Count(&user.User{}) + require.NoError(t, err) + cl := &claims{ Email: "user11@example.com", } @@ -272,6 +322,42 @@ func TestGetOrCreateUser(t *testing.T) { assert.Equal(t, cl.Email, u.Email, "email should match") assert.Equal(t, user.IssuerLocal, u.Issuer, "User should be a local one") assert.Equal(t, 11, int(u.ID), "user id 11 expected") + + // The email-only fallback must link the existing user, not create a duplicate. + usersAfter, err := s.Count(&user.User{}) + require.NoError(t, err) + assert.Equal(t, usersBefore, usersAfter, "no new user should have been created") + }) + t.Run("ProviderFallback: empty email claim does not link to an arbitrary local user", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + s := db.NewSession() + defer s.Close() + + usersBefore, err := s.Count(&user.User{}) + require.NoError(t, err) + + // EmailFallback on, no username fallback, and the IdP sent no email claim. The + // email-only search must not degenerate to an issuer-only lookup matching an + // arbitrary local user. With no email there is nothing safe to match on, so the + // flow falls through to user creation (which then errors because an email is + // required) rather than silently linking an existing local account. + cl := &claims{ + Email: "", + PreferredUsername: "brandNewOidcUser", + } + provider := &Provider{ + EmailFallback: true, + } + idToken := &oidc.IDToken{Issuer: "https://some.issuer", Subject: "opaque-subject-no-email"} + + u, err := getOrCreateUser(s, cl, provider, idToken) + // Must not have linked an existing local user. + require.Error(t, err, "an empty email must not silently link an existing local user") + assert.Nil(t, u, "no existing local user should be returned for an empty email claim") + + usersAfter, err := s.Count(&user.User{}) + require.NoError(t, err) + assert.Equal(t, usersBefore, usersAfter, "no user should have been linked or created from an empty email claim") }) t.Run("ProviderFallback: Match to existing local user on username and email", func(t *testing.T) { diff --git a/pkg/modules/auth/openid/providers.go b/pkg/modules/auth/openid/providers.go index 30f534ad9..710870b7d 100644 --- a/pkg/modules/auth/openid/providers.go +++ b/pkg/modules/auth/openid/providers.go @@ -180,6 +180,29 @@ func GetProvider(key string) (provider *Provider, err error) { return } +// getCachedProvider returns the provider from keyvalue without re-establishing +// the live OIDC connection, so the logout path never blocks on an unreachable OP. +func getCachedProvider(key string) (provider *Provider, err error) { + provider = &Provider{} + exists, err := keyvalue.GetWithValue("openid_provider_"+key, provider) + if err != nil { + return nil, err + } + if !exists { + _, err = GetAllProviders() // This will put all providers in cache + if err != nil { + return nil, err + } + + _, err = keyvalue.GetWithValue("openid_provider_"+key, provider) + if err != nil { + return nil, err + } + } + + return provider, nil +} + // parseBoolField reads a boolean-valued config field from a provider map, // tolerating both native bools (from YAML/JSON) and strings (from env vars or // the GetConfigValueFromFile path, which always return strings). Missing or @@ -313,6 +336,8 @@ func getProviderFromMap(pi map[string]interface{}, key string) (provider *Provid provider.AuthURL = provider.Oauth2Config.Endpoint.AuthURL + provider.EndSessionURL = provider.discoveredEndSessionEndpoint() + return } diff --git a/pkg/modules/avatar/avatar.go b/pkg/modules/avatar/avatar.go index 321fb8553..7c453542e 100644 --- a/pkg/modules/avatar/avatar.go +++ b/pkg/modules/avatar/avatar.go @@ -17,6 +17,12 @@ package avatar import ( + "errors" + "image" + "io" + "strings" + + "code.vikunja.io/api/pkg/config" "code.vikunja.io/api/pkg/log" "code.vikunja.io/api/pkg/modules/avatar/botmarble" "code.vikunja.io/api/pkg/modules/avatar/empty" @@ -27,8 +33,14 @@ import ( "code.vikunja.io/api/pkg/modules/avatar/openid" "code.vikunja.io/api/pkg/modules/avatar/upload" "code.vikunja.io/api/pkg/user" + + "github.com/gabriel-vasile/mimetype" + "xorm.io/xorm" ) +// ErrNotAnImage is returned by StoreUploadedAvatar when the uploaded file is not an image. +var ErrNotAnImage = errors.New("uploaded file is no image") + // Provider defines the avatar provider interface type Provider interface { // GetAvatar is the method used to get an actual avatar for a user @@ -58,6 +70,43 @@ func FlushAllCaches(u *user.User) { } } +// GetAvatarForUsername resolves and renders the avatar for a username. It is the +// shared core behind both the v1 and v2 avatar endpoints: it looks up the user, +// tolerates an unknown/disabled user (returning the default placeholder rather +// than an error, since avatars are loaded via tags), picks the right +// provider (empty for unknown users, botmarble for bots, otherwise the user's +// configured provider) and clamps the size to the server's configured maximum. +func GetAvatarForUsername(s *xorm.Session, username string, size int64) (data []byte, mime string, err error) { + u, err := user.GetUserWithEmail(s, &user.User{Username: username}) + if err != nil && !user.IsErrUserDoesNotExist(err) && !user.IsErrUserStatusError(err) { + log.Errorf("Error getting user for avatar: %v", err) + return nil, "", err + } + + found := err == nil || user.IsErrUserStatusError(err) + + provider := GetProvider(u) + if !found { + // Unknown user: serve the default placeholder. + provider = &empty.Provider{} + } + if found && u.IsBot() { + provider = &botmarble.Provider{} + } + + if size > config.ServiceMaxAvatarSize.GetInt64() { + size = config.ServiceMaxAvatarSize.GetInt64() + } + + data, mime, err = provider.GetAvatar(u, size) + if err != nil { + log.Errorf("Error getting avatar for user %d: %v", u.ID, err) + return nil, "", err + } + + return data, mime, nil +} + // GetProvider returns the appropriate avatar provider for a user func GetProvider(u *user.User) Provider { provider := u.AvatarProvider @@ -82,3 +131,41 @@ func GetProvider(u *user.User) Provider { return &empty.Provider{} } } + +// StoreUploadedAvatar validates that src is an image, switches the user's avatar +// provider to "upload", stores the image as the user's avatar and flushes all +// cached avatars for the user. It returns ErrNotAnImage if src is not an image. +func StoreUploadedAvatar(s *xorm.Session, u *user.User, src io.ReadSeeker) error { + mime, err := mimetype.DetectReader(src) + if err != nil { + return err + } + if !strings.HasPrefix(mime.String(), "image") { + return ErrNotAnImage + } + if _, err := src.Seek(0, io.SeekStart); err != nil { + return err + } + + // The mimetype sniff above accepts image types we cannot actually store + // (e.g. SVG, WebP) because upload.StoreAvatarFile decodes via image.Decode, + // which only has the decoders registered process-wide by the imaging package + // (png, jpeg, gif, tiff, bmp). image.DecodeConfig uses those same decoders, so + // validating here rejects undecodable images with a 400 instead of failing + // deeper in storage with a 500. + if _, _, err := image.DecodeConfig(src); err != nil { + return ErrNotAnImage + } + if _, err := src.Seek(0, io.SeekStart); err != nil { + return err + } + + u.AvatarProvider = "upload" + if err := upload.StoreAvatarFile(s, u, src); err != nil { + return err + } + + FlushAllCaches(u) + + return nil +} diff --git a/pkg/modules/background/background.go b/pkg/modules/background/background.go index 161485bfe..7e48d2d16 100644 --- a/pkg/modules/background/background.go +++ b/pkg/modules/background/background.go @@ -24,12 +24,12 @@ import ( // Image represents an image which can be used as a project background type Image struct { - ID string `json:"id"` - URL string `json:"url"` - Thumb string `json:"thumb,omitempty"` - BlurHash string `json:"blur_hash"` + ID string `json:"id" doc:"The provider-specific id of the image; pass this back to set it as a background."` + URL string `json:"url" doc:"The full-size URL of the image."` + Thumb string `json:"thumb,omitempty" doc:"A thumbnail URL of the image, if the provider supplies one."` + BlurHash string `json:"blur_hash" doc:"A BlurHash placeholder for the image."` // This can be used to supply extra information from an image provider to clients - Info interface{} `json:"info,omitempty"` + Info interface{} `json:"info,omitempty" doc:"Provider-specific extra information about the image (e.g. the Unsplash author for attribution)."` } const MaxBackgroundImageHeight = 3840 diff --git a/pkg/modules/background/handler/background.go b/pkg/modules/background/handler/background.go index a89784ee9..da9d0e522 100644 --- a/pkg/modules/background/handler/background.go +++ b/pkg/modules/background/handler/background.go @@ -31,6 +31,7 @@ import ( "image" "io" "net/http" + "os" "strconv" "strings" @@ -43,6 +44,7 @@ import ( "code.vikunja.io/api/pkg/modules/background/unsplash" "code.vikunja.io/api/pkg/modules/background/upload" "code.vikunja.io/api/pkg/web" + webfiles "code.vikunja.io/api/pkg/web/files" "github.com/bbrks/go-blurhash" "github.com/gabriel-vasile/mimetype" @@ -204,44 +206,17 @@ func (bp *BackgroundProvider) UploadBackground(c *echo.Context) error { } defer srcf.Close() - // Validate we're dealing with an image - mime, err := mimetype.DetectReader(srcf) - if err != nil { + if err := ValidateAndSaveBackgroundUpload(s, auth, project, srcf, file.Filename, uint64(file.Size)); err != nil { _ = s.Rollback() - return err - } - if !strings.HasPrefix(mime.String(), "image") { - _ = s.Rollback() - return c.JSON(http.StatusBadRequest, models.Message{Message: "Uploaded file is no image."}) - } - supported := false - for _, m := range allowedImageMimes { - if mime.Is(m) { - supported = true - break + if IsErrFileIsNoImage(err) { + return c.JSON(http.StatusBadRequest, models.Message{Message: "Uploaded file is no image."}) } - } - if !supported { - _ = s.Rollback() - return c.JSON(http.StatusBadRequest, models.Message{Message: "Unsupported image format. Allowed: " + strings.Join(allowedImageMimes, ",")}) - } - - err = SaveBackgroundFile(s, auth, project, srcf, file.Filename, uint64(file.Size)) - if err != nil { - _ = s.Rollback() if files.IsErrFileIsTooLarge(err) { return echo.ErrBadRequest } if IsErrFileUnsupportedImageFormat(err) { return c.JSON(http.StatusBadRequest, models.Message{Message: "Unsupported image format. Allowed: " + strings.Join(allowedImageMimes, ",")}) } - - return err - } - - err = project.ReadOne(s, auth) - if err != nil { - _ = s.Rollback() return err } @@ -253,6 +228,41 @@ func (bp *BackgroundProvider) UploadBackground(c *echo.Context) error { return c.JSON(http.StatusOK, project) } +// ValidateAndSaveBackgroundUpload validates that srcf is a decodable image of an +// allowed type, stores it as the project's background and reloads the project so +// callers get the updated background metadata. It is the shared body of the v1 and +// v2 upload handlers; the multipart parsing and error-to-HTTP mapping stay in each +// handler. project must already be loaded and the caller must have verified write +// permission. On a non-image it returns ErrFileIsNoImage; on a recognized but +// undecodable format ErrFileUnsupportedImageFormat. +func ValidateAndSaveBackgroundUpload(s *xorm.Session, auth web.Auth, project *models.Project, srcf io.ReadSeeker, filename string, filesize uint64) error { + mime, err := mimetype.DetectReader(srcf) + if err != nil { + return err + } + if !strings.HasPrefix(mime.String(), "image") { + return ErrFileIsNoImage{Mime: mime.String()} + } + supported := false + for _, m := range allowedImageMimes { + if mime.Is(m) { + supported = true + break + } + } + if !supported { + return ErrFileUnsupportedImageFormat{Mime: mime.String()} + } + + // DetectReader consumed the head of the reader; SaveBackgroundFile seeks back to + // the start itself, so no rewind is needed here. + if err := SaveBackgroundFile(s, auth, project, srcf, filename, filesize); err != nil { + return err + } + + return project.ReadOne(s, auth) +} + func SaveBackgroundFile(s *xorm.Session, auth web.Auth, project *models.Project, srcf io.ReadSeeker, filename string, filesize uint64) (err error) { mime, _ := mimetype.DetectReader(srcf) _, _ = srcf.Seek(0, io.SeekStart) @@ -377,54 +387,47 @@ func GetProjectBackground(c *echo.Context) error { return err } - if project.BackgroundFileID == 0 { - _ = s.Rollback() - return echo.NewHTTPError(http.StatusNotFound, "Project background not found") - } - - // Get the file - bgFile := &files.File{ - ID: project.BackgroundFileID, - } - if err := bgFile.LoadFileByID(); err != nil { - _ = s.Rollback() - return err - } - stat, err := files.FileStat(bgFile) + bgFile, stat, err := LoadProjectBackgroundForDownload(s, project) if err != nil { _ = s.Rollback() + if models.IsErrProjectHasNoBackground(err) { + return echo.NewHTTPError(http.StatusNotFound, "Project background not found") + } return err } - // Unsplash requires pingbacks as per their api usage guidelines. - // To do this in a privacy-preserving manner, we do the ping from inside of Vikunja to not expose any user details. - // FIXME: This should use an event once we have events - unsplash.Pingback(s, bgFile) - if err := s.Commit(); err != nil { _ = s.Rollback() return err } - // Override the global no-store directive so browsers can cache background images. - // no-cache allows caching but requires revalidation via If-Modified-Since. - c.Response().Header().Set("Cache-Control", "no-cache") + webfiles.WriteProjectBackground(c.Response(), c.Request(), bgFile, stat) + return nil +} - // Set Last-Modified header if we have the file stat, so clients can decide whether to use cached files - if stat != nil { - modTime := stat.ModTime().UTC() - c.Response().Header().Set(echo.HeaderLastModified, modTime.Format(http.TimeFormat)) - - // Check If-Modified-Since and return 304 if the file hasn't changed - if ifModSince := c.Request().Header.Get("If-Modified-Since"); ifModSince != "" { - if t, err := http.ParseTime(ifModSince); err == nil && !modTime.After(t) { - return c.NoContent(http.StatusNotModified) - } - } +// LoadProjectBackgroundForDownload opens the project's background file (bytes ready to +// read) and stats it for the modtime the download uses for caching. It also fires the +// Unsplash pingback side effect, required by Unsplash's API guidelines and done +// server-side so no user details are exposed. Returns ErrProjectHasNoBackground when the +// project has none; the caller owns committing the session and closing bgFile.File. +func LoadProjectBackgroundForDownload(s *xorm.Session, project *models.Project) (bgFile *files.File, stat os.FileInfo, err error) { + if project.BackgroundFileID == 0 { + return nil, nil, &models.ErrProjectHasNoBackground{ProjectID: project.ID} } - // Serve the file - return c.Stream(http.StatusOK, "image/jpg", bgFile.File) + bgFile = &files.File{ID: project.BackgroundFileID} + if err := bgFile.LoadFileByID(); err != nil { + return nil, nil, err + } + stat, err = files.FileStat(bgFile) + if err != nil { + return nil, nil, err + } + + // FIXME: This should use an event once we have events + unsplash.Pingback(s, bgFile) + + return bgFile, stat, nil } // RemoveProjectBackground removes a project background, no matter the background provider diff --git a/pkg/modules/background/handler/errors.go b/pkg/modules/background/handler/errors.go index beaf46657..dcddf1687 100644 --- a/pkg/modules/background/handler/errors.go +++ b/pkg/modules/background/handler/errors.go @@ -40,3 +40,22 @@ func IsErrFileUnsupportedImageFormat(err error) bool { ok := errors.As(err, &errFileUnsupportedImageFormat) return ok } + +// ErrFileIsNoImage is returned when an uploaded background does not sniff as an +// image at all (its detected mime type does not start with "image"). It is +// distinct from ErrFileUnsupportedImageFormat, which is a recognized image type +// the imaging library can't decode. +type ErrFileIsNoImage struct { + Mime string +} + +// Error is the error implementation of ErrFileIsNoImage +func (err ErrFileIsNoImage) Error() string { + return fmt.Sprintf("uploaded file is not an image [Mime: %s]", err.Mime) +} + +// IsErrFileIsNoImage checks if an error is ErrFileIsNoImage +func IsErrFileIsNoImage(err error) bool { + var errFileIsNoImage ErrFileIsNoImage + return errors.As(err, &errFileIsNoImage) +} diff --git a/pkg/modules/background/unsplash/proxy.go b/pkg/modules/background/unsplash/proxy.go index 69fb23716..1d0b11607 100644 --- a/pkg/modules/background/unsplash/proxy.go +++ b/pkg/modules/background/unsplash/proxy.go @@ -18,32 +18,95 @@ package unsplash import ( "context" + "errors" + "io" "net/http" "strings" "code.vikunja.io/api/pkg/utils" + "code.vikunja.io/api/pkg/web" "github.com/labstack/echo/v5" ) -func unsplashImage(url string, c *echo.Context) error { +// ErrUnsplashImageDoesNotExist is returned when Unsplash answers an image proxy fetch +// with a non-success status, mirroring v1's echo.ErrNotFound. It satisfies +// web.HTTPErrorProcessor so the v2 error bridge maps it to a 404. +type ErrUnsplashImageDoesNotExist struct{} + +// IsErrUnsplashImageDoesNotExist checks if an error is ErrUnsplashImageDoesNotExist. +func IsErrUnsplashImageDoesNotExist(err error) bool { + var target *ErrUnsplashImageDoesNotExist + return errors.As(err, &target) +} + +func (err *ErrUnsplashImageDoesNotExist) Error() string { + return "Unsplash image does not exist" +} + +// HTTPError holds the http error description. +func (err *ErrUnsplashImageDoesNotExist) HTTPError() web.HTTPError { + return web.HTTPError{HTTPCode: http.StatusNotFound, Message: "Not Found"} +} + +// fetchUnsplashImage fetches an image from Unsplash through the SSRF-safe client and +// returns its still-open response body for the caller to stream and close. The url is +// rebased onto the hardcoded images.unsplash.com host (stripping any client-supplied +// host) so the proxy can only ever reach Unsplash. It returns +// ErrUnsplashImageDoesNotExist on a non-success upstream status. +func fetchUnsplashImage(url string) (io.ReadCloser, error) { // Replacing and appending the url for security reasons req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "https://images.unsplash.com/"+strings.Replace(url, "https://images.unsplash.com/", "", 1), nil) if err != nil { - return err + return nil, err } resp, err := utils.NewSSRFSafeHTTPClient().Do(req) //nolint:gosec // SSRF protection is handled by the SSRF-safe client if err != nil { - return err + return nil, err } - defer resp.Body.Close() if resp.StatusCode > 399 { - return echo.ErrNotFound + _ = resp.Body.Close() + return nil, &ErrUnsplashImageDoesNotExist{} } - return c.Stream(http.StatusOK, "image/jpg", resp.Body) + return resp.Body, nil } -// ProxyUnsplashImage proxies a thumbnail from unsplash for privacy reasons. +// FetchUnsplashImageByID resolves an Unsplash image by id, fires the required pingback, +// and returns the full-resolution image body for the caller to stream and close. +func FetchUnsplashImageByID(imageID string) (io.ReadCloser, error) { + photo, err := getUnsplashPhotoInfoByID(imageID) + if err != nil { + return nil, err + } + pingbackByPhotoID(photo.ID) + return fetchUnsplashImage(photo.Urls.Raw) +} + +// FetchUnsplashThumbByID resolves an Unsplash image by id, fires the required pingback, +// and returns a thumbnail (max width 200px) body for the caller to stream and close. +func FetchUnsplashThumbByID(imageID string) (io.ReadCloser, error) { + photo, err := getUnsplashPhotoInfoByID(imageID) + if err != nil { + return nil, err + } + pingbackByPhotoID(photo.ID) + return fetchUnsplashImage("https://images.unsplash.com/" + getImageID(photo.Urls.Raw) + "?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=200&fit=max&ixid=eyJhcHBfaWQiOjcyODAwfQ") +} + +// streamUnsplashImage streams a fetched image body to the v1 echo response, mapping the +// not-found sentinel back to echo.ErrNotFound so v1's wire response is unchanged. +func streamUnsplashImage(body io.ReadCloser, err error, c *echo.Context) error { + if err != nil { + if IsErrUnsplashImageDoesNotExist(err) { + return echo.ErrNotFound + } + return err + } + defer body.Close() + return c.Stream(http.StatusOK, "image/jpg", body) +} + +// ProxyUnsplashImage proxies an image from unsplash for privacy reasons. // @Summary Get an unsplash image // @Description Get an unsplash image. **Returns json on error.** // @tags project @@ -55,12 +118,8 @@ func unsplashImage(url string, c *echo.Context) error { // @Failure 500 {object} models.Message "Internal error" // @Router /backgrounds/unsplash/image/{image} [get] func ProxyUnsplashImage(c *echo.Context) error { - photo, err := getUnsplashPhotoInfoByID(c.Param("image")) - if err != nil { - return err - } - pingbackByPhotoID(photo.ID) - return unsplashImage(photo.Urls.Raw, c) + body, err := FetchUnsplashImageByID(c.Param("image")) + return streamUnsplashImage(body, err, c) } // ProxyUnsplashThumb proxies a thumbnail from unsplash for privacy reasons. @@ -75,10 +134,6 @@ func ProxyUnsplashImage(c *echo.Context) error { // @Failure 500 {object} models.Message "Internal error" // @Router /backgrounds/unsplash/image/{image}/thumb [get] func ProxyUnsplashThumb(c *echo.Context) error { - photo, err := getUnsplashPhotoInfoByID(c.Param("image")) - if err != nil { - return err - } - pingbackByPhotoID(photo.ID) - return unsplashImage("https://images.unsplash.com/"+getImageID(photo.Urls.Raw)+"?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=200&fit=max&ixid=eyJhcHBfaWQiOjcyODAwfQ", c) + body, err := FetchUnsplashThumbByID(c.Param("image")) + return streamUnsplashImage(body, err, c) } diff --git a/pkg/modules/humaecho5/humaecho5.go b/pkg/modules/humaecho5/humaecho5.go new file mode 100644 index 000000000..1c1f28b8e --- /dev/null +++ b/pkg/modules/humaecho5/humaecho5.go @@ -0,0 +1,185 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +// Package humaecho5 is a Huma adapter for labstack/echo/v5, vendored from +// the unmerged upstream PR https://github.com/danielgtaylor/huma/pull/959 +// until it lands (Huma's own humaecho is echo/v4 only). Delete and switch +// back to upstream once that PR merges. +// +// Vikunja-specific glue: every request stashes its *echo.Context on +// context.Context under EchoContextKey so handlers can reach the echo +// context via auth.GetAuthFromContext without per-handler wiring. +package humaecho5 + +import ( + "context" + "crypto/tls" + "io" + "mime/multipart" + "net/http" + "net/url" + "strings" + "time" + + "github.com/danielgtaylor/huma/v2" + "github.com/labstack/echo/v5" +) + +// MultipartMaxMemory caps in-memory buffering for multipart form parsing. +var MultipartMaxMemory int64 = 8 * 1024 + +type echoContextKey struct{} + +// EchoContextKey retrieves the underlying *echo.Context from a Huma +// handler's context.Context. +var EchoContextKey = echoContextKey{} + +// Unwrap extracts the underlying Echo context from a Huma context. Panics if +// called on a context from a different adapter. +func Unwrap(ctx huma.Context) *echo.Context { + for { + if c, ok := ctx.(interface{ Unwrap() huma.Context }); ok { + ctx = c.Unwrap() + continue + } + break + } + if c, ok := ctx.(*echoCtx); ok { + return c.Unwrap() + } + panic("not a humaecho5 context") +} + +type echoCtx struct { + op *huma.Operation + orig *echo.Context + status int +} + +var _ huma.Context = &echoCtx{} + +func (c *echoCtx) Unwrap() *echo.Context { return c.orig } +func (c *echoCtx) Operation() *huma.Operation { return c.op } + +func (c *echoCtx) Context() context.Context { + // Stash echo context so auth.GetAuthFromContext can retrieve it. + return context.WithValue((*c.orig).Request().Context(), EchoContextKey, c.orig) +} + +func (c *echoCtx) Method() string { return (*c.orig).Request().Method } +func (c *echoCtx) Host() string { return (*c.orig).Request().Host } +func (c *echoCtx) RemoteAddr() string { return (*c.orig).Request().RemoteAddr } +func (c *echoCtx) URL() url.URL { return *(*c.orig).Request().URL } + +func (c *echoCtx) Param(name string) string { return (*c.orig).Param(name) } +func (c *echoCtx) Query(name string) string { return (*c.orig).QueryParam(name) } +func (c *echoCtx) Header(name string) string { return (*c.orig).Request().Header.Get(name) } + +func (c *echoCtx) EachHeader(cb func(name, value string)) { + for name, values := range (*c.orig).Request().Header { + for _, value := range values { + cb(name, value) + } + } +} + +func (c *echoCtx) BodyReader() io.Reader { return (*c.orig).Request().Body } + +func (c *echoCtx) GetMultipartForm() (*multipart.Form, error) { + err := (*c.orig).Request().ParseMultipartForm(MultipartMaxMemory) + return (*c.orig).Request().MultipartForm, err +} + +func (c *echoCtx) SetReadDeadline(deadline time.Time) error { + return huma.SetReadDeadline((*c.orig).Response(), deadline) +} + +func (c *echoCtx) SetStatus(code int) { + c.status = code + (*c.orig).Response().WriteHeader(code) +} + +func (c *echoCtx) Status() int { return c.status } + +func (c *echoCtx) AppendHeader(name, value string) { + (*c.orig).Response().Header().Add(name, value) +} + +func (c *echoCtx) SetHeader(name, value string) { + (*c.orig).Response().Header().Set(name, value) +} + +func (c *echoCtx) BodyWriter() io.Writer { return (*c.orig).Response() } + +func (c *echoCtx) TLS() *tls.ConnectionState { return (*c.orig).Request().TLS } + +func (c *echoCtx) Version() huma.ProtoVersion { + r := (*c.orig).Request() + return huma.ProtoVersion{ + Proto: r.Proto, + ProtoMajor: r.ProtoMajor, + ProtoMinor: r.ProtoMinor, + } +} + +type router interface { + Add(method, path string, handler echo.HandlerFunc, middlewares ...echo.MiddlewareFunc) echo.RouteInfo +} + +type echoAdapter struct { + http.Handler + router router + // groupPrefix (e.g. "/api/v2") gets prepended to internal Huma + // dispatches whose path doesn't already start with it — required so + // autopatch's relative path resolution works under a sub-group. + groupPrefix string +} + +func (a *echoAdapter) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if a.groupPrefix != "" && !strings.HasPrefix(r.URL.Path, a.groupPrefix) { + r = r.Clone(r.Context()) + r.URL.Path = a.groupPrefix + r.URL.Path + if r.URL.RawPath != "" { + r.URL.RawPath = a.groupPrefix + r.URL.RawPath + } + } + a.Handler.ServeHTTP(w, r) +} + +func (a *echoAdapter) Handle(op *huma.Operation, handler func(huma.Context)) { + // Convert {param} to :param for Echo's router. + path := op.Path + path = strings.ReplaceAll(path, "{", ":") + path = strings.ReplaceAll(path, "}", "") + a.router.Add(op.Method, path, func(c *echo.Context) error { + ctx := &echoCtx{op: op, orig: c} + handler(ctx) + return nil + }) +} + +// New creates a new Huma API using the provided Echo router. +func New(r *echo.Echo, config huma.Config) huma.API { + return huma.NewAPI(config, &echoAdapter{Handler: r, router: r}) +} + +// NewWithGroup mounts a Huma API on a group so handlers inherit its +// middleware. groupPrefix must equal the prefix g was constructed with; +// the adapter uses it to rewrite internal Huma dispatches (notably +// autopatch's GET+PUT round trip) onto absolute URLs. +func NewWithGroup(r *echo.Echo, g *echo.Group, groupPrefix string, config huma.Config) huma.API { + return huma.NewAPI(config, &echoAdapter{Handler: r, router: g, groupPrefix: groupPrefix}) +} diff --git a/pkg/modules/humaecho5/humaecho5_test.go b/pkg/modules/humaecho5/humaecho5_test.go new file mode 100644 index 000000000..9594ea106 --- /dev/null +++ b/pkg/modules/humaecho5/humaecho5_test.go @@ -0,0 +1,86 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package humaecho5_test + +import ( + "context" + "encoding/json" + "net/http/httptest" + "testing" + + "code.vikunja.io/api/pkg/modules/humaecho5" + "github.com/danielgtaylor/huma/v2" + "github.com/labstack/echo/v5" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestAdapterRoundtrip proves that a Huma operation registered against the +// v5 adapter is served by Echo and that the echo.Context is retrievable +// from the handler's context.Context via EchoContextKey. +func TestAdapterRoundtrip(t *testing.T) { + e := echo.New() + api := humaecho5.New(e, huma.DefaultConfig("spike", "0.0.1")) + + type pingInput struct { + Name string `path:"name"` + } + type pingOutput struct { + Body struct { + Echo string `json:"echo"` + HasEchoCtx bool `json:"has_echo_ctx"` + } + } + + huma.Register(api, huma.Operation{ + OperationID: "ping", + Method: "GET", + Path: "/ping/{name}", + }, func(ctx context.Context, in *pingInput) (*pingOutput, error) { + _, ok := ctx.Value(humaecho5.EchoContextKey).(*echo.Context) + out := &pingOutput{} + out.Body.Echo = in.Name + out.Body.HasEchoCtx = ok + return out, nil + }) + + req := httptest.NewRequest("GET", "/ping/world", nil) + rec := httptest.NewRecorder() + e.ServeHTTP(rec, req) + + require.Equal(t, 200, rec.Code, "body: %s", rec.Body.String()) + var got struct { + Echo string `json:"echo"` + HasEchoCtx bool `json:"has_echo_ctx"` + } + require.NoError(t, json.NewDecoder(rec.Body).Decode(&got)) + assert.Equal(t, "world", got.Echo) + assert.True(t, got.HasEchoCtx, "echo.Context not stashed on request ctx") +} + +// TestOpenAPISpecServed proves Huma serves the OAS 3.1 spec document +// on its configured URL. +func TestOpenAPISpecServed(t *testing.T) { + e := echo.New() + _ = humaecho5.New(e, huma.DefaultConfig("spike", "0.0.1")) + req := httptest.NewRequest("GET", "/openapi.json", nil) + rec := httptest.NewRecorder() + e.ServeHTTP(rec, req) + require.Equal(t, 200, rec.Code) + assert.Contains(t, rec.Body.String(), `"openapi":"3.1`, + "expected OAS 3.1 header, got %s", rec.Body.String()) +} diff --git a/pkg/modules/keyvalue/keyvalue.go b/pkg/modules/keyvalue/keyvalue.go index 507f7322b..137ea8f62 100644 --- a/pkg/modules/keyvalue/keyvalue.go +++ b/pkg/modules/keyvalue/keyvalue.go @@ -17,6 +17,8 @@ package keyvalue import ( + "time" + "code.vikunja.io/api/pkg/config" "code.vikunja.io/api/pkg/log" "code.vikunja.io/api/pkg/modules/keyvalue/memory" @@ -142,3 +144,38 @@ func RememberValue[T any](key string, fn func() (T, error)) (T, error) { return val, nil } + +// expiringValue wraps a cached value with the time it expires. +type expiringValue[T any] struct { + Value T + ExpiresAt time.Time +} + +// RememberFor is like RememberValue but treats the cached value as stale once it is +// older than ttl. On a miss or once expired, it executes fn, caches the result for +// ttl and returns it. If fn returns an error, nothing is cached. +// T must be a concrete (non-pointer) type. +// +// A value that cannot be deserialized into the expected type is treated as a cache +// miss and overwritten, so the cache self-heals across upgrades that change what a key +// stores (e.g. a key that previously held a plain int64 in Redis). +func RememberFor[T any](key string, ttl time.Duration, fn func() (T, error)) (T, error) { + var cached expiringValue[T] + exists, err := GetWithValue(key, &cached) + if err == nil && exists && time.Now().Before(cached.ExpiresAt) { + return cached.Value, nil + } + + val, err := fn() + if err != nil { + var zero T + return zero, err + } + + if err := Put(key, expiringValue[T]{Value: val, ExpiresAt: time.Now().Add(ttl)}); err != nil { + var zero T + return zero, err + } + + return val, nil +} diff --git a/pkg/modules/keyvalue/keyvalue_test.go b/pkg/modules/keyvalue/keyvalue_test.go index 9d7c10a8b..ca32d8aec 100644 --- a/pkg/modules/keyvalue/keyvalue_test.go +++ b/pkg/modules/keyvalue/keyvalue_test.go @@ -19,6 +19,7 @@ package keyvalue import ( "errors" "testing" + "time" "code.vikunja.io/api/pkg/modules/keyvalue/memory" "github.com/stretchr/testify/assert" @@ -81,3 +82,78 @@ func TestRememberErrorDoesNotStore(t *testing.T) { require.NoError(t, err2) assert.False(t, exists) } + +func TestRememberForReturnsCachedWithinTTL(t *testing.T) { + store = memory.NewStorage() + + called := 0 + fn := func() (int64, error) { + called++ + return int64(called), nil + } + + val, err := RememberFor("foo", time.Hour, fn) + require.NoError(t, err) + assert.Equal(t, int64(1), val) + + // Still within the TTL, so fn must not be called again. + val, err = RememberFor("foo", time.Hour, fn) + require.NoError(t, err) + assert.Equal(t, int64(1), val) + assert.Equal(t, 1, called) +} + +func TestRememberForRecomputesAfterExpiry(t *testing.T) { + store = memory.NewStorage() + + // Seed an already-expired value. + require.NoError(t, Put("foo", expiringValue[int64]{Value: 1, ExpiresAt: time.Now().Add(-time.Minute)})) + + called := 0 + val, err := RememberFor("foo", time.Hour, func() (int64, error) { + called++ + return 2, nil + }) + + require.NoError(t, err) + assert.Equal(t, int64(2), val) + assert.Equal(t, 1, called) +} + +func TestRememberForErrorDoesNotStore(t *testing.T) { + store = memory.NewStorage() + + _, err := RememberFor("foo", time.Hour, func() (int64, error) { + return 0, errors.New("fail") + }) + + require.Error(t, err) + _, exists, err2 := Get("foo") + require.NoError(t, err2) + assert.False(t, exists) +} + +// getWithValueErrorStore simulates a backend that cannot deserialize an existing value +// into the requested type, e.g. a key that held a plain int64 before the cache started +// storing a struct (the pre-refactor metrics counters in Redis). +type getWithValueErrorStore struct { + *memory.Storage +} + +func (s *getWithValueErrorStore) GetWithValue(string, interface{}) (bool, error) { + return false, errors.New("decode error") +} + +func TestRememberForRecomputesWhenStoredValueCannotBeDeserialized(t *testing.T) { + store = &getWithValueErrorStore{memory.NewStorage()} + + called := 0 + val, err := RememberFor("foo", time.Hour, func() (int64, error) { + called++ + return 42, nil + }) + + require.NoError(t, err) + assert.Equal(t, int64(42), val) + assert.Equal(t, 1, called) +} diff --git a/pkg/modules/migration/create_from_structure.go b/pkg/modules/migration/create_from_structure.go index 7803d0af5..d59dd6946 100644 --- a/pkg/modules/migration/create_from_structure.go +++ b/pkg/modules/migration/create_from_structure.go @@ -18,6 +18,7 @@ package migration import ( "bytes" + "context" "xorm.io/xorm" @@ -27,6 +28,7 @@ import ( "code.vikunja.io/api/pkg/models" "code.vikunja.io/api/pkg/modules/background/handler" "code.vikunja.io/api/pkg/user" + "code.vikunja.io/api/pkg/utils" ) // InsertFromStructure takes a fully nested Vikunja data structure and a user and then creates everything for this user @@ -49,7 +51,7 @@ func InsertFromStructure(str []*models.ProjectWithTasksAndBuckets, user *user.Us return err } - events.DispatchPending(s) + events.DispatchPending(context.Background(), s) return nil } @@ -57,7 +59,17 @@ func insertFromStructure(s *xorm.Session, str []*models.ProjectWithTasksAndBucke log.Debugf("[creating structure] Creating %d projects", len(str)) + // Seed the dedup map with the user's existing labels so re-imports + // reuse them instead of creating duplicates (see issue #2742). labels := make(map[string]*models.Label) + existingLabels := []*models.Label{} + if err = s.Where("created_by_id = ?", user.ID).Find(&existingLabels); err != nil { + return err + } + for _, l := range existingLabels { + labels[l.Title+utils.NormalizeHex(l.HexColor)] = l + } + archivedProjects := []int64{} childRelations := make(map[int64][]int64) // old id is the key, slice of old children ids @@ -436,14 +448,15 @@ func createProjectWithEverything(s *xorm.Session, project *models.ProjectWithTas if label == nil { continue } - lb, exists = labels[label.Title+label.HexColor] + key := label.Title + utils.NormalizeHex(label.HexColor) + lb, exists = labels[key] if !exists { err = label.Create(s, user) if err != nil { return err } log.Debugf("[creating structure] Created new label %d", label.ID) - labels[label.Title+label.HexColor] = label + labels[key] = label lb = label } diff --git a/pkg/modules/migration/create_from_structure_test.go b/pkg/modules/migration/create_from_structure_test.go index a8c7b8a89..c449db145 100644 --- a/pkg/modules/migration/create_from_structure_test.go +++ b/pkg/modules/migration/create_from_structure_test.go @@ -155,4 +155,59 @@ func TestInsertFromStructure(t *testing.T) { assert.NotEqual(t, 0, testStructure[1].Tasks[0].BucketID) // Should get the default bucket assert.NotEqual(t, 0, testStructure[1].Tasks[6].BucketID) // Should get the default bucket }) + t.Run("reuses existing labels across imports", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + + makeStructure := func() []*models.ProjectWithTasksAndBuckets { + return []*models.ProjectWithTasksAndBuckets{ + { + Project: models.Project{Title: "Import project"}, + Tasks: []*models.TaskWithComments{ + { + Task: models.Task{ + Title: "Task with label", + Labels: []*models.Label{ + {Title: "Mealie", HexColor: "abcdef"}, + }, + }, + }, + }, + }, + } + } + + require.NoError(t, InsertFromStructure(makeStructure(), u)) + require.NoError(t, InsertFromStructure(makeStructure(), u)) + + s := db.NewSession() + defer s.Close() + count, err := s.Where("created_by_id = ? AND title = ?", u.ID, "Mealie").Count(&models.Label{}) + require.NoError(t, err) + assert.Equal(t, int64(1), count, "second import must reuse the existing 'Mealie' label") + }) + t.Run("does not merge into another user's label", func(t *testing.T) { + db.LoadAndAssertFixtures(t) + + // Fixture label #3 'Label #3 - other user' is created_by_id: 2. + // Importing the same title for user 1 must create a new, user-owned label. + structure := []*models.ProjectWithTasksAndBuckets{ + { + Project: models.Project{Title: "Import project"}, + Tasks: []*models.TaskWithComments{ + { + Task: models.Task{ + Title: "Task", + Labels: []*models.Label{{Title: "Label #3 - other user"}}, + }, + }, + }, + }, + } + require.NoError(t, InsertFromStructure(structure, u)) + + db.AssertExists(t, "labels", map[string]interface{}{ + "title": "Label #3 - other user", + "created_by_id": u.ID, + }, false) + }) } diff --git a/pkg/modules/migration/csv/csv.go b/pkg/modules/migration/csv/csv.go index a0bee4f08..6d9ded38d 100644 --- a/pkg/modules/migration/csv/csv.go +++ b/pkg/modules/migration/csv/csv.go @@ -107,28 +107,28 @@ var AllTaskAttributes = []TaskAttribute{ // ColumnMapping represents a mapping from a CSV column to a task attribute type ColumnMapping struct { - ColumnIndex int `json:"column_index"` - ColumnName string `json:"column_name"` - Attribute TaskAttribute `json:"attribute"` + ColumnIndex int `json:"column_index" doc:"The zero-based index of the CSV column this mapping applies to."` + ColumnName string `json:"column_name" doc:"The header name of the CSV column, for display."` + Attribute TaskAttribute `json:"attribute" enum:"title,description,due_date,start_date,end_date,done,priority,labels,project,reminder,ignore" doc:"The task attribute the column maps to. Use \"ignore\" to drop the column."` } // DetectionResult contains the auto-detected CSV structure type DetectionResult struct { - Columns []string `json:"columns"` - Delimiter string `json:"delimiter"` - QuoteChar string `json:"quote_char"` - DateFormat string `json:"date_format"` - SuggestedMapping []ColumnMapping `json:"suggested_mapping"` - PreviewRows [][]string `json:"preview_rows"` + Columns []string `json:"columns" doc:"The detected column header names, in order."` + Delimiter string `json:"delimiter" doc:"The detected field delimiter (one of \",\", \";\", tab, \"|\")."` + QuoteChar string `json:"quote_char" doc:"The detected quote character."` + DateFormat string `json:"date_format" doc:"The detected Go reference date layout used to parse date columns."` + SuggestedMapping []ColumnMapping `json:"suggested_mapping" doc:"A best-guess column-to-attribute mapping; the client may edit it before previewing or migrating."` + PreviewRows [][]string `json:"preview_rows" doc:"The first few raw rows of the file, for the client to render a preview."` } // ImportConfig contains the configuration for CSV import type ImportConfig struct { - Delimiter string `json:"delimiter"` - QuoteChar string `json:"quote_char"` - DateFormat string `json:"date_format"` - SkipRows int `json:"skip_rows"` - Mapping []ColumnMapping `json:"mapping"` + Delimiter string `json:"delimiter" doc:"The field delimiter to parse with. Defaults to comma when empty."` + QuoteChar string `json:"quote_char" doc:"The quote character to parse with."` + DateFormat string `json:"date_format" doc:"The Go reference date layout used to parse date columns."` + SkipRows int `json:"skip_rows" doc:"Number of leading rows to skip (e.g. a header row) before importing."` + Mapping []ColumnMapping `json:"mapping" doc:"The column-to-attribute mappings that drive the import."` } // PreviewTask represents a task preview before import @@ -146,8 +146,8 @@ type PreviewTask struct { // PreviewResult contains preview data before import type PreviewResult struct { - Tasks []PreviewTask `json:"tasks"` - TotalRows int `json:"total_rows"` + Tasks []PreviewTask `json:"tasks" doc:"The first few tasks that would be imported with the given config."` + TotalRows int `json:"total_rows" doc:"The total number of data rows in the file."` } // stripBOM removes the UTF-8 BOM from the beginning of a reader @@ -557,6 +557,22 @@ func (m *Migrator) Migrate(_ *user.User, _ io.ReaderAt, _ int64) error { return &migration.ErrCSVConfigRequired{} } +// RunMigration records the migration's start, imports the CSV with the given +// config and records its finish. Shared by the v1 and v2 HTTP layers so the +// status bookkeeping around MigrateWithConfig lives in one place. +func RunMigration(u *user.User, file io.ReaderAt, size int64, config *ImportConfig) error { + status, err := migration.StartMigration(&Migrator{}, u) + if err != nil { + return err + } + + if err := MigrateWithConfig(u, file, size, config); err != nil { + return err + } + + return migration.FinishMigration(status) +} + // MigrateWithConfig imports CSV data into Vikunja with the provided configuration func MigrateWithConfig(u *user.User, file io.ReaderAt, size int64, config *ImportConfig) error { if size == 0 { diff --git a/pkg/modules/migration/csv/handler.go b/pkg/modules/migration/csv/handler.go index 389c13573..1a99c342d 100644 --- a/pkg/modules/migration/csv/handler.go +++ b/pkg/modules/migration/csv/handler.go @@ -186,19 +186,7 @@ func (c *MigratorWeb) Migrate(ctx *echo.Context) error { } defer src.Close() - m := &Migrator{} - status, err := migration.StartMigration(m, u) - if err != nil { - return err - } - - err = MigrateWithConfig(u, src, file.Size, &config) - if err != nil { - return err - } - - err = migration.FinishMigration(status) - if err != nil { + if err := RunMigration(u, src, file.Size, &config); err != nil { return err } diff --git a/pkg/modules/migration/errors.go b/pkg/modules/migration/errors.go index 3129c5da2..eef789c39 100644 --- a/pkg/modules/migration/errors.go +++ b/pkg/modules/migration/errors.go @@ -18,10 +18,33 @@ package migration import ( "net/http" + "time" "code.vikunja.io/api/pkg/web" ) +// ErrMigrationAlreadyRunning is returned when a migration is started for a user +// who already has one in progress (started but not yet finished). +type ErrMigrationAlreadyRunning struct { + StartedAt time.Time +} + +func (err *ErrMigrationAlreadyRunning) Error() string { + return "Migration already running" +} + +// ErrCodeMigrationAlreadyRunning holds the unique world-error code of this error +const ErrCodeMigrationAlreadyRunning = 14005 + +// HTTPError holds the http error description +func (err *ErrMigrationAlreadyRunning) HTTPError() web.HTTPError { + return web.HTTPError{ + HTTPCode: http.StatusPreconditionFailed, + Code: ErrCodeMigrationAlreadyRunning, + Message: "Migration already running", + } +} + // ErrNotAZipFile represents a "ErrNotAZipFile" kind of error. type ErrNotAZipFile struct{} diff --git a/pkg/modules/migration/handler/handler.go b/pkg/modules/migration/handler/handler.go index 840dbacd6..5ef52d747 100644 --- a/pkg/modules/migration/handler/handler.go +++ b/pkg/modules/migration/handler/handler.go @@ -39,7 +39,7 @@ type MigrationWeb struct { // AuthURL is returned to the user when requesting the auth url type AuthURL struct { - URL string `json:"url"` + URL string `json:"url" readOnly:"true" doc:"The OAuth authorization url the client should redirect the user to. After authorizing, the obtained code is passed back to the migrate endpoint."` } // RegisterMigrator registers all routes for migration @@ -57,6 +57,28 @@ func (mw *MigrationWeb) AuthURL(c *echo.Context) error { return c.JSON(http.StatusOK, &AuthURL{URL: ms.AuthURL()}) } +// StartMigration kicks off a migration for the given user: it refuses with +// migration.ErrMigrationAlreadyRunning if one is already in progress, then +// dispatches the MigrationRequestedEvent that runs the migration asynchronously. +// The migrator must already carry its request payload (e.g. the OAuth code). +// Shared by the v1 and v2 HTTP layers so the orchestration lives in one place. +func StartMigration(ms migration.Migrator, u *user2.User) error { + stats, err := migration.GetMigrationStatus(ms, u) + if err != nil { + return err + } + + if !stats.StartedAt.IsZero() && stats.FinishedAt.IsZero() { + return &migration.ErrMigrationAlreadyRunning{StartedAt: stats.StartedAt} + } + + return events.Dispatch(&MigrationRequestedEvent{ + Migrator: ms, + MigratorKind: ms.Name(), + User: u, + }) +} + // Migrate calls the migration method func (mw *MigrationWeb) Migrate(c *echo.Context) error { ms := mw.MigrationStruct() @@ -85,12 +107,7 @@ func (mw *MigrationWeb) Migrate(c *echo.Context) error { return echo.NewHTTPError(http.StatusBadRequest, "No or invalid model provided: "+err.Error()).Wrap(err) } - err = events.Dispatch(&MigrationRequestedEvent{ - Migrator: ms, - MigratorKind: ms.Name(), - User: user, - }) - if err != nil { + if err := StartMigration(ms, user); err != nil { return err } diff --git a/pkg/modules/migration/handler/handler_file.go b/pkg/modules/migration/handler/handler_file.go index 8fae1d775..76b7f4d13 100644 --- a/pkg/modules/migration/handler/handler_file.go +++ b/pkg/modules/migration/handler/handler_file.go @@ -17,6 +17,7 @@ package handler import ( + "io" "net/http" "code.vikunja.io/api/pkg/models" @@ -36,6 +37,22 @@ func (fw *FileMigratorWeb) RegisterRoutes(g *echo.Group) { g.PUT("/"+ms.Name()+"/migrate", fw.Migrate) } +// RunFileMigration records the migration's start, runs the file migrator and +// records its finish. Shared by the v1 and v2 HTTP layers so the orchestration +// lives in one place; the caller supplies the already-opened upload. +func RunFileMigration(ms migration.FileMigrator, u *user2.User, file io.ReaderAt, size int64) error { + m, err := migration.StartMigration(ms, u) + if err != nil { + return err + } + + if err := ms.Migrate(u, file, size); err != nil { + return err + } + + return migration.FinishMigration(m) +} + // Migrate calls the migration method func (fw *FileMigratorWeb) Migrate(c *echo.Context) error { ms := fw.MigrationStruct() @@ -56,19 +73,7 @@ func (fw *FileMigratorWeb) Migrate(c *echo.Context) error { } defer src.Close() - m, err := migration.StartMigration(ms, user) - if err != nil { - return err - } - - // Do the migration - err = ms.Migrate(user, src, file.Size) - if err != nil { - return err - } - - err = migration.FinishMigration(m) - if err != nil { + if err := RunFileMigration(ms, user, src, file.Size); err != nil { return err } diff --git a/pkg/modules/migration/migration_status.go b/pkg/modules/migration/migration_status.go index a967c950c..419ac880c 100644 --- a/pkg/modules/migration/migration_status.go +++ b/pkg/modules/migration/migration_status.go @@ -25,11 +25,11 @@ import ( // Status represents this migration status type Status struct { - ID int64 `xorm:"bigint autoincr not null unique pk" json:"id"` + ID int64 `xorm:"bigint autoincr not null unique pk" json:"id" readOnly:"true" doc:"The unique, numeric id of this migration status."` UserID int64 `xorm:"bigint not null" json:"-"` - MigratorName string `xorm:"varchar(255)" json:"migrator_name"` - StartedAt time.Time `xorm:"not null" json:"started_at"` - FinishedAt time.Time `xorm:"null" json:"finished_at"` + MigratorName string `xorm:"varchar(255)" json:"migrator_name" readOnly:"true" doc:"The name of the migrator this status belongs to, e.g. \"todoist\"."` + StartedAt time.Time `xorm:"not null" json:"started_at" readOnly:"true" doc:"When the last migration started. Zero value if the user never migrated from this service."` + FinishedAt time.Time `xorm:"null" json:"finished_at" readOnly:"true" doc:"When the last migration finished. Zero value while a migration is still running or was never run."` } // TableName holds the table name for the migration status table diff --git a/pkg/modules/migration/ticktick/testdata_ticktick_invalid_numbers.csv b/pkg/modules/migration/ticktick/testdata_ticktick_invalid_numbers.csv new file mode 100644 index 000000000..b38329f61 --- /dev/null +++ b/pkg/modules/migration/ticktick/testdata_ticktick_invalid_numbers.csv @@ -0,0 +1,9 @@ +"Date: 2026-02-27+0000" +"Version: 7.1" +"Status: +0 Normal +1 Completed +2 Archived" +"Folder Name","List Name","Title","Kind","Tags","Content","Is Check list","Start Date","Due Date","Reminder","Repeat","Priority","Status","Created Time","Completed Time","Order","Timezone","Is All Day","Is Floating","Column Name","Column Order","View Mode","taskId","parentId" +"Work","Project Beta","Task with non-numeric priority","TEXT","","A task whose priority column is p1","N","","","","","p1","0","2026-02-15 09:30:00","","-1099511627776","Europe/Berlin",,"false",,,"list","1","" +"Work","Project Beta","Task with non-numeric ids","TEXT","","A task with garbage in the id columns","N","","","","","0","0","2026-02-10 08:00:00","","0","Europe/Berlin","false","false",,,"list","abc","xyz" diff --git a/pkg/modules/migration/ticktick/ticktick.go b/pkg/modules/migration/ticktick/ticktick.go index 48b74938c..a7b3f84bc 100644 --- a/pkg/modules/migration/ticktick/ticktick.go +++ b/pkg/modules/migration/ticktick/ticktick.go @@ -23,6 +23,7 @@ import ( "errors" "io" "sort" + "strconv" "strings" "time" @@ -44,32 +45,84 @@ type Migrator struct { } type tickTickTask struct { - FolderName string `csv:"Folder Name"` - ProjectName string `csv:"List Name"` - Title string `csv:"Title"` - TagsList string `csv:"Tags"` - Tags []string `csv:"-"` - Content string `csv:"Content"` - IsChecklistString string `csv:"Is Check list"` - IsChecklist bool `csv:"-"` - StartDate tickTickTime `csv:"Start Date"` - DueDate tickTickTime `csv:"Due Date"` - ReminderDuration string `csv:"Reminder"` - Reminder time.Duration `csv:"-"` - Repeat string `csv:"Repeat"` - Priority int `csv:"Priority"` - Status string `csv:"Status"` - CreatedTime tickTickTime `csv:"Created Time"` - CompletedTime tickTickTime `csv:"Completed Time"` - Order float64 `csv:"Order"` - TaskID int64 `csv:"taskId"` - ParentID int64 `csv:"parentId"` + FolderName string `csv:"Folder Name"` + ProjectName string `csv:"List Name"` + Title string `csv:"Title"` + TagsList string `csv:"Tags"` + Tags []string `csv:"-"` + Content string `csv:"Content"` + IsChecklistString string `csv:"Is Check list"` + IsChecklist bool `csv:"-"` + StartDate tickTickTime `csv:"Start Date"` + DueDate tickTickTime `csv:"Due Date"` + ReminderDuration string `csv:"Reminder"` + Reminder time.Duration `csv:"-"` + Repeat string `csv:"Repeat"` + Priority tickTickPriority `csv:"Priority"` + Status string `csv:"Status"` + CreatedTime tickTickTime `csv:"Created Time"` + CompletedTime tickTickTime `csv:"Completed Time"` + Order float64 `csv:"Order"` + TaskID tickTickNumber `csv:"taskId"` + ParentID tickTickNumber `csv:"parentId"` } type tickTickTime struct { time.Time } +// tickTickNumber is an int64 that tolerates non-numeric or malformed values in +// the numeric ID columns (taskId, parentId) of TickTick exports. Such values can +// occur through column misalignment caused by unescaped delimiters, which would +// otherwise make gocsv fail the entire import with an internal server error +// (go-vikunja/vikunja#2822). Rather than aborting the import, we fall back to 0 +// for any value we cannot parse. +type tickTickNumber int64 + +func (n *tickTickNumber) UnmarshalCSV(csv string) error { + csv = strings.TrimSpace(csv) + if csv == "" { + *n = 0 + return nil + } + parsed, err := strconv.ParseInt(csv, 10, 64) + if err != nil { + // Deliberately ignore the parse error and fall back to 0 so a single + // malformed value does not abort the whole import. + *n = 0 + return nil //nolint:nilerr + } + *n = tickTickNumber(parsed) + return nil +} + +// tickTickPriority parses the TickTick "Priority" column. TickTick exports the +// priority either as a plain number (0, 1, 3, 5) or, in some exports, prefixed +// with "p" (p1, p2, p3). We accept both forms and fall back to 0 (no priority) +// for anything we cannot parse, so a stray value never fails the whole import +// (go-vikunja/vikunja#2822). Vikunja's task priority is a free-form sortable +// integer, so the parsed value is carried over as-is. +type tickTickPriority int64 + +func (p *tickTickPriority) UnmarshalCSV(csv string) error { + csv = strings.TrimSpace(csv) + csv = strings.TrimPrefix(csv, "p") + csv = strings.TrimPrefix(csv, "P") + if csv == "" { + *p = 0 + return nil + } + parsed, err := strconv.ParseInt(csv, 10, 64) + if err != nil { + // Deliberately ignore the parse error and fall back to 0 so a single + // malformed value does not abort the whole import. + *p = 0 + return nil //nolint:nilerr + } + *p = tickTickPriority(parsed) + return nil +} + func (date *tickTickTime) UnmarshalCSV(csv string) (err error) { date.Time = time.Time{} if csv == "" { @@ -88,17 +141,21 @@ func (date *tickTickTime) UnmarshalCSV(csv string) (err error) { // appears before any of its children. Tasks without a parent come first. // The relative order of siblings / unrelated tasks is preserved. func sortParentsBeforeChildren(tasks []*tickTickTask) []*tickTickTask { - tasksByID := make(map[int64]*tickTickTask, len(tasks)) + tasksByID := make(map[tickTickNumber]*tickTickTask, len(tasks)) for _, t := range tasks { tasksByID[t.TaskID] = t } - placed := make(map[int64]bool, len(tasks)) + // placed is keyed by the task itself rather than by TaskID: malformed + // exports can collapse several taskIds to 0 (see tickTickNumber), and + // keying by ID would treat every zero-ID task after the first as already + // placed and silently drop it. + placed := make(map[*tickTickTask]bool, len(tasks)) result := make([]*tickTickTask, 0, len(tasks)) var place func(t *tickTickTask) place = func(t *tickTickTask) { - if placed[t.TaskID] { + if placed[t] { return } // If this task has a parent that we know about, place the parent first. @@ -107,7 +164,7 @@ func sortParentsBeforeChildren(tasks []*tickTickTask) []*tickTickTask { place(parent) } } - placed[t.TaskID] = true + placed[t] = true result = append(result, t) } @@ -161,7 +218,7 @@ func convertTickTickToVikunja(tasks []*tickTickTask) (result []*models.ProjectWi task := &models.TaskWithComments{ Task: models.Task{ - ID: t.TaskID, + ID: int64(t.TaskID), Title: t.Title, Description: t.Content, StartDate: t.StartDate.Time, @@ -170,6 +227,7 @@ func convertTickTickToVikunja(tasks []*tickTickTask) (result []*models.ProjectWi Done: t.Status == "1" || t.Status == "2", DoneAt: t.CompletedTime.Time, Position: t.Order, + Priority: int64(t.Priority), Labels: labels, }, } @@ -185,7 +243,7 @@ func convertTickTickToVikunja(tasks []*tickTickTask) (result []*models.ProjectWi if t.ParentID != 0 { task.RelatedTasks = map[models.RelationKind][]*models.Task{ - models.RelationKindParenttask: {{ID: t.ParentID}}, + models.RelationKindParenttask: {{ID: int64(t.ParentID)}}, } } diff --git a/pkg/modules/migration/ticktick/ticktick_test.go b/pkg/modules/migration/ticktick/ticktick_test.go index 47ab622b5..64c60689d 100644 --- a/pkg/modules/migration/ticktick/ticktick_test.go +++ b/pkg/modules/migration/ticktick/ticktick_test.go @@ -131,7 +131,7 @@ func TestConvertTicktickTasksToVikunja(t *testing.T) { assert.Equal(t, models.RelatedTaskMap{ models.RelationKindParenttask: []*models.Task{ { - ID: tickTickTasks[1].ParentID, + ID: int64(tickTickTasks[1].ParentID), }, }, }, vikunjaTasks[1].Tasks[1].RelatedTasks) @@ -770,3 +770,102 @@ func TestEmptyLabelHandlingWithRealCSV(t *testing.T) { t.Logf("Successfully processed %d tasks with %d total labels, no empty labels created", len(tasks), totalLabels) }) } + +func TestTickTickPriorityParsing(t *testing.T) { + cases := []struct { + input string + expected int64 + }{ + {"", 0}, + {"0", 0}, + {"1", 1}, + {"3", 3}, + {"5", 5}, + {"p1", 1}, + {"P2", 2}, + {"p3", 3}, + {" p5 ", 5}, + {"garbage", 0}, + } + for _, c := range cases { + t.Run(c.input, func(t *testing.T) { + var p tickTickPriority + require.NoError(t, p.UnmarshalCSV(c.input)) + assert.Equal(t, tickTickPriority(c.expected), p) + }) + } +} + +// TestNonNumericNumberColumns ensures that exports containing non-numeric values +// in columns Vikunja parses as integers (Priority, taskId, parentId) do not fail +// the whole import. See go-vikunja/vikunja#2822. +func TestNonNumericNumberColumns(t *testing.T) { + file, err := os.Open("testdata_ticktick_invalid_numbers.csv") + require.NoError(t, err) + defer file.Close() + + stat, err := file.Stat() + require.NoError(t, err) + + lines, err := linesToSkipBeforeHeader(file, stat.Size()) + require.NoError(t, err) + + _, err = file.Seek(0, io.SeekStart) + require.NoError(t, err) + + dec, err := newLineSkipDecoder(file, lines) + require.NoError(t, err) + tasks := []*tickTickTask{} + err = gocsv.UnmarshalDecoder(dec, &tasks) + require.NoError(t, err, "non-numeric values in numeric columns should not fail the import") + require.Len(t, tasks, 2) + + // "p1" in the Priority column is parsed as priority 1 instead of crashing. + assert.Equal(t, tickTickPriority(1), tasks[0].Priority) + assert.Equal(t, tickTickNumber(1), tasks[0].TaskID) + + // Non-numeric taskId/parentId fall back to 0 as well. + assert.Equal(t, tickTickNumber(0), tasks[1].TaskID) + assert.Equal(t, tickTickNumber(0), tasks[1].ParentID) + + // And the tasks still convert to the Vikunja structure, carrying the parsed + // priority over to the resulting task. + for _, task := range tasks { + task.Tags = strings.Split(task.TagsList, ", ") + } + vikunjaTasks := convertTickTickToVikunja(tasks) + require.Greater(t, len(vikunjaTasks), 0) + + var priority int64 + for _, project := range vikunjaTasks { + for _, task := range project.Tasks { + if task.Title == "Task with non-numeric priority" { + priority = task.Priority + } + } + } + assert.Equal(t, int64(1), priority) +} + +// TestMultipleTasksWithMalformedIDsAreNotDropped guards against a regression +// where collapsing several unparseable taskIds to 0 caused all but the first +// zero-ID task to be silently dropped by sortParentsBeforeChildren. +func TestMultipleTasksWithMalformedIDsAreNotDropped(t *testing.T) { + tasks := []*tickTickTask{ + {TaskID: 0, ProjectName: "Project 1", Title: "First malformed"}, + {TaskID: 0, ProjectName: "Project 1", Title: "Second malformed"}, + {TaskID: 0, ProjectName: "Project 1", Title: "Third malformed"}, + } + + sorted := sortParentsBeforeChildren(tasks) + require.Len(t, sorted, 3, "no task with a zero ID should be dropped") + + vikunjaTasks := convertTickTickToVikunja(tasks) + titles := []string{} + for _, project := range vikunjaTasks { + for _, task := range project.Tasks { + titles = append(titles, task.Title) + } + } + assert.ElementsMatch(t, []string{"First malformed", "Second malformed", "Third malformed"}, titles) +} diff --git a/pkg/modules/migration/trello/trello_test.go b/pkg/modules/migration/trello/trello_test.go index 563b4afc8..7f64724b9 100644 --- a/pkg/modules/migration/trello/trello_test.go +++ b/pkg/modules/migration/trello/trello_test.go @@ -18,10 +18,13 @@ package trello import ( "bytes" + "net/http" + "net/http/httptest" "os" "testing" "time" + "code.vikunja.io/api/pkg/config" "code.vikunja.io/api/pkg/files" "code.vikunja.io/api/pkg/models" @@ -228,7 +231,6 @@ func getTestBoard(t *testing.T) ([]*trello.Board, time.Time) { }, }, } - trelloData[0].Prefs.BackgroundImage = "https://vikunja.io/testimage.jpg" // Using an image which we are hosting, so it'll still be up return trelloData, time1 } @@ -239,6 +241,23 @@ func TestConvertTrelloToVikunja(t *testing.T) { exampleFile, err := os.ReadFile("../testimage.jpg") require.NoError(t, err) + // Serve the attachment from a local test server so the test does not depend + // on an external host being reachable. The SSRF-safe client used by + // migration.DownloadFile rejects non-routable IPs by default, so allow them + // for the duration of this test. + prevAllowNonRoutable := config.OutgoingRequestsAllowNonRoutableIPs.GetBool() + config.OutgoingRequestsAllowNonRoutableIPs.Set("true") + t.Cleanup(func() { + config.OutgoingRequestsAllowNonRoutableIPs.Set(prevAllowNonRoutable) + }) + attachmentServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + _, _ = w.Write(exampleFile) + })) + t.Cleanup(attachmentServer.Close) + + trelloData[0].Prefs.BackgroundImage = attachmentServer.URL + "/testimage.jpg" + trelloData[0].Lists[0].Cards[0].Attachments[0].URL = attachmentServer.URL + "/testimage.jpg" + expectedHierarchyOrg := map[string][]*models.ProjectWithTasksAndBuckets{ "orgid": { { diff --git a/pkg/notifications/database.go b/pkg/notifications/database.go index b007d8a54..c944e2f7b 100644 --- a/pkg/notifications/database.go +++ b/pkg/notifications/database.go @@ -28,22 +28,22 @@ import ( // DatabaseNotification represents a notification that was saved to the database type DatabaseNotification struct { // The unique, numeric id of this notification. - ID int64 `xorm:"bigint autoincr not null unique pk" json:"id" param:"notificationid"` + ID int64 `xorm:"bigint autoincr not null unique pk" json:"id" param:"notificationid" readOnly:"true" doc:"The unique, numeric id of this notification."` // The ID of the notifiable this notification is associated with. NotifiableID int64 `xorm:"bigint not null" json:"-"` // The actual content of the notification. - Notification interface{} `xorm:"json not null" json:"notification"` + Notification interface{} `xorm:"json not null" json:"notification" readOnly:"true" doc:"The notification payload. Shape depends on the notification's name."` // The name of the notification - Name string `xorm:"varchar(250) index not null" json:"name"` + Name string `xorm:"varchar(250) index not null" json:"name" readOnly:"true" doc:"The name identifying the kind of notification."` // The thing the notification is about. Used to check if a notification for this thing already happened or not. SubjectID int64 `xorm:"bigint null" json:"-"` // When this notification is marked as read, this will be updated with the current timestamp. - ReadAt time.Time `xorm:"datetime null" json:"read_at"` + ReadAt time.Time `xorm:"datetime null" json:"read_at" readOnly:"true" doc:"When the notification was marked read; zero value while unread. Set via the read flag, not written directly."` // A timestamp when this notification was created. You cannot change this value. - Created time.Time `xorm:"created not null" json:"created"` + Created time.Time `xorm:"created not null" json:"created" readOnly:"true" doc:"A timestamp when this notification was created. You cannot change this value."` } // AfterInsert is called by XORM after the row is inserted. For transactional diff --git a/pkg/notifications/mail_render.go b/pkg/notifications/mail_render.go index 67b254d35..7749e1d9c 100644 --- a/pkg/notifications/mail_render.go +++ b/pkg/notifications/mail_render.go @@ -20,6 +20,7 @@ import ( "bytes" "embed" templatehtml "html/template" + "net/url" "regexp" "strings" templatetext "text/template" @@ -187,16 +188,26 @@ const mailTemplateConversationalHTML = ` //go:embed logo.png var logo embed.FS -func convertLinesToHTML(lines []*mailLine) (linesHTML []templatehtml.HTML, err error) { +// newNotificationSanitizer builds the bluemonday policy for all HTML in notification +// emails. Only inline data-URI images (avatars) are allowed: RewriteSrc blanks any +// remote image src so a user-controlled task title, comment or description can't +// smuggle a tracking pixel into a recipient's inbox. +func newNotificationSanitizer() *bluemonday.Policy { p := bluemonday.UGCPolicy() - // Allow data URI images for inline avatars in mentions p.AllowDataURIImages() - // Allow style attribute on img and div elements for avatar and layout styling p.AllowAttrs("style").OnElements("img", "div") - // Allow specific CSS properties for avatar styling p.AllowStyles("border-radius", "vertical-align", "margin-right").OnElements("img") - // Allow padding styles on div elements for content spacing p.AllowStyles("padding-top", "margin-bottom").OnElements("div") + p.RewriteSrc(func(u *url.URL) { + if u.Scheme != "data" { + *u = url.URL{} + } + }) + return p +} + +func convertLinesToHTML(lines []*mailLine) (linesHTML []templatehtml.HTML, err error) { + p := newNotificationSanitizer() for _, line := range lines { if line.isHTML { @@ -225,11 +236,7 @@ func convertLinesToHTML(lines []*mailLine) (linesHTML []templatehtml.HTML, err e // sanitizeLinesToHTML sanitizes lines without wrapping in

tags or adding margins. // Used for footer lines and other content that should not have paragraph styling. func sanitizeLinesToHTML(lines []*mailLine) (linesHTML []templatehtml.HTML, err error) { - p := bluemonday.UGCPolicy() - p.AllowDataURIImages() - p.AllowAttrs("style").OnElements("img", "div") - p.AllowStyles("border-radius", "vertical-align", "margin-right").OnElements("img") - p.AllowStyles("padding-top", "margin-bottom").OnElements("div") + p := newNotificationSanitizer() for _, line := range lines { if line.isHTML { @@ -366,12 +373,8 @@ func RenderMail(m *Mail, lang string) (mailOpts *mail.Opts, err error) { data["CopyURLText"] = i18n.T(lang, "notifications.common.copy_url") if m.headerLine != nil { - p := bluemonday.UGCPolicy() - p.AllowDataURIImages() - p.AllowAttrs("style").OnElements("img", "div") - p.AllowStyles("border-radius", "vertical-align", "margin-right").OnElements("img") // #nosec G203 -- the html is sanitized - data["HeaderLineHTML"] = templatehtml.HTML(p.Sanitize(m.headerLine.Text)) + data["HeaderLineHTML"] = templatehtml.HTML(newNotificationSanitizer().Sanitize(m.headerLine.Text)) } data["IntroLinesHTML"], err = convertLinesToHTML(m.introLines) @@ -407,9 +410,12 @@ func RenderMail(m *Mail, lang string) (mailOpts *mail.Opts, err error) { HTMLMessage: htmlContent.String(), Boundary: boundary, ThreadID: m.threadID, - EmbedFS: map[string]*embed.FS{ + } + + if !m.conversational { + mailOpts.EmbedFS = map[string]*embed.FS{ "logo.png": &logo, - }, + } } return mailOpts, nil diff --git a/pkg/notifications/mail_test.go b/pkg/notifications/mail_test.go index 125a13cd0..d8f5db2e4 100644 --- a/pkg/notifications/mail_test.go +++ b/pkg/notifications/mail_test.go @@ -533,6 +533,7 @@ func TestConversationalMail(t *testing.T) { // Should NOT have logo (completely removed) assert.NotContains(t, mailopts.HTMLMessage, "logo.png") assert.NotContains(t, mailopts.HTMLMessage, "Vikunja") + assert.NotContains(t, mailopts.EmbedFS, "logo.png") // Should have inline action link with arrow assert.Contains(t, mailopts.HTMLMessage, "View Task →") @@ -571,6 +572,7 @@ func TestConversationalMail(t *testing.T) { // Should HAVE logo in formal emails assert.Contains(t, mailopts.HTMLMessage, "logo.png") assert.Contains(t, mailopts.HTMLMessage, "Vikunja") + assert.Contains(t, mailopts.EmbedFS, "logo.png") // Should have formal button styling assert.Contains(t, mailopts.HTMLMessage, "background-color: #1973ff") @@ -709,3 +711,55 @@ func TestConversationalMail(t *testing.T) { assert.Contains(t, headerLine1, "(Project > Task) #1") }) } + +// Regression test for GHSA-2vr2-r3qw-rjvq: a user-controlled task title (or comment +// or description) must not be able to smuggle a remote image into a notification +// email, where it would act as a tracking pixel. Inline data-URI avatars and normal +// links must keep working. +func TestNotificationEmailStripsRemoteImages(t *testing.T) { + const remoteSrc = "https://attacker.example/track.png?u=victim" + + t.Run("remote image injected via task title in header is stripped", func(t *testing.T) { + payloadTitle := `normal title` + header := CreateConversationalHeader("", "attacker left a comment", "https://example.com/task/1", "Project", "#1", payloadTitle) + + mailOpts, err := RenderMail(NewMail(). + Conversational(). + Subject("Test"). + HeaderLine(header). + Action("View Task", "https://example.com/task/1"), "en") + require.NoError(t, err) + + assert.NotContains(t, mailOpts.HTMLMessage, remoteSrc) + assert.NotContains(t, mailOpts.HTMLMessage, "attacker.example") + // The benign text is still delivered, and the legitimate task link survives. + assert.Contains(t, mailOpts.HTMLMessage, "normal title") + assert.Contains(t, mailOpts.HTMLMessage, `href="https://example.com/task/1"`) + }) + + t.Run("remote image in body content is stripped", func(t *testing.T) { + mailOpts, err := RenderMail(NewMail(). + Conversational(). + Subject("Test"). + HTML(`

hi

`). + Action("View Task", "https://example.com/task/1"), "en") + require.NoError(t, err) + + assert.NotContains(t, mailOpts.HTMLMessage, remoteSrc) + assert.Contains(t, mailOpts.HTMLMessage, "hi") + }) + + t.Run("inline data-URI avatar is preserved", func(t *testing.T) { + const avatar = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==" + header := CreateConversationalHeader(avatar, "alice left a comment", "https://example.com/task/1", "Project", "#1", "Task") + + mailOpts, err := RenderMail(NewMail(). + Conversational(). + Subject("Test"). + HeaderLine(header). + Action("View Task", "https://example.com/task/1"), "en") + require.NoError(t, err) + + assert.Contains(t, mailOpts.HTMLMessage, "data:image/png;base64,") + }) +} diff --git a/pkg/notifications/notification.go b/pkg/notifications/notification.go index d50735793..cdb795dfb 100644 --- a/pkg/notifications/notification.go +++ b/pkg/notifications/notification.go @@ -45,6 +45,36 @@ type ThreadID interface { ThreadID() string } +// Titler is an optional capability for notifications that can render a +// one-line, translated title. Used as the mail subject when ToMail does not +// set one explicitly, and as the item title in the notifications feed. +type Titler interface { + ToTitle(lang string) string +} + +var registry = map[string]func() Notification{} + +// Register makes a notification type discoverable by name. It should be +// called from init() in the package that defines the type. Only notifications +// that persist to the database need to register, since only persisted +// notifications are re-hydrated from JSON (e.g. by the feed handler). +// The name is derived from the notification's own Name() method, so it stays +// in one place. +func Register(factory func() Notification) { + registry[factory().Name()] = factory +} + +// Lookup returns a fresh, empty instance of the notification type registered +// under the given name. The second return value is false if no type is +// registered with that name. +func Lookup(name string) (Notification, bool) { + f, ok := registry[name] + if !ok { + return nil, false + } + return f(), true +} + // Notifiable is an entity which can be notified. Usually a user. type Notifiable interface { // RouteForMail should return the email address this notifiable has. @@ -91,6 +121,12 @@ func notifyMail(notifiable Notifiable, notification Notification) error { return nil } + if mail.subject == "" { + if t, is := notification.(Titler); is { + mail.subject = t.ToTitle(notifiable.Lang()) + } + } + to, err := notifiable.RouteForMail() if err != nil { return err diff --git a/pkg/notifications/notification_test.go b/pkg/notifications/notification_test.go index f15eb350b..54b0be9fe 100644 --- a/pkg/notifications/notification_test.go +++ b/pkg/notifications/notification_test.go @@ -20,7 +20,9 @@ import ( "testing" "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/mail" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "xorm.io/xorm" "xorm.io/xorm/schemas" @@ -74,6 +76,68 @@ func (t *testNotifiable) Lang() string { return t.Language } +// titlerNoSubjectNotification implements Titler and intentionally omits a +// Subject from ToMail so the fallback path is exercised. +type titlerNoSubjectNotification struct { + title string +} + +func (n *titlerNoSubjectNotification) ToMail(_ string) *Mail { + return NewMail().Line("body") +} +func (n *titlerNoSubjectNotification) ToDB() interface{} { return nil } +func (n *titlerNoSubjectNotification) Name() string { return "test.titler.no.subject" } +func (n *titlerNoSubjectNotification) ToTitle(_ string) string { return n.title } + +// titlerWithExplicitSubjectNotification implements Titler but also sets an +// explicit subject in ToMail; that explicit subject must win. +type titlerWithExplicitSubjectNotification struct { + title string + subject string +} + +func (n *titlerWithExplicitSubjectNotification) ToMail(_ string) *Mail { + return NewMail().Subject(n.subject).Line("body") +} +func (n *titlerWithExplicitSubjectNotification) ToDB() interface{} { return nil } +func (n *titlerWithExplicitSubjectNotification) Name() string { return "test.titler.with.subject" } +func (n *titlerWithExplicitSubjectNotification) ToTitle(_ string) string { return n.title } + +// noTitlerNotification is the control: no Titler, no Subject, fallback must +// leave subject empty without panicking. +type noTitlerNotification struct{} + +func (n *noTitlerNotification) ToMail(_ string) *Mail { return NewMail().Line("body") } +func (n *noTitlerNotification) ToDB() interface{} { return nil } +func (n *noTitlerNotification) Name() string { return "test.no.titler" } + +// titlerRegisteredNotification is used to exercise Register/Lookup. +type titlerRegisteredNotification struct { + Title string `json:"title"` +} + +func (n *titlerRegisteredNotification) ToMail(_ string) *Mail { return NewMail().Line("body") } +func (n *titlerRegisteredNotification) ToDB() interface{} { return n } +func (n *titlerRegisteredNotification) Name() string { return "test.registry.titler" } +func (n *titlerRegisteredNotification) ToTitle(_ string) string { return n.Title } + +func TestRegistry(t *testing.T) { + Register(func() Notification { return &titlerRegisteredNotification{} }) + + t.Run("known name returns fresh instance", func(t *testing.T) { + n, ok := Lookup("test.registry.titler") + require.True(t, ok) + require.NotNil(t, n) + _, ok = n.(*titlerRegisteredNotification) + assert.True(t, ok) + }) + + t.Run("unknown name returns false", func(t *testing.T) { + _, ok := Lookup("does.not.exist") + assert.False(t, ok) + }) +} + func TestNotify(t *testing.T) { t.Run("normal", func(t *testing.T) { @@ -111,6 +175,42 @@ func TestNotify(t *testing.T) { db.AssertExists(t, "notifications", vals, true) }) + t.Run("subject fallback uses ToTitle when ToMail omits Subject", func(t *testing.T) { + mail.ResetSent() + tnf := &testNotifiable{ShouldSendNotification: true, Language: "en"} + + err := notifyMail(tnf, &titlerNoSubjectNotification{title: "From ToTitle"}) + require.NoError(t, err) + + sent := mail.LastSent() + require.NotNil(t, sent) + assert.Equal(t, "From ToTitle", sent.Subject) + }) + + t.Run("explicit Subject in ToMail wins over ToTitle", func(t *testing.T) { + mail.ResetSent() + tnf := &testNotifiable{ShouldSendNotification: true, Language: "en"} + + err := notifyMail(tnf, &titlerWithExplicitSubjectNotification{title: "From ToTitle", subject: "Explicit"}) + require.NoError(t, err) + + sent := mail.LastSent() + require.NotNil(t, sent) + assert.Equal(t, "Explicit", sent.Subject) + }) + + t.Run("no fallback when notification does not implement Titler", func(t *testing.T) { + mail.ResetSent() + tnf := &testNotifiable{ShouldSendNotification: true, Language: "en"} + + err := notifyMail(tnf, &noTitlerNotification{}) + require.NoError(t, err) + + sent := mail.LastSent() + require.NotNil(t, sent) + assert.Empty(t, sent.Subject) + }) + t.Run("disabled notifiable", func(t *testing.T) { s := db.NewSession() diff --git a/pkg/routes/api/shared/admin_user.go b/pkg/routes/api/shared/admin_user.go new file mode 100644 index 000000000..4459f2698 --- /dev/null +++ b/pkg/routes/api/shared/admin_user.go @@ -0,0 +1,64 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package shared + +import ( + "code.vikunja.io/api/pkg/modules/auth/openid" + "code.vikunja.io/api/pkg/user" +) + +// AdminUser re-exposes fields hidden by the default user.User JSON view. +type AdminUser struct { + *user.User + IsAdmin bool `json:"is_admin" readOnly:"true" doc:"Whether the user is an instance admin."` + Status user.Status `json:"status" readOnly:"true" doc:"Account status (0=active, 1=email-confirmation required, 2=disabled, 3=locked)."` + Issuer string `json:"issuer" readOnly:"true" doc:"Authentication issuer; empty or 'local' for local accounts."` + Subject string `json:"subject,omitempty" readOnly:"true" doc:"External subject identifier, for non-local accounts."` + AuthProvider string `json:"auth_provider,omitempty" readOnly:"true" doc:"Resolved auth provider name (e.g. 'LDAP' or an OIDC provider), empty for local accounts."` +} + +// NewAdminUser builds the admin-facing user view, resolving the auth-provider +// display name from the configured OIDC providers. +func NewAdminUser(u *user.User, providers []*openid.Provider) *AdminUser { + return &AdminUser{ + User: u, + IsAdmin: u.IsAdmin, + Status: u.Status, + Issuer: u.Issuer, + Subject: u.Subject, + AuthProvider: resolveAuthProvider(u, providers), + } +} + +func resolveAuthProvider(u *user.User, providers []*openid.Provider) string { + switch u.Issuer { + case "", user.IssuerLocal: + return "" + case user.IssuerLDAP: + return "LDAP" + } + for _, provider := range providers { + issuerURL, err := provider.Issuer() + if err != nil { + continue + } + if issuerURL == u.Issuer { + return provider.Name + } + } + return u.Issuer +} diff --git a/pkg/routes/api/shared/auth.go b/pkg/routes/api/shared/auth.go new file mode 100644 index 000000000..560ae0f47 --- /dev/null +++ b/pkg/routes/api/shared/auth.go @@ -0,0 +1,343 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package shared + +import ( + "context" + + "code.vikunja.io/api/pkg/config" + "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/events" + "code.vikunja.io/api/pkg/log" + "code.vikunja.io/api/pkg/metrics" + "code.vikunja.io/api/pkg/models" + "code.vikunja.io/api/pkg/modules/auth" + "code.vikunja.io/api/pkg/modules/auth/ldap" + "code.vikunja.io/api/pkg/modules/auth/openid" + "code.vikunja.io/api/pkg/modules/keyvalue" + "code.vikunja.io/api/pkg/user" + + "xorm.io/xorm" +) + +// UserRegister carries the fields accepted by the public registration endpoint: +// username, password and email (from APIUserPassword) plus the new user's +// preferred language. +type UserRegister struct { + // The language of the new user. Must be a valid IETF BCP 47 language code and exist in Vikunja. + Language string `json:"language" valid:"language" doc:"The language of the new user as an IETF BCP 47 code (e.g. en, de-DE)."` + user.APIUserPassword +} + +// RegisterUser creates a new local user account from the registration input and +// busts the cached user-count metric so the registration shows up immediately. +// The caller is responsible for the registration-enabled gate and input +// validation; both v1 and v2 share this body. +func RegisterUser(ctx context.Context, in *UserRegister) (*user.User, error) { + s := db.NewSession() + defer s.Close() + // Discards events queued during a rolled-back transaction; a no-op once + // DispatchPending has run. + defer events.CleanupPending(s) + + newUser, err := models.RegisterUser(s, &user.User{ + Username: in.Username, + Password: in.Password, + Email: in.Email, + Language: in.Language, + }) + if err != nil { + _ = s.Rollback() + return nil, err + } + + if err := s.Commit(); err != nil { + _ = s.Rollback() + return nil, err + } + + events.DispatchPending(ctx, s) + + // Bust the cached user count so the new registration shows up in metrics + // immediately instead of after the regular cache expiry. + if config.MetricsEnabled.GetBool() { + if err := metrics.InvalidateCount(metrics.UserCountKey); err != nil { + log.Errorf("Could not invalidate user count metric: %s", err) + } + } + + return newUser, nil +} + +// AuthenticateUserCredentials verifies a login against local (and, if configured, +// LDAP) credentials and enforces the account-status and TOTP gates, returning the +// authenticated user on success. It is the transport-agnostic core of the login +// flow shared by v1 and v2; the caller issues the token and sets the cookie. The +// returned errors carry their own HTTP semantics (wrong credentials, disabled +// account, missing/invalid TOTP) so both APIs surface them identically. +func AuthenticateUserCredentials(ctx context.Context, login *user.Login) (*user.User, error) { + s := db.NewSession() + defer s.Close() + // Discards events queued during a rolled-back transaction (e.g. LDAP user + // creation); a no-op once DispatchPending has run. + defer events.CleanupPending(s) + + u, err := resolveLoginUser(ctx, s, login) + if err != nil { + _ = s.Rollback() + return nil, err + } + + if u.Status == user.StatusDisabled { + _ = s.Rollback() + return nil, &user.ErrAccountDisabled{UserID: u.ID} + } + if u.Status == user.StatusAccountLocked { + _ = s.Rollback() + return nil, &user.ErrAccountLocked{UserID: u.ID} + } + + if err := enforceLoginTOTP(s, u, login.TOTPPasscode); err != nil { + return nil, err + } + + if err := keyvalue.Del(u.GetFailedTOTPAttemptsKey()); err != nil { + return nil, err + } + if err := keyvalue.Del(u.GetFailedPasswordAttemptsKey()); err != nil { + return nil, err + } + + if err := s.Commit(); err != nil { + _ = s.Rollback() + return nil, err + } + + events.DispatchPending(ctx, s) + + return u, nil +} + +// resolveLoginUser authenticates the credentials against LDAP (when enabled) and +// then against local accounts, mirroring v1's order so local users keep working +// alongside LDAP. Bots are rejected before bcrypt runs because they have no +// password hash. +func resolveLoginUser(ctx context.Context, s *xorm.Session, login *user.Login) (*user.User, error) { + if config.AuthLdapEnabled.GetBool() { + u, err := ldap.AuthenticateUserInLDAP(s, login.Username, login.Password, config.AuthLdapGroupSyncEnabled.GetBool(), config.AuthLdapAvatarSyncAttribute.GetString()) + if err != nil && !user.IsErrWrongUsernameOrPassword(err) { + return nil, err + } + if u != nil { + return u, nil + } + } + + existingUser, lookupErr := user.GetUserByUsername(s, login.Username) + if lookupErr == nil && existingUser.IsBot() { + return nil, &user.ErrAccountIsBot{UserID: existingUser.ID} + } + + return user.CheckUserCredentials(ctx, s, login) +} + +// enforceLoginTOTP runs the TOTP gate for users who have it enabled, mirroring +// v1: a missing passcode is rejected, and a wrong one trips the failed-attempt +// lockout via HandleFailedTOTPAuth. The session is rolled back before +// HandleFailedTOTPAuth so its dedicated session can acquire a write lock on +// SQLite shared-cache (the lockout write is decoupled from this transaction — +// see GHSA-fgfv-pv97-6cmj). +func enforceLoginTOTP(s *xorm.Session, u *user.User, passcode string) error { + totpEnabled, err := user.TOTPEnabledForUser(s, u) + if err != nil { + _ = s.Rollback() + return err + } + if !totpEnabled { + return nil + } + + if passcode == "" { + _ = s.Rollback() + return user.ErrInvalidTOTPPasscode{} + } + + _, err = user.ValidateTOTPPasscode(s, &user.TOTPPasscode{User: u, Passcode: passcode}) + if err != nil { + _ = s.Rollback() + if user.IsErrInvalidTOTPPasscode(err) { + user.HandleFailedTOTPAuth(u) + } + return err + } + + return nil +} + +// DeleteSession removes the session with the given id, logging the user out +// server-side. An empty sid is a no-op (the token carried no session, e.g. an +// API token or a link share), matching v1. Shared by v1 and v2; the caller is +// responsible for clearing the refresh cookie. +func DeleteSession(sid string) error { + _, err := LogoutSession(sid) + return err +} + +// LogoutSession deletes the session and returns its OIDC RP-Initiated Logout URL +// for the frontend to redirect to (empty for non-OIDC sessions or when no logout +// endpoint is configured). An empty sid is a no-op. The caller clears the refresh +// cookie. +func LogoutSession(sid string) (endSessionURL string, err error) { + if sid == "" { + return "", nil + } + + s := db.NewSession() + defer s.Close() + + // Read before deleting so the stored id_token survives for the logout URL. + // A missing session just means there is nothing to log out. + session, err := models.GetSessionByID(s, sid) + if err != nil && !models.IsErrSessionNotFound(err) { + _ = s.Rollback() + return "", err + } + if session != nil && session.OIDCProviderKey != "" { + url, buildErr := openid.BuildEndSessionURL(session.OIDCProviderKey, &models.SessionOIDCData{ + IDToken: session.OIDCIDToken, + ProviderKey: session.OIDCProviderKey, + }) + if buildErr != nil { + // A failed URL build must not block logout; the session is still deleted below. + log.Errorf("Could not build OIDC end-session URL for session %s: %v", sid, buildErr) + } else { + endSessionURL = url + } + } + + if _, err := s.Where("id = ?", sid).Delete(&models.Session{}); err != nil { + _ = s.Rollback() + return "", err + } + + if err := s.Commit(); err != nil { + _ = s.Rollback() + return "", err + } + + return endSessionURL, nil +} + +// ResetPassword resets a user's password from a previously issued reset token +// and invalidates all of that user's sessions, so a leaked password cannot be +// used after a reset. Shared by v1 and v2. +func ResetPassword(reset *user.PasswordReset) error { + s := db.NewSession() + defer s.Close() + + userID, err := user.ResetPassword(s, reset) + if err != nil { + _ = s.Rollback() + return err + } + + if err := models.DeleteAllUserSessions(s, userID); err != nil { + _ = s.Rollback() + return err + } + + return s.Commit() +} + +// RequestPasswordResetToken issues a password-reset token for the account with +// the given email and sends it via email. Shared by v1 and v2. +func RequestPasswordResetToken(req *user.PasswordTokenRequest) error { + s := db.NewSession() + defer s.Close() + + if err := user.RequestUserPasswordResetTokenByEmail(s, req); err != nil { + _ = s.Rollback() + return err + } + + return s.Commit() +} + +// ConfirmEmail confirms a newly registered user's email from the token sent to +// them. Shared by v1 and v2. +func ConfirmEmail(confirm *user.EmailConfirm) error { + s := db.NewSession() + defer s.Close() + + if err := user.ConfirmEmail(s, confirm); err != nil { + _ = s.Rollback() + return err + } + + return s.Commit() +} + +// LinkShareToken is the response for the link-share auth endpoint. It embeds the +// authenticated share alongside the issued JWT and re-exposes the project id +// (which LinkSharing hides with json:"-"). The embedded share's write-only +// Password is blanked by AuthenticateLinkShare before this is returned. +type LinkShareToken struct { + auth.Token + *models.LinkSharing + ProjectID int64 `json:"project_id" readOnly:"true" doc:"The id of the project this share grants access to."` +} + +// AuthenticateLinkShare resolves a link share by its public hash, verifies the +// password for password-protected shares, and issues a JWT auth token for it. +// The returned token's embedded share has its password blanked. Shared by v1 +// and v2. +func AuthenticateLinkShare(hash, password string) (*LinkShareToken, error) { + s := db.NewSession() + defer s.Close() + + share, err := models.GetLinkShareByHash(s, hash) + if err != nil { + _ = s.Rollback() + return nil, err + } + + if share.SharingType == models.SharingTypeWithPassword { + if err := models.VerifyLinkSharePassword(share, password); err != nil { + _ = s.Rollback() + return nil, err + } + } + + t, err := auth.NewLinkShareJWTAuthtoken(share) + if err != nil { + _ = s.Rollback() + return nil, err + } + + if err := s.Commit(); err != nil { + _ = s.Rollback() + return nil, err + } + + share.Password = "" + + return &LinkShareToken{ + Token: auth.Token{Token: t}, + LinkSharing: share, + ProjectID: share.ProjectID, + }, nil +} diff --git a/pkg/routes/api/shared/auth_provider.go b/pkg/routes/api/shared/auth_provider.go new file mode 100644 index 000000000..042a5567d --- /dev/null +++ b/pkg/routes/api/shared/auth_provider.go @@ -0,0 +1,54 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +// Package shared holds helpers used by both the v1 and v2 route packages. It +// sits above the auth/user modules in the import graph, so it can combine them +// without creating a cycle. +package shared + +import ( + "code.vikunja.io/api/pkg/modules/auth/openid" + "code.vikunja.io/api/pkg/user" +) + +// GetAuthProviderName resolves the human-readable name of the source a user +// authenticated with: "local"/"ldap" for those issuers, otherwise the +// configured OpenID provider whose issuer URL matches the user's. Returns "" +// when no provider matches. +func GetAuthProviderName(u *user.User) (string, error) { + switch u.Issuer { + case user.IssuerLocal: + return "local", nil + case user.IssuerLDAP: + return "ldap", nil + } + + providers, err := openid.GetAllProviders() + if err != nil { + return "", err + } + for _, provider := range providers { + issuerURL, err := provider.Issuer() + if err != nil { + return "", err + } + if issuerURL == u.Issuer { + return provider.Name, nil + } + } + + return "", nil +} diff --git a/pkg/routes/api/shared/info.go b/pkg/routes/api/shared/info.go new file mode 100644 index 000000000..48cbb00a5 --- /dev/null +++ b/pkg/routes/api/shared/info.go @@ -0,0 +1,167 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package shared + +import ( + "code.vikunja.io/api/pkg/config" + "code.vikunja.io/api/pkg/license" + "code.vikunja.io/api/pkg/log" + "code.vikunja.io/api/pkg/modules/auth/openid" + csvmigrator "code.vikunja.io/api/pkg/modules/migration/csv" + microsofttodo "code.vikunja.io/api/pkg/modules/migration/microsoft-todo" + "code.vikunja.io/api/pkg/modules/migration/ticktick" + "code.vikunja.io/api/pkg/modules/migration/todoist" + "code.vikunja.io/api/pkg/modules/migration/trello" + vikunja_file "code.vikunja.io/api/pkg/modules/migration/vikunja-file" + "code.vikunja.io/api/pkg/modules/migration/wekan" + "code.vikunja.io/api/pkg/version" +) + +// VikunjaInfos holds public information about this Vikunja instance. +type VikunjaInfos struct { + Version string `json:"version" doc:"The Vikunja version this instance runs."` + FrontendURL string `json:"frontend_url" doc:"The publicly configured frontend URL of this instance."` + Motd string `json:"motd" doc:"The message of the day, shown to all users."` + LinkSharingEnabled bool `json:"link_sharing_enabled" doc:"Whether sharing projects via public links is enabled."` + MaxFileSize string `json:"max_file_size" doc:"The maximum allowed upload size, as a human-readable string (e.g. 20MB)."` + MaxItemsPerPage int `json:"max_items_per_page" doc:"The maximum number of items a paginated endpoint returns per page."` + AvailableMigrators []string `json:"available_migrators" doc:"The migrators enabled on this instance."` + TaskAttachmentsEnabled bool `json:"task_attachments_enabled" doc:"Whether task attachments are enabled."` + EnabledBackgroundProviders []string `json:"enabled_background_providers" doc:"The project-background providers enabled on this instance (e.g. upload, unsplash)."` + TotpEnabled bool `json:"totp_enabled" doc:"Whether TOTP two-factor authentication is enabled."` + Legal LegalInfo `json:"legal" doc:"Links to the instance's legal documents."` + CaldavEnabled bool `json:"caldav_enabled" doc:"Whether the CalDAV interface is enabled."` + AuthInfo AuthInfo `json:"auth" doc:"The authentication methods enabled on this instance."` + EmailRemindersEnabled bool `json:"email_reminders_enabled" doc:"Whether email reminders are enabled."` + UserDeletionEnabled bool `json:"user_deletion_enabled" doc:"Whether users may delete their own account."` + TaskCommentsEnabled bool `json:"task_comments_enabled" doc:"Whether task comments are enabled."` + DemoModeEnabled bool `json:"demo_mode_enabled" doc:"Whether this instance runs in demo mode (data is periodically reset)."` + WebhooksEnabled bool `json:"webhooks_enabled" doc:"Whether webhooks are enabled."` + PublicTeamsEnabled bool `json:"public_teams_enabled" doc:"Whether public teams are enabled."` + AllowIconChanges bool `json:"allow_icon_changes" doc:"Whether users may change project icons."` + EnabledProFeatures []license.Feature `json:"enabled_pro_features" doc:"The licensed pro features enabled on this instance."` + // ConcurrentWrites reports whether the configured database can handle concurrent writes. It is false on SQLite, where overlapping write transactions deadlock, so clients should serialize batched writes instead of firing them in parallel. + ConcurrentWrites bool `json:"concurrent_writes" doc:"Whether the configured database supports concurrent writes. False on SQLite; clients should serialize batched writes when this is false."` +} + +// AuthInfo describes the authentication methods enabled on this instance. +type AuthInfo struct { + Local LocalAuthInfo `json:"local"` + Ldap LdapAuthInfo `json:"ldap"` + OpenIDConnect OpenIDAuthInfo `json:"openid_connect"` +} + +// LocalAuthInfo describes the local (username/password) authentication method. +type LocalAuthInfo struct { + Enabled bool `json:"enabled"` + RegistrationEnabled bool `json:"registration_enabled"` +} + +// LdapAuthInfo describes the LDAP authentication method. +type LdapAuthInfo struct { + Enabled bool `json:"enabled"` +} + +// OpenIDAuthInfo describes the OpenID Connect authentication method. +type OpenIDAuthInfo struct { + Enabled bool `json:"enabled"` + Providers []*openid.Provider `json:"providers"` +} + +// LegalInfo holds links to the instance's legal documents. +type LegalInfo struct { + ImprintURL string `json:"imprint_url"` + PrivacyPolicyURL string `json:"privacy_policy_url"` +} + +// BuildInfo assembles the public instance information returned by GET /info on +// both API versions. +func BuildInfo() VikunjaInfos { + info := VikunjaInfos{ + Version: version.Version, + FrontendURL: config.ServicePublicURL.GetString(), + Motd: config.ServiceMotd.GetString(), + LinkSharingEnabled: config.ServiceEnableLinkSharing.GetBool(), + MaxFileSize: config.FilesMaxSize.GetString(), + MaxItemsPerPage: config.ServiceMaxItemsPerPage.GetInt(), + TaskAttachmentsEnabled: config.ServiceEnableTaskAttachments.GetBool(), + TotpEnabled: config.ServiceEnableTotp.GetBool(), + CaldavEnabled: config.ServiceEnableCaldav.GetBool(), + EmailRemindersEnabled: config.ServiceEnableEmailReminders.GetBool(), + UserDeletionEnabled: config.ServiceEnableUserDeletion.GetBool(), + TaskCommentsEnabled: config.ServiceEnableTaskComments.GetBool(), + DemoModeEnabled: config.ServiceDemoMode.GetBool(), + WebhooksEnabled: config.WebhooksEnabled.GetBool(), + PublicTeamsEnabled: config.ServiceEnablePublicTeams.GetBool(), + AllowIconChanges: config.ServiceAllowIconChanges.GetBool(), + ConcurrentWrites: config.DatabaseType.GetString() != "sqlite", + EnabledProFeatures: license.EnabledProFeatures(), + AvailableMigrators: []string{ + (&vikunja_file.FileMigrator{}).Name(), + (&ticktick.Migrator{}).Name(), + (&wekan.Migrator{}).Name(), + (&csvmigrator.Migrator{}).Name(), + }, + Legal: LegalInfo{ + ImprintURL: config.LegalImprintURL.GetString(), + PrivacyPolicyURL: config.LegalPrivacyURL.GetString(), + }, + AuthInfo: AuthInfo{ + Local: LocalAuthInfo{ + Enabled: config.AuthLocalEnabled.GetBool(), + RegistrationEnabled: config.AuthLocalEnabled.GetBool() && config.ServiceEnableRegistration.GetBool(), + }, + Ldap: LdapAuthInfo{ + Enabled: config.AuthLdapEnabled.GetBool(), + }, + OpenIDConnect: OpenIDAuthInfo{ + Enabled: config.AuthOpenIDEnabled.GetBool(), + }, + }, + } + + providers, err := openid.GetAllProviders() + if err != nil { + log.Errorf("Error while getting openid providers for /info: %s", err) + // No return here to not break /info + } + info.AuthInfo.OpenIDConnect.Providers = providers + + if config.MigrationTodoistEnable.GetBool() { + m := &todoist.Migration{} + info.AvailableMigrators = append(info.AvailableMigrators, m.Name()) + } + if config.MigrationTrelloEnable.GetBool() { + m := &trello.Migration{} + info.AvailableMigrators = append(info.AvailableMigrators, m.Name()) + } + if config.MigrationMicrosoftTodoEnable.GetBool() { + m := µsofttodo.Migration{} + info.AvailableMigrators = append(info.AvailableMigrators, m.Name()) + } + + if config.BackgroundsEnabled.GetBool() { + if config.BackgroundsUploadEnabled.GetBool() { + info.EnabledBackgroundProviders = append(info.EnabledBackgroundProviders, "upload") + } + if config.BackgroundsUnsplashEnabled.GetBool() { + info.EnabledBackgroundProviders = append(info.EnabledBackgroundProviders, "unsplash") + } + } + + return info +} diff --git a/pkg/routes/api/shared/testing.go b/pkg/routes/api/shared/testing.go new file mode 100644 index 000000000..ba9118e5a --- /dev/null +++ b/pkg/routes/api/shared/testing.go @@ -0,0 +1,92 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package shared + +import ( + "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/events" + "code.vikunja.io/api/pkg/license" + "code.vikunja.io/api/pkg/log" +) + +// dependentTestingTables lists tables that reference a reset table by ID and +// must be truncated alongside it. Without foreign key cascades, stale rows +// would persist and pollute subsequent tests that reuse the same +// auto-increment IDs. +var dependentTestingTables = map[string][]string{ + "users": {"notifications"}, +} + +// ReplaceTableContents resets a single table to the provided rows for the e2e +// testing endpoint and returns the table's resulting contents. When truncate is +// true the table (and any dependent tables) is emptied first; otherwise the rows +// are restored on top of existing data. Callers must already have verified the +// testing token. +func ReplaceTableContents(table string, content []map[string]interface{}, truncate bool) ([]map[string]interface{}, error) { + // Wait for all async event handlers from the previous test to complete + // before modifying the database. Without this, handlers hold SQLite + // connections and starve this request's truncate/insert operations. + events.WaitForPendingHandlers() + + var err error + if truncate { + for _, dep := range dependentTestingTables[table] { + if err = db.RestoreAndTruncate(dep, nil); err != nil { + return nil, err + } + } + err = db.RestoreAndTruncate(table, content) + } else { + err = db.Restore(table, content) + } + if err != nil { + return nil, err + } + + // License state is cached at startup; re-apply so tests take effect without a restart. + if table == "license_status" { + if err := license.ReloadFromCache(); err != nil { + return nil, err + } + } + + s := db.NewSession() + defer s.Close() + data := []map[string]interface{}{} + if err := s.Table(table).Find(&data); err != nil { + return nil, err + } + return data, nil +} + +// TruncateAllTestingTables empties every Vikunja table for the e2e testing +// endpoint. Callers must already have verified the testing token. +func TruncateAllTestingTables() error { + events.WaitForPendingHandlers() + + if err := db.TruncateAllTables(); err != nil { + return err + } + + // Reload after truncate; otherwise features enabled by a prior test outlive + // the now-empty license_status table. A reload failure here is non-fatal — + // the truncate already succeeded — so it is logged and swallowed. + if err := license.ReloadFromCache(); err != nil { + log.Errorf("Error reloading license after truncate: %v", err) + } + return nil +} diff --git a/pkg/routes/api/v1/admin/overview.go b/pkg/routes/api/v1/admin/overview.go index 3911e31be..6c5b71858 100644 --- a/pkg/routes/api/v1/admin/overview.go +++ b/pkg/routes/api/v1/admin/overview.go @@ -20,77 +20,27 @@ import ( "net/http" "code.vikunja.io/api/pkg/db" - "code.vikunja.io/api/pkg/license" + "code.vikunja.io/api/pkg/models" + "github.com/labstack/echo/v5" ) -type ShareCounts struct { - LinkShares int64 `json:"link_shares"` - TeamShares int64 `json:"team_shares"` - UserShares int64 `json:"user_shares"` -} - -type Overview struct { - Users int64 `json:"users"` - Projects int64 `json:"projects"` - Tasks int64 `json:"tasks"` - Teams int64 `json:"teams"` - Shares ShareCounts `json:"shares"` - License license.Info `json:"license"` -} - // GetOverview returns aggregate instance counts and metadata. // @Summary Admin overview // @Description Returns per-instance counts (users, projects, shares) plus version and license info. Instance-admin only, gated by the admin_panel feature. // @tags admin // @Produce json // @Security JWTKeyAuth -// @Success 200 {object} admin.Overview +// @Success 200 {object} models.Overview // @Failure 404 {object} web.HTTPError // @Router /admin/overview [get] func GetOverview(c *echo.Context) error { s := db.NewSession() defer s.Close() - users, err := s.Table("users").Count() + overview, err := models.BuildOverview(s) if err != nil { return err } - projects, err := s.Table("projects").Count() - if err != nil { - return err - } - tasks, err := s.Table("tasks").Count() - if err != nil { - return err - } - teams, err := s.Table("teams").Count() - if err != nil { - return err - } - linkShares, err := s.Table("link_shares").Count() - if err != nil { - return err - } - teamShares, err := s.Table("team_projects").Count() - if err != nil { - return err - } - userShares, err := s.Table("users_projects").Count() - if err != nil { - return err - } - - return c.JSON(http.StatusOK, Overview{ - Users: users, - Projects: projects, - Tasks: tasks, - Teams: teams, - Shares: ShareCounts{ - LinkShares: linkShares, - TeamShares: teamShares, - UserShares: userShares, - }, - License: license.CurrentInfo(), - }) + return c.JSON(http.StatusOK, overview) } diff --git a/pkg/routes/api/v1/admin/user_create.go b/pkg/routes/api/v1/admin/user_create.go index 5ba455579..bedddef58 100644 --- a/pkg/routes/api/v1/admin/user_create.go +++ b/pkg/routes/api/v1/admin/user_create.go @@ -20,28 +20,14 @@ import ( "errors" "net/http" - "code.vikunja.io/api/pkg/config" "code.vikunja.io/api/pkg/db" "code.vikunja.io/api/pkg/models" "code.vikunja.io/api/pkg/modules/auth/openid" - "code.vikunja.io/api/pkg/user" + "code.vikunja.io/api/pkg/routes/api/shared" "github.com/labstack/echo/v5" ) -// CreateUserBody wraps user.APIUserPassword with admin-only fields. -type CreateUserBody struct { - // The full name of the new user. Optional. - Name string `json:"name"` - // The language of the new user. Must be a valid IETF BCP 47 language code and exist in Vikunja. - Language string `json:"language" valid:"language"` - user.APIUserPassword - // Mark the new user as an instance admin. - IsAdmin bool `json:"is_admin"` - // Activate the new user immediately without email confirmation. - SkipEmailConfirm bool `json:"skip_email_confirm"` -} - // CreateUser provisions a new account on behalf of an instance admin. // @Summary Create a user (admin) // @Description Create a new local user account. Respects the admin-only fields `is_admin` and `skip_email_confirm`. The public registration toggle is bypassed. @@ -49,12 +35,12 @@ type CreateUserBody struct { // @Accept json // @Produce json // @Security JWTKeyAuth -// @Param body body admin.CreateUserBody true "The user to create" -// @Success 200 {object} admin.User +// @Param body body models.CreateUserBody true "The user to create" +// @Success 200 {object} shared.AdminUser // @Failure 400 {object} web.HTTPError // @Router /admin/users [post] func CreateUser(c *echo.Context) error { - body := &CreateUserBody{} + body := &models.CreateUserBody{} if err := c.Bind(body); err != nil { return c.JSON(http.StatusBadRequest, models.Message{Message: "No or invalid user model provided."}) } @@ -69,52 +55,15 @@ func CreateUser(c *echo.Context) error { s := db.NewSession() defer s.Close() - newUser, err := models.RegisterUser(s, &user.User{ - Username: body.Username, - Password: body.Password, - Email: body.Email, - Name: body.Name, - Language: body.Language, - }) + newUser, err := models.CreateUserAsAdmin(s, body) if err != nil { _ = s.Rollback() return err } - if body.IsAdmin { - if _, err := s.ID(newUser.ID).Cols("is_admin").Update(&user.User{IsAdmin: true}); err != nil { - _ = s.Rollback() - return err - } - newUser.IsAdmin = true - } - - // Force Active when the admin asked to skip, or when no mailer exists to send the confirmation. - if body.SkipEmailConfirm || !config.MailerEnabled.GetBool() { - if err := user.SetUserStatus(s, newUser, user.StatusActive); err != nil { - _ = s.Rollback() - return err - } - newUser.Status = user.StatusActive - } - - if err := s.Commit(); err != nil { - _ = s.Rollback() - return err - } - - // Reload the user so the returned status reflects what was actually persisted - // (e.g. StatusEmailConfirmationRequired on mail-enabled instances). - rs := db.NewSession() - defer rs.Close() - newUser, err = user.GetUserByID(rs, newUser.ID) - if err != nil { - return err - } - providers, err := openid.GetAllProviders() if err != nil { return err } - return c.JSON(http.StatusOK, newAdminUser(newUser, providers)) + return c.JSON(http.StatusOK, shared.NewAdminUser(newUser, providers)) } diff --git a/pkg/routes/api/v1/admin/users.go b/pkg/routes/api/v1/admin/users.go index f9392b772..5117b9e46 100644 --- a/pkg/routes/api/v1/admin/users.go +++ b/pkg/routes/api/v1/admin/users.go @@ -18,52 +18,13 @@ package admin import ( "code.vikunja.io/api/pkg/modules/auth/openid" + "code.vikunja.io/api/pkg/routes/api/shared" "code.vikunja.io/api/pkg/user" "code.vikunja.io/api/pkg/web" "xorm.io/xorm" ) -// User re-exposes fields hidden by the default user.User JSON view. -type User struct { - *user.User - IsAdmin bool `json:"is_admin"` - Status user.Status `json:"status"` - Issuer string `json:"issuer"` - Subject string `json:"subject,omitempty"` - AuthProvider string `json:"auth_provider,omitempty"` -} - -func newAdminUser(u *user.User, providers []*openid.Provider) *User { - return &User{ - User: u, - IsAdmin: u.IsAdmin, - Status: u.Status, - Issuer: u.Issuer, - Subject: u.Subject, - AuthProvider: resolveAuthProvider(u, providers), - } -} - -func resolveAuthProvider(u *user.User, providers []*openid.Provider) string { - switch u.Issuer { - case "", user.IssuerLocal: - return "" - case user.IssuerLDAP: - return "LDAP" - } - for _, provider := range providers { - issuerURL, err := provider.Issuer() - if err != nil { - continue - } - if issuerURL == u.Issuer { - return provider.Name - } - } - return u.Issuer -} - // UserList backs the admin list-users route via handler.ReadAllWeb; only ReadAll is used. type UserList struct { web.CRUDable `xorm:"-" json:"-"` @@ -79,7 +40,7 @@ type UserList struct { // @Param s query string false "Search string matched against username and email." // @Param page query int false "Page number, defaults to 1." // @Param per_page query int false "Items per page, defaults to the service setting." -// @Success 200 {array} admin.User +// @Success 200 {array} shared.AdminUser // @Failure 404 {object} web.HTTPError // @Router /admin/users [get] func (*UserList) ReadAll(s *xorm.Session, _ web.Auth, search string, page, perPage int) (interface{}, int, int64, error) { @@ -106,9 +67,9 @@ func (*UserList) ReadAll(s *xorm.Session, _ web.Auth, search string, page, perPa return nil, 0, 0, err } - out := make([]*User, 0, len(users)) + out := make([]*shared.AdminUser, 0, len(users)) for _, u := range users { - out = append(out, newAdminUser(u, providers)) + out = append(out, shared.NewAdminUser(u, providers)) } return out, len(out), totalCount, nil } diff --git a/pkg/routes/api/v1/admin/users_admin.go b/pkg/routes/api/v1/admin/users_admin.go index 5f31b269f..195bb2092 100644 --- a/pkg/routes/api/v1/admin/users_admin.go +++ b/pkg/routes/api/v1/admin/users_admin.go @@ -23,7 +23,9 @@ import ( "code.vikunja.io/api/pkg/db" "code.vikunja.io/api/pkg/models" "code.vikunja.io/api/pkg/modules/auth/openid" + "code.vikunja.io/api/pkg/routes/api/shared" "code.vikunja.io/api/pkg/user" + "github.com/labstack/echo/v5" ) @@ -41,7 +43,7 @@ type IsAdminPatch struct { // @Security JWTKeyAuth // @Param id path int true "User ID" // @Param body body admin.IsAdminPatch true "New admin value" -// @Success 200 {object} admin.User +// @Success 200 {object} shared.AdminUser // @Failure 400 {object} web.HTTPError // @Failure 404 {object} web.HTTPError // @Router /admin/users/{id}/admin [patch] @@ -63,24 +65,8 @@ func PatchAdmin(c *echo.Context) error { s := db.NewSession() defer s.Close() - target := &user.User{ID: id} - has, err := s.Get(target) + target, err := models.SetUserAdminFlag(s, id, *body.IsAdmin) if err != nil { - return err - } - if !has { - return user.ErrUserDoesNotExist{UserID: id} - } - - if !*body.IsAdmin { - if err := user.GuardLastAdmin(s, target); err != nil { - _ = s.Rollback() - return err - } - } - - target.IsAdmin = *body.IsAdmin - if _, err := s.ID(target.ID).Cols("is_admin").Update(target); err != nil { _ = s.Rollback() return err } @@ -92,5 +78,5 @@ func PatchAdmin(c *echo.Context) error { if err != nil { return err } - return c.JSON(http.StatusOK, newAdminUser(target, providers)) + return c.JSON(http.StatusOK, shared.NewAdminUser(target, providers)) } diff --git a/pkg/routes/api/v1/admin/users_mgmt.go b/pkg/routes/api/v1/admin/users_mgmt.go index 2e95d88b5..1e72fa173 100644 --- a/pkg/routes/api/v1/admin/users_mgmt.go +++ b/pkg/routes/api/v1/admin/users_mgmt.go @@ -23,7 +23,9 @@ import ( "code.vikunja.io/api/pkg/db" "code.vikunja.io/api/pkg/models" "code.vikunja.io/api/pkg/modules/auth/openid" + "code.vikunja.io/api/pkg/routes/api/shared" "code.vikunja.io/api/pkg/user" + "github.com/labstack/echo/v5" ) @@ -41,7 +43,7 @@ type StatusPatch struct { // @Security JWTKeyAuth // @Param id path int true "User ID" // @Param body body admin.StatusPatch true "Status" -// @Success 200 {object} admin.User +// @Success 200 {object} shared.AdminUser // @Failure 400 {object} web.HTTPError // @Failure 404 {object} web.HTTPError // @Router /admin/users/{id}/status [patch] @@ -65,24 +67,8 @@ func PatchStatus(c *echo.Context) error { s := db.NewSession() defer s.Close() - target := &user.User{ID: id} - has, err := s.Get(target) + target, err := models.SetUserStatusAsAdmin(s, id, newStatus) if err != nil { - return err - } - if !has { - return user.ErrUserDoesNotExist{UserID: id} - } - - // Any non-Active status blocks login, so moving an admin out of Active is equivalent to demotion. - if target.IsAdmin && newStatus != user.StatusActive { - if err := user.GuardLastAdmin(s, target); err != nil { - _ = s.Rollback() - return err - } - } - - if err := user.SetUserStatus(s, target, newStatus); err != nil { _ = s.Rollback() return err } @@ -90,13 +76,11 @@ func PatchStatus(c *echo.Context) error { return err } - // Refresh locally since GetUserByID refuses disabled accounts. - target.Status = newStatus providers, err := openid.GetAllProviders() if err != nil { return err } - return c.JSON(http.StatusOK, newAdminUser(target, providers)) + return c.JSON(http.StatusOK, shared.NewAdminUser(target, providers)) } // DeleteUser removes a user either immediately or through the self-deletion flow. @@ -128,32 +112,10 @@ func DeleteUser(c *echo.Context) error { s := db.NewSession() defer s.Close() - target := &user.User{ID: id} - has, err := s.Get(target) - if err != nil { - return err - } - if !has { - return user.ErrUserDoesNotExist{UserID: id} - } - - if err := user.GuardLastAdmin(s, target); err != nil { + if err := models.DeleteUserAsAdmin(s, id, mode); err != nil { _ = s.Rollback() return err } - - if mode == "now" { - if err := models.DeleteUser(s, target); err != nil { - _ = s.Rollback() - return err - } - } else { - if err := user.RequestDeletion(s, target); err != nil { - _ = s.Rollback() - return err - } - } - if err := s.Commit(); err != nil { return err } diff --git a/pkg/routes/api/v1/avatar.go b/pkg/routes/api/v1/avatar.go index d98b9dc32..a80719e72 100644 --- a/pkg/routes/api/v1/avatar.go +++ b/pkg/routes/api/v1/avatar.go @@ -17,22 +17,16 @@ package v1 import ( - "code.vikunja.io/api/pkg/config" "code.vikunja.io/api/pkg/db" "code.vikunja.io/api/pkg/log" "code.vikunja.io/api/pkg/models" "code.vikunja.io/api/pkg/modules/avatar" - "code.vikunja.io/api/pkg/modules/avatar/botmarble" - "code.vikunja.io/api/pkg/modules/avatar/empty" - "code.vikunja.io/api/pkg/modules/avatar/upload" "code.vikunja.io/api/pkg/user" - "io" + "errors" "net/http" "strconv" - "strings" - "github.com/gabriel-vasile/mimetype" "github.com/labstack/echo/v5" ) @@ -54,42 +48,19 @@ func GetAvatar(c *echo.Context) error { s := db.NewSession() defer s.Close() - // Get the user - u, err := user.GetUserWithEmail(s, &user.User{Username: username}) - if err != nil && !user.IsErrUserDoesNotExist(err) && !user.IsErrUserStatusError(err) { - log.Errorf("Error getting user for avatar: %v", err) - return err - } - - found := err == nil || user.IsErrUserStatusError(err) - - avatarProvider := avatar.GetProvider(u) - - if !found { - avatarProvider = &empty.Provider{} - } - - if found && u.IsBot() { - avatarProvider = &botmarble.Provider{} - } - size := c.QueryParam("size") var sizeInt int64 = 250 // Default size of 250 if size != "" { + var err error sizeInt, err = strconv.ParseInt(size, 10, 64) if err != nil { log.Errorf("Error parsing size: %v", err) return models.ErrInvalidModel{Message: "Invalid size parameter"} } } - if sizeInt > config.ServiceMaxAvatarSize.GetInt64() { - sizeInt = config.ServiceMaxAvatarSize.GetInt64() - } - // Get the avatar - a, mimeType, err := avatarProvider.GetAvatar(u, sizeInt) + a, mimeType, err := avatar.GetAvatarForUsername(s, username, sizeInt) if err != nil { - log.Errorf("Error getting avatar for user %d: %v", u.ID, err) return err } @@ -137,21 +108,11 @@ func UploadAvatar(c *echo.Context) (err error) { } defer src.Close() - // Validate we're dealing with an image - mime, err := mimetype.DetectReader(src) - if err != nil { - _ = s.Rollback() - return err - } - if !strings.HasPrefix(mime.String(), "image") { - return c.JSON(http.StatusBadRequest, models.Message{Message: "Uploaded file is no image."}) - } - _, _ = src.Seek(0, io.SeekStart) - - u.AvatarProvider = "upload" - err = upload.StoreAvatarFile(s, u, src) - if err != nil { + if err := avatar.StoreUploadedAvatar(s, u, src); err != nil { _ = s.Rollback() + if errors.Is(err, avatar.ErrNotAnImage) { + return c.JSON(http.StatusBadRequest, models.Message{Message: "Uploaded file is no image."}) + } return err } @@ -160,7 +121,5 @@ func UploadAvatar(c *echo.Context) (err error) { return err } - avatar.FlushAllCaches(u) - return c.JSON(http.StatusOK, models.Message{Message: "Avatar was uploaded successfully."}) } diff --git a/pkg/routes/api/v1/info.go b/pkg/routes/api/v1/info.go index 0f8ded367..87891ff15 100644 --- a/pkg/routes/api/v1/info.go +++ b/pkg/routes/api/v1/info.go @@ -19,149 +19,18 @@ package v1 import ( "net/http" - "code.vikunja.io/api/pkg/config" - "code.vikunja.io/api/pkg/license" - "code.vikunja.io/api/pkg/log" - "code.vikunja.io/api/pkg/modules/auth/openid" - csvmigrator "code.vikunja.io/api/pkg/modules/migration/csv" - microsofttodo "code.vikunja.io/api/pkg/modules/migration/microsoft-todo" - "code.vikunja.io/api/pkg/modules/migration/ticktick" - "code.vikunja.io/api/pkg/modules/migration/todoist" - "code.vikunja.io/api/pkg/modules/migration/trello" - vikunja_file "code.vikunja.io/api/pkg/modules/migration/vikunja-file" - "code.vikunja.io/api/pkg/modules/migration/wekan" - "code.vikunja.io/api/pkg/version" + "code.vikunja.io/api/pkg/routes/api/shared" "github.com/labstack/echo/v5" ) -type vikunjaInfos struct { - Version string `json:"version"` - FrontendURL string `json:"frontend_url"` - Motd string `json:"motd"` - LinkSharingEnabled bool `json:"link_sharing_enabled"` - MaxFileSize string `json:"max_file_size"` - MaxItemsPerPage int `json:"max_items_per_page"` - AvailableMigrators []string `json:"available_migrators"` - TaskAttachmentsEnabled bool `json:"task_attachments_enabled"` - EnabledBackgroundProviders []string `json:"enabled_background_providers"` - TotpEnabled bool `json:"totp_enabled"` - Legal legalInfo `json:"legal"` - CaldavEnabled bool `json:"caldav_enabled"` - AuthInfo authInfo `json:"auth"` - EmailRemindersEnabled bool `json:"email_reminders_enabled"` - UserDeletionEnabled bool `json:"user_deletion_enabled"` - TaskCommentsEnabled bool `json:"task_comments_enabled"` - DemoModeEnabled bool `json:"demo_mode_enabled"` - WebhooksEnabled bool `json:"webhooks_enabled"` - PublicTeamsEnabled bool `json:"public_teams_enabled"` - EnabledProFeatures []license.Feature `json:"enabled_pro_features"` -} - -type authInfo struct { - Local localAuthInfo `json:"local"` - Ldap ldapAuthInfo `json:"ldap"` - OpenIDConnect openIDAuthInfo `json:"openid_connect"` -} - -type localAuthInfo struct { - Enabled bool `json:"enabled"` - RegistrationEnabled bool `json:"registration_enabled"` -} - -type ldapAuthInfo struct { - Enabled bool `json:"enabled"` -} - -type openIDAuthInfo struct { - Enabled bool `json:"enabled"` - Providers []*openid.Provider `json:"providers"` -} - -type legalInfo struct { - ImprintURL string `json:"imprint_url"` - PrivacyPolicyURL string `json:"privacy_policy_url"` -} - // Info is the handler to get infos about this vikunja instance // @Summary Info // @Description Returns the version, frontendurl, motd and various settings of Vikunja // @tags service // @Produce json -// @Success 200 {object} v1.vikunjaInfos +// @Success 200 {object} shared.VikunjaInfos // @Router /info [get] func Info(c *echo.Context) error { - info := vikunjaInfos{ - Version: version.Version, - FrontendURL: config.ServicePublicURL.GetString(), - Motd: config.ServiceMotd.GetString(), - LinkSharingEnabled: config.ServiceEnableLinkSharing.GetBool(), - MaxFileSize: config.FilesMaxSize.GetString(), - MaxItemsPerPage: config.ServiceMaxItemsPerPage.GetInt(), - TaskAttachmentsEnabled: config.ServiceEnableTaskAttachments.GetBool(), - TotpEnabled: config.ServiceEnableTotp.GetBool(), - CaldavEnabled: config.ServiceEnableCaldav.GetBool(), - EmailRemindersEnabled: config.ServiceEnableEmailReminders.GetBool(), - UserDeletionEnabled: config.ServiceEnableUserDeletion.GetBool(), - TaskCommentsEnabled: config.ServiceEnableTaskComments.GetBool(), - DemoModeEnabled: config.ServiceDemoMode.GetBool(), - WebhooksEnabled: config.WebhooksEnabled.GetBool(), - PublicTeamsEnabled: config.ServiceEnablePublicTeams.GetBool(), - EnabledProFeatures: license.EnabledProFeatures(), - AvailableMigrators: []string{ - (&vikunja_file.FileMigrator{}).Name(), - (&ticktick.Migrator{}).Name(), - (&wekan.Migrator{}).Name(), - (&csvmigrator.Migrator{}).Name(), - }, - Legal: legalInfo{ - ImprintURL: config.LegalImprintURL.GetString(), - PrivacyPolicyURL: config.LegalPrivacyURL.GetString(), - }, - AuthInfo: authInfo{ - Local: localAuthInfo{ - Enabled: config.AuthLocalEnabled.GetBool(), - RegistrationEnabled: config.AuthLocalEnabled.GetBool() && config.ServiceEnableRegistration.GetBool(), - }, - Ldap: ldapAuthInfo{ - Enabled: config.AuthLdapEnabled.GetBool(), - }, - OpenIDConnect: openIDAuthInfo{ - Enabled: config.AuthOpenIDEnabled.GetBool(), - }, - }, - } - - providers, err := openid.GetAllProviders() - if err != nil { - log.Errorf("Error while getting openid providers for /info: %s", err) - // No return here to not break /info - } - - info.AuthInfo.OpenIDConnect.Providers = providers - - // Migrators - if config.MigrationTodoistEnable.GetBool() { - m := &todoist.Migration{} - info.AvailableMigrators = append(info.AvailableMigrators, m.Name()) - } - if config.MigrationTrelloEnable.GetBool() { - m := &trello.Migration{} - info.AvailableMigrators = append(info.AvailableMigrators, m.Name()) - } - if config.MigrationMicrosoftTodoEnable.GetBool() { - m := µsofttodo.Migration{} - info.AvailableMigrators = append(info.AvailableMigrators, m.Name()) - } - - if config.BackgroundsEnabled.GetBool() { - if config.BackgroundsUploadEnabled.GetBool() { - info.EnabledBackgroundProviders = append(info.EnabledBackgroundProviders, "upload") - } - if config.BackgroundsUnsplashEnabled.GetBool() { - info.EnabledBackgroundProviders = append(info.EnabledBackgroundProviders, "unsplash") - } - } - - return c.JSON(http.StatusOK, info) + return c.JSON(http.StatusOK, shared.BuildInfo()) } diff --git a/pkg/routes/api/v1/link_sharing_auth.go b/pkg/routes/api/v1/link_sharing_auth.go index 9e20a94f8..f4ca79ed0 100644 --- a/pkg/routes/api/v1/link_sharing_auth.go +++ b/pkg/routes/api/v1/link_sharing_auth.go @@ -19,20 +19,11 @@ package v1 import ( "net/http" - "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/routes/api/shared" - "code.vikunja.io/api/pkg/models" - "code.vikunja.io/api/pkg/modules/auth" "github.com/labstack/echo/v5" ) -// LinkShareToken represents a link share auth token with extra infos about the actual link share -type LinkShareToken struct { - auth.Token - *models.LinkSharing - ProjectID int64 `json:"project_id"` -} - // LinkShareAuth represents everything required to authenticate a link share type LinkShareAuth struct { Hash string `param:"share" json:"-"` @@ -53,36 +44,14 @@ type LinkShareAuth struct { // @Router /shares/{share}/auth [post] func AuthenticateLinkShare(c *echo.Context) error { sh := &LinkShareAuth{} - err := c.Bind(sh) + if err := c.Bind(sh); err != nil { + return err + } + + token, err := shared.AuthenticateLinkShare(sh.Hash, sh.Password) if err != nil { return err } - s := db.NewSession() - defer s.Close() - - share, err := models.GetLinkShareByHash(s, sh.Hash) - if err != nil { - return err - } - - if share.SharingType == models.SharingTypeWithPassword { - err := models.VerifyLinkSharePassword(share, sh.Password) - if err != nil { - return err - } - } - - t, err := auth.NewLinkShareJWTAuthtoken(share) - if err != nil { - return err - } - - share.Password = "" - - return c.JSON(http.StatusOK, LinkShareToken{ - Token: auth.Token{Token: t}, - LinkSharing: share, - ProjectID: share.ProjectID, - }) + return c.JSON(http.StatusOK, token) } diff --git a/pkg/routes/api/v1/login.go b/pkg/routes/api/v1/login.go index eb92945d1..6a4662ae2 100644 --- a/pkg/routes/api/v1/login.go +++ b/pkg/routes/api/v1/login.go @@ -21,10 +21,11 @@ import ( "code.vikunja.io/api/pkg/config" "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/events" + "code.vikunja.io/api/pkg/log" "code.vikunja.io/api/pkg/models" "code.vikunja.io/api/pkg/modules/auth" - "code.vikunja.io/api/pkg/modules/auth/ldap" - "code.vikunja.io/api/pkg/modules/keyvalue" + "code.vikunja.io/api/pkg/routes/api/shared" user2 "code.vikunja.io/api/pkg/user" "github.com/golang-jwt/jwt/v5" @@ -49,84 +50,13 @@ func Login(c *echo.Context) (err error) { return c.JSON(http.StatusBadRequest, models.Message{Message: "Please provide a username and password."}) } - s := db.NewSession() - defer s.Close() - - var user *user2.User - if config.AuthLdapEnabled.GetBool() { - user, err = ldap.AuthenticateUserInLDAP(s, u.Username, u.Password, config.AuthLdapGroupSyncEnabled.GetBool(), config.AuthLdapAvatarSyncAttribute.GetString()) - if err != nil && !user2.IsErrWrongUsernameOrPassword(err) { - _ = s.Rollback() - return err - } - } - - if user == nil { - // Check if the user is a bot before attempting password verification, - // because bots have no password hash and bcrypt would fail with a - // misleading error. - existingUser, lookupErr := user2.GetUserByUsername(s, u.Username) - if lookupErr == nil && existingUser.IsBot() { - _ = s.Rollback() - return &user2.ErrAccountIsBot{UserID: existingUser.ID} - } - - // This allows us to still have local users while ldap is enabled - user, err = user2.CheckUserCredentials(s, &u) - if err != nil { - _ = s.Rollback() - return err - } - } - - if user.Status == user2.StatusDisabled || user.Status == user2.StatusAccountLocked { - _ = s.Rollback() - return &user2.ErrAccountDisabled{UserID: user.ID} - } - - totpEnabled, err := user2.TOTPEnabledForUser(s, user) + user, err := shared.AuthenticateUserCredentials(c.Request().Context(), &u) if err != nil { - _ = s.Rollback() - return err - } - - if totpEnabled { - if u.TOTPPasscode == "" { - _ = s.Rollback() - return user2.ErrInvalidTOTPPasscode{} - } - - _, err = user2.ValidateTOTPPasscode(s, &user2.TOTPPasscode{ - User: user, - Passcode: u.TOTPPasscode, - }) - if err != nil { - // Rollback before HandleFailedTOTPAuth so its dedicated session - // can acquire a write lock on SQLite shared-cache. The lockout - // write is decoupled from this handler's transaction — see - // GHSA-fgfv-pv97-6cmj. - _ = s.Rollback() - if user2.IsErrInvalidTOTPPasscode(err) { - user2.HandleFailedTOTPAuth(user) - } - return err - } - } - - if err := keyvalue.Del(user.GetFailedTOTPAttemptsKey()); err != nil { - return err - } - if err := keyvalue.Del(user.GetFailedPasswordAttemptsKey()); err != nil { - return err - } - - if err := s.Commit(); err != nil { - _ = s.Rollback() return err } // Create token - return auth.NewUserAuthTokenResponse(user, c, u.LongToken) + return auth.NewUserAuthTokenResponse(user, c, u.LongToken, nil) } // RenewToken renews a link share token only. User tokens must use @@ -220,42 +150,52 @@ func RefreshToken(c *echo.Context) (err error) { return c.JSON(http.StatusOK, auth.Token{Token: result.AccessToken}) } +type LogoutResponse struct { + Message string `json:"message"` + // RP-Initiated Logout URL the frontend redirects to. Empty for non-OIDC sessions. + OIDCLogoutURL string `json:"oidc_logout_url,omitempty"` +} + // Logout deletes the current session from the server. // @Summary Logout -// @Description Destroys the current session and clears the refresh token cookie. +// @Description Destroys the current session and clears the refresh token cookie. For OpenID Connect sessions the response includes an `oidc_logout_url` the client should redirect to so the provider session is ended too. // @tags auth // @Produce json -// @Success 200 {object} models.Message "Successfully logged out." +// @Success 200 {object} v1.LogoutResponse "Successfully logged out." // @Router /user/logout [post] func Logout(c *echo.Context) (err error) { auth.ClearRefreshTokenCookie(c) var sid string + var userID int64 if raw := c.Get("user"); raw != nil { if jwtinf, ok := raw.(*jwt.Token); ok { if claims, ok := jwtinf.Claims.(jwt.MapClaims); ok { sid, _ = claims["sid"].(string) + // Only user tokens carry a sid, but check the type explicitly + // so a link share id can never be logged as a user id. + if typ, ok := claims["type"].(float64); ok && int(typ) == auth.AuthTypeUser { + if id, ok := claims["id"].(float64); ok { + userID = int64(id) + } + } } } } - if sid == "" { - return c.JSON(http.StatusOK, models.Message{Message: "Successfully logged out."}) - } - - s := db.NewSession() - defer s.Close() - - _, err = s.Where("id = ?", sid).Delete(&models.Session{}) + oidcLogoutURL, err := shared.LogoutSession(sid) if err != nil { - _ = s.Rollback() return err } - if err := s.Commit(); err != nil { - _ = s.Rollback() - return err + if userID != 0 { + if err := events.DispatchWithContext(c.Request().Context(), &user2.LogoutEvent{UserID: userID}); err != nil { + log.Errorf("Could not dispatch logout event: %s", err) + } } - return c.JSON(http.StatusOK, models.Message{Message: "Successfully logged out."}) + return c.JSON(http.StatusOK, LogoutResponse{ + Message: "Successfully logged out.", + OIDCLogoutURL: oidcLogoutURL, + }) } diff --git a/pkg/routes/api/v1/task_attachment.go b/pkg/routes/api/v1/task_attachment.go index dd6478703..68197a3bf 100644 --- a/pkg/routes/api/v1/task_attachment.go +++ b/pkg/routes/api/v1/task_attachment.go @@ -18,43 +18,16 @@ package v1 import ( "errors" - "io" - "mime" "net/http" - "strconv" - "strings" - "code.vikunja.io/api/pkg/config" "code.vikunja.io/api/pkg/db" "code.vikunja.io/api/pkg/models" auth2 "code.vikunja.io/api/pkg/modules/auth" - "code.vikunja.io/api/pkg/web" + webfiles "code.vikunja.io/api/pkg/web/files" "github.com/labstack/echo/v5" ) -// attachmentUploadError represents a structured error for attachment upload failures -type attachmentUploadError struct { - Code int `json:"code,omitempty"` - Message string `json:"message"` -} - -// toAttachmentUploadError converts an error to a structured attachmentUploadError -func toAttachmentUploadError(err error) attachmentUploadError { - // Try to get structured error info from HTTPErrorProcessor - if httpErr, ok := err.(web.HTTPErrorProcessor); ok { - errDetails := httpErr.HTTPError() - return attachmentUploadError{ - Code: errDetails.Code, - Message: errDetails.Message, - } - } - // Fall back to just the error message - return attachmentUploadError{ - Message: err.Error(), - } -} - // UploadTaskAttachment handles everything needed for the upload of a task attachment // @Summary Upload a task attachment // @Description Upload a task attachment. You can pass multiple files with the files form param. @@ -76,7 +49,6 @@ func UploadTaskAttachment(c *echo.Context) error { return echo.NewHTTPError(http.StatusBadRequest, "No task ID provided").Wrap(err) } - // Permissions check auth, err := auth2.GetAuthFromClaims(c) if err != nil { return err @@ -85,15 +57,6 @@ func UploadTaskAttachment(c *echo.Context) error { s := db.NewSession() defer s.Close() - can, err := taskAttachment.CanCreate(s, auth) - if err != nil { - _ = s.Rollback() - return err - } - if !can { - return echo.ErrForbidden - } - // Multipart form form, err := c.MultipartForm() if err != nil { @@ -104,31 +67,23 @@ func UploadTaskAttachment(c *echo.Context) error { return err } - type result struct { - Errors []attachmentUploadError `json:"errors"` - Success []*models.TaskAttachment `json:"success"` - } - r := &result{} fileHeaders := form.File["files"] + uploads := make([]*models.AttachmentToUpload, 0, len(fileHeaders)) + var openErrors []error for _, file := range fileHeaders { - // We create a new attachment object here to have a clean start - ta := &models.TaskAttachment{ - TaskID: taskAttachment.TaskID, - } - f, err := file.Open() if err != nil { - r.Errors = append(r.Errors, toAttachmentUploadError(err)) + openErrors = append(openErrors, err) continue } defer f.Close() + uploads = append(uploads, &models.AttachmentToUpload{Reader: f, Filename: file.Filename, Size: uint64(file.Size)}) + } - err = ta.NewAttachment(s, f, file.Filename, uint64(file.Size), auth) - if err != nil { - r.Errors = append(r.Errors, toAttachmentUploadError(err)) - continue - } - r.Success = append(r.Success, ta) + success, failures, err := models.UploadTaskAttachments(s, auth, taskAttachment.TaskID, uploads) + if err != nil { + _ = s.Rollback() + return err } if err := s.Commit(); err != nil { @@ -136,7 +91,7 @@ func UploadTaskAttachment(c *echo.Context) error { return err } - return c.JSON(http.StatusOK, r) + return c.JSON(http.StatusOK, webfiles.BuildUploadResult(success, append(openErrors, failures...))) } // GetTaskAttachment returns a task attachment to download for the user @@ -160,7 +115,6 @@ func GetTaskAttachment(c *echo.Context) error { return echo.NewHTTPError(http.StatusBadRequest, "No task ID provided").Wrap(err) } - // Permissions check auth, err := auth2.GetAuthFromClaims(c) if err != nil { return err @@ -169,36 +123,11 @@ func GetTaskAttachment(c *echo.Context) error { s := db.NewSession() defer s.Close() - can, _, err := taskAttachment.CanRead(s, auth) - if err != nil { - _ = s.Rollback() - return err - } - if !can { - return echo.ErrForbidden - } - - // Get the attachment incl file - err = taskAttachment.ReadOne(s, auth) - if err != nil { - _ = s.Rollback() - return err - } - - // Open the file so its content is available for preview generation and download - err = taskAttachment.File.LoadFileByID() - if err != nil { - _ = s.Rollback() - return err - } - - // If the preview query parameter is set, get the preview (cached or generate) previewSize := models.GetPreviewSizeFromString(c.QueryParam("preview_size")) - if previewSize != models.PreviewSizeUnknown && strings.HasPrefix(taskAttachment.File.Mime, "image") { - previewFileBytes := taskAttachment.GetPreview(previewSize) - if previewFileBytes != nil { - return c.Blob(http.StatusOK, "image/png", previewFileBytes) - } + attachment, preview, err := models.LoadTaskAttachmentForDownload(s, auth, taskAttachment.TaskID, taskAttachment.ID, previewSize) + if err != nil { + _ = s.Rollback() + return err } if err := s.Commit(); err != nil { @@ -206,36 +135,6 @@ func GetTaskAttachment(c *echo.Context) error { return err } - mimeToReturn := taskAttachment.File.Mime - if mimeToReturn == "" { - mimeToReturn = "application/octet-stream" - } - - c.Response().Header().Set("Content-Disposition", mime.FormatMediaType("attachment", map[string]string{ - "filename": taskAttachment.File.Name, - })) - c.Response().Header().Set("Content-Type", mimeToReturn) - c.Response().Header().Set("Content-Length", strconv.FormatUint(taskAttachment.File.Size, 10)) - c.Response().Header().Set("Last-Modified", taskAttachment.File.Created.UTC().Format(http.TimeFormat)) - // Override the global no-store directive so browsers can cache attachments. - // no-cache allows caching but requires revalidation via If-Modified-Since. - c.Response().Header().Set("Cache-Control", "no-cache") - - if config.FilesType.GetString() == "s3" { - // Check If-Modified-Since and return 304 if the file hasn't changed. - // http.ServeContent handles this automatically for local files. - if ifModSince := c.Request().Header.Get("If-Modified-Since"); ifModSince != "" { - if t, parseErr := http.ParseTime(ifModSince); parseErr == nil && !taskAttachment.File.Created.UTC().After(t) { - return c.NoContent(http.StatusNotModified) - } - } - - // s3 files cannot use http.ServeContent as it requires a Seekable file - // so we stream the file content directly to the response - _, err = io.Copy(c.Response(), taskAttachment.File.File) - return err - } - - http.ServeContent(c.Response(), c.Request(), taskAttachment.File.Name, taskAttachment.File.Created, taskAttachment.File.File.(io.ReadSeeker)) + webfiles.WriteAttachmentDownload(c.Response(), c.Request(), attachment, preview) return nil } diff --git a/pkg/routes/api/v1/task_by_index.go b/pkg/routes/api/v1/task_by_index.go index 49106a311..19828bae4 100644 --- a/pkg/routes/api/v1/task_by_index.go +++ b/pkg/routes/api/v1/task_by_index.go @@ -22,11 +22,11 @@ package v1 // taskHandler.ReadOneWeb in routes.go. // // @Summary Get one task by its per-project index -// @Description Returns a single task identified by its per-project index. Useful when resolving human-readable references like "PROJ-42" to a canonical task object. Note that task indexes are reassigned when a task is moved between projects, so long-lived references should use the returned task id instead. +// @Description Returns a single task identified by its per-project index. Useful when resolving human-readable references like "PROJ-42" to a canonical task object. The `project` path parameter accepts either a numeric project id or the project's identifier (e.g. "PROJ"); values consisting solely of digits are always interpreted as ids. Note that task indexes are reassigned when a task is moved between projects, so long-lived references should use the returned task id instead. // @tags task // @Accept json // @Produce json -// @Param project path int true "The project ID" +// @Param project path string true "The project id or the project's identifier" // @Param index path int true "The task's per-project index" // @Param expand query string false "If set to `subtasks`, Vikunja will fetch only tasks which do not have subtasks and then in a second step, will fetch all of these subtasks. This may result in more tasks than the pagination limit being returned, but all subtasks will be present in the response. You can only set this to `subtasks`." // @Security JWTKeyAuth diff --git a/pkg/routes/api/v1/testing.go b/pkg/routes/api/v1/testing.go index 98f5aeca1..62d5f5206 100644 --- a/pkg/routes/api/v1/testing.go +++ b/pkg/routes/api/v1/testing.go @@ -22,10 +22,8 @@ import ( "net/http" "code.vikunja.io/api/pkg/config" - "code.vikunja.io/api/pkg/db" - "code.vikunja.io/api/pkg/events" - "code.vikunja.io/api/pkg/license" "code.vikunja.io/api/pkg/log" + "code.vikunja.io/api/pkg/routes/api/shared" "github.com/labstack/echo/v5" ) @@ -63,36 +61,8 @@ func HandleTesting(c *echo.Context) error { }) } - // Wait for all async event handlers from the previous test to complete - // before modifying the database. Without this, handlers hold SQLite - // connections and starve this request's truncate/insert operations. - events.WaitForPendingHandlers() - truncate := c.QueryParam("truncate") - if truncate == "true" || truncate == "" { - // When truncating certain tables, also truncate dependent tables - // whose rows reference the truncated table by user/entity ID. - // Without foreign key cascades, stale rows would persist and - // pollute subsequent tests that reuse the same auto-increment IDs. - dependentTables := map[string][]string{ - "users": {"notifications"}, - } - if deps, ok := dependentTables[table]; ok { - for _, dep := range deps { - if err = db.RestoreAndTruncate(dep, nil); err != nil { - log.Errorf("Error truncating dependent table %s: %v", dep, err) - return c.JSON(http.StatusInternalServerError, map[string]interface{}{ - "error": true, - "message": err.Error(), - }) - } - } - } - err = db.RestoreAndTruncate(table, content) - } else { - err = db.Restore(table, content) - } - + data, err := shared.ReplaceTableContents(table, content, truncate == "true" || truncate == "") if err != nil { log.Errorf("Error replacing table data: %v", err) return c.JSON(http.StatusInternalServerError, map[string]interface{}{ @@ -101,29 +71,6 @@ func HandleTesting(c *echo.Context) error { }) } - // License state is cached at startup; re-apply so tests take effect without a restart. - if table == "license_status" { - if err := license.ReloadFromCache(); err != nil { - log.Errorf("Error reloading license from seeded cache: %v", err) - return c.JSON(http.StatusInternalServerError, map[string]interface{}{ - "error": true, - "message": err.Error(), - }) - } - } - - s := db.NewSession() - defer s.Close() - data := []map[string]interface{}{} - err = s.Table(table).Find(&data) - if err != nil { - log.Errorf("Error fetching table data: %v", err) - return c.JSON(http.StatusInternalServerError, map[string]interface{}{ - "error": true, - "message": err.Error(), - }) - } - return c.JSON(http.StatusCreated, data) } @@ -142,9 +89,7 @@ func HandleTestingTruncateAll(c *echo.Context) error { return echo.ErrForbidden } - events.WaitForPendingHandlers() - - if err := db.TruncateAllTables(); err != nil { + if err := shared.TruncateAllTestingTables(); err != nil { log.Errorf("Error truncating all tables: %v", err) return c.JSON(http.StatusInternalServerError, map[string]interface{}{ "error": true, @@ -152,11 +97,6 @@ func HandleTestingTruncateAll(c *echo.Context) error { }) } - // Reload after truncate; otherwise features enabled by a prior test outlive the now-empty license_status table. - if err := license.ReloadFromCache(); err != nil { - log.Errorf("Error reloading license after truncate: %v", err) - } - return c.JSON(http.StatusOK, map[string]string{ "message": "ok", }) diff --git a/pkg/routes/api/v1/user_confirm_email.go b/pkg/routes/api/v1/user_confirm_email.go index e01865103..254d4142a 100644 --- a/pkg/routes/api/v1/user_confirm_email.go +++ b/pkg/routes/api/v1/user_confirm_email.go @@ -19,9 +19,8 @@ package v1 import ( "net/http" - "code.vikunja.io/api/pkg/db" - "code.vikunja.io/api/pkg/models" + "code.vikunja.io/api/pkg/routes/api/shared" "code.vikunja.io/api/pkg/user" "github.com/labstack/echo/v5" ) @@ -44,17 +43,7 @@ func UserConfirmEmail(c *echo.Context) error { return echo.NewHTTPError(http.StatusBadRequest, "No token provided.").Wrap(err) } - s := db.NewSession() - defer s.Close() - - err := user.ConfirmEmail(s, &emailConfirm) - if err != nil { - _ = s.Rollback() - return err - } - - if err := s.Commit(); err != nil { - _ = s.Rollback() + if err := shared.ConfirmEmail(&emailConfirm); err != nil { return err } diff --git a/pkg/routes/api/v1/user_export.go b/pkg/routes/api/v1/user_export.go index 3c07c9ebc..b01b1fdf3 100644 --- a/pkg/routes/api/v1/user_export.go +++ b/pkg/routes/api/v1/user_export.go @@ -19,14 +19,11 @@ package v1 import ( "io" "net/http" - "os" "strconv" - "time" "code.vikunja.io/api/pkg/config" "code.vikunja.io/api/pkg/db" "code.vikunja.io/api/pkg/events" - "code.vikunja.io/api/pkg/files" "code.vikunja.io/api/pkg/models" "code.vikunja.io/api/pkg/user" "github.com/labstack/echo/v5" @@ -97,7 +94,7 @@ func RequestUserDataExport(c *echo.Context) error { return err } - events.DispatchPending(s) + events.DispatchPending(c.Request().Context(), s) return c.JSON(http.StatusOK, models.Message{Message: "Successfully requested data export. We will send you an email when it's ready."}) } @@ -127,28 +124,18 @@ func DownloadUserDataExport(c *echo.Context) error { return err } - // Check if user has an export file - exportNotFoundError := echo.NewHTTPError(http.StatusNotFound, "No user data export found.") - if u.ExportFileID == 0 { - return exportNotFoundError + exportFile, err := models.GetUserDataExportFile(u) + if err != nil { + if models.IsErrUserDataExportDoesNotExist(err) { + return echo.NewHTTPError(http.StatusNotFound, "No user data export found.") + } + return err } + defer func() { _ = exportFile.File.Close() }() - // Download - exportFile := &files.File{ID: u.ExportFileID} - err = exportFile.LoadFileMetaByID() - if err != nil { - if files.IsErrFileDoesNotExist(err) { - return exportNotFoundError - } - return err - } - err = exportFile.LoadFileByID() - if err != nil { - if os.IsNotExist(err) { - return exportNotFoundError - } - return err - } + // Downloads must never be cached; no-cache overrides the global no-store + // directive while still allowing revalidation. + c.Response().Header().Set("Cache-Control", "no-cache") if config.FilesType.GetString() == "s3" { c.Response().Header().Set("Content-Disposition", "attachment; filename=\""+exportFile.Name+"\"") @@ -163,19 +150,12 @@ func DownloadUserDataExport(c *echo.Context) error { return nil } -type UserExportStatus struct { - ID int64 `json:"id"` - Size uint64 `json:"size"` - Created time.Time `json:"created"` - Expires time.Time `json:"expires"` -} - // GetUserExportStatus returns metadata about the current user export if it exists // @Summary Get current user data export // @tags user // @Produce json // @Security JWTKeyAuth -// @Success 200 {object} v1.UserExportStatus +// @Success 200 {object} models.UserExportStatus // @Router /user/export [get] func GetUserExportStatus(c *echo.Context) error { s := db.NewSession() @@ -186,20 +166,12 @@ func GetUserExportStatus(c *echo.Context) error { return err } - if u.ExportFileID == 0 { - return c.JSON(http.StatusOK, struct{}{}) - } - - exportFile := &files.File{ID: u.ExportFileID} - if err := exportFile.LoadFileMetaByID(); err != nil { + status, err := models.GetUserDataExportStatus(u) + if err != nil { return err } - - status := UserExportStatus{ - ID: exportFile.ID, - Size: exportFile.Size, - Created: exportFile.Created, - Expires: exportFile.Created.Add(7 * 24 * time.Hour), + if status == nil { + return c.JSON(http.StatusOK, struct{}{}) } return c.JSON(http.StatusOK, status) diff --git a/pkg/routes/api/v1/user_list.go b/pkg/routes/api/v1/user_list.go index db4a777a0..6bc6e392f 100644 --- a/pkg/routes/api/v1/user_list.go +++ b/pkg/routes/api/v1/user_list.go @@ -52,17 +52,12 @@ func UserList(c *echo.Context) error { return err } - users, err := user.ListUsers(s, search, currentUser, nil) + users, err := user.SearchUsers(s, search, currentUser) if err != nil { _ = s.Rollback() return err } - // Obfuscate the mailadresses - for in := range users { - users[in].Email = "" - } - return c.JSON(http.StatusOK, users) } @@ -98,15 +93,6 @@ func ListUsersForProject(c *echo.Context) error { s := db.NewSession() defer s.Close() - canRead, _, err := project.CanRead(s, auth) - if err != nil { - _ = s.Rollback() - return err - } - if !canRead { - return echo.ErrForbidden - } - currentUser, err := user.GetCurrentUser(c) if err != nil { _ = s.Rollback() @@ -114,11 +100,14 @@ func ListUsersForProject(c *echo.Context) error { } search := c.QueryParam("s") - users, err := models.ListUsersFromProject(s, &project, currentUser, search) + users, canRead, err := models.SearchUsersForProject(s, &project, auth, currentUser, search) if err != nil { _ = s.Rollback() return err } + if !canRead { + return echo.ErrForbidden + } if err := s.Commit(); err != nil { _ = s.Rollback() diff --git a/pkg/routes/api/v1/user_password_reset.go b/pkg/routes/api/v1/user_password_reset.go index b91a28a7a..6c8090ba0 100644 --- a/pkg/routes/api/v1/user_password_reset.go +++ b/pkg/routes/api/v1/user_password_reset.go @@ -19,9 +19,8 @@ package v1 import ( "net/http" - "code.vikunja.io/api/pkg/db" - "code.vikunja.io/api/pkg/models" + "code.vikunja.io/api/pkg/routes/api/shared" "code.vikunja.io/api/pkg/user" "github.com/labstack/echo/v5" ) @@ -49,22 +48,7 @@ func UserResetPassword(c *echo.Context) error { return err } - s := db.NewSession() - defer s.Close() - - userID, err := user.ResetPassword(s, &pwReset) - if err != nil { - _ = s.Rollback() - return err - } - - if err := models.DeleteAllUserSessions(s, userID); err != nil { - _ = s.Rollback() - return err - } - - if err := s.Commit(); err != nil { - _ = s.Rollback() + if err := shared.ResetPassword(&pwReset); err != nil { return err } @@ -93,17 +77,7 @@ func UserRequestResetPasswordToken(c *echo.Context) error { return echo.NewHTTPError(http.StatusBadRequest, err.Error()).Wrap(err) } - s := db.NewSession() - defer s.Close() - - err := user.RequestUserPasswordResetTokenByEmail(s, &pwTokenReset) - if err != nil { - _ = s.Rollback() - return err - } - - if err := s.Commit(); err != nil { - _ = s.Rollback() + if err := shared.RequestPasswordResetToken(&pwTokenReset); err != nil { return err } diff --git a/pkg/routes/api/v1/user_register.go b/pkg/routes/api/v1/user_register.go index 96a9c02d9..e9a90dc2f 100644 --- a/pkg/routes/api/v1/user_register.go +++ b/pkg/routes/api/v1/user_register.go @@ -21,18 +21,15 @@ import ( "net/http" "code.vikunja.io/api/pkg/config" - "code.vikunja.io/api/pkg/db" "code.vikunja.io/api/pkg/models" - "code.vikunja.io/api/pkg/user" + "code.vikunja.io/api/pkg/routes/api/shared" "github.com/labstack/echo/v5" ) -type UserRegister struct { - // The language of the new user. Must be a valid IETF BCP 47 language code and exist in Vikunja. - Language string `json:"language" valid:"language"` - user.APIUserPassword -} +// UserRegister is an alias for the shared registration input, kept so the v1 +// swagger annotation and any existing imports still resolve. +type UserRegister = shared.UserRegister // RegisterUser is the register handler // @Summary Register @@ -66,22 +63,8 @@ func RegisterUser(c *echo.Context) error { return c.JSON(http.StatusBadRequest, models.Message{Message: "No or invalid user model provided."}) } - s := db.NewSession() - defer s.Close() - - newUser, err := models.RegisterUser(s, &user.User{ - Username: userIn.Username, - Password: userIn.Password, - Email: userIn.Email, - Language: userIn.Language, - }) + newUser, err := shared.RegisterUser(c.Request().Context(), userIn) if err != nil { - _ = s.Rollback() - return err - } - - if err := s.Commit(); err != nil { - _ = s.Rollback() return err } diff --git a/pkg/routes/api/v1/user_settings.go b/pkg/routes/api/v1/user_settings.go index 2efa9c0f0..049330411 100644 --- a/pkg/routes/api/v1/user_settings.go +++ b/pkg/routes/api/v1/user_settings.go @@ -26,7 +26,6 @@ import ( "code.vikunja.io/api/pkg/db" "code.vikunja.io/api/pkg/models" - "code.vikunja.io/api/pkg/modules/avatar" user2 "code.vikunja.io/api/pkg/user" ) @@ -36,35 +35,6 @@ type UserAvatarProvider struct { AvatarProvider string `json:"avatar_provider"` } -// UserSettings holds all user settings -type UserSettings struct { - // The new name of the current user. - Name string `json:"name"` - // If enabled, sends email reminders of tasks to the user. - EmailRemindersEnabled bool `json:"email_reminders_enabled"` - // If true, this user can be found by their name or parts of it when searching for it. - DiscoverableByName bool `json:"discoverable_by_name"` - // If true, the user can be found when searching for their exact email. - DiscoverableByEmail bool `json:"discoverable_by_email"` - // If enabled, the user will get an email for their overdue tasks each morning. - OverdueTasksRemindersEnabled bool `json:"overdue_tasks_reminders_enabled"` - // The time when the daily summary of overdue tasks will be sent via email. - OverdueTasksRemindersTime string `json:"overdue_tasks_reminders_time" valid:"time,required"` - // If a task is created without a specified project this value should be used. Applies - // to tasks made directly in API and from clients. - DefaultProjectID int64 `json:"default_project_id"` - // The day when the week starts for this user. 0 = sunday, 1 = monday, etc. - WeekStart int `json:"week_start" valid:"range(0|6)"` - // The user's language - Language string `json:"language"` - // The user's time zone. Used to send task reminders in the time zone of the user. - Timezone string `json:"timezone"` - // Additional settings only used by the frontend - FrontendSettings interface{} `json:"frontend_settings"` - // Additional settings links as provided by openid - ExtraSettingsLinks map[string]any `json:"extra_settings_links"` -} - // GetUserAvatarProvider returns the currently set user avatar // @Summary Return user avatar setting // @Description Returns the current user's avatar setting. @@ -135,29 +105,16 @@ func ChangeUserAvatarProvider(c *echo.Context) error { return err } - oldProvider := user.AvatarProvider - - user.AvatarProvider = uap.AvatarProvider - - _, err = user2.UpdateUser(s, user, false) - if err != nil { + if err := models.UpdateUserAvatarProvider(s, user, uap.AvatarProvider); err != nil { _ = s.Rollback() return err } - if user.AvatarProvider == "initials" { - avatar.FlushAllCaches(user) - } - if err := s.Commit(); err != nil { _ = s.Rollback() return err } - if oldProvider != user.AvatarProvider { - avatar.FlushAllCaches(user) - } - return c.JSON(http.StatusOK, &models.Message{Message: "Avatar was changed successfully."}) } @@ -167,13 +124,13 @@ func ChangeUserAvatarProvider(c *echo.Context) error { // @Accept json // @Produce json // @Security JWTKeyAuth -// @Param avatar body UserSettings true "The updated user settings" +// @Param avatar body models.UserGeneralSettings true "The updated user settings" // @Success 200 {object} models.Message // @Failure 400 {object} web.HTTPError "Something's invalid." // @Failure 500 {object} models.Message "Internal server error." // @Router /user/settings/general [post] func UpdateGeneralUserSettings(c *echo.Context) error { - us := &UserSettings{} + us := &models.UserGeneralSettings{} err := c.Bind(us) if err != nil { var he *echo.HTTPError @@ -202,22 +159,7 @@ func UpdateGeneralUserSettings(c *echo.Context) error { return err } - invalidateAvatar := user.AvatarProvider == "initials" && user.Name != us.Name - - user.Name = us.Name - user.EmailRemindersEnabled = us.EmailRemindersEnabled - user.DiscoverableByEmail = us.DiscoverableByEmail - user.DiscoverableByName = us.DiscoverableByName - user.OverdueTasksRemindersEnabled = us.OverdueTasksRemindersEnabled - user.DefaultProjectID = us.DefaultProjectID - user.WeekStart = us.WeekStart - user.Language = us.Language - user.Timezone = us.Timezone - user.OverdueTasksRemindersTime = us.OverdueTasksRemindersTime - user.FrontendSettings = us.FrontendSettings - - _, err = user2.UpdateUser(s, user, true) - if err != nil { + if err := models.UpdateUserGeneralSettings(s, user, us); err != nil { _ = s.Rollback() return err } @@ -227,10 +169,6 @@ func UpdateGeneralUserSettings(c *echo.Context) error { return err } - if invalidateAvatar { - avatar.FlushAllCaches(user) - } - return c.JSON(http.StatusOK, &models.Message{Message: "The settings were updated successfully."}) } diff --git a/pkg/routes/api/v1/user_show.go b/pkg/routes/api/v1/user_show.go index d5a391267..655b0fb5c 100644 --- a/pkg/routes/api/v1/user_show.go +++ b/pkg/routes/api/v1/user_show.go @@ -20,7 +20,7 @@ import ( "net/http" "time" - "code.vikunja.io/api/pkg/modules/auth/openid" + "code.vikunja.io/api/pkg/routes/api/shared" "code.vikunja.io/api/pkg/user" @@ -34,11 +34,11 @@ import ( type UserWithSettings struct { user.User - Settings *UserSettings `json:"settings"` - DeletionScheduledAt time.Time `json:"deletion_scheduled_at"` - IsLocalUser bool `json:"is_local_user"` - AuthProvider string `json:"auth_provider"` - IsAdmin bool `json:"is_admin"` + Settings *models.UserGeneralSettings `json:"settings"` + DeletionScheduledAt time.Time `json:"deletion_scheduled_at"` + IsLocalUser bool `json:"is_local_user"` + AuthProvider string `json:"auth_provider"` + IsAdmin bool `json:"is_admin"` } // UserShow gets all information about the current user @@ -67,57 +67,17 @@ func UserShow(c *echo.Context) error { } us := &UserWithSettings{ - User: *u, - Settings: &UserSettings{ - Name: u.Name, - EmailRemindersEnabled: u.EmailRemindersEnabled, - DiscoverableByName: u.DiscoverableByName, - DiscoverableByEmail: u.DiscoverableByEmail, - OverdueTasksRemindersEnabled: u.OverdueTasksRemindersEnabled, - DefaultProjectID: u.DefaultProjectID, - WeekStart: u.WeekStart, - Language: u.Language, - Timezone: u.Timezone, - OverdueTasksRemindersTime: u.OverdueTasksRemindersTime, - FrontendSettings: u.FrontendSettings, - ExtraSettingsLinks: u.ExtraSettingsLinks, - }, + User: *u, + Settings: models.NewUserGeneralSettings(u), DeletionScheduledAt: u.DeletionScheduledAt, IsLocalUser: u.Issuer == user.IssuerLocal, IsAdmin: u.IsAdmin, } - us.AuthProvider, err = getAuthProviderName(u) + us.AuthProvider, err = shared.GetAuthProviderName(u) if err != nil { return err } return c.JSON(http.StatusOK, us) } - -func getAuthProviderName(u *user.User) (name string, err error) { - if u.Issuer == user.IssuerLocal { - return "local", nil - } - - if u.Issuer == user.IssuerLDAP { - return "ldap", nil - } - - providers, err := openid.GetAllProviders() - if err != nil { - return "", err - } - - for _, provider := range providers { - issuerURL, err := provider.Issuer() - if err != nil { - return "", err - } - if issuerURL == u.Issuer { - return provider.Name, nil - } - } - - return -} diff --git a/pkg/routes/api/v1/user_totp.go b/pkg/routes/api/v1/user_totp.go index e3c0ae076..a3c9fc8c4 100644 --- a/pkg/routes/api/v1/user_totp.go +++ b/pkg/routes/api/v1/user_totp.go @@ -17,10 +17,8 @@ package v1 import ( - "bytes" "errors" "fmt" - "image/jpeg" "net/http" "code.vikunja.io/api/pkg/db" @@ -202,14 +200,7 @@ func UserTOTPQrCode(c *echo.Context) error { } defer s.Close() - qrcode, err := user.GetTOTPQrCodeForUser(s, u) - if err != nil { - _ = s.Rollback() - return err - } - - buff := &bytes.Buffer{} - err = jpeg.Encode(buff, qrcode, nil) + qrcode, err := user.GetTOTPQrCodeAsJpegForUser(s, u) if err != nil { _ = s.Rollback() return err @@ -220,7 +211,7 @@ func UserTOTPQrCode(c *echo.Context) error { return err } - return c.Blob(http.StatusOK, "image/jpeg", buff.Bytes()) + return c.Blob(http.StatusOK, "image/jpeg", qrcode) } // UserTOTP returns the current totp implementation if any is enabled. diff --git a/pkg/routes/api/v1/user_update_email.go b/pkg/routes/api/v1/user_update_email.go index ea1077075..7e03b250a 100644 --- a/pkg/routes/api/v1/user_update_email.go +++ b/pkg/routes/api/v1/user_update_email.go @@ -62,17 +62,7 @@ func UpdateUserEmail(c *echo.Context) (err error) { s := db.NewSession() defer s.Close() - emailUpdate.User, err = user.CheckUserCredentials(s, &user.Login{ - Username: emailUpdate.User.Username, - Password: emailUpdate.Password, - }) - if err != nil { - _ = s.Rollback() - return err - } - - err = user.UpdateEmail(s, emailUpdate) - if err != nil { + if err := user.ChangeUserEmail(c.Request().Context(), s, emailUpdate.User, emailUpdate.Password, emailUpdate.NewEmail); err != nil { _ = s.Rollback() return err } diff --git a/pkg/routes/api/v1/user_update_password.go b/pkg/routes/api/v1/user_update_password.go index 0172a21ec..87b372aff 100644 --- a/pkg/routes/api/v1/user_update_password.go +++ b/pkg/routes/api/v1/user_update_password.go @@ -63,26 +63,10 @@ func UserChangePassword(c *echo.Context) error { return err } - if newPW.OldPassword == "" { - return user.ErrEmptyOldPassword{} - } - s := db.NewSession() defer s.Close() - // Check the current password - if _, err = user.CheckUserCredentials(s, &user.Login{Username: doer.Username, Password: newPW.OldPassword}); err != nil { - _ = s.Rollback() - return err - } - - // Update the password - if err = user.UpdateUserPassword(s, doer, newPW.NewPassword); err != nil { - _ = s.Rollback() - return err - } - - if err := models.DeleteAllUserSessions(s, doer.ID); err != nil { + if err := models.ChangeUserPassword(c.Request().Context(), s, doer, newPW.OldPassword, newPW.NewPassword); err != nil { _ = s.Rollback() return err } diff --git a/pkg/routes/api/v2/admin_projects.go b/pkg/routes/api/v2/admin_projects.go new file mode 100644 index 000000000..9d424eb6e --- /dev/null +++ b/pkg/routes/api/v2/admin_projects.go @@ -0,0 +1,109 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package apiv2 + +import ( + "context" + "fmt" + "net/http" + + "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/models" + "code.vikunja.io/api/pkg/web/handler" + + "github.com/danielgtaylor/huma/v2" +) + +type adminProjectListBody struct { + Body Paginated[*models.Project] +} + +type adminProjectBody struct { + Body *models.Project +} + +// adminOwnerPatchBody reassigns a project's owner. owner_id is the only field; +// the regular project-update endpoint refuses owner changes. +type adminOwnerPatchBody struct { + OwnerID int64 `json:"owner_id" minimum:"1" doc:"The numeric ID of the user who should become the project's owner."` +} + +// Permissions are enforced by the gateV2AdminRoutes path middleware, not per-handler. +func RegisterAdminProjectRoutes(api huma.API) { + tags := []string{"admin"} + + Register(api, huma.Operation{ + OperationID: "admin-projects-list", + Summary: "List all projects (admin)", + Description: "Returns every project on the instance, including archived ones and projects the caller does not own. Restricted to instance admins on a licensed instance; unlicensed or non-admin callers get a 404, making the endpoint indistinguishable from one that is not registered.", + Method: http.MethodGet, + Path: "/admin/projects", + Tags: tags, + }, adminProjectsList) + + Register(api, huma.Operation{ + OperationID: "admin-projects-patch-owner", + Summary: "Reassign a project's owner (admin)", + Description: "Reassigns a project to a new owner — the admin-only escape hatch the regular update endpoint does not allow. The new owner must be an active account that is not scheduled for deletion. Restricted to instance admins on a licensed instance.", + Method: http.MethodPatch, + Path: "/admin/projects/{id}/owner", + Tags: tags, + }, adminProjectsPatchOwner) +} + +func init() { AddRouteRegistrar(RegisterAdminProjectRoutes) } + +func adminProjectsList(ctx context.Context, in *ListParams) (*adminProjectListBody, error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + result, _, total, err := handler.DoReadAll(ctx, &models.AdminProjectList{}, a, in.Q, in.Page, in.PerPage) + if err != nil { + return nil, translateDomainError(err) + } + items, ok := result.([]*models.Project) + if !ok { + return nil, fmt.Errorf("AdminProjectList.ReadAll returned unexpected type %T (expected []*models.Project)", result) + } + return &adminProjectListBody{Body: NewPaginated(items, total, in.Page, in.PerPage)}, nil +} + +func adminProjectsPatchOwner(_ context.Context, in *struct { + ID int64 `path:"id" doc:"The numeric ID of the project."` + Body adminOwnerPatchBody +}) (*adminProjectBody, error) { + if in.ID < 1 { + return nil, translateDomainError(models.ErrProjectDoesNotExist{ID: in.ID}) + } + if in.Body.OwnerID < 1 { + return nil, translateDomainError(models.ErrInvalidData{Message: "invalid body"}) + } + + s := db.NewSession() + defer s.Close() + + p, err := models.ReassignProjectOwner(s, in.ID, in.Body.OwnerID) + if err != nil { + _ = s.Rollback() + return nil, translateDomainError(err) + } + if err := s.Commit(); err != nil { + return nil, translateDomainError(err) + } + return &adminProjectBody{Body: p}, nil +} diff --git a/pkg/routes/api/v2/admin_users.go b/pkg/routes/api/v2/admin_users.go new file mode 100644 index 000000000..2724e433c --- /dev/null +++ b/pkg/routes/api/v2/admin_users.go @@ -0,0 +1,206 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package apiv2 + +import ( + "context" + "net/http" + + "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/models" + "code.vikunja.io/api/pkg/modules/auth/openid" + "code.vikunja.io/api/pkg/routes/api/shared" + "code.vikunja.io/api/pkg/user" + + "github.com/danielgtaylor/huma/v2" + "xorm.io/xorm" +) + +type adminOverviewBody struct { + Body *models.Overview +} + +type adminUserBody struct { + Body *shared.AdminUser +} + +// adminIsAdminPatchBody uses a pointer so an omitted is_admin leaves the flag unchanged +// instead of silently demoting. +type adminIsAdminPatchBody struct { + IsAdmin *bool `json:"is_admin" doc:"New admin flag. Omitting it leaves the current value unchanged."` +} + +// adminStatusPatchBody uses a pointer so an omitted status leaves the account unchanged +// instead of silently reactivating. +type adminStatusPatchBody struct { + Status *user.Status `json:"status" doc:"New account status (0=active, 1=email-confirmation required, 2=disabled, 3=locked). Omitting it leaves the current value unchanged."` +} + +// Permissions are enforced by the gateV2AdminRoutes path middleware, not per-handler. +func RegisterAdminUserRoutes(api huma.API) { + tags := []string{"admin"} + + Register(api, huma.Operation{ + OperationID: "admin-overview", + Summary: "Admin overview", + Description: "Returns per-instance counts (users, projects, tasks, teams, shares) plus the current license snapshot. Restricted to instance admins on a licensed instance; unlicensed or non-admin callers get a 404, making the endpoint indistinguishable from one that is not registered.", + Method: http.MethodGet, + Path: "/admin/overview", + Tags: tags, + }, adminOverview) + + Register(api, huma.Operation{ + OperationID: "admin-users-create", + Summary: "Create a user (admin)", + Description: "Creates a local user account, bypassing the public-registration toggle. Honours the admin-only is_admin and skip_email_confirm fields. Restricted to instance admins on a licensed instance.", + Method: http.MethodPost, + Path: "/admin/users", + Tags: tags, + }, adminUsersCreate) + + Register(api, huma.Operation{ + OperationID: "admin-users-patch-admin", + Summary: "Promote or demote a user (admin)", + Description: "Sets a user's instance-admin flag. The body field is a pointer: omitting is_admin leaves the flag unchanged. Demoting the last remaining admin is refused with 400.", + Method: http.MethodPatch, + Path: "/admin/users/{id}/admin", + Tags: tags, + }, adminUsersPatchAdmin) + + Register(api, huma.Operation{ + OperationID: "admin-users-patch-status", + Summary: "Set a user's status (admin)", + Description: "Changes a user's account status without requiring them to log in. The body field is a pointer: omitting status leaves it unchanged. Moving the last remaining admin out of Active is refused with 400.", + Method: http.MethodPatch, + Path: "/admin/users/{id}/status", + Tags: tags, + }, adminUsersPatchStatus) + + Register(api, huma.Operation{ + OperationID: "admin-users-delete", + Summary: "Delete a user (admin)", + Description: "Deletes a user. With mode=now the user is removed immediately. With mode=scheduled (the default) the user is scheduled for deletion through the email-confirmation self-deletion flow. Deleting the last remaining admin is refused with 400.", + Method: http.MethodDelete, + Path: "/admin/users/{id}", + Tags: tags, + }, adminUsersDelete) +} + +func init() { AddRouteRegistrar(RegisterAdminUserRoutes) } + +func adminOverview(_ context.Context, _ *struct{}) (*adminOverviewBody, error) { + s := db.NewSession() + defer s.Close() + + overview, err := models.BuildOverview(s) + if err != nil { + return nil, translateDomainError(err) + } + return &adminOverviewBody{Body: overview}, nil +} + +func adminUsersCreate(_ context.Context, in *struct{ Body models.CreateUserBody }) (*adminUserBody, error) { + s := db.NewSession() + defer s.Close() + + newUser, err := models.CreateUserAsAdmin(s, &in.Body) + if err != nil { + _ = s.Rollback() + return nil, translateDomainError(err) + } + + providers, err := openid.GetAllProviders() //nolint:contextcheck // GetAllProviders reads a cached map; it takes no context, like the v1 admin handlers. + if err != nil { + return nil, translateDomainError(err) + } + return &adminUserBody{Body: shared.NewAdminUser(newUser, providers)}, nil //nolint:contextcheck // OIDC provider init deliberately uses a background context — provider lifetime exceeds the request +} + +func adminUsersPatchAdmin(_ context.Context, in *struct { + ID int64 `path:"id" doc:"The numeric ID of the user."` + Body adminIsAdminPatchBody +}) (*adminUserBody, error) { + if in.Body.IsAdmin == nil { + return nil, translateDomainError(models.ErrInvalidData{Message: "is_admin is required"}) + } + return adminCommitUser(func(s *xorm.Session) (*user.User, error) { //nolint:contextcheck // see adminCommitUser. + return models.SetUserAdminFlag(s, in.ID, *in.Body.IsAdmin) + }) +} + +func adminUsersPatchStatus(_ context.Context, in *struct { + ID int64 `path:"id" doc:"The numeric ID of the user."` + Body adminStatusPatchBody +}) (*adminUserBody, error) { + if in.Body.Status == nil { + return nil, translateDomainError(models.ErrInvalidData{Message: "status is required"}) + } + newStatus := *in.Body.Status + if newStatus < user.StatusActive || newStatus > user.StatusAccountLocked { + return nil, translateDomainError(models.ErrInvalidData{Message: "invalid status"}) + } + return adminCommitUser(func(s *xorm.Session) (*user.User, error) { //nolint:contextcheck // see adminCommitUser. + return models.SetUserStatusAsAdmin(s, in.ID, newStatus) + }) +} + +func adminUsersDelete(_ context.Context, in *struct { + ID int64 `path:"id" doc:"The numeric ID of the user."` + Mode string `query:"mode" doc:"'now' deletes immediately; 'scheduled' (the default) triggers the email-confirmation self-deletion flow."` +}) (*emptyBody, error) { + mode := in.Mode + if mode == "" { + mode = "scheduled" + } + if mode != "now" && mode != "scheduled" { + return nil, translateDomainError(models.ErrInvalidData{Message: "invalid mode, expected 'now' or 'scheduled'"}) + } + + s := db.NewSession() + defer s.Close() + if err := models.DeleteUserAsAdmin(s, in.ID, mode); err != nil { + _ = s.Rollback() + return nil, translateDomainError(err) + } + if err := s.Commit(); err != nil { + return nil, translateDomainError(err) + } + return &emptyBody{}, nil +} + +// adminCommitUser runs a user-returning admin action in its own transaction and +// renders the admin user view. The action does the load/guard/mutate against the +// session (shared with v1 via the models layer); this owns the commit and response. +func adminCommitUser(action func(s *xorm.Session) (*user.User, error)) (*adminUserBody, error) { + s := db.NewSession() + defer s.Close() + + target, err := action(s) + if err != nil { + _ = s.Rollback() + return nil, translateDomainError(err) + } + if err := s.Commit(); err != nil { + return nil, translateDomainError(err) + } + + providers, err := openid.GetAllProviders() //nolint:contextcheck // GetAllProviders reads a cached map; it takes no context, like the v1 admin handlers. + if err != nil { + return nil, translateDomainError(err) + } + return &adminUserBody{Body: shared.NewAdminUser(target, providers)}, nil +} diff --git a/pkg/routes/api/v2/api_tokens.go b/pkg/routes/api/v2/api_tokens.go new file mode 100644 index 000000000..9a70f360d --- /dev/null +++ b/pkg/routes/api/v2/api_tokens.go @@ -0,0 +1,110 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package apiv2 + +import ( + "context" + "fmt" + "net/http" + + "code.vikunja.io/api/pkg/models" + "code.vikunja.io/api/pkg/web/handler" + + "github.com/danielgtaylor/huma/v2" +) + +type apiTokenListBody struct { + Body Paginated[*models.APIToken] +} + +func RegisterAPITokenRoutes(api huma.API) { + tags := []string{"tokens"} + + Register(api, huma.Operation{ + OperationID: "tokens-list", + Summary: "List api tokens", + Description: "Returns the api tokens owned by the authenticated user. Pass owner_id to list a bot's tokens instead — only bots owned by the caller are allowed.", + Method: http.MethodGet, + Path: "/tokens", + Tags: tags, + }, apiTokensList) + + Register(api, huma.Operation{ + OperationID: "tokens-create", + Summary: "Create an api token", + Description: "Creates an api token for the authenticated user, or for a bot they own when owner_id is set. The cleartext token is returned once in this response and is never readable again.", + Method: http.MethodPost, + Path: "/tokens", + Tags: tags, + }, apiTokensCreate) + + Register(api, huma.Operation{ + OperationID: "tokens-delete", + Summary: "Delete an api token", + Description: "Deletes an api token. The caller may delete their own tokens and tokens belonging to bots they own.", + Method: http.MethodDelete, + Path: "/tokens/{id}", + Tags: tags, + }, apiTokensDelete) +} + +func init() { AddRouteRegistrar(RegisterAPITokenRoutes) } + +func apiTokensList(ctx context.Context, in *struct { + ListParams + OwnerID int64 `query:"owner_id" doc:"List tokens of this owner instead of the caller. Must be a bot owned by the authenticated user."` +}) (*apiTokenListBody, error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + result, _, total, err := handler.DoReadAll(ctx, &models.APIToken{OwnerID: in.OwnerID}, a, in.Q, in.Page, in.PerPage) + if err != nil { + return nil, translateDomainError(err) + } + items, ok := result.([]*models.APIToken) + if !ok { + return nil, fmt.Errorf("tokens.ReadAll returned unexpected type %T (expected []*models.APIToken)", result) + } + return &apiTokenListBody{Body: NewPaginated(items, total, in.Page, in.PerPage)}, nil +} + +func apiTokensCreate(ctx context.Context, in *struct { + Body models.APIToken +}) (*singleBody[models.APIToken], error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + if err := handler.DoCreate(ctx, &in.Body, a); err != nil { + return nil, translateDomainError(err) + } + return &singleBody[models.APIToken]{Body: &in.Body}, nil +} + +func apiTokensDelete(ctx context.Context, in *struct { + ID int64 `path:"id"` +}) (*emptyBody, error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + if err := handler.DoDelete(ctx, &models.APIToken{ID: in.ID}, a); err != nil { + return nil, translateDomainError(err) + } + return &emptyBody{}, nil +} diff --git a/pkg/routes/api/v2/auth_login.go b/pkg/routes/api/v2/auth_login.go new file mode 100644 index 000000000..519fcaef1 --- /dev/null +++ b/pkg/routes/api/v2/auth_login.go @@ -0,0 +1,132 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package apiv2 + +import ( + "context" + "net/http" + + "code.vikunja.io/api/pkg/modules/auth" + "code.vikunja.io/api/pkg/modules/humaecho5" + "code.vikunja.io/api/pkg/routes/api/shared" + "code.vikunja.io/api/pkg/user" + + "github.com/danielgtaylor/huma/v2" + "github.com/labstack/echo/v5" +) + +// authTokenBody wraps the issued user JWT. The token is inlined rather than +// embedding auth.Token because Huma derives schema names from the bare Go type +// name and a top-level auth.Token body would collide with user.Token (the +// caldav-token schema, also named "Token"). The refresh token is delivered out +// of band as an HttpOnly cookie, so it is intentionally absent from the schema. +type authTokenBody struct { + // Cache-Control: no-store keeps the access token out of any shared cache. + CacheControl string `header:"Cache-Control"` + Body struct { + Token string `json:"token" readOnly:"true" doc:"The short-lived JWT auth token. Send it as a bearer token on subsequent requests."` + } +} + +// logoutBody confirms a successful logout. +type logoutBody struct { + Body struct { + Message string `json:"message" readOnly:"true" doc:"A human-readable confirmation message."` + OIDCLogoutURL string `json:"oidc_logout_url,omitempty" readOnly:"true" doc:"RP-Initiated Logout URL to redirect to for OpenID Connect sessions; empty otherwise."` + } +} + +func init() { AddRouteRegistrar(RegisterLoginRoutes) } + +// RegisterLoginRoutes wires the local/LDAP login and logout endpoints. Login is +// always registered (LDAP-only deployments still log in here); logout inherits +// the global JWT auth. +func RegisterLoginRoutes(api huma.API) { + tags := []string{"auth"} + + Register(api, huma.Operation{ + OperationID: "auth-login", + Summary: "Login", + Description: "Logs a user in with username and password (and a TOTP passcode when 2FA is enabled), returning a short-lived JWT. A long-lived refresh token is set as an HttpOnly cookie scoped to the refresh endpoint.", + Method: http.MethodPost, + Path: "/login", + DefaultStatus: http.StatusOK, + Tags: tags, + Security: publicSecurity, + }, authLogin) + + Register(api, huma.Operation{ + OperationID: "auth-logout", + Summary: "Logout", + Description: "Destroys the current session server-side and clears the refresh-token cookie. A no-op for API tokens and link shares, which carry no session.", + Method: http.MethodPost, + Path: "/logout", + DefaultStatus: http.StatusOK, + Tags: tags, + }, authLogout) +} + +func authLogin(ctx context.Context, in *struct{ Body user.Login }) (*authTokenBody, error) { + u, err := shared.AuthenticateUserCredentials(ctx, &in.Body) + if err != nil { + return nil, translateDomainError(err) + } + + deviceInfo, ipAddress := requestClientInfo(ctx) + token, err := auth.IssueUserToken(ctx, u, deviceInfo, ipAddress, in.Body.LongToken, nil) + if err != nil { + return nil, translateDomainError(err) + } + + if ec := echoContextFromCtx(ctx); ec != nil { + auth.WriteUserAuthCookies(ec, token) + } + + out := &authTokenBody{CacheControl: "no-store"} + out.Body.Token = token.AccessToken + return out, nil +} + +func authLogout(ctx context.Context, _ *struct{}) (*logoutBody, error) { + var sid string + if ec := echoContextFromCtx(ctx); ec != nil { + auth.ClearRefreshTokenCookie(ec) + sid = auth.SessionIDFromContext(ec) + } + + oidcLogoutURL, err := shared.LogoutSession(sid) //nolint:contextcheck // OIDC provider discovery resolves from a cached, context-less map and runs on its own background context, like the OIDC callback. + if err != nil { + return nil, translateDomainError(err) + } + + out := &logoutBody{} + out.Body.Message = "Successfully logged out." + out.Body.OIDCLogoutURL = oidcLogoutURL + return out, nil +} + +// echoContextFromCtx pulls the underlying *echo.Context off a Huma request +// context so a handler can set cookies and headers the OpenAPI schema does not +// model (the refresh-token cookie). Returns nil when the context carries no echo +// context (it always does under the humaecho5 adapter). +func echoContextFromCtx(ctx context.Context) *echo.Context { + ec, ok := ctx.Value(humaecho5.EchoContextKey).(*echo.Context) + if !ok || ec == nil { + return nil + } + return ec +} diff --git a/pkg/routes/api/v2/auth_openid.go b/pkg/routes/api/v2/auth_openid.go new file mode 100644 index 000000000..5e029a184 --- /dev/null +++ b/pkg/routes/api/v2/auth_openid.go @@ -0,0 +1,93 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package apiv2 + +import ( + "context" + "errors" + "net/http" + + "code.vikunja.io/api/pkg/config" + "code.vikunja.io/api/pkg/models" + "code.vikunja.io/api/pkg/modules/auth" + "code.vikunja.io/api/pkg/modules/auth/openid" + + "github.com/danielgtaylor/huma/v2" +) + +func init() { AddRouteRegistrar(RegisterOpenIDRoutes) } + +// RegisterOpenIDRoutes wires the OpenID Connect callback endpoint. It is only +// registered when OpenID is enabled; individual providers are still resolved per +// request, so an unknown provider key 404s even when others are configured. +func RegisterOpenIDRoutes(api huma.API) { + if !config.AuthOpenIDEnabled.GetBool() { + return + } + + Register(api, huma.Operation{ + OperationID: "auth-openid-callback", + Summary: "Authenticate with OpenID Connect", + Description: "Exchanges the authorization code returned by an OpenID Connect provider for a Vikunja JWT, creating or updating the matching user. A long-lived refresh token is set as an HttpOnly cookie. When the resolved user has 2FA enabled, the call returns 412 and must be retried with totp_passcode set.", + Method: http.MethodPost, + Path: "/auth/openid/{provider}/callback", + DefaultStatus: http.StatusOK, + Tags: []string{"auth"}, + Security: publicSecurity, + }, authOpenIDCallback) +} + +func authOpenIDCallback(ctx context.Context, in *struct { + Provider string `path:"provider" doc:"The OpenID Connect provider key as returned by the /info endpoint."` + Body openid.Callback `doc:"The provider callback, carrying the authorization code."` +}) (*authTokenBody, error) { + u, oidcData, err := openid.AuthenticateCallback(ctx, &in.Body, in.Provider) //nolint:contextcheck // resolves providers from a cached, context-less map and runs OIDC discovery on its own background context, like the v1 callback. + if err != nil { + return nil, translateOpenIDError(err) + } + + deviceInfo, ipAddress := requestClientInfo(ctx) + // OIDC logins are not "remember me" sessions; v1 always issues a short one. + token, err := auth.IssueUserToken(ctx, u, deviceInfo, ipAddress, false, oidcData) + if err != nil { + return nil, translateDomainError(err) + } + + if ec := echoContextFromCtx(ctx); ec != nil { + auth.WriteUserAuthCookies(ec, token) + } + + out := &authTokenBody{CacheControl: "no-store"} + out.Body.Token = token.AccessToken + return out, nil +} + +// translateOpenIDError maps OIDC callback errors to RFC 9457 responses. +// ErrOpenIDBadRequestWithDetails carries no HTTP semantics of its own (v1 renders +// it with a bespoke {message, details} body), so v2 maps it to a 400 with the +// provider detail attached as a structured error detail rather than porting the +// bespoke shape. Everything else flows through translateDomainError. +func translateOpenIDError(err error) error { + var detailedErr *models.ErrOpenIDBadRequestWithDetails + if errors.As(err, &detailedErr) { + return huma.Error400BadRequest(detailedErr.Message, &huma.ErrorDetail{ + Message: "The identity provider rejected the request.", + Value: detailedErr.Details, + }) + } + return translateDomainError(err) +} diff --git a/pkg/routes/api/v2/auth_public.go b/pkg/routes/api/v2/auth_public.go new file mode 100644 index 000000000..c41fb162d --- /dev/null +++ b/pkg/routes/api/v2/auth_public.go @@ -0,0 +1,183 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package apiv2 + +import ( + "context" + "net/http" + + "code.vikunja.io/api/pkg/config" + "code.vikunja.io/api/pkg/routes/api/shared" + "code.vikunja.io/api/pkg/user" + + "github.com/danielgtaylor/huma/v2" +) + +// publicSecurity is the empty security requirement that opts an operation out of +// the globally-applied JWT/API-token auth. The matching Echo path must also be +// listed in unauthenticatedAPIPaths so the token middleware lets it through. +var publicSecurity = []map[string][]string{} + +// registerUserBody is the response wrapper for the registration endpoint. +type registerUserBody struct { + Body *user.User +} + +// messageBody carries a human-readable confirmation for endpoints that report +// success without returning a resource (password reset, email confirm). +type messageBody struct { + Body struct { + Message string `json:"message" readOnly:"true" doc:"A human-readable confirmation message."` + } +} + +// linkShareTokenBody wraps the issued link-share auth token and its share. +type linkShareTokenBody struct { + Body *shared.LinkShareToken +} + +func init() { AddRouteRegistrar(RegisterPublicAuthRoutes) } + +// RegisterPublicAuthRoutes wires the unauthenticated local-account flows +// (registration, password reset, email confirmation) and the link-share auth +// endpoint. The local-account flows mirror v1 by only registering when local +// auth is enabled; the link-share endpoint follows ServiceEnableLinkSharing. +func RegisterPublicAuthRoutes(api huma.API) { + if config.AuthLocalEnabled.GetBool() { + registerLocalAuthRoutes(api) + } + + if config.ServiceEnableLinkSharing.GetBool() { + Register(api, huma.Operation{ + OperationID: "auth-link-share", + Summary: "Get an auth token for a link share", + Description: "Exchanges a link share's public hash (and password, for password-protected shares) for a JWT auth token scoped to the shared project.", + Method: http.MethodPost, + Path: "/shares/{share}/auth", + DefaultStatus: http.StatusOK, + Tags: []string{"sharing"}, + Security: publicSecurity, + }, authLinkShare) + } +} + +func registerLocalAuthRoutes(api huma.API) { + authTags := []string{"auth"} + + // Registration is its own static-config gate on top of local auth: when it + // is disabled the route simply isn't registered (a request then 404s as an + // unknown route), rather than registering it and rejecting per request. + if config.ServiceEnableRegistration.GetBool() { + Register(api, huma.Operation{ + OperationID: "auth-register", + Summary: "Register", + Description: "Creates a new local user account.", + Method: http.MethodPost, + Path: "/register", + Tags: authTags, + Security: publicSecurity, + }, authRegister) + } + + Register(api, huma.Operation{ + OperationID: "auth-password-token", + Summary: "Request a password reset token", + Description: "Requests a token to reset the password for the account with the given email. The token is sent to that email; the response is the same whether or not an account exists.", + Method: http.MethodPost, + Path: "/user/password/token", + DefaultStatus: http.StatusOK, + Tags: authTags, + Security: publicSecurity, + }, authRequestPasswordToken) + + Register(api, huma.Operation{ + OperationID: "auth-password-reset", + Summary: "Reset a password", + Description: "Sets a new password using a previously issued reset token. All of the user's existing sessions are invalidated.", + Method: http.MethodPost, + Path: "/user/password/reset", + DefaultStatus: http.StatusOK, + Tags: authTags, + Security: publicSecurity, + }, authResetPassword) + + Register(api, huma.Operation{ + OperationID: "auth-confirm-email", + Summary: "Confirm an email address", + Description: "Confirms the email address of a newly registered user using the token sent to that email.", + Method: http.MethodPost, + Path: "/user/confirm", + DefaultStatus: http.StatusOK, + Tags: authTags, + Security: publicSecurity, + }, authConfirmEmail) +} + +func authRegister(ctx context.Context, in *struct{ Body shared.UserRegister }) (*registerUserBody, error) { + newUser, err := shared.RegisterUser(ctx, &in.Body) + if err != nil { + return nil, translateDomainError(err) + } + return ®isterUserBody{Body: newUser}, nil +} + +func authRequestPasswordToken(_ context.Context, in *struct{ Body user.PasswordTokenRequest }) (*messageBody, error) { + if err := shared.RequestPasswordResetToken(&in.Body); err != nil { + return nil, translateDomainError(err) + } + out := &messageBody{} + out.Body.Message = "Token was sent." + return out, nil +} + +func authResetPassword(_ context.Context, in *struct{ Body user.PasswordReset }) (*messageBody, error) { + if err := shared.ResetPassword(&in.Body); err != nil { + return nil, translateDomainError(err) + } + out := &messageBody{} + out.Body.Message = "The password was updated successfully." + return out, nil +} + +func authConfirmEmail(_ context.Context, in *struct{ Body user.EmailConfirm }) (*messageBody, error) { + if err := shared.ConfirmEmail(&in.Body); err != nil { + return nil, translateDomainError(err) + } + out := &messageBody{} + out.Body.Message = "The email was confirmed successfully." + return out, nil +} + +func authLinkShare(_ context.Context, in *struct { + Share string `path:"share" doc:"The public hash of the link share."` + // Pointer so the body is optional: shares without a password are + // authenticated with no body at all. + Body *struct { + Password string `json:"password" doc:"The password for password-protected link shares. Ignored for shares without a password."` + } +}) (*linkShareTokenBody, error) { + var password string + if in.Body != nil { + password = in.Body.Password + } + + token, err := shared.AuthenticateLinkShare(in.Share, password) + if err != nil { + return nil, translateDomainError(err) + } + return &linkShareTokenBody{Body: token}, nil +} diff --git a/pkg/routes/api/v2/auth_refresh.go b/pkg/routes/api/v2/auth_refresh.go new file mode 100644 index 000000000..d264da7c1 --- /dev/null +++ b/pkg/routes/api/v2/auth_refresh.go @@ -0,0 +1,75 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package apiv2 + +import ( + "context" + "net/http" + + "code.vikunja.io/api/pkg/config" + "code.vikunja.io/api/pkg/modules/auth" + user2 "code.vikunja.io/api/pkg/user" + + "github.com/danielgtaylor/huma/v2" +) + +func init() { AddRouteRegistrar(RegisterRefreshTokenRoutes) } + +// RegisterRefreshTokenRoutes wires the refresh-token endpoint. It authenticates +// via the HttpOnly refresh cookie rather than a JWT, so it is a public operation. +func RegisterRefreshTokenRoutes(api huma.API) { + Register(api, huma.Operation{ + OperationID: "auth-refresh-token", + Summary: "Refresh user token", + Description: "Exchanges the refresh-token cookie for a new short-lived JWT. The refresh token is rotated on every call, so the previous one stops working. A new HttpOnly refresh cookie is set on the response.", + Method: http.MethodPost, + Path: "/user/token/refresh", + DefaultStatus: http.StatusOK, + Tags: []string{"auth"}, + Security: publicSecurity, + }, authRefreshToken) +} + +func authRefreshToken(ctx context.Context, _ *struct{}) (*authTokenBody, error) { + ec := echoContextFromCtx(ctx) + if ec == nil { + return nil, huma.Error401Unauthorized("No refresh token provided.") + } + + cookie, err := ec.Cookie(auth.RefreshTokenCookieName) + if err != nil || cookie.Value == "" { + return nil, huma.Error401Unauthorized("No refresh token provided.") + } + + result, err := auth.RefreshSession(cookie.Value) + if err != nil { + if user2.IsErrUserStatusError(err) { + auth.ClearRefreshTokenCookie(ec) + } + return nil, translateDomainError(err) + } + + cookieMaxAge := int(config.ServiceJWTTTL.GetInt64()) + if result.IsLongSession { + cookieMaxAge = int(config.ServiceJWTTTLLong.GetInt64()) + } + auth.SetRefreshTokenCookie(ec, result.NewRefreshToken, cookieMaxAge) + + out := &authTokenBody{CacheControl: "no-store"} + out.Body.Token = result.AccessToken + return out, nil +} diff --git a/pkg/routes/api/v2/avatar.go b/pkg/routes/api/v2/avatar.go new file mode 100644 index 000000000..a26c68e56 --- /dev/null +++ b/pkg/routes/api/v2/avatar.go @@ -0,0 +1,89 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package apiv2 + +import ( + "context" + "net/http" + + "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/modules/avatar" + + "github.com/danielgtaylor/huma/v2" +) + +// avatarResponse carries raw image bytes plus the runtime Content-Type. Huma writes +// the []byte Body straight to the wire; the header:"Content-Type" field overrides +// content negotiation so the provider's actual mime type reaches the client. +type avatarResponse struct { + ContentType string `header:"Content-Type"` + Body []byte +} + +type avatarInput struct { + Username string `path:"username" doc:"The username of the user whose avatar to fetch."` + Size int64 `query:"size" default:"250" minimum:"1" doc:"Desired avatar edge length in pixels. Clamped to the server's configured maximum if larger; providers that render fixed-size images may ignore it."` +} + +// RegisterAvatarRoutes wires the avatar binary endpoint onto the Huma API. +func RegisterAvatarRoutes(api huma.API) { + Register(api, huma.Operation{ + OperationID: "avatar-get", + Summary: "Get a user's avatar", + Description: "Returns the user's avatar as raw image bytes. The Content-Type is chosen at " + + "runtime by the user's avatar provider (gravatar, initials, marble, an uploaded image, " + + "or the default placeholder). An unknown username is not an error — the default " + + "placeholder avatar is returned. Authenticated like every other endpoint.", + Method: http.MethodGet, + Path: "/avatar/{username}", + Tags: []string{"user"}, + // Spell out the binary response; a bare []byte Body would otherwise be + // modeled as a base64 JSON string instead of binary image data. + Responses: map[string]*huma.Response{ + "200": { + Description: "The avatar image bytes. The Content-Type header carries the actual image type.", + Content: map[string]*huma.MediaType{ + "application/octet-stream": { + Schema: &huma.Schema{Type: huma.TypeString, Format: "binary"}, + }, + }, + }, + }, + }, avatarGet) +} + +func init() { AddRouteRegistrar(RegisterAvatarRoutes) } + +func avatarGet(ctx context.Context, in *avatarInput) (*avatarResponse, error) { + // Authenticated but no per-user check — any authenticated caller may view any + // avatar (matching v1); authFromCtx just surfaces a clean 401 if auth is missing. + if _, err := authFromCtx(ctx); err != nil { + return nil, err + } + + s := db.NewSession() + defer s.Close() + + // Avatar resolution (user lookup, provider selection, size clamping) is + // shared with the v1 handler; only the transport differs. + a, mimeType, err := avatar.GetAvatarForUsername(s, in.Username, in.Size) + if err != nil { + return nil, translateDomainError(err) + } + + return &avatarResponse{ContentType: mimeType, Body: a}, nil +} diff --git a/pkg/routes/api/v2/avatar_upload.go b/pkg/routes/api/v2/avatar_upload.go new file mode 100644 index 000000000..e8ca6e024 --- /dev/null +++ b/pkg/routes/api/v2/avatar_upload.go @@ -0,0 +1,102 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package apiv2 + +import ( + "context" + "errors" + "net/http" + + "code.vikunja.io/api/pkg/config" + "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/models" + "code.vikunja.io/api/pkg/modules/avatar" + "code.vikunja.io/api/pkg/user" + + "github.com/danielgtaylor/huma/v2" +) + +type avatarUploadInput struct { + // Allow-list mirrors the image formats avatar.StoreUploadedAvatar can actually decode (the decoders registered process-wide by the imaging package: png, jpeg, gif, tiff, bmp); octet-stream covers programmatic clients. Huma's MimeTypeValidator rejects the part pre-handler, so anything advertised here must also pass the byte-level image check in avatar.StoreUploadedAvatar. Formats without a registered decoder (e.g. svg, webp) are intentionally excluded. + RawBody huma.MultipartFormFiles[struct { + Avatar huma.FormFile `form:"avatar" contentType:"image/png,image/jpeg,image/gif,image/tiff,image/bmp,application/octet-stream" required:"true" doc:"The avatar image to upload. Must be a decodable raster image (PNG, JPEG, GIF, TIFF or BMP); it is resized server-side and re-encoded as PNG."` + }] +} + +type avatarUploadBody struct { + Body *models.Message +} + +func RegisterAvatarUploadRoutes(api huma.API) { + tags := []string{"user"} + + Register(api, huma.Operation{ + OperationID: "user-avatar-upload", + Summary: "Upload your avatar", + Description: "Uploads an image as the authenticated user's avatar and switches their avatar provider to \"upload\". The image is validated to be an image, resized server-side, and stored as PNG. Replaces any previously uploaded avatar (idempotent replace, hence PUT).", + Method: http.MethodPut, + Path: "/user/settings/avatar", + Tags: tags, + // +2 MB mirrors Echo's global BodyLimit overhead so a max-sized file isn't rejected by multipart boundary/header bytes. + // #nosec G115 - configured value won't exceed int64 max in practice. + MaxBodyBytes: (int64(config.GetMaxFileSizeInMBytes()) + 2) * 1024 * 1024, + DefaultStatus: http.StatusOK, + }, avatarUpload) +} + +func init() { AddRouteRegistrar(RegisterAvatarUploadRoutes) } + +func avatarUpload(ctx context.Context, in *avatarUploadInput) (*avatarUploadBody, error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + + // Only real users have avatars; a link share cannot upload one. + authUser, is := a.(*user.User) + if !is { + return nil, huma.Error403Forbidden("only users can upload an avatar") + } + + s := db.NewSession() + defer s.Close() + + // Re-fetch the full user: the auth user from the JWT claims is partial. + u, err := user.GetUserByID(s, authUser.ID) + if err != nil { + _ = s.Rollback() + return nil, translateDomainError(err) + } + + src := in.RawBody.Data().Avatar + defer func() { _ = src.Close() }() + + if err := avatar.StoreUploadedAvatar(s, u, src); err != nil { + _ = s.Rollback() + if errors.Is(err, avatar.ErrNotAnImage) { + return nil, huma.Error400BadRequest("Uploaded file is no image.") + } + return nil, translateDomainError(err) + } + + if err := s.Commit(); err != nil { + _ = s.Rollback() + return nil, translateDomainError(err) + } + + return &avatarUploadBody{Body: &models.Message{Message: "Avatar was uploaded successfully."}}, nil +} diff --git a/pkg/routes/api/v2/backgrounds.go b/pkg/routes/api/v2/backgrounds.go new file mode 100644 index 000000000..4d7b5befe --- /dev/null +++ b/pkg/routes/api/v2/backgrounds.go @@ -0,0 +1,407 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package apiv2 + +import ( + "context" + "io" + "net/http" + + "code.vikunja.io/api/pkg/config" + "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/models" + "code.vikunja.io/api/pkg/modules/background" + backgroundHandler "code.vikunja.io/api/pkg/modules/background/handler" + "code.vikunja.io/api/pkg/modules/background/unsplash" + "code.vikunja.io/api/pkg/modules/humaecho5" + webfiles "code.vikunja.io/api/pkg/web/files" + + "github.com/danielgtaylor/huma/v2" +) + +type backgroundSearchBody struct { + Body Paginated[*background.Image] +} + +// RegisterBackgroundRoutes wires the project-background actions onto the Huma +// API. BackgroundsEnabled / BackgroundsUnsplashEnabled are static config, so the +// registrar early-returns instead of gating per request. +func RegisterBackgroundRoutes(api huma.API) { + if !config.BackgroundsEnabled.GetBool() { + return + } + + tags := []string{"project"} + + Register(api, huma.Operation{ + OperationID: "projects-background-delete", + Summary: "Remove a project background", + Description: "Removes a project's background, whichever provider set it. Succeeds even when the project has no background. Requires write access to the project. Returns the updated project.", + Method: http.MethodDelete, + Path: "/projects/{project}/background", + // Return the updated project with 200, not the wrapper's DELETE default 204. + DefaultStatus: http.StatusOK, + Tags: tags, + }, backgroundRemove) + + Register(api, huma.Operation{ + OperationID: "projects-background-get", + Summary: "Get a project background", + Description: "Streams a project's background image, whichever provider set it. Requires read access to the project. Always served as image/jpeg with a revalidation Last-Modified header, so a conditional If-Modified-Since request gets a 304. Returns 404 when the project has no background.", + Method: http.MethodGet, + Path: "/projects/{project}/background", + Tags: tags, + // Spell out the binary response; the default would be modeled as JSON. + Responses: map[string]*huma.Response{ + "200": { + Description: "The project background as a jpeg image.", + Content: map[string]*huma.MediaType{ + "image/jpeg": { + Schema: &huma.Schema{Type: huma.TypeString, Format: "binary"}, + }, + }, + }, + }, + }, backgroundGet) + + if config.BackgroundsUploadEnabled.GetBool() { + Register(api, huma.Operation{ + OperationID: "projects-background-upload", + Summary: "Upload a project background", + Description: "Uploads an image via multipart/form-data under the \"background\" field and sets it as the project's background. Requires write access to the project. The image is resized server-side and stored as JPEG; it replaces any previous background (idempotent replace, hence PUT). Returns the updated project.", + Method: http.MethodPut, + Path: "/projects/{project}/backgrounds/upload", + // Return the updated project with 200, the natural code for an idempotent PUT. + DefaultStatus: http.StatusOK, + Tags: tags, + // +2 MB mirrors Echo's global BodyLimit overhead so a max-sized file isn't rejected by multipart boundary/header bytes. + // #nosec G115 - configured value won't exceed int64 max in practice. + MaxBodyBytes: (int64(config.GetMaxFileSizeInMBytes()) + 2) * 1024 * 1024, + }, backgroundUpload) + } + + if config.BackgroundsUnsplashEnabled.GetBool() { + Register(api, huma.Operation{ + OperationID: "backgrounds-unsplash-search", + Summary: "Search Unsplash backgrounds", + Description: "Searches Unsplash for background images. With an empty query it returns the featured wallpaper collection. Results are paginated by Unsplash; total counts are not available.", + Method: http.MethodGet, + Path: "/backgrounds/unsplash/search", + Tags: tags, + }, backgroundUnsplashSearch) + + Register(api, huma.Operation{ + OperationID: "projects-background-unsplash-set", + Summary: "Set an Unsplash image as project background", + Description: "Sets a previously searched Unsplash image as the project's background, identified by the image id from the search results. Requires write access to the project.", + Method: http.MethodPut, + Path: "/projects/{project}/backgrounds/unsplash", + Tags: tags, + }, backgroundUnsplashSet) + + unsplashProxyResponses := map[string]*huma.Response{ + "200": { + Description: "The proxied Unsplash image as a jpeg image.", + Content: map[string]*huma.MediaType{ + "image/jpeg": { + Schema: &huma.Schema{Type: huma.TypeString, Format: "binary"}, + }, + }, + }, + } + + Register(api, huma.Operation{ + OperationID: "backgrounds-unsplash-image", + Summary: "Proxy a full-resolution Unsplash image", + Description: "Proxies the full-resolution Unsplash image for the given image id through Vikunja, so the client never contacts Unsplash directly (privacy). Vikunja fires the required Unsplash pingback as a side effect. Returns 404 if the image does not exist.", + Method: http.MethodGet, + Path: "/backgrounds/unsplash/images/{image}", + Tags: tags, + // Spell out the binary response; the default would be modeled as JSON. + Responses: unsplashProxyResponses, + }, backgroundUnsplashImage) + + Register(api, huma.Operation{ + OperationID: "backgrounds-unsplash-thumb", + Summary: "Proxy an Unsplash image thumbnail", + Description: "Proxies a thumbnail (max width 200px) of the Unsplash image for the given image id through Vikunja, so the client never contacts Unsplash directly (privacy). Vikunja fires the required Unsplash pingback as a side effect. Returns 404 if the image does not exist.", + Method: http.MethodGet, + Path: "/backgrounds/unsplash/images/{image}/thumb", + Tags: tags, + // Spell out the binary response; the default would be modeled as JSON. + Responses: unsplashProxyResponses, + }, backgroundUnsplashThumb) + } +} + +func init() { AddRouteRegistrar(RegisterBackgroundRoutes) } + +func backgroundUnsplashSearch(ctx context.Context, in *struct { + Q string `query:"q" doc:"Search query; empty returns the featured wallpaper collection."` + Page int64 `query:"page" default:"1" minimum:"1" doc:"1-based page number."` +}) (*backgroundSearchBody, error) { + if _, err := authFromCtx(ctx); err != nil { + return nil, err + } + + page := in.Page + if page < 1 { + page = 1 + } + + s := db.NewSession() + defer s.Close() + + p := &unsplash.Provider{} + result, err := p.Search(s, in.Q, page) + if err != nil { + _ = s.Rollback() + return nil, translateDomainError(err) + } + if err := s.Commit(); err != nil { + return nil, translateDomainError(err) + } + + // Unsplash paginates server-side and p.Search discards the total, so the + // envelope's total is just this page's length (v1 returned a bare array). + return &backgroundSearchBody{Body: NewPaginated(result, int64(len(result)), int(page), len(result))}, nil +} + +func backgroundUnsplashSet(ctx context.Context, in *struct { + ProjectID int64 `path:"project"` + Body background.Image +}) (*singleBody[models.Project], error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + + s := db.NewSession() + defer s.Close() + + project := &models.Project{ID: in.ProjectID} + can, err := project.CanUpdate(s, a) + if err != nil { + _ = s.Rollback() + return nil, translateDomainError(err) + } + if !can { + _ = s.Rollback() + return nil, huma.Error403Forbidden("forbidden") + } + project, err = models.GetProjectSimpleByID(s, in.ProjectID) + if err != nil { + _ = s.Rollback() + return nil, translateDomainError(err) + } + + p := &unsplash.Provider{} + if err := p.Set(s, &in.Body, project, a); err != nil { + _ = s.Rollback() + return nil, translateDomainError(err) + } + if err := project.ReadOne(s, a); err != nil { + _ = s.Rollback() + return nil, translateDomainError(err) + } + if err := s.Commit(); err != nil { + return nil, translateDomainError(err) + } + + return &singleBody[models.Project]{Body: project}, nil +} + +type backgroundUploadInput struct { + ProjectID int64 `path:"project" doc:"The id of the project to set the background on."` + // Allow-list mirrors the formats background uploads can actually be decoded as + // (handler.ValidateAndSaveBackgroundUpload's allowedImageMimes); octet-stream covers + // programmatic clients. Huma's MimeTypeValidator rejects the part pre-handler, so the + // byte-level image check in the shared function is the real gate. + RawBody huma.MultipartFormFiles[struct { + Background huma.FormFile `form:"background" contentType:"image/jpeg,image/png,image/gif,image/bmp,image/tiff,image/webp,application/octet-stream" required:"true" doc:"The background image to upload. Must be a decodable raster image (JPEG, PNG, GIF, BMP, TIFF or WebP); it is resized server-side and re-encoded as JPEG."` + }] +} + +// backgroundUpload owns auth, the session and the permission check because there is +// no handler.Do* for multipart uploads (see the api-v2-routes skill's "Non-CRUDable +// / custom routes" section). It shares its body with v1 via +// handler.ValidateAndSaveBackgroundUpload. +func backgroundUpload(ctx context.Context, in *backgroundUploadInput) (*singleBody[models.Project], error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + + s := db.NewSession() + defer s.Close() + + project := &models.Project{ID: in.ProjectID} + can, err := project.CanUpdate(s, a) + if err != nil { + _ = s.Rollback() + return nil, translateDomainError(err) + } + if !can { + _ = s.Rollback() + return nil, huma.Error403Forbidden("forbidden") + } + project, err = models.GetProjectSimpleByID(s, in.ProjectID) + if err != nil { + _ = s.Rollback() + return nil, translateDomainError(err) + } + + file := in.RawBody.Data().Background + defer func() { _ = file.Close() }() + + if err := backgroundHandler.ValidateAndSaveBackgroundUpload(s, a, project, file, file.Filename, uint64(file.Size)); err != nil { + _ = s.Rollback() + if backgroundHandler.IsErrFileIsNoImage(err) || backgroundHandler.IsErrFileUnsupportedImageFormat(err) { + return nil, huma.Error400BadRequest(err.Error()) + } + return nil, translateDomainError(err) + } + + if err := s.Commit(); err != nil { + return nil, translateDomainError(err) + } + + return &singleBody[models.Project]{Body: project}, nil +} + +// backgroundGet owns auth, the session and the permission check because there is no +// handler.Do* for a file body. CanRead hydrates the project (including its +// BackgroundFileID), which the shared loader then needs. +func backgroundGet(ctx context.Context, in *struct { + ProjectID int64 `path:"project" doc:"The id of the project whose background to fetch."` +}) (*huma.StreamResponse, error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + + s := db.NewSession() + defer s.Close() + + project := &models.Project{ID: in.ProjectID} + can, _, err := project.CanRead(s, a) + if err != nil { + _ = s.Rollback() + return nil, translateDomainError(err) + } + if !can { + _ = s.Rollback() + return nil, huma.Error403Forbidden("forbidden") + } + + bgFile, stat, err := backgroundHandler.LoadProjectBackgroundForDownload(s, project) + if err != nil { + _ = s.Rollback() + return nil, translateDomainError(err) + } + + // The file reader comes from object storage, not the DB session, so it stays + // valid after the commit; the StreamResponse callback runs after this returns. + if err := s.Commit(); err != nil { + _ = s.Rollback() + // The stream callback (which closes the reader) won't run on this error path. + _ = bgFile.File.Close() + return nil, translateDomainError(err) + } + + return &huma.StreamResponse{Body: func(hctx huma.Context) { + defer func() { _ = bgFile.File.Close() }() + c := humaecho5.Unwrap(hctx) + webfiles.WriteProjectBackground((*c).Response(), (*c).Request(), bgFile, stat) + }}, nil +} + +func backgroundUnsplashImage(ctx context.Context, in *struct { + ImageID string `path:"image" doc:"The Unsplash image id, from a prior background search."` +}) (*huma.StreamResponse, error) { + if _, err := authFromCtx(ctx); err != nil { + return nil, err + } + body, err := unsplash.FetchUnsplashImageByID(in.ImageID) + if err != nil { + return nil, translateDomainError(err) + } + return streamUnsplashProxy(body), nil +} + +func backgroundUnsplashThumb(ctx context.Context, in *struct { + ImageID string `path:"image" doc:"The Unsplash image id, from a prior background search."` +}) (*huma.StreamResponse, error) { + if _, err := authFromCtx(ctx); err != nil { + return nil, err + } + body, err := unsplash.FetchUnsplashThumbByID(in.ImageID) + if err != nil { + return nil, translateDomainError(err) + } + return streamUnsplashProxy(body), nil +} + +// streamUnsplashProxy copies the open upstream Unsplash body to the response as +// image/jpeg and closes it, mirroring v1's c.Stream. +func streamUnsplashProxy(body io.ReadCloser) *huma.StreamResponse { + return &huma.StreamResponse{Body: func(hctx huma.Context) { + defer func() { _ = body.Close() }() + c := humaecho5.Unwrap(hctx) + resp := (*c).Response() + resp.Header().Set("Content-Type", "image/jpg") + resp.WriteHeader(http.StatusOK) + _, _ = io.Copy(resp, body) + }} +} + +func backgroundRemove(ctx context.Context, in *struct { + ProjectID int64 `path:"project"` +}) (*singleBody[models.Project], error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + + s := db.NewSession() + defer s.Close() + + project := &models.Project{ID: in.ProjectID} + can, err := project.CanUpdate(s, a) + if err != nil { + _ = s.Rollback() + return nil, translateDomainError(err) + } + if !can { + _ = s.Rollback() + return nil, huma.Error403Forbidden("forbidden") + } + + if err := project.DeleteBackgroundFileIfExists(s); err != nil { + _ = s.Rollback() + return nil, translateDomainError(err) + } + if err := models.ClearProjectBackground(s, project.ID); err != nil { + _ = s.Rollback() + return nil, translateDomainError(err) + } + if err := s.Commit(); err != nil { + return nil, translateDomainError(err) + } + + return &singleBody[models.Project]{Body: project}, nil +} diff --git a/pkg/routes/api/v2/bot_users.go b/pkg/routes/api/v2/bot_users.go new file mode 100644 index 000000000..0af344019 --- /dev/null +++ b/pkg/routes/api/v2/bot_users.go @@ -0,0 +1,169 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package apiv2 + +import ( + "context" + "fmt" + "net/http" + + "code.vikunja.io/api/pkg/models" + "code.vikunja.io/api/pkg/web/handler" + + "github.com/danielgtaylor/huma/v2" + "github.com/danielgtaylor/huma/v2/conditional" +) + +type botUserListBody struct { + Body Paginated[*models.BotUser] +} + +// RegisterBotUserRoutes wires bot-user CRUD onto the Huma API. +func RegisterBotUserRoutes(api huma.API) { + tags := []string{"bots"} + + Register(api, huma.Operation{ + OperationID: "bots-list", + Summary: "List bot users", + Description: "Returns only the bot users owned by the authenticated user. Bots owned by anyone else are never listed.", + Method: http.MethodGet, + Path: "/user/bots", + Tags: tags, + }, botUsersList) + + Register(api, huma.Operation{ + OperationID: "bots-read", + Summary: "Get a bot user", + Description: "Returns a single bot user. Only the owner may read it; otherwise the request is refused. Sends an ETag; pass it as If-None-Match on a later read to get a 304 Not Modified.", + Method: http.MethodGet, + Path: "/user/bots/{bot}", + Tags: tags, + }, botUsersRead) + + Register(api, huma.Operation{ + OperationID: "bots-create", + Summary: "Create a bot user", + Description: "Creates a bot user owned by the authenticated user. The username must start with the 'bot-' prefix. Bots have no email or password and cannot create further bots. Requires a real user account — link shares cannot create bots.", + Method: http.MethodPost, + Path: "/user/bots", + Tags: tags, + }, botUsersCreate) + + Register(api, huma.Operation{ + OperationID: "bots-update", + Summary: "Update a bot user", + Description: "Updates an owned bot user's name, status, and username. Only the owner may update it. Use PATCH for a partial update.", + Method: http.MethodPut, + Path: "/user/bots/{bot}", + Tags: tags, + }, botUsersUpdate) + + Register(api, huma.Operation{ + OperationID: "bots-delete", + Summary: "Delete a bot user", + Description: "Permanently deletes an owned bot user and all data associated with it. Only the owner may delete it.", + Method: http.MethodDelete, + Path: "/user/bots/{bot}", + Tags: tags, + }, botUsersDelete) +} + +func init() { AddRouteRegistrar(RegisterBotUserRoutes) } + +func botUsersList(ctx context.Context, in *ListParams) (*botUserListBody, error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + result, _, total, err := handler.DoReadAll(ctx, &models.BotUser{}, a, in.Q, in.Page, in.PerPage) + if err != nil { + return nil, translateDomainError(err) + } + items, ok := result.([]*models.BotUser) + if !ok { + return nil, fmt.Errorf("bots.ReadAll returned unexpected type %T (expected []*models.BotUser)", result) + } + return &botUserListBody{Body: NewPaginated(items, total, in.Page, in.PerPage)}, nil +} + +type botUserReadBody struct { + models.BotUser + MaxPermission models.Permission `json:"max_permission" readOnly:"true" doc:"The maximum permission the requesting user has on this bot user (0=read, 1=read/write, 2=admin)."` +} + +func botUsersRead(ctx context.Context, in *struct { + ID int64 `path:"bot"` + conditional.Params +}) (*singleReadBody[botUserReadBody], error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + bot := &models.BotUser{} + bot.ID = in.ID + maxPermission, err := handler.DoReadOne(ctx, bot, a) + if err != nil { + return nil, translateDomainError(err) + } + body := &botUserReadBody{BotUser: *bot, MaxPermission: models.Permission(maxPermission)} + return conditionalReadResponse(&in.Params, body, bot.Updated, maxPermission) +} + +func botUsersCreate(ctx context.Context, in *struct { + Body models.BotUser +}) (*singleBody[models.BotUser], error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + if err := handler.DoCreate(ctx, &in.Body, a); err != nil { + return nil, translateDomainError(err) + } + return &singleBody[models.BotUser]{Body: &in.Body}, nil +} + +// Body matches the read shape so AutoPatch's GET→PUT echo of max_permission validates. +func botUsersUpdate(ctx context.Context, in *struct { + ID int64 `path:"bot"` + Body botUserReadBody +}) (*singleBody[models.BotUser], error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + bot := &in.Body.BotUser + bot.ID = in.ID // URL wins over body + if err := handler.DoUpdate(ctx, bot, a); err != nil { + return nil, translateDomainError(err) + } + return &singleBody[models.BotUser]{Body: bot}, nil +} + +func botUsersDelete(ctx context.Context, in *struct { + ID int64 `path:"bot"` +}) (*emptyBody, error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + bot := &models.BotUser{} + bot.ID = in.ID + if err := handler.DoDelete(ctx, bot, a); err != nil { + return nil, translateDomainError(err) + } + return &emptyBody{}, nil +} diff --git a/pkg/routes/api/v2/bulk_task.go b/pkg/routes/api/v2/bulk_task.go new file mode 100644 index 000000000..be5e4b31e --- /dev/null +++ b/pkg/routes/api/v2/bulk_task.go @@ -0,0 +1,61 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package apiv2 + +import ( + "context" + "net/http" + + "code.vikunja.io/api/pkg/models" + "code.vikunja.io/api/pkg/web/handler" + + "github.com/danielgtaylor/huma/v2" +) + +// RegisterBulkTaskRoutes wires the bulk task update action onto the Huma API. +// +// BulkTask is a CRUDable Update, so the handler reuses handler.DoUpdate; its +// CanUpdate fans the write check out across every project the involved tasks +// belong to, so a single project the user can't write to rejects the request. +func RegisterBulkTaskRoutes(api huma.API) { + tags := []string{"tasks"} + + Register(api, huma.Operation{ + OperationID: "tasks-bulk-update", + Summary: "Bulk update tasks", + Description: "Applies the fields named in `fields` from `values` to every task in `task_ids`. The user needs write access to every project the involved tasks belong to; if write is missing on even one, the whole request is rejected and nothing is changed. Returns the updated tasks.", + Method: http.MethodPut, + Path: "/tasks/bulk", + Tags: tags, + }, tasksBulkUpdate) +} + +func init() { AddRouteRegistrar(RegisterBulkTaskRoutes) } + +func tasksBulkUpdate(ctx context.Context, in *struct { + Body models.BulkTask +}) (*singleBody[models.BulkTask], error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + bt := &in.Body + if err := handler.DoUpdate(ctx, bt, a); err != nil { + return nil, translateDomainError(err) + } + return &singleBody[models.BulkTask]{Body: bt}, nil +} diff --git a/pkg/routes/api/v2/caldav_tokens.go b/pkg/routes/api/v2/caldav_tokens.go new file mode 100644 index 000000000..b8cfbc19c --- /dev/null +++ b/pkg/routes/api/v2/caldav_tokens.go @@ -0,0 +1,121 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package apiv2 + +import ( + "context" + "net/http" + + "code.vikunja.io/api/pkg/user" + + "github.com/danielgtaylor/huma/v2" +) + +// CalDAV tokens are scoped to the authenticated user, not a CRUDable resource: +// there is no per-token Can* method, so these handlers own their own user lookup +// (user.GetFromAuth refuses link shares) and session/commit lives in the user package. + +type caldavTokenListBody struct { + Body Paginated[*user.Token] +} + +type caldavTokenBody struct { + Body *user.Token +} + +// RegisterCalDAVTokenRoutes wires the current user's CalDAV token operations onto the Huma API. +func RegisterCalDAVTokenRoutes(api huma.API) { + tags := []string{"user"} + + Register(api, huma.Operation{ + OperationID: "caldav-tokens-create", + Summary: "Generate a CalDAV token", + Description: "Generates a CalDAV token for the authenticated user. The clear-text token is returned only in this response and can never be retrieved again. Link shares cannot have CalDAV tokens.", + Method: http.MethodPost, + Path: "/user/settings/token/caldav", + Tags: tags, + }, caldavTokensCreate) + + Register(api, huma.Operation{ + OperationID: "caldav-tokens-list", + Summary: "List CalDAV tokens", + Description: "Returns the authenticated user's CalDAV tokens. Only the id and creation date are returned — never the token value, which is shown once on creation.", + Method: http.MethodGet, + Path: "/user/settings/token/caldav", + Tags: tags, + }, caldavTokensList) + + Register(api, huma.Operation{ + OperationID: "caldav-tokens-delete", + Summary: "Delete a CalDAV token", + Description: "Deletes one of the authenticated user's CalDAV tokens by id. Tokens of other users are out of scope and cannot be deleted.", + Method: http.MethodDelete, + Path: "/user/settings/token/caldav/{id}", + Tags: tags, + }, caldavTokensDelete) +} + +func init() { AddRouteRegistrar(RegisterCalDAVTokenRoutes) } + +func caldavTokensCreate(ctx context.Context, _ *struct{}) (*caldavTokenBody, error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + u, err := user.GetFromAuth(a) + if err != nil { + return nil, translateDomainError(err) + } + token, err := user.GenerateNewCaldavToken(u) + if err != nil { + return nil, translateDomainError(err) + } + return &caldavTokenBody{Body: token}, nil +} + +func caldavTokensList(ctx context.Context, in *ListParams) (*caldavTokenListBody, error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + u, err := user.GetFromAuth(a) + if err != nil { + return nil, translateDomainError(err) + } + tokens, err := user.GetCaldavTokens(u) + if err != nil { + return nil, translateDomainError(err) + } + return &caldavTokenListBody{Body: NewPaginated(tokens, int64(len(tokens)), in.Page, in.PerPage)}, nil +} + +func caldavTokensDelete(ctx context.Context, in *struct { + ID int64 `path:"id" doc:"The numeric id of the CalDAV token to delete."` +}) (*emptyBody, error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + u, err := user.GetFromAuth(a) + if err != nil { + return nil, translateDomainError(err) + } + if err := user.DeleteCaldavTokenByID(u, in.ID); err != nil { + return nil, translateDomainError(err) + } + return &emptyBody{}, nil +} diff --git a/pkg/routes/api/v2/docs.go b/pkg/routes/api/v2/docs.go new file mode 100644 index 000000000..c2f15e53c --- /dev/null +++ b/pkg/routes/api/v2/docs.go @@ -0,0 +1,40 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package apiv2 + +import ( + _ "embed" + "net/http" + + "github.com/labstack/echo/v5" +) + +//go:embed scalar/scalar.html +var scalarHTML string + +//go:embed scalar/scalar.standalone.js +var scalarJS []byte + +// ScalarUI serves the Scalar API reference HTML; assets ship locally to avoid a CDN dependency. +func ScalarUI(c *echo.Context) error { + return c.HTML(http.StatusOK, scalarHTML) +} + +// ScalarJS serves the embedded Scalar standalone JS bundle. +func ScalarJS(c *echo.Context) error { + return c.Blob(http.StatusOK, echo.MIMEApplicationJavaScript, scalarJS) +} diff --git a/pkg/routes/api/v2/errors.go b/pkg/routes/api/v2/errors.go new file mode 100644 index 000000000..24b73a19d --- /dev/null +++ b/pkg/routes/api/v2/errors.go @@ -0,0 +1,165 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package apiv2 + +import ( + "context" + "errors" + "net/http" + "strings" + + "code.vikunja.io/api/pkg/log" + "code.vikunja.io/api/pkg/models" + "code.vikunja.io/api/pkg/modules/auth" + "code.vikunja.io/api/pkg/web" + + "github.com/danielgtaylor/huma/v2" + "github.com/labstack/echo/v5" +) + +// authFromCtx retrieves the authed user from a Huma handler context, +// surfacing lookup failures as 401 instead of falling through to 500. +func authFromCtx(ctx context.Context) (web.Auth, error) { + a, err := auth.GetAuthFromContext(ctx) + if err != nil { + // The underlying error can carry internal adapter/config detail + // (e.g. a missing Echo context — a programming error, since the + // token middleware authenticates before the handler runs). Log it + // and return a generic 401 so nothing internal leaks to clients. + log.Errorf("v2: could not resolve auth from context: %s", err) + return nil, huma.Error401Unauthorized("invalid or missing authentication") + } + return a, nil +} + +// translateDomainError maps a Vikunja domain error (web.HTTPErrorProcessor) +// onto Huma's status-error type so the response carries the right code +// and an RFC 9457 body. Errors without HTTP semantics fall through, which +// Huma treats as 500. +func translateDomainError(err error) error { + if err == nil { + return nil + } + var hp web.HTTPErrorProcessor + if errors.As(err, &hp) { + details := hp.HTTPError() + msg := details.Message + if msg == "" { + msg = err.Error() + } + se := huma.NewError(details.HTTPCode, msg) + // Preserve Vikunja's numeric domain error code (the value the + // error docs key off) on the problem+json body. v1 exposes it as + // `code`; without this v2 clients always read 0. + if vm, ok := se.(*vikunjaErrorModel); ok { + vm.Code = details.Code + } + return se + } + // v2 maps validation failures to 422 (not v1's 412) so a govalidator failure + // looks identical to Huma's own schema validation. ValidationHTTPError isn't an + // HTTPErrorProcessor (the embedded field shadows the method), so it lands here. + var ve models.ValidationHTTPError + if errors.As(err, &ve) { + se := huma.NewError(http.StatusUnprocessableEntity, ve.Error(), invalidFieldDetails(ve.InvalidFields)...) + if vm, ok := se.(*vikunjaErrorModel); ok { + vm.Code = ve.GetCode() + } + return se + } + // Shared transport-agnostic cores (e.g. auth.RefreshSession) signal HTTP + // semantics with *echo.HTTPError. v1 lets echo's error handler render it; + // without this it would fall through as a 500 on v2. + var he *echo.HTTPError + if errors.As(err, &he) { + msg := he.Message + if msg == "" { + msg = http.StatusText(he.Code) + } + return huma.NewError(he.Code, msg) + } + return err +} + +// invalidFieldDetails turns ValidationHTTPError's invalid_fields into RFC 9457 +// error details. Entries come in two shapes — govalidator's "field: message" and +// model call sites' bare field names — and both must yield a Location. +func invalidFieldDetails(fields []string) []error { + details := make([]error, 0, len(fields)) + for _, f := range fields { + name, msg, ok := strings.Cut(f, ": ") + if !ok { + msg = "Invalid data" + } + details = append(details, &huma.ErrorDetail{Location: "body." + name, Message: msg}) + } + return details +} + +// vikunjaErrorModel extends Huma's RFC 9457 body with Vikunja's numeric +// domain error code, preserving the v1 error-code contract on v2. Wired in +// as the global error type via the huma.NewError override in init(). +type vikunjaErrorModel struct { + huma.ErrorModel + Code int `json:"code,omitempty" readOnly:"true" doc:"Vikunja numeric error code; see https://vikunja.io/docs/errors/"` +} + +func init() { + // Replace Huma's default error constructor so both the generated + // OpenAPI schema and runtime responses use vikunjaErrorModel. Huma + // derives the error-response schema from NewError(0, "") at register + // time and routes runtime errors through the same constructor, so the + // `code` field stays consistent between spec and wire. + huma.NewError = func(status int, msg string, errs ...error) huma.StatusError { + details := make([]*huma.ErrorDetail, 0, len(errs)) + for _, e := range errs { + if e == nil { + continue + } + if d, ok := e.(huma.ErrorDetailer); ok { + details = append(details, d.ErrorDetail()) + } else { + details = append(details, &huma.ErrorDetail{Message: e.Error()}) + } + } + return &vikunjaErrorModel{ErrorModel: huma.ErrorModel{ + Status: status, + Title: http.StatusText(status), + Detail: msg, + Errors: details, + }} + } + + // Strip internal detail from server errors. Huma's handler-error path + // wraps a raw error as NewErrorWithContext(ctx, 500, "unexpected error + // occurred", err) and — because the humaecho5 adapter writes the + // response itself — bypasses Vikunja's CreateHTTPErrorHandler, which for + // v1 returns a generic 500 with no detail. Without this override a raw + // DB/driver error (SQL, table, column names) would leak into the + // problem+json `errors[]`. Log the real cause, return a generic body. + huma.NewErrorWithContext = func(_ huma.Context, status int, msg string, errs ...error) huma.StatusError { + if status >= 500 { + for _, e := range errs { + if e != nil { + log.Errorf("v2: internal server error: %s", e) + } + } + errs = nil + } + return huma.NewError(status, msg, errs...) + } +} diff --git a/pkg/routes/api/v2/errors_test.go b/pkg/routes/api/v2/errors_test.go new file mode 100644 index 000000000..def0c5c53 --- /dev/null +++ b/pkg/routes/api/v2/errors_test.go @@ -0,0 +1,61 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package apiv2 + +import ( + "errors" + "os" + "testing" + + "code.vikunja.io/api/pkg/log" + + "github.com/danielgtaylor/huma/v2" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestMain(m *testing.M) { + // The NewErrorWithContext override logs server errors; initialise a + // logger so that path doesn't nil-panic in the bare test binary. + log.InitLogger() + os.Exit(m.Run()) +} + +// TestNewErrorWithContext_StripsServerErrorDetail guards against leaking +// internal error detail (raw DB/driver messages, etc.) in v2 5xx responses. +// Huma's handler-error path funnels raw errors through NewErrorWithContext +// at status 500; the override must drop the detail there while keeping it +// for client (4xx) errors. Mirrors v1's generic-500 behaviour. +func TestNewErrorWithContext_StripsServerErrorDetail(t *testing.T) { + secret := errors.New(`pq: relation "labels" does not exist`) + + t.Run("500 drops the wrapped detail", func(t *testing.T) { + se := huma.NewErrorWithContext(nil, 500, "unexpected error occurred", secret) + vm, ok := se.(*vikunjaErrorModel) + require.True(t, ok) + assert.Empty(t, vm.Errors, "server errors must not expose internal detail") + assert.Equal(t, "unexpected error occurred", vm.Detail) + }) + + t.Run("4xx keeps the detail", func(t *testing.T) { + se := huma.NewErrorWithContext(nil, 422, "validation failed", secret) + vm, ok := se.(*vikunjaErrorModel) + require.True(t, ok) + require.Len(t, vm.Errors, 1, "client errors keep their detail") + assert.Equal(t, secret.Error(), vm.Errors[0].Message) + }) +} diff --git a/pkg/routes/api/v2/health.go b/pkg/routes/api/v2/health.go new file mode 100644 index 000000000..674dc7b85 --- /dev/null +++ b/pkg/routes/api/v2/health.go @@ -0,0 +1,61 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package apiv2 + +import ( + "context" + "net/http" + + "code.vikunja.io/api/pkg/health" + + "github.com/danielgtaylor/huma/v2" +) + +type healthBody struct { + Body struct { + Status string `json:"status" doc:"\"OK\" when the service and its dependencies are reachable." example:"OK"` + } +} + +// RegisterHealthRoutes wires the public healthcheck endpoint onto the Huma API. +func RegisterHealthRoutes(api huma.API) { + Register(api, huma.Operation{ + OperationID: "health", + Summary: "Healthcheck", + Description: "Reports whether the service and its dependencies (database) are reachable. Returns 200 with status \"OK\" when healthy, 500 otherwise. Public — no authentication required.", + Method: http.MethodGet, + Path: "/health", + Tags: []string{"service"}, + // Public: opt out of the globally-applied auth. The path is also listed + // in unauthenticatedAPIPaths so the token middleware lets it through. + Security: []map[string][]string{}, + }, healthcheck) +} + +func init() { AddRouteRegistrar(RegisterHealthRoutes) } + +func healthcheck(_ context.Context, _ *struct{}) (*healthBody, error) { + //nolint:contextcheck // health.Check is the shared v1/v2 probe; it takes no context and uses background contexts for its own pings. + if err := health.Check(); err != nil { + // Mirror v1: a failed check is an internal error; the cause is logged, + // not leaked to the client. + return nil, huma.Error500InternalServerError("Internal server error", err) + } + out := &healthBody{} + out.Body.Status = "OK" + return out, nil +} diff --git a/pkg/routes/api/v2/huma.go b/pkg/routes/api/v2/huma.go new file mode 100644 index 000000000..9c674c1a7 --- /dev/null +++ b/pkg/routes/api/v2/huma.go @@ -0,0 +1,178 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +// Package apiv2 wires Huma onto the /api/v2 Echo group. +package apiv2 + +import ( + "context" + "encoding/json" + "io" + "net/http" + "net/url" + "strings" + + "code.vikunja.io/api/pkg/config" + "code.vikunja.io/api/pkg/modules/humaecho5" + "code.vikunja.io/api/pkg/version" + + "github.com/danielgtaylor/huma/v2" + "github.com/danielgtaylor/huma/v2/autopatch" + "github.com/labstack/echo/v5" +) + +// formURLEncodedContentType is the content type the OAuth token endpoint accepts +// in addition to JSON, per RFC 6749. +const formURLEncodedContentType = "application/x-www-form-urlencoded" + +// formURLEncodedFormat lets Huma bind application/x-www-form-urlencoded request +// bodies into the same json-tagged structs it uses for JSON: the form values are +// re-marshaled to JSON and decoded via the standard path. Only string scalars +// are produced, which is all the form-encoded endpoints (OAuth token) need. +var formURLEncodedFormat = huma.Format{ + Marshal: func(io.Writer, any) error { + // Responses are always JSON; this format is request-body only. + return huma.ErrUnknownContentType + }, + Unmarshal: func(data []byte, v any) error { + values, err := url.ParseQuery(string(data)) + if err != nil { + return err + } + flat := make(map[string]string, len(values)) + for key := range values { + flat[key] = values.Get(key) + } + raw, err := json.Marshal(flat) + if err != nil { + return err + } + return json.Unmarshal(raw, v) + }, +} + +// GroupPrefix is the URL prefix the Echo group for /api/v2 is mounted at. +const GroupPrefix = "/api/v2" + +// NewAPI mounts Huma on the /api/v2 group. Per-resource Register* calls +// live in sibling files. +func NewAPI(e *echo.Echo, g *echo.Group) huma.API { + cfg := huma.DefaultConfig("Vikunja API", version.Version) + cfg.OpenAPIPath = "/openapi" + // Huma's built-in docs would load from unpkg.com — we serve Scalar locally instead. + cfg.DocsPath = "" + // Real presence/format rules live in `valid:` tags, enforced by govalidator in + // the Register wrapper; leave the schema permissive so partial updates match v1. + cfg.FieldsOptionalByDefault = true + // Accept application/x-www-form-urlencoded bodies (the OAuth token endpoint) + // alongside JSON. Copy the default map so we don't mutate the package global. + formats := make(map[string]huma.Format, len(cfg.Formats)+1) + for ct, f := range cfg.Formats { + formats[ct] = f + } + formats[formURLEncodedContentType] = formURLEncodedFormat + cfg.Formats = formats + + api := humaecho5.NewWithGroup(e, g, GroupPrefix, cfg) + oapi := api.OpenAPI() + if oapi.Components.SecuritySchemes == nil { + oapi.Components.SecuritySchemes = map[string]*huma.SecurityScheme{} + } + // v1 conflated JWTs and tk_-prefixed API tokens under JWTKeyAuth; v2 + // declares them separately so SDK generators and /api/v2/docs distinguish them. + oapi.Components.SecuritySchemes["JWTKeyAuth"] = &huma.SecurityScheme{ + Type: "http", + Scheme: "bearer", + BearerFormat: "JWT", + Description: "User session JWT issued via /api/v1/login.", + } + oapi.Components.SecuritySchemes["APITokenAuth"] = &huma.SecurityScheme{ + Type: "http", + Scheme: "bearer", + Description: "Vikunja API token (tk_ prefix) with scoped permissions. Created via /api/v1/tokens.", + } + // HTTP Basic, used only by the notifications Atom feed: feed readers can't + // carry a bearer header, so the feed accepts the API token as the Basic + // password (username = token owner). See notifications_feed.go. + oapi.Components.SecuritySchemes["BasicAuth"] = &huma.SecurityScheme{ + Type: "http", + Scheme: "basic", + Description: "HTTP Basic auth used by the notifications Atom feed: the username is the token owner and the password is a feeds-scoped Vikunja API token (tk_ prefix).", + } + // Applied globally; public endpoints (spec, docs) opt out with an empty Security list. + oapi.Security = []map[string][]string{ + {"JWTKeyAuth": {}}, + {"APITokenAuth": {}}, + } + // The relative entry MUST stay at index 0. Huma's SchemaLinkTransformer + // reads Servers[0] in three places (getAPIPrefix, addSchemaField, the + // runtime Transform fallback). With a path-bearing absolute URL at + // index 0, the runtime fallback concatenates that URL onto a ref that + // already includes /api/v2, producing a double-prefixed $schema link + // like https://host/api/v2/api/v2/schemas/Label.json. A relative URL + // at index 0 keeps the prefix in the transformer's bookkeeping while + // the absolute URL at index 1 advertises the deployment URL to SDK + // generators and docs UIs. + servers := []*huma.Server{{URL: GroupPrefix}} + if publicURL := strings.TrimRight(config.ServicePublicURL.GetString(), "/"); publicURL != "" { + servers = append(servers, &huma.Server{URL: publicURL + GroupPrefix}) + } + oapi.Servers = servers + return api +} + +// Register wraps huma.Register with verb-based DefaultStatus: POST → 201, +// DELETE → 204. Anything else (including an explicit op.DefaultStatus) is untouched. +// +// It also runs govalidator before the handler — i.e. before handler.Do*'s +// permission check — so v2 validates-then-authorizes like v1. +func Register[I, O any](api huma.API, op huma.Operation, handler func(context.Context, *I) (*O, error)) { + if op.DefaultStatus == 0 { + switch op.Method { + case http.MethodPost: + op.DefaultStatus = http.StatusCreated + case http.MethodDelete: + op.DefaultStatus = http.StatusNoContent + } + } + wrapped := func(ctx context.Context, in *I) (*O, error) { + if err := validateInputBody(in); err != nil { + return nil, translateDomainError(err) + } + return handler(ctx, in) + } + huma.Register(api, op, wrapped) +} + +// EnableAutoPatch synthesises a PATCH for every resource that already +// registered GET + PUT. Must be called AFTER all Register* calls. +func EnableAutoPatch(api huma.API) { + autopatch.AutoPatch(api) + + // AutoPatch names each synthesised PATCH after the GET operation + // ("Patch labels-read"), which reads poorly in the docs nav. Rewrite + // the summary from the sibling PUT so it reads like "Update a label + // (partial)". Only touch summaries AutoPatch generated (the "Patch " + // prefix) so a hand-registered PATCH is left alone. + for _, item := range api.OpenAPI().Paths { + if item == nil || item.Patch == nil || item.Put == nil { + continue + } + if item.Put.Summary != "" && strings.HasPrefix(item.Patch.Summary, "Patch ") { + item.Patch.Summary = item.Put.Summary + " (partial)" + } + } +} diff --git a/pkg/routes/api/v2/info.go b/pkg/routes/api/v2/info.go new file mode 100644 index 000000000..3b4256363 --- /dev/null +++ b/pkg/routes/api/v2/info.go @@ -0,0 +1,51 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package apiv2 + +import ( + "context" + "net/http" + + "code.vikunja.io/api/pkg/routes/api/shared" + + "github.com/danielgtaylor/huma/v2" +) + +type infoBody struct { + Body shared.VikunjaInfos +} + +// RegisterInfoRoutes wires the public instance-info endpoint onto the Huma API. +func RegisterInfoRoutes(api huma.API) { + Register(api, huma.Operation{ + OperationID: "info", + Summary: "Instance info", + Description: "Returns version, frontend URL, motd and the enabled features of this Vikunja instance. Public — no authentication required.", + Method: http.MethodGet, + Path: "/info", + Tags: []string{"service"}, + // Public: opt out of the globally-applied auth. The path is also listed + // in unauthenticatedAPIPaths so the token middleware lets it through. + Security: []map[string][]string{}, + }, info) +} + +func init() { AddRouteRegistrar(RegisterInfoRoutes) } + +func info(_ context.Context, _ *struct{}) (*infoBody, error) { + return &infoBody{Body: shared.BuildInfo()}, nil //nolint:contextcheck // OIDC provider init deliberately uses a background context — provider lifetime exceeds the request +} diff --git a/pkg/routes/api/v2/label_task_bulk.go b/pkg/routes/api/v2/label_task_bulk.go new file mode 100644 index 000000000..82f837540 --- /dev/null +++ b/pkg/routes/api/v2/label_task_bulk.go @@ -0,0 +1,62 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package apiv2 + +import ( + "context" + "net/http" + + "code.vikunja.io/api/pkg/models" + "code.vikunja.io/api/pkg/web/handler" + + "github.com/danielgtaylor/huma/v2" +) + +// RegisterLabelTaskBulkRoutes wires the bulk label-replacement action onto the +// Huma API. The model op is a CRUDable Create (handler.DoCreate, whose +// CanCreate enforces write access to the task), but the verb is PUT because the +// operation replaces the task's whole label set — the idempotent PUT semantics +// describe it more honestly than POST. +func RegisterLabelTaskBulkRoutes(api huma.API) { + tags := []string{"labels"} + + Register(api, huma.Operation{ + OperationID: "task-labels-bulk-replace", + Summary: "Replace all labels on a task", + Description: "Sets the task's labels to exactly the provided list: labels not in the list are removed, missing ones are added, unchanged ones are left alone. Requires write access to the task, and you must be able to see every label you attach. Returns the resulting label set.", + Method: http.MethodPut, + Path: "/tasks/{projecttask}/labels/bulk", + Tags: tags, + }, labelTasksBulkReplace) +} + +func init() { AddRouteRegistrar(RegisterLabelTaskBulkRoutes) } + +func labelTasksBulkReplace(ctx context.Context, in *struct { + TaskID int64 `path:"projecttask" doc:"The numeric id of the task whose labels to replace."` + Body models.LabelTaskBulk +}) (*singleBody[models.LabelTaskBulk], error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + in.Body.TaskID = in.TaskID // parent from the path, not the body + if err := handler.DoCreate(ctx, &in.Body, a); err != nil { + return nil, translateDomainError(err) + } + return &singleBody[models.LabelTaskBulk]{Body: &in.Body}, nil +} diff --git a/pkg/routes/api/v2/label_tasks.go b/pkg/routes/api/v2/label_tasks.go new file mode 100644 index 000000000..1ea47800a --- /dev/null +++ b/pkg/routes/api/v2/label_tasks.go @@ -0,0 +1,119 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package apiv2 + +import ( + "context" + "fmt" + "net/http" + + "code.vikunja.io/api/pkg/models" + "code.vikunja.io/api/pkg/web/handler" + + "github.com/danielgtaylor/huma/v2" +) + +// Element type is *models.LabelWithTaskID because that's what +// models.LabelTask.ReadAll returns; TaskID is json:"-", so the wire shape +// matches plain Label. +type labelTaskListBody struct { + Body Paginated[*models.LabelWithTaskID] +} + +// RegisterLabelTaskRoutes wires the nested labels-on-a-task routes onto the +// Huma API: list, attach and detach. There is no read-one or update — a +// label-task is just a relation, so it has no max_permission. +func RegisterLabelTaskRoutes(api huma.API) { + tags := []string{"labels"} + + Register(api, huma.Operation{ + OperationID: "task-labels-list", + Summary: "List the labels on a task", + Description: "Returns the labels attached to the given task, paginated. Requires read access to the task.", + Method: http.MethodGet, + Path: "/tasks/{projecttask}/labels", + Tags: tags, + }, labelTasksList) + + Register(api, huma.Operation{ + OperationID: "task-labels-create", + Summary: "Add a label to a task", + Description: "Attaches an existing label to the given task. Requires write access to the task and access to the label. Fails if the label is already on the task.", + Method: http.MethodPost, + Path: "/tasks/{projecttask}/labels", + Tags: tags, + }, labelTasksCreate) + + Register(api, huma.Operation{ + OperationID: "task-labels-delete", + Summary: "Remove a label from a task", + Description: "Detaches a label from the given task. Requires write access to the task.", + Method: http.MethodDelete, + Path: "/tasks/{projecttask}/labels/{label}", + Tags: tags, + }, labelTasksDelete) +} + +func init() { AddRouteRegistrar(RegisterLabelTaskRoutes) } + +func labelTasksList(ctx context.Context, in *struct { + TaskID int64 `path:"projecttask"` + ListParams +}) (*labelTaskListBody, error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + result, _, total, err := handler.DoReadAll(ctx, &models.LabelTask{TaskID: in.TaskID}, a, in.Q, in.Page, in.PerPage) + if err != nil { + return nil, translateDomainError(err) + } + items, ok := result.([]*models.LabelWithTaskID) + if !ok { + return nil, fmt.Errorf("labelTasks.ReadAll returned unexpected type %T (expected []*models.LabelWithTaskID)", result) + } + return &labelTaskListBody{Body: NewPaginated(items, total, in.Page, in.PerPage)}, nil +} + +func labelTasksCreate(ctx context.Context, in *struct { + TaskID int64 `path:"projecttask"` + Body models.LabelTask +}) (*singleBody[models.LabelTask], error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + in.Body.TaskID = in.TaskID // parent from the path, not the body + if err := handler.DoCreate(ctx, &in.Body, a); err != nil { + return nil, translateDomainError(err) + } + return &singleBody[models.LabelTask]{Body: &in.Body}, nil +} + +func labelTasksDelete(ctx context.Context, in *struct { + TaskID int64 `path:"projecttask"` + LabelID int64 `path:"label"` +}) (*emptyBody, error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + if err := handler.DoDelete(ctx, &models.LabelTask{TaskID: in.TaskID, LabelID: in.LabelID}, a); err != nil { + return nil, translateDomainError(err) + } + return &emptyBody{}, nil +} diff --git a/pkg/routes/api/v2/labels.go b/pkg/routes/api/v2/labels.go new file mode 100644 index 000000000..a9576bc50 --- /dev/null +++ b/pkg/routes/api/v2/labels.go @@ -0,0 +1,169 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package apiv2 + +import ( + "context" + "fmt" + "net/http" + + "code.vikunja.io/api/pkg/models" + "code.vikunja.io/api/pkg/web/handler" + + "github.com/danielgtaylor/huma/v2" + "github.com/danielgtaylor/huma/v2/conditional" +) + +// Element type is *models.LabelWithTaskID because that's what +// models.Label.ReadAll returns; TaskID is json:"-", so the wire shape +// matches plain Label. +type labelListBody struct { + Body Paginated[*models.LabelWithTaskID] +} + +// RegisterLabelRoutes wires Label CRUD onto the Huma API. +func RegisterLabelRoutes(api huma.API) { + tags := []string{"labels"} + + Register(api, huma.Operation{ + OperationID: "labels-list", + Summary: "List labels", + Description: "Returns the labels visible to the authenticated user — their own plus any used on tasks they can access. Not a global list.", + Method: http.MethodGet, + Path: "/labels", + Tags: tags, + }, labelsList) + + Register(api, huma.Operation{ + OperationID: "labels-read", + Summary: "Get a label", + Description: "Returns a single label. Sends an ETag; pass it as If-None-Match on a later read to get a 304 Not Modified.", + Method: http.MethodGet, + Path: "/labels/{id}", + Tags: tags, + }, labelsRead) + + Register(api, huma.Operation{ + OperationID: "labels-create", + Summary: "Create a label", + Description: "Creates a label; the authenticated user becomes its owner.", + Method: http.MethodPost, + Path: "/labels", + Tags: tags, + }, labelsCreate) + + Register(api, huma.Operation{ + OperationID: "labels-update", + Summary: "Update a label", + Description: "Replaces all of a label's fields — only the owner may update it. Use PATCH for a partial update.", + Method: http.MethodPut, + Path: "/labels/{id}", + Tags: tags, + }, labelsUpdate) + + Register(api, huma.Operation{ + OperationID: "labels-delete", + Summary: "Delete a label", + Description: "Deletes a label. Only the owner may delete it.", + Method: http.MethodDelete, + Path: "/labels/{id}", + Tags: tags, + }, labelsDelete) +} + +func init() { AddRouteRegistrar(RegisterLabelRoutes) } + +func labelsList(ctx context.Context, in *ListParams) (*labelListBody, error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + result, _, total, err := handler.DoReadAll(ctx, &models.Label{}, a, in.Q, in.Page, in.PerPage) + if err != nil { + return nil, translateDomainError(err) + } + items, ok := result.([]*models.LabelWithTaskID) + if !ok { + return nil, fmt.Errorf("labels.ReadAll returned unexpected type %T (expected []*models.LabelWithTaskID)", result) + } + return &labelListBody{Body: NewPaginated(items, total, in.Page, in.PerPage)}, nil +} + +type labelReadBody struct { + models.Label + MaxPermission models.Permission `json:"max_permission" readOnly:"true" doc:"The maximum permission the requesting user has on this label (0=read, 1=read/write, 2=admin)."` +} + +func labelsRead(ctx context.Context, in *struct { + ID int64 `path:"id"` + conditional.Params +}) (*singleReadBody[labelReadBody], error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + label := &models.Label{ID: in.ID} + maxPermission, err := handler.DoReadOne(ctx, label, a) + if err != nil { + return nil, translateDomainError(err) + } + body := &labelReadBody{Label: *label, MaxPermission: models.Permission(maxPermission)} + return conditionalReadResponse(&in.Params, body, label.Updated, maxPermission) +} + +func labelsCreate(ctx context.Context, in *struct { + Body models.Label +}) (*singleBody[models.Label], error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + if err := handler.DoCreate(ctx, &in.Body, a); err != nil { + return nil, translateDomainError(err) + } + return &singleBody[models.Label]{Body: &in.Body}, nil +} + +// Body matches the read shape so AutoPatch's GET→PUT echo of max_permission validates. +func labelsUpdate(ctx context.Context, in *struct { + ID int64 `path:"id"` + Body labelReadBody +}) (*singleBody[models.Label], error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + label := &in.Body.Label + label.ID = in.ID // URL wins over body + if err := handler.DoUpdate(ctx, label, a); err != nil { + return nil, translateDomainError(err) + } + return &singleBody[models.Label]{Body: label}, nil +} + +func labelsDelete(ctx context.Context, in *struct { + ID int64 `path:"id"` +}) (*emptyBody, error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + if err := handler.DoDelete(ctx, &models.Label{ID: in.ID}, a); err != nil { + return nil, translateDomainError(err) + } + return &emptyBody{}, nil +} diff --git a/pkg/routes/api/v2/link_sharing.go b/pkg/routes/api/v2/link_sharing.go new file mode 100644 index 000000000..c4234a141 --- /dev/null +++ b/pkg/routes/api/v2/link_sharing.go @@ -0,0 +1,160 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package apiv2 + +import ( + "context" + "fmt" + "net/http" + + "code.vikunja.io/api/pkg/config" + "code.vikunja.io/api/pkg/models" + "code.vikunja.io/api/pkg/web/handler" + + "github.com/danielgtaylor/huma/v2" + "github.com/danielgtaylor/huma/v2/conditional" +) + +type linkShareListBody struct { + Body Paginated[*models.LinkSharing] +} + +// RegisterLinkSharingRoutes wires the nested LinkSharing routes onto the Huma +// API. There is no update operation — a share is created, read, listed or +// deleted, never modified in place. +// +// The feature gate is checked here, not in the central wiring: the registrar +// runs at RegisterAll time after the config has loaded, so a disabled instance +// registers no link-sharing routes at all. +func RegisterLinkSharingRoutes(api huma.API) { + if !config.ServiceEnableLinkSharing.GetBool() { + return + } + + tags := []string{"sharing"} + + Register(api, huma.Operation{ + OperationID: "shares-list", + Summary: "List the link shares of a project", + Description: "Returns the link shares of the given project, paginated. Only project admins may list them.", + Method: http.MethodGet, + Path: "/projects/{project}/shares", + Tags: tags, + }, linkSharesList) + + Register(api, huma.Operation{ + OperationID: "shares-read", + Summary: "Get a single link share of a project", + Description: "Returns one link share of a project. The share must belong to the project in the path. Sends an ETag; pass it as If-None-Match on a later read to get a 304 Not Modified.", + Method: http.MethodGet, + Path: "/projects/{project}/shares/{share}", + Tags: tags, + }, linkSharesRead) + + Register(api, huma.Operation{ + OperationID: "shares-create", + Summary: "Share a project via link", + Description: "Creates a link share for the given project. The parent project is taken from the URL, not the body, and the authenticated user becomes the sharer. Creating an admin share requires project admin; read/write shares require write access. The hash is generated by the server; a password, if set, is write-only and cannot be read back.", + Method: http.MethodPost, + Path: "/projects/{project}/shares", + Tags: tags, + }, linkSharesCreate) + + Register(api, huma.Operation{ + OperationID: "shares-delete", + Summary: "Remove a link share from a project", + Description: "Deletes a link share of a project. The share must belong to the project in the path. Requires write access to the project.", + Method: http.MethodDelete, + Path: "/projects/{project}/shares/{share}", + Tags: tags, + }, linkSharesDelete) +} + +func init() { AddRouteRegistrar(RegisterLinkSharingRoutes) } + +func linkSharesList(ctx context.Context, in *struct { + ProjectID int64 `path:"project"` + ListParams +}) (*linkShareListBody, error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + result, _, total, err := handler.DoReadAll(ctx, &models.LinkSharing{ProjectID: in.ProjectID}, a, in.Q, in.Page, in.PerPage) + if err != nil { + return nil, translateDomainError(err) + } + items, ok := result.([]*models.LinkSharing) + if !ok { + return nil, fmt.Errorf("linkShares.ReadAll returned unexpected type %T (expected []*models.LinkSharing)", result) + } + return &linkShareListBody{Body: NewPaginated(items, total, in.Page, in.PerPage)}, nil +} + +type linkShareReadBody struct { + models.LinkSharing + MaxPermission models.Permission `json:"max_permission" readOnly:"true" doc:"The maximum permission the requesting user has on this link share (0=read, 1=read/write, 2=admin)."` +} + +func linkSharesRead(ctx context.Context, in *struct { + ProjectID int64 `path:"project"` + ID int64 `path:"share"` + conditional.Params +}) (*singleReadBody[linkShareReadBody], error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + // ProjectID scopes the lookup to the parent project, guarding against + // reading a share of one project through another. + share := &models.LinkSharing{ID: in.ID, ProjectID: in.ProjectID} + maxPermission, err := handler.DoReadOne(ctx, share, a) + if err != nil { + return nil, translateDomainError(err) + } + body := &linkShareReadBody{LinkSharing: *share, MaxPermission: models.Permission(maxPermission)} + return conditionalReadResponse(&in.Params, body, share.Updated, maxPermission) +} + +func linkSharesCreate(ctx context.Context, in *struct { + ProjectID int64 `path:"project"` + Body models.LinkSharing +}) (*singleBody[models.LinkSharing], error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + in.Body.ProjectID = in.ProjectID // URL wins over body + if err := handler.DoCreate(ctx, &in.Body, a); err != nil { + return nil, translateDomainError(err) + } + return &singleBody[models.LinkSharing]{Body: &in.Body}, nil +} + +func linkSharesDelete(ctx context.Context, in *struct { + ProjectID int64 `path:"project"` + ID int64 `path:"share"` +}) (*emptyBody, error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + if err := handler.DoDelete(ctx, &models.LinkSharing{ID: in.ID, ProjectID: in.ProjectID}, a); err != nil { + return nil, translateDomainError(err) + } + return &emptyBody{}, nil +} diff --git a/pkg/routes/api/v2/migration_csv.go b/pkg/routes/api/v2/migration_csv.go new file mode 100644 index 000000000..9f1922671 --- /dev/null +++ b/pkg/routes/api/v2/migration_csv.go @@ -0,0 +1,200 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package apiv2 + +import ( + "context" + "encoding/json" + "net/http" + + "code.vikunja.io/api/pkg/config" + "code.vikunja.io/api/pkg/modules/migration" + "code.vikunja.io/api/pkg/modules/migration/csv" + "code.vikunja.io/api/pkg/user" + + "github.com/danielgtaylor/huma/v2" +) + +// csvDetectInput is the detect upload: just the file. +type csvDetectInput struct { + RawBody huma.MultipartFormFiles[struct { + Import huma.FormFile `form:"import" required:"true" doc:"The CSV file to analyze."` + }] +} + +// csvImportInput is the preview/migrate upload: the file plus a JSON config +// blob carried as a multipart form value (mirrors v1's FormValue(\"config\")). +type csvImportInput struct { + RawBody huma.MultipartFormFiles[struct { + Import huma.FormFile `form:"import" required:"true" doc:"The CSV file to import."` + Config string `form:"config" required:"true" doc:"The import configuration as a JSON object (see the ImportConfig schema), passed as a multipart form value. Obtain a starting config from the detect endpoint."` + }] +} + +type csvDetectBody struct { + Body *csv.DetectionResult +} + +type csvPreviewBody struct { + Body *csv.PreviewResult +} + +// RegisterMigrationCSVRoutes wires the generic CSV importer onto the Huma API. +// Like the other file migrators it has no config flag in v1, so it is always +// registered. +func RegisterMigrationCSVRoutes(api huma.API) { + tags := []string{"migration"} + // +2 MB mirrors Echo's global BodyLimit overhead so a max-sized file isn't rejected by multipart boundary/header bytes. + // #nosec G115 - configured value won't exceed int64 max in practice. + maxBody := (int64(config.GetMaxFileSizeInMBytes()) + 2) * 1024 * 1024 + + Register(api, huma.Operation{ + OperationID: "migration-csv-status", + Summary: "Get the CSV migration status", + Description: "Returns the migration status of the authenticated user for the CSV importer, i.e. whether and when they last imported a CSV.", + Method: http.MethodGet, + Path: "/migration/csv/status", + Tags: tags, + }, csvStatus) + + Register(api, huma.Operation{ + OperationID: "migration-csv-detect", + Summary: "Detect a CSV file's structure", + Description: "Analyzes an uploaded CSV file and returns its detected columns, delimiter, quote character and date format, plus a suggested column-to-attribute mapping the client can edit before previewing or migrating. Read-only: nothing is imported.", + Method: http.MethodPost, + Path: "/migration/csv/detect", + DefaultStatus: http.StatusOK, + Tags: tags, + MaxBodyBytes: maxBody, + }, csvDetect) + + Register(api, huma.Operation{ + OperationID: "migration-csv-preview", + Summary: "Preview a CSV import", + Description: "Returns the first few tasks that would be imported from the uploaded CSV file with the given config, without importing anything. Read-only.", + Method: http.MethodPost, + Path: "/migration/csv/preview", + DefaultStatus: http.StatusOK, + Tags: tags, + MaxBodyBytes: maxBody, + }, csvPreview) + + Register(api, huma.Operation{ + OperationID: "migration-csv-migrate", + Summary: "Import a CSV file", + Description: "Imports the tasks from the uploaded CSV file into Vikunja using the given config. The import runs synchronously and returns once it has finished.", + Method: http.MethodPost, + Path: "/migration/csv/migrate", + // POST runs an import rather than creating a REST resource, so it + // returns 200 with a confirmation, not the wrapper's 201. + DefaultStatus: http.StatusOK, + Tags: tags, + MaxBodyBytes: maxBody, + }, csvMigrate) +} + +func init() { AddRouteRegistrar(RegisterMigrationCSVRoutes) } + +func csvStatus(ctx context.Context, _ *struct{}) (*migrationStatusBody, error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + u, err := user.GetFromAuth(a) + if err != nil { + return nil, translateDomainError(err) + } + + status, err := migration.GetMigrationStatus(&csv.Migrator{}, u) + if err != nil { + return nil, translateDomainError(err) + } + return &migrationStatusBody{Body: status}, nil +} + +func csvDetect(ctx context.Context, in *csvDetectInput) (*csvDetectBody, error) { + if _, err := authFromCtx(ctx); err != nil { + return nil, err + } + + src := in.RawBody.Data().Import + defer func() { _ = src.Close() }() + + result, err := csv.DetectCSVStructure(src, src.Size) + if err != nil { + return nil, translateDomainError(err) + } + return &csvDetectBody{Body: result}, nil +} + +func csvPreview(ctx context.Context, in *csvImportInput) (*csvPreviewBody, error) { + if _, err := authFromCtx(ctx); err != nil { + return nil, err + } + + cfg, err := parseCSVImportConfig(in.RawBody.Data().Config) + if err != nil { + return nil, err + } + + src := in.RawBody.Data().Import + defer func() { _ = src.Close() }() + + result, err := csv.PreviewImport(src, src.Size, cfg) + if err != nil { + return nil, translateDomainError(err) + } + return &csvPreviewBody{Body: result}, nil +} + +func csvMigrate(ctx context.Context, in *csvImportInput) (*migrationStartedBody, error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + u, err := user.GetFromAuth(a) + if err != nil { + return nil, translateDomainError(err) + } + + cfg, err := parseCSVImportConfig(in.RawBody.Data().Config) + if err != nil { + return nil, err + } + + src := in.RawBody.Data().Import + defer func() { _ = src.Close() }() + + if err := csv.RunMigration(u, src, src.Size, cfg); err != nil { + return nil, translateDomainError(err) + } + + out := &migrationStartedBody{} + out.Body.Message = "Everything was migrated successfully." + return out, nil +} + +// parseCSVImportConfig unmarshals the JSON config form value, mirroring v1's +// json.Unmarshal of FormValue("config"). required:"true" guarantees presence, +// so only a malformed body needs guarding here. +func parseCSVImportConfig(raw string) (*csv.ImportConfig, error) { + var cfg csv.ImportConfig + if err := json.Unmarshal([]byte(raw), &cfg); err != nil { + return nil, huma.Error400BadRequest("Invalid configuration: " + err.Error()) + } + return &cfg, nil +} diff --git a/pkg/routes/api/v2/migration_file.go b/pkg/routes/api/v2/migration_file.go new file mode 100644 index 000000000..d02db596e --- /dev/null +++ b/pkg/routes/api/v2/migration_file.go @@ -0,0 +1,126 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package apiv2 + +import ( + "context" + "net/http" + + "code.vikunja.io/api/pkg/config" + "code.vikunja.io/api/pkg/modules/migration" + migrationHandler "code.vikunja.io/api/pkg/modules/migration/handler" + "code.vikunja.io/api/pkg/modules/migration/ticktick" + vikunja_file "code.vikunja.io/api/pkg/modules/migration/vikunja-file" + "code.vikunja.io/api/pkg/modules/migration/wekan" + "code.vikunja.io/api/pkg/user" + + "github.com/danielgtaylor/huma/v2" +) + +// fileMigrateInput is the multipart upload body shared by every file migrator's +// migrate endpoint. +type fileMigrateInput struct { + RawBody huma.MultipartFormFiles[struct { + Import huma.FormFile `form:"import" required:"true" doc:"The export file to import. Its expected format depends on the migrator (e.g. a Vikunja export zip, a TickTick CSV, a WeKan JSON export)."` + }] +} + +// RegisterMigrationFileRoutes wires the file-based migrators (Vikunja export, +// TickTick, WeKan) onto the Huma API. Unlike the OAuth migrators these have no +// config flag in v1, so they are always registered. +func RegisterMigrationFileRoutes(api huma.API) { + registerFileMigrator(api, func() migration.FileMigrator { return &vikunja_file.FileMigrator{} }) + registerFileMigrator(api, func() migration.FileMigrator { return &ticktick.Migrator{} }) + registerFileMigrator(api, func() migration.FileMigrator { return &wekan.Migrator{} }) +} + +func init() { AddRouteRegistrar(RegisterMigrationFileRoutes) } + +// registerFileMigrator registers status + migrate for a single file migrator. +// factory produces a fresh migrator instance per request, matching v1's +// MigrationStruct func so concurrent requests never share mutable state. +func registerFileMigrator(api huma.API, factory func() migration.FileMigrator) { + name := factory().Name() + tags := []string{"migration"} + + Register(api, huma.Operation{ + OperationID: "migration-" + name + "-status", + Summary: "Get the migration status for " + name, + Description: "Returns the migration status of the authenticated user for this service, i.e. whether and when they last migrated.", + Method: http.MethodGet, + Path: "/migration/" + name + "/status", + Tags: tags, + }, func(ctx context.Context, _ *struct{}) (*migrationStatusBody, error) { + return migrationFileStatus(ctx, factory) + }) + + Register(api, huma.Operation{ + OperationID: "migration-" + name + "-migrate", + Summary: "Migrate from " + name, + Description: "Imports the authenticated user's data from an uploaded export file into Vikunja. Send the file under the multipart \"import\" field. The import runs synchronously and returns once it has finished.", + Method: http.MethodPost, + Path: "/migration/" + name + "/migrate", + // POST runs an import rather than creating a REST resource, so it + // returns 200 with a confirmation, not the wrapper's 201. + DefaultStatus: http.StatusOK, + Tags: tags, + // +2 MB mirrors Echo's global BodyLimit overhead so a max-sized file isn't rejected by multipart boundary/header bytes. + // #nosec G115 - configured value won't exceed int64 max in practice. + MaxBodyBytes: (int64(config.GetMaxFileSizeInMBytes()) + 2) * 1024 * 1024, + }, func(ctx context.Context, in *fileMigrateInput) (*migrationStartedBody, error) { + return migrationFileMigrate(ctx, factory, in) + }) +} + +func migrationFileStatus(ctx context.Context, factory func() migration.FileMigrator) (*migrationStatusBody, error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + u, err := user.GetFromAuth(a) + if err != nil { + return nil, translateDomainError(err) + } + + status, err := migration.GetMigrationStatus(factory(), u) + if err != nil { + return nil, translateDomainError(err) + } + return &migrationStatusBody{Body: status}, nil +} + +func migrationFileMigrate(ctx context.Context, factory func() migration.FileMigrator, in *fileMigrateInput) (*migrationStartedBody, error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + u, err := user.GetFromAuth(a) + if err != nil { + return nil, translateDomainError(err) + } + + src := in.RawBody.Data().Import + defer func() { _ = src.Close() }() + + if err := migrationHandler.RunFileMigration(factory(), u, src, src.Size); err != nil { + return nil, translateDomainError(err) + } + + out := &migrationStartedBody{} + out.Body.Message = "Everything was migrated successfully." + return out, nil +} diff --git a/pkg/routes/api/v2/migration_oauth.go b/pkg/routes/api/v2/migration_oauth.go new file mode 100644 index 000000000..4d254632c --- /dev/null +++ b/pkg/routes/api/v2/migration_oauth.go @@ -0,0 +1,167 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package apiv2 + +import ( + "context" + "encoding/json" + "net/http" + + "code.vikunja.io/api/pkg/config" + "code.vikunja.io/api/pkg/modules/migration" + migrationHandler "code.vikunja.io/api/pkg/modules/migration/handler" + microsofttodo "code.vikunja.io/api/pkg/modules/migration/microsoft-todo" + "code.vikunja.io/api/pkg/modules/migration/todoist" + "code.vikunja.io/api/pkg/modules/migration/trello" + "code.vikunja.io/api/pkg/user" + + "github.com/danielgtaylor/huma/v2" +) + +// migrationAuthURLBody is the response for the OAuth auth-url endpoint. +type migrationAuthURLBody struct { + Body migrationHandler.AuthURL +} + +// migrationStatusBody is the response for the migration status endpoint. +type migrationStatusBody struct { + Body *migration.Status +} + +// migrationMigrateBody carries the OAuth code obtained from the auth url back +// to the server. It is applied onto the concrete migrator (whose field carries +// json:"code") so it works across migrators regardless of their field name. +type migrationMigrateBody struct { + Code string `json:"code" doc:"The OAuth code obtained after authorizing against the auth url."` +} + +// migrationStartedBody confirms the migration was kicked off; the actual work +// runs asynchronously. +type migrationStartedBody struct { + Body struct { + Message string `json:"message" readOnly:"true" doc:"A confirmation message."` + } +} + +// RegisterMigrationOAuthRoutes wires the OAuth-based migrators (Todoist, Trello, +// Microsoft To-Do) onto the Huma API. Each migrator is gated behind its static +// config flag and exposes the same three operations, so registration is driven +// by one generic helper instead of three copy-pasted blocks. +func RegisterMigrationOAuthRoutes(api huma.API) { + registerOAuthMigrator(api, config.MigrationTodoistEnable.GetBool(), func() migration.Migrator { return &todoist.Migration{} }) + registerOAuthMigrator(api, config.MigrationTrelloEnable.GetBool(), func() migration.Migrator { return &trello.Migration{} }) + registerOAuthMigrator(api, config.MigrationMicrosoftTodoEnable.GetBool(), func() migration.Migrator { return µsofttodo.Migration{} }) +} + +func init() { AddRouteRegistrar(RegisterMigrationOAuthRoutes) } + +// registerOAuthMigrator registers auth/status/migrate for a single OAuth +// migrator. enabled gates the whole migrator (config early-return, no +// middleware); factory produces a fresh migrator instance per request, matching +// v1's MigrationStruct func so concurrent requests never share mutable state. +func registerOAuthMigrator(api huma.API, enabled bool, factory func() migration.Migrator) { + if !enabled { + return + } + + name := factory().Name() + tags := []string{"migration"} + + Register(api, huma.Operation{ + OperationID: "migration-" + name + "-auth", + Summary: "Get the auth url for " + name, + Description: "Returns the OAuth url the user needs to authenticate against. The code obtained there is passed back to the migrate endpoint.", + Method: http.MethodGet, + Path: "/migration/" + name + "/auth", + Tags: tags, + }, func(_ context.Context, _ *struct{}) (*migrationAuthURLBody, error) { + return &migrationAuthURLBody{Body: migrationHandler.AuthURL{URL: factory().AuthURL()}}, nil + }) + + Register(api, huma.Operation{ + OperationID: "migration-" + name + "-status", + Summary: "Get the migration status for " + name, + Description: "Returns the migration status of the authenticated user for this service, i.e. whether and when they last migrated. Used to prevent starting a second migration while one is running.", + Method: http.MethodGet, + Path: "/migration/" + name + "/status", + Tags: tags, + }, func(ctx context.Context, _ *struct{}) (*migrationStatusBody, error) { + return migrationOAuthStatus(ctx, factory) + }) + + Register(api, huma.Operation{ + OperationID: "migration-" + name + "-migrate", + Summary: "Migrate from " + name, + Description: "Starts a migration of the authenticated user's data from this service into Vikunja. The migration runs asynchronously; this returns once it has been queued. Refuses with 412 if a migration for this service is already running.", + Method: http.MethodPost, + Path: "/migration/" + name + "/migrate", + // POST kicks off a job rather than creating a REST resource, so it + // returns 200 with a confirmation, not the wrapper's 201. + DefaultStatus: http.StatusOK, + Tags: tags, + }, func(ctx context.Context, in *struct{ Body migrationMigrateBody }) (*migrationStartedBody, error) { + return migrationOAuthMigrate(ctx, factory, in.Body) + }) +} + +func migrationOAuthStatus(ctx context.Context, factory func() migration.Migrator) (*migrationStatusBody, error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + u, err := user.GetFromAuth(a) + if err != nil { + return nil, translateDomainError(err) + } + + status, err := migration.GetMigrationStatus(factory(), u) + if err != nil { + return nil, translateDomainError(err) + } + return &migrationStatusBody{Body: status}, nil +} + +func migrationOAuthMigrate(ctx context.Context, factory func() migration.Migrator, body migrationMigrateBody) (*migrationStartedBody, error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + u, err := user.GetFromAuth(a) + if err != nil { + return nil, translateDomainError(err) + } + + ms := factory() + // Apply the request payload onto the concrete migrator the same way v1's + // c.Bind does, so migrator-specific field names (e.g. Trello's Token, + // json:"code") bind transparently. + raw, err := json.Marshal(body) + if err != nil { + return nil, err + } + if err := json.Unmarshal(raw, ms); err != nil { + return nil, huma.Error400BadRequest("invalid migration payload", err) + } + + if err := migrationHandler.StartMigration(ms, u); err != nil { + return nil, translateDomainError(err) + } + + out := &migrationStartedBody{} + out.Body.Message = "Migration was started successfully." + return out, nil +} diff --git a/pkg/routes/api/v2/notifications.go b/pkg/routes/api/v2/notifications.go new file mode 100644 index 000000000..d5e5b67d3 --- /dev/null +++ b/pkg/routes/api/v2/notifications.go @@ -0,0 +1,139 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package apiv2 + +import ( + "context" + "fmt" + "net/http" + + "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/models" + "code.vikunja.io/api/pkg/notifications" + "code.vikunja.io/api/pkg/web/handler" + + "github.com/danielgtaylor/huma/v2" +) + +// Element type is the foreign notifications.DatabaseNotification — that's what +// models.DatabaseNotifications.ReadAll returns directly, not a models wrapper. +type notificationListBody struct { + Body Paginated[*notifications.DatabaseNotification] +} + +// markAllReadBody mirrors v1's {"message":"success"}: the action has no +// resource to return, so it confirms with a status body, not emptyBody. +type markAllReadBody struct { + Body struct { + Message string `json:"message" readOnly:"true" doc:"A confirmation message."` + } +} + +// RegisterNotificationRoutes wires notification list / mark-read / mark-all onto the Huma API. +func RegisterNotificationRoutes(api huma.API) { + tags := []string{"notifications"} + + Register(api, huma.Operation{ + OperationID: "notifications-list", + Summary: "List notifications", + Description: "Returns the authenticated user's own notifications, newest first. Link shares have no notifications and are refused.", + Method: http.MethodGet, + Path: "/notifications", + Tags: tags, + }, notificationsList) + + Register(api, huma.Operation{ + OperationID: "notifications-mark-read", + Summary: "Mark a notification as (un-)read", + Description: "Marks one of the authenticated user's notifications as read or unread. A user can only mark their own notifications.", + Method: http.MethodPut, + Path: "/notifications/{notificationid}", + Tags: tags, + }, notificationsMarkRead) + + Register(api, huma.Operation{ + OperationID: "notifications-mark-all-read", + Summary: "Mark all notifications as read", + Description: "Marks every notification of the authenticated user as read. Link shares have no notifications and are refused.", + Method: http.MethodPost, + Path: "/notifications", + // Override the wrapper's POST→201 create default: this action creates nothing. + DefaultStatus: http.StatusOK, + Tags: tags, + }, notificationsMarkAllRead) +} + +func init() { AddRouteRegistrar(RegisterNotificationRoutes) } + +func notificationsList(ctx context.Context, in *ListParams) (*notificationListBody, error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + result, _, total, err := handler.DoReadAll(ctx, &models.DatabaseNotifications{}, a, in.Q, in.Page, in.PerPage) + if err != nil { + return nil, translateDomainError(err) + } + items, ok := result.([]*notifications.DatabaseNotification) + if !ok { + return nil, fmt.Errorf("notifications.ReadAll returned unexpected type %T (expected []*notifications.DatabaseNotification)", result) + } + return ¬ificationListBody{Body: NewPaginated(items, total, in.Page, in.PerPage)}, nil +} + +func notificationsMarkRead(ctx context.Context, in *struct { + ID int64 `path:"notificationid"` + Body models.DatabaseNotifications +}) (*singleBody[models.DatabaseNotifications], error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + n := &in.Body + n.ID = in.ID // URL wins over body + if err := handler.DoUpdate(ctx, n, a); err != nil { + return nil, translateDomainError(err) + } + return &singleBody[models.DatabaseNotifications]{Body: n}, nil +} + +// notificationsMarkAllRead is a custom action: no CRUDable Do* exists for a bulk +// mark, so the handler owns the link-share guard, session and commit itself. +func notificationsMarkAllRead(ctx context.Context, _ *struct{}) (*markAllReadBody, error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + if _, is := a.(*models.LinkSharing); is { + return nil, huma.Error403Forbidden("link shares cannot have notifications") + } + + s := db.NewSession() + defer s.Close() + + if err := notifications.MarkAllNotificationsAsRead(s, a.GetID()); err != nil { + _ = s.Rollback() + return nil, translateDomainError(err) + } + if err := s.Commit(); err != nil { + return nil, translateDomainError(err) + } + + out := &markAllReadBody{} + out.Body.Message = "success" + return out, nil +} diff --git a/pkg/routes/api/v2/notifications_feed.go b/pkg/routes/api/v2/notifications_feed.go new file mode 100644 index 000000000..d6195def2 --- /dev/null +++ b/pkg/routes/api/v2/notifications_feed.go @@ -0,0 +1,103 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package apiv2 + +import ( + "context" + "net/http" + + "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/modules/humaecho5" + "code.vikunja.io/api/pkg/routes/feeds" + + "github.com/danielgtaylor/huma/v2" + "github.com/labstack/echo/v5" +) + +// RegisterNotificationsFeedRoutes wires the Atom notifications feed onto the +// Huma API. It documents HTTP Basic auth (a feeds-scoped API token) because +// feed readers can't carry a bearer header. +func RegisterNotificationsFeedRoutes(api huma.API) { + Register(api, huma.Operation{ + OperationID: "notifications-atom-feed", + Summary: "Notifications Atom feed", + Description: "Returns the authenticated user's latest notifications as an Atom feed. Authenticated with HTTP Basic auth: the username is the token owner and the password is a feeds-scoped Vikunja API token (tk_ prefix) — password and LDAP credentials are rejected because feed URLs are commonly shared or cached. Fetching the feed does not mark notifications as read.", + Method: http.MethodGet, + Path: "/notifications.atom", + Tags: []string{"service"}, + // This op carries its own HTTP Basic auth instead of the global bearer + // schemes; the path is in unauthenticatedAPIPaths so the JWT middleware + // lets it through and the handler authenticates itself. + Security: []map[string][]string{{"BasicAuth": {}}}, + Responses: map[string]*huma.Response{ + "200": { + Description: "The notifications Atom feed.", + Content: map[string]*huma.MediaType{ + "application/atom+xml": { + Schema: &huma.Schema{Type: huma.TypeString, Format: "binary"}, + }, + }, + }, + }, + }, notificationsAtomFeed) +} + +func init() { AddRouteRegistrar(RegisterNotificationsFeedRoutes) } + +// notificationsAtomFeed authenticates with HTTP Basic (sharing the feeds +// validator) and streams the Atom feed; there is no handler.Do* for a non-JSON +// body and the auth can't ride the group's JWT middleware. +func notificationsAtomFeed(ctx context.Context, _ *struct{}) (*huma.StreamResponse, error) { + c, ok := ctx.Value(humaecho5.EchoContextKey).(*echo.Context) + if !ok { + return nil, huma.Error500InternalServerError("could not resolve request context") + } + + username, password, ok := (*c).Request().BasicAuth() + if !ok { + return nil, basicAuthChallenge(c) + } + + s := db.NewSession() + defer s.Close() + + u, err := feeds.AuthenticateFeedToken(s, username, password) + if err != nil { + return nil, translateDomainError(err) + } + if u == nil { + return nil, basicAuthChallenge(c) + } + + atom, err := feeds.BuildNotificationsAtomFeed(s, u) + if err != nil { + return nil, translateDomainError(err) + } + + return &huma.StreamResponse{Body: func(hctx huma.Context) { + ec := humaecho5.Unwrap(hctx) + (*ec).Response().Header().Set(echo.HeaderContentType, feeds.AtomContentType) + _, _ = (*ec).Response().Write([]byte(atom)) + }}, nil +} + +// basicAuthChallenge returns a 401 carrying a WWW-Authenticate Basic challenge, +// mirroring v1's BasicAuth middleware so feed readers prompt for credentials. +func basicAuthChallenge(c *echo.Context) error { + (*c).Response().Header().Set(echo.HeaderWWWAuthenticate, `Basic realm="Restricted"`) + return huma.Error401Unauthorized(http.StatusText(http.StatusUnauthorized)) +} diff --git a/pkg/routes/api/v2/oauth.go b/pkg/routes/api/v2/oauth.go new file mode 100644 index 000000000..a67441ad3 --- /dev/null +++ b/pkg/routes/api/v2/oauth.go @@ -0,0 +1,111 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package apiv2 + +import ( + "context" + "net/http" + + "code.vikunja.io/api/pkg/modules/auth/oauth2server" + "code.vikunja.io/api/pkg/user" + + "github.com/danielgtaylor/huma/v2" +) + +// oauthTokenBody wraps the OAuth 2.0 token response. +type oauthTokenBody struct { + // Cache-Control: no-store is required by RFC 6749 §5.1 so tokens are not + // cached. v2 already sets it globally, but declaring it keeps the contract + // explicit in the spec. + CacheControl string `header:"Cache-Control"` + Body *oauth2server.TokenResponse +} + +// oauthAuthorizeBody wraps the OAuth 2.0 authorization response. +type oauthAuthorizeBody struct { + Body *oauth2server.AuthorizeResponse +} + +func init() { AddRouteRegistrar(RegisterOAuthRoutes) } + +// RegisterOAuthRoutes wires the OAuth 2.0 token and authorize endpoints. The +// token endpoint is public (it authenticates the request itself); authorize +// inherits the global JWT auth. +func RegisterOAuthRoutes(api huma.API) { + tags := []string{"auth"} + + Register(api, huma.Operation{ + OperationID: "oauth-token", + Summary: "OAuth 2.0 token endpoint", + Description: "Exchanges an authorization code (grant_type=authorization_code) or a refresh token (grant_type=refresh_token) for an access token. Accepts application/x-www-form-urlencoded per RFC 6749 as well as JSON.", + Method: http.MethodPost, + Path: "/oauth/token", + DefaultStatus: http.StatusOK, + Tags: tags, + Security: publicSecurity, + }, oauthToken) + + Register(api, huma.Operation{ + OperationID: "oauth-authorize", + Summary: "OAuth 2.0 authorize endpoint", + Description: "Creates a single-use authorization code for the authenticated user. PKCE (code_challenge with method S256) and a loopback or vikunja- scheme redirect_uri are required.", + Method: http.MethodPost, + Path: "/oauth/authorize", + DefaultStatus: http.StatusOK, + Tags: tags, + }, oauthAuthorize) +} + +func oauthToken(ctx context.Context, in *struct { + Body oauth2server.TokenRequest `contentType:"application/x-www-form-urlencoded"` +}) (*oauthTokenBody, error) { + deviceInfo, ipAddress := requestClientInfo(ctx) + resp, err := oauth2server.ExchangeToken(ctx, &in.Body, deviceInfo, ipAddress) + if err != nil { + return nil, translateDomainError(err) + } + return &oauthTokenBody{CacheControl: "no-store", Body: resp}, nil +} + +func oauthAuthorize(ctx context.Context, in *struct{ Body oauth2server.AuthorizeRequest }) (*oauthAuthorizeBody, error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + u, err := user.GetFromAuth(a) + if err != nil { + return nil, translateDomainError(err) + } + + resp, err := oauth2server.Authorize(&in.Body, u.ID) + if err != nil { + return nil, translateDomainError(err) + } + return &oauthAuthorizeBody{Body: resp}, nil +} + +// requestClientInfo pulls the user agent and client IP off the underlying Echo +// request so the authorization_code grant (and login) can record them on the +// session they create, mirroring v1. Both fall back to "" when the context is +// unavailable. +func requestClientInfo(ctx context.Context) (deviceInfo, ipAddress string) { + ec := echoContextFromCtx(ctx) + if ec == nil { + return "", "" + } + return (*ec).Request().UserAgent(), (*ec).RealIP() +} diff --git a/pkg/routes/api/v2/project_duplicate.go b/pkg/routes/api/v2/project_duplicate.go new file mode 100644 index 000000000..6a050b2c2 --- /dev/null +++ b/pkg/routes/api/v2/project_duplicate.go @@ -0,0 +1,63 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package apiv2 + +import ( + "context" + "net/http" + + "code.vikunja.io/api/pkg/models" + "code.vikunja.io/api/pkg/web/handler" + + "github.com/danielgtaylor/huma/v2" +) + +// RegisterProjectDuplicateRoutes wires the project-duplicate action onto the Huma API. +// +// ProjectDuplicate is a CRUDable Create, so the handler reuses handler.DoCreate +// (its CanCreate enforces access); the only custom part is taking ProjectID from +// the path rather than the request body. +func RegisterProjectDuplicateRoutes(api huma.API) { + tags := []string{"projects"} + + Register(api, huma.Operation{ + OperationID: "projects-duplicate", + Summary: "Duplicate a project", + Description: "Deep-copies a project — its tasks, files, kanban data, assignees, comments, attachments, labels, relations and backgrounds — into a new project owned by the authenticated user. User/team/link shares are only copied when duplicate_shares is set to true. The user needs read access to the source project, plus write access to the parent project when one is given. The copy is placed under parent_project_id (top level if omitted). Returns the duplicate in duplicated_project.", + Method: http.MethodPost, + Path: "/projects/{projectid}/duplicate", + Tags: tags, + }, projectsDuplicate) +} + +func init() { AddRouteRegistrar(RegisterProjectDuplicateRoutes) } + +func projectsDuplicate(ctx context.Context, in *struct { + ProjectID int64 `path:"projectid" doc:"The numeric id of the project to duplicate."` + Body models.ProjectDuplicate +}) (*singleBody[models.ProjectDuplicate], error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + pd := &in.Body + pd.ProjectID = in.ProjectID + if err := handler.DoCreate(ctx, pd, a); err != nil { + return nil, translateDomainError(err) + } + return &singleBody[models.ProjectDuplicate]{Body: pd}, nil +} diff --git a/pkg/routes/api/v2/project_teams.go b/pkg/routes/api/v2/project_teams.go new file mode 100644 index 000000000..b90442075 --- /dev/null +++ b/pkg/routes/api/v2/project_teams.go @@ -0,0 +1,143 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package apiv2 + +import ( + "context" + "fmt" + "net/http" + + "code.vikunja.io/api/pkg/models" + "code.vikunja.io/api/pkg/web/handler" + + "github.com/danielgtaylor/huma/v2" +) + +// ReadAll returns []*models.TeamWithPermission, not the bare relation. +type projectTeamListBody struct { + Body Paginated[*models.TeamWithPermission] +} + +// RegisterProjectTeamRoutes wires the team<->project share CRUD onto Huma. +// There is no read-one operation (v1 has none either). +func RegisterProjectTeamRoutes(api huma.API) { + tags := []string{"sharing"} + + Register(api, huma.Operation{ + OperationID: "project-teams-list", + Summary: "List the teams a project is shared with", + Description: "Returns the teams that have access to the project, each with the permission they were granted. Requires read access to the project.", + Method: http.MethodGet, + Path: "/projects/{project}/teams", + Tags: tags, + }, projectTeamsList) + + Register(api, huma.Operation{ + OperationID: "project-teams-create", + Summary: "Share a project with a team", + Description: "Gives a team access to the project at the requested permission. Only project admins may share. Fails if the team already has access.", + Method: http.MethodPost, + Path: "/projects/{project}/teams", + Tags: tags, + }, projectTeamsCreate) + + Register(api, huma.Operation{ + OperationID: "project-teams-update", + Summary: "Update a team's permission on a project", + Description: "Changes the permission a team has on the project; only the permission is writable. Only project admins may update a share.", + Method: http.MethodPut, + Path: "/projects/{project}/teams/{team}", + Tags: tags, + }, projectTeamsUpdate) + + Register(api, huma.Operation{ + OperationID: "project-teams-delete", + Summary: "Remove a team from a project", + Description: "Revokes a team's access to the project. Only project admins may remove a share.", + Method: http.MethodDelete, + Path: "/projects/{project}/teams/{team}", + Tags: tags, + }, projectTeamsDelete) +} + +func init() { AddRouteRegistrar(RegisterProjectTeamRoutes) } + +func projectTeamsList(ctx context.Context, in *struct { + ProjectID int64 `path:"project"` + ListParams +}) (*projectTeamListBody, error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + result, _, total, err := handler.DoReadAll(ctx, &models.TeamProject{ProjectID: in.ProjectID}, a, in.Q, in.Page, in.PerPage) + if err != nil { + return nil, translateDomainError(err) + } + items, ok := result.([]*models.TeamWithPermission) + if !ok { + return nil, fmt.Errorf("projectTeams.ReadAll returned unexpected type %T (expected []*models.TeamWithPermission)", result) + } + return &projectTeamListBody{Body: NewPaginated(items, total, in.Page, in.PerPage)}, nil +} + +func projectTeamsCreate(ctx context.Context, in *struct { + ProjectID int64 `path:"project"` + Body models.TeamProject +}) (*singleBody[models.TeamProject], error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + in.Body.ProjectID = in.ProjectID // URL wins over body + if err := handler.DoCreate(ctx, &in.Body, a); err != nil { + return nil, translateDomainError(err) + } + return &singleBody[models.TeamProject]{Body: &in.Body}, nil +} + +func projectTeamsUpdate(ctx context.Context, in *struct { + ProjectID int64 `path:"project"` + TeamID int64 `path:"team"` + Body models.TeamProject +}) (*singleBody[models.TeamProject], error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + tp := &in.Body + tp.ProjectID = in.ProjectID // URL wins over body + tp.TeamID = in.TeamID + if err := handler.DoUpdate(ctx, tp, a); err != nil { + return nil, translateDomainError(err) + } + return &singleBody[models.TeamProject]{Body: tp}, nil +} + +func projectTeamsDelete(ctx context.Context, in *struct { + ProjectID int64 `path:"project"` + TeamID int64 `path:"team"` +}) (*emptyBody, error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + if err := handler.DoDelete(ctx, &models.TeamProject{ProjectID: in.ProjectID, TeamID: in.TeamID}, a); err != nil { + return nil, translateDomainError(err) + } + return &emptyBody{}, nil +} diff --git a/pkg/routes/api/v2/project_users.go b/pkg/routes/api/v2/project_users.go new file mode 100644 index 000000000..7bc588609 --- /dev/null +++ b/pkg/routes/api/v2/project_users.go @@ -0,0 +1,144 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package apiv2 + +import ( + "context" + "fmt" + "net/http" + + "code.vikunja.io/api/pkg/models" + "code.vikunja.io/api/pkg/web/handler" + + "github.com/danielgtaylor/huma/v2" +) + +// ReadAll yields the shared users, not the join rows: []*models.UserWithPermission. +type projectUserListBody struct { + Body Paginated[*models.UserWithPermission] +} + +// RegisterProjectUserRoutes registers the project<->user share routes. {user} is +// the username (a string), not a numeric id; there is no read-one. +func RegisterProjectUserRoutes(api huma.API) { + tags := []string{"sharing"} + + Register(api, huma.Operation{ + OperationID: "project-users-list", + Summary: "List the users a project is shared with", + Description: "Returns the users that have direct access to the project, with their permission. Requires read access to the project; team shares are not included. Pass q to filter by username.", + Method: http.MethodGet, + Path: "/projects/{project}/users", + Tags: tags, + }, projectUsersList) + + Register(api, huma.Operation{ + OperationID: "project-users-create", + Summary: "Share a project with a user", + Description: "Grants a user access to the project. The user is named by username in the body. Only project admins may share; the project owner cannot be added.", + Method: http.MethodPost, + Path: "/projects/{project}/users", + Tags: tags, + }, projectUsersCreate) + + Register(api, huma.Operation{ + OperationID: "project-users-update", + Summary: "Update a user's permission on a project", + Description: "Changes the permission a user has on the project; only the permission field is updated. The user is identified by username in the path. Only project admins may update a share.", + Method: http.MethodPut, + Path: "/projects/{project}/users/{user}", + Tags: tags, + }, projectUsersUpdate) + + Register(api, huma.Operation{ + OperationID: "project-users-delete", + Summary: "Remove a user's access to a project", + Description: "Revokes a user's direct access to the project, identified by username in the path. Only project admins may do this.", + Method: http.MethodDelete, + Path: "/projects/{project}/users/{user}", + Tags: tags, + }, projectUsersDelete) +} + +func init() { AddRouteRegistrar(RegisterProjectUserRoutes) } + +func projectUsersList(ctx context.Context, in *struct { + ProjectID int64 `path:"project"` + ListParams +}) (*projectUserListBody, error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + result, _, total, err := handler.DoReadAll(ctx, &models.ProjectUser{ProjectID: in.ProjectID}, a, in.Q, in.Page, in.PerPage) + if err != nil { + return nil, translateDomainError(err) + } + items, ok := result.([]*models.UserWithPermission) + if !ok { + return nil, fmt.Errorf("projectUsers.ReadAll returned unexpected type %T (expected []*models.UserWithPermission)", result) + } + return &projectUserListBody{Body: NewPaginated(items, total, in.Page, in.PerPage)}, nil +} + +func projectUsersCreate(ctx context.Context, in *struct { + ProjectID int64 `path:"project"` + Body models.ProjectUser +}) (*singleBody[models.ProjectUser], error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + in.Body.ProjectID = in.ProjectID // URL wins over body + if err := handler.DoCreate(ctx, &in.Body, a); err != nil { + return nil, translateDomainError(err) + } + return &singleBody[models.ProjectUser]{Body: &in.Body}, nil +} + +func projectUsersUpdate(ctx context.Context, in *struct { + ProjectID int64 `path:"project"` + Username string `path:"user"` + Body models.ProjectUser +}) (*singleBody[models.ProjectUser], error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + // Update only persists permission; the user and project come from the path. + lu := &in.Body + lu.ProjectID = in.ProjectID + lu.Username = in.Username + if err := handler.DoUpdate(ctx, lu, a); err != nil { + return nil, translateDomainError(err) + } + return &singleBody[models.ProjectUser]{Body: lu}, nil +} + +func projectUsersDelete(ctx context.Context, in *struct { + ProjectID int64 `path:"project"` + Username string `path:"user"` +}) (*emptyBody, error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + if err := handler.DoDelete(ctx, &models.ProjectUser{ProjectID: in.ProjectID, Username: in.Username}, a); err != nil { + return nil, translateDomainError(err) + } + return &emptyBody{}, nil +} diff --git a/pkg/routes/api/v2/project_views.go b/pkg/routes/api/v2/project_views.go new file mode 100644 index 000000000..946b5aabf --- /dev/null +++ b/pkg/routes/api/v2/project_views.go @@ -0,0 +1,181 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package apiv2 + +import ( + "context" + "fmt" + "net/http" + + "code.vikunja.io/api/pkg/models" + "code.vikunja.io/api/pkg/web/handler" + + "github.com/danielgtaylor/huma/v2" + "github.com/danielgtaylor/huma/v2/conditional" +) + +// projectViewListBody is the list-response envelope. models.ProjectView.ReadAll +// returns []*models.ProjectView, so that's the element type. +type projectViewListBody struct { + Body Paginated[*models.ProjectView] +} + +// RegisterProjectViewRoutes wires the nested ProjectView CRUD onto the Huma API. +// Every operation binds two path params: {project} → ProjectID and {view} → ID. +// This is the reference shape every nested sub-resource copies. +func RegisterProjectViewRoutes(api huma.API) { + tags := []string{"project_views"} + + Register(api, huma.Operation{ + OperationID: "project-views-list", + Summary: "List the views of a project", + Description: "Returns all views of the given project. Requires read access to the project; the list is not paginated by the server but is returned in the standard list envelope.", + Method: http.MethodGet, + Path: "/projects/{project}/views", + Tags: tags, + }, projectViewsList) + + Register(api, huma.Operation{ + OperationID: "project-views-read", + Summary: "Get a single view of a project", + Description: "Returns one view of a project. The view must belong to the project in the path. Sends an ETag; pass it as If-None-Match on a later read to get a 304 Not Modified.", + Method: http.MethodGet, + Path: "/projects/{project}/views/{view}", + Tags: tags, + }, projectViewsRead) + + Register(api, huma.Operation{ + OperationID: "project-views-create", + Summary: "Create a view in a project", + Description: "Creates a view in the given project. The parent project is taken from the URL, not the body. Only project admins may create a view.", + Method: http.MethodPost, + Path: "/projects/{project}/views", + Tags: tags, + }, projectViewsCreate) + + Register(api, huma.Operation{ + OperationID: "project-views-update", + Summary: "Update a view of a project", + Description: "Replaces a project view's fields. The view must belong to the project in the path, and only project admins may update it. Use PATCH for a partial update.", + Method: http.MethodPut, + Path: "/projects/{project}/views/{view}", + Tags: tags, + }, projectViewsUpdate) + + Register(api, huma.Operation{ + OperationID: "project-views-delete", + Summary: "Delete a view of a project", + Description: "Deletes a project view along with its buckets and task positions. Only project admins may delete it.", + Method: http.MethodDelete, + Path: "/projects/{project}/views/{view}", + Tags: tags, + }, projectViewsDelete) +} + +func init() { AddRouteRegistrar(RegisterProjectViewRoutes) } + +func projectViewsList(ctx context.Context, in *struct { + ProjectID int64 `path:"project"` + ListParams +}) (*projectViewListBody, error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + result, _, total, err := handler.DoReadAll(ctx, &models.ProjectView{ProjectID: in.ProjectID}, a, in.Q, in.Page, in.PerPage) + if err != nil { + return nil, translateDomainError(err) + } + items, ok := result.([]*models.ProjectView) + if !ok { + return nil, fmt.Errorf("projectViews.ReadAll returned unexpected type %T (expected []*models.ProjectView)", result) + } + return &projectViewListBody{Body: NewPaginated(items, total, in.Page, in.PerPage)}, nil +} + +type projectViewReadBody struct { + models.ProjectView + MaxPermission models.Permission `json:"max_permission" readOnly:"true" doc:"The maximum permission the requesting user has on this view (0=read, 1=read/write, 2=admin)."` +} + +func projectViewsRead(ctx context.Context, in *struct { + ProjectID int64 `path:"project"` + ID int64 `path:"view"` + conditional.Params +}) (*singleReadBody[projectViewReadBody], error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + // ReadOne resolves the view via GetProjectViewByIDAndProject, which needs + // both ids — the parent project scopes the lookup. + view := &models.ProjectView{ID: in.ID, ProjectID: in.ProjectID} + maxPermission, err := handler.DoReadOne(ctx, view, a) + if err != nil { + return nil, translateDomainError(err) + } + body := &projectViewReadBody{ProjectView: *view, MaxPermission: models.Permission(maxPermission)} + return conditionalReadResponse(&in.Params, body, view.Updated, maxPermission) +} + +func projectViewsCreate(ctx context.Context, in *struct { + ProjectID int64 `path:"project"` + Body models.ProjectView +}) (*singleBody[models.ProjectView], error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + in.Body.ProjectID = in.ProjectID // URL wins over body + if err := handler.DoCreate(ctx, &in.Body, a); err != nil { + return nil, translateDomainError(err) + } + return &singleBody[models.ProjectView]{Body: &in.Body}, nil +} + +// Body matches the read shape so AutoPatch's GET→PUT echo of max_permission validates. +func projectViewsUpdate(ctx context.Context, in *struct { + ProjectID int64 `path:"project"` + ID int64 `path:"view"` + Body projectViewReadBody +}) (*singleBody[models.ProjectView], error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + view := &in.Body.ProjectView + view.ID = in.ID // URL wins over body + view.ProjectID = in.ProjectID // parent from the path scopes the update + if err := handler.DoUpdate(ctx, view, a); err != nil { + return nil, translateDomainError(err) + } + return &singleBody[models.ProjectView]{Body: view}, nil +} + +func projectViewsDelete(ctx context.Context, in *struct { + ProjectID int64 `path:"project"` + ID int64 `path:"view"` +}) (*emptyBody, error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + if err := handler.DoDelete(ctx, &models.ProjectView{ID: in.ID, ProjectID: in.ProjectID}, a); err != nil { + return nil, translateDomainError(err) + } + return &emptyBody{}, nil +} diff --git a/pkg/routes/api/v2/projects.go b/pkg/routes/api/v2/projects.go new file mode 100644 index 000000000..27a40ea20 --- /dev/null +++ b/pkg/routes/api/v2/projects.go @@ -0,0 +1,186 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package apiv2 + +import ( + "context" + "fmt" + "net/http" + + "code.vikunja.io/api/pkg/models" + "code.vikunja.io/api/pkg/web/handler" + + "github.com/danielgtaylor/huma/v2" +) + +// projectListBody is the list-response envelope. models.Project.ReadAll +// returns []*models.Project, so that's the element type. +type projectListBody struct { + Body Paginated[*models.Project] +} + +// RegisterProjectRoutes wires Project CRUD onto the Huma API. +func RegisterProjectRoutes(api huma.API) { + tags := []string{"projects"} + + Register(api, huma.Operation{ + OperationID: "projects-list", + Summary: "List projects", + Description: "Returns the projects the authenticated user has access to (owned plus shared, with child projects of accessible parents), paginated. Archived projects are excluded unless is_archived=true. Pass expand=permissions to include each project's max_permission for the caller.", + Method: http.MethodGet, + Path: "/projects", + Tags: tags, + }, projectsList) + + Register(api, huma.Operation{ + OperationID: "projects-read", + Summary: "Get a project", + Description: "Returns a single project the caller can read, including its views, the caller's favorite/subscription state and the caller's max_permission. Resolves the Favorites pseudo-project and saved-filter-backed projects. Served fresh on every call (no conditional/ETag) because the response carries user-scoped state that changes without bumping the project's updated timestamp.", + Method: http.MethodGet, + Path: "/projects/{id}", + Tags: tags, + }, projectsRead) + + Register(api, huma.Operation{ + OperationID: "projects-create", + Summary: "Create a project", + Description: "Creates a project; the authenticated user becomes its owner. When parent_project_id is set, the caller needs write access to that parent. Default views and a backlog bucket are created automatically.", + Method: http.MethodPost, + Path: "/projects", + Tags: tags, + }, projectsCreate) + + Register(api, huma.Operation{ + OperationID: "projects-update", + Summary: "Update a project", + Description: "Replaces a project's fields. Requires write access (admin to reparent or delete). Use PATCH for a partial update.", + Method: http.MethodPut, + Path: "/projects/{id}", + Tags: tags, + }, projectsUpdate) + + Register(api, huma.Operation{ + OperationID: "projects-delete", + Summary: "Delete a project", + Description: "Deletes a project together with its tasks, views, buckets and child projects. Only project admins may delete it.", + Method: http.MethodDelete, + Path: "/projects/{id}", + Tags: tags, + }, projectsDelete) +} + +func init() { AddRouteRegistrar(RegisterProjectRoutes) } + +func projectsList(ctx context.Context, in *struct { + ListParams + Expand string `query:"expand" enum:"permissions" doc:"If set to \"permissions\", each returned project includes the max permission the requesting user has on it (max_permission). Currently only \"permissions\" is supported."` + IsArchived bool `query:"is_archived" doc:"If true, also returns archived projects."` +}) (*projectListBody, error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + p := &models.Project{ + Expand: models.ProjectExpandable(in.Expand), + IsArchived: in.IsArchived, + } + result, _, total, err := handler.DoReadAll(ctx, p, a, in.Q, in.Page, in.PerPage) + if err != nil { + return nil, translateDomainError(err) + } + items, ok := result.([]*models.Project) + if !ok { + return nil, fmt.Errorf("projects.ReadAll returned unexpected type %T (expected []*models.Project)", result) + } + return &projectListBody{Body: NewPaginated(items, total, in.Page, in.PerPage)}, nil +} + +// projectReadBody is the read shape. Unlike labels/views, models.Project +// already carries a max_permission field (v1 surfaces it via expand on the +// list route), so the embed just reuses it rather than declaring a sibling. +type projectReadBody struct { + models.Project +} + +func projectsRead(ctx context.Context, in *struct { + ID int64 `path:"id"` +}) (*singleBody[projectReadBody], error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + project := &models.Project{ID: in.ID} + maxPermission, err := handler.DoReadOne(ctx, project, a) + if err != nil { + return nil, translateDomainError(err) + } + // CanRead returns a real permission for every readable project (including + // the Favorites pseudo-project and saved-filter-backed ones), so the field + // is always meaningful here — surfaced unconditionally like labels/views. + project.MaxPermission = models.Permission(maxPermission) + // No ETag/conditional read: a project response carries user-scoped, derived + // state (subscription, favorite, views, computed archived state) that + // changes without bumping project.Updated, so it's always served fresh. + return &singleBody[projectReadBody]{Body: &projectReadBody{Project: *project}}, nil +} + +func projectsCreate(ctx context.Context, in *struct { + Body models.Project +}) (*singleBody[models.Project], error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + if err := handler.DoCreate(ctx, &in.Body, a); err != nil { + return nil, translateDomainError(err) + } + // Create/Update don't compute the caller's permission; null says "read it" + // rather than echoing the zero value (0 = read), misleading for the owner. + in.Body.MaxPermission = models.PermissionUnknown + return &singleBody[models.Project]{Body: &in.Body}, nil +} + +// Body matches the read shape so AutoPatch's GET→PUT echo of max_permission validates. +func projectsUpdate(ctx context.Context, in *struct { + ID int64 `path:"id"` + Body projectReadBody +}) (*singleBody[models.Project], error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + project := &in.Body.Project + project.ID = in.ID // URL wins over body + if err := handler.DoUpdate(ctx, project, a); err != nil { + return nil, translateDomainError(err) + } + project.MaxPermission = models.PermissionUnknown // see projectsCreate + return &singleBody[models.Project]{Body: project}, nil +} + +func projectsDelete(ctx context.Context, in *struct { + ID int64 `path:"id"` +}) (*emptyBody, error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + if err := handler.DoDelete(ctx, &models.Project{ID: in.ID}, a); err != nil { + return nil, translateDomainError(err) + } + return &emptyBody{}, nil +} diff --git a/pkg/routes/api/v2/reactions.go b/pkg/routes/api/v2/reactions.go new file mode 100644 index 000000000..722b2615a --- /dev/null +++ b/pkg/routes/api/v2/reactions.go @@ -0,0 +1,135 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package apiv2 + +import ( + "context" + "fmt" + "net/http" + + "code.vikunja.io/api/pkg/models" + "code.vikunja.io/api/pkg/web/handler" + + "github.com/danielgtaylor/huma/v2" +) + +// {entitykind} stays a string: the model derives the numeric EntityKind from +// it and rejects unknown kinds. The enum tag (repeated on the create/delete +// inputs) makes Huma reject anything else with a 422 before the handler runs; +// keep the values in sync with models.Reaction.setEntityKindFromString. +type reactionPathParams struct { + EntityKind string `path:"entitykind" enum:"tasks,comments" doc:"The kind of entity being reacted to. Either tasks or comments (task comments)."` + EntityID int64 `path:"entityid" doc:"The numeric id of the entity being reacted to."` +} + +// Reactions list as a map keyed by reaction value, not a slice, so it does not +// fit the Paginated envelope. +type reactionListBody struct { + Body models.ReactionMap +} + +func RegisterReactionRoutes(api huma.API) { + tags := []string{"reactions"} + + Register(api, huma.Operation{ + OperationID: "reactions-list", + Summary: "List reactions for an entity", + Description: "Returns every reaction on the entity, grouped as a map keyed by reaction value; each value maps to the users who reacted with it. Requires read access to the entity. Not paginated.", + Method: http.MethodGet, + Path: "/{entitykind}/{entityid}/reactions", + Tags: tags, + }, reactionsList) + + Register(api, huma.Operation{ + OperationID: "reactions-create", + Summary: "React to an entity", + Description: "Adds the authenticated user's reaction to the entity. Requires write access. No-op if the same reaction already exists.", + Method: http.MethodPost, + Path: "/{entitykind}/{entityid}/reactions", + Tags: tags, + }, reactionsCreate) + + Register(api, huma.Operation{ + OperationID: "reactions-delete", + Summary: "Remove a reaction from an entity", + Description: "Removes the authenticated user's own reaction from the entity. The reaction to remove is named in the body (there is no per-reaction id), so this is a POST with a body rather than a DELETE. Requires write access.", + Method: http.MethodPost, + Path: "/{entitykind}/{entityid}/reactions/delete", + Tags: tags, + DefaultStatus: http.StatusOK, + }, reactionsDelete) +} + +func init() { AddRouteRegistrar(RegisterReactionRoutes) } + +func reactionsList(ctx context.Context, in *reactionPathParams) (*reactionListBody, error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + r := &models.Reaction{EntityID: in.EntityID, EntityKindString: in.EntityKind} + result, _, _, err := handler.DoReadAll(ctx, r, a, "", 1, -1) + if err != nil { + return nil, translateDomainError(err) + } + reactions, ok := result.(models.ReactionMap) + if !ok { + return nil, fmt.Errorf("reactions.ReadAll returned unexpected type %T (expected models.ReactionMap)", result) + } + if reactions == nil { + reactions = models.ReactionMap{} + } + return &reactionListBody{Body: reactions}, nil +} + +// Path params are flattened (not via the embedded reactionPathParams) because +// Huma fails to bind an embedded path-param struct when the input also has a Body. +func reactionsCreate(ctx context.Context, in *struct { + EntityKind string `path:"entitykind" enum:"tasks,comments" doc:"The kind of entity being reacted to. Either tasks or comments (task comments)."` + EntityID int64 `path:"entityid" doc:"The numeric id of the entity being reacted to."` + Body models.Reaction +}) (*singleBody[models.Reaction], error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + r := &in.Body + r.EntityID = in.EntityID + r.EntityKindString = in.EntityKind + if err := handler.DoCreate(ctx, r, a); err != nil { + return nil, translateDomainError(err) + } + return &singleBody[models.Reaction]{Body: r}, nil +} + +func reactionsDelete(ctx context.Context, in *struct { + EntityKind string `path:"entitykind" enum:"tasks,comments" doc:"The kind of entity being reacted to. Either tasks or comments (task comments)."` + EntityID int64 `path:"entityid" doc:"The numeric id of the entity being reacted to."` + Body models.Reaction +}) (*emptyBody, error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + r := &in.Body + r.EntityID = in.EntityID + r.EntityKindString = in.EntityKind + if err := handler.DoDelete(ctx, r, a); err != nil { + return nil, translateDomainError(err) + } + return &emptyBody{}, nil +} diff --git a/pkg/routes/api/v2/registry.go b/pkg/routes/api/v2/registry.go new file mode 100644 index 000000000..ab186045e --- /dev/null +++ b/pkg/routes/api/v2/registry.go @@ -0,0 +1,45 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package apiv2 + +import "github.com/danielgtaylor/huma/v2" + +var routeRegistrars []func(huma.API) + +// AddRouteRegistrar records a resource's route-registration function. Each +// resource file calls this from an init() so new resources never touch the +// central wiring. +// +// It mutates the package-level routeRegistrars slice without synchronization, so +// it is NOT safe for concurrent use. Call it only during package initialization +// (from an init() func), never at runtime. +func AddRouteRegistrar(f func(huma.API)) { + routeRegistrars = append(routeRegistrars, f) +} + +// RegisterAll runs every registrar collected via AddRouteRegistrar, then +// enables AutoPatch. The order in which registrars run is unspecified (Go does +// not guarantee a stable init order across files) and does not matter: each +// resource registers a distinct set of routes. AutoPatch runs last — inside +// RegisterAll, after every registrar — so it can synthesise PATCH counterparts +// for all GET + PUT pairs. +func RegisterAll(api huma.API) { + for _, r := range routeRegistrars { + r(api) + } + EnableAutoPatch(api) +} diff --git a/pkg/routes/api/v2/saved_filters.go b/pkg/routes/api/v2/saved_filters.go new file mode 100644 index 000000000..bc82776f5 --- /dev/null +++ b/pkg/routes/api/v2/saved_filters.go @@ -0,0 +1,137 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package apiv2 + +import ( + "context" + "net/http" + + "code.vikunja.io/api/pkg/models" + "code.vikunja.io/api/pkg/web/handler" + + "github.com/danielgtaylor/huma/v2" + "github.com/danielgtaylor/huma/v2/conditional" +) + +// RegisterSavedFilterRoutes wires saved filter CRUD onto the Huma API. +// No list operation, by design — v1 has none either. +func RegisterSavedFilterRoutes(api huma.API) { + tags := []string{"filters"} + + Register(api, huma.Operation{ + OperationID: "filters-read", + Summary: "Get a saved filter", + Description: "Returns a single saved filter. Only the owner may see it. Sends an ETag; pass it as If-None-Match on a later read to get a 304 Not Modified.", + Method: http.MethodGet, + Path: "/filters/{filter}", + Tags: tags, + }, savedFiltersRead) + + Register(api, huma.Operation{ + OperationID: "filters-create", + Summary: "Create a saved filter", + Description: "Creates a saved filter; the authenticated user becomes its owner. The filter query is validated before it is stored.", + Method: http.MethodPost, + Path: "/filters", + Tags: tags, + }, savedFiltersCreate) + + Register(api, huma.Operation{ + OperationID: "filters-update", + Summary: "Update a saved filter", + Description: "Replaces all of a saved filter's fields — only the owner may update it. Use PATCH for a partial update.", + Method: http.MethodPut, + Path: "/filters/{filter}", + Tags: tags, + }, savedFiltersUpdate) + + Register(api, huma.Operation{ + OperationID: "filters-delete", + Summary: "Delete a saved filter", + Description: "Deletes a saved filter. Only the owner may delete it.", + Method: http.MethodDelete, + Path: "/filters/{filter}", + Tags: tags, + }, savedFiltersDelete) +} + +func init() { AddRouteRegistrar(RegisterSavedFilterRoutes) } + +type savedFilterReadBody struct { + models.SavedFilter + MaxPermission models.Permission `json:"max_permission" readOnly:"true" doc:"The maximum permission the requesting user has on this saved filter (0=read, 1=read/write, 2=admin). Filters are owner-only, so this is always 2 for a successful read."` +} + +func savedFiltersRead(ctx context.Context, in *struct { + ID int64 `path:"filter"` + conditional.Params +}) (*singleReadBody[savedFilterReadBody], error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + filter := &models.SavedFilter{ID: in.ID} + maxPermission, err := handler.DoReadOne(ctx, filter, a) + if err != nil { + return nil, translateDomainError(err) + } + body := &savedFilterReadBody{SavedFilter: *filter, MaxPermission: models.Permission(maxPermission)} + return conditionalReadResponse(&in.Params, body, filter.Updated, maxPermission) +} + +func savedFiltersCreate(ctx context.Context, in *struct { + Body models.SavedFilter +}) (*singleBody[models.SavedFilter], error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + if err := handler.DoCreate(ctx, &in.Body, a); err != nil { + return nil, translateDomainError(err) + } + return &singleBody[models.SavedFilter]{Body: &in.Body}, nil +} + +// Body matches the read shape so AutoPatch's GET→PUT echo of max_permission validates. +func savedFiltersUpdate(ctx context.Context, in *struct { + ID int64 `path:"filter"` + Body savedFilterReadBody +}) (*singleBody[models.SavedFilter], error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + filter := &in.Body.SavedFilter + filter.ID = in.ID // URL wins over body + if err := handler.DoUpdate(ctx, filter, a); err != nil { + return nil, translateDomainError(err) + } + return &singleBody[models.SavedFilter]{Body: filter}, nil +} + +func savedFiltersDelete(ctx context.Context, in *struct { + ID int64 `path:"filter"` +}) (*emptyBody, error) { + a, err := authFromCtx(ctx) + if err != nil { + return nil, err + } + if err := handler.DoDelete(ctx, &models.SavedFilter{ID: in.ID}, a); err != nil { + return nil, translateDomainError(err) + } + return &emptyBody{}, nil +} diff --git a/pkg/routes/api/v2/scalar/scalar.html b/pkg/routes/api/v2/scalar/scalar.html new file mode 100644 index 000000000..6f76f5879 --- /dev/null +++ b/pkg/routes/api/v2/scalar/scalar.html @@ -0,0 +1,16 @@ + + + + + + + Vikunja API + + + + + + diff --git a/pkg/routes/api/v2/scalar/scalar.standalone.js b/pkg/routes/api/v2/scalar/scalar.standalone.js new file mode 100644 index 000000000..a9de9c8ac --- /dev/null +++ b/pkg/routes/api/v2/scalar/scalar.standalone.js @@ -0,0 +1,38827 @@ +/** + * _____ _________ __ ___ ____ + * / ___// ____/ | / / / | / __ \ + * \__ \/ / / /| | / / / /| | / /_/ / + * ___/ / /___/ ___ |/ /___/ ___ |/ _, _/ + * /____/\____/_/ |_/_____/_/ |_/_/ |_| + * + * @scalar/api-reference 1.44.20 + * + * Website: https://scalar.com + * GitHub: https://github.com/scalar/scalar + * License: https://github.com/scalar/scalar/blob/main/LICENSE +**/ + +!function(){"use strict";try{if("undefined"!=typeof document){var a=document.createElement("style");a.appendChild(document.createTextNode('@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-divide-x-reverse:0;--tw-border-style:solid;--tw-divide-y-reverse:0;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-mask-linear:linear-gradient(#fff,#fff);--tw-mask-radial:linear-gradient(#fff,#fff);--tw-mask-conic:linear-gradient(#fff,#fff);--tw-mask-left:linear-gradient(#fff,#fff);--tw-mask-right:linear-gradient(#fff,#fff);--tw-mask-bottom:linear-gradient(#fff,#fff);--tw-mask-top:linear-gradient(#fff,#fff);--tw-mask-top-from-position:0%;--tw-mask-top-to-position:100%;--tw-mask-top-from-color:black;--tw-mask-top-to-color:transparent;--tw-mask-bottom-from-position:0%;--tw-mask-bottom-to-position:100%;--tw-mask-bottom-from-color:black;--tw-mask-bottom-to-color:transparent;--tw-leading:initial;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-ease:initial}}}@layer scalar-base{@supports (color:color-mix(in lab,red,red)){.light-mode,.dark-mode{--scalar-sidebar-search-background:color-mix(in srgb,var(--scalar-background-2),var(--scalar-background-1))}}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-color-alert:color-mix(in srgb,var(--scalar-color-orange),var(--scalar-color-1)20%)}}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-color-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-color-1)20%)}}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-background-alert:color-mix(in srgb,var(--scalar-color-orange),var(--scalar-background-1)95%)}}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-background-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1)95%)}}@supports (color:color-mix(in lab,red,red)){.dark-mode{--scalar-tooltip-background:color-mix(in srgb,var(--scalar-background-1),#fff 10%)}}@supports (color:color-mix(in lab,red,red)){.dark-mode{--scalar-color-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1)20%)}}@supports (color:color-mix(in lab,red,red)){.dark-mode{--scalar-background-alert:color-mix(in srgb,var(--scalar-color-orange),var(--scalar-background-1)95%)}}@supports (color:color-mix(in lab,red,red)){.dark-mode{--scalar-background-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1)95%)}}body{line-height:inherit;margin:0}:root{--scalar-border-width:.5px;--scalar-radius:3px;--scalar-radius-lg:6px;--scalar-radius-xl:8px;--scalar-font:"Inter",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,"Open Sans","Helvetica Neue",sans-serif;--scalar-font-code:"JetBrains Mono",ui-monospace,Menlo,Monaco,"Cascadia Mono","Segoe UI Mono","Roboto Mono","Oxygen Mono","Ubuntu Monospace","Source Code Pro","Fira Mono","Droid Sans Mono","Courier New",monospace;--scalar-heading-1:24px;--scalar-page-description:16px;--scalar-heading-2:20px;--scalar-heading-3:16px;--scalar-heading-4:16px;--scalar-heading-5:16px;--scalar-heading-6:16px;--scalar-paragraph:16px;--scalar-small:14px;--scalar-mini:13px;--scalar-micro:12px;--scalar-bold:600;--scalar-semibold:500;--scalar-regular:400;--scalar-font-size-1:21px;--scalar-font-size-2:16px;--scalar-font-size-3:14px;--scalar-font-size-4:13px;--scalar-font-size-5:12px;--scalar-font-size-6:12px;--scalar-font-size-7:10px;--scalar-line-height-1:32px;--scalar-line-height-2:24px;--scalar-line-height-3:20px;--scalar-line-height-4:18px;--scalar-line-height-5:16px;--scalar-font-normal:400;--scalar-font-medium:500;--scalar-font-bold:700;--scalar-text-decoration:none;--scalar-text-decoration-hover:underline;--scalar-link-font-weight:inherit;--scalar-sidebar-indent:20px}.dark-mode{color-scheme:dark;--scalar-scrollbar-color:#ffffff2e;--scalar-scrollbar-color-active:#ffffff5c;--scalar-button-1:#fff;--scalar-button-1-hover:#ffffffe6;--scalar-button-1-color:black;--scalar-shadow-1:0 1px 3px 0 #0000001a;--scalar-shadow-2:0 0 0 .5px var(--scalar-border-color),#0f0f0f33 0px 3px 6px,#0f0f0f66 0px 9px 24px;--scalar-lifted-brightness:1.45;--scalar-backdrop-brightness:.5;--scalar-text-decoration-color:currentColor;--scalar-text-decoration-color-hover:currentColor}.light-mode{color-scheme:light;--scalar-scrollbar-color-active:#0000005c;--scalar-scrollbar-color:#0000002e;--scalar-button-1:#000;--scalar-button-1-hover:#000c;--scalar-button-1-color:#ffffffe6;--scalar-shadow-1:0 1px 3px 0 #0000001c;--scalar-shadow-2:#00000014 0px 13px 20px 0px,#00000014 0px 3px 8px 0px,#eeeeed 0px 0 0 .5px;--scalar-lifted-brightness:1;--scalar-backdrop-brightness:1;--scalar-text-decoration-color:currentColor;--scalar-text-decoration-color-hover:currentColor}.light-mode .dark-mode{color-scheme:dark!important}@media(max-width:460px){:root{--scalar-font-size-1:22px;--scalar-font-size-2:14px;--scalar-font-size-3:12px}}@media(max-width:720px){:root{--scalar-heading-1:24px;--scalar-page-description:20px}}:root{--scalar-text-decoration:underline;--scalar-text-decoration-hover:underline}.light-mode{--scalar-background-1:#fff;--scalar-background-2:#f6f6f6;--scalar-background-3:#e7e7e7;--scalar-background-accent:#8ab4f81f;--scalar-color-1:#1b1b1b;--scalar-color-2:#757575;--scalar-color-3:#8e8e8e;--scalar-color-accent:#09f;--scalar-border-color:#dfdfdf}.dark-mode{--scalar-background-1:#0f0f0f;--scalar-background-2:#1a1a1a;--scalar-background-3:#272727;--scalar-color-1:#e7e7e7;--scalar-color-2:#a4a4a4;--scalar-color-3:#797979;--scalar-color-accent:#00aeff;--scalar-background-accent:#3ea6ff1f;--scalar-border-color:#2d2d2d}.light-mode,.dark-mode{--scalar-sidebar-background-1:var(--scalar-background-1);--scalar-sidebar-color-1:var(--scalar-color-1);--scalar-sidebar-color-2:var(--scalar-color-2);--scalar-sidebar-border-color:var(--scalar-border-color);--scalar-sidebar-item-hover-background:var(--scalar-background-2);--scalar-sidebar-item-hover-color:var(--scalar-sidebar-color-2);--scalar-sidebar-item-active-background:var(--scalar-background-2);--scalar-sidebar-color-active:var(--scalar-sidebar-color-1);--scalar-sidebar-indent-border:var(--scalar-sidebar-border-color);--scalar-sidebar-indent-border-hover:var(--scalar-sidebar-border-color);--scalar-sidebar-indent-border-active:var(--scalar-sidebar-border-color);--scalar-sidebar-search-background:var(--scalar-background-2)}@supports (color:color-mix(in lab,red,red)){.light-mode,.dark-mode{--scalar-sidebar-search-background:var(--scalar-background-2)}@supports (color:color-mix(in lab,red,red)){.light-mode,.dark-mode{--scalar-sidebar-search-background:color-mix(in srgb,var(--scalar-background-2),var(--scalar-background-1))}}}.light-mode,.dark-mode{--scalar-sidebar-search-color:var(--scalar-color-3);--scalar-sidebar-search-border-color:var(--scalar-border-color)}.light-mode{--scalar-color-green:#069061;--scalar-color-red:#ef0006;--scalar-color-yellow:#edbe20;--scalar-color-blue:#0082d0;--scalar-color-orange:#ff5800;--scalar-color-purple:#5203d1;--scalar-link-color:var(--scalar-color-1);--scalar-link-color-hover:var(--scalar-link-color);--scalar-button-1:#000;--scalar-button-1-hover:#000c;--scalar-button-1-color:#ffffffe6;--scalar-tooltip-background:#1a1a1ae6;--scalar-tooltip-color:#ffffffd9;--scalar-color-alert:var(--scalar-color-orange)}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-color-alert:var(--scalar-color-orange)}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-color-alert:color-mix(in srgb,var(--scalar-color-orange),var(--scalar-color-1)20%)}}}.light-mode{--scalar-color-danger:var(--scalar-color-red)}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-color-danger:var(--scalar-color-red)}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-color-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-color-1)20%)}}}.light-mode{--scalar-background-alert:var(--scalar-color-orange)}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-background-alert:var(--scalar-color-orange)}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-background-alert:color-mix(in srgb,var(--scalar-color-orange),var(--scalar-background-1)95%)}}}.light-mode{--scalar-background-danger:var(--scalar-color-red)}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-background-danger:var(--scalar-color-red)}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-background-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1)95%)}}}.dark-mode{--scalar-color-green:#00b648;--scalar-color-red:#dc1b19;--scalar-color-yellow:#ffc90d;--scalar-color-blue:#4eb3ec;--scalar-color-orange:#ff8d4d;--scalar-color-purple:#b191f9;--scalar-link-color:var(--scalar-color-1);--scalar-link-color-hover:var(--scalar-link-color);--scalar-button-1:#fff;--scalar-button-1-hover:#ffffffe6;--scalar-button-1-color:black;--scalar-tooltip-background:var(--scalar-background-1)}@supports (color:color-mix(in lab,red,red)){.dark-mode{--scalar-tooltip-background:var(--scalar-background-1)}@supports (color:color-mix(in lab,red,red)){.dark-mode{--scalar-tooltip-background:color-mix(in srgb,var(--scalar-background-1),#fff 10%)}}}.dark-mode{--scalar-tooltip-color:#fffffff2;--scalar-color-danger:var(--scalar-color-red)}@supports (color:color-mix(in lab,red,red)){.dark-mode{--scalar-color-danger:var(--scalar-color-red)}@supports (color:color-mix(in lab,red,red)){.dark-mode{--scalar-color-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1)20%)}}}.dark-mode{--scalar-background-alert:var(--scalar-color-orange)}@supports (color:color-mix(in lab,red,red)){.dark-mode{--scalar-background-alert:var(--scalar-color-orange)}@supports (color:color-mix(in lab,red,red)){.dark-mode{--scalar-background-alert:color-mix(in srgb,var(--scalar-color-orange),var(--scalar-background-1)95%)}}}.dark-mode{--scalar-background-danger:var(--scalar-color-red)}@supports (color:color-mix(in lab,red,red)){.dark-mode{--scalar-background-danger:var(--scalar-color-red)}@supports (color:color-mix(in lab,red,red)){.dark-mode{--scalar-background-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1)95%)}}}@supports (color:color(display-p3 1 1 1)){.light-mode{--scalar-color-accent:color(display-p3 0 .6 1);--scalar-color-green:color(display-p3 .023529 .564706 .380392);--scalar-color-red:color(display-p3 .937255 0 .023529);--scalar-color-yellow:color(display-p3 .929412 .745098 .12549);--scalar-color-blue:color(display-p3 0 .509804 .815686);--scalar-color-orange:color(display-p3 1 .4 .02);--scalar-color-purple:color(display-p3 .321569 .011765 .819608)}.dark-mode{--scalar-color-accent:color(display-p3 .07 .67 1);--scalar-color-green:color(display-p3 0 .713725 .282353);--scalar-color-red:color(display-p3 .862745 .105882 .098039);--scalar-color-yellow:color(display-p3 1 .788235 .05098);--scalar-color-blue:color(display-p3 .305882 .701961 .92549);--scalar-color-orange:color(display-p3 1 .552941 .301961);--scalar-color-purple:color(display-p3 .694118 .568627 .976471)}}:root,:host{--leading-snug:1.375;--ease-in:cubic-bezier(.4,0,1,1);--ease-out:cubic-bezier(0,0,.2,1);--ease-in-out:cubic-bezier(.4,0,.2,1);--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1)}}@layer scalar-theme;.scalar-app .text-wrap{text-wrap:wrap}@media(hover:hover){.scalar-app .hover\\:bg-b-3:hover{background-color:var(--scalar-background-3)}}@supports (color:color-mix(in lab,red,red)){.scalar-app .to-b-1\\.5{--tw-gradient-to:var(--scalar-background-1)}@supports (color:color-mix(in lab,red,red)){.scalar-app .to-b-1\\.5{--tw-gradient-to:color-mix(in srgb,var(--scalar-background-1),var(--scalar-background-2))}}}@supports (color:color-mix(in lab,red,red)){:is(.scalar-app .\\*\\:border-border-tooltip>*){border-color:var(--scalar-tooltip-color)}@supports (color:color-mix(in lab,red,red)){:is(.scalar-app .\\*\\:border-border-tooltip>*){border-color:color-mix(in srgb,var(--scalar-tooltip-color),var(--scalar-tooltip-background))}}}.scalar-app .markdown summary:before{content:"";width:var(--markdown-spacing-md);height:var(--markdown-spacing-md);background-color:var(--scalar-color-3);flex-shrink:0;margin-top:5px;display:block;-webkit-mask-image:url(\'data:image/svg+xml,\');mask-image:url(\'data:image/svg+xml,\')}.scalar-app .markdown .markdown-alert.markdown-alert-note .markdown-alert-icon:before,.scalar-app .markdown .markdown-alert.markdown-alert-tip .markdown-alert-icon:before{color:var(--scalar-color-blue);-webkit-mask-image:url(\'data:image/svg+xml,\');mask-image:url(\'data:image/svg+xml,\')}.scalar-app .markdown .markdown-alert.markdown-alert-important .markdown-alert-icon:before,.scalar-app .markdown .markdown-alert.markdown-alert-warning .markdown-alert-icon:before{-webkit-mask-image:url(\'data:image/svg+xml,\');mask-image:url(\'data:image/svg+xml,\')}.scalar-app .markdown .markdown-alert.markdown-alert-caution .markdown-alert-icon:before{color:var(--scalar-color-red);-webkit-mask-image:url(\'data:image/svg+xml,\');mask-image:url(\'data:image/svg+xml,\')}.scalar-app .markdown .markdown-alert.markdown-alert-success .markdown-alert-icon:before{color:var(--scalar-color-green);-webkit-mask-image:url(\'data:image/svg+xml,\');mask-image:url(\'data:image/svg+xml,\')}.authenticationProvided[data-v-e3416cd5]{display:flex;align-items:center;gap:6px;color:var(--scalar-color-1);font-weight:var(--scalar-semibold);min-height:40px;font-size:var(--scalar-font-size-3);position:relative}.authenticationRequired[data-v-d15ef40b]{display:flex;align-items:center;gap:6px;color:var(--scalar-color-blue);font-weight:var(--scalar-semibold);min-height:40px;font-size:var(--scalar-font-size-3);position:relative}.askForAuthentication[data-v-169d0bc0]{display:flex;flex-direction:column;width:100%;position:relative;border-top:var(--scalar-border-width) solid var(--scalar-border-color);border-bottom:var(--scalar-border-width) solid var(--scalar-border-color);margin-bottom:12px;box-shadow:0 var(--scalar-border-width) 0 var(--scalar-background-1),0 calc(-1 * var(--scalar-border-width)) 0 var(--scalar-background-1);padding:0}.authContent[data-v-169d0bc0]{display:grid;grid-template-rows:0fr;min-height:0;overflow:hidden;transition:grid-template-rows .2s ease-out;max-width:520px;margin:auto;width:100%}.authContentInner[data-v-169d0bc0]>div{margin:36px 0 48px}.authContent[data-v-169d0bc0] .markdown{margin-bottom:0!important}.askForAuthentication.open .authContent[data-v-169d0bc0]{grid-template-rows:1fr}.continueButton[data-v-169d0bc0]{align-self:flex-end}.toggleButton[data-v-169d0bc0]{background:none;border:none;cursor:pointer;text-align:left;position:relative;display:flex;align-items:center;color:var(--scalar-color-3);justify-content:space-between;border-radius:var(--scalar-radius-lg)}.authContentInner[data-v-169d0bc0]{min-height:0;overflow:hidden}.authorizeButton[data-v-169d0bc0]{background:var(--scalar-color-blue)!important;color:#fff!important;margin:0!important;z-index:1;display:flex;gap:5px}.autosendPaused[data-v-d08225db]{display:flex;align-items:center;gap:6px;color:var(--scalar-color-blue);font-weight:var(--scalar-semibold);min-height:40px;font-size:var(--scalar-font-size-3);position:relative}.requestApproved[data-v-bb311586]{display:flex;align-items:center;gap:6px;color:var(--scalar-color-green);font-weight:var(--scalar-semibold);min-height:40px;font-size:var(--scalar-font-size-3);position:relative}.requestFailed[data-v-bc27e533]{display:flex;align-items:center;gap:6px;color:var(--scalar-color-red);font-weight:var(--scalar-semibold);min-height:40px;font-size:var(--scalar-font-size-3);position:relative}.requestFailedIcon[data-v-bc27e533]{box-shadow:inset 0 0 0 1.5px currentColor;padding:4px;width:16px;height:16px;border-radius:50%}.requestRejected[data-v-9803a54c]{display:flex;align-items:center;gap:6px;color:var(--scalar-color-red);font-weight:var(--scalar-semibold);min-height:40px;font-size:var(--scalar-font-size-3);position:relative}.requestSuccess[data-v-acc2c0d8]{display:flex;align-items:center;gap:6px;color:var(--scalar-color-1);font-weight:var(--scalar-semibold);min-height:40px;font-size:var(--scalar-font-size-3);position:relative}.light-mode .bg-preview[data-v-92f84612]{background-image:url("data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' width=\'16\' height=\'16\' fill=\'%23000\' fill-opacity=\'10%25\'%3E%3Crect width=\'8\' height=\'8\' /%3E%3Crect x=\'8\' y=\'8\' width=\'8\' height=\'8\' /%3E%3C/svg%3E")}.dark-mode .bg-preview[data-v-92f84612]{background-image:url("data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' width=\'16\' height=\'16\' fill=\'%23FFF\' fill-opacity=\'10%25\'%3E%3Crect width=\'8\' height=\'8\' /%3E%3Crect x=\'8\' y=\'8\' width=\'8\' height=\'8\' /%3E%3C/svg%3E")}.playIcon[data-v-65dc6dfb]{padding:4px;height:16px;width:16px;z-index:1;display:flex;align-items:center;justify-content:center;position:relative;background:var(--scalar-background-1);border-radius:50%}.playIcon[data-v-65dc6dfb]:before{content:"";width:16px;height:16px;display:inline-block;box-sizing:border-box;position:absolute;border-width:1.75px;border-style:solid;border-color:currentcolor currentcolor transparent;border-image:initial;border-radius:50%;background:var(--scalar-background-1);animation:.42s linear 0s infinite normal none running rotation-65dc6dfb}.sendingRequest[data-v-65dc6dfb]{display:flex;align-items:center;gap:6px;color:var(--scalar-color-blue);font-weight:var(--scalar-semibold);min-height:40px;font-size:var(--scalar-font-size-3);position:relative}.sendingRequest svg[data-v-65dc6dfb]{width:100%;height:100%;z-index:1;border-radius:50%}@keyframes rotation-65dc6dfb{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.requestHeaderContainer[data-v-f4245586]{display:flex;align-items:center;justify-content:space-between;padding:0 5px}.requestPreview[data-v-f4245586]{border-radius:12px;display:flex;flex-direction:column;width:100%;position:relative}.requestContent[data-v-f4245586]{display:grid;grid-template-rows:0fr;min-height:0;overflow:hidden;transition:grid-template-rows .2s ease-out}.requestPreview.open .requestContent[data-v-f4245586]{grid-template-rows:1fr}.requestPreview.succeeded[data-v-f4245586]{padding:0}.requestContentInner[data-v-f4245586]{min-height:0;overflow:hidden}.code[data-v-f4245586]{display:flex;flex-direction:column;font-size:var(--scalar-font-size-4);border-radius:12px;background:color-mix(in srgb,var(--scalar-background-2),var(--scalar-background-1));overflow:hidden;margin-bottom:12px}.dark-mode .code[data-v-f4245586]{background:var(--scalar-background-2)}.code h1[data-v-f4245586]{font-size:var(--scalar-font-size-3);color:var(--scalar-color-3);padding:8px}.code[data-v-f4245586] .codeBlock{max-height:calc(50vh - 100px);padding-top:0}.autosendContainer[data-v-f4245586]{display:flex;justify-content:space-between}.sendButton[data-v-f4245586]{background:var(--scalar-color-blue);color:#fff;font-weight:var(--scalar-semibold);padding:5px 10px}.sendButton[data-v-f4245586]:hover,.sendButton[data-v-f4245586]:active{background:color-mix(in srgb,var(--scalar-color-blue),black 10%);color:#fff!important}.toggleButton[data-v-f4245586]{background:none;border:none;cursor:pointer;text-align:left;position:relative;display:flex;align-items:center;color:var(--scalar-color-3);justify-content:space-between;border-radius:var(--scalar-radius-lg)}.toggleButton[data-v-f4245586]:hover{text-decoration:underline}.executeRequestTool[data-v-49d28052]{display:flex;flex-direction:column;gap:10px;border-top:var(--scalar-border-width) solid var(--scalar-border-color);border-bottom:var(--scalar-border-width) solid var(--scalar-border-color);margin-bottom:12px;box-shadow:0 var(--scalar-border-width) 0 var(--scalar-background-1),0 calc(-1 * var(--scalar-border-width)) 0 var(--scalar-background-1)}.tool[data-v-49d28052]{border:var(--scalar-border-width) solid var(--scalar-border-color);padding:15px;border-radius:15px;margin-bottom:20px}.contextItem[data-v-b6e5aa96]{font-size:10px;display:inline-block;color:var(--scalar-color-2);cursor:pointer;vertical-align:middle;border-radius:12px;padding:5px 10px;display:flex;align-items:center;background:color-mix(in srgb,var(--scalar-background-2),var(--scalar-background-1))}.shimmer[data-v-b6e5aa96]{background:var(--scalar-background-2);background-image:linear-gradient(90deg,#202020 0%,var(--scalar-background-2) 40%,var(--scalar-background-3) 80%);background-size:200% 100%;animation:shimmer-b6e5aa96 1.4s ease-in-out infinite}.light-mode .shimmer[data-v-b6e5aa96]{background:var(--scalar-background-2);background-image:linear-gradient(90deg,#fafafa 0%,var(--scalar-background-2) 40%,var(--scalar-background-3) 80%);background-size:200% 100%;animation:shimmer-b6e5aa96 1.4s ease-in-out infinite}@keyframes shimmer-b6e5aa96{0%{background-position:200% 0}to{background-position:-200% 0}}.playIcon[data-v-653c66b3]{padding:4px;height:16px;width:16px;z-index:1;display:flex;align-items:center;justify-content:center;position:relative;background:var(--scalar-background-1);border-radius:50%}.playIcon[data-v-653c66b3]:before{content:"";width:16px;height:16px;display:inline-block;box-sizing:border-box;position:absolute;border-width:1.5px;border-style:solid;border-color:currentcolor currentcolor transparent;border-image:initial;border-radius:50%;background:var(--scalar-background-1);animation:.42s linear 0s infinite normal none running rotation-653c66b3}.sendingRequest[data-v-653c66b3]{display:flex;align-items:center;gap:6px;color:var(--scalar-color-2);font-weight:var(--scalar-semibold);font-size:var(--scalar-font-size-3);margin-bottom:10px}.sendingRequest svg[data-v-653c66b3]{width:100%;height:100%;z-index:1;border-radius:50%}@keyframes rotation-653c66b3{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.operations[data-v-ecee6203]{display:flex;gap:5px;align-items:center;margin-bottom:12px}.operations[data-v-ecee6203]:empty{margin-bottom:-12px}.playIcon[data-v-9d9724d2]{padding:4px;height:16px;width:16px;z-index:1;display:flex;align-items:center;justify-content:center;position:relative;background:var(--scalar-background-1);border-radius:50%}.playIcon[data-v-9d9724d2]:before{content:"";width:16px;height:16px;display:inline-block;box-sizing:border-box;position:absolute;border-width:1.5px;border-style:solid;border-color:currentcolor currentcolor transparent;border-image:initial;border-radius:50%;background:var(--scalar-background-1);animation:.42s linear 0s infinite normal none running rotation-9d9724d2}.loadingApiSpecs[data-v-9d9724d2]{display:flex;align-items:center;gap:6px;color:var(--scalar-color-2);font-weight:var(--scalar-semibold);font-size:var(--scalar-font-size-3);margin-bottom:10px}.loadingApiSpecs svg[data-v-9d9724d2]{width:100%;height:100%;z-index:1;border-radius:50%}@keyframes rotation-9d9724d2{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.catalogModal .scalar-modal-body{display:flex;flex-direction:column}.searchInput[data-v-bc24f891]{border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:var(--scalar-radius-lg);margin-bottom:10px}.catalog[data-v-bc24f891]{display:grid;grid-template-columns:1fr 1fr;gap:10px;overflow-y:scroll;font-size:var(--scalar-font-size-3)}.item[data-v-bc24f891]{display:flex;padding:15px;gap:10px;align-items:center;background-color:var(--scalar-background-2);border-radius:var(--scalar-radius-lg);transition:background-color .16s ease}.item[data-v-bc24f891]:hover{background-color:color-mix(in srgb,var(--scalar-background-3),transparent 40%)!important}.left[data-v-bc24f891]{align-items:center}.right[data-v-bc24f891]{display:flex;flex-direction:column}.logo[data-v-bc24f891]{width:25px}.item-top[data-v-bc24f891]{display:flex;gap:10px}.version[data-v-bc24f891]{background:var(--scalar-background-3);padding:2px 5px;border-radius:var(--scalar-radius);font-size:var(--scalar-font-size-5);color:var(--scalar-color-3)}.description[data-v-bc24f891]{color:var(--scalar-color-2)}.dropdown-item[data-v-2d142bb5]{display:flex;align-items:center;gap:10px}.approvalSection[data-v-a7e6c699]{width:100%;margin-bottom:-16px;padding:8px 8px 24px 12px;background:color-mix(in srgb,var(--scalar-color-blue),var(--scalar-background-1) 95%);border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:16px 16px 0 0;display:flex;align-items:center;justify-content:space-between;position:absolute;top:0;transform:translate3d(0,calc(-100% + 16px),0)}.approvalText[data-v-a7e6c699]{font-weight:var(--scalar-semibold);font-size:var(--scalar-font-size-3)}.approveContainer[data-v-a7e6c699]{display:flex;gap:5px}.actionButton[data-v-a7e6c699]{display:flex;align-items:center;font-weight:var(--scalar-semibold);border-radius:50px;padding:6px 12px;font-size:var(--scalar-font-size-3)}.rejectButton[data-v-a7e6c699]{color:#fff;background:var(--scalar-color-red)}.rejectButton[data-v-a7e6c699]:hover,.rejectButton[data-v-a7e6c699]:active{background:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1) 10%);color:#fff!important}.approveButton[data-v-a7e6c699]{color:#fff;background:var(--scalar-color-blue)}.approveButton[data-v-a7e6c699]:hover,.approveButton[data-v-a7e6c699]:active{background:color-mix(in srgb,var(--scalar-color-blue),var(--scalar-background-1) 10%);color:#fff!important}.error[data-v-63a481da]{display:flex;align-items:center;margin-bottom:-16px;padding:8px 8px 24px 12px;border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:16px 16px 0 0;background:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1) 95%);font-weight:var(--scalar-semibold);font-size:var(--scalar-font-size-3);position:absolute;top:0;transform:translate3d(0,calc(-100% + 16px),0)}.freeMessagesInfoSection[data-v-593b8dcb]{width:100%;margin-bottom:-16px;padding:8px 8px 24px 12px;position:relative;background:color-mix(in srgb,var(--scalar-color-blue),var(--scalar-background-1) 95%);border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:16px 16px 0 0;display:flex;align-items:center;justify-content:space-between}.infoText[data-v-593b8dcb]{font-weight:var(--scalar-semibold);font-size:var(--scalar-font-size-3)}.actionsContainer[data-v-593b8dcb]{display:flex;align-items:center;gap:8px}.actionButton[data-v-593b8dcb]{display:flex;align-items:center;font-weight:var(--scalar-semibold);border-radius:50px;padding:6px 12px}.upgradeButton[data-v-593b8dcb]{color:#fff;font-size:var(--scalar-font-size-3);background:var(--scalar-color-blue)}.upgradeButton[data-v-593b8dcb]:hover,.upgradeButton[data-v-593b8dcb]:active{background:color-mix(in srgb,var(--scalar-color-blue),var(--scalar-background-1) 10%);color:#fff!important}.closeButton[data-v-593b8dcb]{display:flex;align-items:center;justify-content:center;width:28px;height:28px;border-radius:50%;color:var(--scalar-color-2);background:transparent;border:none;cursor:pointer}.closeButton[data-v-593b8dcb]:hover{background:color-mix(in srgb,var(--scalar-color-blue),var(--scalar-background-1) 80%);color:var(--scalar-color-1)}.paymentSection[data-v-59a0be07]{width:100%;margin-bottom:-16px;padding:8px 8px 24px 12px;position:relative;background:color-mix(in srgb,var(--scalar-color-blue),var(--scalar-background-1) 95%);border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:16px 16px 0 0;display:flex;align-items:center;justify-content:space-between;position:absolute;top:0;transform:translate3d(0,calc(-100% + 16px),0)}.approvalText[data-v-59a0be07]{font-weight:var(--scalar-semibold);font-size:var(--scalar-font-size-3)}.paymentContainer[data-v-59a0be07]{display:flex;gap:5px}.actionButton[data-v-59a0be07]{display:flex;align-items:center;font-weight:var(--scalar-semibold);border-radius:50px;padding:6px 12px}.rejectButton[data-v-59a0be07]{color:#fff;background:var(--scalar-color-red)}.rejectButton[data-v-59a0be07]:hover,.rejectButton[data-v-59a0be07]:active{background:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1) 10%);color:#fff!important}.approveButton[data-v-59a0be07]{color:#fff;font-size:var(--scalar-font-size-3);background:var(--scalar-color-blue)}.approveButton[data-v-59a0be07]:hover,.approveButton[data-v-59a0be07]:active{background:color-mix(in srgb,var(--scalar-color-blue),var(--scalar-background-1) 10%);color:#fff!important}.paymentInfo[data-v-59a0be07]{width:300px;position:absolute;right:0;bottom:70px;box-shadow:var(--scalar-shadow-2);background:var(--scalar-background-1);border-radius:16px;pointer-events:none;padding:12px;transform:translate3d(0,-5px,0);opacity:0;transition:all .2s ease-in-out}.paymentInfo h3[data-v-59a0be07]{font-size:var(--scalar-font-size-1);font-weight:var(--scalar-bold);margin-bottom:18px}.paymentInfo h3 span[data-v-59a0be07]{font-size:var(--scalar-font-size-2)}.dark-mode .paymentInfo[data-v-59a0be07]{background:var(--scalar-background-2)}.paymentContainer:hover .paymentInfo[data-v-59a0be07]{transform:translateZ(0);opacity:1}.paymentInfoItem[data-v-59a0be07]{display:flex;justify-content:space-between;margin-top:8px;font-size:var(--scalar-font-size-3);color:var(--scalar-color-2);font-weight:var(--scalar-semibold)}.paymentInfoSection[data-v-59a0be07]:not(:last-child){border-bottom:var(--scalar-border-width) solid var(--scalar-border-color);padding-bottom:8px}.searchItem[data-v-7945f74c]{display:flex;align-items:center;gap:9px;padding:8px 10px;font-size:var(--scalar-font-size-3)}.searchInput[data-v-7945f74c]{margin-bottom:5px}.searchItem[data-v-7945f74c]:hover{background:var(--scalar-background-2)}.searchItemLogo[data-v-7945f74c]{width:15px}.searchIcon[data-v-7945f74c]{margin-right:7px}.searchResultsEmpty[data-v-7945f74c]{font-size:var(--scalar-font-size-3);color:var(--scalar-color-2);margin:10px}.uploadSection[data-v-f9d3a579]{width:100%;margin-bottom:-16px;padding:8px 8px 24px 12px;position:relative;background:color-mix(in srgb,var(--scalar-color-blue),var(--scalar-background-1) 95%);border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:16px 16px 0 0;display:flex;align-items:center;justify-content:space-between;position:absolute;top:0;transform:translate3d(0,calc(-100% + 16px),0)}.uploadSection.error[data-v-f9d3a579]{background:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1) 95%)}.uploadSection.done[data-v-f9d3a579]{background:color-mix(in srgb,var(--scalar-color-green),var(--scalar-background-1) 95%)}.uploadText[data-v-f9d3a579]{font-weight:var(--scalar-semibold);font-size:var(--scalar-font-size-3)}.icon[data-v-f9d3a579]{height:20px;width:20px}.actionContainer[data-v-3899441c]{background:color-mix(in srgb,var(--scalar-background-2),var(--scalar-background-1));border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:16px;width:100%;position:relative;box-shadow:0 24px 0 2px var(--scalar-background-1)}.promptForm[data-v-3899441c]{width:100%;position:relative;display:flex;flex-direction:column;background:var(--scalar-background-1);box-shadow:var(--scalar-shadow-1),0 0 0 var(--scalar-border-width) var(--scalar-border-color);border-radius:16px}.inputActionsContainer[data-v-3899441c]{display:flex;justify-content:space-between;padding:0 8px 8px}.inputActionsLeft[data-v-3899441c]{display:flex;flex-wrap:wrap;align-items:center;gap:5px}.inputActionsRight[data-v-3899441c]{display:flex;gap:5px;position:relative}.apiPill[data-v-3899441c]{font-size:var(--scalar-font-size-3);border:var(--scalar-border-width) solid var(--scalar-border-color);color:var(--scalar-color-2);font-weight:var(--scalar-semibold);height:28px;align-items:center;display:flex;border-radius:16px;padding:0 8px;pointer-events:all;z-index:1;gap:4px;-webkit-user-select:none;user-select:none}.apiPillLogo[data-v-3899441c]{width:15px}.apiPillRemove[data-v-3899441c]{width:24px;height:24px;margin-right:-6px;border-radius:50%;display:flex;align-items:center;justify-content:center}.apiPill:hover .apiPillRemove[data-v-3899441c]{background:var(--scalar-background-2)}.dark-mode .apiPill:hover .apiPillRemove[data-v-3899441c]{background:var(--scalar-background-3)}.apiPillRemove[data-v-3899441c]:hover{color:var(--scalar-color-1)}.prompt[data-v-3899441c]{width:100%;outline:none;border:none;resize:none;field-sizing:content;min-height:64px;z-index:1;max-height:250px;max-width:100%;overflow-y:auto;scrollbar-width:thin;word-wrap:break-word;font-family:var(--scalar-font);font-size:16px;padding:12px 12px 14px}.dark-mode .promptForm[data-v-3899441c]{background:var(--scalar-background-2)}.prompt[data-v-3899441c]:disabled{color:var(--scalar-color-3)}.addAPIButton[data-v-3899441c]{justify-content:center;color:var(--scalar-color-2);font-size:var(--scalar-font-size-3);height:28px;width:28px;font-weight:var(--scalar-bold);border-radius:100%;display:flex;align-items:center;gap:4px;pointer-events:all;z-index:1;box-shadow:0 0 0 var(--scalar-border-width) var(--scalar-border-color)}.addAPIButton[data-v-3899441c]:hover{background:color-mix(in srgb,var(--scalar-background-2),var(--scalar-background-1));box-shadow:0 0 0 var(--scalar-border-width) var(--scalar-border-color)}.dark-mode .addAPIButton[data-v-3899441c]:hover{background:var(--scalar-background-3)}.settingsButton[data-v-3899441c]{color:var(--scalar-color-3)!important;border-radius:50%!important;margin:0!important;z-index:1}.settingsButton[aria-disabled=true][data-v-3899441c]{background:var(--scalar-background-2)}.dark-mode .settingsButton[data-v-3899441c]:hover{background:var(--scalar-background-3)}.sendButton[data-v-3899441c]{background:var(--scalar-color-blue)!important;border-radius:50%!important;margin:0!important;z-index:1;border:var(--scalar-border-width) solid var(--scalar-color-blue)}.sendButton[data-v-3899441c]:not([aria-disabled=true]){color:#fff!important}.sendButton[data-v-3899441c]:not([aria-disabled=true]):hover{background:color-mix(in srgb,var(--scalar-color-blue),transparent 10%)!important}.sendButton[aria-disabled=true][data-v-3899441c]{background:var(--scalar-background-2)!important;color:var(--scalar-color-3)!important;border:var(--scalar-border-width) solid var(--scalar-border-color)}.dark-mode .sendButton[aria-disabled=true][data-v-3899441c]{background:var(--scalar-background-3)!important}.contextContainer[data-v-3899441c]{display:flex;width:100%;padding:10px 12px 12px;color:var(--scalar-color-2);font-size:var(--scalar-font-size-3);-webkit-user-select:none;user-select:none;justify-content:space-between}.settingsButton[data-v-3899441c]{font-weight:var(--scalar-semibold);border-radius:var(--scalar-radius-lg);padding:4px 6px;margin:-4px -6px}.settingsButton[data-v-3899441c]:hover{background:var(--scalar-background-2);box-shadow:0 0 var(--scalar-border-width) 0 var(--scalar-border-color);cursor:pointer}.agentLabel[data-v-3899441c]{font-size:0px;position:absolute;width:100%;height:100%;cursor:text}.sendCheckboxContinue[data-v-3899441c]:has(input){display:flex;align-items:center;border-radius:14px;background:var(--scalar-background-2);box-shadow:0 0 0 1.5px var(--scalar-background-2);color:var(--scalar-color-2);font-size:var(--scalar-font-size-3);font-weight:var(--scalar-semibold);-webkit-user-select:none;user-select:none;height:28px}.dark-mode .sendCheckboxContinue[data-v-3899441c]:has(input){background:var(--scalar-background-3);box-shadow:0 0 0 1.5px var(--scalar-background-3)}.addMoreContext[data-v-3899441c]{height:40px;display:flex;position:relative;font-size:var(--scalar-font-size-3);color:var(--scalar-color-3);padding:0 8px 0 12px;align-items:center}.addMoreContext[data-v-3899441c]:before{content:"";width:8px;height:8px;background:color-mix(in srgb,var(--scalar-background-1),var(--scalar-background-2));transform:rotate(45deg);left:18px;top:-3px;position:absolute;box-shadow:-.5px -.5px 0 var(--scalar-border-color),inset .5px .5px 1px var(--scalar-border-color)}.dark-mode .addMoreContext[data-v-3899441c]:before{box-shadow:-.5px -.5px 0 var(--scalar-border-color)}.addAPIContext[data-v-3899441c]{width:28px;height:28px;border-radius:50%;display:flex;align-items:center;justify-content:center;border:var(--scalar-border-width) solid var(--scalar-border-color)}.termsAgree[data-v-3899441c]{display:flex;cursor:pointer;height:inherit;align-items:center;border-radius:14px;gap:5px;margin:0 5px}.termsAgree[data-v-3899441c]:hover{color:var(--scalar-color-1)}.termsAgree:hover .termsAgreeIcon[data-v-3899441c]{background:var(--scalar-color-1);color:var(--scalar-background-1)}.termsAgreeIcon[data-v-3899441c]{width:inherit;height:inherit;padding:2px;border-radius:50%;background:var(--scalar-background-2)}.chat[data-v-41e50a16]{flex:1;display:flex;flex-direction:column;width:100%;padding:24px 0;max-width:744px}.userMessage[data-v-41e50a16]{padding-top:6px;padding-bottom:6px;padding-inline:16px;border-radius:18px;background:var(--scalar-background-2);width:fit-content;max-width:80%;margin-left:auto;font-size:16px;line-height:24px;color:var(--scalar-color-1);margin-bottom:12px}div+.userMessage[data-v-41e50a16]{margin-top:64px}.chat[data-v-41e50a16]>div:has(.executeRequestTool)+div:has(.executeRequestTool){margin-top:-12px}.spacer[data-v-41e50a16]{min-height:280px;width:100%}.formContainer[data-v-41e50a16]{position:sticky;bottom:20px;width:100%;max-width:744px;z-index:1}.chat[data-v-41e50a16] .markdown{margin-bottom:12px}.agentLogo[data-v-1fbb0eb5]{margin-bottom:15px}.startContainer[data-v-1fbb0eb5]{display:flex;flex-direction:column;align-items:center;justify-content:center;width:100%;height:100%;max-width:720px;position:relative}.heading[data-v-1fbb0eb5]{font-size:1.5rem;font-weight:var(--scalar-font-bold);margin-bottom:50px}.suggestionsContainer[data-v-1fbb0eb5]{display:flex;justify-content:center;gap:10px;flex-wrap:wrap;margin:25px 0}.disclaimerText[data-v-1fbb0eb5]{text-align:center;color:var(--scalar-color-3);font-size:var(--scalar-font-size-3);text-wrap:balance;line-height:1.44;margin-top:40px}.disclaimerLink[data-v-1fbb0eb5]{text-decoration:underline}.wrapper[data-v-f1eee0af]{display:flex;flex-direction:column;align-items:center;height:100%;width:100%}.docSettings[data-v-2dfa823a]{display:flex;flex-direction:column;gap:12px;margin-bottom:12px;font-size:var(--scalar-font-size-3);max-height:600px}.documentName[data-v-2dfa823a]{font-weight:var(--scalar-semibold)}.settingsModal .scalar-modal-layout{z-index:10!important}.settingsModal .scalar-modal-body{overflow-y:scroll;overflow-x:hidden}.documentList[data-v-745651bc]{display:flex;flex-direction:column;font-size:var(--scalar-font-size-3);margin-bottom:12px}.document[data-v-745651bc]{display:flex;flex-direction:column;width:calc(100% + 24px);left:-12px;position:relative;padding:0 12px;border-top:var(--scalar-border-width) solid var(--scalar-border-color);border-bottom:var(--scalar-border-width) solid var(--scalar-border-color)}.document[data-v-745651bc]:first-of-type:not(:last-of-type){border-bottom:none}.documentName[data-v-745651bc]{gap:4px;display:flex;align-items:center;font-weight:var(--scalar-semibold);color:var(--scalar-color-2);padding:12px 0}.documentNameActive[data-v-745651bc]{color:var(--scalar-color-1)}.settingsHeading[data-v-745651bc]{font-size:19px;margin-bottom:12px;display:flex;gap:5px;align-items:center;font-weight:var(--scalar-semibold)}.proxyUrlContainer[data-v-745651bc]{font-size:var(--scalar-font-size-3);display:flex;gap:5px;flex-direction:column}.proxyUrlContainer label[data-v-745651bc]{font-weight:var(--scalar-semibold)}.noDocuments[data-v-745651bc]{color:var(--scalar-color-2);margin-bottom:10px}.agent-scalar[data-v-b45a83b1]{position:fixed;top:0;left:0;width:calc(100% - 50px);height:100dvh;background:var(--scalar-background-1);border-right:var(--scalar-border-width) solid var(--scalar-border-color);transform:translate3d(calc(-100% + var(--scalar-sidebar-width, 288px)),0,0);z-index:2;animation:.35s forwards scalaragentslidein-b45a83b1;box-shadow:var(--scalar-shadow-2)}.agent-scalar-container[data-v-b45a83b1]{width:calc(100% - var(--scalar-sidebar-width, 288px));height:100%;margin-left:auto;overflow:auto;padding:0 24px}.scalar-app-exit[data-v-b45a83b1]{cursor:pointer;z-index:2;width:100vw;height:100vh;transition:all .3s ease-in-out;position:fixed;top:0;left:0}@media(max-width:1000px){.agent-scalar-container[data-v-b45a83b1]{width:100%}.agent-scalar[data-v-b45a83b1]{width:100%;height:calc(100dvh - 50px);bottom:0;top:initial;border-radius:var(--scalar-radius-lg) var(--scalar-radius-lg) 0 0;z-index:12}.scalar-app-exit[data-v-b45a83b1]{z-index:11}}.scalar-app-exit-animation[data-v-b45a83b1]:before{content:"";position:absolute;width:100%;height:100%;background:#00000038;animation:.5s forwards scalardrawerexitfadein-b45a83b1;animation-timing-function:cubic-bezier(.77,0,.175,1)}.dark-mode .scalar .scalar-app-exit-animation[data-v-b45a83b1]:before{background:#00000073}@keyframes scalaragentslidein-b45a83b1{0%{transform:translate3d(calc(-100% + var(--scalar-sidebar-width, 288px)),0,0)}to{transform:translateZ(0)}}@keyframes scalardrawerexitfadein-b45a83b1{0%{opacity:0}to{opacity:1}}.app-exit-button[data-v-b45a83b1]{color:#fff;background:#0000001a}.app-exit-button[data-v-b45a83b1]:hover{background:#ffffff1a}.references-classic-header[data-v-9198d025]{display:flex;align-items:center;gap:12px;max-width:var(--refs-content-max-width);margin:auto;padding:12px 0}.references-classic-header-content[data-v-9198d025]{display:flex;gap:12px;flex-grow:1}.references-classic-header-container[data-v-9198d025]{padding:0 60px}@container narrow-references-container (max-width: 900px){.references-classic-header[data-v-9198d025]{padding:12px 24px}.references-classic-header-container[data-v-9198d025]{padding:0}}.references-classic-header-icon[data-v-9198d025]{height:24px;color:var(--scalar-color-1)}.client-libraries-content[data-v-6a49c111]{container:client-libraries-content / inline-size;display:flex;justify-content:center;overflow:hidden;padding:0 12px;background-color:var(--scalar-background-1);border-left:var(--scalar-border-width) solid var(--scalar-border-color);border-right:var(--scalar-border-width) solid var(--scalar-border-color)}.client-libraries[data-v-6a49c111]{display:flex;align-items:center;justify-content:center;width:100%;position:relative;cursor:pointer;white-space:nowrap;padding:8px 2px;gap:6px;color:var(--scalar-color-3);border-bottom:1px solid transparent;-webkit-user-select:none;user-select:none}.client-libraries[data-v-6a49c111]:not(.client-libraries__active):hover:before{content:"";position:absolute;width:calc(100% - 4px);height:calc(100% - 4px);background:var(--scalar-background-2);left:2px;top:2px;z-index:0;border-radius:var(--scalar-radius)}.client-libraries[data-v-6a49c111]:active{color:var(--scalar-color-1)}.client-libraries[data-v-6a49c111]:focus-visible{outline:none;box-shadow:inset 0 0 0 1px var(--scalar-color-accent)}@media screen and (max-width:450px){.client-libraries[data-v-6a49c111]:nth-of-type(4),.client-libraries[data-v-6a49c111]:nth-of-type(5){display:none}}.client-libraries-icon[data-v-6a49c111]{max-width:14px;max-height:14px;min-width:14px;width:100%;aspect-ratio:1;display:flex;align-items:center;justify-content:center;position:relative;box-sizing:border-box;color:currentColor}.client-libraries-icon__more svg[data-v-6a49c111]{height:initial}@container client-libraries-content (width < 400px){.client-libraries__select[data-v-6a49c111]{width:fit-content}.client-libraries__select .client-libraries-icon__more+span[data-v-6a49c111]{display:none}}@container client-libraries-content (width < 380px){.client-libraries[data-v-6a49c111]{width:100%}.client-libraries span[data-v-6a49c111]{display:none}}.client-libraries__active[data-v-6a49c111]{color:var(--scalar-color-1);border-bottom:1px solid var(--scalar-color-1)}@keyframes codeloader-6a49c111{0%{transform:rotate(0)}to{transform:rotate(1turn)}}.client-libraries .client-libraries-text[data-v-6a49c111]{font-size:var(--scalar-small);position:relative;display:flex;align-items:center}.client-libraries__active .client-libraries-text[data-v-6a49c111]{color:var(--scalar-color-1);font-weight:var(--scalar-semibold)}@media screen and (max-width:600px){.references-classic .client-libraries[data-v-6a49c111]{flex-direction:column}}.selected-client[data-v-dd2e9b07]{color:var(--scalar-color-1);font-size:var(--scalar-small);font-family:var(--scalar-font-code);padding:9px 12px;border-top:none;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;background:var(--scalar-background-1);border:var(--scalar-border-width) solid var(--scalar-border-color);border-bottom-left-radius:var(--scalar-radius-xl);border-bottom-right-radius:var(--scalar-radius-xl);min-height:fit-content}.client-libraries-heading[data-v-dd2e9b07]{font-size:var(--scalar-small);font-weight:var(--scalar-font-medium);color:var(--scalar-color-1);padding:9px 12px;background-color:var(--scalar-background-2);display:flex;align-items:center;max-height:32px;border:var(--scalar-border-width) solid var(--scalar-border-color);border-top-left-radius:var(--scalar-radius-xl);border-top-right-radius:var(--scalar-radius-xl)}[data-v-dd2e9b07] .scalar-codeblock-pre .hljs{margin-top:8px}.badge[data-v-3dedb7e4]{color:var(--badge-text-color, var(--scalar-color-2));font-size:var(--scalar-mini);background:var(--badge-background-color, var(--scalar-background-2));border:var(--scalar-border-width) solid var(--badge-border-color, var(--scalar-border-color));padding:2px 6px;border-radius:12px;display:inline-block}.badge.text-orange[data-v-3dedb7e4]{background:color-mix(in srgb,var(--scalar-color-orange),transparent 90%);border:transparent}.badge.text-yellow[data-v-3dedb7e4]{background:color-mix(in srgb,var(--scalar-color-yellow),transparent 90%);border:transparent}.badge.text-red[data-v-3dedb7e4]{background:color-mix(in srgb,var(--scalar-color-red),transparent 90%);border:transparent}.badge.text-purple[data-v-3dedb7e4]{background:color-mix(in srgb,var(--scalar-color-purple),transparent 90%);border:transparent}.badge.text-green[data-v-3dedb7e4]{background:color-mix(in srgb,var(--scalar-color-green),transparent 90%);border:transparent}@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){[data-v-59b5011b],[data-v-59b5011b]:before,[data-v-59b5011b]:after,[data-v-59b5011b]::backdrop{--tw-outline-style:solid}}}.download-container[data-v-59b5011b]{z-index:1;flex-direction:column;gap:16px;width:fit-content;margin:0 .5px 8px;display:flex;position:relative}.download-container[data-v-59b5011b]:has(:focus-visible):before,.download-container.download-both[data-v-59b5011b]:hover:before{content:"";border-radius:var(--scalar-radius-lg);width:calc(100% + 24px);height:90px;box-shadow:var(--scalar-shadow-2);pointer-events:none;background:var(--scalar-background-1);position:absolute;top:-11px;left:-12px}.download-button[data-v-59b5011b]{color:var(--scalar-link-color);cursor:pointer;outline:none;justify-content:center;align-items:center;gap:4px;height:fit-content;padding:0;display:flex;position:relative;white-space:nowrap!important}.download-button[data-v-59b5011b]:before{border-radius:var(--scalar-radius);content:"";width:calc(100% + 18px);height:calc(100% + 16px);position:absolute;top:-8px;left:-9px}.download-button[data-v-59b5011b]:last-of-type:before{width:calc(100% + 15px)}.download-button[data-v-59b5011b]:hover:before{background:var(--scalar-background-2);border:var(--scalar-border-width)solid var(--scalar-border-color)}.download-button[data-v-59b5011b]:focus-visible:before{background:var(--scalar-background-2);border:var(--scalar-border-width)solid var(--scalar-border-color);outline-style:var(--tw-outline-style);outline-width:1px}.download-button span[data-v-59b5011b]{--font-color:var(--scalar-link-color,var(--scalar-color-accent));--font-visited:var(--scalar-link-color-visited,var(--scalar-color-2));-webkit-text-decoration:var(--scalar-text-decoration);text-decoration:var(--scalar-text-decoration);color:var(--font-color);font-weight:var(--scalar-link-font-weight,var(--scalar-semibold));text-underline-offset:.25rem;text-decoration-thickness:1px;-webkit-text-decoration-color:var(--font-color);text-decoration-color:var(--font-color)}@supports (color:color-mix(in lab,red,red)){.download-button span[data-v-59b5011b]{-webkit-text-decoration-color:color-mix(in srgb,var(--font-color)30%,transparent);text-decoration-color:color-mix(in srgb,var(--font-color)30%,transparent)}}.download-button span[data-v-59b5011b]{z-index:1;align-items:center;gap:6px;line-height:1.625;display:flex}.download-button:hover span[data-v-59b5011b]{-webkit-text-decoration-color:var(--scalar-color-1,currentColor);text-decoration-color:var(--scalar-color-1,currentColor);color:var(--scalar-link-color-hover,var(--scalar-color-accent));-webkit-text-decoration:var(--scalar-text-decoration-hover);text-decoration:var(--scalar-text-decoration-hover)}.download-button[data-v-59b5011b]:nth-of-type(2){clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.download-container:has(:focus-visible) .download-button[data-v-59b5011b]:nth-of-type(2),.download-container:hover .download-button[data-v-59b5011b]:nth-of-type(2){clip-path:none;white-space:normal;width:auto;height:auto;margin:0;padding:0;position:absolute;top:42px;overflow:visible}.extension[data-v-59b5011b]{z-index:1;background:var(--scalar-link-color,var(--scalar-color-accent));color:var(--scalar-background-1)}.download-container:has(:focus-visible) .extension[data-v-59b5011b],.download-container:hover .extension[data-v-59b5011b]{opacity:1}.download-link[data-v-59b5011b]{--font-color:var(--scalar-link-color,var(--scalar-color-accent));--font-visited:var(--scalar-link-color-visited,var(--scalar-color-2));-webkit-text-decoration:var(--scalar-text-decoration);text-decoration:var(--scalar-text-decoration);color:var(--font-color);font-weight:var(--scalar-link-font-weight,var(--scalar-semibold));text-underline-offset:.25rem;text-decoration-thickness:1px;-webkit-text-decoration-color:var(--font-color);text-decoration-color:var(--font-color)}@supports (color:color-mix(in lab,red,red)){.download-link[data-v-59b5011b]{-webkit-text-decoration-color:color-mix(in srgb,var(--font-color)30%,transparent);text-decoration-color:color-mix(in srgb,var(--font-color)30%,transparent)}}.download-link[data-v-59b5011b]:hover{--font-color:var(--scalar-link-color,var(--scalar-color-accent));-webkit-text-decoration-color:var(--font-color);text-decoration-color:var(--font-color)}.introduction-card[data-v-5764c94a]{display:flex;flex-direction:column;gap:12px}.introduction-card-row[data-v-5764c94a]{gap:24px}@media(min-width:600px){.introduction-card-row[data-v-5764c94a]{flex-flow:row wrap}}.introduction-card-row[data-v-5764c94a]>*{flex:1}@media(min-width:600px){.introduction-card-row[data-v-5764c94a]>*{min-width:min-content}}@media(max-width:600px){.introduction-card-row[data-v-5764c94a]>*{max-width:100%}}@container (max-width: 900px){.introduction-card-row[data-v-5764c94a]{flex-direction:column;align-items:stretch;gap:0px}}.introduction-card[data-v-5764c94a] .security-scheme-label{text-transform:uppercase;font-weight:var(--scalar-semibold)}.introduction-card-row[data-v-5764c94a] .scalar-card:nth-of-type(2) .scalar-card-header{display:none}.introduction-card-row[data-v-5764c94a] .scalar-card:nth-of-type(2) .scalar-card-header.scalar-card--borderless+.scalar-card-content{margin-top:0}.section[data-v-be4443e9]{position:relative;display:flex;flex-direction:column;max-width:var(--refs-content-max-width);margin:auto;padding:90px 0;scroll-margin-top:var(--refs-viewport-offset)}.section[data-v-be4443e9]:has(~div.contents){border-bottom:var(--scalar-border-width) solid var(--scalar-border-color)}.references-classic .section[data-v-be4443e9]{padding:48px 0;gap:24px}@container narrow-references-container (max-width: 900px){.references-classic .section[data-v-be4443e9],.section[data-v-be4443e9]{padding:48px 24px}}.section[data-v-be4443e9]:not(:last-of-type){border-bottom:var(--scalar-border-width) solid var(--scalar-border-color)}.section-wrapper[data-v-ff689b94]{color:var(--scalar-color-1);padding-top:12px;margin-top:-12px}.section-accordion[data-v-ff689b94]{display:flex;flex-direction:column;border-radius:var(--scalar-radius-lg);background:var(--scalar-background-2);scroll-margin-top:var(--refs-viewport-offset)}.section-accordion-transparent[data-v-ff689b94]{background:transparent;border:var(--scalar-border-width) solid var(--scalar-border-color)}.section-accordion-button[data-v-ff689b94]{padding:6px}.section-accordion-button[data-v-ff689b94]{display:flex;align-items:center;gap:6px;cursor:pointer}.section-accordion-button-content[data-v-ff689b94]{flex:1;min-width:0}.section-accordion-button-actions[data-v-ff689b94]{display:flex;align-items:center;gap:6px;color:var(--scalar-color-3)}.section-accordion-chevron[data-v-ff689b94]{margin-right:4px;cursor:pointer;opacity:1;color:var(--scalar-color-3)}.section-accordion-button:hover .section-accordion-chevron[data-v-ff689b94]{color:var(--scalar-color-1)}.section-accordion-content[data-v-ff689b94]{border-top:var(--scalar-border-width) solid var(--scalar-border-color);display:flex;flex-direction:column}.section-accordion-description[data-v-ff689b94]{font-weight:var(--scalar-semibold);font-size:var(--scalar-mini);color:var(--scalar-color--1);padding:10px 12px 0}.section-accordion-content-card[data-v-ff689b94] .property:last-of-type{padding-bottom:9px}.section-column[data-v-699c28e3]{flex:1;min-width:0}@container narrow-references-container (max-width: 900px){.section-column[data-v-699c28e3]:nth-of-type(2){padding-top:0}}.section-columns[data-v-8b9602bf]{display:flex;gap:48px}@container narrow-references-container (max-width: 900px){.section-columns[data-v-8b9602bf]{flex-direction:column;gap:24px}}.section-container[data-v-20a1472a]{position:relative;padding:0 60px;width:100%;border-top:var(--scalar-border-width) solid var(--scalar-border-color)}.section-container[data-v-20a1472a]:has(.introduction-section){border-top:none}@container narrow-references-container (max-width: 900px){.section-container[data-v-20a1472a]{padding:0}}.section-accordion-wrapper[data-v-9419dd23]{padding:0 60px}.section-accordion[data-v-9419dd23]{position:relative;width:100%;max-width:var(--refs-content-max-width);margin:auto}.section-accordion-content[data-v-9419dd23]{display:flex;flex-direction:column;gap:12px;padding-top:12px}.section-accordion-button[data-v-9419dd23]{width:100%;display:flex;cursor:pointer;padding:6px 0;margin:-6px 0;border-radius:var(--scalar-radius)}.section-accordion-chevron[data-v-9419dd23]{position:absolute;left:-22px;top:12px;color:var(--scalar-color-3)}.section-accordion-button:hover .section-accordion-chevron[data-v-9419dd23]{color:var(--scalar-color-1)}.section-accordion-title[data-v-9419dd23]{display:flex;flex-direction:column;align-items:flex-start;flex:1;padding:0 6px}.section-accordion-title[data-v-9419dd23] .section-header-wrapper{grid-template-columns:1fr}.section-accordion-title[data-v-9419dd23] .section-header{margin-bottom:0}@container narrow-references-container (max-width: 900px){.section-accordion-chevron[data-v-9419dd23]{width:16px;left:-16px;top:14px}.section-accordion-wrapper[data-v-9419dd23]{padding:calc(var(--refs-viewport-offset)) 24px 0 24px}}.loading[data-v-8e0226d7]{background:var(--scalar-background-3);animation:loading-skeleton-8e0226d7 1.5s infinite alternate;border-radius:var(--scalar-radius-lg);min-height:1.6em;margin:.6em 0;max-width:100%}.loading[data-v-8e0226d7]:first-of-type{min-height:3em;margin-bottom:24px;margin-top:0}.loading[data-v-8e0226d7]:last-of-type{width:60%}.loading.single-line[data-v-8e0226d7]{min-height:3em;margin:.6em 0;max-width:80%}@keyframes loading-skeleton-8e0226d7{0%{opacity:1}to{opacity:.33}}@container narrow-references-container (max-width: 900px){.section-content--with-columns[data-v-9735459e]{flex-direction:column;gap:24px}}.section-header-wrapper[data-v-465a7a78]{grid-template-columns:1fr;display:grid}@media(min-width:1200px){.section-header-wrapper[data-v-465a7a78]{grid-template-columns:repeat(2,1fr)}}.section-header[data-v-465a7a78]{font-size:var(--font-size,var(--scalar-heading-1));font-weight:var(--font-weight,var(--scalar-bold));color:var(--scalar-color-1);word-wrap:break-word;margin-top:0;margin-bottom:12px;line-height:1.45}.section-header.tight[data-v-465a7a78]{margin-bottom:6px}.section-header.loading[data-v-465a7a78]{width:80%}.section-header-label[data-v-f1ac6c38]{display:inline}.screenreader-only[data-v-df2e1026]{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.collapsible-section[data-v-999a158a]{border-top:var(--scalar-border-width) solid var(--scalar-border-color);position:relative}.collapsible-section-header[data-v-999a158a]{color:var(--scalar-color-1)}.collapsible-section .collapsible-section-trigger[data-v-999a158a]{display:flex;align-items:center;cursor:pointer;padding:10px 0;font-size:var(--scalar-font-size-3);z-index:1;position:relative}.collapsible-section-trigger svg[data-v-999a158a]{color:var(--scalar-color-3);position:absolute;left:-19px}.collapsible-section:hover .collapsible-section-trigger svg[data-v-999a158a]{color:var(--scalar-color-1)}.collapsible-section .collapsible-section-trigger[data-v-999a158a] .anchor-copy{line-height:18.5px}.collapsible-section-content[data-v-999a158a]{padding:0;margin:0 0 10px;scroll-margin-top:140px}.references-classic .introduction-description[data-v-fe80002d] img{max-width:720px}.icons-only[data-v-b59b0acf] span{display:none}.sticky-cards[data-v-0b1e2255]{display:flex;flex-direction:column;position:sticky;top:calc(var(--refs-viewport-offset) + 24px)}.introduction-card-item[data-v-dfab866f]{display:flex;flex-direction:column;justify-content:flex-start}.introduction-card-item[data-v-dfab866f]:empty{display:none}.introduction-card-item[data-v-dfab866f]:has(.description) .server-form-container{border-bottom-left-radius:0;border-bottom-right-radius:0}.introduction-card-item[data-v-dfab866f] .request-item{border-bottom:0}.schema-type-icon[data-v-70cb5c13]{color:var(--scalar-color-1);display:none}.schema-type[data-v-70cb5c13]{font-family:var(--scalar-font-code);color:var(--scalar-color-1)}.property-enum-value[data-v-f4b54bdd]{color:var(--scalar-color-3);line-height:1.5;overflow-wrap:break-word;display:flex;align-items:stretch;position:relative;--decorator-width: 1px;--decorator-color: color-mix( in srgb, var(--scalar-background-1), var(--scalar-color-1) 25% )}.property-enum-value-content[data-v-f4b54bdd]{display:flex;flex-direction:column;padding:3px 0}.property-enum-value-label[data-v-f4b54bdd]{font-family:var(--scalar-font-code);color:var(--scalar-color-1);font-size:var(--scalar-font-size-4);position:relative}.property-enum-value:last-of-type .property-enum-value-label[data-v-f4b54bdd]{padding-bottom:0}.property-enum-value[data-v-f4b54bdd]:before{content:"";margin-right:12px;width:var(--decorator-width);display:block;background-color:var(--decorator-color)}.property-enum-value[data-v-f4b54bdd]:last-of-type:before,.property-enum-values:has(.enum-toggle-button) .property-enum-value[data-v-f4b54bdd]:nth-last-child(2):before{height:calc(.5lh + 4px)}.property-enum-value-label[data-v-f4b54bdd]:after{content:"";position:absolute;top:.5lh;left:-12px;width:8px;height:var(--decorator-width);background-color:var(--decorator-color)}.property-enum-value[data-v-f4b54bdd]:last-of-type:after{bottom:0;height:50%;background:var(--scalar-background-1);border-top:var(--scalar-border-width) solid var(--decorator-color)}.property-enum-value-description[data-v-f4b54bdd]{color:var(--scalar-color-3)}.property-heading:empty+.property-description[data-v-d5367294]:last-of-type,.property-description[data-v-d5367294]:first-of-type:last-of-type{margin-top:0}.property-list[data-v-d5367294]{border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:var(--scalar-radius);margin-top:10px}.property-list .property[data-v-d5367294]:last-of-type{padding-bottom:10px}.property-enum-values[data-v-d5367294]{font-size:var(--scalar-font-size-3);list-style:none;margin-top:8px;padding-left:2px}.enum-toggle-button[data-v-d5367294]:hover{color:var(--scalar-color-1)}.property-detail[data-v-1295f965]{display:inline-flex}.property-detail+.property-detail[data-v-1295f965]:before{display:block;content:"·";margin:0 .5ch}.property-detail-truncate[data-v-1295f965]{overflow:hidden}.property-detail-truncate>.property-detail-value[data-v-1295f965]{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.property-detail-prefix[data-v-1295f965]{color:var(--scalar-color-2)}code.property-detail-value[data-v-1295f965]{font-family:var(--scalar-font-code);font-size:var(--scalar-font-size-3);color:var(--scalar-color-2);background:color-mix(in srgb,var(--scalar-background-2),var(--scalar-background-1));padding:0 4px;border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:var(--scalar-radius)}.property-example[data-v-72def0ea]{display:flex;flex-direction:column;font-size:var(--scalar-mini);position:relative}.property-example[data-v-72def0ea]:hover:before{content:"";position:absolute;top:0;left:0;width:100%;height:20px;border-radius:var(--scalar-radius)}.property-example:hover .property-example-label span[data-v-72def0ea]{color:var(--scalar-color-1)}.property-example-label span[data-v-72def0ea]{color:var(--scalar-color-3);position:relative;border-bottom:var(--scalar-border-width) dotted currentColor}.property-example-value[data-v-72def0ea]{font-family:var(--scalar-font-code);display:flex;gap:8px;align-items:center;width:100%;padding:6px}.property-example-value span[data-v-72def0ea]{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.property-example-value[data-v-72def0ea] svg{color:var(--scalar-color-3)}.property-example-value[data-v-72def0ea]:hover svg{color:var(--scalar-color-1)}.property-example-value[data-v-72def0ea]{background:var(--scalar-background-2);border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:var(--scalar-radius)}.property-example-value-list[data-v-72def0ea]{position:absolute;top:18px;left:50%;transform:translate3d(-50%,0,0);overflow:auto;background-color:var(--scalar-background-1);box-shadow:var(--scalar-shadow-1);border-radius:var(--scalar-radius-lg);border:var(--scalar-border-width) solid var(--scalar-border-color);padding:9px;min-width:200px;max-width:300px;flex-direction:column;gap:3px;display:none;z-index:2}.property-example:hover .property-example-value-list[data-v-72def0ea],.property-example:focus-within .property-example-value-list[data-v-72def0ea]{display:flex}.property-heading[data-v-f1f4255a]{display:flex;flex-wrap:wrap;align-items:baseline;row-gap:9px;white-space:nowrap}.property-heading[data-v-f1f4255a]:has(+.children),.property-heading[data-v-f1f4255a]:has(+.property-rule){margin-bottom:9px}.property-heading[data-v-f1f4255a]>*{margin-right:9px}.property-heading[data-v-f1f4255a]:last-child{margin-right:0}.property-heading>.property-detail[data-v-f1f4255a]:not(:last-of-type){margin-right:0}.property-name[data-v-f1f4255a]{max-width:100%;font-family:var(--scalar-font-code);font-weight:var(--scalar-bold);font-size:var(--scalar-font-size-4);white-space:normal;overflow-wrap:break-word}.property-additional[data-v-f1f4255a]{font-family:var(--scalar-font-code)}.property-required[data-v-f1f4255a],.property-optional[data-v-f1f4255a]{color:var(--scalar-color-2)}.property-required[data-v-f1f4255a]{font-size:var(--scalar-mini);color:var(--scalar-color-orange)}.property-read-only[data-v-f1f4255a]{font-size:var(--scalar-mini);color:var(--scalar-color-blue)}.property-write-only[data-v-f1f4255a]{font-size:var(--scalar-mini);color:var(--scalar-color-green)}.property-discriminator[data-v-f1f4255a]{font-size:var(--scalar-mini);color:var(--scalar-color-purple)}.property-detail[data-v-f1f4255a]{font-size:var(--scalar-mini);color:var(--scalar-color-2);display:flex;align-items:center;min-width:0}.property-const[data-v-f1f4255a]{color:var(--scalar-color-1)}.deprecated[data-v-f1f4255a]{text-decoration:line-through}.property[data-v-84242873]{color:var(--scalar-color-1);display:flex;flex-direction:column;padding:10px;font-size:var(--scalar-small);position:relative}.property.property--level-0[data-v-84242873]:has(>.property-rule>.schema-card>.schema-properties.schema-properties-open>ul>li.property){padding-top:0}.property--compact.property--level-0[data-v-84242873],.property--compact.property--level-1[data-v-84242873]{padding:10px 0}.composition-panel .property.property.property.property--level-0[data-v-84242873]{padding:0}.property--compact.property--level-0 .composition-panel .property--compact.property--level-1[data-v-84242873]{padding:8px}.property[data-v-84242873]:has(>.property-rule:nth-of-type(1)):not(.property--compact){padding-top:8px;padding-bottom:8px}.property--deprecated[data-v-84242873]{background:repeating-linear-gradient(-45deg,var(--scalar-background-2) 0,var(--scalar-background-2) 2px,transparent 2px,transparent 5px);background-size:100%}.property--deprecated[data-v-84242873]>*{opacity:.75}.property-description[data-v-84242873]{margin-top:6px;line-height:1.4;font-size:var(--scalar-small)}.property-description[data-v-84242873]:has(+.property-rule){margin-bottom:9px}[data-v-84242873] .property-description *{color:var(--scalar-color-2)!important}.property[data-v-84242873]:not(:last-of-type){border-bottom:var(--scalar-border-width) solid var(--scalar-border-color)}.property-description+.children[data-v-84242873],.children+.property-rule[data-v-84242873]{margin-top:9px}.children[data-v-84242873]{display:flex;flex-direction:column}.children .property--compact.property--level-1[data-v-84242873]{padding:12px}.property-example-value[data-v-84242873]{all:unset;font-family:var(--scalar-font-code);padding:6px;border-top:var(--scalar-border-width) solid var(--scalar-border-color)}.property-rule[data-v-84242873]{border-radius:var(--scalar-radius-lg);display:flex;flex-direction:column}.property-rule[data-v-84242873] .composition-panel .schema-card--level-1>.schema-properties.schema-properties-open{border-radius:0 0 var(--scalar-radius-lg) var(--scalar-radius-lg)}.property-rule[data-v-84242873] .composition-panel>.schema-card>.schema-card-description{padding:10px;border-left:var(--scalar-border-width) solid var(--scalar-border-color);border-right:var(--scalar-border-width) solid var(--scalar-border-color)}.property-rule[data-v-84242873] .composition-panel>.schema-card>.schema-card-description+.schema-properties{margin-top:0}.property-example[data-v-84242873]{background:transparent;border:none;display:flex;flex-direction:row;gap:8px}.property-example-label[data-v-84242873],.property-example-value[data-v-84242873]{padding:3px 0 0}.property-example-value[data-v-84242873]{background:var(--scalar-background-2);border-top:0;border-radius:var(--scalar-radius);padding:3px 4px}.property-name[data-v-84242873]{font-family:var(--scalar-font-code);font-weight:var(--scalar-semibold)}.property-name-additional-properties[data-v-84242873]:before,.property-name-pattern-properties[data-v-84242873]:before{text-transform:uppercase;font-size:var(--scalar-micro);display:inline-block;padding:2px 4px;border-radius:var(--scalar-radius);color:var(--scalar-color-1);border:var(--scalar-border-width) solid var(--scalar-border-color);background-color:var(--scalar-background-2);margin-right:4px}.property-name-pattern-properties[data-v-84242873]:before{content:"regex"}.property-name-additional-properties[data-v-84242873]:before{content:"unknown property name"}.error[data-v-d9bd8110]{background-color:var(--scalar-color-red)}.schema-card[data-v-d9bd8110]{font-size:var(--scalar-font-size-4);color:var(--scalar-color-1)}.schema-card-title[data-v-d9bd8110]{height:var(--schema-title-height);padding:6px 8px;display:flex;align-items:center;gap:4px;color:var(--scalar-color-2);font-weight:var(--scalar-semibold);font-size:var(--scalar-mini);border-bottom:var(--scalar-border-width) solid transparent}button.schema-card-title[data-v-d9bd8110]{cursor:pointer}button.schema-card-title[data-v-d9bd8110]:hover{color:var(--scalar-color-1)}.schema-card-title-icon--open[data-v-d9bd8110]{transform:rotate(45deg)}.schema-properties-open>.schema-card-title[data-v-d9bd8110]{border-bottom-left-radius:0;border-bottom-right-radius:0;border-bottom:var(--scalar-border-width) solid var(--scalar-border-color)}.schema-properties-open>.schema-properties[data-v-d9bd8110]{width:fit-content}.schema-card-description[data-v-d9bd8110]{color:var(--scalar-color-2)}.schema-card-description+.schema-properties[data-v-d9bd8110]{width:fit-content}.schema-card-description+.schema-properties[data-v-d9bd8110]{margin-top:8px}.schema-card--level-0:nth-of-type(1)>.schema-card-description[data-v-d9bd8110]:has(+.schema-properties){margin-bottom:-8px;padding-bottom:8px;border-bottom:var(--scalar-border-width) solid var(--scalar-border-color)}.schema-card--level-0~.schema-card--level-0>.schema-card-description[data-v-d9bd8110]:has(+.schema-properties){padding-top:8px}.schema-properties-open.schema-properties[data-v-d9bd8110],.schema-properties-open>.schema-card--open[data-v-d9bd8110]{width:100%}.schema-properties[data-v-d9bd8110]{display:flex;flex-direction:column;border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:var(--scalar-radius-lg);width:fit-content}.schema-properties-name[data-v-d9bd8110]{width:100%}.schema-properties .schema-properties[data-v-d9bd8110]{border-radius:13.5px}.schema-properties .schema-properties.schema-properties-open[data-v-d9bd8110]{border-radius:var(--scalar-radius-lg)}.schema-properties-open[data-v-d9bd8110]{width:100%}.schema-card--compact[data-v-d9bd8110]{align-self:flex-start}.schema-card--compact.schema-card--open[data-v-d9bd8110]{align-self:initial}.schema-card-title--compact[data-v-d9bd8110]{color:var(--scalar-color-2);padding:6px 10px 6px 8px;height:auto;border-bottom:none}.schema-card-title--compact>.schema-card-title-icon[data-v-d9bd8110]{margin:0}.schema-card-title--compact>.schema-card-title-icon--open[data-v-d9bd8110]{transform:rotate(45deg)}.schema-properties-open>.schema-card-title--compact[data-v-d9bd8110]{position:static}.property--level-0>.schema-properties>.schema-card--level-0>.schema-properties[data-v-d9bd8110]{border:none}.property--level-0 .schema-card--level-0:not(.schema-card--compact) .property--level-1[data-v-d9bd8110]{padding:0 0 8px}:not(.composition-panel)>.schema-card--compact.schema-card--level-0>.schema-properties[data-v-d9bd8110]{border:none}[data-v-d9bd8110] .schema-card-description p{font-size:var(--scalar-small, var(--scalar-paragraph));color:var(--scalar-color-2);line-height:1.5;display:block;margin-bottom:6px}.children .schema-card-description[data-v-d9bd8110]:first-of-type{padding-top:0}.reference-models-anchor[data-v-e4ca3c0f]{display:flex;align-items:center;font-size:20px;padding-left:6px;color:var(--scalar-color-1)}.reference-models-label[data-v-e4ca3c0f]{display:block;font-size:var(--scalar-mini)}.reference-models-label[data-v-e4ca3c0f] em{font-weight:var(--scalar-bold)}.show-more[data-v-d1c2b649]{appearance:none;border:none;border:var(--scalar-border-width) solid var(--scalar-border-color);margin:auto;padding:8px 12px 8px 16px;border-radius:30px;color:var(--scalar-color-1);font-weight:var(--scalar-semibold);font-size:var(--scalar-small);display:flex;align-items:center;justify-content:center;position:relative;gap:6px;top:-48px}.show-more[data-v-d1c2b649]:hover{background:var(--scalar-background-2);cursor:pointer}.show-more[data-v-d1c2b649]:active{box-shadow:0 0 0 1px var(--scalar-border-color)}@container narrow-references-container (max-width: 900px){.show-more[data-v-d1c2b649]{top:-24px}}.tag-section[data-v-1124be5d]{margin-bottom:48px}.tag-name[data-v-1124be5d]{text-transform:capitalize}.tag-description[data-v-1124be5d]{padding-bottom:4px;text-align:left}.endpoint[data-v-ad8530a6]{display:flex;white-space:nowrap;cursor:pointer;text-decoration:none}.endpoint:hover .endpoint-path[data-v-ad8530a6],.endpoint:focus-visible .endpoint-path[data-v-ad8530a6]{text-decoration:underline}.endpoint .post[data-v-ad8530a6],.endpoint .get[data-v-ad8530a6],.endpoint .delete[data-v-ad8530a6],.endpoint .put[data-v-ad8530a6]{white-space:nowrap}.endpoint-method[data-v-ad8530a6],.endpoint-path[data-v-ad8530a6]{color:var(--scalar-color-1);min-width:62px;display:inline-flex;line-height:1.55;font-family:var(--scalar-font-code);font-size:var(--scalar-small);cursor:pointer}.endpoint-method[data-v-ad8530a6]{text-align:right}.endpoint-path[data-v-ad8530a6]{margin-left:12px;text-transform:initial}.deprecated[data-v-ad8530a6]{text-decoration:line-through}.endpoints-card[data-v-f726f753]{position:sticky;top:calc(var(--refs-viewport-offset) + 24px);font-size:var(--scalar-font-size-3)}.endpoints[data-v-f726f753]{overflow:auto;background:var(--scalar-background-2);padding:10px 12px;width:100%}.section-container[data-v-0d42fbc8]{border-top:var(--scalar-border-width) solid var(--scalar-border-color)}.section-container[data-v-0d42fbc8]:has(.show-more){background-color:color-mix(in srgb,var(--scalar-background-2),transparent)}.operation-path[data-v-ec6c8861]{overflow:hidden;word-wrap:break-word;font-weight:var(--scalar-semibold);line-break:anywhere}.deprecated[data-v-ec6c8861]{text-decoration:line-through}.empty-state[data-v-431974da]{margin:10px 0 10px 12px;text-align:center;font-size:var(--scalar-mini);min-height:56px;display:flex;align-items:center;justify-content:center;border-radius:var(--scalar-radius-lg);color:var(--scalar-color-2)}.rule-title[data-v-431974da]{font-family:var(--scalar-font-code);color:var(--scalar-color-1);display:inline-block;margin:12px 0 6px;border-radius:var(--scalar-radius)}.rule[data-v-431974da]{margin:0 12px;border-radius:var(--scalar-radius-lg)}.rule-items[data-v-431974da]{counter-reset:list-number;display:flex;flex-direction:column;gap:12px;border-left:1px solid var(--scalar-border-color);padding:12px 0}.rule-item[data-v-431974da]{counter-increment:list-number;border:1px solid var(--scalar-border-color);border-radius:var(--scalar-radius-lg);overflow:hidden;margin-left:24px}.rule-item[data-v-431974da]:before{border:1px solid var(--scalar-border-color);border-top:0;border-right:0;content:" ";display:block;width:24px;height:6px;border-radius:0 0 0 var(--scalar-radius-lg);margin-top:6px;color:var(--scalar-color-2);transform:translate(-25px);color:var(--scalar-color-1);position:absolute}.tab[data-v-804dba49]{background:none;border:none;font-size:var(--scalar-small);font-family:var(--scalar-font);font-weight:var(--scalar-font-normal);color:var(--scalar-color-2);line-height:calc(var(--scalar-small) + 2px);white-space:nowrap;cursor:pointer;padding:0;margin-right:3px;text-transform:uppercase;position:relative;line-height:22px}.tab[data-v-804dba49]:before{content:"";position:absolute;z-index:0;left:-6px;top:-2px;width:calc(100% + 12px);height:calc(100% + 4px);border-radius:var(--scalar-radius);background:var(--scalar-background-3);opacity:0}.tab[data-v-804dba49]:hover:before,.tab[data-v-804dba49]:focus-visible:before{opacity:1}.tab[data-v-804dba49]:focus-visible:before{outline:1px solid var(--scalar-color-accent)}.tab span[data-v-804dba49]{z-index:1;position:relative}.tab-selected[data-v-804dba49]{color:var(--scalar-color-1);font-weight:var(--scalar-semibold)}.tab-selected[data-v-804dba49]:after{content:"";position:absolute;background:currentColor;width:100%;left:0;height:1px;bottom:calc(var(--tab-list-padding-y) * -1)}.tab-list[data-v-fec8fbbb]{display:flex;gap:6px;position:relative;flex:1;--tab-list-padding-y: 7px;--tab-list-padding-x: 12px;padding:var(--tab-list-padding-y) var(--tab-list-padding-x);overflow:auto}.scalar-card-header.scalar-card-header-tabs[data-v-fec8fbbb]{padding:0}.response-card[data-v-4f7e7d02]{font-size:var(--scalar-font-size-3)}.markdown[data-v-4f7e7d02] *{margin:0}.code-copy[data-v-4f7e7d02]{display:flex;align-items:center;justify-content:center;appearance:none;-webkit-appearance:none;outline:none;background:transparent;cursor:pointer;color:var(--scalar-color-3);border:none;padding:0;margin-right:12px}.code-copy[data-v-4f7e7d02]:hover{color:var(--scalar-color-1)}.code-copy svg[data-v-4f7e7d02]{width:13px;height:13px}.response-card-footer[data-v-4f7e7d02]{display:flex;flex-direction:row;justify-content:space-between;flex-shrink:0;padding:7px 12px;gap:8px}.response-example-selector[data-v-4f7e7d02]{align-self:flex-start;margin:-4px}.response-description[data-v-4f7e7d02]{font-weight:var(--scalar-semibold);font-size:var(--scalar-small);color:var(--scalar-color--1);display:flex;align-items:center;box-sizing:border-box}.schema-type[data-v-4f7e7d02]{font-size:var(--scalar-micro);color:var(--scalar-color-2);font-weight:var(--scalar-semibold);background:var(--scalar-background-3);padding:2px 4px;border-radius:4px;margin-right:4px}.schema-example[data-v-4f7e7d02]{font-size:var(--scalar-micro);color:var(--scalar-color-2);font-weight:var(--scalar-semibold)}.example-response-tab[data-v-4f7e7d02]{display:block;margin:6px}.scalar-card-checkbox[data-v-4f7e7d02]{display:flex;align-items:center;justify-content:center;position:relative;min-height:17px;cursor:pointer;-webkit-user-select:none;user-select:none;font-size:var(--scalar-small);font-weight:var(--scalar-font-normal);color:var(--scalar-color-2);width:fit-content;white-space:nowrap;gap:6px;padding:7px 6px}.scalar-card-checkbox:has(.scalar-card-checkbox-input:focus-visible) .scalar-card-checkbox-checkmark[data-v-4f7e7d02]{outline:1px solid var(--scalar-color-accent)}.scalar-card-checkbox[data-v-4f7e7d02]:hover{color:var(--scalar-color--1)}.scalar-card-checkbox .scalar-card-checkbox-input[data-v-4f7e7d02]{position:absolute;opacity:0;cursor:pointer;height:0;width:0}.scalar-card-checkbox-checkmark[data-v-4f7e7d02]{height:16px;width:16px;border-radius:var(--scalar-radius);background-color:transparent;background-color:var(--scalar-background-3);box-shadow:inset 0 0 0 var(--scalar-border-width) var(--scalar-border-color)}.scalar-card-checkbox[data-v-4f7e7d02]:has(.scalar-card-checkbox-input:checked){color:var(--scalar-color-1);font-weight:var(--scalar-semibold)}.scalar-card-checkbox .scalar-card-checkbox-input:checked~.scalar-card-checkbox-checkmark[data-v-4f7e7d02]{background-color:var(--scalar-button-1);box-shadow:none}.scalar-card-checkbox-checkmark[data-v-4f7e7d02]:after{content:"";position:absolute;display:none}.scalar-card-checkbox .scalar-card-checkbox-input:checked~.scalar-card-checkbox-checkmark[data-v-4f7e7d02]:after{display:block}.scalar-card-checkbox .scalar-card-checkbox-checkmark[data-v-4f7e7d02]:after{right:11.5px;top:12.5px;width:5px;height:9px;border:solid 1px var(--scalar-button-1-color);border-width:0 1.5px 1.5px 0;transform:rotate(45deg)}.headers-card[data-v-ab19704d]{z-index:0;margin-top:12px;margin-bottom:6px;position:relative;font-size:var(--scalar-font-size-4);color:var(--scalar-color-1);align-self:flex-start}.headers-card.headers-card--open[data-v-ab19704d]{align-self:initial}.headers-card-title[data-v-ab19704d]{padding:6px 10px;display:flex;align-items:center;gap:4px;color:var(--scalar-color-3);font-weight:var(--scalar-semibold);font-size:var(--scalar-micro);border-radius:13.5px}button.headers-card-title[data-v-ab19704d]{cursor:pointer}button.headers-card-title[data-v-ab19704d]:hover{color:var(--scalar-color-1)}.headers-card-title-icon--open[data-v-ab19704d]{transform:rotate(45deg)}.headers-properties[data-v-ab19704d]{display:flex;flex-direction:column;border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:13.5px;width:fit-content}.headers-properties-open>.headers-card-title[data-v-ab19704d]{border-bottom-left-radius:0;border-bottom-right-radius:0;border-bottom:var(--scalar-border-width) solid var(--scalar-border-color)}.headers-properties-open[data-v-ab19704d]{border-radius:var(--scalar-radius-lg);width:100%}.headers-card .property[data-v-ab19704d]:last-of-type{padding-bottom:10px}.headers-card-title>.headers-card-title-icon[data-v-ab19704d]{width:14px;height:14px;margin:0}.headers-card-title>.headers-card-title-icon--open[data-v-ab19704d]{transform:rotate(45deg)}.parameter-item[data-v-b97d19d9]{display:flex;flex-direction:column;position:relative;border-top:var(--scalar-border-width) solid var(--scalar-border-color)}.parameter-item:last-of-type .parameter-schema[data-v-b97d19d9]{padding-bottom:0}.parameter-item-container[data-v-b97d19d9]{padding:0}.parameter-item-headers[data-v-b97d19d9]{border:var(--scalar-border-width) solid var(--scalar-border-color)}.parameter-item-name[data-v-b97d19d9]{position:relative;font-weight:var(--scalar-bold);font-size:var(--scalar-font-size-4);font-family:var(--scalar-font-code);color:var(--scalar-color-1);overflow-wrap:break-word}.parameter-item-description[data-v-b97d19d9],.parameter-item-description-summary[data-v-b97d19d9]{font-size:var(--scalar-mini);color:var(--scalar-color-2)}.parameter-item-description-summary.parameter-item-description-summary[data-v-b97d19d9]>*{--markdown-line-height: 1}.parameter-item-trigger+.parameter-item-container[data-v-b97d19d9] .property--level-0>.property-heading .property-detail-value{font-size:var(--scalar-micro)}.parameter-item-required-optional[data-v-b97d19d9]{color:var(--scalar-color-2);font-weight:var(--scalar-semibold);margin-right:6px;position:relative}.parameter-item--required[data-v-b97d19d9]{text-transform:uppercase;font-size:var(--scalar-micro);font-weight:var(--scalar-semibold);color:var(--scalar-color-orange)}.parameter-item-description[data-v-b97d19d9]{margin-top:6px;font-size:var(--scalar-small);color:var(--scalar-color-2);line-height:1.4}.parameter-item-description[data-v-b97d19d9] p{margin-top:4px;font-size:var(--scalar-small);color:var(--scalar-color-2);line-height:1.4}.parameter-schema[data-v-b97d19d9]{padding-bottom:9px;margin-top:3px}.parameter-item-trigger[data-v-b97d19d9]{display:flex;align-items:baseline;line-height:var(--scalar-line-height-5);gap:6px;flex-wrap:wrap;padding:10px 0;outline:none}.parameter-item-trigger-open[data-v-b97d19d9]{padding-bottom:0}.parameter-item-icon[data-v-b97d19d9]{color:var(--scalar-color-3);left:-19px;top:.5lh;translate:0 -50%;position:absolute}.parameter-item-trigger:hover .parameter-item-icon[data-v-b97d19d9],.parameter-item-trigger:focus-visible .parameter-item-icon[data-v-b97d19d9]{color:var(--scalar-color-1)}.parameter-item-trigger:focus-visible .parameter-item-icon[data-v-b97d19d9]{outline:1px solid var(--scalar-color-accent);outline-offset:2px;border-radius:var(--scalar-radius)}.request-body[data-v-17941e59]{margin-top:24px}.request-body-header[data-v-17941e59]{display:flex;align-items:center;justify-content:space-between;padding-bottom:12px;border-bottom:var(--scalar-border-width) solid var(--scalar-border-color);flex-flow:wrap}.request-body-title[data-v-17941e59]{display:flex;align-items:center;gap:8px;font-size:var(--scalar-font-size-2);font-weight:var(--scalar-semibold);color:var(--scalar-color-1)}.request-body-required[data-v-17941e59]{font-size:var(--scalar-micro);color:var(--scalar-color-orange);font-weight:400;border-radius:16px;border:var(--scalar-border-width) solid var(--scalar-border-color);padding:2px 8px;height:20px}.request-body-description[data-v-17941e59]{margin-top:6px;font-size:var(--scalar-small);width:100%}.request-body-header+.request-body-schema[data-v-17941e59]:has(>.schema-card>.schema-card-description),.request-body-header+.request-body-schema[data-v-17941e59]:has(>.schema-card>.schema-properties>*>.property--level-0){padding-top:8px}.request-body-description[data-v-17941e59] .markdown *{color:var(--scalar-color-2)!important}.callback-sticky-offset[data-v-b94c2b8b]{top:var(--refs-viewport-offset, 0px);z-index:1}.callback-operation-container[data-v-b94c2b8b] .request-body,.callback-operation-container[data-v-b94c2b8b] .request-body-description,.callback-operation-container[data-v-b94c2b8b] .request-body-header{margin-top:0}.callback-operation-container[data-v-b94c2b8b] .request-body-header{--scalar-font-size-2: var(--scalar-font-size-4);padding:10px;border-bottom:none;border:.5px solid var(--scalar-border-color);border-radius:var(--scalar-radius-lg) var(--scalar-radius-lg) 0 0;background:color-mix(in srgb,var(--scalar-background-2) 50%,transparent)}.callback-operation-container[data-v-b94c2b8b] .request-body-schema>.schema-card>.schema-card-description{padding-inline:8px}.callback-operation-container[data-v-b94c2b8b] ul li.property.property--level-1{padding:10px}.callback-operation-container[data-v-b94c2b8b] .request-body-schema{background-color:var(--scalar-background-1);border:var(--scalar-border-width) solid var(--scalar-border-color);border-top:none;overflow:hidden;border-radius:0 0 var(--scalar-radius-lg) var(--scalar-radius-lg)}.callback-operation-container[data-v-b94c2b8b] .parameter-list{margin-top:0}.callback-operation-container[data-v-b94c2b8b] .parameter-list-title{background:color-mix(in srgb,var(--scalar-background-2) 50%,transparent);border-radius:var(--scalar-radius-lg) var(--scalar-radius-lg) 0 0;padding:10px;margin-bottom:0;border:var(--scalar-border-width) solid var(--scalar-border-color);border-bottom:none;--scalar-font-size-2: var(--scalar-font-size-4)}.callback-operation-container[data-v-b94c2b8b] .parameter-list-items{border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:0 0 var(--scalar-radius-lg) var(--scalar-radius-lg)}.callback-operation-container[data-v-b94c2b8b] .parameter-list-items>li:first-of-type{border-top:none}.callback-operation-container[data-v-b94c2b8b] .parameter-list-items>li{padding:0 8px}.show-api-client-button[data-v-342ba62a]{appearance:none;border:none;padding:1px 6px;white-space:nowrap;border-radius:var(--scalar-radius);display:flex;justify-content:center;align-items:center;font-weight:var(--scalar-semibold);font-size:var(--scalar-small);line-height:22px;color:var(--scalar-background-2);font-family:var(--scalar-font);background:var(--scalar-button-1);position:relative;cursor:pointer;box-sizing:border-box;box-shadow:inset 0 0 0 1px #0000001a;outline-offset:2px}.show-api-client-button span[data-v-342ba62a],.show-api-client-button svg[data-v-342ba62a]{fill:currentColor;color:var(--scalar-button-1-color);z-index:1}.show-api-client-button[data-v-342ba62a]:hover{background:var(--scalar-button-1-hover)}.show-api-client-button svg[data-v-342ba62a]{margin-right:4px}.operation-title[data-v-55addca4]{justify-content:space-between;display:flex}.operation-details[data-v-55addca4]{flex-shrink:1;align-items:center;gap:9px;min-width:0;margin-top:0;display:flex}.operation-details[data-v-55addca4] .endpoint-anchor .scalar-button svg{width:16px;height:16px}.endpoint-type[data-v-55addca4]{z-index:0;width:60px;font-size:var(--scalar-small);text-transform:uppercase;font-weight:var(--scalar-bold);font-family:var(--scalar-font);flex-shrink:0;justify-content:center;align-items:center;gap:6px;padding:6px;display:flex;position:relative}.endpoint-type[data-v-55addca4]:after{content:"";z-index:-1;opacity:.15;border-radius:var(--scalar-radius);background:currentColor;position:absolute;inset:0}.endpoint-anchor[data-v-55addca4]{flex-shrink:1;align-items:center;min-width:0;display:flex}.endpoint-anchor.label[data-v-55addca4]{display:flex}.endpoint-label[data-v-55addca4]{min-width:0;color:var(--scalar-color-1);flex-shrink:1;align-items:baseline;gap:9px;display:flex}.endpoint-label-path[data-v-55addca4]{font-family:var(--scalar-font-code);font-size:var(--scalar-mini);text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.endpoint-label-path[data-v-55addca4] em{color:var(--scalar-color-2)}.endpoint-label-name[data-v-55addca4]{color:var(--scalar-color-2);font-size:var(--scalar-small);text-overflow:ellipsis;white-space:nowrap;flex-shrink:1000000000;overflow:hidden}.endpoint-try-hint[data-v-55addca4]{flex-shrink:0;padding:2px}.endpoint-copy[data-v-55addca4]{color:currentColor}.endpoint-copy[data-v-55addca4] svg{stroke-width:2px}.endpoint-content[data-v-55addca4]{grid-auto-columns:1fr;grid-auto-flow:row;gap:9px;padding:9px;display:grid}@media(min-width:1000px){.endpoint-content[data-v-55addca4]{grid-auto-flow:column}}@container (max-width:900px){.endpoint-content[data-v-55addca4]{grid-template-columns:1fr}}.endpoint-content[data-v-55addca4]>*{min-width:0}.operation-details-card[data-v-55addca4]{flex-direction:column;gap:12px;min-width:0;display:flex}:is(.operation-details-card-item[data-v-55addca4] .parameter-list,.operation-details-card-item[data-v-55addca4] .callbacks-list){border:var(--scalar-border-width)solid var(--scalar-border-color);border-radius:var(--scalar-radius-lg);margin-top:0}.operation-details-card-item[data-v-55addca4]{flex-direction:column;gap:12px;display:flex}.operation-details-card-item[data-v-55addca4] .parameter-list-items{margin-bottom:0}.operation-details-card[data-v-55addca4] .parameter-item:last-of-type .parameter-schema{padding-bottom:12px}.operation-details-card[data-v-55addca4] .parameter-list .parameter-list{margin-bottom:12px}.operation-details-card[data-v-55addca4] .parameter-item{margin:0;padding:0}.operation-details-card[data-v-55addca4] .property{margin:0;padding:9px}:is(.operation-details-card[data-v-55addca4] .parameter-list-title,.operation-details-card[data-v-55addca4] .request-body-title,.operation-details-card[data-v-55addca4] .callbacks-title){text-transform:uppercase;font-weight:var(--scalar-bold);font-size:var(--scalar-mini);color:var(--scalar-color-2);margin:0;padding:9px;line-height:1.33}.operation-details-card[data-v-55addca4] .callback-list-item-title{padding-left:28px;padding-right:12px}.operation-details-card[data-v-55addca4] .callback-list-item-icon{left:6px}.operation-details-card[data-v-55addca4] .callback-operation-container{padding-inline:9px;padding-bottom:9px}:is(.operation-details-card[data-v-55addca4] .callback-operation-container>.request-body,.operation-details-card[data-v-55addca4] .callback-operation-container>.parameter-list){border:none}.operation-details-card[data-v-55addca4] .callback-operation-container>.request-body>.request-body-header{border-bottom:var(--scalar-border-width)solid var(--scalar-border-color);padding:0 0 9px}.operation-details-card[data-v-55addca4] .request-body-description{border-top:var(--scalar-border-width)solid var(--scalar-border-color);margin-top:0;padding:9px 9px 0}.operation-details-card[data-v-55addca4] .request-body{border-radius:var(--scalar-radius-lg);border:var(--scalar-border-width)solid var(--scalar-border-color);margin-top:0}.operation-details-card[data-v-55addca4] .request-body .schema-card--level-0>.schema-card-description{padding-inline:9px}.operation-details-card[data-v-55addca4] .request-body-header{border-bottom:0;padding-bottom:0}.operation-details-card[data-v-55addca4] .contents button{margin-right:9px}.operation-details-card[data-v-55addca4] .schema-card--open+.schema-card:not(.schema-card--open){margin-inline:9px;margin-bottom:9px}.operation-details-card[data-v-55addca4] .request-body-schema .property--level-0{padding:0}.operation-details-card[data-v-55addca4] .selected-content-type{margin-right:9px}.operation-example-card[data-v-55addca4]{top:calc(var(--refs-viewport-offset) + 24px);max-height:calc(var(--refs-viewport-height) - 48px);position:sticky}@media(max-width:600px){.operation-example-card[data-v-55addca4]{max-height:unset;position:static}}.ask-agent-scalar-input[data-v-b5a61e43]{appearance:none;border:none;white-space:nowrap;display:flex;justify-content:center;align-items:center;font-weight:var(--scalar-semibold);font-size:var(--scalar-small);line-height:22px;font-family:var(--scalar-font);position:relative;cursor:pointer;box-sizing:border-box;outline:none;outline-offset:2px;field-sizing:content;max-width:88px}.ask-agent-scalar-input[data-v-b5a61e43]:focus{cursor:text;width:100%!important;max-width:calc(100% - 50px)}.ask-agent-scalar-input[data-v-b5a61e43]:not(:placeholder-shown){width:100%!important;height:100%;field-sizing:border-box;cursor:text;max-width:calc(100% - 50px)}.ask-agent-scalar-input[data-v-b5a61e43]::placeholder{color:var(--scalar-color-1)}.ask-agent-scalar-input[data-v-b5a61e43]:focus::placeholder{color:var(--scalar-color-2)}.agent-button-container[data-v-b5a61e43]{position:relative;color:var(--scalar-color-1);background:color-mix(in srgb,var(--scalar-background-3),white 15%);display:flex;align-items:center;padding:1px 6px;margin-right:4px;border-radius:var(--scalar-radius);gap:4px;z-index:2;height:24px}.agent-button-container[data-v-b5a61e43]:hover:not(:focus-within){background:color-mix(in srgb,var(--scalar-background-3),white 20%)}.agent-button-container[data-v-b5a61e43]:has(.ask-agent-scalar-input:not(:placeholder-shown)),.agent-button-container[data-v-b5a61e43]:focus-within{width:100%;height:100%;position:absolute;left:0;top:0;border-radius:0}.ask-agent-scalar-send[data-v-b5a61e43]{background:var(--scalar-color-blue);color:#fff;width:24px;height:24px;flex-shrink:0;display:flex;justify-content:center;align-items:center;border-radius:var(--scalar-radius);margin-left:auto;display:none}.ask-agent-scalar-send[data-v-b5a61e43]:hover{background:color-mix(in srgb,var(--scalar-color-blue),transparent 10%)!important}.agent-button-container:has(.ask-agent-scalar-input:not(:placeholder-shown)) .ask-agent-scalar-send[data-v-b5a61e43]{display:flex}.examples[data-v-d5291d61]{position:sticky;top:calc(var(--refs-viewport-offset) + 24px)}.examples[data-v-d5291d61]>*{max-height:calc((var(--refs-viewport-height) - 60px) / 2);position:relative}.examples[data-v-d5291d61]>*:first-of-type:last-of-type{max-height:calc((var(--refs-viewport-height) - 60px))}@media(max-width:600px){.examples[data-v-d5291d61]>*{max-height:unset}}.deprecated[data-v-d5291d61] *{text-decoration:line-through}.section-flare[data-v-2a9c8c02]{top:0;right:0;pointer-events:none}.narrow-references-container{container-name:narrow-references-container;container-type:inline-size}.ref-search-meta[data-v-c1c368f9]{background:var(--scalar-background-1);border-bottom-left-radius:var(--scalar-radius-lg);border-bottom-right-radius:var(--scalar-radius-lg);padding:6px 12px;font-size:var(--scalar-font-size-4);color:var(--scalar-color-3);font-weight:var(--scalar-semibold);display:flex;gap:12px;border-top:var(--scalar-border-width) solid var(--scalar-border-color)}@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-divide-x-reverse:0;--tw-border-style:solid;--tw-divide-y-reverse:0;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-mask-linear:linear-gradient(#fff,#fff);--tw-mask-radial:linear-gradient(#fff,#fff);--tw-mask-conic:linear-gradient(#fff,#fff);--tw-mask-left:linear-gradient(#fff,#fff);--tw-mask-right:linear-gradient(#fff,#fff);--tw-mask-bottom:linear-gradient(#fff,#fff);--tw-mask-top:linear-gradient(#fff,#fff);--tw-mask-top-from-position:0%;--tw-mask-top-to-position:100%;--tw-mask-top-from-color:black;--tw-mask-top-to-color:transparent;--tw-mask-bottom-from-position:0%;--tw-mask-bottom-to-position:100%;--tw-mask-bottom-from-color:black;--tw-mask-bottom-to-color:transparent;--tw-leading:initial;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-ease:initial;--tw-content:"";--tw-space-x-reverse:0}}}@layer scalar-base{@supports (color:color-mix(in lab,red,red)){.light-mode,.dark-mode{--scalar-sidebar-search-background:color-mix(in srgb,var(--scalar-background-2),var(--scalar-background-1))}}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-color-alert:color-mix(in srgb,var(--scalar-color-orange),var(--scalar-color-1)20%)}}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-color-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-color-1)20%)}}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-background-alert:color-mix(in srgb,var(--scalar-color-orange),var(--scalar-background-1)95%)}}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-background-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1)95%)}}@supports (color:color-mix(in lab,red,red)){.dark-mode{--scalar-tooltip-background:color-mix(in srgb,var(--scalar-background-1),#fff 10%)}}@supports (color:color-mix(in lab,red,red)){.dark-mode{--scalar-color-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1)20%)}}@supports (color:color-mix(in lab,red,red)){.dark-mode{--scalar-background-alert:color-mix(in srgb,var(--scalar-color-orange),var(--scalar-background-1)95%)}}@supports (color:color-mix(in lab,red,red)){.dark-mode{--scalar-background-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1)95%)}}:root,:host{--leading-snug:1.375;--leading-normal:1.5;--leading-relaxed:1.625;--ease-in:cubic-bezier(.4,0,1,1);--ease-out:cubic-bezier(0,0,.2,1);--ease-in-out:cubic-bezier(.4,0,.2,1);--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1)}@supports (color:color-mix(in lab,red,red)){.light-mode,.dark-mode{--scalar-sidebar-search-background:var(--scalar-background-2)}@supports (color:color-mix(in lab,red,red)){.light-mode,.dark-mode{--scalar-sidebar-search-background:color-mix(in srgb,var(--scalar-background-2),var(--scalar-background-1))}}.light-mode{--scalar-color-alert:var(--scalar-color-orange)}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-color-alert:color-mix(in srgb,var(--scalar-color-orange),var(--scalar-color-1)20%)}}.light-mode{--scalar-color-danger:var(--scalar-color-red)}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-color-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-color-1)20%)}}.light-mode{--scalar-background-alert:var(--scalar-color-orange)}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-background-alert:color-mix(in srgb,var(--scalar-color-orange),var(--scalar-background-1)95%)}}.light-mode{--scalar-background-danger:var(--scalar-color-red)}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-background-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1)95%)}}.dark-mode{--scalar-tooltip-background:var(--scalar-background-1)}@supports (color:color-mix(in lab,red,red)){.dark-mode{--scalar-tooltip-background:color-mix(in srgb,var(--scalar-background-1),#fff 10%)}}.dark-mode{--scalar-color-danger:var(--scalar-color-red)}@supports (color:color-mix(in lab,red,red)){.dark-mode{--scalar-color-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1)20%)}}.dark-mode{--scalar-background-alert:var(--scalar-color-orange)}@supports (color:color-mix(in lab,red,red)){.dark-mode{--scalar-background-alert:color-mix(in srgb,var(--scalar-color-orange),var(--scalar-background-1)95%)}}.dark-mode{--scalar-background-danger:var(--scalar-color-red)}@supports (color:color-mix(in lab,red,red)){.dark-mode{--scalar-background-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1)95%)}}}@supports (color:color-mix(in lab,red,red)){.light-mode,.dark-mode{--scalar-sidebar-search-background:var(--scalar-background-2)}@supports (color:color-mix(in lab,red,red)){.light-mode,.dark-mode{--scalar-sidebar-search-background:var(--scalar-background-2)}@supports (color:color-mix(in lab,red,red)){.light-mode,.dark-mode{--scalar-sidebar-search-background:color-mix(in srgb,var(--scalar-background-2),var(--scalar-background-1))}}}.light-mode{--scalar-color-alert:var(--scalar-color-orange)}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-color-alert:var(--scalar-color-orange)}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-color-alert:color-mix(in srgb,var(--scalar-color-orange),var(--scalar-color-1)20%)}}}.light-mode{--scalar-color-danger:var(--scalar-color-red)}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-color-danger:var(--scalar-color-red)}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-color-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-color-1)20%)}}}.light-mode{--scalar-background-alert:var(--scalar-color-orange)}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-background-alert:var(--scalar-color-orange)}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-background-alert:color-mix(in srgb,var(--scalar-color-orange),var(--scalar-background-1)95%)}}}.light-mode{--scalar-background-danger:var(--scalar-color-red)}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-background-danger:var(--scalar-color-red)}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-background-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1)95%)}}}.dark-mode{--scalar-tooltip-background:var(--scalar-background-1)}@supports (color:color-mix(in lab,red,red)){.dark-mode{--scalar-tooltip-background:var(--scalar-background-1)}@supports (color:color-mix(in lab,red,red)){.dark-mode{--scalar-tooltip-background:color-mix(in srgb,var(--scalar-background-1),#fff 10%)}}}.dark-mode{--scalar-color-danger:var(--scalar-color-red)}@supports (color:color-mix(in lab,red,red)){.dark-mode{--scalar-color-danger:var(--scalar-color-red)}@supports (color:color-mix(in lab,red,red)){.dark-mode{--scalar-color-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1)20%)}}}.dark-mode{--scalar-background-alert:var(--scalar-color-orange)}@supports (color:color-mix(in lab,red,red)){.dark-mode{--scalar-background-alert:var(--scalar-color-orange)}@supports (color:color-mix(in lab,red,red)){.dark-mode{--scalar-background-alert:color-mix(in srgb,var(--scalar-color-orange),var(--scalar-background-1)95%)}}}.dark-mode{--scalar-background-danger:var(--scalar-color-red)}@supports (color:color-mix(in lab,red,red)){.dark-mode{--scalar-background-danger:var(--scalar-color-red)}@supports (color:color-mix(in lab,red,red)){.dark-mode{--scalar-background-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1)95%)}}}}:root,:host{--leading-snug:1.375;--ease-in:cubic-bezier(.4,0,1,1);--ease-out:cubic-bezier(0,0,.2,1);--ease-in-out:cubic-bezier(.4,0,.2,1);--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1)}@supports (color:color-mix(in lab,red,red)){@supports (color:color-mix(in lab,red,red)){.light-mode,.dark-mode{--scalar-sidebar-search-background:color-mix(in srgb,var(--scalar-background-2),var(--scalar-background-1))}}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-color-alert:color-mix(in srgb,var(--scalar-color-orange),var(--scalar-color-1)20%)}}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-color-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-color-1)20%)}}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-background-alert:color-mix(in srgb,var(--scalar-color-orange),var(--scalar-background-1)95%)}}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-background-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1)95%)}}@supports (color:color-mix(in lab,red,red)){.dark-mode{--scalar-tooltip-background:color-mix(in srgb,var(--scalar-background-1),#fff 10%)}}@supports (color:color-mix(in lab,red,red)){.dark-mode{--scalar-color-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1)20%)}}@supports (color:color-mix(in lab,red,red)){.dark-mode{--scalar-background-alert:color-mix(in srgb,var(--scalar-color-orange),var(--scalar-background-1)95%)}}@supports (color:color-mix(in lab,red,red)){.dark-mode{--scalar-background-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1)95%)}}.light-mode,.dark-mode{--scalar-sidebar-search-background:var(--scalar-background-2)}@supports (color:color-mix(in lab,red,red)){.light-mode,.dark-mode{--scalar-sidebar-search-background:var(--scalar-background-2)}@supports (color:color-mix(in lab,red,red)){.light-mode,.dark-mode{--scalar-sidebar-search-background:color-mix(in srgb,var(--scalar-background-2),var(--scalar-background-1))}}}.light-mode{--scalar-color-alert:var(--scalar-color-orange)}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-color-alert:var(--scalar-color-orange)}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-color-alert:color-mix(in srgb,var(--scalar-color-orange),var(--scalar-color-1)20%)}}}.light-mode{--scalar-color-danger:var(--scalar-color-red)}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-color-danger:var(--scalar-color-red)}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-color-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-color-1)20%)}}}.light-mode{--scalar-background-alert:var(--scalar-color-orange)}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-background-alert:var(--scalar-color-orange)}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-background-alert:color-mix(in srgb,var(--scalar-color-orange),var(--scalar-background-1)95%)}}}.light-mode{--scalar-background-danger:var(--scalar-color-red)}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-background-danger:var(--scalar-color-red)}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-background-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1)95%)}}}.dark-mode{--scalar-tooltip-background:var(--scalar-background-1)}@supports (color:color-mix(in lab,red,red)){.dark-mode{--scalar-tooltip-background:var(--scalar-background-1)}@supports (color:color-mix(in lab,red,red)){.dark-mode{--scalar-tooltip-background:color-mix(in srgb,var(--scalar-background-1),#fff 10%)}}}.dark-mode{--scalar-color-danger:var(--scalar-color-red)}@supports (color:color-mix(in lab,red,red)){.dark-mode{--scalar-color-danger:var(--scalar-color-red)}@supports (color:color-mix(in lab,red,red)){.dark-mode{--scalar-color-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1)20%)}}}.dark-mode{--scalar-background-alert:var(--scalar-color-orange)}@supports (color:color-mix(in lab,red,red)){.dark-mode{--scalar-background-alert:var(--scalar-color-orange)}@supports (color:color-mix(in lab,red,red)){.dark-mode{--scalar-background-alert:color-mix(in srgb,var(--scalar-color-orange),var(--scalar-background-1)95%)}}}.dark-mode{--scalar-background-danger:var(--scalar-color-red)}@supports (color:color-mix(in lab,red,red)){.dark-mode{--scalar-background-danger:var(--scalar-color-red)}@supports (color:color-mix(in lab,red,red)){.dark-mode{--scalar-background-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1)95%)}}}}body{line-height:inherit;margin:0}:root{--scalar-border-width:.5px;--scalar-radius:3px;--scalar-radius-lg:6px;--scalar-radius-xl:8px;--scalar-font:"Inter",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,"Open Sans","Helvetica Neue",sans-serif;--scalar-font-code:"JetBrains Mono",ui-monospace,Menlo,Monaco,"Cascadia Mono","Segoe UI Mono","Roboto Mono","Oxygen Mono","Ubuntu Monospace","Source Code Pro","Fira Mono","Droid Sans Mono","Courier New",monospace;--scalar-heading-1:24px;--scalar-page-description:16px;--scalar-heading-2:20px;--scalar-heading-3:16px;--scalar-heading-4:16px;--scalar-heading-5:16px;--scalar-heading-6:16px;--scalar-paragraph:16px;--scalar-small:14px;--scalar-mini:13px;--scalar-micro:12px;--scalar-bold:600;--scalar-semibold:500;--scalar-regular:400;--scalar-font-size-1:21px;--scalar-font-size-2:16px;--scalar-font-size-3:14px;--scalar-font-size-4:13px;--scalar-font-size-5:12px;--scalar-font-size-6:12px;--scalar-font-size-7:10px;--scalar-line-height-1:32px;--scalar-line-height-2:24px;--scalar-line-height-3:20px;--scalar-line-height-4:18px;--scalar-line-height-5:16px;--scalar-font-normal:400;--scalar-font-medium:500;--scalar-font-bold:700;--scalar-text-decoration:none;--scalar-text-decoration-hover:underline;--scalar-link-font-weight:inherit;--scalar-sidebar-indent:20px}.dark-mode{color-scheme:dark;--scalar-scrollbar-color:#ffffff2e;--scalar-scrollbar-color-active:#ffffff5c;--scalar-button-1:#fff;--scalar-button-1-hover:#ffffffe6;--scalar-button-1-color:black;--scalar-shadow-1:0 1px 3px 0 #0000001a;--scalar-shadow-2:0 0 0 .5px var(--scalar-border-color),#0f0f0f33 0px 3px 6px,#0f0f0f66 0px 9px 24px;--scalar-lifted-brightness:1.45;--scalar-backdrop-brightness:.5;--scalar-text-decoration-color:currentColor;--scalar-text-decoration-color-hover:currentColor}.light-mode{color-scheme:light;--scalar-scrollbar-color-active:#0000005c;--scalar-scrollbar-color:#0000002e;--scalar-button-1:#000;--scalar-button-1-hover:#000c;--scalar-button-1-color:#ffffffe6;--scalar-shadow-1:0 1px 3px 0 #0000001c;--scalar-shadow-2:#00000014 0px 13px 20px 0px,#00000014 0px 3px 8px 0px,#eeeeed 0px 0 0 .5px;--scalar-lifted-brightness:1;--scalar-backdrop-brightness:1;--scalar-text-decoration-color:currentColor;--scalar-text-decoration-color-hover:currentColor}.light-mode .dark-mode{color-scheme:dark!important}@media(max-width:460px){:root{--scalar-font-size-1:22px;--scalar-font-size-2:14px;--scalar-font-size-3:12px}}@media(max-width:720px){:root{--scalar-heading-1:24px;--scalar-page-description:20px}}:root{--scalar-text-decoration:underline;--scalar-text-decoration-hover:underline}.light-mode{--scalar-background-1:#fff;--scalar-background-2:#f6f6f6;--scalar-background-3:#e7e7e7;--scalar-background-accent:#8ab4f81f;--scalar-color-1:#1b1b1b;--scalar-color-2:#757575;--scalar-color-3:#8e8e8e;--scalar-color-accent:#09f;--scalar-border-color:#dfdfdf}.dark-mode{--scalar-background-1:#0f0f0f;--scalar-background-2:#1a1a1a;--scalar-background-3:#272727;--scalar-color-1:#e7e7e7;--scalar-color-2:#a4a4a4;--scalar-color-3:#797979;--scalar-color-accent:#00aeff;--scalar-background-accent:#3ea6ff1f;--scalar-border-color:#2d2d2d}.light-mode,.dark-mode{--scalar-sidebar-background-1:var(--scalar-background-1);--scalar-sidebar-color-1:var(--scalar-color-1);--scalar-sidebar-color-2:var(--scalar-color-2);--scalar-sidebar-border-color:var(--scalar-border-color);--scalar-sidebar-item-hover-background:var(--scalar-background-2);--scalar-sidebar-item-hover-color:var(--scalar-sidebar-color-2);--scalar-sidebar-item-active-background:var(--scalar-background-2);--scalar-sidebar-color-active:var(--scalar-sidebar-color-1);--scalar-sidebar-indent-border:var(--scalar-sidebar-border-color);--scalar-sidebar-indent-border-hover:var(--scalar-sidebar-border-color);--scalar-sidebar-indent-border-active:var(--scalar-sidebar-border-color);--scalar-sidebar-search-background:var(--scalar-background-2)}@supports (color:color-mix(in lab,red,red)){.light-mode,.dark-mode{--scalar-sidebar-search-background:var(--scalar-background-2)}@supports (color:color-mix(in lab,red,red)){.light-mode,.dark-mode{--scalar-sidebar-search-background:var(--scalar-background-2)}@supports (color:color-mix(in lab,red,red)){.light-mode,.dark-mode{--scalar-sidebar-search-background:var(--scalar-background-2)}@supports (color:color-mix(in lab,red,red)){.light-mode,.dark-mode{--scalar-sidebar-search-background:color-mix(in srgb,var(--scalar-background-2),var(--scalar-background-1))}}}}}.light-mode,.dark-mode{--scalar-sidebar-search-color:var(--scalar-color-3);--scalar-sidebar-search-border-color:var(--scalar-border-color)}.light-mode{--scalar-color-green:#069061;--scalar-color-red:#ef0006;--scalar-color-yellow:#edbe20;--scalar-color-blue:#0082d0;--scalar-color-orange:#ff5800;--scalar-color-purple:#5203d1;--scalar-link-color:var(--scalar-color-1);--scalar-link-color-hover:var(--scalar-link-color);--scalar-button-1:#000;--scalar-button-1-hover:#000c;--scalar-button-1-color:#ffffffe6;--scalar-tooltip-background:#1a1a1ae6;--scalar-tooltip-color:#ffffffd9;--scalar-color-alert:var(--scalar-color-orange)}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-color-alert:var(--scalar-color-orange)}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-color-alert:var(--scalar-color-orange)}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-color-alert:var(--scalar-color-orange)}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-color-alert:color-mix(in srgb,var(--scalar-color-orange),var(--scalar-color-1)20%)}}}}}.light-mode{--scalar-color-danger:var(--scalar-color-red)}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-color-danger:var(--scalar-color-red)}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-color-danger:var(--scalar-color-red)}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-color-danger:var(--scalar-color-red)}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-color-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-color-1)20%)}}}}}.light-mode{--scalar-background-alert:var(--scalar-color-orange)}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-background-alert:var(--scalar-color-orange)}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-background-alert:var(--scalar-color-orange)}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-background-alert:var(--scalar-color-orange)}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-background-alert:color-mix(in srgb,var(--scalar-color-orange),var(--scalar-background-1)95%)}}}}}.light-mode{--scalar-background-danger:var(--scalar-color-red)}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-background-danger:var(--scalar-color-red)}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-background-danger:var(--scalar-color-red)}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-background-danger:var(--scalar-color-red)}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-background-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1)95%)}}}}}.dark-mode{--scalar-color-green:#00b648;--scalar-color-red:#dc1b19;--scalar-color-yellow:#ffc90d;--scalar-color-blue:#4eb3ec;--scalar-color-orange:#ff8d4d;--scalar-color-purple:#b191f9;--scalar-link-color:var(--scalar-color-1);--scalar-link-color-hover:var(--scalar-link-color);--scalar-button-1:#fff;--scalar-button-1-hover:#ffffffe6;--scalar-button-1-color:black;--scalar-tooltip-background:var(--scalar-background-1)}@supports (color:color-mix(in lab,red,red)){.dark-mode{--scalar-tooltip-background:var(--scalar-background-1)}@supports (color:color-mix(in lab,red,red)){.dark-mode{--scalar-tooltip-background:var(--scalar-background-1)}@supports (color:color-mix(in lab,red,red)){.dark-mode{--scalar-tooltip-background:var(--scalar-background-1)}@supports (color:color-mix(in lab,red,red)){.dark-mode{--scalar-tooltip-background:color-mix(in srgb,var(--scalar-background-1),#fff 10%)}}}}}.dark-mode{--scalar-tooltip-color:#fffffff2;--scalar-color-danger:var(--scalar-color-red)}@supports (color:color-mix(in lab,red,red)){.dark-mode{--scalar-color-danger:var(--scalar-color-red)}@supports (color:color-mix(in lab,red,red)){.dark-mode{--scalar-color-danger:var(--scalar-color-red)}@supports (color:color-mix(in lab,red,red)){.dark-mode{--scalar-color-danger:var(--scalar-color-red)}@supports (color:color-mix(in lab,red,red)){.dark-mode{--scalar-color-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1)20%)}}}}}.dark-mode{--scalar-background-alert:var(--scalar-color-orange)}@supports (color:color-mix(in lab,red,red)){.dark-mode{--scalar-background-alert:var(--scalar-color-orange)}@supports (color:color-mix(in lab,red,red)){.dark-mode{--scalar-background-alert:var(--scalar-color-orange)}@supports (color:color-mix(in lab,red,red)){.dark-mode{--scalar-background-alert:var(--scalar-color-orange)}@supports (color:color-mix(in lab,red,red)){.dark-mode{--scalar-background-alert:color-mix(in srgb,var(--scalar-color-orange),var(--scalar-background-1)95%)}}}}}.dark-mode{--scalar-background-danger:var(--scalar-color-red)}@supports (color:color-mix(in lab,red,red)){.dark-mode{--scalar-background-danger:var(--scalar-color-red)}@supports (color:color-mix(in lab,red,red)){.dark-mode{--scalar-background-danger:var(--scalar-color-red)}@supports (color:color-mix(in lab,red,red)){.dark-mode{--scalar-background-danger:var(--scalar-color-red)}@supports (color:color-mix(in lab,red,red)){.dark-mode{--scalar-background-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1)95%)}}}}}@supports (color:color(display-p3 1 1 1)){.light-mode{--scalar-color-accent:color(display-p3 0 .6 1);--scalar-color-green:color(display-p3 .023529 .564706 .380392);--scalar-color-red:color(display-p3 .937255 0 .023529);--scalar-color-yellow:color(display-p3 .929412 .745098 .12549);--scalar-color-blue:color(display-p3 0 .509804 .815686);--scalar-color-orange:color(display-p3 1 .4 .02);--scalar-color-purple:color(display-p3 .321569 .011765 .819608)}.dark-mode{--scalar-color-accent:color(display-p3 .07 .67 1);--scalar-color-green:color(display-p3 0 .713725 .282353);--scalar-color-red:color(display-p3 .862745 .105882 .098039);--scalar-color-yellow:color(display-p3 1 .788235 .05098);--scalar-color-blue:color(display-p3 .305882 .701961 .92549);--scalar-color-orange:color(display-p3 1 .552941 .301961);--scalar-color-purple:color(display-p3 .694118 .568627 .976471)}}:root,:host{--leading-snug:1.375;--ease-in:cubic-bezier(.4,0,1,1);--ease-out:cubic-bezier(0,0,.2,1);--ease-in-out:cubic-bezier(.4,0,.2,1);--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--leading-normal:1.5}body{background-color:var(--scalar-background-1);margin:0}}@layer scalar-theme;.scalar-app .\\@container{container-type:inline-size}.scalar-app .-top-2{top:-8px}.scalar-app .top-\\(--refs-header-height\\){top:var(--refs-header-height)}.scalar-app .top-\\(--scalar-custom-header-height\\,0\\){top:var(--scalar-custom-header-height,0)}.scalar-app .top-3\\.5{top:14px}.scalar-app .top-\\[calc\\(9px\\+0\\.5lh\\)\\]{top:calc(9px + .5lh)}.scalar-app .-left-4\\.5{left:-18px}.scalar-app .-left-5{left:-20px}.scalar-app .-left-6{left:-24px}.scalar-app .order-789{order:789}.scalar-app .-m-2{margin:-8px}.scalar-app .-mx-2{margin-inline:-8px}.scalar-app .my-2{margin-block:8px}.scalar-app .my-3{margin-block:12px}.scalar-app .-mt-1{margin-top:-4px}.scalar-app .mt-6{margin-top:24px}.scalar-app .mb-3{margin-bottom:12px}.scalar-app .size-4\\.5{width:18px;height:18px}.scalar-app .h-\\(--refs-sidebar-height\\){height:var(--refs-sidebar-height)}.scalar-app .h-\\(--scalar-header-height\\){height:var(--scalar-header-height)}.scalar-app .h-\\[calc\\(100\\%\\+16px\\)\\]{height:calc(100% + 16px)}.scalar-app .max-h-\\[60vh\\]{max-height:60vh}.scalar-app .min-h-3{min-height:12px}.scalar-app .min-h-7{min-height:28px}.scalar-app .min-h-dvh{min-height:100dvh}.scalar-app .w-\\(--refs-sidebar-width\\){width:var(--refs-sidebar-width)}.scalar-app .w-4\\.5{width:18px}.scalar-app .w-96{width:384px}.scalar-app .w-110{width:440px}.scalar-app .w-120{width:480px}.scalar-app .max-w-\\(--refs-content-max-width\\){max-width:var(--refs-content-max-width)}.scalar-app .max-w-64{max-width:256px}.scalar-app .min-w-3{min-width:12px}.scalar-app .min-w-7{min-width:28px}.scalar-app .flex-grow{flex-grow:1}.scalar-app .rotate-45{rotate:45deg}.scalar-app .scroll-mt-16{scroll-margin-top:64px}.scalar-app .scroll-mt-24{scroll-margin-top:96px}.scalar-app .list-none{list-style-type:none}.scalar-app .content-end{align-content:flex-end}.scalar-app .gap-7{gap:28px}.scalar-app .\\!rounded-b-xl{border-bottom-right-radius:var(--scalar-radius-xl)!important;border-bottom-left-radius:var(--scalar-radius-xl)!important}@supports (color:color-mix(in lab,red,red)){.scalar-app .bg-b-1\\.5{background-color:color-mix(in srgb,var(--scalar-background-1),var(--scalar-background-2))}}.scalar-app .bg-linear-to-l{--tw-gradient-position:to left}@supports (background-image:linear-gradient(in lab,red,red)){.scalar-app .bg-linear-to-l{--tw-gradient-position:to left in oklab}}.scalar-app .bg-linear-to-l{background-image:linear-gradient(var(--tw-gradient-stops))}.scalar-app .from-40\\%{--tw-gradient-from-position:40%}.scalar-app .to-transparent{--tw-gradient-to:transparent;--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.scalar-app .p-7{padding:28px}.scalar-app .px-15{padding-inline:60px}.scalar-app .py-2\\.25{padding-block:9px}.scalar-app .pt-1\\.5{padding-top:6px}.scalar-app .pb-12{padding-bottom:48px}.scalar-app .leading-\\[1\\.45\\]{--tw-leading:1.45;line-height:1.45}.scalar-app .leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.scalar-app .text-current{color:currentColor}.scalar-app .\\[--scalar-address-bar-height\\:0px\\]{--scalar-address-bar-height:0px}.scalar-app .\\[grid-area\\:header\\]{grid-area:header}.scalar-app .\\[grid-area\\:navigation\\]{grid-area:navigation}.scalar-app .group-last\\:mr-0:is(:where(.group):last-child *){margin-right:0}.scalar-app .group-open\\:rotate-90:is(:where(.group):is([open],:popover-open,:open) *){rotate:90deg}.scalar-app .group-open\\:flex-wrap:is(:where(.group):is([open],:popover-open,:open) *){flex-wrap:wrap}.scalar-app .group-open\\:whitespace-normal:is(:where(.group):is([open],:popover-open,:open) *){white-space:normal}.scalar-app .group-focus-within\\/parameter-item\\:opacity-100:is(:where(.group\\/parameter-item):focus-within *){opacity:1}@media(hover:hover){.scalar-app .group-hover\\:flex:is(:where(.group):hover *){display:flex}.scalar-app .group-hover\\:text-c-1:is(:where(.group):hover *){color:var(--scalar-color-1)}.scalar-app .group-hover\\:opacity-100:is(:where(.group):hover *),.scalar-app .group-hover\\/heading\\:opacity-100:is(:where(.group\\/heading):hover *),.scalar-app .group-hover\\/parameter-item\\:opacity-100:is(:where(.group\\/parameter-item):hover *){opacity:1}}.scalar-app .group-has-focus-visible\\/heading\\:opacity-100:is(:where(.group\\/heading):has(:focus-visible) *){opacity:1}:is(.scalar-app .\\*\\:first\\:p-3>*):first-child{padding:12px}.scalar-app .empty\\:hidden:empty{display:none}@media(hover:hover){.scalar-app .hover\\:bg-b-2:hover{background-color:var(--scalar-background-2)}.scalar-app .hover\\:text-c-1:hover{color:var(--scalar-color-1)}.scalar-app .hover\\:text-sidebar-c-1:hover{color:var(--scalar-sidebar-color-1,var(--scalar-color-1))}}.scalar-app .focus-visible\\:opacity-100:focus-visible{opacity:1}@media(min-width:1200px){.scalar-app .xl\\:mb-1\\.5{margin-bottom:6px}.scalar-app .xl\\:gap-12{gap:48px}.scalar-app .xl\\:border-r{border-right-style:var(--tw-border-style);border-right-width:var(--scalar-border-width)}.scalar-app .xl\\:border-none{--tw-border-style:none;border-style:none}.scalar-app .xl\\:first\\:ml-auto:first-child{margin-left:auto}}.scalar-app .\\[\\&_a\\]\\:underline a{text-decoration-line:underline}.scalar-app .\\[\\&_a\\:hover\\]\\:text-c-1 a:hover{color:var(--scalar-color-1)}.scalar-app .\\[\\&_code\\]\\:font-code code{font-family:var(--scalar-font-code)}.scalar-app .\\[\\&_em\\]\\:text-c-1 em{color:var(--scalar-color-1)}.scalar-app .\\[\\&_em\\]\\:not-italic em{font-style:normal}@supports (color:color-mix(in lab,red,red)){.scalar-app .bg-b-1\\.5{background-color:var(--scalar-background-1)}@supports (color:color-mix(in lab,red,red)){.scalar-app .bg-b-1\\.5{background-color:color-mix(in srgb,var(--scalar-background-1),var(--scalar-background-2))}}}@supports (color:color-mix(in lab,red,red)){.scalar-app .to-b-1\\.5{--tw-gradient-to:var(--scalar-background-1)}@supports (color:color-mix(in lab,red,red)){.scalar-app .to-b-1\\.5{--tw-gradient-to:color-mix(in srgb,var(--scalar-background-1),var(--scalar-background-2))}}:is(.scalar-app .\\*\\:border-border-tooltip>*){border-color:var(--scalar-tooltip-color)}@supports (color:color-mix(in lab,red,red)){:is(.scalar-app .\\*\\:border-border-tooltip>*){border-color:color-mix(in srgb,var(--scalar-tooltip-color),var(--scalar-tooltip-background))}}}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}@property --tw-divide-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-mask-linear{syntax:"*";inherits:false;initial-value:linear-gradient(#fff,#fff)}@property --tw-mask-radial{syntax:"*";inherits:false;initial-value:linear-gradient(#fff,#fff)}@property --tw-mask-conic{syntax:"*";inherits:false;initial-value:linear-gradient(#fff,#fff)}@property --tw-mask-left{syntax:"*";inherits:false;initial-value:linear-gradient(#fff,#fff)}@property --tw-mask-right{syntax:"*";inherits:false;initial-value:linear-gradient(#fff,#fff)}@property --tw-mask-bottom{syntax:"*";inherits:false;initial-value:linear-gradient(#fff,#fff)}@property --tw-mask-top{syntax:"*";inherits:false;initial-value:linear-gradient(#fff,#fff)}@property --tw-mask-top-from-position{syntax:"*";inherits:false;initial-value:0%}@property --tw-mask-top-to-position{syntax:"*";inherits:false;initial-value:100%}@property --tw-mask-top-from-color{syntax:"*";inherits:false;initial-value:black}@property --tw-mask-top-to-color{syntax:"*";inherits:false;initial-value:transparent}@property --tw-mask-bottom-from-position{syntax:"*";inherits:false;initial-value:0%}@property --tw-mask-bottom-to-position{syntax:"*";inherits:false;initial-value:100%}@property --tw-mask-bottom-from-color{syntax:"*";inherits:false;initial-value:black}@property --tw-mask-bottom-to-color{syntax:"*";inherits:false;initial-value:transparent}@property --tw-ease{syntax:"*";inherits:false}@keyframes fade-in-27df5cd8{0%{opacity:0}70%{opacity:0}to{opacity:1}}@keyframes rotate-27df5cd8{0%{transform:scale(3.5)rotate(0)}to{transform:scale(3.5)rotate(360deg)}}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown a{-webkit-text-decoration-color:color-mix(in srgb,var(--font-color)30%,transparent);text-decoration-color:color-mix(in srgb,var(--font-color)30%,transparent)}}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown .markdown-alert{background-color:color-mix(in srgb,var(--scalar-background-2),transparent)}}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown .markdown-alert.markdown-alert-note{background-color:color-mix(in srgb,var(--scalar-color-blue),transparent 97%)}}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown .markdown-alert.markdown-alert-note{border:var(--scalar-border-width)solid color-mix(in srgb,var(--scalar-color-blue),transparent 50%)}}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown .markdown-alert.markdown-alert-tip{background-color:color-mix(in srgb,var(--scalar-color-2),transparent 97%)}}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown .markdown-alert.markdown-alert-tip{border:var(--scalar-border-width)solid color-mix(in srgb,var(--scalar-color-2),transparent 50%)}}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown .markdown-alert.markdown-alert-important,.scalar-app .markdown .markdown-alert.markdown-alert-warning{background-color:color-mix(in srgb,var(--scalar-color-orange),transparent 97%)}}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown .markdown-alert.markdown-alert-important,.scalar-app .markdown .markdown-alert.markdown-alert-warning{border:var(--scalar-border-width)solid color-mix(in srgb,var(--scalar-color-orange),transparent 50%)}}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown .markdown-alert.markdown-alert-caution{background-color:color-mix(in srgb,var(--scalar-color-red),transparent 97%)}}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown .markdown-alert.markdown-alert-caution{border:var(--scalar-border-width)solid color-mix(in srgb,var(--scalar-color-red),transparent 50%)}}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown .markdown-alert.markdown-alert-success{background-color:color-mix(in srgb,var(--scalar-color-green),transparent 97%)}}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown .markdown-alert.markdown-alert-success{border:var(--scalar-border-width)solid color-mix(in srgb,var(--scalar-color-green),transparent 50%)}}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@keyframes fadein-layout-c36b47da{0%{opacity:0}to{opacity:1}}@keyframes fadein-modal-c36b47da{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translate(0)}}@media(hover:hover){.scalar-app .group-hover\\/button\\:opacity-0:is(:where(.group\\/button):hover *){opacity:0}.scalar-app .peer-hover\\/button\\:opacity-100:is(:where(.peer\\/button):hover~*),.scalar-app .hover\\:opacity-100:hover{opacity:1}}@supports (color:color-mix(in lab,red,red)){.scalar-app .bg-b-1\\.5{background-color:var(--scalar-background-1)}@supports (color:color-mix(in lab,red,red)){.scalar-app .bg-b-1\\.5{background-color:var(--scalar-background-1)}@supports (color:color-mix(in lab,red,red)){.scalar-app .bg-b-1\\.5{background-color:color-mix(in srgb,var(--scalar-background-1),var(--scalar-background-2))}}}}.scalar-app .bg-c-accent{background-color:var(--scalar-color-accent)}@supports (color:color-mix(in lab,red,red)){.scalar-app .to-b-1\\.5{--tw-gradient-to:var(--scalar-background-1)}@supports (color:color-mix(in lab,red,red)){.scalar-app .to-b-1\\.5{--tw-gradient-to:var(--scalar-background-1)}@supports (color:color-mix(in lab,red,red)){.scalar-app .to-b-1\\.5{--tw-gradient-to:color-mix(in srgb,var(--scalar-background-1),var(--scalar-background-2))}}}:is(.scalar-app .\\*\\:border-border-tooltip>*){border-color:var(--scalar-tooltip-color)}@supports (color:color-mix(in lab,red,red)){:is(.scalar-app .\\*\\:border-border-tooltip>*){border-color:var(--scalar-tooltip-color)}@supports (color:color-mix(in lab,red,red)){:is(.scalar-app .\\*\\:border-border-tooltip>*){border-color:color-mix(in srgb,var(--scalar-tooltip-color),var(--scalar-tooltip-background))}}}}@property --tw-content{syntax:"*";inherits:false;initial-value:""}@supports (color:color-mix(in lab,red,red)){.scalar-app .bg-b-1\\.5{background-color:var(--scalar-background-1)}@supports (color:color-mix(in lab,red,red)){.scalar-app .bg-b-1\\.5{background-color:var(--scalar-background-1)}@supports (color:color-mix(in lab,red,red)){.scalar-app .bg-b-1\\.5{background-color:color-mix(in srgb,var(--scalar-background-1),var(--scalar-background-2))}}}.scalar-app .to-b-1\\.5{--tw-gradient-to:var(--scalar-background-1)}@supports (color:color-mix(in lab,red,red)){.scalar-app .to-b-1\\.5{--tw-gradient-to:var(--scalar-background-1)}@supports (color:color-mix(in lab,red,red)){.scalar-app .to-b-1\\.5{--tw-gradient-to:color-mix(in srgb,var(--scalar-background-1),var(--scalar-background-2))}}}:is(.scalar-app .\\*\\:border-border-tooltip>*){border-color:var(--scalar-tooltip-color)}@supports (color:color-mix(in lab,red,red)){:is(.scalar-app .\\*\\:border-border-tooltip>*){border-color:var(--scalar-tooltip-color)}@supports (color:color-mix(in lab,red,red)){:is(.scalar-app .\\*\\:border-border-tooltip>*){border-color:color-mix(in srgb,var(--scalar-tooltip-color),var(--scalar-tooltip-background))}}}}@media(hover:hover){.scalar-app .group-hover\\:text-c-1:is(:where(.group):hover *){color:var(--scalar-color-1)}.scalar-app .group-hover\\/button\\:bg-sidebar-indent-border-hover:is(:where(.group\\/button):hover *){background-color:var(--scalar-sidebar-indent-border-hover,var(--scalar-border-color))}.scalar-app .group-hover\\/button\\:text-c-1:is(:where(.group\\/button):hover *){color:var(--scalar-color-1)}.scalar-app .hover\\:bg-b-2:hover{background-color:var(--scalar-background-2)}.scalar-app .hover\\:bg-b-3:hover{background-color:var(--scalar-background-3)}.scalar-app .hover\\:bg-h-btn:hover{background-color:var(--scalar-button-1-hover)}.scalar-app .hover\\:bg-sidebar-b-1:hover{background-color:var(--scalar-sidebar-background-1,var(--scalar-background-1))}.scalar-app .hover\\:bg-sidebar-b-hover:hover{background-color:var(--scalar-sidebar-item-hover-background,var(--scalar-background-2))}.scalar-app .hover\\:bg-linear-to-b:hover{--tw-gradient-position:to bottom}@supports (background-image:linear-gradient(in lab,red,red)){.scalar-app .hover\\:bg-linear-to-b:hover{--tw-gradient-position:to bottom in oklab}}.scalar-app .hover\\:bg-linear-to-b:hover{background-image:linear-gradient(var(--tw-gradient-stops))}.scalar-app .hover\\:bg-linear-to-t:hover{--tw-gradient-position:to top}@supports (background-image:linear-gradient(in lab,red,red)){.scalar-app .hover\\:bg-linear-to-t:hover{--tw-gradient-position:to top in oklab}}.scalar-app .hover\\:bg-linear-to-t:hover{background-image:linear-gradient(var(--tw-gradient-stops))}.scalar-app .hover\\:text-c-1:hover{color:var(--scalar-color-1)}.scalar-app .hover\\:text-sidebar-c-1:hover{color:var(--scalar-sidebar-color-1,var(--scalar-color-1))}.scalar-app .hover\\:text-sidebar-c-hover:hover{color:var(--scalar-sidebar-item-hover-color,var(--scalar-sidebar-color-2))}.scalar-app .hover\\:underline:hover{text-decoration-line:underline}.scalar-app .hover\\:brightness-90:hover{--tw-brightness:brightness(90%);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown a{-webkit-text-decoration-color:var(--font-color)}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown a{-webkit-text-decoration-color:color-mix(in srgb,var(--font-color)30%,transparent)}}.scalar-app .markdown a{-webkit-text-decoration-color:var(--font-color);text-decoration-color:var(--font-color)}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown a{-webkit-text-decoration-color:color-mix(in srgb,var(--font-color)30%,transparent);text-decoration-color:color-mix(in srgb,var(--font-color)30%,transparent)}}.scalar-app .markdown .markdown-alert{background-color:var(--scalar-background-2)}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown .markdown-alert{background-color:color-mix(in srgb,var(--scalar-background-2),transparent)}}.scalar-app .markdown .markdown-alert.markdown-alert-note{background-color:var(--scalar-color-blue)}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown .markdown-alert.markdown-alert-note{background-color:color-mix(in srgb,var(--scalar-color-blue),transparent 97%)}}.scalar-app .markdown .markdown-alert.markdown-alert-note{border:var(--scalar-border-width)solid var(--scalar-color-blue)}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown .markdown-alert.markdown-alert-note{border:var(--scalar-border-width)solid color-mix(in srgb,var(--scalar-color-blue),transparent 50%)}}.scalar-app .markdown .markdown-alert.markdown-alert-tip{background-color:var(--scalar-color-2)}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown .markdown-alert.markdown-alert-tip{background-color:color-mix(in srgb,var(--scalar-color-2),transparent 97%)}}.scalar-app .markdown .markdown-alert.markdown-alert-tip{border:var(--scalar-border-width)solid var(--scalar-color-2)}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown .markdown-alert.markdown-alert-tip{border:var(--scalar-border-width)solid color-mix(in srgb,var(--scalar-color-2),transparent 50%)}}.scalar-app .markdown .markdown-alert.markdown-alert-important,.scalar-app .markdown .markdown-alert.markdown-alert-warning{background-color:var(--scalar-color-orange)}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown .markdown-alert.markdown-alert-important,.scalar-app .markdown .markdown-alert.markdown-alert-warning{background-color:color-mix(in srgb,var(--scalar-color-orange),transparent 97%)}}.scalar-app .markdown .markdown-alert.markdown-alert-important,.scalar-app .markdown .markdown-alert.markdown-alert-warning{border:var(--scalar-border-width)solid var(--scalar-color-orange)}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown .markdown-alert.markdown-alert-important,.scalar-app .markdown .markdown-alert.markdown-alert-warning{border:var(--scalar-border-width)solid color-mix(in srgb,var(--scalar-color-orange),transparent 50%)}}.scalar-app .markdown .markdown-alert.markdown-alert-caution{background-color:var(--scalar-color-red)}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown .markdown-alert.markdown-alert-caution{background-color:color-mix(in srgb,var(--scalar-color-red),transparent 97%)}}.scalar-app .markdown .markdown-alert.markdown-alert-caution{border:var(--scalar-border-width)solid var(--scalar-color-red)}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown .markdown-alert.markdown-alert-caution{border:var(--scalar-border-width)solid color-mix(in srgb,var(--scalar-color-red),transparent 50%)}}.scalar-app .markdown .markdown-alert.markdown-alert-success{background-color:var(--scalar-color-green)}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown .markdown-alert.markdown-alert-success{background-color:color-mix(in srgb,var(--scalar-color-green),transparent 97%)}}.scalar-app .markdown .markdown-alert.markdown-alert-success{border:var(--scalar-border-width)solid var(--scalar-color-green)}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown .markdown-alert.markdown-alert-success{border:var(--scalar-border-width)solid color-mix(in srgb,var(--scalar-color-green),transparent 50%)}}}.scalar-app .right-0\\.75{right:3px}.scalar-app .ml-2{margin-left:8px}.scalar-app .self-start{align-self:flex-start}@media(hover:hover){.scalar-app .group-hover\\/button\\:opacity-0:is(:where(.group\\/button):hover *){opacity:0}}.scalar-app .group-focus-visible\\/button\\:opacity-0:is(:where(.group\\/button):focus-visible *),.scalar-app .group-has-\\[\\~\\*_\\[aria-expanded\\=true\\]\\]\\/button\\:opacity-0:is(:where(.group\\/button):has(~* [aria-expanded=true]) *),.scalar-app .group-has-\\[\\~\\*\\:focus-within\\]\\/button\\:opacity-0:is(:where(.group\\/button):has(~:focus-within) *),.scalar-app .group-has-\\[\\~\\*\\:hover\\]\\/button\\:opacity-0:is(:where(.group\\/button):has(~:hover) *){opacity:0}@media(hover:hover){.scalar-app .peer-hover\\/button\\:opacity-100:is(:where(.peer\\/button):hover~*){opacity:1}}.scalar-app .peer-focus-visible\\/button\\:opacity-100:is(:where(.peer\\/button):focus-visible~*){opacity:1}.scalar-app .after\\:pointer-events-none:after{content:var(--tw-content);pointer-events:none}.scalar-app .after\\:absolute:after{content:var(--tw-content);position:absolute}.scalar-app .after\\:inset-0:after{content:var(--tw-content);inset:0}.scalar-app .after\\:inset-x-0:after{content:var(--tw-content);inset-inline:0}.scalar-app .after\\:-top-0\\.5:after{content:var(--tw-content);top:-2px}.scalar-app .after\\:-bottom-0\\.5:after{content:var(--tw-content);bottom:-2px}.scalar-app .after\\:block:after{content:var(--tw-content);display:block}.scalar-app .after\\:h-0\\.75:after{content:var(--tw-content);height:3px}.scalar-app .after\\:rounded:after{content:var(--tw-content);border-radius:var(--scalar-radius)}.scalar-app .after\\:bg-blue:after{content:var(--tw-content);background-color:var(--scalar-color-blue)}.scalar-app .after\\:opacity-15:after{content:var(--tw-content);opacity:.15}.scalar-app .focus-within\\:opacity-100:focus-within{opacity:1}@media(hover:hover){.scalar-app .hover\\:opacity-100:hover{opacity:1}}.scalar-app .has-\\[\\&\\[aria-expanded\\=true\\]\\]\\:opacity-100:has([aria-expanded=true]){opacity:1}:where(.scalar-app){font-family:var(--scalar-font);color:var(--scalar-color-1);-webkit-text-size-adjust:100%;tab-size:4;line-height:1.15}:where(.scalar-app) *,:where(.scalar-app) :before,:where(.scalar-app) :after{box-sizing:border-box;border-style:solid;border-width:0;border-color:var(--scalar-border-color);outline-width:1px;outline-style:none;outline-color:var(--scalar-color-accent);font-feature-settings:inherit;font-variation-settings:inherit;font-family:inherit;font-size:inherit;font-weight:inherit;font-style:inherit;-webkit-text-decoration:inherit;text-decoration:inherit;text-align:inherit;line-height:inherit;color:inherit;margin:unset;padding:unset;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}:where(.scalar-app) :before,:where(.scalar-app) :after{--tw-content:""}:where(.scalar-app) button,:where(.scalar-app) input,:where(.scalar-app) optgroup,:where(.scalar-app) select,:where(.scalar-app) textarea{background:0 0}:where(.scalar-app) ::file-selector-button{background:0 0}:where(.scalar-app) ol,:where(.scalar-app) ul,:where(.scalar-app) menu{list-style:none}:where(.scalar-app) input:where(:not([type=button],[type=reset],[type=submit])),:where(.scalar-app) select,:where(.scalar-app) textarea{border-radius:var(--scalar-radius);border-width:1px}:where(.scalar-app) input::placeholder{color:var(--scalar-color-3);font-family:var(--scalar-font)}:where(.scalar-app) input[type=search]::-webkit-search-cancel-button{appearance:none}:where(.scalar-app) input[type=search]::-webkit-search-decoration{appearance:none}:where(.scalar-app) summary::-webkit-details-marker{display:none}:where(.scalar-app) input:-webkit-autofill{-webkit-background-clip:text!important;background-clip:text!important}:where(.scalar-app) :focus-visible{border-radius:var(--scalar-radius);outline-style:solid}:where(.scalar-app) button:focus-visible,:where(.scalar-app) [role=button]:focus-visible{outline-offset:-1px}:where(.scalar-app) button,:where(.scalar-app) [role=button]{cursor:pointer}:where(.scalar-app) :disabled{cursor:default}:where(.scalar-app) img,:where(.scalar-app) svg,:where(.scalar-app) video,:where(.scalar-app) canvas,:where(.scalar-app) audio,:where(.scalar-app) iframe,:where(.scalar-app) embed,:where(.scalar-app) object{vertical-align:middle;display:block}:where(.scalar-app) [hidden]{display:none}.scalar-app .cm-scroller,.scalar-app .custom-scroll{scrollbar-color:transparent transparent;scrollbar-width:thin;-webkit-overflow-scrolling:touch;overflow-y:auto}.scalar-app .custom-scroll-self-contain-overflow{overscroll-behavior:contain}.scalar-app .cm-scroller:hover,.scalar-app .custom-scroll:hover,.scalar-app.scalar-scrollbars-obtrusive .cm-scroller,.scalar-app.scalar-scrollbars-obtrusive .custom-scroll{scrollbar-color:var(--scalar-scrollbar-color,transparent)transparent}.scalar-app .cm-scroller:hover::-webkit-scrollbar-thumb{background:var(--scalar-scrollbar-color);background-clip:content-box;border:3px solid #0000}.scalar-app .custom-scroll:hover::-webkit-scrollbar-thumb{background:var(--scalar-scrollbar-color);background-clip:content-box;border:3px solid #0000}.scalar-app .cm-scroller::-webkit-scrollbar-thumb:active{background:var(--scalar-scrollbar-color-active);background-clip:content-box;border:3px solid #0000}.scalar-app .custom-scroll::-webkit-scrollbar-thumb:active{background:var(--scalar-scrollbar-color-active);background-clip:content-box;border:3px solid #0000}.scalar-app .cm-scroller::-webkit-scrollbar-corner{background:0 0}.scalar-app .custom-scroll::-webkit-scrollbar-corner{background:0 0}.scalar-app .cm-scroller::-webkit-scrollbar{width:12px;height:12px}.scalar-app .custom-scroll::-webkit-scrollbar{width:12px;height:12px}.scalar-app .cm-scroller::-webkit-scrollbar-track{background:0 0}.scalar-app .custom-scroll::-webkit-scrollbar-track{background:0 0}.scalar-app .cm-scroller::-webkit-scrollbar-thumb{background:padding-box content-box;border:3px solid #0000;border-radius:20px}.scalar-app .custom-scroll::-webkit-scrollbar-thumb{background:padding-box content-box;border:3px solid #0000;border-radius:20px}@media(pointer:coarse){.scalar-app .cm-scroller,.scalar-app .custom-scroll{padding-right:12px}}.scalar-app .invisible{visibility:hidden}.scalar-app .-inset-y-0\\.5{inset-block:-2px}.scalar-app .-inset-y-0\\.75{inset-block:-3px}.scalar-app .inset-y-0{inset-block:0}.scalar-app .inset-y-0\\.5{inset-block:2px}.scalar-app .-top-1{top:-4px}.scalar-app .top-\\(--nested-items-offset\\){top:var(--nested-items-offset)}.scalar-app .top-0\\.5{top:2px}.scalar-app .top-1\\/2{top:50%}.scalar-app .top-2\\.5{top:10px}.scalar-app .top-22{top:88px}.scalar-app .top-\\[1lh\\]{top:1lh}.scalar-app .top-\\[calc\\(10px\\+0\\.5lh\\)\\]{top:calc(10px + .5lh)}.scalar-app .top-px{top:1px}.scalar-app .-right-1{right:-4px}.scalar-app .-right-1\\.5{right:-6px}.scalar-app .right-1\\.25{right:5px}.scalar-app .right-2\\.5{right:10px}.scalar-app .left-2{left:8px}.scalar-app .left-2\\.5{left:10px}.scalar-app .left-4{left:16px}.scalar-app .left-border{left:var(--scalar-border-width)}.scalar-app .left-px{left:1px}.scalar-app .-z-2{z-index:-2}.scalar-app .z-tooltip{z-index:99999}.scalar-app .-m-1{margin:-4px}.scalar-app .-m-1\\.5{margin:-6px}.scalar-app .-m-px{margin:-1px}.scalar-app .m-1{margin:4px}.scalar-app .-mx-0\\.75{margin-inline:-3px}.scalar-app .-mx-px{margin-inline:-1px}.scalar-app .mx-px{margin-inline:1px}.scalar-app .-my-1\\.5{margin-block:-6px}.scalar-app .-my-2{margin-block:-8px}.scalar-app .my-0\\.75{margin-block:3px}.scalar-app .my-1\\.5{margin-block:6px}.scalar-app .-mt-1\\.5{margin-top:-6px}.scalar-app .mt-0{margin-top:0}.scalar-app .mt-\\[15svh\\]{margin-top:15svh}.scalar-app .mt-\\[20svh\\]{margin-top:20svh}.scalar-app .-mr-0\\.25{margin-right:-1px}.scalar-app .mr-0{margin-right:0}.scalar-app .mr-\\[calc\\(20px-var\\(--scalar-sidebar-indent\\)\\)\\]{margin-right:calc(20px - var(--scalar-sidebar-indent))}.scalar-app .-mb-1{margin-bottom:-4px}.scalar-app .-ml-0\\.75{margin-left:-3px}.scalar-app .line-clamp-\\(--markdown-clamp\\){-webkit-line-clamp:var(--markdown-clamp);-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.scalar-app .\\!hidden{display:none!important}.scalar-app .size-2{width:8px;height:8px}.scalar-app .size-2\\.75{width:11px;height:11px}.scalar-app .size-3\\.25{width:13px;height:13px}.scalar-app .size-60{width:240px;height:240px}.scalar-app .size-\\[23px\\]{width:23px;height:23px}.scalar-app .h-0{height:0}.scalar-app .h-1{height:4px}.scalar-app .h-24{height:96px}.scalar-app .h-32{height:128px}.scalar-app .h-\\[1lh\\]{height:1lh}.scalar-app .h-border{height:var(--scalar-border-width)}.scalar-app .max-h-20{max-height:80px}.scalar-app .max-h-\\[80svh\\]{max-height:80svh}.scalar-app .max-h-\\[90svh\\]{max-height:90svh}.scalar-app .max-h-dvh{max-height:100dvh}.scalar-app .max-h-radix-popper{max-height:calc(var(--radix-popper-available-height) - 8px)}.scalar-app .min-h-96{min-height:384px}.scalar-app .min-h-header{min-height:48px}.scalar-app .w-12{width:48px}.scalar-app .w-24{width:96px}.scalar-app .w-32{width:128px}.scalar-app .w-40{width:160px}.scalar-app .w-48{width:192px}.scalar-app .w-\\[38px\\]{width:38px}.scalar-app .w-\\[calc\\(100vw-12px\\)\\]{width:calc(100vw - 12px)}.scalar-app .w-\\[var\\(--scalar-sidebar-indent\\)\\]{width:var(--scalar-sidebar-indent)}.scalar-app .w-border{width:var(--scalar-border-width)}.scalar-app .w-min{width:min-content}.scalar-app .w-screen{width:100vw}.scalar-app .max-w-\\[360px\\]{max-width:360px}.scalar-app .max-w-\\[480px\\]{max-width:480px}.scalar-app .max-w-\\[540px\\]{max-width:540px}.scalar-app .max-w-\\[640px\\]{max-width:640px}.scalar-app .max-w-\\[800px\\]{max-width:800px}.scalar-app .max-w-\\[1000px\\]{max-width:1000px}.scalar-app .max-w-\\[inherit\\]{max-width:inherit}.scalar-app .max-w-xs{max-width:320px}.scalar-app .min-w-6{min-width:24px}.scalar-app .min-w-40{min-width:160px}.scalar-app .min-w-min{min-width:min-content}.scalar-app .flex-shrink,.scalar-app .shrink{flex-shrink:1}.scalar-app .-translate-x-full{--tw-translate-x:-100%;translate:var(--tw-translate-x)var(--tw-translate-y)}.scalar-app .translate-x-2\\.5{--tw-translate-x:10px;translate:var(--tw-translate-x)var(--tw-translate-y)}.scalar-app .translate-x-\\[14px\\]{--tw-translate-x:14px;translate:var(--tw-translate-x)var(--tw-translate-y)}.scalar-app .translate-x-full{--tw-translate-x:100%;translate:var(--tw-translate-x)var(--tw-translate-y)}.scalar-app .-translate-y-1\\.5{--tw-translate-y:-6px;translate:var(--tw-translate-x)var(--tw-translate-y)}.scalar-app .-translate-y-1\\/2{--tw-translate-y:-50%;translate:var(--tw-translate-x)var(--tw-translate-y)}.scalar-app .translate-y-0{--tw-translate-y:0px;translate:var(--tw-translate-x)var(--tw-translate-y)}.scalar-app .translate-y-1\\.5{--tw-translate-y:6px;translate:var(--tw-translate-x)var(--tw-translate-y)}.scalar-app .scale-0{--tw-scale-x:0%;--tw-scale-y:0%;--tw-scale-z:0%;scale:var(--tw-scale-x)var(--tw-scale-y)}.scalar-app .scale-100{--tw-scale-x:100%;--tw-scale-y:100%;--tw-scale-z:100%;scale:var(--tw-scale-x)var(--tw-scale-y)}.scalar-app .appearance-none{appearance:none}.scalar-app .grid-flow-col{grid-auto-flow:column}.scalar-app .\\!items-end{align-items:flex-end!important}.scalar-app .\\!items-start{align-items:flex-start!important}.scalar-app .items-baseline{align-items:baseline}.scalar-app .\\!justify-end{justify-content:flex-end!important}.scalar-app .\\!justify-start{justify-content:flex-start!important}.scalar-app .gap-2\\.25{gap:9px}.scalar-app .gap-x-4{column-gap:16px}.scalar-app .gap-y-8{row-gap:32px}:where(.scalar-app .divide-x>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(var(--scalar-border-width)*var(--tw-divide-x-reverse));border-inline-end-width:calc(var(--scalar-border-width)*calc(1 - var(--tw-divide-x-reverse)))}.scalar-app .self-end{align-self:flex-end}.scalar-app .overflow-x-clip{overflow-x:clip}.scalar-app .overscroll-contain{overscroll-behavior:contain}.scalar-app .rounded-\\[inherit\\]{border-radius:inherit}.scalar-app .rounded-l-none{border-top-left-radius:0;border-bottom-left-radius:0}.scalar-app .border-1{border-style:var(--tw-border-style);border-width:1px}.scalar-app .border-solid{--tw-border-style:solid;border-style:solid}.scalar-app .border-\\(--scalar-background-3\\){border-color:var(--scalar-background-3)}.scalar-app .border-border{border-color:var(--scalar-border-color)}.scalar-app .border-c-alert{border-color:var(--scalar-color-alert)}.scalar-app .border-red{border-color:var(--scalar-color-red)}.scalar-app .border-sidebar-border{border-color:var(--scalar-sidebar-border-color,var(--scalar-border-color))}.scalar-app .border-sidebar-border-search{border-color:var(--scalar-sidebar-search-border-color,var(--scalar-border-color))}.scalar-app .bg-\\(--bg-light\\){background-color:var(--bg-light)}.scalar-app .bg-b-1,.scalar-app .bg-b-1\\.5{background-color:var(--scalar-background-1)}@supports (color:color-mix(in lab,red,red)){.scalar-app .bg-b-1\\.5{background-color:var(--scalar-background-1)}@supports (color:color-mix(in lab,red,red)){.scalar-app .bg-b-1\\.5{background-color:var(--scalar-background-1)}@supports (color:color-mix(in lab,red,red)){.scalar-app .bg-b-1\\.5{background-color:var(--scalar-background-1)}@supports (color:color-mix(in lab,red,red)){.scalar-app .bg-b-1\\.5{background-color:color-mix(in srgb,var(--scalar-background-1),var(--scalar-background-2))}}}}}.scalar-app .bg-b-alert{background-color:var(--scalar-background-alert)}.scalar-app .bg-b-btn{background-color:var(--scalar-button-1)}.scalar-app .bg-b-tooltip{background-color:var(--scalar-tooltip-background)}.scalar-app .bg-backdrop{background-color:#00000038}.scalar-app .bg-border{background-color:var(--scalar-border-color)}.scalar-app .bg-c-danger{background-color:var(--scalar-color-danger)}.scalar-app .bg-inherit{background-color:inherit}.scalar-app .bg-red{background-color:var(--scalar-color-red)}.scalar-app .bg-sidebar-b-search{background-color:var(--scalar-sidebar-search-background,var(--scalar-background-2))}.scalar-app .bg-sidebar-indent-border{background-color:var(--scalar-sidebar-indent-border,var(--scalar-border-color))}.scalar-app .bg-sidebar-indent-border-active{background-color:var(--scalar-sidebar-indent-border-active,var(--scalar-color-accent))}.scalar-app .bg-transparent{background-color:#0000}.scalar-app .bg-linear-to-b{--tw-gradient-position:to bottom}@supports (background-image:linear-gradient(in lab,red,red)){.scalar-app .bg-linear-to-b{--tw-gradient-position:to bottom in oklab}}.scalar-app .bg-linear-to-b{background-image:linear-gradient(var(--tw-gradient-stops))}.scalar-app .from-b-1{--tw-gradient-from:var(--scalar-background-1);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.scalar-app .to-b-1\\.5{--tw-gradient-to:var(--scalar-background-1)}@supports (color:color-mix(in lab,red,red)){.scalar-app .to-b-1\\.5{--tw-gradient-to:var(--scalar-background-1)}@supports (color:color-mix(in lab,red,red)){.scalar-app .to-b-1\\.5{--tw-gradient-to:var(--scalar-background-1)}@supports (color:color-mix(in lab,red,red)){.scalar-app .to-b-1\\.5{--tw-gradient-to:var(--scalar-background-1)}@supports (color:color-mix(in lab,red,red)){.scalar-app .to-b-1\\.5{--tw-gradient-to:color-mix(in srgb,var(--scalar-background-1),var(--scalar-background-2))}}}}}.scalar-app .to-b-1\\.5{--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.scalar-app .to-b-2{--tw-gradient-to:var(--scalar-background-2);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.scalar-app .mask-y-from-\\[calc\\(100\\%-8px\\)\\]{-webkit-mask-image:var(--tw-mask-linear),var(--tw-mask-radial),var(--tw-mask-conic);mask-image:var(--tw-mask-linear),var(--tw-mask-radial),var(--tw-mask-conic);--tw-mask-linear:var(--tw-mask-left),var(--tw-mask-right),var(--tw-mask-bottom),var(--tw-mask-top);--tw-mask-top:linear-gradient(to top,var(--tw-mask-top-from-color)var(--tw-mask-top-from-position),var(--tw-mask-top-to-color)var(--tw-mask-top-to-position));--tw-mask-top-from-position: calc(100% - 8px) ;--tw-mask-bottom:linear-gradient(to bottom,var(--tw-mask-bottom-from-color)var(--tw-mask-bottom-from-position),var(--tw-mask-bottom-to-color)var(--tw-mask-bottom-to-position));--tw-mask-bottom-from-position: calc(100% - 8px) ;-webkit-mask-composite:source-in;mask-composite:intersect}.scalar-app .mask-y-to-100\\%{-webkit-mask-image:var(--tw-mask-linear),var(--tw-mask-radial),var(--tw-mask-conic);mask-image:var(--tw-mask-linear),var(--tw-mask-radial),var(--tw-mask-conic);--tw-mask-linear:var(--tw-mask-left),var(--tw-mask-right),var(--tw-mask-bottom),var(--tw-mask-top);--tw-mask-top:linear-gradient(to top,var(--tw-mask-top-from-color)var(--tw-mask-top-from-position),var(--tw-mask-top-to-color)var(--tw-mask-top-to-position));--tw-mask-top-to-position:100%;--tw-mask-bottom:linear-gradient(to bottom,var(--tw-mask-bottom-from-color)var(--tw-mask-bottom-from-position),var(--tw-mask-bottom-to-color)var(--tw-mask-bottom-to-position));--tw-mask-bottom-to-position:100%;-webkit-mask-composite:source-in;mask-composite:intersect}.scalar-app .mask-repeat{-webkit-mask-repeat:repeat;mask-repeat:repeat}.scalar-app .p-0\\.25{padding:1px}.scalar-app .p-2\\.5{padding:10px}.scalar-app .p-6{padding:24px}.scalar-app .px-3\\.5{padding-inline:14px}.scalar-app .px-9{padding-inline:36px}.scalar-app .py-4{padding-block:16px}.scalar-app .py-\\[6\\.75px\\]{padding-block:6.75px}.scalar-app .pr-\\[100\\%\\]{padding-right:100%}.scalar-app .pl-8{padding-left:32px}.scalar-app .pl-\\[100\\%\\]{padding-left:100%}.scalar-app .text-base\\/5{font-size:var(--scalar-font-size-3);line-height:var(--scalar-line-height-5)}.scalar-app .text-sm\\/5{font-size:var(--scalar-font-size-4);line-height:var(--scalar-line-height-5)}.scalar-app .text-lg{font-size:var(--scalar-font-size-2)}.scalar-app .leading-5{--tw-leading:var(--scalar-line-height-5);line-height:var(--scalar-line-height-5)}.scalar-app .font-sidebar{--tw-font-weight:var(--scalar-sidebar-font-weight,var(--scalar-regular));font-weight:var(--scalar-sidebar-font-weight,var(--scalar-regular))}.scalar-app .font-sidebar-active{--tw-font-weight:var(--scalar-sidebar-font-weight-active,var(--scalar-semibold));font-weight:var(--scalar-sidebar-font-weight-active,var(--scalar-semibold))}.scalar-app .break-words,.scalar-app .wrap-break-word{overflow-wrap:break-word}.scalar-app .text-c-alert{color:var(--scalar-color-alert)}.scalar-app .text-c-tooltip{color:var(--scalar-tooltip-color)}.scalar-app .text-sidebar-c-1{color:var(--scalar-sidebar-color-1,var(--scalar-color-1))}.scalar-app .text-sidebar-c-search{color:var(--scalar-sidebar-search-color,var(--scalar-color-3))}.scalar-app .opacity-40{opacity:.4}.scalar-app .-outline-offset-2{outline-offset:-2px}.scalar-app .outline-offset-1{outline-offset:1px}.scalar-app .outline-offset-\\[-1px\\]{outline-offset:-1px}.scalar-app .backdrop-blur{--tw-backdrop-blur:blur(8px);-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.scalar-app .ease-in{--tw-ease:var(--ease-in);transition-timing-function:var(--ease-in)}.scalar-app .ease-out{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}:is(.scalar-app .\\*\\:size-3>*){width:12px;height:12px}:is(.scalar-app .\\*\\:size-4>*){width:16px;height:16px}:is(.scalar-app .\\*\\:h-5>*){height:20px}:is(.scalar-app .\\*\\:min-w-5>*){min-width:20px}:is(.scalar-app .\\*\\:flex-1>*){flex:1}:is(.scalar-app .\\*\\:justify-center>*){justify-content:center}:is(.scalar-app .\\*\\:gap-px>*){gap:1px}:is(.scalar-app .\\*\\:rounded>*){border-radius:var(--scalar-radius)}:is(.scalar-app .\\*\\:border>*){border-style:var(--tw-border-style);border-width:var(--scalar-border-width)}:is(.scalar-app .\\*\\:border-border-tooltip>*){border-color:var(--scalar-tooltip-color)}@supports (color:color-mix(in lab,red,red)){:is(.scalar-app .\\*\\:border-border-tooltip>*){border-color:var(--scalar-tooltip-color)}@supports (color:color-mix(in lab,red,red)){:is(.scalar-app .\\*\\:border-border-tooltip>*){border-color:var(--scalar-tooltip-color)}@supports (color:color-mix(in lab,red,red)){:is(.scalar-app .\\*\\:border-border-tooltip>*){border-color:var(--scalar-tooltip-color)}@supports (color:color-mix(in lab,red,red)){:is(.scalar-app .\\*\\:border-border-tooltip>*){border-color:color-mix(in srgb,var(--scalar-tooltip-color),var(--scalar-tooltip-background))}}}}}:is(.scalar-app .\\*\\:px-1>*){padding-inline:4px}:is(.scalar-app .\\*\\:text-xs>*){font-size:var(--scalar-font-size-5)}@media(hover:hover){.scalar-app .group-hover\\:text-c-1:is(:where(.group):hover *){color:var(--scalar-color-1)}.scalar-app .group-hover\\/button\\:bg-sidebar-indent-border-hover:is(:where(.group\\/button):hover *){background-color:var(--scalar-sidebar-indent-border-hover,var(--scalar-border-color))}.scalar-app .group-hover\\/button\\:text-c-1:is(:where(.group\\/button):hover *){color:var(--scalar-color-1)}}.scalar-app .group-focus-visible\\/toggle\\:outline:is(:where(.group\\/toggle):focus-visible *){outline-style:var(--tw-outline-style);outline-width:1px}.scalar-app .group-hocus\\/copy-button\\:sr-only:is(:is(:where(.group\\/copy-button):hover,:where(.group\\/copy-button):focus-visible) *){clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.scalar-app .group-hocus\\/copy-button\\:not-sr-only:is(:is(:where(.group\\/copy-button):hover,:where(.group\\/copy-button):focus-visible) *){clip-path:none;white-space:normal;width:auto;height:auto;margin:0;padding:0;position:static;overflow:visible}.scalar-app .group-hocus\\/copy-button\\:block:is(:is(:where(.group\\/copy-button):hover,:where(.group\\/copy-button):focus-visible) *){display:block}.scalar-app .group-hocus-within\\/code-block\\:-left-0\\.5:is(:is(:where(.group\\/code-block):hover,:where(.group\\/code-block):focus-within) *){left:-2px}.scalar-app .group-hocus-within\\/code-block\\:inline:is(:is(:where(.group\\/code-block):hover,:where(.group\\/code-block):focus-within) *){display:inline}.scalar-app .group-hocus-within\\/code-block\\:opacity-100:is(:is(:where(.group\\/code-block):hover,:where(.group\\/code-block):focus-within) *){opacity:1}.scalar-app .placeholder\\:font-\\[inherit\\]::placeholder{font-family:inherit}.scalar-app .first\\:rounded-t-\\[inherit\\]:first-child,:is(.scalar-app .\\*\\:first\\:rounded-t-\\[inherit\\]>*):first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.scalar-app .last\\:rounded-b-\\[inherit\\]:last-child,:is(.scalar-app .\\*\\:last\\:rounded-b-\\[inherit\\]>*):last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.scalar-app .focus-within\\:outline-none:focus-within{--tw-outline-style:none;outline-style:none}@media(hover:hover){.scalar-app .hover\\:bg-b-2:hover{background-color:var(--scalar-background-2)}.scalar-app .hover\\:bg-b-3:hover{background-color:var(--scalar-background-3)}.scalar-app .hover\\:bg-h-btn:hover{background-color:var(--scalar-button-1-hover)}.scalar-app .hover\\:bg-sidebar-b-1:hover{background-color:var(--scalar-sidebar-background-1,var(--scalar-background-1))}.scalar-app .hover\\:bg-sidebar-b-hover:hover{background-color:var(--scalar-sidebar-item-hover-background,var(--scalar-background-2))}.scalar-app .hover\\:bg-linear-to-b:hover{--tw-gradient-position:to bottom}@supports (background-image:linear-gradient(in lab,red,red)){.scalar-app .hover\\:bg-linear-to-b:hover{--tw-gradient-position:to bottom in oklab}}.scalar-app .hover\\:bg-linear-to-b:hover{background-image:linear-gradient(var(--tw-gradient-stops))}.scalar-app .hover\\:bg-linear-to-t:hover{--tw-gradient-position:to top}@supports (background-image:linear-gradient(in lab,red,red)){.scalar-app .hover\\:bg-linear-to-t:hover{--tw-gradient-position:to top in oklab}}.scalar-app .hover\\:bg-linear-to-t:hover{background-image:linear-gradient(var(--tw-gradient-stops))}.scalar-app .hover\\:text-c-1:hover{color:var(--scalar-color-1)}.scalar-app .hover\\:text-sidebar-c-1:hover{color:var(--scalar-sidebar-color-1,var(--scalar-color-1))}.scalar-app .hover\\:text-sidebar-c-hover:hover{color:var(--scalar-sidebar-item-hover-color,var(--scalar-sidebar-color-2))}.scalar-app .hover\\:underline:hover{text-decoration-line:underline}.scalar-app .hover\\:brightness-90:hover{--tw-brightness:brightness(90%);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}}.scalar-app .focus-visible\\:border-c-btn:focus-visible{border-color:var(--scalar-button-1-color)}.scalar-app .focus-visible\\:outline:focus-visible{outline-style:var(--tw-outline-style);outline-width:1px}.scalar-app .active\\:bg-b-btn:active{background-color:var(--scalar-button-1)}.scalar-app .active\\:brightness-90:active{--tw-brightness:brightness(90%);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.scalar-app .has-\\[\\:focus-visible\\]\\:bg-sidebar-b-1:has(:focus-visible){background-color:var(--scalar-sidebar-background-1,var(--scalar-background-1))}.scalar-app .has-\\[\\:focus-visible\\]\\:outline:has(:focus-visible),.scalar-app .has-\\[input\\:focus-visible\\]\\:outline:has(:is(input:focus-visible)){outline-style:var(--tw-outline-style);outline-width:1px}@media(min-width:800px){.scalar-app .md\\:w-\\[calc\\(100vw-16px\\)\\]{width:calc(100vw - 16px)}}@media(min-width:1000px){.scalar-app .lg\\:w-\\[calc\\(100vw-32px\\)\\]{width:calc(100vw - 32px)}.scalar-app .lg\\:w-full{width:100%}}.scalar-app .dark\\:bg-\\(--bg-dark\\):where(.dark-mode,.dark-mode *){background-color:var(--bg-dark)}.scalar-app .dark\\:bg-b-3:where(.dark-mode,.dark-mode *){background-color:var(--scalar-background-3)}.scalar-app .dark\\:bg-backdrop-dark:where(.dark-mode,.dark-mode *){background-color:#00000073}.scalar-app .dark\\:bg-linear-to-t:where(.dark-mode,.dark-mode *){--tw-gradient-position:to top}@supports (background-image:linear-gradient(in lab,red,red)){.scalar-app .dark\\:bg-linear-to-t:where(.dark-mode,.dark-mode *){--tw-gradient-position:to top in oklab}}.scalar-app .dark\\:bg-linear-to-t:where(.dark-mode,.dark-mode *){background-image:linear-gradient(var(--tw-gradient-stops))}@media(hover:hover){.scalar-app .dark\\:hover\\:bg-b-3:where(.dark-mode,.dark-mode *):hover{background-color:var(--scalar-background-3)}.scalar-app .dark\\:hover\\:bg-linear-to-b:where(.dark-mode,.dark-mode *):hover{--tw-gradient-position:to bottom}@supports (background-image:linear-gradient(in lab,red,red)){.scalar-app .dark\\:hover\\:bg-linear-to-b:where(.dark-mode,.dark-mode *):hover{--tw-gradient-position:to bottom in oklab}}.scalar-app .dark\\:hover\\:bg-linear-to-b:where(.dark-mode,.dark-mode *):hover{background-image:linear-gradient(var(--tw-gradient-stops))}.scalar-app .dark\\:hover\\:bg-linear-to-t:where(.dark-mode,.dark-mode *):hover{--tw-gradient-position:to top}@supports (background-image:linear-gradient(in lab,red,red)){.scalar-app .dark\\:hover\\:bg-linear-to-t:where(.dark-mode,.dark-mode *):hover{--tw-gradient-position:to top in oklab}}.scalar-app .dark\\:hover\\:bg-linear-to-t:where(.dark-mode,.dark-mode *):hover{background-image:linear-gradient(var(--tw-gradient-stops))}}@media(max-width:720px)and (max-height:480px){.scalar-app .zoomed\\:\\!whitespace-normal{white-space:normal!important}}[data-radix-popper-content-wrapper]{z-index:1!important}.loader-wrapper[data-v-27df5cd8]{--loader-size:50%;justify-content:center;align-items:center;display:flex;position:relative}.svg-loader[data-v-27df5cd8]{width:var(--loader-size);height:var(--loader-size);fill:none;stroke:currentColor;background-color:#0000;top:1rem;right:.9rem;overflow:visible}.svg-path[data-v-27df5cd8]{stroke-width:12px;fill:none;transition:all .3s}.svg-x-mark[data-v-27df5cd8]{stroke-dasharray:57;stroke-dashoffset:57px;transition-delay:0s}.svg-check-mark[data-v-27df5cd8]{stroke-dasharray:149;stroke-dashoffset:149px;transition-delay:0s}.icon-is-invalid .svg-x-mark[data-v-27df5cd8],.icon-is-valid .svg-check-mark[data-v-27df5cd8]{stroke-dashoffset:0;transition-delay:.3s}.circular-loader[data-v-27df5cd8]{transform-origin:50%;background:0 0;animation:.7s linear infinite rotate-27df5cd8,.4s fade-in-27df5cd8;transform:scale(3.5)}.loader-path[data-v-27df5cd8]{stroke-dasharray:50 200;stroke-dashoffset:-100px;stroke-linecap:round}.loader-path-off[data-v-27df5cd8]{stroke-dasharray:50 200;stroke-dashoffset:-100px;opacity:0;transition:opacity .3s}.scalar-code-block.bg-b-1 .scalar-code-copy-backdrop{background-color:var(--scalar-background-1)}.scalar-code-block.bg-b-2 .scalar-code-copy-backdrop{background-color:var(--scalar-background-2)}.scalar-code-block.bg-b-2 .scalar-code-copy{background-color:var(--scalar-background-3)}.toggle-icon-ellipse[data-v-60be8692]{background:var(--scalar-background-1);border-radius:50%;width:7px;height:7px;transition:width .3s ease-in-out,height .3s ease-in-out;display:inline-block;position:relative;overflow:hidden;box-shadow:inset 0 0 0 1px}.toggle-icon-moon-mask[data-v-60be8692]{background:var(--scalar-background-1);border:1px solid;border-radius:50%;width:100%;height:100%;transition:transform .3s ease-in-out;display:block;position:absolute;bottom:2.5px;left:2.5px;transform:translate(4px,-4px)}.toggle-icon-sun-ray[data-v-60be8692]{background:currentColor;border-radius:8px;width:12px;height:1px;transition:transform .3s ease-in-out;position:absolute}.toggle-icon-sun-ray[data-v-60be8692]:nth-of-type(2){transform:rotate(90deg)}.toggle-icon-sun-ray[data-v-60be8692]:nth-of-type(3){transform:rotate(45deg)}.toggle-icon-sun-ray[data-v-60be8692]:nth-of-type(4){transform:rotate(-45deg)}.toggle-icon-dark .toggle-icon-ellipse[data-v-60be8692]{width:10px;height:10px;-webkit-mask-image:radial-gradient(circle at 0 100%,pink 10px,#0000 12px);mask-image:radial-gradient(circle at 0 100%,pink 10px,#0000 12px)}.toggle-icon-dark .toggle-icon-sun-ray[data-v-60be8692]{transform:scale(0)}.toggle-icon-dark .toggle-icon-moon-mask[data-v-60be8692]{transform:translateZ(0)}.scalar-icon[data-v-b651bb23],.scalar-icon[data-v-b651bb23] *{stroke-width:var(--c07589c2)}.scalar-app :where(code.hljs) *{font-size:inherit;font-family:var(--scalar-font-code);text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;tab-size:4;line-height:1.4}.scalar-app code.hljs{all:unset;font-size:inherit;color:var(--scalar-color-2);font-family:var(--scalar-font-code);counter-reset:linenumber}.scalar-app .hljs{color:var(--scalar-color-2);background:0 0}.scalar-app .hljs .line:before{color:var(--scalar-color-3);counter-increment:linenumber;content:counter(linenumber);min-width:calc(var(--line-digits)*1ch);text-align:right;margin-right:.875rem;display:inline-block}.scalar-app .hljs-comment,.scalar-app .hljs-quote{color:var(--scalar-color-3);font-style:italic}.scalar-app .hljs-number{color:var(--scalar-color-orange)}.scalar-app .hljs-regexp,.scalar-app .hljs-string,.scalar-app .hljs-built_in{color:var(--scalar-color-blue)}.scalar-app .hljs-title.class_{color:var(--scalar-color-1)}.scalar-app .hljs-keyword{color:var(--scalar-color-purple)}.scalar-app .hljs-title.function_{color:var(--scalar-color-orange)}.scalar-app .hljs-subst,.scalar-app .hljs-name{color:var(--scalar-color-blue)}.scalar-app .hljs-attr,.scalar-app .hljs-attribute{color:var(--scalar-color-1)}.scalar-app .hljs-addition,.scalar-app .hljs-literal,.scalar-app .hljs-selector-tag,.scalar-app .hljs-type{color:var(--scalar-color-green)}.scalar-app .hljs-selector-attr,.scalar-app .hljs-selector-pseudo{color:var(--scalar-color-orange)}.scalar-app .hljs-doctag,.scalar-app .hljs-section,.scalar-app .hljs-title{color:var(--scalar-color-blue)}.scalar-app .hljs-selector-id,.scalar-app .hljs-template-variable,.scalar-app .hljs-variable{color:var(--scalar-color-1)}.scalar-app .hljs-name,.scalar-app .hljs-section,.scalar-app .hljs-strong{font-weight:var(--scalar-semibold)}.scalar-app .hljs-bullet,.scalar-app .hljs-link,.scalar-app .hljs-meta,.scalar-app .hljs-symbol{color:var(--scalar-color-blue)}.scalar-app .hljs-deletion{color:var(--scalar-color-red)}.scalar-app .hljs-formula{background:var(--scalar-color-1)}.scalar-app .hljs-emphasis{font-style:italic}.scalar-app .credential .credential-value{color:#0000;font-size:0}.scalar-app .credential:after{content:"·····";color:var(--scalar-color-3);-webkit-user-select:none;user-select:none}.hljs.language-html{color:var(--scalar-color-1)}.hljs.language-html .hljs-attr{color:var(--scalar-color-2)}.hljs.language-curl .hljs-string{color:var(--scalar-color-blue)}.hljs.language-curl .hljs-literal{color:var(--scalar-color-1)}.hljs.language-php .hljs-variable{color:var(--scalar-color-blue)}.hljs.language-objectivec .hljs-meta{color:var(--scalar-color-1)}.hljs.language-objectivec .hljs-built_in,.hljs-built_in{color:var(--scalar-color-orange)}.scalar-app .markdown{--scalar-refs-heading-spacing:24px;--markdown-border:var(--scalar-border-width)solid var(--scalar-border-color);--markdown-spacing-sm:12px;--markdown-spacing-md:16px;--markdown-line-height:1.625;--markdown-heading-line-height:1.15;font-family:var(--scalar-font);word-break:break-word;line-height:var(--markdown-line-height)}.scalar-app .markdown>*{margin-bottom:var(--markdown-spacing-md)}.scalar-app .markdown>:not(h1):not(h2):not(h3):not(h4):not(h5):not(h6):last-child{margin-bottom:0}.scalar-app .markdown h1,.scalar-app .markdown h2,.scalar-app .markdown h3,.scalar-app .markdown h4,.scalar-app .markdown h5,.scalar-app .markdown h6{font-weight:var(--scalar-bold);margin-top:var(--scalar-refs-heading-spacing);margin-bottom:var(--markdown-spacing-sm);line-height:var(--markdown-heading-line-height,1.15);scroll-margin-top:1rem;display:block}.scalar-app .markdown h1{font-size:1.5rem}.scalar-app .markdown h2,.scalar-app .markdown h3{font-size:1.25rem}.scalar-app .markdown h4,.scalar-app .markdown h5,.scalar-app .markdown h6{font-size:1rem}.scalar-app .markdown b,.scalar-app .markdown strong{font-weight:var(--scalar-bold)}.scalar-app .markdown p{color:inherit;line-height:var(--markdown-line-height);display:block}.scalar-app .markdown img{border-radius:var(--scalar-radius);max-width:100%;display:inline-block;overflow:hidden}.scalar-app .markdown ul,.scalar-app .markdown ol{line-height:var(--markdown-line-height);flex-direction:column;gap:2px;padding-left:1.6em;display:flex}.scalar-app .markdown li{margin-top:2px;padding-left:7px}.scalar-app ol>li::marker{font:var(--scalar-font);font-variant-numeric:tabular-nums;font-weight:var(--scalar-semibold);white-space:nowrap}.scalar-app ol>*>li::marker{font:var(--scalar-font);font-variant-numeric:tabular-nums;font-weight:var(--scalar-semibold);white-space:nowrap}.scalar-app .markdown ol{list-style-type:decimal}.scalar-app .markdown ol ol{list-style-type:lower-alpha}.scalar-app .markdown ol ol ol ol,.scalar-app .markdown ol ol ol ol ol ol ol{list-style-type:decimal}.scalar-app .markdown ol ol ol ol ol,.scalar-app .markdown ol ol ol ol ol ol ol ol{list-style-type:lower-alpha}.scalar-app .markdown ol ol ol,.scalar-app .markdown ol ol ol ol ol ol,.scalar-app .markdown ol ol ol ol ol ol ol ol ol{list-style-type:lower-roman}.scalar-app .markdown ul>li,.scalar-app .markdown ul>*>li{list-style-type:disc}.scalar-app .markdown table{table-layout:fixed;border:var(--scalar-border-width)solid var(--scalar-border-color);border-radius:var(--scalar-radius);border-spacing:0;width:100%;margin:1em 0;display:table;position:relative;overflow-x:auto}.scalar-app .markdown tbody,.scalar-app .markdown thead{vertical-align:middle}.scalar-app .markdown tbody{display:table-row-group}.scalar-app .markdown thead{display:table-header-group}.scalar-app .markdown tr{border-color:inherit;vertical-align:inherit;display:table-row}.scalar-app .markdown td,.scalar-app .markdown th{vertical-align:top;min-width:1em;line-height:var(--markdown-line-height);word-break:break-word;font-size:var(--scalar-small);color:var(--scalar-color-1);border-right:var(--markdown-border);border-bottom:var(--markdown-border);padding:8.5px 16px;display:table-cell;position:relative}.scalar-app .markdown td>*,.scalar-app .markdown th>*{margin-bottom:0}.scalar-app .markdown th:empty{display:none}.scalar-app .markdown td:first-of-type,.scalar-app .markdown th:first-of-type{border-left:none}.scalar-app .markdown td:last-of-type,.scalar-app .markdown th:last-of-type{border-right:none}.scalar-app .markdown tr:last-of-type td{border-bottom:none}.scalar-app .markdown th{font-weight:var(--scalar-bold);text-align:left;background:var(--scalar-background-2);border-left-color:#0000}.scalar-app .markdown th:first-of-type{border-top-left-radius:var(--scalar-radius)}.scalar-app .markdown th:last-of-type{border-top-right-radius:var(--scalar-radius)}.scalar-app .markdown tr>[align=left]{text-align:left}.scalar-app .markdown tr>[align=right]{text-align:right}.scalar-app .markdown tr>[align=center]{text-align:center}.scalar-app .markdown details{border:var(--markdown-border);border-radius:var(--scalar-radius-xl);color:var(--scalar-color-1)}.scalar-app .markdown details>:not(summary){margin:var(--markdown-spacing-md);margin-bottom:0}.scalar-app .markdown details>p:has(>strong):not(:has(:not(strong))){margin-bottom:8px}.scalar-app .markdown details>p:has(>strong):not(:has(:not(strong)))+*{margin-top:0}.scalar-app .markdown details>table{width:calc(100% - calc(var(--markdown-spacing-md)*2))}.scalar-app .markdown summary{min-height:40px;font-weight:var(--scalar-semibold);line-height:var(--markdown-line-height);cursor:pointer;-webkit-user-select:none;user-select:none;border-radius:2.5px;align-items:flex-start;gap:8px;padding:7px 14px;display:flex;position:relative}.scalar-app .markdown summary:hover{background-color:var(--scalar-background-2)}.scalar-app .markdown details[open]{padding-bottom:var(--markdown-spacing-md)}.scalar-app .markdown details[open]>summary{border-bottom:var(--markdown-border);border-bottom-right-radius:0;border-bottom-left-radius:0}.scalar-app .markdown summary:before{content:"";width:var(--markdown-spacing-md);height:var(--markdown-spacing-md);background-color:var(--scalar-color-3);flex-shrink:0;margin-top:5px;display:block;-webkit-mask-image:url(\'data:image/svg+xml,\');mask-image:url(\'data:image/svg+xml,\')}.scalar-app .markdown summary:hover:before{background-color:var(--scalar-color-1)}.scalar-app .markdown details[open]>summary:before{transition:transform .1s ease-in-out;transform:rotate(90deg)}.scalar-app .markdown details:has(+details){border-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0;margin-bottom:0}.scalar-app .markdown details:has(+details)+details,.scalar-app .markdown details:has(+details)+details>summary{border-top-left-radius:0;border-top-right-radius:0}.scalar-app .markdown a{--font-color:var(--scalar-link-color,var(--scalar-color-accent));--font-visited:var(--scalar-link-color-visited,var(--scalar-color-2));-webkit-text-decoration:var(--scalar-text-decoration);text-decoration:var(--scalar-text-decoration);color:var(--font-color);font-weight:var(--scalar-link-font-weight,var(--scalar-semibold));text-underline-offset:.25rem;text-decoration-thickness:1px;-webkit-text-decoration-color:var(--font-color);text-decoration-color:var(--font-color)}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown a{-webkit-text-decoration-color:var(--font-color)}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown a{-webkit-text-decoration-color:var(--font-color)}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown a{-webkit-text-decoration-color:color-mix(in srgb,var(--font-color)30%,transparent)}}}.scalar-app .markdown a{-webkit-text-decoration-color:var(--font-color);text-decoration-color:var(--font-color)}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown a{-webkit-text-decoration-color:var(--font-color)}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown a{-webkit-text-decoration-color:color-mix(in srgb,var(--font-color)30%,transparent)}}.scalar-app .markdown a{-webkit-text-decoration-color:var(--font-color);text-decoration-color:var(--font-color)}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown a{-webkit-text-decoration-color:color-mix(in srgb,var(--font-color)30%,transparent);text-decoration-color:color-mix(in srgb,var(--font-color)30%,transparent)}}}}.scalar-app .markdown a:hover{-webkit-text-decoration-color:var(--scalar-color-1,currentColor);text-decoration-color:var(--scalar-color-1,currentColor);color:var(--scalar-link-color-hover,var(--scalar-color-accent));-webkit-text-decoration:var(--scalar-text-decoration-hover);text-decoration:var(--scalar-text-decoration-hover)}.scalar-app .markdown a:visited{color:var(--font-visited)}.scalar-app .markdown em{font-style:italic}.scalar-app .markdown sup,.scalar-app .markdown sub{font-size:var(--scalar-micro);font-weight:450}.scalar-app .markdown sup{vertical-align:super}.scalar-app .markdown sub{vertical-align:sub}.scalar-app .markdown del{text-decoration:line-through}.scalar-app .markdown code{font-family:var(--scalar-font-code);background-color:var(--scalar-background-2);box-shadow:0 0 0 var(--scalar-border-width) var(--scalar-border-color);font-size:var(--scalar-micro);border-radius:2px;padding:0 3px}.scalar-app .markdown .hljs{font-size:var(--scalar-small)}.scalar-app .markdown pre code{white-space:pre;padding:var(--markdown-spacing-sm);margin:var(--markdown-spacing-sm)0;-webkit-overflow-scrolling:touch;min-width:100px;max-width:100%;line-height:1.5;display:block;overflow-x:auto}.scalar-app .markdown hr{border:none;border-bottom:var(--markdown-border)}.scalar-app .markdown blockquote{border-left:1px solid var(--scalar-color-1);padding-left:var(--markdown-spacing-md);font-weight:var(--scalar-bold);font-size:var(--scalar-font-size-2);margin:0;display:block}.scalar-app .markdown li.task-list-item{list-style:none;position:relative}.scalar-app .markdown li.task-list-item>input{appearance:none;width:var(--markdown-spacing-md);height:var(--markdown-spacing-md);border:1px solid var(--scalar-color-3);border-radius:var(--scalar-radius);display:inline;position:absolute;top:.225em;left:-1.4em}.scalar-app .markdown li.task-list-item>input[type=checkbox]:checked{background-color:var(--scalar-color-1);border-color:var(--scalar-color-1)}.scalar-app .markdown li.task-list-item>input[type=checkbox]:before{content:"";border:solid var(--scalar-background-1);opacity:0;border-width:0 1.5px 1.5px 0;width:5px;height:10px;position:absolute;top:1px;left:5px;transform:rotate(45deg)}.scalar-app .markdown li.task-list-item>input[type=checkbox]:checked:before{opacity:1}.scalar-app .markdown .markdown-alert{border-radius:var(--scalar-radius);background-color:var(--scalar-background-2);align-items:stretch}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown .markdown-alert{background-color:var(--scalar-background-2)}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown .markdown-alert{background-color:var(--scalar-background-2)}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown .markdown-alert{background-color:color-mix(in srgb,var(--scalar-background-2),transparent)}}}}.scalar-app .markdown .markdown-alert{border:var(--markdown-border);gap:var(--markdown-spacing-sm);padding:10px 14px;display:flex;position:relative}.scalar-app .markdown .markdown-alert .markdown-alert-icon:before{content:"";background-color:currentColor;flex-shrink:0;width:18px;height:18px;margin-top:3px;display:block;-webkit-mask-position:50%;mask-position:50%;-webkit-mask-size:contain;mask-size:contain;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat}.scalar-app .markdown .markdown-alert.markdown-alert-note{background-color:var(--scalar-color-blue)}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown .markdown-alert.markdown-alert-note{background-color:var(--scalar-color-blue)}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown .markdown-alert.markdown-alert-note{background-color:var(--scalar-color-blue)}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown .markdown-alert.markdown-alert-note{background-color:color-mix(in srgb,var(--scalar-color-blue),transparent 97%)}}}}.scalar-app .markdown .markdown-alert.markdown-alert-note{border:var(--scalar-border-width)solid var(--scalar-color-blue)}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown .markdown-alert.markdown-alert-note{border:var(--scalar-border-width)solid var(--scalar-color-blue)}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown .markdown-alert.markdown-alert-note{border:var(--scalar-border-width)solid var(--scalar-color-blue)}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown .markdown-alert.markdown-alert-note{border:var(--scalar-border-width)solid color-mix(in srgb,var(--scalar-color-blue),transparent 50%)}}}}.scalar-app .markdown .markdown-alert.markdown-alert-tip{background-color:var(--scalar-color-2)}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown .markdown-alert.markdown-alert-tip{background-color:var(--scalar-color-2)}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown .markdown-alert.markdown-alert-tip{background-color:var(--scalar-color-2)}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown .markdown-alert.markdown-alert-tip{background-color:color-mix(in srgb,var(--scalar-color-2),transparent 97%)}}}}.scalar-app .markdown .markdown-alert.markdown-alert-tip{border:var(--scalar-border-width)solid var(--scalar-color-2)}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown .markdown-alert.markdown-alert-tip{border:var(--scalar-border-width)solid var(--scalar-color-2)}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown .markdown-alert.markdown-alert-tip{border:var(--scalar-border-width)solid var(--scalar-color-2)}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown .markdown-alert.markdown-alert-tip{border:var(--scalar-border-width)solid color-mix(in srgb,var(--scalar-color-2),transparent 50%)}}}}.scalar-app .markdown .markdown-alert.markdown-alert-note .markdown-alert-icon:before,.scalar-app .markdown .markdown-alert.markdown-alert-tip .markdown-alert-icon:before{color:var(--scalar-color-blue);-webkit-mask-image:url(\'data:image/svg+xml,\');mask-image:url(\'data:image/svg+xml,\')}.scalar-app .markdown .markdown-alert.markdown-alert-important,.scalar-app .markdown .markdown-alert.markdown-alert-warning{background-color:var(--scalar-color-orange)}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown .markdown-alert.markdown-alert-important,.scalar-app .markdown .markdown-alert.markdown-alert-warning{background-color:var(--scalar-color-orange)}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown .markdown-alert.markdown-alert-important,.scalar-app .markdown .markdown-alert.markdown-alert-warning{background-color:var(--scalar-color-orange)}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown .markdown-alert.markdown-alert-important,.scalar-app .markdown .markdown-alert.markdown-alert-warning{background-color:color-mix(in srgb,var(--scalar-color-orange),transparent 97%)}}}}.scalar-app .markdown .markdown-alert.markdown-alert-important,.scalar-app .markdown .markdown-alert.markdown-alert-warning{border:var(--scalar-border-width)solid var(--scalar-color-orange)}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown .markdown-alert.markdown-alert-important,.scalar-app .markdown .markdown-alert.markdown-alert-warning{border:var(--scalar-border-width)solid var(--scalar-color-orange)}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown .markdown-alert.markdown-alert-important,.scalar-app .markdown .markdown-alert.markdown-alert-warning{border:var(--scalar-border-width)solid var(--scalar-color-orange)}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown .markdown-alert.markdown-alert-important,.scalar-app .markdown .markdown-alert.markdown-alert-warning{border:var(--scalar-border-width)solid color-mix(in srgb,var(--scalar-color-orange),transparent 50%)}}}}.scalar-app .markdown .markdown-alert.markdown-alert-important .markdown-alert-icon:before,.scalar-app .markdown .markdown-alert.markdown-alert-warning .markdown-alert-icon:before{-webkit-mask-image:url(\'data:image/svg+xml,\');mask-image:url(\'data:image/svg+xml,\')}.scalar-app .markdown .markdown-alert.markdown-alert-caution{background-color:var(--scalar-color-red)}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown .markdown-alert.markdown-alert-caution{background-color:var(--scalar-color-red)}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown .markdown-alert.markdown-alert-caution{background-color:var(--scalar-color-red)}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown .markdown-alert.markdown-alert-caution{background-color:color-mix(in srgb,var(--scalar-color-red),transparent 97%)}}}}.scalar-app .markdown .markdown-alert.markdown-alert-caution{border:var(--scalar-border-width)solid var(--scalar-color-red)}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown .markdown-alert.markdown-alert-caution{border:var(--scalar-border-width)solid var(--scalar-color-red)}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown .markdown-alert.markdown-alert-caution{border:var(--scalar-border-width)solid var(--scalar-color-red)}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown .markdown-alert.markdown-alert-caution{border:var(--scalar-border-width)solid color-mix(in srgb,var(--scalar-color-red),transparent 50%)}}}}.scalar-app .markdown .markdown-alert.markdown-alert-caution .markdown-alert-icon:before{color:var(--scalar-color-red);-webkit-mask-image:url(\'data:image/svg+xml,\');mask-image:url(\'data:image/svg+xml,\')}.scalar-app .markdown .markdown-alert.markdown-alert-success{background-color:var(--scalar-color-green)}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown .markdown-alert.markdown-alert-success{background-color:var(--scalar-color-green)}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown .markdown-alert.markdown-alert-success{background-color:var(--scalar-color-green)}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown .markdown-alert.markdown-alert-success{background-color:color-mix(in srgb,var(--scalar-color-green),transparent 97%)}}}}.scalar-app .markdown .markdown-alert.markdown-alert-success{border:var(--scalar-border-width)solid var(--scalar-color-green)}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown .markdown-alert.markdown-alert-success{border:var(--scalar-border-width)solid var(--scalar-color-green)}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown .markdown-alert.markdown-alert-success{border:var(--scalar-border-width)solid var(--scalar-color-green)}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown .markdown-alert.markdown-alert-success{border:var(--scalar-border-width)solid color-mix(in srgb,var(--scalar-color-green),transparent 50%)}}}}.scalar-app .markdown .markdown-alert.markdown-alert-success .markdown-alert-icon:before{color:var(--scalar-color-green);-webkit-mask-image:url(\'data:image/svg+xml,\');mask-image:url(\'data:image/svg+xml,\')}.scalar-app .markdown .markdown-alert.markdown-alert-note .markdown-alert-icon:before{color:var(--scalar-color-blue)}.scalar-app .markdown .markdown-alert.markdown-alert-tip .markdown-alert-icon:before{color:var(--scalar-color-2)}.scalar-app .markdown .markdown-alert.markdown-alert-important .markdown-alert-icon:before{color:var(--scalar-color-purple)}.scalar-app .markdown .markdown-alert.markdown-alert-warning .markdown-alert-icon:before{color:var(--scalar-color-orange)}.scalar-app .markdown .markdown-alert .markdown-alert-content{line-height:var(--markdown-line-height);margin:0}.scalar-app .markdown.markdown-summary.markdown-summary :before,.scalar-app .markdown.markdown-summary.markdown-summary :after{content:none}.scalar-app .markdown.markdown-summary.markdown-summary :not(strong,em,a){font-size:inherit;font-weight:inherit;line-height:var(--markdown-line-height);display:contents}.scalar-app .markdown.markdown-summary.markdown-summary img,.scalar-app .markdown.markdown-summary.markdown-summary svg,.scalar-app .markdown.markdown-summary.markdown-summary hr,.scalar-app .markdown.markdown-summary.markdown-summary pre{display:none}.dark-mode .scalar-dropdown-item[data-v-6660bbc5]:hover{filter:brightness(1.1)}.group\\/item>*>.scalar-sidebar-indent .scalar-sidebar-indent-border[data-v-3e080c68]{inset-block:-1px}.group\\/item:first-child>*>.scalar-sidebar-indent .scalar-sidebar-indent-border[data-v-3e080c68]{top:0}.group\\/item:last-child>*>.scalar-sidebar-indent .scalar-sidebar-indent-border[data-v-3e080c68]{bottom:0}.group\\/items.-translate-x-full .group\\/button{transition-behavior:allow-discrete;max-height:0;transition-property:display,max-height;transition-duration:0s;transition-delay:.3s;display:none}.group\\/item.group\\/nested-items-open>*>.group\\/items.translate-x-0 .group\\/button{max-height:3.40282e38px;display:flex}.group\\/sidebar-section:first-of-type>.group\\/spacer-before,.group\\/sidebar-section:last-of-type>.group\\/spacer-after{height:0}.group\\/sidebar-section:has(+.group\\/sidebar-section)>.group\\/spacer-after{height:0;margin-bottom:-1px}:where(body)>.scalar-tooltip{--scalar-tooltip-padding:8px;padding:calc(var(--scalar-tooltip-padding) + var(--scalar-tooltip-offset));z-index:99999;max-width:320px;font-size:var(--scalar-font-size-5);--tw-leading:var(--scalar-line-height-5);line-height:var(--scalar-line-height-5);--tw-font-weight:var(--scalar-semibold);font-weight:var(--scalar-semibold);overflow-wrap:break-word;color:var(--scalar-tooltip-color)}:where(body)>.scalar-tooltip:before{content:"";inset:var(--scalar-tooltip-offset);z-index:-1;border-radius:var(--scalar-radius);background-color:var(--scalar-tooltip-background);--tw-backdrop-blur:blur(8px);-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);position:absolute}:where(body.dark-mode)>.scalar-tooltip:before{--tw-shadow:inset 0 0 0 var(--tw-shadow-color,calc(var(--scalar-border-width)*2))var(--scalar-border-color);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.dark-mode .scalar-dropdown-item[data-v-3402682d]:hover{filter:brightness(1.1)}.scalar-modal-layout[data-v-c36b47da]{animation:.3s ease-in-out forwards fadein-layout-c36b47da}.scalar-modal[data-v-c36b47da]{box-shadow:var(--scalar-shadow-2);animation:.3s ease-in-out .1s forwards fadein-modal-c36b47da;transform:translateY(10px)}.scalar-modal-layout-full[data-v-c36b47da]{opacity:1!important;background:0 0!important}.modal-content-search .modal-body[data-v-c36b47da]{flex-direction:column;max-height:440px;padding:0;display:flex;overflow:hidden}@media(max-width:720px)and (max-height:480px){.scalar-modal-layout .scalar-modal[data-v-c36b47da]{max-height:90svh;margin-top:5svh}}.full-size-styles[data-v-c36b47da]{margin:initial;border-right:var(--scalar-border-width)solid var(--scalar-border-color);animation:.3s ease-in-out forwards fadein-layout-c36b47da;left:0;transform:translate(0);background-color:var(--scalar-background-1)!important;max-height:100%!important;box-shadow:none!important;border-radius:0!important;position:absolute!important;top:0!important}@media(min-width:800px){.full-size-styles[data-v-c36b47da]{width:50dvw!important}}.full-size-styles[data-v-c36b47da]:after{content:"";width:50dvw;height:100dvh;position:absolute;top:0;right:-50dvw}.sidebar-heading-type[data-v-1857170e]{text-transform:uppercase;color:var(--method-color,var(--scalar-color-1));font-size:10px;line-height:14px;font-weight:var(--scalar-bold);font-family:var(--scalar-font-code);white-space:nowrap;flex-shrink:0;align-items:center;gap:4px;display:inline-flex;overflow:hidden}.scalar-app .pointer-events-auto{pointer-events:auto}.scalar-app .pointer-events-none{pointer-events:none}.scalar-app .collapse{visibility:collapse}.scalar-app .visible{visibility:visible}.scalar-app .floating-bg:before{background-color:var(--scalar-background-2);border-radius:var(--scalar-radius);content:"";opacity:0;z-index:1;width:calc(100% + 8px);height:calc(100% - 4px);transition:opacity .2s ease-in-out;position:absolute;top:2.5px;left:-4px}.scalar-app .floating-bg:hover:before{opacity:1}.scalar-app .centered{--tw-translate-y:-50%;--tw-translate-x:-50%;translate:var(--tw-translate-x)var(--tw-translate-y);position:absolute;top:50%;left:50%}.scalar-app .centered-y{--tw-translate-y:-50%;translate:var(--tw-translate-x)var(--tw-translate-y);position:absolute;top:50%}.scalar-app .centered-x{--tw-translate-x:-50%;translate:var(--tw-translate-x)var(--tw-translate-y);position:absolute;left:50%}.scalar-app .sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.scalar-app .absolute{position:absolute}.scalar-app .fixed{position:fixed}.scalar-app .relative{position:relative}.scalar-app .static{position:static}.scalar-app .sticky{position:sticky}.scalar-app .inset-0{inset:0}.scalar-app .inset-x-0{inset-inline:0}.scalar-app .inset-x-1{inset-inline:4px}.scalar-app .inset-x-px{inset-inline:1px}.scalar-app .-top-\\(--scalar-address-bar-height\\){top:calc(var(--scalar-address-bar-height)*-1)}.scalar-app .-top-1\\.25{top:-5px}.scalar-app .-top-\\[104px\\]{top:-104px}.scalar-app .top-0{top:0}.scalar-app .top-2{top:8px}.scalar-app .top-12{top:48px}.scalar-app .top-\\[calc\\(100\\%\\+4px\\)\\]{top:calc(100% + 4px)}.scalar-app .-right-1\\.25{right:-5px}.scalar-app .-right-\\[30px\\]{right:-30px}.scalar-app .right-0{right:0}.scalar-app .right-1{right:4px}.scalar-app .right-1\\.5{right:6px}.scalar-app .right-1\\/2{right:50%}.scalar-app .right-2{right:8px}.scalar-app .right-4{right:16px}.scalar-app .right-7{right:28px}.scalar-app .right-14{right:56px}.scalar-app .right-16{right:64px}.scalar-app .bottom-0{bottom:0}.scalar-app .bottom-1{bottom:4px}.scalar-app .bottom-1\\/2{bottom:50%}.scalar-app .bottom-\\[var\\(--scalar-border-width\\)\\]{bottom:var(--scalar-border-width)}.scalar-app .left-0{left:0}.scalar-app .left-1\\/2{left:50%}.scalar-app .left-3{left:12px}.scalar-app .-z-1{z-index:-1}.scalar-app .z-0{z-index:0}.scalar-app .z-1{z-index:1}.scalar-app .z-2{z-index:2}.scalar-app .z-10{z-index:10}.scalar-app .z-20{z-index:20}.scalar-app .z-50{z-index:50}.scalar-app .z-\\[1\\]{z-index:1}.scalar-app .z-\\[1002\\]{z-index:1002}.scalar-app .z-context{z-index:1000}.scalar-app .z-context-plus{z-index:1001}.scalar-app .z-overlay{z-index:10000}.scalar-app .order-last{order:9999}.scalar-app .col-span-full{grid-column:1/-1}.scalar-app .container{width:100%}@media(min-width:400px){.scalar-app .container{max-width:400px}}@media(min-width:600px){.scalar-app .container{max-width:600px}}@media(min-width:800px){.scalar-app .container{max-width:800px}}@media(min-width:1000px){.scalar-app .container{max-width:1000px}}@media(min-width:1200px){.scalar-app .container{max-width:1200px}}@media(min-width:96rem){.scalar-app .container{max-width:96rem}}.scalar-app .\\!m-0{margin:0!important}.scalar-app .-m-0\\.5{margin:-2px}.scalar-app .m-0{margin:0}.scalar-app .m-4{margin:16px}.scalar-app .m-auto{margin:auto}.scalar-app .m-header{margin:48px}.scalar-app .-mx-0\\.25{margin-inline:-1px}.scalar-app .mx-1{margin-inline:4px}.scalar-app .mx-auto{margin-inline:auto}.scalar-app .-my-0\\.5{margin-block:-2px}.scalar-app .-my-1{margin-block:-4px}.scalar-app .my-12{margin-block:48px}.scalar-app .-mt-\\[\\.5px\\]{margin-top:-.5px}.scalar-app .mt-0\\.25{margin-top:1px}.scalar-app .mt-1{margin-top:4px}.scalar-app .mt-1\\.5{margin-top:6px}.scalar-app .mt-2{margin-top:8px}.scalar-app .mt-3{margin-top:12px}.scalar-app .mt-5{margin-top:20px}.scalar-app .mt-10{margin-top:40px}.scalar-app .mt-\\[0\\.5px\\]{margin-top:.5px}.scalar-app .mt-auto{margin-top:auto}.scalar-app .\\!mr-0{margin-right:0!important}.scalar-app .-mr-0\\.5{margin-right:-2px}.scalar-app .-mr-1{margin-right:-4px}.scalar-app .-mr-1\\.5{margin-right:-6px}.scalar-app .-mr-3{margin-right:-12px}.scalar-app .mr-0\\.5{margin-right:2px}.scalar-app .mr-0\\.75{margin-right:3px}.scalar-app .mr-1{margin-right:4px}.scalar-app .mr-1\\.5{margin-right:6px}.scalar-app .mr-1\\.25{margin-right:5px}.scalar-app .mr-2{margin-right:8px}.scalar-app .mr-2\\.5{margin-right:10px}.scalar-app .mr-3{margin-right:12px}.scalar-app .mr-\\[6\\.25px\\]{margin-right:6.25px}.scalar-app .mr-auto{margin-right:auto}.scalar-app .\\!mb-0{margin-bottom:0!important}.scalar-app .-mb-\\[var\\(--scalar-border-width\\)\\]{margin-bottom:calc(var(--scalar-border-width)*-1)}.scalar-app .mb-0{margin-bottom:0}.scalar-app .mb-1{margin-bottom:4px}.scalar-app .mb-1\\.5{margin-bottom:6px}.scalar-app .mb-2{margin-bottom:8px}.scalar-app .mb-4{margin-bottom:16px}.scalar-app .mb-5{margin-bottom:20px}.scalar-app .mb-\\[\\.5px\\]{margin-bottom:.5px}.scalar-app .-ml-0\\.5{margin-left:-2px}.scalar-app .-ml-0\\.25{margin-left:-1px}.scalar-app .-ml-1{margin-left:-4px}.scalar-app .-ml-2{margin-left:-8px}.scalar-app .-ml-12{margin-left:-48px}.scalar-app .ml-0\\.5{margin-left:2px}.scalar-app .ml-0\\.75{margin-left:3px}.scalar-app .ml-1{margin-left:4px}.scalar-app .ml-1\\.25{margin-left:5px}.scalar-app .ml-3{margin-left:12px}.scalar-app .ml-auto{margin-left:auto}.scalar-app .box-border{box-sizing:border-box}.scalar-app .box-content{box-sizing:content-box}.scalar-app .flex-center{justify-content:center;align-items:center;display:flex}.scalar-app .line-clamp-1{-webkit-line-clamp:1;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.scalar-app .\\!block{display:block!important}.scalar-app .\\!flex{display:flex!important}.scalar-app .block{display:block}.scalar-app .contents{display:contents}.scalar-app .flex{display:flex}.scalar-app .grid{display:grid}.scalar-app .hidden{display:none}.scalar-app .inline{display:inline}.scalar-app .inline-block{display:inline-block}.scalar-app .inline-flex{display:inline-flex}.scalar-app .table{display:table}.scalar-app .aspect-\\[4\\/3\\]{aspect-ratio:4/3}.scalar-app .aspect-square{aspect-ratio:1}.scalar-app .size-1\\.5{width:6px;height:6px}.scalar-app .size-2\\.5{width:10px;height:10px}.scalar-app .size-3{width:12px;height:12px}.scalar-app .size-3\\.5{width:14px;height:14px}.scalar-app .size-3\\/4{width:75%;height:75%}.scalar-app .size-4{width:16px;height:16px}.scalar-app .size-5{width:20px;height:20px}.scalar-app .size-6{width:24px;height:24px}.scalar-app .size-7{width:28px;height:28px}.scalar-app .size-8{width:32px;height:32px}.scalar-app .size-10{width:40px;height:40px}.scalar-app .size-full{width:100%;height:100%}.scalar-app .h-\\(--scalar-address-bar-height\\){height:var(--scalar-address-bar-height)}.scalar-app .h-1\\.5{height:6px}.scalar-app .h-2\\.5{height:10px}.scalar-app .h-2\\.25{height:9px}.scalar-app .h-3{height:12px}.scalar-app .h-3\\.5{height:14px}.scalar-app .h-4{height:16px}.scalar-app .h-5{height:20px}.scalar-app .h-6{height:24px}.scalar-app .h-7{height:28px}.scalar-app .h-8{height:32px}.scalar-app .h-9{height:36px}.scalar-app .h-10{height:40px}.scalar-app .h-12{height:48px}.scalar-app .h-16{height:64px}.scalar-app .h-64{height:256px}.scalar-app .h-\\[68px\\]{height:68px}.scalar-app .h-\\[calc\\(100\\%-273\\.5px\\)\\]{height:calc(100% - 273.5px)}.scalar-app .h-\\[calc\\(100\\%_-_50px\\)\\]{height:calc(100% - 50px)}.scalar-app .h-auto{height:auto}.scalar-app .h-dvh{height:100dvh}.scalar-app .h-fit{height:fit-content}.scalar-app .h-full{height:100%}.scalar-app .h-header{height:48px}.scalar-app .h-min{height:min-content}.scalar-app .h-px{height:1px}.scalar-app .h-screen{height:100vh}.scalar-app .\\!max-h-\\[initial\\]{max-height:initial!important}.scalar-app .max-h-8{max-height:32px}.scalar-app .max-h-40{max-height:160px}.scalar-app .max-h-80{max-height:320px}.scalar-app .max-h-\\[40dvh\\]{max-height:40dvh}.scalar-app .max-h-\\[50dvh\\]{max-height:50dvh}.scalar-app .max-h-\\[60svh\\]{max-height:60svh}.scalar-app .max-h-\\[auto\\]{max-height:auto}.scalar-app .max-h-\\[calc\\(100\\%-32px\\)\\]{max-height:calc(100% - 32px)}.scalar-app .max-h-\\[inherit\\]{max-height:inherit}.scalar-app .max-h-fit{max-height:fit-content}.scalar-app .max-h-screen{max-height:100vh}.scalar-app .min-h-0{min-height:0}.scalar-app .min-h-8{min-height:32px}.scalar-app .min-h-10{min-height:40px}.scalar-app .min-h-11{min-height:44px}.scalar-app .min-h-12{min-height:48px}.scalar-app .min-h-16{min-height:64px}.scalar-app .min-h-20{min-height:80px}.scalar-app .min-h-\\[64px\\]{min-height:64px}.scalar-app .min-h-\\[65px\\]{min-height:65px}.scalar-app .min-h-\\[calc\\(1rem\\*4\\)\\]{min-height:4rem}.scalar-app .min-h-\\[calc\\(4rem\\+0\\.5px\\)\\]{min-height:calc(4rem + .5px)}.scalar-app .min-h-\\[calc\\(4rem\\+1px\\)\\]{min-height:calc(4rem + 1px)}.scalar-app .min-h-fit{min-height:fit-content}.scalar-app .\\!w-fit{width:fit-content!important}.scalar-app .w-0\\.5{width:2px}.scalar-app .w-1\\.5{width:6px}.scalar-app .w-1\\/2{width:50%}.scalar-app .w-2\\.5{width:10px}.scalar-app .w-2\\.25{width:9px}.scalar-app .w-3{width:12px}.scalar-app .w-3\\.5{width:14px}.scalar-app .w-4{width:16px}.scalar-app .w-5{width:20px}.scalar-app .w-6{width:24px}.scalar-app .w-7{width:28px}.scalar-app .w-8{width:32px}.scalar-app .w-10{width:40px}.scalar-app .w-16{width:64px}.scalar-app .w-20{width:80px}.scalar-app .w-56{width:224px}.scalar-app .w-64{width:256px}.scalar-app .w-72{width:288px}.scalar-app .w-\\[60px\\]{width:60px}.scalar-app .w-\\[100px\\]{width:100px}.scalar-app .w-\\[150px\\]{width:150px}.scalar-app .w-\\[calc\\(100\\%-10px\\)\\]{width:calc(100% - 10px)}.scalar-app .w-\\[calc\\(100\\%_-_8px\\)\\]{width:calc(100% - 8px)}.scalar-app .w-\\[inherit\\]{width:inherit}.scalar-app .w-auto{width:auto}.scalar-app .w-dvw{width:100dvw}.scalar-app .w-fit{width:fit-content}.scalar-app .w-full{width:100%}.scalar-app .w-max{width:max-content}.scalar-app .max-w-8{max-width:32px}.scalar-app .max-w-40{max-width:160px}.scalar-app .max-w-\\[14px\\]{max-width:14px}.scalar-app .max-w-\\[16rem\\]{max-width:16rem}.scalar-app .max-w-\\[37px\\]{max-width:37px}.scalar-app .max-w-\\[100\\%\\]{max-width:100%}.scalar-app .max-w-\\[120px\\]{max-width:120px}.scalar-app .max-w-\\[150px\\]{max-width:150px}.scalar-app .max-w-\\[380px\\]{max-width:380px}.scalar-app .max-w-\\[420px\\]{max-width:420px}.scalar-app .max-w-\\[720px\\]{max-width:720px}.scalar-app .max-w-\\[calc\\(100dvw-24px\\)\\]{max-width:calc(100dvw - 24px)}.scalar-app .max-w-full{max-width:100%}.scalar-app .min-w-0{min-width:0}.scalar-app .min-w-2\\.25{min-width:9px}.scalar-app .min-w-3\\.5{min-width:14px}.scalar-app .min-w-4{min-width:16px}.scalar-app .min-w-8{min-width:32px}.scalar-app .min-w-32{min-width:128px}.scalar-app .min-w-48{min-width:192px}.scalar-app .min-w-\\[37px\\]{min-width:37px}.scalar-app .min-w-\\[100px\\]{min-width:100px}.scalar-app .min-w-\\[150px\\]{min-width:150px}.scalar-app .min-w-\\[296px\\]{min-width:296px}.scalar-app .min-w-fit{min-width:fit-content}.scalar-app .min-w-full{min-width:100%}.scalar-app .flex-1{flex:1}.scalar-app .flex-shrink{flex-shrink:1}.scalar-app .shrink-0{flex-shrink:0}.scalar-app .flex-grow,.scalar-app .grow{flex-grow:1}.scalar-app .-translate-x-1\\/2{--tw-translate-x:-50%;translate:var(--tw-translate-x)var(--tw-translate-y)}.scalar-app .translate-x-0{--tw-translate-x:0px;translate:var(--tw-translate-x)var(--tw-translate-y)}.scalar-app .translate-x-1\\/2{--tw-translate-x:50%;translate:var(--tw-translate-x)var(--tw-translate-y)}.scalar-app .translate-y-1\\/2{--tw-translate-y:50%;translate:var(--tw-translate-x)var(--tw-translate-y)}.scalar-app .scale-75{--tw-scale-x:75%;--tw-scale-y:75%;--tw-scale-z:75%;scale:var(--tw-scale-x)var(--tw-scale-y)}.scalar-app .rotate-90{rotate:90deg}.scalar-app .rotate-180{rotate:180deg}.scalar-app .transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.scalar-app .cursor-auto{cursor:auto}.scalar-app .cursor-default{cursor:default}.scalar-app .cursor-grab{cursor:grab}.scalar-app .cursor-help{cursor:help}.scalar-app .cursor-not-allowed{cursor:not-allowed}.scalar-app .cursor-pointer{cursor:pointer}.scalar-app .cursor-text{cursor:text}.scalar-app .resize{resize:both}.scalar-app .resize-none{resize:none}.scalar-app .auto-rows-\\[32px\\]{grid-auto-rows:32px}.scalar-app .auto-rows-auto{grid-auto-rows:auto}.scalar-app .grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.scalar-app .grid-cols-\\[44px_1fr_repeat\\(3\\,auto\\)\\]{grid-template-columns:44px 1fr repeat(3,auto)}.scalar-app .grid-cols-\\[auto_1fr\\]{grid-template-columns:auto 1fr}.scalar-app .grid-cols-\\[repeat\\(auto-fill\\,minmax\\(32px\\,1fr\\)\\)\\]{grid-template-columns:repeat(auto-fill,minmax(32px,1fr))}.scalar-app .flex-col{flex-direction:column}.scalar-app .flex-row{flex-direction:row}.scalar-app .flex-wrap{flex-wrap:wrap}.scalar-app .content-between{align-content:space-between}.scalar-app .content-start{align-content:flex-start}.scalar-app .items-center{align-items:center}.scalar-app .items-end{align-items:flex-end}.scalar-app .items-start{align-items:flex-start}.scalar-app .items-stretch{align-items:stretch}.scalar-app .justify-between{justify-content:space-between}.scalar-app .justify-center{justify-content:center}.scalar-app .justify-end{justify-content:flex-end}.scalar-app .justify-start{justify-content:flex-start}.scalar-app .justify-stretch{justify-content:stretch}.scalar-app .\\!gap-2{gap:8px!important}.scalar-app .gap-0\\.5{gap:2px}.scalar-app .gap-0\\.75{gap:3px}.scalar-app .gap-1{gap:4px}.scalar-app .gap-1\\.5{gap:6px}.scalar-app .gap-1\\.75{gap:7px}.scalar-app .gap-2{gap:8px}.scalar-app .gap-2\\.5{gap:10px}.scalar-app .gap-3{gap:12px}.scalar-app .gap-4{gap:16px}.scalar-app .gap-6{gap:24px}.scalar-app .gap-8{gap:32px}.scalar-app .gap-10{gap:40px}.scalar-app .gap-12{gap:48px}.scalar-app .gap-\\[1\\.5px\\]{gap:1.5px}.scalar-app .gap-px{gap:1px}.scalar-app .gap-x-2\\.5{column-gap:10px}:where(.scalar-app .space-x-1>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(4px*var(--tw-space-x-reverse));margin-inline-end:calc(4px*calc(1 - var(--tw-space-x-reverse)))}:where(.scalar-app .divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(var(--scalar-border-width)*var(--tw-divide-y-reverse));border-bottom-width:calc(var(--scalar-border-width)*calc(1 - var(--tw-divide-y-reverse)))}.scalar-app .self-center{align-self:center}.scalar-app .truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.scalar-app .overflow-auto{overflow:auto}.scalar-app .overflow-hidden{overflow:hidden}.scalar-app .overflow-visible{overflow:visible}.scalar-app .overflow-x-auto{overflow-x:auto}.scalar-app .overflow-x-hidden{overflow-x:hidden}.scalar-app .overflow-x-scroll{overflow-x:scroll}.scalar-app .overflow-y-auto{overflow-y:auto}.scalar-app .overflow-y-hidden{overflow-y:hidden}.scalar-app .overflow-y-scroll{overflow-y:scroll}.scalar-app .\\!rounded-none{border-radius:0!important}.scalar-app .rounded{border-radius:var(--scalar-radius)}.scalar-app .rounded-\\[10px\\]{border-radius:10px}.scalar-app .rounded-full{border-radius:9999px}.scalar-app .rounded-lg{border-radius:var(--scalar-radius-lg)}.scalar-app .rounded-md{border-radius:var(--scalar-radius)}.scalar-app .rounded-none{border-radius:0}.scalar-app .rounded-px{border-radius:1px}.scalar-app .rounded-xl{border-radius:var(--scalar-radius-xl)}.scalar-app .rounded-t{border-top-left-radius:var(--scalar-radius);border-top-right-radius:var(--scalar-radius)}.scalar-app .rounded-t-lg{border-top-left-radius:var(--scalar-radius-lg);border-top-right-radius:var(--scalar-radius-lg)}.scalar-app .rounded-t-none{border-top-left-radius:0;border-top-right-radius:0}.scalar-app .rounded-t-xl{border-top-left-radius:var(--scalar-radius-xl);border-top-right-radius:var(--scalar-radius-xl)}.scalar-app .rounded-b{border-bottom-right-radius:var(--scalar-radius);border-bottom-left-radius:var(--scalar-radius)}.scalar-app .rounded-b-lg{border-bottom-right-radius:var(--scalar-radius-lg);border-bottom-left-radius:var(--scalar-radius-lg)}.scalar-app .rounded-b-none{border-bottom-right-radius:0;border-bottom-left-radius:0}.scalar-app .rounded-b-xl{border-bottom-right-radius:var(--scalar-radius-xl);border-bottom-left-radius:var(--scalar-radius-xl)}.scalar-app .\\!border-0{border-style:var(--tw-border-style)!important;border-width:0!important}.scalar-app .border{border-style:var(--tw-border-style);border-width:var(--scalar-border-width)}.scalar-app .border-0{border-style:var(--tw-border-style);border-width:0}.scalar-app .border-\\[1\\.5px\\]{border-style:var(--tw-border-style);border-width:1.5px}.scalar-app .border-\\[1px\\]{border-style:var(--tw-border-style);border-width:1px}.scalar-app .border-x{border-inline-style:var(--tw-border-style);border-inline-width:var(--scalar-border-width)}.scalar-app .border-x-0{border-inline-style:var(--tw-border-style);border-inline-width:0}.scalar-app .border-y{border-block-style:var(--tw-border-style);border-block-width:var(--scalar-border-width)}.scalar-app .border-t{border-top-style:var(--tw-border-style);border-top-width:var(--scalar-border-width)}.scalar-app .border-t-0{border-top-style:var(--tw-border-style);border-top-width:0}.scalar-app .\\!border-r{border-right-style:var(--tw-border-style)!important;border-right-width:var(--scalar-border-width)!important}.scalar-app .border-r{border-right-style:var(--tw-border-style);border-right-width:var(--scalar-border-width)}.scalar-app .border-r-0{border-right-style:var(--tw-border-style);border-right-width:0}.scalar-app .border-r-1{border-right-style:var(--tw-border-style);border-right-width:1px}.scalar-app .border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:var(--scalar-border-width)}.scalar-app .border-b-0{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.scalar-app .border-b-\\[1px\\]{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.scalar-app .border-l{border-left-style:var(--tw-border-style);border-left-width:var(--scalar-border-width)}.scalar-app .border-l-0{border-left-style:var(--tw-border-style);border-left-width:0}.scalar-app .border-dashed{--tw-border-style:dashed;border-style:dashed}.scalar-app .border-none{--tw-border-style:none;border-style:none}.scalar-app .border-none\\!{--tw-border-style:none!important;border-style:none!important}.scalar-app .\\!border-current{border-color:currentColor!important}.scalar-app .border-c-1{border-color:var(--scalar-color-1)}.scalar-app .border-c-3{border-color:var(--scalar-color-3)}.scalar-app .border-c-accent,.scalar-app .border-c-accent\\/30{border-color:var(--scalar-color-accent)}@supports (color:color-mix(in lab,red,red)){.scalar-app .border-c-accent\\/30{border-color:var(--scalar-color-accent)}@supports (color:color-mix(in lab,red,red)){.scalar-app .border-c-accent\\/30{border-color:color-mix(in oklab,var(--scalar-color-accent)30%,transparent)}}}.scalar-app .border-c-danger{border-color:var(--scalar-color-danger)}.scalar-app .border-transparent{border-color:#0000}.scalar-app .border-r-transparent{border-right-color:#0000}.scalar-app .bg-b-1{background-color:var(--scalar-background-1)}.scalar-app .bg-b-2{background-color:var(--scalar-background-2)}.scalar-app .bg-b-3{background-color:var(--scalar-background-3)}.scalar-app .bg-b-danger{background-color:var(--scalar-background-danger)}.scalar-app .bg-c-3\\/5{background-color:var(--scalar-color-3)}@supports (color:color-mix(in lab,red,red)){.scalar-app .bg-c-3\\/5{background-color:var(--scalar-color-3)}@supports (color:color-mix(in lab,red,red)){.scalar-app .bg-c-3\\/5{background-color:color-mix(in oklab,var(--scalar-color-3)5%,transparent)}}}.scalar-app .bg-c-accent,.scalar-app .bg-c-accent\\/5{background-color:var(--scalar-color-accent)}@supports (color:color-mix(in lab,red,red)){.scalar-app .bg-c-accent\\/5{background-color:var(--scalar-color-accent)}@supports (color:color-mix(in lab,red,red)){.scalar-app .bg-c-accent\\/5{background-color:color-mix(in oklab,var(--scalar-color-accent)5%,transparent)}}}.scalar-app .bg-c-accent\\/10{background-color:var(--scalar-color-accent)}@supports (color:color-mix(in lab,red,red)){.scalar-app .bg-c-accent\\/10{background-color:var(--scalar-color-accent)}@supports (color:color-mix(in lab,red,red)){.scalar-app .bg-c-accent\\/10{background-color:color-mix(in oklab,var(--scalar-color-accent)10%,transparent)}}}.scalar-app .bg-current{background-color:currentColor}.scalar-app .bg-grey{background-color:var(--scalar-color-3)}.scalar-app .bg-orange{background-color:var(--scalar-color-orange)}.scalar-app .bg-sidebar-b-1{background-color:var(--scalar-sidebar-background-1,var(--scalar-background-1))}.scalar-app .bg-sidebar-b-active{background-color:var(--scalar-sidebar-item-active-background,var(--scalar-background-2))}.scalar-app .bg-none{background-image:none}.scalar-app .fill-current{fill:currentColor}.scalar-app .stroke-2{stroke-width:2px}.scalar-app .stroke-\\[1\\.5\\]{stroke-width:1.5px}.scalar-app .stroke-\\[1\\.75\\]{stroke-width:1.75px}.scalar-app .stroke-\\[2\\.25\\]{stroke-width:2.25px}.scalar-app .object-contain{object-fit:contain}.scalar-app .\\!p-0{padding:0!important}.scalar-app .p-0{padding:0}.scalar-app .p-0\\.5{padding:2px}.scalar-app .p-0\\.75{padding:3px}.scalar-app .p-1{padding:4px}.scalar-app .p-1\\.5{padding:6px}.scalar-app .p-1\\.25{padding:5px}.scalar-app .p-1\\.75{padding:7px}.scalar-app .p-2{padding:8px}.scalar-app .p-3{padding:12px}.scalar-app .p-4{padding:16px}.scalar-app .p-\\[3px\\]{padding:3px}.scalar-app .p-\\[5px\\]{padding:5px}.scalar-app .p-px{padding:1px}.scalar-app .\\!px-3{padding-inline:12px!important}.scalar-app .px-0{padding-inline:0}.scalar-app .px-0\\.5{padding-inline:2px}.scalar-app .px-0\\.75{padding-inline:3px}.scalar-app .px-1{padding-inline:4px}.scalar-app .px-1\\.5{padding-inline:6px}.scalar-app .px-1\\.25{padding-inline:5px}.scalar-app .px-2{padding-inline:8px}.scalar-app .px-2\\.5{padding-inline:10px}.scalar-app .px-3{padding-inline:12px}.scalar-app .px-4{padding-inline:16px}.scalar-app .px-5{padding-inline:20px}.scalar-app .px-6{padding-inline:24px}.scalar-app .px-8{padding-inline:32px}.scalar-app .\\!py-1\\.5{padding-block:6px!important}.scalar-app .py-0{padding-block:0}.scalar-app .py-0\\.5{padding-block:2px}.scalar-app .py-0\\.25{padding-block:1px}.scalar-app .py-0\\.75{padding-block:3px}.scalar-app .py-1{padding-block:4px}.scalar-app .py-1\\.5{padding-block:6px}.scalar-app .py-1\\.25{padding-block:5px}.scalar-app .py-1\\.75{padding-block:7px}.scalar-app .py-2{padding-block:8px}.scalar-app .py-2\\.5{padding-block:10px}.scalar-app .py-3{padding-block:12px}.scalar-app .py-5{padding-block:20px}.scalar-app .py-8{padding-block:32px}.scalar-app .py-px{padding-block:1px}.scalar-app .\\!pt-0{padding-top:0!important}.scalar-app .pt-0{padding-top:0}.scalar-app .pt-2{padding-top:8px}.scalar-app .pt-3{padding-top:12px}.scalar-app .pt-4{padding-top:16px}.scalar-app .pt-6{padding-top:24px}.scalar-app .pt-8{padding-top:32px}.scalar-app .pt-px{padding-top:1px}.scalar-app .pr-0{padding-right:0}.scalar-app .pr-0\\.75{padding-right:3px}.scalar-app .pr-1{padding-right:4px}.scalar-app .pr-1\\.5{padding-right:6px}.scalar-app .pr-2{padding-right:8px}.scalar-app .pr-2\\.5{padding-right:10px}.scalar-app .pr-2\\.25{padding-right:9px}.scalar-app .pr-3{padding-right:12px}.scalar-app .pr-6{padding-right:24px}.scalar-app .pr-8{padding-right:32px}.scalar-app .pr-9{padding-right:36px}.scalar-app .pr-10{padding-right:40px}.scalar-app .pr-12{padding-right:48px}.scalar-app .pr-\\[26px\\]{padding-right:26px}.scalar-app .pb-0{padding-bottom:0}.scalar-app .pb-1\\.5{padding-bottom:6px}.scalar-app .pb-2{padding-bottom:8px}.scalar-app .pb-3{padding-bottom:12px}.scalar-app .pb-5{padding-bottom:20px}.scalar-app .pb-6{padding-bottom:24px}.scalar-app .pb-8{padding-bottom:32px}.scalar-app .pb-14{padding-bottom:56px}.scalar-app .pb-\\[75px\\]{padding-bottom:75px}.scalar-app .\\!pl-3{padding-left:12px!important}.scalar-app .pl-1{padding-left:4px}.scalar-app .pl-1\\.5{padding-left:6px}.scalar-app .pl-1\\.25{padding-left:5px}.scalar-app .pl-2{padding-left:8px}.scalar-app .pl-3{padding-left:12px}.scalar-app .pl-5{padding-left:20px}.scalar-app .pl-6{padding-left:24px}.scalar-app .pl-8\\.5{padding-left:34px}.scalar-app .pl-9{padding-left:36px}.scalar-app .pl-12{padding-left:48px}.scalar-app .pl-px{padding-left:1px}.scalar-app .text-center{text-align:center}.scalar-app .text-left{text-align:left}.scalar-app .text-right{text-align:right}.scalar-app .font-code{font-family:var(--scalar-font-code)}.scalar-app .font-sans{font-family:var(--scalar-font)}.scalar-app .text-3xs{font-size:var(--scalar-font-size-7)}.scalar-app .text-\\[6px\\]{font-size:6px}.scalar-app .text-\\[9px\\]{font-size:9px}.scalar-app .text-\\[11px\\]{font-size:11px}.scalar-app .text-\\[21px\\]{font-size:21px}.scalar-app .text-base{font-size:var(--scalar-font-size-3)}.scalar-app .text-sm{font-size:var(--scalar-font-size-4)}.scalar-app .text-xl{font-size:var(--scalar-font-size-1)}.scalar-app .text-xs{font-size:var(--scalar-font-size-5)}.scalar-app .text-xxs{font-size:var(--scalar-font-size-6)}.scalar-app .\\!leading-\\[6px\\]{--tw-leading:6px!important;line-height:6px!important}.scalar-app .leading-2{--tw-leading:var(--scalar-line-height-2);line-height:var(--scalar-line-height-2)}.scalar-app .leading-3{--tw-leading:var(--scalar-line-height-3);line-height:var(--scalar-line-height-3)}.scalar-app .leading-\\[1\\.44\\]{--tw-leading:1.44;line-height:1.44}.scalar-app .leading-\\[7px\\]{--tw-leading:7px;line-height:7px}.scalar-app .leading-\\[20px\\]{--tw-leading:20px;line-height:20px}.scalar-app .leading-\\[21px\\]{--tw-leading:21px;line-height:21px}.scalar-app .leading-\\[22px\\]{--tw-leading:22px;line-height:22px}.scalar-app .leading-\\[normal\\]{--tw-leading:normal;line-height:normal}.scalar-app .leading-none{--tw-leading:1;line-height:1}.scalar-app .leading-normal{--tw-leading:var(--leading-normal);line-height:var(--leading-normal)}.scalar-app .leading-snug{--tw-leading:var(--leading-snug);line-height:var(--leading-snug)}.scalar-app .font-bold{--tw-font-weight:var(--scalar-bold);font-weight:var(--scalar-bold)}.scalar-app .font-medium{--tw-font-weight:var(--scalar-semibold);font-weight:var(--scalar-semibold)}.scalar-app .font-normal{--tw-font-weight:var(--scalar-regular);font-weight:var(--scalar-regular)}.scalar-app .text-balance{text-wrap:balance}.scalar-app .text-nowrap{text-wrap:nowrap}.scalar-app .text-pretty{text-wrap:pretty}.scalar-app .break-words{overflow-wrap:break-word}.scalar-app .break-all{word-break:break-all}.scalar-app .text-ellipsis{text-overflow:ellipsis}.scalar-app .whitespace-nowrap{white-space:nowrap}.scalar-app .whitespace-pre{white-space:pre}.scalar-app .whitespace-pre-wrap{white-space:pre-wrap}.scalar-app .\\!text-c-1{color:var(--scalar-color-1)!important}.scalar-app .text-b-1{color:var(--scalar-background-1)}.scalar-app .text-b-2{color:var(--scalar-background-2)}.scalar-app .text-blue{color:var(--scalar-color-blue)}.scalar-app .text-border{color:var(--scalar-border-color)}.scalar-app .text-c-1{color:var(--scalar-color-1)}.scalar-app .text-c-2{color:var(--scalar-color-2)}.scalar-app .text-c-3{color:var(--scalar-color-3)}.scalar-app .text-c-accent{color:var(--scalar-color-accent)}.scalar-app .text-c-btn{color:var(--scalar-button-1-color)}.scalar-app .text-c-danger{color:var(--scalar-color-danger)}.scalar-app .text-green{color:var(--scalar-color-green)}.scalar-app .text-grey{color:var(--scalar-color-3)}.scalar-app .text-orange{color:var(--scalar-color-orange)}.scalar-app .text-purple{color:var(--scalar-color-purple)}.scalar-app .text-red{color:var(--scalar-color-red)}.scalar-app .text-sidebar-c-2{color:var(--scalar-sidebar-color-2,var(--scalar-color-2))}.scalar-app .text-sidebar-c-active{color:var(--scalar-sidebar-color-active,var(--scalar-sidebar-color-1))}.scalar-app .text-transparent{color:#0000}.scalar-app .text-white{color:#fff}.scalar-app .text-yellow{color:var(--scalar-color-yellow)}.scalar-app .capitalize{text-transform:capitalize}.scalar-app .lowercase{text-transform:lowercase}.scalar-app .uppercase{text-transform:uppercase}.scalar-app .italic{font-style:italic}.scalar-app .line-through{text-decoration-line:line-through}.scalar-app .no-underline{text-decoration-line:none}.scalar-app .underline{text-decoration-line:underline}.scalar-app .decoration-c-3{-webkit-text-decoration-color:var(--scalar-color-3);text-decoration-color:var(--scalar-color-3)}.scalar-app .underline-offset-2{text-underline-offset:2px}.scalar-app .opacity-0{opacity:0}.scalar-app .opacity-50{opacity:.5}.scalar-app .opacity-100{opacity:1}.scalar-app .bg-blend-normal{background-blend-mode:normal}.scalar-app .mix-blend-luminosity{mix-blend-mode:luminosity}.scalar-app .shadow{--tw-shadow:var(--scalar-shadow-1);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.scalar-app .shadow-\\[-8px_0_4px_var\\(--scalar-background-1\\)\\]{--tw-shadow:-8px 0 4px var(--tw-shadow-color,var(--scalar-background-1));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.scalar-app .shadow-\\[0_-8px_0_8px_var\\(--scalar-background-1\\)\\,0_0_8px_8px_var\\(--scalar-background-1\\)\\]{--tw-shadow:0 -8px 0 8px var(--tw-shadow-color,var(--scalar-background-1)),0 0 8px 8px var(--tw-shadow-color,var(--scalar-background-1));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.scalar-app .shadow-border{--tw-shadow:inset 0 0 0 var(--tw-shadow-color,calc(var(--scalar-border-width)*2))var(--scalar-border-color);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.scalar-app .shadow-lg{--tw-shadow:var(--scalar-shadow-2);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.scalar-app .shadow-none{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.scalar-app .outline{outline-style:var(--tw-outline-style);outline-width:1px}.scalar-app .-outline-offset-1{outline-offset:-1px}.scalar-app .outline-offset-2{outline-offset:2px}.scalar-app .outline-b-3{outline-color:var(--scalar-background-3)}.scalar-app .outline-c-danger{outline-color:var(--scalar-color-danger)}.scalar-app .blur{--tw-blur:blur(8px);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.scalar-app .brightness-90{--tw-brightness:brightness(90%);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.scalar-app .brightness-\\[\\.9\\]{--tw-brightness:brightness(.9);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.scalar-app .brightness-lifted{--tw-brightness:brightness(var(--scalar-lifted-brightness));filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.scalar-app .filter{filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.scalar-app .backdrop-filter{-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.scalar-app .transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.scalar-app .transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.scalar-app .transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.scalar-app .transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.scalar-app .transition-none{transition-property:none}.scalar-app .duration-100{--tw-duration:.1s;transition-duration:.1s}.scalar-app .duration-150{--tw-duration:.15s;transition-duration:.15s}.scalar-app .duration-200{--tw-duration:.2s;transition-duration:.2s}.scalar-app .duration-300{--tw-duration:.3s;transition-duration:.3s}.scalar-app .ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.scalar-app .outline-none{--tw-outline-style:none;outline-style:none}.scalar-app .select-none{-webkit-user-select:none;user-select:none}.scalar-app .\\[--scalar-address-bar-height\\:32px\\]{--scalar-address-bar-height:32px}.scalar-app .app-drag-region{-webkit-app-region:drag}.scalar-app .app-no-drag-region{-webkit-app-region:no-drag}:is(.scalar-app .\\*\\:flex>*){display:flex}:is(.scalar-app .\\*\\:h-8>*){height:32px}:is(.scalar-app .\\*\\:cursor-pointer>*){cursor:pointer}:is(.scalar-app .\\*\\:items-center>*){align-items:center}:is(.scalar-app .\\*\\:rounded-none>*){border-radius:0}:is(.scalar-app .\\*\\:border-t>*){border-top-style:var(--tw-border-style);border-top-width:var(--scalar-border-width)}:is(.scalar-app .\\*\\:border-b-0>*){border-bottom-style:var(--tw-border-style);border-bottom-width:0}:is(.scalar-app .\\*\\:px-1\\.5>*){padding-inline:6px}:is(.scalar-app .\\*\\:pl-4>*){padding-left:16px}.scalar-app .group-first\\/row\\:border-t-0:is(:where(.group\\/row):first-child *){border-top-style:var(--tw-border-style);border-top-width:0}.scalar-app .group-last\\:border-b-transparent:is(:where(.group):last-child *){border-bottom-color:#0000}.scalar-app .group-last\\/label\\:rounded-br-lg:is(:where(.group\\/label):last-child *){border-bottom-right-radius:var(--scalar-radius-lg)}.scalar-app .group-focus-within\\:flex:is(:where(.group):focus-within *){display:flex}@media(hover:hover){.scalar-app .group-hover\\:block:is(:where(.group):hover *){display:block}.scalar-app .group-hover\\:flex:is(:where(.group):hover *){display:flex}.scalar-app .group-hover\\:hidden:is(:where(.group):hover *){display:none}.scalar-app .group-hover\\:inline:is(:where(.group):hover *){display:inline}.scalar-app .group-hover\\:pr-5:is(:where(.group):hover *){padding-right:20px}.scalar-app .group-hover\\:pr-6:is(:where(.group):hover *){padding-right:24px}.scalar-app .group-hover\\:pr-10:is(:where(.group):hover *){padding-right:40px}.scalar-app .group-hover\\:text-c-1:is(:where(.group):hover *){color:var(--scalar-color-1)}.scalar-app .group-hover\\:opacity-80:is(:where(.group):hover *){opacity:.8}.scalar-app .group-hover\\:opacity-100:is(:where(.group):hover *){opacity:1}.scalar-app .group-hover\\/auth\\:absolute:is(:where(.group\\/auth):hover *){position:absolute}.scalar-app .group-hover\\/auth\\:h-auto:is(:where(.group\\/auth):hover *){height:auto}.scalar-app .group-hover\\/auth\\:border-b:is(:where(.group\\/auth):hover *){border-bottom-style:var(--tw-border-style);border-bottom-width:var(--scalar-border-width)}.scalar-app .group-hover\\/cell\\:opacity-100:is(:where(.group\\/cell):hover *){opacity:1}.scalar-app .group-hover\\/item\\:flex:is(:where(.group\\/item):hover *){display:flex}.scalar-app .group-hover\\/item\\:opacity-100:is(:where(.group\\/item):hover *),.scalar-app .group-hover\\/params\\:opacity-100:is(:where(.group\\/params):hover *){opacity:1}.scalar-app .group-hover\\/row\\:flex:is(:where(.group\\/row):hover *){display:flex}.scalar-app .group-hover\\/scopes-accordion\\:text-c-2:is(:where(.group\\/scopes-accordion):hover *){color:var(--scalar-color-2)}.scalar-app .group-hover\\/upload\\:block:is(:where(.group\\/upload):hover *){display:block}}.scalar-app .group-focus-visible\\:opacity-100:is(:where(.group):focus-visible *){opacity:1}.scalar-app .group-focus-visible\\:outline:is(:where(.group):focus-visible *){outline-style:var(--tw-outline-style);outline-width:1px}.scalar-app .group-has-focus-visible\\:hidden:is(:where(.group):has(:focus-visible) *){display:none}.scalar-app .group-has-\\[\\.cm-focused\\]\\:z-1:is(:where(.group):has(.cm-focused) *){z-index:1}.scalar-app .group-has-\\[\\.cm-focused\\]\\:flex:is(:where(.group):has(.cm-focused) *){display:flex}.scalar-app .group-has-\\[\\.cm-focused\\]\\:pr-6:is(:where(.group):has(.cm-focused) *){padding-right:24px}.scalar-app .group-has-\\[\\.cm-focused\\]\\:pr-10:is(:where(.group):has(.cm-focused) *){padding-right:40px}.scalar-app .group-has-\\[\\:focus-visible\\]\\:hidden:is(:where(.group):has(:focus-visible) *){display:none}.scalar-app .group-has-\\[\\:focus-visible\\]\\:opacity-100:is(:where(.group):has(:focus-visible) *){opacity:1}.scalar-app .group-has-\\[\\:focus-visible\\]\\/cell\\:border-c-accent:is(:where(.group\\/cell):has(:focus-visible) *){border-color:var(--scalar-color-accent)}.scalar-app .group-has-\\[\\:focus-visible\\]\\/cell\\:opacity-100:is(:where(.group\\/cell):has(:focus-visible) *){opacity:1}.scalar-app .group-has-\\[\\:focus-visible\\]\\/input\\:block:is(:where(.group\\/input):has(:focus-visible) *){display:block}.scalar-app .group-has-\\[input\\]\\/label\\:mr-0:is(:where(.group\\/label):has(:is(input)) *){margin-right:0}.scalar-app .group-aria-expanded\\/button\\:rotate-180:is(:where(.group\\/button)[aria-expanded=true] *),.scalar-app .group-aria-expanded\\/combobox-button\\:rotate-180:is(:where(.group\\/combobox-button)[aria-expanded=true] *){rotate:180deg}.scalar-app .group-\\[\\.alert\\]\\:bg-b-alert:is(:where(.group).alert *){background-color:var(--scalar-background-alert)}.scalar-app .group-\\[\\.alert\\]\\:bg-transparent:is(:where(.group).alert *){background-color:#0000}.scalar-app .group-\\[\\.alert\\]\\:shadow-none:is(:where(.group).alert *){--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.scalar-app .group-\\[\\.alert\\]\\:outline-orange:is(:where(.group).alert *){outline-color:var(--scalar-color-orange)}.scalar-app .group-\\[\\.error\\]\\:bg-b-danger:is(:where(.group).error *){background-color:var(--scalar-background-danger)}.scalar-app .group-\\[\\.error\\]\\:bg-transparent:is(:where(.group).error *){background-color:#0000}.scalar-app .group-\\[\\.error\\]\\:text-red:is(:where(.group).error *){color:var(--scalar-color-red)}.scalar-app .group-\\[\\.error\\]\\:shadow-none:is(:where(.group).error *){--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.scalar-app .group-\\[\\.error\\]\\:outline-red:is(:where(.group).error *){outline-color:var(--scalar-color-red)}.scalar-app .peer-checked\\:text-c-1:is(:where(.peer):checked~*){color:var(--scalar-color-1)}.scalar-app .peer-has-\\[\\.cm-focused\\]\\:opacity-0:is(:where(.peer):has(.cm-focused)~*){opacity:0}.scalar-app .peer-has-\\[\\.color-selector\\]\\:hidden:is(:where(.peer):has(.color-selector)~*){display:none}.scalar-app .before\\:pointer-events-none:before{content:var(--tw-content);pointer-events:none}.scalar-app .before\\:absolute:before{content:var(--tw-content);position:absolute}.scalar-app .before\\:top-0:before{content:var(--tw-content);top:0}.scalar-app .before\\:left-3:before{content:var(--tw-content);left:12px}.scalar-app .before\\:left-\\[calc\\(\\.75rem_\\+_\\.5px\\)\\]:before{content:var(--tw-content);left:calc(.75rem + .5px)}.scalar-app .before\\:z-1:before{content:var(--tw-content);z-index:1}.scalar-app .before\\:h-\\[calc\\(100\\%_\\+_\\.5px\\)\\]:before{content:var(--tw-content);height:calc(100% + .5px)}.scalar-app .before\\:w-\\[\\.5px\\]:before{content:var(--tw-content);width:.5px}.scalar-app .before\\:bg-border:before{content:var(--tw-content);background-color:var(--scalar-border-color)}.scalar-app .after\\:content-\\[\\\'\\:\\\'\\]:after{--tw-content:":";content:var(--tw-content)}:is(.scalar-app .\\*\\:first\\:line-clamp-1>*):first-child{-webkit-line-clamp:1;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}:is(.scalar-app .\\*\\:first\\:rounded-l>*):first-child{border-top-left-radius:var(--scalar-radius);border-bottom-left-radius:var(--scalar-radius)}:is(.scalar-app .\\*\\:first\\:border-t-0>*):first-child,:is(.scalar-app .first\\:\\*\\:border-t-0:first-child>*){border-top-style:var(--tw-border-style);border-top-width:0}:is(.scalar-app .\\*\\:first\\:text-ellipsis>*):first-child{text-overflow:ellipsis}@media(hover:hover){:is(.scalar-app .group-hover\\/auth\\:\\*\\:first\\:line-clamp-none:is(:where(.group\\/auth):hover *)>*):first-child{-webkit-line-clamp:unset;-webkit-box-orient:horizontal;display:block;overflow:visible}}.scalar-app .last\\:mb-0:last-child{margin-bottom:0}.scalar-app .last\\:rounded-b-lg:last-child{border-bottom-right-radius:var(--scalar-radius-lg);border-bottom-left-radius:var(--scalar-radius-lg)}.scalar-app .last\\:border-r-0:last-child{border-right-style:var(--tw-border-style);border-right-width:0}:is(.scalar-app .\\*\\:last\\:rounded-r>*):last-child{border-top-right-radius:var(--scalar-radius);border-bottom-right-radius:var(--scalar-radius)}.scalar-app .last\\:before\\:h-full:last-child:before{content:var(--tw-content);height:100%}.scalar-app .last-of-type\\:first-of-type\\:border-b-0:last-of-type:first-of-type{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.scalar-app .focus-within\\:z-20:focus-within{z-index:20}.scalar-app .focus-within\\:border-\\(--scalar-background-3\\):focus-within{border-color:var(--scalar-background-3)}.scalar-app .focus-within\\:bg-b-1:focus-within{background-color:var(--scalar-background-1)}.scalar-app .focus-within\\:text-c-1:focus-within{color:var(--scalar-color-1)}@media(hover:hover){.scalar-app .hover\\:cursor-default:hover{cursor:default}.scalar-app .hover\\:border-\\(--scalar-background-3\\):hover{border-color:var(--scalar-background-3)}.scalar-app .hover\\:border-inherit:hover{border-color:inherit}.scalar-app .hover\\:bg-b-2:hover{background-color:var(--scalar-background-2)}.scalar-app .hover\\:bg-b-3:hover{background-color:var(--scalar-background-3)}.scalar-app .hover\\:bg-c-accent\\/20:hover{background-color:var(--scalar-color-accent)}@supports (color:color-mix(in lab,red,red)){.scalar-app .hover\\:bg-c-accent\\/20:hover{background-color:var(--scalar-color-accent)}@supports (color:color-mix(in lab,red,red)){.scalar-app .hover\\:bg-c-accent\\/20:hover{background-color:color-mix(in oklab,var(--scalar-color-accent)20%,transparent)}}}.scalar-app .hover\\:bg-inherit:hover{background-color:inherit}.scalar-app .hover\\:bg-sidebar-b-active:hover{background-color:var(--scalar-sidebar-item-active-background,var(--scalar-background-2))}.scalar-app .hover\\:whitespace-normal:hover{white-space:normal}.scalar-app .hover\\:text-c-1:hover{color:var(--scalar-color-1)}.scalar-app .hover\\:text-c-2:hover{color:var(--scalar-color-2)}.scalar-app .hover\\:underline:hover{text-decoration-line:underline}.scalar-app .hover\\:brightness-75:hover{--tw-brightness:brightness(75%);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}}.scalar-app .focus\\:border-b-1:focus{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--scalar-background-1)}.scalar-app .focus\\:text-c-1:focus{color:var(--scalar-color-1)}.scalar-app .focus\\:outline-none:focus{--tw-outline-style:none;outline-style:none}.scalar-app .focus-visible\\:z-10:focus-visible{z-index:10}.scalar-app .active\\:text-c-1:active{color:var(--scalar-color-1)}.scalar-app .disabled\\:cursor-default:disabled{cursor:default}.scalar-app .disabled\\:text-c-2:disabled{color:var(--scalar-color-2)}.scalar-app .has-focus-visible\\:bg-b-1:has(:focus-visible){background-color:var(--scalar-background-1)}.scalar-app .has-focus-visible\\:outline:has(:focus-visible){outline-style:var(--tw-outline-style);outline-width:1px}.scalar-app .has-\\[\\.empty-sidebar-item\\]\\:border-t:has(.empty-sidebar-item){border-top-style:var(--tw-border-style);border-top-width:var(--scalar-border-width)}.scalar-app .has-\\[\\:focus-visible\\]\\:absolute:has(:focus-visible){position:absolute}.scalar-app .has-\\[\\:focus-visible\\]\\:z-1:has(:focus-visible){z-index:1}.scalar-app .has-\\[\\:focus-visible\\]\\:rounded-\\[4px\\]:has(:focus-visible){border-radius:4px}.scalar-app .has-\\[\\:focus-visible\\]\\:bg-b-1:has(:focus-visible){background-color:var(--scalar-background-1)}.scalar-app .has-\\[\\:focus-visible\\]\\:opacity-100:has(:focus-visible){opacity:1}.scalar-app .has-\\[\\:focus-visible\\]\\:outline:has(:focus-visible){outline-style:var(--tw-outline-style);outline-width:1px}@media not all and (min-width:800px){.scalar-app .max-md\\:absolute\\!{position:absolute!important}.scalar-app .max-md\\:w-full\\!{width:100%!important}}@media(min-width:600px){.scalar-app .sm\\:not-sr-only{clip-path:none;white-space:normal;width:auto;height:auto;margin:0;padding:0;position:static;overflow:visible}.scalar-app .sm\\:order-none{order:0}.scalar-app .sm\\:mr-1\\.5{margin-right:6px}.scalar-app .sm\\:mb-1\\.5{margin-bottom:6px}.scalar-app .sm\\:ml-1\\.5{margin-left:6px}.scalar-app .sm\\:flex{display:flex}.scalar-app .sm\\:hidden{display:none}.scalar-app .sm\\:max-w-max{max-width:max-content}.scalar-app .sm\\:min-w-max{min-width:max-content}.scalar-app .sm\\:flex-col{flex-direction:column}.scalar-app .sm\\:flex-row{flex-direction:row}.scalar-app .sm\\:justify-between{justify-content:space-between}.scalar-app .sm\\:gap-px{gap:1px}.scalar-app .sm\\:rounded{border-radius:var(--scalar-radius)}.scalar-app .sm\\:rounded-lg{border-radius:var(--scalar-radius-lg)}.scalar-app .sm\\:px-2{padding-inline:8px}.scalar-app .sm\\:px-3{padding-inline:12px}.scalar-app .sm\\:py-1\\.5{padding-block:6px}:is(.scalar-app .sm\\:\\*\\:rounded-lg>*){border-radius:var(--scalar-radius-lg)}}@media(min-width:800px){.scalar-app .md\\:right-10{right:40px}.scalar-app .md\\:bottom-10{bottom:40px}.scalar-app .md\\:mx-auto{margin-inline:auto}.scalar-app .md\\:-ml-1\\.25{margin-left:-5px}.scalar-app .md\\:ml-1\\.5{margin-left:6px}.scalar-app .md\\:block{display:block}.scalar-app .md\\:flex{display:flex}.scalar-app .md\\:grid{display:grid}.scalar-app .md\\:w-full{width:100%}.scalar-app .md\\:max-w-180,.scalar-app .md\\:max-w-\\[720px\\]{max-width:720px}.scalar-app .md\\:min-w-fit{min-width:fit-content}.scalar-app .md\\:flex-none{flex:none}.scalar-app .md\\:translate-x-0{--tw-translate-x:0px;translate:var(--tw-translate-x)var(--tw-translate-y)}.scalar-app .md\\:translate-y-0{--tw-translate-y:0px;translate:var(--tw-translate-x)var(--tw-translate-y)}.scalar-app .md\\:grid-cols-\\[1fr_720px_1fr\\]{grid-template-columns:1fr 720px 1fr}.scalar-app .md\\:flex-row{flex-direction:row}.scalar-app .md\\:border-r{border-right-style:var(--tw-border-style);border-right-width:var(--scalar-border-width)}.scalar-app .md\\:border-b-0{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.scalar-app .md\\:p-1\\.5{padding:6px}.scalar-app .md\\:px-0{padding-inline:0}.scalar-app .md\\:px-1\\.5{padding-inline:6px}.scalar-app .md\\:px-2{padding-inline:8px}.scalar-app .md\\:px-2\\.5{padding-inline:10px}.scalar-app .md\\:px-4{padding-inline:16px}.scalar-app .md\\:px-\\[18px\\]{padding-inline:18px}.scalar-app .md\\:py-2\\.5{padding-block:10px}.scalar-app .md\\:pb-2\\.5{padding-bottom:10px}.scalar-app .md\\:pb-\\[37px\\]{padding-bottom:37px}.scalar-app .md\\:pl-0{padding-left:0}:is(.scalar-app .md\\:\\*\\:border-t-0>*){border-top-style:var(--tw-border-style);border-top-width:0}}@media(min-width:1000px){.scalar-app .lg\\:order-0,.scalar-app .lg\\:order-none{order:0}.scalar-app .lg\\:-mr-1{margin-right:-4px}.scalar-app .lg\\:mb-0{margin-bottom:0}.scalar-app .lg\\:flex{display:flex}.scalar-app .lg\\:min-h-header{min-height:48px}.scalar-app .lg\\:w-auto{width:auto}.scalar-app .lg\\:max-w-\\[580px\\]{max-width:580px}.scalar-app .lg\\:min-w-\\[580px\\]{min-width:580px}.scalar-app .lg\\:flex-1{flex:1}.scalar-app .lg\\:p-0{padding:0}.scalar-app .lg\\:p-1{padding:4px}.scalar-app .lg\\:px-1{padding-inline:4px}.scalar-app .lg\\:px-2\\.5{padding-inline:10px}.scalar-app .lg\\:pt-1{padding-top:4px}.scalar-app .lg\\:pr-24{padding-right:96px}}@media(min-width:1200px){.scalar-app .xl\\:\\!flex{display:flex!important}.scalar-app .xl\\:flex{display:flex}.scalar-app .xl\\:hidden{display:none}.scalar-app .xl\\:h-fit{height:fit-content}.scalar-app .xl\\:h-full{height:100%}.scalar-app .xl\\:min-h-header{min-height:48px}.scalar-app .xl\\:max-w-\\[720px\\]{max-width:720px}.scalar-app .xl\\:min-w-0{min-width:0}.scalar-app .xl\\:min-w-\\[720px\\]{min-width:720px}.scalar-app .xl\\:flex-row{flex-direction:row}.scalar-app .xl\\:overflow-auto{overflow:auto}.scalar-app .xl\\:overflow-hidden{overflow:hidden}.scalar-app .xl\\:rounded-none{border-radius:0}.scalar-app .xl\\:pr-0\\.5{padding-right:2px}.scalar-app .xl\\:pl-2{padding-left:8px}:is(.scalar-app .\\*\\:xl\\:border-t-0>*){border-top-style:var(--tw-border-style);border-top-width:0}:is(.scalar-app .\\*\\:xl\\:border-l>*){border-left-style:var(--tw-border-style);border-left-width:var(--scalar-border-width)}:is(.scalar-app .\\*\\:first\\:xl\\:border-l-0>*):first-child{border-left-style:var(--tw-border-style);border-left-width:0}}.scalar-app .dark\\:bg-b-2:where(.dark-mode,.dark-mode *){background-color:var(--scalar-background-2)}@media(hover:hover){.scalar-app .hover\\:dark\\:bg-b-2:hover:where(.dark-mode,.dark-mode *){background-color:var(--scalar-background-2)}}.scalar-app .ui-open\\:rotate-90[data-headlessui-state~=open],:where([data-headlessui-state~=open]) :is(.scalar-app .ui-open\\:rotate-90){rotate:90deg}.scalar-app .ui-open\\:rotate-180[data-headlessui-state~=open],:where([data-headlessui-state~=open]) :is(.scalar-app .ui-open\\:rotate-180){rotate:180deg}.scalar-app .last\\:ui-open\\:border-b-0:last-child[data-headlessui-state~=open],:where([data-headlessui-state~=open]) .scalar-app .last\\:ui-open\\:border-b-0:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.scalar-app .ui-not-open\\:hidden[data-headlessui-state]:not([data-headlessui-state~=open]),:where([data-headlessui-state]:not([data-headlessui-state~=open])) :is(.scalar-app .ui-not-open\\:hidden):not([data-headlessui-state]){display:none}.scalar-app .ui-not-open\\:rotate-0[data-headlessui-state]:not([data-headlessui-state~=open]),:where([data-headlessui-state]:not([data-headlessui-state~=open])) :is(.scalar-app .ui-not-open\\:rotate-0):not([data-headlessui-state]){rotate:none}.scalar-app .ui-checked\\:bg-b-3[data-headlessui-state~=checked],:where([data-headlessui-state~=checked]) :is(.scalar-app .ui-checked\\:bg-b-3){background-color:var(--scalar-background-3)}.scalar-app .ui-active\\:bg-b-2[data-headlessui-state~=active],:where([data-headlessui-state~=active]) :is(.scalar-app .ui-active\\:bg-b-2),:is(.scalar-app .ui-active\\:\\*\\:bg-b-2[data-headlessui-state~=active]>*),:is(:where([data-headlessui-state~=active]) :is(.scalar-app .ui-active\\:\\*\\:bg-b-2)>*){background-color:var(--scalar-background-2)}@media(max-width:720px)and (max-height:480px){.scalar-app .zoomed\\:static{position:static}.scalar-app .zoomed\\:p-1{padding:4px}}.app-platform-mac :is(.scalar-app .mac\\:pl-\\[72px\\]){padding-left:72px}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}.nav-item[data-v-507381a3]{cursor:pointer;border-radius:var(--scalar-radius-lg);background:var(--scalar-background-3);border:var(--scalar-border-width)solid var(--scalar-background-2);color:var(--scalar-color-3);flex:1;justify-content:center;align-items:center;min-width:0;padding:4.5px;display:flex;position:relative;overflow:hidden}.dark-mode .nav-item[data-v-507381a3]{background:var(--scalar-background-2)}@supports (color:color-mix(in lab,red,red)){.dark-mode .nav-item[data-v-507381a3]{background:color-mix(in srgb,var(--scalar-background-2),transparent)}}.nav-item-icon-copy[data-v-507381a3]{white-space:nowrap;max-width:100%;-webkit-mask-image:linear-gradient(to left,transparent 0,var(--scalar-background-2)20px);mask-image:linear-gradient(to left,transparent 0,var(--scalar-background-2)20px);overflow:hidden}.nav-item:hover .nav-item-icon-copy[data-v-507381a3]{-webkit-mask-image:linear-gradient(to left,transparent 20px,var(--scalar-background-2)40px);mask-image:linear-gradient(to left,transparent 20px,var(--scalar-background-2)40px)}.nav-item-copy[data-v-507381a3]{max-width:calc(100% - 20px)}.nav-item[data-v-507381a3]:hover{color:var(--scalar-color-1)}.nav-item__active[data-v-507381a3]{background-color:var(--scalar-background-1);color:var(--scalar-color-1);border-color:var(--scalar-border-color)}.dark-mode .nav-item__active[data-v-507381a3]{background-color:var(--scalar-background-2)}.nav-item-close[data-v-507381a3]{border-radius:var(--scalar-radius);stroke-width:1.5px;max-width:20px;color:var(--scalar-color-3);opacity:0;background:0 0;margin-left:-20px;padding:2px;position:absolute;right:3px}.nav-item:hover .nav-item-close[data-v-507381a3]{opacity:1}.nav-item-close[data-v-507381a3]:hover{background-color:var(--scalar-background-4)}.nav-item__active .nav-item-close[data-v-507381a3]:hover{background-color:var(--scalar-background-2)}.download-app-button[data-v-cb45fa05]{box-shadow:0 0 0 .5px var(--scalar-border-color);background:linear-gradient(#ffffffbf,#00000009)}.dark-mode .download-app-button[data-v-cb45fa05]{background:linear-gradient(#ffffff1a,#00000026)}.download-app-button[data-v-cb45fa05]:hover{background:linear-gradient(#00000009,#ffffffbf)}.dark-mode .download-app-button[data-v-cb45fa05]:hover{background:linear-gradient(#00000026,#ffffff1a)}.http-bg-gradient[data-v-076b14a1]{background:linear-gradient(#ffffffbf,#00000009)}.http-bg-gradient[data-v-076b14a1]:hover{background:linear-gradient(#00000009,#ffffffbf)}.dark-mode .http-bg-gradient[data-v-076b14a1]{background:linear-gradient(#ffffff09,#00000026)}.dark-mode .http-bg-gradient[data-v-076b14a1]:hover{background:linear-gradient(#00000026,#ffffff09)}.scroll-timeline-x[data-v-e0578855]{scroll-timeline:--scroll-timeline x;scroll-timeline:--scroll-timeline horizontal;-ms-overflow-style:none;scrollbar-width:none;overflow:auto}.commandmenu[data-v-dd90fe74]{box-shadow:var(--scalar-shadow-2);border-radius:var(--scalar-radius-lg);background-color:var(--scalar-background-1);opacity:0;width:100%;max-width:580px;max-height:60dvh;margin:12px;animation:.3s ease-in-out .1s forwards fadeincommandmenu-dd90fe74;position:fixed;top:150px;left:50%;transform:translate(-50%,10px)}.commandmenu-overlay[data-v-dd90fe74]{cursor:pointer;background:#0003;animation:.3s ease-in-out forwards fadeincommand-dd90fe74;position:fixed;inset:0}@keyframes fadeincommand-dd90fe74{0%{opacity:0}to{opacity:1}}@keyframes fadeincommandmenu-dd90fe74{0%{opacity:0;transform:translate(-50%,10px)}to{opacity:1;transform:translate(-50%)}}.scalar .scalar-app-layout[data-v-6118685a]{background:var(--scalar-background-1);border:var(--scalar-border-width)solid var(--scalar-border-color);border-radius:8px;width:100%;max-width:1390px;height:calc(100% - 120px);margin:auto;position:relative;overflow:hidden}@media(max-width:720px)and (max-height:480px){.scalar .scalar-app-layout[data-v-6118685a]{height:100%;max-height:90svh}}.scalar .scalar-app-exit[data-v-6118685a]{cursor:pointer;z-index:-1;background:#00000038;width:100vw;height:100vh;position:fixed;top:0;left:0}.dark-mode .scalar .scalar-app-exit[data-v-6118685a]{background:#00000073}.scalar .scalar-app-exit[data-v-6118685a]:before{text-align:center;color:#fff;opacity:.6;font-family:sans-serif;font-size:30px;font-weight:100;line-height:50px;position:absolute;top:0;right:12px}.scalar .scalar-app-exit[data-v-6118685a]:hover:before{opacity:1}.scalar-container[data-v-6118685a]{visibility:visible;justify-content:center;align-items:center;width:100%;height:100%;display:flex;position:fixed;top:0;bottom:0;left:0;overflow:hidden}.scalar .url-form-input[data-v-6118685a]{min-height:auto!important}.scalar .scalar-container[data-v-6118685a]{line-height:normal}.scalar-client-fade-enter-active[data-v-6118685a],.scalar-client-fade-leave-active[data-v-6118685a]{transition:opacity .35s}.scalar-client-fade-enter-from[data-v-6118685a],.scalar-client-fade-leave-to[data-v-6118685a]{opacity:0}.open-api-client-button[data-v-c7bdd328]{cursor:pointer;text-align:center;white-space:nowrap;width:100%;height:31px;font-size:var(--scalar-small);border-radius:var(--scalar-radius);box-shadow:0 0 0 .5px var(--scalar-border-color);color:var(--scalar-sidebar-color-1);justify-content:center;align-items:center;gap:6px;padding:9px 12px;line-height:1.385;text-decoration:none;display:flex}.open-api-client-button[data-v-c7bdd328]:hover{background:var(--scalar-sidebar-item-hover-background,var(--scalar-background-2))}[data-v-103d9d56] .cm-editor{background:0 0;outline:none;height:100%;padding:0}[data-v-103d9d56] .cm-placeholder{color:var(--scalar-color-3)}[data-v-103d9d56] .cm-content{font-family:var(--scalar-font-code);font-size:var(--scalar-small);max-height:20px;padding:8px 0}[data-v-103d9d56] .cm-tooltip{filter:brightness(var(--scalar-lifted-brightness));border-radius:var(--scalar-radius);box-shadow:var(--scalar-shadow-2);background:0 0!important;border:none!important;outline:none!important;overflow:hidden!important}[data-v-103d9d56] .cm-tooltip-autocomplete ul li{padding:3px 6px!important}[data-v-103d9d56] .cm-completionIcon-type:after{color:var(--scalar-color-3)!important}[data-v-103d9d56] .cm-tooltip-autocomplete ul li[aria-selected]{background:var(--scalar-background-2)!important;color:var(--scalar-color-1)!important}[data-v-103d9d56] .cm-tooltip-autocomplete ul{position:relative;padding:6px!important}[data-v-103d9d56] .cm-tooltip-autocomplete ul li:hover{border-radius:3px;color:var(--scalar-color-1)!important;background:var(--scalar-background-3)!important}[data-v-103d9d56] .cm-activeLine,[data-v-103d9d56] .cm-activeLineGutter{background-color:#0000}[data-v-103d9d56] .cm-selectionMatch,[data-v-103d9d56] .cm-matchingBracket{border-radius:var(--scalar-radius);background:var(--scalar-background-4)!important}[data-v-103d9d56] .cm-css-color-picker-wrapper{outline:1px solid var(--scalar-background-3);border-radius:3px;display:inline-flex;overflow:hidden}[data-v-103d9d56] .cm-gutters{color:var(--scalar-color-3);font-size:var(--scalar-small);background-color:#0000;border-right:none;border-radius:0 0 0 3px;line-height:22px}[data-v-103d9d56] .cm-gutters:before{content:"";border-radius:var(--scalar-radius)0 0 var(--scalar-radius);background-color:var(--scalar-background-1);width:calc(100% - 2px);height:calc(100% - 4px);position:absolute;top:2px;left:2px}[data-v-103d9d56] .cm-gutterElement{justify-content:flex-end;align-items:center;display:flex;position:relative;font-family:var(--scalar-font-code)!important;padding-left:0!important;padding-right:6px!important}[data-v-103d9d56] .cm-lineNumbers .cm-gutterElement{min-width:fit-content}[data-v-103d9d56] .cm-gutter+.cm-gutter :not(.cm-foldGutter) .cm-gutterElement{padding-left:0!important}[data-v-103d9d56] .cm-scroller{overflow:auto}.line-wrapping[data-v-103d9d56]:focus-within .cm-content{white-space:break-spaces;word-break:break-all;min-height:fit-content;padding:3px 6px;display:inline-table}.address-bar-history-button[data-v-a93fa60f]:hover{background:var(--scalar-background-3)}.address-bar-history-button[data-v-a93fa60f]:focus-within{background:var(--scalar-background-2)}.description[data-v-92012388] .markdown{font-weight:var(--scalar-semibold);color:var(--scalar-color--1);padding:0;display:block}.description[data-v-92012388] .markdown>:first-child{margin-top:0}[data-v-cb2a35da] .cm-editor{outline:none;width:100%;height:100%}[data-v-cb2a35da] .cm-line{padding:0}[data-v-cb2a35da] .cm-content{font-size:var(--scalar-small);align-items:center;padding:0;display:flex}.scroll-timeline-x[data-v-cb2a35da]{scroll-timeline:--scroll-timeline x;scroll-timeline:--scroll-timeline horizontal;-ms-overflow-style:none}.scroll-timeline-x-hidden[data-v-cb2a35da]{overflow-x:auto}.scroll-timeline-x-hidden[data-v-cb2a35da] .cm-scroller{scrollbar-width:none;-ms-overflow-style:none;padding-right:20px;overflow:auto}.scroll-timeline-x-hidden[data-v-cb2a35da]::-webkit-scrollbar{width:0;height:0;display:none}.scroll-timeline-x-hidden[data-v-cb2a35da] .cm-scroller::-webkit-scrollbar{width:0;height:0;display:none}.scroll-timeline-x-address[data-v-cb2a35da]{scrollbar-width:none;line-height:27px}.scroll-timeline-x-address[data-v-cb2a35da]:after{content:"";cursor:text;width:24px;height:100%;position:absolute;right:0}.scroll-timeline-x-address[data-v-cb2a35da]:empty:before{content:"Enter URL or cURL request";color:var(--scalar-color-3);pointer-events:none}.fade-left[data-v-cb2a35da],.fade-right[data-v-cb2a35da]{content:"";pointer-events:none;z-index:1;height:100%;animation-name:fadein-cb2a35da;animation-duration:1ms;animation-direction:reverse;animation-timeline:--scroll-timeline;position:sticky}.fade-left[data-v-cb2a35da]{background:linear-gradient(-90deg,var(--scalar-address-bar-bg)0%,var(--scalar-address-bar-bg)30%,var(--scalar-address-bar-bg)100%)}@supports (color:color-mix(in lab,red,red)){.fade-left[data-v-cb2a35da]{background:linear-gradient(-90deg,color-mix(in srgb,var(--scalar-address-bar-bg),transparent 100%)0%,color-mix(in srgb,var(--scalar-address-bar-bg),transparent 20%)30%,var(--scalar-address-bar-bg)100%)}}.fade-left[data-v-cb2a35da]{min-width:6px;animation-direction:normal;left:-1px}.fade-right[data-v-cb2a35da]{background:linear-gradient(90deg,var(--scalar-address-bar-bg)0%,var(--scalar-address-bar-bg)30%,var(--scalar-address-bar-bg)100%)}@supports (color:color-mix(in lab,red,red)){.fade-right[data-v-cb2a35da]{background:linear-gradient(90deg,color-mix(in srgb,var(--scalar-address-bar-bg),transparent 100%)0%,color-mix(in srgb,var(--scalar-address-bar-bg),transparent 20%)30%,var(--scalar-address-bar-bg)100%)}}.fade-right[data-v-cb2a35da]{min-width:24px;right:-1px}@keyframes fadein-cb2a35da{0%{opacity:0}1%{opacity:1}}.address-bar-bg-states[data-v-cb2a35da]{--scalar-address-bar-bg:var(--scalar-background-1)}@supports (color:color-mix(in lab,red,red)){.address-bar-bg-states[data-v-cb2a35da]{--scalar-address-bar-bg:color-mix(in srgb,var(--scalar-background-1),var(--scalar-background-2))}}.address-bar-bg-states[data-v-cb2a35da]{background:var(--scalar-address-bar-bg)}.address-bar-bg-states[data-v-cb2a35da]:has(.cm-focused){--scalar-address-bar-bg:var(--scalar-background-1);border-color:var(--scalar-border-color);outline:1px solid var(--scalar-color-accent)}.address-bar-bg-states:has(.cm-focused) .fade-left[data-v-cb2a35da],.address-bar-bg-states:has(.cm-focused) .fade-right[data-v-cb2a35da]{--scalar-address-bar-bg:var(--scalar-background-1)}.sidebar-height[data-v-dcff7b49]{min-height:100%}@media(min-width:800px){.sidebar-mask[data-v-dcff7b49]{-webkit-mask-image:linear-gradient(0,transparent 0,transparent 0,var(--scalar-background-2)30px);mask-image:linear-gradient(0,transparent 0,transparent 0,var(--scalar-background-2)30px)}}.resizer[data-v-dcff7b49]{cursor:col-resize;border-right:2px solid #0000;width:5px;transition:border-right-color .3s;position:absolute;top:0;bottom:0;right:0}.resizer[data-v-dcff7b49]:hover,.dragging .resizer[data-v-dcff7b49]{border-right-color:var(--scalar-background-3)}.dragging[data-v-dcff7b49]{cursor:col-resize}.dragging[data-v-dcff7b49]:before{content:"";width:100%;height:100%;display:block;position:absolute}[data-v-c1a50a6e] .cm-editor{padding:0}[data-v-c1a50a6e] .cm-content{font-family:var(--scalar-font);font-size:var(--scalar-small);background-color:#0000;align-items:center;width:100%;padding:5px 8px;display:flex}[data-v-c1a50a6e] .cm-content:has(.cm-pill){padding:5px 8px}[data-v-c1a50a6e] .cm-content .cm-pill:not(:last-of-type){margin-right:.5px}[data-v-c1a50a6e] .cm-content .cm-pill:not(:first-of-type){margin-left:.5px}[data-v-c1a50a6e] .cm-line{text-overflow:ellipsis;word-break:break-word;padding:0;overflow:hidden}.required[data-v-c1a50a6e]:after{content:"Required"}input[data-v-c1a50a6e]::placeholder{color:var(--scalar-color-3)}.scalar-password-input[data-v-c1a50a6e]{text-security:disc;-webkit-text-security:disc;-moz-text-security:disc}@media(min-width:800px){.has-no-import-url,.has-import-url{contain:paint;max-width:100dvw;overflow-x:hidden}.has-no-import-url .scalar-client>main{opacity:1;background:var(--scalar-background-1);animation:.3s ease-in-out forwards transform-restore-layout}.has-import-url .scalar-client>main{opacity:0;border:var(--scalar-border-width)solid var(--scalar-border-color);z-index:10000;border-radius:12px;animation:.3s ease-in-out forwards transform-fade-layout;overflow:hidden;transform:scale(.85)translate(calc(50dvw + 80px))}.has-import-url .scalar-client .sidenav{display:none}.has-no-import-url .scalar-app,.has-import-url .scalar-app{background:var(--scalar-background-1)!important}}@keyframes transform-fade-layout{0%{opacity:0;transform:scale(.85)translate(calc(50dvw + 80px),10px)}to{opacity:1;transform:scale(.85)translate(calc(50dvw + 80px))}}@keyframes transform-restore-layout{0%{opacity:1;transform:scale(.85)translate(calc(50dvw + 80px))}to{opacity:1;transform:scale(1)translate(0)}}.openapi-color{color:var(--scalar-color-green)}.section-flare{position:fixed;top:0;right:-50dvw}#scalar-client{background-color:var(--scalar-background-2);flex-direction:column;width:100dvw;height:100dvh;display:flex;position:relative}.address-bar-history-button[data-v-c15c6573]:hover{background:var(--scalar-background-3)}.address-bar-history-button[data-v-c15c6573]:focus-within{background:var(--scalar-background-2)}.description[data-v-1b7a32a4] .markdown{font-weight:var(--scalar-semibold);color:var(--scalar-color--1);padding:0;display:block}.description[data-v-1b7a32a4] .markdown>:first-child{margin-top:0}[data-v-a0bd2752] .cm-editor{background:0 0;outline:none;height:100%;padding:0}[data-v-a0bd2752] .cm-placeholder{color:var(--scalar-color-3)}[data-v-a0bd2752] .cm-content{font-family:var(--scalar-font-code);font-size:var(--scalar-small);max-height:20px;padding:8px 0}[data-v-a0bd2752] .cm-tooltip{filter:brightness(var(--scalar-lifted-brightness));border-radius:var(--scalar-radius);box-shadow:var(--scalar-shadow-2);background:0 0!important;border:none!important;outline:none!important;overflow:hidden!important}[data-v-a0bd2752] .cm-tooltip-autocomplete ul li{padding:3px 6px!important}[data-v-a0bd2752] .cm-completionIcon-type:after{color:var(--scalar-color-3)!important}[data-v-a0bd2752] .cm-tooltip-autocomplete ul li[aria-selected]{background:var(--scalar-background-2)!important;color:var(--scalar-color-1)!important}[data-v-a0bd2752] .cm-tooltip-autocomplete ul{position:relative;padding:6px!important}[data-v-a0bd2752] .cm-tooltip-autocomplete ul li:hover{border-radius:3px;color:var(--scalar-color-1)!important;background:var(--scalar-background-3)!important}[data-v-a0bd2752] .cm-activeLine,[data-v-a0bd2752] .cm-activeLineGutter{background-color:#0000}[data-v-a0bd2752] .cm-selectionMatch,[data-v-a0bd2752] .cm-matchingBracket{border-radius:var(--scalar-radius);background:var(--scalar-background-4)!important}[data-v-a0bd2752] .cm-css-color-picker-wrapper{outline:1px solid var(--scalar-background-3);border-radius:3px;display:inline-flex;overflow:hidden}[data-v-a0bd2752] .cm-gutters{color:var(--scalar-color-3);font-size:var(--scalar-small);background-color:#0000;border-right:none;border-radius:0 0 0 3px;line-height:22px}[data-v-a0bd2752] .cm-gutters:before{content:"";border-radius:var(--scalar-radius)0 0 var(--scalar-radius);background-color:var(--scalar-background-1);width:calc(100% - 2px);height:calc(100% - 4px);position:absolute;top:2px;left:2px}[data-v-a0bd2752] .cm-gutterElement{justify-content:flex-end;align-items:center;display:flex;position:relative;font-family:var(--scalar-font-code)!important;padding-left:0!important;padding-right:6px!important}[data-v-a0bd2752] .cm-lineNumbers .cm-gutterElement{min-width:fit-content}[data-v-a0bd2752] .cm-gutter+.cm-gutter :not(.cm-foldGutter) .cm-gutterElement{padding-left:0!important}[data-v-a0bd2752] .cm-scroller{overflow:auto}.line-wrapping[data-v-a0bd2752]:focus-within .cm-content{white-space:break-spaces;word-break:break-all;min-height:fit-content;padding:3px 6px;display:inline-table}.cm-pill{--tw-bg-base:var(--scalar-color-1);color:var(--tw-bg-base);font-size:var(--scalar-small);border-radius:30px;padding:0 9px;display:inline-block;background:var(--tw-bg-base)!important}@supports (color:color-mix(in lab,red,red)){.cm-pill{background:color-mix(in srgb,var(--tw-bg-base),transparent 94%)!important}}.cm-pill.bg-grey{background:var(--scalar-background-3)!important}.dark-mode .cm-pill{background:var(--tw-bg-base)!important}@supports (color:color-mix(in lab,red,red)){.dark-mode .cm-pill{background:color-mix(in srgb,var(--tw-bg-base),transparent 90%)!important}}.cm-pill:first-of-type{margin-left:0}.cm-editor .cm-widgetBuffer{display:none}.cm-foldPlaceholder:hover{color:var(--scalar-color-1)}.cm-foldGutter .cm-gutterElement{font-size:var(--scalar-heading-4);padding:2px!important}.cm-foldGutter .cm-gutterElement:first-of-type{display:none}.cm-foldGutter .cm-gutterElement .cm-foldMarker{padding:2px}.cm-foldGutter .cm-gutterElement:hover .cm-foldMarker{background:var(--scalar-background-2);border-radius:var(--scalar-radius);color:var(--scalar-color-1)}[data-v-791e0491] .cm-editor{outline:none;width:100%;height:100%}[data-v-791e0491] .cm-line{padding:0}[data-v-791e0491] .cm-content{font-size:var(--scalar-small);align-items:center;padding:0;display:flex}.scroll-timeline-x[data-v-791e0491]{scroll-timeline:--scroll-timeline x;scroll-timeline:--scroll-timeline horizontal;-ms-overflow-style:none}.scroll-timeline-x-hidden[data-v-791e0491]{overflow-x:auto}.scroll-timeline-x-hidden[data-v-791e0491] .cm-scroller{scrollbar-width:none;-ms-overflow-style:none;padding-right:20px;overflow:auto}.scroll-timeline-x-hidden[data-v-791e0491]::-webkit-scrollbar{width:0;height:0;display:none}.scroll-timeline-x-hidden[data-v-791e0491] .cm-scroller::-webkit-scrollbar{width:0;height:0;display:none}.scroll-timeline-x-address[data-v-791e0491]{scrollbar-width:none;line-height:27px}.scroll-timeline-x-address[data-v-791e0491]:after{content:"";cursor:text;width:24px;height:100%;position:absolute;right:0}.scroll-timeline-x-address[data-v-791e0491]:empty:before{content:"Enter URL or cURL request";color:var(--scalar-color-3);pointer-events:none}.fade-left[data-v-791e0491],.fade-right[data-v-791e0491]{content:"";pointer-events:none;z-index:1;height:100%;animation-name:fadein-791e0491;animation-duration:1ms;animation-direction:reverse;animation-timeline:--scroll-timeline;position:sticky}.fade-left[data-v-791e0491]{background:linear-gradient(-90deg,var(--scalar-address-bar-bg)0%,var(--scalar-address-bar-bg)30%,var(--scalar-address-bar-bg)100%)}@supports (color:color-mix(in lab,red,red)){.fade-left[data-v-791e0491]{background:linear-gradient(-90deg,color-mix(in srgb,var(--scalar-address-bar-bg),transparent 100%)0%,color-mix(in srgb,var(--scalar-address-bar-bg),transparent 20%)30%,var(--scalar-address-bar-bg)100%)}}.fade-left[data-v-791e0491]{min-width:6px;animation-direction:normal;left:-1px}.fade-right[data-v-791e0491]{background:linear-gradient(90deg,var(--scalar-address-bar-bg)0%,var(--scalar-address-bar-bg)30%,var(--scalar-address-bar-bg)100%)}@supports (color:color-mix(in lab,red,red)){.fade-right[data-v-791e0491]{background:linear-gradient(90deg,color-mix(in srgb,var(--scalar-address-bar-bg),transparent 100%)0%,color-mix(in srgb,var(--scalar-address-bar-bg),transparent 20%)30%,var(--scalar-address-bar-bg)100%)}}.fade-right[data-v-791e0491]{min-width:24px;right:-1px}@keyframes fadein-791e0491{0%{opacity:0}1%{opacity:1}}.address-bar-bg-states[data-v-791e0491]{--scalar-address-bar-bg:var(--scalar-background-1)}@supports (color:color-mix(in lab,red,red)){.address-bar-bg-states[data-v-791e0491]{--scalar-address-bar-bg:color-mix(in srgb,var(--scalar-background-1),var(--scalar-background-2))}}.address-bar-bg-states[data-v-791e0491]{background:var(--scalar-address-bar-bg)}.address-bar-bg-states[data-v-791e0491]:has(.cm-focused){--scalar-address-bar-bg:var(--scalar-background-1);border-color:var(--scalar-border-color);outline-width:1px;outline-style:solid}.address-bar-bg-states:has(.cm-focused) .fade-left[data-v-791e0491],.address-bar-bg-states:has(.cm-focused) .fade-right[data-v-791e0491]{--scalar-address-bar-bg:var(--scalar-background-1)}.app-exit-button[data-v-7b4b0249]{color:#fff;background:#0000001a}.app-exit-button[data-v-7b4b0249]:hover{background:#ffffff1a}.fade-request-section-content[data-v-f97cc68c]{background:linear-gradient(to left,var(--scalar-background-1)64%,transparent)}.filter-hover[data-v-f97cc68c]{height:100%;padding-left:24px;padding-right:39px;transition:width 0s ease-in-out .2s;position:absolute;right:0;overflow:hidden}.filter-hover[data-v-f97cc68c]:hover,.filter-hover[data-v-f97cc68c]:has(:focus-visible){z-index:10;width:100%}.filter-hover[data-v-f97cc68c]:before{content:"";background-color:var(--scalar-background-1);opacity:0;pointer-events:none;width:100%;height:fit-content;transition:all .3s ease-in-out;position:absolute;top:0;left:0}.filter-hover-item[data-v-f97cc68c]{opacity:0}.filter-hover-item[data-v-f97cc68c]:not(:last-of-type){transform:translateY(3px)}.filter-hover:hover .filter-hover-item[data-v-f97cc68c]{transition:opacity .2s ease-in-out,transform .2s ease-in-out}.filter-hover:hover .filter-hover-item[data-v-f97cc68c]:last-of-type{transition-delay:50ms}.filter-hover:hover .filter-hover-item[data-v-f97cc68c]:nth-last-of-type(2){transition-delay:.1s}.filter-hover:hover .filter-hover-item[data-v-f97cc68c]:nth-last-of-type(3){transition-delay:.15s}.filter-hover:hover .filter-hover-item[data-v-f97cc68c]:nth-last-of-type(4){transition-delay:.2s}.filter-hover:hover .filter-hover-item[data-v-f97cc68c]:nth-last-of-type(5){transition-delay:.25s}.filter-hover:hover .filter-hover-item[data-v-f97cc68c]:nth-last-of-type(6){transition-delay:.3s}.filter-hover:hover .filter-hover-item[data-v-f97cc68c]:nth-last-of-type(7){transition-delay:.35s}.filter-hover:hover .filter-hover-item[data-v-f97cc68c],.filter-hover:has(:focus-visible) .filter-hover-item[data-v-f97cc68c]{opacity:1;transform:translateZ(0)}.filter-hover[data-v-f97cc68c]:hover:before,.filter-hover[data-v-f97cc68c]:has(:focus-visible):before{opacity:.9;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px)}.filter-button[data-v-f97cc68c]{top:50%;transform:translateY(-50%)}.context-bar-group:hover .context-bar-group-hover\\:text-c-1[data-v-f97cc68c],.context-bar-group:has(:focus-visible) .context-bar-group-hover\\:text-c-1[data-v-f97cc68c]{--tw-text-opacity:1;color:rgb(var(--scalar-color-1)/var(--tw-text-opacity))}.context-bar-group:hover .context-bar-group-hover\\:hidden[data-v-f97cc68c],.context-bar-group:has(:focus-visible) .context-bar-group-hover\\:hidden[data-v-f97cc68c]{display:none}.schema>span[data-v-f2ab7aa3]:not(:first-child):before{content:"·";margin:0 .5ch;display:block}.schema>span[data-v-f2ab7aa3]{white-space:nowrap;display:flex}[data-v-36811e28] .cm-editor{padding:0}[data-v-36811e28] .cm-content{font-family:var(--scalar-font);font-size:var(--scalar-small);background-color:#0000;align-items:center;width:100%;padding:5px 8px;display:flex}[data-v-36811e28] .cm-content:has(.cm-pill){padding:5px 8px}[data-v-36811e28] .cm-content .cm-pill:not(:last-of-type){margin-right:.5px}[data-v-36811e28] .cm-content .cm-pill:not(:first-of-type){margin-left:.5px}[data-v-36811e28] .cm-line{text-overflow:ellipsis;padding:0;overflow:hidden}.filemask[data-v-36811e28]{-webkit-mask-image:linear-gradient(to right,transparent 0,var(--scalar-background-2)20px);mask-image:linear-gradient(to right,transparent 0,var(--scalar-background-2)20px)}[data-v-3d420065] .cm-content{font-size:var(--scalar-small)}.form-group[data-v-43df1726]{margin-bottom:1rem}.modal-actions[data-v-43df1726]{justify-content:flex-end;gap:1rem;display:flex}.no-scrollbar::-webkit-scrollbar{display:none}.no-scrollbar{-ms-overflow-style:none;scrollbar-width:none}[data-v-3157c3c7] .cm-editor{padding:0}[data-v-3157c3c7] .cm-content{font-family:var(--scalar-font);font-size:var(--scalar-small);background-color:#0000;align-items:center;width:100%;padding:5px 8px;display:flex}[data-v-3157c3c7] .cm-content:has(.cm-pill){padding:5px 8px}[data-v-3157c3c7] .cm-content .cm-pill:not(:last-of-type){margin-right:.5px}[data-v-3157c3c7] .cm-content .cm-pill:not(:first-of-type){margin-left:.5px}[data-v-3157c3c7] .cm-line{text-overflow:ellipsis;word-break:break-word;padding:0;overflow:hidden}.required[data-v-3157c3c7]:after{content:"Required"}input[data-v-3157c3c7]::placeholder{color:var(--scalar-color-3)}.scalar-password-input[data-v-3157c3c7]{text-security:disc;-webkit-text-security:disc;-moz-text-security:disc}.request-section-content[data-v-b1bbc64b]{--scalar-border-width:.5px}.request-section-content-filter[data-v-b1bbc64b]{box-shadow:0 -10px 0 10px var(--scalar-background-1)}.request-item:focus-within .request-meta-buttons[data-v-b1bbc64b]{opacity:1}.group-hover-input[data-v-b1bbc64b]{border-width:var(--scalar-border-width);border-color:#0000}.group:hover .group-hover-input[data-v-b1bbc64b]{background:var(--scalar-background-1)}@supports (color:color-mix(in lab,red,red)){.group:hover .group-hover-input[data-v-b1bbc64b]{background:color-mix(in srgb,var(--scalar-background-1),var(--scalar-background-2))}}.group:hover .group-hover-input[data-v-b1bbc64b]{border-color:var(--scalar-border-color)}.group-hover-input[data-v-b1bbc64b]:focus{border-color:var(--scalar-border-color)!important;background:0 0!important}.light-mode .bg-preview[data-v-c02b5bb8]{background-image:url("data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' width=\'16\' height=\'16\' fill=\'%23000\' fill-opacity=\'10%25\'%3E%3Crect width=\'8\' height=\'8\' /%3E%3Crect x=\'8\' y=\'8\' width=\'8\' height=\'8\' /%3E%3C/svg%3E")}.dark-mode .bg-preview[data-v-c02b5bb8]{background-image:url("data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' width=\'16\' height=\'16\' fill=\'%23FFF\' fill-opacity=\'10%25\'%3E%3Crect width=\'8\' height=\'8\' /%3E%3Crect x=\'8\' y=\'8\' width=\'8\' height=\'8\' /%3E%3C/svg%3E")}[data-v-9c64cd5e] .cm-editor{font-size:var(--scalar-small);background-color:#0000;outline:none}[data-v-9c64cd5e] .cm-gutters{background-color:var(--scalar-background-1);border-radius:var(--scalar-radius)0 0 var(--scalar-radius)}.body-raw[data-v-9c64cd5e] .cm-scroller{min-width:100%;overflow:auto}.scalar-code-block[data-v-8ae4f555] .hljs *{font-size:var(--scalar-small)}.ascii-art-animate .ascii-art-line[data-v-69ebd973]{border-right:1ch solid #0000;animation:4s step-end 1s both typewriter-69ebd973,.5s step-end infinite blinkTextCursor-69ebd973}@keyframes typewriter-69ebd973{0%{width:0}to{width:100%}}@keyframes blinkTextCursor-69ebd973{0%{border-right-color:currentColor}50%{border-right-color:#0000}}.scalar-version-number[data-v-34b57d9d]{width:76px;height:76px;font-size:8px;font-family:var(--scalar-font-code);box-shadow:inset 2px 0 0 2px var(--scalar-background-2);text-align:center;text-transform:initial;-webkit-text-decoration-color:var(--scalar-color-3);text-decoration-color:var(--scalar-color-3);border-radius:9px 9px 16px 12px;flex-direction:column;justify-content:center;align-items:center;margin-top:-113px;margin-left:-36px;line-height:11px;display:flex;position:absolute;transform:skewY(13deg)}.scalar-version-number a[data-v-34b57d9d]{background:var(--scalar-background-2);border:.5px solid var(--scalar-border-color);border-radius:3px;padding:2px 4px;font-weight:700;text-decoration:none}.gitbook-show[data-v-34b57d9d]{display:none}.v-enter-active[data-v-1f35725e]{transition:opacity .5s}.v-enter-from[data-v-1f35725e]{opacity:0}.animate-response-heading .response-heading[data-v-9c1fd1c7]{opacity:1;animation:.2s ease-in-out forwards push-response-9c1fd1c7}@keyframes push-response-9c1fd1c7{0%{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(-4px)}}.animate-response-heading .animate-response-children[data-v-9c1fd1c7]{opacity:0;animation:.2s ease-in-out 50ms forwards response-spans-9c1fd1c7}@keyframes response-spans-9c1fd1c7{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}.request-card[data-v-cbc307e3]{font-size:var(--scalar-font-size-3)}.request-method[data-v-cbc307e3]{font-family:var(--scalar-font-code);text-transform:uppercase;margin-right:6px}.request-card-footer[data-v-cbc307e3]{flex-shrink:0;justify-content:flex-end;padding:6px;display:flex;position:relative}.request-card-footer-addon[data-v-cbc307e3]{flex:1;align-items:center;min-width:0;display:flex}.request-editor-section[data-v-cbc307e3]{flex:1;display:flex}.request-card-simple[data-v-cbc307e3]{font-size:var(--scalar-small);justify-content:space-between;align-items:center;padding:8px 8px 8px 12px;display:flex}.code-snippet[data-v-cbc307e3]{flex-direction:column;width:100%;display:flex}.resizer[data-v-e2c54c18]{cursor:col-resize;z-index:100;border-right:2px solid #0000;width:5px;transition:border-right-color .3s;position:absolute;top:0;bottom:0;right:0}.scalar-dragging{cursor:col-resize}.resizer:hover,.scalar-dragging .resizer{border-right-color:var(--scalar-background-3)}.scalar-dragging:after{content:"";display:block;position:absolute;inset:0}.scroll-timeline-x[data-v-f4568236]{scroll-timeline:--scroll-timeline x;scroll-timeline:--scroll-timeline horizontal;-ms-overflow-style:none;scrollbar-width:none;overflow:auto}.scroll-timeline-x[data-v-f4568236]::-webkit-scrollbar{display:none}.splash-screen[data-v-af32615f]{opacity:0;animation:.5s ease-in-out forwards fadeIn-af32615f}.logo-icon[data-v-af32615f]{opacity:0;animation:.6s ease-in-out .2s forwards fadeInLogo-af32615f,2s ease-in-out .8s infinite pulse-af32615f}@keyframes fadeIn-af32615f{0%{opacity:0}to{opacity:.9}}@keyframes fadeInLogo-af32615f{0%{opacity:0;transform:scale(.9)}to{opacity:.8;transform:scale(1)}}@keyframes pulse-af32615f{0%,to{opacity:.8}50%{opacity:.6}}.commandmenu[data-v-2fe57517]{box-shadow:var(--scalar-shadow-2);border-radius:var(--scalar-radius-lg);background-color:var(--scalar-background-1);opacity:0;width:100%;max-width:580px;max-height:60dvh;margin:12px;animation:.3s ease-in-out .1s forwards fadeincommandmenu-2fe57517;position:fixed;top:150px;left:50%;transform:translate(-50%,10px)}.commandmenu-overlay[data-v-2fe57517]{cursor:pointer;background:#0003;animation:.3s ease-in-out forwards fadeincommand-2fe57517;position:fixed;inset:0}@keyframes fadeincommand-2fe57517{0%{opacity:0}to{opacity:1}}@keyframes fadeincommandmenu-2fe57517{0%{opacity:0;transform:translate(-50%,10px)}to{opacity:1;transform:translate(-50%)}}.empty-sidebar-item-content[data-v-658f060e]{display:none}.empty-sidebar-item .empty-sidebar-item-content[data-v-658f060e]{display:block}.rabbitjump[data-v-658f060e]{opacity:0}.empty-sidebar-item:hover .rabbitjump[data-v-658f060e]{opacity:1;animation:.5s step-end infinite rabbitAnimation-658f060e}.empty-sidebar-item:hover .rabbitsit[data-v-658f060e]{opacity:0;animation:.5s step-end infinite rabbitAnimation2-658f060e}.empty-sidebar-item:hover .rabbit-ascii[data-v-658f060e]{animation:8s linear infinite rabbitRun-658f060e}@keyframes rabbitRun-658f060e{0%{transform:translateZ(0)}25%{transform:translate(250px)}25.01%{transform:translate(-250px)}75%{transform:translate(250px)}75.01%{transform:translate(-250px)}to{transform:translateZ(0)}}@keyframes rabbitAnimation-658f060e{0%,to{opacity:1}50%{opacity:0}}@keyframes rabbitAnimation2-658f060e{0%,to{opacity:0}50%{opacity:1;transform:translateY(-8px)}}.nav-single-tab[data-v-2e741aab]{width:100%;height:100%;color:var(--scalar-color-1);justify-content:center;align-items:center;display:flex;overflow:hidden}.nav-item[data-v-2e741aab]{cursor:pointer;border-radius:var(--scalar-radius-lg);background:var(--scalar-background-3);border:var(--scalar-border-width)solid var(--scalar-background-2);color:var(--scalar-color-3);flex:1;justify-content:center;align-items:center;min-width:0;padding:4.5px 1rem;display:flex;position:relative;overflow:hidden}.dark-mode .nav-item[data-v-2e741aab]{background:var(--scalar-background-2)}@supports (color:color-mix(in lab,red,red)){.dark-mode .nav-item[data-v-2e741aab]{background:color-mix(in srgb,var(--scalar-background-2),transparent)}}.nav-item-icon-copy[data-v-2e741aab]{white-space:nowrap;max-width:100%;-webkit-mask-image:linear-gradient(to left,transparent 0,var(--scalar-background-2)20px);mask-image:linear-gradient(to left,transparent 0,var(--scalar-background-2)20px);overflow:hidden}.nav-item:hover .nav-item-icon-copy[data-v-2e741aab]{-webkit-mask-image:linear-gradient(to left,transparent 20px,var(--scalar-background-2)40px);mask-image:linear-gradient(to left,transparent 20px,var(--scalar-background-2)40px)}.nav-item-copy[data-v-2e741aab]{max-width:calc(100% - 20px)}.nav-item[data-v-2e741aab]:hover{color:var(--scalar-color-1)}.nav-item__active[data-v-2e741aab]{background-color:var(--scalar-background-1);color:var(--scalar-color-1);border-color:var(--scalar-border-color)}.dark-mode .nav-item__active[data-v-2e741aab]{background-color:var(--scalar-background-2)}.nav-item-close[data-v-2e741aab]{border-radius:var(--scalar-radius);stroke-width:1.5px;max-width:20px;color:var(--scalar-color-3);opacity:0;background:0 0;margin-left:-20px;padding:2px;position:absolute;right:3px}.nav-item:hover .nav-item-close[data-v-2e741aab]{opacity:1}.nav-item-close[data-v-2e741aab]:hover{background-color:var(--scalar-background-4)}.nav-item__active .nav-item-close[data-v-2e741aab]:hover{background-color:var(--scalar-background-2)}.download-app-button[data-v-d9bec97b]{box-shadow:0 0 0 .5px var(--scalar-border-color);background:linear-gradient(#ffffffbf,#00000009)}.dark-mode .download-app-button[data-v-d9bec97b]{background:linear-gradient(#ffffff1a,#00000026)}.download-app-button[data-v-d9bec97b]:hover{background:linear-gradient(#00000009,#ffffffbf)}.dark-mode .download-app-button[data-v-d9bec97b]:hover{background:linear-gradient(#00000026,#ffffff1a)}#scalar-client{background-color:var(--scalar-background-2);position:relative}.dark-mode #scalar-client{background-color:var(--scalar-background-1)}@supports (color:color-mix(in lab,red,red)){.dark-mode #scalar-client{background-color:color-mix(in srgb,var(--scalar-background-1)65%,black)}}.scalar-collection-auth[data-v-9d8e1c09]{border:var(--scalar-border-width)solid var(--scalar-border-color);border-radius:var(--scalar-radius-lg);overflow:hidden}[data-v-ddfccc08] .cm-editor{padding:0}[data-v-ddfccc08] .cm-content{font-family:var(--scalar-font);font-size:var(--scalar-small);background-color:#0000;align-items:center;width:100%;padding:5px 8px;display:flex}[data-v-ddfccc08] .cm-content:has(.cm-pill){padding:5px 8px}[data-v-ddfccc08] .cm-content .cm-pill:not(:last-of-type){margin-right:.5px}[data-v-ddfccc08] .cm-content .cm-pill:not(:first-of-type){margin-left:.5px}[data-v-ddfccc08] .cm-line{text-overflow:ellipsis;padding:0;overflow:hidden}[data-v-28c8509c] .cm-editor{padding:0}[data-v-28c8509c] .cm-content{font-family:var(--scalar-font);font-size:var(--scalar-small);background-color:#0000;align-items:center;width:100%;padding:5px 8px;display:flex}[data-v-28c8509c] .cm-content:has(.cm-pill){padding:5px 8px}[data-v-28c8509c] .cm-content .cm-pill:not(:last-of-type){margin-right:.5px}[data-v-28c8509c] .cm-content .cm-pill:not(:first-of-type){margin-left:.5px}[data-v-28c8509c] .cm-line{text-overflow:ellipsis;padding:0;overflow:hidden}[data-v-59dae578] .cm-content{min-height:fit-content}[data-v-59dae578] .cm-scroller{max-width:100%;overflow:auto hidden}.dark-mode .t-editor__callout[data-v-fa58cbab],.light-mode .t-editor__callout[data-v-fa58cbab]{--callout-font-size:var(--scalar-small);--callout-neutral-primary:var(--scalar-color-3);--callout-neutral-secondary:var(--scalar-background-2)}@supports (color:color-mix(in lab,red,red)){.dark-mode .t-editor__callout[data-v-fa58cbab],.light-mode .t-editor__callout[data-v-fa58cbab]{--callout-neutral-secondary:color-mix(in srgb,var(--scalar-background-2),transparent 50%)}}.dark-mode .t-editor__callout[data-v-fa58cbab],.light-mode .t-editor__callout[data-v-fa58cbab]{--callout-neutral-font-color:var(--scalar-color-1);--callout-success-primary:var(--scalar-color-green);--callout-success-secondary:var(--scalar-color-green)}@supports (color:color-mix(in lab,red,red)){.dark-mode .t-editor__callout[data-v-fa58cbab],.light-mode .t-editor__callout[data-v-fa58cbab]{--callout-success-secondary:color-mix(in srgb,var(--scalar-color-green),transparent 97%)}}.dark-mode .t-editor__callout[data-v-fa58cbab],.light-mode .t-editor__callout[data-v-fa58cbab]{--callout-success-font-color:var(--scalar-color-1);--callout-danger-primary:var(--scalar-color-red);--callout-danger-secondary:var(--scalar-color-red)}@supports (color:color-mix(in lab,red,red)){.dark-mode .t-editor__callout[data-v-fa58cbab],.light-mode .t-editor__callout[data-v-fa58cbab]{--callout-danger-secondary:color-mix(in srgb,var(--scalar-color-red),transparent 97%)}}.dark-mode .t-editor__callout[data-v-fa58cbab],.light-mode .t-editor__callout[data-v-fa58cbab]{--callout-danger-font-color:var(--scalar-color-1);--callout-warning-primary:var(--scalar-color-yellow);--callout-warning-secondary:var(--scalar-color-yellow)}@supports (color:color-mix(in lab,red,red)){.dark-mode .t-editor__callout[data-v-fa58cbab],.light-mode .t-editor__callout[data-v-fa58cbab]{--callout-warning-secondary:color-mix(in srgb,var(--scalar-color-yellow),transparent 97%)}}.dark-mode .t-editor__callout[data-v-fa58cbab],.light-mode .t-editor__callout[data-v-fa58cbab]{--callout-warning-font-color:var(--scalar-color-1);--callout-info-primary:var(--scalar-color-blue);--callout-info-secondary:var(--scalar-color-blue)}@supports (color:color-mix(in lab,red,red)){.dark-mode .t-editor__callout[data-v-fa58cbab],.light-mode .t-editor__callout[data-v-fa58cbab]{--callout-info-secondary:color-mix(in srgb,var(--scalar-color-blue),transparent 97%)}}.dark-mode .t-editor__callout[data-v-fa58cbab],.light-mode .t-editor__callout[data-v-fa58cbab]{--callout-info-font-color:var(--scalar-color-1);--callout-line-height:22px}.t-editor__callout[data-v-fa58cbab]{border-radius:var(--scalar-radius);margin-top:var(--scalar-block-spacing);--callout-primary:var(--scalar-border-color);--callout-secondary:var(--scalar-background-2);--callout-svg:var(--callout-primary);background:var(--callout-secondary);border:var(--scalar-border-width)solid var(--callout-primary);padding:10px 14px}@supports (color:color-mix(in lab,red,red)){.t-editor__callout[data-v-fa58cbab]{border:var(--scalar-border-width)solid color-mix(in srgb,var(--callout-primary),transparent 50%)}}.t-editor__callout .callout-content__text[data-v-fa58cbab]{font-size:var(--callout-font-size);line-height:var(--callout-line-height);flex:1}.t-editor__callout .callout-content__icon[data-v-fa58cbab]{border-radius:var(--scalar-radius);width:18px;height:fit-content;color:var(--callout-svg);justify-content:center;align-items:center;display:flex;position:relative}.t-editor__callout .callout-content__icon svg[data-v-fa58cbab],.t-editor__callout .callout-content__icon img[data-v-fa58cbab]{width:18px;height:18px}.t-editor__callout .callout-content__icon[data-v-fa58cbab]:before{content:"​";line-height:var(--callout-line-height)}.t-editor__callout.callout__neutral[data-v-fa58cbab]{--callout-primary:var(--callout-neutral-primary);--callout-secondary:var(--callout-neutral-secondary);--callout-font-color:var(--callout-neutral-font-color);--callout-svg:var(--callout-neutral-font-color)}.t-editor__callout.callout__info[data-v-fa58cbab]{--callout-primary:var(--callout-info-primary);--callout-secondary:var(--callout-info-secondary);--callout-font-color:var(--callout-info-font-color);--callout-svg:var(--callout-info-primary)}.t-editor__callout.callout__warning[data-v-fa58cbab]{--callout-primary:var(--callout-warning-primary);--callout-secondary:var(--callout-warning-secondary);--callout-font-color:var(--callout-warning-font-color);--callout-svg:var(--callout-warning-primary)}.t-editor__callout.callout__success[data-v-fa58cbab]{--callout-primary:var(--callout-success-primary);--callout-secondary:var(--callout-success-secondary);--callout-font-color:var(--callout-success-font-color);--callout-svg:var(--callout-success-primary)}.t-editor__callout.callout__danger[data-v-fa58cbab]{--callout-primary:var(--callout-danger-primary);--callout-secondary:var(--callout-danger-secondary);--callout-font-color:var(--callout-danger-font-color);--callout-svg:var(--callout-danger-primary)}.group-hover-input[data-v-5a23cb87]{border-width:var(--scalar-border-width);border-color:#0000}.group:hover .group-hover-input[data-v-5a23cb87]{background:var(--scalar-background-1)}@supports (color:color-mix(in lab,red,red)){.group:hover .group-hover-input[data-v-5a23cb87]{background:color-mix(in srgb,var(--scalar-background-1),var(--scalar-background-2))}}.group:hover .group-hover-input[data-v-5a23cb87]{border-color:var(--scalar-border-color)}.group-hover-input[data-v-5a23cb87]:focus{border-color:var(--scalar-border-color)!important;background:0 0!important}.schema>span[data-v-4df72868]:not(:first-child):before{content:"·";margin:0 .5ch;display:block}.schema>span[data-v-4df72868]{white-space:nowrap;display:flex}[data-v-04661eb4] .cm-editor{padding:0}[data-v-04661eb4] .cm-content{font-family:var(--scalar-font);font-size:var(--scalar-small);background-color:#0000;align-items:center;width:100%;padding:5px 8px;display:flex}[data-v-04661eb4] .cm-content:has(.cm-pill){padding:5px 8px}[data-v-04661eb4] .cm-content .cm-pill:not(:last-of-type){margin-right:.5px}[data-v-04661eb4] .cm-content .cm-pill:not(:first-of-type){margin-left:.5px}[data-v-04661eb4] .cm-line{text-overflow:ellipsis;padding:0;overflow:hidden}.filemask[data-v-04661eb4]{-webkit-mask-image:linear-gradient(to right,transparent 0,var(--scalar-background-2)20px);mask-image:linear-gradient(to right,transparent 0,var(--scalar-background-2)20px)}[data-v-9aa4b63a] .cm-content{font-size:var(--scalar-small)}.auth-combobox-position[data-v-0bb98074]{margin-left:120px}.scroll-timeline-x[data-v-0bb98074]{scroll-timeline:--scroll-timeline x;scroll-timeline:--scroll-timeline horizontal;scrollbar-width:none;-ms-overflow-style:none;overflow:auto}.fade-left[data-v-0bb98074],.fade-right[data-v-0bb98074]{content:"";pointer-events:none;height:100%;min-height:24px;animation-name:fadein-0bb98074;animation-duration:1ms;animation-direction:reverse;animation-timeline:--scroll-timeline;position:sticky}.fade-left[data-v-0bb98074]{background:linear-gradient(-90deg,var(--scalar-background-1)0%,var(--scalar-background-1)60%,var(--scalar-background-1)100%)}@supports (color:color-mix(in lab,red,red)){.fade-left[data-v-0bb98074]{background:linear-gradient(-90deg,color-mix(in srgb,var(--scalar-background-1),transparent 100%)0%,color-mix(in srgb,var(--scalar-background-1),transparent 20%)60%,var(--scalar-background-1)100%)}}.fade-left[data-v-0bb98074]{min-width:3px;animation-direction:normal;left:-1px}.fade-right[data-v-0bb98074]{background:linear-gradient(90deg,var(--scalar-background-1)0%,var(--scalar-background-1)60%,var(--scalar-background-1)100%)}@supports (color:color-mix(in lab,red,red)){.fade-right[data-v-0bb98074]{background:linear-gradient(90deg,color-mix(in srgb,var(--scalar-background-1),transparent 100%)0%,color-mix(in srgb,var(--scalar-background-1),transparent 20%)60%,var(--scalar-background-1)100%)}}.fade-right[data-v-0bb98074]{min-width:24px;margin-left:-20px;top:0;right:-1px}@keyframes fadein-0bb98074{0%{opacity:0}15%{opacity:1}}.auth-combobox-position[data-v-3f1067a4]{margin-left:120px}.scroll-timeline-x[data-v-3f1067a4]{scroll-timeline:--scroll-timeline x;scroll-timeline:--scroll-timeline horizontal;scrollbar-width:none;-ms-overflow-style:none;overflow:auto}.fade-left[data-v-3f1067a4],.fade-right[data-v-3f1067a4]{content:"";pointer-events:none;height:100%;min-height:24px;animation-name:fadein-3f1067a4;animation-duration:1ms;animation-direction:reverse;animation-timeline:--scroll-timeline;position:sticky}.fade-left[data-v-3f1067a4]{background:linear-gradient(-90deg,var(--scalar-background-1)0%,var(--scalar-background-1)60%,var(--scalar-background-1)100%)}@supports (color:color-mix(in lab,red,red)){.fade-left[data-v-3f1067a4]{background:linear-gradient(-90deg,color-mix(in srgb,var(--scalar-background-1),transparent 100%)0%,color-mix(in srgb,var(--scalar-background-1),transparent 20%)60%,var(--scalar-background-1)100%)}}.fade-left[data-v-3f1067a4]{min-width:3px;animation-direction:normal;left:-1px}.fade-right[data-v-3f1067a4]{background:linear-gradient(90deg,var(--scalar-background-1)0%,var(--scalar-background-1)60%,var(--scalar-background-1)100%)}@supports (color:color-mix(in lab,red,red)){.fade-right[data-v-3f1067a4]{background:linear-gradient(90deg,color-mix(in srgb,var(--scalar-background-1),transparent 100%)0%,color-mix(in srgb,var(--scalar-background-1),transparent 20%)60%,var(--scalar-background-1)100%)}}.fade-right[data-v-3f1067a4]{min-width:24px;margin-left:-20px;top:0;right:-1px}@keyframes fadein-3f1067a4{0%{opacity:0}15%{opacity:1}}[data-v-2891f052] code.hljs *{font-size:var(--scalar-small)}.request-section-content[data-v-287f5ecf]{--scalar-border-width:.5px}.request-section-content-filter[data-v-287f5ecf]{box-shadow:0 -10px 0 10px var(--scalar-background-1)}.request-item:focus-within .request-meta-buttons[data-v-287f5ecf]{opacity:1}.group-hover-input[data-v-287f5ecf]{border-width:var(--scalar-border-width);border-color:#0000}.group:hover .group-hover-input[data-v-287f5ecf]{background:var(--scalar-background-1)}@supports (color:color-mix(in lab,red,red)){.group:hover .group-hover-input[data-v-287f5ecf]{background:color-mix(in srgb,var(--scalar-background-1),var(--scalar-background-2))}}.group:hover .group-hover-input[data-v-287f5ecf]{border-color:var(--scalar-border-color)}.group-hover-input[data-v-287f5ecf]:focus{border-color:var(--scalar-border-color)!important;background:0 0!important}.light-mode .bg-preview[data-v-0956ad2d]{background-image:url("data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' width=\'16\' height=\'16\' fill=\'%23000\' fill-opacity=\'10%25\'%3E%3Crect width=\'8\' height=\'8\' /%3E%3Crect x=\'8\' y=\'8\' width=\'8\' height=\'8\' /%3E%3C/svg%3E")}.dark-mode .bg-preview[data-v-0956ad2d]{background-image:url("data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' width=\'16\' height=\'16\' fill=\'%23FFF\' fill-opacity=\'10%25\'%3E%3Crect width=\'8\' height=\'8\' /%3E%3Crect x=\'8\' y=\'8\' width=\'8\' height=\'8\' /%3E%3C/svg%3E")}[data-v-1399120c] .cm-editor{font-size:var(--scalar-small);background-color:#0000;outline:none}[data-v-1399120c] .cm-gutters{background-color:var(--scalar-background-1);border-radius:var(--scalar-radius)0 0 var(--scalar-radius)}.body-raw[data-v-1399120c] .cm-scroller{min-width:100%;overflow:auto}.scalar-code-block[data-v-17966bf4] .hljs *{font-size:var(--scalar-small)}.response-body-virtual[data-headlessui-state=open],.response-body-virtual[data-headlessui-state=open] .diclosure-panel{flex-direction:column;flex-grow:1;display:flex}.keycap-n[data-v-b1211b87]{background:-webkit-linear-gradient(5deg,transparent 30%,var(--scalar-color-3)50%);-webkit-text-fill-color:transparent;-webkit-background-clip:text;background-clip:text}.keycap-hotkey[data-v-b1211b87]{line-height:26px;position:absolute;top:32px}.scalar-version-number[data-v-6d2bdb61]{width:76px;height:76px;font-size:8px;font-family:var(--scalar-font-code);box-shadow:inset 2px 0 0 2px var(--scalar-background-2);text-align:center;text-transform:initial;-webkit-text-decoration-color:var(--scalar-color-3);text-decoration-color:var(--scalar-color-3);border-radius:9px 9px 16px 12px;flex-direction:column;justify-content:center;align-items:center;margin-top:-113px;margin-left:-36px;line-height:11px;display:flex;position:absolute;transform:skewY(13deg)}.scalar-version-number a[data-v-6d2bdb61]{background:var(--scalar-background-2);border:.5px solid var(--scalar-border-color);border-radius:3px;padding:2px 4px;font-weight:700;text-decoration:none}.gitbook-show[data-v-6d2bdb61]{display:none}.v-enter-active[data-v-7ec8af01]{transition:opacity .5s}.v-enter-from[data-v-7ec8af01]{opacity:0}.animate-response-heading .response-heading[data-v-6e4eec82]{opacity:1;animation:.2s ease-in-out forwards push-response-6e4eec82}@keyframes push-response-6e4eec82{0%{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(-4px)}}.animate-response-heading .animate-response-children[data-v-6e4eec82]{opacity:0;animation:.2s ease-in-out 50ms forwards response-spans-6e4eec82}@keyframes response-spans-6e4eec82{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}.ellipsis-position[data-v-01a1ab71]{transform:translate(calc(-100% - 4.5px))}.indent-border-line-offset[data-v-24d396a0]:before{left:var(--v337cd30d)}.indent-padding-left[data-v-24d396a0]{padding-left:calc(var(--v1d572b91) + 6px)}.sidebar-folderitem[data-v-24d396a0] .ellipsis-position{right:6px;transform:none}.search-button-fade[data-v-23d35bb5]{background:linear-gradient(var(--scalar-background-1)32px,var(--scalar-background-1)38px,transparent)}@supports (color:color-mix(in lab,red,red)){.search-button-fade[data-v-23d35bb5]{background:linear-gradient(var(--scalar-background-1)32px,color-mix(in srgb,var(--scalar-background-1),transparent)38px,transparent)}}.empty-sidebar-item-content[data-v-23d35bb5]{display:none}.empty-sidebar-item .empty-sidebar-item-content[data-v-23d35bb5]{display:block}.rabbitjump[data-v-23d35bb5]{opacity:0}.empty-sidebar-item:hover .rabbitjump[data-v-23d35bb5]{opacity:1;animation:.5s step-end infinite rabbitAnimation-23d35bb5}.empty-sidebar-item:hover .rabbitsit[data-v-23d35bb5]{opacity:0;animation:.5s step-end infinite rabbitAnimation2-23d35bb5}.empty-sidebar-item:hover .rabbit-ascii[data-v-23d35bb5]{animation:8s linear infinite rabbitRun-23d35bb5}@keyframes rabbitRun-23d35bb5{0%{transform:translateZ(0)}25%{transform:translate(250px)}25.01%{transform:translate(-250px)}75%{transform:translate(250px)}75.01%{transform:translate(-250px)}to{transform:translateZ(0)}}@keyframes rabbitAnimation-23d35bb5{0%,to{opacity:1}50%{opacity:0}}@keyframes rabbitAnimation2-23d35bb5{0%,to{opacity:0}50%{opacity:1;transform:translateY(-8px)}}.request-text-color-text[data-v-02af05d1]{color:var(--scalar-color-1);background:linear-gradient(var(--scalar-background-1),var(--scalar-background-3));box-shadow:0 0 0 1px var(--scalar-border-color)}@media screen and (max-width:800px){.sidebar-active-hide-layout[data-v-02af05d1]{display:none}.sidebar-active-width[data-v-02af05d1]{width:100%}}.gitbook-show[data-v-c8df97c6]{display:none}.app-exit-button[data-v-c8df97c6]{color:#fff;background:#0000001a}.app-exit-button[data-v-c8df97c6]:hover{background:#ffffff1a}.request-text-color-text[data-v-57ae0d10]{color:var(--scalar-color-1);background:linear-gradient(var(--scalar-background-1),var(--scalar-background-3));box-shadow:0 0 0 1px var(--scalar-border-color)}@media screen and (max-width:800px){.sidebar-active-hide-layout[data-v-57ae0d10]{display:none}.sidebar-active-width[data-v-57ae0d10]{width:100%}}.group-hover-input[data-v-fced736a]{border-width:var(--scalar-border-width);border-color:#0000}.group:hover .group-hover-input[data-v-fced736a]{background:var(--scalar-background-1)}@supports (color:color-mix(in lab,red,red)){.group:hover .group-hover-input[data-v-fced736a]{background:color-mix(in srgb,var(--scalar-background-1),var(--scalar-background-2))}}.group:hover .group-hover-input[data-v-fced736a]{border-color:var(--scalar-border-color)}.group-hover-input[data-v-fced736a]:focus{border-color:var(--scalar-border-color)!important;background:0 0!important}[data-v-68d5218e] .markdown h2{font-size:var(--scalar-font-size-2)}[data-v-5997a667] .cm-content{min-height:fit-content}[data-v-5997a667] .cm-scroller{max-width:100%;overflow:auto hidden}[data-v-83bfcc8a] .cm-editor{padding:0}[data-v-83bfcc8a] .cm-content{font-family:var(--scalar-font);font-size:var(--scalar-small);background-color:#0000;align-items:center;width:100%;padding:5px 8px;display:flex}[data-v-83bfcc8a] .cm-content:has(.cm-pill){padding:5px 8px}[data-v-83bfcc8a] .cm-content .cm-pill:not(:last-of-type){margin-right:.5px}[data-v-83bfcc8a] .cm-content .cm-pill:not(:first-of-type){margin-left:.5px}[data-v-83bfcc8a] .cm-line{text-overflow:ellipsis;padding:0;overflow:hidden}.scalar-collection-auth[data-v-cc87292e]{border:var(--scalar-border-width)solid var(--scalar-border-color);border-radius:var(--scalar-radius-lg);overflow:hidden}.dragover-asChild[data-v-a89d6a6e],.dragover-above[data-v-a89d6a6e],.dragover-below[data-v-a89d6a6e]{position:relative}.dragover-above[data-v-a89d6a6e]:after,.dragover-below[data-v-a89d6a6e]:after{content:"";background:var(--scalar-color-blue);width:100%;height:3px;display:block;position:absolute;top:-1.5px}@supports (color:color-mix(in lab,red,red)){.dragover-above[data-v-a89d6a6e]:after,.dragover-below[data-v-a89d6a6e]:after{background:color-mix(in srgb,var(--scalar-color-blue),transparent 85%)}}.dragover-above[data-v-a89d6a6e]:after,.dragover-below[data-v-a89d6a6e]:after{pointer-events:none;border-radius:var(--scalar-radius)}.dragover-below[data-v-a89d6a6e]:after{top:initial;bottom:-1.5px}.dragover-asChild[data-v-a89d6a6e]:after{content:"";background:var(--scalar-color-blue);width:100%;height:100%;display:block;position:absolute;top:0;left:0}@supports (color:color-mix(in lab,red,red)){.dragover-asChild[data-v-a89d6a6e]:after{background:color-mix(in srgb,var(--scalar-color-blue),transparent 85%)}}.dragover-asChild[data-v-a89d6a6e]:after{pointer-events:none;border-radius:var(--scalar-radius)}.empty-variable-name[data-v-298ba76d]:empty:before{content:"Untitled";color:var(--scalar-color-3)}.form-group[data-v-694018d6]{margin-bottom:1rem}.modal-actions[data-v-694018d6]{justify-content:flex-end;gap:1rem;display:flex}:root{--scalar-loaded-api-reference:true}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-gradient-position{syntax:"*";inherits:false}@property --tw-gradient-from{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-via{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-to{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-stops{syntax:"*";inherits:false}@property --tw-gradient-via-stops{syntax:"*";inherits:false}@property --tw-gradient-from-position{syntax:"";inherits:false;initial-value:0%}@property --tw-gradient-via-position{syntax:"";inherits:false;initial-value:50%}@property --tw-gradient-to-position{syntax:"";inherits:false;initial-value:100%}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@layer scalar-config{.scalar-api-reference[data-v-2a5231c6]{--refs-header-height: calc( var(--scalar-custom-header-height, 0px) + var(--scalar-header-height, 0px) );--refs-viewport-offset: calc( var(--refs-header-height, 0px) + var(--refs-content-offset, 0px) );--refs-viewport-height: calc( var(--full-height, 100dvh) - var(--refs-viewport-offset, 0px) );--refs-sidebar-width: var(--scalar-sidebar-width, 0px);--refs-sidebar-height: calc( var(--full-height, 100dvh) - var(--refs-header-height, 0px) );--refs-content-max-width: var(--scalar-content-max-width, 1540px)}.scalar-api-reference.references-classic[data-v-2a5231c6]{--refs-content-max-width: var(--scalar-content-max-width, 1420px);min-height:100dvh;--refs-sidebar-width: 0}}.t-doc__sidebar[data-v-2a5231c6]{z-index:10}.references-layout[data-v-2a5231c6]{min-height:100dvh;min-width:100%;max-width:100%;flex:1;--full-height: 100dvh;display:grid;grid-template-rows:var(--scalar-header-height, 0px) repeat(2,auto);grid-template-columns:auto 1fr;grid-template-areas:"header header" "navigation rendered" "footer footer";background:var(--scalar-background-1)}.references-editor[data-v-2a5231c6]{grid-area:editor;display:flex;min-width:0;background:var(--scalar-background-1)}.references-rendered[data-v-2a5231c6]{position:relative;grid-area:rendered;min-width:0;background:var(--scalar-background-1)}.scalar-api-reference.references-classic[data-v-2a5231c6],.references-classic .references-rendered[data-v-2a5231c6]{height:initial!important;max-height:initial!important}@layer scalar-config{.references-sidebar[data-v-2a5231c6]{--refs-sidebar-width: var(--scalar-sidebar-width, 288px)}}.references-footer[data-v-2a5231c6]{grid-area:footer}@media(max-width:1000px){.references-layout[data-v-2a5231c6]{--refs-sidebar-height: calc( var(--full-height, 100dvh) - var(--scalar-custom-header-height, 0px) );grid-template-columns:100%;grid-template-rows:var(--scalar-header-height, 0px) 0px auto auto;grid-template-areas:"header" "navigation" "rendered" "footer"}.references-editable[data-v-2a5231c6]{grid-template-areas:"header" "navigation" "editor"}.references-rendered[data-v-2a5231c6]{position:static}}@media(max-width:1000px){.scalar-api-references-standalone-mobile[data-v-2a5231c6]:not(.references-classic){--scalar-header-height: 50px}}.darklight-reference[data-v-2a5231c6]{width:100%;margin-top:auto}')),document.head.appendChild(a)}}catch(r){console.error("vite-plugin-css-injected-by-js",r)}}(); +!function(e){"function"==typeof define&&define.amd?define(e):e()}((function(){ +"use strict";const e=Object.freeze({status:"aborted"});function t(e,t,n){ +function r(n,r){if(n._zod||Object.defineProperty(n,"_zod",{value:{def:r, +constr:i,traits:new Set},enumerable:!1}),n._zod.traits.has(e))return +;n._zod.traits.add(e),t(n,r);const a=i.prototype,o=Object.keys(a) +;for(let e=0;e!!(n?.Parent&&t instanceof n.Parent)||t?._zod?.traits?.has(e) +}),Object.defineProperty(i,"name",{value:e}),i}const n=Symbol("zod_brand") +;class r extends Error{constructor(){ +super("Encountered Promise during synchronous parse. Use .parseAsync() instead.") +}}class a extends Error{constructor(e){ +super(`Encountered unidirectional transform during encode: ${e}`), +this.name="ZodEncodeError"}}const o={};function i(e){ +return e&&Object.assign(o,e),o}function s(e){ +const t=Object.values(e).filter((e=>"number"==typeof e)) +;return Object.entries(e).filter((([e,n])=>-1===t.indexOf(+e))).map((([e,t])=>t)) +}function l(e,t="|"){return e.map((e=>$(e))).join(t)}function c(e,t){ +return"bigint"==typeof t?t.toString():t}function u(e){return{get value(){{ +const t=e();return Object.defineProperty(this,"value",{value:t}),t}}}} +function d(e){return null==e}function p(e){ +const t=e.startsWith("^")?1:0,n=e.endsWith("$")?e.length-1:e.length +;return e.slice(t,n)}function h(e,t){ +const n=(e.toString().split(".")[1]||"").length,r=t.toString() +;let a=(r.split(".")[1]||"").length;if(0===a&&/\d?e-\d?/.test(r)){ +const e=r.match(/\d?e-(\d?)/);e?.[1]&&(a=Number.parseInt(e[1]))}const o=n>a?n:a +;return Number.parseInt(e.toFixed(o).replace(".",""))%Number.parseInt(t.toFixed(o).replace(".",""))/10**o +}const f=Symbol("evaluating");function m(e,t,n){let r +;Object.defineProperty(e,t,{get(){if(r!==f)return void 0===r&&(r=f,r=n()),r}, +set(n){Object.defineProperty(e,t,{value:n})},configurable:!0})} +function g(e,t,n){Object.defineProperty(e,t,{value:n,writable:!0,enumerable:!0, +configurable:!0})}function v(...e){const t={};for(const n of e){ +const e=Object.getOwnPropertyDescriptors(n);Object.assign(t,e)} +return Object.defineProperties({},t)}function b(e){return JSON.stringify(e)} +function y(e){ +return e.toLowerCase().trim().replace(/[^\w\s-]/g,"").replace(/[\s_-]+/g,"-").replace(/^-+|-+$/g,"") +}const O="captureStackTrace"in Error?Error.captureStackTrace:(...e)=>{} +;function w(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)} +const x=u((()=>{ +if("undefined"!=typeof navigator&&navigator?.userAgent?.includes("Cloudflare"))return!1 +;try{return new Function(""),!0}catch(e){return!1}}));function k(e){ +if(!1===w(e))return!1;const t=e.constructor;if(void 0===t)return!0 +;if("function"!=typeof t)return!0;const n=t.prototype +;return!1!==w(n)&&!1!==Object.prototype.hasOwnProperty.call(n,"isPrototypeOf")} +function S(e){return k(e)?{...e}:Array.isArray(e)?[...e]:e} +const _=new Set(["string","number","symbol"]),A=new Set(["string","number","bigint","boolean","symbol","undefined"]) +;function T(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function E(e,t,n){ +const r=new e._zod.constr(t??e._zod.def) +;return t&&!n?.parent||(r._zod.parent=e),r}function C(e){const t=e +;if(!t)return{};if("string"==typeof t)return{error:()=>t} +;if(void 0!==t?.message){ +if(void 0!==t?.error)throw new Error("Cannot specify both `message` and `error` params") +;t.error=t.message}return delete t.message,"string"==typeof t.error?{...t, +error:()=>t.error}:t}function $(e){ +return"bigint"==typeof e?e.toString()+"n":"string"==typeof e?`"${e}"`:`${e}`} +function P(e){ +return Object.keys(e).filter((t=>"optional"===e[t]._zod.optin&&"optional"===e[t]._zod.optout)) +}const I={safeint:[Number.MIN_SAFE_INTEGER,Number.MAX_SAFE_INTEGER], +int32:[-2147483648,2147483647],uint32:[0,4294967295], +float32:[-34028234663852886e22,34028234663852886e22], +float64:[-Number.MAX_VALUE,Number.MAX_VALUE]},D={ +int64:[BigInt("-9223372036854775808"),BigInt("9223372036854775807")], +uint64:[BigInt(0),BigInt("18446744073709551615")]};function M(e,t){ +const n=e._zod.def,r=n.checks +;if(r&&r.length>0)throw new Error(".pick() cannot be used on object schemas containing refinements") +;return E(e,v(e._zod.def,{get shape(){const e={};for(const r in t){ +if(!(r in n.shape))throw new Error(`Unrecognized key: "${r}"`) +;t[r]&&(e[r]=n.shape[r])}return g(this,"shape",e),e},checks:[]}))} +function N(e,t){const n=e._zod.def,r=n.checks +;if(r&&r.length>0)throw new Error(".omit() cannot be used on object schemas containing refinements") +;const a=v(e._zod.def,{get shape(){const r={...e._zod.def.shape} +;for(const e in t){ +if(!(e in n.shape))throw new Error(`Unrecognized key: "${e}"`);t[e]&&delete r[e] +}return g(this,"shape",r),r},checks:[]});return E(e,a)}function R(e,t){ +if(!k(t))throw new Error("Invalid input to extend: expected a plain object") +;const n=e._zod.def.checks;if(n&&n.length>0){const n=e._zod.def.shape +;for(const e in t)if(void 0!==Object.getOwnPropertyDescriptor(n,e))throw new Error("Cannot overwrite keys on object schemas containing refinements. Use `.safeExtend()` instead.") +}const r=v(e._zod.def,{get shape(){const n={...e._zod.def.shape,...t} +;return g(this,"shape",n),n}});return E(e,r)}function L(e,t){ +if(!k(t))throw new Error("Invalid input to safeExtend: expected a plain object") +;const n=v(e._zod.def,{get shape(){const n={...e._zod.def.shape,...t} +;return g(this,"shape",n),n}});return E(e,n)}function B(e,t){ +const n=v(e._zod.def,{get shape(){const n={...e._zod.def.shape, +...t._zod.def.shape};return g(this,"shape",n),n},get catchall(){ +return t._zod.def.catchall},checks:[]});return E(e,n)}function j(e,t,n){ +const r=t._zod.def.checks +;if(r&&r.length>0)throw new Error(".partial() cannot be used on object schemas containing refinements") +;const a=v(t._zod.def,{get shape(){const r=t._zod.def.shape,a={...r} +;if(n)for(const t in n){if(!(t in r))throw new Error(`Unrecognized key: "${t}"`) +;n[t]&&(a[t]=e?new e({type:"optional",innerType:r[t]}):r[t]) +}else for(const t in r)a[t]=e?new e({type:"optional",innerType:r[t]}):r[t] +;return g(this,"shape",a),a},checks:[]});return E(t,a)}function U(e,t,n){ +const r=v(t._zod.def,{get shape(){const r=t._zod.def.shape,a={...r} +;if(n)for(const t in n){if(!(t in a))throw new Error(`Unrecognized key: "${t}"`) +;n[t]&&(a[t]=new e({type:"nonoptional",innerType:r[t]})) +}else for(const t in r)a[t]=new e({type:"nonoptional",innerType:r[t]}) +;return g(this,"shape",a),a}});return E(t,r)}function z(e,t=0){ +if(!0===e.aborted)return!0 +;for(let n=t;n{var n;return(n=t).path??(n.path=[]), +t.path.unshift(e),t}))}function F(e){return"string"==typeof e?e:e?.message} +function H(e,t,n){const r={...e,path:e.path??[]};if(!e.message){ +const a=F(e.inst?._zod.def?.error?.(e))??F(t?.error?.(e))??F(n.customError?.(e))??F(n.localeError?.(e))??"Invalid input" +;r.message=a} +return delete r.inst,delete r.continue,t?.reportInput||delete r.input,r} +function Q(e){ +return e instanceof Set?"set":e instanceof Map?"map":e instanceof File?"file":"unknown" +}function V(e){ +return Array.isArray(e)?"array":"string"==typeof e?"string":"unknown"} +function q(e){const t=typeof e;switch(t){case"number": +return Number.isNaN(e)?"nan":"number";case"object":{if(null===e)return"null" +;if(Array.isArray(e))return"array";const t=e +;if(t&&Object.getPrototypeOf(t)!==Object.prototype&&"constructor"in t&&t.constructor)return t.constructor.name +}}return t}function W(...e){const[t,n,r]=e;return"string"==typeof t?{message:t, +code:"custom",input:n,inst:r}:{...t}}function X(e){ +const t=atob(e),n=new Uint8Array(t.length) +;for(let r=0;re[t]));return Promise.all(n).then((e=>{ +const n={};for(let r=0;re.toString(16).padStart(2,"0"))).join("")}, +unwrapMessage:F},Symbol.toStringTag,{value:"Module"})),K=(e,t)=>{ +e.name="$ZodError",Object.defineProperty(e,"_zod",{value:e._zod,enumerable:!1}), +Object.defineProperty(e,"issues",{value:t,enumerable:!1 +}),e.message=JSON.stringify(t,c,2),Object.defineProperty(e,"toString",{ +value:()=>e.message,enumerable:!1})},J=t("$ZodError",K),ee=t("$ZodError",K,{ +Parent:Error});function te(e,t=e=>e.message){const n={},r=[] +;for(const a of e.issues)a.path.length>0?(n[a.path[0]]=n[a.path[0]]||[], +n[a.path[0]].push(t(a))):r.push(t(a));return{formErrors:r,fieldErrors:n}} +function ne(e,t=e=>e.message){const n={_errors:[]},r=e=>{ +for(const a of e.issues)if("invalid_union"===a.code&&a.errors.length)a.errors.map((e=>r({ +issues:e})));else if("invalid_key"===a.code)r({issues:a.issues +});else if("invalid_element"===a.code)r({issues:a.issues +});else if(0===a.path.length)n._errors.push(t(a));else{let e=n,r=0 +;for(;re.message){const n={errors:[]},r=(e,a=[])=>{ +var o,i +;for(const s of e.issues)if("invalid_union"===s.code&&s.errors.length)s.errors.map((e=>r({ +issues:e},s.path)));else if("invalid_key"===s.code)r({issues:s.issues +},s.path);else if("invalid_element"===s.code)r({issues:s.issues},s.path);else{ +const e=[...a,...s.path];if(0===e.length){n.errors.push(t(s));continue} +let r=n,l=0;for(;l"object"==typeof e?e.key:e)) +;for(const r of n)"number"==typeof r?t.push(`[${r}]`):"symbol"==typeof r?t.push(`[${JSON.stringify(String(r))}]`):/[^\w$]/.test(r)?t.push(`[${JSON.stringify(r)}]`):(t.length&&t.push("."), +t.push(r));return t.join("")}function oe(e){ +const t=[],n=[...e.issues].sort(((e,t)=>(e.path??[]).length-(t.path??[]).length)) +;for(const r of n)t.push(`✖ ${r.message}`), +r.path?.length&&t.push(` → at ${ae(r.path)}`);return t.join("\n")} +const ie=e=>(t,n,a,o)=>{const s=a?Object.assign(a,{async:!1}):{async:!1 +},l=t._zod.run({value:n,issues:[]},s);if(l instanceof Promise)throw new r +;if(l.issues.length){const t=new(o?.Err??e)(l.issues.map((e=>H(e,s,i())))) +;throw O(t,o?.callee),t}return l.value},se=ie(ee),le=e=>async(t,n,r,a)=>{ +const o=r?Object.assign(r,{async:!0}):{async:!0};let s=t._zod.run({value:n, +issues:[]},o);if(s instanceof Promise&&(s=await s),s.issues.length){ +const t=new(a?.Err??e)(s.issues.map((e=>H(e,o,i()))));throw O(t,a?.callee),t} +return s.value},ce=le(ee),ue=e=>(t,n,a)=>{const o=a?{...a,async:!1}:{async:!1 +},s=t._zod.run({value:n,issues:[]},o);if(s instanceof Promise)throw new r +;return s.issues.length?{success:!1, +error:new(e??J)(s.issues.map((e=>H(e,o,i()))))}:{success:!0,data:s.value} +},de=ue(ee),pe=e=>async(t,n,r)=>{const a=r?Object.assign(r,{async:!0}):{async:!0 +};let o=t._zod.run({value:n,issues:[]},a) +;return o instanceof Promise&&(o=await o),o.issues.length?{success:!1, +error:new e(o.issues.map((e=>H(e,a,i()))))}:{success:!0,data:o.value} +},he=pe(ee),fe=e=>(t,n,r)=>{const a=r?Object.assign(r,{direction:"backward"}):{ +direction:"backward"};return ie(e)(t,n,a) +},me=fe(ee),ge=e=>(t,n,r)=>ie(e)(t,n,r),ve=ge(ee),be=e=>async(t,n,r)=>{ +const a=r?Object.assign(r,{direction:"backward"}):{direction:"backward"} +;return le(e)(t,n,a) +},ye=be(ee),Oe=e=>async(t,n,r)=>le(e)(t,n,r),we=Oe(ee),xe=e=>(t,n,r)=>{ +const a=r?Object.assign(r,{direction:"backward"}):{direction:"backward"} +;return ue(e)(t,n,a) +},ke=xe(ee),Se=e=>(t,n,r)=>ue(e)(t,n,r),_e=Se(ee),Ae=e=>async(t,n,r)=>{ +const a=r?Object.assign(r,{direction:"backward"}):{direction:"backward"} +;return pe(e)(t,n,a) +},Te=Ae(ee),Ee=e=>async(t,n,r)=>pe(e)(t,n,r),Ce=Ee(ee),$e=/^[cC][^\s-]{8,}$/,Pe=/^[0-9a-z]+$/,Ie=/^[0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{26}$/,De=/^[0-9a-vA-V]{20}$/,Me=/^[A-Za-z0-9]{27}$/,Ne=/^[a-zA-Z0-9_-]{21}$/,Re=/^P(?:(\d+W)|(?!.*W)(?=\d|T\d)(\d+Y)?(\d+M)?(\d+D)?(T(?=\d)(\d+H)?(\d+M)?(\d+([.,]\d+)?S)?)?)$/,Le=/^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$/,Be=e=>e?new RegExp(`^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-${e}[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$`):/^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/,je=Be(4),Ue=Be(6),ze=Be(7),Ze=/^(?!\.)(?!.*\.\.)([A-Za-z0-9_'+\-\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\-]*\.)+[A-Za-z]{2,}$/,Fe=/^[^\s@"]{1,64}@[^\s@]{1,255}$/u,He=Fe +;function Qe(){ +return new RegExp("^(\\p{Extended_Pictographic}|\\p{Emoji_Component})+$","u")} +const Ve=/^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/,qe=/^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$/,We=e=>{ +const t=T(e??":") +;return new RegExp(`^(?:[0-9A-F]{2}${t}){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}${t}){5}[0-9a-f]{2}$`) +},Xe=/^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\/([0-9]|[1-2][0-9]|3[0-2])$/,Ge=/^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::|([0-9a-fA-F]{1,4})?::([0-9a-fA-F]{1,4}:?){0,6})\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$/,Ye=/^$|^(?:[0-9a-zA-Z+/]{4})*(?:(?:[0-9a-zA-Z+/]{2}==)|(?:[0-9a-zA-Z+/]{3}=))?$/,Ke=/^[A-Za-z0-9_-]*$/,Je=/^(?=.{1,253}\.?$)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[-0-9a-zA-Z]{0,61}[0-9a-zA-Z])?)*\.?$/,et=/^([a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/,tt=/^\+[1-9]\d{6,14}$/,nt="(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))",rt=new RegExp(`^${nt}$`) +;function at(e){const t="(?:[01]\\d|2[0-3]):[0-5]\\d" +;return"number"==typeof e.precision?-1===e.precision?`${t}`:0===e.precision?`${t}:[0-5]\\d`:`${t}:[0-5]\\d\\.\\d{${e.precision}}`:`${t}(?::[0-5]\\d(?:\\.\\d+)?)?` +}function ot(e){return new RegExp(`^${at(e)}$`)}function it(e){const t=at({ +precision:e.precision}),n=["Z"] +;e.local&&n.push(""),e.offset&&n.push("([+-](?:[01]\\d|2[0-3]):[0-5]\\d)") +;const r=`${t}(?:${n.join("|")})`;return new RegExp(`^${nt}T(?:${r})$`)} +const st=e=>new RegExp(`^${e?`[\\s\\S]{${e?.minimum??0},${e?.maximum??""}}`:"[\\s\\S]*"}$`),lt=/^-?\d+n?$/,ct=/^-?\d+$/,ut=/^-?\d+(?:\.\d+)?$/,dt=/^(?:true|false)$/i,pt=/^null$/i,ht=/^undefined$/i,ft=/^[^A-Z]*$/,mt=/^[^a-z]*$/,gt=/^[0-9a-fA-F]*$/ +;function vt(e,t){return new RegExp(`^[A-Za-z0-9+/]{${e}}${t}$`)}function bt(e){ +return new RegExp(`^[A-Za-z0-9_-]{${e}}$`)} +const yt=vt(22,"=="),Ot=bt(22),wt=vt(27,"="),xt=bt(27),kt=vt(43,"="),St=bt(43),_t=vt(64,""),At=bt(64),Tt=vt(86,"=="),Et=bt(86),Ct=Object.freeze(Object.defineProperty({ +__proto__:null,base64:Ye,base64url:Ke,bigint:lt,boolean:dt, +browserEmail:/^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/, +cidrv4:Xe,cidrv6:Ge,cuid:$e,cuid2:Pe,date:rt,datetime:it,domain:et,duration:Re, +e164:tt,email:Ze,emoji:Qe, +extendedDuration:/^[-+]?P(?!$)(?:(?:[-+]?\d+Y)|(?:[-+]?\d+[.,]\d+Y$))?(?:(?:[-+]?\d+M)|(?:[-+]?\d+[.,]\d+M$))?(?:(?:[-+]?\d+W)|(?:[-+]?\d+[.,]\d+W$))?(?:(?:[-+]?\d+D)|(?:[-+]?\d+[.,]\d+D$))?(?:T(?=[\d+-])(?:(?:[-+]?\d+H)|(?:[-+]?\d+[.,]\d+H$))?(?:(?:[-+]?\d+M)|(?:[-+]?\d+[.,]\d+M$))?(?:[-+]?\d+(?:[.,]\d+)?S)?)??$/, +guid:Le,hex:gt,hostname:Je, +html5Email:/^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/, +idnEmail:He,integer:ct,ipv4:Ve,ipv6:qe,ksuid:Me,lowercase:ft,mac:We, +md5_base64:yt,md5_base64url:Ot,md5_hex:/^[0-9a-fA-F]{32}$/,nanoid:Ne,null:pt, +number:ut, +rfc5322Email:/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/, +sha1_base64:wt,sha1_base64url:xt,sha1_hex:/^[0-9a-fA-F]{40}$/,sha256_base64:kt, +sha256_base64url:St,sha256_hex:/^[0-9a-fA-F]{64}$/,sha384_base64:_t, +sha384_base64url:At,sha384_hex:/^[0-9a-fA-F]{96}$/,sha512_base64:Tt, +sha512_base64url:Et,sha512_hex:/^[0-9a-fA-F]{128}$/,string:st,time:ot,ulid:Ie, +undefined:ht,unicodeEmail:Fe,uppercase:mt,uuid:Be,uuid4:je,uuid6:Ue,uuid7:ze, +xid:De},Symbol.toStringTag,{value:"Module"})),$t=t("$ZodCheck",((e,t)=>{var n +;e._zod??(e._zod={}),e._zod.def=t,(n=e._zod).onattach??(n.onattach=[])})),Pt={ +number:"number",bigint:"bigint",object:"date" +},It=t("$ZodCheckLessThan",((e,t)=>{$t.init(e,t);const n=Pt[typeof t.value] +;e._zod.onattach.push((e=>{ +const n=e._zod.bag,r=(t.inclusive?n.maximum:n.exclusiveMaximum)??Number.POSITIVE_INFINITY +;t.value{ +(t.inclusive?r.value<=t.value:r.value{$t.init(e,t);const n=Pt[typeof t.value] +;e._zod.onattach.push((e=>{ +const n=e._zod.bag,r=(t.inclusive?n.minimum:n.exclusiveMinimum)??Number.NEGATIVE_INFINITY +;t.value>r&&(t.inclusive?n.minimum=t.value:n.exclusiveMinimum=t.value) +})),e._zod.check=r=>{ +(t.inclusive?r.value>=t.value:r.value>t.value)||r.issues.push({origin:n, +code:"too_small",minimum:"object"==typeof t.value?t.value.getTime():t.value, +input:r.value,inclusive:t.inclusive,inst:e,continue:!t.abort})} +})),Mt=t("$ZodCheckMultipleOf",((e,t)=>{$t.init(e,t),e._zod.onattach.push((e=>{ +var n;(n=e._zod.bag).multipleOf??(n.multipleOf=t.value)})),e._zod.check=n=>{ +if(typeof n.value!=typeof t.value)throw new Error("Cannot mix number and bigint in multiple_of check.") +;("bigint"==typeof n.value?n.value%t.value===BigInt(0):0===h(n.value,t.value))||n.issues.push({ +origin:typeof n.value,code:"not_multiple_of",divisor:t.value,input:n.value, +inst:e,continue:!t.abort})}})),Nt=t("$ZodCheckNumberFormat",((e,t)=>{ +$t.init(e,t),t.format=t.format||"float64" +;const n=t.format?.includes("int"),r=n?"int":"number",[a,o]=I[t.format] +;e._zod.onattach.push((e=>{const r=e._zod.bag +;r.format=t.format,r.minimum=a,r.maximum=o,n&&(r.pattern=ct) +})),e._zod.check=i=>{const s=i.value;if(n){ +if(!Number.isInteger(s))return void i.issues.push({expected:r,format:t.format, +code:"invalid_type",continue:!1,input:s,inst:e}) +;if(!Number.isSafeInteger(s))return void(s>0?i.issues.push({input:s, +code:"too_big",maximum:Number.MAX_SAFE_INTEGER, +note:"Integers must be within the safe integer range.",inst:e,origin:r, +inclusive:!0,continue:!t.abort}):i.issues.push({input:s,code:"too_small", +minimum:Number.MIN_SAFE_INTEGER, +note:"Integers must be within the safe integer range.",inst:e,origin:r, +inclusive:!0,continue:!t.abort}))}so&&i.issues.push({origin:"number",input:s,code:"too_big",maximum:o, +inclusive:!0,inst:e,continue:!t.abort})} +})),Rt=t("$ZodCheckBigIntFormat",((e,t)=>{$t.init(e,t);const[n,r]=D[t.format] +;e._zod.onattach.push((e=>{const a=e._zod.bag +;a.format=t.format,a.minimum=n,a.maximum=r})),e._zod.check=a=>{const o=a.value +;or&&a.issues.push({origin:"bigint", +input:o,code:"too_big",maximum:r,inclusive:!0,inst:e,continue:!t.abort})} +})),Lt=t("$ZodCheckMaxSize",((e,t)=>{var n +;$t.init(e,t),(n=e._zod.def).when??(n.when=e=>{const t=e.value +;return!d(t)&&void 0!==t.size}),e._zod.onattach.push((e=>{ +const n=e._zod.bag.maximum??Number.POSITIVE_INFINITY +;t.maximum{const r=n.value +;r.size<=t.maximum||n.issues.push({origin:Q(r),code:"too_big",maximum:t.maximum, +inclusive:!0,input:r,inst:e,continue:!t.abort})} +})),Bt=t("$ZodCheckMinSize",((e,t)=>{var n +;$t.init(e,t),(n=e._zod.def).when??(n.when=e=>{const t=e.value +;return!d(t)&&void 0!==t.size}),e._zod.onattach.push((e=>{ +const n=e._zod.bag.minimum??Number.NEGATIVE_INFINITY +;t.minimum>n&&(e._zod.bag.minimum=t.minimum)})),e._zod.check=n=>{const r=n.value +;r.size>=t.minimum||n.issues.push({origin:Q(r),code:"too_small", +minimum:t.minimum,inclusive:!0,input:r,inst:e,continue:!t.abort})} +})),jt=t("$ZodCheckSizeEquals",((e,t)=>{var n +;$t.init(e,t),(n=e._zod.def).when??(n.when=e=>{const t=e.value +;return!d(t)&&void 0!==t.size}),e._zod.onattach.push((e=>{const n=e._zod.bag +;n.minimum=t.size,n.maximum=t.size,n.size=t.size})),e._zod.check=n=>{ +const r=n.value,a=r.size;if(a===t.size)return;const o=a>t.size;n.issues.push({ +origin:Q(r),...o?{code:"too_big",maximum:t.size}:{code:"too_small", +minimum:t.size},inclusive:!0,exact:!0,input:n.value,inst:e,continue:!t.abort})} +})),Ut=t("$ZodCheckMaxLength",((e,t)=>{var n +;$t.init(e,t),(n=e._zod.def).when??(n.when=e=>{const t=e.value +;return!d(t)&&void 0!==t.length}),e._zod.onattach.push((e=>{ +const n=e._zod.bag.maximum??Number.POSITIVE_INFINITY +;t.maximum{const r=n.value +;if(r.length<=t.maximum)return;const a=V(r);n.issues.push({origin:a, +code:"too_big",maximum:t.maximum,inclusive:!0,input:r,inst:e,continue:!t.abort}) +}})),zt=t("$ZodCheckMinLength",((e,t)=>{var n +;$t.init(e,t),(n=e._zod.def).when??(n.when=e=>{const t=e.value +;return!d(t)&&void 0!==t.length}),e._zod.onattach.push((e=>{ +const n=e._zod.bag.minimum??Number.NEGATIVE_INFINITY +;t.minimum>n&&(e._zod.bag.minimum=t.minimum)})),e._zod.check=n=>{const r=n.value +;if(r.length>=t.minimum)return;const a=V(r);n.issues.push({origin:a, +code:"too_small",minimum:t.minimum,inclusive:!0,input:r,inst:e,continue:!t.abort +})}})),Zt=t("$ZodCheckLengthEquals",((e,t)=>{var n +;$t.init(e,t),(n=e._zod.def).when??(n.when=e=>{const t=e.value +;return!d(t)&&void 0!==t.length}),e._zod.onattach.push((e=>{const n=e._zod.bag +;n.minimum=t.length,n.maximum=t.length,n.length=t.length})),e._zod.check=n=>{ +const r=n.value,a=r.length;if(a===t.length)return;const o=V(r),i=a>t.length +;n.issues.push({origin:o,...i?{code:"too_big",maximum:t.length}:{ +code:"too_small",minimum:t.length},inclusive:!0,exact:!0,input:n.value,inst:e, +continue:!t.abort})}})),Ft=t("$ZodCheckStringFormat",((e,t)=>{var n,r +;$t.init(e,t),e._zod.onattach.push((e=>{const n=e._zod.bag +;n.format=t.format,t.pattern&&(n.patterns??(n.patterns=new Set), +n.patterns.add(t.pattern))})),t.pattern?(n=e._zod).check??(n.check=n=>{ +t.pattern.lastIndex=0,t.pattern.test(n.value)||n.issues.push({origin:"string", +code:"invalid_format",format:t.format,input:n.value,...t.pattern?{ +pattern:t.pattern.toString()}:{},inst:e,continue:!t.abort}) +}):(r=e._zod).check??(r.check=()=>{})})),Ht=t("$ZodCheckRegex",((e,t)=>{ +Ft.init(e,t),e._zod.check=n=>{ +t.pattern.lastIndex=0,t.pattern.test(n.value)||n.issues.push({origin:"string", +code:"invalid_format",format:"regex",input:n.value,pattern:t.pattern.toString(), +inst:e,continue:!t.abort})}})),Qt=t("$ZodCheckLowerCase",((e,t)=>{ +t.pattern??(t.pattern=ft),Ft.init(e,t)})),Vt=t("$ZodCheckUpperCase",((e,t)=>{ +t.pattern??(t.pattern=mt),Ft.init(e,t)})),qt=t("$ZodCheckIncludes",((e,t)=>{ +$t.init(e,t) +;const n=T(t.includes),r=new RegExp("number"==typeof t.position?`^.{${t.position}}${n}`:n) +;t.pattern=r,e._zod.onattach.push((e=>{const t=e._zod.bag +;t.patterns??(t.patterns=new Set),t.patterns.add(r)})),e._zod.check=n=>{ +n.value.includes(t.includes,t.position)||n.issues.push({origin:"string", +code:"invalid_format",format:"includes",includes:t.includes,input:n.value, +inst:e,continue:!t.abort})}})),Wt=t("$ZodCheckStartsWith",((e,t)=>{$t.init(e,t) +;const n=new RegExp(`^${T(t.prefix)}.*`) +;t.pattern??(t.pattern=n),e._zod.onattach.push((e=>{const t=e._zod.bag +;t.patterns??(t.patterns=new Set),t.patterns.add(n)})),e._zod.check=n=>{ +n.value.startsWith(t.prefix)||n.issues.push({origin:"string", +code:"invalid_format",format:"starts_with",prefix:t.prefix,input:n.value,inst:e, +continue:!t.abort})}})),Xt=t("$ZodCheckEndsWith",((e,t)=>{$t.init(e,t) +;const n=new RegExp(`.*${T(t.suffix)}$`) +;t.pattern??(t.pattern=n),e._zod.onattach.push((e=>{const t=e._zod.bag +;t.patterns??(t.patterns=new Set),t.patterns.add(n)})),e._zod.check=n=>{ +n.value.endsWith(t.suffix)||n.issues.push({origin:"string", +code:"invalid_format",format:"ends_with",suffix:t.suffix,input:n.value,inst:e, +continue:!t.abort})}}));function Gt(e,t,n){ +e.issues.length&&t.issues.push(...Z(n,e.issues))} +const Yt=t("$ZodCheckProperty",((e,t)=>{$t.init(e,t),e._zod.check=e=>{ +const n=t.schema._zod.run({value:e.value[t.property],issues:[]},{}) +;if(n instanceof Promise)return n.then((n=>Gt(n,e,t.property))) +;Gt(n,e,t.property)}})),Kt=t("$ZodCheckMimeType",((e,t)=>{$t.init(e,t) +;const n=new Set(t.mime);e._zod.onattach.push((e=>{e._zod.bag.mime=t.mime +})),e._zod.check=r=>{n.has(r.value.type)||r.issues.push({code:"invalid_value", +values:t.mime,input:r.value.type,inst:e,continue:!t.abort})} +})),Jt=t("$ZodCheckOverwrite",((e,t)=>{$t.init(e,t),e._zod.check=e=>{ +e.value=t.tx(e.value)}}));class en{constructor(e=[]){ +this.content=[],this.indent=0,this&&(this.args=e)}indented(e){ +this.indent+=1,e(this),this.indent-=1}write(e){ +if("function"==typeof e)return e(this,{execution:"sync"}),void e(this,{ +execution:"async"}) +;const t=e.split("\n").filter((e=>e)),n=Math.min(...t.map((e=>e.length-e.trimStart().length))),r=t.map((e=>e.slice(n))).map((e=>" ".repeat(2*this.indent)+e)) +;for(const a of r)this.content.push(a)}compile(){const e=Function,t=this?.args +;return new e(...t,[...(this?.content??[""]).map((e=>` ${e}`))].join("\n"))}} +const tn={major:4,minor:3,patch:5},nn=t("$ZodType",((e,t)=>{var n +;e??(e={}),e._zod.def=t,e._zod.bag=e._zod.bag||{},e._zod.version=tn +;const a=[...e._zod.def.checks??[]];e._zod.traits.has("$ZodCheck")&&a.unshift(e) +;for(const r of a)for(const t of r._zod.onattach)t(e) +;if(0===a.length)(n=e._zod).deferred??(n.deferred=[]), +e._zod.deferred?.push((()=>{e._zod.run=e._zod.parse}));else{const t=(e,t,n)=>{ +let a,o=z(e);for(const i of t){if(i._zod.def.when){ +if(!i._zod.def.when(e))continue}else if(o)continue +;const t=e.issues.length,s=i._zod.check(e) +;if(s instanceof Promise&&!1===n?.async)throw new r +;if(a||s instanceof Promise)a=(a??Promise.resolve()).then((async()=>{await s +;e.issues.length!==t&&(o||(o=z(e,t)))}));else{if(e.issues.length===t)continue +;o||(o=z(e,t))}}return a?a.then((()=>e)):e},n=(n,o,i)=>{ +if(z(n))return n.aborted=!0,n;const s=t(o,a,i);if(s instanceof Promise){ +if(!1===i.async)throw new r;return s.then((t=>e._zod.parse(t,i)))} +return e._zod.parse(s,i)};e._zod.run=(o,i)=>{ +if(i.skipChecks)return e._zod.parse(o,i);if("backward"===i.direction){ +const t=e._zod.parse({value:o.value,issues:[]},{...i,skipChecks:!0}) +;return t instanceof Promise?t.then((e=>n(e,o,i))):n(t,o,i)} +const s=e._zod.parse(o,i);if(s instanceof Promise){if(!1===i.async)throw new r +;return s.then((e=>t(e,a,i)))}return t(s,a,i)}}m(e,"~standard",(()=>({ +validate:t=>{try{const n=de(e,t);return n.success?{value:n.data}:{ +issues:n.error?.issues}}catch(n){return he(e,t).then((e=>e.success?{value:e.data +}:{issues:e.error?.issues}))}},vendor:"zod",version:1}))) +})),rn=t("$ZodString",((e,t)=>{ +nn.init(e,t),e._zod.pattern=[...e?._zod.bag?.patterns??[]].pop()??st(e._zod.bag), +e._zod.parse=(n,r)=>{if(t.coerce)try{n.value=String(n.value)}catch(a){} +return"string"==typeof n.value||n.issues.push({expected:"string", +code:"invalid_type",input:n.value,inst:e}),n} +})),an=t("$ZodStringFormat",((e,t)=>{Ft.init(e,t),rn.init(e,t) +})),on=t("$ZodGUID",((e,t)=>{t.pattern??(t.pattern=Le),an.init(e,t) +})),sn=t("$ZodUUID",((e,t)=>{if(t.version){const e={v1:1,v2:2,v3:3,v4:4,v5:5, +v6:6,v7:7,v8:8}[t.version] +;if(void 0===e)throw new Error(`Invalid UUID version: "${t.version}"`) +;t.pattern??(t.pattern=Be(e))}else t.pattern??(t.pattern=Be());an.init(e,t) +})),ln=t("$ZodEmail",((e,t)=>{t.pattern??(t.pattern=Ze),an.init(e,t) +})),cn=t("$ZodURL",((e,t)=>{an.init(e,t),e._zod.check=n=>{try{ +const r=n.value.trim(),a=new URL(r) +;return t.hostname&&(t.hostname.lastIndex=0,t.hostname.test(a.hostname)||n.issues.push({ +code:"invalid_format",format:"url",note:"Invalid hostname", +pattern:t.hostname.source,input:n.value,inst:e,continue:!t.abort +})),t.protocol&&(t.protocol.lastIndex=0, +t.protocol.test(a.protocol.endsWith(":")?a.protocol.slice(0,-1):a.protocol)||n.issues.push({ +code:"invalid_format",format:"url",note:"Invalid protocol", +pattern:t.protocol.source,input:n.value,inst:e,continue:!t.abort +})),void(t.normalize?n.value=a.href:n.value=r)}catch(r){n.issues.push({ +code:"invalid_format",format:"url",input:n.value,inst:e,continue:!t.abort})}} +})),un=t("$ZodEmoji",((e,t)=>{t.pattern??(t.pattern=Qe()),an.init(e,t) +})),dn=t("$ZodNanoID",((e,t)=>{t.pattern??(t.pattern=Ne),an.init(e,t) +})),pn=t("$ZodCUID",((e,t)=>{t.pattern??(t.pattern=$e),an.init(e,t) +})),hn=t("$ZodCUID2",((e,t)=>{t.pattern??(t.pattern=Pe),an.init(e,t) +})),fn=t("$ZodULID",((e,t)=>{t.pattern??(t.pattern=Ie),an.init(e,t) +})),mn=t("$ZodXID",((e,t)=>{t.pattern??(t.pattern=De),an.init(e,t) +})),gn=t("$ZodKSUID",((e,t)=>{t.pattern??(t.pattern=Me),an.init(e,t) +})),vn=t("$ZodISODateTime",((e,t)=>{t.pattern??(t.pattern=it(t)),an.init(e,t) +})),bn=t("$ZodISODate",((e,t)=>{t.pattern??(t.pattern=rt),an.init(e,t) +})),yn=t("$ZodISOTime",((e,t)=>{t.pattern??(t.pattern=ot(t)),an.init(e,t) +})),On=t("$ZodISODuration",((e,t)=>{t.pattern??(t.pattern=Re),an.init(e,t) +})),wn=t("$ZodIPv4",((e,t)=>{ +t.pattern??(t.pattern=Ve),an.init(e,t),e._zod.bag.format="ipv4" +})),xn=t("$ZodIPv6",((e,t)=>{ +t.pattern??(t.pattern=qe),an.init(e,t),e._zod.bag.format="ipv6", +e._zod.check=n=>{try{new URL(`http://[${n.value}]`)}catch{n.issues.push({ +code:"invalid_format",format:"ipv6",input:n.value,inst:e,continue:!t.abort})}} +})),kn=t("$ZodMAC",((e,t)=>{t.pattern??(t.pattern=We(t.delimiter)),an.init(e,t), +e._zod.bag.format="mac"})),Sn=t("$ZodCIDRv4",((e,t)=>{t.pattern??(t.pattern=Xe), +an.init(e,t)})),_n=t("$ZodCIDRv6",((e,t)=>{ +t.pattern??(t.pattern=Ge),an.init(e,t),e._zod.check=n=>{ +const r=n.value.split("/");try{if(2!==r.length)throw new Error;const[e,t]=r +;if(!t)throw new Error;const n=Number(t);if(`${n}`!==t)throw new Error +;if(n<0||n>128)throw new Error;new URL(`http://[${e}]`)}catch{n.issues.push({ +code:"invalid_format",format:"cidrv6",input:n.value,inst:e,continue:!t.abort})}} +}));function An(e){if(""===e)return!0;if(e.length%4!=0)return!1;try{ +return atob(e),!0}catch{return!1}}const Tn=t("$ZodBase64",((e,t)=>{ +t.pattern??(t.pattern=Ye), +an.init(e,t),e._zod.bag.contentEncoding="base64",e._zod.check=n=>{ +An(n.value)||n.issues.push({code:"invalid_format",format:"base64",input:n.value, +inst:e,continue:!t.abort})}}));function En(e){if(!Ke.test(e))return!1 +;const t=e.replace(/[-_]/g,(e=>"-"===e?"+":"/")) +;return An(t.padEnd(4*Math.ceil(t.length/4),"="))} +const Cn=t("$ZodBase64URL",((e,t)=>{ +t.pattern??(t.pattern=Ke),an.init(e,t),e._zod.bag.contentEncoding="base64url", +e._zod.check=n=>{En(n.value)||n.issues.push({code:"invalid_format", +format:"base64url",input:n.value,inst:e,continue:!t.abort})} +})),$n=t("$ZodE164",((e,t)=>{t.pattern??(t.pattern=tt),an.init(e,t)})) +;function Pn(e,t=null){try{const n=e.split(".");if(3!==n.length)return!1 +;const[r]=n;if(!r)return!1;const a=JSON.parse(atob(r)) +;return(!("typ"in a)||"JWT"===a?.typ)&&(!!a.alg&&(!t||"alg"in a&&a.alg===t)) +}catch{return!1}}const In=t("$ZodJWT",((e,t)=>{an.init(e,t),e._zod.check=n=>{ +Pn(n.value,t.alg)||n.issues.push({code:"invalid_format",format:"jwt", +input:n.value,inst:e,continue:!t.abort})} +})),Dn=t("$ZodCustomStringFormat",((e,t)=>{an.init(e,t),e._zod.check=n=>{ +t.fn(n.value)||n.issues.push({code:"invalid_format",format:t.format, +input:n.value,inst:e,continue:!t.abort})}})),Mn=t("$ZodNumber",((e,t)=>{ +nn.init(e,t),e._zod.pattern=e._zod.bag.pattern??ut,e._zod.parse=(n,r)=>{ +if(t.coerce)try{n.value=Number(n.value)}catch(i){}const a=n.value +;if("number"==typeof a&&!Number.isNaN(a)&&Number.isFinite(a))return n +;const o="number"==typeof a?Number.isNaN(a)?"NaN":Number.isFinite(a)?void 0:"Infinity":void 0 +;return n.issues.push({expected:"number",code:"invalid_type",input:a,inst:e, +...o?{received:o}:{}}),n}})),Nn=t("$ZodNumberFormat",((e,t)=>{ +Nt.init(e,t),Mn.init(e,t)})),Rn=t("$ZodBoolean",((e,t)=>{ +nn.init(e,t),e._zod.pattern=dt,e._zod.parse=(n,r)=>{if(t.coerce)try{ +n.value=Boolean(n.value)}catch(o){}const a=n.value +;return"boolean"==typeof a||n.issues.push({expected:"boolean", +code:"invalid_type",input:a,inst:e}),n}})),Ln=t("$ZodBigInt",((e,t)=>{ +nn.init(e,t),e._zod.pattern=lt,e._zod.parse=(n,r)=>{if(t.coerce)try{ +n.value=BigInt(n.value)}catch(a){} +return"bigint"==typeof n.value||n.issues.push({expected:"bigint", +code:"invalid_type",input:n.value,inst:e}),n} +})),Bn=t("$ZodBigIntFormat",((e,t)=>{Rt.init(e,t),Ln.init(e,t) +})),jn=t("$ZodSymbol",((e,t)=>{nn.init(e,t),e._zod.parse=(t,n)=>{const r=t.value +;return"symbol"==typeof r||t.issues.push({expected:"symbol",code:"invalid_type", +input:r,inst:e}),t}})),Un=t("$ZodUndefined",((e,t)=>{ +nn.init(e,t),e._zod.pattern=ht, +e._zod.values=new Set([void 0]),e._zod.optin="optional", +e._zod.optout="optional",e._zod.parse=(t,n)=>{const r=t.value +;return void 0===r||t.issues.push({expected:"undefined",code:"invalid_type", +input:r,inst:e}),t}})),zn=t("$ZodNull",((e,t)=>{ +nn.init(e,t),e._zod.pattern=pt,e._zod.values=new Set([null]), +e._zod.parse=(t,n)=>{const r=t.value;return null===r||t.issues.push({ +expected:"null",code:"invalid_type",input:r,inst:e}),t} +})),Zn=t("$ZodAny",((e,t)=>{nn.init(e,t),e._zod.parse=e=>e +})),Fn=t("$ZodUnknown",((e,t)=>{nn.init(e,t),e._zod.parse=e=>e +})),Hn=t("$ZodNever",((e,t)=>{nn.init(e,t),e._zod.parse=(t,n)=>(t.issues.push({ +expected:"never",code:"invalid_type",input:t.value,inst:e}),t) +})),Qn=t("$ZodVoid",((e,t)=>{nn.init(e,t),e._zod.parse=(t,n)=>{const r=t.value +;return void 0===r||t.issues.push({expected:"void",code:"invalid_type",input:r, +inst:e}),t}})),Vn=t("$ZodDate",((e,t)=>{nn.init(e,t),e._zod.parse=(n,r)=>{ +if(t.coerce)try{n.value=new Date(n.value)}catch(i){} +const a=n.value,o=a instanceof Date +;return o&&!Number.isNaN(a.getTime())||n.issues.push({expected:"date", +code:"invalid_type",input:a,...o?{received:"Invalid Date"}:{},inst:e}),n}})) +;function qn(e,t,n){ +e.issues.length&&t.issues.push(...Z(n,e.issues)),t.value[n]=e.value} +const Wn=t("$ZodArray",((e,t)=>{nn.init(e,t),e._zod.parse=(n,r)=>{ +const a=n.value;if(!Array.isArray(a))return n.issues.push({expected:"array", +code:"invalid_type",input:a,inst:e}),n;n.value=Array(a.length);const o=[] +;for(let e=0;eqn(t,n,e)))):qn(s,n,e)} +return o.length?Promise.all(o).then((()=>n)):n}}));function Xn(e,t,n,r,a){ +if(e.issues.length){if(a&&!(n in r))return;t.issues.push(...Z(n,e.issues))} +void 0===e.value?n in r&&(t.value[n]=void 0):t.value[n]=e.value}function Gn(e){ +const t=Object.keys(e.shape) +;for(const r of t)if(!e.shape?.[r]?._zod?.traits?.has("$ZodType"))throw new Error(`Invalid element at key "${r}": expected a Zod schema`) +;const n=P(e.shape);return{...e,keys:t,keySet:new Set(t),numKeys:t.length, +optionalKeys:new Set(n)}}function Yn(e,t,n,r,a,o){ +const i=[],s=a.keySet,l=a.catchall._zod,c=l.def.type,u="optional"===l.optout +;for(const d in t){if(s.has(d))continue;if("never"===c){i.push(d);continue} +const a=l.run({value:t[d],issues:[]},r) +;a instanceof Promise?e.push(a.then((e=>Xn(e,n,d,t,u)))):Xn(a,n,d,t,u)} +return i.length&&n.issues.push({code:"unrecognized_keys",keys:i,input:t,inst:o +}),e.length?Promise.all(e).then((()=>n)):n}const Kn=t("$ZodObject",((e,t)=>{ +nn.init(e,t);const n=Object.getOwnPropertyDescriptor(t,"shape");if(!n?.get){ +const e=t.shape;Object.defineProperty(t,"shape",{get:()=>{const n={...e} +;return Object.defineProperty(t,"shape",{value:n}),n}})}const r=u((()=>Gn(t))) +;m(e._zod,"propValues",(()=>{const e=t.shape,n={};for(const t in e){ +const r=e[t]._zod;if(r.values){n[t]??(n[t]=new Set) +;for(const e of r.values)n[t].add(e)}}return n}));const a=w,o=t.catchall;let i +;e._zod.parse=(t,n)=>{i??(i=r.value);const s=t.value +;if(!a(s))return t.issues.push({expected:"object",code:"invalid_type",input:s, +inst:e}),t;t.value={};const l=[],c=i.shape;for(const e of i.keys){ +const r=c[e],a="optional"===r._zod.optout,o=r._zod.run({value:s[e],issues:[]},n) +;o instanceof Promise?l.push(o.then((n=>Xn(n,t,e,s,a)))):Xn(o,t,e,s,a)} +return o?Yn(l,s,t,n,r.value,e):l.length?Promise.all(l).then((()=>t)):t} +})),Jn=t("$ZodObjectJIT",((e,t)=>{Kn.init(e,t) +;const n=e._zod.parse,r=u((()=>Gn(t)));let a +;const i=w,s=!o.jitless,l=s&&x.value,c=t.catchall;let d;e._zod.parse=(o,u)=>{ +d??(d=r.value);const p=o.value +;return i(p)?s&&l&&!1===u?.async&&!0!==u.jitless?(a||(a=(e=>{ +const t=new en(["shape","payload","ctx"]),n=r.value,a=e=>{const t=b(e) +;return`shape[${t}]._zod.run({ value: input[${t}], issues: [] }, ctx)`} +;t.write("const input = payload.value;");const o=Object.create(null);let i=0 +;for(const r of n.keys)o[r]="key_"+i++;t.write("const newResult = {};") +;for(const r of n.keys){ +const n=o[r],i=b(r),s=e[r],l="optional"===s?._zod?.optout +;t.write(`const ${n} = ${a(r)};`), +l?t.write(`\n if (${n}.issues.length) {\n if (${i} in input) {\n payload.issues = payload.issues.concat(${n}.issues.map(iss => ({\n ...iss,\n path: iss.path ? [${i}, ...iss.path] : [${i}]\n })));\n }\n }\n \n if (${n}.value === undefined) {\n if (${i} in input) {\n newResult[${i}] = undefined;\n }\n } else {\n newResult[${i}] = ${n}.value;\n }\n \n `):t.write(`\n if (${n}.issues.length) {\n payload.issues = payload.issues.concat(${n}.issues.map(iss => ({\n ...iss,\n path: iss.path ? [${i}, ...iss.path] : [${i}]\n })));\n }\n \n if (${n}.value === undefined) {\n if (${i} in input) {\n newResult[${i}] = undefined;\n }\n } else {\n newResult[${i}] = ${n}.value;\n }\n \n `) +}t.write("payload.value = newResult;"),t.write("return payload;") +;const s=t.compile();return(t,n)=>s(e,t,n) +})(t.shape)),o=a(o,u),c?Yn([],p,o,u,d,e):o):n(o,u):(o.issues.push({ +expected:"object",code:"invalid_type",input:p,inst:e}),o)}})) +;function er(e,t,n,r){ +for(const o of e)if(0===o.issues.length)return t.value=o.value,t +;const a=e.filter((e=>!z(e))) +;return 1===a.length?(t.value=a[0].value,a[0]):(t.issues.push({ +code:"invalid_union",input:t.value,inst:n, +errors:e.map((e=>e.issues.map((e=>H(e,r,i())))))}),t)} +const tr=t("$ZodUnion",((e,t)=>{ +nn.init(e,t),m(e._zod,"optin",(()=>t.options.some((e=>"optional"===e._zod.optin))?"optional":void 0)), +m(e._zod,"optout",(()=>t.options.some((e=>"optional"===e._zod.optout))?"optional":void 0)), +m(e._zod,"values",(()=>{ +if(t.options.every((e=>e._zod.values)))return new Set(t.options.flatMap((e=>Array.from(e._zod.values)))) +})),m(e._zod,"pattern",(()=>{if(t.options.every((e=>e._zod.pattern))){ +const e=t.options.map((e=>e._zod.pattern)) +;return new RegExp(`^(${e.map((e=>p(e.source))).join("|")})$`)}})) +;const n=1===t.options.length,r=t.options[0]._zod.run;e._zod.parse=(a,o)=>{ +if(n)return r(a,o);let i=!1;const s=[];for(const e of t.options){ +const t=e._zod.run({value:a.value,issues:[]},o) +;if(t instanceof Promise)s.push(t),i=!0;else{if(0===t.issues.length)return t +;s.push(t)}}return i?Promise.all(s).then((t=>er(t,a,e,o))):er(s,a,e,o)}})) +;function nr(e,t,n,r){const a=e.filter((e=>0===e.issues.length)) +;return 1===a.length?(t.value=a[0].value,t):(0===a.length?t.issues.push({ +code:"invalid_union",input:t.value,inst:n, +errors:e.map((e=>e.issues.map((e=>H(e,r,i())))))}):t.issues.push({ +code:"invalid_union",input:t.value,inst:n,errors:[],inclusive:!1}),t)} +const rr=t("$ZodXor",((e,t)=>{tr.init(e,t),t.inclusive=!1 +;const n=1===t.options.length,r=t.options[0]._zod.run;e._zod.parse=(a,o)=>{ +if(n)return r(a,o);let i=!1;const s=[];for(const e of t.options){ +const t=e._zod.run({value:a.value,issues:[]},o);t instanceof Promise?(s.push(t), +i=!0):s.push(t)}return i?Promise.all(s).then((t=>nr(t,a,e,o))):nr(s,a,e,o)} +})),ar=t("$ZodDiscriminatedUnion",((e,t)=>{t.inclusive=!1,tr.init(e,t) +;const n=e._zod.parse;m(e._zod,"propValues",(()=>{const e={} +;for(const n of t.options){const r=n._zod.propValues +;if(!r||0===Object.keys(r).length)throw new Error(`Invalid discriminated union option at index "${t.options.indexOf(n)}"`) +;for(const[t,n]of Object.entries(r)){e[t]||(e[t]=new Set) +;for(const r of n)e[t].add(r)}}return e}));const r=u((()=>{ +const e=t.options,n=new Map;for(const r of e){ +const e=r._zod.propValues?.[t.discriminator] +;if(!e||0===e.size)throw new Error(`Invalid discriminated union option at index "${t.options.indexOf(r)}"`) +;for(const t of e){ +if(n.has(t))throw new Error(`Duplicate discriminator value "${String(t)}"`) +;n.set(t,r)}}return n}));e._zod.parse=(a,o)=>{const i=a.value +;if(!w(i))return a.issues.push({code:"invalid_type",expected:"object",input:i, +inst:e}),a;const s=r.value.get(i?.[t.discriminator]) +;return s?s._zod.run(a,o):t.unionFallback?n(a,o):(a.issues.push({ +code:"invalid_union",errors:[],note:"No matching discriminator", +discriminator:t.discriminator,input:i,path:[t.discriminator],inst:e}),a)} +})),or=t("$ZodIntersection",((e,t)=>{nn.init(e,t),e._zod.parse=(e,n)=>{ +const r=e.value,a=t.left._zod.run({value:r,issues:[]},n),o=t.right._zod.run({ +value:r,issues:[]},n) +;return a instanceof Promise||o instanceof Promise?Promise.all([a,o]).then((([t,n])=>sr(e,t,n))):sr(e,a,o) +}}));function ir(e,t){if(e===t)return{valid:!0,data:e} +;if(e instanceof Date&&t instanceof Date&&+e==+t)return{valid:!0,data:e} +;if(k(e)&&k(t)){ +const n=Object.keys(t),r=Object.keys(e).filter((e=>-1!==n.indexOf(e))),a={...e, +...t};for(const o of r){const n=ir(e[o],t[o]);if(!n.valid)return{valid:!1, +mergeErrorPath:[o,...n.mergeErrorPath]};a[o]=n.data}return{valid:!0,data:a}} +if(Array.isArray(e)&&Array.isArray(t)){if(e.length!==t.length)return{valid:!1, +mergeErrorPath:[]};const n=[];for(let r=0;re.l&&e.r)).map((([e])=>e)) +;if(o.length&&a&&e.issues.push({...a,keys:o}),z(e))return e +;const i=ir(t.value,n.value) +;if(!i.valid)throw new Error(`Unmergable intersection. Error path: ${JSON.stringify(i.mergeErrorPath)}`) +;return e.value=i.data,e}const lr=t("$ZodTuple",((e,t)=>{nn.init(e,t) +;const n=t.items;e._zod.parse=(r,a)=>{const o=r.value +;if(!Array.isArray(o))return r.issues.push({input:o,inst:e,expected:"tuple", +code:"invalid_type"}),r;r.value=[] +;const i=[],s=[...n].reverse().findIndex((e=>"optional"!==e._zod.optin)),l=-1===s?0:n.length-s +;if(!t.rest){const t=o.length>n.length,a=o.length=o.length&&c>=l)continue +;const t=e._zod.run({value:o[c],issues:[]},a) +;t instanceof Promise?i.push(t.then((e=>cr(e,r,c)))):cr(t,r,c)}if(t.rest){ +const e=o.slice(n.length);for(const n of e){c++;const e=t.rest._zod.run({ +value:n,issues:[]},a) +;e instanceof Promise?i.push(e.then((e=>cr(e,r,c)))):cr(e,r,c)}} +return i.length?Promise.all(i).then((()=>r)):r}}));function cr(e,t,n){ +e.issues.length&&t.issues.push(...Z(n,e.issues)),t.value[n]=e.value} +const ur=t("$ZodRecord",((e,t)=>{nn.init(e,t),e._zod.parse=(n,r)=>{ +const a=n.value;if(!k(a))return n.issues.push({expected:"record", +code:"invalid_type",input:a,inst:e}),n;const o=[],s=t.keyType._zod.values;if(s){ +n.value={};const i=new Set +;for(const e of s)if("string"==typeof e||"number"==typeof e||"symbol"==typeof e){ +i.add("number"==typeof e?e.toString():e);const s=t.valueType._zod.run({ +value:a[e],issues:[]},r);s instanceof Promise?o.push(s.then((t=>{ +t.issues.length&&n.issues.push(...Z(e,t.issues)),n.value[e]=t.value +}))):(s.issues.length&&n.issues.push(...Z(e,s.issues)),n.value[e]=s.value)}let l +;for(const e in a)i.has(e)||(l=l??[],l.push(e));l&&l.length>0&&n.issues.push({ +code:"unrecognized_keys",input:a,inst:e,keys:l})}else{n.value={} +;for(const s of Reflect.ownKeys(a)){if("__proto__"===s)continue +;let l=t.keyType._zod.run({value:s,issues:[]},r) +;if(l instanceof Promise)throw new Error("Async schemas not supported in object keys currently") +;if("string"==typeof s&&ut.test(s)&&l.issues.length&&l.issues.some((e=>"invalid_type"===e.code&&"number"===e.expected))){ +const e=t.keyType._zod.run({value:Number(s),issues:[]},r) +;if(e instanceof Promise)throw new Error("Async schemas not supported in object keys currently") +;0===e.issues.length&&(l=e)}if(l.issues.length){ +"loose"===t.mode?n.value[s]=a[s]:n.issues.push({code:"invalid_key", +origin:"record",issues:l.issues.map((e=>H(e,r,i()))),input:s,path:[s],inst:e}) +;continue}const c=t.valueType._zod.run({value:a[s],issues:[]},r) +;c instanceof Promise?o.push(c.then((e=>{ +e.issues.length&&n.issues.push(...Z(s,e.issues)),n.value[l.value]=e.value +}))):(c.issues.length&&n.issues.push(...Z(s,c.issues)),n.value[l.value]=c.value) +}}return o.length?Promise.all(o).then((()=>n)):n}})),dr=t("$ZodMap",((e,t)=>{ +nn.init(e,t),e._zod.parse=(n,r)=>{const a=n.value +;if(!(a instanceof Map))return n.issues.push({expected:"map", +code:"invalid_type",input:a,inst:e}),n;const o=[];n.value=new Map +;for(const[i,s]of a){const l=t.keyType._zod.run({value:i,issues:[] +},r),c=t.valueType._zod.run({value:s,issues:[]},r) +;l instanceof Promise||c instanceof Promise?o.push(Promise.all([l,c]).then((([t,o])=>{ +pr(t,o,n,i,a,e,r)}))):pr(l,c,n,i,a,e,r)} +return o.length?Promise.all(o).then((()=>n)):n}}));function pr(e,t,n,r,a,o,s){ +e.issues.length&&(_.has(typeof r)?n.issues.push(...Z(r,e.issues)):n.issues.push({ +code:"invalid_key",origin:"map",input:a,inst:o, +issues:e.issues.map((e=>H(e,s,i()))) +})),t.issues.length&&(_.has(typeof r)?n.issues.push(...Z(r,t.issues)):n.issues.push({ +origin:"map",code:"invalid_element",input:a,inst:o,key:r, +issues:t.issues.map((e=>H(e,s,i())))})),n.value.set(e.value,t.value)} +const hr=t("$ZodSet",((e,t)=>{nn.init(e,t),e._zod.parse=(n,r)=>{const a=n.value +;if(!(a instanceof Set))return n.issues.push({input:a,inst:e,expected:"set", +code:"invalid_type"}),n;const o=[];n.value=new Set;for(const e of a){ +const a=t.valueType._zod.run({value:e,issues:[]},r) +;a instanceof Promise?o.push(a.then((e=>fr(e,n)))):fr(a,n)} +return o.length?Promise.all(o).then((()=>n)):n}}));function fr(e,t){ +e.issues.length&&t.issues.push(...e.issues),t.value.add(e.value)} +const mr=t("$ZodEnum",((e,t)=>{nn.init(e,t);const n=s(t.entries),r=new Set(n) +;e._zod.values=r, +e._zod.pattern=new RegExp(`^(${n.filter((e=>_.has(typeof e))).map((e=>"string"==typeof e?T(e):e.toString())).join("|")})$`), +e._zod.parse=(t,a)=>{const o=t.value;return r.has(o)||t.issues.push({ +code:"invalid_value",values:n,input:o,inst:e}),t} +})),gr=t("$ZodLiteral",((e,t)=>{ +if(nn.init(e,t),0===t.values.length)throw new Error("Cannot create literal schema with no valid values") +;const n=new Set(t.values) +;e._zod.values=n,e._zod.pattern=new RegExp(`^(${t.values.map((e=>"string"==typeof e?T(e):e?T(e.toString()):String(e))).join("|")})$`), +e._zod.parse=(r,a)=>{const o=r.value;return n.has(o)||r.issues.push({ +code:"invalid_value",values:t.values,input:o,inst:e}),r} +})),vr=t("$ZodFile",((e,t)=>{nn.init(e,t),e._zod.parse=(t,n)=>{const r=t.value +;return r instanceof File||t.issues.push({expected:"file",code:"invalid_type", +input:r,inst:e}),t}})),br=t("$ZodTransform",((e,t)=>{ +nn.init(e,t),e._zod.parse=(n,o)=>{ +if("backward"===o.direction)throw new a(e.constructor.name) +;const i=t.transform(n.value,n);if(o.async){ +return(i instanceof Promise?i:Promise.resolve(i)).then((e=>(n.value=e,n)))} +if(i instanceof Promise)throw new r;return n.value=i,n}}));function yr(e,t){ +return e.issues.length&&void 0===t?{issues:[],value:void 0}:e} +const Or=t("$ZodOptional",((e,t)=>{ +nn.init(e,t),e._zod.optin="optional",e._zod.optout="optional", +m(e._zod,"values",(()=>t.innerType._zod.values?new Set([...t.innerType._zod.values,void 0]):void 0)), +m(e._zod,"pattern",(()=>{const e=t.innerType._zod.pattern +;return e?new RegExp(`^(${p(e.source)})?$`):void 0})),e._zod.parse=(e,n)=>{ +if("optional"===t.innerType._zod.optin){const r=t.innerType._zod.run(e,n) +;return r instanceof Promise?r.then((t=>yr(t,e.value))):yr(r,e.value)} +return void 0===e.value?e:t.innerType._zod.run(e,n)} +})),wr=t("$ZodExactOptional",((e,t)=>{ +Or.init(e,t),m(e._zod,"values",(()=>t.innerType._zod.values)), +m(e._zod,"pattern",(()=>t.innerType._zod.pattern)), +e._zod.parse=(e,n)=>t.innerType._zod.run(e,n)})),xr=t("$ZodNullable",((e,t)=>{ +nn.init(e,t), +m(e._zod,"optin",(()=>t.innerType._zod.optin)),m(e._zod,"optout",(()=>t.innerType._zod.optout)), +m(e._zod,"pattern",(()=>{const e=t.innerType._zod.pattern +;return e?new RegExp(`^(${p(e.source)}|null)$`):void 0 +})),m(e._zod,"values",(()=>t.innerType._zod.values?new Set([...t.innerType._zod.values,null]):void 0)), +e._zod.parse=(e,n)=>null===e.value?e:t.innerType._zod.run(e,n) +})),kr=t("$ZodDefault",((e,t)=>{ +nn.init(e,t),e._zod.optin="optional",m(e._zod,"values",(()=>t.innerType._zod.values)), +e._zod.parse=(e,n)=>{ +if("backward"===n.direction)return t.innerType._zod.run(e,n) +;if(void 0===e.value)return e.value=t.defaultValue,e +;const r=t.innerType._zod.run(e,n) +;return r instanceof Promise?r.then((e=>Sr(e,t))):Sr(r,t)}}));function Sr(e,t){ +return void 0===e.value&&(e.value=t.defaultValue),e} +const _r=t("$ZodPrefault",((e,t)=>{ +nn.init(e,t),e._zod.optin="optional",m(e._zod,"values",(()=>t.innerType._zod.values)), +e._zod.parse=(e,n)=>("backward"===n.direction||void 0===e.value&&(e.value=t.defaultValue), +t.innerType._zod.run(e,n))})),Ar=t("$ZodNonOptional",((e,t)=>{ +nn.init(e,t),m(e._zod,"values",(()=>{const e=t.innerType._zod.values +;return e?new Set([...e].filter((e=>void 0!==e))):void 0 +})),e._zod.parse=(n,r)=>{const a=t.innerType._zod.run(n,r) +;return a instanceof Promise?a.then((t=>Tr(t,e))):Tr(a,e)}}));function Tr(e,t){ +return e.issues.length||void 0!==e.value||e.issues.push({code:"invalid_type", +expected:"nonoptional",input:e.value,inst:t}),e} +const Er=t("$ZodSuccess",((e,t)=>{nn.init(e,t),e._zod.parse=(e,n)=>{ +if("backward"===n.direction)throw new a("ZodSuccess") +;const r=t.innerType._zod.run(e,n) +;return r instanceof Promise?r.then((t=>(e.value=0===t.issues.length, +e))):(e.value=0===r.issues.length,e)}})),Cr=t("$ZodCatch",((e,t)=>{nn.init(e,t), +m(e._zod,"optin",(()=>t.innerType._zod.optin)), +m(e._zod,"optout",(()=>t.innerType._zod.optout)), +m(e._zod,"values",(()=>t.innerType._zod.values)),e._zod.parse=(e,n)=>{ +if("backward"===n.direction)return t.innerType._zod.run(e,n) +;const r=t.innerType._zod.run(e,n) +;return r instanceof Promise?r.then((r=>(e.value=r.value, +r.issues.length&&(e.value=t.catchValue({...e,error:{ +issues:r.issues.map((e=>H(e,n,i())))},input:e.value +}),e.issues=[]),e))):(e.value=r.value,r.issues.length&&(e.value=t.catchValue({ +...e,error:{issues:r.issues.map((e=>H(e,n,i())))},input:e.value +}),e.issues=[]),e)}})),$r=t("$ZodNaN",((e,t)=>{ +nn.init(e,t),e._zod.parse=(t,n)=>("number"==typeof t.value&&Number.isNaN(t.value)||t.issues.push({ +input:t.value,inst:e,expected:"nan",code:"invalid_type"}),t) +})),Pr=t("$ZodPipe",((e,t)=>{ +nn.init(e,t),m(e._zod,"values",(()=>t.in._zod.values)), +m(e._zod,"optin",(()=>t.in._zod.optin)), +m(e._zod,"optout",(()=>t.out._zod.optout)), +m(e._zod,"propValues",(()=>t.in._zod.propValues)),e._zod.parse=(e,n)=>{ +if("backward"===n.direction){const r=t.out._zod.run(e,n) +;return r instanceof Promise?r.then((e=>Ir(e,t.in,n))):Ir(r,t.in,n)} +const r=t.in._zod.run(e,n) +;return r instanceof Promise?r.then((e=>Ir(e,t.out,n))):Ir(r,t.out,n)}})) +;function Ir(e,t,n){return e.issues.length?(e.aborted=!0,e):t._zod.run({ +value:e.value,issues:e.issues},n)}const Dr=t("$ZodCodec",((e,t)=>{ +nn.init(e,t),m(e._zod,"values",(()=>t.in._zod.values)), +m(e._zod,"optin",(()=>t.in._zod.optin)), +m(e._zod,"optout",(()=>t.out._zod.optout)), +m(e._zod,"propValues",(()=>t.in._zod.propValues)),e._zod.parse=(e,n)=>{ +if("forward"===(n.direction||"forward")){const r=t.in._zod.run(e,n) +;return r instanceof Promise?r.then((e=>Mr(e,t,n))):Mr(r,t,n)}{ +const r=t.out._zod.run(e,n) +;return r instanceof Promise?r.then((e=>Mr(e,t,n))):Mr(r,t,n)}}})) +;function Mr(e,t,n){if(e.issues.length)return e.aborted=!0,e +;if("forward"===(n.direction||"forward")){const r=t.transform(e.value,e) +;return r instanceof Promise?r.then((r=>Nr(e,r,t.out,n))):Nr(e,r,t.out,n)}{ +const r=t.reverseTransform(e.value,e) +;return r instanceof Promise?r.then((r=>Nr(e,r,t.in,n))):Nr(e,r,t.in,n)}} +function Nr(e,t,n,r){return e.issues.length?(e.aborted=!0,e):n._zod.run({ +value:t,issues:e.issues},r)}const Rr=t("$ZodReadonly",((e,t)=>{ +nn.init(e,t),m(e._zod,"propValues",(()=>t.innerType._zod.propValues)), +m(e._zod,"values",(()=>t.innerType._zod.values)), +m(e._zod,"optin",(()=>t.innerType?._zod?.optin)), +m(e._zod,"optout",(()=>t.innerType?._zod?.optout)),e._zod.parse=(e,n)=>{ +if("backward"===n.direction)return t.innerType._zod.run(e,n) +;const r=t.innerType._zod.run(e,n);return r instanceof Promise?r.then(Lr):Lr(r)} +}));function Lr(e){return e.value=Object.freeze(e.value),e} +const Br=t("$ZodTemplateLiteral",((e,t)=>{nn.init(e,t);const n=[] +;for(const r of t.parts)if("object"==typeof r&&null!==r){ +if(!r._zod.pattern)throw new Error(`Invalid template literal part, no pattern found: ${[...r._zod.traits].shift()}`) +;const e=r._zod.pattern instanceof RegExp?r._zod.pattern.source:r._zod.pattern +;if(!e)throw new Error(`Invalid template literal part: ${r._zod.traits}`) +;const t=e.startsWith("^")?1:0,a=e.endsWith("$")?e.length-1:e.length +;n.push(e.slice(t,a))}else{ +if(null!==r&&!A.has(typeof r))throw new Error(`Invalid template literal part: ${r}`) +;n.push(T(`${r}`))} +e._zod.pattern=new RegExp(`^${n.join("")}$`),e._zod.parse=(n,r)=>"string"!=typeof n.value?(n.issues.push({ +input:n.value,inst:e,expected:"string",code:"invalid_type" +}),n):(e._zod.pattern.lastIndex=0,e._zod.pattern.test(n.value)||n.issues.push({ +input:n.value,inst:e,code:"invalid_format",format:t.format??"template_literal", +pattern:e._zod.pattern.source}),n)})),jr=t("$ZodFunction",((e,t)=>(nn.init(e,t), +e._def=t,e._zod.def=t,e.implement=t=>{ +if("function"!=typeof t)throw new Error("implement() must be called with a function") +;return function(...n){ +const r=e._def.input?se(e._def.input,n):n,a=Reflect.apply(t,this,r) +;return e._def.output?se(e._def.output,a):a}},e.implementAsync=t=>{ +if("function"!=typeof t)throw new Error("implementAsync() must be called with a function") +;return async function(...n){ +const r=e._def.input?await ce(e._def.input,n):n,a=await Reflect.apply(t,this,r) +;return e._def.output?await ce(e._def.output,a):a}},e._zod.parse=(t,n)=>{ +if("function"!=typeof t.value)return t.issues.push({code:"invalid_type", +expected:"function",input:t.value,inst:e}),t +;const r=e._def.output&&"promise"===e._def.output._zod.def.type +;return t.value=r?e.implementAsync(t.value):e.implement(t.value),t +},e.input=(...t)=>{const n=e.constructor;return Array.isArray(t[0])?new n({ +type:"function",input:new lr({type:"tuple",items:t[0],rest:t[1]}), +output:e._def.output}):new n({type:"function",input:t[0],output:e._def.output}) +},e.output=t=>new(0,e.constructor)({type:"function",input:e._def.input,output:t +}),e))),Ur=t("$ZodPromise",((e,t)=>{ +nn.init(e,t),e._zod.parse=(e,n)=>Promise.resolve(e.value).then((e=>t.innerType._zod.run({ +value:e,issues:[]},n)))})),zr=t("$ZodLazy",((e,t)=>{ +nn.init(e,t),m(e._zod,"innerType",(()=>t.getter())), +m(e._zod,"pattern",(()=>e._zod.innerType?._zod?.pattern)), +m(e._zod,"propValues",(()=>e._zod.innerType?._zod?.propValues)), +m(e._zod,"optin",(()=>e._zod.innerType?._zod?.optin??void 0)), +m(e._zod,"optout",(()=>e._zod.innerType?._zod?.optout??void 0)), +e._zod.parse=(t,n)=>e._zod.innerType._zod.run(t,n) +})),Zr=t("$ZodCustom",((e,t)=>{ +$t.init(e,t),nn.init(e,t),e._zod.parse=(e,t)=>e,e._zod.check=n=>{ +const r=n.value,a=t.fn(r) +;if(a instanceof Promise)return a.then((t=>Fr(t,n,r,e)));Fr(a,n,r,e)}})) +;function Fr(e,t,n,r){if(!e){const e={code:"custom",input:n,inst:r, +path:[...r._zod.def.path??[]],continue:!r._zod.def.abort} +;r._zod.def.params&&(e.params=r._zod.def.params),t.issues.push(W(e))}} +const Hr=()=>{const e={string:{unit:"حرف",verb:"أن يحوي"},file:{unit:"بايت", +verb:"أن يحوي"},array:{unit:"عنصر",verb:"أن يحوي"},set:{unit:"عنصر", +verb:"أن يحوي"}};function t(t){return e[t]??null}const n={regex:"مدخل", +email:"بريد إلكتروني",url:"رابط",emoji:"إيموجي",uuid:"UUID",uuidv4:"UUIDv4", +uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2", +ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"تاريخ ووقت بمعيار ISO", +date:"تاريخ بمعيار ISO",time:"وقت بمعيار ISO",duration:"مدة بمعيار ISO", +ipv4:"عنوان IPv4",ipv6:"عنوان IPv6",cidrv4:"مدى عناوين بصيغة IPv4", +cidrv6:"مدى عناوين بصيغة IPv6",base64:"نَص بترميز base64-encoded", +base64url:"نَص بترميز base64url-encoded",json_string:"نَص على هيئة JSON", +e164:"رقم هاتف بمعيار E.164",jwt:"JWT",template_literal:"مدخل"},r={nan:"NaN"} +;return e=>{switch(e.code){case"invalid_type":{ +const t=r[e.expected]??e.expected,n=q(e.input),a=r[n]??n +;return/^[A-Z]/.test(e.expected)?`مدخلات غير مقبولة: يفترض إدخال instanceof ${e.expected}، ولكن تم إدخال ${a}`:`مدخلات غير مقبولة: يفترض إدخال ${t}، ولكن تم إدخال ${a}` +}case"invalid_value": +return 1===e.values.length?`مدخلات غير مقبولة: يفترض إدخال ${$(e.values[0])}`:`اختيار غير مقبول: يتوقع انتقاء أحد هذه الخيارات: ${l(e.values,"|")}` +;case"too_big":{const n=e.inclusive?"<=":"<",r=t(e.origin) +;return r?` أكبر من اللازم: يفترض أن تكون ${e.origin??"القيمة"} ${n} ${e.maximum.toString()} ${r.unit??"عنصر"}`:`أكبر من اللازم: يفترض أن تكون ${e.origin??"القيمة"} ${n} ${e.maximum.toString()}` +}case"too_small":{const n=e.inclusive?">=":">",r=t(e.origin) +;return r?`أصغر من اللازم: يفترض لـ ${e.origin} أن يكون ${n} ${e.minimum.toString()} ${r.unit}`:`أصغر من اللازم: يفترض لـ ${e.origin} أن يكون ${n} ${e.minimum.toString()}` +}case"invalid_format":{const t=e +;return"starts_with"===t.format?`نَص غير مقبول: يجب أن يبدأ بـ "${e.prefix}"`:"ends_with"===t.format?`نَص غير مقبول: يجب أن ينتهي بـ "${t.suffix}"`:"includes"===t.format?`نَص غير مقبول: يجب أن يتضمَّن "${t.includes}"`:"regex"===t.format?`نَص غير مقبول: يجب أن يطابق النمط ${t.pattern}`:`${n[t.format]??e.format} غير مقبول` +}case"not_multiple_of": +return`رقم غير مقبول: يجب أن يكون من مضاعفات ${e.divisor}` +;case"unrecognized_keys": +return`معرف${e.keys.length>1?"ات":""} غريب${e.keys.length>1?"ة":""}: ${l(e.keys,"، ")}` +;case"invalid_key":return`معرف غير مقبول في ${e.origin}`;case"invalid_union": +default:return"مدخل غير مقبول";case"invalid_element": +return`مدخل غير مقبول في ${e.origin}`}}};const Qr=()=>{const e={string:{ +unit:"simvol",verb:"olmalıdır"},file:{unit:"bayt",verb:"olmalıdır"},array:{ +unit:"element",verb:"olmalıdır"},set:{unit:"element",verb:"olmalıdır"}} +;function t(t){return e[t]??null}const n={regex:"input",email:"email address", +url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6", +nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID", +ksuid:"KSUID",datetime:"ISO datetime",date:"ISO date",time:"ISO time", +duration:"ISO duration",ipv4:"IPv4 address",ipv6:"IPv6 address", +cidrv4:"IPv4 range",cidrv6:"IPv6 range",base64:"base64-encoded string", +base64url:"base64url-encoded string",json_string:"JSON string", +e164:"E.164 number",jwt:"JWT",template_literal:"input"},r={nan:"NaN"} +;return e=>{switch(e.code){case"invalid_type":{ +const t=r[e.expected]??e.expected,n=q(e.input),a=r[n]??n +;return/^[A-Z]/.test(e.expected)?`Yanlış dəyər: gözlənilən instanceof ${e.expected}, daxil olan ${a}`:`Yanlış dəyər: gözlənilən ${t}, daxil olan ${a}` +}case"invalid_value": +return 1===e.values.length?`Yanlış dəyər: gözlənilən ${$(e.values[0])}`:`Yanlış seçim: aşağıdakılardan biri olmalıdır: ${l(e.values,"|")}` +;case"too_big":{const n=e.inclusive?"<=":"<",r=t(e.origin) +;return r?`Çox böyük: gözlənilən ${e.origin??"dəyər"} ${n}${e.maximum.toString()} ${r.unit??"element"}`:`Çox böyük: gözlənilən ${e.origin??"dəyər"} ${n}${e.maximum.toString()}` +}case"too_small":{const n=e.inclusive?">=":">",r=t(e.origin) +;return r?`Çox kiçik: gözlənilən ${e.origin} ${n}${e.minimum.toString()} ${r.unit}`:`Çox kiçik: gözlənilən ${e.origin} ${n}${e.minimum.toString()}` +}case"invalid_format":{const t=e +;return"starts_with"===t.format?`Yanlış mətn: "${t.prefix}" ilə başlamalıdır`:"ends_with"===t.format?`Yanlış mətn: "${t.suffix}" ilə bitməlidir`:"includes"===t.format?`Yanlış mətn: "${t.includes}" daxil olmalıdır`:"regex"===t.format?`Yanlış mətn: ${t.pattern} şablonuna uyğun olmalıdır`:`Yanlış ${n[t.format]??e.format}` +}case"not_multiple_of": +return`Yanlış ədəd: ${e.divisor} ilə bölünə bilən olmalıdır` +;case"unrecognized_keys": +return`Tanınmayan açar${e.keys.length>1?"lar":""}: ${l(e.keys,", ")}` +;case"invalid_key":return`${e.origin} daxilində yanlış açar` +;case"invalid_union":default:return"Yanlış dəyər";case"invalid_element": +return`${e.origin} daxilində yanlış dəyər`}}};function Vr(e,t,n,r){ +const a=Math.abs(e),o=a%10,i=a%100;return i>=11&&i<=19?r:1===o?t:o>=2&&o<=4?n:r} +const qr=()=>{const e={string:{unit:{one:"сімвал",few:"сімвалы",many:"сімвалаў" +},verb:"мець"},array:{unit:{one:"элемент",few:"элементы",many:"элементаў"}, +verb:"мець"},set:{unit:{one:"элемент",few:"элементы",many:"элементаў"}, +verb:"мець"},file:{unit:{one:"байт",few:"байты",many:"байтаў"},verb:"мець"}} +;function t(t){return e[t]??null}const n={regex:"увод",email:"email адрас", +url:"URL",emoji:"эмодзі",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6", +nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID", +ksuid:"KSUID",datetime:"ISO дата і час",date:"ISO дата",time:"ISO час", +duration:"ISO працягласць",ipv4:"IPv4 адрас",ipv6:"IPv6 адрас", +cidrv4:"IPv4 дыяпазон",cidrv6:"IPv6 дыяпазон",base64:"радок у фармаце base64", +base64url:"радок у фармаце base64url",json_string:"JSON радок", +e164:"нумар E.164",jwt:"JWT",template_literal:"увод"},r={nan:"NaN",number:"лік", +array:"масіў"};return e=>{switch(e.code){case"invalid_type":{ +const t=r[e.expected]??e.expected,n=q(e.input),a=r[n]??n +;return/^[A-Z]/.test(e.expected)?`Няправільны ўвод: чакаўся instanceof ${e.expected}, атрымана ${a}`:`Няправільны ўвод: чакаўся ${t}, атрымана ${a}` +}case"invalid_value": +return 1===e.values.length?`Няправільны ўвод: чакалася ${$(e.values[0])}`:`Няправільны варыянт: чакаўся адзін з ${l(e.values,"|")}` +;case"too_big":{const n=e.inclusive?"<=":"<",r=t(e.origin);if(r){ +const t=Vr(Number(e.maximum),r.unit.one,r.unit.few,r.unit.many) +;return`Занадта вялікі: чакалася, што ${e.origin??"значэнне"} павінна ${r.verb} ${n}${e.maximum.toString()} ${t}` +} +return`Занадта вялікі: чакалася, што ${e.origin??"значэнне"} павінна быць ${n}${e.maximum.toString()}` +}case"too_small":{const n=e.inclusive?">=":">",r=t(e.origin);if(r){ +const t=Vr(Number(e.minimum),r.unit.one,r.unit.few,r.unit.many) +;return`Занадта малы: чакалася, што ${e.origin} павінна ${r.verb} ${n}${e.minimum.toString()} ${t}` +} +return`Занадта малы: чакалася, што ${e.origin} павінна быць ${n}${e.minimum.toString()}` +}case"invalid_format":{const t=e +;return"starts_with"===t.format?`Няправільны радок: павінен пачынацца з "${t.prefix}"`:"ends_with"===t.format?`Няправільны радок: павінен заканчвацца на "${t.suffix}"`:"includes"===t.format?`Няправільны радок: павінен змяшчаць "${t.includes}"`:"regex"===t.format?`Няправільны радок: павінен адпавядаць шаблону ${t.pattern}`:`Няправільны ${n[t.format]??e.format}` +}case"not_multiple_of": +return`Няправільны лік: павінен быць кратным ${e.divisor}` +;case"unrecognized_keys": +return`Нераспазнаны ${e.keys.length>1?"ключы":"ключ"}: ${l(e.keys,", ")}` +;case"invalid_key":return`Няправільны ключ у ${e.origin}`;case"invalid_union": +default:return"Няправільны ўвод";case"invalid_element": +return`Няправільнае значэнне ў ${e.origin}`}}};const Wr=()=>{const e={string:{ +unit:"символа",verb:"да съдържа"},file:{unit:"байта",verb:"да съдържа"},array:{ +unit:"елемента",verb:"да съдържа"},set:{unit:"елемента",verb:"да съдържа"}} +;function t(t){return e[t]??null}const n={regex:"вход",email:"имейл адрес", +url:"URL",emoji:"емоджи",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6", +nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID", +ksuid:"KSUID",datetime:"ISO време",date:"ISO дата",time:"ISO време", +duration:"ISO продължителност",ipv4:"IPv4 адрес",ipv6:"IPv6 адрес", +cidrv4:"IPv4 диапазон",cidrv6:"IPv6 диапазон",base64:"base64-кодиран низ", +base64url:"base64url-кодиран низ",json_string:"JSON низ",e164:"E.164 номер", +jwt:"JWT",template_literal:"вход"},r={nan:"NaN",number:"число",array:"масив"} +;return e=>{switch(e.code){case"invalid_type":{ +const t=r[e.expected]??e.expected,n=q(e.input),a=r[n]??n +;return/^[A-Z]/.test(e.expected)?`Невалиден вход: очакван instanceof ${e.expected}, получен ${a}`:`Невалиден вход: очакван ${t}, получен ${a}` +}case"invalid_value": +return 1===e.values.length?`Невалиден вход: очакван ${$(e.values[0])}`:`Невалидна опция: очаквано едно от ${l(e.values,"|")}` +;case"too_big":{const n=e.inclusive?"<=":"<",r=t(e.origin) +;return r?`Твърде голямо: очаква се ${e.origin??"стойност"} да съдържа ${n}${e.maximum.toString()} ${r.unit??"елемента"}`:`Твърде голямо: очаква се ${e.origin??"стойност"} да бъде ${n}${e.maximum.toString()}` +}case"too_small":{const n=e.inclusive?">=":">",r=t(e.origin) +;return r?`Твърде малко: очаква се ${e.origin} да съдържа ${n}${e.minimum.toString()} ${r.unit}`:`Твърде малко: очаква се ${e.origin} да бъде ${n}${e.minimum.toString()}` +}case"invalid_format":{const t=e +;if("starts_with"===t.format)return`Невалиден низ: трябва да започва с "${t.prefix}"` +;if("ends_with"===t.format)return`Невалиден низ: трябва да завършва с "${t.suffix}"` +;if("includes"===t.format)return`Невалиден низ: трябва да включва "${t.includes}"` +;if("regex"===t.format)return`Невалиден низ: трябва да съвпада с ${t.pattern}` +;let r="Невалиден" +;return"emoji"===t.format&&(r="Невалидно"),"datetime"===t.format&&(r="Невалидно"), +"date"===t.format&&(r="Невалидна"), +"time"===t.format&&(r="Невалидно"),"duration"===t.format&&(r="Невалидна"), +`${r} ${n[t.format]??e.format}`}case"not_multiple_of": +return`Невалидно число: трябва да бъде кратно на ${e.divisor}` +;case"unrecognized_keys": +return`Неразпознат${e.keys.length>1?"и":""} ключ${e.keys.length>1?"ове":""}: ${l(e.keys,", ")}` +;case"invalid_key":return`Невалиден ключ в ${e.origin}`;case"invalid_union": +default:return"Невалиден вход";case"invalid_element": +return`Невалидна стойност в ${e.origin}`}}};const Xr=()=>{const e={string:{ +unit:"caràcters",verb:"contenir"},file:{unit:"bytes",verb:"contenir"},array:{ +unit:"elements",verb:"contenir"},set:{unit:"elements",verb:"contenir"}} +;function t(t){return e[t]??null}const n={regex:"entrada", +email:"adreça electrònica",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4", +uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2", +ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"data i hora ISO",date:"data ISO", +time:"hora ISO",duration:"durada ISO",ipv4:"adreça IPv4",ipv6:"adreça IPv6", +cidrv4:"rang IPv4",cidrv6:"rang IPv6",base64:"cadena codificada en base64", +base64url:"cadena codificada en base64url",json_string:"cadena JSON", +e164:"número E.164",jwt:"JWT",template_literal:"entrada"},r={nan:"NaN"} +;return e=>{switch(e.code){case"invalid_type":{ +const t=r[e.expected]??e.expected,n=q(e.input),a=r[n]??n +;return/^[A-Z]/.test(e.expected)?`Tipus invàlid: s'esperava instanceof ${e.expected}, s'ha rebut ${a}`:`Tipus invàlid: s'esperava ${t}, s'ha rebut ${a}` +}case"invalid_value": +return 1===e.values.length?`Valor invàlid: s'esperava ${$(e.values[0])}`:`Opció invàlida: s'esperava una de ${l(e.values," o ")}` +;case"too_big":{const n=e.inclusive?"com a màxim":"menys de",r=t(e.origin) +;return r?`Massa gran: s'esperava que ${e.origin??"el valor"} contingués ${n} ${e.maximum.toString()} ${r.unit??"elements"}`:`Massa gran: s'esperava que ${e.origin??"el valor"} fos ${n} ${e.maximum.toString()}` +}case"too_small":{const n=e.inclusive?"com a mínim":"més de",r=t(e.origin) +;return r?`Massa petit: s'esperava que ${e.origin} contingués ${n} ${e.minimum.toString()} ${r.unit}`:`Massa petit: s'esperava que ${e.origin} fos ${n} ${e.minimum.toString()}` +}case"invalid_format":{const t=e +;return"starts_with"===t.format?`Format invàlid: ha de començar amb "${t.prefix}"`:"ends_with"===t.format?`Format invàlid: ha d'acabar amb "${t.suffix}"`:"includes"===t.format?`Format invàlid: ha d'incloure "${t.includes}"`:"regex"===t.format?`Format invàlid: ha de coincidir amb el patró ${t.pattern}`:`Format invàlid per a ${n[t.format]??e.format}` +}case"not_multiple_of": +return`Número invàlid: ha de ser múltiple de ${e.divisor}` +;case"unrecognized_keys": +return`Clau${e.keys.length>1?"s":""} no reconeguda${e.keys.length>1?"s":""}: ${l(e.keys,", ")}` +;case"invalid_key":return`Clau invàlida a ${e.origin}`;case"invalid_union": +default:return"Entrada invàlida";case"invalid_element": +return`Element invàlid a ${e.origin}`}}};const Gr=()=>{const e={string:{ +unit:"znaků",verb:"mít"},file:{unit:"bajtů",verb:"mít"},array:{unit:"prvků", +verb:"mít"},set:{unit:"prvků",verb:"mít"}};function t(t){return e[t]??null} +const n={regex:"regulární výraz",email:"e-mailová adresa",url:"URL", +emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid", +guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID", +datetime:"datum a čas ve formátu ISO",date:"datum ve formátu ISO", +time:"čas ve formátu ISO",duration:"doba trvání ISO",ipv4:"IPv4 adresa", +ipv6:"IPv6 adresa",cidrv4:"rozsah IPv4",cidrv6:"rozsah IPv6", +base64:"řetězec zakódovaný ve formátu base64", +base64url:"řetězec zakódovaný ve formátu base64url", +json_string:"řetězec ve formátu JSON",e164:"číslo E.164",jwt:"JWT", +template_literal:"vstup"},r={nan:"NaN",number:"číslo",string:"řetězec", +function:"funkce",array:"pole"};return e=>{switch(e.code){case"invalid_type":{ +const t=r[e.expected]??e.expected,n=q(e.input),a=r[n]??n +;return/^[A-Z]/.test(e.expected)?`Neplatný vstup: očekáváno instanceof ${e.expected}, obdrženo ${a}`:`Neplatný vstup: očekáváno ${t}, obdrženo ${a}` +}case"invalid_value": +return 1===e.values.length?`Neplatný vstup: očekáváno ${$(e.values[0])}`:`Neplatná možnost: očekávána jedna z hodnot ${l(e.values,"|")}` +;case"too_big":{const n=e.inclusive?"<=":"<",r=t(e.origin) +;return r?`Hodnota je příliš velká: ${e.origin??"hodnota"} musí mít ${n}${e.maximum.toString()} ${r.unit??"prvků"}`:`Hodnota je příliš velká: ${e.origin??"hodnota"} musí být ${n}${e.maximum.toString()}` +}case"too_small":{const n=e.inclusive?">=":">",r=t(e.origin) +;return r?`Hodnota je příliš malá: ${e.origin??"hodnota"} musí mít ${n}${e.minimum.toString()} ${r.unit??"prvků"}`:`Hodnota je příliš malá: ${e.origin??"hodnota"} musí být ${n}${e.minimum.toString()}` +}case"invalid_format":{const t=e +;return"starts_with"===t.format?`Neplatný řetězec: musí začínat na "${t.prefix}"`:"ends_with"===t.format?`Neplatný řetězec: musí končit na "${t.suffix}"`:"includes"===t.format?`Neplatný řetězec: musí obsahovat "${t.includes}"`:"regex"===t.format?`Neplatný řetězec: musí odpovídat vzoru ${t.pattern}`:`Neplatný formát ${n[t.format]??e.format}` +}case"not_multiple_of":return`Neplatné číslo: musí být násobkem ${e.divisor}` +;case"unrecognized_keys":return`Neznámé klíče: ${l(e.keys,", ")}` +;case"invalid_key":return`Neplatný klíč v ${e.origin}`;case"invalid_union": +default:return"Neplatný vstup";case"invalid_element": +return`Neplatná hodnota v ${e.origin}`}}};const Yr=()=>{const e={string:{ +unit:"tegn",verb:"havde"},file:{unit:"bytes",verb:"havde"},array:{ +unit:"elementer",verb:"indeholdt"},set:{unit:"elementer",verb:"indeholdt"}} +;function t(t){return e[t]??null}const n={regex:"input",email:"e-mailadresse", +url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6", +nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID", +ksuid:"KSUID",datetime:"ISO dato- og klokkeslæt",date:"ISO-dato", +time:"ISO-klokkeslæt",duration:"ISO-varighed",ipv4:"IPv4-område", +ipv6:"IPv6-område",cidrv4:"IPv4-spektrum",cidrv6:"IPv6-spektrum", +base64:"base64-kodet streng",base64url:"base64url-kodet streng", +json_string:"JSON-streng",e164:"E.164-nummer",jwt:"JWT",template_literal:"input" +},r={nan:"NaN",string:"streng",number:"tal",boolean:"boolean",array:"liste", +object:"objekt",set:"sæt",file:"fil"};return e=>{switch(e.code){ +case"invalid_type":{const t=r[e.expected]??e.expected,n=q(e.input),a=r[n]??n +;return/^[A-Z]/.test(e.expected)?`Ugyldigt input: forventede instanceof ${e.expected}, fik ${a}`:`Ugyldigt input: forventede ${t}, fik ${a}` +}case"invalid_value": +return 1===e.values.length?`Ugyldig værdi: forventede ${$(e.values[0])}`:`Ugyldigt valg: forventede en af følgende ${l(e.values,"|")}` +;case"too_big":{ +const n=e.inclusive?"<=":"<",a=t(e.origin),o=r[e.origin]??e.origin +;return a?`For stor: forventede ${o??"value"} ${a.verb} ${n} ${e.maximum.toString()} ${a.unit??"elementer"}`:`For stor: forventede ${o??"value"} havde ${n} ${e.maximum.toString()}` +}case"too_small":{ +const n=e.inclusive?">=":">",a=t(e.origin),o=r[e.origin]??e.origin +;return a?`For lille: forventede ${o} ${a.verb} ${n} ${e.minimum.toString()} ${a.unit}`:`For lille: forventede ${o} havde ${n} ${e.minimum.toString()}` +}case"invalid_format":{const t=e +;return"starts_with"===t.format?`Ugyldig streng: skal starte med "${t.prefix}"`:"ends_with"===t.format?`Ugyldig streng: skal ende med "${t.suffix}"`:"includes"===t.format?`Ugyldig streng: skal indeholde "${t.includes}"`:"regex"===t.format?`Ugyldig streng: skal matche mønsteret ${t.pattern}`:`Ugyldig ${n[t.format]??e.format}` +}case"not_multiple_of":return`Ugyldigt tal: skal være deleligt med ${e.divisor}` +;case"unrecognized_keys": +return`${e.keys.length>1?"Ukendte nøgler":"Ukendt nøgle"}: ${l(e.keys,", ")}` +;case"invalid_key":return`Ugyldig nøgle i ${e.origin}`;case"invalid_union": +return"Ugyldigt input: matcher ingen af de tilladte typer" +;case"invalid_element":return`Ugyldig værdi i ${e.origin}`;default: +return"Ugyldigt input"}}};const Kr=()=>{const e={string:{unit:"Zeichen", +verb:"zu haben"},file:{unit:"Bytes",verb:"zu haben"},array:{unit:"Elemente", +verb:"zu haben"},set:{unit:"Elemente",verb:"zu haben"}};function t(t){ +return e[t]??null}const n={regex:"Eingabe",email:"E-Mail-Adresse",url:"URL", +emoji:"Emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid", +guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID", +datetime:"ISO-Datum und -Uhrzeit",date:"ISO-Datum",time:"ISO-Uhrzeit", +duration:"ISO-Dauer",ipv4:"IPv4-Adresse",ipv6:"IPv6-Adresse", +cidrv4:"IPv4-Bereich",cidrv6:"IPv6-Bereich",base64:"Base64-codierter String", +base64url:"Base64-URL-codierter String",json_string:"JSON-String", +e164:"E.164-Nummer",jwt:"JWT",template_literal:"Eingabe"},r={nan:"NaN", +number:"Zahl",array:"Array"};return e=>{switch(e.code){case"invalid_type":{ +const t=r[e.expected]??e.expected,n=q(e.input),a=r[n]??n +;return/^[A-Z]/.test(e.expected)?`Ungültige Eingabe: erwartet instanceof ${e.expected}, erhalten ${a}`:`Ungültige Eingabe: erwartet ${t}, erhalten ${a}` +}case"invalid_value": +return 1===e.values.length?`Ungültige Eingabe: erwartet ${$(e.values[0])}`:`Ungültige Option: erwartet eine von ${l(e.values,"|")}` +;case"too_big":{const n=e.inclusive?"<=":"<",r=t(e.origin) +;return r?`Zu groß: erwartet, dass ${e.origin??"Wert"} ${n}${e.maximum.toString()} ${r.unit??"Elemente"} hat`:`Zu groß: erwartet, dass ${e.origin??"Wert"} ${n}${e.maximum.toString()} ist` +}case"too_small":{const n=e.inclusive?">=":">",r=t(e.origin) +;return r?`Zu klein: erwartet, dass ${e.origin} ${n}${e.minimum.toString()} ${r.unit} hat`:`Zu klein: erwartet, dass ${e.origin} ${n}${e.minimum.toString()} ist` +}case"invalid_format":{const t=e +;return"starts_with"===t.format?`Ungültiger String: muss mit "${t.prefix}" beginnen`:"ends_with"===t.format?`Ungültiger String: muss mit "${t.suffix}" enden`:"includes"===t.format?`Ungültiger String: muss "${t.includes}" enthalten`:"regex"===t.format?`Ungültiger String: muss dem Muster ${t.pattern} entsprechen`:`Ungültig: ${n[t.format]??e.format}` +}case"not_multiple_of": +return`Ungültige Zahl: muss ein Vielfaches von ${e.divisor} sein` +;case"unrecognized_keys": +return`${e.keys.length>1?"Unbekannte Schlüssel":"Unbekannter Schlüssel"}: ${l(e.keys,", ")}` +;case"invalid_key":return`Ungültiger Schlüssel in ${e.origin}` +;case"invalid_union":default:return"Ungültige Eingabe";case"invalid_element": +return`Ungültiger Wert in ${e.origin}`}}};const Jr=()=>{const e={string:{ +unit:"characters",verb:"to have"},file:{unit:"bytes",verb:"to have"},array:{ +unit:"items",verb:"to have"},set:{unit:"items",verb:"to have"},map:{ +unit:"entries",verb:"to have"}};function t(t){return e[t]??null}const n={ +regex:"input",email:"email address",url:"URL",emoji:"emoji",uuid:"UUID", +uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid", +cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO datetime", +date:"ISO date",time:"ISO time",duration:"ISO duration",ipv4:"IPv4 address", +ipv6:"IPv6 address",mac:"MAC address",cidrv4:"IPv4 range",cidrv6:"IPv6 range", +base64:"base64-encoded string",base64url:"base64url-encoded string", +json_string:"JSON string",e164:"E.164 number",jwt:"JWT",template_literal:"input" +},r={nan:"NaN"};return e=>{switch(e.code){case"invalid_type":{ +const t=r[e.expected]??e.expected,n=q(e.input) +;return`Invalid input: expected ${t}, received ${r[n]??n}`}case"invalid_value": +return 1===e.values.length?`Invalid input: expected ${$(e.values[0])}`:`Invalid option: expected one of ${l(e.values,"|")}` +;case"too_big":{const n=e.inclusive?"<=":"<",r=t(e.origin) +;return r?`Too big: expected ${e.origin??"value"} to have ${n}${e.maximum.toString()} ${r.unit??"elements"}`:`Too big: expected ${e.origin??"value"} to be ${n}${e.maximum.toString()}` +}case"too_small":{const n=e.inclusive?">=":">",r=t(e.origin) +;return r?`Too small: expected ${e.origin} to have ${n}${e.minimum.toString()} ${r.unit}`:`Too small: expected ${e.origin} to be ${n}${e.minimum.toString()}` +}case"invalid_format":{const t=e +;return"starts_with"===t.format?`Invalid string: must start with "${t.prefix}"`:"ends_with"===t.format?`Invalid string: must end with "${t.suffix}"`:"includes"===t.format?`Invalid string: must include "${t.includes}"`:"regex"===t.format?`Invalid string: must match pattern ${t.pattern}`:`Invalid ${n[t.format]??e.format}` +}case"not_multiple_of": +return`Invalid number: must be a multiple of ${e.divisor}` +;case"unrecognized_keys": +return`Unrecognized key${e.keys.length>1?"s":""}: ${l(e.keys,", ")}` +;case"invalid_key":return`Invalid key in ${e.origin}`;case"invalid_union": +default:return"Invalid input";case"invalid_element": +return`Invalid value in ${e.origin}`}}};function ea(){return{localeError:Jr()}} +const ta=()=>{const e={string:{unit:"karaktrojn",verb:"havi"},file:{ +unit:"bajtojn",verb:"havi"},array:{unit:"elementojn",verb:"havi"},set:{ +unit:"elementojn",verb:"havi"}};function t(t){return e[t]??null}const n={ +regex:"enigo",email:"retadreso",url:"URL",emoji:"emoĝio",uuid:"UUID", +uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid", +cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO-datotempo", +date:"ISO-dato",time:"ISO-tempo",duration:"ISO-daŭro",ipv4:"IPv4-adreso", +ipv6:"IPv6-adreso",cidrv4:"IPv4-rango",cidrv6:"IPv6-rango", +base64:"64-ume kodita karaktraro",base64url:"URL-64-ume kodita karaktraro", +json_string:"JSON-karaktraro",e164:"E.164-nombro",jwt:"JWT", +template_literal:"enigo"},r={nan:"NaN",number:"nombro",array:"tabelo", +null:"senvalora"};return e=>{switch(e.code){case"invalid_type":{ +const t=r[e.expected]??e.expected,n=q(e.input),a=r[n]??n +;return/^[A-Z]/.test(e.expected)?`Nevalida enigo: atendiĝis instanceof ${e.expected}, riceviĝis ${a}`:`Nevalida enigo: atendiĝis ${t}, riceviĝis ${a}` +}case"invalid_value": +return 1===e.values.length?`Nevalida enigo: atendiĝis ${$(e.values[0])}`:`Nevalida opcio: atendiĝis unu el ${l(e.values,"|")}` +;case"too_big":{const n=e.inclusive?"<=":"<",r=t(e.origin) +;return r?`Tro granda: atendiĝis ke ${e.origin??"valoro"} havu ${n}${e.maximum.toString()} ${r.unit??"elementojn"}`:`Tro granda: atendiĝis ke ${e.origin??"valoro"} havu ${n}${e.maximum.toString()}` +}case"too_small":{const n=e.inclusive?">=":">",r=t(e.origin) +;return r?`Tro malgranda: atendiĝis ke ${e.origin} havu ${n}${e.minimum.toString()} ${r.unit}`:`Tro malgranda: atendiĝis ke ${e.origin} estu ${n}${e.minimum.toString()}` +}case"invalid_format":{const t=e +;return"starts_with"===t.format?`Nevalida karaktraro: devas komenciĝi per "${t.prefix}"`:"ends_with"===t.format?`Nevalida karaktraro: devas finiĝi per "${t.suffix}"`:"includes"===t.format?`Nevalida karaktraro: devas inkluzivi "${t.includes}"`:"regex"===t.format?`Nevalida karaktraro: devas kongrui kun la modelo ${t.pattern}`:`Nevalida ${n[t.format]??e.format}` +}case"not_multiple_of":return`Nevalida nombro: devas esti oblo de ${e.divisor}` +;case"unrecognized_keys": +return`Nekonata${e.keys.length>1?"j":""} ŝlosilo${e.keys.length>1?"j":""}: ${l(e.keys,", ")}` +;case"invalid_key":return`Nevalida ŝlosilo en ${e.origin}`;case"invalid_union": +default:return"Nevalida enigo";case"invalid_element": +return`Nevalida valoro en ${e.origin}`}}};const na=()=>{const e={string:{ +unit:"caracteres",verb:"tener"},file:{unit:"bytes",verb:"tener"},array:{ +unit:"elementos",verb:"tener"},set:{unit:"elementos",verb:"tener"}} +;function t(t){return e[t]??null}const n={regex:"entrada", +email:"dirección de correo electrónico",url:"URL",emoji:"emoji",uuid:"UUID", +uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid", +cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"fecha y hora ISO", +date:"fecha ISO",time:"hora ISO",duration:"duración ISO",ipv4:"dirección IPv4", +ipv6:"dirección IPv6",cidrv4:"rango IPv4",cidrv6:"rango IPv6", +base64:"cadena codificada en base64",base64url:"URL codificada en base64", +json_string:"cadena JSON",e164:"número E.164",jwt:"JWT", +template_literal:"entrada"},r={nan:"NaN",string:"texto",number:"número", +boolean:"booleano",array:"arreglo",object:"objeto",set:"conjunto", +file:"archivo",date:"fecha",bigint:"número grande",symbol:"símbolo", +undefined:"indefinido",null:"nulo",function:"función",map:"mapa", +record:"registro",tuple:"tupla",enum:"enumeración",union:"unión", +literal:"literal",promise:"promesa",void:"vacío",never:"nunca", +unknown:"desconocido",any:"cualquiera"};return e=>{switch(e.code){ +case"invalid_type":{const t=r[e.expected]??e.expected,n=q(e.input),a=r[n]??n +;return/^[A-Z]/.test(e.expected)?`Entrada inválida: se esperaba instanceof ${e.expected}, recibido ${a}`:`Entrada inválida: se esperaba ${t}, recibido ${a}` +}case"invalid_value": +return 1===e.values.length?`Entrada inválida: se esperaba ${$(e.values[0])}`:`Opción inválida: se esperaba una de ${l(e.values,"|")}` +;case"too_big":{ +const n=e.inclusive?"<=":"<",a=t(e.origin),o=r[e.origin]??e.origin +;return a?`Demasiado grande: se esperaba que ${o??"valor"} tuviera ${n}${e.maximum.toString()} ${a.unit??"elementos"}`:`Demasiado grande: se esperaba que ${o??"valor"} fuera ${n}${e.maximum.toString()}` +}case"too_small":{ +const n=e.inclusive?">=":">",a=t(e.origin),o=r[e.origin]??e.origin +;return a?`Demasiado pequeño: se esperaba que ${o} tuviera ${n}${e.minimum.toString()} ${a.unit}`:`Demasiado pequeño: se esperaba que ${o} fuera ${n}${e.minimum.toString()}` +}case"invalid_format":{const t=e +;return"starts_with"===t.format?`Cadena inválida: debe comenzar con "${t.prefix}"`:"ends_with"===t.format?`Cadena inválida: debe terminar en "${t.suffix}"`:"includes"===t.format?`Cadena inválida: debe incluir "${t.includes}"`:"regex"===t.format?`Cadena inválida: debe coincidir con el patrón ${t.pattern}`:`Inválido ${n[t.format]??e.format}` +}case"not_multiple_of": +return`Número inválido: debe ser múltiplo de ${e.divisor}` +;case"unrecognized_keys": +return`Llave${e.keys.length>1?"s":""} desconocida${e.keys.length>1?"s":""}: ${l(e.keys,", ")}` +;case"invalid_key":return`Llave inválida en ${r[e.origin]??e.origin}` +;case"invalid_union":default:return"Entrada inválida";case"invalid_element": +return`Valor inválido en ${r[e.origin]??e.origin}`}}};const ra=()=>{const e={ +string:{unit:"کاراکتر",verb:"داشته باشد"},file:{unit:"بایت",verb:"داشته باشد"}, +array:{unit:"آیتم",verb:"داشته باشد"},set:{unit:"آیتم",verb:"داشته باشد"}} +;function t(t){return e[t]??null}const n={regex:"ورودی",email:"آدرس ایمیل", +url:"URL",emoji:"ایموجی",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6", +nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID", +ksuid:"KSUID",datetime:"تاریخ و زمان ایزو",date:"تاریخ ایزو",time:"زمان ایزو", +duration:"مدت زمان ایزو",ipv4:"IPv4 آدرس",ipv6:"IPv6 آدرس",cidrv4:"IPv4 دامنه", +cidrv6:"IPv6 دامنه",base64:"base64-encoded رشته", +base64url:"base64url-encoded رشته",json_string:"JSON رشته",e164:"E.164 عدد", +jwt:"JWT",template_literal:"ورودی"},r={nan:"NaN",number:"عدد",array:"آرایه"} +;return e=>{switch(e.code){case"invalid_type":{ +const t=r[e.expected]??e.expected,n=q(e.input),a=r[n]??n +;return/^[A-Z]/.test(e.expected)?`ورودی نامعتبر: می‌بایست instanceof ${e.expected} می‌بود، ${a} دریافت شد`:`ورودی نامعتبر: می‌بایست ${t} می‌بود، ${a} دریافت شد` +}case"invalid_value": +return 1===e.values.length?`ورودی نامعتبر: می‌بایست ${$(e.values[0])} می‌بود`:`گزینه نامعتبر: می‌بایست یکی از ${l(e.values,"|")} می‌بود` +;case"too_big":{const n=e.inclusive?"<=":"<",r=t(e.origin) +;return r?`خیلی بزرگ: ${e.origin??"مقدار"} باید ${n}${e.maximum.toString()} ${r.unit??"عنصر"} باشد`:`خیلی بزرگ: ${e.origin??"مقدار"} باید ${n}${e.maximum.toString()} باشد` +}case"too_small":{const n=e.inclusive?">=":">",r=t(e.origin) +;return r?`خیلی کوچک: ${e.origin} باید ${n}${e.minimum.toString()} ${r.unit} باشد`:`خیلی کوچک: ${e.origin} باید ${n}${e.minimum.toString()} باشد` +}case"invalid_format":{const t=e +;return"starts_with"===t.format?`رشته نامعتبر: باید با "${t.prefix}" شروع شود`:"ends_with"===t.format?`رشته نامعتبر: باید با "${t.suffix}" تمام شود`:"includes"===t.format?`رشته نامعتبر: باید شامل "${t.includes}" باشد`:"regex"===t.format?`رشته نامعتبر: باید با الگوی ${t.pattern} مطابقت داشته باشد`:`${n[t.format]??e.format} نامعتبر` +}case"not_multiple_of":return`عدد نامعتبر: باید مضرب ${e.divisor} باشد` +;case"unrecognized_keys": +return`کلید${e.keys.length>1?"های":""} ناشناس: ${l(e.keys,", ")}` +;case"invalid_key":return`کلید ناشناس در ${e.origin}`;case"invalid_union": +default:return"ورودی نامعتبر";case"invalid_element": +return`مقدار نامعتبر در ${e.origin}`}}};const aa=()=>{const e={string:{ +unit:"merkkiä",subject:"merkkijonon"},file:{unit:"tavua",subject:"tiedoston"}, +array:{unit:"alkiota",subject:"listan"},set:{unit:"alkiota",subject:"joukon"}, +number:{unit:"",subject:"luvun"},bigint:{unit:"",subject:"suuren kokonaisluvun" +},int:{unit:"",subject:"kokonaisluvun"},date:{unit:"",subject:"päivämäärän"}} +;function t(t){return e[t]??null}const n={regex:"säännöllinen lauseke", +email:"sähköpostiosoite",url:"URL-osoite",emoji:"emoji",uuid:"UUID", +uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid", +cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO-aikaleima", +date:"ISO-päivämäärä",time:"ISO-aika",duration:"ISO-kesto",ipv4:"IPv4-osoite", +ipv6:"IPv6-osoite",cidrv4:"IPv4-alue",cidrv6:"IPv6-alue", +base64:"base64-koodattu merkkijono",base64url:"base64url-koodattu merkkijono", +json_string:"JSON-merkkijono",e164:"E.164-luku",jwt:"JWT", +template_literal:"templaattimerkkijono"},r={nan:"NaN"};return e=>{ +switch(e.code){case"invalid_type":{ +const t=r[e.expected]??e.expected,n=q(e.input),a=r[n]??n +;return/^[A-Z]/.test(e.expected)?`Virheellinen tyyppi: odotettiin instanceof ${e.expected}, oli ${a}`:`Virheellinen tyyppi: odotettiin ${t}, oli ${a}` +}case"invalid_value": +return 1===e.values.length?`Virheellinen syöte: täytyy olla ${$(e.values[0])}`:`Virheellinen valinta: täytyy olla yksi seuraavista: ${l(e.values,"|")}` +;case"too_big":{const n=e.inclusive?"<=":"<",r=t(e.origin) +;return r?`Liian suuri: ${r.subject} täytyy olla ${n}${e.maximum.toString()} ${r.unit}`.trim():`Liian suuri: arvon täytyy olla ${n}${e.maximum.toString()}` +}case"too_small":{const n=e.inclusive?">=":">",r=t(e.origin) +;return r?`Liian pieni: ${r.subject} täytyy olla ${n}${e.minimum.toString()} ${r.unit}`.trim():`Liian pieni: arvon täytyy olla ${n}${e.minimum.toString()}` +}case"invalid_format":{const t=e +;return"starts_with"===t.format?`Virheellinen syöte: täytyy alkaa "${t.prefix}"`:"ends_with"===t.format?`Virheellinen syöte: täytyy loppua "${t.suffix}"`:"includes"===t.format?`Virheellinen syöte: täytyy sisältää "${t.includes}"`:"regex"===t.format?`Virheellinen syöte: täytyy vastata säännöllistä lauseketta ${t.pattern}`:`Virheellinen ${n[t.format]??e.format}` +}case"not_multiple_of": +return`Virheellinen luku: täytyy olla luvun ${e.divisor} monikerta` +;case"unrecognized_keys": +return`${e.keys.length>1?"Tuntemattomat avaimet":"Tuntematon avain"}: ${l(e.keys,", ")}` +;case"invalid_key":return"Virheellinen avain tietueessa";case"invalid_union": +return"Virheellinen unioni";case"invalid_element": +return"Virheellinen arvo joukossa";default:return"Virheellinen syöte"}}} +;const oa=()=>{const e={string:{unit:"caractères",verb:"avoir"},file:{ +unit:"octets",verb:"avoir"},array:{unit:"éléments",verb:"avoir"},set:{ +unit:"éléments",verb:"avoir"}};function t(t){return e[t]??null}const n={ +regex:"entrée",email:"adresse e-mail",url:"URL",emoji:"emoji",uuid:"UUID", +uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid", +cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"date et heure ISO", +date:"date ISO",time:"heure ISO",duration:"durée ISO",ipv4:"adresse IPv4", +ipv6:"adresse IPv6",cidrv4:"plage IPv4",cidrv6:"plage IPv6", +base64:"chaîne encodée en base64",base64url:"chaîne encodée en base64url", +json_string:"chaîne JSON",e164:"numéro E.164",jwt:"JWT", +template_literal:"entrée"},r={nan:"NaN",number:"nombre",array:"tableau"} +;return e=>{switch(e.code){case"invalid_type":{ +const t=r[e.expected]??e.expected,n=q(e.input),a=r[n]??n +;return/^[A-Z]/.test(e.expected)?`Entrée invalide : instanceof ${e.expected} attendu, ${a} reçu`:`Entrée invalide : ${t} attendu, ${a} reçu` +}case"invalid_value": +return 1===e.values.length?`Entrée invalide : ${$(e.values[0])} attendu`:`Option invalide : une valeur parmi ${l(e.values,"|")} attendue` +;case"too_big":{const n=e.inclusive?"<=":"<",r=t(e.origin) +;return r?`Trop grand : ${e.origin??"valeur"} doit ${r.verb} ${n}${e.maximum.toString()} ${r.unit??"élément(s)"}`:`Trop grand : ${e.origin??"valeur"} doit être ${n}${e.maximum.toString()}` +}case"too_small":{const n=e.inclusive?">=":">",r=t(e.origin) +;return r?`Trop petit : ${e.origin} doit ${r.verb} ${n}${e.minimum.toString()} ${r.unit}`:`Trop petit : ${e.origin} doit être ${n}${e.minimum.toString()}` +}case"invalid_format":{const t=e +;return"starts_with"===t.format?`Chaîne invalide : doit commencer par "${t.prefix}"`:"ends_with"===t.format?`Chaîne invalide : doit se terminer par "${t.suffix}"`:"includes"===t.format?`Chaîne invalide : doit inclure "${t.includes}"`:"regex"===t.format?`Chaîne invalide : doit correspondre au modèle ${t.pattern}`:`${n[t.format]??e.format} invalide` +}case"not_multiple_of": +return`Nombre invalide : doit être un multiple de ${e.divisor}` +;case"unrecognized_keys": +return`Clé${e.keys.length>1?"s":""} non reconnue${e.keys.length>1?"s":""} : ${l(e.keys,", ")}` +;case"invalid_key":return`Clé invalide dans ${e.origin}`;case"invalid_union": +default:return"Entrée invalide";case"invalid_element": +return`Valeur invalide dans ${e.origin}`}}};const ia=()=>{const e={string:{ +unit:"caractères",verb:"avoir"},file:{unit:"octets",verb:"avoir"},array:{ +unit:"éléments",verb:"avoir"},set:{unit:"éléments",verb:"avoir"}};function t(t){ +return e[t]??null}const n={regex:"entrée",email:"adresse courriel",url:"URL", +emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid", +guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID", +datetime:"date-heure ISO",date:"date ISO",time:"heure ISO",duration:"durée ISO", +ipv4:"adresse IPv4",ipv6:"adresse IPv6",cidrv4:"plage IPv4",cidrv6:"plage IPv6", +base64:"chaîne encodée en base64",base64url:"chaîne encodée en base64url", +json_string:"chaîne JSON",e164:"numéro E.164",jwt:"JWT", +template_literal:"entrée"},r={nan:"NaN"};return e=>{switch(e.code){ +case"invalid_type":{const t=r[e.expected]??e.expected,n=q(e.input),a=r[n]??n +;return/^[A-Z]/.test(e.expected)?`Entrée invalide : attendu instanceof ${e.expected}, reçu ${a}`:`Entrée invalide : attendu ${t}, reçu ${a}` +}case"invalid_value": +return 1===e.values.length?`Entrée invalide : attendu ${$(e.values[0])}`:`Option invalide : attendu l'une des valeurs suivantes ${l(e.values,"|")}` +;case"too_big":{const n=e.inclusive?"≤":"<",r=t(e.origin) +;return r?`Trop grand : attendu que ${e.origin??"la valeur"} ait ${n}${e.maximum.toString()} ${r.unit}`:`Trop grand : attendu que ${e.origin??"la valeur"} soit ${n}${e.maximum.toString()}` +}case"too_small":{const n=e.inclusive?"≥":">",r=t(e.origin) +;return r?`Trop petit : attendu que ${e.origin} ait ${n}${e.minimum.toString()} ${r.unit}`:`Trop petit : attendu que ${e.origin} soit ${n}${e.minimum.toString()}` +}case"invalid_format":{const t=e +;return"starts_with"===t.format?`Chaîne invalide : doit commencer par "${t.prefix}"`:"ends_with"===t.format?`Chaîne invalide : doit se terminer par "${t.suffix}"`:"includes"===t.format?`Chaîne invalide : doit inclure "${t.includes}"`:"regex"===t.format?`Chaîne invalide : doit correspondre au motif ${t.pattern}`:`${n[t.format]??e.format} invalide` +}case"not_multiple_of": +return`Nombre invalide : doit être un multiple de ${e.divisor}` +;case"unrecognized_keys": +return`Clé${e.keys.length>1?"s":""} non reconnue${e.keys.length>1?"s":""} : ${l(e.keys,", ")}` +;case"invalid_key":return`Clé invalide dans ${e.origin}`;case"invalid_union": +default:return"Entrée invalide";case"invalid_element": +return`Valeur invalide dans ${e.origin}`}}};const sa=()=>{const e={string:{ +label:"מחרוזת",gender:"f"},number:{label:"מספר",gender:"m"},boolean:{ +label:"ערך בוליאני",gender:"m"},bigint:{label:"BigInt",gender:"m"},date:{ +label:"תאריך",gender:"m"},array:{label:"מערך",gender:"m"},object:{ +label:"אובייקט",gender:"m"},null:{label:"ערך ריק (null)",gender:"m"},undefined:{ +label:"ערך לא מוגדר (undefined)",gender:"m"},symbol:{label:"סימבול (Symbol)", +gender:"m"},function:{label:"פונקציה",gender:"f"},map:{label:"מפה (Map)", +gender:"f"},set:{label:"קבוצה (Set)",gender:"f"},file:{label:"קובץ",gender:"m"}, +promise:{label:"Promise",gender:"m"},NaN:{label:"NaN",gender:"m"},unknown:{ +label:"ערך לא ידוע",gender:"m"},value:{label:"ערך",gender:"m"}},t={string:{ +unit:"תווים",shortLabel:"קצר",longLabel:"ארוך"},file:{unit:"בייטים", +shortLabel:"קטן",longLabel:"גדול"},array:{unit:"פריטים",shortLabel:"קטן", +longLabel:"גדול"},set:{unit:"פריטים",shortLabel:"קטן",longLabel:"גדול"},number:{ +unit:"",shortLabel:"קטן",longLabel:"גדול"}},n=t=>t?e[t]:void 0,r=t=>{ +const r=n(t);return r?r.label:t??e.unknown.label},a=e=>`ה${r(e)}`,o=e=>{ +const t=n(e);return"f"===(t?.gender??"m")?"צריכה להיות":"צריך להיות" +},i=e=>e?t[e]??null:null,s={regex:{label:"קלט",gender:"m"},email:{ +label:"כתובת אימייל",gender:"f"},url:{label:"כתובת רשת",gender:"f"},emoji:{ +label:"אימוג'י",gender:"m"},uuid:{label:"UUID",gender:"m"},nanoid:{ +label:"nanoid",gender:"m"},guid:{label:"GUID",gender:"m"},cuid:{label:"cuid", +gender:"m"},cuid2:{label:"cuid2",gender:"m"},ulid:{label:"ULID",gender:"m"}, +xid:{label:"XID",gender:"m"},ksuid:{label:"KSUID",gender:"m"},datetime:{ +label:"תאריך וזמן ISO",gender:"m"},date:{label:"תאריך ISO",gender:"m"},time:{ +label:"זמן ISO",gender:"m"},duration:{label:"משך זמן ISO",gender:"m"},ipv4:{ +label:"כתובת IPv4",gender:"f"},ipv6:{label:"כתובת IPv6",gender:"f"},cidrv4:{ +label:"טווח IPv4",gender:"m"},cidrv6:{label:"טווח IPv6",gender:"m"},base64:{ +label:"מחרוזת בבסיס 64",gender:"f"},base64url:{ +label:"מחרוזת בבסיס 64 לכתובות רשת",gender:"f"},json_string:{ +label:"מחרוזת JSON",gender:"f"},e164:{label:"מספר E.164",gender:"m"},jwt:{ +label:"JWT",gender:"m"},ends_with:{label:"קלט",gender:"m"},includes:{ +label:"קלט",gender:"m"},lowercase:{label:"קלט",gender:"m"},starts_with:{ +label:"קלט",gender:"m"},uppercase:{label:"קלט",gender:"m"}},c={nan:"NaN"} +;return t=>{switch(t.code){case"invalid_type":{ +const n=t.expected,a=c[n??""]??r(n),o=q(t.input),i=c[o]??e[o]?.label??o +;return/^[A-Z]/.test(t.expected)?`קלט לא תקין: צריך להיות instanceof ${t.expected}, התקבל ${i}`:`קלט לא תקין: צריך להיות ${a}, התקבל ${i}` +}case"invalid_value":{ +if(1===t.values.length)return`ערך לא תקין: הערך חייב להיות ${$(t.values[0])}` +;const e=t.values.map((e=>$(e))) +;if(2===t.values.length)return`ערך לא תקין: האפשרויות המתאימות הן ${e[0]} או ${e[1]}` +;const n=e[e.length-1] +;return`ערך לא תקין: האפשרויות המתאימות הן ${e.slice(0,-1).join(", ")} או ${n}`} +case"too_big":{const e=i(t.origin),n=a(t.origin??"value") +;if("string"===t.origin)return`${e?.longLabel??"ארוך"} מדי: ${n} צריכה להכיל ${t.maximum.toString()} ${e?.unit??""} ${t.inclusive?"או פחות":"לכל היותר"}`.trim() +;if("number"===t.origin){ +return`גדול מדי: ${n} צריך להיות ${t.inclusive?`קטן או שווה ל-${t.maximum}`:`קטן מ-${t.maximum}`}` +}if("array"===t.origin||"set"===t.origin){ +return`גדול מדי: ${n} ${"set"===t.origin?"צריכה":"צריך"} להכיל ${t.inclusive?`${t.maximum} ${e?.unit??""} או פחות`:`פחות מ-${t.maximum} ${e?.unit??""}`}`.trim() +}const r=t.inclusive?"<=":"<",s=o(t.origin??"value") +;return e?.unit?`${e.longLabel} מדי: ${n} ${s} ${r}${t.maximum.toString()} ${e.unit}`:`${e?.longLabel??"גדול"} מדי: ${n} ${s} ${r}${t.maximum.toString()}` +}case"too_small":{const e=i(t.origin),n=a(t.origin??"value") +;if("string"===t.origin)return`${e?.shortLabel??"קצר"} מדי: ${n} צריכה להכיל ${t.minimum.toString()} ${e?.unit??""} ${t.inclusive?"או יותר":"לפחות"}`.trim() +;if("number"===t.origin){ +return`קטן מדי: ${n} צריך להיות ${t.inclusive?`גדול או שווה ל-${t.minimum}`:`גדול מ-${t.minimum}`}` +}if("array"===t.origin||"set"===t.origin){ +const r="set"===t.origin?"צריכה":"צריך";if(1===t.minimum&&t.inclusive){ +return`קטן מדי: ${n} ${r} להכיל ${t.origin,"לפחות פריט אחד"}`} +return`קטן מדי: ${n} ${r} להכיל ${t.inclusive?`${t.minimum} ${e?.unit??""} או יותר`:`יותר מ-${t.minimum} ${e?.unit??""}`}`.trim() +}const r=t.inclusive?">=":">",s=o(t.origin??"value") +;return e?.unit?`${e.shortLabel} מדי: ${n} ${s} ${r}${t.minimum.toString()} ${e.unit}`:`${e?.shortLabel??"קטן"} מדי: ${n} ${s} ${r}${t.minimum.toString()}` +}case"invalid_format":{const e=t +;if("starts_with"===e.format)return`המחרוזת חייבת להתחיל ב "${e.prefix}"` +;if("ends_with"===e.format)return`המחרוזת חייבת להסתיים ב "${e.suffix}"` +;if("includes"===e.format)return`המחרוזת חייבת לכלול "${e.includes}"` +;if("regex"===e.format)return`המחרוזת חייבת להתאים לתבנית ${e.pattern}` +;const n=s[e.format] +;return`${n?.label??e.format} לא ${"f"===(n?.gender??"m")?"תקינה":"תקין"}`} +case"not_multiple_of":return`מספר לא תקין: חייב להיות מכפלה של ${t.divisor}` +;case"unrecognized_keys": +return`מפתח${t.keys.length>1?"ות":""} לא מזוה${t.keys.length>1?"ים":"ה"}: ${l(t.keys,", ")}` +;case"invalid_key":return"שדה לא תקין באובייקט";case"invalid_union":default: +return"קלט לא תקין";case"invalid_element": +return`ערך לא תקין ב${a(t.origin??"array")}`}}};const la=()=>{const e={string:{ +unit:"karakter",verb:"legyen"},file:{unit:"byte",verb:"legyen"},array:{ +unit:"elem",verb:"legyen"},set:{unit:"elem",verb:"legyen"}};function t(t){ +return e[t]??null}const n={regex:"bemenet",email:"email cím",url:"URL", +emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid", +guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID", +datetime:"ISO időbélyeg",date:"ISO dátum",time:"ISO idő", +duration:"ISO időintervallum",ipv4:"IPv4 cím",ipv6:"IPv6 cím", +cidrv4:"IPv4 tartomány",cidrv6:"IPv6 tartomány",base64:"base64-kódolt string", +base64url:"base64url-kódolt string",json_string:"JSON string",e164:"E.164 szám", +jwt:"JWT",template_literal:"bemenet"},r={nan:"NaN",number:"szám",array:"tömb"} +;return e=>{switch(e.code){case"invalid_type":{ +const t=r[e.expected]??e.expected,n=q(e.input),a=r[n]??n +;return/^[A-Z]/.test(e.expected)?`Érvénytelen bemenet: a várt érték instanceof ${e.expected}, a kapott érték ${a}`:`Érvénytelen bemenet: a várt érték ${t}, a kapott érték ${a}` +}case"invalid_value": +return 1===e.values.length?`Érvénytelen bemenet: a várt érték ${$(e.values[0])}`:`Érvénytelen opció: valamelyik érték várt ${l(e.values,"|")}` +;case"too_big":{const n=e.inclusive?"<=":"<",r=t(e.origin) +;return r?`Túl nagy: ${e.origin??"érték"} mérete túl nagy ${n}${e.maximum.toString()} ${r.unit??"elem"}`:`Túl nagy: a bemeneti érték ${e.origin??"érték"} túl nagy: ${n}${e.maximum.toString()}` +}case"too_small":{const n=e.inclusive?">=":">",r=t(e.origin) +;return r?`Túl kicsi: a bemeneti érték ${e.origin} mérete túl kicsi ${n}${e.minimum.toString()} ${r.unit}`:`Túl kicsi: a bemeneti érték ${e.origin} túl kicsi ${n}${e.minimum.toString()}` +}case"invalid_format":{const t=e +;return"starts_with"===t.format?`Érvénytelen string: "${t.prefix}" értékkel kell kezdődnie`:"ends_with"===t.format?`Érvénytelen string: "${t.suffix}" értékkel kell végződnie`:"includes"===t.format?`Érvénytelen string: "${t.includes}" értéket kell tartalmaznia`:"regex"===t.format?`Érvénytelen string: ${t.pattern} mintának kell megfelelnie`:`Érvénytelen ${n[t.format]??e.format}` +}case"not_multiple_of": +return`Érvénytelen szám: ${e.divisor} többszörösének kell lennie` +;case"unrecognized_keys": +return`Ismeretlen kulcs${e.keys.length>1?"s":""}: ${l(e.keys,", ")}` +;case"invalid_key":return`Érvénytelen kulcs ${e.origin}`;case"invalid_union": +default:return"Érvénytelen bemenet";case"invalid_element": +return`Érvénytelen érték: ${e.origin}`}}};function ca(e,t,n){ +return 1===Math.abs(e)?t:n}function ua(e){if(!e)return"";const t=e[e.length-1] +;return e+(["ա","ե","ը","ի","ո","ու","օ"].includes(t)?"ն":"ը")}const da=()=>{ +const e={string:{unit:{one:"նշան",many:"նշաններ"},verb:"ունենալ"},file:{unit:{ +one:"բայթ",many:"բայթեր"},verb:"ունենալ"},array:{unit:{one:"տարր",many:"տարրեր" +},verb:"ունենալ"},set:{unit:{one:"տարր",many:"տարրեր"},verb:"ունենալ"}} +;function t(t){return e[t]??null}const n={regex:"մուտք",email:"էլ. հասցե", +url:"URL",emoji:"էմոջի",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6", +nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID", +ksuid:"KSUID",datetime:"ISO ամսաթիվ և ժամ",date:"ISO ամսաթիվ",time:"ISO ժամ", +duration:"ISO տևողություն",ipv4:"IPv4 հասցե",ipv6:"IPv6 հասցե", +cidrv4:"IPv4 միջակայք",cidrv6:"IPv6 միջակայք",base64:"base64 ձևաչափով տող", +base64url:"base64url ձևաչափով տող",json_string:"JSON տող",e164:"E.164 համար", +jwt:"JWT",template_literal:"մուտք"},r={nan:"NaN",number:"թիվ",array:"զանգված"} +;return e=>{switch(e.code){case"invalid_type":{ +const t=r[e.expected]??e.expected,n=q(e.input),a=r[n]??n +;return/^[A-Z]/.test(e.expected)?`Սխալ մուտքագրում․ սպասվում էր instanceof ${e.expected}, ստացվել է ${a}`:`Սխալ մուտքագրում․ սպասվում էր ${t}, ստացվել է ${a}` +}case"invalid_value": +return 1===e.values.length?`Սխալ մուտքագրում․ սպասվում էր ${$(e.values[1])}`:`Սխալ տարբերակ․ սպասվում էր հետևյալներից մեկը՝ ${l(e.values,"|")}` +;case"too_big":{const n=e.inclusive?"<=":"<",r=t(e.origin);if(r){ +const t=ca(Number(e.maximum),r.unit.one,r.unit.many) +;return`Չափազանց մեծ արժեք․ սպասվում է, որ ${ua(e.origin??"արժեք")} կունենա ${n}${e.maximum.toString()} ${t}` +} +return`Չափազանց մեծ արժեք․ սպասվում է, որ ${ua(e.origin??"արժեք")} լինի ${n}${e.maximum.toString()}` +}case"too_small":{const n=e.inclusive?">=":">",r=t(e.origin);if(r){ +const t=ca(Number(e.minimum),r.unit.one,r.unit.many) +;return`Չափազանց փոքր արժեք․ սպասվում է, որ ${ua(e.origin)} կունենա ${n}${e.minimum.toString()} ${t}` +} +return`Չափազանց փոքր արժեք․ սպասվում է, որ ${ua(e.origin)} լինի ${n}${e.minimum.toString()}` +}case"invalid_format":{const t=e +;return"starts_with"===t.format?`Սխալ տող․ պետք է սկսվի "${t.prefix}"-ով`:"ends_with"===t.format?`Սխալ տող․ պետք է ավարտվի "${t.suffix}"-ով`:"includes"===t.format?`Սխալ տող․ պետք է պարունակի "${t.includes}"`:"regex"===t.format?`Սխալ տող․ պետք է համապատասխանի ${t.pattern} ձևաչափին`:`Սխալ ${n[t.format]??e.format}` +}case"not_multiple_of":return`Սխալ թիվ․ պետք է բազմապատիկ լինի ${e.divisor}-ի` +;case"unrecognized_keys": +return`Չճանաչված բանալի${e.keys.length>1?"ներ":""}. ${l(e.keys,", ")}` +;case"invalid_key":return`Սխալ բանալի ${ua(e.origin)}-ում`;case"invalid_union": +default:return"Սխալ մուտքագրում";case"invalid_element": +return`Սխալ արժեք ${ua(e.origin)}-ում`}}};const pa=()=>{const e={string:{ +unit:"karakter",verb:"memiliki"},file:{unit:"byte",verb:"memiliki"},array:{ +unit:"item",verb:"memiliki"},set:{unit:"item",verb:"memiliki"}};function t(t){ +return e[t]??null}const n={regex:"input",email:"alamat email",url:"URL", +emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid", +guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID", +datetime:"tanggal dan waktu format ISO",date:"tanggal format ISO", +time:"jam format ISO",duration:"durasi format ISO",ipv4:"alamat IPv4", +ipv6:"alamat IPv6",cidrv4:"rentang alamat IPv4",cidrv6:"rentang alamat IPv6", +base64:"string dengan enkode base64",base64url:"string dengan enkode base64url", +json_string:"string JSON",e164:"angka E.164",jwt:"JWT",template_literal:"input" +},r={nan:"NaN"};return e=>{switch(e.code){case"invalid_type":{ +const t=r[e.expected]??e.expected,n=q(e.input),a=r[n]??n +;return/^[A-Z]/.test(e.expected)?`Input tidak valid: diharapkan instanceof ${e.expected}, diterima ${a}`:`Input tidak valid: diharapkan ${t}, diterima ${a}` +}case"invalid_value": +return 1===e.values.length?`Input tidak valid: diharapkan ${$(e.values[0])}`:`Pilihan tidak valid: diharapkan salah satu dari ${l(e.values,"|")}` +;case"too_big":{const n=e.inclusive?"<=":"<",r=t(e.origin) +;return r?`Terlalu besar: diharapkan ${e.origin??"value"} memiliki ${n}${e.maximum.toString()} ${r.unit??"elemen"}`:`Terlalu besar: diharapkan ${e.origin??"value"} menjadi ${n}${e.maximum.toString()}` +}case"too_small":{const n=e.inclusive?">=":">",r=t(e.origin) +;return r?`Terlalu kecil: diharapkan ${e.origin} memiliki ${n}${e.minimum.toString()} ${r.unit}`:`Terlalu kecil: diharapkan ${e.origin} menjadi ${n}${e.minimum.toString()}` +}case"invalid_format":{const t=e +;return"starts_with"===t.format?`String tidak valid: harus dimulai dengan "${t.prefix}"`:"ends_with"===t.format?`String tidak valid: harus berakhir dengan "${t.suffix}"`:"includes"===t.format?`String tidak valid: harus menyertakan "${t.includes}"`:"regex"===t.format?`String tidak valid: harus sesuai pola ${t.pattern}`:`${n[t.format]??e.format} tidak valid` +}case"not_multiple_of": +return`Angka tidak valid: harus kelipatan dari ${e.divisor}` +;case"unrecognized_keys": +return`Kunci tidak dikenali ${e.keys.length>1?"s":""}: ${l(e.keys,", ")}` +;case"invalid_key":return`Kunci tidak valid di ${e.origin}`;case"invalid_union": +default:return"Input tidak valid";case"invalid_element": +return`Nilai tidak valid di ${e.origin}`}}};const ha=()=>{const e={string:{ +unit:"stafi",verb:"að hafa"},file:{unit:"bæti",verb:"að hafa"},array:{ +unit:"hluti",verb:"að hafa"},set:{unit:"hluti",verb:"að hafa"}};function t(t){ +return e[t]??null}const n={regex:"gildi",email:"netfang",url:"vefslóð", +emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid", +guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID", +datetime:"ISO dagsetning og tími",date:"ISO dagsetning",time:"ISO tími", +duration:"ISO tímalengd",ipv4:"IPv4 address",ipv6:"IPv6 address", +cidrv4:"IPv4 range",cidrv6:"IPv6 range",base64:"base64-encoded strengur", +base64url:"base64url-encoded strengur",json_string:"JSON strengur", +e164:"E.164 tölugildi",jwt:"JWT",template_literal:"gildi"},r={nan:"NaN", +number:"númer",array:"fylki"};return e=>{switch(e.code){case"invalid_type":{ +const t=r[e.expected]??e.expected,n=q(e.input),a=r[n]??n +;return/^[A-Z]/.test(e.expected)?`Rangt gildi: Þú slóst inn ${a} þar sem á að vera instanceof ${e.expected}`:`Rangt gildi: Þú slóst inn ${a} þar sem á að vera ${t}` +}case"invalid_value": +return 1===e.values.length?`Rangt gildi: gert ráð fyrir ${$(e.values[0])}`:`Ógilt val: má vera eitt af eftirfarandi ${l(e.values,"|")}` +;case"too_big":{const n=e.inclusive?"<=":"<",r=t(e.origin) +;return r?`Of stórt: gert er ráð fyrir að ${e.origin??"gildi"} hafi ${n}${e.maximum.toString()} ${r.unit??"hluti"}`:`Of stórt: gert er ráð fyrir að ${e.origin??"gildi"} sé ${n}${e.maximum.toString()}` +}case"too_small":{const n=e.inclusive?">=":">",r=t(e.origin) +;return r?`Of lítið: gert er ráð fyrir að ${e.origin} hafi ${n}${e.minimum.toString()} ${r.unit}`:`Of lítið: gert er ráð fyrir að ${e.origin} sé ${n}${e.minimum.toString()}` +}case"invalid_format":{const t=e +;return"starts_with"===t.format?`Ógildur strengur: verður að byrja á "${t.prefix}"`:"ends_with"===t.format?`Ógildur strengur: verður að enda á "${t.suffix}"`:"includes"===t.format?`Ógildur strengur: verður að innihalda "${t.includes}"`:"regex"===t.format?`Ógildur strengur: verður að fylgja mynstri ${t.pattern}`:`Rangt ${n[t.format]??e.format}` +}case"not_multiple_of": +return`Röng tala: verður að vera margfeldi af ${e.divisor}` +;case"unrecognized_keys": +return`Óþekkt ${e.keys.length>1?"ir lyklar":"ur lykill"}: ${l(e.keys,", ")}` +;case"invalid_key":return`Rangur lykill í ${e.origin}`;case"invalid_union": +default:return"Rangt gildi";case"invalid_element": +return`Rangt gildi í ${e.origin}`}}};const fa=()=>{const e={string:{ +unit:"caratteri",verb:"avere"},file:{unit:"byte",verb:"avere"},array:{ +unit:"elementi",verb:"avere"},set:{unit:"elementi",verb:"avere"}};function t(t){ +return e[t]??null}const n={regex:"input",email:"indirizzo email",url:"URL", +emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid", +guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID", +datetime:"data e ora ISO",date:"data ISO",time:"ora ISO",duration:"durata ISO", +ipv4:"indirizzo IPv4",ipv6:"indirizzo IPv6",cidrv4:"intervallo IPv4", +cidrv6:"intervallo IPv6",base64:"stringa codificata in base64", +base64url:"URL codificata in base64",json_string:"stringa JSON", +e164:"numero E.164",jwt:"JWT",template_literal:"input"},r={nan:"NaN", +number:"numero",array:"vettore"};return e=>{switch(e.code){case"invalid_type":{ +const t=r[e.expected]??e.expected,n=q(e.input),a=r[n]??n +;return/^[A-Z]/.test(e.expected)?`Input non valido: atteso instanceof ${e.expected}, ricevuto ${a}`:`Input non valido: atteso ${t}, ricevuto ${a}` +}case"invalid_value": +return 1===e.values.length?`Input non valido: atteso ${$(e.values[0])}`:`Opzione non valida: atteso uno tra ${l(e.values,"|")}` +;case"too_big":{const n=e.inclusive?"<=":"<",r=t(e.origin) +;return r?`Troppo grande: ${e.origin??"valore"} deve avere ${n}${e.maximum.toString()} ${r.unit??"elementi"}`:`Troppo grande: ${e.origin??"valore"} deve essere ${n}${e.maximum.toString()}` +}case"too_small":{const n=e.inclusive?">=":">",r=t(e.origin) +;return r?`Troppo piccolo: ${e.origin} deve avere ${n}${e.minimum.toString()} ${r.unit}`:`Troppo piccolo: ${e.origin} deve essere ${n}${e.minimum.toString()}` +}case"invalid_format":{const t=e +;return"starts_with"===t.format?`Stringa non valida: deve iniziare con "${t.prefix}"`:"ends_with"===t.format?`Stringa non valida: deve terminare con "${t.suffix}"`:"includes"===t.format?`Stringa non valida: deve includere "${t.includes}"`:"regex"===t.format?`Stringa non valida: deve corrispondere al pattern ${t.pattern}`:`Invalid ${n[t.format]??e.format}` +}case"not_multiple_of": +return`Numero non valido: deve essere un multiplo di ${e.divisor}` +;case"unrecognized_keys": +return`Chiav${e.keys.length>1?"i":"e"} non riconosciut${e.keys.length>1?"e":"a"}: ${l(e.keys,", ")}` +;case"invalid_key":return`Chiave non valida in ${e.origin}`;case"invalid_union": +default:return"Input non valido";case"invalid_element": +return`Valore non valido in ${e.origin}`}}};const ma=()=>{const e={string:{ +unit:"文字",verb:"である"},file:{unit:"バイト",verb:"である"},array:{unit:"要素",verb:"である"}, +set:{unit:"要素",verb:"である"}};function t(t){return e[t]??null}const n={ +regex:"入力値",email:"メールアドレス",url:"URL",emoji:"絵文字",uuid:"UUID",uuidv4:"UUIDv4", +uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2", +ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO日時",date:"ISO日付",time:"ISO時刻", +duration:"ISO期間",ipv4:"IPv4アドレス",ipv6:"IPv6アドレス",cidrv4:"IPv4範囲", +cidrv6:"IPv6範囲",base64:"base64エンコード文字列",base64url:"base64urlエンコード文字列", +json_string:"JSON文字列",e164:"E.164番号",jwt:"JWT",template_literal:"入力値"},r={ +nan:"NaN",number:"数値",array:"配列"};return e=>{switch(e.code){case"invalid_type":{ +const t=r[e.expected]??e.expected,n=q(e.input),a=r[n]??n +;return/^[A-Z]/.test(e.expected)?`無効な入力: instanceof ${e.expected}が期待されましたが、${a}が入力されました`:`無効な入力: ${t}が期待されましたが、${a}が入力されました` +}case"invalid_value": +return 1===e.values.length?`無効な入力: ${$(e.values[0])}が期待されました`:`無効な選択: ${l(e.values,"、")}のいずれかである必要があります` +;case"too_big":{const n=e.inclusive?"以下である":"より小さい",r=t(e.origin) +;return r?`大きすぎる値: ${e.origin??"値"}は${e.maximum.toString()}${r.unit??"要素"}${n}必要があります`:`大きすぎる値: ${e.origin??"値"}は${e.maximum.toString()}${n}必要があります` +}case"too_small":{const n=e.inclusive?"以上である":"より大きい",r=t(e.origin) +;return r?`小さすぎる値: ${e.origin}は${e.minimum.toString()}${r.unit}${n}必要があります`:`小さすぎる値: ${e.origin}は${e.minimum.toString()}${n}必要があります` +}case"invalid_format":{const t=e +;return"starts_with"===t.format?`無効な文字列: "${t.prefix}"で始まる必要があります`:"ends_with"===t.format?`無効な文字列: "${t.suffix}"で終わる必要があります`:"includes"===t.format?`無効な文字列: "${t.includes}"を含む必要があります`:"regex"===t.format?`無効な文字列: パターン${t.pattern}に一致する必要があります`:`無効な${n[t.format]??e.format}` +}case"not_multiple_of":return`無効な数値: ${e.divisor}の倍数である必要があります` +;case"unrecognized_keys": +return`認識されていないキー${e.keys.length>1?"群":""}: ${l(e.keys,"、")}`;case"invalid_key": +return`${e.origin}内の無効なキー`;case"invalid_union":default:return"無効な入力" +;case"invalid_element":return`${e.origin}内の無効な値`}}};const ga=()=>{const e={ +string:{unit:"სიმბოლო",verb:"უნდა შეიცავდეს"},file:{unit:"ბაიტი", +verb:"უნდა შეიცავდეს"},array:{unit:"ელემენტი",verb:"უნდა შეიცავდეს"},set:{ +unit:"ელემენტი",verb:"უნდა შეიცავდეს"}};function t(t){return e[t]??null} +const n={regex:"შეყვანა",email:"ელ-ფოსტის მისამართი",url:"URL",emoji:"ემოჯი", +uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID", +cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID", +datetime:"თარიღი-დრო",date:"თარიღი",time:"დრო",duration:"ხანგრძლივობა", +ipv4:"IPv4 მისამართი",ipv6:"IPv6 მისამართი",cidrv4:"IPv4 დიაპაზონი", +cidrv6:"IPv6 დიაპაზონი",base64:"base64-კოდირებული სტრინგი", +base64url:"base64url-კოდირებული სტრინგი",json_string:"JSON სტრინგი", +e164:"E.164 ნომერი",jwt:"JWT",template_literal:"შეყვანა"},r={nan:"NaN", +number:"რიცხვი",string:"სტრინგი",boolean:"ბულეანი",function:"ფუნქცია", +array:"მასივი"};return e=>{switch(e.code){case"invalid_type":{ +const t=r[e.expected]??e.expected,n=q(e.input),a=r[n]??n +;return/^[A-Z]/.test(e.expected)?`არასწორი შეყვანა: მოსალოდნელი instanceof ${e.expected}, მიღებული ${a}`:`არასწორი შეყვანა: მოსალოდნელი ${t}, მიღებული ${a}` +}case"invalid_value": +return 1===e.values.length?`არასწორი შეყვანა: მოსალოდნელი ${$(e.values[0])}`:`არასწორი ვარიანტი: მოსალოდნელია ერთ-ერთი ${l(e.values,"|")}-დან` +;case"too_big":{const n=e.inclusive?"<=":"<",r=t(e.origin) +;return r?`ზედმეტად დიდი: მოსალოდნელი ${e.origin??"მნიშვნელობა"} ${r.verb} ${n}${e.maximum.toString()} ${r.unit}`:`ზედმეტად დიდი: მოსალოდნელი ${e.origin??"მნიშვნელობა"} იყოს ${n}${e.maximum.toString()}` +}case"too_small":{const n=e.inclusive?">=":">",r=t(e.origin) +;return r?`ზედმეტად პატარა: მოსალოდნელი ${e.origin} ${r.verb} ${n}${e.minimum.toString()} ${r.unit}`:`ზედმეტად პატარა: მოსალოდნელი ${e.origin} იყოს ${n}${e.minimum.toString()}` +}case"invalid_format":{const t=e +;return"starts_with"===t.format?`არასწორი სტრინგი: უნდა იწყებოდეს "${t.prefix}"-ით`:"ends_with"===t.format?`არასწორი სტრინგი: უნდა მთავრდებოდეს "${t.suffix}"-ით`:"includes"===t.format?`არასწორი სტრინგი: უნდა შეიცავდეს "${t.includes}"-ს`:"regex"===t.format?`არასწორი სტრინგი: უნდა შეესაბამებოდეს შაბლონს ${t.pattern}`:`არასწორი ${n[t.format]??e.format}` +}case"not_multiple_of":return`არასწორი რიცხვი: უნდა იყოს ${e.divisor}-ის ჯერადი` +;case"unrecognized_keys": +return`უცნობი გასაღებ${e.keys.length>1?"ები":"ი"}: ${l(e.keys,", ")}` +;case"invalid_key":return`არასწორი გასაღები ${e.origin}-ში`;case"invalid_union": +default:return"არასწორი შეყვანა";case"invalid_element": +return`არასწორი მნიშვნელობა ${e.origin}-ში`}}};const va=()=>{const e={string:{ +unit:"តួអក្សរ",verb:"គួរមាន"},file:{unit:"បៃ",verb:"គួរមាន"},array:{unit:"ធាតុ", +verb:"គួរមាន"},set:{unit:"ធាតុ",verb:"គួរមាន"}};function t(t){return e[t]??null} +const n={regex:"ទិន្នន័យបញ្ចូល",email:"អាសយដ្ឋានអ៊ីមែល",url:"URL", +emoji:"សញ្ញាអារម្មណ៍",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6", +nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID", +ksuid:"KSUID",datetime:"កាលបរិច្ឆេទ និងម៉ោង ISO",date:"កាលបរិច្ឆេទ ISO", +time:"ម៉ោង ISO",duration:"រយៈពេល ISO",ipv4:"អាសយដ្ឋាន IPv4", +ipv6:"អាសយដ្ឋាន IPv6",cidrv4:"ដែនអាសយដ្ឋាន IPv4",cidrv6:"ដែនអាសយដ្ឋាន IPv6", +base64:"ខ្សែអក្សរអ៊ិកូដ base64",base64url:"ខ្សែអក្សរអ៊ិកូដ base64url", +json_string:"ខ្សែអក្សរ JSON",e164:"លេខ E.164",jwt:"JWT", +template_literal:"ទិន្នន័យបញ្ចូល"},r={nan:"NaN",number:"លេខ", +array:"អារេ (Array)",null:"គ្មានតម្លៃ (null)"};return e=>{switch(e.code){ +case"invalid_type":{const t=r[e.expected]??e.expected,n=q(e.input),a=r[n]??n +;return/^[A-Z]/.test(e.expected)?`ទិន្នន័យបញ្ចូលមិនត្រឹមត្រូវ៖ ត្រូវការ instanceof ${e.expected} ប៉ុន្តែទទួលបាន ${a}`:`ទិន្នន័យបញ្ចូលមិនត្រឹមត្រូវ៖ ត្រូវការ ${t} ប៉ុន្តែទទួលបាន ${a}` +}case"invalid_value": +return 1===e.values.length?`ទិន្នន័យបញ្ចូលមិនត្រឹមត្រូវ៖ ត្រូវការ ${$(e.values[0])}`:`ជម្រើសមិនត្រឹមត្រូវ៖ ត្រូវជាមួយក្នុងចំណោម ${l(e.values,"|")}` +;case"too_big":{const n=e.inclusive?"<=":"<",r=t(e.origin) +;return r?`ធំពេក៖ ត្រូវការ ${e.origin??"តម្លៃ"} ${n} ${e.maximum.toString()} ${r.unit??"ធាតុ"}`:`ធំពេក៖ ត្រូវការ ${e.origin??"តម្លៃ"} ${n} ${e.maximum.toString()}` +}case"too_small":{const n=e.inclusive?">=":">",r=t(e.origin) +;return r?`តូចពេក៖ ត្រូវការ ${e.origin} ${n} ${e.minimum.toString()} ${r.unit}`:`តូចពេក៖ ត្រូវការ ${e.origin} ${n} ${e.minimum.toString()}` +}case"invalid_format":{const t=e +;return"starts_with"===t.format?`ខ្សែអក្សរមិនត្រឹមត្រូវ៖ ត្រូវចាប់ផ្តើមដោយ "${t.prefix}"`:"ends_with"===t.format?`ខ្សែអក្សរមិនត្រឹមត្រូវ៖ ត្រូវបញ្ចប់ដោយ "${t.suffix}"`:"includes"===t.format?`ខ្សែអក្សរមិនត្រឹមត្រូវ៖ ត្រូវមាន "${t.includes}"`:"regex"===t.format?`ខ្សែអក្សរមិនត្រឹមត្រូវ៖ ត្រូវតែផ្គូផ្គងនឹងទម្រង់ដែលបានកំណត់ ${t.pattern}`:`មិនត្រឹមត្រូវ៖ ${n[t.format]??e.format}` +}case"not_multiple_of":return`លេខមិនត្រឹមត្រូវ៖ ត្រូវតែជាពហុគុណនៃ ${e.divisor}` +;case"unrecognized_keys":return`រកឃើញសោមិនស្គាល់៖ ${l(e.keys,", ")}` +;case"invalid_key":return`សោមិនត្រឹមត្រូវនៅក្នុង ${e.origin}` +;case"invalid_union":default:return"ទិន្នន័យមិនត្រឹមត្រូវ" +;case"invalid_element":return`ទិន្នន័យមិនត្រឹមត្រូវនៅក្នុង ${e.origin}`}}} +;function ba(){return{localeError:va()}}const ya=()=>{const e={string:{ +unit:"문자",verb:"to have"},file:{unit:"바이트",verb:"to have"},array:{unit:"개", +verb:"to have"},set:{unit:"개",verb:"to have"}};function t(t){return e[t]??null} +const n={regex:"입력",email:"이메일 주소",url:"URL",emoji:"이모지",uuid:"UUID", +uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid", +cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO 날짜시간", +date:"ISO 날짜",time:"ISO 시간",duration:"ISO 기간",ipv4:"IPv4 주소",ipv6:"IPv6 주소", +cidrv4:"IPv4 범위",cidrv6:"IPv6 범위",base64:"base64 인코딩 문자열", +base64url:"base64url 인코딩 문자열",json_string:"JSON 문자열",e164:"E.164 번호",jwt:"JWT", +template_literal:"입력"},r={nan:"NaN"};return e=>{switch(e.code){ +case"invalid_type":{const t=r[e.expected]??e.expected,n=q(e.input),a=r[n]??n +;return/^[A-Z]/.test(e.expected)?`잘못된 입력: 예상 타입은 instanceof ${e.expected}, 받은 타입은 ${a}입니다`:`잘못된 입력: 예상 타입은 ${t}, 받은 타입은 ${a}입니다` +}case"invalid_value": +return 1===e.values.length?`잘못된 입력: 값은 ${$(e.values[0])} 이어야 합니다`:`잘못된 옵션: ${l(e.values,"또는 ")} 중 하나여야 합니다` +;case"too_big":{ +const n=e.inclusive?"이하":"미만",r="미만"===n?"이어야 합니다":"여야 합니다",a=t(e.origin),o=a?.unit??"요소" +;return a?`${e.origin??"값"}이 너무 큽니다: ${e.maximum.toString()}${o} ${n}${r}`:`${e.origin??"값"}이 너무 큽니다: ${e.maximum.toString()} ${n}${r}` +}case"too_small":{ +const n=e.inclusive?"이상":"초과",r="이상"===n?"이어야 합니다":"여야 합니다",a=t(e.origin),o=a?.unit??"요소" +;return a?`${e.origin??"값"}이 너무 작습니다: ${e.minimum.toString()}${o} ${n}${r}`:`${e.origin??"값"}이 너무 작습니다: ${e.minimum.toString()} ${n}${r}` +}case"invalid_format":{const t=e +;return"starts_with"===t.format?`잘못된 문자열: "${t.prefix}"(으)로 시작해야 합니다`:"ends_with"===t.format?`잘못된 문자열: "${t.suffix}"(으)로 끝나야 합니다`:"includes"===t.format?`잘못된 문자열: "${t.includes}"을(를) 포함해야 합니다`:"regex"===t.format?`잘못된 문자열: 정규식 ${t.pattern} 패턴과 일치해야 합니다`:`잘못된 ${n[t.format]??e.format}` +}case"not_multiple_of":return`잘못된 숫자: ${e.divisor}의 배수여야 합니다` +;case"unrecognized_keys":return`인식할 수 없는 키: ${l(e.keys,", ")}` +;case"invalid_key":return`잘못된 키: ${e.origin}`;case"invalid_union":default: +return"잘못된 입력";case"invalid_element":return`잘못된 값: ${e.origin}`}}} +;const Oa=e=>e.charAt(0).toUpperCase()+e.slice(1);function wa(e){ +const t=Math.abs(e),n=t%10,r=t%100 +;return r>=11&&r<=19||0===n?"many":1===n?"one":"few"}const xa=()=>{const e={ +string:{unit:{one:"simbolis",few:"simboliai",many:"simbolių"},verb:{smaller:{ +inclusive:"turi būti ne ilgesnė kaip",notInclusive:"turi būti trumpesnė kaip"}, +bigger:{inclusive:"turi būti ne trumpesnė kaip", +notInclusive:"turi būti ilgesnė kaip"}}},file:{unit:{one:"baitas",few:"baitai", +many:"baitų"},verb:{smaller:{inclusive:"turi būti ne didesnis kaip", +notInclusive:"turi būti mažesnis kaip"},bigger:{ +inclusive:"turi būti ne mažesnis kaip",notInclusive:"turi būti didesnis kaip"}} +},array:{unit:{one:"elementą",few:"elementus",many:"elementų"},verb:{smaller:{ +inclusive:"turi turėti ne daugiau kaip",notInclusive:"turi turėti mažiau kaip"}, +bigger:{inclusive:"turi turėti ne mažiau kaip", +notInclusive:"turi turėti daugiau kaip"}}},set:{unit:{one:"elementą", +few:"elementus",many:"elementų"},verb:{smaller:{ +inclusive:"turi turėti ne daugiau kaip",notInclusive:"turi turėti mažiau kaip"}, +bigger:{inclusive:"turi turėti ne mažiau kaip", +notInclusive:"turi turėti daugiau kaip"}}}};function t(t,n,r,a){ +const o=e[t]??null;return null===o?o:{unit:o.unit[n], +verb:o.verb[a][r?"inclusive":"notInclusive"]}}const n={regex:"įvestis", +email:"el. pašto adresas",url:"URL",emoji:"jaustukas",uuid:"UUID", +uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid", +cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO data ir laikas", +date:"ISO data",time:"ISO laikas",duration:"ISO trukmė",ipv4:"IPv4 adresas", +ipv6:"IPv6 adresas",cidrv4:"IPv4 tinklo prefiksas (CIDR)", +cidrv6:"IPv6 tinklo prefiksas (CIDR)",base64:"base64 užkoduota eilutė", +base64url:"base64url užkoduota eilutė",json_string:"JSON eilutė", +e164:"E.164 numeris",jwt:"JWT",template_literal:"įvestis"},r={nan:"NaN", +number:"skaičius",bigint:"sveikasis skaičius",string:"eilutė", +boolean:"loginė reikšmė",undefined:"neapibrėžta reikšmė",function:"funkcija", +symbol:"simbolis",array:"masyvas",object:"objektas",null:"nulinė reikšmė"} +;return e=>{switch(e.code){case"invalid_type":{ +const t=r[e.expected]??e.expected,n=q(e.input),a=r[n]??n +;return/^[A-Z]/.test(e.expected)?`Gautas tipas ${a}, o tikėtasi - instanceof ${e.expected}`:`Gautas tipas ${a}, o tikėtasi - ${t}` +}case"invalid_value": +return 1===e.values.length?`Privalo būti ${$(e.values[0])}`:`Privalo būti vienas iš ${l(e.values,"|")} pasirinkimų` +;case"too_big":{ +const n=r[e.origin]??e.origin,a=t(e.origin,wa(Number(e.maximum)),e.inclusive??!1,"smaller") +;if(a?.verb)return`${Oa(n??e.origin??"reikšmė")} ${a.verb} ${e.maximum.toString()} ${a.unit??"elementų"}` +;const o=e.inclusive?"ne didesnis kaip":"mažesnis kaip" +;return`${Oa(n??e.origin??"reikšmė")} turi būti ${o} ${e.maximum.toString()} ${a?.unit}` +}case"too_small":{ +const n=r[e.origin]??e.origin,a=t(e.origin,wa(Number(e.minimum)),e.inclusive??!1,"bigger") +;if(a?.verb)return`${Oa(n??e.origin??"reikšmė")} ${a.verb} ${e.minimum.toString()} ${a.unit??"elementų"}` +;const o=e.inclusive?"ne mažesnis kaip":"didesnis kaip" +;return`${Oa(n??e.origin??"reikšmė")} turi būti ${o} ${e.minimum.toString()} ${a?.unit}` +}case"invalid_format":{const t=e +;return"starts_with"===t.format?`Eilutė privalo prasidėti "${t.prefix}"`:"ends_with"===t.format?`Eilutė privalo pasibaigti "${t.suffix}"`:"includes"===t.format?`Eilutė privalo įtraukti "${t.includes}"`:"regex"===t.format?`Eilutė privalo atitikti ${t.pattern}`:`Neteisingas ${n[t.format]??e.format}` +}case"not_multiple_of":return`Skaičius privalo būti ${e.divisor} kartotinis.` +;case"unrecognized_keys": +return`Neatpažint${e.keys.length>1?"i":"as"} rakt${e.keys.length>1?"ai":"as"}: ${l(e.keys,", ")}` +;case"invalid_key":return"Rastas klaidingas raktas";case"invalid_union":default: +return"Klaidinga įvestis";case"invalid_element":{const t=r[e.origin]??e.origin +;return`${Oa(t??e.origin??"reikšmė")} turi klaidingą įvestį`}}}};const ka=()=>{ +const e={string:{unit:"знаци",verb:"да имаат"},file:{unit:"бајти", +verb:"да имаат"},array:{unit:"ставки",verb:"да имаат"},set:{unit:"ставки", +verb:"да имаат"}};function t(t){return e[t]??null}const n={regex:"внес", +email:"адреса на е-пошта",url:"URL",emoji:"емоџи",uuid:"UUID",uuidv4:"UUIDv4", +uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2", +ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO датум и време", +date:"ISO датум",time:"ISO време",duration:"ISO времетраење",ipv4:"IPv4 адреса", +ipv6:"IPv6 адреса",cidrv4:"IPv4 опсег",cidrv6:"IPv6 опсег", +base64:"base64-енкодирана низа",base64url:"base64url-енкодирана низа", +json_string:"JSON низа",e164:"E.164 број",jwt:"JWT",template_literal:"внес"},r={ +nan:"NaN",number:"број",array:"низа"};return e=>{switch(e.code){ +case"invalid_type":{const t=r[e.expected]??e.expected,n=q(e.input),a=r[n]??n +;return/^[A-Z]/.test(e.expected)?`Грешен внес: се очекува instanceof ${e.expected}, примено ${a}`:`Грешен внес: се очекува ${t}, примено ${a}` +}case"invalid_value": +return 1===e.values.length?`Invalid input: expected ${$(e.values[0])}`:`Грешана опција: се очекува една ${l(e.values,"|")}` +;case"too_big":{const n=e.inclusive?"<=":"<",r=t(e.origin) +;return r?`Премногу голем: се очекува ${e.origin??"вредноста"} да има ${n}${e.maximum.toString()} ${r.unit??"елементи"}`:`Премногу голем: се очекува ${e.origin??"вредноста"} да биде ${n}${e.maximum.toString()}` +}case"too_small":{const n=e.inclusive?">=":">",r=t(e.origin) +;return r?`Премногу мал: се очекува ${e.origin} да има ${n}${e.minimum.toString()} ${r.unit}`:`Премногу мал: се очекува ${e.origin} да биде ${n}${e.minimum.toString()}` +}case"invalid_format":{const t=e +;return"starts_with"===t.format?`Неважечка низа: мора да започнува со "${t.prefix}"`:"ends_with"===t.format?`Неважечка низа: мора да завршува со "${t.suffix}"`:"includes"===t.format?`Неважечка низа: мора да вклучува "${t.includes}"`:"regex"===t.format?`Неважечка низа: мора да одгоара на патернот ${t.pattern}`:`Invalid ${n[t.format]??e.format}` +}case"not_multiple_of":return`Грешен број: мора да биде делив со ${e.divisor}` +;case"unrecognized_keys": +return`${e.keys.length>1?"Непрепознаени клучеви":"Непрепознаен клуч"}: ${l(e.keys,", ")}` +;case"invalid_key":return`Грешен клуч во ${e.origin}`;case"invalid_union": +default:return"Грешен внес";case"invalid_element": +return`Грешна вредност во ${e.origin}`}}};const Sa=()=>{const e={string:{ +unit:"aksara",verb:"mempunyai"},file:{unit:"bait",verb:"mempunyai"},array:{ +unit:"elemen",verb:"mempunyai"},set:{unit:"elemen",verb:"mempunyai"}} +;function t(t){return e[t]??null}const n={regex:"input",email:"alamat e-mel", +url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6", +nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID", +ksuid:"KSUID",datetime:"tarikh masa ISO",date:"tarikh ISO",time:"masa ISO", +duration:"tempoh ISO",ipv4:"alamat IPv4",ipv6:"alamat IPv6",cidrv4:"julat IPv4", +cidrv6:"julat IPv6",base64:"string dikodkan base64", +base64url:"string dikodkan base64url",json_string:"string JSON", +e164:"nombor E.164",jwt:"JWT",template_literal:"input"},r={nan:"NaN", +number:"nombor"};return e=>{switch(e.code){case"invalid_type":{ +const t=r[e.expected]??e.expected,n=q(e.input),a=r[n]??n +;return/^[A-Z]/.test(e.expected)?`Input tidak sah: dijangka instanceof ${e.expected}, diterima ${a}`:`Input tidak sah: dijangka ${t}, diterima ${a}` +}case"invalid_value": +return 1===e.values.length?`Input tidak sah: dijangka ${$(e.values[0])}`:`Pilihan tidak sah: dijangka salah satu daripada ${l(e.values,"|")}` +;case"too_big":{const n=e.inclusive?"<=":"<",r=t(e.origin) +;return r?`Terlalu besar: dijangka ${e.origin??"nilai"} ${r.verb} ${n}${e.maximum.toString()} ${r.unit??"elemen"}`:`Terlalu besar: dijangka ${e.origin??"nilai"} adalah ${n}${e.maximum.toString()}` +}case"too_small":{const n=e.inclusive?">=":">",r=t(e.origin) +;return r?`Terlalu kecil: dijangka ${e.origin} ${r.verb} ${n}${e.minimum.toString()} ${r.unit}`:`Terlalu kecil: dijangka ${e.origin} adalah ${n}${e.minimum.toString()}` +}case"invalid_format":{const t=e +;return"starts_with"===t.format?`String tidak sah: mesti bermula dengan "${t.prefix}"`:"ends_with"===t.format?`String tidak sah: mesti berakhir dengan "${t.suffix}"`:"includes"===t.format?`String tidak sah: mesti mengandungi "${t.includes}"`:"regex"===t.format?`String tidak sah: mesti sepadan dengan corak ${t.pattern}`:`${n[t.format]??e.format} tidak sah` +}case"not_multiple_of":return`Nombor tidak sah: perlu gandaan ${e.divisor}` +;case"unrecognized_keys":return`Kunci tidak dikenali: ${l(e.keys,", ")}` +;case"invalid_key":return`Kunci tidak sah dalam ${e.origin}` +;case"invalid_union":default:return"Input tidak sah";case"invalid_element": +return`Nilai tidak sah dalam ${e.origin}`}}};const _a=()=>{const e={string:{ +unit:"tekens",verb:"heeft"},file:{unit:"bytes",verb:"heeft"},array:{ +unit:"elementen",verb:"heeft"},set:{unit:"elementen",verb:"heeft"}} +;function t(t){return e[t]??null}const n={regex:"invoer",email:"emailadres", +url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6", +nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID", +ksuid:"KSUID",datetime:"ISO datum en tijd",date:"ISO datum",time:"ISO tijd", +duration:"ISO duur",ipv4:"IPv4-adres",ipv6:"IPv6-adres",cidrv4:"IPv4-bereik", +cidrv6:"IPv6-bereik",base64:"base64-gecodeerde tekst", +base64url:"base64 URL-gecodeerde tekst",json_string:"JSON string", +e164:"E.164-nummer",jwt:"JWT",template_literal:"invoer"},r={nan:"NaN", +number:"getal"};return e=>{switch(e.code){case"invalid_type":{ +const t=r[e.expected]??e.expected,n=q(e.input),a=r[n]??n +;return/^[A-Z]/.test(e.expected)?`Ongeldige invoer: verwacht instanceof ${e.expected}, ontving ${a}`:`Ongeldige invoer: verwacht ${t}, ontving ${a}` +}case"invalid_value": +return 1===e.values.length?`Ongeldige invoer: verwacht ${$(e.values[0])}`:`Ongeldige optie: verwacht één van ${l(e.values,"|")}` +;case"too_big":{ +const n=e.inclusive?"<=":"<",r=t(e.origin),a="date"===e.origin?"laat":"string"===e.origin?"lang":"groot" +;return r?`Te ${a}: verwacht dat ${e.origin??"waarde"} ${n}${e.maximum.toString()} ${r.unit??"elementen"} ${r.verb}`:`Te ${a}: verwacht dat ${e.origin??"waarde"} ${n}${e.maximum.toString()} is` +}case"too_small":{ +const n=e.inclusive?">=":">",r=t(e.origin),a="date"===e.origin?"vroeg":"string"===e.origin?"kort":"klein" +;return r?`Te ${a}: verwacht dat ${e.origin} ${n}${e.minimum.toString()} ${r.unit} ${r.verb}`:`Te ${a}: verwacht dat ${e.origin} ${n}${e.minimum.toString()} is` +}case"invalid_format":{const t=e +;return"starts_with"===t.format?`Ongeldige tekst: moet met "${t.prefix}" beginnen`:"ends_with"===t.format?`Ongeldige tekst: moet op "${t.suffix}" eindigen`:"includes"===t.format?`Ongeldige tekst: moet "${t.includes}" bevatten`:"regex"===t.format?`Ongeldige tekst: moet overeenkomen met patroon ${t.pattern}`:`Ongeldig: ${n[t.format]??e.format}` +}case"not_multiple_of": +return`Ongeldig getal: moet een veelvoud van ${e.divisor} zijn` +;case"unrecognized_keys": +return`Onbekende key${e.keys.length>1?"s":""}: ${l(e.keys,", ")}` +;case"invalid_key":return`Ongeldige key in ${e.origin}`;case"invalid_union": +default:return"Ongeldige invoer";case"invalid_element": +return`Ongeldige waarde in ${e.origin}`}}};const Aa=()=>{const e={string:{ +unit:"tegn",verb:"å ha"},file:{unit:"bytes",verb:"å ha"},array:{ +unit:"elementer",verb:"å inneholde"},set:{unit:"elementer",verb:"å inneholde"}} +;function t(t){return e[t]??null}const n={regex:"input",email:"e-postadresse", +url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6", +nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID", +ksuid:"KSUID",datetime:"ISO dato- og klokkeslett",date:"ISO-dato", +time:"ISO-klokkeslett",duration:"ISO-varighet",ipv4:"IPv4-område", +ipv6:"IPv6-område",cidrv4:"IPv4-spekter",cidrv6:"IPv6-spekter", +base64:"base64-enkodet streng",base64url:"base64url-enkodet streng", +json_string:"JSON-streng",e164:"E.164-nummer",jwt:"JWT",template_literal:"input" +},r={nan:"NaN",number:"tall",array:"liste"};return e=>{switch(e.code){ +case"invalid_type":{const t=r[e.expected]??e.expected,n=q(e.input),a=r[n]??n +;return/^[A-Z]/.test(e.expected)?`Ugyldig input: forventet instanceof ${e.expected}, fikk ${a}`:`Ugyldig input: forventet ${t}, fikk ${a}` +}case"invalid_value": +return 1===e.values.length?`Ugyldig verdi: forventet ${$(e.values[0])}`:`Ugyldig valg: forventet en av ${l(e.values,"|")}` +;case"too_big":{const n=e.inclusive?"<=":"<",r=t(e.origin) +;return r?`For stor(t): forventet ${e.origin??"value"} til å ha ${n}${e.maximum.toString()} ${r.unit??"elementer"}`:`For stor(t): forventet ${e.origin??"value"} til å ha ${n}${e.maximum.toString()}` +}case"too_small":{const n=e.inclusive?">=":">",r=t(e.origin) +;return r?`For lite(n): forventet ${e.origin} til å ha ${n}${e.minimum.toString()} ${r.unit}`:`For lite(n): forventet ${e.origin} til å ha ${n}${e.minimum.toString()}` +}case"invalid_format":{const t=e +;return"starts_with"===t.format?`Ugyldig streng: må starte med "${t.prefix}"`:"ends_with"===t.format?`Ugyldig streng: må ende med "${t.suffix}"`:"includes"===t.format?`Ugyldig streng: må inneholde "${t.includes}"`:"regex"===t.format?`Ugyldig streng: må matche mønsteret ${t.pattern}`:`Ugyldig ${n[t.format]??e.format}` +}case"not_multiple_of": +return`Ugyldig tall: må være et multiplum av ${e.divisor}` +;case"unrecognized_keys": +return`${e.keys.length>1?"Ukjente nøkler":"Ukjent nøkkel"}: ${l(e.keys,", ")}` +;case"invalid_key":return`Ugyldig nøkkel i ${e.origin}`;case"invalid_union": +default:return"Ugyldig input";case"invalid_element": +return`Ugyldig verdi i ${e.origin}`}}};const Ta=()=>{const e={string:{ +unit:"harf",verb:"olmalıdır"},file:{unit:"bayt",verb:"olmalıdır"},array:{ +unit:"unsur",verb:"olmalıdır"},set:{unit:"unsur",verb:"olmalıdır"}} +;function t(t){return e[t]??null}const n={regex:"giren",email:"epostagâh", +url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6", +nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID", +ksuid:"KSUID",datetime:"ISO hengâmı",date:"ISO tarihi",time:"ISO zamanı", +duration:"ISO müddeti",ipv4:"IPv4 nişânı",ipv6:"IPv6 nişânı", +cidrv4:"IPv4 menzili",cidrv6:"IPv6 menzili",base64:"base64-şifreli metin", +base64url:"base64url-şifreli metin",json_string:"JSON metin", +e164:"E.164 sayısı",jwt:"JWT",template_literal:"giren"},r={nan:"NaN", +number:"numara",array:"saf",null:"gayb"};return e=>{switch(e.code){ +case"invalid_type":{const t=r[e.expected]??e.expected,n=q(e.input),a=r[n]??n +;return/^[A-Z]/.test(e.expected)?`Fâsit giren: umulan instanceof ${e.expected}, alınan ${a}`:`Fâsit giren: umulan ${t}, alınan ${a}` +}case"invalid_value": +return 1===e.values.length?`Fâsit giren: umulan ${$(e.values[0])}`:`Fâsit tercih: mûteberler ${l(e.values,"|")}` +;case"too_big":{const n=e.inclusive?"<=":"<",r=t(e.origin) +;return r?`Fazla büyük: ${e.origin??"value"}, ${n}${e.maximum.toString()} ${r.unit??"elements"} sahip olmalıydı.`:`Fazla büyük: ${e.origin??"value"}, ${n}${e.maximum.toString()} olmalıydı.` +}case"too_small":{const n=e.inclusive?">=":">",r=t(e.origin) +;return r?`Fazla küçük: ${e.origin}, ${n}${e.minimum.toString()} ${r.unit} sahip olmalıydı.`:`Fazla küçük: ${e.origin}, ${n}${e.minimum.toString()} olmalıydı.` +}case"invalid_format":{const t=e +;return"starts_with"===t.format?`Fâsit metin: "${t.prefix}" ile başlamalı.`:"ends_with"===t.format?`Fâsit metin: "${t.suffix}" ile bitmeli.`:"includes"===t.format?`Fâsit metin: "${t.includes}" ihtivâ etmeli.`:"regex"===t.format?`Fâsit metin: ${t.pattern} nakşına uymalı.`:`Fâsit ${n[t.format]??e.format}` +}case"not_multiple_of":return`Fâsit sayı: ${e.divisor} katı olmalıydı.` +;case"unrecognized_keys": +return`Tanınmayan anahtar ${e.keys.length>1?"s":""}: ${l(e.keys,", ")}` +;case"invalid_key":return`${e.origin} için tanınmayan anahtar var.` +;case"invalid_union":return"Giren tanınamadı.";case"invalid_element": +return`${e.origin} için tanınmayan kıymet var.`;default: +return"Kıymet tanınamadı."}}};const Ea=()=>{const e={string:{unit:"توکي", +verb:"ولري"},file:{unit:"بایټس",verb:"ولري"},array:{unit:"توکي",verb:"ولري"}, +set:{unit:"توکي",verb:"ولري"}};function t(t){return e[t]??null}const n={ +regex:"ورودي",email:"بریښنالیک",url:"یو آر ال",emoji:"ایموجي",uuid:"UUID", +uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid", +cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"نیټه او وخت", +date:"نېټه",time:"وخت",duration:"موده",ipv4:"د IPv4 پته",ipv6:"د IPv6 پته", +cidrv4:"د IPv4 ساحه",cidrv6:"د IPv6 ساحه",base64:"base64-encoded متن", +base64url:"base64url-encoded متن",json_string:"JSON متن",e164:"د E.164 شمېره", +jwt:"JWT",template_literal:"ورودي"},r={nan:"NaN",number:"عدد",array:"ارې"} +;return e=>{switch(e.code){case"invalid_type":{ +const t=r[e.expected]??e.expected,n=q(e.input),a=r[n]??n +;return/^[A-Z]/.test(e.expected)?`ناسم ورودي: باید instanceof ${e.expected} وای, مګر ${a} ترلاسه شو`:`ناسم ورودي: باید ${t} وای, مګر ${a} ترلاسه شو` +}case"invalid_value": +return 1===e.values.length?`ناسم ورودي: باید ${$(e.values[0])} وای`:`ناسم انتخاب: باید یو له ${l(e.values,"|")} څخه وای` +;case"too_big":{const n=e.inclusive?"<=":"<",r=t(e.origin) +;return r?`ډیر لوی: ${e.origin??"ارزښت"} باید ${n}${e.maximum.toString()} ${r.unit??"عنصرونه"} ولري`:`ډیر لوی: ${e.origin??"ارزښت"} باید ${n}${e.maximum.toString()} وي` +}case"too_small":{const n=e.inclusive?">=":">",r=t(e.origin) +;return r?`ډیر کوچنی: ${e.origin} باید ${n}${e.minimum.toString()} ${r.unit} ولري`:`ډیر کوچنی: ${e.origin} باید ${n}${e.minimum.toString()} وي` +}case"invalid_format":{const t=e +;return"starts_with"===t.format?`ناسم متن: باید د "${t.prefix}" سره پیل شي`:"ends_with"===t.format?`ناسم متن: باید د "${t.suffix}" سره پای ته ورسيږي`:"includes"===t.format?`ناسم متن: باید "${t.includes}" ولري`:"regex"===t.format?`ناسم متن: باید د ${t.pattern} سره مطابقت ولري`:`${n[t.format]??e.format} ناسم دی` +}case"not_multiple_of":return`ناسم عدد: باید د ${e.divisor} مضرب وي` +;case"unrecognized_keys": +return`ناسم ${e.keys.length>1?"کلیډونه":"کلیډ"}: ${l(e.keys,", ")}` +;case"invalid_key":return`ناسم کلیډ په ${e.origin} کې`;case"invalid_union": +default:return"ناسمه ورودي";case"invalid_element": +return`ناسم عنصر په ${e.origin} کې`}}};const Ca=()=>{const e={string:{ +unit:"znaków",verb:"mieć"},file:{unit:"bajtów",verb:"mieć"},array:{ +unit:"elementów",verb:"mieć"},set:{unit:"elementów",verb:"mieć"}};function t(t){ +return e[t]??null}const n={regex:"wyrażenie",email:"adres email",url:"URL", +emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid", +guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID", +datetime:"data i godzina w formacie ISO",date:"data w formacie ISO", +time:"godzina w formacie ISO",duration:"czas trwania ISO",ipv4:"adres IPv4", +ipv6:"adres IPv6",cidrv4:"zakres IPv4",cidrv6:"zakres IPv6", +base64:"ciąg znaków zakodowany w formacie base64", +base64url:"ciąg znaków zakodowany w formacie base64url", +json_string:"ciąg znaków w formacie JSON",e164:"liczba E.164",jwt:"JWT", +template_literal:"wejście"},r={nan:"NaN",number:"liczba",array:"tablica"} +;return e=>{switch(e.code){case"invalid_type":{ +const t=r[e.expected]??e.expected,n=q(e.input),a=r[n]??n +;return/^[A-Z]/.test(e.expected)?`Nieprawidłowe dane wejściowe: oczekiwano instanceof ${e.expected}, otrzymano ${a}`:`Nieprawidłowe dane wejściowe: oczekiwano ${t}, otrzymano ${a}` +}case"invalid_value": +return 1===e.values.length?`Nieprawidłowe dane wejściowe: oczekiwano ${$(e.values[0])}`:`Nieprawidłowa opcja: oczekiwano jednej z wartości ${l(e.values,"|")}` +;case"too_big":{const n=e.inclusive?"<=":"<",r=t(e.origin) +;return r?`Za duża wartość: oczekiwano, że ${e.origin??"wartość"} będzie mieć ${n}${e.maximum.toString()} ${r.unit??"elementów"}`:`Zbyt duż(y/a/e): oczekiwano, że ${e.origin??"wartość"} będzie wynosić ${n}${e.maximum.toString()}` +}case"too_small":{const n=e.inclusive?">=":">",r=t(e.origin) +;return r?`Za mała wartość: oczekiwano, że ${e.origin??"wartość"} będzie mieć ${n}${e.minimum.toString()} ${r.unit??"elementów"}`:`Zbyt mał(y/a/e): oczekiwano, że ${e.origin??"wartość"} będzie wynosić ${n}${e.minimum.toString()}` +}case"invalid_format":{const t=e +;return"starts_with"===t.format?`Nieprawidłowy ciąg znaków: musi zaczynać się od "${t.prefix}"`:"ends_with"===t.format?`Nieprawidłowy ciąg znaków: musi kończyć się na "${t.suffix}"`:"includes"===t.format?`Nieprawidłowy ciąg znaków: musi zawierać "${t.includes}"`:"regex"===t.format?`Nieprawidłowy ciąg znaków: musi odpowiadać wzorcowi ${t.pattern}`:`Nieprawidłow(y/a/e) ${n[t.format]??e.format}` +}case"not_multiple_of": +return`Nieprawidłowa liczba: musi być wielokrotnością ${e.divisor}` +;case"unrecognized_keys": +return`Nierozpoznane klucze${e.keys.length>1?"s":""}: ${l(e.keys,", ")}` +;case"invalid_key":return`Nieprawidłowy klucz w ${e.origin}` +;case"invalid_union":default:return"Nieprawidłowe dane wejściowe" +;case"invalid_element":return`Nieprawidłowa wartość w ${e.origin}`}}} +;const $a=()=>{const e={string:{unit:"caracteres",verb:"ter"},file:{ +unit:"bytes",verb:"ter"},array:{unit:"itens",verb:"ter"},set:{unit:"itens", +verb:"ter"}};function t(t){return e[t]??null}const n={regex:"padrão", +email:"endereço de e-mail",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4", +uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2", +ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"data e hora ISO",date:"data ISO", +time:"hora ISO",duration:"duração ISO",ipv4:"endereço IPv4", +ipv6:"endereço IPv6",cidrv4:"faixa de IPv4",cidrv6:"faixa de IPv6", +base64:"texto codificado em base64",base64url:"URL codificada em base64", +json_string:"texto JSON",e164:"número E.164",jwt:"JWT", +template_literal:"entrada"},r={nan:"NaN",number:"número",null:"nulo"} +;return e=>{switch(e.code){case"invalid_type":{ +const t=r[e.expected]??e.expected,n=q(e.input),a=r[n]??n +;return/^[A-Z]/.test(e.expected)?`Tipo inválido: esperado instanceof ${e.expected}, recebido ${a}`:`Tipo inválido: esperado ${t}, recebido ${a}` +}case"invalid_value": +return 1===e.values.length?`Entrada inválida: esperado ${$(e.values[0])}`:`Opção inválida: esperada uma das ${l(e.values,"|")}` +;case"too_big":{const n=e.inclusive?"<=":"<",r=t(e.origin) +;return r?`Muito grande: esperado que ${e.origin??"valor"} tivesse ${n}${e.maximum.toString()} ${r.unit??"elementos"}`:`Muito grande: esperado que ${e.origin??"valor"} fosse ${n}${e.maximum.toString()}` +}case"too_small":{const n=e.inclusive?">=":">",r=t(e.origin) +;return r?`Muito pequeno: esperado que ${e.origin} tivesse ${n}${e.minimum.toString()} ${r.unit}`:`Muito pequeno: esperado que ${e.origin} fosse ${n}${e.minimum.toString()}` +}case"invalid_format":{const t=e +;return"starts_with"===t.format?`Texto inválido: deve começar com "${t.prefix}"`:"ends_with"===t.format?`Texto inválido: deve terminar com "${t.suffix}"`:"includes"===t.format?`Texto inválido: deve incluir "${t.includes}"`:"regex"===t.format?`Texto inválido: deve corresponder ao padrão ${t.pattern}`:`${n[t.format]??e.format} inválido` +}case"not_multiple_of": +return`Número inválido: deve ser múltiplo de ${e.divisor}` +;case"unrecognized_keys": +return`Chave${e.keys.length>1?"s":""} desconhecida${e.keys.length>1?"s":""}: ${l(e.keys,", ")}` +;case"invalid_key":return`Chave inválida em ${e.origin}`;case"invalid_union": +return"Entrada inválida";case"invalid_element": +return`Valor inválido em ${e.origin}`;default:return"Campo inválido"}}} +;function Pa(e,t,n,r){const a=Math.abs(e),o=a%10,i=a%100 +;return i>=11&&i<=19?r:1===o?t:o>=2&&o<=4?n:r}const Ia=()=>{const e={string:{ +unit:{one:"символ",few:"символа",many:"символов"},verb:"иметь"},file:{unit:{ +one:"байт",few:"байта",many:"байт"},verb:"иметь"},array:{unit:{one:"элемент", +few:"элемента",many:"элементов"},verb:"иметь"},set:{unit:{one:"элемент", +few:"элемента",many:"элементов"},verb:"иметь"}};function t(t){return e[t]??null} +const n={regex:"ввод",email:"email адрес",url:"URL",emoji:"эмодзи",uuid:"UUID", +uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid", +cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO дата и время", +date:"ISO дата",time:"ISO время",duration:"ISO длительность",ipv4:"IPv4 адрес", +ipv6:"IPv6 адрес",cidrv4:"IPv4 диапазон",cidrv6:"IPv6 диапазон", +base64:"строка в формате base64",base64url:"строка в формате base64url", +json_string:"JSON строка",e164:"номер E.164",jwt:"JWT",template_literal:"ввод" +},r={nan:"NaN",number:"число",array:"массив"};return e=>{switch(e.code){ +case"invalid_type":{const t=r[e.expected]??e.expected,n=q(e.input),a=r[n]??n +;return/^[A-Z]/.test(e.expected)?`Неверный ввод: ожидалось instanceof ${e.expected}, получено ${a}`:`Неверный ввод: ожидалось ${t}, получено ${a}` +}case"invalid_value": +return 1===e.values.length?`Неверный ввод: ожидалось ${$(e.values[0])}`:`Неверный вариант: ожидалось одно из ${l(e.values,"|")}` +;case"too_big":{const n=e.inclusive?"<=":"<",r=t(e.origin);if(r){ +const t=Pa(Number(e.maximum),r.unit.one,r.unit.few,r.unit.many) +;return`Слишком большое значение: ожидалось, что ${e.origin??"значение"} будет иметь ${n}${e.maximum.toString()} ${t}` +} +return`Слишком большое значение: ожидалось, что ${e.origin??"значение"} будет ${n}${e.maximum.toString()}` +}case"too_small":{const n=e.inclusive?">=":">",r=t(e.origin);if(r){ +const t=Pa(Number(e.minimum),r.unit.one,r.unit.few,r.unit.many) +;return`Слишком маленькое значение: ожидалось, что ${e.origin} будет иметь ${n}${e.minimum.toString()} ${t}` +} +return`Слишком маленькое значение: ожидалось, что ${e.origin} будет ${n}${e.minimum.toString()}` +}case"invalid_format":{const t=e +;return"starts_with"===t.format?`Неверная строка: должна начинаться с "${t.prefix}"`:"ends_with"===t.format?`Неверная строка: должна заканчиваться на "${t.suffix}"`:"includes"===t.format?`Неверная строка: должна содержать "${t.includes}"`:"regex"===t.format?`Неверная строка: должна соответствовать шаблону ${t.pattern}`:`Неверный ${n[t.format]??e.format}` +}case"not_multiple_of":return`Неверное число: должно быть кратным ${e.divisor}` +;case"unrecognized_keys": +return`Нераспознанн${e.keys.length>1?"ые":"ый"} ключ${e.keys.length>1?"и":""}: ${l(e.keys,", ")}` +;case"invalid_key":return`Неверный ключ в ${e.origin}`;case"invalid_union": +default:return"Неверные входные данные";case"invalid_element": +return`Неверное значение в ${e.origin}`}}};const Da=()=>{const e={string:{ +unit:"znakov",verb:"imeti"},file:{unit:"bajtov",verb:"imeti"},array:{ +unit:"elementov",verb:"imeti"},set:{unit:"elementov",verb:"imeti"}} +;function t(t){return e[t]??null}const n={regex:"vnos",email:"e-poštni naslov", +url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6", +nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID", +ksuid:"KSUID",datetime:"ISO datum in čas",date:"ISO datum",time:"ISO čas", +duration:"ISO trajanje",ipv4:"IPv4 naslov",ipv6:"IPv6 naslov", +cidrv4:"obseg IPv4",cidrv6:"obseg IPv6",base64:"base64 kodiran niz", +base64url:"base64url kodiran niz",json_string:"JSON niz",e164:"E.164 številka", +jwt:"JWT",template_literal:"vnos"},r={nan:"NaN",number:"število",array:"tabela"} +;return e=>{switch(e.code){case"invalid_type":{ +const t=r[e.expected]??e.expected,n=q(e.input),a=r[n]??n +;return/^[A-Z]/.test(e.expected)?`Neveljaven vnos: pričakovano instanceof ${e.expected}, prejeto ${a}`:`Neveljaven vnos: pričakovano ${t}, prejeto ${a}` +}case"invalid_value": +return 1===e.values.length?`Neveljaven vnos: pričakovano ${$(e.values[0])}`:`Neveljavna možnost: pričakovano eno izmed ${l(e.values,"|")}` +;case"too_big":{const n=e.inclusive?"<=":"<",r=t(e.origin) +;return r?`Preveliko: pričakovano, da bo ${e.origin??"vrednost"} imelo ${n}${e.maximum.toString()} ${r.unit??"elementov"}`:`Preveliko: pričakovano, da bo ${e.origin??"vrednost"} ${n}${e.maximum.toString()}` +}case"too_small":{const n=e.inclusive?">=":">",r=t(e.origin) +;return r?`Premajhno: pričakovano, da bo ${e.origin} imelo ${n}${e.minimum.toString()} ${r.unit}`:`Premajhno: pričakovano, da bo ${e.origin} ${n}${e.minimum.toString()}` +}case"invalid_format":{const t=e +;return"starts_with"===t.format?`Neveljaven niz: mora se začeti z "${t.prefix}"`:"ends_with"===t.format?`Neveljaven niz: mora se končati z "${t.suffix}"`:"includes"===t.format?`Neveljaven niz: mora vsebovati "${t.includes}"`:"regex"===t.format?`Neveljaven niz: mora ustrezati vzorcu ${t.pattern}`:`Neveljaven ${n[t.format]??e.format}` +}case"not_multiple_of": +return`Neveljavno število: mora biti večkratnik ${e.divisor}` +;case"unrecognized_keys": +return`Neprepoznan${e.keys.length>1?"i ključi":" ključ"}: ${l(e.keys,", ")}` +;case"invalid_key":return`Neveljaven ključ v ${e.origin}`;case"invalid_union": +default:return"Neveljaven vnos";case"invalid_element": +return`Neveljavna vrednost v ${e.origin}`}}};const Ma=()=>{const e={string:{ +unit:"tecken",verb:"att ha"},file:{unit:"bytes",verb:"att ha"},array:{ +unit:"objekt",verb:"att innehålla"},set:{unit:"objekt",verb:"att innehålla"}} +;function t(t){return e[t]??null}const n={regex:"reguljärt uttryck", +email:"e-postadress",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4", +uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2", +ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO-datum och tid", +date:"ISO-datum",time:"ISO-tid",duration:"ISO-varaktighet", +ipv4:"IPv4-intervall",ipv6:"IPv6-intervall",cidrv4:"IPv4-spektrum", +cidrv6:"IPv6-spektrum",base64:"base64-kodad sträng", +base64url:"base64url-kodad sträng",json_string:"JSON-sträng", +e164:"E.164-nummer",jwt:"JWT",template_literal:"mall-literal"},r={nan:"NaN", +number:"antal",array:"lista"};return e=>{switch(e.code){case"invalid_type":{ +const t=r[e.expected]??e.expected,n=q(e.input),a=r[n]??n +;return/^[A-Z]/.test(e.expected)?`Ogiltig inmatning: förväntat instanceof ${e.expected}, fick ${a}`:`Ogiltig inmatning: förväntat ${t}, fick ${a}` +}case"invalid_value": +return 1===e.values.length?`Ogiltig inmatning: förväntat ${$(e.values[0])}`:`Ogiltigt val: förväntade en av ${l(e.values,"|")}` +;case"too_big":{const n=e.inclusive?"<=":"<",r=t(e.origin) +;return r?`För stor(t): förväntade ${e.origin??"värdet"} att ha ${n}${e.maximum.toString()} ${r.unit??"element"}`:`För stor(t): förväntat ${e.origin??"värdet"} att ha ${n}${e.maximum.toString()}` +}case"too_small":{const n=e.inclusive?">=":">",r=t(e.origin) +;return r?`För lite(t): förväntade ${e.origin??"värdet"} att ha ${n}${e.minimum.toString()} ${r.unit}`:`För lite(t): förväntade ${e.origin??"värdet"} att ha ${n}${e.minimum.toString()}` +}case"invalid_format":{const t=e +;return"starts_with"===t.format?`Ogiltig sträng: måste börja med "${t.prefix}"`:"ends_with"===t.format?`Ogiltig sträng: måste sluta med "${t.suffix}"`:"includes"===t.format?`Ogiltig sträng: måste innehålla "${t.includes}"`:"regex"===t.format?`Ogiltig sträng: måste matcha mönstret "${t.pattern}"`:`Ogiltig(t) ${n[t.format]??e.format}` +}case"not_multiple_of": +return`Ogiltigt tal: måste vara en multipel av ${e.divisor}` +;case"unrecognized_keys": +return`${e.keys.length>1?"Okända nycklar":"Okänd nyckel"}: ${l(e.keys,", ")}` +;case"invalid_key":return`Ogiltig nyckel i ${e.origin??"värdet"}` +;case"invalid_union":default:return"Ogiltig input";case"invalid_element": +return`Ogiltigt värde i ${e.origin??"värdet"}`}}};const Na=()=>{const e={ +string:{unit:"எழுத்துக்கள்",verb:"கொண்டிருக்க வேண்டும்"},file:{unit:"பைட்டுகள்", +verb:"கொண்டிருக்க வேண்டும்"},array:{unit:"உறுப்புகள்", +verb:"கொண்டிருக்க வேண்டும்"},set:{unit:"உறுப்புகள்",verb:"கொண்டிருக்க வேண்டும்"} +};function t(t){return e[t]??null}const n={regex:"உள்ளீடு", +email:"மின்னஞ்சல் முகவரி",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4", +uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2", +ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO தேதி நேரம்",date:"ISO தேதி", +time:"ISO நேரம்",duration:"ISO கால அளவு",ipv4:"IPv4 முகவரி",ipv6:"IPv6 முகவரி", +cidrv4:"IPv4 வரம்பு",cidrv6:"IPv6 வரம்பு",base64:"base64-encoded சரம்", +base64url:"base64url-encoded சரம்",json_string:"JSON சரம்",e164:"E.164 எண்", +jwt:"JWT",template_literal:"input"},r={nan:"NaN",number:"எண்",array:"அணி", +null:"வெறுமை"};return e=>{switch(e.code){case"invalid_type":{ +const t=r[e.expected]??e.expected,n=q(e.input),a=r[n]??n +;return/^[A-Z]/.test(e.expected)?`தவறான உள்ளீடு: எதிர்பார்க்கப்பட்டது instanceof ${e.expected}, பெறப்பட்டது ${a}`:`தவறான உள்ளீடு: எதிர்பார்க்கப்பட்டது ${t}, பெறப்பட்டது ${a}` +}case"invalid_value": +return 1===e.values.length?`தவறான உள்ளீடு: எதிர்பார்க்கப்பட்டது ${$(e.values[0])}`:`தவறான விருப்பம்: எதிர்பார்க்கப்பட்டது ${l(e.values,"|")} இல் ஒன்று` +;case"too_big":{const n=e.inclusive?"<=":"<",r=t(e.origin) +;return r?`மிக பெரியது: எதிர்பார்க்கப்பட்டது ${e.origin??"மதிப்பு"} ${n}${e.maximum.toString()} ${r.unit??"உறுப்புகள்"} ஆக இருக்க வேண்டும்`:`மிக பெரியது: எதிர்பார்க்கப்பட்டது ${e.origin??"மதிப்பு"} ${n}${e.maximum.toString()} ஆக இருக்க வேண்டும்` +}case"too_small":{const n=e.inclusive?">=":">",r=t(e.origin) +;return r?`மிகச் சிறியது: எதிர்பார்க்கப்பட்டது ${e.origin} ${n}${e.minimum.toString()} ${r.unit} ஆக இருக்க வேண்டும்`:`மிகச் சிறியது: எதிர்பார்க்கப்பட்டது ${e.origin} ${n}${e.minimum.toString()} ஆக இருக்க வேண்டும்` +}case"invalid_format":{const t=e +;return"starts_with"===t.format?`தவறான சரம்: "${t.prefix}" இல் தொடங்க வேண்டும்`:"ends_with"===t.format?`தவறான சரம்: "${t.suffix}" இல் முடிவடைய வேண்டும்`:"includes"===t.format?`தவறான சரம்: "${t.includes}" ஐ உள்ளடக்க வேண்டும்`:"regex"===t.format?`தவறான சரம்: ${t.pattern} முறைபாட்டுடன் பொருந்த வேண்டும்`:`தவறான ${n[t.format]??e.format}` +}case"not_multiple_of":return`தவறான எண்: ${e.divisor} இன் பலமாக இருக்க வேண்டும்` +;case"unrecognized_keys": +return`அடையாளம் தெரியாத விசை${e.keys.length>1?"கள்":""}: ${l(e.keys,", ")}` +;case"invalid_key":return`${e.origin} இல் தவறான விசை`;case"invalid_union": +default:return"தவறான உள்ளீடு";case"invalid_element": +return`${e.origin} இல் தவறான மதிப்பு`}}};const Ra=()=>{const e={string:{ +unit:"ตัวอักษร",verb:"ควรมี"},file:{unit:"ไบต์",verb:"ควรมี"},array:{ +unit:"รายการ",verb:"ควรมี"},set:{unit:"รายการ",verb:"ควรมี"}};function t(t){ +return e[t]??null}const n={regex:"ข้อมูลที่ป้อน",email:"ที่อยู่อีเมล",url:"URL", +emoji:"อิโมจิ",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid", +guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID", +datetime:"วันที่เวลาแบบ ISO",date:"วันที่แบบ ISO",time:"เวลาแบบ ISO", +duration:"ช่วงเวลาแบบ ISO",ipv4:"ที่อยู่ IPv4",ipv6:"ที่อยู่ IPv6", +cidrv4:"ช่วง IP แบบ IPv4",cidrv6:"ช่วง IP แบบ IPv6",base64:"ข้อความแบบ Base64", +base64url:"ข้อความแบบ Base64 สำหรับ URL",json_string:"ข้อความแบบ JSON", +e164:"เบอร์โทรศัพท์ระหว่างประเทศ (E.164)",jwt:"โทเคน JWT", +template_literal:"ข้อมูลที่ป้อน"},r={nan:"NaN",number:"ตัวเลข", +array:"อาร์เรย์ (Array)",null:"ไม่มีค่า (null)"};return e=>{switch(e.code){ +case"invalid_type":{const t=r[e.expected]??e.expected,n=q(e.input),a=r[n]??n +;return/^[A-Z]/.test(e.expected)?`ประเภทข้อมูลไม่ถูกต้อง: ควรเป็น instanceof ${e.expected} แต่ได้รับ ${a}`:`ประเภทข้อมูลไม่ถูกต้อง: ควรเป็น ${t} แต่ได้รับ ${a}` +}case"invalid_value": +return 1===e.values.length?`ค่าไม่ถูกต้อง: ควรเป็น ${$(e.values[0])}`:`ตัวเลือกไม่ถูกต้อง: ควรเป็นหนึ่งใน ${l(e.values,"|")}` +;case"too_big":{const n=e.inclusive?"ไม่เกิน":"น้อยกว่า",r=t(e.origin) +;return r?`เกินกำหนด: ${e.origin??"ค่า"} ควรมี${n} ${e.maximum.toString()} ${r.unit??"รายการ"}`:`เกินกำหนด: ${e.origin??"ค่า"} ควรมี${n} ${e.maximum.toString()}` +}case"too_small":{const n=e.inclusive?"อย่างน้อย":"มากกว่า",r=t(e.origin) +;return r?`น้อยกว่ากำหนด: ${e.origin} ควรมี${n} ${e.minimum.toString()} ${r.unit}`:`น้อยกว่ากำหนด: ${e.origin} ควรมี${n} ${e.minimum.toString()}` +}case"invalid_format":{const t=e +;return"starts_with"===t.format?`รูปแบบไม่ถูกต้อง: ข้อความต้องขึ้นต้นด้วย "${t.prefix}"`:"ends_with"===t.format?`รูปแบบไม่ถูกต้อง: ข้อความต้องลงท้ายด้วย "${t.suffix}"`:"includes"===t.format?`รูปแบบไม่ถูกต้อง: ข้อความต้องมี "${t.includes}" อยู่ในข้อความ`:"regex"===t.format?`รูปแบบไม่ถูกต้อง: ต้องตรงกับรูปแบบที่กำหนด ${t.pattern}`:`รูปแบบไม่ถูกต้อง: ${n[t.format]??e.format}` +}case"not_multiple_of": +return`ตัวเลขไม่ถูกต้อง: ต้องเป็นจำนวนที่หารด้วย ${e.divisor} ได้ลงตัว` +;case"unrecognized_keys":return`พบคีย์ที่ไม่รู้จัก: ${l(e.keys,", ")}` +;case"invalid_key":return`คีย์ไม่ถูกต้องใน ${e.origin}`;case"invalid_union": +return"ข้อมูลไม่ถูกต้อง: ไม่ตรงกับรูปแบบยูเนียนที่กำหนดไว้" +;case"invalid_element":return`ข้อมูลไม่ถูกต้องใน ${e.origin}`;default: +return"ข้อมูลไม่ถูกต้อง"}}};const La=()=>{const e={string:{unit:"karakter", +verb:"olmalı"},file:{unit:"bayt",verb:"olmalı"},array:{unit:"öğe",verb:"olmalı" +},set:{unit:"öğe",verb:"olmalı"}};function t(t){return e[t]??null}const n={ +regex:"girdi",email:"e-posta adresi",url:"URL",emoji:"emoji",uuid:"UUID", +uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid", +cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO tarih ve saat", +date:"ISO tarih",time:"ISO saat",duration:"ISO süre",ipv4:"IPv4 adresi", +ipv6:"IPv6 adresi",cidrv4:"IPv4 aralığı",cidrv6:"IPv6 aralığı", +base64:"base64 ile şifrelenmiş metin", +base64url:"base64url ile şifrelenmiş metin",json_string:"JSON dizesi", +e164:"E.164 sayısı",jwt:"JWT",template_literal:"Şablon dizesi"},r={nan:"NaN"} +;return e=>{switch(e.code){case"invalid_type":{ +const t=r[e.expected]??e.expected,n=q(e.input),a=r[n]??n +;return/^[A-Z]/.test(e.expected)?`Geçersiz değer: beklenen instanceof ${e.expected}, alınan ${a}`:`Geçersiz değer: beklenen ${t}, alınan ${a}` +}case"invalid_value": +return 1===e.values.length?`Geçersiz değer: beklenen ${$(e.values[0])}`:`Geçersiz seçenek: aşağıdakilerden biri olmalı: ${l(e.values,"|")}` +;case"too_big":{const n=e.inclusive?"<=":"<",r=t(e.origin) +;return r?`Çok büyük: beklenen ${e.origin??"değer"} ${n}${e.maximum.toString()} ${r.unit??"öğe"}`:`Çok büyük: beklenen ${e.origin??"değer"} ${n}${e.maximum.toString()}` +}case"too_small":{const n=e.inclusive?">=":">",r=t(e.origin) +;return r?`Çok küçük: beklenen ${e.origin} ${n}${e.minimum.toString()} ${r.unit}`:`Çok küçük: beklenen ${e.origin} ${n}${e.minimum.toString()}` +}case"invalid_format":{const t=e +;return"starts_with"===t.format?`Geçersiz metin: "${t.prefix}" ile başlamalı`:"ends_with"===t.format?`Geçersiz metin: "${t.suffix}" ile bitmeli`:"includes"===t.format?`Geçersiz metin: "${t.includes}" içermeli`:"regex"===t.format?`Geçersiz metin: ${t.pattern} desenine uymalı`:`Geçersiz ${n[t.format]??e.format}` +}case"not_multiple_of":return`Geçersiz sayı: ${e.divisor} ile tam bölünebilmeli` +;case"unrecognized_keys": +return`Tanınmayan anahtar${e.keys.length>1?"lar":""}: ${l(e.keys,", ")}` +;case"invalid_key":return`${e.origin} içinde geçersiz anahtar` +;case"invalid_union":default:return"Geçersiz değer";case"invalid_element": +return`${e.origin} içinde geçersiz değer`}}};const Ba=()=>{const e={string:{ +unit:"символів",verb:"матиме"},file:{unit:"байтів",verb:"матиме"},array:{ +unit:"елементів",verb:"матиме"},set:{unit:"елементів",verb:"матиме"}} +;function t(t){return e[t]??null}const n={regex:"вхідні дані", +email:"адреса електронної пошти",url:"URL",emoji:"емодзі",uuid:"UUID", +uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid", +cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"дата та час ISO", +date:"дата ISO",time:"час ISO",duration:"тривалість ISO",ipv4:"адреса IPv4", +ipv6:"адреса IPv6",cidrv4:"діапазон IPv4",cidrv6:"діапазон IPv6", +base64:"рядок у кодуванні base64",base64url:"рядок у кодуванні base64url", +json_string:"рядок JSON",e164:"номер E.164",jwt:"JWT", +template_literal:"вхідні дані"},r={nan:"NaN",number:"число",array:"масив"} +;return e=>{switch(e.code){case"invalid_type":{ +const t=r[e.expected]??e.expected,n=q(e.input),a=r[n]??n +;return/^[A-Z]/.test(e.expected)?`Неправильні вхідні дані: очікується instanceof ${e.expected}, отримано ${a}`:`Неправильні вхідні дані: очікується ${t}, отримано ${a}` +}case"invalid_value": +return 1===e.values.length?`Неправильні вхідні дані: очікується ${$(e.values[0])}`:`Неправильна опція: очікується одне з ${l(e.values,"|")}` +;case"too_big":{const n=e.inclusive?"<=":"<",r=t(e.origin) +;return r?`Занадто велике: очікується, що ${e.origin??"значення"} ${r.verb} ${n}${e.maximum.toString()} ${r.unit??"елементів"}`:`Занадто велике: очікується, що ${e.origin??"значення"} буде ${n}${e.maximum.toString()}` +}case"too_small":{const n=e.inclusive?">=":">",r=t(e.origin) +;return r?`Занадто мале: очікується, що ${e.origin} ${r.verb} ${n}${e.minimum.toString()} ${r.unit}`:`Занадто мале: очікується, що ${e.origin} буде ${n}${e.minimum.toString()}` +}case"invalid_format":{const t=e +;return"starts_with"===t.format?`Неправильний рядок: повинен починатися з "${t.prefix}"`:"ends_with"===t.format?`Неправильний рядок: повинен закінчуватися на "${t.suffix}"`:"includes"===t.format?`Неправильний рядок: повинен містити "${t.includes}"`:"regex"===t.format?`Неправильний рядок: повинен відповідати шаблону ${t.pattern}`:`Неправильний ${n[t.format]??e.format}` +}case"not_multiple_of": +return`Неправильне число: повинно бути кратним ${e.divisor}` +;case"unrecognized_keys": +return`Нерозпізнаний ключ${e.keys.length>1?"і":""}: ${l(e.keys,", ")}` +;case"invalid_key":return`Неправильний ключ у ${e.origin}`;case"invalid_union": +default:return"Неправильні вхідні дані";case"invalid_element": +return`Неправильне значення у ${e.origin}`}}};function ja(){return{ +localeError:Ba()}}const Ua=()=>{const e={string:{unit:"حروف",verb:"ہونا"},file:{ +unit:"بائٹس",verb:"ہونا"},array:{unit:"آئٹمز",verb:"ہونا"},set:{unit:"آئٹمز", +verb:"ہونا"}};function t(t){return e[t]??null}const n={regex:"ان پٹ", +email:"ای میل ایڈریس",url:"یو آر ایل",emoji:"ایموجی",uuid:"یو یو آئی ڈی", +uuidv4:"یو یو آئی ڈی وی 4",uuidv6:"یو یو آئی ڈی وی 6",nanoid:"نینو آئی ڈی", +guid:"جی یو آئی ڈی",cuid:"سی یو آئی ڈی",cuid2:"سی یو آئی ڈی 2", +ulid:"یو ایل آئی ڈی",xid:"ایکس آئی ڈی",ksuid:"کے ایس یو آئی ڈی", +datetime:"آئی ایس او ڈیٹ ٹائم",date:"آئی ایس او تاریخ",time:"آئی ایس او وقت", +duration:"آئی ایس او مدت",ipv4:"آئی پی وی 4 ایڈریس",ipv6:"آئی پی وی 6 ایڈریس", +cidrv4:"آئی پی وی 4 رینج",cidrv6:"آئی پی وی 6 رینج", +base64:"بیس 64 ان کوڈڈ سٹرنگ",base64url:"بیس 64 یو آر ایل ان کوڈڈ سٹرنگ", +json_string:"جے ایس او این سٹرنگ",e164:"ای 164 نمبر",jwt:"جے ڈبلیو ٹی", +template_literal:"ان پٹ"},r={nan:"NaN",number:"نمبر",array:"آرے",null:"نل"} +;return e=>{switch(e.code){case"invalid_type":{ +const t=r[e.expected]??e.expected,n=q(e.input),a=r[n]??n +;return/^[A-Z]/.test(e.expected)?`غلط ان پٹ: instanceof ${e.expected} متوقع تھا، ${a} موصول ہوا`:`غلط ان پٹ: ${t} متوقع تھا، ${a} موصول ہوا` +}case"invalid_value": +return 1===e.values.length?`غلط ان پٹ: ${$(e.values[0])} متوقع تھا`:`غلط آپشن: ${l(e.values,"|")} میں سے ایک متوقع تھا` +;case"too_big":{const n=e.inclusive?"<=":"<",r=t(e.origin) +;return r?`بہت بڑا: ${e.origin??"ویلیو"} کے ${n}${e.maximum.toString()} ${r.unit??"عناصر"} ہونے متوقع تھے`:`بہت بڑا: ${e.origin??"ویلیو"} کا ${n}${e.maximum.toString()} ہونا متوقع تھا` +}case"too_small":{const n=e.inclusive?">=":">",r=t(e.origin) +;return r?`بہت چھوٹا: ${e.origin} کے ${n}${e.minimum.toString()} ${r.unit} ہونے متوقع تھے`:`بہت چھوٹا: ${e.origin} کا ${n}${e.minimum.toString()} ہونا متوقع تھا` +}case"invalid_format":{const t=e +;return"starts_with"===t.format?`غلط سٹرنگ: "${t.prefix}" سے شروع ہونا چاہیے`:"ends_with"===t.format?`غلط سٹرنگ: "${t.suffix}" پر ختم ہونا چاہیے`:"includes"===t.format?`غلط سٹرنگ: "${t.includes}" شامل ہونا چاہیے`:"regex"===t.format?`غلط سٹرنگ: پیٹرن ${t.pattern} سے میچ ہونا چاہیے`:`غلط ${n[t.format]??e.format}` +}case"not_multiple_of":return`غلط نمبر: ${e.divisor} کا مضاعف ہونا چاہیے` +;case"unrecognized_keys": +return`غیر تسلیم شدہ کی${e.keys.length>1?"ز":""}: ${l(e.keys,"، ")}` +;case"invalid_key":return`${e.origin} میں غلط کی`;case"invalid_union":default: +return"غلط ان پٹ";case"invalid_element":return`${e.origin} میں غلط ویلیو`}}} +;const za=()=>{const e={string:{unit:"belgi",verb:"bo‘lishi kerak"},file:{ +unit:"bayt",verb:"bo‘lishi kerak"},array:{unit:"element",verb:"bo‘lishi kerak"}, +set:{unit:"element",verb:"bo‘lishi kerak"}};function t(t){return e[t]??null} +const n={regex:"kirish",email:"elektron pochta manzili",url:"URL",emoji:"emoji", +uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID", +cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID", +datetime:"ISO sana va vaqti",date:"ISO sana",time:"ISO vaqt", +duration:"ISO davomiylik",ipv4:"IPv4 manzil",ipv6:"IPv6 manzil", +mac:"MAC manzil",cidrv4:"IPv4 diapazon",cidrv6:"IPv6 diapazon", +base64:"base64 kodlangan satr",base64url:"base64url kodlangan satr", +json_string:"JSON satr",e164:"E.164 raqam",jwt:"JWT",template_literal:"kirish" +},r={nan:"NaN",number:"raqam",array:"massiv"};return e=>{switch(e.code){ +case"invalid_type":{const t=r[e.expected]??e.expected,n=q(e.input),a=r[n]??n +;return/^[A-Z]/.test(e.expected)?`Noto‘g‘ri kirish: kutilgan instanceof ${e.expected}, qabul qilingan ${a}`:`Noto‘g‘ri kirish: kutilgan ${t}, qabul qilingan ${a}` +}case"invalid_value": +return 1===e.values.length?`Noto‘g‘ri kirish: kutilgan ${$(e.values[0])}`:`Noto‘g‘ri variant: quyidagilardan biri kutilgan ${l(e.values,"|")}` +;case"too_big":{const n=e.inclusive?"<=":"<",r=t(e.origin) +;return r?`Juda katta: kutilgan ${e.origin??"qiymat"} ${n}${e.maximum.toString()} ${r.unit} ${r.verb}`:`Juda katta: kutilgan ${e.origin??"qiymat"} ${n}${e.maximum.toString()}` +}case"too_small":{const n=e.inclusive?">=":">",r=t(e.origin) +;return r?`Juda kichik: kutilgan ${e.origin} ${n}${e.minimum.toString()} ${r.unit} ${r.verb}`:`Juda kichik: kutilgan ${e.origin} ${n}${e.minimum.toString()}` +}case"invalid_format":{const t=e +;return"starts_with"===t.format?`Noto‘g‘ri satr: "${t.prefix}" bilan boshlanishi kerak`:"ends_with"===t.format?`Noto‘g‘ri satr: "${t.suffix}" bilan tugashi kerak`:"includes"===t.format?`Noto‘g‘ri satr: "${t.includes}" ni o‘z ichiga olishi kerak`:"regex"===t.format?`Noto‘g‘ri satr: ${t.pattern} shabloniga mos kelishi kerak`:`Noto‘g‘ri ${n[t.format]??e.format}` +}case"not_multiple_of": +return`Noto‘g‘ri raqam: ${e.divisor} ning karralisi bo‘lishi kerak` +;case"unrecognized_keys": +return`Noma’lum kalit${e.keys.length>1?"lar":""}: ${l(e.keys,", ")}` +;case"invalid_key":return`${e.origin} dagi kalit noto‘g‘ri`;case"invalid_union": +default:return"Noto‘g‘ri kirish";case"invalid_element": +return`${e.origin} da noto‘g‘ri qiymat`}}};const Za=()=>{const e={string:{ +unit:"ký tự",verb:"có"},file:{unit:"byte",verb:"có"},array:{unit:"phần tử", +verb:"có"},set:{unit:"phần tử",verb:"có"}};function t(t){return e[t]??null} +const n={regex:"đầu vào",email:"địa chỉ email",url:"URL",emoji:"emoji", +uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID", +cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID", +datetime:"ngày giờ ISO",date:"ngày ISO",time:"giờ ISO", +duration:"khoảng thời gian ISO",ipv4:"địa chỉ IPv4",ipv6:"địa chỉ IPv6", +cidrv4:"dải IPv4",cidrv6:"dải IPv6",base64:"chuỗi mã hóa base64", +base64url:"chuỗi mã hóa base64url",json_string:"chuỗi JSON",e164:"số E.164", +jwt:"JWT",template_literal:"đầu vào"},r={nan:"NaN",number:"số",array:"mảng"} +;return e=>{switch(e.code){case"invalid_type":{ +const t=r[e.expected]??e.expected,n=q(e.input),a=r[n]??n +;return/^[A-Z]/.test(e.expected)?`Đầu vào không hợp lệ: mong đợi instanceof ${e.expected}, nhận được ${a}`:`Đầu vào không hợp lệ: mong đợi ${t}, nhận được ${a}` +}case"invalid_value": +return 1===e.values.length?`Đầu vào không hợp lệ: mong đợi ${$(e.values[0])}`:`Tùy chọn không hợp lệ: mong đợi một trong các giá trị ${l(e.values,"|")}` +;case"too_big":{const n=e.inclusive?"<=":"<",r=t(e.origin) +;return r?`Quá lớn: mong đợi ${e.origin??"giá trị"} ${r.verb} ${n}${e.maximum.toString()} ${r.unit??"phần tử"}`:`Quá lớn: mong đợi ${e.origin??"giá trị"} ${n}${e.maximum.toString()}` +}case"too_small":{const n=e.inclusive?">=":">",r=t(e.origin) +;return r?`Quá nhỏ: mong đợi ${e.origin} ${r.verb} ${n}${e.minimum.toString()} ${r.unit}`:`Quá nhỏ: mong đợi ${e.origin} ${n}${e.minimum.toString()}` +}case"invalid_format":{const t=e +;return"starts_with"===t.format?`Chuỗi không hợp lệ: phải bắt đầu bằng "${t.prefix}"`:"ends_with"===t.format?`Chuỗi không hợp lệ: phải kết thúc bằng "${t.suffix}"`:"includes"===t.format?`Chuỗi không hợp lệ: phải bao gồm "${t.includes}"`:"regex"===t.format?`Chuỗi không hợp lệ: phải khớp với mẫu ${t.pattern}`:`${n[t.format]??e.format} không hợp lệ` +}case"not_multiple_of":return`Số không hợp lệ: phải là bội số của ${e.divisor}` +;case"unrecognized_keys":return`Khóa không được nhận dạng: ${l(e.keys,", ")}` +;case"invalid_key":return`Khóa không hợp lệ trong ${e.origin}` +;case"invalid_union":default:return"Đầu vào không hợp lệ";case"invalid_element": +return`Giá trị không hợp lệ trong ${e.origin}`}}};const Fa=()=>{const e={ +string:{unit:"字符",verb:"包含"},file:{unit:"字节",verb:"包含"},array:{unit:"项", +verb:"包含"},set:{unit:"项",verb:"包含"}};function t(t){return e[t]??null}const n={ +regex:"输入",email:"电子邮件",url:"URL",emoji:"表情符号",uuid:"UUID",uuidv4:"UUIDv4", +uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2", +ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO日期时间",date:"ISO日期", +time:"ISO时间",duration:"ISO时长",ipv4:"IPv4地址",ipv6:"IPv6地址",cidrv4:"IPv4网段", +cidrv6:"IPv6网段",base64:"base64编码字符串",base64url:"base64url编码字符串", +json_string:"JSON字符串",e164:"E.164号码",jwt:"JWT",template_literal:"输入"},r={ +nan:"NaN",number:"数字",array:"数组",null:"空值(null)"};return e=>{switch(e.code){ +case"invalid_type":{const t=r[e.expected]??e.expected,n=q(e.input),a=r[n]??n +;return/^[A-Z]/.test(e.expected)?`无效输入:期望 instanceof ${e.expected},实际接收 ${a}`:`无效输入:期望 ${t},实际接收 ${a}` +}case"invalid_value": +return 1===e.values.length?`无效输入:期望 ${$(e.values[0])}`:`无效选项:期望以下之一 ${l(e.values,"|")}` +;case"too_big":{const n=e.inclusive?"<=":"<",r=t(e.origin) +;return r?`数值过大:期望 ${e.origin??"值"} ${n}${e.maximum.toString()} ${r.unit??"个元素"}`:`数值过大:期望 ${e.origin??"值"} ${n}${e.maximum.toString()}` +}case"too_small":{const n=e.inclusive?">=":">",r=t(e.origin) +;return r?`数值过小:期望 ${e.origin} ${n}${e.minimum.toString()} ${r.unit}`:`数值过小:期望 ${e.origin} ${n}${e.minimum.toString()}` +}case"invalid_format":{const t=e +;return"starts_with"===t.format?`无效字符串:必须以 "${t.prefix}" 开头`:"ends_with"===t.format?`无效字符串:必须以 "${t.suffix}" 结尾`:"includes"===t.format?`无效字符串:必须包含 "${t.includes}"`:"regex"===t.format?`无效字符串:必须满足正则表达式 ${t.pattern}`:`无效${n[t.format]??e.format}` +}case"not_multiple_of":return`无效数字:必须是 ${e.divisor} 的倍数` +;case"unrecognized_keys":return`出现未知的键(key): ${l(e.keys,", ")}` +;case"invalid_key":return`${e.origin} 中的键(key)无效`;case"invalid_union":default: +return"无效输入";case"invalid_element":return`${e.origin} 中包含无效值(value)`}}} +;const Ha=()=>{const e={string:{unit:"字元",verb:"擁有"},file:{unit:"位元組",verb:"擁有" +},array:{unit:"項目",verb:"擁有"},set:{unit:"項目",verb:"擁有"}};function t(t){ +return e[t]??null}const n={regex:"輸入",email:"郵件地址",url:"URL",emoji:"emoji", +uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID", +cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID", +datetime:"ISO 日期時間",date:"ISO 日期",time:"ISO 時間",duration:"ISO 期間", +ipv4:"IPv4 位址",ipv6:"IPv6 位址",cidrv4:"IPv4 範圍",cidrv6:"IPv6 範圍", +base64:"base64 編碼字串",base64url:"base64url 編碼字串",json_string:"JSON 字串", +e164:"E.164 數值",jwt:"JWT",template_literal:"輸入"},r={nan:"NaN"};return e=>{ +switch(e.code){case"invalid_type":{ +const t=r[e.expected]??e.expected,n=q(e.input),a=r[n]??n +;return/^[A-Z]/.test(e.expected)?`無效的輸入值:預期為 instanceof ${e.expected},但收到 ${a}`:`無效的輸入值:預期為 ${t},但收到 ${a}` +}case"invalid_value": +return 1===e.values.length?`無效的輸入值:預期為 ${$(e.values[0])}`:`無效的選項:預期為以下其中之一 ${l(e.values,"|")}` +;case"too_big":{const n=e.inclusive?"<=":"<",r=t(e.origin) +;return r?`數值過大:預期 ${e.origin??"值"} 應為 ${n}${e.maximum.toString()} ${r.unit??"個元素"}`:`數值過大:預期 ${e.origin??"值"} 應為 ${n}${e.maximum.toString()}` +}case"too_small":{const n=e.inclusive?">=":">",r=t(e.origin) +;return r?`數值過小:預期 ${e.origin} 應為 ${n}${e.minimum.toString()} ${r.unit}`:`數值過小:預期 ${e.origin} 應為 ${n}${e.minimum.toString()}` +}case"invalid_format":{const t=e +;return"starts_with"===t.format?`無效的字串:必須以 "${t.prefix}" 開頭`:"ends_with"===t.format?`無效的字串:必須以 "${t.suffix}" 結尾`:"includes"===t.format?`無效的字串:必須包含 "${t.includes}"`:"regex"===t.format?`無效的字串:必須符合格式 ${t.pattern}`:`無效的 ${n[t.format]??e.format}` +}case"not_multiple_of":return`無效的數字:必須為 ${e.divisor} 的倍數` +;case"unrecognized_keys": +return`無法識別的鍵值${e.keys.length>1?"們":""}:${l(e.keys,"、")}`;case"invalid_key": +return`${e.origin} 中有無效的鍵值`;case"invalid_union":default:return"無效的輸入值" +;case"invalid_element":return`${e.origin} 中有無效的值`}}};const Qa=()=>{const e={ +string:{unit:"àmi",verb:"ní"},file:{unit:"bytes",verb:"ní"},array:{unit:"nkan", +verb:"ní"},set:{unit:"nkan",verb:"ní"}};function t(t){return e[t]??null} +const n={regex:"ẹ̀rọ ìbáwọlé",email:"àdírẹ́sì ìmẹ́lì",url:"URL",emoji:"emoji", +uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID", +cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID", +datetime:"àkókò ISO",date:"ọjọ́ ISO",time:"àkókò ISO", +duration:"àkókò tó pé ISO",ipv4:"àdírẹ́sì IPv4",ipv6:"àdírẹ́sì IPv6", +cidrv4:"àgbègbè IPv4",cidrv6:"àgbègbè IPv6",base64:"ọ̀rọ̀ tí a kọ́ ní base64", +base64url:"ọ̀rọ̀ base64url",json_string:"ọ̀rọ̀ JSON",e164:"nọ́mbà E.164", +jwt:"JWT",template_literal:"ẹ̀rọ ìbáwọlé"},r={nan:"NaN",number:"nọ́mbà", +array:"akopọ"};return e=>{switch(e.code){case"invalid_type":{ +const t=r[e.expected]??e.expected,n=q(e.input),a=r[n]??n +;return/^[A-Z]/.test(e.expected)?`Ìbáwọlé aṣìṣe: a ní láti fi instanceof ${e.expected}, àmọ̀ a rí ${a}`:`Ìbáwọlé aṣìṣe: a ní láti fi ${t}, àmọ̀ a rí ${a}` +}case"invalid_value": +return 1===e.values.length?`Ìbáwọlé aṣìṣe: a ní láti fi ${$(e.values[0])}`:`Àṣàyàn aṣìṣe: yan ọ̀kan lára ${l(e.values,"|")}` +;case"too_big":{const n=e.inclusive?"<=":"<",r=t(e.origin) +;return r?`Tó pọ̀ jù: a ní láti jẹ́ pé ${e.origin??"iye"} ${r.verb} ${n}${e.maximum} ${r.unit}`:`Tó pọ̀ jù: a ní láti jẹ́ ${n}${e.maximum}` +}case"too_small":{const n=e.inclusive?">=":">",r=t(e.origin) +;return r?`Kéré ju: a ní láti jẹ́ pé ${e.origin} ${r.verb} ${n}${e.minimum} ${r.unit}`:`Kéré ju: a ní láti jẹ́ ${n}${e.minimum}` +}case"invalid_format":{const t=e +;return"starts_with"===t.format?`Ọ̀rọ̀ aṣìṣe: gbọ́dọ̀ bẹ̀rẹ̀ pẹ̀lú "${t.prefix}"`:"ends_with"===t.format?`Ọ̀rọ̀ aṣìṣe: gbọ́dọ̀ parí pẹ̀lú "${t.suffix}"`:"includes"===t.format?`Ọ̀rọ̀ aṣìṣe: gbọ́dọ̀ ní "${t.includes}"`:"regex"===t.format?`Ọ̀rọ̀ aṣìṣe: gbọ́dọ̀ bá àpẹẹrẹ mu ${t.pattern}`:`Aṣìṣe: ${n[t.format]??e.format}` +}case"not_multiple_of": +return`Nọ́mbà aṣìṣe: gbọ́dọ̀ jẹ́ èyà pípín ti ${e.divisor}` +;case"unrecognized_keys":return`Bọtìnì àìmọ̀: ${l(e.keys,", ")}` +;case"invalid_key":return`Bọtìnì aṣìṣe nínú ${e.origin}`;case"invalid_union": +default:return"Ìbáwọlé aṣìṣe";case"invalid_element": +return`Iye aṣìṣe nínú ${e.origin}`}}} +;const Va=Object.freeze(Object.defineProperty({__proto__:null,ar:function(){ +return{localeError:Hr()}},az:function(){return{localeError:Qr()}},be:function(){ +return{localeError:qr()}},bg:function(){return{localeError:Wr()}},ca:function(){ +return{localeError:Xr()}},cs:function(){return{localeError:Gr()}},da:function(){ +return{localeError:Yr()}},de:function(){return{localeError:Kr()}},en:ea, +eo:function(){return{localeError:ta()}},es:function(){return{localeError:na()}}, +fa:function(){return{localeError:ra()}},fi:function(){return{localeError:aa()}}, +fr:function(){return{localeError:oa()}},frCA:function(){return{localeError:ia()} +},he:function(){return{localeError:sa()}},hu:function(){return{localeError:la()} +},hy:function(){return{localeError:da()}},id:function(){return{localeError:pa()} +},is:function(){return{localeError:ha()}},it:function(){return{localeError:fa()} +},ja:function(){return{localeError:ma()}},ka:function(){return{localeError:ga()} +},kh:function(){return ba()},km:ba,ko:function(){return{localeError:ya()}}, +lt:function(){return{localeError:xa()}},mk:function(){return{localeError:ka()}}, +ms:function(){return{localeError:Sa()}},nl:function(){return{localeError:_a()}}, +no:function(){return{localeError:Aa()}},ota:function(){return{localeError:Ta()} +},pl:function(){return{localeError:Ca()}},ps:function(){return{localeError:Ea()} +},pt:function(){return{localeError:$a()}},ru:function(){return{localeError:Ia()} +},sl:function(){return{localeError:Da()}},sv:function(){return{localeError:Ma()} +},ta:function(){return{localeError:Na()}},th:function(){return{localeError:Ra()} +},tr:function(){return{localeError:La()}},ua:function(){return ja()},uk:ja, +ur:function(){return{localeError:Ua()}},uz:function(){return{localeError:za()}}, +vi:function(){return{localeError:Za()}},yo:function(){return{localeError:Qa()}}, +zhCN:function(){return{localeError:Fa()}},zhTW:function(){return{ +localeError:Ha()}}},Symbol.toStringTag,{value:"Module"}));var qa +;const Wa=Symbol("ZodOutput"),Xa=Symbol("ZodInput");class Ga{constructor(){ +this._map=new WeakMap,this._idmap=new Map}add(e,...t){const n=t[0] +;return this._map.set(e,n), +n&&"object"==typeof n&&"id"in n&&this._idmap.set(n.id,e),this}clear(){ +return this._map=new WeakMap,this._idmap=new Map,this}remove(e){ +const t=this._map.get(e) +;return t&&"object"==typeof t&&"id"in t&&this._idmap.delete(t.id), +this._map.delete(e),this}get(e){const t=e._zod.parent;if(t){const n={ +...this.get(t)??{}};delete n.id;const r={...n,...this._map.get(e)} +;return Object.keys(r).length?r:void 0}return this._map.get(e)}has(e){ +return this._map.has(e)}}function Ya(){return new Ga} +(qa=globalThis).__zod_globalRegistry??(qa.__zod_globalRegistry=Ya()) +;const Ka=globalThis.__zod_globalRegistry;function Ja(e,t){return new e({ +type:"string",...C(t)})}function eo(e,t){return new e({type:"string",coerce:!0, +...C(t)})}function to(e,t){return new e({type:"string",format:"email", +check:"string_format",abort:!1,...C(t)})}function no(e,t){return new e({ +type:"string",format:"guid",check:"string_format",abort:!1,...C(t)})} +function ro(e,t){return new e({type:"string",format:"uuid", +check:"string_format",abort:!1,...C(t)})}function ao(e,t){return new e({ +type:"string",format:"uuid",check:"string_format",abort:!1,version:"v4",...C(t) +})}function oo(e,t){return new e({type:"string",format:"uuid", +check:"string_format",abort:!1,version:"v6",...C(t)})}function io(e,t){ +return new e({type:"string",format:"uuid",check:"string_format",abort:!1, +version:"v7",...C(t)})}function so(e,t){return new e({type:"string", +format:"url",check:"string_format",abort:!1,...C(t)})}function lo(e,t){ +return new e({type:"string",format:"emoji",check:"string_format",abort:!1, +...C(t)})}function co(e,t){return new e({type:"string",format:"nanoid", +check:"string_format",abort:!1,...C(t)})}function uo(e,t){return new e({ +type:"string",format:"cuid",check:"string_format",abort:!1,...C(t)})} +function po(e,t){return new e({type:"string",format:"cuid2", +check:"string_format",abort:!1,...C(t)})}function ho(e,t){return new e({ +type:"string",format:"ulid",check:"string_format",abort:!1,...C(t)})} +function fo(e,t){return new e({type:"string",format:"xid",check:"string_format", +abort:!1,...C(t)})}function mo(e,t){return new e({type:"string",format:"ksuid", +check:"string_format",abort:!1,...C(t)})}function go(e,t){return new e({ +type:"string",format:"ipv4",check:"string_format",abort:!1,...C(t)})} +function vo(e,t){return new e({type:"string",format:"ipv6", +check:"string_format",abort:!1,...C(t)})}function bo(e,t){return new e({ +type:"string",format:"mac",check:"string_format",abort:!1,...C(t)})} +function yo(e,t){return new e({type:"string",format:"cidrv4", +check:"string_format",abort:!1,...C(t)})}function Oo(e,t){return new e({ +type:"string",format:"cidrv6",check:"string_format",abort:!1,...C(t)})} +function wo(e,t){return new e({type:"string",format:"base64", +check:"string_format",abort:!1,...C(t)})}function xo(e,t){return new e({ +type:"string",format:"base64url",check:"string_format",abort:!1,...C(t)})} +function ko(e,t){return new e({type:"string",format:"e164", +check:"string_format",abort:!1,...C(t)})}function So(e,t){return new e({ +type:"string",format:"jwt",check:"string_format",abort:!1,...C(t)})}const _o={ +Any:null,Minute:-1,Second:0,Millisecond:3,Microsecond:6};function Ao(e,t){ +return new e({type:"string",format:"datetime",check:"string_format",offset:!1, +local:!1,precision:null,...C(t)})}function To(e,t){return new e({type:"string", +format:"date",check:"string_format",...C(t)})}function Eo(e,t){return new e({ +type:"string",format:"time",check:"string_format",precision:null,...C(t)})} +function Co(e,t){return new e({type:"string",format:"duration", +check:"string_format",...C(t)})}function $o(e,t){return new e({type:"number", +checks:[],...C(t)})}function Po(e,t){return new e({type:"number",coerce:!0, +checks:[],...C(t)})}function Io(e,t){return new e({type:"number", +check:"number_format",abort:!1,format:"safeint",...C(t)})}function Do(e,t){ +return new e({type:"number",check:"number_format",abort:!1,format:"float32", +...C(t)})}function Mo(e,t){return new e({type:"number",check:"number_format", +abort:!1,format:"float64",...C(t)})}function No(e,t){return new e({ +type:"number",check:"number_format",abort:!1,format:"int32",...C(t)})} +function Ro(e,t){return new e({type:"number",check:"number_format",abort:!1, +format:"uint32",...C(t)})}function Lo(e,t){return new e({type:"boolean",...C(t) +})}function Bo(e,t){return new e({type:"boolean",coerce:!0,...C(t)})} +function jo(e,t){return new e({type:"bigint",...C(t)})}function Uo(e,t){ +return new e({type:"bigint",coerce:!0,...C(t)})}function zo(e,t){return new e({ +type:"bigint",check:"bigint_format",abort:!1,format:"int64",...C(t)})} +function Zo(e,t){return new e({type:"bigint",check:"bigint_format",abort:!1, +format:"uint64",...C(t)})}function Fo(e,t){return new e({type:"symbol",...C(t)}) +}function Ho(e,t){return new e({type:"undefined",...C(t)})}function Qo(e,t){ +return new e({type:"null",...C(t)})}function Vo(e){return new e({type:"any"})} +function qo(e){return new e({type:"unknown"})}function Wo(e,t){return new e({ +type:"never",...C(t)})}function Xo(e,t){return new e({type:"void",...C(t)})} +function Go(e,t){return new e({type:"date",...C(t)})}function Yo(e,t){ +return new e({type:"date",coerce:!0,...C(t)})}function Ko(e,t){return new e({ +type:"nan",...C(t)})}function Jo(e,t){return new It({check:"less_than",...C(t), +value:e,inclusive:!1})}function ei(e,t){return new It({check:"less_than", +...C(t),value:e,inclusive:!0})}function ti(e,t){return new Dt({ +check:"greater_than",...C(t),value:e,inclusive:!1})}function ni(e,t){ +return new Dt({check:"greater_than",...C(t),value:e,inclusive:!0})} +function ri(e){return ti(0,e)}function ai(e){return Jo(0,e)}function oi(e){ +return ei(0,e)}function ii(e){return ni(0,e)}function si(e,t){return new Mt({ +check:"multiple_of",...C(t),value:e})}function li(e,t){return new Lt({ +check:"max_size",...C(t),maximum:e})}function ci(e,t){return new Bt({ +check:"min_size",...C(t),minimum:e})}function ui(e,t){return new jt({ +check:"size_equals",...C(t),size:e})}function di(e,t){return new Ut({ +check:"max_length",...C(t),maximum:e})}function pi(e,t){return new zt({ +check:"min_length",...C(t),minimum:e})}function hi(e,t){return new Zt({ +check:"length_equals",...C(t),length:e})}function fi(e,t){return new Ht({ +check:"string_format",format:"regex",...C(t),pattern:e})}function mi(e){ +return new Qt({check:"string_format",format:"lowercase",...C(e)})} +function gi(e){return new Vt({check:"string_format",format:"uppercase",...C(e)}) +}function vi(e,t){return new qt({check:"string_format",format:"includes", +...C(t),includes:e})}function bi(e,t){return new Wt({check:"string_format", +format:"starts_with",...C(t),prefix:e})}function yi(e,t){return new Xt({ +check:"string_format",format:"ends_with",...C(t),suffix:e})}function Oi(e,t,n){ +return new Yt({check:"property",property:e,schema:t,...C(n)})}function wi(e,t){ +return new Kt({check:"mime_type",mime:e,...C(t)})}function xi(e){return new Jt({ +check:"overwrite",tx:e})}function ki(e){return xi((t=>t.normalize(e)))} +function Si(){return xi((e=>e.trim()))}function _i(){ +return xi((e=>e.toLowerCase()))}function Ai(){return xi((e=>e.toUpperCase()))} +function Ti(){return xi((e=>y(e)))}function Ei(e,t,n){return new e({ +type:"array",element:t,...C(n)})}function Ci(e,t){return new e({type:"file", +...C(t)})}function $i(e,t,n){const r=C(n);r.abort??(r.abort=!0);return new e({ +type:"custom",check:"custom",fn:t,...r})}function Pi(e,t,n){return new e({ +type:"custom",check:"custom",fn:t,...C(n)})}function Ii(e){ +const t=Di((n=>(n.addIssue=e=>{ +if("string"==typeof e)n.issues.push(W(e,n.value,t._zod.def));else{const r=e +;r.fatal&&(r.continue=!1), +r.code??(r.code="custom"),r.input??(r.input=n.value),r.inst??(r.inst=t), +r.continue??(r.continue=!t._zod.def.abort),n.issues.push(W(r))}},e(n.value,n)))) +;return t}function Di(e,t){const n=new $t({check:"custom",...C(t)}) +;return n._zod.check=e,n}function Mi(e){const t=new $t({check:"describe"}) +;return t._zod.onattach=[t=>{const n=Ka.get(t)??{};Ka.add(t,{...n,description:e +})}],t._zod.check=()=>{},t}function Ni(e){const t=new $t({check:"meta"}) +;return t._zod.onattach=[t=>{const n=Ka.get(t)??{};Ka.add(t,{...n,...e}) +}],t._zod.check=()=>{},t}function Ri(e,t){const n=C(t) +;let r=n.truthy??["true","1","yes","on","y","enabled"],a=n.falsy??["false","0","no","off","n","disabled"] +;"sensitive"!==n.case&&(r=r.map((e=>"string"==typeof e?e.toLowerCase():e)), +a=a.map((e=>"string"==typeof e?e.toLowerCase():e))) +;const o=new Set(r),i=new Set(a),s=e.Codec??Dr,l=e.Boolean??Rn,c=new s({ +type:"pipe",in:new(e.String??rn)({type:"string",error:n.error}),out:new l({ +type:"boolean",error:n.error}),transform:(e,t)=>{let r=e +;return"sensitive"!==n.case&&(r=r.toLowerCase()), +!!o.has(r)||!i.has(r)&&(t.issues.push({code:"invalid_value", +expected:"stringbool",values:[...o,...i],input:t.value,inst:c,continue:!1}),{}) +},reverseTransform:(e,t)=>!0===e?r[0]||"true":a[0]||"false",error:n.error}) +;return c}function Li(e,t,n,r={}){const a=C(r),o={...C(r),check:"string_format", +type:"string",format:t,fn:"function"==typeof n?n:e=>n.test(e),...a} +;n instanceof RegExp&&(o.pattern=n);return new e(o)}function Bi(e){ +let t=e?.target??"draft-2020-12" +;return"draft-4"===t&&(t="draft-04"),"draft-7"===t&&(t="draft-07"),{ +processors:e.processors??{},metadataRegistry:e?.metadata??Ka,target:t, +unrepresentable:e?.unrepresentable??"throw",override:e?.override??(()=>{}), +io:e?.io??"output",counter:0,seen:new Map,cycles:e?.cycles??"ref", +reused:e?.reused??"inline",external:e?.external??void 0}}function ji(e,t,n={ +path:[],schemaPath:[]}){var r;const a=e._zod.def,o=t.seen.get(e);if(o){o.count++ +;return n.schemaPath.includes(e)&&(o.cycle=n.path),o.schema}const i={schema:{}, +count:1,cycle:void 0,path:n.path};t.seen.set(e,i) +;const s=e._zod.toJSONSchema?.();if(s)i.schema=s;else{const r={...n, +schemaPath:[...n.schemaPath,e],path:n.path} +;if(e._zod.processJSONSchema)e._zod.processJSONSchema(t,i.schema,r);else{ +const n=i.schema,o=t.processors[a.type] +;if(!o)throw new Error(`[toJSONSchema]: Non-representable type encountered: ${a.type}`) +;o(e,t,n,r)}const o=e._zod.parent +;o&&(i.ref||(i.ref=o),ji(o,t,r),t.seen.get(o).isParent=!0)} +const l=t.metadataRegistry.get(e) +;l&&Object.assign(i.schema,l),"input"===t.io&&Zi(e)&&(delete i.schema.examples, +delete i.schema.default), +"input"===t.io&&i.schema._prefault&&((r=i.schema).default??(r.default=i.schema._prefault)), +delete i.schema._prefault;return t.seen.get(e).schema}function Ui(e,t){ +const n=e.seen.get(t) +;if(!n)throw new Error("Unprocessed schema. This is a bug in Zod.") +;const r=new Map;for(const o of e.seen.entries()){ +const t=e.metadataRegistry.get(o[0])?.id;if(t){const e=r.get(t) +;if(e&&e!==o[0])throw new Error(`Duplicate schema id "${t}" detected during JSON Schema conversion. Two different schemas cannot share the same id when converted together.`) +;r.set(t,o[0])}}const a=t=>{if(t[1].schema.$ref)return +;const r=t[1],{ref:a,defId:o}=(t=>{ +const r="draft-2020-12"===e.target?"$defs":"definitions";if(e.external){ +const n=e.external.registry.get(t[0])?.id,a=e.external.uri??(e=>e);if(n)return{ +ref:a(n)};const o=t[1].defId??t[1].schema.id??"schema"+e.counter++ +;return t[1].defId=o,{defId:o,ref:`${a("__shared")}#/${r}/${o}`}} +if(t[1]===n)return{ref:"#"} +;const a=`#/${r}/`,o=t[1].schema.id??"__schema"+e.counter++;return{defId:o, +ref:a+o}})(t);r.def={...r.schema},o&&(r.defId=o);const i=r.schema +;for(const e in i)delete i[e];i.$ref=a} +;if("throw"===e.cycles)for(const o of e.seen.entries()){const e=o[1] +;if(e.cycle)throw new Error(`Cycle detected: #/${e.cycle?.join("/")}/\n\nSet the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.`) +}for(const o of e.seen.entries()){const n=o[1];if(t===o[0]){a(o);continue} +if(e.external){const n=e.external.registry.get(o[0])?.id;if(t!==o[0]&&n){a(o) +;continue}}const r=e.metadataRegistry.get(o[0])?.id +;r?a(o):(n.cycle||n.count>1&&"ref"===e.reused)&&a(o)}}function zi(e,t){ +const n=e.seen.get(t) +;if(!n)throw new Error("Unprocessed schema. This is a bug in Zod.");const r=t=>{ +const n=e.seen.get(t);if(null===n.ref)return;const a=n.def??n.schema,o={...a +},i=n.ref;if(n.ref=null,i){r(i);const n=e.seen.get(i),s=n.schema +;!s.$ref||"draft-07"!==e.target&&"draft-04"!==e.target&&"openapi-3.0"!==e.target?Object.assign(a,s):(a.allOf=a.allOf??[], +a.allOf.push(s)),Object.assign(a,o) +;if(t._zod.parent===i)for(const e in a)"$ref"!==e&&"allOf"!==e&&(e in o||delete a[e]) +;if(s.$ref)for(const e in a)"$ref"!==e&&"allOf"!==e&&e in n.def&&JSON.stringify(a[e])===JSON.stringify(n.def[e])&&delete a[e] +}const s=t._zod.parent;if(s&&s!==i){r(s);const t=e.seen.get(s) +;if(t?.schema.$ref&&(a.$ref=t.schema.$ref, +t.def))for(const e in a)"$ref"!==e&&"allOf"!==e&&e in t.def&&JSON.stringify(a[e])===JSON.stringify(t.def[e])&&delete a[e] +}e.override({zodSchema:t,jsonSchema:a,path:n.path??[]})} +;for(const s of[...e.seen.entries()].reverse())r(s[0]);const a={} +;if("draft-2020-12"===e.target?a.$schema="https://json-schema.org/draft/2020-12/schema":"draft-07"===e.target?a.$schema="http://json-schema.org/draft-07/schema#":"draft-04"===e.target?a.$schema="http://json-schema.org/draft-04/schema#":e.target, +e.external?.uri){const n=e.external.registry.get(t)?.id +;if(!n)throw new Error("Schema is missing an `id` property") +;a.$id=e.external.uri(n)}Object.assign(a,n.def??n.schema) +;const o=e.external?.defs??{};for(const s of e.seen.entries()){const e=s[1] +;e.def&&e.defId&&(o[e.defId]=e.def)} +e.external||Object.keys(o).length>0&&("draft-2020-12"===e.target?a.$defs=o:a.definitions=o) +;try{const n=JSON.parse(JSON.stringify(a)) +;return Object.defineProperty(n,"~standard",{value:{...t["~standard"], +jsonSchema:{input:Hi(t,"input",e.processors),output:Hi(t,"output",e.processors)} +},enumerable:!1,writable:!1}),n}catch(i){ +throw new Error("Error converting schema to JSON.")}}function Zi(e,t){ +const n=t??{seen:new Set};if(n.seen.has(e))return!1;n.seen.add(e) +;const r=e._zod.def;if("transform"===r.type)return!0 +;if("array"===r.type)return Zi(r.element,n) +;if("set"===r.type)return Zi(r.valueType,n) +;if("lazy"===r.type)return Zi(r.getter(),n) +;if("promise"===r.type||"optional"===r.type||"nonoptional"===r.type||"nullable"===r.type||"readonly"===r.type||"default"===r.type||"prefault"===r.type)return Zi(r.innerType,n) +;if("intersection"===r.type)return Zi(r.left,n)||Zi(r.right,n) +;if("record"===r.type||"map"===r.type)return Zi(r.keyType,n)||Zi(r.valueType,n) +;if("pipe"===r.type)return Zi(r.in,n)||Zi(r.out,n);if("object"===r.type){ +for(const e in r.shape)if(Zi(r.shape[e],n))return!0;return!1} +if("union"===r.type){for(const e of r.options)if(Zi(e,n))return!0;return!1} +if("tuple"===r.type){for(const e of r.items)if(Zi(e,n))return!0 +;return!(!r.rest||!Zi(r.rest,n))}return!1}const Fi=(e,t={})=>n=>{const r=Bi({ +...n,processors:t});return ji(e,r),Ui(r,e),zi(r,e)},Hi=(e,t,n={})=>r=>{ +const{libraryOptions:a,target:o}=r??{},i=Bi({...a??{},target:o,io:t,processors:n +});return ji(e,i),Ui(i,e),zi(i,e)},Qi={guid:"uuid",url:"uri", +datetime:"date-time",json_string:"json-string",regex:""},Vi=(e,t,n,r)=>{ +const a=n;a.type="string" +;const{minimum:o,maximum:i,format:s,patterns:l,contentEncoding:c}=e._zod.bag +;if("number"==typeof o&&(a.minLength=o), +"number"==typeof i&&(a.maxLength=i),s&&(a.format=Qi[s]??s, +""===a.format&&delete a.format, +"time"===s&&delete a.format),c&&(a.contentEncoding=c),l&&l.size>0){ +const e=[...l] +;1===e.length?a.pattern=e[0].source:e.length>1&&(a.allOf=[...e.map((e=>({ +..."draft-07"===t.target||"draft-04"===t.target||"openapi-3.0"===t.target?{ +type:"string"}:{},pattern:e.source})))])}},qi=(e,t,n,r)=>{ +const a=n,{minimum:o,maximum:i,format:s,multipleOf:l,exclusiveMaximum:c,exclusiveMinimum:u}=e._zod.bag +;"string"==typeof s&&s.includes("int")?a.type="integer":a.type="number", +"number"==typeof u&&("draft-04"===t.target||"openapi-3.0"===t.target?(a.minimum=u, +a.exclusiveMinimum=!0):a.exclusiveMinimum=u), +"number"==typeof o&&(a.minimum=o,"number"==typeof u&&"draft-04"!==t.target&&(u>=o?delete a.minimum:delete a.exclusiveMinimum)), +"number"==typeof c&&("draft-04"===t.target||"openapi-3.0"===t.target?(a.maximum=c, +a.exclusiveMaximum=!0):a.exclusiveMaximum=c), +"number"==typeof i&&(a.maximum=i,"number"==typeof c&&"draft-04"!==t.target&&(c<=i?delete a.maximum:delete a.exclusiveMaximum)), +"number"==typeof l&&(a.multipleOf=l)},Wi=(e,t,n,r)=>{n.type="boolean" +},Xi=(e,t,n,r)=>{ +if("throw"===t.unrepresentable)throw new Error("BigInt cannot be represented in JSON Schema") +},Gi=(e,t,n,r)=>{ +if("throw"===t.unrepresentable)throw new Error("Symbols cannot be represented in JSON Schema") +},Yi=(e,t,n,r)=>{ +"openapi-3.0"===t.target?(n.type="string",n.nullable=!0,n.enum=[null]):n.type="null" +},Ki=(e,t,n,r)=>{ +if("throw"===t.unrepresentable)throw new Error("Undefined cannot be represented in JSON Schema") +},Ji=(e,t,n,r)=>{ +if("throw"===t.unrepresentable)throw new Error("Void cannot be represented in JSON Schema") +},es=(e,t,n,r)=>{n.not={}},ts=(e,t,n,r)=>{},ns=(e,t,n,r)=>{},rs=(e,t,n,r)=>{ +if("throw"===t.unrepresentable)throw new Error("Date cannot be represented in JSON Schema") +},as=(e,t,n,r)=>{const a=s(e._zod.def.entries) +;a.every((e=>"number"==typeof e))&&(n.type="number"), +a.every((e=>"string"==typeof e))&&(n.type="string"),n.enum=a},os=(e,t,n,r)=>{ +const a=e._zod.def,o=[];for(const i of a.values)if(void 0===i){ +if("throw"===t.unrepresentable)throw new Error("Literal `undefined` cannot be represented in JSON Schema") +}else if("bigint"==typeof i){ +if("throw"===t.unrepresentable)throw new Error("BigInt literals cannot be represented in JSON Schema") +;o.push(Number(i))}else o.push(i);if(0===o.length);else if(1===o.length){ +const e=o[0] +;n.type=null===e?"null":typeof e,"draft-04"===t.target||"openapi-3.0"===t.target?n.enum=[e]:n.const=e +}else o.every((e=>"number"==typeof e))&&(n.type="number"), +o.every((e=>"string"==typeof e))&&(n.type="string"), +o.every((e=>"boolean"==typeof e))&&(n.type="boolean"), +o.every((e=>null===e))&&(n.type="null"),n.enum=o},is=(e,t,n,r)=>{ +if("throw"===t.unrepresentable)throw new Error("NaN cannot be represented in JSON Schema") +},ss=(e,t,n,r)=>{const a=n,o=e._zod.pattern +;if(!o)throw new Error("Pattern not found in template literal");a.type="string", +a.pattern=o.source},ls=(e,t,n,r)=>{const a=n,o={type:"string",format:"binary", +contentEncoding:"binary"},{minimum:i,maximum:s,mime:l}=e._zod.bag +;void 0!==i&&(o.minLength=i), +void 0!==s&&(o.maxLength=s),l?1===l.length?(o.contentMediaType=l[0], +Object.assign(a,o)):(Object.assign(a,o),a.anyOf=l.map((e=>({contentMediaType:e +})))):Object.assign(a,o)},cs=(e,t,n,r)=>{n.type="boolean"},us=(e,t,n,r)=>{ +if("throw"===t.unrepresentable)throw new Error("Custom types cannot be represented in JSON Schema") +},ds=(e,t,n,r)=>{ +if("throw"===t.unrepresentable)throw new Error("Function types cannot be represented in JSON Schema") +},ps=(e,t,n,r)=>{ +if("throw"===t.unrepresentable)throw new Error("Transforms cannot be represented in JSON Schema") +},hs=(e,t,n,r)=>{ +if("throw"===t.unrepresentable)throw new Error("Map cannot be represented in JSON Schema") +},fs=(e,t,n,r)=>{ +if("throw"===t.unrepresentable)throw new Error("Set cannot be represented in JSON Schema") +},ms=(e,t,n,r)=>{const a=n,o=e._zod.def,{minimum:i,maximum:s}=e._zod.bag +;"number"==typeof i&&(a.minItems=i), +"number"==typeof s&&(a.maxItems=s),a.type="array",a.items=ji(o.element,t,{...r, +path:[...r.path,"items"]})},gs=(e,t,n,r)=>{const a=n,o=e._zod.def +;a.type="object",a.properties={};const i=o.shape +;for(const c in i)a.properties[c]=ji(i[c],t,{...r, +path:[...r.path,"properties",c]}) +;const s=new Set(Object.keys(i)),l=new Set([...s].filter((e=>{ +const n=o.shape[e]._zod;return"input"===t.io?void 0===n.optin:void 0===n.optout +}))) +;l.size>0&&(a.required=Array.from(l)),"never"===o.catchall?._zod.def.type?a.additionalProperties=!1:o.catchall?o.catchall&&(a.additionalProperties=ji(o.catchall,t,{ +...r,path:[...r.path,"additionalProperties"] +})):"output"===t.io&&(a.additionalProperties=!1)},vs=(e,t,n,r)=>{ +const a=e._zod.def,o=!1===a.inclusive,i=a.options.map(((e,n)=>ji(e,t,{...r, +path:[...r.path,o?"oneOf":"anyOf",n]})));o?n.oneOf=i:n.anyOf=i},bs=(e,t,n,r)=>{ +const a=e._zod.def,o=ji(a.left,t,{...r,path:[...r.path,"allOf",0] +}),i=ji(a.right,t,{...r,path:[...r.path,"allOf",1] +}),s=e=>"allOf"in e&&1===Object.keys(e).length,l=[...s(o)?o.allOf:[o],...s(i)?i.allOf:[i]] +;n.allOf=l},ys=(e,t,n,r)=>{const a=n,o=e._zod.def;a.type="array" +;const i="draft-2020-12"===t.target?"prefixItems":"items",s="draft-2020-12"===t.target||"openapi-3.0"===t.target?"items":"additionalItems",l=o.items.map(((e,n)=>ji(e,t,{ +...r,path:[...r.path,i,n]}))),c=o.rest?ji(o.rest,t,{...r, +path:[...r.path,s,..."openapi-3.0"===t.target?[o.items.length]:[]]}):null +;"draft-2020-12"===t.target?(a.prefixItems=l, +c&&(a.items=c)):"openapi-3.0"===t.target?(a.items={anyOf:l +},c&&a.items.anyOf.push(c), +a.minItems=l.length,c||(a.maxItems=l.length)):(a.items=l, +c&&(a.additionalItems=c));const{minimum:u,maximum:d}=e._zod.bag +;"number"==typeof u&&(a.minItems=u),"number"==typeof d&&(a.maxItems=d) +},Os=(e,t,n,r)=>{const a=n,o=e._zod.def;a.type="object" +;const i=o.keyType,s=i._zod.bag,l=s?.patterns;if("loose"===o.mode&&l&&l.size>0){ +const e=ji(o.valueType,t,{...r,path:[...r.path,"patternProperties","*"]}) +;a.patternProperties={};for(const t of l)a.patternProperties[t.source]=e +}else"draft-07"!==t.target&&"draft-2020-12"!==t.target||(a.propertyNames=ji(o.keyType,t,{ +...r,path:[...r.path,"propertyNames"] +})),a.additionalProperties=ji(o.valueType,t,{...r, +path:[...r.path,"additionalProperties"]});const c=i._zod.values;if(c){ +const e=[...c].filter((e=>"string"==typeof e||"number"==typeof e)) +;e.length>0&&(a.required=e)}},ws=(e,t,n,r)=>{ +const a=e._zod.def,o=ji(a.innerType,t,r),i=t.seen.get(e) +;"openapi-3.0"===t.target?(i.ref=a.innerType,n.nullable=!0):n.anyOf=[o,{ +type:"null"}]},xs=(e,t,n,r)=>{const a=e._zod.def;ji(a.innerType,t,r) +;t.seen.get(e).ref=a.innerType},ks=(e,t,n,r)=>{const a=e._zod.def +;ji(a.innerType,t,r) +;t.seen.get(e).ref=a.innerType,n.default=JSON.parse(JSON.stringify(a.defaultValue)) +},Ss=(e,t,n,r)=>{const a=e._zod.def;ji(a.innerType,t,r) +;t.seen.get(e).ref=a.innerType, +"input"===t.io&&(n._prefault=JSON.parse(JSON.stringify(a.defaultValue))) +},_s=(e,t,n,r)=>{const a=e._zod.def;ji(a.innerType,t,r);let o +;t.seen.get(e).ref=a.innerType;try{o=a.catchValue(void 0)}catch{ +throw new Error("Dynamic catch values are not supported in JSON Schema")} +n.default=o},As=(e,t,n,r)=>{ +const a=e._zod.def,o="input"===t.io?"transform"===a.in._zod.def.type?a.out:a.in:a.out +;ji(o,t,r);t.seen.get(e).ref=o},Ts=(e,t,n,r)=>{const a=e._zod.def +;ji(a.innerType,t,r);t.seen.get(e).ref=a.innerType,n.readOnly=!0 +},Es=(e,t,n,r)=>{const a=e._zod.def;ji(a.innerType,t,r) +;t.seen.get(e).ref=a.innerType},Cs=(e,t,n,r)=>{const a=e._zod.def +;ji(a.innerType,t,r);t.seen.get(e).ref=a.innerType},$s=(e,t,n,r)=>{ +const a=e._zod.innerType;ji(a,t,r);t.seen.get(e).ref=a},Ps={string:Vi,number:qi, +boolean:Wi,bigint:Xi,symbol:Gi,null:Yi,undefined:Ki,void:Ji,never:es,any:ts, +unknown:ns,date:rs,enum:as,literal:os,nan:is,template_literal:ss,file:ls, +success:cs,custom:us,function:ds,transform:ps,map:hs,set:fs,array:ms,object:gs, +union:vs,intersection:bs,tuple:ys,record:Os,nullable:ws,nonoptional:xs, +default:ks,prefault:Ss,catch:_s,pipe:As,readonly:Ts,promise:Es,optional:Cs, +lazy:$s};function Is(e,t){if("_idmap"in e){const n=e,r=Bi({...t,processors:Ps +}),a={};for(const e of n._idmap.entries()){const[t,n]=e;ji(n,r)}const o={},i={ +registry:n,uri:t?.uri,defs:a};r.external=i;for(const e of n._idmap.entries()){ +const[t,n]=e;Ui(r,n),o[t]=zi(r,n)}if(Object.keys(a).length>0){ +const e="draft-2020-12"===r.target?"$defs":"definitions";o.__shared={[e]:a}} +return{schemas:o}}const n=Bi({...t,processors:Ps}) +;return ji(e,n),Ui(n,e),zi(n,e)}const Ds=Object.freeze(Object.defineProperty({ +__proto__:null},Symbol.toStringTag,{value:"Module" +})),Ms=Object.freeze(Object.defineProperty({__proto__:null,$ZodAny:Zn, +$ZodArray:Wn,$ZodAsyncError:r,$ZodBase64:Tn,$ZodBase64URL:Cn,$ZodBigInt:Ln, +$ZodBigIntFormat:Bn,$ZodBoolean:Rn,$ZodCIDRv4:Sn,$ZodCIDRv6:_n,$ZodCUID:pn, +$ZodCUID2:hn,$ZodCatch:Cr,$ZodCheck:$t,$ZodCheckBigIntFormat:Rt, +$ZodCheckEndsWith:Xt,$ZodCheckGreaterThan:Dt,$ZodCheckIncludes:qt, +$ZodCheckLengthEquals:Zt,$ZodCheckLessThan:It,$ZodCheckLowerCase:Qt, +$ZodCheckMaxLength:Ut,$ZodCheckMaxSize:Lt,$ZodCheckMimeType:Kt, +$ZodCheckMinLength:zt,$ZodCheckMinSize:Bt,$ZodCheckMultipleOf:Mt, +$ZodCheckNumberFormat:Nt,$ZodCheckOverwrite:Jt,$ZodCheckProperty:Yt, +$ZodCheckRegex:Ht,$ZodCheckSizeEquals:jt,$ZodCheckStartsWith:Wt, +$ZodCheckStringFormat:Ft,$ZodCheckUpperCase:Vt,$ZodCodec:Dr,$ZodCustom:Zr, +$ZodCustomStringFormat:Dn,$ZodDate:Vn,$ZodDefault:kr,$ZodDiscriminatedUnion:ar, +$ZodE164:$n,$ZodEmail:ln,$ZodEmoji:un,$ZodEncodeError:a,$ZodEnum:mr,$ZodError:J, +$ZodExactOptional:wr,$ZodFile:vr,$ZodFunction:jr,$ZodGUID:on,$ZodIPv4:wn, +$ZodIPv6:xn,$ZodISODate:bn,$ZodISODateTime:vn,$ZodISODuration:On,$ZodISOTime:yn, +$ZodIntersection:or,$ZodJWT:In,$ZodKSUID:gn,$ZodLazy:zr,$ZodLiteral:gr, +$ZodMAC:kn,$ZodMap:dr,$ZodNaN:$r,$ZodNanoID:dn,$ZodNever:Hn,$ZodNonOptional:Ar, +$ZodNull:zn,$ZodNullable:xr,$ZodNumber:Mn,$ZodNumberFormat:Nn,$ZodObject:Kn, +$ZodObjectJIT:Jn,$ZodOptional:Or,$ZodPipe:Pr,$ZodPrefault:_r,$ZodPromise:Ur, +$ZodReadonly:Rr,$ZodRealError:ee,$ZodRecord:ur,$ZodRegistry:Ga,$ZodSet:hr, +$ZodString:rn,$ZodStringFormat:an,$ZodSuccess:Er,$ZodSymbol:jn, +$ZodTemplateLiteral:Br,$ZodTransform:br,$ZodTuple:lr,$ZodType:nn,$ZodULID:fn, +$ZodURL:cn,$ZodUUID:sn,$ZodUndefined:Un,$ZodUnion:tr,$ZodUnknown:Fn,$ZodVoid:Qn, +$ZodXID:mn,$ZodXor:rr,$brand:n,$constructor:t,$input:Xa,$output:Wa,Doc:en, +JSONSchema:Ds,JSONSchemaGenerator:class{get metadataRegistry(){ +return this.ctx.metadataRegistry}get target(){return this.ctx.target} +get unrepresentable(){return this.ctx.unrepresentable}get override(){ +return this.ctx.override}get io(){return this.ctx.io}get counter(){ +return this.ctx.counter}set counter(e){this.ctx.counter=e}get seen(){ +return this.ctx.seen}constructor(e){let t=e?.target??"draft-2020-12" +;"draft-4"===t&&(t="draft-04"),"draft-7"===t&&(t="draft-07"),this.ctx=Bi({ +processors:Ps,target:t,...e?.metadata&&{metadata:e.metadata}, +...e?.unrepresentable&&{unrepresentable:e.unrepresentable},...e?.override&&{ +override:e.override},...e?.io&&{io:e.io}})}process(e,t={path:[],schemaPath:[]}){ +return ji(e,this.ctx,t)}emit(e,t){ +t&&(t.cycles&&(this.ctx.cycles=t.cycles),t.reused&&(this.ctx.reused=t.reused), +t.external&&(this.ctx.external=t.external)),Ui(this.ctx,e) +;const n=zi(this.ctx,e),{"~standard":r,...a}=n;return a}},NEVER:e, +TimePrecision:_o,_any:Vo,_array:Ei,_base64:wo,_base64url:xo,_bigint:jo, +_boolean:Lo,_catch:function(e,t,n){return new e({type:"catch",innerType:t, +catchValue:"function"==typeof n?n:()=>n})},_check:Di,_cidrv4:yo,_cidrv6:Oo, +_coercedBigint:Uo,_coercedBoolean:Bo,_coercedDate:Yo,_coercedNumber:Po, +_coercedString:eo,_cuid:uo,_cuid2:po,_custom:$i,_date:Go,_decode:ge, +_decodeAsync:Oe,_default:function(e,t,n){return new e({type:"default", +innerType:t,get defaultValue(){return"function"==typeof n?n():S(n)}})}, +_discriminatedUnion:function(e,t,n,r){return new e({type:"union",options:n, +discriminator:t,...C(r)})},_e164:ko,_email:to,_emoji:lo,_encode:fe, +_encodeAsync:be,_endsWith:yi,_enum:function(e,t,n){return new e({type:"enum", +entries:Array.isArray(t)?Object.fromEntries(t.map((e=>[e,e]))):t,...C(n)})}, +_file:Ci,_float32:Do,_float64:Mo,_gt:ti,_gte:ni,_guid:no,_includes:vi,_int:Io, +_int32:No,_int64:zo,_intersection:function(e,t,n){return new e({ +type:"intersection",left:t,right:n})},_ipv4:go,_ipv6:vo,_isoDate:To, +_isoDateTime:Ao,_isoDuration:Co,_isoTime:Eo,_jwt:So,_ksuid:mo, +_lazy:function(e,t){return new e({type:"lazy",getter:t})},_length:hi, +_literal:function(e,t,n){return new e({type:"literal", +values:Array.isArray(t)?t:[t],...C(n)})},_lowercase:mi,_lt:Jo,_lte:ei,_mac:bo, +_map:function(e,t,n,r){return new e({type:"map",keyType:t,valueType:n,...C(r)}) +},_max:ei,_maxLength:di,_maxSize:li,_mime:wi,_min:ni,_minLength:pi,_minSize:ci, +_multipleOf:si,_nan:Ko,_nanoid:co,_nativeEnum:function(e,t,n){return new e({ +type:"enum",entries:t,...C(n)})},_negative:ai,_never:Wo,_nonnegative:ii, +_nonoptional:function(e,t,n){return new e({type:"nonoptional",innerType:t, +...C(n)})},_nonpositive:oi,_normalize:ki,_null:Qo,_nullable:function(e,t){ +return new e({type:"nullable",innerType:t})},_number:$o,_optional:function(e,t){ +return new e({type:"optional",innerType:t})},_overwrite:xi,_parse:ie, +_parseAsync:le,_pipe:function(e,t,n){return new e({type:"pipe",in:t,out:n})}, +_positive:ri,_promise:function(e,t){return new e({type:"promise",innerType:t})}, +_property:Oi,_readonly:function(e,t){return new e({type:"readonly",innerType:t}) +},_record:function(e,t,n,r){return new e({type:"record",keyType:t,valueType:n, +...C(r)})},_refine:Pi,_regex:fi,_safeDecode:Se,_safeDecodeAsync:Ee, +_safeEncode:xe,_safeEncodeAsync:Ae,_safeParse:ue,_safeParseAsync:pe, +_set:function(e,t,n){return new e({type:"set",valueType:t,...C(n)})},_size:ui, +_slugify:Ti,_startsWith:bi,_string:Ja,_stringFormat:Li,_stringbool:Ri, +_success:function(e,t){return new e({type:"success",innerType:t})}, +_superRefine:Ii,_symbol:Fo,_templateLiteral:function(e,t,n){return new e({ +type:"template_literal",parts:t,...C(n)})},_toLowerCase:_i,_toUpperCase:Ai, +_transform:function(e,t){return new e({type:"transform",transform:t})},_trim:Si, +_tuple:function(e,t,n,r){const a=n instanceof nn;return new e({type:"tuple", +items:t,rest:a?n:null,...C(a?r:n)})},_uint32:Ro,_uint64:Zo,_ulid:ho, +_undefined:Ho,_union:function(e,t,n){return new e({type:"union",options:t, +...C(n)})},_unknown:qo,_uppercase:gi,_url:so,_uuid:ro,_uuidv4:ao,_uuidv6:oo, +_uuidv7:io,_void:Xo,_xid:fo,_xor:function(e,t,n){return new e({type:"union", +options:t,inclusive:!1,...C(n)})},clone:E,config:i, +createStandardJSONSchemaMethod:Hi,createToJSONSchemaMethod:Fi,decode:ve, +decodeAsync:we,describe:Mi,encode:me,encodeAsync:ye,extractDefs:Ui,finalize:zi, +flattenError:te,formatError:ne,globalConfig:o,globalRegistry:Ka, +initializeContext:Bi,isValidBase64:An,isValidBase64URL:En,isValidJWT:Pn, +locales:Va,meta:Ni,parse:se,parseAsync:ce,prettifyError:oe,process:ji, +regexes:Ct,registry:Ya,safeDecode:_e,safeDecodeAsync:Ce,safeEncode:ke, +safeEncodeAsync:Te,safeParse:de,safeParseAsync:he,toDotPath:ae,toJSONSchema:Is, +treeifyError:re,util:Y,version:tn},Symbol.toStringTag,{value:"Module" +})),Ns=Object.freeze(Object.defineProperty({__proto__:null,endsWith:yi,gt:ti, +gte:ni,includes:vi,length:hi,lowercase:mi,lt:Jo,lte:ei,maxLength:di,maxSize:li, +mime:wi,minLength:pi,minSize:ci,multipleOf:si,negative:ai,nonnegative:ii, +nonpositive:oi,normalize:ki,overwrite:xi,positive:ri,property:Oi,regex:fi, +size:ui,slugify:Ti,startsWith:bi,toLowerCase:_i,toUpperCase:Ai,trim:Si, +uppercase:gi},Symbol.toStringTag,{value:"Module" +})),Rs=t("ZodISODateTime",((e,t)=>{vn.init(e,t),ul.init(e,t)}));function Ls(e){ +return Ao(Rs,e)}const Bs=t("ZodISODate",((e,t)=>{bn.init(e,t),ul.init(e,t)})) +;function js(e){return To(Bs,e)}const Us=t("ZodISOTime",((e,t)=>{ +yn.init(e,t),ul.init(e,t)}));function zs(e){return Eo(Us,e)} +const Zs=t("ZodISODuration",((e,t)=>{On.init(e,t),ul.init(e,t)})) +;function Fs(e){return Co(Zs,e)}const Hs=Object.freeze(Object.defineProperty({ +__proto__:null,ZodISODate:Bs,ZodISODateTime:Rs,ZodISODuration:Zs,ZodISOTime:Us, +date:js,datetime:Ls,duration:Fs,time:zs},Symbol.toStringTag,{value:"Module" +})),Qs=(e,t)=>{J.init(e,t),e.name="ZodError",Object.defineProperties(e,{format:{ +value:t=>ne(e,t)},flatten:{value:t=>te(e,t)},addIssue:{value:t=>{ +e.issues.push(t),e.message=JSON.stringify(e.issues,c,2)}},addIssues:{value:t=>{ +e.issues.push(...t),e.message=JSON.stringify(e.issues,c,2)}},isEmpty:{ +get:()=>0===e.issues.length}})},Vs=t("ZodError",Qs),qs=t("ZodError",Qs,{ +Parent:Error +}),Ws=ie(qs),Xs=le(qs),Gs=ue(qs),Ys=pe(qs),Ks=fe(qs),Js=ge(qs),el=be(qs),tl=Oe(qs),nl=xe(qs),rl=Se(qs),al=Ae(qs),ol=Ee(qs),il=t("ZodType",((e,t)=>(nn.init(e,t), +Object.assign(e["~standard"],{jsonSchema:{input:Hi(e,"input"), +output:Hi(e,"output")} +}),e.toJSONSchema=Fi(e,{}),e.def=t,e.type=t.type,Object.defineProperty(e,"_def",{ +value:t}),e.check=(...n)=>e.clone(v(t,{ +checks:[...t.checks??[],...n.map((e=>"function"==typeof e?{_zod:{check:e,def:{ +check:"custom"},onattach:[]}}:e))]}),{parent:!0 +}),e.with=e.check,e.clone=(t,n)=>E(e,t,n), +e.brand=()=>e,e.register=(t,n)=>(t.add(e,n),e),e.parse=(t,n)=>Ws(e,t,n,{ +callee:e.parse +}),e.safeParse=(t,n)=>Gs(e,t,n),e.parseAsync=async(t,n)=>Xs(e,t,n,{ +callee:e.parseAsync +}),e.safeParseAsync=async(t,n)=>Ys(e,t,n),e.spa=e.safeParseAsync, +e.encode=(t,n)=>Ks(e,t,n), +e.decode=(t,n)=>Js(e,t,n),e.encodeAsync=async(t,n)=>el(e,t,n), +e.decodeAsync=async(t,n)=>tl(e,t,n), +e.safeEncode=(t,n)=>nl(e,t,n),e.safeDecode=(t,n)=>rl(e,t,n), +e.safeEncodeAsync=async(t,n)=>al(e,t,n),e.safeDecodeAsync=async(t,n)=>ol(e,t,n), +e.refine=(t,n)=>e.check(Ju(t,n)), +e.superRefine=t=>e.check(ed(t)),e.overwrite=t=>e.check(xi(t)), +e.optional=()=>bu(e), +e.exactOptional=()=>Ou(e),e.nullable=()=>xu(e),e.nullish=()=>bu(xu(e)), +e.nonoptional=t=>Cu(e,t), +e.array=()=>Bc(e),e.or=t=>Qc([e,t]),e.and=t=>Yc(e,t),e.transform=t=>Lu(e,gu(t)), +e.default=t=>_u(e,t),e.prefault=t=>Tu(e,t),e.catch=t=>Du(e,t),e.pipe=t=>Lu(e,t), +e.readonly=()=>zu(e),e.describe=t=>{const n=e.clone();return Ka.add(n,{ +description:t}),n},Object.defineProperty(e,"description",{ +get:()=>Ka.get(e)?.description,configurable:!0}),e.meta=(...t)=>{ +if(0===t.length)return Ka.get(e);const n=e.clone();return Ka.add(n,t[0]),n +},e.isOptional=()=>e.safeParse(void 0).success, +e.isNullable=()=>e.safeParse(null).success, +e.apply=t=>t(e),e))),sl=t("_ZodString",((e,t)=>{ +rn.init(e,t),il.init(e,t),e._zod.processJSONSchema=(t,n,r)=>Vi(e,t,n) +;const n=e._zod.bag +;e.format=n.format??null,e.minLength=n.minimum??null,e.maxLength=n.maximum??null, +e.regex=(...t)=>e.check(fi(...t)), +e.includes=(...t)=>e.check(vi(...t)),e.startsWith=(...t)=>e.check(bi(...t)), +e.endsWith=(...t)=>e.check(yi(...t)), +e.min=(...t)=>e.check(pi(...t)),e.max=(...t)=>e.check(di(...t)), +e.length=(...t)=>e.check(hi(...t)), +e.nonempty=(...t)=>e.check(pi(1,...t)),e.lowercase=t=>e.check(mi(t)), +e.uppercase=t=>e.check(gi(t)), +e.trim=()=>e.check(Si()),e.normalize=(...t)=>e.check(ki(...t)), +e.toLowerCase=()=>e.check(_i()), +e.toUpperCase=()=>e.check(Ai()),e.slugify=()=>e.check(Ti()) +})),ll=t("ZodString",((e,t)=>{ +rn.init(e,t),sl.init(e,t),e.email=t=>e.check(to(dl,t)), +e.url=t=>e.check(so(Ol,t)), +e.jwt=t=>e.check(So(Jl,t)),e.emoji=t=>e.check(lo(kl,t)), +e.guid=t=>e.check(no(hl,t)), +e.uuid=t=>e.check(ro(ml,t)),e.uuidv4=t=>e.check(ao(ml,t)), +e.uuidv6=t=>e.check(oo(ml,t)), +e.uuidv7=t=>e.check(io(ml,t)),e.nanoid=t=>e.check(co(_l,t)), +e.guid=t=>e.check(no(hl,t)), +e.cuid=t=>e.check(uo(Tl,t)),e.cuid2=t=>e.check(po(Cl,t)), +e.ulid=t=>e.check(ho(Pl,t)), +e.base64=t=>e.check(wo(ql,t)),e.base64url=t=>e.check(xo(Xl,t)), +e.xid=t=>e.check(fo(Dl,t)), +e.ksuid=t=>e.check(mo(Nl,t)),e.ipv4=t=>e.check(go(Ll,t)), +e.ipv6=t=>e.check(vo(zl,t)), +e.cidrv4=t=>e.check(yo(Fl,t)),e.cidrv6=t=>e.check(Oo(Ql,t)), +e.e164=t=>e.check(ko(Yl,t)), +e.datetime=t=>e.check(Ls(t)),e.date=t=>e.check(js(t)), +e.time=t=>e.check(zs(t)),e.duration=t=>e.check(Fs(t))}));function cl(e){ +return Ja(ll,e)}const ul=t("ZodStringFormat",((e,t)=>{an.init(e,t),sl.init(e,t) +})),dl=t("ZodEmail",((e,t)=>{ln.init(e,t),ul.init(e,t)}));function pl(e){ +return to(dl,e)}const hl=t("ZodGUID",((e,t)=>{on.init(e,t),ul.init(e,t)})) +;function fl(e){return no(hl,e)}const ml=t("ZodUUID",((e,t)=>{ +sn.init(e,t),ul.init(e,t)}));function gl(e){return ro(ml,e)}function vl(e){ +return ao(ml,e)}function bl(e){return oo(ml,e)}function yl(e){return io(ml,e)} +const Ol=t("ZodURL",((e,t)=>{cn.init(e,t),ul.init(e,t)}));function wl(e){ +return so(Ol,e)}function xl(e){return so(Ol,{protocol:/^https?$/,hostname:et, +...C(e)})}const kl=t("ZodEmoji",((e,t)=>{un.init(e,t),ul.init(e,t)})) +;function Sl(e){return lo(kl,e)}const _l=t("ZodNanoID",((e,t)=>{ +dn.init(e,t),ul.init(e,t)}));function Al(e){return co(_l,e)} +const Tl=t("ZodCUID",((e,t)=>{pn.init(e,t),ul.init(e,t)}));function El(e){ +return uo(Tl,e)}const Cl=t("ZodCUID2",((e,t)=>{hn.init(e,t),ul.init(e,t)})) +;function $l(e){return po(Cl,e)}const Pl=t("ZodULID",((e,t)=>{ +fn.init(e,t),ul.init(e,t)}));function Il(e){return ho(Pl,e)} +const Dl=t("ZodXID",((e,t)=>{mn.init(e,t),ul.init(e,t)}));function Ml(e){ +return fo(Dl,e)}const Nl=t("ZodKSUID",((e,t)=>{gn.init(e,t),ul.init(e,t)})) +;function Rl(e){return mo(Nl,e)}const Ll=t("ZodIPv4",((e,t)=>{ +wn.init(e,t),ul.init(e,t)}));function Bl(e){return go(Ll,e)} +const jl=t("ZodMAC",((e,t)=>{kn.init(e,t),ul.init(e,t)}));function Ul(e){ +return bo(jl,e)}const zl=t("ZodIPv6",((e,t)=>{xn.init(e,t),ul.init(e,t)})) +;function Zl(e){return vo(zl,e)}const Fl=t("ZodCIDRv4",((e,t)=>{ +Sn.init(e,t),ul.init(e,t)}));function Hl(e){return yo(Fl,e)} +const Ql=t("ZodCIDRv6",((e,t)=>{_n.init(e,t),ul.init(e,t)}));function Vl(e){ +return Oo(Ql,e)}const ql=t("ZodBase64",((e,t)=>{Tn.init(e,t),ul.init(e,t)})) +;function Wl(e){return wo(ql,e)}const Xl=t("ZodBase64URL",((e,t)=>{Cn.init(e,t), +ul.init(e,t)}));function Gl(e){return xo(Xl,e)}const Yl=t("ZodE164",((e,t)=>{ +$n.init(e,t),ul.init(e,t)}));function Kl(e){return ko(Yl,e)} +const Jl=t("ZodJWT",((e,t)=>{In.init(e,t),ul.init(e,t)}));function ec(e){ +return So(Jl,e)}const tc=t("ZodCustomStringFormat",((e,t)=>{ +Dn.init(e,t),ul.init(e,t)}));function nc(e,t,n={}){return Li(tc,e,t,n)} +function rc(e){return Li(tc,"hostname",Je,e)}function ac(e){ +return Li(tc,"hex",gt,e)}function oc(e,t){ +const n=`${e}_${t?.enc??"hex"}`,r=Ct[n] +;if(!r)throw new Error(`Unrecognized hash format: ${n}`);return Li(tc,n,r,t)} +const ic=t("ZodNumber",((e,t)=>{ +Mn.init(e,t),il.init(e,t),e._zod.processJSONSchema=(t,n,r)=>qi(e,t,n), +e.gt=(t,n)=>e.check(ti(t,n)), +e.gte=(t,n)=>e.check(ni(t,n)),e.min=(t,n)=>e.check(ni(t,n)), +e.lt=(t,n)=>e.check(Jo(t,n)), +e.lte=(t,n)=>e.check(ei(t,n)),e.max=(t,n)=>e.check(ei(t,n)), +e.int=t=>e.check(cc(t)),e.safe=t=>e.check(cc(t)),e.positive=t=>e.check(ti(0,t)), +e.nonnegative=t=>e.check(ni(0,t)), +e.negative=t=>e.check(Jo(0,t)),e.nonpositive=t=>e.check(ei(0,t)), +e.multipleOf=(t,n)=>e.check(si(t,n)), +e.step=(t,n)=>e.check(si(t,n)),e.finite=()=>e;const n=e._zod.bag +;e.minValue=Math.max(n.minimum??Number.NEGATIVE_INFINITY,n.exclusiveMinimum??Number.NEGATIVE_INFINITY)??null, +e.maxValue=Math.min(n.maximum??Number.POSITIVE_INFINITY,n.exclusiveMaximum??Number.POSITIVE_INFINITY)??null, +e.isInt=(n.format??"").includes("int")||Number.isSafeInteger(n.multipleOf??.5), +e.isFinite=!0,e.format=n.format??null}));function sc(e){return $o(ic,e)} +const lc=t("ZodNumberFormat",((e,t)=>{Nn.init(e,t),ic.init(e,t)})) +;function cc(e){return Io(lc,e)}function uc(e){return Do(lc,e)}function dc(e){ +return Mo(lc,e)}function pc(e){return No(lc,e)}function hc(e){return Ro(lc,e)} +const fc=t("ZodBoolean",((e,t)=>{ +Rn.init(e,t),il.init(e,t),e._zod.processJSONSchema=(e,t,n)=>Wi(0,0,t)})) +;function mc(e){return Lo(fc,e)}const gc=t("ZodBigInt",((e,t)=>{ +Ln.init(e,t),il.init(e,t), +e._zod.processJSONSchema=(e,t,n)=>Xi(0,e),e.gte=(t,n)=>e.check(ni(t,n)), +e.min=(t,n)=>e.check(ni(t,n)), +e.gt=(t,n)=>e.check(ti(t,n)),e.gte=(t,n)=>e.check(ni(t,n)), +e.min=(t,n)=>e.check(ni(t,n)), +e.lt=(t,n)=>e.check(Jo(t,n)),e.lte=(t,n)=>e.check(ei(t,n)), +e.max=(t,n)=>e.check(ei(t,n)), +e.positive=t=>e.check(ti(BigInt(0),t)),e.negative=t=>e.check(Jo(BigInt(0),t)), +e.nonpositive=t=>e.check(ei(BigInt(0),t)), +e.nonnegative=t=>e.check(ni(BigInt(0),t)),e.multipleOf=(t,n)=>e.check(si(t,n)) +;const n=e._zod.bag +;e.minValue=n.minimum??null,e.maxValue=n.maximum??null,e.format=n.format??null +}));function vc(e){return jo(gc,e)}const bc=t("ZodBigIntFormat",((e,t)=>{ +Bn.init(e,t),gc.init(e,t)}));function yc(e){return zo(bc,e)}function Oc(e){ +return Zo(bc,e)}const wc=t("ZodSymbol",((e,t)=>{ +jn.init(e,t),il.init(e,t),e._zod.processJSONSchema=(e,t,n)=>Gi(0,e)})) +;function xc(e){return Fo(wc,e)}const kc=t("ZodUndefined",((e,t)=>{Un.init(e,t), +il.init(e,t),e._zod.processJSONSchema=(e,t,n)=>Ki(0,e)}));function Sc(e){ +return Ho(kc,e)}const _c=t("ZodNull",((e,t)=>{ +zn.init(e,t),il.init(e,t),e._zod.processJSONSchema=(e,t,n)=>Yi(0,e,t)})) +;function Ac(e){return Qo(_c,e)}const Tc=t("ZodAny",((e,t)=>{ +Zn.init(e,t),il.init(e,t),e._zod.processJSONSchema=(e,t,n)=>{}}));function Ec(){ +return Vo(Tc)}const Cc=t("ZodUnknown",((e,t)=>{ +Fn.init(e,t),il.init(e,t),e._zod.processJSONSchema=(e,t,n)=>{}}));function $c(){ +return qo(Cc)}const Pc=t("ZodNever",((e,t)=>{ +Hn.init(e,t),il.init(e,t),e._zod.processJSONSchema=(e,t,n)=>es(0,0,t)})) +;function Ic(e){return Wo(Pc,e)}const Dc=t("ZodVoid",((e,t)=>{ +Qn.init(e,t),il.init(e,t),e._zod.processJSONSchema=(e,t,n)=>Ji(0,e)})) +;function Mc(e){return Xo(Dc,e)}const Nc=t("ZodDate",((e,t)=>{ +Vn.init(e,t),il.init(e,t), +e._zod.processJSONSchema=(e,t,n)=>rs(0,e),e.min=(t,n)=>e.check(ni(t,n)), +e.max=(t,n)=>e.check(ei(t,n));const n=e._zod.bag +;e.minDate=n.minimum?new Date(n.minimum):null, +e.maxDate=n.maximum?new Date(n.maximum):null}));function Rc(e){return Go(Nc,e)} +const Lc=t("ZodArray",((e,t)=>{ +Wn.init(e,t),il.init(e,t),e._zod.processJSONSchema=(t,n,r)=>ms(e,t,n,r), +e.element=t.element, +e.min=(t,n)=>e.check(pi(t,n)),e.nonempty=t=>e.check(pi(1,t)), +e.max=(t,n)=>e.check(di(t,n)), +e.length=(t,n)=>e.check(hi(t,n)),e.unwrap=()=>e.element}));function Bc(e,t){ +return Ei(Lc,e,t)}function jc(e){const t=e._zod.def.shape +;return cu(Object.keys(t))}const Uc=t("ZodObject",((e,t)=>{ +Jn.init(e,t),il.init(e,t), +e._zod.processJSONSchema=(t,n,r)=>gs(e,t,n,r),m(e,"shape",(()=>t.shape)), +e.keyof=()=>cu(Object.keys(e._zod.def.shape)),e.catchall=t=>e.clone({ +...e._zod.def,catchall:t}),e.passthrough=()=>e.clone({...e._zod.def, +catchall:$c()}),e.loose=()=>e.clone({...e._zod.def,catchall:$c() +}),e.strict=()=>e.clone({...e._zod.def,catchall:Ic()}),e.strip=()=>e.clone({ +...e._zod.def,catchall:void 0 +}),e.extend=t=>R(e,t),e.safeExtend=t=>L(e,t),e.merge=t=>B(e,t),e.pick=t=>M(e,t), +e.omit=t=>N(e,t),e.partial=(...t)=>j(vu,e,t[0]),e.required=(...t)=>U(Eu,e,t[0]) +}));function zc(e,t){const n={type:"object",shape:e??{},...C(t)} +;return new Uc(n)}function Zc(e,t){return new Uc({type:"object",shape:e, +catchall:Ic(),...C(t)})}function Fc(e,t){return new Uc({type:"object",shape:e, +catchall:$c(),...C(t)})}const Hc=t("ZodUnion",((e,t)=>{ +tr.init(e,t),il.init(e,t), +e._zod.processJSONSchema=(t,n,r)=>vs(e,t,n,r),e.options=t.options})) +;function Qc(e,t){return new Hc({type:"union",options:e,...C(t)})} +const Vc=t("ZodXor",((e,t)=>{ +Hc.init(e,t),rr.init(e,t),e._zod.processJSONSchema=(t,n,r)=>vs(e,t,n,r), +e.options=t.options}));function qc(e,t){return new Vc({type:"union",options:e, +inclusive:!1,...C(t)})}const Wc=t("ZodDiscriminatedUnion",((e,t)=>{Hc.init(e,t), +ar.init(e,t)}));function Xc(e,t,n){return new Wc({type:"union",options:t, +discriminator:e,...C(n)})}const Gc=t("ZodIntersection",((e,t)=>{ +or.init(e,t),il.init(e,t),e._zod.processJSONSchema=(t,n,r)=>bs(e,t,n,r)})) +;function Yc(e,t){return new Gc({type:"intersection",left:e,right:t})} +const Kc=t("ZodTuple",((e,t)=>{ +lr.init(e,t),il.init(e,t),e._zod.processJSONSchema=(t,n,r)=>ys(e,t,n,r), +e.rest=t=>e.clone({...e._zod.def,rest:t})}));function Jc(e,t,n){ +const r=t instanceof nn;return new Kc({type:"tuple",items:e,rest:r?t:null, +...C(r?n:t)})}const eu=t("ZodRecord",((e,t)=>{ +ur.init(e,t),il.init(e,t),e._zod.processJSONSchema=(t,n,r)=>Os(e,t,n,r), +e.keyType=t.keyType,e.valueType=t.valueType}));function tu(e,t,n){ +return new eu({type:"record",keyType:e,valueType:t,...C(n)})}function nu(e,t,n){ +const r=E(e);return r._zod.values=void 0,new eu({type:"record",keyType:r, +valueType:t,...C(n)})}function ru(e,t,n){return new eu({type:"record",keyType:e, +valueType:t,mode:"loose",...C(n)})}const au=t("ZodMap",((e,t)=>{ +dr.init(e,t),il.init(e,t), +e._zod.processJSONSchema=(e,t,n)=>hs(0,e),e.keyType=t.keyType, +e.valueType=t.valueType, +e.min=(...t)=>e.check(ci(...t)),e.nonempty=t=>e.check(ci(1,t)), +e.max=(...t)=>e.check(li(...t)),e.size=(...t)=>e.check(ui(...t))})) +;function ou(e,t,n){return new au({type:"map",keyType:e,valueType:t,...C(n)})} +const iu=t("ZodSet",((e,t)=>{ +hr.init(e,t),il.init(e,t),e._zod.processJSONSchema=(e,t,n)=>fs(0,e), +e.min=(...t)=>e.check(ci(...t)), +e.nonempty=t=>e.check(ci(1,t)),e.max=(...t)=>e.check(li(...t)), +e.size=(...t)=>e.check(ui(...t))}));function su(e,t){return new iu({type:"set", +valueType:e,...C(t)})}const lu=t("ZodEnum",((e,t)=>{ +mr.init(e,t),il.init(e,t),e._zod.processJSONSchema=(t,n,r)=>as(e,0,n), +e.enum=t.entries,e.options=Object.values(t.entries) +;const n=new Set(Object.keys(t.entries));e.extract=(e,r)=>{const a={} +;for(const o of e){if(!n.has(o))throw new Error(`Key ${o} not found in enum`) +;a[o]=t.entries[o]}return new lu({...t,checks:[],...C(r),entries:a}) +},e.exclude=(e,r)=>{const a={...t.entries};for(const t of e){ +if(!n.has(t))throw new Error(`Key ${t} not found in enum`);delete a[t]} +return new lu({...t,checks:[],...C(r),entries:a})}}));function cu(e,t){ +const n=Array.isArray(e)?Object.fromEntries(e.map((e=>[e,e]))):e;return new lu({ +type:"enum",entries:n,...C(t)})}function uu(e,t){return new lu({type:"enum", +entries:e,...C(t)})}const du=t("ZodLiteral",((e,t)=>{ +gr.init(e,t),il.init(e,t),e._zod.processJSONSchema=(t,n,r)=>os(e,t,n), +e.values=new Set(t.values),Object.defineProperty(e,"value",{get(){ +if(t.values.length>1)throw new Error("This schema contains multiple valid literal values. Use `.values` instead.") +;return t.values[0]}})}));function pu(e,t){return new du({type:"literal", +values:Array.isArray(e)?e:[e],...C(t)})}const hu=t("ZodFile",((e,t)=>{ +vr.init(e,t), +il.init(e,t),e._zod.processJSONSchema=(t,n,r)=>ls(e,0,n),e.min=(t,n)=>e.check(ci(t,n)), +e.max=(t,n)=>e.check(li(t,n)), +e.mime=(t,n)=>e.check(wi(Array.isArray(t)?t:[t],n))}));function fu(e){ +return Ci(hu,e)}const mu=t("ZodTransform",((e,t)=>{ +br.init(e,t),il.init(e,t),e._zod.processJSONSchema=(e,t,n)=>ps(0,e), +e._zod.parse=(n,r)=>{if("backward"===r.direction)throw new a(e.constructor.name) +;n.addIssue=r=>{if("string"==typeof r)n.issues.push(W(r,n.value,t));else{ +const t=r +;t.fatal&&(t.continue=!1),t.code??(t.code="custom"),t.input??(t.input=n.value), +t.inst??(t.inst=e),n.issues.push(W(t))}};const o=t.transform(n.value,n) +;return o instanceof Promise?o.then((e=>(n.value=e,n))):(n.value=o,n)}})) +;function gu(e){return new mu({type:"transform",transform:e})} +const vu=t("ZodOptional",((e,t)=>{ +Or.init(e,t),il.init(e,t),e._zod.processJSONSchema=(t,n,r)=>Cs(e,t,0,r), +e.unwrap=()=>e._zod.def.innerType}));function bu(e){return new vu({ +type:"optional",innerType:e})}const yu=t("ZodExactOptional",((e,t)=>{ +wr.init(e,t), +il.init(e,t),e._zod.processJSONSchema=(t,n,r)=>Cs(e,t,0,r),e.unwrap=()=>e._zod.def.innerType +}));function Ou(e){return new yu({type:"optional",innerType:e})} +const wu=t("ZodNullable",((e,t)=>{ +xr.init(e,t),il.init(e,t),e._zod.processJSONSchema=(t,n,r)=>ws(e,t,n,r), +e.unwrap=()=>e._zod.def.innerType}));function xu(e){return new wu({ +type:"nullable",innerType:e})}function ku(e){return bu(xu(e))} +const Su=t("ZodDefault",((e,t)=>{ +kr.init(e,t),il.init(e,t),e._zod.processJSONSchema=(t,n,r)=>ks(e,t,n,r), +e.unwrap=()=>e._zod.def.innerType,e.removeDefault=e.unwrap}));function _u(e,t){ +return new Su({type:"default",innerType:e,get defaultValue(){ +return"function"==typeof t?t():S(t)}})}const Au=t("ZodPrefault",((e,t)=>{ +_r.init(e,t), +il.init(e,t),e._zod.processJSONSchema=(t,n,r)=>Ss(e,t,n,r),e.unwrap=()=>e._zod.def.innerType +}));function Tu(e,t){return new Au({type:"prefault",innerType:e, +get defaultValue(){return"function"==typeof t?t():S(t)}})} +const Eu=t("ZodNonOptional",((e,t)=>{ +Ar.init(e,t),il.init(e,t),e._zod.processJSONSchema=(t,n,r)=>xs(e,t,0,r), +e.unwrap=()=>e._zod.def.innerType}));function Cu(e,t){return new Eu({ +type:"nonoptional",innerType:e,...C(t)})}const $u=t("ZodSuccess",((e,t)=>{ +Er.init(e,t), +il.init(e,t),e._zod.processJSONSchema=(e,t,n)=>cs(0,0,t),e.unwrap=()=>e._zod.def.innerType +}));function Pu(e){return new $u({type:"success",innerType:e})} +const Iu=t("ZodCatch",((e,t)=>{ +Cr.init(e,t),il.init(e,t),e._zod.processJSONSchema=(t,n,r)=>_s(e,t,n,r), +e.unwrap=()=>e._zod.def.innerType,e.removeCatch=e.unwrap}));function Du(e,t){ +return new Iu({type:"catch",innerType:e,catchValue:"function"==typeof t?t:()=>t +})}const Mu=t("ZodNaN",((e,t)=>{ +$r.init(e,t),il.init(e,t),e._zod.processJSONSchema=(e,t,n)=>is(0,e)})) +;function Nu(e){return Ko(Mu,e)}const Ru=t("ZodPipe",((e,t)=>{ +Pr.init(e,t),il.init(e,t), +e._zod.processJSONSchema=(t,n,r)=>As(e,t,0,r),e.in=t.in,e.out=t.out})) +;function Lu(e,t){return new Ru({type:"pipe",in:e,out:t})} +const Bu=t("ZodCodec",((e,t)=>{Ru.init(e,t),Dr.init(e,t)}));function ju(e,t,n){ +return new Bu({type:"pipe",in:e,out:t,transform:n.decode, +reverseTransform:n.encode})}const Uu=t("ZodReadonly",((e,t)=>{ +Rr.init(e,t),il.init(e,t), +e._zod.processJSONSchema=(t,n,r)=>Ts(e,t,n,r),e.unwrap=()=>e._zod.def.innerType +}));function zu(e){return new Uu({type:"readonly",innerType:e})} +const Zu=t("ZodTemplateLiteral",((e,t)=>{ +Br.init(e,t),il.init(e,t),e._zod.processJSONSchema=(t,n,r)=>ss(e,0,n)})) +;function Fu(e,t){return new Zu({type:"template_literal",parts:e,...C(t)})} +const Hu=t("ZodLazy",((e,t)=>{ +zr.init(e,t),il.init(e,t),e._zod.processJSONSchema=(t,n,r)=>$s(e,t,0,r), +e.unwrap=()=>e._zod.def.getter()}));function Qu(e){return new Hu({type:"lazy", +getter:e})}const Vu=t("ZodPromise",((e,t)=>{ +Ur.init(e,t),il.init(e,t),e._zod.processJSONSchema=(t,n,r)=>Es(e,t,0,r), +e.unwrap=()=>e._zod.def.innerType}));function qu(e){return new Vu({ +type:"promise",innerType:e})}const Wu=t("ZodFunction",((e,t)=>{ +jr.init(e,t),il.init(e,t),e._zod.processJSONSchema=(e,t,n)=>ds(0,e)})) +;function Xu(e){return new Wu({type:"function", +input:Array.isArray(e?.input)?Jc(e?.input):e?.input??Bc($c()), +output:e?.output??$c()})}const Gu=t("ZodCustom",((e,t)=>{ +Zr.init(e,t),il.init(e,t),e._zod.processJSONSchema=(e,t,n)=>us(0,e)})) +;function Yu(e){const t=new $t({check:"custom"});return t._zod.check=e,t} +function Ku(e,t){return $i(Gu,e??(()=>!0),t)}function Ju(e,t={}){ +return Pi(Gu,e,t)}function ed(e){return Ii(e)}const td=Mi,nd=Ni +;function rd(e,t={}){const n=new Gu({type:"custom",check:"custom", +fn:t=>t instanceof e,abort:!0,...C(t)}) +;return n._zod.bag.Class=e,n._zod.check=t=>{ +t.value instanceof e||t.issues.push({code:"invalid_type",expected:e.name, +input:t.value,inst:n,path:[...n._zod.def.path??[]]})},n}const ad=(...e)=>Ri({ +Codec:Bu,Boolean:fc,String:ll},...e);function od(e){ +const t=Qu((()=>Qc([cl(e),sc(),mc(),Ac(),Bc(t),tu(cl(),t)])));return t} +function id(e,t){return Lu(gu(e),t)} +const sd=Object.freeze(Object.defineProperty({__proto__:null,ZodAny:Tc, +ZodArray:Lc,ZodBase64:ql,ZodBase64URL:Xl,ZodBigInt:gc,ZodBigIntFormat:bc, +ZodBoolean:fc,ZodCIDRv4:Fl,ZodCIDRv6:Ql,ZodCUID:Tl,ZodCUID2:Cl,ZodCatch:Iu, +ZodCodec:Bu,ZodCustom:Gu,ZodCustomStringFormat:tc,ZodDate:Nc,ZodDefault:Su, +ZodDiscriminatedUnion:Wc,ZodE164:Yl,ZodEmail:dl,ZodEmoji:kl,ZodEnum:lu, +ZodExactOptional:yu,ZodFile:hu,ZodFunction:Wu,ZodGUID:hl,ZodIPv4:Ll,ZodIPv6:zl, +ZodIntersection:Gc,ZodJWT:Jl,ZodKSUID:Nl,ZodLazy:Hu,ZodLiteral:du,ZodMAC:jl, +ZodMap:au,ZodNaN:Mu,ZodNanoID:_l,ZodNever:Pc,ZodNonOptional:Eu,ZodNull:_c, +ZodNullable:wu,ZodNumber:ic,ZodNumberFormat:lc,ZodObject:Uc,ZodOptional:vu, +ZodPipe:Ru,ZodPrefault:Au,ZodPromise:Vu,ZodReadonly:Uu,ZodRecord:eu,ZodSet:iu, +ZodString:ll,ZodStringFormat:ul,ZodSuccess:$u,ZodSymbol:wc, +ZodTemplateLiteral:Zu,ZodTransform:mu,ZodTuple:Kc,ZodType:il,ZodULID:Pl, +ZodURL:Ol,ZodUUID:ml,ZodUndefined:kc,ZodUnion:Hc,ZodUnknown:Cc,ZodVoid:Dc, +ZodXID:Dl,ZodXor:Vc,_ZodString:sl,_default:_u,_function:Xu,any:Ec,array:Bc, +base64:Wl,base64url:Gl,bigint:vc,boolean:mc,catch:Du,check:Yu,cidrv4:Hl, +cidrv6:Vl,codec:ju,cuid:El,cuid2:$l,custom:Ku,date:Rc,describe:td, +discriminatedUnion:Xc,e164:Kl,email:pl,emoji:Sl,enum:cu,exactOptional:Ou, +file:fu,float32:uc,float64:dc,function:Xu,guid:fl,hash:oc,hex:ac,hostname:rc, +httpUrl:xl,instanceof:rd,int:cc,int32:pc,int64:yc,intersection:Yc,ipv4:Bl, +ipv6:Zl,json:od,jwt:ec,keyof:jc,ksuid:Rl,lazy:Qu,literal:pu,looseObject:Fc, +looseRecord:ru,mac:Ul,map:ou,meta:nd,nan:Nu,nanoid:Al,nativeEnum:uu,never:Ic, +nonoptional:Cu,null:Ac,nullable:xu,nullish:ku,number:sc,object:zc,optional:bu, +partialRecord:nu,pipe:Lu,prefault:Tu,preprocess:id,promise:qu,readonly:zu, +record:tu,refine:Ju,set:su,strictObject:Zc,string:cl,stringFormat:nc, +stringbool:ad,success:Pu,superRefine:ed,symbol:xc,templateLiteral:Fu, +transform:gu,tuple:Jc,uint32:hc,uint64:Oc,ulid:Il,undefined:Sc,union:Qc, +unknown:$c,url:wl,uuid:gl,uuidv4:vl,uuidv6:bl,uuidv7:yl,void:Mc,xid:Ml,xor:qc +},Symbol.toStringTag,{value:"Module"}));var ld;ld||(ld={});const cd={...sd, +...Ns,iso:Hs +},ud=new Set(["$schema","$ref","$defs","definitions","$id","id","$comment","$anchor","$vocabulary","$dynamicRef","$dynamicAnchor","type","enum","const","anyOf","oneOf","allOf","not","properties","required","additionalProperties","patternProperties","propertyNames","minProperties","maxProperties","items","prefixItems","additionalItems","minItems","maxItems","uniqueItems","contains","minContains","maxContains","minLength","maxLength","pattern","format","minimum","maximum","exclusiveMinimum","exclusiveMaximum","multipleOf","description","default","contentEncoding","contentMediaType","contentSchema","unevaluatedItems","unevaluatedProperties","if","then","else","dependentSchemas","dependentRequired","nullable","readOnly"]) +;function dd(e,t){if(void 0!==e.not){ +if("object"==typeof e.not&&0===Object.keys(e.not).length)return cd.never() +;throw new Error("not is not supported in Zod (except { not: {} } for never)")} +if(void 0!==e.unevaluatedItems)throw new Error("unevaluatedItems is not supported") +;if(void 0!==e.unevaluatedProperties)throw new Error("unevaluatedProperties is not supported") +;if(void 0!==e.if||void 0!==e.then||void 0!==e.else)throw new Error("Conditional schemas (if/then/else) are not supported") +;if(void 0!==e.dependentSchemas||void 0!==e.dependentRequired)throw new Error("dependentSchemas and dependentRequired are not supported") +;if(e.$ref){const n=e.$ref;if(t.refs.has(n))return t.refs.get(n) +;if(t.processing.has(n))return cd.lazy((()=>{ +if(!t.refs.has(n))throw new Error(`Circular reference not resolved: ${n}`) +;return t.refs.get(n)}));t.processing.add(n);const r=function(e,t){ +if(!e.startsWith("#"))throw new Error("External $ref is not supported, only local refs (#/...) are allowed") +;const n=e.slice(1).split("/").filter(Boolean) +;if(0===n.length)return t.rootSchema +;const r="draft-2020-12"===t.version?"$defs":"definitions";if(n[0]===r){ +const r=n[1];if(!r||!t.defs[r])throw new Error(`Reference not found: ${e}`) +;return t.defs[r]}throw new Error(`Reference not found: ${e}`)}(n,t),a=pd(r,t) +;return t.refs.set(n,a),t.processing.delete(n),a}if(void 0!==e.enum){ +const n=e.enum +;if("openapi-3.0"===t.version&&!0===e.nullable&&1===n.length&&null===n[0])return cd.null() +;if(0===n.length)return cd.never();if(1===n.length)return cd.literal(n[0]) +;if(n.every((e=>"string"==typeof e)))return cd.enum(n) +;const r=n.map((e=>cd.literal(e))) +;return r.length<2?r[0]:cd.union([r[0],r[1],...r.slice(2)])} +if(void 0!==e.const)return cd.literal(e.const);const n=e.type +;if(Array.isArray(n)){const r=n.map((n=>dd({...e,type:n},t))) +;return 0===r.length?cd.never():1===r.length?r[0]:cd.union(r)} +if(!n)return cd.any();let r;switch(n){case"string":{let t=cd.string() +;if(e.format){const n=e.format +;"email"===n?t=t.check(cd.email()):"uri"===n||"uri-reference"===n?t=t.check(cd.url()):"uuid"===n||"guid"===n?t=t.check(cd.uuid()):"date-time"===n?t=t.check(cd.iso.datetime()):"date"===n?t=t.check(cd.iso.date()):"time"===n?t=t.check(cd.iso.time()):"duration"===n?t=t.check(cd.iso.duration()):"ipv4"===n?t=t.check(cd.ipv4()):"ipv6"===n?t=t.check(cd.ipv6()):"mac"===n?t=t.check(cd.mac()):"cidr"===n?t=t.check(cd.cidrv4()):"cidr-v6"===n?t=t.check(cd.cidrv6()):"base64"===n?t=t.check(cd.base64()):"base64url"===n?t=t.check(cd.base64url()):"e164"===n?t=t.check(cd.e164()):"jwt"===n?t=t.check(cd.jwt()):"emoji"===n?t=t.check(cd.emoji()):"nanoid"===n?t=t.check(cd.nanoid()):"cuid"===n?t=t.check(cd.cuid()):"cuid2"===n?t=t.check(cd.cuid2()):"ulid"===n?t=t.check(cd.ulid()):"xid"===n?t=t.check(cd.xid()):"ksuid"===n&&(t=t.check(cd.ksuid())) +} +"number"==typeof e.minLength&&(t=t.min(e.minLength)),"number"==typeof e.maxLength&&(t=t.max(e.maxLength)), +e.pattern&&(t=t.regex(new RegExp(e.pattern))),r=t;break}case"number": +case"integer":{let t="integer"===n?cd.number().int():cd.number() +;"number"==typeof e.minimum&&(t=t.min(e.minimum)), +"number"==typeof e.maximum&&(t=t.max(e.maximum)), +"number"==typeof e.exclusiveMinimum?t=t.gt(e.exclusiveMinimum):!0===e.exclusiveMinimum&&"number"==typeof e.minimum&&(t=t.gt(e.minimum)), +"number"==typeof e.exclusiveMaximum?t=t.lt(e.exclusiveMaximum):!0===e.exclusiveMaximum&&"number"==typeof e.maximum&&(t=t.lt(e.maximum)), +"number"==typeof e.multipleOf&&(t=t.multipleOf(e.multipleOf)),r=t;break} +case"boolean":r=cd.boolean();break;case"null":r=cd.null();break;case"object":{ +const n={},a=e.properties||{},o=new Set(e.required||[]) +;for(const[e,r]of Object.entries(a)){const a=pd(r,t) +;n[e]=o.has(e)?a:a.optional()}if(e.propertyNames){ +const a=pd(e.propertyNames,t),o=e.additionalProperties&&"object"==typeof e.additionalProperties?pd(e.additionalProperties,t):cd.any() +;if(0===Object.keys(n).length){r=cd.record(a,o);break} +const i=cd.object(n).passthrough(),s=cd.looseRecord(a,o);r=cd.intersection(i,s) +;break}if(e.patternProperties){const a=e.patternProperties,o=Object.keys(a),i=[] +;for(const e of o){const n=pd(a[e],t),r=cd.string().regex(new RegExp(e)) +;i.push(cd.looseRecord(r,n))}const s=[] +;if(Object.keys(n).length>0&&s.push(cd.object(n).passthrough()), +s.push(...i),0===s.length)r=cd.object({}).passthrough();else if(1===s.length)r=s[0];else{ +let e=cd.intersection(s[0],s[1]) +;for(let t=2;tpd(e,t))),i=a&&"object"==typeof a&&!Array.isArray(a)?pd(a,t):void 0 +;r=i?cd.tuple(o).rest(i):cd.tuple(o), +"number"==typeof e.minItems&&(r=r.check(cd.minLength(e.minItems))), +"number"==typeof e.maxItems&&(r=r.check(cd.maxLength(e.maxItems))) +}else if(Array.isArray(a)){ +const n=a.map((e=>pd(e,t))),o=e.additionalItems&&"object"==typeof e.additionalItems?pd(e.additionalItems,t):void 0 +;r=o?cd.tuple(n).rest(o):cd.tuple(n), +"number"==typeof e.minItems&&(r=r.check(cd.minLength(e.minItems))), +"number"==typeof e.maxItems&&(r=r.check(cd.maxLength(e.maxItems))) +}else if(void 0!==a){const n=pd(a,t);let o=cd.array(n) +;"number"==typeof e.minItems&&(o=o.min(e.minItems)), +"number"==typeof e.maxItems&&(o=o.max(e.maxItems)),r=o}else r=cd.array(cd.any()) +;break}default:throw new Error(`Unsupported type: ${n}`)} +return e.description&&(r=r.describe(e.description)), +void 0!==e.default&&(r=r.default(e.default)),r}function pd(e,t){ +if("boolean"==typeof e)return e?cd.any():cd.never();let n=dd(e,t) +;const r=e.type||void 0!==e.enum||void 0!==e.const +;if(e.anyOf&&Array.isArray(e.anyOf)){ +const a=e.anyOf.map((e=>pd(e,t))),o=cd.union(a);n=r?cd.intersection(n,o):o} +if(e.oneOf&&Array.isArray(e.oneOf)){ +const a=e.oneOf.map((e=>pd(e,t))),o=cd.xor(a);n=r?cd.intersection(n,o):o} +if(e.allOf&&Array.isArray(e.allOf))if(0===e.allOf.length)n=r?n:cd.any();else{ +let a=r?n:pd(e.allOf[0],t) +;for(let n=r?0:1;n0&&t.registry.add(n,a),n}function hd(e){ +return eo(ll,e)}const fd=Object.freeze(Object.defineProperty({__proto__:null, +bigint:function(e){return Uo(gc,e)},boolean:function(e){return Bo(fc,e)}, +date:function(e){return Yo(Nc,e)},number:function(e){return Po(ic,e)},string:hd +},Symbol.toStringTag,{value:"Module"}));i(ea()) +;const md=Object.freeze(Object.defineProperty({__proto__:null,$brand:n, +$input:Xa,$output:Wa,NEVER:e,TimePrecision:_o,ZodAny:Tc,ZodArray:Lc, +ZodBase64:ql,ZodBase64URL:Xl,ZodBigInt:gc,ZodBigIntFormat:bc,ZodBoolean:fc, +ZodCIDRv4:Fl,ZodCIDRv6:Ql,ZodCUID:Tl,ZodCUID2:Cl,ZodCatch:Iu,ZodCodec:Bu, +ZodCustom:Gu,ZodCustomStringFormat:tc,ZodDate:Nc,ZodDefault:Su, +ZodDiscriminatedUnion:Wc,ZodE164:Yl,ZodEmail:dl,ZodEmoji:kl,ZodEnum:lu, +ZodError:Vs,ZodExactOptional:yu,ZodFile:hu,get ZodFirstPartyTypeKind(){return ld +},ZodFunction:Wu,ZodGUID:hl,ZodIPv4:Ll,ZodIPv6:zl,ZodISODate:Bs, +ZodISODateTime:Rs,ZodISODuration:Zs,ZodISOTime:Us,ZodIntersection:Gc, +ZodIssueCode:{invalid_type:"invalid_type",too_big:"too_big", +too_small:"too_small",invalid_format:"invalid_format", +not_multiple_of:"not_multiple_of",unrecognized_keys:"unrecognized_keys", +invalid_union:"invalid_union",invalid_key:"invalid_key", +invalid_element:"invalid_element",invalid_value:"invalid_value",custom:"custom" +},ZodJWT:Jl,ZodKSUID:Nl,ZodLazy:Hu,ZodLiteral:du,ZodMAC:jl,ZodMap:au,ZodNaN:Mu, +ZodNanoID:_l,ZodNever:Pc,ZodNonOptional:Eu,ZodNull:_c,ZodNullable:wu, +ZodNumber:ic,ZodNumberFormat:lc,ZodObject:Uc,ZodOptional:vu,ZodPipe:Ru, +ZodPrefault:Au,ZodPromise:Vu,ZodReadonly:Uu,ZodRealError:qs,ZodRecord:eu, +ZodSet:iu,ZodString:ll,ZodStringFormat:ul,ZodSuccess:$u,ZodSymbol:wc, +ZodTemplateLiteral:Zu,ZodTransform:mu,ZodTuple:Kc,ZodType:il,ZodULID:Pl, +ZodURL:Ol,ZodUUID:ml,ZodUndefined:kc,ZodUnion:Hc,ZodUnknown:Cc,ZodVoid:Dc, +ZodXID:Dl,ZodXor:Vc,_ZodString:sl,_default:_u,_function:Xu,any:Ec,array:Bc, +base64:Wl,base64url:Gl,bigint:vc,boolean:mc,catch:Du,check:Yu,cidrv4:Hl, +cidrv6:Vl,clone:E,codec:ju,coerce:fd,config:i,core:Ms,cuid:El,cuid2:$l, +custom:Ku,date:Rc,decode:Js,decodeAsync:tl,describe:td,discriminatedUnion:Xc, +e164:Kl,email:pl,emoji:Sl,encode:Ks,encodeAsync:el,endsWith:yi,enum:cu, +exactOptional:Ou,file:fu,flattenError:te,float32:uc,float64:dc,formatError:ne, +fromJSONSchema:function(e,t){if("boolean"==typeof e)return e?cd.any():cd.never() +;const n=function(e,t){const n=e.$schema +;return"https://json-schema.org/draft/2020-12/schema"===n?"draft-2020-12":"http://json-schema.org/draft-07/schema#"===n?"draft-7":"http://json-schema.org/draft-04/schema#"===n?"draft-4":t??"draft-2020-12" +}(e,t?.defaultTarget);return pd(e,{version:n,defs:e.$defs||e.definitions||{}, +refs:new Map,processing:new Set,rootSchema:e,registry:t?.registry??Ka})}, +function:Xu,getErrorMap:function(){return i().customError},globalRegistry:Ka, +gt:ti,gte:ni,guid:fl,hash:oc,hex:ac,hostname:rc,httpUrl:xl,includes:vi, +instanceof:rd,int:cc,int32:pc,int64:yc,intersection:Yc,ipv4:Bl,ipv6:Zl,iso:Hs, +json:od,jwt:ec,keyof:jc,ksuid:Rl,lazy:Qu,length:hi,literal:pu,locales:Va, +looseObject:Fc,looseRecord:ru,lowercase:mi,lt:Jo,lte:ei,mac:Ul,map:ou, +maxLength:di,maxSize:li,meta:nd,mime:wi,minLength:pi,minSize:ci,multipleOf:si, +nan:Nu,nanoid:Al,nativeEnum:uu,negative:ai,never:Ic,nonnegative:ii, +nonoptional:Cu,nonpositive:oi,normalize:ki,null:Ac,nullable:xu,nullish:ku, +number:sc,object:zc,optional:bu,overwrite:xi,parse:Ws,parseAsync:Xs, +partialRecord:nu,pipe:Lu,positive:ri,prefault:Tu,preprocess:id,prettifyError:oe, +promise:qu,property:Oi,readonly:zu,record:tu,refine:Ju,regex:fi,regexes:Ct, +registry:Ya,safeDecode:rl,safeDecodeAsync:ol,safeEncode:nl,safeEncodeAsync:al, +safeParse:Gs,safeParseAsync:Ys,set:su,setErrorMap:function(e){i({customError:e}) +},size:ui,slugify:Ti,startsWith:bi,strictObject:Zc,string:cl,stringFormat:nc, +stringbool:ad,success:Pu,superRefine:ed,symbol:xc,templateLiteral:Fu, +toJSONSchema:Is,toLowerCase:_i,toUpperCase:Ai,transform:gu,treeifyError:re, +trim:Si,tuple:Jc,uint32:hc,uint64:Oc,ulid:Il,undefined:Sc,union:Qc,unknown:$c, +uppercase:gi,url:wl,util:Y,uuid:gl,uuidv4:vl,uuidv6:bl,uuidv7:yl,void:Mc,xid:Ml, +xor:qc},Symbol.toStringTag,{value:"Module"})),gd=zc({title:cl().optional(), +component:$c(),props:tu(cl(),Ec()).optional()}),vd=zc({ +"request.section":Bc(gd).optional(),"response.section":Bc(gd).optional() +}),bd=zc({onBeforeRequest:Xu({input:[zc({request:rd(Request)})]}).optional(), +onResponseReceived:Xu({input:[zc({response:rd(Response),operation:tu(cl(),Ec()) +})]}).optional()}),yd=Xu({input:[],output:zc({name:cl(),views:vd.optional(), +hooks:bd.optional()}) +}),Od="https://api.scalar.com/request-proxy",wd="https://proxy.scalar.com",xd=md.object({ +title:md.string().optional(),slug:md.string().optional(), +authentication:md.any().optional(),baseServerURL:md.string().optional(), +hideClientButton:md.boolean().optional().default(!1).catch(!1), +proxyUrl:md.string().optional(), +searchHotKey:md.enum(["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z"]).optional(), +servers:md.array(md.any()).optional(), +showSidebar:md.boolean().optional().default(!0).catch(!0), +showDeveloperTools:md.enum(["always","localhost","never"]).optional().default("localhost").catch("localhost"), +showToolbar:md.enum(["always","localhost","never"]).optional().default("localhost").catch("localhost"), +operationTitleSource:md.enum(["summary","path"]).optional().default("summary").catch("summary"), +theme:md.enum(["alternate","default","moon","purple","solarized","bluePlanet","deepSpace","saturn","kepler","elysiajs","fastify","mars","laserwave","none"]).optional().default("default").catch("default"), +_integration:md.enum(["adonisjs","astro","docusaurus","dotnet","elysiajs","express","fastapi","fastify","go","hono","html","laravel","litestar","nestjs","nextjs","nitro","nuxt","platformatic","react","rust","svelte","vue"]).nullable().optional(), +onRequestSent:md.function({input:[md.string()],output:md.void()}).optional(), +persistAuth:md.boolean().optional().default(!1).catch(!1), +plugins:md.array(yd).optional(),telemetry:md.boolean().optional().default(!0) +}),kd=md.object({default:md.boolean().default(!1).optional().catch(!1), +url:md.string().optional(), +content:md.union([md.string(),md.null(),md.record(md.string(),md.any()),md.function({ +input:[],output:md.record(md.string(),md.any())})]).optional(), +title:md.string().optional(),slug:md.string().optional(),spec:md.object({ +url:md.string().optional(), +content:md.union([md.string(),md.null(),md.record(md.string(),md.any()),md.function({ +input:[],output:md.record(md.string(),md.any())})]).optional()}).optional(), +agent:md.object({key:md.string().optional(),disabled:md.boolean().optional() +}).optional()});xd.extend(kd.shape);const Sd=zc({name:cl().regex(/^x-/), +component:$c(),renderer:$c().optional()}),_d=zc({component:$c(), +renderer:$c().optional(),props:tu(cl(),Ec()).optional()}),Ad=zc({ +"content.end":Bc(_d).optional()}),Td=Xu({input:[],output:zc({name:cl(), +extensions:Bc(Sd),views:Ad.optional()})}),Ed=Ku(),Cd=xd.extend({ +layout:cu(["modern","classic"]).optional().default("modern").catch("modern"), +proxy:cl().optional(),fetch:Ed.optional(),plugins:Bc(Td).optional(), +isEditable:mc().optional().default(!1).catch(!1), +isLoading:mc().optional().default(!1).catch(!1), +hideModels:mc().optional().default(!1).catch(!1), +documentDownloadType:cu(["yaml","json","both","direct","none"]).optional().default("both").catch("both"), +hideDownloadButton:mc().optional(), +hideTestRequestButton:mc().optional().default(!1).catch(!1), +hideSearch:mc().optional().default(!1).catch(!1), +showOperationId:mc().optional().default(!1).catch(!1),darkMode:mc().optional(), +forceDarkModeState:cu(["dark","light"]).optional(), +hideDarkModeToggle:mc().optional().default(!1).catch(!1), +metaData:Ec().optional(),favicon:cl().optional(), +hiddenClients:Qc([tu(cl(),Qc([mc(),Bc(cl())])),Bc(cl()),pu(!0)]).optional(), +defaultHttpClient:zc({targetKey:Ku(),clientKey:cl()}).optional(), +customCss:cl().optional(),onSpecUpdate:Xu({input:[cl()],output:Mc() +}).optional(),onServerChange:Xu({input:[cl()],output:Mc()}).optional(), +onDocumentSelect:Xu({input:[]}).optional(),onLoaded:Xu().optional(), +onBeforeRequest:Xu({input:[zc({request:rd(Request)})]}).optional(), +onShowMore:Xu({input:[cl()]}).optional(),onSidebarClick:Xu({input:[cl()] +}).optional(),pathRouting:zc({basePath:cl()}).optional(), +generateHeadingSlug:Xu({input:[zc({slug:cl().default("headingSlug")})], +output:cl()}).optional(),generateModelSlug:Xu({input:[zc({ +name:cl().default("modelName")})],output:cl()}).optional(),generateTagSlug:Xu({ +input:[zc({name:cl().default("tagName")})],output:cl()}).optional(), +generateOperationSlug:Xu({input:[zc({path:cl(),operationId:cl().optional(), +method:cl(),summary:cl().optional()})],output:cl()}).optional(), +generateWebhookSlug:Xu({input:[zc({name:cl(),method:cl().optional()})], +output:cl()}).optional(),redirect:Xu({input:[cl()], +output:cl().nullable().optional()}).optional(), +withDefaultFonts:mc().optional().default(!0).catch(!0), +defaultOpenAllTags:mc().optional().default(!1).catch(!1), +expandAllModelSections:mc().optional().default(!1).catch(!1), +expandAllResponses:mc().optional().default(!1).catch(!1), +tagsSorter:Qc([pu("alpha"),Xu({input:[Ec(),Ec()],output:sc()})]).optional(), +operationsSorter:Qc([pu("alpha"),pu("method"),Xu({input:[Ec(),Ec()],output:sc() +})]).optional(), +orderSchemaPropertiesBy:Qc([pu("alpha"),pu("preserve")]).optional().default("alpha").catch("alpha"), +orderRequiredPropertiesFirst:mc().optional().default(!0).catch(!0) +}),$d=Cd.extend(kd.shape).transform((e=>(e.hideDownloadButton&&(console.warn("[DEPRECATED] You're using the deprecated 'hideDownloadButton' attribute. Use 'documentDownloadType: 'none'' instead."), +e.documentDownloadType="none"), +e.spec?.url&&(console.warn("[DEPRECATED] You're using the deprecated 'spec.url' attribute. Remove the spec prefix and move the 'url' attribute to the top level."), +e.url=e.spec.url, +delete e.spec),e.spec?.content&&(console.warn("[DEPRECATED] You're using the deprecated 'spec.content' attribute. Remove the spec prefix and move the 'content' attribute to the top level."), +e.content=e.spec.content, +delete e.spec),e.proxy&&(console.warn("[DEPRECATED] You're using the deprecated 'proxy' attribute, rename it to 'proxyUrl' or update the package."), +e.proxyUrl||(e.proxyUrl=e.proxy), +delete e.proxy),e.proxyUrl===Od&&(console.warn(`[DEPRECATED] Warning: configuration.proxyUrl points to our old proxy (${Od}).`), +console.warn(`[DEPRECATED] We are overwriting the value and use the new proxy URL (${wd}) instead.`), +console.warn(`[DEPRECATED] Action Required: You should manually update your configuration to use the new URL (${wd}). Read more: https://github.com/scalar/scalar`), +e.proxyUrl=wd), +e.showToolbar&&"localhost"!==e.showToolbar&&(console.warn("[DEPRECATED] You're using the deprecated 'showToolbar' attribute. Use 'showDeveloperTools' instead."), +e.showDeveloperTools=e.showToolbar,delete e.showToolbar),e)));function Pd(e,t){ +const n=[],r=t.resolveKeyData||(e=>e.key),a=t.resolveValueData||(e=>e.value) +;for(const[o,i]of Object.entries(e))n.push(...(Array.isArray(i)?i:[i]).map((e=>{ +const n={key:o,value:e},i=a(n) +;return"object"==typeof i?Pd(i,t):Array.isArray(i)?i:{ +["function"==typeof t.key?t.key(n):t.key]:r(n), +["function"==typeof t.value?t.value(n):t.value]:i}})).flat());return n} +function Id(e,t){return Object.entries(e).map((([e,n])=>{ +if("object"==typeof n&&(n=Id(n,t)),t.resolve){const r=t.resolve({key:e,value:n}) +;if(void 0!==r)return r} +return"number"==typeof n&&(n=n.toString()),"string"==typeof n&&t.wrapValue&&(n=n.replace(new RegExp(t.wrapValue,"g"),`\\${t.wrapValue}`), +n=`${t.wrapValue}${n}${t.wrapValue}`),`${e}${t.keyValueSeparator||""}${n}` +})).join(t.entrySeparator||"")}zc({ +cdn:cl().optional().default("https://cdn.jsdelivr.net/npm/@scalar/api-reference"), +pageTitle:cl().optional().default("Scalar API Reference")}) +;const Dd=new Set(["title","titleTemplate","script","style","noscript"]),Md=new Set(["base","meta","link","style","script","noscript"]),Nd=new Set(["title","titleTemplate","templateParams","base","htmlAttrs","bodyAttrs","meta","link","style","script","noscript"]),Rd=new Set(["base","title","titleTemplate","bodyAttrs","htmlAttrs","templateParams"]),Ld=new Set(["tagPosition","tagPriority","tagDuplicateStrategy","children","innerHTML","textContent","processTemplateParams"]),Bd="undefined"!=typeof window +;function jd(e){let t=9 +;for(let n=0;n>>9)).toString(16).substring(1,8).toLowerCase()} +function Ud(e){if(e._h)return e._h;if(e._d)return jd(e._d) +;let t=`${e.tag}:${e.textContent||e.innerHTML||""}:` +;for(const n in e.props)t+=`${n}:${String(e.props[n])},`;return jd(t)} +const zd=e=>({keyValue:e,metaKey:"property"}),Zd=e=>({keyValue:e}),Fd={ +appleItunesApp:{unpack:{entrySeparator:", ", +resolve:({key:e,value:t})=>`${Vd(e)}=${t}`}}, +articleExpirationTime:zd("article:expiration_time"), +articleModifiedTime:zd("article:modified_time"), +articlePublishedTime:zd("article:published_time"), +bookReleaseDate:zd("book:release_date"),charset:{metaKey:"charset"}, +contentSecurityPolicy:{unpack:{entrySeparator:"; ", +resolve:({key:e,value:t})=>`${Vd(e)} ${t}`},metaKey:"http-equiv"},contentType:{ +metaKey:"http-equiv"},defaultStyle:{metaKey:"http-equiv"}, +fbAppId:zd("fb:app_id"),msapplicationConfig:Zd("msapplication-Config"), +msapplicationTileColor:Zd("msapplication-TileColor"), +msapplicationTileImage:Zd("msapplication-TileImage"), +ogAudioSecureUrl:zd("og:audio:secure_url"),ogAudioUrl:zd("og:audio"), +ogImageSecureUrl:zd("og:image:secure_url"),ogImageUrl:zd("og:image"), +ogSiteName:zd("og:site_name"),ogVideoSecureUrl:zd("og:video:secure_url"), +ogVideoUrl:zd("og:video"),profileFirstName:zd("profile:first_name"), +profileLastName:zd("profile:last_name"),profileUsername:zd("profile:username"), +refresh:{metaKey:"http-equiv",unpack:{entrySeparator:";", +resolve({key:e,value:t}){if("seconds"===e)return`${t}`}}},robots:{unpack:{ +entrySeparator:", ", +resolve:({key:e,value:t})=>"boolean"==typeof t?`${Vd(e)}`:`${Vd(e)}:${t}`}}, +xUaCompatible:{metaKey:"http-equiv"} +},Hd=new Set(["og","book","article","profile"]);function Qd(e){ +const t=Vd(e),n=t.indexOf(":") +;return Hd.has(t.substring(0,n))?"property":Fd[e]?.metaKey||"name"} +function Vd(e){ +const t=e.replace(/([A-Z])/g,"-$1").toLowerCase(),n=t.indexOf("-"),r=t.substring(0,n) +;return"twitter"===r||Hd.has(r)?e.replace(/([A-Z])/g,":$1").toLowerCase():t} +function qd(e){if(Array.isArray(e))return e.map((e=>qd(e))) +;if("object"!=typeof e||Array.isArray(e))return e;const t={} +;for(const n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[Vd(n)]=qd(e[n])) +;return t}function Wd(e,t){const n=Fd[t] +;return"refresh"===t?`${e.seconds};url=${e.url}`:Id(qd(e),{ +keyValueSeparator:"=",entrySeparator:", ", +resolve:({value:e,key:t})=>null===e?"":"boolean"==typeof e?`${t}`:void 0, +...n?.unpack})} +const Xd=new Set(["og:image","og:video","og:audio","twitter:image"]) +;function Gd(e){const t={};for(const n in e){ +if(!Object.prototype.hasOwnProperty.call(e,n))continue;const r=e[n] +;"false"!==String(r)&&n&&(t[n]=r)}return t}function Yd(e,t){ +const n=Gd(t),r=Vd(e),a=Qd(r);if(Xd.has(r)){const t={} +;for(const r in n)Object.prototype.hasOwnProperty.call(n,r)&&(t[`${e}${"url"===r?"":`${r[0].toUpperCase()}${r.slice(1)}`}`]=n[r]) +;return Kd(t).sort(((e,t)=>(e[a]?.length||0)-(t[a]?.length||0)))}return[{[a]:r, +...n}]}function Kd(e){const t=[],n={};for(const a in e){ +if(!Object.prototype.hasOwnProperty.call(e,a))continue;const r=e[a] +;if(Array.isArray(r))for(const e of r)t.push(..."string"==typeof e?Kd({[a]:e +}):Yd(a,e));else if("object"==typeof r&&r){if(Xd.has(Vd(a))){t.push(...Yd(a,r)) +;continue}n[a]=Gd(r)}else n[a]=r}const r=Pd(n,{key:({key:e})=>Qd(e), +value:({key:e})=>"charset"===e?"charset":"content", +resolveKeyData:({key:e})=>function(e){return Fd[e]?.keyValue||Vd(e)}(e), +resolveValueData:({value:e,key:t})=>null===e?"_null":"object"==typeof e?Wd(e,t):"number"==typeof e?e.toString():e +});return[...t,...r].map((e=>("_null"===e.content&&(e.content=null),e)))} +function Jd(e,t,n,r){ +const a=r||np("object"!=typeof t||"function"==typeof t||t instanceof Promise?{ +["script"===e||"noscript"===e||"style"===e?"innerHTML":"textContent"]:t}:{...t +},"templateParams"===e||"titleTemplate"===e) +;if(a instanceof Promise)return a.then((r=>Jd(e,t,n,r)));const o={tag:e,props:a} +;for(const i of Ld){const e=void 0!==o.props[i]?o.props[i]:n[i] +;void 0!==e&&(("innerHTML"!==i&&"textContent"!==i&&"children"!==i||Dd.has(o.tag))&&(o["children"===i?"innerHTML":i]=e), +delete o.props[i])} +return o.props.body&&(o.tagPosition="bodyClose",delete o.props.body), +"script"===o.tag&&"object"==typeof o.innerHTML&&(o.innerHTML=JSON.stringify(o.innerHTML), +o.props.type=o.props.type||"application/json"), +Array.isArray(o.props.content)?o.props.content.map((e=>({...o,props:{...o.props, +content:e}}))):o}function ep(e,t){const n="class"===e?" ":";" +;return t&&"object"==typeof t&&!Array.isArray(t)&&(t=Object.entries(t).filter((([,e])=>e)).map((([t,n])=>"style"===e?`${t}:${n}`:t))), +String(Array.isArray(t)?t.join(n):t)?.split(n).filter((e=>Boolean(e.trim()))).join(n) +}function tp(e,t,n,r){for(let a=r;a(e[r]=o,tp(e,t,n,a)))) +;if(!t&&!Ld.has(r)){const t=String(e[r]),n=r.startsWith("data-") +;"true"===t||""===t?e[r]=!n||"true":e[r]||(n&&"false"===t?e[r]="false":delete e[r]) +}}else e[r]=ep(r,e[r])}}function np(e,t=!1){const n=tp(e,t,Object.keys(e),0) +;return n instanceof Promise?n.then((()=>e)):e}const rp=10;function ap(e,t,n){ +for(let r=n;r(t[r]=n,ap(e,t,r)))) +;Array.isArray(n)?e.push(...n):e.push(n)}}function op(e){ +const t=[],n=e.resolvedInput;for(const i in n){ +if(!Object.prototype.hasOwnProperty.call(n,i))continue;const r=n[i] +;if(void 0!==r&&Nd.has(i))if(Array.isArray(r))for(const n of r)t.push(Jd(i,n,e));else t.push(Jd(i,r,e)) +}if(0===t.length)return[];const r=[] +;return a=ap(r,t,0),o=()=>r.map(((t,n)=>(t._e=e._i, +e.mode&&(t._m=e.mode),t._p=(e._i<{if(e===hp||!o.includes(e))return e +;const n=function(e,t,n=!1){let r +;if("s"===t||"pageTitle"===t)r=e.pageTitle;else if(t.includes(".")){ +const n=t.indexOf(".");r=e[t.substring(0,n)]?.[t.substring(n+1)]}else r=e[t] +;if(void 0!==r)return n?(r||"").replace(/"/g,'\\"'):r||""}(t,e.slice(1),r) +;return void 0!==n?n:e +})).trim(),i&&(e.endsWith(hp)&&(e=e.slice(0,-10)),e.startsWith(hp)&&(e=e.slice(10)), +e=e.replace(fp,n).trim()),e}function gp(e,t){ +return null==e?t||null:"function"==typeof e?e(t):e}function vp(e,t={}){ +const n=t.delayFn||(e=>setTimeout(e,10)) +;return e._domDebouncedUpdatePromise=e._domDebouncedUpdatePromise||new Promise((r=>n((()=>async function(e,t={}){ +const n=t.document||e.resolvedOptions.document;if(!n||!e.dirty)return;const r={ +shouldRender:!0,tags:[]} +;return await e.hooks.callHook("dom:beforeRender",r),r.shouldRender?(e._domUpdatePromise||(e._domUpdatePromise=new Promise((async t=>{ +const r=(await e.resolveTags()).map((e=>({tag:e,id:Md.has(e.tag)?Ud(e):e.tag, +shouldRender:!0})));let a=e._dom;if(!a){a={elMap:{htmlAttrs:n.documentElement, +bodyAttrs:n.body}};const e=new Set;for(const t of["body","head"]){ +const r=n[t]?.children;for(const t of r){const n=t.tagName.toLowerCase() +;if(!Md.has(n))continue;const r={tag:n, +props:await np(t.getAttributeNames().reduce(((e,n)=>({...e,[n]:t.getAttribute(n) +})),{})),innerHTML:t.innerHTML},o=pp(r);let i=o,s=1 +;for(;i&&e.has(i);)i=`${o}:${s++}` +;i&&(r._d=i,e.add(i)),a.elMap[t.getAttribute("data-hid")||Ud(r)]=t}}} +function o(e,t,n){const r=`${e}:${t}` +;a.sideEffects[r]=n,delete a.pendingSideEffects[r]} +function i({id:e,$el:t,tag:r}){const i=r.tag.endsWith("Attrs") +;if(a.elMap[e]=t,i||(r.textContent&&r.textContent!==t.textContent&&(t.textContent=r.textContent), +r.innerHTML&&r.innerHTML!==t.innerHTML&&(t.innerHTML=r.innerHTML), +o(e,"el",(()=>{a.elMap[e]?.remove(),delete a.elMap[e] +}))),r._eventHandlers)for(const a in r._eventHandlers)Object.prototype.hasOwnProperty.call(r._eventHandlers,a)&&""!==t.getAttribute(`data-${a}`)&&(("bodyAttrs"===r.tag?n.defaultView:t).addEventListener(a.substring(2),r._eventHandlers[a].bind(t)), +t.setAttribute(`data-${a}`,""));for(const n in r.props){ +if(!Object.prototype.hasOwnProperty.call(r.props,n))continue +;const a=r.props[n],s=`attr:${n}`;if("class"===n){if(!a)continue +;for(const n of a.split(" "))i&&o(e,`${s}:${n}`,(()=>t.classList.remove(n))), +!t.classList.contains(n)&&t.classList.add(n)}else if("style"===n){if(!a)continue +;for(const n of a.split(";")){ +const r=n.indexOf(":"),a=n.substring(0,r).trim(),i=n.substring(r+1).trim() +;o(e,`${s}:${a}`,(()=>{t.style.removeProperty(a)})),t.style.setProperty(a,i)} +}else t.getAttribute(n)!==a&&t.setAttribute(n,!0===a?"":String(a)), +i&&o(e,s,(()=>t.removeAttribute(n)))}}a.pendingSideEffects={...a.sideEffects +},a.sideEffects={};const s=[],l={bodyClose:void 0,bodyOpen:void 0,head:void 0} +;for(const e of r){const{tag:t,shouldRender:r,id:o}=e +;r&&("title"!==t.tag?(e.$el=e.$el||a.elMap[o], +e.$el?i(e):Md.has(t.tag)&&s.push(e)):n.title=t.textContent)}for(const e of s){ +const t=e.tag.tagPosition||"head" +;e.$el=n.createElement(e.tag.tag),i(e),l[t]=l[t]||n.createDocumentFragment(), +l[t].appendChild(e.$el)} +for(const c of r)await e.hooks.callHook("dom:renderTag",c,n,o) +;l.head&&n.head.appendChild(l.head), +l.bodyOpen&&n.body.insertBefore(l.bodyOpen,n.body.firstChild), +l.bodyClose&&n.body.appendChild(l.bodyClose) +;for(const e in a.pendingSideEffects)a.pendingSideEffects[e]() +;e._dom=a,await e.hooks.callHook("dom:rendered",{renders:r}),t() +})).finally((()=>{e._domUpdatePromise=void 0,e.dirty=!1 +}))),e._domUpdatePromise):void 0}(e,t).then((()=>{ +delete e._domDebouncedUpdatePromise,r()}))))))}function bp(e){return t=>{ +const n=t.resolvedOptions.document?.head.querySelector('script[id="unhead:payload"]')?.innerHTML||!1 +;return n&&t.push(JSON.parse(n)),{mode:"client",hooks:{"entries:updated":t=>{ +vp(t,e)}}}}}function yp(e,t={},n){for(const r in e){ +const a=e[r],o=n?`${n}:${r}`:r +;"object"==typeof a&&null!==a?yp(a,t,o):"function"==typeof a&&(t[o]=a)}return t} +const Op={run:e=>e()},wp=void 0!==console.createTask?console.createTask:()=>Op +;function xp(e,t){const n=t.shift(),r=wp(n) +;return e.reduce(((e,n)=>e.then((()=>r.run((()=>n(...t)))))),Promise.resolve())} +function kp(e,t){const n=t.shift(),r=wp(n) +;return Promise.all(e.map((e=>r.run((()=>e(...t))))))}function Sp(e,t){ +for(const n of[...e])n(t)}class _p{constructor(){ +this._hooks={},this._before=void 0, +this._after=void 0,this._deprecatedMessages=void 0, +this._deprecatedHooks={},this.hook=this.hook.bind(this), +this.callHook=this.callHook.bind(this), +this.callHookWith=this.callHookWith.bind(this)}hook(e,t,n={}){ +if(!e||"function"!=typeof t)return()=>{};const r=e;let a +;for(;this._deprecatedHooks[e];)a=this._deprecatedHooks[e],e=a.to +;if(a&&!n.allowDeprecated){let e=a.message +;e||(e=`${r} hook has been deprecated`+(a.to?`, please use ${a.to}`:"")), +this._deprecatedMessages||(this._deprecatedMessages=new Set), +this._deprecatedMessages.has(e)||(console.warn(e), +this._deprecatedMessages.add(e))}if(!t.name)try{Object.defineProperty(t,"name",{ +get:()=>"_"+e.replace(/\W+/g,"_")+"_hook_cb",configurable:!0})}catch{} +return this._hooks[e]=this._hooks[e]||[],this._hooks[e].push(t),()=>{ +t&&(this.removeHook(e,t),t=void 0)}}hookOnce(e,t){ +let n,r=(...e)=>("function"==typeof n&&n(),n=void 0,r=void 0,t(...e)) +;return n=this.hook(e,r),n}removeHook(e,t){if(this._hooks[e]){ +const n=this._hooks[e].indexOf(t) +;-1!==n&&this._hooks[e].splice(n,1),0===this._hooks[e].length&&delete this._hooks[e] +}}deprecateHook(e,t){this._deprecatedHooks[e]="string"==typeof t?{to:t}:t +;const n=this._hooks[e]||[];delete this._hooks[e] +;for(const r of n)this.hook(e,r)}deprecateHooks(e){ +Object.assign(this._deprecatedHooks,e) +;for(const t in e)this.deprecateHook(t,e[t])}addHooks(e){ +const t=yp(e),n=Object.keys(t).map((e=>this.hook(e,t[e])));return()=>{ +for(const e of n.splice(0,n.length))e()}}removeHooks(e){const t=yp(e) +;for(const n in t)this.removeHook(n,t[n])}removeAllHooks(){ +for(const e in this._hooks)delete this._hooks[e]}callHook(e,...t){ +return t.unshift(e),this.callHookWith(xp,e,...t)}callHookParallel(e,...t){ +return t.unshift(e),this.callHookWith(kp,e,...t)}callHookWith(e,t,...n){ +const r=this._before||this._after?{name:t,args:n,context:{}}:void 0 +;this._before&&Sp(this._before,r) +;const a=e(t in this._hooks?[...this._hooks[t]]:[],n) +;return a instanceof Promise?a.finally((()=>{this._after&&r&&Sp(this._after,r) +})):(this._after&&r&&Sp(this._after,r),a)}beforeEach(e){ +return this._before=this._before||[],this._before.push(e),()=>{ +if(void 0!==this._before){const t=this._before.indexOf(e) +;-1!==t&&this._before.splice(t,1)}}}afterEach(e){ +return this._after=this._after||[],this._after.push(e),()=>{ +if(void 0!==this._after){const t=this._after.indexOf(e) +;-1!==t&&this._after.splice(t,1)}}}} +const Ap=new Set(["templateParams","htmlAttrs","bodyAttrs"]),Tp={hooks:{ +"tag:normalise":({tag:e})=>{e.props.hid&&(e.key=e.props.hid,delete e.props.hid), +e.props.vmid&&(e.key=e.props.vmid, +delete e.props.vmid),e.props.key&&(e.key=e.props.key,delete e.props.key) +;const t=pp(e) +;!t||t.startsWith("meta:og:")||t.startsWith("meta:twitter:")||delete e.key +;const n=t||!!e.key&&`${e.tag}:${e.key}`;n&&(e._d=n)},"tags:resolve":e=>{ +const t=Object.create(null);for(const r of e.tags){ +const e=(r.key?`${r.tag}:${r.key}`:r._d)||Ud(r),n=t[e];if(n){ +let a=r?.tagDuplicateStrategy;if(!a&&Ap.has(r.tag)&&(a="merge"),"merge"===a){ +const a=n.props +;a.style&&r.props.style&&(";"!==a.style[a.style.length-1]&&(a.style+=";"), +r.props.style=`${a.style} ${r.props.style}`), +a.class&&r.props.class?r.props.class=`${a.class} ${r.props.class}`:a.class&&(r.props.class=a.class), +t[e].props={...a,...r.props};continue}if(r._e===n._e){ +n._duped=n._duped||[],r._d=`${n._d}:${n._duped.length+1}`,n._duped.push(r) +;continue}if(cp(r)>cp(n))continue} +r.innerHTML||r.textContent||0!==Object.keys(r.props).length||!Md.has(r.tag)?t[e]=r:delete t[e] +}const n=[];for(const r in t){const e=t[r],a=e._duped +;n.push(e),a&&(delete e._duped,n.push(...a))} +e.tags=n,e.tags=e.tags.filter((e=>!("meta"===e.tag&&(e.props.name||e.props.property)&&!e.props.content))) +}}},Ep=new Set(["script","link","bodyAttrs"]),Cp=e=>({hooks:{"tags:resolve":t=>{ +for(const n of t.tags){if(!Ep.has(n.tag))continue;const t=n.props +;for(const r in t){if("o"!==r[0]||"n"!==r[1])continue +;if(!Object.prototype.hasOwnProperty.call(t,r))continue;const a=t[r] +;"function"==typeof a&&(e.ssr&&ip.has(r)?t[r]=`this.dataset.${r}fired = true`:delete t[r], +n._eventHandlers=n._eventHandlers||{},n._eventHandlers[r]=a)} +e.ssr&&n._eventHandlers&&(n.props.src||n.props.href)&&(n.key=n.key||jd(n.props.src||n.props.href)) +}},"dom:renderTag":({$el:e,tag:t})=>{const n=e?.dataset;if(n)for(const r in n){ +if(!r.endsWith("fired"))continue;const n=r.slice(0,-5) +;ip.has(n)&&t._eventHandlers?.[n]?.call(e,new Event(n.substring(2)))}}} +}),$p=new Set(["link","style","script","noscript"]),Pp={hooks:{ +"tag:normalise":({tag:e})=>{ +e.key&&$p.has(e.tag)&&(e.props["data-hid"]=e._h=jd(e.key))}}},Ip={mode:"server", +hooks:{"tags:beforeResolve":e=>{const t={};let n=!1 +;for(const r of e.tags)"server"!==r._m||"titleTemplate"!==r.tag&&"templateParams"!==r.tag&&"title"!==r.tag||(t[r.tag]="title"===r.tag||"titleTemplate"===r.tag?r.textContent:r.props, +n=!0);n&&e.tags.push({tag:"script",innerHTML:JSON.stringify(t),props:{ +id:"unhead:payload",type:"application/json"}})}}},Dp={hooks:{"tags:resolve":e=>{ +for(const t of e.tags)if("string"==typeof t.tagPriority)for(const{prefix:n,offset:r}of up){ +if(!t.tagPriority.startsWith(n))continue +;const a=t.tagPriority.substring(n.length),o=e.tags.find((e=>e._d===a))?._p +;if(void 0!==o){t._p=o+r;break}}e.tags.sort(((e,t)=>{const n=cp(e),r=cp(t) +;return nr?1:e._p-t._p}))}}},Mp={meta:"content",link:"href", +htmlAttrs:"lang"},Np=["innerHTML","textContent"],Rp=e=>({hooks:{ +"tags:resolve":t=>{const{tags:n}=t;let r;for(let e=0;e"title"===e.tag))?.textContent||"",a,o) +;for(const e of n){if(!1===e.processTemplateParams)continue;const t=Mp[e.tag] +;if(t&&"string"==typeof e.props[t])e.props[t]=mp(e.props[t],a,o);else if(e.processTemplateParams||"titleTemplate"===e.tag||"title"===e.tag)for(const n of Np)"string"==typeof e[n]&&(e[n]=mp(e[n],a,o,"script"===e.tag&&e.props.type.endsWith("json"))) +}e._templateParams=a,e._separator=o},"tags:afterResolve":({tags:t})=>{let n +;for(let e=0;e{const{tags:t}=e;let n,r +;for(let a=0;a{ +for(const t of e.tags)"string"==typeof t.innerHTML&&(!t.innerHTML||"application/ld+json"!==t.props.type&&"application/json"!==t.props.type?t.innerHTML=t.innerHTML.replace(new RegExp(`{s.dirty=!0,t.callHook("entries:updated",s)} +;let a=0,o=[];const i=[],s={plugins:i,dirty:!1,resolvedOptions:e,hooks:t, +headEntries:()=>o,use(e){const r="function"==typeof e?e(s):e +;r.key&&i.some((e=>e.key===r.key))||(i.push(r), +zp(r.mode,n)&&t.addHooks(r.hooks||{}))},push(e,t){delete t?.head;const i={ +_i:a++,input:e,...t};return zp(i.mode,n)&&(o.push(i),r()),{dispose(){ +o=o.filter((e=>e._i!==i._i)),r()},patch(e){ +for(const t of o)t._i===i._i&&(t.input=i.input=e);r()}}},async resolveTags(){ +const e={tags:[],entries:[...o]};await t.callHook("entries:resolve",e) +;for(const n of e.entries){const r=n.resolvedInput||n.input +;if(n.resolvedInput=await(n.transform?n.transform(r):r), +n.resolvedInput)for(const a of await op(n)){const r={tag:a,entry:n, +resolvedOptions:s.resolvedOptions} +;await t.callHook("tag:normalise",r),e.tags.push(r.tag)}} +return await t.callHook("tags:beforeResolve",e), +await t.callHook("tags:resolve",e), +await t.callHook("tags:afterResolve",e),e.tags},ssr:n} +;return[Tp,Ip,Cp,Pp,Dp,Rp,Lp,Bp,...e?.plugins||[]].forEach((e=>s.use(e))), +s.hooks.callHook("init",s),s}(e);return t.use(bp()),jp=t}function zp(e,t){ +return!e||"server"===e&&t||"client"===e&&!t}function Zp(e){ +const t=Object.create(null);for(const n of e.split(","))t[n]=1;return e=>e in t} +const Fp={},Hp=[],Qp=()=>{},Vp=()=>!1,qp=e=>111===e.charCodeAt(0)&&110===e.charCodeAt(1)&&(e.charCodeAt(2)>122||e.charCodeAt(2)<97),Wp=e=>e.startsWith("onUpdate:"),Xp=Object.assign,Gp=(e,t)=>{ +const n=e.indexOf(t);n>-1&&e.splice(n,1) +},Yp=Object.prototype.hasOwnProperty,Kp=(e,t)=>Yp.call(e,t),Jp=Array.isArray,eh=e=>"[object Map]"===ch(e),th=e=>"[object Set]"===ch(e),nh=e=>"[object Date]"===ch(e),rh=e=>"function"==typeof e,ah=e=>"string"==typeof e,oh=e=>"symbol"==typeof e,ih=e=>null!==e&&"object"==typeof e,sh=e=>(ih(e)||rh(e))&&rh(e.then)&&rh(e.catch),lh=Object.prototype.toString,ch=e=>lh.call(e),uh=e=>ch(e).slice(8,-1),dh=e=>"[object Object]"===ch(e),ph=e=>ah(e)&&"NaN"!==e&&"-"!==e[0]&&""+parseInt(e,10)===e,hh=Zp(",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"),fh=e=>{ +const t=Object.create(null);return n=>t[n]||(t[n]=e(n)) +},mh=/-\w/g,gh=fh((e=>e.replace(mh,(e=>e.slice(1).toUpperCase())))),vh=/\B([A-Z])/g,bh=fh((e=>e.replace(vh,"-$1").toLowerCase())),yh=fh((e=>e.charAt(0).toUpperCase()+e.slice(1))),Oh=fh((e=>e?`on${yh(e)}`:"")),wh=(e,t)=>!Object.is(e,t),xh=(e,...t)=>{ +for(let n=0;n{ +Object.defineProperty(e,t,{configurable:!0,enumerable:!1,writable:r,value:n}) +},Sh=e=>{const t=parseFloat(e);return isNaN(t)?e:t},_h=e=>{ +const t=ah(e)?Number(e):NaN;return isNaN(t)?e:t};let Ah +;const Th=()=>Ah||(Ah="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:"undefined"!=typeof window?window:"undefined"!=typeof global?global:{}) +;function Eh(e){if(Jp(e)){const t={};for(let n=0;n{if(e){ +const n=e.split($h);n.length>1&&(t[n[0].trim()]=n[1].trim())}})),t} +function Dh(e){let t="";if(ah(e))t=e;else if(Jp(e))for(let n=0;nLh(e,t)))} +const jh=e=>!(!e||!0!==e.__v_isRef),Uh=e=>ah(e)?e:null==e?"":Jp(e)||ih(e)&&(e.toString===lh||!rh(e.toString))?jh(e)?Uh(e.value):JSON.stringify(e,zh,2):String(e),zh=(e,t)=>jh(t)?zh(e,t.value):eh(t)?{ +[`Map(${t.size})`]:[...t.entries()].reduce(((e,[t,n],r)=>(e[Zh(t,r)+" =>"]=n, +e)),{})}:th(t)?{[`Set(${t.size})`]:[...t.values()].map((e=>Zh(e))) +}:oh(t)?Zh(t):!ih(t)||Jp(t)||dh(t)?t:String(t),Zh=(e,t="")=>{var n +;return oh(e)?`Symbol(${null!=(n=e.description)?n:t})`:e};function Fh(e){ +return null==e?"initial":"string"==typeof e?""===e?" ":e:String(e)}let Hh,Qh +;class Vh{constructor(e=!1){ +this.detached=e,this._active=!0,this._on=0,this.effects=[], +this.cleanups=[],this._isPaused=!1, +this.parent=Hh,!e&&Hh&&(this.index=(Hh.scopes||(Hh.scopes=[])).push(this)-1)} +get active(){return this._active}pause(){if(this._active){let e,t +;if(this._isPaused=!0, +this.scopes)for(e=0,t=this.scopes.length;e0&&0==--this._on&&(Hh=this.prevScope,this.prevScope=void 0)}stop(e){ +if(this._active){let t,n +;for(this._active=!1,t=0,n=this.effects.length;t0)return;if(Jh){let e=Jh +;for(Jh=void 0;e;){const t=e.next;e.next=void 0,e.flags&=-9,e=t}}let e +;for(;Kh;){let n=Kh;for(Kh=void 0;n;){const r=n.next +;if(n.next=void 0,n.flags&=-9,1&n.flags)try{n.trigger()}catch(t){e||(e=t)}n=r}} +if(e)throw e}function af(e){ +for(let t=e.deps;t;t=t.nextDep)t.version=-1,t.prevActiveLink=t.dep.activeLink, +t.dep.activeLink=t}function of(e){let t,n=e.depsTail,r=n;for(;r;){ +const e=r.prevDep +;-1===r.version?(r===n&&(n=e),cf(r),uf(r)):t=r,r.dep.activeLink=r.prevActiveLink, +r.prevActiveLink=void 0,r=e}e.deps=t,e.depsTail=n}function sf(e){ +for(let t=e.deps;t;t=t.nextDep)if(t.dep.version!==t.version||t.dep.computed&&(lf(t.dep.computed)||t.dep.version!==t.version))return!0 +;return!!e._dirty}function lf(e){if(4&e.flags&&!(16&e.flags))return +;if(e.flags&=-17,e.globalVersion===gf)return +;if(e.globalVersion=gf,!e.isSSR&&128&e.flags&&(!e.deps&&!e._dirty||!sf(e)))return +;e.flags|=2;const t=e.dep,n=Qh,r=df;Qh=e,df=!0;try{af(e);const n=e.fn(e._value) +;(0===t.version||wh(n,e._value))&&(e.flags|=128,e._value=n,t.version++) +}catch(a){throw t.version++,a}finally{Qh=n,df=r,of(e),e.flags&=-3}} +function cf(e,t=!1){const{dep:n,prevSub:r,nextSub:a}=e +;if(r&&(r.nextSub=a,e.prevSub=void 0), +a&&(a.prevSub=r,e.nextSub=void 0),n.subs===e&&(n.subs=r,!r&&n.computed)){ +n.computed.flags&=-5;for(let e=n.computed.deps;e;e=e.nextDep)cf(e,!0)} +t||--n.sc||!n.map||n.map.delete(n.key)}function uf(e){ +const{prevDep:t,nextDep:n}=e +;t&&(t.nextDep=n,e.prevDep=void 0),n&&(n.prevDep=t,e.nextDep=void 0)}let df=!0 +;const pf=[];function hf(){pf.push(df),df=!1}function ff(){const e=pf.pop() +;df=void 0===e||e}function mf(e){const{cleanup:t}=e;if(e.cleanup=void 0,t){ +const e=Qh;Qh=void 0;try{t()}finally{Qh=e}}}let gf=0;class vf{constructor(e,t){ +this.sub=e, +this.dep=t,this.version=t.version,this.nextDep=this.prevDep=this.nextSub=this.prevSub=this.prevActiveLink=void 0 +}}class bf{constructor(e){this.computed=e,this.version=0,this.activeLink=void 0, +this.subs=void 0,this.map=void 0,this.key=void 0,this.sc=0,this.__v_skip=!0} +track(e){if(!Qh||!df||Qh===this.computed)return;let t=this.activeLink +;if(void 0===t||t.sub!==Qh)t=this.activeLink=new vf(Qh,this), +Qh.deps?(t.prevDep=Qh.depsTail, +Qh.depsTail.nextDep=t,Qh.depsTail=t):Qh.deps=Qh.depsTail=t, +yf(t);else if(-1===t.version&&(t.version=this.version,t.nextDep)){ +const e=t.nextDep +;e.prevDep=t.prevDep,t.prevDep&&(t.prevDep.nextDep=e),t.prevDep=Qh.depsTail, +t.nextDep=void 0,Qh.depsTail.nextDep=t,Qh.depsTail=t,Qh.deps===t&&(Qh.deps=e)} +return t}trigger(e){this.version++,gf++,this.notify(e)}notify(e){nf();try{0 +;for(let e=this.subs;e;e=e.prevSub)e.sub.notify()&&e.sub.dep.notify()}finally{ +rf()}}}function yf(e){if(e.dep.sc++,4&e.sub.flags){const t=e.dep.computed +;if(t&&!e.dep.subs){t.flags|=20;for(let e=t.deps;e;e=e.nextDep)yf(e)} +const n=e.dep.subs;n!==e&&(e.prevSub=n,n&&(n.nextSub=e)),e.dep.subs=e}} +const Of=new WeakMap,wf=Symbol(""),xf=Symbol(""),kf=Symbol("") +;function Sf(e,t,n){if(df&&Qh){let t=Of.get(e);t||Of.set(e,t=new Map) +;let r=t.get(n);r||(t.set(n,r=new bf),r.map=t,r.key=n),r.track()}} +function _f(e,t,n,r,a,o){const i=Of.get(e);if(!i)return void gf++;const s=e=>{ +e&&e.trigger()};if(nf(),"clear"===t)i.forEach(s);else{const a=Jp(e),o=a&&ph(n) +;if(a&&"length"===n){const e=Number(r);i.forEach(((t,n)=>{ +("length"===n||n===kf||!oh(n)&&n>=e)&&s(t)})) +}else switch((void 0!==n||i.has(void 0))&&s(i.get(n)),o&&s(i.get(kf)),t){ +case"add":a?o&&s(i.get("length")):(s(i.get(wf)),eh(e)&&s(i.get(xf)));break +;case"delete":a||(s(i.get(wf)),eh(e)&&s(i.get(xf)));break;case"set": +eh(e)&&s(i.get(wf))}}rf()}function Af(e){const t=hm(e) +;return t===e?t:(Sf(t,0,kf),dm(e)?t:t.map(mm))}function Tf(e){ +return Sf(e=hm(e),0,kf),e}function Ef(e,t){ +return um(e)?cm(e)?gm(mm(t)):gm(t):mm(t)}const Cf={__proto__:null, +[Symbol.iterator](){return $f(this,Symbol.iterator,(e=>Ef(this,e)))}, +concat(...e){return Af(this).concat(...e.map((e=>Jp(e)?Af(e):e)))},entries(){ +return $f(this,"entries",(e=>(e[1]=Ef(this,e[1]),e)))},every(e,t){ +return If(this,"every",e,t,void 0,arguments)},filter(e,t){ +return If(this,"filter",e,t,(e=>e.map((e=>Ef(this,e)))),arguments)},find(e,t){ +return If(this,"find",e,t,(e=>Ef(this,e)),arguments)},findIndex(e,t){ +return If(this,"findIndex",e,t,void 0,arguments)},findLast(e,t){ +return If(this,"findLast",e,t,(e=>Ef(this,e)),arguments)},findLastIndex(e,t){ +return If(this,"findLastIndex",e,t,void 0,arguments)},forEach(e,t){ +return If(this,"forEach",e,t,void 0,arguments)},includes(...e){ +return Mf(this,"includes",e)},indexOf(...e){return Mf(this,"indexOf",e)}, +join(e){return Af(this).join(e)},lastIndexOf(...e){ +return Mf(this,"lastIndexOf",e)},map(e,t){ +return If(this,"map",e,t,void 0,arguments)},pop(){return Nf(this,"pop")}, +push(...e){return Nf(this,"push",e)},reduce(e,...t){return Df(this,"reduce",e,t) +},reduceRight(e,...t){return Df(this,"reduceRight",e,t)},shift(){ +return Nf(this,"shift")},some(e,t){return If(this,"some",e,t,void 0,arguments)}, +splice(...e){return Nf(this,"splice",e)},toReversed(){ +return Af(this).toReversed()},toSorted(e){return Af(this).toSorted(e)}, +toSpliced(...e){return Af(this).toSpliced(...e)},unshift(...e){ +return Nf(this,"unshift",e)},values(){return $f(this,"values",(e=>Ef(this,e)))}} +;function $f(e,t,n){const r=Tf(e),a=r[t]();return r===e||dm(e)||(a._next=a.next, +a.next=()=>{const e=a._next();return e.done||(e.value=n(e.value)),e}),a} +const Pf=Array.prototype;function If(e,t,n,r,a,o){ +const i=Tf(e),s=i!==e&&!dm(e),l=i[t];if(l!==Pf[t]){const t=l.apply(e,o) +;return s?mm(t):t}let c=n;i!==e&&(s?c=function(t,r){ +return n.call(this,Ef(e,t),r,e)}:n.length>2&&(c=function(t,r){ +return n.call(this,t,r,e)}));const u=l.call(i,c,r);return s&&a?a(u):u} +function Df(e,t,n,r){const a=Tf(e);let o=n +;return a!==e&&(dm(e)?n.length>3&&(o=function(t,r,a){return n.call(this,t,r,a,e) +}):o=function(t,r,a){return n.call(this,t,Ef(e,r),a,e)}),a[t](o,...r)} +function Mf(e,t,n){const r=hm(e);Sf(r,0,kf);const a=r[t](...n) +;return-1!==a&&!1!==a||!pm(n[0])?a:(n[0]=hm(n[0]),r[t](...n))} +function Nf(e,t,n=[]){hf(),nf();const r=hm(e)[t].apply(e,n);return rf(),ff(),r} +const Rf=Zp("__proto__,__v_isRef,__isVue"),Lf=new Set(Object.getOwnPropertyNames(Symbol).filter((e=>"arguments"!==e&&"caller"!==e)).map((e=>Symbol[e])).filter(oh)) +;function Bf(e){oh(e)||(e=String(e));const t=hm(this) +;return Sf(t,0,e),t.hasOwnProperty(e)}class jf{constructor(e=!1,t=!1){ +this._isReadonly=e,this._isShallow=t}get(e,t,n){ +if("__v_skip"===t)return e.__v_skip;const r=this._isReadonly,a=this._isShallow +;if("__v_isReactive"===t)return!r;if("__v_isReadonly"===t)return r +;if("__v_isShallow"===t)return a +;if("__v_raw"===t)return n===(r?a?am:rm:a?nm:tm).get(e)||Object.getPrototypeOf(e)===Object.getPrototypeOf(n)?e:void 0 +;const o=Jp(e);if(!r){let e;if(o&&(e=Cf[t]))return e +;if("hasOwnProperty"===t)return Bf}const i=Reflect.get(e,t,vm(e)?e:n) +;if(oh(t)?Lf.has(t):Rf(t))return i;if(r||Sf(e,0,t),a)return i;if(vm(i)){ +const e=o&&ph(t)?i:i.value;return r&&ih(e)?im(e):e}return ih(i)?r?im(i):om(i):i} +}class Uf extends jf{constructor(e=!1){super(!1,e)}set(e,t,n,r){let a=e[t] +;const o=Jp(e)&&ph(t);if(!this._isShallow){const e=um(a) +;if(dm(n)||um(n)||(a=hm(a),n=hm(n)),!o&&vm(a)&&!vm(n))return e||(a.value=n),!0} +const i=o?Number(t)e,qf=e=>Reflect.getPrototypeOf(e) +;function Wf(e){return function(...t){ +return"delete"!==e&&("clear"===e?void 0:this)}}function Xf(e,t){const n={get(n){ +const r=this.__v_raw,a=hm(r),o=hm(n);e||(wh(n,o)&&Sf(a,0,n),Sf(a,0,o)) +;const{has:i}=qf(a),s=t?Vf:e?gm:mm +;return i.call(a,n)?s(r.get(n)):i.call(a,o)?s(r.get(o)):void(r!==a&&r.get(n))}, +get size(){const t=this.__v_raw;return!e&&Sf(hm(t),0,wf),t.size},has(t){ +const n=this.__v_raw,r=hm(n),a=hm(t) +;return e||(wh(t,a)&&Sf(r,0,t),Sf(r,0,a)),t===a?n.has(t):n.has(t)||n.has(a)}, +forEach(n,r){const a=this,o=a.__v_raw,i=hm(o),s=t?Vf:e?gm:mm +;return!e&&Sf(i,0,wf),o.forEach(((e,t)=>n.call(r,s(e),s(t),a)))}};Xp(n,e?{ +add:Wf("add"),set:Wf("set"),delete:Wf("delete"),clear:Wf("clear")}:{add(e){ +t||dm(e)||um(e)||(e=hm(e));const n=hm(this) +;return qf(n).has.call(n,e)||(n.add(e),_f(n,"add",e,e)),this},set(e,n){ +t||dm(n)||um(n)||(n=hm(n));const r=hm(this),{has:a,get:o}=qf(r) +;let i=a.call(r,e);i||(e=hm(e),i=a.call(r,e));const s=o.call(r,e) +;return r.set(e,n),i?wh(n,s)&&_f(r,"set",e,n):_f(r,"add",e,n),this},delete(e){ +const t=hm(this),{has:n,get:r}=qf(t);let a=n.call(t,e) +;a||(e=hm(e),a=n.call(t,e)),r&&r.call(t,e);const o=t.delete(e) +;return a&&_f(t,"delete",e,void 0),o},clear(){ +const e=hm(this),t=0!==e.size,n=e.clear();return t&&_f(e,"clear",void 0,void 0), +n}});return["keys","values","entries",Symbol.iterator].forEach((r=>{ +n[r]=function(e,t,n){return function(...r){ +const a=this.__v_raw,o=hm(a),i=eh(o),s="entries"===e||e===Symbol.iterator&&i,l="keys"===e&&i,c=a[e](...r),u=n?Vf:t?gm:mm +;return!t&&Sf(o,0,l?xf:wf),{next(){const{value:e,done:t}=c.next();return t?{ +value:e,done:t}:{value:s?[u(e[0]),u(e[1])]:u(e),done:t}},[Symbol.iterator](){ +return this}}}}(r,e,t)})),n}function Gf(e,t){const n=Xf(e,t) +;return(t,r,a)=>"__v_isReactive"===r?!e:"__v_isReadonly"===r?e:"__v_raw"===r?t:Reflect.get(Kp(n,r)&&r in t?n:t,r,a) +}const Yf={get:Gf(!1,!1)},Kf={get:Gf(!1,!0)},Jf={get:Gf(!0,!1)},em={ +get:Gf(!0,!0)},tm=new WeakMap,nm=new WeakMap,rm=new WeakMap,am=new WeakMap +;function om(e){return um(e)?e:lm(e,!1,Zf,Yf,tm)}function im(e){ +return lm(e,!0,Ff,Jf,rm)}function sm(e){return lm(e,!0,Qf,em,am)} +function lm(e,t,n,r,a){if(!ih(e))return e +;if(e.__v_raw&&(!t||!e.__v_isReactive))return e +;const o=(i=e).__v_skip||!Object.isExtensible(i)?0:function(e){switch(e){ +case"Object":case"Array":return 1;case"Map":case"Set":case"WeakMap": +case"WeakSet":return 2;default:return 0}}(uh(i));var i;if(0===o)return e +;const s=a.get(e);if(s)return s;const l=new Proxy(e,2===o?r:n) +;return a.set(e,l),l}function cm(e){ +return um(e)?cm(e.__v_raw):!(!e||!e.__v_isReactive)}function um(e){ +return!(!e||!e.__v_isReadonly)}function dm(e){return!(!e||!e.__v_isShallow)} +function pm(e){return!!e&&!!e.__v_raw}function hm(e){const t=e&&e.__v_raw +;return t?hm(t):e}function fm(e){ +return!Kp(e,"__v_skip")&&Object.isExtensible(e)&&kh(e,"__v_skip",!0),e} +const mm=e=>ih(e)?om(e):e,gm=e=>ih(e)?im(e):e;function vm(e){ +return!!e&&!0===e.__v_isRef}function bm(e){return Om(e,!1)}function ym(e){ +return Om(e,!0)}function Om(e,t){return vm(e)?e:new wm(e,t)}class wm{ +constructor(e,t){ +this.dep=new bf,this.__v_isRef=!0,this.__v_isShallow=!1,this._rawValue=t?e:hm(e), +this._value=t?e:mm(e),this.__v_isShallow=t}get value(){ +return this.dep.track(),this._value}set value(e){ +const t=this._rawValue,n=this.__v_isShallow||dm(e)||um(e) +;e=n?e:hm(e),wh(e,t)&&(this._rawValue=e, +this._value=n?e:mm(e),this.dep.trigger())}}function xm(e){return vm(e)?e.value:e +}function km(e){return rh(e)?e():xm(e)}const Sm={ +get:(e,t,n)=>"__v_raw"===t?e:xm(Reflect.get(e,t,n)),set:(e,t,n,r)=>{const a=e[t] +;return vm(a)&&!vm(n)?(a.value=n,!0):Reflect.set(e,t,n,r)}};function _m(e){ +return cm(e)?e:new Proxy(e,Sm)}class Am{constructor(e){ +this.__v_isRef=!0,this._value=void 0 +;const t=this.dep=new bf,{get:n,set:r}=e(t.track.bind(t),t.trigger.bind(t)) +;this._get=n,this._set=r}get value(){return this._value=this._get()} +set value(e){this._set(e)}}function Tm(e){return new Am(e)}function Em(e){ +const t=Jp(e)?new Array(e.length):{};for(const n in e)t[n]=Im(e,n);return t} +class Cm{constructor(e,t,n){ +this._object=e,this._key=t,this._defaultValue=n,this.__v_isRef=!0, +this._value=void 0,this._raw=hm(e);let r=!0,a=e;if(!Jp(e)||!ph(String(t)))do{ +r=!pm(a)||dm(a)}while(r&&(a=a.__v_raw));this._shallow=r}get value(){ +let e=this._object[this._key] +;return this._shallow&&(e=xm(e)),this._value=void 0===e?this._defaultValue:e} +set value(e){if(this._shallow&&vm(this._raw[this._key])){ +const t=this._object[this._key];if(vm(t))return void(t.value=e)} +this._object[this._key]=e}get dep(){return function(e,t){const n=Of.get(e) +;return n&&n.get(t)}(this._raw,this._key)}}class $m{constructor(e){ +this._getter=e,this.__v_isRef=!0,this.__v_isReadonly=!0,this._value=void 0} +get value(){return this._value=this._getter()}}function Pm(e,t,n){ +return vm(e)?e:rh(e)?new $m(e):ih(e)&&arguments.length>1?Im(e,t,n):bm(e)} +function Im(e,t,n){return new Cm(e,t,n)}class Dm{constructor(e,t,n){ +this.fn=e,this.setter=t, +this._value=void 0,this.dep=new bf(this),this.__v_isRef=!0, +this.deps=void 0,this.depsTail=void 0, +this.flags=16,this.globalVersion=gf-1,this.next=void 0, +this.effect=this,this.__v_isReadonly=!t,this.isSSR=n}notify(){if(this.flags|=16, +!(8&this.flags)&&Qh!==this)return tf(this,!0),!0}get value(){ +const e=this.dep.track() +;return lf(this),e&&(e.version=this.dep.version),this._value}set value(e){ +this.setter&&this.setter(e)}}const Mm={},Nm=new WeakMap;let Rm +;function Lm(e,t,n=Fp){ +const{immediate:r,deep:a,once:o,scheduler:i,augmentJob:s,call:l}=n,c=e=>a?e:dm(e)||!1===a||0===a?Bm(e,1):Bm(e) +;let u,d,p,h,f=!1,m=!1 +;if(vm(e)?(d=()=>e.value,f=dm(e)):cm(e)?(d=()=>c(e),f=!0):Jp(e)?(m=!0, +f=e.some((e=>cm(e)||dm(e))), +d=()=>e.map((e=>vm(e)?e.value:cm(e)?c(e):rh(e)?l?l(e,2):e():void 0))):d=rh(e)?t?l?()=>l(e,2):e:()=>{ +if(p){hf();try{p()}finally{ff()}}const t=Rm;Rm=u;try{return l?l(e,3,[h]):e(h) +}finally{Rm=t}}:Qp,t&&a){const e=d,t=!0===a?1/0:a;d=()=>Bm(e(),t)} +const g=Wh(),v=()=>{u.stop(),g&&g.active&&Gp(g.effects,u)};if(o&&t){const e=t +;t=(...t)=>{e(...t),v()}}let b=m?new Array(e.length).fill(Mm):Mm;const y=e=>{ +if(1&u.flags&&(u.dirty||e))if(t){const e=u.run() +;if(a||f||(m?e.some(((e,t)=>wh(e,b[t]))):wh(e,b))){p&&p();const n=Rm;Rm=u;try{ +const n=[e,b===Mm?void 0:m&&b[0]===Mm?[]:b,h];b=e,l?l(t,3,n):t(...n)}finally{ +Rm=n}}}else u.run()} +;return s&&s(y),u=new Yh(d),u.scheduler=i?()=>i(y,!1):y,h=e=>function(e,t=!1,n=Rm){ +if(n){let t=Nm.get(n);t||Nm.set(n,t=[]),t.push(e)}}(e,!1,u),p=u.onStop=()=>{ +const e=Nm.get(u);if(e){if(l)l(e,4);else for(const t of e)t();Nm.delete(u)} +},t?r?y(!0):b=u.run():i?i(y.bind(null,!0),!0):u.run(), +v.pause=u.pause.bind(u),v.resume=u.resume.bind(u),v.stop=v,v} +function Bm(e,t=1/0,n){if(t<=0||!ih(e)||e.__v_skip)return e +;if(((n=n||new Map).get(e)||0)>=t)return e +;if(n.set(e,t),t--,vm(e))Bm(e.value,t,n);else if(Jp(e))for(let r=0;r{ +Bm(e,t,n)}));else if(dh(e)){for(const r in e)Bm(e[r],t,n) +;for(const r of Object.getOwnPropertySymbols(e))Object.prototype.propertyIsEnumerable.call(e,r)&&Bm(e[r],t,n) +}return e}function jm(e,t,n,r){try{return r?e(...r):e()}catch(a){zm(a,t,n)}} +function Um(e,t,n,r){if(rh(e)){const a=jm(e,t,n,r) +;return a&&sh(a)&&a.catch((e=>{zm(e,t,n)})),a}if(Jp(e)){const a=[] +;for(let o=0;o=tg(n)?Zm.push(e):Zm.splice(function(e){ +let t=Fm+1,n=Zm.length;for(;t>>1,a=Zm[r],o=tg(a) +;otg(e)-tg(t))) +;if(Hm.length=0,Qm)return void Qm.push(...e);for(Qm=e,Vm=0;Vmnull==e.id?2&e.flags?-1:1/0:e.id;function ng(e){try{ +for(Fm=0;Fm{ +r._d&&Qb(-1);const a=og(t);let o;try{o=e(...n)}finally{og(a),r._d&&Qb(1)} +return o};return r._n=!0,r._c=!0,r._d=!0,r}function sg(e,t){ +if(null===rg)return e;const n=_y(rg),r=e.dirs||(e.dirs=[]) +;for(let a=0;a1)return n&&rh(t)?t.call(r&&r.proxy):t}}function dg(){ +return!(!fy()&&!eb)}const pg=Symbol.for("v-scx"),hg=()=>ug(pg);function fg(e,t){ +return gg(e,null,t)}function mg(e,t,n){return gg(e,t,n)}function gg(e,t,n=Fp){ +const{immediate:r,deep:a,flush:o,once:i}=n,s=Xp({},n),l=t&&r||!t&&"post"!==o +;let c;if(Oy)if("sync"===o){const e=hg() +;c=e.__watcherHandles||(e.__watcherHandles=[])}else if(!l){const e=()=>{} +;return e.stop=Qp,e.resume=Qp,e.pause=Qp,e}const u=hy +;s.call=(e,t,n)=>Um(e,u,t,n);let d=!1;"post"===o?s.scheduler=e=>{ +Eb(e,u&&u.suspense)}:"sync"!==o&&(d=!0,s.scheduler=(e,t)=>{t?e():Gm(e) +}),s.augmentJob=e=>{t&&(e.flags|=4),d&&(e.flags|=2,u&&(e.id=u.uid,e.i=u))} +;const p=Lm(e,t,s);return Oy&&(c?c.push(p):l&&p()),p}function vg(e,t,n){ +const r=this.proxy,a=ah(e)?e.includes(".")?bg(r,e):()=>r[e]:e.bind(r,r);let o +;rh(t)?o=t:(o=t.handler,n=t);const i=vy(this),s=gg(a,o.bind(r),n);return i(),s} +function bg(e,t){const n=t.split(".");return()=>{let t=e +;for(let e=0;ee.__isTeleport,wg=e=>e&&(e.disabled||""===e.disabled),xg=e=>e&&(e.defer||""===e.defer),kg=e=>"undefined"!=typeof SVGElement&&e instanceof SVGElement,Sg=e=>"function"==typeof MathMLElement&&e instanceof MathMLElement,_g=(e,t)=>{ +const n=e&&e.to;if(ah(n)){if(t){return t(n)}return null}return n},Ag={ +name:"Teleport",__isTeleport:!0,process(e,t,n,r,a,o,i,s,l,c){ +const{mc:u,pc:d,pbc:p,o:{insert:h,querySelector:f,createText:m,createComment:g}}=c,v=wg(t.props) +;let{shapeFlag:b,children:y,dynamicChildren:O}=t;if(null==e){ +const e=t.el=m(""),c=t.anchor=m("");h(e,n,r),h(c,n,r);const d=(e,t)=>{ +16&b&&u(y,e,t,a,o,i,s,l)},p=()=>{const e=t.target=_g(t.props,f),n=$g(e,t,m,h) +;e&&("svg"!==i&&kg(e)?i="svg":"mathml"!==i&&Sg(e)&&(i="mathml"), +a&&a.isCE&&(a.ce._teleportTargets||(a.ce._teleportTargets=new Set)).add(e), +v||(d(e,n),Cg(t,!1)))} +;v&&(d(n,c),Cg(t,!0)),xg(t.props)?(t.el.__isMounted=!1,Eb((()=>{ +p(),delete t.el.__isMounted}),o)):p()}else{ +if(xg(t.props)&&!1===e.el.__isMounted)return void Eb((()=>{ +Ag.process(e,t,n,r,a,o,i,s,l,c)}),o);t.el=e.el,t.targetStart=e.targetStart +;const u=t.anchor=e.anchor,h=t.target=e.target,m=t.targetAnchor=e.targetAnchor,g=wg(e.props),b=g?n:h,y=g?u:m +;if("svg"===i||kg(h)?i="svg":("mathml"===i||Sg(h))&&(i="mathml"), +O?(p(e.dynamicChildren,O,b,a,o,i,s), +Ib(e,t,!0)):l||d(e,t,b,y,a,o,i,s,!1),v)g?t.props&&e.props&&t.props.to!==e.props.to&&(t.props.to=e.props.to):Tg(t,n,u,c,1);else if((t.props&&t.props.to)!==(e.props&&e.props.to)){ +const e=t.target=_g(t.props,f);e&&Tg(t,e,null,c,0)}else g&&Tg(t,h,m,c,1);Cg(t,v) +}},remove(e,t,n,{um:r,o:{remove:a}},o){ +const{shapeFlag:i,children:s,anchor:l,targetStart:c,targetAnchor:u,target:d,props:p}=e +;if(d&&(a(c),a(u)),o&&a(l),16&i){const e=o||!wg(p);for(let a=0;a{const t=e.subTree +;return t.component?Ng(t.component):t};function Rg(e){let t=e[0] +;if(e.length>1)for(const n of e)if(n.type!==jb){t=n;break}return t}const Lg={ +name:"BaseTransition",props:Mg,setup(e,{slots:t}){const n=fy(),r=function(){ +const e={isMounted:!1,isLeaving:!1,isUnmounting:!1,leavingVNodes:new Map} +;return cv((()=>{e.isMounted=!0})),pv((()=>{e.isUnmounting=!0})),e}() +;return()=>{const a=t.default&&Fg(t.default(),!0);if(!a||!a.length)return +;const o=Rg(a),i=hm(e),{mode:s}=i;if(r.isLeaving)return Ug(o);const l=zg(o) +;if(!l)return Ug(o);let c=jg(l,i,r,n,(e=>c=e));l.type!==jb&&Zg(l,c) +;let u=n.subTree&&zg(n.subTree);if(u&&u.type!==jb&&!Gb(u,l)&&Ng(n).type!==jb){ +let e=jg(u,i,r,n) +;if(Zg(u,e),"out-in"===s&&l.type!==jb)return r.isLeaving=!0,e.afterLeave=()=>{ +r.isLeaving=!1,8&n.job.flags||n.update(),delete e.afterLeave,u=void 0},Ug(o) +;"in-out"===s&&l.type!==jb?e.delayLeave=(e,t,n)=>{ +Bg(r,u)[String(u.key)]=u,e[Pg]=()=>{ +t(),e[Pg]=void 0,delete c.delayedLeave,u=void 0},c.delayedLeave=()=>{ +n(),delete c.delayedLeave,u=void 0}}:u=void 0}else u&&(u=void 0);return o}}} +;function Bg(e,t){const{leavingVNodes:n}=e;let r=n.get(t.type) +;return r||(r=Object.create(null),n.set(t.type,r)),r}function jg(e,t,n,r,a){ +const{appear:o,mode:i,persisted:s=!1,onBeforeEnter:l,onEnter:c,onAfterEnter:u,onEnterCancelled:d,onBeforeLeave:p,onLeave:h,onAfterLeave:f,onLeaveCancelled:m,onBeforeAppear:g,onAppear:v,onAfterAppear:b,onAppearCancelled:y}=t,O=String(e.key),w=Bg(n,e),x=(e,t)=>{ +e&&Um(e,r,9,t)},k=(e,t)=>{const n=t[1] +;x(e,t),Jp(e)?e.every((e=>e.length<=1))&&n():e.length<=1&&n()},S={mode:i, +persisted:s,beforeEnter(t){let r=l;if(!n.isMounted){if(!o)return;r=g||l} +t[Pg]&&t[Pg](!0);const a=w[O];a&&Gb(e,a)&&a.el[Pg]&&a.el[Pg](),x(r,[t])}, +enter(e){let t=c,r=u,a=d;if(!n.isMounted){if(!o)return;t=v||c,r=b||u,a=y||d} +let i=!1;const s=e[Ig]=t=>{ +i||(i=!0,x(t?a:r,[e]),S.delayedLeave&&S.delayedLeave(),e[Ig]=void 0)} +;t?k(t,[e,s]):s()},leave(t,r){const a=String(e.key) +;if(t[Ig]&&t[Ig](!0),n.isUnmounting)return r();x(p,[t]);let o=!1 +;const i=t[Pg]=n=>{o||(o=!0,r(),x(n?m:f,[t]),t[Pg]=void 0,w[a]===e&&delete w[a]) +};w[a]=e,h?k(h,[t,i]):i()},clone(e){const o=jg(e,t,n,r,a);return a&&a(o),o}} +;return S}function Ug(e){if(tv(e))return(e=ny(e)).children=null,e} +function zg(e){if(!tv(e))return Og(e.type)&&e.children?Rg(e.children):e +;if(e.component)return e.component.subTree;const{shapeFlag:t,children:n}=e +;if(n){if(16&t)return n[0];if(32&t&&rh(n.default))return n.default()}} +function Zg(e,t){ +6&e.shapeFlag&&e.component?(e.transition=t,Zg(e.component.subTree,t)):128&e.shapeFlag?(e.ssContent.transition=t.clone(e.ssContent), +e.ssFallback.transition=t.clone(e.ssFallback)):e.transition=t} +function Fg(e,t=!1,n){let r=[],a=0;for(let o=0;o1)for(let o=0;oXp({name:e.name},t,{setup:e}))():e} +function Qg(){const e=fy() +;return e?(e.appContext.config.idPrefix||"v")+"-"+e.ids[0]+e.ids[1]++:""} +function Vg(e){e.ids=[e.ids[0]+e.ids[2]+++"-",0,0]}function qg(e){ +const t=fy(),n=ym(null);if(t){const r=t.refs===Fp?t.refs={}:t.refs +;Object.defineProperty(r,e,{enumerable:!0,get:()=>n.value,set:e=>n.value=e})} +return n}const Wg=new WeakMap;function Xg(e,t,n,r,a=!1){ +if(Jp(e))return void e.forEach(((e,o)=>Xg(e,t&&(Jp(t)?t[o]:t),n,r,a))) +;if(Kg(r)&&!a)return void(512&r.shapeFlag&&r.type.__asyncResolved&&r.component.subTree.component&&Xg(e,t,n,r.component.subTree)) +;const o=4&r.shapeFlag?_y(r.component):r.el,i=a?null:o,{i:s,r:l}=e,c=t&&t.r,u=s.refs===Fp?s.refs={}:s.refs,d=s.setupState,p=hm(d),h=d===Fp?Vp:e=>Kp(p,e) +;if(null!=c&&c!==l)if(Gg(t),ah(c))u[c]=null,h(c)&&(d[c]=null);else if(vm(c)){ +c.value=null;const e=t;e.k&&(u[e.k]=null)}if(rh(l))jm(l,s,12,[i,u]);else{ +const t=ah(l),r=vm(l);if(t||r){const s=()=>{if(e.f){ +const n=t?h(l)?d[l]:u[l]:l.value +;if(a)Jp(n)&&Gp(n,o);else if(Jp(n))n.includes(o)||n.push(o);else if(t)u[l]=[o], +h(l)&&(d[l]=u[l]);else{const t=[o];l.value=t,e.k&&(u[e.k]=t)} +}else t?(u[l]=i,h(l)&&(d[l]=i)):r&&(l.value=i,e.k&&(u[e.k]=i))};if(i){ +const t=()=>{s(),Wg.delete(e)};t.id=-1,Wg.set(e,t),Eb(t,n)}else Gg(e),s()}}} +function Gg(e){const t=Wg.get(e);t&&(t.flags|=8,Wg.delete(e))} +const Yg=e=>8===e.nodeType;Th().requestIdleCallback,Th().cancelIdleCallback +;const Kg=e=>!!e.type.__asyncLoader;function Jg(e){rh(e)&&(e={loader:e}) +;const{loader:t,loadingComponent:n,errorComponent:r,delay:a=200,hydrate:o,timeout:i,suspensible:s=!0,onError:l}=e +;let c,u=null,d=0;const p=()=>{let e;return u||(e=u=t().catch((e=>{ +if(e=e instanceof Error?e:new Error(String(e)),l)return new Promise(((t,n)=>{ +l(e,(()=>t((d++,u=null,p()))),(()=>n(e)),d+1)}));throw e +})).then((t=>e!==u&&u?u:(t&&(t.__esModule||"Module"===t[Symbol.toStringTag])&&(t=t.default), +c=t,t))))};return Hg({name:"AsyncComponentWrapper",__asyncLoader:p, +__asyncHydrate(e,t,n){let r=!1;(t.bu||(t.bu=[])).push((()=>r=!0));const a=()=>{ +r||n()},i=o?()=>{const n=o(a,(t=>function(e,t){if(Yg(e)&&"["===e.data){ +let n=1,r=e.nextSibling;for(;r;){if(1===r.nodeType){if(!1===t(r))break +}else if(Yg(r))if("]"===r.data){if(0==--n)break}else"["===r.data&&n++ +;r=r.nextSibling}}else t(e)}(e,t)));n&&(t.bum||(t.bum=[])).push(n)}:a +;c?i():p().then((()=>!t.isUnmounted&&i()))},get __asyncResolved(){return c}, +setup(){const e=hy;if(Vg(e),c)return()=>ev(c,e);const t=t=>{u=null,zm(t,e,13,!r) +} +;if(s&&e.suspense||Oy)return p().then((t=>()=>ev(t,e))).catch((e=>(t(e),()=>r?ey(r,{ +error:e}):null)));const o=bm(!1),l=bm(),d=bm(!!a);return a&&setTimeout((()=>{ +d.value=!1}),a),null!=i&&setTimeout((()=>{if(!o.value&&!l.value){ +const e=new Error(`Async component timed out after ${i}ms.`);t(e),l.value=e} +}),i),p().then((()=>{o.value=!0,e.parent&&tv(e.parent.vnode)&&e.parent.update() +})).catch((e=>{t(e),l.value=e})),()=>o.value&&c?ev(c,e):l.value&&r?ey(r,{ +error:l.value}):n&&!d.value?ev(n,e):void 0}})}function ev(e,t){ +const{ref:n,props:r,children:a,ce:o}=t.vnode,i=ey(e,r,a) +;return i.ref=n,i.ce=o,delete t.vnode.ce,i}const tv=e=>e.type.__isKeepAlive +;function nv(e,t){av(e,"a",t)}function rv(e,t){av(e,"da",t)} +function av(e,t,n=hy){const r=e.__wdc||(e.__wdc=()=>{let t=n;for(;t;){ +if(t.isDeactivated)return;t=t.parent}return e()});if(iv(t,r,n),n){let e=n.parent +;for(;e&&e.parent;)tv(e.parent.vnode)&&ov(r,t,n,e),e=e.parent}} +function ov(e,t,n,r){const a=iv(t,e,r,!0);hv((()=>{Gp(r[t],a)}),n)} +function iv(e,t,n=hy,r=!1){if(n){ +const a=n[e]||(n[e]=[]),o=t.__weh||(t.__weh=(...r)=>{hf() +;const a=vy(n),o=Um(t,n,e,r);return a(),ff(),o}) +;return r?a.unshift(o):a.push(o),o}}const sv=e=>(t,n=hy)=>{ +Oy&&"sp"!==e||iv(e,((...e)=>t(...e)),n) +},lv=sv("bm"),cv=sv("m"),uv=sv("bu"),dv=sv("u"),pv=sv("bum"),hv=sv("um"),fv=sv("sp"),mv=sv("rtg"),gv=sv("rtc") +;function vv(e,t=hy){iv("ec",e,t)}const bv="components";function yv(e,t){ +return xv(bv,e,!0,t)||e}const Ov=Symbol.for("v-ndc");function wv(e){ +return ah(e)?xv(bv,e,!1)||e:e||Ov}function xv(e,t,n=!0,r=!1){const a=rg||hy +;if(a){const n=a.type;{const e=Ay(n,!1) +;if(e&&(e===t||e===gh(t)||e===yh(gh(t))))return n} +const o=kv(a[e]||n[e],t)||kv(a.appContext[e],t);return!o&&r?n:o}} +function kv(e,t){return e&&(e[t]||e[gh(t)]||e[yh(gh(t))])}function Sv(e,t,n,r){ +let a;const o=n,i=Jp(e);if(i||ah(e)){let n=!1,r=!1 +;i&&cm(e)&&(n=!dm(e),r=um(e),e=Tf(e)),a=new Array(e.length) +;for(let i=0,s=e.length;it(e,n,void 0,o)));else{ +const n=Object.keys(e);a=new Array(n.length);for(let r=0,i=n.length;r{ +const t=r.fn(...e);return t&&(t.key=r.key),t}:r.fn)}return e} +function Av(e,t,n={},r,a){if(rg.ce||rg.parent&&Kg(rg.parent)&&rg.parent.ce){ +const e=Object.keys(n).length>0 +;return"default"!==t&&(n.name=t),Fb(),Wb(Lb,null,[ey("slot",n,r&&r())],e?-2:64)} +let o=e[t];o&&o._c&&(o._d=!1),Fb() +;const i=o&&Tv(o(n)),s=n.key||i&&i.key,l=Wb(Lb,{ +key:(s&&!oh(s)?s:`_${t}`)+(!i&&r?"_fb":"")},i||(r?r():[]),i&&1===e._?64:-2) +;return!a&&l.scopeId&&(l.slotScopeIds=[l.scopeId+"-s"]),o&&o._c&&(o._d=!0),l} +function Tv(e){ +return e.some((e=>!Xb(e)||e.type!==jb&&!(e.type===Lb&&!Tv(e.children))))?e:null} +function Ev(e,t){const n={};for(const r in e)n[Oh(r)]=e[r];return n} +const Cv=e=>e?yy(e)?_y(e):Cv(e.parent):null,$v=Xp(Object.create(null),{$:e=>e, +$el:e=>e.vnode.el,$data:e=>e.data,$props:e=>e.props,$attrs:e=>e.attrs, +$slots:e=>e.slots,$refs:e=>e.refs,$parent:e=>Cv(e.parent),$root:e=>Cv(e.root), +$host:e=>e.ce,$emit:e=>e.emit,$options:e=>Fv(e),$forceUpdate:e=>e.f||(e.f=()=>{ +Gm(e.update)}),$nextTick:e=>e.n||(e.n=Xm.bind(e.proxy)),$watch:e=>vg.bind(e) +}),Pv=(e,t)=>e!==Fp&&!e.__isScriptSetup&&Kp(e,t),Iv={get({_:e},t){ +if("__v_skip"===t)return!0 +;const{ctx:n,setupState:r,data:a,props:o,accessCache:i,type:s,appContext:l}=e +;if("$"!==t[0]){const e=i[t];if(void 0!==e)switch(e){case 1:return r[t];case 2: +return a[t];case 4:return n[t];case 3:return o[t]}else{if(Pv(r,t))return i[t]=1, +r[t];if(a!==Fp&&Kp(a,t))return i[t]=2,a[t];if(Kp(o,t))return i[t]=3,o[t] +;if(n!==Fp&&Kp(n,t))return i[t]=4,n[t];jv&&(i[t]=0)}}const c=$v[t];let u,d +;return c?("$attrs"===t&&Sf(e.attrs,0,""), +c(e)):(u=s.__cssModules)&&(u=u[t])?u:n!==Fp&&Kp(n,t)?(i[t]=4, +n[t]):(d=l.config.globalProperties,Kp(d,t)?d[t]:void 0)},set({_:e},t,n){ +const{data:r,setupState:a,ctx:o}=e +;return Pv(a,t)?(a[t]=n,!0):r!==Fp&&Kp(r,t)?(r[t]=n, +!0):!Kp(e.props,t)&&(("$"!==t[0]||!(t.slice(1)in e))&&(o[t]=n,!0))}, +has({_:{data:e,setupState:t,accessCache:n,ctx:r,appContext:a,props:o,type:i}},s){ +let l +;return!!(n[s]||e!==Fp&&"$"!==s[0]&&Kp(e,s)||Pv(t,s)||Kp(o,s)||Kp(r,s)||Kp($v,s)||Kp(a.config.globalProperties,s)||(l=i.__cssModules)&&l[s]) +},defineProperty(e,t,n){ +return null!=n.get?e._.accessCache[t]=0:Kp(n,"value")&&this.set(e,t,n.value,null), +Reflect.defineProperty(e,t,n)}};function Dv(){return Nv().slots}function Mv(){ +return Nv().attrs}function Nv(e){const t=fy() +;return t.setupContext||(t.setupContext=Sy(t))}function Rv(e){ +return Jp(e)?e.reduce(((e,t)=>(e[t]=null,e)),{}):e}function Lv(e,t){ +const n=Rv(e);for(const r in t){if(r.startsWith("__skip"))continue;let e=n[r] +;e?Jp(e)||rh(e)?e=n[r]={type:e,default:t[r]}:e.default=t[r]:null===e&&(e=n[r]={ +default:t[r]}),e&&t[`__skip_${r}`]&&(e.skipFactory=!0)}return n} +function Bv(e,t){return e&&t?Jp(e)&&Jp(t)?e.concat(t):Xp({},Rv(e),Rv(t)):e||t} +let jv=!0;function Uv(e){const t=Fv(e),n=e.proxy,r=e.ctx +;jv=!1,t.beforeCreate&&zv(t.beforeCreate,e,"bc") +;const{data:a,computed:o,methods:i,watch:s,provide:l,inject:c,created:u,beforeMount:d,mounted:p,beforeUpdate:h,updated:f,activated:m,deactivated:g,beforeDestroy:v,beforeUnmount:b,destroyed:y,unmounted:O,render:w,renderTracked:x,renderTriggered:k,errorCaptured:S,serverPrefetch:_,expose:A,inheritAttrs:T,components:E,directives:C,filters:$}=t +;if(c&&function(e,t,n=Qp){Jp(e)&&(e=qv(e));for(const r in e){const n=e[r];let a +;a=ih(n)?"default"in n?ug(n.from||r,n.default,!0):ug(n.from||r):ug(n), +vm(a)?Object.defineProperty(t,r,{enumerable:!0,configurable:!0,get:()=>a.value, +set:e=>a.value=e}):t[r]=a}}(c,r,null),i)for(const I in i){const e=i[I] +;rh(e)&&(r[I]=e.bind(n))}if(a){const t=a.call(n,n);ih(t)&&(e.data=om(t))} +if(jv=!0,o)for(const I in o){ +const e=o[I],t=rh(e)?e.bind(n,n):rh(e.get)?e.get.bind(n,n):Qp,a=!rh(e)&&rh(e.set)?e.set.bind(n):Qp,i=Ty({ +get:t,set:a});Object.defineProperty(r,I,{enumerable:!0,configurable:!0, +get:()=>i.value,set:e=>i.value=e})}if(s)for(const I in s)Zv(s[I],r,n,I);if(l){ +const e=rh(l)?l.call(n):l;Reflect.ownKeys(e).forEach((t=>{cg(t,e[t])}))} +function P(e,t){Jp(t)?t.forEach((t=>e(t.bind(n)))):t&&e(t.bind(n))} +if(u&&zv(u,e,"c"), +P(lv,d),P(cv,p),P(uv,h),P(dv,f),P(nv,m),P(rv,g),P(vv,S),P(gv,x),P(mv,k),P(pv,b), +P(hv,O),P(fv,_),Jp(A))if(A.length){const t=e.exposed||(e.exposed={}) +;A.forEach((e=>{Object.defineProperty(t,e,{get:()=>n[e],set:t=>n[e]=t, +enumerable:!0})}))}else e.exposed||(e.exposed={}) +;w&&e.render===Qp&&(e.render=w),null!=T&&(e.inheritAttrs=T),E&&(e.components=E), +C&&(e.directives=C),_&&Vg(e)}function zv(e,t,n){ +Um(Jp(e)?e.map((e=>e.bind(t.proxy))):e.bind(t.proxy),t,n)}function Zv(e,t,n,r){ +let a=r.includes(".")?bg(n,r):()=>n[r];if(ah(e)){const n=t[e];rh(n)&&mg(a,n) +}else if(rh(e))mg(a,e.bind(n));else if(ih(e))if(Jp(e))e.forEach((e=>Zv(e,t,n,r)));else{ +const r=rh(e.handler)?e.handler.bind(n):t[e.handler];rh(r)&&mg(a,r,e)}} +function Fv(e){ +const t=e.type,{mixins:n,extends:r}=t,{mixins:a,optionsCache:o,config:{optionMergeStrategies:i}}=e.appContext,s=o.get(t) +;let l;return s?l=s:a.length||n||r?(l={},a.length&&a.forEach((e=>Hv(l,e,i,!0))), +Hv(l,t,i)):l=t,ih(t)&&o.set(t,l),l}function Hv(e,t,n,r=!1){ +const{mixins:a,extends:o}=t;o&&Hv(e,o,n,!0),a&&a.forEach((t=>Hv(e,t,n,!0))) +;for(const i in t)if(r&&"expose"===i);else{const r=Qv[i]||n&&n[i] +;e[i]=r?r(e[i],t[i]):t[i]}return e}const Qv={data:Vv,props:Gv,emits:Gv, +methods:Xv,computed:Xv,beforeCreate:Wv,created:Wv,beforeMount:Wv,mounted:Wv, +beforeUpdate:Wv,updated:Wv,beforeDestroy:Wv,beforeUnmount:Wv,destroyed:Wv, +unmounted:Wv,activated:Wv,deactivated:Wv,errorCaptured:Wv,serverPrefetch:Wv, +components:Xv,directives:Xv,watch:function(e,t){if(!e)return t;if(!t)return e +;const n=Xp(Object.create(null),e);for(const r in t)n[r]=Wv(e[r],t[r]);return n +},provide:Vv,inject:function(e,t){return Xv(qv(e),qv(t))}};function Vv(e,t){ +return t?e?function(){ +return Xp(rh(e)?e.call(this,this):e,rh(t)?t.call(this,this):t)}:t:e} +function qv(e){if(Jp(e)){const t={};for(let n=0;n(a.has(e)||(e&&rh(e.install)?(a.add(e),e.install(s,...t)):rh(e)&&(a.add(e), +e(s,...t))),s),mixin:e=>(r.mixins.includes(e)||r.mixins.push(e),s), +component:(e,t)=>t?(r.components[e]=t,s):r.components[e], +directive:(e,t)=>t?(r.directives[e]=t,s):r.directives[e],mount(a,o,l){if(!i){ +const o=s._ceVNode||ey(t,n) +;return o.appContext=r,!0===l?l="svg":!1===l&&(l=void 0), +e(o,a,l),i=!0,s._container=a,a.__vue_app__=s,_y(o.component)}},onUnmount(e){ +o.push(e)},unmount(){ +i&&(Um(o,s._instance,16),e(null,s._container),delete s._container.__vue_app__)}, +provide:(e,t)=>(r.provides[e]=t,s),runWithContext(e){const t=eb;eb=s;try{ +return e()}finally{eb=t}}};return s}}let eb=null;function tb(e,t,n=Fp){ +const r=fy(),a=gh(t),o=bh(t),i=nb(e,a),s=Tm(((i,s)=>{let l,c,u=Fp +;return gg((()=>{const t=e[a];wh(l,t)&&(l=t,s())}),null,{flush:"sync"}),{ +get:()=>(i(),n.get?n.get(l):l),set(e){const i=n.set?n.set(e):e +;if(!(wh(i,l)||u!==Fp&&wh(e,u)))return;const d=r.vnode.props +;d&&(t in d||a in d||o in d)&&(`onUpdate:${t}`in d||`onUpdate:${a}`in d||`onUpdate:${o}`in d)||(l=e, +s()),r.emit(`update:${t}`,i),wh(e,i)&&wh(e,u)&&!wh(i,c)&&s(),u=e,c=i}}})) +;return s[Symbol.iterator]=()=>{let e=0;return{next:()=>e<2?{value:e++?i||Fp:s, +done:!1}:{done:!0}}},s} +const nb=(e,t)=>"modelValue"===t||"model-value"===t?e.modelModifiers:e[`${t}Modifiers`]||e[`${gh(t)}Modifiers`]||e[`${bh(t)}Modifiers`] +;function rb(e,t,...n){if(e.isUnmounted)return;const r=e.vnode.props||Fp;let a=n +;const o=t.startsWith("update:"),i=o&&nb(r,t.slice(7));let s +;i&&(i.trim&&(a=n.map((e=>ah(e)?e.trim():e))),i.number&&(a=n.map(Sh))) +;let l=r[s=Oh(t)]||r[s=Oh(gh(t))];!l&&o&&(l=r[s=Oh(bh(t))]),l&&Um(l,e,6,a) +;const c=r[s+"Once"];if(c){if(e.emitted){if(e.emitted[s])return +}else e.emitted={};e.emitted[s]=!0,Um(c,e,6,a)}}const ab=new WeakMap +;function ob(e,t,n=!1){const r=n?ab:t.emitsCache,a=r.get(e) +;if(void 0!==a)return a;const o=e.emits;let i={},s=!1;if(!rh(e)){const r=e=>{ +const n=ob(e,t,!0);n&&(s=!0,Xp(i,n))} +;!n&&t.mixins.length&&t.mixins.forEach(r),e.extends&&r(e.extends), +e.mixins&&e.mixins.forEach(r)} +return o||s?(Jp(o)?o.forEach((e=>i[e]=null)):Xp(i,o), +ih(e)&&r.set(e,i),i):(ih(e)&&r.set(e,null),null)}function ib(e,t){ +return!(!e||!qp(t))&&(t=t.slice(2).replace(/Once$/,""), +Kp(e,t[0].toLowerCase()+t.slice(1))||Kp(e,bh(t))||Kp(e,t))}function sb(e){ +const{type:t,vnode:n,proxy:r,withProxy:a,propsOptions:[o],slots:i,attrs:s,emit:l,render:c,renderCache:u,props:d,data:p,setupState:h,ctx:f,inheritAttrs:m}=e,g=og(e) +;let v,b;try{if(4&n.shapeFlag){const e=a||r,t=e;v=iy(c.call(t,e,u,d,h,p,f)),b=s +}else{const e=t;0,v=iy(e.length>1?e(d,{attrs:s,slots:i,emit:l +}):e(d,null)),b=t.props?s:lb(s)}}catch(O){zb.length=0,zm(O,e,1),v=ey(jb)}let y=v +;if(b&&!1!==m){const e=Object.keys(b),{shapeFlag:t}=y +;e.length&&7&t&&(o&&e.some(Wp)&&(b=cb(b,o)),y=ny(y,b,!1,!0))} +return n.dirs&&(y=ny(y,null,!1,!0), +y.dirs=y.dirs?y.dirs.concat(n.dirs):n.dirs),n.transition&&Zg(y,n.transition), +v=y,og(g),v}const lb=e=>{let t +;for(const n in e)("class"===n||"style"===n||qp(n))&&((t||(t={}))[n]=e[n]) +;return t},cb=(e,t)=>{const n={} +;for(const r in e)Wp(r)&&r.slice(9)in t||(n[r]=e[r]);return n} +;function ub(e,t,n){const r=Object.keys(t) +;if(r.length!==Object.keys(e).length)return!0;for(let a=0;aObject.create(db),hb=e=>Object.getPrototypeOf(e)===db +;function fb(e,t,n,r=!1){const a={},o=pb() +;e.propsDefaults=Object.create(null),mb(e,t,a,o) +;for(const i in e.propsOptions[0])i in a||(a[i]=void 0) +;n?e.props=r?a:lm(a,!1,Hf,Kf,nm):e.type.props?e.props=a:e.props=o,e.attrs=o} +function mb(e,t,n,r){const[a,o]=e.propsOptions;let i,s=!1;if(t)for(let l in t){ +if(hh(l))continue;const c=t[l];let u +;a&&Kp(a,u=gh(l))?o&&o.includes(u)?(i||(i={}))[u]=c:n[u]=c:ib(e.emitsOptions,l)||l in r&&c===r[l]||(r[l]=c, +s=!0)}if(o){const t=hm(n),r=i||Fp;for(let i=0;i{l=!0;const[n,r]=bb(e,t,!0) +;Xp(i,n),r&&s.push(...r)} +;!n&&t.mixins.length&&t.mixins.forEach(r),e.extends&&r(e.extends), +e.mixins&&e.mixins.forEach(r)}if(!o&&!l)return ih(e)&&r.set(e,Hp),Hp +;if(Jp(o))for(let u=0;u"_"===e||"_ctx"===e||"$stable"===e,wb=e=>Jp(e)?e.map(iy):[iy(e)],xb=(e,t,n)=>{ +if(t._n)return t;const r=ig(((...e)=>wb(t(...e))),n);return r._c=!1,r +},kb=(e,t,n)=>{const r=e._ctx;for(const a in e){if(Ob(a))continue;const n=e[a] +;if(rh(n))t[a]=xb(0,n,r);else if(null!=n){const e=wb(n);t[a]=()=>e}} +},Sb=(e,t)=>{const n=wb(t);e.slots.default=()=>n},_b=(e,t,n)=>{ +for(const r in t)!n&&Ob(r)||(e[r]=t[r])},Ab=(e,t,n)=>{const r=e.slots=pb() +;if(32&e.vnode.shapeFlag){const e=t._;e?(_b(r,t,n),n&&kh(r,"_",e,!0)):kb(t,r) +}else t&&Sb(e,t)},Tb=(e,t,n)=>{const{vnode:r,slots:a}=e;let o=!0,i=Fp +;if(32&r.shapeFlag){const e=t._ +;e?n&&1===e?o=!1:_b(a,t,n):(o=!t.$stable,kb(t,a)),i=t}else t&&(Sb(e,t),i={ +default:1});if(o)for(const s in a)Ob(s)||null!=i[s]||delete a[s] +},Eb=function(e,t){ +t&&t.pendingBranch?Jp(e)?t.effects.push(...e):t.effects.push(e):Km(e)} +;function Cb(e){return function(e,t){Th().__VUE__=!0 +;const{insert:n,remove:r,patchProp:a,createElement:o,createText:i,createComment:s,setText:l,setElementText:c,parentNode:u,nextSibling:d,setScopeId:p=Qp,insertStaticContent:h}=e,f=(e,t,n,r=null,a=null,o=null,i=void 0,s=null,l=!!t.dynamicChildren)=>{ +if(e===t)return +;e&&!Gb(e,t)&&(r=Z(e),L(e,a,o,!0),e=null),-2===t.patchFlag&&(l=!1, +t.dynamicChildren=null);const{type:c,ref:u,shapeFlag:d}=t;switch(c){case Bb: +m(e,t,n,r);break;case jb:g(e,t,n,r);break;case Ub:null==e&&v(t,n,r,i);break +;case Lb:T(e,t,n,r,a,o,i,s,l);break;default: +1&d?O(e,t,n,r,a,o,i,s,l):6&d?E(e,t,n,r,a,o,i,s,l):(64&d||128&d)&&c.process(e,t,n,r,a,o,i,s,l,Q) +} +null!=u&&a?Xg(u,e&&e.ref,o,t||e,!t):null==u&&e&&null!=e.ref&&Xg(e.ref,null,o,e,!0) +},m=(e,t,r,a)=>{if(null==e)n(t.el=i(t.children),r,a);else{const n=t.el=e.el +;t.children!==e.children&&l(n,t.children)}},g=(e,t,r,a)=>{ +null==e?n(t.el=s(t.children||""),r,a):t.el=e.el},v=(e,t,n,r)=>{ +[e.el,e.anchor]=h(e.children,t,n,r,e.el,e.anchor)},b=({el:e,anchor:t},r,a)=>{ +let o;for(;e&&e!==t;)o=d(e),n(e,r,a),e=o;n(t,r,a)},y=({el:e,anchor:t})=>{let n +;for(;e&&e!==t;)n=d(e),r(e),e=n;r(t)},O=(e,t,n,r,a,o,i,s,l)=>{ +if("svg"===t.type?i="svg":"math"===t.type&&(i="mathml"), +null==e)w(t,n,r,a,o,i,s,l);else{const n=e.el&&e.el._isVueCE?e.el:null;try{ +n&&n._beginPatch(),S(e,t,a,o,i,s,l)}finally{n&&n._endPatch()}} +},w=(e,t,r,i,s,l,u,d)=>{let p,h;const{props:f,shapeFlag:m,transition:g,dirs:v}=e +;if(p=e.el=o(e.type,l,f&&f.is,f), +8&m?c(p,e.children):16&m&&k(e.children,p,null,i,s,$b(e,l),u,d), +v&&lg(e,null,i,"created"),x(p,e,e.scopeId,u,i),f){ +for(const e in f)"value"===e||hh(e)||a(p,e,null,f[e],l,i) +;"value"in f&&a(p,"value",null,f.value,l),(h=f.onVnodeBeforeMount)&&uy(h,i,e)} +v&&lg(e,null,i,"beforeMount");const b=function(e,t){ +return(!e||e&&!e.pendingBranch)&&t&&!t.persisted}(s,g) +;b&&g.beforeEnter(p),n(p,t,r),((h=f&&f.onVnodeMounted)||b||v)&&Eb((()=>{ +h&&uy(h,i,e),b&&g.enter(p),v&&lg(e,null,i,"mounted")}),s)},x=(e,t,n,r,a)=>{ +if(n&&p(e,n),r)for(let o=0;o{ +for(let c=l;c{const l=t.el=e.el +;let{patchFlag:u,dynamicChildren:d,dirs:p}=t;u|=16&e.patchFlag +;const h=e.props||Fp,f=t.props||Fp;let m +;if(n&&Pb(n,!1),(m=f.onVnodeBeforeUpdate)&&uy(m,n,t,e), +p&&lg(t,e,n,"beforeUpdate"), +n&&Pb(n,!0),(h.innerHTML&&null==f.innerHTML||h.textContent&&null==f.textContent)&&c(l,""), +d?_(e.dynamicChildren,d,l,n,r,$b(t,o),i):s||D(e,t,l,null,n,r,$b(t,o),i,!1),u>0){ +if(16&u)A(l,h,f,n,o);else if(2&u&&h.class!==f.class&&a(l,"class",null,f.class,o), +4&u&&a(l,"style",h.style,f.style,o),8&u){const e=t.dynamicProps +;for(let t=0;t{m&&uy(m,n,t,e),p&&lg(t,e,n,"updated")}),r) +},_=(e,t,n,r,a,o,i)=>{for(let s=0;s{if(t!==n){ +if(t!==Fp)for(const i in t)hh(i)||i in n||a(e,i,t[i],null,o,r) +;for(const i in n){if(hh(i))continue;const s=n[i],l=t[i] +;s!==l&&"value"!==i&&a(e,i,l,s,o,r)}"value"in n&&a(e,"value",t.value,n.value,o)} +},T=(e,t,r,a,o,s,l,c,u)=>{const d=t.el=e?e.el:i(""),p=t.anchor=e?e.anchor:i("") +;let{patchFlag:h,dynamicChildren:f,slotScopeIds:m}=t +;m&&(c=c?c.concat(m):m),null==e?(n(d,r,a), +n(p,r,a),k(t.children||[],r,p,o,s,l,c,u)):h>0&&64&h&&f&&e.dynamicChildren&&e.dynamicChildren.length===f.length?(_(e.dynamicChildren,f,r,o,s,l,c), +(null!=t.key||o&&t===o.subTree)&&Ib(e,t,!0)):D(e,t,r,p,o,s,l,c,u) +},E=(e,t,n,r,a,o,i,s,l)=>{ +t.slotScopeIds=s,null==e?512&t.shapeFlag?a.ctx.activate(t,n,r,i,l):C(t,n,r,a,o,i,l):$(e,t,l) +},C=(e,t,n,r,a,o,i)=>{const s=e.component=function(e,t,n){ +const r=e.type,a=(t?t.appContext:e.appContext)||dy,o={uid:py++,vnode:e,type:r, +parent:t,appContext:a,root:null,next:null,subTree:null,effect:null,update:null, +job:null,scope:new Vh(!0),render:null,proxy:null,exposed:null,exposeProxy:null, +withProxy:null,provides:t?t.provides:Object.create(a.provides), +ids:t?t.ids:["",0,0],accessCache:null,renderCache:[],components:null, +directives:null,propsOptions:bb(r,a),emitsOptions:ob(r,a),emit:null, +emitted:null,propsDefaults:Fp,inheritAttrs:r.inheritAttrs,ctx:Fp,data:Fp, +props:Fp,attrs:Fp,slots:Fp,refs:Fp,setupState:Fp,setupContext:null,suspense:n, +suspenseId:n?n.pendingId:0,asyncDep:null,asyncResolved:!1,isMounted:!1, +isUnmounted:!1,isDeactivated:!1,bc:null,c:null,bm:null,m:null,bu:null,u:null, +um:null,bum:null,da:null,a:null,rtg:null,rtc:null,ec:null,sp:null};o.ctx={_:o +},o.root=t?t.root:o,o.emit=rb.bind(null,o),e.ce&&e.ce(o);return o}(e,r,a) +;if(tv(e)&&(s.ctx.renderer=Q),function(e,t=!1,n=!1){t&&gy(t) +;const{props:r,children:a}=e.vnode,o=yy(e);fb(e,r,o,t),Ab(e,a,n||t) +;const i=o?function(e,t){const n=e.type +;e.accessCache=Object.create(null),e.proxy=new Proxy(e.ctx,Iv);const{setup:r}=n +;if(r){hf() +;const n=e.setupContext=r.length>1?Sy(e):null,a=vy(e),o=jm(r,e,0,[e.props,n]),i=sh(o) +;if(ff(),a(),!i&&!e.sp||Kg(e)||Vg(e),i){if(o.then(by,by),t)return o.then((t=>{ +wy(e,t)})).catch((t=>{zm(t,e,0)}));e.asyncDep=o}else wy(e,o)}else xy(e) +}(e,t):void 0;t&&gy(!1)}(s,!1,i),s.asyncDep){if(a&&a.registerDep(s,P,i),!e.el){ +const r=s.subTree=ey(jb);g(null,r,t,n),e.placeholder=r.el}}else P(s,e,t,n,a,o,i) +},$=(e,t,n)=>{const r=t.component=e.component;if(function(e,t,n){ +const{props:r,children:a,component:o}=e,{props:i,children:s,patchFlag:l}=t,c=o.emitsOptions +;if(t.dirs||t.transition)return!0 +;if(!(n&&l>=0))return!(!a&&!s||s&&s.$stable)||r!==i&&(r?!i||ub(r,i,c):!!i) +;if(1024&l)return!0;if(16&l)return r?ub(r,i,c):!!i;if(8&l){ +const e=t.dynamicProps;for(let t=0;t{const s=()=>{if(e.isMounted){ +let{next:t,bu:n,u:r,parent:l,vnode:c}=e;{const n=Db(e) +;if(n)return t&&(t.el=c.el,I(e,t,i)),void n.asyncDep.then((()=>{ +e.isUnmounted||s()}))}let d,p=t +;Pb(e,!1),t?(t.el=c.el,I(e,t,i)):t=c,n&&xh(n),(d=t.props&&t.props.onVnodeBeforeUpdate)&&uy(d,l,t,c), +Pb(e,!0);const h=sb(e),m=e.subTree +;e.subTree=h,f(m,h,u(m.el),Z(m),e,a,o),t.el=h.el, +null===p&&function({vnode:e,parent:t},n){for(;t;){const r=t.subTree +;if(r.suspense&&r.suspense.activeBranch===e&&(r.el=e.el),r!==e)break +;(e=t.vnode).el=n,t=t.parent} +}(e,h.el),r&&Eb(r,a),(d=t.props&&t.props.onVnodeUpdated)&&Eb((()=>uy(d,l,t,c)),a) +}else{let i;const{el:s,props:l}=t,{bm:c,m:u,parent:d,root:p,type:h}=e,m=Kg(t) +;Pb(e,!1),c&&xh(c),!m&&(i=l&&l.onVnodeBeforeMount)&&uy(i,d,t),Pb(e,!0);{ +p.ce&&!1!==p.ce._def.shadowRoot&&p.ce._injectChildStyle(h) +;const i=e.subTree=sb(e);f(null,i,n,r,e,a,o),t.el=i.el} +if(u&&Eb(u,a),!m&&(i=l&&l.onVnodeMounted)){const e=t;Eb((()=>uy(i,d,e)),a)} +(256&t.shapeFlag||d&&Kg(d.vnode)&&256&d.vnode.shapeFlag)&&e.a&&Eb(e.a,a), +e.isMounted=!0,t=n=r=null}};e.scope.on();const l=e.effect=new Yh(s) +;e.scope.off();const c=e.update=l.run.bind(l),d=e.job=l.runIfDirty.bind(l) +;d.i=e,d.id=e.uid,l.scheduler=()=>Gm(d),Pb(e,!0),c()},I=(e,t,n)=>{t.component=e +;const r=e.vnode.props;e.vnode=t,e.next=null,function(e,t,n,r){ +const{props:a,attrs:o,vnode:{patchFlag:i}}=e,s=hm(a),[l]=e.propsOptions;let c=!1 +;if(!(r||i>0)||16&i){let r;mb(e,t,a,o)&&(c=!0) +;for(const o in s)t&&(Kp(t,o)||(r=bh(o))!==o&&Kp(t,r))||(l?!n||void 0===n[o]&&void 0===n[r]||(a[o]=gb(l,s,o,void 0,e,!0)):delete a[o]) +;if(o!==s)for(const e in o)t&&Kp(t,e)||(delete o[e],c=!0)}else if(8&i){ +const n=e.vnode.dynamicProps;for(let r=0;r{ +const u=e&&e.children,d=e?e.shapeFlag:0,p=t.children,{patchFlag:h,shapeFlag:f}=t +;if(h>0){if(128&h)return void N(u,p,n,r,a,o,i,s,l) +;if(256&h)return void M(u,p,n,r,a,o,i,s,l)} +8&f?(16&d&&z(u,a,o),p!==u&&c(n,p)):16&d?16&f?N(u,p,n,r,a,o,i,s,l):z(u,a,o,!0):(8&d&&c(n,""), +16&f&&k(p,n,r,a,o,i,s,l))},M=(e,t,n,r,a,o,i,s,l)=>{t=t||Hp +;const c=(e=e||Hp).length,u=t.length,d=Math.min(c,u);let p;for(p=0;pu?z(e,a,o,!0,!1,d):k(t,n,r,a,o,i,s,l,d)},N=(e,t,n,r,a,o,i,s,l)=>{let c=0 +;const u=t.length;let d=e.length-1,p=u-1;for(;c<=d&&c<=p;){ +const r=e[c],u=t[c]=l?sy(t[c]):iy(t[c]);if(!Gb(r,u))break +;f(r,u,n,null,a,o,i,s,l),c++}for(;c<=d&&c<=p;){ +const r=e[d],c=t[p]=l?sy(t[p]):iy(t[p]);if(!Gb(r,c))break +;f(r,c,n,null,a,o,i,s,l),d--,p--}if(c>d){if(c<=p){const e=p+1,d=ep)for(;c<=d;)L(e[c],a,o,!0),c++;else{const h=c,m=c,g=new Map +;for(c=m;c<=p;c++){const e=t[c]=l?sy(t[c]):iy(t[c]);null!=e.key&&g.set(e.key,c)} +let v,b=0;const y=p-m+1;let O=!1,w=0;const x=new Array(y);for(c=0;c=y){L(r,a,o,!0);continue}let u +;if(null!=r.key)u=g.get(r.key);else for(v=m;v<=p;v++)if(0===x[v-m]&&Gb(r,t[v])){ +u=v;break} +void 0===u?L(r,a,o,!0):(x[u-m]=c+1,u>=w?w=u:O=!0,f(r,t[u],n,null,a,o,i,s,l),b++) +}const k=O?function(e){const t=e.slice(),n=[0];let r,a,o,i,s;const l=e.length +;for(r=0;r>1,e[n[s]]0&&(t[r]=n[o-1]),n[o]=r)}}o=n.length,i=n[o-1] +;for(;o-- >0;)n[o]=i,i=t[i];return n}(x):Hp;for(v=k.length-1,c=y-1;c>=0;c--){ +const e=m+c,d=t[e],p=t[e+1],h=e+1{const{el:s,type:l,transition:c,children:u,shapeFlag:d}=e +;if(6&d)return void R(e.component.subTree,t,a,o) +;if(128&d)return void e.suspense.move(t,a,o);if(64&d)return void l.move(e,t,a,Q) +;if(l===Lb){n(s,t,a);for(let e=0;ec.enter(s)),i);else{ +const{leave:o,delayLeave:i,afterLeave:l}=c,u=()=>{ +e.ctx.isUnmounted?r(s):n(s,t,a)},d=()=>{s._isLeaving&&s[Pg](!0),o(s,(()=>{ +u(),l&&l()}))};i?i(s,u,d):d()}else n(s,t,a)},L=(e,t,n,r=!1,a=!1)=>{ +const{type:o,props:i,ref:s,children:l,dynamicChildren:c,shapeFlag:u,patchFlag:d,dirs:p,cacheIndex:h}=e +;if(-2===d&&(a=!1), +null!=s&&(hf(),Xg(s,null,n,e,!0),ff()),null!=h&&(t.renderCache[h]=void 0), +256&u)return void t.ctx.deactivate(e);const f=1&u&&p,m=!Kg(e);let g +;if(m&&(g=i&&i.onVnodeBeforeUnmount)&&uy(g,t,e),6&u)U(e.component,n,r);else{ +if(128&u)return void e.suspense.unmount(n,r) +;f&&lg(e,null,t,"beforeUnmount"),64&u?e.type.remove(e,t,n,Q,r):c&&!c.hasOnce&&(o!==Lb||d>0&&64&d)?z(c,t,n,!1,!0):(o===Lb&&384&d||!a&&16&u)&&z(l,t,n), +r&&B(e)}(m&&(g=i&&i.onVnodeUnmounted)||f)&&Eb((()=>{ +g&&uy(g,t,e),f&&lg(e,null,t,"unmounted")}),n)},B=e=>{ +const{type:t,el:n,anchor:a,transition:o}=e;if(t===Lb)return void j(n,a) +;if(t===Ub)return void y(e);const i=()=>{ +r(n),o&&!o.persisted&&o.afterLeave&&o.afterLeave()} +;if(1&e.shapeFlag&&o&&!o.persisted){const{leave:t,delayLeave:r}=o,a=()=>t(n,i) +;r?r(e.el,i,a):a()}else i()},j=(e,t)=>{let n;for(;e!==t;)n=d(e),r(e),e=n;r(t) +},U=(e,t,n)=>{const{bum:r,scope:a,job:o,subTree:i,um:s,m:l,a:c}=e +;Mb(l),Mb(c),r&&xh(r),a.stop(),o&&(o.flags|=8,L(i,e,t,n)),s&&Eb(s,t),Eb((()=>{ +e.isUnmounted=!0}),t)},z=(e,t,n,r=!1,a=!1,o=0)=>{ +for(let i=o;i{ +if(6&e.shapeFlag)return Z(e.component.subTree) +;if(128&e.shapeFlag)return e.suspense.next() +;const t=d(e.anchor||e.el),n=t&&t[yg];return n?d(n):t};let F=!1 +;const H=(e,t,n)=>{let r +;null==e?t._vnode&&(L(t._vnode,null,null,!0),r=t._vnode.component):f(t._vnode||null,e,t,null,null,null,n), +t._vnode=e,F||(F=!0,Jm(r),eg(),F=!1)},Q={p:f,um:L,m:R,r:B,mt:C,mc:k,pc:D,pbc:_, +n:Z,o:e};let V;return{render:H,hydrate:V,createApp:Jv(H)}}(e)} +function $b({type:e,props:t},n){ +return"svg"===n&&"foreignObject"===e||"mathml"===n&&"annotation-xml"===e&&t&&t.encoding&&t.encoding.includes("html")?void 0:n +}function Pb({effect:e,job:t},n){ +n?(e.flags|=32,t.flags|=4):(e.flags&=-33,t.flags&=-5)}function Ib(e,t,n=!1){ +const r=e.children,a=t.children;if(Jp(r)&&Jp(a))for(let o=0;oe.__isSuspense +;const Lb=Symbol.for("v-fgt"),Bb=Symbol.for("v-txt"),jb=Symbol.for("v-cmt"),Ub=Symbol.for("v-stc"),zb=[] +;let Zb=null;function Fb(e=!1){zb.push(Zb=e?null:[])}let Hb=1 +;function Qb(e,t=!1){Hb+=e,e<0&&Zb&&t&&(Zb.hasOnce=!0)}function Vb(e){ +return e.dynamicChildren=Hb>0?Zb||Hp:null, +zb.pop(),Zb=zb[zb.length-1]||null,Hb>0&&Zb&&Zb.push(e),e} +function qb(e,t,n,r,a,o){return Vb(Jb(e,t,n,r,a,o,!0))}function Wb(e,t,n,r,a){ +return Vb(ey(e,t,n,r,a,!0))}function Xb(e){return!!e&&!0===e.__v_isVNode} +function Gb(e,t){return e.type===t.type&&e.key===t.key} +const Yb=({key:e})=>null!=e?e:null,Kb=({ref:e,ref_key:t,ref_for:n})=>("number"==typeof e&&(e=""+e), +null!=e?ah(e)||vm(e)||rh(e)?{i:rg,r:e,k:t,f:!!n}:e:null) +;function Jb(e,t=null,n=null,r=0,a=null,o=(e===Lb?0:1),i=!1,s=!1){const l={ +__v_isVNode:!0,__v_skip:!0,type:e,props:t,key:t&&Yb(t),ref:t&&Kb(t),scopeId:ag, +slotScopeIds:null,children:n,component:null,suspense:null,ssContent:null, +ssFallback:null,dirs:null,transition:null,el:null,anchor:null,target:null, +targetStart:null,targetAnchor:null,staticCount:0,shapeFlag:o,patchFlag:r, +dynamicProps:a,dynamicChildren:null,appContext:null,ctx:rg} +;return s?(ly(l,n),128&o&&e.normalize(l)):n&&(l.shapeFlag|=ah(n)?8:16), +Hb>0&&!i&&Zb&&(l.patchFlag>0||6&o)&&32!==l.patchFlag&&Zb.push(l),l} +const ey=function(e,t=null,n=null,r=0,a=null,o=!1){e&&e!==Ov||(e=jb);if(Xb(e)){ +const r=ny(e,t,!0) +;return n&&ly(r,n),Hb>0&&!o&&Zb&&(6&r.shapeFlag?Zb[Zb.indexOf(e)]=r:Zb.push(r)), +r.patchFlag=-2,r}i=e,rh(i)&&"__vccOpts"in i&&(e=e.__vccOpts);var i;if(t){t=ty(t) +;let{class:e,style:n}=t +;e&&!ah(e)&&(t.class=Dh(e)),ih(n)&&(pm(n)&&!Jp(n)&&(n=Xp({},n)),t.style=Eh(n))} +const s=ah(e)?1:Rb(e)?128:Og(e)?64:ih(e)?4:rh(e)?2:0;return Jb(e,t,n,r,a,s,o,!0) +};function ty(e){return e?pm(e)||hb(e)?Xp({},e):e:null} +function ny(e,t,n=!1,r=!1){ +const{props:a,ref:o,patchFlag:i,children:s,transition:l}=e,c=t?cy(a||{},t):a,u={ +__v_isVNode:!0,__v_skip:!0,type:e.type,props:c,key:c&&Yb(c), +ref:t&&t.ref?n&&o?Jp(o)?o.concat(Kb(t)):[o,Kb(t)]:Kb(t):o,scopeId:e.scopeId, +slotScopeIds:e.slotScopeIds,children:s,target:e.target, +targetStart:e.targetStart,targetAnchor:e.targetAnchor,staticCount:e.staticCount, +shapeFlag:e.shapeFlag,patchFlag:t&&e.type!==Lb?-1===i?16:16|i:i, +dynamicProps:e.dynamicProps,dynamicChildren:e.dynamicChildren, +appContext:e.appContext,dirs:e.dirs,transition:l,component:e.component, +suspense:e.suspense,ssContent:e.ssContent&&ny(e.ssContent), +ssFallback:e.ssFallback&&ny(e.ssFallback),placeholder:e.placeholder,el:e.el, +anchor:e.anchor,ctx:e.ctx,ce:e.ce};return l&&r&&Zg(u,l.clone(u)),u} +function ry(e=" ",t=0){return ey(Bb,null,e,t)}function ay(e,t){ +const n=ey(Ub,null,e);return n.staticCount=t,n}function oy(e="",t=!1){ +return t?(Fb(),Wb(jb,null,e)):ey(jb,null,e)}function iy(e){ +return null==e||"boolean"==typeof e?ey(jb):Jp(e)?ey(Lb,null,e.slice()):Xb(e)?sy(e):ey(Bb,null,String(e)) +}function sy(e){return null===e.el&&-1!==e.patchFlag||e.memo?e:ny(e)} +function ly(e,t){let n=0;const{shapeFlag:r}=e +;if(null==t)t=null;else if(Jp(t))n=16;else if("object"==typeof t){if(65&r){ +const n=t.default;return void(n&&(n._c&&(n._d=!1),ly(e,n()),n._c&&(n._d=!0)))}{ +n=32;const r=t._ +;r||hb(t)?3===r&&rg&&(1===rg.slots._?t._=1:(t._=2,e.patchFlag|=1024)):t._ctx=rg} +}else rh(t)?(t={default:t,_ctx:rg},n=32):(t=String(t),64&r?(n=16,t=[ry(t)]):n=8) +;e.children=t,e.shapeFlag|=n}function cy(...e){const t={} +;for(let n=0;nhy||rg;let my,gy;{ +const e=Th(),t=(t,n)=>{let r;return(r=e[t])||(r=e[t]=[]),r.push(n),e=>{ +r.length>1?r.forEach((t=>t(e))):r[0](e)}} +;my=t("__VUE_INSTANCE_SETTERS__",(e=>hy=e)), +gy=t("__VUE_SSR_SETTERS__",(e=>Oy=e))}const vy=e=>{const t=hy +;return my(e),e.scope.on(),()=>{e.scope.off(),my(t)}},by=()=>{ +hy&&hy.scope.off(),my(null)};function yy(e){return 4&e.vnode.shapeFlag}let Oy=!1 +;function wy(e,t,n){ +rh(t)?e.type.__ssrInlineRender?e.ssrRender=t:e.render=t:ih(t)&&(e.setupState=_m(t)), +xy(e)}function xy(e,t,n){const r=e.type;e.render||(e.render=r.render||Qp);{ +const t=vy(e);hf();try{Uv(e)}finally{ff(),t()}}}const ky={ +get:(e,t)=>(Sf(e,0,""),e[t])};function Sy(e){const t=t=>{e.exposed=t||{}} +;return{attrs:new Proxy(e.attrs,ky),slots:e.slots,emit:e.emit,expose:t}} +function _y(e){ +return e.exposed?e.exposeProxy||(e.exposeProxy=new Proxy(_m(fm(e.exposed)),{ +get:(t,n)=>n in t?t[n]:n in $v?$v[n](e):void 0,has:(e,t)=>t in e||t in $v +})):e.proxy}function Ay(e,t=!0){ +return rh(e)?e.displayName||e.name:e.name||t&&e.__name}const Ty=(e,t)=>{ +const n=function(e,t,n=!1){let r,a +;return rh(e)?r=e:(r=e.get,a=e.set),new Dm(r,a,n)}(e,0,Oy);return n} +;function Ey(e,t,n){try{Qb(-1);const r=arguments.length +;return 2===r?ih(t)&&!Jp(t)?Xb(t)?ey(e,null,[t]):ey(e,t):ey(e,null,t):(r>3?n=Array.prototype.slice.call(arguments,2):3===r&&Xb(n)&&(n=[n]), +ey(e,t,n))}finally{Qb(1)}}const Cy="3.5.26";let $y +;const Py="undefined"!=typeof window&&window.trustedTypes;if(Py)try{ +$y=Py.createPolicy("vue",{createHTML:e=>e})}catch(zw){} +const Iy=$y?e=>$y.createHTML(e):e=>e,Dy="undefined"!=typeof document?document:null,My=Dy&&Dy.createElement("template"),Ny={ +insert:(e,t,n)=>{t.insertBefore(e,n||null)},remove:e=>{const t=e.parentNode +;t&&t.removeChild(e)},createElement:(e,t,n,r)=>{ +const a="svg"===t?Dy.createElementNS("http://www.w3.org/2000/svg",e):"mathml"===t?Dy.createElementNS("http://www.w3.org/1998/Math/MathML",e):n?Dy.createElement(e,{ +is:n}):Dy.createElement(e) +;return"select"===e&&r&&null!=r.multiple&&a.setAttribute("multiple",r.multiple), +a},createText:e=>Dy.createTextNode(e),createComment:e=>Dy.createComment(e), +setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t}, +parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling, +querySelector:e=>Dy.querySelector(e),setScopeId(e,t){e.setAttribute(t,"")}, +insertStaticContent(e,t,n,r,a,o){const i=n?n.previousSibling:t.lastChild +;if(a&&(a===o||a.nextSibling))for(;t.insertBefore(a.cloneNode(!0),n), +a!==o&&(a=a.nextSibling););else{ +My.innerHTML=Iy("svg"===r?`${e}`:"mathml"===r?`${e}`:e) +;const a=My.content;if("svg"===r||"mathml"===r){const e=a.firstChild +;for(;e.firstChild;)a.appendChild(e.firstChild);a.removeChild(e)} +t.insertBefore(a,n)} +return[i?i.nextSibling:t.firstChild,n?n.previousSibling:t.lastChild]} +},Ry="transition",Ly="animation",By=Symbol("_vtc"),jy={name:String,type:String, +css:{type:Boolean,default:!0},duration:[String,Number,Object], +enterFromClass:String,enterActiveClass:String,enterToClass:String, +appearFromClass:String,appearActiveClass:String,appearToClass:String, +leaveFromClass:String,leaveActiveClass:String,leaveToClass:String +},Uy=Xp({},Mg,jy),zy=e=>(e.displayName="Transition", +e.props=Uy,e),Zy=zy(((e,{slots:t})=>Ey(Lg,function(e){const t={} +;for(const E in e)E in jy||(t[E]=e[E]);if(!1===e.css)return t +;const{name:n="v",type:r,duration:a,enterFromClass:o=`${n}-enter-from`,enterActiveClass:i=`${n}-enter-active`,enterToClass:s=`${n}-enter-to`,appearFromClass:l=o,appearActiveClass:c=i,appearToClass:u=s,leaveFromClass:d=`${n}-leave-from`,leaveActiveClass:p=`${n}-leave-active`,leaveToClass:h=`${n}-leave-to`}=e,f=function(e){ +if(null==e)return null;if(ih(e))return[Qy(e.enter),Qy(e.leave)];{const t=Qy(e) +;return[t,t]} +}(a),m=f&&f[0],g=f&&f[1],{onBeforeEnter:v,onEnter:b,onEnterCancelled:y,onLeave:O,onLeaveCancelled:w,onBeforeAppear:x=v,onAppear:k=b,onAppearCancelled:S=y}=t,_=(e,t,n,r)=>{ +e._enterCancelled=r,qy(e,t?u:s),qy(e,t?c:i),n&&n()},A=(e,t)=>{ +e._isLeaving=!1,qy(e,d),qy(e,h),qy(e,p),t&&t()},T=e=>(t,n)=>{ +const a=e?k:b,i=()=>_(t,e,n);Fy(a,[t,i]),Wy((()=>{ +qy(t,e?l:o),Vy(t,e?u:s),Hy(a)||Gy(t,r,m,i)}))};return Xp(t,{onBeforeEnter(e){ +Fy(v,[e]),Vy(e,o),Vy(e,i)},onBeforeAppear(e){Fy(x,[e]),Vy(e,l),Vy(e,c)}, +onEnter:T(!1),onAppear:T(!0),onLeave(e,t){e._isLeaving=!0;const n=()=>A(e,t) +;Vy(e,d),e._enterCancelled?(Vy(e,p),Jy(e)):(Jy(e),Vy(e,p)),Wy((()=>{ +e._isLeaving&&(qy(e,d),Vy(e,h),Hy(O)||Gy(e,r,g,n))})),Fy(O,[e,n])}, +onEnterCancelled(e){_(e,!1,void 0,!0),Fy(y,[e])},onAppearCancelled(e){ +_(e,!0,void 0,!0),Fy(S,[e])},onLeaveCancelled(e){A(e),Fy(w,[e])}}) +}(e),t))),Fy=(e,t=[])=>{Jp(e)?e.forEach((e=>e(...t))):e&&e(...t) +},Hy=e=>!!e&&(Jp(e)?e.some((e=>e.length>1)):e.length>1);function Qy(e){ +return _h(e)}function Vy(e,t){ +t.split(/\s+/).forEach((t=>t&&e.classList.add(t))), +(e[By]||(e[By]=new Set)).add(t)}function qy(e,t){ +t.split(/\s+/).forEach((t=>t&&e.classList.remove(t)));const n=e[By] +;n&&(n.delete(t),n.size||(e[By]=void 0))}function Wy(e){ +requestAnimationFrame((()=>{requestAnimationFrame(e)}))}let Xy=0 +;function Gy(e,t,n,r){const a=e._endId=++Xy,o=()=>{a===e._endId&&r()} +;if(null!=n)return setTimeout(o,n) +;const{type:i,timeout:s,propCount:l}=function(e,t){ +const n=window.getComputedStyle(e),r=e=>(n[e]||"").split(", "),a=r(`${Ry}Delay`),o=r(`${Ry}Duration`),i=Yy(a,o),s=r(`${Ly}Delay`),l=r(`${Ly}Duration`),c=Yy(s,l) +;let u=null,d=0,p=0 +;t===Ry?i>0&&(u=Ry,d=i,p=o.length):t===Ly?c>0&&(u=Ly,d=c,p=l.length):(d=Math.max(i,c), +u=d>0?i>c?Ry:Ly:null,p=u?u===Ry?o.length:l.length:0) +;const h=u===Ry&&/\b(?:transform|all)(?:,|$)/.test(r(`${Ry}Property`).toString()) +;return{type:u,timeout:d,propCount:p,hasTransform:h}}(e,t);if(!i)return r() +;const c=i+"end";let u=0;const d=()=>{e.removeEventListener(c,p),o()},p=t=>{ +t.target===e&&++u>=l&&d()};setTimeout((()=>{uKy(t)+Ky(e[n]))))}function Ky(e){ +return"auto"===e?0:1e3*Number(e.slice(0,-1).replace(",","."))}function Jy(e){ +return(e?e.ownerDocument:document).body.offsetHeight} +const eO=Symbol("_vod"),tO=Symbol("_vsh"),nO={name:"show", +beforeMount(e,{value:t},{transition:n}){ +e[eO]="none"===e.style.display?"":e.style.display,n&&t?n.beforeEnter(e):rO(e,t) +},mounted(e,{value:t},{transition:n}){n&&t&&n.enter(e)}, +updated(e,{value:t,oldValue:n},{transition:r}){ +!t!=!n&&(r?t?(r.beforeEnter(e),rO(e,!0),r.enter(e)):r.leave(e,(()=>{rO(e,!1) +})):rO(e,t))},beforeUnmount(e,{value:t}){rO(e,t)}};function rO(e,t){ +e.style.display=t?e[eO]:"none",e[tO]=!t}const aO=Symbol("");function oO(e){ +const t=fy();if(!t)return;const n=t.ut=(n=e(t.proxy))=>{ +Array.from(document.querySelectorAll(`[data-v-owner="${t.uid}"]`)).forEach((e=>sO(e,n))) +},r=()=>{const r=e(t.proxy);t.ce?sO(t.ce,r):iO(t.subTree,r),n(r)};uv((()=>{Km(r) +})),cv((()=>{mg(r,Qp,{flush:"post"});const e=new MutationObserver(r) +;e.observe(t.subTree.el.parentNode,{childList:!0}),hv((()=>e.disconnect()))}))} +function iO(e,t){if(128&e.shapeFlag){const n=e.suspense +;e=n.activeBranch,n.pendingBranch&&!n.isHydrating&&n.effects.push((()=>{ +iO(n.activeBranch,t)}))}for(;e.component;)e=e.component.subTree +;if(1&e.shapeFlag&&e.el)sO(e.el,t);else if(e.type===Lb)e.children.forEach((e=>iO(e,t)));else if(e.type===Ub){ +let{el:n,anchor:r}=e;for(;n&&(sO(n,t),n!==r);)n=n.nextSibling}}function sO(e,t){ +if(1===e.nodeType){const n=e.style;let r="";for(const e in t){const a=Fh(t[e]) +;n.setProperty(`--${e}`,a),r+=`--${e}: ${a};`}n[aO]=r}} +const lO=/(?:^|;)\s*display\s*:/;const cO=/\s*!important$/;function uO(e,t,n){ +if(Jp(n))n.forEach((n=>uO(e,t,n)));else if(null==n&&(n=""), +t.startsWith("--"))e.setProperty(t,n);else{const r=function(e,t){const n=pO[t] +;if(n)return n;let r=gh(t);if("filter"!==r&&r in e)return pO[t]=r;r=yh(r) +;for(let a=0;a{if(e._vts){ +if(e._vts<=n.attached)return}else e._vts=Date.now();Um(function(e,t){if(Jp(t)){ +const n=e.stopImmediatePropagation;return e.stopImmediatePropagation=()=>{ +n.call(e),e._stopped=!0},t.map((e=>t=>!t._stopped&&e&&e(t)))}return t +}(e,n.value),t,5,[e])};return n.value=e,n.attached=xO(),n}(r,a);gO(e,n,i,s) +}else i&&(!function(e,t,n,r){e.removeEventListener(t,n,r)}(e,n,i,s),o[t]=void 0) +}}const yO=/(?:Once|Passive|Capture)$/;let OO=0 +;const wO=Promise.resolve(),xO=()=>OO||(wO.then((()=>OO=0)),OO=Date.now()) +;const kO=e=>111===e.charCodeAt(0)&&110===e.charCodeAt(1)&&e.charCodeAt(2)>96&&e.charCodeAt(2)<123 +;const SO=e=>{const t=e.props["onUpdate:modelValue"]||!1 +;return Jp(t)?e=>xh(t,e):t};function _O(e){e.target.composing=!0}function AO(e){ +const t=e.target +;t.composing&&(t.composing=!1,t.dispatchEvent(new Event("input")))} +const TO=Symbol("_assign");function EO(e,t,n){ +return t&&(e=e.trim()),n&&(e=Sh(e)),e}const CO={ +created(e,{modifiers:{lazy:t,trim:n,number:r}},a){e[TO]=SO(a) +;const o=r||a.props&&"number"===a.props.type;gO(e,t?"change":"input",(t=>{ +t.target.composing||e[TO](EO(e.value,n,o))})),(n||o)&&gO(e,"change",(()=>{ +e.value=EO(e.value,n,o) +})),t||(gO(e,"compositionstart",_O),gO(e,"compositionend",AO),gO(e,"change",AO)) +},mounted(e,{value:t}){e.value=null==t?"":t}, +beforeUpdate(e,{value:t,oldValue:n,modifiers:{lazy:r,trim:a,number:o}},i){ +if(e[TO]=SO(i),e.composing)return;const s=null==t?"":t +;if((!o&&"number"!==e.type||/^0\d/.test(e.value)?e.value:Sh(e.value))!==s){ +if(document.activeElement===e&&"range"!==e.type){if(r&&t===n)return +;if(a&&e.value.trim()===s)return}e.value=s}}},$O={deep:!0,created(e,t,n){ +e[TO]=SO(n),gO(e,"change",(()=>{ +const t=e._modelValue,n=NO(e),r=e.checked,a=e[TO];if(Jp(t)){ +const e=Bh(t,n),o=-1!==e;if(r&&!o)a(t.concat(n));else if(!r&&o){const n=[...t] +;n.splice(e,1),a(n)}}else if(th(t)){const e=new Set(t) +;r?e.add(n):e.delete(n),a(e)}else a(RO(e,r))}))},mounted:PO,beforeUpdate(e,t,n){ +e[TO]=SO(n),PO(e,t,n)}};function PO(e,{value:t,oldValue:n},r){let a +;if(e._modelValue=t, +Jp(t))a=Bh(t,r.props.value)>-1;else if(th(t))a=t.has(r.props.value);else{ +if(t===n)return;a=Lh(t,RO(e,!0))}e.checked!==a&&(e.checked=a)}const IO={ +created(e,{value:t},n){ +e.checked=Lh(t,n.props.value),e[TO]=SO(n),gO(e,"change",(()=>{e[TO](NO(e))}))}, +beforeUpdate(e,{value:t,oldValue:n},r){ +e[TO]=SO(r),t!==n&&(e.checked=Lh(t,r.props.value))}},DO={deep:!0, +created(e,{value:t,modifiers:{number:n}},r){const a=th(t);gO(e,"change",(()=>{ +const t=Array.prototype.filter.call(e.options,(e=>e.selected)).map((e=>n?Sh(NO(e)):NO(e))) +;e[TO](e.multiple?a?new Set(t):t:t[0]),e._assigning=!0,Xm((()=>{e._assigning=!1 +}))})),e[TO]=SO(r)},mounted(e,{value:t}){MO(e,t)},beforeUpdate(e,t,n){ +e[TO]=SO(n)},updated(e,{value:t}){e._assigning||MO(e,t)}};function MO(e,t){ +const n=e.multiple,r=Jp(t);if(!n||r||th(t)){ +for(let a=0,o=e.options.length;aString(e)===String(i))):Bh(t,i)>-1 +}else o.selected=t.has(i);else if(Lh(NO(o),t))return void(e.selectedIndex!==a&&(e.selectedIndex=a)) +}n||-1===e.selectedIndex||(e.selectedIndex=-1)}}function NO(e){ +return"_value"in e?e._value:e.value}function RO(e,t){ +const n=t?"_trueValue":"_falseValue";return n in e?e[n]:t}const LO={ +created(e,t,n){BO(e,t,n,null,"created")},mounted(e,t,n){BO(e,t,n,null,"mounted") +},beforeUpdate(e,t,n,r){BO(e,t,n,r,"beforeUpdate")},updated(e,t,n,r){ +BO(e,t,n,r,"updated")}};function BO(e,t,n,r,a){const o=function(e,t){switch(e){ +case"SELECT":return DO;case"TEXTAREA":return CO;default:switch(t){ +case"checkbox":return $O;case"radio":return IO;default:return CO}} +}(e.tagName,n.props&&n.props.type)[a];o&&o(e,t,n,r)} +const jO=["ctrl","shift","alt","meta"],UO={stop:e=>e.stopPropagation(), +prevent:e=>e.preventDefault(),self:e=>e.target!==e.currentTarget, +ctrl:e=>!e.ctrlKey,shift:e=>!e.shiftKey,alt:e=>!e.altKey,meta:e=>!e.metaKey, +left:e=>"button"in e&&0!==e.button,middle:e=>"button"in e&&1!==e.button, +right:e=>"button"in e&&2!==e.button, +exact:(e,t)=>jO.some((n=>e[`${n}Key`]&&!t.includes(n)))},zO=(e,t)=>{ +const n=e._withMods||(e._withMods={}),r=t.join(".") +;return n[r]||(n[r]=(n,...r)=>{for(let e=0;e{ +const n=e._withKeys||(e._withKeys={}),r=t.join(".");return n[r]||(n[r]=n=>{ +if(!("key"in n))return;const r=bh(n.key) +;return t.some((e=>e===r||ZO[e]===r))?e(n):void 0})},HO=Xp({ +patchProp:(e,t,n,r,a,o)=>{const i="svg"===a;"class"===t?function(e,t,n){ +const r=e[By] +;r&&(t=(t?[t,...r]:[...r]).join(" ")),null==t?e.removeAttribute("class"):n?e.setAttribute("class",t):e.className=t +}(e,r,i):"style"===t?function(e,t,n){const r=e.style,a=ah(n);let o=!1;if(n&&!a){ +if(t)if(ah(t))for(const e of t.split(";")){ +const t=e.slice(0,e.indexOf(":")).trim();null==n[t]&&uO(r,t,"") +}else for(const e in t)null==n[e]&&uO(r,e,"") +;for(const e in n)"display"===e&&(o=!0),uO(r,e,n[e])}else if(a){if(t!==n){ +const e=r[aO];e&&(n+=";"+e),r.cssText=n,o=lO.test(n)} +}else t&&e.removeAttribute("style") +;eO in e&&(e[eO]=o?r.display:"",e[tO]&&(r.display="none")) +}(e,n,r):qp(t)?Wp(t)||bO(e,t,0,r,o):("."===t[0]?(t=t.slice(1), +1):"^"===t[0]?(t=t.slice(1),0):function(e,t,n,r){ +if(r)return"innerHTML"===t||"textContent"===t||!!(t in e&&kO(t)&&rh(n)) +;if("spellcheck"===t||"draggable"===t||"translate"===t||"autocorrect"===t)return!1 +;if("sandbox"===t&&"IFRAME"===e.tagName)return!1;if("form"===t)return!1 +;if("list"===t&&"INPUT"===e.tagName)return!1 +;if("type"===t&&"TEXTAREA"===e.tagName)return!1;if("width"===t||"height"===t){ +const t=e.tagName;if("IMG"===t||"VIDEO"===t||"CANVAS"===t||"SOURCE"===t)return!1 +}if(kO(t)&&ah(n))return!1;return t in e +}(e,t,r,i))?(mO(e,t,r),e.tagName.includes("-")||"value"!==t&&"checked"!==t&&"selected"!==t||fO(e,t,r,i,0,"value"!==t)):!e._isVueCE||!/[A-Z]/.test(t)&&ah(r)?("true-value"===t?e._trueValue=r:"false-value"===t&&(e._falseValue=r), +fO(e,t,r,i)):mO(e,gh(t),r,0,t)}},Ny);let QO;function VO(){return QO||(QO=Cb(HO)) +}const qO=(...e)=>{VO().render(...e)},WO=(...e)=>{ +const t=VO().createApp(...e),{mount:n}=t;return t.mount=e=>{const r=function(e){ +if(ah(e)){return document.querySelector(e)}return e}(e);if(!r)return +;const a=t._component +;rh(a)||a.render||a.template||(a.template=r.innerHTML),1===r.nodeType&&(r.textContent="") +;const o=n(r,!1,function(e){if(e instanceof SVGElement)return"svg" +;if("function"==typeof MathMLElement&&e instanceof MathMLElement)return"mathml" +}(r)) +;return r instanceof Element&&(r.removeAttribute("v-cloak"),r.setAttribute("data-v-app","")), +o},t};const XO="3"===Cy[0];function GO(e){ +if(e instanceof Promise||e instanceof Date||e instanceof RegExp)return e +;const t="function"==typeof(n=e)?n():xm(n);var n;if(!e||!t)return t +;if(Array.isArray(t))return t.map((e=>GO(e)));if("object"==typeof t){const e={} +;for(const n in t)Object.prototype.hasOwnProperty.call(t,n)&&("titleTemplate"===n||"o"===n[0]&&"n"===n[1]?e[n]=xm(t[n]):e[n]=GO(t[n])) +;return e}return t}const YO={hooks:{"entries:resolve":e=>{ +for(const t of e.entries)t.resolvedInput=GO(t.input)}}},KO="usehead" +;function JO(e={}){ +e.domDelayFn=e.domDelayFn||(e=>Xm((()=>setTimeout((()=>e()),0))));const t=Up(e) +;return t.use(YO),t.install=function(e){return{install(t){ +XO&&(t.config.globalProperties.$unhead=e, +t.config.globalProperties.$head=e,t.provide(KO,e))}}.install}(t),t} +const ew="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{},tw="__unhead_injection_handler__" +;function nw(){if(tw in ew)return ew[tw]();return ug(KO)||jp} +function rw(e,t={}){const n=t.head||nw() +;if(n)return n.ssr?n.push(e,t):function(e,t,n={}){const r=bm(!1),a=bm({}) +;fg((()=>{a.value=r.value?{}:GO(t)}));const o=e.push(a.value,n);mg(a,(e=>{ +o.patch(e)}));fy()&&(pv((()=>{o.dispose()})),rv((()=>{r.value=!0})),nv((()=>{ +r.value=!1})));return o}(n,e,t)}function aw(e){ +"function"==typeof queueMicrotask?queueMicrotask(e):Promise.resolve().then(e).catch((e=>setTimeout((()=>{ +throw e}))))}function ow(){let e=[],t={ +addEventListener:(e,n,r,a)=>(e.addEventListener(n,r,a), +t.add((()=>e.removeEventListener(n,r,a)))),requestAnimationFrame(...e){ +let n=requestAnimationFrame(...e);t.add((()=>cancelAnimationFrame(n)))}, +nextFrame(...e){t.requestAnimationFrame((()=>{t.requestAnimationFrame(...e)}))}, +setTimeout(...e){let n=setTimeout(...e);t.add((()=>clearTimeout(n)))}, +microTask(...e){let n={current:!0};return aw((()=>{n.current&&e[0]() +})),t.add((()=>{n.current=!1}))},style(e,t,n){let r=e.style.getPropertyValue(t) +;return Object.assign(e.style,{[t]:n}),this.add((()=>{Object.assign(e.style,{ +[t]:r})}))},group(e){let t=ow();return e(t),this.add((()=>t.dispose()))}, +add:t=>(e.push(t),()=>{let n=e.indexOf(t);if(n>=0)for(let t of e.splice(n,1))t() +}),dispose(){for(let t of e.splice(0))t()}};return t}var iw +;let sw=Symbol("headlessui.useid"),lw=0;const cw=null!=(iw=Qg)?iw:function(){ +return ug(sw,(()=>""+ ++lw))()};function uw(e){var t +;if(null==e||null==e.value)return null;let n=null!=(t=e.value.$el)?t:e.value +;return n instanceof Node?n:null}function dw(e,t,...n){if(e in t){let r=t[e] +;return"function"==typeof r?r(...n):r} +let r=new Error(`Tried to handle "${e}" but there is no handler defined. Only defined handlers are: ${Object.keys(t).map((e=>`"${e}"`)).join(", ")}.`) +;throw Error.captureStackTrace&&Error.captureStackTrace(r,dw),r} +var pw=Object.defineProperty,hw=(e,t,n)=>(((e,t,n)=>{t in e?pw(e,t,{ +enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n +})(e,"symbol"!=typeof t?t+"":t,n),n);let fw=new class{constructor(){ +hw(this,"current",this.detect()),hw(this,"currentId",0)}set(e){ +this.current!==e&&(this.currentId=0,this.current=e)}reset(){ +this.set(this.detect())}nextId(){return++this.currentId}get isServer(){ +return"server"===this.current}get isClient(){return"client"===this.current} +detect(){ +return"undefined"==typeof window||"undefined"==typeof document?"server":"client" +}};function mw(e){if(fw.isServer)return null +;if(e instanceof Node)return e.ownerDocument +;if(null!=e&&e.hasOwnProperty("value")){let t=uw(e);if(t)return t.ownerDocument} +return document} +let gw=["[contentEditable=true]","[tabindex]","a[href]","area[href]","button:not([disabled])","iframe","input:not([disabled])","select:not([disabled])","textarea:not([disabled])"].map((e=>`${e}:not([tabindex='-1'])`)).join(",") +;var vw,bw,yw,Ow=((yw=Ow||{})[yw.First=1]="First", +yw[yw.Previous=2]="Previous",yw[yw.Next=4]="Next", +yw[yw.Last=8]="Last",yw[yw.WrapAround=16]="WrapAround", +yw[yw.NoScroll=32]="NoScroll", +yw),ww=((bw=ww||{})[bw.Error=0]="Error",bw[bw.Overflow=1]="Overflow", +bw[bw.Success=2]="Success", +bw[bw.Underflow=3]="Underflow",bw),xw=((vw=xw||{})[vw.Previous=-1]="Previous", +vw[vw.Next=1]="Next",vw);function kw(e=document.body){ +return null==e?[]:Array.from(e.querySelectorAll(gw)).sort(((e,t)=>Math.sign((e.tabIndex||Number.MAX_SAFE_INTEGER)-(t.tabIndex||Number.MAX_SAFE_INTEGER)))) +}var Sw=(e=>(e[e.Strict=0]="Strict",e[e.Loose=1]="Loose",e))(Sw||{}) +;function _w(e,t=0){var n;return e!==(null==(n=mw(e))?void 0:n.body)&&dw(t,{ +0:()=>e.matches(gw),1(){let t=e;for(;null!==t;){if(t.matches(gw))return!0 +;t=t.parentElement}return!1}})}function Aw(e){let t=mw(e);Xm((()=>{ +t&&!_w(t.activeElement,0)&&Ew(e)}))} +var Tw=(e=>(e[e.Keyboard=0]="Keyboard",e[e.Mouse=1]="Mouse",e))(Tw||{}) +;function Ew(e){null==e||e.focus({preventScroll:!0})} +"undefined"!=typeof window&&"undefined"!=typeof document&&(document.addEventListener("keydown",(e=>{ +e.metaKey||e.altKey||e.ctrlKey||(document.documentElement.dataset.headlessuiFocusVisible="") +}),!0),document.addEventListener("click",(e=>{ +1===e.detail?delete document.documentElement.dataset.headlessuiFocusVisible:0===e.detail&&(document.documentElement.dataset.headlessuiFocusVisible="") +}),!0));let Cw=["textarea","input"].join(",");function $w(e,t=e=>e){ +return e.slice().sort(((e,n)=>{let r=t(e),a=t(n);if(null===r||null===a)return 0 +;let o=r.compareDocumentPosition(a) +;return o&Node.DOCUMENT_POSITION_FOLLOWING?-1:o&Node.DOCUMENT_POSITION_PRECEDING?1:0 +}))}function Pw(e,t,{sorted:n=!0,relativeTo:r=null,skipElements:a=[]}={}){var o +;let i=null!=(o=Array.isArray(e)?e.length>0?e[0].ownerDocument:document:null==e?void 0:e.ownerDocument)?o:document,s=Array.isArray(e)?n?$w(e):e:kw(e) +;a.length>0&&s.length>1&&(s=s.filter((e=>!a.includes(e)))), +r=null!=r?r:i.activeElement;let l,c=(()=>{if(5&t)return 1;if(10&t)return-1 +;throw new Error("Missing Focus.First, Focus.Previous, Focus.Next or Focus.Last") +})(),u=(()=>{if(1&t)return 0;if(2&t)return Math.max(0,s.indexOf(r))-1 +;if(4&t)return Math.max(0,s.indexOf(r))+1;if(8&t)return s.length-1 +;throw new Error("Missing Focus.First, Focus.Previous, Focus.Next or Focus.Last") +})(),d=32&t?{preventScroll:!0}:{},p=0,h=s.length;do{if(p>=h||p+h<=0)return 0 +;let e=u+p;if(16&t)e=(e+h)%h;else{if(e<0)return 3;if(e>=h)return 1} +l=s[e],null==l||l.focus(d),p+=c}while(l!==i.activeElement) +;return 6&t&&function(e){var t,n +;return null!=(n=null==(t=null==e?void 0:e.matches)?void 0:t.call(e,Cw))&&n +}(l)&&l.select(),2}function Iw(){ +return/iPhone/gi.test(window.navigator.platform)||/Mac/gi.test(window.navigator.platform)&&window.navigator.maxTouchPoints>0 +}function Dw(){return Iw()||/Android/gi.test(window.navigator.userAgent)} +function Mw(e,t,n){fw.isServer||fg((r=>{ +document.addEventListener(e,t,n),r((()=>document.removeEventListener(e,t,n)))})) +}function Nw(e,t,n){fw.isServer||fg((r=>{ +window.addEventListener(e,t,n),r((()=>window.removeEventListener(e,t,n)))}))} +function Rw(e,t,n=Ty((()=>!0))){function r(r,a){ +if(!n.value||r.defaultPrevented)return;let o=a(r) +;if(null===o||!o.getRootNode().contains(o))return;let i=function e(t){ +return"function"==typeof t?e(t()):Array.isArray(t)||t instanceof Set?t:[t]}(e) +;for(let e of i){if(null===e)continue;let t=e instanceof HTMLElement?e:uw(e) +;if(null!=t&&t.contains(o)||r.composed&&r.composedPath().includes(t))return} +return!_w(o,Sw.Loose)&&-1!==o.tabIndex&&r.preventDefault(),t(r,o)}let a=bm(null) +;Mw("pointerdown",(e=>{var t,r +;n.value&&(a.value=(null==(r=null==(t=e.composedPath)?void 0:t.call(e))?void 0:r[0])||e.target) +}),!0),Mw("mousedown",(e=>{var t,r +;n.value&&(a.value=(null==(r=null==(t=e.composedPath)?void 0:t.call(e))?void 0:r[0])||e.target) +}),!0),Mw("click",(e=>{Dw()||a.value&&(r(e,(()=>a.value)),a.value=null) +}),!0),Mw("touchend",(e=>r(e,(()=>e.target instanceof HTMLElement?e.target:null))),!0), +Nw("blur",(e=>r(e,(()=>window.document.activeElement instanceof HTMLIFrameElement?window.document.activeElement:null))),!0) +}function Lw(e,t){if(e)return e;let n=null!=t?t:"button" +;return"string"==typeof n&&"button"===n.toLowerCase()?"button":void 0} +function Bw(e,t){let n=bm(Lw(e.value.type,e.value.as));return cv((()=>{ +n.value=Lw(e.value.type,e.value.as)})),fg((()=>{var e +;n.value||uw(t)&&uw(t)instanceof HTMLButtonElement&&(null==(e=uw(t))||!e.hasAttribute("type"))&&(n.value="button") +})),n}function jw(e){return[e.screenX,e.screenY]}function Uw(){let e=bm([-1,-1]) +;return{wasMoved(t){let n=jw(t) +;return(e.value[0]!==n[0]||e.value[1]!==n[1])&&(e.value=n,!0)},update(t){ +e.value=jw(t)}}} +var zw,Zw=(e=>(e[e.None=0]="None",e[e.RenderStrategy=1]="RenderStrategy", +e[e.Static=2]="Static", +e))(Zw||{}),Fw=((zw=Fw||{})[zw.Unmount=0]="Unmount",zw[zw.Hidden=1]="Hidden",zw) +;function Hw({visible:e=!0,features:t=0,ourProps:n,theirProps:r,...a}){var o +;let i=qw(r,n),s=Object.assign(a,{props:i});if(e||2&t&&i.static)return Qw(s) +;if(1&t){return dw(null==(o=i.unmount)||o?0:1,{0:()=>null,1:()=>Qw({...a,props:{ +...i,hidden:!0,style:{display:"none"}}})})}return Qw(s)} +function Qw({props:e,attrs:t,slots:n,slot:r,name:a}){var o,i +;let{as:s,...l}=Ww(e,["unmount","static"]),c=null==(o=n.default)?void 0:o.call(n,r),u={} +;if(r){let e=!1,t=[] +;for(let[n,a]of Object.entries(r))"boolean"==typeof a&&(e=!0),!0===a&&t.push(n) +;e&&(u["data-headlessui-state"]=t.join(" "))}if("template"===s){ +if(c=Vw(null!=c?c:[]),Object.keys(l).length>0||Object.keys(t).length>0){ +let[e,...n]=null!=c?c:[];if(!function(e){ +return null!=e&&("string"==typeof e.type||"object"==typeof e.type||"function"==typeof e.type) +}(e)||n.length>0)throw new Error(['Passing props on "template"!',"",`The current component <${a} /> is rendering a "template".`,"However we need to passthrough the following props:",Object.keys(l).concat(Object.keys(t)).map((e=>e.trim())).filter(((e,t,n)=>n.indexOf(e)===t)).sort(((e,t)=>e.localeCompare(t))).map((e=>` - ${e}`)).join("\n"),"","You can apply a few solutions:",['Add an `as="..."` prop, to ensure that we render an actual element instead of a "template".',"Render a single element as the child so that we can forward the props onto that element."].map((e=>` - ${e}`)).join("\n")].join("\n")) +;let r=qw(null!=(i=e.props)?i:{},l,u),o=ny(e,r,!0) +;for(let t in r)t.startsWith("on")&&(o.props||(o.props={}),o.props[t]=r[t]) +;return o}return Array.isArray(c)&&1===c.length?c[0]:c} +return Ey(s,Object.assign({},l,u),{default:()=>c})}function Vw(e){ +return e.flatMap((e=>e.type===Lb?Vw(e.children):[e]))}function qw(...e){ +if(0===e.length)return{};if(1===e.length)return e[0];let t={},n={} +;for(let r of e)for(let e in r)e.startsWith("on")&&"function"==typeof r[e]?(null!=n[e]||(n[e]=[]), +n[e].push(r[e])):t[e]=r[e] +;if(t.disabled||t["aria-disabled"])return Object.assign(t,Object.fromEntries(Object.keys(n).map((e=>[e,void 0])))) +;for(let r in n)Object.assign(t,{[r](e,...t){let a=n[r];for(let n of a){ +if(e instanceof Event&&e.defaultPrevented)return;n(e,...t)}}});return t} +function Ww(e,t=[]){let n=Object.assign({},e);for(let r of t)r in n&&delete n[r] +;return n} +var Xw=(e=>(e[e.None=1]="None",e[e.Focusable=2]="Focusable",e[e.Hidden=4]="Hidden", +e))(Xw||{});let Gw=Hg({name:"Hidden",props:{as:{type:[Object,String], +default:"div"},features:{type:Number,default:1}}, +setup:(e,{slots:t,attrs:n})=>()=>{var r;let{features:a,...o}=e;return Hw({ +ourProps:{"aria-hidden":!(2&~a)||(null!=(r=o["aria-hidden"])?r:void 0), +hidden:!(4&~a)||void 0,style:{position:"fixed",top:1,left:1,width:1,height:0, +padding:0,margin:-1,overflow:"hidden",clip:"rect(0, 0, 0, 0)", +whiteSpace:"nowrap",borderWidth:"0",...!(4&~a)&&!!(2&~a)&&{display:"none"}}}, +theirProps:o,slot:{},attrs:n,slots:t,name:"Hidden"})}}),Yw=Symbol("Context") +;var Kw=(e=>(e[e.Open=1]="Open",e[e.Closed=2]="Closed",e[e.Closing=4]="Closing", +e[e.Opening=8]="Opening",e))(Kw||{});function Jw(){return ug(Yw,null)} +function ex(e){cg(Yw,e)} +var tx,nx=((tx=nx||{}).Space=" ",tx.Enter="Enter",tx.Escape="Escape", +tx.Backspace="Backspace", +tx.Delete="Delete",tx.ArrowLeft="ArrowLeft",tx.ArrowUp="ArrowUp", +tx.ArrowRight="ArrowRight",tx.ArrowDown="ArrowDown",tx.Home="Home",tx.End="End", +tx.PageUp="PageUp",tx.PageDown="PageDown",tx.Tab="Tab",tx);let rx=[] +;!function(e){function t(){ +"loading"!==document.readyState&&(e(),document.removeEventListener("DOMContentLoaded",t)) +} +"undefined"!=typeof window&&"undefined"!=typeof document&&(document.addEventListener("DOMContentLoaded",t), +t())}((()=>{function e(e){ +e.target instanceof HTMLElement&&e.target!==document.body&&rx[0]!==e.target&&(rx.unshift(e.target), +rx=rx.filter((e=>null!=e&&e.isConnected)),rx.splice(10))} +window.addEventListener("click",e,{capture:!0 +}),window.addEventListener("mousedown",e,{capture:!0 +}),window.addEventListener("focus",e,{capture:!0 +}),document.body.addEventListener("click",e,{capture:!0 +}),document.body.addEventListener("mousedown",e,{capture:!0 +}),document.body.addEventListener("focus",e,{capture:!0})})) +;var ax,ox=((ax=ox||{})[ax.First=0]="First", +ax[ax.Previous=1]="Previous",ax[ax.Next=2]="Next", +ax[ax.Last=3]="Last",ax[ax.Specific=4]="Specific",ax[ax.Nothing=5]="Nothing",ax) +;function ix(e,t){let n=t.resolveItems();if(n.length<=0)return null +;let r=t.resolveActiveIndex(),a=null!=r?r:-1;switch(e.focus){case 0: +for(let e=0;e=0;--e)if(!t.resolveDisabled(n[e],e,n))return e;return r +;case 2:for(let e=a+1;e=0;--e)if(!t.resolveDisabled(n[e],e,n))return e;return r +;case 4:for(let r=0;r{ +(e=null!=e?e:window).addEventListener(t,n,r), +a((()=>e.removeEventListener(t,n,r)))}))}var dx=(e=>(e[e.Forwards=0]="Forwards", +e[e.Backwards=1]="Backwards",e))(dx||{});function px(){let e=bm(0) +;return Nw("keydown",(t=>{"Tab"===t.key&&(e.value=t.shiftKey?1:0)})),e} +function hx(e){if(!e)return new Set;if("function"==typeof e)return new Set(e()) +;let t=new Set;for(let n of e.value){let e=uw(n) +;e instanceof HTMLElement&&t.add(e)}return t} +var fx=(e=>(e[e.None=1]="None",e[e.InitialFocus=2]="InitialFocus", +e[e.TabLock=4]="TabLock", +e[e.FocusLock=8]="FocusLock",e[e.RestoreFocus=16]="RestoreFocus", +e[e.All=30]="All",e))(fx||{});let mx=Object.assign(Hg({name:"FocusTrap",props:{ +as:{type:[Object,String],default:"div"},initialFocus:{type:Object,default:null}, +features:{type:Number,default:30},containers:{type:[Object,Function], +default:bm(new Set)}},inheritAttrs:!1,setup(e,{attrs:t,slots:n,expose:r}){ +let a=bm(null);r({el:a,$el:a});let o=Ty((()=>mw(a))),i=bm(!1) +;cv((()=>i.value=!0)),hv((()=>i.value=!1)),function({ownerDocument:e},t){ +let n=function(e){let t=bm(rx.slice());return mg([e],(([e],[n])=>{ +!0===n&&!1===e?aw((()=>{t.value.splice(0) +})):!1===n&&!0===e&&(t.value=rx.slice())}),{flush:"post"}),()=>{var e +;return null!=(e=t.value.find((e=>null!=e&&e.isConnected)))?e:null}}(t) +;cv((()=>{fg((()=>{var r,a +;t.value||(null==(r=e.value)?void 0:r.activeElement)===(null==(a=e.value)?void 0:a.body)&&Ew(n()) +}),{flush:"post"})})),hv((()=>{t.value&&Ew(n())}))}({ownerDocument:o +},Ty((()=>i.value&&Boolean(16&e.features)))) +;let s=function({ownerDocument:e,container:t,initialFocus:n},r){ +let a=bm(null),o=bm(!1) +;return cv((()=>o.value=!0)),hv((()=>o.value=!1)),cv((()=>{mg([t,n,r],((i,s)=>{ +if(i.every(((e,t)=>(null==s?void 0:s[t])===e))||!r.value)return;let l=uw(t) +;l&&aw((()=>{var t,r;if(!o.value)return +;let i=uw(n),s=null==(t=e.value)?void 0:t.activeElement;if(i){ +if(i===s)return void(a.value=s)}else if(l.contains(s))return void(a.value=s) +;i?Ew(i):Pw(l,Ow.First|Ow.NoScroll)===ww.Error&&console.warn("There are no focusable elements inside the "), +a.value=null==(r=e.value)?void 0:r.activeElement}))}),{immediate:!0,flush:"post" +})})),a}({ownerDocument:o,container:a,initialFocus:Ty((()=>e.initialFocus)) +},Ty((()=>i.value&&Boolean(2&e.features)))) +;!function({ownerDocument:e,container:t,containers:n,previousActiveElement:r},a){ +var o;ux(null==(o=e.value)?void 0:o.defaultView,"focus",(e=>{if(!a.value)return +;let o=hx(n);uw(t)instanceof HTMLElement&&o.add(uw(t));let i=r.value +;if(!i)return;let s=e.target +;s&&s instanceof HTMLElement?gx(o,s)?(r.value=s,Ew(s)):(e.preventDefault(), +e.stopPropagation(),Ew(i)):Ew(r.value)}),!0)}({ownerDocument:o,container:a, +containers:e.containers,previousActiveElement:s +},Ty((()=>i.value&&Boolean(8&e.features))));let l=px();function c(e){let t=uw(a) +;t&&dw(l.value,{[dx.Forwards]:()=>{Pw(t,Ow.First,{skipElements:[e.relatedTarget] +})},[dx.Backwards]:()=>{Pw(t,Ow.Last,{skipElements:[e.relatedTarget]})}})} +let u=bm(!1);function d(e){ +"Tab"===e.key&&(u.value=!0,requestAnimationFrame((()=>{u.value=!1})))} +function p(t){if(!i.value)return;let n=hx(e.containers) +;uw(a)instanceof HTMLElement&&n.add(uw(a));let r=t.relatedTarget +;r instanceof HTMLElement&&"true"!==r.dataset.headlessuiFocusGuard&&(gx(n,r)||(u.value?Pw(uw(a),dw(l.value,{ +[dx.Forwards]:()=>Ow.Next,[dx.Backwards]:()=>Ow.Previous})|Ow.WrapAround,{ +relativeTo:t.target}):t.target instanceof HTMLElement&&Ew(t.target)))} +return()=>{let r={ref:a,onKeydown:d,onFocusout:p +},{features:o,initialFocus:i,containers:s,...l}=e +;return Ey(Lb,[Boolean(4&o)&&Ey(Gw,{as:"button",type:"button", +"data-headlessui-focus-guard":!0,onFocus:c,features:Xw.Focusable}),Hw({ +ourProps:r,theirProps:{...t,...l},slot:{},attrs:t,slots:n,name:"FocusTrap" +}),Boolean(4&o)&&Ey(Gw,{as:"button",type:"button", +"data-headlessui-focus-guard":!0,onFocus:c,features:Xw.Focusable})])}}}),{ +features:fx});function gx(e,t){for(let n of e)if(n.contains(t))return!0;return!1 +}function vx(){let e;return{before({doc:t}){var n;let r=t.documentElement +;e=(null!=(n=t.defaultView)?n:window).innerWidth-r.clientWidth}, +after({doc:t,d:n}){let r=t.documentElement,a=r.clientWidth-r.offsetWidth,o=e-a +;n.style(r,"paddingRight",`${o}px`)}}}function bx(e){let t={} +;for(let n of e)Object.assign(t,n(t));return t}let yx=function(e,t){ +let n=e(),r=new Set;return{getSnapshot:()=>n, +subscribe:e=>(r.add(e),()=>r.delete(e)),dispatch(e,...a){let o=t[e].call(n,...a) +;o&&(n=o,r.forEach((e=>e())))}}}((()=>new Map),{PUSH(e,t){var n +;let r=null!=(n=this.get(e))?n:{doc:e,count:0,d:ow(),meta:new Set} +;return r.count++,r.meta.add(t),this.set(e,r),this},POP(e,t){let n=this.get(e) +;return n&&(n.count--,n.meta.delete(t)),this}, +SCROLL_PREVENT({doc:e,d:t,meta:n}){let r={doc:e,d:t,meta:bx(n)},a=[Iw()?{ +before({doc:e,d:t,meta:n}){function r(e){ +return n.containers.flatMap((e=>e())).some((t=>t.contains(e)))} +t.microTask((()=>{var n +;if("auto"!==window.getComputedStyle(e.documentElement).scrollBehavior){ +let n=ow() +;n.style(e.documentElement,"scrollBehavior","auto"),t.add((()=>t.microTask((()=>n.dispose())))) +}let a=null!=(n=window.scrollY)?n:window.pageYOffset,o=null +;t.addEventListener(e,"click",(t=>{if(t.target instanceof HTMLElement)try{ +let n=t.target.closest("a");if(!n)return +;let{hash:a}=new URL(n.href),i=e.querySelector(a);i&&!r(i)&&(o=i)}catch{}}),!0), +t.addEventListener(e,"touchstart",(e=>{ +if(e.target instanceof HTMLElement)if(r(e.target)){let n=e.target +;for(;n.parentElement&&r(n.parentElement);)n=n.parentElement +;t.style(n,"overscrollBehavior","contain") +}else t.style(e.target,"touchAction","none") +})),t.addEventListener(e,"touchmove",(e=>{if(e.target instanceof HTMLElement){ +if("INPUT"===e.target.tagName)return;if(r(e.target)){let t=e.target +;for(;t.parentElement&&""!==t.dataset.headlessuiPortal&&!(t.scrollHeight>t.clientHeight||t.scrollWidth>t.clientWidth);)t=t.parentElement +;""===t.dataset.headlessuiPortal&&e.preventDefault()}else e.preventDefault()} +}),{passive:!1}),t.add((()=>{var e +;let t=null!=(e=window.scrollY)?e:window.pageYOffset +;a!==t&&window.scrollTo(0,a),o&&o.isConnected&&(o.scrollIntoView({ +block:"nearest"}),o=null)}))}))}}:{},vx(),{before({doc:e,d:t}){ +t.style(e.documentElement,"overflow","hidden")}}] +;a.forEach((({before:e})=>null==e?void 0:e(r))), +a.forEach((({after:e})=>null==e?void 0:e(r)))},SCROLL_ALLOW({d:e}){e.dispose()}, +TEARDOWN({doc:e}){this.delete(e)}});function Ox(e,t,n){let r=function(e){ +let t=ym(e.getSnapshot());return hv(e.subscribe((()=>{t.value=e.getSnapshot() +}))),t}(yx),a=Ty((()=>{let t=e.value?r.value.get(e.value):void 0 +;return!!t&&t.count>0}));return mg([e,t],(([e,t],[r],a)=>{if(!e||!t)return +;yx.dispatch("PUSH",e,n);let o=!1;a((()=>{ +o||(yx.dispatch("POP",null!=r?r:e,n),o=!0)}))}),{immediate:!0}),a} +yx.subscribe((()=>{let e=yx.getSnapshot(),t=new Map +;for(let[n]of e)t.set(n,n.documentElement.style.overflow) +;for(let n of e.values()){let e="hidden"===t.get(n.doc),r=0!==n.count +;(r&&!e||!r&&e)&&yx.dispatch(n.count>0?"SCROLL_PREVENT":"SCROLL_ALLOW",n), +0===n.count&&yx.dispatch("TEARDOWN",n)}}));let wx=new Map,xx=new Map +;function kx(e,t=bm(!0)){fg((n=>{var r;if(!t.value)return;let a=uw(e) +;if(!a)return;n((function(){var e;if(!a)return;let t=null!=(e=xx.get(a))?e:1 +;if(1===t?xx.delete(a):xx.set(a,t-1),1!==t)return;let n=wx.get(a) +;n&&(null===n["aria-hidden"]?a.removeAttribute("aria-hidden"):a.setAttribute("aria-hidden",n["aria-hidden"]), +a.inert=n.inert,wx.delete(a))}));let o=null!=(r=xx.get(a))?r:0 +;xx.set(a,o+1),0===o&&(wx.set(a,{"aria-hidden":a.getAttribute("aria-hidden"), +inert:a.inert}),a.setAttribute("aria-hidden","true"),a.inert=!0)}))} +function Sx({defaultContainers:e=[],portals:t,mainTreeNodeRef:n}={}){ +let r=bm(null),a=mw(r);function o(){var n,o,i;let s=[] +;for(let t of e)null!==t&&(t instanceof HTMLElement?s.push(t):"value"in t&&t.value instanceof HTMLElement&&s.push(t.value)) +;if(null!=t&&t.value)for(let e of t.value)s.push(e) +;for(let e of null!=(n=null==a?void 0:a.querySelectorAll("html > *, body > *"))?n:[])e!==document.body&&e!==document.head&&e instanceof HTMLElement&&"headlessui-portal-root"!==e.id&&(e.contains(uw(r))||e.contains(null==(i=null==(o=uw(r))?void 0:o.getRootNode())?void 0:i.host)||s.some((t=>e.contains(t)))||s.push(e)) +;return s}return{resolveContainers:o,contains:e=>o().some((t=>t.contains(e))), +mainTreeNodeRef:r,MainTreeNode:()=>null!=n?null:Ey(Gw,{features:Xw.Hidden,ref:r +})}}let _x=Symbol("ForcePortalRootContext");let Ax=Hg({name:"ForcePortalRoot", +props:{as:{type:[Object,String],default:"template"},force:{type:Boolean, +default:!1}},setup:(e,{slots:t,attrs:n})=>(cg(_x,e.force),()=>{ +let{force:r,...a}=e;return Hw({theirProps:a,ourProps:{},slot:{},slots:t,attrs:n, +name:"ForcePortalRoot"})})}),Tx=Symbol("StackContext") +;var Ex=(e=>(e[e.Add=0]="Add",e[e.Remove=1]="Remove",e))(Ex||{}) +;function Cx({type:e,enabled:t,element:n,onUpdate:r}){let a=ug(Tx,(()=>{})) +;function o(...e){null==r||r(...e),a(...e)}cv((()=>{mg(t,((t,r)=>{ +t?o(0,e,n):!0===r&&o(1,e,n)}),{immediate:!0,flush:"sync"})})),hv((()=>{ +t.value&&o(1,e,n)})),cg(Tx,o)}let $x=Symbol("DescriptionContext") +;const Px=new WeakMap;function Ix(e,t){let n=t(function(e){var t +;return null!=(t=Px.get(e))?t:0}(e));return n<=0?Px.delete(e):Px.set(e,n),n} +let Dx=Hg({name:"Portal",props:{as:{type:[Object,String],default:"div"}}, +setup(e,{slots:t,attrs:n}){ +let r=bm(null),a=Ty((()=>mw(r))),o=ug(_x,!1),i=ug(Rx,null),s=bm(!0===o||null==i?function(e){ +let t=mw(e);if(!t){if(null===e)return null +;throw new Error(`[Headless UI]: Cannot find ownerDocument for contextElement: ${e}`) +}let n=t.getElementById("headlessui-portal-root");if(n)return n +;let r=t.createElement("div") +;return r.setAttribute("id","headlessui-portal-root"),t.body.appendChild(r) +}(r.value):i.resolveTarget());s.value&&Ix(s.value,(e=>e+1));let l=bm(!1) +;cv((()=>{l.value=!0})),fg((()=>{o||null!=i&&(s.value=i.resolveTarget())})) +;let c=ug(Mx,null),u=!1,d=fy();return mg(r,(()=>{if(u||!c)return;let e=uw(r) +;e&&(hv(c.register(e),d),u=!0)})),hv((()=>{var e,t +;let n=null==(e=a.value)?void 0:e.getElementById("headlessui-portal-root") +;!n||s.value!==n||Ix(s.value,(e=>e-1))||s.value.children.length>0||null==(t=s.value.parentElement)||t.removeChild(s.value) +})),()=>{if(!l.value||null===s.value)return null;let a={ref:r, +"data-headlessui-portal":""};return Ey(Eg,{to:s.value},Hw({ourProps:a, +theirProps:e,slot:{},attrs:n,slots:t,name:"Portal"}))}} +}),Mx=Symbol("PortalParentContext");function Nx(){let e=ug(Mx,null),t=bm([]) +;function n(n){let r=t.value.indexOf(n) +;-1!==r&&t.value.splice(r,1),e&&e.unregister(n)}let r={register:function(r){ +return t.value.push(r),e&&e.register(r),()=>n(r)},unregister:n,portals:t} +;return[t,Hg({name:"PortalWrapper",setup:(e,{slots:t})=>(cg(Mx,r),()=>{var e +;return null==(e=t.default)?void 0:e.call(t)})})]} +let Rx=Symbol("PortalGroupContext"),Lx=Hg({name:"PortalGroup",props:{as:{ +type:[Object,String],default:"template"},target:{type:Object,default:null}}, +setup(e,{attrs:t,slots:n}){let r=om({resolveTarget:()=>e.target}) +;return cg(Rx,r),()=>{let{target:r,...a}=e;return Hw({theirProps:a,ourProps:{}, +slot:{},attrs:t,slots:n,name:"PortalGroup"})}}}) +;var Bx,jx=((Bx=jx||{})[Bx.Open=0]="Open",Bx[Bx.Closed=1]="Closed",Bx) +;let Ux=Symbol("DialogContext");function zx(e){let t=ug(Ux,null);if(null===t){ +let t=new Error(`<${e} /> is missing a parent component.`) +;throw Error.captureStackTrace&&Error.captureStackTrace(t,zx),t}return t} +let Zx="DC8F892D-2EBD-447C-A4C8-A03058436FF4",Fx=Hg({name:"Dialog", +inheritAttrs:!1,props:{as:{type:[Object,String],default:"div"},static:{ +type:Boolean,default:!1},unmount:{type:Boolean,default:!0},open:{ +type:[Boolean,String],default:Zx},initialFocus:{type:Object,default:null},id:{ +type:String,default:null},role:{type:String,default:"dialog"}},emits:{ +close:e=>!0},setup(e,{emit:t,attrs:n,slots:r,expose:a}){var o,i +;let s=null!=(o=e.id)?o:`headlessui-dialog-${cw()}`,l=bm(!1);cv((()=>{l.value=!0 +})) +;let c=!1,u=Ty((()=>"dialog"===e.role||"alertdialog"===e.role?e.role:(c||(c=!0, +console.warn(`Invalid role [${u}] passed to . Only \`dialog\` and and \`alertdialog\` are supported. Using \`dialog\` instead.`)), +"dialog"))),d=bm(0),p=Jw(),h=Ty((()=>e.open===Zx&&null!==p?(p.value&Kw.Open)===Kw.Open:e.open)),f=bm(null),m=Ty((()=>mw(f))) +;if(a({el:f,$el:f +}),e.open===Zx&&null===p)throw new Error("You forgot to provide an `open` prop to the `Dialog`.") +;if("boolean"!=typeof h.value)throw new Error(`You provided an \`open\` prop to the \`Dialog\`, but the value is not a boolean. Received: ${h.value===Zx?void 0:e.open}`) +;let g=Ty((()=>l.value&&h.value?0:1)),v=Ty((()=>0===g.value)),b=Ty((()=>d.value>1)),y=null!==ug(Ux,null),[O,w]=Nx(),{resolveContainers:x,mainTreeNodeRef:k,MainTreeNode:S}=Sx({ +portals:O,defaultContainers:[Ty((()=>{var e +;return null!=(e=D.panelRef.value)?e:f.value}))] +}),_=Ty((()=>b.value?"parent":"leaf")),A=Ty((()=>null!==p&&(p.value&Kw.Closing)===Kw.Closing)),T=Ty((()=>!y&&!A.value&&v.value)),E=Ty((()=>{ +var e,t,n +;return null!=(n=Array.from(null!=(t=null==(e=m.value)?void 0:e.querySelectorAll("body > *"))?t:[]).find((e=>"headlessui-portal-root"!==e.id&&(e.contains(uw(k))&&e instanceof HTMLElement))))?n:null +}));kx(E,T);let C=Ty((()=>!!b.value||v.value)),$=Ty((()=>{var e,t,n +;return null!=(n=Array.from(null!=(t=null==(e=m.value)?void 0:e.querySelectorAll("[data-headlessui-portal]"))?t:[]).find((e=>e.contains(uw(k))&&e instanceof HTMLElement)))?n:null +}));kx($,C),Cx({type:"Dialog",enabled:Ty((()=>0===g.value)),element:f, +onUpdate:(e,t)=>{if("Dialog"===t)return dw(e,{[Ex.Add]:()=>d.value+=1, +[Ex.Remove]:()=>d.value-=1})}}) +;let P=function({slot:e=bm({}),name:t="Description",props:n={}}={}){let r=bm([]) +;return cg($x,{register:function(e){return r.value.push(e),()=>{ +let t=r.value.indexOf(e);-1!==t&&r.value.splice(t,1)}},slot:e,name:t,props:n +}),Ty((()=>r.value.length>0?r.value.join(" "):void 0))}({ +name:"DialogDescription",slot:Ty((()=>({open:h.value})))}),I=bm(null),D={ +titleId:I,panelRef:bm(null),dialogState:g,setTitleId(e){I.value!==e&&(I.value=e) +},close(){t("close",!1)}};cg(Ux,D);let M=Ty((()=>!(!v.value||b.value))) +;Rw(x,((e,t)=>{e.preventDefault(),D.close(),Xm((()=>null==t?void 0:t.focus())) +}),M);let N=Ty((()=>!(b.value||0!==g.value))) +;ux(null==(i=m.value)?void 0:i.defaultView,"keydown",(e=>{ +N.value&&(e.defaultPrevented||e.key===nx.Escape&&(e.preventDefault(), +e.stopPropagation(),D.close()))}));let R=Ty((()=>!(A.value||0!==g.value||y))) +;return Ox(m,R,(e=>{var t;return{containers:[...null!=(t=e.containers)?t:[],x]} +})),fg((e=>{if(0!==g.value)return;let t=uw(f);if(!t)return +;let n=new ResizeObserver((e=>{for(let t of e){ +let e=t.target.getBoundingClientRect() +;0===e.x&&0===e.y&&0===e.width&&0===e.height&&D.close()}})) +;n.observe(t),e((()=>n.disconnect()))})),()=>{ +let{open:t,initialFocus:a,...o}=e,i={...n,ref:f,id:s,role:u.value, +"aria-modal":0===g.value||void 0,"aria-labelledby":I.value, +"aria-describedby":P.value},l={open:0===g.value};return Ey(Ax,{force:!0 +},(()=>[Ey(Dx,(()=>Ey(Lx,{target:f.value},(()=>Ey(Ax,{force:!1},(()=>Ey(mx,{ +initialFocus:a,containers:x,features:v.value?dw(_.value,{ +parent:mx.features.RestoreFocus,leaf:mx.features.All&~mx.features.FocusLock +}):mx.features.None},(()=>Ey(w,{},(()=>Hw({ourProps:i,theirProps:{...o,...n}, +slot:l,attrs:n,slots:r,visible:0===g.value,features:Zw.RenderStrategy|Zw.Static, +name:"Dialog"}))))))))))),Ey(S)]))}}}),Hx=Hg({name:"DialogPanel",props:{as:{ +type:[Object,String],default:"div"},id:{type:String,default:null}}, +setup(e,{attrs:t,slots:n,expose:r}){var a +;let o=null!=(a=e.id)?a:`headlessui-dialog-panel-${cw()}`,i=zx("DialogPanel") +;function s(e){e.stopPropagation()}return r({el:i.panelRef,$el:i.panelRef +}),()=>{let{...r}=e;return Hw({ourProps:{id:o,ref:i.panelRef,onClick:s}, +theirProps:r,slot:{open:0===i.dialogState.value},attrs:t,slots:n, +name:"DialogPanel"})}}}),Qx=Hg({name:"DialogTitle",props:{as:{ +type:[Object,String],default:"h2"},id:{type:String,default:null}}, +setup(e,{attrs:t,slots:n}){var r +;let a=null!=(r=e.id)?r:`headlessui-dialog-title-${cw()}`,o=zx("DialogTitle") +;return cv((()=>{o.setTitleId(a),hv((()=>o.setTitleId(null)))})),()=>{ +let{...r}=e;return Hw({ourProps:{id:a},theirProps:r,slot:{ +open:0===o.dialogState.value},attrs:t,slots:n,name:"DialogTitle"})}}}) +;var Vx=(e=>(e[e.Open=0]="Open",e[e.Closed=1]="Closed",e))(Vx||{}) +;let qx=Symbol("DisclosureContext");function Wx(e){let t=ug(qx,null) +;if(null===t){ +let t=new Error(`<${e} /> is missing a parent component.`) +;throw Error.captureStackTrace&&Error.captureStackTrace(t,Wx),t}return t} +let Xx=Symbol("DisclosurePanelContext");let Gx=Hg({name:"Disclosure",props:{as:{ +type:[Object,String],default:"template"},defaultOpen:{type:[Boolean],default:!1} +},setup(e,{slots:t,attrs:n}){ +let r=bm(e.defaultOpen?0:1),a=bm(null),o=bm(null),i={ +buttonId:bm(`headlessui-disclosure-button-${cw()}`), +panelId:bm(`headlessui-disclosure-panel-${cw()}`),disclosureState:r,panel:a, +button:o,toggleDisclosure(){r.value=dw(r.value,{0:1,1:0})},closeDisclosure(){ +1!==r.value&&(r.value=1)},close(e){i.closeDisclosure() +;let t=e?e instanceof HTMLElement?e:e.value instanceof HTMLElement?uw(e):uw(i.button):uw(i.button) +;null==t||t.focus()}};return cg(qx,i),ex(Ty((()=>dw(r.value,{0:Kw.Open, +1:Kw.Closed})))),()=>{let{defaultOpen:a,...o}=e;return Hw({theirProps:o, +ourProps:{},slot:{open:0===r.value,close:i.close},slots:t,attrs:n, +name:"Disclosure"})}}}),Yx=Hg({name:"DisclosureButton",props:{as:{ +type:[Object,String],default:"button"},disabled:{type:[Boolean],default:!1},id:{ +type:String,default:null}},setup(e,{attrs:t,slots:n,expose:r}){ +let a=Wx("DisclosureButton"),o=ug(Xx,null),i=Ty((()=>null!==o&&o.value===a.panelId.value)) +;cv((()=>{i.value||null!==e.id&&(a.buttonId.value=e.id)})),hv((()=>{ +i.value||(a.buttonId.value=null)}));let s=bm(null);r({el:s,$el:s +}),i.value||fg((()=>{a.button.value=s.value}));let l=Bw(Ty((()=>({as:e.as, +type:t.type}))),s);function c(){var t +;e.disabled||(i.value?(a.toggleDisclosure(), +null==(t=uw(a.button))||t.focus()):a.toggleDisclosure())}function u(t){var n +;if(!e.disabled)if(i.value)switch(t.key){case nx.Space:case nx.Enter: +t.preventDefault(), +t.stopPropagation(),a.toggleDisclosure(),null==(n=uw(a.button))||n.focus() +}else switch(t.key){case nx.Space:case nx.Enter: +t.preventDefault(),t.stopPropagation(),a.toggleDisclosure()}}function d(e){ +if(e.key===nx.Space)e.preventDefault()}return()=>{var r;let o={ +open:0===a.disclosureState.value},{id:p,...h}=e;return Hw({ourProps:i.value?{ +ref:s,type:l.value,onClick:c,onKeydown:u}:{id:null!=(r=a.buttonId.value)?r:p, +ref:s,type:l.value,"aria-expanded":0===a.disclosureState.value, +"aria-controls":0===a.disclosureState.value||uw(a.panel)?a.panelId.value:void 0, +disabled:!!e.disabled||void 0,onClick:c,onKeydown:u,onKeyup:d},theirProps:h, +slot:o,attrs:t,slots:n,name:"DisclosureButton"})}}}),Kx=Hg({ +name:"DisclosurePanel",props:{as:{type:[Object,String],default:"div"},static:{ +type:Boolean,default:!1},unmount:{type:Boolean,default:!0},id:{type:String, +default:null}},setup(e,{attrs:t,slots:n,expose:r}){let a=Wx("DisclosurePanel") +;cv((()=>{null!==e.id&&(a.panelId.value=e.id)})),hv((()=>{a.panelId.value=null +})),r({el:a.panel,$el:a.panel}),cg(Xx,a.panelId) +;let o=Jw(),i=Ty((()=>null!==o?(o.value&Kw.Open)===Kw.Open:0===a.disclosureState.value)) +;return()=>{var r;let o={open:0===a.disclosureState.value,close:a.close +},{id:s,...l}=e;return Hw({ourProps:{id:null!=(r=a.panelId.value)?r:s, +ref:a.panel},theirProps:l,slot:o,attrs:t,slots:n, +features:Zw.RenderStrategy|Zw.Static,visible:i.value,name:"DisclosurePanel"})}} +}),Jx=/([\u2700-\u27BF]|[\uE000-\uF8FF]|\uD83C[\uDC00-\uDFFF]|\uD83D[\uDC00-\uDFFF]|[\u2011-\u26FF]|\uD83E[\uDD10-\uDDFF])/g +;function ek(e){var t,n;let r=null!=(t=e.innerText)?t:"",a=e.cloneNode(!0) +;if(!(a instanceof HTMLElement))return r;let o=!1 +;for(let s of a.querySelectorAll('[hidden],[aria-hidden],[role="img"]'))s.remove(), +o=!0;let i=o?null!=(n=a.innerText)?n:"":r +;return Jx.test(i)&&(i=i.replace(Jx,"")),i}function tk(e){let t=bm(""),n=bm("") +;return()=>{let r=uw(e);if(!r)return"";let a=r.innerText +;if(t.value===a)return n.value;let o=function(e){ +let t=e.getAttribute("aria-label");if("string"==typeof t)return t.trim() +;let n=e.getAttribute("aria-labelledby");if(n){let e=n.split(" ").map((e=>{ +let t=document.getElementById(e);if(t){let e=t.getAttribute("aria-label") +;return"string"==typeof e?e.trim():ek(t).trim()}return null})).filter(Boolean) +;if(e.length>0)return e.join(", ")}return ek(e).trim()}(r).trim().toLowerCase() +;return t.value=a,n.value=o,o}}function nk(e,t){return e===t} +var rk=(e=>(e[e.Open=0]="Open", +e[e.Closed=1]="Closed",e))(rk||{}),ak=(e=>(e[e.Single=0]="Single", +e[e.Multi=1]="Multi", +e))(ak||{}),ok=(e=>(e[e.Pointer=0]="Pointer",e[e.Other=1]="Other",e))(ok||{}) +;let ik=Symbol("ListboxContext");function sk(e){let t=ug(ik,null);if(null===t){ +let t=new Error(`<${e} /> is missing a parent component.`) +;throw Error.captureStackTrace&&Error.captureStackTrace(t,sk),t}return t} +let lk=Hg({name:"Listbox",emits:{"update:modelValue":e=>!0},props:{as:{ +type:[Object,String],default:"template"},disabled:{type:[Boolean],default:!1}, +by:{type:[String,Function],default:()=>nk},horizontal:{type:[Boolean],default:!1 +},modelValue:{type:[Object,String,Number,Boolean],default:void 0},defaultValue:{ +type:[Object,String,Number,Boolean],default:void 0},form:{type:String, +optional:!0},name:{type:String,optional:!0},multiple:{type:[Boolean],default:!1} +},inheritAttrs:!1,setup(e,{slots:t,attrs:n,emit:r}){ +let a=bm(1),o=bm(null),i=bm(null),s=bm(null),l=bm([]),c=bm(""),u=bm(null),d=bm(1) +;function p(e=e=>e){ +let t=null!==u.value?l.value[u.value]:null,n=$w(e(l.value.slice()),(e=>uw(e.dataRef.domRef))),r=t?n.indexOf(t):null +;return-1===r&&(r=null),{options:n,activeOptionIndex:r}} +let h=Ty((()=>e.multiple?1:0)),[f,m]=function(e,t,n){ +let r=bm(null==n?void 0:n.value),a=Ty((()=>void 0!==e.value)) +;return[Ty((()=>a.value?e.value:r.value)),function(e){ +return a.value||(r.value=e),null==t?void 0:t(e)}] +}(Ty((()=>e.modelValue)),(e=>r("update:modelValue",e)),Ty((()=>e.defaultValue))),g=Ty((()=>void 0===f.value?dw(h.value,{ +1:[],0:void 0}):f.value)),v={listboxState:a,value:g,mode:h,compare(t,n){ +if("string"==typeof e.by){let r=e.by +;return(null==t?void 0:t[r])===(null==n?void 0:n[r])}return e.by(t,n)}, +orientation:Ty((()=>e.horizontal?"horizontal":"vertical")),labelRef:o, +buttonRef:i,optionsRef:s,disabled:Ty((()=>e.disabled)),options:l,searchQuery:c, +activeOptionIndex:u,activationTrigger:d,closeListbox(){ +e.disabled||1!==a.value&&(a.value=1,u.value=null)},openListbox(){ +e.disabled||0!==a.value&&(a.value=0)},goToOption(t,n,r){ +if(e.disabled||1===a.value)return;let o=p(),i=ix(t===ox.Specific?{ +focus:ox.Specific,id:n}:{focus:t},{resolveItems:()=>o.options, +resolveActiveIndex:()=>o.activeOptionIndex,resolveId:e=>e.id, +resolveDisabled:e=>e.dataRef.disabled}) +;c.value="",u.value=i,d.value=null!=r?r:1,l.value=o.options},search(t){ +if(e.disabled||1===a.value)return;let n=""!==c.value?0:1 +;c.value+=t.toLowerCase() +;let r=(null!==u.value?l.value.slice(u.value+n).concat(l.value.slice(0,u.value+n)):l.value).find((e=>e.dataRef.textValue.startsWith(c.value)&&!e.dataRef.disabled)),o=r?l.value.indexOf(r):-1 +;-1===o||o===u.value||(u.value=o,d.value=1)},clearSearch(){ +e.disabled||1!==a.value&&""!==c.value&&(c.value="")},registerOption(e,t){ +let n=p((n=>[...n,{id:e,dataRef:t}])) +;l.value=n.options,u.value=n.activeOptionIndex},unregisterOption(e){ +let t=p((t=>{let n=t.findIndex((t=>t.id===e));return-1!==n&&t.splice(n,1),t})) +;l.value=t.options,u.value=t.activeOptionIndex,d.value=1},theirOnChange(t){ +e.disabled||m(t)},select(t){e.disabled||m(dw(h.value,{0:()=>t,1:()=>{ +let e=hm(v.value.value).slice(),n=hm(t),r=e.findIndex((e=>v.compare(n,hm(e)))) +;return-1===r?e.push(n):e.splice(r,1),e}}))}};Rw([i,s],((e,t)=>{var n +;v.closeListbox(), +_w(t,Sw.Loose)||(e.preventDefault(),null==(n=uw(i))||n.focus()) +}),Ty((()=>0===a.value))),cg(ik,v),ex(Ty((()=>dw(a.value,{0:Kw.Open,1:Kw.Closed +}))));let b=Ty((()=>{var e;return null==(e=uw(i))?void 0:e.closest("form")})) +;return cv((()=>{mg([b],(()=>{ +if(b.value&&void 0!==e.defaultValue)return b.value.addEventListener("reset",t), +()=>{var e;null==(e=b.value)||e.removeEventListener("reset",t)};function t(){ +v.theirOnChange(e.defaultValue)}}),{immediate:!0})})),()=>{ +let{name:r,modelValue:o,disabled:i,form:s,...l}=e,c={open:0===a.value, +disabled:i,value:g.value};return Ey(Lb,[...null!=r&&null!=g.value?sx({ +[r]:g.value}).map((([e,t])=>Ey(Gw,function(e){let t=Object.assign({},e) +;for(let n in t)void 0===t[n]&&delete t[n];return t}({features:Xw.Hidden,key:e, +as:"input",type:"hidden",hidden:!0,readOnly:!0,form:s,disabled:i,name:e,value:t +})))):[],Hw({ourProps:{},theirProps:{...n, +...Ww(l,["defaultValue","onUpdate:modelValue","horizontal","multiple","by"])}, +slot:c,slots:t,attrs:n,name:"Listbox"})])}}}),ck=Hg({name:"ListboxLabel",props:{ +as:{type:[Object,String],default:"label"},id:{type:String,default:null}}, +setup(e,{attrs:t,slots:n}){var r +;let a=null!=(r=e.id)?r:`headlessui-listbox-label-${cw()}`,o=sk("ListboxLabel") +;function i(){var e;null==(e=uw(o.buttonRef))||e.focus({preventScroll:!0})} +return()=>{let r={open:0===o.listboxState.value,disabled:o.disabled.value +},{...s}=e;return Hw({ourProps:{id:a,ref:o.labelRef,onClick:i},theirProps:s, +slot:r,attrs:t,slots:n,name:"ListboxLabel"})}}}),uk=Hg({name:"ListboxButton", +props:{as:{type:[Object,String],default:"button"},id:{type:String,default:null} +},setup(e,{attrs:t,slots:n,expose:r}){var a +;let o=null!=(a=e.id)?a:`headlessui-listbox-button-${cw()}`,i=sk("ListboxButton") +;function s(e){switch(e.key){case nx.Space:case nx.Enter:case nx.ArrowDown: +e.preventDefault(),i.openListbox(),Xm((()=>{var e +;null==(e=uw(i.optionsRef))||e.focus({preventScroll:!0 +}),i.value.value||i.goToOption(ox.First)}));break;case nx.ArrowUp: +e.preventDefault(),i.openListbox(),Xm((()=>{var e +;null==(e=uw(i.optionsRef))||e.focus({preventScroll:!0 +}),i.value.value||i.goToOption(ox.Last)}))}}function l(e){ +if(e.key===nx.Space)e.preventDefault()}function c(e){ +i.disabled.value||(0===i.listboxState.value?(i.closeListbox(),Xm((()=>{var e +;return null==(e=uw(i.buttonRef))?void 0:e.focus({preventScroll:!0}) +}))):(e.preventDefault(),i.openListbox(),function(e){ +requestAnimationFrame((()=>requestAnimationFrame(e)))}((()=>{var e +;return null==(e=uw(i.optionsRef))?void 0:e.focus({preventScroll:!0})}))))}r({ +el:i.buttonRef,$el:i.buttonRef});let u=Bw(Ty((()=>({as:e.as,type:t.type +}))),i.buttonRef);return()=>{var r,a;let d={open:0===i.listboxState.value, +disabled:i.disabled.value,value:i.value.value},{...p}=e;return Hw({ourProps:{ +ref:i.buttonRef,id:o,type:u.value,"aria-haspopup":"listbox", +"aria-controls":null==(r=uw(i.optionsRef))?void 0:r.id, +"aria-expanded":0===i.listboxState.value, +"aria-labelledby":i.labelRef.value?[null==(a=uw(i.labelRef))?void 0:a.id,o].join(" "):void 0, +disabled:!0===i.disabled.value||void 0,onKeydown:s,onKeyup:l,onClick:c}, +theirProps:p,slot:d,attrs:t,slots:n,name:"ListboxButton"})}}}),dk=Hg({ +name:"ListboxOptions",props:{as:{type:[Object,String],default:"ul"},static:{ +type:Boolean,default:!1},unmount:{type:Boolean,default:!0},id:{type:String, +default:null}},setup(e,{attrs:t,slots:n,expose:r}){var a +;let o=null!=(a=e.id)?a:`headlessui-listbox-options-${cw()}`,i=sk("ListboxOptions"),s=bm(null) +;function l(e){switch(s.value&&clearTimeout(s.value),e.key){case nx.Space: +if(""!==i.searchQuery.value)return e.preventDefault(), +e.stopPropagation(),i.search(e.key);case nx.Enter: +if(e.preventDefault(),e.stopPropagation(),null!==i.activeOptionIndex.value){ +let e=i.options.value[i.activeOptionIndex.value];i.select(e.dataRef.value)} +0===i.mode.value&&(i.closeListbox(),Xm((()=>{var e +;return null==(e=uw(i.buttonRef))?void 0:e.focus({preventScroll:!0})})));break +;case dw(i.orientation.value,{vertical:nx.ArrowDown,horizontal:nx.ArrowRight}): +return e.preventDefault(),e.stopPropagation(),i.goToOption(ox.Next) +;case dw(i.orientation.value,{vertical:nx.ArrowUp,horizontal:nx.ArrowLeft}): +return e.preventDefault(),e.stopPropagation(),i.goToOption(ox.Previous) +;case nx.Home:case nx.PageUp: +return e.preventDefault(),e.stopPropagation(),i.goToOption(ox.First) +;case nx.End:case nx.PageDown: +return e.preventDefault(),e.stopPropagation(),i.goToOption(ox.Last) +;case nx.Escape: +e.preventDefault(),e.stopPropagation(),i.closeListbox(),Xm((()=>{var e +;return null==(e=uw(i.buttonRef))?void 0:e.focus({preventScroll:!0})}));break +;case nx.Tab:e.preventDefault(),e.stopPropagation();break;default: +1===e.key.length&&(i.search(e.key), +s.value=setTimeout((()=>i.clearSearch()),350))}}r({el:i.optionsRef, +$el:i.optionsRef}) +;let c=Jw(),u=Ty((()=>null!==c?(c.value&Kw.Open)===Kw.Open:0===i.listboxState.value)) +;return()=>{var r,a;let s={open:0===i.listboxState.value},{...c}=e;return Hw({ +ourProps:{ +"aria-activedescendant":null===i.activeOptionIndex.value||null==(r=i.options.value[i.activeOptionIndex.value])?void 0:r.id, +"aria-multiselectable":1===i.mode.value||void 0, +"aria-labelledby":null==(a=uw(i.buttonRef))?void 0:a.id, +"aria-orientation":i.orientation.value,id:o,onKeydown:l,role:"listbox", +tabIndex:0,ref:i.optionsRef},theirProps:c,slot:s,attrs:t,slots:n, +features:Zw.RenderStrategy|Zw.Static,visible:u.value,name:"ListboxOptions"})}} +}),pk=Hg({name:"ListboxOption",props:{as:{type:[Object,String],default:"li"}, +value:{type:[Object,String,Number,Boolean]},disabled:{type:Boolean,default:!1}, +id:{type:String,default:null}},setup(e,{slots:t,attrs:n,expose:r}){var a +;let o=null!=(a=e.id)?a:`headlessui-listbox-option-${cw()}`,i=sk("ListboxOption"),s=bm(null) +;r({el:s,$el:s}) +;let l=Ty((()=>null!==i.activeOptionIndex.value&&i.options.value[i.activeOptionIndex.value].id===o)),c=Ty((()=>dw(i.mode.value,{ +0:()=>i.compare(hm(i.value.value),hm(e.value)), +1:()=>hm(i.value.value).some((t=>i.compare(hm(t),hm(e.value)))) +}))),u=Ty((()=>dw(i.mode.value,{1:()=>{var e;let t=hm(i.value.value) +;return(null==(e=i.options.value.find((e=>t.some((t=>i.compare(hm(t),hm(e.dataRef.value)))))))?void 0:e.id)===o +},0:()=>c.value}))),d=tk(s),p=Ty((()=>({disabled:e.disabled,value:e.value, +get textValue(){return d()},domRef:s})));function h(t){ +if(e.disabled)return t.preventDefault() +;i.select(e.value),0===i.mode.value&&(i.closeListbox(),Xm((()=>{var e +;return null==(e=uw(i.buttonRef))?void 0:e.focus({preventScroll:!0})})))} +function f(){if(e.disabled)return i.goToOption(ox.Nothing) +;i.goToOption(ox.Specific,o)} +cv((()=>i.registerOption(o,p))),hv((()=>i.unregisterOption(o))),cv((()=>{ +mg([i.listboxState,c],(()=>{0===i.listboxState.value&&c.value&&dw(i.mode.value,{ +1:()=>{u.value&&i.goToOption(ox.Specific,o)},0:()=>{i.goToOption(ox.Specific,o)} +})}),{immediate:!0})})),fg((()=>{ +0===i.listboxState.value&&l.value&&0!==i.activationTrigger.value&&Xm((()=>{ +var e,t +;return null==(t=null==(e=uw(s))?void 0:e.scrollIntoView)?void 0:t.call(e,{ +block:"nearest"})}))}));let m=Uw();function g(e){m.update(e)}function v(t){ +m.wasMoved(t)&&(e.disabled||l.value||i.goToOption(ox.Specific,o,0))} +function b(t){m.wasMoved(t)&&(e.disabled||l.value&&i.goToOption(ox.Nothing))} +return()=>{let{disabled:r}=e,a={active:l.value,selected:c.value,disabled:r +},{value:i,disabled:u,...d}=e;return Hw({ourProps:{id:o,ref:s,role:"option", +tabIndex:!0===r?void 0:-1,"aria-disabled":!0===r||void 0, +"aria-selected":c.value,disabled:void 0,onClick:h,onFocus:f,onPointerenter:g, +onMouseenter:g,onPointermove:v,onMousemove:v,onPointerleave:b,onMouseleave:b}, +theirProps:d,slot:a,attrs:n,slots:t,name:"ListboxOption"})}}}) +;var hk=(e=>(e[e.Open=0]="Open", +e[e.Closed=1]="Closed",e))(hk||{}),fk=(e=>(e[e.Pointer=0]="Pointer", +e[e.Other=1]="Other",e))(fk||{});let mk=Symbol("MenuContext");function gk(e){ +let t=ug(mk,null);if(null===t){ +let t=new Error(`<${e} /> is missing a parent component.`) +;throw Error.captureStackTrace&&Error.captureStackTrace(t,gk),t}return t} +let vk=Hg({name:"Menu",props:{as:{type:[Object,String],default:"template"}}, +setup(e,{slots:t,attrs:n}){ +let r=bm(1),a=bm(null),o=bm(null),i=bm([]),s=bm(""),l=bm(null),c=bm(1) +;function u(e=e=>e){ +let t=null!==l.value?i.value[l.value]:null,n=$w(e(i.value.slice()),(e=>uw(e.dataRef.domRef))),r=t?n.indexOf(t):null +;return-1===r&&(r=null),{items:n,activeItemIndex:r}}let d={menuState:r, +buttonRef:a,itemsRef:o,items:i,searchQuery:s,activeItemIndex:l, +activationTrigger:c,closeMenu:()=>{r.value=1,l.value=null}, +openMenu:()=>r.value=0,goToItem(e,t,n){let r=u(),a=ix(e===ox.Specific?{ +focus:ox.Specific,id:t}:{focus:e},{resolveItems:()=>r.items, +resolveActiveIndex:()=>r.activeItemIndex,resolveId:e=>e.id, +resolveDisabled:e=>e.dataRef.disabled}) +;s.value="",l.value=a,c.value=null!=n?n:1,i.value=r.items},search(e){ +let t=""!==s.value?0:1;s.value+=e.toLowerCase() +;let n=(null!==l.value?i.value.slice(l.value+t).concat(i.value.slice(0,l.value+t)):i.value).find((e=>e.dataRef.textValue.startsWith(s.value)&&!e.dataRef.disabled)),r=n?i.value.indexOf(n):-1 +;-1===r||r===l.value||(l.value=r,c.value=1)},clearSearch(){s.value=""}, +registerItem(e,t){let n=u((n=>[...n,{id:e,dataRef:t}])) +;i.value=n.items,l.value=n.activeItemIndex,c.value=1},unregisterItem(e){ +let t=u((t=>{let n=t.findIndex((t=>t.id===e));return-1!==n&&t.splice(n,1),t})) +;i.value=t.items,l.value=t.activeItemIndex,c.value=1}};return Rw([a,o],((e,t)=>{ +var n +;d.closeMenu(),_w(t,Sw.Loose)||(e.preventDefault(),null==(n=uw(a))||n.focus()) +}),Ty((()=>0===r.value))),cg(mk,d),ex(Ty((()=>dw(r.value,{0:Kw.Open,1:Kw.Closed +})))),()=>{let a={open:0===r.value,close:d.closeMenu};return Hw({ourProps:{}, +theirProps:e,slot:a,slots:t,attrs:n,name:"Menu"})}}}),bk=Hg({name:"MenuButton", +props:{disabled:{type:Boolean,default:!1},as:{type:[Object,String], +default:"button"},id:{type:String,default:null}}, +setup(e,{attrs:t,slots:n,expose:r}){var a +;let o=null!=(a=e.id)?a:`headlessui-menu-button-${cw()}`,i=gk("MenuButton") +;function s(e){switch(e.key){case nx.Space:case nx.Enter:case nx.ArrowDown: +e.preventDefault(),e.stopPropagation(),i.openMenu(),Xm((()=>{var e +;null==(e=uw(i.itemsRef))||e.focus({preventScroll:!0}),i.goToItem(ox.First)})) +;break;case nx.ArrowUp: +e.preventDefault(),e.stopPropagation(),i.openMenu(),Xm((()=>{var e +;null==(e=uw(i.itemsRef))||e.focus({preventScroll:!0}),i.goToItem(ox.Last)}))}} +function l(e){if(e.key===nx.Space)e.preventDefault()}function c(t){ +e.disabled||(0===i.menuState.value?(i.closeMenu(),Xm((()=>{var e +;return null==(e=uw(i.buttonRef))?void 0:e.focus({preventScroll:!0}) +}))):(t.preventDefault(),i.openMenu(),function(e){ +requestAnimationFrame((()=>requestAnimationFrame(e)))}((()=>{var e +;return null==(e=uw(i.itemsRef))?void 0:e.focus({preventScroll:!0})}))))}r({ +el:i.buttonRef,$el:i.buttonRef});let u=Bw(Ty((()=>({as:e.as,type:t.type +}))),i.buttonRef);return()=>{var r;let a={open:0===i.menuState.value},{...d}=e +;return Hw({ourProps:{ref:i.buttonRef,id:o,type:u.value,"aria-haspopup":"menu", +"aria-controls":null==(r=uw(i.itemsRef))?void 0:r.id, +"aria-expanded":0===i.menuState.value,onKeydown:s,onKeyup:l,onClick:c}, +theirProps:d,slot:a,attrs:t,slots:n,name:"MenuButton"})}}}),yk=Hg({ +name:"MenuItems",props:{as:{type:[Object,String],default:"div"},static:{ +type:Boolean,default:!1},unmount:{type:Boolean,default:!0},id:{type:String, +default:null}},setup(e,{attrs:t,slots:n,expose:r}){var a +;let o=null!=(a=e.id)?a:`headlessui-menu-items-${cw()}`,i=gk("MenuItems"),s=bm(null) +;function l(e){var t;switch(s.value&&clearTimeout(s.value),e.key){case nx.Space: +if(""!==i.searchQuery.value)return e.preventDefault(), +e.stopPropagation(),i.search(e.key);case nx.Enter: +if(e.preventDefault(),e.stopPropagation(),null!==i.activeItemIndex.value){ +null==(t=uw(i.items.value[i.activeItemIndex.value].dataRef.domRef))||t.click()} +i.closeMenu(),Aw(uw(i.buttonRef));break;case nx.ArrowDown: +return e.preventDefault(),e.stopPropagation(),i.goToItem(ox.Next) +;case nx.ArrowUp: +return e.preventDefault(),e.stopPropagation(),i.goToItem(ox.Previous) +;case nx.Home:case nx.PageUp: +return e.preventDefault(),e.stopPropagation(),i.goToItem(ox.First);case nx.End: +case nx.PageDown: +return e.preventDefault(),e.stopPropagation(),i.goToItem(ox.Last) +;case nx.Escape:e.preventDefault(),e.stopPropagation(),i.closeMenu(),Xm((()=>{ +var e;return null==(e=uw(i.buttonRef))?void 0:e.focus({preventScroll:!0})})) +;break;case nx.Tab: +e.preventDefault(),e.stopPropagation(),i.closeMenu(),Xm((()=>function(e,t){ +return Pw(kw(),t,{relativeTo:e}) +}(uw(i.buttonRef),e.shiftKey?Ow.Previous:Ow.Next)));break;default: +1===e.key.length&&(i.search(e.key), +s.value=setTimeout((()=>i.clearSearch()),350))}}function c(e){ +if(e.key===nx.Space)e.preventDefault()}r({el:i.itemsRef,$el:i.itemsRef +}),function({container:e,accept:t,walk:n,enabled:r}){fg((()=>{let a=e.value +;if(!a||void 0!==r&&!r.value)return;let o=mw(e);if(!o)return +;let i=Object.assign((e=>t(e)),{acceptNode:t +}),s=o.createTreeWalker(a,NodeFilter.SHOW_ELEMENT,i,!1) +;for(;s.nextNode();)n(s.currentNode)}))}({container:Ty((()=>uw(i.itemsRef))), +enabled:Ty((()=>0===i.menuState.value)), +accept:e=>"menuitem"===e.getAttribute("role")?NodeFilter.FILTER_REJECT:e.hasAttribute("role")?NodeFilter.FILTER_SKIP:NodeFilter.FILTER_ACCEPT, +walk(e){e.setAttribute("role","none")}}) +;let u=Jw(),d=Ty((()=>null!==u?(u.value&Kw.Open)===Kw.Open:0===i.menuState.value)) +;return()=>{var r,a;let s={open:0===i.menuState.value},{...u}=e;return Hw({ +ourProps:{ +"aria-activedescendant":null===i.activeItemIndex.value||null==(r=i.items.value[i.activeItemIndex.value])?void 0:r.id, +"aria-labelledby":null==(a=uw(i.buttonRef))?void 0:a.id,id:o,onKeydown:l, +onKeyup:c,role:"menu",tabIndex:0,ref:i.itemsRef},theirProps:u,slot:s,attrs:t, +slots:n,features:Zw.RenderStrategy|Zw.Static,visible:d.value,name:"MenuItems"})} +}}),Ok=Hg({name:"MenuItem",inheritAttrs:!1,props:{as:{type:[Object,String], +default:"template"},disabled:{type:Boolean,default:!1},id:{type:String, +default:null}},setup(e,{slots:t,attrs:n,expose:r}){var a +;let o=null!=(a=e.id)?a:`headlessui-menu-item-${cw()}`,i=gk("MenuItem"),s=bm(null) +;r({el:s,$el:s}) +;let l=Ty((()=>null!==i.activeItemIndex.value&&i.items.value[i.activeItemIndex.value].id===o)),c=tk(s),u=Ty((()=>({ +disabled:e.disabled,get textValue(){return c()},domRef:s})));function d(t){ +if(e.disabled)return t.preventDefault();i.closeMenu(),Aw(uw(i.buttonRef))} +function p(){if(e.disabled)return i.goToItem(ox.Nothing) +;i.goToItem(ox.Specific,o)} +cv((()=>i.registerItem(o,u))),hv((()=>i.unregisterItem(o))),fg((()=>{ +0===i.menuState.value&&l.value&&0!==i.activationTrigger.value&&Xm((()=>{var e,t +;return null==(t=null==(e=uw(s))?void 0:e.scrollIntoView)?void 0:t.call(e,{ +block:"nearest"})}))}));let h=Uw();function f(e){h.update(e)}function m(t){ +h.wasMoved(t)&&(e.disabled||l.value||i.goToItem(ox.Specific,o,0))}function g(t){ +h.wasMoved(t)&&(e.disabled||l.value&&i.goToItem(ox.Nothing))}return()=>{ +let{disabled:r,...a}=e,c={active:l.value,disabled:r,close:i.closeMenu} +;return Hw({ourProps:{id:o,ref:s,role:"menuitem",tabIndex:!0===r?void 0:-1, +"aria-disabled":!0===r||void 0,onClick:d,onFocus:p,onPointerenter:f, +onMouseenter:f,onPointermove:m,onMousemove:m,onPointerleave:g,onMouseleave:g}, +theirProps:{...n,...a},slot:c,attrs:n,slots:t,name:"MenuItem"})}}}) +;var wk,xk=((wk=xk||{})[wk.Open=0]="Open",wk[wk.Closed=1]="Closed",wk) +;let kk=Symbol("PopoverContext");function Sk(e){let t=ug(kk,null);if(null===t){ +let t=new Error(`<${e} /> is missing a parent <${Ek.name} /> component.`) +;throw Error.captureStackTrace&&Error.captureStackTrace(t,Sk),t}return t} +let _k=Symbol("PopoverGroupContext");function Ak(){return ug(_k,null)} +let Tk=Symbol("PopoverPanelContext");let Ek=Hg({name:"Popover",inheritAttrs:!1, +props:{as:{type:[Object,String],default:"div"}}, +setup(e,{slots:t,attrs:n,expose:r}){var a;let o=bm(null);r({el:o,$el:o}) +;let i=bm(1),s=bm(null),l=bm(null),c=bm(null),u=bm(null),d=Ty((()=>mw(o))),p=Ty((()=>{ +var e,t;if(!uw(s)||!uw(u))return!1 +;for(let c of document.querySelectorAll("body > *"))if(Number(null==c?void 0:c.contains(uw(s)))^Number(null==c?void 0:c.contains(uw(u))))return!0 +;let n=kw(),r=n.indexOf(uw(s)),a=(r+n.length-1)%n.length,o=(r+1)%n.length,i=n[a],l=n[o] +;return!(null!=(e=uw(u))&&e.contains(i)||null!=(t=uw(u))&&t.contains(l))})),h={ +popoverState:i,buttonId:bm(null),panelId:bm(null),panel:u,button:s, +isPortalled:p,beforePanelSentinel:l,afterPanelSentinel:c,togglePopover(){ +i.value=dw(i.value,{0:1,1:0})},closePopover(){1!==i.value&&(i.value=1)}, +close(e){h.closePopover() +;let t=e?e instanceof HTMLElement?e:e.value instanceof HTMLElement?uw(e):uw(h.button):uw(h.button) +;null==t||t.focus()}};cg(kk,h),ex(Ty((()=>dw(i.value,{0:Kw.Open,1:Kw.Closed})))) +;let f={buttonId:h.buttonId,panelId:h.panelId,close(){h.closePopover()} +},m=Ak(),g=null==m?void 0:m.registerPopover,[v,b]=Nx(),y=Sx({ +mainTreeNodeRef:null==m?void 0:m.mainTreeNodeRef,portals:v, +defaultContainers:[s,u]}) +;return fg((()=>null==g?void 0:g(f))),ux(null==(a=d.value)?void 0:a.defaultView,"focus",(e=>{ +var t,n +;e.target!==window&&e.target instanceof HTMLElement&&0===i.value&&(function(){ +var e,t,n,r +;return null!=(r=null==m?void 0:m.isFocusWithinPopoverGroup())?r:(null==(e=d.value)?void 0:e.activeElement)&&((null==(t=uw(s))?void 0:t.contains(d.value.activeElement))||(null==(n=uw(u))?void 0:n.contains(d.value.activeElement))) +}()||s&&u&&(y.contains(e.target)||null!=(t=uw(h.beforePanelSentinel))&&t.contains(e.target)||null!=(n=uw(h.afterPanelSentinel))&&n.contains(e.target)||h.closePopover())) +}),!0),Rw(y.resolveContainers,((e,t)=>{var n +;h.closePopover(),_w(t,Sw.Loose)||(e.preventDefault(), +null==(n=uw(s))||n.focus())}),Ty((()=>0===i.value))),()=>{let r={ +open:0===i.value,close:h.close};return Ey(Lb,[Ey(b,{},(()=>Hw({theirProps:{...e, +...n},ourProps:{ref:o},slot:r,slots:t,attrs:n,name:"Popover" +}))),Ey(y.MainTreeNode)])}}}),Ck=Hg({name:"PopoverButton",props:{as:{ +type:[Object,String],default:"button"},disabled:{type:[Boolean],default:!1},id:{ +type:String,default:null}},inheritAttrs:!1,setup(e,{attrs:t,slots:n,expose:r}){ +var a +;let o=null!=(a=e.id)?a:`headlessui-popover-button-${cw()}`,i=Sk("PopoverButton"),s=Ty((()=>mw(i.button))) +;r({el:i.button,$el:i.button}),cv((()=>{i.buttonId.value=o})),hv((()=>{ +i.buttonId.value=null})) +;let l=Ak(),c=null==l?void 0:l.closeOthers,u=ug(Tk,null),d=Ty((()=>null!==u&&u.value===i.panelId.value)),p=bm(null),h=`headlessui-focus-sentinel-${cw()}` +;d.value||fg((()=>{i.button.value=uw(p)}));let f=Bw(Ty((()=>({as:e.as, +type:t.type}))),p);function m(e){var t,n,r,a,o;if(d.value){ +if(1===i.popoverState.value)return;switch(e.key){case nx.Space:case nx.Enter: +e.preventDefault(), +null==(n=(t=e.target).click)||n.call(t),i.closePopover(),null==(r=uw(i.button))||r.focus() +}}else switch(e.key){case nx.Space:case nx.Enter: +e.preventDefault(),e.stopPropagation(), +1===i.popoverState.value&&(null==c||c(i.buttonId.value)),i.togglePopover();break +;case nx.Escape: +if(0!==i.popoverState.value)return null==c?void 0:c(i.buttonId.value) +;if(!uw(i.button)||null!=(a=s.value)&&a.activeElement&&(null==(o=uw(i.button))||!o.contains(s.value.activeElement)))return +;e.preventDefault(),e.stopPropagation(),i.closePopover()}}function g(e){ +d.value||e.key===nx.Space&&e.preventDefault()}function v(t){var n,r +;e.disabled||(d.value?(i.closePopover(), +null==(n=uw(i.button))||n.focus()):(t.preventDefault(), +t.stopPropagation(),1===i.popoverState.value&&(null==c||c(i.buttonId.value)), +i.togglePopover(),null==(r=uw(i.button))||r.focus()))}function b(e){ +e.preventDefault(),e.stopPropagation()}let y=px();function O(){let e=uw(i.panel) +;e&&dw(y.value,{[dx.Forwards]:()=>Pw(e,Ow.First), +[dx.Backwards]:()=>Pw(e,Ow.Last) +})===ww.Error&&Pw(kw().filter((e=>"true"!==e.dataset.headlessuiFocusGuard)),dw(y.value,{ +[dx.Forwards]:Ow.Next,[dx.Backwards]:Ow.Previous}),{relativeTo:uw(i.button)})} +return()=>{let r=0===i.popoverState.value,a={open:r},{...s}=e,l=d.value?{ref:p, +type:f.value,onKeydown:m,onClick:v}:{ref:p,id:o,type:f.value, +"aria-expanded":0===i.popoverState.value, +"aria-controls":uw(i.panel)?i.panelId.value:void 0, +disabled:!!e.disabled||void 0,onKeydown:m,onKeyup:g,onClick:v,onMousedown:b} +;return Ey(Lb,[Hw({ourProps:l,theirProps:{...t,...s},slot:a,attrs:t,slots:n, +name:"PopoverButton"}),r&&!d.value&&i.isPortalled.value&&Ey(Gw,{id:h, +features:Xw.Focusable,"data-headlessui-focus-guard":!0,as:"button", +type:"button",onFocus:O})])}}}),$k=Hg({name:"PopoverPanel",props:{as:{ +type:[Object,String],default:"div"},static:{type:Boolean,default:!1},unmount:{ +type:Boolean,default:!0},focus:{type:Boolean,default:!1},id:{type:String, +default:null}},inheritAttrs:!1,setup(e,{attrs:t,slots:n,expose:r}){var a +;let o=null!=(a=e.id)?a:`headlessui-popover-panel-${cw()}`,{focus:i}=e,s=Sk("PopoverPanel"),l=Ty((()=>mw(s.panel))),c=`headlessui-focus-sentinel-before-${cw()}`,u=`headlessui-focus-sentinel-after-${cw()}` +;r({el:s.panel,$el:s.panel}),cv((()=>{s.panelId.value=o})),hv((()=>{ +s.panelId.value=null})),cg(Tk,s.panelId),fg((()=>{var e,t +;if(!i||0!==s.popoverState.value||!s.panel)return +;let n=null==(e=l.value)?void 0:e.activeElement +;null!=(t=uw(s.panel))&&t.contains(n)||Pw(uw(s.panel),Ow.First)})) +;let d=Jw(),p=Ty((()=>null!==d?(d.value&Kw.Open)===Kw.Open:0===s.popoverState.value)) +;function h(e){var t,n;if(e.key===nx.Escape){ +if(0!==s.popoverState.value||!uw(s.panel)||l.value&&(null==(t=uw(s.panel))||!t.contains(l.value.activeElement)))return +;e.preventDefault(), +e.stopPropagation(),s.closePopover(),null==(n=uw(s.button))||n.focus()}} +function f(e){var t,n,r,a,o;let i=e.relatedTarget +;i&&uw(s.panel)&&(null!=(t=uw(s.panel))&&t.contains(i)||(s.closePopover(), +(null!=(r=null==(n=uw(s.beforePanelSentinel))?void 0:n.contains)&&r.call(n,i)||null!=(o=null==(a=uw(s.afterPanelSentinel))?void 0:a.contains)&&o.call(a,i))&&i.focus({ +preventScroll:!0})))}let m=px();function g(){let e=uw(s.panel);e&&dw(m.value,{ +[dx.Forwards]:()=>{var t +;Pw(e,Ow.First)===ww.Error&&(null==(t=uw(s.afterPanelSentinel))||t.focus())}, +[dx.Backwards]:()=>{var e;null==(e=uw(s.button))||e.focus({preventScroll:!0})}}) +}function v(){let e=uw(s.panel);e&&dw(m.value,{[dx.Forwards]:()=>{ +let e=uw(s.button),t=uw(s.panel);if(!e)return +;let n=kw(),r=n.indexOf(e),a=n.slice(0,r+1),o=[...n.slice(r+1),...a] +;for(let i of o.slice())if("true"===i.dataset.headlessuiFocusGuard||null!=t&&t.contains(i)){ +let e=o.indexOf(i);-1!==e&&o.splice(e,1)}Pw(o,Ow.First,{sorted:!1})}, +[dx.Backwards]:()=>{var t +;Pw(e,Ow.Previous)===ww.Error&&(null==(t=uw(s.button))||t.focus())}})} +return()=>{let r={open:0===s.popoverState.value,close:s.close},{focus:a,...l}=e +;return Hw({ourProps:{ref:s.panel,id:o,onKeydown:h, +onFocusout:i&&0===s.popoverState.value?f:void 0,tabIndex:-1},theirProps:{...t, +...l},attrs:t,slot:r,slots:{...n,default:(...e)=>{var t +;return[Ey(Lb,[p.value&&s.isPortalled.value&&Ey(Gw,{id:c, +ref:s.beforePanelSentinel,features:Xw.Focusable, +"data-headlessui-focus-guard":!0,as:"button",type:"button",onFocus:g +}),null==(t=n.default)?void 0:t.call(n,...e),p.value&&s.isPortalled.value&&Ey(Gw,{ +id:u,ref:s.afterPanelSentinel,features:Xw.Focusable, +"data-headlessui-focus-guard":!0,as:"button",type:"button",onFocus:v})])]}}, +features:Zw.RenderStrategy|Zw.Static,visible:p.value,name:"PopoverPanel"})}} +}),Pk=Hg({props:{onFocus:{type:Function,required:!0}},setup(e){let t=bm(!0) +;return()=>t.value?Ey(Gw,{as:"button",type:"button",features:Xw.Focusable, +onFocus(n){n.preventDefault();let r,a=50;r=requestAnimationFrame((function n(){ +var o +;if(!(a--<=0))return null!=(o=e.onFocus)&&o.call(e)?(t.value=!1,void cancelAnimationFrame(r)):void(r=requestAnimationFrame(n)) +;r&&cancelAnimationFrame(r)}))}}):null}}) +;var Ik,Dk=(e=>(e[e.Forwards=0]="Forwards", +e[e.Backwards=1]="Backwards",e))(Dk||{}),Mk=((Ik=Mk||{})[Ik.Less=-1]="Less", +Ik[Ik.Equal=0]="Equal",Ik[Ik.Greater=1]="Greater",Ik) +;let Nk=Symbol("TabsContext");function Rk(e){let t=ug(Nk,null);if(null===t){ +let t=new Error(`<${e} /> is missing a parent component.`) +;throw Error.captureStackTrace&&Error.captureStackTrace(t,Rk),t}return t} +let Lk=Symbol("TabsSSRContext"),Bk=Hg({name:"TabGroup",emits:{change:e=>!0}, +props:{as:{type:[Object,String],default:"template"},selectedIndex:{ +type:[Number],default:null},defaultIndex:{type:[Number],default:0},vertical:{ +type:[Boolean],default:!1},manual:{type:[Boolean],default:!1}},inheritAttrs:!1, +setup(e,{slots:t,attrs:n,emit:r}){var a +;let o=bm(null!=(a=e.selectedIndex)?a:e.defaultIndex),i=bm([]),s=bm([]),l=Ty((()=>null!==e.selectedIndex)),c=Ty((()=>l.value?e.selectedIndex:o.value)) +;function u(e){var t +;let n=$w(d.tabs.value,uw),r=$w(d.panels.value,uw),a=n.filter((e=>{var t +;return!(null!=(t=uw(e))&&t.hasAttribute("disabled"))}));if(e<0||e>n.length-1){ +let t=dw(null===o.value?0:Math.sign(e-o.value),{[-1]:()=>1, +0:()=>dw(Math.sign(e),{[-1]:()=>0,0:()=>0,1:()=>1}),1:()=>0}),i=dw(t,{ +0:()=>n.indexOf(a[0]),1:()=>n.indexOf(a[a.length-1])}) +;-1!==i&&(o.value=i),d.tabs.value=n,d.panels.value=r}else{ +let i=n.slice(0,e),s=[...n.slice(e),...i].find((e=>a.includes(e)));if(!s)return +;let l=null!=(t=n.indexOf(s))?t:d.selectedIndex.value +;-1===l&&(l=d.selectedIndex.value),o.value=l,d.tabs.value=n,d.panels.value=r}} +let d={selectedIndex:Ty((()=>{var t,n +;return null!=(n=null!=(t=o.value)?t:e.defaultIndex)?n:null})), +orientation:Ty((()=>e.vertical?"vertical":"horizontal")), +activation:Ty((()=>e.manual?"manual":"auto")),tabs:i,panels:s, +setSelectedIndex(e){c.value!==e&&r("change",e),l.value||u(e)},registerTab(e){ +var t;if(i.value.includes(e))return;let n=i.value[o.value] +;if(i.value.push(e),i.value=$w(i.value,uw),!l.value){ +let e=null!=(t=i.value.indexOf(n))?t:o.value;-1!==e&&(o.value=e)}}, +unregisterTab(e){let t=i.value.indexOf(e);-1!==t&&i.value.splice(t,1)}, +registerPanel(e){s.value.includes(e)||(s.value.push(e),s.value=$w(s.value,uw))}, +unregisterPanel(e){let t=s.value.indexOf(e);-1!==t&&s.value.splice(t,1)}} +;cg(Nk,d);let p=bm({tabs:[],panels:[]}),h=bm(!1);cv((()=>{h.value=!0 +})),cg(Lk,Ty((()=>h.value?null:p.value)));let f=Ty((()=>e.selectedIndex)) +;return cv((()=>{mg([f],(()=>{var t +;return u(null!=(t=e.selectedIndex)?t:e.defaultIndex)}),{immediate:!0}) +})),fg((()=>{if(!l.value||null==c.value||d.tabs.value.length<=0)return +;let e=$w(d.tabs.value,uw) +;e.some(((e,t)=>uw(d.tabs.value[t])!==uw(e)))&&d.setSelectedIndex(e.findIndex((e=>uw(e)===uw(d.tabs.value[c.value])))) +})),()=>{let r={selectedIndex:o.value};return Ey(Lb,[i.value.length<=0&&Ey(Pk,{ +onFocus:()=>{for(let e of i.value){let t=uw(e) +;if(0===(null==t?void 0:t.tabIndex))return t.focus(),!0}return!1}}),Hw({ +theirProps:{...n, +...Ww(e,["selectedIndex","defaultIndex","manual","vertical","onChange"])}, +ourProps:{},slot:r,slots:t,attrs:n,name:"TabGroup"})])}}}),jk=Hg({ +name:"TabList",props:{as:{type:[Object,String],default:"div"}}, +setup(e,{attrs:t,slots:n}){let r=Rk("TabList");return()=>{let a={ +selectedIndex:r.selectedIndex.value};return Hw({ourProps:{role:"tablist", +"aria-orientation":r.orientation.value},theirProps:e,slot:a,attrs:t,slots:n, +name:"TabList"})}}}),Uk=Hg({name:"Tab",props:{as:{type:[Object,String], +default:"button"},disabled:{type:[Boolean],default:!1},id:{type:String, +default:null}},setup(e,{attrs:t,slots:n,expose:r}){var a +;let o=null!=(a=e.id)?a:`headlessui-tabs-tab-${cw()}`,i=Rk("Tab"),s=bm(null);r({ +el:s,$el:s}),cv((()=>i.registerTab(s))),hv((()=>i.unregisterTab(s))) +;let l=ug(Lk),c=Ty((()=>{if(l.value){let e=l.value.tabs.indexOf(o) +;return-1===e?l.value.tabs.push(o)-1:e}return-1})),u=Ty((()=>{ +let e=i.tabs.value.indexOf(s);return-1===e?c.value:e +})),d=Ty((()=>u.value===i.selectedIndex.value));function p(e){var t;let n=e() +;if(n===ww.Success&&"auto"===i.activation.value){ +let e=null==(t=mw(s))?void 0:t.activeElement,n=i.tabs.value.findIndex((t=>uw(t)===e)) +;-1!==n&&i.setSelectedIndex(n)}return n}function h(e){ +let t=i.tabs.value.map((e=>uw(e))).filter(Boolean) +;if(e.key===nx.Space||e.key===nx.Enter)return e.preventDefault(), +e.stopPropagation(),void i.setSelectedIndex(u.value);switch(e.key){case nx.Home: +case nx.PageUp: +return e.preventDefault(),e.stopPropagation(),p((()=>Pw(t,Ow.First))) +;case nx.End:case nx.PageDown: +return e.preventDefault(),e.stopPropagation(),p((()=>Pw(t,Ow.Last)))} +return p((()=>dw(i.orientation.value,{ +vertical:()=>e.key===nx.ArrowUp?Pw(t,Ow.Previous|Ow.WrapAround):e.key===nx.ArrowDown?Pw(t,Ow.Next|Ow.WrapAround):ww.Error, +horizontal:()=>e.key===nx.ArrowLeft?Pw(t,Ow.Previous|Ow.WrapAround):e.key===nx.ArrowRight?Pw(t,Ow.Next|Ow.WrapAround):ww.Error +})))===ww.Success?e.preventDefault():void 0}let f=bm(!1);function m(){var t +;f.value||(f.value=!0,!e.disabled&&(null==(t=uw(s))||t.focus({preventScroll:!0 +}),i.setSelectedIndex(u.value),aw((()=>{f.value=!1}))))}function g(e){ +e.preventDefault()}let v=Bw(Ty((()=>({as:e.as,type:t.type}))),s);return()=>{ +var r,a;let l={selected:d.value,disabled:null!=(r=e.disabled)&&r},{...c}=e +;return Hw({ourProps:{ref:s,onKeydown:h,onMousedown:g,onClick:m,id:o,role:"tab", +type:v.value,"aria-controls":null==(a=uw(i.panels.value[u.value]))?void 0:a.id, +"aria-selected":d.value,tabIndex:d.value?0:-1,disabled:!!e.disabled||void 0}, +theirProps:c,slot:l,attrs:t,slots:n,name:"Tab"})}}}),zk=Hg({name:"TabPanels", +props:{as:{type:[Object,String],default:"div"}},setup(e,{slots:t,attrs:n}){ +let r=Rk("TabPanels");return()=>{let a={selectedIndex:r.selectedIndex.value} +;return Hw({theirProps:e,ourProps:{},slot:a,attrs:n,slots:t,name:"TabPanels"})}} +}),Zk=Hg({name:"TabPanel",props:{as:{type:[Object,String],default:"div"}, +static:{type:Boolean,default:!1},unmount:{type:Boolean,default:!0},id:{ +type:String,default:null},tabIndex:{type:Number,default:0}}, +setup(e,{attrs:t,slots:n,expose:r}){var a +;let o=null!=(a=e.id)?a:`headlessui-tabs-panel-${cw()}`,i=Rk("TabPanel"),s=bm(null) +;r({el:s,$el:s}),cv((()=>i.registerPanel(s))),hv((()=>i.unregisterPanel(s))) +;let l=ug(Lk),c=Ty((()=>{if(l.value){let e=l.value.panels.indexOf(o) +;return-1===e?l.value.panels.push(o)-1:e}return-1})),u=Ty((()=>{ +let e=i.panels.value.indexOf(s);return-1===e?c.value:e +})),d=Ty((()=>u.value===i.selectedIndex.value));return()=>{var r;let a={ +selected:d.value},{tabIndex:l,...c}=e,p={ref:s,id:o,role:"tabpanel", +"aria-labelledby":null==(r=uw(i.tabs.value[u.value]))?void 0:r.id, +tabIndex:d.value?l:-1};return d.value||!e.unmount||e.static?Hw({ourProps:p, +theirProps:c,slot:a,attrs:t,slots:n,features:Zw.Static|Zw.RenderStrategy, +visible:d.value,name:"TabPanel"}):Ey(Gw,{as:"span","aria-hidden":!0,...p})}}}) +;function Fk(e){var t,n,r="" +;if("string"==typeof e||"number"==typeof e)r+=e;else if("object"==typeof e)if(Array.isArray(e)){ +var a=e.length;for(t=0;t"boolean"==typeof e?`${e}`:0===e?"0":e,Vk=(e=new Map,t=null,n)=>({ +nextPart:e,validators:t,classGroupId:n}),qk="-",Wk=[],Xk=e=>{ +const t=Kk(e),{conflictingClassGroups:n,conflictingClassGroupModifiers:r}=e +;return{getClassGroupId:e=>{if(e.startsWith("[")&&e.endsWith("]"))return Yk(e) +;const n=e.split(qk),r=""===n[0]&&n.length>1?1:0;return Gk(n,r,t)}, +getConflictingClassGroupIds:(e,t)=>{if(t){const t=r[e],a=n[e] +;return t?a?((e,t)=>{const n=new Array(e.length+t.length) +;for(let r=0;r{if(0===e.length-t)return n.classGroupId +;const r=e[t],a=n.nextPart.get(r);if(a){const n=Gk(e,t+1,a);if(n)return n} +const o=n.validators;if(null===o)return +;const i=0===t?e.join(qk):e.slice(t).join(qk),s=o.length;for(let l=0;l-1===e.slice(1,-1).indexOf(":")?void 0:(()=>{ +const t=e.slice(1,-1),n=t.indexOf(":"),r=t.slice(0,n) +;return r?"arbitrary.."+r:void 0})(),Kk=e=>{const{theme:t,classGroups:n}=e +;return Jk(n,t)},Jk=(e,t)=>{const n=Vk();for(const r in e){const a=e[r] +;eS(a,n,r,t)}return n},eS=(e,t,n,r)=>{const a=e.length;for(let o=0;o{ +"string"!=typeof e?"function"!=typeof e?aS(e,t,n,r):rS(e,t,n,r):nS(e,t,n) +},nS=(e,t,n)=>{(""===e?t:oS(t,e)).classGroupId=n},rS=(e,t,n,r)=>{ +iS(e)?eS(e(r),t,n,r):(null===t.validators&&(t.validators=[]), +t.validators.push(((e,t)=>({classGroupId:e,validator:t}))(n,e))) +},aS=(e,t,n,r)=>{const a=Object.entries(e),o=a.length;for(let i=0;i{let n=e +;const r=t.split(qk),a=r.length;for(let o=0;o"isThemeGetter"in e&&!0===e.isThemeGetter,sS=e=>{if(e<1)return{ +get:()=>{},set:()=>{}};let t=0,n=Object.create(null),r=Object.create(null) +;const a=(a,o)=>{n[a]=o,t++,t>e&&(t=0,r=n,n=Object.create(null))};return{get(e){ +let t=n[e];return void 0!==t?t:void 0!==(t=r[e])?(a(e,t),t):void 0},set(e,t){ +e in n?n[e]=t:a(e,t)}}},lS=[],cS=(e,t,n,r,a)=>({modifiers:e, +hasImportantModifier:t,baseClassName:n,maybePostfixModifierPosition:r, +isExternal:a}),uS=e=>{const{prefix:t,experimentalParseClassName:n}=e;let r=e=>{ +const t=[];let n,r=0,a=0,o=0;const i=e.length;for(let u=0;uo?n-o:void 0)} +;if(t){const e=t+":",n=r +;r=t=>t.startsWith(e)?n(t.slice(e.length)):cS(lS,!1,t,void 0,!0)}if(n){const e=r +;r=t=>n({className:t,parseClassName:e})}return r},dS=e=>{const t=new Map +;return e.orderSensitiveModifiers.forEach(((e,n)=>{t.set(e,1e6+n)})),e=>{ +const n=[];let r=[];for(let a=0;a0&&(r.sort(),n.push(...r),r=[]),n.push(o)):r.push(o)} +return r.length>0&&(r.sort(),n.push(...r)),n}},pS=/\s+/,hS=e=>{ +if("string"==typeof e)return e;let t,n="" +;for(let r=0;r{let n,r,a,o;const i=e=>{const t=r(e);if(t)return t +;const o=((e,t)=>{ +const{parseClassName:n,getClassGroupId:r,getConflictingClassGroupIds:a,sortModifiers:o}=t,i=[],s=e.trim().split(pS) +;let l="";for(let c=s.length-1;c>=0;c-=1){ +const e=s[c],{isExternal:t,modifiers:u,hasImportantModifier:d,baseClassName:p,maybePostfixModifierPosition:h}=n(e) +;if(t){l=e+(l.length>0?" "+l:l);continue}let f=!!h,m=r(f?p.substring(0,h):p) +;if(!m){if(!f){l=e+(l.length>0?" "+l:l);continue}if(m=r(p),!m){ +l=e+(l.length>0?" "+l:l);continue}f=!1} +const g=0===u.length?"":1===u.length?u[0]:o(u).join(":"),v=d?g+"!":g,b=v+m +;if(i.indexOf(b)>-1)continue;i.push(b);const y=a(m,f) +;for(let n=0;n0?" "+l:l)} +return l})(e,n);return a(e,o),o};return o=s=>{ +const l=t.reduce(((e,t)=>t(e)),e());return n=(e=>({cache:sS(e.cacheSize), +parseClassName:uS(e),sortModifiers:dS(e),...Xk(e) +}))(l),r=n.cache.get,a=n.cache.set,o=i,i(s)},(...e)=>o(((...e)=>{ +let t,n,r=0,a="";for(;r{const t=t=>t[e]||mS;return t.isThemeGetter=!0,t +},vS=/^\[(?:(\w[\w-]*):)?(.+)\]$/i,bS=/^\((?:(\w[\w-]*):)?(.+)\)$/i,yS=/^\d+\/\d+$/,OS=/^(\d+(\.\d+)?)?(xs|sm|md|lg|xl)$/,wS=/\d+(%|px|r?em|[sdl]?v([hwib]|min|max)|pt|pc|in|cm|mm|cap|ch|ex|r?lh|cq(w|h|i|b|min|max))|\b(calc|min|max|clamp)\(.+\)|^0$/,xS=/^(rgba?|hsla?|hwb|(ok)?(lab|lch)|color-mix)\(.+\)$/,kS=/^(inset_)?-?((\d+)?\.?(\d+)[a-z]+|0)_-?((\d+)?\.?(\d+)[a-z]+|0)/,SS=/^(url|image|image-set|cross-fade|element|(repeating-)?(linear|radial|conic)-gradient)\(.+\)$/,_S=e=>yS.test(e),AS=e=>!!e&&!Number.isNaN(Number(e)),TS=e=>!!e&&Number.isInteger(Number(e)),ES=e=>e.endsWith("%")&&AS(e.slice(0,-1)),CS=e=>OS.test(e),$S=()=>!0,PS=e=>wS.test(e)&&!xS.test(e),IS=()=>!1,DS=e=>kS.test(e),MS=e=>SS.test(e),NS=e=>!LS(e)&&!FS(e),RS=e=>GS(e,e_,IS),LS=e=>vS.test(e),BS=e=>GS(e,t_,PS),jS=e=>GS(e,n_,AS),US=e=>GS(e,KS,IS),zS=e=>GS(e,JS,MS),ZS=e=>GS(e,a_,DS),FS=e=>bS.test(e),HS=e=>YS(e,t_),QS=e=>YS(e,r_),VS=e=>YS(e,KS),qS=e=>YS(e,e_),WS=e=>YS(e,JS),XS=e=>YS(e,a_,!0),GS=(e,t,n)=>{ +const r=vS.exec(e);return!!r&&(r[1]?t(r[1]):n(r[2]))},YS=(e,t,n=!1)=>{ +const r=bS.exec(e);return!!r&&(r[1]?t(r[1]):n) +},KS=e=>"position"===e||"percentage"===e,JS=e=>"image"===e||"url"===e,e_=e=>"length"===e||"size"===e||"bg-size"===e,t_=e=>"length"===e,n_=e=>"number"===e,r_=e=>"family-name"===e,a_=e=>"shadow"===e,o_=()=>{ +const e=gS("color"),t=gS("font"),n=gS("text"),r=gS("font-weight"),a=gS("tracking"),o=gS("leading"),i=gS("breakpoint"),s=gS("container"),l=gS("spacing"),c=gS("radius"),u=gS("shadow"),d=gS("inset-shadow"),p=gS("text-shadow"),h=gS("drop-shadow"),f=gS("blur"),m=gS("perspective"),g=gS("aspect"),v=gS("ease"),b=gS("animate"),y=()=>["center","top","bottom","left","right","top-left","left-top","top-right","right-top","bottom-right","right-bottom","bottom-left","left-bottom",FS,LS],O=()=>[FS,LS,l],w=()=>[_S,"full","auto",...O()],x=()=>[TS,"none","subgrid",FS,LS],k=()=>["auto",{ +span:["full",TS,FS,LS] +},TS,FS,LS],S=()=>[TS,"auto",FS,LS],_=()=>["auto","min","max","fr",FS,LS],A=()=>["auto",...O()],T=()=>[_S,"auto","full","dvw","dvh","lvw","lvh","svw","svh","min","max","fit",...O()],E=()=>[e,FS,LS],C=()=>["center","top","bottom","left","right","top-left","left-top","top-right","right-top","bottom-right","right-bottom","bottom-left","left-bottom",VS,US,{ +position:[FS,LS]}],$=()=>["auto","cover","contain",qS,RS,{size:[FS,LS] +}],P=()=>[ES,HS,BS],I=()=>["","none","full",c,FS,LS],D=()=>["",AS,HS,BS],M=()=>[AS,ES,VS,US],N=()=>["","none",f,FS,LS],R=()=>["none",AS,FS,LS],L=()=>["none",AS,FS,LS],B=()=>[AS,FS,LS],j=()=>[_S,"full",...O()] +;return{cacheSize:500,theme:{animate:["spin","ping","pulse","bounce"], +aspect:["video"],blur:[CS],breakpoint:[CS],color:[$S],container:[CS], +"drop-shadow":[CS],ease:["in","out","in-out"],font:[NS], +"font-weight":["thin","extralight","light","normal","medium","semibold","bold","extrabold","black"], +"inset-shadow":[CS],leading:["none","tight","snug","normal","relaxed","loose"], +perspective:["dramatic","near","normal","midrange","distant","none"], +radius:[CS],shadow:[CS],spacing:["px",AS],text:[CS],"text-shadow":[CS], +tracking:["tighter","tight","normal","wide","wider","widest"]},classGroups:{ +aspect:[{aspect:["auto","square",_S,LS,FS,g]}],container:["container"], +columns:[{columns:[AS,LS,FS,s]}],"break-after":[{ +"break-after":["auto","avoid","all","avoid-page","page","left","right","column"] +}],"break-before":[{ +"break-before":["auto","avoid","all","avoid-page","page","left","right","column"] +}],"break-inside":[{"break-inside":["auto","avoid","avoid-page","avoid-column"] +}],"box-decoration":[{"box-decoration":["slice","clone"]}],box:[{ +box:["border","content"]}], +display:["block","inline-block","inline","flex","inline-flex","table","inline-table","table-caption","table-cell","table-column","table-column-group","table-footer-group","table-header-group","table-row-group","table-row","flow-root","grid","inline-grid","contents","list-item","hidden"], +sr:["sr-only","not-sr-only"],float:[{float:["right","left","none","start","end"] +}],clear:[{clear:["left","right","both","none","start","end"]}], +isolation:["isolate","isolation-auto"],"object-fit":[{ +object:["contain","cover","fill","none","scale-down"]}],"object-position":[{ +object:y()}],overflow:[{overflow:["auto","hidden","clip","visible","scroll"]}], +"overflow-x":[{"overflow-x":["auto","hidden","clip","visible","scroll"]}], +"overflow-y":[{"overflow-y":["auto","hidden","clip","visible","scroll"]}], +overscroll:[{overscroll:["auto","contain","none"]}],"overscroll-x":[{ +"overscroll-x":["auto","contain","none"]}],"overscroll-y":[{ +"overscroll-y":["auto","contain","none"]}], +position:["static","fixed","absolute","relative","sticky"],inset:[{inset:w()}], +"inset-x":[{"inset-x":w()}],"inset-y":[{"inset-y":w()}],start:[{start:w()}], +end:[{end:w()}],top:[{top:w()}],right:[{right:w()}],bottom:[{bottom:w()}], +left:[{left:w()}],visibility:["visible","invisible","collapse"],z:[{ +z:[TS,"auto",FS,LS]}],basis:[{basis:[_S,"full","auto",s,...O()]}], +"flex-direction":[{flex:["row","row-reverse","col","col-reverse"]}], +"flex-wrap":[{flex:["nowrap","wrap","wrap-reverse"]}],flex:[{ +flex:[AS,_S,"auto","initial","none",LS]}],grow:[{grow:["",AS,FS,LS]}],shrink:[{ +shrink:["",AS,FS,LS]}],order:[{order:[TS,"first","last","none",FS,LS]}], +"grid-cols":[{"grid-cols":x()}],"col-start-end":[{col:k()}],"col-start":[{ +"col-start":S()}],"col-end":[{"col-end":S()}],"grid-rows":[{"grid-rows":x()}], +"row-start-end":[{row:k()}],"row-start":[{"row-start":S()}],"row-end":[{ +"row-end":S()}],"grid-flow":[{ +"grid-flow":["row","col","dense","row-dense","col-dense"]}],"auto-cols":[{ +"auto-cols":_()}],"auto-rows":[{"auto-rows":_()}],gap:[{gap:O()}],"gap-x":[{ +"gap-x":O()}],"gap-y":[{"gap-y":O()}],"justify-content":[{ +justify:["start","end","center","between","around","evenly","stretch","baseline","center-safe","end-safe","normal"] +}],"justify-items":[{ +"justify-items":["start","end","center","stretch","center-safe","end-safe","normal"] +}],"justify-self":[{ +"justify-self":["auto","start","end","center","stretch","center-safe","end-safe"] +}],"align-content":[{ +content:["normal","start","end","center","between","around","evenly","stretch","baseline","center-safe","end-safe"] +}],"align-items":[{ +items:["start","end","center","stretch","center-safe","end-safe",{ +baseline:["","last"]}]}],"align-self":[{ +self:["auto","start","end","center","stretch","center-safe","end-safe",{ +baseline:["","last"]}]}],"place-content":[{ +"place-content":["start","end","center","between","around","evenly","stretch","baseline","center-safe","end-safe"] +}],"place-items":[{ +"place-items":["start","end","center","stretch","center-safe","end-safe","baseline"] +}],"place-self":[{ +"place-self":["auto","start","end","center","stretch","center-safe","end-safe"] +}],p:[{p:O()}],px:[{px:O()}],py:[{py:O()}],ps:[{ps:O()}],pe:[{pe:O()}],pt:[{ +pt:O()}],pr:[{pr:O()}],pb:[{pb:O()}],pl:[{pl:O()}],m:[{m:A()}],mx:[{mx:A()}], +my:[{my:A()}],ms:[{ms:A()}],me:[{me:A()}],mt:[{mt:A()}],mr:[{mr:A()}],mb:[{ +mb:A()}],ml:[{ml:A()}],"space-x":[{"space-x":O()}], +"space-x-reverse":["space-x-reverse"],"space-y":[{"space-y":O()}], +"space-y-reverse":["space-y-reverse"],size:[{size:T()}],w:[{ +w:[s,"screen",...T()]}],"min-w":[{"min-w":[s,"screen","none",...T()]}], +"max-w":[{"max-w":[s,"screen","none","prose",{screen:[i]},...T()]}],h:[{ +h:["screen","lh",...T()]}],"min-h":[{"min-h":["screen","lh","none",...T()]}], +"max-h":[{"max-h":["screen","lh",...T()]}],"font-size":[{text:["base",n,HS,BS] +}],"font-smoothing":["antialiased","subpixel-antialiased"], +"font-style":["italic","not-italic"],"font-weight":[{font:[r,FS,jS]}], +"font-stretch":[{ +"font-stretch":["ultra-condensed","extra-condensed","condensed","semi-condensed","normal","semi-expanded","expanded","extra-expanded","ultra-expanded",ES,LS] +}],"font-family":[{font:[QS,LS,t]}],"fvn-normal":["normal-nums"], +"fvn-ordinal":["ordinal"],"fvn-slashed-zero":["slashed-zero"], +"fvn-figure":["lining-nums","oldstyle-nums"], +"fvn-spacing":["proportional-nums","tabular-nums"], +"fvn-fraction":["diagonal-fractions","stacked-fractions"],tracking:[{ +tracking:[a,FS,LS]}],"line-clamp":[{"line-clamp":[AS,"none",FS,jS]}],leading:[{ +leading:[o,...O()]}],"list-image":[{"list-image":["none",FS,LS]}], +"list-style-position":[{list:["inside","outside"]}],"list-style-type":[{ +list:["disc","decimal","none",FS,LS]}],"text-alignment":[{ +text:["left","center","right","justify","start","end"]}],"placeholder-color":[{ +placeholder:E()}],"text-color":[{text:E()}], +"text-decoration":["underline","overline","line-through","no-underline"], +"text-decoration-style":[{decoration:["solid","dashed","dotted","double","wavy"] +}],"text-decoration-thickness":[{decoration:[AS,"from-font","auto",FS,BS]}], +"text-decoration-color":[{decoration:E()}],"underline-offset":[{ +"underline-offset":[AS,"auto",FS,LS]}], +"text-transform":["uppercase","lowercase","capitalize","normal-case"], +"text-overflow":["truncate","text-ellipsis","text-clip"],"text-wrap":[{ +text:["wrap","nowrap","balance","pretty"]}],indent:[{indent:O()}], +"vertical-align":[{ +align:["baseline","top","middle","bottom","text-top","text-bottom","sub","super",FS,LS] +}],whitespace:[{ +whitespace:["normal","nowrap","pre","pre-line","pre-wrap","break-spaces"]}], +break:[{break:["normal","words","all","keep"]}],wrap:[{ +wrap:["break-word","anywhere","normal"]}],hyphens:[{ +hyphens:["none","manual","auto"]}],content:[{content:["none",FS,LS]}], +"bg-attachment":[{bg:["fixed","local","scroll"]}],"bg-clip":[{ +"bg-clip":["border","padding","content","text"]}],"bg-origin":[{ +"bg-origin":["border","padding","content"]}],"bg-position":[{bg:C()}], +"bg-repeat":[{bg:["no-repeat",{repeat:["","x","y","space","round"]}]}], +"bg-size":[{bg:$()}],"bg-image":[{bg:["none",{linear:[{ +to:["t","tr","r","br","b","bl","l","tl"]},TS,FS,LS],radial:["",FS,LS], +conic:[TS,FS,LS]},WS,zS]}],"bg-color":[{bg:E()}],"gradient-from-pos":[{from:P() +}],"gradient-via-pos":[{via:P()}],"gradient-to-pos":[{to:P()}], +"gradient-from":[{from:E()}],"gradient-via":[{via:E()}],"gradient-to":[{to:E() +}],rounded:[{rounded:I()}],"rounded-s":[{"rounded-s":I()}],"rounded-e":[{ +"rounded-e":I()}],"rounded-t":[{"rounded-t":I()}],"rounded-r":[{"rounded-r":I() +}],"rounded-b":[{"rounded-b":I()}],"rounded-l":[{"rounded-l":I()}], +"rounded-ss":[{"rounded-ss":I()}],"rounded-se":[{"rounded-se":I()}], +"rounded-ee":[{"rounded-ee":I()}],"rounded-es":[{"rounded-es":I()}], +"rounded-tl":[{"rounded-tl":I()}],"rounded-tr":[{"rounded-tr":I()}], +"rounded-br":[{"rounded-br":I()}],"rounded-bl":[{"rounded-bl":I()}], +"border-w":[{border:D()}],"border-w-x":[{"border-x":D()}],"border-w-y":[{ +"border-y":D()}],"border-w-s":[{"border-s":D()}],"border-w-e":[{"border-e":D() +}],"border-w-t":[{"border-t":D()}],"border-w-r":[{"border-r":D()}], +"border-w-b":[{"border-b":D()}],"border-w-l":[{"border-l":D()}],"divide-x":[{ +"divide-x":D()}],"divide-x-reverse":["divide-x-reverse"],"divide-y":[{ +"divide-y":D()}],"divide-y-reverse":["divide-y-reverse"],"border-style":[{ +border:["solid","dashed","dotted","double","hidden","none"]}],"divide-style":[{ +divide:["solid","dashed","dotted","double","hidden","none"]}],"border-color":[{ +border:E()}],"border-color-x":[{"border-x":E()}],"border-color-y":[{ +"border-y":E()}],"border-color-s":[{"border-s":E()}],"border-color-e":[{ +"border-e":E()}],"border-color-t":[{"border-t":E()}],"border-color-r":[{ +"border-r":E()}],"border-color-b":[{"border-b":E()}],"border-color-l":[{ +"border-l":E()}],"divide-color":[{divide:E()}],"outline-style":[{ +outline:["solid","dashed","dotted","double","none","hidden"]}], +"outline-offset":[{"outline-offset":[AS,FS,LS]}],"outline-w":[{ +outline:["",AS,HS,BS]}],"outline-color":[{outline:E()}],shadow:[{ +shadow:["","none",u,XS,ZS]}],"shadow-color":[{shadow:E()}],"inset-shadow":[{ +"inset-shadow":["none",d,XS,ZS]}],"inset-shadow-color":[{"inset-shadow":E()}], +"ring-w":[{ring:D()}],"ring-w-inset":["ring-inset"],"ring-color":[{ring:E()}], +"ring-offset-w":[{"ring-offset":[AS,BS]}],"ring-offset-color":[{ +"ring-offset":E()}],"inset-ring-w":[{"inset-ring":D()}],"inset-ring-color":[{ +"inset-ring":E()}],"text-shadow":[{"text-shadow":["none",p,XS,ZS]}], +"text-shadow-color":[{"text-shadow":E()}],opacity:[{opacity:[AS,FS,LS]}], +"mix-blend":[{ +"mix-blend":["normal","multiply","screen","overlay","darken","lighten","color-dodge","color-burn","hard-light","soft-light","difference","exclusion","hue","saturation","color","luminosity","plus-darker","plus-lighter"] +}],"bg-blend":[{ +"bg-blend":["normal","multiply","screen","overlay","darken","lighten","color-dodge","color-burn","hard-light","soft-light","difference","exclusion","hue","saturation","color","luminosity"] +}],"mask-clip":[{ +"mask-clip":["border","padding","content","fill","stroke","view"] +},"mask-no-clip"],"mask-composite":[{ +mask:["add","subtract","intersect","exclude"]}],"mask-image-linear-pos":[{ +"mask-linear":[AS]}],"mask-image-linear-from-pos":[{"mask-linear-from":M()}], +"mask-image-linear-to-pos":[{"mask-linear-to":M()}], +"mask-image-linear-from-color":[{"mask-linear-from":E()}], +"mask-image-linear-to-color":[{"mask-linear-to":E()}],"mask-image-t-from-pos":[{ +"mask-t-from":M()}],"mask-image-t-to-pos":[{"mask-t-to":M()}], +"mask-image-t-from-color":[{"mask-t-from":E()}],"mask-image-t-to-color":[{ +"mask-t-to":E()}],"mask-image-r-from-pos":[{"mask-r-from":M()}], +"mask-image-r-to-pos":[{"mask-r-to":M()}],"mask-image-r-from-color":[{ +"mask-r-from":E()}],"mask-image-r-to-color":[{"mask-r-to":E()}], +"mask-image-b-from-pos":[{"mask-b-from":M()}],"mask-image-b-to-pos":[{ +"mask-b-to":M()}],"mask-image-b-from-color":[{"mask-b-from":E()}], +"mask-image-b-to-color":[{"mask-b-to":E()}],"mask-image-l-from-pos":[{ +"mask-l-from":M()}],"mask-image-l-to-pos":[{"mask-l-to":M()}], +"mask-image-l-from-color":[{"mask-l-from":E()}],"mask-image-l-to-color":[{ +"mask-l-to":E()}],"mask-image-x-from-pos":[{"mask-x-from":M()}], +"mask-image-x-to-pos":[{"mask-x-to":M()}],"mask-image-x-from-color":[{ +"mask-x-from":E()}],"mask-image-x-to-color":[{"mask-x-to":E()}], +"mask-image-y-from-pos":[{"mask-y-from":M()}],"mask-image-y-to-pos":[{ +"mask-y-to":M()}],"mask-image-y-from-color":[{"mask-y-from":E()}], +"mask-image-y-to-color":[{"mask-y-to":E()}],"mask-image-radial":[{ +"mask-radial":[FS,LS]}],"mask-image-radial-from-pos":[{"mask-radial-from":M()}], +"mask-image-radial-to-pos":[{"mask-radial-to":M()}], +"mask-image-radial-from-color":[{"mask-radial-from":E()}], +"mask-image-radial-to-color":[{"mask-radial-to":E()}], +"mask-image-radial-shape":[{"mask-radial":["circle","ellipse"]}], +"mask-image-radial-size":[{"mask-radial":[{closest:["side","corner"], +farthest:["side","corner"]}]}],"mask-image-radial-pos":[{ +"mask-radial-at":["center","top","bottom","left","right","top-left","left-top","top-right","right-top","bottom-right","right-bottom","bottom-left","left-bottom"] +}],"mask-image-conic-pos":[{"mask-conic":[AS]}],"mask-image-conic-from-pos":[{ +"mask-conic-from":M()}],"mask-image-conic-to-pos":[{"mask-conic-to":M()}], +"mask-image-conic-from-color":[{"mask-conic-from":E()}], +"mask-image-conic-to-color":[{"mask-conic-to":E()}],"mask-mode":[{ +mask:["alpha","luminance","match"]}],"mask-origin":[{ +"mask-origin":["border","padding","content","fill","stroke","view"]}], +"mask-position":[{mask:C()}],"mask-repeat":[{mask:["no-repeat",{ +repeat:["","x","y","space","round"]}]}],"mask-size":[{mask:$()}],"mask-type":[{ +"mask-type":["alpha","luminance"]}],"mask-image":[{mask:["none",FS,LS]}], +filter:[{filter:["","none",FS,LS]}],blur:[{blur:N()}],brightness:[{ +brightness:[AS,FS,LS]}],contrast:[{contrast:[AS,FS,LS]}],"drop-shadow":[{ +"drop-shadow":["","none",h,XS,ZS]}],"drop-shadow-color":[{"drop-shadow":E()}], +grayscale:[{grayscale:["",AS,FS,LS]}],"hue-rotate":[{"hue-rotate":[AS,FS,LS]}], +invert:[{invert:["",AS,FS,LS]}],saturate:[{saturate:[AS,FS,LS]}],sepia:[{ +sepia:["",AS,FS,LS]}],"backdrop-filter":[{"backdrop-filter":["","none",FS,LS]}], +"backdrop-blur":[{"backdrop-blur":N()}],"backdrop-brightness":[{ +"backdrop-brightness":[AS,FS,LS]}],"backdrop-contrast":[{ +"backdrop-contrast":[AS,FS,LS]}],"backdrop-grayscale":[{ +"backdrop-grayscale":["",AS,FS,LS]}],"backdrop-hue-rotate":[{ +"backdrop-hue-rotate":[AS,FS,LS]}],"backdrop-invert":[{ +"backdrop-invert":["",AS,FS,LS]}],"backdrop-opacity":[{ +"backdrop-opacity":[AS,FS,LS]}],"backdrop-saturate":[{ +"backdrop-saturate":[AS,FS,LS]}],"backdrop-sepia":[{ +"backdrop-sepia":["",AS,FS,LS]}],"border-collapse":[{ +border:["collapse","separate"]}],"border-spacing":[{"border-spacing":O()}], +"border-spacing-x":[{"border-spacing-x":O()}],"border-spacing-y":[{ +"border-spacing-y":O()}],"table-layout":[{table:["auto","fixed"]}],caption:[{ +caption:["top","bottom"]}],transition:[{ +transition:["","all","colors","opacity","shadow","transform","none",FS,LS]}], +"transition-behavior":[{transition:["normal","discrete"]}],duration:[{ +duration:[AS,"initial",FS,LS]}],ease:[{ease:["linear","initial",v,FS,LS]}], +delay:[{delay:[AS,FS,LS]}],animate:[{animate:["none",b,FS,LS]}],backface:[{ +backface:["hidden","visible"]}],perspective:[{perspective:[m,FS,LS]}], +"perspective-origin":[{"perspective-origin":y()}],rotate:[{rotate:R()}], +"rotate-x":[{"rotate-x":R()}],"rotate-y":[{"rotate-y":R()}],"rotate-z":[{ +"rotate-z":R()}],scale:[{scale:L()}],"scale-x":[{"scale-x":L()}],"scale-y":[{ +"scale-y":L()}],"scale-z":[{"scale-z":L()}],"scale-3d":["scale-3d"],skew:[{ +skew:B()}],"skew-x":[{"skew-x":B()}],"skew-y":[{"skew-y":B()}],transform:[{ +transform:[FS,LS,"","none","gpu","cpu"]}],"transform-origin":[{origin:y()}], +"transform-style":[{transform:["3d","flat"]}],translate:[{translate:j()}], +"translate-x":[{"translate-x":j()}],"translate-y":[{"translate-y":j()}], +"translate-z":[{"translate-z":j()}],"translate-none":["translate-none"], +accent:[{accent:E()}],appearance:[{appearance:["none","auto"]}],"caret-color":[{ +caret:E()}],"color-scheme":[{ +scheme:["normal","dark","light","light-dark","only-dark","only-light"]}], +cursor:[{ +cursor:["auto","default","pointer","wait","text","move","help","not-allowed","none","context-menu","progress","cell","crosshair","vertical-text","alias","copy","no-drop","grab","grabbing","all-scroll","col-resize","row-resize","n-resize","e-resize","s-resize","w-resize","ne-resize","nw-resize","se-resize","sw-resize","ew-resize","ns-resize","nesw-resize","nwse-resize","zoom-in","zoom-out",FS,LS] +}],"field-sizing":[{"field-sizing":["fixed","content"]}],"pointer-events":[{ +"pointer-events":["auto","none"]}],resize:[{resize:["none","","y","x"]}], +"scroll-behavior":[{scroll:["auto","smooth"]}],"scroll-m":[{"scroll-m":O()}], +"scroll-mx":[{"scroll-mx":O()}],"scroll-my":[{"scroll-my":O()}],"scroll-ms":[{ +"scroll-ms":O()}],"scroll-me":[{"scroll-me":O()}],"scroll-mt":[{"scroll-mt":O() +}],"scroll-mr":[{"scroll-mr":O()}],"scroll-mb":[{"scroll-mb":O()}], +"scroll-ml":[{"scroll-ml":O()}],"scroll-p":[{"scroll-p":O()}],"scroll-px":[{ +"scroll-px":O()}],"scroll-py":[{"scroll-py":O()}],"scroll-ps":[{"scroll-ps":O() +}],"scroll-pe":[{"scroll-pe":O()}],"scroll-pt":[{"scroll-pt":O()}], +"scroll-pr":[{"scroll-pr":O()}],"scroll-pb":[{"scroll-pb":O()}],"scroll-pl":[{ +"scroll-pl":O()}],"snap-align":[{snap:["start","end","center","align-none"]}], +"snap-stop":[{snap:["normal","always"]}],"snap-type":[{ +snap:["none","x","y","both"]}],"snap-strictness":[{ +snap:["mandatory","proximity"]}],touch:[{touch:["auto","none","manipulation"]}], +"touch-x":[{"touch-pan":["x","left","right"]}],"touch-y":[{ +"touch-pan":["y","up","down"]}],"touch-pz":["touch-pinch-zoom"],select:[{ +select:["none","text","all","auto"]}],"will-change":[{ +"will-change":["auto","scroll","contents","transform",FS,LS]}],fill:[{ +fill:["none",...E()]}],"stroke-w":[{stroke:[AS,HS,BS,jS]}],stroke:[{ +stroke:["none",...E()]}],"forced-color-adjust":[{ +"forced-color-adjust":["auto","none"]}]},conflictingClassGroups:{ +overflow:["overflow-x","overflow-y"],overscroll:["overscroll-x","overscroll-y"], +inset:["inset-x","inset-y","start","end","top","right","bottom","left"], +"inset-x":["right","left"],"inset-y":["top","bottom"], +flex:["basis","grow","shrink"],gap:["gap-x","gap-y"], +p:["px","py","ps","pe","pt","pr","pb","pl"],px:["pr","pl"],py:["pt","pb"], +m:["mx","my","ms","me","mt","mr","mb","ml"],mx:["mr","ml"],my:["mt","mb"], +size:["w","h"],"font-size":["leading"], +"fvn-normal":["fvn-ordinal","fvn-slashed-zero","fvn-figure","fvn-spacing","fvn-fraction"], +"fvn-ordinal":["fvn-normal"],"fvn-slashed-zero":["fvn-normal"], +"fvn-figure":["fvn-normal"],"fvn-spacing":["fvn-normal"], +"fvn-fraction":["fvn-normal"],"line-clamp":["display","overflow"], +rounded:["rounded-s","rounded-e","rounded-t","rounded-r","rounded-b","rounded-l","rounded-ss","rounded-se","rounded-ee","rounded-es","rounded-tl","rounded-tr","rounded-br","rounded-bl"], +"rounded-s":["rounded-ss","rounded-es"],"rounded-e":["rounded-se","rounded-ee"], +"rounded-t":["rounded-tl","rounded-tr"],"rounded-r":["rounded-tr","rounded-br"], +"rounded-b":["rounded-br","rounded-bl"],"rounded-l":["rounded-tl","rounded-bl"], +"border-spacing":["border-spacing-x","border-spacing-y"], +"border-w":["border-w-x","border-w-y","border-w-s","border-w-e","border-w-t","border-w-r","border-w-b","border-w-l"], +"border-w-x":["border-w-r","border-w-l"], +"border-w-y":["border-w-t","border-w-b"], +"border-color":["border-color-x","border-color-y","border-color-s","border-color-e","border-color-t","border-color-r","border-color-b","border-color-l"], +"border-color-x":["border-color-r","border-color-l"], +"border-color-y":["border-color-t","border-color-b"], +translate:["translate-x","translate-y","translate-none"], +"translate-none":["translate","translate-x","translate-y","translate-z"], +"scroll-m":["scroll-mx","scroll-my","scroll-ms","scroll-me","scroll-mt","scroll-mr","scroll-mb","scroll-ml"], +"scroll-mx":["scroll-mr","scroll-ml"],"scroll-my":["scroll-mt","scroll-mb"], +"scroll-p":["scroll-px","scroll-py","scroll-ps","scroll-pe","scroll-pt","scroll-pr","scroll-pb","scroll-pl"], +"scroll-px":["scroll-pr","scroll-pl"],"scroll-py":["scroll-pt","scroll-pb"], +touch:["touch-x","touch-y","touch-pz"],"touch-x":["touch"],"touch-y":["touch"], +"touch-pz":["touch"]},conflictingClassGroupModifiers:{"font-size":["leading"]}, +orderSensitiveModifiers:["*","**","after","backdrop","before","details-content","file","first-letter","first-line","marker","placeholder","selection"] +}},i_=(e,t,n)=>{void 0!==n&&(e[t]=n)},s_=(e,t)=>{ +if(t)for(const n in t)i_(e,n,t[n])},l_=(e,t)=>{if(t)for(const n in t)c_(e,t,n) +},c_=(e,t,n)=>{const r=t[n];void 0!==r&&(e[n]=e[n]?e[n].concat(r):r) +},u_=((e,...t)=>"function"==typeof e?fS(o_,e,...t):fS((()=>((e,{cacheSize:t,prefix:n,experimentalParseClassName:r,extend:a={},override:o={}})=>(i_(e,"cacheSize",t), +i_(e,"prefix",n), +i_(e,"experimentalParseClassName",r),s_(e.theme,o.theme),s_(e.classGroups,o.classGroups), +s_(e.conflictingClassGroups,o.conflictingClassGroups), +s_(e.conflictingClassGroupModifiers,o.conflictingClassGroupModifiers), +i_(e,"orderSensitiveModifiers",o.orderSensitiveModifiers), +l_(e.theme,a.theme),l_(e.classGroups,a.classGroups), +l_(e.conflictingClassGroups,a.conflictingClassGroups), +l_(e.conflictingClassGroupModifiers,a.conflictingClassGroupModifiers), +c_(e,a,"orderSensitiveModifiers"),e))(o_(),e)),...t))({extend:{classGroups:{ +"font-size":["text-3xs","text-xxs"], +"font-weight":["font-sidebar","font-sidebar-active"]}}}),{cva:d_,cx:p_}=(e=>{ +const t=function(){ +for(var t=arguments.length,n=new Array(t),r=0;r{const r=Object.fromEntries(Object.entries(e||{}).filter((e=>{ +let[t]=e;return!["class","className"].includes(t)}))) +;return t(n.map((e=>e(r))),null==e?void 0:e.class,null==e?void 0:e.className)}}, +cva:e=>n=>{var r +;if(null==(null==e?void 0:e.variants))return t(null==e?void 0:e.base,null==n?void 0:n.class,null==n?void 0:n.className) +;const{variants:a,defaultVariants:o}=e,i=Object.keys(a).map((e=>{ +const t=null==n?void 0:n[e],r=null==o?void 0:o[e],i=Qk(t)||Qk(r);return a[e][i] +})),s={...o,...n&&Object.entries(n).reduce(((e,t)=>{let[n,r]=t +;return void 0===r?e:{...e,[n]:r}}),{}) +},l=null==e||null===(r=e.compoundVariants)||void 0===r?void 0:r.reduce(((e,t)=>{ +let{class:n,className:r,...a}=t;return Object.entries(a).every((e=>{let[t,n]=e +;const r=s[t];return Array.isArray(n)?n.includes(r):r===n}))?[...e,n,r]:e}),[]) +;return t(null==e?void 0:e.base,i,l,null==n?void 0:n.class,null==n?void 0:n.className) +},cx:t}})({hooks:{onComplete:e=>u_(e)}});function h_(){const e=Mv(),t=Ty((()=>{ +const{class:t,style:n,...r}=e;return{class:t||"",style:n,rest:r}}));return{ +cx:function(...e){return{class:p_(...e,t.value.class),style:t.value.style, +...t.value.rest}},stylingAttrsCx:function(...e){return{ +class:p_(...e,t.value.class),style:t.value.style}}, +otherAttrs:Ty((()=>t.value.rest))}}const f_={ +solid:["scalar-button-solid","bg-b-btn text-c-btn focus-visible:border-c-btn active:bg-b-btn hover:bg-h-btn outline-offset-1"], +outlined:["scalar-button-outlined","active:bg-btn-1 border border-solid border-border bg-b-1 text-c-1 hover:bg-b-2"], +ghost:["scalar-button-ghost","bg-transparent text-c-3 active:text-c-1 hover:text-c-1"], +gradient:["scalar-button-gradient","border bg-b-1.5 bg-linear-to-b from-b-1 to-b-2 hover:bg-linear-to-t","dark:bg-linear-to-t dark:hover:bg-linear-to-b"], +danger:["scalar-button-danger","bg-c-danger text-white active:brightness-90 hover:brightness-90"] +},m_={class:"circular-loader"},g_=Hg({inheritAttrs:!1,__name:"ScalarLoading", +props:{loader:{},size:{}},setup(e){const{cx:t}=h_(),n=d_({variants:{size:{ +xs:"size-3",sm:"size-3.5",md:"size-4",lg:"size-5",xl:"size-6","2xl":"size-8", +"3xl":"size-10",full:"size-full"}},defaultVariants:{size:"full"}}) +;return(r,a)=>e.loader?(Fb(),qb("div",Mh(cy({key:0 +},xm(t)("loader-wrapper",xm(n)({size:e.size})))),[(Fb(),qb("svg",{ +class:Dh(["svg-loader",{"icon-is-valid":e.loader.isValid, +"icon-is-invalid":e.loader.isInvalid}]),viewBox:"0 0 100 100", +xmlns:"http://www.w3.org/2000/svg","xmlns:xlink":"http://www.w3.org/1999/xlink" +},[a[0]||(a[0]=ay('',5)),Jb("g",m_,[Jb("circle",{ +class:Dh(["loader-path",{"loader-path-off":!e.loader.isLoading}]),cx:"50", +cy:"50",fill:"none",r:"20","stroke-width":"3"},null,2)])],2))],16)):oy("",!0)} +}),v_=(e,t)=>{const n=e.__vccOpts||e;for(const[r,a]of t)n[r]=a;return n +},b_=v_(g_,[["__scopeId","data-v-27df5cd8"]]),y_={key:3,class:"centered" +},O_=Hg({inheritAttrs:!1,__name:"ScalarButton",props:{is:{default:"button"}, +variant:{default:"solid"},size:{default:"md"},disabled:{type:Boolean},icon:{ +type:[Object,Function]},loader:{}},setup(e){const t=d_({ +base:"scalar-button flex cursor-pointer items-center justify-center rounded font-medium -outline-offset-1", +variants:{disabled:{true:"bg-b-2 text-color-3 shadow-none"},size:{ +xs:"px-2 py-1 text-xs leading-5",sm:"px-3.5 py-2 text-sm leading-5", +md:"px-5 py-3 text-sm leading-5"},variant:f_},compoundVariants:[{disabled:!0, +variant:["solid","outlined","ghost","gradient","danger"], +class:"bg-b-2 text-c-3 shadow-none hover:bg-b-[_] cursor-not-allowed active:bg-b-[_] hover:text-c-[_] active:text-c-[_]" +},{disabled:!0,variant:["gradient"], +class:"to-b-1.5 bg-linear-to-b hover:bg-linear-to-b dark:hover:bg-linear-to-t"}] +}),n=d_({base:"shrink-0",variants:{size:{xs:"size-2.75 -ml-0.25 mr-1", +sm:"size-3.25 -ml-0.5 mr-1.5",md:"size-3.5 -ml-0.5 mr-1.5"}}}),r=d_({variants:{ +size:{xs:"size-4",sm:"size-5",md:"size-6"}}}),{cx:a}=h_() +;return(o,i)=>(Fb(),Wb(wv(e.is),cy({"aria-disabled":e.disabled||void 0, +type:"button"===e.is?"button":void 0},xm(a)(xm(t)({disabled:e.disabled, +size:e.size,variant:e.variant}),{relative:e.loader?.isActive})),{ +default:ig((()=>[o.$slots.icon||e.icon?(Fb(),qb("div",{key:0,class:Dh([xm(n)({ +size:e.size}),{invisible:e.loader?.isActive}]) +},[Av(o.$slots,"icon",{},(()=>[(Fb(),Wb(wv(e.icon),{class:"size-full" +}))]))],2)):oy("",!0),e.loader?(Fb(),qb("span",{key:1,class:Dh({ +invisible:e.loader?.isActive}) +},[Av(o.$slots,"default")],2)):Av(o.$slots,"default",{key:2 +}),e.loader?.isActive?(Fb(),qb("div",y_,[ey(xm(b_),{class:Dh(xm(r)({size:e.size +})),loader:e.loader},null,8,["class","loader"])])):oy("",!0)])),_:3 +},16,["aria-disabled","type"]))}}),w_=Symbol(),x_=Hg({inheritAttrs:!1, +__name:"ScalarCard",props:{label:{}},setup(e){const{id:t}=(()=>{const e=bm() +;return cg(w_,e),{id:e}})(),n=Ty((()=>e.label?{"aria-label":e.label}:t.value?{ +"aria-labelledby":t.value}:{})),{cx:r}=h_();return(e,t)=>(Fb(),qb("div",cy({ +role:"group"},{...n.value, +...xm(r)("scalar-card bg-b-2 flex flex-col divide-y rounded-xl border *:first:rounded-t-[inherit] *:last:rounded-b-[inherit]") +}),[Av(e.$slots,"default")],16))}}),k_=Hg({inheritAttrs:!1, +__name:"ScalarCardSection",setup(e){const{cx:t}=h_() +;return(e,n)=>(Fb(),qb("div",Mh(ty(xm(t)("scalar-card-content flex overflow-auto"))),[Av(e.$slots,"default")],16)) +}}),S_=Hg({inheritAttrs:!1,__name:"ScalarCardFooter",setup(e){const{cx:t}=h_() +;return(e,n)=>(Fb(),Wb(k_,Mh(ty(xm(t)("scalar-card-footer"))),{ +default:ig((()=>[Av(e.$slots,"default")])),_:3},16))}}),__=["id"],A_={key:0, +class:"flex"},T_=Hg({inheritAttrs:!1,__name:"ScalarCardHeader",setup(e){ +const{cx:t}=h_(),n=Qg();return(e=>{const t=ug(w_,void 0);t&&(t.value=e) +})(n),(e,r)=>(Fb(), +Wb(k_,Mh(ty(xm(t)("scalar-card-header leading-[22px] font-medium py-[6.75px] px-3 shrink-0"))),{ +default:ig((()=>[Jb("div",{id:xm(n), +class:"scalar-card-header-title min-w-0 flex-1 truncate" +},[Av(e.$slots,"default")],8,__),e.$slots.actions?(Fb(), +qb("div",A_,[Av(e.$slots,"actions")])):oy("",!0)])),_:3},16))}}) +;function E_(e={}){const t=Ty((()=>e.label?{"aria-label":e.label}:{ +"aria-hidden":!0,role:"presentation"}));return{bind:Ty((()=>({width:"1em", +height:"1em",...t.value}))),weight:Ty((()=>e.weight??"regular"))}}const C_={ +key:0},$_={key:1},P_={key:2},I_={key:3},D_={key:4},M_={key:5},N_=Hg({ +name:"ScalarIconArrowRight",props:{label:{},weight:{}},setup(e){ +const t=e,{bind:n,weight:r}=E_(t);return(e,t)=>(Fb(),qb("svg",cy({ +xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 256 256",fill:"currentColor" +},xm(n)),[Av(e.$slots,"default"),"bold"===xm(r)?(Fb(), +qb("g",C_,[...t[0]||(t[0]=[Jb("path",{ +d:"M224.49,136.49l-72,72a12,12,0,0,1-17-17L187,140H40a12,12,0,0,1,0-24H187L135.51,64.48a12,12,0,0,1,17-17l72,72A12,12,0,0,1,224.49,136.49Z" +},null,-1)])])):"duotone"===xm(r)?(Fb(),qb("g",$_,[...t[1]||(t[1]=[Jb("path",{ +d:"M216,128l-72,72V56Z",opacity:"0.2"},null,-1),Jb("path",{ +d:"M221.66,122.34l-72-72A8,8,0,0,0,136,56v64H40a8,8,0,0,0,0,16h96v64a8,8,0,0,0,13.66,5.66l72-72A8,8,0,0,0,221.66,122.34ZM152,180.69V75.31L204.69,128Z" +},null,-1)])])):"fill"===xm(r)?(Fb(),qb("g",P_,[...t[2]||(t[2]=[Jb("path",{ +d:"M221.66,133.66l-72,72A8,8,0,0,1,136,200V136H40a8,8,0,0,1,0-16h96V56a8,8,0,0,1,13.66-5.66l72,72A8,8,0,0,1,221.66,133.66Z" +},null,-1)])])):"light"===xm(r)?(Fb(),qb("g",I_,[...t[3]||(t[3]=[Jb("path",{ +d:"M220.24,132.24l-72,72a6,6,0,0,1-8.48-8.48L201.51,134H40a6,6,0,0,1,0-12H201.51L139.76,60.24a6,6,0,0,1,8.48-8.48l72,72A6,6,0,0,1,220.24,132.24Z" +},null,-1)])])):"regular"===xm(r)?(Fb(),qb("g",D_,[...t[4]||(t[4]=[Jb("path",{ +d:"M221.66,133.66l-72,72a8,8,0,0,1-11.32-11.32L196.69,136H40a8,8,0,0,1,0-16H196.69L138.34,61.66a8,8,0,0,1,11.32-11.32l72,72A8,8,0,0,1,221.66,133.66Z" +},null,-1)])])):"thin"===xm(r)?(Fb(),qb("g",M_,[...t[5]||(t[5]=[Jb("path",{ +d:"M218.83,130.83l-72,72a4,4,0,0,1-5.66-5.66L206.34,132H40a4,4,0,0,1,0-8H206.34L141.17,58.83a4,4,0,0,1,5.66-5.66l72,72A4,4,0,0,1,218.83,130.83Z" +},null,-1)])])):oy("",!0)],16))}}),R_={key:0},L_={key:1},B_={key:2},j_={key:3 +},U_={key:4},z_={key:5},Z_=Hg({name:"ScalarIconArrowUp",props:{label:{}, +weight:{}},setup(e){const t=e,{bind:n,weight:r}=E_(t) +;return(e,t)=>(Fb(),qb("svg",cy({xmlns:"http://www.w3.org/2000/svg", +viewBox:"0 0 256 256",fill:"currentColor" +},xm(n)),[Av(e.$slots,"default"),"bold"===xm(r)?(Fb(), +qb("g",R_,[...t[0]||(t[0]=[Jb("path",{ +d:"M208.49,120.49a12,12,0,0,1-17,0L140,69V216a12,12,0,0,1-24,0V69L64.49,120.49a12,12,0,0,1-17-17l72-72a12,12,0,0,1,17,0l72,72A12,12,0,0,1,208.49,120.49Z" +},null,-1)])])):"duotone"===xm(r)?(Fb(),qb("g",L_,[...t[1]||(t[1]=[Jb("path",{ +d:"M200,112H56l72-72Z",opacity:"0.2"},null,-1),Jb("path",{ +d:"M205.66,106.34l-72-72a8,8,0,0,0-11.32,0l-72,72A8,8,0,0,0,56,120h64v96a8,8,0,0,0,16,0V120h64a8,8,0,0,0,5.66-13.66ZM75.31,104,128,51.31,180.69,104Z" +},null,-1)])])):"fill"===xm(r)?(Fb(),qb("g",B_,[...t[2]||(t[2]=[Jb("path",{ +d:"M207.39,115.06A8,8,0,0,1,200,120H136v96a8,8,0,0,1-16,0V120H56a8,8,0,0,1-5.66-13.66l72-72a8,8,0,0,1,11.32,0l72,72A8,8,0,0,1,207.39,115.06Z" +},null,-1)])])):"light"===xm(r)?(Fb(),qb("g",j_,[...t[3]||(t[3]=[Jb("path",{ +d:"M204.24,116.24a6,6,0,0,1-8.48,0L134,54.49V216a6,6,0,0,1-12,0V54.49L60.24,116.24a6,6,0,0,1-8.48-8.48l72-72a6,6,0,0,1,8.48,0l72,72A6,6,0,0,1,204.24,116.24Z" +},null,-1)])])):"regular"===xm(r)?(Fb(),qb("g",U_,[...t[4]||(t[4]=[Jb("path",{ +d:"M205.66,117.66a8,8,0,0,1-11.32,0L136,59.31V216a8,8,0,0,1-16,0V59.31L61.66,117.66a8,8,0,0,1-11.32-11.32l72-72a8,8,0,0,1,11.32,0l72,72A8,8,0,0,1,205.66,117.66Z" +},null,-1)])])):"thin"===xm(r)?(Fb(),qb("g",z_,[...t[5]||(t[5]=[Jb("path",{ +d:"M202.83,114.83a4,4,0,0,1-5.66,0L132,49.66V216a4,4,0,0,1-8,0V49.66L58.83,114.83a4,4,0,0,1-5.66-5.66l72-72a4,4,0,0,1,5.66,0l72,72A4,4,0,0,1,202.83,114.83Z" +},null,-1)])])):oy("",!0)],16))}}),F_={key:0},H_={key:1},Q_={key:2},V_={key:3 +},q_={key:4},W_={key:5},X_=Hg({name:"ScalarIconArrowUpRight",props:{label:{}, +weight:{}},setup(e){const t=e,{bind:n,weight:r}=E_(t) +;return(e,t)=>(Fb(),qb("svg",cy({xmlns:"http://www.w3.org/2000/svg", +viewBox:"0 0 256 256",fill:"currentColor" +},xm(n)),[Av(e.$slots,"default"),"bold"===xm(r)?(Fb(), +qb("g",F_,[...t[0]||(t[0]=[Jb("path",{ +d:"M204,64V168a12,12,0,0,1-24,0V93L72.49,200.49a12,12,0,0,1-17-17L163,76H88a12,12,0,0,1,0-24H192A12,12,0,0,1,204,64Z" +},null,-1)])])):"duotone"===xm(r)?(Fb(),qb("g",H_,[...t[1]||(t[1]=[Jb("path",{ +d:"M192,64V168L88,64Z",opacity:"0.2"},null,-1),Jb("path",{ +d:"M192,56H88a8,8,0,0,0-5.66,13.66L128.69,116,58.34,186.34a8,8,0,0,0,11.32,11.32L140,127.31l46.34,46.35A8,8,0,0,0,200,168V64A8,8,0,0,0,192,56Zm-8,92.69-38.34-38.34h0L107.31,72H184Z" +},null,-1)])])):"fill"===xm(r)?(Fb(),qb("g",Q_,[...t[2]||(t[2]=[Jb("path",{ +d:"M200,64V168a8,8,0,0,1-13.66,5.66L140,127.31,69.66,197.66a8,8,0,0,1-11.32-11.32L128.69,116,82.34,69.66A8,8,0,0,1,88,56H192A8,8,0,0,1,200,64Z" +},null,-1)])])):"light"===xm(r)?(Fb(),qb("g",V_,[...t[3]||(t[3]=[Jb("path",{ +d:"M198,64V168a6,6,0,0,1-12,0V78.48L68.24,196.24a6,6,0,0,1-8.48-8.48L177.52,70H88a6,6,0,0,1,0-12H192A6,6,0,0,1,198,64Z" +},null,-1)])])):"regular"===xm(r)?(Fb(),qb("g",q_,[...t[4]||(t[4]=[Jb("path",{ +d:"M200,64V168a8,8,0,0,1-16,0V83.31L69.66,197.66a8,8,0,0,1-11.32-11.32L172.69,72H88a8,8,0,0,1,0-16H192A8,8,0,0,1,200,64Z" +},null,-1)])])):"thin"===xm(r)?(Fb(),qb("g",W_,[...t[5]||(t[5]=[Jb("path",{ +d:"M196,64V168a4,4,0,0,1-8,0V73.66L66.83,194.83a4,4,0,0,1-5.66-5.66L182.34,68H88a4,4,0,0,1,0-8H192A4,4,0,0,1,196,64Z" +},null,-1)])])):oy("",!0)],16))}}),G_={key:0},Y_={key:1},K_={key:2},J_={key:3 +},eA={key:4},tA={key:5},nA=Hg({name:"ScalarIconBook",props:{label:{},weight:{}}, +setup(e){const t=e,{bind:n,weight:r}=E_(t);return(e,t)=>(Fb(),qb("svg",cy({ +xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 256 256",fill:"currentColor" +},xm(n)),[Av(e.$slots,"default"),"bold"===xm(r)?(Fb(), +qb("g",G_,[...t[0]||(t[0]=[Jb("path",{ +d:"M208,20H72A36,36,0,0,0,36,56V224a12,12,0,0,0,12,12H192a12,12,0,0,0,0-24H60v-4a12,12,0,0,1,12-12H208a12,12,0,0,0,12-12V32A12,12,0,0,0,208,20ZM196,172H72a35.59,35.59,0,0,0-12,2.06V56A12,12,0,0,1,72,44H196Z" +},null,-1)])])):"duotone"===xm(r)?(Fb(),qb("g",Y_,[...t[1]||(t[1]=[Jb("path",{ +d:"M208,32V192H72a24,24,0,0,0-24,24V56A24,24,0,0,1,72,32Z",opacity:"0.2" +},null,-1),Jb("path",{ +d:"M208,24H72A32,32,0,0,0,40,56V224a8,8,0,0,0,8,8H192a8,8,0,0,0,0-16H56a16,16,0,0,1,16-16H208a8,8,0,0,0,8-8V32A8,8,0,0,0,208,24Zm-8,160H72a31.82,31.82,0,0,0-16,4.29V56A16,16,0,0,1,72,40H200Z" +},null,-1)])])):"fill"===xm(r)?(Fb(),qb("g",K_,[...t[2]||(t[2]=[Jb("path",{ +d:"M216,32V192a8,8,0,0,1-8,8H72a16,16,0,0,0-16,16H192a8,8,0,0,1,0,16H48a8,8,0,0,1-8-8V56A32,32,0,0,1,72,24H208A8,8,0,0,1,216,32Z" +},null,-1)])])):"light"===xm(r)?(Fb(),qb("g",J_,[...t[3]||(t[3]=[Jb("path",{ +d:"M208,26H72A30,30,0,0,0,42,56V224a6,6,0,0,0,6,6H192a6,6,0,0,0,0-12H54v-2a18,18,0,0,1,18-18H208a6,6,0,0,0,6-6V32A6,6,0,0,0,208,26Zm-6,160H72a29.87,29.87,0,0,0-18,6V56A18,18,0,0,1,72,38H202Z" +},null,-1)])])):"regular"===xm(r)?(Fb(),qb("g",eA,[...t[4]||(t[4]=[Jb("path",{ +d:"M208,24H72A32,32,0,0,0,40,56V224a8,8,0,0,0,8,8H192a8,8,0,0,0,0-16H56a16,16,0,0,1,16-16H208a8,8,0,0,0,8-8V32A8,8,0,0,0,208,24Zm-8,160H72a31.82,31.82,0,0,0-16,4.29V56A16,16,0,0,1,72,40H200Z" +},null,-1)])])):"thin"===xm(r)?(Fb(),qb("g",tA,[...t[5]||(t[5]=[Jb("path",{ +d:"M208,28H72A28,28,0,0,0,44,56V224a4,4,0,0,0,4,4H192a4,4,0,0,0,0-8H52v-4a20,20,0,0,1,20-20H208a4,4,0,0,0,4-4V32A4,4,0,0,0,208,28Zm-4,160H72a27.94,27.94,0,0,0-20,8.42V56A20,20,0,0,1,72,36H204Z" +},null,-1)])])):oy("",!0)],16))}}),rA={key:0},aA={key:1},oA={key:2},iA={key:3 +},sA={key:4},lA={key:5},cA=Hg({name:"ScalarIconBookOpenText",props:{label:{}, +weight:{}},setup(e){const t=e,{bind:n,weight:r}=E_(t) +;return(e,t)=>(Fb(),qb("svg",cy({xmlns:"http://www.w3.org/2000/svg", +viewBox:"0 0 256 256",fill:"currentColor" +},xm(n)),[Av(e.$slots,"default"),"bold"===xm(r)?(Fb(), +qb("g",rA,[...t[0]||(t[0]=[Jb("path",{ +d:"M232,44H160a43.86,43.86,0,0,0-32,13.85A43.86,43.86,0,0,0,96,44H24A12,12,0,0,0,12,56V200a12,12,0,0,0,12,12H96a20,20,0,0,1,20,20,12,12,0,0,0,24,0,20,20,0,0,1,20-20h72a12,12,0,0,0,12-12V56A12,12,0,0,0,232,44ZM96,188H36V68H96a20,20,0,0,1,20,20V192.81A43.79,43.79,0,0,0,96,188Zm124,0H160a43.71,43.71,0,0,0-20,4.83V88a20,20,0,0,1,20-20h60ZM164,96h32a12,12,0,0,1,0,24H164a12,12,0,0,1,0-24Zm44,52a12,12,0,0,1-12,12H164a12,12,0,0,1,0-24h32A12,12,0,0,1,208,148Z" +},null,-1)])])):"duotone"===xm(r)?(Fb(),qb("g",aA,[...t[1]||(t[1]=[Jb("path",{ +d:"M232,56V200H160a32,32,0,0,0-32,32V88a32,32,0,0,1,32-32Z",opacity:"0.2" +},null,-1),Jb("path",{ +d:"M232,48H160a40,40,0,0,0-32,16A40,40,0,0,0,96,48H24a8,8,0,0,0-8,8V200a8,8,0,0,0,8,8H96a24,24,0,0,1,24,24,8,8,0,0,0,16,0,24,24,0,0,1,24-24h72a8,8,0,0,0,8-8V56A8,8,0,0,0,232,48ZM96,192H32V64H96a24,24,0,0,1,24,24V200A39.81,39.81,0,0,0,96,192Zm128,0H160a39.81,39.81,0,0,0-24,8V88a24,24,0,0,1,24-24h64ZM160,88h40a8,8,0,0,1,0,16H160a8,8,0,0,1,0-16Zm48,40a8,8,0,0,1-8,8H160a8,8,0,0,1,0-16h40A8,8,0,0,1,208,128Zm0,32a8,8,0,0,1-8,8H160a8,8,0,0,1,0-16h40A8,8,0,0,1,208,160Z" +},null,-1)])])):"fill"===xm(r)?(Fb(),qb("g",oA,[...t[2]||(t[2]=[Jb("path",{ +d:"M232,48H168a32,32,0,0,0-32,32v87.73a8.17,8.17,0,0,1-7.47,8.25,8,8,0,0,1-8.53-8V80A32,32,0,0,0,88,48H24a8,8,0,0,0-8,8V200a8,8,0,0,0,8,8H96a24,24,0,0,1,24,23.94,7.9,7.9,0,0,0,5.12,7.55A8,8,0,0,0,136,232a24,24,0,0,1,24-24h72a8,8,0,0,0,8-8V56A8,8,0,0,0,232,48ZM208,168H168.27a8.17,8.17,0,0,1-8.25-7.47,8,8,0,0,1,8-8.53h39.73a8.17,8.17,0,0,1,8.25,7.47A8,8,0,0,1,208,168Zm0-32H168.27a8.17,8.17,0,0,1-8.25-7.47,8,8,0,0,1,8-8.53h39.73a8.17,8.17,0,0,1,8.25,7.47A8,8,0,0,1,208,136Zm0-32H168.27A8.17,8.17,0,0,1,160,96.53,8,8,0,0,1,168,88h39.73A8.17,8.17,0,0,1,216,95.47,8,8,0,0,1,208,104Z" +},null,-1)])])):"light"===xm(r)?(Fb(),qb("g",iA,[...t[3]||(t[3]=[Jb("path",{ +d:"M232,50H160a38,38,0,0,0-32,17.55A38,38,0,0,0,96,50H24a6,6,0,0,0-6,6V200a6,6,0,0,0,6,6H96a26,26,0,0,1,26,26,6,6,0,0,0,12,0,26,26,0,0,1,26-26h72a6,6,0,0,0,6-6V56A6,6,0,0,0,232,50ZM96,194H30V62H96a26,26,0,0,1,26,26V204.31A37.86,37.86,0,0,0,96,194Zm130,0H160a37.87,37.87,0,0,0-26,10.32V88a26,26,0,0,1,26-26h66ZM160,90h40a6,6,0,0,1,0,12H160a6,6,0,0,1,0-12Zm46,38a6,6,0,0,1-6,6H160a6,6,0,0,1,0-12h40A6,6,0,0,1,206,128Zm0,32a6,6,0,0,1-6,6H160a6,6,0,0,1,0-12h40A6,6,0,0,1,206,160Z" +},null,-1)])])):"regular"===xm(r)?(Fb(),qb("g",sA,[...t[4]||(t[4]=[Jb("path",{ +d:"M232,48H160a40,40,0,0,0-32,16A40,40,0,0,0,96,48H24a8,8,0,0,0-8,8V200a8,8,0,0,0,8,8H96a24,24,0,0,1,24,24,8,8,0,0,0,16,0,24,24,0,0,1,24-24h72a8,8,0,0,0,8-8V56A8,8,0,0,0,232,48ZM96,192H32V64H96a24,24,0,0,1,24,24V200A39.81,39.81,0,0,0,96,192Zm128,0H160a39.81,39.81,0,0,0-24,8V88a24,24,0,0,1,24-24h64ZM160,88h40a8,8,0,0,1,0,16H160a8,8,0,0,1,0-16Zm48,40a8,8,0,0,1-8,8H160a8,8,0,0,1,0-16h40A8,8,0,0,1,208,128Zm0,32a8,8,0,0,1-8,8H160a8,8,0,0,1,0-16h40A8,8,0,0,1,208,160Z" +},null,-1)])])):"thin"===xm(r)?(Fb(),qb("g",lA,[...t[5]||(t[5]=[Jb("path",{ +d:"M232,52H160a36,36,0,0,0-32,19.54A36,36,0,0,0,96,52H24a4,4,0,0,0-4,4V200a4,4,0,0,0,4,4H96a28,28,0,0,1,28,28,4,4,0,0,0,8,0,28,28,0,0,1,28-28h72a4,4,0,0,0,4-4V56A4,4,0,0,0,232,52ZM96,196H28V60H96a28,28,0,0,1,28,28V209.4A35.94,35.94,0,0,0,96,196Zm132,0H160a35.94,35.94,0,0,0-28,13.41V88a28,28,0,0,1,28-28h68ZM160,92h40a4,4,0,0,1,0,8H160a4,4,0,0,1,0-8Zm44,36a4,4,0,0,1-4,4H160a4,4,0,0,1,0-8h40A4,4,0,0,1,204,128Zm0,32a4,4,0,0,1-4,4H160a4,4,0,0,1,0-8h40A4,4,0,0,1,204,160Z" +},null,-1)])])):oy("",!0)],16))}}),uA={key:0},dA={key:1},pA={key:2},hA={key:3 +},fA={key:4},mA={key:5},gA=Hg({name:"ScalarIconBracketsCurly",props:{label:{}, +weight:{}},setup(e){const t=e,{bind:n,weight:r}=E_(t) +;return(e,t)=>(Fb(),qb("svg",cy({xmlns:"http://www.w3.org/2000/svg", +viewBox:"0 0 256 256",fill:"currentColor" +},xm(n)),[Av(e.$slots,"default"),"bold"===xm(r)?(Fb(), +qb("g",uA,[...t[0]||(t[0]=[Jb("path",{ +d:"M54.8,119.49A35.06,35.06,0,0,1,49.05,128a35.06,35.06,0,0,1,5.75,8.51C60,147.24,60,159.83,60,172c0,25.94,1.84,32,20,32a12,12,0,0,1,0,24c-19.14,0-32.2-6.9-38.8-20.51C36,196.76,36,184.17,36,172c0-25.94-1.84-32-20-32a12,12,0,0,1,0-24c18.16,0,20-6.06,20-32,0-12.17,0-24.76,5.2-35.49C47.8,34.9,60.86,28,80,28a12,12,0,0,1,0,24c-18.16,0-20,6.06-20,32C60,96.17,60,108.76,54.8,119.49ZM240,116c-18.16,0-20-6.06-20-32,0-12.17,0-24.76-5.2-35.49C208.2,34.9,195.14,28,176,28a12,12,0,0,0,0,24c18.16,0,20,6.06,20,32,0,12.17,0,24.76,5.2,35.49A35.06,35.06,0,0,0,207,128a35.06,35.06,0,0,0-5.75,8.51C196,147.24,196,159.83,196,172c0,25.94-1.84,32-20,32a12,12,0,0,0,0,24c19.14,0,32.2-6.9,38.8-20.51C220,196.76,220,184.17,220,172c0-25.94,1.84-32,20-32a12,12,0,0,0,0-24Z" +},null,-1)])])):"duotone"===xm(r)?(Fb(),qb("g",dA,[...t[1]||(t[1]=[Jb("path",{ +d:"M240,128c-64,0,0,88-64,88H80c-64,0,0-88-64-88,64,0,0-88,64-88h96C240,40,176,128,240,128Z", +opacity:"0.2"},null,-1),Jb("path",{ +d:"M43.18,128a29.78,29.78,0,0,1,8,10.26c4.8,9.9,4.8,22,4.8,33.74,0,24.31,1,36,24,36a8,8,0,0,1,0,16c-17.48,0-29.32-6.14-35.2-18.26-4.8-9.9-4.8-22-4.8-33.74,0-24.31-1-36-24-36a8,8,0,0,1,0-16c23,0,24-11.69,24-36,0-11.72,0-23.84,4.8-33.74C50.68,38.14,62.52,32,80,32a8,8,0,0,1,0,16C57,48,56,59.69,56,84c0,11.72,0,23.84-4.8,33.74A29.78,29.78,0,0,1,43.18,128ZM240,120c-23,0-24-11.69-24-36,0-11.72,0-23.84-4.8-33.74C205.32,38.14,193.48,32,176,32a8,8,0,0,0,0,16c23,0,24,11.69,24,36,0,11.72,0,23.84,4.8,33.74a29.78,29.78,0,0,0,8,10.26,29.78,29.78,0,0,0-8,10.26c-4.8,9.9-4.8,22-4.8,33.74,0,24.31-1,36-24,36a8,8,0,0,0,0,16c17.48,0,29.32-6.14,35.2-18.26,4.8-9.9,4.8-22,4.8-33.74,0-24.31,1-36,24-36a8,8,0,0,0,0-16Z" +},null,-1)])])):"fill"===xm(r)?(Fb(),qb("g",pA,[...t[2]||(t[2]=[Jb("path",{ +d:"M216,40H40A16,16,0,0,0,24,56V200a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V56A16,16,0,0,0,216,40ZM88,155.84c.29,14.26.41,20.16,16,20.16a8,8,0,0,1,0,16c-31.27,0-31.72-22.43-32-35.84C71.71,141.9,71.59,136,56,136a8,8,0,0,1,0-16c15.59,0,15.71-5.9,16-20.16C72.28,86.43,72.73,64,104,64a8,8,0,0,1,0,16c-15.59,0-15.71,5.9-16,20.16-.17,8.31-.41,20.09-8,27.84C87.59,135.75,87.83,147.53,88,155.84ZM200,136c-15.59,0-15.71,5.9-16,20.16-.28,13.41-.73,35.84-32,35.84a8,8,0,0,1,0-16c15.59,0,15.71-5.9,16-20.16.17-8.31.41-20.09,8-27.84-7.6-7.75-7.84-19.53-8-27.84C167.71,85.9,167.59,80,152,80a8,8,0,0,1,0-16c31.27,0,31.72,22.43,32,35.84.29,14.26.41,20.16,16,20.16a8,8,0,0,1,0,16Z" +},null,-1)])])):"light"===xm(r)?(Fb(),qb("g",hA,[...t[3]||(t[3]=[Jb("path",{ +d:"M39.91,128a27.68,27.68,0,0,1,9.49,11.13C54,148.62,54,160.51,54,172c0,24.27,1.21,38,26,38a6,6,0,0,1,0,12c-16.88,0-27.81-5.6-33.4-17.13C42,195.38,42,183.49,42,172c0-24.27-1.21-38-26-38a6,6,0,0,1,0-12c24.79,0,26-13.73,26-38,0-11.49,0-23.38,4.6-32.87C52.19,39.6,63.12,34,80,34a6,6,0,0,1,0,12C55.21,46,54,59.73,54,84c0,11.49,0,23.38-4.6,32.87A27.68,27.68,0,0,1,39.91,128ZM240,122c-24.79,0-26-13.73-26-38,0-11.49,0-23.38-4.6-32.87C203.81,39.6,192.88,34,176,34a6,6,0,0,0,0,12c24.79,0,26,13.73,26,38,0,11.49,0,23.38,4.6,32.87A27.68,27.68,0,0,0,216.09,128a27.68,27.68,0,0,0-9.49,11.13C202,148.62,202,160.51,202,172c0,24.27-1.21,38-26,38a6,6,0,0,0,0,12c16.88,0,27.81-5.6,33.4-17.13,4.6-9.49,4.6-21.38,4.6-32.87,0-24.27,1.21-38,26-38a6,6,0,0,0,0-12Z" +},null,-1)])])):"regular"===xm(r)?(Fb(),qb("g",fA,[...t[4]||(t[4]=[Jb("path",{ +d:"M43.18,128a29.78,29.78,0,0,1,8,10.26c4.8,9.9,4.8,22,4.8,33.74,0,24.31,1,36,24,36a8,8,0,0,1,0,16c-17.48,0-29.32-6.14-35.2-18.26-4.8-9.9-4.8-22-4.8-33.74,0-24.31-1-36-24-36a8,8,0,0,1,0-16c23,0,24-11.69,24-36,0-11.72,0-23.84,4.8-33.74C50.68,38.14,62.52,32,80,32a8,8,0,0,1,0,16C57,48,56,59.69,56,84c0,11.72,0,23.84-4.8,33.74A29.78,29.78,0,0,1,43.18,128ZM240,120c-23,0-24-11.69-24-36,0-11.72,0-23.84-4.8-33.74C205.32,38.14,193.48,32,176,32a8,8,0,0,0,0,16c23,0,24,11.69,24,36,0,11.72,0,23.84,4.8,33.74a29.78,29.78,0,0,0,8,10.26,29.78,29.78,0,0,0-8,10.26c-4.8,9.9-4.8,22-4.8,33.74,0,24.31-1,36-24,36a8,8,0,0,0,0,16c17.48,0,29.32-6.14,35.2-18.26,4.8-9.9,4.8-22,4.8-33.74,0-24.31,1-36,24-36a8,8,0,0,0,0-16Z" +},null,-1)])])):"thin"===xm(r)?(Fb(),qb("g",mA,[...t[5]||(t[5]=[Jb("path",{ +d:"M35.89,128C52,136.23,52,155.64,52,172c0,24.8,1.35,40,28,40a4,4,0,0,1,0,8c-36,0-36-26.61-36-48,0-24.8-1.35-40-28-40a4,4,0,0,1,0-8c26.65,0,28-15.2,28-40,0-21.39,0-48,36-48a4,4,0,0,1,0,8C53.35,44,52,59.2,52,84,52,100.36,52,119.77,35.89,128ZM240,124c-26.65,0-28-15.2-28-40,0-21.39,0-48-36-48a4,4,0,0,0,0,8c26.65,0,28,15.2,28,40,0,16.36,0,35.77,16.11,44C204,136.23,204,155.64,204,172c0,24.8-1.35,40-28,40a4,4,0,0,0,0,8c36,0,36-26.61,36-48,0-24.8,1.35-40,28-40a4,4,0,0,0,0-8Z" +},null,-1)])])):oy("",!0)],16))}}),vA={key:0},bA={key:1},yA={key:2},OA={key:3 +},wA={key:4},xA={key:5},kA=Hg({name:"ScalarIconCaretDown",props:{label:{}, +weight:{}},setup(e){const t=e,{bind:n,weight:r}=E_(t) +;return(e,t)=>(Fb(),qb("svg",cy({xmlns:"http://www.w3.org/2000/svg", +viewBox:"0 0 256 256",fill:"currentColor" +},xm(n)),[Av(e.$slots,"default"),"bold"===xm(r)?(Fb(), +qb("g",vA,[...t[0]||(t[0]=[Jb("path",{ +d:"M216.49,104.49l-80,80a12,12,0,0,1-17,0l-80-80a12,12,0,0,1,17-17L128,159l71.51-71.52a12,12,0,0,1,17,17Z" +},null,-1)])])):"duotone"===xm(r)?(Fb(),qb("g",bA,[...t[1]||(t[1]=[Jb("path",{ +d:"M208,96l-80,80L48,96Z",opacity:"0.2"},null,-1),Jb("path",{ +d:"M215.39,92.94A8,8,0,0,0,208,88H48a8,8,0,0,0-5.66,13.66l80,80a8,8,0,0,0,11.32,0l80-80A8,8,0,0,0,215.39,92.94ZM128,164.69,67.31,104H188.69Z" +},null,-1)])])):"fill"===xm(r)?(Fb(),qb("g",yA,[...t[2]||(t[2]=[Jb("path",{ +d:"M213.66,101.66l-80,80a8,8,0,0,1-11.32,0l-80-80A8,8,0,0,1,48,88H208a8,8,0,0,1,5.66,13.66Z" +},null,-1)])])):"light"===xm(r)?(Fb(),qb("g",OA,[...t[3]||(t[3]=[Jb("path",{ +d:"M212.24,100.24l-80,80a6,6,0,0,1-8.48,0l-80-80a6,6,0,0,1,8.48-8.48L128,167.51l75.76-75.75a6,6,0,0,1,8.48,8.48Z" +},null,-1)])])):"regular"===xm(r)?(Fb(),qb("g",wA,[...t[4]||(t[4]=[Jb("path",{ +d:"M213.66,101.66l-80,80a8,8,0,0,1-11.32,0l-80-80A8,8,0,0,1,53.66,90.34L128,164.69l74.34-74.35a8,8,0,0,1,11.32,11.32Z" +},null,-1)])])):"thin"===xm(r)?(Fb(),qb("g",xA,[...t[5]||(t[5]=[Jb("path",{ +d:"M210.83,98.83l-80,80a4,4,0,0,1-5.66,0l-80-80a4,4,0,0,1,5.66-5.66L128,170.34l77.17-77.17a4,4,0,1,1,5.66,5.66Z" +},null,-1)])])):oy("",!0)],16))}}),SA={key:0},_A={key:1},AA={key:2},TA={key:3 +},EA={key:4},CA={key:5},$A=Hg({name:"ScalarIconCaretRight",props:{label:{}, +weight:{}},setup(e){const t=e,{bind:n,weight:r}=E_(t) +;return(e,t)=>(Fb(),qb("svg",cy({xmlns:"http://www.w3.org/2000/svg", +viewBox:"0 0 256 256",fill:"currentColor" +},xm(n)),[Av(e.$slots,"default"),"bold"===xm(r)?(Fb(), +qb("g",SA,[...t[0]||(t[0]=[Jb("path",{ +d:"M184.49,136.49l-80,80a12,12,0,0,1-17-17L159,128,87.51,56.49a12,12,0,1,1,17-17l80,80A12,12,0,0,1,184.49,136.49Z" +},null,-1)])])):"duotone"===xm(r)?(Fb(),qb("g",_A,[...t[1]||(t[1]=[Jb("path",{ +d:"M176,128,96,208V48Z",opacity:"0.2"},null,-1),Jb("path",{ +d:"M181.66,122.34l-80-80A8,8,0,0,0,88,48V208a8,8,0,0,0,13.66,5.66l80-80A8,8,0,0,0,181.66,122.34ZM104,188.69V67.31L164.69,128Z" +},null,-1)])])):"fill"===xm(r)?(Fb(),qb("g",AA,[...t[2]||(t[2]=[Jb("path",{ +d:"M181.66,133.66l-80,80A8,8,0,0,1,88,208V48a8,8,0,0,1,13.66-5.66l80,80A8,8,0,0,1,181.66,133.66Z" +},null,-1)])])):"light"===xm(r)?(Fb(),qb("g",TA,[...t[3]||(t[3]=[Jb("path",{ +d:"M180.24,132.24l-80,80a6,6,0,0,1-8.48-8.48L167.51,128,91.76,52.24a6,6,0,0,1,8.48-8.48l80,80A6,6,0,0,1,180.24,132.24Z" +},null,-1)])])):"regular"===xm(r)?(Fb(),qb("g",EA,[...t[4]||(t[4]=[Jb("path",{ +d:"M181.66,133.66l-80,80a8,8,0,0,1-11.32-11.32L164.69,128,90.34,53.66a8,8,0,0,1,11.32-11.32l80,80A8,8,0,0,1,181.66,133.66Z" +},null,-1)])])):"thin"===xm(r)?(Fb(),qb("g",CA,[...t[5]||(t[5]=[Jb("path",{ +d:"M178.83,130.83l-80,80a4,4,0,0,1-5.66-5.66L170.34,128,93.17,50.83a4,4,0,0,1,5.66-5.66l80,80A4,4,0,0,1,178.83,130.83Z" +},null,-1)])])):oy("",!0)],16))}}),PA={key:0},IA={key:1},DA={key:2},MA={key:3 +},NA={key:4},RA={key:5},LA=Hg({name:"ScalarIconCheck",props:{label:{},weight:{} +},setup(e){const t=e,{bind:n,weight:r}=E_(t);return(e,t)=>(Fb(),qb("svg",cy({ +xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 256 256",fill:"currentColor" +},xm(n)),[Av(e.$slots,"default"),"bold"===xm(r)?(Fb(), +qb("g",PA,[...t[0]||(t[0]=[Jb("path",{ +d:"M232.49,80.49l-128,128a12,12,0,0,1-17,0l-56-56a12,12,0,1,1,17-17L96,183,215.51,63.51a12,12,0,0,1,17,17Z" +},null,-1)])])):"duotone"===xm(r)?(Fb(),qb("g",IA,[...t[1]||(t[1]=[Jb("path",{ +d:"M232,56V200a16,16,0,0,1-16,16H40a16,16,0,0,1-16-16V56A16,16,0,0,1,40,40H216A16,16,0,0,1,232,56Z", +opacity:"0.2"},null,-1),Jb("path",{ +d:"M205.66,85.66l-96,96a8,8,0,0,1-11.32,0l-40-40a8,8,0,0,1,11.32-11.32L104,164.69l90.34-90.35a8,8,0,0,1,11.32,11.32Z" +},null,-1)])])):"fill"===xm(r)?(Fb(),qb("g",DA,[...t[2]||(t[2]=[Jb("path",{ +d:"M216,40H40A16,16,0,0,0,24,56V200a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V56A16,16,0,0,0,216,40ZM205.66,85.66l-96,96a8,8,0,0,1-11.32,0l-40-40a8,8,0,0,1,11.32-11.32L104,164.69l90.34-90.35a8,8,0,0,1,11.32,11.32Z" +},null,-1)])])):"light"===xm(r)?(Fb(),qb("g",MA,[...t[3]||(t[3]=[Jb("path",{ +d:"M228.24,76.24l-128,128a6,6,0,0,1-8.48,0l-56-56a6,6,0,0,1,8.48-8.48L96,191.51,219.76,67.76a6,6,0,0,1,8.48,8.48Z" +},null,-1)])])):"regular"===xm(r)?(Fb(),qb("g",NA,[...t[4]||(t[4]=[Jb("path",{ +d:"M229.66,77.66l-128,128a8,8,0,0,1-11.32,0l-56-56a8,8,0,0,1,11.32-11.32L96,188.69,218.34,66.34a8,8,0,0,1,11.32,11.32Z" +},null,-1)])])):"thin"===xm(r)?(Fb(),qb("g",RA,[...t[5]||(t[5]=[Jb("path",{ +d:"M226.83,74.83l-128,128a4,4,0,0,1-5.66,0l-56-56a4,4,0,0,1,5.66-5.66L96,194.34,221.17,69.17a4,4,0,1,1,5.66,5.66Z" +},null,-1)])])):oy("",!0)],16))}}),BA={key:0},jA={key:1},UA={key:2},zA={key:3 +},ZA={key:4},FA={key:5},HA=Hg({name:"ScalarIconCopy",props:{label:{},weight:{}}, +setup(e){const t=e,{bind:n,weight:r}=E_(t);return(e,t)=>(Fb(),qb("svg",cy({ +xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 256 256",fill:"currentColor" +},xm(n)),[Av(e.$slots,"default"),"bold"===xm(r)?(Fb(), +qb("g",BA,[...t[0]||(t[0]=[Jb("path",{ +d:"M216,28H88A12,12,0,0,0,76,40V76H40A12,12,0,0,0,28,88V216a12,12,0,0,0,12,12H168a12,12,0,0,0,12-12V180h36a12,12,0,0,0,12-12V40A12,12,0,0,0,216,28ZM156,204H52V100H156Zm48-48H180V88a12,12,0,0,0-12-12H100V52H204Z" +},null,-1)])])):"duotone"===xm(r)?(Fb(),qb("g",jA,[...t[1]||(t[1]=[Jb("path",{ +d:"M216,40V168H168V88H88V40Z",opacity:"0.2"},null,-1),Jb("path",{ +d:"M216,32H88a8,8,0,0,0-8,8V80H40a8,8,0,0,0-8,8V216a8,8,0,0,0,8,8H168a8,8,0,0,0,8-8V176h40a8,8,0,0,0,8-8V40A8,8,0,0,0,216,32ZM160,208H48V96H160Zm48-48H176V88a8,8,0,0,0-8-8H96V48H208Z" +},null,-1)])])):"fill"===xm(r)?(Fb(),qb("g",UA,[...t[2]||(t[2]=[Jb("path",{ +d:"M216,32H88a8,8,0,0,0-8,8V80H40a8,8,0,0,0-8,8V216a8,8,0,0,0,8,8H168a8,8,0,0,0,8-8V176h40a8,8,0,0,0,8-8V40A8,8,0,0,0,216,32Zm-8,128H176V88a8,8,0,0,0-8-8H96V48H208Z" +},null,-1)])])):"light"===xm(r)?(Fb(),qb("g",zA,[...t[3]||(t[3]=[Jb("path",{ +d:"M216,34H88a6,6,0,0,0-6,6V82H40a6,6,0,0,0-6,6V216a6,6,0,0,0,6,6H168a6,6,0,0,0,6-6V174h42a6,6,0,0,0,6-6V40A6,6,0,0,0,216,34ZM162,210H46V94H162Zm48-48H174V88a6,6,0,0,0-6-6H94V46H210Z" +},null,-1)])])):"regular"===xm(r)?(Fb(),qb("g",ZA,[...t[4]||(t[4]=[Jb("path",{ +d:"M216,32H88a8,8,0,0,0-8,8V80H40a8,8,0,0,0-8,8V216a8,8,0,0,0,8,8H168a8,8,0,0,0,8-8V176h40a8,8,0,0,0,8-8V40A8,8,0,0,0,216,32ZM160,208H48V96H160Zm48-48H176V88a8,8,0,0,0-8-8H96V48H208Z" +},null,-1)])])):"thin"===xm(r)?(Fb(),qb("g",FA,[...t[5]||(t[5]=[Jb("path",{ +d:"M216,36H88a4,4,0,0,0-4,4V84H40a4,4,0,0,0-4,4V216a4,4,0,0,0,4,4H168a4,4,0,0,0,4-4V172h44a4,4,0,0,0,4-4V40A4,4,0,0,0,216,36ZM164,212H44V92H164Zm48-48H172V88a4,4,0,0,0-4-4H92V44H212Z" +},null,-1)])])):oy("",!0)],16))}}),QA={key:0},VA={key:1},qA={key:2},WA={key:3 +},XA={key:4},GA={key:5},YA=Hg({name:"ScalarIconDiscordLogo",props:{label:{}, +weight:{}},setup(e){const t=e,{bind:n,weight:r}=E_(t) +;return(e,t)=>(Fb(),qb("svg",cy({xmlns:"http://www.w3.org/2000/svg", +viewBox:"0 0 256 256",fill:"currentColor" +},xm(n)),[Av(e.$slots,"default"),"bold"===xm(r)?(Fb(), +qb("g",QA,[...t[0]||(t[0]=[Jb("path",{ +d:"M108,136a16,16,0,1,1-16-16A16,16,0,0,1,108,136Zm56-16a16,16,0,1,0,16,16A16,16,0,0,0,164,120Zm76.07,76.56-67,29.71A20.15,20.15,0,0,1,146,214.9l-8.54-23.13c-3.13.14-6.27.24-9.45.24s-6.32-.1-9.45-.24L110,214.9a20.19,20.19,0,0,1-27.08,11.37l-67-29.71A19.93,19.93,0,0,1,4.62,173.41L34.15,57A20,20,0,0,1,50.37,42.19l36.06-5.93A20.26,20.26,0,0,1,109.22,51.1l4.41,17.41c4.74-.33,9.52-.51,14.37-.51s9.63.18,14.37.51l4.41-17.41a20.25,20.25,0,0,1,22.79-14.84l36.06,5.93A20,20,0,0,1,221.85,57l29.53,116.38A19.93,19.93,0,0,1,240.07,196.56ZM227.28,176,199.23,65.46l-30.07-4.94-2.84,11.17c2.9.58,5.78,1.2,8.61,1.92a12,12,0,1,1-5.86,23.27A168.43,168.43,0,0,0,128,92a168.43,168.43,0,0,0-41.07,4.88,12,12,0,0,1-5.86-23.27c2.83-.72,5.71-1.34,8.61-1.92L86.85,60.52,56.77,65.46,28.72,176l60.22,26.7,5-13.57c-4.37-.76-8.67-1.65-12.88-2.71a12,12,0,0,1,5.86-23.28A168.43,168.43,0,0,0,128,168a168.43,168.43,0,0,0,41.07-4.88,12,12,0,0,1,5.86,23.28c-4.21,1.06-8.51,1.95-12.88,2.71l5,13.57Z" +},null,-1)])])):"duotone"===xm(r)?(Fb(),qb("g",VA,[...t[1]||(t[1]=[Jb("path",{ +d:"M235.21,185.59l-67,29.7a8.15,8.15,0,0,1-11-4.56L147,183.06a190.5,190.5,0,0,1-19,.94,190.5,190.5,0,0,1-19-.94L98.75,210.73a8.15,8.15,0,0,1-11,4.56l-67-29.7a8,8,0,0,1-4.55-9.24L45.77,60A8.08,8.08,0,0,1,52.31,54l36.06-5.92a8.1,8.1,0,0,1,9.21,6l5,19.63a192.32,192.32,0,0,1,50.88,0l5-19.63a8.1,8.1,0,0,1,9.21-6L203.69,54A8.08,8.08,0,0,1,210.23,60l29.53,116.37A8,8,0,0,1,235.21,185.59Z", +opacity:"0.2"},null,-1),Jb("path",{ +d:"M104,140a12,12,0,1,1-12-12A12,12,0,0,1,104,140Zm60-12a12,12,0,1,0,12,12A12,12,0,0,0,164,128Zm74.45,64.9-67,29.71a16.17,16.17,0,0,1-21.71-9.1l-8.11-22q-6.72.45-13.63.46t-13.63-.46l-8.11,22a16.18,16.18,0,0,1-21.71,9.1l-67-29.71a15.94,15.94,0,0,1-9.06-18.51L38,58A16.08,16.08,0,0,1,51,46.13l36.06-5.92a16.21,16.21,0,0,1,18.26,11.88l3.26,12.83Q118.11,64,128,64t19.4.92l3.26-12.83a16.22,16.22,0,0,1,18.26-11.88L205,46.13A16.08,16.08,0,0,1,218,58l29.53,116.38A15.94,15.94,0,0,1,238.45,192.9ZM232,178.28,202.47,62s0,0-.08,0L166.33,56a.17.17,0,0,0-.17,0l-2.83,11.14c5,.94,10,2.06,14.83,3.42A8,8,0,0,1,176,86.31a8.09,8.09,0,0,1-2.16-.3A172.25,172.25,0,0,0,128,80a172.25,172.25,0,0,0-45.84,6,8,8,0,1,1-4.32-15.4c4.82-1.36,9.78-2.48,14.82-3.42L89.83,56a.21.21,0,0,0-.12,0h0L53.61,61.92a.24.24,0,0,0-.09,0L24,178.33,91,208a.21.21,0,0,0,.22,0L98,189.72a173.2,173.2,0,0,1-20.14-4.32A8,8,0,0,1,82.16,170,171.85,171.85,0,0,0,128,176a171.85,171.85,0,0,0,45.84-6,8,8,0,0,1,4.32,15.41A173.2,173.2,0,0,1,158,189.72L164.75,208a.22.22,0,0,0,.21,0Z" +},null,-1)])])):"fill"===xm(r)?(Fb(),qb("g",qA,[...t[2]||(t[2]=[Jb("path",{ +d:"M247.51,174.39,218,58a16.08,16.08,0,0,0-13-11.88l-36.06-5.92a16.22,16.22,0,0,0-18.26,11.88l-.21.85a4,4,0,0,0,3.27,4.93,155.62,155.62,0,0,1,24.41,5.62,8.2,8.2,0,0,1,5.62,9.7,8,8,0,0,1-10.19,5.64,155.4,155.4,0,0,0-90.8-.1,8.22,8.22,0,0,1-10.28-4.81,8,8,0,0,1,5.08-10.33,156.85,156.85,0,0,1,24.72-5.72,4,4,0,0,0,3.27-4.93l-.21-.85A16.21,16.21,0,0,0,87.08,40.21L51,46.13A16.08,16.08,0,0,0,38,58L8.49,174.39a15.94,15.94,0,0,0,9.06,18.51l67,29.71a16.17,16.17,0,0,0,21.71-9.1l3.49-9.45a4,4,0,0,0-3.27-5.35,158.13,158.13,0,0,1-28.63-6.2,8.2,8.2,0,0,1-5.61-9.67,8,8,0,0,1,10.2-5.66,155.59,155.59,0,0,0,91.12,0,8,8,0,0,1,10.19,5.65,8.19,8.19,0,0,1-5.61,9.68,157.84,157.84,0,0,1-28.62,6.2,4,4,0,0,0-3.27,5.35l3.49,9.45a16.18,16.18,0,0,0,21.71,9.1l67-29.71A15.94,15.94,0,0,0,247.51,174.39ZM92,152a12,12,0,1,1,12-12A12,12,0,0,1,92,152Zm72,0a12,12,0,1,1,12-12A12,12,0,0,1,164,152Z" +},null,-1)])])):"light"===xm(r)?(Fb(),qb("g",WA,[...t[3]||(t[3]=[Jb("path",{ +d:"M102,140a10,10,0,1,1-10-10A10,10,0,0,1,102,140Zm62-10a10,10,0,1,0,10,10A10,10,0,0,0,164,130Zm73.64,61.08-67,29.71a14.43,14.43,0,0,1-5.77,1.21,14.13,14.13,0,0,1-13.25-9.18L143,189.43c-4.93.37-9.92.58-15,.58s-10.06-.21-15-.58l-8.63,23.39A14.13,14.13,0,0,1,91.13,222a14.43,14.43,0,0,1-5.77-1.21l-67-29.71a14,14,0,0,1-7.93-16.2L40,58.5A14.07,14.07,0,0,1,51.34,48.11L87.4,42.19a14.19,14.19,0,0,1,16,10.39l3.69,14.53a197.5,197.5,0,0,1,41.82,0l3.69-14.53a14.19,14.19,0,0,1,16-10.39l36.06,5.92A14.07,14.07,0,0,1,216,58.5l29.53,116.38A14,14,0,0,1,237.64,191.08Zm-3.7-13.25L204.41,61.45a2.08,2.08,0,0,0-1.7-1.5L166.65,54a2.13,2.13,0,0,0-2.42,1.5l-3.36,13.24a169.28,169.28,0,0,1,16.75,3.76A6,6,0,0,1,176,84.31a5.71,5.71,0,0,1-1.62-.23A174.26,174.26,0,0,0,128,78a174.26,174.26,0,0,0-46.38,6.08,6,6,0,1,1-3.24-11.55,169.28,169.28,0,0,1,16.75-3.76L91.77,55.53A2.12,2.12,0,0,0,89.35,54L53.29,60a2.08,2.08,0,0,0-1.7,1.5L22.06,177.83a2,2,0,0,0,1.16,2.28l67,29.7a2.19,2.19,0,0,0,1.76,0,2.07,2.07,0,0,0,1.14-1.17l7.58-20.55a171.46,171.46,0,0,1-22.33-4.64,6,6,0,1,1,3.24-11.55A174.26,174.26,0,0,0,128,178a174.26,174.26,0,0,0,46.38-6.08,6,6,0,1,1,3.24,11.55,171.46,171.46,0,0,1-22.33,4.64l7.58,20.55a2.07,2.07,0,0,0,1.14,1.17,2.19,2.19,0,0,0,1.76,0l67-29.7A2,2,0,0,0,233.94,177.83Z" +},null,-1)])])):"regular"===xm(r)?(Fb(),qb("g",XA,[...t[4]||(t[4]=[Jb("path",{ +d:"M104,140a12,12,0,1,1-12-12A12,12,0,0,1,104,140Zm60-12a12,12,0,1,0,12,12A12,12,0,0,0,164,128Zm74.45,64.9-67,29.71a16.17,16.17,0,0,1-21.71-9.1l-8.11-22q-6.72.45-13.63.46t-13.63-.46l-8.11,22a16.18,16.18,0,0,1-21.71,9.1l-67-29.71a15.93,15.93,0,0,1-9.06-18.51L38,58A16.07,16.07,0,0,1,51,46.14l36.06-5.93a16.22,16.22,0,0,1,18.26,11.88l3.26,12.84Q118.11,64,128,64t19.4.93l3.26-12.84a16.21,16.21,0,0,1,18.26-11.88L205,46.14A16.07,16.07,0,0,1,218,58l29.53,116.38A15.93,15.93,0,0,1,238.45,192.9ZM232,178.28,202.47,62s0,0-.08,0L166.33,56a.17.17,0,0,0-.17,0l-2.83,11.14c5,.94,10,2.06,14.83,3.42A8,8,0,0,1,176,86.31a8.09,8.09,0,0,1-2.16-.3A172.25,172.25,0,0,0,128,80a172.25,172.25,0,0,0-45.84,6,8,8,0,1,1-4.32-15.4c4.82-1.36,9.78-2.48,14.82-3.42L89.83,56s0,0-.12,0h0L53.61,61.93a.17.17,0,0,0-.09,0L24,178.33,91,208a.23.23,0,0,0,.22,0L98,189.72a173.2,173.2,0,0,1-20.14-4.32A8,8,0,0,1,82.16,170,171.85,171.85,0,0,0,128,176a171.85,171.85,0,0,0,45.84-6,8,8,0,0,1,4.32,15.41A173.2,173.2,0,0,1,158,189.72L164.75,208a.22.22,0,0,0,.21,0Z" +},null,-1)])])):"thin"===xm(r)?(Fb(),qb("g",GA,[...t[5]||(t[5]=[Jb("path",{ +d:"M100,140a8,8,0,1,1-8-8A8,8,0,0,1,100,140Zm64-8a8,8,0,1,0,8,8A8,8,0,0,0,164,132Zm72.83,57.25-67,29.71a12.36,12.36,0,0,1-5,1,12.13,12.13,0,0,1-11.38-7.88l-9.15-24.81c-5.36.45-10.81.69-16.34.69s-11-.24-16.34-.69l-9.15,24.81A12.13,12.13,0,0,1,91.13,220a12.36,12.36,0,0,1-5-1l-67-29.71a12,12,0,0,1-6.8-13.88L41.9,59a12.06,12.06,0,0,1,9.77-8.91l36.06-5.92a12.18,12.18,0,0,1,13.73,8.91l4.12,16.22a195.47,195.47,0,0,1,44.84,0l4.12-16.22a12.18,12.18,0,0,1,13.73-8.91l36.06,5.92A12.06,12.06,0,0,1,214.1,59l29.53,116.38A12,12,0,0,1,236.83,189.25Zm-1-11.91L206.35,61A4.07,4.07,0,0,0,203,58L167,52.05a4.15,4.15,0,0,0-4.69,3L158.4,70.38a166.74,166.74,0,0,1,18.68,4.08,4,4,0,1,1-2.16,7.7A176.21,176.21,0,0,0,128,76a176.21,176.21,0,0,0-46.92,6.16,4,4,0,1,1-2.16-7.7A166.74,166.74,0,0,1,97.6,70.38L93.71,55a4.15,4.15,0,0,0-4.69-3L53,58a4.07,4.07,0,0,0-3.31,3L20.12,177.34a4,4,0,0,0,2.29,4.59l67,29.71a4.16,4.16,0,0,0,3.35,0A4,4,0,0,0,95,209.35l8.45-22.88a171.49,171.49,0,0,1-24.53-4.92,4,4,0,0,1,2.16-7.71A176.21,176.21,0,0,0,128,180a176.21,176.21,0,0,0,46.92-6.16,4,4,0,0,1,2.16,7.71,171.49,171.49,0,0,1-24.53,4.92L161,209.35a4,4,0,0,0,2.23,2.32,4.16,4.16,0,0,0,3.35,0l67-29.71A4,4,0,0,0,235.88,177.34Z" +},null,-1)])])):oy("",!0)],16))}}),KA={key:0},JA={key:1},eT={key:2},tT={key:3 +},nT={key:4},rT={key:5},aT=Hg({name:"ScalarIconEnvelopeSimple",props:{label:{}, +weight:{}},setup(e){const t=e,{bind:n,weight:r}=E_(t) +;return(e,t)=>(Fb(),qb("svg",cy({xmlns:"http://www.w3.org/2000/svg", +viewBox:"0 0 256 256",fill:"currentColor" +},xm(n)),[Av(e.$slots,"default"),"bold"===xm(r)?(Fb(), +qb("g",KA,[...t[0]||(t[0]=[Jb("path",{ +d:"M224,44H32A12,12,0,0,0,20,56V192a20,20,0,0,0,20,20H216a20,20,0,0,0,20-20V56A12,12,0,0,0,224,44ZM193.15,68,128,127.72,62.85,68ZM44,188V83.28l75.89,69.57a12,12,0,0,0,16.22,0L212,83.28V188Z" +},null,-1)])])):"duotone"===xm(r)?(Fb(),qb("g",JA,[...t[1]||(t[1]=[Jb("path",{ +d:"M224,56l-96,88L32,56Z",opacity:"0.2"},null,-1),Jb("path",{ +d:"M224,48H32a8,8,0,0,0-8,8V192a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V56A8,8,0,0,0,224,48ZM203.43,64,128,133.15,52.57,64ZM216,192H40V74.19l82.59,75.71a8,8,0,0,0,10.82,0L216,74.19V192Z" +},null,-1)])])):"fill"===xm(r)?(Fb(),qb("g",eT,[...t[2]||(t[2]=[Jb("path",{ +d:"M224,48H32a8,8,0,0,0-8,8V192a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V56A8,8,0,0,0,224,48Zm-8,144H40V74.19l82.59,75.71a8,8,0,0,0,10.82,0L216,74.19V192Z" +},null,-1)])])):"light"===xm(r)?(Fb(),qb("g",tT,[...t[3]||(t[3]=[Jb("path",{ +d:"M224,50H32a6,6,0,0,0-6,6V192a14,14,0,0,0,14,14H216a14,14,0,0,0,14-14V56A6,6,0,0,0,224,50ZM208.58,62,128,135.86,47.42,62ZM216,194H40a2,2,0,0,1-2-2V69.64l86,78.78a6,6,0,0,0,8.1,0L218,69.64V192A2,2,0,0,1,216,194Z" +},null,-1)])])):"regular"===xm(r)?(Fb(),qb("g",nT,[...t[4]||(t[4]=[Jb("path",{ +d:"M224,48H32a8,8,0,0,0-8,8V192a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V56A8,8,0,0,0,224,48ZM203.43,64,128,133.15,52.57,64ZM216,192H40V74.19l82.59,75.71a8,8,0,0,0,10.82,0L216,74.19V192Z" +},null,-1)])])):"thin"===xm(r)?(Fb(),qb("g",rT,[...t[5]||(t[5]=[Jb("path",{ +d:"M224,52H32a4,4,0,0,0-4,4V192a12,12,0,0,0,12,12H216a12,12,0,0,0,12-12V56A4,4,0,0,0,224,52Zm-10.28,8L128,138.57,42.28,60ZM216,196H40a4,4,0,0,1-4-4V65.09L125.3,147a4,4,0,0,0,5.4,0L220,65.09V192A4,4,0,0,1,216,196Z" +},null,-1)])])):oy("",!0)],16))}}),oT={key:0},iT={key:1},sT={key:2},lT={key:3 +},cT={key:4},uT={key:5},dT=Hg({name:"ScalarIconEye",props:{label:{},weight:{}}, +setup(e){const t=e,{bind:n,weight:r}=E_(t);return(e,t)=>(Fb(),qb("svg",cy({ +xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 256 256",fill:"currentColor" +},xm(n)),[Av(e.$slots,"default"),"bold"===xm(r)?(Fb(), +qb("g",oT,[...t[0]||(t[0]=[Jb("path",{ +d:"M251,123.13c-.37-.81-9.13-20.26-28.48-39.61C196.63,57.67,164,44,128,44S59.37,57.67,33.51,83.52C14.16,102.87,5.4,122.32,5,123.13a12.08,12.08,0,0,0,0,9.75c.37.82,9.13,20.26,28.49,39.61C59.37,198.34,92,212,128,212s68.63-13.66,94.48-39.51c19.36-19.35,28.12-38.79,28.49-39.61A12.08,12.08,0,0,0,251,123.13Zm-46.06,33C183.47,177.27,157.59,188,128,188s-55.47-10.73-76.91-31.88A130.36,130.36,0,0,1,29.52,128,130.45,130.45,0,0,1,51.09,99.89C72.54,78.73,98.41,68,128,68s55.46,10.73,76.91,31.89A130.36,130.36,0,0,1,226.48,128,130.45,130.45,0,0,1,204.91,156.12ZM128,84a44,44,0,1,0,44,44A44.05,44.05,0,0,0,128,84Zm0,64a20,20,0,1,1,20-20A20,20,0,0,1,128,148Z" +},null,-1)])])):"duotone"===xm(r)?(Fb(),qb("g",iT,[...t[1]||(t[1]=[Jb("path",{ +d:"M128,56C48,56,16,128,16,128s32,72,112,72,112-72,112-72S208,56,128,56Zm0,112a40,40,0,1,1,40-40A40,40,0,0,1,128,168Z", +opacity:"0.2"},null,-1),Jb("path",{ +d:"M247.31,124.76c-.35-.79-8.82-19.58-27.65-38.41C194.57,61.26,162.88,48,128,48S61.43,61.26,36.34,86.35C17.51,105.18,9,124,8.69,124.76a8,8,0,0,0,0,6.5c.35.79,8.82,19.57,27.65,38.4C61.43,194.74,93.12,208,128,208s66.57-13.26,91.66-38.34c18.83-18.83,27.3-37.61,27.65-38.4A8,8,0,0,0,247.31,124.76ZM128,192c-30.78,0-57.67-11.19-79.93-33.25A133.47,133.47,0,0,1,25,128,133.33,133.33,0,0,1,48.07,97.25C70.33,75.19,97.22,64,128,64s57.67,11.19,79.93,33.25A133.46,133.46,0,0,1,231.05,128C223.84,141.46,192.43,192,128,192Zm0-112a48,48,0,1,0,48,48A48.05,48.05,0,0,0,128,80Zm0,80a32,32,0,1,1,32-32A32,32,0,0,1,128,160Z" +},null,-1)])])):"fill"===xm(r)?(Fb(),qb("g",sT,[...t[2]||(t[2]=[Jb("path",{ +d:"M247.31,124.76c-.35-.79-8.82-19.58-27.65-38.41C194.57,61.26,162.88,48,128,48S61.43,61.26,36.34,86.35C17.51,105.18,9,124,8.69,124.76a8,8,0,0,0,0,6.5c.35.79,8.82,19.57,27.65,38.4C61.43,194.74,93.12,208,128,208s66.57-13.26,91.66-38.34c18.83-18.83,27.3-37.61,27.65-38.4A8,8,0,0,0,247.31,124.76ZM128,168a40,40,0,1,1,40-40A40,40,0,0,1,128,168Z" +},null,-1)])])):"light"===xm(r)?(Fb(),qb("g",lT,[...t[3]||(t[3]=[Jb("path",{ +d:"M245.48,125.57c-.34-.78-8.66-19.23-27.24-37.81C201,70.54,171.38,50,128,50S55,70.54,37.76,87.76c-18.58,18.58-26.9,37-27.24,37.81a6,6,0,0,0,0,4.88c.34.77,8.66,19.22,27.24,37.8C55,185.47,84.62,206,128,206s73-20.53,90.24-37.75c18.58-18.58,26.9-37,27.24-37.8A6,6,0,0,0,245.48,125.57ZM128,194c-31.38,0-58.78-11.42-81.45-33.93A134.77,134.77,0,0,1,22.69,128,134.56,134.56,0,0,1,46.55,95.94C69.22,73.42,96.62,62,128,62s58.78,11.42,81.45,33.94A134.56,134.56,0,0,1,233.31,128C226.94,140.21,195,194,128,194Zm0-112a46,46,0,1,0,46,46A46.06,46.06,0,0,0,128,82Zm0,80a34,34,0,1,1,34-34A34,34,0,0,1,128,162Z" +},null,-1)])])):"regular"===xm(r)?(Fb(),qb("g",cT,[...t[4]||(t[4]=[Jb("path",{ +d:"M247.31,124.76c-.35-.79-8.82-19.58-27.65-38.41C194.57,61.26,162.88,48,128,48S61.43,61.26,36.34,86.35C17.51,105.18,9,124,8.69,124.76a8,8,0,0,0,0,6.5c.35.79,8.82,19.57,27.65,38.4C61.43,194.74,93.12,208,128,208s66.57-13.26,91.66-38.34c18.83-18.83,27.3-37.61,27.65-38.4A8,8,0,0,0,247.31,124.76ZM128,192c-30.78,0-57.67-11.19-79.93-33.25A133.47,133.47,0,0,1,25,128,133.33,133.33,0,0,1,48.07,97.25C70.33,75.19,97.22,64,128,64s57.67,11.19,79.93,33.25A133.46,133.46,0,0,1,231.05,128C223.84,141.46,192.43,192,128,192Zm0-112a48,48,0,1,0,48,48A48.05,48.05,0,0,0,128,80Zm0,80a32,32,0,1,1,32-32A32,32,0,0,1,128,160Z" +},null,-1)])])):"thin"===xm(r)?(Fb(),qb("g",uT,[...t[5]||(t[5]=[Jb("path",{ +d:"M243.66,126.38c-.34-.76-8.52-18.89-26.83-37.2C199.87,72.22,170.7,52,128,52S56.13,72.22,39.17,89.18c-18.31,18.31-26.49,36.44-26.83,37.2a4.08,4.08,0,0,0,0,3.25c.34.77,8.52,18.89,26.83,37.2,17,17,46.14,37.17,88.83,37.17s71.87-20.21,88.83-37.17c18.31-18.31,26.49-36.43,26.83-37.2A4.08,4.08,0,0,0,243.66,126.38Zm-32.7,35c-23.07,23-51,34.62-83,34.62s-59.89-11.65-83-34.62A135.71,135.71,0,0,1,20.44,128,135.69,135.69,0,0,1,45,94.62C68.11,71.65,96,60,128,60s59.89,11.65,83,34.62A135.79,135.79,0,0,1,235.56,128,135.71,135.71,0,0,1,211,161.38ZM128,84a44,44,0,1,0,44,44A44.05,44.05,0,0,0,128,84Zm0,80a36,36,0,1,1,36-36A36,36,0,0,1,128,164Z" +},null,-1)])])):oy("",!0)],16))}}),pT={key:0},hT={key:1},fT={key:2},mT={key:3 +},gT={key:4},vT={key:5},bT=Hg({name:"ScalarIconEyeSlash",props:{label:{}, +weight:{}},setup(e){const t=e,{bind:n,weight:r}=E_(t) +;return(e,t)=>(Fb(),qb("svg",cy({xmlns:"http://www.w3.org/2000/svg", +viewBox:"0 0 256 256",fill:"currentColor" +},xm(n)),[Av(e.$slots,"default"),"bold"===xm(r)?(Fb(), +qb("g",pT,[...t[0]||(t[0]=[Jb("path",{ +d:"M56.88,31.93A12,12,0,1,0,39.12,48.07l16,17.65C20.67,88.66,5.72,121.58,5,123.13a12.08,12.08,0,0,0,0,9.75c.37.82,9.13,20.26,28.49,39.61C59.37,198.34,92,212,128,212a131.34,131.34,0,0,0,51-10l20.09,22.1a12,12,0,0,0,17.76-16.14ZM128,188c-29.59,0-55.47-10.73-76.91-31.88A130.69,130.69,0,0,1,29.52,128c5.27-9.31,18.79-29.9,42-44.29l90.09,99.11A109.33,109.33,0,0,1,128,188Zm123-55.12c-.36.81-9,20-28,39.16a12,12,0,1,1-17-16.9A130.48,130.48,0,0,0,226.48,128a130.36,130.36,0,0,0-21.57-28.12C183.46,78.73,157.59,68,128,68c-3.35,0-6.7.14-10,.42a12,12,0,1,1-2-23.91c3.93-.34,8-.51,12-.51,36,0,68.63,13.67,94.49,39.52,19.35,19.35,28.11,38.8,28.48,39.61A12.08,12.08,0,0,1,251,132.88Z" +},null,-1)])])):"duotone"===xm(r)?(Fb(),qb("g",hT,[...t[1]||(t[1]=[Jb("path",{ +d:"M128,56C48,56,16,128,16,128s32,72,112,72,112-72,112-72S208,56,128,56Zm0,112a40,40,0,1,1,40-40A40,40,0,0,1,128,168Z", +opacity:"0.2"},null,-1),Jb("path",{ +d:"M53.92,34.62A8,8,0,1,0,42.08,45.38L61.32,66.55C25,88.84,9.38,123.2,8.69,124.76a8,8,0,0,0,0,6.5c.35.79,8.82,19.57,27.65,38.4C61.43,194.74,93.12,208,128,208a127.11,127.11,0,0,0,52.07-10.83l22,24.21a8,8,0,1,0,11.84-10.76Zm47.33,75.84,41.67,45.85a32,32,0,0,1-41.67-45.85ZM128,192c-30.78,0-57.67-11.19-79.93-33.25A133.16,133.16,0,0,1,25,128c4.69-8.79,19.66-33.39,47.35-49.38l18,19.75a48,48,0,0,0,63.66,70l14.73,16.2A112,112,0,0,1,128,192Zm6-95.43a8,8,0,0,1,3-15.72,48.16,48.16,0,0,1,38.77,42.64,8,8,0,0,1-7.22,8.71,6.39,6.39,0,0,1-.75,0,8,8,0,0,1-8-7.26A32.09,32.09,0,0,0,134,96.57Zm113.28,34.69c-.42.94-10.55,23.37-33.36,43.8a8,8,0,1,1-10.67-11.92A132.77,132.77,0,0,0,231.05,128a133.15,133.15,0,0,0-23.12-30.77C185.67,75.19,158.78,64,128,64a118.37,118.37,0,0,0-19.36,1.57A8,8,0,1,1,106,49.79,134,134,0,0,1,128,48c34.88,0,66.57,13.26,91.66,38.35,18.83,18.83,27.3,37.62,27.65,38.41A8,8,0,0,1,247.31,131.26Z" +},null,-1)])])):"fill"===xm(r)?(Fb(),qb("g",fT,[...t[2]||(t[2]=[Jb("path",{ +d:"M96.68,57.87a4,4,0,0,1,2.08-6.6A130.13,130.13,0,0,1,128,48c34.88,0,66.57,13.26,91.66,38.35,18.83,18.83,27.3,37.62,27.65,38.41a8,8,0,0,1,0,6.5c-.35.79-8.82,19.57-27.65,38.4q-4.28,4.26-8.79,8.07a4,4,0,0,1-5.55-.36ZM213.92,210.62a8,8,0,1,1-11.84,10.76L180,197.13A127.21,127.21,0,0,1,128,208c-34.88,0-66.57-13.26-91.66-38.34C17.51,150.83,9,132.05,8.69,131.26a8,8,0,0,1,0-6.5C9,124,17.51,105.18,36.34,86.35a135,135,0,0,1,25-19.78L42.08,45.38A8,8,0,1,1,53.92,34.62Zm-65.49-48.25-52.69-58a40,40,0,0,0,52.69,58Z" +},null,-1)])])):"light"===xm(r)?(Fb(),qb("g",mT,[...t[3]||(t[3]=[Jb("path",{ +d:"M52.44,36A6,6,0,0,0,43.56,44L64.44,67c-37.28,21.9-53.23,57-53.92,58.57a6,6,0,0,0,0,4.88c.34.77,8.66,19.22,27.24,37.8C55,185.47,84.62,206,128,206a124.91,124.91,0,0,0,52.57-11.25l23,25.29a6,6,0,0,0,8.88-8.08Zm48.62,71.32,45,49.52a34,34,0,0,1-45-49.52ZM128,194c-31.38,0-58.78-11.42-81.45-33.93A134.57,134.57,0,0,1,22.69,128c4.29-8.2,20.1-35.18,50-51.91L92.89,98.3a46,46,0,0,0,61.35,67.48l17.81,19.6A113.47,113.47,0,0,1,128,194Zm6.4-99.4a6,6,0,0,1,2.25-11.79,46.17,46.17,0,0,1,37.15,40.87,6,6,0,0,1-5.42,6.53l-.56,0a6,6,0,0,1-6-5.45A34.1,34.1,0,0,0,134.4,94.6Zm111.08,35.85c-.41.92-10.37,23-32.86,43.12a6,6,0,1,1-8-8.94A134.07,134.07,0,0,0,233.31,128a134.67,134.67,0,0,0-23.86-32.07C186.78,73.42,159.38,62,128,62a120.19,120.19,0,0,0-19.69,1.6,6,6,0,1,1-2-11.83A131.12,131.12,0,0,1,128,50c43.38,0,73,20.54,90.24,37.76,18.58,18.58,26.9,37,27.24,37.81A6,6,0,0,1,245.48,130.45Z" +},null,-1)])])):"regular"===xm(r)?(Fb(),qb("g",gT,[...t[4]||(t[4]=[Jb("path",{ +d:"M53.92,34.62A8,8,0,1,0,42.08,45.38L61.32,66.55C25,88.84,9.38,123.2,8.69,124.76a8,8,0,0,0,0,6.5c.35.79,8.82,19.57,27.65,38.4C61.43,194.74,93.12,208,128,208a127.11,127.11,0,0,0,52.07-10.83l22,24.21a8,8,0,1,0,11.84-10.76Zm47.33,75.84,41.67,45.85a32,32,0,0,1-41.67-45.85ZM128,192c-30.78,0-57.67-11.19-79.93-33.25A133.16,133.16,0,0,1,25,128c4.69-8.79,19.66-33.39,47.35-49.38l18,19.75a48,48,0,0,0,63.66,70l14.73,16.2A112,112,0,0,1,128,192Zm6-95.43a8,8,0,0,1,3-15.72,48.16,48.16,0,0,1,38.77,42.64,8,8,0,0,1-7.22,8.71,6.39,6.39,0,0,1-.75,0,8,8,0,0,1-8-7.26A32.09,32.09,0,0,0,134,96.57Zm113.28,34.69c-.42.94-10.55,23.37-33.36,43.8a8,8,0,1,1-10.67-11.92A132.77,132.77,0,0,0,231.05,128a133.15,133.15,0,0,0-23.12-30.77C185.67,75.19,158.78,64,128,64a118.37,118.37,0,0,0-19.36,1.57A8,8,0,1,1,106,49.79,134,134,0,0,1,128,48c34.88,0,66.57,13.26,91.66,38.35,18.83,18.83,27.3,37.62,27.65,38.41A8,8,0,0,1,247.31,131.26Z" +},null,-1)])])):"thin"===xm(r)?(Fb(),qb("g",vT,[...t[5]||(t[5]=[Jb("path",{ +d:"M51,37.31A4,4,0,0,0,45,42.69L67.59,67.5C29.34,89,13,124.81,12.34,126.38a4.08,4.08,0,0,0,0,3.25c.34.77,8.52,18.89,26.83,37.2,17,17,46.14,37.17,88.83,37.17a122.59,122.59,0,0,0,53.06-11.69l24,26.38a4,4,0,1,0,5.92-5.38ZM149.1,157.16A36,36,0,0,1,101,104.22ZM128,196c-32,0-59.89-11.65-83-34.62A135.81,135.81,0,0,1,20.44,128c3.65-7.23,20.09-36.81,52.68-54.43l22.45,24.7a44,44,0,0,0,59,64.83l20.89,23A114.94,114.94,0,0,1,128,196Zm6.78-103.36a4,4,0,0,1,1.49-7.86,44.15,44.15,0,0,1,35.54,39.09,4,4,0,0,1-3.61,4.35l-.38,0a4,4,0,0,1-4-3.63A36.1,36.1,0,0,0,134.78,92.64Zm108.88,37c-.41.91-10.2,22.58-32.38,42.45a4,4,0,0,1-2.67,1,4,4,0,0,1-2.67-7A136.71,136.71,0,0,0,235.56,128,136.07,136.07,0,0,0,211,94.62C187.89,71.65,160,60,128,60a122,122,0,0,0-20,1.63,4,4,0,0,1-1.32-7.89A129.3,129.3,0,0,1,128,52c42.7,0,71.87,20.22,88.83,37.18,18.31,18.31,26.49,36.44,26.83,37.2A4.08,4.08,0,0,1,243.66,129.63Z" +},null,-1)])])):oy("",!0)],16))}}),yT={key:0},OT={key:1},wT={key:2},xT={key:3 +},kT={key:4},ST={key:5},_T=Hg({name:"ScalarIconFileDashed",props:{label:{}, +weight:{}},setup(e){const t=e,{bind:n,weight:r}=E_(t) +;return(e,t)=>(Fb(),qb("svg",cy({xmlns:"http://www.w3.org/2000/svg", +viewBox:"0 0 256 256",fill:"currentColor" +},xm(n)),[Av(e.$slots,"default"),"bold"===xm(r)?(Fb(), +qb("g",yT,[...t[0]||(t[0]=[Jb("path",{ +d:"M84,224a12,12,0,0,1-12,12H56a20,20,0,0,1-20-20V184a12,12,0,0,1,24,0v28H72A12,12,0,0,1,84,224ZM220,88v48a12,12,0,0,1-24,0V104H148a12,12,0,0,1-12-12V44H120a12,12,0,0,1,0-24h32a12,12,0,0,1,8.49,3.51l56,56A12,12,0,0,1,220,88Zm-60-8h23L160,57ZM80,20H56A20,20,0,0,0,36,40V64a12,12,0,0,0,24,0V44H80a12,12,0,0,0,0-24ZM208,164a12,12,0,0,0-12,12v36h-4a12,12,0,0,0,0,24h8a20,20,0,0,0,20-20V176A12,12,0,0,0,208,164ZM48,156a12,12,0,0,0,12-12V104a12,12,0,0,0-24,0v40A12,12,0,0,0,48,156Zm104,56H112a12,12,0,0,0,0,24h40a12,12,0,0,0,0-24Z" +},null,-1)])])):"duotone"===xm(r)?(Fb(),qb("g",OT,[...t[1]||(t[1]=[Jb("path",{ +d:"M208,88H152V32Z",opacity:"0.2"},null,-1),Jb("path",{ +d:"M80,224a8,8,0,0,1-8,8H56a16,16,0,0,1-16-16V184a8,8,0,0,1,16,0v32H72A8,8,0,0,1,80,224ZM216,88v48a8,8,0,0,1-16,0V96H152a8,8,0,0,1-8-8V40H120a8,8,0,0,1,0-16h32a8,8,0,0,1,5.66,2.34l56,56A8,8,0,0,1,216,88Zm-56-8h28.69L160,51.31ZM80,24H56A16,16,0,0,0,40,40V64a8,8,0,0,0,16,0V40H80a8,8,0,0,0,0-16ZM208,168a8,8,0,0,0-8,8v40h-8a8,8,0,0,0,0,16h8a16,16,0,0,0,16-16V176A8,8,0,0,0,208,168ZM48,152a8,8,0,0,0,8-8V104a8,8,0,0,0-16,0v40A8,8,0,0,0,48,152Zm104,64H112a8,8,0,0,0,0,16h40a8,8,0,0,0,0-16Z" +},null,-1)])])):"fill"===xm(r)?(Fb(),qb("g",wT,[...t[2]||(t[2]=[Jb("path",{ +d:"M80,224a8,8,0,0,1-8,8H56a16,16,0,0,1-16-16V184a8,8,0,0,1,16,0v32H72A8,8,0,0,1,80,224ZM213.66,82.34l-56-56A8,8,0,0,0,152,24H120a8,8,0,0,0,0,16h24V88a8,8,0,0,0,8,8h48v40a8,8,0,0,0,16,0V88A8,8,0,0,0,213.66,82.34ZM80,24H56A16,16,0,0,0,40,40V64a8,8,0,0,0,16,0V40H80a8,8,0,0,0,0-16ZM208,168a8,8,0,0,0-8,8v40h-8a8,8,0,0,0,0,16h8a16,16,0,0,0,16-16V176A8,8,0,0,0,208,168ZM48,152a8,8,0,0,0,8-8V104a8,8,0,0,0-16,0v40A8,8,0,0,0,48,152Zm104,64H112a8,8,0,0,0,0,16h40a8,8,0,0,0,0-16Z" +},null,-1)])])):"light"===xm(r)?(Fb(),qb("g",xT,[...t[3]||(t[3]=[Jb("path",{ +d:"M78,224a6,6,0,0,1-6,6H56a14,14,0,0,1-14-14V184a6,6,0,0,1,12,0v32a2,2,0,0,0,2,2H72A6,6,0,0,1,78,224ZM214,88v48a6,6,0,0,1-12,0V94H152a6,6,0,0,1-6-6V38H120a6,6,0,0,1,0-12h32a6,6,0,0,1,4.24,1.76l56,56A6,6,0,0,1,214,88Zm-56-6h35.51L158,46.49ZM80,26H56A14,14,0,0,0,42,40V64a6,6,0,0,0,12,0V40a2,2,0,0,1,2-2H80a6,6,0,0,0,0-12ZM208,170a6,6,0,0,0-6,6v40a2,2,0,0,1-2,2h-8a6,6,0,0,0,0,12h8a14,14,0,0,0,14-14V176A6,6,0,0,0,208,170ZM48,150a6,6,0,0,0,6-6V104a6,6,0,0,0-12,0v40A6,6,0,0,0,48,150Zm104,68H112a6,6,0,0,0,0,12h40a6,6,0,0,0,0-12Z" +},null,-1)])])):"regular"===xm(r)?(Fb(),qb("g",kT,[...t[4]||(t[4]=[Jb("path",{ +d:"M80,224a8,8,0,0,1-8,8H56a16,16,0,0,1-16-16V184a8,8,0,0,1,16,0v32H72A8,8,0,0,1,80,224ZM216,88v48a8,8,0,0,1-16,0V96H152a8,8,0,0,1-8-8V40H120a8,8,0,0,1,0-16h32a8,8,0,0,1,5.66,2.34l56,56A8,8,0,0,1,216,88Zm-56-8h28.69L160,51.31ZM80,24H56A16,16,0,0,0,40,40V64a8,8,0,0,0,16,0V40H80a8,8,0,0,0,0-16ZM208,168a8,8,0,0,0-8,8v40h-8a8,8,0,0,0,0,16h8a16,16,0,0,0,16-16V176A8,8,0,0,0,208,168ZM48,152a8,8,0,0,0,8-8V104a8,8,0,0,0-16,0v40A8,8,0,0,0,48,152Zm104,64H112a8,8,0,0,0,0,16h40a8,8,0,0,0,0-16Z" +},null,-1)])])):"thin"===xm(r)?(Fb(),qb("g",ST,[...t[5]||(t[5]=[Jb("path",{ +d:"M76,224a4,4,0,0,1-4,4H56a12,12,0,0,1-12-12V184a4,4,0,0,1,8,0v32a4,4,0,0,0,4,4H72A4,4,0,0,1,76,224ZM212,88v48a4,4,0,0,1-8,0V92H152a4,4,0,0,1-4-4V36H120a4,4,0,0,1,0-8h32a4,4,0,0,1,2.83,1.17l56,56A4,4,0,0,1,212,88Zm-56-4h42.34L156,41.66ZM80,28H56A12,12,0,0,0,44,40V64a4,4,0,0,0,8,0V40a4,4,0,0,1,4-4H80a4,4,0,0,0,0-8ZM208,172a4,4,0,0,0-4,4v40a4,4,0,0,1-4,4h-8a4,4,0,0,0,0,8h8a12,12,0,0,0,12-12V176A4,4,0,0,0,208,172ZM48,148a4,4,0,0,0,4-4V104a4,4,0,0,0-8,0v40A4,4,0,0,0,48,148Zm104,72H112a4,4,0,0,0,0,8h40a4,4,0,0,0,0-8Z" +},null,-1)])])):oy("",!0)],16))}}),AT={key:0},TT={key:1},ET={key:2},CT={key:3 +},$T={key:4},PT={key:5},IT=Hg({name:"ScalarIconFileMd",props:{label:{},weight:{} +},setup(e){const t=e,{bind:n,weight:r}=E_(t);return(e,t)=>(Fb(),qb("svg",cy({ +xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 256 256",fill:"currentColor" +},xm(n)),[Av(e.$slots,"default"),"bold"===xm(r)?(Fb(), +qb("g",AT,[...t[0]||(t[0]=[Jb("path",{ +d:"M100,152v56a12,12,0,0,1-24,0V190.07l-6.17,8.81a12,12,0,0,1-19.66,0L44,190.07V208a12,12,0,0,1-24,0V152a12,12,0,0,1,21.83-6.88L60,171.07l18.17-25.95A12,12,0,0,1,100,152Zm84,28a40,40,0,0,1-40,40H128a12,12,0,0,1-12-12V152a12,12,0,0,1,12-12h16A40,40,0,0,1,184,180Zm-24,0a16,16,0,0,0-16-16h-4v32h4A16,16,0,0,0,160,180Zm60-92V224a12,12,0,0,1-24,0V104H148a12,12,0,0,1-12-12V44H60v64a12,12,0,0,1-24,0V40A20,20,0,0,1,56,20h96a12,12,0,0,1,8.49,3.52l56,56A12,12,0,0,1,220,88Zm-60-8h23L160,57Z" +},null,-1)])])):"duotone"===xm(r)?(Fb(),qb("g",TT,[...t[1]||(t[1]=[Jb("path",{ +d:"M208,88H152V32Z",opacity:"0.2"},null,-1),Jb("path",{ +d:"M213.66,82.34l-56-56A8,8,0,0,0,152,24H56A16,16,0,0,0,40,40v72a8,8,0,0,0,16,0V40h88V88a8,8,0,0,0,8,8h48V224a8,8,0,0,0,16,0V88A8,8,0,0,0,213.66,82.34ZM160,51.31,188.69,80H160ZM144,144H128a8,8,0,0,0-8,8v56a8,8,0,0,0,8,8h16a36,36,0,0,0,0-72Zm0,56h-8V160h8a20,20,0,0,1,0,40Zm-40-48v56a8,8,0,0,1-16,0V177.38L74.55,196.59a8,8,0,0,1-13.1,0L48,177.38V208a8,8,0,0,1-16,0V152a8,8,0,0,1,14.55-4.59L68,178.05l21.45-30.64A8,8,0,0,1,104,152Z" +},null,-1)])])):"fill"===xm(r)?(Fb(),qb("g",ET,[...t[2]||(t[2]=[Jb("path",{ +d:"M213.66,82.34l-56-56A8,8,0,0,0,152,24H56A16,16,0,0,0,40,40v76a4,4,0,0,0,4,4H196a4,4,0,0,1,4,4V224a8,8,0,0,0,9.19,7.91,8.15,8.15,0,0,0,6.81-8.16V88A8,8,0,0,0,213.66,82.34ZM152,88V44l44,44Zm-8,56H128a8,8,0,0,0-8,8v56a8,8,0,0,0,8,8h15.32c19.66,0,36.21-15.48,36.67-35.13A36,36,0,0,0,144,144Zm-.49,56H136V160h8a20,20,0,0,1,20,20.77C163.58,191.59,154.34,200,143.51,200ZM104,152v55.73A8.17,8.17,0,0,1,96.53,216,8,8,0,0,1,88,208V177.38l-13.32,19a8.3,8.3,0,0,1-4.2,3.2,8,8,0,0,1-9-3L48,177.38v30.35A8.17,8.17,0,0,1,40.53,216,8,8,0,0,1,32,208V152.31a8.27,8.27,0,0,1,4.56-7.53,8,8,0,0,1,10,2.63L68,178.05l21.27-30.39a8.28,8.28,0,0,1,8.06-3.55A8,8,0,0,1,104,152Z" +},null,-1)])])):"light"===xm(r)?(Fb(),qb("g",CT,[...t[3]||(t[3]=[Jb("path",{ +d:"M212.24,83.76l-56-56A6,6,0,0,0,152,26H56A14,14,0,0,0,42,40v72a6,6,0,0,0,12,0V40a2,2,0,0,1,2-2h90V88a6,6,0,0,0,6,6h50V224a6,6,0,0,0,12,0V88A6,6,0,0,0,212.24,83.76ZM158,46.48,193.52,82H158ZM144,146H128a6,6,0,0,0-6,6v56a6,6,0,0,0,6,6h16a34,34,0,0,0,0-68Zm0,56H134V158h10a22,22,0,0,1,0,44Zm-42-50v56a6,6,0,0,1-12,0V171L72.92,195.44a6,6,0,0,1-9.84,0L46,171v37a6,6,0,0,1-12,0V152a6,6,0,0,1,10.92-3.44l23.08,33,23.08-33A6,6,0,0,1,102,152Z" +},null,-1)])])):"regular"===xm(r)?(Fb(),qb("g",$T,[...t[4]||(t[4]=[Jb("path",{ +d:"M213.66,82.34l-56-56A8,8,0,0,0,152,24H56A16,16,0,0,0,40,40v72a8,8,0,0,0,16,0V40h88V88a8,8,0,0,0,8,8h48V224a8,8,0,0,0,16,0V88A8,8,0,0,0,213.66,82.34ZM160,51.31,188.69,80H160ZM144,144H128a8,8,0,0,0-8,8v56a8,8,0,0,0,8,8h16a36,36,0,0,0,0-72Zm0,56h-8V160h8a20,20,0,0,1,0,40Zm-40-48v56a8,8,0,0,1-16,0V177.38L74.55,196.59a8,8,0,0,1-13.1,0L48,177.38V208a8,8,0,0,1-16,0V152a8,8,0,0,1,14.55-4.59L68,178.05l21.45-30.64A8,8,0,0,1,104,152Z" +},null,-1)])])):"thin"===xm(r)?(Fb(),qb("g",PT,[...t[5]||(t[5]=[Jb("path",{ +d:"M210.83,85.17l-56-56A4,4,0,0,0,152,28H56A12,12,0,0,0,44,40v72a4,4,0,0,0,8,0V40a4,4,0,0,1,4-4h92V88a4,4,0,0,0,4,4h52V224a4,4,0,0,0,8,0V88A4,4,0,0,0,210.83,85.17ZM156,41.65,198.34,84H156ZM144,148H128a4,4,0,0,0-4,4v56a4,4,0,0,0,4,4h16a32,32,0,0,0,0-64Zm0,56H132V156h12a24,24,0,0,1,0,48Zm-44-52v56a4,4,0,0,1-8,0V164.69l-20.72,29.6a4,4,0,0,1-6.56,0L44,164.69V208a4,4,0,0,1-8,0V152a4,4,0,0,1,7.28-2.29L68,185l24.72-35.31A4,4,0,0,1,100,152Z" +},null,-1)])])):oy("",!0)],16))}}),DT={key:0},MT={key:1},NT={key:2},RT={key:3 +},LT={key:4},BT={key:5},jT=Hg({name:"ScalarIconFileText",props:{label:{}, +weight:{}},setup(e){const t=e,{bind:n,weight:r}=E_(t) +;return(e,t)=>(Fb(),qb("svg",cy({xmlns:"http://www.w3.org/2000/svg", +viewBox:"0 0 256 256",fill:"currentColor" +},xm(n)),[Av(e.$slots,"default"),"bold"===xm(r)?(Fb(), +qb("g",DT,[...t[0]||(t[0]=[Jb("path",{ +d:"M216.49,79.52l-56-56A12,12,0,0,0,152,20H56A20,20,0,0,0,36,40V216a20,20,0,0,0,20,20H200a20,20,0,0,0,20-20V88A12,12,0,0,0,216.49,79.52ZM160,57l23,23H160ZM60,212V44h76V92a12,12,0,0,0,12,12h48V212Zm112-80a12,12,0,0,1-12,12H96a12,12,0,0,1,0-24h64A12,12,0,0,1,172,132Zm0,40a12,12,0,0,1-12,12H96a12,12,0,0,1,0-24h64A12,12,0,0,1,172,172Z" +},null,-1)])])):"duotone"===xm(r)?(Fb(),qb("g",MT,[...t[1]||(t[1]=[Jb("path",{ +d:"M208,88H152V32Z",opacity:"0.2"},null,-1),Jb("path",{ +d:"M213.66,82.34l-56-56A8,8,0,0,0,152,24H56A16,16,0,0,0,40,40V216a16,16,0,0,0,16,16H200a16,16,0,0,0,16-16V88A8,8,0,0,0,213.66,82.34ZM160,51.31,188.69,80H160ZM200,216H56V40h88V88a8,8,0,0,0,8,8h48V216Zm-32-80a8,8,0,0,1-8,8H96a8,8,0,0,1,0-16h64A8,8,0,0,1,168,136Zm0,32a8,8,0,0,1-8,8H96a8,8,0,0,1,0-16h64A8,8,0,0,1,168,168Z" +},null,-1)])])):"fill"===xm(r)?(Fb(),qb("g",NT,[...t[2]||(t[2]=[Jb("path",{ +d:"M213.66,82.34l-56-56A8,8,0,0,0,152,24H56A16,16,0,0,0,40,40V216a16,16,0,0,0,16,16H200a16,16,0,0,0,16-16V88A8,8,0,0,0,213.66,82.34ZM160,176H96a8,8,0,0,1,0-16h64a8,8,0,0,1,0,16Zm0-32H96a8,8,0,0,1,0-16h64a8,8,0,0,1,0,16Zm-8-56V44l44,44Z" +},null,-1)])])):"light"===xm(r)?(Fb(),qb("g",RT,[...t[3]||(t[3]=[Jb("path",{ +d:"M212.24,83.76l-56-56A6,6,0,0,0,152,26H56A14,14,0,0,0,42,40V216a14,14,0,0,0,14,14H200a14,14,0,0,0,14-14V88A6,6,0,0,0,212.24,83.76ZM158,46.48,193.52,82H158ZM200,218H56a2,2,0,0,1-2-2V40a2,2,0,0,1,2-2h90V88a6,6,0,0,0,6,6h50V216A2,2,0,0,1,200,218Zm-34-82a6,6,0,0,1-6,6H96a6,6,0,0,1,0-12h64A6,6,0,0,1,166,136Zm0,32a6,6,0,0,1-6,6H96a6,6,0,0,1,0-12h64A6,6,0,0,1,166,168Z" +},null,-1)])])):"regular"===xm(r)?(Fb(),qb("g",LT,[...t[4]||(t[4]=[Jb("path",{ +d:"M213.66,82.34l-56-56A8,8,0,0,0,152,24H56A16,16,0,0,0,40,40V216a16,16,0,0,0,16,16H200a16,16,0,0,0,16-16V88A8,8,0,0,0,213.66,82.34ZM160,51.31,188.69,80H160ZM200,216H56V40h88V88a8,8,0,0,0,8,8h48V216Zm-32-80a8,8,0,0,1-8,8H96a8,8,0,0,1,0-16h64A8,8,0,0,1,168,136Zm0,32a8,8,0,0,1-8,8H96a8,8,0,0,1,0-16h64A8,8,0,0,1,168,168Z" +},null,-1)])])):"thin"===xm(r)?(Fb(),qb("g",BT,[...t[5]||(t[5]=[Jb("path",{ +d:"M210.83,85.17l-56-56A4,4,0,0,0,152,28H56A12,12,0,0,0,44,40V216a12,12,0,0,0,12,12H200a12,12,0,0,0,12-12V88A4,4,0,0,0,210.83,85.17ZM156,41.65,198.34,84H156ZM200,220H56a4,4,0,0,1-4-4V40a4,4,0,0,1,4-4h92V88a4,4,0,0,0,4,4h52V216A4,4,0,0,1,200,220Zm-36-84a4,4,0,0,1-4,4H96a4,4,0,0,1,0-8h64A4,4,0,0,1,164,136Zm0,32a4,4,0,0,1-4,4H96a4,4,0,0,1,0-8h64A4,4,0,0,1,164,168Z" +},null,-1)])])):oy("",!0)],16))}}),UT={key:0},zT={key:1},ZT={key:2},FT={key:3 +},HT={key:4},QT={key:5},VT=Hg({name:"ScalarIconGavel",props:{label:{},weight:{} +},setup(e){const t=e,{bind:n,weight:r}=E_(t);return(e,t)=>(Fb(),qb("svg",cy({ +xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 256 256",fill:"currentColor" +},xm(n)),[Av(e.$slots,"default"),"bold"===xm(r)?(Fb(), +qb("g",UT,[...t[0]||(t[0]=[Jb("path",{ +d:"M246.14,113.86l-16-16a20,20,0,0,0-23.06-3.75l-45.2-45.2a20,20,0,0,0-3.74-23.06l-16-16a20,20,0,0,0-28.28,0l-64,64a20,20,0,0,0,0,28.28l16,16a20,20,0,0,0,23,3.79L29.36,181.38a32,32,0,0,0,45.26,45.26L134,167.21a20,20,0,0,0,3.81,22.94l16,16a20,20,0,0,0,28.29,0l64-64a20,20,0,0,0,0-28.29ZM80,98.34,69.64,88,128,29.65,138.34,40ZM57.64,209.67a8,8,0,0,1-11.31-11.32l59.52-59.52,11.31,11.32Zm92.7-60.29-43.72-43.72,39-39,43.72,43.72Zm17.65,37L157.65,176,216,117.66,226.34,128Z" +},null,-1)])])):"duotone"===xm(r)?(Fb(),qb("g",zT,[...t[1]||(t[1]=[Jb("path",{ +d:"M149.66,45.66l-64,64a8,8,0,0,1-11.32,0l-16-16a8,8,0,0,1,0-11.32l64-64a8,8,0,0,1,11.32,0l16,16A8,8,0,0,1,149.66,45.66Zm88,76.68-16-16a8,8,0,0,0-11.32,0l-64,64a8,8,0,0,0,0,11.32l16,16a8,8,0,0,0,11.32,0l64-64A8,8,0,0,0,237.66,122.34Z", +opacity:"0.2"},null,-1),Jb("path",{ +d:"M243.32,116.69l-16-16a16,16,0,0,0-20.84-1.53L156.84,49.52a16,16,0,0,0-1.52-20.84l-16-16a16,16,0,0,0-22.63,0l-64,64a16,16,0,0,0,0,22.63l16,16a16,16,0,0,0,20.83,1.52L96.69,124,31.31,189.38A25,25,0,0,0,66.63,224.7L132,159.32l7.17,7.16a16,16,0,0,0,1.52,20.84l16,16a16,16,0,0,0,22.63,0l64-64A16,16,0,0,0,243.32,116.69ZM80,104,64,88l64-64,16,16ZM55.32,213.38a9,9,0,0,1-12.69,0,9,9,0,0,1,0-12.68L108,135.32,120.69,148ZM101,105.66,145.66,61,195,110.34,150.35,155ZM168,192l-16-16,4-4h0l56-56h0l4-4,16,16Z" +},null,-1)])])):"fill"===xm(r)?(Fb(),qb("g",ZT,[...t[2]||(t[2]=[Jb("path",{ +d:"M52.69,99.31a16,16,0,0,1,0-22.63l64-64a16,16,0,0,1,22.63,22.63l-64,64a16,16,0,0,1-22.63,0Zm190.63,17.37a16,16,0,0,0-22.63,0l-64,64a16,16,0,0,0,0,22.63h0a16,16,0,0,0,22.63,0l64-64A16,16,0,0,0,243.32,116.68Zm-35.11-15.8L155.12,47.79a4,4,0,0,0-5.66,0L87.8,109.45a4,4,0,0,0,0,5.66L103,130.34,28.69,204.69a16,16,0,0,0,22.62,22.62L125.66,153l15.23,15.23a4,4,0,0,0,5.66,0l61.66-61.66A4,4,0,0,0,208.21,100.88Z" +},null,-1)])])):"light"===xm(r)?(Fb(),qb("g",FT,[...t[3]||(t[3]=[Jb("path",{ +d:"M241.91,118.1l-16-16a14,14,0,0,0-19.55-.23L154.13,49.64a14,14,0,0,0-.23-19.55l-16-16a14,14,0,0,0-19.8,0l-64,64a14,14,0,0,0,0,19.8l16,16a14,14,0,0,0,19.55.23L99.52,124,32.73,190.79a23,23,0,0,0,32.48,32.49L132,156.49l9.87,9.87a14,14,0,0,0,.23,19.55l16,16a14,14,0,0,0,19.8,0l64-64A14,14,0,0,0,241.91,118.1Zm-91.56,39.76-52.21-52.2,47.52-47.52,52.2,52.2ZM78.59,105.41l-16-16a2,2,0,0,1,0-2.83l64-64a2,2,0,0,1,2.83,0l16,16a2,2,0,0,1,0,2.83l-64,64A2,2,0,0,1,78.59,105.41ZM56.73,214.8a11,11,0,0,1-15.52-15.52L108,132.49,123.52,148Zm176.69-85.38-64,64a2,2,0,0,1-2.83,0l-16-16a2,2,0,0,1,0-2.83l64-64a2,2,0,0,1,2.83,0l16,16A2,2,0,0,1,233.42,129.42Z" +},null,-1)])])):"regular"===xm(r)?(Fb(),qb("g",HT,[...t[4]||(t[4]=[Jb("path",{ +d:"M243.32,116.69l-16-16a16,16,0,0,0-20.84-1.53L156.84,49.52a16,16,0,0,0-1.52-20.84l-16-16a16,16,0,0,0-22.63,0l-64,64a16,16,0,0,0,0,22.63l16,16a16,16,0,0,0,20.83,1.52L96.69,124,31.31,189.38A25,25,0,0,0,66.63,224.7L132,159.32l7.17,7.16a16,16,0,0,0,1.52,20.84l16,16a16,16,0,0,0,22.63,0l64-64A16,16,0,0,0,243.32,116.69ZM80,104,64,88l64-64,16,16ZM55.32,213.38a9,9,0,0,1-12.69,0,9,9,0,0,1,0-12.68L108,135.32,120.69,148ZM101,105.66,145.66,61,195,110.34,150.35,155ZM168,192l-16-16,4-4h0l56-56h0l4-4,16,16Z" +},null,-1)])])):"thin"===xm(r)?(Fb(),qb("g",QT,[...t[5]||(t[5]=[Jb("path",{ +d:"M240.49,119.52l-16-16a12,12,0,0,0-17,0l-1.17,1.17-55-55,1.18-1.17a12,12,0,0,0,0-17l-16-16a12,12,0,0,0-17,0l-64,64a12,12,0,0,0,0,17l16,16a12,12,0,0,0,17,0l1.17-1.18L102.34,124l-68.2,68.21A21,21,0,0,0,63.8,221.87L132,153.66l12.69,12.69-1.18,1.17a12,12,0,0,0,0,17l16,16a12,12,0,0,0,17,0l64-64a12,12,0,0,0,0-17ZM77.17,106.83l-16-16a4,4,0,0,1,0-5.66l64-64a4,4,0,0,1,5.66,0l16,16a4,4,0,0,1,0,5.65l-64,64A4,4,0,0,1,77.17,106.83Zm-19,109.38A13,13,0,1,1,39.8,197.87L108,129.66,126.34,148ZM95.31,105.66l50.35-50.35,55,55-50.35,50.35Zm139.52,25.17-64,64a4,4,0,0,1-5.66,0l-16-16a4,4,0,0,1,0-5.65l64-64a4,4,0,0,1,5.66,0l16,16a4,4,0,0,1,0,5.66Z" +},null,-1)])])):oy("",!0)],16))}}),qT={key:0},WT={key:1},XT={key:2},GT={key:3 +},YT={key:4},KT={key:5},JT=Hg({name:"ScalarIconGear",props:{label:{},weight:{}}, +setup(e){const t=e,{bind:n,weight:r}=E_(t);return(e,t)=>(Fb(),qb("svg",cy({ +xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 256 256",fill:"currentColor" +},xm(n)),[Av(e.$slots,"default"),"bold"===xm(r)?(Fb(), +qb("g",qT,[...t[0]||(t[0]=[Jb("path",{ +d:"M128,76a52,52,0,1,0,52,52A52.06,52.06,0,0,0,128,76Zm0,80a28,28,0,1,1,28-28A28,28,0,0,1,128,156Zm92-27.21v-1.58l14-17.51a12,12,0,0,0,2.23-10.59A111.75,111.75,0,0,0,225,71.89,12,12,0,0,0,215.89,66L193.61,63.5l-1.11-1.11L190,40.1A12,12,0,0,0,184.11,31a111.67,111.67,0,0,0-27.23-11.27A12,12,0,0,0,146.3,22L128.79,36h-1.58L109.7,22a12,12,0,0,0-10.59-2.23A111.75,111.75,0,0,0,71.89,31.05,12,12,0,0,0,66,40.11L63.5,62.39,62.39,63.5,40.1,66A12,12,0,0,0,31,71.89,111.67,111.67,0,0,0,19.77,99.12,12,12,0,0,0,22,109.7l14,17.51v1.58L22,146.3a12,12,0,0,0-2.23,10.59,111.75,111.75,0,0,0,11.29,27.22A12,12,0,0,0,40.11,190l22.28,2.48,1.11,1.11L66,215.9A12,12,0,0,0,71.89,225a111.67,111.67,0,0,0,27.23,11.27A12,12,0,0,0,109.7,234l17.51-14h1.58l17.51,14a12,12,0,0,0,10.59,2.23A111.75,111.75,0,0,0,184.11,225a12,12,0,0,0,5.91-9.06l2.48-22.28,1.11-1.11L215.9,190a12,12,0,0,0,9.06-5.91,111.67,111.67,0,0,0,11.27-27.23A12,12,0,0,0,234,146.3Zm-24.12-4.89a70.1,70.1,0,0,1,0,8.2,12,12,0,0,0,2.61,8.22l12.84,16.05A86.47,86.47,0,0,1,207,166.86l-20.43,2.27a12,12,0,0,0-7.65,4,69,69,0,0,1-5.8,5.8,12,12,0,0,0-4,7.65L166.86,207a86.47,86.47,0,0,1-10.49,4.35l-16.05-12.85a12,12,0,0,0-7.5-2.62c-.24,0-.48,0-.72,0a70.1,70.1,0,0,1-8.2,0,12.06,12.06,0,0,0-8.22,2.6L99.63,211.33A86.47,86.47,0,0,1,89.14,207l-2.27-20.43a12,12,0,0,0-4-7.65,69,69,0,0,1-5.8-5.8,12,12,0,0,0-7.65-4L49,166.86a86.47,86.47,0,0,1-4.35-10.49l12.84-16.05a12,12,0,0,0,2.61-8.22,70.1,70.1,0,0,1,0-8.2,12,12,0,0,0-2.61-8.22L44.67,99.63A86.47,86.47,0,0,1,49,89.14l20.43-2.27a12,12,0,0,0,7.65-4,69,69,0,0,1,5.8-5.8,12,12,0,0,0,4-7.65L89.14,49a86.47,86.47,0,0,1,10.49-4.35l16.05,12.85a12.06,12.06,0,0,0,8.22,2.6,70.1,70.1,0,0,1,8.2,0,12,12,0,0,0,8.22-2.6l16.05-12.85A86.47,86.47,0,0,1,166.86,49l2.27,20.43a12,12,0,0,0,4,7.65,69,69,0,0,1,5.8,5.8,12,12,0,0,0,7.65,4L207,89.14a86.47,86.47,0,0,1,4.35,10.49l-12.84,16.05A12,12,0,0,0,195.88,123.9Z" +},null,-1)])])):"duotone"===xm(r)?(Fb(),qb("g",WT,[...t[1]||(t[1]=[Jb("path",{ +d:"M207.86,123.18l16.78-21a99.14,99.14,0,0,0-10.07-24.29l-26.7-3a81,81,0,0,0-6.81-6.81l-3-26.71a99.43,99.43,0,0,0-24.3-10l-21,16.77a81.59,81.59,0,0,0-9.64,0l-21-16.78A99.14,99.14,0,0,0,77.91,41.43l-3,26.7a81,81,0,0,0-6.81,6.81l-26.71,3a99.43,99.43,0,0,0-10,24.3l16.77,21a81.59,81.59,0,0,0,0,9.64l-16.78,21a99.14,99.14,0,0,0,10.07,24.29l26.7,3a81,81,0,0,0,6.81,6.81l3,26.71a99.43,99.43,0,0,0,24.3,10l21-16.77a81.59,81.59,0,0,0,9.64,0l21,16.78a99.14,99.14,0,0,0,24.29-10.07l3-26.7a81,81,0,0,0,6.81-6.81l26.71-3a99.43,99.43,0,0,0,10-24.3l-16.77-21A81.59,81.59,0,0,0,207.86,123.18ZM128,168a40,40,0,1,1,40-40A40,40,0,0,1,128,168Z", +opacity:"0.2"},null,-1),Jb("path",{ +d:"M128,80a48,48,0,1,0,48,48A48.05,48.05,0,0,0,128,80Zm0,80a32,32,0,1,1,32-32A32,32,0,0,1,128,160Zm88-29.84q.06-2.16,0-4.32l14.92-18.64a8,8,0,0,0,1.48-7.06,107.6,107.6,0,0,0-10.88-26.25,8,8,0,0,0-6-3.93l-23.72-2.64q-1.48-1.56-3-3L186,40.54a8,8,0,0,0-3.94-6,107.29,107.29,0,0,0-26.25-10.86,8,8,0,0,0-7.06,1.48L130.16,40Q128,40,125.84,40L107.2,25.11a8,8,0,0,0-7.06-1.48A107.6,107.6,0,0,0,73.89,34.51a8,8,0,0,0-3.93,6L67.32,64.27q-1.56,1.49-3,3L40.54,70a8,8,0,0,0-6,3.94,107.71,107.71,0,0,0-10.87,26.25,8,8,0,0,0,1.49,7.06L40,125.84Q40,128,40,130.16L25.11,148.8a8,8,0,0,0-1.48,7.06,107.6,107.6,0,0,0,10.88,26.25,8,8,0,0,0,6,3.93l23.72,2.64q1.49,1.56,3,3L70,215.46a8,8,0,0,0,3.94,6,107.71,107.71,0,0,0,26.25,10.87,8,8,0,0,0,7.06-1.49L125.84,216q2.16.06,4.32,0l18.64,14.92a8,8,0,0,0,7.06,1.48,107.21,107.21,0,0,0,26.25-10.88,8,8,0,0,0,3.93-6l2.64-23.72q1.56-1.48,3-3L215.46,186a8,8,0,0,0,6-3.94,107.71,107.71,0,0,0,10.87-26.25,8,8,0,0,0-1.49-7.06Zm-16.1-6.5a73.93,73.93,0,0,1,0,8.68,8,8,0,0,0,1.74,5.48l14.19,17.73a91.57,91.57,0,0,1-6.23,15L187,173.11a8,8,0,0,0-5.1,2.64,74.11,74.11,0,0,1-6.14,6.14,8,8,0,0,0-2.64,5.1l-2.51,22.58a91.32,91.32,0,0,1-15,6.23l-17.74-14.19a8,8,0,0,0-5-1.75h-.48a73.93,73.93,0,0,1-8.68,0,8.06,8.06,0,0,0-5.48,1.74L100.45,215.8a91.57,91.57,0,0,1-15-6.23L82.89,187a8,8,0,0,0-2.64-5.1,74.11,74.11,0,0,1-6.14-6.14,8,8,0,0,0-5.1-2.64L46.43,170.6a91.32,91.32,0,0,1-6.23-15l14.19-17.74a8,8,0,0,0,1.74-5.48,73.93,73.93,0,0,1,0-8.68,8,8,0,0,0-1.74-5.48L40.2,100.45a91.57,91.57,0,0,1,6.23-15L69,82.89a8,8,0,0,0,5.1-2.64,74.11,74.11,0,0,1,6.14-6.14A8,8,0,0,0,82.89,69L85.4,46.43a91.32,91.32,0,0,1,15-6.23l17.74,14.19a8,8,0,0,0,5.48,1.74,73.93,73.93,0,0,1,8.68,0,8.06,8.06,0,0,0,5.48-1.74L155.55,40.2a91.57,91.57,0,0,1,15,6.23L173.11,69a8,8,0,0,0,2.64,5.1,74.11,74.11,0,0,1,6.14,6.14,8,8,0,0,0,5.1,2.64l22.58,2.51a91.32,91.32,0,0,1,6.23,15l-14.19,17.74A8,8,0,0,0,199.87,123.66Z" +},null,-1)])])):"fill"===xm(r)?(Fb(),qb("g",XT,[...t[2]||(t[2]=[Jb("path",{ +d:"M216,130.16q.06-2.16,0-4.32l14.92-18.64a8,8,0,0,0,1.48-7.06,107.6,107.6,0,0,0-10.88-26.25,8,8,0,0,0-6-3.93l-23.72-2.64q-1.48-1.56-3-3L186,40.54a8,8,0,0,0-3.94-6,107.29,107.29,0,0,0-26.25-10.86,8,8,0,0,0-7.06,1.48L130.16,40Q128,40,125.84,40L107.2,25.11a8,8,0,0,0-7.06-1.48A107.6,107.6,0,0,0,73.89,34.51a8,8,0,0,0-3.93,6L67.32,64.27q-1.56,1.49-3,3L40.54,70a8,8,0,0,0-6,3.94,107.71,107.71,0,0,0-10.87,26.25,8,8,0,0,0,1.49,7.06L40,125.84Q40,128,40,130.16L25.11,148.8a8,8,0,0,0-1.48,7.06,107.6,107.6,0,0,0,10.88,26.25,8,8,0,0,0,6,3.93l23.72,2.64q1.49,1.56,3,3L70,215.46a8,8,0,0,0,3.94,6,107.71,107.71,0,0,0,26.25,10.87,8,8,0,0,0,7.06-1.49L125.84,216q2.16.06,4.32,0l18.64,14.92a8,8,0,0,0,7.06,1.48,107.21,107.21,0,0,0,26.25-10.88,8,8,0,0,0,3.93-6l2.64-23.72q1.56-1.48,3-3L215.46,186a8,8,0,0,0,6-3.94,107.71,107.71,0,0,0,10.87-26.25,8,8,0,0,0-1.49-7.06ZM128,168a40,40,0,1,1,40-40A40,40,0,0,1,128,168Z" +},null,-1)])])):"light"===xm(r)?(Fb(),qb("g",GT,[...t[3]||(t[3]=[Jb("path",{ +d:"M128,82a46,46,0,1,0,46,46A46.06,46.06,0,0,0,128,82Zm0,80a34,34,0,1,1,34-34A34,34,0,0,1,128,162ZM214,130.84c.06-1.89.06-3.79,0-5.68L229.33,106a6,6,0,0,0,1.11-5.29A105.34,105.34,0,0,0,219.76,74.9a6,6,0,0,0-4.53-3l-24.45-2.71q-1.93-2.07-4-4l-2.72-24.46a6,6,0,0,0-3-4.53,105.65,105.65,0,0,0-25.77-10.66A6,6,0,0,0,150,26.68l-19.2,15.37c-1.89-.06-3.79-.06-5.68,0L106,26.67a6,6,0,0,0-5.29-1.11A105.34,105.34,0,0,0,74.9,36.24a6,6,0,0,0-3,4.53L69.23,65.22q-2.07,1.94-4,4L40.76,72a6,6,0,0,0-4.53,3,105.65,105.65,0,0,0-10.66,25.77A6,6,0,0,0,26.68,106l15.37,19.2c-.06,1.89-.06,3.79,0,5.68L26.67,150.05a6,6,0,0,0-1.11,5.29A105.34,105.34,0,0,0,36.24,181.1a6,6,0,0,0,4.53,3l24.45,2.71q1.94,2.07,4,4L72,215.24a6,6,0,0,0,3,4.53,105.65,105.65,0,0,0,25.77,10.66,6,6,0,0,0,5.29-1.11L125.16,214c1.89.06,3.79.06,5.68,0l19.21,15.38a6,6,0,0,0,3.75,1.31,6.2,6.2,0,0,0,1.54-.2,105.34,105.34,0,0,0,25.76-10.68,6,6,0,0,0,3-4.53l2.71-24.45q2.07-1.93,4-4l24.46-2.72a6,6,0,0,0,4.53-3,105.49,105.49,0,0,0,10.66-25.77,6,6,0,0,0-1.11-5.29Zm-3.1,41.63-23.64,2.63a6,6,0,0,0-3.82,2,75.14,75.14,0,0,1-6.31,6.31,6,6,0,0,0-2,3.82l-2.63,23.63A94.28,94.28,0,0,1,155.14,218l-18.57-14.86a6,6,0,0,0-3.75-1.31h-.36a78.07,78.07,0,0,1-8.92,0,6,6,0,0,0-4.11,1.3L100.87,218a94.13,94.13,0,0,1-17.34-7.17L80.9,187.21a6,6,0,0,0-2-3.82,75.14,75.14,0,0,1-6.31-6.31,6,6,0,0,0-3.82-2l-23.63-2.63A94.28,94.28,0,0,1,38,155.14l14.86-18.57a6,6,0,0,0,1.3-4.11,78.07,78.07,0,0,1,0-8.92,6,6,0,0,0-1.3-4.11L38,100.87a94.13,94.13,0,0,1,7.17-17.34L68.79,80.9a6,6,0,0,0,3.82-2,75.14,75.14,0,0,1,6.31-6.31,6,6,0,0,0,2-3.82l2.63-23.63A94.28,94.28,0,0,1,100.86,38l18.57,14.86a6,6,0,0,0,4.11,1.3,78.07,78.07,0,0,1,8.92,0,6,6,0,0,0,4.11-1.3L155.13,38a94.13,94.13,0,0,1,17.34,7.17l2.63,23.64a6,6,0,0,0,2,3.82,75.14,75.14,0,0,1,6.31,6.31,6,6,0,0,0,3.82,2l23.63,2.63A94.28,94.28,0,0,1,218,100.86l-14.86,18.57a6,6,0,0,0-1.3,4.11,78.07,78.07,0,0,1,0,8.92,6,6,0,0,0,1.3,4.11L218,155.13A94.13,94.13,0,0,1,210.85,172.47Z" +},null,-1)])])):"regular"===xm(r)?(Fb(),qb("g",YT,[...t[4]||(t[4]=[Jb("path",{ +d:"M128,80a48,48,0,1,0,48,48A48.05,48.05,0,0,0,128,80Zm0,80a32,32,0,1,1,32-32A32,32,0,0,1,128,160Zm88-29.84q.06-2.16,0-4.32l14.92-18.64a8,8,0,0,0,1.48-7.06,107.21,107.21,0,0,0-10.88-26.25,8,8,0,0,0-6-3.93l-23.72-2.64q-1.48-1.56-3-3L186,40.54a8,8,0,0,0-3.94-6,107.71,107.71,0,0,0-26.25-10.87,8,8,0,0,0-7.06,1.49L130.16,40Q128,40,125.84,40L107.2,25.11a8,8,0,0,0-7.06-1.48A107.6,107.6,0,0,0,73.89,34.51a8,8,0,0,0-3.93,6L67.32,64.27q-1.56,1.49-3,3L40.54,70a8,8,0,0,0-6,3.94,107.71,107.71,0,0,0-10.87,26.25,8,8,0,0,0,1.49,7.06L40,125.84Q40,128,40,130.16L25.11,148.8a8,8,0,0,0-1.48,7.06,107.21,107.21,0,0,0,10.88,26.25,8,8,0,0,0,6,3.93l23.72,2.64q1.49,1.56,3,3L70,215.46a8,8,0,0,0,3.94,6,107.71,107.71,0,0,0,26.25,10.87,8,8,0,0,0,7.06-1.49L125.84,216q2.16.06,4.32,0l18.64,14.92a8,8,0,0,0,7.06,1.48,107.21,107.21,0,0,0,26.25-10.88,8,8,0,0,0,3.93-6l2.64-23.72q1.56-1.48,3-3L215.46,186a8,8,0,0,0,6-3.94,107.71,107.71,0,0,0,10.87-26.25,8,8,0,0,0-1.49-7.06Zm-16.1-6.5a73.93,73.93,0,0,1,0,8.68,8,8,0,0,0,1.74,5.48l14.19,17.73a91.57,91.57,0,0,1-6.23,15L187,173.11a8,8,0,0,0-5.1,2.64,74.11,74.11,0,0,1-6.14,6.14,8,8,0,0,0-2.64,5.1l-2.51,22.58a91.32,91.32,0,0,1-15,6.23l-17.74-14.19a8,8,0,0,0-5-1.75h-.48a73.93,73.93,0,0,1-8.68,0,8,8,0,0,0-5.48,1.74L100.45,215.8a91.57,91.57,0,0,1-15-6.23L82.89,187a8,8,0,0,0-2.64-5.1,74.11,74.11,0,0,1-6.14-6.14,8,8,0,0,0-5.1-2.64L46.43,170.6a91.32,91.32,0,0,1-6.23-15l14.19-17.74a8,8,0,0,0,1.74-5.48,73.93,73.93,0,0,1,0-8.68,8,8,0,0,0-1.74-5.48L40.2,100.45a91.57,91.57,0,0,1,6.23-15L69,82.89a8,8,0,0,0,5.1-2.64,74.11,74.11,0,0,1,6.14-6.14A8,8,0,0,0,82.89,69L85.4,46.43a91.32,91.32,0,0,1,15-6.23l17.74,14.19a8,8,0,0,0,5.48,1.74,73.93,73.93,0,0,1,8.68,0,8,8,0,0,0,5.48-1.74L155.55,40.2a91.57,91.57,0,0,1,15,6.23L173.11,69a8,8,0,0,0,2.64,5.1,74.11,74.11,0,0,1,6.14,6.14,8,8,0,0,0,5.1,2.64l22.58,2.51a91.32,91.32,0,0,1,6.23,15l-14.19,17.74A8,8,0,0,0,199.87,123.66Z" +},null,-1)])])):"thin"===xm(r)?(Fb(),qb("g",KT,[...t[5]||(t[5]=[Jb("path",{ +d:"M128,84a44,44,0,1,0,44,44A44.05,44.05,0,0,0,128,84Zm0,80a36,36,0,1,1,36-36A36,36,0,0,1,128,164Zm83.93-32.49q.13-3.51,0-7l15.83-19.79a4,4,0,0,0,.75-3.53A103.64,103.64,0,0,0,218,75.9a4,4,0,0,0-3-2l-25.19-2.8c-1.58-1.71-3.24-3.37-4.95-4.95L182.07,41a4,4,0,0,0-2-3A104,104,0,0,0,154.82,27.5a4,4,0,0,0-3.53.74L131.51,44.07q-3.51-.14-7,0L104.7,28.24a4,4,0,0,0-3.53-.75A103.64,103.64,0,0,0,75.9,38a4,4,0,0,0-2,3l-2.8,25.19c-1.71,1.58-3.37,3.24-4.95,4.95L41,73.93a4,4,0,0,0-3,2A104,104,0,0,0,27.5,101.18a4,4,0,0,0,.74,3.53l15.83,19.78q-.14,3.51,0,7L28.24,151.3a4,4,0,0,0-.75,3.53A103.64,103.64,0,0,0,38,180.1a4,4,0,0,0,3,2l25.19,2.8c1.58,1.71,3.24,3.37,4.95,4.95l2.8,25.2a4,4,0,0,0,2,3,104,104,0,0,0,25.28,10.46,4,4,0,0,0,3.53-.74l19.78-15.83q3.51.13,7,0l19.79,15.83a4,4,0,0,0,2.5.88,4,4,0,0,0,1-.13A103.64,103.64,0,0,0,180.1,218a4,4,0,0,0,2-3l2.8-25.19c1.71-1.58,3.37-3.24,4.95-4.95l25.2-2.8a4,4,0,0,0,3-2,104,104,0,0,0,10.46-25.28,4,4,0,0,0-.74-3.53Zm.17,42.83-24.67,2.74a4,4,0,0,0-2.55,1.32,76.2,76.2,0,0,1-6.48,6.48,4,4,0,0,0-1.32,2.55l-2.74,24.66a95.45,95.45,0,0,1-19.64,8.15l-19.38-15.51a4,4,0,0,0-2.5-.87h-.24a73.67,73.67,0,0,1-9.16,0,4,4,0,0,0-2.74.87l-19.37,15.5a95.33,95.33,0,0,1-19.65-8.13l-2.74-24.67a4,4,0,0,0-1.32-2.55,76.2,76.2,0,0,1-6.48-6.48,4,4,0,0,0-2.55-1.32l-24.66-2.74a95.45,95.45,0,0,1-8.15-19.64l15.51-19.38a4,4,0,0,0,.87-2.74,77.76,77.76,0,0,1,0-9.16,4,4,0,0,0-.87-2.74l-15.5-19.37A95.33,95.33,0,0,1,43.9,81.66l24.67-2.74a4,4,0,0,0,2.55-1.32,76.2,76.2,0,0,1,6.48-6.48,4,4,0,0,0,1.32-2.55l2.74-24.66a95.45,95.45,0,0,1,19.64-8.15l19.38,15.51a4,4,0,0,0,2.74.87,73.67,73.67,0,0,1,9.16,0,4,4,0,0,0,2.74-.87l19.37-15.5a95.33,95.33,0,0,1,19.65,8.13l2.74,24.67a4,4,0,0,0,1.32,2.55,76.2,76.2,0,0,1,6.48,6.48,4,4,0,0,0,2.55,1.32l24.66,2.74a95.45,95.45,0,0,1,8.15,19.64l-15.51,19.38a4,4,0,0,0-.87,2.74,77.76,77.76,0,0,1,0,9.16,4,4,0,0,0,.87,2.74l15.5,19.37A95.33,95.33,0,0,1,212.1,174.34Z" +},null,-1)])])):oy("",!0)],16))}}),eE={key:0},tE={key:1},nE={key:2},rE={key:3 +},aE={key:4},oE={key:5},iE=Hg({name:"ScalarIconGitBranch",props:{label:{}, +weight:{}},setup(e){const t=e,{bind:n,weight:r}=E_(t) +;return(e,t)=>(Fb(),qb("svg",cy({xmlns:"http://www.w3.org/2000/svg", +viewBox:"0 0 256 256",fill:"currentColor" +},xm(n)),[Av(e.$slots,"default"),"bold"===xm(r)?(Fb(), +qb("g",eE,[...t[0]||(t[0]=[Jb("path",{ +d:"M236,64a36,36,0,1,0-48,33.94V112a4,4,0,0,1-4,4H96a27.8,27.8,0,0,0-4,.29V97.94a36,36,0,1,0-24,0v60.12a36,36,0,1,0,24,0V144a4,4,0,0,1,4-4h88a28,28,0,0,0,28-28V97.94A36.07,36.07,0,0,0,236,64ZM80,52A12,12,0,1,1,68,64,12,12,0,0,1,80,52Zm0,152a12,12,0,1,1,12-12A12,12,0,0,1,80,204ZM200,76a12,12,0,1,1,12-12A12,12,0,0,1,200,76Z" +},null,-1)])])):"duotone"===xm(r)?(Fb(),qb("g",tE,[...t[1]||(t[1]=[Jb("path",{ +d:"M224,64a24,24,0,1,1-24-24A24,24,0,0,1,224,64Z",opacity:"0.2" +},null,-1),Jb("path",{ +d:"M232,64a32,32,0,1,0-40,31v17a8,8,0,0,1-8,8H96a23.84,23.84,0,0,0-8,1.38V95a32,32,0,1,0-16,0v66a32,32,0,1,0,16,0V144a8,8,0,0,1,8-8h88a24,24,0,0,0,24-24V95A32.06,32.06,0,0,0,232,64ZM64,64A16,16,0,1,1,80,80,16,16,0,0,1,64,64ZM96,192a16,16,0,1,1-16-16A16,16,0,0,1,96,192ZM200,80a16,16,0,1,1,16-16A16,16,0,0,1,200,80Z" +},null,-1)])])):"fill"===xm(r)?(Fb(),qb("g",nE,[...t[2]||(t[2]=[Jb("path",{ +d:"M232,64a32,32,0,1,0-40,31v17a8,8,0,0,1-8,8H96a23.84,23.84,0,0,0-8,1.38V95a32,32,0,1,0-16,0v66a32,32,0,1,0,16,0V144a8,8,0,0,1,8-8h88a24,24,0,0,0,24-24V95A32.06,32.06,0,0,0,232,64ZM64,64A16,16,0,1,1,80,80,16,16,0,0,1,64,64ZM96,192a16,16,0,1,1-16-16A16,16,0,0,1,96,192Z" +},null,-1)])])):"light"===xm(r)?(Fb(),qb("g",rE,[...t[3]||(t[3]=[Jb("path",{ +d:"M230,64a30,30,0,1,0-36,29.4V112a10,10,0,0,1-10,10H96a21.84,21.84,0,0,0-10,2.42v-31a30,30,0,1,0-12,0v69.2a30,30,0,1,0,12,0V144a10,10,0,0,1,10-10h88a22,22,0,0,0,22-22V93.4A30.05,30.05,0,0,0,230,64ZM62,64A18,18,0,1,1,80,82,18,18,0,0,1,62,64ZM98,192a18,18,0,1,1-18-18A18,18,0,0,1,98,192ZM200,82a18,18,0,1,1,18-18A18,18,0,0,1,200,82Z" +},null,-1)])])):"regular"===xm(r)?(Fb(),qb("g",aE,[...t[4]||(t[4]=[Jb("path",{ +d:"M232,64a32,32,0,1,0-40,31v17a8,8,0,0,1-8,8H96a23.84,23.84,0,0,0-8,1.38V95a32,32,0,1,0-16,0v66a32,32,0,1,0,16,0V144a8,8,0,0,1,8-8h88a24,24,0,0,0,24-24V95A32.06,32.06,0,0,0,232,64ZM64,64A16,16,0,1,1,80,80,16,16,0,0,1,64,64ZM96,192a16,16,0,1,1-16-16A16,16,0,0,1,96,192ZM200,80a16,16,0,1,1,16-16A16,16,0,0,1,200,80Z" +},null,-1)])])):"thin"===xm(r)?(Fb(),qb("g",oE,[...t[5]||(t[5]=[Jb("path",{ +d:"M228,64a28,28,0,1,0-32,27.71V112a12,12,0,0,1-12,12H96a19.91,19.91,0,0,0-12,4V91.71a28,28,0,1,0-8,0v72.58a28,28,0,1,0,8,0V144a12,12,0,0,1,12-12h88a20,20,0,0,0,20-20V91.71A28,28,0,0,0,228,64ZM60,64A20,20,0,1,1,80,84,20,20,0,0,1,60,64Zm40,128a20,20,0,1,1-20-20A20,20,0,0,1,100,192ZM200,84a20,20,0,1,1,20-20A20,20,0,0,1,200,84Z" +},null,-1)])])):oy("",!0)],16))}}),sE={key:0},lE={key:1},cE={key:2},uE={key:3 +},dE={key:4},pE={key:5},hE=Hg({name:"ScalarIconGithubLogo",props:{label:{}, +weight:{}},setup(e){const t=e,{bind:n,weight:r}=E_(t) +;return(e,t)=>(Fb(),qb("svg",cy({xmlns:"http://www.w3.org/2000/svg", +viewBox:"0 0 256 256",fill:"currentColor" +},xm(n)),[Av(e.$slots,"default"),"bold"===xm(r)?(Fb(), +qb("g",sE,[...t[0]||(t[0]=[Jb("path",{ +d:"M212.62,75.17A63.7,63.7,0,0,0,206.39,26,12,12,0,0,0,196,20a63.71,63.71,0,0,0-50,24H126A63.71,63.71,0,0,0,76,20a12,12,0,0,0-10.39,6,63.7,63.7,0,0,0-6.23,49.17A61.5,61.5,0,0,0,52,104v8a60.1,60.1,0,0,0,45.76,58.28A43.66,43.66,0,0,0,92,192v4H76a20,20,0,0,1-20-20,44.05,44.05,0,0,0-44-44,12,12,0,0,0,0,24,20,20,0,0,1,20,20,44.05,44.05,0,0,0,44,44H92v12a12,12,0,0,0,24,0V192a20,20,0,0,1,40,0v40a12,12,0,0,0,24,0V192a43.66,43.66,0,0,0-5.76-21.72A60.1,60.1,0,0,0,220,112v-8A61.5,61.5,0,0,0,212.62,75.17ZM196,112a36,36,0,0,1-36,36H112a36,36,0,0,1-36-36v-8a37.87,37.87,0,0,1,6.13-20.12,11.65,11.65,0,0,0,1.58-11.49,39.9,39.9,0,0,1-.4-27.72,39.87,39.87,0,0,1,26.41,17.8A12,12,0,0,0,119.82,68h32.35a12,12,0,0,0,10.11-5.53,39.84,39.84,0,0,1,26.41-17.8,39.9,39.9,0,0,1-.4,27.72,12,12,0,0,0,1.61,11.53A37.85,37.85,0,0,1,196,104Z" +},null,-1)])])):"duotone"===xm(r)?(Fb(),qb("g",lE,[...t[1]||(t[1]=[Jb("path",{ +d:"M208,104v8a48,48,0,0,1-48,48H136a32,32,0,0,1,32,32v40H104V192a32,32,0,0,1,32-32H112a48,48,0,0,1-48-48v-8a49.28,49.28,0,0,1,8.51-27.3A51.92,51.92,0,0,1,76,32a52,52,0,0,1,43.83,24h32.34A52,52,0,0,1,196,32a51.92,51.92,0,0,1,3.49,44.7A49.28,49.28,0,0,1,208,104Z", +opacity:"0.2"},null,-1),Jb("path",{ +d:"M208.3,75.68A59.74,59.74,0,0,0,202.93,28,8,8,0,0,0,196,24a59.75,59.75,0,0,0-48,24H124A59.75,59.75,0,0,0,76,24a8,8,0,0,0-6.93,4,59.78,59.78,0,0,0-5.38,47.68A58.14,58.14,0,0,0,56,104v8a56.06,56.06,0,0,0,48.44,55.47A39.8,39.8,0,0,0,96,192v8H72a24,24,0,0,1-24-24A40,40,0,0,0,8,136a8,8,0,0,0,0,16,24,24,0,0,1,24,24,40,40,0,0,0,40,40H96v16a8,8,0,0,0,16,0V192a24,24,0,0,1,48,0v40a8,8,0,0,0,16,0V192a39.8,39.8,0,0,0-8.44-24.53A56.06,56.06,0,0,0,216,112v-8A58,58,0,0,0,208.3,75.68ZM200,112a40,40,0,0,1-40,40H112a40,40,0,0,1-40-40v-8a41.74,41.74,0,0,1,6.9-22.48A8,8,0,0,0,80,73.83a43.81,43.81,0,0,1,.79-33.58,43.88,43.88,0,0,1,32.32,20.06A8,8,0,0,0,119.82,64h32.35a8,8,0,0,0,6.74-3.69,43.87,43.87,0,0,1,32.32-20.06A43.81,43.81,0,0,1,192,73.83a8.09,8.09,0,0,0,1,7.65A41.76,41.76,0,0,1,200,104Z" +},null,-1)])])):"fill"===xm(r)?(Fb(),qb("g",cE,[...t[2]||(t[2]=[Jb("path",{ +d:"M216,104v8a56.06,56.06,0,0,1-48.44,55.47A39.8,39.8,0,0,1,176,192v40a8,8,0,0,1-8,8H104a8,8,0,0,1-8-8V216H72a40,40,0,0,1-40-40A24,24,0,0,0,8,152a8,8,0,0,1,0-16,40,40,0,0,1,40,40,24,24,0,0,0,24,24H96v-8a39.8,39.8,0,0,1,8.44-24.53A56.06,56.06,0,0,1,56,112v-8a58.14,58.14,0,0,1,7.69-28.32A59.78,59.78,0,0,1,69.07,28,8,8,0,0,1,76,24a59.75,59.75,0,0,1,48,24h24a59.75,59.75,0,0,1,48-24,8,8,0,0,1,6.93,4,59.74,59.74,0,0,1,5.37,47.68A58,58,0,0,1,216,104Z" +},null,-1)])])):"light"===xm(r)?(Fb(),qb("g",uE,[...t[3]||(t[3]=[Jb("path",{ +d:"M206.13,75.92A57.79,57.79,0,0,0,201.2,29a6,6,0,0,0-5.2-3,57.77,57.77,0,0,0-47,24H123A57.77,57.77,0,0,0,76,26a6,6,0,0,0-5.2,3,57.79,57.79,0,0,0-4.93,46.92A55.88,55.88,0,0,0,58,104v8a54.06,54.06,0,0,0,50.45,53.87A37.85,37.85,0,0,0,98,192v10H72a26,26,0,0,1-26-26A38,38,0,0,0,8,138a6,6,0,0,0,0,12,26,26,0,0,1,26,26,38,38,0,0,0,38,38H98v18a6,6,0,0,0,12,0V192a26,26,0,0,1,52,0v40a6,6,0,0,0,12,0V192a37.85,37.85,0,0,0-10.45-26.13A54.06,54.06,0,0,0,214,112v-8A55.88,55.88,0,0,0,206.13,75.92ZM202,112a42,42,0,0,1-42,42H112a42,42,0,0,1-42-42v-8a43.86,43.86,0,0,1,7.3-23.69,6,6,0,0,0,.81-5.76,45.85,45.85,0,0,1,1.43-36.42,45.85,45.85,0,0,1,35.23,21.1A6,6,0,0,0,119.83,62h32.34a6,6,0,0,0,5.06-2.76,45.83,45.83,0,0,1,35.23-21.11,45.85,45.85,0,0,1,1.43,36.42,6,6,0,0,0,.79,5.74A43.78,43.78,0,0,1,202,104Z" +},null,-1)])])):"regular"===xm(r)?(Fb(),qb("g",dE,[...t[4]||(t[4]=[Jb("path",{ +d:"M208.31,75.68A59.78,59.78,0,0,0,202.93,28,8,8,0,0,0,196,24a59.75,59.75,0,0,0-48,24H124A59.75,59.75,0,0,0,76,24a8,8,0,0,0-6.93,4,59.78,59.78,0,0,0-5.38,47.68A58.14,58.14,0,0,0,56,104v8a56.06,56.06,0,0,0,48.44,55.47A39.8,39.8,0,0,0,96,192v8H72a24,24,0,0,1-24-24A40,40,0,0,0,8,136a8,8,0,0,0,0,16,24,24,0,0,1,24,24,40,40,0,0,0,40,40H96v16a8,8,0,0,0,16,0V192a24,24,0,0,1,48,0v40a8,8,0,0,0,16,0V192a39.8,39.8,0,0,0-8.44-24.53A56.06,56.06,0,0,0,216,112v-8A58.14,58.14,0,0,0,208.31,75.68ZM200,112a40,40,0,0,1-40,40H112a40,40,0,0,1-40-40v-8a41.74,41.74,0,0,1,6.9-22.48A8,8,0,0,0,80,73.83a43.81,43.81,0,0,1,.79-33.58,43.88,43.88,0,0,1,32.32,20.06A8,8,0,0,0,119.82,64h32.35a8,8,0,0,0,6.74-3.69,43.87,43.87,0,0,1,32.32-20.06A43.81,43.81,0,0,1,192,73.83a8.09,8.09,0,0,0,1,7.65A41.72,41.72,0,0,1,200,104Z" +},null,-1)])])):"thin"===xm(r)?(Fb(),qb("g",pE,[...t[5]||(t[5]=[Jb("path",{ +d:"M203.94,76.16A55.73,55.73,0,0,0,199.46,30,4,4,0,0,0,196,28a55.78,55.78,0,0,0-46,24H122A55.78,55.78,0,0,0,76,28a4,4,0,0,0-3.46,2,55.73,55.73,0,0,0-4.48,46.16A53.78,53.78,0,0,0,60,104v8a52.06,52.06,0,0,0,52,52h1.41A36,36,0,0,0,100,192v12H72a28,28,0,0,1-28-28A36,36,0,0,0,8,140a4,4,0,0,0,0,8,28,28,0,0,1,28,28,36,36,0,0,0,36,36h28v20a4,4,0,0,0,8,0V192a28,28,0,0,1,56,0v40a4,4,0,0,0,8,0V192a36,36,0,0,0-13.41-28H160a52.06,52.06,0,0,0,52-52v-8A53.78,53.78,0,0,0,203.94,76.16ZM204,112a44.05,44.05,0,0,1-44,44H112a44.05,44.05,0,0,1-44-44v-8a45.76,45.76,0,0,1,7.71-24.89,4,4,0,0,0,.53-3.84,47.82,47.82,0,0,1,2.1-39.21,47.8,47.8,0,0,1,38.12,22.1A4,4,0,0,0,119.83,60h32.34a4,4,0,0,0,3.37-1.84,47.8,47.8,0,0,1,38.12-22.1,47.82,47.82,0,0,1,2.1,39.21,4,4,0,0,0,.53,3.83A45.85,45.85,0,0,1,204,104Z" +},null,-1)])])):oy("",!0)],16))}}),fE={key:0},mE={key:1},gE={key:2},vE={key:3 +},bE={key:4},yE={key:5},OE=Hg({name:"ScalarIconGlobe",props:{label:{},weight:{} +},setup(e){const t=e,{bind:n,weight:r}=E_(t);return(e,t)=>(Fb(),qb("svg",cy({ +xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 256 256",fill:"currentColor" +},xm(n)),[Av(e.$slots,"default"),"bold"===xm(r)?(Fb(), +qb("g",fE,[...t[0]||(t[0]=[Jb("path",{ +d:"M128,20A108,108,0,1,0,236,128,108.12,108.12,0,0,0,128,20Zm0,187a113.4,113.4,0,0,1-20.39-35h40.82a116.94,116.94,0,0,1-10,20.77A108.61,108.61,0,0,1,128,207Zm-26.49-59a135.42,135.42,0,0,1,0-40h53a135.42,135.42,0,0,1,0,40ZM44,128a83.49,83.49,0,0,1,2.43-20H77.25a160.63,160.63,0,0,0,0,40H46.43A83.49,83.49,0,0,1,44,128Zm84-79a113.4,113.4,0,0,1,20.39,35H107.59a116.94,116.94,0,0,1,10-20.77A108.61,108.61,0,0,1,128,49Zm50.73,59h30.82a83.52,83.52,0,0,1,0,40H178.75a160.63,160.63,0,0,0,0-40Zm20.77-24H173.71a140.82,140.82,0,0,0-15.5-34.36A84.51,84.51,0,0,1,199.52,84ZM97.79,49.64A140.82,140.82,0,0,0,82.29,84H56.48A84.51,84.51,0,0,1,97.79,49.64ZM56.48,172H82.29a140.82,140.82,0,0,0,15.5,34.36A84.51,84.51,0,0,1,56.48,172Zm101.73,34.36A140.82,140.82,0,0,0,173.71,172h25.81A84.51,84.51,0,0,1,158.21,206.36Z" +},null,-1)])])):"duotone"===xm(r)?(Fb(),qb("g",mE,[...t[1]||(t[1]=[Jb("path",{ +d:"M224,128a96,96,0,1,1-96-96A96,96,0,0,1,224,128Z",opacity:"0.2" +},null,-1),Jb("path",{ +d:"M128,24h0A104,104,0,1,0,232,128,104.12,104.12,0,0,0,128,24Zm88,104a87.61,87.61,0,0,1-3.33,24H174.16a157.44,157.44,0,0,0,0-48h38.51A87.61,87.61,0,0,1,216,128ZM102,168H154a115.11,115.11,0,0,1-26,45A115.27,115.27,0,0,1,102,168Zm-3.9-16a140.84,140.84,0,0,1,0-48h59.88a140.84,140.84,0,0,1,0,48ZM40,128a87.61,87.61,0,0,1,3.33-24H81.84a157.44,157.44,0,0,0,0,48H43.33A87.61,87.61,0,0,1,40,128ZM154,88H102a115.11,115.11,0,0,1,26-45A115.27,115.27,0,0,1,154,88Zm52.33,0H170.71a135.28,135.28,0,0,0-22.3-45.6A88.29,88.29,0,0,1,206.37,88ZM107.59,42.4A135.28,135.28,0,0,0,85.29,88H49.63A88.29,88.29,0,0,1,107.59,42.4ZM49.63,168H85.29a135.28,135.28,0,0,0,22.3,45.6A88.29,88.29,0,0,1,49.63,168Zm98.78,45.6a135.28,135.28,0,0,0,22.3-45.6h35.66A88.29,88.29,0,0,1,148.41,213.6Z" +},null,-1)])])):"fill"===xm(r)?(Fb(),qb("g",gE,[...t[2]||(t[2]=[Jb("path",{ +d:"M128,24h0A104,104,0,1,0,232,128,104.12,104.12,0,0,0,128,24Zm78.36,64H170.71a135.28,135.28,0,0,0-22.3-45.6A88.29,88.29,0,0,1,206.37,88ZM216,128a87.61,87.61,0,0,1-3.33,24H174.16a157.44,157.44,0,0,0,0-48h38.51A87.61,87.61,0,0,1,216,128ZM128,43a115.27,115.27,0,0,1,26,45H102A115.11,115.11,0,0,1,128,43ZM102,168H154a115.11,115.11,0,0,1-26,45A115.27,115.27,0,0,1,102,168Zm-3.9-16a140.84,140.84,0,0,1,0-48h59.88a140.84,140.84,0,0,1,0,48Zm50.35,61.6a135.28,135.28,0,0,0,22.3-45.6h35.66A88.29,88.29,0,0,1,148.41,213.6Z" +},null,-1)])])):"light"===xm(r)?(Fb(),qb("g",vE,[...t[3]||(t[3]=[Jb("path",{ +d:"M128,26A102,102,0,1,0,230,128,102.12,102.12,0,0,0,128,26Zm81.57,64H169.19a132.58,132.58,0,0,0-25.73-50.67A90.29,90.29,0,0,1,209.57,90ZM218,128a89.7,89.7,0,0,1-3.83,26H171.81a155.43,155.43,0,0,0,0-52h42.36A89.7,89.7,0,0,1,218,128Zm-90,87.83a110,110,0,0,1-15.19-19.45A124.24,124.24,0,0,1,99.35,166h57.3a124.24,124.24,0,0,1-13.46,30.38A110,110,0,0,1,128,215.83ZM96.45,154a139.18,139.18,0,0,1,0-52h63.1a139.18,139.18,0,0,1,0,52ZM38,128a89.7,89.7,0,0,1,3.83-26H84.19a155.43,155.43,0,0,0,0,52H41.83A89.7,89.7,0,0,1,38,128Zm90-87.83a110,110,0,0,1,15.19,19.45A124.24,124.24,0,0,1,156.65,90H99.35a124.24,124.24,0,0,1,13.46-30.38A110,110,0,0,1,128,40.17Zm-15.46-.84A132.58,132.58,0,0,0,86.81,90H46.43A90.29,90.29,0,0,1,112.54,39.33ZM46.43,166H86.81a132.58,132.58,0,0,0,25.73,50.67A90.29,90.29,0,0,1,46.43,166Zm97,50.67A132.58,132.58,0,0,0,169.19,166h40.38A90.29,90.29,0,0,1,143.46,216.67Z" +},null,-1)])])):"regular"===xm(r)?(Fb(),qb("g",bE,[...t[4]||(t[4]=[Jb("path",{ +d:"M128,24h0A104,104,0,1,0,232,128,104.12,104.12,0,0,0,128,24Zm88,104a87.61,87.61,0,0,1-3.33,24H174.16a157.44,157.44,0,0,0,0-48h38.51A87.61,87.61,0,0,1,216,128ZM102,168H154a115.11,115.11,0,0,1-26,45A115.27,115.27,0,0,1,102,168Zm-3.9-16a140.84,140.84,0,0,1,0-48h59.88a140.84,140.84,0,0,1,0,48ZM40,128a87.61,87.61,0,0,1,3.33-24H81.84a157.44,157.44,0,0,0,0,48H43.33A87.61,87.61,0,0,1,40,128ZM154,88H102a115.11,115.11,0,0,1,26-45A115.27,115.27,0,0,1,154,88Zm52.33,0H170.71a135.28,135.28,0,0,0-22.3-45.6A88.29,88.29,0,0,1,206.37,88ZM107.59,42.4A135.28,135.28,0,0,0,85.29,88H49.63A88.29,88.29,0,0,1,107.59,42.4ZM49.63,168H85.29a135.28,135.28,0,0,0,22.3,45.6A88.29,88.29,0,0,1,49.63,168Zm98.78,45.6a135.28,135.28,0,0,0,22.3-45.6h35.66A88.29,88.29,0,0,1,148.41,213.6Z" +},null,-1)])])):"thin"===xm(r)?(Fb(),qb("g",yE,[...t[5]||(t[5]=[Jb("path",{ +d:"M128,28h0A100,100,0,1,0,228,128,100.11,100.11,0,0,0,128,28Zm0,190.61c-6.33-6.09-23-24.41-31.27-54.61h62.54C151,194.2,134.33,212.52,128,218.61ZM94.82,156a140.42,140.42,0,0,1,0-56h66.36a140.42,140.42,0,0,1,0,56ZM128,37.39c6.33,6.09,23,24.41,31.27,54.61H96.73C105,61.8,121.67,43.48,128,37.39ZM169.41,100h46.23a92.09,92.09,0,0,1,0,56H169.41a152.65,152.65,0,0,0,0-56Zm43.25-8h-45a129.39,129.39,0,0,0-29.19-55.4A92.25,92.25,0,0,1,212.66,92ZM117.54,36.6A129.39,129.39,0,0,0,88.35,92h-45A92.25,92.25,0,0,1,117.54,36.6ZM40.36,100H86.59a152.65,152.65,0,0,0,0,56H40.36a92.09,92.09,0,0,1,0-56Zm3,64h45a129.39,129.39,0,0,0,29.19,55.4A92.25,92.25,0,0,1,43.34,164Zm95.12,55.4A129.39,129.39,0,0,0,167.65,164h45A92.25,92.25,0,0,1,138.46,219.4Z" +},null,-1)])])):oy("",!0)],16))}}),wE={key:0},xE={key:1},kE={key:2},SE={key:3 +},_E={key:4},AE={key:5},TE=Hg({name:"ScalarIconGlobeSimple",props:{label:{}, +weight:{}},setup(e){const t=e,{bind:n,weight:r}=E_(t) +;return(e,t)=>(Fb(),qb("svg",cy({xmlns:"http://www.w3.org/2000/svg", +viewBox:"0 0 256 256",fill:"currentColor" +},xm(n)),[Av(e.$slots,"default"),"bold"===xm(r)?(Fb(), +qb("g",wE,[...t[0]||(t[0]=[Jb("path",{ +d:"M128,20A108,108,0,1,0,236,128,108.12,108.12,0,0,0,128,20Zm83.13,96H179.56a144.3,144.3,0,0,0-21.35-66.36A84.22,84.22,0,0,1,211.13,116ZM128,207c-9.36-10.81-24.46-33.13-27.45-67h54.94a119.74,119.74,0,0,1-17.11,52.77A108.61,108.61,0,0,1,128,207Zm-27.45-91a119.74,119.74,0,0,1,17.11-52.77A108.61,108.61,0,0,1,128,49c9.36,10.81,24.46,33.13,27.45,67ZM97.79,49.64A144.3,144.3,0,0,0,76.44,116H44.87A84.22,84.22,0,0,1,97.79,49.64ZM44.87,140H76.44a144.3,144.3,0,0,0,21.35,66.36A84.22,84.22,0,0,1,44.87,140Zm113.34,66.36A144.3,144.3,0,0,0,179.56,140h31.57A84.22,84.22,0,0,1,158.21,206.36Z" +},null,-1)])])):"duotone"===xm(r)?(Fb(),qb("g",xE,[...t[1]||(t[1]=[Jb("path",{ +d:"M224,128a96,96,0,1,1-96-96A96,96,0,0,1,224,128Z",opacity:"0.2" +},null,-1),Jb("path",{ +d:"M128,24h0A104,104,0,1,0,232,128,104.12,104.12,0,0,0,128,24Zm87.62,96H175.79C174,83.49,159.94,57.67,148.41,42.4A88.19,88.19,0,0,1,215.63,120ZM96.23,136h63.54c-2.31,41.61-22.23,67.11-31.77,77C118.45,203.1,98.54,177.6,96.23,136Zm0-16C98.54,78.39,118.46,52.89,128,43c9.55,9.93,29.46,35.43,31.77,77Zm11.36-77.6C96.06,57.67,82,83.49,80.21,120H40.37A88.19,88.19,0,0,1,107.59,42.4ZM40.37,136H80.21c1.82,36.51,15.85,62.33,27.38,77.6A88.19,88.19,0,0,1,40.37,136Zm108,77.6c11.53-15.27,25.56-41.09,27.38-77.6h39.84A88.19,88.19,0,0,1,148.41,213.6Z" +},null,-1)])])):"fill"===xm(r)?(Fb(),qb("g",kE,[...t[2]||(t[2]=[Jb("path",{ +d:"M128,24h0A104,104,0,1,0,232,128,104.12,104.12,0,0,0,128,24Zm87.62,96H175.79C174,83.49,159.94,57.67,148.41,42.4A88.19,88.19,0,0,1,215.63,120ZM96.23,136h63.54c-2.31,41.61-22.23,67.11-31.77,77C118.45,203.1,98.54,177.6,96.23,136Zm0-16C98.54,78.39,118.46,52.89,128,43c9.55,9.93,29.46,35.43,31.77,77Zm52.18,93.6c11.53-15.27,25.56-41.09,27.38-77.6h39.84A88.19,88.19,0,0,1,148.41,213.6Z" +},null,-1)])])):"light"===xm(r)?(Fb(),qb("g",SE,[...t[3]||(t[3]=[Jb("path",{ +d:"M128,26A102,102,0,1,0,230,128,102.12,102.12,0,0,0,128,26Zm89.8,96H173.89c-1.54-40.77-18.48-68.23-30.43-82.67A90.19,90.19,0,0,1,217.8,122ZM128,215.83a110,110,0,0,1-15.19-19.45A128.37,128.37,0,0,1,94.13,134h67.74a128.37,128.37,0,0,1-18.68,62.38A110,110,0,0,1,128,215.83ZM94.13,122a128.37,128.37,0,0,1,18.68-62.38A110,110,0,0,1,128,40.17a110,110,0,0,1,15.19,19.45A128.37,128.37,0,0,1,161.87,122Zm18.41-82.67c-12,14.44-28.89,41.9-30.43,82.67H38.2A90.19,90.19,0,0,1,112.54,39.33ZM38.2,134H82.11c1.54,40.77,18.48,68.23,30.43,82.67A90.19,90.19,0,0,1,38.2,134Zm105.26,82.67c11.95-14.44,28.89-41.9,30.43-82.67H217.8A90.19,90.19,0,0,1,143.46,216.67Z" +},null,-1)])])):"regular"===xm(r)?(Fb(),qb("g",_E,[...t[4]||(t[4]=[Jb("path",{ +d:"M128,24h0A104,104,0,1,0,232,128,104.12,104.12,0,0,0,128,24Zm87.62,96H175.79C174,83.49,159.94,57.67,148.41,42.4A88.19,88.19,0,0,1,215.63,120ZM96.23,136h63.54c-2.31,41.61-22.23,67.11-31.77,77C118.45,203.1,98.54,177.6,96.23,136Zm0-16C98.54,78.39,118.46,52.89,128,43c9.55,9.93,29.46,35.43,31.77,77Zm11.36-77.6C96.06,57.67,82,83.49,80.21,120H40.37A88.19,88.19,0,0,1,107.59,42.4ZM40.37,136H80.21c1.82,36.51,15.85,62.33,27.38,77.6A88.19,88.19,0,0,1,40.37,136Zm108,77.6c11.53-15.27,25.56-41.09,27.38-77.6h39.84A88.19,88.19,0,0,1,148.41,213.6Z" +},null,-1)])])):"thin"===xm(r)?(Fb(),qb("g",AE,[...t[5]||(t[5]=[Jb("path",{ +d:"M128,28h0A100,100,0,1,0,228,128,100.11,100.11,0,0,0,128,28Zm91.9,96h-48c-1.15-45.55-21.74-74.52-33.48-87.4A92.14,92.14,0,0,1,219.91,124ZM128,218.61c-8.32-8-34.57-37.13-35.93-86.61h71.86C162.57,181.48,136.32,210.61,128,218.61ZM92.07,124C93.43,74.52,119.68,45.39,128,37.39c8.32,8,34.57,37.13,35.93,86.61Zm25.47-87.4C105.8,49.48,85.21,78.45,84.06,124h-48A92.14,92.14,0,0,1,117.54,36.6ZM36.09,132h48c1.15,45.55,21.74,74.52,33.48,87.4A92.14,92.14,0,0,1,36.09,132Zm102.37,87.4c11.74-12.88,32.33-41.85,33.48-87.4h48A92.14,92.14,0,0,1,138.46,219.4Z" +},null,-1)])])):oy("",!0)],16))}}),EE={key:0},CE={key:1},$E={key:2},PE={key:3 +},IE={key:4},DE={key:5},ME=Hg({name:"ScalarIconHash",props:{label:{},weight:{}}, +setup(e){const t=e,{bind:n,weight:r}=E_(t);return(e,t)=>(Fb(),qb("svg",cy({ +xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 256 256",fill:"currentColor" +},xm(n)),[Av(e.$slots,"default"),"bold"===xm(r)?(Fb(), +qb("g",EE,[...t[0]||(t[0]=[Jb("path",{ +d:"M224,84H180.2l7.61-41.85a12,12,0,0,0-23.62-4.3L155.8,84H116.2l7.61-41.85a12,12,0,1,0-23.62-4.3L91.8,84H48a12,12,0,0,0,0,24H87.44l-7.27,40H32a12,12,0,0,0,0,24H75.8l-7.61,41.85a12,12,0,0,0,9.66,14A11.43,11.43,0,0,0,80,228a12,12,0,0,0,11.8-9.86L100.2,172h39.6l-7.61,41.85a12,12,0,0,0,9.66,14,11.43,11.43,0,0,0,2.16.2,12,12,0,0,0,11.8-9.86L164.2,172H208a12,12,0,0,0,0-24H168.56l7.27-40H224a12,12,0,0,0,0-24Zm-79.83,64H104.56l7.27-40h39.61Z" +},null,-1)])])):"duotone"===xm(r)?(Fb(),qb("g",CE,[...t[1]||(t[1]=[Jb("path",{ +d:"M165.82,96l-11.64,64h-64l11.64-64Z",opacity:"0.2"},null,-1),Jb("path",{ +d:"M224,88H175.4l8.47-46.57a8,8,0,0,0-15.74-2.86l-9,49.43H111.4l8.47-46.57a8,8,0,0,0-15.74-2.86L95.14,88H48a8,8,0,0,0,0,16H92.23L83.5,152H32a8,8,0,0,0,0,16H80.6l-8.47,46.57a8,8,0,0,0,6.44,9.3A7.79,7.79,0,0,0,80,224a8,8,0,0,0,7.86-6.57l9-49.43H144.6l-8.47,46.57a8,8,0,0,0,6.44,9.3A7.79,7.79,0,0,0,144,224a8,8,0,0,0,7.86-6.57l9-49.43H208a8,8,0,0,0,0-16H163.77l8.73-48H224a8,8,0,0,0,0-16Zm-76.5,64H99.77l8.73-48h47.73Z" +},null,-1)])])):"fill"===xm(r)?(Fb(),qb("g",$E,[...t[2]||(t[2]=[Jb("path",{ +d:"M116.25,112h31.5l-8,32h-31.5ZM224,48V208a16,16,0,0,1-16,16H48a16,16,0,0,1-16-16V48A16,16,0,0,1,48,32H208A16,16,0,0,1,224,48Zm-16,56a8,8,0,0,0-8-8H168.25l7.51-30.06a8,8,0,0,0-15.52-3.88L151.75,96h-31.5l7.51-30.06a8,8,0,0,0-15.52-3.88L103.75,96H64a8,8,0,0,0,0,16H99.75l-8,32H56a8,8,0,0,0,0,16H87.75l-7.51,30.06a8,8,0,0,0,5.82,9.7,8.13,8.13,0,0,0,2,.24,8,8,0,0,0,7.75-6.06L104.25,160h31.5l-7.51,30.06a8,8,0,0,0,5.82,9.7A8.13,8.13,0,0,0,136,200a8,8,0,0,0,7.75-6.06L152.25,160H192a8,8,0,0,0,0-16H156.25l8-32H200A8,8,0,0,0,208,104Z" +},null,-1)])])):"light"===xm(r)?(Fb(),qb("g",PE,[...t[3]||(t[3]=[Jb("path",{ +d:"M224,90H173l8.89-48.93a6,6,0,1,0-11.8-2.14L160.81,90H109l8.89-48.93a6,6,0,0,0-11.8-2.14L96.81,90H48a6,6,0,0,0,0,12H94.63l-9.46,52H32a6,6,0,0,0,0,12H83L74.1,214.93a6,6,0,0,0,4.83,7A5.64,5.64,0,0,0,80,222a6,6,0,0,0,5.89-4.93L95.19,166H147l-8.89,48.93a6,6,0,0,0,4.83,7,5.64,5.64,0,0,0,1.08.1,6,6,0,0,0,5.89-4.93L159.19,166H208a6,6,0,0,0,0-12H161.37l9.46-52H224a6,6,0,0,0,0-12Zm-74.83,64H97.37l9.46-52h51.8Z" +},null,-1)])])):"regular"===xm(r)?(Fb(),qb("g",IE,[...t[4]||(t[4]=[Jb("path",{ +d:"M224,88H175.4l8.47-46.57a8,8,0,0,0-15.74-2.86l-9,49.43H111.4l8.47-46.57a8,8,0,0,0-15.74-2.86L95.14,88H48a8,8,0,0,0,0,16H92.23L83.5,152H32a8,8,0,0,0,0,16H80.6l-8.47,46.57a8,8,0,0,0,6.44,9.3A7.79,7.79,0,0,0,80,224a8,8,0,0,0,7.86-6.57l9-49.43H144.6l-8.47,46.57a8,8,0,0,0,6.44,9.3A7.79,7.79,0,0,0,144,224a8,8,0,0,0,7.86-6.57l9-49.43H208a8,8,0,0,0,0-16H163.77l8.73-48H224a8,8,0,0,0,0-16Zm-76.5,64H99.77l8.73-48h47.73Z" +},null,-1)])])):"thin"===xm(r)?(Fb(),qb("g",DE,[...t[5]||(t[5]=[Jb("path",{ +d:"M224,92H170.61l9.33-51.28a4,4,0,1,0-7.88-1.44L162.48,92H106.61l9.33-51.28a4,4,0,1,0-7.88-1.44L98.48,92H48a4,4,0,0,0,0,8H97L86.84,156H32a4,4,0,0,0,0,8H85.39l-9.33,51.28a4,4,0,0,0,3.22,4.65A3.65,3.65,0,0,0,80,220a4,4,0,0,0,3.94-3.29L93.52,164h55.87l-9.33,51.28a4,4,0,0,0,3.22,4.65,3.65,3.65,0,0,0,.72.07,4,4,0,0,0,3.94-3.29L157.52,164H208a4,4,0,0,0,0-8H159l10.19-56H224a4,4,0,0,0,0-8Zm-73.16,64H95l10.19-56H161Z" +},null,-1)])])):oy("",!0)],16))}}),NE={key:0},RE={key:1},LE={key:2},BE={key:3 +},jE={key:4},UE={key:5},zE=Hg({name:"ScalarIconHouse",props:{label:{},weight:{} +},setup(e){const t=e,{bind:n,weight:r}=E_(t);return(e,t)=>(Fb(),qb("svg",cy({ +xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 256 256",fill:"currentColor" +},xm(n)),[Av(e.$slots,"default"),"bold"===xm(r)?(Fb(), +qb("g",NE,[...t[0]||(t[0]=[Jb("path",{ +d:"M222.14,105.85l-80-80a20,20,0,0,0-28.28,0l-80,80A19.86,19.86,0,0,0,28,120v96a12,12,0,0,0,12,12h64a12,12,0,0,0,12-12V164h24v52a12,12,0,0,0,12,12h64a12,12,0,0,0,12-12V120A19.86,19.86,0,0,0,222.14,105.85ZM204,204H164V152a12,12,0,0,0-12-12H104a12,12,0,0,0-12,12v52H52V121.65l76-76,76,76Z" +},null,-1)])])):"duotone"===xm(r)?(Fb(),qb("g",RE,[...t[1]||(t[1]=[Jb("path",{ +d:"M216,120v96H152V152H104v64H40V120a8,8,0,0,1,2.34-5.66l80-80a8,8,0,0,1,11.32,0l80,80A8,8,0,0,1,216,120Z", +opacity:"0.2"},null,-1),Jb("path",{ +d:"M219.31,108.68l-80-80a16,16,0,0,0-22.62,0l-80,80A15.87,15.87,0,0,0,32,120v96a8,8,0,0,0,8,8h64a8,8,0,0,0,8-8V160h32v56a8,8,0,0,0,8,8h64a8,8,0,0,0,8-8V120A15.87,15.87,0,0,0,219.31,108.68ZM208,208H160V152a8,8,0,0,0-8-8H104a8,8,0,0,0-8,8v56H48V120l80-80,80,80Z" +},null,-1)])])):"fill"===xm(r)?(Fb(),qb("g",LE,[...t[2]||(t[2]=[Jb("path",{ +d:"M224,120v96a8,8,0,0,1-8,8H160a8,8,0,0,1-8-8V164a4,4,0,0,0-4-4H108a4,4,0,0,0-4,4v52a8,8,0,0,1-8,8H40a8,8,0,0,1-8-8V120a16,16,0,0,1,4.69-11.31l80-80a16,16,0,0,1,22.62,0l80,80A16,16,0,0,1,224,120Z" +},null,-1)])])):"light"===xm(r)?(Fb(),qb("g",BE,[...t[3]||(t[3]=[Jb("path",{ +d:"M217.9,110.1l-80-80a14,14,0,0,0-19.8,0l-80,80A13.92,13.92,0,0,0,34,120v96a6,6,0,0,0,6,6h64a6,6,0,0,0,6-6V158h36v58a6,6,0,0,0,6,6h64a6,6,0,0,0,6-6V120A13.92,13.92,0,0,0,217.9,110.1ZM210,210H158V152a6,6,0,0,0-6-6H104a6,6,0,0,0-6,6v58H46V120a2,2,0,0,1,.58-1.42l80-80a2,2,0,0,1,2.84,0l80,80A2,2,0,0,1,210,120Z" +},null,-1)])])):"regular"===xm(r)?(Fb(),qb("g",jE,[...t[4]||(t[4]=[Jb("path",{ +d:"M219.31,108.68l-80-80a16,16,0,0,0-22.62,0l-80,80A15.87,15.87,0,0,0,32,120v96a8,8,0,0,0,8,8h64a8,8,0,0,0,8-8V160h32v56a8,8,0,0,0,8,8h64a8,8,0,0,0,8-8V120A15.87,15.87,0,0,0,219.31,108.68ZM208,208H160V152a8,8,0,0,0-8-8H104a8,8,0,0,0-8,8v56H48V120l80-80,80,80Z" +},null,-1)])])):"thin"===xm(r)?(Fb(),qb("g",UE,[...t[5]||(t[5]=[Jb("path",{ +d:"M216.49,111.51l-80-80a12,12,0,0,0-17,0l-80,80A12,12,0,0,0,36,120v96a4,4,0,0,0,4,4h64a4,4,0,0,0,4-4V156h40v60a4,4,0,0,0,4,4h64a4,4,0,0,0,4-4V120A12,12,0,0,0,216.49,111.51ZM212,212H156V152a4,4,0,0,0-4-4H104a4,4,0,0,0-4,4v60H44V120a4,4,0,0,1,1.17-2.83l80-80a4,4,0,0,1,5.66,0l80,80A4,4,0,0,1,212,120Z" +},null,-1)])])):oy("",!0)],16))}}),ZE={key:0},FE={key:1},HE={key:2},QE={key:3 +},VE={key:4},qE={key:5},WE=Hg({name:"ScalarIconInfo",props:{label:{},weight:{}}, +setup(e){const t=e,{bind:n,weight:r}=E_(t);return(e,t)=>(Fb(),qb("svg",cy({ +xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 256 256",fill:"currentColor" +},xm(n)),[Av(e.$slots,"default"),"bold"===xm(r)?(Fb(), +qb("g",ZE,[...t[0]||(t[0]=[Jb("path",{ +d:"M108,84a16,16,0,1,1,16,16A16,16,0,0,1,108,84Zm128,44A108,108,0,1,1,128,20,108.12,108.12,0,0,1,236,128Zm-24,0a84,84,0,1,0-84,84A84.09,84.09,0,0,0,212,128Zm-72,36.68V132a20,20,0,0,0-20-20,12,12,0,0,0-4,23.32V168a20,20,0,0,0,20,20,12,12,0,0,0,4-23.32Z" +},null,-1)])])):"duotone"===xm(r)?(Fb(),qb("g",FE,[...t[1]||(t[1]=[Jb("path",{ +d:"M224,128a96,96,0,1,1-96-96A96,96,0,0,1,224,128Z",opacity:"0.2" +},null,-1),Jb("path",{ +d:"M144,176a8,8,0,0,1-8,8,16,16,0,0,1-16-16V128a8,8,0,0,1,0-16,16,16,0,0,1,16,16v40A8,8,0,0,1,144,176Zm88-48A104,104,0,1,1,128,24,104.11,104.11,0,0,1,232,128Zm-16,0a88,88,0,1,0-88,88A88.1,88.1,0,0,0,216,128ZM124,96a12,12,0,1,0-12-12A12,12,0,0,0,124,96Z" +},null,-1)])])):"fill"===xm(r)?(Fb(),qb("g",HE,[...t[2]||(t[2]=[Jb("path",{ +d:"M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm-4,48a12,12,0,1,1-12,12A12,12,0,0,1,124,72Zm12,112a16,16,0,0,1-16-16V128a8,8,0,0,1,0-16,16,16,0,0,1,16,16v40a8,8,0,0,1,0,16Z" +},null,-1)])])):"light"===xm(r)?(Fb(),qb("g",QE,[...t[3]||(t[3]=[Jb("path",{ +d:"M142,176a6,6,0,0,1-6,6,14,14,0,0,1-14-14V128a2,2,0,0,0-2-2,6,6,0,0,1,0-12,14,14,0,0,1,14,14v40a2,2,0,0,0,2,2A6,6,0,0,1,142,176ZM124,94a10,10,0,1,0-10-10A10,10,0,0,0,124,94Zm106,34A102,102,0,1,1,128,26,102.12,102.12,0,0,1,230,128Zm-12,0a90,90,0,1,0-90,90A90.1,90.1,0,0,0,218,128Z" +},null,-1)])])):"regular"===xm(r)?(Fb(),qb("g",VE,[...t[4]||(t[4]=[Jb("path",{ +d:"M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Zm16-40a8,8,0,0,1-8,8,16,16,0,0,1-16-16V128a8,8,0,0,1,0-16,16,16,0,0,1,16,16v40A8,8,0,0,1,144,176ZM112,84a12,12,0,1,1,12,12A12,12,0,0,1,112,84Z" +},null,-1)])])):"thin"===xm(r)?(Fb(),qb("g",qE,[...t[5]||(t[5]=[Jb("path",{ +d:"M140,176a4,4,0,0,1-4,4,12,12,0,0,1-12-12V128a4,4,0,0,0-4-4,4,4,0,0,1,0-8,12,12,0,0,1,12,12v40a4,4,0,0,0,4,4A4,4,0,0,1,140,176ZM124,92a8,8,0,1,0-8-8A8,8,0,0,0,124,92Zm104,36A100,100,0,1,1,128,28,100.11,100.11,0,0,1,228,128Zm-8,0a92,92,0,1,0-92,92A92.1,92.1,0,0,0,220,128Z" +},null,-1)])])):oy("",!0)],16))}}),XE={key:0},GE={key:1},YE={key:2},KE={key:3 +},JE={key:4},eC={key:5},tC=Hg({name:"ScalarIconLink",props:{label:{},weight:{}}, +setup(e){const t=e,{bind:n,weight:r}=E_(t);return(e,t)=>(Fb(),qb("svg",cy({ +xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 256 256",fill:"currentColor" +},xm(n)),[Av(e.$slots,"default"),"bold"===xm(r)?(Fb(), +qb("g",XE,[...t[0]||(t[0]=[Jb("path",{ +d:"M117.18,188.74a12,12,0,0,1,0,17l-5.12,5.12A58.26,58.26,0,0,1,70.6,228h0A58.62,58.62,0,0,1,29.14,127.92L63.89,93.17a58.64,58.64,0,0,1,98.56,28.11,12,12,0,1,1-23.37,5.44,34.65,34.65,0,0,0-58.22-16.58L46.11,144.89A34.62,34.62,0,0,0,70.57,204h0a34.41,34.41,0,0,0,24.49-10.14l5.11-5.12A12,12,0,0,1,117.18,188.74ZM226.83,45.17a58.65,58.65,0,0,0-82.93,0l-5.11,5.11a12,12,0,0,0,17,17l5.12-5.12a34.63,34.63,0,1,1,49,49L175.1,145.86A34.39,34.39,0,0,1,150.61,156h0a34.63,34.63,0,0,1-33.69-26.72,12,12,0,0,0-23.38,5.44A58.64,58.64,0,0,0,150.56,180h.05a58.28,58.28,0,0,0,41.47-17.17l34.75-34.75a58.62,58.62,0,0,0,0-82.91Z" +},null,-1)])])):"duotone"===xm(r)?(Fb(),qb("g",GE,[...t[1]||(t[1]=[Jb("path",{ +d:"M218.34,119.6,183.6,154.34a46.58,46.58,0,0,1-44.31,12.26c-.31.34-.62.67-.95,1L103.6,202.34A46.63,46.63,0,1,1,37.66,136.4L72.4,101.66A46.6,46.6,0,0,1,116.71,89.4c.31-.34.62-.67,1-1L152.4,53.66a46.63,46.63,0,0,1,65.94,65.94Z", +opacity:"0.2"},null,-1),Jb("path",{ +d:"M240,88.23a54.43,54.43,0,0,1-16,37L189.25,160a54.27,54.27,0,0,1-38.63,16h-.05A54.63,54.63,0,0,1,96,119.84a8,8,0,0,1,16,.45A38.62,38.62,0,0,0,150.58,160h0a38.39,38.39,0,0,0,27.31-11.31l34.75-34.75a38.63,38.63,0,0,0-54.63-54.63l-11,11A8,8,0,0,1,135.7,59l11-11A54.65,54.65,0,0,1,224,48,54.86,54.86,0,0,1,240,88.23ZM109,185.66l-11,11A38.41,38.41,0,0,1,70.6,208h0a38.63,38.63,0,0,1-27.29-65.94L78,107.31A38.63,38.63,0,0,1,144,135.71a8,8,0,0,0,7.78,8.22H152a8,8,0,0,0,8-7.78A54.86,54.86,0,0,0,144,96a54.65,54.65,0,0,0-77.27,0L32,130.75A54.62,54.62,0,0,0,70.56,224h0a54.28,54.28,0,0,0,38.64-16l11-11A8,8,0,0,0,109,185.66Z" +},null,-1)])])):"fill"===xm(r)?(Fb(),qb("g",YE,[...t[2]||(t[2]=[Jb("path",{ +d:"M208,32H48A16,16,0,0,0,32,48V208a16,16,0,0,0,16,16H208a16,16,0,0,0,16-16V48A16,16,0,0,0,208,32ZM115.7,192.49a43.31,43.31,0,0,1-55-66.43l25.37-25.37a43.35,43.35,0,0,1,61.25,0,42.9,42.9,0,0,1,9.95,15.43,8,8,0,1,1-15,5.6A27.33,27.33,0,0,0,97.37,112L72,137.37a27.32,27.32,0,0,0,34.68,41.91,8,8,0,1,1,9,13.21Zm79.61-62.55-25.37,25.37A43,43,0,0,1,139.32,168h0a43.35,43.35,0,0,1-40.53-28.12,8,8,0,1,1,15-5.6A27.35,27.35,0,0,0,139.28,152h0a27.14,27.14,0,0,0,19.32-8L184,118.63a27.32,27.32,0,0,0-34.68-41.91,8,8,0,1,1-9-13.21,43.32,43.32,0,0,1,55,66.43Z" +},null,-1)])])):"light"===xm(r)?(Fb(),qb("g",KE,[...t[3]||(t[3]=[Jb("path",{ +d:"M238,88.18a52.42,52.42,0,0,1-15.4,35.66l-34.75,34.75A52.28,52.28,0,0,1,150.62,174h-.05A52.63,52.63,0,0,1,98,119.9a6,6,0,0,1,6-5.84h.17a6,6,0,0,1,5.83,6.16A40.62,40.62,0,0,0,150.58,162h0a40.4,40.4,0,0,0,28.73-11.9l34.75-34.74A40.63,40.63,0,0,0,156.63,57.9l-11,11a6,6,0,0,1-8.49-8.49l11-11a52.62,52.62,0,0,1,74.43,0A52.83,52.83,0,0,1,238,88.18Zm-127.62,98.9-11,11A40.36,40.36,0,0,1,70.6,210h0a40.63,40.63,0,0,1-28.7-69.36L76.62,105.9A40.63,40.63,0,0,1,146,135.77a6,6,0,0,0,5.83,6.16H152a6,6,0,0,0,6-5.84A52.63,52.63,0,0,0,68.14,97.42L33.38,132.16A52.63,52.63,0,0,0,70.56,222h0a52.26,52.26,0,0,0,37.22-15.42l11-11a6,6,0,1,0-8.49-8.48Z" +},null,-1)])])):"regular"===xm(r)?(Fb(),qb("g",JE,[...t[4]||(t[4]=[Jb("path",{ +d:"M240,88.23a54.43,54.43,0,0,1-16,37L189.25,160a54.27,54.27,0,0,1-38.63,16h-.05A54.63,54.63,0,0,1,96,119.84a8,8,0,0,1,16,.45A38.62,38.62,0,0,0,150.58,160h0a38.39,38.39,0,0,0,27.31-11.31l34.75-34.75a38.63,38.63,0,0,0-54.63-54.63l-11,11A8,8,0,0,1,135.7,59l11-11A54.65,54.65,0,0,1,224,48,54.86,54.86,0,0,1,240,88.23ZM109,185.66l-11,11A38.41,38.41,0,0,1,70.6,208h0a38.63,38.63,0,0,1-27.29-65.94L78,107.31A38.63,38.63,0,0,1,144,135.71a8,8,0,0,0,16,.45A54.86,54.86,0,0,0,144,96a54.65,54.65,0,0,0-77.27,0L32,130.75A54.62,54.62,0,0,0,70.56,224h0a54.28,54.28,0,0,0,38.64-16l11-11A8,8,0,0,0,109,185.66Z" +},null,-1)])])):"thin"===xm(r)?(Fb(),qb("g",eC,[...t[5]||(t[5]=[Jb("path",{ +d:"M236,88.12a50.44,50.44,0,0,1-14.81,34.31l-34.75,34.74A50.33,50.33,0,0,1,150.62,172h-.05A50.63,50.63,0,0,1,100,120a4,4,0,0,1,4-3.89h.11a4,4,0,0,1,3.89,4.11A42.64,42.64,0,0,0,150.58,164h0a42.32,42.32,0,0,0,30.14-12.49l34.75-34.74a42.63,42.63,0,1,0-60.29-60.28l-11,11a4,4,0,0,1-5.66-5.65l11-11A50.64,50.64,0,0,1,236,88.12ZM111.78,188.49l-11,11A42.33,42.33,0,0,1,70.6,212h0a42.63,42.63,0,0,1-30.11-72.77l34.75-34.74A42.63,42.63,0,0,1,148,135.82a4,4,0,0,0,8,.23A50.64,50.64,0,0,0,69.55,98.83L34.8,133.57A50.63,50.63,0,0,0,70.56,220h0a50.33,50.33,0,0,0,35.81-14.83l11-11a4,4,0,1,0-5.65-5.66Z" +},null,-1)])])):oy("",!0)],16))}}),nC={key:0},rC={key:1},aC={key:2},oC={key:3 +},iC={key:4},sC={key:5},lC=Hg({name:"ScalarIconList",props:{label:{},weight:{}}, +setup(e){const t=e,{bind:n,weight:r}=E_(t);return(e,t)=>(Fb(),qb("svg",cy({ +xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 256 256",fill:"currentColor" +},xm(n)),[Av(e.$slots,"default"),"bold"===xm(r)?(Fb(), +qb("g",nC,[...t[0]||(t[0]=[Jb("path",{ +d:"M228,128a12,12,0,0,1-12,12H40a12,12,0,0,1,0-24H216A12,12,0,0,1,228,128ZM40,76H216a12,12,0,0,0,0-24H40a12,12,0,0,0,0,24ZM216,180H40a12,12,0,0,0,0,24H216a12,12,0,0,0,0-24Z" +},null,-1)])])):"duotone"===xm(r)?(Fb(),qb("g",rC,[...t[1]||(t[1]=[Jb("path",{ +d:"M216,64V192H40V64Z",opacity:"0.2"},null,-1),Jb("path",{ +d:"M224,128a8,8,0,0,1-8,8H40a8,8,0,0,1,0-16H216A8,8,0,0,1,224,128ZM40,72H216a8,8,0,0,0,0-16H40a8,8,0,0,0,0,16ZM216,184H40a8,8,0,0,0,0,16H216a8,8,0,0,0,0-16Z" +},null,-1)])])):"fill"===xm(r)?(Fb(),qb("g",aC,[...t[2]||(t[2]=[Jb("path",{ +d:"M208,32H48A16,16,0,0,0,32,48V208a16,16,0,0,0,16,16H208a16,16,0,0,0,16-16V48A16,16,0,0,0,208,32ZM192,184H64a8,8,0,0,1,0-16H192a8,8,0,0,1,0,16Zm0-48H64a8,8,0,0,1,0-16H192a8,8,0,0,1,0,16Zm0-48H64a8,8,0,0,1,0-16H192a8,8,0,0,1,0,16Z" +},null,-1)])])):"light"===xm(r)?(Fb(),qb("g",oC,[...t[3]||(t[3]=[Jb("path",{ +d:"M222,128a6,6,0,0,1-6,6H40a6,6,0,0,1,0-12H216A6,6,0,0,1,222,128ZM40,70H216a6,6,0,0,0,0-12H40a6,6,0,0,0,0,12ZM216,186H40a6,6,0,0,0,0,12H216a6,6,0,0,0,0-12Z" +},null,-1)])])):"regular"===xm(r)?(Fb(),qb("g",iC,[...t[4]||(t[4]=[Jb("path",{ +d:"M224,128a8,8,0,0,1-8,8H40a8,8,0,0,1,0-16H216A8,8,0,0,1,224,128ZM40,72H216a8,8,0,0,0,0-16H40a8,8,0,0,0,0,16ZM216,184H40a8,8,0,0,0,0,16H216a8,8,0,0,0,0-16Z" +},null,-1)])])):"thin"===xm(r)?(Fb(),qb("g",sC,[...t[5]||(t[5]=[Jb("path",{ +d:"M220,128a4,4,0,0,1-4,4H40a4,4,0,0,1,0-8H216A4,4,0,0,1,220,128ZM40,68H216a4,4,0,0,0,0-8H40a4,4,0,0,0,0,8ZM216,188H40a4,4,0,0,0,0,8H216a4,4,0,0,0,0-8Z" +},null,-1)])])):oy("",!0)],16))}}),cC={key:0},uC={key:1},dC={key:2},pC={key:3 +},hC={key:4},fC={key:5},mC=Hg({name:"ScalarIconLockSimple",props:{label:{}, +weight:{}},setup(e){const t=e,{bind:n,weight:r}=E_(t) +;return(e,t)=>(Fb(),qb("svg",cy({xmlns:"http://www.w3.org/2000/svg", +viewBox:"0 0 256 256",fill:"currentColor" +},xm(n)),[Av(e.$slots,"default"),"bold"===xm(r)?(Fb(), +qb("g",cC,[...t[0]||(t[0]=[Jb("path",{ +d:"M208,76H180V56A52,52,0,0,0,76,56V76H48A20,20,0,0,0,28,96V208a20,20,0,0,0,20,20H208a20,20,0,0,0,20-20V96A20,20,0,0,0,208,76ZM100,56a28,28,0,0,1,56,0V76H100ZM204,204H52V100H204Z" +},null,-1)])])):"duotone"===xm(r)?(Fb(),qb("g",uC,[...t[1]||(t[1]=[Jb("path",{ +d:"M216,96V208a8,8,0,0,1-8,8H48a8,8,0,0,1-8-8V96a8,8,0,0,1,8-8H208A8,8,0,0,1,216,96Z", +opacity:"0.2"},null,-1),Jb("path",{ +d:"M208,80H176V56a48,48,0,0,0-96,0V80H48A16,16,0,0,0,32,96V208a16,16,0,0,0,16,16H208a16,16,0,0,0,16-16V96A16,16,0,0,0,208,80ZM96,56a32,32,0,0,1,64,0V80H96ZM208,208H48V96H208V208Z" +},null,-1)])])):"fill"===xm(r)?(Fb(),qb("g",dC,[...t[2]||(t[2]=[Jb("path",{ +d:"M208,80H176V56a48,48,0,0,0-96,0V80H48A16,16,0,0,0,32,96V208a16,16,0,0,0,16,16H208a16,16,0,0,0,16-16V96A16,16,0,0,0,208,80ZM96,56a32,32,0,0,1,64,0V80H96Z" +},null,-1)])])):"light"===xm(r)?(Fb(),qb("g",pC,[...t[3]||(t[3]=[Jb("path",{ +d:"M208,82H174V56a46,46,0,0,0-92,0V82H48A14,14,0,0,0,34,96V208a14,14,0,0,0,14,14H208a14,14,0,0,0,14-14V96A14,14,0,0,0,208,82ZM94,56a34,34,0,0,1,68,0V82H94ZM210,208a2,2,0,0,1-2,2H48a2,2,0,0,1-2-2V96a2,2,0,0,1,2-2H208a2,2,0,0,1,2,2Z" +},null,-1)])])):"regular"===xm(r)?(Fb(),qb("g",hC,[...t[4]||(t[4]=[Jb("path",{ +d:"M208,80H176V56a48,48,0,0,0-96,0V80H48A16,16,0,0,0,32,96V208a16,16,0,0,0,16,16H208a16,16,0,0,0,16-16V96A16,16,0,0,0,208,80ZM96,56a32,32,0,0,1,64,0V80H96ZM208,208H48V96H208V208Z" +},null,-1)])])):"thin"===xm(r)?(Fb(),qb("g",fC,[...t[5]||(t[5]=[Jb("path",{ +d:"M208,84H172V56a44,44,0,0,0-88,0V84H48A12,12,0,0,0,36,96V208a12,12,0,0,0,12,12H208a12,12,0,0,0,12-12V96A12,12,0,0,0,208,84ZM92,56a36,36,0,0,1,72,0V84H92ZM212,208a4,4,0,0,1-4,4H48a4,4,0,0,1-4-4V96a4,4,0,0,1,4-4H208a4,4,0,0,1,4,4Z" +},null,-1)])])):oy("",!0)],16))}}),gC={key:0},vC={key:1},bC={key:2},yC={key:3 +},OC={key:4},wC={key:5},xC=Hg({name:"ScalarIconMagnifyingGlass",props:{label:{}, +weight:{}},setup(e){const t=e,{bind:n,weight:r}=E_(t) +;return(e,t)=>(Fb(),qb("svg",cy({xmlns:"http://www.w3.org/2000/svg", +viewBox:"0 0 256 256",fill:"currentColor" +},xm(n)),[Av(e.$slots,"default"),"bold"===xm(r)?(Fb(), +qb("g",gC,[...t[0]||(t[0]=[Jb("path",{ +d:"M232.49,215.51,185,168a92.12,92.12,0,1,0-17,17l47.53,47.54a12,12,0,0,0,17-17ZM44,112a68,68,0,1,1,68,68A68.07,68.07,0,0,1,44,112Z" +},null,-1)])])):"duotone"===xm(r)?(Fb(),qb("g",vC,[...t[1]||(t[1]=[Jb("path",{ +d:"M192,112a80,80,0,1,1-80-80A80,80,0,0,1,192,112Z",opacity:"0.2" +},null,-1),Jb("path",{ +d:"M229.66,218.34,179.6,168.28a88.21,88.21,0,1,0-11.32,11.31l50.06,50.07a8,8,0,0,0,11.32-11.32ZM40,112a72,72,0,1,1,72,72A72.08,72.08,0,0,1,40,112Z" +},null,-1)])])):"fill"===xm(r)?(Fb(),qb("g",bC,[...t[2]||(t[2]=[Jb("path",{ +d:"M168,112a56,56,0,1,1-56-56A56,56,0,0,1,168,112Zm61.66,117.66a8,8,0,0,1-11.32,0l-50.06-50.07a88,88,0,1,1,11.32-11.31l50.06,50.06A8,8,0,0,1,229.66,229.66ZM112,184a72,72,0,1,0-72-72A72.08,72.08,0,0,0,112,184Z" +},null,-1)])])):"light"===xm(r)?(Fb(),qb("g",yC,[...t[3]||(t[3]=[Jb("path",{ +d:"M228.24,219.76l-51.38-51.38a86.15,86.15,0,1,0-8.48,8.48l51.38,51.38a6,6,0,0,0,8.48-8.48ZM38,112a74,74,0,1,1,74,74A74.09,74.09,0,0,1,38,112Z" +},null,-1)])])):"regular"===xm(r)?(Fb(),qb("g",OC,[...t[4]||(t[4]=[Jb("path",{ +d:"M229.66,218.34l-50.07-50.06a88.11,88.11,0,1,0-11.31,11.31l50.06,50.07a8,8,0,0,0,11.32-11.32ZM40,112a72,72,0,1,1,72,72A72.08,72.08,0,0,1,40,112Z" +},null,-1)])])):"thin"===xm(r)?(Fb(),qb("g",wC,[...t[5]||(t[5]=[Jb("path",{ +d:"M226.83,221.17l-52.7-52.7a84.1,84.1,0,1,0-5.66,5.66l52.7,52.7a4,4,0,0,0,5.66-5.66ZM36,112a76,76,0,1,1,76,76A76.08,76.08,0,0,1,36,112Z" +},null,-1)])])):oy("",!0)],16))}}),kC={key:0},SC={key:1},_C={key:2},AC={key:3 +},TC={key:4},EC={key:5},CC=Hg({name:"ScalarIconNotepad",props:{label:{}, +weight:{}},setup(e){const t=e,{bind:n,weight:r}=E_(t) +;return(e,t)=>(Fb(),qb("svg",cy({xmlns:"http://www.w3.org/2000/svg", +viewBox:"0 0 256 256",fill:"currentColor" +},xm(n)),[Av(e.$slots,"default"),"bold"===xm(r)?(Fb(), +qb("g",kC,[...t[0]||(t[0]=[Jb("path",{ +d:"M172,124a12,12,0,0,1-12,12H96a12,12,0,0,1,0-24h64A12,12,0,0,1,172,124Zm-12,28H96a12,12,0,0,0,0,24h64a12,12,0,0,0,0-24ZM220,40V200a36,36,0,0,1-36,36H72a36,36,0,0,1-36-36V40A12,12,0,0,1,48,28H72V24a12,12,0,0,1,24,0v4h20V24a12,12,0,0,1,24,0v4h20V24a12,12,0,0,1,24,0v4h24A12,12,0,0,1,220,40ZM196,52H184v4a12,12,0,0,1-24,0V52H140v4a12,12,0,0,1-24,0V52H96v4a12,12,0,0,1-24,0V52H60V200a12,12,0,0,0,12,12H184a12,12,0,0,0,12-12Z" +},null,-1)])])):"duotone"===xm(r)?(Fb(),qb("g",SC,[...t[1]||(t[1]=[Jb("path",{ +d:"M208,40V200a24,24,0,0,1-24,24H72a24,24,0,0,1-24-24V40Z",opacity:"0.2" +},null,-1),Jb("path",{ +d:"M168,128a8,8,0,0,1-8,8H96a8,8,0,0,1,0-16h64A8,8,0,0,1,168,128Zm-8,24H96a8,8,0,0,0,0,16h64a8,8,0,0,0,0-16ZM216,40V200a32,32,0,0,1-32,32H72a32,32,0,0,1-32-32V40a8,8,0,0,1,8-8H72V24a8,8,0,0,1,16,0v8h32V24a8,8,0,0,1,16,0v8h32V24a8,8,0,0,1,16,0v8h24A8,8,0,0,1,216,40Zm-16,8H184v8a8,8,0,0,1-16,0V48H136v8a8,8,0,0,1-16,0V48H88v8a8,8,0,0,1-16,0V48H56V200a16,16,0,0,0,16,16H184a16,16,0,0,0,16-16Z" +},null,-1)])])):"fill"===xm(r)?(Fb(),qb("g",_C,[...t[2]||(t[2]=[Jb("path",{ +d:"M208,32H184V24a8,8,0,0,0-16,0v8H136V24a8,8,0,0,0-16,0v8H88V24a8,8,0,0,0-16,0v8H48a8,8,0,0,0-8,8V200a32,32,0,0,0,32,32H184a32,32,0,0,0,32-32V40A8,8,0,0,0,208,32ZM120,56a8,8,0,0,1,16,0v8a8,8,0,0,1-16,0ZM80,72a8,8,0,0,1-8-8V56a8,8,0,0,1,16,0v8A8,8,0,0,1,80,72Zm80,96H96a8,8,0,0,1,0-16h64a8,8,0,0,1,0,16Zm0-32H96a8,8,0,0,1,0-16h64a8,8,0,0,1,0,16Zm24-72a8,8,0,0,1-16,0V56a8,8,0,0,1,16,0Z" +},null,-1)])])):"light"===xm(r)?(Fb(),qb("g",AC,[...t[3]||(t[3]=[Jb("path",{ +d:"M166,128a6,6,0,0,1-6,6H96a6,6,0,0,1,0-12h64A6,6,0,0,1,166,128Zm-6,26H96a6,6,0,0,0,0,12h64a6,6,0,0,0,0-12ZM214,40V200a30,30,0,0,1-30,30H72a30,30,0,0,1-30-30V40a6,6,0,0,1,6-6H74V24a6,6,0,0,1,12,0V34h36V24a6,6,0,0,1,12,0V34h36V24a6,6,0,0,1,12,0V34h26A6,6,0,0,1,214,40Zm-12,6H182V56a6,6,0,0,1-12,0V46H134V56a6,6,0,0,1-12,0V46H86V56a6,6,0,0,1-12,0V46H54V200a18,18,0,0,0,18,18H184a18,18,0,0,0,18-18Z" +},null,-1)])])):"regular"===xm(r)?(Fb(),qb("g",TC,[...t[4]||(t[4]=[Jb("path",{ +d:"M168,128a8,8,0,0,1-8,8H96a8,8,0,0,1,0-16h64A8,8,0,0,1,168,128Zm-8,24H96a8,8,0,0,0,0,16h64a8,8,0,0,0,0-16ZM216,40V200a32,32,0,0,1-32,32H72a32,32,0,0,1-32-32V40a8,8,0,0,1,8-8H72V24a8,8,0,0,1,16,0v8h32V24a8,8,0,0,1,16,0v8h32V24a8,8,0,0,1,16,0v8h24A8,8,0,0,1,216,40Zm-16,8H184v8a8,8,0,0,1-16,0V48H136v8a8,8,0,0,1-16,0V48H88v8a8,8,0,0,1-16,0V48H56V200a16,16,0,0,0,16,16H184a16,16,0,0,0,16-16Z" +},null,-1)])])):"thin"===xm(r)?(Fb(),qb("g",EC,[...t[5]||(t[5]=[Jb("path",{ +d:"M164,128a4,4,0,0,1-4,4H96a4,4,0,0,1,0-8h64A4,4,0,0,1,164,128Zm-4,28H96a4,4,0,0,0,0,8h64a4,4,0,0,0,0-8ZM212,40V200a28,28,0,0,1-28,28H72a28,28,0,0,1-28-28V40a4,4,0,0,1,4-4H76V24a4,4,0,0,1,8,0V36h40V24a4,4,0,0,1,8,0V36h40V24a4,4,0,0,1,8,0V36h28A4,4,0,0,1,212,40Zm-8,4H180V56a4,4,0,0,1-8,0V44H132V56a4,4,0,0,1-8,0V44H84V56a4,4,0,0,1-8,0V44H52V200a20,20,0,0,0,20,20H184a20,20,0,0,0,20-20Z" +},null,-1)])])):oy("",!0)],16))}}),$C={key:0},PC={key:1},IC={key:2},DC={key:3 +},MC={key:4},NC={key:5},RC=Hg({name:"ScalarIconPencilSimple",props:{label:{}, +weight:{}},setup(e){const t=e,{bind:n,weight:r}=E_(t) +;return(e,t)=>(Fb(),qb("svg",cy({xmlns:"http://www.w3.org/2000/svg", +viewBox:"0 0 256 256",fill:"currentColor" +},xm(n)),[Av(e.$slots,"default"),"bold"===xm(r)?(Fb(), +qb("g",$C,[...t[0]||(t[0]=[Jb("path",{ +d:"M230.14,70.54,185.46,25.85a20,20,0,0,0-28.29,0L33.86,149.17A19.85,19.85,0,0,0,28,163.31V208a20,20,0,0,0,20,20H92.69a19.86,19.86,0,0,0,14.14-5.86L230.14,98.82a20,20,0,0,0,0-28.28ZM91,204H52V165l84-84,39,39ZM192,103,153,64l18.34-18.34,39,39Z" +},null,-1)])])):"duotone"===xm(r)?(Fb(),qb("g",PC,[...t[1]||(t[1]=[Jb("path",{ +d:"M221.66,90.34,192,120,136,64l29.66-29.66a8,8,0,0,1,11.31,0L221.66,79A8,8,0,0,1,221.66,90.34Z", +opacity:"0.2"},null,-1),Jb("path",{ +d:"M227.31,73.37,182.63,28.68a16,16,0,0,0-22.63,0L36.69,152A15.86,15.86,0,0,0,32,163.31V208a16,16,0,0,0,16,16H92.69A15.86,15.86,0,0,0,104,219.31L227.31,96a16,16,0,0,0,0-22.63ZM92.69,208H48V163.31l88-88L180.69,120ZM192,108.68,147.31,64l24-24L216,84.68Z" +},null,-1)])])):"fill"===xm(r)?(Fb(),qb("g",IC,[...t[2]||(t[2]=[Jb("path",{ +d:"M227.31,73.37,182.63,28.68a16,16,0,0,0-22.63,0L36.69,152A15.86,15.86,0,0,0,32,163.31V208a16,16,0,0,0,16,16H92.69A15.86,15.86,0,0,0,104,219.31L227.31,96a16,16,0,0,0,0-22.63ZM192,108.68,147.31,64l24-24L216,84.68Z" +},null,-1)])])):"light"===xm(r)?(Fb(),qb("g",DC,[...t[3]||(t[3]=[Jb("path",{ +d:"M225.9,74.78,181.21,30.09a14,14,0,0,0-19.8,0L38.1,153.41a13.94,13.94,0,0,0-4.1,9.9V208a14,14,0,0,0,14,14H92.69a13.94,13.94,0,0,0,9.9-4.1L225.9,94.58a14,14,0,0,0,0-19.8ZM94.1,209.41a2,2,0,0,1-1.41.59H48a2,2,0,0,1-2-2V163.31a2,2,0,0,1,.59-1.41L136,72.48,183.51,120ZM217.41,86.1,192,111.51,144.49,64,169.9,38.58a2,2,0,0,1,2.83,0l44.68,44.69a2,2,0,0,1,0,2.83Z" +},null,-1)])])):"regular"===xm(r)?(Fb(),qb("g",MC,[...t[4]||(t[4]=[Jb("path",{ +d:"M227.31,73.37,182.63,28.68a16,16,0,0,0-22.63,0L36.69,152A15.86,15.86,0,0,0,32,163.31V208a16,16,0,0,0,16,16H92.69A15.86,15.86,0,0,0,104,219.31L227.31,96a16,16,0,0,0,0-22.63ZM92.69,208H48V163.31l88-88L180.69,120ZM192,108.68,147.31,64l24-24L216,84.68Z" +},null,-1)])])):"thin"===xm(r)?(Fb(),qb("g",NC,[...t[5]||(t[5]=[Jb("path",{ +d:"M224.49,76.2,179.8,31.51a12,12,0,0,0-17,0L133.17,61.17h0L39.52,154.83A11.9,11.9,0,0,0,36,163.31V208a12,12,0,0,0,12,12H92.69a12,12,0,0,0,8.48-3.51L224.48,93.17a12,12,0,0,0,0-17Zm-129,134.63A4,4,0,0,1,92.69,212H48a4,4,0,0,1-4-4V163.31a4,4,0,0,1,1.17-2.83L136,69.65,186.34,120ZM218.83,87.51,192,114.34,141.66,64l26.82-26.83a4,4,0,0,1,5.66,0l44.69,44.68a4,4,0,0,1,0,5.66Z" +},null,-1)])])):oy("",!0)],16))}}),LC={key:0},BC={key:1},jC={key:2},UC={key:3 +},zC={key:4},ZC={key:5},FC=Hg({name:"ScalarIconPlay",props:{label:{},weight:{}}, +setup(e){const t=e,{bind:n,weight:r}=E_(t);return(e,t)=>(Fb(),qb("svg",cy({ +xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 256 256",fill:"currentColor" +},xm(n)),[Av(e.$slots,"default"),"bold"===xm(r)?(Fb(), +qb("g",LC,[...t[0]||(t[0]=[Jb("path",{ +d:"M234.49,111.07,90.41,22.94A20,20,0,0,0,60,39.87V216.13a20,20,0,0,0,30.41,16.93l144.08-88.13a19.82,19.82,0,0,0,0-33.86ZM84,208.85V47.15L216.16,128Z" +},null,-1)])])):"duotone"===xm(r)?(Fb(),qb("g",BC,[...t[1]||(t[1]=[Jb("path",{ +d:"M228.23,134.69,84.15,222.81A8,8,0,0,1,72,216.12V39.88a8,8,0,0,1,12.15-6.69l144.08,88.12A7.82,7.82,0,0,1,228.23,134.69Z", +opacity:"0.2"},null,-1),Jb("path",{ +d:"M232.4,114.49,88.32,26.35a16,16,0,0,0-16.2-.3A15.86,15.86,0,0,0,64,39.87V216.13A15.94,15.94,0,0,0,80,232a16.07,16.07,0,0,0,8.36-2.35L232.4,141.51a15.81,15.81,0,0,0,0-27ZM80,215.94V40l143.83,88Z" +},null,-1)])])):"fill"===xm(r)?(Fb(),qb("g",jC,[...t[2]||(t[2]=[Jb("path",{ +d:"M240,128a15.74,15.74,0,0,1-7.6,13.51L88.32,229.65a16,16,0,0,1-16.2.3A15.86,15.86,0,0,1,64,216.13V39.87a15.86,15.86,0,0,1,8.12-13.82,16,16,0,0,1,16.2.3L232.4,114.49A15.74,15.74,0,0,1,240,128Z" +},null,-1)])])):"light"===xm(r)?(Fb(),qb("g",UC,[...t[3]||(t[3]=[Jb("path",{ +d:"M231.36,116.19,87.28,28.06a14,14,0,0,0-14.18-.27A13.69,13.69,0,0,0,66,39.87V216.13a13.69,13.69,0,0,0,7.1,12.08,14,14,0,0,0,14.18-.27l144.08-88.13a13.82,13.82,0,0,0,0-23.62Zm-6.26,13.38L81,217.7a2,2,0,0,1-2.06,0,1.78,1.78,0,0,1-1-1.61V39.87a1.78,1.78,0,0,1,1-1.61A2.06,2.06,0,0,1,80,38a2,2,0,0,1,1,.31L225.1,126.43a1.82,1.82,0,0,1,0,3.14Z" +},null,-1)])])):"regular"===xm(r)?(Fb(),qb("g",zC,[...t[4]||(t[4]=[Jb("path",{ +d:"M232.4,114.49,88.32,26.35a16,16,0,0,0-16.2-.3A15.86,15.86,0,0,0,64,39.87V216.13A15.94,15.94,0,0,0,80,232a16.07,16.07,0,0,0,8.36-2.35L232.4,141.51a15.81,15.81,0,0,0,0-27ZM80,215.94V40l143.83,88Z" +},null,-1)])])):"thin"===xm(r)?(Fb(),qb("g",ZC,[...t[5]||(t[5]=[Jb("path",{ +d:"M230.32,117.9,86.24,29.79a11.91,11.91,0,0,0-12.17-.23A11.71,11.71,0,0,0,68,39.89V216.11a11.71,11.71,0,0,0,6.07,10.33,11.91,11.91,0,0,0,12.17-.23L230.32,138.1a11.82,11.82,0,0,0,0-20.2Zm-4.18,13.37L82.06,219.39a4,4,0,0,1-4.07.07,3.77,3.77,0,0,1-2-3.35V39.89a3.77,3.77,0,0,1,2-3.35,4,4,0,0,1,4.07.07l144.08,88.12a3.8,3.8,0,0,1,0,6.54Z" +},null,-1)])])):oy("",!0)],16))}}),HC={key:0},QC={key:1},VC={key:2},qC={key:3 +},WC={key:4},XC={key:5},GC=Hg({name:"ScalarIconPlus",props:{label:{},weight:{}}, +setup(e){const t=e,{bind:n,weight:r}=E_(t);return(e,t)=>(Fb(),qb("svg",cy({ +xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 256 256",fill:"currentColor" +},xm(n)),[Av(e.$slots,"default"),"bold"===xm(r)?(Fb(), +qb("g",HC,[...t[0]||(t[0]=[Jb("path",{ +d:"M228,128a12,12,0,0,1-12,12H140v76a12,12,0,0,1-24,0V140H40a12,12,0,0,1,0-24h76V40a12,12,0,0,1,24,0v76h76A12,12,0,0,1,228,128Z" +},null,-1)])])):"duotone"===xm(r)?(Fb(),qb("g",QC,[...t[1]||(t[1]=[Jb("path",{ +d:"M216,56V200a16,16,0,0,1-16,16H56a16,16,0,0,1-16-16V56A16,16,0,0,1,56,40H200A16,16,0,0,1,216,56Z", +opacity:"0.2"},null,-1),Jb("path",{ +d:"M224,128a8,8,0,0,1-8,8H136v80a8,8,0,0,1-16,0V136H40a8,8,0,0,1,0-16h80V40a8,8,0,0,1,16,0v80h80A8,8,0,0,1,224,128Z" +},null,-1)])])):"fill"===xm(r)?(Fb(),qb("g",VC,[...t[2]||(t[2]=[Jb("path",{ +d:"M208,32H48A16,16,0,0,0,32,48V208a16,16,0,0,0,16,16H208a16,16,0,0,0,16-16V48A16,16,0,0,0,208,32ZM184,136H136v48a8,8,0,0,1-16,0V136H72a8,8,0,0,1,0-16h48V72a8,8,0,0,1,16,0v48h48a8,8,0,0,1,0,16Z" +},null,-1)])])):"light"===xm(r)?(Fb(),qb("g",qC,[...t[3]||(t[3]=[Jb("path",{ +d:"M222,128a6,6,0,0,1-6,6H134v82a6,6,0,0,1-12,0V134H40a6,6,0,0,1,0-12h82V40a6,6,0,0,1,12,0v82h82A6,6,0,0,1,222,128Z" +},null,-1)])])):"regular"===xm(r)?(Fb(),qb("g",WC,[...t[4]||(t[4]=[Jb("path",{ +d:"M224,128a8,8,0,0,1-8,8H136v80a8,8,0,0,1-16,0V136H40a8,8,0,0,1,0-16h80V40a8,8,0,0,1,16,0v80h80A8,8,0,0,1,224,128Z" +},null,-1)])])):"thin"===xm(r)?(Fb(),qb("g",XC,[...t[5]||(t[5]=[Jb("path",{ +d:"M220,128a4,4,0,0,1-4,4H132v84a4,4,0,0,1-8,0V132H40a4,4,0,0,1,0-8h84V40a4,4,0,0,1,8,0v84h84A4,4,0,0,1,220,128Z" +},null,-1)])])):oy("",!0)],16))}}),YC={key:0},KC={key:1},JC={key:2},e$={key:3 +},t$={key:4},n$={key:5},r$=Hg({name:"ScalarIconScroll",props:{label:{},weight:{} +},setup(e){const t=e,{bind:n,weight:r}=E_(t);return(e,t)=>(Fb(),qb("svg",cy({ +xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 256 256",fill:"currentColor" +},xm(n)),[Av(e.$slots,"default"),"bold"===xm(r)?(Fb(), +qb("g",YC,[...t[0]||(t[0]=[Jb("path",{ +d:"M92,92a12,12,0,0,1,12-12h60a12,12,0,0,1,0,24H104A12,12,0,0,1,92,92Zm12,52h60a12,12,0,0,0,0-24H104a12,12,0,0,0,0,24Zm132,48a36,36,0,0,1-36,36H88a36,36,0,0,1-36-36V64a12,12,0,0,0-24,0c0,3.73,3.35,6.51,3.38,6.54l-.18-.14h0A12,12,0,1,1,16.81,89.59h0C15.49,88.62,4,79.55,4,64A36,36,0,0,1,40,28H176a36,36,0,0,1,36,36V164h4a12,12,0,0,1,7.2,2.4C224.51,167.38,236,176.45,236,192ZM92.62,172.2A12,12,0,0,1,104,164h84V64a12,12,0,0,0-12-12H73.94A35.88,35.88,0,0,1,76,64V192a12,12,0,0,0,24,0c0-3.58-3.17-6.38-3.2-6.4A12,12,0,0,1,92.62,172.2ZM212,192a7.69,7.69,0,0,0-1.24-4h-87a30.32,30.32,0,0,1,.26,4,35.84,35.84,0,0,1-2.06,12H200A12,12,0,0,0,212,192Z" +},null,-1)])])):"duotone"===xm(r)?(Fb(),qb("g",KC,[...t[1]||(t[1]=[Jb("path",{ +d:"M200,176H104s8,6,8,16a24,24,0,0,1-48,0V64A24,24,0,0,0,40,40H176a24,24,0,0,1,24,24Z", +opacity:"0.2"},null,-1),Jb("path",{ +d:"M96,104a8,8,0,0,1,8-8h64a8,8,0,0,1,0,16H104A8,8,0,0,1,96,104Zm8,40h64a8,8,0,0,0,0-16H104a8,8,0,0,0,0,16Zm128,48a32,32,0,0,1-32,32H88a32,32,0,0,1-32-32V64a16,16,0,0,0-32,0c0,5.74,4.83,9.62,4.88,9.66h0A8,8,0,0,1,24,88a7.89,7.89,0,0,1-4.79-1.61h0C18.05,85.54,8,77.61,8,64A32,32,0,0,1,40,32H176a32,32,0,0,1,32,32V168h8a8,8,0,0,1,4.8,1.6C222,170.46,232,178.39,232,192ZM96.26,173.48A8.07,8.07,0,0,1,104,168h88V64a16,16,0,0,0-16-16H67.69A31.71,31.71,0,0,1,72,64V192a16,16,0,0,0,32,0c0-5.74-4.83-9.62-4.88-9.66A7.82,7.82,0,0,1,96.26,173.48ZM216,192a12.58,12.58,0,0,0-3.23-8h-94a26.92,26.92,0,0,1,1.21,8,31.82,31.82,0,0,1-4.29,16H200A16,16,0,0,0,216,192Z" +},null,-1)])])):"fill"===xm(r)?(Fb(),qb("g",JC,[...t[2]||(t[2]=[Jb("path",{ +d:"M220.8,169.6A8,8,0,0,0,216,168h-8V64a32,32,0,0,0-32-32H40A32,32,0,0,0,8,64C8,77.61,18.05,85.54,19.2,86.4h0A7.89,7.89,0,0,0,24,88a8,8,0,0,0,4.87-14.33h0C28.83,73.62,24,69.74,24,64a16,16,0,0,1,32,0V192a32,32,0,0,0,32,32H200a32,32,0,0,0,32-32C232,178.39,222,170.46,220.8,169.6ZM104,96h64a8,8,0,0,1,0,16H104a8,8,0,0,1,0-16Zm-8,40a8,8,0,0,1,8-8h64a8,8,0,0,1,0,16H104A8,8,0,0,1,96,136Zm104,72H107.71A31.82,31.82,0,0,0,112,192a26.92,26.92,0,0,0-1.21-8h102a12.58,12.58,0,0,1,3.23,8A16,16,0,0,1,200,208Z" +},null,-1)])])):"light"===xm(r)?(Fb(),qb("g",e$,[...t[3]||(t[3]=[Jb("path",{ +d:"M98,136a6,6,0,0,1,6-6h64a6,6,0,0,1,0,12H104A6,6,0,0,1,98,136Zm6-26h64a6,6,0,0,0,0-12H104a6,6,0,0,0,0,12Zm126,82a30,30,0,0,1-30,30H88a30,30,0,0,1-30-30V64a18,18,0,0,0-36,0c0,6.76,5.58,11.19,5.64,11.23A6,6,0,1,1,20.4,84.8C20,84.48,10,76.85,10,64A30,30,0,0,1,40,34H176a30,30,0,0,1,30,30V170h10a6,6,0,0,1,3.6,1.2C220,171.52,230,179.15,230,192Zm-124,0c0-6.76-5.59-11.19-5.64-11.23A6,6,0,0,1,104,170h90V64a18,18,0,0,0-18-18H64a29.82,29.82,0,0,1,6,18V192a18,18,0,0,0,36,0Zm112,0a14.94,14.94,0,0,0-4.34-10H115.88A24.83,24.83,0,0,1,118,192a29.87,29.87,0,0,1-6,18h88A18,18,0,0,0,218,192Z" +},null,-1)])])):"regular"===xm(r)?(Fb(),qb("g",t$,[...t[4]||(t[4]=[Jb("path",{ +d:"M96,104a8,8,0,0,1,8-8h64a8,8,0,0,1,0,16H104A8,8,0,0,1,96,104Zm8,40h64a8,8,0,0,0,0-16H104a8,8,0,0,0,0,16Zm128,48a32,32,0,0,1-32,32H88a32,32,0,0,1-32-32V64a16,16,0,0,0-32,0c0,5.74,4.83,9.62,4.88,9.66h0A8,8,0,0,1,24,88a7.89,7.89,0,0,1-4.79-1.61h0C18.05,85.54,8,77.61,8,64A32,32,0,0,1,40,32H176a32,32,0,0,1,32,32V168h8a8,8,0,0,1,4.8,1.6C222,170.46,232,178.39,232,192ZM96.26,173.48A8.07,8.07,0,0,1,104,168h88V64a16,16,0,0,0-16-16H67.69A31.71,31.71,0,0,1,72,64V192a16,16,0,0,0,32,0c0-5.74-4.83-9.62-4.88-9.66A7.82,7.82,0,0,1,96.26,173.48ZM216,192a12.58,12.58,0,0,0-3.23-8h-94a26.92,26.92,0,0,1,1.21,8,31.82,31.82,0,0,1-4.29,16H200A16,16,0,0,0,216,192Z" +},null,-1)])])):"thin"===xm(r)?(Fb(),qb("g",n$,[...t[5]||(t[5]=[Jb("path",{ +d:"M100,104a4,4,0,0,1,4-4h64a4,4,0,0,1,0,8H104A4,4,0,0,1,100,104Zm4,36h64a4,4,0,0,0,0-8H104a4,4,0,0,0,0,8Zm124,52a28,28,0,0,1-28,28H88a28,28,0,0,1-28-28V64a20,20,0,0,0-40,0c0,7.78,6.34,12.75,6.4,12.8a4,4,0,1,1-4.8,6.4C21.21,82.91,12,75.86,12,64A28,28,0,0,1,40,36H176a28,28,0,0,1,28,28V172h12a4,4,0,0,1,2.4.8C218.79,173.09,228,180.14,228,192Zm-120,0c0-7.78-6.34-12.75-6.4-12.8A4,4,0,0,1,104,172h92V64a20,20,0,0,0-20-20H59.57A27.9,27.9,0,0,1,68,64V192a20,20,0,0,0,40,0Zm112,0c0-6-3.74-10.3-5.5-12H112.61A23.31,23.31,0,0,1,116,192a27.94,27.94,0,0,1-8.42,20H200A20,20,0,0,0,220,192Z" +},null,-1)])])):oy("",!0)],16))}}),a$={key:0},o$={key:1},i$={key:2},s$={key:3 +},l$={key:4},c$={key:5},u$=Hg({name:"ScalarIconSparkle",props:{label:{}, +weight:{}},setup(e){const t=e,{bind:n,weight:r}=E_(t) +;return(e,t)=>(Fb(),qb("svg",cy({xmlns:"http://www.w3.org/2000/svg", +viewBox:"0 0 256 256",fill:"currentColor" +},xm(n)),[Av(e.$slots,"default"),"bold"===xm(r)?(Fb(), +qb("g",a$,[...t[0]||(t[0]=[Jb("path",{ +d:"M199,125.31l-49.88-18.39L130.69,57a19.92,19.92,0,0,0-37.38,0L74.92,106.92,25,125.31a19.92,19.92,0,0,0,0,37.38l49.88,18.39L93.31,231a19.92,19.92,0,0,0,37.38,0l18.39-49.88L199,162.69a19.92,19.92,0,0,0,0-37.38Zm-63.38,35.16a12,12,0,0,0-7.11,7.11L112,212.28l-16.47-44.7a12,12,0,0,0-7.11-7.11L43.72,144l44.7-16.47a12,12,0,0,0,7.11-7.11L112,75.72l16.47,44.7a12,12,0,0,0,7.11,7.11L180.28,144ZM140,40a12,12,0,0,1,12-12h12V16a12,12,0,0,1,24,0V28h12a12,12,0,0,1,0,24H188V64a12,12,0,0,1-24,0V52H152A12,12,0,0,1,140,40ZM252,88a12,12,0,0,1-12,12h-4v4a12,12,0,0,1-24,0v-4h-4a12,12,0,0,1,0-24h4V72a12,12,0,0,1,24,0v4h4A12,12,0,0,1,252,88Z" +},null,-1)])])):"duotone"===xm(r)?(Fb(),qb("g",o$,[...t[1]||(t[1]=[Jb("path",{ +d:"M194.82,151.43l-55.09,20.3-20.3,55.09a7.92,7.92,0,0,1-14.86,0l-20.3-55.09-55.09-20.3a7.92,7.92,0,0,1,0-14.86l55.09-20.3,20.3-55.09a7.92,7.92,0,0,1,14.86,0l20.3,55.09,55.09,20.3A7.92,7.92,0,0,1,194.82,151.43Z", +opacity:"0.2"},null,-1),Jb("path",{ +d:"M197.58,129.06,146,110l-19-51.62a15.92,15.92,0,0,0-29.88,0L78,110l-51.62,19a15.92,15.92,0,0,0,0,29.88L78,178l19,51.62a15.92,15.92,0,0,0,29.88,0L146,178l51.62-19a15.92,15.92,0,0,0,0-29.88ZM137,164.22a8,8,0,0,0-4.74,4.74L112,223.85,91.78,169A8,8,0,0,0,87,164.22L32.15,144,87,123.78A8,8,0,0,0,91.78,119L112,64.15,132.22,119a8,8,0,0,0,4.74,4.74L191.85,144ZM144,40a8,8,0,0,1,8-8h16V16a8,8,0,0,1,16,0V32h16a8,8,0,0,1,0,16H184V64a8,8,0,0,1-16,0V48H152A8,8,0,0,1,144,40ZM248,88a8,8,0,0,1-8,8h-8v8a8,8,0,0,1-16,0V96h-8a8,8,0,0,1,0-16h8V72a8,8,0,0,1,16,0v8h8A8,8,0,0,1,248,88Z" +},null,-1)])])):"fill"===xm(r)?(Fb(),qb("g",i$,[...t[2]||(t[2]=[Jb("path",{ +d:"M208,144a15.78,15.78,0,0,1-10.42,14.94L146,178l-19,51.62a15.92,15.92,0,0,1-29.88,0L78,178l-51.62-19a15.92,15.92,0,0,1,0-29.88L78,110l19-51.62a15.92,15.92,0,0,1,29.88,0L146,110l51.62,19A15.78,15.78,0,0,1,208,144ZM152,48h16V64a8,8,0,0,0,16,0V48h16a8,8,0,0,0,0-16H184V16a8,8,0,0,0-16,0V32H152a8,8,0,0,0,0,16Zm88,32h-8V72a8,8,0,0,0-16,0v8h-8a8,8,0,0,0,0,16h8v8a8,8,0,0,0,16,0V96h8a8,8,0,0,0,0-16Z" +},null,-1)])])):"light"===xm(r)?(Fb(),qb("g",s$,[...t[3]||(t[3]=[Jb("path",{ +d:"M196.89,130.94,144.4,111.6,125.06,59.11a13.92,13.92,0,0,0-26.12,0L79.6,111.6,27.11,130.94a13.92,13.92,0,0,0,0,26.12L79.6,176.4l19.34,52.49a13.92,13.92,0,0,0,26.12,0L144.4,176.4l52.49-19.34a13.92,13.92,0,0,0,0-26.12Zm-4.15,14.86-55.08,20.3a6,6,0,0,0-3.56,3.56l-20.3,55.08a1.92,1.92,0,0,1-3.6,0L89.9,169.66a6,6,0,0,0-3.56-3.56L31.26,145.8a1.92,1.92,0,0,1,0-3.6l55.08-20.3a6,6,0,0,0,3.56-3.56l20.3-55.08a1.92,1.92,0,0,1,3.6,0l20.3,55.08a6,6,0,0,0,3.56,3.56l55.08,20.3a1.92,1.92,0,0,1,0,3.6ZM146,40a6,6,0,0,1,6-6h18V16a6,6,0,0,1,12,0V34h18a6,6,0,0,1,0,12H182V64a6,6,0,0,1-12,0V46H152A6,6,0,0,1,146,40ZM246,88a6,6,0,0,1-6,6H230v10a6,6,0,0,1-12,0V94H208a6,6,0,0,1,0-12h10V72a6,6,0,0,1,12,0V82h10A6,6,0,0,1,246,88Z" +},null,-1)])])):"regular"===xm(r)?(Fb(),qb("g",l$,[...t[4]||(t[4]=[Jb("path",{ +d:"M197.58,129.06,146,110l-19-51.62a15.92,15.92,0,0,0-29.88,0L78,110l-51.62,19a15.92,15.92,0,0,0,0,29.88L78,178l19,51.62a15.92,15.92,0,0,0,29.88,0L146,178l51.62-19a15.92,15.92,0,0,0,0-29.88ZM137,164.22a8,8,0,0,0-4.74,4.74L112,223.85,91.78,169A8,8,0,0,0,87,164.22L32.15,144,87,123.78A8,8,0,0,0,91.78,119L112,64.15,132.22,119a8,8,0,0,0,4.74,4.74L191.85,144ZM144,40a8,8,0,0,1,8-8h16V16a8,8,0,0,1,16,0V32h16a8,8,0,0,1,0,16H184V64a8,8,0,0,1-16,0V48H152A8,8,0,0,1,144,40ZM248,88a8,8,0,0,1-8,8h-8v8a8,8,0,0,1-16,0V96h-8a8,8,0,0,1,0-16h8V72a8,8,0,0,1,16,0v8h8A8,8,0,0,1,248,88Z" +},null,-1)])])):"thin"===xm(r)?(Fb(),qb("g",c$,[...t[5]||(t[5]=[Jb("path",{ +d:"M196.2,132.81l-53.36-19.65L123.19,59.8a11.93,11.93,0,0,0-22.38,0L81.16,113.16,27.8,132.81a11.93,11.93,0,0,0,0,22.38l53.36,19.65,19.65,53.36a11.93,11.93,0,0,0,22.38,0l19.65-53.36,53.36-19.65a11.93,11.93,0,0,0,0-22.38Zm-2.77,14.87L138.35,168a4,4,0,0,0-2.37,2.37l-20.3,55.08a3.92,3.92,0,0,1-7.36,0L88,170.35A4,4,0,0,0,85.65,168l-55.08-20.3a3.92,3.92,0,0,1,0-7.36L85.65,120A4,4,0,0,0,88,117.65l20.3-55.08a3.92,3.92,0,0,1,7.36,0L136,117.65a4,4,0,0,0,2.37,2.37l55.08,20.3a3.92,3.92,0,0,1,0,7.36ZM148,40a4,4,0,0,1,4-4h20V16a4,4,0,0,1,8,0V36h20a4,4,0,0,1,0,8H180V64a4,4,0,0,1-8,0V44H152A4,4,0,0,1,148,40Zm96,48a4,4,0,0,1-4,4H228v12a4,4,0,0,1-8,0V92H208a4,4,0,0,1,0-8h12V72a4,4,0,0,1,8,0V84h12A4,4,0,0,1,244,88Z" +},null,-1)])])):oy("",!0)],16))}}),d$={key:0},p$={key:1},h$={key:2},f$={key:3 +},m$={key:4},g$={key:5},v$=Hg({name:"ScalarIconSwap",props:{label:{},weight:{}}, +setup(e){const t=e,{bind:n,weight:r}=E_(t);return(e,t)=>(Fb(),qb("svg",cy({ +xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 256 256",fill:"currentColor" +},xm(n)),[Av(e.$slots,"default"),"bold"===xm(r)?(Fb(), +qb("g",d$,[...t[0]||(t[0]=[Jb("path",{ +d:"M228,48V152a20,20,0,0,1-20,20H112.92a12,12,0,0,1-17.41,16.49l-20-20a12,12,0,0,1,0-17l20-20A12,12,0,0,1,112.92,148H204V52H100a12,12,0,0,1-24,0V48A20,20,0,0,1,96,28H208A20,20,0,0,1,228,48ZM168,192a12,12,0,0,0-12,12H52V108h91.08a12,12,0,0,0,17.41,16.49l20-20a12,12,0,0,0,0-17l-20-20A12,12,0,0,0,143.08,84H48a20,20,0,0,0-20,20V208a20,20,0,0,0,20,20H160a20,20,0,0,0,20-20v-4A12,12,0,0,0,168,192Z" +},null,-1)])])):"duotone"===xm(r)?(Fb(),qb("g",p$,[...t[1]||(t[1]=[Jb("path",{ +d:"M216,48V152a8,8,0,0,1-8,8H168v48a8,8,0,0,1-8,8H48a8,8,0,0,1-8-8V104a8,8,0,0,1,8-8H88V48a8,8,0,0,1,8-8H208A8,8,0,0,1,216,48Z", +opacity:"0.2"},null,-1),Jb("path",{ +d:"M224,48V152a16,16,0,0,1-16,16H99.31l10.35,10.34a8,8,0,0,1-11.32,11.32l-24-24a8,8,0,0,1,0-11.32l24-24a8,8,0,0,1,11.32,11.32L99.31,152H208V48H96v8a8,8,0,0,1-16,0V48A16,16,0,0,1,96,32H208A16,16,0,0,1,224,48ZM168,192a8,8,0,0,0-8,8v8H48V104H156.69l-10.35,10.34a8,8,0,0,0,11.32,11.32l24-24a8,8,0,0,0,0-11.32l-24-24a8,8,0,0,0-11.32,11.32L156.69,88H48a16,16,0,0,0-16,16V208a16,16,0,0,0,16,16H160a16,16,0,0,0,16-16v-8A8,8,0,0,0,168,192Z" +},null,-1)])])):"fill"===xm(r)?(Fb(),qb("g",h$,[...t[2]||(t[2]=[Jb("path",{ +d:"M224,48V152a16,16,0,0,1-16,16H112v16a8,8,0,0,1-13.66,5.66l-24-24a8,8,0,0,1,0-11.32l24-24A8,8,0,0,1,112,136v16h96V48H96v8a8,8,0,0,1-16,0V48A16,16,0,0,1,96,32H208A16,16,0,0,1,224,48ZM168,192a8,8,0,0,0-8,8v8H48V104h96v16a8,8,0,0,0,13.66,5.66l24-24a8,8,0,0,0,0-11.32l-24-24A8,8,0,0,0,144,72V88H48a16,16,0,0,0-16,16V208a16,16,0,0,0,16,16H160a16,16,0,0,0,16-16v-8A8,8,0,0,0,168,192Z" +},null,-1)])])):"light"===xm(r)?(Fb(),qb("g",f$,[...t[3]||(t[3]=[Jb("path",{ +d:"M222,48V152a14,14,0,0,1-14,14H94.49l13.75,13.76a6,6,0,1,1-8.48,8.48l-24-24a6,6,0,0,1,0-8.48l24-24a6,6,0,0,1,8.48,8.48L94.49,154H208a2,2,0,0,0,2-2V48a2,2,0,0,0-2-2H96a2,2,0,0,0-2,2v8a6,6,0,0,1-12,0V48A14,14,0,0,1,96,34H208A14,14,0,0,1,222,48ZM168,194a6,6,0,0,0-6,6v8a2,2,0,0,1-2,2H48a2,2,0,0,1-2-2V104a2,2,0,0,1,2-2H161.51l-13.75,13.76a6,6,0,1,0,8.48,8.48l24-24a6,6,0,0,0,0-8.48l-24-24a6,6,0,0,0-8.48,8.48L161.51,90H48a14,14,0,0,0-14,14V208a14,14,0,0,0,14,14H160a14,14,0,0,0,14-14v-8A6,6,0,0,0,168,194Z" +},null,-1)])])):"regular"===xm(r)?(Fb(),qb("g",m$,[...t[4]||(t[4]=[Jb("path",{ +d:"M224,48V152a16,16,0,0,1-16,16H99.31l10.35,10.34a8,8,0,0,1-11.32,11.32l-24-24a8,8,0,0,1,0-11.32l24-24a8,8,0,0,1,11.32,11.32L99.31,152H208V48H96v8a8,8,0,0,1-16,0V48A16,16,0,0,1,96,32H208A16,16,0,0,1,224,48ZM168,192a8,8,0,0,0-8,8v8H48V104H156.69l-10.35,10.34a8,8,0,0,0,11.32,11.32l24-24a8,8,0,0,0,0-11.32l-24-24a8,8,0,0,0-11.32,11.32L156.69,88H48a16,16,0,0,0-16,16V208a16,16,0,0,0,16,16H160a16,16,0,0,0,16-16v-8A8,8,0,0,0,168,192Z" +},null,-1)])])):"thin"===xm(r)?(Fb(),qb("g",g$,[...t[5]||(t[5]=[Jb("path",{ +d:"M220,48V152a12,12,0,0,1-12,12H89.66l17.17,17.17a4,4,0,0,1-5.66,5.66l-24-24a4,4,0,0,1,0-5.66l24-24a4,4,0,0,1,5.66,5.66L89.66,156H208a4,4,0,0,0,4-4V48a4,4,0,0,0-4-4H96a4,4,0,0,0-4,4v8a4,4,0,0,1-8,0V48A12,12,0,0,1,96,36H208A12,12,0,0,1,220,48ZM168,196a4,4,0,0,0-4,4v8a4,4,0,0,1-4,4H48a4,4,0,0,1-4-4V104a4,4,0,0,1,4-4H166.34l-17.17,17.17a4,4,0,0,0,5.66,5.66l24-24a4,4,0,0,0,0-5.66l-24-24a4,4,0,0,0-5.66,5.66L166.34,92H48a12,12,0,0,0-12,12V208a12,12,0,0,0,12,12H160a12,12,0,0,0,12-12v-8A4,4,0,0,0,168,196Z" +},null,-1)])])):oy("",!0)],16))}}),b$={key:0},y$={key:1},O$={key:2},w$={key:3 +},x$={key:4},k$={key:5},S$=Hg({name:"ScalarIconTag",props:{label:{},weight:{}}, +setup(e){const t=e,{bind:n,weight:r}=E_(t);return(e,t)=>(Fb(),qb("svg",cy({ +xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 256 256",fill:"currentColor" +},xm(n)),[Av(e.$slots,"default"),"bold"===xm(r)?(Fb(), +qb("g",b$,[...t[0]||(t[0]=[Jb("path",{ +d:"M246.15,133.18,146.83,33.86A19.85,19.85,0,0,0,132.69,28H40A12,12,0,0,0,28,40v92.69a19.85,19.85,0,0,0,5.86,14.14l99.32,99.32a20,20,0,0,0,28.28,0l84.69-84.69A20,20,0,0,0,246.15,133.18Zm-98.83,93.17L52,131V52h79l95.32,95.32ZM104,88A16,16,0,1,1,88,72,16,16,0,0,1,104,88Z" +},null,-1)])])):"duotone"===xm(r)?(Fb(),qb("g",y$,[...t[1]||(t[1]=[Jb("path",{ +d:"M237.66,153,153,237.66a8,8,0,0,1-11.31,0L42.34,138.34A8,8,0,0,1,40,132.69V40h92.69a8,8,0,0,1,5.65,2.34l99.32,99.32A8,8,0,0,1,237.66,153Z", +opacity:"0.2"},null,-1),Jb("path",{ +d:"M243.31,136,144,36.69A15.86,15.86,0,0,0,132.69,32H40a8,8,0,0,0-8,8v92.69A15.86,15.86,0,0,0,36.69,144L136,243.31a16,16,0,0,0,22.63,0l84.68-84.68a16,16,0,0,0,0-22.63Zm-96,96L48,132.69V48h84.69L232,147.31ZM96,84A12,12,0,1,1,84,72,12,12,0,0,1,96,84Z" +},null,-1)])])):"fill"===xm(r)?(Fb(),qb("g",O$,[...t[2]||(t[2]=[Jb("path",{ +d:"M243.31,136,144,36.69A15.86,15.86,0,0,0,132.69,32H40a8,8,0,0,0-8,8v92.69A15.86,15.86,0,0,0,36.69,144L136,243.31a16,16,0,0,0,22.63,0l84.68-84.68a16,16,0,0,0,0-22.63ZM84,96A12,12,0,1,1,96,84,12,12,0,0,1,84,96Z" +},null,-1)])])):"light"===xm(r)?(Fb(),qb("g",w$,[...t[3]||(t[3]=[Jb("path",{ +d:"M241.91,137.42,142.59,38.1a13.94,13.94,0,0,0-9.9-4.1H40a6,6,0,0,0-6,6v92.69a13.94,13.94,0,0,0,4.1,9.9l99.32,99.32a14,14,0,0,0,19.8,0l84.69-84.69A14,14,0,0,0,241.91,137.42Zm-8.49,11.31-84.69,84.69a2,2,0,0,1-2.83,0L46.59,134.1a2,2,0,0,1-.59-1.41V46h86.69a2,2,0,0,1,1.41.59l99.32,99.31A2,2,0,0,1,233.42,148.73ZM94,84A10,10,0,1,1,84,74,10,10,0,0,1,94,84Z" +},null,-1)])])):"regular"===xm(r)?(Fb(),qb("g",x$,[...t[4]||(t[4]=[Jb("path",{ +d:"M243.31,136,144,36.69A15.86,15.86,0,0,0,132.69,32H40a8,8,0,0,0-8,8v92.69A15.86,15.86,0,0,0,36.69,144L136,243.31a16,16,0,0,0,22.63,0l84.68-84.68a16,16,0,0,0,0-22.63Zm-96,96L48,132.69V48h84.69L232,147.31ZM96,84A12,12,0,1,1,84,72,12,12,0,0,1,96,84Z" +},null,-1)])])):"thin"===xm(r)?(Fb(),qb("g",k$,[...t[5]||(t[5]=[Jb("path",{ +d:"M240.49,138.83,141.17,39.51A11.93,11.93,0,0,0,132.69,36H40a4,4,0,0,0-4,4v92.69a11.93,11.93,0,0,0,3.51,8.48l99.32,99.32a12,12,0,0,0,17,0l84.69-84.69a12,12,0,0,0,0-17Zm-5.66,11.31-84.69,84.69a4,4,0,0,1-5.65,0L45.17,135.51A4,4,0,0,1,44,132.69V44h88.69a4,4,0,0,1,2.82,1.17l99.32,99.32A4,4,0,0,1,234.83,150.14ZM92,84a8,8,0,1,1-8-8A8,8,0,0,1,92,84Z" +},null,-1)])])):oy("",!0)],16))}}),_$={key:0},A$={key:1},T$={key:2},E$={key:3 +},C$={key:4},$$={key:5},P$=Hg({name:"ScalarIconTerminalWindow",props:{label:{}, +weight:{}},setup(e){const t=e,{bind:n,weight:r}=E_(t) +;return(e,t)=>(Fb(),qb("svg",cy({xmlns:"http://www.w3.org/2000/svg", +viewBox:"0 0 256 256",fill:"currentColor" +},xm(n)),[Av(e.$slots,"default"),"bold"===xm(r)?(Fb(), +qb("g",_$,[...t[0]||(t[0]=[Jb("path",{ +d:"M72.5,150.63,100.79,128,72.5,105.37a12,12,0,1,1,15-18.74l40,32a12,12,0,0,1,0,18.74l-40,32a12,12,0,0,1-15-18.74ZM144,172h32a12,12,0,0,0,0-24H144a12,12,0,0,0,0,24ZM236,56V200a20,20,0,0,1-20,20H40a20,20,0,0,1-20-20V56A20,20,0,0,1,40,36H216A20,20,0,0,1,236,56Zm-24,4H44V196H212Z" +},null,-1)])])):"duotone"===xm(r)?(Fb(),qb("g",A$,[...t[1]||(t[1]=[Jb("path",{ +d:"M224,56V200a8,8,0,0,1-8,8H40a8,8,0,0,1-8-8V56a8,8,0,0,1,8-8H216A8,8,0,0,1,224,56Z", +opacity:"0.2"},null,-1),Jb("path",{ +d:"M128,128a8,8,0,0,1-3,6.25l-40,32a8,8,0,1,1-10-12.5L107.19,128,75,102.25a8,8,0,1,1,10-12.5l40,32A8,8,0,0,1,128,128Zm48,24H136a8,8,0,0,0,0,16h40a8,8,0,0,0,0-16Zm56-96V200a16,16,0,0,1-16,16H40a16,16,0,0,1-16-16V56A16,16,0,0,1,40,40H216A16,16,0,0,1,232,56ZM216,200V56H40V200H216Z" +},null,-1)])])):"fill"===xm(r)?(Fb(),qb("g",T$,[...t[2]||(t[2]=[Jb("path",{ +d:"M216,40H40A16,16,0,0,0,24,56V200a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V56A16,16,0,0,0,216,40Zm-91,94.25-40,32a8,8,0,1,1-10-12.5L107.19,128,75,102.25a8,8,0,1,1,10-12.5l40,32a8,8,0,0,1,0,12.5ZM176,168H136a8,8,0,0,1,0-16h40a8,8,0,0,1,0,16Z" +},null,-1)])])):"light"===xm(r)?(Fb(),qb("g",E$,[...t[3]||(t[3]=[Jb("path",{ +d:"M126,128a6,6,0,0,1-2.25,4.69l-40,32a6,6,0,0,1-7.5-9.38L110.4,128,76.25,100.69a6,6,0,1,1,7.5-9.38l40,32A6,6,0,0,1,126,128Zm50,26H136a6,6,0,0,0,0,12h40a6,6,0,0,0,0-12Zm54-98V200a14,14,0,0,1-14,14H40a14,14,0,0,1-14-14V56A14,14,0,0,1,40,42H216A14,14,0,0,1,230,56Zm-12,0a2,2,0,0,0-2-2H40a2,2,0,0,0-2,2V200a2,2,0,0,0,2,2H216a2,2,0,0,0,2-2Z" +},null,-1)])])):"regular"===xm(r)?(Fb(),qb("g",C$,[...t[4]||(t[4]=[Jb("path",{ +d:"M128,128a8,8,0,0,1-3,6.25l-40,32a8,8,0,1,1-10-12.5L107.19,128,75,102.25a8,8,0,1,1,10-12.5l40,32A8,8,0,0,1,128,128Zm48,24H136a8,8,0,0,0,0,16h40a8,8,0,0,0,0-16Zm56-96V200a16,16,0,0,1-16,16H40a16,16,0,0,1-16-16V56A16,16,0,0,1,40,40H216A16,16,0,0,1,232,56ZM216,200V56H40V200H216Z" +},null,-1)])])):"thin"===xm(r)?(Fb(),qb("g",$$,[...t[5]||(t[5]=[Jb("path",{ +d:"M122.5,124.88a4,4,0,0,1,0,6.24l-40,32a4,4,0,0,1-5-6.24L113.6,128,77.5,99.12a4,4,0,0,1,5-6.24ZM176,156H136a4,4,0,0,0,0,8h40a4,4,0,0,0,0-8ZM228,56V200a12,12,0,0,1-12,12H40a12,12,0,0,1-12-12V56A12,12,0,0,1,40,44H216A12,12,0,0,1,228,56Zm-8,0a4,4,0,0,0-4-4H40a4,4,0,0,0-4,4V200a4,4,0,0,0,4,4H216a4,4,0,0,0,4-4Z" +},null,-1)])])):oy("",!0)],16))}}),I$={key:0},D$={key:1},M$={key:2},N$={key:3 +},R$={key:4},L$={key:5},B$=Hg({name:"ScalarIconTextAlignLeft",props:{label:{}, +weight:{}},setup(e){const t=e,{bind:n,weight:r}=E_(t) +;return(e,t)=>(Fb(),qb("svg",cy({xmlns:"http://www.w3.org/2000/svg", +viewBox:"0 0 256 256",fill:"currentColor" +},xm(n)),[Av(e.$slots,"default"),"bold"===xm(r)?(Fb(), +qb("g",I$,[...t[0]||(t[0]=[Jb("path",{ +d:"M28,64A12,12,0,0,1,40,52H216a12,12,0,0,1,0,24H40A12,12,0,0,1,28,64Zm12,52H168a12,12,0,0,0,0-24H40a12,12,0,0,0,0,24Zm176,16H40a12,12,0,0,0,0,24H216a12,12,0,0,0,0-24Zm-48,40H40a12,12,0,0,0,0,24H168a12,12,0,0,0,0-24Z" +},null,-1)])])):"duotone"===xm(r)?(Fb(),qb("g",D$,[...t[1]||(t[1]=[Jb("path",{ +d:"M216,64V168a16,16,0,0,1-16,16H40V64Z",opacity:"0.2"},null,-1),Jb("path",{ +d:"M32,64a8,8,0,0,1,8-8H216a8,8,0,0,1,0,16H40A8,8,0,0,1,32,64Zm8,48H168a8,8,0,0,0,0-16H40a8,8,0,0,0,0,16Zm176,24H40a8,8,0,0,0,0,16H216a8,8,0,0,0,0-16Zm-48,40H40a8,8,0,0,0,0,16H168a8,8,0,0,0,0-16Z" +},null,-1)])])):"fill"===xm(r)?(Fb(),qb("g",M$,[...t[2]||(t[2]=[Jb("path",{ +d:"M208,32H48A16,16,0,0,0,32,48V208a16,16,0,0,0,16,16H208a16,16,0,0,0,16-16V48A16,16,0,0,0,208,32ZM160,184H64a8,8,0,0,1,0-16h96a8,8,0,0,1,0,16Zm32-32H64a8,8,0,0,1,0-16H192a8,8,0,0,1,0,16ZM56,112a8,8,0,0,1,8-8h96a8,8,0,0,1,0,16H64A8,8,0,0,1,56,112ZM192,88H64a8,8,0,0,1,0-16H192a8,8,0,0,1,0,16Z" +},null,-1)])])):"light"===xm(r)?(Fb(),qb("g",N$,[...t[3]||(t[3]=[Jb("path",{ +d:"M34,64a6,6,0,0,1,6-6H216a6,6,0,0,1,0,12H40A6,6,0,0,1,34,64Zm6,46H168a6,6,0,0,0,0-12H40a6,6,0,0,0,0,12Zm176,28H40a6,6,0,0,0,0,12H216a6,6,0,0,0,0-12Zm-48,40H40a6,6,0,0,0,0,12H168a6,6,0,0,0,0-12Z" +},null,-1)])])):"regular"===xm(r)?(Fb(),qb("g",R$,[...t[4]||(t[4]=[Jb("path",{ +d:"M32,64a8,8,0,0,1,8-8H216a8,8,0,0,1,0,16H40A8,8,0,0,1,32,64Zm8,48H168a8,8,0,0,0,0-16H40a8,8,0,0,0,0,16Zm176,24H40a8,8,0,0,0,0,16H216a8,8,0,0,0,0-16Zm-48,40H40a8,8,0,0,0,0,16H168a8,8,0,0,0,0-16Z" +},null,-1)])])):"thin"===xm(r)?(Fb(),qb("g",L$,[...t[5]||(t[5]=[Jb("path",{ +d:"M36,64a4,4,0,0,1,4-4H216a4,4,0,0,1,0,8H40A4,4,0,0,1,36,64Zm4,44H168a4,4,0,0,0,0-8H40a4,4,0,0,0,0,8Zm176,32H40a4,4,0,0,0,0,8H216a4,4,0,0,0,0-8Zm-48,40H40a4,4,0,0,0,0,8H168a4,4,0,0,0,0-8Z" +},null,-1)])])):oy("",!0)],16))}}),j$={key:0},U$={key:1},z$={key:2},Z$={key:3 +},F$={key:4},H$={key:5},Q$=Hg({name:"ScalarIconTrash",props:{label:{},weight:{} +},setup(e){const t=e,{bind:n,weight:r}=E_(t);return(e,t)=>(Fb(),qb("svg",cy({ +xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 256 256",fill:"currentColor" +},xm(n)),[Av(e.$slots,"default"),"bold"===xm(r)?(Fb(), +qb("g",j$,[...t[0]||(t[0]=[Jb("path",{ +d:"M216,48H180V36A28,28,0,0,0,152,8H104A28,28,0,0,0,76,36V48H40a12,12,0,0,0,0,24h4V208a20,20,0,0,0,20,20H192a20,20,0,0,0,20-20V72h4a12,12,0,0,0,0-24ZM100,36a4,4,0,0,1,4-4h48a4,4,0,0,1,4,4V48H100Zm88,168H68V72H188ZM116,104v64a12,12,0,0,1-24,0V104a12,12,0,0,1,24,0Zm48,0v64a12,12,0,0,1-24,0V104a12,12,0,0,1,24,0Z" +},null,-1)])])):"duotone"===xm(r)?(Fb(),qb("g",U$,[...t[1]||(t[1]=[Jb("path",{ +d:"M200,56V208a8,8,0,0,1-8,8H64a8,8,0,0,1-8-8V56Z",opacity:"0.2" +},null,-1),Jb("path",{ +d:"M216,48H176V40a24,24,0,0,0-24-24H104A24,24,0,0,0,80,40v8H40a8,8,0,0,0,0,16h8V208a16,16,0,0,0,16,16H192a16,16,0,0,0,16-16V64h8a8,8,0,0,0,0-16ZM96,40a8,8,0,0,1,8-8h48a8,8,0,0,1,8,8v8H96Zm96,168H64V64H192ZM112,104v64a8,8,0,0,1-16,0V104a8,8,0,0,1,16,0Zm48,0v64a8,8,0,0,1-16,0V104a8,8,0,0,1,16,0Z" +},null,-1)])])):"fill"===xm(r)?(Fb(),qb("g",z$,[...t[2]||(t[2]=[Jb("path",{ +d:"M216,48H176V40a24,24,0,0,0-24-24H104A24,24,0,0,0,80,40v8H40a8,8,0,0,0,0,16h8V208a16,16,0,0,0,16,16H192a16,16,0,0,0,16-16V64h8a8,8,0,0,0,0-16ZM112,168a8,8,0,0,1-16,0V104a8,8,0,0,1,16,0Zm48,0a8,8,0,0,1-16,0V104a8,8,0,0,1,16,0Zm0-120H96V40a8,8,0,0,1,8-8h48a8,8,0,0,1,8,8Z" +},null,-1)])])):"light"===xm(r)?(Fb(),qb("g",Z$,[...t[3]||(t[3]=[Jb("path",{ +d:"M216,50H174V40a22,22,0,0,0-22-22H104A22,22,0,0,0,82,40V50H40a6,6,0,0,0,0,12H50V208a14,14,0,0,0,14,14H192a14,14,0,0,0,14-14V62h10a6,6,0,0,0,0-12ZM94,40a10,10,0,0,1,10-10h48a10,10,0,0,1,10,10V50H94ZM194,208a2,2,0,0,1-2,2H64a2,2,0,0,1-2-2V62H194ZM110,104v64a6,6,0,0,1-12,0V104a6,6,0,0,1,12,0Zm48,0v64a6,6,0,0,1-12,0V104a6,6,0,0,1,12,0Z" +},null,-1)])])):"regular"===xm(r)?(Fb(),qb("g",F$,[...t[4]||(t[4]=[Jb("path",{ +d:"M216,48H176V40a24,24,0,0,0-24-24H104A24,24,0,0,0,80,40v8H40a8,8,0,0,0,0,16h8V208a16,16,0,0,0,16,16H192a16,16,0,0,0,16-16V64h8a8,8,0,0,0,0-16ZM96,40a8,8,0,0,1,8-8h48a8,8,0,0,1,8,8v8H96Zm96,168H64V64H192ZM112,104v64a8,8,0,0,1-16,0V104a8,8,0,0,1,16,0Zm48,0v64a8,8,0,0,1-16,0V104a8,8,0,0,1,16,0Z" +},null,-1)])])):"thin"===xm(r)?(Fb(),qb("g",H$,[...t[5]||(t[5]=[Jb("path",{ +d:"M216,52H172V40a20,20,0,0,0-20-20H104A20,20,0,0,0,84,40V52H40a4,4,0,0,0,0,8H52V208a12,12,0,0,0,12,12H192a12,12,0,0,0,12-12V60h12a4,4,0,0,0,0-8ZM92,40a12,12,0,0,1,12-12h48a12,12,0,0,1,12,12V52H92ZM196,208a4,4,0,0,1-4,4H64a4,4,0,0,1-4-4V60H196ZM108,104v64a4,4,0,0,1-8,0V104a4,4,0,0,1,8,0Zm48,0v64a4,4,0,0,1-8,0V104a4,4,0,0,1,8,0Z" +},null,-1)])])):oy("",!0)],16))}}),V$={key:0},q$={key:1},W$={key:2},X$={key:3 +},G$={key:4},Y$={key:5},K$=Hg({name:"ScalarIconUpload",props:{label:{},weight:{} +},setup(e){const t=e,{bind:n,weight:r}=E_(t);return(e,t)=>(Fb(),qb("svg",cy({ +xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 256 256",fill:"currentColor" +},xm(n)),[Av(e.$slots,"default"),"bold"===xm(r)?(Fb(), +qb("g",V$,[...t[0]||(t[0]=[Jb("path",{ +d:"M188,184a16,16,0,1,1,16-16A16,16,0,0,1,188,184Zm36-68H180a12,12,0,0,0,0,24h40v56H36V140H76a12,12,0,0,0,0-24H32a20,20,0,0,0-20,20v64a20,20,0,0,0,20,20H224a20,20,0,0,0,20-20V136A20,20,0,0,0,224,116ZM88.49,80.49,116,53v75a12,12,0,0,0,24,0V53l27.51,27.52a12,12,0,1,0,17-17l-48-48a12,12,0,0,0-17,0l-48,48a12,12,0,1,0,17,17Z" +},null,-1)])])):"duotone"===xm(r)?(Fb(),qb("g",q$,[...t[1]||(t[1]=[Jb("path",{ +d:"M232,136v64a8,8,0,0,1-8,8H32a8,8,0,0,1-8-8V136a8,8,0,0,1,8-8H224A8,8,0,0,1,232,136Z", +opacity:"0.2"},null,-1),Jb("path",{ +d:"M240,136v64a16,16,0,0,1-16,16H32a16,16,0,0,1-16-16V136a16,16,0,0,1,16-16H80a8,8,0,0,1,0,16H32v64H224V136H176a8,8,0,0,1,0-16h48A16,16,0,0,1,240,136ZM85.66,77.66,120,43.31V128a8,8,0,0,0,16,0V43.31l34.34,34.35a8,8,0,0,0,11.32-11.32l-48-48a8,8,0,0,0-11.32,0l-48,48A8,8,0,0,0,85.66,77.66ZM200,168a12,12,0,1,0-12,12A12,12,0,0,0,200,168Z" +},null,-1)])])):"fill"===xm(r)?(Fb(),qb("g",W$,[...t[2]||(t[2]=[Jb("path",{ +d:"M74.34,77.66a8,8,0,0,1,0-11.32l48-48a8,8,0,0,1,11.32,0l48,48a8,8,0,0,1-11.32,11.32L136,43.31V128a8,8,0,0,1-16,0V43.31L85.66,77.66A8,8,0,0,1,74.34,77.66ZM240,136v64a16,16,0,0,1-16,16H32a16,16,0,0,1-16-16V136a16,16,0,0,1,16-16h68a4,4,0,0,1,4,4v3.46c0,13.45,11,24.79,24.46,24.54A24,24,0,0,0,152,128v-4a4,4,0,0,1,4-4h68A16,16,0,0,1,240,136Zm-40,32a12,12,0,1,0-12,12A12,12,0,0,0,200,168Z" +},null,-1)])])):"light"===xm(r)?(Fb(),qb("g",X$,[...t[3]||(t[3]=[Jb("path",{ +d:"M238,136v64a14,14,0,0,1-14,14H32a14,14,0,0,1-14-14V136a14,14,0,0,1,14-14H80a6,6,0,0,1,0,12H32a2,2,0,0,0-2,2v64a2,2,0,0,0,2,2H224a2,2,0,0,0,2-2V136a2,2,0,0,0-2-2H176a6,6,0,0,1,0-12h48A14,14,0,0,1,238,136ZM84.24,76.24,122,38.49V128a6,6,0,0,0,12,0V38.49l37.76,37.75a6,6,0,0,0,8.48-8.48l-48-48a6,6,0,0,0-8.48,0l-48,48a6,6,0,0,0,8.48,8.48ZM198,168a10,10,0,1,0-10,10A10,10,0,0,0,198,168Z" +},null,-1)])])):"regular"===xm(r)?(Fb(),qb("g",G$,[...t[4]||(t[4]=[Jb("path",{ +d:"M240,136v64a16,16,0,0,1-16,16H32a16,16,0,0,1-16-16V136a16,16,0,0,1,16-16H80a8,8,0,0,1,0,16H32v64H224V136H176a8,8,0,0,1,0-16h48A16,16,0,0,1,240,136ZM85.66,77.66,120,43.31V128a8,8,0,0,0,16,0V43.31l34.34,34.35a8,8,0,0,0,11.32-11.32l-48-48a8,8,0,0,0-11.32,0l-48,48A8,8,0,0,0,85.66,77.66ZM200,168a12,12,0,1,0-12,12A12,12,0,0,0,200,168Z" +},null,-1)])])):"thin"===xm(r)?(Fb(),qb("g",Y$,[...t[5]||(t[5]=[Jb("path",{ +d:"M236,136v64a12,12,0,0,1-12,12H32a12,12,0,0,1-12-12V136a12,12,0,0,1,12-12H80a4,4,0,0,1,0,8H32a4,4,0,0,0-4,4v64a4,4,0,0,0,4,4H224a4,4,0,0,0,4-4V136a4,4,0,0,0-4-4H176a4,4,0,0,1,0-8h48A12,12,0,0,1,236,136ZM82.83,74.83,124,33.66V128a4,4,0,0,0,8,0V33.66l41.17,41.17a4,4,0,1,0,5.66-5.66l-48-48a4,4,0,0,0-5.66,0l-48,48a4,4,0,0,0,5.66,5.66ZM196,168a8,8,0,1,0-8,8A8,8,0,0,0,196,168Z" +},null,-1)])])):oy("",!0)],16))}}),J$={key:0},eP={key:1},tP={key:2},nP={key:3 +},rP={key:4},aP={key:5},oP=Hg({name:"ScalarIconWarning",props:{label:{}, +weight:{}},setup(e){const t=e,{bind:n,weight:r}=E_(t) +;return(e,t)=>(Fb(),qb("svg",cy({xmlns:"http://www.w3.org/2000/svg", +viewBox:"0 0 256 256",fill:"currentColor" +},xm(n)),[Av(e.$slots,"default"),"bold"===xm(r)?(Fb(), +qb("g",J$,[...t[0]||(t[0]=[Jb("path",{ +d:"M240.26,186.1,152.81,34.23h0a28.74,28.74,0,0,0-49.62,0L15.74,186.1a27.45,27.45,0,0,0,0,27.71A28.31,28.31,0,0,0,40.55,228h174.9a28.31,28.31,0,0,0,24.79-14.19A27.45,27.45,0,0,0,240.26,186.1Zm-20.8,15.7a4.46,4.46,0,0,1-4,2.2H40.55a4.46,4.46,0,0,1-4-2.2,3.56,3.56,0,0,1,0-3.73L124,46.2a4.77,4.77,0,0,1,8,0l87.44,151.87A3.56,3.56,0,0,1,219.46,201.8ZM116,136V104a12,12,0,0,1,24,0v32a12,12,0,0,1-24,0Zm28,40a16,16,0,1,1-16-16A16,16,0,0,1,144,176Z" +},null,-1)])])):"duotone"===xm(r)?(Fb(),qb("g",eP,[...t[1]||(t[1]=[Jb("path",{ +d:"M215.46,216H40.54C27.92,216,20,202.79,26.13,192.09L113.59,40.22c6.3-11,22.52-11,28.82,0l87.46,151.87C236,202.79,228.08,216,215.46,216Z", +opacity:"0.2"},null,-1),Jb("path",{ +d:"M236.8,188.09,149.35,36.22h0a24.76,24.76,0,0,0-42.7,0L19.2,188.09a23.51,23.51,0,0,0,0,23.72A24.35,24.35,0,0,0,40.55,224h174.9a24.35,24.35,0,0,0,21.33-12.19A23.51,23.51,0,0,0,236.8,188.09ZM222.93,203.8a8.5,8.5,0,0,1-7.48,4.2H40.55a8.5,8.5,0,0,1-7.48-4.2,7.59,7.59,0,0,1,0-7.72L120.52,44.21a8.75,8.75,0,0,1,15,0l87.45,151.87A7.59,7.59,0,0,1,222.93,203.8ZM120,144V104a8,8,0,0,1,16,0v40a8,8,0,0,1-16,0Zm20,36a12,12,0,1,1-12-12A12,12,0,0,1,140,180Z" +},null,-1)])])):"fill"===xm(r)?(Fb(),qb("g",tP,[...t[2]||(t[2]=[Jb("path",{ +d:"M236.8,188.09,149.35,36.22h0a24.76,24.76,0,0,0-42.7,0L19.2,188.09a23.51,23.51,0,0,0,0,23.72A24.35,24.35,0,0,0,40.55,224h174.9a24.35,24.35,0,0,0,21.33-12.19A23.51,23.51,0,0,0,236.8,188.09ZM120,104a8,8,0,0,1,16,0v40a8,8,0,0,1-16,0Zm8,88a12,12,0,1,1,12-12A12,12,0,0,1,128,192Z" +},null,-1)])])):"light"===xm(r)?(Fb(),qb("g",nP,[...t[3]||(t[3]=[Jb("path",{ +d:"M235.07,189.09,147.61,37.22h0a22.75,22.75,0,0,0-39.22,0L20.93,189.09a21.53,21.53,0,0,0,0,21.72A22.35,22.35,0,0,0,40.55,222h174.9a22.35,22.35,0,0,0,19.6-11.19A21.53,21.53,0,0,0,235.07,189.09ZM224.66,204.8a10.46,10.46,0,0,1-9.21,5.2H40.55a10.46,10.46,0,0,1-9.21-5.2,9.51,9.51,0,0,1,0-9.72L118.79,43.21a10.75,10.75,0,0,1,18.42,0l87.46,151.87A9.51,9.51,0,0,1,224.66,204.8ZM122,144V104a6,6,0,0,1,12,0v40a6,6,0,0,1-12,0Zm16,36a10,10,0,1,1-10-10A10,10,0,0,1,138,180Z" +},null,-1)])])):"regular"===xm(r)?(Fb(),qb("g",rP,[...t[4]||(t[4]=[Jb("path",{ +d:"M236.8,188.09,149.35,36.22h0a24.76,24.76,0,0,0-42.7,0L19.2,188.09a23.51,23.51,0,0,0,0,23.72A24.35,24.35,0,0,0,40.55,224h174.9a24.35,24.35,0,0,0,21.33-12.19A23.51,23.51,0,0,0,236.8,188.09ZM222.93,203.8a8.5,8.5,0,0,1-7.48,4.2H40.55a8.5,8.5,0,0,1-7.48-4.2,7.59,7.59,0,0,1,0-7.72L120.52,44.21a8.75,8.75,0,0,1,15,0l87.45,151.87A7.59,7.59,0,0,1,222.93,203.8ZM120,144V104a8,8,0,0,1,16,0v40a8,8,0,0,1-16,0Zm20,36a12,12,0,1,1-12-12A12,12,0,0,1,140,180Z" +},null,-1)])])):"thin"===xm(r)?(Fb(),qb("g",aP,[...t[5]||(t[5]=[Jb("path",{ +d:"M233.34,190.09,145.88,38.22h0a20.75,20.75,0,0,0-35.76,0L22.66,190.09a19.52,19.52,0,0,0,0,19.71A20.36,20.36,0,0,0,40.54,220H215.46a20.36,20.36,0,0,0,17.86-10.2A19.52,19.52,0,0,0,233.34,190.09ZM226.4,205.8a12.47,12.47,0,0,1-10.94,6.2H40.54a12.47,12.47,0,0,1-10.94-6.2,11.45,11.45,0,0,1,0-11.72L117.05,42.21a12.76,12.76,0,0,1,21.9,0L226.4,194.08A11.45,11.45,0,0,1,226.4,205.8ZM124,144V104a4,4,0,0,1,8,0v40a4,4,0,0,1-8,0Zm12,36a8,8,0,1,1-8-8A8,8,0,0,1,136,180Z" +},null,-1)])])):oy("",!0)],16))}}),iP={key:0},sP={key:1},lP={key:2},cP={key:3 +},uP={key:4},dP={key:5},pP=Hg({name:"ScalarIconWarningCircle",props:{label:{}, +weight:{}},setup(e){const t=e,{bind:n,weight:r}=E_(t) +;return(e,t)=>(Fb(),qb("svg",cy({xmlns:"http://www.w3.org/2000/svg", +viewBox:"0 0 256 256",fill:"currentColor" +},xm(n)),[Av(e.$slots,"default"),"bold"===xm(r)?(Fb(), +qb("g",iP,[...t[0]||(t[0]=[Jb("path",{ +d:"M128,20A108,108,0,1,0,236,128,108.12,108.12,0,0,0,128,20Zm0,192a84,84,0,1,1,84-84A84.09,84.09,0,0,1,128,212Zm-12-80V80a12,12,0,0,1,24,0v52a12,12,0,0,1-24,0Zm28,40a16,16,0,1,1-16-16A16,16,0,0,1,144,172Z" +},null,-1)])])):"duotone"===xm(r)?(Fb(),qb("g",sP,[...t[1]||(t[1]=[Jb("path",{ +d:"M224,128a96,96,0,1,1-96-96A96,96,0,0,1,224,128Z",opacity:"0.2" +},null,-1),Jb("path",{ +d:"M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Zm-8-80V80a8,8,0,0,1,16,0v56a8,8,0,0,1-16,0Zm20,36a12,12,0,1,1-12-12A12,12,0,0,1,140,172Z" +},null,-1)])])):"fill"===xm(r)?(Fb(),qb("g",lP,[...t[2]||(t[2]=[Jb("path",{ +d:"M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm-8,56a8,8,0,0,1,16,0v56a8,8,0,0,1-16,0Zm8,104a12,12,0,1,1,12-12A12,12,0,0,1,128,184Z" +},null,-1)])])):"light"===xm(r)?(Fb(),qb("g",cP,[...t[3]||(t[3]=[Jb("path",{ +d:"M128,26A102,102,0,1,0,230,128,102.12,102.12,0,0,0,128,26Zm0,192a90,90,0,1,1,90-90A90.1,90.1,0,0,1,128,218Zm-6-82V80a6,6,0,0,1,12,0v56a6,6,0,0,1-12,0Zm16,36a10,10,0,1,1-10-10A10,10,0,0,1,138,172Z" +},null,-1)])])):"regular"===xm(r)?(Fb(),qb("g",uP,[...t[4]||(t[4]=[Jb("path",{ +d:"M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Zm-8-80V80a8,8,0,0,1,16,0v56a8,8,0,0,1-16,0Zm20,36a12,12,0,1,1-12-12A12,12,0,0,1,140,172Z" +},null,-1)])])):"thin"===xm(r)?(Fb(),qb("g",dP,[...t[5]||(t[5]=[Jb("path",{ +d:"M128,28A100,100,0,1,0,228,128,100.11,100.11,0,0,0,128,28Zm0,192a92,92,0,1,1,92-92A92.1,92.1,0,0,1,128,220Zm-4-84V80a4,4,0,0,1,8,0v56a4,4,0,0,1-8,0Zm12,36a8,8,0,1,1-8-8A8,8,0,0,1,136,172Z" +},null,-1)])])):oy("",!0)],16))}}),hP={key:0},fP={key:1},mP={key:2},gP={key:3 +},vP={key:4},bP={key:5},yP=Hg({name:"ScalarIconWarningOctagon",props:{label:{}, +weight:{}},setup(e){const t=e,{bind:n,weight:r}=E_(t) +;return(e,t)=>(Fb(),qb("svg",cy({xmlns:"http://www.w3.org/2000/svg", +viewBox:"0 0 256 256",fill:"currentColor" +},xm(n)),[Av(e.$slots,"default"),"bold"===xm(r)?(Fb(), +qb("g",hP,[...t[0]||(t[0]=[Jb("path",{ +d:"M116,132V80a12,12,0,0,1,24,0v52a12,12,0,0,1-24,0ZM236,91.55v72.9a19.86,19.86,0,0,1-5.86,14.14l-51.55,51.55A19.85,19.85,0,0,1,164.45,236H91.55a19.85,19.85,0,0,1-14.14-5.86L25.86,178.59A19.86,19.86,0,0,1,20,164.45V91.55a19.86,19.86,0,0,1,5.86-14.14L77.41,25.86A19.85,19.85,0,0,1,91.55,20h72.9a19.85,19.85,0,0,1,14.14,5.86l51.55,51.55A19.86,19.86,0,0,1,236,91.55Zm-24,1.66L162.79,44H93.21L44,93.21v69.58L93.21,212h69.58L212,162.79ZM128,156a16,16,0,1,0,16,16A16,16,0,0,0,128,156Z" +},null,-1)])])):"duotone"===xm(r)?(Fb(),qb("g",fP,[...t[1]||(t[1]=[Jb("path",{ +d:"M224,91.55v72.9a8,8,0,0,1-2.34,5.66l-51.55,51.55a8,8,0,0,1-5.66,2.34H91.55a8,8,0,0,1-5.66-2.34L34.34,170.11A8,8,0,0,1,32,164.45V91.55a8,8,0,0,1,2.34-5.66L85.89,34.34A8,8,0,0,1,91.55,32h72.9a8,8,0,0,1,5.66,2.34l51.55,51.55A8,8,0,0,1,224,91.55Z", +opacity:"0.2"},null,-1),Jb("path",{ +d:"M120,136V80a8,8,0,0,1,16,0v56a8,8,0,0,1-16,0ZM232,91.55v72.9a15.86,15.86,0,0,1-4.69,11.31l-51.55,51.55A15.86,15.86,0,0,1,164.45,232H91.55a15.86,15.86,0,0,1-11.31-4.69L28.69,175.76A15.86,15.86,0,0,1,24,164.45V91.55a15.86,15.86,0,0,1,4.69-11.31L80.24,28.69A15.86,15.86,0,0,1,91.55,24h72.9a15.86,15.86,0,0,1,11.31,4.69l51.55,51.55A15.86,15.86,0,0,1,232,91.55Zm-16,0L164.45,40H91.55L40,91.55v72.9L91.55,216h72.9L216,164.45ZM128,160a12,12,0,1,0,12,12A12,12,0,0,0,128,160Z" +},null,-1)])])):"fill"===xm(r)?(Fb(),qb("g",mP,[...t[2]||(t[2]=[Jb("path",{ +d:"M227.31,80.23,175.77,28.69A16.13,16.13,0,0,0,164.45,24H91.55a16.13,16.13,0,0,0-11.32,4.69L28.69,80.23A16.13,16.13,0,0,0,24,91.55v72.9a16.13,16.13,0,0,0,4.69,11.32l51.54,51.54A16.13,16.13,0,0,0,91.55,232h72.9a16.13,16.13,0,0,0,11.32-4.69l51.54-51.54A16.13,16.13,0,0,0,232,164.45V91.55A16.13,16.13,0,0,0,227.31,80.23ZM120,80a8,8,0,0,1,16,0v56a8,8,0,0,1-16,0Zm8,104a12,12,0,1,1,12-12A12,12,0,0,1,128,184Z" +},null,-1)])])):"light"===xm(r)?(Fb(),qb("g",gP,[...t[3]||(t[3]=[Jb("path",{ +d:"M122,136V80a6,6,0,0,1,12,0v56a6,6,0,0,1-12,0ZM230,91.55v72.9a13.92,13.92,0,0,1-4.1,9.9L174.35,225.9a13.92,13.92,0,0,1-9.9,4.1H91.55a13.92,13.92,0,0,1-9.9-4.1L30.1,174.35a13.92,13.92,0,0,1-4.1-9.9V91.55a13.92,13.92,0,0,1,4.1-9.9L81.65,30.1a13.92,13.92,0,0,1,9.9-4.1h72.9a13.92,13.92,0,0,1,9.9,4.1L225.9,81.65A13.92,13.92,0,0,1,230,91.55Zm-12,0a2,2,0,0,0-.59-1.42L165.87,38.59a2,2,0,0,0-1.42-.59H91.55a2,2,0,0,0-1.41.59L38.58,90.13A2,2,0,0,0,38,91.55v72.9a2,2,0,0,0,.59,1.42l51.54,51.54a2,2,0,0,0,1.42.59h72.9a2,2,0,0,0,1.41-.59l51.56-51.54a2,2,0,0,0,.58-1.42ZM128,162a10,10,0,1,0,10,10A10,10,0,0,0,128,162Z" +},null,-1)])])):"regular"===xm(r)?(Fb(),qb("g",vP,[...t[4]||(t[4]=[Jb("path",{ +d:"M120,136V80a8,8,0,0,1,16,0v56a8,8,0,0,1-16,0ZM232,91.55v72.9a15.86,15.86,0,0,1-4.69,11.31l-51.55,51.55A15.86,15.86,0,0,1,164.45,232H91.55a15.86,15.86,0,0,1-11.31-4.69L28.69,175.76A15.86,15.86,0,0,1,24,164.45V91.55a15.86,15.86,0,0,1,4.69-11.31L80.24,28.69A15.86,15.86,0,0,1,91.55,24h72.9a15.86,15.86,0,0,1,11.31,4.69l51.55,51.55A15.86,15.86,0,0,1,232,91.55Zm-16,0L164.45,40H91.55L40,91.55v72.9L91.55,216h72.9L216,164.45ZM128,160a12,12,0,1,0,12,12A12,12,0,0,0,128,160Z" +},null,-1)])])):"thin"===xm(r)?(Fb(),qb("g",bP,[...t[5]||(t[5]=[Jb("path",{ +d:"M124,136V80a4,4,0,0,1,8,0v56a4,4,0,0,1-8,0ZM228,91.55v72.9a12,12,0,0,1-3.51,8.49l-51.55,51.55a12,12,0,0,1-8.49,3.51H91.55a12,12,0,0,1-8.49-3.51L31.51,172.94A12,12,0,0,1,28,164.45V91.55a12,12,0,0,1,3.51-8.49L83.06,31.51A12,12,0,0,1,91.55,28h72.9a12,12,0,0,1,8.49,3.51l51.55,51.55A12,12,0,0,1,228,91.55Zm-8,0a4,4,0,0,0-1.17-2.83L167.28,37.17A4.06,4.06,0,0,0,164.45,36H91.55a4.06,4.06,0,0,0-2.83,1.17L37.17,88.72A4,4,0,0,0,36,91.55v72.9a4,4,0,0,0,1.17,2.83l51.55,51.55A4.06,4.06,0,0,0,91.55,220h72.9a4.06,4.06,0,0,0,2.83-1.17l51.55-51.55a4,4,0,0,0,1.17-2.83ZM128,164a8,8,0,1,0,8,8A8,8,0,0,0,128,164Z" +},null,-1)])])):oy("",!0)],16))}}),OP={key:0},wP={key:1},xP={key:2},kP={key:3 +},SP={key:4},_P={key:5},AP=Hg({name:"ScalarIconWebhooksLogo",props:{label:{}, +weight:{}},setup(e){const t=e,{bind:n,weight:r}=E_(t) +;return(e,t)=>(Fb(),qb("svg",cy({xmlns:"http://www.w3.org/2000/svg", +viewBox:"0 0 256 256",fill:"currentColor" +},xm(n)),[Av(e.$slots,"default"),"bold"===xm(r)?(Fb(), +qb("g",OP,[...t[0]||(t[0]=[Jb("path",{ +d:"M192,180H118.71a56,56,0,1,1-104.6-37.46,12,12,0,1,1,21.37,10.92A31.64,31.64,0,0,0,32,168a32,32,0,0,0,64,0,12,12,0,0,1,12-12h84a12,12,0,0,1,0,24Zm0-68a55.9,55.9,0,0,0-18.45,3.12L138.22,57.71a12,12,0,0,0-20.44,12.58l40.94,66.52a12,12,0,0,0,16.52,3.93,32,32,0,1,1,19.68,59.13A12,12,0,0,0,196,223.82a10.05,10.05,0,0,0,1.09,0A56,56,0,0,0,192,112ZM57.71,178.22a12,12,0,0,0,16.51-3.93l40.94-66.52a12,12,0,0,0-3.92-16.51,32,32,0,1,1,45.28-41.8,12,12,0,1,0,21.37-10.92A56,56,0,1,0,89.1,104.32L53.78,161.71A12,12,0,0,0,57.71,178.22Z" +},null,-1)])])):"duotone"===xm(r)?(Fb(),qb("g",wP,[...t[1]||(t[1]=[Jb("path",{ +d:"M128,104a40,40,0,1,1,40-40A40,40,0,0,1,128,104Zm64,24a40,40,0,1,0,40,40A40,40,0,0,0,192,128ZM64,128a40,40,0,1,0,40,40A40,40,0,0,0,64,128Z", +opacity:"0.2"},null,-1),Jb("path",{ +d:"M178.16,176H111.32A48,48,0,1,1,25.6,139.19a8,8,0,0,1,12.8,9.61A31.69,31.69,0,0,0,32,168a32,32,0,0,0,64,0,8,8,0,0,1,8-8h74.16a16,16,0,1,1,0,16ZM64,184a16,16,0,0,0,14.08-23.61l35.77-58.14a8,8,0,0,0-2.62-11,32,32,0,1,1,46.1-40.06A8,8,0,1,0,172,44.79a48,48,0,1,0-75.62,55.33L64.44,152c-.15,0-.29,0-.44,0a16,16,0,0,0,0,32Zm128-64a48.18,48.18,0,0,0-18,3.49L142.08,71.6A16,16,0,1,0,128,80l.44,0,35.78,58.15a8,8,0,0,0,11,2.61A32,32,0,1,1,192,200a8,8,0,0,0,0,16,48,48,0,0,0,0-96Z" +},null,-1)])])):"fill"===xm(r)?(Fb(),qb("g",xP,[...t[2]||(t[2]=[Jb("path",{ +d:"M50.15,160,89.07,92.57l-2.24-3.88a48,48,0,1,1,85.05-44.17,8.17,8.17,0,0,1-3.19,10.4,8,8,0,0,1-11.35-3.72,32,32,0,1,0-56.77,29.3.57.57,0,0,1,.08.13l13.83,23.94a8,8,0,0,1,0,8L77.86,176a16,16,0,0,1-27.71-16Zm141-40H178.81L141.86,56a16,16,0,0,0-27.71,16l34.64,60a8,8,0,0,0,6.92,4h35.63c17.89,0,32.95,14.64,32.66,32.53A32,32,0,0,1,192.31,200a8.23,8.23,0,0,0-8.28,7.33,8,8,0,0,0,8,8.67,48.05,48.05,0,0,0,48-48.93C239.49,140.79,217.48,120,191.19,120ZM208,167.23c-.4-8.61-7.82-15.23-16.43-15.23H114.81a8,8,0,0,0-6.93,4L91.72,184h0a32,32,0,1,1-53.47-35,8.2,8.2,0,0,0-.92-11,8,8,0,0,0-11.72,1.17A47.63,47.63,0,0,0,16,167.54,48,48,0,0,0,105.55,192v0l4.62-8H192A16,16,0,0,0,208,167.23Z" +},null,-1)])])):"light"===xm(r)?(Fb(),qb("g",kP,[...t[3]||(t[3]=[Jb("path",{ +d:"M179.37,174H109.6a46,46,0,1,1-82.4-33.61,6,6,0,0,1,9.6,7.21A33.68,33.68,0,0,0,30,168a34,34,0,0,0,68,0,6,6,0,0,1,6-6h75.37a14,14,0,1,1,0,12ZM64,182a14,14,0,0,0,11.73-21.62l36.42-59.18a6,6,0,0,0-2-8.25,34,34,0,1,1,49-42.57,6,6,0,1,0,11-4.79A46,46,0,1,0,99,99.7L65.52,154.08c-.5-.05-1-.08-1.52-.08a14,14,0,0,0,0,28Zm128-60a46,46,0,0,0-18.8,4L139.73,71.61A14,14,0,1,0,128,78a12.79,12.79,0,0,0,1.52-.09l36.4,59.17a6.05,6.05,0,0,0,3.73,2.69,6,6,0,0,0,4.53-.73A34,34,0,1,1,192,202a6,6,0,0,0,0,12,46,46,0,0,0,0-92Z" +},null,-1)])])):"regular"===xm(r)?(Fb(),qb("g",SP,[...t[4]||(t[4]=[Jb("path",{ +d:"M178.16,176H111.32A48,48,0,1,1,25.6,139.19a8,8,0,0,1,12.8,9.61A31.69,31.69,0,0,0,32,168a32,32,0,0,0,64,0,8,8,0,0,1,8-8h74.16a16,16,0,1,1,0,16ZM64,184a16,16,0,0,0,14.08-23.61l35.77-58.14a8,8,0,0,0-2.62-11,32,32,0,1,1,46.1-40.06A8,8,0,1,0,172,44.79a48,48,0,1,0-75.62,55.33L64.44,152c-.15,0-.29,0-.44,0a16,16,0,0,0,0,32Zm128-64a48.18,48.18,0,0,0-18,3.49L142.08,71.6A16,16,0,1,0,128,80l.44,0,35.78,58.15a8,8,0,0,0,11,2.61A32,32,0,1,1,192,200a8,8,0,0,0,0,16,48,48,0,0,0,0-96Z" +},null,-1)])])):"thin"===xm(r)?(Fb(),qb("g",_P,[...t[5]||(t[5]=[Jb("path",{ +d:"M180.7,172H107.81a44,44,0,1,1-79-30.41,4,4,0,0,1,6.4,4.81A35.67,35.67,0,0,0,28,168a36,36,0,0,0,72,0,4,4,0,0,1,4-4h76.7a12,12,0,1,1,0,8ZM64,180a12,12,0,0,0,9.33-19.54l37.11-60.3a4,4,0,0,0-1.31-5.51A36,36,0,1,1,161,49.58a4,4,0,1,0,7.33-3.19,44,44,0,1,0-66.71,52.83l-35.1,57.05A11.58,11.58,0,0,0,64,156a12,12,0,0,0,0,24Zm128-56a44,44,0,0,0-19.56,4.58l-35.11-57A12,12,0,1,0,128,76a12.24,12.24,0,0,0,2.52-.27L167.63,136a4,4,0,0,0,5.5,1.31A36,36,0,1,1,192,204a4,4,0,0,0,0,8,44,44,0,0,0,0-88Z" +},null,-1)])])):oy("",!0)],16))}}),TP={key:0},EP={key:1},CP={key:2},$P={key:3 +},PP={key:4},IP={key:5},DP=Hg({name:"ScalarIconX",props:{label:{},weight:{}}, +setup(e){const t=e,{bind:n,weight:r}=E_(t);return(e,t)=>(Fb(),qb("svg",cy({ +xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 256 256",fill:"currentColor" +},xm(n)),[Av(e.$slots,"default"),"bold"===xm(r)?(Fb(), +qb("g",TP,[...t[0]||(t[0]=[Jb("path",{ +d:"M208.49,191.51a12,12,0,0,1-17,17L128,145,64.49,208.49a12,12,0,0,1-17-17L111,128,47.51,64.49a12,12,0,0,1,17-17L128,111l63.51-63.52a12,12,0,0,1,17,17L145,128Z" +},null,-1)])])):"duotone"===xm(r)?(Fb(),qb("g",EP,[...t[1]||(t[1]=[Jb("path",{ +d:"M216,56V200a16,16,0,0,1-16,16H56a16,16,0,0,1-16-16V56A16,16,0,0,1,56,40H200A16,16,0,0,1,216,56Z", +opacity:"0.2"},null,-1),Jb("path",{ +d:"M205.66,194.34a8,8,0,0,1-11.32,11.32L128,139.31,61.66,205.66a8,8,0,0,1-11.32-11.32L116.69,128,50.34,61.66A8,8,0,0,1,61.66,50.34L128,116.69l66.34-66.35a8,8,0,0,1,11.32,11.32L139.31,128Z" +},null,-1)])])):"fill"===xm(r)?(Fb(),qb("g",CP,[...t[2]||(t[2]=[Jb("path",{ +d:"M208,32H48A16,16,0,0,0,32,48V208a16,16,0,0,0,16,16H208a16,16,0,0,0,16-16V48A16,16,0,0,0,208,32ZM181.66,170.34a8,8,0,0,1-11.32,11.32L128,139.31,85.66,181.66a8,8,0,0,1-11.32-11.32L116.69,128,74.34,85.66A8,8,0,0,1,85.66,74.34L128,116.69l42.34-42.35a8,8,0,0,1,11.32,11.32L139.31,128Z" +},null,-1)])])):"light"===xm(r)?(Fb(),qb("g",$P,[...t[3]||(t[3]=[Jb("path",{ +d:"M204.24,195.76a6,6,0,1,1-8.48,8.48L128,136.49,60.24,204.24a6,6,0,0,1-8.48-8.48L119.51,128,51.76,60.24a6,6,0,0,1,8.48-8.48L128,119.51l67.76-67.75a6,6,0,0,1,8.48,8.48L136.49,128Z" +},null,-1)])])):"regular"===xm(r)?(Fb(),qb("g",PP,[...t[4]||(t[4]=[Jb("path",{ +d:"M205.66,194.34a8,8,0,0,1-11.32,11.32L128,139.31,61.66,205.66a8,8,0,0,1-11.32-11.32L116.69,128,50.34,61.66A8,8,0,0,1,61.66,50.34L128,116.69l66.34-66.35a8,8,0,0,1,11.32,11.32L139.31,128Z" +},null,-1)])])):"thin"===xm(r)?(Fb(),qb("g",IP,[...t[5]||(t[5]=[Jb("path",{ +d:"M202.83,197.17a4,4,0,0,1-5.66,5.66L128,133.66,58.83,202.83a4,4,0,0,1-5.66-5.66L122.34,128,53.17,58.83a4,4,0,0,1,5.66-5.66L128,122.34l69.17-69.17a4,4,0,1,1,5.66,5.66L133.66,128Z" +},null,-1)])])):oy("",!0)],16))}}),MP={key:0},NP={key:1},RP={key:2},LP={key:3 +},BP={key:4},jP={key:5},UP=Hg({name:"ScalarIconXCircle",props:{label:{}, +weight:{}},setup(e){const t=e,{bind:n,weight:r}=E_(t) +;return(e,t)=>(Fb(),qb("svg",cy({xmlns:"http://www.w3.org/2000/svg", +viewBox:"0 0 256 256",fill:"currentColor" +},xm(n)),[Av(e.$slots,"default"),"bold"===xm(r)?(Fb(), +qb("g",MP,[...t[0]||(t[0]=[Jb("path",{ +d:"M168.49,104.49,145,128l23.52,23.51a12,12,0,0,1-17,17L128,145l-23.51,23.52a12,12,0,0,1-17-17L111,128,87.51,104.49a12,12,0,0,1,17-17L128,111l23.51-23.52a12,12,0,0,1,17,17ZM236,128A108,108,0,1,1,128,20,108.12,108.12,0,0,1,236,128Zm-24,0a84,84,0,1,0-84,84A84.09,84.09,0,0,0,212,128Z" +},null,-1)])])):"duotone"===xm(r)?(Fb(),qb("g",NP,[...t[1]||(t[1]=[Jb("path",{ +d:"M224,128a96,96,0,1,1-96-96A96,96,0,0,1,224,128Z",opacity:"0.2" +},null,-1),Jb("path",{ +d:"M165.66,101.66,139.31,128l26.35,26.34a8,8,0,0,1-11.32,11.32L128,139.31l-26.34,26.35a8,8,0,0,1-11.32-11.32L116.69,128,90.34,101.66a8,8,0,0,1,11.32-11.32L128,116.69l26.34-26.35a8,8,0,0,1,11.32,11.32ZM232,128A104,104,0,1,1,128,24,104.11,104.11,0,0,1,232,128Zm-16,0a88,88,0,1,0-88,88A88.1,88.1,0,0,0,216,128Z" +},null,-1)])])):"fill"===xm(r)?(Fb(),qb("g",RP,[...t[2]||(t[2]=[Jb("path",{ +d:"M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm37.66,130.34a8,8,0,0,1-11.32,11.32L128,139.31l-26.34,26.35a8,8,0,0,1-11.32-11.32L116.69,128,90.34,101.66a8,8,0,0,1,11.32-11.32L128,116.69l26.34-26.35a8,8,0,0,1,11.32,11.32L139.31,128Z" +},null,-1)])])):"light"===xm(r)?(Fb(),qb("g",LP,[...t[3]||(t[3]=[Jb("path",{ +d:"M164.24,100.24,136.48,128l27.76,27.76a6,6,0,1,1-8.48,8.48L128,136.48l-27.76,27.76a6,6,0,0,1-8.48-8.48L119.52,128,91.76,100.24a6,6,0,0,1,8.48-8.48L128,119.52l27.76-27.76a6,6,0,0,1,8.48,8.48ZM230,128A102,102,0,1,1,128,26,102.12,102.12,0,0,1,230,128Zm-12,0a90,90,0,1,0-90,90A90.1,90.1,0,0,0,218,128Z" +},null,-1)])])):"regular"===xm(r)?(Fb(),qb("g",BP,[...t[4]||(t[4]=[Jb("path",{ +d:"M165.66,101.66,139.31,128l26.35,26.34a8,8,0,0,1-11.32,11.32L128,139.31l-26.34,26.35a8,8,0,0,1-11.32-11.32L116.69,128,90.34,101.66a8,8,0,0,1,11.32-11.32L128,116.69l26.34-26.35a8,8,0,0,1,11.32,11.32ZM232,128A104,104,0,1,1,128,24,104.11,104.11,0,0,1,232,128Zm-16,0a88,88,0,1,0-88,88A88.1,88.1,0,0,0,216,128Z" +},null,-1)])])):"thin"===xm(r)?(Fb(),qb("g",jP,[...t[5]||(t[5]=[Jb("path",{ +d:"M162.83,98.83,133.66,128l29.17,29.17a4,4,0,0,1-5.66,5.66L128,133.66,98.83,162.83a4,4,0,0,1-5.66-5.66L122.34,128,93.17,98.83a4,4,0,0,1,5.66-5.66L128,122.34l29.17-29.17a4,4,0,1,1,5.66,5.66ZM228,128A100,100,0,1,1,128,28,100.11,100.11,0,0,1,228,128Zm-8,0a92,92,0,1,0-92,92A92.1,92.1,0,0,0,220,128Z" +},null,-1)])])):oy("",!0)],16))}}),zP=Hg({__name:"ScalarCheckbox",props:{ +selected:{type:Boolean},type:{default:"checkbox"}}, +setup:e=>(t,n)=>(Fb(),qb("div",{ +class:Dh(["flex size-4 items-center justify-center p-0.75",[e.selected?"bg-c-accent text-b-1":"text-transparent shadow-border","checkbox"===e.type?"rounded":"rounded-full"]]) +},[e.selected?(Fb(),Wb(xm(LA),{key:0,class:"size-3",weight:"bold" +})):oy("",!0)],2))}),ZP=Symbol(),FP=Hg({inheritAttrs:!1, +__name:"ScalarFormInput",props:{is:{default:"button"}},setup(e){ +const{cx:t}=h_(),n=ug(ZP,!1),r=d_({ +base:["bg-b-1.5 flex items-center text-c-2 gap-0.75 px-3 py-2.5 ","outline-offset-[-1px] has-[:focus-visible]:outline"], +variants:{grouped:{true:"first:rounded-t-[inherit] last:rounded-b-[inherit]", +false:"rounded border"},button:{true:"cursor-pointer hover:bg-b-2"}}}) +;return(a,o)=>(Fb(),Wb(wv(e.is),cy({type:"button"===e.is?"button":void 0 +},xm(t)(xm(r)({button:"button"===e.is,grouped:xm(n)}))),{ +default:ig((()=>[Av(a.$slots,"default")])),_:3},16,["type"]))}}),HP={ +class:"flex-1 text-left min-w-0 truncate"},QP=["type"],VP=Hg({inheritAttrs:!1, +__name:"ScalarCheckboxInput",props:Bv({type:{default:"checkbox"}},{modelValue:{ +type:Boolean},modelModifiers:{}}),emits:["update:modelValue"],setup(e){ +const t=tb(e,"modelValue"),{stylingAttrsCx:n,otherAttrs:r}=h_() +;return(a,o)=>(Fb(),Wb(xm(FP),cy({is:"label" +},xm(n)("cursor-pointer gap-2 hover:bg-b-2",{"text-c-1":t.value})),{ +default:ig((()=>[ey(zP,{class:"shrink-0",selected:t.value,type:e.type +},null,8,["selected","type"]),Jb("div",HP,[Av(a.$slots,"default")]),sg(Jb("input",cy({ +"onUpdate:modelValue":o[0]||(o[0]=e=>t.value=e),class:"sr-only",type:e.type +},xm(r)),null,16,QP),[[LO,t.value]])])),_:3},16))}}),qP=Hg({inheritAttrs:!1, +__name:"ScalarFormInputGroup",props:{is:{default:"div"}},setup(e){ +const{cx:t}=h_() +;return cg(ZP,!0),(n,r)=>(Fb(),Wb(wv(e.is),Mh(ty(xm(t)("flex flex-col border rounded divide-y"))),{ +default:ig((()=>[Av(n.$slots,"default")])),_:3},16))}}),WP=Hg({ +__name:"ScalarCheckboxRadioGroup",props:Bv({options:{default:()=>[]}},{ +modelValue:{},modelModifiers:{}}),emits:["update:modelValue"],setup(e){ +const t=tb(e,"modelValue"),n=Qg();return(r,a)=>(Fb(),Wb(xm(qP),null,{ +default:ig((()=>[(Fb(!0),qb(Lb,null,Sv(e.options,(e=>(Fb(),Wb(VP,{key:e.value, +modelValue:t.value?.value===e.value,name:xm(n),type:"radio",value:e.value, +"onUpdate:modelValue":n=>t.value=n?e:void 0},{ +default:ig((()=>[ry(Uh(e.label),1)])),_:2 +},1032,["modelValue","name","value","onUpdate:modelValue"])))),128))])),_:1}))} +}),XP=Hg({inheritAttrs:!1,__name:"ScalarCopyBackdrop",setup(e){const{cx:t}=h_() +;return(e,n)=>(Fb(), +qb("div",Mh(ty(xm(t)("absolute inset-y-0.5 -z-2 left-0 right-0 bg-b-1 rounded"))),null,16)) +}});let GP=class{constructor(e,t,n){ +this.property=e,this.normal=t,n&&(this.space=n)}};function YP(e,t){ +const n={},r={};let a=-1 +;for(;++a"xlink:"+t.slice(5).toLowerCase(),properties:{ +xLinkActuate:null,xLinkArcRole:null,xLinkHref:null,xLinkRole:null, +xLinkShow:null,xLinkTitle:null,xLinkType:null}}),gI=fI({space:"xml", +transform:(e,t)=>"xml:"+t.slice(3).toLowerCase(),properties:{xmlLang:null, +xmlBase:null,xmlSpace:null}});function vI(e,t){return t in e?e[t]:t} +function bI(e,t){return vI(e,t.toLowerCase())}const yI=fI({space:"xmlns", +attributes:{xmlnsxlink:"xmlns:xlink"},transform:bI,properties:{xmlns:null, +xmlnsXLink:null}}),OI=fI({ +transform:(e,t)=>"role"===t?t:"aria-"+t.slice(4).toLowerCase(),properties:{ +ariaActiveDescendant:null,ariaAtomic:nI,ariaAutoComplete:null,ariaBusy:nI, +ariaChecked:nI,ariaColCount:aI,ariaColIndex:aI,ariaColSpan:aI,ariaControls:oI, +ariaCurrent:null,ariaDescribedBy:oI,ariaDetails:null,ariaDisabled:nI, +ariaDropEffect:oI,ariaErrorMessage:null,ariaExpanded:nI,ariaFlowTo:oI, +ariaGrabbed:nI,ariaHasPopup:null,ariaHidden:nI,ariaInvalid:null, +ariaKeyShortcuts:null,ariaLabel:null,ariaLabelledBy:oI,ariaLevel:aI, +ariaLive:null,ariaModal:nI,ariaMultiLine:nI,ariaMultiSelectable:nI, +ariaOrientation:null,ariaOwns:oI,ariaPlaceholder:null,ariaPosInSet:aI, +ariaPressed:nI,ariaReadOnly:nI,ariaRelevant:null,ariaRequired:nI, +ariaRoleDescription:oI,ariaRowCount:aI,ariaRowIndex:aI,ariaRowSpan:aI, +ariaSelected:nI,ariaSetSize:aI,ariaSort:null,ariaValueMax:aI,ariaValueMin:aI, +ariaValueNow:aI,ariaValueText:null,role:null}}),wI=fI({space:"html",attributes:{ +acceptcharset:"accept-charset",classname:"class",htmlfor:"for", +httpequiv:"http-equiv"},transform:bI, +mustUseProperty:["checked","multiple","muted","selected"],properties:{abbr:null, +accept:iI,acceptCharset:oI,accessKey:oI,action:null,allow:null, +allowFullScreen:tI,allowPaymentRequest:tI,allowUserMedia:tI,alt:null,as:null, +async:tI,autoCapitalize:null,autoComplete:oI,autoFocus:tI,autoPlay:tI, +blocking:oI,capture:null,charSet:null,checked:tI,cite:null,className:oI,cols:aI, +colSpan:null,content:null,contentEditable:nI,controls:tI,controlsList:oI, +coords:aI|iI,crossOrigin:null,data:null,dateTime:null,decoding:null,default:tI, +defer:tI,dir:null,dirName:null,disabled:tI,download:rI,draggable:nI, +encType:null,enterKeyHint:null,fetchPriority:null,form:null,formAction:null, +formEncType:null,formMethod:null,formNoValidate:tI,formTarget:null,headers:oI, +height:aI,hidden:tI,high:aI,href:null,hrefLang:null,htmlFor:oI,httpEquiv:oI, +id:null,imageSizes:null,imageSrcSet:null,inert:tI,inputMode:null,integrity:null, +is:null,isMap:tI,itemId:null,itemProp:oI,itemRef:oI,itemScope:tI,itemType:oI, +kind:null,label:null,lang:null,language:null,list:null,loading:null,loop:tI, +low:aI,manifest:null,max:null,maxLength:aI,media:null,method:null,min:null, +minLength:aI,multiple:tI,muted:tI,name:null,nonce:null,noModule:tI, +noValidate:tI,onAbort:null,onAfterPrint:null,onAuxClick:null,onBeforeMatch:null, +onBeforePrint:null,onBeforeToggle:null,onBeforeUnload:null,onBlur:null, +onCancel:null,onCanPlay:null,onCanPlayThrough:null,onChange:null,onClick:null, +onClose:null,onContextLost:null,onContextMenu:null,onContextRestored:null, +onCopy:null,onCueChange:null,onCut:null,onDblClick:null,onDrag:null, +onDragEnd:null,onDragEnter:null,onDragExit:null,onDragLeave:null, +onDragOver:null,onDragStart:null,onDrop:null,onDurationChange:null, +onEmptied:null,onEnded:null,onError:null,onFocus:null,onFormData:null, +onHashChange:null,onInput:null,onInvalid:null,onKeyDown:null,onKeyPress:null, +onKeyUp:null,onLanguageChange:null,onLoad:null,onLoadedData:null, +onLoadedMetadata:null,onLoadEnd:null,onLoadStart:null,onMessage:null, +onMessageError:null,onMouseDown:null,onMouseEnter:null,onMouseLeave:null, +onMouseMove:null,onMouseOut:null,onMouseOver:null,onMouseUp:null,onOffline:null, +onOnline:null,onPageHide:null,onPageShow:null,onPaste:null,onPause:null, +onPlay:null,onPlaying:null,onPopState:null,onProgress:null,onRateChange:null, +onRejectionHandled:null,onReset:null,onResize:null,onScroll:null, +onScrollEnd:null,onSecurityPolicyViolation:null,onSeeked:null,onSeeking:null, +onSelect:null,onSlotChange:null,onStalled:null,onStorage:null,onSubmit:null, +onSuspend:null,onTimeUpdate:null,onToggle:null,onUnhandledRejection:null, +onUnload:null,onVolumeChange:null,onWaiting:null,onWheel:null,open:tI, +optimum:aI,pattern:null,ping:oI,placeholder:null,playsInline:tI,popover:null, +popoverTarget:null,popoverTargetAction:null,poster:null,preload:null, +readOnly:tI,referrerPolicy:null,rel:oI,required:tI,reversed:tI,rows:aI, +rowSpan:aI,sandbox:oI,scope:null,scoped:tI,seamless:tI,selected:tI, +shadowRootClonable:tI,shadowRootDelegatesFocus:tI,shadowRootMode:null, +shape:null,size:aI,sizes:null,slot:null,span:aI,spellCheck:nI,src:null, +srcDoc:null,srcLang:null,srcSet:null,start:aI,step:null,style:null,tabIndex:aI, +target:null,title:null,translate:null,type:null,typeMustMatch:tI,useMap:null, +value:nI,width:aI,wrap:null,writingSuggestions:null,align:null,aLink:null, +archive:oI,axis:null,background:null,bgColor:null,border:aI,borderColor:null, +bottomMargin:aI,cellPadding:null,cellSpacing:null,char:null,charOff:null, +classId:null,clear:null,code:null,codeBase:null,codeType:null,color:null, +compact:tI,declare:tI,event:null,face:null,frame:null,frameBorder:null, +hSpace:aI,leftMargin:aI,link:null,longDesc:null,lowSrc:null,marginHeight:aI, +marginWidth:aI,noResize:tI,noHref:tI,noShade:tI,noWrap:tI,object:null, +profile:null,prompt:null,rev:null,rightMargin:aI,rules:null,scheme:null, +scrolling:nI,standby:null,summary:null,text:null,topMargin:aI,valueType:null, +version:null,vAlign:null,vLink:null,vSpace:aI,allowTransparency:null, +autoCorrect:null,autoSave:null,disablePictureInPicture:tI, +disableRemotePlayback:tI,prefix:null,property:null,results:aI,security:null, +unselectable:null}}),xI=fI({space:"svg",attributes:{ +accentHeight:"accent-height",alignmentBaseline:"alignment-baseline", +arabicForm:"arabic-form",baselineShift:"baseline-shift",capHeight:"cap-height", +className:"class",clipPath:"clip-path",clipRule:"clip-rule", +colorInterpolation:"color-interpolation", +colorInterpolationFilters:"color-interpolation-filters", +colorProfile:"color-profile",colorRendering:"color-rendering", +crossOrigin:"crossorigin",dataType:"datatype", +dominantBaseline:"dominant-baseline",enableBackground:"enable-background", +fillOpacity:"fill-opacity",fillRule:"fill-rule",floodColor:"flood-color", +floodOpacity:"flood-opacity",fontFamily:"font-family",fontSize:"font-size", +fontSizeAdjust:"font-size-adjust",fontStretch:"font-stretch", +fontStyle:"font-style",fontVariant:"font-variant",fontWeight:"font-weight", +glyphName:"glyph-name", +glyphOrientationHorizontal:"glyph-orientation-horizontal", +glyphOrientationVertical:"glyph-orientation-vertical",hrefLang:"hreflang", +horizAdvX:"horiz-adv-x",horizOriginX:"horiz-origin-x", +horizOriginY:"horiz-origin-y",imageRendering:"image-rendering", +letterSpacing:"letter-spacing",lightingColor:"lighting-color", +markerEnd:"marker-end",markerMid:"marker-mid",markerStart:"marker-start", +navDown:"nav-down",navDownLeft:"nav-down-left",navDownRight:"nav-down-right", +navLeft:"nav-left",navNext:"nav-next",navPrev:"nav-prev",navRight:"nav-right", +navUp:"nav-up",navUpLeft:"nav-up-left",navUpRight:"nav-up-right", +onAbort:"onabort",onActivate:"onactivate",onAfterPrint:"onafterprint", +onBeforePrint:"onbeforeprint",onBegin:"onbegin",onCancel:"oncancel", +onCanPlay:"oncanplay",onCanPlayThrough:"oncanplaythrough",onChange:"onchange", +onClick:"onclick",onClose:"onclose",onCopy:"oncopy",onCueChange:"oncuechange", +onCut:"oncut",onDblClick:"ondblclick",onDrag:"ondrag",onDragEnd:"ondragend", +onDragEnter:"ondragenter",onDragExit:"ondragexit",onDragLeave:"ondragleave", +onDragOver:"ondragover",onDragStart:"ondragstart",onDrop:"ondrop", +onDurationChange:"ondurationchange",onEmptied:"onemptied",onEnd:"onend", +onEnded:"onended",onError:"onerror",onFocus:"onfocus",onFocusIn:"onfocusin", +onFocusOut:"onfocusout",onHashChange:"onhashchange",onInput:"oninput", +onInvalid:"oninvalid",onKeyDown:"onkeydown",onKeyPress:"onkeypress", +onKeyUp:"onkeyup",onLoad:"onload",onLoadedData:"onloadeddata", +onLoadedMetadata:"onloadedmetadata",onLoadStart:"onloadstart", +onMessage:"onmessage",onMouseDown:"onmousedown",onMouseEnter:"onmouseenter", +onMouseLeave:"onmouseleave",onMouseMove:"onmousemove",onMouseOut:"onmouseout", +onMouseOver:"onmouseover",onMouseUp:"onmouseup",onMouseWheel:"onmousewheel", +onOffline:"onoffline",onOnline:"ononline",onPageHide:"onpagehide", +onPageShow:"onpageshow",onPaste:"onpaste",onPause:"onpause",onPlay:"onplay", +onPlaying:"onplaying",onPopState:"onpopstate",onProgress:"onprogress", +onRateChange:"onratechange",onRepeat:"onrepeat",onReset:"onreset", +onResize:"onresize",onScroll:"onscroll",onSeeked:"onseeked", +onSeeking:"onseeking",onSelect:"onselect",onShow:"onshow",onStalled:"onstalled", +onStorage:"onstorage",onSubmit:"onsubmit",onSuspend:"onsuspend", +onTimeUpdate:"ontimeupdate",onToggle:"ontoggle",onUnload:"onunload", +onVolumeChange:"onvolumechange",onWaiting:"onwaiting",onZoom:"onzoom", +overlinePosition:"overline-position",overlineThickness:"overline-thickness", +paintOrder:"paint-order",panose1:"panose-1",pointerEvents:"pointer-events", +referrerPolicy:"referrerpolicy",renderingIntent:"rendering-intent", +shapeRendering:"shape-rendering",stopColor:"stop-color", +stopOpacity:"stop-opacity",strikethroughPosition:"strikethrough-position", +strikethroughThickness:"strikethrough-thickness", +strokeDashArray:"stroke-dasharray",strokeDashOffset:"stroke-dashoffset", +strokeLineCap:"stroke-linecap",strokeLineJoin:"stroke-linejoin", +strokeMiterLimit:"stroke-miterlimit",strokeOpacity:"stroke-opacity", +strokeWidth:"stroke-width",tabIndex:"tabindex",textAnchor:"text-anchor", +textDecoration:"text-decoration",textRendering:"text-rendering", +transformOrigin:"transform-origin",typeOf:"typeof", +underlinePosition:"underline-position",underlineThickness:"underline-thickness", +unicodeBidi:"unicode-bidi",unicodeRange:"unicode-range", +unitsPerEm:"units-per-em",vAlphabetic:"v-alphabetic",vHanging:"v-hanging", +vIdeographic:"v-ideographic",vMathematical:"v-mathematical", +vectorEffect:"vector-effect",vertAdvY:"vert-adv-y",vertOriginX:"vert-origin-x", +vertOriginY:"vert-origin-y",wordSpacing:"word-spacing", +writingMode:"writing-mode",xHeight:"x-height",playbackOrder:"playbackorder", +timelineBegin:"timelinebegin"},transform:vI,properties:{about:sI, +accentHeight:aI,accumulate:null,additive:null,alignmentBaseline:null, +alphabetic:aI,amplitude:aI,arabicForm:null,ascent:aI,attributeName:null, +attributeType:null,azimuth:aI,bandwidth:null,baselineShift:null, +baseFrequency:null,baseProfile:null,bbox:null,begin:null,bias:aI,by:null, +calcMode:null,capHeight:aI,className:oI,clip:null,clipPath:null, +clipPathUnits:null,clipRule:null,color:null,colorInterpolation:null, +colorInterpolationFilters:null,colorProfile:null,colorRendering:null, +content:null,contentScriptType:null,contentStyleType:null,crossOrigin:null, +cursor:null,cx:null,cy:null,d:null,dataType:null,defaultAction:null,descent:aI, +diffuseConstant:aI,direction:null,display:null,dur:null,divisor:aI, +dominantBaseline:null,download:tI,dx:null,dy:null,edgeMode:null,editable:null, +elevation:aI,enableBackground:null,end:null,event:null,exponent:aI, +externalResourcesRequired:null,fill:null,fillOpacity:aI,fillRule:null, +filter:null,filterRes:null,filterUnits:null,floodColor:null,floodOpacity:null, +focusable:null,focusHighlight:null,fontFamily:null,fontSize:null, +fontSizeAdjust:null,fontStretch:null,fontStyle:null,fontVariant:null, +fontWeight:null,format:null,fr:null,from:null,fx:null,fy:null,g1:iI,g2:iI, +glyphName:iI,glyphOrientationHorizontal:null,glyphOrientationVertical:null, +glyphRef:null,gradientTransform:null,gradientUnits:null,handler:null,hanging:aI, +hatchContentUnits:null,hatchUnits:null,height:null,href:null,hrefLang:null, +horizAdvX:aI,horizOriginX:aI,horizOriginY:aI,id:null,ideographic:aI, +imageRendering:null,initialVisibility:null,in:null,in2:null,intercept:aI,k:aI, +k1:aI,k2:aI,k3:aI,k4:aI,kernelMatrix:sI,kernelUnitLength:null,keyPoints:null, +keySplines:null,keyTimes:null,kerning:null,lang:null,lengthAdjust:null, +letterSpacing:null,lightingColor:null,limitingConeAngle:aI,local:null, +markerEnd:null,markerMid:null,markerStart:null,markerHeight:null, +markerUnits:null,markerWidth:null,mask:null,maskContentUnits:null, +maskUnits:null,mathematical:null,max:null,media:null, +mediaCharacterEncoding:null,mediaContentEncodings:null,mediaSize:aI, +mediaTime:null,method:null,min:null,mode:null,name:null,navDown:null, +navDownLeft:null,navDownRight:null,navLeft:null,navNext:null,navPrev:null, +navRight:null,navUp:null,navUpLeft:null,navUpRight:null,numOctaves:null, +observer:null,offset:null,onAbort:null,onActivate:null,onAfterPrint:null, +onBeforePrint:null,onBegin:null,onCancel:null,onCanPlay:null, +onCanPlayThrough:null,onChange:null,onClick:null,onClose:null,onCopy:null, +onCueChange:null,onCut:null,onDblClick:null,onDrag:null,onDragEnd:null, +onDragEnter:null,onDragExit:null,onDragLeave:null,onDragOver:null, +onDragStart:null,onDrop:null,onDurationChange:null,onEmptied:null,onEnd:null, +onEnded:null,onError:null,onFocus:null,onFocusIn:null,onFocusOut:null, +onHashChange:null,onInput:null,onInvalid:null,onKeyDown:null,onKeyPress:null, +onKeyUp:null,onLoad:null,onLoadedData:null,onLoadedMetadata:null, +onLoadStart:null,onMessage:null,onMouseDown:null,onMouseEnter:null, +onMouseLeave:null,onMouseMove:null,onMouseOut:null,onMouseOver:null, +onMouseUp:null,onMouseWheel:null,onOffline:null,onOnline:null,onPageHide:null, +onPageShow:null,onPaste:null,onPause:null,onPlay:null,onPlaying:null, +onPopState:null,onProgress:null,onRateChange:null,onRepeat:null,onReset:null, +onResize:null,onScroll:null,onSeeked:null,onSeeking:null,onSelect:null, +onShow:null,onStalled:null,onStorage:null,onSubmit:null,onSuspend:null, +onTimeUpdate:null,onToggle:null,onUnload:null,onVolumeChange:null, +onWaiting:null,onZoom:null,opacity:null,operator:null,order:null,orient:null, +orientation:null,origin:null,overflow:null,overlay:null,overlinePosition:aI, +overlineThickness:aI,paintOrder:null,panose1:null,path:null,pathLength:aI, +patternContentUnits:null,patternTransform:null,patternUnits:null,phase:null, +ping:oI,pitch:null,playbackOrder:null,pointerEvents:null,points:null, +pointsAtX:aI,pointsAtY:aI,pointsAtZ:aI,preserveAlpha:null, +preserveAspectRatio:null,primitiveUnits:null,propagate:null,property:sI,r:null, +radius:null,referrerPolicy:null,refX:null,refY:null,rel:sI,rev:sI, +renderingIntent:null,repeatCount:null,repeatDur:null,requiredExtensions:sI, +requiredFeatures:sI,requiredFonts:sI,requiredFormats:sI,resource:null, +restart:null,result:null,rotate:null,rx:null,ry:null,scale:null,seed:null, +shapeRendering:null,side:null,slope:null,snapshotTime:null,specularConstant:aI, +specularExponent:aI,spreadMethod:null,spacing:null,startOffset:null, +stdDeviation:null,stemh:null,stemv:null,stitchTiles:null,stopColor:null, +stopOpacity:null,strikethroughPosition:aI,strikethroughThickness:aI,string:null, +stroke:null,strokeDashArray:sI,strokeDashOffset:null,strokeLineCap:null, +strokeLineJoin:null,strokeMiterLimit:aI,strokeOpacity:aI,strokeWidth:null, +style:null,surfaceScale:aI,syncBehavior:null,syncBehaviorDefault:null, +syncMaster:null,syncTolerance:null,syncToleranceDefault:null,systemLanguage:sI, +tabIndex:aI,tableValues:null,target:null,targetX:aI,targetY:aI,textAnchor:null, +textDecoration:null,textRendering:null,textLength:null,timelineBegin:null, +title:null,transformBehavior:null,type:null,typeOf:sI,to:null,transform:null, +transformOrigin:null,u1:null,u2:null,underlinePosition:aI,underlineThickness:aI, +unicode:null,unicodeBidi:null,unicodeRange:null,unitsPerEm:aI,values:null, +vAlphabetic:aI,vMathematical:aI,vectorEffect:null,vHanging:aI,vIdeographic:aI, +version:null,vertAdvY:aI,vertOriginX:aI,vertOriginY:aI,viewBox:null, +viewTarget:null,visibility:null,width:null,widths:null,wordSpacing:null, +writingMode:null,x:null,x1:null,x2:null,xChannelSelector:null,xHeight:aI,y:null, +y1:null,y2:null,yChannelSelector:null,z:null,zoomAndPan:null} +}),kI=/^data[-\w.:]+$/i,SI=/-[a-z]/g,_I=/[A-Z]/g;function AI(e,t){const n=KP(t) +;let r=t,a=JP;if(n in e.normal)return e.property[e.normal[n]] +;if(n.length>4&&"data"===n.slice(0,4)&&kI.test(t)){if("-"===t.charAt(4)){ +const e=t.slice(5).replace(SI,EI);r="data"+e.charAt(0).toUpperCase()+e.slice(1) +}else{const e=t.slice(4);if(!SI.test(e)){let n=e.replace(_I,TI) +;"-"!==n.charAt(0)&&(n="-"+n),t="data"+n}}a=dI}return new a(r,t)}function TI(e){ +return"-"+e.toLowerCase()}function EI(e){return e.charAt(1).toUpperCase()} +const CI=YP([gI,mI,yI,OI,wI],"html"),$I=YP([gI,mI,yI,OI,xI],"svg") +;function PI(e){const t=[],n=String(e||"");let r=n.indexOf(","),a=0,o=!1 +;for(;!o;){-1===r&&(r=n.length,o=!0);const e=n.slice(a,r).trim() +;!e&&o||t.push(e),a=r+1,r=n.indexOf(",",a)}return t}function II(e,t){ +const n=t||{} +;return(""===e[e.length-1]?[...e,""]:e).join((n.padRight?" ":"")+","+(!1===n.padLeft?"":" ")).trim() +}const DI=/[#.]/g;function MI(e){const t=String(e||"").trim() +;return t?t.split(/[ \t\n\r\f]+/g):[]}function NI(e){return e.join(" ").trim()} +const RI=new Set(["button","menu","reset","submit"]),LI={}.hasOwnProperty +;function BI(e,t,n){const r=n&&function(e){const t={};let n=-1 +;for(;++n-1&&ee)return{ +line:t+1,column:e-(t>0?n[t-1]:0)+1,offset:e}},toOffset:function(e){ +const t=e&&e.line,r=e&&e.column +;if("number"==typeof t&&"number"==typeof r&&!Number.isNaN(t)&&!Number.isNaN(r)&&t-1 in n){ +const e=(n[t-2]||0)+r-1||0;if(e>-1&&e=55296&&e<=57343}function dD(e){ +return 32!==e&&10!==e&&13!==e&&9!==e&&12!==e&&e>=1&&e<=31||e>=127&&e<=159} +function pD(e){return e>=64976&&e<=65007||eD.has(e)}var hD,fD +;(fD=hD||(hD={})).controlCharacterInInputStream="control-character-in-input-stream", +fD.noncharacterInInputStream="noncharacter-in-input-stream", +fD.surrogateInInputStream="surrogate-in-input-stream", +fD.nonVoidHtmlElementStartTagWithTrailingSolidus="non-void-html-element-start-tag-with-trailing-solidus", +fD.endTagWithAttributes="end-tag-with-attributes", +fD.endTagWithTrailingSolidus="end-tag-with-trailing-solidus", +fD.unexpectedSolidusInTag="unexpected-solidus-in-tag", +fD.unexpectedNullCharacter="unexpected-null-character", +fD.unexpectedQuestionMarkInsteadOfTagName="unexpected-question-mark-instead-of-tag-name", +fD.invalidFirstCharacterOfTagName="invalid-first-character-of-tag-name", +fD.unexpectedEqualsSignBeforeAttributeName="unexpected-equals-sign-before-attribute-name", +fD.missingEndTagName="missing-end-tag-name", +fD.unexpectedCharacterInAttributeName="unexpected-character-in-attribute-name", +fD.unknownNamedCharacterReference="unknown-named-character-reference", +fD.missingSemicolonAfterCharacterReference="missing-semicolon-after-character-reference", +fD.unexpectedCharacterAfterDoctypeSystemIdentifier="unexpected-character-after-doctype-system-identifier", +fD.unexpectedCharacterInUnquotedAttributeValue="unexpected-character-in-unquoted-attribute-value", +fD.eofBeforeTagName="eof-before-tag-name", +fD.eofInTag="eof-in-tag",fD.missingAttributeValue="missing-attribute-value", +fD.missingWhitespaceBetweenAttributes="missing-whitespace-between-attributes", +fD.missingWhitespaceAfterDoctypePublicKeyword="missing-whitespace-after-doctype-public-keyword", +fD.missingWhitespaceBetweenDoctypePublicAndSystemIdentifiers="missing-whitespace-between-doctype-public-and-system-identifiers", +fD.missingWhitespaceAfterDoctypeSystemKeyword="missing-whitespace-after-doctype-system-keyword", +fD.missingQuoteBeforeDoctypePublicIdentifier="missing-quote-before-doctype-public-identifier", +fD.missingQuoteBeforeDoctypeSystemIdentifier="missing-quote-before-doctype-system-identifier", +fD.missingDoctypePublicIdentifier="missing-doctype-public-identifier", +fD.missingDoctypeSystemIdentifier="missing-doctype-system-identifier", +fD.abruptDoctypePublicIdentifier="abrupt-doctype-public-identifier", +fD.abruptDoctypeSystemIdentifier="abrupt-doctype-system-identifier", +fD.cdataInHtmlContent="cdata-in-html-content", +fD.incorrectlyOpenedComment="incorrectly-opened-comment", +fD.eofInScriptHtmlCommentLikeText="eof-in-script-html-comment-like-text", +fD.eofInDoctype="eof-in-doctype", +fD.nestedComment="nested-comment",fD.abruptClosingOfEmptyComment="abrupt-closing-of-empty-comment", +fD.eofInComment="eof-in-comment", +fD.incorrectlyClosedComment="incorrectly-closed-comment", +fD.eofInCdata="eof-in-cdata", +fD.absenceOfDigitsInNumericCharacterReference="absence-of-digits-in-numeric-character-reference", +fD.nullCharacterReference="null-character-reference", +fD.surrogateCharacterReference="surrogate-character-reference", +fD.characterReferenceOutsideUnicodeRange="character-reference-outside-unicode-range", +fD.controlCharacterReference="control-character-reference", +fD.noncharacterCharacterReference="noncharacter-character-reference", +fD.missingWhitespaceBeforeDoctypeName="missing-whitespace-before-doctype-name", +fD.missingDoctypeName="missing-doctype-name", +fD.invalidCharacterSequenceAfterDoctypeName="invalid-character-sequence-after-doctype-name", +fD.duplicateAttribute="duplicate-attribute", +fD.nonConformingDoctype="non-conforming-doctype", +fD.missingDoctype="missing-doctype", +fD.misplacedDoctype="misplaced-doctype",fD.endTagWithoutMatchingOpenElement="end-tag-without-matching-open-element", +fD.closingOfElementWithOpenChildElements="closing-of-element-with-open-child-elements", +fD.disallowedContentInNoscriptInHead="disallowed-content-in-noscript-in-head", +fD.openElementsLeftAfterEof="open-elements-left-after-eof", +fD.abandonedHeadElementChild="abandoned-head-element-child", +fD.misplacedStartTagForHeadElement="misplaced-start-tag-for-head-element", +fD.nestedNoscriptInHead="nested-noscript-in-head", +fD.eofInElementThatCanContainOnlyText="eof-in-element-that-can-contain-only-text" +;class mD{constructor(e){ +this.handler=e,this.html="",this.pos=-1,this.lastGapPos=-2, +this.gapStack=[],this.skipNextNewLine=!1, +this.lastChunkWritten=!1,this.endOfChunkHit=!1, +this.bufferWaterline=65536,this.isEol=!1, +this.lineStartPos=0,this.droppedBufferSize=0,this.line=1,this.lastErrOffset=-1} +get col(){return this.pos-this.lineStartPos+Number(this.lastGapPos!==this.pos)} +get offset(){return this.droppedBufferSize+this.pos}getError(e,t){ +const{line:n,col:r,offset:a}=this,o=r+t,i=a+t;return{code:e,startLine:n, +endLine:n,startCol:o,endCol:o,startOffset:i,endOffset:i}}_err(e){ +this.handler.onParseError&&this.lastErrOffset!==this.offset&&(this.lastErrOffset=this.offset, +this.handler.onParseError(this.getError(e,0)))}_addGap(){ +this.gapStack.push(this.lastGapPos),this.lastGapPos=this.pos} +_processSurrogate(e){if(this.pos!==this.html.length-1){ +const t=this.html.charCodeAt(this.pos+1);if(function(e){ +return e>=56320&&e<=57343 +}(t))return this.pos++,this._addGap(),1024*(e-55296)+9216+t +}else if(!this.lastChunkWritten)return this.endOfChunkHit=!0,nD.EOF +;return this._err(hD.surrogateInInputStream),e}willDropParsedChunk(){ +return this.pos>this.bufferWaterline}dropParsedChunk(){ +this.willDropParsedChunk()&&(this.html=this.html.substring(this.pos), +this.lineStartPos-=this.pos, +this.droppedBufferSize+=this.pos,this.pos=0,this.lastGapPos=-2, +this.gapStack.length=0)}write(e,t){ +this.html.length>0?this.html+=e:this.html=e,this.endOfChunkHit=!1, +this.lastChunkWritten=t}insertHtmlAtCurrentPos(e){ +this.html=this.html.substring(0,this.pos+1)+e+this.html.substring(this.pos+1), +this.endOfChunkHit=!1}startsWith(e,t){ +if(this.pos+e.length>this.html.length)return this.endOfChunkHit=!this.lastChunkWritten, +!1;if(t)return this.html.startsWith(e,this.pos);for(let n=0;n=this.html.length)return this.endOfChunkHit=!this.lastChunkWritten,nD.EOF +;const n=this.html.charCodeAt(t);return n===nD.CARRIAGE_RETURN?nD.LINE_FEED:n} +advance(){ +if(this.pos++,this.isEol&&(this.isEol=!1,this.line++,this.lineStartPos=this.pos), +this.pos>=this.html.length)return this.endOfChunkHit=!this.lastChunkWritten, +nD.EOF;let e=this.html.charCodeAt(this.pos) +;if(e===nD.CARRIAGE_RETURN)return this.isEol=!0, +this.skipNextNewLine=!0,nD.LINE_FEED +;if(e===nD.LINE_FEED&&(this.isEol=!0,this.skipNextNewLine))return this.line--, +this.skipNextNewLine=!1,this._addGap(),this.advance() +;this.skipNextNewLine=!1,uD(e)&&(e=this._processSurrogate(e)) +;return null===this.handler.onParseError||e>31&&e<127||e===nD.LINE_FEED||e===nD.CARRIAGE_RETURN||e>159&&e<64976||this._checkForProblematicCharacters(e), +e}_checkForProblematicCharacters(e){ +dD(e)?this._err(hD.controlCharacterInInputStream):pD(e)&&this._err(hD.noncharacterInInputStream) +}retreat(e){ +for(this.pos-=e;this.pos=0;n--)if(e.attrs[n].name===t)return e.attrs[n].value +;return null} +(vD=gD||(gD={}))[vD.CHARACTER=0]="CHARACTER",vD[vD.NULL_CHARACTER=1]="NULL_CHARACTER", +vD[vD.WHITESPACE_CHARACTER=2]="WHITESPACE_CHARACTER", +vD[vD.START_TAG=3]="START_TAG", +vD[vD.END_TAG=4]="END_TAG",vD[vD.COMMENT=5]="COMMENT", +vD[vD.DOCTYPE=6]="DOCTYPE",vD[vD.EOF=7]="EOF",vD[vD.HIBERNATION=8]="HIBERNATION" +;const yD=new Uint16Array('ᵁ<Õıʊҝջאٵ۞ޢߖࠏ੊ઑඡ๭༉༦჊ረዡᐕᒝᓃᓟᔥ\0\0\0\0\0\0ᕫᛍᦍᰒᷝ὾⁠↰⊍⏀⏻⑂⠤⤒ⴈ⹈⿎〖㊺㘹㞬㣾㨨㩱㫠㬮ࠀEMabcfglmnoprstu\\bfms„‹•˜¦³¹ÈÏlig耻Æ䃆P耻&䀦cute耻Á䃁reve;䄂Āiyx}rc耻Â䃂;䐐r;쀀𝔄rave耻À䃀pha;䎑acr;䄀d;橓Āgp¡on;䄄f;쀀𝔸plyFunction;恡ing耻Å䃅Ācs¾Ãr;쀀𝒜ign;扔ilde耻Ã䃃ml耻Ä䃄ЀaceforsuåûþėĜĢħĪĀcrêòkslash;或Ŷöø;櫧ed;挆y;䐑ƀcrtąċĔause;戵noullis;愬a;䎒r;쀀𝔅pf;쀀𝔹eve;䋘còēmpeq;扎܀HOacdefhilorsuōőŖƀƞƢƵƷƺǜȕɳɸɾcy;䐧PY耻©䂩ƀcpyŝŢźute;䄆Ā;iŧŨ拒talDifferentialD;慅leys;愭ȀaeioƉƎƔƘron;䄌dil耻Ç䃇rc;䄈nint;戰ot;䄊ĀdnƧƭilla;䂸terDot;䂷òſi;䎧rcleȀDMPTLJNjǑǖot;抙inus;抖lus;投imes;抗oĀcsǢǸkwiseContourIntegral;戲eCurlyĀDQȃȏoubleQuote;思uote;怙ȀlnpuȞȨɇɕonĀ;eȥȦ户;橴ƀgitȯȶȺruent;扡nt;戯ourIntegral;戮ĀfrɌɎ;愂oduct;成nterClockwiseContourIntegral;戳oss;樯cr;쀀𝒞pĀ;Cʄʅ拓ap;才րDJSZacefiosʠʬʰʴʸˋ˗ˡ˦̳ҍĀ;oŹʥtrahd;椑cy;䐂cy;䐅cy;䐏ƀgrsʿ˄ˇger;怡r;憡hv;櫤Āayː˕ron;䄎;䐔lĀ;t˝˞戇a;䎔r;쀀𝔇Āaf˫̧Ācm˰̢riticalȀADGT̖̜̀̆cute;䂴oŴ̋̍;䋙bleAcute;䋝rave;䁠ilde;䋜ond;拄ferentialD;慆Ѱ̽\0\0\0͔͂\0Ѕf;쀀𝔻ƀ;DE͈͉͍䂨ot;惜qual;扐blèCDLRUVͣͲ΂ϏϢϸontourIntegraìȹoɴ͹\0\0ͻ»͉nArrow;懓Āeo·ΤftƀARTΐΖΡrrow;懐ightArrow;懔eåˊngĀLRΫτeftĀARγιrrow;柸ightArrow;柺ightArrow;柹ightĀATϘϞrrow;懒ee;抨pɁϩ\0\0ϯrrow;懑ownArrow;懕erticalBar;戥ǹABLRTaВЪаўѿͼrrowƀ;BUНОТ憓ar;椓pArrow;懵reve;䌑eft˒к\0ц\0ѐightVector;楐eeVector;楞ectorĀ;Bљњ憽ar;楖ightǔѧ\0ѱeeVector;楟ectorĀ;BѺѻ懁ar;楗eeĀ;A҆҇护rrow;憧ĀctҒҗr;쀀𝒟rok;䄐ࠀNTacdfglmopqstuxҽӀӄӋӞӢӧӮӵԡԯԶՒ՝ՠեG;䅊H耻Ð䃐cute耻É䃉ƀaiyӒӗӜron;䄚rc耻Ê䃊;䐭ot;䄖r;쀀𝔈rave耻È䃈ement;戈ĀapӺӾcr;䄒tyɓԆ\0\0ԒmallSquare;旻erySmallSquare;斫ĀgpԦԪon;䄘f;쀀𝔼silon;䎕uĀaiԼՉlĀ;TՂՃ橵ilde;扂librium;懌Āci՗՚r;愰m;橳a;䎗ml耻Ë䃋Āipժկsts;戃onentialE;慇ʀcfiosօֈ֍ֲ׌y;䐤r;쀀𝔉lledɓ֗\0\0֣mallSquare;旼erySmallSquare;斪Ͱֺ\0ֿ\0\0ׄf;쀀𝔽All;戀riertrf;愱cò׋؀JTabcdfgorstר׬ׯ׺؀ؒؖ؛؝أ٬ٲcy;䐃耻>䀾mmaĀ;d׷׸䎓;䏜reve;䄞ƀeiy؇،ؐdil;䄢rc;䄜;䐓ot;䄠r;쀀𝔊;拙pf;쀀𝔾eater̀EFGLSTصلَٖٛ٦qualĀ;Lؾؿ扥ess;招ullEqual;执reater;檢ess;扷lantEqual;橾ilde;扳cr;쀀𝒢;扫ЀAacfiosuڅڋږڛڞڪھۊRDcy;䐪Āctڐڔek;䋇;䁞irc;䄤r;愌lbertSpace;愋ǰگ\0ڲf;愍izontalLine;攀Āctۃۅòکrok;䄦mpńېۘownHumðįqual;扏܀EJOacdfgmnostuۺ۾܃܇܎ܚܞܡܨ݄ݸދޏޕcy;䐕lig;䄲cy;䐁cute耻Í䃍Āiyܓܘrc耻Î䃎;䐘ot;䄰r;愑rave耻Ì䃌ƀ;apܠܯܿĀcgܴܷr;䄪inaryI;慈lieóϝǴ݉\0ݢĀ;eݍݎ戬Āgrݓݘral;戫section;拂isibleĀCTݬݲomma;恣imes;恢ƀgptݿރވon;䄮f;쀀𝕀a;䎙cr;愐ilde;䄨ǫޚ\0ޞcy;䐆l耻Ï䃏ʀcfosuެ޷޼߂ߐĀiyޱ޵rc;䄴;䐙r;쀀𝔍pf;쀀𝕁ǣ߇\0ߌr;쀀𝒥rcy;䐈kcy;䐄΀HJacfosߤߨ߽߬߱ࠂࠈcy;䐥cy;䐌ppa;䎚Āey߶߻dil;䄶;䐚r;쀀𝔎pf;쀀𝕂cr;쀀𝒦րJTaceflmostࠥࠩࠬࡐࡣ঳সে্਷ੇcy;䐉耻<䀼ʀcmnpr࠷࠼ࡁࡄࡍute;䄹bda;䎛g;柪lacetrf;愒r;憞ƀaeyࡗ࡜ࡡron;䄽dil;䄻;䐛Āfsࡨ॰tԀACDFRTUVarࡾࢩࢱࣦ࣠ࣼयज़ΐ४Ānrࢃ࢏gleBracket;柨rowƀ;BR࢙࢚࢞憐ar;懤ightArrow;懆eiling;挈oǵࢷ\0ࣃbleBracket;柦nǔࣈ\0࣒eeVector;楡ectorĀ;Bࣛࣜ懃ar;楙loor;挊ightĀAV࣯ࣵrrow;憔ector;楎Āerँगeƀ;AVउऊऐ抣rrow;憤ector;楚iangleƀ;BEतथऩ抲ar;槏qual;抴pƀDTVषूौownVector;楑eeVector;楠ectorĀ;Bॖॗ憿ar;楘ectorĀ;B॥०憼ar;楒ightáΜs̀EFGLSTॾঋকঝঢভqualGreater;拚ullEqual;扦reater;扶ess;檡lantEqual;橽ilde;扲r;쀀𝔏Ā;eঽা拘ftarrow;懚idot;䄿ƀnpw৔ਖਛgȀLRlr৞৷ਂਐeftĀAR০৬rrow;柵ightArrow;柷ightArrow;柶eftĀarγਊightáοightáϊf;쀀𝕃erĀLRਢਬeftArrow;憙ightArrow;憘ƀchtਾੀੂòࡌ;憰rok;䅁;扪Ѐacefiosuਗ਼੝੠੷੼અઋ઎p;椅y;䐜Ādl੥੯iumSpace;恟lintrf;愳r;쀀𝔐nusPlus;戓pf;쀀𝕄cò੶;䎜ҀJacefostuણધભીଔଙඑ඗ඞcy;䐊cute;䅃ƀaey઴હાron;䅇dil;䅅;䐝ƀgswે૰଎ativeƀMTV૓૟૨ediumSpace;怋hiĀcn૦૘ë૙eryThiî૙tedĀGL૸ଆreaterGreateòٳessLesóੈLine;䀊r;쀀𝔑ȀBnptଢନଷ଺reak;恠BreakingSpace;䂠f;愕ڀ;CDEGHLNPRSTV୕ୖ୪୼஡௫ఄ౞಄ದ೘ൡඅ櫬Āou୛୤ngruent;扢pCap;扭oubleVerticalBar;戦ƀlqxஃஊ஛ement;戉ualĀ;Tஒஓ扠ilde;쀀≂̸ists;戄reater΀;EFGLSTஶஷ஽௉௓௘௥扯qual;扱ullEqual;쀀≧̸reater;쀀≫̸ess;批lantEqual;쀀⩾̸ilde;扵umpń௲௽ownHump;쀀≎̸qual;쀀≏̸eĀfsఊధtTriangleƀ;BEచఛడ拪ar;쀀⧏̸qual;括s̀;EGLSTవశ఼ౄోౘ扮qual;扰reater;扸ess;쀀≪̸lantEqual;쀀⩽̸ilde;扴estedĀGL౨౹reaterGreater;쀀⪢̸essLess;쀀⪡̸recedesƀ;ESಒಓಛ技qual;쀀⪯̸lantEqual;拠ĀeiಫಹverseElement;戌ghtTriangleƀ;BEೋೌ೒拫ar;쀀⧐̸qual;拭ĀquೝഌuareSuĀbp೨೹setĀ;E೰ೳ쀀⊏̸qual;拢ersetĀ;Eഃആ쀀⊐̸qual;拣ƀbcpഓതൎsetĀ;Eഛഞ쀀⊂⃒qual;抈ceedsȀ;ESTലള഻െ抁qual;쀀⪰̸lantEqual;拡ilde;쀀≿̸ersetĀ;E൘൛쀀⊃⃒qual;抉ildeȀ;EFT൮൯൵ൿ扁qual;扄ullEqual;扇ilde;扉erticalBar;戤cr;쀀𝒩ilde耻Ñ䃑;䎝܀Eacdfgmoprstuvලෂ෉෕ෛ෠෧෼ขภยา฿ไlig;䅒cute耻Ó䃓Āiy෎ීrc耻Ô䃔;䐞blac;䅐r;쀀𝔒rave耻Ò䃒ƀaei෮ෲ෶cr;䅌ga;䎩cron;䎟pf;쀀𝕆enCurlyĀDQฎบoubleQuote;怜uote;怘;橔Āclวฬr;쀀𝒪ash耻Ø䃘iŬื฼de耻Õ䃕es;樷ml耻Ö䃖erĀBP๋๠Āar๐๓r;怾acĀek๚๜;揞et;掴arenthesis;揜Ҁacfhilors๿ງຊຏຒດຝະ໼rtialD;戂y;䐟r;쀀𝔓i;䎦;䎠usMinus;䂱Āipຢອncareplanåڝf;愙Ȁ;eio຺ູ໠໤檻cedesȀ;EST່້໏໚扺qual;檯lantEqual;扼ilde;找me;怳Ādp໩໮uct;戏ortionĀ;aȥ໹l;戝Āci༁༆r;쀀𝒫;䎨ȀUfos༑༖༛༟OT耻"䀢r;쀀𝔔pf;愚cr;쀀𝒬؀BEacefhiorsu༾གྷཇའཱིྦྷྪྭ႖ႩႴႾarr;椐G耻®䂮ƀcnrཎནབute;䅔g;柫rĀ;tཛྷཝ憠l;椖ƀaeyཧཬཱron;䅘dil;䅖;䐠Ā;vླྀཹ愜erseĀEUྂྙĀlq྇ྎement;戋uilibrium;懋pEquilibrium;楯r»ཹo;䎡ghtЀACDFTUVa࿁࿫࿳ဢဨၛႇϘĀnr࿆࿒gleBracket;柩rowƀ;BL࿜࿝࿡憒ar;懥eftArrow;懄eiling;按oǵ࿹\0စbleBracket;柧nǔည\0နeeVector;楝ectorĀ;Bဝသ懂ar;楕loor;挋Āerိ၃eƀ;AVဵံြ抢rrow;憦ector;楛iangleƀ;BEၐၑၕ抳ar;槐qual;抵pƀDTVၣၮၸownVector;楏eeVector;楜ectorĀ;Bႂႃ憾ar;楔ectorĀ;B႑႒懀ar;楓Āpuႛ႞f;愝ndImplies;楰ightarrow;懛ĀchႹႼr;愛;憱leDelayed;槴ڀHOacfhimoqstuფჱჷჽᄙᄞᅑᅖᅡᅧᆵᆻᆿĀCcჩხHcy;䐩y;䐨FTcy;䐬cute;䅚ʀ;aeiyᄈᄉᄎᄓᄗ檼ron;䅠dil;䅞rc;䅜;䐡r;쀀𝔖ortȀDLRUᄪᄴᄾᅉownArrow»ОeftArrow»࢚ightArrow»࿝pArrow;憑gma;䎣allCircle;战pf;쀀𝕊ɲᅭ\0\0ᅰt;戚areȀ;ISUᅻᅼᆉᆯ斡ntersection;抓uĀbpᆏᆞsetĀ;Eᆗᆘ抏qual;抑ersetĀ;Eᆨᆩ抐qual;抒nion;抔cr;쀀𝒮ar;拆ȀbcmpᇈᇛሉላĀ;sᇍᇎ拐etĀ;Eᇍᇕqual;抆ĀchᇠህeedsȀ;ESTᇭᇮᇴᇿ扻qual;檰lantEqual;扽ilde;承Tháྌ;我ƀ;esሒሓሣ拑rsetĀ;Eሜም抃qual;抇et»ሓրHRSacfhiorsሾቄ቉ቕ቞ቱቶኟዂወዑORN耻Þ䃞ADE;愢ĀHc቎ቒcy;䐋y;䐦Ābuቚቜ;䀉;䎤ƀaeyብቪቯron;䅤dil;䅢;䐢r;쀀𝔗Āeiቻ኉Dzኀ\0ኇefore;戴a;䎘Ācn኎ኘkSpace;쀀  Space;怉ldeȀ;EFTካኬኲኼ戼qual;扃ullEqual;扅ilde;扈pf;쀀𝕋ipleDot;惛Āctዖዛr;쀀𝒯rok;䅦ૡዷጎጚጦ\0ጬጱ\0\0\0\0\0ጸጽ፷ᎅ\0᏿ᐄᐊᐐĀcrዻጁute耻Ú䃚rĀ;oጇገ憟cir;楉rǣጓ\0጖y;䐎ve;䅬Āiyጞጣrc耻Û䃛;䐣blac;䅰r;쀀𝔘rave耻Ù䃙acr;䅪Ādiፁ፩erĀBPፈ፝Āarፍፐr;䁟acĀekፗፙ;揟et;掵arenthesis;揝onĀ;P፰፱拃lus;抎Āgp፻፿on;䅲f;쀀𝕌ЀADETadps᎕ᎮᎸᏄϨᏒᏗᏳrrowƀ;BDᅐᎠᎤar;椒ownArrow;懅ownArrow;憕quilibrium;楮eeĀ;AᏋᏌ报rrow;憥ownáϳerĀLRᏞᏨeftArrow;憖ightArrow;憗iĀ;lᏹᏺ䏒on;䎥ing;䅮cr;쀀𝒰ilde;䅨ml耻Ü䃜ҀDbcdefosvᐧᐬᐰᐳᐾᒅᒊᒐᒖash;披ar;櫫y;䐒ashĀ;lᐻᐼ抩;櫦Āerᑃᑅ;拁ƀbtyᑌᑐᑺar;怖Ā;iᑏᑕcalȀBLSTᑡᑥᑪᑴar;戣ine;䁼eparator;杘ilde;所ThinSpace;怊r;쀀𝔙pf;쀀𝕍cr;쀀𝒱dash;抪ʀcefosᒧᒬᒱᒶᒼirc;䅴dge;拀r;쀀𝔚pf;쀀𝕎cr;쀀𝒲Ȁfiosᓋᓐᓒᓘr;쀀𝔛;䎞pf;쀀𝕏cr;쀀𝒳ҀAIUacfosuᓱᓵᓹᓽᔄᔏᔔᔚᔠcy;䐯cy;䐇cy;䐮cute耻Ý䃝Āiyᔉᔍrc;䅶;䐫r;쀀𝔜pf;쀀𝕐cr;쀀𝒴ml;䅸ЀHacdefosᔵᔹᔿᕋᕏᕝᕠᕤcy;䐖cute;䅹Āayᕄᕉron;䅽;䐗ot;䅻Dzᕔ\0ᕛoWidtè૙a;䎖r;愨pf;愤cr;쀀𝒵௡ᖃᖊᖐ\0ᖰᖶᖿ\0\0\0\0ᗆᗛᗫᙟ᙭\0ᚕ᚛ᚲᚹ\0ᚾcute耻á䃡reve;䄃̀;Ediuyᖜᖝᖡᖣᖨᖭ戾;쀀∾̳;房rc耻â䃢te肻´̆;䐰lig耻æ䃦Ā;r²ᖺ;쀀𝔞rave耻à䃠ĀepᗊᗖĀfpᗏᗔsym;愵èᗓha;䎱ĀapᗟcĀclᗤᗧr;䄁g;樿ɤᗰ\0\0ᘊʀ;adsvᗺᗻᗿᘁᘇ戧nd;橕;橜lope;橘;橚΀;elmrszᘘᘙᘛᘞᘿᙏᙙ戠;榤e»ᘙsdĀ;aᘥᘦ戡ѡᘰᘲᘴᘶᘸᘺᘼᘾ;榨;榩;榪;榫;榬;榭;榮;榯tĀ;vᙅᙆ戟bĀ;dᙌᙍ抾;榝Āptᙔᙗh;戢»¹arr;捼Āgpᙣᙧon;䄅f;쀀𝕒΀;Eaeiop዁ᙻᙽᚂᚄᚇᚊ;橰cir;橯;扊d;手s;䀧roxĀ;e዁ᚒñᚃing耻å䃥ƀctyᚡᚦᚨr;쀀𝒶;䀪mpĀ;e዁ᚯñʈilde耻ã䃣ml耻ä䃤Āciᛂᛈoninôɲnt;樑ࠀNabcdefiklnoprsu᛭ᛱᜰ᜼ᝃᝈ᝸᝽០៦ᠹᡐᜍ᤽᥈ᥰot;櫭Ācrᛶ᜞kȀcepsᜀᜅᜍᜓong;扌psilon;䏶rime;怵imĀ;e᜚᜛戽q;拍Ŷᜢᜦee;抽edĀ;gᜬᜭ挅e»ᜭrkĀ;t፜᜷brk;掶Āoyᜁᝁ;䐱quo;怞ʀcmprtᝓ᝛ᝡᝤᝨausĀ;eĊĉptyv;榰séᜌnoõēƀahwᝯ᝱ᝳ;䎲;愶een;扬r;쀀𝔟g΀costuvwឍឝឳេ៕៛៞ƀaiuបពរðݠrc;旯p»፱ƀdptឤឨឭot;樀lus;樁imes;樂ɱឹ\0\0ើcup;樆ar;昅riangleĀdu៍្own;施p;斳plus;樄eåᑄåᒭarow;植ƀako៭ᠦᠵĀcn៲ᠣkƀlst៺֫᠂ozenge;槫riangleȀ;dlr᠒᠓᠘᠝斴own;斾eft;旂ight;斸k;搣Ʊᠫ\0ᠳƲᠯ\0ᠱ;斒;斑4;斓ck;斈ĀeoᠾᡍĀ;qᡃᡆ쀀=⃥uiv;쀀≡⃥t;挐Ȁptwxᡙᡞᡧᡬf;쀀𝕓Ā;tᏋᡣom»Ꮜtie;拈؀DHUVbdhmptuvᢅᢖᢪᢻᣗᣛᣬ᣿ᤅᤊᤐᤡȀLRlrᢎᢐᢒᢔ;敗;敔;敖;敓ʀ;DUduᢡᢢᢤᢦᢨ敐;敦;敩;敤;敧ȀLRlrᢳᢵᢷᢹ;敝;敚;敜;教΀;HLRhlrᣊᣋᣍᣏᣑᣓᣕ救;敬;散;敠;敫;敢;敟ox;槉ȀLRlrᣤᣦᣨᣪ;敕;敒;攐;攌ʀ;DUduڽ᣷᣹᣻᣽;敥;敨;攬;攴inus;抟lus;択imes;抠ȀLRlrᤙᤛᤝ᤟;敛;敘;攘;攔΀;HLRhlrᤰᤱᤳᤵᤷ᤻᤹攂;敪;敡;敞;攼;攤;攜Āevģ᥂bar耻¦䂦Ȁceioᥑᥖᥚᥠr;쀀𝒷mi;恏mĀ;e᜚᜜lƀ;bhᥨᥩᥫ䁜;槅sub;柈Ŭᥴ᥾lĀ;e᥹᥺怢t»᥺pƀ;Eeįᦅᦇ;檮Ā;qۜۛೡᦧ\0᧨ᨑᨕᨲ\0ᨷᩐ\0\0᪴\0\0᫁\0\0ᬡᬮ᭍᭒\0᯽\0ᰌƀcpr᦭ᦲ᧝ute;䄇̀;abcdsᦿᧀᧄ᧊᧕᧙戩nd;橄rcup;橉Āau᧏᧒p;橋p;橇ot;橀;쀀∩︀Āeo᧢᧥t;恁îړȀaeiu᧰᧻ᨁᨅǰ᧵\0᧸s;橍on;䄍dil耻ç䃧rc;䄉psĀ;sᨌᨍ橌m;橐ot;䄋ƀdmnᨛᨠᨦil肻¸ƭptyv;榲t脀¢;eᨭᨮ䂢räƲr;쀀𝔠ƀceiᨽᩀᩍy;䑇ckĀ;mᩇᩈ朓ark»ᩈ;䏇r΀;Ecefms᩟᩠ᩢᩫ᪤᪪᪮旋;槃ƀ;elᩩᩪᩭ䋆q;扗eɡᩴ\0\0᪈rrowĀlr᩼᪁eft;憺ight;憻ʀRSacd᪒᪔᪖᪚᪟»ཇ;擈st;抛irc;抚ash;抝nint;樐id;櫯cir;槂ubsĀ;u᪻᪼晣it»᪼ˬ᫇᫔᫺\0ᬊonĀ;eᫍᫎ䀺Ā;qÇÆɭ᫙\0\0᫢aĀ;t᫞᫟䀬;䁀ƀ;fl᫨᫩᫫戁îᅠeĀmx᫱᫶ent»᫩eóɍǧ᫾\0ᬇĀ;dኻᬂot;橭nôɆƀfryᬐᬔᬗ;쀀𝕔oäɔ脀©;sŕᬝr;愗Āaoᬥᬩrr;憵ss;朗Ācuᬲᬷr;쀀𝒸Ābpᬼ᭄Ā;eᭁᭂ櫏;櫑Ā;eᭉᭊ櫐;櫒dot;拯΀delprvw᭠᭬᭷ᮂᮬᯔ᯹arrĀlr᭨᭪;椸;椵ɰ᭲\0\0᭵r;拞c;拟arrĀ;p᭿ᮀ憶;椽̀;bcdosᮏᮐᮖᮡᮥᮨ截rcap;橈Āauᮛᮞp;橆p;橊ot;抍r;橅;쀀∪︀Ȁalrv᮵ᮿᯞᯣrrĀ;mᮼᮽ憷;椼yƀevwᯇᯔᯘqɰᯎ\0\0ᯒreã᭳uã᭵ee;拎edge;拏en耻¤䂤earrowĀlrᯮ᯳eft»ᮀight»ᮽeäᯝĀciᰁᰇoninôǷnt;戱lcty;挭ঀAHabcdefhijlorstuwz᰸᰻᰿ᱝᱩᱵᲊᲞᲬᲷ᳻᳿ᴍᵻᶑᶫᶻ᷆᷍rò΁ar;楥Ȁglrs᱈ᱍ᱒᱔ger;怠eth;愸òᄳhĀ;vᱚᱛ怐»ऊūᱡᱧarow;椏aã̕Āayᱮᱳron;䄏;䐴ƀ;ao̲ᱼᲄĀgrʿᲁr;懊tseq;橷ƀglmᲑᲔᲘ耻°䂰ta;䎴ptyv;榱ĀirᲣᲨsht;楿;쀀𝔡arĀlrᲳᲵ»ࣜ»သʀaegsv᳂͸᳖᳜᳠mƀ;oș᳊᳔ndĀ;ș᳑uit;晦amma;䏝in;拲ƀ;io᳧᳨᳸䃷de脀÷;o᳧ᳰntimes;拇nø᳷cy;䑒cɯᴆ\0\0ᴊrn;挞op;挍ʀlptuwᴘᴝᴢᵉᵕlar;䀤f;쀀𝕕ʀ;emps̋ᴭᴷᴽᵂqĀ;d͒ᴳot;扑inus;戸lus;戔quare;抡blebarwedgåúnƀadhᄮᵝᵧownarrowóᲃarpoonĀlrᵲᵶefôᲴighôᲶŢᵿᶅkaro÷གɯᶊ\0\0ᶎrn;挟op;挌ƀcotᶘᶣᶦĀryᶝᶡ;쀀𝒹;䑕l;槶rok;䄑Ādrᶰᶴot;拱iĀ;fᶺ᠖斿Āah᷀᷃ròЩaòྦangle;榦Āci᷒ᷕy;䑟grarr;柿ऀDacdefglmnopqrstuxḁḉḙḸոḼṉṡṾấắẽỡἪἷὄ὎὚ĀDoḆᴴoôᲉĀcsḎḔute耻é䃩ter;橮ȀaioyḢḧḱḶron;䄛rĀ;cḭḮ扖耻ê䃪lon;払;䑍ot;䄗ĀDrṁṅot;扒;쀀𝔢ƀ;rsṐṑṗ檚ave耻è䃨Ā;dṜṝ檖ot;檘Ȁ;ilsṪṫṲṴ檙nters;揧;愓Ā;dṹṺ檕ot;檗ƀapsẅẉẗcr;䄓tyƀ;svẒẓẕ戅et»ẓpĀ1;ẝẤijạả;怄;怅怃ĀgsẪẬ;䅋p;怂ĀgpẴẸon;䄙f;쀀𝕖ƀalsỄỎỒrĀ;sỊị拕l;槣us;橱iƀ;lvỚớở䎵on»ớ;䏵ȀcsuvỪỳἋἣĀioữḱrc»Ḯɩỹ\0\0ỻíՈantĀglἂἆtr»ṝess»Ṻƀaeiἒ἖Ἒls;䀽st;扟vĀ;DȵἠD;橸parsl;槥ĀDaἯἳot;打rr;楱ƀcdiἾὁỸr;愯oô͒ĀahὉὋ;䎷耻ð䃰Āmrὓὗl耻ë䃫o;悬ƀcipὡὤὧl;䀡sôծĀeoὬὴctatioîՙnentialåչৡᾒ\0ᾞ\0ᾡᾧ\0\0ῆῌ\0ΐ\0ῦῪ \0 ⁚llingdotseñṄy;䑄male;晀ƀilrᾭᾳ῁lig;耀ffiɩᾹ\0\0᾽g;耀ffig;耀ffl;쀀𝔣lig;耀filig;쀀fjƀaltῙ῜ῡt;晭ig;耀flns;斱of;䆒ǰ΅\0ῳf;쀀𝕗ĀakֿῷĀ;vῼ´拔;櫙artint;樍Āao‌⁕Ācs‑⁒ႉ‸⁅⁈\0⁐β•‥‧‪‬\0‮耻½䂽;慓耻¼䂼;慕;慙;慛Ƴ‴\0‶;慔;慖ʴ‾⁁\0\0⁃耻¾䂾;慗;慜5;慘ƶ⁌\0⁎;慚;慝8;慞l;恄wn;挢cr;쀀𝒻ࢀEabcdefgijlnorstv₂₉₟₥₰₴⃰⃵⃺⃿℃ℒℸ̗ℾ⅒↞Ā;lٍ₇;檌ƀcmpₐₕ₝ute;䇵maĀ;dₜ᳚䎳;檆reve;䄟Āiy₪₮rc;䄝;䐳ot;䄡Ȁ;lqsؾق₽⃉ƀ;qsؾٌ⃄lanô٥Ȁ;cdl٥⃒⃥⃕c;檩otĀ;o⃜⃝檀Ā;l⃢⃣檂;檄Ā;e⃪⃭쀀⋛︀s;檔r;쀀𝔤Ā;gٳ؛mel;愷cy;䑓Ȁ;Eajٚℌℎℐ;檒;檥;檤ȀEaesℛℝ℩ℴ;扩pĀ;p℣ℤ檊rox»ℤĀ;q℮ℯ檈Ā;q℮ℛim;拧pf;쀀𝕘Āci⅃ⅆr;愊mƀ;el٫ⅎ⅐;檎;檐茀>;cdlqr׮ⅠⅪⅮⅳⅹĀciⅥⅧ;檧r;橺ot;拗Par;榕uest;橼ʀadelsↄⅪ←ٖ↛ǰ↉\0↎proø₞r;楸qĀlqؿ↖lesó₈ií٫Āen↣↭rtneqq;쀀≩︀Å↪ԀAabcefkosy⇄⇇⇱⇵⇺∘∝∯≨≽ròΠȀilmr⇐⇔⇗⇛rsðᒄf»․ilôکĀdr⇠⇤cy;䑊ƀ;cwࣴ⇫⇯ir;楈;憭ar;意irc;䄥ƀalr∁∎∓rtsĀ;u∉∊晥it»∊lip;怦con;抹r;쀀𝔥sĀew∣∩arow;椥arow;椦ʀamopr∺∾≃≞≣rr;懿tht;戻kĀlr≉≓eftarrow;憩ightarrow;憪f;쀀𝕙bar;怕ƀclt≯≴≸r;쀀𝒽asè⇴rok;䄧Ābp⊂⊇ull;恃hen»ᱛૡ⊣\0⊪\0⊸⋅⋎\0⋕⋳\0\0⋸⌢⍧⍢⍿\0⎆⎪⎴cute耻í䃭ƀ;iyݱ⊰⊵rc耻î䃮;䐸Ācx⊼⊿y;䐵cl耻¡䂡ĀfrΟ⋉;쀀𝔦rave耻ì䃬Ȁ;inoܾ⋝⋩⋮Āin⋢⋦nt;樌t;戭fin;槜ta;愩lig;䄳ƀaop⋾⌚⌝ƀcgt⌅⌈⌗r;䄫ƀelpܟ⌏⌓inåގarôܠh;䄱f;抷ed;䆵ʀ;cfotӴ⌬⌱⌽⍁are;愅inĀ;t⌸⌹戞ie;槝doô⌙ʀ;celpݗ⍌⍐⍛⍡al;抺Āgr⍕⍙eróᕣã⍍arhk;樗rod;樼Ȁcgpt⍯⍲⍶⍻y;䑑on;䄯f;쀀𝕚a;䎹uest耻¿䂿Āci⎊⎏r;쀀𝒾nʀ;EdsvӴ⎛⎝⎡ӳ;拹ot;拵Ā;v⎦⎧拴;拳Ā;iݷ⎮lde;䄩ǫ⎸\0⎼cy;䑖l耻ï䃯̀cfmosu⏌⏗⏜⏡⏧⏵Āiy⏑⏕rc;䄵;䐹r;쀀𝔧ath;䈷pf;쀀𝕛ǣ⏬\0⏱r;쀀𝒿rcy;䑘kcy;䑔Ѐacfghjos␋␖␢␧␭␱␵␻ppaĀ;v␓␔䎺;䏰Āey␛␠dil;䄷;䐺r;쀀𝔨reen;䄸cy;䑅cy;䑜pf;쀀𝕜cr;쀀𝓀஀ABEHabcdefghjlmnoprstuv⑰⒁⒆⒍⒑┎┽╚▀♎♞♥♹♽⚚⚲⛘❝❨➋⟀⠁⠒ƀart⑷⑺⑼rò৆òΕail;椛arr;椎Ā;gঔ⒋;檋ar;楢ॣ⒥\0⒪\0⒱\0\0\0\0\0⒵Ⓔ\0ⓆⓈⓍ\0⓹ute;䄺mptyv;榴raîࡌbda;䎻gƀ;dlࢎⓁⓃ;榑åࢎ;檅uo耻«䂫rЀ;bfhlpst࢙ⓞⓦⓩ⓫⓮⓱⓵Ā;f࢝ⓣs;椟s;椝ë≒p;憫l;椹im;楳l;憢ƀ;ae⓿─┄檫il;椙Ā;s┉┊檭;쀀⪭︀ƀabr┕┙┝rr;椌rk;杲Āak┢┬cĀek┨┪;䁻;䁛Āes┱┳;榋lĀdu┹┻;榏;榍Ȁaeuy╆╋╖╘ron;䄾Ādi═╔il;䄼ìࢰâ┩;䐻Ȁcqrs╣╦╭╽a;椶uoĀ;rนᝆĀdu╲╷har;楧shar;楋h;憲ʀ;fgqs▋▌উ◳◿扤tʀahlrt▘▤▷◂◨rrowĀ;t࢙□aé⓶arpoonĀdu▯▴own»њp»०eftarrows;懇ightƀahs◍◖◞rrowĀ;sࣴࢧarpoonó྘quigarro÷⇰hreetimes;拋ƀ;qs▋ও◺lanôবʀ;cdgsব☊☍☝☨c;檨otĀ;o☔☕橿Ā;r☚☛檁;檃Ā;e☢☥쀀⋚︀s;檓ʀadegs☳☹☽♉♋pproøⓆot;拖qĀgq♃♅ôউgtò⒌ôছiíলƀilr♕࣡♚sht;楼;쀀𝔩Ā;Eজ♣;檑š♩♶rĀdu▲♮Ā;l॥♳;楪lk;斄cy;䑙ʀ;achtੈ⚈⚋⚑⚖rò◁orneòᴈard;楫ri;旺Āio⚟⚤dot;䅀ustĀ;a⚬⚭掰che»⚭ȀEaes⚻⚽⛉⛔;扨pĀ;p⛃⛄檉rox»⛄Ā;q⛎⛏檇Ā;q⛎⚻im;拦Ѐabnoptwz⛩⛴⛷✚✯❁❇❐Ānr⛮⛱g;柬r;懽rëࣁgƀlmr⛿✍✔eftĀar০✇ightá৲apsto;柼ightá৽parrowĀlr✥✩efô⓭ight;憬ƀafl✶✹✽r;榅;쀀𝕝us;樭imes;樴š❋❏st;戗áፎƀ;ef❗❘᠀旊nge»❘arĀ;l❤❥䀨t;榓ʀachmt❳❶❼➅➇ròࢨorneòᶌarĀ;d྘➃;業;怎ri;抿̀achiqt➘➝ੀ➢➮➻quo;怹r;쀀𝓁mƀ;egল➪➬;檍;檏Ābu┪➳oĀ;rฟ➹;怚rok;䅂萀<;cdhilqrࠫ⟒☹⟜⟠⟥⟪⟰Āci⟗⟙;檦r;橹reå◲mes;拉arr;楶uest;橻ĀPi⟵⟹ar;榖ƀ;ef⠀भ᠛旃rĀdu⠇⠍shar;楊har;楦Āen⠗⠡rtneqq;쀀≨︀Å⠞܀Dacdefhilnopsu⡀⡅⢂⢎⢓⢠⢥⢨⣚⣢⣤ઃ⣳⤂Dot;戺Ȁclpr⡎⡒⡣⡽r耻¯䂯Āet⡗⡙;時Ā;e⡞⡟朠se»⡟Ā;sျ⡨toȀ;dluျ⡳⡷⡻owîҌefôएðᏑker;斮Āoy⢇⢌mma;権;䐼ash;怔asuredangle»ᘦr;쀀𝔪o;愧ƀcdn⢯⢴⣉ro耻µ䂵Ȁ;acdᑤ⢽⣀⣄sôᚧir;櫰ot肻·Ƶusƀ;bd⣒ᤃ⣓戒Ā;uᴼ⣘;横ţ⣞⣡p;櫛ò−ðઁĀdp⣩⣮els;抧f;쀀𝕞Āct⣸⣽r;쀀𝓂pos»ᖝƀ;lm⤉⤊⤍䎼timap;抸ఀGLRVabcdefghijlmoprstuvw⥂⥓⥾⦉⦘⧚⧩⨕⨚⩘⩝⪃⪕⪤⪨⬄⬇⭄⭿⮮ⰴⱧⱼ⳩Āgt⥇⥋;쀀⋙̸Ā;v⥐௏쀀≫⃒ƀelt⥚⥲⥶ftĀar⥡⥧rrow;懍ightarrow;懎;쀀⋘̸Ā;v⥻ే쀀≪⃒ightarrow;懏ĀDd⦎⦓ash;抯ash;抮ʀbcnpt⦣⦧⦬⦱⧌la»˞ute;䅄g;쀀∠⃒ʀ;Eiop඄⦼⧀⧅⧈;쀀⩰̸d;쀀≋̸s;䅉roø඄urĀ;a⧓⧔普lĀ;s⧓ସdz⧟\0⧣p肻 ଷmpĀ;e௹ఀʀaeouy⧴⧾⨃⨐⨓ǰ⧹\0⧻;橃on;䅈dil;䅆ngĀ;dൾ⨊ot;쀀⩭̸p;橂;䐽ash;怓΀;Aadqsxஒ⨩⨭⨻⩁⩅⩐rr;懗rĀhr⨳⨶k;椤Ā;oᏲᏰot;쀀≐̸uiöୣĀei⩊⩎ar;椨í஘istĀ;s஠டr;쀀𝔫ȀEest௅⩦⩹⩼ƀ;qs஼⩭௡ƀ;qs஼௅⩴lanô௢ií௪Ā;rஶ⪁»ஷƀAap⪊⪍⪑rò⥱rr;憮ar;櫲ƀ;svྍ⪜ྌĀ;d⪡⪢拼;拺cy;䑚΀AEadest⪷⪺⪾⫂⫅⫶⫹rò⥦;쀀≦̸rr;憚r;急Ȁ;fqs఻⫎⫣⫯tĀar⫔⫙rro÷⫁ightarro÷⪐ƀ;qs఻⪺⫪lanôౕĀ;sౕ⫴»శiíౝĀ;rవ⫾iĀ;eచథiäඐĀpt⬌⬑f;쀀𝕟膀¬;in⬙⬚⬶䂬nȀ;Edvஉ⬤⬨⬮;쀀⋹̸ot;쀀⋵̸ǡஉ⬳⬵;拷;拶iĀ;vಸ⬼ǡಸ⭁⭃;拾;拽ƀaor⭋⭣⭩rȀ;ast୻⭕⭚⭟lleì୻l;쀀⫽⃥;쀀∂̸lint;樔ƀ;ceಒ⭰⭳uåಥĀ;cಘ⭸Ā;eಒ⭽ñಘȀAait⮈⮋⮝⮧rò⦈rrƀ;cw⮔⮕⮙憛;쀀⤳̸;쀀↝̸ghtarrow»⮕riĀ;eೋೖ΀chimpqu⮽⯍⯙⬄୸⯤⯯Ȁ;cerല⯆ഷ⯉uå൅;쀀𝓃ortɭ⬅\0\0⯖ará⭖mĀ;e൮⯟Ā;q൴൳suĀbp⯫⯭å೸åഋƀbcp⯶ⰑⰙȀ;Ees⯿ⰀഢⰄ抄;쀀⫅̸etĀ;eഛⰋqĀ;qണⰀcĀ;eലⰗñസȀ;EesⰢⰣൟⰧ抅;쀀⫆̸etĀ;e൘ⰮqĀ;qൠⰣȀgilrⰽⰿⱅⱇìௗlde耻ñ䃱çృiangleĀlrⱒⱜeftĀ;eచⱚñదightĀ;eೋⱥñ೗Ā;mⱬⱭ䎽ƀ;esⱴⱵⱹ䀣ro;愖p;怇ҀDHadgilrsⲏⲔⲙⲞⲣⲰⲶⳓⳣash;抭arr;椄p;쀀≍⃒ash;抬ĀetⲨⲬ;쀀≥⃒;쀀>⃒nfin;槞ƀAetⲽⳁⳅrr;椂;쀀≤⃒Ā;rⳊⳍ쀀<⃒ie;쀀⊴⃒ĀAtⳘⳜrr;椃rie;쀀⊵⃒im;쀀∼⃒ƀAan⳰⳴ⴂrr;懖rĀhr⳺⳽k;椣Ā;oᏧᏥear;椧ቓ᪕\0\0\0\0\0\0\0\0\0\0\0\0\0ⴭ\0ⴸⵈⵠⵥ⵲ⶄᬇ\0\0ⶍⶫ\0ⷈⷎ\0ⷜ⸙⸫⸾⹃Ācsⴱ᪗ute耻ó䃳ĀiyⴼⵅrĀ;c᪞ⵂ耻ô䃴;䐾ʀabios᪠ⵒⵗLjⵚlac;䅑v;樸old;榼lig;䅓Ācr⵩⵭ir;榿;쀀𝔬ͯ⵹\0\0⵼\0ⶂn;䋛ave耻ò䃲;槁Ābmⶈ෴ar;榵Ȁacitⶕ⶘ⶥⶨrò᪀Āir⶝ⶠr;榾oss;榻nå๒;槀ƀaeiⶱⶵⶹcr;䅍ga;䏉ƀcdnⷀⷅǍron;䎿;榶pf;쀀𝕠ƀaelⷔ⷗ǒr;榷rp;榹΀;adiosvⷪⷫⷮ⸈⸍⸐⸖戨rò᪆Ȁ;efmⷷⷸ⸂⸅橝rĀ;oⷾⷿ愴f»ⷿ耻ª䂪耻º䂺gof;抶r;橖lope;橗;橛ƀclo⸟⸡⸧ò⸁ash耻ø䃸l;折iŬⸯ⸴de耻õ䃵esĀ;aǛ⸺s;樶ml耻ö䃶bar;挽ૡ⹞\0⹽\0⺀⺝\0⺢⺹\0\0⻋ຜ\0⼓\0\0⼫⾼\0⿈rȀ;astЃ⹧⹲຅脀¶;l⹭⹮䂶leìЃɩ⹸\0\0⹻m;櫳;櫽y;䐿rʀcimpt⺋⺏⺓ᡥ⺗nt;䀥od;䀮il;怰enk;怱r;쀀𝔭ƀimo⺨⺰⺴Ā;v⺭⺮䏆;䏕maô੶ne;明ƀ;tv⺿⻀⻈䏀chfork»´;䏖Āau⻏⻟nĀck⻕⻝kĀ;h⇴⻛;愎ö⇴sҀ;abcdemst⻳⻴ᤈ⻹⻽⼄⼆⼊⼎䀫cir;樣ir;樢Āouᵀ⼂;樥;橲n肻±ຝim;樦wo;樧ƀipu⼙⼠⼥ntint;樕f;쀀𝕡nd耻£䂣Ԁ;Eaceinosu່⼿⽁⽄⽇⾁⾉⾒⽾⾶;檳p;檷uå໙Ā;c໎⽌̀;acens່⽙⽟⽦⽨⽾pproø⽃urlyeñ໙ñ໎ƀaes⽯⽶⽺pprox;檹qq;檵im;拨iíໟmeĀ;s⾈ຮ怲ƀEas⽸⾐⽺ð⽵ƀdfp໬⾙⾯ƀals⾠⾥⾪lar;挮ine;挒urf;挓Ā;t໻⾴ï໻rel;抰Āci⿀⿅r;쀀𝓅;䏈ncsp;怈̀fiopsu⿚⋢⿟⿥⿫⿱r;쀀𝔮pf;쀀𝕢rime;恗cr;쀀𝓆ƀaeo⿸〉〓tĀei⿾々rnionóڰnt;樖stĀ;e【】䀿ñἙô༔઀ABHabcdefhilmnoprstux぀けさすムㄎㄫㅇㅢㅲㆎ㈆㈕㈤㈩㉘㉮㉲㊐㊰㊷ƀartぇおがròႳòϝail;検aròᱥar;楤΀cdenqrtとふへみわゔヌĀeuねぱ;쀀∽̱te;䅕iãᅮmptyv;榳gȀ;del࿑らるろ;榒;榥å࿑uo耻»䂻rր;abcfhlpstw࿜ガクシスゼゾダッデナp;極Ā;f࿠ゴs;椠;椳s;椞ë≝ð✮l;楅im;楴l;憣;憝Āaiパフil;椚oĀ;nホボ戶aló༞ƀabrョリヮrò៥rk;杳ĀakンヽcĀekヹ・;䁽;䁝Āes㄂㄄;榌lĀduㄊㄌ;榎;榐Ȁaeuyㄗㄜㄧㄩron;䅙Ādiㄡㄥil;䅗ì࿲âヺ;䑀Ȁclqsㄴㄷㄽㅄa;椷dhar;楩uoĀ;rȎȍh;憳ƀacgㅎㅟངlȀ;ipsླྀㅘㅛႜnåႻarôྩt;断ƀilrㅩဣㅮsht;楽;쀀𝔯ĀaoㅷㆆrĀduㅽㅿ»ѻĀ;l႑ㆄ;楬Ā;vㆋㆌ䏁;䏱ƀgns㆕ㇹㇼht̀ahlrstㆤㆰ㇂㇘㇤㇮rrowĀ;t࿜ㆭaéトarpoonĀduㆻㆿowîㅾp»႒eftĀah㇊㇐rrowó࿪arpoonóՑightarrows;應quigarro÷ニhreetimes;拌g;䋚ingdotseñἲƀahm㈍㈐㈓rò࿪aòՑ;怏oustĀ;a㈞㈟掱che»㈟mid;櫮Ȁabpt㈲㈽㉀㉒Ānr㈷㈺g;柭r;懾rëဃƀafl㉇㉊㉎r;榆;쀀𝕣us;樮imes;樵Āap㉝㉧rĀ;g㉣㉤䀩t;榔olint;樒arò㇣Ȁachq㉻㊀Ⴜ㊅quo;怺r;쀀𝓇Ābu・㊊oĀ;rȔȓƀhir㊗㊛㊠reåㇸmes;拊iȀ;efl㊪ၙᠡ㊫方tri;槎luhar;楨;愞ൡ㋕㋛㋟㌬㌸㍱\0㍺㎤\0\0㏬㏰\0㐨㑈㑚㒭㒱㓊㓱\0㘖\0\0㘳cute;䅛quï➺Ԁ;Eaceinpsyᇭ㋳㋵㋿㌂㌋㌏㌟㌦㌩;檴ǰ㋺\0㋼;檸on;䅡uåᇾĀ;dᇳ㌇il;䅟rc;䅝ƀEas㌖㌘㌛;檶p;檺im;择olint;樓iíሄ;䑁otƀ;be㌴ᵇ㌵担;橦΀Aacmstx㍆㍊㍗㍛㍞㍣㍭rr;懘rĀhr㍐㍒ë∨Ā;oਸ਼਴t耻§䂧i;䀻war;椩mĀin㍩ðnuóñt;朶rĀ;o㍶⁕쀀𝔰Ȁacoy㎂㎆㎑㎠rp;景Āhy㎋㎏cy;䑉;䑈rtɭ㎙\0\0㎜iäᑤaraì⹯耻­䂭Āgm㎨㎴maƀ;fv㎱㎲㎲䏃;䏂Ѐ;deglnprካ㏅㏉㏎㏖㏞㏡㏦ot;橪Ā;q኱ኰĀ;E㏓㏔檞;檠Ā;E㏛㏜檝;檟e;扆lus;樤arr;楲aròᄽȀaeit㏸㐈㐏㐗Āls㏽㐄lsetmé㍪hp;樳parsl;槤Ādlᑣ㐔e;挣Ā;e㐜㐝檪Ā;s㐢㐣檬;쀀⪬︀ƀflp㐮㐳㑂tcy;䑌Ā;b㐸㐹䀯Ā;a㐾㐿槄r;挿f;쀀𝕤aĀdr㑍ЂesĀ;u㑔㑕晠it»㑕ƀcsu㑠㑹㒟Āau㑥㑯pĀ;sᆈ㑫;쀀⊓︀pĀ;sᆴ㑵;쀀⊔︀uĀbp㑿㒏ƀ;esᆗᆜ㒆etĀ;eᆗ㒍ñᆝƀ;esᆨᆭ㒖etĀ;eᆨ㒝ñᆮƀ;afᅻ㒦ְrť㒫ֱ»ᅼaròᅈȀcemt㒹㒾㓂㓅r;쀀𝓈tmîñiì㐕aræᆾĀar㓎㓕rĀ;f㓔ឿ昆Āan㓚㓭ightĀep㓣㓪psiloîỠhé⺯s»⡒ʀbcmnp㓻㕞ሉ㖋㖎Ҁ;Edemnprs㔎㔏㔑㔕㔞㔣㔬㔱㔶抂;櫅ot;檽Ā;dᇚ㔚ot;櫃ult;櫁ĀEe㔨㔪;櫋;把lus;檿arr;楹ƀeiu㔽㕒㕕tƀ;en㔎㕅㕋qĀ;qᇚ㔏eqĀ;q㔫㔨m;櫇Ābp㕚㕜;櫕;櫓c̀;acensᇭ㕬㕲㕹㕻㌦pproø㋺urlyeñᇾñᇳƀaes㖂㖈㌛pproø㌚qñ㌗g;晪ڀ123;Edehlmnps㖩㖬㖯ሜ㖲㖴㗀㗉㗕㗚㗟㗨㗭耻¹䂹耻²䂲耻³䂳;櫆Āos㖹㖼t;檾ub;櫘Ā;dሢ㗅ot;櫄sĀou㗏㗒l;柉b;櫗arr;楻ult;櫂ĀEe㗤㗦;櫌;抋lus;櫀ƀeiu㗴㘉㘌tƀ;enሜ㗼㘂qĀ;qሢ㖲eqĀ;q㗧㗤m;櫈Ābp㘑㘓;櫔;櫖ƀAan㘜㘠㘭rr;懙rĀhr㘦㘨ë∮Ā;oਫ਩war;椪lig耻ß䃟௡㙑㙝㙠ዎ㙳㙹\0㙾㛂\0\0\0\0\0㛛㜃\0㜉㝬\0\0\0㞇ɲ㙖\0\0㙛get;挖;䏄rë๟ƀaey㙦㙫㙰ron;䅥dil;䅣;䑂lrec;挕r;쀀𝔱Ȁeiko㚆㚝㚵㚼Dz㚋\0㚑eĀ4fኄኁaƀ;sv㚘㚙㚛䎸ym;䏑Ācn㚢㚲kĀas㚨㚮pproø዁im»ኬsðኞĀas㚺㚮ð዁rn耻þ䃾Ǭ̟㛆⋧es膀×;bd㛏㛐㛘䃗Ā;aᤏ㛕r;樱;樰ƀeps㛡㛣㜀á⩍Ȁ;bcf҆㛬㛰㛴ot;挶ir;櫱Ā;o㛹㛼쀀𝕥rk;櫚á㍢rime;怴ƀaip㜏㜒㝤dåቈ΀adempst㜡㝍㝀㝑㝗㝜㝟ngleʀ;dlqr㜰㜱㜶㝀㝂斵own»ᶻeftĀ;e⠀㜾ñम;扜ightĀ;e㊪㝋ñၚot;旬inus;樺lus;樹b;槍ime;樻ezium;揢ƀcht㝲㝽㞁Āry㝷㝻;쀀𝓉;䑆cy;䑛rok;䅧Āio㞋㞎xô᝷headĀlr㞗㞠eftarro÷ࡏightarrow»ཝऀAHabcdfghlmoprstuw㟐㟓㟗㟤㟰㟼㠎㠜㠣㠴㡑㡝㡫㢩㣌㣒㣪㣶ròϭar;楣Ācr㟜㟢ute耻ú䃺òᅐrǣ㟪\0㟭y;䑞ve;䅭Āiy㟵㟺rc耻û䃻;䑃ƀabh㠃㠆㠋ròᎭlac;䅱aòᏃĀir㠓㠘sht;楾;쀀𝔲rave耻ù䃹š㠧㠱rĀlr㠬㠮»ॗ»ႃlk;斀Āct㠹㡍ɯ㠿\0\0㡊rnĀ;e㡅㡆挜r»㡆op;挏ri;旸Āal㡖㡚cr;䅫肻¨͉Āgp㡢㡦on;䅳f;쀀𝕦̀adhlsuᅋ㡸㡽፲㢑㢠ownáᎳarpoonĀlr㢈㢌efô㠭ighô㠯iƀ;hl㢙㢚㢜䏅»ᏺon»㢚parrows;懈ƀcit㢰㣄㣈ɯ㢶\0\0㣁rnĀ;e㢼㢽挝r»㢽op;挎ng;䅯ri;旹cr;쀀𝓊ƀdir㣙㣝㣢ot;拰lde;䅩iĀ;f㜰㣨»᠓Āam㣯㣲rò㢨l耻ü䃼angle;榧ހABDacdeflnoprsz㤜㤟㤩㤭㦵㦸㦽㧟㧤㧨㧳㧹㧽㨁㨠ròϷarĀ;v㤦㤧櫨;櫩asèϡĀnr㤲㤷grt;榜΀eknprst㓣㥆㥋㥒㥝㥤㦖appá␕othinçẖƀhir㓫⻈㥙opô⾵Ā;hᎷ㥢ïㆍĀiu㥩㥭gmá㎳Ābp㥲㦄setneqĀ;q㥽㦀쀀⊊︀;쀀⫋︀setneqĀ;q㦏㦒쀀⊋︀;쀀⫌︀Āhr㦛㦟etá㚜iangleĀlr㦪㦯eft»थight»ၑy;䐲ash»ံƀelr㧄㧒㧗ƀ;beⷪ㧋㧏ar;抻q;扚lip;拮Ābt㧜ᑨaòᑩr;쀀𝔳tré㦮suĀbp㧯㧱»ജ»൙pf;쀀𝕧roð໻tré㦴Ācu㨆㨋r;쀀𝓋Ābp㨐㨘nĀEe㦀㨖»㥾nĀEe㦒㨞»㦐igzag;榚΀cefoprs㨶㨻㩖㩛㩔㩡㩪irc;䅵Ādi㩀㩑Ābg㩅㩉ar;機eĀ;qᗺ㩏;扙erp;愘r;쀀𝔴pf;쀀𝕨Ā;eᑹ㩦atèᑹcr;쀀𝓌ૣណ㪇\0㪋\0㪐㪛\0\0㪝㪨㪫㪯\0\0㫃㫎\0㫘ៜ៟tré៑r;쀀𝔵ĀAa㪔㪗ròσrò৶;䎾ĀAa㪡㪤ròθrò৫að✓is;拻ƀdptឤ㪵㪾Āfl㪺ឩ;쀀𝕩imåឲĀAa㫇㫊ròώròਁĀcq㫒ីr;쀀𝓍Āpt៖㫜ré។Ѐacefiosu㫰㫽㬈㬌㬑㬕㬛㬡cĀuy㫶㫻te耻ý䃽;䑏Āiy㬂㬆rc;䅷;䑋n耻¥䂥r;쀀𝔶cy;䑗pf;쀀𝕪cr;쀀𝓎Ācm㬦㬩y;䑎l耻ÿ䃿Ԁacdefhiosw㭂㭈㭔㭘㭤㭩㭭㭴㭺㮀cute;䅺Āay㭍㭒ron;䅾;䐷ot;䅼Āet㭝㭡træᕟa;䎶r;쀀𝔷cy;䐶grarr;懝pf;쀀𝕫cr;쀀𝓏Ājn㮅㮇;怍j;怌'.split("").map((e=>e.charCodeAt(0)))),OD=new Map([[0,65533],[128,8364],[130,8218],[131,402],[132,8222],[133,8230],[134,8224],[135,8225],[136,710],[137,8240],[138,352],[139,8249],[140,338],[142,381],[145,8216],[146,8217],[147,8220],[148,8221],[149,8226],[150,8211],[151,8212],[152,732],[153,8482],[154,353],[155,8250],[156,339],[158,382],[159,376]]) +;var wD,xD +;(xD=wD||(wD={}))[xD.NUM=35]="NUM",xD[xD.SEMI=59]="SEMI",xD[xD.EQUALS=61]="EQUALS", +xD[xD.ZERO=48]="ZERO", +xD[xD.NINE=57]="NINE",xD[xD.LOWER_A=97]="LOWER_A",xD[xD.LOWER_F=102]="LOWER_F", +xD[xD.LOWER_X=120]="LOWER_X", +xD[xD.LOWER_Z=122]="LOWER_Z",xD[xD.UPPER_A=65]="UPPER_A", +xD[xD.UPPER_F=70]="UPPER_F",xD[xD.UPPER_Z=90]="UPPER_Z" +;var kD,SD,_D,AD,TD,ED,CD,$D,PD,ID,DD,MD,ND,RD,LD,BD;function jD(e){ +return e>=wD.ZERO&&e<=wD.NINE}function UD(e){return e===wD.EQUALS||function(e){ +return e>=wD.UPPER_A&&e<=wD.UPPER_Z||e>=wD.LOWER_A&&e<=wD.LOWER_Z||jD(e)}(e)} +(SD=kD||(kD={}))[SD.VALUE_LENGTH=49152]="VALUE_LENGTH", +SD[SD.BRANCH_LENGTH=16256]="BRANCH_LENGTH", +SD[SD.JUMP_TABLE=127]="JUMP_TABLE",(AD=_D||(_D={}))[AD.EntityStart=0]="EntityStart", +AD[AD.NumericStart=1]="NumericStart", +AD[AD.NumericDecimal=2]="NumericDecimal",AD[AD.NumericHex=3]="NumericHex", +AD[AD.NamedEntity=4]="NamedEntity", +(ED=TD||(TD={}))[ED.Legacy=0]="Legacy",ED[ED.Strict=1]="Strict", +ED[ED.Attribute=2]="Attribute";class zD{constructor(e,t,n){ +this.decodeTree=e,this.emitCodePoint=t, +this.errors=n,this.state=_D.EntityStart,this.consumed=1, +this.result=0,this.treeIndex=0,this.excess=1,this.decodeMode=TD.Strict} +startEntity(e){ +this.decodeMode=e,this.state=_D.EntityStart,this.result=0,this.treeIndex=0, +this.excess=1,this.consumed=1}write(e,t){switch(this.state){case _D.EntityStart: +return e.charCodeAt(t)===wD.NUM?(this.state=_D.NumericStart, +this.consumed+=1,this.stateNumericStart(e,t+1)):(this.state=_D.NamedEntity, +this.stateNamedEntity(e,t));case _D.NumericStart: +return this.stateNumericStart(e,t);case _D.NumericDecimal: +return this.stateNumericDecimal(e,t);case _D.NumericHex: +return this.stateNumericHex(e,t);case _D.NamedEntity: +return this.stateNamedEntity(e,t)}}stateNumericStart(e,t){ +return t>=e.length?-1:(32|e.charCodeAt(t))===wD.LOWER_X?(this.state=_D.NumericHex, +this.consumed+=1, +this.stateNumericHex(e,t+1)):(this.state=_D.NumericDecimal,this.stateNumericDecimal(e,t)) +}addToNumericResult(e,t,n,r){if(t!==n){const a=n-t +;this.result=this.result*Math.pow(r,a)+Number.parseInt(e.substr(t,a),r), +this.consumed+=a}}stateNumericHex(e,t){const n=t;for(;t=wD.UPPER_A&&r<=wD.UPPER_F||r>=wD.LOWER_A&&r<=wD.LOWER_F)))return this.addToNumericResult(e,n,t,16), +this.emitNumericEntity(a,3);t+=1}var r;return this.addToNumericResult(e,n,t,16), +-1}stateNumericDecimal(e,t){const n=t;for(;t=55296&&e<=57343||e>1114111?65533:null!==(t=OD.get(e))&&void 0!==t?t:e +}(this.result),this.consumed), +this.errors&&(e!==wD.SEMI&&this.errors.missingSemicolonAfterCharacterReference(), +this.errors.validateNumericCharacterReference(this.result)),this.consumed} +stateNamedEntity(e,t){const{decodeTree:n}=this +;let r=n[this.treeIndex],a=(r&kD.VALUE_LENGTH)>>14 +;for(;t>14,0!==a){ +if(o===wD.SEMI)return this.emitNamedEntityData(this.treeIndex,a,this.consumed+this.excess) +;this.decodeMode!==TD.Strict&&(this.result=this.treeIndex, +this.consumed+=this.excess,this.excess=0)}}return-1} +emitNotTerminatedNamedEntity(){var e +;const{result:t,decodeTree:n}=this,r=(n[t]&kD.VALUE_LENGTH)>>14 +;return this.emitNamedEntityData(t,r,this.consumed), +null===(e=this.errors)||void 0===e||e.missingSemicolonAfterCharacterReference(), +this.consumed}emitNamedEntityData(e,t,n){const{decodeTree:r}=this +;return this.emitCodePoint(1===t?r[e]&~kD.VALUE_LENGTH:r[e+1],n), +3===t&&this.emitCodePoint(r[e+2],n),n}end(){var e;switch(this.state){ +case _D.NamedEntity: +return 0===this.result||this.decodeMode===TD.Attribute&&this.result!==this.treeIndex?0:this.emitNotTerminatedNamedEntity() +;case _D.NumericDecimal:return this.emitNumericEntity(0,2);case _D.NumericHex: +return this.emitNumericEntity(0,3);case _D.NumericStart: +return null===(e=this.errors)||void 0===e||e.absenceOfDigitsInNumericCharacterReference(this.consumed), +0;case _D.EntityStart:return 0}}}function ZD(e,t,n,r){ +const a=(t&kD.BRANCH_LENGTH)>>7,o=t&kD.JUMP_TABLE +;if(0===a)return 0!==o&&r===o?n:-1;if(o){const t=r-o +;return t<0||t>=a?-1:e[n+t]-1}let i=n,s=i+a-1;for(;i<=s;){const t=i+s>>>1,n=e[t] +;if(nr))return e[t+a];s=t-1}}return-1} +($D=CD||(CD={})).HTML="http://www.w3.org/1999/xhtml", +$D.MATHML="http://www.w3.org/1998/Math/MathML", +$D.SVG="http://www.w3.org/2000/svg", +$D.XLINK="http://www.w3.org/1999/xlink",$D.XML="http://www.w3.org/XML/1998/namespace", +$D.XMLNS="http://www.w3.org/2000/xmlns/", +(ID=PD||(PD={})).TYPE="type",ID.ACTION="action", +ID.ENCODING="encoding",ID.PROMPT="prompt", +ID.NAME="name",ID.COLOR="color",ID.FACE="face", +ID.SIZE="size",(MD=DD||(DD={})).NO_QUIRKS="no-quirks", +MD.QUIRKS="quirks",MD.LIMITED_QUIRKS="limited-quirks", +(RD=ND||(ND={})).A="a",RD.ADDRESS="address", +RD.ANNOTATION_XML="annotation-xml",RD.APPLET="applet", +RD.AREA="area",RD.ARTICLE="article", +RD.ASIDE="aside",RD.B="b",RD.BASE="base",RD.BASEFONT="basefont", +RD.BGSOUND="bgsound", +RD.BIG="big",RD.BLOCKQUOTE="blockquote",RD.BODY="body",RD.BR="br", +RD.BUTTON="button", +RD.CAPTION="caption",RD.CENTER="center",RD.CODE="code",RD.COL="col", +RD.COLGROUP="colgroup", +RD.DD="dd",RD.DESC="desc",RD.DETAILS="details",RD.DIALOG="dialog", +RD.DIR="dir",RD.DIV="div", +RD.DL="dl",RD.DT="dt",RD.EM="em",RD.EMBED="embed",RD.FIELDSET="fieldset", +RD.FIGCAPTION="figcaption",RD.FIGURE="figure",RD.FONT="font",RD.FOOTER="footer", +RD.FOREIGN_OBJECT="foreignObject", +RD.FORM="form",RD.FRAME="frame",RD.FRAMESET="frameset", +RD.H1="h1",RD.H2="h2",RD.H3="h3", +RD.H4="h4",RD.H5="h5",RD.H6="h6",RD.HEAD="head", +RD.HEADER="header",RD.HGROUP="hgroup", +RD.HR="hr",RD.HTML="html",RD.I="i",RD.IMG="img", +RD.IMAGE="image",RD.INPUT="input", +RD.IFRAME="iframe",RD.KEYGEN="keygen",RD.LABEL="label", +RD.LI="li",RD.LINK="link", +RD.LISTING="listing",RD.MAIN="main",RD.MALIGNMARK="malignmark", +RD.MARQUEE="marquee", +RD.MATH="math",RD.MENU="menu",RD.META="meta",RD.MGLYPH="mglyph", +RD.MI="mi",RD.MO="mo", +RD.MN="mn",RD.MS="ms",RD.MTEXT="mtext",RD.NAV="nav",RD.NOBR="nobr", +RD.NOFRAMES="noframes", +RD.NOEMBED="noembed",RD.NOSCRIPT="noscript",RD.OBJECT="object", +RD.OL="ol",RD.OPTGROUP="optgroup", +RD.OPTION="option",RD.P="p",RD.PARAM="param",RD.PLAINTEXT="plaintext", +RD.PRE="pre", +RD.RB="rb",RD.RP="rp",RD.RT="rt",RD.RTC="rtc",RD.RUBY="ruby",RD.S="s", +RD.SCRIPT="script", +RD.SEARCH="search",RD.SECTION="section",RD.SELECT="select",RD.SOURCE="source", +RD.SMALL="small", +RD.SPAN="span",RD.STRIKE="strike",RD.STRONG="strong",RD.STYLE="style", +RD.SUB="sub", +RD.SUMMARY="summary",RD.SUP="sup",RD.TABLE="table",RD.TBODY="tbody", +RD.TEMPLATE="template", +RD.TEXTAREA="textarea",RD.TFOOT="tfoot",RD.TD="td",RD.TH="th", +RD.THEAD="thead",RD.TITLE="title", +RD.TR="tr",RD.TRACK="track",RD.TT="tt",RD.U="u", +RD.UL="ul",RD.SVG="svg",RD.VAR="var", +RD.WBR="wbr",RD.XMP="xmp",(BD=LD||(LD={}))[BD.UNKNOWN=0]="UNKNOWN", +BD[BD.A=1]="A", +BD[BD.ADDRESS=2]="ADDRESS",BD[BD.ANNOTATION_XML=3]="ANNOTATION_XML", +BD[BD.APPLET=4]="APPLET", +BD[BD.AREA=5]="AREA",BD[BD.ARTICLE=6]="ARTICLE",BD[BD.ASIDE=7]="ASIDE", +BD[BD.B=8]="B", +BD[BD.BASE=9]="BASE",BD[BD.BASEFONT=10]="BASEFONT",BD[BD.BGSOUND=11]="BGSOUND", +BD[BD.BIG=12]="BIG", +BD[BD.BLOCKQUOTE=13]="BLOCKQUOTE",BD[BD.BODY=14]="BODY",BD[BD.BR=15]="BR", +BD[BD.BUTTON=16]="BUTTON",BD[BD.CAPTION=17]="CAPTION",BD[BD.CENTER=18]="CENTER", +BD[BD.CODE=19]="CODE", +BD[BD.COL=20]="COL",BD[BD.COLGROUP=21]="COLGROUP",BD[BD.DD=22]="DD", +BD[BD.DESC=23]="DESC", +BD[BD.DETAILS=24]="DETAILS",BD[BD.DIALOG=25]="DIALOG",BD[BD.DIR=26]="DIR", +BD[BD.DIV=27]="DIV", +BD[BD.DL=28]="DL",BD[BD.DT=29]="DT",BD[BD.EM=30]="EM",BD[BD.EMBED=31]="EMBED", +BD[BD.FIELDSET=32]="FIELDSET", +BD[BD.FIGCAPTION=33]="FIGCAPTION",BD[BD.FIGURE=34]="FIGURE", +BD[BD.FONT=35]="FONT", +BD[BD.FOOTER=36]="FOOTER",BD[BD.FOREIGN_OBJECT=37]="FOREIGN_OBJECT", +BD[BD.FORM=38]="FORM", +BD[BD.FRAME=39]="FRAME",BD[BD.FRAMESET=40]="FRAMESET",BD[BD.H1=41]="H1", +BD[BD.H2=42]="H2", +BD[BD.H3=43]="H3",BD[BD.H4=44]="H4",BD[BD.H5=45]="H5",BD[BD.H6=46]="H6", +BD[BD.HEAD=47]="HEAD", +BD[BD.HEADER=48]="HEADER",BD[BD.HGROUP=49]="HGROUP",BD[BD.HR=50]="HR", +BD[BD.HTML=51]="HTML", +BD[BD.I=52]="I",BD[BD.IMG=53]="IMG",BD[BD.IMAGE=54]="IMAGE", +BD[BD.INPUT=55]="INPUT", +BD[BD.IFRAME=56]="IFRAME",BD[BD.KEYGEN=57]="KEYGEN",BD[BD.LABEL=58]="LABEL", +BD[BD.LI=59]="LI", +BD[BD.LINK=60]="LINK",BD[BD.LISTING=61]="LISTING",BD[BD.MAIN=62]="MAIN", +BD[BD.MALIGNMARK=63]="MALIGNMARK", +BD[BD.MARQUEE=64]="MARQUEE",BD[BD.MATH=65]="MATH", +BD[BD.MENU=66]="MENU",BD[BD.META=67]="META", +BD[BD.MGLYPH=68]="MGLYPH",BD[BD.MI=69]="MI",BD[BD.MO=70]="MO",BD[BD.MN=71]="MN", +BD[BD.MS=72]="MS", +BD[BD.MTEXT=73]="MTEXT",BD[BD.NAV=74]="NAV",BD[BD.NOBR=75]="NOBR", +BD[BD.NOFRAMES=76]="NOFRAMES", +BD[BD.NOEMBED=77]="NOEMBED",BD[BD.NOSCRIPT=78]="NOSCRIPT", +BD[BD.OBJECT=79]="OBJECT", +BD[BD.OL=80]="OL",BD[BD.OPTGROUP=81]="OPTGROUP",BD[BD.OPTION=82]="OPTION", +BD[BD.P=83]="P", +BD[BD.PARAM=84]="PARAM",BD[BD.PLAINTEXT=85]="PLAINTEXT",BD[BD.PRE=86]="PRE", +BD[BD.RB=87]="RB", +BD[BD.RP=88]="RP",BD[BD.RT=89]="RT",BD[BD.RTC=90]="RTC",BD[BD.RUBY=91]="RUBY", +BD[BD.S=92]="S", +BD[BD.SCRIPT=93]="SCRIPT",BD[BD.SEARCH=94]="SEARCH",BD[BD.SECTION=95]="SECTION", +BD[BD.SELECT=96]="SELECT", +BD[BD.SOURCE=97]="SOURCE",BD[BD.SMALL=98]="SMALL",BD[BD.SPAN=99]="SPAN", +BD[BD.STRIKE=100]="STRIKE", +BD[BD.STRONG=101]="STRONG",BD[BD.STYLE=102]="STYLE",BD[BD.SUB=103]="SUB", +BD[BD.SUMMARY=104]="SUMMARY", +BD[BD.SUP=105]="SUP",BD[BD.TABLE=106]="TABLE",BD[BD.TBODY=107]="TBODY", +BD[BD.TEMPLATE=108]="TEMPLATE", +BD[BD.TEXTAREA=109]="TEXTAREA",BD[BD.TFOOT=110]="TFOOT", +BD[BD.TD=111]="TD",BD[BD.TH=112]="TH", +BD[BD.THEAD=113]="THEAD",BD[BD.TITLE=114]="TITLE", +BD[BD.TR=115]="TR",BD[BD.TRACK=116]="TRACK",BD[BD.TT=117]="TT",BD[BD.U=118]="U", +BD[BD.UL=119]="UL", +BD[BD.SVG=120]="SVG",BD[BD.VAR=121]="VAR",BD[BD.WBR=122]="WBR", +BD[BD.XMP=123]="XMP" +;const FD=new Map([[ND.A,LD.A],[ND.ADDRESS,LD.ADDRESS],[ND.ANNOTATION_XML,LD.ANNOTATION_XML],[ND.APPLET,LD.APPLET],[ND.AREA,LD.AREA],[ND.ARTICLE,LD.ARTICLE],[ND.ASIDE,LD.ASIDE],[ND.B,LD.B],[ND.BASE,LD.BASE],[ND.BASEFONT,LD.BASEFONT],[ND.BGSOUND,LD.BGSOUND],[ND.BIG,LD.BIG],[ND.BLOCKQUOTE,LD.BLOCKQUOTE],[ND.BODY,LD.BODY],[ND.BR,LD.BR],[ND.BUTTON,LD.BUTTON],[ND.CAPTION,LD.CAPTION],[ND.CENTER,LD.CENTER],[ND.CODE,LD.CODE],[ND.COL,LD.COL],[ND.COLGROUP,LD.COLGROUP],[ND.DD,LD.DD],[ND.DESC,LD.DESC],[ND.DETAILS,LD.DETAILS],[ND.DIALOG,LD.DIALOG],[ND.DIR,LD.DIR],[ND.DIV,LD.DIV],[ND.DL,LD.DL],[ND.DT,LD.DT],[ND.EM,LD.EM],[ND.EMBED,LD.EMBED],[ND.FIELDSET,LD.FIELDSET],[ND.FIGCAPTION,LD.FIGCAPTION],[ND.FIGURE,LD.FIGURE],[ND.FONT,LD.FONT],[ND.FOOTER,LD.FOOTER],[ND.FOREIGN_OBJECT,LD.FOREIGN_OBJECT],[ND.FORM,LD.FORM],[ND.FRAME,LD.FRAME],[ND.FRAMESET,LD.FRAMESET],[ND.H1,LD.H1],[ND.H2,LD.H2],[ND.H3,LD.H3],[ND.H4,LD.H4],[ND.H5,LD.H5],[ND.H6,LD.H6],[ND.HEAD,LD.HEAD],[ND.HEADER,LD.HEADER],[ND.HGROUP,LD.HGROUP],[ND.HR,LD.HR],[ND.HTML,LD.HTML],[ND.I,LD.I],[ND.IMG,LD.IMG],[ND.IMAGE,LD.IMAGE],[ND.INPUT,LD.INPUT],[ND.IFRAME,LD.IFRAME],[ND.KEYGEN,LD.KEYGEN],[ND.LABEL,LD.LABEL],[ND.LI,LD.LI],[ND.LINK,LD.LINK],[ND.LISTING,LD.LISTING],[ND.MAIN,LD.MAIN],[ND.MALIGNMARK,LD.MALIGNMARK],[ND.MARQUEE,LD.MARQUEE],[ND.MATH,LD.MATH],[ND.MENU,LD.MENU],[ND.META,LD.META],[ND.MGLYPH,LD.MGLYPH],[ND.MI,LD.MI],[ND.MO,LD.MO],[ND.MN,LD.MN],[ND.MS,LD.MS],[ND.MTEXT,LD.MTEXT],[ND.NAV,LD.NAV],[ND.NOBR,LD.NOBR],[ND.NOFRAMES,LD.NOFRAMES],[ND.NOEMBED,LD.NOEMBED],[ND.NOSCRIPT,LD.NOSCRIPT],[ND.OBJECT,LD.OBJECT],[ND.OL,LD.OL],[ND.OPTGROUP,LD.OPTGROUP],[ND.OPTION,LD.OPTION],[ND.P,LD.P],[ND.PARAM,LD.PARAM],[ND.PLAINTEXT,LD.PLAINTEXT],[ND.PRE,LD.PRE],[ND.RB,LD.RB],[ND.RP,LD.RP],[ND.RT,LD.RT],[ND.RTC,LD.RTC],[ND.RUBY,LD.RUBY],[ND.S,LD.S],[ND.SCRIPT,LD.SCRIPT],[ND.SEARCH,LD.SEARCH],[ND.SECTION,LD.SECTION],[ND.SELECT,LD.SELECT],[ND.SOURCE,LD.SOURCE],[ND.SMALL,LD.SMALL],[ND.SPAN,LD.SPAN],[ND.STRIKE,LD.STRIKE],[ND.STRONG,LD.STRONG],[ND.STYLE,LD.STYLE],[ND.SUB,LD.SUB],[ND.SUMMARY,LD.SUMMARY],[ND.SUP,LD.SUP],[ND.TABLE,LD.TABLE],[ND.TBODY,LD.TBODY],[ND.TEMPLATE,LD.TEMPLATE],[ND.TEXTAREA,LD.TEXTAREA],[ND.TFOOT,LD.TFOOT],[ND.TD,LD.TD],[ND.TH,LD.TH],[ND.THEAD,LD.THEAD],[ND.TITLE,LD.TITLE],[ND.TR,LD.TR],[ND.TRACK,LD.TRACK],[ND.TT,LD.TT],[ND.U,LD.U],[ND.UL,LD.UL],[ND.SVG,LD.SVG],[ND.VAR,LD.VAR],[ND.WBR,LD.WBR],[ND.XMP,LD.XMP]]) +;function HD(e){var t;return null!==(t=FD.get(e))&&void 0!==t?t:LD.UNKNOWN} +const QD=LD,VD={ +[CD.HTML]:new Set([QD.ADDRESS,QD.APPLET,QD.AREA,QD.ARTICLE,QD.ASIDE,QD.BASE,QD.BASEFONT,QD.BGSOUND,QD.BLOCKQUOTE,QD.BODY,QD.BR,QD.BUTTON,QD.CAPTION,QD.CENTER,QD.COL,QD.COLGROUP,QD.DD,QD.DETAILS,QD.DIR,QD.DIV,QD.DL,QD.DT,QD.EMBED,QD.FIELDSET,QD.FIGCAPTION,QD.FIGURE,QD.FOOTER,QD.FORM,QD.FRAME,QD.FRAMESET,QD.H1,QD.H2,QD.H3,QD.H4,QD.H5,QD.H6,QD.HEAD,QD.HEADER,QD.HGROUP,QD.HR,QD.HTML,QD.IFRAME,QD.IMG,QD.INPUT,QD.LI,QD.LINK,QD.LISTING,QD.MAIN,QD.MARQUEE,QD.MENU,QD.META,QD.NAV,QD.NOEMBED,QD.NOFRAMES,QD.NOSCRIPT,QD.OBJECT,QD.OL,QD.P,QD.PARAM,QD.PLAINTEXT,QD.PRE,QD.SCRIPT,QD.SECTION,QD.SELECT,QD.SOURCE,QD.STYLE,QD.SUMMARY,QD.TABLE,QD.TBODY,QD.TD,QD.TEMPLATE,QD.TEXTAREA,QD.TFOOT,QD.TH,QD.THEAD,QD.TITLE,QD.TR,QD.TRACK,QD.UL,QD.WBR,QD.XMP]), +[CD.MATHML]:new Set([QD.MI,QD.MO,QD.MN,QD.MS,QD.MTEXT,QD.ANNOTATION_XML]), +[CD.SVG]:new Set([QD.TITLE,QD.FOREIGN_OBJECT,QD.DESC]),[CD.XLINK]:new Set, +[CD.XML]:new Set,[CD.XMLNS]:new Set +},qD=new Set([QD.H1,QD.H2,QD.H3,QD.H4,QD.H5,QD.H6]);var WD,XD +;ND.STYLE,ND.SCRIPT, +ND.XMP,ND.IFRAME,ND.NOEMBED,ND.NOFRAMES,ND.PLAINTEXT,(XD=WD||(WD={}))[XD.DATA=0]="DATA", +XD[XD.RCDATA=1]="RCDATA", +XD[XD.RAWTEXT=2]="RAWTEXT",XD[XD.SCRIPT_DATA=3]="SCRIPT_DATA", +XD[XD.PLAINTEXT=4]="PLAINTEXT", +XD[XD.TAG_OPEN=5]="TAG_OPEN",XD[XD.END_TAG_OPEN=6]="END_TAG_OPEN", +XD[XD.TAG_NAME=7]="TAG_NAME", +XD[XD.RCDATA_LESS_THAN_SIGN=8]="RCDATA_LESS_THAN_SIGN", +XD[XD.RCDATA_END_TAG_OPEN=9]="RCDATA_END_TAG_OPEN", +XD[XD.RCDATA_END_TAG_NAME=10]="RCDATA_END_TAG_NAME", +XD[XD.RAWTEXT_LESS_THAN_SIGN=11]="RAWTEXT_LESS_THAN_SIGN", +XD[XD.RAWTEXT_END_TAG_OPEN=12]="RAWTEXT_END_TAG_OPEN", +XD[XD.RAWTEXT_END_TAG_NAME=13]="RAWTEXT_END_TAG_NAME", +XD[XD.SCRIPT_DATA_LESS_THAN_SIGN=14]="SCRIPT_DATA_LESS_THAN_SIGN", +XD[XD.SCRIPT_DATA_END_TAG_OPEN=15]="SCRIPT_DATA_END_TAG_OPEN", +XD[XD.SCRIPT_DATA_END_TAG_NAME=16]="SCRIPT_DATA_END_TAG_NAME", +XD[XD.SCRIPT_DATA_ESCAPE_START=17]="SCRIPT_DATA_ESCAPE_START", +XD[XD.SCRIPT_DATA_ESCAPE_START_DASH=18]="SCRIPT_DATA_ESCAPE_START_DASH", +XD[XD.SCRIPT_DATA_ESCAPED=19]="SCRIPT_DATA_ESCAPED", +XD[XD.SCRIPT_DATA_ESCAPED_DASH=20]="SCRIPT_DATA_ESCAPED_DASH", +XD[XD.SCRIPT_DATA_ESCAPED_DASH_DASH=21]="SCRIPT_DATA_ESCAPED_DASH_DASH", +XD[XD.SCRIPT_DATA_ESCAPED_LESS_THAN_SIGN=22]="SCRIPT_DATA_ESCAPED_LESS_THAN_SIGN", +XD[XD.SCRIPT_DATA_ESCAPED_END_TAG_OPEN=23]="SCRIPT_DATA_ESCAPED_END_TAG_OPEN", +XD[XD.SCRIPT_DATA_ESCAPED_END_TAG_NAME=24]="SCRIPT_DATA_ESCAPED_END_TAG_NAME", +XD[XD.SCRIPT_DATA_DOUBLE_ESCAPE_START=25]="SCRIPT_DATA_DOUBLE_ESCAPE_START", +XD[XD.SCRIPT_DATA_DOUBLE_ESCAPED=26]="SCRIPT_DATA_DOUBLE_ESCAPED", +XD[XD.SCRIPT_DATA_DOUBLE_ESCAPED_DASH=27]="SCRIPT_DATA_DOUBLE_ESCAPED_DASH", +XD[XD.SCRIPT_DATA_DOUBLE_ESCAPED_DASH_DASH=28]="SCRIPT_DATA_DOUBLE_ESCAPED_DASH_DASH", +XD[XD.SCRIPT_DATA_DOUBLE_ESCAPED_LESS_THAN_SIGN=29]="SCRIPT_DATA_DOUBLE_ESCAPED_LESS_THAN_SIGN", +XD[XD.SCRIPT_DATA_DOUBLE_ESCAPE_END=30]="SCRIPT_DATA_DOUBLE_ESCAPE_END", +XD[XD.BEFORE_ATTRIBUTE_NAME=31]="BEFORE_ATTRIBUTE_NAME", +XD[XD.ATTRIBUTE_NAME=32]="ATTRIBUTE_NAME", +XD[XD.AFTER_ATTRIBUTE_NAME=33]="AFTER_ATTRIBUTE_NAME", +XD[XD.BEFORE_ATTRIBUTE_VALUE=34]="BEFORE_ATTRIBUTE_VALUE", +XD[XD.ATTRIBUTE_VALUE_DOUBLE_QUOTED=35]="ATTRIBUTE_VALUE_DOUBLE_QUOTED", +XD[XD.ATTRIBUTE_VALUE_SINGLE_QUOTED=36]="ATTRIBUTE_VALUE_SINGLE_QUOTED", +XD[XD.ATTRIBUTE_VALUE_UNQUOTED=37]="ATTRIBUTE_VALUE_UNQUOTED", +XD[XD.AFTER_ATTRIBUTE_VALUE_QUOTED=38]="AFTER_ATTRIBUTE_VALUE_QUOTED", +XD[XD.SELF_CLOSING_START_TAG=39]="SELF_CLOSING_START_TAG", +XD[XD.BOGUS_COMMENT=40]="BOGUS_COMMENT", +XD[XD.MARKUP_DECLARATION_OPEN=41]="MARKUP_DECLARATION_OPEN", +XD[XD.COMMENT_START=42]="COMMENT_START", +XD[XD.COMMENT_START_DASH=43]="COMMENT_START_DASH", +XD[XD.COMMENT=44]="COMMENT",XD[XD.COMMENT_LESS_THAN_SIGN=45]="COMMENT_LESS_THAN_SIGN", +XD[XD.COMMENT_LESS_THAN_SIGN_BANG=46]="COMMENT_LESS_THAN_SIGN_BANG", +XD[XD.COMMENT_LESS_THAN_SIGN_BANG_DASH=47]="COMMENT_LESS_THAN_SIGN_BANG_DASH", +XD[XD.COMMENT_LESS_THAN_SIGN_BANG_DASH_DASH=48]="COMMENT_LESS_THAN_SIGN_BANG_DASH_DASH", +XD[XD.COMMENT_END_DASH=49]="COMMENT_END_DASH", +XD[XD.COMMENT_END=50]="COMMENT_END", +XD[XD.COMMENT_END_BANG=51]="COMMENT_END_BANG", +XD[XD.DOCTYPE=52]="DOCTYPE",XD[XD.BEFORE_DOCTYPE_NAME=53]="BEFORE_DOCTYPE_NAME", +XD[XD.DOCTYPE_NAME=54]="DOCTYPE_NAME", +XD[XD.AFTER_DOCTYPE_NAME=55]="AFTER_DOCTYPE_NAME", +XD[XD.AFTER_DOCTYPE_PUBLIC_KEYWORD=56]="AFTER_DOCTYPE_PUBLIC_KEYWORD", +XD[XD.BEFORE_DOCTYPE_PUBLIC_IDENTIFIER=57]="BEFORE_DOCTYPE_PUBLIC_IDENTIFIER", +XD[XD.DOCTYPE_PUBLIC_IDENTIFIER_DOUBLE_QUOTED=58]="DOCTYPE_PUBLIC_IDENTIFIER_DOUBLE_QUOTED", +XD[XD.DOCTYPE_PUBLIC_IDENTIFIER_SINGLE_QUOTED=59]="DOCTYPE_PUBLIC_IDENTIFIER_SINGLE_QUOTED", +XD[XD.AFTER_DOCTYPE_PUBLIC_IDENTIFIER=60]="AFTER_DOCTYPE_PUBLIC_IDENTIFIER", +XD[XD.BETWEEN_DOCTYPE_PUBLIC_AND_SYSTEM_IDENTIFIERS=61]="BETWEEN_DOCTYPE_PUBLIC_AND_SYSTEM_IDENTIFIERS", +XD[XD.AFTER_DOCTYPE_SYSTEM_KEYWORD=62]="AFTER_DOCTYPE_SYSTEM_KEYWORD", +XD[XD.BEFORE_DOCTYPE_SYSTEM_IDENTIFIER=63]="BEFORE_DOCTYPE_SYSTEM_IDENTIFIER", +XD[XD.DOCTYPE_SYSTEM_IDENTIFIER_DOUBLE_QUOTED=64]="DOCTYPE_SYSTEM_IDENTIFIER_DOUBLE_QUOTED", +XD[XD.DOCTYPE_SYSTEM_IDENTIFIER_SINGLE_QUOTED=65]="DOCTYPE_SYSTEM_IDENTIFIER_SINGLE_QUOTED", +XD[XD.AFTER_DOCTYPE_SYSTEM_IDENTIFIER=66]="AFTER_DOCTYPE_SYSTEM_IDENTIFIER", +XD[XD.BOGUS_DOCTYPE=67]="BOGUS_DOCTYPE",XD[XD.CDATA_SECTION=68]="CDATA_SECTION", +XD[XD.CDATA_SECTION_BRACKET=69]="CDATA_SECTION_BRACKET", +XD[XD.CDATA_SECTION_END=70]="CDATA_SECTION_END", +XD[XD.CHARACTER_REFERENCE=71]="CHARACTER_REFERENCE", +XD[XD.AMBIGUOUS_AMPERSAND=72]="AMBIGUOUS_AMPERSAND";const GD={DATA:WD.DATA, +RCDATA:WD.RCDATA,RAWTEXT:WD.RAWTEXT,SCRIPT_DATA:WD.SCRIPT_DATA, +PLAINTEXT:WD.PLAINTEXT,CDATA_SECTION:WD.CDATA_SECTION};function YD(e){ +return e>=nD.LATIN_CAPITAL_A&&e<=nD.LATIN_CAPITAL_Z}function KD(e){ +return function(e){return e>=nD.LATIN_SMALL_A&&e<=nD.LATIN_SMALL_Z}(e)||YD(e)} +function JD(e){return KD(e)||function(e){return e>=nD.DIGIT_0&&e<=nD.DIGIT_9}(e) +}function eM(e){return e+32}function tM(e){ +return e===nD.SPACE||e===nD.LINE_FEED||e===nD.TABULATION||e===nD.FORM_FEED} +function nM(e){return tM(e)||e===nD.SOLIDUS||e===nD.GREATER_THAN_SIGN}class rM{ +constructor(e,t){ +this.options=e,this.handler=t,this.paused=!1,this.inLoop=!1,this.inForeignNode=!1, +this.lastStartTagName="", +this.active=!1,this.state=WD.DATA,this.returnState=WD.DATA, +this.entityStartPos=0, +this.consumedAfterSnapshot=-1,this.currentCharacterToken=null, +this.currentToken=null,this.currentAttr={name:"",value:"" +},this.preprocessor=new mD(t), +this.currentLocation=this.getCurrentLocation(-1),this.entityDecoder=new zD(yD,((e,t)=>{ +this.preprocessor.pos=this.entityStartPos+t-1, +this._flushCodePointConsumedAsCharacterReference(e)}),t.onParseError?{ +missingSemicolonAfterCharacterReference:()=>{ +this._err(hD.missingSemicolonAfterCharacterReference,1)}, +absenceOfDigitsInNumericCharacterReference:e=>{ +this._err(hD.absenceOfDigitsInNumericCharacterReference,this.entityStartPos-this.preprocessor.pos+e) +},validateNumericCharacterReference:e=>{const t=function(e){ +return e===nD.NULL?hD.nullCharacterReference:e>1114111?hD.characterReferenceOutsideUnicodeRange:uD(e)?hD.surrogateCharacterReference:pD(e)?hD.noncharacterCharacterReference:dD(e)||e===nD.CARRIAGE_RETURN?hD.controlCharacterReference:null +}(e);t&&this._err(t,1)}}:void 0)}_err(e,t=0){var n,r +;null===(r=(n=this.handler).onParseError)||void 0===r||r.call(n,this.preprocessor.getError(e,t)) +}getCurrentLocation(e){return this.options.sourceCodeLocationInfo?{ +startLine:this.preprocessor.line,startCol:this.preprocessor.col-e, +startOffset:this.preprocessor.offset-e,endLine:-1,endCol:-1,endOffset:-1}:null} +_runParsingLoop(){if(!this.inLoop){ +for(this.inLoop=!0;this.active&&!this.paused;){this.consumedAfterSnapshot=0 +;const e=this._consume();this._ensureHibernation()||this._callState(e)} +this.inLoop=!1}}pause(){this.paused=!0}resume(e){ +if(!this.paused)throw new Error("Parser was already resumed") +;this.paused=!1,this.inLoop||(this._runParsingLoop(),this.paused||null==e||e())} +write(e,t,n){this.active=!0,this.preprocessor.write(e,t),this._runParsingLoop(), +this.paused||null==n||n()}insertHtmlAtCurrentPos(e){ +this.active=!0,this.preprocessor.insertHtmlAtCurrentPos(e), +this._runParsingLoop()}_ensureHibernation(){ +return!!this.preprocessor.endOfChunkHit&&(this.preprocessor.retreat(this.consumedAfterSnapshot), +this.consumedAfterSnapshot=0,this.active=!1,!0)}_consume(){ +return this.consumedAfterSnapshot++,this.preprocessor.advance()}_advanceBy(e){ +this.consumedAfterSnapshot+=e;for(let t=0;t0&&this._err(hD.endTagWithAttributes), +e.selfClosing&&this._err(hD.endTagWithTrailingSolidus), +this.handler.onEndTag(e)),this.preprocessor.dropParsedChunk()} +emitCurrentComment(e){ +this.prepareToken(e),this.handler.onComment(e),this.preprocessor.dropParsedChunk() +}emitCurrentDoctype(e){ +this.prepareToken(e),this.handler.onDoctype(e),this.preprocessor.dropParsedChunk() +}_emitCurrentCharacterToken(e){if(this.currentCharacterToken){ +switch(e&&this.currentCharacterToken.location&&(this.currentCharacterToken.location.endLine=e.startLine, +this.currentCharacterToken.location.endCol=e.startCol, +this.currentCharacterToken.location.endOffset=e.startOffset), +this.currentCharacterToken.type){case gD.CHARACTER: +this.handler.onCharacter(this.currentCharacterToken);break +;case gD.NULL_CHARACTER:this.handler.onNullCharacter(this.currentCharacterToken) +;break;case gD.WHITESPACE_CHARACTER: +this.handler.onWhitespaceCharacter(this.currentCharacterToken)} +this.currentCharacterToken=null}}_emitEOFToken(){ +const e=this.getCurrentLocation(0) +;e&&(e.endLine=e.startLine,e.endCol=e.startCol, +e.endOffset=e.startOffset),this._emitCurrentCharacterToken(e), +this.handler.onEof({type:gD.EOF,location:e}),this.active=!1} +_appendCharToCurrentCharacterToken(e,t){if(this.currentCharacterToken){ +if(this.currentCharacterToken.type===e)return void(this.currentCharacterToken.chars+=t) +;this.currentLocation=this.getCurrentLocation(0), +this._emitCurrentCharacterToken(this.currentLocation), +this.preprocessor.dropParsedChunk()}this._createCharacterToken(e,t)} +_emitCodePoint(e){ +const t=tM(e)?gD.WHITESPACE_CHARACTER:e===nD.NULL?gD.NULL_CHARACTER:gD.CHARACTER +;this._appendCharToCurrentCharacterToken(t,String.fromCodePoint(e))} +_emitChars(e){this._appendCharToCurrentCharacterToken(gD.CHARACTER,e)} +_startCharacterReference(){ +this.returnState=this.state,this.state=WD.CHARACTER_REFERENCE, +this.entityStartPos=this.preprocessor.pos, +this.entityDecoder.startEntity(this._isCharacterReferenceInAttribute()?TD.Attribute:TD.Legacy) +}_isCharacterReferenceInAttribute(){ +return this.returnState===WD.ATTRIBUTE_VALUE_DOUBLE_QUOTED||this.returnState===WD.ATTRIBUTE_VALUE_SINGLE_QUOTED||this.returnState===WD.ATTRIBUTE_VALUE_UNQUOTED +}_flushCodePointConsumedAsCharacterReference(e){ +this._isCharacterReferenceInAttribute()?this.currentAttr.value+=String.fromCodePoint(e):this._emitCodePoint(e) +}_callState(e){switch(this.state){case WD.DATA:this._stateData(e);break +;case WD.RCDATA:this._stateRcdata(e);break;case WD.RAWTEXT:this._stateRawtext(e) +;break;case WD.SCRIPT_DATA:this._stateScriptData(e);break;case WD.PLAINTEXT: +this._statePlaintext(e);break;case WD.TAG_OPEN:this._stateTagOpen(e);break +;case WD.END_TAG_OPEN:this._stateEndTagOpen(e);break;case WD.TAG_NAME: +this._stateTagName(e);break;case WD.RCDATA_LESS_THAN_SIGN: +this._stateRcdataLessThanSign(e);break;case WD.RCDATA_END_TAG_OPEN: +this._stateRcdataEndTagOpen(e);break;case WD.RCDATA_END_TAG_NAME: +this._stateRcdataEndTagName(e);break;case WD.RAWTEXT_LESS_THAN_SIGN: +this._stateRawtextLessThanSign(e);break;case WD.RAWTEXT_END_TAG_OPEN: +this._stateRawtextEndTagOpen(e);break;case WD.RAWTEXT_END_TAG_NAME: +this._stateRawtextEndTagName(e);break;case WD.SCRIPT_DATA_LESS_THAN_SIGN: +this._stateScriptDataLessThanSign(e);break;case WD.SCRIPT_DATA_END_TAG_OPEN: +this._stateScriptDataEndTagOpen(e);break;case WD.SCRIPT_DATA_END_TAG_NAME: +this._stateScriptDataEndTagName(e);break;case WD.SCRIPT_DATA_ESCAPE_START: +this._stateScriptDataEscapeStart(e);break;case WD.SCRIPT_DATA_ESCAPE_START_DASH: +this._stateScriptDataEscapeStartDash(e);break;case WD.SCRIPT_DATA_ESCAPED: +this._stateScriptDataEscaped(e);break;case WD.SCRIPT_DATA_ESCAPED_DASH: +this._stateScriptDataEscapedDash(e);break;case WD.SCRIPT_DATA_ESCAPED_DASH_DASH: +this._stateScriptDataEscapedDashDash(e);break +;case WD.SCRIPT_DATA_ESCAPED_LESS_THAN_SIGN: +this._stateScriptDataEscapedLessThanSign(e);break +;case WD.SCRIPT_DATA_ESCAPED_END_TAG_OPEN: +this._stateScriptDataEscapedEndTagOpen(e);break +;case WD.SCRIPT_DATA_ESCAPED_END_TAG_NAME: +this._stateScriptDataEscapedEndTagName(e);break +;case WD.SCRIPT_DATA_DOUBLE_ESCAPE_START: +this._stateScriptDataDoubleEscapeStart(e);break +;case WD.SCRIPT_DATA_DOUBLE_ESCAPED:this._stateScriptDataDoubleEscaped(e);break +;case WD.SCRIPT_DATA_DOUBLE_ESCAPED_DASH: +this._stateScriptDataDoubleEscapedDash(e);break +;case WD.SCRIPT_DATA_DOUBLE_ESCAPED_DASH_DASH: +this._stateScriptDataDoubleEscapedDashDash(e);break +;case WD.SCRIPT_DATA_DOUBLE_ESCAPED_LESS_THAN_SIGN: +this._stateScriptDataDoubleEscapedLessThanSign(e);break +;case WD.SCRIPT_DATA_DOUBLE_ESCAPE_END:this._stateScriptDataDoubleEscapeEnd(e) +;break;case WD.BEFORE_ATTRIBUTE_NAME:this._stateBeforeAttributeName(e);break +;case WD.ATTRIBUTE_NAME:this._stateAttributeName(e);break +;case WD.AFTER_ATTRIBUTE_NAME:this._stateAfterAttributeName(e);break +;case WD.BEFORE_ATTRIBUTE_VALUE:this._stateBeforeAttributeValue(e);break +;case WD.ATTRIBUTE_VALUE_DOUBLE_QUOTED:this._stateAttributeValueDoubleQuoted(e) +;break;case WD.ATTRIBUTE_VALUE_SINGLE_QUOTED: +this._stateAttributeValueSingleQuoted(e);break;case WD.ATTRIBUTE_VALUE_UNQUOTED: +this._stateAttributeValueUnquoted(e);break;case WD.AFTER_ATTRIBUTE_VALUE_QUOTED: +this._stateAfterAttributeValueQuoted(e);break;case WD.SELF_CLOSING_START_TAG: +this._stateSelfClosingStartTag(e);break;case WD.BOGUS_COMMENT: +this._stateBogusComment(e);break;case WD.MARKUP_DECLARATION_OPEN: +this._stateMarkupDeclarationOpen(e);break;case WD.COMMENT_START: +this._stateCommentStart(e);break;case WD.COMMENT_START_DASH: +this._stateCommentStartDash(e);break;case WD.COMMENT:this._stateComment(e);break +;case WD.COMMENT_LESS_THAN_SIGN:this._stateCommentLessThanSign(e);break +;case WD.COMMENT_LESS_THAN_SIGN_BANG:this._stateCommentLessThanSignBang(e);break +;case WD.COMMENT_LESS_THAN_SIGN_BANG_DASH: +this._stateCommentLessThanSignBangDash(e);break +;case WD.COMMENT_LESS_THAN_SIGN_BANG_DASH_DASH: +this._stateCommentLessThanSignBangDashDash(e);break;case WD.COMMENT_END_DASH: +this._stateCommentEndDash(e);break;case WD.COMMENT_END:this._stateCommentEnd(e) +;break;case WD.COMMENT_END_BANG:this._stateCommentEndBang(e);break +;case WD.DOCTYPE:this._stateDoctype(e);break;case WD.BEFORE_DOCTYPE_NAME: +this._stateBeforeDoctypeName(e);break;case WD.DOCTYPE_NAME: +this._stateDoctypeName(e);break;case WD.AFTER_DOCTYPE_NAME: +this._stateAfterDoctypeName(e);break;case WD.AFTER_DOCTYPE_PUBLIC_KEYWORD: +this._stateAfterDoctypePublicKeyword(e);break +;case WD.BEFORE_DOCTYPE_PUBLIC_IDENTIFIER: +this._stateBeforeDoctypePublicIdentifier(e);break +;case WD.DOCTYPE_PUBLIC_IDENTIFIER_DOUBLE_QUOTED: +this._stateDoctypePublicIdentifierDoubleQuoted(e);break +;case WD.DOCTYPE_PUBLIC_IDENTIFIER_SINGLE_QUOTED: +this._stateDoctypePublicIdentifierSingleQuoted(e);break +;case WD.AFTER_DOCTYPE_PUBLIC_IDENTIFIER: +this._stateAfterDoctypePublicIdentifier(e);break +;case WD.BETWEEN_DOCTYPE_PUBLIC_AND_SYSTEM_IDENTIFIERS: +this._stateBetweenDoctypePublicAndSystemIdentifiers(e);break +;case WD.AFTER_DOCTYPE_SYSTEM_KEYWORD:this._stateAfterDoctypeSystemKeyword(e) +;break;case WD.BEFORE_DOCTYPE_SYSTEM_IDENTIFIER: +this._stateBeforeDoctypeSystemIdentifier(e);break +;case WD.DOCTYPE_SYSTEM_IDENTIFIER_DOUBLE_QUOTED: +this._stateDoctypeSystemIdentifierDoubleQuoted(e);break +;case WD.DOCTYPE_SYSTEM_IDENTIFIER_SINGLE_QUOTED: +this._stateDoctypeSystemIdentifierSingleQuoted(e);break +;case WD.AFTER_DOCTYPE_SYSTEM_IDENTIFIER: +this._stateAfterDoctypeSystemIdentifier(e);break;case WD.BOGUS_DOCTYPE: +this._stateBogusDoctype(e);break;case WD.CDATA_SECTION: +this._stateCdataSection(e);break;case WD.CDATA_SECTION_BRACKET: +this._stateCdataSectionBracket(e);break;case WD.CDATA_SECTION_END: +this._stateCdataSectionEnd(e);break;case WD.CHARACTER_REFERENCE: +this._stateCharacterReference();break;case WD.AMBIGUOUS_AMPERSAND: +this._stateAmbiguousAmpersand(e);break;default:throw new Error("Unknown state")} +}_stateData(e){switch(e){case nD.LESS_THAN_SIGN:this.state=WD.TAG_OPEN;break +;case nD.AMPERSAND:this._startCharacterReference();break;case nD.NULL: +this._err(hD.unexpectedNullCharacter),this._emitCodePoint(e);break;case nD.EOF: +this._emitEOFToken();break;default:this._emitCodePoint(e)}}_stateRcdata(e){ +switch(e){case nD.AMPERSAND:this._startCharacterReference();break +;case nD.LESS_THAN_SIGN:this.state=WD.RCDATA_LESS_THAN_SIGN;break;case nD.NULL: +this._err(hD.unexpectedNullCharacter),this._emitChars(tD);break;case nD.EOF: +this._emitEOFToken();break;default:this._emitCodePoint(e)}}_stateRawtext(e){ +switch(e){case nD.LESS_THAN_SIGN:this.state=WD.RAWTEXT_LESS_THAN_SIGN;break +;case nD.NULL:this._err(hD.unexpectedNullCharacter),this._emitChars(tD);break +;case nD.EOF:this._emitEOFToken();break;default:this._emitCodePoint(e)}} +_stateScriptData(e){switch(e){case nD.LESS_THAN_SIGN: +this.state=WD.SCRIPT_DATA_LESS_THAN_SIGN;break;case nD.NULL: +this._err(hD.unexpectedNullCharacter),this._emitChars(tD);break;case nD.EOF: +this._emitEOFToken();break;default:this._emitCodePoint(e)}}_statePlaintext(e){ +switch(e){case nD.NULL:this._err(hD.unexpectedNullCharacter),this._emitChars(tD) +;break;case nD.EOF:this._emitEOFToken();break;default:this._emitCodePoint(e)}} +_stateTagOpen(e){ +if(KD(e))this._createStartTagToken(),this.state=WD.TAG_NAME,this._stateTagName(e);else switch(e){ +case nD.EXCLAMATION_MARK:this.state=WD.MARKUP_DECLARATION_OPEN;break +;case nD.SOLIDUS:this.state=WD.END_TAG_OPEN;break;case nD.QUESTION_MARK: +this._err(hD.unexpectedQuestionMarkInsteadOfTagName), +this._createCommentToken(1), +this.state=WD.BOGUS_COMMENT,this._stateBogusComment(e);break;case nD.EOF: +this._err(hD.eofBeforeTagName),this._emitChars("<"),this._emitEOFToken();break +;default: +this._err(hD.invalidFirstCharacterOfTagName),this._emitChars("<"),this.state=WD.DATA, +this._stateData(e)}}_stateEndTagOpen(e){ +if(KD(e))this._createEndTagToken(),this.state=WD.TAG_NAME, +this._stateTagName(e);else switch(e){case nD.GREATER_THAN_SIGN: +this._err(hD.missingEndTagName),this.state=WD.DATA;break;case nD.EOF: +this._err(hD.eofBeforeTagName),this._emitChars("");break +;case nD.NULL: +this._err(hD.unexpectedNullCharacter),this.state=WD.SCRIPT_DATA_ESCAPED, +this._emitChars(tD);break;case nD.EOF: +this._err(hD.eofInScriptHtmlCommentLikeText),this._emitEOFToken();break;default: +this.state=WD.SCRIPT_DATA_ESCAPED,this._emitCodePoint(e)}} +_stateScriptDataEscapedLessThanSign(e){ +e===nD.SOLIDUS?this.state=WD.SCRIPT_DATA_ESCAPED_END_TAG_OPEN:KD(e)?(this._emitChars("<"), +this.state=WD.SCRIPT_DATA_DOUBLE_ESCAPE_START, +this._stateScriptDataDoubleEscapeStart(e)):(this._emitChars("<"), +this.state=WD.SCRIPT_DATA_ESCAPED,this._stateScriptDataEscaped(e))} +_stateScriptDataEscapedEndTagOpen(e){ +KD(e)?(this.state=WD.SCRIPT_DATA_ESCAPED_END_TAG_NAME, +this._stateScriptDataEscapedEndTagName(e)):(this._emitChars("") +;break;case nD.NULL: +this._err(hD.unexpectedNullCharacter),this.state=WD.SCRIPT_DATA_DOUBLE_ESCAPED, +this._emitChars(tD);break;case nD.EOF: +this._err(hD.eofInScriptHtmlCommentLikeText),this._emitEOFToken();break;default: +this.state=WD.SCRIPT_DATA_DOUBLE_ESCAPED,this._emitCodePoint(e)}} +_stateScriptDataDoubleEscapedLessThanSign(e){ +e===nD.SOLIDUS?(this.state=WD.SCRIPT_DATA_DOUBLE_ESCAPE_END, +this._emitChars("/")):(this.state=WD.SCRIPT_DATA_DOUBLE_ESCAPED, +this._stateScriptDataDoubleEscaped(e))}_stateScriptDataDoubleEscapeEnd(e){ +if(this.preprocessor.startsWith(sD,!1)&&nM(this.preprocessor.peek(sD.length))){ +this._emitCodePoint(e) +;for(let e=0;e0&&this._isInTemplate()&&this.tmplCount--,this.stackTop--, +this._updateCurrentElement(),this.handler.onItemPop(e,!0)}replace(e,t){ +const n=this._indexOf(e);this.items[n]=t,n===this.stackTop&&(this.current=t)} +insertAfter(e,t,n){const r=this._indexOf(e)+1 +;this.items.splice(r,0,t),this.tagIDs.splice(r,0,n), +this.stackTop++,r===this.stackTop&&this._updateCurrentElement(), +this.current&&void 0!==this.currentTagId&&this.handler.onItemPush(this.current,this.currentTagId,r===this.stackTop) +}popUntilTagNamePopped(e){let t=this.stackTop+1;do{ +t=this.tagIDs.lastIndexOf(e,t-1) +}while(t>0&&this.treeAdapter.getNamespaceURI(this.items[t])!==CD.HTML) +;this.shortenToLength(Math.max(t,0))}shortenToLength(e){for(;this.stackTop>=e;){ +const t=this.current +;this.tmplCount>0&&this._isInTemplate()&&(this.tmplCount-=1), +this.stackTop--,this._updateCurrentElement(), +this.handler.onItemPop(t,this.stackTop=0;n--)if(e.has(this.tagIDs[n])&&this.treeAdapter.getNamespaceURI(this.items[n])===t)return n +;return-1}clearBackTo(e,t){const n=this._indexOfTagNames(e,t) +;this.shortenToLength(n+1)}clearBackToTableContext(){ +this.clearBackTo(hM,CD.HTML)}clearBackToTableBodyContext(){ +this.clearBackTo(pM,CD.HTML)}clearBackToTableRowContext(){ +this.clearBackTo(dM,CD.HTML)}remove(e){const t=this._indexOf(e) +;t>=0&&(t===this.stackTop?this.pop():(this.items.splice(t,1), +this.tagIDs.splice(t,1), +this.stackTop--,this._updateCurrentElement(),this.handler.onItemPop(e,!1)))} +tryPeekProperlyNestedBodyElement(){ +return this.stackTop>=1&&this.tagIDs[1]===LD.BODY?this.items[1]:null} +contains(e){return this._indexOf(e)>-1}getCommonAncestor(e){ +const t=this._indexOf(e)-1;return t>=0?this.items[t]:null} +isRootHtmlElementCurrent(){return 0===this.stackTop&&this.tagIDs[0]===LD.HTML} +hasInDynamicScope(e,t){for(let n=this.stackTop;n>=0;n--){const r=this.tagIDs[n] +;switch(this.treeAdapter.getNamespaceURI(this.items[n])){case CD.HTML: +if(r===e)return!0;if(t.has(r))return!1;break;case CD.SVG:if(uM.has(r))return!1 +;break;case CD.MATHML:if(cM.has(r))return!1}}return!0}hasInScope(e){ +return this.hasInDynamicScope(e,iM)}hasInListItemScope(e){ +return this.hasInDynamicScope(e,sM)}hasInButtonScope(e){ +return this.hasInDynamicScope(e,lM)}hasNumberedHeaderInScope(){ +for(let e=this.stackTop;e>=0;e--){const t=this.tagIDs[e] +;switch(this.treeAdapter.getNamespaceURI(this.items[e])){case CD.HTML: +if(qD.has(t))return!0;if(iM.has(t))return!1;break;case CD.SVG: +if(uM.has(t))return!1;break;case CD.MATHML:if(cM.has(t))return!1}}return!0} +hasInTableScope(e){ +for(let t=this.stackTop;t>=0;t--)if(this.treeAdapter.getNamespaceURI(this.items[t])===CD.HTML)switch(this.tagIDs[t]){ +case e:return!0;case LD.TABLE:case LD.HTML:return!1}return!0} +hasTableBodyContextInTableScope(){ +for(let e=this.stackTop;e>=0;e--)if(this.treeAdapter.getNamespaceURI(this.items[e])===CD.HTML)switch(this.tagIDs[e]){ +case LD.TBODY:case LD.THEAD:case LD.TFOOT:return!0;case LD.TABLE:case LD.HTML: +return!1}return!0}hasInSelectScope(e){ +for(let t=this.stackTop;t>=0;t--)if(this.treeAdapter.getNamespaceURI(this.items[t])===CD.HTML)switch(this.tagIDs[t]){ +case e:return!0;case LD.OPTION:case LD.OPTGROUP:break;default:return!1}return!0} +generateImpliedEndTags(){ +for(;void 0!==this.currentTagId&&aM.has(this.currentTagId);)this.pop()} +generateImpliedEndTagsThoroughly(){ +for(;void 0!==this.currentTagId&&oM.has(this.currentTagId);)this.pop()} +generateImpliedEndTagsWithExclusion(e){ +for(;void 0!==this.currentTagId&&this.currentTagId!==e&&oM.has(this.currentTagId);)this.pop() +}}var gM,vM;(vM=gM||(gM={}))[vM.Marker=0]="Marker",vM[vM.Element=1]="Element" +;const bM={type:gM.Marker};class yM{constructor(e){ +this.treeAdapter=e,this.entries=[],this.bookmark=null} +_getNoahArkConditionCandidates(e,t){ +const n=[],r=t.length,a=this.treeAdapter.getTagName(e),o=this.treeAdapter.getNamespaceURI(e) +;for(let i=0;i[e.name,e.value])));let a=0 +;for(let o=0;or.get(e.name)===e.value))&&(a+=1, +a>=3&&this.entries.splice(e.idx,1))}}insertMarker(){this.entries.unshift(bM)} +pushElement(e,t){this._ensureNoahArkCondition(e),this.entries.unshift({ +type:gM.Element,element:e,token:t})}insertElementAfterBookmark(e,t){ +const n=this.entries.indexOf(this.bookmark);this.entries.splice(n,0,{ +type:gM.Element,element:e,token:t})}removeEntry(e){ +const t=this.entries.indexOf(e);-1!==t&&this.entries.splice(t,1)} +clearToLastMarker(){const e=this.entries.indexOf(bM) +;-1===e?this.entries.length=0:this.entries.splice(0,e+1)} +getElementEntryInScopeWithTagName(e){ +const t=this.entries.find((t=>t.type===gM.Marker||this.treeAdapter.getTagName(t.element)===e)) +;return t&&t.type===gM.Element?t:null}getElementEntry(e){ +return this.entries.find((t=>t.type===gM.Element&&t.element===e))}}const OM={ +createDocument:()=>({nodeName:"#document",mode:DD.NO_QUIRKS,childNodes:[]}), +createDocumentFragment:()=>({nodeName:"#document-fragment",childNodes:[]}), +createElement:(e,t,n)=>({nodeName:e,tagName:e,attrs:n,namespaceURI:t, +childNodes:[],parentNode:null}),createCommentNode:e=>({nodeName:"#comment", +data:e,parentNode:null}),createTextNode:e=>({nodeName:"#text",value:e, +parentNode:null}),appendChild(e,t){e.childNodes.push(t),t.parentNode=e}, +insertBefore(e,t,n){const r=e.childNodes.indexOf(n) +;e.childNodes.splice(r,0,t),t.parentNode=e},setTemplateContent(e,t){e.content=t +},getTemplateContent:e=>e.content,setDocumentType(e,t,n,r){ +const a=e.childNodes.find((e=>"#documentType"===e.nodeName)) +;if(a)a.name=t,a.publicId=n,a.systemId=r;else{const a={nodeName:"#documentType", +name:t,publicId:n,systemId:r,parentNode:null};OM.appendChild(e,a)}}, +setDocumentMode(e,t){e.mode=t},getDocumentMode:e=>e.mode,detachNode(e){ +if(e.parentNode){const t=e.parentNode.childNodes.indexOf(e) +;e.parentNode.childNodes.splice(t,1),e.parentNode=null}},insertText(e,t){ +if(e.childNodes.length>0){const n=e.childNodes[e.childNodes.length-1] +;if(OM.isTextNode(n))return void(n.value+=t)} +OM.appendChild(e,OM.createTextNode(t))},insertTextBefore(e,t,n){ +const r=e.childNodes[e.childNodes.indexOf(n)-1] +;r&&OM.isTextNode(r)?r.value+=t:OM.insertBefore(e,OM.createTextNode(t),n)}, +adoptAttributes(e,t){const n=new Set(e.attrs.map((e=>e.name))) +;for(let r=0;re.childNodes[0],getChildNodes:e=>e.childNodes, +getParentNode:e=>e.parentNode,getAttrList:e=>e.attrs,getTagName:e=>e.tagName, +getNamespaceURI:e=>e.namespaceURI,getTextNodeContent:e=>e.value, +getCommentNodeContent:e=>e.data,getDocumentTypeNodeName:e=>e.name, +getDocumentTypeNodePublicId:e=>e.publicId, +getDocumentTypeNodeSystemId:e=>e.systemId,isTextNode:e=>"#text"===e.nodeName, +isCommentNode:e=>"#comment"===e.nodeName, +isDocumentTypeNode:e=>"#documentType"===e.nodeName, +isElementNode:e=>Object.prototype.hasOwnProperty.call(e,"tagName"), +setNodeSourceCodeLocation(e,t){e.sourceCodeLocation=t}, +getNodeSourceCodeLocation:e=>e.sourceCodeLocation, +updateNodeSourceCodeLocation(e,t){e.sourceCodeLocation={...e.sourceCodeLocation, +...t}} +},wM="html",xM="about:legacy-compat",kM="http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd",SM=["+//silmaril//dtd html pro v0r11 19970101//","-//as//dtd html 3.0 aswedit + extensions//","-//advasoft ltd//dtd html 3.0 aswedit + extensions//","-//ietf//dtd html 2.0 level 1//","-//ietf//dtd html 2.0 level 2//","-//ietf//dtd html 2.0 strict level 1//","-//ietf//dtd html 2.0 strict level 2//","-//ietf//dtd html 2.0 strict//","-//ietf//dtd html 2.0//","-//ietf//dtd html 2.1e//","-//ietf//dtd html 3.0//","-//ietf//dtd html 3.2 final//","-//ietf//dtd html 3.2//","-//ietf//dtd html 3//","-//ietf//dtd html level 0//","-//ietf//dtd html level 1//","-//ietf//dtd html level 2//","-//ietf//dtd html level 3//","-//ietf//dtd html strict level 0//","-//ietf//dtd html strict level 1//","-//ietf//dtd html strict level 2//","-//ietf//dtd html strict level 3//","-//ietf//dtd html strict//","-//ietf//dtd html//","-//metrius//dtd metrius presentational//","-//microsoft//dtd internet explorer 2.0 html strict//","-//microsoft//dtd internet explorer 2.0 html//","-//microsoft//dtd internet explorer 2.0 tables//","-//microsoft//dtd internet explorer 3.0 html strict//","-//microsoft//dtd internet explorer 3.0 html//","-//microsoft//dtd internet explorer 3.0 tables//","-//netscape comm. corp.//dtd html//","-//netscape comm. corp.//dtd strict html//","-//o'reilly and associates//dtd html 2.0//","-//o'reilly and associates//dtd html extended 1.0//","-//o'reilly and associates//dtd html extended relaxed 1.0//","-//sq//dtd html 2.0 hotmetal + extensions//","-//softquad software//dtd hotmetal pro 6.0::19990601::extensions to html 4.0//","-//softquad//dtd hotmetal pro 4.0::19971010::extensions to html 4.0//","-//spyglass//dtd html 2.0 extended//","-//sun microsystems corp.//dtd hotjava html//","-//sun microsystems corp.//dtd hotjava strict html//","-//w3c//dtd html 3 1995-03-24//","-//w3c//dtd html 3.2 draft//","-//w3c//dtd html 3.2 final//","-//w3c//dtd html 3.2//","-//w3c//dtd html 3.2s draft//","-//w3c//dtd html 4.0 frameset//","-//w3c//dtd html 4.0 transitional//","-//w3c//dtd html experimental 19960712//","-//w3c//dtd html experimental 970421//","-//w3c//dtd w3 html//","-//w3o//dtd w3 html 3.0//","-//webtechs//dtd mozilla html 2.0//","-//webtechs//dtd mozilla html//"],_M=[...SM,"-//w3c//dtd html 4.01 frameset//","-//w3c//dtd html 4.01 transitional//"],AM=new Set(["-//w3o//dtd w3 html strict 3.0//en//","-/w3c/dtd html 4.0 transitional/en","html"]),TM=["-//w3c//dtd xhtml 1.0 frameset//","-//w3c//dtd xhtml 1.0 transitional//"],EM=[...TM,"-//w3c//dtd html 4.01 frameset//","-//w3c//dtd html 4.01 transitional//"] +;function CM(e,t){return t.some((t=>e.startsWith(t)))}const $M={ +TEXT_HTML:"text/html",APPLICATION_XML:"application/xhtml+xml" +},PM="definitionurl",IM="definitionURL",DM=new Map(["attributeName","attributeType","baseFrequency","baseProfile","calcMode","clipPathUnits","diffuseConstant","edgeMode","filterUnits","glyphRef","gradientTransform","gradientUnits","kernelMatrix","kernelUnitLength","keyPoints","keySplines","keyTimes","lengthAdjust","limitingConeAngle","markerHeight","markerUnits","markerWidth","maskContentUnits","maskUnits","numOctaves","pathLength","patternContentUnits","patternTransform","patternUnits","pointsAtX","pointsAtY","pointsAtZ","preserveAlpha","preserveAspectRatio","primitiveUnits","refX","refY","repeatCount","repeatDur","requiredExtensions","requiredFeatures","specularConstant","specularExponent","spreadMethod","startOffset","stdDeviation","stitchTiles","surfaceScale","systemLanguage","tableValues","targetX","targetY","textLength","viewBox","viewTarget","xChannelSelector","yChannelSelector","zoomAndPan"].map((e=>[e.toLowerCase(),e]))),MM=new Map([["xlink:actuate",{ +prefix:"xlink",name:"actuate",namespace:CD.XLINK}],["xlink:arcrole",{ +prefix:"xlink",name:"arcrole",namespace:CD.XLINK}],["xlink:href",{ +prefix:"xlink",name:"href",namespace:CD.XLINK}],["xlink:role",{prefix:"xlink", +name:"role",namespace:CD.XLINK}],["xlink:show",{prefix:"xlink",name:"show", +namespace:CD.XLINK}],["xlink:title",{prefix:"xlink",name:"title", +namespace:CD.XLINK}],["xlink:type",{prefix:"xlink",name:"type", +namespace:CD.XLINK}],["xml:lang",{prefix:"xml",name:"lang",namespace:CD.XML +}],["xml:space",{prefix:"xml",name:"space",namespace:CD.XML}],["xmlns",{ +prefix:"",name:"xmlns",namespace:CD.XMLNS}],["xmlns:xlink",{prefix:"xmlns", +name:"xlink",namespace:CD.XMLNS +}]]),NM=new Map(["altGlyph","altGlyphDef","altGlyphItem","animateColor","animateMotion","animateTransform","clipPath","feBlend","feColorMatrix","feComponentTransfer","feComposite","feConvolveMatrix","feDiffuseLighting","feDisplacementMap","feDistantLight","feFlood","feFuncA","feFuncB","feFuncG","feFuncR","feGaussianBlur","feImage","feMerge","feMergeNode","feMorphology","feOffset","fePointLight","feSpecularLighting","feSpotLight","feTile","feTurbulence","foreignObject","glyphRef","linearGradient","radialGradient","textPath"].map((e=>[e.toLowerCase(),e]))),RM=new Set([LD.B,LD.BIG,LD.BLOCKQUOTE,LD.BODY,LD.BR,LD.CENTER,LD.CODE,LD.DD,LD.DIV,LD.DL,LD.DT,LD.EM,LD.EMBED,LD.H1,LD.H2,LD.H3,LD.H4,LD.H5,LD.H6,LD.HEAD,LD.HR,LD.I,LD.IMG,LD.LI,LD.LISTING,LD.MENU,LD.META,LD.NOBR,LD.OL,LD.P,LD.PRE,LD.RUBY,LD.S,LD.SMALL,LD.SPAN,LD.STRONG,LD.STRIKE,LD.SUB,LD.SUP,LD.TABLE,LD.TT,LD.U,LD.UL,LD.VAR]) +;function LM(e){for(let t=0;t0&&this._setContextModes(e,t)}onItemPop(e,t){ +var n,r +;if(this.options.sourceCodeLocationInfo&&this._setEndLocation(e,this.currentToken), +null===(r=(n=this.treeAdapter).onItemPop)||void 0===r||r.call(n,e,this.openElements.current), +t){let e,t +;0===this.openElements.stackTop&&this.fragmentContext?(e=this.fragmentContext, +t=this.fragmentContextID):({current:e,currentTagId:t}=this.openElements), +this._setContextModes(e,t)}}_setContextModes(e,t){ +const n=e===this.document||e&&this.treeAdapter.getNamespaceURI(e)===CD.HTML +;this.currentNotInHTML=!n, +this.tokenizer.inForeignNode=!n&&void 0!==e&&void 0!==t&&!this._isIntegrationPoint(t,e) +}_switchToTextParsing(e,t){ +this._insertElement(e,CD.HTML),this.tokenizer.state=t, +this.originalInsertionMode=this.insertionMode,this.insertionMode=HM.TEXT} +switchToPlaintextParsing(){ +this.insertionMode=HM.TEXT,this.originalInsertionMode=HM.IN_BODY, +this.tokenizer.state=GD.PLAINTEXT}_getAdjustedCurrentElement(){ +return 0===this.openElements.stackTop&&this.fragmentContext?this.fragmentContext:this.openElements.current +}_findFormInFragmentContext(){let e=this.fragmentContext;for(;e;){ +if(this.treeAdapter.getTagName(e)===ND.FORM){this.formElement=e;break} +e=this.treeAdapter.getParentNode(e)}}_initTokenizerForFragmentParsing(){ +if(this.fragmentContext&&this.treeAdapter.getNamespaceURI(this.fragmentContext)===CD.HTML)switch(this.fragmentContextID){ +case LD.TITLE:case LD.TEXTAREA:this.tokenizer.state=GD.RCDATA;break +;case LD.STYLE:case LD.XMP:case LD.IFRAME:case LD.NOEMBED:case LD.NOFRAMES: +case LD.NOSCRIPT:this.tokenizer.state=GD.RAWTEXT;break;case LD.SCRIPT: +this.tokenizer.state=GD.SCRIPT_DATA;break;case LD.PLAINTEXT: +this.tokenizer.state=GD.PLAINTEXT}}_setDocumentType(e){ +const t=e.name||"",n=e.publicId||"",r=e.systemId||"" +;if(this.treeAdapter.setDocumentType(this.document,t,n,r),e.location){ +const t=this.treeAdapter.getChildNodes(this.document).find((e=>this.treeAdapter.isDocumentTypeNode(e))) +;t&&this.treeAdapter.setNodeSourceCodeLocation(t,e.location)}} +_attachElementToTree(e,t){if(this.options.sourceCodeLocationInfo){const n=t&&{ +...t,startTag:t};this.treeAdapter.setNodeSourceCodeLocation(e,n)} +if(this._shouldFosterParentOnInsertion())this._fosterParentElement(e);else{ +const t=this.openElements.currentTmplContentOrNode +;this.treeAdapter.appendChild(null!=t?t:this.document,e)}}_appendElement(e,t){ +const n=this.treeAdapter.createElement(e.tagName,t,e.attrs) +;this._attachElementToTree(n,e.location)}_insertElement(e,t){ +const n=this.treeAdapter.createElement(e.tagName,t,e.attrs) +;this._attachElementToTree(n,e.location),this.openElements.push(n,e.tagID)} +_insertFakeElement(e,t){const n=this.treeAdapter.createElement(e,CD.HTML,[]) +;this._attachElementToTree(n,null),this.openElements.push(n,t)} +_insertTemplate(e){ +const t=this.treeAdapter.createElement(e.tagName,CD.HTML,e.attrs),n=this.treeAdapter.createDocumentFragment() +;this.treeAdapter.setTemplateContent(t,n), +this._attachElementToTree(t,e.location), +this.openElements.push(t,e.tagID),this.options.sourceCodeLocationInfo&&this.treeAdapter.setNodeSourceCodeLocation(n,null) +}_insertFakeRootElement(){ +const e=this.treeAdapter.createElement(ND.HTML,CD.HTML,[]) +;this.options.sourceCodeLocationInfo&&this.treeAdapter.setNodeSourceCodeLocation(e,null), +this.treeAdapter.appendChild(this.openElements.current,e), +this.openElements.push(e,LD.HTML)}_appendCommentNode(e,t){ +const n=this.treeAdapter.createCommentNode(e.data) +;this.treeAdapter.appendChild(t,n), +this.options.sourceCodeLocationInfo&&this.treeAdapter.setNodeSourceCodeLocation(n,e.location) +}_insertCharacters(e){let t,n +;if(this._shouldFosterParentOnInsertion()?(({parent:t,beforeElement:n}=this._findFosterParentingLocation()), +n?this.treeAdapter.insertTextBefore(t,e.chars,n):this.treeAdapter.insertText(t,e.chars)):(t=this.openElements.currentTmplContentOrNode, +this.treeAdapter.insertText(t,e.chars)),!e.location)return +;const r=this.treeAdapter.getChildNodes(t),a=n?r.lastIndexOf(n):r.length,o=r[a-1] +;if(this.treeAdapter.getNodeSourceCodeLocation(o)){ +const{endLine:t,endCol:n,endOffset:r}=e.location +;this.treeAdapter.updateNodeSourceCodeLocation(o,{endLine:t,endCol:n,endOffset:r +}) +}else this.options.sourceCodeLocationInfo&&this.treeAdapter.setNodeSourceCodeLocation(o,e.location) +}_adoptNodes(e,t){ +for(let n=this.treeAdapter.getFirstChild(e);n;n=this.treeAdapter.getFirstChild(e))this.treeAdapter.detachNode(n), +this.treeAdapter.appendChild(t,n)}_setEndLocation(e,t){ +if(this.treeAdapter.getNodeSourceCodeLocation(e)&&t.location){ +const n=t.location,r=this.treeAdapter.getTagName(e),a=t.type===gD.END_TAG&&r===t.tagName?{ +endTag:{...n},endLine:n.endLine,endCol:n.endCol,endOffset:n.endOffset}:{ +endLine:n.startLine,endCol:n.startCol,endOffset:n.startOffset} +;this.treeAdapter.updateNodeSourceCodeLocation(e,a)}} +shouldProcessStartTagTokenInForeignContent(e){if(!this.currentNotInHTML)return!1 +;let t,n +;return 0===this.openElements.stackTop&&this.fragmentContext?(t=this.fragmentContext, +n=this.fragmentContextID):({current:t,currentTagId:n}=this.openElements), +(e.tagID!==LD.SVG||this.treeAdapter.getTagName(t)!==ND.ANNOTATION_XML||this.treeAdapter.getNamespaceURI(t)!==CD.MATHML)&&(this.tokenizer.inForeignNode||(e.tagID===LD.MGLYPH||e.tagID===LD.MALIGNMARK)&&void 0!==n&&!this._isIntegrationPoint(n,t,CD.HTML)) +}_processToken(e){switch(e.type){case gD.CHARACTER:this.onCharacter(e);break +;case gD.NULL_CHARACTER:this.onNullCharacter(e);break;case gD.COMMENT: +this.onComment(e);break;case gD.DOCTYPE:this.onDoctype(e);break +;case gD.START_TAG:this._processStartTag(e);break;case gD.END_TAG: +this.onEndTag(e);break;case gD.EOF:this.onEof(e);break +;case gD.WHITESPACE_CHARACTER:this.onWhitespaceCharacter(e)}} +_isIntegrationPoint(e,t,n){ +return UM(e,this.treeAdapter.getNamespaceURI(t),this.treeAdapter.getAttrList(t),n) +}_reconstructActiveFormattingElements(){ +const e=this.activeFormattingElements.entries.length;if(e){ +const t=this.activeFormattingElements.entries.findIndex((e=>e.type===gM.Marker||this.openElements.contains(e.element))) +;for(let n=-1===t?e-1:t-1;n>=0;n--){ +const e=this.activeFormattingElements.entries[n] +;this._insertElement(e.token,this.treeAdapter.getNamespaceURI(e.element)), +e.element=this.openElements.current}}}_closeTableCell(){ +this.openElements.generateImpliedEndTags(), +this.openElements.popUntilTableCellPopped(), +this.activeFormattingElements.clearToLastMarker(),this.insertionMode=HM.IN_ROW} +_closePElement(){ +this.openElements.generateImpliedEndTagsWithExclusion(LD.P),this.openElements.popUntilTagNamePopped(LD.P) +}_resetInsertionMode(){ +for(let e=this.openElements.stackTop;e>=0;e--)switch(0===e&&this.fragmentContext?this.fragmentContextID:this.openElements.tagIDs[e]){ +case LD.TR:return void(this.insertionMode=HM.IN_ROW);case LD.TBODY: +case LD.THEAD:case LD.TFOOT:return void(this.insertionMode=HM.IN_TABLE_BODY) +;case LD.CAPTION:return void(this.insertionMode=HM.IN_CAPTION);case LD.COLGROUP: +return void(this.insertionMode=HM.IN_COLUMN_GROUP);case LD.TABLE: +return void(this.insertionMode=HM.IN_TABLE);case LD.BODY: +return void(this.insertionMode=HM.IN_BODY);case LD.FRAMESET: +return void(this.insertionMode=HM.IN_FRAMESET);case LD.SELECT: +return void this._resetInsertionModeForSelect(e);case LD.TEMPLATE: +return void(this.insertionMode=this.tmplInsertionModeStack[0]);case LD.HTML: +return void(this.insertionMode=this.headElement?HM.AFTER_HEAD:HM.BEFORE_HEAD) +;case LD.TD:case LD.TH:if(e>0)return void(this.insertionMode=HM.IN_CELL);break +;case LD.HEAD:if(e>0)return void(this.insertionMode=HM.IN_HEAD)} +this.insertionMode=HM.IN_BODY}_resetInsertionModeForSelect(e){ +if(e>0)for(let t=e-1;t>0;t--){const e=this.openElements.tagIDs[t] +;if(e===LD.TEMPLATE)break +;if(e===LD.TABLE)return void(this.insertionMode=HM.IN_SELECT_IN_TABLE)} +this.insertionMode=HM.IN_SELECT}_isElementCausesFosterParenting(e){ +return qM.has(e)}_shouldFosterParentOnInsertion(){ +return this.fosterParentingEnabled&&void 0!==this.openElements.currentTagId&&this._isElementCausesFosterParenting(this.openElements.currentTagId) +}_findFosterParentingLocation(){for(let e=this.openElements.stackTop;e>=0;e--){ +const t=this.openElements.items[e];switch(this.openElements.tagIDs[e]){ +case LD.TEMPLATE:if(this.treeAdapter.getNamespaceURI(t)===CD.HTML)return{ +parent:this.treeAdapter.getTemplateContent(t),beforeElement:null};break +;case LD.TABLE:{const n=this.treeAdapter.getParentNode(t);return n?{parent:n, +beforeElement:t}:{parent:this.openElements.items[e-1],beforeElement:null}}}} +return{parent:this.openElements.items[0],beforeElement:null}} +_fosterParentElement(e){const t=this._findFosterParentingLocation() +;t.beforeElement?this.treeAdapter.insertBefore(t.parent,e,t.beforeElement):this.treeAdapter.appendChild(t.parent,e) +}_isSpecialElement(e,t){const n=this.treeAdapter.getNamespaceURI(e) +;return VD[n].has(t)}onCharacter(e){ +if(this.skipNextNewLine=!1,this.tokenizer.inForeignNode)!function(e,t){ +e._insertCharacters(t),e.framesetOk=!1}(this,e);else switch(this.insertionMode){ +case HM.INITIAL:oN(this,e);break;case HM.BEFORE_HTML:iN(this,e);break +;case HM.BEFORE_HEAD:sN(this,e);break;case HM.IN_HEAD:uN(this,e);break +;case HM.IN_HEAD_NO_SCRIPT:dN(this,e);break;case HM.AFTER_HEAD:pN(this,e);break +;case HM.IN_BODY:case HM.IN_CAPTION:case HM.IN_CELL:case HM.IN_TEMPLATE: +mN(this,e);break;case HM.TEXT:case HM.IN_SELECT:case HM.IN_SELECT_IN_TABLE: +this._insertCharacters(e);break;case HM.IN_TABLE:case HM.IN_TABLE_BODY: +case HM.IN_ROW:SN(this,e);break;case HM.IN_TABLE_TEXT:CN(this,e);break +;case HM.IN_COLUMN_GROUP:DN(this,e);break;case HM.AFTER_BODY:ZN(this,e);break +;case HM.AFTER_AFTER_BODY:FN(this,e)}}onNullCharacter(e){ +if(this.skipNextNewLine=!1,this.tokenizer.inForeignNode)!function(e,t){ +t.chars=tD,e._insertCharacters(t)}(this,e);else switch(this.insertionMode){ +case HM.INITIAL:oN(this,e);break;case HM.BEFORE_HTML:iN(this,e);break +;case HM.BEFORE_HEAD:sN(this,e);break;case HM.IN_HEAD:uN(this,e);break +;case HM.IN_HEAD_NO_SCRIPT:dN(this,e);break;case HM.AFTER_HEAD:pN(this,e);break +;case HM.TEXT:this._insertCharacters(e);break;case HM.IN_TABLE: +case HM.IN_TABLE_BODY:case HM.IN_ROW:SN(this,e);break;case HM.IN_COLUMN_GROUP: +DN(this,e);break;case HM.AFTER_BODY:ZN(this,e);break;case HM.AFTER_AFTER_BODY: +FN(this,e)}}onComment(e){ +if(this.skipNextNewLine=!1,this.currentNotInHTML)rN(this,e);else switch(this.insertionMode){ +case HM.INITIAL:case HM.BEFORE_HTML:case HM.BEFORE_HEAD:case HM.IN_HEAD: +case HM.IN_HEAD_NO_SCRIPT:case HM.AFTER_HEAD:case HM.IN_BODY:case HM.IN_TABLE: +case HM.IN_CAPTION:case HM.IN_COLUMN_GROUP:case HM.IN_TABLE_BODY:case HM.IN_ROW: +case HM.IN_CELL:case HM.IN_SELECT:case HM.IN_SELECT_IN_TABLE: +case HM.IN_TEMPLATE:case HM.IN_FRAMESET:case HM.AFTER_FRAMESET:rN(this,e);break +;case HM.IN_TABLE_TEXT:$N(this,e);break;case HM.AFTER_BODY:!function(e,t){ +e._appendCommentNode(t,e.openElements.items[0])}(this,e);break +;case HM.AFTER_AFTER_BODY:case HM.AFTER_AFTER_FRAMESET:!function(e,t){ +e._appendCommentNode(t,e.document)}(this,e)}}onDoctype(e){ +switch(this.skipNextNewLine=!1,this.insertionMode){case HM.INITIAL: +!function(e,t){e._setDocumentType(t) +;const n=t.forceQuirks?DD.QUIRKS:function(e){if(e.name!==wM)return DD.QUIRKS +;const{systemId:t}=e;if(t&&t.toLowerCase()===kM)return DD.QUIRKS +;let{publicId:n}=e;if(null!==n){if(n=n.toLowerCase(),AM.has(n))return DD.QUIRKS +;let e=null===t?_M:SM;if(CM(n,e))return DD.QUIRKS +;if(e=null===t?TM:EM,CM(n,e))return DD.LIMITED_QUIRKS}return DD.NO_QUIRKS}(t) +;(function(e){ +return e.name===wM&&null===e.publicId&&(null===e.systemId||e.systemId===xM) +})(t)||e._err(t,hD.nonConformingDoctype) +;e.treeAdapter.setDocumentMode(e.document,n),e.insertionMode=HM.BEFORE_HTML +}(this,e);break;case HM.BEFORE_HEAD:case HM.IN_HEAD:case HM.IN_HEAD_NO_SCRIPT: +case HM.AFTER_HEAD:this._err(e,hD.misplacedDoctype);break;case HM.IN_TABLE_TEXT: +$N(this,e)}}onStartTag(e){ +this.skipNextNewLine=!1,this.currentToken=e,this._processStartTag(e), +e.selfClosing&&!e.ackSelfClosing&&this._err(e,hD.nonVoidHtmlElementStartTagWithTrailingSolidus) +}_processStartTag(e){ +this.shouldProcessStartTagTokenInForeignContent(e)?function(e,t){if(function(e){ +const t=e.tagID +;return t===LD.FONT&&e.attrs.some((({name:e})=>e===PD.COLOR||e===PD.SIZE||e===PD.FACE))||RM.has(t) +}(t))HN(e),e._startTagOutsideForeignContent(t);else{ +const n=e._getAdjustedCurrentElement(),r=e.treeAdapter.getNamespaceURI(n) +;r===CD.MATHML?LM(t):r===CD.SVG&&(!function(e){const t=NM.get(e.tagName) +;null!=t&&(e.tagName=t,e.tagID=HD(e.tagName)) +}(t),BM(t)),jM(t),t.selfClosing?e._appendElement(t,r):e._insertElement(t,r), +t.ackSelfClosing=!0}}(this,e):this._startTagOutsideForeignContent(e)} +_startTagOutsideForeignContent(e){switch(this.insertionMode){case HM.INITIAL: +oN(this,e);break;case HM.BEFORE_HTML:!function(e,t){ +t.tagID===LD.HTML?(e._insertElement(t,CD.HTML), +e.insertionMode=HM.BEFORE_HEAD):iN(e,t)}(this,e);break;case HM.BEFORE_HEAD: +!function(e,t){switch(t.tagID){case LD.HTML:ON(e,t);break;case LD.HEAD: +e._insertElement(t,CD.HTML), +e.headElement=e.openElements.current,e.insertionMode=HM.IN_HEAD;break;default: +sN(e,t)}}(this,e);break;case HM.IN_HEAD:lN(this,e);break +;case HM.IN_HEAD_NO_SCRIPT:!function(e,t){switch(t.tagID){case LD.HTML:ON(e,t) +;break;case LD.BASEFONT:case LD.BGSOUND:case LD.HEAD:case LD.LINK:case LD.META: +case LD.NOFRAMES:case LD.STYLE:lN(e,t);break;case LD.NOSCRIPT: +e._err(t,hD.nestedNoscriptInHead);break;default:dN(e,t)}}(this,e);break +;case HM.AFTER_HEAD:!function(e,t){switch(t.tagID){case LD.HTML:ON(e,t);break +;case LD.BODY: +e._insertElement(t,CD.HTML),e.framesetOk=!1,e.insertionMode=HM.IN_BODY;break +;case LD.FRAMESET:e._insertElement(t,CD.HTML),e.insertionMode=HM.IN_FRAMESET +;break;case LD.BASE:case LD.BASEFONT:case LD.BGSOUND:case LD.LINK:case LD.META: +case LD.NOFRAMES:case LD.SCRIPT:case LD.STYLE:case LD.TEMPLATE:case LD.TITLE: +e._err(t,hD.abandonedHeadElementChild), +e.openElements.push(e.headElement,LD.HEAD), +lN(e,t),e.openElements.remove(e.headElement);break;case LD.HEAD: +e._err(t,hD.misplacedStartTagForHeadElement);break;default:pN(e,t)}}(this,e) +;break;case HM.IN_BODY:ON(this,e);break;case HM.IN_TABLE:_N(this,e);break +;case HM.IN_TABLE_TEXT:$N(this,e);break;case HM.IN_CAPTION:!function(e,t){ +const n=t.tagID +;PN.has(n)?e.openElements.hasInTableScope(LD.CAPTION)&&(e.openElements.generateImpliedEndTags(), +e.openElements.popUntilTagNamePopped(LD.CAPTION), +e.activeFormattingElements.clearToLastMarker(), +e.insertionMode=HM.IN_TABLE,_N(e,t)):ON(e,t)}(this,e);break +;case HM.IN_COLUMN_GROUP:IN(this,e);break;case HM.IN_TABLE_BODY:MN(this,e);break +;case HM.IN_ROW:RN(this,e);break;case HM.IN_CELL:!function(e,t){const n=t.tagID +;PN.has(n)?(e.openElements.hasInTableScope(LD.TD)||e.openElements.hasInTableScope(LD.TH))&&(e._closeTableCell(), +RN(e,t)):ON(e,t)}(this,e);break;case HM.IN_SELECT:BN(this,e);break +;case HM.IN_SELECT_IN_TABLE:!function(e,t){const n=t.tagID +;n===LD.CAPTION||n===LD.TABLE||n===LD.TBODY||n===LD.TFOOT||n===LD.THEAD||n===LD.TR||n===LD.TD||n===LD.TH?(e.openElements.popUntilTagNamePopped(LD.SELECT), +e._resetInsertionMode(),e._processStartTag(t)):BN(e,t)}(this,e);break +;case HM.IN_TEMPLATE:!function(e,t){switch(t.tagID){case LD.BASE: +case LD.BASEFONT:case LD.BGSOUND:case LD.LINK:case LD.META:case LD.NOFRAMES: +case LD.SCRIPT:case LD.STYLE:case LD.TEMPLATE:case LD.TITLE:lN(e,t);break +;case LD.CAPTION:case LD.COLGROUP:case LD.TBODY:case LD.TFOOT:case LD.THEAD: +e.tmplInsertionModeStack[0]=HM.IN_TABLE,e.insertionMode=HM.IN_TABLE,_N(e,t) +;break;case LD.COL: +e.tmplInsertionModeStack[0]=HM.IN_COLUMN_GROUP,e.insertionMode=HM.IN_COLUMN_GROUP, +IN(e,t);break;case LD.TR: +e.tmplInsertionModeStack[0]=HM.IN_TABLE_BODY,e.insertionMode=HM.IN_TABLE_BODY, +MN(e,t);break;case LD.TD:case LD.TH: +e.tmplInsertionModeStack[0]=HM.IN_ROW,e.insertionMode=HM.IN_ROW,RN(e,t);break +;default: +e.tmplInsertionModeStack[0]=HM.IN_BODY,e.insertionMode=HM.IN_BODY,ON(e,t)} +}(this,e);break;case HM.AFTER_BODY:!function(e,t){ +t.tagID===LD.HTML?ON(e,t):ZN(e,t)}(this,e);break;case HM.IN_FRAMESET: +!function(e,t){switch(t.tagID){case LD.HTML:ON(e,t);break;case LD.FRAMESET: +e._insertElement(t,CD.HTML);break;case LD.FRAME: +e._appendElement(t,CD.HTML),t.ackSelfClosing=!0;break;case LD.NOFRAMES:lN(e,t)} +}(this,e);break;case HM.AFTER_FRAMESET:!function(e,t){switch(t.tagID){ +case LD.HTML:ON(e,t);break;case LD.NOFRAMES:lN(e,t)}}(this,e);break +;case HM.AFTER_AFTER_BODY:!function(e,t){t.tagID===LD.HTML?ON(e,t):FN(e,t) +}(this,e);break;case HM.AFTER_AFTER_FRAMESET:!function(e,t){switch(t.tagID){ +case LD.HTML:ON(e,t);break;case LD.NOFRAMES:lN(e,t)}}(this,e)}}onEndTag(e){ +this.skipNextNewLine=!1,this.currentToken=e,this.currentNotInHTML?function(e,t){ +if(t.tagID===LD.P||t.tagID===LD.BR)return HN(e), +void e._endTagOutsideForeignContent(t) +;for(let n=e.openElements.stackTop;n>0;n--){const r=e.openElements.items[n] +;if(e.treeAdapter.getNamespaceURI(r)===CD.HTML){ +e._endTagOutsideForeignContent(t);break}const a=e.treeAdapter.getTagName(r) +;if(a.toLowerCase()===t.tagName){t.tagName=a,e.openElements.shortenToLength(n) +;break}}}(this,e):this._endTagOutsideForeignContent(e)} +_endTagOutsideForeignContent(e){switch(this.insertionMode){case HM.INITIAL: +oN(this,e);break;case HM.BEFORE_HTML:!function(e,t){const n=t.tagID +;n!==LD.HTML&&n!==LD.HEAD&&n!==LD.BODY&&n!==LD.BR||iN(e,t)}(this,e);break +;case HM.BEFORE_HEAD:!function(e,t){const n=t.tagID +;n===LD.HEAD||n===LD.BODY||n===LD.HTML||n===LD.BR?sN(e,t):e._err(t,hD.endTagWithoutMatchingOpenElement) +}(this,e);break;case HM.IN_HEAD:!function(e,t){switch(t.tagID){case LD.HEAD: +e.openElements.pop(),e.insertionMode=HM.AFTER_HEAD;break;case LD.BODY: +case LD.BR:case LD.HTML:uN(e,t);break;case LD.TEMPLATE:cN(e,t);break;default: +e._err(t,hD.endTagWithoutMatchingOpenElement)}}(this,e);break +;case HM.IN_HEAD_NO_SCRIPT:!function(e,t){switch(t.tagID){case LD.NOSCRIPT: +e.openElements.pop(),e.insertionMode=HM.IN_HEAD;break;case LD.BR:dN(e,t);break +;default:e._err(t,hD.endTagWithoutMatchingOpenElement)}}(this,e);break +;case HM.AFTER_HEAD:!function(e,t){switch(t.tagID){case LD.BODY:case LD.HTML: +case LD.BR:pN(e,t);break;case LD.TEMPLATE:cN(e,t);break;default: +e._err(t,hD.endTagWithoutMatchingOpenElement)}}(this,e);break;case HM.IN_BODY: +xN(this,e);break;case HM.TEXT:!function(e,t){var n +;t.tagID===LD.SCRIPT&&(null===(n=e.scriptHandler)||void 0===n||n.call(e,e.openElements.current)) +;e.openElements.pop(),e.insertionMode=e.originalInsertionMode}(this,e);break +;case HM.IN_TABLE:AN(this,e);break;case HM.IN_TABLE_TEXT:$N(this,e);break +;case HM.IN_CAPTION:!function(e,t){const n=t.tagID;switch(n){case LD.CAPTION: +case LD.TABLE: +e.openElements.hasInTableScope(LD.CAPTION)&&(e.openElements.generateImpliedEndTags(), +e.openElements.popUntilTagNamePopped(LD.CAPTION), +e.activeFormattingElements.clearToLastMarker(), +e.insertionMode=HM.IN_TABLE,n===LD.TABLE&&AN(e,t));break;case LD.BODY: +case LD.COL:case LD.COLGROUP:case LD.HTML:case LD.TBODY:case LD.TD: +case LD.TFOOT:case LD.TH:case LD.THEAD:case LD.TR:break;default:xN(e,t)} +}(this,e);break;case HM.IN_COLUMN_GROUP:!function(e,t){switch(t.tagID){ +case LD.COLGROUP: +e.openElements.currentTagId===LD.COLGROUP&&(e.openElements.pop(), +e.insertionMode=HM.IN_TABLE);break;case LD.TEMPLATE:cN(e,t);break;case LD.COL: +break;default:DN(e,t)}}(this,e);break;case HM.IN_TABLE_BODY:NN(this,e);break +;case HM.IN_ROW:LN(this,e);break;case HM.IN_CELL:!function(e,t){const n=t.tagID +;switch(n){case LD.TD:case LD.TH: +e.openElements.hasInTableScope(n)&&(e.openElements.generateImpliedEndTags(), +e.openElements.popUntilTagNamePopped(n), +e.activeFormattingElements.clearToLastMarker(),e.insertionMode=HM.IN_ROW);break +;case LD.TABLE:case LD.TBODY:case LD.TFOOT:case LD.THEAD:case LD.TR: +e.openElements.hasInTableScope(n)&&(e._closeTableCell(),LN(e,t));break +;case LD.BODY:case LD.CAPTION:case LD.COL:case LD.COLGROUP:case LD.HTML:break +;default:xN(e,t)}}(this,e);break;case HM.IN_SELECT:jN(this,e);break +;case HM.IN_SELECT_IN_TABLE:!function(e,t){const n=t.tagID +;n===LD.CAPTION||n===LD.TABLE||n===LD.TBODY||n===LD.TFOOT||n===LD.THEAD||n===LD.TR||n===LD.TD||n===LD.TH?e.openElements.hasInTableScope(n)&&(e.openElements.popUntilTagNamePopped(LD.SELECT), +e._resetInsertionMode(),e.onEndTag(t)):jN(e,t)}(this,e);break +;case HM.IN_TEMPLATE:!function(e,t){t.tagID===LD.TEMPLATE&&cN(e,t)}(this,e) +;break;case HM.AFTER_BODY:zN(this,e);break;case HM.IN_FRAMESET:!function(e,t){ +t.tagID!==LD.FRAMESET||e.openElements.isRootHtmlElementCurrent()||(e.openElements.pop(), +e.fragmentContext||e.openElements.currentTagId===LD.FRAMESET||(e.insertionMode=HM.AFTER_FRAMESET)) +}(this,e);break;case HM.AFTER_FRAMESET:!function(e,t){ +t.tagID===LD.HTML&&(e.insertionMode=HM.AFTER_AFTER_FRAMESET)}(this,e);break +;case HM.AFTER_AFTER_BODY:FN(this,e)}}onEof(e){switch(this.insertionMode){ +case HM.INITIAL:oN(this,e);break;case HM.BEFORE_HTML:iN(this,e);break +;case HM.BEFORE_HEAD:sN(this,e);break;case HM.IN_HEAD:uN(this,e);break +;case HM.IN_HEAD_NO_SCRIPT:dN(this,e);break;case HM.AFTER_HEAD:pN(this,e);break +;case HM.IN_BODY:case HM.IN_TABLE:case HM.IN_CAPTION:case HM.IN_COLUMN_GROUP: +case HM.IN_TABLE_BODY:case HM.IN_ROW:case HM.IN_CELL:case HM.IN_SELECT: +case HM.IN_SELECT_IN_TABLE:kN(this,e);break;case HM.TEXT:!function(e,t){ +e._err(t,hD.eofInElementThatCanContainOnlyText), +e.openElements.pop(),e.insertionMode=e.originalInsertionMode,e.onEof(t)}(this,e) +;break;case HM.IN_TABLE_TEXT:$N(this,e);break;case HM.IN_TEMPLATE:UN(this,e) +;break;case HM.AFTER_BODY:case HM.IN_FRAMESET:case HM.AFTER_FRAMESET: +case HM.AFTER_AFTER_BODY:case HM.AFTER_AFTER_FRAMESET:aN(this,e)}} +onWhitespaceCharacter(e){ +if(this.skipNextNewLine&&(this.skipNextNewLine=!1,e.chars.charCodeAt(0)===nD.LINE_FEED)){ +if(1===e.chars.length)return;e.chars=e.chars.substr(1)} +if(this.tokenizer.inForeignNode)this._insertCharacters(e);else switch(this.insertionMode){ +case HM.IN_HEAD:case HM.IN_HEAD_NO_SCRIPT:case HM.AFTER_HEAD:case HM.TEXT: +case HM.IN_COLUMN_GROUP:case HM.IN_SELECT:case HM.IN_SELECT_IN_TABLE: +case HM.IN_FRAMESET:case HM.AFTER_FRAMESET:this._insertCharacters(e);break +;case HM.IN_BODY:case HM.IN_CAPTION:case HM.IN_CELL:case HM.IN_TEMPLATE: +case HM.AFTER_BODY:case HM.AFTER_AFTER_BODY:case HM.AFTER_AFTER_FRAMESET: +fN(this,e);break;case HM.IN_TABLE:case HM.IN_TABLE_BODY:case HM.IN_ROW: +SN(this,e);break;case HM.IN_TABLE_TEXT:EN(this,e)}}};function GM(e,t){ +let n=e.activeFormattingElements.getElementEntryInScopeWithTagName(t.tagName) +;return n?e.openElements.contains(n.element)?e.openElements.hasInScope(t.tagID)||(n=null):(e.activeFormattingElements.removeEntry(n), +n=null):wN(e,t),n}function YM(e,t){let n=null,r=e.openElements.stackTop +;for(;r>=0;r--){const a=e.openElements.items[r];if(a===t.element)break +;e._isSpecialElement(a,e.openElements.tagIDs[r])&&(n=a)} +return n||(e.openElements.shortenToLength(Math.max(r,0)), +e.activeFormattingElements.removeEntry(t)),n}function KM(e,t,n){ +let r=t,a=e.openElements.getCommonAncestor(t);for(let o=0,i=a;i!==n;o++,i=a){ +a=e.openElements.getCommonAncestor(i) +;const n=e.activeFormattingElements.getElementEntry(i),s=n&&o>=FM +;!n||s?(s&&e.activeFormattingElements.removeEntry(n), +e.openElements.remove(i)):(i=JM(e,n), +r===t&&(e.activeFormattingElements.bookmark=n), +e.treeAdapter.detachNode(r),e.treeAdapter.appendChild(i,r),r=i)}return r} +function JM(e,t){ +const n=e.treeAdapter.getNamespaceURI(t.element),r=e.treeAdapter.createElement(t.token.tagName,n,t.token.attrs) +;return e.openElements.replace(t.element,r),t.element=r,r}function eN(e,t,n){ +const r=HD(e.treeAdapter.getTagName(t)) +;if(e._isElementCausesFosterParenting(r))e._fosterParentElement(n);else{ +const a=e.treeAdapter.getNamespaceURI(t) +;r===LD.TEMPLATE&&a===CD.HTML&&(t=e.treeAdapter.getTemplateContent(t)), +e.treeAdapter.appendChild(t,n)}}function tN(e,t,n){ +const r=e.treeAdapter.getNamespaceURI(n.element),{token:a}=n,o=e.treeAdapter.createElement(a.tagName,r,a.attrs) +;e._adoptNodes(t,o), +e.treeAdapter.appendChild(t,o),e.activeFormattingElements.insertElementAfterBookmark(o,a), +e.activeFormattingElements.removeEntry(n), +e.openElements.remove(n.element),e.openElements.insertAfter(t,o,a.tagID)} +function nN(e,t){for(let n=0;n=n;r--)e._setEndLocation(e.openElements.items[r],t) +;if(!e.fragmentContext&&e.openElements.stackTop>=0){ +const n=e.openElements.items[0],r=e.treeAdapter.getNodeSourceCodeLocation(n) +;if(r&&!r.endTag&&(e._setEndLocation(n,t),e.openElements.stackTop>=1)){ +const n=e.openElements.items[1],r=e.treeAdapter.getNodeSourceCodeLocation(n) +;r&&!r.endTag&&e._setEndLocation(n,t)}}}}function oN(e,t){ +e._err(t,hD.missingDoctype,!0), +e.treeAdapter.setDocumentMode(e.document,DD.QUIRKS), +e.insertionMode=HM.BEFORE_HTML,e._processToken(t)}function iN(e,t){ +e._insertFakeRootElement(),e.insertionMode=HM.BEFORE_HEAD,e._processToken(t)} +function sN(e,t){ +e._insertFakeElement(ND.HEAD,LD.HEAD),e.headElement=e.openElements.current, +e.insertionMode=HM.IN_HEAD,e._processToken(t)}function lN(e,t){switch(t.tagID){ +case LD.HTML:ON(e,t);break;case LD.BASE:case LD.BASEFONT:case LD.BGSOUND: +case LD.LINK:case LD.META:e._appendElement(t,CD.HTML),t.ackSelfClosing=!0;break +;case LD.TITLE:e._switchToTextParsing(t,GD.RCDATA);break;case LD.NOSCRIPT: +e.options.scriptingEnabled?e._switchToTextParsing(t,GD.RAWTEXT):(e._insertElement(t,CD.HTML), +e.insertionMode=HM.IN_HEAD_NO_SCRIPT);break;case LD.NOFRAMES:case LD.STYLE: +e._switchToTextParsing(t,GD.RAWTEXT);break;case LD.SCRIPT: +e._switchToTextParsing(t,GD.SCRIPT_DATA);break;case LD.TEMPLATE: +e._insertTemplate(t), +e.activeFormattingElements.insertMarker(),e.framesetOk=!1,e.insertionMode=HM.IN_TEMPLATE, +e.tmplInsertionModeStack.unshift(HM.IN_TEMPLATE);break;case LD.HEAD: +e._err(t,hD.misplacedStartTagForHeadElement);break;default:uN(e,t)}} +function cN(e,t){ +e.openElements.tmplCount>0?(e.openElements.generateImpliedEndTagsThoroughly(), +e.openElements.currentTagId!==LD.TEMPLATE&&e._err(t,hD.closingOfElementWithOpenChildElements), +e.openElements.popUntilTagNamePopped(LD.TEMPLATE), +e.activeFormattingElements.clearToLastMarker(),e.tmplInsertionModeStack.shift(), +e._resetInsertionMode()):e._err(t,hD.endTagWithoutMatchingOpenElement)} +function uN(e,t){ +e.openElements.pop(),e.insertionMode=HM.AFTER_HEAD,e._processToken(t)} +function dN(e,t){ +const n=t.type===gD.EOF?hD.openElementsLeftAfterEof:hD.disallowedContentInNoscriptInHead +;e._err(t,n),e.openElements.pop(),e.insertionMode=HM.IN_HEAD,e._processToken(t)} +function pN(e,t){ +e._insertFakeElement(ND.BODY,LD.BODY),e.insertionMode=HM.IN_BODY,hN(e,t)} +function hN(e,t){switch(t.type){case gD.CHARACTER:mN(e,t);break +;case gD.WHITESPACE_CHARACTER:fN(e,t);break;case gD.COMMENT:rN(e,t);break +;case gD.START_TAG:ON(e,t);break;case gD.END_TAG:xN(e,t);break;case gD.EOF: +kN(e,t)}}function fN(e,t){ +e._reconstructActiveFormattingElements(),e._insertCharacters(t)} +function mN(e,t){ +e._reconstructActiveFormattingElements(),e._insertCharacters(t),e.framesetOk=!1} +function gN(e,t){ +e._reconstructActiveFormattingElements(),e._appendElement(t,CD.HTML), +e.framesetOk=!1,t.ackSelfClosing=!0}function vN(e){const t=bD(e,PD.TYPE) +;return null!=t&&t.toLowerCase()===zM}function bN(e,t){ +e._switchToTextParsing(t,GD.RAWTEXT)}function yN(e,t){ +e._reconstructActiveFormattingElements(),e._insertElement(t,CD.HTML)} +function ON(e,t){switch(t.tagID){case LD.I:case LD.S:case LD.B:case LD.U: +case LD.EM:case LD.TT:case LD.BIG:case LD.CODE:case LD.FONT:case LD.SMALL: +case LD.STRIKE:case LD.STRONG:!function(e,t){ +e._reconstructActiveFormattingElements(), +e._insertElement(t,CD.HTML),e.activeFormattingElements.pushElement(e.openElements.current,t) +}(e,t);break;case LD.A:!function(e,t){ +const n=e.activeFormattingElements.getElementEntryInScopeWithTagName(ND.A) +;n&&(nN(e,t), +e.openElements.remove(n.element),e.activeFormattingElements.removeEntry(n)), +e._reconstructActiveFormattingElements(), +e._insertElement(t,CD.HTML),e.activeFormattingElements.pushElement(e.openElements.current,t) +}(e,t);break;case LD.H1:case LD.H2:case LD.H3:case LD.H4:case LD.H5:case LD.H6: +!function(e,t){ +e.openElements.hasInButtonScope(LD.P)&&e._closePElement(),void 0!==e.openElements.currentTagId&&qD.has(e.openElements.currentTagId)&&e.openElements.pop(), +e._insertElement(t,CD.HTML)}(e,t);break;case LD.P:case LD.DL:case LD.OL: +case LD.UL:case LD.DIV:case LD.DIR:case LD.NAV:case LD.MAIN:case LD.MENU: +case LD.ASIDE:case LD.CENTER:case LD.FIGURE:case LD.FOOTER:case LD.HEADER: +case LD.HGROUP:case LD.DIALOG:case LD.DETAILS:case LD.ADDRESS:case LD.ARTICLE: +case LD.SEARCH:case LD.SECTION:case LD.SUMMARY:case LD.FIELDSET: +case LD.BLOCKQUOTE:case LD.FIGCAPTION:!function(e,t){ +e.openElements.hasInButtonScope(LD.P)&&e._closePElement(), +e._insertElement(t,CD.HTML)}(e,t);break;case LD.LI:case LD.DD:case LD.DT: +!function(e,t){e.framesetOk=!1;const n=t.tagID +;for(let r=e.openElements.stackTop;r>=0;r--){const t=e.openElements.tagIDs[r] +;if(n===LD.LI&&t===LD.LI||(n===LD.DD||n===LD.DT)&&(t===LD.DD||t===LD.DT)){ +e.openElements.generateImpliedEndTagsWithExclusion(t), +e.openElements.popUntilTagNamePopped(t);break} +if(t!==LD.ADDRESS&&t!==LD.DIV&&t!==LD.P&&e._isSpecialElement(e.openElements.items[r],t))break +} +e.openElements.hasInButtonScope(LD.P)&&e._closePElement(),e._insertElement(t,CD.HTML) +}(e,t);break;case LD.BR:case LD.IMG:case LD.WBR:case LD.AREA:case LD.EMBED: +case LD.KEYGEN:gN(e,t);break;case LD.HR:!function(e,t){ +e.openElements.hasInButtonScope(LD.P)&&e._closePElement(), +e._appendElement(t,CD.HTML),e.framesetOk=!1,t.ackSelfClosing=!0}(e,t);break +;case LD.RB:case LD.RTC:!function(e,t){ +e.openElements.hasInScope(LD.RUBY)&&e.openElements.generateImpliedEndTags(), +e._insertElement(t,CD.HTML)}(e,t);break;case LD.RT:case LD.RP:!function(e,t){ +e.openElements.hasInScope(LD.RUBY)&&e.openElements.generateImpliedEndTagsWithExclusion(LD.RTC), +e._insertElement(t,CD.HTML)}(e,t);break;case LD.PRE:case LD.LISTING: +!function(e,t){ +e.openElements.hasInButtonScope(LD.P)&&e._closePElement(),e._insertElement(t,CD.HTML), +e.skipNextNewLine=!0,e.framesetOk=!1}(e,t);break;case LD.XMP:!function(e,t){ +e.openElements.hasInButtonScope(LD.P)&&e._closePElement(), +e._reconstructActiveFormattingElements(), +e.framesetOk=!1,e._switchToTextParsing(t,GD.RAWTEXT)}(e,t);break;case LD.SVG: +!function(e,t){ +e._reconstructActiveFormattingElements(),BM(t),jM(t),t.selfClosing?e._appendElement(t,CD.SVG):e._insertElement(t,CD.SVG), +t.ackSelfClosing=!0}(e,t);break;case LD.HTML:!function(e,t){ +0===e.openElements.tmplCount&&e.treeAdapter.adoptAttributes(e.openElements.items[0],t.attrs) +}(e,t);break;case LD.BASE:case LD.LINK:case LD.META:case LD.STYLE:case LD.TITLE: +case LD.SCRIPT:case LD.BGSOUND:case LD.BASEFONT:case LD.TEMPLATE:lN(e,t);break +;case LD.BODY:!function(e,t){ +const n=e.openElements.tryPeekProperlyNestedBodyElement() +;n&&0===e.openElements.tmplCount&&(e.framesetOk=!1, +e.treeAdapter.adoptAttributes(n,t.attrs))}(e,t);break;case LD.FORM: +!function(e,t){const n=e.openElements.tmplCount>0 +;e.formElement&&!n||(e.openElements.hasInButtonScope(LD.P)&&e._closePElement(), +e._insertElement(t,CD.HTML),n||(e.formElement=e.openElements.current))}(e,t) +;break;case LD.NOBR:!function(e,t){ +e._reconstructActiveFormattingElements(),e.openElements.hasInScope(LD.NOBR)&&(nN(e,t), +e._reconstructActiveFormattingElements()), +e._insertElement(t,CD.HTML),e.activeFormattingElements.pushElement(e.openElements.current,t) +}(e,t);break;case LD.MATH:!function(e,t){ +e._reconstructActiveFormattingElements(), +LM(t),jM(t),t.selfClosing?e._appendElement(t,CD.MATHML):e._insertElement(t,CD.MATHML), +t.ackSelfClosing=!0}(e,t);break;case LD.TABLE:!function(e,t){ +e.treeAdapter.getDocumentMode(e.document)!==DD.QUIRKS&&e.openElements.hasInButtonScope(LD.P)&&e._closePElement(), +e._insertElement(t,CD.HTML),e.framesetOk=!1,e.insertionMode=HM.IN_TABLE}(e,t) +;break;case LD.INPUT:!function(e,t){ +e._reconstructActiveFormattingElements(),e._appendElement(t,CD.HTML), +vN(t)||(e.framesetOk=!1),t.ackSelfClosing=!0}(e,t);break;case LD.PARAM: +case LD.TRACK:case LD.SOURCE:!function(e,t){ +e._appendElement(t,CD.HTML),t.ackSelfClosing=!0}(e,t);break;case LD.IMAGE: +!function(e,t){t.tagName=ND.IMG,t.tagID=LD.IMG,gN(e,t)}(e,t);break +;case LD.BUTTON:!function(e,t){ +e.openElements.hasInScope(LD.BUTTON)&&(e.openElements.generateImpliedEndTags(), +e.openElements.popUntilTagNamePopped(LD.BUTTON)), +e._reconstructActiveFormattingElements(), +e._insertElement(t,CD.HTML),e.framesetOk=!1}(e,t);break;case LD.APPLET: +case LD.OBJECT:case LD.MARQUEE:!function(e,t){ +e._reconstructActiveFormattingElements(), +e._insertElement(t,CD.HTML),e.activeFormattingElements.insertMarker(), +e.framesetOk=!1}(e,t);break;case LD.IFRAME:!function(e,t){ +e.framesetOk=!1,e._switchToTextParsing(t,GD.RAWTEXT)}(e,t);break;case LD.SELECT: +!function(e,t){ +e._reconstructActiveFormattingElements(),e._insertElement(t,CD.HTML), +e.framesetOk=!1, +e.insertionMode=e.insertionMode===HM.IN_TABLE||e.insertionMode===HM.IN_CAPTION||e.insertionMode===HM.IN_TABLE_BODY||e.insertionMode===HM.IN_ROW||e.insertionMode===HM.IN_CELL?HM.IN_SELECT_IN_TABLE:HM.IN_SELECT +}(e,t);break;case LD.OPTION:case LD.OPTGROUP:!function(e,t){ +e.openElements.currentTagId===LD.OPTION&&e.openElements.pop(), +e._reconstructActiveFormattingElements(),e._insertElement(t,CD.HTML)}(e,t);break +;case LD.NOEMBED:case LD.NOFRAMES:bN(e,t);break;case LD.FRAMESET:!function(e,t){ +const n=e.openElements.tryPeekProperlyNestedBodyElement() +;e.framesetOk&&n&&(e.treeAdapter.detachNode(n), +e.openElements.popAllUpToHtmlElement(), +e._insertElement(t,CD.HTML),e.insertionMode=HM.IN_FRAMESET)}(e,t);break +;case LD.TEXTAREA:!function(e,t){ +e._insertElement(t,CD.HTML),e.skipNextNewLine=!0, +e.tokenizer.state=GD.RCDATA,e.originalInsertionMode=e.insertionMode, +e.framesetOk=!1,e.insertionMode=HM.TEXT}(e,t);break;case LD.NOSCRIPT: +e.options.scriptingEnabled?bN(e,t):yN(e,t);break;case LD.PLAINTEXT: +!function(e,t){ +e.openElements.hasInButtonScope(LD.P)&&e._closePElement(),e._insertElement(t,CD.HTML), +e.tokenizer.state=GD.PLAINTEXT}(e,t);break;case LD.COL:case LD.TH:case LD.TD: +case LD.TR:case LD.HEAD:case LD.FRAME:case LD.TBODY:case LD.TFOOT:case LD.THEAD: +case LD.CAPTION:case LD.COLGROUP:break;default:yN(e,t)}}function wN(e,t){ +const n=t.tagName,r=t.tagID;for(let a=e.openElements.stackTop;a>0;a--){ +const t=e.openElements.items[a],o=e.openElements.tagIDs[a] +;if(r===o&&(r!==LD.UNKNOWN||e.treeAdapter.getTagName(t)===n)){ +e.openElements.generateImpliedEndTagsWithExclusion(r), +e.openElements.stackTop>=a&&e.openElements.shortenToLength(a);break} +if(e._isSpecialElement(t,o))break}}function xN(e,t){switch(t.tagID){case LD.A: +case LD.B:case LD.I:case LD.S:case LD.U:case LD.EM:case LD.TT:case LD.BIG: +case LD.CODE:case LD.FONT:case LD.NOBR:case LD.SMALL:case LD.STRIKE: +case LD.STRONG:nN(e,t);break;case LD.P:!function(e){ +e.openElements.hasInButtonScope(LD.P)||e._insertFakeElement(ND.P,LD.P), +e._closePElement()}(e);break;case LD.DL:case LD.UL:case LD.OL:case LD.DIR: +case LD.DIV:case LD.NAV:case LD.PRE:case LD.MAIN:case LD.MENU:case LD.ASIDE: +case LD.BUTTON:case LD.CENTER:case LD.FIGURE:case LD.FOOTER:case LD.HEADER: +case LD.HGROUP:case LD.DIALOG:case LD.ADDRESS:case LD.ARTICLE:case LD.DETAILS: +case LD.SEARCH:case LD.SECTION:case LD.SUMMARY:case LD.LISTING:case LD.FIELDSET: +case LD.BLOCKQUOTE:case LD.FIGCAPTION:!function(e,t){const n=t.tagID +;e.openElements.hasInScope(n)&&(e.openElements.generateImpliedEndTags(), +e.openElements.popUntilTagNamePopped(n))}(e,t);break;case LD.LI:!function(e){ +e.openElements.hasInListItemScope(LD.LI)&&(e.openElements.generateImpliedEndTagsWithExclusion(LD.LI), +e.openElements.popUntilTagNamePopped(LD.LI))}(e);break;case LD.DD:case LD.DT: +!function(e,t){const n=t.tagID +;e.openElements.hasInScope(n)&&(e.openElements.generateImpliedEndTagsWithExclusion(n), +e.openElements.popUntilTagNamePopped(n))}(e,t);break;case LD.H1:case LD.H2: +case LD.H3:case LD.H4:case LD.H5:case LD.H6:!function(e){ +e.openElements.hasNumberedHeaderInScope()&&(e.openElements.generateImpliedEndTags(), +e.openElements.popUntilNumberedHeaderPopped())}(e);break;case LD.BR: +!function(e){ +e._reconstructActiveFormattingElements(),e._insertFakeElement(ND.BR,LD.BR), +e.openElements.pop(),e.framesetOk=!1}(e);break;case LD.BODY:!function(e,t){ +if(e.openElements.hasInScope(LD.BODY)&&(e.insertionMode=HM.AFTER_BODY, +e.options.sourceCodeLocationInfo)){ +const n=e.openElements.tryPeekProperlyNestedBodyElement() +;n&&e._setEndLocation(n,t)}}(e,t);break;case LD.HTML:!function(e,t){ +e.openElements.hasInScope(LD.BODY)&&(e.insertionMode=HM.AFTER_BODY,zN(e,t)) +}(e,t);break;case LD.FORM:!function(e){ +const t=e.openElements.tmplCount>0,{formElement:n}=e +;t||(e.formElement=null),(n||t)&&e.openElements.hasInScope(LD.FORM)&&(e.openElements.generateImpliedEndTags(), +t?e.openElements.popUntilTagNamePopped(LD.FORM):n&&e.openElements.remove(n))}(e) +;break;case LD.APPLET:case LD.OBJECT:case LD.MARQUEE:!function(e,t){ +const n=t.tagID +;e.openElements.hasInScope(n)&&(e.openElements.generateImpliedEndTags(), +e.openElements.popUntilTagNamePopped(n), +e.activeFormattingElements.clearToLastMarker())}(e,t);break;case LD.TEMPLATE: +cN(e,t);break;default:wN(e,t)}}function kN(e,t){ +e.tmplInsertionModeStack.length>0?UN(e,t):aN(e,t)}function SN(e,t){ +if(void 0!==e.openElements.currentTagId&&qM.has(e.openElements.currentTagId))switch(e.pendingCharacterTokens.length=0, +e.hasNonWhitespacePendingCharacterToken=!1, +e.originalInsertionMode=e.insertionMode, +e.insertionMode=HM.IN_TABLE_TEXT,t.type){case gD.CHARACTER:CN(e,t);break +;case gD.WHITESPACE_CHARACTER:EN(e,t)}else TN(e,t)}function _N(e,t){ +switch(t.tagID){case LD.TD:case LD.TH:case LD.TR:!function(e,t){ +e.openElements.clearBackToTableContext(), +e._insertFakeElement(ND.TBODY,LD.TBODY),e.insertionMode=HM.IN_TABLE_BODY,MN(e,t) +}(e,t);break;case LD.STYLE:case LD.SCRIPT:case LD.TEMPLATE:lN(e,t);break +;case LD.COL:!function(e,t){ +e.openElements.clearBackToTableContext(),e._insertFakeElement(ND.COLGROUP,LD.COLGROUP), +e.insertionMode=HM.IN_COLUMN_GROUP,IN(e,t)}(e,t);break;case LD.FORM: +!function(e,t){ +e.formElement||0!==e.openElements.tmplCount||(e._insertElement(t,CD.HTML), +e.formElement=e.openElements.current,e.openElements.pop())}(e,t);break +;case LD.TABLE:!function(e,t){ +e.openElements.hasInTableScope(LD.TABLE)&&(e.openElements.popUntilTagNamePopped(LD.TABLE), +e._resetInsertionMode(),e._processStartTag(t))}(e,t);break;case LD.TBODY: +case LD.TFOOT:case LD.THEAD:!function(e,t){ +e.openElements.clearBackToTableContext(), +e._insertElement(t,CD.HTML),e.insertionMode=HM.IN_TABLE_BODY}(e,t);break +;case LD.INPUT:!function(e,t){ +vN(t)?e._appendElement(t,CD.HTML):TN(e,t),t.ackSelfClosing=!0}(e,t);break +;case LD.CAPTION:!function(e,t){ +e.openElements.clearBackToTableContext(),e.activeFormattingElements.insertMarker(), +e._insertElement(t,CD.HTML),e.insertionMode=HM.IN_CAPTION}(e,t);break +;case LD.COLGROUP:!function(e,t){ +e.openElements.clearBackToTableContext(),e._insertElement(t,CD.HTML), +e.insertionMode=HM.IN_COLUMN_GROUP}(e,t);break;default:TN(e,t)}} +function AN(e,t){switch(t.tagID){case LD.TABLE: +e.openElements.hasInTableScope(LD.TABLE)&&(e.openElements.popUntilTagNamePopped(LD.TABLE), +e._resetInsertionMode());break;case LD.TEMPLATE:cN(e,t);break;case LD.BODY: +case LD.CAPTION:case LD.COL:case LD.COLGROUP:case LD.HTML:case LD.TBODY: +case LD.TD:case LD.TFOOT:case LD.TH:case LD.THEAD:case LD.TR:break;default: +TN(e,t)}}function TN(e,t){const n=e.fosterParentingEnabled +;e.fosterParentingEnabled=!0,hN(e,t),e.fosterParentingEnabled=n} +function EN(e,t){e.pendingCharacterTokens.push(t)}function CN(e,t){ +e.pendingCharacterTokens.push(t),e.hasNonWhitespacePendingCharacterToken=!0} +function $N(e,t){let n=0 +;if(e.hasNonWhitespacePendingCharacterToken)for(;n0&&e.openElements.currentTagId===LD.OPTION&&e.openElements.tagIDs[e.openElements.stackTop-1]===LD.OPTGROUP&&e.openElements.pop(), +e.openElements.currentTagId===LD.OPTGROUP&&e.openElements.pop();break +;case LD.OPTION:e.openElements.currentTagId===LD.OPTION&&e.openElements.pop() +;break;case LD.SELECT: +e.openElements.hasInSelectScope(LD.SELECT)&&(e.openElements.popUntilTagNamePopped(LD.SELECT), +e._resetInsertionMode());break;case LD.TEMPLATE:cN(e,t)}}function UN(e,t){ +e.openElements.tmplCount>0?(e.openElements.popUntilTagNamePopped(LD.TEMPLATE), +e.activeFormattingElements.clearToLastMarker(),e.tmplInsertionModeStack.shift(), +e._resetInsertionMode(),e.onEof(t)):aN(e,t)}function zN(e,t){var n +;if(t.tagID===LD.HTML){ +if(e.fragmentContext||(e.insertionMode=HM.AFTER_AFTER_BODY), +e.options.sourceCodeLocationInfo&&e.openElements.tagIDs[0]===LD.HTML){ +e._setEndLocation(e.openElements.items[0],t);const r=e.openElements.items[1] +;r&&!(null===(n=e.treeAdapter.getNodeSourceCodeLocation(r))||void 0===n?void 0:n.endTag)&&e._setEndLocation(r,t) +}}else ZN(e,t)}function ZN(e,t){e.insertionMode=HM.IN_BODY,hN(e,t)} +function FN(e,t){e.insertionMode=HM.IN_BODY,hN(e,t)}function HN(e){ +for(;e.treeAdapter.getNamespaceURI(e.openElements.current)!==CD.HTML&&void 0!==e.openElements.currentTagId&&!e._isIntegrationPoint(e.openElements.currentTagId,e.openElements.current);)e.openElements.pop() +}function QN(e,t){return XM.parse(e,t)}function VN(e,t,n){ +"string"==typeof e&&(n=t,t=e,e=null);const r=XM.getFragmentParser(e,n) +;return r.tokenizer.write(t,!0),r.getFragment()}function qN(e){ +return e&&"object"==typeof e?"position"in e||"type"in e?XN(e.position):"start"in e||"end"in e?XN(e):"line"in e||"column"in e?WN(e):"":"" +}function WN(e){return GN(e&&e.line)+":"+GN(e&&e.column)}function XN(e){ +return WN(e&&e.start)+"-"+WN(e&&e.end)}function GN(e){ +return e&&"number"==typeof e?e:1} +ND.AREA,ND.BASE,ND.BASEFONT,ND.BGSOUND,ND.BR,ND.COL, +ND.EMBED,ND.FRAME,ND.HR,ND.IMG, +ND.INPUT,ND.KEYGEN,ND.LINK,ND.META,ND.PARAM,ND.SOURCE,ND.TRACK,ND.WBR +;class YN extends Error{constructor(e,t,n){ +super(),"string"==typeof t&&(n=t,t=void 0);let r="",a={},o=!1 +;if(t&&(a="line"in t&&"column"in t||"start"in t&&"end"in t?{place:t +}:"type"in t?{ancestors:[t],place:t.position}:{...t +}),"string"==typeof e?r=e:!a.cause&&e&&(o=!0, +r=e.message,a.cause=e),!a.ruleId&&!a.source&&"string"==typeof n){ +const e=n.indexOf(":") +;-1===e?a.ruleId=n:(a.source=n.slice(0,e),a.ruleId=n.slice(e+1))} +if(!a.place&&a.ancestors&&a.ancestors){const e=a.ancestors[a.ancestors.length-1] +;e&&(a.place=e.position)} +const i=a.place&&"start"in a.place?a.place.start:a.place +;this.ancestors=a.ancestors||void 0, +this.cause=a.cause||void 0,this.column=i?i.column:void 0, +this.fatal=void 0,this.file, +this.message=r,this.line=i?i.line:void 0,this.name=qN(a.place)||"1:1", +this.place=a.place||void 0, +this.reason=this.message,this.ruleId=a.ruleId||void 0, +this.source=a.source||void 0, +this.stack=o&&a.cause&&"string"==typeof a.cause.stack?a.cause.stack:"", +this.actual,this.expected,this.note,this.url}} +YN.prototype.file="",YN.prototype.name="", +YN.prototype.reason="",YN.prototype.message="", +YN.prototype.stack="",YN.prototype.column=void 0, +YN.prototype.line=void 0,YN.prototype.ancestors=void 0, +YN.prototype.cause=void 0, +YN.prototype.fatal=void 0,YN.prototype.place=void 0,YN.prototype.ruleId=void 0, +YN.prototype.source=void 0;const KN={basename:function(e,t){ +if(void 0!==t&&"string"!=typeof t)throw new TypeError('"ext" argument must be a string') +;JN(e);let n,r=0,a=-1,o=e.length +;if(void 0===t||0===t.length||t.length>e.length){ +for(;o--;)if(47===e.codePointAt(o)){if(n){r=o+1;break}}else a<0&&(n=!0,a=o+1) +;return a<0?"":e.slice(r,a)}if(t===e)return"";let i=-1,s=t.length-1 +;for(;o--;)if(47===e.codePointAt(o)){if(n){r=o+1;break} +}else i<0&&(n=!0,i=o+1),s>-1&&(e.codePointAt(o)===t.codePointAt(s--)?s<0&&(a=o):(s=-1, +a=i));r===a?a=i:a<0&&(a=e.length);return e.slice(r,a)},dirname:function(e){ +if(JN(e),0===e.length)return".";let t,n=-1,r=e.length +;for(;--r;)if(47===e.codePointAt(r)){if(t){n=r;break}}else t||(t=!0) +;return n<0?47===e.codePointAt(0)?"/":".":1===n&&47===e.codePointAt(0)?"//":e.slice(0,n) +},extname:function(e){JN(e);let t,n=e.length,r=-1,a=0,o=-1,i=0;for(;n--;){ +const s=e.codePointAt(n) +;if(47!==s)r<0&&(t=!0,r=n+1),46===s?o<0?o=n:1!==i&&(i=1):o>-1&&(i=-1);else if(t){ +a=n+1;break}}if(o<0||r<0||0===i||1===i&&o===r-1&&o===a+1)return"" +;return e.slice(o,r)},join:function(...e){let t,n=-1 +;for(;++n2){ +if(r=a.lastIndexOf("/"),r!==a.length-1){ +r<0?(a="",o=0):(a=a.slice(0,r),o=a.length-1-a.lastIndexOf("/")),i=l,s=0;continue +}}else if(a.length>0){a="",o=0,i=l,s=0;continue} +t&&(a=a.length>0?a+"/..":"..",o=2) +}else a.length>0?a+="/"+e.slice(i+1,l):a=e.slice(i+1,l),o=l-i-1;i=l,s=0 +}else 46===n&&s>-1?s++:s=-1}return a}(e,!t);0!==n.length||t||(n=".") +;n.length>0&&47===e.codePointAt(e.length-1)&&(n+="/");return t?"/"+n:n}(t)}, +sep:"/"};function JN(e){ +if("string"!=typeof e)throw new TypeError("Path must be a string. Received "+JSON.stringify(e)) +}const eR={cwd:function(){return"/"}};function tR(e){ +return Boolean(null!==e&&"object"==typeof e&&"href"in e&&e.href&&"protocol"in e&&e.protocol&&void 0===e.auth) +}function nR(e){if("string"==typeof e)e=new URL(e);else if(!tR(e)){ +const t=new TypeError('The "path" argument must be of type string or an instance of URL. Received `'+e+"`") +;throw t.code="ERR_INVALID_ARG_TYPE",t}if("file:"!==e.protocol){ +const e=new TypeError("The URL must be of scheme file") +;throw e.code="ERR_INVALID_URL_SCHEME",e}return function(e){if(""!==e.hostname){ +const e=new TypeError('File URL host must be "localhost" or empty on darwin') +;throw e.code="ERR_INVALID_FILE_URL_HOST",e}const t=e.pathname;let n=-1 +;for(;++n`", +url:!1},abruptClosingOfEmptyComment:{ +reason:"Unexpected abruptly closed empty comment", +description:"Unexpected `>` or `->`. Expected `--\x3e` to close comments"}, +abruptDoctypePublicIdentifier:{ +reason:"Unexpected abruptly closed public identifier", +description:"Unexpected `>`. Expected a closing `\"` or `'` after the public identifier" +},abruptDoctypeSystemIdentifier:{ +reason:"Unexpected abruptly closed system identifier", +description:"Unexpected `>`. Expected a closing `\"` or `'` after the identifier identifier" +},absenceOfDigitsInNumericCharacterReference:{ +reason:"Unexpected non-digit at start of numeric character reference", +description:"Unexpected `%c`. Expected `[0-9]` for decimal references or `[0-9a-fA-F]` for hexadecimal references" +},cdataInHtmlContent:{reason:"Unexpected CDATA section in HTML", +description:"Unexpected `` in ``", +description:"Unexpected text character `%c`. Only use text in `