diff --git a/README.md b/README.md index b34e29706..76a3feb2e 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ - **Filament Cost Tracking** - Track costs per print with customizable filament database - **Photo Attachments** - Attach photos to archived prints for documentation - **Failure Analysis** - Document failed prints with notes and photos +- **Project Page Editor** - View and edit embedded MakerWorld project pages with images, descriptions, and designer info - **Cloud Profiles Sync** - Access your Bambu Cloud slicer presets - **File Manager** - Browse and manage files on your printer's SD card - **Re-print** - Send archived prints back to any connected printer @@ -309,6 +310,15 @@ Prints are automatically archived when they complete. You can also: - Re-print any archived 3MF to a connected printer - Export archives for backup +### Project Page + +3MF files downloaded from MakerWorld contain embedded project pages with model information. To view: +1. Right-click any archive in the Archives page +2. Select "Project Page" from the context menu +3. View title, description, designer info, license, and images +4. Click "Edit" to modify the project page metadata +5. Changes are saved directly to the 3MF file + ## Tech Stack - **Backend**: Python / FastAPI diff --git a/backend/app/api/routes/archives.py b/backend/app/api/routes/archives.py index a264767bb..bba693a3d 100644 --- a/backend/app/api/routes/archives.py +++ b/backend/app/api/routes/archives.py @@ -888,3 +888,89 @@ async def reprint_archive( "archive_id": archive_id, "filename": archive.filename, } + + +# ============================================================================= +# Project Page API +# ============================================================================= + +@router.get("/{archive_id}/project-page") +async def get_project_page(archive_id: int, db: AsyncSession = Depends(get_db)): + """Get the project page data from the 3MF file.""" + from backend.app.services.archive import ProjectPageParser + from backend.app.schemas.archive import ProjectPageResponse + + service = ArchiveService(db) + archive = await service.get_archive(archive_id) + if not archive: + raise HTTPException(404, "Archive not found") + + file_path = settings.base_dir / archive.file_path + if not file_path.exists(): + raise HTTPException(404, "Archive file not found") + + parser = ProjectPageParser(file_path) + data = parser.parse(archive_id) + + return ProjectPageResponse(**data) + + +@router.patch("/{archive_id}/project-page") +async def update_project_page( + archive_id: int, + update_data: dict, + db: AsyncSession = Depends(get_db), +): + """Update project page metadata in the 3MF file.""" + from backend.app.services.archive import ProjectPageParser + + service = ArchiveService(db) + archive = await service.get_archive(archive_id) + if not archive: + raise HTTPException(404, "Archive not found") + + file_path = settings.base_dir / archive.file_path + if not file_path.exists(): + raise HTTPException(404, "Archive file not found") + + parser = ProjectPageParser(file_path) + success = parser.update_metadata(update_data) + + if not success: + raise HTTPException(500, "Failed to update project page") + + # Return updated data + data = parser.parse(archive_id) + return data + + +@router.get("/{archive_id}/project-image/{image_path:path}") +async def get_project_image( + archive_id: int, + image_path: str, + db: AsyncSession = Depends(get_db), +): + """Get an image from the 3MF project page.""" + from backend.app.services.archive import ProjectPageParser + + service = ArchiveService(db) + archive = await service.get_archive(archive_id) + if not archive: + raise HTTPException(404, "Archive not found") + + file_path = settings.base_dir / archive.file_path + if not file_path.exists(): + raise HTTPException(404, "Archive file not found") + + parser = ProjectPageParser(file_path) + result = parser.get_image(image_path) + + if not result: + raise HTTPException(404, "Image not found in 3MF file") + + image_data, content_type = result + return Response( + content=image_data, + media_type=content_type, + headers={"Cache-Control": "max-age=3600"}, + ) diff --git a/backend/app/schemas/archive.py b/backend/app/schemas/archive.py index e78608d82..1d4df01e7 100644 --- a/backend/app/schemas/archive.py +++ b/backend/app/schemas/archive.py @@ -65,3 +65,52 @@ class ArchiveStats(BaseModel): total_cost: float prints_by_filament_type: dict prints_by_printer: dict + + +class ProjectPageImage(BaseModel): + """Image embedded in 3MF project page.""" + name: str + path: str # Path within 3MF + url: str # API URL to fetch image + + +class ProjectPageResponse(BaseModel): + """Project page data extracted from 3MF file.""" + # Model info + title: str | None = None + description: str | None = None # HTML content + designer: str | None = None + designer_user_id: str | None = None + license: str | None = None + copyright: str | None = None + creation_date: str | None = None + modification_date: str | None = None + origin: str | None = None # "original" or "remix" + + # Profile info + profile_title: str | None = None + profile_description: str | None = None + profile_cover: str | None = None + profile_user_id: str | None = None + profile_user_name: str | None = None + + # MakerWorld info + design_model_id: str | None = None + design_profile_id: str | None = None + design_region: str | None = None + + # Images + model_pictures: list[ProjectPageImage] = [] + profile_pictures: list[ProjectPageImage] = [] + thumbnails: list[ProjectPageImage] = [] + + +class ProjectPageUpdate(BaseModel): + """Update project page data in 3MF file.""" + title: str | None = None + description: str | None = None + designer: str | None = None + license: str | None = None + copyright: str | None = None + profile_title: str | None = None + profile_description: str | None = None diff --git a/backend/app/services/archive.py b/backend/app/services/archive.py index 782188f0a..7972276b1 100644 --- a/backend/app/services/archive.py +++ b/backend/app/services/archive.py @@ -264,6 +264,206 @@ class ThreeMFParser: break +class ProjectPageParser: + """Parser for extracting project page data from Bambu Lab 3MF files.""" + + def __init__(self, file_path: Path): + self.file_path = file_path + + def parse(self, archive_id: int) -> dict: + """Extract project page metadata and images from 3MF file.""" + import html + import re + + result = { + "title": None, + "description": None, + "designer": None, + "designer_user_id": None, + "license": None, + "copyright": None, + "creation_date": None, + "modification_date": None, + "origin": None, + "profile_title": None, + "profile_description": None, + "profile_cover": None, + "profile_user_id": None, + "profile_user_name": None, + "design_model_id": None, + "design_profile_id": None, + "design_region": None, + "model_pictures": [], + "profile_pictures": [], + "thumbnails": [], + } + + try: + with zipfile.ZipFile(self.file_path, "r") as zf: + # Parse 3D/3dmodel.model for metadata + model_path = "3D/3dmodel.model" + if model_path in zf.namelist(): + content = zf.read(model_path).decode("utf-8", errors="ignore") + + # Extract metadata elements using regex + # Format: Value or + metadata_pattern = r']*>([^<]*)' + matches = re.findall(metadata_pattern, content) + + field_mapping = { + "Title": "title", + "Description": "description", + "Designer": "designer", + "DesignerUserId": "designer_user_id", + "License": "license", + "Copyright": "copyright", + "CreationDate": "creation_date", + "ModificationDate": "modification_date", + "Origin": "origin", + "ProfileTitle": "profile_title", + "ProfileDescription": "profile_description", + "ProfileCover": "profile_cover", + "ProfileUserId": "profile_user_id", + "ProfileUserName": "profile_user_name", + "DesignModelId": "design_model_id", + "DesignProfileId": "design_profile_id", + "DesignRegion": "design_region", + } + + for name, value in matches: + if name in field_mapping: + # Decode HTML entities multiple times (content is often triple-encoded) + decoded = value.strip() + prev = None + while prev != decoded: + prev = decoded + decoded = html.unescape(decoded) + # Normalize non-breaking spaces to regular spaces + decoded = decoded.replace('\xa0', ' ') + result[field_mapping[name]] = decoded if decoded else None + + # List images in Auxiliaries folder + from urllib.parse import quote + for name in zf.namelist(): + if name.startswith("Auxiliaries/Model Pictures/"): + filename = name.split("/")[-1] + if filename: + result["model_pictures"].append({ + "name": filename, + "path": name, + "url": f"/api/v1/archives/{archive_id}/project-image/{quote(name, safe='')}", + }) + elif name.startswith("Auxiliaries/Profile Pictures/"): + filename = name.split("/")[-1] + if filename: + result["profile_pictures"].append({ + "name": filename, + "path": name, + "url": f"/api/v1/archives/{archive_id}/project-image/{quote(name, safe='')}", + }) + elif name.startswith("Auxiliaries/.thumbnails/"): + filename = name.split("/")[-1] + if filename: + result["thumbnails"].append({ + "name": filename, + "path": name, + "url": f"/api/v1/archives/{archive_id}/project-image/{quote(name, safe='')}", + }) + + except Exception as e: + result["_error"] = str(e) + + return result + + def get_image(self, image_path: str) -> tuple[bytes, str] | None: + """Extract an image from the 3MF file. + + Returns tuple of (image_data, content_type) or None if not found. + """ + try: + with zipfile.ZipFile(self.file_path, "r") as zf: + if image_path in zf.namelist(): + data = zf.read(image_path) + # Determine content type from extension + ext = image_path.lower().split(".")[-1] + content_types = { + "png": "image/png", + "jpg": "image/jpeg", + "jpeg": "image/jpeg", + "webp": "image/webp", + "gif": "image/gif", + } + content_type = content_types.get(ext, "application/octet-stream") + return (data, content_type) + except Exception: + pass + return None + + def update_metadata(self, updates: dict) -> bool: + """Update project page metadata in the 3MF file. + + Args: + updates: Dict with fields to update (title, description, designer, etc.) + + Returns: + True if successful, False otherwise. + """ + import html + import re + import tempfile + + try: + # Read the 3MF file + with zipfile.ZipFile(self.file_path, "r") as zf_read: + # Find and read the 3dmodel.model file + model_path = "3D/3dmodel.model" + if model_path not in zf_read.namelist(): + return False + + content = zf_read.read(model_path).decode("utf-8") + + # Update metadata fields + field_mapping = { + "title": "Title", + "description": "Description", + "designer": "Designer", + "license": "License", + "copyright": "Copyright", + "profile_title": "ProfileTitle", + "profile_description": "ProfileDescription", + } + + for field, xml_name in field_mapping.items(): + if field in updates and updates[field] is not None: + new_value = html.escape(updates[field]) + # Replace existing metadata or we'd need to add it + pattern = rf'(]*>)[^<]*()' + replacement = rf'\g<1>{new_value}\g<2>' + content = re.sub(pattern, replacement, content) + + # Write to a temporary file first + with tempfile.NamedTemporaryFile(delete=False, suffix=".3mf") as tmp: + tmp_path = Path(tmp.name) + + # Create new zip with updated content + with zipfile.ZipFile(tmp_path, "w", zipfile.ZIP_DEFLATED) as zf_write: + for item in zf_read.namelist(): + if item == model_path: + zf_write.writestr(item, content.encode("utf-8")) + else: + zf_write.writestr(item, zf_read.read(item)) + + # Replace original file with updated one + shutil.move(tmp_path, self.file_path) + return True + + except Exception: + # Clean up temp file if it exists + if "tmp_path" in locals() and tmp_path.exists(): + tmp_path.unlink() + return False + + class ArchiveService: """Service for archiving print jobs.""" diff --git a/frontend/package-lock.json b/frontend/package-lock.json index bbeb58e8f..383cbdd22 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -12,6 +12,14 @@ "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", "@tanstack/react-query": "^5.90.11", + "@tiptap/extension-color": "^3.11.1", + "@tiptap/extension-image": "^3.11.1", + "@tiptap/extension-link": "^3.11.1", + "@tiptap/extension-text-align": "^3.11.1", + "@tiptap/extension-text-style": "^3.11.1", + "@tiptap/extension-underline": "^3.11.1", + "@tiptap/react": "^3.11.1", + "@tiptap/starter-kit": "^3.11.1", "@types/three": "^0.181.0", "gcode-preview": "^2.18.0", "jszip": "^3.10.1", @@ -996,6 +1004,23 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@floating-ui/core": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", + "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", + "license": "MIT", + "optional": true, + "dependencies": { + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "license": "MIT", + "optional": true + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -1134,6 +1159,12 @@ "url": "https://opencollective.com/immer" } }, + "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/@rolldown/pluginutils": { "version": "1.0.0-beta.47", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.47.tgz", @@ -1758,6 +1789,497 @@ "react": "^18 || ^19" } }, + "node_modules/@tiptap/core": { + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.11.1.tgz", + "integrity": "sha512-q7uzYrCq40JOIi6lceWe2HuA8tSr97iPwP/xtJd0bZjyL1rWhUyqxMb7y+aq4RcELrx/aNRa2JIvLtRRdy02Dg==", + "license": "MIT", + "peer": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/pm": "^3.11.1" + } + }, + "node_modules/@tiptap/extension-blockquote": { + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-3.11.1.tgz", + "integrity": "sha512-c3DN5c/9kl8w1wCcylH9XqW0OyCegqE3EL4rDlVYkyBD0GwCnUS30pN+jdxCUq/tl94lkkRk7XMyEUwzQmG+5g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.11.1" + } + }, + "node_modules/@tiptap/extension-bold": { + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-3.11.1.tgz", + "integrity": "sha512-ee/OoAPViUAgJb8dxF7D2YSSYUWcw8RXqhNSDx15w58rxpYbJbvOv3WDMrGNvl4M9nuwXYfXc3iPl/eYtwHx2w==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.11.1" + } + }, + "node_modules/@tiptap/extension-bubble-menu": { + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-3.11.1.tgz", + "integrity": "sha512-rLgU2drvoSTpdXEmoo61ZSmtRR44vMeS36OoDpUA1dNzo/vWAiOzQeLnm8gC9cD2TmvJ+WIe7tOkpAEfw4kmiQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "@floating-ui/dom": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.11.1", + "@tiptap/pm": "^3.11.1" + } + }, + "node_modules/@tiptap/extension-bullet-list": { + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-3.11.1.tgz", + "integrity": "sha512-6fj0b0Ynam8FMsP3NiCZ4a2uP7lCBHDFBXfcRwFDOqAgBIPvIK+r6CuHEGothGaF7EeQ9MTyj9fwlGjyHsPQcg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/extension-list": "^3.11.1" + } + }, + "node_modules/@tiptap/extension-code": { + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-3.11.1.tgz", + "integrity": "sha512-R3HtNPAuaKqbDwK1uWO/6QFHXbbKcxbV27XVCVtTQ4gCAzIZbJElp9REEqAOp/zI6bqt774UrAekeV+5i8NQYw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.11.1" + } + }, + "node_modules/@tiptap/extension-code-block": { + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-3.11.1.tgz", + "integrity": "sha512-Bk7mmA+m510zzLG5AMFmywrL50NlBA5p7bR0cKfdp4ckXr8FohxH3QS0Woy1MRnFUGRtIzJkSYQTJ3O/G1lBqQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.11.1", + "@tiptap/pm": "^3.11.1" + } + }, + "node_modules/@tiptap/extension-color": { + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-color/-/extension-color-3.11.1.tgz", + "integrity": "sha512-ZKK0ZbqlpmYgtRBJcSxDPtMgmPQBoQv7I7xTMF1+E4DbwSgU7HPUkGLDOcc/ezmWfZLyMKXgKmvy+54nuut4vg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/extension-text-style": "^3.11.1" + } + }, + "node_modules/@tiptap/extension-document": { + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-3.11.1.tgz", + "integrity": "sha512-Px8T7Kv8EEiFpM/h13Rro8HoynrlK8zA3u3ekHq/FBSTXnPtqPAUYNx/DUhIrLs3eWWJ8+P0Onm+sVLZmaLMug==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.11.1" + } + }, + "node_modules/@tiptap/extension-dropcursor": { + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-3.11.1.tgz", + "integrity": "sha512-+tmWD/4tg7Mt1TArrvc1Gna1FiSyru2rE6sapEerXCH3RFfaqGBeMqeRaOeZrCiqB+vIsXfthHDC/7xz5rFp/g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/extensions": "^3.11.1" + } + }, + "node_modules/@tiptap/extension-floating-menu": { + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-3.11.1.tgz", + "integrity": "sha512-HGF04KhDn1oAD+2/zStSWeGIgR41l/raf64h/Rwkvde5Sf2g3BPRW4M1ESS6e2Rjw74Kwa4/xNO6CzZNid6yRg==", + "license": "MIT", + "optional": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@floating-ui/dom": "^1.0.0", + "@tiptap/core": "^3.11.1", + "@tiptap/pm": "^3.11.1" + } + }, + "node_modules/@tiptap/extension-gapcursor": { + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-3.11.1.tgz", + "integrity": "sha512-rZ4eIFOPrLPM0bAMW560v/i9WeAz6D6PPtmFJ/Rwh7F5QFbg+jSXAyGvg7V9ZwzA5OaXqsToyJBR7qtGXBXAhQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/extensions": "^3.11.1" + } + }, + "node_modules/@tiptap/extension-hard-break": { + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-3.11.1.tgz", + "integrity": "sha512-JMp6CizdB7LoY2jmaZub2D+Aj6RJTkSu0EhIcN/bmBrm4MjYa/ir6nRoo4/gYGIHzHwgwGR/1KmlqTJZW/xl4g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.11.1" + } + }, + "node_modules/@tiptap/extension-heading": { + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-3.11.1.tgz", + "integrity": "sha512-b9ShCSQhWXNzdbdn9a3j33cq646nS0EpVyNBQr0BMOpIcMI4Ot8LGEvPo0BNqPPvpjMJaP2N6xp+EIdk6tunfQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.11.1" + } + }, + "node_modules/@tiptap/extension-horizontal-rule": { + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-3.11.1.tgz", + "integrity": "sha512-9zr6dItcJvzZtFlC+dyFb5VfWGzKzldPAOuln1d/GwKrBZds53O2vBmu4Jxfy22N9LuwiGB+2PYerq0UkLnxnA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.11.1", + "@tiptap/pm": "^3.11.1" + } + }, + "node_modules/@tiptap/extension-image": { + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-image/-/extension-image-3.11.1.tgz", + "integrity": "sha512-lI+FyyHavUXHmDKxvSAdqGAvaYtVesAxHckeA60ZjZu9fBkUnVWHD8uR0TStX7EdOIRBWpzYrG7dDT4EkFVjTA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.11.1" + } + }, + "node_modules/@tiptap/extension-italic": { + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-3.11.1.tgz", + "integrity": "sha512-SrjsU+gvhjoPKelc4YSeC2AQ0lhwLiDWMO7fW83CVitCF8iWXpBSeVCI5SxtPeZNKTZ1ZCc3lIQCeEHOC/gP0g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.11.1" + } + }, + "node_modules/@tiptap/extension-link": { + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-3.11.1.tgz", + "integrity": "sha512-ds5auQnWGcHwC2/c1iEvvybdLPcSDlxsii7FPaZg4LaSGdNojRB0qDRZw5dzYQZbfIf5vgYGcIVCVjNPZs1UwQ==", + "license": "MIT", + "dependencies": { + "linkifyjs": "^4.3.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.11.1", + "@tiptap/pm": "^3.11.1" + } + }, + "node_modules/@tiptap/extension-list": { + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-list/-/extension-list-3.11.1.tgz", + "integrity": "sha512-XJRN9pOPMi3SsaKv4qM8WBEi3YDrjXYtYlAlZutQe1JpdKykSjLwwYq7k3V8UHqR3YKxyOV8HTYOYoOaZ9TMTQ==", + "license": "MIT", + "peer": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.11.1", + "@tiptap/pm": "^3.11.1" + } + }, + "node_modules/@tiptap/extension-list-item": { + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-3.11.1.tgz", + "integrity": "sha512-KFw3TAwN6hQ+oDeE3lRqwzCRKhxU1NWf9q5SAwiUxlp/LcEjuhXcYJYX8SHPOLOlTo0P42v1i0KBeLUPKnO58g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/extension-list": "^3.11.1" + } + }, + "node_modules/@tiptap/extension-list-keymap": { + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-list-keymap/-/extension-list-keymap-3.11.1.tgz", + "integrity": "sha512-MpeuVi+bOHulbN65bOjaeoNJstNuAAEPdLwNjW25c9y2a8b5iZFY8vdVNENDiqq+dI+F5EaFGaEq0FN0uslfiA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/extension-list": "^3.11.1" + } + }, + "node_modules/@tiptap/extension-ordered-list": { + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-3.11.1.tgz", + "integrity": "sha512-Aphq0kfk6J/hNQennJ+bntvDzqRPT7RVpnow1s4U4dLBsR6PP7X4zEBg96uAv2OW0RjDHFK9NFqpJPbZtQTmFw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/extension-list": "^3.11.1" + } + }, + "node_modules/@tiptap/extension-paragraph": { + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-3.11.1.tgz", + "integrity": "sha512-a3lm1WvYewAP2IESq+qnbOtLSJ9yULY2Bj/6DvBq9fzWpb2gSlUdElYh6JLunxB1HEPECTuuRsNPdTrMsSpV4g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.11.1" + } + }, + "node_modules/@tiptap/extension-strike": { + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-3.11.1.tgz", + "integrity": "sha512-1LfkHNkrGR509cPRgcMr95+nWcAHE0JDm9LkuzdexunhCfJ2tl/h1rA14x3sic8GxQFqEnMefvBUpUbQwPydYw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.11.1" + } + }, + "node_modules/@tiptap/extension-text": { + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-3.11.1.tgz", + "integrity": "sha512-5E94ggkFAZ7OSFSwnofAsmxqmSStRoeCB8AnRuWrR+nnXi43Rq7yptdejQaLi13Z9fSVdnF6h+pB3ua2Exg6WQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.11.1" + } + }, + "node_modules/@tiptap/extension-text-align": { + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-text-align/-/extension-text-align-3.11.1.tgz", + "integrity": "sha512-lZvM8HF4qlHuXX1u0ngj1Si1zVzWhS+RiF5kczScul+F1lEQgK+ugL6iF87MSc1yxw5eZQDpA0byx1N+ZqZWZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.11.1" + } + }, + "node_modules/@tiptap/extension-text-style": { + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-text-style/-/extension-text-style-3.11.1.tgz", + "integrity": "sha512-KLLrABvf609/Z4dPChRowvpqeefYiq5csEj4Ogfp4EFd3KqDvPZIoFepau1+BW4gOAlm8UK+ig+fOLgnUzH7ww==", + "license": "MIT", + "peer": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.11.1" + } + }, + "node_modules/@tiptap/extension-underline": { + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-3.11.1.tgz", + "integrity": "sha512-Y3EJxfE1g4XSGbUZN+74o38mp3O+BQXtlqxAQvedzXiGGrdK2kWhp2b4nj3IkxHdRdoSijf+oZzgyBrRDdgC/w==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.11.1" + } + }, + "node_modules/@tiptap/extensions": { + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/@tiptap/extensions/-/extensions-3.11.1.tgz", + "integrity": "sha512-/xXJdV+EVvSQv2slvAUChb5iGVv5K0EqBqxPGAAuBHdIc4Y7Id1aaKKSiyDmqon+kjSnnQIIda9oUt+o/Z66uA==", + "license": "MIT", + "peer": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.11.1", + "@tiptap/pm": "^3.11.1" + } + }, + "node_modules/@tiptap/pm": { + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.11.1.tgz", + "integrity": "sha512-8RIUhlEoCFGsbdNb+EUdQctG1Wnd7rl4wlMLS6giO7UcZT5dVfg625eMZVrl0/kA7JBJdKLIuqNmzzQ0MxsJEw==", + "license": "MIT", + "peer": true, + "dependencies": { + "prosemirror-changeset": "^2.3.0", + "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.24.1", + "prosemirror-schema-basic": "^1.2.3", + "prosemirror-schema-list": "^1.5.0", + "prosemirror-state": "^1.4.3", + "prosemirror-tables": "^1.6.4", + "prosemirror-trailing-node": "^3.0.0", + "prosemirror-transform": "^1.10.2", + "prosemirror-view": "^1.38.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + } + }, + "node_modules/@tiptap/react": { + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/@tiptap/react/-/react-3.11.1.tgz", + "integrity": "sha512-aPInZbpSWYzJvCFXaY6EhxD+H5ITURElUmUXBoRvlAB6QrR6NIWBt68hNe8i+aDGmuvLS18g60HWK5S6K2RjWQ==", + "license": "MIT", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "fast-deep-equal": "^3.1.3", + "use-sync-external-store": "^1.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "optionalDependencies": { + "@tiptap/extension-bubble-menu": "^3.11.1", + "@tiptap/extension-floating-menu": "^3.11.1" + }, + "peerDependencies": { + "@tiptap/core": "^3.11.1", + "@tiptap/pm": "^3.11.1", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "@types/react-dom": "^17.0.0 || ^18.0.0 || ^19.0.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": "3.11.1", + "resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-3.11.1.tgz", + "integrity": "sha512-weRrhp0p5J6cMNcybYobhbOVrgym7KYIwBblJ/1M1snykg+avZawVk2M5Y7j9gM1p2zo112MCw8z8nOa9Yrwow==", + "license": "MIT", + "dependencies": { + "@tiptap/core": "^3.11.1", + "@tiptap/extension-blockquote": "^3.11.1", + "@tiptap/extension-bold": "^3.11.1", + "@tiptap/extension-bullet-list": "^3.11.1", + "@tiptap/extension-code": "^3.11.1", + "@tiptap/extension-code-block": "^3.11.1", + "@tiptap/extension-document": "^3.11.1", + "@tiptap/extension-dropcursor": "^3.11.1", + "@tiptap/extension-gapcursor": "^3.11.1", + "@tiptap/extension-hard-break": "^3.11.1", + "@tiptap/extension-heading": "^3.11.1", + "@tiptap/extension-horizontal-rule": "^3.11.1", + "@tiptap/extension-italic": "^3.11.1", + "@tiptap/extension-link": "^3.11.1", + "@tiptap/extension-list": "^3.11.1", + "@tiptap/extension-list-item": "^3.11.1", + "@tiptap/extension-list-keymap": "^3.11.1", + "@tiptap/extension-ordered-list": "^3.11.1", + "@tiptap/extension-paragraph": "^3.11.1", + "@tiptap/extension-strike": "^3.11.1", + "@tiptap/extension-text": "^3.11.1", + "@tiptap/extension-underline": "^3.11.1", + "@tiptap/extensions": "^3.11.1", + "@tiptap/pm": "^3.11.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + } + }, "node_modules/@tweenjs/tween.js": { "version": "23.1.3", "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.3.tgz", @@ -1886,6 +2408,28 @@ "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": "24.10.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", @@ -1901,7 +2445,6 @@ "version": "19.2.7", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz", "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", - "devOptional": true, "license": "MIT", "peer": true, "dependencies": { @@ -1912,8 +2455,8 @@ "version": "19.2.3", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", - "dev": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -2310,7 +2853,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, "license": "Python-2.0" }, "node_modules/autoprefixer": { @@ -2524,6 +3066,12 @@ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "license": "MIT" }, + "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", @@ -2543,7 +3091,6 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "devOptional": true, "license": "MIT" }, "node_modules/d3-array": { @@ -2729,6 +3276,18 @@ "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-toolkit": { "version": "1.42.0", "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.42.0.tgz", @@ -2795,7 +3354,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -2999,7 +3557,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, "license": "MIT" }, "node_modules/fast-json-stable-stringify": { @@ -3706,6 +4263,21 @@ "integrity": "sha512-nU8j4ND702ouGfQZoaTN4dfXxacvGOAVK0DtmZBVcUYUAeYQXLQAjAN50igMHiba3T5jZyKEjXZU+Ntm1Qs6ZQ==", "license": "MIT" }, + "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.3.2", + "resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.3.2.tgz", + "integrity": "sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==", + "license": "MIT" + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -3758,6 +4330,29 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "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/meshoptimizer": { "version": "0.22.0", "resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-0.22.0.tgz", @@ -3845,6 +4440,12 @@ "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", @@ -3990,6 +4591,204 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "license": "MIT" }, + "node_modules/prosemirror-changeset": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.3.1.tgz", + "integrity": "sha512-j0kORIBm8ayJNl3zQvD1TTPHJX3g042et6y/KQhZhnPrruO8exkTgG8X+NRpj7kIyMMEx74Xb3DyMIBtO0IKkQ==", + "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.7.1", + "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.7.1.tgz", + "integrity": "sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==", + "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.2", + "resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.2.tgz", + "integrity": "sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.1.0", + "prosemirror-view": "^1.1.0" + } + }, + "node_modules/prosemirror-gapcursor": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.4.0.tgz", + "integrity": "sha512-z00qvurSdCEWUIulij/isHaqu4uLS8r/Fi61IbjdIPJEonQgggbJsLnstW7Lgdk4zQ68/yr6B6bf7sJXowIgdQ==", + "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.5.0", + "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.5.0.tgz", + "integrity": "sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==", + "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.5.1", + "resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.5.1.tgz", + "integrity": "sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.0.0" + } + }, + "node_modules/prosemirror-keymap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.3.tgz", + "integrity": "sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.0.0", + "w3c-keyname": "^2.2.0" + } + }, + "node_modules/prosemirror-markdown": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/prosemirror-markdown/-/prosemirror-markdown-1.13.2.tgz", + "integrity": "sha512-FPD9rHPdA9fqzNmIIDhhnYQ6WgNoSWX9StUZ8LEKapaXU9i6XgykaHKhp6XMyXlOWetmaFgGDS/nu/w9/vUc5g==", + "license": "MIT", + "dependencies": { + "@types/markdown-it": "^14.0.0", + "markdown-it": "^14.0.0", + "prosemirror-model": "^1.25.0" + } + }, + "node_modules/prosemirror-menu": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/prosemirror-menu/-/prosemirror-menu-1.2.5.tgz", + "integrity": "sha512-qwXzynnpBIeg1D7BAtjOusR+81xCp53j7iWu/IargiRZqRjGIlQuu1f3jFi+ehrHhWMLoyOQTSRx/IWZJqOYtQ==", + "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.25.4", + "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.4.tgz", + "integrity": "sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA==", + "license": "MIT", + "peer": true, + "dependencies": { + "orderedmap": "^2.0.0" + } + }, + "node_modules/prosemirror-schema-basic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.4.tgz", + "integrity": "sha512-ELxP4TlX3yr2v5rM7Sb70SqStq5NvI15c0j9j/gjsrO5vaw+fnnpovCLEGIcpeGfifkuqJwl4fon6b+KdrODYQ==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.25.0" + } + }, + "node_modules/prosemirror-schema-list": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.5.1.tgz", + "integrity": "sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==", + "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.4", + "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.4.tgz", + "integrity": "sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==", + "license": "MIT", + "peer": true, + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-transform": "^1.0.0", + "prosemirror-view": "^1.27.0" + } + }, + "node_modules/prosemirror-tables": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.8.1.tgz", + "integrity": "sha512-DAgDoUYHCcc6tOGpLVPSU1k84kCUWTWnfWX3UDy2Delv4ryH0KqTD6RBI6k4yi9j9I8gl3j8MkPpRD/vWPZbug==", + "license": "MIT", + "dependencies": { + "prosemirror-keymap": "^1.2.2", + "prosemirror-model": "^1.25.0", + "prosemirror-state": "^1.4.3", + "prosemirror-transform": "^1.10.3", + "prosemirror-view": "^1.39.1" + } + }, + "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.5", + "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.10.5.tgz", + "integrity": "sha512-RPDQCxIDhIBb1o36xxwsaeAvivO8VLJcgBtzmOwQ64bMtsVFh5SSuJ6dWSxO1UsHTiTXPCgQm3PDJt7p6IOLbw==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.21.0" + } + }, + "node_modules/prosemirror-view": { + "version": "1.41.3", + "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.41.3.tgz", + "integrity": "sha512-SqMiYMUQNNBP9kfPhLO8WXEk/fon47vc52FQsUiJzTBuyjKgEcoAwMyF04eQ4WZ2ArMn7+ReypYL60aKngbACQ==", + "license": "MIT", + "peer": true, + "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", @@ -4000,6 +4799,15 @@ "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/react": { "version": "19.2.0", "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", @@ -4221,6 +5029,12 @@ "fsevents": "~2.3.2" } }, + "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/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -4444,6 +5258,12 @@ "typescript": ">=4.8.4 <6.0.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": "7.16.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", @@ -4605,6 +5425,12 @@ } } }, + "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/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index f66791dfa..e61669b6a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -14,6 +14,14 @@ "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", "@tanstack/react-query": "^5.90.11", + "@tiptap/extension-color": "^3.11.1", + "@tiptap/extension-image": "^3.11.1", + "@tiptap/extension-link": "^3.11.1", + "@tiptap/extension-text-align": "^3.11.1", + "@tiptap/extension-text-style": "^3.11.1", + "@tiptap/extension-underline": "^3.11.1", + "@tiptap/react": "^3.11.1", + "@tiptap/starter-kit": "^3.11.1", "@types/three": "^0.181.0", "gcode-preview": "^2.18.0", "jszip": "^3.10.1", diff --git a/frontend/public/img/android-chrome-192x192.png b/frontend/public/img/android-chrome-192x192.png new file mode 100644 index 000000000..a394aed35 Binary files /dev/null and b/frontend/public/img/android-chrome-192x192.png differ diff --git a/frontend/public/img/android-chrome-512x512.png b/frontend/public/img/android-chrome-512x512.png new file mode 100644 index 000000000..1f3509f6d Binary files /dev/null and b/frontend/public/img/android-chrome-512x512.png differ diff --git a/frontend/public/img/apple-touch-icon.png b/frontend/public/img/apple-touch-icon.png new file mode 100644 index 000000000..69e136c42 Binary files /dev/null and b/frontend/public/img/apple-touch-icon.png differ diff --git a/frontend/public/img/favicon-16x16.png b/frontend/public/img/favicon-16x16.png new file mode 100644 index 000000000..df56b4740 Binary files /dev/null and b/frontend/public/img/favicon-16x16.png differ diff --git a/frontend/public/img/favicon-32x32.png b/frontend/public/img/favicon-32x32.png new file mode 100644 index 000000000..a727e0122 Binary files /dev/null and b/frontend/public/img/favicon-32x32.png differ diff --git a/frontend/public/img/favicon.png b/frontend/public/img/favicon.png new file mode 100644 index 000000000..1f3509f6d Binary files /dev/null and b/frontend/public/img/favicon.png differ diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index 4b7a0e0bd..788893b20 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -286,6 +286,45 @@ export const api = { has_gcode: boolean; build_volume: { x: number; y: number; z: number }; }>(`/archives/${id}/capabilities`), + // Project Page + getArchiveProjectPage: (id: number) => + request<{ + title: string | null; + description: string | null; + designer: string | null; + designer_user_id: string | null; + license: string | null; + copyright: string | null; + creation_date: string | null; + modification_date: string | null; + origin: string | null; + profile_title: string | null; + profile_description: string | null; + profile_cover: string | null; + profile_user_id: string | null; + profile_user_name: string | null; + design_model_id: string | null; + design_profile_id: string | null; + design_region: string | null; + model_pictures: Array<{ name: string; path: string; url: string }>; + profile_pictures: Array<{ name: string; path: string; url: string }>; + thumbnails: Array<{ name: string; path: string; url: string }>; + }>(`/archives/${id}/project-page`), + updateArchiveProjectPage: (id: number, data: { + title?: string; + description?: string; + designer?: string; + license?: string; + copyright?: string; + profile_title?: string; + profile_description?: string; + }) => + request(`/archives/${id}/project-page`, { + method: 'PATCH', + body: JSON.stringify(data), + }), + getArchiveProjectImageUrl: (archiveId: number, imagePath: string) => + `${API_BASE}/archives/${archiveId}/project-image/${encodeURIComponent(imagePath)}`, getArchiveForSlicer: (id: number, filename: string) => `${API_BASE}/archives/${id}/file/${encodeURIComponent(filename.endsWith('.3mf') ? filename : filename + '.3mf')}`, reprintArchive: (archiveId: number, printerId: number) => diff --git a/frontend/src/components/ProjectPageModal.tsx b/frontend/src/components/ProjectPageModal.tsx new file mode 100644 index 000000000..27b6c8bd9 --- /dev/null +++ b/frontend/src/components/ProjectPageModal.tsx @@ -0,0 +1,475 @@ +import { useState, useEffect } from 'react'; +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import { + X, + User, + Calendar, + FileText, + Image, + Edit3, + Save, + ExternalLink, + ChevronLeft, + ChevronRight, +} from 'lucide-react'; +import { api } from '../api/client'; +import { Button } from './Button'; +import { RichTextEditor } from './RichTextEditor'; + +interface ProjectPageModalProps { + archiveId: number; + archiveName?: string; + onClose: () => void; +} + +export function ProjectPageModal({ archiveId, archiveName, onClose }: ProjectPageModalProps) { + const queryClient = useQueryClient(); + const [isEditing, setIsEditing] = useState(false); + const [selectedImageIndex, setSelectedImageIndex] = useState(null); + const [editData, setEditData] = useState<{ + title?: string; + description?: string; + designer?: string; + license?: string; + profile_title?: string; + profile_description?: string; + }>({}); + + const { data: projectPage, isLoading, error } = useQuery({ + queryKey: ['archive-project-page', archiveId], + queryFn: () => api.getArchiveProjectPage(archiveId), + }); + + const updateMutation = useMutation({ + mutationFn: (data: typeof editData) => api.updateArchiveProjectPage(archiveId, data), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['archive-project-page', archiveId] }); + setIsEditing(false); + setEditData({}); + }, + }); + + // Handle escape key to close modal + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Escape') { + if (selectedImageIndex !== null) { + setSelectedImageIndex(null); + } else if (isEditing) { + handleCancelEdit(); + } else { + onClose(); + } + } + }; + document.addEventListener('keydown', handleKeyDown); + return () => document.removeEventListener('keydown', handleKeyDown); + }, [selectedImageIndex, isEditing, onClose]); + + // Combine all images for gallery + const allImages = [ + ...(projectPage?.model_pictures || []), + ...(projectPage?.profile_pictures || []), + ]; + + const handleStartEdit = () => { + setEditData({ + title: projectPage?.title || '', + description: projectPage?.description || '', + designer: projectPage?.designer || '', + license: projectPage?.license || '', + profile_title: projectPage?.profile_title || '', + profile_description: projectPage?.profile_description || '', + }); + setIsEditing(true); + }; + + const handleSave = () => { + updateMutation.mutate(editData); + }; + + const handleCancelEdit = () => { + setIsEditing(false); + setEditData({}); + }; + + // Sanitize HTML content (basic XSS prevention) + const sanitizeHtml = (html: string) => { + // Allow basic formatting tags only + const allowed = ['p', 'br', 'b', 'strong', 'i', 'em', 'u', 'a', 'ul', 'ol', 'li', 'figure', 'img']; + const doc = new DOMParser().parseFromString(html, 'text/html'); + + const clean = (node: Node): string => { + if (node.nodeType === Node.TEXT_NODE) { + return node.textContent || ''; + } + if (node.nodeType === Node.ELEMENT_NODE) { + const el = node as Element; + const tag = el.tagName.toLowerCase(); + + if (!allowed.includes(tag)) { + // Return children content without the tag + return Array.from(el.childNodes).map(clean).join(''); + } + + // Build allowed attributes + let attrs = ''; + if (tag === 'a' && el.getAttribute('href')) { + const href = el.getAttribute('href'); + if (href?.toLowerCase().startsWith('http')) { + attrs = ` href="${href}" target="_blank" rel="noopener noreferrer"`; + } + } + if (tag === 'img') { + const src = el.getAttribute('src'); + // Only render img if it has a valid http(s) URL, otherwise skip entirely + if (!src?.toLowerCase().startsWith('http')) { + return ''; // Skip images without valid URLs + } + attrs = ` src="${src}" style="max-width: 100%; height: auto;"`; + } + + const children = Array.from(el.childNodes).map(clean).join(''); + + if (['br', 'img'].includes(tag)) { + return `<${tag}${attrs} />`; + } + return `<${tag}${attrs}>${children}`; + } + return ''; + }; + + return Array.from(doc.body.childNodes).map(clean).join(''); + }; + + const hasContent = projectPage && ( + projectPage.title || + projectPage.description || + projectPage.designer || + projectPage.profile_title || + allImages.length > 0 + ); + + // Handle backdrop click to close modal + const handleBackdropClick = (e: React.MouseEvent) => { + if (e.target === e.currentTarget) { + onClose(); + } + }; + + return ( +
+
+ {/* Header */} +
+
+ +

+ Project Page + {archiveName && - {archiveName}} +

+
+
+ {!isEditing && hasContent && ( + + )} + {isEditing && ( + <> + + + + )} + +
+
+ + {/* Content */} +
+ {isLoading && ( +
+
+
+ )} + + {error && ( +
+ Failed to load project page data +
+ )} + + {projectPage && !hasContent && ( +
+ +

No project page data found in this 3MF file.

+

+ Project pages are typically included in files downloaded from MakerWorld. +

+
+ )} + + {projectPage && hasContent && ( +
+ {/* Title & Designer */} +
+ {isEditing ? ( + setEditData({ ...editData, title: e.target.value })} + placeholder="Title" + className="w-full bg-bambu-dark border border-bambu-dark-tertiary rounded-lg px-4 py-2 text-white text-xl font-semibold" + /> + ) : ( + projectPage.title && ( +

{projectPage.title}

+ ) + )} + +
+ {isEditing ? ( +
+ + setEditData({ ...editData, designer: e.target.value })} + placeholder="Designer" + className="bg-bambu-dark border border-bambu-dark-tertiary rounded px-2 py-1 text-white" + /> +
+ ) : ( + projectPage.designer && ( +
+ + {projectPage.designer} + {projectPage.designer_user_id && ( + + + + )} +
+ ) + )} + + {projectPage.creation_date && ( +
+ + {projectPage.creation_date} +
+ )} + + {isEditing ? ( +
+ + setEditData({ ...editData, license: e.target.value })} + placeholder="License" + className="bg-bambu-dark border border-bambu-dark-tertiary rounded px-2 py-1 text-white" + /> +
+ ) : ( + projectPage.license && ( +
+ + {projectPage.license} +
+ ) + )} + + {projectPage.origin && ( + + {projectPage.origin} + + )} +
+
+ + {/* Description */} + {(projectPage.description || isEditing) && ( +
+

+ Description +

+ {isEditing ? ( + setEditData({ ...editData, description: html })} + placeholder="Enter description..." + /> + ) : ( +
+ )} +
+ )} + + {/* Profile Info */} + {(projectPage.profile_title || projectPage.profile_description || isEditing) && ( +
+

+ Print Profile +

+ {isEditing ? ( +
+ setEditData({ ...editData, profile_title: e.target.value })} + placeholder="Profile Title" + className="w-full bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded px-3 py-2 text-white" + /> + setEditData({ ...editData, profile_description: html })} + placeholder="Profile description..." + /> +
+ ) : ( + <> + {projectPage.profile_title && ( +

{projectPage.profile_title}

+ )} + {projectPage.profile_description && ( +
+ )} + {projectPage.profile_user_name && ( +

+ by {projectPage.profile_user_name} +

+ )} + + )} +
+ )} + + {/* Image Gallery */} + {allImages.length > 0 && ( +
+

+ + Images ({allImages.length}) +

+
+ {allImages.map((img, index) => ( + + ))} +
+
+ )} + + {/* MakerWorld Link */} + {projectPage.design_model_id && ( + + )} +
+ )} +
+
+ + {/* Image Lightbox */} + {selectedImageIndex !== null && allImages[selectedImageIndex] && ( +
setSelectedImageIndex(null)} + > + + + {allImages[selectedImageIndex].name} e.stopPropagation()} + /> + + + + + +
+ {selectedImageIndex + 1} / {allImages.length} +
+
+ )} +
+ ); +} diff --git a/frontend/src/components/RichTextEditor.tsx b/frontend/src/components/RichTextEditor.tsx new file mode 100644 index 000000000..5e0369ce5 --- /dev/null +++ b/frontend/src/components/RichTextEditor.tsx @@ -0,0 +1,191 @@ +import { useEditor, EditorContent } from '@tiptap/react'; +import StarterKit from '@tiptap/starter-kit'; +import Link from '@tiptap/extension-link'; +import Underline from '@tiptap/extension-underline'; +import TextAlign from '@tiptap/extension-text-align'; +import { TextStyle } from '@tiptap/extension-text-style'; +import Color from '@tiptap/extension-color'; +import Image from '@tiptap/extension-image'; +import { + Bold, + Italic, + Underline as UnderlineIcon, + List, + ListOrdered, + AlignLeft, + AlignCenter, + AlignRight, + Link as LinkIcon, + Unlink, +} from 'lucide-react'; + +interface RichTextEditorProps { + content: string; + onChange: (html: string) => void; + placeholder?: string; +} + +export function RichTextEditor({ content, onChange, placeholder }: RichTextEditorProps) { + const editor = useEditor({ + extensions: [ + StarterKit.configure({ + heading: false, + codeBlock: false, + code: false, + }), + Underline, + Link.configure({ + openOnClick: false, + HTMLAttributes: { + target: '_blank', + rel: 'noopener noreferrer', + }, + }), + TextAlign.configure({ + types: ['paragraph'], + }), + TextStyle, + Color, + Image.configure({ + HTMLAttributes: { + style: 'max-width: 100%; height: auto;', + }, + }), + ], + content, + onUpdate: ({ editor }) => { + onChange(editor.getHTML()); + }, + editorProps: { + attributes: { + class: 'prose prose-invert prose-sm max-w-none focus:outline-none min-h-[120px] px-3 py-2', + placeholder: placeholder || '', + }, + }, + }); + + if (!editor) { + return null; + } + + const ToolbarButton = ({ + onClick, + isActive = false, + children, + title, + }: { + onClick: () => void; + isActive?: boolean; + children: React.ReactNode; + title: string; + }) => ( + + ); + + const setLink = () => { + const url = window.prompt('Enter URL:'); + if (url) { + editor.chain().focus().setLink({ href: url }).run(); + } + }; + + return ( +
+ {/* Toolbar */} +
+ editor.chain().focus().toggleBold().run()} + isActive={editor.isActive('bold')} + title="Bold" + > + + + editor.chain().focus().toggleItalic().run()} + isActive={editor.isActive('italic')} + title="Italic" + > + + + editor.chain().focus().toggleUnderline().run()} + isActive={editor.isActive('underline')} + title="Underline" + > + + + +
+ + editor.chain().focus().toggleBulletList().run()} + isActive={editor.isActive('bulletList')} + title="Bullet List" + > + + + editor.chain().focus().toggleOrderedList().run()} + isActive={editor.isActive('orderedList')} + title="Numbered List" + > + + + +
+ + editor.chain().focus().setTextAlign('left').run()} + isActive={editor.isActive({ textAlign: 'left' })} + title="Align Left" + > + + + editor.chain().focus().setTextAlign('center').run()} + isActive={editor.isActive({ textAlign: 'center' })} + title="Align Center" + > + + + editor.chain().focus().setTextAlign('right').run()} + isActive={editor.isActive({ textAlign: 'right' })} + title="Align Right" + > + + + +
+ + + + + {editor.isActive('link') && ( + editor.chain().focus().unsetLink().run()} + title="Remove Link" + > + + + )} +
+ + {/* Editor */} + +
+ ); +} diff --git a/frontend/src/pages/ArchivesPage.tsx b/frontend/src/pages/ArchivesPage.tsx index 0b887f26e..cf88b4e44 100644 --- a/frontend/src/pages/ArchivesPage.tsx +++ b/frontend/src/pages/ArchivesPage.tsx @@ -33,6 +33,7 @@ import { ScanSearch, QrCode, Camera, + FileText, } from 'lucide-react'; import { api } from '../api/client'; import type { Archive } from '../api/client'; @@ -48,6 +49,7 @@ import { BatchTagModal } from '../components/BatchTagModal'; import { CalendarView } from '../components/CalendarView'; import { QRCodeModal } from '../components/QRCodeModal'; import { PhotoGalleryModal } from '../components/PhotoGalleryModal'; +import { ProjectPageModal } from '../components/ProjectPageModal'; import { useToast } from '../contexts/ToastContext'; function formatFileSize(bytes: number): string { @@ -95,6 +97,7 @@ function ArchiveCard({ const [showTimelapse, setShowTimelapse] = useState(false); const [showQRCode, setShowQRCode] = useState(false); const [showPhotos, setShowPhotos] = useState(false); + const [showProjectPage, setShowProjectPage] = useState(false); const [contextMenu, setContextMenu] = useState<{ x: number; y: number } | null>(null); const timelapseScanMutation = useMutation({ @@ -210,6 +213,11 @@ function ArchiveCard({ onClick: () => setShowPhotos(true), disabled: !archive.photos?.length, }, + { + label: 'Project Page', + icon: , + onClick: () => setShowProjectPage(true), + }, { label: '', divider: true, onClick: () => {} }, { label: archive.is_favorite ? 'Remove from Favorites' : 'Add to Favorites', @@ -598,6 +606,15 @@ function ArchiveCard({ }} /> )} + + {/* Project Page Modal */} + {showProjectPage && ( + setShowProjectPage(false)} + /> + )} ); } diff --git a/static/assets/index-BrIMpS3z.js b/static/assets/index-BrIMpS3z.js deleted file mode 100644 index a1bb96b28..000000000 --- a/static/assets/index-BrIMpS3z.js +++ /dev/null @@ -1,8450 +0,0 @@ -function F9(t,e){for(var n=0;ni[r]})}}}return Object.freeze(Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}))}(function(){const e=document.createElement("link").relList;if(e&&e.supports&&e.supports("modulepreload"))return;for(const r of document.querySelectorAll('link[rel="modulepreload"]'))i(r);new MutationObserver(r=>{for(const a of r)if(a.type==="childList")for(const s of a.addedNodes)s.tagName==="LINK"&&s.rel==="modulepreload"&&i(s)}).observe(document,{childList:!0,subtree:!0});function n(r){const a={};return r.integrity&&(a.integrity=r.integrity),r.referrerPolicy&&(a.referrerPolicy=r.referrerPolicy),r.crossOrigin==="use-credentials"?a.credentials="include":r.crossOrigin==="anonymous"?a.credentials="omit":a.credentials="same-origin",a}function i(r){if(r.ep)return;r.ep=!0;const a=n(r);fetch(r.href,a)}})();var s0=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{};function Xs(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var bS={exports:{}},Xh={};var LD;function z9(){if(LD)return Xh;LD=1;var t=Symbol.for("react.transitional.element"),e=Symbol.for("react.fragment");function n(i,r,a){var s=null;if(a!==void 0&&(s=""+a),r.key!==void 0&&(s=""+r.key),"key"in r){a={};for(var l in r)l!=="key"&&(a[l]=r[l])}else a=r;return r=a.ref,{$$typeof:t,type:i,key:s,ref:r!==void 0?r:null,props:a}}return Xh.Fragment=e,Xh.jsx=n,Xh.jsxs=n,Xh}var ID;function B9(){return ID||(ID=1,bS.exports=z9()),bS.exports}var E=B9(),_S={exports:{}},Ft={};var UD;function j9(){if(UD)return Ft;UD=1;var t=Symbol.for("react.transitional.element"),e=Symbol.for("react.portal"),n=Symbol.for("react.fragment"),i=Symbol.for("react.strict_mode"),r=Symbol.for("react.profiler"),a=Symbol.for("react.consumer"),s=Symbol.for("react.context"),l=Symbol.for("react.forward_ref"),c=Symbol.for("react.suspense"),d=Symbol.for("react.memo"),f=Symbol.for("react.lazy"),h=Symbol.for("react.activity"),g=Symbol.iterator;function m(H){return H===null||typeof H!="object"?null:(H=g&&H[g]||H["@@iterator"],typeof H=="function"?H:null)}var b={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},y=Object.assign,x={};function v(H,q,re){this.props=H,this.context=q,this.refs=x,this.updater=re||b}v.prototype.isReactComponent={},v.prototype.setState=function(H,q){if(typeof H!="object"&&typeof H!="function"&&H!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,H,q,"setState")},v.prototype.forceUpdate=function(H){this.updater.enqueueForceUpdate(this,H,"forceUpdate")};function w(){}w.prototype=v.prototype;function S(H,q,re){this.props=H,this.context=q,this.refs=x,this.updater=re||b}var A=S.prototype=new w;A.constructor=S,y(A,v.prototype),A.isPureReactComponent=!0;var R=Array.isArray;function N(){}var C={H:null,A:null,T:null,S:null},F=Object.prototype.hasOwnProperty;function P(H,q,re){var ce=re.ref;return{$$typeof:t,type:H,key:q,ref:ce!==void 0?ce:null,props:re}}function L(H,q){return P(H.type,q,H.props)}function B(H){return typeof H=="object"&&H!==null&&H.$$typeof===t}function U(H){var q={"=":"=0",":":"=2"};return"$"+H.replace(/[=:]/g,function(re){return q[re]})}var $=/\/+/g;function I(H,q){return typeof H=="object"&&H!==null&&H.key!=null?U(""+H.key):q.toString(36)}function W(H){switch(H.status){case"fulfilled":return H.value;case"rejected":throw H.reason;default:switch(typeof H.status=="string"?H.then(N,N):(H.status="pending",H.then(function(q){H.status==="pending"&&(H.status="fulfilled",H.value=q)},function(q){H.status==="pending"&&(H.status="rejected",H.reason=q)})),H.status){case"fulfilled":return H.value;case"rejected":throw H.reason}}throw H}function X(H,q,re,ce,ye){var me=typeof H;(me==="undefined"||me==="boolean")&&(H=null);var be=!1;if(H===null)be=!0;else switch(me){case"bigint":case"string":case"number":be=!0;break;case"object":switch(H.$$typeof){case t:case e:be=!0;break;case f:return be=H._init,X(be(H._payload),q,re,ce,ye)}}if(be)return ye=ye(H),be=ce===""?"."+I(H,0):ce,R(ye)?(re="",be!=null&&(re=be.replace($,"$&/")+"/"),X(ye,q,re,"",function(Ae){return Ae})):ye!=null&&(B(ye)&&(ye=L(ye,re+(ye.key==null||H&&H.key===ye.key?"":(""+ye.key).replace($,"$&/")+"/")+be)),q.push(ye)),1;be=0;var Ne=ce===""?".":ce+":";if(R(H))for(var he=0;he>>1,se=X[J];if(0>>1;Jr(re,Y))cer(ye,re)?(X[J]=ye,X[ce]=Y,J=ce):(X[J]=re,X[q]=Y,J=q);else if(cer(ye,Y))X[J]=ye,X[ce]=Y,J=ce;else break e}}return V}function r(X,V){var Y=X.sortIndex-V.sortIndex;return Y!==0?Y:X.id-V.id}if(t.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var a=performance;t.unstable_now=function(){return a.now()}}else{var s=Date,l=s.now();t.unstable_now=function(){return s.now()-l}}var c=[],d=[],f=1,h=null,g=3,m=!1,b=!1,y=!1,x=!1,v=typeof setTimeout=="function"?setTimeout:null,w=typeof clearTimeout=="function"?clearTimeout:null,S=typeof setImmediate<"u"?setImmediate:null;function A(X){for(var V=n(d);V!==null;){if(V.callback===null)i(d);else if(V.startTime<=X)i(d),V.sortIndex=V.expirationTime,e(c,V);else break;V=n(d)}}function R(X){if(y=!1,A(X),!b)if(n(c)!==null)b=!0,N||(N=!0,U());else{var V=n(d);V!==null&&W(R,V.startTime-X)}}var N=!1,C=-1,F=5,P=-1;function L(){return x?!0:!(t.unstable_now()-PX&&L());){var J=h.callback;if(typeof J=="function"){h.callback=null,g=h.priorityLevel;var se=J(h.expirationTime<=X);if(X=t.unstable_now(),typeof se=="function"){h.callback=se,A(X),V=!0;break t}h===n(c)&&i(c),A(X)}else i(c);h=n(c)}if(h!==null)V=!0;else{var H=n(d);H!==null&&W(R,H.startTime-X),V=!1}}break e}finally{h=null,g=Y,m=!1}V=void 0}}finally{V?U():N=!1}}}var U;if(typeof S=="function")U=function(){S(B)};else if(typeof MessageChannel<"u"){var $=new MessageChannel,I=$.port2;$.port1.onmessage=B,U=function(){I.postMessage(null)}}else U=function(){v(B,0)};function W(X,V){C=v(function(){X(t.unstable_now())},V)}t.unstable_IdlePriority=5,t.unstable_ImmediatePriority=1,t.unstable_LowPriority=4,t.unstable_NormalPriority=3,t.unstable_Profiling=null,t.unstable_UserBlockingPriority=2,t.unstable_cancelCallback=function(X){X.callback=null},t.unstable_forceFrameRate=function(X){0>X||125J?(X.sortIndex=Y,e(d,X),n(c)===null&&X===n(d)&&(y?(w(C),C=-1):y=!0,W(R,Y-J))):(X.sortIndex=se,e(c,X),b||m||(b=!0,N||(N=!0,U()))),X},t.unstable_shouldYield=L,t.unstable_wrapCallback=function(X){var V=g;return function(){var Y=g;g=V;try{return X.apply(this,arguments)}finally{g=Y}}}})(ES)),ES}var zD;function V9(){return zD||(zD=1,wS.exports=G9()),wS.exports}var MS={exports:{}},gr={};var BD;function $9(){if(BD)return gr;BD=1;var t=If();function e(c){var d="https://react.dev/errors/"+c;if(1"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(t)}catch(e){console.error(e)}}return t(),MS.exports=$9(),MS.exports}var HD;function W9(){if(HD)return qh;HD=1;var t=V9(),e=If(),n=Q4();function i(o){var u="https://react.dev/errors/"+o;if(1se||(o.current=J[se],J[se]=null,se--)}function re(o,u){se++,J[se]=o.current,o.current=u}var ce=H(null),ye=H(null),me=H(null),be=H(null);function Ne(o,u){switch(re(me,u),re(ye,o),re(ce,null),u.nodeType){case 9:case 11:o=(o=u.documentElement)&&(o=o.namespaceURI)?iD(o):0;break;default:if(o=u.tagName,u=u.namespaceURI)u=iD(u),o=rD(u,o);else switch(o){case"svg":o=1;break;case"math":o=2;break;default:o=0}}q(ce),re(ce,o)}function he(){q(ce),q(ye),q(me)}function Ae(o){o.memoizedState!==null&&re(be,o);var u=ce.current,p=rD(u,o.type);u!==p&&(re(ye,o),re(ce,p))}function Be(o){ye.current===o&&(q(ce),q(ye)),be.current===o&&(q(be),Gh._currentValue=Y)}var le,lt;function z(o){if(le===void 0)try{throw Error()}catch(p){var u=p.stack.trim().match(/\n( *(at )?)/);le=u&&u[1]||"",lt=-1)":-1M||Se[_]!==ze[M]){var Qe=` -`+Se[_].replace(" at new "," at ");return o.displayName&&Qe.includes("")&&(Qe=Qe.replace("",o.displayName)),Qe}while(1<=_&&0<=M);break}}}finally{G=!1,Error.prepareStackTrace=p}return(p=o?o.displayName||o.name:"")?z(p):""}function Q(o,u){switch(o.tag){case 26:case 27:case 5:return z(o.type);case 16:return z("Lazy");case 13:return o.child!==u&&u!==null?z("Suspense Fallback"):z("Suspense");case 19:return z("SuspenseList");case 0:case 15:return ne(o.type,!1);case 11:return ne(o.type.render,!1);case 1:return ne(o.type,!0);case 31:return z("Activity");default:return""}}function ee(o){try{var u="",p=null;do u+=Q(o,p),p=o,o=o.return;while(o);return u}catch(_){return` -Error generating stack: `+_.message+` -`+_.stack}}var ae=Object.prototype.hasOwnProperty,ie=t.unstable_scheduleCallback,we=t.unstable_cancelCallback,ve=t.unstable_shouldYield,k=t.unstable_requestPaint,O=t.unstable_now,K=t.unstable_getCurrentPriorityLevel,pe=t.unstable_ImmediatePriority,xe=t.unstable_UserBlockingPriority,fe=t.unstable_NormalPriority,Ke=t.unstable_LowPriority,Ie=t.unstable_IdlePriority,$e=t.log,rt=t.unstable_setDisableYieldValue,ke=null,De=null;function ct(o){if(typeof $e=="function"&&rt(o),De&&typeof De.setStrictMode=="function")try{De.setStrictMode(ke,o)}catch{}}var et=Math.clz32?Math.clz32:ue,Je=Math.log,st=Math.LN2;function ue(o){return o>>>=0,o===0?32:31-(Je(o)/st|0)|0}var tt=256,de=262144,Le=4194304;function Me(o){var u=o&42;if(u!==0)return u;switch(o&-o){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return o&261888;case 262144:case 524288:case 1048576:case 2097152:return o&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return o&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return o}}function ge(o,u,p){var _=o.pendingLanes;if(_===0)return 0;var M=0,D=o.suspendedLanes,j=o.pingedLanes;o=o.warmLanes;var te=_&134217727;return te!==0?(_=te&~D,_!==0?M=Me(_):(j&=te,j!==0?M=Me(j):p||(p=te&~o,p!==0&&(M=Me(p))))):(te=_&~D,te!==0?M=Me(te):j!==0?M=Me(j):p||(p=_&~o,p!==0&&(M=Me(p)))),M===0?0:u!==0&&u!==M&&(u&D)===0&&(D=M&-M,p=u&-u,D>=p||D===32&&(p&4194048)!==0)?u:M}function je(o,u){return(o.pendingLanes&~(o.suspendedLanes&~o.pingedLanes)&u)===0}function at(o,u){switch(o){case 1:case 2:case 4:case 8:case 64:return u+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return u+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function mt(){var o=Le;return Le<<=1,(Le&62914560)===0&&(Le=4194304),o}function wt(o){for(var u=[],p=0;31>p;p++)u.push(o);return u}function Wt(o,u){o.pendingLanes|=u,u!==268435456&&(o.suspendedLanes=0,o.pingedLanes=0,o.warmLanes=0)}function Ct(o,u,p,_,M,D){var j=o.pendingLanes;o.pendingLanes=p,o.suspendedLanes=0,o.pingedLanes=0,o.warmLanes=0,o.expiredLanes&=p,o.entangledLanes&=p,o.errorRecoveryDisabledLanes&=p,o.shellSuspendCounter=0;var te=o.entanglements,Se=o.expirationTimes,ze=o.hiddenUpdates;for(p=j&~p;0"u")return null;try{return o.activeElement||o.body}catch{return o.body}}var ti=/[\n"\\]/g;function pn(o){return o.replace(ti,function(u){return"\\"+u.charCodeAt(0).toString(16)+" "})}function es(o,u,p,_,M,D,j,te){o.name="",j!=null&&typeof j!="function"&&typeof j!="symbol"&&typeof j!="boolean"?o.type=j:o.removeAttribute("type"),u!=null?j==="number"?(u===0&&o.value===""||o.value!=u)&&(o.value=""+qt(u)):o.value!==""+qt(u)&&(o.value=""+qt(u)):j!=="submit"&&j!=="reset"||o.removeAttribute("value"),u!=null?ji(o,j,qt(u)):p!=null?ji(o,j,qt(p)):_!=null&&o.removeAttribute("value"),M==null&&D!=null&&(o.defaultChecked=!!D),M!=null&&(o.checked=M&&typeof M!="function"&&typeof M!="symbol"),te!=null&&typeof te!="function"&&typeof te!="symbol"&&typeof te!="boolean"?o.name=""+qt(te):o.removeAttribute("name")}function gn(o,u,p,_,M,D,j,te){if(D!=null&&typeof D!="function"&&typeof D!="symbol"&&typeof D!="boolean"&&(o.type=D),u!=null||p!=null){if(!(D!=="submit"&&D!=="reset"||u!=null)){kt(o);return}p=p!=null?""+qt(p):"",u=u!=null?""+qt(u):p,te||u===o.value||(o.value=u),o.defaultValue=u}_=_??M,_=typeof _!="function"&&typeof _!="symbol"&&!!_,o.checked=te?o.checked:!!_,o.defaultChecked=!!_,j!=null&&typeof j!="function"&&typeof j!="symbol"&&typeof j!="boolean"&&(o.name=j),kt(o)}function ji(o,u,p){u==="number"&&Kn(o.ownerDocument)===o||o.defaultValue===""+p||(o.defaultValue=""+p)}function Ei(o,u,p,_){if(o=o.options,u){u={};for(var M=0;M"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),bb=!1;if(Js)try{var oh={};Object.defineProperty(oh,"passive",{get:function(){bb=!0}}),window.addEventListener("test",oh,oh),window.removeEventListener("test",oh,oh)}catch{bb=!1}var ll=null,_b=null,Qm=null;function r3(){if(Qm)return Qm;var o,u=_b,p=u.length,_,M="value"in ll?ll.value:ll.textContent,D=M.length;for(o=0;o=uh),u3=" ",d3=!1;function f3(o,u){switch(o){case"keyup":return sV.indexOf(u.keyCode)!==-1;case"keydown":return u.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function h3(o){return o=o.detail,typeof o=="object"&&"data"in o?o.data:null}var ju=!1;function lV(o,u){switch(o){case"compositionend":return h3(u);case"keypress":return u.which!==32?null:(d3=!0,u3);case"textInput":return o=u.data,o===u3&&d3?null:o;default:return null}}function cV(o,u){if(ju)return o==="compositionend"||!Ab&&f3(o,u)?(o=r3(),Qm=_b=ll=null,ju=!1,o):null;switch(o){case"paste":return null;case"keypress":if(!(u.ctrlKey||u.altKey||u.metaKey)||u.ctrlKey&&u.altKey){if(u.char&&1=u)return{node:p,offset:u-o};o=_}e:{for(;p;){if(p.nextSibling){p=p.nextSibling;break e}p=p.parentNode}p=void 0}p=_3(p)}}function w3(o,u){return o&&u?o===u?!0:o&&o.nodeType===3?!1:u&&u.nodeType===3?w3(o,u.parentNode):"contains"in o?o.contains(u):o.compareDocumentPosition?!!(o.compareDocumentPosition(u)&16):!1:!1}function E3(o){o=o!=null&&o.ownerDocument!=null&&o.ownerDocument.defaultView!=null?o.ownerDocument.defaultView:window;for(var u=Kn(o.document);u instanceof o.HTMLIFrameElement;){try{var p=typeof u.contentWindow.location.href=="string"}catch{p=!1}if(p)o=u.contentWindow;else break;u=Kn(o.document)}return u}function Rb(o){var u=o&&o.nodeName&&o.nodeName.toLowerCase();return u&&(u==="input"&&(o.type==="text"||o.type==="search"||o.type==="tel"||o.type==="url"||o.type==="password")||u==="textarea"||o.contentEditable==="true")}var vV=Js&&"documentMode"in document&&11>=document.documentMode,Hu=null,Pb=null,ph=null,Db=!1;function M3(o,u,p){var _=p.window===p?p.document:p.nodeType===9?p:p.ownerDocument;Db||Hu==null||Hu!==Kn(_)||(_=Hu,"selectionStart"in _&&Rb(_)?_={start:_.selectionStart,end:_.selectionEnd}:(_=(_.ownerDocument&&_.ownerDocument.defaultView||window).getSelection(),_={anchorNode:_.anchorNode,anchorOffset:_.anchorOffset,focusNode:_.focusNode,focusOffset:_.focusOffset}),ph&&hh(ph,_)||(ph=_,_=$g(Pb,"onSelect"),0<_.length&&(u=new tg("onSelect","select",null,u,p),o.push({event:u,listeners:_}),u.target=Hu)))}function bc(o,u){var p={};return p[o.toLowerCase()]=u.toLowerCase(),p["Webkit"+o]="webkit"+u,p["Moz"+o]="moz"+u,p}var Gu={animationend:bc("Animation","AnimationEnd"),animationiteration:bc("Animation","AnimationIteration"),animationstart:bc("Animation","AnimationStart"),transitionrun:bc("Transition","TransitionRun"),transitionstart:bc("Transition","TransitionStart"),transitioncancel:bc("Transition","TransitionCancel"),transitionend:bc("Transition","TransitionEnd")},Nb={},A3={};Js&&(A3=document.createElement("div").style,"AnimationEvent"in window||(delete Gu.animationend.animation,delete Gu.animationiteration.animation,delete Gu.animationstart.animation),"TransitionEvent"in window||delete Gu.transitionend.transition);function _c(o){if(Nb[o])return Nb[o];if(!Gu[o])return o;var u=Gu[o],p;for(p in u)if(u.hasOwnProperty(p)&&p in A3)return Nb[o]=u[p];return o}var T3=_c("animationend"),C3=_c("animationiteration"),R3=_c("animationstart"),xV=_c("transitionrun"),yV=_c("transitionstart"),bV=_c("transitioncancel"),P3=_c("transitionend"),D3=new Map,Ob="abort auxClick beforeToggle cancel canPlay canPlayThrough click close contextMenu copy cut drag dragEnd dragEnter dragExit dragLeave dragOver dragStart drop durationChange emptied encrypted ended error gotPointerCapture input invalid keyDown keyPress keyUp load loadedData loadedMetadata loadStart lostPointerCapture mouseDown mouseMove mouseOut mouseOver mouseUp paste pause play playing pointerCancel pointerDown pointerMove pointerOut pointerOver pointerUp progress rateChange reset resize seeked seeking stalled submit suspend timeUpdate touchCancel touchEnd touchStart volumeChange scroll toggle touchMove waiting wheel".split(" ");Ob.push("scrollEnd");function ts(o,u){D3.set(o,u),ot(u,[o])}var rg=typeof reportError=="function"?reportError:function(o){if(typeof window=="object"&&typeof window.ErrorEvent=="function"){var u=new window.ErrorEvent("error",{bubbles:!0,cancelable:!0,message:typeof o=="object"&&o!==null&&typeof o.message=="string"?String(o.message):String(o),error:o});if(!window.dispatchEvent(u))return}else if(typeof process=="object"&&typeof process.emit=="function"){process.emit("uncaughtException",o);return}console.error(o)},Ea=[],Vu=0,Lb=0;function ag(){for(var o=Vu,u=Lb=Vu=0;u>=j,M-=j,Ms=1<<32-et(u)+M|p<jt?(tn=Tt,Tt=null):tn=Tt.sibling;var ln=Ge(Oe,Tt,Fe[jt],nt);if(ln===null){Tt===null&&(Tt=tn);break}o&&Tt&&ln.alternate===null&&u(Oe,Tt),Te=D(ln,Te,jt),on===null?Dt=ln:on.sibling=ln,on=ln,Tt=tn}if(jt===Fe.length)return p(Oe,Tt),rn&&to(Oe,jt),Dt;if(Tt===null){for(;jtjt?(tn=Tt,Tt=null):tn=Tt.sibling;var Pl=Ge(Oe,Tt,ln.value,nt);if(Pl===null){Tt===null&&(Tt=tn);break}o&&Tt&&Pl.alternate===null&&u(Oe,Tt),Te=D(Pl,Te,jt),on===null?Dt=Pl:on.sibling=Pl,on=Pl,Tt=tn}if(ln.done)return p(Oe,Tt),rn&&to(Oe,jt),Dt;if(Tt===null){for(;!ln.done;jt++,ln=Fe.next())ln=it(Oe,ln.value,nt),ln!==null&&(Te=D(ln,Te,jt),on===null?Dt=ln:on.sibling=ln,on=ln);return rn&&to(Oe,jt),Dt}for(Tt=_(Tt);!ln.done;jt++,ln=Fe.next())ln=Ye(Tt,Oe,jt,ln.value,nt),ln!==null&&(o&&ln.alternate!==null&&Tt.delete(ln.key===null?jt:ln.key),Te=D(ln,Te,jt),on===null?Dt=ln:on.sibling=ln,on=ln);return o&&Tt.forEach(function(k9){return u(Oe,k9)}),rn&&to(Oe,jt),Dt}function Sn(Oe,Te,Fe,nt){if(typeof Fe=="object"&&Fe!==null&&Fe.type===y&&Fe.key===null&&(Fe=Fe.props.children),typeof Fe=="object"&&Fe!==null){switch(Fe.$$typeof){case m:e:{for(var Dt=Fe.key;Te!==null;){if(Te.key===Dt){if(Dt=Fe.type,Dt===y){if(Te.tag===7){p(Oe,Te.sibling),nt=M(Te,Fe.props.children),nt.return=Oe,Oe=nt;break e}}else if(Te.elementType===Dt||typeof Dt=="object"&&Dt!==null&&Dt.$$typeof===F&&Cc(Dt)===Te.type){p(Oe,Te.sibling),nt=M(Te,Fe.props),bh(nt,Fe),nt.return=Oe,Oe=nt;break e}p(Oe,Te);break}else u(Oe,Te);Te=Te.sibling}Fe.type===y?(nt=wc(Fe.props.children,Oe.mode,nt,Fe.key),nt.return=Oe,Oe=nt):(nt=lg(Fe.type,Fe.key,Fe.props,null,Oe.mode,nt),bh(nt,Fe),nt.return=Oe,Oe=nt)}return j(Oe);case b:e:{for(Dt=Fe.key;Te!==null;){if(Te.key===Dt)if(Te.tag===4&&Te.stateNode.containerInfo===Fe.containerInfo&&Te.stateNode.implementation===Fe.implementation){p(Oe,Te.sibling),nt=M(Te,Fe.children||[]),nt.return=Oe,Oe=nt;break e}else{p(Oe,Te);break}else u(Oe,Te);Te=Te.sibling}nt=Fb(Fe,Oe.mode,nt),nt.return=Oe,Oe=nt}return j(Oe);case F:return Fe=Cc(Fe),Sn(Oe,Te,Fe,nt)}if(W(Fe))return St(Oe,Te,Fe,nt);if(U(Fe)){if(Dt=U(Fe),typeof Dt!="function")throw Error(i(150));return Fe=Dt.call(Fe),Ot(Oe,Te,Fe,nt)}if(typeof Fe.then=="function")return Sn(Oe,Te,mg(Fe),nt);if(Fe.$$typeof===S)return Sn(Oe,Te,dg(Oe,Fe),nt);gg(Oe,Fe)}return typeof Fe=="string"&&Fe!==""||typeof Fe=="number"||typeof Fe=="bigint"?(Fe=""+Fe,Te!==null&&Te.tag===6?(p(Oe,Te.sibling),nt=M(Te,Fe),nt.return=Oe,Oe=nt):(p(Oe,Te),nt=kb(Fe,Oe.mode,nt),nt.return=Oe,Oe=nt),j(Oe)):p(Oe,Te)}return function(Oe,Te,Fe,nt){try{yh=0;var Dt=Sn(Oe,Te,Fe,nt);return Ju=null,Dt}catch(Tt){if(Tt===Qu||Tt===hg)throw Tt;var on=ra(29,Tt,null,Oe.mode);return on.lanes=nt,on.return=Oe,on}finally{}}}var Pc=q3(!0),Y3=q3(!1),hl=!1;function Kb(o){o.updateQueue={baseState:o.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function Zb(o,u){o=o.updateQueue,u.updateQueue===o&&(u.updateQueue={baseState:o.baseState,firstBaseUpdate:o.firstBaseUpdate,lastBaseUpdate:o.lastBaseUpdate,shared:o.shared,callbacks:null})}function pl(o){return{lane:o,tag:0,payload:null,callback:null,next:null}}function ml(o,u,p){var _=o.updateQueue;if(_===null)return null;if(_=_.shared,(hn&2)!==0){var M=_.pending;return M===null?u.next=u:(u.next=M.next,M.next=u),_.pending=u,u=og(o),N3(o,null,p),u}return sg(o,_,u,p),og(o)}function _h(o,u,p){if(u=u.updateQueue,u!==null&&(u=u.shared,(p&4194048)!==0)){var _=u.lanes;_&=o.pendingLanes,p|=_,u.lanes=p,Rn(o,p)}}function Qb(o,u){var p=o.updateQueue,_=o.alternate;if(_!==null&&(_=_.updateQueue,p===_)){var M=null,D=null;if(p=p.firstBaseUpdate,p!==null){do{var j={lane:p.lane,tag:p.tag,payload:p.payload,callback:null,next:null};D===null?M=D=j:D=D.next=j,p=p.next}while(p!==null);D===null?M=D=u:D=D.next=u}else M=D=u;p={baseState:_.baseState,firstBaseUpdate:M,lastBaseUpdate:D,shared:_.shared,callbacks:_.callbacks},o.updateQueue=p;return}o=p.lastBaseUpdate,o===null?p.firstBaseUpdate=u:o.next=u,p.lastBaseUpdate=u}var Jb=!1;function Sh(){if(Jb){var o=Zu;if(o!==null)throw o}}function wh(o,u,p,_){Jb=!1;var M=o.updateQueue;hl=!1;var D=M.firstBaseUpdate,j=M.lastBaseUpdate,te=M.shared.pending;if(te!==null){M.shared.pending=null;var Se=te,ze=Se.next;Se.next=null,j===null?D=ze:j.next=ze,j=Se;var Qe=o.alternate;Qe!==null&&(Qe=Qe.updateQueue,te=Qe.lastBaseUpdate,te!==j&&(te===null?Qe.firstBaseUpdate=ze:te.next=ze,Qe.lastBaseUpdate=Se))}if(D!==null){var it=M.baseState;j=0,Qe=ze=Se=null,te=D;do{var Ge=te.lane&-536870913,Ye=Ge!==te.lane;if(Ye?(en&Ge)===Ge:(_&Ge)===Ge){Ge!==0&&Ge===Ku&&(Jb=!0),Qe!==null&&(Qe=Qe.next={lane:0,tag:te.tag,payload:te.payload,callback:null,next:null});e:{var St=o,Ot=te;Ge=u;var Sn=p;switch(Ot.tag){case 1:if(St=Ot.payload,typeof St=="function"){it=St.call(Sn,it,Ge);break e}it=St;break e;case 3:St.flags=St.flags&-65537|128;case 0:if(St=Ot.payload,Ge=typeof St=="function"?St.call(Sn,it,Ge):St,Ge==null)break e;it=h({},it,Ge);break e;case 2:hl=!0}}Ge=te.callback,Ge!==null&&(o.flags|=64,Ye&&(o.flags|=8192),Ye=M.callbacks,Ye===null?M.callbacks=[Ge]:Ye.push(Ge))}else Ye={lane:Ge,tag:te.tag,payload:te.payload,callback:te.callback,next:null},Qe===null?(ze=Qe=Ye,Se=it):Qe=Qe.next=Ye,j|=Ge;if(te=te.next,te===null){if(te=M.shared.pending,te===null)break;Ye=te,te=Ye.next,Ye.next=null,M.lastBaseUpdate=Ye,M.shared.pending=null}}while(!0);Qe===null&&(Se=it),M.baseState=Se,M.firstBaseUpdate=ze,M.lastBaseUpdate=Qe,D===null&&(M.shared.lanes=0),bl|=j,o.lanes=j,o.memoizedState=it}}function K3(o,u){if(typeof o!="function")throw Error(i(191,o));o.call(u)}function Z3(o,u){var p=o.callbacks;if(p!==null)for(o.callbacks=null,o=0;oD?D:8;var j=X.T,te={};X.T=te,x_(o,!1,u,p);try{var Se=M(),ze=X.S;if(ze!==null&&ze(te,Se),Se!==null&&typeof Se=="object"&&typeof Se.then=="function"){var Qe=AV(Se,_);Ah(o,u,Qe,ca(o))}else Ah(o,u,_,ca(o))}catch(it){Ah(o,u,{then:function(){},status:"rejected",reason:it},ca())}finally{V.p=D,j!==null&&te.types!==null&&(j.types=te.types),X.T=j}}function NV(){}function g_(o,u,p,_){if(o.tag!==5)throw Error(i(476));var M=RR(o).queue;CR(o,M,u,Y,p===null?NV:function(){return PR(o),p(_)})}function RR(o){var u=o.memoizedState;if(u!==null)return u;u={memoizedState:Y,baseState:Y,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:ao,lastRenderedState:Y},next:null};var p={};return u.next={memoizedState:p,baseState:p,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:ao,lastRenderedState:p},next:null},o.memoizedState=u,o=o.alternate,o!==null&&(o.memoizedState=u),u}function PR(o){var u=RR(o);u.next===null&&(u=o.alternate.memoizedState),Ah(o,u.next.queue,{},ca())}function v_(){return rr(Gh)}function DR(){return ii().memoizedState}function NR(){return ii().memoizedState}function OV(o){for(var u=o.return;u!==null;){switch(u.tag){case 24:case 3:var p=ca();o=pl(p);var _=ml(u,o,p);_!==null&&(Vr(_,u,p),_h(_,u,p)),u={cache:Wb()},o.payload=u;return}u=u.return}}function LV(o,u,p){var _=ca();p={lane:_,revertLane:0,gesture:null,action:p,hasEagerState:!1,eagerState:null,next:null},Ag(o)?LR(u,p):(p=Ib(o,u,p,_),p!==null&&(Vr(p,o,_),IR(p,u,_)))}function OR(o,u,p){var _=ca();Ah(o,u,p,_)}function Ah(o,u,p,_){var M={lane:_,revertLane:0,gesture:null,action:p,hasEagerState:!1,eagerState:null,next:null};if(Ag(o))LR(u,M);else{var D=o.alternate;if(o.lanes===0&&(D===null||D.lanes===0)&&(D=u.lastRenderedReducer,D!==null))try{var j=u.lastRenderedState,te=D(j,p);if(M.hasEagerState=!0,M.eagerState=te,ia(te,j))return sg(o,u,M,0),Pn===null&&ag(),!1}catch{}finally{}if(p=Ib(o,u,M,_),p!==null)return Vr(p,o,_),IR(p,u,_),!0}return!1}function x_(o,u,p,_){if(_={lane:2,revertLane:K_(),gesture:null,action:_,hasEagerState:!1,eagerState:null,next:null},Ag(o)){if(u)throw Error(i(479))}else u=Ib(o,p,_,2),u!==null&&Vr(u,o,2)}function Ag(o){var u=o.alternate;return o===Bt||u!==null&&u===Bt}function LR(o,u){td=yg=!0;var p=o.pending;p===null?u.next=u:(u.next=p.next,p.next=u),o.pending=u}function IR(o,u,p){if((p&4194048)!==0){var _=u.lanes;_&=o.pendingLanes,p|=_,u.lanes=p,Rn(o,p)}}var Th={readContext:rr,use:Sg,useCallback:Zn,useContext:Zn,useEffect:Zn,useImperativeHandle:Zn,useLayoutEffect:Zn,useInsertionEffect:Zn,useMemo:Zn,useReducer:Zn,useRef:Zn,useState:Zn,useDebugValue:Zn,useDeferredValue:Zn,useTransition:Zn,useSyncExternalStore:Zn,useId:Zn,useHostTransitionStatus:Zn,useFormState:Zn,useActionState:Zn,useOptimistic:Zn,useMemoCache:Zn,useCacheRefresh:Zn};Th.useEffectEvent=Zn;var UR={readContext:rr,use:Sg,useCallback:function(o,u){return Tr().memoizedState=[o,u===void 0?null:u],o},useContext:rr,useEffect:yR,useImperativeHandle:function(o,u,p){p=p!=null?p.concat([o]):null,Eg(4194308,4,wR.bind(null,u,o),p)},useLayoutEffect:function(o,u){return Eg(4194308,4,o,u)},useInsertionEffect:function(o,u){Eg(4,2,o,u)},useMemo:function(o,u){var p=Tr();u=u===void 0?null:u;var _=o();if(Dc){ct(!0);try{o()}finally{ct(!1)}}return p.memoizedState=[_,u],_},useReducer:function(o,u,p){var _=Tr();if(p!==void 0){var M=p(u);if(Dc){ct(!0);try{p(u)}finally{ct(!1)}}}else M=u;return _.memoizedState=_.baseState=M,o={pending:null,lanes:0,dispatch:null,lastRenderedReducer:o,lastRenderedState:M},_.queue=o,o=o.dispatch=LV.bind(null,Bt,o),[_.memoizedState,o]},useRef:function(o){var u=Tr();return o={current:o},u.memoizedState=o},useState:function(o){o=d_(o);var u=o.queue,p=OR.bind(null,Bt,u);return u.dispatch=p,[o.memoizedState,p]},useDebugValue:p_,useDeferredValue:function(o,u){var p=Tr();return m_(p,o,u)},useTransition:function(){var o=d_(!1);return o=CR.bind(null,Bt,o.queue,!0,!1),Tr().memoizedState=o,[!1,o]},useSyncExternalStore:function(o,u,p){var _=Bt,M=Tr();if(rn){if(p===void 0)throw Error(i(407));p=p()}else{if(p=u(),Pn===null)throw Error(i(349));(en&127)!==0||iR(_,u,p)}M.memoizedState=p;var D={value:p,getSnapshot:u};return M.queue=D,yR(aR.bind(null,_,D,o),[o]),_.flags|=2048,id(9,{destroy:void 0},rR.bind(null,_,D,p,u),null),p},useId:function(){var o=Tr(),u=Pn.identifierPrefix;if(rn){var p=As,_=Ms;p=(_&~(1<<32-et(_)-1)).toString(32)+p,u="_"+u+"R_"+p,p=bg++,0<\/script>",D=D.removeChild(D.firstChild);break;case"select":D=typeof _.is=="string"?j.createElement("select",{is:_.is}):j.createElement("select"),_.multiple?D.multiple=!0:_.size&&(D.size=_.size);break;default:D=typeof _.is=="string"?j.createElement(M,{is:_.is}):j.createElement(M)}}D[Wn]=u,D[ei]=_;e:for(j=u.child;j!==null;){if(j.tag===5||j.tag===6)D.appendChild(j.stateNode);else if(j.tag!==4&&j.tag!==27&&j.child!==null){j.child.return=j,j=j.child;continue}if(j===u)break e;for(;j.sibling===null;){if(j.return===null||j.return===u)break e;j=j.return}j.sibling.return=j.return,j=j.sibling}u.stateNode=D;e:switch(sr(D,M,_),M){case"button":case"input":case"select":case"textarea":_=!!_.autoFocus;break e;case"img":_=!0;break e;default:_=!1}_&&oo(u)}}return kn(u),N_(u,u.type,o===null?null:o.memoizedProps,u.pendingProps,p),null;case 6:if(o&&u.stateNode!=null)o.memoizedProps!==_&&oo(u);else{if(typeof _!="string"&&u.stateNode===null)throw Error(i(166));if(o=me.current,qu(u)){if(o=u.stateNode,p=u.memoizedProps,_=null,M=ir,M!==null)switch(M.tag){case 27:case 5:_=M.memoizedProps}o[Wn]=u,o=!!(o.nodeValue===p||_!==null&&_.suppressHydrationWarning===!0||tD(o.nodeValue,p)),o||dl(u,!0)}else o=Wg(o).createTextNode(_),o[Wn]=u,u.stateNode=o}return kn(u),null;case 31:if(p=u.memoizedState,o===null||o.memoizedState!==null){if(_=qu(u),p!==null){if(o===null){if(!_)throw Error(i(318));if(o=u.memoizedState,o=o!==null?o.dehydrated:null,!o)throw Error(i(557));o[Wn]=u}else Ec(),(u.flags&128)===0&&(u.memoizedState=null),u.flags|=4;kn(u),o=!1}else p=Hb(),o!==null&&o.memoizedState!==null&&(o.memoizedState.hydrationErrors=p),o=!0;if(!o)return u.flags&256?(sa(u),u):(sa(u),null);if((u.flags&128)!==0)throw Error(i(558))}return kn(u),null;case 13:if(_=u.memoizedState,o===null||o.memoizedState!==null&&o.memoizedState.dehydrated!==null){if(M=qu(u),_!==null&&_.dehydrated!==null){if(o===null){if(!M)throw Error(i(318));if(M=u.memoizedState,M=M!==null?M.dehydrated:null,!M)throw Error(i(317));M[Wn]=u}else Ec(),(u.flags&128)===0&&(u.memoizedState=null),u.flags|=4;kn(u),M=!1}else M=Hb(),o!==null&&o.memoizedState!==null&&(o.memoizedState.hydrationErrors=M),M=!0;if(!M)return u.flags&256?(sa(u),u):(sa(u),null)}return sa(u),(u.flags&128)!==0?(u.lanes=p,u):(p=_!==null,o=o!==null&&o.memoizedState!==null,p&&(_=u.child,M=null,_.alternate!==null&&_.alternate.memoizedState!==null&&_.alternate.memoizedState.cachePool!==null&&(M=_.alternate.memoizedState.cachePool.pool),D=null,_.memoizedState!==null&&_.memoizedState.cachePool!==null&&(D=_.memoizedState.cachePool.pool),D!==M&&(_.flags|=2048)),p!==o&&p&&(u.child.flags|=8192),Dg(u,u.updateQueue),kn(u),null);case 4:return he(),o===null&&eS(u.stateNode.containerInfo),kn(u),null;case 10:return io(u.type),kn(u),null;case 19:if(q(ni),_=u.memoizedState,_===null)return kn(u),null;if(M=(u.flags&128)!==0,D=_.rendering,D===null)if(M)Rh(_,!1);else{if(Qn!==0||o!==null&&(o.flags&128)!==0)for(o=u.child;o!==null;){if(D=xg(o),D!==null){for(u.flags|=128,Rh(_,!1),o=D.updateQueue,u.updateQueue=o,Dg(u,o),u.subtreeFlags=0,o=p,p=u.child;p!==null;)O3(p,o),p=p.sibling;return re(ni,ni.current&1|2),rn&&to(u,_.treeForkCount),u.child}o=o.sibling}_.tail!==null&&O()>Ug&&(u.flags|=128,M=!0,Rh(_,!1),u.lanes=4194304)}else{if(!M)if(o=xg(D),o!==null){if(u.flags|=128,M=!0,o=o.updateQueue,u.updateQueue=o,Dg(u,o),Rh(_,!0),_.tail===null&&_.tailMode==="hidden"&&!D.alternate&&!rn)return kn(u),null}else 2*O()-_.renderingStartTime>Ug&&p!==536870912&&(u.flags|=128,M=!0,Rh(_,!1),u.lanes=4194304);_.isBackwards?(D.sibling=u.child,u.child=D):(o=_.last,o!==null?o.sibling=D:u.child=D,_.last=D)}return _.tail!==null?(o=_.tail,_.rendering=o,_.tail=o.sibling,_.renderingStartTime=O(),o.sibling=null,p=ni.current,re(ni,M?p&1|2:p&1),rn&&to(u,_.treeForkCount),o):(kn(u),null);case 22:case 23:return sa(u),t_(),_=u.memoizedState!==null,o!==null?o.memoizedState!==null!==_&&(u.flags|=8192):_&&(u.flags|=8192),_?(p&536870912)!==0&&(u.flags&128)===0&&(kn(u),u.subtreeFlags&6&&(u.flags|=8192)):kn(u),p=u.updateQueue,p!==null&&Dg(u,p.retryQueue),p=null,o!==null&&o.memoizedState!==null&&o.memoizedState.cachePool!==null&&(p=o.memoizedState.cachePool.pool),_=null,u.memoizedState!==null&&u.memoizedState.cachePool!==null&&(_=u.memoizedState.cachePool.pool),_!==p&&(u.flags|=2048),o!==null&&q(Tc),null;case 24:return p=null,o!==null&&(p=o.memoizedState.cache),u.memoizedState.cache!==p&&(u.flags|=2048),io(ci),kn(u),null;case 25:return null;case 30:return null}throw Error(i(156,u.tag))}function zV(o,u){switch(Bb(u),u.tag){case 1:return o=u.flags,o&65536?(u.flags=o&-65537|128,u):null;case 3:return io(ci),he(),o=u.flags,(o&65536)!==0&&(o&128)===0?(u.flags=o&-65537|128,u):null;case 26:case 27:case 5:return Be(u),null;case 31:if(u.memoizedState!==null){if(sa(u),u.alternate===null)throw Error(i(340));Ec()}return o=u.flags,o&65536?(u.flags=o&-65537|128,u):null;case 13:if(sa(u),o=u.memoizedState,o!==null&&o.dehydrated!==null){if(u.alternate===null)throw Error(i(340));Ec()}return o=u.flags,o&65536?(u.flags=o&-65537|128,u):null;case 19:return q(ni),null;case 4:return he(),null;case 10:return io(u.type),null;case 22:case 23:return sa(u),t_(),o!==null&&q(Tc),o=u.flags,o&65536?(u.flags=o&-65537|128,u):null;case 24:return io(ci),null;case 25:return null;default:return null}}function sP(o,u){switch(Bb(u),u.tag){case 3:io(ci),he();break;case 26:case 27:case 5:Be(u);break;case 4:he();break;case 31:u.memoizedState!==null&&sa(u);break;case 13:sa(u);break;case 19:q(ni);break;case 10:io(u.type);break;case 22:case 23:sa(u),t_(),o!==null&&q(Tc);break;case 24:io(ci)}}function Ph(o,u){try{var p=u.updateQueue,_=p!==null?p.lastEffect:null;if(_!==null){var M=_.next;p=M;do{if((p.tag&o)===o){_=void 0;var D=p.create,j=p.inst;_=D(),j.destroy=_}p=p.next}while(p!==M)}}catch(te){xn(u,u.return,te)}}function xl(o,u,p){try{var _=u.updateQueue,M=_!==null?_.lastEffect:null;if(M!==null){var D=M.next;_=D;do{if((_.tag&o)===o){var j=_.inst,te=j.destroy;if(te!==void 0){j.destroy=void 0,M=u;var Se=p,ze=te;try{ze()}catch(Qe){xn(M,Se,Qe)}}}_=_.next}while(_!==D)}}catch(Qe){xn(u,u.return,Qe)}}function oP(o){var u=o.updateQueue;if(u!==null){var p=o.stateNode;try{Z3(u,p)}catch(_){xn(o,o.return,_)}}}function lP(o,u,p){p.props=Nc(o.type,o.memoizedProps),p.state=o.memoizedState;try{p.componentWillUnmount()}catch(_){xn(o,u,_)}}function Dh(o,u){try{var p=o.ref;if(p!==null){switch(o.tag){case 26:case 27:case 5:var _=o.stateNode;break;case 30:_=o.stateNode;break;default:_=o.stateNode}typeof p=="function"?o.refCleanup=p(_):p.current=_}}catch(M){xn(o,u,M)}}function Ts(o,u){var p=o.ref,_=o.refCleanup;if(p!==null)if(typeof _=="function")try{_()}catch(M){xn(o,u,M)}finally{o.refCleanup=null,o=o.alternate,o!=null&&(o.refCleanup=null)}else if(typeof p=="function")try{p(null)}catch(M){xn(o,u,M)}else p.current=null}function cP(o){var u=o.type,p=o.memoizedProps,_=o.stateNode;try{e:switch(u){case"button":case"input":case"select":case"textarea":p.autoFocus&&_.focus();break e;case"img":p.src?_.src=p.src:p.srcSet&&(_.srcset=p.srcSet)}}catch(M){xn(o,o.return,M)}}function O_(o,u,p){try{var _=o.stateNode;o9(_,o.type,p,u),_[ei]=u}catch(M){xn(o,o.return,M)}}function uP(o){return o.tag===5||o.tag===3||o.tag===26||o.tag===27&&Ml(o.type)||o.tag===4}function L_(o){e:for(;;){for(;o.sibling===null;){if(o.return===null||uP(o.return))return null;o=o.return}for(o.sibling.return=o.return,o=o.sibling;o.tag!==5&&o.tag!==6&&o.tag!==18;){if(o.tag===27&&Ml(o.type)||o.flags&2||o.child===null||o.tag===4)continue e;o.child.return=o,o=o.child}if(!(o.flags&2))return o.stateNode}}function I_(o,u,p){var _=o.tag;if(_===5||_===6)o=o.stateNode,u?(p.nodeType===9?p.body:p.nodeName==="HTML"?p.ownerDocument.body:p).insertBefore(o,u):(u=p.nodeType===9?p.body:p.nodeName==="HTML"?p.ownerDocument.body:p,u.appendChild(o),p=p._reactRootContainer,p!=null||u.onclick!==null||(u.onclick=Qs));else if(_!==4&&(_===27&&Ml(o.type)&&(p=o.stateNode,u=null),o=o.child,o!==null))for(I_(o,u,p),o=o.sibling;o!==null;)I_(o,u,p),o=o.sibling}function Ng(o,u,p){var _=o.tag;if(_===5||_===6)o=o.stateNode,u?p.insertBefore(o,u):p.appendChild(o);else if(_!==4&&(_===27&&Ml(o.type)&&(p=o.stateNode),o=o.child,o!==null))for(Ng(o,u,p),o=o.sibling;o!==null;)Ng(o,u,p),o=o.sibling}function dP(o){var u=o.stateNode,p=o.memoizedProps;try{for(var _=o.type,M=u.attributes;M.length;)u.removeAttributeNode(M[0]);sr(u,_,p),u[Wn]=o,u[ei]=p}catch(D){xn(o,o.return,D)}}var lo=!1,fi=!1,U_=!1,fP=typeof WeakSet=="function"?WeakSet:Set,Gi=null;function BV(o,u){if(o=o.containerInfo,iS=Jg,o=E3(o),Rb(o)){if("selectionStart"in o)var p={start:o.selectionStart,end:o.selectionEnd};else e:{p=(p=o.ownerDocument)&&p.defaultView||window;var _=p.getSelection&&p.getSelection();if(_&&_.rangeCount!==0){p=_.anchorNode;var M=_.anchorOffset,D=_.focusNode;_=_.focusOffset;try{p.nodeType,D.nodeType}catch{p=null;break e}var j=0,te=-1,Se=-1,ze=0,Qe=0,it=o,Ge=null;t:for(;;){for(var Ye;it!==p||M!==0&&it.nodeType!==3||(te=j+M),it!==D||_!==0&&it.nodeType!==3||(Se=j+_),it.nodeType===3&&(j+=it.nodeValue.length),(Ye=it.firstChild)!==null;)Ge=it,it=Ye;for(;;){if(it===o)break t;if(Ge===p&&++ze===M&&(te=j),Ge===D&&++Qe===_&&(Se=j),(Ye=it.nextSibling)!==null)break;it=Ge,Ge=it.parentNode}it=Ye}p=te===-1||Se===-1?null:{start:te,end:Se}}else p=null}p=p||{start:0,end:0}}else p=null;for(rS={focusedElem:o,selectionRange:p},Jg=!1,Gi=u;Gi!==null;)if(u=Gi,o=u.child,(u.subtreeFlags&1028)!==0&&o!==null)o.return=u,Gi=o;else for(;Gi!==null;){switch(u=Gi,D=u.alternate,o=u.flags,u.tag){case 0:if((o&4)!==0&&(o=u.updateQueue,o=o!==null?o.events:null,o!==null))for(p=0;p title"))),sr(D,_,p),D[Wn]=o,Re(D),_=D;break e;case"link":var j=xD("link","href",M).get(_+(p.href||""));if(j){for(var te=0;teSn&&(j=Sn,Sn=Ot,Ot=j);var Oe=S3(te,Ot),Te=S3(te,Sn);if(Oe&&Te&&(Ye.rangeCount!==1||Ye.anchorNode!==Oe.node||Ye.anchorOffset!==Oe.offset||Ye.focusNode!==Te.node||Ye.focusOffset!==Te.offset)){var Fe=it.createRange();Fe.setStart(Oe.node,Oe.offset),Ye.removeAllRanges(),Ot>Sn?(Ye.addRange(Fe),Ye.extend(Te.node,Te.offset)):(Fe.setEnd(Te.node,Te.offset),Ye.addRange(Fe))}}}}for(it=[],Ye=te;Ye=Ye.parentNode;)Ye.nodeType===1&&it.push({element:Ye,left:Ye.scrollLeft,top:Ye.scrollTop});for(typeof te.focus=="function"&&te.focus(),te=0;tep?32:p,X.T=null,p=G_,G_=null;var D=Sl,j=po;if(Mi=0,ld=Sl=null,po=0,(hn&6)!==0)throw Error(i(331));var te=hn;if(hn|=4,wP(D.current),bP(D,D.current,j,p),hn=te,kh(0,!1),De&&typeof De.onPostCommitFiberRoot=="function")try{De.onPostCommitFiberRoot(ke,D)}catch{}return!0}finally{V.p=M,X.T=_,jP(o,u)}}function GP(o,u,p){u=Ma(p,u),u=S_(o.stateNode,u,2),o=ml(o,u,2),o!==null&&(Wt(o,2),Cs(o))}function xn(o,u,p){if(o.tag===3)GP(o,o,p);else for(;u!==null;){if(u.tag===3){GP(u,o,p);break}else if(u.tag===1){var _=u.stateNode;if(typeof u.type.getDerivedStateFromError=="function"||typeof _.componentDidCatch=="function"&&(_l===null||!_l.has(_))){o=Ma(p,o),p=VR(2),_=ml(u,p,2),_!==null&&($R(p,_,u,o),Wt(_,2),Cs(_));break}}u=u.return}}function X_(o,u,p){var _=o.pingCache;if(_===null){_=o.pingCache=new GV;var M=new Set;_.set(u,M)}else M=_.get(u),M===void 0&&(M=new Set,_.set(u,M));M.has(p)||(z_=!0,M.add(p),o=qV.bind(null,o,u,p),u.then(o,o))}function qV(o,u,p){var _=o.pingCache;_!==null&&_.delete(u),o.pingedLanes|=o.suspendedLanes&p,o.warmLanes&=~p,Pn===o&&(en&p)===p&&(Qn===4||Qn===3&&(en&62914560)===en&&300>O()-Ig?(hn&2)===0&&cd(o,0):B_|=p,od===en&&(od=0)),Cs(o)}function VP(o,u){u===0&&(u=mt()),o=Sc(o,u),o!==null&&(Wt(o,u),Cs(o))}function YV(o){var u=o.memoizedState,p=0;u!==null&&(p=u.retryLane),VP(o,p)}function KV(o,u){var p=0;switch(o.tag){case 31:case 13:var _=o.stateNode,M=o.memoizedState;M!==null&&(p=M.retryLane);break;case 19:_=o.stateNode;break;case 22:_=o.stateNode._retryCache;break;default:throw Error(i(314))}_!==null&&_.delete(u),VP(o,p)}function ZV(o,u){return ie(o,u)}var Hg=null,dd=null,q_=!1,Gg=!1,Y_=!1,El=0;function Cs(o){o!==dd&&o.next===null&&(dd===null?Hg=dd=o:dd=dd.next=o),Gg=!0,q_||(q_=!0,JV())}function kh(o,u){if(!Y_&&Gg){Y_=!0;do for(var p=!1,_=Hg;_!==null;){if(o!==0){var M=_.pendingLanes;if(M===0)var D=0;else{var j=_.suspendedLanes,te=_.pingedLanes;D=(1<<31-et(42|o)+1)-1,D&=M&~(j&~te),D=D&201326741?D&201326741|1:D?D|2:0}D!==0&&(p=!0,qP(_,D))}else D=en,D=ge(_,_===Pn?D:0,_.cancelPendingCommit!==null||_.timeoutHandle!==-1),(D&3)===0||je(_,D)||(p=!0,qP(_,D));_=_.next}while(p);Y_=!1}}function QV(){$P()}function $P(){Gg=q_=!1;var o=0;El!==0&&c9()&&(o=El);for(var u=O(),p=null,_=Hg;_!==null;){var M=_.next,D=WP(_,u);D===0?(_.next=null,p===null?Hg=M:p.next=M,M===null&&(dd=p)):(p=_,(o!==0||(D&3)!==0)&&(Gg=!0)),_=M}Mi!==0&&Mi!==5||kh(o),El!==0&&(El=0)}function WP(o,u){for(var p=o.suspendedLanes,_=o.pingedLanes,M=o.expirationTimes,D=o.pendingLanes&-62914561;0te)break;var Qe=Se.transferSize,it=Se.initiatorType;Qe&&nD(it)&&(Se=Se.responseEnd,j+=Qe*(Se"u"?null:document;function pD(o,u,p){var _=fd;if(_&&typeof u=="string"&&u){var M=pn(u);M='link[rel="'+o+'"][href="'+M+'"]',typeof p=="string"&&(M+='[crossorigin="'+p+'"]'),hD.has(M)||(hD.add(M),o={rel:o,crossOrigin:p,href:u},_.querySelector(M)===null&&(u=_.createElement("link"),sr(u,"link",o),Re(u),_.head.appendChild(u)))}}function x9(o){mo.D(o),pD("dns-prefetch",o,null)}function y9(o,u){mo.C(o,u),pD("preconnect",o,u)}function b9(o,u,p){mo.L(o,u,p);var _=fd;if(_&&o&&u){var M='link[rel="preload"][as="'+pn(u)+'"]';u==="image"&&p&&p.imageSrcSet?(M+='[imagesrcset="'+pn(p.imageSrcSet)+'"]',typeof p.imageSizes=="string"&&(M+='[imagesizes="'+pn(p.imageSizes)+'"]')):M+='[href="'+pn(o)+'"]';var D=M;switch(u){case"style":D=hd(o);break;case"script":D=pd(o)}Da.has(D)||(o=h({rel:"preload",href:u==="image"&&p&&p.imageSrcSet?void 0:o,as:u},p),Da.set(D,o),_.querySelector(M)!==null||u==="style"&&_.querySelector(jh(D))||u==="script"&&_.querySelector(Hh(D))||(u=_.createElement("link"),sr(u,"link",o),Re(u),_.head.appendChild(u)))}}function _9(o,u){mo.m(o,u);var p=fd;if(p&&o){var _=u&&typeof u.as=="string"?u.as:"script",M='link[rel="modulepreload"][as="'+pn(_)+'"][href="'+pn(o)+'"]',D=M;switch(_){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":D=pd(o)}if(!Da.has(D)&&(o=h({rel:"modulepreload",href:o},u),Da.set(D,o),p.querySelector(M)===null)){switch(_){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":if(p.querySelector(Hh(D)))return}_=p.createElement("link"),sr(_,"link",o),Re(_),p.head.appendChild(_)}}}function S9(o,u,p){mo.S(o,u,p);var _=fd;if(_&&o){var M=Pe(_).hoistableStyles,D=hd(o);u=u||"default";var j=M.get(D);if(!j){var te={loading:0,preload:null};if(j=_.querySelector(jh(D)))te.loading=5;else{o=h({rel:"stylesheet",href:o,"data-precedence":u},p),(p=Da.get(D))&&dS(o,p);var Se=j=_.createElement("link");Re(Se),sr(Se,"link",o),Se._p=new Promise(function(ze,Qe){Se.onload=ze,Se.onerror=Qe}),Se.addEventListener("load",function(){te.loading|=1}),Se.addEventListener("error",function(){te.loading|=2}),te.loading|=4,qg(j,u,_)}j={type:"stylesheet",instance:j,count:1,state:te},M.set(D,j)}}}function w9(o,u){mo.X(o,u);var p=fd;if(p&&o){var _=Pe(p).hoistableScripts,M=pd(o),D=_.get(M);D||(D=p.querySelector(Hh(M)),D||(o=h({src:o,async:!0},u),(u=Da.get(M))&&fS(o,u),D=p.createElement("script"),Re(D),sr(D,"link",o),p.head.appendChild(D)),D={type:"script",instance:D,count:1,state:null},_.set(M,D))}}function E9(o,u){mo.M(o,u);var p=fd;if(p&&o){var _=Pe(p).hoistableScripts,M=pd(o),D=_.get(M);D||(D=p.querySelector(Hh(M)),D||(o=h({src:o,async:!0,type:"module"},u),(u=Da.get(M))&&fS(o,u),D=p.createElement("script"),Re(D),sr(D,"link",o),p.head.appendChild(D)),D={type:"script",instance:D,count:1,state:null},_.set(M,D))}}function mD(o,u,p,_){var M=(M=me.current)?Xg(M):null;if(!M)throw Error(i(446));switch(o){case"meta":case"title":return null;case"style":return typeof p.precedence=="string"&&typeof p.href=="string"?(u=hd(p.href),p=Pe(M).hoistableStyles,_=p.get(u),_||(_={type:"style",instance:null,count:0,state:null},p.set(u,_)),_):{type:"void",instance:null,count:0,state:null};case"link":if(p.rel==="stylesheet"&&typeof p.href=="string"&&typeof p.precedence=="string"){o=hd(p.href);var D=Pe(M).hoistableStyles,j=D.get(o);if(j||(M=M.ownerDocument||M,j={type:"stylesheet",instance:null,count:0,state:{loading:0,preload:null}},D.set(o,j),(D=M.querySelector(jh(o)))&&!D._p&&(j.instance=D,j.state.loading=5),Da.has(o)||(p={rel:"preload",as:"style",href:p.href,crossOrigin:p.crossOrigin,integrity:p.integrity,media:p.media,hrefLang:p.hrefLang,referrerPolicy:p.referrerPolicy},Da.set(o,p),D||M9(M,o,p,j.state))),u&&_===null)throw Error(i(528,""));return j}if(u&&_!==null)throw Error(i(529,""));return null;case"script":return u=p.async,p=p.src,typeof p=="string"&&u&&typeof u!="function"&&typeof u!="symbol"?(u=pd(p),p=Pe(M).hoistableScripts,_=p.get(u),_||(_={type:"script",instance:null,count:0,state:null},p.set(u,_)),_):{type:"void",instance:null,count:0,state:null};default:throw Error(i(444,o))}}function hd(o){return'href="'+pn(o)+'"'}function jh(o){return'link[rel="stylesheet"]['+o+"]"}function gD(o){return h({},o,{"data-precedence":o.precedence,precedence:null})}function M9(o,u,p,_){o.querySelector('link[rel="preload"][as="style"]['+u+"]")?_.loading=1:(u=o.createElement("link"),_.preload=u,u.addEventListener("load",function(){return _.loading|=1}),u.addEventListener("error",function(){return _.loading|=2}),sr(u,"link",p),Re(u),o.head.appendChild(u))}function pd(o){return'[src="'+pn(o)+'"]'}function Hh(o){return"script[async]"+o}function vD(o,u,p){if(u.count++,u.instance===null)switch(u.type){case"style":var _=o.querySelector('style[data-href~="'+pn(p.href)+'"]');if(_)return u.instance=_,Re(_),_;var M=h({},p,{"data-href":p.href,"data-precedence":p.precedence,href:null,precedence:null});return _=(o.ownerDocument||o).createElement("style"),Re(_),sr(_,"style",M),qg(_,p.precedence,o),u.instance=_;case"stylesheet":M=hd(p.href);var D=o.querySelector(jh(M));if(D)return u.state.loading|=4,u.instance=D,Re(D),D;_=gD(p),(M=Da.get(M))&&dS(_,M),D=(o.ownerDocument||o).createElement("link"),Re(D);var j=D;return j._p=new Promise(function(te,Se){j.onload=te,j.onerror=Se}),sr(D,"link",_),u.state.loading|=4,qg(D,p.precedence,o),u.instance=D;case"script":return D=pd(p.src),(M=o.querySelector(Hh(D)))?(u.instance=M,Re(M),M):(_=p,(M=Da.get(D))&&(_=h({},p),fS(_,M)),o=o.ownerDocument||o,M=o.createElement("script"),Re(M),sr(M,"link",_),o.head.appendChild(M),u.instance=M);case"void":return null;default:throw Error(i(443,u.type))}else u.type==="stylesheet"&&(u.state.loading&4)===0&&(_=u.instance,u.state.loading|=4,qg(_,p.precedence,o));return u.instance}function qg(o,u,p){for(var _=p.querySelectorAll('link[rel="stylesheet"][data-precedence],style[data-precedence]'),M=_.length?_[_.length-1]:null,D=M,j=0;j<_.length;j++){var te=_[j];if(te.dataset.precedence===u)D=te;else if(D!==M)break}D?D.parentNode.insertBefore(o,D.nextSibling):(u=p.nodeType===9?p.head:p,u.insertBefore(o,u.firstChild))}function dS(o,u){o.crossOrigin==null&&(o.crossOrigin=u.crossOrigin),o.referrerPolicy==null&&(o.referrerPolicy=u.referrerPolicy),o.title==null&&(o.title=u.title)}function fS(o,u){o.crossOrigin==null&&(o.crossOrigin=u.crossOrigin),o.referrerPolicy==null&&(o.referrerPolicy=u.referrerPolicy),o.integrity==null&&(o.integrity=u.integrity)}var Yg=null;function xD(o,u,p){if(Yg===null){var _=new Map,M=Yg=new Map;M.set(p,_)}else M=Yg,_=M.get(p),_||(_=new Map,M.set(p,_));if(_.has(o))return _;for(_.set(o,null),p=p.getElementsByTagName(o),M=0;M title"):null)}function A9(o,u,p){if(p===1||u.itemProp!=null)return!1;switch(o){case"meta":case"title":return!0;case"style":if(typeof u.precedence!="string"||typeof u.href!="string"||u.href==="")break;return!0;case"link":if(typeof u.rel!="string"||typeof u.href!="string"||u.href===""||u.onLoad||u.onError)break;switch(u.rel){case"stylesheet":return o=u.disabled,typeof u.precedence=="string"&&o==null;default:return!0}case"script":if(u.async&&typeof u.async!="function"&&typeof u.async!="symbol"&&!u.onLoad&&!u.onError&&u.src&&typeof u.src=="string")return!0}return!1}function bD(o){return!(o.type==="stylesheet"&&(o.state.loading&3)===0)}function T9(o,u,p,_){if(p.type==="stylesheet"&&(typeof _.media!="string"||matchMedia(_.media).matches!==!1)&&(p.state.loading&4)===0){if(p.instance===null){var M=hd(_.href),D=u.querySelector(jh(M));if(D){u=D._p,u!==null&&typeof u=="object"&&typeof u.then=="function"&&(o.count++,o=Kg.bind(o),u.then(o,o)),p.state.loading|=4,p.instance=D,Re(D);return}D=u.ownerDocument||u,_=gD(_),(M=Da.get(M))&&dS(_,M),D=D.createElement("link"),Re(D);var j=D;j._p=new Promise(function(te,Se){j.onload=te,j.onerror=Se}),sr(D,"link",_),p.instance=D}o.stylesheets===null&&(o.stylesheets=new Map),o.stylesheets.set(p,u),(u=p.state.preload)&&(p.state.loading&3)===0&&(o.count++,p=Kg.bind(o),u.addEventListener("load",p),u.addEventListener("error",p))}}var hS=0;function C9(o,u){return o.stylesheets&&o.count===0&&Qg(o,o.stylesheets),0hS?50:800)+u);return o.unsuspend=p,function(){o.unsuspend=null,clearTimeout(_),clearTimeout(M)}}:null}function Kg(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)Qg(this,this.stylesheets);else if(this.unsuspend){var o=this.unsuspend;this.unsuspend=null,o()}}}var Zg=null;function Qg(o,u){o.stylesheets=null,o.unsuspend!==null&&(o.count++,Zg=new Map,u.forEach(R9,o),Zg=null,Kg.call(o))}function R9(o,u){if(!(u.state.loading&4)){var p=Zg.get(o);if(p)var _=p.get(null);else{p=new Map,Zg.set(o,p);for(var M=o.querySelectorAll("link[data-precedence],style[data-precedence]"),D=0;D"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(t)}catch(e){console.error(e)}}return t(),SS.exports=W9(),SS.exports}var q9=X9();var VD="popstate";function Y9(t={}){function e(i,r){let{pathname:a,search:s,hash:l}=i.location;return LE("",{pathname:a,search:s,hash:l},r.state&&r.state.usr||null,r.state&&r.state.key||"default")}function n(i,r){return typeof r=="string"?r:Lp(r)}return Z9(e,n,null,t)}function Xn(t,e){if(t===!1||t===null||typeof t>"u")throw new Error(e)}function ys(t,e){if(!t){typeof console<"u"&&console.warn(e);try{throw new Error(e)}catch{}}}function K9(){return Math.random().toString(36).substring(2,10)}function $D(t,e){return{usr:t.state,key:t.key,idx:e}}function LE(t,e,n=null,i){return{pathname:typeof t=="string"?t:t.pathname,search:"",hash:"",...typeof e=="string"?Uf(e):e,state:n,key:e&&e.key||i||K9()}}function Lp({pathname:t="/",search:e="",hash:n=""}){return e&&e!=="?"&&(t+=e.charAt(0)==="?"?e:"?"+e),n&&n!=="#"&&(t+=n.charAt(0)==="#"?n:"#"+n),t}function Uf(t){let e={};if(t){let n=t.indexOf("#");n>=0&&(e.hash=t.substring(n),t=t.substring(0,n));let i=t.indexOf("?");i>=0&&(e.search=t.substring(i),t=t.substring(0,i)),t&&(e.pathname=t)}return e}function Z9(t,e,n,i={}){let{window:r=document.defaultView,v5Compat:a=!1}=i,s=r.history,l="POP",c=null,d=f();d==null&&(d=0,s.replaceState({...s.state,idx:d},""));function f(){return(s.state||{idx:null}).idx}function h(){l="POP";let x=f(),v=x==null?null:x-d;d=x,c&&c({action:l,location:y.location,delta:v})}function g(x,v){l="PUSH";let w=LE(y.location,x,v);d=f()+1;let S=$D(w,d),A=y.createHref(w);try{s.pushState(S,"",A)}catch(R){if(R instanceof DOMException&&R.name==="DataCloneError")throw R;r.location.assign(A)}a&&c&&c({action:l,location:y.location,delta:1})}function m(x,v){l="REPLACE";let w=LE(y.location,x,v);d=f();let S=$D(w,d),A=y.createHref(w);s.replaceState(S,"",A),a&&c&&c({action:l,location:y.location,delta:0})}function b(x){return Q9(x)}let y={get action(){return l},get location(){return t(r,s)},listen(x){if(c)throw new Error("A history only accepts one active listener");return r.addEventListener(VD,h),c=x,()=>{r.removeEventListener(VD,h),c=null}},createHref(x){return e(r,x)},createURL:b,encodeLocation(x){let v=b(x);return{pathname:v.pathname,search:v.search,hash:v.hash}},push:g,replace:m,go(x){return s.go(x)}};return y}function Q9(t,e=!1){let n="http://localhost";typeof window<"u"&&(n=window.location.origin!=="null"?window.location.origin:window.location.href),Xn(n,"No window.location.(origin|href) available to create URL");let i=typeof t=="string"?t:Lp(t);return i=i.replace(/ $/,"%20"),!e&&i.startsWith("//")&&(i=n+i),new URL(i,n)}function J4(t,e,n="/"){return J9(t,e,n,!1)}function J9(t,e,n,i){let r=typeof e=="string"?Uf(e):e,a=Vo(r.pathname||"/",n);if(a==null)return null;let s=ez(t);e$(s);let l=null;for(let c=0;l==null&&c{let f={relativePath:d===void 0?s.path||"":d,caseSensitive:s.caseSensitive===!0,childrenIndex:l,route:s};if(f.relativePath.startsWith("/")){if(!f.relativePath.startsWith(i)&&c)return;Xn(f.relativePath.startsWith(i),`Absolute route path "${f.relativePath}" nested under path "${i}" is not valid. An absolute child route path must start with the combined path of all its parent routes.`),f.relativePath=f.relativePath.slice(i.length)}let h=Fo([i,f.relativePath]),g=n.concat(f);s.children&&s.children.length>0&&(Xn(s.index!==!0,`Index routes must not have child routes. Please remove all child routes from route path "${h}".`),ez(s.children,e,g,h,c)),!(s.path==null&&!s.index)&&e.push({path:h,score:o$(h,s.index),routesMeta:g})};return t.forEach((s,l)=>{if(s.path===""||!s.path?.includes("?"))a(s,l);else for(let c of tz(s.path))a(s,l,!0,c)}),e}function tz(t){let e=t.split("/");if(e.length===0)return[];let[n,...i]=e,r=n.endsWith("?"),a=n.replace(/\?$/,"");if(i.length===0)return r?[a,""]:[a];let s=tz(i.join("/")),l=[];return l.push(...s.map(c=>c===""?a:[a,c].join("/"))),r&&l.push(...s),l.map(c=>t.startsWith("/")&&c===""?"/":c)}function e$(t){t.sort((e,n)=>e.score!==n.score?n.score-e.score:l$(e.routesMeta.map(i=>i.childrenIndex),n.routesMeta.map(i=>i.childrenIndex)))}var t$=/^:[\w-]+$/,n$=3,i$=2,r$=1,a$=10,s$=-2,WD=t=>t==="*";function o$(t,e){let n=t.split("/"),i=n.length;return n.some(WD)&&(i+=s$),e&&(i+=i$),n.filter(r=>!WD(r)).reduce((r,a)=>r+(t$.test(a)?n$:a===""?r$:a$),i)}function l$(t,e){return t.length===e.length&&t.slice(0,-1).every((i,r)=>i===e[r])?t[t.length-1]-e[e.length-1]:0}function c$(t,e,n=!1){let{routesMeta:i}=t,r={},a="/",s=[];for(let l=0;l{if(f==="*"){let b=l[g]||"";s=a.slice(0,a.length-b.length).replace(/(.)\/+$/,"$1")}const m=l[g];return h&&!m?d[f]=void 0:d[f]=(m||"").replace(/%2F/g,"/"),d},{}),pathname:a,pathnameBase:s,pattern:t}}function u$(t,e=!1,n=!0){ys(t==="*"||!t.endsWith("*")||t.endsWith("/*"),`Route path "${t}" will be treated as if it were "${t.replace(/\*$/,"/*")}" because the \`*\` character must always follow a \`/\` in the pattern. To get rid of this warning, please change the route path to "${t.replace(/\*$/,"/*")}".`);let i=[],r="^"+t.replace(/\/*\*?$/,"").replace(/^\/*/,"/").replace(/[\\.*+^${}|()[\]]/g,"\\$&").replace(/\/:([\w-]+)(\?)?/g,(s,l,c)=>(i.push({paramName:l,isOptional:c!=null}),c?"/?([^\\/]+)?":"/([^\\/]+)")).replace(/\/([\w-]+)\?(\/|$)/g,"(/$1)?$2");return t.endsWith("*")?(i.push({paramName:"*"}),r+=t==="*"||t==="/*"?"(.*)$":"(?:\\/(.+)|\\/*)$"):n?r+="\\/*$":t!==""&&t!=="/"&&(r+="(?:(?=\\/|$))"),[new RegExp(r,e?void 0:"i"),i]}function d$(t){try{return t.split("/").map(e=>decodeURIComponent(e).replace(/\//g,"%2F")).join("/")}catch(e){return ys(!1,`The URL path "${t}" could not be decoded because it is a malformed URL segment. This is probably due to a bad percent encoding (${e}).`),t}}function Vo(t,e){if(e==="/")return t;if(!t.toLowerCase().startsWith(e.toLowerCase()))return null;let n=e.endsWith("/")?e.length-1:e.length,i=t.charAt(n);return i&&i!=="/"?null:t.slice(n)||"/"}var f$=/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i,h$=t=>f$.test(t);function p$(t,e="/"){let{pathname:n,search:i="",hash:r=""}=typeof t=="string"?Uf(t):t,a;if(n)if(h$(n))a=n;else{if(n.includes("//")){let s=n;n=n.replace(/\/\/+/g,"/"),ys(!1,`Pathnames cannot have embedded double slashes - normalizing ${s} -> ${n}`)}n.startsWith("/")?a=XD(n.substring(1),"/"):a=XD(n,e)}else a=e;return{pathname:a,search:v$(i),hash:x$(r)}}function XD(t,e){let n=e.replace(/\/+$/,"").split("/");return t.split("/").forEach(r=>{r===".."?n.length>1&&n.pop():r!=="."&&n.push(r)}),n.length>1?n.join("/"):"/"}function AS(t,e,n,i){return`Cannot include a '${t}' character in a manually specified \`to.${e}\` field [${JSON.stringify(i)}]. Please separate it out to the \`to.${n}\` field. Alternatively you may provide the full path as a string in and the router will parse it for you.`}function m$(t){return t.filter((e,n)=>n===0||e.route.path&&e.route.path.length>0)}function nz(t){let e=m$(t);return e.map((n,i)=>i===e.length-1?n.pathname:n.pathnameBase)}function iz(t,e,n,i=!1){let r;typeof t=="string"?r=Uf(t):(r={...t},Xn(!r.pathname||!r.pathname.includes("?"),AS("?","pathname","search",r)),Xn(!r.pathname||!r.pathname.includes("#"),AS("#","pathname","hash",r)),Xn(!r.search||!r.search.includes("#"),AS("#","search","hash",r)));let a=t===""||r.pathname==="",s=a?"/":r.pathname,l;if(s==null)l=n;else{let h=e.length-1;if(!i&&s.startsWith("..")){let g=s.split("/");for(;g[0]==="..";)g.shift(),h-=1;r.pathname=g.join("/")}l=h>=0?e[h]:"/"}let c=p$(r,l),d=s&&s!=="/"&&s.endsWith("/"),f=(a||s===".")&&n.endsWith("/");return!c.pathname.endsWith("/")&&(d||f)&&(c.pathname+="/"),c}var Fo=t=>t.join("/").replace(/\/\/+/g,"/"),g$=t=>t.replace(/\/+$/,"").replace(/^\/*/,"/"),v$=t=>!t||t==="?"?"":t.startsWith("?")?t:"?"+t,x$=t=>!t||t==="#"?"":t.startsWith("#")?t:"#"+t;function y$(t){return t!=null&&typeof t.status=="number"&&typeof t.statusText=="string"&&typeof t.internal=="boolean"&&"data"in t}Object.getOwnPropertyNames(Object.prototype).sort().join("\0");var rz=["POST","PUT","PATCH","DELETE"];new Set(rz);var b$=["GET",...rz];new Set(b$);var kf=T.createContext(null);kf.displayName="DataRouter";var cy=T.createContext(null);cy.displayName="DataRouterState";T.createContext(!1);var az=T.createContext({isTransitioning:!1});az.displayName="ViewTransition";var _$=T.createContext(new Map);_$.displayName="Fetchers";var S$=T.createContext(null);S$.displayName="Await";var qs=T.createContext(null);qs.displayName="Navigation";var pm=T.createContext(null);pm.displayName="Location";var Ys=T.createContext({outlet:null,matches:[],isDataRoute:!1});Ys.displayName="Route";var nT=T.createContext(null);nT.displayName="RouteError";function w$(t,{relative:e}={}){Xn(mm(),"useHref() may be used only in the context of a component.");let{basename:n,navigator:i}=T.useContext(qs),{hash:r,pathname:a,search:s}=gm(t,{relative:e}),l=a;return n!=="/"&&(l=a==="/"?n:Fo([n,a])),i.createHref({pathname:l,search:s,hash:r})}function mm(){return T.useContext(pm)!=null}function Ou(){return Xn(mm(),"useLocation() may be used only in the context of a component."),T.useContext(pm).location}var sz="You should call navigate() in a React.useEffect(), not when your component is first rendered.";function oz(t){T.useContext(qs).static||T.useLayoutEffect(t)}function lz(){let{isDataRoute:t}=T.useContext(Ys);return t?F$():E$()}function E$(){Xn(mm(),"useNavigate() may be used only in the context of a component.");let t=T.useContext(kf),{basename:e,navigator:n}=T.useContext(qs),{matches:i}=T.useContext(Ys),{pathname:r}=Ou(),a=JSON.stringify(nz(i)),s=T.useRef(!1);return oz(()=>{s.current=!0}),T.useCallback((c,d={})=>{if(ys(s.current,sz),!s.current)return;if(typeof c=="number"){n.go(c);return}let f=iz(c,JSON.parse(a),r,d.relative==="path");t==null&&e!=="/"&&(f.pathname=f.pathname==="/"?e:Fo([e,f.pathname])),(d.replace?n.replace:n.push)(f,d.state,d)},[e,n,a,r,t])}var M$=T.createContext(null);function A$(t){let e=T.useContext(Ys).outlet;return T.useMemo(()=>e&&T.createElement(M$.Provider,{value:t},e),[e,t])}function gm(t,{relative:e}={}){let{matches:n}=T.useContext(Ys),{pathname:i}=Ou(),r=JSON.stringify(nz(n));return T.useMemo(()=>iz(t,JSON.parse(r),i,e==="path"),[t,r,i,e])}function T$(t,e){return cz(t,e)}function cz(t,e,n,i,r){Xn(mm(),"useRoutes() may be used only in the context of a component.");let{navigator:a}=T.useContext(qs),{matches:s}=T.useContext(Ys),l=s[s.length-1],c=l?l.params:{},d=l?l.pathname:"/",f=l?l.pathnameBase:"/",h=l&&l.route;{let w=h&&h.path||"";uz(d,!h||w.endsWith("*")||w.endsWith("*?"),`You rendered descendant (or called \`useRoutes()\`) at "${d}" (under ) but the parent route path has no trailing "*". This means if you navigate deeper, the parent won't match anymore and therefore the child routes will never render. - -Please change the parent to .`)}let g=Ou(),m;if(e){let w=typeof e=="string"?Uf(e):e;Xn(f==="/"||w.pathname?.startsWith(f),`When overriding the location using \`\` or \`useRoutes(routes, location)\`, the location pathname must begin with the portion of the URL pathname that was matched by all parent routes. The current pathname base is "${f}" but pathname "${w.pathname}" was given in the \`location\` prop.`),m=w}else m=g;let b=m.pathname||"/",y=b;if(f!=="/"){let w=f.replace(/^\//,"").split("/");y="/"+b.replace(/^\//,"").split("/").slice(w.length).join("/")}let x=J4(t,{pathname:y});ys(h||x!=null,`No routes matched location "${m.pathname}${m.search}${m.hash}" `),ys(x==null||x[x.length-1].route.element!==void 0||x[x.length-1].route.Component!==void 0||x[x.length-1].route.lazy!==void 0,`Matched leaf route at location "${m.pathname}${m.search}${m.hash}" does not have an element or Component. This means it will render an with a null value by default resulting in an "empty" page.`);let v=N$(x&&x.map(w=>Object.assign({},w,{params:Object.assign({},c,w.params),pathname:Fo([f,a.encodeLocation?a.encodeLocation(w.pathname.replace(/\?/g,"%3F").replace(/#/g,"%23")).pathname:w.pathname]),pathnameBase:w.pathnameBase==="/"?f:Fo([f,a.encodeLocation?a.encodeLocation(w.pathnameBase.replace(/\?/g,"%3F").replace(/#/g,"%23")).pathname:w.pathnameBase])})),s,n,i,r);return e&&v?T.createElement(pm.Provider,{value:{location:{pathname:"/",search:"",hash:"",state:null,key:"default",...m},navigationType:"POP"}},v):v}function C$(){let t=k$(),e=y$(t)?`${t.status} ${t.statusText}`:t instanceof Error?t.message:JSON.stringify(t),n=t instanceof Error?t.stack:null,i="rgba(200,200,200, 0.5)",r={padding:"0.5rem",backgroundColor:i},a={padding:"2px 4px",backgroundColor:i},s=null;return console.error("Error handled by React Router default ErrorBoundary:",t),s=T.createElement(T.Fragment,null,T.createElement("p",null,"💿 Hey developer 👋"),T.createElement("p",null,"You can provide a way better UX than this when your app throws errors by providing your own ",T.createElement("code",{style:a},"ErrorBoundary")," or"," ",T.createElement("code",{style:a},"errorElement")," prop on your route.")),T.createElement(T.Fragment,null,T.createElement("h2",null,"Unexpected Application Error!"),T.createElement("h3",{style:{fontStyle:"italic"}},e),n?T.createElement("pre",{style:r},n):null,s)}var R$=T.createElement(C$,null),P$=class extends T.Component{constructor(t){super(t),this.state={location:t.location,revalidation:t.revalidation,error:t.error}}static getDerivedStateFromError(t){return{error:t}}static getDerivedStateFromProps(t,e){return e.location!==t.location||e.revalidation!=="idle"&&t.revalidation==="idle"?{error:t.error,location:t.location,revalidation:t.revalidation}:{error:t.error!==void 0?t.error:e.error,location:e.location,revalidation:t.revalidation||e.revalidation}}componentDidCatch(t,e){this.props.onError?this.props.onError(t,e):console.error("React Router caught the following error during render",t)}render(){return this.state.error!==void 0?T.createElement(Ys.Provider,{value:this.props.routeContext},T.createElement(nT.Provider,{value:this.state.error,children:this.props.component})):this.props.children}};function D$({routeContext:t,match:e,children:n}){let i=T.useContext(kf);return i&&i.static&&i.staticContext&&(e.route.errorElement||e.route.ErrorBoundary)&&(i.staticContext._deepestRenderedBoundaryId=e.route.id),T.createElement(Ys.Provider,{value:t},n)}function N$(t,e=[],n=null,i=null,r=null){if(t==null){if(!n)return null;if(n.errors)t=n.matches;else if(e.length===0&&!n.initialized&&n.matches.length>0)t=n.matches;else return null}let a=t,s=n?.errors;if(s!=null){let f=a.findIndex(h=>h.route.id&&s?.[h.route.id]!==void 0);Xn(f>=0,`Could not find a matching route for errors on route IDs: ${Object.keys(s).join(",")}`),a=a.slice(0,Math.min(a.length,f+1))}let l=!1,c=-1;if(n)for(let f=0;f=0?a=a.slice(0,c+1):a=[a[0]];break}}}let d=n&&i?(f,h)=>{i(f,{location:n.location,params:n.matches?.[0]?.params??{},errorInfo:h})}:void 0;return a.reduceRight((f,h,g)=>{let m,b=!1,y=null,x=null;n&&(m=s&&h.route.id?s[h.route.id]:void 0,y=h.route.errorElement||R$,l&&(c<0&&g===0?(uz("route-fallback",!1,"No `HydrateFallback` element provided to render during initial hydration"),b=!0,x=null):c===g&&(b=!0,x=h.route.hydrateFallbackElement||null)));let v=e.concat(a.slice(0,g+1)),w=()=>{let S;return m?S=y:b?S=x:h.route.Component?S=T.createElement(h.route.Component,null):h.route.element?S=h.route.element:S=f,T.createElement(D$,{match:h,routeContext:{outlet:f,matches:v,isDataRoute:n!=null},children:S})};return n&&(h.route.ErrorBoundary||h.route.errorElement||g===0)?T.createElement(P$,{location:n.location,revalidation:n.revalidation,component:y,error:m,children:w(),routeContext:{outlet:null,matches:v,isDataRoute:!0},onError:d}):w()},null)}function iT(t){return`${t} must be used within a data router. See https://reactrouter.com/en/main/routers/picking-a-router.`}function O$(t){let e=T.useContext(kf);return Xn(e,iT(t)),e}function L$(t){let e=T.useContext(cy);return Xn(e,iT(t)),e}function I$(t){let e=T.useContext(Ys);return Xn(e,iT(t)),e}function rT(t){let e=I$(t),n=e.matches[e.matches.length-1];return Xn(n.route.id,`${t} can only be used on routes that contain a unique "id"`),n.route.id}function U$(){return rT("useRouteId")}function k$(){let t=T.useContext(nT),e=L$("useRouteError"),n=rT("useRouteError");return t!==void 0?t:e.errors?.[n]}function F$(){let{router:t}=O$("useNavigate"),e=rT("useNavigate"),n=T.useRef(!1);return oz(()=>{n.current=!0}),T.useCallback(async(r,a={})=>{ys(n.current,sz),n.current&&(typeof r=="number"?t.navigate(r):await t.navigate(r,{fromRouteId:e,...a}))},[t,e])}var qD={};function uz(t,e,n){!e&&!qD[t]&&(qD[t]=!0,ys(!1,n))}T.memo(z$);function z$({routes:t,future:e,state:n,unstable_onError:i}){return cz(t,void 0,n,i,e)}function B$(t){return A$(t.context)}function Wc(t){Xn(!1,"A is only ever to be used as the child of element, never rendered directly. Please wrap your in a .")}function j$({basename:t="/",children:e=null,location:n,navigationType:i="POP",navigator:r,static:a=!1}){Xn(!mm(),"You cannot render a inside another . You should never have more than one in your app.");let s=t.replace(/^\/*/,"/"),l=T.useMemo(()=>({basename:s,navigator:r,static:a,future:{}}),[s,r,a]);typeof n=="string"&&(n=Uf(n));let{pathname:c="/",search:d="",hash:f="",state:h=null,key:g="default"}=n,m=T.useMemo(()=>{let b=Vo(c,s);return b==null?null:{location:{pathname:b,search:d,hash:f,state:h,key:g},navigationType:i}},[s,c,d,f,h,g,i]);return ys(m!=null,` is not able to match the URL "${c}${d}${f}" because it does not start with the basename, so the won't render anything.`),m==null?null:T.createElement(qs.Provider,{value:l},T.createElement(pm.Provider,{children:e,value:m}))}function H$({children:t,location:e}){return T$(IE(t),e)}function IE(t,e=[]){let n=[];return T.Children.forEach(t,(i,r)=>{if(!T.isValidElement(i))return;let a=[...e,r];if(i.type===T.Fragment){n.push.apply(n,IE(i.props.children,a));return}Xn(i.type===Wc,`[${typeof i.type=="string"?i.type:i.type.name}] is not a component. All component children of must be a or `),Xn(!i.props.index||!i.props.children,"An index route cannot have child routes.");let s={id:i.props.id||a.join("-"),caseSensitive:i.props.caseSensitive,element:i.props.element,Component:i.props.Component,index:i.props.index,path:i.props.path,middleware:i.props.middleware,loader:i.props.loader,action:i.props.action,hydrateFallbackElement:i.props.hydrateFallbackElement,HydrateFallback:i.props.HydrateFallback,errorElement:i.props.errorElement,ErrorBoundary:i.props.ErrorBoundary,hasErrorBoundary:i.props.hasErrorBoundary===!0||i.props.ErrorBoundary!=null||i.props.errorElement!=null,shouldRevalidate:i.props.shouldRevalidate,handle:i.props.handle,lazy:i.props.lazy};i.props.children&&(s.children=IE(i.props.children,a)),n.push(s)}),n}var Tv="get",Cv="application/x-www-form-urlencoded";function uy(t){return t!=null&&typeof t.tagName=="string"}function G$(t){return uy(t)&&t.tagName.toLowerCase()==="button"}function V$(t){return uy(t)&&t.tagName.toLowerCase()==="form"}function $$(t){return uy(t)&&t.tagName.toLowerCase()==="input"}function W$(t){return!!(t.metaKey||t.altKey||t.ctrlKey||t.shiftKey)}function X$(t,e){return t.button===0&&(!e||e==="_self")&&!W$(t)}var o0=null;function q$(){if(o0===null)try{new FormData(document.createElement("form"),0),o0=!1}catch{o0=!0}return o0}var Y$=new Set(["application/x-www-form-urlencoded","multipart/form-data","text/plain"]);function TS(t){return t!=null&&!Y$.has(t)?(ys(!1,`"${t}" is not a valid \`encType\` for \`
\`/\`\` and will default to "${Cv}"`),null):t}function K$(t,e){let n,i,r,a,s;if(V$(t)){let l=t.getAttribute("action");i=l?Vo(l,e):null,n=t.getAttribute("method")||Tv,r=TS(t.getAttribute("enctype"))||Cv,a=new FormData(t)}else if(G$(t)||$$(t)&&(t.type==="submit"||t.type==="image")){let l=t.form;if(l==null)throw new Error('Cannot submit a