diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..cb5f7a2
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,16 @@
+# Ignore everything by default
+*
+
+# Allow explicitly
+!src/
+!config/
+!public/
+!packages/
+!.eslint.config.mjs
+!index.html
+!LICENSE
+!package-lock.json
+!package.json
+!postcss.config.mjs
+!tsconfig.json
+!vite.config.ts
\ No newline at end of file
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..373eae2
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,21 @@
+# To get started with Dependabot version updates, you'll need to specify which
+# package ecosystems to update and where the package manifests are located.
+# Please see the documentation for all configuration options:
+# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
+
+version: 2
+updates:
+ - package-ecosystem: "npm"
+ directory: "/" # Location of package manifests
+ schedule:
+ interval: "weekly"
+ open-pull-requests-limit: 15
+ groups:
+ production-dependencies:
+ dependency-type: "production"
+ development-dependencies:
+ dependency-type: "development"
+ - package-ecosystem: "github-actions"
+ directory: "/" # Location of package manifests
+ schedule:
+ interval: "weekly"
diff --git a/.github/workflows/on_pr_run_tests.yml b/.github/workflows/on_pr_run_tests.yml
new file mode 100644
index 0000000..a6f1fa6
--- /dev/null
+++ b/.github/workflows/on_pr_run_tests.yml
@@ -0,0 +1,52 @@
+# https://docs.github.com/en/packages/managing-github-packages-using-github-actions-workflows/publishing-and-installing-a-package-with-github-actions#upgrading-a-workflow-that-accesses-ghcrio
+
+# This workflow uses actions that are not certified by GitHub.
+# They are provided by a third-party and are governed by
+# separate terms of service, privacy policy, and support
+# documentation.
+
+# GitHub recommends pinning actions to a commit SHA.
+# To get a newer version, you will need to update the SHA.
+# You can also reference a tag or branch, but the action may change without warning.
+
+name: Validate and test
+
+on:
+ push:
+ branches: [main]
+ # See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#onpushpull_requestbranchestags
+ pull_request:
+ # Only branches and tags on the base are evaluated
+ branches: [main]
+
+env:
+ NODE_VERSION: "22"
+
+jobs:
+ npm_test:
+ runs-on: ubuntu-latest
+ env:
+ VITE_HOCUSPOCUS_SERVER_URL: "http://localhost:3000"
+ steps:
+ - name: Check out repository code
+ uses: actions/checkout@v4
+ - name: Setup Node
+ uses: actions/setup-node@v4
+ with:
+ node-version: ${{ env.NODE_VERSION }}
+ cache: "npm"
+ - run: npm ci
+ - run: npm test
+
+ npm_lint_check:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Check out repository code
+ uses: actions/checkout@v4
+ - name: Setup Node
+ uses: actions/setup-node@v4
+ with:
+ node-version: ${{ env.NODE_VERSION }}
+ cache: "npm"
+ - run: npm ci
+ - run: npm run lint-check
\ No newline at end of file
diff --git a/.github/workflows/on_release_build_image.yml b/.github/workflows/on_release_build_image.yml
new file mode 100644
index 0000000..7156fb4
--- /dev/null
+++ b/.github/workflows/on_release_build_image.yml
@@ -0,0 +1,61 @@
+# https://docs.github.com/en/packages/managing-github-packages-using-github-actions-workflows/publishing-and-installing-a-package-with-github-actions#upgrading-a-workflow-that-accesses-ghcrio
+
+# This workflow uses actions that are not certified by GitHub.
+# They are provided by a third-party and are governed by
+# separate terms of service, privacy policy, and support
+# documentation.
+
+# GitHub recommends pinning actions to a commit SHA.
+# To get a newer version, you will need to update the SHA.
+# You can also reference a tag or branch, but the action may change without warning.
+
+name: Create and publish a Docker image
+
+on:
+ push:
+ branches:
+ - main
+ release:
+ types: [published]
+
+env:
+ REGISTRY: ghcr.io
+ IMAGE_NAME: ${{ github.repository }}
+
+jobs:
+ build-and-push-image:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ packages: write
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ - name: Set up QEMU
+ uses: docker/setup-qemu-action@v3
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+ - name: Log in to the Container registry
+ uses: docker/login-action@v3
+ with:
+ registry: ${{ env.REGISTRY }}
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Extract metadata (tags, labels) for Docker
+ id: meta
+ uses: docker/metadata-action@v5
+ with:
+ images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
+
+ - name: Build and push Docker image
+ uses: docker/build-push-action@v6
+ with:
+ platforms: linux/amd64,linux/arm64
+ target: production
+ push: true
+ tags: ${{ steps.meta.outputs.tags }}
+ labels: ${{ steps.meta.outputs.labels }}
+ provenance: false
+ build-args: VITE_HOCUSPOCUS_SUBDOMAIN=write-backend
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..2c55e6d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,30 @@
+# Ignore all
+/*
+
+# Now whitelist:
+!/.github/
+!/packages
+/packages/**/dist/
+/packages/**/node_modules/
+!/config/
+!/public/
+!/src/
+!.dockerignore
+!.gitignore
+!.prettierrc
+!docker-compose-prod.yml
+!docker-compose.yml
+!Dockerfile
+!eslint.config.mjs
+!index.html
+!LICENSE
+!package.json
+!package-lock.json
+!postcss.config.mjs
+!README.md
+!tsconfig.json
+!vite.config.ts
+!vitest.config.ts
+!vitest.setup.ts
+
+.DS_Store
\ No newline at end of file
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..a1bd96f
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,6 @@
+{
+ "semi": true,
+ "trailingComma": "none",
+ "singleQuote": true,
+ "printWidth": 80
+}
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..2905924
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,44 @@
+# Base:
+FROM node:22-alpine AS base
+
+RUN apk add --no-cache bash
+
+USER node
+
+ENV APP_PATH=/home/node/app
+
+RUN mkdir -p $APP_PATH
+WORKDIR $APP_PATH
+
+COPY --chown=node:node package.json package-lock.json tsconfig.json $APP_PATH/
+
+# Development:
+FROM base AS development
+USER node
+
+RUN npm install
+
+# Production builder:
+FROM base AS production_builder
+ARG VITE_HOCUSPOCUS_SERVER_URL
+ARG VITE_HOCUSPOCUS_SUBDOMAIN
+
+USER node
+
+# first, copy packages as these are built during the install step
+COPY --chown=node:node packages $APP_PATH/packages
+
+RUN npm ci
+
+COPY --chown=node:node src $APP_PATH/src
+COPY --chown=node:node public $APP_PATH/public
+COPY --chown=node:node config $APP_PATH/config
+COPY --chown=node:node postcss.config.mjs vite.config.ts index.html $APP_PATH/
+
+RUN npm run build
+
+# Production:
+FROM nginxinc/nginx-unprivileged:stable-alpine-slim AS production
+
+COPY --from=production_builder --chown=nginx:nginx /home/node/app/dist /usr/share/nginx/html
+COPY --from=production_builder --chown=nginx:nginx /home/node/app/config/nginx/default.conf /etc/nginx/conf.d/default.conf
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
index 5e22367..ede74ff 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2025 b310-digital
+Copyright (c) 2024 b310 digital gmbh
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index efa76ea..762b0ba 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,62 @@
-# groupwriter-frontend
\ No newline at end of file
+# GroupWriter: Frontend
+
+GroupWriter's frontend is a React application served via Nginx. It synchronizes data with a Hocuspocus-based Node.js backend using the Yjs framework for real-time collaboration.
+
+## Setup
+
+### Development
+
+Clone the backend first in the parental folder: `git clone git@github.com:b310-digital/groupwriter-backend.git`.
+
+```
+docker compose build
+docker compose up -d
+docker compose exec editor npm run dev # server starts on 5173 by default
+docker compose exec backend npm run start:dev # start the backend
+```
+Requests are proxied to the backend with the `/backend` path in dev, see `vite.config.ts`.
+
+Browse to `http://localhost:5173`
+
+
+### Production
+Currently, the app is expected to run as a subdomain `write` and the backend under the subdomain `write-backend`.
+
+### Options
+Attention: Options need to be passed during build time.
+
+- `VITE_HOCUSPOCUS_SUBDOMAIN`: Name of the subdomain where the backend resides and where requests are routed to. Default: `write-backend`
+- `VITE_HOCUSPOCUS_SERVER_URL`: Backend Server URL, in case the subdomain is not used. If set, overwrites the subdomain connection option. Undefined by default.
+- `VITE_LEGAL_URL`: URL to legal statement. Undefined by default.
+- `VITE_PRIVACY_STATEMENT_URL`: URL to privacy statement. Undefined by default.
+
+## Design Decisions
+
+### [TipTap](https://tiptap.dev) / [ProseMirror](https://prosemirror.net)
+The editor is based on TipTap which is itself based on the popular ProseMirror Editor. The editor is extended with a couple of plugins.
+
+### [hocuspocus](https://tiptap.dev/docs/hocuspocus/introduction) / [yjs](https://yjs.dev)
+The sync the data, yjs and a compatible server is being used.
+
+### Comments & Suggestions
+
+Comments are added as marks, meaning they are inlined in nodes. A comment id is generated and saved along with the color of the comment (which is identical to the user's color) in the attributes of the mark (span element). The raw data of comments, meaning text and other meta data, are saved in a different yjs map but inside the same document to ensure both data structures are synced together. If a comment is deleted, first the marking inside the editor is deleted and then the comment itself in the yjs map is deleted.
+
+Its gets more interesting for redo/undo operations: When undoing, the marking disappears but the user might redo this operation. Therefore, the comment is NOT deleted from the yjs map. This means eventually storing unused / deleted comments but ensures successful redoing and undoing operations.
+
+## Testimonials / Sponsors
+
+
+
+
+kits is a project platform hosted by a public institution for quality
+development in schools (Lower Saxony, Germany) and focusses on digital tools
+and media in language teaching. GroupWriter can
+be found on https://kits.blog/tools and can be used by schools for free.
+
+Logos and text provided with courtesy of kits.
+
+## Acknowledgements
+- Background image by [Jessica Lewis](https://www.pexels.com/de-de/foto/gelbe-orange-rosa-und-blaue-malstifte-auf-weissem-notizbuch-998591/)
+- Main icons: [Hero Icons](https://github.com/tailwindlabs/heroicons)
+- Additional icons: [Tabler Icons](https://github.com/tabler/tabler-icons)
diff --git a/config/nginx/default.conf b/config/nginx/default.conf
new file mode 100644
index 0000000..f2cbddd
--- /dev/null
+++ b/config/nginx/default.conf
@@ -0,0 +1,30 @@
+server {
+ listen 8080;
+ server_name _;
+ charset utf-8;
+
+ location / {
+ root /usr/share/nginx/html;
+ index index.html;
+ try_files $uri /index.html;
+
+
+
+ location ~* \.(?:ico|css|js|gif|jpe?g|png|woff2?|eot|ttf|svg|webp)$ {
+ expires 1y;
+ access_log off;
+ add_header Cache-Control "public, max-age=31536000, immutable";
+
+ gzip on;
+ gzip_types image/svg+xml image/webp text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss font/woff2;
+ gzip_proxied any;
+ gzip_min_length 256;
+ gzip_vary on;
+ gzip_comp_level 5;
+
+ }
+ }
+
+ error_page 404 /index.html;
+ error_page 500 502 503 504 /50x.html;
+}
\ No newline at end of file
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..9db7a5d
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,75 @@
+services:
+ editor:
+ build:
+ context: .
+ target: development
+ tty: true
+ stdin_open: true
+ environment:
+ BINDING: "0.0.0.0"
+ VITE_HOCUSPOCUS_SERVER_URL: "http://localhost:5173/backend"
+ ports:
+ - "${APP_FRONTEND_PORT:-5173}:5173"
+ # preview
+ - "4173:4173"
+ volumes:
+ - .:/home/node/app
+ #- app_editor_node_modules:/home/node/app
+ backend:
+ build:
+ context: ../groupwriter-backend
+ target: development
+ container_name: backend
+ tty: true
+ stdin_open: true
+ environment:
+ DATABASE_URL: postgresql://groupwriter-user:groupwriter-password@postgres/groupwriter-backend-dev
+ PORT: 3000
+ OBJECT_STORAGE_BUCKET: groupwriter
+ OBJECT_STORAGE_SCHEME: "http://"
+ OBJECT_STORAGE_HOST: minio
+ OBJECT_STORAGE_PORT: 9000
+ OBJECT_STORAGE_REGION: local
+ OBJECT_STORAGE_USER: ${DOCKER_COMPOSE_APP_OBJECT_STORAGE_USER}
+ OBJECT_STORAGE_PASSWORD: ${DOCKER_COMPOSE_APP_OBJECT_STORAGE_PASSWORD}
+ VAULT_ENCRYPTION_KEY_BASE64: ${DOCKER_COMPOSE_APP_VAULT_ENCRYPTION_KEY_BASE64}
+ FEATURE_REMOVE_DOCUMENTS_TOGGLE: ${DOCKER_COMPOSE_APP_FEATURE_REMOVE_DOCUMENTS_TOGGLE:-true}
+ FEATURE_REMOVE_DOCUMENTS_MAX_AGE_IN_DAYS: ${DOCKER_COMPOSE_APP_FEATURE_REMOVE_DOCUMENTS_MAX_AGE_IN_DAYS:-730}
+ ports:
+ - "3000:3000"
+ volumes:
+ - ../groupwriter-backend:/home/node/app
+ #- app_backend_node_modules:/home/node/app/node_modules
+ restart: always
+ postgres:
+ image: postgres:15-alpine
+ environment:
+ PGDATA: /var/lib/postgresql/data/pgdata
+ POSTGRES_DB: ${POSTGRES_DB:-groupwriter-backend-dev}
+ POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-groupwriter-password}
+ POSTGRES_USER: ${POSTGRES_USER:-groupwriter-user}
+
+ # Exposing the port is not needed unless you want to access this database instance from the host.
+ # Be careful when other postgres docker container are running on the same port
+ ports:
+ - "${POSTGRES_PORT:-5432}:5432"
+ volumes:
+ - postgres_data:/var/lib/postgresql/data/pgdata
+
+ minio:
+ image: minio/minio
+ container_name: minio
+ ports:
+ - "9000:9000"
+ - "9001:9001"
+ environment:
+ MINIO_ROOT_USER: ${DOCKER_COMPOSE_MINIO_USER:-ROOTNAME}
+ MINIO_ROOT_PASSWORD: ${DOCKER_COMPOSE_MINIO_PASSWORD:-CHANGEME123}
+ volumes:
+ - ~/minio/data:/data
+ command: server /data --console-address ":9001"
+
+volumes:
+ postgres_data:
+ app_editor_node_modules:
+ app_backend_node_modules:
\ No newline at end of file
diff --git a/eslint.config.mjs b/eslint.config.mjs
new file mode 100644
index 0000000..59c5f7a
--- /dev/null
+++ b/eslint.config.mjs
@@ -0,0 +1,22 @@
+// @ts-check
+
+import eslint from '@eslint/js';
+import tseslint from 'typescript-eslint';
+
+
+export default tseslint.config(
+ eslint.configs.recommended,
+ tseslint.configs.recommendedTypeChecked,
+ tseslint.configs.stylisticTypeChecked,
+ { ignores: ['dist/*', 'packages/**/dist/*'] },
+ {
+ languageOptions: {
+ parserOptions: {
+ projectService: {
+ allowDefaultProject: ['*.js', '*.mjs'],
+ },
+ tsconfigRootDir: import.meta.dirname,
+ },
+ },
+ },
+);
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..23649fd
--- /dev/null
+++ b/index.html
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ GroupWriter
+
+
+
+
+
+
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..6c46fae
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,6996 @@
+{
+ "name": "groupwriter-editor",
+ "version": "0.0.1",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "groupwriter-editor",
+ "version": "0.0.1",
+ "workspaces": [
+ "packages/tiptap-extension-comment-collaboration",
+ "packages/tiptap-extension-color-with-classes",
+ "packages/tiptap-extension-image-delete-callback"
+ ],
+ "dependencies": {
+ "@heroicons/react": "^2.2.0",
+ "@hocuspocus/provider": "^2.15.0",
+ "@packages/tiptap-extension-color-with-classes": "*",
+ "@packages/tiptap-extension-comment-collaboration": "*",
+ "@packages/tiptap-extension-image-delete-callback": "*",
+ "@tailwindcss/postcss": "^4.0.0",
+ "@tailwindcss/vite": "^4.0.0",
+ "@tiptap/core": "^2.11.3",
+ "@tiptap/extension-collaboration": "^2.11.3",
+ "@tiptap/extension-collaboration-cursor": "^2.11.3",
+ "@tiptap/extension-color": "^2.11.3",
+ "@tiptap/extension-image": "^2.11.3",
+ "@tiptap/extension-link": "^2.11.3",
+ "@tiptap/extension-placeholder": "^2.11.3",
+ "@tiptap/extension-table": "^2.11.3",
+ "@tiptap/extension-table-cell": "^2.11.3",
+ "@tiptap/extension-table-header": "^2.11.3",
+ "@tiptap/extension-table-row": "^2.11.3",
+ "@tiptap/extension-text-style": "^2.11.3",
+ "@tiptap/extension-underline": "^2.11.3",
+ "@tiptap/pm": "^2.11.3",
+ "@tiptap/react": "^2.11.3",
+ "@tiptap/starter-kit": "^2.11.3",
+ "i18next": "^24.2.1",
+ "i18next-browser-languagedetector": "^8.0.2",
+ "jspdf": "^2.5.2",
+ "postcss": "^8.5.1",
+ "qr-code-styling": "^1.9.1",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0",
+ "react-i18next": "^15.4.0",
+ "react-router": "^7.1.3",
+ "tailwindcss": "^4.0.0",
+ "uuid": "^11.0.5",
+ "y-prosemirror": "^1.2.15",
+ "yjs": "^13.6.23"
+ },
+ "devDependencies": {
+ "@eslint/js": "^9.18.0",
+ "@testing-library/jest-dom": "^6.6.3",
+ "@testing-library/react": "^16.2.0",
+ "@types/node": "^22.10.10",
+ "@types/react": "^19.0.8",
+ "@types/react-dom": "^19.0.3",
+ "@vitejs/plugin-react": "^4.3.1",
+ "autoprefixer": "^10.4.20",
+ "eslint": "^9.18.0",
+ "eslint-plugin-react-hooks": "^5.1.0",
+ "eslint-plugin-react-refresh": "^0.4.18",
+ "globals": "^15.14.0",
+ "jsdom": "^26.0.0",
+ "prettier": "^3.4.2",
+ "typescript": "^5.7.3",
+ "typescript-eslint": "^8.21.0",
+ "vite": "^6.0.11",
+ "vitest": "^3.0.4"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-linux-x64-musl": "4.31.0"
+ }
+ },
+ "node_modules/@adobe/css-tools": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.1.tgz",
+ "integrity": "sha512-12WGKBQzjUAI4ayyF4IAtfw2QR/IDoqk6jTddXDhtYTJF9ASmoE1zst7cVtP0aL/F1jUJL5r+JxKXKEgHNbEUQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@alloc/quick-lru": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
+ "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@ampproject/remapping": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
+ "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@asamuzakjp/css-color": {
+ "version": "2.8.2",
+ "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-2.8.2.tgz",
+ "integrity": "sha512-RtWv9jFN2/bLExuZgFFZ0I3pWWeezAHGgrmjqGGWclATl1aDe3yhCUaI0Ilkp6OCk9zX7+FjvDasEX8Q9Rxc5w==",
+ "dev": true,
+ "dependencies": {
+ "@csstools/css-calc": "^2.1.1",
+ "@csstools/css-color-parser": "^3.0.7",
+ "@csstools/css-parser-algorithms": "^3.0.4",
+ "@csstools/css-tokenizer": "^3.0.3",
+ "lru-cache": "^11.0.2"
+ }
+ },
+ "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": {
+ "version": "11.0.2",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.2.tgz",
+ "integrity": "sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==",
+ "dev": true,
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.26.2",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz",
+ "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.25.9",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.26.2",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.2.tgz",
+ "integrity": "sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.26.0",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz",
+ "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@ampproject/remapping": "^2.2.0",
+ "@babel/code-frame": "^7.26.0",
+ "@babel/generator": "^7.26.0",
+ "@babel/helper-compilation-targets": "^7.25.9",
+ "@babel/helper-module-transforms": "^7.26.0",
+ "@babel/helpers": "^7.26.0",
+ "@babel/parser": "^7.26.0",
+ "@babel/template": "^7.25.9",
+ "@babel/traverse": "^7.25.9",
+ "@babel/types": "^7.26.0",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.26.2",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.2.tgz",
+ "integrity": "sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.26.2",
+ "@babel/types": "^7.26.0",
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.25",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz",
+ "integrity": "sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.25.9",
+ "@babel/helper-validator-option": "^7.25.9",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz",
+ "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.25.9",
+ "@babel/types": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.26.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz",
+ "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.25.9",
+ "@babel/helper-validator-identifier": "^7.25.9",
+ "@babel/traverse": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz",
+ "integrity": "sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
+ "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
+ "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz",
+ "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.26.0",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz",
+ "integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.25.9",
+ "@babel/types": "^7.26.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.26.2",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz",
+ "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.26.0"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.25.9.tgz",
+ "integrity": "sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.25.9.tgz",
+ "integrity": "sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/runtime": {
+ "version": "7.26.0",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz",
+ "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==",
+ "license": "MIT",
+ "dependencies": {
+ "regenerator-runtime": "^0.14.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz",
+ "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.25.9",
+ "@babel/parser": "^7.25.9",
+ "@babel/types": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.9.tgz",
+ "integrity": "sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.25.9",
+ "@babel/generator": "^7.25.9",
+ "@babel/parser": "^7.25.9",
+ "@babel/template": "^7.25.9",
+ "@babel/types": "^7.25.9",
+ "debug": "^4.3.1",
+ "globals": "^11.1.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse/node_modules/globals": {
+ "version": "11.12.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
+ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.26.0",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz",
+ "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.25.9",
+ "@babel/helper-validator-identifier": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@bufbuild/protobuf": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.2.2.tgz",
+ "integrity": "sha512-UNtPCbrwrenpmrXuRwn9jYpPoweNXj8X5sMvYgsqYyaH8jQ6LfUJSk3dJLnBK+6sfYPrF4iAIo5sd5HQ+tg75A==",
+ "license": "(Apache-2.0 AND BSD-3-Clause)",
+ "optional": true,
+ "peer": true
+ },
+ "node_modules/@csstools/color-helpers": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.1.tgz",
+ "integrity": "sha512-MKtmkA0BX87PKaO1NFRTFH+UnkgnmySQOvNxJubsadusqPEC2aJ9MOQiMceZJJ6oitUl/i0L6u0M1IrmAOmgBA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@csstools/css-calc": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.1.tgz",
+ "integrity": "sha512-rL7kaUnTkL9K+Cvo2pnCieqNpTKgQzy5f+N+5Iuko9HAoasP+xgprVh7KN/MaJVvVL1l0EzQq2MoqBHKSrDrag==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@csstools/css-parser-algorithms": "^3.0.4",
+ "@csstools/css-tokenizer": "^3.0.3"
+ }
+ },
+ "node_modules/@csstools/css-color-parser": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.7.tgz",
+ "integrity": "sha512-nkMp2mTICw32uE5NN+EsJ4f5N+IGFeCFu4bGpiKgb2Pq/7J/MpyLBeQ5ry4KKtRFZaYs6sTmcMYrSRIyj5DFKA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "dependencies": {
+ "@csstools/color-helpers": "^5.0.1",
+ "@csstools/css-calc": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@csstools/css-parser-algorithms": "^3.0.4",
+ "@csstools/css-tokenizer": "^3.0.3"
+ }
+ },
+ "node_modules/@csstools/css-parser-algorithms": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.4.tgz",
+ "integrity": "sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@csstools/css-tokenizer": "^3.0.3"
+ }
+ },
+ "node_modules/@csstools/css-tokenizer": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.3.tgz",
+ "integrity": "sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz",
+ "integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz",
+ "integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz",
+ "integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz",
+ "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eslint-visitor-keys": "^3.4.3"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.12.1",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz",
+ "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/config-array": {
+ "version": "0.19.0",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.0.tgz",
+ "integrity": "sha512-zdHg2FPIFNKPdcHWtiNT+jEFCHYVplAXRDlQDyqy0zGx/q2parwh7brGJSiTxRk/TSMkbM//zt/f5CHgyTyaSQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/object-schema": "^2.1.4",
+ "debug": "^4.3.1",
+ "minimatch": "^3.1.2"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/core": {
+ "version": "0.10.0",
+ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.10.0.tgz",
+ "integrity": "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@types/json-schema": "^7.0.15"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz",
+ "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^10.0.1",
+ "globals": "^14.0.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.0",
+ "minimatch": "^3.1.2",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/globals": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
+ "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@eslint/js": {
+ "version": "9.18.0",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.18.0.tgz",
+ "integrity": "sha512-fK6L7rxcq6/z+AaQMtiFTkvbHkBLNlwyRxHpKawP0x3u9+NC6MQTnFW+AdpwC6gfHTW0051cokQgtTN2FqlxQA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/object-schema": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz",
+ "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/plugin-kit": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz",
+ "integrity": "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/core": "^0.10.0",
+ "levn": "^0.4.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@heroicons/react": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@heroicons/react/-/react-2.2.0.tgz",
+ "integrity": "sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": ">= 16 || ^19.0.0-rc"
+ }
+ },
+ "node_modules/@hocuspocus/common": {
+ "version": "2.15.0",
+ "resolved": "https://registry.npmjs.org/@hocuspocus/common/-/common-2.15.0.tgz",
+ "integrity": "sha512-xmapO5PnQvf3lYFWrrOaYPjmdrEIVYOpyjInuiCkCzkUmMQCZDVM3wXjPdMJbgAtQXEPUDVkwpr6dJTLjZgTTQ==",
+ "license": "MIT",
+ "dependencies": {
+ "lib0": "^0.2.87"
+ }
+ },
+ "node_modules/@hocuspocus/provider": {
+ "version": "2.15.0",
+ "resolved": "https://registry.npmjs.org/@hocuspocus/provider/-/provider-2.15.0.tgz",
+ "integrity": "sha512-Zd1YYVIg1PYfxqMbwWlb89+R/pOGg+UQZWBr7u3g2RDOttdM8F9zzyUzm9XcI7CAGZAUvuqB/oiSsMyGxDsuNg==",
+ "license": "MIT",
+ "dependencies": {
+ "@hocuspocus/common": "^2.15.0",
+ "@lifeomic/attempt": "^3.0.2",
+ "lib0": "^0.2.87",
+ "ws": "^8.17.1"
+ },
+ "peerDependencies": {
+ "y-protocols": "^1.0.6",
+ "yjs": "^13.6.8"
+ }
+ },
+ "node_modules/@humanfs/core": {
+ "version": "0.19.1",
+ "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
+ "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanfs/node": {
+ "version": "0.16.6",
+ "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz",
+ "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@humanfs/core": "^0.19.1",
+ "@humanwhocodes/retry": "^0.3.0"
+ },
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz",
+ "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/retry": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz",
+ "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
+ "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/set-array": "^1.2.1",
+ "@jridgewell/sourcemap-codec": "^1.4.10",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/set-array": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
+ "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
+ "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.25",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
+ "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@lifeomic/attempt": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@lifeomic/attempt/-/attempt-3.1.0.tgz",
+ "integrity": "sha512-QZqem4QuAnAyzfz+Gj5/+SLxqwCAw2qmt7732ZXodr6VDWGeYLG6w1i/vYLa55JQM9wRuBKLmXmiZ2P0LtE5rw==",
+ "license": "MIT"
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@packages/tiptap-extension-color-with-classes": {
+ "resolved": "packages/tiptap-extension-color-with-classes",
+ "link": true
+ },
+ "node_modules/@packages/tiptap-extension-comment-collaboration": {
+ "resolved": "packages/tiptap-extension-comment-collaboration",
+ "link": true
+ },
+ "node_modules/@packages/tiptap-extension-image-delete-callback": {
+ "resolved": "packages/tiptap-extension-image-delete-callback",
+ "link": true
+ },
+ "node_modules/@popperjs/core": {
+ "version": "2.11.8",
+ "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
+ "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/popperjs"
+ }
+ },
+ "node_modules/@remirror/core-constants": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-3.0.0.tgz",
+ "integrity": "sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==",
+ "license": "MIT"
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.27.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.27.4.tgz",
+ "integrity": "sha512-2Y3JT6f5MrQkICUyRVCw4oa0sutfAsgaSsb0Lmmy1Wi2y7X5vT9Euqw4gOsCyy0YfKURBg35nhUKZS4mDcfULw==",
+ "cpu": [
+ "arm"
+ ],
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.27.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.27.4.tgz",
+ "integrity": "sha512-wzKRQXISyi9UdCVRqEd0H4cMpzvHYt1f/C3CoIjES6cG++RHKhrBj2+29nPF0IB5kpy9MS71vs07fvrNGAl/iA==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.27.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.27.4.tgz",
+ "integrity": "sha512-PlNiRQapift4LNS8DPUHuDX/IdXiLjf8mc5vdEmUR0fF/pyy2qWwzdLjB+iZquGr8LuN4LnUoSEvKRwjSVYz3Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.27.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.27.4.tgz",
+ "integrity": "sha512-o9bH2dbdgBDJaXWJCDTNDYa171ACUdzpxSZt+u/AAeQ20Nk5x+IhA+zsGmrQtpkLiumRJEYef68gcpn2ooXhSQ==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.27.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.27.4.tgz",
+ "integrity": "sha512-NBI2/i2hT9Q+HySSHTBh52da7isru4aAAo6qC3I7QFVsuhxi2gM8t/EI9EVcILiHLj1vfi+VGGPaLOUENn7pmw==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.27.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.27.4.tgz",
+ "integrity": "sha512-wYcC5ycW2zvqtDYrE7deary2P2UFmSh85PUpAx+dwTCO9uw3sgzD6Gv9n5X4vLaQKsrfTSZZ7Z7uynQozPVvWA==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.27.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.27.4.tgz",
+ "integrity": "sha512-9OwUnK/xKw6DyRlgx8UizeqRFOfi9mf5TYCw1uolDaJSbUmBxP85DE6T4ouCMoN6pXw8ZoTeZCSEfSaYo+/s1w==",
+ "cpu": [
+ "arm"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.27.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.27.4.tgz",
+ "integrity": "sha512-Vgdo4fpuphS9V24WOV+KwkCVJ72u7idTgQaBoLRD0UxBAWTF9GWurJO9YD9yh00BzbkhpeXtm6na+MvJU7Z73A==",
+ "cpu": [
+ "arm"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.27.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.27.4.tgz",
+ "integrity": "sha512-pleyNgyd1kkBkw2kOqlBx+0atfIIkkExOTiifoODo6qKDSpnc6WzUY5RhHdmTdIJXBdSnh6JknnYTtmQyobrVg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.27.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.27.4.tgz",
+ "integrity": "sha512-caluiUXvUuVyCHr5DxL8ohaaFFzPGmgmMvwmqAITMpV/Q+tPoaHZ/PWa3t8B2WyoRcIIuu1hkaW5KkeTDNSnMA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
+ "version": "4.27.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.27.4.tgz",
+ "integrity": "sha512-FScrpHrO60hARyHh7s1zHE97u0KlT/RECzCKAdmI+LEoC1eDh/RDji9JgFqyO+wPDb86Oa/sXkily1+oi4FzJQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.27.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.27.4.tgz",
+ "integrity": "sha512-qyyprhyGb7+RBfMPeww9FlHwKkCXdKHeGgSqmIXw9VSUtvyFZ6WZRtnxgbuz76FK7LyoN8t/eINRbPUcvXB5fw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.27.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.27.4.tgz",
+ "integrity": "sha512-PFz+y2kb6tbh7m3A7nA9++eInGcDVZUACulf/KzDtovvdTizHpZaJty7Gp0lFwSQcrnebHOqxF1MaKZd7psVRg==",
+ "cpu": [
+ "s390x"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.27.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.27.4.tgz",
+ "integrity": "sha512-Ni8mMtfo+o/G7DVtweXXV/Ol2TFf63KYjTtoZ5f078AUgJTmaIJnj4JFU7TK/9SVWTaSJGxPi5zMDgK4w+Ez7Q==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.31.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.31.0.tgz",
+ "integrity": "sha512-ypB/HMtcSGhKUQNiFwqgdclWNRrAYDH8iMYH4etw/ZlGwiTVxBz2tDrGRrPlfZu6QjXwtd+C3Zib5pFqID97ZA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.27.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.27.4.tgz",
+ "integrity": "sha512-yOpVsA4K5qVwu2CaS3hHxluWIK5HQTjNV4tWjQXluMiiiu4pJj4BN98CvxohNCpcjMeTXk/ZMJBRbgRg8HBB6A==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.27.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.27.4.tgz",
+ "integrity": "sha512-KtwEJOaHAVJlxV92rNYiG9JQwQAdhBlrjNRp7P9L8Cb4Rer3in+0A+IPhJC9y68WAi9H0sX4AiG2NTsVlmqJeQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.27.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.27.4.tgz",
+ "integrity": "sha512-3j4jx1TppORdTAoBJRd+/wJRGCPC0ETWkXOecJ6PPZLj6SptXkrXcNqdj0oclbKML6FkQltdz7bBA3rUSirZug==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@tailwindcss/node": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.0.0.tgz",
+ "integrity": "sha512-tfG2uBvo6j6kDIPmntxwXggCOZAt7SkpAXJ6pTIYirNdk5FBqh/CZZ9BZPpgcl/tNFLs6zc4yghM76sqiELG9g==",
+ "license": "MIT",
+ "dependencies": {
+ "enhanced-resolve": "^5.18.0",
+ "jiti": "^2.4.2",
+ "tailwindcss": "4.0.0"
+ }
+ },
+ "node_modules/@tailwindcss/oxide": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.0.0.tgz",
+ "integrity": "sha512-W3FjpJgy4VV1JiL7iBYDf2n/WkeDg1Il+0Q7eWnqPyvkPPCo/Mbwc5BiaT7dfBNV6tQKAhVE34rU5xl8pSl50w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10"
+ },
+ "optionalDependencies": {
+ "@tailwindcss/oxide-android-arm64": "4.0.0",
+ "@tailwindcss/oxide-darwin-arm64": "4.0.0",
+ "@tailwindcss/oxide-darwin-x64": "4.0.0",
+ "@tailwindcss/oxide-freebsd-x64": "4.0.0",
+ "@tailwindcss/oxide-linux-arm-gnueabihf": "4.0.0",
+ "@tailwindcss/oxide-linux-arm64-gnu": "4.0.0",
+ "@tailwindcss/oxide-linux-arm64-musl": "4.0.0",
+ "@tailwindcss/oxide-linux-x64-gnu": "4.0.0",
+ "@tailwindcss/oxide-linux-x64-musl": "4.0.0",
+ "@tailwindcss/oxide-win32-arm64-msvc": "4.0.0",
+ "@tailwindcss/oxide-win32-x64-msvc": "4.0.0"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-android-arm64": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.0.0.tgz",
+ "integrity": "sha512-EAhjU0+FIdyGPR+7MbBWubLLPtmOu+p7c2egTTFBRk/n//zYjNvVK0WhcBK5Y7oUB5mo4EjA2mCbY7dcEMWSRw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-arm64": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.0.0.tgz",
+ "integrity": "sha512-hdz4xnSWS11cIp+7ye+3dGHqs0X33z+BXXTtgPOguDWVa+TdXUzwxonklSzf5wlJFuot3dv5eWzhlNai0oYYQg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-x64": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.0.0.tgz",
+ "integrity": "sha512-+dOUUaXTkPKKhtUI9QtVaYg+MpmLh2CN0dHohiYXaBirEyPMkjaT0zbRgzQlNnQWjCVVXPQluIEb0OMEjSTH+Q==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-freebsd-x64": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.0.0.tgz",
+ "integrity": "sha512-CJhGDhxnrmu4SwyC62fA+wP24MhA/TZlIhRHqg1kRuIHoGoVR2uSSm1qxTxU37tSSZj8Up0q6jsBJCAP4k7rgQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.0.0.tgz",
+ "integrity": "sha512-Wy7Av0xzXfY2ujZBcYy4+7GQm25/J1iHvlQU2CfwdDCuPWfIjYzR6kggz+uVdSJyKV2s64znchBxRE8kV4uXSA==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.0.0.tgz",
+ "integrity": "sha512-srwBo2l6pvM0swBntc1ucuhGsfFOLkqPRFQ3dWARRTfSkL1U9nAsob2MKc/n47Eva/W9pZZgMOuf7rDw8pK1Ew==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-musl": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.0.0.tgz",
+ "integrity": "sha512-abhusswkduYWuezkBmgo0K0/erGq3M4Se5xP0fhc/0dKs0X/rJUYYCFWntHb3IGh3aVzdQ0SXJs93P76DbUqtw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-gnu": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.0.0.tgz",
+ "integrity": "sha512-hGtRYIUEx377/HlU49+jvVKKwU1MDSKYSMMs0JFO2Wp7LGxk5+0j5+RBk9NFnmp/lbp32yPTgIOO5m1BmDq36A==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-musl": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.0.0.tgz",
+ "integrity": "sha512-7xgQgSAThs0I14VAgmxpJnK6XFSZBxHMGoDXkLyYkEnu+8WRQMbCP93dkCUn2PIv+Q+JulRgc00PJ09uORSLXQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.0.0.tgz",
+ "integrity": "sha512-qEcgTIPcWY5ZE7f6VxQ/JPrSFMcehzVIlZj7sGE3mVd5YWreAT+Fl1vSP8q2pjnWXn0avZG3Iw7a2hJQAm+fTQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-x64-msvc": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.0.0.tgz",
+ "integrity": "sha512-bqT0AY8RXb8GMDy28JtngvqaOSB2YixbLPLvUo6I6lkvvUwA6Eqh2Tj60e2Lh7O/k083f8tYiB0WEK4wmTI7Jg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/postcss": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.0.0.tgz",
+ "integrity": "sha512-lI2bPk4TvwavHdehjr5WiC6HnZ59hacM6ySEo4RM/H7tsjWd8JpqiNW9ThH7rO/yKtrn4mGBoXshpvn8clXjPg==",
+ "license": "MIT",
+ "dependencies": {
+ "@alloc/quick-lru": "^5.2.0",
+ "@tailwindcss/node": "^4.0.0",
+ "@tailwindcss/oxide": "^4.0.0",
+ "lightningcss": "^1.29.1",
+ "postcss": "^8.4.41",
+ "tailwindcss": "4.0.0"
+ }
+ },
+ "node_modules/@tailwindcss/vite": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.0.0.tgz",
+ "integrity": "sha512-4uukMiU9gHui8KMPMdWic5SP1O/tmQ1NFSRNrQWmcop5evAVl/LZ6/LuWL3quEiecp2RBcRWwqJrG+mFXlRlew==",
+ "license": "MIT",
+ "dependencies": {
+ "@tailwindcss/node": "^4.0.0",
+ "@tailwindcss/oxide": "^4.0.0",
+ "lightningcss": "^1.29.1",
+ "tailwindcss": "4.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^5.2.0 || ^6"
+ }
+ },
+ "node_modules/@testing-library/dom": {
+ "version": "10.4.0",
+ "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz",
+ "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.10.4",
+ "@babel/runtime": "^7.12.5",
+ "@types/aria-query": "^5.0.1",
+ "aria-query": "5.3.0",
+ "chalk": "^4.1.0",
+ "dom-accessibility-api": "^0.5.9",
+ "lz-string": "^1.5.0",
+ "pretty-format": "^27.0.2"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@testing-library/jest-dom": {
+ "version": "6.6.3",
+ "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.6.3.tgz",
+ "integrity": "sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@adobe/css-tools": "^4.4.0",
+ "aria-query": "^5.0.0",
+ "chalk": "^3.0.0",
+ "css.escape": "^1.5.1",
+ "dom-accessibility-api": "^0.6.3",
+ "lodash": "^4.17.21",
+ "redent": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=14",
+ "npm": ">=6",
+ "yarn": ">=1"
+ }
+ },
+ "node_modules/@testing-library/jest-dom/node_modules/chalk": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
+ "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz",
+ "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@testing-library/react": {
+ "version": "16.2.0",
+ "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.2.0.tgz",
+ "integrity": "sha512-2cSskAvA1QNtKc8Y9VJQRv0tm3hLVgxRGDB+KYhIaPQJ1I+RHbhIXcM+zClKXzMes/wshsMVzf4B9vS4IZpqDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.12.5"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@testing-library/dom": "^10.0.0",
+ "@types/react": "^18.0.0 || ^19.0.0",
+ "@types/react-dom": "^18.0.0 || ^19.0.0",
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@tiptap/core": {
+ "version": "2.11.3",
+ "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.11.3.tgz",
+ "integrity": "sha512-ibfi6U1gMRLo319Re6olv8uAuxtUpK343ygcVoZrJ8O4sqRnU9CEqPAM+n7YAKlOks1+Di0sTheIxZRak7Pj4g==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/pm": "^2.7.0"
+ }
+ },
+ "node_modules/@tiptap/extension-blockquote": {
+ "version": "2.11.3",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-2.11.3.tgz",
+ "integrity": "sha512-UmKBmk7USY5Ufd7jcOI1W//nmRwRWdKLEYVLQ/L/nelpa7LXhDI/T3k4Oa4JlNQCEgUqI5Wz8TbVJYaFcyV/jA==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^2.7.0"
+ }
+ },
+ "node_modules/@tiptap/extension-bold": {
+ "version": "2.11.3",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-2.11.3.tgz",
+ "integrity": "sha512-YafJ+BavtzQBir81HoM62G0cik1ww7zNcElkg35sQWtVFnT99s+fLEuSurixtmiLdgmSSb/YsPrCmNp/R75zOw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^2.7.0"
+ }
+ },
+ "node_modules/@tiptap/extension-bubble-menu": {
+ "version": "2.11.3",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.11.3.tgz",
+ "integrity": "sha512-KOAy9zCzqssJO7cGIwZNgv2hFyxrZ2AHoWptICPA79nVZrHQQw2ZP1/FDTR8cDEZzLQMbpgGqQhUhjZcAs3/zQ==",
+ "license": "MIT",
+ "dependencies": {
+ "tippy.js": "^6.3.7"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^2.7.0",
+ "@tiptap/pm": "^2.7.0"
+ }
+ },
+ "node_modules/@tiptap/extension-bullet-list": {
+ "version": "2.11.3",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-2.11.3.tgz",
+ "integrity": "sha512-Q6ukkuD+Bt4UcJ5Pt0ZcF3ZzE6akC5l7gaXsTIZ4rqRS6Bmol13h5BshTNhhZhxFqJKwyt6MWHG60j7agtRoHQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^2.7.0"
+ }
+ },
+ "node_modules/@tiptap/extension-code": {
+ "version": "2.11.3",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-2.11.3.tgz",
+ "integrity": "sha512-w36Pb4DlB/cQZwsIpd5pSDwYuLBBSGh6dwGc9TVUdv+hdh8vIsnkGCjynapXgUrT2RFEJwObRYK+r5Gw84uGSA==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^2.7.0"
+ }
+ },
+ "node_modules/@tiptap/extension-code-block": {
+ "version": "2.11.3",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-2.11.3.tgz",
+ "integrity": "sha512-7VsufXUJt1Aq0UjQ2gQg6+boYsHdCi3+OBabbSMcf5TUWBmPlZnHAsDaocw2c/ZnOeu8Gmg6yrtBxbwjaiIO6g==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^2.7.0",
+ "@tiptap/pm": "^2.7.0"
+ }
+ },
+ "node_modules/@tiptap/extension-collaboration": {
+ "version": "2.11.3",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-collaboration/-/extension-collaboration-2.11.3.tgz",
+ "integrity": "sha512-2zZrd1pA3jDhMiqlFUSewPvwSnFjjaA7+sjulQO49HCZykyqdR1Fd5cxNKKBsjMczZfR7mop8I/NANeSZyRGmg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^2.7.0",
+ "@tiptap/pm": "^2.7.0",
+ "y-prosemirror": "^1.2.11"
+ }
+ },
+ "node_modules/@tiptap/extension-collaboration-cursor": {
+ "version": "2.11.3",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-collaboration-cursor/-/extension-collaboration-cursor-2.11.3.tgz",
+ "integrity": "sha512-Hw6ByVzE8KiuPqSC3BTFDFIidrtlyrJnJJ9Ff3PCMgloLz89H910yvWyAXnw8OhlzdZBRVdWML53kVS+402suw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^2.7.0",
+ "y-prosemirror": "^1.2.11"
+ }
+ },
+ "node_modules/@tiptap/extension-color": {
+ "version": "2.11.3",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-color/-/extension-color-2.11.3.tgz",
+ "integrity": "sha512-REUsyIq4OFsWMS7+pCKcTJ10IqBZLZYFOCD9b+yq/+NBKoEUdh2i7Uie/ZKRvE7skyMKOR2Vw6f5CNuRiotAhw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^2.7.0",
+ "@tiptap/extension-text-style": "^2.7.0"
+ }
+ },
+ "node_modules/@tiptap/extension-document": {
+ "version": "2.11.3",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-2.11.3.tgz",
+ "integrity": "sha512-utY1JZgxRLt0/oFPPUH8OT8Ltu3nmdycM2EwkM85vil83MnM5kuEYHF1l1q2xhnJ52wdU3afx+e7dFgvMDuunA==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^2.7.0"
+ }
+ },
+ "node_modules/@tiptap/extension-dropcursor": {
+ "version": "2.11.3",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-2.11.3.tgz",
+ "integrity": "sha512-Ppw46/1Vt9PlTT6TMloL1KjO2W89QUjRRptk5OtDvAGoOahLWwLji2k7dHyPeeCsG1J2KpHIPxngs922uhOEMw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^2.7.0",
+ "@tiptap/pm": "^2.7.0"
+ }
+ },
+ "node_modules/@tiptap/extension-floating-menu": {
+ "version": "2.11.3",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-2.11.3.tgz",
+ "integrity": "sha512-Za1x475cvv+URegCsoDr8rZI5GIoC4N6rHg/xqmozY4bA326Ko1cMrUbwpVF6p17nerDGAMCIstZM7SSUQdNSA==",
+ "license": "MIT",
+ "dependencies": {
+ "tippy.js": "^6.3.7"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^2.7.0",
+ "@tiptap/pm": "^2.7.0"
+ }
+ },
+ "node_modules/@tiptap/extension-gapcursor": {
+ "version": "2.11.3",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-2.11.3.tgz",
+ "integrity": "sha512-QNVoMNvsinnpvIBAADCbPXMAxY6nv38dxLY3mmPBF0j51H1ggGRX2MdD8VsSBM+AP5az9vTa1+rO+0wBfDwDWw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^2.7.0",
+ "@tiptap/pm": "^2.7.0"
+ }
+ },
+ "node_modules/@tiptap/extension-hard-break": {
+ "version": "2.11.3",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-2.11.3.tgz",
+ "integrity": "sha512-Jsz1qV/h4GFZiBtcrJ2yAF1Euw25IXgx5m4EBr/33TV6gT5+zRUr4e0y6h3jHicyInviZeXd9HXELCcQCEtHRg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^2.7.0"
+ }
+ },
+ "node_modules/@tiptap/extension-heading": {
+ "version": "2.11.3",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-2.11.3.tgz",
+ "integrity": "sha512-PuScgMuVxD/dUcizLCfQ1G4lI8ie2Wg5UCcixKefN2feFJneZdsIW6gUYYcjyH285VSjj+A76mwHzNdJGlGU2w==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^2.7.0"
+ }
+ },
+ "node_modules/@tiptap/extension-history": {
+ "version": "2.11.3",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-history/-/extension-history-2.11.3.tgz",
+ "integrity": "sha512-dxJeuGuLEn9V4iGfsvMOBcTwufcw971NoBdsyW1TOzYvucDkYHgIlOVE4DEWIVuOkfIjKEiCGl8IdZLaHWU8Sg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^2.7.0",
+ "@tiptap/pm": "^2.7.0"
+ }
+ },
+ "node_modules/@tiptap/extension-horizontal-rule": {
+ "version": "2.11.3",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.11.3.tgz",
+ "integrity": "sha512-+O6W6EbD4TLsUF8t0ApgZWLpcwn3tajRZtBU6u0SuwHtvhMTrQYySUTH5j06KfTDbw6JAqKKPCpKhPgH2Z6eFg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^2.7.0",
+ "@tiptap/pm": "^2.7.0"
+ }
+ },
+ "node_modules/@tiptap/extension-image": {
+ "version": "2.11.3",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-image/-/extension-image-2.11.3.tgz",
+ "integrity": "sha512-W6NqRu4wXynBdwSD0pjk2sAtLwu/LK6XAalHGyO4h4axuO5YRKDo8DhseQ/7llbYEd8GR8qjsuEdnVAFQWWGrg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^2.7.0"
+ }
+ },
+ "node_modules/@tiptap/extension-italic": {
+ "version": "2.11.3",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-2.11.3.tgz",
+ "integrity": "sha512-GpeQh2tMb6ys/ft7xqitoXeWO7uM8z4hvLEhD92ACuk6VpcHUhksUwnv4G4Qc/cZ9i+qk3GYEsRha0JaHp3GVw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^2.7.0"
+ }
+ },
+ "node_modules/@tiptap/extension-link": {
+ "version": "2.11.3",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-2.11.3.tgz",
+ "integrity": "sha512-FPLLBPqJQmqVMww7qx+Oznru2OaeoeZ6wTSfwtZZv1jNa1wwtK9O0wRJ3g71qvdFAt1veOesZNiMn3q7MOrw0A==",
+ "license": "MIT",
+ "dependencies": {
+ "linkifyjs": "^4.2.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^2.7.0",
+ "@tiptap/pm": "^2.7.0"
+ }
+ },
+ "node_modules/@tiptap/extension-list-item": {
+ "version": "2.11.3",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-2.11.3.tgz",
+ "integrity": "sha512-wUBuxoIN3XZQfesZqhgktJkJfcUaHUzUSzoCvQsDpaMsShpoFeptqs3DznHny9fRzrACkREds2dg6JV455+hLg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^2.7.0"
+ }
+ },
+ "node_modules/@tiptap/extension-ordered-list": {
+ "version": "2.11.3",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-2.11.3.tgz",
+ "integrity": "sha512-jRO3O6u0/el3l437pXFKsMv3YJuEfHUaEQHPUg5t1Dj8T+20X1LDg7tBKEbylBVLcgqB07aUbnFBqoQ4unwdsQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^2.7.0"
+ }
+ },
+ "node_modules/@tiptap/extension-paragraph": {
+ "version": "2.11.3",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-2.11.3.tgz",
+ "integrity": "sha512-snH9aIRJGpHCLm0zzuBwhXpRYMyZvyNBlF5MulJKxkwremFhD9fVP26UtQEneL/CnwpNs3q1QOQGTRlqFP2hbg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^2.7.0"
+ }
+ },
+ "node_modules/@tiptap/extension-placeholder": {
+ "version": "2.11.3",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-placeholder/-/extension-placeholder-2.11.3.tgz",
+ "integrity": "sha512-wXNcqsxkc+85NPrNpA/iuLa86RL2oOiOGWheJoIjtW2m9BEJSDsyHdUa9Nxwm28+PgonzG1uUfEv4JEzT5m4xg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^2.7.0",
+ "@tiptap/pm": "^2.7.0"
+ }
+ },
+ "node_modules/@tiptap/extension-strike": {
+ "version": "2.11.3",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-2.11.3.tgz",
+ "integrity": "sha512-Ei4rGEqytwXSj4Th1CN8EZFHnYmM5lAp8YUj1V3wWGX7EtRtnq1YG3+b7q68NKmdPSMFOjEz6pHtBzO+p+0aWw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^2.7.0"
+ }
+ },
+ "node_modules/@tiptap/extension-table": {
+ "version": "2.11.3",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-table/-/extension-table-2.11.3.tgz",
+ "integrity": "sha512-yxtEXhWB3804Uzr1YEpA4eynBD2syJr0UXfaJmPiz2dVxDF0FtB4K/1OWzTWF1CUBdbQmGKmAQYXDzHOyecfDw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^2.7.0",
+ "@tiptap/pm": "^2.7.0"
+ }
+ },
+ "node_modules/@tiptap/extension-table-cell": {
+ "version": "2.11.3",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-table-cell/-/extension-table-cell-2.11.3.tgz",
+ "integrity": "sha512-evdzllWjBey8bzSR7sUxTdrHKjN6M3XOv+1XfaoQpqMr8ryRWDb6yPSEIssAjc8bIa7yvCl7jaXMreeXE7oBSQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^2.7.0"
+ }
+ },
+ "node_modules/@tiptap/extension-table-header": {
+ "version": "2.11.3",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-table-header/-/extension-table-header-2.11.3.tgz",
+ "integrity": "sha512-Dcs7R0r5VDhuRvEfLhUKFvK9Yj2xPP8aTQ2UvLVCUJMlvo5Qv/xaJWDbXxH/AQ3wL4LvZomHTOWvdNad7WEkgg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^2.7.0"
+ }
+ },
+ "node_modules/@tiptap/extension-table-row": {
+ "version": "2.11.3",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-table-row/-/extension-table-row-2.11.3.tgz",
+ "integrity": "sha512-ExCsW/7SFJ9geXhEBKb3eyLCvUOvfLC6pn69aua/AvXPJE4cv//5D42e6o5Iyu/xwLLPH1oAINa0HYmsFDp75w==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^2.7.0"
+ }
+ },
+ "node_modules/@tiptap/extension-text": {
+ "version": "2.11.3",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-2.11.3.tgz",
+ "integrity": "sha512-DhrwR9tmDU2U4yjqdaX6odrnOYaE/Ai2ERs2bU4Sgm0ZF5QCyO31Cflg1OQ4erTi0IiqD5ilDPRXqFuu6FGzOQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^2.7.0"
+ }
+ },
+ "node_modules/@tiptap/extension-text-style": {
+ "version": "2.11.3",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-text-style/-/extension-text-style-2.11.3.tgz",
+ "integrity": "sha512-kF4pxThvsN7KAb4Ry+ifMGm/To97PeCtosOyjSIvlAnXdc+XvTf6+dkyCsq6smnOhqLw2NErn1gQ4eFbji8YQQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^2.7.0"
+ }
+ },
+ "node_modules/@tiptap/extension-underline": {
+ "version": "2.11.3",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-2.11.3.tgz",
+ "integrity": "sha512-OPVG+D6byOn7mLTOwYVCIQL6j32SlYgic+Ku3km4qadynfqhHcALpmhMkWXcrlA7S9R7aIP2s5XW73dV//XCyQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^2.7.0"
+ }
+ },
+ "node_modules/@tiptap/pm": {
+ "version": "2.11.3",
+ "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.11.3.tgz",
+ "integrity": "sha512-AEpiWvYmXdELpuGGhX6lS2aU155ANwS7WbQ/+/SFqH3YIYHjgUzP8UnY6KSiEBI7a7kX4TWhG84mWrzPA3dPaw==",
+ "license": "MIT",
+ "dependencies": {
+ "prosemirror-changeset": "^2.2.1",
+ "prosemirror-collab": "^1.3.1",
+ "prosemirror-commands": "^1.6.2",
+ "prosemirror-dropcursor": "^1.8.1",
+ "prosemirror-gapcursor": "^1.3.2",
+ "prosemirror-history": "^1.4.1",
+ "prosemirror-inputrules": "^1.4.0",
+ "prosemirror-keymap": "^1.2.2",
+ "prosemirror-markdown": "^1.13.1",
+ "prosemirror-menu": "^1.2.4",
+ "prosemirror-model": "^1.23.0",
+ "prosemirror-schema-basic": "^1.2.3",
+ "prosemirror-schema-list": "^1.4.1",
+ "prosemirror-state": "^1.4.3",
+ "prosemirror-tables": "^1.6.1",
+ "prosemirror-trailing-node": "^3.0.0",
+ "prosemirror-transform": "^1.10.2",
+ "prosemirror-view": "^1.37.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ }
+ },
+ "node_modules/@tiptap/react": {
+ "version": "2.11.3",
+ "resolved": "https://registry.npmjs.org/@tiptap/react/-/react-2.11.3.tgz",
+ "integrity": "sha512-tNY/xJ7swV1Ffc6W5CSEWJnBo3grDapkZnd2udSTJ7/zYMUe+vSN2bcdKga2Zo2rAC0WFuLUl27iUhrZ29FFuQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@tiptap/extension-bubble-menu": "^2.11.3",
+ "@tiptap/extension-floating-menu": "^2.11.3",
+ "@types/use-sync-external-store": "^0.0.6",
+ "fast-deep-equal": "^3",
+ "use-sync-external-store": "^1"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^2.7.0",
+ "@tiptap/pm": "^2.7.0",
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/@tiptap/starter-kit": {
+ "version": "2.11.3",
+ "resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-2.11.3.tgz",
+ "integrity": "sha512-UGKS6+TA/7yMGqHBK5S/Kxis6iy3Tw0gvVg1EkYHUmkApLJypE87wUMkIeLeD9dd5+2WkxWcYMhC9R3ByjulBg==",
+ "license": "MIT",
+ "dependencies": {
+ "@tiptap/core": "^2.11.3",
+ "@tiptap/extension-blockquote": "^2.11.3",
+ "@tiptap/extension-bold": "^2.11.3",
+ "@tiptap/extension-bullet-list": "^2.11.3",
+ "@tiptap/extension-code": "^2.11.3",
+ "@tiptap/extension-code-block": "^2.11.3",
+ "@tiptap/extension-document": "^2.11.3",
+ "@tiptap/extension-dropcursor": "^2.11.3",
+ "@tiptap/extension-gapcursor": "^2.11.3",
+ "@tiptap/extension-hard-break": "^2.11.3",
+ "@tiptap/extension-heading": "^2.11.3",
+ "@tiptap/extension-history": "^2.11.3",
+ "@tiptap/extension-horizontal-rule": "^2.11.3",
+ "@tiptap/extension-italic": "^2.11.3",
+ "@tiptap/extension-list-item": "^2.11.3",
+ "@tiptap/extension-ordered-list": "^2.11.3",
+ "@tiptap/extension-paragraph": "^2.11.3",
+ "@tiptap/extension-strike": "^2.11.3",
+ "@tiptap/extension-text": "^2.11.3",
+ "@tiptap/extension-text-style": "^2.11.3",
+ "@tiptap/pm": "^2.11.3"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ }
+ },
+ "node_modules/@types/aria-query": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
+ "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.6.8",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz",
+ "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.20.6",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz",
+ "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.20.7"
+ }
+ },
+ "node_modules/@types/cookie": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
+ "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==",
+ "license": "MIT"
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
+ "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
+ "license": "MIT"
+ },
+ "node_modules/@types/json-schema": {
+ "version": "7.0.15",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/linkify-it": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz",
+ "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==",
+ "license": "MIT"
+ },
+ "node_modules/@types/markdown-it": {
+ "version": "14.1.2",
+ "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz",
+ "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/linkify-it": "^5",
+ "@types/mdurl": "^2"
+ }
+ },
+ "node_modules/@types/mdurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz",
+ "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==",
+ "license": "MIT"
+ },
+ "node_modules/@types/node": {
+ "version": "22.10.10",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.10.tgz",
+ "integrity": "sha512-X47y/mPNzxviAGY5TcYPtYL8JsY3kAq2n8fMmKoRCxq/c4v4pyGNCzM2R6+M5/umG4ZfHuT+sgqDYqWc9rJ6ww==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.20.0"
+ }
+ },
+ "node_modules/@types/raf": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz",
+ "integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==",
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/@types/react": {
+ "version": "19.0.8",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.8.tgz",
+ "integrity": "sha512-9P/o1IGdfmQxrujGbIMDyYaaCykhLKc0NGCtYcECNUr9UAaDe4gwvV9bR6tvd5Br1SG0j+PBpbKr2UYY8CwqSw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "19.0.3",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.0.3.tgz",
+ "integrity": "sha512-0Knk+HJiMP/qOZgMyNFamlIjw9OFCsyC2ZbigmEEyXXixgre6IQpm/4V+r3qH4GC1JPvRJKInw+on2rV6YZLeA==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^19.0.0"
+ }
+ },
+ "node_modules/@types/use-sync-external-store": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
+ "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==",
+ "license": "MIT"
+ },
+ "node_modules/@typescript-eslint/eslint-plugin": {
+ "version": "8.21.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.21.0.tgz",
+ "integrity": "sha512-eTH+UOR4I7WbdQnG4Z48ebIA6Bgi7WO8HvFEneeYBxG8qCOYgTOFPSg6ek9ITIDvGjDQzWHcoWHCDO2biByNzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/regexpp": "^4.10.0",
+ "@typescript-eslint/scope-manager": "8.21.0",
+ "@typescript-eslint/type-utils": "8.21.0",
+ "@typescript-eslint/utils": "8.21.0",
+ "@typescript-eslint/visitor-keys": "8.21.0",
+ "graphemer": "^1.4.0",
+ "ignore": "^5.3.1",
+ "natural-compare": "^1.4.0",
+ "ts-api-utils": "^2.0.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0",
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <5.8.0"
+ }
+ },
+ "node_modules/@typescript-eslint/parser": {
+ "version": "8.21.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.21.0.tgz",
+ "integrity": "sha512-Wy+/sdEH9kI3w9civgACwabHbKl+qIOu0uFZ9IMKzX3Jpv9og0ZBJrZExGrPpFAY7rWsXuxs5e7CPPP17A4eYA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/scope-manager": "8.21.0",
+ "@typescript-eslint/types": "8.21.0",
+ "@typescript-eslint/typescript-estree": "8.21.0",
+ "@typescript-eslint/visitor-keys": "8.21.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <5.8.0"
+ }
+ },
+ "node_modules/@typescript-eslint/scope-manager": {
+ "version": "8.21.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.21.0.tgz",
+ "integrity": "sha512-G3IBKz0/0IPfdeGRMbp+4rbjfSSdnGkXsM/pFZA8zM9t9klXDnB/YnKOBQ0GoPmoROa4bCq2NeHgJa5ydsQ4mA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.21.0",
+ "@typescript-eslint/visitor-keys": "8.21.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/type-utils": {
+ "version": "8.21.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.21.0.tgz",
+ "integrity": "sha512-95OsL6J2BtzoBxHicoXHxgk3z+9P3BEcQTpBKriqiYzLKnM2DeSqs+sndMKdamU8FosiadQFT3D+BSL9EKnAJQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/typescript-estree": "8.21.0",
+ "@typescript-eslint/utils": "8.21.0",
+ "debug": "^4.3.4",
+ "ts-api-utils": "^2.0.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <5.8.0"
+ }
+ },
+ "node_modules/@typescript-eslint/types": {
+ "version": "8.21.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.21.0.tgz",
+ "integrity": "sha512-PAL6LUuQwotLW2a8VsySDBwYMm129vFm4tMVlylzdoTybTHaAi0oBp7Ac6LhSrHHOdLM3efH+nAR6hAWoMF89A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree": {
+ "version": "8.21.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.21.0.tgz",
+ "integrity": "sha512-x+aeKh/AjAArSauz0GiQZsjT8ciadNMHdkUSwBB9Z6PrKc/4knM4g3UfHml6oDJmKC88a6//cdxnO/+P2LkMcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.21.0",
+ "@typescript-eslint/visitor-keys": "8.21.0",
+ "debug": "^4.3.4",
+ "fast-glob": "^3.3.2",
+ "is-glob": "^4.0.3",
+ "minimatch": "^9.0.4",
+ "semver": "^7.6.0",
+ "ts-api-utils": "^2.0.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <5.8.0"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
+ "version": "7.6.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
+ "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@typescript-eslint/utils": {
+ "version": "8.21.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.21.0.tgz",
+ "integrity": "sha512-xcXBfcq0Kaxgj7dwejMbFyq7IOHgpNMtVuDveK7w3ZGwG9owKzhALVwKpTF2yrZmEwl9SWdetf3fxNzJQaVuxw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.4.0",
+ "@typescript-eslint/scope-manager": "8.21.0",
+ "@typescript-eslint/types": "8.21.0",
+ "@typescript-eslint/typescript-estree": "8.21.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <5.8.0"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys": {
+ "version": "8.21.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.21.0.tgz",
+ "integrity": "sha512-BkLMNpdV6prozk8LlyK/SOoWLmUFi+ZD+pcqti9ILCbVvHGk1ui1g4jJOc2WDLaeExz2qWwojxlPce5PljcT3w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.21.0",
+ "eslint-visitor-keys": "^4.2.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.4.tgz",
+ "integrity": "sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.26.0",
+ "@babel/plugin-transform-react-jsx-self": "^7.25.9",
+ "@babel/plugin-transform-react-jsx-source": "^7.25.9",
+ "@types/babel__core": "^7.20.5",
+ "react-refresh": "^0.14.2"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0"
+ }
+ },
+ "node_modules/@vitest/expect": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.0.4.tgz",
+ "integrity": "sha512-Nm5kJmYw6P2BxhJPkO3eKKhGYKRsnqJqf+r0yOGRKpEP+bSCBDsjXgiu1/5QFrnPMEgzfC38ZEjvCFgaNBC0Eg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/spy": "3.0.4",
+ "@vitest/utils": "3.0.4",
+ "chai": "^5.1.2",
+ "tinyrainbow": "^2.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/mocker": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.0.4.tgz",
+ "integrity": "sha512-gEef35vKafJlfQbnyOXZ0Gcr9IBUsMTyTLXsEQwuyYAerpHqvXhzdBnDFuHLpFqth3F7b6BaFr4qV/Cs1ULx5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/spy": "3.0.4",
+ "estree-walker": "^3.0.3",
+ "magic-string": "^0.30.17"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "msw": "^2.4.9",
+ "vite": "^5.0.0 || ^6.0.0"
+ },
+ "peerDependenciesMeta": {
+ "msw": {
+ "optional": true
+ },
+ "vite": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@vitest/pretty-format": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.0.4.tgz",
+ "integrity": "sha512-ts0fba+dEhK2aC9PFuZ9LTpULHpY/nd6jhAQ5IMU7Gaj7crPCTdCFfgvXxruRBLFS+MLraicCuFXxISEq8C93g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyrainbow": "^2.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/runner": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.0.4.tgz",
+ "integrity": "sha512-dKHzTQ7n9sExAcWH/0sh1elVgwc7OJ2lMOBrAm73J7AH6Pf9T12Zh3lNE1TETZaqrWFXtLlx3NVrLRb5hCK+iw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/utils": "3.0.4",
+ "pathe": "^2.0.2"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/snapshot": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.0.4.tgz",
+ "integrity": "sha512-+p5knMLwIk7lTQkM3NonZ9zBewzVp9EVkVpvNta0/PlFWpiqLaRcF4+33L1it3uRUCh0BGLOaXPPGEjNKfWb4w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "3.0.4",
+ "magic-string": "^0.30.17",
+ "pathe": "^2.0.2"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/spy": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.0.4.tgz",
+ "integrity": "sha512-sXIMF0oauYyUy2hN49VFTYodzEAu744MmGcPR3ZBsPM20G+1/cSW/n1U+3Yu/zHxX2bIDe1oJASOkml+osTU6Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyspy": "^3.0.2"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/utils": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.0.4.tgz",
+ "integrity": "sha512-8BqC1ksYsHtbWH+DfpOAKrFw3jl3Uf9J7yeFh85Pz52IWuh1hBBtyfEbRNNZNjl8H8A5yMLH9/t+k7HIKzQcZQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "3.0.4",
+ "loupe": "^3.1.2",
+ "tinyrainbow": "^2.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.14.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
+ "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/agent-base": {
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz",
+ "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "license": "Python-2.0"
+ },
+ "node_modules/aria-query": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
+ "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "dequal": "^2.0.3"
+ }
+ },
+ "node_modules/assertion-error": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
+ "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/atob": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
+ "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
+ "license": "(MIT OR Apache-2.0)",
+ "bin": {
+ "atob": "bin/atob.js"
+ },
+ "engines": {
+ "node": ">= 4.5.0"
+ }
+ },
+ "node_modules/autoprefixer": {
+ "version": "10.4.20",
+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz",
+ "integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/autoprefixer"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "browserslist": "^4.23.3",
+ "caniuse-lite": "^1.0.30001646",
+ "fraction.js": "^4.3.7",
+ "normalize-range": "^0.1.2",
+ "picocolors": "^1.0.1",
+ "postcss-value-parser": "^4.2.0"
+ },
+ "bin": {
+ "autoprefixer": "bin/autoprefixer"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ },
+ "peerDependencies": {
+ "postcss": "^8.1.0"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/base64-arraybuffer": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
+ "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">= 0.6.0"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.24.2",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz",
+ "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "caniuse-lite": "^1.0.30001669",
+ "electron-to-chromium": "^1.5.41",
+ "node-releases": "^2.0.18",
+ "update-browserslist-db": "^1.1.1"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/btoa": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz",
+ "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==",
+ "license": "(MIT OR Apache-2.0)",
+ "bin": {
+ "btoa": "bin/btoa.js"
+ },
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/buffer-builder": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/buffer-builder/-/buffer-builder-0.2.0.tgz",
+ "integrity": "sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg==",
+ "license": "MIT/X11",
+ "optional": true,
+ "peer": true
+ },
+ "node_modules/cac": {
+ "version": "6.7.14",
+ "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
+ "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001684",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001684.tgz",
+ "integrity": "sha512-G1LRwLIQjBQoyq0ZJGqGIJUXzJ8irpbjHLpVRXDvBEScFJ9b17sgK6vlx0GAJFE21okD7zXl08rRRUfq6HdoEQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/canvg": {
+ "version": "3.0.10",
+ "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.10.tgz",
+ "integrity": "sha512-qwR2FRNO9NlzTeKIPIKpnTY6fqwuYSequ8Ru8c0YkYU7U0oW+hLUvWadLvAu1Rl72OMNiFhoLu4f8eUjQ7l/+Q==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@babel/runtime": "^7.12.5",
+ "@types/raf": "^3.4.0",
+ "core-js": "^3.8.3",
+ "raf": "^3.4.1",
+ "regenerator-runtime": "^0.13.7",
+ "rgbcolor": "^1.0.1",
+ "stackblur-canvas": "^2.0.0",
+ "svg-pathdata": "^6.0.3"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/canvg/node_modules/regenerator-runtime": {
+ "version": "0.13.11",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
+ "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/chai": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.2.tgz",
+ "integrity": "sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "assertion-error": "^2.0.1",
+ "check-error": "^2.1.1",
+ "deep-eql": "^5.0.1",
+ "loupe": "^3.1.0",
+ "pathval": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/check-error": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz",
+ "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 16"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/colorjs.io": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/colorjs.io/-/colorjs.io-0.5.2.tgz",
+ "integrity": "sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==",
+ "license": "MIT",
+ "optional": true,
+ "peer": true
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cookie": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
+ "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/core-js": {
+ "version": "3.40.0",
+ "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.40.0.tgz",
+ "integrity": "sha512-7vsMc/Lty6AGnn7uFpYT56QesI5D2Y/UkgKounk87OP9Z2H9Z8kj6jzcSGAxFmUtDOS0ntK6lbQz+Nsa0Jj6mQ==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/core-js"
+ }
+ },
+ "node_modules/crelt": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
+ "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==",
+ "license": "MIT"
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/css-line-break": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
+ "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "utrie": "^1.0.2"
+ }
+ },
+ "node_modules/css.escape": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
+ "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cssstyle": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.2.1.tgz",
+ "integrity": "sha512-9+vem03dMXG7gDmZ62uqmRiMRNtinIZ9ZyuF6BdxzfOD+FdN5hretzynkn0ReS2DO2GSw76RWHs0UmJPI2zUjw==",
+ "dev": true,
+ "dependencies": {
+ "@asamuzakjp/css-color": "^2.8.2",
+ "rrweb-cssom": "^0.8.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/data-urls": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz",
+ "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "whatwg-mimetype": "^4.0.0",
+ "whatwg-url": "^14.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
+ "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/decimal.js": {
+ "version": "10.4.3",
+ "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz",
+ "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/deep-eql": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz",
+ "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/dequal": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
+ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/detect-libc": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
+ "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
+ "license": "Apache-2.0",
+ "bin": {
+ "detect-libc": "bin/detect-libc.js"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/dom-accessibility-api": {
+ "version": "0.5.16",
+ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
+ "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/dompurify": {
+ "version": "2.5.8",
+ "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.5.8.tgz",
+ "integrity": "sha512-o1vSNgrmYMQObbSSvF/1brBYEQPHhV1+gsmrusO7/GXtp1T9rCS8cXFqVxK/9crT1jA6Ccv+5MTSjBNqr7Sovw==",
+ "license": "(MPL-2.0 OR Apache-2.0)",
+ "optional": true
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.66",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.66.tgz",
+ "integrity": "sha512-pI2QF6+i+zjPbqRzJwkMvtvkdI7MjVbSh2g8dlMguDJIXEPw+kwasS1Jl+YGPEBfGVxsVgGUratAKymPdPo2vQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/enhanced-resolve": {
+ "version": "5.18.0",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.0.tgz",
+ "integrity": "sha512-0/r0MySGYG8YqlayBZ6MuCfECmHFdJ5qyPh8s8wa5Hnm6SaFLSK1VYCbj+NKp090Nm1caZhD+QTnmxO7esYGyQ==",
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.4",
+ "tapable": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/entities": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/es-module-lexer": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz",
+ "integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/esbuild": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz",
+ "integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==",
+ "hasInstallScript": true,
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.24.2",
+ "@esbuild/android-arm": "0.24.2",
+ "@esbuild/android-arm64": "0.24.2",
+ "@esbuild/android-x64": "0.24.2",
+ "@esbuild/darwin-arm64": "0.24.2",
+ "@esbuild/darwin-x64": "0.24.2",
+ "@esbuild/freebsd-arm64": "0.24.2",
+ "@esbuild/freebsd-x64": "0.24.2",
+ "@esbuild/linux-arm": "0.24.2",
+ "@esbuild/linux-arm64": "0.24.2",
+ "@esbuild/linux-ia32": "0.24.2",
+ "@esbuild/linux-loong64": "0.24.2",
+ "@esbuild/linux-mips64el": "0.24.2",
+ "@esbuild/linux-ppc64": "0.24.2",
+ "@esbuild/linux-riscv64": "0.24.2",
+ "@esbuild/linux-s390x": "0.24.2",
+ "@esbuild/linux-x64": "0.24.2",
+ "@esbuild/netbsd-arm64": "0.24.2",
+ "@esbuild/netbsd-x64": "0.24.2",
+ "@esbuild/openbsd-arm64": "0.24.2",
+ "@esbuild/openbsd-x64": "0.24.2",
+ "@esbuild/sunos-x64": "0.24.2",
+ "@esbuild/win32-arm64": "0.24.2",
+ "@esbuild/win32-ia32": "0.24.2",
+ "@esbuild/win32-x64": "0.24.2"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/aix-ppc64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz",
+ "integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/android-arm": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.2.tgz",
+ "integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==",
+ "cpu": [
+ "arm"
+ ],
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/android-arm64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz",
+ "integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/android-x64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.2.tgz",
+ "integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/darwin-arm64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz",
+ "integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/darwin-x64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz",
+ "integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz",
+ "integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/freebsd-x64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz",
+ "integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/linux-arm": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz",
+ "integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==",
+ "cpu": [
+ "arm"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/linux-ia32": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz",
+ "integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==",
+ "cpu": [
+ "ia32"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/linux-loong64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz",
+ "integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/linux-mips64el": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz",
+ "integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/linux-ppc64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz",
+ "integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/linux-riscv64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz",
+ "integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==",
+ "cpu": [
+ "riscv64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/linux-s390x": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz",
+ "integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==",
+ "cpu": [
+ "s390x"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/linux-x64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz",
+ "integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/netbsd-x64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz",
+ "integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/openbsd-x64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz",
+ "integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/sunos-x64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz",
+ "integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/win32-arm64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz",
+ "integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/win32-ia32": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz",
+ "integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==",
+ "cpu": [
+ "ia32"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/win32-x64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz",
+ "integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "9.18.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.18.0.tgz",
+ "integrity": "sha512-+waTfRWQlSbpt3KWE+CjrPPYnbq9kfZIYUqapc0uBXyjTp8aYXZDsUH16m39Ryq3NjAVP4tjuF7KaukeqoCoaA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.2.0",
+ "@eslint-community/regexpp": "^4.12.1",
+ "@eslint/config-array": "^0.19.0",
+ "@eslint/core": "^0.10.0",
+ "@eslint/eslintrc": "^3.2.0",
+ "@eslint/js": "9.18.0",
+ "@eslint/plugin-kit": "^0.2.5",
+ "@humanfs/node": "^0.16.6",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@humanwhocodes/retry": "^0.4.1",
+ "@types/estree": "^1.0.6",
+ "@types/json-schema": "^7.0.15",
+ "ajv": "^6.12.4",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.6",
+ "debug": "^4.3.2",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^8.2.0",
+ "eslint-visitor-keys": "^4.2.0",
+ "espree": "^10.3.0",
+ "esquery": "^1.5.0",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^8.0.0",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ },
+ "peerDependencies": {
+ "jiti": "*"
+ },
+ "peerDependenciesMeta": {
+ "jiti": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-plugin-react-hooks": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.1.0.tgz",
+ "integrity": "sha512-mpJRtPgHN2tNAvZ35AMfqeB3Xqeo273QxrHJsbBEPWODRM4r0yB6jfoROqKEYrOn27UtRPpcpHc2UqyBSuUNTw==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0"
+ }
+ },
+ "node_modules/eslint-plugin-react-refresh": {
+ "version": "0.4.18",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.18.tgz",
+ "integrity": "sha512-IRGEoFn3OKalm3hjfolEWGqoF/jPqeEYFp+C8B0WMzwGwBMvlRDQd06kghDhF0C61uJ6WfSDhEZE/sAQjduKgw==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "eslint": ">=8.40"
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "8.2.0",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz",
+ "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
+ "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/espree": {
+ "version": "10.3.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz",
+ "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "acorn": "^8.14.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^4.2.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
+ "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estree-walker": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+ "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/expect-type": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.1.0.tgz",
+ "integrity": "sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "license": "MIT"
+ },
+ "node_modules/fast-glob": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
+ "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.8"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fast-glob/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fastq": {
+ "version": "1.18.0",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.18.0.tgz",
+ "integrity": "sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/fflate": {
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
+ "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
+ "license": "MIT"
+ },
+ "node_modules/file-entry-cache": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
+ "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flat-cache": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
+ "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.4"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz",
+ "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/form-data": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz",
+ "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fraction.js": {
+ "version": "4.3.7",
+ "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
+ "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "type": "patreon",
+ "url": "https://github.com/sponsors/rawify"
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/globals": {
+ "version": "15.14.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-15.14.0.tgz",
+ "integrity": "sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==",
+ "dev": true,
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "license": "ISC"
+ },
+ "node_modules/graphemer": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
+ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/html-encoding-sniffer": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz",
+ "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "whatwg-encoding": "^3.1.1"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/html-parse-stringify": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
+ "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
+ "license": "MIT",
+ "dependencies": {
+ "void-elements": "3.1.0"
+ }
+ },
+ "node_modules/html2canvas": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
+ "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "css-line-break": "^2.1.0",
+ "text-segmentation": "^1.0.3"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/http-proxy-agent": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
+ "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/https-proxy-agent": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
+ "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.2",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/i18next": {
+ "version": "24.2.1",
+ "resolved": "https://registry.npmjs.org/i18next/-/i18next-24.2.1.tgz",
+ "integrity": "sha512-Q2wC1TjWcSikn1VAJg13UGIjc+okpFxQTxjVAymOnSA3RpttBQNMPf2ovcgoFVsV4QNxTfNZMAxorXZXsk4fBA==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://locize.com"
+ },
+ {
+ "type": "individual",
+ "url": "https://locize.com/i18next.html"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.23.2"
+ },
+ "peerDependencies": {
+ "typescript": "^5"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/i18next-browser-languagedetector": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.0.2.tgz",
+ "integrity": "sha512-shBvPmnIyZeD2VU5jVGIOWP7u9qNG3Lj7mpaiPFpbJ3LVfHZJvVzKR4v1Cb91wAOFpNw442N+LGPzHOHsten2g==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.23.2"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/immutable": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.0.3.tgz",
+ "integrity": "sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw==",
+ "license": "MIT",
+ "optional": true,
+ "peer": true
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
+ "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/indent-string": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
+ "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-potential-custom-element-name": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
+ "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/isomorphic.js": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.2.5.tgz",
+ "integrity": "sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==",
+ "license": "MIT",
+ "funding": {
+ "type": "GitHub Sponsors ❤",
+ "url": "https://github.com/sponsors/dmonad"
+ }
+ },
+ "node_modules/jiti": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz",
+ "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==",
+ "license": "MIT",
+ "bin": {
+ "jiti": "lib/jiti-cli.mjs"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/jsdom": {
+ "version": "26.0.0",
+ "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.0.0.tgz",
+ "integrity": "sha512-BZYDGVAIriBWTpIxYzrXjv3E/4u8+/pSG5bQdIYCbNCGOvsPkDQfTVLAIXAf9ETdCpduCVTkDe2NNZ8NIwUVzw==",
+ "dev": true,
+ "dependencies": {
+ "cssstyle": "^4.2.1",
+ "data-urls": "^5.0.0",
+ "decimal.js": "^10.4.3",
+ "form-data": "^4.0.1",
+ "html-encoding-sniffer": "^4.0.0",
+ "http-proxy-agent": "^7.0.2",
+ "https-proxy-agent": "^7.0.6",
+ "is-potential-custom-element-name": "^1.0.1",
+ "nwsapi": "^2.2.16",
+ "parse5": "^7.2.1",
+ "rrweb-cssom": "^0.8.0",
+ "saxes": "^6.0.0",
+ "symbol-tree": "^3.2.4",
+ "tough-cookie": "^5.0.0",
+ "w3c-xmlserializer": "^5.0.0",
+ "webidl-conversions": "^7.0.0",
+ "whatwg-encoding": "^3.1.1",
+ "whatwg-mimetype": "^4.0.0",
+ "whatwg-url": "^14.1.0",
+ "ws": "^8.18.0",
+ "xml-name-validator": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "canvas": "^3.0.0"
+ },
+ "peerDependenciesMeta": {
+ "canvas": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jsesc": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz",
+ "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/jspdf": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-2.5.2.tgz",
+ "integrity": "sha512-myeX9c+p7znDWPk0eTrujCzNjT+CXdXyk7YmJq5nD5V7uLLKmSXnlQ/Jn/kuo3X09Op70Apm0rQSnFWyGK8uEQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.23.2",
+ "atob": "^2.1.2",
+ "btoa": "^1.2.1",
+ "fflate": "^0.8.1"
+ },
+ "optionalDependencies": {
+ "canvg": "^3.0.6",
+ "core-js": "^3.6.0",
+ "dompurify": "^2.5.4",
+ "html2canvas": "^1.0.0-rc.5"
+ }
+ },
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/lib0": {
+ "version": "0.2.99",
+ "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.99.tgz",
+ "integrity": "sha512-vwztYuUf1uf/1zQxfzRfO5yzfNKhTtgOByCruuiQQxWQXnPb8Itaube5ylofcV0oM0aKal9Mv+S1s1Ky0UYP1w==",
+ "license": "MIT",
+ "dependencies": {
+ "isomorphic.js": "^0.2.4"
+ },
+ "bin": {
+ "0ecdsa-generate-keypair": "bin/0ecdsa-generate-keypair.js",
+ "0gentesthtml": "bin/gentesthtml.js",
+ "0serve": "bin/0serve.js"
+ },
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "type": "GitHub Sponsors ❤",
+ "url": "https://github.com/sponsors/dmonad"
+ }
+ },
+ "node_modules/lightningcss": {
+ "version": "1.29.1",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.29.1.tgz",
+ "integrity": "sha512-FmGoeD4S05ewj+AkhTY+D+myDvXI6eL27FjHIjoyUkO/uw7WZD1fBVs0QxeYWa7E17CUHJaYX/RUGISCtcrG4Q==",
+ "license": "MPL-2.0",
+ "dependencies": {
+ "detect-libc": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-darwin-arm64": "1.29.1",
+ "lightningcss-darwin-x64": "1.29.1",
+ "lightningcss-freebsd-x64": "1.29.1",
+ "lightningcss-linux-arm-gnueabihf": "1.29.1",
+ "lightningcss-linux-arm64-gnu": "1.29.1",
+ "lightningcss-linux-arm64-musl": "1.29.1",
+ "lightningcss-linux-x64-gnu": "1.29.1",
+ "lightningcss-linux-x64-musl": "1.29.1",
+ "lightningcss-win32-arm64-msvc": "1.29.1",
+ "lightningcss-win32-x64-msvc": "1.29.1"
+ }
+ },
+ "node_modules/lightningcss-darwin-arm64": {
+ "version": "1.29.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.29.1.tgz",
+ "integrity": "sha512-HtR5XJ5A0lvCqYAoSv2QdZZyoHNttBpa5EP9aNuzBQeKGfbyH5+UipLWvVzpP4Uml5ej4BYs5I9Lco9u1fECqw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-x64": {
+ "version": "1.29.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.29.1.tgz",
+ "integrity": "sha512-k33G9IzKUpHy/J/3+9MCO4e+PzaFblsgBjSGlpAaFikeBFm8B/CkO3cKU9oI4g+fjS2KlkLM/Bza9K/aw8wsNA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-freebsd-x64": {
+ "version": "1.29.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.29.1.tgz",
+ "integrity": "sha512-0SUW22fv/8kln2LnIdOCmSuXnxgxVC276W5KLTwoehiO0hxkacBxjHOL5EtHD8BAXg2BvuhsJPmVMasvby3LiQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.29.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.29.1.tgz",
+ "integrity": "sha512-sD32pFvlR0kDlqsOZmYqH/68SqUMPNj+0pucGxToXZi4XZgZmqeX/NkxNKCPsswAXU3UeYgDSpGhu05eAufjDg==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.29.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.29.1.tgz",
+ "integrity": "sha512-0+vClRIZ6mmJl/dxGuRsE197o1HDEeeRk6nzycSy2GofC2JsY4ifCRnvUWf/CUBQmlrvMzt6SMQNMSEu22csWQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.29.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.29.1.tgz",
+ "integrity": "sha512-UKMFrG4rL/uHNgelBsDwJcBqVpzNJbzsKkbI3Ja5fg00sgQnHw/VrzUTEc4jhZ+AN2BvQYz/tkHu4vt1kLuJyw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.29.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.29.1.tgz",
+ "integrity": "sha512-u1S+xdODy/eEtjADqirA774y3jLcm8RPtYztwReEXoZKdzgsHYPl0s5V52Tst+GKzqjebkULT86XMSxejzfISw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.29.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.29.1.tgz",
+ "integrity": "sha512-L0Tx0DtaNUTzXv0lbGCLB/c/qEADanHbu4QdcNOXLIe1i8i22rZRpbT3gpWYsCh9aSL9zFujY/WmEXIatWvXbw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.29.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.29.1.tgz",
+ "integrity": "sha512-QoOVnkIEFfbW4xPi+dpdft/zAKmgLgsRHfJalEPYuJDOWf7cLQzYg0DEh8/sn737FaeMJxHZRc1oBreiwZCjog==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.29.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.29.1.tgz",
+ "integrity": "sha512-NygcbThNBe4JElP+olyTI/doBNGJvLs3bFCRPdvuCcxZCcCZ71B858IHpdm7L1btZex0FvCmM17FK98Y9MRy1Q==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/linkify-it": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
+ "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
+ "license": "MIT",
+ "dependencies": {
+ "uc.micro": "^2.0.0"
+ }
+ },
+ "node_modules/linkifyjs": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.2.0.tgz",
+ "integrity": "sha512-pCj3PrQyATaoTYKHrgWRF3SJwsm61udVh+vuls/Rl6SptiDhgE7ziUIudAedRY9QEfynmM7/RmLEfPUyw1HPCw==",
+ "license": "MIT"
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/loupe": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.2.tgz",
+ "integrity": "sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/lz-string": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
+ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "bin": {
+ "lz-string": "bin/bin.js"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.17",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
+ "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0"
+ }
+ },
+ "node_modules/markdown-it": {
+ "version": "14.1.0",
+ "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz",
+ "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==",
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1",
+ "entities": "^4.4.0",
+ "linkify-it": "^5.0.0",
+ "mdurl": "^2.0.0",
+ "punycode.js": "^2.3.1",
+ "uc.micro": "^2.1.0"
+ },
+ "bin": {
+ "markdown-it": "bin/markdown-it.mjs"
+ }
+ },
+ "node_modules/mdurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
+ "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
+ "license": "MIT"
+ },
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/min-indent": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
+ "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.8",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
+ "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.18",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz",
+ "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/normalize-range": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
+ "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/nwsapi": {
+ "version": "2.2.16",
+ "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.16.tgz",
+ "integrity": "sha512-F1I/bimDpj3ncaNDhfyMWuFqmQDBwDB0Fogc2qpL3BWvkQteFD/8BzWuIRl83rq0DXfm8SGt/HFhLXZyljTXcQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/optionator": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/orderedmap": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.1.tgz",
+ "integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==",
+ "license": "MIT"
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/parse5": {
+ "version": "7.2.1",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz",
+ "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "entities": "^4.5.0"
+ },
+ "funding": {
+ "url": "https://github.com/inikulin/parse5?sponsor=1"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pathe": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.2.tgz",
+ "integrity": "sha512-15Ztpk+nov8DR524R4BF7uEuzESgzUEAV4Ah7CUMNGXdE5ELuvxElxGXndBl32vMSsWa1jpNf22Z+Er3sKwq+w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/pathval": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz",
+ "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14.16"
+ }
+ },
+ "node_modules/performance-now": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
+ "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==",
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.1",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz",
+ "integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.8",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/postcss-value-parser": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/prettier": {
+ "version": "3.4.2",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz",
+ "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==",
+ "dev": true,
+ "bin": {
+ "prettier": "bin/prettier.cjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
+ "node_modules/pretty-format": {
+ "version": "27.5.1",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
+ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^17.0.1"
+ },
+ "engines": {
+ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
+ }
+ },
+ "node_modules/pretty-format/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pretty-format/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/prosemirror-changeset": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.2.1.tgz",
+ "integrity": "sha512-J7msc6wbxB4ekDFj+n9gTW/jav/p53kdlivvuppHsrZXCaQdVgRghoZbSS3kwrRyAstRVQ4/+u5k7YfLgkkQvQ==",
+ "license": "MIT",
+ "dependencies": {
+ "prosemirror-transform": "^1.0.0"
+ }
+ },
+ "node_modules/prosemirror-collab": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/prosemirror-collab/-/prosemirror-collab-1.3.1.tgz",
+ "integrity": "sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==",
+ "license": "MIT",
+ "dependencies": {
+ "prosemirror-state": "^1.0.0"
+ }
+ },
+ "node_modules/prosemirror-commands": {
+ "version": "1.6.2",
+ "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.6.2.tgz",
+ "integrity": "sha512-0nDHH++qcf/BuPLYvmqZTUUsPJUCPBUXt0J1ErTcDIS369CTp773itzLGIgIXG4LJXOlwYCr44+Mh4ii6MP1QA==",
+ "license": "MIT",
+ "dependencies": {
+ "prosemirror-model": "^1.0.0",
+ "prosemirror-state": "^1.0.0",
+ "prosemirror-transform": "^1.10.2"
+ }
+ },
+ "node_modules/prosemirror-dropcursor": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.1.tgz",
+ "integrity": "sha512-M30WJdJZLyXHi3N8vxN6Zh5O8ZBbQCz0gURTfPmTIBNQ5pxrdU7A58QkNqfa98YEjSAL1HUyyU34f6Pm5xBSGw==",
+ "license": "MIT",
+ "dependencies": {
+ "prosemirror-state": "^1.0.0",
+ "prosemirror-transform": "^1.1.0",
+ "prosemirror-view": "^1.1.0"
+ }
+ },
+ "node_modules/prosemirror-gapcursor": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.3.2.tgz",
+ "integrity": "sha512-wtjswVBd2vaQRrnYZaBCbyDqr232Ed4p2QPtRIUK5FuqHYKGWkEwl08oQM4Tw7DOR0FsasARV5uJFvMZWxdNxQ==",
+ "license": "MIT",
+ "dependencies": {
+ "prosemirror-keymap": "^1.0.0",
+ "prosemirror-model": "^1.0.0",
+ "prosemirror-state": "^1.0.0",
+ "prosemirror-view": "^1.0.0"
+ }
+ },
+ "node_modules/prosemirror-history": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.4.1.tgz",
+ "integrity": "sha512-2JZD8z2JviJrboD9cPuX/Sv/1ChFng+xh2tChQ2X4bB2HeK+rra/bmJ3xGntCcjhOqIzSDG6Id7e8RJ9QPXLEQ==",
+ "license": "MIT",
+ "dependencies": {
+ "prosemirror-state": "^1.2.2",
+ "prosemirror-transform": "^1.0.0",
+ "prosemirror-view": "^1.31.0",
+ "rope-sequence": "^1.3.0"
+ }
+ },
+ "node_modules/prosemirror-inputrules": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.4.0.tgz",
+ "integrity": "sha512-6ygpPRuTJ2lcOXs9JkefieMst63wVJBgHZGl5QOytN7oSZs3Co/BYbc3Yx9zm9H37Bxw8kVzCnDsihsVsL4yEg==",
+ "license": "MIT",
+ "dependencies": {
+ "prosemirror-state": "^1.0.0",
+ "prosemirror-transform": "^1.0.0"
+ }
+ },
+ "node_modules/prosemirror-keymap": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.2.tgz",
+ "integrity": "sha512-EAlXoksqC6Vbocqc0GtzCruZEzYgrn+iiGnNjsJsH4mrnIGex4qbLdWWNza3AW5W36ZRrlBID0eM6bdKH4OStQ==",
+ "license": "MIT",
+ "dependencies": {
+ "prosemirror-state": "^1.0.0",
+ "w3c-keyname": "^2.2.0"
+ }
+ },
+ "node_modules/prosemirror-markdown": {
+ "version": "1.13.1",
+ "resolved": "https://registry.npmjs.org/prosemirror-markdown/-/prosemirror-markdown-1.13.1.tgz",
+ "integrity": "sha512-Sl+oMfMtAjWtlcZoj/5L/Q39MpEnVZ840Xo330WJWUvgyhNmLBLN7MsHn07s53nG/KImevWHSE6fEj4q/GihHw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/markdown-it": "^14.0.0",
+ "markdown-it": "^14.0.0",
+ "prosemirror-model": "^1.20.0"
+ }
+ },
+ "node_modules/prosemirror-menu": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/prosemirror-menu/-/prosemirror-menu-1.2.4.tgz",
+ "integrity": "sha512-S/bXlc0ODQup6aiBbWVsX/eM+xJgCTAfMq/nLqaO5ID/am4wS0tTCIkzwytmao7ypEtjj39i7YbJjAgO20mIqA==",
+ "license": "MIT",
+ "dependencies": {
+ "crelt": "^1.0.0",
+ "prosemirror-commands": "^1.0.0",
+ "prosemirror-history": "^1.0.0",
+ "prosemirror-state": "^1.0.0"
+ }
+ },
+ "node_modules/prosemirror-model": {
+ "version": "1.24.0",
+ "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.24.0.tgz",
+ "integrity": "sha512-Ft7epNnycoQSM+2ObF35SBbBX+5WY39v8amVlrtlAcpglhlHs2tCTnWl7RX5tbp/PsMKcRcWV9cXPuoBWq0AIQ==",
+ "license": "MIT",
+ "dependencies": {
+ "orderedmap": "^2.0.0"
+ }
+ },
+ "node_modules/prosemirror-schema-basic": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.3.tgz",
+ "integrity": "sha512-h+H0OQwZVqMon1PNn0AG9cTfx513zgIG2DY00eJ00Yvgb3UD+GQ/VlWW5rcaxacpCGT1Yx8nuhwXk4+QbXUfJA==",
+ "license": "MIT",
+ "dependencies": {
+ "prosemirror-model": "^1.19.0"
+ }
+ },
+ "node_modules/prosemirror-schema-list": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.4.1.tgz",
+ "integrity": "sha512-jbDyaP/6AFfDfu70VzySsD75Om2t3sXTOdl5+31Wlxlg62td1haUpty/ybajSfJ1pkGadlOfwQq9kgW5IMo1Rg==",
+ "license": "MIT",
+ "dependencies": {
+ "prosemirror-model": "^1.0.0",
+ "prosemirror-state": "^1.0.0",
+ "prosemirror-transform": "^1.7.3"
+ }
+ },
+ "node_modules/prosemirror-state": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.3.tgz",
+ "integrity": "sha512-goFKORVbvPuAQaXhpbemJFRKJ2aixr+AZMGiquiqKxaucC6hlpHNZHWgz5R7dS4roHiwq9vDctE//CZ++o0W1Q==",
+ "license": "MIT",
+ "dependencies": {
+ "prosemirror-model": "^1.0.0",
+ "prosemirror-transform": "^1.0.0",
+ "prosemirror-view": "^1.27.0"
+ }
+ },
+ "node_modules/prosemirror-tables": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.6.1.tgz",
+ "integrity": "sha512-p8WRJNA96jaNQjhJolmbxTzd6M4huRE5xQ8OxjvMhQUP0Nzpo4zz6TztEiwk6aoqGBhz9lxRWR1yRZLlpQN98w==",
+ "license": "MIT",
+ "dependencies": {
+ "prosemirror-keymap": "^1.1.2",
+ "prosemirror-model": "^1.8.1",
+ "prosemirror-state": "^1.3.1",
+ "prosemirror-transform": "^1.2.1",
+ "prosemirror-view": "^1.13.3"
+ }
+ },
+ "node_modules/prosemirror-trailing-node": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/prosemirror-trailing-node/-/prosemirror-trailing-node-3.0.0.tgz",
+ "integrity": "sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@remirror/core-constants": "3.0.0",
+ "escape-string-regexp": "^4.0.0"
+ },
+ "peerDependencies": {
+ "prosemirror-model": "^1.22.1",
+ "prosemirror-state": "^1.4.2",
+ "prosemirror-view": "^1.33.8"
+ }
+ },
+ "node_modules/prosemirror-transform": {
+ "version": "1.10.2",
+ "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.10.2.tgz",
+ "integrity": "sha512-2iUq0wv2iRoJO/zj5mv8uDUriOHWzXRnOTVgCzSXnktS/2iQRa3UUQwVlkBlYZFtygw6Nh1+X4mGqoYBINn5KQ==",
+ "license": "MIT",
+ "dependencies": {
+ "prosemirror-model": "^1.21.0"
+ }
+ },
+ "node_modules/prosemirror-view": {
+ "version": "1.37.0",
+ "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.37.0.tgz",
+ "integrity": "sha512-z2nkKI1sJzyi7T47Ji/ewBPuIma1RNvQCCYVdV+MqWBV7o4Sa1n94UJCJJ1aQRF/xRkFfyqLGlGFWitIcCOtbg==",
+ "license": "MIT",
+ "dependencies": {
+ "prosemirror-model": "^1.20.0",
+ "prosemirror-state": "^1.0.0",
+ "prosemirror-transform": "^1.1.0"
+ }
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/punycode.js": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
+ "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/qr-code-styling": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/qr-code-styling/-/qr-code-styling-1.9.1.tgz",
+ "integrity": "sha512-T/VxQchuZkQwYhIcyyMUmtXHPeDT6lJBYHfqGD5CBDyIjswxS7JZKf443q+SXO1K/9SUswi6JpXEUQ5AoMCpyg==",
+ "license": "MIT",
+ "dependencies": {
+ "qrcode-generator": "^1.4.4"
+ },
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/qrcode-generator": {
+ "version": "1.4.4",
+ "resolved": "https://registry.npmjs.org/qrcode-generator/-/qrcode-generator-1.4.4.tgz",
+ "integrity": "sha512-HM7yY8O2ilqhmULxGMpcHSF1EhJJ9yBj8gvDEuZ6M+KGJ0YY2hKpnXvRD+hZPLrDVck3ExIGhmPtSdcjC+guuw==",
+ "license": "MIT"
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/raf": {
+ "version": "3.4.1",
+ "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz",
+ "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "performance-now": "^2.1.0"
+ }
+ },
+ "node_modules/react": {
+ "version": "19.0.0",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz",
+ "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "19.0.0",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz",
+ "integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==",
+ "dependencies": {
+ "scheduler": "^0.25.0"
+ },
+ "peerDependencies": {
+ "react": "^19.0.0"
+ }
+ },
+ "node_modules/react-i18next": {
+ "version": "15.4.0",
+ "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.4.0.tgz",
+ "integrity": "sha512-Py6UkX3zV08RTvL6ZANRoBh9sL/ne6rQq79XlkHEdd82cZr2H9usbWpUNVadJntIZP2pu3M2rL1CN+5rQYfYFw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.25.0",
+ "html-parse-stringify": "^3.0.1"
+ },
+ "peerDependencies": {
+ "i18next": ">= 23.2.3",
+ "react": ">= 16.8.0"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ },
+ "react-native": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-is": {
+ "version": "17.0.2",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
+ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/react-refresh": {
+ "version": "0.14.2",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz",
+ "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-router": {
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.1.3.tgz",
+ "integrity": "sha512-EezYymLY6Guk/zLQ2vRA8WvdUhWFEj5fcE3RfWihhxXBW7+cd1LsIiA3lmx+KCmneAGQuyBv820o44L2+TtkSA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/cookie": "^0.6.0",
+ "cookie": "^1.0.1",
+ "set-cookie-parser": "^2.6.0",
+ "turbo-stream": "2.4.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/redent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
+ "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "indent-string": "^4.0.0",
+ "strip-indent": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/regenerator-runtime": {
+ "version": "0.14.1",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
+ "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
+ "license": "MIT"
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
+ "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rgbcolor": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz",
+ "integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==",
+ "license": "MIT OR SEE LICENSE IN FEEL-FREE.md",
+ "optional": true,
+ "engines": {
+ "node": ">= 0.8.15"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.27.4",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.27.4.tgz",
+ "integrity": "sha512-RLKxqHEMjh/RGLsDxAEsaLO3mWgyoU6x9w6n1ikAzet4B3gI2/3yP6PWY2p9QzRTh6MfEIXB3MwsOY0Iv3vNrw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.6"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.27.4",
+ "@rollup/rollup-android-arm64": "4.27.4",
+ "@rollup/rollup-darwin-arm64": "4.27.4",
+ "@rollup/rollup-darwin-x64": "4.27.4",
+ "@rollup/rollup-freebsd-arm64": "4.27.4",
+ "@rollup/rollup-freebsd-x64": "4.27.4",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.27.4",
+ "@rollup/rollup-linux-arm-musleabihf": "4.27.4",
+ "@rollup/rollup-linux-arm64-gnu": "4.27.4",
+ "@rollup/rollup-linux-arm64-musl": "4.27.4",
+ "@rollup/rollup-linux-powerpc64le-gnu": "4.27.4",
+ "@rollup/rollup-linux-riscv64-gnu": "4.27.4",
+ "@rollup/rollup-linux-s390x-gnu": "4.27.4",
+ "@rollup/rollup-linux-x64-gnu": "4.27.4",
+ "@rollup/rollup-linux-x64-musl": "4.27.4",
+ "@rollup/rollup-win32-arm64-msvc": "4.27.4",
+ "@rollup/rollup-win32-ia32-msvc": "4.27.4",
+ "@rollup/rollup-win32-x64-msvc": "4.27.4",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/rollup/node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.27.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.27.4.tgz",
+ "integrity": "sha512-5AeeAF1PB9TUzD+3cROzFTnAJAcVUGLuR8ng0E0WXGkYhp6RD6L+6szYVX+64Rs0r72019KHZS1ka1q+zU/wUw==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/rope-sequence": {
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz",
+ "integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==",
+ "license": "MIT"
+ },
+ "node_modules/rrweb-cssom": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz",
+ "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==",
+ "dev": true
+ },
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/rxjs": {
+ "version": "7.8.1",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
+ "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
+ "license": "Apache-2.0",
+ "optional": true,
+ "peer": true,
+ "dependencies": {
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/sass-embedded": {
+ "version": "1.83.4",
+ "resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.83.4.tgz",
+ "integrity": "sha512-Hf2burRA/y5PGxsg6jB9UpoK/xZ6g/pgrkOcdl6j+rRg1Zj8XhGKZ1MTysZGtTPUUmiiErqzkP5+Kzp95yv9GQ==",
+ "license": "MIT",
+ "optional": true,
+ "peer": true,
+ "dependencies": {
+ "@bufbuild/protobuf": "^2.0.0",
+ "buffer-builder": "^0.2.0",
+ "colorjs.io": "^0.5.0",
+ "immutable": "^5.0.2",
+ "rxjs": "^7.4.0",
+ "supports-color": "^8.1.1",
+ "sync-child-process": "^1.0.2",
+ "varint": "^6.0.0"
+ },
+ "bin": {
+ "sass": "dist/bin/sass.js"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ },
+ "optionalDependencies": {
+ "sass-embedded-android-arm": "1.83.4",
+ "sass-embedded-android-arm64": "1.83.4",
+ "sass-embedded-android-ia32": "1.83.4",
+ "sass-embedded-android-riscv64": "1.83.4",
+ "sass-embedded-android-x64": "1.83.4",
+ "sass-embedded-darwin-arm64": "1.83.4",
+ "sass-embedded-darwin-x64": "1.83.4",
+ "sass-embedded-linux-arm": "1.83.4",
+ "sass-embedded-linux-arm64": "1.83.4",
+ "sass-embedded-linux-ia32": "1.83.4",
+ "sass-embedded-linux-musl-arm": "1.83.4",
+ "sass-embedded-linux-musl-arm64": "1.83.4",
+ "sass-embedded-linux-musl-ia32": "1.83.4",
+ "sass-embedded-linux-musl-riscv64": "1.83.4",
+ "sass-embedded-linux-musl-x64": "1.83.4",
+ "sass-embedded-linux-riscv64": "1.83.4",
+ "sass-embedded-linux-x64": "1.83.4",
+ "sass-embedded-win32-arm64": "1.83.4",
+ "sass-embedded-win32-ia32": "1.83.4",
+ "sass-embedded-win32-x64": "1.83.4"
+ }
+ },
+ "node_modules/sass-embedded-android-arm": {
+ "version": "1.83.4",
+ "resolved": "https://registry.npmjs.org/sass-embedded-android-arm/-/sass-embedded-android-arm-1.83.4.tgz",
+ "integrity": "sha512-9Z4pJAOgEkXa3VDY/o+U6l5XvV0mZTJcSl0l/mSPHihjAHSpLYnOW6+KOWeM8dxqrsqTYcd6COzhanI/a++5Gw==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/sass-embedded-android-arm64": {
+ "version": "1.83.4",
+ "resolved": "https://registry.npmjs.org/sass-embedded-android-arm64/-/sass-embedded-android-arm64-1.83.4.tgz",
+ "integrity": "sha512-tgX4FzmbVqnQmD67ZxQDvI+qFNABrboOQgwsG05E5bA/US42zGajW9AxpECJYiMXVOHmg+d81ICbjb0fsVHskw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/sass-embedded-android-ia32": {
+ "version": "1.83.4",
+ "resolved": "https://registry.npmjs.org/sass-embedded-android-ia32/-/sass-embedded-android-ia32-1.83.4.tgz",
+ "integrity": "sha512-RsFOziFqPcfZXdFRULC4Ayzy9aK6R6FwQ411broCjlOBX+b0gurjRadkue3cfUEUR5mmy0KeCbp7zVKPLTK+5Q==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/sass-embedded-android-riscv64": {
+ "version": "1.83.4",
+ "resolved": "https://registry.npmjs.org/sass-embedded-android-riscv64/-/sass-embedded-android-riscv64-1.83.4.tgz",
+ "integrity": "sha512-EHwh0nmQarBBrMRU928eTZkFGx19k/XW2YwbPR4gBVdWLkbTgCA5aGe8hTE6/1zStyx++3nDGvTZ78+b/VvvLg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/sass-embedded-android-x64": {
+ "version": "1.83.4",
+ "resolved": "https://registry.npmjs.org/sass-embedded-android-x64/-/sass-embedded-android-x64-1.83.4.tgz",
+ "integrity": "sha512-0PgQNuPWYy1jEOEPDVsV89KfqOsMLIp9CSbjBY7jRcwRhyVAcigqrUG6bDeNtojHUYKA1kU+Eh/85WxOHUOgBw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/sass-embedded-darwin-arm64": {
+ "version": "1.83.4",
+ "resolved": "https://registry.npmjs.org/sass-embedded-darwin-arm64/-/sass-embedded-darwin-arm64-1.83.4.tgz",
+ "integrity": "sha512-rp2ywymWc3nymnSnAFG5R/8hvxWCsuhK3wOnD10IDlmNB7o4rzKby1c+2ZfpQGowlYGWsWWTgz8FW2qzmZsQRw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/sass-embedded-darwin-x64": {
+ "version": "1.83.4",
+ "resolved": "https://registry.npmjs.org/sass-embedded-darwin-x64/-/sass-embedded-darwin-x64-1.83.4.tgz",
+ "integrity": "sha512-kLkN2lXz9PCgGfDS8Ev5YVcl/V2173L6379en/CaFuJJi7WiyPgBymW7hOmfCt4uO4R1y7CP2Uc08DRtZsBlAA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/sass-embedded-linux-arm": {
+ "version": "1.83.4",
+ "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm/-/sass-embedded-linux-arm-1.83.4.tgz",
+ "integrity": "sha512-nL90ryxX2lNmFucr9jYUyHHx21AoAgdCL1O5Ltx2rKg2xTdytAGHYo2MT5S0LIeKLa/yKP/hjuSvrbICYNDvtA==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/sass-embedded-linux-arm64": {
+ "version": "1.83.4",
+ "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm64/-/sass-embedded-linux-arm64-1.83.4.tgz",
+ "integrity": "sha512-E0zjsZX2HgESwyqw31EHtI39DKa7RgK7nvIhIRco1d0QEw227WnoR9pjH3M/ZQy4gQj3GKilOFHM5Krs/omeIA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/sass-embedded-linux-ia32": {
+ "version": "1.83.4",
+ "resolved": "https://registry.npmjs.org/sass-embedded-linux-ia32/-/sass-embedded-linux-ia32-1.83.4.tgz",
+ "integrity": "sha512-ew5HpchSzgAYbQoriRh8QhlWn5Kw2nQ2jHoV9YLwGKe3fwwOWA0KDedssvDv7FWnY/FCqXyymhLd6Bxae4Xquw==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/sass-embedded-linux-musl-arm": {
+ "version": "1.83.4",
+ "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm/-/sass-embedded-linux-musl-arm-1.83.4.tgz",
+ "integrity": "sha512-0RrJRwMrmm+gG0VOB5b5Cjs7Sd+lhqpQJa6EJNEaZHljJokEfpE5GejZsGMRMIQLxEvVphZnnxl6sonCGFE/QQ==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/sass-embedded-linux-musl-arm64": {
+ "version": "1.83.4",
+ "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm64/-/sass-embedded-linux-musl-arm64-1.83.4.tgz",
+ "integrity": "sha512-IzMgalf6MZOxgp4AVCgsaWAFDP/IVWOrgVXxkyhw29fyAEoSWBJH4k87wyPhEtxSuzVHLxKNbc8k3UzdWmlBFg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/sass-embedded-linux-musl-ia32": {
+ "version": "1.83.4",
+ "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-ia32/-/sass-embedded-linux-musl-ia32-1.83.4.tgz",
+ "integrity": "sha512-LLb4lYbcxPzX4UaJymYXC+WwokxUlfTJEFUv5VF0OTuSsHAGNRs/rslPtzVBTvMeG9TtlOQDhku1F7G6iaDotA==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/sass-embedded-linux-musl-riscv64": {
+ "version": "1.83.4",
+ "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-riscv64/-/sass-embedded-linux-musl-riscv64-1.83.4.tgz",
+ "integrity": "sha512-zoKlPzD5Z13HKin1UGR74QkEy+kZEk2AkGX5RelRG494mi+IWwRuWCppXIovor9+BQb9eDWPYPoMVahwN5F7VA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/sass-embedded-linux-musl-x64": {
+ "version": "1.83.4",
+ "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-x64/-/sass-embedded-linux-musl-x64-1.83.4.tgz",
+ "integrity": "sha512-hB8+/PYhfEf2zTIcidO5Bpof9trK6WJjZ4T8g2MrxQh8REVtdPcgIkoxczRynqybf9+fbqbUwzXtiUao2GV+vQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/sass-embedded-linux-riscv64": {
+ "version": "1.83.4",
+ "resolved": "https://registry.npmjs.org/sass-embedded-linux-riscv64/-/sass-embedded-linux-riscv64-1.83.4.tgz",
+ "integrity": "sha512-83fL4n+oeDJ0Y4KjASmZ9jHS1Vl9ESVQYHMhJE0i4xDi/P3BNarm2rsKljq/QtrwGpbqwn8ujzOu7DsNCMDSHA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/sass-embedded-linux-x64": {
+ "version": "1.83.4",
+ "resolved": "https://registry.npmjs.org/sass-embedded-linux-x64/-/sass-embedded-linux-x64-1.83.4.tgz",
+ "integrity": "sha512-NlnGdvCmTD5PK+LKXlK3sAuxOgbRIEoZfnHvxd157imCm/s2SYF/R28D0DAAjEViyI8DovIWghgbcqwuertXsA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/sass-embedded-win32-arm64": {
+ "version": "1.83.4",
+ "resolved": "https://registry.npmjs.org/sass-embedded-win32-arm64/-/sass-embedded-win32-arm64-1.83.4.tgz",
+ "integrity": "sha512-J2BFKrEaeSrVazU2qTjyQdAk+MvbzJeTuCET0uAJEXSKtvQ3AzxvzndS7LqkDPbF32eXAHLw8GVpwcBwKbB3Uw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/sass-embedded-win32-ia32": {
+ "version": "1.83.4",
+ "resolved": "https://registry.npmjs.org/sass-embedded-win32-ia32/-/sass-embedded-win32-ia32-1.83.4.tgz",
+ "integrity": "sha512-uPAe9T/5sANFhJS5dcfAOhOJy8/l2TRYG4r+UO3Wp4yhqbN7bggPvY9c7zMYS0OC8tU/bCvfYUDFHYMCl91FgA==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/sass-embedded-win32-x64": {
+ "version": "1.83.4",
+ "resolved": "https://registry.npmjs.org/sass-embedded-win32-x64/-/sass-embedded-win32-x64-1.83.4.tgz",
+ "integrity": "sha512-C9fkDY0jKITdJFij4UbfPFswxoXN9O/Dr79v17fJnstVwtUojzVJWKHUXvF0Zg2LIR7TCc4ju3adejKFxj7ueA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/sass-embedded/node_modules/supports-color": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+ "license": "MIT",
+ "optional": true,
+ "peer": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/supports-color?sponsor=1"
+ }
+ },
+ "node_modules/saxes": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
+ "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "xmlchars": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=v12.22.7"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz",
+ "integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA=="
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/set-cookie-parser": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
+ "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
+ "license": "MIT"
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/siginfo": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
+ "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/stackback": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
+ "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/stackblur-canvas": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz",
+ "integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=0.1.14"
+ }
+ },
+ "node_modules/std-env": {
+ "version": "3.8.0",
+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.8.0.tgz",
+ "integrity": "sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/strip-indent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
+ "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "min-indent": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/svg-pathdata": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz",
+ "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/symbol-tree": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
+ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/sync-child-process": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/sync-child-process/-/sync-child-process-1.0.2.tgz",
+ "integrity": "sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA==",
+ "license": "MIT",
+ "optional": true,
+ "peer": true,
+ "dependencies": {
+ "sync-message-port": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/sync-message-port": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/sync-message-port/-/sync-message-port-1.1.3.tgz",
+ "integrity": "sha512-GTt8rSKje5FilG+wEdfCkOcLL7LWqpMlr2c3LRuKt/YXxcJ52aGSbGBAdI4L3aaqfrBt6y711El53ItyH1NWzg==",
+ "license": "MIT",
+ "optional": true,
+ "peer": true,
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/tailwindcss": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.0.tgz",
+ "integrity": "sha512-ULRPI3A+e39T7pSaf1xoi58AqqJxVCLg8F/uM5A3FadUbnyDTgltVnXJvdkTjwCOGA6NazqHVcwPJC5h2vRYVQ==",
+ "license": "MIT"
+ },
+ "node_modules/tapable": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
+ "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/text-segmentation": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz",
+ "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "utrie": "^1.0.2"
+ }
+ },
+ "node_modules/tinybench": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
+ "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinyexec": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz",
+ "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinypool": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.2.tgz",
+ "integrity": "sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ }
+ },
+ "node_modules/tinyrainbow": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz",
+ "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/tinyspy": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz",
+ "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/tippy.js": {
+ "version": "6.3.7",
+ "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz",
+ "integrity": "sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@popperjs/core": "^2.9.0"
+ }
+ },
+ "node_modules/tldts": {
+ "version": "6.1.68",
+ "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.68.tgz",
+ "integrity": "sha512-JKF17jROiYkjJPT73hUTEiTp2OBCf+kAlB+1novk8i6Q6dWjHsgEjw9VLiipV4KTJavazXhY1QUXyQFSem2T7w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tldts-core": "^6.1.68"
+ },
+ "bin": {
+ "tldts": "bin/cli.js"
+ }
+ },
+ "node_modules/tldts-core": {
+ "version": "6.1.68",
+ "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.68.tgz",
+ "integrity": "sha512-85TdlS/DLW/gVdf2oyyzqp3ocS30WxjaL4la85EArl9cHUR/nizifKAJPziWewSZjDZS71U517/i6ciUeqtB5Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/tough-cookie": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.0.0.tgz",
+ "integrity": "sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "tldts": "^6.1.32"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/tr46": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz",
+ "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "punycode": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/ts-api-utils": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.0.tgz",
+ "integrity": "sha512-xCt/TOAc+EOHS1XPnijD3/yzpH6qg2xppZO1YDqGoVsNXfQfzHpOdNuXwrwOU8u4ITXJyDCTyt8w5g1sZv9ynQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.12"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "license": "0BSD",
+ "optional": true,
+ "peer": true
+ },
+ "node_modules/turbo-stream": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz",
+ "integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==",
+ "license": "ISC"
+ },
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.7.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz",
+ "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/typescript-eslint": {
+ "version": "8.21.0",
+ "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.21.0.tgz",
+ "integrity": "sha512-txEKYY4XMKwPXxNkN8+AxAdX6iIJAPiJbHE/FpQccs/sxw8Lf26kqwC3cn0xkHlW8kEbLhkhCsjWuMveaY9Rxw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/eslint-plugin": "8.21.0",
+ "@typescript-eslint/parser": "8.21.0",
+ "@typescript-eslint/utils": "8.21.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <5.8.0"
+ }
+ },
+ "node_modules/uc.micro": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
+ "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
+ "license": "MIT"
+ },
+ "node_modules/undici-types": {
+ "version": "6.20.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
+ "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz",
+ "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.0"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/use-sync-external-store": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz",
+ "integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/utrie": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
+ "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "base64-arraybuffer": "^1.0.2"
+ }
+ },
+ "node_modules/uuid": {
+ "version": "11.0.5",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.5.tgz",
+ "integrity": "sha512-508e6IcKLrhxKdBbcA2b4KQZlLVp2+J5UwQ6F7Drckkc5N9ZJwFa4TgWtsww9UG8fGHbm6gbV19TdM5pQ4GaIA==",
+ "funding": [
+ "https://github.com/sponsors/broofa",
+ "https://github.com/sponsors/ctavan"
+ ],
+ "license": "MIT",
+ "bin": {
+ "uuid": "dist/esm/bin/uuid"
+ }
+ },
+ "node_modules/varint": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz",
+ "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==",
+ "license": "MIT",
+ "optional": true,
+ "peer": true
+ },
+ "node_modules/vite": {
+ "version": "6.0.11",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-6.0.11.tgz",
+ "integrity": "sha512-4VL9mQPKoHy4+FE0NnRE/kbY51TOfaknxAjt3fJbGJxhIpBZiqVzlZDEesWWsuREXHwNdAoOFZ9MkPEVXczHwg==",
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.24.2",
+ "postcss": "^8.4.49",
+ "rollup": "^4.23.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
+ "jiti": ">=1.21.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite-node": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.0.4.tgz",
+ "integrity": "sha512-7JZKEzcYV2Nx3u6rlvN8qdo3QV7Fxyt6hx+CCKz9fbWxdX5IvUOmTWEAxMrWxaiSf7CKGLJQ5rFu8prb/jBjOA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cac": "^6.7.14",
+ "debug": "^4.4.0",
+ "es-module-lexer": "^1.6.0",
+ "pathe": "^2.0.2",
+ "vite": "^5.0.0 || ^6.0.0"
+ },
+ "bin": {
+ "vite-node": "vite-node.mjs"
+ },
+ "engines": {
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/vitest": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.0.4.tgz",
+ "integrity": "sha512-6XG8oTKy2gnJIFTHP6LD7ExFeNLxiTkK3CfMvT7IfR8IN+BYICCf0lXUQmX7i7JoxUP8QmeP4mTnWXgflu4yjw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/expect": "3.0.4",
+ "@vitest/mocker": "3.0.4",
+ "@vitest/pretty-format": "^3.0.4",
+ "@vitest/runner": "3.0.4",
+ "@vitest/snapshot": "3.0.4",
+ "@vitest/spy": "3.0.4",
+ "@vitest/utils": "3.0.4",
+ "chai": "^5.1.2",
+ "debug": "^4.4.0",
+ "expect-type": "^1.1.0",
+ "magic-string": "^0.30.17",
+ "pathe": "^2.0.2",
+ "std-env": "^3.8.0",
+ "tinybench": "^2.9.0",
+ "tinyexec": "^0.3.2",
+ "tinypool": "^1.0.2",
+ "tinyrainbow": "^2.0.0",
+ "vite": "^5.0.0 || ^6.0.0",
+ "vite-node": "3.0.4",
+ "why-is-node-running": "^2.3.0"
+ },
+ "bin": {
+ "vitest": "vitest.mjs"
+ },
+ "engines": {
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "@edge-runtime/vm": "*",
+ "@types/debug": "^4.1.12",
+ "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
+ "@vitest/browser": "3.0.4",
+ "@vitest/ui": "3.0.4",
+ "happy-dom": "*",
+ "jsdom": "*"
+ },
+ "peerDependenciesMeta": {
+ "@edge-runtime/vm": {
+ "optional": true
+ },
+ "@types/debug": {
+ "optional": true
+ },
+ "@types/node": {
+ "optional": true
+ },
+ "@vitest/browser": {
+ "optional": true
+ },
+ "@vitest/ui": {
+ "optional": true
+ },
+ "happy-dom": {
+ "optional": true
+ },
+ "jsdom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/void-elements": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
+ "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/w3c-keyname": {
+ "version": "2.2.8",
+ "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
+ "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
+ "license": "MIT"
+ },
+ "node_modules/w3c-xmlserializer": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
+ "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "xml-name-validator": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/webidl-conversions": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
+ "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/whatwg-encoding": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
+ "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "iconv-lite": "0.6.3"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/whatwg-mimetype": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
+ "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/whatwg-url": {
+ "version": "14.1.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.1.0.tgz",
+ "integrity": "sha512-jlf/foYIKywAt3x/XWKZ/3rz8OSJPiWktjmk891alJUEjiVxKX9LEO92qH3hv4aJ0mN3MWPvGMCy8jQi95xK4w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tr46": "^5.0.0",
+ "webidl-conversions": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/why-is-node-running": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
+ "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "siginfo": "^2.0.0",
+ "stackback": "0.0.2"
+ },
+ "bin": {
+ "why-is-node-running": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/ws": {
+ "version": "8.18.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
+ "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/xml-name-validator": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
+ "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/xmlchars": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
+ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/y-prosemirror": {
+ "version": "1.2.15",
+ "resolved": "https://registry.npmjs.org/y-prosemirror/-/y-prosemirror-1.2.15.tgz",
+ "integrity": "sha512-XDdrytq2M5bIy3qusQvfRclLu2eWZYPA+BbGWAb9FFWEhOB5FCrnzez2vsA+gvAd0FJTAcr89mjJ5g45r0j7TQ==",
+ "dependencies": {
+ "lib0": "^0.2.42"
+ },
+ "engines": {
+ "node": ">=16.0.0",
+ "npm": ">=8.0.0"
+ },
+ "funding": {
+ "type": "GitHub Sponsors ❤",
+ "url": "https://github.com/sponsors/dmonad"
+ },
+ "peerDependencies": {
+ "prosemirror-model": "^1.7.1",
+ "prosemirror-state": "^1.2.3",
+ "prosemirror-view": "^1.9.10",
+ "y-protocols": "^1.0.1",
+ "yjs": "^13.5.38"
+ }
+ },
+ "node_modules/y-protocols": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/y-protocols/-/y-protocols-1.0.6.tgz",
+ "integrity": "sha512-vHRF2L6iT3rwj1jub/K5tYcTT/mEYDUppgNPXwp8fmLpui9f7Yeq3OEtTLVF012j39QnV+KEQpNqoN7CWU7Y9Q==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "lib0": "^0.2.85"
+ },
+ "engines": {
+ "node": ">=16.0.0",
+ "npm": ">=8.0.0"
+ },
+ "funding": {
+ "type": "GitHub Sponsors ❤",
+ "url": "https://github.com/sponsors/dmonad"
+ },
+ "peerDependencies": {
+ "yjs": "^13.0.0"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/yaml": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.1.tgz",
+ "integrity": "sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==",
+ "license": "ISC",
+ "optional": true,
+ "peer": true,
+ "bin": {
+ "yaml": "bin.mjs"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/yjs": {
+ "version": "13.6.23",
+ "resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.23.tgz",
+ "integrity": "sha512-ExtnT5WIOVpkL56bhLeisG/N5c4fmzKn4k0ROVfJa5TY2QHbH7F0Wu2T5ZhR7ErsFWQEFafyrnSI8TPKVF9Few==",
+ "license": "MIT",
+ "dependencies": {
+ "lib0": "^0.2.99"
+ },
+ "engines": {
+ "node": ">=16.0.0",
+ "npm": ">=8.0.0"
+ },
+ "funding": {
+ "type": "GitHub Sponsors ❤",
+ "url": "https://github.com/sponsors/dmonad"
+ }
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "packages/tiptap-color-with-classes": {
+ "name": "@packages/tiptap-color-with-classes",
+ "version": "0.0.1",
+ "extraneous": true,
+ "license": "MIT",
+ "devDependencies": {
+ "@tiptap/core": "^2.11.0",
+ "@tiptap/extension-text-style": "^2.11.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/b310"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^2.7.0",
+ "@tiptap/extension-text-style": "^2.7.0"
+ }
+ },
+ "packages/tiptap-comment-collaboration": {
+ "name": "@packages/tiptap-comment-collaboration",
+ "version": "0.0.1",
+ "extraneous": true,
+ "license": "MIT",
+ "devDependencies": {
+ "@tiptap/core": "^2.11.0",
+ "@tiptap/pm": "^2.11.0",
+ "typescript": "^5.7.3",
+ "uuid": "^11.0.4",
+ "vite": "^6.0.7",
+ "yjs": "^13.6.21"
+ }
+ },
+ "packages/tiptap-extension-color-with-classes": {
+ "name": "@packages/tiptap-extension-color-with-classes",
+ "version": "0.0.1",
+ "license": "MIT",
+ "devDependencies": {
+ "@tiptap/core": "^2.11.3",
+ "@tiptap/extension-text-style": "^2.11.3"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^2.7.0",
+ "@tiptap/extension-text-style": "^2.7.0"
+ }
+ },
+ "packages/tiptap-extension-comment-collaboration": {
+ "name": "@packages/tiptap-extension-comment-collaboration",
+ "version": "0.0.1",
+ "license": "MIT",
+ "devDependencies": {
+ "@tiptap/core": "^2.11.3",
+ "@tiptap/pm": "^2.11.3",
+ "typescript": "^5.7.3",
+ "uuid": "^11.0.5",
+ "vite": "^6.0.11",
+ "yjs": "^13.6.23"
+ }
+ },
+ "packages/tiptap-extension-image-delete-callback": {
+ "name": "@packages/tiptap-extension-image-delete-callback",
+ "version": "0.0.1",
+ "license": "MIT",
+ "devDependencies": {
+ "@tiptap/core": "^2.11.3",
+ "@tiptap/pm": "^2.11.3",
+ "typescript": "^5.7.3",
+ "uuid": "^11.0.5",
+ "vite": "^6.0.11",
+ "yjs": "^13.6.23"
+ }
+ },
+ "packages/tiptap-image-delete-callback": {
+ "name": "@packages/tiptap-image-delete-callback",
+ "version": "0.0.1",
+ "extraneous": true,
+ "license": "MIT",
+ "devDependencies": {
+ "@tiptap/core": "^2.11.0",
+ "@tiptap/pm": "^2.11.0",
+ "typescript": "^5.7.3",
+ "uuid": "^11.0.4",
+ "vite": "^6.0.7",
+ "yjs": "^13.6.21"
+ }
+ },
+ "packages/tiptap-image-uploaded": {
+ "name": "@packages/tiptap-image-uploaded",
+ "version": "0.0.1",
+ "extraneous": true,
+ "license": "MIT",
+ "devDependencies": {
+ "@tiptap/core": "^2.11.0",
+ "@tiptap/pm": "^2.11.0",
+ "typescript": "^5.7.3",
+ "uuid": "^11.0.4",
+ "vite": "^6.0.7",
+ "yjs": "^13.6.21"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..de4f84c
--- /dev/null
+++ b/package.json
@@ -0,0 +1,84 @@
+{
+ "name": "groupwriter-editor",
+ "private": true,
+ "version": "0.0.1",
+ "type": "module",
+ "description": "groupwriter editor",
+ "homepage": "https://b310.de",
+ "author": "B310 Digital GmbH",
+ "scripts": {
+ "dev": "vite --host 0.0.0.0",
+ "build": "vite build",
+ "lint": "eslint .",
+ "lint-check": "eslint --max-warnings=0 --no-fix .",
+ "test": "vitest",
+ "prettier-format": "prettier --config .prettierrc 'src/**/*.{ts,tsx}' --write",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@heroicons/react": "^2.2.0",
+ "@hocuspocus/provider": "^2.15.0",
+ "@packages/tiptap-extension-color-with-classes": "*",
+ "@packages/tiptap-extension-comment-collaboration": "*",
+ "@packages/tiptap-extension-image-delete-callback": "*",
+ "@tailwindcss/postcss": "^4.0.0",
+ "@tailwindcss/vite": "^4.0.0",
+ "@tiptap/core": "^2.11.3",
+ "@tiptap/extension-collaboration": "^2.11.3",
+ "@tiptap/extension-collaboration-cursor": "^2.11.3",
+ "@tiptap/extension-color": "^2.11.3",
+ "@tiptap/extension-image": "^2.11.3",
+ "@tiptap/extension-link": "^2.11.3",
+ "@tiptap/extension-placeholder": "^2.11.3",
+ "@tiptap/extension-table": "^2.11.3",
+ "@tiptap/extension-table-cell": "^2.11.3",
+ "@tiptap/extension-table-header": "^2.11.3",
+ "@tiptap/extension-table-row": "^2.11.3",
+ "@tiptap/extension-text-style": "^2.11.3",
+ "@tiptap/extension-underline": "^2.11.3",
+ "@tiptap/pm": "^2.11.3",
+ "@tiptap/react": "^2.11.3",
+ "@tiptap/starter-kit": "^2.11.3",
+ "i18next": "^24.2.1",
+ "i18next-browser-languagedetector": "^8.0.2",
+ "jspdf": "^2.5.2",
+ "postcss": "^8.5.1",
+ "qr-code-styling": "^1.9.1",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0",
+ "react-i18next": "^15.4.0",
+ "react-router": "^7.1.3",
+ "tailwindcss": "^4.0.0",
+ "uuid": "^11.0.5",
+ "y-prosemirror": "^1.2.15",
+ "yjs": "^13.6.23"
+ },
+ "devDependencies": {
+ "@eslint/js": "^9.18.0",
+ "@testing-library/jest-dom": "^6.6.3",
+ "@testing-library/react": "^16.2.0",
+ "@types/node": "^22.10.10",
+ "@types/react": "^19.0.8",
+ "@types/react-dom": "^19.0.3",
+ "@vitejs/plugin-react": "^4.3.1",
+ "autoprefixer": "^10.4.20",
+ "eslint": "^9.18.0",
+ "eslint-plugin-react-hooks": "^5.1.0",
+ "eslint-plugin-react-refresh": "^0.4.18",
+ "globals": "^15.14.0",
+ "jsdom": "^26.0.0",
+ "prettier": "^3.4.2",
+ "typescript": "^5.7.3",
+ "typescript-eslint": "^8.21.0",
+ "vite": "^6.0.11",
+ "vitest": "^3.0.4"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-linux-x64-musl": "4.31.0"
+ },
+ "workspaces": [
+ "packages/tiptap-extension-comment-collaboration",
+ "packages/tiptap-extension-color-with-classes",
+ "packages/tiptap-extension-image-delete-callback"
+ ]
+}
diff --git a/packages/tiptap-extension-color-with-classes/package.json b/packages/tiptap-extension-color-with-classes/package.json
new file mode 100644
index 0000000..cbe63e6
--- /dev/null
+++ b/packages/tiptap-extension-color-with-classes/package.json
@@ -0,0 +1,42 @@
+{
+ "name": "@packages/tiptap-extension-color-with-classes",
+ "version": "0.0.1",
+ "type": "module",
+ "types": "dist/index.d.ts",
+ "main": "dist/index.js",
+ "description": "text color extension for tiptap with classes instead of styles",
+ "homepage": "https://b310.de",
+ "author": "B310 Digital GmbH",
+ "keywords": [
+ "tiptap",
+ "tiptap extension"
+ ],
+ "license": "MIT",
+ "exports": {
+ ".": {
+ "types": "./dist/index.d.ts",
+ "import": "./dist/index.js"
+ }
+ },
+ "files": [
+ "src",
+ "dist"
+ ],
+ "devDependencies": {
+ "@tiptap/core": "^2.11.3",
+ "@tiptap/extension-text-style": "^2.11.3"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^2.7.0",
+ "@tiptap/extension-text-style": "^2.7.0"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/b310/tiptap-extension-color-with-classes"
+ },
+ "scripts": {
+ "clean": "rm -rf dist",
+ "build": "tsc --build",
+ "prepare": "tsc --build"
+ }
+}
\ No newline at end of file
diff --git a/packages/tiptap-extension-color-with-classes/src/color.ts b/packages/tiptap-extension-color-with-classes/src/color.ts
new file mode 100644
index 0000000..054f21c
--- /dev/null
+++ b/packages/tiptap-extension-color-with-classes/src/color.ts
@@ -0,0 +1,83 @@
+import '@tiptap/extension-text-style'
+
+import { Extension } from '@tiptap/core'
+
+export interface ColorWithClassesOptions {
+ /**
+ * The types where the color can be applied
+ * @default ['textStyle']
+ * @example ['heading', 'paragraph']
+ */
+ types: string[],
+}
+
+declare module '@tiptap/core' {
+ interface Commands {
+ color: {
+ /**
+ * Set the text color
+ * @param colorClass The color to set
+ * @example editor.commands.setColor('red')
+ */
+ setColor: (colorClass: string) => ReturnType,
+
+ /**
+ * Unset the text color
+ * @example editor.commands.unsetColor()
+ */
+ unsetColor: () => ReturnType,
+ }
+ }
+}
+
+/**
+ * This extension allows you to color your text.
+ * @see https://tiptap.dev/api/extensions/color
+ */
+export const ColorWithClasses = Extension.create({
+ name: 'colorWithClasses',
+
+ addOptions() {
+ return {
+ types: ['textStyle'],
+ }
+ },
+
+ addGlobalAttributes() {
+ return [
+ {
+ types: this.options.types,
+ attributes: {
+ colorClass: {
+ default: null,
+ renderHTML: attributes => {
+ if (!attributes.colorClass) {
+ return {}
+ }
+
+ return {
+ class: `${attributes.colorClass}`,
+ }
+ },
+ },
+ },
+ },
+ ]
+ },
+
+ addCommands() {
+ return {
+ setColor: colorClass => ({ chain }) => {
+ return chain()
+ .setMark('textStyle', { colorClass })
+ .run()
+ },
+ unsetColor: () => ({ chain }) => {
+ return chain()
+ .setMark('textStyle', { colorClass: null })
+ .removeEmptyTextStyle()
+ .run()
+ },
+ }
+ },
+})
\ No newline at end of file
diff --git a/packages/tiptap-extension-color-with-classes/src/index.ts b/packages/tiptap-extension-color-with-classes/src/index.ts
new file mode 100644
index 0000000..a912555
--- /dev/null
+++ b/packages/tiptap-extension-color-with-classes/src/index.ts
@@ -0,0 +1,5 @@
+import { ColorWithClasses } from './color'
+
+export * from './color'
+
+export default ColorWithClasses
\ No newline at end of file
diff --git a/packages/tiptap-extension-color-with-classes/tsconfig.json b/packages/tiptap-extension-color-with-classes/tsconfig.json
new file mode 100644
index 0000000..e4a5080
--- /dev/null
+++ b/packages/tiptap-extension-color-with-classes/tsconfig.json
@@ -0,0 +1,26 @@
+{
+ "compilerOptions": {
+ "module": "ESNext",
+ "esModuleInterop": true,
+ "target": "ESNext",
+ "lib": [
+ "esnext",
+ "dom"
+ ],
+ "moduleResolution": "Bundler",
+ "sourceMap": true,
+ "outDir": "dist",
+ "declaration": true,
+ "declarationDir": "dist",
+ "skipLibCheck": true,
+ "strictNullChecks": true,
+ "types": [
+ "vitest/globals"
+ ]
+ },
+ "include": ["./src/*.ts"],
+ "exclude": [
+ "node_modules",
+ "dist"
+ ]
+}
\ No newline at end of file
diff --git a/packages/tiptap-extension-comment-collaboration/package.json b/packages/tiptap-extension-comment-collaboration/package.json
new file mode 100644
index 0000000..c4a1f67
--- /dev/null
+++ b/packages/tiptap-extension-comment-collaboration/package.json
@@ -0,0 +1,28 @@
+{
+ "name": "@packages/tiptap-extension-comment-collaboration",
+ "version": "0.0.1",
+ "type": "module",
+ "types": "dist/index.d.ts",
+ "main": "dist/index.js",
+ "homepage": "https://b310.de",
+ "devDependencies": {
+ "@tiptap/core": "^2.11.3",
+ "@tiptap/pm": "^2.11.3",
+ "yjs": "^13.6.23",
+ "typescript": "^5.7.3",
+ "vite": "^6.0.11",
+ "uuid": "^11.0.5"
+ },
+ "scripts": {
+ "clean": "rm -rf dist",
+ "build": "tsc --build",
+ "prepare": "tsc --build"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/b310/tiptap-extension-comment-collaboration"
+ },
+ "author": "B310 Digital GmbH",
+ "license": "MIT",
+ "description": "comment collaboration extension for tiptap"
+}
diff --git a/packages/tiptap-extension-comment-collaboration/src/collaboration-comments.ts b/packages/tiptap-extension-comment-collaboration/src/collaboration-comments.ts
new file mode 100644
index 0000000..939fc36
--- /dev/null
+++ b/packages/tiptap-extension-comment-collaboration/src/collaboration-comments.ts
@@ -0,0 +1,353 @@
+import { Mark } from '@tiptap/core';
+import { v4 as uuidv4 } from 'uuid';
+import { Plugin } from '@tiptap/pm/state';
+import { CommentItem, CommentOptions, CommentStorage, CommentUser } from './types';
+import { createComment, createReply, debouncedUpdateCommentsPos } from './utils';
+import { DEFAULT_COLOR_CLASS, Y_MAP_COMMENT_KEY } from './constants';
+
+/**
+ * Extends the Commands interface to add comment-related commands
+ * Hint: set and unset are kept in the exiting editor command api,
+ * The rest of the commands are prefixed with comment to make it clear that they are related to comments
+ */
+declare module '@tiptap/core' {
+ interface Commands {
+ comment: {
+ /**
+ * Sets and adds a comment to a selection
+ */
+ setComment: (attributes: Partial) => ReturnType;
+ /**
+ * Unsets and removes a comment
+ */
+ unsetComment: () => ReturnType;
+ /**
+ * Accepts a proposal from a comment
+ */
+ commentAcceptProposal: (attributes: {
+ commentId: string;
+ }) => ReturnType;
+ /**
+ * Add a reply to a comment
+ */
+ commentAddReply: (attributes: Partial) => ReturnType;
+ /**
+ * Removes a comment by id
+ */
+ commentRemove: (attributes: { commentId: string }) => ReturnType;
+ /**
+ * Updates a comment by id
+ */
+ commentUpdate: (attributes: {
+ commentId: string;
+ text: string;
+ user: CommentUser | null;
+ }) => ReturnType;
+ /**
+ * Changes the username of a user in all comments
+ */
+ commentUsernameUpdate: (attributes: {
+ userId: string;
+ userName: string;
+ }) => ReturnType;
+ };
+ }
+}
+
+export const commentRemoveRegex =
+ /]*data-comment-id="[^"]*"[^>]*>(.*?)<\/span>/g;
+
+/* See documentaiton on tiptap extensions https://tiptap.dev/docs/editor/extensions/custom-extensions/extend-existing
+* Hint: Comments are currently exluded from history, despite removing text and redoing the insertion which also adds back the previously deleted comment.
+
+* Note on comment persistence:
+* - When text with a comment is deleted, the comment remains in storage
+* - This allows for proper undo/redo functionality
+* - Without this, redoing a deletion would fail since the comment would be missing
+*
+* If a comment is deleted with the commentRemove command, the comment is removed from the storage as well as from the editor.
+*
+* Alternative approach could be (on editor content changes):
+* 1. Remove comments from editor that don't exist in storage
+* 2. Then remove comments from storage that don't exist in editor
+* However this would break undo/redo functionality
+*/
+export const CollaborationCommentsExtension = Mark.create<
+ CommentOptions,
+ CommentStorage
+>({
+ name: 'comment',
+
+ addProseMirrorPlugins() {
+ return this.options.removeFromPaste
+ ? [
+ // This plugin is used to remove comments from pasted text
+ new Plugin({
+ props: {
+ transformPastedHTML: (html) =>
+ html.replace(commentRemoveRegex, '$1')
+ }
+ })
+ ]
+ : [];
+ },
+
+ onCreate() {
+ if (this.options.document) {
+ this.storage.comments =
+ this.options.document.getMap(Y_MAP_COMMENT_KEY);
+ this.storage.comments.observe(() => {
+ if (this.storage.comments) {
+ this.options.onCommentsDataUpdated(this.storage.comments);
+ }
+ });
+ }
+ },
+
+ addOptions() {
+ return {
+ HTMLAttributes: {},
+ onCommentsPosUpdated: () => void {},
+ onCommentsDataUpdated: () => void {},
+ onCommentActivated: () => void {},
+ document: null,
+ defaultColorClass: DEFAULT_COLOR_CLASS,
+ addToHistory: false,
+ removeFromPaste: true
+ };
+ },
+
+ addAttributes() {
+ return {
+ commentId: {
+ default: null,
+ parseHTML: (el) =>
+ (el as HTMLSpanElement).getAttribute('data-comment-id'),
+ renderHTML: (attrs) => ({
+ 'data-comment-id': typeof(attrs.commentId) === 'string' ? attrs.commentId : ''
+ })
+ },
+ colorClass: {
+ default: this.options.defaultColorClass,
+ parseHTML: (el) => (el as HTMLSpanElement).getAttribute('data-color-class'),
+ renderHTML: (attrs) => {
+ const colorClass = typeof(attrs.colorClass) === 'string' ? attrs.colorClass : '';
+ return { 'data-color-class': colorClass, class: `${colorClass} text-inherit` }
+ }
+ }
+ };
+ },
+
+ onUpdate() {
+ debouncedUpdateCommentsPos(this.editor, this.storage, this.options);
+ },
+
+ parseHTML() {
+ return [
+ {
+ tag: 'span[data-comment-id]'
+ }
+ ];
+ },
+
+ renderHTML({ HTMLAttributes }: { HTMLAttributes: Record }) {
+ const colorClass = typeof(HTMLAttributes['data-color-class']) === 'string' ? HTMLAttributes['data-color-class'] : '';
+ const commentId = typeof(HTMLAttributes['data-comment-id']) === 'string' ? HTMLAttributes['data-comment-id'] : '';
+
+ return [
+ 'span',
+ {
+ 'data-color-class': colorClass,
+ 'data-comment-id': commentId,
+ 'class': `${colorClass} text-inherit`
+ }
+ ];
+ },
+
+ addStorage() {
+ return {
+ comments: null
+ };
+ },
+
+ onSelectionUpdate() {
+ if (!this.editor?.state?.selection || !this.options) return;
+
+ const selection = this.editor?.state?.selection;
+ if (!('$from' in selection)) return;
+
+ const marks = selection.$from.marks();
+ if (!marks.length) {
+ this.options.onCommentActivated(null);
+ return;
+ }
+
+ const activeCommentMark = marks.find(
+ (mark) => mark.type.name === 'comment'
+ );
+ if (!activeCommentMark) {
+ this.options.onCommentActivated(null);
+ return;
+ }
+
+ this.options.onCommentActivated(
+ (activeCommentMark?.attrs?.commentId as string) ?? null
+ );
+ },
+
+ addCommands() {
+ return {
+ setComment:
+ (attributes) =>
+ ({ commands }) => {
+ const commentId = attributes?.commentId ?? uuidv4();
+ const commentText = attributes?.text ?? null;
+ const colorClass =
+ attributes?.colorClass ?? this.options.defaultColorClass ?? DEFAULT_COLOR_CLASS;
+
+ if (!attributes?.user) {
+ console.error('Set comment: User is required');
+ return false;
+ }
+
+ const comment = createComment(commentId, attributes.commentType, attributes.user, commentText, colorClass);
+
+ this.storage.comments?.set(commentId, comment);
+ commands.setMeta('addToHistory', this.options.addToHistory);
+ // the commentId is optional and potentially generated in this method, so we need to pass it along
+ return commands.setMark('comment', {
+ ...attributes,
+ commentId,
+ colorClass
+ });
+ },
+ unsetComment:
+ () =>
+ ({ commands }) => {
+ return commands.unsetMark(this.name);
+ },
+ commentAcceptProposal:
+ ({ commentId }) =>
+ ({ dispatch, state, tr }) => {
+ const comment = this.storage.comments?.get(commentId);
+ if (!comment) return false;
+ if (comment.commentType !== 'suggestion') {
+ console.error('Accept proposal: Comment is not a suggestion');
+ return false;
+ }
+
+ let replaced = false;
+
+ tr.doc.descendants((node, pos) => {
+ const commentMarks = node.marks.filter(
+ (mark) =>
+ mark.type.name === 'comment' &&
+ mark.attrs.commentId === commentId
+ );
+ const from = pos;
+ const to = pos + node.nodeSize;
+
+ commentMarks.forEach(() => {
+ // only replace the first match
+ if (!replaced) {
+ const mappedFrom = tr.mapping.map(from);
+ const mappedTo = tr.mapping.map(to);
+ if (comment?.text && comment.text !== '') {
+ tr.replaceWith(
+ mappedFrom,
+ mappedTo,
+ state.schema.text(comment.text)
+ );
+ } else {
+ tr.delete(mappedFrom, mappedTo);
+ }
+ replaced = true;
+ } else {
+ tr.delete(tr.mapping.map(from), tr.mapping.map(to));
+ }
+ });
+ });
+
+ dispatch?.(tr);
+
+ return replaced;
+ },
+ // this does not set or unset any marks, it just adds a replying comment to the storage
+ commentAddReply: (attributes) => () => {
+ const commentId = attributes?.commentId ?? uuidv4();
+ const commentText = attributes?.text ?? null;
+
+ if (!attributes?.parentId) {
+ console.error('Add reply: Parent comment id is required');
+ return false;
+ }
+
+ if (!attributes?.user) {
+ console.error('Add reply: User is required');
+ return false;
+ }
+
+ const reply = createReply(commentId, attributes.parentId, attributes.user, commentText);
+
+ this.storage.comments?.set(commentId, reply);
+ return true;
+ },
+ commentRemove:
+ (attributes) =>
+ ({ dispatch, state }) => {
+ const commentId = attributes?.commentId;
+ if (!commentId) return false;
+
+ state.doc.descendants((node, pos) => {
+ const commentMarks = node.marks.filter(
+ (mark) =>
+ mark.type.name === 'comment' &&
+ mark.attrs.commentId === commentId
+ );
+ commentMarks.forEach((mark) => {
+ const from = pos;
+ const to = pos + node.nodeSize;
+ dispatch?.(
+ state.tr
+ .setMeta('addToHistory', this.options.addToHistory)
+ .removeMark(from, to, mark.type)
+ );
+ });
+ });
+ this.storage.comments?.delete(commentId);
+ return true;
+ },
+ commentUpdate:
+ ({ commentId, text, user }) =>
+ () => {
+ const comment = this.storage.comments?.get(commentId);
+ if (!comment) return false;
+ this.storage.comments?.set(commentId, {
+ ...comment,
+ text,
+ updatedAt: Date.now(),
+ updatedBy: user ?? null
+ });
+ return true;
+ },
+ commentUsernameUpdate:
+ ({ userId, userName }) =>
+ () => {
+ const comments = this.storage.comments?.entries();
+ if (typeof comments !== 'object') return false;
+
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ for (const [_key, comment] of comments) {
+ const isCommentCreator = comment.user.id === userId;
+ const isCommentUpdater = comment.updatedBy?.id === userId;
+ if (!isCommentCreator && !isCommentUpdater) continue;
+
+ const newUser = isCommentCreator ? { ...comment.user, username: userName } : comment.user;
+ const newCommentUpdater = isCommentUpdater && comment.updatedBy ? { ...comment.updatedBy, username: userName } : comment.updatedBy;
+ this.storage.comments?.set(comment.commentId, { ...comment, user: newUser, updatedBy: newCommentUpdater });
+ };
+ return true;
+ }
+ };
+ }
+});
diff --git a/packages/tiptap-extension-comment-collaboration/src/constants.ts b/packages/tiptap-extension-comment-collaboration/src/constants.ts
new file mode 100644
index 0000000..3134193
--- /dev/null
+++ b/packages/tiptap-extension-comment-collaboration/src/constants.ts
@@ -0,0 +1,10 @@
+export const DEFAULT_COLOR_CLASS = 'bg-transparent';
+export const Y_MAP_COMMENT_KEY = 'comments';
+
+export const DEFAULT_COMMENT_OPTIONS = {
+ commentType: 'comment' as const,
+ draft: false,
+ resolved: false,
+ colorClass: DEFAULT_COLOR_CLASS,
+ parentId: null
+};
\ No newline at end of file
diff --git a/packages/tiptap-extension-comment-collaboration/src/index.ts b/packages/tiptap-extension-comment-collaboration/src/index.ts
new file mode 100644
index 0000000..a1963cd
--- /dev/null
+++ b/packages/tiptap-extension-comment-collaboration/src/index.ts
@@ -0,0 +1,4 @@
+import { CollaborationCommentsExtension, commentRemoveRegex } from './collaboration-comments'
+export { CommentOptions, CommentItem, CommentType, CommentStorage, MarkWithPos } from './types'
+
+export { CollaborationCommentsExtension as default, CollaborationCommentsExtension, commentRemoveRegex }
diff --git a/packages/tiptap-extension-comment-collaboration/src/types.ts b/packages/tiptap-extension-comment-collaboration/src/types.ts
new file mode 100644
index 0000000..492c0e9
--- /dev/null
+++ b/packages/tiptap-extension-comment-collaboration/src/types.ts
@@ -0,0 +1,57 @@
+import { Map as YMap, Doc } from 'yjs';
+import { Range } from '@tiptap/core';
+
+export interface MarkWithPos {
+ commentId: string;
+ range: Range;
+ coords?: {
+ left: number;
+ right: number;
+ top: number;
+ bottom: number;
+ };
+}
+
+export type CommentType = 'comment' | 'comment-reply' | 'suggestion';
+
+export interface CommentUser {
+ id: string | null;
+ username: string;
+}
+
+export interface CommentItem {
+ commentId: string;
+ commentType: CommentType;
+ text: string | null;
+ draft: boolean;
+ resolved: boolean;
+ parentId: string | null;
+ colorClass: string | null;
+ user: CommentUser;
+ updatedBy: CommentUser | null;
+ createdAt: number;
+ updatedAt: number;
+}
+
+export interface CommentStorage {
+ comments: YMap | null;
+}
+
+export interface CommentOptions {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ HTMLAttributes: Record;
+ // Callback for when comments positions are updated
+ onCommentsPosUpdated: (marks: Record) => void;
+ // Callback for when comments data is updated
+ onCommentsDataUpdated: (comments: YMap | null) => void;
+ // Callback for when a comment is activated (e.g. clicked on inside the editor)
+ onCommentActivated: (commentId: string | null) => void;
+ // The document to observe
+ document: Doc | null;
+ // The default color for comments
+ defaultColorClass: string | null;
+ // Controls if comment actions should be added to history
+ addToHistory: boolean | null;
+ // Controls if comment markings should be removed from paste
+ removeFromPaste: boolean | null;
+}
\ No newline at end of file
diff --git a/packages/tiptap-extension-comment-collaboration/src/utils.ts b/packages/tiptap-extension-comment-collaboration/src/utils.ts
new file mode 100644
index 0000000..cc0fa2b
--- /dev/null
+++ b/packages/tiptap-extension-comment-collaboration/src/utils.ts
@@ -0,0 +1,118 @@
+import { Editor } from "@tiptap/core";
+import { CommentItem, CommentOptions, CommentStorage, CommentType, CommentUser, MarkWithPos } from "./types";
+import { DEFAULT_COMMENT_OPTIONS } from "./constants";
+
+export const debounce = (fn: (...args: unknown[]) => void, timeout = 300) => {
+ let timer: NodeJS.Timeout;
+
+ return function (...args: unknown[]) {
+ clearTimeout(timer);
+ timer = setTimeout(() => {
+ fn(...args);
+ }, timeout);
+ };
+};
+
+export const createReply = (commentId: string, parentId: string, user: CommentUser, text: string | null): CommentItem => {
+ return {
+ commentType: 'comment-reply',
+ commentId: commentId,
+ user: user,
+ text: text === undefined ? null : text,
+ draft: false,
+ resolved: false,
+ parentId: parentId,
+ colorClass: null,
+ createdAt: Date.now(),
+ updatedBy: null,
+ updatedAt: Date.now()
+ }
+};
+
+export const createComment = (commentId: string, commentType: CommentType | undefined, user: CommentUser, text: string | null, colorClass: string | null): CommentItem => {
+return {
+ ...DEFAULT_COMMENT_OPTIONS,
+ ...{
+ commentId: commentId,
+ commentType: commentType ?? 'comment',
+ text: text === undefined ? null : text,
+ colorClass: colorClass,
+ user: user,
+ createdAt: Date.now(),
+ updatedBy: null,
+ updatedAt: Date.now()
+ }
+}
+};
+
+/**
+ * Attaches an event listener to all images in the editor.
+ * This listener triggers an update of the comments positions when an image is loaded.
+ */
+export const attachImageLoadedCallback = (
+ editor: Editor,
+ storage: CommentStorage,
+ options: CommentOptions
+) => {
+ const images = editor.view.dom.getElementsByTagName('img');
+
+ Array.from(images).forEach((img) => {
+ if (!img.hasAttribute('data-loading-handled')) {
+ img.setAttribute('data-loading-handled', 'true');
+ img.addEventListener('load', () => {
+ updateCommentsPos(editor, storage, options);
+ });
+ }
+ });
+};
+
+/**
+ * Calculates absolute positions of comments within the editor.
+ * These positions are used to render comments in the UI outside the editor.
+ */
+export const updateCommentsPos = (
+ editor: Editor,
+ _storage: CommentStorage,
+ options: CommentOptions
+) => {
+ if(editor.isDestroyed) return;
+
+ const marks: Record = {};
+ editor.state.doc.descendants((node, pos) => {
+ const commentMark = node.marks.find((mark) => mark.type.name === 'comment');
+ if (commentMark) {
+ // calculate the positions of comments in relation to the editor
+ const key = commentMark.attrs.commentId as string;
+ const coordsAtPos = editor.view.coordsAtPos(pos);
+ const editorBoundingRect = editor.view.dom.getBoundingClientRect();
+ const editorPosY = editorBoundingRect.y;
+ const editorPosX = editorBoundingRect.x;
+
+ // prioritize old positions of comments
+ if (!marks[key])
+ marks[key] = {
+ commentId: key,
+ range: { from: pos, to: pos + node.nodeSize },
+ coords: {
+ top: coordsAtPos.top - editorPosY,
+ left: coordsAtPos.left - editorPosX,
+ bottom: coordsAtPos.bottom - editorPosY,
+ right: coordsAtPos.right - editorPosX
+ }
+ };
+ }
+ });
+
+ options.onCommentsPosUpdated(marks);
+};
+
+/**
+ * Debounces the update of comments positions to prevent excessive re-renders.
+ */
+export const debouncedUpdateCommentsPos = debounce(
+ (editor: Editor, storage: CommentStorage, options: CommentOptions) => {
+ attachImageLoadedCallback(editor, storage, options);
+ updateCommentsPos(editor, storage, options);
+ },
+ 100
+);
\ No newline at end of file
diff --git a/packages/tiptap-extension-comment-collaboration/tsconfig.json b/packages/tiptap-extension-comment-collaboration/tsconfig.json
new file mode 100644
index 0000000..e4a5080
--- /dev/null
+++ b/packages/tiptap-extension-comment-collaboration/tsconfig.json
@@ -0,0 +1,26 @@
+{
+ "compilerOptions": {
+ "module": "ESNext",
+ "esModuleInterop": true,
+ "target": "ESNext",
+ "lib": [
+ "esnext",
+ "dom"
+ ],
+ "moduleResolution": "Bundler",
+ "sourceMap": true,
+ "outDir": "dist",
+ "declaration": true,
+ "declarationDir": "dist",
+ "skipLibCheck": true,
+ "strictNullChecks": true,
+ "types": [
+ "vitest/globals"
+ ]
+ },
+ "include": ["./src/*.ts"],
+ "exclude": [
+ "node_modules",
+ "dist"
+ ]
+}
\ No newline at end of file
diff --git a/packages/tiptap-extension-image-delete-callback/package.json b/packages/tiptap-extension-image-delete-callback/package.json
new file mode 100644
index 0000000..6469a67
--- /dev/null
+++ b/packages/tiptap-extension-image-delete-callback/package.json
@@ -0,0 +1,28 @@
+{
+ "name": "@packages/tiptap-extension-image-delete-callback",
+ "version": "0.0.1",
+ "type": "module",
+ "types": "dist/index.d.ts",
+ "main": "dist/index.js",
+ "homepage": "https://b310.de",
+ "devDependencies": {
+ "@tiptap/core": "^2.11.3",
+ "@tiptap/pm": "^2.11.3",
+ "yjs": "^13.6.23",
+ "typescript": "^5.7.3",
+ "vite": "^6.0.11",
+ "uuid": "^11.0.5"
+ },
+ "scripts": {
+ "clean": "rm -rf dist",
+ "build": "tsc --build",
+ "prepare": "tsc --build"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/b310/tiptap-extension-image-delete-callback"
+ },
+ "author": "B310 Digital GmbH",
+ "license": "MIT",
+ "description": "image delete callback extension for tiptap"
+}
diff --git a/packages/tiptap-extension-image-delete-callback/src/image-delete-callback.ts b/packages/tiptap-extension-image-delete-callback/src/image-delete-callback.ts
new file mode 100644
index 0000000..99ce97f
--- /dev/null
+++ b/packages/tiptap-extension-image-delete-callback/src/image-delete-callback.ts
@@ -0,0 +1,42 @@
+import { Extension } from '@tiptap/core'
+
+interface ImageDeleteCallbackOptions {
+ url: string;
+ deleteCallback: (url: string) => void;
+}
+
+interface YSyncMeta {
+ isChangeOrigin: boolean;
+ isUndoRedoOperation: boolean;
+}
+
+export const ImageDeleteCallback = Extension.create({
+ name: 'imageDeleteCallback',
+
+ addOptions() {
+ return {
+ url: '',
+ deleteCallback: () => void {}
+ }
+ },
+
+ onTransaction({transaction}) {
+ const srcs = new Set();
+ transaction.doc.forEach((node) => {
+ if (node.attrs.src && node.type.name === 'image') {
+ srcs.add(node.attrs.src);
+ }
+ });
+ transaction.before.forEach((node) => {
+ const src = node?.attrs?.src as string | undefined
+ if (src && node.type.name === 'image' && !srcs.has(src)) {
+ // Only use the callback for local changes, ignore changes from the origin server
+ const ySyncUpdate = transaction.getMeta('y-sync$') as YSyncMeta | undefined
+ if (src?.startsWith(this.options.url) && !ySyncUpdate?.isChangeOrigin) {
+ console.info('ImageDeleteCallback: Deleting image ', src)
+ this.options.deleteCallback(src)
+ }
+ }
+ });
+ }
+})
diff --git a/packages/tiptap-extension-image-delete-callback/src/index.ts b/packages/tiptap-extension-image-delete-callback/src/index.ts
new file mode 100644
index 0000000..a29102d
--- /dev/null
+++ b/packages/tiptap-extension-image-delete-callback/src/index.ts
@@ -0,0 +1,5 @@
+import { ImageDeleteCallback } from './image-delete-callback'
+
+export * from './image-delete-callback'
+
+export default ImageDeleteCallback
\ No newline at end of file
diff --git a/packages/tiptap-extension-image-delete-callback/tsconfig.json b/packages/tiptap-extension-image-delete-callback/tsconfig.json
new file mode 100644
index 0000000..e4a5080
--- /dev/null
+++ b/packages/tiptap-extension-image-delete-callback/tsconfig.json
@@ -0,0 +1,26 @@
+{
+ "compilerOptions": {
+ "module": "ESNext",
+ "esModuleInterop": true,
+ "target": "ESNext",
+ "lib": [
+ "esnext",
+ "dom"
+ ],
+ "moduleResolution": "Bundler",
+ "sourceMap": true,
+ "outDir": "dist",
+ "declaration": true,
+ "declarationDir": "dist",
+ "skipLibCheck": true,
+ "strictNullChecks": true,
+ "types": [
+ "vitest/globals"
+ ]
+ },
+ "include": ["./src/*.ts"],
+ "exclude": [
+ "node_modules",
+ "dist"
+ ]
+}
\ No newline at end of file
diff --git a/postcss.config.mjs b/postcss.config.mjs
new file mode 100644
index 0000000..b776c62
--- /dev/null
+++ b/postcss.config.mjs
@@ -0,0 +1,5 @@
+export default {
+ plugins: {
+ "@tailwindcss/postcss": {},
+ }
+}
\ No newline at end of file
diff --git a/public/images/android-chrome-192x192.png b/public/images/android-chrome-192x192.png
new file mode 100644
index 0000000..1675343
Binary files /dev/null and b/public/images/android-chrome-192x192.png differ
diff --git a/public/images/android-chrome-512x512.png b/public/images/android-chrome-512x512.png
new file mode 100644
index 0000000..e1e11dc
Binary files /dev/null and b/public/images/android-chrome-512x512.png differ
diff --git a/public/images/apple-touch-icon.png b/public/images/apple-touch-icon.png
new file mode 100644
index 0000000..164c11c
Binary files /dev/null and b/public/images/apple-touch-icon.png differ
diff --git a/public/images/favicon-16x16.png b/public/images/favicon-16x16.png
new file mode 100644
index 0000000..31bcb51
Binary files /dev/null and b/public/images/favicon-16x16.png differ
diff --git a/public/images/favicon-32x32.png b/public/images/favicon-32x32.png
new file mode 100644
index 0000000..3b5e02f
Binary files /dev/null and b/public/images/favicon-32x32.png differ
diff --git a/public/images/favicon.ico b/public/images/favicon.ico
new file mode 100644
index 0000000..ad82ba0
Binary files /dev/null and b/public/images/favicon.ico differ
diff --git a/public/images/logo.svg b/public/images/logo.svg
new file mode 100644
index 0000000..5219189
--- /dev/null
+++ b/public/images/logo.svg
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public/images/safari-pinned-tab.svg b/public/images/safari-pinned-tab.svg
new file mode 100644
index 0000000..5219189
--- /dev/null
+++ b/public/images/safari-pinned-tab.svg
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public/vite.svg b/public/vite.svg
new file mode 100644
index 0000000..e7b8dfb
--- /dev/null
+++ b/public/vite.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/App.tsx b/src/App.tsx
new file mode 100644
index 0000000..b56168a
--- /dev/null
+++ b/src/App.tsx
@@ -0,0 +1,11 @@
+import React from 'react';
+import { useParams } from 'react-router';
+import DocumentPage from './pages/DocumentPage';
+
+function App() {
+ const params = useParams();
+
+ return <>{params.id && }>;
+}
+
+export default App;
diff --git a/src/assets/exportTemplate.html b/src/assets/exportTemplate.html
new file mode 100644
index 0000000..7c7e5c0
--- /dev/null
+++ b/src/assets/exportTemplate.html
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/assets/exportTemplatePdf.html b/src/assets/exportTemplatePdf.html
new file mode 100644
index 0000000..2781e56
--- /dev/null
+++ b/src/assets/exportTemplatePdf.html
@@ -0,0 +1,103 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/assets/pexels-thepaintedsquare-998591.jpg b/src/assets/pexels-thepaintedsquare-998591.jpg
new file mode 100644
index 0000000..be17c6c
Binary files /dev/null and b/src/assets/pexels-thepaintedsquare-998591.jpg differ
diff --git a/src/assets/tablerColumnInsert.svg b/src/assets/tablerColumnInsert.svg
new file mode 100644
index 0000000..adac37f
--- /dev/null
+++ b/src/assets/tablerColumnInsert.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/tablerColumnRemove.svg b/src/assets/tablerColumnRemove.svg
new file mode 100644
index 0000000..1741430
--- /dev/null
+++ b/src/assets/tablerColumnRemove.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/tablerHtml.svg b/src/assets/tablerHtml.svg
new file mode 100644
index 0000000..d49728d
--- /dev/null
+++ b/src/assets/tablerHtml.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/tablerPdf.svg b/src/assets/tablerPdf.svg
new file mode 100644
index 0000000..9821166
--- /dev/null
+++ b/src/assets/tablerPdf.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/tablerRowInsert.svg b/src/assets/tablerRowInsert.svg
new file mode 100644
index 0000000..04b2221
--- /dev/null
+++ b/src/assets/tablerRowInsert.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/tablerRowRemove.svg b/src/assets/tablerRowRemove.svg
new file mode 100644
index 0000000..aeb65c2
--- /dev/null
+++ b/src/assets/tablerRowRemove.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/tablerTablePlus.svg b/src/assets/tablerTablePlus.svg
new file mode 100644
index 0000000..06264b4
--- /dev/null
+++ b/src/assets/tablerTablePlus.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/components/AboutButton.tsx b/src/components/AboutButton.tsx
new file mode 100644
index 0000000..fb696ac
--- /dev/null
+++ b/src/components/AboutButton.tsx
@@ -0,0 +1,29 @@
+import { useState } from 'react';
+import { InformationCircleIcon } from '@heroicons/react/24/solid';
+import { useTranslation } from 'react-i18next';
+import { AboutModal } from './AboutModal';
+
+const AboutButton = () => {
+ const [isModalOpen, setIsModalOpen] = useState(false);
+ const { t } = useTranslation();
+
+ const toggleModal = () => setIsModalOpen(!isModalOpen);
+
+ return (
+ <>
+
+
+
+ {isModalOpen && (
+
+ )}
+ >
+ );
+};
+
+export default AboutButton;
diff --git a/src/components/AboutModal.tsx b/src/components/AboutModal.tsx
new file mode 100644
index 0000000..8458afd
--- /dev/null
+++ b/src/components/AboutModal.tsx
@@ -0,0 +1,75 @@
+import { useContext } from 'react';
+import Modal from './Modal';
+import { EditorContext } from '../contexts/EditorContext';
+import { useNavigate } from 'react-router';
+import { deleteDocument } from '../utils/serverRequests';
+import { useTranslation } from 'react-i18next';
+import logo from '../../public/images/logo.svg';
+import { deleteLocalDocument } from '../utils/localstorage';
+
+export function AboutModal({
+ isModalOpen,
+ toggleModal
+}: {
+ isModalOpen: boolean;
+ toggleModal: () => void;
+}) {
+ const { t } = useTranslation();
+ const { readOnly, documentId, modificationSecret } =
+ useContext(EditorContext);
+ const navigate = useNavigate();
+
+ const handleDeleteDocument = async () => {
+ await deleteDocument(documentId, modificationSecret);
+ deleteLocalDocument(documentId);
+ void navigate('/');
+ };
+ return (
+
+
+
+ {t('modals.about.content')}
+
+
+
+
+ {t('buttons.close')}
+
+ {!readOnly && (
+ void handleDeleteDocument()}
+ >
+ {t('modals.about.buttons.delete')}
+
+ )}
+
+
+ );
+}
diff --git a/src/components/CommentCard.tsx b/src/components/CommentCard.tsx
new file mode 100644
index 0000000..538a4cd
--- /dev/null
+++ b/src/components/CommentCard.tsx
@@ -0,0 +1,260 @@
+import {
+ ChatBubbleBottomCenterIcon,
+ CheckIcon,
+ ChevronDownIcon,
+ ChevronUpIcon,
+ DocumentCheckIcon,
+ XCircleIcon
+} from '@heroicons/react/24/outline';
+import { PencilIcon } from '@heroicons/react/24/outline';
+import { Editor } from '@tiptap/core';
+import { CommentItem } from '@packages/tiptap-extension-comment-collaboration';
+import React, { useState, useContext, useEffect } from 'react';
+import { UserContext } from '../contexts/UserContext';
+import { EditorContext } from '../contexts/EditorContext';
+import { useTranslation } from 'react-i18next';
+
+const CommentCard = ({
+ editor,
+ comment,
+ setLastClickedCommentId,
+ activated,
+ isLastClicked,
+ absoluteTop,
+ toBeEdited
+}: {
+ editor: Editor | null;
+ comment: CommentItem;
+ setLastClickedCommentId: (commentId: string | null) => void;
+ activated: boolean;
+ isLastClicked: boolean;
+ absoluteTop: number;
+ toBeEdited: boolean;
+}) => {
+ const { currentUser } = useContext(UserContext);
+ const { readOnly } = useContext(EditorContext);
+
+ const [isOpened, setIsOpened] = useState(toBeEdited ?? false);
+ const [isEditing, setIsEditing] = useState(toBeEdited ?? false);
+ const [commentText, setCommentText] = useState(comment?.text);
+
+ const { t } = useTranslation();
+
+ useEffect(() => {
+ const handleClick = () => {
+ if (
+ comment &&
+ comment.text === null &&
+ editor &&
+ currentUser &&
+ comment.user?.id === currentUser?.userId
+ ) {
+ editor.commands.commentRemove({ commentId: comment.commentId });
+ }
+ };
+
+ document.addEventListener('click', handleClick);
+
+ return () => {
+ document.removeEventListener('click', handleClick);
+ };
+ }, [comment, editor, currentUser]);
+
+ const onCardClick = (): void => {
+ if (!isOpened) setLastClickedCommentId(comment.commentId);
+ setIsOpened(!isOpened);
+ setIsEditing(false);
+ };
+
+ const onDeleteClick = (): void => {
+ if (readOnly) return;
+ if (editor) editor.commands.commentRemove({ commentId: comment.commentId });
+ };
+
+ const onCommentEdit = (event: React.MouseEvent): void => {
+ event.stopPropagation();
+ if (readOnly) return;
+
+ if (isEditing && isOpened) {
+ setIsOpened(false);
+ setIsEditing(false);
+ } else if (isOpened && !isEditing) {
+ setIsEditing(true);
+ } else {
+ setIsEditing(true);
+ setIsOpened(true);
+ }
+ };
+
+ const onCommentAbortEdit = (
+ event: React.MouseEvent
+ ): void => {
+ event.stopPropagation();
+
+ if (readOnly) return;
+
+ setIsOpened(false);
+ setIsEditing(false);
+ if (comment.text === null && editor) {
+ editor.commands.commentRemove({ commentId: comment.commentId });
+ }
+ };
+
+ const onCommentSave = (event: React.MouseEvent): void => {
+ event.stopPropagation();
+
+ if (readOnly) return;
+
+ setIsOpened(false);
+ setIsEditing(false);
+ setLastClickedCommentId(null);
+ if (editor && currentUser) {
+ // Comments are instantly created, so only an non defined text reveals a new comment
+ const isCommentUpdate = !!comment.text;
+ const commentUser = isCommentUpdate
+ ? { id: currentUser.userId, username: currentUser.name }
+ : null;
+ editor.commands.commentUpdate({
+ commentId: comment.commentId,
+ text: commentText ?? '',
+ user: commentUser
+ });
+ }
+ };
+
+ const onCommentAcceptProposal = (
+ event: React.MouseEvent
+ ): void => {
+ event.stopPropagation();
+
+ if (readOnly) return;
+
+ if (editor)
+ editor.commands.commentAcceptProposal({
+ commentId: comment.commentId
+ });
+ };
+
+ const handleEmptyText = (text: string | null): string => {
+ if (text === null) return '...';
+
+ return text !== '' ? text : '(delete)';
+ };
+
+ const formatDate = (date: number | undefined): string => {
+ if (typeof date === 'number') return new Date(date).toLocaleString();
+ return '';
+ };
+
+ if (!comment) return <>>;
+
+ return (
+ onCardClick()}
+ >
+
+
+
+ {comment.commentType === 'suggestion' && (
+
+ )}
+ {comment.commentType === 'comment' && (
+
+ )}
+
{comment.user?.username}
+
+
{formatDate(comment.createdAt)}
+
+
+
+ {!readOnly && (
+ <>
+ {comment.commentType === 'suggestion' && (
+ onCommentAcceptProposal(event)}
+ className="border-none p-0 ms-2"
+ >
+
+
+ )}
+ onCommentEdit(event)}
+ className="border-none p-0 ms-2"
+ >
+
+
+ onDeleteClick()}
+ className="border-none p-0 ms-2"
+ >
+
+
+ >
+ )}
+
+
+
+
+ {isEditing ? (
+
+
+
+
+ onCommentSave(event)}
+ className="p-2 me-2"
+ >
+ {t('buttons.save')}
+
+ onCommentAbortEdit(event)}
+ className="p-2"
+ >
+ {t('buttons.abort')}
+
+
+
+ ) : (
+
+ {comment.commentType === 'suggestion' && (
+
+ {handleEmptyText(comment.text)}
+
+ )}
+ {comment.commentType !== 'suggestion' && (
+
{comment.text}
+ )}
+ {comment.updatedBy && typeof comment.updatedAt === 'number' && (
+
+ Last updated by {comment.updatedBy?.username} at{' '}
+ {formatDate(comment.updatedAt)}
+
+ )}
+
+ )}
+
+
+ {isOpened && }
+ {!isOpened && }
+
+
+ );
+};
+
+export default CommentCard;
diff --git a/src/components/CommentsList.tsx b/src/components/CommentsList.tsx
new file mode 100644
index 0000000..982a260
--- /dev/null
+++ b/src/components/CommentsList.tsx
@@ -0,0 +1,110 @@
+import React, {
+ ReactElement,
+ useCallback,
+ useContext,
+ useEffect,
+ useState
+} from 'react';
+import {
+ CommentItem,
+ MarkWithPos
+} from '@packages/tiptap-extension-comment-collaboration';
+import { Editor } from '@tiptap/core';
+import CommentCard from './CommentCard';
+import { UserContext } from '../contexts/UserContext';
+
+interface CommentCardMargins {
+ absoluteTop: number;
+ key: string;
+}
+
+const COLLAPSED_CARD_HEIGHT = 150;
+const COLLAPSED_CARD_MARGIN = 5;
+
+const EditorComments = ({
+ comments,
+ markPos,
+ editor,
+ activatedComment
+}: {
+ comments: Record;
+ markPos: Record;
+ editor: Editor | null;
+ activatedComment: string | null;
+}) => {
+ const { currentUser } = useContext(UserContext);
+ const [lastClickedCommentId, setLastClickedCommentId] = useState<
+ string | null
+ >(null);
+
+ // Recently added comments have priority so the user can fill them out
+ useEffect(() => {
+ if (!comments) return;
+
+ const toBeEditedComments = Object.values(comments).filter(
+ (comment) =>
+ comment?.text === null && currentUser?.userId === comment?.user?.id
+ );
+
+ if (toBeEditedComments.length > 0) {
+ setLastClickedCommentId(toBeEditedComments[0].commentId);
+ }
+ }, [comments, currentUser]);
+
+ const calculateMargins = useCallback(
+ (
+ comments: Record,
+ markPos: Record
+ ): CommentCardMargins[] | undefined => {
+ if (!comments || !markPos) {
+ return;
+ }
+ // Positional entries of comment marks inside the editor
+ const markPosEntries = Object.entries(markPos);
+ return markPosEntries.reduce((acc, [key, value], index) => {
+ // Absolute distance from the comment mark inside the editor to the top of the editor
+ const currentAbsTop = value?.coords?.top ?? 0;
+ const prevAbsTop = acc.at(acc.length - 1)?.absoluteTop ?? 0;
+
+ const newAbsTop = Math.max(
+ currentAbsTop,
+ prevAbsTop +
+ (index > 0 ? COLLAPSED_CARD_HEIGHT + COLLAPSED_CARD_MARGIN : 0),
+ 0
+ );
+
+ acc.push({
+ key,
+ absoluteTop: newAbsTop
+ });
+ return acc;
+ }, new Array(markPosEntries.length));
+ },
+ []
+ );
+
+ const renderComments = (): ReactElement[] => {
+ const margins = calculateMargins(comments, markPos) ?? [];
+ return margins.map(({ key, absoluteTop }) => {
+ const comment = comments?.[key];
+ if (!comment) return
;
+ return (
+
+ );
+ });
+ };
+ return {renderComments()}
;
+};
+
+export default EditorComments;
diff --git a/src/components/CopyButton.test.tsx b/src/components/CopyButton.test.tsx
new file mode 100644
index 0000000..583027c
--- /dev/null
+++ b/src/components/CopyButton.test.tsx
@@ -0,0 +1,33 @@
+import { describe, expect, test } from 'vitest';
+import { fireEvent, render, screen, waitFor } from '@testing-library/react';
+
+import { CopyButton } from './CopyButton';
+
+describe('CopyButton', () => {
+ test('if component is loading', () => {
+ render( );
+ expect(screen.getByText(/Copy/i)).toBeDefined();
+ });
+
+ test('if button is clicked', async () => {
+ // Mock the clipboard API
+ const writeTextMock = vi.fn(() => Promise.resolve());
+ vi.stubGlobal('navigator', {
+ clipboard: {
+ writeText: writeTextMock
+ }
+ });
+
+ const textToCopy = 'text to copy';
+
+ render( );
+
+ fireEvent.click(screen.getByText('modals.share.buttons.copy'));
+
+ await waitFor(() => {
+ expect(screen.getByText(/Copied/i)).toBeDefined();
+ });
+
+ expect(writeTextMock).toHaveBeenCalledWith(textToCopy);
+ });
+});
diff --git a/src/components/CopyButton.tsx b/src/components/CopyButton.tsx
new file mode 100644
index 0000000..f696fc6
--- /dev/null
+++ b/src/components/CopyButton.tsx
@@ -0,0 +1,37 @@
+import { useEffect, useState } from 'react';
+import { useTranslation } from 'react-i18next';
+
+interface CopyProps {
+ contentToCopy: string;
+}
+
+export const CopyButton = ({
+ contentToCopy
+}: CopyProps): React.ReactElement => {
+ const [copied, setCopied] = useState(false);
+
+ const { t } = useTranslation();
+
+ useEffect(() => {
+ setCopied(false);
+ }, [contentToCopy]);
+
+ const copyUrl = (): void => {
+ navigator.clipboard
+ .writeText(contentToCopy)
+ .then(() => setCopied(true))
+ .catch(() => setCopied(false));
+ };
+
+ const copyText = copied
+ ? t('modals.share.messages.copied')
+ : t('modals.share.buttons.copy');
+ return (
+
+ {copyText}
+
+ );
+};
diff --git a/src/components/DownloadDropdown.tsx b/src/components/DownloadDropdown.tsx
new file mode 100644
index 0000000..70cee51
--- /dev/null
+++ b/src/components/DownloadDropdown.tsx
@@ -0,0 +1,77 @@
+import { ArrowDownTrayIcon } from '@heroicons/react/24/outline';
+import { Editor } from '@tiptap/core';
+import IconDropdown, { DropdownValue } from './IconDropdown';
+import { Level } from '@tiptap/extension-heading';
+import { useTranslation } from 'react-i18next';
+import tablerHtml from '../assets/tablerHtml.svg';
+import tablerPdf from '../assets/tablerPdf.svg';
+import { exportedHTMLLink, exportedPDFLink } from '../utils/editorExport';
+
+const handlePDFExport = async (editor: Editor) => {
+ if (editor) {
+ const link = await exportedPDFLink(editor);
+ const linkElement = document.createElement('a');
+
+ linkElement.href = link;
+ linkElement.download = 'download.pdf';
+ linkElement.click();
+ }
+};
+
+const handleHTMLExport = (editor: Editor) => {
+ if (editor) {
+ const link = exportedHTMLLink(editor);
+ const linkElement = document.createElement('a');
+
+ linkElement.href = link;
+ linkElement.download = 'download.html';
+ linkElement.click();
+ }
+};
+
+const DownloadDropdown = ({ editor }: { editor: Editor }) => {
+ const { t } = useTranslation();
+ const availableHeadings = [
+ {
+ name: 'html',
+ children: (
+
+ ),
+ value: null,
+ title: t('menuBar.buttons.download.html')
+ },
+ {
+ name: 'pdf',
+ children: (
+
+ ),
+ value: null,
+ title: t('menuBar.buttons.download.pdf')
+ }
+ ];
+
+ const handleSelect = (dropdownValue: DropdownValue) => {
+ if (!editor) return;
+
+ if (dropdownValue.name === 'html') {
+ handleHTMLExport(editor);
+ } else {
+ void handlePDFExport(editor);
+ }
+ };
+
+ const textHeadingIcon = (
+
+ );
+
+ return (
+
+ );
+};
+
+export default DownloadDropdown;
diff --git a/src/components/EditorMenuBar.tsx b/src/components/EditorMenuBar.tsx
new file mode 100644
index 0000000..82358bf
--- /dev/null
+++ b/src/components/EditorMenuBar.tsx
@@ -0,0 +1,424 @@
+import {
+ ArrowUpTrayIcon,
+ ArrowUturnLeftIcon,
+ ArrowUturnRightIcon,
+ Bars3Icon,
+ BoldIcon,
+ ChatBubbleBottomCenterIcon,
+ DocumentCheckIcon,
+ DocumentPlusIcon,
+ ItalicIcon,
+ LinkIcon,
+ ListBulletIcon,
+ NumberedListIcon,
+ PhotoIcon,
+ StrikethroughIcon,
+ UnderlineIcon
+} from '@heroicons/react/24/outline';
+import { Editor } from '@tiptap/react';
+import React, {
+ ReactElement,
+ ReactNode,
+ useContext,
+ useEffect,
+ useState
+} from 'react';
+import { EditorContext } from '../contexts/EditorContext';
+import FixedMenuBar from './FixedMenuBar';
+import { serverUrl } from '../utils/editorSetup';
+import PaintBrushDropdown from './PaintBrushDropdown';
+import { createDocument, uploadImage } from '../utils/serverRequests';
+import TextHeadingDropdown from './TextHeadingDropdown';
+import DownloadDropdown from './DownloadDropdown';
+import { useTranslation } from 'react-i18next';
+import { TFunction } from 'i18next';
+import { LocalDocumentUser } from '../utils/localstorage';
+import { getAwarenessColor } from '../utils/userColors';
+import { setEditorContentFromFile } from '../utils/editorExport';
+import TableDropdown from './TableDropdown';
+
+const handleImageUpload = async (
+ editor: Editor,
+ documentId: string,
+ modificationSecret: string,
+ file: File
+) => {
+ const imageUrl = await uploadImage(file, documentId, modificationSecret);
+ if (imageUrl) {
+ editor
+ .chain()
+ .focus()
+ .setImage({ src: `${serverUrl()}/${imageUrl}` })
+ .run();
+ }
+};
+
+export const renderCommentButtons = (
+ editor: Editor,
+ currentUser: LocalDocumentUser | null,
+ setMobileCommentMenuOpen: (state: boolean) => void,
+ t: TFunction,
+ options?: {
+ className?: string;
+ }
+): ReactElement[] => {
+ return [
+ {
+ if (!currentUser) {
+ return;
+ }
+ setMobileCommentMenuOpen(true);
+ // Needed to prevent the new comment from being directly removed when clicking on the menu bar
+ event.stopPropagation();
+ if (editor.isActive('comment')) {
+ editor.chain().focus().unsetComment().run();
+ } else {
+ const colorAwarenessInfo = getAwarenessColor(currentUser.colorId);
+ editor?.commands.setComment({
+ colorClass: colorAwarenessInfo?.bgClass,
+ user: {
+ id: currentUser.userId,
+ username: currentUser.name
+ }
+ });
+ }
+ }}
+ disabled={editor.state.selection?.empty}
+ className={[
+ editor.isActive('comment') ? 'is-active' : '',
+ 'btn-editor',
+ options?.className ?? ''
+ ].join(' ')}
+ >
+
+ ,
+ {
+ if (!currentUser) {
+ return;
+ }
+ setMobileCommentMenuOpen(true);
+ // Needed to prevent the new comment from being directly removed when clicking on the menu bar
+ event.stopPropagation();
+ if (editor.isActive('comment')) {
+ editor.chain().focus().unsetComment().run();
+ } else {
+ const colorAwarenessInfo = getAwarenessColor(currentUser.colorId);
+ editor?.commands.setComment({
+ commentType: 'suggestion',
+ colorClass: colorAwarenessInfo?.bgClass,
+ user: {
+ id: currentUser.userId,
+ username: currentUser.name
+ }
+ });
+ }
+ }}
+ disabled={editor.state.selection?.empty}
+ className={[
+ editor.isActive('comment') ? 'is-active' : '',
+ 'btn-editor',
+ options?.className ?? ''
+ ].join(' ')}
+ >
+
+
+ ];
+};
+
+export default function MenuBar({
+ editor,
+ documentId,
+ modificationSecret,
+ currentUser,
+ children,
+ setMobileCommentMenuOpen
+}: {
+ editor: Editor;
+ documentId: string;
+ modificationSecret: string;
+ currentUser: LocalDocumentUser | null;
+ children: ReactNode;
+ setMobileCommentMenuOpen: (state: boolean) => void;
+}) {
+ const { t } = useTranslation();
+ const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
+ const { readOnly } = useContext(EditorContext);
+
+ const toggleMobileMenu = () => {
+ setMobileMenuOpen(!mobileMenuOpen);
+ };
+
+ useEffect(() => {
+ const handleClick = () => {
+ setMobileMenuOpen(false);
+ };
+
+ document.addEventListener('click', handleClick);
+
+ return () => {
+ document.removeEventListener('click', handleClick);
+ };
+ }, [editor, documentId, modificationSecret]);
+
+ if (!editor) {
+ return null;
+ }
+
+ if (readOnly) {
+ return {children} ;
+ } else {
+ return (
+ <>
+
+
+ {
+ event.stopPropagation();
+ toggleMobileMenu();
+ }}
+ className="btn-editor"
+ >
+
+
+
+
+
+
+ {
+ void (async () => {
+ const link = await createDocument();
+ if (link) window.open(link, '_blank');
+ })();
+ }}
+ className={['btn-editor'].join(' ')}
+ >
+
+
+
+
+ editor.chain().focus().toggleBold().run()}
+ disabled={!editor.can().chain().focus().toggleBold().run()}
+ className={[
+ editor.isActive('bold') ? 'is-active' : '',
+ 'btn-editor'
+ ].join(' ')}
+ >
+
+
+
+
+ editor.chain().focus().toggleItalic().run()}
+ disabled={!editor.can().chain().focus().toggleItalic().run()}
+ className={[
+ editor.isActive('italic') ? 'is-active' : '',
+ 'btn-editor'
+ ].join(' ')}
+ >
+
+
+
+
+ editor.chain().focus().toggleUnderline().run()}
+ disabled={
+ !editor.can().chain().focus().toggleUnderline().run()
+ }
+ className={[
+ editor.isActive('underline') ? 'is-active' : '',
+ 'btn-editor'
+ ].join(' ')}
+ >
+
+
+
+
+ editor.chain().focus().toggleStrike().run()}
+ disabled={!editor.can().chain().focus().toggleStrike().run()}
+ className={[
+ editor.isActive('strike') ? 'is-active' : '',
+ 'btn-editor'
+ ].join(' ')}
+ >
+
+
+
+
+
+
+
+
+ editor.chain().focus().toggleBulletList().run()
+ }
+ className={[
+ editor.isActive('bulletList') ? 'is-active' : '',
+ 'btn-editor'
+ ].join(' ')}
+ >
+
+
+
+
+
+ editor.chain().focus().toggleOrderedList().run()
+ }
+ className={[
+ editor.isActive('orderedList') ? 'is-active' : '',
+ 'btn-editor'
+ ].join(' ')}
+ >
+
+
+
+
+
+
+
+ {
+ if (editor.isActive('link')) {
+ editor.chain().focus().unsetLink().run();
+ } else {
+ const link = prompt(t('menuBar.buttons.link.prompt'));
+ if (link?.startsWith('http')) {
+ editor.chain().focus().setLink({ href: link }).run();
+ }
+ }
+ }}
+ className={[
+ editor.isActive('link') ? 'is-active' : '',
+ 'btn-editor'
+ ].join(' ')}
+ >
+
+
+
+
+
+
+
+ editor.chain().focus().undo().run()}
+ disabled={!editor.can().chain().focus().undo().run()}
+ className={['btn-editor'].join(' ')}
+ >
+
+
+
+
+ editor.chain().focus().redo().run()}
+ disabled={!editor.can().chain().focus().redo().run()}
+ className={['btn-editor'].join(' ')}
+ >
+
+
+
+
+
+ editor.chain().focus().toggleBlockquote().run()
+ }
+ disabled={
+ !editor.can().chain().focus().toggleBlockquote().run()
+ }
+ className={[
+ editor.isActive('blockquote') ? 'is-active' : '',
+ 'btn-editor'
+ ].join(' ')}
+ >
+ "
+
+
+ {renderCommentButtons(
+ editor,
+ currentUser,
+ setMobileCommentMenuOpen,
+ t
+ ).map((e) => (
+
+ {e}
+
+ ))}
+
+ ) => {
+ if (e.target.files?.[0]) {
+ void handleImageUpload(
+ editor,
+ documentId,
+ modificationSecret,
+ e.target.files?.[0]
+ );
+ }
+ e.target.value = '';
+ }}
+ disabled={false}
+ className="hidden"
+ />
+
+
+
+
+
+
+
+
+ ) => {
+ void setEditorContentFromFile(editor, e.target.files?.[0]);
+ }}
+ disabled={false}
+ className="hidden"
+ />
+
+
+
+
+
+
+ {children}
+
+ >
+ );
+ }
+}
diff --git a/src/components/FixedMenuBar.tsx b/src/components/FixedMenuBar.tsx
new file mode 100644
index 0000000..76b2a64
--- /dev/null
+++ b/src/components/FixedMenuBar.tsx
@@ -0,0 +1,9 @@
+import { ReactNode } from 'react';
+
+export default function FixedMenuBar({ children }: { children: ReactNode }) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/src/components/FlashMessage.tsx b/src/components/FlashMessage.tsx
new file mode 100644
index 0000000..38d64b1
--- /dev/null
+++ b/src/components/FlashMessage.tsx
@@ -0,0 +1,25 @@
+import { useTranslation } from 'react-i18next';
+import { useLocation } from 'react-router';
+
+export const FlashMessage = () => {
+ const { t } = useTranslation();
+ const location = useLocation();
+ const messageCode =
+ (location.state as { messageCode: string })?.messageCode || null;
+
+ // remove old message from history:
+ window.history.replaceState({}, '');
+
+ return (
+ <>
+ {messageCode && (
+
+ {t(`messages.${messageCode}`)}
+
+ )}
+ >
+ );
+};
diff --git a/src/components/IconDropdown.test.tsx b/src/components/IconDropdown.test.tsx
new file mode 100644
index 0000000..4bbc26d
--- /dev/null
+++ b/src/components/IconDropdown.test.tsx
@@ -0,0 +1,53 @@
+import { render, screen, fireEvent } from '@testing-library/react';
+import { PaintBrushIcon } from '@heroicons/react/24/outline';
+import IconDropdown from './IconDropdown';
+
+describe('IconDropdown', () => {
+ const mockOnSelect = vi.fn();
+ const values = [
+ { name: 'a', value: 'a', children: a
, title: 'a' }
+ ];
+
+ it('toggles the visibility', () => {
+ render(
+ }
+ onSelect={mockOnSelect}
+ values={values}
+ />
+ );
+ const button = screen.getByTestId('icon-dropdown-button');
+
+ // initially, it's hidden:
+ expect(screen.queryByTestId('icon-dropdown-menu')).not.toBeInTheDocument();
+
+ // it becomes visible after a click on button:
+ fireEvent.click(button);
+ expect(screen.queryByTestId('icon-dropdown-menu')).toBeVisible();
+
+ // and does not get removed again after another click:
+ fireEvent.click(button);
+ expect(screen.queryByTestId('icon-dropdown-menu')).not.toBeInTheDocument();
+ });
+
+ it('calls the onSelect method with the correct value', () => {
+ render(
+ }
+ onSelect={mockOnSelect}
+ values={values}
+ />
+ );
+
+ const button = screen.getByTestId('icon-dropdown-button');
+ fireEvent.click(button);
+
+ const dropdownMenu = screen.getByTestId('icon-dropdown-menu');
+ expect(dropdownMenu).toBeVisible();
+
+ fireEvent.click(screen.getByText('a'));
+ expect(mockOnSelect).toHaveBeenCalledWith(values[0]);
+ });
+});
diff --git a/src/components/IconDropdown.tsx b/src/components/IconDropdown.tsx
new file mode 100644
index 0000000..bb6de63
--- /dev/null
+++ b/src/components/IconDropdown.tsx
@@ -0,0 +1,86 @@
+import { useEffect, useState } from 'react';
+
+export interface DropdownValue {
+ name: string;
+ children: React.ReactNode;
+ title: string;
+ value: T;
+}
+
+const IconDropdown = ({
+ title,
+ icon,
+ values,
+ onSelect
+}: {
+ title: string;
+ icon: React.ReactElement;
+ values: DropdownValue[];
+ onSelect: (value: DropdownValue) => void;
+}) => {
+ const [isDropdownOpen, setIsDropdownOpen] = useState(false);
+
+ const toggleDropdown = () => {
+ setIsDropdownOpen((prev) => !prev);
+ };
+
+ const handleSelect = (value: DropdownValue) => {
+ onSelect(value);
+
+ setIsDropdownOpen(false);
+ };
+
+ useEffect(() => {
+ const handleClick = () => {
+ setIsDropdownOpen(false);
+ };
+
+ document.addEventListener('click', handleClick);
+
+ return () => {
+ document.removeEventListener('click', handleClick);
+ };
+ }, [icon, values]);
+
+ return (
+ <>
+
+
{
+ event.stopPropagation();
+ toggleDropdown();
+ }}
+ className="btn-editor"
+ data-testid="icon-dropdown-button"
+ >
+ {icon}
+
+ {isDropdownOpen && (
+
+
+ {values.map((option) => (
+
{
+ event.stopPropagation();
+ handleSelect(option);
+ }}
+ >
+ {option.children}
+
+ ))}
+
+
+ )}
+
+ >
+ );
+};
+
+export default IconDropdown;
diff --git a/src/components/Modal.tsx b/src/components/Modal.tsx
new file mode 100644
index 0000000..b0fcd2e
--- /dev/null
+++ b/src/components/Modal.tsx
@@ -0,0 +1,35 @@
+import { XMarkIcon } from '@heroicons/react/24/solid';
+import { ReactNode } from 'react';
+
+const Modal = ({
+ header,
+ isOpen,
+ onToggle,
+ children
+}: {
+ header: string;
+ isOpen: boolean;
+ onToggle: () => void;
+ children: ReactNode;
+}) => {
+ if (!isOpen) return null;
+
+ return (
+
+
+
+
{header}
+
+
+
+
+ {children}
+
+
+ );
+};
+
+export default Modal;
diff --git a/src/components/PaintBrushDropdown.test.tsx b/src/components/PaintBrushDropdown.test.tsx
new file mode 100644
index 0000000..8f2480b
--- /dev/null
+++ b/src/components/PaintBrushDropdown.test.tsx
@@ -0,0 +1,33 @@
+import { render, screen } from '@testing-library/react';
+import PaintBrushDropdown from './PaintBrushDropdown';
+import { Editor } from '@tiptap/core';
+
+vi.mock('@tiptap/core');
+
+describe('PaintBrushDropdown', () => {
+ const editorMock = new Editor();
+
+ it('does not add a colored background by default', () => {
+ const mockGetAttributes = vi.fn().mockReturnValue({});
+ editorMock.getAttributes = mockGetAttributes;
+
+ render( );
+
+ // By default, there is no color added to the icon:
+ const icon = screen.getByTestId('paint-brush-icon');
+ expect(icon).toHaveClass('size-4', { exact: true });
+ });
+
+ it('adds a colored background by default', () => {
+ const mockGetAttributes = vi
+ .fn()
+ .mockReturnValue({ colorClass: 'text-yellow-300' });
+ editorMock.getAttributes = mockGetAttributes;
+
+ render( );
+
+ // the color is added to the icon since the colorClass was provided in the mock:
+ const icon = screen.getByTestId('paint-brush-icon');
+ expect(icon).toHaveClass('size-4 text-yellow-300', { exact: true });
+ });
+});
diff --git a/src/components/PaintBrushDropdown.tsx b/src/components/PaintBrushDropdown.tsx
new file mode 100644
index 0000000..db60bf4
--- /dev/null
+++ b/src/components/PaintBrushDropdown.tsx
@@ -0,0 +1,122 @@
+import { PaintBrushIcon } from '@heroicons/react/24/outline';
+import { Editor } from '@tiptap/core';
+import IconDropdown, { DropdownValue } from './IconDropdown';
+import { useTranslation } from 'react-i18next';
+
+const PaintBrushDropdown = ({ editor }: { editor: Editor }) => {
+ const { t } = useTranslation();
+
+ const renderDropdownValue = (
+ className: string,
+ previewClassName: string,
+ value: string
+ ) => {
+ return (
+
+ );
+ };
+
+ const availableColors = [
+ {
+ name: 'black',
+ value: 'text-black',
+ children: renderDropdownValue(
+ 'text-black',
+ 'bg-black',
+ t('colors.black')
+ ),
+ title: t('colors.black')
+ },
+ {
+ name: 'red',
+ value: 'text-red-500',
+ children: renderDropdownValue(
+ 'text-red-500',
+ 'bg-red-500',
+ t('colors.red')
+ ),
+ title: t('colors.red')
+ },
+ {
+ name: 'blue',
+ value: 'text-blue-800',
+ children: renderDropdownValue(
+ 'text-blue-800',
+ 'bg-blue-800',
+ t('colors.blue')
+ ),
+ title: t('colors.blue')
+ },
+ {
+ name: 'green',
+ value: 'text-green-800',
+ children: renderDropdownValue(
+ 'text-green-800',
+ 'bg-green-800',
+ t('colors.green')
+ ),
+ title: t('colors.green')
+ },
+ {
+ name: 'yellow',
+ value: 'text-yellow-400',
+ children: renderDropdownValue(
+ 'text-yellow-400',
+ 'bg-yellow-400',
+ t('colors.yellow')
+ ),
+ title: t('colors.yellow')
+ }
+ ];
+
+ const applyColor = (color: string): void => {
+ if (!editor) return;
+ editor.chain().focus().setColor(color).run();
+ };
+
+ const unsetColor = (): void => {
+ if (!editor) return;
+ editor.chain().focus().unsetColor().run();
+ };
+
+ const currentColor = (): string => {
+ return (editor.getAttributes('textStyle').colorClass as string) || '';
+ };
+
+ const handleSelect = (color: DropdownValue) => {
+ if (color && color.value !== '') {
+ if (color.name == 'black') {
+ unsetColor();
+ } else {
+ applyColor(color.value);
+ }
+ } else {
+ unsetColor();
+ }
+ };
+
+ const paintBrushIcon = (
+
+ );
+
+ return (
+
+ );
+};
+
+export default PaintBrushDropdown;
diff --git a/src/components/ShareDocumentButton.tsx b/src/components/ShareDocumentButton.tsx
new file mode 100644
index 0000000..ba35e65
--- /dev/null
+++ b/src/components/ShareDocumentButton.tsx
@@ -0,0 +1,126 @@
+import { ShareIcon } from '@heroicons/react/24/outline';
+import { useCallback, useEffect, useRef, useState } from 'react';
+import QRCodeStyling from 'qr-code-styling';
+import Switch from './Switch';
+import Modal from './Modal';
+import { CopyButton } from './CopyButton';
+import { useTranslation } from 'react-i18next';
+
+const initQrCode = () =>
+ new QRCodeStyling({
+ width: 300,
+ height: 300,
+ type: 'svg',
+ image: '',
+ dotsOptions: {
+ color: '#000000',
+ type: 'dots'
+ },
+ cornersSquareOptions: {
+ type: 'square'
+ },
+ cornersDotOptions: {
+ type: 'dot'
+ },
+ backgroundOptions: {
+ color: '#fff'
+ },
+ imageOptions: {
+ crossOrigin: 'anonymous',
+ margin: 20
+ }
+ });
+
+const urlWithouthHash = (urlHash: string): string => {
+ return urlHash.split('#')[0];
+};
+
+const url = (readOnly: boolean): string => {
+ const href = window.location.href;
+ return readOnly ? urlWithouthHash(href) : href;
+};
+
+const ShareDocumentButton = () => {
+ const { t } = useTranslation();
+ const [qrCode] = useState(initQrCode());
+ const qrCodeRef = useRef(null);
+ const [locationUrl, setLocationUrl] = useState(url(false));
+ const [isModalOpen, setIsModalOpen] = useState(false);
+ const [readOnly, setReadOnly] = useState(false);
+
+ const toggleModal = () => setIsModalOpen(!isModalOpen);
+ const toggleReadOnly = () => {
+ setReadOnly(!readOnly);
+ };
+
+ useEffect(() => {
+ setLocationUrl(url(readOnly));
+ }, [readOnly, window.location.href]);
+
+ // append qr-code when modal is opened:
+ useEffect(() => {
+ if (qrCodeRef?.current === null) return;
+
+ qrCode.append(qrCodeRef.current);
+ }, [isModalOpen, qrCode]);
+
+ // update the qr-code when readOnly is toggled
+ useEffect(() => {
+ qrCode.update({
+ data: locationUrl
+ });
+ }, [locationUrl, qrCode]);
+
+ const onDownload = useCallback(() => {
+ void qrCode.download({
+ extension: 'png'
+ });
+ }, [qrCode, locationUrl]);
+
+ return (
+ <>
+
+
+
+ {isModalOpen && (
+
+
+
+
+ {t('modals.share.buttons.download')}
+
+
+
+
+ {t('modals.share.readOnly')}
+
+
+
+
+
+
+
+
+
+ )}
+ >
+ );
+};
+
+export default ShareDocumentButton;
diff --git a/src/components/Switch.tsx b/src/components/Switch.tsx
new file mode 100644
index 0000000..6fcfa17
--- /dev/null
+++ b/src/components/Switch.tsx
@@ -0,0 +1,24 @@
+const Switch = ({
+ isOn,
+ onToggle
+}: {
+ isOn: boolean;
+ onToggle: (isOn: boolean) => void;
+}) => {
+ const toggleSwitch = () => {
+ onToggle(!isOn); // Pass the new state to the parent
+ };
+
+ return (
+
+ );
+};
+
+export default Switch;
diff --git a/src/components/TableDropdown.tsx b/src/components/TableDropdown.tsx
new file mode 100644
index 0000000..ac25314
--- /dev/null
+++ b/src/components/TableDropdown.tsx
@@ -0,0 +1,96 @@
+import { TableCellsIcon } from '@heroicons/react/24/solid';
+import { Editor } from '@tiptap/core';
+import IconDropdown, { DropdownValue } from './IconDropdown';
+import { useTranslation } from 'react-i18next';
+import tablerColumnInsert from '../assets/tablerColumnInsert.svg';
+import tablerColumnRemove from '../assets/tablerColumnRemove.svg';
+import tablerRowInsert from '../assets/tablerRowInsert.svg';
+import tablerRowRemove from '../assets/tablerRowRemove.svg';
+import tablerTablePlus from '../assets/tablerTablePlus.svg';
+
+const TableDropdown = ({ editor }: { editor: Editor }) => {
+ const { t } = useTranslation();
+ const availableActions = [
+ {
+ name: 'insertTable',
+ children: (
+
+ ),
+ value: 'insertTable',
+ title: t('menuBar.buttons.table.insertTable')
+ },
+ {
+ name: 'addColumnAfter',
+ children: (
+
+ ),
+ value: 'addColumnAfter',
+ title: t('menuBar.buttons.table.addColumnAfter')
+ },
+ {
+ name: 'deleteColumn',
+ children: (
+
+ ),
+ value: 'deleteColumn',
+ title: t('menuBar.buttons.table.deleteColumn')
+ },
+ {
+ name: 'addRowAfter',
+ children: (
+
+ ),
+ value: 'addRowAfter',
+ title: t('menuBar.buttons.table.addRowAfter')
+ },
+ {
+ name: 'deleteRow',
+ children: (
+
+ ),
+ value: 'deleteRow',
+ title: t('menuBar.buttons.table.deleteRow')
+ }
+ ];
+
+ const handleSelect = (dropdownValue: DropdownValue) => {
+ if (!editor) return;
+
+ if (dropdownValue.name === 'addColumnAfter') {
+ editor.chain().focus().addColumnAfter().run();
+ } else if (dropdownValue.name === 'deleteColumn') {
+ editor.chain().focus().deleteColumn().run();
+ } else if (dropdownValue.name === 'addRowAfter') {
+ editor.chain().focus().addRowAfter().run();
+ } else if (dropdownValue.name === 'deleteRow') {
+ editor.chain().focus().deleteRow().run();
+ } else if (dropdownValue.name === 'insertTable') {
+ editor.chain().focus().insertTable({ rows: 3, cols: 3 }).run();
+ }
+ };
+
+ const tableIcon = (
+
+ );
+
+ return (
+
+ );
+};
+
+export default TableDropdown;
diff --git a/src/components/TextHeadingDropdown.tsx b/src/components/TextHeadingDropdown.tsx
new file mode 100644
index 0000000..22f6111
--- /dev/null
+++ b/src/components/TextHeadingDropdown.tsx
@@ -0,0 +1,69 @@
+import {
+ Bars3CenterLeftIcon,
+ H1Icon,
+ H2Icon,
+ H3Icon
+} from '@heroicons/react/24/outline';
+import { Editor } from '@tiptap/core';
+import IconDropdown, { DropdownValue } from './IconDropdown';
+import { Level } from '@tiptap/extension-heading';
+import { useTranslation } from 'react-i18next';
+
+const TextHeadingDropdown = ({ editor }: { editor: Editor }) => {
+ const { t } = useTranslation();
+ const availableHeadings = [
+ {
+ name: 'paragraph',
+ children: ,
+ value: null,
+ title: t('menuBar.buttons.heading.paragraph')
+ },
+ {
+ name: 'h1',
+ children: ,
+ value: 1,
+ title: t('menuBar.buttons.heading.h1')
+ },
+ {
+ name: 'h2',
+ children: ,
+ value: 2,
+ title: t('menuBar.buttons.heading.h2')
+ },
+ {
+ name: 'h3',
+ children: ,
+ value: 3,
+ title: t('menuBar.buttons.heading.h3')
+ }
+ ];
+
+ const handleSelect = (dropdownValue: DropdownValue) => {
+ if (!editor) return;
+
+ if (dropdownValue.value === null) {
+ editor.chain().focus().setParagraph().run();
+ } else {
+ editor
+ .chain()
+ .focus()
+ .toggleHeading({ level: dropdownValue.value })
+ .run();
+ }
+ };
+
+ const textHeadingIcon = (
+
+ );
+
+ return (
+
+ );
+};
+
+export default TextHeadingDropdown;
diff --git a/src/components/UserList.tsx b/src/components/UserList.tsx
new file mode 100644
index 0000000..0fa6fea
--- /dev/null
+++ b/src/components/UserList.tsx
@@ -0,0 +1,35 @@
+import { ReactElement } from 'react';
+import { getInitials } from '../utils/editorSetup';
+import { LocalDocumentUser } from '../utils/localstorage';
+import { getAwarenessColor } from '../utils/userColors';
+
+export const UserList = ({
+ users
+}: {
+ users: Record;
+}) => {
+ const renderUserList = (): ReactElement[] => {
+ if (!users) return [];
+
+ // we use userId to sort the users because it is unique and stable
+ return Object.values(users)
+ .sort((userA, userB) => userA.userId.localeCompare(userB.userId))
+ .map((user) => {
+ const colorAwarenessInfo = getAwarenessColor(user.colorId);
+ return (
+
+ {getInitials(user?.name)}
+
+ );
+ });
+ };
+
+ return (
+
+ {renderUserList()}
+
+ );
+};
diff --git a/src/components/UtilMenuBar.tsx b/src/components/UtilMenuBar.tsx
new file mode 100644
index 0000000..79f1a7f
--- /dev/null
+++ b/src/components/UtilMenuBar.tsx
@@ -0,0 +1,58 @@
+import {
+ ChatBubbleBottomCenterIcon,
+ UserIcon
+} from '@heroicons/react/24/outline';
+import ShareDocumentButton from './ShareDocumentButton';
+import AboutButton from './AboutButton';
+import { useTranslation } from 'react-i18next';
+import { Editor } from '@tiptap/core';
+import { LocalDocumentUser } from '../utils/localstorage';
+
+export const UtilMenuBar = ({
+ toggleMobileCommentMenu,
+ updateUser,
+ currentUser,
+ editor
+}: {
+ toggleMobileCommentMenu: () => void;
+ updateUser: (user: LocalDocumentUser) => void;
+ currentUser: LocalDocumentUser | null;
+ editor: Editor;
+}) => {
+ const { t } = useTranslation();
+
+ return (
+
+
+
+
+
{
+ const newUsername = window.prompt(t('modals.user.username'));
+ if (!newUsername || !currentUser) return;
+
+ updateUser({
+ ...currentUser,
+ name: newUsername
+ });
+
+ editor.commands.commentUsernameUpdate({
+ userId: currentUser.userId,
+ userName: newUsername
+ });
+ }}
+ disabled={false}
+ className="btn-editor"
+ >
+
+
+
+
+
+ );
+};
diff --git a/src/components/editor/Tiptap.tsx b/src/components/editor/Tiptap.tsx
new file mode 100644
index 0000000..327bd43
--- /dev/null
+++ b/src/components/editor/Tiptap.tsx
@@ -0,0 +1,212 @@
+import './styles.scss';
+
+import { BubbleMenu, EditorContent, useEditor } from '@tiptap/react';
+
+import * as Y from 'yjs';
+import { onAwarenessUpdateParameters, StatesArray } from '@hocuspocus/provider';
+
+import {
+ createExtensions,
+ createProvider,
+ debounce
+} from '../../utils/editorSetup';
+import EditorMenuBar, { renderCommentButtons } from '../EditorMenuBar';
+import React, {
+ useCallback,
+ useContext,
+ useEffect,
+ useMemo,
+ useState
+} from 'react';
+import CollaborationCommentsExtension from '@packages/tiptap-extension-comment-collaboration';
+import {
+ CommentItem,
+ MarkWithPos
+} from '@packages/tiptap-extension-comment-collaboration';
+import CommentsList from '../CommentsList';
+
+import { UserContext } from '../../contexts/UserContext';
+import { UserList } from '../UserList';
+import { EditorContext } from '../../contexts/EditorContext';
+import { UtilMenuBar } from '../UtilMenuBar';
+import { useNavigate } from 'react-router';
+import { useTranslation } from 'react-i18next';
+import { LocalDocumentUser } from '../../utils/localstorage';
+
+const Tiptap = ({ documentId }: { documentId: string }) => {
+ const navigate = useNavigate();
+ const { t } = useTranslation();
+ const [comments, setComments] = useState>({});
+ const [markPos, setMarkPos] = useState>({});
+ const [activatedComment, setActivatedComment] = useState(null);
+ const { currentUser, storeUserSetting } = useContext(UserContext);
+ const { readOnly, modificationSecret } = useContext(EditorContext);
+ const [users, setUsers] = useState>({});
+ const [mobileCommentMenuOpen, setMobileCommentMenuOpen] =
+ useState(false);
+
+ useEffect(() => {
+ editor?.destroy();
+ }, [documentId]);
+
+ // Sets current users from awareness states
+ const setAwarenessUsers = (states: StatesArray) => {
+ const awarenessUsers = Object.values(states).reduce<
+ Record
+ >((acc, state) => {
+ const user = state.user as LocalDocumentUser;
+ if (!user?.userId) return acc;
+
+ acc[user.userId] = user;
+ return acc;
+ }, {});
+ setUsers(awarenessUsers);
+ };
+
+ const debouncedSetUsers = debounce((states: StatesArray) => {
+ setAwarenessUsers(states);
+ });
+
+ const ydoc = useMemo(() => new Y.Doc(), [documentId]);
+
+ const provider = useMemo(
+ () => createProvider(documentId, ydoc, modificationSecret),
+ [documentId, ydoc, modificationSecret]
+ );
+
+ useEffect(() => {
+ if (provider) {
+ provider.on(
+ 'awarenessUpdate',
+ ({ states }: onAwarenessUpdateParameters) => {
+ debouncedSetUsers(states, setUsers);
+ }
+ );
+
+ provider.on('close', () => {
+ void navigate('/', {
+ state: { messageCode: 'connectionClosed' }
+ });
+ });
+ }
+ }, [provider]);
+
+ const handleCommentsPosUpdated = useCallback(
+ (marks: Record) => {
+ setMarkPos(marks);
+ },
+ [setMarkPos]
+ );
+
+ const handleCommentActivated = useCallback(
+ (commentId: string) => setActivatedComment(commentId),
+ []
+ );
+
+ const handleCommentsDataUpdated = useCallback(
+ (comments: Y.Map | null) => {
+ setComments(comments?.toJSON() ?? {});
+ },
+ [setComments]
+ );
+ const editor = useEditor({
+ injectCSS: false,
+ shouldRerenderOnTransaction: true,
+ enablePasteRules: [CollaborationCommentsExtension],
+ immediatelyRender: false,
+ editable: !readOnly,
+ extensions: createExtensions(
+ ydoc,
+ t,
+ provider,
+ modificationSecret,
+ handleCommentsPosUpdated,
+ handleCommentsDataUpdated,
+ handleCommentActivated,
+ currentUser
+ ),
+ editorProps: {
+ attributes: {
+ class:
+ 'h-full bg-white border border-neutral-200 rounded-lg text-left p-8'
+ }
+ }
+ });
+
+ const updateUser = useCallback(
+ (user: LocalDocumentUser) => {
+ editor?.commands.updateUser(user);
+ storeUserSetting(user);
+ },
+ [editor, storeUserSetting]
+ );
+
+ const toggleMobileCommentMenu = () => {
+ setMobileCommentMenuOpen(!mobileCommentMenuOpen);
+ };
+
+ return (
+ <>
+ {editor && (
+ <>
+
+
+
+
+ >
+ )}
+
+
+
+
+
+
+
+ {editor &&
+ renderCommentButtons(
+ editor,
+ currentUser,
+ setMobileCommentMenuOpen,
+ t,
+ {
+ className: 'inline-block'
+ }
+ )}
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
+
+export default Tiptap;
diff --git a/src/components/editor/styles.scss b/src/components/editor/styles.scss
new file mode 100644
index 0000000..3b42d91
--- /dev/null
+++ b/src/components/editor/styles.scss
@@ -0,0 +1,286 @@
+@reference "tailwindcss";
+
+.collaboration-cursor__caret {
+ @apply inline;
+ position: relative;
+ border-left: 1px solid #0d0d0d;
+ border-right: 1px solid #0d0d0d;
+ margin-left: -1px;
+ margin-right: -1px;
+ pointer-events: none;
+ word-break: normal;
+}
+
+.grid-rows-editor {
+ grid-template-rows: auto 1fr;
+}
+
+.collaboration-cursor__label {
+ @apply inline;
+ position: absolute;
+ top: -1.5rem;
+}
+
+.btn-editor {
+ @apply block border border-solid rounded-sm m-2 border-neutral-200;
+
+ &:disabled {
+ @apply bg-neutral-200 border-neutral-200;
+ }
+}
+
+.is-active {
+ @apply border-neutral-600;
+}
+
+.comment-card {
+ @apply absolute block lg:inline-block right-0 left-0 w-full bg-white border rounded-lg hover:border-neutral-400 break-words border-neutral-200;
+}
+
+.comment-card-content {
+ @apply overflow-hidden ps-4 pe-4;
+ height: 50px;
+}
+
+.comment-card-header {
+ @apply pt-4 ps-4 pe-4 pb-2;
+ height: 60px;
+}
+
+.comment-card-footer {
+ @apply pt-2 ps-4 pe-4 pb-4;
+ height: 42px;
+}
+
+.comment-card-content-opened {
+ @apply overflow-visible h-auto z-10;
+ min-height: 50px;
+}
+
+.comment-card-last-clicked {
+ @apply z-40;
+}
+
+.comment-card-activated {
+ @apply transition-colors duration-200 border-neutral-400;
+}
+
+.comment-card-editing {
+ @apply z-50;
+}
+
+.tiptap p.is-editor-empty:first-child::before {
+ color: #adb5bd;
+ content: attr(data-placeholder);
+ float: left;
+ height: 0;
+ pointer-events: none;
+}
+
+.tiptap {
+ :first-child {
+ margin-top: 0;
+ }
+
+ /* List styles */
+ ul {
+ li {
+ list-style-type: circle;
+ }
+ }
+ ol {
+ li {
+ list-style-type: decimal;
+ }
+ }
+ ul,
+ ol {
+ padding: 0 1rem;
+ margin: 1.25rem 1rem 1.25rem 0.4rem;
+
+ li p {
+ margin-top: 0.25em;
+ margin-bottom: 0.25em;
+ }
+ }
+
+ /* Heading styles */
+ h1,
+ h2,
+ h3,
+ h4,
+ h5,
+ h6 {
+ line-height: 1.1;
+ margin-top: 2.5rem;
+ text-wrap: pretty;
+ }
+
+ h1,
+ h2 {
+ margin-top: 3.5rem;
+ margin-bottom: 1.5rem;
+ }
+
+ h1 {
+ font-size: 1.4rem;
+ }
+
+ h2 {
+ font-size: 1.2rem;
+ }
+
+ h3 {
+ font-size: 1.1rem;
+ }
+
+ h4,
+ h5,
+ h6 {
+ font-size: 1rem;
+ }
+
+ /* Code and preformatted text styles */
+ code {
+ @apply bg-neutral-100;
+ border-radius: 0.4rem;
+ font-size: 0.85rem;
+ padding: 0.25em 0.3em;
+ }
+
+ table {
+ border: 1px solid var(--color-neutral-200);
+ }
+
+ tr {
+ border: 1px solid var(--color-neutral-200);
+ min-width: 100px;
+ }
+
+ td {
+ border: 1px solid var(--color-neutral-200);
+ word-break: break-all;
+ }
+
+ th {
+ border: 1px solid var(--color-neutral-200);
+ min-width: 100px;
+ word-break: break-all;
+ }
+
+ pre {
+ border-radius: 0.5rem;
+ font-family: 'JetBrainsMono', monospace;
+ margin: 1.5rem 0;
+ padding: 0.75rem 1rem;
+
+ code {
+ background: none;
+ color: inherit;
+ font-size: 0.8rem;
+ padding: 0;
+ }
+ }
+
+ a {
+ text-decoration: underline;
+ cursor: pointer;
+ color: var(--color-primary);
+ }
+
+ blockquote {
+ border-left: 3px solid var(--color-neutral-200);
+ margin: 1.5rem 0;
+ padding-left: 1rem;
+ background-color: var(--color-neutral-100);
+ }
+
+ hr {
+ border: none;
+ border-top: 1px solid var(--color-neutral-200);
+ margin: 2rem 0;
+ }
+}
+
+ // Important! If not set leads to safari performance issues
+ .ProseMirror {
+ @apply focus:outline-none;
+ }
+
+ // See https://github.com/ueberdosis/tiptap/blob/main/packages/core/src/style.ts
+ // As the option injectCSS is false, we need to add the styles manually
+ .ProseMirror {
+ position: relative;
+ }
+
+ .ProseMirror {
+ word-wrap: break-word;
+ white-space: pre-wrap;
+ white-space: break-spaces;
+ -webkit-font-variant-ligatures: none;
+ font-variant-ligatures: none;
+ font-feature-settings: "liga" 0; /* the above doesn't seem to work in Edge */
+ }
+
+ .ProseMirror [contenteditable="false"] {
+ white-space: normal;
+ }
+
+ .ProseMirror [contenteditable="false"] [contenteditable="true"] {
+ white-space: pre-wrap;
+ }
+
+ .ProseMirror pre {
+ white-space: pre-wrap;
+ }
+
+ img.ProseMirror-separator {
+ display: inline !important;
+ border: none !important;
+ margin: 0 !important;
+ width: 0 !important;
+ height: 0 !important;
+ }
+
+ .ProseMirror-gapcursor {
+ display: none;
+ pointer-events: none;
+ position: absolute;
+ margin: 0;
+ }
+
+ .ProseMirror-gapcursor:after {
+ content: "";
+ display: block;
+ position: absolute;
+ top: -2px;
+ width: 20px;
+ border-top: 1px solid black;
+ animation: ProseMirror-cursor-blink 1.1s steps(2, start) infinite;
+ }
+
+ @keyframes ProseMirror-cursor-blink {
+ to {
+ visibility: hidden;
+ }
+ }
+
+ .ProseMirror-hideselection *::selection {
+ background: transparent;
+ }
+
+ .ProseMirror-hideselection *::-moz-selection {
+ background: transparent;
+ }
+
+ .ProseMirror-hideselection * {
+ caret-color: transparent;
+ }
+
+ .ProseMirror-focused .ProseMirror-gapcursor {
+ display: block;
+ }
+
+ .tippy-box[data-animation=fade][data-state=hidden] {
+ opacity: 0
+ }
diff --git a/src/contexts/EditorContext.ts b/src/contexts/EditorContext.ts
new file mode 100644
index 0000000..9a30ada
--- /dev/null
+++ b/src/contexts/EditorContext.ts
@@ -0,0 +1,12 @@
+import { createContext } from 'react';
+
+export interface EditorContextType {
+ modificationSecret: string;
+ readOnly: boolean;
+ documentId: string;
+}
+export const EditorContext = createContext({
+ modificationSecret: '',
+ readOnly: false,
+ documentId: ''
+});
diff --git a/src/contexts/UserContext.ts b/src/contexts/UserContext.ts
new file mode 100644
index 0000000..da41a78
--- /dev/null
+++ b/src/contexts/UserContext.ts
@@ -0,0 +1,11 @@
+import { createContext } from 'react';
+import { LocalDocumentUser } from '../utils/localstorage';
+
+export interface UserContextType {
+ currentUser: LocalDocumentUser | null;
+ storeUserSetting: (user: LocalDocumentUser | null) => void;
+}
+export const UserContext = createContext({
+ currentUser: null,
+ storeUserSetting: () => void {}
+});
diff --git a/src/html.d.ts b/src/html.d.ts
new file mode 100644
index 0000000..1198a9a
--- /dev/null
+++ b/src/html.d.ts
@@ -0,0 +1,4 @@
+declare module '*.html' {
+ const value: string;
+ export default value;
+}
diff --git a/src/i18n.ts b/src/i18n.ts
new file mode 100644
index 0000000..437ac90
--- /dev/null
+++ b/src/i18n.ts
@@ -0,0 +1,16 @@
+import i18n from 'i18next';
+import LanguageDetector from 'i18next-browser-languagedetector';
+import translationDE from './locales/de/translations.json';
+import translationEN from './locales/en/translations.json';
+import { initReactI18next } from 'react-i18next';
+
+void i18n
+ .use(initReactI18next)
+ .use(LanguageDetector)
+ .init({
+ resources: { de: translationDE, en: translationEN },
+ fallbackLng: 'en',
+ debug: false
+ });
+
+export default i18n;
diff --git a/src/index.css b/src/index.css
new file mode 100644
index 0000000..f342c41
--- /dev/null
+++ b/src/index.css
@@ -0,0 +1,17 @@
+@import "tailwindcss";
+
+
+@theme {
+ --font-kits: "FiraSans", "sans-serif";
+ --color-primary: var(--color-blue-700);
+ --color-secondary: var(--color-blue-500);
+ --color-neutral: var(--color-gray);
+}
+
+input {
+ @apply border-2 border-neutral-200 rounded-md p-2 me-2;
+}
+
+button {
+ @apply border-2 border-neutral-200 rounded-md p-2 cursor-pointer;
+}
diff --git a/src/locales/de/translations.json b/src/locales/de/translations.json
new file mode 100644
index 0000000..333f7a1
--- /dev/null
+++ b/src/locales/de/translations.json
@@ -0,0 +1,118 @@
+{
+ "translation": {
+ "menuBar": {
+ "buttons": {
+ "new": "Neues Dokument",
+ "changeUsername": "Benutzername ändern",
+ "share": "Dokument teilen",
+ "about": "Info",
+ "download": {
+ "title": "Download",
+ "html": "HTML",
+ "pdf": "PDF"
+ },
+ "import": "Importieren von HTML",
+ "imageUpload": "Bild hochladen",
+ "comment": "Kommentar",
+ "suggestion": "Vorschlag",
+ "mobileMenu": "Menü",
+ "bold": "Fett",
+ "italic": "Kursiv",
+ "underline": "Unterstreichen",
+ "strike": "Durchstreichen",
+ "bulletList": "Ungeordnete Liste",
+ "orderedList": "Geordnete Liste",
+ "undo": "Rückgängig",
+ "redo": "Wiederherstellen",
+ "quote": "Zitat",
+ "textColor": "Textfarbe",
+ "showComments": "Kommentare anzeigen",
+ "link": {
+ "title": "Link",
+ "prompt": "Link eingeben:"
+ },
+ "heading": {
+ "title": "Überschrift",
+ "paragraph": "Absatz",
+ "h1": "Überschrift 1",
+ "h2": "Überschrift 2",
+ "h3": "Überschrift 3"
+ },
+ "table": {
+ "title": "Tabelle",
+ "insertTable": "Tabelle einfügen",
+ "addColumnAfter": "Spalte hinzufügen",
+ "deleteColumn": "Spalte löschen",
+ "addRowAfter": "Zeile hinzufügen",
+ "deleteRow": "Zeile löschen"
+ }
+ }
+ },
+ "editor": {
+ "placeholder": "Bitte Text einfügen...",
+ "defaultUsername": "Nutzer"
+ },
+ "colors": {
+ "black": "Schwarz",
+ "red": "Rot",
+ "blue": "Blau",
+ "green": "Grün",
+ "yellow": "Gelb"
+ },
+ "buttons": {
+ "close": "Schließen",
+ "save": "Speichern",
+ "abort": "Abbrechen"
+ },
+ "messages": {
+ "connectionClosed": "Verbindung geschlossen",
+ "documentIdInvalid": "Dokument ID nicht gültig"
+ },
+ "modals": {
+ "user": {
+ "username": "Benutzername"
+ },
+ "share": {
+ "title": "Teilen",
+ "readOnly": "Nur lesen",
+ "buttons": {
+ "download": "Download",
+ "copy": "Kopieren"
+ },
+ "messages": {
+ "copied": "Kopiert!"
+ }
+ },
+ "about": {
+ "title": "Info",
+ "content": "Mit GroupWriter schreibst du gemeinsam mit anderen Texte. Du kannst Kommentare und Änderungsvorschläge hinzufügen und die Texte gestalten.",
+ "linkSourceCode": "Quellcode",
+ "linkPrivacy": "Datenschutz",
+ "linkLegal": "Impressum",
+ "buttons": {
+ "delete": "Text löschen"
+ }
+ }
+ },
+ "page": {
+ "landing": {
+ "title": "GroupWriter",
+ "description": "Lass uns schreiben!",
+ "buttons": {
+ "new": "Neues Dokument"
+ },
+ "recentTexts": {
+ "title": "Letzte Texte:",
+ "item": "Text vom"
+ }
+ }
+ },
+ "commentCard": {
+ "buttons": {
+ "acceptProposal": "Vorschlag akzeptieren",
+ "edit": "Bearbeiten",
+ "delete": "Löschen"
+ }
+ }
+ }
+}
diff --git a/src/locales/en/translations.json b/src/locales/en/translations.json
new file mode 100644
index 0000000..01885c5
--- /dev/null
+++ b/src/locales/en/translations.json
@@ -0,0 +1,118 @@
+{
+ "translation": {
+ "menuBar": {
+ "buttons": {
+ "new": "New document",
+ "changeUsername": "Change username",
+ "share": "Share document",
+ "about": "Info",
+ "download": {
+ "title": "Download",
+ "html": "HTML",
+ "pdf": "PDF"
+ },
+ "import": "Import from HTML",
+ "imageUpload": "Upload image",
+ "comment": "Comment",
+ "suggestion": "Suggestion",
+ "mobileMenu": "Menu",
+ "bold": "Bold",
+ "italic": "Italic",
+ "underline": "Underline",
+ "strike": "Strike",
+ "bulletList": "Unordered list",
+ "orderedList": "Ordered list",
+ "undo": "Undo",
+ "redo": "Redo",
+ "quote": "Quote",
+ "heading": {
+ "title": "Heading",
+ "paragraph": "Paragraph",
+ "h1": "Heading 1",
+ "h2": "Heading 2",
+ "h3": "Heading 3"
+ },
+ "table": {
+ "title": "Table",
+ "insertTable": "Insert table",
+ "addColumnAfter": "Add column",
+ "deleteColumn": "Delete column",
+ "addRowAfter": "Add row",
+ "deleteRow": "Delete row"
+ },
+ "textColor": "Text color",
+ "showComments": "Show comments",
+ "link": {
+ "title": "Link",
+ "prompt": "Enter link"
+ }
+ }
+ },
+ "editor": {
+ "placeholder": "Please insert text...",
+ "defaultUsername": "User"
+ },
+ "colors": {
+ "black": "Black",
+ "red": "Red",
+ "blue": "Blue",
+ "green": "Green",
+ "yellow": "Yellow"
+ },
+ "buttons": {
+ "close": "Close",
+ "save": "Save",
+ "abort": "Cancel"
+ },
+ "messages": {
+ "connectionClosed": "Connection closed",
+ "documentIdInvalid": "Document ID invalid"
+ },
+ "modals": {
+ "user": {
+ "username": "Username"
+ },
+ "share": {
+ "title": "Share",
+ "readOnly": "Read only",
+ "buttons": {
+ "download": "Download",
+ "copy": "Copy"
+ },
+ "messages": {
+ "copied": "Copied!"
+ }
+ },
+ "about": {
+ "title": "About",
+ "linkSourceCode": "Source code",
+ "linkPrivacy": "Privacy policy",
+ "linkLegal": "Legal",
+ "content": "GroupWriter is a collaboration tool for teams to write and edit documents together.",
+ "buttons": {
+ "delete": "Delete text"
+ }
+ }
+ },
+ "page": {
+ "landing": {
+ "title": "GroupWriter",
+ "description": "Let's write!",
+ "buttons": {
+ "new": "New document"
+ },
+ "recentTexts": {
+ "title": "Recent texts:",
+ "item": "Text from"
+ }
+ }
+ },
+ "commentCard": {
+ "buttons": {
+ "acceptProposal": "Accept proposal",
+ "edit": "Edit",
+ "delete": "Delete"
+ }
+ }
+ }
+}
diff --git a/src/main.tsx b/src/main.tsx
new file mode 100644
index 0000000..07df01c
--- /dev/null
+++ b/src/main.tsx
@@ -0,0 +1,23 @@
+import { StrictMode } from 'react';
+import { createRoot } from 'react-dom/client';
+import App from './App';
+import './index.css';
+import { BrowserRouter, Navigate, Route, Routes } from 'react-router';
+import './i18n';
+import LandingPage from './pages/LandingPage';
+
+const root = document.getElementById('root');
+
+if (root) {
+ createRoot(root).render(
+
+
+
+ } />
+ } />
+ } />
+
+
+
+ );
+}
diff --git a/src/pages/DocumentPage.test.tsx b/src/pages/DocumentPage.test.tsx
new file mode 100644
index 0000000..819fed9
--- /dev/null
+++ b/src/pages/DocumentPage.test.tsx
@@ -0,0 +1,81 @@
+import { describe, it, expect } from 'vitest';
+import { render } from '@testing-library/react';
+import DocumentPage from './DocumentPage';
+import { randomUUID } from 'crypto';
+import { MemoryRouter, Route, Routes } from 'react-router';
+import { I18nextProvider } from 'react-i18next';
+import i18n from '../i18n';
+
+describe('DocumentPage', () => {
+ it('editor is enabled with modificationSecret', () => {
+ vi.spyOn(window, 'location', 'get').mockReturnValue({
+ ...window.location,
+ hash: randomUUID()
+ });
+
+ const { container } = render(
+
+
+
+
+
+ );
+
+ const element = container.querySelector('.tiptap');
+ expect(element?.getAttribute('contenteditable')).toBeTruthy();
+ });
+
+ it('editor is disabled without modificationSecret', () => {
+ const { container } = render(
+
+
+
+
+
+ );
+
+ const element = container.querySelector('.tiptap');
+ expect(element?.getAttribute('contenteditable')).toEqual('false');
+ });
+
+ it('navigates to / when the documentId is invalid', () => {
+ const { container } = render(
+
+
+
+ }
+ />
+ Landing Page} />
+
+
+
+ );
+
+ expect(container.textContent).toBe('Landing Page');
+ });
+
+ it('stays on the document page when the documentId is valid', () => {
+ const uuid = randomUUID();
+ const validLocation = `/document/${uuid}`;
+
+ const { container } = render(
+
+
+
+ }
+ />
+ Landing Page} />
+
+
+
+ );
+
+ expect(container.textContent).not.toBe('Landing Page');
+ const element = container.querySelector('.tiptap');
+ expect(element).toBeVisible();
+ });
+});
diff --git a/src/pages/DocumentPage.tsx b/src/pages/DocumentPage.tsx
new file mode 100644
index 0000000..3eb23a0
--- /dev/null
+++ b/src/pages/DocumentPage.tsx
@@ -0,0 +1,94 @@
+import React, { useEffect, useState } from 'react';
+import Tiptap from '../components/editor/Tiptap';
+import { Navigate } from 'react-router';
+import { UserContext } from '../contexts/UserContext';
+import { v4 as uuidv4, validate } from 'uuid';
+import { generateRandomAwarenessColor } from '../utils/editorSetup';
+import { EditorContext } from '../contexts/EditorContext';
+import {
+ getLocalUserSetting,
+ LocalDocumentUser,
+ mergeLocalDocument,
+ storeLocalUserSetting
+} from '../utils/localstorage';
+import { useTranslation } from 'react-i18next';
+
+function DocumentPage({ documentId }: { documentId: string | undefined }) {
+ const { t } = useTranslation();
+ const [currentUser, setCurrentUser] = useState(
+ null
+ );
+
+ const storeUserSetting = (user: LocalDocumentUser | null) => {
+ if (!user) return;
+
+ setCurrentUser(user);
+ storeLocalUserSetting(user);
+ };
+
+ useEffect(() => {
+ if (!documentId) return;
+
+ const userSetting = getLocalUserSetting(documentId);
+ if (!userSetting) {
+ const newUserSetting = {
+ userId: uuidv4(),
+ name: t('editor.defaultUsername'),
+ colorId: generateRandomAwarenessColor().id,
+ documentId
+ };
+ storeUserSetting(newUserSetting);
+ } else {
+ setCurrentUser(userSetting);
+ }
+ }, [documentId]);
+
+ const modificationSecret = window.location.hash.substring(1);
+ const readOnly = modificationSecret === '';
+
+ if (documentId && validate(documentId)) {
+ if (!currentUser) {
+ return <>>;
+ }
+
+ // The createdAt and updatedAt are set to the current date when the document is accessed.
+ // Currently, it does not reflect the server timestamp.
+ mergeLocalDocument({
+ id: documentId,
+ modificationSecret: readOnly ? undefined : modificationSecret,
+ lastAccessedAt: new Date().toISOString(),
+ createdAt: new Date().toISOString(),
+ updatedAt: new Date().toISOString()
+ });
+
+ return (
+
+
+
+
+
+
+
+ );
+ } else {
+ return (
+
+ );
+ }
+}
+
+export default DocumentPage;
diff --git a/src/pages/LandingPage.test.tsx b/src/pages/LandingPage.test.tsx
new file mode 100644
index 0000000..1bafb29
--- /dev/null
+++ b/src/pages/LandingPage.test.tsx
@@ -0,0 +1,16 @@
+import { describe, it, expect } from 'vitest';
+import { render, screen } from '@testing-library/react';
+import LandingPage from './LandingPage';
+import { MemoryRouter } from 'react-router';
+
+describe('Landing Page', () => {
+ it('renders correctly', () => {
+ render(
+
+
+
+ );
+ const element = screen.getByText('page.landing.title');
+ expect(element).toBeInTheDocument();
+ });
+});
diff --git a/src/pages/LandingPage.tsx b/src/pages/LandingPage.tsx
new file mode 100644
index 0000000..851ee0f
--- /dev/null
+++ b/src/pages/LandingPage.tsx
@@ -0,0 +1,89 @@
+import { createDocument } from '../utils/serverRequests';
+import { Link, useNavigate } from 'react-router';
+import { useTranslation } from 'react-i18next';
+import './landingPageStyles.scss';
+import { getLocalMostRecentThreeDocuments } from '../utils/localstorage';
+
+function LandingPage() {
+ const { t } = useTranslation();
+ const navigate = useNavigate();
+
+ const sortedDocuments = getLocalMostRecentThreeDocuments();
+
+ return (
+
+
+
+ {t('page.landing.title')}
+
+
+ {t('page.landing.description')}
+
+
+
{
+ void (async () => {
+ const link = await createDocument();
+ if (link) await navigate(link);
+ })();
+ }}
+ >
+ {t('page.landing.buttons.new')}
+
+
+
+ {t('page.landing.recentTexts.title')}
+
+
+ {Object.values(sortedDocuments).map((document) => (
+
+
+ {document?.createdAt && (
+
+ {t('page.landing.recentTexts.item')}{' '}
+ {new Date(document?.createdAt).toLocaleString()}
+
+ )}
+
+
+ ))}
+ {Object.values(sortedDocuments).length === 0 && (
+ -
+ )}
+
+
+
+
+
+ );
+}
+
+export default LandingPage;
diff --git a/src/pages/landingPageStyles.scss b/src/pages/landingPageStyles.scss
new file mode 100644
index 0000000..8929805
--- /dev/null
+++ b/src/pages/landingPageStyles.scss
@@ -0,0 +1,20 @@
+@reference "tailwindcss";
+
+@keyframes caret-blink {
+ 0% {
+ opacity: 0;
+ }
+}
+
+.background {
+ background-image: url('../assets/pexels-thepaintedsquare-998591.jpg');
+ background-size: cover;
+ background-position: center;
+}
+
+.landing-box h1::after {
+ @apply h-8 w-1 inline-block ml-2;
+ content: "";
+ background: var(--color-primary);
+ animation: caret-blink 2s steps(2) infinite;
+}
\ No newline at end of file
diff --git a/src/utils/editorExport.ts b/src/utils/editorExport.ts
new file mode 100644
index 0000000..4551e89
--- /dev/null
+++ b/src/utils/editorExport.ts
@@ -0,0 +1,56 @@
+import { Editor } from '@tiptap/core';
+import jsPDF from 'jspdf';
+import exportHTML from '../assets/exportTemplate.html?raw';
+import exportHTMLPdf from '../assets/exportTemplatePdf.html?raw';
+import { commentRemoveRegex } from '@packages/tiptap-extension-comment-collaboration';
+export const setEditorContentFromFile = async (
+ editor: Editor,
+ file: File | undefined
+) => {
+ if (editor && file) {
+ editor.commands.setContent(
+ (await file.text()).replace(commentRemoveRegex, '$1')
+ );
+ }
+};
+
+export const exportedHTMLLink = (editor: Editor): string => {
+ if (!editor) return '';
+
+ const parser = new DOMParser();
+ const template = parser.parseFromString(exportHTML, 'text/html');
+ const htmlWithoutComments = editor
+ .getHTML()
+ .replace(commentRemoveRegex, '$1');
+ template.body.innerHTML = htmlWithoutComments;
+
+ const blob = new Blob([template.documentElement.outerHTML], {
+ type: 'text/html'
+ });
+ return URL.createObjectURL(blob);
+};
+
+export const exportedPDFLink = async (editor: Editor): Promise => {
+ if (!editor) return '';
+
+ const parser = new DOMParser();
+ // HTML2Canvas is buggy, therefore we need hacky workarounds and thus a different template
+ const template = parser.parseFromString(exportHTMLPdf, 'text/html');
+
+ const htmlWithoutComments = editor
+ .getHTML()
+ .replace(commentRemoveRegex, '$1');
+
+ const pdfDoc = new jsPDF('p', 'px', 'letter');
+ template.body = parser.parseFromString(htmlWithoutComments, 'text/html').body;
+
+ // Note: Adding the wrapping div directly in the template does not work
+ const wrappedHTML = `${template.documentElement.outerHTML}
`;
+
+ await pdfDoc.html(wrappedHTML, {
+ margin: 10
+ });
+
+ const blob = new Blob([pdfDoc.output('blob')], { type: 'application/pdf' });
+ return URL.createObjectURL(blob);
+};
diff --git a/src/utils/editorSetup.ts b/src/utils/editorSetup.ts
new file mode 100644
index 0000000..720bba1
--- /dev/null
+++ b/src/utils/editorSetup.ts
@@ -0,0 +1,158 @@
+import { HocuspocusProvider } from '@hocuspocus/provider';
+import * as Y from 'yjs';
+import {
+ awarenessColors,
+ ColorAwarenessInfo,
+ getAwarenessColor
+} from './userColors';
+import CollaborationCommentsExtension, {
+ CommentItem,
+ MarkWithPos
+} from '@packages/tiptap-extension-comment-collaboration';
+import { LocalDocumentUser } from './localstorage';
+import StarterKit from '@tiptap/starter-kit';
+import Image from '@tiptap/extension-image';
+import ImageDeleteCallback from '@packages/tiptap-extension-image-delete-callback';
+import TextStyle from '@tiptap/extension-text-style';
+import Collaboration from '@tiptap/extension-collaboration';
+import CollaborationCursor from '@tiptap/extension-collaboration-cursor';
+import Placeholder from '@tiptap/extension-placeholder';
+import { deleteImage } from './serverRequests';
+import ColorWithClasses from '@packages/tiptap-extension-color-with-classes';
+import { TFunction } from 'i18next';
+import Underline from '@tiptap/extension-underline';
+import Table from '@tiptap/extension-table';
+import TableCell from '@tiptap/extension-table-cell';
+import TableHeader from '@tiptap/extension-table-header';
+import TableRow from '@tiptap/extension-table-row';
+import Link from '@tiptap/extension-link';
+
+// Create server url for a host using the subdomain groupwriter.host.tld for the editor.
+const createServerUrl = (targetSubdomain: string, postFix?: string): string => {
+ const hostArray = window.location.host.split('.');
+ const protocol = `${window.location.protocol}//`;
+ if (hostArray.length === 1) {
+ console.warn('localhost does not work for URL_PART_NAME variables');
+ }
+
+ const subdomain = hostArray[0].replace('write', targetSubdomain);
+
+ return `${protocol}${subdomain}.${hostArray
+ .slice(1, hostArray.length)
+ .join('.')}${postFix ? postFix : ''}`;
+};
+
+export const serverUrl = (): string => {
+ return import.meta.env.VITE_HOCUSPOCUS_SUBDOMAIN
+ ? createServerUrl(import.meta.env.VITE_HOCUSPOCUS_SUBDOMAIN)
+ : import.meta.env.VITE_HOCUSPOCUS_SERVER_URL;
+};
+
+export const createProvider = (
+ documentId: string,
+ ydoc: Y.Doc,
+ modificationSecret: string
+) => {
+ return new HocuspocusProvider({
+ url: serverUrl(),
+ name: documentId,
+ document: ydoc,
+ token: modificationSecret
+ });
+};
+
+export const createExtensions = (
+ ydoc: Y.Doc,
+ t: TFunction,
+ provider: HocuspocusProvider,
+ modificationSecret: string,
+ onCommentsPosUpdated: (marks: Record) => void,
+ onCommentsDataUpdated: (comments: Y.Map | null) => void,
+ onCommentActivated: (commentId: string) => void,
+ user: LocalDocumentUser | null
+) => [
+ Link,
+ TextStyle,
+ ColorWithClasses.configure({ types: [TextStyle.name] }),
+ Placeholder.configure({
+ placeholder: t('editor.placeholder')
+ }),
+ Image,
+ Table,
+ TableRow,
+ TableHeader,
+ TableCell,
+ Underline,
+ ImageDeleteCallback.configure({
+ url: serverUrl(),
+ deleteCallback: (url: string) => void deleteImage(url, modificationSecret)
+ }),
+ StarterKit.configure({
+ history: false,
+ bulletList: {
+ keepMarks: true,
+ keepAttributes: true
+ },
+ orderedList: {
+ keepMarks: true,
+ keepAttributes: true
+ }
+ }),
+ CollaborationCommentsExtension.configure({
+ document: ydoc,
+ onCommentsPosUpdated,
+ onCommentsDataUpdated,
+ onCommentActivated
+ }),
+ Collaboration.configure({
+ document: ydoc
+ }),
+ CollaborationCursor.configure({
+ provider,
+ selectionRender: selectionRender,
+ render: cursorRender,
+ user: { ...user }
+ })
+];
+
+const selectionRender = (user: LocalDocumentUser) => {
+ const colorAwarenessInfo = getAwarenessColor(user.colorId);
+ return {
+ nodeName: 'span',
+ class: `collaboration-cursor__selection ${colorAwarenessInfo?.bgSelectionClass}`,
+ 'data-user': user.name
+ };
+};
+
+const cursorRender = (user: LocalDocumentUser) => {
+ const cursor = document.createElement('span');
+ cursor.classList.add('collaboration-cursor__caret');
+
+ const label = document.createElement('div');
+ label.classList.add('collaboration-cursor__label');
+ label.classList.add(getAwarenessColor(user.colorId)?.bgClass ?? '');
+ label.insertBefore(document.createTextNode(user.name), null);
+ cursor.insertBefore(label, null);
+ return cursor;
+};
+
+export const generateRandomAwarenessColor = (): ColorAwarenessInfo => {
+ const randomIndex = Math.floor(Math.random() * awarenessColors.length);
+ return awarenessColors[randomIndex];
+};
+
+export const getInitials = (name: string): string => {
+ const nameParts = name.split(' ');
+ return nameParts.map((part) => part[0].toUpperCase()).join('');
+};
+
+export const debounce = (fn: (...args: unknown[]) => void, timeout = 300) => {
+ let timer: NodeJS.Timeout;
+
+ return function (...args: unknown[]) {
+ clearTimeout(timer);
+ timer = setTimeout(() => {
+ fn(...args);
+ }, timeout);
+ };
+};
diff --git a/src/utils/localstorage.ts b/src/utils/localstorage.ts
new file mode 100644
index 0000000..e2ae9f4
--- /dev/null
+++ b/src/utils/localstorage.ts
@@ -0,0 +1,88 @@
+export interface LocalDocument {
+ id: string;
+ modificationSecret?: string;
+ createdAt?: string;
+ updatedAt?: string;
+ lastAccessedAt: string;
+}
+
+export interface LocalDocumentUser {
+ userId: string;
+ documentId: string;
+ name: string;
+ colorId: string;
+}
+
+export const storeLocalUserSetting = (userEntry: LocalDocumentUser): void => {
+ const newDocumentsUsersSettings = JSON.parse(
+ localStorage.getItem('documentsUsersSettings') ?? '{}'
+ ) as Record;
+ newDocumentsUsersSettings[userEntry.documentId] = userEntry;
+ localStorage.setItem(
+ 'documentsUsersSettings',
+ JSON.stringify(newDocumentsUsersSettings)
+ );
+};
+
+export const getLocalUserSetting = (
+ documentId: string
+): LocalDocumentUser | undefined => {
+ const documentsUsersSettings = JSON.parse(
+ localStorage.getItem('documentsUsersSettings') ?? '{}'
+ ) as Record;
+ return documentsUsersSettings[documentId];
+};
+
+export const storeLocalDocument = (document: LocalDocument): void => {
+ const newDocuments = JSON.parse(
+ localStorage.getItem('documents') ?? '{}'
+ ) as Record;
+ newDocuments[document.id] = document;
+ localStorage.setItem('documents', JSON.stringify(newDocuments));
+};
+
+export const mergeLocalDocument = (document: LocalDocument): void => {
+ const newDocuments = getLocalDocuments();
+ const existingDocument = newDocuments?.[document.id];
+ newDocuments[document.id] = { ...(existingDocument ?? {}), ...document };
+ localStorage.setItem('documents', JSON.stringify(newDocuments));
+};
+
+export const getLocalDocuments = (): Record => {
+ return JSON.parse(localStorage.getItem('documents') ?? '{}') as Record<
+ string,
+ LocalDocument
+ >;
+};
+
+export const getLocalUserSettings = (): Record => {
+ return JSON.parse(
+ localStorage.getItem('documentsUsersSettings') ?? '{}'
+ ) as Record;
+};
+
+export const getLocalMostRecentThreeDocuments = (): LocalDocument[] => {
+ const documents = getLocalDocuments();
+
+ return Object.values(documents)
+ .sort(
+ (a, b) =>
+ new Date(b?.lastAccessedAt).getTime() -
+ new Date(a?.lastAccessedAt).getTime()
+ )
+ .slice(0, 3);
+};
+
+export const deleteLocalDocument = (documentId: string) => {
+ const updatedDocuments = getLocalDocuments();
+ delete updatedDocuments[documentId];
+
+ const updatedUserSettings = getLocalUserSettings();
+ delete updatedUserSettings[documentId];
+
+ localStorage.setItem('documents', JSON.stringify(updatedDocuments));
+ localStorage.setItem(
+ 'documentsUsersSettings',
+ JSON.stringify(updatedUserSettings)
+ );
+};
diff --git a/src/utils/serverRequests.ts b/src/utils/serverRequests.ts
new file mode 100644
index 0000000..3d6779e
--- /dev/null
+++ b/src/utils/serverRequests.ts
@@ -0,0 +1,90 @@
+import { serverUrl } from './editorSetup';
+import { storeLocalDocument } from './localstorage';
+import { LocalDocument } from './localstorage';
+
+export const deleteDocument = async (
+ documentId: string,
+ modificationSecret: string
+): Promise => {
+ const response = await fetch(`${serverUrl()}/documents/${documentId}`, {
+ method: 'DELETE',
+ headers: {
+ Authorization: modificationSecret
+ }
+ });
+
+ if (!response.ok) {
+ console.error(`HTTP error! status: ${response.status}`);
+ }
+};
+
+export const createDocument = async (): Promise => {
+ try {
+ const response = await fetch(`${serverUrl()}/documents`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ }
+ });
+
+ if (!response.ok) {
+ console.error(`HTTP error! status: ${response.status}`);
+ } else {
+ const document = (await response.json()) as LocalDocument;
+ storeLocalDocument(document);
+ return `/document/${document.id}#${document.modificationSecret}`;
+ }
+ } catch (error) {
+ console.error('Error posting data:', error);
+ }
+ return null;
+};
+
+export const uploadImage = async (
+ file: File,
+ documentId: string,
+ modificationSecret: string
+): Promise => {
+ try {
+ const formData = new FormData();
+ formData.append('file', file, file.name);
+ const response = await fetch(
+ `${serverUrl()}/documents/${documentId}/images`,
+ {
+ method: 'POST',
+ headers: {
+ Authorization: modificationSecret
+ },
+ body: formData
+ }
+ );
+
+ if (response.ok) {
+ const json = (await response.json()) as { imageUrl: string };
+ return json.imageUrl;
+ } else {
+ console.error(
+ `HTTP error while uploading image! Status: ${response.status}`
+ );
+ }
+ } catch (error) {
+ console.error('Error uploading image:', error);
+ }
+ return null;
+};
+
+export const deleteImage = async (
+ imageUrl: string,
+ modificationSecret: string
+): Promise => {
+ const response = await fetch(imageUrl, {
+ method: 'DELETE',
+ headers: {
+ Authorization: modificationSecret
+ }
+ });
+
+ if (!response.ok) {
+ console.error(`HTTP error! status: ${response.status}`);
+ }
+};
diff --git a/src/utils/userColors.ts b/src/utils/userColors.ts
new file mode 100644
index 0000000..d88014b
--- /dev/null
+++ b/src/utils/userColors.ts
@@ -0,0 +1,120 @@
+// For tailwind to work properly, the color classes shouldnt be constructed dynamically
+
+export interface ColorAwarenessInfo {
+ id: string;
+ bgClass: string;
+ textClass: string;
+ bgSelectionClass: string;
+}
+
+export const getAwarenessColor = (
+ colorId: string
+): ColorAwarenessInfo | undefined => {
+ return awarenessColors.find((color) => color.id === colorId);
+};
+
+// https://tailwindcss.com/docs/content-configuration#dynamic-class-names
+export const awarenessColors: ColorAwarenessInfo[] = [
+ {
+ id: 'red',
+ bgClass: 'bg-red-300',
+ textClass: 'text-red-300',
+ bgSelectionClass: 'bg-red-200'
+ },
+ {
+ id: 'orange',
+ bgClass: 'bg-orange-300',
+ textClass: 'text-orange-300',
+ bgSelectionClass: 'bg-orange-200'
+ },
+ {
+ id: 'amber',
+ bgClass: 'bg-amber-300',
+ textClass: 'text-amber-300',
+ bgSelectionClass: 'bg-amber-200'
+ },
+ {
+ id: 'yellow',
+ bgClass: 'bg-yellow-300',
+ textClass: 'text-yellow-300',
+ bgSelectionClass: 'bg-yellow-200'
+ },
+ {
+ id: 'lime',
+ bgClass: 'bg-lime-300',
+ textClass: 'text-lime-300',
+ bgSelectionClass: 'bg-lime-200'
+ },
+ {
+ id: 'green',
+ bgClass: 'bg-green-300',
+ textClass: 'text-green-300',
+ bgSelectionClass: 'bg-green-200'
+ },
+ {
+ id: 'emerald',
+ bgClass: 'bg-emerald-300',
+ textClass: 'text-emerald-300',
+ bgSelectionClass: 'bg-emerald-200'
+ },
+ {
+ id: 'teal',
+ bgClass: 'bg-teal-300',
+ textClass: 'text-teal-300',
+ bgSelectionClass: 'bg-teal-200'
+ },
+ {
+ id: 'cyan',
+ bgClass: 'bg-cyan-300',
+ textClass: 'text-cyan-300',
+ bgSelectionClass: 'bg-cyan-200'
+ },
+ {
+ id: 'sky',
+ bgClass: 'bg-sky-300',
+ textClass: 'text-sky-300',
+ bgSelectionClass: 'bg-sky-200'
+ },
+ {
+ id: 'blue',
+ bgClass: 'bg-blue-300',
+ textClass: 'text-blue-300',
+ bgSelectionClass: 'bg-blue-200'
+ },
+ {
+ id: 'indigo',
+ bgClass: 'bg-indigo-300',
+ textClass: 'text-indigo-300',
+ bgSelectionClass: 'bg-indigo-200'
+ },
+ {
+ id: 'violet',
+ bgClass: 'bg-violet-300',
+ textClass: 'text-violet-300',
+ bgSelectionClass: 'bg-violet-200'
+ },
+ {
+ id: 'purple',
+ bgClass: 'bg-purple-300',
+ textClass: 'text-purple-300',
+ bgSelectionClass: 'bg-purple-200'
+ },
+ {
+ id: 'fuchsia',
+ bgClass: 'bg-fuchsia-300',
+ textClass: 'text-fuchsia-300',
+ bgSelectionClass: 'bg-fuchsia-200'
+ },
+ {
+ id: 'pink',
+ bgClass: 'bg-pink-300',
+ textClass: 'text-pink-300',
+ bgSelectionClass: 'bg-pink-200'
+ },
+ {
+ id: 'rose',
+ bgClass: 'bg-rose-300',
+ textClass: 'text-rose-300',
+ bgSelectionClass: 'bg-rose-200'
+ }
+];
diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts
new file mode 100644
index 0000000..2e3ea36
--- /dev/null
+++ b/src/vite-env.d.ts
@@ -0,0 +1,8 @@
+///
+
+interface ImportMetaEnv {
+ VITE_HOCUSPOCUS_SERVER_URL: string;
+ VITE_HOCUSPOCUS_SUBDOMAIN: string;
+ VITE_LEGAL_URL: string | undefined;
+ VITE_PRIVACY_STATEMENT_URL: string | undefined;
+}
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..9849b47
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,25 @@
+{
+ "compilerOptions": {
+ "module": "ESNext",
+ "esModuleInterop": true,
+ "target": "ESNext",
+ "lib": [
+ "esnext",
+ "dom"
+ ],
+ "moduleResolution": "Bundler",
+ "sourceMap": false,
+ "outDir": "dist",
+ "jsx": "react-jsx",
+ "skipLibCheck": true,
+ "strictNullChecks": true,
+ "types": [
+ "vitest/globals",
+ "@testing-library/jest-dom"
+ ]
+ },
+ "exclude": [
+ "node_modules",
+ "dist"
+ ]
+}
\ No newline at end of file
diff --git a/vite.config.ts b/vite.config.ts
new file mode 100644
index 0000000..5de4ec7
--- /dev/null
+++ b/vite.config.ts
@@ -0,0 +1,19 @@
+import { defineConfig } from 'vite'
+import react from '@vitejs/plugin-react'
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ plugins: [react()],
+ build: {
+ assetsInlineLimit: 0
+ },
+ server: {
+ proxy: {
+ '/backend': {
+ target: 'ws://backend:3000',
+ ws: true,
+ rewrite: path => path.replace(/^\/backend/, '')
+ }
+ }
+ }
+})
diff --git a/vitest.config.ts b/vitest.config.ts
new file mode 100644
index 0000000..9806e05
--- /dev/null
+++ b/vitest.config.ts
@@ -0,0 +1,13 @@
+import { defineConfig } from 'vitest/config'
+import react from '@vitejs/plugin-react'
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ plugins: [react()],
+ test: {
+ globals: true,
+ environment: 'jsdom',
+ setupFiles: './vitest.setup.ts',
+ restoreMocks: true
+ },
+})
diff --git a/vitest.setup.ts b/vitest.setup.ts
new file mode 100644
index 0000000..f533ae7
--- /dev/null
+++ b/vitest.setup.ts
@@ -0,0 +1,9 @@
+import { expect, afterEach } from 'vitest'
+import { cleanup } from '@testing-library/react'
+import * as matchers from '@testing-library/jest-dom/matchers'
+
+expect.extend(matchers)
+
+afterEach(() => {
+ cleanup()
+})