[
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\ncharset = utf-8\nend_of_line = lf\ninsert_final_newline = true\nindent_style = space\nindent_size = 4\ntrim_trailing_whitespace = true\n\n[*.md]\ntrim_trailing_whitespace = false\n\n[*.yml]\nindent_size = 2\n\n[*.css]\nindent_size = 2\n"
  },
  {
    "path": ".gitattributes",
    "content": "* text=auto\nCHANGELOG.md export-ignore\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "content": "name: Bug Report\ndescription: Fill out a bug report about something that's broken.\nbody:\n  - type: textarea\n    attributes:\n      label: Bug description\n      description: What happened? What did you expect to happen? Feel free to drop any screenshots in here.\n      placeholder: I did this, and then this happened...\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: Steps to reproduce\n      description: List the steps so we're able to recreate this bug. If possible, please provide a GitHub repository to demonstrate your issue.\n      placeholder: Go here, type this, click that, look over there.\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: Logs\n      description: You can paste any relevant logs here, they'll be automatically rendered in code blocks. You can find your logs in `storage/logs`.\n      render: shell\n  - type: textarea\n    attributes:\n      label: Environment\n      description: |\n        Details about your environment. Versions of Waterhole, PHP, Laravel, any extensions that are installed, etc.\n        Paste the output of the `php artisan about --only=environment` command.\n      render: yaml\n    validations:\n      required: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\n\ncontact_links:\n  - name: Feature Requests and Ideas\n    url: https://waterhole.dev/community/channels/ideas\n    about: 'Please submit feature requests to the Ideas channel in our community.'\n  - name: Support Questions & Other\n    url: https://waterhole.dev/support\n    about: 'This repository is only for reporting bugs. If you have a question or need help using Waterhole, see our support page.'\n  - name: Documentation Issue\n    url: https://github.com/waterholeforum/docs\n    about: For documentation issues, open a pull request at the waterholeforum/docs repository.\n"
  },
  {
    "path": ".github/SECURITY.md",
    "content": "# Security Policy\n\n## Reporting a Vulnerability\n\nIf you discover a security vulnerability, please report the issue directly to us from [waterhole.dev/support](https://waterhole.dev/support). We will review and respond privately via email. All security vulnerabilities will be promptly addressed.\n\nWe are only interested in vulnerabilities that affect Waterhole itself, tested against your own local installation of the software, running the latest version. You can install a local copy of Waterhole by following these [installation instructions](https://waterhole.dev/docs/installation). Do not test against any Waterhole installation that you don't own, including waterhole.dev.\n"
  },
  {
    "path": ".github/workflows/build-assets.yml",
    "content": "name: Build Assets\n\non: [push, pull_request]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    permissions:\n      contents: write\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v3\n\n      - name: Install pnpm\n        uses: pnpm/action-setup@v4\n        with:\n          cache: true\n\n      - name: Build\n        run: |\n          pnpm i\n          pnpm build\n\n      - name: Commit\n        uses: stefanzweifel/git-auto-commit-action@v4\n        with:\n          commit_message: 'Build assets'\n          file_pattern: resources/dist/*\n"
  },
  {
    "path": ".github/workflows/phpstan.yml",
    "content": "name: PHPStan\n\non: [push, pull_request]\n\njobs:\n  phpstan:\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v3\n\n      - name: Setup PHP\n        uses: shivammathur/setup-php@v2\n        with:\n          php-version: '8.2'\n          coverage: none\n\n      - name: Install dependencies\n        run: composer install --prefer-dist\n\n      - name: Run PHPStan\n        run: vendor/bin/phpstan analyze\n"
  },
  {
    "path": ".github/workflows/prettier.yml",
    "content": "name: Prettier\n\non: [push, pull_request]\n\njobs:\n  prettier:\n    runs-on: ubuntu-latest\n\n    permissions:\n      contents: write\n\n    steps:\n      - uses: actions/checkout@v3\n        with:\n          # Make sure the actual branch is checked out when running on pull requests\n          ref: ${{ github.head_ref }}\n          fetch-depth: 0\n\n      - name: Install pnpm\n        uses: pnpm/action-setup@v4\n        with:\n          cache: true\n\n      - name: Build\n        run: |\n          pnpm i\n          pnpm prettier\n\n      - name: Commit changes\n        uses: stefanzweifel/git-auto-commit-action@v4\n        with:\n          commit_message: Run Prettier\n          branch: ${{ github.head_ref }}\n"
  },
  {
    "path": ".github/workflows/stale.yml",
    "content": "name: Close Stale Issues\n\non:\n  workflow_dispatch:\n  schedule:\n    - cron: '30 1 * * *'\n\njobs:\n  stale:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/stale@v7\n        with:\n          only-labels: 'needs more info'\n          stale-issue-label: stale\n          stale-issue-message: >\n            This issue has not had recent activity and has been marked as stale.\n            Reply to keep it open – otherwise I will close it in a week.\n"
  },
  {
    "path": ".github/workflows/tests.yml",
    "content": "name: Tests\n\non: [push, pull_request]\n\njobs:\n  tests:\n    runs-on: ubuntu-latest\n\n    services:\n      mysql:\n        image: mysql:8.0\n        env:\n          MYSQL_ALLOW_EMPTY_PASSWORD: yes\n          MYSQL_DATABASE: waterhole_test\n        ports:\n          - 33306:3306\n        options: --health-cmd=\"mysqladmin ping\" --health-interval=10s --health-timeout=5s --health-retries=3\n\n    strategy:\n      fail-fast: true\n      matrix:\n        php: [8.2, 8.3, 8.4]\n\n    name: PHP ${{ matrix.php }}\n\n    steps:\n      - uses: actions/checkout@v3\n\n      - name: Setup PHP\n        uses: shivammathur/setup-php@v2\n        with:\n          php-version: ${{ matrix.php }}\n          coverage: none\n\n      - name: Install dependencies\n        run: composer update --prefer-dist --no-interaction --no-progress\n\n      - name: Run tests\n        run: vendor/bin/pest\n        env:\n          DB_PORT: ${{ job.services.mysql.ports[3306] }}\n          DB_USERNAME: root\n"
  },
  {
    "path": ".gitignore",
    "content": ".phpunit.cache\nnode_modules\n/vendor\ncomposer.lock\n.DS_Store\nThumbs.db\n.idea\n.fleet\n.vscode\n.phpunit.result.cache\n"
  },
  {
    "path": ".prettierignore",
    "content": "config\nnode_modules\nresources/dist\nresources/views/mail\nvendor\n"
  },
  {
    "path": ".prettierrc",
    "content": "{\n    \"singleQuote\": true,\n    \"phpVersion\": \"8.1\",\n    \"plugins\": [\"@prettier/plugin-php\", \"prettier-plugin-blade\"],\n    \"overrides\": [\n        {\n            \"files\": [\"*.blade.php\"],\n            \"options\": {\n                \"parser\": \"blade\"\n            }\n        },\n        {\n            \"files\": [\"*.php\"],\n            \"options\": {\n                \"printWidth\": 100\n            }\n        },\n        {\n            \"files\": [\"*.css\"],\n            \"options\": {\n                \"parser\": \"less\"\n            }\n        }\n    ]\n}\n"
  },
  {
    "path": ".release-it.yml",
    "content": "npm: false\n\ngit:\n  commitMessage: 'Release v${version}'\n  tagName: v${version}\n\ngithub:\n  release: true\n  releaseName: v${version}\n\nplugins:\n  '@release-it/bumper':\n    out:\n      file: src/Waterhole.php\n      type: text/php\n  '@release-it/keep-a-changelog':\n    filename: CHANGELOG.md\n    addVersionUrl: true\n    addUnreleased: true\n\nhooks:\n  after:bump: pnpm build\n  after:release:\n    'curl -f -X POST --location \"https://api.waterhole.dev/releases\"\n    -H \"Content-Type: application/json\"\n    -H \"Accept: application/json\"\n    -H \"Authorization: Bearer $WATERHOLE_TOKEN\"\n    -d \"{\\\"version\\\": \\\"${version}\\\"}\"'\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),\nand this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n\n## [Unreleased]\n\n### Added\n\n- Add support for Laravel 13\n\n### Removed\n\n- Remove unused Workbench setup from the package Testbench configuration\n\n## [0.6.2] - 2026-02-15\n\n### Fixed\n\n- Fix dismiss flag action authorization check\n- Bypass global visibility scope in CLI/queue workers\n\n## [0.6.1] - 2026-02-09\n\n### Changed\n\n- Switch discussion structured data on post/comment pages from JSON-LD to Microdata\n\n### Fixed\n\n- Fix migration using wrong database connection ([#100](https://github.com/waterholeforum/core/pull/100))\n- Fix discussion structured data to include required forum fields (`author`, `datePublished`, `text`)\n- Fix channel page SEO description containing HTML\n- Filter out empty search results for posts that are not visible to the current user\n- Fix strict flag eager loading error for non-moderators\n- Fall back to the app default database connection when a Waterhole connection is not configured\n\n## [0.6.0] - 2026-01-31\n\n### ⚠️ Breaking Changes\n\n- Replace public comment hiding with moderator-only soft-deletion\n- Rename `PostScopes` extender to `PostVisibilityScopes`\n\n### Added\n\n- Add basic SEO, including meta descriptions, Open Graph tags, Twitter card tags, JSON-LD structured data, and nofollow rules configurable in `waterhole.seo` config\n- Add user reports and moderator flag queue\n- Allow groups and channels to require moderator approval for new posts/comments\n- Allow groups to be auto-assigned to new users\n- Add comment soft-deletion with ability to select a reason and message\n- Add \"Suspend users\" permission for groups\n- Add configurable post/comment edit time limit (`waterhole.forum.edit_time_limit`)\n- Allow configuring permissions for the built-in Public and Member groups\n- Allow configuring the appearance of the built-in Admin group badge\n- Add support for SVG icon file uploads\n- Add MariaDB, PostgreSQL, and SQLite database support\n- Add support for Reverb as the broadcasting driver\n- Allow disabling reactions per-channel\n- Allow configuring uploads (`waterhole.uploads.disk`) and assets (`waterhole.system.assets_disk`) disks\n- Add support for SSO `PendingUser::forceName` option\n- Add success message to copy link action\n- Add confirmation for Composer repository addition in `waterhole:make:extension`\n- Add `CommentQuery` extender for comment list queries\n- Allow configuring search engine via `waterhole.system.search_engine`\n- Improve version number display in CP\n- Add a \"last\" pagination link in sidebar while reading post\n\n### Changed\n\n- Use `DateTimeInterface` to support `CarbonImmutable` dates ([#88](https://github.com/waterholeforum/core/pulls/88) by @BasilLangevin)\n- Various styling tweaks\n- Migrate asset pipeline to `tsdown`\n- Upgrade to Turbo 8\n- Replace `inclusive-sort` with `dnd-kit` for sortable UI\n- Upgrade Intervention Image to 3.x\n- Use cursor pagination for notifications list\n- Move license alert to more prominent location on CP dashboard\n- Rename `PostScopes` extender to `PostVisibilityScopes`\n\n### Fixed\n\n- Improve compatibility with Statamic ([#82](https://github.com/waterholeforum/core/issues/82))\n- Fix actions dropdown for trashed posts ([#89](https://github.com/waterholeforum/core/issues/89))\n- Fix MariaDB crash due to mixed types in notification queries ([#90](https://github.com/waterholeforum/core/pulls/90) by @iamdarkle)\n- Fix crash when saving a new channel with taxonomies ([#91](https://github.com/waterholeforum/core/pulls/91) by @iamdarkle)\n- Fix crash when using table prefix ([#94](https://github.com/waterholeforum/core/issues/94))\n- Retain tags when switching filters on the post feed\n- Fix flash alerts sometimes not displaying\n- Sort trashed items by latest deleted date\n- Fix crash when viewing content that mentions a deleted user\n- Fix edit user submission returning to actions menu route\n- Fix modal backdrop persisting when `prefers-reduced-motion` is enabled\n\n### Security\n\n- Fix an XSS vulnerability\n- Fix JSON:API exposing sensitive user information\n\n## [0.5.0] - 2026-01-02\n\n### ⚠️ Breaking Changes\n\n- Refresh the extension API with new namespaces and container-resolved extenders\n- Replace `reactionsSummary()` with `reactionCounts()` for reaction totals and user-reacted state\n\n### Added\n\n- Add a read-only JSON:API for forum data with includes, sorting, filtering, and pagination\n- Add API configuration for enabling, public/private access, and path in `config/waterhole/api.php`\n- Add `waterhole:openapi` commmand to generate an OpenAPI schema for the API\n\n### Changed\n\n- Rely on Laravel core for the `Registered` event email verification listener\n- Add `Waterhole::isApiRoute()` and route-name based checks for forum/CP detection\n\n### Fixed\n\n- Fix permission lookups when IDs are returned as strings in some databases\n- Avoid loading `userState` relationship for guests\n- Fix PHP 8.4 deprecation warning\n\n## [0.4.12] - 2025-04-23\n\n### Fixed\n\n- Fix incorrect RSS feed `pubDate` for unedited posts\n\n## [0.4.11] - 2025-04-21\n\n### Added\n\n- Add support for Laravel 12\n- Add descriptive error message for CSRF token expiry ([#73](https://github.com/waterholeforum/core/issues/73))\n\n### Fixed\n\n- Fix taxonomies not being assignable by non-admins ([#75](https://github.com/waterholeforum/core/issues/75))\n- Fix error when sending non-Waterhole notifications ([#79](https://github.com/waterholeforum/core/issues/79))\n- Fix `FluentTranslator` not forwarding macros calls ([#81](https://github.com/waterholeforum/core/pull/81) by @jacksleight)\n- Prevent post page sidebar width from changing\n- Fix MariaDB `ONLY_FULL_GROUP_BY` error\n- Fix HTML entities in notification email subjects\n\n## [0.4.10] - 2024-10-03\n\n### Fixed\n\n- Fix some search queries causing a server error\n- Fix formatter rendering cache error when Waterhole is run across multiple servers\n\n## [0.4.9] - 2024-05-16\n\n### Fixed\n\n- Fix redirecting to incorrect URL when there is a sole auth provider\n- Fix lazy loading error in some reaction configurations\n- Fix broken Dutch localization ([#77](https://github.com/waterholeforum/core/pull/77) by @Emmanuel71git)\n\n## [0.4.8] - 2024-04-16\n\n### Added\n\n- Add support for Laravel 11\n\n### Fixed\n\n- Fix \"show more\" rendering incorrectly on Firefox ([#64](https://github.com/waterholeforum/core/issues/64))\n- Fix videos having no max width\n- Fix emojis appearing full-width in notification emails ([#67](https://github.com/waterholeforum/core/issues/67))\n- Fix PHP deprecation error ([#69](https://github.com/waterholeforum/core/issues/69))\n\n## [0.4.7] - 2024-03-17\n\n### Added\n\n- Add PHP 8.3 compatibility ([#62](https://github.com/waterholeforum/core/pull/62) by @saturnphp)\n- Add Dutch translation ([#60](https://github.com/waterholeforum/core/pull/60) by @Emmanuel71)\n\n### Fixed\n\n- Fix Chrome bug where textarea scrolls the viewport when selecting text\n- Fix post slug mutation when title is highlighted in search results\n- Fix icons displaying incorrectly with latest version of `blade-tabler-icons` ([#63](https://github.com/waterholeforum/core/issues/63))\n\n## [0.4.6] - 2024-02-02\n\n### Fixed\n\n- Fix compatibility with `Model::shouldBeStrict()` ([#56](https://github.com/waterholeforum/core/issues/56))\n- Fall back to base translator when using translation string keys ([#57](https://github.com/waterholeforum/core/pull/57) by @dsturm)\n- Fall back to resource directory for missing translation bundle ([#58](https://github.com/waterholeforum/core/pull/58) by @dsturm)\n\n## [0.4.5] - 2024-01-31\n\n### Fixed\n\n- Fix incorrect database connection being used in some cases\n- Fix success message not appearing after change email/password\n\n## [0.4.4] - 2024-01-30\n\n### Fixed\n\n- Fix an issue with Laravel auth integration causing user to be logged out after registration\n\n## [0.4.3] - 2024-01-26\n\n### Fixed\n\n- Fix incorrect database connection being used in some cases ([#55](https://github.com/waterholeforum/core/issues/55))\n\n## [0.4.2] - 2023-12-31\n\n### Fixed\n\n- Fix issues with Laravel integration ([#53](https://github.com/waterholeforum/core/issues/53))\n- Add missing translations for CP reactions\n\n## [0.4.1] - 2023-12-24\n\n### Fixed\n\n- Fix auth routes using incorrect path ([#51](https://github.com/waterholeforum/core/issues/51))\n- Fix reactions not working due to incorrect permission check\n- Fix error when sending notification to unverified email\n\n## [0.4.0] - 2023-12-23\n\n### ⚠️ Breaking Changes\n\n- `waterhole.auth.oauth_providers` config key renamed to `waterhole.auth.providers`\n- `.oauth-button` class renamed to `.auth-button`\n- `<x-waterhole::oauth-buttons>` component renamed to `<x-waterhole::auth-buttons>`\n- `Waterhole\\OAuth\\Payload` class renamed to `Waterhole\\Auth\\SsoPayload`\n- Waterhole Gate abilities are now prefixed with `waterhole.`\n- Waterhole routes no longer use the `web` route middleware group\n\n### Added\n\n- New `sso` auth provider to support custom single sign-on flows\n- Auth guard used by Waterhole requests can be configured by setting `waterhole.auth.guard`\n- Database connection can be configured by setting `waterhole.system.database`\n- Route domain can be configured by settings `waterhole.system.domain`\n- Support implicit authentication from existing user base by implementing `Waterhole\\Auth\\AuthenticatesWaterhole` interface\n- Automatically create the formatter and translation cache directories if they don't exist\n- Laravel Socialite is now included by default\n- Update Traditional Chinese (zh-Hant) translation ([#48](https://github.com/waterholeforum/core/pull/48) by @efast1568)\n\n### Changed\n\n- If there is a single auth provider and passwords are disabled, the login and registration pages will now automatically redirect to the provider\n- Add color to inline code spans\n- Reduce size of the Create Post button\n- Waterhole routes no longer rely on app-aliased middleware\n\n### Fixed\n\n- Fix the forum URL shown at the end of the installation command\n- Fix emoji picker inserting emoji multiple times after navigations\n- Fix \"last reply\" link to jump to the last comment instead of below it\n- Fix CP users table pagination/sorting links\n- Only send notifications to verified email addresses\n\n## [0.3.2] - 2023-12-02\n\n### Added\n\n- Show comment button in post footer on mobile\n\n### Fixed\n\n- Fix comment composer being unreachable on mobile ([#44](https://github.com/waterholeforum/core/issues/44))\n- Prevent composer re-appearing on page reload after it has been closed ([#46](https://github.com/waterholeforum/core/issues/46))\n- Fix comment composer not clearing after submission\n- Prevent unnecessary post page load when jumping to page 1\n- Only configure Laravel Echo if Pusher is configured\n- Fix entire page scrolling when navigating through @mention suggestions\n\n## [0.3.1] - 2023-11-17\n\n### Added\n\n- Add Traditional Chinese (zh-Hant) translation ([#8](https://github.com/waterholeforum/core/pull/8) by @efast1568)\n- Update French translation ([#14](https://github.com/waterholeforum/core/pull/14) by @qiaeru)\n\n### Changed\n\n- Load reaction counts as a relationship rather than via query scopes\n\n### Fixed\n\n- Fix reactions disappearing when following/unfollowing a post\n- Opt out of smooth scrolling on Google Chrome\n- Fix Copy Link action not working\n- Disable login submit button to prevent double-submission\n- Fix mobile page selector sometimes displaying incorrect page number\n- Fix active nav items and buttons not highlighted in Firefox\n- Remove `max-height` from images causing loss of aspect ratio\n- Remove extra space from post title on comment page\n- Fix post Delete Forever action not working\n- Fix crawlers causing 500 error with invalid pagination cursor\n- Don't scroll all the way to bottom of the page when opening composer\n\n## [0.3.0] - 2023-07-31\n\n### Added\n\n- Show recent commenters and original poster in @mention suggestions\n- Add ability to pin posts to top of feed\n- Add ability to hide comments\n- Show links in channel picker when creating post\n- Get a \"mention\" notification when someone replies to your comment\n- Add a comment button in the post sidebar\n- Add ability to soft-delete posts\n- Add `.btn--start` and `.btn--end` classes\n- Add `--color-fill-soft` token and `.bg-fill-soft` utility\n\n### Changed\n\n- Use more compact layout for comments on small screens\n- Use color to make search result keyword highlights more prominent\n- Clamp `--space-gutter` between `lg` and `xl`\n- Reduce default number of posts/comments per page to improve performance\n- Optimize performance of avatar component\n- Lazy-load the list of reaction users to improve performance\n- Lazy-load action menus to improve performance\n- Make \"Automatically follow posts I comment on\" preference also follow posts you create\n- Limit one notification per user per new comment\n- Change from idiomorph to nanomorph (due to buggy behavior with some attributes of old nodes remaining)\n- Refactored the way Turbo Frames show loading spinners - using CSS instead of HTML\n\n### Removed\n\n- Remove unread badge from channel navigation items to improve performance\n- Remove greeting and subcopy from verify email/reset password emails\n- Remove unnecessary action button `title` attribute ([#21](https://github.com/waterholeforum/core/issues/21))\n\n### Fixed\n\n- Fix emoji alignment in reactions menu\n- Highlight code blocks in streamed content\n- Fix unread posts sometimes jumping to the incorrect page\n- Don't globally override pagination views which is problematic on Octane\n- Fix default layout for Announcements channel\n- Fix avatar display for UTF-8 usernames\n- Fix highlighting UTF-8 characters and phrases in search results ([#12](https://github.com/waterholeforum/core/issues/12), [#25](https://github.com/waterholeforum/core/issues/25))\n- Fix color picker disappearing on Chrome ([#15](https://github.com/waterholeforum/core/issues/15))\n- Fix fetch error alerts not showing in some cases, and allow them to disappear\n- Fix email verification alert layout\n- Fix comment index being incorrectly calculated in some cases\n- Fix composer not disappearing after submitting comment\n- Encode UTF-8 symbols in CSS files\n- Fix other users' notifications getting marked as read\n- Remove whitespace in user links\n- Fix not being able to edit comments on lazy-loaded pages ([#24](https://github.com/waterholeforum/core/issues/24))\n- Add missing translations ([#22](https://github.com/waterholeforum/core/issues/22))\n- Fix copy link action being run multiple times ([#20](https://github.com/waterholeforum/core/issues/20))\n- Fix composer overlapping header on small screens ([#19](https://github.com/waterholeforum/core/issues/19))\n\n## [0.2.0] - 2023-05-30\n\n### Added\n\n- Add Russian translation ([#2](https://github.com/waterholeforum/core/pull/2) by @Awilum)\n- Add French translation ([#5](https://github.com/waterholeforum/core/pull/5) by @qiaeru)\n- Add support for looking up users by ID\n- Make post last activity time link to the last comment\n- Set the `color-scheme` CSS property according to the current theme\n- Add `waterhole:reformat` command to reparse formatted content\n- Add `body_text` getter to `Post` and `Comment` to get plain-text version of the body\n- Add `waterhole.design.emoji_url` config option to configure TextFormatter emoji rendering\n\n### Changed\n\n- Use TextFormatter to render emoji instead of php-twemoji\n- Don't open internal links in a new window\n- Adjust container width and typographic measure\n- Decrease padding on nested comments\n- Change default sort for users table to last created\n- Display more dates using relative time component\n- Stop using deprecated `micro` format for relative times\n- Rename `time-ago` component to `relative-time`\n- Rename `.twemoji` class to `.emoji`\n\n### Removed\n\n- Remove `Waterhole\\Extend\\Emoji` class\n- Remove `waterhole.design.twemoji_base` config option\n- Remove Twemoji rendering in the emoji picker for now\n- Remove unused HTML truncation utils\n\n### Fixed\n\n- Fix text editor emoji popup not displaying\n- Fix a JavaScript error on posts with no comments\n- Fix untranslated variable in resend confirmation email message ([#4](https://github.com/waterholeforum/core/pull/4) by @askerakbar)\n- Fix error when running `waterhole:make:extension` command ([#6](https://github.com/waterholeforum/core/issues/6))\n- Emojify the post title on the single comment page\n- Allow code blocks to wrap in notification emails\n- Fix bottom of composer textarea going off-screen\n- Remove top border from first comment\n- Remove height limit on comment preview tooltips\n- Prevent crashing if database content contains invalid XML\n- Fix localization of dates\n\n### Security\n\n- Fix an XSS vulnerability where HTML could be injected into emojified text\n\n## [0.1.1] - 2023-05-23\n\n### Fixed\n\n- Fix license error alert incorrectly appearing in trial mode.\n\n## [0.1.0] - 2023-05-23\n\nInitial release.\n\n[Unreleased]: https://github.com/waterholeforum/core/compare/v0.6.2...HEAD\n[0.6.2]: https://github.com/waterholeforum/core/compare/v0.6.1...v0.6.2\n[0.6.1]: https://github.com/waterholeforum/core/compare/v0.6.0...v0.6.1\n[0.6.0]: https://github.com/waterholeforum/core/compare/v0.5.0...v0.6.0\n[0.5.0]: https://github.com/waterholeforum/core/compare/v0.4.12...v0.5.0\n[0.4.12]: https://github.com/waterholeforum/core/compare/v0.4.11...v0.4.12\n[0.4.11]: https://github.com/waterholeforum/core/compare/v0.4.10...v0.4.11\n[0.4.10]: https://github.com/waterholeforum/core/compare/v0.4.9...v0.4.10\n[0.4.9]: https://github.com/waterholeforum/core/compare/v0.4.8...v0.4.9\n[0.4.8]: https://github.com/waterholeforum/core/compare/v0.4.7...v0.4.8\n[0.4.7]: https://github.com/waterholeforum/core/compare/v0.4.6...v0.4.7\n[0.4.6]: https://github.com/waterholeforum/core/compare/v0.4.5...v0.4.6\n[0.4.5]: https://github.com/waterholeforum/core/compare/v0.4.4...v0.4.5\n[0.4.4]: https://github.com/waterholeforum/core/compare/v0.4.3...v0.4.4\n[0.4.3]: https://github.com/waterholeforum/core/compare/v0.4.2...v0.4.3\n[0.4.2]: https://github.com/waterholeforum/core/compare/v0.4.1...v0.4.2\n[0.4.1]: https://github.com/waterholeforum/core/compare/v0.4.0...v0.4.1\n[0.4.0]: https://github.com/waterholeforum/core/compare/v0.3.2...v0.4.0\n[0.3.2]: https://github.com/waterholeforum/core/compare/v0.3.1...v0.3.2\n[0.3.1]: https://github.com/waterholeforum/core/compare/v0.3.0...v0.3.1\n[0.3.0]: https://github.com/waterholeforum/core/compare/v0.2.0...v0.3.0\n[0.2.0]: https://github.com/waterholeforum/core/compare/v0.1.1...v0.2.0\n[0.1.1]: https://github.com/waterholeforum/core/compare/v0.1.0...v0.1.1\n[0.1.0]: https://github.com/waterholeforum/core/releases/tag/v0.1.0\n"
  },
  {
    "path": "LICENSE.md",
    "content": "# Waterhole Software Licence Agreement\n\n1. **The Software:** This licence agreement relates to the software program known as 'Waterhole' including any updates that have been incorporated in the software program (\"Software\").\n\n2. **Copyright and Licence:** The copyright in the Software is owned by Waterhole Pty Ltd (Australian Business Number 69 667 084 577) (\"Waterhole\"). Waterhole permits any person to use the Software, but only on the terms of this licence agreement.\n\n3. **Free Trial:** Any person may use the Software in an environment that is purely for the purposes of development and client preview, and only accessible by a restricted number of users (like on a personal computer, on a server in a network with restricted access, or when protecting a staging website with a password that only a restricted number of users know).\n\n4. **Production Licence:** Any person wishing to use the Software in a live, usable operation for the intended end users (\"Production Environment\") is required to either:\n\n    (a) have purchased a licence (\"Licensee\") to use the Software in a Production Environment from Waterhole (\"Production Licence\"); or\n\n    (b) be an officer, employee, agent, contractor, representative or affiliate of the Licensee who has been authorised to use the Software by the Licensee (\"Authorised User\").\n\n    Any use of the Software under a Production Licence is subject to any restrictions or additional terms advised to the Licensee at the time the Production Licence is purchased (“Scope of the Licence”). Where the Scope of the Licence is inconsistent with the terms of this licence agreement, the Scope of the Licence will prevail over the terms of this licence agreement to the extent of that inconsistency.\n\n    The Licensee must ensure that its Authorised Users are aware of the terms of this licence agreement and the Scope of the Licence.\n\n5. **Single Project:** The Licensee and its Authorised Users may only use the Software for its intended purposes, for a single project at a time and in a single Production Environment at a time.\n\n6. **Copying and Distribution:** Any person accessing or using the Software is not permitted to copy, reproduce, merge, publish and/or distribute the Software, unless that person is:\n\n    (a) a Licensee or an Authorised User deploying a website to a server under a Production Licence; or\n\n    (b) copying the Software when developing a website on a personal computer or server; or\n\n    (c) copying the Software when working on code contributions to be made available to Waterhole; or\n\n    (d) copying the Software as a backup; or\n\n    (e) a Licensee or Authorised User making the Software available to third parties via a Software-as-a-Service (SaaS) offering, provided that a separate Production Licence is purchased for each separate project or separate client.\n\n    All proprietary notices and this licence agreement shall be included in all copies or substantial portions of the Software.\n\n7. **Alteration of Software:** Any person may alter, modify or extend the Software. However, they may not:\n\n    (a) alter or circumvent the licensing features, including (but not limited to) the licence validation and payment prompts; or\n\n    (b) resell, redistribute or transfer the modified or derivative version.\n\n8. **Not For Reuse:** The Software and the proprietary code therein, including (but not limited to) designs, components, classes, and patterns, may not be reused in other software or projects.\n\n9. **No Illegal or Immoral Use:** The Software may not be used for any illegal, immoral or objectionable purpose.\n\n10. **Automatic Termination for Non-Compliance:** If any person using this Software does not comply with any requirements of this licence agreement, this licence agreement will automatically terminate and that person, and if that person is an Authorised User then also the Licensee, shall no longer have any right to use the Software.\n\n11. **Separate Agreement for Updates:** This licence agreement does not of itself include any right to receive updates to the Software. Any such right must be purchased from Waterhole.\n\n12. **Use Solely at Own Risk:** Each user of the Software must make their own assessment of the suitability and functionality of the Software and bears all risk related to the quality and performance of the Software, including the risk of actual and consequential harm, such as loss or corruption of data, and any necessary service, repair, or correction.\n\n13. **Disclaimer:** THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, INCLUDING SPECIAL, INCIDENTAL AND CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n14. **Governing Law:** The Scope of the Licence and this licence agreement are governed by the laws of South Australia. Any person using the Software submits to the jurisdiction of South Australian courts in respect of any dispute, action, proceeding or demand arising out of or in connection with the Scope of the Licence, this licence agreement or the use of the Software.\n"
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\">\n    <img src=\"https://waterhole.dev/images/waterhole-logo.svg\" width=\"400\" alt=\"Waterhole logo\" />\n</p>\n\n## About Waterhole\n\nWaterhole is the Laravel-powered community platform giving life to vibrant online communities.\n\n> **Note:** This repository contains the code for the core Waterhole package. To start your own community project with Waterhole, visit the [Waterhole skeleton repository][skeleton].\n\n## Learning Waterhole\n\nWaterhole has extensive [documentation][docs] and a powerful extension API, making it easy to get started building a completely custom community. Feel free to open issues for anything you find confusing or incomplete.\n\n## Support\n\nWe provide official developer support with [paid licenses](https://waterhole.dev/pricing). Community support is available in our [Community][community].\n\n## Contributing\n\nThank you for considering contributing to Waterhole! Please review the [contribution guide](https://waterhole.dev/docs/contributing) before you open issues or send pull requests.\n\n## Code of Conduct\n\nIn order to ensure that the Waterhole community is welcoming to all, please review and abide by the [Code of Conduct](https://waterhole.dev/docs/code-of-conduct).\n\n## Important Links\n\n- [Waterhole Website](https://waterhole.dev)\n- [Waterhole Documentation][docs]\n- [Waterhole Community][community]\n- [Waterhole Skeleton Repo][skeleton]\n\n[docs]: https://waterhole.dev/docs\n[community]: https://waterhole.dev/community\n[skeleton]: https://github.com/waterholeforum/waterhole\n"
  },
  {
    "path": "composer.json",
    "content": "{\n    \"name\": \"waterhole/core\",\n    \"description\": \"Waterhole core\",\n    \"keywords\": [\n        \"forum\"\n    ],\n    \"license\": \"proprietary\",\n    \"require\": {\n        \"ext-json\": \"*\",\n        \"composer/composer\": \"^2.4\",\n        \"guzzlehttp/guzzle\": \"^7.0.1\",\n        \"hotwired-laravel/turbo-laravel\": \"^2.0.0-beta5\",\n        \"intervention/image\": \"^3.0\",\n        \"intervention/image-laravel\": \"^1.0\",\n        \"jrmajor/fluent\": \"^1.0.1\",\n        \"kirschbaum-development/eloquent-power-joins\": \"^3.2|^4.2\",\n        \"laminas/laminas-feed\": \"^2.20\",\n        \"laravel/framework\": \"^10.0|^11.0|^12.0|^13.0\",\n        \"laravel/sanctum\": \"^4.0\",\n        \"laravel/socialite\": \"^v5.10.0\",\n        \"nyholm/psr7\": \"^1.8\",\n        \"s9e/text-formatter\": \"^2.13\",\n        \"secondnetwork/blade-tabler-icons\": \"^3.0\",\n        \"staudenmeir/laravel-adjacency-list\": \"^1.7\",\n        \"staudenmeir/laravel-cte\": \"^1.0\",\n        \"symfony/psr-http-message-bridge\": \"^2.0|^7.0\",\n        \"tobyz/json-api-server\": \"^1.0.0-rc.1\",\n        \"waterhole/sso\": \"^0.1\"\n    },\n    \"require-dev\": {\n        \"larastan/larastan\": \"^3.9.3\",\n        \"orchestra/testbench\": \"^10.2|^11.0\",\n        \"pestphp/pest\": \"^3.8\"\n    },\n    \"autoload\": {\n        \"psr-4\": {\n            \"Waterhole\\\\\": \"src/\",\n            \"Waterhole\\\\Database\\\\Factories\\\\\": \"database/factories/\",\n            \"Waterhole\\\\Database\\\\Seeders\\\\\": \"database/seeders/\"\n        },\n        \"files\": [\n            \"src/helpers.php\"\n        ]\n    },\n    \"autoload-dev\": {\n        \"psr-4\": {\n            \"Tests\\\\\": \"tests/\"\n        }\n    },\n    \"config\": {\n        \"preferred-install\": \"dist\",\n        \"sort-packages\": true,\n        \"allow-plugins\": {\n            \"pestphp/pest-plugin\": true\n        }\n    },\n    \"extra\": {\n        \"branch-alias\": {\n            \"dev-main\": \"0.6.x-dev\"\n        },\n        \"laravel\": {\n            \"providers\": [\n                \"Waterhole\\\\Providers\\\\WaterholeServiceProvider\"\n            ],\n            \"aliases\": {\n                \"Waterhole\": \"Waterhole\\\\Waterhole\"\n            }\n        }\n    },\n    \"scripts\": {\n        \"post-autoload-dump\": [\n            \"@clear\",\n            \"@prepare\"\n        ],\n        \"clear\": \"@php vendor/bin/testbench package:purge-skeleton --ansi\",\n        \"prepare\": \"@php vendor/bin/testbench package:discover --ansi\",\n        \"serve\": [\n            \"Composer\\\\Config::disableProcessTimeout\",\n            \"@php vendor/bin/testbench serve --ansi\"\n        ],\n        \"lint\": [\n            \"@php vendor/bin/phpstan analyse --verbose --ansi\"\n        ],\n        \"test\": [\n            \"@clear\",\n            \"@php vendor/bin/pest\"\n        ]\n    }\n}\n"
  },
  {
    "path": "config/api.php",
    "content": "<?php\n\nreturn [\n\n    /*\n    |--------------------------------------------------------------------------\n    | JSON:API Enabled\n    |--------------------------------------------------------------------------\n    |\n    | The Waterhole JSON:API allows forum data to be accessed through an API\n    | that conforms to the JSON:API specification (https://jsonapi.org).\n    |\n    | Learn more: https://waterhole.dev/docs/api\n    |\n    */\n\n    'enabled' => true,\n\n    /*\n    |--------------------------------------------------------------------------\n    | JSON:API Public\n    |--------------------------------------------------------------------------\n    |\n    | This option makes the Waterhole JSON:API public, so anyone will be able\n    | to access it, and users will be able to generate API tokens in their\n    | account preferences. If disabled, the API will require authentication\n    | using API tokens which only administrators will be able to create.\n    |\n    | Learn more: https://waterhole.dev/docs/api\n    |\n    */\n\n    'public' => true,\n\n    /*\n    |--------------------------------------------------------------------------\n    | JSON:API Path\n    |--------------------------------------------------------------------------\n    |\n    | This is the URI path where the Waterhole JSON:API will be accessible\n    | from. Feel free to change this to anything you like.\n    |\n    */\n\n    'path' => 'api',\n\n];\n"
  },
  {
    "path": "config/auth.php",
    "content": "<?php\n\nreturn [\n\n    /*\n    |--------------------------------------------------------------------------\n    | Authentication Guard\n    |--------------------------------------------------------------------------\n    |\n    | By default, Waterhole will use the `web` authentication guard. However,\n    | if you want to run Waterhole alongside the default Laravel auth\n    | guard, you can configure that below.\n    |\n    */\n\n    'guard' => 'web',\n\n    /*\n    |--------------------------------------------------------------------------\n    | Allow Registration\n    |--------------------------------------------------------------------------\n    |\n    | Whether to allow visitors to register new user accounts. If this\n    | is false, the registration page will become inaccessible and the\n    | \"sign up\" link will be hidden.\n    |\n    */\n\n    'allow_registration' => true,\n\n    /*\n    |--------------------------------------------------------------------------\n    | Password Authentication Enabled\n    |--------------------------------------------------------------------------\n    |\n    | Whether to enable passwords for authentication. If this is false,\n    | visitors will only be able to log in and register via auth providers.\n    |\n    | Learn more: https://waterhole.dev/docs/authentication\n    |\n    */\n\n    'password_enabled' => true,\n\n    /*\n    |--------------------------------------------------------------------------\n    | Auth Providers\n    |--------------------------------------------------------------------------\n    |\n    | Add the names of the auth providers you want to support. Waterhole will\n    | show buttons for these providers on the login and registration pages.\n    |\n    | Learn more: https://waterhole.dev/docs/authentication\n    |\n    */\n\n    'providers' => [\n        // 'github',\n    ],\n\n    /*\n    |--------------------------------------------------------------------------\n    | Single Sign-On\n    |--------------------------------------------------------------------------\n    |\n    | These settings are to configure the \"sso\" auth provider. The \"url\" is\n    | your website's URL where Waterhole will send users when they attempt to\n    | log in. \"secret\" is a secret string that will be used to sign payloads\n    | used in the process and ensure they are authentic.\n    |\n    | Learn more: https://waterhole.dev/docs/authentication\n    |\n    */\n\n    'sso' => [\n        'url' => env('WATERHOLE_SSO_URL'),\n        'secret' => env('WATERHOLE_SSO_SECRET'),\n    ],\n\n];\n"
  },
  {
    "path": "config/cp.php",
    "content": "<?php\n\nreturn [\n    /*\n    |--------------------------------------------------------------------------\n    | Control Panel Path\n    |--------------------------------------------------------------------------\n    |\n    | This is the URI path where the Control Panel will be accessible from.\n    | Feel free to change this to anything you like.\n    |\n    */\n\n    'path' => 'cp',\n\n    /*\n    |--------------------------------------------------------------------------\n    | Dashboard Widgets\n    |--------------------------------------------------------------------------\n    |\n    | Here you may the layout of your dashboard widgets. You're free to\n    | use the same widget multiple times in different configurations.\n    |\n    | Learn more: https://waterhole.dev/docs/dashboard\n    */\n\n    'widgets' => [\n        [\n            'component' => Waterhole\\Widgets\\GettingStarted::class,\n            'width' => 1 / 2,\n        ],\n        [\n            'component' => Waterhole\\Widgets\\Feed::class,\n            'width' => 1 / 2,\n            'title' => 'Waterhole Blog',\n            'url' => 'https://waterhole.dev/community/channels/blog/posts.rss',\n        ],\n        [\n            'component' => Waterhole\\Widgets\\LineChart::class,\n            'width' => 1 / 3,\n            'title' => 'waterhole::cp.dashboard-users-title',\n            'model' => Waterhole\\Models\\User::class,\n        ],\n        [\n            'component' => Waterhole\\Widgets\\LineChart::class,\n            'width' => 1 / 3,\n            'title' => 'waterhole::cp.dashboard-posts-title',\n            'model' => Waterhole\\Models\\Post::class,\n        ],\n        [\n            'component' => Waterhole\\Widgets\\LineChart::class,\n            'width' => 1 / 3,\n            'title' => 'waterhole::cp.dashboard-comments-title',\n            'model' => Waterhole\\Models\\Comment::class,\n        ],\n    ],\n];\n"
  },
  {
    "path": "config/design.php",
    "content": "<?php\n\nreturn [\n    /*\n    |--------------------------------------------------------------------------\n    | Theme\n    |--------------------------------------------------------------------------\n    |\n    | This option allows you to configure which theme Waterhole uses. A null\n    | value means that Waterhole will allow users to toggle between light\n    | and dark mode. You can disable this toggle by setting this to either\n    | \"light\" or \"dark\".\n    |\n    */\n\n    'theme' => null,\n\n    /*\n    |--------------------------------------------------------------------------\n    | Emoji URL\n    |--------------------------------------------------------------------------\n    |\n    | This URL will be used to output emoji. You can set this to null to\n    | disable emoji parsing and fall back to system emoji.\n    |\n    | Learn more: https://s9etextformatter.readthedocs.io/Plugins/Emoji/Synopsis/\n    |\n    */\n\n    'emoji_url' => 'https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/{@tseq}.svg',\n];\n"
  },
  {
    "path": "config/forum.php",
    "content": "<?php\n\nreturn [\n    /*\n    |--------------------------------------------------------------------------\n    | Forum Name\n    |--------------------------------------------------------------------------\n    |\n    | This value is the full name of your forum. This value is displayed in the\n    | <title> tag, your forum header, and in notification emails.\n    |\n    */\n\n    'name' => env('APP_NAME', 'Waterhole'),\n\n    /*\n    |--------------------------------------------------------------------------\n    | Forum Path\n    |--------------------------------------------------------------------------\n    |\n    | This is the URI path where your Waterhole forum will be accessible\n    | from. Feel free to change this to anything you like.\n    |\n    */\n\n    'path' => '',\n\n    /*\n    |--------------------------------------------------------------------------\n    | Post Filters\n    |--------------------------------------------------------------------------\n    |\n    | Here you can configure which filters are available on the forum index.\n    | The first one will be used as the default. This can be overridden for\n    | individual channels in the Structure section of the Control Panel.\n    |\n    */\n\n    'post_filters' => [\n        \\Waterhole\\Filters\\Latest::class,\n        \\Waterhole\\Filters\\Newest::class,\n        \\Waterhole\\Filters\\Trending::class,\n        \\Waterhole\\Filters\\Top::class,\n        \\Waterhole\\Filters\\Oldest::class,\n    ],\n\n    /*\n    |--------------------------------------------------------------------------\n    | Post Layout\n    |--------------------------------------------------------------------------\n    |\n    | Here you can specify which post layout is used on the forum index. This\n    | can be overridden for individual channels in the Structure section of\n    | the Control Panel.\n    |\n    */\n\n    'post_layout' => \\Waterhole\\Layouts\\ListLayout::class,\n\n    /*\n    |--------------------------------------------------------------------------\n    | Posts & Comments Per Page\n    |--------------------------------------------------------------------------\n    |\n    | The numbers of items to show on each page of posts and comments.\n    |\n    */\n\n    'posts_per_page' => 15,\n    'comments_per_page' => 15,\n\n    /*\n    |--------------------------------------------------------------------------\n    | Edit Time Limit\n    |--------------------------------------------------------------------------\n    |\n    | The number of minutes after creation that a post or comment can be edited\n    | by its author. Set to null for no limit, or 0 to never allow editing.\n    | Moderators are always allowed.\n    |\n    */\n\n    'edit_time_limit' => null,\n\n    /*\n    |--------------------------------------------------------------------------\n    | Rate Limits\n    |--------------------------------------------------------------------------\n    |\n    | These limits will help to slow down malicious users and spammers.\n    | \"Create\" refers to the creation of new posts and comments.\n    |\n    */\n\n    'create_per_minute' => 3,\n    'search_per_minute' => 10,\n\n    /*\n    |--------------------------------------------------------------------------\n    | Report Reasons\n    |--------------------------------------------------------------------------\n    |\n    | Keys are stored in the database. Labels come from Fluent translations\n    | like `waterhole::forum.report-reason-<key>-label`.\n    |\n    */\n\n    'report_reasons' => ['off-topic', 'inappropriate', 'spam', 'other'],\n];\n"
  },
  {
    "path": "config/seo.php",
    "content": "<?php\n\nreturn [\n    /*\n    |--------------------------------------------------------------------------\n    | Default Description\n    |--------------------------------------------------------------------------\n    |\n    | Used when a page-specific description is not available.\n    |\n    */\n    'default_description' => null,\n\n    /*\n    |--------------------------------------------------------------------------\n    | Default OpenGraph Image\n    |--------------------------------------------------------------------------\n    |\n    | Absolute URL to a fallback image for social previews.\n    |\n    */\n    'default_og_image' => null,\n\n    /*\n    |--------------------------------------------------------------------------\n    | Nofollow Allowlist\n    |--------------------------------------------------------------------------\n    |\n    | Comma-separated list of domains that should not receive \"nofollow\".\n    |\n    */\n    'nofollow_allow' => [],\n\n    /*\n    |--------------------------------------------------------------------------\n    | Nofollow Rel Attribute\n    |--------------------------------------------------------------------------\n    |\n    | The rel attribute to apply to external links not on the allowlist.\n    |\n    */\n    'nofollow_rel' => 'nofollow ugc',\n];\n"
  },
  {
    "path": "config/system.php",
    "content": "<?php\n\nreturn [\n    /*\n    |--------------------------------------------------------------------------\n    | Site Key\n    |--------------------------------------------------------------------------\n    |\n    | The site key for the corresponding domain from your Waterhole account.\n    | Without a key entered, your forum is considered to be in Trial Mode and\n    | you cannot make it available on a public domain.\n    |\n    */\n\n    'site_key' => env('WATERHOLE_SITE_KEY'),\n\n    /*\n    |--------------------------------------------------------------------------\n    | Database Connection\n    |--------------------------------------------------------------------------\n    |\n    | Here you may specify which database connection Waterhole should use\n    | for its data storage.\n    |\n    */\n\n    'database' => env('DB_CONNECTION'),\n\n    /*\n    |--------------------------------------------------------------------------\n    | Search Engine\n    |--------------------------------------------------------------------------\n    |\n    | The search engine used for forum search. Set to null to disable search\n    | entirely (routes and UI will be hidden). The default is determined from\n    | the database driver.\n    |\n    */\n\n    'search_engine' => env(\n        'WATERHOLE_SEARCH_ENGINE',\n        match (env('DB_CONNECTION')) {\n            'mysql', 'mariadb', 'pgsql' => 'full_text',\n            default => null,\n        },\n    ),\n\n    /*\n    |--------------------------------------------------------------------------\n    | Route Domain\n    |--------------------------------------------------------------------------\n    |\n    | If you want Waterhole to register its routes under a sub-domain, you\n    | can specify that here.\n    |\n    */\n\n    'domain' => null,\n\n    /*\n    |--------------------------------------------------------------------------\n    | Assets Storage Disk\n    |--------------------------------------------------------------------------\n    |\n    | The filesystem disk used for compiled Waterhole assets. This should be a\n    | publicly accessible disk.\n    |\n    */\n\n    'assets_disk' => env('WATERHOLE_ASSETS_DISK', 'public'),\n\n    /*\n    |--------------------------------------------------------------------------\n    | Laravel Echo Configuration\n    |--------------------------------------------------------------------------\n    |\n    | This is the configuration that will be passed in when Waterhole's\n    | frontend JavaScript sets up the Laravel Echo client.\n    |\n    */\n\n    'echo_config' => match (env('BROADCAST_DRIVER')) {\n        'pusher' => [\n            'broadcaster' => 'pusher',\n            'key' => env('PUSHER_APP_KEY'),\n            'cluster' => env('PUSHER_APP_CLUSTER', 'mt1'),\n        ],\n        'reverb' => [\n            'broadcaster' => 'reverb',\n            'key' => env('REVERB_APP_KEY'),\n            'wsHost' => env('REVERB_HOST'),\n            'wsPort' => env('REVERB_PORT', 80),\n            'wssPort' => env('REVERB_PORT', 443),\n            'forceTLS' => env('REVERB_SCHEME', 'https') === 'https',\n            'enabledTransports' => ['ws', 'wss'],\n        ],\n        default => [\n            'broadcaster' => null,\n        ],\n    },\n\n    /*\n    |--------------------------------------------------------------------------\n    | Default Extensions Path\n    |--------------------------------------------------------------------------\n    |\n    | When generating addons via `php artisan make:waterhole-extension`, this\n    | path will be used by default. You can still specify custom repository\n    | paths in your composer.json, but this is the path used by the generator.\n    |\n    */\n\n    'extensions_path' => base_path('extensions'),\n\n    /*\n    |--------------------------------------------------------------------------\n    | Send the Powered-By Header\n    |--------------------------------------------------------------------------\n    |\n    | Websites like builtwith.com use the X-Powered-By header to determine\n    | what technologies are used on a particular site. By default, we'll\n    | send this header, but feel free to disable it.\n    |\n    */\n\n    'send_powered_by_header' => true,\n\n    /*\n    |--------------------------------------------------------------------------\n    | Intensive Operations\n    |--------------------------------------------------------------------------\n    |\n    | Sometimes Waterhole requires extra resources to complete intensive\n    | operations. Here you may configure these system resource limits.\n    |\n    */\n\n    'php_memory_limit' => '-1',\n    'php_max_execution_time' => '-1',\n];\n"
  },
  {
    "path": "config/uploads.php",
    "content": "<?php\n\nreturn [\n    /*\n    |--------------------------------------------------------------------------\n    | Storage Disk\n    |--------------------------------------------------------------------------\n    |\n    | The filesystem disk used for storing user uploads (attachments, avatars,\n    | icons). This should be a publicly accessible disk.\n    |\n    */\n\n    'disk' => env('WATERHOLE_UPLOADS_DISK', 'public'),\n\n    /*\n    |--------------------------------------------------------------------------\n    | Allowed MIME Types\n    |--------------------------------------------------------------------------\n    |\n    | Uploaded files must match one of the MIME types listed here. Values can\n    | be either a MIME type (`text/plain`) or a file extension (`jpg`). If\n    | empty, no validation will take place and all MIME types will be allowed.\n    |\n    */\n\n    'allowed_mimetypes' => [],\n\n    /*\n    |--------------------------------------------------------------------------\n    | Maximum Upload Size (Kilobytes)\n    |--------------------------------------------------------------------------\n    |\n    | Any uploads larger than this will be rejected. Make sure this value is\n    | below the size limit of POST requests enforced by your webserver, as\n    | well as PHP's `upload_max_filesize` and `post_max_size` settings.\n    |\n    */\n\n    'max_upload_size' => 5120,\n];\n"
  },
  {
    "path": "config/users.php",
    "content": "<?php\n\nreturn [\n    /*\n    |--------------------------------------------------------------------------\n    | Re-verify After Inactive Days\n    |--------------------------------------------------------------------------\n    |\n    | If set to a number of days, users who have been inactive longer than\n    | that will be required to re-verify their email address. Set to null\n    | to disable.\n    |\n    */\n\n    'reverify_after_inactive_days' => 365,\n\n    /*\n    |--------------------------------------------------------------------------\n    | Post Filters\n    |--------------------------------------------------------------------------\n    |\n    | Here you can configure which filters are available when viewing a user's.\n    | posts on their profile page. The first one will be used as the default.\n    |\n    */\n\n    'post_filters' => [\n        \\Waterhole\\Filters\\Newest::class,\n        \\Waterhole\\Filters\\Top::class,\n    ],\n\n    /*\n    |--------------------------------------------------------------------------\n    | Comment Filters\n    |--------------------------------------------------------------------------\n    |\n    | Here you can configure which filters are available when viewing a user's.\n    | comments on their profile page. The first one will be used as the default.\n    |\n    */\n\n    'comment_filters' => [\n        \\Waterhole\\Filters\\Newest::class,\n        \\Waterhole\\Filters\\Top::class,\n    ],\n];\n"
  },
  {
    "path": "database/factories/ChannelFactory.php",
    "content": "<?php\n\nnamespace Waterhole\\Database\\Factories;\n\nuse Illuminate\\Database\\Eloquent\\Factories\\Factory;\nuse Waterhole\\Models\\Channel;\n\nclass ChannelFactory extends Factory\n{\n    protected $model = Channel::class;\n\n    public function definition(): array\n    {\n        return ['name' => fake()->name];\n    }\n\n    public function public(): static\n    {\n        return $this->afterCreating(function (Channel $channel) {\n            $channel->savePermissions(['group:1' => ['view' => true]]);\n        });\n    }\n}\n"
  },
  {
    "path": "database/factories/CommentFactory.php",
    "content": "<?php\n\nnamespace Waterhole\\Database\\Factories;\n\nuse Illuminate\\Database\\Eloquent\\Factories\\Factory;\nuse Waterhole\\Models\\Comment;\n\nclass CommentFactory extends Factory\n{\n    protected $model = Comment::class;\n\n    public function definition(): array\n    {\n        return ['body' => fake()->text];\n    }\n}\n"
  },
  {
    "path": "database/factories/PageFactory.php",
    "content": "<?php\n\nnamespace Waterhole\\Database\\Factories;\n\nuse Illuminate\\Database\\Eloquent\\Factories\\Factory;\nuse Waterhole\\Models\\Page;\n\nclass PageFactory extends Factory\n{\n    protected $model = Page::class;\n\n    public function definition(): array\n    {\n        return ['name' => fake()->name, 'body' => fake()->text];\n    }\n\n    public function public(): static\n    {\n        return $this->afterCreating(function (Page $page) {\n            $page->savePermissions(['group:1' => ['view' => true]]);\n        });\n    }\n}\n"
  },
  {
    "path": "database/factories/PostFactory.php",
    "content": "<?php\n\nnamespace Waterhole\\Database\\Factories;\n\nuse Illuminate\\Database\\Eloquent\\Factories\\Factory;\nuse Waterhole\\Models\\Post;\n\nclass PostFactory extends Factory\n{\n    protected $model = Post::class;\n\n    public function definition(): array\n    {\n        return ['body' => fake()->text];\n    }\n}\n"
  },
  {
    "path": "database/factories/TagFactory.php",
    "content": "<?php\n\nnamespace Waterhole\\Database\\Factories;\n\nuse Illuminate\\Database\\Eloquent\\Factories\\Factory;\nuse Waterhole\\Models\\Tag;\n\nclass TagFactory extends Factory\n{\n    protected $model = Tag::class;\n\n    public function definition(): array\n    {\n        return ['name' => fake()->name];\n    }\n}\n"
  },
  {
    "path": "database/factories/UserFactory.php",
    "content": "<?php\n\nnamespace Waterhole\\Database\\Factories;\n\nuse Illuminate\\Database\\Eloquent\\Factories\\Factory;\nuse Waterhole\\Models\\Group;\nuse Waterhole\\Models\\User;\n\nclass UserFactory extends Factory\n{\n    protected $model = User::class;\n\n    public function definition(): array\n    {\n        return [\n            'name' => fake()->userName,\n            'email' => fake()->email,\n            'email_verified_at' => now(),\n        ];\n    }\n\n    public function admin(): UserFactory\n    {\n        return $this->afterCreating(function (User $user) {\n            $user->groups()->attach(Group::ADMIN_ID);\n        });\n    }\n}\n"
  },
  {
    "path": "database/migrations/2021_06_21_021231_create_channels_table.php",
    "content": "<?php\n\nuse Waterhole\\Database\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nreturn new class extends Migration {\n    public function up()\n    {\n        Schema::create('channels', function (Blueprint $table) {\n            $table->id();\n            $table->string('name');\n            $table->string('slug')->unique();\n            $table->string('icon')->nullable();\n            $table->text('description')->nullable();\n            $table->text('instructions')->nullable();\n            $table->json('filters')->nullable();\n            $table->string('default_layout')->nullable();\n            $table->boolean('sandbox')->default(0);\n        });\n    }\n\n    public function down()\n    {\n        Schema::dropIfExists('channels');\n    }\n};\n"
  },
  {
    "path": "database/migrations/2021_06_21_021344_create_users_table.php",
    "content": "<?php\n\nuse Waterhole\\Database\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nreturn new class extends Migration {\n    public function up()\n    {\n        Schema::create('users', function (Blueprint $table) {\n            $table->id();\n            $table->string('name')->index();\n            $table->string('email')->unique();\n            $table->timestamp('email_verified_at')->nullable();\n            $table->string('password')->nullable();\n            $table->rememberToken();\n            $table->string('locale')->nullable();\n            $table->string('headline')->nullable();\n            $table->text('bio')->nullable();\n            $table->string('location')->nullable();\n            $table->string('website')->nullable();\n            $table->string('avatar')->nullable();\n            $table->timestamp('created_at')->nullable()->index();\n            $table->timestamp('last_seen_at')->nullable()->index();\n            $table->boolean('show_online')->default(1);\n            $table->json('notification_channels')->nullable();\n            $table->timestamp('notifications_read_at')->nullable();\n            $table->boolean('follow_on_comment')->default(0);\n        });\n    }\n\n    public function down()\n    {\n        Schema::dropIfExists('users');\n    }\n};\n"
  },
  {
    "path": "database/migrations/2021_06_21_021345_create_notifications_table.php",
    "content": "<?php\n\nuse Waterhole\\Database\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nreturn new class extends Migration {\n    public function up()\n    {\n        Schema::create('notifications', function (Blueprint $table) {\n            $table->uuid('id')->primary();\n            $table->string('type');\n            $table->morphs('notifiable');\n            $table->text('data');\n            $table\n                ->foreignId('sender_id')\n                ->nullable()\n                ->constrained('users')\n                ->cascadeOnUpdate()\n                ->nullOnDelete();\n            $table->nullableMorphs('group');\n            $table->nullableMorphs('content');\n            $table->timestamps();\n            $table->timestamp('read_at')->nullable();\n        });\n    }\n\n    public function down()\n    {\n        Schema::dropIfExists('notifications');\n    }\n};\n"
  },
  {
    "path": "database/migrations/2021_06_21_021348_create_posts_table.php",
    "content": "<?php\n\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\nuse Waterhole\\Database\\Migration;\n\nreturn new class extends Migration {\n    public function up()\n    {\n        Schema::create('posts', function (Blueprint $table) {\n            $table->id();\n            $table->foreignId('channel_id')->constrained()->cascadeOnUpdate()->cascadeOnDelete();\n            $table\n                ->foreignId('user_id')\n                ->nullable()\n                ->constrained()\n                ->cascadeOnUpdate()\n                ->nullOnDelete();\n            $table->string('title')->nullable();\n            $table->string('slug')->nullable();\n            $table->mediumText('body');\n            $table->timestamp('created_at')->nullable()->index();\n            $table->timestamp('edited_at')->nullable()->index();\n            $table->timestamp('last_activity_at')->nullable()->index();\n            $table->unsignedInteger('comment_count')->default(0)->index();\n            $table->integer('score')->default(0)->index();\n            $table->boolean('is_locked')->default(0);\n\n            $table->index(['channel_id', 'created_at']); // used to get new post count within each channel\n\n            if (Schema::getConnection()->getDriverName() !== 'sqlite') {\n                $table->fullText(['title']);\n                $table->fullText(['body']);\n            }\n        });\n    }\n\n    public function down()\n    {\n        Schema::dropIfExists('posts');\n    }\n};\n"
  },
  {
    "path": "database/migrations/2021_06_21_021355_create_comments_table.php",
    "content": "<?php\n\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\nuse Waterhole\\Database\\Migration;\n\nreturn new class extends Migration {\n    public function up()\n    {\n        Schema::create('comments', function (Blueprint $table) {\n            $table->id();\n            $table->foreignId('post_id')->constrained()->cascadeOnUpdate()->cascadeOnDelete();\n            $table\n                ->foreignId('parent_id')\n                ->nullable()\n                ->constrained('comments')\n                ->cascadeOnUpdate()\n                ->nullOnDelete();\n            $table\n                ->foreignId('user_id')\n                ->nullable()\n                ->constrained()\n                ->cascadeOnUpdate()\n                ->nullOnDelete();\n            $table->mediumText('body');\n            $table->timestamp('created_at')->nullable()->index();\n            $table->timestamp('edited_at')->nullable()->index();\n            $table->unsignedInteger('reply_count')->default(0);\n            $table->integer('score')->default(0)->index();\n\n            if (Schema::getConnection()->getDriverName() !== 'sqlite') {\n                $table->fullText(['body']);\n            }\n        });\n    }\n\n    public function down()\n    {\n        Schema::dropIfExists('comments');\n    }\n};\n"
  },
  {
    "path": "database/migrations/2021_06_21_021413_create_groups_table.php",
    "content": "<?php\n\nuse Waterhole\\Database\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nreturn new class extends Migration {\n    public function up()\n    {\n        Schema::create('groups', function (Blueprint $table) {\n            $table->id();\n            $table->string('name');\n            $table->boolean('is_public')->default(0);\n            $table->string('icon')->nullable();\n            $table->string('color')->nullable();\n        });\n    }\n\n    public function down()\n    {\n        Schema::dropIfExists('groups');\n    }\n};\n"
  },
  {
    "path": "database/migrations/2021_06_21_021449_create_group_user_table.php",
    "content": "<?php\n\nuse Waterhole\\Database\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nreturn new class extends Migration {\n    public function up()\n    {\n        Schema::create('group_user', function (Blueprint $table) {\n            $table->foreignId('group_id')->constrained()->cascadeOnUpdate()->cascadeOnDelete();\n            $table->foreignId('user_id')->constrained()->cascadeOnUpdate()->cascadeOnDelete();\n\n            $table->primary(['group_id', 'user_id']);\n        });\n    }\n\n    public function down()\n    {\n        Schema::dropIfExists('group_user');\n    }\n};\n"
  },
  {
    "path": "database/migrations/2021_06_21_021454_create_mentions_table.php",
    "content": "<?php\n\nuse Waterhole\\Database\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nreturn new class extends Migration {\n    public function up()\n    {\n        Schema::create('mentions', function (Blueprint $table) {\n            $table->morphs('content');\n            $table->foreignId('user_id')->constrained()->cascadeOnUpdate()->cascadeOnDelete();\n\n            $table->primary(['content_type', 'content_id', 'user_id']);\n        });\n    }\n\n    public function down()\n    {\n        Schema::dropIfExists('mentions');\n    }\n};\n"
  },
  {
    "path": "database/migrations/2021_06_21_021502_create_post_user_table.php",
    "content": "<?php\n\nuse Waterhole\\Database\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nreturn new class extends Migration {\n    public function up()\n    {\n        Schema::create('post_user', function (Blueprint $table) {\n            $table->foreignId('post_id')->constrained()->cascadeOnUpdate()->cascadeOnDelete();\n            $table->foreignId('user_id')->constrained()->cascadeOnUpdate()->cascadeOnDelete();\n            $table->timestamp('last_read_at')->nullable();\n            $table->string('notifications')->nullable();\n            $table->timestamp('followed_at')->nullable();\n            $table->timestamp('mentioned_at')->nullable();\n\n            $table->primary(['post_id', 'user_id']);\n        });\n    }\n\n    public function down()\n    {\n        Schema::dropIfExists('post_user');\n    }\n};\n"
  },
  {
    "path": "database/migrations/2021_06_21_111526_create_permissions_table.php",
    "content": "<?php\n\nuse Waterhole\\Database\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nreturn new class extends Migration {\n    public function up()\n    {\n        Schema::create('permissions', function (Blueprint $table) {\n            $table->id();\n            $table->nullableMorphs('scope');\n            $table->morphs('recipient');\n            $table->string('ability');\n        });\n    }\n\n    public function down()\n    {\n        Schema::dropIfExists('permissions');\n    }\n};\n"
  },
  {
    "path": "database/migrations/2021_10_18_130929_create_channel_user_table.php",
    "content": "<?php\n\nuse Waterhole\\Database\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nreturn new class extends Migration {\n    public function up()\n    {\n        Schema::create('channel_user', function (Blueprint $table) {\n            $table->foreignId('channel_id')->constrained()->cascadeOnUpdate()->cascadeOnDelete();\n            $table->foreignId('user_id')->constrained()->cascadeOnUpdate()->cascadeOnDelete();\n            $table->string('notifications')->nullable();\n            $table->timestamp('followed_at')->nullable();\n\n            $table->primary(['channel_id', 'user_id']);\n        });\n    }\n\n    public function down()\n    {\n        Schema::dropIfExists('channel_user');\n    }\n};\n"
  },
  {
    "path": "database/migrations/2021_12_04_093522_create_pages_table.php",
    "content": "<?php\n\nuse Waterhole\\Database\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nreturn new class extends Migration {\n    public function up()\n    {\n        Schema::create('pages', function (Blueprint $table) {\n            $table->id();\n            $table->string('name');\n            $table->string('slug')->unique();\n            $table->string('icon')->nullable();\n            $table->mediumText('body');\n        });\n    }\n\n    public function down()\n    {\n        Schema::dropIfExists('pages');\n    }\n};\n"
  },
  {
    "path": "database/migrations/2021_12_04_095210_create_structure_table.php",
    "content": "<?php\n\nuse Waterhole\\Database\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nreturn new class extends Migration {\n    public function up()\n    {\n        Schema::create('structure', function (Blueprint $table) {\n            $table->id();\n            $table->unsignedInteger('position')->default(0);\n            $table->morphs('content');\n            $table->boolean('is_listed')->default(0);\n        });\n    }\n\n    public function down()\n    {\n        Schema::dropIfExists('structure');\n    }\n};\n"
  },
  {
    "path": "database/migrations/2021_12_04_095221_create_structure_headings_table.php",
    "content": "<?php\n\nuse Waterhole\\Database\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nreturn new class extends Migration {\n    public function up()\n    {\n        Schema::create('structure_headings', function (Blueprint $table) {\n            $table->id();\n            $table->string('name')->nullable();\n        });\n    }\n\n    public function down()\n    {\n        Schema::dropIfExists('structure_headings');\n    }\n};\n"
  },
  {
    "path": "database/migrations/2021_12_04_095226_create_structure_links_table.php",
    "content": "<?php\n\nuse Waterhole\\Database\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nreturn new class extends Migration {\n    public function up()\n    {\n        Schema::create('structure_links', function (Blueprint $table) {\n            $table->id();\n            $table->string('name');\n            $table->string('href');\n            $table->string('icon')->nullable();\n        });\n    }\n\n    public function down()\n    {\n        Schema::dropIfExists('structure_links');\n    }\n};\n"
  },
  {
    "path": "database/migrations/2023_01_06_083525_create_reaction_sets_table.php",
    "content": "<?php\n\nuse Waterhole\\Database\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nreturn new class extends Migration {\n    public function up()\n    {\n        Schema::create('reaction_sets', function (Blueprint $table) {\n            $table->id();\n            $table->string('name');\n            $table->boolean('is_default_posts')->default(0);\n            $table->boolean('is_default_comments')->default(0);\n            $table->boolean('allow_multiple')->default(0);\n            $table->timestamps();\n        });\n    }\n\n    public function down()\n    {\n        Schema::dropIfExists('reaction_sets');\n    }\n};\n"
  },
  {
    "path": "database/migrations/2023_01_06_083700_create_reaction_types_table.php",
    "content": "<?php\n\nuse Waterhole\\Database\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nreturn new class extends Migration {\n    public function up()\n    {\n        Schema::create('reaction_types', function (Blueprint $table) {\n            $table->id();\n            $table\n                ->foreignId('reaction_set_id')\n                ->constrained()\n                ->cascadeOnUpdate()\n                ->cascadeOnDelete();\n            $table->string('name');\n            $table->string('icon')->nullable();\n            $table->tinyInteger('score');\n            $table->unsignedSmallInteger('position');\n            $table->timestamps();\n        });\n    }\n\n    public function down()\n    {\n        Schema::dropIfExists('reaction_types');\n    }\n};\n"
  },
  {
    "path": "database/migrations/2023_01_06_084208_create_reactions_table.php",
    "content": "<?php\n\nuse Waterhole\\Database\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nreturn new class extends Migration {\n    public function up()\n    {\n        Schema::create('reactions', function (Blueprint $table) {\n            $table->id();\n            $table->foreignId('user_id')->constrained()->cascadeOnUpdate()->cascadeOnDelete();\n            $table\n                ->foreignId('reaction_type_id')\n                ->constrained()\n                ->cascadeOnUpdate()\n                ->cascadeOnDelete();\n            $table->morphs('content');\n            $table->timestamps();\n        });\n    }\n\n    public function down()\n    {\n        Schema::dropIfExists('reactions');\n    }\n};\n"
  },
  {
    "path": "database/migrations/2023_01_06_165934_add_reaction_set_columns_to_channels.php",
    "content": "<?php\n\nuse Waterhole\\Database\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nreturn new class extends Migration {\n    public function up()\n    {\n        Schema::table('channels', function (Blueprint $table) {\n            $table\n                ->foreignId('posts_reaction_set_id')\n                ->nullable()\n                ->constrained('reaction_sets')\n                ->cascadeOnUpdate()\n                ->nullOnDelete();\n            $table\n                ->foreignId('comments_reaction_set_id')\n                ->nullable()\n                ->constrained('reaction_sets')\n                ->cascadeOnUpdate()\n                ->nullOnDelete();\n        });\n    }\n\n    public function down()\n    {\n        Schema::table('channels', function (Blueprint $table) {\n            $table->dropConstrainedForeignId('posts_reaction_set_id');\n            $table->dropConstrainedForeignId('comments_reaction_set_id');\n        });\n    }\n};\n"
  },
  {
    "path": "database/migrations/2023_01_13_110454_create_taxonomies_table.php",
    "content": "<?php\n\nuse Waterhole\\Database\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nreturn new class extends Migration {\n    public function up()\n    {\n        Schema::create('taxonomies', function (Blueprint $table) {\n            $table->id();\n            $table->string('name');\n            $table->boolean('allow_multiple')->default(0);\n            $table->timestamps();\n        });\n    }\n\n    public function down()\n    {\n        Schema::dropIfExists('taxonomies');\n    }\n};\n"
  },
  {
    "path": "database/migrations/2023_01_13_110537_create_tags_table.php",
    "content": "<?php\n\nuse Waterhole\\Database\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nreturn new class extends Migration {\n    public function up()\n    {\n        Schema::create('tags', function (Blueprint $table) {\n            $table->id();\n            $table->foreignId('taxonomy_id')->constrained()->cascadeOnUpdate()->cascadeOnDelete();\n            $table->string('name');\n            $table->timestamps();\n        });\n    }\n\n    public function down()\n    {\n        Schema::dropIfExists('tags');\n    }\n};\n"
  },
  {
    "path": "database/migrations/2023_01_13_110933_create_post_tag_table.php",
    "content": "<?php\n\nuse Waterhole\\Database\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nreturn new class extends Migration {\n    public function up()\n    {\n        Schema::create('post_tag', function (Blueprint $table) {\n            $table->foreignId('post_id')->constrained()->cascadeOnUpdate()->cascadeOnDelete();\n            $table->foreignId('tag_id')->constrained()->cascadeOnUpdate()->cascadeOnDelete();\n        });\n    }\n\n    public function down()\n    {\n        Schema::dropIfExists('post_tag');\n    }\n};\n"
  },
  {
    "path": "database/migrations/2023_01_13_110952_create_channel_taxonomy_table.php",
    "content": "<?php\n\nuse Waterhole\\Database\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nreturn new class extends Migration {\n    public function up()\n    {\n        Schema::create('channel_taxonomy', function (Blueprint $table) {\n            $table->foreignId('channel_id')->constrained()->cascadeOnUpdate()->cascadeOnDelete();\n            $table->foreignId('taxonomy_id')->constrained()->cascadeOnUpdate()->cascadeOnDelete();\n        });\n    }\n\n    public function down()\n    {\n        Schema::dropIfExists('channel_taxonomy');\n    }\n};\n"
  },
  {
    "path": "database/migrations/2023_01_19_113324_create_auth_providers_table.php",
    "content": "<?php\n\nuse Waterhole\\Database\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nreturn new class extends Migration {\n    public function up()\n    {\n        Schema::create('auth_providers', function (Blueprint $table) {\n            $table->id();\n            $table->foreignId('user_id')->constrained()->cascadeOnUpdate()->cascadeOnDelete();\n            $table->string('provider');\n            $table->string('identifier');\n            $table->timestamp('created_at');\n            $table->timestamp('last_login_at')->nullable();\n        });\n    }\n\n    public function down()\n    {\n        Schema::dropIfExists('auth_providers');\n    }\n};\n"
  },
  {
    "path": "database/migrations/2023_01_26_145619_add_view_count_to_posts.php",
    "content": "<?php\n\nuse Waterhole\\Database\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nreturn new class extends Migration {\n    public function up()\n    {\n        Schema::table('posts', function (Blueprint $table) {\n            $table->unsignedInteger('view_count')->default(0)->index();\n        });\n    }\n\n    public function down()\n    {\n        Schema::table('posts', function (Blueprint $table) {\n            $table->dropColumn('view_count');\n        });\n    }\n};\n"
  },
  {
    "path": "database/migrations/2023_02_02_115230_create_uploads_table.php",
    "content": "<?php\n\nuse Waterhole\\Database\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nreturn new class extends Migration {\n    public function up()\n    {\n        Schema::create('uploads', function (Blueprint $table) {\n            $table->id();\n            $table\n                ->foreignId('user_id')\n                ->nullable()\n                ->constrained()\n                ->cascadeOnUpdate()\n                ->nullOnDelete();\n            $table->string('filename')->unique();\n            $table->string('type')->nullable();\n            $table->unsignedSmallInteger('width')->nullable();\n            $table->unsignedSmallInteger('height')->nullable();\n            $table->timestamps();\n        });\n    }\n\n    public function down()\n    {\n        Schema::dropIfExists('uploads');\n    }\n};\n"
  },
  {
    "path": "database/migrations/2023_02_02_115236_create_attachments_table.php",
    "content": "<?php\n\nuse Waterhole\\Database\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nreturn new class extends Migration {\n    public function up()\n    {\n        Schema::create('attachments', function (Blueprint $table) {\n            $table->morphs('content');\n            $table->foreignId('upload_id')->constrained()->cascadeOnUpdate()->cascadeOnDelete();\n        });\n    }\n\n    public function down()\n    {\n        Schema::dropIfExists('attachments');\n    }\n};\n"
  },
  {
    "path": "database/migrations/2023_02_02_151121_add_hotness_to_posts.php",
    "content": "<?php\n\nuse Waterhole\\Database\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nreturn new class extends Migration {\n    public function up()\n    {\n        Schema::table('posts', function (Blueprint $table) {\n            $table->float('hotness', 10, 4)->nullable()->index();\n        });\n    }\n\n    public function down()\n    {\n        Schema::table('posts', function (Blueprint $table) {\n            $table->dropColumn('hotness');\n        });\n    }\n};\n"
  },
  {
    "path": "database/migrations/2023_02_02_153704_add_suspsended_until_to_users.php",
    "content": "<?php\n\nuse Waterhole\\Database\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nreturn new class extends Migration {\n    public function up()\n    {\n        Schema::table('users', function (Blueprint $table) {\n            $table->timestamp('suspended_until')->nullable();\n        });\n    }\n\n    public function down()\n    {\n        Schema::table('users', function (Blueprint $table) {\n            $table->dropColumn('suspended_until');\n        });\n    }\n};\n"
  },
  {
    "path": "database/migrations/2023_03_03_083340_add_is_required_to_taxonomies.php",
    "content": "<?php\n\nuse Waterhole\\Database\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nreturn new class extends Migration {\n    public function up()\n    {\n        Schema::table('taxonomies', function (Blueprint $table) {\n            $table->boolean('is_required')->default(0);\n        });\n    }\n\n    public function down()\n    {\n        Schema::table('taxonomies', function (Blueprint $table) {\n            $table->dropColumn('is_required');\n        });\n    }\n};\n"
  },
  {
    "path": "database/migrations/2023_03_11_114659_add_answerable_to_channels.php",
    "content": "<?php\n\nuse Waterhole\\Database\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nreturn new class extends Migration {\n    public function up(): void\n    {\n        Schema::table('channels', function (Blueprint $table) {\n            $table->boolean('answerable')->default(0);\n        });\n    }\n\n    public function down(): void\n    {\n        Schema::table('channels', function (Blueprint $table) {\n            $table->dropColumn('answerable');\n        });\n    }\n};\n"
  },
  {
    "path": "database/migrations/2023_03_11_114850_add_answer_id_to_posts.php",
    "content": "<?php\n\nuse Waterhole\\Database\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nreturn new class extends Migration {\n    public function up(): void\n    {\n        Schema::table('posts', function (Blueprint $table) {\n            $table\n                ->foreignId('answer_id')\n                ->nullable()\n                ->constrained('comments')\n                ->cascadeOnUpdate()\n                ->nullOnDelete();\n        });\n    }\n\n    public function down(): void\n    {\n        Schema::table('posts', function (Blueprint $table) {\n            $table->dropConstrainedForeignId('answer_id');\n        });\n    }\n};\n"
  },
  {
    "path": "database/migrations/2023_03_16_095929_add_show_similar_posts_to_channels.php",
    "content": "<?php\n\nuse Waterhole\\Database\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nreturn new class extends Migration {\n    public function up(): void\n    {\n        Schema::table('channels', function (Blueprint $table) {\n            $table->boolean('show_similar_posts')->default(0);\n        });\n    }\n\n    public function down(): void\n    {\n        Schema::table('channels', function (Blueprint $table) {\n            $table->dropColumn('show_similar_posts');\n        });\n    }\n};\n"
  },
  {
    "path": "database/migrations/2023_04_05_165546_add_post_id_created_at_index_to_comments.php",
    "content": "<?php\n\nuse Waterhole\\Database\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nreturn new class extends Migration {\n    public function up(): void\n    {\n        Schema::table('comments', function (Blueprint $table) {\n            $table->index(['post_id', 'created_at']);\n        });\n    }\n\n    public function down(): void\n    {\n        Schema::table('comments', function (Blueprint $table) {\n            $table->dropIndex(['post_id', 'created_at']);\n        });\n    }\n};\n"
  },
  {
    "path": "database/migrations/2023_04_05_165601_add_is_listed_position_index_to_structure.php",
    "content": "<?php\n\nuse Waterhole\\Database\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nreturn new class extends Migration {\n    public function up(): void\n    {\n        Schema::table('structure', function (Blueprint $table) {\n            $table->index(['is_listed', 'position']);\n        });\n    }\n\n    public function down(): void\n    {\n        Schema::table('structure', function (Blueprint $table) {\n            $table->dropIndex(['is_listed', 'position']);\n        });\n    }\n};\n"
  },
  {
    "path": "database/migrations/2023_04_13_163452_rename_channels_sandbox_to_ignore.php",
    "content": "<?php\n\nuse Waterhole\\Database\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nreturn new class extends Migration {\n    public function up(): void\n    {\n        Schema::table('channels', function (Blueprint $table) {\n            $table->renameColumn('sandbox', 'ignore');\n        });\n    }\n\n    public function down(): void\n    {\n        Schema::table('channels', function (Blueprint $table) {\n            $table->renameColumn('ignore', 'sandbox');\n        });\n    }\n};\n"
  },
  {
    "path": "database/migrations/2023_04_13_203750_rename_channels_default_layout_to_layout.php",
    "content": "<?php\n\nuse Waterhole\\Database\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nreturn new class extends Migration {\n    public function up(): void\n    {\n        Schema::table('channels', function (Blueprint $table) {\n            $table->renameColumn('default_layout', 'layout');\n        });\n    }\n\n    public function down(): void\n    {\n        Schema::table('channels', function (Blueprint $table) {\n            $table->renameColumn('layout', 'default_layout');\n        });\n    }\n};\n"
  },
  {
    "path": "database/migrations/2023_04_13_203835_add_layout_config_to_channels.php",
    "content": "<?php\n\nuse Waterhole\\Database\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nreturn new class extends Migration {\n    public function up(): void\n    {\n        Schema::table('channels', function (Blueprint $table) {\n            $table->json('layout_config')->nullable()->after('layout');\n        });\n    }\n\n    public function down(): void\n    {\n        Schema::table('channels', function (Blueprint $table) {\n            $table->dropColumn('layout_config');\n        });\n    }\n};\n"
  },
  {
    "path": "database/migrations/2023_04_22_131025_add_translations_to_channels.php",
    "content": "<?php\n\nuse Waterhole\\Database\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nreturn new class extends Migration {\n    public function up(): void\n    {\n        Schema::table('channels', function (Blueprint $table) {\n            $table->json('translations')->nullable();\n        });\n    }\n\n    public function down(): void\n    {\n        Schema::table('channels', function (Blueprint $table) {\n            $table->dropColumn('translations');\n        });\n    }\n};\n"
  },
  {
    "path": "database/migrations/2023_06_08_093656_add_is_pinned_to_posts.php",
    "content": "<?php\n\nuse Waterhole\\Database\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nreturn new class extends Migration {\n    public function up(): void\n    {\n        Schema::table('posts', function (Blueprint $table) {\n            $table->boolean('is_pinned')->default(0)->index();\n        });\n    }\n\n    public function down(): void\n    {\n        Schema::table('posts', function (Blueprint $table) {\n            $table->dropColumn('is_pinned');\n        });\n    }\n};\n"
  },
  {
    "path": "database/migrations/2023_06_08_113707_add_hiding_columns_to_comments.php",
    "content": "<?php\n\nuse Waterhole\\Database\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nreturn new class extends Migration {\n    public function up(): void\n    {\n        Schema::table('comments', function (Blueprint $table) {\n            $table->timestamp('hidden_at')->nullable();\n\n            $table\n                ->foreignId('hidden_by')\n                ->nullable()\n                ->constrained('users')\n                ->cascadeOnUpdate()\n                ->nullOnDelete();\n\n            $table->string('hidden_reason')->nullable();\n        });\n    }\n\n    public function down(): void\n    {\n        Schema::table('comments', function (Blueprint $table) {\n            $table->dropColumn('hidden_at');\n            $table->dropConstrainedForeignId('hidden_by');\n            $table->dropColumn('hidden_reason');\n        });\n    }\n};\n"
  },
  {
    "path": "database/migrations/2023_06_10_151000_add_created_at_to_posts_is_pinned_index.php",
    "content": "<?php\n\nuse Waterhole\\Database\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nreturn new class extends Migration {\n    public function up(): void\n    {\n        Schema::table('posts', function (Blueprint $table) {\n            $table->dropIndex(['is_pinned']);\n            $table->index(['is_pinned', 'created_at']);\n        });\n    }\n\n    public function down(): void\n    {\n        Schema::table('posts', function (Blueprint $table) {\n            $table->dropIndex(['is_pinned', 'created_at']);\n            $table->index(['is_pinned']);\n        });\n    }\n};\n"
  },
  {
    "path": "database/migrations/2023_07_12_165151_add_deleted_at_to_posts.php",
    "content": "<?php\n\nuse Waterhole\\Database\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nreturn new class extends Migration {\n    public function up(): void\n    {\n        Schema::table('posts', function (Blueprint $table) {\n            $table->timestamp('deleted_at')->nullable()->after('edited_at')->index();\n        });\n    }\n\n    public function down(): void\n    {\n        Schema::table('posts', function (Blueprint $table) {\n            $table->dropColumn('deleted_at');\n        });\n    }\n};\n"
  },
  {
    "path": "database/migrations/2026_01_22_000001_add_rules_and_auto_assign_to_groups.php",
    "content": "<?php\n\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\nuse Waterhole\\Database\\Migration;\n\nreturn new class extends Migration {\n    public function up()\n    {\n        Schema::table('groups', function (Blueprint $table) {\n            $table->boolean('auto_assign')->default(0);\n            $table->json('rules')->nullable();\n        });\n    }\n\n    public function down()\n    {\n        Schema::table('groups', function (Blueprint $table) {\n            $table->dropColumn(['auto_assign', 'rules']);\n        });\n    }\n};\n"
  },
  {
    "path": "database/migrations/2026_01_22_000002_add_is_approved_to_posts.php",
    "content": "<?php\n\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\nuse Waterhole\\Database\\Migration;\n\nreturn new class extends Migration {\n    public function up()\n    {\n        Schema::table('posts', function (Blueprint $table) {\n            $table->boolean('is_approved')->default(1)->index()->after('is_locked');\n        });\n    }\n\n    public function down()\n    {\n        Schema::table('posts', function (Blueprint $table) {\n            $table->dropColumn('is_approved');\n        });\n    }\n};\n"
  },
  {
    "path": "database/migrations/2026_01_22_000003_add_is_approved_to_comments.php",
    "content": "<?php\n\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\nuse Waterhole\\Database\\Migration;\n\nreturn new class extends Migration {\n    public function up()\n    {\n        Schema::table('comments', function (Blueprint $table) {\n            $table->boolean('is_approved')->default(1)->index()->after('score');\n        });\n    }\n\n    public function down()\n    {\n        Schema::table('comments', function (Blueprint $table) {\n            $table->dropColumn('is_approved');\n        });\n    }\n};\n"
  },
  {
    "path": "database/migrations/2026_01_22_000004_add_deleted_reason_to_posts.php",
    "content": "<?php\n\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\nuse Waterhole\\Database\\Migration;\n\nreturn new class extends Migration {\n    public function up(): void\n    {\n        Schema::table('posts', function (Blueprint $table) {\n            $table\n                ->foreignId('deleted_by')\n                ->nullable()\n                ->constrained('users')\n                ->cascadeOnUpdate()\n                ->nullOnDelete()\n                ->after('deleted_at');\n\n            $table->string('deleted_reason')->nullable()->after('deleted_by');\n        });\n    }\n\n    public function down(): void\n    {\n        Schema::table('posts', function (Blueprint $table) {\n            $table->dropColumn('deleted_reason');\n            $table->dropConstrainedForeignId('deleted_by');\n        });\n    }\n};\n"
  },
  {
    "path": "database/migrations/2026_01_22_000005_create_flags_table.php",
    "content": "<?php\n\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\nuse Waterhole\\Database\\Migration;\n\nreturn new class extends Migration {\n    public function up()\n    {\n        Schema::create('flags', function (Blueprint $table) {\n            $table->id();\n            $table->morphs('subject');\n            $table->string('reason');\n            $table\n                ->foreignId('created_by')\n                ->nullable()\n                ->constrained('users')\n                ->cascadeOnUpdate()\n                ->nullOnDelete();\n            $table->timestamps();\n            $table->timestamp('resolved_at')->nullable();\n            $table\n                ->foreignId('resolved_by')\n                ->nullable()\n                ->constrained('users')\n                ->cascadeOnUpdate()\n                ->nullOnDelete();\n        });\n    }\n\n    public function down()\n    {\n        Schema::dropIfExists('flags');\n    }\n};\n"
  },
  {
    "path": "database/migrations/2026_01_22_000006_migrate_comment_hidden_to_deleted.php",
    "content": "<?php\n\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\DB;\nuse Illuminate\\Support\\Facades\\Schema;\nuse Waterhole\\Database\\Migration;\n\nreturn new class extends Migration {\n    public function up(): void\n    {\n        Schema::table('comments', function (Blueprint $table) {\n            $table->timestamp('deleted_at')->nullable()->index();\n\n            $table\n                ->foreignId('deleted_by')\n                ->nullable()\n                ->constrained('users')\n                ->cascadeOnUpdate()\n                ->nullOnDelete();\n\n            $table->string('deleted_reason')->nullable();\n        });\n\n        DB::table('comments')->update([\n            'deleted_at' => DB::raw('hidden_at'),\n            'deleted_by' => DB::raw('hidden_by'),\n            'deleted_reason' => DB::raw('hidden_reason'),\n        ]);\n\n        Schema::table('comments', function (Blueprint $table) {\n            $table->dropColumn('hidden_at');\n            $table->dropConstrainedForeignId('hidden_by');\n            $table->dropColumn('hidden_reason');\n        });\n    }\n\n    public function down(): void\n    {\n        Schema::table('comments', function (Blueprint $table) {\n            $table->timestamp('hidden_at')->nullable();\n\n            $table\n                ->foreignId('hidden_by')\n                ->nullable()\n                ->constrained('users')\n                ->cascadeOnUpdate()\n                ->nullOnDelete();\n\n            $table->string('hidden_reason')->nullable();\n        });\n\n        DB::table('comments')->update([\n            'hidden_at' => DB::raw('deleted_at'),\n            'hidden_by' => DB::raw('deleted_by'),\n            'hidden_reason' => DB::raw('deleted_reason'),\n        ]);\n\n        Schema::table('comments', function (Blueprint $table) {\n            $table->dropColumn('deleted_at');\n            $table->dropConstrainedForeignId('deleted_by');\n            $table->dropColumn('deleted_reason');\n        });\n    }\n};\n"
  },
  {
    "path": "database/migrations/2026_01_22_000007_add_approval_requirements_to_channels.php",
    "content": "<?php\n\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\nuse Waterhole\\Database\\Migration;\n\nreturn new class extends Migration {\n    public function up(): void\n    {\n        Schema::table('channels', function (Blueprint $table) {\n            $table->boolean('require_approval_posts')->default(false);\n            $table->boolean('require_approval_comments')->default(false);\n        });\n    }\n\n    public function down(): void\n    {\n        Schema::table('channels', function (Blueprint $table) {\n            $table->dropColumn(['require_approval_posts', 'require_approval_comments']);\n        });\n    }\n};\n"
  },
  {
    "path": "database/migrations/2026_01_22_000008_add_deleted_notes_to_posts_and_comments.php",
    "content": "<?php\n\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\nuse Waterhole\\Database\\Migration;\n\nreturn new class extends Migration {\n    public function up()\n    {\n        Schema::table('posts', function (Blueprint $table) {\n            $table->text('deleted_message')->nullable()->after('deleted_reason');\n        });\n\n        Schema::table('comments', function (Blueprint $table) {\n            $table->text('deleted_message')->nullable()->after('deleted_reason');\n        });\n    }\n\n    public function down()\n    {\n        Schema::table('posts', function (Blueprint $table) {\n            $table->dropColumn('deleted_message');\n        });\n\n        Schema::table('comments', function (Blueprint $table) {\n            $table->dropColumn('deleted_message');\n        });\n    }\n};\n"
  },
  {
    "path": "database/migrations/2026_01_24_000009_add_note_to_flags_table.php",
    "content": "<?php\n\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\nuse Waterhole\\Database\\Migration;\n\nreturn new class extends Migration {\n    public function up()\n    {\n        Schema::table('flags', function (Blueprint $table) {\n            $table->text('note')->nullable()->after('reason');\n        });\n    }\n\n    public function down()\n    {\n        Schema::table('flags', function (Blueprint $table) {\n            $table->dropColumn('note');\n        });\n    }\n};\n"
  },
  {
    "path": "database/migrations/2026_01_24_000010_add_reactions_enabled_to_channels.php",
    "content": "<?php\n\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\nuse Waterhole\\Database\\Migration;\n\nreturn new class extends Migration {\n    public function up()\n    {\n        Schema::table('channels', function (Blueprint $table) {\n            $table->boolean('posts_reactions_enabled')->default(true)->after('ignore');\n            $table\n                ->boolean('comments_reactions_enabled')\n                ->default(true)\n                ->after('posts_reaction_set_id');\n        });\n    }\n\n    public function down()\n    {\n        Schema::table('channels', function (Blueprint $table) {\n            $table->dropColumn('posts_reactions_enabled');\n            $table->dropColumn('comments_reactions_enabled');\n        });\n    }\n};\n"
  },
  {
    "path": "database/seeders/DefaultSeeder.php",
    "content": "<?php\n\nnamespace Waterhole\\Database\\Seeders;\n\nuse Illuminate\\Database\\Seeder;\nuse Illuminate\\Support\\Arr;\nuse Illuminate\\Support\\Str;\nuse Waterhole\\Filters;\nuse Waterhole\\Layouts;\nuse Waterhole\\Models\\Channel;\nuse Waterhole\\Models\\Group;\nuse Waterhole\\Models\\Page;\nuse Waterhole\\Models\\Permission;\nuse Waterhole\\Models\\ReactionSet;\n\n/**\n * A seeder that creates default groups, pages, channels, permissions, and\n * reactions upon installation.\n */\nclass DefaultSeeder extends Seeder\n{\n    public function run(): void\n    {\n        $this->callOnce(GroupsSeeder::class);\n\n        $guest = Group::guest();\n        $member = Group::member();\n        $mod = Group::custom()->firstOrFail();\n\n        // Community Guide\n        $guide = Page::firstOrCreate(\n            ['slug' => Str::slug(__('waterhole::install.guide-title'))],\n            [\n                'icon' => 'emoji:📖',\n                'name' => __('waterhole::install.guide-title'),\n                'body' => __('waterhole::install.guide-body', [\n                    'forumName' => config('waterhole.forum.name'),\n                ]),\n            ],\n        );\n\n        if ($guide->wasRecentlyCreated) {\n            $guide\n                ->permissions()\n                ->save((new Permission(['ability' => 'view']))->recipient()->associate($guest));\n\n            $guide->structure->update(['is_listed' => true]);\n        }\n\n        // Reactions\n        $emoji = ReactionSet::firstOrNew(\n            ['name' => __('waterhole::install.reaction-set-emoji')],\n            ['is_default_posts' => true, 'is_default_comments' => true],\n        );\n\n        if (!$emoji->exists) {\n            $emoji->save();\n            $emoji->reactionTypes()->createMany([\n                [\n                    'name' => __('waterhole::install.reaction-type-like'),\n                    'icon' => 'emoji:👍️',\n                    'score' => 1,\n                ],\n                [\n                    'name' => __('waterhole::install.reaction-type-love'),\n                    'icon' => 'emoji:❤️',\n                    'score' => 2,\n                ],\n                [\n                    'name' => __('waterhole::install.reaction-type-laugh'),\n                    'icon' => 'emoji:😆',\n                    'score' => 1,\n                ],\n                [\n                    'name' => __('waterhole::install.reaction-type-wow'),\n                    'icon' => 'emoji:😮',\n                    'score' => 1,\n                ],\n                [\n                    'name' => __('waterhole::install.reaction-type-sad'),\n                    'icon' => 'emoji:😢',\n                    'score' => 1,\n                ],\n                [\n                    'name' => __('waterhole::install.reaction-type-angry'),\n                    'icon' => 'emoji:😡',\n                    'score' => -1,\n                ],\n            ]);\n        }\n\n        $votes = ReactionSet::firstOrNew(['name' => __('waterhole::install.reaction-set-votes')]);\n\n        if (!$votes->exists) {\n            $votes->save();\n            $votes->reactionTypes()->create([\n                'name' => __('waterhole::install.reaction-type-upvote'),\n                'icon' => 'emoji:🔺',\n                'score' => 1,\n            ]);\n        }\n\n        // Channels\n        $channels = [\n            [\n                'name' => __('waterhole::install.announcements-name'),\n                'description' => __('waterhole::install.announcements-description'),\n                'icon' => 'emoji:📣',\n                'layout' => Layouts\\CardsLayout::class,\n                'filters' => [\n                    Filters\\Newest::class,\n                    Filters\\Latest::class,\n                    Filters\\Top::class,\n                    Filters\\Oldest::class,\n                ],\n                'group_post' => $mod,\n            ],\n            [\n                'name' => __('waterhole::install.introductions-name'),\n                'description' => __('waterhole::install.introductions-description'),\n                'icon' => 'emoji:👋',\n            ],\n            [\n                'name' => __('waterhole::install.support-name'),\n                'description' => __('waterhole::install.support-description'),\n                'icon' => 'emoji:❓',\n                'answerable' => true,\n                'show_similar_posts' => true,\n            ],\n            [\n                'name' => __('waterhole::install.ideas-name'),\n                'description' => __('waterhole::install.ideas-description'),\n                'icon' => 'emoji:💡',\n                'posts_reaction_set_id' => $votes->id,\n                'show_similar_posts' => true,\n            ],\n            [\n                'name' => __('waterhole::install.staff-name'),\n                'description' => __('waterhole::install.staff-description'),\n                'icon' => 'emoji:🔒',\n                'group' => $mod,\n            ],\n        ];\n\n        foreach ($channels as $data) {\n            $data['slug'] = Str::slug($data['name']);\n\n            $channel = Channel::firstOrCreate(\n                Arr::only($data, 'slug'),\n                Arr::except($data, ['slug', 'group', 'group_post']) + [\n                    'layout' => Layouts\\ListLayout::class,\n                ],\n            );\n\n            if ($channel->wasRecentlyCreated) {\n                $channel\n                    ->permissions()\n                    ->saveMany([\n                        (new Permission(['ability' => 'view']))\n                            ->recipient()\n                            ->associate($data['group'] ?? $guest),\n                        (new Permission(['ability' => 'post']))\n                            ->recipient()\n                            ->associate($data['group_post'] ?? ($data['group'] ?? $member)),\n                        (new Permission(['ability' => 'comment']))\n                            ->recipient()\n                            ->associate($data['group'] ?? $member),\n                        (new Permission(['ability' => 'moderate']))->recipient()->associate($mod),\n                    ]);\n\n                $channel\n                    ->structure()\n                    ->withoutGlobalScope('hasVisibleContent')\n                    ->update(['is_listed' => true]);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "database/seeders/GroupsSeeder.php",
    "content": "<?php\n\nnamespace Waterhole\\Database\\Seeders;\n\nuse Illuminate\\Database\\Seeder;\nuse Illuminate\\Support\\Facades\\DB;\nuse Waterhole\\Models\\Group;\n\n/**\n * A seeder that creates default groups.\n */\nclass GroupsSeeder extends Seeder\n{\n    public function run(): void\n    {\n        Group::updateOrCreate([\n            'id' => Group::GUEST_ID,\n            'name' => __('waterhole::install.group-guest'),\n        ]);\n\n        Group::updateOrCreate([\n            'id' => Group::MEMBER_ID,\n            'name' => __('waterhole::install.group-member'),\n        ]);\n\n        Group::updateOrCreate([\n            'id' => Group::ADMIN_ID,\n            'name' => __('waterhole::install.group-admin'),\n        ]);\n\n        // In PostgreSQL, sequences don't advance on explicit ID inserts.\n        // Manually sync to current max ID.\n        if (DB::connection()->getDriverName() === 'pgsql') {\n            $model = new Group();\n            $table = $model->getConnection()->getTablePrefix() . $model->getTable();\n\n            DB::statement(\n                \"SELECT setval(pg_get_serial_sequence('\\\"$table\\\"', 'id'), (SELECT MAX(id) FROM \\\"$table\\\"))\",\n            );\n        }\n\n        Group::updateOrCreate([\n            'name' => __('waterhole::install.group-moderator'),\n            'is_public' => true,\n        ]);\n\n        Group::updateOrCreate(\n            ['name' => __('waterhole::install.group-quarantine')],\n            [\n                'is_public' => false,\n                'auto_assign' => true,\n                'rules' => [\n                    'requires_approval' => true,\n                    'remove_after_approval' => true,\n                ],\n            ],\n        );\n    }\n}\n"
  },
  {
    "path": "lang/en/auth.ftl",
    "content": "### Auth\n\n## Laravel strings\n## https://github.com/laravel/framework/blob/master/src/Illuminate/Translation/lang/en/auth.php\n\nfailed = These credentials do not match our records.\npassword = The provided password is incorrect.\nthrottle = Too many login attempts. Please try again in { $seconds } seconds.\n\n## Login\n\nlogin-title = Log in to Your Account\nlogin-submit = Log In\nlogin-register-prompt = Don't have an account?\nlogin-register-link = Sign Up\ncontinue-with-provider = Continue with { $provider }\n\nemail-label = Email\npassword-label = Password\nremember-me-label = Remember me\nforgot-password-link = Forgot your password?\n\n## Forgot Password\n\nforgot-password-title = Forgot your password?\nforgot-password-introduction = Request a password reset link by entering your email address below.\nforgot-password-submit = Send Password Reset Link\n\nreset-password-title = Reset Password\nnew-password-label = New Password\nconfirm-password-label = Confirm Password\nreset-password-submit = Reset Password\n\nreset-password-mail-subject = Reset Your Password\nreset-password-mail-body = We received a password reset request for your account on { $forum }. If this was not you, no further action is required. This link will expire in { $minutes } minutes.\nreset-password-mail-button = Reset Password\n\n## Register\n\nregister-title = Create an Account\nname-label = Username\nregister-submit = Create Account\nregister-login-prompt = Already have an account?\nregister-login-link = Log In\n\n## Confirm Password\n\nconfirm-password-title = Confirm Your Password\nconfirm-password-introduction = Please confirm your password before continuing.\nconfirm-password-submit = Confirm\n\n## Email Verification\n\nemail-verification-sent-message = We've sent a confirmation email to { $email }. If it doesn't arrive soon, check your spam folder.\nemail-verification-resend-button = Resend\nemail-verification-required-message = You must verify your email address.\nemail-verification-success-message = Thanks for verifying your email!\nemail-verification-mail-subject = Verify Email Address\nemail-verification-mail-body = Please click the button below to verify your email address. If you do not have an account on { $forum }, no further action is required.\nemail-verification-mail-button = Verify Email Address\n"
  },
  {
    "path": "lang/en/cp.ftl",
    "content": "### Localization for Waterhole Control Panel\n\ntitle = Control Panel\n\n## Dashboard\n\ndashboard-title = Dashboard\n\nconfigure-mail-message = You need to configure a mail driver in order for Waterhole to be able to send out verification emails and notifications.\ndebug-mode-on-message = Debug mode is ON. Sensitive configuration values may be exposed.\n\ngetting-started-title = Get Started With Waterhole\ngetting-started-strategy-title = Read the Docs\ngetting-started-strategy-description = Learn how to build a successful community with Waterhole.\ngetting-started-structure-title = Set Up Your Structure\ngetting-started-structure-description = Configure the channels and pages that make up the skeleton of your community.\ngetting-started-groups-title = Define User Groups\ngetting-started-groups-description = Set up groups for moderators, staff, and superusers.\ngetting-started-design-title = Join the Waterhole Community\ngetting-started-design-description = Ask questions, share tips, and learn how to get the most out of your community.\n\ndashboard-users-title = Users\ndashboard-posts-title = Posts\ndashboard-comments-title = Comments\n\nperiod-today = Today\nperiod-last-7-days = Last 7 days\nperiod-last-4-weeks = Last 4 weeks\nperiod-last-3-months = Last 3 months\nperiod-last-12-months = Last 12 months\nperiod-this-month = This month\nperiod-this-quarter = This quarter\nperiod-this-year = This year\nperiod-all-time = All time\nperiod-current-heading = Current Period\npreiod-previous-heading = Previous Period\n\n## Structure\n\nstructure-title = Structure\n\nstructure-channel-label = Channel\nstructure-page-label = Page\nstructure-link-label = Link\nstructure-heading-label = Heading\nstructure-visibility-public-label = Public\nstructure-visibility-members-label = Members\n\nstructure-navigation-title = Navigation\nstructure-navigation-description = Move items here to show them in the navigation menu.\n\nstructure-unlisted-title = Unlisted\nstructure-unlisted-description = Move items here to hide them from the navigation menu.\n\ndelete-structure-confirm-message = Are you sure you want to delete this node?\n\n## Structure - Heading\n\nedit-heading-title = Edit Heading\ncreate-heading-title = Create a Heading\nheading-name-label = Name\n\n## Structure - Link\n\nedit-link-title = Edit Link\ncreate-link-title = Create a Link\nlink-details-title = Details\nlink-name-label = Name\nlink-url-label = URL\nlink-permissions-title = Permissions\n\n## Structure - Page\n\nedit-page-title = Edit Page\ncreate-page-title = Create a Page\npage-details-title = Details\npage-name-label = Name\npage-slug-label = Slug\npage-slug-url-label = This page will be accessible at:\npage-body-label = Body\npage-permissions-title = Permissions\n\n## Structure - Channel\n\nedit-channel-title = Edit Channel\ncreate-channel-title = Create a Channel\nchannel-details-title = Details\nchannel-name-label = Name\nchannel-slug-label = Slug\nchannel-slug-url-label = This channel will be accessible at:\nchannel-description-label = Description\nchannel-description-description = A brief description of what this channel is for.\nchannel-options-title = Options\nchannel-visibility-label = Visibility\nchannel-ignore-label = Ignored by default\nchannel-ignore-description = Hide posts in this channel from the Feed for all users, unless they explicitly follow it.\nchannel-layout-title = Layout\nchannel-layout-label = Layout\nchannel-layout-show-author-label = Show post author\nchannel-layout-show-excerpt-label = Show post excerpt\nchannel-filters-label = Filters\nchannel-custom-filters-label = Use custom filters for this channel\nchannel-custom-filters-description = Override the global filters for this channel.\nchannel-permissions-title = Permissions\nchannel-approval-label = Approval\nchannel-approval-moderators-exempt = Moderators are exempt from approval.\nchannel-require-approval-posts-label = Require approval for posts\nchannel-require-approval-comments-label = Require approval for comments\nchannel-features-title = Features\nchannel-reactions-label = Reactions\nchannel-reactions-posts-label = Posts\nchannel-reactions-comments-label = Comments\nreaction-set-picker-default = Default ({ $name })\nreaction-set-picker-none = None\nchannel-taxonomies-label = Taxonomies\nchannel-answers-label = Answers\nchannel-enable-answers-label = Enable answers on this channel\nchannel-enable-answers-description = Allow post authors to mark a comment as the answer.\nchannel-posting-title = Posting\nchannel-instructions-label = Posting Instructions\nchannel-instructions-description = Give instructions to be shown to users as they create posts in this channel.\nchannel-similar-posts-title = Similar Posts\nchannel-show-similar-posts-label = Show similar posts from this channel based on the title\n\ndelete-channel-title = Delete Channel:\ndelete-channel-posts-label = Delete { $count } { $count ->\n    [one] post\n    *[other] posts\n}\nmove-to-channel-posts-label = Move { $count } { $count ->\n    [one] post\n    *[other] posts\n} to another channel\n\n## Groups\n\ngroups-title = Groups\ncreate-group-button = Create Group\ngroup-user-count = { $count } { $count ->\n    [one] user\n    *[other] users\n}\n\nedit-group-title = Edit Group\ncreate-group-title = Create a Group\ngroup-details-title = Details\ngroup-name-label = Name\ngroup-appearance-label = Appearance\ngroup-show-as-badge-label = Show this group as a user badge\ngroup-color-label = Color\ngroup-icon-label = Icon\ngroup-rules-title = Participation\ngroup-auto-assign-label = Auto-assign this group to new members\ngroup-rules-requires-approval-label = Require approval for new members' content\ngroup-rules-remove-after-approval-label = Remove from group after approval\ngroup-permissions-title = Permissions\ngroup-global-permissions-title = Global Permissions\ngroup-structure-permissions-title = Structure Permissions\ngroup-permission-suspend-users-label = Allow suspending users\n\ndelete-group-confirm-message = Are you sure you want to delete this group?\n\n## Users\n\nusers-title = Users\nusers-filter-placeholder = Filter users\nusers-filter-group-description = Filter by group\ncreate-user-button = Create User\n\nusers-name-column = Name\nusers-email-column = Email\nusers-groups-column = Groups\nusers-created-at-column = Created\nusers-last-seen-at-column = Last Seen\nusers-empty-message = No Results Found\n\nedit-user-title = Edit User\ncreate-user-title = Create a User\nuser-account-title = Account\nuser-name-label = Name\nuser-email-label = Email\nuser-password-label = Password\nuser-set-password-label = Set new password\nuser-groups-label = Groups\nuser-profile-title = Profile\nuser-created-message = User created.\nuser-saved-message = User saved.\n\ndelete-user-title = Delete { $count ->\n    [one] User:\n    *[other] { $count } Users\n}\nkeep-user-content-label = Keep content and mark as anonymous\ndelete-user-content-label = Delete content permanently\ndelete-user-success-message = User deleted.\n\n## Reactions\n\nreactions-title = Reactions\nreaction-sets-title = Reaction Sets\ncreate-reaction-set-button = Create Reaction Set\nedit-reaction-set-title = Edit Reaction Set\ncreate-reaction-set-title = Create a Reaction Set\nreaction-set-details-title = Details\nreaction-set-name-label = Name\nreaction-set-usage-label = Usage\nreaction-set-default-posts = Use as default for posts\nreaction-set-default-comments = Use as default for comments\ndelete-reaction-set-confirm-message = Are you sure you want to delete this reaction set?\nreaction-set-saved-message = Reaction set saved.\n\nreaction-types-title = Reaction Types\nreaction-types-empty-message = No Reaction Types\nreaction-types-add-button = Add\nedit-reaction-type-title = Edit Reaction Type\ncreate-reaction-type-title = Create a Reaction Type\nreaction-type-name-label = Name\nreaction-type-score-label = Score\nreaction-type-score-description = The number of points that this reaction is worth.\ndelete-reaction-type-confirm-message = Are you sure you want to delete this reaction type?\nreaction-type-saved-message = Reaction type saved.\n\n## Taxonomies\n\ntaxonomies-title = Taxonomies\ncreate-taxonomy-button = Create Taxonomy\ncreate-taxonomy-title = Create a Taxonomy\nedit-taxonomy-title = Edit Taxonomy\ntaxonomy-details-title = Details\ntaxonomy-permissions-title = Permissions\ntaxonomy-tags-title = Tags\ntaxonomy-name-label = Name\ntaxonomy-options-title = Options\ntaxonomy-required-label = Require a tag to be selected on post creation\ntaxonomy-allow-multiple-label = Allow selection of multiple tags\ntaxonomy-saved-message = Taxonomy saved.\ndelete-taxonomy-confirm-message = Are you sure you want to delete this taxonomy?\n\ncreate-tag-title = Create a Tag\nedit-tag-title = Edit Tag\ntag-name-label = Name\ntag-saved-message = Tag saved.\ndelete-tag-confirm-message = Are you sure you want to delete this tag?\n\n## Licensing\n\nlicense-error-message = Your license could not be validated, because there was an error communicating with the Waterhole API. ({ $status })\nlicense-invalid-message = Please purchase or enter a valid license key for this site to comply with the License Agreement.\nlicense-expired-message = You are not licensed to use this version of Waterhole. Please downgrade or renew your license.\nlicense-suspended-message = Your Waterhole license has been suspended. Please contact us for more information.\n\ntrial-badge = Trial\nlicensed-badge = Licensed\nunlicensed-badge = Unlicensed\n"
  },
  {
    "path": "lang/en/forum.ftl",
    "content": "### Forum\n\nfeed-link = Feed\n\n## Header\n\nlog-in = Log In\nregister = Sign Up\n\n## Search\n\nsearch-placeholder = Search all discussions\nsearch-results-title = Search Results for \"{ $query }\"\nsearch-button = Search\n\nsearch-filter-button = Filter\n\nsearch-showing-results-title = Showing { $total } { $total ->\n    [one] result\n    *[other] results\n}\n\nsearch-showing-results-non-exhaustive-title = Showing { $total }+ { $total ->\n    [one] result\n    *[other] results\n}\n\nsearch-sort-relevance = Sort by Relevance\nsearch-sort-latest = Sort by Latest\nsearch-sort-top = Sort by Top\n\nsearch-empty-message = No Results Found\nsearch-keywords-too-short-message = Your keywords are too short – try something longer.\n\n## Posts\n\npost-activity-replied = replied\npost-activity-posted = posted\n\npost-new-badge = New\npost-new-badge-tooltip = New post\npost-locked-badge = Locked\npost-answered-badge = Answered\npost-trash-badge = Trash\n\npost-removed-message = Post removed\n\npost-answered-by = Answered by\npost-view-answer-link = View Answer\n\npost-unread-comments-badge-tooltip = { $count } { $count ->\n    [one] unread comment\n    *[other] unread comments\n}\n\npost-comments-heading = { $count } { $count ->\n    [one] Comment\n    *[other] Comments\n}\n\nmark-as-read-instruction = Click to mark as read\n\npost-comments-link = { $count } { $count ->\n    [one] comment\n    *[other] comments\n}\n\nadd-reaction-button = Add Reaction\n\nmove-post-title = Move { $count ->\n    [one] Post:\n    *[other] { $count } Posts\n}\n\nmove-to-channel-button = Move to Channel\nmove-to-channel-confirm-button = Move\n\nmark-as-read-button = Mark as Read\n\ncreate-post-button = Create a Post\ncreate-post-title = New Post\npost-channel-label = Channel\npost-submit-button = Post\nedit-post-title = Edit Post\nedit-post-link = Edit Post\npost-title-label = Title\nsimilar-posts-label = See these similar posts:\npost-body-label = Body\n\nchannel-picker-placeholder = Select a Channel\n\ndelete-post-confirm-message = Are you sure you want to permanently delete this post?\ndelete-post-success-message = Post deleted.\n\noriginal-post-link = Original Post\n\npin-to-top-button = Pin to Top\nunpin-button = Unpin\n\npost-comment-button = Comment\n\nmove-to-trash-button = Move to Trash\nrestore-button = Restore\ndelete-forever-button = Delete Forever\n\n## Comments\n\ncomments-unread-heading = Unread\ncomments-unread-link = Unread\n\ncreate-comment-title = Write a Comment\nedit-comment-title = Edit Comment\ncomment-number-title = Comment #{ $number }\n\ncomment-in-reply-to-link = In reply to\ncomment-show-replies-button = Show { $count } { $count ->\n    [one] reply\n    *[other] replies\n}\ncomment-reply-button = Reply\nmark-as-answer-button = Mark as Answer\nunmark-as-answer-button = Unmark as Answer\ncomment-answer-badge = Answer\n\ncomments-locked-message = Comments are locked.\nlock-comments-button = Lock Comments\nunlock-comments-button = Unlock Comments\n\ncomposer-placeholder = Write a comment...\ncomposer-reply-to-placeholder = Reply to { $userName }...\ncomposer-replying-to-label = Replying to\ncomposer-clear-reply-button = Clear\ncomposer-submit = Post\n\ndelete-comment-confirm-message = Are you sure you want to permanently delete this comment?\n\ncomment-removed-message = Comment removed\ncomment-removed-tooltip = { $user } removed { $timestamp }\n\n## Moderation\n\nremove-button = Remove\nremoved-by-label = Removed by\n\nremoval-reason-label = Removal Reason\nremoval-reason-unspecified-label = Unspecified\nremoval-message-label = Message to Author\nreport-button = Report\nreport-confirm-button = Submit Report\nreport-note-placeholder = Add a note (optional)\nreport-system-user = System\nuser-actions-label = User Actions\nuser-actions-suspend-label = Suspend { $user }\nuser-actions-suspend-days = Days\nuser-actions-suspend-weeks = Weeks\nuser-actions-suspend-indefinitely = Indefinitely\n\napprove-button = Approve\nflag-dismiss-button = Dismiss\n\nreport-reason-off-topic-label = Off-topic\nreport-reason-off-topic-description = This content is not relevant to the current discussion.\n\nreport-reason-inappropriate-label = Inappropriate\nreport-reason-inappropriate-description = This content is offensive, abusive, or violates the community guidelines.\n\nreport-reason-spam-label = Spam\nreport-reason-spam-description = This content is an advertisement or vandalism.\n\nreport-reason-other-label = Other\nreport-reason-other-description = This content requires attention for some other reason.\n\nreport-reason-approval-label = Pending approval\n\nmoderation-title = Moderation\nmoderation-empty-message = No Pending Flags\nmoderation-finished-message = You're all caught up.\n\npending-approval-title = Pending approval\n\n## Misc\n\nquote-button = Quote\n\nattribution-timestamp-created-label = Posted\nattribution-timestamp-edited-label = Edited\n\n## Filters\n\nfilter-alphabetical = Alphabetical\nfilter-following = Following\nfilter-ignoring = Ignoring\nfilter-newest = Newest\nfilter-latest = Latest\nfilter-oldest = Oldest\nfilter-top = Top\nfilter-top-all-time = All Time\nfilter-top-year = Year\nfilter-top-quarter = Quarter\nfilter-top-month = Month\nfilter-top-week = Week\nfilter-top-day = Day\nfilter-trending = Trending\nfilter-trash = Trash\n\n## Followables\n\nfollow-button = Follow\nfollow-button-following = Following\nfollow-button-ignored = Ignored\n\nignore-button = Ignore\nunfollow-button = Unfollow\nunignore-button = Unignore\n\nchannel-follow-description = Get notified when there are new posts in this channel.\npost-follow-description = Get notified when there are new comments on this post.\n\npost-following-badge = Following\npost-ignored-badge = Ignored\n\n## Index\n\nmenu-button = Menu\nnavigation-title = Forum Navigation\n\npost-feed-new-activity-button = New Activity\npost-feed-new-activity-heading = New Activity\npost-feed-empty-message = No Posts\npost-feed-controls-layout-heading = Display as\n"
  },
  {
    "path": "lang/en/install.ftl",
    "content": "### Installation\n\n## Groups\n\ngroup-guest = Guest\ngroup-member = Member\ngroup-admin = Admin\ngroup-moderator = Mod\ngroup-quarantine = Quarantine\n\n## Reactions\n\nreaction-set-emoji = Emoji\nreaction-type-like = Like\nreaction-type-love = Love\nreaction-type-laugh = Laugh\nreaction-type-wow = Wow\nreaction-type-sad = Sad\nreaction-type-angry = Angry\n\nreaction-set-votes = Votes\nreaction-type-upvote = Upvote\n\n## Structure\n\nannouncements-name = Announcements\nannouncements-description = News and other updates from the team.\n\nintroductions-name = Introductions\nintroductions-description = New to the community? Introduce yourself!\n\nsupport-name = Support\nsupport-description = Get help setting up, using, and customising our product.\n\nideas-name = Ideas\nideas-description = Have an idea? We want to hear it!\n\nstaff-name = Staff Only\nstaff-description = A private channel for staff discussion.\n\nguide-title = Community Guide\nguide-body =\n  Welcome to { $forumName }, and thanks for joining us! We want everyone to get the most out of of this community, so we ask that you please read and follow these guidelines.\n\n  - **Be civil.** This is a place to share knowledge and interests through conversation. Be kind, patient, and respectful towards everyone, including people outside of the Waterhole community.\n\n  - **No personal attacks.** Criticizing ideas, by means of reasoned arguments, is an important part of what we're doing here. But it's not okay for that to devolve into personal attacks. Harassment and other exclusionary behavior are never acceptable.\n\n  - **Assume good faith.** When there is disagreement, try to understand why, always assuming good intentions. Remember that different people have different perspectives on issues, and that's okay.\n"
  },
  {
    "path": "lang/en/notifications.ftl",
    "content": "### Notifications\n\ntitle = Notifications\nmark-all-as-read-button = Mark All as Read\npreferences-button = Notification Preferences\nempty-message = No Notifications\n\n## Unsubscribe\n\nunsubscribe-link = Unsubscribe from these notifications\nunsubscribe-success-message = You've been unsubscribed from these notifications.\nmanage-notification-preferences-link = Manage notification preferences\n\n## Mention\n\nmention-description = Mentions and replies to my comments\nmention-title = Mentioned in { $post }\nmention-reason = You received this because you are subscribed to mention notifications.\nmention-unsubscribe = Unsubscribe from mention notifications\n\n## Content Approved\n\npost-approved-title = Post approved: { $post }\ncomment-approved-title = Comment approved in { $post }\n\n## New Comment\n\nnew-comment-description = New comments on followed posts\nnew-comment-title = New comment in { $post }\nnew-comment-reason = You received this because you are following this post.\nnew-comment-unsubscribe = Unfollow this post\n\n## New Post\n\nnew-post-description = New posts in followed channels\nnew-post-title = New post in { $channel }: { $post }\nnew-post-reason = You received this because you are following this channel.\nnew-post-unsubscribe = Unfollow this channel\n\n## New Flag\n\nnew-flag-description = Flags in channels I moderate\nflagged-post-title = Post flagged: { $post }\nflagged-comment-title = Comment flagged in { $post }\nnew-flag-reason = You received this because you moderate a channel.\nnew-flag-unsubscribe = Unsubscribe from flag notifications\n\n## Content Removed\n\npost-removed-title = Post removed: { $post }\ncomment-removed-title = Comment removed in { $post }\n\n## Common\n\nview-post-button = View Post\nview-comment-button = View Comment\n"
  },
  {
    "path": "lang/en/passwords.php",
    "content": "<?php\n\n// https://github.com/laravel/framework/blob/master/src/Illuminate/Translation/lang/en/passwords.php\n\nreturn [\n    'reset' => 'Your password has been reset.',\n    'sent' => 'We have emailed your password reset link.',\n    'throttled' => 'Please wait before retrying.',\n    'token' => 'This password reset token is invalid.',\n    'user' => \"We can't find a user with that email address.\",\n];\n"
  },
  {
    "path": "lang/en/system.ftl",
    "content": "## Accessibility\n\nskip-to-main-content-link = Skip to main content\n\n## Errors\n\nfatal-error-heading = Something Went Wrong\ntry-again-button = Try Again\n\nfatal-error-message = Something went wrong! Please reload the page and try again.\ntoo-many-requests-message = You're going a bit too fast! Take a break and try again in a moment.\nforbidden-message = You don't have permission to do this.\nsession-expired-message = Your session has expired. Please reload the page and try again.\nvalidation-errors-message = The following errors were found:\n\n## Generic Buttons & Links\n\nsave-changes-button = Save Changes\ncreate-button = Create\ncancel-button = Cancel\nchange-button = Change\ncontinue-button = Continue\nactions-button = Actions\nlearn-more-link = Learn More\ndelete-button = Delete\nedit-link = Edit\ncopy-link-button = Copy Link\ncontrols-button = Controls\nmore-button = More\nloading = Loading\nshow-more-button = Show more\n\n## Post Feed Layouts\n\nlayout-list = List\nlayout-cards = Cards\n\n## Actions\n\nconfirm-action-title = Confirm Action\ndelete-confirm-button = Delete\nlink-copied-message = Link copied to clipboard.\n\n## Users\n\ndeleted-user = Deleted User\nuser-list-overflow = { $count } others\n\n## Pagination\n\npagination-first-link = First\npagination-previous-link = Previous\npagination-next-link = Next\npagination-last-link = Last\nload-more-button = Load More\npage-number-prefix = Page\npage-number-heading = Page { $number }\n\n## Theme Switcher\n\ntheme-button = Theme\ntheme-light = Light\ntheme-dark = Dark\ntheme-automatic = Automatic\n\n## Text Editor\n\ntext-editor-heading = Heading\ntext-editor-bold = Bold\ntext-editor-italic = Italic\ntext-editor-quote = Quote\ntext-editor-code = Code\ntext-editor-link = Link\ntext-editor-bulleted-list = Bulleted List\ntext-editor-numbered-list = Numbered List\ntext-editor-mention = Mention a User\ntext-editor-emoji = Insert Emoji\ntext-editor-attachment = Attach Files\ntext-editor-preview = Preview\n\n## Icon Picker\n\nicon-field-label = Icon\nicon-picker-change-button = Change\nicon-picker-none-option = None\nicon-picker-emoji-option = Emoji\nicon-picker-emoji-description = Enter a single emoji character.\nicon-picker-svg-option = SVG Icon\nicon-picker-svg-description = Enter the name of an icon from one of the following installed sets: { $sets }.\nicon-picker-svg-search-link = Search icons\nicon-picker-image-option = Image\n\n## Abilities\n\nability-view = View\nability-comment = Comment\nability-post = Post\nability-moderate = Moderate\nability-assign-tags = Assign Tags\n\n## Used in the Waterhole\\compact_number() function\n\ncompact-number-1000 = 0.0K\ncompact-number-10000 = 00K\ncompact-number-100000 = 000K\ncompact-number-1000000 = 0.0M\ncompact-number-10000000 = 00M\ncompact-number-100000000 = 000M\ncompact-number-1000000000 = 0.0B\n"
  },
  {
    "path": "lang/en/user.ftl",
    "content": "### User\n\n## Account Settings\n\naccount-settings-title = Account Settings\n\ndelete-account-button = Delete Your Account\ndelete-account-confirmation-title = Are you sure you want to delete your account?\ndelete-account-confirmation-description = Your account data will be removed. Your contributions will be retained but marked as anonymous. This cannot be undone.\ndelete-account-success-message = Your account has been deleted.\n\n## Notification Preferences\n\nnotification-preferences-title = Notification Preferences\nnotifications-label = Notifications\nnotification-channel-web = Web\nnotification-channel-email = Email\nnotifications-following-label = Following\nfollow-on-comment-label = Automatically follow posts I comment on\nnotification-preferences-saved-message = Notification preferences saved.\n\n## Edit Profile\n\nedit-profile-title = Edit Profile\nprofile-saved-message = Profile saved.\n\navatar-label = Avatar\nremove-avatar-label = Remove avatar\nheadline-label = Headline\nheadline-description = Describe yourself in a few words. This will be displayed next to your name.\nbio-label = Bio\nbio-description = Write more about yourself. This will be displayed on your profile.\nlocation-label = Location\nwebsite-label = Website\nprivacy-title = Privacy\nshow-online-label = Show when I was last online\n\n## Comments\n\nuser-comments-title = { $userName }'s Comments\ncomments-empty-message = No Comments\n\n## Posts\n\nuser-posts-title = { $userName }'s Posts\nposts-empty-message = No Posts\n\n## User Menu\n\nprofile-link = Profile\npreferences-link = Preferences\nadministration-link = Administration\nlog-out-link = Log Out\n\n## Profile\n\nuser-joined-text = Joined\nuser-last-seen-text = Last seen\nonline-label = Online\n\n## Sidebar\n\nposts-link = Posts\ncomments-link = Comments\npreferences-heading = Preferences\naccount-settings-link = Account\nedit-profile-link = Profile\nnotification-preferences-link = Notifications\n\n## Admin\n\nsuspend-button = Suspend\nedit-suspension-button = Edit Suspension\nsuspend-user-title = Suspend\nnot-suspended-label = Not suspended\nsuspended-indefinitely-label = Suspended indefinitely\nsuspended-until-label = Suspended until...\nsuspended-badge = Suspended\nsuspended-message = Your account has been suspended.\n\ncopy-impersonation-url-button = Copy Impersonation URL\nimpersonation-url-copied-message = Impersonation URL copied – open it in a new private window.\n"
  },
  {
    "path": "lang/en/validation.php",
    "content": "<?php\n\n// https://github.com/laravel/framework/blob/master/src/Illuminate/Translation/lang/en/validation.php\n\nreturn [\n    'accepted' => 'The :attribute field must be accepted.',\n    'accepted_if' => 'The :attribute field must be accepted when :other is :value.',\n    'active_url' => 'The :attribute field must be a valid URL.',\n    'after' => 'The :attribute field must be a date after :date.',\n    'after_or_equal' => 'The :attribute field must be a date after or equal to :date.',\n    'alpha' => 'The :attribute field must only contain letters.',\n    'alpha_dash' =>\n        'The :attribute field must only contain letters, numbers, dashes, and underscores.',\n    'alpha_num' => 'The :attribute field must only contain letters and numbers.',\n    'any_of' => 'The :attribute field is invalid.',\n    'array' => 'The :attribute field must be an array.',\n    'ascii' =>\n        'The :attribute field must only contain single-byte alphanumeric characters and symbols.',\n    'before' => 'The :attribute field must be a date before :date.',\n    'before_or_equal' => 'The :attribute field must be a date before or equal to :date.',\n    'between' => [\n        'array' => 'The :attribute field must have between :min and :max items.',\n        'file' => 'The :attribute field must be between :min and :max kilobytes.',\n        'numeric' => 'The :attribute field must be between :min and :max.',\n        'string' => 'The :attribute field must be between :min and :max characters.',\n    ],\n    'boolean' => 'The :attribute field must be true or false.',\n    'can' => 'The :attribute field contains an unauthorized value.',\n    'confirmed' => 'The :attribute field confirmation does not match.',\n    'contains' => 'The :attribute field is missing a required value.',\n    'current_password' => 'The password is incorrect.',\n    'date' => 'The :attribute field must be a valid date.',\n    'date_equals' => 'The :attribute field must be a date equal to :date.',\n    'date_format' => 'The :attribute field must match the format :format.',\n    'decimal' => 'The :attribute field must have :decimal decimal places.',\n    'declined' => 'The :attribute field must be declined.',\n    'declined_if' => 'The :attribute field must be declined when :other is :value.',\n    'different' => 'The :attribute field and :other must be different.',\n    'digits' => 'The :attribute field must be :digits digits.',\n    'digits_between' => 'The :attribute field must be between :min and :max digits.',\n    'dimensions' => 'The :attribute field has invalid image dimensions.',\n    'distinct' => 'The :attribute field has a duplicate value.',\n    'doesnt_contain' => 'The :attribute field must not contain any of the following: :values.',\n    'doesnt_end_with' => 'The :attribute field must not end with one of the following: :values.',\n    'doesnt_start_with' =>\n        'The :attribute field must not start with one of the following: :values.',\n    'email' => 'The :attribute field must be a valid email address.',\n    'encoding' => 'The :attribute field must be encoded in :encoding.',\n    'ends_with' => 'The :attribute field must end with one of the following: :values.',\n    'enum' => 'The selected :attribute is invalid.',\n    'exists' => 'The selected :attribute is invalid.',\n    'extensions' => 'The :attribute field must have one of the following extensions: :values.',\n    'file' => 'The :attribute field must be a file.',\n    'filled' => 'The :attribute field must have a value.',\n    'gt' => [\n        'array' => 'The :attribute field must have more than :value items.',\n        'file' => 'The :attribute field must be greater than :value kilobytes.',\n        'numeric' => 'The :attribute field must be greater than :value.',\n        'string' => 'The :attribute field must be greater than :value characters.',\n    ],\n    'gte' => [\n        'array' => 'The :attribute field must have :value items or more.',\n        'file' => 'The :attribute field must be greater than or equal to :value kilobytes.',\n        'numeric' => 'The :attribute field must be greater than or equal to :value.',\n        'string' => 'The :attribute field must be greater than or equal to :value characters.',\n    ],\n    'hex_color' => 'The :attribute field must be a valid hexadecimal color.',\n    'image' => 'The :attribute field must be an image.',\n    'in' => 'The selected :attribute is invalid.',\n    'in_array' => 'The :attribute field must exist in :other.',\n    'in_array_keys' =>\n        'The :attribute field must contain at least one of the following keys: :values.',\n    'integer' => 'The :attribute field must be an integer.',\n    'ip' => 'The :attribute field must be a valid IP address.',\n    'ipv4' => 'The :attribute field must be a valid IPv4 address.',\n    'ipv6' => 'The :attribute field must be a valid IPv6 address.',\n    'json' => 'The :attribute field must be a valid JSON string.',\n    'list' => 'The :attribute field must be a list.',\n    'lowercase' => 'The :attribute field must be lowercase.',\n    'lt' => [\n        'array' => 'The :attribute field must have less than :value items.',\n        'file' => 'The :attribute field must be less than :value kilobytes.',\n        'numeric' => 'The :attribute field must be less than :value.',\n        'string' => 'The :attribute field must be less than :value characters.',\n    ],\n    'lte' => [\n        'array' => 'The :attribute field must not have more than :value items.',\n        'file' => 'The :attribute field must be less than or equal to :value kilobytes.',\n        'numeric' => 'The :attribute field must be less than or equal to :value.',\n        'string' => 'The :attribute field must be less than or equal to :value characters.',\n    ],\n    'mac_address' => 'The :attribute field must be a valid MAC address.',\n    'max' => [\n        'array' => 'The :attribute field must not have more than :max items.',\n        'file' => 'The :attribute field must not be greater than :max kilobytes.',\n        'numeric' => 'The :attribute field must not be greater than :max.',\n        'string' => 'The :attribute field must not be greater than :max characters.',\n    ],\n    'max_digits' => 'The :attribute field must not have more than :max digits.',\n    'mimes' => 'The :attribute field must be a file of type: :values.',\n    'mimetypes' => 'The :attribute field must be a file of type: :values.',\n    'min' => [\n        'array' => 'The :attribute field must have at least :min items.',\n        'file' => 'The :attribute field must be at least :min kilobytes.',\n        'numeric' => 'The :attribute field must be at least :min.',\n        'string' => 'The :attribute field must be at least :min characters.',\n    ],\n    'min_digits' => 'The :attribute field must have at least :min digits.',\n    'missing' => 'The :attribute field must be missing.',\n    'missing_if' => 'The :attribute field must be missing when :other is :value.',\n    'missing_unless' => 'The :attribute field must be missing unless :other is :value.',\n    'missing_with' => 'The :attribute field must be missing when :values is present.',\n    'missing_with_all' => 'The :attribute field must be missing when :values are present.',\n    'multiple_of' => 'The :attribute field must be a multiple of :value.',\n    'not_in' => 'The selected :attribute is invalid.',\n    'not_regex' => 'The :attribute field format is invalid.',\n    'numeric' => 'The :attribute field must be a number.',\n    'password' => [\n        'letters' => 'The :attribute field must contain at least one letter.',\n        'mixed' =>\n            'The :attribute field must contain at least one uppercase and one lowercase letter.',\n        'numbers' => 'The :attribute field must contain at least one number.',\n        'symbols' => 'The :attribute field must contain at least one symbol.',\n        'uncompromised' =>\n            'The given :attribute has appeared in a data leak. Please choose a different :attribute.',\n    ],\n    'present' => 'The :attribute field must be present.',\n    'present_if' => 'The :attribute field must be present when :other is :value.',\n    'present_unless' => 'The :attribute field must be present unless :other is :value.',\n    'present_with' => 'The :attribute field must be present when :values is present.',\n    'present_with_all' => 'The :attribute field must be present when :values are present.',\n    'prohibited' => 'The :attribute field is prohibited.',\n    'prohibited_if' => 'The :attribute field is prohibited when :other is :value.',\n    'prohibited_if_accepted' => 'The :attribute field is prohibited when :other is accepted.',\n    'prohibited_if_declined' => 'The :attribute field is prohibited when :other is declined.',\n    'prohibited_unless' => 'The :attribute field is prohibited unless :other is in :values.',\n    'prohibits' => 'The :attribute field prohibits :other from being present.',\n    'regex' => 'The :attribute field format is invalid.',\n    'required' => 'The :attribute field is required.',\n    'required_array_keys' => 'The :attribute field must contain entries for: :values.',\n    'required_if' => 'The :attribute field is required when :other is :value.',\n    'required_if_accepted' => 'The :attribute field is required when :other is accepted.',\n    'required_if_declined' => 'The :attribute field is required when :other is declined.',\n    'required_unless' => 'The :attribute field is required unless :other is in :values.',\n    'required_with' => 'The :attribute field is required when :values is present.',\n    'required_with_all' => 'The :attribute field is required when :values are present.',\n    'required_without' => 'The :attribute field is required when :values is not present.',\n    'required_without_all' => 'The :attribute field is required when none of :values are present.',\n    'same' => 'The :attribute field must match :other.',\n    'size' => [\n        'array' => 'The :attribute field must contain :size items.',\n        'file' => 'The :attribute field must be :size kilobytes.',\n        'numeric' => 'The :attribute field must be :size.',\n        'string' => 'The :attribute field must be :size characters.',\n    ],\n    'starts_with' => 'The :attribute field must start with one of the following: :values.',\n    'string' => 'The :attribute field must be a string.',\n    'timezone' => 'The :attribute field must be a valid timezone.',\n    'unique' => 'The :attribute has already been taken.',\n    'uploaded' => 'The :attribute failed to upload.',\n    'uppercase' => 'The :attribute field must be uppercase.',\n    'url' => 'The :attribute field must be a valid URL.',\n    'ulid' => 'The :attribute field must be a valid ULID.',\n    'uuid' => 'The :attribute field must be a valid UUID.',\n];\n"
  },
  {
    "path": "lang/fr/auth.ftl",
    "content": "### Auth\n\n## Laravel strings\n## https://github.com/laravel/framework/blob/master/src/Illuminate/Translation/lang/en/auth.php\n\nfailed = Ces identifiants ne correspondent pas à ceux de nos bases de données.\npassword = Le mot de passe saisi est incorrect.\nthrottle = Trop de tentatives de connexion. Veuillez réessayer dans { $seconds } secondes.\n\n## Login\n\nlogin-title = Se connecter à votre compte\nlogin-submit = Se connecter\nlogin-register-prompt = Vous n'avez pas de compte ?\nlogin-register-link = S'inscrire\ncontinue-with-provider = Continuer avec { $provider }\n\nemail-label = Adresse de courriel\npassword-label = Mot de passe\nremember-me-label = Se souvenir de moi\nforgot-password-link = Mot de passe oublié ?\n\n## Forgot Password\n\nforgot-password-title = Mot de passe oublié ?\nforgot-password-introduction = Demandez un lien de réinitialisation du mot de passe en saisissant ci-dessous votre adresse de courriel.\nforgot-password-submit = Envoyer le lien de réinitialisation du mot de passe\n\nreset-password-title = Réinitialiser le mot de passe\nnew-password-label = Nouveau mot de passe\nconfirm-password-label = Confirmer le mot de passe\nreset-password-submit = Réinitialiser le mot de passe\n\nreset-password-mail-subject = Réinitialiser votre mot de passe\nreset-password-mail-body = Nous avons reçu une demande de réinitialisation du mot de passe associé à votre compte sur { $forum }. Si vous n'êtes pas à l'origine de cette demande, aucune action n'est requise. Ce lien expirera dans { $minutes } minutes.\nreset-password-mail-button = Réinitialiser le mot de passe\n\n## Register\n\nregister-title = Créer un compte\nname-label = Nom d'utilisateur\nregister-submit = Créer un compte\nregister-login-prompt = Vous avez déjà un compte ?\nregister-login-link = Se connecter\n\n## Confirm Password\n\nconfirm-password-title = Confirmez votre mot de passe\nconfirm-password-introduction = Veuillez confirmer votre mot de passe avant de continuer.\nconfirm-password-submit = Confirmer\n\n## Email Verification\n\nemail-verification-sent-message = Nous avons envoyé un courriel de confirmation à { $email }. S'il n'arrive pas dans les prochaines minutes, veuillez vérifier vos courriers indésirables.\nemail-verification-resend-button = Renvoyer\nemail-verification-required-message = Vous devez vérifier votre adresse de courriel.\nemail-verification-success-message = Merci d'avoir vérifié votre adresse de courriel !\nemail-verification-mail-subject = Vérifier l'adresse de courriel\nemail-verification-mail-body = Veuillez cliquer sur le bouton ci-dessous pour vérifier votre adresse de courriel. Si vous n'avez pas de compte sur { $forum }, aucune autre action n'est requise.\nemail-verification-mail-button = Vérifier l'adresse de courriel\n"
  },
  {
    "path": "lang/fr/cp.ftl",
    "content": "### Localization for Waterhole Control Panel\n\ntitle = Panneau de contrôle\n\n## Dashboard\n\ndashboard-title = Tableau de bord\n\nconfigure-mail-message = Vous devez configurer un pilote de messagerie pour que Waterhole puisse envoyer des courriels de vérification et des notifications.\ndebug-mode-on-message = Le mode débogage est activé. Des valeurs de configuration sensibles peuvent être exposées.\n\ngetting-started-title = Démarrer avec Waterhole\ngetting-started-strategy-title = Lire la documentation\ngetting-started-strategy-description = Apprendre à bâtir une communauté avec Waterhole.\ngetting-started-structure-title = Mettre en place votre structure\ngetting-started-structure-description = Configurer les canaux et les pages qui constituent le squelette de votre communauté.\ngetting-started-groups-title = Définir les groupes d'utilisateurs\ngetting-started-groups-description = Créer des groupes pour les modérateurs, les membres de l'équipe et les super utilisateurs.\ngetting-started-design-title = Rejoindre la communauté de Waterhole\ngetting-started-design-description = Poser des questions, partager des conseils et apprendre à exploiter au mieux votre communauté.\n\ndashboard-users-title = Utilisateurs\ndashboard-posts-title = Messages\ndashboard-comments-title = Commentaires\n\nperiod-today = Aujourd'hui\nperiod-last-7-days = Ces 7 derniers jours\nperiod-last-4-weeks = Ces 4 dernières semaines\nperiod-last-3-months = Ces 3 derniers mois\nperiod-last-12-months = Ces 12 derniers mois\nperiod-this-month = Ce mois\nperiod-this-quarter = Ce trimestre\nperiod-this-year = Cette année\nperiod-all-time = Depuis toujours\nperiod-current-heading = Période actuelle\npreiod-previous-heading = Période antérieure\n\n## Structure\n\nstructure-title = Structure\n\nstructure-channel-label = Canal\nstructure-page-label = Page\nstructure-link-label = Lien\nstructure-heading-label = Rubrique\nstructure-visibility-public-label = Public\nstructure-visibility-members-label = Membres\n\nstructure-navigation-title = Navigation\nstructure-navigation-description = Déplacez les éléments ici pour les afficher dans le menu de navigation.\n\nstructure-unlisted-title = Non répertorié\nstructure-unlisted-description = Déplacez les éléments ici pour les masquer du menu de navigation.\n\ndelete-structure-confirm-message = Êtes-vous sûr de vouloir supprimer ce nœud ?\n\n## Structure - Heading\n\nedit-heading-title = Modifier la rubrique\ncreate-heading-title = Créer une rubrique\nheading-name-label = Nom\n\n## Structure - Link\n\nedit-link-title = Modifier le lien\ncreate-link-title = Créer un lien\nlink-details-title = Informations\nlink-name-label = Nom\nlink-url-label = URL\nlink-permissions-title = Permissions\n\n## Structure - Page\n\nedit-page-title = Modifier la page\ncreate-page-title = Créer une page\npage-details-title = Informations\npage-name-label = Nom\npage-slug-label = Identifiant texte unique\npage-slug-url-label = Cette page sera accessible sur :\npage-body-label = Corps\npage-permissions-title = Permissions\n\n## Structure - Channel\n\nedit-channel-title = Modifier le canal\ncreate-channel-title = Créer un canal\nchannel-details-title = Informations\nchannel-name-label = Nom\nchannel-slug-label = Identifiant texte unique\nchannel-slug-url-label = Ce canal sera accessible sur :\nchannel-description-label = Description\nchannel-description-description = Une courte description de ce à quoi sert ce canal.\nchannel-options-title = Options\nchannel-visibility-label = Visibilité\nchannel-ignore-label = Ignoré par défaut\nchannel-ignore-description = Masque les messages de ce canal dans le fil d'actualité de tous les utilisateurs, à moins qu'ils ne le suivent explicitement.\nchannel-layout-title = Disposition\nchannel-layout-label = Disposition\nchannel-layout-show-author-label = Afficher l'auteur du message\nchannel-layout-show-excerpt-label = Afficher l'extrait du message\nchannel-filters-label = Filtres\nchannel-custom-filters-label = Utiliser des filtres personnalisés sur ce canal\nchannel-custom-filters-description = Remplace les filtres généraux de ce canal.\nchannel-permissions-title = Permissions\nchannel-approval-label = Approbation\nchannel-approval-moderators-exempt = Les modérateurs sont exemptés d'approbation.\nchannel-require-approval-posts-label = Exiger l'approbation des messages\nchannel-require-approval-comments-label = Exiger l'approbation des commentaires\nchannel-features-title = Fonctionnalités\nchannel-reactions-label = Réactions\nchannel-reactions-posts-label = Messages\nchannel-reactions-comments-label = Commentaires\nreaction-set-picker-default = Par défaut ({ $name })\nreaction-set-picker-none = Aucun\nchannel-taxonomies-label = Taxonomies\nchannel-answers-label = Réponses\nchannel-enable-answers-label = Activer les réponses sur ce canal\nchannel-enable-answers-description = Permet aux auteurs de messages de marquer un commentaire comme étant la réponse.\nchannel-posting-title = Publication\nchannel-instructions-label = Instructions de publication\nchannel-instructions-description = Affiche les instructions de publication aux utilisateurs lorsqu'ils rédigent des messages dans ce canal.\nchannel-similar-posts-title = Messages similaires\nchannel-show-similar-posts-label = Afficher les messages similaires de ce canal en fonction du titre\n\ndelete-channel-title = Supprimer le canal :\ndelete-channel-posts-label = Supprimer { $count } { $count ->\n    [one] message\n    *[other] messages\n}\nmove-to-channel-posts-label = Déplacer { $count } { $count ->\n    [one] message\n    *[other] messages\n} vers un autre canal\n\n## Groups\n\ngroups-title = Groupes\ncreate-group-button = Créer un groupe\ngroup-user-count = { $count } { $count ->\n    [one] utilisateur\n    *[other] utilisateurs\n}\n\nedit-group-title = Modifier le groupe\ncreate-group-title = Créer un groupe\ngroup-details-title = Informations\ngroup-name-label = Nom\ngroup-appearance-label = Apparence\ngroup-show-as-badge-label = Afficher ce groupe comme badge pour l'utilisateur\ngroup-color-label = Couleur\ngroup-icon-label = Icône\ngroup-rules-title = Participation\ngroup-auto-assign-label = Attribuer automatiquement ce groupe aux nouveaux membres\ngroup-rules-requires-approval-label = Exiger l'approbation du contenu des nouveaux membres\ngroup-rules-remove-after-approval-label = Retirer du groupe après approbation\ngroup-permissions-title = Permissions\ngroup-global-permissions-title = Autorisations globales\ngroup-structure-permissions-title = Autorisations de structure\ngroup-permission-suspend-users-label = Autoriser la suspension des utilisateurs\n\ndelete-group-confirm-message = Êtes-vous sûr de vouloir supprimer ce groupe ?\n\n## Users\n\nusers-title = Utilisateurs\nusers-filter-placeholder = Filtrer les utilisateurs\nusers-filter-group-description = Filtrer par groupe\ncreate-user-button = Créer un utilisateur\n\nusers-name-column = Nom\nusers-email-column = Adresse de courriel\nusers-groups-column = Groupes\nusers-created-at-column = Création\nusers-last-seen-at-column = Dernière visite\nusers-empty-message = Aucun résultat n'a été trouvé\n\nedit-user-title = Modifier l'utilisateur\ncreate-user-title = Créer un utilisateur\nuser-account-title = Compte\nuser-name-label = Nom\nuser-email-label = Adresse de courriel\nuser-password-label = Mot de passe\nuser-set-password-label = Définir un nouveau mot de passe\nuser-groups-label = Groupes\nuser-profile-title = Profil\nuser-created-message = L'utilisateur a été créé.\nuser-saved-message = L'utilisateur a été enregistré.\n\ndelete-user-title = Supprimer { $count ->\n    [one] l'utilisateur :\n    *[other] { $count } utilisateurs\n}\nkeep-user-content-label = Conserver le contenu et le marquer comme anonyme\ndelete-user-content-label = Supprimer définitivement le contenu\ndelete-user-success-message = L'utilisateur a été supprimé.\n\n## Reactions\n\nreactions-title = Réactions\nreaction-sets-title = Ensembles de réactions\ncreate-reaction-set-button = Créer un ensemble de réactions\nedit-reaction-set-title = Modifier l'ensemble de réactions\ncreate-reaction-set-title = Créer un ensemble de réactions\nreaction-set-details-title = Détails\nreaction-set-name-label = Nom\nreaction-set-usage-label = Utilisation\nreaction-set-default-posts = Utiliser par défaut pour les messages\nreaction-set-default-comments = Utiliser par défaut pour les commentaires\ndelete-reaction-set-confirm-message = Êtes-vous sûr de vouloir supprimer cet ensemble de réactions ?\nreaction-set-saved-message = L'ensemble de réactions a été enregistré.\n\nreaction-types-title = Types de réactions\nreaction-types-empty-message = Aucun type de réaction\nreaction-types-add-button = Ajouter\nedit-reaction-type-title = Modifier le type de réaction\ncreate-reaction-type-title = Créer un type de réaction\nreaction-type-name-label = Nom\nreaction-type-score-label = Score\nreaction-type-score-description = Le nombre de points que vaut cette réaction.\ndelete-reaction-type-confirm-message = Êtes-vous sûr de vouloir supprimer ce type de réaction ?\nreaction-type-saved-message = Le type de réaction a été enregistré.\n\n## Taxonomies\n\ntaxonomies-title = Taxonomies\ncreate-taxonomy-button = Créer une taxonomie\ncreate-taxonomy-title = Créer une taxonomie\nedit-taxonomy-title = Modifier la taxonomie\ntaxonomy-details-title = Informations\ntaxonomy-permissions-title = Permissions\ntaxonomy-tags-title = Étiquettes\ntaxonomy-name-label = Nom\ntaxonomy-options-title = Options\ntaxonomy-required-label = Exiger la sélection d'une étiquette lors de la création d'un message\ntaxonomy-allow-multiple-label = Permettre la sélection de plusieurs étiquettes\ntaxonomy-saved-message = La taxonomie a été enregistrée.\ndelete-taxonomy-confirm-message = Êtes-vous sûr de vouloir supprimer cette taxonomie ?\n\ncreate-tag-title = Créer une étiquette\nedit-tag-title = Modifier l'étiquette\ntag-name-label = Nom\ntag-saved-message = L'étiquette a été enregistrée.\ndelete-tag-confirm-message = Êtes-vous sûr de vouloir supprimer cette étiquette ?\n\n## Licensing\n\nlicense-error-message = Votre licence n'a pas pu être validée en raison d'une erreur de communication avec l'API de Waterhole. ({ $status })\nlicense-invalid-message = Veuillez acheter ou saisir une clé de licence valide pour ce site afin de vous conformer à l'accord de licence.\nlicense-expired-message = Vous n'êtes pas autorisé à utiliser cette version de Waterhole. Veuillez rétrograder votre version ou renouveler votre licence.\nlicense-suspended-message = Votre licence Waterhole a été suspendue. Veuillez nous contacter pour plus d'informations.\n\ntrial-badge = Version d'essai\nlicensed-badge = Licence valide\nunlicensed-badge = Sans licence\n"
  },
  {
    "path": "lang/fr/forum.ftl",
    "content": "### Forum\n\nfeed-link = Flux\n\n## Header\n\nlog-in = Se connecter\nregister = S'inscrire\n\n## Search\n\nsearch-placeholder = Rechercher dans toutes les discussions\nsearch-results-title = Résultats de la recherche pour \"{ $query }\"\nsearch-button = Rechercher\n\nsearch-filter-button = Filtre\n\nsearch-showing-results-title = Affichage de { $total } { $total ->\n    [one] résultat\n    *[other] résultats\n}\n\nsearch-showing-results-non-exhaustive-title = Affichage de { $total }+ { $total ->\n    [one] résultat\n    *[other] résultats\n}\n\nsearch-sort-relevance = Trier par pertinence\nsearch-sort-latest = Trier par ordre chronologique\nsearch-sort-top = Trier par popularité\n\nsearch-empty-message = Aucun résultat n'a été trouvé\nsearch-keywords-too-short-message = Vos mots-clés sont trop courts, essayez une requête plus longue.\n\n## Posts\n\npost-activity-replied = a répondu\npost-activity-posted = a publié\n\npost-new-badge = Nouveau\npost-new-badge-tooltip = Nouveau message\npost-locked-badge = Verrouillé\npost-answered-badge = Répondu\npost-trash-badge = Corbeille\n\npost-removed-message = Message supprimé\n\npost-answered-by = Répondu par\npost-view-answer-link = Voir la réponse\n\npost-unread-comments-badge-tooltip = { $count } { $count ->\n    [one] commentaire non lu\n    *[other] commentaires non lus\n}\n\npost-comments-heading = { $count } { $count ->\n    [one] commentaire\n    *[other] commentaires\n}\n\nmark-as-read-instruction = Cliquer pour marquer comme lu\n\npost-comments-link = { $count } { $count ->\n    [one] commentaire\n    *[other] commentaires\n}\n\nadd-reaction-button = Ajouter une réaction\n\nmove-post-title = Déplacer { $count ->\n    [one] le message :\n    *[other] { $count } messages\n}\n\nmove-to-channel-button = Déplacer vers le canal\nmove-to-channel-confirm-button = Déplacer\n\nmark-as-read-button = Marquer comme lu\n\ncreate-post-button = Créer un message\ncreate-post-title = Nouveau message\npost-channel-label = Canal\npost-submit-button = Message\nedit-post-title = Modifier le message\nedit-post-link = Modifier le message\npost-title-label = Titre\nsimilar-posts-label = Voir ces messages similaires :\npost-body-label = Corps\n\nchannel-picker-placeholder = Sélectionner un canal\n\ndelete-post-confirm-message = Êtes-vous sûr de vouloir supprimer ce message ?\ndelete-post-success-message = Le message a été supprimé.\n\noriginal-post-link = Message original\n\npin-to-top-button = Épingler en haut\nunpin-button = Ne plus épingler\n\npost-comment-button = Commenter\n\nmove-to-trash-button = Déplacer vers la corbeille\nrestore-button = Restaurer\ndelete-forever-button = Supprimer définitivement\n\n## Comments\n\ncomments-unread-heading = Non lu\ncomments-unread-link = Non lu\n\ncreate-comment-title = Écrire un commentaire\nedit-comment-title = Modifier le commentaire\ncomment-number-title = Commentaire #{ $number }\n\ncomment-in-reply-to-link = En réponse à\ncomment-show-replies-button = Afficher { $count } { $count ->\n    [one] la réponse\n    *[other] les réponses\n}\ncomment-reply-button = Répondre\nmark-as-answer-button = Marquer comme réponse\nunmark-as-answer-button = Ne plus marquer comme réponse\ncomment-answer-badge = Réponse\n\ncomments-locked-message = Les commentaires sont verrouillés.\nlock-comments-button = Verrouiller les commentaires\nunlock-comments-button = Déverrouiller les commentaires\n\ncomposer-placeholder = Écrire un commentaire…\ncomposer-reply-to-placeholder = Répondre à { $userName }…\ncomposer-replying-to-label = En réponse à\ncomposer-clear-reply-button = Effacer\ncomposer-submit = Publier\n\ndelete-comment-confirm-message = Êtes-vous sûr de vouloir supprimer ce commentaire ?\n\ncomment-removed-message = Commentaire supprimé\ncomment-removed-tooltip = { $user } a supprimé { $timestamp }\n\n## Moderation\n\nremove-button = Retirer\nremoved-by-label = Retiré par\n\nremoval-reason-label = Raison du retrait\nremoval-reason-unspecified-label = Non précisée\nremoval-message-label = Message à l'auteur\nreport-button = Signaler\nreport-confirm-button = Envoyer le signalement\nreport-note-placeholder = Ajouter une note (facultatif)\nreport-system-user = Système\nuser-actions-label = Actions utilisateur\nuser-actions-suspend-label = Suspendre { $user }\nuser-actions-suspend-days = Jours\nuser-actions-suspend-weeks = Semaines\nuser-actions-suspend-indefinitely = Indéfiniment\n\napprove-button = Approuver\nflag-dismiss-button = Ignorer\n\nreport-reason-off-topic-label = Hors sujet\nreport-reason-off-topic-description = Ce contenu n'est pas pertinent pour la discussion en cours.\n\nreport-reason-inappropriate-label = Inapproprié\nreport-reason-inappropriate-description = Ce contenu est offensant, abusif ou enfreint les règles de la communauté.\n\nreport-reason-spam-label = Spam\nreport-reason-spam-description = Ce contenu est une publicité ou du vandalisme.\n\nreport-reason-other-label = Autre\nreport-reason-other-description = Ce contenu nécessite une attention pour une autre raison.\n\nreport-reason-approval-label = En attente d'approbation\n\nmoderation-title = Modération\nmoderation-empty-message = Aucun signalement en attente\nmoderation-finished-message = Tout est à jour.\n\npending-approval-title = En attente d'approbation\n\n## Misc\n\nquote-button = Citer\n\nattribution-timestamp-created-label = Publié\nattribution-timestamp-edited-label = Modifié\n\n## Filters\n\nfilter-alphabetical = Ordre alphabétique\nfilter-following = Plus suivis\nfilter-ignoring = Plus ignorés\nfilter-newest = Plus récents\nfilter-latest = Plus actifs\nfilter-oldest = Plus anciens\nfilter-top = Plus populaires\nfilter-top-all-time = Depuis toujours\nfilter-top-year = Année\nfilter-top-quarter = Trimestre\nfilter-top-month = Mois\nfilter-top-week = Semaine\nfilter-top-day = Jour\nfilter-trending = Plus tendances\nfilter-trash = Corbeille\n\n## Followables\n\nfollow-button = Suivre\nfollow-button-following = Abonnés\nfollow-button-ignored = Ignorés\n\nignore-button = Ignorer\nunfollow-button = Ne plus suivre\nunignore-button = Ne plus ignorer\n\nchannel-follow-description = Recevez une notification lorsque de nouveaux messages sont publiés dans ce canal.\npost-follow-description = Recevez une notification lorsque de nouveaux commentaires sont publiés dans ce message.\n\npost-following-badge = Abonnés\npost-ignored-badge = Ignorés\n\n## Index\n\nmenu-button = Menu\nnavigation-title = Navigation du forum\n\npost-feed-new-activity-button = Nouvelle activité\npost-feed-new-activity-heading = Nouvelle activité\npost-feed-empty-message = Il n'y a aucun message\npost-feed-controls-layout-heading = Afficher comme\n"
  },
  {
    "path": "lang/fr/install.ftl",
    "content": "### Installation\n\n## Groups\n\ngroup-guest = Invité\ngroup-member = Membre\ngroup-admin = Administrateur\ngroup-moderator = Modérateur\ngroup-quarantine = Quarantaine\n\n## Reactions\n\nreaction-set-emoji = Émoji\nreaction-type-like = J'aime\nreaction-type-love = Cœur\nreaction-type-laugh = Rire\nreaction-type-wow = Waouh\nreaction-type-sad = Triste\nreaction-type-angry = En colère\n\nreaction-set-votes = Votes\nreaction-type-upvote = Voter pour\n\n## Structure\n\nannouncements-name = Annonces\nannouncements-description = Actualités et autres annonces de l'équipe.\n\nintroductions-name = Présentations\nintroductions-description = Nouveau dans la communauté ? Présentez-vous !\n\nsupport-name = Assistance\nsupport-description = Obtenez de l'aide pour configurer, utiliser et personnaliser notre logiciel.\n\nideas-name = Idées\nideas-description = Vous avez une idée ? Nous voulons l'entendre !\n\nstaff-name = Réservé à l'équipe\nstaff-description = Un canal privé pour les discussions entre membres de l'équipe.\n\nguide-title = Guide communautaire\nguide-body =\n  Bienvenue sur { $forumName } et merci de nous rejoindre ! Nous souhaitons que chacun puisse profiter au maximum de cette communauté, et c'est pourquoi nous vous demandons de bien vouloir lire et respecter les lignes directrices suivantes.\n\n  - **Soyez courtois.** Il s'agit d'un lieu de partage de connaissances et d'intérêts par le biais de conversations. Soyez aimable, patient et respectueux envers tout le monde, y compris les personnes extérieures à la communauté de Waterhole.\n\n  - **Pas d'attaque personnelle.** La critique des idées, au moyen d'arguments raisonnés, est une partie importante de ce que nous faisons ici. Il n'est cependant pas acceptable que cela dégénère en attaques personnelles. Le harcèlement et les autres comportements discriminatoires ne sont pas tolérés.\n\n  - **Présumez de la bonne foi.** En cas de désaccord, essayez d'en comprendre les raisons tout en présumant que leurs intentions sont toujours bonnes. N'oubliez pas que les gens ont des points de vue différents sur de nombreux sujets, ce qui est parfaitement normal.\n"
  },
  {
    "path": "lang/fr/notifications.ftl",
    "content": "### Notifications\n\ntitle = Notifications\nmark-all-as-read-button = Marquer tout comme lu\npreferences-button = Préférences de notification\nempty-message = Aucune notification\n\n## Unsubscribe\n\nunsubscribe-link = Se désabonner de ces notifications\nunsubscribe-success-message = Vous êtes à présent désabonné de ces notifications.\nmanage-notification-preferences-link = Gérer les préférences de notification\n\n## Mention\n\nmention-description = Mentions et réponses à mes commentaires\nmention-title = Mentionné dans { $post }\nmention-reason = Vous recevez cette notification parce que vous avez été mentionné.\nmention-unsubscribe = Ne plus recevoir de notification lorsque je suis mentionné\n\n## Content Approved\n\npost-approved-title = Message approuvé : { $post }\ncomment-approved-title = Commentaire approuvé dans { $post }\n\n## New Comment\n\nnew-comment-description = Nouveaux commentaires dans un message suivi\nnew-comment-title = Nouveau commentaire dans { $post }\nnew-comment-reason = Vous recevez cette notification parce que vous êtes abonné à ce message.\nnew-comment-unsubscribe = Se désabonner de ce message\n\n## New Post\n\nnew-post-description = Nouveaux messages dans un canal suivi\nnew-post-title = Nouveau message dans { $channel } : { $post }\nnew-post-reason = Vous recevez cette notification parce que vous êtes abonné à ce canal.\nnew-post-unsubscribe = Se désabonner de ce canal\n\n## New Flag\n\nnew-flag-description = Signalements dans les canaux que je modère\nflagged-post-title = Message signalé : { $post }\nflagged-comment-title = Commentaire signalé dans { $post }\nnew-flag-reason = Vous recevez ceci parce que vous modérez un canal.\nnew-flag-unsubscribe = Se désabonner des notifications de signalement\n\n## Content Removed\n\npost-removed-title = Message supprimé : { $post }\ncomment-removed-title = Commentaire supprimé dans { $post }\n\n## Common\n\nview-post-button = Voir le message\nview-comment-button = Voir le commentaire\n"
  },
  {
    "path": "lang/fr/passwords.php",
    "content": "<?php\n\n// https://github.com/laravel/framework/blob/master/src/Illuminate/Translation/lang/en/passwords.php\n\nreturn [\n    'reset' => 'Votre mot de passe a été réinitialisé !',\n    'sent' =>\n        'Nous vous avons envoyé le lien de réinitialisation de votre mot de passe par courriel !',\n    'throttled' => 'Veuillez patienter avant de réessayer.',\n    'token' => \"Ce jeton de réinitialisation du mot de passe n'est pas valide.\",\n    'user' => \"Nous ne trouvons pas d'utilisateur associé à cette adresse de courriel.\",\n];\n"
  },
  {
    "path": "lang/fr/system.ftl",
    "content": "## Accessibility\n\nskip-to-main-content-link = Passer au contenu principal\n\n## Errors\n\nfatal-error-heading = Une erreur s'est produite\ntry-again-button = Réessayer\n\nfatal-error-message = Une erreur s'est produite ! Veuillez recharger la page puis réessayer\ntoo-many-requests-message = Vous allez un peu trop vite ! Faites une pause et réessayez dans un instant.\nforbidden-message = Vous n'avez pas la permission de faire cela.\nsession-expired-message = Votre session a expiré. Veuillez recharger la page et réessayer.\nvalidation-errors-message = Les erreurs suivantes ont été détectées :\n\n## Generic Buttons & Links\n\nsave-changes-button = Enregistrer les modifications\ncreate-button = Créer\ncancel-button = Annuler\nchange-button = Modifier\ncontinue-button = Continuer\nactions-button = Actions\nlearn-more-link = En savoir plus\ndelete-button = Supprimer\nedit-link = Modifier\ncopy-link-button = Copier le lien\ncontrols-button = Contrôles\nmore-button = Plus\nloading = Chargement\nshow-more-button = Afficher plus\n\n## Post Feed Layouts\n\nlayout-list = Liste\nlayout-cards = Cartes\n\n## Actions\n\nconfirm-action-title = Confirmer l'action\ndelete-confirm-button = Supprimer\nlink-copied-message = Lien copié dans le presse-papiers.\n\n## Users\n\ndeleted-user = Utilisateur supprimé\nuser-list-overflow = { $count } autres\n\n## Pagination\n\npagination-first-link = Premier\npagination-previous-link = Précédent\npagination-next-link = Suivant\npagination-last-link = Dernier\nload-more-button = Charger plus\npage-number-prefix = Page\npage-number-heading = Page { $number }\n\n## Theme Switcher\n\ntheme-button = Thème\ntheme-light = Clair\ntheme-dark = Sombre\ntheme-automatic = Automatique\n\n## Text Editor\n\ntext-editor-heading = Titre\ntext-editor-bold = Gras\ntext-editor-italic = Italique\ntext-editor-quote = Citer\ntext-editor-code = Code\ntext-editor-link = Lien\ntext-editor-bulleted-list = Liste à puces\ntext-editor-numbered-list = Liste numérotée\ntext-editor-mention = Mentionner un utilisateur\ntext-editor-emoji = Insérer un émoji\ntext-editor-attachment = Joindre des fichiers\ntext-editor-preview = Prévisualiser\n\n## Icon Picker\n\nicon-field-label = Icône\nicon-picker-change-button = Modifier\nicon-picker-none-option = Aucun\nicon-picker-emoji-option = Émoji\nicon-picker-emoji-description = Saisissez un seul caractère émoji.\nicon-picker-svg-option = Icône SVG\nicon-picker-svg-description = Saisissez le nom d'une icône appartenant à l'un des ensembles installés suivants : { $sets }.\nicon-picker-svg-search-link = Rechercher des icônes\nicon-picker-image-option = Image\n\n## Abilities\n\nability-view = Voir\nability-comment = Commenter\nability-post = Publier\nability-moderate = Modérer\nability-assign-tags = Étiqueter\n\n## Used in the Waterhole\\compact_number() function\n\ncompact-number-1000 = 0.0 k\ncompact-number-10000 = 00 k\ncompact-number-100000 = 000 k\ncompact-number-1000000 = 0.0 M\ncompact-number-10000000 = 00 M\ncompact-number-100000000 = 000 M\ncompact-number-1000000000 = 0.0 G\n"
  },
  {
    "path": "lang/fr/user.ftl",
    "content": "### User\n\n## Account Settings\n\naccount-settings-title = Paramètres du compte\n\ndelete-account-button = Supprimer votre compte\ndelete-account-confirmation-title = Êtes-vous sûr de vouloir supprimer votre compte ?\ndelete-account-confirmation-description = Les données de votre compte seront supprimées. Vos contributions seront conservées mais marquées comme anonymes. Cette action est irréversible.\ndelete-account-success-message = Votre compte a été supprimé.\n\n## Notification Preferences\n\nnotification-preferences-title = Préférences de notification\nnotifications-label = Notifications\nnotification-channel-web = Web\nnotification-channel-email = Courriel\nnotifications-following-label = Abonnements\nfollow-on-comment-label = Suivre automatiquement les messages que je commente\nnotification-preferences-saved-message = Les préférences de notification ont été enregistrées.\n\n## Edit Profile\n\nedit-profile-title = Modifier le profil\nprofile-saved-message = le profil a été enregistré.\n\navatar-label = Avatar\nremove-avatar-label = Supprimer l'avatar\nheadline-label = Intitulé\nheadline-description = Décrivez-vous en quelques mots. Cette description sera affichée à côté de votre nom.\nbio-label = Biographie\nbio-description = Donnez plus d'informations sur vous. Ces informations seront affichées sur votre profil.\nlocation-label = Localisation\nwebsite-label = Site internet\nprivacy-title = Confidentialité\nshow-online-label = Afficher la date de ma dernière connexion\n\n## Comments\n\nuser-comments-title = Commentaires de { $userName }\ncomments-empty-message = Aucun commentaire\n\n## Posts\n\nuser-posts-title = Messages de { $userName }\nposts-empty-message = Aucun message\n\n## User Menu\n\nprofile-link = Profil\npreferences-link = Préférences\nadministration-link = Administration\nlog-out-link = Se déconnecter\n\n## Profile\n\nuser-joined-text = Inscrit le\nuser-last-seen-text = Dernière visite le\nonline-label = En ligne\n\n## Sidebar\n\nposts-link = Messages\ncomments-link = Commentaires\npreferences-heading = Préférences\naccount-settings-link = Compte\nedit-profile-link = Profil\nnotification-preferences-link = Notifications\n\n## Admin\n\nsuspend-button = Suspendre\nedit-suspension-button = Modifier la suspension\nsuspend-user-title = Suspendre\nnot-suspended-label = Non suspendu\nsuspended-indefinitely-label = Suspendu indéfiniment\nsuspended-until-label = Suspendu jusqu'à…\nsuspended-badge = Suspendu\nsuspended-message = Votre compte a été suspendu.\n\ncopy-impersonation-url-button = Copier l'URL d'usurpation d'identité\nimpersonation-url-copied-message = L'URL d'usurpation d'identité a été copiée, vous pouvez l'ouvrir dans une nouvelle fenêtre privée.\n"
  },
  {
    "path": "lang/fr/validation.php",
    "content": "<?php\n\n// https://github.com/laravel/framework/blob/master/src/Illuminate/Translation/lang/en/validation.php\n\nreturn [\n    'accepted' => 'Le :attribute doit être accepté.',\n    'accepted_if' => 'Le :attribute doit être accepté lorsque :other est :value.',\n    'active_url' => \"Le :attribute n'est pas une adresse URL valide.\",\n    'after' => 'Le :attribute doit être une date postérieure au :date.',\n    'after_or_equal' => 'Le :attribute doit être une date postérieure ou égale au :date.',\n    'alpha' => 'Le :attribute ne doit contenir que des lettres.',\n    'alpha_dash' =>\n        'Le :attribute ne doit contenir que des lettres, des chiffres, des tirets et des tirets bas.',\n    'alpha_num' => 'Le :attribute ne doit contenir que des lettres et des chiffres.',\n    'array' => 'Le :attribute doit être un tableau.',\n    'ascii' =>\n        \"Le :attribute ne doit contenir que des caractères alphanumériques et des symboles d'un seul octet.\",\n    'before' => 'Le :attribute doit être une date antérieure au :date.',\n    'before_or_equal' => 'Le :attribute doit être une date antérieure ou égale au :date.',\n    'between' => [\n        'array' => 'Le tableau :attribute doit contenir entre :min et :max éléments.',\n        'file' =>\n            'La taille du fichier de :attribute doit être comprise entre :min et :max kilo-octets.',\n        'numeric' => 'La valeur de :attribute doit être comprise entre :min et :max.',\n        'string' => 'Le texte :attribute doit contenir entre :min et :max caractèress.',\n    ],\n    'boolean' => 'Le :attribute doit être vrai ou faux.',\n    'confirmed' => 'Le champ de confirmation du :attribute ne correspond pas.',\n    'current_password' => 'Le mot de passe est incorrect.',\n    'date' => \"Le :attribute n'est pas une date valide.\",\n    'date_equals' => 'Le :attribute doit être une date égale à :date.',\n    'date_format' => 'Le :attribute ne correspond pas au format :format.',\n    'decimal' => 'Le :attribut doit avoir :decimal décimales.',\n    'declined' => 'Le :attribute doit être décliné.',\n    'declined_if' => 'Le :attribute doit être décliné lorsque :other est :value.',\n    'different' => 'Le :attribute et le :other doivent être différents.',\n    'digits' => 'Le :attribute doit contenir :digits chiffres.',\n    'digits_between' => 'Le :attribute doit contenir entre :min et :max chiffres.',\n    'dimensions' => \"Les dimensions de l'image du :attribute ne sont pas valides.\",\n    'distinct' => 'Le :attribute a une valeur dupliquée.',\n    'doesnt_end_with' =>\n        \"Le :attribute ne doit pas se terminer par l'un des éléments suivants : :values.\",\n    'doesnt_start_with' =>\n        \"Le :attribute ne doit pas commencer par l'un des éléments suivants : :values.\",\n    'email' => 'Le :attribute doit être une adresse de courriel valide.',\n    'ends_with' => 'Le :attribute doit terminer avec une des valeurs suivantes : :values.',\n    'enum' => \"Le :attribute sélectionné n'est pas valide.\",\n    'exists' => \"Le :attribute sélectionné n'est pas valide.\",\n    'file' => 'Le :attribute doit être un fichier.',\n    'filled' => 'Le :attribute doit contenir une valeur.',\n    'gt' => [\n        'array' => 'Le tableau :attribute doit contenir plus de :value éléments.',\n        'file' => 'La taille du fichier de :attribute doit être supérieure à :value kilo-octets.',\n        'numeric' => 'La valeur de :attribute doit être supérieure à :value.',\n        'string' => 'Le texte :attribute doit contenir plus de :value caractères.',\n    ],\n    'gte' => [\n        'array' => 'Le tableau :attribute doit contenir au moins :value éléments.',\n        'file' =>\n            'La taille du fichier de :attribute doit être supérieure ou égale à :value kilo-octets.',\n        'numeric' => 'La valeur de :attribute doit être supérieure ou égale à :value.',\n        'string' => 'Le texte :attribute doit contenir au moins :value caractères.',\n    ],\n    'image' => 'Le :attribute doit être une image.',\n    'in' => \"Le :attribute sélectionné n'est pas invalide.\",\n    'in_array' => \"Le :attribute n'existe pas dans :other.\",\n    'integer' => 'Le :attribute doit être un nombre entier.',\n    'ip' => 'Le :attribute doit être une adresse IP valide.',\n    'ipv4' => 'Le :attribute doit être une adresse IPv4 valide.',\n    'ipv6' => 'Le :attribute doit être une adresse IPv6 valide.',\n    'json' => 'Le :attribute doit être une chaîne JSON valide.',\n    'lowercase' => 'Le :attribute doit être en minuscules.',\n    'lt' => [\n        'array' => 'Le tableau :attribute doit contenir moins de :value éléments.',\n        'file' => 'La taille du fichier de :attribute doit être inférieure à :value kilo-octets.',\n        'numeric' => 'La valeur de :attribute doit être inférieure à :value.',\n        'string' => 'Le texte :attribute doit contenir moins de :value caractères.',\n    ],\n    'lte' => [\n        'array' => 'Le tableau :attribute doit contenir au plus :value éléments.',\n        'file' =>\n            'La taille du fichier de :attribute doit être inférieure ou égale à :value kilo-octets.',\n        'numeric' => 'La valeur de :attribute doit être inférieure ou égale à :value.',\n        'string' => 'Le texte :attribute doit contenir au plus :value caractères.',\n    ],\n    'mac_address' => 'Le :attribute doit être une adresse MAC valide.',\n    'max' => [\n        'array' => 'Le tableau :attribute ne doit pas contenir plus de :max éléments.',\n        'file' => 'La taille du fichier de :attribute ne doit pas dépasser :max kilo-octets.',\n        'numeric' => 'La valeur de :attribute ne doit pas dépasser :max.',\n        'string' => 'Le texte :attribute ne doit pas contenir plus de :max caractères.',\n    ],\n    'max_digits' => 'Le :attribute ne doit pas comporter plus de chiffres que :max.',\n    'mimes' => 'Le :attribute doit être un fichier de type : :values.',\n    'mimetypes' => 'Le :attribute doit être un fichier de type : :values.',\n    'min' => [\n        'array' => 'Le tableau :attribute doit contenir au moins :min éléments.',\n        'file' => 'La taille du fichier de :attribute doit dépasser :min kilo-octets.',\n        'numeric' => 'La valeur de :attribute doit dépasser :min.',\n        'string' => 'Le texte :attribute doit contenir au moins :min caractères.',\n    ],\n    'min_digits' => 'Le :attribute doit contenir au moins :min chiffres.',\n    'missing' => 'Le champ du :attribute doit être absent.',\n    'missing_if' => 'Le champ du :attribute doit être absent lorsque :other est :value.',\n    'missing_unless' => 'Le champ du :attribute doit être absent sauf si :other est :value.',\n    'missing_with' => 'Le champ du :attribute doit être absent lorsque :values est présent.',\n    'missing_with_all' => 'Le champ du :attribute doit être absent lorsque :values est présent.',\n    'multiple_of' => \"L'attribut :attribute doit être un multiple de :value.\",\n    'not_in' => \"Le :attribute sélectionné n'est pas valide.\",\n    'not_regex' => \"Le format du :attribute n'est pas valide.\",\n    'numeric' => 'Le :attribute doit être un nombre.',\n    'password' => [\n        'letters' => 'Le :attribute doit contenir au moins une lettre.',\n        'mixed' =>\n            'Le :attribute doit contenir au moins une lettre majuscule et une lettre minuscule.',\n        'numbers' => 'Le :attribute doit contenir au moins un chiffre.',\n        'symbols' => 'Le :attribute doit contenir au moins un symbole.',\n        'uncompromised' =>\n            \"L'attribut :attribute spécifié est apparu dans une fuite de données. Veuillez saisir un autre :attribut.\",\n    ],\n    'present' => 'Le champ du :attribute doit être présent.',\n    'prohibited' => 'Le champ :attribute est prohibé.',\n    'prohibited_if' => 'Le champ :attribute est prohibé lorsque :other est :value.',\n    'prohibited_unless' => 'Le champ :attribute est prohibé sauf si :other figure dans :values.',\n    'prohibits' => 'Le champ :attribute interdit la présence de :other.',\n    'regex' => \"Le format du :attribute n'est pas valide.\",\n    'required' => 'Le champ du :attribute est obligatoire.',\n    'required_array_keys' => 'Le champ du :attribute doit contenir des entrées pour : :values.',\n    'required_if' =>\n        'Le champ du :attribute est obligatoire lorsque la valeur du :other est :value.',\n    'required_if_accepted' => 'Le champ du :attribute est obligatoire lorsque :other est accepté.',\n    'required_unless' => 'Le champ du :attribute est obligatoire sauf si :other est :values.',\n    'required_with' => 'Le champ du :attribute est obligatoire lorsque le :values est présent.',\n    'required_with_all' =>\n        'Le champ du :attribute est obligatoire lorsque le :values sont présents.',\n    'required_without' =>\n        \"Le champ du :attribute est obligatoire lorsque le :values n'est pas présent.\",\n    'required_without_all' =>\n        'Le champ du :attribute est obligatoire lorsque le :values ne sont pas présents.',\n    'same' => 'Le :attribute et le :other doivent être identiques.',\n    'size' => [\n        'array' => 'Le tableau :attribute doit contenir :size éléments.',\n        'file' => 'La taille du fichier de :attribute doit être de :size kilo-octets.',\n        'numeric' => 'La valeur de :attribute doit être de :size.',\n        'string' => 'Le texte :attribute doit contenir :size caractères.',\n    ],\n    'starts_with' => 'Le :attribute doit commencer avec une des valeurs suivantes : :values.',\n    'string' => 'Le :attribute doit être une chaîne de caractères.',\n    'timezone' => 'Le :attribute doit être un fuseau horaire valide.',\n    'unique' => 'Le :attribute est déjà pris.',\n    'uploaded' => 'Le transfert de :attribute a échoué.',\n    'uppercase' => 'Le :attribute doit être en majuscules.',\n    'url' => \"Le format du :attribute n'est pas valide.\",\n    'ulid' => 'TLe :attribute doit être un ULID valide.',\n    'uuid' => 'Le :attribute doit être un UUID valide.',\n];\n"
  },
  {
    "path": "lang/nl/auth.ftl",
    "content": "### Auth\n\n## Laravel strings\n## https://github.com/laravel/framework/blob/master/src/Illuminate/Translation/lang/en/auth.php\n\nfailed = Deze inloggegevens komen niet overeen met onze gegevens.\npassword = Het opgegeven wachtwoord is foutief.\nthrottle = Te veel inlogpogingen. Probeer het opnieuw over { $seconds } seconden.\n\n## Login\n\nlogin-title = Log in op uw account\nlogin-submit = Inloggen\nlogin-register-prompt = Nog geen account?\nlogin-register-link = Aanmelden\ncontinue-with-provider = Doorgaan met { $provider }\n\nemail-label = E-mail\npassword-label = Wachtwoord\nremember-me-label = Onthoud mij\nforgot-password-link = Wachtwoord vergeten?\n\n## Forgot Password\n\nforgot-password-title = Wachtwoord vergeten?\nforgot-password-introduction = Vraag een wachtwoord reset link aan door hieronder je e-mailadres in te voeren.\nforgot-password-submit = Wachtwoord reset link verzenden\n\nreset-password-title = Wachtwoord resetten\nnew-password-label = Nieuw wachtwoord\nconfirm-password-label = Bevestig wachtwoord\nreset-password-submit = Wachtwoord resetten\n\nreset-password-mail-subject = Uw wachtwoord opnieuw instellen\nreset-password-mail-body = We hebben een verzoek ontvangen om het wachtwoord voor uw account op { $forum } te resetten. Als u dit niet was, is er geen verdere actie nodig. Deze link verloopt over { $minutes } minuten.\nreset-password-mail-button = Wachtwoord resetten\n\n## Register\n\nregister-title = Maak een account aan\nname-label = Gebruikersnaam\nregister-submit = Account aanmaken\nregister-login-prompt = U hebt reeds een account?\nregister-login-link = Inloggen\n\n## Confirm Password\n\nconfirm-password-title = Bevestig uw wachtwoord\nconfirm-password-introduction = Bevestig uw wachtwoord voordat u doorgaat.\nconfirm-password-submit = Bevestigen\n\n## Email Verification\n\nemail-verification-sent-message = We hebben een bevestigingsmail gestuurd naar { $email }. Als deze niet snel aankomt, controleer dan uw spam folder.\nemail-verification-resend-button = Opnieuw verzenden\nemail-verification-required-message = U moet uw e-mailadres verifiëren.\nemail-verification-success-message = Bedankt voor het verifiëren van uw e-mail!\nemail-verification-mail-subject = Verifieer e-mailadres\nemail-verification-mail-body = Klik op de onderstaande knop om uw e-mailadres te verifiëren. Als u geen account heeft op { $forum }, is er geen verdere actie nodig.\nemail-verification-mail-button = E-mailadres verifiëren\n"
  },
  {
    "path": "lang/nl/cp.ftl",
    "content": "### Localization for Waterhole Control Panel\n\ntitle = Bedieningspaneel\n\n## Dashboard\n\ndashboard-title = Dashboard\n\nconfigure-mail-message = U moet een mail driver configureren zodat Waterhole verificatie e-mails en meldingen kan versturen.\ndebug-mode-on-message = Debugmodus is AAN. Gevoelige instellingen kunnen worden blootgesteld.\n\ngetting-started-title = Aan de slag met Waterhole\ngetting-started-strategy-title = Lees de Documentatie\ngetting-started-strategy-description = Leer hoe je een succesvolle gemeenschap opbouwt met Waterhole.\ngetting-started-structure-title = Stel uw Structuur in\ngetting-started-structure-description = Configureer de kanalen en pagina's die het skelet van uw gemeenschap vormen.\ngetting-started-groups-title = Definieer Gebruikersgroepen\ngetting-started-groups-description = Stel groepen in voor moderators, personeel en supergebruikers.\ngetting-started-design-title = Sluit je aan bij de Waterhole Gemeenschap\ngetting-started-design-description = Stel vragen, deel tips en leer hoe je het meeste uit je gemeenschap haalt.\n\ndashboard-users-title = Gebruikers\ndashboard-posts-title = Berichten\ndashboard-comments-title = Reacties\n\nperiod-today = Vandaag\nperiod-last-7-days = Voorbije 7 dagen\nperiod-last-4-weeks = Voorbije 4 weken\nperiod-last-3-months = Voorbije 3 maanden\nperiod-last-12-months = Voorbije 12 maanden\nperiod-this-month = Huidige maand\nperiod-this-quarter = Huidig kwartaal\nperiod-this-year = Huidig jaar\nperiod-all-time = Altijd\nperiod-current-heading = Huidige Periode\npreiod-previous-heading = Vorige Periode\n\n## Structure\n\nstructure-title = Structuur\n\nstructure-channel-label = Kanaal\nstructure-page-label = Pagina\nstructure-link-label = Link\nstructure-heading-label = Kop\nstructure-visibility-public-label = Openbaar\nstructure-visibility-members-label = Leden\n\nstructure-navigation-title = Navigatie\nstructure-navigation-description = Verplaats items hierheen om ze in het navigatiemenu te tonen.\n\nstructure-unlisted-title = Niet in lijst\nstructure-unlisted-description = Verplaats items hierheen om ze te verbergen in het navigatiemenu.\n\ndelete-structure-confirm-message = Weet u zeker dat u dit item wilt verwijderen?\n\n## Structure - Heading\n\nedit-heading-title = Bewerk Kop\ncreate-heading-title = Maak een Kop\nheading-name-label = Naam\n\n## Structure - Link\n\nedit-link-title = Bewerk Link\ncreate-link-title = Maak een Link\nlink-details-title = Details\nlink-name-label = Naam\nlink-url-label = URL\nlink-permissions-title = Permissies\n\n## Structure - Page\n\nedit-page-title = Bewerk Pagina\ncreate-page-title = Maak een Pagina\npage-details-title = Details\npage-name-label = Naam\npage-slug-label = Slug\npage-slug-url-label = Deze pagina is toegankelijk op:\npage-body-label = Inhoud\npage-permissions-title = Permissies\n\n## Structure - Channel\n\nedit-channel-title = Bewerk Kanaal\ncreate-channel-title = Kanaal toevoegen\nchannel-details-title = Details\nchannel-name-label = Naam\nchannel-slug-label = Slug\nchannel-slug-url-label = Dit kanaal is toegankelijk op:\nchannel-description-label = Beschrijving\nchannel-description-description = Een korte beschrijving waar dit kanaal voor dient.\nchannel-options-title = Opties\nchannel-visibility-label = Zichtbaarheid\nchannel-ignore-label = Standaard genegeerd\nchannel-ignore-description = Verberg berichten in dit kanaal van de Feed voor alle gebruikers, tenzij ze het expliciet volgen.\nchannel-layout-title = Layout\nchannel-layout-label = Layout\nchannel-layout-show-author-label = Toon auteur bericht\nchannel-layout-show-excerpt-label = Toon berichtfragment\nchannel-filters-label = Filters\nchannel-custom-filters-label = Gebruik aangepaste filters voor dit kanaal\nchannel-custom-filters-description = Overschrijf de globale filters voor dit kanaal.\nchannel-permissions-title = Permissies\nchannel-approval-label = Goedkeuring\nchannel-approval-moderators-exempt = Moderators zijn vrijgesteld van goedkeuring.\nchannel-require-approval-posts-label = Goedkeuring vereisen voor berichten\nchannel-require-approval-comments-label = Goedkeuring vereisen voor reacties\nchannel-features-title = Functies\nchannel-reactions-label = Reacties\nchannel-reactions-posts-label = Berichten\nchannel-reactions-comments-label = Reacties\nreaction-set-picker-default = Standaard ({ $name })\nreaction-set-picker-none = Geen\nchannel-taxonomies-label = Taxonomieën\nchannel-answers-label = Antwoorden\nchannel-enable-answers-label = Sta antwoorden toe op dit kanaal\nchannel-enable-answers-description = Sta auteurs van berichten toe een reactie als antwoord te markeren.\nchannel-posting-title = Berichten toevoegen\nchannel-instructions-label = Instructies voor het toevoegen van berichten\nchannel-instructions-description = Geef instructies die aan gebruikers worden getoond terwijl ze berichten in dit kanaal maken.\nchannel-similar-posts-title = Vergelijkbare Berichten\nchannel-show-similar-posts-label = Toon vergelijkbare berichten uit dit kanaal op basis van de titel\n\ndelete-channel-title = Verwijder Kanaal:\ndelete-channel-posts-label = Verwijder { $count } { $count ->\n    [one] bericht\n    *[other] berichten\n}\nmove-to-channel-posts-label = Verplaats { $count } { $count ->\n    [one] bericht\n    *[other] berichten\n} naar een ander kanaal\n\n## Groups\n\ngroups-title = Groepen\ncreate-group-button = Groep toevoegen\ngroup-user-count = { $count } { $count ->\n    [one] gebruiker\n    *[other] gebruikers\n}\n\nedit-group-title = Bewerk Groep\ncreate-group-title = Groep Toevoegen\ngroup-details-title = Details\ngroup-name-label = Naam\ngroup-appearance-label = Uiterlijk\ngroup-show-as-badge-label = Toon deze groep als een gebruikersbadge\ngroup-color-label = Kleur\ngroup-icon-label = Icoon\ngroup-rules-title = Deelname\ngroup-auto-assign-label = Deze groep automatisch toewijzen aan nieuwe leden\ngroup-rules-requires-approval-label = Goedkeuring vereisen voor inhoud van nieuwe leden\ngroup-rules-remove-after-approval-label = Na goedkeuring uit de groep verwijderen\ngroup-permissions-title = Permissies\ngroup-global-permissions-title = Globale permissies\ngroup-structure-permissions-title = Structuurpermissies\ngroup-permission-suspend-users-label = Toestaan om gebruikers te schorsen\n\ndelete-group-confirm-message = Weet u zeker dat u deze groep wilt verwijderen?\n\n## Users\n\nusers-title = Gebruikers\nusers-filter-placeholder = Filter gebruikers\nusers-filter-group-description = Filter op groep\ncreate-user-button = Gebruiker Toevoegen\n\nusers-name-column = Naam\nusers-email-column = E-mail\nusers-groups-column = Groepen\nusers-created-at-column = Aangemaakt\nusers-last-seen-at-column = Laatst gezien\nusers-empty-message = Geen resultaten gevonden\n\nedit-user-title = Bewerk Gebruiker\ncreate-user-title = Maak een Gebruiker\nuser-account-title = Account\nuser-name-label = Naam\nuser-email-label = E-mail\nuser-password-label = Wachtwoord\nuser-set-password-label = Stel nieuw wachtwoord in\nuser-groups-label = Groepen\nuser-profile-title = Profiel\nuser-created-message = Gebruiker aangemaakt.\nuser-saved-message = Gebruiker opgeslagen.\n\ndelete-user-title = Verwijder { $count ->\n    [one] Gebruiker:\n    *[other] { $count } Gebruikers\n}\nkeep-user-content-label = Inhoud behouden en als anoniem markeren\ndelete-user-content-label = Verwijder inhoud permanent\ndelete-user-success-message = Gebruiker verwijderd.\n\n## Reactions\n\nreactions-title = Reacties\nreaction-sets-title = Reactiesets\ncreate-reaction-set-button = Maak Reactieset\nedit-reaction-set-title = Bewerk Reactieset\ncreate-reaction-set-title = Reactieset Toevoegen\nreaction-set-details-title = Details\nreaction-set-name-label = Naam\nreaction-set-usage-label = Gebruik\nreaction-set-default-posts = Gebruik als standaard voor berichten\nreaction-set-default-comments = Gebruik als standaard voor reacties\ndelete-reaction-set-confirm-message = Weet u zeker dat u deze reactieset wilt verwijderen?\nreaction-set-saved-message = Reactieset opgeslagen.\n\nreaction-types-title = Reactietypes\nreaction-types-empty-message = Geen Reactietypes\nreaction-types-add-button = Toevoegen\nedit-reaction-type-title = Bewerk Reactietype\ncreate-reaction-type-title = Reactietype Toevoegen\nreaction-type-name-label = Naam\nreaction-type-score-label = Score\nreaction-type-score-description = Het aantal punten dat deze reactie waard is.\ndelete-reaction-type-confirm-message = Weet u zeker dat u dit reactietype wilt verwijderen?\nreaction-type-saved-message = Reactietype opgeslagen.\n\n## Taxonomies\n\ntaxonomies-title = Taxonomieën\ncreate-taxonomy-button = Taxonomie Toevoegen\ncreate-taxonomy-title = Taxonomie Toevoegen\nedit-taxonomy-title = Bewerk Taxonomie\ntaxonomy-details-title = Details\ntaxonomy-permissions-title = Permissies\ntaxonomy-tags-title = Tags\ntaxonomy-name-label = Naam\ntaxonomy-options-title = Opties\ntaxonomy-required-label = Vereis een tag bij het aanmaken van een bericht\ntaxonomy-allow-multiple-label = Sta selectie van meerdere tags toe\ntaxonomy-saved-message = Taxonomie opgeslagen.\ndelete-taxonomy-confirm-message = Weet u zeker dat u deze taxonomie wilt verwijderen?\n\ncreate-tag-title = Maak een Tag\nedit-tag-title = Bewerk Tag\ntag-name-label = Naam\ntag-saved-message = Tag opgeslagen.\ndelete-tag-confirm-message = Weet u zeker dat u deze tag wilt verwijderen?\n\n## Licensing\n\nlicense-error-message = Uw licentie kon niet worden gevalideerd, omdat er een fout optrad bij de communicatie met de Waterhole API. ({ $status })\nlicense-invalid-message = Koop of voer een geldige licentiesleutel in voor deze site om te voldoen aan de licentie overeenkomst.\nlicense-expired-message = U bent niet gelicentieerd om deze versie van Waterhole te gebruiken. Downgrade of vernieuw uw licentie.\nlicense-suspended-message = Uw Waterhole licentie is geschorst. Neem contact met ons op voor meer informatie.\n\ntrial-badge = Proefversie\nlicensed-badge = Gelicenseerd\nunlicensed-badge = Ongelicenseerd\n"
  },
  {
    "path": "lang/nl/forum.ftl",
    "content": "### Forum\n\nfeed-link = Nieuwsfeed\n\n## Header\n\nlog-in = Inloggen\nregister = Registreren\n\n## Search\n\nsearch-placeholder = Zoek in alle discussies\nsearch-results-title = Zoekresultaten voor \"{ $query }\"\nsearch-button = Zoeken\n\nsearch-filter-button = Filteren\n\nsearch-showing-results-title = Toont { $total } { $total ->\n    [one] resultaat\n    *[other] resultaten\n}\n\nsearch-showing-results-non-exhaustive-title = Toont { $total }+ { $total ->\n    [one] resultaat\n    *[other] resultaten\n}\n\nsearch-sort-relevance = Sorteer op Relevantie\nsearch-sort-latest = Sorteer op Nieuwste\nsearch-sort-top = Sorteer op Top\n\nsearch-empty-message = Geen Resultaten Gevonden\nsearch-keywords-too-short-message = Uw zoekwoorden zijn te kort - probeer iets langer.\n\n## Posts\n\npost-activity-replied = gereageerd\npost-activity-posted = geplaatst\n\npost-new-badge = Nieuw\npost-new-badge-tooltip = Nieuw bericht\npost-locked-badge = Vergrendeld\npost-answered-badge = Beantwoord\npost-trash-badge = Prullenbak\n\npost-removed-message = Bericht verwijderd\n\npost-answered-by = Beantwoord door\npost-view-answer-link = Bekijk Antwoord\n\npost-unread-comments-badge-tooltip = { $count } { $count ->\n    [one] ongelezen reactie\n    *[other] ongelezen reacties\n}\n\npost-comments-heading = { $count } { $count ->\n    [one] Reactie\n    *[other] Reacties\n}\n\nmark-as-read-instruction = Klik om als gelezen te markeren\n\npost-comments-link = { $count } { $count ->\n    [one] reactie\n    *[other] reacties\n}\n\nadd-reaction-button = Reactie Toevoegen\n\nmove-post-title = Verplaats { $count ->\n    [one] Bericht:\n    *[other] { $count } Berichten\n}\n\nmove-to-channel-button = Verplaats naar Kanaal\nmove-to-channel-confirm-button = Verplaatsen\n\nmark-as-read-button = Markeer als Gelezen\n\ncreate-post-button = Maak een Bericht\ncreate-post-title = Nieuw Bericht\npost-channel-label = Kanaal\npost-submit-button = Plaatsen\nedit-post-title = Bewerk Bericht\nedit-post-link = Bewerk Bericht\npost-title-label = Titel\nsimilar-posts-label = Bekijk deze vergelijkbare berichten:\npost-body-label = Inhoud\n\nchannel-picker-placeholder = Selecteer een Kanaal\n\ndelete-post-confirm-message = Weet u zeker dat u dit bericht wilt verwijderen?\ndelete-post-success-message = Bericht verwijderd.\n\noriginal-post-link = Origineel Bericht\n\npin-to-top-button = Bovenaan Vastpinnen\nunpin-button = Losmaken\n\npost-comment-button = Reageer\n\nmove-to-trash-button = Verplaats naar Prullenbak\nrestore-button = Herstellen\ndelete-forever-button = Voorgoed Verwijderen\n\n## Comments\n\ncomments-unread-heading = Ongelezen\ncomments-unread-link = Ongelezen\n\ncreate-comment-title = Schrijf een Reactie\nedit-comment-title = Bewerk Reactie\ncomment-number-title = Reactie #{ $number }\n\ncomment-in-reply-to-link = Als antwoord op\ncomment-show-replies-button = Toon { $count } { $count ->\n    [one] antwoord\n    *[other] antwoorden\n}\ncomment-reply-button = Antwoord\nmark-as-answer-button = Markeer als Antwoord\nunmark-as-answer-button = Deselecteren als Antwoord\ncomment-answer-badge = Antwoord\n\ncomments-locked-message = Reacties zijn vergrendeld.\nlock-comments-button = Vergrendel Reacties\nunlock-comments-button = Ontgrendel Reacties\n\ncomposer-placeholder = Schrijf een reactie...\ncomposer-reply-to-placeholder = Antwoord aan { $userName }...\ncomposer-replying-to-label = Antwoord aan\ncomposer-clear-reply-button = Wissen\ncomposer-submit = Plaatsen\n\ndelete-comment-confirm-message = Weet u zeker dat u deze reactie wilt verwijderen?\n\ncomment-removed-message = Reactie verwijderd\ncomment-removed-tooltip = { $user } heeft { $timestamp } verwijderd\n\n## Moderation\n\nremove-button = Verwijderen\nremoved-by-label = Verwijderd door\n\nremoval-reason-label = Reden van verwijdering\nremoval-reason-unspecified-label = Niet opgegeven\nremoval-message-label = Bericht aan auteur\nreport-button = Melden\nreport-confirm-button = Melding indienen\nreport-note-placeholder = Voeg een notitie toe (optioneel)\nreport-system-user = Systeem\nuser-actions-label = Gebruikersacties\nuser-actions-suspend-label = Schors { $user }\nuser-actions-suspend-days = Dagen\nuser-actions-suspend-weeks = Weken\nuser-actions-suspend-indefinitely = Onbepaald\n\napprove-button = Goedkeuren\nflag-dismiss-button = Afwijzen\n\nreport-reason-off-topic-label = Buiten onderwerp\nreport-reason-off-topic-description = Deze inhoud is niet relevant voor de huidige discussie.\n\nreport-reason-inappropriate-label = Ongepast\nreport-reason-inappropriate-description = Deze inhoud is beledigend, grof of in strijd met de gemeenschapsrichtlijnen.\n\nreport-reason-spam-label = Spam\nreport-reason-spam-description = Deze inhoud is reclame of vandalisme.\n\nreport-reason-other-label = Overig\nreport-reason-other-description = Deze inhoud vereist om een andere reden aandacht.\n\nreport-reason-approval-label = In afwachting van goedkeuring\n\nmoderation-title = Moderatie\nmoderation-empty-message = Geen meldingen in afwachting\nmoderation-finished-message = Je bent helemaal bij.\n\npending-approval-title = In afwachting van goedkeuring\n\n## Misc\n\nquote-button = Citeren\n\nattribution-timestamp-created-label = Geplaatst\nattribution-timestamp-edited-label = Bewerkt\n\n## Filters\n\nfilter-alphabetical = Alfabetisch\nfilter-following = Volgend\nfilter-ignoring = Negerend\nfilter-newest = Nieuwste\nfilter-latest = Laatste\nfilter-oldest = Oudste\nfilter-top = Top\nfilter-top-all-time = Altijd\nfilter-top-year = Jaar\nfilter-top-quarter = Kwartaal\nfilter-top-month = Maand\nfilter-top-week = Week\nfilter-top-day = Dag\nfilter-trending = Populair\nfilter-trash = Prullenbak\n\n## Followables\n\nfollow-button = Volgen\nfollow-button-following = Volgend\nfollow-button-ignored = Genegeerd\n\nignore-button = Negeren\nunfollow-button = Ontvolgen\nunignore-button = Niet meer negeren\n\nchannel-follow-description = Ontvang een melding wanneer er nieuwe berichten zijn in dit kanaal.\npost-follow-description = Ontvang een melding wanneer er nieuwe reacties zijn op dit bericht.\n\npost-following-badge = Volgend\npost-ignored-badge = Genegeerd\n\n## Index\n\nmenu-button = Menu\nnavigation-title = Forum Navigatie\n\npost-feed-new-activity-button = Nieuwe Activiteit\npost-feed-new-activity-heading = Nieuwe Activiteit\npost-feed-empty-message = Geen Berichten\npost-feed-controls-layout-heading = Weergeven als\n"
  },
  {
    "path": "lang/nl/install.ftl",
    "content": "### Installation\n\n## Groups\n\ngroup-guest = Gast\ngroup-member = Lid\ngroup-admin = Beheerder\ngroup-moderator = Moderator\ngroup-quarantine = Quarantaine\n\n## Reactions\n\nreaction-set-emoji = Emoji\nreaction-type-like = Vind ik leuk\nreaction-type-love = Liefde\nreaction-type-laugh = Lachen\nreaction-type-wow = Wauw\nreaction-type-sad = Verdrietig\nreaction-type-angry = Boos\n\nreaction-set-votes = Stemmen\nreaction-type-upvote = Stem omhoog\n\n## Structure\n\nannouncements-name = Aankondigingen\nannouncements-description = Nieuws en andere updates van het team.\n\nintroductions-name = Introducties\nintroductions-description = Nieuw in de gemeenschap? Stel jezelf voor!\n\nsupport-name = Ondersteuning\nsupport-description = Hulp nodig bij de installatie, het gebruik en het aanpassen van ons product.\n\nideas-name = Ideeën\nideas-description = Heb je een idee? We horen het graag!\n\nstaff-name = Alleen voor Personeel\nstaff-description = Een privékanaal voor het personeel.\n\nguide-title = Gemeenschapsgids\nguide-body =\n  Welkom bij { $forumName }, en bedankt voor je deelname! We willen dat iedereen het meeste uit deze gemeenschap haalt, dus vragen we je om deze richtlijnen te lezen en te volgen.\n\n  - **Wees beschaafd.** Dit is een plek om kennis en interesses te delen via gesprekken. Wees vriendelijk, geduldig en respectvol naar iedereen, inclusief mensen buiten de gemeenschap.\n\n  - **Geen persoonlijke aanvallen.** Het bekritiseren van ideeën, door middel van beredeneerde argumenten, is een belangrijk onderdeel van wat we hier doen. Maar het is niet oké als dat ontaardt in persoonlijke aanvallen. Andere lastig vallen of ander uitsluitend gedrag zijn nooit aanvaardbaar.\n\n  - **Ga uit van goede bedoelingen.** Wanneer er meningsverschillen zijn, probeer dan te begrijpen waarom, altijd uitgaande van de goede bedoelingen van de tegenpartij. Onthoud dat verschillende mensen verschillende meningen hebben, en dat dit oké is.\n"
  },
  {
    "path": "lang/nl/notifications.ftl",
    "content": "### Notifications\n\ntitle = Notificaties\nmark-all-as-read-button = Alles als gelezen markeren\npreferences-button = Notificatievoorkeuren\nempty-message = Geen Notificaties\n\n## Unsubscribe\n\nunsubscribe-link = Afmelden voor deze notificaties\nunsubscribe-success-message = Je bent afgemeld voor deze notificaties.\nmanage-notification-preferences-link = Beheer notificatievoorkeuren\n\n## Mention\n\nmention-description = Vermeldingen en antwoorden op mijn reacties\nmention-title = Vermeld in { $post }\nmention-reason = Je ontvangt dit omdat je geabonneerd bent op notificaties voor dit bericht.\nmention-unsubscribe = Afmelden voor notificaties voor dit bericht\n\n## Content Approved\n\npost-approved-title = Bericht goedgekeurd: { $post }\ncomment-approved-title = Reactie goedgekeurd in { $post }\n\n## New Comment\n\nnew-comment-description = Nieuwe reacties op gevolgde berichten\nnew-comment-title = Nieuwe reactie in { $post }\nnew-comment-reason = Je ontvangt dit omdat je dit bericht volgt.\nnew-comment-unsubscribe = Dit bericht niet meer volgen\n\n## New Post\n\nnew-post-description = Nieuwe berichten in gevolgde kanalen\nnew-post-title = Nieuw bericht in { $channel }: { $post }\nnew-post-reason = Je ontvangt dit omdat je dit kanaal volgt.\nnew-post-unsubscribe = Dit kanaal niet meer volgen\n\n## New Flag\n\nnew-flag-description = Meldingen in kanalen die ik modereer\nflagged-post-title = Bericht gemeld: { $post }\nflagged-comment-title = Reactie gemeld in { $post }\nnew-flag-reason = Je ontvangt dit omdat je een kanaal modereert.\nnew-flag-unsubscribe = Afmelden voor meldingsnotificaties\n\n## Content Removed\n\npost-removed-title = Bericht verwijderd: { $post }\ncomment-removed-title = Reactie verwijderd in { $post }\n\n## Common\n\nview-post-button = Bekijk Bericht\nview-comment-button = Bekijk Reactie\n"
  },
  {
    "path": "lang/nl/passwords.php",
    "content": "<?php\n\nreturn [\n    'reset' => 'Je wachtwoord is gereset!',\n    'sent' => 'We hebben je een link gestuurd om je wachtwoord te resetten!',\n    'throttled' => 'Wacht even voordat je het opnieuw probeert.',\n    'token' => 'Dit token voor wachtwoordreset is ongeldig.',\n    'user' => 'We kunnen geen gebruiker vinden met dat e-mailadres.',\n];\n"
  },
  {
    "path": "lang/nl/system.ftl",
    "content": "## Accessibility\n\nskip-to-main-content-link = Ga naar de hoofdinhoud\n\n## Errors\n\nfatal-error-heading = Er is iets fout gegaan\ntry-again-button = Probeer opnieuw\n\nfatal-error-message = Er is iets fout gegaan! Herlaad de pagina en probeer het opnieuw.\ntoo-many-requests-message = Je gaat een beetje te snel! Neem even pauze en probeer het zo meteen opnieuw.\nforbidden-message = Je hebt geen toestemming om dit te doen.\nsession-expired-message = Je sessie is verlopen. Herlaad de pagina en probeer het opnieuw.\nvalidation-errors-message = De volgende fouten zijn gevonden:\n\n## Generic Buttons & Links\n\nsave-changes-button = Wijzigingen opslaan\ncreate-button = Toevoegen\ncancel-button = Annuleren\nchange-button = Wijzigen\ncontinue-button = Doorgaan\nactions-button = Acties\nlearn-more-link = Meer info\ndelete-button = Verwijderen\nedit-link = Bewerken\ncopy-link-button = Link kopiëren\ncontrols-button = Bediening\nmore-button = Meer\nloading = Laden\nshow-more-button = Toon meer\n\n## Post Feed Layouts\n\nlayout-list = Lijst\nlayout-cards = Kaarten\n\n## Actions\n\nconfirm-action-title = Actie bevestigen\ndelete-confirm-button = Verwijderen\nlink-copied-message = Link gekopieerd naar het klembord.\n\n## Users\n\ndeleted-user = Verwijderde gebruiker\nuser-list-overflow = { $count } anderen\n\n## Pagination\n\npagination-first-link = Eerste\npagination-previous-link = Vorige\npagination-next-link = Volgende\npagination-last-link = Laatste\nload-more-button = Meer\npage-number-prefix = Pagina\npage-number-heading = Pagina { $number }\n\n## Theme Switcher\n\ntheme-button = Thema\ntheme-light = Licht\ntheme-dark = Donker\ntheme-automatic = Automatisch\n\n## Text Editor\n\ntext-editor-heading = Kop\ntext-editor-bold = Vet\ntext-editor-italic = Cursief\ntext-editor-quote = Citaat\ntext-editor-code = Code\ntext-editor-link = Link\ntext-editor-bulleted-list = Lijst met opsommingstekens\ntext-editor-numbered-list = Genummerde lijst\ntext-editor-mention = Vermeld een gebruiker\ntext-editor-emoji = Emoji invoegen\ntext-editor-attachment = Bestanden bijvoegen\ntext-editor-preview = Voorbeeld\n\n## Icon Picker\n\nicon-field-label = Icoon\nicon-picker-change-button = Wijzigen\nicon-picker-none-option = Geen\nicon-picker-emoji-option = Emoji\nicon-picker-emoji-description = Voer één enkel emoji-teken in.\nicon-picker-svg-option = SVG-icoon\nicon-picker-svg-description = Voer de naam in van een icoon uit een van de volgende geïnstalleerde sets: { $sets }.\nicon-picker-svg-search-link = Iconen zoeken\nicon-picker-image-option = Afbeelding\n\n## Abilities\n\nability-view = Bekijken\nability-comment = Reageren\nability-post = Bericht\nability-moderate = Modereren\nability-assign-tags = Tags toewijzen\n\n## Used in the Waterhole\\compact_number() function\n\ncompact-number-1000 = 0.0K\ncompact-number-10000 = 00K\ncompact-number-100000 = 000K\ncompact-number-1000000 = 0.0M\ncompact-number-10000000 = 00M\ncompact-number-100000000 = 000M\ncompact-number-1000000000 = 0.0B\n"
  },
  {
    "path": "lang/nl/user.ftl",
    "content": "### User\n\n## Account Settings\n\naccount-settings-title = Accountinstellingen\n\ndelete-account-button = Verwijder je account\ndelete-account-confirmation-title = Weet je zeker dat je je account wilt verwijderen?\ndelete-account-confirmation-description = Je accountgegevens zullen worden verwijderd. Je bijdragen blijven bewaard maar worden gemarkeerd als anoniem. Dit kan niet ongedaan worden gemaakt.\ndelete-account-success-message = Je account is verwijderd.\n\n## Notification Preferences\n\nnotification-preferences-title = Notificatievoorkeuren\nnotifications-label = Notificaties\nnotification-channel-web = Web\nnotification-channel-email = E-mail\nnotifications-following-label = Volgende\nfollow-on-comment-label = Automatisch berichten volgen waarop ik reageer\nnotification-preferences-saved-message = Notificatievoorkeuren opgeslagen.\n\n## Edit Profile\n\nedit-profile-title = Bewerk Profiel\nprofile-saved-message = Profiel opgeslagen.\n\navatar-label = Avatar\nremove-avatar-label = Avatar verwijderen\nheadline-label = Kop\nheadline-description = Beschrijf jezelf in een paar woorden. Dit wordt naast je naam weergegeven.\nbio-label = Bio\nbio-description = Schrijf meer over jezelf. Dit wordt op je profiel weergegeven.\nlocation-label = Locatie\nwebsite-label = Website\nprivacy-title = Privacy\nshow-online-label = Laat zien wanneer ik voor het laatst online was\n\n## Comments\n\nuser-comments-title = Reacties van { $userName }\ncomments-empty-message = Geen Reacties\n\n## Posts\n\nuser-posts-title = Berichten van { $userName }\nposts-empty-message = Geen Berichten\n\n## User Menu\n\nprofile-link = Profiel\npreferences-link = Voorkeuren\nadministration-link = Beheer\nlog-out-link = Uitloggen\n\n## Profile\n\nuser-joined-text = Lid sinds\nuser-last-seen-text = Laatst gezien\nonline-label = Online\n\n## Sidebar\n\nposts-link = Berichten\ncomments-link = Reacties\npreferences-heading = Voorkeuren\naccount-settings-link = Account\nedit-profile-link = Profiel\nnotification-preferences-link = Notificaties\n\n## Admin\n\nsuspend-button = Schorsen\nedit-suspension-button = Schorsing bewerken\nsuspend-user-title = Schors Gebruiker\nnot-suspended-label = Niet geschorst\nsuspended-indefinitely-label = Onbepaald geschorst\nsuspended-until-label = Geschorst tot...\nsuspended-badge = Geschorst\nsuspended-message = Je account is geschorst.\n\ncopy-impersonation-url-button = Kopieer Impersonatie-URL\nimpersonation-url-copied-message = Impersonatie-URL gekopieerd - open het in een nieuw privévenster.\n"
  },
  {
    "path": "lang/nl/validation.php",
    "content": "<?php\n\nreturn [\n    'accepted' => 'Het :attribute veld moet geaccepteerd worden.',\n    'accepted_if' =>\n        'Het :attribute veld moet geaccepteerd worden wanneer :other gelijk is aan :value.',\n    'active_url' => 'Het :attribute veld moet een geldige URL zijn.',\n    'after' => 'Het :attribute veld moet een datum zijn na :date.',\n    'after_or_equal' => 'Het :attribute veld moet een datum zijn na of gelijk aan :date.',\n    'alpha' => 'Het :attribute veld mag alleen letters bevatten.',\n    'alpha_dash' =>\n        'Het :attribute veld mag alleen letters, cijfers, streepjes en onderstrepingstekens bevatten.',\n    'alpha_num' => 'Het :attribute veld mag alleen letters en cijfers bevatten.',\n    'array' => 'Het :attribute veld moet een array zijn.',\n    'ascii' =>\n        'Het :attribute veld mag alleen enkelbyte alfanumerieke tekens en symbolen bevatten.',\n    'before' => 'Het :attribute veld moet een datum zijn voor :date.',\n    'before_or_equal' => 'Het :attribute veld moet een datum zijn voor of gelijk aan :date.',\n    'between' => [\n        'array' => 'Het :attribute veld moet tussen :min en :max items hebben.',\n        'file' => 'Het :attribute veld moet tussen :min en :max kilobytes zijn.',\n        'numeric' => 'Het :attribute veld moet tussen :min en :max zijn.',\n        'string' => 'Het :attribute veld moet tussen :min en :max karakters zijn.',\n    ],\n    'boolean' => 'Het :attribute veld moet waar of onwaar zijn.',\n    'confirmed' => 'De bevestiging van :attribute komt niet overeen.',\n    'current_password' => 'Het wachtwoord is niet correct.',\n    'date' => 'Het :attribute veld moet een geldige datum zijn.',\n    'date_equals' => 'Het :attribute veld moet een datum gelijk aan :date zijn.',\n    'date_format' => 'Het :attribute veld komt niet overeen met het formaat :format.',\n    'decimal' => 'Het :attribute veld moet :decimal decimalen hebben.',\n    'declined' => 'Het :attribute veld moet afgewezen worden.',\n    'declined_if' =>\n        'Het :attribute veld moet afgewezen worden wanneer :other gelijk is aan :value.',\n    'different' => 'Het :attribute veld en :other moeten verschillend zijn.',\n    'digits' => 'Het :attribute veld moet :digits cijfers zijn.',\n    'digits_between' => 'Het :attribute veld moet tussen :min en :max cijfers zijn.',\n    'dimensions' => 'Het :attribute veld heeft ongeldige afbeeldingsdimensies.',\n    'distinct' => 'Het :attribute veld heeft een dubbele waarde.',\n    'doesnt_end_with' => 'Het :attribute veld mag niet eindigen met een van de volgende: :values.',\n    'doesnt_start_with' =>\n        'Het :attribute veld mag niet beginnen met een van de volgende: :values.',\n    'email' => 'Het :attribute veld moet een geldig e-mailadres zijn.',\n    'ends_with' => 'Het :attribute veld moet eindigen met een van de volgende: :values.',\n    'enum' => 'De geselecteerde :attribute is ongeldig.',\n    'exists' => 'De geselecteerde :attribute is ongeldig.',\n    'file' => 'Het :attribute veld moet een bestand zijn.',\n    'filled' => 'Het :attribute veld moet een waarde hebben.',\n    'gt' => [\n        'array' => 'Het :attribute veld moet meer dan :value items hebben.',\n        'file' => 'Het :attribute veld moet groter zijn dan :value kilobytes.',\n        'numeric' => 'Het :attribute veld moet groter zijn dan :value.',\n        'string' => 'Het :attribute veld moet meer dan :value karakters zijn.',\n    ],\n    'gte' => [\n        'array' => 'Het :attribute veld moet :value items of meer hebben.',\n        'file' => 'Het :attribute veld moet groter dan of gelijk aan :value kilobytes zijn.',\n        'numeric' => 'Het :attribute veld moet groter dan of gelijk aan :value zijn.',\n        'string' => 'Het :attribute veld moet groter dan of gelijk aan :value karakters zijn.',\n    ],\n    'image' => 'Het :attribute veld moet een afbeelding zijn.',\n    'in' => 'De geselecteerde :attribute is ongeldig.',\n    'in_array' => 'Het :attribute veld bestaat niet in :other.',\n    'integer' => 'Het :attribute veld moet een geheel getal zijn.',\n    'ip' => 'Het :attribute veld moet een geldig IP-adres zijn.',\n    'ipv4' => 'Het :attribute veld moet een geldig IPv4-adres zijn.',\n    'ipv6' => 'Het :attribute veld moet een geldig IPv6-adres zijn.',\n    'json' => 'Het :attribute veld moet een geldige JSON string zijn.',\n    'lowercase' => 'Het :attribute veld moet in kleine letters zijn.',\n    'lt' => [\n        'array' => 'Het :attribute veld moet minder dan :value items hebben.',\n        'file' => 'Het :attribute veld moet minder dan :value kilobytes zijn.',\n        'numeric' => 'Het :attribute veld moet minder dan :value zijn.',\n        'string' => 'Het :attribute veld moet minder dan :value karakters zijn.',\n    ],\n    'lte' => [\n        'array' => 'Het :attribute veld mag niet meer dan :value items hebben.',\n        'file' => 'Het :attribute veld moet minder dan of gelijk aan :value kilobytes zijn.',\n        'numeric' => 'Het :attribute veld moet minder dan of gelijk aan :value zijn.',\n        'string' => 'Het :attribute veld moet minder dan of gelijk aan :value karakters zijn.',\n    ],\n    'mac_address' => 'Het :attribute veld moet een geldig MAC-adres zijn.',\n    'max' => [\n        'array' => 'Het :attribute veld mag niet meer dan :max items hebben.',\n        'file' => 'Het :attribute veld mag niet groter zijn dan :max kilobytes.',\n        'numeric' => 'Het :attribute veld mag niet groter zijn dan :max.',\n        'string' => 'Het :attribute veld mag niet meer dan :max karakters zijn.',\n    ],\n    'max_digits' => 'Het :attribute veld mag niet meer dan :max cijfers hebben.',\n    'mimes' => 'Het :attribute veld moet een bestand zijn van het type: :values.',\n    'mimetypes' => 'Het :attribute veld moet een bestand zijn van het type: :values.',\n    'min' => [\n        'array' => 'Het :attribute veld moet minimaal :min items hebben.',\n        'file' => 'Het :attribute veld moet minimaal :min kilobytes zijn.',\n        'numeric' => 'Het :attribute veld moet minimaal :min zijn.',\n        'string' => 'Het :attribute veld moet minimaal :min karakters zijn.',\n    ],\n    'min_digits' => 'Het :attribute veld moet minimaal :min cijfers hebben.',\n    'missing' => 'Het :attribute veld moet ontbreken.',\n    'missing_if' => 'Het :attribute veld moet ontbreken wanneer :other gelijk is aan :value.',\n    'missing_unless' => 'Het :attribute veld moet ontbreken tenzij :other gelijk is aan :value.',\n    'missing_with' => 'Het :attribute veld moet ontbreken wanneer :values aanwezig is.',\n    'missing_with_all' => 'Het :attribute veld moet ontbreken wanneer :values aanwezig zijn.',\n    'multiple_of' => 'Het :attribute veld moet een veelvoud zijn van :value.',\n    'not_in' => 'De geselecteerde :attribute is ongeldig.',\n    'not_regex' => 'Het formaat van :attribute is ongeldig.',\n    'numeric' => 'Het :attribute veld moet een nummer zijn.',\n    'password' => [\n        'letters' => 'Het :attribute veld moet ten minste één letter bevatten.',\n        'mixed' =>\n            'Het :attribute veld moet ten minste één hoofdletter en één kleine letter bevatten.',\n        'numbers' => 'Het :attribute veld moet ten minste één nummer bevatten.',\n        'symbols' => 'Het :attribute veld moet ten minste één symbool bevatten.',\n        'uncompromised' =>\n            'Het gegeven :attribute is verschenen in een datalek. Kies een ander :attribute.',\n    ],\n    'present' => 'Het :attribute veld moet aanwezig zijn.',\n    'prohibited' => 'Het :attribute veld is verboden.',\n    'prohibited_if' => 'Het :attribute veld is verboden wanneer :other gelijk is aan :value.',\n    'prohibited_unless' => 'Het :attribute veld is verboden tenzij :other in :values zit.',\n    'prohibits' => 'Het :attribute veld verbiedt de aanwezigheid van :other.',\n    'regex' => 'Het formaat van :attribute is ongeldig.',\n    'required' => 'Het :attribute veld is verplicht.',\n    'required_array_keys' => 'Het :attribute veld moet ingaves bevatten voor: :values.',\n    'required_if' => 'Het :attribute veld is verplicht wanneer :other gelijk is aan :value.',\n    'required_if_accepted' => 'Het :attribute veld is verplicht wanneer :other geaccepteerd is.',\n    'required_unless' => 'Het :attribute veld is verplicht tenzij :other in :values zit.',\n    'required_with' => 'Het :attribute veld is verplicht wanneer :values aanwezig is.',\n    'required_with_all' => 'Het :attribute veld is verplicht wanneer :values aanwezig zijn.',\n    'required_without' => 'Het :attribute veld is verplicht wanneer :values niet aanwezig is.',\n    'required_without_all' =>\n        'Het :attribute veld is verplicht wanneer geen van :values aanwezig zijn.',\n    'same' => 'Het :attribute en :other moeten overeenkomen.',\n    'size' => [\n        'array' => 'Het :attribute veld moet :size items bevatten.',\n        'file' => 'Het :attribute veld moet :size kilobytes zijn.',\n        'numeric' => 'Het :attribute veld moet :size zijn.',\n        'string' => 'Het :attribute veld moet :size karakters zijn.',\n    ],\n    'starts_with' => 'Het :attribute veld moet beginnen met een van de volgende: :values.',\n    'string' => 'Het :attribute veld moet een string zijn.',\n    'timezone' => 'Het :attribute veld moet een geldige tijdzone zijn.',\n    'unique' => 'Het :attribute is al in gebruik.',\n    'uploaded' => 'Het :attribute werd niet geüploaded.',\n    'uppercase' => 'Het :attribute veld moet in hoofdletters zijn.',\n    'url' => 'Het :attribute veld moet een geldige URL zijn.',\n    'ulid' => 'Het :attribute veld moet een geldige ULID zijn.',\n    'uuid' => 'Het :attribute veld moet een geldige UUID zijn.',\n];\n"
  },
  {
    "path": "lang/ru/auth.ftl",
    "content": "### Auth\n\n## Laravel strings\n## https://github.com/laravel/framework/blob/master/src/Illuminate/Translation/lang/en/auth.php\n\nfailed = Эти учетные данные не соответствуют нашим записям.\npassword = Предоставленный пароль неверен.\nthrottle = Слишком много попыток входа. Повторите попытку через { $seconds } секунд.\n\n## Login\n\nlogin-title = Войдите в свою учетную запись\nlogin-submit = Вход\nlogin-register-prompt = У вас нет аккаунта?\nlogin-register-link = Зарегистрироваться\ncontinue-with-provider = Продолжить с { $provider }\n\nemail-label = Электронная почта\npassword-label = Пароль\nremember-me-label = Запомнить меня\nforgot-password-link = Забыли пароль?\n\n## Forgot Password\n\nforgot-password-title = Забыли пароль?\nforgot-password-introduction = Запросите ссылку для сброса пароля, введя свой адрес электронной почты ниже.\nforgot-password-submit = Отправить ссылку для сброса пароля\n\nreset-password-title = Сброс пароля\nnew-password-label = Новый пароль\nconfirm-password-label = Подтвердите пароль\nreset-password-submit = Сброс пароля\n\nreset-password-mail-subject = Сбросить пароль\nreset-password-mail-body = Мы получили запрос на сброс пароля для вашей учетной записи на { $forum }. Если это были не вы, никаких дальнейших действий не требуется. Срок действия этой ссылки истекает через { $minutes } минут.\nreset-password-mail-button = Сброс пароля\n\n## Register\n\nregister-title = Создать аккаунт\nname-label = Имя пользователя\nregister-submit = Создать аккаунт\nregister-login-prompt = У вас уже есть аккаунт?\nregister-login-link = Вход\n\n## Confirm Password\n\nconfirm-password-title = Подтвердите ваш пароль\nconfirm-password-introduction = Пожалуйста, подтвердите свой пароль, прежде чем продолжить.\nconfirm-password-submit = Подтвердить\n\n## Email Verification\n\nemail-verification-sent-message = Мы отправили электронное письмо с подтверждением на { $email }. Если оно не придет в ближайшее время, проверьте папку со спамом.\nemail-verification-resend-button = Отправить снова\nemail-verification-required-message = Вы должны подтвердить свой адрес электронной почты.\nemail-verification-success-message = Спасибо за подтверждение вашей электронной почты!\nemail-verification-mail-subject = Подтвердите адрес электронной почты\nemail-verification-mail-body = Нажмите кнопку ниже, чтобы подтвердить свой адрес электронной почты. Если у вас нет учетной записи на { $forum }, никаких дальнейших действий не требуется.\nemail-verification-mail-button = Подтвердите адрес электронной почты\n"
  },
  {
    "path": "lang/ru/cp.ftl",
    "content": "### Localization for Waterhole Control Panel\n\ntitle = Панель управления\n\n## Dashboard\n\ndashboard-title = Панель управления\n\nconfigure-mail-message = Для того, чтобы Waterhole мог отправлять письма с подтверждением, вам необходимо настроить почтовый драйвер.\ndebug-mode-on-message = Режим отладки включен. Чувствительные значения конфигурации могут быть видны.\n\ngetting-started-title = Начало работы с Waterhole\ngetting-started-strategy-title = Документация\ngetting-started-strategy-description = Узнайте, как построить успешное сообщество с помощью Waterhole.\ngetting-started-structure-title = Настройка структуры\ngetting-started-structure-description = Настройте каналы и страницы, которые составляют основу вашего сообщества.\ngetting-started-groups-title = Определение групп пользователей\ngetting-started-groups-description = Создайте группы для модераторов, персонала и суперпользователей.\ngetting-started-design-title = Присоединитесь к сообществу Waterhole\ngetting-started-design-description = Задавайте вопросы, делитесь советами и узнавайте, как получить максимальную отдачу от вашего сообщества.\n\ndashboard-users-title = Пользователи\ndashboard-posts-title = Сообщения\ndashboard-comments-title = Комментарии\n\nperiod-today = Сегодня\nperiod-last-7-days = Последние 7 дней\nperiod-last-4-weeks = Последние 4 недели\nperiod-last-3-months = Последние 3 месяца\nperiod-last-12-months = Последние 12 месяцев\nperiod-this-month = Текущий месяц\nperiod-this-quarter = Текущий квартал\nperiod-this-year = Текущий год\nperiod-all-time = За все время\nperiod-current-heading = Текущий период\npreiod-previous-heading = Предыдущий период\n\n## Structure\n\nstructure-title = Структура\n\nstructure-channel-label = Канал\nstructure-page-label = Страница\nstructure-link-label = Ссылка\nstructure-heading-label = Заголовок\nstructure-visibility-public-label = Публичный\nstructure-visibility-members-label = Участники\n\nstructure-navigation-title = Навигация\nstructure-navigation-description = Переместите элементы сюда, чтобы отображать их в меню навигации.\n\nstructure-unlisted-title = Скрытые\nstructure-unlisted-description = Переместите элементы сюда, чтобы скрыть их из меню навигации.\n\ndelete-structure-confirm-message = Вы уверены, что хотите удалить этот узел?\n\n## Structure - Heading\n\nedit-heading-title = Редактировать заголовок\ncreate-heading-title = Создать заголовок\nheading-name-label = Название\n\n## Structure - Link\n\nedit-link-title = Редактировать ссылку\ncreate-link-title = Создать ссылку\nlink-details-title = Детали\nlink-name-label = Название\nlink-url-label = URL-адрес\nlink-permissions-title = Разрешения\n\n## Structure - Page\n\nedit-page-title = Редактировать страницу\ncreate-page-title = Создать страницу\npage-details-title = Детали\npage-name-label = Название\npage-slug-label = ЧПУ (Slug)\npage-slug-url-label = Эта страница будет доступна по адресу:\npage-body-label = Содержание\npage-permissions-title = Разрешения\n\n## Structure - Channel\n\nedit-channel-title = Редактировать канал\ncreate-channel-title = Создать канал\nchannel-details-title = Детали\nchannel-name-label = Название\nchannel-slug-label = ЧПУ (Slug)\nchannel-slug-url-label = Этот канал будет доступен по адресу:\nchannel-description-label = Описание\nchannel-description-description = Краткое описание назначения этого канала.\nchannel-options-title = Параметры\nchannel-visibility-label = Видимость\nchannel-ignore-label = По умолчанию игнорируется\nchannel-ignore-description = Скрыть сообщения в этом канале из ленты для всех пользователей, если они явно не подписаны на него.\nchannel-layout-title = Разметка\nchannel-layout-label = Разметка\nchannel-layout-show-author-label = Показывать автора сообщения\nchannel-layout-show-excerpt-label = Показывать отрывок сообщения\nchannel-filters-label = Фильтры\nchannel-custom-filters-label = Использовать пользовательские фильтры для этого канала\nchannel-custom-filters-description = Переопределите глобальные фильтры для этого канала.\nchannel-permissions-title = Разрешения\nchannel-approval-label = Одобрение\nchannel-approval-moderators-exempt = Модераторы освобождены от одобрения.\nchannel-require-approval-posts-label = Требовать одобрения для сообщений\nchannel-require-approval-comments-label = Требовать одобрения для комментариев\nchannel-features-title = Возможности\nchannel-reactions-label = Реакции\nchannel-reactions-posts-label = Сообщения\nchannel-reactions-comments-label = Комментарии\nreaction-set-picker-default = По умолчанию ({ $name })\nreaction-set-picker-none = Нет\nchannel-taxonomies-label = Таксономии\nchannel-answers-label = Ответы\nchannel-enable-answers-label = Включить ответы в этом канале\nchannel-enable-answers-description = Разрешить авторам сообщений отмечать комментарии как ответы.\nchannel-posting-title = Размещение\nchannel-instructions-label = Инструкции по размещению\nchannel-instructions-description = Укажите инструкции, которые будут отображаться пользователям при создании сообщений в этом канале.\nchannel-similar-posts-title = Похожие сообщения\nchannel-show-similar-posts-label = Показывать похожие сообщения из этого канала на основе названия\n\ndelete-channel-title = Удалить канал:\ndelete-channel-posts-label = Удалить { $count } { $count ->\n    [one] сообщение\n    *[few] сообщения\n    *[other] сообщений\n}\nmove-to-channel-posts-label = Переместить { $count } { $count ->\n    [one] сообщение\n    *[few] сообщения\n    *[other] сообщений\n} в другой канал\n\n## Groups\n\ngroups-title = Группы\ncreate-group-button = Создать группу\ngroup-user-count = { $count } { $count ->\n    [one] пользователь\n    *[few] пользователя\n    *[other] пользователей\n}\n\nedit-group-title = Редактировать группу\ncreate-group-title = Создать группу\ngroup-details-title = Детали\ngroup-name-label = Название\ngroup-appearance-label = Внешний вид\ngroup-show-as-badge-label = Отображать эту группу как значок пользователя\ngroup-color-label = Цвет\ngroup-icon-label = Иконка\ngroup-rules-title = Участие\ngroup-auto-assign-label = Автоматически назначать эту группу новым участникам\ngroup-rules-requires-approval-label = Требовать одобрения контента новых участников\ngroup-rules-remove-after-approval-label = Удалять из группы после одобрения\ngroup-permissions-title = Разрешения\ngroup-global-permissions-title = Глобальные разрешения\ngroup-structure-permissions-title = Разрешения структуры\ngroup-permission-suspend-users-label = Разрешить приостанавливать пользователей\n\ndelete-group-confirm-message = Вы уверены, что хотите удалить эту группу?\n\n## Users\n\nusers-title = Пользователи\nusers-filter-placeholder = Фильтр пользователей\nusers-filter-group-description = Фильтр по группе\ncreate-user-button = Создать пользователя\n\nusers-name-column = Имя\nusers-email-column = Эл. почта\nusers-groups-column = Группы\nusers-created-at-column = Создан\nusers-last-seen-at-column = Последний визит\nusers-empty-message = Нет результатов\n\nedit-user-title = Редактировать пользователя\ncreate-user-title = Создать пользователя\nuser-account-title = Аккаунт\nuser-name-label = Имя\nuser-email-label = Эл. почта\nuser-password-label = Пароль\nuser-set-password-label = Установить новый пароль\nuser-groups-label = Группы\nuser-profile-title = Профиль\nuser-created-message = Пользователь создан.\nuser-saved-message = Пользователь сохранен.\n\ndelete-user-title = Удалить { $count ->\n    [one] пользователя:\n    *[few] { $count } пользователя\n    *[other] { $count } пользователей\n}\nkeep-user-content-label = Сохранить контент и отметить как анонимный\ndelete-user-content-label = Удалить контент навсегда\ndelete-user-success-message = Пользователь удален.\n\n## Reactions\n\nreactions-title = Реакции\nreaction-sets-title = Наборы реакций\ncreate-reaction-set-button = Создать набор реакций\nedit-reaction-set-title = Редактировать набор реакций\ncreate-reaction-set-title = Создать набор реакций\nreaction-set-details-title = Детали\nreaction-set-name-label = Название\nreaction-set-usage-label = Использование\nreaction-set-default-posts = Использовать по умолчанию для сообщений\nreaction-set-default-comments = Использовать по умолчанию для комментариев\ndelete-reaction-set-confirm-message = Вы уверены, что хотите удалить этот набор реакций?\nreaction-set-saved-message = Набор реакций сохранен.\n\nreaction-types-title = Типы реакций\nreaction-types-empty-message = Нет типов реакций\nreaction-types-add-button = Добавить\nedit-reaction-type-title = Редактировать тип реакции\ncreate-reaction-type-title = Создать тип реакции\nreaction-type-name-label = Название\nreaction-type-score-label = Очки\nreaction-type-score-description = Количество очков, присваиваемых этой реакции.\ndelete-reaction-type-confirm-message = Вы уверены, что хотите удалить этот тип реакции?\nreaction-type-saved-message = Тип реакции сохранен.\n\n## Taxonomies\n\ntaxonomies-title = Таксономии\ncreate-taxonomy-button = Создать таксономию\ncreate-taxonomy-title = Создать таксономию\nedit-taxonomy-title = Редактировать таксономию\ntaxonomy-details-title = Детали\ntaxonomy-permissions-title = Разрешения\ntaxonomy-tags-title = Теги\ntaxonomy-name-label = Название\ntaxonomy-options-title = Параметры\ntaxonomy-required-label = Требовать выбор тега при создании сообщения\ntaxonomy-allow-multiple-label = Разрешить выбор нескольких тегов\ntaxonomy-saved-message = Таксономия сохранена.\ndelete-taxonomy-confirm-message = Вы уверены, что хотите удалить эту таксономию?\n\ncreate-tag-title = Создать тег\nedit-tag-title = Редактировать тег\ntag-name-label = Название\ntag-saved-message = Тег сохранен.\ndelete-tag-confirm-message = Вы уверены, что хотите удалить этот тег?\n\n## Licensing\n\nlicense-error-message = Ваш лицензионный ключ не может быть проверен из-за ошибки связи с API Waterhole. ({ $status })\nlicense-invalid-message = Пожалуйста, приобретите или введите действительный лицензионный ключ для этого сайта в соответствии с лицензионным соглашением.\nlicense-expired-message = У вас нет лицензии на использование этой версии Waterhole. Пожалуйста, снизьте версию или продлите свою лицензию.\nlicense-suspended-message = Ваша лицензия Waterhole была приостановлена. Пожалуйста, свяжитесь с нами для получения дополнительной информации.\n\ntrial-badge = Пробная версия\nlicensed-badge = Лицензировано\nunlicensed-badge = Без лицензии\n"
  },
  {
    "path": "lang/ru/forum.ftl",
    "content": "### Forum\n\nfeed-link = Лента\n\n## Header\n\nlog-in = Вход\nregister = Регистрация\n\n## Search\n\nsearch-placeholder = Поиск по всем дискусиям\nsearch-results-title = Результаты поиска для \"{ $query }\"\nsearch-button = Поиск\n\nsearch-filter-button = Фильтр\n\nsearch-showing-results-title = Показывается { $total } { $total ->\n    [one] результат\n    *[other] результаты\n}\n\nsearch-showing-results-non-exhaustive-title = Показывается { $total }+ { $total ->\n    [one] результат\n    *[other] результаты\n}\n\nsearch-sort-relevance = Сортировать по релевантности\nsearch-sort-latest = Сортировать по последним\nsearch-sort-top = Сортировать по топу\n\nsearch-empty-message = Результатов не найдено\nsearch-keywords-too-short-message = Ваши ключевые слова слишком короткие — попробуйте что-нибудь подлиннее.\n\n## Posts\n\npost-activity-replied = ответил\npost-activity-posted = опубликовано\n\npost-new-badge = Новый\npost-new-badge-tooltip = Новая дискуссия\npost-locked-badge = Заблокировано\npost-answered-badge = Ответил\npost-trash-badge = Корзина\n\npost-removed-message = Сообщение удалено\n\npost-answered-by = Ответил\npost-view-answer-link = Посмотреть ответ\n\npost-unread-comments-badge-tooltip = { $count } { $count ->\n    [one] непрочитанный комментарий\n    *[other] непрочитанные комментарии\n}\n\npost-comments-heading = { $count } { $count ->\n    [one] комментарий\n    *[other] комментарии\n}\n\nmark-as-read-instruction = Нажмите, чтобы отметить как прочитанное\n\npost-comments-link = { $count } { $count ->\n    [one] комментарий\n    *[other] комментарии\n}\n\nadd-reaction-button = Добавить реакцию\n\nmove-post-title = Перенести { $count ->\n    [one] Дискуссию:\n    *[other] { $count } Дискуссии\n}\n\nmove-to-channel-button = Перенести в канал\nmove-to-channel-confirm-button = Перенести\n\nmark-as-read-button = Пометить, как прочитанное\n\ncreate-post-button = Создать дискуссию\ncreate-post-title = Новая дискуссия\npost-channel-label = Канал\npost-submit-button = Создать\nedit-post-title = Редактировать дискуссию\nedit-post-link = Редактировать дискуссию\npost-title-label = Заголовок\nsimilar-posts-label = Посмотрите эти похожие дискуссии:\npost-body-label = Содержимое\n\nchannel-picker-placeholder = Выберите канал\n\ndelete-post-confirm-message = Вы уверены, что хотите удалить эту запись?\ndelete-post-success-message = Дискуссия удалено.\n\noriginal-post-link = Оригинальная дискуссия\n\npin-to-top-button = Закрепить сверху\nunpin-button = Открепить\n\npost-comment-button = Комментировать\n\nmove-to-trash-button = Переместить в корзину\nrestore-button = Восстановить\ndelete-forever-button = Удалить навсегда\n\n## Comments\n\ncomments-unread-heading = Не прочитано\ncomments-unread-link = Не прочитано\n\ncreate-comment-title = Написать комментарий\nedit-comment-title = Редактировать комментарий\ncomment-number-title = Комментарий #{ $number }\n\ncomment-in-reply-to-link = В ответ на\ncomment-show-replies-button = Показать { $count } { $count ->\n    [one] ответ\n    *[other] ответов\n}\ncomment-reply-button = Ответить\nmark-as-answer-button = Отметить как ответ\nunmark-as-answer-button = Снять пометку как ответ\ncomment-answer-badge = Ответить\n\ncomments-locked-message = Комментарии заблокированы.\nlock-comments-button = Заблокировать комментарии\nunlock-comments-button = Разблокировать комментарии\n\ncomposer-placeholder = Написать комментарий...\ncomposer-reply-to-placeholder = Ответить { $userName }...\ncomposer-replying-to-label = Ответить\ncomposer-clear-reply-button = Очисить\ncomposer-submit = Опубликовать\n\ndelete-comment-confirm-message = Вы уверенны, что хотите удалить этот комментарий?\n\ncomment-removed-message = Комментарий удален\ncomment-removed-tooltip = { $user } удалил { $timestamp }\n\n## Moderation\n\nremove-button = Удалить\nremoved-by-label = Удалено пользователем\n\nremoval-reason-label = Причина удаления\nremoval-reason-unspecified-label = Не указана\nremoval-message-label = Сообщение автору\nreport-button = Пожаловаться\nreport-confirm-button = Отправить жалобу\nreport-note-placeholder = Добавьте заметку (необязательно)\nreport-system-user = Система\nuser-actions-label = Действия пользователя\nuser-actions-suspend-label = Приостановить { $user }\nuser-actions-suspend-days = Дни\nuser-actions-suspend-weeks = Недели\nuser-actions-suspend-indefinitely = Бессрочно\n\napprove-button = Одобрить\nflag-dismiss-button = Отклонить\n\nreport-reason-off-topic-label = Не по теме\nreport-reason-off-topic-description = Этот контент не относится к текущему обсуждению.\n\nreport-reason-inappropriate-label = Неприемлемо\nreport-reason-inappropriate-description = Этот контент оскорбителен, содержит злоупотребления или нарушает правила сообщества.\n\nreport-reason-spam-label = Спам\nreport-reason-spam-description = Этот контент является рекламой или вандализмом.\n\nreport-reason-other-label = Другое\nreport-reason-other-description = Этот контент требует внимания по другой причине.\n\nreport-reason-approval-label = Ожидает одобрения\n\nmoderation-title = Модерация\nmoderation-empty-message = Нет ожидающих жалоб\nmoderation-finished-message = Вы все просмотрели.\n\npending-approval-title = Ожидает одобрения\n\n## Misc\n\nquote-button = Цитировать\n\nattribution-timestamp-created-label = Опубликовано\nattribution-timestamp-edited-label = Отредактировано\n\n## Filters\n\nfilter-alphabetical = Алфавитный\nfilter-following = Подписка\nfilter-ignoring = Игнорирование \nfilter-newest = Новые\nfilter-latest = Последние\nfilter-oldest = Старые\nfilter-top = Топ\nfilter-top-all-time = За все время\nfilter-top-year = Год\nfilter-top-quarter = Четверть\nfilter-top-month = Месяц\nfilter-top-week = Неделя\nfilter-top-day = День\nfilter-trending = Популярые\nfilter-trash = Корзина\n\n## Followables\n\nfollow-button = Подписаться\nfollow-button-following = Подписаться\nfollow-button-ignored = Игнорируется\n\nignore-button = Игнорировать\nunfollow-button = Отписаться\nunignore-button = Перестать игнорировать\n\nchannel-follow-description = Получайте уведомления, когда на этом канале появляются новые сообщения.\npost-follow-description = Получайте уведомления, когда к этому посту появляются новые комментарии.\n\npost-following-badge = Подписаться\npost-ignored-badge = Игнорируется\n\n## Index\n\nmenu-button = Меню\nnavigation-title = Навигация по форуму\n\npost-feed-new-activity-button = Новая активность\npost-feed-new-activity-heading = овая активность\npost-feed-empty-message = Нет сообщений\npost-feed-controls-layout-heading = Показать как\n"
  },
  {
    "path": "lang/ru/install.ftl",
    "content": "### Installation\n\n## Groups\n\ngroup-guest = Гость\ngroup-member = Участник\ngroup-admin = Администратор\ngroup-moderator = Модератор\ngroup-quarantine = Карантин\n\n## Reactions\n\nreaction-set-emoji = Эмодзи\nreaction-type-like = Нравится\nreaction-type-love = Любовь\nreaction-type-laugh = Смех\nreaction-type-wow = Вау\nreaction-type-sad = Грусть\nreaction-type-angry = Злость\n\nreaction-set-votes = Голоса\nreaction-type-upvote = Голос вверх\n\n## Structure\n\nannouncements-name = Объявления\nannouncements-description = Новости и другие обновления от команды.\n\nintroductions-name = Представления\nintroductions-description = Новичок в сообществе? Представьтесь!\n\nsupport-name = Поддержка\nsupport-description = Получите помощь по установке, использованию и настройке нашего продукта.\n\nideas-name = Идеи\nideas-description = У вас есть идея? Мы хотим ее услышать!\n\nstaff-name = Только для сотрудников\nstaff-description = Частный канал для обсуждения сотрудниками.\n\nguide-title = Руководство по сообществу\nguide-body =\n  Добро пожаловать в { $forumName } и спасибо, что присоединились к нам! Мы хотим, чтобы каждый получал максимум удовольствия от этого сообщества, поэтому просим вас прочитать и следовать этим руководствам.\n\n  - **Будьте вежливы.** Это место, где можно делиться знаниями и интересами через разговоры. Будьте добрыми, терпеливыми и уважительными ко всем, включая людей вне сообщества Waterhole.\n\n  - **Нет личных нападок.** Критика идей с помощью обоснованных аргументов является важной частью нашей работы. Однако это не означает, что это должно переходить в личные нападки. Харассмент и другое исключительное поведение неприемлемы.\n\n  - **Предположите добросовестность.** Когда возникает разногласие, старайтесь понять, почему оно возникло, всегда исходя из добрых намерений. Помните, что у разных людей могут быть разные взгляды на проблемы, и это нормально.\n"
  },
  {
    "path": "lang/ru/notifications.ftl",
    "content": "### Notifications\n\ntitle = Уведомления\nmark-all-as-read-button = Отметить все как прочитанное\npreferences-button = Настройки уведомлений\nempty-message = Нет уведомлений\n\n## Unsubscribe\n\nunsubscribe-link = Отписаться от этих уведомлений\nunsubscribe-success-message = Вы отписались от этих уведомлений.\nmanage-notification-preferences-link = Управлять настройками уведомлений\n\n## Mention\n\nmention-description = Упоминания и ответы на мои комментарии\nmention-title = Упомянут в { $post }\nmention-reason = Вы получили это, потому что подписаны на уведомления об упоминаниях.\nmention-unsubscribe = Отписаться от уведомлений об упоминаниях\n\n## Content Approved\n\npost-approved-title = Сообщение одобрено: { $post }\ncomment-approved-title = Комментарий одобрен в { $post }\n\n## New Comment\n\nnew-comment-description = Новые комментарии в отслеживаемых сообщениях\nnew-comment-title = Новый комментарий в { $post }\nnew-comment-reason = Вы получили это, потому что следите за этим сообщением.\nnew-comment-unsubscribe = Перестать следить за этим сообщением\n\n## New Post\n\nnew-post-description = Новые сообщения в отслеживаемых каналах\nnew-post-title = Новый пост в { $channel }: { $post }\nnew-post-reason = Вы получили это, потому что следите за этим каналом.\nnew-post-unsubscribe = Перестать следить за этим каналом\n\n## New Flag\n\nnew-flag-description = Жалобы в каналах, которые я модерирую\nflagged-post-title = Сообщение помечено: { $post }\nflagged-comment-title = Комментарий помечен в { $post }\nnew-flag-reason = Вы получили это, потому что модерируете канал.\nnew-flag-unsubscribe = Отписаться от уведомлений о жалобах\n\n## Content Removed\n\npost-removed-title = Сообщение удалено: { $post }\ncomment-removed-title = Комментарий удален в { $post }\n\n## Common\n\nview-post-button = Просмотреть сообщение\nview-comment-button = Просмотреть комментарий\n"
  },
  {
    "path": "lang/ru/passwords.php",
    "content": "<?php\n\n// https://github.com/laravel/framework/blob/master/src/Illuminate/Translation/lang/en/passwords.php\n\nreturn [\n    'reset' => 'Ваш пароль был сброшен!',\n    'sent' => 'Мы отправили вам ссылку для сброса пароля по электронной почте!',\n    'throttled' => 'Пожалуйста, подождите перед повторной попыткой.',\n    'token' => 'Этот токен для сброса пароля недействителен.',\n    'user' => 'Мы не можем найти пользователя с указанным адресом электронной почты.',\n];\n"
  },
  {
    "path": "lang/ru/system.ftl",
    "content": "## Accessibility\n\nskip-to-main-content-link = Перейти к основному содержанию\n\n## Errors\n\nfatal-error-heading = Что-то пошло не так\ntry-again-button = Попробуйте еще раз\n\nfatal-error-message = Что-то пошло не так! Пожалуйста, обновите страницу и попробуйте еще раз.\ntoo-many-requests-message = Сделайте перерыв и повторите попытку через мгновение.\nforbidden-message = У вас нет на это разрешения.\nsession-expired-message = Сессия истекла. Пожалуйста, перезагрузите страницу и попробуйте снова.\nvalidation-errors-message = Были обнаружены следующие ошибки:\n\n## Generic Buttons & Links\n\nsave-changes-button = Сохранить изменения\ncreate-button = Создать\ncancel-button = Отменить\nchange-button = Изменить\ncontinue-button = Продолжить\nactions-button = Действия\nlearn-more-link = Узнать больше\ndelete-button = Удалить\nedit-link = Редактировать\ncopy-link-button = Копировать ссылку\ncontrols-button = Действия\nmore-button = Больше\nloading = Загрузка\nshow-more-button = Показать больше\n\n## Post Feed Layouts\n\nlayout-list = Список\nlayout-cards = Карточки\n\n## Actions\n\nconfirm-action-title = Подтвердить действие\ndelete-confirm-button = Удалить\nlink-copied-message = Ссылка скопирована в буфер обмена.\n\n## Users\n\ndeleted-user = Удалить пользователя\nuser-list-overflow = { $count } другие\n\n## Pagination\n\npagination-first-link = Первая\npagination-previous-link = Предыдущая\npagination-next-link = Следующая\npagination-last-link = Последняя\nload-more-button = Загрузить еще\npage-number-prefix = Страница\npage-number-heading = Страница { $number }\n\n## Theme Switcher\n\ntheme-button = Тема\ntheme-light = Светлая\ntheme-dark = Темная\ntheme-automatic = Автоматическая\n\n## Text Editor\n\ntext-editor-heading = Заголовок\ntext-editor-bold = Жирный\ntext-editor-italic = Курсив\ntext-editor-quote = Цитата\ntext-editor-code = Код\ntext-editor-link = Ссылка\ntext-editor-bulleted-list = Маркированный список\ntext-editor-numbered-list = Нумерованный список\ntext-editor-mention = Упомянуть пользователя\ntext-editor-emoji = Вставить эмодзи\ntext-editor-attachment = Прикрепить файлы\ntext-editor-preview = Предпросмотр\n\n## Icon Picker\n\nicon-field-label = Иконка\nicon-picker-change-button = Изменить\nicon-picker-none-option = Нет\nicon-picker-emoji-option = Эмодзи\nicon-picker-emoji-description = Введите один символ эмодзи.\nicon-picker-svg-option = Иконка SVG\nicon-picker-svg-description = Введите название иконки из одного из установленных наборов: { $sets }.\nicon-picker-svg-search-link = Поиск иконок\nicon-picker-image-option = Изображение\n\n## Abilities\n\nability-view = Просмотр\nability-comment = Комментирование\nability-post = Опубликование\nability-moderate = Модерация\nability-assign-tags = Назначение тегов\n\n## Used in the Waterhole\\compact_number() function\n\ncompact-number-1000 = 0.0K\ncompact-number-10000 = 00K\ncompact-number-100000 = 000K\ncompact-number-1000000 = 0.0M\ncompact-number-10000000 = 00M\ncompact-number-100000000 = 000M\ncompact-number-1000000000 = 0.0B\n"
  },
  {
    "path": "lang/ru/user.ftl",
    "content": "### User\n\n## Account Settings\n\naccount-settings-title = Настройки аккаунта\n\ndelete-account-button = Удалить аккаунт\ndelete-account-confirmation-title = Вы уверены, что хотите удалить свой аккаунт?\ndelete-account-confirmation-description = Ваши данные аккаунта будут удалены. Ваши вклады будут сохранены, но помечены как анонимные. Это действие нельзя отменить.\ndelete-account-success-message = Ваш аккаунт был удален.\n\n## Notification Preferences\n\nnotification-preferences-title = Настройки уведомлений\nnotifications-label = Уведомления\nnotification-channel-web = Веб-платформа\nnotification-channel-email = Электронная почта\nnotifications-following-label = Подписка\nfollow-on-comment-label = Автоматически подписываться на посты, на которые я оставляю комментарии\nnotification-preferences-saved-message = Настройки уведомлений сохранены.\n\n## Edit Profile\n\nedit-profile-title = Редактирование профиля\nprofile-saved-message = Профиль сохранен.\n\navatar-label = Аватар\nremove-avatar-label = Удалить аватар\nheadline-label = Заголовок\nheadline-description = Опишите себя в нескольких словах. Это будет отображаться рядом с вашим именем.\nbio-label = Биография\nbio-description = Расскажите больше о себе. Это будет отображаться на вашем профиле.\nlocation-label = Местоположение\nwebsite-label = Веб-сайт\nprivacy-title = Конфиденциальность\nshow-online-label = Показывать, когда я был(а) в сети\n\n## Comments\n\nuser-comments-title = Комментарии { $userName }\ncomments-empty-message = Комментариев нет\n\n## Posts\n\nuser-posts-title = Дискуссии пользователя { $userName }\nposts-empty-message = Нет постов\n\n## User Menu\n\nprofile-link = Профиль\npreferences-link = Настройки\nadministration-link = Администрирование\nlog-out-link = Выйти\n\n## Profile\n\nuser-joined-text = Присоединился\nuser-last-seen-text = Был в сети\nonline-label = В сети\n\n## Sidebar\n\nposts-link = Сообщения\ncomments-link = Комментарии\npreferences-heading = Настройки\naccount-settings-link = Аккаунт\nedit-profile-link = Профиль\nnotification-preferences-link = Уведомления\n\n## Admin\n\nsuspend-button = Приостановить\nedit-suspension-button = Редактировать приостановку\nsuspend-user-title = Приостановить\nnot-suspended-label = Не приостановлен\nsuspended-indefinitely-label = Бессрочная приостановка\nsuspended-until-label = Приостановлен до...\nsuspended-badge = Приостановлен\nsuspended-message = Ваш аккаунт приостановлен.\n\ncopy-impersonation-url-button = Копировать URL для подмены\nimpersonation-url-copied-message = URL для подмены скопирован – откройте его в новом приватном окне.\n"
  },
  {
    "path": "lang/ru/validation.php",
    "content": "<?php\n\n// https://github.com/laravel/framework/blob/master/src/Illuminate/Translation/lang/en/validation.php\n\nreturn [\n    'accepted' => 'Поле :attribute должно быть принято.',\n    'accepted_if' => 'Поле :attribute должно быть принято, когда :other равно :value.',\n    'active_url' => 'Поле :attribute должно быть корректным URL.',\n    'after' => 'Поле :attribute должно быть датой после :date.',\n    'after_or_equal' => 'Поле :attribute должно быть датой после или равной :date.',\n    'alpha' => 'Поле :attribute должно содержать только буквы.',\n    'alpha_dash' => 'Поле :attribute должно содержать только буквы, цифры, дефисы и подчеркивания.',\n    'alpha_num' => 'Поле :attribute должно содержать только буквы и цифры.',\n    'array' => 'Поле :attribute должно быть массивом.',\n    'ascii' =>\n        'Поле :attribute должно содержать только однобайтовые алфавитно-цифровые символы и символы.',\n    'before' => 'Поле :attribute должно быть датой до :date.',\n    'before_or_equal' => 'Поле :attribute должно быть датой до или равной :date.',\n    'between' => [\n        'array' => 'Поле :attribute должно содержать от :min до :max элементов.',\n        'file' => 'Поле :attribute должно быть от :min до :max килобайт.',\n        'numeric' => 'Поле :attribute должно быть от :min до :max.',\n        'string' => 'Поле :attribute должно быть от :min до :max символов.',\n    ],\n    'boolean' => 'Поле :attribute должно быть true или false.',\n    'confirmed' => 'Подтверждение поля :attribute не совпадает.',\n    'current_password' => 'Неверный пароль.',\n    'date' => 'Поле :attribute должно быть корректной датой.',\n    'date_equals' => 'Поле :attribute должно быть датой, равной :date.',\n    'date_format' => 'Поле :attribute должно соответствовать формату :format.',\n    'decimal' => 'Поле :attribute должно иметь :decimal десятичных знаков.',\n    'declined' => 'Поле :attribute должно быть отклонено.',\n    'declined_if' => 'Поле :attribute должно быть отклонено, когда :other равно :value.',\n    'different' => 'Поле :attribute и :other должны быть разными.',\n    'digits' => 'Поле :attribute должно быть :digits цифрами.',\n    'digits_between' => 'Поле :attribute должно быть от :min до :max цифр.',\n    'dimensions' => 'Поле :attribute имеет недопустимые размеры изображения.',\n    'distinct' => 'Поле :attribute имеет повторяющееся значение.',\n    'doesnt_end_with' =>\n        'Поле :attribute не должно оканчиваться на одно из следующих значений: :values.',\n    'doesnt_start_with' =>\n        'Поле :attribute не должно начинаться с одного из следующих значений: :values.',\n    'email' => 'Поле :attribute должно быть корректным адресом электронной почты.',\n    'ends_with' => 'Поле :attribute должно оканчиваться одним из следующих значений: :values.',\n    'enum' => 'Выбранное значение :attribute недопустимо.',\n    'exists' => 'Выбранное значение :attribute недопустимо.',\n    'file' => 'Поле :attribute должно быть файлом.',\n    'filled' => 'Поле :attribute должно иметь значение.',\n    'gt' => [\n        'array' => 'Поле :attribute должно содержать больше :value элементов.',\n        'file' => 'Поле :attribute должно быть больше :value килобайт.',\n        'numeric' => 'Поле :attribute должно быть больше :value.',\n        'string' => 'Поле :attribute должно быть больше :value символов.',\n    ],\n    'gte' => [\n        'array' => 'Поле :attribute должно содержать :value элементов или больше.',\n        'file' => 'Поле :attribute должно быть больше или равно :value килобайт.',\n        'numeric' => 'Поле :attribute должно быть больше или равно :value.',\n        'string' => 'Поле :attribute должно быть больше или равно :value символов.',\n    ],\n    'image' => 'Поле :attribute должно быть изображением.',\n    'in' => 'Выбранное значение :attribute недопустимо.',\n    'in_array' => 'Поле :attribute должно существовать в :other.',\n    'integer' => 'Поле :attribute должно быть целым числом.',\n    'ip' => 'Поле :attribute должно быть корректным IP-адресом.',\n    'ipv4' => 'Поле :attribute должно быть корректным IPv4-адресом.',\n    'ipv6' => 'Поле :attribute должно быть корректным IPv6-адресом.',\n    'json' => 'Поле :attribute должно быть корректной JSON-строкой.',\n    'lowercase' => 'Поле :attribute должно быть в нижнем регистре.',\n    'lt' => [\n        'array' => 'Поле :attribute должно содержать меньше :value элементов.',\n        'file' => 'Поле :attribute должно быть меньше :value килобайт.',\n        'numeric' => 'Поле :attribute должно быть меньше :value.',\n        'string' => 'Поле :attribute должно быть меньше :value символов.',\n    ],\n    'lte' => [\n        'array' => 'Поле :attribute не должно содержать более :value элементов.',\n        'file' => 'Поле :attribute должно быть меньше или равно :value килобайт.',\n        'numeric' => 'Поле :attribute должно быть меньше или равно :value.',\n        'string' => 'Поле :attribute должно быть меньше или равно :value символам.',\n    ],\n    'mac_address' => 'Поле :attribute должно быть действительным MAC-адресом.',\n    'max' => [\n        'array' => 'Поле :attribute не должно содержать более :max элементов.',\n        'file' => 'Поле :attribute должно быть не больше :max килобайт.',\n        'numeric' => 'Поле :attribute должно быть не больше :max.',\n        'string' => 'Поле :attribute должно быть не больше :max символов.',\n    ],\n    'max_digits' => 'Поле :attribute не должно содержать более :max цифр.',\n    'mimes' => 'Поле :attribute должно быть файлом типа: :values.',\n    'mimetypes' => 'Поле :attribute должно быть файлом типа: :values.',\n    'min' => [\n        'array' => 'Поле :attribute должно содержать как минимум :min элементов.',\n        'file' => 'Поле :attribute должно быть не меньше :min килобайт.',\n        'numeric' => 'Поле :attribute должно быть не меньше :min.',\n        'string' => 'Поле :attribute должно быть не меньше :min символов.',\n    ],\n    'min_digits' => 'Поле :attribute должно содержать как минимум :min цифр.',\n    'missing' => 'Поле :attribute должно отсутствовать.',\n    'missing_if' => 'Поле :attribute должно отсутствовать, когда :other равно :value.',\n    'missing_unless' => 'Поле :attribute должно отсутствовать, если :other не равно :value.',\n    'missing_with' => 'Поле :attribute должно отсутствовать, когда присутствует :values.',\n    'missing_with_all' => 'Поле :attribute должно отсутствовать, когда присутствуют :values.',\n    'multiple_of' => 'Поле :attribute должно быть кратным :value.',\n    'not_in' => 'Выбранное значение :attribute недопустимо.',\n    'not_regex' => 'Неверный формат поля :attribute.',\n    'numeric' => 'Поле :attribute должно быть числом.',\n    'password' => [\n        'letters' => 'Поле :attribute должно содержать хотя бы одну букву.',\n        'mixed' => 'Поле :attribute должно содержать хотя бы одну прописную и одну строчную букву.',\n        'numbers' => 'Поле :attribute должно содержать хотя бы одну цифру.',\n        'symbols' => 'Поле :attribute должно содержать хотя бы один символ.',\n        'uncompromised' =>\n            'Введенный :attribute появился в утечке данных. Пожалуйста, выберите другой :attribute.',\n    ],\n    'present' => 'Поле :attribute должно быть заполнено.',\n    'prohibited' => 'Поле :attribute запрещено.',\n    'prohibited_if' => 'Поле :attribute запрещено, когда :other равно :value.',\n    'prohibited_unless' => 'Поле :attribute запрещено, если :other не входит в :values.',\n    'prohibits' => 'Поле :attribute запрещает наличие :other.',\n    'regex' => 'Неверный формат поля :attribute.',\n    'required' => 'Поле :attribute является обязательным.',\n    'required_array_keys' => 'Поле :attribute должно содержать записи для: :values.',\n    'required_if' => 'Поле :attribute является обязательным, когда :other равно :value.',\n    'required_if_accepted' => 'Поле :attribute является обязательным, когда :other принято.',\n    'required_unless' => 'Поле :attribute является обязательным, если :other не входит в :values.',\n    'required_with' => 'Поле :attribute является обязательным, когда присутствует :values.',\n    'required_with_all' => 'Поле :attribute является обязательным, когда присутствуют все :values.',\n    'required_without' => 'Поле :attribute является обязательным, когда отсутствует :values.',\n    'required_without_all' =>\n        'Поле :attribute является обязательным, когда отсутствуют все :values.',\n    'same' => 'Поле :attribute должно совпадать с :other.',\n    'size' => [\n        'array' => 'Поле :attribute должно содержать :size элементов.',\n        'file' => 'Поле :attribute должно быть :size килобайт.',\n        'numeric' => 'Поле :attribute должно быть :size.',\n        'string' => 'Поле :attribute должно содержать :size символов.',\n    ],\n    'starts_with' => 'Поле :attribute должно начинаться с одного из следующих значений: :values.',\n    'string' => 'Поле :attribute должно быть строкой.',\n    'timezone' => 'Поле :attribute должно быть допустимым часовым поясом.',\n    'unique' => 'Поле :attribute уже занято.',\n    'uploaded' => 'Не удалось загрузить файл :attribute.',\n    'uppercase' => 'Поле :attribute должно быть в верхнем регистре.',\n    'url' => 'Поле :attribute должно быть допустимым URL.',\n    'ulid' => 'Поле :attribute должно быть допустимым ULID.',\n    'uuid' => 'Поле :attribute должно быть допустимым UUID.',\n];\n"
  },
  {
    "path": "lang/zh-Hant/auth.ftl",
    "content": "### Auth\n\n## Laravel strings\n## https://github.com/laravel/framework/blob/master/src/Illuminate/Translation/lang/en/auth.php\n\nfailed = 憑證與我們的記錄不符。\npassword = 提供的密碼不正確。\nthrottle = 登入嘗試過多。請在 { $seconds } 秒後再試。\n\n## Login\n\nlogin-title = 登入您的帳號\nlogin-submit = 登入\nlogin-register-prompt = 沒有帳號嗎？\nlogin-register-link = 註冊\ncontinue-with-provider = 使用 { $provider } 繼續\n\nemail-label = 電子郵件\npassword-label = 密碼\nremember-me-label = 記住我\nforgot-password-link = 忘記密碼？\n\n## Forgot Password\n\nforgot-password-title = 忘記密碼？\nforgot-password-introduction = 輸入您的電子郵件地址以請求重設密碼連結。\nforgot-password-submit = 發送重設密碼連結\n\nreset-password-title = 重設密碼\nnew-password-label = 新密碼\nconfirm-password-label = 確認密碼\nreset-password-submit = 重設密碼\n\nreset-password-mail-subject = 重設您的密碼\nreset-password-mail-body = 我們收到了您在 { $forum } 上的密碼重設請求。如果這不是您的操作，則無需進一步操作。此連結將在 { $minutes } 分鐘後過期。\nreset-password-mail-button = 重設密碼\n\n## Register\n\nregister-title = 建立帳號\nname-label = 使用者名稱\nregister-submit = 建立帳號\nregister-login-prompt = 已經有帳號了嗎？\nregister-login-link = 登入\n\n## Confirm Password\n\nconfirm-password-title = 檢查密碼\nconfirm-password-introduction = 在繼續之前，請重新輸入您的密碼。\nconfirm-password-submit = 確認\n\n## Email Verification\n\nemail-verification-sent-message = 我們已經向 { $email } 發送了一封確認郵件。如果沒有收到，請檢查您的垃圾郵件資料夾。\nemail-verification-resend-button = 重新發送\nemail-verification-required-message = 您必須驗證您的電子郵件地址。\nemail-verification-success-message = 感謝您驗證您的電子郵件！\nemail-verification-mail-subject = 驗證電子郵件地址\nemail-verification-mail-body = 請點擊下面的按鈕驗證您的電子郵件地址。如果您在 { $forum } 上沒有帳號，則無需進一步操作。\nemail-verification-mail-button = 驗證電子郵件地址\n"
  },
  {
    "path": "lang/zh-Hant/cp.ftl",
    "content": "### Localization for Waterhole Control Panel\n\ntitle = 控制台\n\n## Dashboard\n\ndashboard-title = 儀表板\n\nconfigure-mail-message = 您需要配置郵件驅動程序，以便 Waterhole 能夠發送驗證郵件及寄送通知。\ndebug-mode-on-message = 調試模式已啟用。敏感配置值可能會被暴露。\n\ngetting-started-title = 開始使用 Waterhole\ngetting-started-strategy-title = 閱讀文檔\ngetting-started-strategy-description = 學習如何通過 Waterhole 構建一個成功的社區。\ngetting-started-structure-title = 設置您的佈局\ngetting-started-structure-description = 配置構成您社區框架的頻道和頁面。\ngetting-started-groups-title = 定義用戶組\ngetting-started-groups-description = 為版主、工作人員和超級用戶設置用戶組。\ngetting-started-design-title = 加入 Waterhole 社區\ngetting-started-design-description = 提出問題、分享技巧，並了解如何充分利用您的社區。\n\ndashboard-users-title = 用戶\ndashboard-posts-title = 帖子\ndashboard-comments-title = 評論\n\nperiod-today = 今天\nperiod-last-7-days = 最近 7 天\nperiod-last-4-weeks = 最近 4 週\nperiod-last-3-months = 最近 3 個月\nperiod-last-12-months = 最近 12 個月\nperiod-this-month = 本月\nperiod-this-quarter = 本季\nperiod-this-year = 今年\nperiod-all-time = 所有時間\nperiod-current-heading = 當前時期\npreiod-previous-heading = 上一時期\n\n## Structure\n\nstructure-title = 佈局\n\nstructure-channel-label = 頻道\nstructure-page-label = 頁面\nstructure-link-label = 連結\nstructure-heading-label = 標題\nstructure-visibility-public-label = 公開\nstructure-visibility-members-label = 會員\n\nstructure-navigation-title = 導航\nstructure-navigation-description = 將項目移至此處以在導航菜單中顯示。\n\nstructure-unlisted-title = 不列入清單\nstructure-unlisted-description = 將項目移至此處以從導航菜單中隱藏。\n\ndelete-structure-confirm-message = 您確定要刪除此佈局嗎？\n\n## Structure - Heading\n\nedit-heading-title = 編輯標題\ncreate-heading-title = 建立標題\nheading-name-label = 名稱\n\n## Structure - Link\n\nedit-link-title = 編輯連結\ncreate-link-title = 建立連結\nlink-details-title = 詳細資料\nlink-name-label = 名稱\nlink-url-label = 網址\nlink-permissions-title = 權限\n\n## Structure - Page\n\nedit-page-title = 編輯頁面\ncreate-page-title = 建立頁面\npage-details-title = 詳細資料\npage-name-label = 名稱\npage-slug-label = 網址別名\npage-slug-url-label = 此頁面將可透過以下網址存取：\npage-body-label = 內容\npage-permissions-title = 權限\n\n## Structure - Channel\n\nedit-channel-title = 編輯頻道\ncreate-channel-title = 建立頻道\nchannel-details-title = 詳細資料\nchannel-name-label = 名稱\nchannel-slug-label = 網址別名\nchannel-slug-url-label = 此頻道將可透過以下網址存取：\nchannel-description-label = 描述\nchannel-description-description = 簡要描述此頻道的用途。\nchannel-options-title = 選項\nchannel-visibility-label = 可見性\nchannel-ignore-label = 預設為\"忽略\"\nchannel-ignore-description = 對於所有使用者，在動態消息中隱藏此頻道的帖子，除非他們明確追蹤該頻道。\nchannel-layout-title = 顯示式樣\nchannel-layout-label = 格式\nchannel-layout-show-author-label = 顯示發帖者\nchannel-layout-show-excerpt-label = 顯示摘要\nchannel-filters-label = 篩選器\nchannel-custom-filters-label = 使用自訂篩選器\nchannel-custom-filters-description = 覆蓋此頻道的全域篩選器。\nchannel-permissions-title = 權限\nchannel-approval-label = 核准\nchannel-approval-moderators-exempt = 版主不需核准。\nchannel-require-approval-posts-label = 貼文需核准\nchannel-require-approval-comments-label = 評論需核准\nchannel-features-title = 功能\nchannel-reactions-label = 回應\nchannel-reactions-posts-label = 帖子\nchannel-reactions-comments-label = 評論\nreaction-set-picker-default = 預設（{ $name }）\nreaction-set-picker-none = 無\nchannel-taxonomies-label = 標籤\nchannel-answers-label = 答案\nchannel-enable-answers-label = 啟用此頻道的答案功能\nchannel-enable-answers-description = 允許帖子作者將評論標記為答案。\nchannel-posting-title = 發帖\nchannel-instructions-label = 發帖指南\nchannel-instructions-description = 提供給使用者在此頻道中創建帖子時顯示的指示。\nchannel-similar-posts-title = 相似帖子\nchannel-show-similar-posts-label = 根據標題顯示此頻道中的相似帖子\n\ndelete-channel-title = 刪除頻道：\ndelete-channel-posts-label = 刪除 { $count } { $count ->\n    [one] 帖子\n    *[other] 帖子\n}\nmove-to-channel-posts-label = 將 { $count } { $count ->\n    [one] 帖子\n    *[other] 帖子\n} 移至其他頻道\n\n## Groups\n\ngroups-title = 群組\ncreate-group-button = 建立群組\ngroup-user-count = { $count } { $count ->\n    [one] 使用者\n    *[other] 使用者\n}\n\nedit-group-title = 編輯群組\ncreate-group-title = 建立群組\ngroup-details-title = 詳細資訊\ngroup-name-label = 名稱\ngroup-appearance-label = 外觀\ngroup-show-as-badge-label = 以使用者徽章顯示此群組\ngroup-color-label = 顏色\ngroup-icon-label = 圖示\ngroup-rules-title = 參與\ngroup-auto-assign-label = 自動將此群組指派給新成員\ngroup-rules-requires-approval-label = 新成員的內容需核准\ngroup-rules-remove-after-approval-label = 核准後從群組移除\ngroup-permissions-title = 權限\ngroup-global-permissions-title = 全域權限\ngroup-structure-permissions-title = 結構權限\ngroup-permission-suspend-users-label = 允許停權使用者\n\ndelete-group-confirm-message = 確定要刪除這個群組嗎？\n\n## Users\n\nusers-title = 會員用戶\nusers-filter-placeholder = 篩選用戶\nusers-filter-group-description = 依群組篩選\ncreate-user-button = 建立用戶\n\nusers-name-column = 姓名\nusers-email-column = 電子郵件\nusers-groups-column = 群組\nusers-created-at-column = 建立時間\nusers-last-seen-at-column = 最後登入時間\nusers-empty-message = 找不到結果\n\nedit-user-title = 編輯用戶\ncreate-user-title = 建立用戶\nuser-account-title = 帳戶\nuser-name-label = 姓名\nuser-email-label = 電子郵件\nuser-password-label = 密碼\nuser-set-password-label = 設定新密碼\nuser-groups-label = 群組\nuser-profile-title = 個人資料\nuser-created-message = 用戶已建立。\nuser-saved-message = 用戶已儲存。\n\ndelete-user-title = 刪除 { $count ->\n    [one] 用戶：\n    *[other] { $count } 用戶\n}\nkeep-user-content-label = 保留內容並標記為匿名\ndelete-user-content-label = 永久刪除內容\ndelete-user-success-message = 使用者已刪除。\n\n## Reactions\n\nreactions-title = 心情回應\nreaction-sets-title = 表情集\ncreate-reaction-set-button = 建立表情集\nedit-reaction-set-title = 編輯表情集\ncreate-reaction-set-title = 建立表情集\nreaction-set-details-title = 詳細資訊\nreaction-set-name-label = 名稱\nreaction-set-usage-label = 設置\nreaction-set-default-posts = 預設於文章使用\nreaction-set-default-comments = 預設於評論使用\ndelete-reaction-set-confirm-message = 確定要刪除此表情集嗎？\nreaction-set-saved-message = 表情集已儲存\n\nreaction-types-title = 反應類型\nreaction-types-empty-message = 沒有設置表情集\nreaction-types-add-button = 建立\nedit-reaction-type-title = 編輯表情\ncreate-reaction-type-title = 建立表情\nreaction-type-name-label = 名稱\nreaction-type-score-label = 分數\nreaction-type-score-description = 此表情的分數值\ndelete-reaction-type-confirm-message = 確定要刪除此表情嗎？\nreaction-type-saved-message = 表情已儲存\n\n## Taxonomies\n\ntaxonomies-title = 分類法\ncreate-taxonomy-button = 建立分類法\ncreate-taxonomy-title = 建立分類法\nedit-taxonomy-title = 編輯分類法\ntaxonomy-details-title = 詳細資訊\ntaxonomy-permissions-title = 權限\ntaxonomy-tags-title = 標籤\ntaxonomy-name-label = 名稱\ntaxonomy-options-title = 選項\ntaxonomy-required-label = 發表文章時，要求選擇標籤\ntaxonomy-allow-multiple-label = 允許選擇多個標籤\ntaxonomy-saved-message = 分類法已儲存。\ndelete-taxonomy-confirm-message = 確定要刪除此分類法嗎？\n\ncreate-tag-title = 建立標籤\nedit-tag-title = 編輯標籤\ntag-name-label = 名稱\ntag-saved-message = 標籤已儲存。\ndelete-tag-confirm-message = 確定要刪除此標籤嗎？\n\n## Licensing\n\nlicense-error-message = 無法驗證您的許可證，因為與 Waterhole API 通訊時發生錯誤（{ $status }）。\nlicense-invalid-message = 請購買或輸入有效的許可證金鑰，以符合許可協議。\nlicense-expired-message = 您無權使用此版本的 Waterhole。請降級或更新您的許可證。\nlicense-suspended-message = 您的 Waterhole 許可證已被暫停。請聯繫我們瞭解更多訊息。\n\ntrial-badge = 試用版\nlicensed-badge = 已許可\nunlicensed-badge = 未許可\n"
  },
  {
    "path": "lang/zh-Hant/forum.ftl",
    "content": "### Forum\n\nfeed-link = 動態消息\n\n## Header\n\nlog-in = 登入\nregister = 註冊\n\n## Search\n\nsearch-placeholder = 站內文章搜尋\nsearch-results-title = \"{ $query }\" 的搜尋結果\nsearch-button = 搜尋\n\nsearch-filter-button = 篩選\n\nsearch-showing-results-title = 顯示 { $total } { $total ->\n    [one] 個結果\n    *[other] 個結果\n}\n\nsearch-showing-results-non-exhaustive-title = 顯示 { $total }+ { $total ->\n    [one] 個結果\n    *[other] 個結果\n}\n\nsearch-sort-relevance = 相關性排序\nsearch-sort-latest = 最新排序\nsearch-sort-top = 熱門排序\n\nsearch-empty-message = 找不到結果\nsearch-keywords-too-short-message = 您的關鍵字太短了，請嘗試使用更長的關鍵字。\n\n## Posts\n\npost-activity-replied = 回覆於\npost-activity-posted = 發佈於\n\npost-new-badge = 新\npost-new-badge-tooltip = 新的貼文\npost-locked-badge = 已鎖定\npost-answered-badge = 已回答\npost-trash-badge = 已刪除\n\npost-removed-message = 貼文已移除\n\npost-answered-by = 最佳答案\npost-view-answer-link = 查看答案\n\npost-unread-comments-badge-tooltip = { $count } { $count ->\n    [one] 個未讀評論\n    *[other] 個未讀評論\n}\n\npost-comments-heading = { $count } { $count ->\n    [one] 個評論\n    *[other] 個評論\n}\n\nmark-as-read-instruction = 點擊標記為已讀\n\npost-comments-link = { $count } { $count ->\n    [one] 個評論\n    *[other] 個評論\n}\n\nadd-reaction-button = 表達心情\n\nmove-post-title = 移動 { $count ->\n    [one] 個貼文：\n    *[other] { $count } 個貼文\n}\n\nmove-to-channel-button = 移動頻道\nmove-to-channel-confirm-button = 移動\n\nmark-as-read-button = 標記為已讀\n\ncreate-post-button = 發表文章\ncreate-post-title = 新增文章\npost-channel-label = 頻道\npost-submit-button = 發表\nedit-post-title = 編輯文章\nedit-post-link = 編輯文章\npost-title-label = 標題\nsimilar-posts-label = 查看相似文章：\npost-body-label = 內容\n\nchannel-picker-placeholder = 選擇一個頻道\n\ndelete-post-confirm-message = 您確定要刪除此文章嗎？\ndelete-post-success-message = 文章已刪除。\n\noriginal-post-link = 原始主題\n\npin-to-top-button = 置頂\nunpin-button = 取消置頂\n\npost-comment-button = 發表評論\n\nmove-to-trash-button = 移至回收桶\nrestore-button = 還原\ndelete-forever-button = 永久刪除\n\n## Comments\n\ncomments-unread-heading = 未讀\ncomments-unread-link = 未讀\n\ncreate-comment-title = 發表評論\nedit-comment-title = 編輯評論\ncomment-number-title = 評論 #{ $number }\n\ncomment-in-reply-to-link = 回覆給\ncomment-show-replies-button = 顯示 { $count } { $count ->\n    [one] 個回覆\n    *[other] 個回覆\n}\ncomment-reply-button = 回覆\nmark-as-answer-button = 標記為最佳答案\nunmark-as-answer-button = 取消最佳答案\ncomment-answer-badge = 最佳答案\n\ncomments-locked-message = 評論已鎖定。\nlock-comments-button = 鎖定評論\nunlock-comments-button = 解鎖評論\n\ncomposer-placeholder = 撰寫評論…\ncomposer-reply-to-placeholder = 回覆 { $userName }…\ncomposer-replying-to-label = 正在回覆\ncomposer-clear-reply-button = 清除\ncomposer-submit = 發表\n\ndelete-comment-confirm-message = 您確定要刪除此評論嗎？\n\ncomment-removed-message = 評論已移除\ncomment-removed-tooltip = { $user } 已移除 { $timestamp }\n\n## Moderation\n\nremove-button = 移除\nremoved-by-label = 移除者\n\nremoval-reason-label = 移除原因\nremoval-reason-unspecified-label = 未指定\nremoval-message-label = 給作者的訊息\nreport-button = 檢舉\nreport-confirm-button = 提交檢舉\nreport-note-placeholder = 新增備註（選填）\nreport-system-user = 系統\nuser-actions-label = 使用者動作\nuser-actions-suspend-label = 停權 { $user }\nuser-actions-suspend-days = 天\nuser-actions-suspend-weeks = 週\nuser-actions-suspend-indefinitely = 永久\n\napprove-button = 核准\nflag-dismiss-button = 忽略\n\nreport-reason-off-topic-label = 離題\nreport-reason-off-topic-description = 此內容與目前討論無關。\n\nreport-reason-inappropriate-label = 不當\nreport-reason-inappropriate-description = 此內容具冒犯性、辱罵或違反社群規範。\n\nreport-reason-spam-label = 垃圾訊息\nreport-reason-spam-description = 此內容為廣告或惡意破壞。\n\nreport-reason-other-label = 其他\nreport-reason-other-description = 此內容因其他原因需要注意。\n\nreport-reason-approval-label = 等待核准\n\nmoderation-title = 審核\nmoderation-empty-message = 沒有待處理的檢舉\nmoderation-finished-message = 都處理完了。\n\npending-approval-title = 等待核准\n\n## Misc\n\nquote-button = 引用\n\nattribution-timestamp-created-label = 發佈\nattribution-timestamp-edited-label = 已編輯\n\n## Filters\n\nfilter-alphabetical = 字母順序\nfilter-following = 追蹤中\nfilter-ignoring = 已忽略\nfilter-newest = 近期主題\nfilter-latest = 最新評論\nfilter-oldest = 舊文排序\nfilter-top = 熱門討論\nfilter-top-all-time = 無限制\nfilter-top-year = 全年\nfilter-top-quarter = 季度\nfilter-top-month = 本月\nfilter-top-week = 當週\nfilter-top-day = 今日\nfilter-trending = 流行趨勢\nfilter-trash = 回收筒\n\n## Followables\n\nfollow-button = 追蹤\nfollow-button-following = 追蹤中\nfollow-button-ignored = 已忽略\n\nignore-button = 忽略\nunfollow-button = 取消追蹤\nunignore-button = 取消忽略\n\nchannel-follow-description = 此頻道有新貼文時通知我。\npost-follow-description = 此貼文有新留言時通知我。\n\npost-following-badge = 追蹤中\npost-ignored-badge = 已忽略\n\n## Index\n\nmenu-button = 選單\nnavigation-title = 論壇導覽\n\npost-feed-new-activity-button = 新活動\npost-feed-new-activity-heading = 新活動\npost-feed-empty-message = 沒有貼文\npost-feed-controls-layout-heading = 顯示方式\n"
  },
  {
    "path": "lang/zh-Hant/header.ftl",
    "content": ""
  },
  {
    "path": "lang/zh-Hant/install.ftl",
    "content": "### Installation\n\n## Groups\n\ngroup-guest = 訪客\ngroup-member = 會員\ngroup-admin = 管理員\ngroup-moderator = 版主\ngroup-quarantine = 隔離\n\n## Reactions\n\nreaction-set-emoji = 表情符號\nreaction-type-like = 讚\nreaction-type-love = 愛心\nreaction-type-laugh = 笑臉\nreaction-type-wow = 哇\nreaction-type-sad = 傷心\nreaction-type-angry = 生氣\n\nreaction-set-votes = 投票\nreaction-type-upvote = 讚成\n\n## Structure\n\nannouncements-name = 公告\nannouncements-description = 團隊的最新消息和其他更新。\n\nintroductions-name = 自我介紹\nintroductions-description = 是新來的嗎？在這裡介紹一下自己吧！\n\nsupport-name = 技術支援\nsupport-description = 在設定、使用和自訂產品方面需要幫助嗎？\n\nideas-name = 想法\nideas-description = 有什麼想法嗎？我們想聽聽您的意見！\n\nstaff-name = 僅限員工\nstaff-description = 員工討論的私密頻道。\n\nguide-title = 社群指南\nguide-body =\n  歡迎來到 { $forumName }，感謝您的加入！為了讓每個人都能充分參與這個社群，請您閱讀並遵守以下準則。\n\n  - **保持文明** 這是一個透過對話分享知識和興趣的場所。對待每個人，包括 Waterhole 社群之外的人，要友善、耐心和尊重。\n\n  - **禁止人身攻擊** 批評觀點是我們在這裡進行的重要部分，但不能讓它演變成人身攻擊。騷擾和其他排斥行為絕不可接受。\n\n  - **信任善意** 在存在分歧時，試著理解為什麼，始終假設對方有善意。請記住，不同的人對問題有不同的觀點，這是正常的。\n"
  },
  {
    "path": "lang/zh-Hant/notifications.ftl",
    "content": "### Notifications\n\ntitle = 通知\nmark-all-as-read-button = 全部標記為已讀\npreferences-button = 通知偏好設定\nempty-message = 沒有通知\n\n## Unsubscribe\n\nunsubscribe-link = 取消訂閱這些通知\nunsubscribe-success-message = 您已成功取消訂閱這些通知。\nmanage-notification-preferences-link = 管理通知偏好設定\n\n## Mention\n\nmention-description = 提及我或回覆我評論的通知\nmention-title = 在 { $post } 中提及了您\nmention-reason = 您收到此通知是因為您已訂閱提及通知。\nmention-unsubscribe = 取消訂閱提及通知\n\n## Content Approved\n\npost-approved-title = 貼文已核准：{ $post }\ncomment-approved-title = 評論已在 { $post } 中核准\n\n## New Comment\n\nnew-comment-description = 我追蹤的貼文有新評論\nnew-comment-title = { $post } 有新評論\nnew-comment-reason = 您收到此通知是因為您正在追蹤此貼文。\nnew-comment-unsubscribe = 取消追蹤此貼文\n\n## New Post\n\nnew-post-description = 我追蹤的頻道有新貼文\nnew-post-title = { $channel } 中有新貼文：{ $post }\nnew-post-reason = 您收到此通知是因為您正在追蹤此頻道。\nnew-post-unsubscribe = 取消追蹤此頻道\n\n## New Flag\n\nnew-flag-description = 我所管理頻道中的檢舉\nflagged-post-title = 貼文已被檢舉：{ $post }\nflagged-comment-title = 評論已被檢舉於 { $post }\nnew-flag-reason = 您收到此通知是因為您管理某個頻道。\nnew-flag-unsubscribe = 取消訂閱檢舉通知\n\n## Content Removed\n\npost-removed-title = 貼文已移除：{ $post }\ncomment-removed-title = 評論已在 { $post } 中移除\n\n## Common\n\nview-post-button = 查看貼文\nview-comment-button = 查看評論\n"
  },
  {
    "path": "lang/zh-Hant/passwords.php",
    "content": "<?php\n\n// https://github.com/laravel/framework/blob/master/src/Illuminate/Translation/lang/en/passwords.php\n\nreturn [\n    'reset' => '您的密碼已重設。',\n    'sent' => '我們已將密碼重設連結發送到您的電子郵件信箱。',\n    'throttled' => '請稍候再試。',\n    'token' => '此密碼重設令牌無效。',\n    'user' => '我們找不到使用該電子郵件地址的使用者。',\n];\n"
  },
  {
    "path": "lang/zh-Hant/system.ftl",
    "content": "## Accessibility\n\nskip-to-main-content-link = 跳至主要內容\n\n## Errors\n\nfatal-error-heading = 發生錯誤\ntry-again-button = 重試\n\nfatal-error-message = 發生錯誤！請重新載入頁面並重試。\ntoo-many-requests-message = 您的操作速度有點快！請稍作休息，然後再試一次。\nforbidden-message = 您無權執行此操作。\nsession-expired-message = 您的登入已逾時，請重新載入頁面並再試一次。\nvalidation-errors-message = 發生以下錯誤：\n\n## Generic Buttons & Links\n\nsave-changes-button = 儲存變更\ncreate-button = 建立\ncancel-button = 取消\nchange-button = 更改\ncontinue-button = 繼續\nactions-button = 操作\nlearn-more-link = 了解更多\ndelete-button = 刪除\nedit-link = 編輯\ncopy-link-button = 複製連結\ncontrols-button = 選單\nmore-button = 更多\nloading = 載入中\nshow-more-button = 顯示更多\n\n## Post Feed Layouts\n\nlayout-list = 列表\nlayout-cards = 卡片\n\n## Actions\n\nconfirm-action-title = 確認操作\ndelete-confirm-button = 刪除\nlink-copied-message = 連結已複製到剪貼簿。\n\n## Users\n\ndeleted-user = [已註銷]\nuser-list-overflow = 還有 { $count } 人\n\n## Pagination\n\npagination-first-link = 第一頁\npagination-previous-link = 上一頁\npagination-next-link = 下一頁\npagination-last-link = 最新評論\nload-more-button = 載入更多\npage-number-prefix = 頁面\npage-number-heading = 第 { $number } 頁\n\n## Theme Switcher\n\ntheme-button = 主題\ntheme-light = 明亮模式\ntheme-dark = 暗黑模式\ntheme-automatic = 自動\n\n## Text Editor\n\ntext-editor-heading = 標題\ntext-editor-bold = 粗體\ntext-editor-italic = 斜體\ntext-editor-quote = 引用\ntext-editor-code = 代碼\ntext-editor-link = 鏈接\ntext-editor-bulleted-list = 無序列表\ntext-editor-numbered-list = 有序列表\ntext-editor-mention = 提及使用者\ntext-editor-emoji = 插入表情符號\ntext-editor-attachment = 附加檔案\ntext-editor-preview = 預覽\n\n## Icon Picker\n\nicon-field-label = 圖示\nicon-picker-change-button = 變更\nicon-picker-none-option = 無\nicon-picker-emoji-option = 表情符號\nicon-picker-emoji-description = 輸入一個表情符號字符。\nicon-picker-svg-option = SVG 圖示\nicon-picker-svg-description = 輸入從以下安裝的集合中的一個圖示名稱：{ $sets }。\nicon-picker-svg-search-link = 搜尋圖示\nicon-picker-image-option = 圖片\n\n## Abilities\n\nability-view = 檢視\nability-comment = 評論\nability-post = 張貼\nability-moderate = 審核\nability-assign-tags = 指定標籤\n\n## Used in the Waterhole\\compact_number() function\n\ncompact-number-1000 = 0.0K\ncompact-number-10000 = 00K\ncompact-number-100000 = 000K\ncompact-number-1000000 = 0.0M\ncompact-number-10000000 = 00M\ncompact-number-100000000 = 000M\ncompact-number-1000000000 = 0.0B\n"
  },
  {
    "path": "lang/zh-Hant/user.ftl",
    "content": "### User\n\n## Account Settings\n\naccount-settings-title = 帳戶設定\n\ndelete-account-button = 刪除帳戶\ndelete-account-confirmation-title = 您確定要刪除您的帳戶嗎？\ndelete-account-confirmation-description = 您的帳戶資料將被移除。您的貢獻將會保留，但會以匿名方式顯示。此操作無法復原。\ndelete-account-success-message = 您的帳戶已被刪除。\n\n## Notification Preferences\n\nnotification-preferences-title = 通知偏好設定\nnotifications-label = 通知\nnotification-channel-web = 網頁\nnotification-channel-email = 電子郵件\nnotifications-following-label = 追蹤\nfollow-on-comment-label = 在我評論的貼文上自動追蹤\nnotification-preferences-saved-message = 通知偏好設定已儲存。\n\n## Edit Profile\n\nedit-profile-title = 編輯個人檔案\nprofile-saved-message = 個人檔案已儲存。\n\navatar-label = 頭像\nremove-avatar-label = 移除頭像\nheadline-label = 標語\nheadline-description = 用簡短的文字介紹自己，將顯示在您的名稱旁。\nbio-label = 個人簡介\nbio-description = 更詳細地介紹自己，將顯示在您的個人檔案中。\nlocation-label = 位置\nwebsite-label = 網站\nprivacy-title = 隱私\nshow-online-label = 顯示上次上線時間\n\n## Comments\n\nuser-comments-title = { $userName } 的評論\ncomments-empty-message = 沒有評論\n\n## Posts\n\nuser-posts-title = { $userName } 的貼文\nposts-empty-message = 沒有貼文\n\n## User Menu\n\nprofile-link = 個人檔案\npreferences-link = 偏好設定\nadministration-link = 管理\nlog-out-link = 登出\n\n## Profile\n\nuser-joined-text = 加入於\nuser-last-seen-text = 最後上線\nonline-label = 線上\n\n## Sidebar\n\nposts-link = 貼文\ncomments-link = 評論\npreferences-heading = 偏好設定\naccount-settings-link = 帳戶\nedit-profile-link = 個人檔案\nnotification-preferences-link = 通知\n\n## Admin\n\nsuspend-button = 停權\nedit-suspension-button = 編輯停權\nsuspend-user-title = 停權使用者\nnot-suspended-label = 未停權\nsuspended-indefinitely-label = 無限期停權\nsuspended-until-label = 停權至…\nsuspended-badge = 已停權\nsuspended-message = 您的帳戶已被停權。\n\ncopy-impersonation-url-button = 複製模擬登入網址\nimpersonation-url-copied-message = 模擬登入網址已複製，請在新的無痕視窗中開啟。\n"
  },
  {
    "path": "lang/zh-Hant/validation.php",
    "content": "<?php\n\n// https://github.com/laravel/framework/blob/master/src/Illuminate/Translation/lang/en/validation.php\n\nreturn [\n    'accepted' => '必須接受 :attribute 欄位。',\n    'accepted_if' => '當 :other 是 :value 時，必須接受 :attribute 欄位。',\n    'active_url' => ':attribute 欄位必須是有效的網址。',\n    'after' => ':attribute 欄位必須是 :date 之後的日期。',\n    'after_or_equal' => ':attribute 欄位必須是 :date 之後或等於 :date 的日期。',\n    'alpha' => ':attribute 欄位只能包含字母。',\n    'alpha_dash' => ':attribute 欄位只能包含字母、數字、破折號和底線。',\n    'alpha_num' => ':attribute 欄位只能包含字母和數字。',\n    'any_of' => '欄位 :attribute 無效。',\n    'array' => ':attribute 欄位必須是一個陣列。',\n    'ascii' => ':attribute 欄位只能包含單位元組的字母數字字元與符號。',\n    'before' => ':attribute 欄位必須是 :date 之前的日期。',\n    'before_or_equal' => ':attribute 欄位必須是 :date 之前或等於 :date 的日期。',\n\n    'between' => [\n        'array' => ':attribute 欄位必須包含 :min 到 :max 個項目。',\n        'file' => ':attribute 欄位必須在 :min 到 :max KB 之間。',\n        'numeric' => ':attribute 欄位必須在 :min 到 :max 之間。',\n        'string' => ':attribute 欄位必須包含 :min 到 :max 個字元。',\n    ],\n\n    'boolean' => ':attribute 欄位必須是 true 或 false。',\n    'can' => '欄位 :attribute 包含未授權的值。',\n    'confirmed' => ':attribute 欄位的確認不符。',\n    'contains' => '欄位 :attribute 缺少必要的值。',\n    'current_password' => '密碼不正確。',\n    'date' => ':attribute 欄位必須是有效的日期。',\n    'date_equals' => ':attribute 欄位必須是等於 :date 的日期。',\n    'date_format' => ':attribute 欄位必須符合格式 :format。',\n    'decimal' => ':attribute 欄位必須有 :decimal 位小數。',\n    'declined' => ':attribute 欄位必須為拒絕。',\n    'declined_if' => '當 :other 為 :value 時，:attribute 欄位必須為拒絕。',\n    'different' => ':attribute 欄位與 :other 必須不同。',\n    'digits' => ':attribute 欄位必須是 :digits 位數字。',\n    'digits_between' => ':attribute 欄位必須是 :min 到 :max 位數字。',\n    'dimensions' => ':attribute 欄位的圖片尺寸無效。',\n    'distinct' => ':attribute 欄位包含重複的值。',\n    'doesnt_contain' => '欄位 :attribute 不得包含以下任何值：:values。',\n    'doesnt_end_with' => ':attribute 欄位不得以下列任一項結尾：:values。',\n    'doesnt_start_with' => ':attribute 欄位不得以下列任一項開頭：:values。',\n    'email' => ':attribute 欄位必須是有效的電子郵件地址。',\n    'encoding' => '欄位 :attribute 必須使用 :encoding 編碼。',\n    'ends_with' => ':attribute 欄位必須以下列任一項結尾：:values。',\n    'enum' => '所選的 :attribute 無效。',\n    'exists' => '所選的 :attribute 無效。',\n    'extensions' => '欄位 :attribute 必須具有以下副檔名之一：:values。',\n    'file' => ':attribute 欄位必須是一個檔案。',\n    'filled' => ':attribute 欄位必須有值。',\n\n    'gt' => [\n        'array' => ':attribute 欄位必須多於 :value 個項目。',\n        'file' => ':attribute 欄位必須大於 :value KB。',\n        'numeric' => ':attribute 欄位必須大於 :value。',\n        'string' => ':attribute 欄位必須多於 :value 個字元。',\n    ],\n\n    'gte' => [\n        'array' => ':attribute 欄位必須至少有 :value 個項目。',\n        'file' => ':attribute 欄位必須大於或等於 :value KB。',\n        'numeric' => ':attribute 欄位必須大於或等於 :value。',\n        'string' => ':attribute 欄位必須大於或等於 :value 個字元。',\n    ],\n\n    'hex_color' => ':attribute 欄位必須是有效的十六進位顏色。',\n    'image' => ':attribute 欄位必須是圖片。',\n    'in' => '所選的 :attribute 無效。',\n    'in_array' => ':attribute 欄位必須存在於 :other 中。',\n    'in_array_keys' => '欄位 :attribute 必須至少包含以下其中一個鍵值：:values。',\n    'integer' => ':attribute 欄位必須是整數。',\n    'ip' => ':attribute 欄位必須是有效的 IP 位址。',\n    'ipv4' => ':attribute 欄位必須是有效的 IPv4 位址。',\n    'ipv6' => ':attribute 欄位必須是有效的 IPv6 位址。',\n    'json' => ':attribute 欄位必須是有效的 JSON 字串。',\n    'list' => ':attribute 欄位必須是一個清單。',\n    'lowercase' => ':attribute 欄位必須為小寫。',\n\n    'lt' => [\n        'array' => ':attribute 欄位必須少於 :value 個項目。',\n        'file' => ':attribute 欄位必須小於 :value KB。',\n        'numeric' => ':attribute 欄位必須小於 :value。',\n        'string' => ':attribute 欄位必須少於 :value 個字元。',\n    ],\n\n    'lte' => [\n        'array' => ':attribute 欄位不得超過 :value 個項目。',\n        'file' => ':attribute 欄位必須小於或等於 :value KB。',\n        'numeric' => ':attribute 欄位必須小於或等於 :value。',\n        'string' => ':attribute 欄位必須小於或等於 :value 個字元。',\n    ],\n\n    'mac_address' => ':attribute 欄位必須是有效的 MAC 位址。',\n\n    'max' => [\n        'array' => ':attribute 欄位不得超過 :max 個項目。',\n        'file' => ':attribute 欄位不得大於 :max KB。',\n        'numeric' => ':attribute 欄位不得大於 :max。',\n        'string' => ':attribute 欄位不得超過 :max 個字元。',\n    ],\n\n    'max_digits' => ':attribute 欄位不得超過 :max 位數字。',\n    'mimes' => ':attribute 欄位必須是以下類型的檔案：:values。',\n    'mimetypes' => ':attribute 欄位必須是以下 MIME 類型的檔案：:values。',\n\n    'min' => [\n        'array' => ':attribute 欄位至少必須有 :min 個項目。',\n        'file' => ':attribute 欄位至少必須為 :min KB。',\n        'numeric' => ':attribute 欄位至少必須為 :min。',\n        'string' => ':attribute 欄位至少必須為 :min 個字元。',\n    ],\n\n    'min_digits' => ':attribute 欄位至少必須有 :min 位數字。',\n    'missing' => ':attribute 欄位不得出現。',\n    'missing_if' => '當 :other 為 :value 時，:attribute 欄位不得出現。',\n    'missing_unless' => '除非 :other 為 :value，否則 :attribute 欄位不得出現。',\n    'missing_with' => '當 :values 存在時，:attribute 欄位不得出現。',\n    'missing_with_all' => '當 :values 全部存在時，:attribute 欄位不得出現。',\n    'multiple_of' => ':attribute 欄位必須是 :value 的倍數。',\n    'not_in' => '所選的 :attribute 無效。',\n    'not_regex' => ':attribute 欄位的格式無效。',\n    'numeric' => ':attribute 欄位必須是數字。',\n\n    'password' => [\n        'letters' => ':attribute 欄位必須至少包含一個字母。',\n        'mixed' => ':attribute 欄位必須至少包含一個大寫字母與一個小寫字母。',\n        'numbers' => ':attribute 欄位必須至少包含一個數字。',\n        'symbols' => ':attribute 欄位必須至少包含一個符號。',\n        'uncompromised' => '提供的 :attribute 曾出現在資料外洩事件中，請改用其他 :attribute。',\n    ],\n\n    'present' => ':attribute 欄位必須存在。',\n    'present_if' => '當 :other 為 :value 時，:attribute 欄位必須存在。',\n    'present_unless' => '除非 :other 為 :value，否則 :attribute 欄位必須存在。',\n    'present_with' => '當 :values 存在時，:attribute 欄位必須存在。',\n    'present_with_all' => '當 :values 全部存在時，:attribute 欄位必須存在。',\n\n    'prohibited' => ':attribute 欄位被禁止。',\n    'prohibited_if' => '當 :other 為 :value 時，:attribute 欄位被禁止。',\n    'prohibited_if_accepted' => '當 :other 被接受時，:attribute 欄位被禁止。',\n    'prohibited_if_declined' => '當 :other 被拒絕時，:attribute 欄位被禁止。',\n    'prohibited_unless' => '除非 :other 位於 :values 中，否則 :attribute 欄位被禁止。',\n    'prohibits' => ':attribute 欄位禁止 :other 出現。',\n\n    'regex' => ':attribute 欄位的格式無效。',\n    'required' => ':attribute 欄位為必填。',\n    'required_array_keys' => ':attribute 欄位必須包含以下鍵值：:values。',\n    'required_if' => '當 :other 為 :value 時，:attribute 欄位為必填。',\n    'required_if_accepted' => '當 :other 被接受時，:attribute 欄位為必填。',\n    'required_if_declined' => '當 :other 被拒絕時，:attribute 欄位為必填。',\n    'required_unless' => '除非 :other 位於 :values 中，否則 :attribute 欄位為必填。',\n    'required_with' => '當 :values 存在時，:attribute 欄位為必填。',\n    'required_with_all' => '當 :values 全部存在時，:attribute 欄位為必填。',\n    'required_without' => '當 :values 不存在時，:attribute 欄位為必填。',\n    'required_without_all' => '當 :values 全部不存在時，:attribute 欄位為必填。',\n\n    'same' => ':attribute 欄位必須與 :other 相同。',\n\n    'size' => [\n        'array' => ':attribute 欄位必須包含 :size 個項目。',\n        'file' => ':attribute 欄位大小必須為 :size KB。',\n        'numeric' => ':attribute 欄位必須為 :size。',\n        'string' => ':attribute 欄位長度必須為 :size 個字元。',\n    ],\n\n    'starts_with' => ':attribute 欄位必須以下列任一項開頭：:values。',\n    'string' => ':attribute 欄位必須為字串。',\n    'timezone' => ':attribute 欄位必須是有效的時區。',\n    'unique' => ':attribute 已經被使用。',\n    'uploaded' => ':attribute 上傳失敗。',\n    'uppercase' => ':attribute 欄位必須為大寫。',\n    'url' => ':attribute 欄位必須是有效的網址。',\n    'ulid' => ':attribute 欄位必須是有效的 ULID。',\n    'uuid' => ':attribute 欄位必須是有效的 UUID。',\n];\n"
  },
  {
    "path": "package.json",
    "content": "{\n    \"type\": \"module\",\n    \"packageManager\": \"pnpm@10.28.1\",\n    \"scripts\": {\n        \"dev\": \"DEV=1 tsdown\",\n        \"build\": \"tsdown\",\n        \"prettier\": \"prettier --write '**/*' --ignore-unknown\",\n        \"release\": \"release-it\"\n    },\n    \"devDependencies\": {\n        \"@csstools/postcss-global-data\": \"^4.0.0\",\n        \"@dnd-kit/abstract\": \"^0.2.3\",\n        \"@dnd-kit/dom\": \"^0.2.3\",\n        \"@floating-ui/dom\": \"^1.7.4\",\n        \"@github/combobox-nav\": \"^3.0.1\",\n        \"@github/hotkey\": \"^3.1.1\",\n        \"@github/paste-markdown\": \"^1.5.3\",\n        \"@github/relative-time-element\": \"^5.0.0\",\n        \"@github/text-expander-element\": \"^2.9.4\",\n        \"@hotwired/stimulus\": \"^3.2.2\",\n        \"@hotwired/turbo\": \"^8.0.21\",\n        \"@prettier/plugin-php\": \"^0.24.0\",\n        \"@release-it/bumper\": \"^7.0.5\",\n        \"@release-it/keep-a-changelog\": \"^7.0.0\",\n        \"@types/hotwired__turbo\": \"^8.0.5\",\n        \"@types/lodash-es\": \"^4.17.12\",\n        \"@types/node\": \"^25.0.10\",\n        \"animated-scroll-to\": \"^2.3.2\",\n        \"emoji-picker-element\": \"^1.28.1\",\n        \"highlight.js\": \"^11.11.1\",\n        \"inclusive-elements\": \"^0.5.1\",\n        \"ky\": \"^1.14.2\",\n        \"laravel-echo\": \"^2.3.0\",\n        \"lodash-es\": \"^4.17.23\",\n        \"postcss\": \"^8.5.6\",\n        \"postcss-each\": \"^1.1.0\",\n        \"postcss-import\": \"^16.1.1\",\n        \"postcss-mixins\": \"^12.1.2\",\n        \"postcss-preset-env\": \"^11.1.1\",\n        \"postcss-scss\": \"^4.0.9\",\n        \"prettier\": \"^3.8.1\",\n        \"prettier-plugin-blade\": \"^2.1.21\",\n        \"pusher-js\": \"^8.4.0\",\n        \"release-it\": \"^19.2.4\",\n        \"rollup-plugin-postcss\": \"^4.0.2\",\n        \"sticky-observer\": \"^1.0.1\",\n        \"text-field-edit\": \"^4.1.1\",\n        \"textarea-editor\": \"^2.1.1\",\n        \"tsdown\": \"^0.20.1\",\n        \"typescript\": \"^5.9.3\",\n        \"uplot\": \"^1.6.32\",\n        \"vanilla-colorful\": \"^0.7.2\"\n    }\n}\n"
  },
  {
    "path": "phpstan.dist.neon",
    "content": "includes:\n    - ./vendor/larastan/larastan/extension.neon\nparameters:\n    paths:\n        - src/\n    level: 1\n    checkOctaneCompatibility: true\n"
  },
  {
    "path": "phpunit.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:noNamespaceSchemaLocation=\"vendor/phpunit/phpunit/phpunit.xsd\"\n         bootstrap=\"vendor/autoload.php\"\n         colors=\"true\"\n>\n    <testsuites>\n        <testsuite name=\"Feature\">\n            <directory>tests/Feature</directory>\n        </testsuite>\n    </testsuites>\n    <source>\n        <include>\n            <directory>src</directory>\n        </include>\n    </source>\n    <php>\n        <env name=\"APP_ENV\" value=\"testing\"/>\n        <env name=\"APP_KEY\" value=\"AckfSECXIvnK5r28GVIWUAxmbBSjTsmF\"/>\n        <env name=\"APP_MAINTENANCE_DRIVER\" value=\"file\"/>\n        <env name=\"BCRYPT_ROUNDS\" value=\"4\"/>\n        <env name=\"CACHE_STORE\" value=\"array\"/>\n        <env name=\"DB_CONNECTION\" value=\"testing\"/>\n        <env name=\"DB_DATABASE\" value=\":memory:\"/>\n        <env name=\"DB_FOREIGN_KEYS\" value=\"true\"/>\n        <env name=\"MAIL_MAILER\" value=\"array\"/>\n        <env name=\"PULSE_ENABLED\" value=\"false\"/>\n        <env name=\"QUEUE_CONNECTION\" value=\"sync\"/>\n        <env name=\"SESSION_DRIVER\" value=\"array\"/>\n        <env name=\"TELESCOPE_ENABLED\" value=\"false\"/>\n    </php>\n</phpunit>\n"
  },
  {
    "path": "postcss.config.cjs",
    "content": "module.exports = {\n    parser: 'postcss-scss', // for single-line comment support\n    plugins: [\n        require('postcss-import'),\n        require('@csstools/postcss-global-data')({\n            files: ['./resources/css/system/breakpoints.css'],\n        }),\n        require('postcss-each'),\n        require('postcss-mixins')({\n            mixinsFiles: ['./resources/css/system/mixins.css'],\n        }),\n        require('postcss-preset-env')({\n            stage: 2,\n            features: {\n                'nesting-rules': true, // Stage 1\n            },\n        }),\n    ],\n};\n"
  },
  {
    "path": "resources/css/cp/_dashboard.css",
    "content": ".cp-dashboard__widgets {\n  display: flex;\n  flex-wrap: wrap;\n  align-items: stretch;\n  margin: calc(-1 * var(--space-lg) / 2);\n\n  > * {\n    width: var(--cp-dashboard-widget-width, 100%);\n    height: var(--cp-dashboard-widget-height, auto);\n    padding: calc(var(--space-lg) / 2);\n    min-width: 30ch;\n    flex-grow: 1;\n  }\n\n  & turbo-frame,\n  .card {\n    height: 100%;\n  }\n}\n\n.line-chart-widget {\n  min-height: 14em;\n}\n\n.getting-started__grid {\n  --grid-min: 35ch;\n}\n"
  },
  {
    "path": "resources/css/cp/_permission-grid.css",
    "content": ".permission-grid {\n  & thead th {\n    width: 9ch;\n    padding-inline: var(--space-xxs);\n    text-align: center;\n    vertical-align: top;\n  }\n}\n"
  },
  {
    "path": "resources/css/cp/_structure.css",
    "content": ".cp-structure {\n  padding: 0;\n  overflow: hidden;\n\n  & li {\n    border-top: 1px solid var(--color-stroke);\n  }\n\n  > :first-child {\n    border-top: 0;\n  }\n\n  .placeholder:not(:only-child) {\n    display: none;\n  }\n}\n\n.cp-structure__node {\n  list-style: none;\n}\n\n.drag-handle {\n  display: flex;\n  color: var(--color-muted);\n  cursor: move;\n\n  > * {\n    opacity: 0.5;\n  }\n\n  .no-js & {\n    display: none;\n  }\n}\n\n[data-dnd-dragging='true'] {\n  border: 0 !important;\n  background: var(--color-bg);\n  border-radius: var(--radius);\n  box-shadow: var(--shadow-md);\n}\n\n.cp-structure__label {\n  font-weight: var(--weight-medium);\n}\n"
  },
  {
    "path": "resources/css/cp/app.css",
    "content": "@import '_dashboard.css';\n@import '_permission-grid.css';\n@import '_structure.css';\n\n.container {\n  max-width: none;\n}\n\n.cp__content {\n  max-width: 110ch;\n  margin-inline: auto;\n}\n\n.cp-title {\n  margin-bottom: var(--space-lg);\n}\n\n.sortable > * {\n  transition: transform 0.3s;\n}\n\n.cp-help {\n  text-align: center;\n  font-size: var(--text-xs);\n  margin-top: var(--space-xl);\n}\n"
  },
  {
    "path": "resources/css/global/_auth.css",
    "content": ".auth-button[data-provider='facebook'] {\n  background-color: #4267b2;\n  color: #fff;\n}\n\n.auth-button[data-provider^='twitter'] {\n  background-color: #1da1f2;\n  color: #fff;\n}\n\n.auth-button[data-provider='linkedin'] {\n  background-color: #0077b5;\n  color: #fff;\n}\n\n.auth-button[data-provider='google'] {\n  background-color: #fff;\n  color: #555;\n  border: 1px solid var(--color-stroke);\n}\n\n.auth-button[data-provider='github'] {\n  background-color: #333;\n  color: #fff;\n}\n\n.auth-button[data-provider='gitlab'] {\n  background-color: #e24329;\n  color: #fff;\n}\n\n.auth-button[data-provider='bitbucket'] {\n  background-color: #205081;\n  color: #fff;\n}\n"
  },
  {
    "path": "resources/css/global/_channel-card.css",
    "content": ".channel-card__inner {\n  flex-basis: 30ch;\n}\n\n.channel-card__info {\n  flex-basis: 30ch;\n}\n"
  },
  {
    "path": "resources/css/global/_comment.css",
    "content": ".comment {\n  --comment-padding: var(--space-lg);\n  --comment-padding-left: var(--comment-padding);\n\n  padding: var(--comment-padding);\n  padding-left: var(--comment-padding-left);\n  transition: background 0.2s;\n\n  @media (--md-up) {\n    --comment-padding-left: calc(\n      var(--attribution-avatar-size) + var(--space-md) + var(--comment-padding)\n    );\n\n    .attribution {\n      padding-left: 0;\n    }\n\n    .attribution__info {\n      display: inline;\n      margin-left: var(--space-xxs);\n    }\n  }\n\n  &.is-highlighted {\n    background: var(--color-warning-soft);\n  }\n\n  &.is-answer {\n    border: 2px solid var(--color-success);\n    box-shadow: inset 0 0 10px 2px var(--color-success-soft);\n    border-radius: var(--radius);\n  }\n}\n\n.comment.is-removed {\n  padding-top: var(--space-md);\n  background: var(--palette-fill);\n\n  &.is-expanded > .removed-banner {\n    margin-bottom: var(--space-lg);\n\n    .icon-tabler-chevron-right {\n      transform: rotate(90deg);\n    }\n  }\n\n  &:not(.is-expanded) {\n    padding-bottom: var(--space-md);\n\n    > .removed-banner ~ * {\n      display: none;\n    }\n  }\n}\n\n@media (--sm) {\n  .comment.is-removed:not(.is-expanded) .removed-banner__attribution {\n    display: none;\n  }\n}\n\n@media (--md-up) {\n  .comment__icon {\n    position: absolute;\n    margin-left: calc(-1 * (var(--attribution-avatar-size) + var(--space-md)));\n    width: var(--attribution-avatar-size);\n    text-align: right;\n  }\n}\n\n// The \"in reply to\" bit in the comment header\n.comment__parent {\n  margin-top: var(--space-xs);\n  width: fit-content;\n\n  > a {\n    color: var(--color-muted);\n    font-size: var(--text-xs);\n    font-weight: var(--weight-medium);\n  }\n}\n\n.comment__parent-tooltip {\n  max-width: 80ch;\n  overflow: hidden;\n  text-align: left;\n  padding: 0;\n\n  .comment {\n    --comment-padding: var(--space-md);\n  }\n\n  .comment > * + * {\n    margin-top: var(--space-xs);\n  }\n}\n\n.comment__replies {\n  margin-top: var(--space-md);\n\n  .comment__parent {\n    display: none;\n  }\n\n  .comment {\n    --comment-padding: var(--space-md);\n  }\n}\n\n.comment-list {\n  .card__row {\n    padding: 0;\n  }\n\n  #page_1 + .card__row {\n    border-top: 0;\n  }\n}\n"
  },
  {
    "path": "resources/css/global/_composer.css",
    "content": ".composer {\n  --height-transition-duration: 0s;\n\n  border-radius: var(--radius);\n  background: var(--color-bg);\n  transition:\n    box-shadow 0.2s,\n    border 0.2s,\n    height var(--height-transition-duration),\n    bottom 0.2s;\n  max-height: calc(100vh - var(--header-height));\n  position: sticky;\n  bottom: calc(-1 * var(--radius));\n\n  // The composer is \"static\" (cannot be opened/closed) on a standalone comment page\n  &.is-static {\n    position: static;\n    height: 70vh !important;\n\n    .composer__close {\n      visibility: hidden;\n      width: var(--space-xs);\n    }\n  }\n\n  &:not(.is-open) {\n    --height-transition-duration: 0.2s;\n    height: 4.75em !important; // override user resizing\n    bottom: -4.75em;\n\n    .composer__form {\n      display: none;\n    }\n  }\n\n  &.is-open {\n    --color-bg: var(--palette-surface);\n    --focus-shadow: 0 0 0 transparent;\n    height: 300px;\n    min-height: 200px;\n    box-shadow: var(--focus-shadow), var(--shadow-md);\n    padding-bottom: var(--radius);\n    z-index: calc(var(--z-index-header) + 1);\n\n    &:focus-within {\n      --focus-shadow: 0 0 0 2px var(--color-accent);\n    }\n\n    .composer__placeholder {\n      display: none;\n    }\n  }\n}\n\n@media (--sm) {\n  .composer.is-open {\n    margin-inline: calc(-1 * var(--space-gutter));\n    border-radius: 0;\n  }\n}\n\n.composer__placeholder {\n  border: 2px dashed var(--color-stroke);\n  border-radius: var(--radius);\n  padding: var(--space-md);\n  cursor: text;\n  transition:\n    box-shadow 0.2s,\n    border 0.2s,\n    color 0.2s;\n  text-decoration: none !important;\n\n  &:hover {\n    box-shadow: var(--shadow-md);\n    border-color: transparent;\n    color: inherit;\n  }\n\n  .avatar {\n    width: var(--control-height);\n  }\n}\n\n.composer__form {\n  .text-editor {\n    min-height: 0;\n    border: 0;\n    box-shadow: none;\n  }\n\n  .text-editor__input {\n    resize: none;\n  }\n}\n\n.composer__handle {\n  position: absolute;\n  left: 100px;\n  right: 100px;\n  top: -5px;\n  height: 20px;\n  cursor: ns-resize;\n  touch-action: none;\n\n  &::before {\n    content: '';\n    position: absolute;\n    top: 10px;\n    left: 50%;\n    transform: translateX(-50%);\n    width: 20%;\n    height: 5px;\n    border-radius: var(--radius);\n    background: var(--color-fill);\n  }\n}\n\n.composer__toolbar {\n  padding: var(--space-xxs);\n  border-bottom: 1px solid var(--color-stroke);\n}\n\n// The \"replying to\" bit\n.composer__parent {\n  padding-left: var(--space-sm);\n}\n"
  },
  {
    "path": "resources/css/global/_emoji.css",
    "content": ".emoji {\n  display: inline-block;\n}\n\nspan.emoji {\n  transform: scale(1.2);\n}\n\nimg.emoji {\n  height: 1.2em;\n  width: 1.2em;\n  vertical-align: -0.2em;\n}\n\n.icon .emoji {\n  width: 100%;\n  height: 100%;\n  vertical-align: 0;\n}\n"
  },
  {
    "path": "resources/css/global/_flags.css",
    "content": ".flag-container {\n  border: 2px solid var(--color-activity);\n  box-shadow: inset 0 0 10px 2px var(--color-activity-soft);\n  border-radius: var(--radius) !important;\n  overflow: clip;\n\n  > .alert {\n    border-radius: 0;\n  }\n}\n\n.flags-menu {\n  max-width: 50ch;\n}\n"
  },
  {
    "path": "resources/css/global/_header.css",
    "content": ".header {\n  --color-bg: var(--palette-surface);\n\n  position: sticky;\n  top: -1px; // ensures that .is-stuck only applies when scrolled down\n  z-index: var(--z-index-header);\n  transition: box-shadow 0.2s;\n  background: var(--color-bg);\n  box-shadow: var(--shadow-sm);\n\n  .container {\n    height: var(--header-height);\n  }\n}\n\n.header-breadcrumb {\n  color: var(--color-muted);\n  transition:\n    opacity 0.2s,\n    transform 0.2s;\n  margin-left: var(--space-xs);\n  flex-shrink: 99;\n\n  &::before {\n    content: '\\203A';\n    margin: 0 var(--space-xs);\n  }\n\n  &[hidden] {\n    display: block !important;\n    visibility: hidden;\n    transform: translateY(10px);\n    opacity: 0;\n    transition:\n      opacity 0.2s,\n      transform 0.2s,\n      visibility 0s 0.2s;\n  }\n}\n\n.header-search__form {\n  margin-right: var(--space-xs);\n\n  input {\n    width: 30ch;\n  }\n}\n\n.header-user {\n  margin-left: var(--space-xs);\n}\n\n.email-verification-banner {\n  padding-block: var(--space-md);\n}\n"
  },
  {
    "path": "resources/css/global/_index.css",
    "content": "// When the sidebar becomes horizontal, move the create post button to the right side.\n@media (--md-down) {\n  .index-sidebar .index-create-post {\n    margin-left: auto;\n  }\n}\n\n.index-footer {\n  margin-top: var(--space-lg);\n}\n"
  },
  {
    "path": "resources/css/global/_notifications.css",
    "content": ".notifications-menu,\n.moderation-menu {\n  width: 55ch;\n  max-width: 100%;\n  max-height: 40em;\n\n  .menu-item {\n    margin-top: 2px;\n  }\n}\n\n.notification {\n  & strong {\n    font-weight: var(--weight-medium);\n    color: var(--color-accent-text);\n  }\n\n  &.is-unread {\n    background-color: var(--color-bg);\n    background-image: linear-gradient(\n      var(--color-activity-soft),\n      var(--color-activity-soft)\n    );\n\n    > .icon,\n    .notification__time,\n    strong {\n      color: var(--color-activity-text);\n    }\n  }\n}\n\n.notification-grid {\n  .choice {\n    width: 8ch;\n  }\n}\n\n.alert--notification {\n  background-color: var(--color-bg);\n  background-image: linear-gradient(\n    var(--color-activity-soft),\n    var(--color-activity-soft)\n  );\n\n  .alert__message {\n    margin: calc(-1 * var(--space-xs));\n    line-height: unset;\n  }\n\n  .notification__time {\n    display: none;\n  }\n}\n"
  },
  {
    "path": "resources/css/global/_post-feed.css",
    "content": ".post-feed__pinned {\n  --grid-min: 31ch;\n}\n\n.post-feed__refresh {\n  position: sticky;\n  top: calc(var(--header-height) + var(--space-md));\n\n  > * {\n    position: absolute;\n    left: 50%;\n    transform: translateX(-50%);\n  }\n\n  .btn {\n    box-shadow: var(--shadow-md);\n  }\n}\n\n.post-list > :first-child > .post-list-item:first-of-type {\n  border-top: 0;\n}\n\n.post-list-item {\n  padding: var(--space-md);\n}\n\n@media (--md-up) {\n  .post-list-item {\n    padding-left: var(--space-lg);\n  }\n}\n\n.post-list-item__avatar {\n  width: 2.5rem;\n  text-decoration: none !important;\n  flex-shrink: 0;\n}\n\n.post-list-item__avatar,\n.post-list-item__end,\n.post-list-item__controls {\n  margin-top: 3px;\n}\n\n@media (--sm) {\n  .post-list-item__main {\n    width: 100%;\n  }\n\n  .post-list-item__upper {\n    flex-wrap: wrap;\n    gap: var(--space-xs);\n  }\n\n  .post-list-item__end {\n    flex-direction: row-reverse;\n  }\n}\n\n.post-title-link {\n  padding: 10px 0;\n  margin: -10px 0;\n  display: block;\n  color: inherit;\n\n  .not-logged-in &:visited,\n  .is-read & {\n    color: var(--color-muted);\n  }\n}\n\n.is-read .post-list-item__unread {\n  display: none;\n}\n\n.post-cards {\n  .post-card {\n    margin-bottom: var(--space-lg);\n  }\n}\n\n// Hide the channel label from posts when we're viewing a specific channel\n[data-route='waterhole.channels.show'] .post-feed__content .channel-label {\n  display: none;\n}\n"
  },
  {
    "path": "resources/css/global/_post-page.css",
    "content": ".post-header {\n  .post-tags-summary {\n    .tag {\n      font-size: var(--text-sm);\n    }\n  }\n\n  .attribution,\n  .post-title {\n    width: 100%;\n  }\n}\n\n.post-page {\n  .sidebar {\n    flex-basis: 16ch;\n  }\n}\n\n#comments {\n  scroll-margin-top: calc(var(--space-lg) + var(--header-height));\n}\n\n.comments-pagination__pages {\n  max-height: 20rem;\n}\n"
  },
  {
    "path": "resources/css/global/_reactions.css",
    "content": ".reactions-menu {\n  border-radius: 999px;\n  overflow: visible;\n  min-width: 0;\n  max-width: none;\n  display: flex;\n  flex-direction: row;\n\n  & button {\n    transition: transform 0.2s;\n    transform-origin: bottom;\n    padding: 2px;\n\n    &:hover {\n      transform: scale(1.2);\n    }\n  }\n}\n\n.reactions-condensed {\n  .icon {\n    position: relative;\n    margin-left: -4px;\n    filter: drop-shadow(1px 0 0 var(--color-bg));\n  }\n\n  &[data-count='0'] {\n    display: none;\n  }\n}\n\n.reaction[data-count='0'] .icon {\n  filter: grayscale(0.9);\n  opacity: 0.3;\n}\n"
  },
  {
    "path": "resources/css/global/_search.css",
    "content": ".search-results .post-list-item__title a:visited {\n  color: var(--color-muted);\n}\n"
  },
  {
    "path": "resources/css/global/_text-editor.css",
    "content": ".text-editor {\n  padding: 0;\n  height: auto;\n  min-height: 15em;\n}\n\n.text-editor__toolbar {\n  margin: var(--space-xxs);\n  margin-bottom: 0;\n\n  .is-previewing & {\n    z-index: 1;\n    pointer-events: none;\n\n    > * {\n      pointer-events: all;\n    }\n\n    > :not(.text-editor__preview-button) {\n      display: none;\n    }\n  }\n}\n\n.text-editor__expander {\n  position: relative;\n}\n\n.text-editor__input {\n  width: 100%;\n  outline: none;\n  padding: var(--space-md);\n  padding-top: var(--space-xs);\n  resize: vertical;\n  border: 0;\n  background: transparent;\n  max-height: 70vh;\n  position: relative;\n  box-shadow: none !important;\n\n  &[hidden] {\n    display: initial !important;\n    visibility: hidden;\n  }\n}\n\n.text-editor__preview {\n  overflow: auto;\n  padding: var(--space-md);\n}\n"
  },
  {
    "path": "resources/css/global/_user-profile.css",
    "content": ".user-profile__card {\n  .avatar {\n    width: 12ch;\n  }\n}\n\n.user-profile__content {\n  flex-basis: 60ch;\n}\n\n.user-profile__controls {\n  float: right;\n  margin-left: -100%;\n  position: relative;\n}\n"
  },
  {
    "path": "resources/css/global/app.css",
    "content": "@import '../system/system.css';\n\n@import '_auth.css';\n@import '_channel-card.css';\n@import '_comment.css';\n@import '_composer.css';\n@import '_emoji.css';\n@import '_flags.css';\n@import '_header.css';\n@import '_index.css';\n@import '_notifications.css';\n@import '_post-feed.css';\n@import '_post-page.css';\n@import '_reactions.css';\n@import '_search.css';\n@import '_text-editor.css';\n@import '_user-profile.css';\n"
  },
  {
    "path": "resources/css/system/_alert.css",
    "content": ".alert {\n  border-radius: var(--radius);\n  padding: var(--space-sm) var(--space-md);\n  background: var(--color-fill);\n  display: flex;\n  align-items: flex-start;\n  font-size: var(--text-xs);\n  gap: var(--space-sm);\n  line-height: var(--line-height-expanded);\n}\n\n.alert__icon {\n  display: flex;\n  font-size: var(--text-md);\n  line-height: var(--line-height-default);\n}\n\n.alert__message {\n  min-width: 0;\n  flex-grow: 1;\n}\n\n.alert__actions {\n  display: flex;\n  align-items: center;\n  margin-block: calc(-1 * var(--space-xs));\n  margin-right: calc(-1 * var(--space-sm));\n  margin-left: auto;\n  gap: var(--space-sm);\n}\n"
  },
  {
    "path": "resources/css/system/_alerts.css",
    "content": ".alerts {\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  gap: var(--space-sm);\n  padding: var(--space-sm);\n  pointer-events: none;\n  position: fixed;\n  top: 0;\n  left: 0;\n  right: 0;\n  z-index: var(--z-index-alerts);\n\n  > * {\n    pointer-events: auto;\n    box-shadow: var(--shadow-md);\n  }\n\n  @media (--sm) {\n    align-items: stretch;\n    top: auto;\n    bottom: 0;\n  }\n\n  @media (--md-up) {\n    > * {\n      max-width: 50ch;\n    }\n  }\n}\n"
  },
  {
    "path": "resources/css/system/_attribution.css",
    "content": ":root {\n  --attribution-avatar-size: 2.5em;\n}\n\n.attribution {\n  padding-left: calc(var(--attribution-avatar-size) + var(--space-md));\n\n  .avatar {\n    position: absolute;\n    margin-left: calc(-1 * (var(--attribution-avatar-size) + var(--space-md)));\n    width: var(--attribution-avatar-size);\n    height: var(--attribution-avatar-size);\n  }\n}\n\n.attribution__link {\n  color: inherit;\n  font-weight: var(--weight-medium);\n  text-decoration: none !important;\n  margin-right: var(--space-xxs);\n\n  &:hover .attribution__name {\n    text-decoration: underline;\n  }\n}\n\n.attribution__info {\n  display: block;\n  font-size: var(--text-xs);\n  color: var(--color-muted);\n  margin-top: var(--space-xxs);\n\n  > * + *::before {\n    content: ' \\00B7 ';\n  }\n}\n"
  },
  {
    "path": "resources/css/system/_avatar.css",
    "content": ".avatar {\n  border-radius: 100%;\n  background: var(--color-stroke);\n  width: 100%;\n  height: 100%;\n  aspect-ratio: 1;\n\n  & text {\n    font-size: 50px;\n    fill: var(--color-bg);\n  }\n}\n"
  },
  {
    "path": "resources/css/system/_badge.css",
    "content": ".badge,\n.tag {\n  border-radius: 999px;\n  background: var(--color-fill);\n  color: var(--color-muted);\n  border: 0;\n  margin: 0;\n  font-size: var(--text-xxs);\n  font-weight: var(--weight-medium);\n  line-height: 1.2;\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  min-width: 2.5ch;\n  min-height: 1.7em;\n  text-align: center;\n  padding: 0 0.6em;\n  vertical-align: middle;\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  flex-shrink: 0;\n\n  > .icon:first-child {\n    margin-left: -0.2em;\n    margin-right: 0.2em;\n  }\n\n  .icon {\n    stroke-width: 2;\n  }\n}\n\n.group-badge {\n  background-color: var(--group-color, var(--color-fill));\n  color: var(--group-color-constrast, var(--color-muted));\n}\n\n.badge--hidden {\n  border: 1px dashed var(--color-stroke);\n  background-color: transparent;\n}\n\n.suspended-badge {\n  background: var(--color-muted);\n  color: var(--color-bg);\n}\n\n.tag {\n  border-radius: 4px;\n  font-weight: normal;\n  padding: 0 0.5em;\n  font-size: var(--text-xs);\n}\n"
  },
  {
    "path": "resources/css/system/_base.css",
    "content": ":root {\n  scroll-behavior: auto;\n}\n\nhtml {\n  background: var(--color-bg);\n  color: var(--color-text);\n  font-family: var(--font-text);\n  font-size: var(--text-sm);\n  line-height: var(--line-height-default);\n\n  @media (--sm) {\n    font-size: 95%;\n  }\n}\n\ninput,\ntextarea,\nselect {\n  color: var(--color-text);\n}\n\n* {\n  overflow-wrap: anywhere;\n  -webkit-tap-highlight-color: transparent;\n}\n\na,\n.link {\n  color: var(--color-accent-text);\n  text-decoration: none;\n  cursor: pointer;\n\n  &:hover {\n    text-decoration: underline;\n  }\n}\n\nhr {\n  border: 0;\n  border-top: 1px solid var(--color-stroke);\n  margin: var(--space-xl) 0;\n}\n\nstrong {\n  font-weight: var(--weight-bold);\n}\n\nmark {\n  background: transparent;\n  font-weight: var(--weight-bold);\n  color: var(--color-accent-text);\n}\n\npre,\ncode {\n  font-family: var(--font-mono);\n}\n\n:focus {\n  outline: 2px solid var(--color-accent);\n  outline-offset: 2px;\n}\n\n[tabindex='-1']:focus {\n  outline: none;\n}\n\n:focus:not(:focus-visible) {\n  outline: none;\n}\n\n.waterhole {\n  display: flex;\n  flex-direction: column;\n  align-items: stretch;\n  min-height: 100vh;\n  min-height: 100svh;\n}\n\n.waterhole__main {\n  flex-grow: 1;\n  display: flex;\n  flex-direction: column;\n\n  // Bug on Chrome 121: textarea with scroll-margin-top scrolls the\n  // viewport when selecting text\n  *:not(textarea) {\n    scroll-margin-top: var(--header-height);\n  }\n}\n\n@media (prefers-reduced-motion: no-preference) {\n  ui-popup > .enter-active,\n  ui-popup > .leave-active {\n    transition:\n      transform 0.15s,\n      opacity 0.15s;\n  }\n\n  ui-popup > .enter-from,\n  ui-popup > .leave-to {\n    transform: scale(0);\n    opacity: 0;\n  }\n\n  ui-popup::part(backdrop),\n  ui-popup > :nth-child(2) {\n    z-index: var(--z-index-overlay);\n  }\n}\n\n[data-placement='bottom'] {\n  transform-origin: top center;\n}\n[data-placement='bottom-start'] {\n  transform-origin: top left;\n}\n[data-placement='bottom-end'] {\n  transform-origin: top right;\n}\n[data-placement='top'] {\n  transform-origin: bottom center;\n}\n[data-placement='top-start'] {\n  transform-origin: bottom left;\n}\n[data-placement='top-end'] {\n  transform-origin: bottom right;\n}\n[data-placement='left'] {\n  transform-origin: right center;\n}\n[data-placement='left-start'] {\n  transform-origin: right top;\n}\n[data-placement='left-end'] {\n  transform-origin: right bottom;\n}\n[data-placement='right'] {\n  transform-origin: left center;\n}\n[data-placement='right-start'] {\n  transform-origin: left top;\n}\n[data-placement='right-end'] {\n  transform-origin: left bottom;\n}\n\n[data-placement^='top'] {\n  margin-top: calc(-1 * var(--space-xs));\n}\n[data-placement^='bottom'] {\n  margin-top: var(--space-xs);\n}\n[data-placement^='left'] {\n  margin-left: calc(-1 * var(--space-xs));\n}\n[data-placement^='right'] {\n  margin-left: var(--space-xs);\n}\n"
  },
  {
    "path": "resources/css/system/_block-link.css",
    "content": ".block-link {\n  display: block;\n  color: inherit;\n  text-decoration: none !important;\n  border-radius: var(--radius);\n  padding: var(--space-md);\n\n  &:hover {\n    background: var(--color-fill);\n  }\n\n  &:active {\n    filter: var(--filter-active);\n  }\n}\n"
  },
  {
    "path": "resources/css/system/_breadcrumb.css",
    "content": ".breadcrumb {\n  list-style: none;\n  padding: 0;\n  color: var(--color-muted);\n\n  > * {\n    display: inline;\n  }\n\n  > * + ::before {\n    content: '\\203A';\n    margin: 0 var(--space-xxs);\n    color: var(--color-muted);\n  }\n\n  [aria-current='page'] {\n    color: var(--color-text);\n  }\n}\n"
  },
  {
    "path": "resources/css/system/_btn.css",
    "content": ".btn {\n  --btn-height: var(--control-height);\n  --btn-padding: calc(0.4 * var(--btn-height));\n\n  height: var(--btn-height);\n  background: var(--color-fill);\n  color: var(--color-text);\n  border-radius: var(--control-radius);\n  border: 0;\n  margin: 0;\n  display: inline-flex;\n  gap: var(--space-xxs);\n  align-items: center;\n  justify-content: center;\n  padding: 0 var(--btn-padding);\n  text-decoration: none !important;\n  font-weight: var(--weight-medium);\n  white-space: nowrap;\n  vertical-align: middle;\n  flex-shrink: 0;\n\n  @mixin clickable;\n}\n\n// Outline\n\n.btn--outline {\n  border: 1px solid var(--color-stroke);\n  background: transparent;\n  color: var(--color-muted);\n\n  &:hover:not(:disabled, .is-disabled, .is-inert) {\n    background: var(--color-bg);\n  }\n}\n\n// Transparent\n\n:where(:not([open]))\n  > .btn--transparent:where(:not(:hover, .is-hovered, :focus, .is-focused)),\n.btn--transparent:is(:disabled, .is-disabled),\n.btn--transparent:not(button, a, [role='button'], [role='link']) {\n  background: transparent;\n  color: var(--color-muted);\n}\n\n// Sizes\n\n.btn--sm {\n  --btn-height: var(--control-height-small);\n  --btn-padding: calc(0.3 * var(--btn-height));\n\n  font-size: 87.5%;\n}\n\n.btn--narrow {\n  --btn-padding: calc(0.3 * var(--btn-height));\n}\n\n.btn--wide {\n  --btn-padding: calc(0.5 * var(--btn-height));\n}\n\n.btn--start {\n  margin-left: calc(-1 * var(--btn-padding));\n}\n\n.btn--end {\n  margin-right: calc(-1 * var(--btn-padding));\n}\n\n// Icon button\n\n.btn--icon {\n  width: var(--btn-height);\n  padding: 0;\n  border-radius: 100px;\n  position: relative;\n\n  .avatar {\n    width: 100%;\n    height: 100%;\n  }\n\n  .icon {\n    font-size: 120%;\n  }\n\n  .badge {\n    position: absolute;\n    top: 0;\n    right: 0;\n  }\n\n  .label {\n    // .visually-hidden\n    clip: rect(0 0 0 0);\n    clip-path: inset(50%);\n    height: 1px;\n    overflow: hidden;\n    position: absolute;\n    white-space: nowrap;\n    width: 1px;\n  }\n}\n\n// Active state\n\n.btn {\n  &.is-active,\n  &[aria-pressed='true'],\n  :checked + & {\n    color: var(--color-accent-text) !important;\n    background: var(--color-accent-soft) !important;\n    border-color: transparent;\n  }\n}\n\n// Button group\n\n.btn-group {\n  display: flex;\n  align-items: stretch;\n  gap: 1px;\n\n  > :first-child:not(:only-child) {\n    &,\n    > .btn {\n      border-top-right-radius: 0;\n      border-bottom-right-radius: 0;\n    }\n  }\n\n  > :not(:first-child, :last-child) {\n    &,\n    > .btn {\n      border-radius: 0;\n    }\n  }\n\n  > :last-child:not(:only-child) {\n    &,\n    > .btn {\n      border-top-left-radius: 0;\n      border-bottom-left-radius: 0;\n    }\n  }\n}\n"
  },
  {
    "path": "resources/css/system/_card.css",
    "content": ".card {\n  --color-bg: var(--palette-surface);\n\n  border-radius: var(--radius);\n  background: var(--color-bg);\n  box-shadow: var(--shadow-sm);\n  overflow: clip;\n}\n\n.card .card,\n.dialog .card {\n  border: 1px solid var(--color-fill);\n}\n\n.card__header {\n  padding: var(--space-sm) var(--space-md);\n  border-radius: var(--radius) var(--radius) 0 0;\n  background: var(--color-bg);\n\n  &summary {\n    @mixin clickable;\n    transform: none !important;\n    outline: none !important;\n\n    &::marker,\n    &::-webkit-details-marker {\n      color: var(--color-stroke);\n      margin-right: var(--space-sm);\n    }\n\n    &:focus-visible {\n      background: var(--color-accent-soft);\n    }\n  }\n}\n\ndetails.card:not([open]) .card__header {\n  border-radius: var(--radius);\n}\n\n.card__body {\n  padding: min(var(--space-gutter), var(--space-lg));\n}\n\n.card__row {\n  padding: var(--space-sm) var(--space-md);\n\n  :not(.divider) + & {\n    border-top: 1px solid var(--color-fill);\n  }\n}\n\n.card-list {\n  > * {\n    margin-bottom: var(--space-lg);\n  }\n}\n\n@media (--sm) {\n  .container .card:not(.card .card) {\n    margin-inline: calc(-1 * var(--space-gutter)) !important;\n    border-radius: 0;\n\n    > .card__header {\n      border-radius: 0;\n    }\n  }\n}\n\n.card,\n.card__row {\n  &:target {\n    background: var(--color-warning-soft);\n  }\n}\n"
  },
  {
    "path": "resources/css/system/_channel-label.css",
    "content": ".channel-label {\n  display: inline-flex;\n  align-items: center;\n  gap: var(--space-xxs);\n  vertical-align: bottom;\n  color: inherit;\n  text-decoration: none;\n\n  &a:hover > :last-child {\n    text-decoration: underline;\n  }\n}\n"
  },
  {
    "path": "resources/css/system/_channel-picker.css",
    "content": ".channel-picker {\n  .menu-item {\n    padding: var(--space-sm) var(--space-md);\n    gap: var(--space-md);\n\n    > .icon:first-child {\n      font-size: var(--text-lg);\n    }\n  }\n\n  .menu-heading {\n    padding-inline: var(--space-md);\n\n    &:not(:first-child) {\n      padding-top: var(--space-md);\n    }\n  }\n}\n"
  },
  {
    "path": "resources/css/system/_choice.css",
    "content": ".choice {\n  cursor: pointer;\n  display: flex;\n  align-items: flex-start;\n  gap: var(--space-sm);\n\n  > :disabled + * {\n    color: var(--color-muted);\n  }\n}\n\n.choice-indent {\n  margin-left: calc(var(--space-sm) + 1.2em);\n}\n\ninput[type='checkbox'],\ninput[type='radio'] {\n  -webkit-appearance: none;\n  appearance: none;\n  background-color: var(--color-bg);\n  margin: 0;\n  font: inherit;\n  color: currentColor;\n  width: 1.2em;\n  height: 1.2em;\n  flex-shrink: 0;\n  border: 2px solid var(--color-stroke);\n  vertical-align: -0.2em;\n  position: relative;\n  cursor: pointer;\n\n  &:checked {\n    border: 0;\n  }\n\n  &:checked::before {\n    content: '';\n    position: absolute;\n    left: 50%;\n    top: 50%;\n    transform: translate(-50%, -50%);\n    background: var(--color-accent-contrast);\n  }\n\n  &:not(:disabled, [aria-disabled='true']) {\n    &:active {\n      filter: var(--filter-active);\n    }\n\n    &:checked {\n      background: var(--color-accent);\n    }\n  }\n\n  &:disabled,\n  &[aria-disabled='true'] {\n    background: var(--color-stroke);\n    border: 0;\n\n    &:checked::before {\n      background: var(--color-muted);\n    }\n  }\n}\n\ninput[type='checkbox'] {\n  border-radius: 0.3em;\n\n  &:checked::before {\n    width: 60%;\n    height: 60%;\n    clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%);\n  }\n}\n\ninput[type='radio'] {\n  border-radius: 100%;\n\n  &:checked::before {\n    width: 40%;\n    height: 40%;\n    border-radius: 100%;\n  }\n}\n"
  },
  {
    "path": "resources/css/system/_color-picker.css",
    "content": ".color-picker__picker {\n  position: absolute;\n  z-index: var(--z-index-overlay);\n  box-shadow: var(--shadow-md);\n  margin-top: 6px;\n  border-radius: var(--radius);\n}\n\n.color-picker__swatch,\n.swatch {\n  display: inline-block;\n  vertical-align: middle;\n  width: 1.5em;\n  height: 1.5em;\n  border-radius: 4px;\n  border: 1px solid hsl(var(--palette-text-hsl) / 0.1);\n}\n"
  },
  {
    "path": "resources/css/system/_combobox.css",
    "content": ".combobox {\n  position: relative;\n}\n\n.combobox__list {\n  position: absolute;\n  top: 100%;\n  left: 0;\n  max-width: none !important;\n  width: 100% !important;\n}\n"
  },
  {
    "path": "resources/css/system/_dialog.css",
    "content": ".dialog {\n  --color-bg: var(--palette-surface);\n\n  margin-left: auto;\n  margin-right: auto;\n  max-width: 100%;\n  border-radius: var(--radius);\n  box-shadow: var(--shadow-sm);\n  background: var(--color-bg);\n  color: var(--color-text);\n}\n\n.dialog__header {\n  display: flex;\n  align-items: center;\n  gap: var(--space-md);\n  padding: var(--space-xl);\n  padding-bottom: 0;\n}\n\n.dialog__body {\n  padding: var(--space-xl);\n}\n\n.dialog--sm {\n  width: 45ch;\n}\n\n@media (--sm) {\n  .dialog__header,\n  .dialog__body {\n    padding: var(--space-gutter);\n  }\n\n  .dialog__header {\n    padding-bottom: 0;\n  }\n\n  .container .dialog {\n    margin-inline: calc(-1 * var(--space-gutter)) !important;\n    border-radius: 0;\n    max-width: none;\n    width: calc(100% + 2 * var(--space-gutter));\n  }\n}\n"
  },
  {
    "path": "resources/css/system/_divider.css",
    "content": ".divider {\n  display: flex;\n  align-items: center;\n  color: var(--color-muted);\n  text-transform: uppercase;\n  font-size: var(--text-xxs);\n  font-weight: var(--weight-bold);\n  padding: var(--space-xs) 0;\n\n  &::before,\n  &::after {\n    content: '';\n    flex-grow: 1;\n    border-bottom: 2px solid;\n  }\n\n  &:not(:empty) {\n    &::before {\n      margin-right: 5px;\n    }\n\n    &::after {\n      margin-left: 5px;\n    }\n  }\n}\n"
  },
  {
    "path": "resources/css/system/_emoji-picker.css",
    "content": ".emoji-picker {\n  padding: 0;\n  max-width: none;\n\n  & emoji-picker {\n    --border-color: var(--color-stroke);\n    --border-size: 0;\n    --background: var(--color-bg);\n    --button-hover-background: var(--color-fill);\n    --button-active-background: var(--color-fill);\n    --category-font-color: var(--color-muted);\n    --indicator-color: var(--color-accent);\n    --input-border-color: var(--color-stroke);\n    --input-border-radius: var(--radius);\n    --input-font-color: var(--color-text);\n    --input-placeholder-color: var(--color-muted);\n    --input-padding: 0.4em 0.7em;\n    --outline-color: var(--color-accent);\n  }\n}\n"
  },
  {
    "path": "resources/css/system/_form.css",
    "content": ".field {\n  display: flex;\n  flex-wrap: wrap;\n  column-gap: var(--space-md);\n  row-gap: var(--space-xs);\n  align-items: flex-start;\n\n  > :first-child:not(:only-child) {\n    flex-basis: 20%;\n    min-width: 15ch;\n    flex-shrink: 0;\n    flex-grow: 1;\n  }\n\n  > :nth-child(2) {\n    flex-basis: calc(80% - var(--space-md));\n    flex-grow: 999;\n    min-width: 0;\n    margin: 0;\n  }\n}\n\n.stacked-fields {\n  .field > :first-child {\n    flex-basis: 100%;\n  }\n}\n\n.field__label {\n  font-size: var(--text-xs);\n  font-weight: var(--weight-medium);\n}\n\n.field__description {\n  font-size: var(--text-xs);\n  color: var(--color-muted);\n}\n\n.field__status {\n  font-size: var(--text-xs);\n  font-weight: var(--weight-medium);\n  color: var(--color-muted);\n\n  .has-error & {\n    color: var(--color-danger-text);\n  }\n}\n"
  },
  {
    "path": "resources/css/system/_highlightjs.css",
    "content": ":root,\n[data-theme='light'] {\n  /*!\n    Theme: GitHub\n    Description: Light theme as seen on github.com\n    Author: github.com\n    Maintainer: @Hirse\n    Updated: 2021-05-15\n    Outdated base version: https://github.com/primer/github-syntax-light\n    Current colors taken from GitHub's CSS\n  */\n\n  .hljs {\n    color: #24292e;\n  }\n\n  .hljs-doctag,\n  .hljs-keyword,\n  .hljs-meta .hljs-keyword,\n  .hljs-template-tag,\n  .hljs-template-variable,\n  .hljs-type,\n  .hljs-variable.language_ {\n    /* prettylights-syntax-keyword */\n    color: #d73a49;\n  }\n\n  .hljs-title,\n  .hljs-title.class_,\n  .hljs-title.class_.inherited__,\n  .hljs-title.function_ {\n    /* prettylights-syntax-entity */\n    color: #6f42c1;\n  }\n\n  .hljs-attr,\n  .hljs-attribute,\n  .hljs-literal,\n  .hljs-meta,\n  .hljs-number,\n  .hljs-operator,\n  .hljs-variable,\n  .hljs-selector-attr,\n  .hljs-selector-class,\n  .hljs-selector-id {\n    /* prettylights-syntax-constant */\n    color: #005cc5;\n  }\n\n  .hljs-regexp,\n  .hljs-string,\n  .hljs-meta .hljs-string {\n    /* prettylights-syntax-string */\n    color: #032f62;\n  }\n\n  .hljs-built_in,\n  .hljs-symbol {\n    /* prettylights-syntax-variable */\n    color: #e36209;\n  }\n\n  .hljs-comment,\n  .hljs-code,\n  .hljs-formula {\n    /* prettylights-syntax-comment */\n    color: #6a737d;\n  }\n\n  .hljs-name,\n  .hljs-quote,\n  .hljs-selector-tag,\n  .hljs-selector-pseudo {\n    /* prettylights-syntax-entity-tag */\n    color: #22863a;\n  }\n\n  .hljs-subst {\n    /* prettylights-syntax-storage-modifier-import */\n    color: #24292e;\n  }\n\n  .hljs-section {\n    /* prettylights-syntax-markup-heading */\n    color: #005cc5;\n    font-weight: bold;\n  }\n\n  .hljs-bullet {\n    /* prettylights-syntax-markup-list */\n    color: #735c0f;\n  }\n\n  .hljs-emphasis {\n    /* prettylights-syntax-markup-italic */\n    color: #24292e;\n    font-style: italic;\n  }\n\n  .hljs-strong {\n    /* prettylights-syntax-markup-bold */\n    color: #24292e;\n    font-weight: bold;\n  }\n\n  .hljs-addition {\n    /* prettylights-syntax-markup-inserted */\n    color: #22863a;\n    background-color: #f0fff4;\n  }\n\n  .hljs-deletion {\n    /* prettylights-syntax-markup-deleted */\n    color: #b31d28;\n    background-color: #ffeef0;\n  }\n\n  .hljs-char.escape_,\n  .hljs-link,\n  .hljs-params,\n  .hljs-property,\n  .hljs-punctuation,\n  .hljs-tag {\n    /* purposely ignored */\n  }\n}\n\n[data-theme='dark'] {\n  /*!\n    Theme: GitHub Dark\n    Description: Dark theme as seen on github.com\n    Author: github.com\n    Maintainer: @Hirse\n    Updated: 2021-05-15\n    Outdated base version: https://github.com/primer/github-syntax-dark\n    Current colors taken from GitHub's CSS\n  */\n\n  .hljs {\n    color: #c9d1d9;\n  }\n\n  .hljs-doctag,\n  .hljs-keyword,\n  .hljs-meta .hljs-keyword,\n  .hljs-template-tag,\n  .hljs-template-variable,\n  .hljs-type,\n  .hljs-variable.language_ {\n    /* prettylights-syntax-keyword */\n    color: #ff7b72;\n  }\n\n  .hljs-title,\n  .hljs-title.class_,\n  .hljs-title.class_.inherited__,\n  .hljs-title.function_ {\n    /* prettylights-syntax-entity */\n    color: #d2a8ff;\n  }\n\n  .hljs-attr,\n  .hljs-attribute,\n  .hljs-literal,\n  .hljs-meta,\n  .hljs-number,\n  .hljs-operator,\n  .hljs-variable,\n  .hljs-selector-attr,\n  .hljs-selector-class,\n  .hljs-selector-id {\n    /* prettylights-syntax-constant */\n    color: #79c0ff;\n  }\n\n  .hljs-regexp,\n  .hljs-string,\n  .hljs-meta .hljs-string {\n    /* prettylights-syntax-string */\n    color: #a5d6ff;\n  }\n\n  .hljs-built_in,\n  .hljs-symbol {\n    /* prettylights-syntax-variable */\n    color: #ffa657;\n  }\n\n  .hljs-comment,\n  .hljs-code,\n  .hljs-formula {\n    /* prettylights-syntax-comment */\n    color: #8b949e;\n  }\n\n  .hljs-name,\n  .hljs-quote,\n  .hljs-selector-tag,\n  .hljs-selector-pseudo {\n    /* prettylights-syntax-entity-tag */\n    color: #7ee787;\n  }\n\n  .hljs-subst {\n    /* prettylights-syntax-storage-modifier-import */\n    color: #c9d1d9;\n  }\n\n  .hljs-section {\n    /* prettylights-syntax-markup-heading */\n    color: #1f6feb;\n    font-weight: bold;\n  }\n\n  .hljs-bullet {\n    /* prettylights-syntax-markup-list */\n    color: #f2cc60;\n  }\n\n  .hljs-emphasis {\n    /* prettylights-syntax-markup-italic */\n    color: #c9d1d9;\n    font-style: italic;\n  }\n\n  .hljs-strong {\n    /* prettylights-syntax-markup-bold */\n    color: #c9d1d9;\n    font-weight: bold;\n  }\n\n  .hljs-addition {\n    /* prettylights-syntax-markup-inserted */\n    color: #aff5b4;\n    background-color: #033a16;\n  }\n\n  .hljs-deletion {\n    /* prettylights-syntax-markup-deleted */\n    color: #ffdcd7;\n    background-color: #67060c;\n  }\n\n  .hljs-char.escape_,\n  .hljs-link,\n  .hljs-params,\n  .hljs-property,\n  .hljs-punctuation,\n  .hljs-tag {\n    /* purposely ignored */\n  }\n}\n"
  },
  {
    "path": "resources/css/system/_icon.css",
    "content": ".icon {\n  display: inline-block;\n  height: 1.2em;\n  width: 1.2em;\n  vertical-align: -0.2em;\n  flex-shrink: 0;\n  object-fit: contain;\n  stroke-width: 1.5;\n}\n\n.icon--thin {\n  stroke-width: 1;\n}\n\n.icon--thick {\n  stroke-width: 2;\n}\n\n.icon--narrow {\n  margin-left: -0.2em;\n  margin-right: -0.2em;\n}\n\n.with-icon {\n  display: inline-flex;\n  align-items: baseline;\n  vertical-align: baseline;\n\n  > .icon {\n    margin-right: 0.2em;\n    align-self: center;\n\n    &:not(:first-child) {\n      margin-left: 0.2em;\n    }\n  }\n}\n\n.icon-tabler-lock circle {\n  display: none;\n}\n\n.dot {\n  display: inline-block;\n  width: 10px;\n  height: 10px;\n  margin: 0.3em 0.1em;\n  border-radius: 100%;\n  background: currentColor;\n  vertical-align: -5px;\n}\n"
  },
  {
    "path": "resources/css/system/_input.css",
    "content": "input:where(\n  [type='date'],\n  [type='datetime-local'],\n  [type='email'],\n  [type='file'],\n  [type='month'],\n  [type='number'],\n  [type='password'],\n  [type='search'],\n  [type='tel'],\n  [type='text'],\n  [type='time'],\n  [type='url'],\n  [type='week']\n),\ntextarea,\nselect,\n.input {\n  display: block;\n  width: 100%;\n  border: 1px solid var(--color-stroke);\n  background: var(--color-bg);\n  color: var(--color-text);\n  height: var(--control-height);\n  box-sizing: border-box;\n  border-radius: var(--control-radius);\n  padding: 0.55em 0.7em;\n  -webkit-appearance: none;\n\n  &:focus,\n  &:focus-within {\n    outline: none;\n    border-color: var(--color-accent);\n    box-shadow: inset 0 0 0 1px var(--color-accent);\n  }\n\n  &:disabled {\n    background: var(--color-fill);\n  }\n\n  .has-error & {\n    border-color: var(--color-danger);\n  }\n}\n\ninput[type='file'] {\n  overflow: hidden;\n\n  &:not(:disabled):not([readonly]) {\n    cursor: pointer;\n  }\n\n  &::file-selector-button {\n    padding: 0 var(--space-md) 0 0;\n    margin: 0;\n    width: auto;\n    background: none;\n    color: var(--color-accent-text);\n    font-weight: var(--weight-bold);\n    pointer-events: none;\n    border: 0;\n  }\n}\n\n*::placeholder {\n  color: var(--color-muted);\n}\n\ntextarea,\n.textarea,\nselect[multiple] {\n  height: auto;\n  padding: 0.7rem;\n  border-radius: var(--radius);\n}\n\nselect:not([multiple]),\n.select {\n  cursor: default;\n  background-image: url('data:image/svg+xml,%3Csvg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-5 w-5\" viewBox=\"0 0 20 20\" fill=\"%23888\"%3E%3Cpath fill-rule=\"evenodd\" d=\"M10 3a1 1 0 01.707.293l3 3a1 1 0 01-1.414 1.414L10 5.414 7.707 7.707a1 1 0 01-1.414-1.414l3-3A1 1 0 0110 3zm-3.707 9.293a1 1 0 011.414 0L10 14.586l2.293-2.293a1 1 0 011.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z\" clip-rule=\"evenodd\" /%3E%3C/svg%3E');\n  background-position: center right 0.5em;\n  background-size: 1.25em;\n  background-repeat: no-repeat;\n  padding-right: 2em;\n}\n\n*:invalid + .hide-if-invalid {\n  visibility: hidden;\n}\n\n.input-container {\n  --input-container-padding-start: 2.6em;\n  --input-container-padding-end: 2.6em;\n\n  display: flex;\n  align-items: center;\n\n  > :not(input, .input, select, script) {\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    position: relative;\n\n    &:first-child {\n      width: var(--input-container-padding-start);\n      margin-right: calc(-1 * var(--input-container-padding-start));\n    }\n\n    &:last-child {\n      width: var(--input-container-padding-end);\n      margin-left: calc(-1 * var(--input-container-padding-end));\n    }\n  }\n\n  > :is(input, .input, select) {\n    &:nth-child(2) {\n      padding-left: var(--input-container-padding-start);\n    }\n\n    &:nth-last-child(2) {\n      padding-right: var(--input-container-padding-end);\n    }\n  }\n}\n"
  },
  {
    "path": "resources/css/system/_mention.css",
    "content": ".mention {\n  color: inherit;\n  border-radius: var(--radius);\n  font-weight: var(--weight-bold);\n}\n\n.mention--self {\n  background: var(--color-warning-soft);\n  color: var(--color-warning-text);\n  border-radius: 4px;\n  margin: -2px;\n  padding: 2px;\n}\n"
  },
  {
    "path": "resources/css/system/_menu.css",
    "content": ".menu {\n  --color-bg: var(--palette-surface);\n  --color-text: var(--palette-text);\n\n  display: block;\n  background: var(--color-bg);\n  color: var(--color-text);\n  box-shadow: var(--shadow-md);\n  padding: var(--space-xs);\n  border-radius: var(--radius);\n  min-width: 15ch;\n  width: max-content;\n  max-width: 30ch;\n  z-index: var(--z-index-overlay);\n  font-size: var(--text-xs);\n  overflow: auto;\n}\n\n.menu--lg {\n  max-width: 50ch;\n}\n\n.menu-item {\n  border-radius: calc(var(--radius) * 2 / 3);\n  border: 0;\n  background: transparent;\n  appearance: none;\n  margin: 0;\n  display: flex;\n  gap: var(--space-xs);\n  width: 100%;\n  align-items: flex-start;\n  padding: var(--space-xs);\n  text-decoration: none !important;\n  color: var(--color-text);\n  transform: none !important;\n  text-align: left;\n\n  @mixin clickable;\n\n  &:not(.is-inert):is(\n      :hover,\n      .is-hovered,\n      :focus,\n      .is-focused,\n      [aria-selected='true']\n    ) {\n    background: var(--color-fill);\n    outline: none;\n  }\n\n  &:not(.is-inert).color-danger:is(:hover, .is-hovered) {\n    background: var(--color-danger);\n    color: var(--color-danger-contrast);\n  }\n\n  &.is-active,\n  &[aria-current='page'],\n  &[aria-checked='true'] {\n    color: var(--color-accent-text);\n    background: var(--color-accent-soft);\n  }\n}\n\n.menu-item__title {\n  display: block;\n  font-weight: var(--weight-medium);\n}\n\n.menu-item__description {\n  display: block;\n  color: var(--color-muted);\n  font-size: 90%;\n  margin-top: var(--space-xxs);\n}\n\n.menu-item__check {\n  margin-left: auto;\n\n  .menu-item:not(.is-active, [aria-current='page'], [aria-checked='true']) & {\n    visibility: hidden;\n  }\n}\n\n.menu-divider {\n  margin: var(--space-xs);\n\n  + .menu-divider,\n  .menu > &:first-child,\n  .menu > &:last-child {\n    display: none;\n  }\n}\n\n.menu-heading {\n  padding: var(--space-xs);\n  color: var(--color-muted);\n  font-family: var(--font-text);\n  font-size: var(--text-xxs);\n  font-weight: var(--weight-medium);\n  text-transform: uppercase;\n}\n\n.menu-sticky {\n  position: sticky;\n  top: calc(-1 * var(--space-sm));\n  margin-top: calc(-1 * var(--space-sm));\n  padding-top: var(--space-xs);\n  background: var(--color-bg);\n  z-index: 1;\n}\n\nui-popup:has(> .drawer)::part(backdrop) {\n  background: var(--color-overlay);\n}\n\n.drawer {\n  background: var(--color-bg);\n  position: fixed !important;\n  left: 0 !important;\n  top: 0 !important;\n  max-height: none !important;\n  margin: 0;\n  height: 100vh;\n  width: min(70vw, 30ch);\n  box-shadow: var(--shadow-md);\n  padding: var(--space-lg);\n  z-index: var(--z-index-overlay);\n  overflow: auto;\n\n  &.enter-from,\n  &.leave-to {\n    transform: translateX(-100%);\n    opacity: initial;\n  }\n}\n\n.drawer--right {\n  left: auto !important;\n  right: 0 !important;\n\n  &.enter-from,\n  &.leave-to {\n    transform: translateX(100%);\n  }\n}\n"
  },
  {
    "path": "resources/css/system/_modal.css",
    "content": "/* Style the modal container */\nui-modal {\n  position: fixed;\n  top: 0;\n  left: 0;\n  right: 0;\n  bottom: 0;\n  align-items: flex-start;\n  justify-content: center;\n  z-index: var(--z-index-overlay);\n  padding: var(--space-xxxl) var(--space-gutter);\n  overflow: auto;\n\n  &:not([hidden]) {\n    display: flex;\n  }\n\n  .dialog {\n    box-shadow: var(--shadow-md);\n    border: 0;\n  }\n}\n\n/* Style the backdrop */\nui-modal::part(backdrop) {\n  background: var(--color-overlay);\n}\n\nui-modal::part(content) {\n  max-width: 100%;\n}\n\n/* Transitions using hello-goodbye */\n@media (prefers-reduced-motion: no-preference) {\n  ui-modal.enter-active,\n  ui-modal.leave-active {\n    transition: opacity 0.3s;\n  }\n\n  ui-modal.enter-from,\n  ui-modal.leave-to {\n    opacity: 0;\n  }\n\n  ui-modal.enter-active::part(content),\n  ui-modal.leave-active::part(content) {\n    transition:\n      transform 0.3s cubic-bezier(0.54, 1.12, 0.38, 1.11),\n      opacity 0.3s;\n  }\n\n  ui-modal.enter-from::part(content),\n  ui-modal.leave-to::part(content) {\n    transform: scale(0.7);\n    opacity: 0;\n  }\n}\n"
  },
  {
    "path": "resources/css/system/_nav.css",
    "content": ".nav {\n  list-style: none;\n  margin-inline: 0;\n  padding-inline: 0;\n}\n\n.nav-heading {\n  margin-bottom: var(--space-xs);\n  padding: 0 var(--space-sm);\n  width: 100%;\n  color: var(--color-muted);\n  font-family: var(--font-text);\n  font-size: var(--text-xxs);\n  font-weight: var(--weight-medium);\n  text-transform: uppercase;\n\n  * + & {\n    margin-top: var(--space-lg);\n  }\n}\n\n.nav-link {\n  display: flex;\n  align-items: center;\n  gap: var(--space-xs);\n  min-height: var(--control-height);\n  padding: var(--space-xs) var(--space-sm);\n  font-weight: var(--weight-medium);\n  text-decoration: none !important;\n  color: var(--color-text);\n  border-radius: var(--control-radius);\n\n  > :not(.icon) {\n    overflow: hidden;\n    white-space: nowrap;\n    text-overflow: ellipsis;\n  }\n\n  > .icon {\n    font-size: 120%;\n  }\n\n  > [class^='icon-fa'] {\n    padding: 2px;\n  }\n\n  > .badge {\n    margin-left: auto;\n  }\n\n  @mixin clickable;\n  transform: none !important;\n\n  &:hover,\n  &:focus {\n    background: var(--color-fill);\n  }\n\n  &.is-active,\n  &[aria-current='page'] {\n    color: var(--color-accent-text);\n    background: var(--color-accent-soft);\n\n    .badge {\n      background: inherit;\n      color: inherit;\n    }\n  }\n}\n\n@media (--md-down) {\n  // Allow the responsive nav button to shrink down to 10ch before wrapping\n  .collapsible-nav {\n    flex: 1 1 10ch;\n    min-width: 0;\n    max-width: fit-content;\n  }\n}\n\n@media (--lg-up) {\n  .collapsible-nav {\n    > :first-child {\n      display: none;\n    }\n\n    > :last-child {\n      display: contents !important;\n    }\n  }\n}\n"
  },
  {
    "path": "resources/css/system/_placeholder.css",
    "content": ".placeholder {\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  gap: var(--space-sm);\n  text-align: center;\n  color: var(--color-muted);\n  padding: var(--space-xl) 0;\n\n  > * {\n    max-width: 50ch;\n  }\n}\n\n.placeholder__icon {\n  width: 4em;\n  height: 4em;\n  stroke-width: 1.5px;\n}\n"
  },
  {
    "path": "resources/css/system/_reset.css",
    "content": "// Inspired by https://github.com/andy-piccalilli/modern-css-reset\n\n*,\n*::before,\n*::after {\n  box-sizing: border-box;\n  margin: 0;\n}\n\ninput,\nbutton,\ntextarea,\nselect {\n  font: inherit;\n}\n\n[hidden] {\n  display: none !important;\n}\n\n[role='list'] {\n  list-style: none;\n  padding: 0;\n}\n\nbutton {\n  appearance: none;\n  background: transparent;\n  border: 0;\n  color: inherit;\n  cursor: pointer;\n  margin: 0;\n  padding: 0;\n}\n\n@media (prefers-reduced-motion: reduce) {\n  *,\n  *::before,\n  *::after {\n    animation-duration: 0.01ms !important;\n    animation-iteration-count: 1 !important;\n    transition-duration: 0.01ms !important;\n    scroll-behavior: auto !important;\n  }\n}\n"
  },
  {
    "path": "resources/css/system/_sidebar.css",
    "content": ":root {\n  --sidebar-width: 25ch;\n}\n\n.with-sidebar {\n  display: flex;\n  align-items: flex-start;\n  justify-content: center;\n  gap: var(--space-gutter);\n}\n\n.with-sidebar > :not(.sidebar) {\n  flex-grow: 999;\n  min-width: 0;\n}\n\n.sidebar {\n  display: flex;\n}\n\n@media (--md-down) {\n  .with-sidebar {\n    flex-direction: column;\n    gap: var(--space-md);\n    align-items: stretch;\n  }\n\n  .with-sidebar > :not(.sidebar) {\n    width: 100%;\n  }\n\n  .sidebar {\n    flex-direction: row;\n    flex-wrap: wrap;\n  }\n\n  .sidebar--bottom {\n    position: sticky;\n    bottom: 0;\n    margin-inline: calc(-1 * var(--space-gutter));\n    padding-inline: var(--space-gutter);\n    background: var(--color-bg);\n    width: calc(100% + 2 * var(--space-gutter)) !important;\n    padding-block: var(--space-sm);\n    flex-wrap: nowrap !important;\n    flex-direction: row !important;\n    flex-basis: auto !important;\n    align-items: center;\n    z-index: var(--z-index-header);\n\n    &.is-stuck {\n      box-shadow: var(--shadow-sm);\n    }\n  }\n}\n\n@media (--lg-up) {\n  .sidebar {\n    flex-basis: var(--sidebar-width);\n    flex-shrink: 0;\n    flex-direction: column;\n  }\n\n  .sidebar--sticky {\n    position: sticky;\n    top: var(--header-height);\n    margin-block: calc(-1 * var(--space-gutter));\n    padding-block: var(--space-gutter);\n    max-height: calc(100vh - var(--header-height) - 2 * var(--space-gutter));\n    overflow: auto;\n    box-sizing: content-box;\n  }\n\n  .sidebar__collapsed {\n    display: none;\n  }\n}\n"
  },
  {
    "path": "resources/css/system/_skip-link.css",
    "content": ".skip-link {\n  color: var(--color-accent-contrast);\n  background-color: var(--color-accent);\n  padding: var(--space-xs);\n  position: absolute;\n  top: -10rem;\n\n  &:focus {\n    position: absolute;\n    z-index: var(--z-index-overlay);\n    top: var(--space-xs);\n    left: var(--space-xs);\n  }\n}\n"
  },
  {
    "path": "resources/css/system/_spinner.css",
    "content": ".spinner {\n  display: inline-flex;\n  flex-direction: column;\n  align-items: center;\n  gap: var(--space-sm);\n  color: var(--color-muted);\n\n  &::before {\n    content: '';\n    display: block;\n    margin: 0 auto;\n\n    @mixin spinner;\n  }\n}\n\n.spinner--sm {\n  padding: 0;\n  vertical-align: -0.3em;\n\n  &::before {\n    width: 1.2em;\n    height: 1.2em;\n    border-width: 0.25em;\n  }\n}\n\n.spinner--block {\n  display: flex;\n  padding: var(--space-md);\n}\n\n@keyframes spinner {\n  from {\n    transform: rotate(0deg);\n  }\n  to {\n    transform: rotate(359deg);\n  }\n}\n"
  },
  {
    "path": "resources/css/system/_table.css",
    "content": ".table-container {\n  overflow: auto;\n  max-width: 100%;\n  width: fit-content;\n\n  * {\n    overflow-wrap: normal;\n  }\n\n  & table {\n    width: 100%;\n    border: 0;\n  }\n}\n\ntable {\n  border-spacing: 0;\n  border-collapse: collapse;\n\n  &,\n  td,\n  th {\n    border: 1px solid var(--color-fill);\n  }\n\n  & td,\n  th {\n    padding: var(--space-sm);\n    text-align: left;\n  }\n\n  & th {\n    font-weight: var(--weight-bold);\n  }\n}\n\n.table-container table,\n.table {\n  font-size: var(--text-xs);\n  border-collapse: separate;\n\n  & td,\n  th {\n    padding: var(--space-sm);\n    text-align: left;\n    border-width: 1px 0 0;\n  }\n\n  & th {\n    font-weight: var(--weight-bold);\n  }\n\n  & thead {\n    & td,\n    th {\n      border-width: 0;\n    }\n  }\n}\n\ntd.choice-cell {\n  padding: 0;\n  position: relative;\n  min-width: 4ch;\n\n  > * {\n    position: absolute;\n    top: 0;\n    left: 0;\n    right: 0;\n    bottom: 0;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    cursor: pointer;\n\n    &:not(.is-disabled):hover,\n    &.is-highlighted {\n      background: var(--color-fill);\n    }\n  }\n}\n\n.is-highlighted {\n  background: var(--color-fill);\n}\n"
  },
  {
    "path": "resources/css/system/_tabs.css",
    "content": ".tabs {\n  display: flex;\n  flex-wrap: wrap;\n  align-items: center;\n  margin-inline: calc(-1 * var(--space-sm));\n}\n\n.tab {\n  color: var(--color-muted);\n  font-weight: var(--weight-medium);\n  text-decoration: none !important;\n  padding: var(--space-xs) var(--space-sm);\n  white-space: nowrap;\n  min-width: 2ch;\n  text-align: center;\n  position: relative;\n  flex-shrink: 0;\n  overflow: hidden;\n  text-overflow: ellipsis;\n\n  &:not(.is-disabled, [aria-disabled='true']) {\n    &:hover,\n    &.is-hovered {\n      color: var(--color-text);\n    }\n  }\n\n  &.is-active,\n  &[aria-current='page'],\n  &[aria-selected='true'] {\n    color: var(--color-accent-text) !important;\n\n    &::after {\n      content: '';\n      position: absolute;\n      bottom: 0;\n      left: 50%;\n      transform: translateX(-50%);\n      min-width: 2ch;\n      width: calc(100% - 2 * var(--space-sm));\n      background: var(--color-accent-soft);\n      height: 4px;\n      border-radius: var(--radius);\n    }\n  }\n\n  &.is-disabled,\n  &[aria-disabled='true'] {\n    opacity: 0.3;\n    cursor: default;\n  }\n}\n\n.tabs--vertical {\n  margin-right: 0;\n  flex-direction: column;\n  flex-wrap: nowrap;\n  align-items: stretch;\n  font-size: var(--text-xs);\n\n  .tab {\n    display: block;\n    text-align: left;\n    padding-block: var(--space-xxs);\n    padding-inline: var(--space-sm);\n\n    &.is-active,\n    &[aria-current='page'],\n    &[aria-selected='true'] {\n      &::after {\n        width: 4px;\n        height: calc(100% - 2 * var(--space-xxs));\n        bottom: var(--space-xxs);\n        left: 0;\n        transform: none;\n        min-width: 0;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "resources/css/system/_tooltip.css",
    "content": "ui-tooltip {\n  // .visually-hidden\n  clip: rect(0 0 0 0);\n  clip-path: inset(50%);\n  height: 1px;\n  overflow: hidden;\n  position: absolute;\n  white-space: nowrap;\n  width: 1px;\n}\n\n.tooltip {\n  background: var(--color-emphasis);\n  border-radius: var(--radius);\n  box-shadow: var(--space-md);\n  color: var(--color-emphasis-contrast);\n  font-size: var(--text-xs);\n  font-weight: var(--weight-medium);\n  max-width: 30ch;\n  padding: var(--space-xs) var(--space-sm);\n  z-index: var(--z-index-overlay);\n  text-align: center;\n  overflow: hidden;\n\n  --color-text: var(--color-emphasis-contrast);\n  --color-muted: rgb(255 255 255 / 0.5);\n  --color-accent: var(--color-text);\n\n  & small {\n    font-size: var(--text-xxs);\n    color: var(--color-muted);\n  }\n\n  &.enter-active,\n  &.leave-active {\n    transition:\n      opacity 0.2s,\n      transform 0.2s;\n  }\n\n  &.enter-from,\n  &.leave-to {\n    opacity: 0;\n    transform: scale(0.95);\n  }\n}\n\n.tooltip--block {\n  text-align: left;\n  padding: var(--space-xs) var(--space-sm);\n  font-size: var(--text-xxs);\n}\n"
  },
  {
    "path": "resources/css/system/_turbo.css",
    "content": "turbo-frame {\n  display: block;\n}\n\n.busy-spinner[aria-busy='true'] {\n  position: relative;\n  min-height: 4em;\n  visibility: hidden;\n\n  &::after {\n    visibility: visible;\n    content: '';\n    position: absolute;\n    z-index: 2;\n    left: calc(50% - 1em);\n    top: var(--space-md);\n\n    @mixin spinner;\n  }\n}\n\n.turbo-progress-bar {\n  background-color: var(--color-accent);\n}\n\n.next-page {\n  position: relative;\n\n  &::before {\n    content: '';\n    position: absolute;\n    top: -100px;\n    bottom: -100px;\n  }\n}\n"
  },
  {
    "path": "resources/css/system/_typography.css",
    "content": ".h1,\n.h2,\n.h3,\nh1,\nh2,\nh3 {\n  font-family: var(--font-display);\n}\n\n.h4,\n.h5,\n.h6,\nh4,\nh5,\nh6 {\n  font-family: var(--font-text);\n}\n\n.h1,\nh1 {\n  font-size: var(--text-xxl);\n  font-weight: var(--weight-bold);\n  line-height: var(--line-height-condensed);\n}\n\n.h2,\nh2 {\n  font-size: var(--text-xl);\n  font-weight: var(--weight-bold);\n  line-height: var(--line-height-condensed);\n}\n\n.h3,\nh3 {\n  font-size: var(--text-lg);\n  font-weight: var(--weight-bold);\n}\n\n.h4,\nh4 {\n  font-size: var(--text-md);\n  font-weight: var(--weight-bold);\n}\n\n.h5,\nh5 {\n  font-size: var(--text-sm);\n  font-weight: var(--weight-bold);\n}\n\n.h6,\nh6 {\n  font-size: var(--text-xs);\n  font-weight: var(--weight-bold);\n}\n\n.subtitle {\n  color: var(--color-muted);\n  font-family: var(--font-text);\n  font-size: var(--text-xxs);\n  font-weight: var(--weight-medium);\n  text-transform: uppercase;\n}\n\n.lead {\n  font-size: var(--text-md);\n  line-height: var(--line-height-expanded);\n}\n\n.content {\n  overflow-wrap: break-word;\n  line-height: var(--line-height-expanded);\n}\n\n:where(.content) {\n  > * + *,\n  li p {\n    margin-top: 1em;\n  }\n\n  > * + :is(h1, h2, h3, h4, h5, h6) {\n    margin-top: 1.8em;\n  }\n\n  & a {\n    text-decoration: underline;\n  }\n\n  & ol {\n    list-style-position: outside;\n    padding-left: 2em;\n  }\n\n  & ul {\n    list-style: disc outside;\n    padding-left: 2em;\n\n    & ul {\n      list-style-type: circle;\n\n      & ul {\n        list-style-type: square;\n      }\n    }\n  }\n\n  & blockquote {\n    color: var(--color-muted);\n    border-left: 5px solid var(--color-fill);\n    padding-left: var(--space-md);\n\n    > * + * {\n      margin-top: 1em;\n    }\n  }\n\n  & figcaption {\n    color: var(--color-muted);\n    font-size: 90%;\n  }\n\n  & code {\n    font-family: var(--font-mono);\n    font-size: 80%;\n    background: var(--color-fill-soft);\n    color: var(--color-accent-text);\n    padding: 3px;\n    border-radius: 4px;\n  }\n\n  & pre code {\n    display: block;\n    overflow: auto;\n    background: var(--color-fill);\n    color: inherit;\n    border-radius: var(--radius);\n    padding: var(--space-md) !important;\n  }\n\n  & :is(img, video):not([class]) {\n    max-width: 100%;\n    height: auto;\n  }\n\n  & hr:not([class]) {\n    border: 0;\n    border-top: 3px solid var(--color-stroke);\n    margin: 2em 0;\n  }\n}\n\n:where(.content--compact) {\n  > * + * {\n    margin-top: 1em;\n  }\n\n  & h1,\n  h2,\n  h3,\n  h4,\n  h5,\n  h6 {\n    font-size: 100%;\n  }\n}\n"
  },
  {
    "path": "resources/css/system/_user-label.css",
    "content": ".user-label {\n  display: inline-flex;\n  align-items: baseline;\n  gap: var(--space-xxs);\n  vertical-align: baseline;\n  color: inherit;\n  text-decoration: none;\n\n  .avatar {\n    width: 18px;\n    height: 18px;\n    align-self: center;\n  }\n}\n"
  },
  {
    "path": "resources/css/system/_utils.css",
    "content": "// Accessibility\n\n.visually-hidden {\n  clip: rect(0 0 0 0);\n  clip-path: inset(50%);\n  height: 1px;\n  overflow: hidden;\n  position: absolute;\n  white-space: nowrap;\n  width: 1px;\n}\n\n// Layout\n\n.container {\n  margin-inline: auto;\n  padding-inline: var(--space-gutter);\n  max-width: 72rem;\n  width: 100%;\n}\n\n.section {\n  padding-block: var(--space-gutter);\n}\n\n// Typographic measure\n\n.measure {\n  max-width: var(--measure);\n}\n\n// Size\n\n@each $size in (px, xxs, xs, sm, md, lg, xl, xxl, xxxl, gutter) {\n  .gap-$(size) {\n    gap: var(--space-$(size));\n  }\n\n  .gap-x-$(size) {\n    column-gap: var(--space-$(size));\n  }\n\n  .gap-y-$(size) {\n    row-gap: var(--space-$(size));\n  }\n\n  .p-$(size) {\n    padding: var(--space-$(size));\n  }\n\n  .px-$(size) {\n    padding-inline: var(--space-$(size));\n  }\n\n  .py-$(size) {\n    padding-block: var(--space-$(size));\n  }\n\n  .m-$(size) {\n    margin: var(--space-$(size));\n  }\n\n  .mx-$(size) {\n    margin-inline: var(--space-$(size));\n  }\n\n  .my-$(size) {\n    margin-block: var(--space-$(size));\n  }\n\n  .mt-$(size) {\n    margin-top: var(--space-$(size));\n  }\n\n  .-m-$(size) {\n    margin: calc(-1 * var(--space-$(size)));\n  }\n\n  .-mx-$(size) {\n    margin-inline: calc(-1 * var(--space-$(size)));\n  }\n\n  .-my-$(size) {\n    margin-block: calc(-1 * var(--space-$(size)));\n  }\n\n  .-mt-$(size) {\n    margin-top: calc(-1 * var(--space-$(size)));\n  }\n}\n\n// Flexbox and Grid\n\n.stack {\n  display: flex;\n  flex-direction: column;\n\n  &.reverse {\n    flex-direction: column-reverse;\n  }\n}\n\n.row {\n  display: flex;\n  align-items: center;\n\n  &.reverse {\n    flex-direction: row-reverse;\n  }\n}\n\n.grid {\n  display: grid;\n  grid-template-columns: repeat(\n    auto-fill,\n    minmax(min(var(--grid-min, 15ch), 100%), 1fr)\n  );\n}\n\n.grid-fit {\n  display: grid;\n  grid-template-columns: repeat(\n    auto-fit,\n    minmax(min(var(--grid-min, 15ch), 100%), 1fr)\n  );\n}\n\n.block {\n  display: block;\n  width: 100%;\n}\n\n.inline {\n  display: inline;\n  vertical-align: middle;\n}\n\n.inline-block {\n  display: inline-block;\n}\n\n.justify-center {\n  justify-content: center;\n}\n\n.justify-end {\n  justify-content: flex-end;\n}\n\n.justify-start {\n  justify-content: flex-start;\n}\n\n.justify-between {\n  justify-content: space-between;\n}\n\n.align-center {\n  align-items: center;\n}\n\n.align-start {\n  align-items: flex-start;\n}\n\n.align-end {\n  align-items: flex-end;\n}\n\n.align-baseline {\n  align-items: baseline;\n}\n\n.align-stretch {\n  align-items: stretch;\n}\n\n.align-self-center {\n  align-self: center;\n}\n\n.align-self-start {\n  align-self: flex-start;\n}\n\n.align-self-end {\n  align-self: flex-end;\n}\n\n.align-self-baseline {\n  align-self: baseline;\n}\n\n.align-self-stretch {\n  align-self: stretch;\n}\n\n.push-start {\n  margin-inline-end: auto;\n}\n\n.push-center {\n  margin-inline: auto;\n}\n\n.push-end {\n  margin-inline-start: auto;\n}\n\n@media (--sm) {\n  .break-sm {\n    order: 999;\n    width: 100%;\n    flex-grow: 1;\n  }\n}\n\n.grow {\n  flex-grow: 1;\n}\n\n.shrink {\n  min-width: 0;\n}\n\n.no-shrink {\n  flex-shrink: 0;\n}\n\n.full-width {\n  width: 100%;\n}\n\n.full-height {\n  height: 100%;\n}\n\n.wrap {\n  flex-wrap: wrap;\n}\n\n.wrap-reverse {\n  flex-wrap: wrap-reverse;\n}\n\n.nowrap {\n  flex-wrap: nowrap;\n  white-space: nowrap;\n}\n\n.dividers {\n  > * + * {\n    border-top: 1px solid var(--color-stroke);\n    margin-top: var(--space-lg);\n    padding-top: var(--space-lg);\n  }\n}\n\n// Float\n\n.float-left {\n  float: left;\n}\n\n.float-right {\n  float: right;\n}\n\n// Overflow\n\n.overflow-ellipsis {\n  white-space: nowrap;\n  text-overflow: ellipsis;\n  overflow: hidden;\n}\n\n.overflow-visible {\n  overflow: visible;\n}\n\n.overflow-hidden {\n  overflow: hidden;\n}\n\n.scrollable,\n.scrollable-x,\n.scrollable-y {\n  overflow: auto;\n  -ms-overflow-style: none;\n  scrollbar-width: none;\n\n  &::-webkit-scrollbar {\n    display: none;\n  }\n\n  &::before,\n  &::after {\n    content: '';\n    display: block;\n    position: sticky;\n    z-index: 10;\n    flex-shrink: 0;\n    pointer-events: none;\n    opacity: 0;\n    transition: opacity 0.5s;\n  }\n}\n\n.scrollable-x {\n  &::before,\n  &::after {\n    width: 40px;\n    margin-left: -40px;\n    align-self: stretch;\n  }\n\n  &::before {\n    left: 0;\n    background-image: linear-gradient(to right, var(--color-bg), transparent);\n  }\n\n  &::after {\n    right: 0;\n    background-image: linear-gradient(to left, var(--color-bg), transparent);\n  }\n\n  &.is-scrolled-right::before,\n  &.is-scrolled-left::after {\n    opacity: 1;\n  }\n}\n\n.scrollable-y {\n  &::before,\n  &::after {\n    height: 40px;\n    margin-top: -40px;\n    width: 100%;\n  }\n\n  &::before {\n    top: 0;\n    background-image: linear-gradient(to bottom, var(--color-bg), transparent);\n  }\n\n  &::after {\n    bottom: 0;\n    background-image: linear-gradient(to top, var(--color-bg), transparent);\n  }\n\n  &.is-scrolled-down::before,\n  &.is-scrolled-up::after {\n    opacity: 1;\n  }\n}\n\n// Rotation\n\n.rotate-90 {\n  transform: rotate(90deg);\n}\n\n.rotate-180 {\n  transform: rotate(180deg);\n}\n\n.rotate-270 {\n  transform: rotate(270deg);\n}\n\n.flip-horizontal {\n  transform: scaleX(-1);\n}\n\n.flip-vertical {\n  transform: scaleY(-1);\n}\n\n// Text Alignment\n\n.text-left {\n  text-align: left;\n}\n\n.text-center {\n  text-align: center;\n}\n\n.text-right {\n  text-align: right;\n}\n\n// Text Color\n\n.color-text {\n  color: var(--color-text);\n}\n\n.color-muted {\n  color: var(--color-muted);\n}\n\n.color-accent {\n  color: var(--color-accent-text);\n}\n\n.color-success {\n  color: var(--color-success-text);\n}\n\n.color-warning {\n  color: var(--color-warning-text);\n}\n\n.color-danger {\n  color: var(--color-danger-text);\n}\n\n.color-activity {\n  color: var(--color-activity-text);\n}\n\n.color-inherit {\n  color: inherit;\n}\n\n// Text Size\n\n.text-xxs {\n  font-size: var(--text-xxs);\n}\n\n.text-xs {\n  font-size: var(--text-xs);\n}\n\n.text-sm {\n  font-size: var(--text-sm);\n}\n\n.text-md {\n  font-size: var(--text-md);\n}\n\n.text-lg {\n  font-size: var(--text-lg);\n}\n\n.text-xl {\n  font-size: var(--text-xl);\n}\n\n.text-xxl {\n  font-size: var(--text-xxl);\n}\n\n// Animation\n\n.animate-shake {\n  animation: shake 0.8s;\n}\n\n@keyframes shake {\n  10%,\n  90% {\n    transform: translateX(-1px);\n  }\n\n  20%,\n  80% {\n    transform: translateX(2px);\n  }\n\n  30%,\n  50%,\n  70% {\n    transform: translateX(-4px);\n  }\n\n  40%,\n  60% {\n    transform: translateX(4px);\n  }\n}\n\n.animate-appear {\n  animation: appear 0.5s;\n}\n\n@keyframes appear {\n  0% {\n    transform: scale(0.9);\n    opacity: 0;\n  }\n  100% {\n    transform: none;\n    opacity: 1;\n  }\n}\n\n// Background Color\n\n.bg-emphasis,\n.bg-accent,\n.bg-success,\n.bg-warning,\n.bg-danger,\n.bg-activity {\n  --color-fill: rgb(255 255 255 / 0.1);\n  --color-stroke: rgb(255 255 255 / 0.25);\n  --color-muted: var(--color-text);\n  --color-accent: var(--color-text);\n  --color-accent-text: var(--color-text);\n\n  background-color: var(--color-bg);\n  color: var(--color-text);\n}\n\n.bg-fill {\n  background-color: var(--color-fill);\n}\n\n.bg-fill-soft {\n  background-color: var(--color-fill-soft);\n}\n\n.bg-emphasis {\n  --color-bg: var(--palette-emphasis);\n  --color-text: var(--palette-emphasis-contrast);\n}\n\n.bg-accent {\n  --color-bg: var(--palette-accent);\n  --color-text: var(--palette-accent-contrast);\n}\n\n.bg-accent-soft {\n  background-color: var(--color-accent-soft);\n  color: var(--color-accent-text);\n}\n\n.bg-success {\n  --color-bg: var(--palette-success);\n  --color-text: var(--palette-success-contrast);\n}\n\n.bg-success-soft {\n  background-color: var(--color-success-soft);\n  color: var(--color-success-text);\n}\n\n.bg-warning {\n  --color-bg: var(--palette-warning);\n  --color-text: var(--palette-warning-contrast);\n}\n\n.bg-warning-soft {\n  background-color: var(--color-warning-soft);\n  color: var(--color-warning-text);\n}\n\n.bg-danger {\n  --color-bg: var(--palette-danger);\n  --color-text: var(--palette-danger-contrast);\n}\n\n.bg-danger-soft {\n  background-color: var(--color-danger-soft);\n  color: var(--color-danger-text);\n}\n\n.bg-activity {\n  --color-bg: var(--palette-activity);\n  --color-text: var(--palette-activity-contrast);\n}\n\n.bg-activity-soft {\n  background-color: var(--color-activity-soft);\n  color: var(--color-activity-text);\n}\n\n// Interactions\n\n.no-pointer {\n  pointer-events: none;\n}\n\n.no-select {\n  user-select: none;\n  -webkit-user-select: none;\n}\n\n.clickable {\n  @mixin clickable;\n}\n\n.cursor-default {\n  cursor: default !important;\n}\n\n.cursor-help {\n  cursor: help !important;\n}\n\n.cursor-pointer {\n  cursor: pointer !important;\n}\n\n// JavaScript\n\n.js .no-js-only,\n.no-js .js-only {\n  display: none !important;\n}\n\n// Border Radius\n\n.rounded {\n  border-radius: var(--radius);\n}\n\n.pill {\n  border-radius: 999px;\n}\n\n// Overlay\n\n.overlay-container {\n  position: relative;\n}\n\n.overlay,\n.has-overlay::before {\n  position: absolute !important; // !important to override .busy-spinner\n  top: 0;\n  left: 0;\n  right: 0;\n  bottom: 0;\n}\n\n.has-overlay::before {\n  content: '';\n}\n\n// Truncated\n\n.truncated {\n  overflow: hidden;\n  position: relative;\n  max-height: 15em;\n}\n\n.truncated__expander {\n  position: absolute;\n  left: 0;\n  right: 0;\n  bottom: 0;\n  width: 100%;\n  height: 5em;\n  background: linear-gradient(to bottom, transparent, var(--color-bg) 60%);\n  display: flex;\n  align-items: flex-end;\n}\n\n// Font Family\n\n.font-text {\n  font-family: var(--font-text);\n}\n\n.font-display {\n  font-family: var(--font-display);\n}\n\n.font-mono {\n  font-family: var(--font-mono);\n}\n\n// Font Weight\n\n.weight-normal {\n  font-weight: var(--weight-normal);\n}\n\n.weight-medium {\n  font-weight: var(--weight-medium);\n}\n\n.weight-bold {\n  font-weight: var(--weight-bold);\n}\n\n// Underline\n\n.underline {\n  text-decoration: underline;\n}\n\n.no-underline {\n  text-decoration: none;\n}\n\n// Responsive Hiding\n\n@media (--sm) {\n  .hide-sm {\n    display: none !important;\n  }\n}\n\n@media (--md-up) {\n  .hide-md-up {\n    display: none !important;\n  }\n}\n\n@media (--md-down) {\n  .hide-md-down {\n    display: none !important;\n  }\n}\n\n@media (--lg-up) {\n  .hide-lg-up {\n    display: none !important;\n  }\n}\n\n@media (--lg-down) {\n  .hide-lg-down {\n    display: none !important;\n  }\n}\n\n.hide-if-not-only-child:not(:only-child),\n.hide-if-empty:empty {\n  display: none;\n}\n\n// Dark mode\n\n[data-theme='dark'] .light-only,\n[data-theme='light'] .dark-only {\n  display: none;\n}\n"
  },
  {
    "path": "resources/css/system/_variables.css",
    "content": ":root,\n[data-theme='light'] {\n  color-scheme: light;\n\n  // Background color\n  --palette-bg-hsl: 250 38% 98%;\n  --palette-bg: hsl(var(--palette-bg-hsl));\n\n  // Surface color\n  --palette-surface-hsl: 255 100% 100%;\n  --palette-surface: hsl(var(--palette-surface-hsl));\n\n  // Text color\n  --palette-text-hsl: 0 0% 0%;\n  --palette-text: hsl(var(--palette-text-hsl));\n\n  // Muted color\n  --palette-muted-hsl: 250 20% 50%;\n  --palette-muted: hsl(var(--palette-muted-hsl));\n  --palette-fill: hsl(var(--palette-muted-hsl) / 0.1);\n  --palette-fill-soft: hsl(var(--palette-muted-hsl) / 0.05);\n  --palette-stroke: hsl(var(--palette-muted-hsl) / 0.25);\n\n  // Emphasis color\n  --palette-emphasis: hsl(217, 16%, 22%);\n  --palette-emphasis-contrast: #fff;\n\n  // Accent color\n  --palette-accent-h: 252;\n  --palette-accent-s: 93%;\n  --palette-accent-l: 58%;\n  --palette-accent: hsl(\n    var(--palette-accent-h) var(--palette-accent-s) var(--palette-accent-l)\n  );\n  --palette-accent-contrast: #fff;\n  --palette-accent-soft: hsl(\n    var(--palette-accent-h) var(--palette-accent-s) 92%\n  );\n  --palette-accent-text: hsl(\n    var(--palette-accent-h) var(--palette-accent-s)\n      calc(var(--palette-accent-l) * 0.9)\n  );\n\n  // Danger color\n  --palette-danger-h: 0;\n  --palette-danger-s: 100%;\n  --palette-danger-l: 38%;\n  --palette-danger: hsl(\n    var(--palette-danger-h) var(--palette-danger-s) var(--palette-danger-l)\n  );\n  --palette-danger-contrast: #fff;\n  --palette-danger-soft: hsl(\n    var(--palette-danger-h) var(--palette-danger-s) 92%\n  );\n  --palette-danger-text: hsl(\n    var(--palette-danger-h) var(--palette-danger-s)\n      calc(var(--palette-danger-l) * 0.9)\n  );\n\n  // Warning color\n  --palette-warning-h: 60;\n  --palette-warning-s: 100%;\n  --palette-warning-l: 45%;\n  --palette-warning: hsl(\n    var(--palette-warning-h) var(--palette-warning-s) var(--palette-warning-l)\n  );\n  --palette-warning-contrast: hsl(\n    var(--palette-warning-h) var(--palette-warning-s) 15%\n  );\n  --palette-warning-soft: hsl(\n    var(--palette-warning-h) var(--palette-warning-s) 92%\n  );\n  --palette-warning-text: hsl(\n    var(--palette-warning-h) var(--palette-warning-s) 24%\n  );\n\n  // Success color\n  --palette-success-h: 127;\n  --palette-success-s: 100%;\n  --palette-success-l: 27%;\n  --palette-success: hsl(\n    var(--palette-success-h) var(--palette-success-s) var(--palette-success-l)\n  );\n  --palette-success-contrast: #fff;\n  --palette-success-soft: hsl(\n    var(--palette-success-h) var(--palette-success-s) 92%\n  );\n  --palette-success-text: hsl(\n    var(--palette-success-h) var(--palette-success-s)\n      calc(var(--palette-success-l) * 0.9)\n  );\n\n  // Activity color\n  --palette-activity-h: 23;\n  --palette-activity-s: 100%;\n  --palette-activity-l: 45%;\n  --palette-activity: hsl(\n    var(--palette-activity-h) var(--palette-activity-s)\n      var(--palette-activity-l)\n  );\n  --palette-activity-contrast: #fff;\n  --palette-activity-soft: hsl(\n    var(--palette-activity-h) var(--palette-activity-s) 92%\n  );\n  --palette-activity-text: hsl(\n    var(--palette-activity-h) var(--palette-activity-s)\n      calc(var(--palette-activity-l) * 0.9)\n  );\n}\n\n[data-theme='dark'] {\n  color-scheme: dark;\n\n  --palette-bg-hsl: 250 20% 8%;\n  --palette-surface-hsl: 250 20% 12%;\n\n  --palette-text-hsl: 250 20% 93%;\n  --palette-muted-hsl: 250 20% 60%;\n\n  --palette-accent-soft: hsl(\n    var(--palette-accent-h) var(--palette-accent-s) 20%\n  );\n  --palette-accent-text: hsl(\n    var(--palette-accent-h) var(--palette-accent-s)\n      calc(var(--palette-accent-l) * 1.3)\n  );\n\n  --palette-danger-soft: hsl(\n    var(--palette-danger-h) var(--palette-danger-s) 10%\n  );\n  --palette-danger-text: hsl(\n    var(--palette-danger-h) var(--palette-danger-s)\n      calc(var(--palette-danger-l) * 1.3)\n  );\n\n  --palette-warning-soft: hsl(\n    var(--palette-warning-h) var(--palette-warning-s) 10%\n  );\n  --palette-warning-text: hsl(\n    var(--palette-warning-h) var(--palette-warning-s) 40%\n  );\n\n  --palette-success-soft: hsl(\n    var(--palette-success-h) var(--palette-success-s) 10%\n  );\n  --palette-success-text: hsl(\n    var(--palette-success-h) var(--palette-success-s)\n      calc(var(--palette-success-l) * 1.3)\n  );\n\n  --palette-activity-soft: hsl(\n    var(--palette-activity-h) var(--palette-activity-s) 10%\n  );\n  --palette-activity-text: hsl(\n    var(--palette-activity-h) var(--palette-activity-s)\n      calc(var(--palette-activity-l) * 1.3)\n  );\n}\n\n:root {\n  // Shadows\n  --shadow-sm: 0 1px 4px var(--palette-fill);\n  --shadow-md: 0 0 0 1px var(--palette-fill), 0 5px 15px var(--palette-stroke);\n\n  // Overlays\n  --color-overlay: rgb(0 0 0 / 0.5);\n\n  // Filters\n  --filter-hover: brightness(0.95);\n  --filter-active: brightness(0.9);\n\n  // Spacing\n  --ratio: 1.5;\n  --space-px: 1px;\n  --space-xxs: calc(var(--space-xs) / var(--ratio));\n  --space-xs: calc(var(--space-sm) / var(--ratio));\n  --space-sm: calc(var(--space-md) / var(--ratio));\n  --space-md: 1rem;\n  --space-lg: calc(var(--space-md) * var(--ratio));\n  --space-xl: calc(var(--space-lg) * var(--ratio));\n  --space-xxl: calc(var(--space-xl) * var(--ratio));\n  --space-xxxl: calc(var(--space-xxl) * var(--ratio));\n  --space-gutter: clamp(var(--space-lg), 3vw, var(--space-xl));\n\n  // Font\n  --font-system: system-ui;\n  --font-text: var(--font-system);\n  --font-display: var(--font-text);\n  --font-mono:\n    SFMono-Regular, Menlo, Monaco, Consolas, 'Courier New', monospace;\n\n  // Font weights\n  --weight-normal: 400;\n  --weight-medium: 500;\n  --weight-bold: 600;\n\n  // Line height\n  --line-height-default: 1.3;\n  --line-height-condensed: 1.2;\n  --line-height-expanded: 1.5;\n\n  // Typographic measure\n  --measure: 92ch;\n\n  // Z-indexes\n  --z-index-header: 100;\n  --z-index-overlay: 200;\n  --z-index-alerts: 300;\n\n  // Font size\n  --text-xxs: 0.8rem;\n  --text-xs: 0.875rem;\n  --text-sm: 1rem;\n  --text-md: 1.125rem;\n  --text-lg: 1.5rem;\n  --text-xl: 2rem;\n  --text-xxl: 2.5rem;\n\n  // Border radius\n  --radius: 10px;\n\n  // Controls\n  --control-height: 2.5em;\n  --control-height-small: 2.3em;\n  --control-radius: var(--radius);\n\n  // Header\n  --header-height: 4rem;\n}\n\n:root,\n.menu {\n  --color-bg: var(--palette-bg);\n  --color-surface: var(--palette-surface);\n  --color-text: var(--palette-text);\n  --color-muted: var(--palette-muted);\n  --color-fill: var(--palette-fill);\n  --color-fill-soft: var(--palette-fill-soft);\n  --color-stroke: var(--palette-stroke);\n  --color-emphasis: var(--palette-emphasis);\n  --color-emphasis-contrast: var(--palette-emphasis-contrast);\n  --color-accent: var(--palette-accent);\n  --color-accent-contrast: var(--palette-accent-contrast);\n  --color-accent-soft: var(--palette-accent-soft);\n  --color-accent-text: var(--palette-accent-text);\n  --color-danger: var(--palette-danger);\n  --color-danger-contrast: var(--palette-danger-contrast);\n  --color-danger-soft: var(--palette-danger-soft);\n  --color-danger-text: var(--palette-danger-text);\n  --color-warning: var(--palette-warning);\n  --color-warning-contrast: var(--palette-warning-contrast);\n  --color-warning-soft: var(--palette-warning-soft);\n  --color-warning-text: var(--palette-warning-text);\n  --color-success: var(--palette-success);\n  --color-success-contrast: var(--palette-success-contrast);\n  --color-success-soft: var(--palette-success-soft);\n  --color-success-text: var(--palette-success-text);\n  --color-activity: var(--palette-activity);\n  --color-activity-contrast: var(--palette-activity-contrast);\n  --color-activity-soft: var(--palette-activity-soft);\n  --color-activity-text: var(--palette-activity-text);\n}\n"
  },
  {
    "path": "resources/css/system/breakpoints.css",
    "content": "@custom-media --sm (width <= 40rem);\n@custom-media --md-up (width > 40rem);\n@custom-media --md-down (width <= 52rem);\n@custom-media --md (--md-up) and (--md-down);\n@custom-media --lg-up (width > 52rem);\n@custom-media --lg-down (width <= 68rem);\n@custom-media --lg (--lg-up) and (--lg-down);\n@custom-media --xl-up (width > 68rem);\n"
  },
  {
    "path": "resources/css/system/mixins.css",
    "content": "@define-mixin clickable {\n  &:is(:disabled, .is-disabled) {\n    opacity: 0.5;\n    cursor: default;\n  }\n\n  &.is-inert {\n    cursor: default;\n  }\n\n  &:not(:disabled, .is-disabled, .is-inert) {\n    cursor: pointer;\n\n    &:is(:hover, .is-hovered, :focus, .is-focused, [aria-selected='true']) {\n      filter: var(--filter-hover);\n    }\n\n    &:is(:active) {\n      filter: var(--filter-active);\n    }\n\n    &:active {\n      transform: scale(0.97);\n    }\n  }\n}\n\n@define-mixin spinner {\n  width: 2em;\n  height: 2em;\n  border: 0.35em solid var(--color-fill);\n  border-top-color: var(--color-stroke);\n  border-radius: 50%;\n  animation: spinner 0.6s infinite linear;\n}\n"
  },
  {
    "path": "resources/css/system/system.css",
    "content": "@import '_variables.css';\n\n// Base styles\n@import '_reset.css';\n@import '_base.css';\n@import '_typography.css';\n\n// Base components\n@import '_alert.css';\n@import '_alerts.css';\n@import '_attribution.css';\n@import '_avatar.css';\n@import '_badge.css';\n@import '_block-link.css';\n@import '_breadcrumb.css';\n@import '_btn.css';\n@import '_card.css';\n@import '_channel-label.css';\n@import '_channel-picker.css';\n@import '_choice.css';\n@import '_color-picker.css';\n@import '_combobox.css';\n@import '_dialog.css';\n@import '_divider.css';\n@import '_form.css';\n@import '_highlightjs.css';\n@import '_icon.css';\n@import '_input.css';\n@import '_mention.css';\n@import '_menu.css';\n@import '_modal.css';\n@import '_nav.css';\n@import '_placeholder.css';\n@import '_sidebar.css';\n@import '_skip-link.css';\n@import '_spinner.css';\n@import '_table.css';\n@import '_tabs.css';\n@import '_tooltip.css';\n@import '_turbo.css';\n@import '_user-label.css';\n\n// Other components\n@import '_emoji-picker.css';\n\n// Utils\n@import '_utils.css';\n"
  },
  {
    "path": "resources/dist/cp.css",
    "content": ".cp-dashboard__widgets{align-items:stretch;display:flex;flex-wrap:wrap;margin:calc(var(--space-lg)*-1/2)}.cp-dashboard__widgets>*{flex-grow:1;height:auto;height:var(--cp-dashboard-widget-height,auto);min-width:30ch;padding:calc(var(--space-lg)/2);width:100%;width:var(--cp-dashboard-widget-width,100%)}.cp-dashboard__widgets .card,.cp-dashboard__widgets turbo-frame{height:100%}.line-chart-widget{min-height:14em}.getting-started__grid{--grid-min:35ch}.permission-grid thead th{padding-left:var(--space-xxs);padding-right:var(--space-xxs);text-align:center;vertical-align:top;width:9ch}.cp-structure{overflow:hidden;padding:0}.cp-structure li{border-top:1px solid var(--color-stroke)}.cp-structure>:first-child{border-top:0}.cp-structure .placeholder:not(:only-child){display:none}.cp-structure__node{list-style:none}.drag-handle{color:var(--color-muted);cursor:move;display:flex}.drag-handle>*{opacity:.5}.no-js .drag-handle{display:none}[data-dnd-dragging=true]{background:var(--color-bg);border:0!important;border-radius:var(--radius);box-shadow:var(--shadow-md)}.cp-structure__label{font-weight:var(--weight-medium)}.container{max-width:none}.cp__content{margin-left:auto;margin-right:auto;max-width:110ch}.cp-title{margin-bottom:var(--space-lg)}.sortable>*{transition:transform .3s}.cp-help{font-size:var(--text-xs);margin-top:var(--space-xl);text-align:center}.uplot,.uplot *,.uplot :after,.uplot :before{box-sizing:border-box}.uplot{font-family:system-ui,Segoe UI,Ubuntu,Cantarell,Noto Sans,-apple-system,Roboto,Helvetica Neue,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;line-height:1.5;width:-moz-min-content;width:min-content}.u-title{font-size:18px;font-weight:700;text-align:center}.u-wrap{position:relative;-webkit-user-select:none;-moz-user-select:none;user-select:none}.u-over,.u-under{position:absolute}.u-under{overflow:hidden}.uplot canvas{display:block;height:100%;position:relative;width:100%}.u-axis{position:absolute}.u-legend{font-size:14px;margin:auto;text-align:center}.u-inline{display:block}.u-inline *{display:inline-block}.u-inline tr{margin-right:16px}.u-legend th{font-weight:600}.u-legend th>*{display:inline-block;vertical-align:middle}.u-legend .u-marker{background-clip:padding-box!important;height:1em;margin-right:4px;width:1em}.u-inline.u-live th:after{content:\":\";vertical-align:middle}.u-inline:not(.u-live) .u-value{display:none}.u-series>*{padding:4px}.u-series th{cursor:pointer}.u-legend .u-off>*{opacity:.3}.u-select{background:rgba(0,0,0,.07)}.u-cursor-x,.u-cursor-y,.u-select{pointer-events:none;position:absolute}.u-cursor-x,.u-cursor-y{left:0;top:0;will-change:transform}.u-hz .u-cursor-x,.u-vt .u-cursor-y{border-right:1px dashed #607d8b;height:100%}.u-hz .u-cursor-y,.u-vt .u-cursor-x{border-bottom:1px dashed #607d8b;width:100%}.u-cursor-pt{background-clip:padding-box!important;border:0 solid;border-radius:50%;left:0;pointer-events:none;position:absolute;top:0;will-change:transform}.u-axis.u-off,.u-cursor-pt.u-off,.u-cursor-x.u-off,.u-cursor-y.u-off,.u-select.u-off{display:none}"
  },
  {
    "path": "resources/dist/cp.js",
    "content": "(function(){var e=Object.defineProperty,t=(t,n)=>{let r={};for(var i in t)e(r,i,{get:t[i],enumerable:!0});return n&&e(r,Symbol.toStringTag,{value:`Module`}),r};let n=(e,t=0,n=1)=>e>n?n:e<t?t:e,r=(e,t=0,n=10**t)=>Math.round(n*e)/n;360/(Math.PI*2);let i=e=>p(a(e)),a=e=>(e[0]===`#`&&(e=e.substring(1)),e.length<6?{r:parseInt(e[0]+e[0],16),g:parseInt(e[1]+e[1],16),b:parseInt(e[2]+e[2],16),a:e.length===4?r(parseInt(e[3]+e[3],16)/255,2):1}:{r:parseInt(e.substring(0,2),16),g:parseInt(e.substring(2,4),16),b:parseInt(e.substring(4,6),16),a:e.length===8?r(parseInt(e.substring(6,8),16)/255,2):1}),o=e=>f(u(e)),s=({h:e,s:t,v:n,a:i})=>{let a=(200-t)*n/100;return{h:r(e),s:r(a>0&&a<200?t*n/100/(a<=100?a:200-a)*100:0),l:r(a/2),a:r(i,2)}},c=e=>{let{h:t,s:n,l:r}=s(e);return`hsl(${t}, ${n}%, ${r}%)`},l=e=>{let{h:t,s:n,l:r,a:i}=s(e);return`hsla(${t}, ${n}%, ${r}%, ${i})`},u=({h:e,s:t,v:n,a:i})=>{e=e/360*6,t/=100,n/=100;let a=Math.floor(e),o=n*(1-t),s=n*(1-(e-a)*t),c=n*(1-(1-e+a)*t),l=a%6;return{r:r([n,s,o,o,c,n][l]*255),g:r([c,n,n,s,o,o][l]*255),b:r([o,o,c,n,n,s][l]*255),a:r(i,2)}},d=e=>{let t=e.toString(16);return t.length<2?`0`+t:t},f=({r:e,g:t,b:n,a:i})=>{let a=i<1?d(r(i*255)):``;return`#`+d(e)+d(t)+d(n)+a},p=({r:e,g:t,b:n,a:i})=>{let a=Math.max(e,t,n),o=a-Math.min(e,t,n),s=o?a===e?(t-n)/o:a===t?2+(n-e)/o:4+(e-t)/o:0;return{h:r(60*(s<0?s+6:s)),s:r(a?o/a*100:0),v:r(a/255*100),a:i}},m=(e,t)=>{if(e===t)return!0;for(let n in e)if(e[n]!==t[n])return!1;return!0},h=(e,t)=>e.toLowerCase()===t.toLowerCase()?!0:m(a(e),a(t)),g={},_=e=>{let t=g[e];return t||(t=document.createElement(`template`),t.innerHTML=e,g[e]=t),t},v=(e,t,n)=>{e.dispatchEvent(new CustomEvent(t,{bubbles:!0,detail:n}))},y=!1,b=e=>`touches`in e,x=e=>y&&!b(e)?!1:(y||=b(e),!0),S=(e,t)=>{let r=b(t)?t.touches[0]:t,i=e.el.getBoundingClientRect();v(e.el,`move`,e.getMove({x:n((r.pageX-(i.left+window.pageXOffset))/i.width),y:n((r.pageY-(i.top+window.pageYOffset))/i.height)}))},C=(e,t)=>{let n=t.keyCode;n>40||e.xy&&n<37||n<33||(t.preventDefault(),v(e.el,`move`,e.getMove({x:n===39?.01:n===37?-.01:n===34?.05:n===33?-.05:n===35?1:n===36?-1:0,y:n===40?.01:n===38?-.01:0},!0)))};var w=class{constructor(e,t,n,r){let i=_(`<div role=\"slider\" tabindex=\"0\" part=\"${t}\" ${n}><div part=\"${t}-pointer\"></div></div>`);e.appendChild(i.content.cloneNode(!0));let a=e.querySelector(`[part=${t}]`);a.addEventListener(`mousedown`,this),a.addEventListener(`touchstart`,this),a.addEventListener(`keydown`,this),this.el=a,this.xy=r,this.nodes=[a.firstChild,a]}set dragging(e){let t=e?document.addEventListener:document.removeEventListener;t(y?`touchmove`:`mousemove`,this),t(y?`touchend`:`mouseup`,this)}handleEvent(e){switch(e.type){case`mousedown`:case`touchstart`:if(e.preventDefault(),!x(e)||!y&&e.button!=0)return;this.el.focus(),S(this,e),this.dragging=!0;break;case`mousemove`:case`touchmove`:e.preventDefault(),S(this,e);break;case`mouseup`:case`touchend`:this.dragging=!1;break;case`keydown`:C(this,e);break}}style(e){e.forEach((e,t)=>{for(let n in e)this.nodes[t].style.setProperty(n,e[n])})}},T=class extends w{constructor(e){super(e,`hue`,`aria-label=\"Hue\" aria-valuemin=\"0\" aria-valuemax=\"360\"`,!1)}update({h:e}){this.h=e,this.style([{left:`${e/360*100}%`,color:c({h:e,s:100,v:100,a:1})}]),this.el.setAttribute(`aria-valuenow`,`${r(e)}`)}getMove(e,t){return{h:t?n(this.h+e.x*360,0,360):360*e.x}}},E=class extends w{constructor(e){super(e,`saturation`,`aria-label=\"Color\"`,!0)}update(e){this.hsva=e,this.style([{top:`${100-e.v}%`,left:`${e.s}%`,color:c(e)},{\"background-color\":c({h:e.h,s:100,v:100,a:1})}]),this.el.setAttribute(`aria-valuetext`,`Saturation ${r(e.s)}%, Brightness ${r(e.v)}%`)}getMove(e,t){return{s:t?n(this.hsva.s+e.x*100,0,100):e.x*100,v:t?n(this.hsva.v-e.y*100,0,100):Math.round(100-e.y*100)}}},D=`:host{display:flex;flex-direction:column;position:relative;width:200px;height:200px;user-select:none;-webkit-user-select:none;cursor:default}:host([hidden]){display:none!important}[role=slider]{position:relative;touch-action:none;user-select:none;-webkit-user-select:none;outline:0}[role=slider]:last-child{border-radius:0 0 8px 8px}[part$=pointer]{position:absolute;z-index:1;box-sizing:border-box;width:28px;height:28px;display:flex;place-content:center center;transform:translate(-50%,-50%);background-color:#fff;border:2px solid #fff;border-radius:50%;box-shadow:0 2px 4px rgba(0,0,0,.2)}[part$=pointer]::after{content:\"\";width:100%;height:100%;border-radius:inherit;background-color:currentColor}[role=slider]:focus [part$=pointer]{transform:translate(-50%,-50%) scale(1.1)}`,O=`[part=hue]{flex:0 0 24px;background:linear-gradient(to right,red 0,#ff0 17%,#0f0 33%,#0ff 50%,#00f 67%,#f0f 83%,red 100%)}[part=hue-pointer]{top:50%;z-index:2}`,ee=`[part=saturation]{flex-grow:1;border-color:transparent;border-bottom:12px solid #000;border-radius:8px 8px 0 0;background-image:linear-gradient(to top,#000,transparent),linear-gradient(to right,#fff,rgba(255,255,255,0));box-shadow:inset 0 0 0 1px rgba(0,0,0,.05)}[part=saturation-pointer]{z-index:3}`;let te=Symbol(`same`),k=Symbol(`color`),ne=Symbol(`hsva`),re=Symbol(`update`),ie=Symbol(`parts`),ae=Symbol(`css`),oe=Symbol(`sliders`);var se=class extends HTMLElement{static get observedAttributes(){return[`color`]}get[ae](){return[D,O,ee]}get[oe](){return[E,T]}get color(){return this[k]}set color(e){if(!this[te](e)){let t=this.colorModel.toHsva(e);this[re](t),this[k]=e}}constructor(){super();let e=_(`<style>${this[ae].join(``)}</style>`),t=this.attachShadow({mode:`open`});t.appendChild(e.content.cloneNode(!0)),t.addEventListener(`move`,this),this[ie]=this[oe].map(e=>new e(t))}connectedCallback(){if(this.hasOwnProperty(`color`)){let e=this.color;delete this.color,this.color=e}else this.color||=this.colorModel.defaultColor}attributeChangedCallback(e,t,n){let r=this.colorModel.fromAttr(n);this[te](r)||(this.color=r)}handleEvent(e){let t=this[ne],n={...t,...e.detail};this[re](n);let r;!m(n,t)&&!this[te](r=this.colorModel.fromHsva(n))&&(this[k]=r,v(this,`color-changed`,{value:r}))}[te](e){return this.color&&this.colorModel.equal(e,this.color)}[re](e){this[ne]=e,this[ie].forEach(t=>t.update(e))}},A=class extends w{constructor(e){super(e,`alpha`,`aria-label=\"Alpha\" aria-valuemin=\"0\" aria-valuemax=\"1\"`,!1)}update(e){this.hsva=e;let t=l({...e,a:0}),n=l({...e,a:1}),i=e.a*100;this.style([{left:`${i}%`,color:l(e)},{\"--gradient\":`linear-gradient(90deg, ${t}, ${n}`}]);let a=r(i);this.el.setAttribute(`aria-valuenow`,`${a}`),this.el.setAttribute(`aria-valuetext`,`${a}%`)}getMove(e,t){return{a:t?n(this.hsva.a+e.x):e.x}}},j=`[part=alpha]{flex:0 0 24px}[part=alpha]::after{display:block;content:\"\";position:absolute;top:0;left:0;right:0;bottom:0;border-radius:inherit;background-image:var(--gradient);box-shadow:inset 0 0 0 1px rgba(0,0,0,.05)}[part^=alpha]{background-color:#fff;background-image:url('data:image/svg+xml,<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" fill-opacity=\".05\"><rect x=\"8\" width=\"8\" height=\"8\"/><rect y=\"8\" width=\"8\" height=\"8\"/></svg>')}[part=alpha-pointer]{top:50%}`,M=class extends se{get[ae](){return[...super[ae],j]}get[oe](){return[...super[oe],A]}};let ce={defaultColor:`#0001`,toHsva:i,fromHsva:o,equal:h,fromAttr:e=>e};var N=class extends M{get colorModel(){return ce}},P=class extends N{};customElements.define(`hex-alpha-color-picker`,P);let le=/^#?([0-9A-F]{3,8})$/i,ue=(e,t)=>{let n=le.exec(e),r=n?n[1].length:0;return r===3||r===6||!!t&&r===4||!!t&&r===8},de=_(`<slot><input part=\"input\" spellcheck=\"false\"></slot>`),fe=(e,t)=>e.replace(/([^0-9A-F]+)/gi,``).substring(0,t?8:6),pe=Symbol(`alpha`),me=Symbol(`color`),he=Symbol(`saved`),ge=Symbol(`input`),_e=Symbol(`init`),ve=Symbol(`prefix`),ye=Symbol(`update`);var be=class extends HTMLElement{static get observedAttributes(){return[`alpha`,`color`,`prefixed`]}get color(){return this[me]}set color(e){this[me]=e,this[ye](e)}get alpha(){return this[pe]}set alpha(e){this[pe]=e,this.toggleAttribute(`alpha`,e);let t=this.color;t&&!ue(t,e)&&(this.color=t.startsWith(`#`)?t.substring(0,t.length===5?4:7):t.substring(0,t.length===4?3:6))}get prefixed(){return this[ve]}set prefixed(e){this[ve]=e,this.toggleAttribute(`prefixed`,e),this[ye](this.color)}constructor(){super();let e=this.attachShadow({mode:`open`});e.appendChild(de.content.cloneNode(!0)),e.firstElementChild.addEventListener(`slotchange`,()=>this[_e](e))}connectedCallback(){if(this[_e](this.shadowRoot),this.hasOwnProperty(`alpha`)){let e=this.alpha;delete this.alpha,this.alpha=e}else this.alpha=this.hasAttribute(`alpha`);if(this.hasOwnProperty(`prefixed`)){let e=this.prefixed;delete this.prefixed,this.prefixed=e}else this.prefixed=this.hasAttribute(`prefixed`);if(this.hasOwnProperty(`color`)){let e=this.color;delete this.color,this.color=e}else this.color==null?this.color=this.getAttribute(`color`)||``:this[me]&&this[ye](this[me])}handleEvent(e){let{value:t}=e.target;switch(e.type){case`input`:let e=fe(t,this.alpha);this[he]=this.color,(ue(e,this.alpha)||t===``)&&(this.color=e,this.dispatchEvent(new CustomEvent(`color-changed`,{bubbles:!0,detail:{value:e?`#`+e:``}})));break;case`blur`:t&&!ue(t,this.alpha)&&(this.color=this[he])}}attributeChangedCallback(e,t,n){e===`color`&&this.color!==n&&(this.color=n);let r=n!=null;e===`alpha`&&this.alpha!==r&&(this.alpha=r),e===`prefixed`&&this.prefixed!==r&&(this.prefixed=r)}[_e](e){let t=this.querySelector(`input`);if(!t){let n;for(;n=this.firstChild;)n.remove();t=e.querySelector(`input`)}t.addEventListener(`input`,this),t.addEventListener(`blur`,this),this[ge]=t,this[ye](this.color)}[ye](e){this[ge]&&(this[ge].value=e==null||e==``?``:(this.prefixed?`#`:``)+fe(e,this.alpha))}},xe=class extends be{};customElements.define(`hex-input`,xe);function Se(e){return e.toLowerCase().replace(/[^a-z0-9]/gi,`-`).replace(/-+/g,`-`).replace(/-$|^-/g,``)}function Ce(e){return Object.entries(e).map(([e,t])=>({identifier:e.match(/\\.\\/controllers\\/(.*)\\.ts$/)[1].replace(/\\//g,`--`).replace(/_/g,`-`).replace(/-controller$/,``),controllerConstructor:t.default}))}function we(e){return e.replace(/(?:[_-])([a-z0-9])/g,(e,t)=>t.toUpperCase())}function F(e){return we(e.replace(/--/g,`-`).replace(/__/g,`_`))}function I(e){return e.charAt(0).toUpperCase()+e.slice(1)}function Te(e){return e.replace(/([A-Z])/g,(e,t)=>`-${t.toLowerCase()}`)}function Ee(e){return e!=null}function De(e,t){return Object.prototype.hasOwnProperty.call(e,t)}function Oe(e,t){let n=Ae(e);return Array.from(n.reduce((e,n)=>(je(n,t).forEach(t=>e.add(t)),e),new Set))}function ke(e,t){return Ae(e).reduce((e,n)=>(e.push(...Me(n,t)),e),[])}function Ae(e){let t=[];for(;e;)t.push(e),e=Object.getPrototypeOf(e);return t.reverse()}function je(e,t){let n=e[t];return Array.isArray(n)?n:[]}function Me(e,t){let n=e[t];return n?Object.keys(n).map(e=>[e,n[e]]):[]}typeof Object.getOwnPropertySymbols==`function`||Object.getOwnPropertyNames,(()=>{function e(e){function t(){return Reflect.construct(e,arguments,new.target)}return t.prototype=Object.create(e.prototype,{constructor:{value:t}}),Reflect.setPrototypeOf(t,e),t}function t(){let t=e(function(){this.a.call(this)});return t.prototype.a=function(){},new t}try{return t(),e}catch{return e=>class extends e{}}})(),Object.assign(Object.assign({enter:`Enter`,tab:`Tab`,esc:`Escape`,space:` `,up:`ArrowUp`,down:`ArrowDown`,left:`ArrowLeft`,right:`ArrowRight`,home:`Home`,end:`End`,page_up:`PageUp`,page_down:`PageDown`},Ne(`abcdefghijklmnopqrstuvwxyz`.split(``).map(e=>[e,e]))),Ne(`0123456789`.split(``).map(e=>[e,e])));function Ne(e){return e.reduce((e,[t,n])=>Object.assign(Object.assign({},e),{[t]:n}),{})}function Pe(e){return Oe(e,`classes`).reduce((e,t)=>Object.assign(e,Fe(t)),{})}function Fe(e){return{[`${e}Class`]:{get(){let{classes:t}=this;if(t.has(e))return t.get(e);{let n=t.getAttributeName(e);throw Error(`Missing attribute \"${n}\"`)}}},[`${e}Classes`]:{get(){return this.classes.getAll(e)}},[`has${I(e)}Class`]:{get(){return this.classes.has(e)}}}}function Ie(e){return Oe(e,`outlets`).reduce((e,t)=>Object.assign(e,ze(t)),{})}function Le(e,t,n){return e.application.getControllerForElementAndIdentifier(t,n)}function Re(e,t,n){let r=Le(e,t,n);if(r||(e.application.router.proposeToConnectScopeForElementAndIdentifier(t,n),r=Le(e,t,n),r))return r}function ze(e){let t=F(e);return{[`${t}Outlet`]:{get(){let t=this.outlets.find(e),n=this.outlets.getSelectorForOutletName(e);if(t){let n=Re(this,t,e);if(n)return n;throw Error(`The provided outlet element is missing an outlet controller \"${e}\" instance for host controller \"${this.identifier}\"`)}throw Error(`Missing outlet element \"${e}\" for host controller \"${this.identifier}\". Stimulus couldn't find a matching outlet element using selector \"${n}\".`)}},[`${t}Outlets`]:{get(){let t=this.outlets.findAll(e);return t.length>0?t.map(t=>{let n=Re(this,t,e);if(n)return n;console.warn(`The provided outlet element is missing an outlet controller \"${e}\" instance for host controller \"${this.identifier}\"`,t)}).filter(e=>e):[]}},[`${t}OutletElement`]:{get(){let t=this.outlets.find(e),n=this.outlets.getSelectorForOutletName(e);if(t)return t;throw Error(`Missing outlet element \"${e}\" for host controller \"${this.identifier}\". Stimulus couldn't find a matching outlet element using selector \"${n}\".`)}},[`${t}OutletElements`]:{get(){return this.outlets.findAll(e)}},[`has${I(t)}Outlet`]:{get(){return this.outlets.has(e)}}}}function Be(e){return Oe(e,`targets`).reduce((e,t)=>Object.assign(e,Ve(t)),{})}function Ve(e){return{[`${e}Target`]:{get(){let t=this.targets.find(e);if(t)return t;throw Error(`Missing target element \"${e}\" for \"${this.identifier}\" controller`)}},[`${e}Targets`]:{get(){return this.targets.findAll(e)}},[`has${I(e)}Target`]:{get(){return this.targets.has(e)}}}}function He(e){let t=ke(e,`values`);return t.reduce((e,t)=>Object.assign(e,Ue(t)),{valueDescriptorMap:{get(){return t.reduce((e,t)=>{let n=We(t,this.identifier),r=this.data.getAttributeNameForKey(n.key);return Object.assign(e,{[r]:n})},{})}}})}function Ue(e,t){let n=We(e,t),{key:r,name:i,reader:a,writer:o}=n;return{[i]:{get(){let e=this.data.get(r);return e===null?n.defaultValue:a(e)},set(e){e===void 0?this.data.delete(r):this.data.set(r,o(e))}},[`has${I(i)}`]:{get(){return this.data.has(r)||n.hasCustomDefaultValue}}}}function We([e,t],n){return Xe({controller:n,token:e,typeDefinition:t})}function Ge(e){switch(e){case Array:return`array`;case Boolean:return`boolean`;case Number:return`number`;case Object:return`object`;case String:return`string`}}function Ke(e){switch(typeof e){case`boolean`:return`boolean`;case`number`:return`number`;case`string`:return`string`}if(Array.isArray(e))return`array`;if(Object.prototype.toString.call(e)===`[object Object]`)return`object`}function qe(e){let{controller:t,token:n,typeObject:r}=e,i=Ee(r.type),a=Ee(r.default),o=i&&a,s=i&&!a,c=!i&&a,l=Ge(r.type),u=Ke(e.typeObject.default);if(s)return l;if(c)return u;if(l!==u){let e=t?`${t}.${n}`:n;throw Error(`The specified default value for the Stimulus Value \"${e}\" must match the defined type \"${l}\". The provided default value of \"${r.default}\" is of type \"${u}\".`)}if(o)return l}function Je(e){let{controller:t,token:n,typeDefinition:r}=e,i=qe({controller:t,token:n,typeObject:r}),a=Ke(r),o=Ge(r),s=i||a||o;if(s)return s;let c=t?`${t}.${r}`:n;throw Error(`Unknown value type \"${c}\" for \"${n}\" value`)}function Ye(e){let t=Ge(e);if(t)return Ze[t];let n=De(e,`default`),r=De(e,`type`),i=e;if(n)return i.default;if(r){let{type:e}=i,t=Ge(e);if(t)return Ze[t]}return e}function Xe(e){let{token:t,typeDefinition:n}=e,r=`${Te(t)}-value`,i=Je(e);return{type:i,key:r,name:we(r),get defaultValue(){return Ye(n)},get hasCustomDefaultValue(){return Ke(n)!==void 0},reader:Qe[i],writer:$e[i]||$e.default}}let Ze={get array(){return[]},boolean:!1,number:0,get object(){return{}},string:``},Qe={array(e){let t=JSON.parse(e);if(!Array.isArray(t))throw TypeError(`expected value of type \"array\" but instead got value \"${e}\" of type \"${Ke(t)}\"`);return t},boolean(e){return!(e==`0`||String(e).toLowerCase()==`false`)},number(e){return Number(e.replace(/_/g,``))},object(e){let t=JSON.parse(e);if(typeof t!=`object`||!t||Array.isArray(t))throw TypeError(`expected value of type \"object\" but instead got value \"${e}\" of type \"${Ke(t)}\"`);return t},string(e){return e}},$e={default:tt,array:et,object:et};function et(e){return JSON.stringify(e)}function tt(e){return`${e}`}var nt=class{constructor(e){this.context=e}static get shouldLoad(){return!0}static afterLoad(e,t){}get application(){return this.context.application}get scope(){return this.context.scope}get element(){return this.scope.element}get identifier(){return this.scope.identifier}get targets(){return this.scope.targets}get outlets(){return this.scope.outlets}get classes(){return this.scope.classes}get data(){return this.scope.data}initialize(){}connect(){}disconnect(){}dispatch(e,{target:t=this.element,detail:n={},prefix:r=this.identifier,bubbles:i=!0,cancelable:a=!0}={}){let o=r?`${r}:${e}`:e,s=new CustomEvent(o,{detail:n,bubbles:i,cancelable:a});return t.dispatchEvent(s),s}};nt.blessings=[Pe,Be,He,Ie],nt.targets=[],nt.outlets=[],nt.values={};var rt=t({default:()=>it}),it=class extends nt{static{this.targets=[`input`,`picker`,`swatch`]}show(){clearTimeout(this.timeout),this.pickerTarget.hidden=!1}hide(){clearTimeout(this.timeout),this.timeout=window.setTimeout(()=>this.pickerTarget.hidden=!0)}colorChanged(e){this.inputTarget.color=e.detail.value,this.pickerTarget.color=e.detail.value,this.swatchTarget.style.backgroundColor=e.detail.value}},at=class{constructor(e,t,{tabInsertsSuggestions:n,firstOptionSelectionMode:r,scrollIntoViewOptions:i}={}){this.input=e,this.list=t,this.tabInsertsSuggestions=n??!0,this.firstOptionSelectionMode=r??`none`,this.scrollIntoViewOptions=i??{block:`nearest`,inline:`nearest`},this.isComposing=!1,t.id||=`combobox-${Math.random().toString().slice(2,6)}`,this.ctrlBindings=!!navigator.userAgent.match(/Macintosh/),this.keyboardEventHandler=e=>ot(e,this),this.compositionEventHandler=e=>ft(e,this),this.inputHandler=this.clearSelection.bind(this),e.setAttribute(`role`,`combobox`),e.setAttribute(`aria-controls`,t.id),e.setAttribute(`aria-expanded`,`false`),e.setAttribute(`aria-autocomplete`,`list`),e.setAttribute(`aria-haspopup`,`listbox`)}destroy(){this.clearSelection(),this.stop(),this.input.removeAttribute(`role`),this.input.removeAttribute(`aria-controls`),this.input.removeAttribute(`aria-expanded`),this.input.removeAttribute(`aria-autocomplete`),this.input.removeAttribute(`aria-haspopup`)}start(){this.input.setAttribute(`aria-expanded`,`true`),this.input.addEventListener(`compositionstart`,this.compositionEventHandler),this.input.addEventListener(`compositionend`,this.compositionEventHandler),this.input.addEventListener(`input`,this.inputHandler),this.input.addEventListener(`keydown`,this.keyboardEventHandler),this.list.addEventListener(`click`,st),this.resetSelection()}stop(){this.clearSelection(),this.input.setAttribute(`aria-expanded`,`false`),this.input.removeEventListener(`compositionstart`,this.compositionEventHandler),this.input.removeEventListener(`compositionend`,this.compositionEventHandler),this.input.removeEventListener(`input`,this.inputHandler),this.input.removeEventListener(`keydown`,this.keyboardEventHandler),this.list.removeEventListener(`click`,st)}indicateDefaultOption(){var e;this.firstOptionSelectionMode===`active`?(e=Array.from(this.list.querySelectorAll(`[role=\"option\"]:not([aria-disabled=\"true\"])`)).filter(dt)[0])==null||e.setAttribute(`data-combobox-option-default`,`true`):this.firstOptionSelectionMode===`selected`&&this.navigate(1)}navigate(e=1){let t=Array.from(this.list.querySelectorAll(`[aria-selected=\"true\"]`)).filter(dt)[0],n=Array.from(this.list.querySelectorAll(`[role=\"option\"]`)).filter(dt),r=n.indexOf(t);if(r===n.length-1&&e===1||r===0&&e===-1){this.clearSelection(),this.input.focus();return}let i=e===1?0:n.length-1;if(t&&r>=0){let t=r+e;t>=0&&t<n.length&&(i=t)}let a=n[i];if(a)for(let e of n)e.removeAttribute(`data-combobox-option-default`),a===e?(this.input.setAttribute(`aria-activedescendant`,a.id),a.setAttribute(`aria-selected`,`true`),ut(a),a.scrollIntoView(this.scrollIntoViewOptions)):e.removeAttribute(`aria-selected`)}clearSelection(){this.input.removeAttribute(`aria-activedescendant`);for(let e of this.list.querySelectorAll(`[aria-selected=\"true\"], [data-combobox-option-default=\"true\"]`))e.removeAttribute(`aria-selected`),e.removeAttribute(`data-combobox-option-default`)}resetSelection(){this.clearSelection(),this.indicateDefaultOption()}};function ot(e,t){if(!(e.shiftKey||e.metaKey||e.altKey)&&!(!t.ctrlBindings&&e.ctrlKey)&&!t.isComposing)switch(e.key){case`Enter`:ct(t.input,t.list)&&e.preventDefault();break;case`Tab`:t.tabInsertsSuggestions&&ct(t.input,t.list)&&e.preventDefault();break;case`Escape`:t.clearSelection();break;case`ArrowDown`:t.navigate(1),e.preventDefault();break;case`ArrowUp`:t.navigate(-1),e.preventDefault();break;case`n`:t.ctrlBindings&&e.ctrlKey&&(t.navigate(1),e.preventDefault());break;case`p`:t.ctrlBindings&&e.ctrlKey&&(t.navigate(-1),e.preventDefault());break;default:if(e.ctrlKey)break;t.resetSelection()}}function st(e){if(!(e.target instanceof Element))return;let t=e.target.closest(`[role=\"option\"]`);t&&t.getAttribute(`aria-disabled`)!==`true`&&lt(t,{event:e})}function ct(e,t){let n=t.querySelector(`[aria-selected=\"true\"], [data-combobox-option-default=\"true\"]`);return n?(n.getAttribute(`aria-disabled`)===`true`||n.click(),!0):!1}function lt(e,t){e.dispatchEvent(new CustomEvent(`combobox-commit`,{bubbles:!0,detail:t}))}function ut(e){e.dispatchEvent(new Event(`combobox-select`,{bubbles:!0}))}function dt(e){return!e.hidden&&!(e instanceof HTMLInputElement&&e.type===`hidden`)&&(e.offsetWidth>0||e.offsetHeight>0)}function ft(e,t){t.isComposing=e.type===`compositionstart`,document.getElementById(t.input.getAttribute(`aria-controls`)||``)&&t.clearSelection()}function pt(e){return e instanceof HTMLInputElement||e instanceof HTMLTextAreaElement}function mt(e,t){let n=e.ownerDocument.activeElement;if(n===e)return t();try{return e.focus(),t()}finally{e.blur(),n instanceof HTMLElement&&n.focus()}}function ht(e,t){t===``?e.execCommand(`delete`):e.execCommand(`insertText`,!1,t)}function gt(e,t){mt(e,()=>{ht(e.ownerDocument,t)})}function _t(e,t){if(pt(e))e.select(),gt(e,t);else{let n=e.ownerDocument;mt(e,()=>{n.execCommand(`selectAll`,!1,t),ht(n,t)})}}let vt=_t;var yt=t({default:()=>xt});let bt=/([^\\s\"]*)\"([^\"]*)(?:\"|$)|[^\\s\"]+/gi;var xt=class extends nt{static{this.targets=[`input`,`list`]}connect(){this.combobox=new at(this.inputTarget,this.listTarget)}focus(){this.combobox?.start(),this.update()}blur(){this.combobox?.stop(),this.listTarget.hidden=!0}tokens(){let e=this.inputTarget.value.matchAll(bt);return Array.from(e).reverse()}currentToken(){let e=this.inputTarget.selectionStart||0;return this.tokens().find(t=>t.index!==void 0&&t.index<e&&t.index+t[0].length>=e)}update(){let e=this.tokens(),t=this.currentToken(),n=t&&t[0].toLowerCase(),r=Array.from(this.listTarget.children),i=[];r.forEach(t=>{let r=(t.dataset.value||t.textContent)?.trim().toLowerCase()||``;e.some(e=>e[0].toLowerCase()===r)?t.hidden=!0:n?t.hidden=!r.startsWith(n)||n.includes(`:`)===r.endsWith(`:`):(t.hidden=i.some(e=>r.startsWith(e)),i.push(r))}),this.listTarget.hidden=!r.some(e=>!e.hidden)}commit(e){let t=e.target,n=this.currentToken(),r=(t.dataset.value||t.textContent)?.trim()||``;vt(this.inputTarget,this.inputTarget.value.slice(0,n?.index)+r+(r.endsWith(`:`)?``:` `))}preventBlur(e){e.preventDefault()}},St=t({default:()=>Ct}),Ct=class extends nt{static{this.targets=[`form`]}submit(){setTimeout(()=>{this.formTarget.requestSubmit()})}},wt=t({default:()=>Tt}),Tt=class extends nt{static{this.targets=[`preview`,`form`,`emoji`]}change(){this.previewTarget.hidden=!0,this.formTarget.hidden=!1}emojiTargetConnected(e){let t=e.querySelector(`input`),n=e.querySelector(`ui-popup`),r=e.querySelector(`button`),i=e.querySelector(`emoji-picker`);t.type=`hidden`,n.hidden=!1,i.addEventListener(`emoji-click`,e=>{t.value=e.detail.unicode||``,r.innerHTML=t.value,n.open=!1})}},Et=typeof global==`object`&&global&&global.Object===Object&&global,Dt=typeof self==`object`&&self&&self.Object===Object&&self,Ot=Et||Dt||Function(`return this`)(),kt=Ot.Symbol,At=Object.prototype,jt=At.hasOwnProperty,Mt=At.toString,Nt=kt?kt.toStringTag:void 0;function Pt(e){var t=jt.call(e,Nt),n=e[Nt];try{e[Nt]=void 0;var r=!0}catch{}var i=Mt.call(e);return r&&(t?e[Nt]=n:delete e[Nt]),i}var Ft=Pt,It=Object.prototype.toString;function Lt(e){return It.call(e)}var Rt=Lt,zt=`[object Null]`,Bt=`[object Undefined]`,Vt=kt?kt.toStringTag:void 0;function Ht(e){return e==null?e===void 0?Bt:zt:Vt&&Vt in Object(e)?Ft(e):Rt(e)}var Ut=Ht;function Wt(e){return typeof e==`object`&&!!e}var Gt=Wt,Kt=`[object Symbol]`;function qt(e){return typeof e==`symbol`||Gt(e)&&Ut(e)==Kt}var Jt=qt,Yt=/\\s/;function Xt(e){for(var t=e.length;t--&&Yt.test(e.charAt(t)););return t}var Zt=Xt,Qt=/^\\s+/;function $t(e){return e&&e.slice(0,Zt(e)+1).replace(Qt,``)}var en=$t;function tn(e){var t=typeof e;return e!=null&&(t==`object`||t==`function`)}var nn=tn,rn=NaN,an=/^[-+]0x[0-9a-f]+$/i,on=/^0b[01]+$/i,sn=/^0o[0-7]+$/i,cn=parseInt;function ln(e){if(typeof e==`number`)return e;if(Jt(e))return rn;if(nn(e)){var t=typeof e.valueOf==`function`?e.valueOf():e;e=nn(t)?t+``:t}if(typeof e!=`string`)return e===0?e:+e;e=en(e);var n=on.test(e);return n||sn.test(e)?cn(e.slice(2),n?2:8):an.test(e)?rn:+e}var un=ln,dn=function(){return Ot.Date.now()},L=`Expected a function`,R=Math.max,fn=Math.min;function pn(e,t,n){var r,i,a,o,s,c,l=0,u=!1,d=!1,f=!0;if(typeof e!=`function`)throw TypeError(L);t=un(t)||0,nn(n)&&(u=!!n.leading,d=`maxWait`in n,a=d?R(un(n.maxWait)||0,t):a,f=`trailing`in n?!!n.trailing:f);function p(t){var n=r,a=i;return r=i=void 0,l=t,o=e.apply(a,n),o}function m(e){return l=e,s=setTimeout(_,t),u?p(e):o}function h(e){var n=e-c,r=e-l,i=t-n;return d?fn(i,a-r):i}function g(e){var n=e-c,r=e-l;return c===void 0||n>=t||n<0||d&&r>=a}function _(){var e=dn();if(g(e))return v(e);s=setTimeout(_,h(e))}function v(e){return s=void 0,f&&r?p(e):(r=i=void 0,o)}function y(){s!==void 0&&clearTimeout(s),l=0,r=c=i=s=void 0}function b(){return s===void 0?o:v(dn())}function x(){var e=dn(),n=g(e);if(r=arguments,i=this,c=e,n){if(s===void 0)return m(c);if(d)return clearTimeout(s),s=setTimeout(_,t),p(c)}return s===void 0&&(s=setTimeout(_,t)),o}return x.cancel=y,x.flush=b,x}var mn=pn,hn=t({default:()=>z}),z=class extends nt{constructor(...e){super(...e),this.debouncedSubmit=mn(this.submit,250)}input(e){e.target.value?this.debouncedSubmit():this.submit()}submit(){this.element.form?.requestSubmit()}};let gn=`u-off`,_n=`u-label`,vn=`width`,yn=`height`,bn=`bottom`,xn=`left`,Sn=`right`,Cn=`#000`,wn=Cn+`0`,Tn=`mousemove`,En=`mousedown`,Dn=`mouseup`,On=`mouseenter`,kn=`mouseleave`,An=`dblclick`,jn=`change`,Mn=`dppxchange`,Nn=typeof window<`u`,Pn=Nn?document:null,Fn=Nn?window:null,In=Nn?navigator:null,B,Ln;function Rn(){let e=devicePixelRatio;B!=e&&(B=e,Ln&&Qn(jn,Ln,Rn),Ln=matchMedia(`(min-resolution: ${B-.001}dppx) and (max-resolution: ${B+.001}dppx)`),Zn(jn,Ln,Rn),Fn.dispatchEvent(new CustomEvent(Mn)))}function zn(e,t){if(t!=null){let n=e.classList;!n.contains(t)&&n.add(t)}}function Bn(e,t){let n=e.classList;n.contains(t)&&n.remove(t)}function V(e,t,n){e.style[t]=n+`px`}function Vn(e,t,n,r){let i=Pn.createElement(e);return t!=null&&zn(i,t),n?.insertBefore(i,r),i}function Hn(e,t){return Vn(`div`,e,t)}let Un=new WeakMap;function Wn(e,t,n,r,i){let a=`translate(`+t+`px,`+n+`px)`;a!=Un.get(e)&&(e.style.transform=a,Un.set(e,a),t<0||n<0||t>r||n>i?zn(e,gn):Bn(e,gn))}let Gn=new WeakMap;function Kn(e,t,n){let r=t+n;r!=Gn.get(e)&&(Gn.set(e,r),e.style.background=t,e.style.borderColor=n)}let qn=new WeakMap;function Jn(e,t,n,r){let i=t+``+n;i!=qn.get(e)&&(qn.set(e,i),e.style.height=n+`px`,e.style.width=t+`px`,e.style.marginLeft=r?-t/2+`px`:0,e.style.marginTop=r?-n/2+`px`:0)}let Yn={passive:!0},Xn={...Yn,capture:!0};function Zn(e,t,n,r){t.addEventListener(e,n,r?Xn:Yn)}function Qn(e,t,n,r){t.removeEventListener(e,n,Yn)}Nn&&Rn();function $n(e,t,n,r){let i;n||=0,r||=t.length-1;let a=r<=2147483647;for(;r-n>1;)i=a?n+r>>1:br((n+r)/2),t[i]<e?n=i:r=i;return e-t[n]<=t[r]-e?n:r}function er(e){return(t,n,r)=>{let i=-1,a=-1;for(let a=n;a<=r;a++)if(e(t[a])){i=a;break}for(let i=r;i>=n;i--)if(e(t[i])){a=i;break}return[i,a]}}let tr=e=>e!=null,nr=e=>e!=null&&e>0,rr=er(tr),ir=er(nr);function ar(e,t,n,r=0,i=!1){let a=i?ir:rr,o=i?nr:tr;[t,n]=a(e,t,n);let s=e[t],c=e[t];if(t>-1)if(r==1)s=e[t],c=e[n];else if(r==-1)s=e[n],c=e[t];else for(let r=t;r<=n;r++){let t=e[r];o(t)&&(t<s?s=t:t>c&&(c=t))}return[s??U,c??-U]}function or(e,t,n,r){let i=Er(e),a=Er(t);e==t&&(i==-1?(e*=n,t/=n):(e/=n,t*=n));let o=n==10?Dr:Or,s=i==1?br:Sr,c=a==1?Sr:br,l=s(o(yr(e))),u=c(o(yr(t))),d=Tr(n,l),f=Tr(n,u);return n==10&&(l<0&&(d=G(d,-l)),u<0&&(f=G(f,-u))),r||n==2?(e=d*i,t=f*a):(e=Wr(e,d),t=Ur(t,f)),[e,t]}function sr(e,t,n,r){let i=or(e,t,n,r);return e==0&&(i[0]=0),t==0&&(i[1]=0),i}let cr=.1,lr={mode:3,pad:cr},ur={pad:0,soft:null,mode:0},dr={min:ur,max:ur};function fr(e,t,n,r){return ti(n)?mr(e,t,n):(ur.pad=n,ur.soft=r?0:null,ur.mode=r?3:0,mr(e,t,dr))}function H(e,t){return e??t}function pr(e,t,n){for(t=H(t,0),n=H(n,e.length-1);t<=n;){if(e[t]!=null)return!0;t++}return!1}function mr(e,t,n){let r=n.min,i=n.max,a=H(r.pad,0),o=H(i.pad,0),s=H(r.hard,-U),c=H(i.hard,U),l=H(r.soft,U),u=H(i.soft,-U),d=H(r.mode,0),f=H(i.mode,0),p=t-e,m=Dr(p),h=wr(yr(e),yr(t)),g=yr(Dr(h)-m);(p<1e-24||g>10)&&(p=0,(e==0||t==0)&&(p=1e-24,d==2&&l!=U&&(a=0),f==2&&u!=-U&&(o=0)));let _=p||h||1e3,v=Tr(10,br(Dr(_))),y=G(Wr(e-_*(p==0?e==0?.1:1:a),v/10),24),b=e>=l&&(d==1||d==3&&y<=l||d==2&&y>=l)?l:U,x=wr(s,y<b&&e>=b?b:Cr(b,y)),S=G(Ur(t+_*(p==0?t==0?.1:1:o),v/10),24),C=t<=u&&(f==1||f==3&&S>=u||f==2&&S<=u)?u:-U,w=Cr(c,S>C&&t<=C?C:wr(C,S));return x==w&&x==0&&(w=100),[x,w]}let hr=new Intl.NumberFormat(Nn?In.language:`en-US`),gr=e=>hr.format(e),_r=Math,vr=_r.PI,yr=_r.abs,br=_r.floor,xr=_r.round,Sr=_r.ceil,Cr=_r.min,wr=_r.max,Tr=_r.pow,Er=_r.sign,Dr=_r.log10,Or=_r.log2,kr=(e,t=1)=>_r.sinh(e)*t,Ar=(e,t=1)=>_r.asinh(e/t),U=1/0;function jr(e){return(Dr((e^e>>31)-(e>>31))|0)+1}function Mr(e,t,n){return Cr(wr(e,t),n)}function Nr(e){return typeof e==`function`}function W(e){return Nr(e)?e:()=>e}let Pr=()=>{},Fr=e=>e,Ir=(e,t)=>t,Lr=e=>null,Rr=e=>!0,zr=(e,t)=>e==t,Br=/\\.\\d*?(?=9{6,}|0{6,})/gm,Vr=e=>{if(Qr(e)||Gr.has(e))return e;let t=`${e}`,n=t.match(Br);if(n==null)return e;let r=n[0].length-1;if(t.indexOf(`e-`)!=-1){let[e,n]=t.split(`e`);return+`${Vr(e)}e${n}`}return G(e,r)};function Hr(e,t){return Vr(G(Vr(e/t))*t)}function Ur(e,t){return Vr(Sr(Vr(e/t))*t)}function Wr(e,t){return Vr(br(Vr(e/t))*t)}function G(e,t=0){if(Qr(e))return e;let n=10**t;return xr(e*n*(1+2**-52))/n}let Gr=new Map;function Kr(e){return((``+e).split(`.`)[1]||``).length}function qr(e,t,n,r){let i=[],a=r.map(Kr);for(let o=t;o<n;o++){let t=yr(o),n=G(Tr(e,o),t);for(let s=0;s<r.length;s++){let c=e==10?+`${r[s]}e${o}`:r[s]*n,l=(o>=0?0:t)+(o>=a[s]?0:a[s]),u=e==10?c:G(c,l);i.push(u),Gr.set(u,l)}}return i}let Jr={},Yr=[],Xr=[null,null],Zr=Array.isArray,Qr=Number.isInteger,$r=e=>e===void 0;function ei(e){return typeof e==`string`}function ti(e){let t=!1;if(e!=null){let n=e.constructor;t=n==null||n==Object}return t}function ni(e){return typeof e==`object`&&!!e}let ri=Object.getPrototypeOf(Uint8Array),ii=`__proto__`;function ai(e,t=ti){let n;if(Zr(e)){let r=e.find(e=>e!=null);if(Zr(r)||t(r)){n=Array(e.length);for(let r=0;r<e.length;r++)n[r]=ai(e[r],t)}else n=e.slice()}else if(e instanceof ri)n=e.slice();else if(t(e))for(let r in n={},e)r!=ii&&(n[r]=ai(e[r],t));else n=e;return n}function oi(e){let t=arguments;for(let n=1;n<t.length;n++){let r=t[n];for(let t in r)t!=ii&&(ti(e[t])?oi(e[t],ai(r[t])):e[t]=ai(r[t]))}return e}function si(e,t,n){for(let r=0,i,a=-1;r<t.length;r++){let o=t[r];if(o>a){for(i=o-1;i>=0&&e[i]==null;)e[i--]=null;for(i=o+1;i<n&&e[i]==null;)e[a=i++]=null}}}function ci(e,t){if(di(e)){let t=e[0].slice();for(let n=1;n<e.length;n++)t.push(...e[n].slice(1));return fi(t[0])||(t=ui(t)),t}let n=new Set;for(let t=0;t<e.length;t++){let r=e[t][0],i=r.length;for(let e=0;e<i;e++)n.add(r[e])}let r=[Array.from(n).sort((e,t)=>e-t)],i=r[0].length,a=new Map;for(let e=0;e<i;e++)a.set(r[0][e],e);for(let n=0;n<e.length;n++){let o=e[n],s=o[0];for(let e=1;e<o.length;e++){let c=o[e],l=Array(i).fill(void 0),u=t?t[n][e]:1,d=[];for(let e=0;e<c.length;e++){let t=c[e],n=a.get(s[e]);t===null?u!=0&&(l[n]=t,u==2&&d.push(n)):l[n]=t}si(l,d,i),r.push(l)}}return r}let li=typeof queueMicrotask>`u`?e=>Promise.resolve().then(e):queueMicrotask;function ui(e){let t=e[0],n=t.length,r=Array(n);for(let e=0;e<r.length;e++)r[e]=e;r.sort((e,n)=>t[e]-t[n]);let i=[];for(let t=0;t<e.length;t++){let a=e[t],o=Array(n);for(let e=0;e<n;e++)o[e]=a[r[e]];i.push(o)}return i}function di(e){let t=e[0][0],n=t.length;for(let r=1;r<e.length;r++){let i=e[r][0];if(i.length!=n)return!1;if(i!=t){for(let e=0;e<n;e++)if(i[e]!=t[e])return!1}}return!0}function fi(e,t=100){let n=e.length;if(n<=1)return!0;let r=0,i=n-1;for(;r<=i&&e[r]==null;)r++;for(;i>=r&&e[i]==null;)i--;if(i<=r)return!0;let a=wr(1,br((i-r+1)/t));for(let t=e[r],n=r+a;n<=i;n+=a){let r=e[n];if(r!=null){if(r<=t)return!1;t=r}}return!0}let pi=[`January`,`February`,`March`,`April`,`May`,`June`,`July`,`August`,`September`,`October`,`November`,`December`],mi=[`Sunday`,`Monday`,`Tuesday`,`Wednesday`,`Thursday`,`Friday`,`Saturday`];function hi(e){return e.slice(0,3)}let gi=mi.map(hi),_i={MMMM:pi,MMM:pi.map(hi),WWWW:mi,WWW:gi};function vi(e){return(e<10?`0`:``)+e}function yi(e){return(e<10?`00`:e<100?`0`:``)+e}let bi={YYYY:e=>e.getFullYear(),YY:e=>(e.getFullYear()+``).slice(2),MMMM:(e,t)=>t.MMMM[e.getMonth()],MMM:(e,t)=>t.MMM[e.getMonth()],MM:e=>vi(e.getMonth()+1),M:e=>e.getMonth()+1,DD:e=>vi(e.getDate()),D:e=>e.getDate(),WWWW:(e,t)=>t.WWWW[e.getDay()],WWW:(e,t)=>t.WWW[e.getDay()],HH:e=>vi(e.getHours()),H:e=>e.getHours(),h:e=>{let t=e.getHours();return t==0?12:t>12?t-12:t},AA:e=>e.getHours()>=12?`PM`:`AM`,aa:e=>e.getHours()>=12?`pm`:`am`,a:e=>e.getHours()>=12?`p`:`a`,mm:e=>vi(e.getMinutes()),m:e=>e.getMinutes(),ss:e=>vi(e.getSeconds()),s:e=>e.getSeconds(),fff:e=>yi(e.getMilliseconds())};function xi(e,t){t||=_i;let n=[],r=/\\{([a-z]+)\\}|[^{]+/gi,i;for(;i=r.exec(e);)n.push(i[0][0]==`{`?bi[i[1]]:i[0]);return e=>{let r=``;for(let i=0;i<n.length;i++)r+=typeof n[i]==`string`?n[i]:n[i](e,t);return r}}let Si=new Intl.DateTimeFormat().resolvedOptions().timeZone;function Ci(e,t){let n;return t==`UTC`||t==`Etc/UTC`?n=new Date(+e+e.getTimezoneOffset()*6e4):t==Si?n=e:(n=new Date(e.toLocaleString(`en-US`,{timeZone:t})),n.setMilliseconds(e.getMilliseconds())),n}let wi=e=>e%1==0,Ti=[1,2,2.5,5],Ei=qr(10,-32,0,Ti),Di=qr(10,0,32,Ti),Oi=Di.filter(wi),ki=Ei.concat(Di),Ai=`{YYYY}`,ji=`\n`+Ai,Mi=`{M}/{D}`,Ni=`\n`+Mi,Pi=Ni+`/{YY}`,Fi=`{aa}`,Ii=`{h}:{mm}`+Fi,Li=`\n`+Ii,Ri=`:{ss}`;function zi(e){let t=e*1e3,n=t*60,r=n*60,i=r*24,a=i*30,o=i*365,s=(e==1?qr(10,0,3,Ti).filter(wi):qr(10,-3,0,Ti)).concat([t,t*5,t*10,t*15,t*30,n,n*5,n*10,n*15,n*30,r,r*2,r*3,r*4,r*6,r*8,r*12,i,i*2,i*3,i*4,i*5,i*6,i*7,i*8,i*9,i*10,i*15,a,a*2,a*3,a*4,a*6,o,o*2,o*5,o*10,o*25,o*50,o*100]),c=[[o,Ai,null,null,null,null,null,null,1],[i*28,`{MMM}`,ji,null,null,null,null,null,1],[i,Mi,ji,null,null,null,null,null,1],[r,`{h}`+Fi,Pi,null,Ni,null,null,null,1],[n,Ii,Pi,null,Ni,null,null,null,1],[t,Ri,Pi+` {h}:{mm}{aa}`,null,Ni+` {h}:{mm}{aa}`,null,Li,null,1],[e,Ri+`.{fff}`,Pi+` {h}:{mm}{aa}`,null,Ni+` {h}:{mm}{aa}`,null,Li,null,1]];function l(t){return(s,c,l,u,d,f)=>{let p=[],m=d>=o,h=d>=a&&d<o,g=t(l),_=G(g*e,3),v=Yi(g.getFullYear(),m?0:g.getMonth(),h||m?1:g.getDate()),y=G(v*e,3);if(h||m){let n=h?d/a:0,r=m?d/o:0,i=_==y?_:G(Yi(v.getFullYear()+r,v.getMonth()+n,1)*e,3),s=new Date(xr(i/e)),c=s.getFullYear(),l=s.getMonth();for(let a=0;i<=u;a++){let o=Yi(c+r*a,l+n*a,1),s=o-t(G(o*e,3));i=G((+o+s)*e,3),i<=u&&p.push(i)}}else{let a=d>=i?i:d,o=y+(br(l)-br(_))+Ur(_-y,a);p.push(o);let m=t(o),h=m.getHours()+m.getMinutes()/n+m.getSeconds()/r,g=d/r,v=f/s.axes[c]._space;for(;o=G(o+d,e==1?0:3),!(o>u);)if(g>1){let e=br(G(h+g,6))%24,n=t(o).getHours()-e;n>1&&(n=-1),o-=n*r,h=(h+g)%24;let i=p[p.length-1];G((o-i)/d,3)*v>=.7&&p.push(o)}else p.push(o)}return p}}return[s,c,l]}let[Bi,Vi,Hi]=zi(1),[Ui,Wi,Gi]=zi(.001);qr(2,-53,53,[1]);function Ki(e,t){return e.map(e=>e.map((n,r)=>r==0||r==8||n==null?n:t(r==1||e[8]==0?n:e[1]+n)))}function qi(e,t){return(n,r,i,a,o)=>{let s=t.find(e=>o>=e[0])||t[t.length-1],c,l,u,d,f,p;return r.map(t=>{let n=e(t),r=n.getFullYear(),i=n.getMonth(),a=n.getDate(),o=n.getHours(),m=n.getMinutes(),h=n.getSeconds(),g=r!=c&&s[2]||i!=l&&s[3]||a!=u&&s[4]||o!=d&&s[5]||m!=f&&s[6]||h!=p&&s[7]||s[1];return c=r,l=i,u=a,d=o,f=m,p=h,g(n)})}}function Ji(e,t){let n=xi(t);return(t,r,i,a,o)=>r.map(t=>n(e(t)))}function Yi(e,t,n){return new Date(e,t,n)}function Xi(e,t){return t(e)}function Zi(e,t){return(n,r,i,a)=>a==null?`--`:t(e(r))}function Qi(e,t){let n=e.series[t];return n.width?n.stroke(e,t):n.points.width?n.points.stroke(e,t):null}function $i(e,t){return e.series[t].fill(e,t)}let ea={show:!0,live:!0,isolate:!1,mount:Pr,markers:{show:!0,width:2,stroke:Qi,fill:$i,dash:`solid`},idx:null,idxs:null,values:[]};function ta(e,t){let n=e.cursor.points,r=Hn(),i=n.size(e,t);V(r,vn,i),V(r,yn,i);let a=i/-2;V(r,`marginLeft`,a),V(r,`marginTop`,a);let o=n.width(e,t,i);return o&&V(r,`borderWidth`,o),r}function na(e,t){let n=e.series[t].points;return n._fill||n._stroke}function ra(e,t){let n=e.series[t].points;return n._stroke||n._fill}function ia(e,t){return e.series[t].points.size}let aa=[0,0];function oa(e,t,n){return aa[0]=t,aa[1]=n,aa}function sa(e,t,n,r=!0){return e=>{e.button==0&&(!r||e.target==t)&&n(e)}}function ca(e,t,n,r=!0){return e=>{(!r||e.target==t)&&n(e)}}let la={show:!0,x:!0,y:!0,lock:!1,move:oa,points:{one:!1,show:ta,size:ia,width:0,stroke:ra,fill:na},bind:{mousedown:sa,mouseup:sa,click:sa,dblclick:sa,mousemove:ca,mouseleave:ca,mouseenter:ca},drag:{setScale:!0,x:!0,y:!1,dist:0,uni:null,click:(e,t)=>{t.stopPropagation(),t.stopImmediatePropagation()},_x:!1,_y:!1},focus:{dist:(e,t,n,r,i)=>r-i,prox:-1,bias:0},hover:{skip:[void 0],prox:null,bias:0},left:-10,top:-10,idx:null,dataIdx:null,idxs:null,event:null},ua={show:!0,stroke:`rgba(0,0,0,0.07)`,width:2},da=oi({},ua,{filter:Ir}),fa=oi({},da,{size:10}),pa=oi({},ua,{show:!1}),ma=`12px system-ui, -apple-system, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, \"Noto Sans\", sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\"`,ha=`bold `+ma,ga=1.5,_a={show:!0,scale:`x`,stroke:Cn,space:50,gap:5,alignTo:1,size:50,labelGap:0,labelSize:30,labelFont:ha,side:2,grid:da,ticks:fa,border:pa,font:ma,lineGap:ga,rotate:0},va={show:!0,scale:`x`,auto:!1,sorted:1,min:U,max:-U,idxs:[]};function ya(e,t,n,r,i){return t.map(e=>e==null?``:gr(e))}function ba(e,t,n,r,i,a,o){let s=[],c=Gr.get(i)||0;n=o?n:G(Ur(n,i),c);for(let e=n;e<=r;e=G(e+i,c))s.push(Object.is(e,-0)?0:e);return s}function xa(e,t,n,r,i,a,o){let s=[],c=e.scales[e.axes[t].scale].log;i=Tr(c,br((c==10?Dr:Or)(n))),c==10&&(i=ki[$n(i,ki)]);let l=n,u=i*c;c==10&&(u=ki[$n(u,ki)]);do s.push(l),l+=i,c==10&&!Gr.has(l)&&(l=G(l,Gr.get(i))),l>=u&&(i=l,u=i*c,c==10&&(u=ki[$n(u,ki)]));while(l<=r);return s}function Sa(e,t,n,r,i,a,o){let s=e.scales[e.axes[t].scale].asinh,c=r>s?xa(e,t,wr(s,n),r,i):[s],l=r>=0&&n<=0?[0]:[];return(n<-s?xa(e,t,wr(s,-r),-n,i):[s]).reverse().map(e=>-e).concat(l,c)}let Ca=/./,wa=/[12357]/,Ta=/[125]/,Ea=/1/,Da=(e,t,n,r)=>e.map((e,i)=>t==4&&e==0||i%r==0&&n.test(e.toExponential()[e<0?1:0])?e:null);function Oa(e,t,n,r,i){let a=e.axes[n],o=a.scale,s=e.scales[o],c=e.valToPos,l=a._space,u=c(10,o),d=c(9,o)-u>=l?Ca:c(7,o)-u>=l?wa:c(5,o)-u>=l?Ta:Ea;if(d==Ea){let e=yr(c(1,o)-u);if(e<l)return Da(t.slice().reverse(),s.distr,d,Sr(l/e)).reverse()}return Da(t,s.distr,d,1)}function ka(e,t,n,r,i){let a=e.axes[n],o=a.scale,s=a._space,c=e.valToPos,l=yr(c(1,o)-c(2,o));return l<s?Da(t.slice().reverse(),3,Ca,Sr(s/l)).reverse():t}function Aa(e,t,n,r){return r==null?`--`:t==null?``:gr(t)}let ja={show:!0,scale:`y`,stroke:Cn,space:30,gap:5,alignTo:1,size:50,labelGap:0,labelSize:30,labelFont:ha,side:3,grid:da,ticks:fa,border:pa,font:ma,lineGap:ga,rotate:0};function Ma(e,t){return G((3+(e||1)*2)*t,3)}function Na(e,t){let{scale:n,idxs:r}=e.series[0],i=e._data[0],a=e.valToPos(i[r[0]],n,!0),o=yr(e.valToPos(i[r[1]],n,!0)-a)/(e.series[t].points.space*B);return r[1]-r[0]<=o}let Pa={scale:null,auto:!0,sorted:0,min:U,max:-U},Fa=(e,t,n,r,i)=>i,Ia={show:!0,auto:!0,sorted:0,gaps:Fa,alpha:1,facets:[oi({},Pa,{scale:`x`}),oi({},Pa,{scale:`y`})]},La={scale:`y`,auto:!0,sorted:0,show:!0,spanGaps:!1,gaps:Fa,alpha:1,points:{show:Na,filter:null},values:null,min:U,max:-U,idxs:[],path:null,clip:null};function Ra(e,t,n,r,i){return n/10}let za={time:!0,auto:!0,distr:1,log:10,asinh:1,min:null,max:null,dir:1,ori:0},Ba=oi({},za,{time:!1,ori:1}),Va={};function Ha(e,t){let n=Va[e];return n||(n={key:e,plots:[],sub(e){n.plots.push(e)},unsub(e){n.plots=n.plots.filter(t=>t!=e)},pub(e,t,r,i,a,o,s){for(let c=0;c<n.plots.length;c++)n.plots[c]!=t&&n.plots[c].pub(e,t,r,i,a,o,s)}},e!=null&&(Va[e]=n)),n}function Ua(e,t,n){let r=e.mode,i=e.series[t],a=r==2?e._data[t]:e._data,o=e.scales,s=e.bbox,c=a[0],l=r==2?a[1]:a[t],u=r==2?o[i.facets[0].scale]:o[e.series[0].scale],d=r==2?o[i.facets[1].scale]:o[i.scale],f=s.left,p=s.top,m=s.width,h=s.height,g=e.valToPosH,_=e.valToPosV;return u.ori==0?n(i,c,l,u,d,g,_,f,p,m,h,Qa,eo,no,io,oo):n(i,c,l,u,d,_,g,p,f,h,m,$a,to,ro,ao,so)}function Wa(e,t){let n=0,r=0,i=H(e.bands,Yr);for(let e=0;e<i.length;e++){let a=i[e];a.series[0]==t?n=a.dir:a.series[1]==t&&(a.dir==1?r|=1:r|=2)}return[n,r==1?-1:r==2?1:r==3?2:0]}function Ga(e,t,n,r,i){let a=e.mode,o=e.series[t],s=a==2?o.facets[1].scale:o.scale,c=e.scales[s];return i==-1?c.min:i==1?c.max:c.distr==3?c.dir==1?c.min:c.max:0}function Ka(e,t,n,r,i,a){return Ua(e,t,(e,t,o,s,c,l,u,d,f,p,m)=>{let h=e.pxRound,g=s.dir*(s.ori==0?1:-1),_=s.ori==0?eo:to,v,y;g==1?(v=n,y=r):(v=r,y=n);let b=h(l(t[v],s,p,d)),x=h(u(o[v],c,m,f)),S=h(l(t[y],s,p,d)),C=h(u(a==1?c.max:c.min,c,m,f)),w=new Path2D(i);return _(w,S,C),_(w,b,C),_(w,b,x),w})}function qa(e,t,n,r,i,a){let o=null;if(e.length>0){o=new Path2D;let s=t==0?no:ro,c=n;for(let t=0;t<e.length;t++){let n=e[t];if(n[1]>n[0]){let e=n[0]-c;e>0&&s(o,c,r,e,r+a),c=n[1]}}let l=n+i-c;l>0&&s(o,c,r-10/2,l,r+a+10)}return o}function Ja(e,t,n){let r=e[e.length-1];r&&r[0]==t?r[1]=n:e.push([t,n])}function Ya(e,t,n,r,i,a,o){let s=[],c=e.length;for(let l=i==1?n:r;l>=n&&l<=r;l+=i)if(t[l]===null){let u=l,d=l;if(i==1)for(;++l<=r&&t[l]===null;)d=l;else for(;--l>=n&&t[l]===null;)d=l;let f=a(e[u]),p=d==u?f:a(e[d]),m=u-i;f=o<=0&&m>=0&&m<c?a(e[m]):f;let h=d+i;p=o>=0&&h>=0&&h<c?a(e[h]):p,p>=f&&s.push([f,p])}return s}function Xa(e){return e==0?Fr:e==1?xr:t=>Hr(t,e)}function Za(e){let t=e==0?Qa:$a,n=e==0?(e,t,n,r,i,a)=>{e.arcTo(t,n,r,i,a)}:(e,t,n,r,i,a)=>{e.arcTo(n,t,i,r,a)},r=e==0?(e,t,n,r,i)=>{e.rect(t,n,r,i)}:(e,t,n,r,i)=>{e.rect(n,t,i,r)};return(e,i,a,o,s,c=0,l=0)=>{c==0&&l==0?r(e,i,a,o,s):(c=Cr(c,o/2,s/2),l=Cr(l,o/2,s/2),t(e,i+c,a),n(e,i+o,a,i+o,a+s,c),n(e,i+o,a+s,i,a+s,l),n(e,i,a+s,i,a,l),n(e,i,a,i+o,a,c),e.closePath())}}let Qa=(e,t,n)=>{e.moveTo(t,n)},$a=(e,t,n)=>{e.moveTo(n,t)},eo=(e,t,n)=>{e.lineTo(t,n)},to=(e,t,n)=>{e.lineTo(n,t)},no=Za(0),ro=Za(1),io=(e,t,n,r,i,a)=>{e.arc(t,n,r,i,a)},ao=(e,t,n,r,i,a)=>{e.arc(n,t,r,i,a)},oo=(e,t,n,r,i,a,o)=>{e.bezierCurveTo(t,n,r,i,a,o)},so=(e,t,n,r,i,a,o)=>{e.bezierCurveTo(n,t,i,r,o,a)};function co(e){return(e,t,n,r,i)=>Ua(e,t,(t,a,o,s,c,l,u,d,f,p,m)=>{let{pxRound:h,points:g}=t,_,v;s.ori==0?(_=Qa,v=io):(_=$a,v=ao);let y=G(g.width*B,3),b=(g.size-g.width)/2*B,x=G(b*2,3),S=new Path2D,C=new Path2D,{left:w,top:T,width:E,height:D}=e.bbox;no(C,w-x,T-x,E+x*2,D+x*2);let O=e=>{if(o[e]!=null){let t=h(l(a[e],s,p,d)),n=h(u(o[e],c,m,f));_(S,t+b,n),v(S,t,n,b,0,vr*2)}};if(i)i.forEach(O);else for(let e=n;e<=r;e++)O(e);return{stroke:y>0?S:null,fill:S,clip:C,flags:3}})}function lo(e){return(t,n,r,i,a,o)=>{r!=i&&(a!=r&&o!=r&&e(t,n,r),a!=i&&o!=i&&e(t,n,i),e(t,n,o))}}let uo=lo(eo),fo=lo(to);function po(e){let t=H(e?.alignGaps,0);return(e,n,r,i)=>Ua(e,n,(a,o,s,c,l,u,d,f,p,m,h)=>{[r,i]=rr(s,r,i);let g=a.pxRound,_=e=>g(u(e,c,m,f)),v=e=>g(d(e,l,h,p)),y,b;c.ori==0?(y=eo,b=uo):(y=to,b=fo);let x=c.dir*(c.ori==0?1:-1),S={stroke:new Path2D,fill:null,clip:null,band:null,gaps:null,flags:1},C=S.stroke,w=!1;if(i-r>=m*4){let t=t=>e.posToVal(t,c.key,!0),n=null,a=null,l,u,d=_(o[x==1?r:i]),f=_(o[r]),p=_(o[i]),m=t(x==1?f+1:p-1);for(let e=x==1?r:i;e>=r&&e<=i;e+=x){let r=o[e],i=(x==1?r<m:r>m)?d:_(r),c=s[e];i==d?c==null?c===null&&(w=!0):(u=c,n==null?(y(C,i,v(u)),l=n=a=u):u<n?n=u:u>a&&(a=u)):(n!=null&&b(C,d,v(n),v(a),v(l),v(u)),c==null?(n=a=null,c===null&&(w=!0)):(u=c,y(C,i,v(u)),n=a=l=u),d=i,m=t(d+x))}n!=null&&n!=a&&d!=null&&b(C,d,v(n),v(a),v(l),v(u))}else for(let e=x==1?r:i;e>=r&&e<=i;e+=x){let t=s[e];t===null?w=!0:t!=null&&y(C,_(o[e]),v(t))}let[T,E]=Wa(e,n);if(a.fill!=null||T!=0){let t=S.fill=new Path2D(C),s=v(a.fillTo(e,n,a.min,a.max,T)),c=_(o[r]),l=_(o[i]);x==-1&&([l,c]=[c,l]),y(t,l,s),y(t,c,s)}if(!a.spanGaps){let l=[];w&&l.push(...Ya(o,s,r,i,x,_,t)),S.gaps=l=a.gaps(e,n,r,i,l),S.clip=qa(l,c.ori,f,p,m,h)}return E!=0&&(S.band=E==2?[Ka(e,n,r,i,C,-1),Ka(e,n,r,i,C,1)]:Ka(e,n,r,i,C,E)),S})}function mo(e){let t=H(e.align,1),n=H(e.ascDesc,!1),r=H(e.alignGaps,0),i=H(e.extend,!1);return(e,a,o,s)=>Ua(e,a,(c,l,u,d,f,p,m,h,g,_,v)=>{[o,s]=rr(u,o,s);let y=c.pxRound,{left:b,width:x}=e.bbox,S=e=>y(p(e,d,_,h)),C=e=>y(m(e,f,v,g)),w=d.ori==0?eo:to,T={stroke:new Path2D,fill:null,clip:null,band:null,gaps:null,flags:1},E=T.stroke,D=d.dir*(d.ori==0?1:-1),O=C(u[D==1?o:s]),ee=S(l[D==1?o:s]),te=ee,k=ee;i&&t==-1&&(k=b,w(E,k,O)),w(E,ee,O);for(let e=D==1?o:s;e>=o&&e<=s;e+=D){let n=u[e];if(n==null)continue;let r=S(l[e]),i=C(n);t==1?w(E,r,O):w(E,te,i),w(E,r,i),O=i,te=r}let ne=te;i&&t==1&&(ne=b+x,w(E,ne,O));let[re,ie]=Wa(e,a);if(c.fill!=null||re!=0){let t=T.fill=new Path2D(E),n=C(c.fillTo(e,a,c.min,c.max,re));w(t,ne,n),w(t,k,n)}if(!c.spanGaps){let i=[];i.push(...Ya(l,u,o,s,D,S,r));let f=c.width*B/2,p=n||t==1?f:-f,m=n||t==-1?-f:f;i.forEach(e=>{e[0]+=p,e[1]+=m}),T.gaps=i=c.gaps(e,a,o,s,i),T.clip=qa(i,d.ori,h,g,_,v)}return ie!=0&&(T.band=ie==2?[Ka(e,a,o,s,E,-1),Ka(e,a,o,s,E,1)]:Ka(e,a,o,s,E,ie)),T})}function ho(e,t,n,r,i,a,o=U){if(e.length>1){let s=null;for(let c=0,l=1/0;c<e.length;c++)if(t[c]!==void 0){if(s!=null){let t=yr(e[c]-e[s]);t<l&&(l=t,o=yr(n(e[c],r,i,a)-n(e[s],r,i,a)))}s=c}}return o}function go(e){e||=Jr;let t=H(e.size,[.6,U,1]),n=e.align||0,r=e.gap||0,i=e.radius;i=i==null?[0,0]:typeof i==`number`?[i,0]:i;let a=W(i),o=1-t[0],s=H(t[1],U),c=H(t[2],1),l=H(e.disp,Jr),u=H(e.each,e=>{}),{fill:d,stroke:f}=l;return(e,t,i,p)=>Ua(e,t,(m,h,g,_,v,y,b,x,S,C,w)=>{let T=m.pxRound,E=n,D=r*B,O=s*B,ee=c*B,te,k;_.ori==0?[te,k]=a(e,t):[k,te]=a(e,t);let ne=_.dir*(_.ori==0?1:-1),re=_.ori==0?no:ro,ie=_.ori==0?u:(e,t,n,r,i,a,o)=>{u(e,t,n,i,r,o,a)},ae=H(e.bands,Yr).find(e=>e.series[0]==t),oe=ae==null?0:ae.dir,se=m.fillTo(e,t,m.min,m.max,oe),A=T(b(se,v,w,S)),j,M,ce,N=C,P=T(m.width*B),le=!1,ue=null,de=null,fe=null,pe=null;d!=null&&(P==0||f!=null)&&(le=!0,ue=d.values(e,t,i,p),de=new Map,new Set(ue).forEach(e=>{e!=null&&de.set(e,new Path2D)}),P>0&&(fe=f.values(e,t,i,p),pe=new Map,new Set(fe).forEach(e=>{e!=null&&pe.set(e,new Path2D)})));let{x0:me,size:he}=l;if(me!=null&&he!=null){E=1,h=me.values(e,t,i,p),me.unit==2&&(h=h.map(t=>e.posToVal(x+t*C,_.key,!0)));let n=he.values(e,t,i,p);M=he.unit==2?n[0]*C:y(n[0],_,C,x)-y(0,_,C,x),N=ho(h,g,y,_,C,x,N),ce=N-M+D}else N=ho(h,g,y,_,C,x,N),ce=N*o+D,M=N-ce;ce<1&&(ce=0),P>=M/2&&(P=0),ce<5&&(T=Fr);let ge=ce>0,_e=N-ce-(ge?P:0);M=T(Mr(_e,ee,O)),j=(E==0?M/2:E==ne?0:M)-E*ne*((E==0?D/2:0)+(ge?P/2:0));let ve={stroke:null,fill:null,clip:null,band:null,gaps:null,flags:0},ye=le?null:new Path2D,be=null;if(ae!=null)be=e.data[ae.series[1]];else{let{y0:n,y1:r}=l;n!=null&&r!=null&&(g=r.values(e,t,i,p),be=n.values(e,t,i,p))}let xe=te*M,Se=k*M;for(let n=ne==1?i:p;n>=i&&n<=p;n+=ne){let r=g[n];if(r==null)continue;if(be!=null){let e=be[n]??0;if(r-e==0)continue;A=b(e,v,w,S)}let i=y(_.distr!=2||l!=null?h[n]:n,_,C,x),a=b(H(r,se),v,w,S),o=T(i-j),s=T(wr(a,A)),c=T(Cr(a,A)),u=s-c;if(r!=null){let i=r<0?Se:xe,a=r<0?xe:Se;le?(P>0&&fe[n]!=null&&re(pe.get(fe[n]),o,c+br(P/2),M,wr(0,u-P),i,a),ue[n]!=null&&re(de.get(ue[n]),o,c+br(P/2),M,wr(0,u-P),i,a)):re(ye,o,c+br(P/2),M,wr(0,u-P),i,a),ie(e,t,n,o-P/2,c,M+P,u)}}return P>0?ve.stroke=le?pe:ye:le||(ve._fill=m.width==0?m._fill:m._stroke??m._fill,ve.width=0),ve.fill=le?de:ye,ve})}function _o(e,t){let n=H(t?.alignGaps,0);return(t,r,i,a)=>Ua(t,r,(o,s,c,l,u,d,f,p,m,h,g)=>{[i,a]=rr(c,i,a);let _=o.pxRound,v=e=>_(d(e,l,h,p)),y=e=>_(f(e,u,g,m)),b,x,S;l.ori==0?(b=Qa,S=eo,x=oo):(b=$a,S=to,x=so);let C=l.dir*(l.ori==0?1:-1),w=v(s[C==1?i:a]),T=w,E=[],D=[];for(let e=C==1?i:a;e>=i&&e<=a;e+=C)if(c[e]!=null){let t=s[e],n=v(t);E.push(T=n),D.push(y(c[e]))}let O={stroke:e(E,D,b,S,x,_),fill:null,clip:null,band:null,gaps:null,flags:1},ee=O.stroke,[te,k]=Wa(t,r);if(o.fill!=null||te!=0){let e=O.fill=new Path2D(ee),n=y(o.fillTo(t,r,o.min,o.max,te));S(e,T,n),S(e,w,n)}if(!o.spanGaps){let e=[];e.push(...Ya(s,c,i,a,C,v,n)),O.gaps=e=o.gaps(t,r,i,a,e),O.clip=qa(e,l.ori,p,m,h,g)}return k!=0&&(O.band=k==2?[Ka(t,r,i,a,ee,-1),Ka(t,r,i,a,ee,1)]:Ka(t,r,i,a,ee,k)),O})}function vo(e){return _o(yo,e)}function yo(e,t,n,r,i,a){let o=e.length;if(o<2)return null;let s=new Path2D;if(n(s,e[0],t[0]),o==2)r(s,e[1],t[1]);else{let n=Array(o),r=Array(o-1),a=Array(o-1),c=Array(o-1);for(let n=0;n<o-1;n++)a[n]=t[n+1]-t[n],c[n]=e[n+1]-e[n],r[n]=a[n]/c[n];n[0]=r[0];for(let e=1;e<o-1;e++)r[e]===0||r[e-1]===0||r[e-1]>0!=r[e]>0?n[e]=0:(n[e]=3*(c[e-1]+c[e])/((2*c[e]+c[e-1])/r[e-1]+(c[e]+2*c[e-1])/r[e]),isFinite(n[e])||(n[e]=0));n[o-1]=r[o-2];for(let r=0;r<o-1;r++)i(s,e[r]+c[r]/3,t[r]+n[r]*c[r]/3,e[r+1]-c[r]/3,t[r+1]-n[r+1]*c[r]/3,e[r+1],t[r+1])}return s}let bo=new Set;function xo(){for(let e of bo)e.syncRect(!0)}Nn&&(Zn(`resize`,Fn,xo),Zn(`scroll`,Fn,xo,!0),Zn(Mn,Fn,()=>{Lo.pxRatio=B}));let So=po(),Co=co();function wo(e,t,n,r){return(r?[e[0],e[1]].concat(e.slice(2)):[e[0]].concat(e.slice(1))).map((e,r)=>Eo(e,r,t,n))}function To(e,t){return e.map((e,n)=>n==0?{}:oi({},t,e))}function Eo(e,t,n,r){return oi({},t==0?n:r,e)}function Do(e,t,n){return t==null?Xr:[t,n]}let Oo=Do;function ko(e,t,n){return t==null?Xr:fr(t,n,cr,!0)}function Ao(e,t,n,r){return t==null?Xr:or(t,n,e.scales[r].log,!1)}let jo=Ao;function Mo(e,t,n,r){return t==null?Xr:sr(t,n,e.scales[r].log,!1)}let No=Mo;function Po(e,t,n,r,i){let a=wr(jr(e),jr(t)),o=t-e,s=$n(i/r*o,n);do{let e=n[s],t=r*e/o;if(t>=i&&a+(e<5?Gr.get(e):0)<=17)return[e,t]}while(++s<n.length);return[0,0]}function Fo(e){let t,n;return e=e.replace(/(\\d+)px/,(e,r)=>(t=xr((n=+r)*B))+`px`),[e,t,n]}function Io(e){e.show&&[e.font,e.labelFont].forEach(e=>{let t=G(e[2]*B,1);e[0]=e[0].replace(/[0-9.]+px/,t+`px`),e[1]=t})}function Lo(e,t,n){let r={mode:H(e.mode,1)},i=r.mode;function a(e,t,n,r){let i=t.valToPct(e);return r+n*(t.dir==-1?1-i:i)}function o(e,t,n,r){let i=t.valToPct(e);return r+n*(t.dir==-1?i:1-i)}function s(e,t,n,r){return t.ori==0?a(e,t,n,r):o(e,t,n,r)}r.valToPosH=a,r.valToPosV=o;let c=!1;r.status=0;let l=r.root=Hn(`uplot`);if(e.id!=null&&(l.id=e.id),zn(l,e.class),e.title){let t=Hn(`u-title`,l);t.textContent=e.title}let u=Vn(`canvas`),d=r.ctx=u.getContext(`2d`),f=Hn(`u-wrap`,l);Zn(`click`,f,e=>{e.target===m&&(L!=cn||R!=ln)&&pn.click(r,e)},!0);let p=r.under=Hn(`u-under`,f);f.appendChild(u);let m=r.over=Hn(`u-over`,f);e=ai(e);let h=+H(e.pxAlign,1),g=Xa(h);(e.plugins||[]).forEach(t=>{t.opts&&(e=t.opts(r,e)||e)});let _=e.ms||.001,v=r.series=i==1?wo(e.series||[],va,La,!1):To(e.series||[null],Ia),y=r.axes=wo(e.axes||[],_a,ja,!0),b=r.scales={},x=r.bands=e.bands||[];x.forEach(e=>{e.fill=W(e.fill||null),e.dir=H(e.dir,-1)});let S=i==2?v[1].facets[0].scale:v[0].scale,C={axes:Gt,series:Pt},w=(e.drawOrder||[`axes`,`series`]).map(e=>C[e]);function T(e){let t=e.distr==3?t=>Dr(t>0?t:e.clamp(r,t,e.min,e.max,e.key)):e.distr==4?t=>Ar(t,e.asinh):e.distr==100?t=>e.fwd(t):e=>e;return n=>{let r=t(n),{_min:i,_max:a}=e,o=a-i;return(r-i)/o}}function E(t){let n=b[t];if(n==null){let r=(e.scales||Jr)[t]||Jr;if(r.from!=null){E(r.from);let e=oi({},b[r.from],r,{key:t});e.valToPct=T(e),b[t]=e}else{n=b[t]=oi({},t==S?za:Ba,r),n.key=t;let e=n.time,a=n.range,o=Zr(a);if((t!=S||i==2&&!e)&&(o&&(a[0]==null||a[1]==null)&&(a={min:a[0]==null?lr:{mode:1,hard:a[0],soft:a[0]},max:a[1]==null?lr:{mode:1,hard:a[1],soft:a[1]}},o=!1),!o&&ti(a))){let e=a;a=(t,n,r)=>n==null?Xr:fr(n,r,e)}n.range=W(a||(e?Oo:t==S?n.distr==3?jo:n.distr==4?No:Do:n.distr==3?Ao:n.distr==4?Mo:ko)),n.auto=W(o?!1:n.auto),n.clamp=W(n.clamp||Ra),n._min=n._max=null,n.valToPct=T(n)}}}for(let t in E(`x`),E(`y`),i==1&&v.forEach(e=>{E(e.scale)}),y.forEach(e=>{E(e.scale)}),e.scales)E(t);let D=b[S],O=D.distr,ee,te;D.ori==0?(zn(l,`u-hz`),ee=a,te=o):(zn(l,`u-vt`),ee=o,te=a);let k={};for(let e in b){let t=b[e];(t.min!=null||t.max!=null)&&(k[e]={min:t.min,max:t.max},t.min=t.max=null)}let ne=e.tzDate||(e=>new Date(xr(e/_))),re=e.fmtDate||xi,ie=_==1?Hi(ne):Gi(ne),ae=qi(ne,Ki(_==1?Vi:Wi,re)),oe=Zi(ne,Xi(`{YYYY}-{MM}-{DD} {h}:{mm}{aa}`,re)),se=[],A=r.legend=oi({},ea,e.legend),j=r.cursor=oi({},la,{drag:{y:i==2}},e.cursor),M=A.show,ce=j.show,N=A.markers;A.idxs=se,N.width=W(N.width),N.dash=W(N.dash),N.stroke=W(N.stroke),N.fill=W(N.fill);let P,le,ue,de=[],fe=[],pe,me=!1,he={};if(A.live){let e=v[1]?v[1].values:null;for(let t in me=e!=null,pe=me?e(r,1,0):{_:0},pe)he[t]=`--`}if(M)if(P=Vn(`table`,`u-legend`,l),ue=Vn(`tbody`,null,P),A.mount(r,P),me){le=Vn(`thead`,null,P,ue);let e=Vn(`tr`,null,le);for(var ge in Vn(`th`,null,e),pe)Vn(`th`,_n,e).textContent=ge}else zn(P,`u-inline`),A.live&&zn(P,`u-live`);let _e={show:!0},ve={show:!1};function ye(e,t){if(t==0&&(me||!A.live||i==2))return Xr;let n=[],a=Vn(`tr`,`u-series`,ue,ue.childNodes[t]);zn(a,e.class),e.show||zn(a,gn);let o=Vn(`th`,null,a);if(N.show){let e=Hn(`u-marker`,o);if(t>0){let n=N.width(r,t);n&&(e.style.border=n+`px `+N.dash(r,t)+` `+N.stroke(r,t)),e.style.background=N.fill(r,t)}}let s=Hn(_n,o);for(var c in e.label instanceof HTMLElement?s.appendChild(e.label):s.textContent=e.label,t>0&&(N.show||(s.style.color=e.width>0?N.stroke(r,t):N.fill(r,t)),xe(`click`,o,t=>{if(j._lock)return;qe(t);let n=v.indexOf(e);if((t.ctrlKey||t.metaKey)!=A.isolate){let e=v.some((e,t)=>t>0&&t!=n&&e.show);v.forEach((t,r)=>{r>0&&Ln(r,e?r==n?_e:ve:_e,!0,hi.setSeries)})}else Ln(n,{show:!e.show},!0,hi.setSeries)},!1),Xe&&xe(On,o,t=>{j._lock||(qe(t),Ln(v.indexOf(e),tr,!0,hi.setSeries))},!1)),pe){let e=Vn(`td`,`u-value`,a);e.textContent=`--`,n.push(e)}return[a,n]}let be=new Map;function xe(e,t,n,i=!0){let a=be.get(t)||{},o=j.bind[e](r,t,n,i);o&&(Zn(e,t,a[e]=o),be.set(t,a))}function Se(e,t,n){let r=be.get(t)||{};for(let n in r)(e==null||n==e)&&(Qn(n,t,r[n]),delete r[n]);e??be.delete(t)}let Ce=0,we=0,F=0,I=0,Te=0,Ee=0,De=Te,Oe=Ee,ke=F,Ae=I,je=0,Me=0,Ne=0,Pe=0;r.bbox={};let Fe=!1,Ie=!1,Le=!1,Re=!1,ze=!1,Be=!1;function Ve(e,t,n){(n||e!=r.width||t!=r.height)&&He(e,t),Kt(!1),Le=!0,Ie=!0,Zt()}function He(e,t){r.width=Ce=F=e,r.height=we=I=t,Te=Ee=0,Ge(),Ke();let n=r.bbox;je=n.left=Hr(Te*B,.5),Me=n.top=Hr(Ee*B,.5),Ne=n.width=Hr(F*B,.5),Pe=n.height=Hr(I*B,.5)}function Ue(){let e=!1,t=0;for(;!e;){t++;let n=Ut(t),i=Wt(t);e=t==3||n&&i,e||(He(r.width,r.height),Ie=!0)}}function We({width:e,height:t}){Ve(e,t)}r.setSize=We;function Ge(){let e=!1,t=!1,n=!1,r=!1;y.forEach((i,a)=>{if(i.show&&i._show){let{side:a,_size:o}=i,s=a%2,c=o+(i.label==null?0:i.labelSize);c>0&&(s?(F-=c,a==3?(Te+=c,r=!0):n=!0):(I-=c,a==0?(Ee+=c,e=!0):t=!0))}}),at[0]=e,at[1]=n,at[2]=t,at[3]=r,F-=lt[1]+lt[3],Te+=lt[3],I-=lt[2]+lt[0],Ee+=lt[0]}function Ke(){let e=Te+F,t=Ee+I,n=Te,r=Ee;function i(i,a){switch(i){case 1:return e+=a,e-a;case 2:return t+=a,t-a;case 3:return n-=a,n+a;case 0:return r-=a,r+a}}y.forEach((e,t)=>{if(e.show&&e._show){let t=e.side;e._pos=i(t,e._size),e.label!=null&&(e._lpos=i(t,e.labelSize))}})}if(j.dataIdx==null){let e=j.hover,n=e.skip=new Set(e.skip??[]);n.add(void 0);let r=e.prox=W(e.prox),i=e.bias??=0;j.dataIdx=(e,a,o,s)=>{if(a==0)return o;let c=o,l=r(e,a,o,s)??U,u=l>=0&&l<U,d=D.ori==0?F:I,f=j.left,p=t[0],m=t[a];if(n.has(m[o])){c=null;let e=null,t=null,r;if(i==0||i==-1)for(r=o;e==null&&r-- >0;)n.has(m[r])||(e=r);if(i==0||i==1)for(r=o;t==null&&r++<m.length;)n.has(m[r])||(t=r);if(e!=null||t!=null)if(u){let n=e==null?-1/0:ee(p[e],D,d,0),r=t==null?1/0:ee(p[t],D,d,0),i=f-n,a=r-f;i<=a?i<=l&&(c=e):a<=l&&(c=t)}else c=t==null?e:e==null?t:o-e<=t-o?e:t}else u&&yr(f-ee(p[o],D,d,0))>l&&(c=null);return c}}let qe=e=>{j.event=e};j.idxs=se,j._lock=!1;let Je=j.points;Je.show=W(Je.show),Je.size=W(Je.size),Je.stroke=W(Je.stroke),Je.width=W(Je.width),Je.fill=W(Je.fill);let Ye=r.focus=oi({},e.focus||{alpha:.3},j.focus),Xe=Ye.prox>=0,Ze=Xe&&Je.one,Qe=[],$e=[],et=[];function tt(e,t){let n=Je.show(r,t);if(n instanceof HTMLElement)return zn(n,`u-cursor-pt`),zn(n,e.class),Wn(n,-10,-10,F,I),m.insertBefore(n,Qe[t]),n}function nt(e,t){if(i==1||t>0){let t=i==1&&b[e.scale].time,n=e.value;e.value=t?ei(n)?Zi(ne,Xi(n,re)):n||oe:n||Aa,e.label=e.label||(t?`Time`:`Value`)}if(Ze||t>0){e.width=e.width==null?1:e.width,e.paths=e.paths||So||Lr,e.fillTo=W(e.fillTo||Ga),e.pxAlign=+H(e.pxAlign,h),e.pxRound=Xa(e.pxAlign),e.stroke=W(e.stroke||null),e.fill=W(e.fill||null),e._stroke=e._fill=e._paths=e._focus=null;let t=Ma(wr(1,e.width),1),n=e.points=oi({},{size:t,width:wr(1,t*.2),stroke:e.stroke,space:t*2,paths:Co,_stroke:null,_fill:null},e.points);n.show=W(n.show),n.filter=W(n.filter),n.fill=W(n.fill),n.stroke=W(n.stroke),n.paths=W(n.paths),n.pxAlign=e.pxAlign}if(M){let n=ye(e,t);de.splice(t,0,n[0]),fe.splice(t,0,n[1]),A.values.push(null)}if(ce){se.splice(t,0,null);let n=null;Ze?t==0&&(n=tt(e,t)):t>0&&(n=tt(e,t)),Qe.splice(t,0,n),$e.splice(t,0,0),et.splice(t,0,0)}pi(`addSeries`,t)}function rt(e,t){t??=v.length,e=i==1?Eo(e,t,va,La):Eo(e,t,{},Ia),v.splice(t,0,e),nt(v[t],t)}r.addSeries=rt;function it(e){if(v.splice(e,1),M){A.values.splice(e,1),fe.splice(e,1);let t=de.splice(e,1)[0];Se(null,t.firstChild),t.remove()}ce&&(se.splice(e,1),Qe.splice(e,1)[0].remove(),$e.splice(e,1),et.splice(e,1)),pi(`delSeries`,e)}r.delSeries=it;let at=[!1,!1,!1,!1];function ot(e,t){if(e._show=e.show,e.show){let n=e.side%2,i=b[e.scale];i??=(e.scale=n?v[1].scale:S,b[e.scale]);let a=i.time;e.size=W(e.size),e.space=W(e.space),e.rotate=W(e.rotate),Zr(e.incrs)&&e.incrs.forEach(e=>{!Gr.has(e)&&Gr.set(e,Kr(e))}),e.incrs=W(e.incrs||(i.distr==2?Oi:a?_==1?Bi:Ui:ki)),e.splits=W(e.splits||(a&&i.distr==1?ie:i.distr==3?xa:i.distr==4?Sa:ba)),e.stroke=W(e.stroke),e.grid.stroke=W(e.grid.stroke),e.ticks.stroke=W(e.ticks.stroke),e.border.stroke=W(e.border.stroke);let o=e.values;e.values=Zr(o)&&!Zr(o[0])?W(o):a?Zr(o)?qi(ne,Ki(o,re)):ei(o)?Ji(ne,o):o||ae:o||ya,e.filter=W(e.filter||(i.distr>=3&&i.log==10?Oa:i.distr==3&&i.log==2?ka:Ir)),e.font=Fo(e.font),e.labelFont=Fo(e.labelFont),e._size=e.size(r,null,t,0),e._space=e._rotate=e._incrs=e._found=e._splits=e._values=null,e._size>0&&(at[t]=!0,e._el=Hn(`u-axis`,f))}}function st(e,t,n,r){let[i,a,o,s]=n,c=t%2,l=0;return c==0&&(s||a)&&(l=t==0&&!i||t==2&&!o?xr(_a.size/3):0),c==1&&(i||o)&&(l=t==1&&!a||t==3&&!s?xr(ja.size/2):0),l}let ct=r.padding=(e.padding||[st,st,st,st]).map(e=>W(H(e,st))),lt=r._padding=ct.map((e,t)=>e(r,t,at,0)),ut,dt=null,ft=null,pt=i==1?v[0].idxs:null,mt=null,ht=!1;function gt(e,n){if(t=e??[],r.data=r._data=t,i==2){ut=0;for(let e=1;e<v.length;e++)ut+=t[e][0].length}else{t.length==0&&(r.data=r._data=t=[[]]),mt=t[0],ut=mt.length;let e=t;if(O==2){e=t.slice();let n=e[0]=Array(ut);for(let e=0;e<ut;e++)n[e]=e}r._data=t=e}if(Kt(!0),pi(`setData`),O==2&&(Le=!0),n!==!1){let e=D;e.auto(r,ht)?_t():In(S,e.min,e.max),Re||=j.left>=0,Be=!0,Zt()}}r.setData=gt;function _t(){ht=!0;let e,n;i==1&&(ut>0?(dt=pt[0]=0,ft=pt[1]=ut-1,e=t[0][dt],n=t[0][ft],O==2?(e=dt,n=ft):e==n&&(O==3?[e,n]=or(e,e,D.log,!1):O==4?[e,n]=sr(e,e,D.log,!1):D.time?n=e+xr(86400/_):[e,n]=fr(e,n,cr,!0))):(dt=pt[0]=e=null,ft=pt[1]=n=null)),In(S,e,n)}let vt,yt,bt,xt,St,Ct,wt,Tt,Et,Dt;function Ot(e,t,n,r,i,a){e??=wn,n??=Yr,r??=`butt`,i??=wn,a??=`round`,e!=vt&&(d.strokeStyle=vt=e),i!=yt&&(d.fillStyle=yt=i),t!=bt&&(d.lineWidth=bt=t),a!=St&&(d.lineJoin=St=a),r!=Ct&&(d.lineCap=Ct=r),n!=xt&&d.setLineDash(xt=n)}function kt(e,t,n,r){t!=yt&&(d.fillStyle=yt=t),e!=wt&&(d.font=wt=e),n!=Tt&&(d.textAlign=Tt=n),r!=Et&&(d.textBaseline=Et=r)}function At(e,t,n,i,a=0){if(i.length>0&&e.auto(r,ht)&&(t==null||t.min==null)){let t=H(dt,0),r=H(ft,i.length-1),o=n.min==null?ar(i,t,r,a,e.distr==3):[n.min,n.max];e.min=Cr(e.min,n.min=o[0]),e.max=wr(e.max,n.max=o[1])}}let jt={min:null,max:null};function Mt(){for(let e in b){let t=b[e];k[e]==null&&(t.min==null||k[S]!=null&&t.auto(r,ht))&&(k[e]=jt)}for(let e in b){let t=b[e];k[e]==null&&t.from!=null&&k[t.from]!=null&&(k[e]=jt)}k[S]!=null&&Kt(!0);let e={};for(let t in k){let n=k[t];if(n!=null){let a=e[t]=ai(b[t],ni);if(n.min!=null)oi(a,n);else if(t!=S||i==2)if(ut==0&&a.from==null){let e=a.range(r,null,null,t);a.min=e[0],a.max=e[1]}else a.min=U,a.max=-U}}if(ut>0)for(let n in v.forEach((n,a)=>{if(i==1){let i=n.scale,o=k[i];if(o==null)return;let s=e[i];if(a==0){let e=s.range(r,s.min,s.max,i);s.min=e[0],s.max=e[1],dt=$n(s.min,t[0]),ft=$n(s.max,t[0]),ft-dt>1&&(t[0][dt]<s.min&&dt++,t[0][ft]>s.max&&ft--),n.min=mt[dt],n.max=mt[ft]}else n.show&&n.auto&&At(s,o,n,t[a],n.sorted);n.idxs[0]=dt,n.idxs[1]=ft}else if(a>0&&n.show&&n.auto){let[r,i]=n.facets,o=r.scale,s=i.scale,[c,l]=t[a],u=e[o],d=e[s];u!=null&&At(u,k[o],r,c,r.sorted),d!=null&&At(d,k[s],i,l,i.sorted),n.min=i.min,n.max=i.max}}),e){let t=e[n],i=k[n];if(t.from==null&&(i==null||i.min==null)){let e=t.range(r,t.min==U?null:t.min,t.max==-U?null:t.max,n);t.min=e[0],t.max=e[1]}}for(let t in e){let n=e[t];if(n.from!=null){let i=e[n.from];if(i.min==null)n.min=n.max=null;else{let e=n.range(r,i.min,i.max,t);n.min=e[0],n.max=e[1]}}}let n={},a=!1;for(let t in e){let r=e[t],i=b[t];if(i.min!=r.min||i.max!=r.max){i.min=r.min,i.max=r.max;let e=i.distr;i._min=e==3?Dr(i.min):e==4?Ar(i.min,i.asinh):e==100?i.fwd(i.min):i.min,i._max=e==3?Dr(i.max):e==4?Ar(i.max,i.asinh):e==100?i.fwd(i.max):i.max,n[t]=a=!0}}if(a){for(let e in v.forEach((e,t)=>{i==2?t>0&&n.y&&(e._paths=null):n[e.scale]&&(e._paths=null)}),n)Le=!0,pi(`setScale`,e);ce&&j.left>=0&&(Re=Be=!0)}for(let e in k)k[e]=null}function Nt(e){let t=Mr(dt-1,0,ut-1),n=Mr(ft+1,0,ut-1);for(;e[t]==null&&t>0;)t--;for(;e[n]==null&&n<ut-1;)n++;return[t,n]}function Pt(){if(ut>0){let e=v.some(e=>e._focus)&&Dt!=Ye.alpha;e&&(d.globalAlpha=Dt=Ye.alpha),v.forEach((e,n)=>{if(n>0&&e.show&&(Ft(n,!1),Ft(n,!0),e._paths==null)){let a=Dt;Dt!=e.alpha&&(d.globalAlpha=Dt=e.alpha);let o=i==2?[0,t[n][0].length-1]:Nt(t[n]);e._paths=e.paths(r,n,o[0],o[1]),Dt!=a&&(d.globalAlpha=Dt=a)}}),v.forEach((e,t)=>{if(t>0&&e.show){let n=Dt;Dt!=e.alpha&&(d.globalAlpha=Dt=e.alpha),e._paths!=null&&It(t,!1);{let n=e._paths==null?null:e._paths.gaps,i=e.points.show(r,t,dt,ft,n),a=e.points.filter(r,t,i,n);(i||a)&&(e.points._paths=e.points.paths(r,t,dt,ft,a),It(t,!0))}Dt!=n&&(d.globalAlpha=Dt=n),pi(`drawSeries`,t)}}),e&&(d.globalAlpha=Dt=1)}}function Ft(e,t){let n=t?v[e].points:v[e];n._stroke=n.stroke(r,e),n._fill=n.fill(r,e)}function It(e,t){let n=t?v[e].points:v[e],{stroke:r,fill:i,clip:a,flags:o,_stroke:s=n._stroke,_fill:c=n._fill,_width:l=n.width}=n._paths;l=G(l*B,3);let u=null,f=l%2/2;t&&c==null&&(c=l>0?`#fff`:s);let p=n.pxAlign==1&&f>0;if(p&&d.translate(f,f),!t){let e=je-l/2,t=Me-l/2,n=Ne+l,r=Pe+l;u=new Path2D,u.rect(e,t,n,r)}t?Rt(s,l,n.dash,n.cap,c,r,i,o,a):Lt(e,s,l,n.dash,n.cap,c,r,i,o,u,a),p&&d.translate(-f,-f)}function Lt(e,n,i,a,o,s,c,l,u,d,f){let p=!1;u!=0&&x.forEach((m,h)=>{if(m.series[0]==e){let e=v[m.series[1]],g=t[m.series[1]],_=(e._paths||Jr).band;Zr(_)&&(_=m.dir==1?_[0]:_[1]);let y,b=null;e.show&&_&&pr(g,dt,ft)?(b=m.fill(r,h)||s,y=e._paths.clip):_=null,Rt(n,i,a,o,b,c,l,u,d,f,y,_),p=!0}}),p||Rt(n,i,a,o,s,c,l,u,d,f)}function Rt(e,t,n,r,i,a,o,s,c,l,u,f){Ot(e,t,n,r,i),(c||l||f)&&(d.save(),c&&d.clip(c),l&&d.clip(l)),f?(s&3)==3?(d.clip(f),u&&d.clip(u),Bt(i,o),zt(e,a,t)):s&2?(Bt(i,o),d.clip(f),zt(e,a,t)):s&1&&(d.save(),d.clip(f),u&&d.clip(u),Bt(i,o),d.restore(),zt(e,a,t)):(Bt(i,o),zt(e,a,t)),(c||l||f)&&d.restore()}function zt(e,t,n){n>0&&(t instanceof Map?t.forEach((e,t)=>{d.strokeStyle=vt=t,d.stroke(e)}):t!=null&&e&&d.stroke(t))}function Bt(e,t){t instanceof Map?t.forEach((e,t)=>{d.fillStyle=yt=t,d.fill(e)}):t!=null&&e&&d.fill(t)}function Vt(e,t,n,i){let a=y[e],o;if(i<=0)o=[0,0];else{let s=a._space=a.space(r,e,t,n,i);o=Po(t,n,a._incrs=a.incrs(r,e,t,n,i,s),i,s)}return a._found=o}function Ht(e,t,n,r,i,a,o,s,c,l){let u=o%2/2;h==1&&d.translate(u,u),Ot(s,o,c,l,s),d.beginPath();let f,p,m,g,_=i+(r==0||r==3?-a:a);n==0?(p=i,g=_):(f=i,m=_);for(let r=0;r<e.length;r++)t[r]!=null&&(n==0?f=m=e[r]:p=g=e[r],d.moveTo(f,p),d.lineTo(m,g));d.stroke(),h==1&&d.translate(-u,-u)}function Ut(e){let t=!0;return y.forEach((n,i)=>{if(!n.show)return;let a=b[n.scale];if(a.min==null){n._show&&(t=!1,n._show=!1,Kt(!1));return}else n._show||(t=!1,n._show=!0,Kt(!1));let o=n.side,s=o%2,{min:c,max:l}=a,[u,d]=Vt(i,c,l,s==0?F:I);if(d==0)return;let f=a.distr==2,p=n._splits=n.splits(r,i,c,l,u,d,f),m=a.distr==2?p.map(e=>mt[e]):p,h=a.distr==2?mt[p[1]]-mt[p[0]]:u,g=n._values=n.values(r,n.filter(r,m,i,d,h),i,d,h);n._rotate=o==2?n.rotate(r,g,i,d):0;let _=n._size;n._size=Sr(n.size(r,g,i,e)),_!=null&&n._size!=_&&(t=!1)}),t}function Wt(e){let t=!0;return ct.forEach((n,i)=>{let a=n(r,i,at,e);a!=lt[i]&&(t=!1),lt[i]=a}),t}function Gt(){for(let e=0;e<y.length;e++){let t=y[e];if(!t.show||!t._show)continue;let n=t.side,i=n%2,a,o,c=t.stroke(r,e),l=n==0||n==3?-1:1,[u,f]=t._found;if(t.label!=null){let s=t.labelGap*l,p=xr((t._lpos+s)*B);kt(t.labelFont[0],c,`center`,n==2?`top`:bn),d.save(),i==1?(a=o=0,d.translate(p,xr(Me+Pe/2)),d.rotate((n==3?-vr:vr)/2)):(a=xr(je+Ne/2),o=p);let m=Nr(t.label)?t.label(r,e,u,f):t.label;d.fillText(m,a,o),d.restore()}if(f==0)continue;let p=b[t.scale],m=i==0?Ne:Pe,h=i==0?je:Me,_=t._splits,v=p.distr==2?_.map(e=>mt[e]):_,x=p.distr==2?mt[_[1]]-mt[_[0]]:u,S=t.ticks,C=t.border,w=S.show?S.size:0,T=xr(w*B),E=xr((t.alignTo==2?t._size-w-t.gap:t.gap)*B),D=t._rotate*-vr/180,O=g(t._pos*B),ee=O+(T+E)*l;o=i==0?ee:0,a=i==1?ee:0;let te=t.font[0];kt(te,c,t.align==1?xn:t.align==2?Sn:D>0?xn:D<0?Sn:i==0?`center`:n==3?Sn:xn,D||i==1?`middle`:n==2?`top`:bn);let k=t.font[1]*t.lineGap,ne=_.map(e=>g(s(e,p,m,h))),re=t._values;for(let e=0;e<re.length;e++){let t=re[e];if(t!=null){i==0?a=ne[e]:o=ne[e],t=``+t;let n=t.indexOf(`\n`)==-1?[t]:t.split(/\\n/gm);for(let e=0;e<n.length;e++){let t=n[e];D?(d.save(),d.translate(a,o+e*k),d.rotate(D),d.fillText(t,0,0),d.restore()):d.fillText(t,a,o+e*k)}}}S.show&&Ht(ne,S.filter(r,v,e,f,x),i,n,O,T,G(S.width*B,3),S.stroke(r,e),S.dash,S.cap);let ie=t.grid;ie.show&&Ht(ne,ie.filter(r,v,e,f,x),i,i==0?2:1,i==0?Me:je,i==0?Pe:Ne,G(ie.width*B,3),ie.stroke(r,e),ie.dash,ie.cap),C.show&&Ht([O],[1],i==0?1:0,i==0?1:2,i==1?Me:je,i==1?Pe:Ne,G(C.width*B,3),C.stroke(r,e),C.dash,C.cap)}pi(`drawAxes`)}function Kt(e){v.forEach((t,n)=>{n>0&&(t._paths=null,e&&(i==1?(t.min=null,t.max=null):t.facets.forEach(e=>{e.min=null,e.max=null})))})}let qt=!1,Jt=!1,Yt=[];function Xt(){Jt=!1;for(let e=0;e<Yt.length;e++)pi(...Yt[e]);Yt.length=0}function Zt(){qt||=(li($t),!0)}function Qt(e,t=!1){qt=!0,Jt=t,e(r),$t(),t&&Yt.length>0&&queueMicrotask(Xt)}r.batch=Qt;function $t(){if(Fe&&=(Mt(),!1),Le&&=(Ue(),!1),Ie){if(V(p,xn,Te),V(p,`top`,Ee),V(p,vn,F),V(p,yn,I),V(m,xn,Te),V(m,`top`,Ee),V(m,vn,F),V(m,yn,I),V(f,vn,Ce),V(f,yn,we),u.width=xr(Ce*B),u.height=xr(we*B),y.forEach(({_el:e,_show:t,_size:n,_pos:r,side:i})=>{if(e!=null)if(t){let t=i===3||i===0?n:0,a=i%2==1;V(e,a?`left`:`top`,r-t),V(e,a?`width`:`height`,n),V(e,a?`top`:`left`,a?Ee:Te),V(e,a?`height`:`width`,a?I:F),Bn(e,gn)}else zn(e,gn)}),vt=yt=bt=St=Ct=wt=Tt=Et=xt=null,Dt=1,jr(!0),Te!=De||Ee!=Oe||F!=ke||I!=Ae){Kt(!1);let e=F/ke,t=I/Ae;if(ce&&!Re&&j.left>=0){j.left*=e,j.top*=t,rn&&Wn(rn,xr(j.left),0,F,I),an&&Wn(an,0,xr(j.top),F,I);for(let n=0;n<Qe.length;n++){let r=Qe[n];r!=null&&($e[n]*=e,et[n]*=t,Wn(r,Sr($e[n]),Sr(et[n]),F,I))}}if(z.show&&!ze&&z.left>=0&&z.width>0)for(let n in z.left*=e,z.width*=e,z.top*=t,z.height*=t,Br)V(Cn,n,z[n]);De=Te,Oe=Ee,ke=F,Ae=I}pi(`setSize`),Ie=!1}Ce>0&&we>0&&(d.clearRect(0,0,u.width,u.height),pi(`drawClear`),w.forEach(e=>e()),pi(`draw`)),z.show&&ze&&(jn(z),ze=!1),ce&&Re&&(Er(null,!0,!1),Re=!1),A.show&&A.live&&Be&&(_r(),Be=!1),c||(c=!0,r.status=1,pi(`ready`)),ht=!1,qt=!1}r.redraw=(e,t)=>{Le=t||!1,e===!1?Zt():In(S,D.min,D.max)};function en(e,n){let i=b[e];if(i.from==null){if(ut==0){let t=i.range(r,n.min,n.max,e);n.min=t[0],n.max=t[1]}if(n.min>n.max){let e=n.min;n.min=n.max,n.max=e}if(ut>1&&n.min!=null&&n.max!=null&&n.max-n.min<1e-16)return;e==S&&i.distr==2&&ut>0&&(n.min=$n(n.min,t[0]),n.max=$n(n.max,t[0]),n.min==n.max&&n.max++),k[e]=n,Fe=!0,Zt()}}r.setScale=en;let tn,nn,rn,an,on,sn,cn,ln,un,dn,L,R,fn=!1,pn=j.drag,mn=pn.x,hn=pn.y;ce&&(j.x&&(tn=Hn(`u-cursor-x`,m)),j.y&&(nn=Hn(`u-cursor-y`,m)),D.ori==0?(rn=tn,an=nn):(rn=nn,an=tn),L=j.left,R=j.top);let z=r.select=oi({show:!0,over:!0,left:0,width:0,top:0,height:0},e.select),Cn=z.show?Hn(`u-select`,z.over?m:p):null;function jn(e,t){if(z.show){for(let t in e)z[t]=e[t],t in Br&&V(Cn,t,e[t]);t!==!1&&pi(`setSelect`)}}r.setSelect=jn;function Nn(e){if(v[e].show)M&&Bn(de[e],gn);else if(M&&zn(de[e],gn),ce){let t=Ze?Qe[0]:Qe[e];t!=null&&Wn(t,-10,-10,F,I)}}function In(e,t,n){en(e,{min:t,max:n})}function Ln(e,t,n,a){t.focus!=null&&nr(e),t.show!=null&&v.forEach((n,r)=>{r>0&&(e==r||e==null)&&(n.show=t.show,Nn(r),i==2?(In(n.facets[0].scale,null,null),In(n.facets[1].scale,null,null)):In(n.scale,null,null),Zt())}),n!==!1&&pi(`setSeries`,e,t),a&&vi(`setSeries`,r,e,t)}r.setSeries=Ln;function Rn(e,t){oi(x[e],t)}function Un(e,t){e.fill=W(e.fill||null),e.dir=H(e.dir,-1),t??=x.length,x.splice(t,0,e)}function Gn(e){e==null?x.length=0:x.splice(e,1)}r.addBand=Un,r.setBand=Rn,r.delBand=Gn;function qn(e,t){v[e].alpha=t,ce&&Qe[e]!=null&&(Qe[e].style.opacity=t),M&&de[e]&&(de[e].style.opacity=t)}let Yn,Xn,er,tr={focus:!0};function nr(e){if(e!=er){let t=e==null,n=Ye.alpha!=1;v.forEach((r,a)=>{if(i==1||a>0){let i=t||a==0||a==e;r._focus=t?null:i,n&&qn(a,i?1:Ye.alpha)}}),er=e,n&&Zt()}}M&&Xe&&xe(kn,P,e=>{j._lock||(qe(e),er!=null&&Ln(null,tr,!0,hi.setSeries))});function rr(e,t,n){let r=b[t];n&&(e=e/B-(r.ori==1?Ee:Te));let i=F;r.ori==1&&(i=I,e=i-e),r.dir==-1&&(e=i-e);let a=r._min,o=r._max,s=e/i,c=a+(o-a)*s,l=r.distr;return l==3?Tr(10,c):l==4?kr(c,r.asinh):l==100?r.bwd(c):c}function ir(e,n){return $n(rr(e,S,n),t[0],dt,ft)}r.valToIdx=e=>$n(e,t[0]),r.posToIdx=ir,r.posToVal=rr,r.valToPos=(e,t,n)=>b[t].ori==0?a(e,b[t],n?Ne:F,n?je:0):o(e,b[t],n?Pe:I,n?Me:0),r.setCursor=(e,t,n)=>{L=e.left,R=e.top,Er(null,t,n)};function ur(e,t){V(Cn,xn,z.left=e),V(Cn,vn,z.width=t)}function dr(e,t){V(Cn,`top`,z.top=e),V(Cn,yn,z.height=t)}let mr=D.ori==0?ur:dr,hr=D.ori==1?ur:dr;function gr(){if(M&&A.live)for(let e=i==2?1:0;e<v.length;e++){if(e==0&&me)continue;let t=A.values[e],n=0;for(let r in t)fe[e][n++].firstChild.nodeValue=t[r]}}function _r(e,t){if(e!=null&&(e.idxs?e.idxs.forEach((e,t)=>{se[t]=e}):$r(e.idx)||se.fill(e.idx),A.idx=se[0]),M&&A.live){for(let e=0;e<v.length;e++)(e>0||i==1&&!me)&&br(e,se[e]);gr()}Be=!1,t!==!1&&pi(`setLegend`)}r.setLegend=_r;function br(e,n){let i=v[e],a=e==0&&O==2?mt:t[e],o;me?o=i.values(r,e,n)??he:(o=i.value(r,n==null?null:a[n],e,n),o=o==null?he:{_:o}),A.values[e]=o}function Er(e,n,a){un=L,dn=R,[L,R]=j.move(r,L,R),j.left=L,j.top=R,ce&&(rn&&Wn(rn,xr(L),0,F,I),an&&Wn(an,0,xr(R),F,I));let o,s=dt>ft;Yn=U,Xn=null;let c=D.ori==0?F:I,l=D.ori==1?F:I;if(L<0||ut==0||s){o=j.idx=null;for(let e=0;e<v.length;e++){let t=Qe[e];t!=null&&Wn(t,-10,-10,F,I)}Xe&&Ln(null,tr,!0,e==null&&hi.setSeries),A.live&&(se.fill(o),Be=!0)}else{let e,n,a;i==1&&(e=D.ori==0?L:R,n=rr(e,S),o=j.idx=$n(n,t[0],dt,ft),a=ee(t[0][o],D,c,0));let s=-10,u=-10,d=0,f=0,p=!0,m=``,h=``;for(let e=i==2?1:0;e<v.length;e++){let g=v[e],_=se[e],y=_==null?null:i==1?t[e][_]:t[e][1][_],x=j.dataIdx(r,e,o,n),S=x==null?null:i==1?t[e][x]:t[e][1][x];if(Be=Be||S!=y||x!=_,se[e]=x,e>0&&g.show){let n=x==null?-10:x==o?a:ee(i==1?t[0][x]:t[e][0][x],D,c,0),_=S==null?-10:te(S,i==1?b[g.scale]:b[g.facets[1].scale],l,0);if(Xe&&S!=null){let t=D.ori==1?L:R,n=yr(Ye.dist(r,e,x,_,t));if(n<Yn){let r=Ye.bias;if(r!=0){let i=rr(t,g.scale),a=S>=0?1:-1,o=i>=0?1:-1;o==a&&(o==1?r==1?S>=i:S<=i:r==1?S<=i:S>=i)&&(Yn=n,Xn=e)}else Yn=n,Xn=e}}if(Be||Ze){let t,i;D.ori==0?(t=n,i=_):(t=_,i=n);let a,o,c,l,g,v,y=!0,b=Je.bbox;if(b!=null){y=!1;let t=b(r,e);c=t.left,l=t.top,a=t.width,o=t.height}else c=t,l=i,a=o=Je.size(r,e);if(v=Je.fill(r,e),g=Je.stroke(r,e),Ze)e==Xn&&Yn<=Ye.prox&&(s=c,u=l,d=a,f=o,p=y,m=v,h=g);else{let t=Qe[e];t!=null&&($e[e]=c,et[e]=l,Jn(t,a,o,y),Kn(t,v,g),Wn(t,Sr(c),Sr(l),F,I))}}}}if(Ze){let e=Ye.prox;if(Be||(er==null?Yn<=e:Yn>e||Xn!=er)){let e=Qe[0];e!=null&&($e[0]=s,et[0]=u,Jn(e,d,f,p),Kn(e,m,h),Wn(e,Sr(s),Sr(u),F,I))}}}if(z.show&&fn)if(e!=null){let[t,n]=hi.scales,[r,i]=hi.match,[a,o]=e.cursor.sync.scales,s=e.cursor.drag;if(mn=s._x,hn=s._y,mn||hn){let{left:s,top:u,width:d,height:f}=e.select,p=e.scales[a].ori,m=e.posToVal,h,g,_,v,y,x=t!=null&&r(t,a),S=n!=null&&i(n,o);x&&mn?(p==0?(h=s,g=d):(h=u,g=f),_=b[t],v=ee(m(h,a),_,c,0),y=ee(m(h+g,a),_,c,0),mr(Cr(v,y),yr(y-v))):mr(0,c),S&&hn?(p==1?(h=s,g=d):(h=u,g=f),_=b[n],v=te(m(h,o),_,l,0),y=te(m(h+g,o),_,l,0),hr(Cr(v,y),yr(y-v))):hr(0,l)}else Vr()}else{let e=yr(un-on),t=yr(dn-sn);if(D.ori==1){let n=e;e=t,t=n}mn=pn.x&&e>=pn.dist,hn=pn.y&&t>=pn.dist;let n=pn.uni;n==null?pn.x&&pn.y&&(mn||hn)&&(mn=hn=!0):mn&&hn&&(mn=e>=n,hn=t>=n,!mn&&!hn&&(t>e?hn=!0:mn=!0));let r,i;mn&&(D.ori==0?(r=cn,i=L):(r=ln,i=R),mr(Cr(r,i),yr(i-r)),hn||hr(0,l)),hn&&(D.ori==1?(r=cn,i=L):(r=ln,i=R),hr(Cr(r,i),yr(i-r)),mn||mr(0,c)),!mn&&!hn&&(mr(0,0),hr(0,0))}if(pn._x=mn,pn._y=hn,e==null){if(a){if(gi!=null){let[e,t]=hi.scales;hi.values[0]=e==null?null:rr(D.ori==0?L:R,e),hi.values[1]=t==null?null:rr(D.ori==1?L:R,t)}vi(Tn,r,L,R,F,I,o)}if(Xe){let e=a&&hi.setSeries,t=Ye.prox;er==null?Yn<=t&&Ln(Xn,tr,!0,e):Yn>t?Ln(null,tr,!0,e):Xn!=er&&Ln(Xn,tr,!0,e)}}Be&&(A.idx=o,_r()),n!==!1&&pi(`setCursor`)}let Or=null;Object.defineProperty(r,`rect`,{get(){return Or??jr(!1),Or}});function jr(e=!1){e?Or=null:(Or=m.getBoundingClientRect(),pi(`syncRect`,Or))}function Pr(e,t,n,r,i,a,o){j._lock||fn&&e!=null&&e.movementX==0&&e.movementY==0||(Fr(e,t,n,r,i,a,o,!1,e!=null),e==null?Er(t,!0,!1):Er(null,!0,!0))}function Fr(e,t,n,i,a,o,c,l,u){if(Or??jr(!1),qe(e),e!=null)n=e.clientX-Or.left,i=e.clientY-Or.top;else{if(n<0||i<0){L=-10,R=-10;return}let[e,r]=hi.scales,c=t.cursor.sync,[l,u]=c.values,[d,f]=c.scales,[p,m]=hi.match,h=t.axes[0].side%2==1,g=D.ori==0?F:I,_=D.ori==1?F:I,v=h?o:a,y=h?a:o,x=h?i:n,S=h?n:i;if(n=d==null?x/v*g:p(e,d)?s(l,b[e],g,0):-10,i=f==null?S/y*_:m(r,f)?s(u,b[r],_,0):-10,D.ori==1){let e=n;n=i,i=e}}u&&(t==null||t.cursor.event.type==Tn)&&((n<=1||n>=F-1)&&(n=Hr(n,F)),(i<=1||i>=I-1)&&(i=Hr(i,I))),l?(on=n,sn=i,[cn,ln]=j.move(r,n,i)):(L=n,R=i)}let Br={width:0,height:0,left:0,top:0};function Vr(){jn(Br,!1)}let Ur,Wr,qr,Qr;function ri(e,t,n,i,a,o,s){fn=!0,mn=hn=pn._x=pn._y=!1,Fr(e,t,n,i,a,o,s,!0,!1),e!=null&&(xe(Dn,Pn,ii,!1),vi(En,r,cn,ln,F,I,null));let{left:c,top:l,width:u,height:d}=z;Ur=c,Wr=l,qr=u,Qr=d}function ii(e,t,n,i,a,o,s){fn=pn._x=pn._y=!1,Fr(e,t,n,i,a,o,s,!1,!0);let{left:c,top:l,width:u,height:d}=z,f=u>0||d>0,p=Ur!=c||Wr!=l||qr!=u||Qr!=d;if(f&&p&&jn(z),pn.setScale&&f&&p){let e=c,t=u,n=l,r=d;if(D.ori==1&&(e=l,t=d,n=c,r=u),mn&&In(S,rr(e,S),rr(e+t,S)),hn)for(let e in b){let t=b[e];e!=S&&t.from==null&&t.min!=U&&In(e,rr(n+r,e),rr(n,e))}Vr()}else j.lock&&(j._lock=!j._lock,Er(t,!0,e!=null));e!=null&&(Se(Dn,Pn),vi(Dn,r,L,R,F,I,null))}function si(e,t,n,r,i,a,o){if(j._lock)return;qe(e);let s=fn;if(fn){let e=!0,t=!0,n,r;D.ori==0?(n=mn,r=hn):(n=hn,r=mn),n&&r&&(e=L<=10||L>=F-10,t=R<=10||R>=I-10),n&&e&&(L=L<cn?0:F),r&&t&&(R=R<ln?0:I),Er(null,!0,!0),fn=!1}L=-10,R=-10,se.fill(null),Er(null,!0,!0),s&&(fn=s)}function ci(e,t,n,i,a,o,s){j._lock||(qe(e),_t(),Vr(),e!=null&&vi(An,r,L,R,F,I,null))}function ui(){y.forEach(Io),Ve(r.width,r.height,!0)}Zn(Mn,Fn,ui);let di={};di.mousedown=ri,di.mousemove=Pr,di.mouseup=ii,di.dblclick=ci,di.setSeries=(e,t,n,i)=>{let a=hi.match[2];n=a(r,t,n),n!=-1&&Ln(n,i,!0,!1)},ce&&(xe(En,m,ri),xe(Tn,m,Pr),xe(On,m,e=>{qe(e),jr(!1)}),xe(kn,m,si),xe(An,m,ci),bo.add(r),r.syncRect=jr);let fi=r.hooks=e.hooks||{};function pi(e,t,n){Jt?Yt.push([e,t,n]):e in fi&&fi[e].forEach(e=>{e.call(null,r,t,n)})}(e.plugins||[]).forEach(e=>{for(let t in e.hooks)fi[t]=(fi[t]||[]).concat(e.hooks[t])});let mi=(e,t,n)=>n,hi=oi({key:null,setSeries:!1,filters:{pub:Rr,sub:Rr},scales:[S,v[1]?v[1].scale:null],match:[zr,zr,mi],values:[null,null]},j.sync);hi.match.length==2&&hi.match.push(mi),j.sync=hi;let gi=hi.key,_i=Ha(gi);function vi(e,t,n,r,i,a,o){hi.filters.pub(e,t,n,r,i,a,o)&&_i.pub(e,t,n,r,i,a,o)}_i.sub(r);function yi(e,t,n,r,i,a,o){hi.filters.sub(e,t,n,r,i,a,o)&&di[e](null,t,n,r,i,a,o)}r.pub=yi;function bi(){_i.unsub(r),bo.delete(r),be.clear(),Qn(Mn,Fn,ui),l.remove(),P?.remove(),pi(`destroy`)}r.destroy=bi;function Si(){pi(`init`,e,t),gt(t||e.data,!1),k[S]?en(S,k[S]):_t(),ze=z.show&&(z.width>0||z.height>0),Re=Be=!0,Ve(e.width,e.height)}return v.forEach(nt),y.forEach(ot),n?n instanceof HTMLElement?(n.appendChild(l),Si()):n(r,Si):Si(),r}Lo.assign=oi,Lo.fmtNum=gr,Lo.rangeNum=fr,Lo.rangeLog=or,Lo.rangeAsinh=sr,Lo.orient=Ua,Lo.pxRatio=B,Lo.join=ci,Lo.fmtDate=xi,Lo.tzDate=Ci,Lo.sync=Ha;{Lo.addGap=Ja,Lo.clipGaps=qa;let e=Lo.paths={points:co};e.linear=po,e.stepped=mo,e.bars=go,e.spline=vo}var Ro=t({default:()=>zo}),zo=class extends nt{static{this.targets=[`table`,`chart`,`summary`,`legend`,`legendAmount`,`legendPeriod`,`axis`]}getSize(){return{width:this.chartTarget.offsetWidth,height:this.chartTarget.offsetHeight}}connect(){this.tableTarget.hidden=!0,this.chartTarget.hidden=!1,this.axisTarget.hidden=!1,this.observer=new ResizeObserver(()=>{this.uplot?.setSize(this.getSize())}),this.observer.observe(this.chartTarget);let e=getComputedStyle(document.documentElement),t={...this.getSize(),padding:[1,1,1,1],series:[{},{stroke:e.getPropertyValue(`--color-stroke`),width:2,points:{show:!1}},{stroke:e.getPropertyValue(`--color-accent`),width:2,points:{show:!1}}],cursor:{y:!1},axes:[{show:!1},{show:!1}],scales:{y:{range:(e,t,n)=>[t,n]}},select:{show:!1},legend:{show:!1},hooks:{init:[e=>{e.over.addEventListener(`mouseenter`,()=>{this.summaryTarget.hidden=!0,this.legendTarget.hidden=!1}),e.over.addEventListener(`mouseleave`,()=>{this.summaryTarget.hidden=!1,this.legendTarget.hidden=!0})}],setCursor:[e=>{let{idx:t}=e.cursor;this.legendAmountTarget.textContent=typeof t==`number`?Lo.fmtNum(e.data[2][t]||0):``,this.legendPeriodTarget.textContent=typeof t==`number`?n[t].textContent:``}]}},n=Array.from(this.tableTarget.querySelectorAll(`thead th`)).slice(1);this.uplot=new Lo(t,[n.map(e=>Number(e.dataset.timestamp)),...Array.from(this.tableTarget.querySelectorAll(`tbody tr`)).reverse().map(e=>Array.from(e.querySelectorAll(`td`)).map(e=>Number(e.textContent)))],this.chartTarget)}disconnect(){this.observer?.disconnect(),this.uplot?.destroy()}},Bo=t({default:()=>Vo}),Vo=class extends nt{constructor(...e){super(...e),this.mouseover=e=>{let t=e.target;if(t.closest(`thead th`)){let e=Array.from(t.parentElement.children).indexOf(t);this.element.querySelector(`colgroup`).children[e].classList.add(`is-highlighted`),this.element.style.cursor=`pointer`}t.closest(`tbody th`)&&(t.closest(`tr`).classList.add(`is-highlighted`),this.element.style.cursor=`pointer`)},this.reset=()=>{this.element.querySelectorAll(`col, tr`).forEach(e=>e.classList.remove(`is-highlighted`)),this.element.style.cursor=``},this.click=e=>{let t=e.target;if(t.closest(`thead th`)){let e=Array.from(t.parentElement.children).indexOf(t),n=Array.from(this.element.querySelectorAll(`tbody tr td:nth-child(${e+1}) input[type=\"checkbox\"]`)).filter(e=>!this.disabled?.includes(e)),r=!n.find(e=>!e.disabled&&e.getAttribute(`aria-disabled`)!==`true`)?.checked;n.forEach(e=>e.checked=r)}if(t.closest(`tbody th`)){let e=Array.from(t.closest(`tr`).querySelectorAll(`td input[type=\"checkbox\"]`)).filter(e=>!this.disabled?.includes(e)),n=!e.find(e=>!e.disabled&&e.getAttribute(`aria-disabled`)!==`true`)?.checked;e.forEach(e=>e.checked=n)}this.update()}}connect(){this.disabled=Array.from(this.element.querySelectorAll(`input:disabled`)),this.update(),this.element.addEventListener(`click`,this.click),this.element.addEventListener(`mouseover`,this.mouseover),this.element.addEventListener(`mouseout`,this.reset)}disconnect(){this.element.removeEventListener(`click`,this.click),this.element.removeEventListener(`mouseover`,this.mouseover),this.element.removeEventListener(`mouseout`,this.reset)}update(){this.element.querySelectorAll(`tbody td input[type=\"checkbox\"]`).forEach(e=>{this.disabled?.includes(e)||(e.disabled=!1)}),this.element.querySelectorAll(`[data-implied-by], [data-depends-on]`).forEach(e=>{e.dataset.impliedBy?.trim().split(/\\s+/).filter(Boolean).forEach(t=>{let n=document.querySelector(`[name=\"${t}\"]:last-of-type`);n&&n.checked&&(e.checked=!0,e.disabled=!0)}),e.dataset.dependsOn?.trim().split(/\\s+/).filter(Boolean).forEach(t=>{let n=document.querySelector(`[name=\"${t}\"]:last-of-type`);n&&!n.checked&&(e.checked=!1,e.disabled=!0)})})}},Ho=t({default:()=>Uo}),Uo=class extends nt{constructor(...e){super(...e),this.locked=!1}static{this.targets=[`slug`,`mirror`]}updateName(e){let t=e.target;this.locked||(this.slugTarget.value=Se(t.value),this.mirror())}updateSlug(e){let t=e.target;this.mirror(),this.locked=!!t.value}mirror(){this.mirrorTargets.forEach(e=>{e.textContent=this.slugTarget.value})}},Wo=Symbol.for(`preact-signals`);function Go(){if(Jo>1)Jo--;else{for(var e,t=!1;qo!==void 0;){var n=qo;for(qo=void 0,Yo++;n!==void 0;){var r=n.o;if(n.o=void 0,n.f&=-3,!(8&n.f)&&es(n))try{n.c()}catch(n){t||=(e=n,!0)}n=r}}if(Yo=0,Jo--,t)throw e}}function Ko(e){if(Jo>0)return e();Jo++;try{return e()}finally{Go()}}var K=void 0;function q(e){var t=K;K=void 0;try{return e()}finally{K=t}}var qo=void 0,Jo=0,Yo=0,Xo=0;function Zo(e){if(K!==void 0){var t=e.n;if(t===void 0||t.t!==K)return t={i:0,S:e,p:K.s,n:void 0,t:K,e:void 0,x:void 0,r:t},K.s!==void 0&&(K.s.n=t),K.s=t,e.n=t,32&K.f&&e.S(t),t;if(t.i===-1)return t.i=0,t.n!==void 0&&(t.n.p=t.p,t.p!==void 0&&(t.p.n=t.n),t.p=K.s,t.n=void 0,K.s.n=t,K.s=t),t}}function Qo(e,t){this.v=e,this.i=0,this.n=void 0,this.t=void 0,this.W=t?.watched,this.Z=t?.unwatched,this.name=t?.name}Qo.prototype.brand=Wo,Qo.prototype.h=function(){return!0},Qo.prototype.S=function(e){var t=this,n=this.t;n!==e&&e.e===void 0&&(e.x=n,this.t=e,n===void 0?q(function(){var e;(e=t.W)==null||e.call(t)}):n.e=e)},Qo.prototype.U=function(e){var t=this;if(this.t!==void 0){var n=e.e,r=e.x;n!==void 0&&(n.x=r,e.e=void 0),r!==void 0&&(r.e=n,e.x=void 0),e===this.t&&(this.t=r,r===void 0&&q(function(){var e;(e=t.Z)==null||e.call(t)}))}},Qo.prototype.subscribe=function(e){var t=this;return ls(function(){var n=t.value,r=K;K=void 0;try{e(n)}finally{K=r}},{name:`sub`})},Qo.prototype.valueOf=function(){return this.value},Qo.prototype.toString=function(){return this.value+``},Qo.prototype.toJSON=function(){return this.value},Qo.prototype.peek=function(){var e=K;K=void 0;try{return this.value}finally{K=e}},Object.defineProperty(Qo.prototype,`value`,{get:function(){var e=Zo(this);return e!==void 0&&(e.i=this.i),this.v},set:function(e){if(e!==this.v){if(Yo>100)throw Error(`Cycle detected`);this.v=e,this.i++,Xo++,Jo++;try{for(var t=this.t;t!==void 0;t=t.x)t.t.N()}finally{Go()}}}});function $o(e,t){return new Qo(e,t)}function es(e){for(var t=e.s;t!==void 0;t=t.n)if(t.S.i!==t.i||!t.S.h()||t.S.i!==t.i)return!0;return!1}function ts(e){for(var t=e.s;t!==void 0;t=t.n){var n=t.S.n;if(n!==void 0&&(t.r=n),t.S.n=t,t.i=-1,t.n===void 0){e.s=t;break}}}function ns(e){for(var t=e.s,n=void 0;t!==void 0;){var r=t.p;t.i===-1?(t.S.U(t),r!==void 0&&(r.n=t.n),t.n!==void 0&&(t.n.p=r)):n=t,t.S.n=t.r,t.r!==void 0&&(t.r=void 0),t=r}e.s=n}function rs(e,t){Qo.call(this,void 0),this.x=e,this.s=void 0,this.g=Xo-1,this.f=4,this.W=t?.watched,this.Z=t?.unwatched,this.name=t?.name}rs.prototype=new Qo,rs.prototype.h=function(){if(this.f&=-3,1&this.f)return!1;if((36&this.f)==32||(this.f&=-5,this.g===Xo))return!0;if(this.g=Xo,this.f|=1,this.i>0&&!es(this))return this.f&=-2,!0;var e=K;try{ts(this),K=this;var t=this.x();(16&this.f||this.v!==t||this.i===0)&&(this.v=t,this.f&=-17,this.i++)}catch(e){this.v=e,this.f|=16,this.i++}return K=e,ns(this),this.f&=-2,!0},rs.prototype.S=function(e){if(this.t===void 0){this.f|=36;for(var t=this.s;t!==void 0;t=t.n)t.S.S(t)}Qo.prototype.S.call(this,e)},rs.prototype.U=function(e){if(this.t!==void 0&&(Qo.prototype.U.call(this,e),this.t===void 0)){this.f&=-33;for(var t=this.s;t!==void 0;t=t.n)t.S.U(t)}},rs.prototype.N=function(){if(!(2&this.f)){this.f|=6;for(var e=this.t;e!==void 0;e=e.x)e.t.N()}},Object.defineProperty(rs.prototype,`value`,{get:function(){if(1&this.f)throw Error(`Cycle detected`);var e=Zo(this);if(this.h(),e!==void 0&&(e.i=this.i),16&this.f)throw this.v;return this.v}});function is(e,t){return new rs(e,t)}function as(e){var t=e.u;if(e.u=void 0,typeof t==`function`){Jo++;var n=K;K=void 0;try{t()}catch(t){throw e.f&=-2,e.f|=8,os(e),t}finally{K=n,Go()}}}function os(e){for(var t=e.s;t!==void 0;t=t.n)t.S.U(t);e.x=void 0,e.s=void 0,as(e)}function ss(e){if(K!==this)throw Error(`Out-of-order effect`);ns(this),K=e,this.f&=-2,8&this.f&&os(this),Go()}function cs(e,t){this.x=e,this.u=void 0,this.s=void 0,this.o=void 0,this.f=32,this.name=t?.name}cs.prototype.c=function(){var e=this.S();try{if(8&this.f||this.x===void 0)return;var t=this.x();typeof t==`function`&&(this.u=t)}finally{e()}},cs.prototype.S=function(){if(1&this.f)throw Error(`Cycle detected`);this.f|=1,this.f&=-9,as(this),ts(this),Jo++;var e=K;return K=this,ss.bind(this,e)},cs.prototype.N=function(){2&this.f||(this.f|=2,this.o=qo,qo=this)},cs.prototype.d=function(){this.f|=8,1&this.f||os(this)},cs.prototype.dispose=function(){this.d()};function ls(e,t){var n=new cs(e,t);try{n.c()}catch(e){throw n.d(),e}var r=n.d.bind(n);return r[Symbol.dispose]=r,r}var us=Object.create,ds=Object.defineProperty,fs=Object.defineProperties,ps=Object.getOwnPropertyDescriptor,ms=Object.getOwnPropertyDescriptors,hs=Object.getOwnPropertySymbols,gs=Object.prototype.hasOwnProperty,_s=Object.prototype.propertyIsEnumerable,vs=(e,t)=>(t=Symbol[e])?t:Symbol.for(`Symbol.`+e),ys=e=>{throw TypeError(e)},bs=(e,t,n)=>t in e?ds(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,xs=(e,t)=>{for(var n in t||={})gs.call(t,n)&&bs(e,n,t[n]);if(hs)for(var n of hs(t))_s.call(t,n)&&bs(e,n,t[n]);return e},Ss=(e,t)=>fs(e,ms(t)),Cs=(e,t)=>ds(e,`name`,{value:t,configurable:!0}),ws=e=>[,,,us(e?.[vs(`metadata`)]??null)],Ts=[`class`,`method`,`getter`,`setter`,`accessor`,`field`,`value`,`get`,`set`],Es=e=>e!==void 0&&typeof e!=`function`?ys(`Function expected`):e,Ds=(e,t,n,r,i)=>({kind:Ts[e],name:t,metadata:r,addInitializer:e=>n._?ys(`Already initialized`):i.push(Es(e||null))}),Os=(e,t)=>bs(t,vs(`metadata`),e[3]),ks=(e,t,n,r)=>{for(var i=0,a=e[t>>1],o=a&&a.length;i<o;i++)t&1?a[i].call(n):r=a[i].call(n,r);return r},As=(e,t,n,r,i,a)=>{var o,s,c,l,u,d=t&7,f=!!(t&8),p=!!(t&16),m=d>3?e.length+1:d?f?1:2:0,h=Ts[d+5],g=d>3&&(e[m-1]=[]),_=e[m]||(e[m]=[]),v=d&&(!p&&!f&&(i=i.prototype),d<5&&(d>3||!p)&&ps(d<4?i:{get[n](){return Ns(this,a)},set[n](e){return Fs(this,a,e)}},n));d?p&&d<4&&Cs(a,(d>2?`set `:d>1?`get `:``)+n):Cs(i,n);for(var y=r.length-1;y>=0;y--)l=Ds(d,n,c={},e[3],_),d&&(l.static=f,l.private=p,u=l.access={has:p?e=>Ms(i,e):e=>n in e},d^3&&(u.get=p?e=>(d^1?Ns:Is)(e,i,d^4?a:v.get):e=>e[n]),d>2&&(u.set=p?(e,t)=>Fs(e,i,t,d^4?a:v.set):(e,t)=>e[n]=t)),s=(0,r[y])(d?d<4?p?a:v[h]:d>4?void 0:{get:v.get,set:v.set}:i,l),c._=1,d^4||s===void 0?Es(s)&&(d>4?g.unshift(s):d?p?a=s:v[h]=s:i=s):typeof s!=`object`||!s?ys(`Object expected`):(Es(o=s.get)&&(v.get=o),Es(o=s.set)&&(v.set=o),Es(o=s.init)&&g.unshift(o));return d||Os(e,i),v&&ds(i,n,v),p?d^4?a:v:i},js=(e,t,n)=>t.has(e)||ys(`Cannot `+n),Ms=(e,t)=>Object(t)===t?e.has(t):ys(`Cannot use the \"in\" operator on this value`),Ns=(e,t,n)=>(js(e,t,`read from private field`),n?n.call(e):t.get(e)),Ps=(e,t,n)=>t.has(e)?ys(`Cannot add the same private member more than once`):t instanceof WeakSet?t.add(e):t.set(e,n),Fs=(e,t,n,r)=>(js(e,t,`write to private field`),r?r.call(e,n):t.set(e,n),n),Is=(e,t,n)=>(js(e,t,`access private method`),n);function Ls(e,t){if(t){let n;return is(()=>{let r=e();return r&&n&&t(n,r)?n:(n=r,r)})}return is(e)}function Rs(e,t){if(Object.is(e,t))return!0;if(e===null||t===null)return!1;if(typeof e==`function`&&typeof t==`function`)return e===t;if(e instanceof Set&&t instanceof Set){if(e.size!==t.size)return!1;for(let n of e)if(!t.has(n))return!1;return!0}if(Array.isArray(e))return!Array.isArray(t)||e.length!==t.length?!1:!e.some((e,n)=>!Rs(e,t[n]));if(typeof e==`object`&&typeof t==`object`){let n=Object.keys(e),r=Object.keys(t);return n.length===r.length?!n.some(n=>!Rs(e[n],t[n])):!1}return!1}function J({get:e},t){return{init(e){return $o(e)},get(){return e.call(this).value},set(t){let n=e.call(this);n.peek()!==t&&(n.value=t)}}}function zs(e,t){let n=new WeakMap;return function(){let t=n.get(this);return t||(t=Ls(e.bind(this)),n.set(this,t)),t.value}}function Bs(e=!0){return function(t,n){n.addInitializer(function(){let t=n.kind===`field`||n.static?this:Object.getPrototypeOf(this),r=Object.getOwnPropertyDescriptor(t,n.name);r&&Object.defineProperty(t,n.name,Ss(xs({},r),{enumerable:e}))})}}function Vs(...e){let t=e.map(e=>ls(e));return()=>t.forEach(e=>e())}var Hs,Us,Ws,Gs,Ks,qs=[J],Js,Ys,Xs,Zs,Qs,$s,ec,tc,nc,rc,ic,ac,oc,sc;Ks=[J],Gs=[J],Ws=[Bs()],Us=[Bs()],Hs=[Bs()];var cc=class{constructor(e,t=Object.is){this.defaultValue=e,this.equals=t,ks(Js,5,this),Ps(this,$s),Ps(this,Ys,ks(Js,8,this)),ks(Js,11,this),Ps(this,ec,ks(Js,12,this)),ks(Js,15,this),Ps(this,ic,ks(Js,16,this)),ks(Js,19,this),this.reset=this.reset.bind(this),this.reset()}get current(){return Ns(this,$s,oc)}get initial(){return Ns(this,$s,Zs)}get previous(){return Ns(this,$s,nc)}set current(e){let t=q(()=>Ns(this,$s,oc));e&&t&&this.equals(t,e)||Ko(()=>{Ns(this,$s,Zs)||Fs(this,$s,e,Qs),Fs(this,$s,t,rc),Fs(this,$s,e,sc)})}reset(e=this.defaultValue){Ko(()=>{Fs(this,$s,void 0,rc),Fs(this,$s,e,Qs),Fs(this,$s,e,sc)})}};Js=ws(null),Ys=new WeakMap,$s=new WeakSet,ec=new WeakMap,ic=new WeakMap,Xs=As(Js,20,`#initial`,qs,$s,Ys),Zs=Xs.get,Qs=Xs.set,tc=As(Js,20,`#previous`,Ks,$s,ec),nc=tc.get,rc=tc.set,ac=As(Js,20,`#current`,Gs,$s,ic),oc=ac.get,sc=ac.set,As(Js,2,`current`,Ws,cc),As(Js,2,`initial`,Us,cc),As(Js,2,`previous`,Hs,cc),Os(Js,cc);function lc(e){return q(()=>{let t={};for(let n in e)t[n]=e[n];return t})}var uc,dc=class{constructor(){Ps(this,uc,new WeakMap)}get(e,t){return e?Ns(this,uc).get(e)?.get(t):void 0}set(e,t,n){if(e)return Ns(this,uc).has(e)||Ns(this,uc).set(e,new Map),Ns(this,uc).get(e)?.set(t,n)}clear(e){return e?Ns(this,uc).get(e)?.clear():void 0}};uc=new WeakMap;var fc=Object.create,pc=Object.defineProperty,mc=Object.getOwnPropertyDescriptor,hc=Object.getOwnPropertySymbols,gc=Object.prototype.hasOwnProperty,_c=Object.prototype.propertyIsEnumerable,vc=(e,t)=>(t=Symbol[e])?t:Symbol.for(`Symbol.`+e),yc=e=>{throw TypeError(e)},bc=Math.pow,xc=(e,t,n)=>t in e?pc(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,Sc=(e,t)=>{for(var n in t||={})gc.call(t,n)&&xc(e,n,t[n]);if(hc)for(var n of hc(t))_c.call(t,n)&&xc(e,n,t[n]);return e},Cc=(e,t)=>pc(e,`name`,{value:t,configurable:!0}),wc=e=>[,,,fc(e?.[vc(`metadata`)]??null)],Tc=[`class`,`method`,`getter`,`setter`,`accessor`,`field`,`value`,`get`,`set`],Ec=e=>e!==void 0&&typeof e!=`function`?yc(`Function expected`):e,Dc=(e,t,n,r,i)=>({kind:Tc[e],name:t,metadata:r,addInitializer:e=>n._?yc(`Already initialized`):i.push(Ec(e||null))}),Oc=(e,t)=>xc(t,vc(`metadata`),e[3]),kc=(e,t,n,r)=>{for(var i=0,a=e[t>>1],o=a&&a.length;i<o;i++)t&1?a[i].call(n):r=a[i].call(n,r);return r},Ac=(e,t,n,r,i,a)=>{var o,s,c,l,u,d=t&7,f=!!(t&8),p=!!(t&16),m=d>3?e.length+1:d?f?1:2:0,h=Tc[d+5],g=d>3&&(e[m-1]=[]),_=e[m]||(e[m]=[]),v=d&&(!p&&!f&&(i=i.prototype),d<5&&(d>3||!p)&&mc(d<4?i:{get[n](){return Nc(this,a)},set[n](e){return Fc(this,a,e)}},n));d?p&&d<4&&Cc(a,(d>2?`set `:d>1?`get `:``)+n):Cc(i,n);for(var y=r.length-1;y>=0;y--)l=Dc(d,n,c={},e[3],_),d&&(l.static=f,l.private=p,u=l.access={has:p?e=>Mc(i,e):e=>n in e},d^3&&(u.get=p?e=>(d^1?Nc:Ic)(e,i,d^4?a:v.get):e=>e[n]),d>2&&(u.set=p?(e,t)=>Fc(e,i,t,d^4?a:v.set):(e,t)=>e[n]=t)),s=(0,r[y])(d?d<4?p?a:v[h]:d>4?void 0:{get:v.get,set:v.set}:i,l),c._=1,d^4||s===void 0?Ec(s)&&(d>4?g.unshift(s):d?p?a=s:v[h]=s:i=s):typeof s!=`object`||!s?yc(`Object expected`):(Ec(o=s.get)&&(v.get=o),Ec(o=s.set)&&(v.set=o),Ec(o=s.init)&&g.unshift(o));return d||Oc(e,i),v&&pc(i,n,v),p?d^4?a:v:i},jc=(e,t,n)=>t.has(e)||yc(`Cannot `+n),Mc=(e,t)=>Object(t)===t?e.has(t):yc(`Cannot use the \"in\" operator on this value`),Nc=(e,t,n)=>(jc(e,t,`read from private field`),n?n.call(e):t.get(e)),Pc=(e,t,n)=>t.has(e)?yc(`Cannot add the same private member more than once`):t instanceof WeakSet?t.add(e):t.set(e,n),Fc=(e,t,n,r)=>(jc(e,t,`write to private field`),r?r.call(e,n):t.set(e,n),n),Ic=(e,t,n)=>(jc(e,t,`access private method`),n),Lc=class e{constructor(e,t){this.x=e,this.y=t}static delta(t,n){return new e(t.x-n.x,t.y-n.y)}static distance(e,t){return Math.hypot(e.x-t.x,e.y-t.y)}static equals(e,t){return e.x===t.x&&e.y===t.y}static from({x:t,y:n}){return new e(t,n)}},Rc=class e{constructor(e,t,n,r){this.left=e,this.top=t,this.width=n,this.height=r,this.scale={x:1,y:1}}get inverseScale(){return{x:1/this.scale.x,y:1/this.scale.y}}translate(t,n){let{top:r,left:i,width:a,height:o,scale:s}=this,c=new e(i+t,r+n,a,o);return c.scale=Sc({},s),c}get boundingRectangle(){let{width:e,height:t,left:n,top:r,right:i,bottom:a}=this;return{width:e,height:t,left:n,top:r,right:i,bottom:a}}get center(){let{left:e,top:t,right:n,bottom:r}=this;return new Lc((e+n)/2,(t+r)/2)}get area(){let{width:e,height:t}=this;return e*t}equals(t){if(!(t instanceof e))return!1;let{left:n,top:r,width:i,height:a}=this;return n===t.left&&r===t.top&&i===t.width&&a===t.height}containsPoint(e){let{top:t,left:n,bottom:r,right:i}=this;return t<=e.y&&e.y<=r&&n<=e.x&&e.x<=i}intersectionArea(t){return t instanceof e?zc(this,t):0}intersectionRatio(e){let{area:t}=this,n=this.intersectionArea(e);return n/(e.area+t-n)}get bottom(){let{top:e,height:t}=this;return e+t}get right(){let{left:e,width:t}=this;return e+t}get aspectRatio(){let{width:e,height:t}=this;return e/t}get corners(){return[{x:this.left,y:this.top},{x:this.right,y:this.top},{x:this.left,y:this.bottom},{x:this.right,y:this.bottom}]}static from({top:t,left:n,width:r,height:i}){return new e(n,t,r,i)}static delta(e,t,n={x:`center`,y:`center`}){let r=(e,t)=>{let r=n[t],i=t===`x`?e.left:e.top,a=t===`x`?e.width:e.height;return r==`start`?i:r==`end`?i+a:i+a/2};return Lc.delta({x:r(e,`x`),y:r(e,`y`)},{x:r(t,`x`),y:r(t,`y`)})}static intersectionRatio(t,n){return e.from(t).intersectionRatio(e.from(n))}};function zc(e,t){let n=Math.max(t.top,e.top),r=Math.max(t.left,e.left),i=Math.min(t.left+t.width,e.left+e.width),a=Math.min(t.top+t.height,e.top+e.height),o=i-r,s=a-n;return r<i&&n<a?o*s:0}var Bc,Vc,Hc,Uc,Wc,Gc=class extends (Hc=cc,Vc=[zs],Bc=[zs],Hc){constructor(e){let t=Lc.from(e);super(t,(e,t)=>Lc.equals(e,t)),kc(Wc,5,this),Pc(this,Uc,0),this.velocity={x:0,y:0}}get delta(){return Lc.delta(this.current,this.initial)}get direction(){let{current:e,previous:t}=this;if(!t)return null;let n={x:e.x-t.x,y:e.y-t.y};return!n.x&&!n.y?null:Math.abs(n.x)>Math.abs(n.y)?n.x>0?`right`:`left`:n.y>0?`down`:`up`}get current(){return super.current}set current(e){let{current:t}=this,n=Lc.from(e),r={x:n.x-t.x,y:n.y-t.y},i=Date.now(),a=i-Nc(this,Uc),o=e=>Math.round(e/a*100);Ko(()=>{Fc(this,Uc,i),this.velocity={x:o(r.x),y:o(r.y)},super.current=n})}reset(e=this.defaultValue){super.reset(Lc.from(e)),this.velocity={x:0,y:0}}};Wc=wc(Hc),Uc=new WeakMap,Ac(Wc,2,`delta`,Vc,Gc),Ac(Wc,2,`direction`,Bc,Gc),Oc(Wc,Gc);function Kc({x:e,y:t},n){let r=Math.abs(e),i=Math.abs(t);return typeof n==`number`?Math.sqrt(bc(r,2)+bc(i,2))>n:`x`in n&&`y`in n?r>n.x&&i>n.y:`x`in n?r>n.x:`y`in n?i>n.y:!1}var qc=(e=>(e.Horizontal=`x`,e.Vertical=`y`,e))(qc||{}),Jc=Object.values(qc),Yc=Object.create,Xc=Object.defineProperty,Zc=Object.defineProperties,Qc=Object.getOwnPropertyDescriptor,$c=Object.getOwnPropertyDescriptors,el=Object.getOwnPropertySymbols,tl=Object.prototype.hasOwnProperty,nl=Object.prototype.propertyIsEnumerable,rl=(e,t)=>(t=Symbol[e])?t:Symbol.for(`Symbol.`+e),il=e=>{throw TypeError(e)},al=(e,t,n)=>t in e?Xc(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,ol=(e,t)=>{for(var n in t||={})tl.call(t,n)&&al(e,n,t[n]);if(el)for(var n of el(t))nl.call(t,n)&&al(e,n,t[n]);return e},sl=(e,t)=>Zc(e,$c(t)),cl=(e,t)=>Xc(e,`name`,{value:t,configurable:!0}),ll=(e,t)=>{var n={};for(var r in e)tl.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(e!=null&&el)for(var r of el(e))t.indexOf(r)<0&&nl.call(e,r)&&(n[r]=e[r]);return n},ul=e=>[,,,Yc(e?.[rl(`metadata`)]??null)],dl=[`class`,`method`,`getter`,`setter`,`accessor`,`field`,`value`,`get`,`set`],fl=e=>e!==void 0&&typeof e!=`function`?il(`Function expected`):e,pl=(e,t,n,r,i)=>({kind:dl[e],name:t,metadata:r,addInitializer:e=>n._?il(`Already initialized`):i.push(fl(e||null))}),ml=(e,t)=>al(t,rl(`metadata`),e[3]),Y=(e,t,n,r)=>{for(var i=0,a=e[t>>1],o=a&&a.length;i<o;i++)t&1?a[i].call(n):r=a[i].call(n,r);return r},X=(e,t,n,r,i,a)=>{var o,s,c,l,u,d=t&7,f=!!(t&8),p=!!(t&16),m=d>3?e.length+1:d?f?1:2:0,h=dl[d+5],g=d>3&&(e[m-1]=[]),_=e[m]||(e[m]=[]),v=d&&(!p&&!f&&(i=i.prototype),d<5&&(d>3||!p)&&Qc(d<4?i:{get[n](){return _l(this,a)},set[n](e){return vl(this,a,e)}},n));d?p&&d<4&&cl(a,(d>2?`set `:d>1?`get `:``)+n):cl(i,n);for(var y=r.length-1;y>=0;y--)l=pl(d,n,c={},e[3],_),d&&(l.static=f,l.private=p,u=l.access={has:p?e=>gl(i,e):e=>n in e},d^3&&(u.get=p?e=>(d^1?_l:yl)(e,i,d^4?a:v.get):e=>e[n]),d>2&&(u.set=p?(e,t)=>vl(e,i,t,d^4?a:v.set):(e,t)=>e[n]=t)),s=(0,r[y])(d?d<4?p?a:v[h]:d>4?void 0:{get:v.get,set:v.set}:i,l),c._=1,d^4||s===void 0?fl(s)&&(d>4?g.unshift(s):d?p?a=s:v[h]=s:i=s):typeof s!=`object`||!s?il(`Object expected`):(fl(o=s.get)&&(v.get=o),fl(o=s.set)&&(v.set=o),fl(o=s.init)&&g.unshift(o));return d||ml(e,i),v&&Xc(i,n,v),p?d^4?a:v:i},hl=(e,t,n)=>t.has(e)||il(`Cannot `+n),gl=(e,t)=>Object(t)===t?e.has(t):il(`Cannot use the \"in\" operator on this value`),_l=(e,t,n)=>(hl(e,t,`read from private field`),n?n.call(e):t.get(e)),Z=(e,t,n)=>t.has(e)?il(`Cannot add the same private member more than once`):t instanceof WeakSet?t.add(e):t.set(e,n),vl=(e,t,n,r)=>(hl(e,t,`write to private field`),r?r.call(e,n):t.set(e,n),n),yl=(e,t,n)=>(hl(e,t,`access private method`),n);function bl(e,t){return{plugin:e,options:t}}function xl(e){return t=>bl(e,t)}function Sl(e){return typeof e==`function`?{plugin:e,options:void 0}:e}var Cl=[J],wl,Tl,El,Dl=class{constructor(e,t){this.manager=e,this.options=t,Z(this,Tl,Y(wl,8,this,!1)),Y(wl,11,this),Z(this,El,new Set)}enable(){this.disabled=!1}disable(){this.disabled=!0}isDisabled(){return q(()=>this.disabled)}configure(e){this.options=e}registerEffect(e){let t=ls(e.bind(this));return _l(this,El).add(t),t}destroy(){_l(this,El).forEach(e=>e())}static configure(e){return bl(this,e)}};wl=ul(null),Tl=new WeakMap,El=new WeakMap,X(wl,4,`disabled`,Cl,Dl,Tl),ml(wl,Dl);var Ol=class extends Dl{},kl,Al=class{constructor(e){this.manager=e,this.instances=new Map,Z(this,kl,[])}get values(){return Array.from(this.instances.values())}set values(e){let t=e.map(Sl).reduceRight((e,t)=>e.some(({plugin:e})=>e===t.plugin)?e:[t,...e],[]),n=t.map(({plugin:e})=>e);for(let e of _l(this,kl))if(!n.includes(e)){if(e.prototype instanceof Ol)continue;this.unregister(e)}for(let{plugin:e,options:n}of t)this.register(e,n);vl(this,kl,n)}get(e){return this.instances.get(e)}register(e,t){let n=this.instances.get(e);if(n)return n.options!==t&&(n.options=t),n;let r=new e(this.manager,t);return this.instances.set(e,r),r}unregister(e){let t=this.instances.get(e);t&&(t.destroy(),this.instances.delete(e))}destroy(){for(let e of this.instances.values())e.destroy();this.instances.clear()}};kl=new WeakMap;function jl(e,t){return e.priority===t.priority?e.type===t.type?t.value-e.value:t.type-e.type:t.priority-e.priority}var Ml=[],Nl,Pl,Fl=class extends Dl{constructor(e){super(e),Z(this,Nl),Z(this,Pl),this.computeCollisions=this.computeCollisions.bind(this),vl(this,Pl,$o(Ml)),this.destroy=Vs(()=>{let e=this.computeCollisions(),t=q(()=>this.manager.dragOperation.position.current);if(e!==Ml){let e=_l(this,Nl);if(vl(this,Nl,t),e&&t.x==e.x&&t.y==e.y)return}else vl(this,Nl,void 0);_l(this,Pl).value=e},()=>{let{dragOperation:e}=this.manager;e.status.initialized&&this.forceUpdate()})}forceUpdate(e=!0){q(()=>{e?_l(this,Pl).value=this.computeCollisions():vl(this,Nl,void 0)})}computeCollisions(e,t){let{registry:n,dragOperation:r}=this.manager,{source:i,shape:a,status:o}=r;if(!o.initialized||!a)return Ml;let s=[],c=[];for(let a of e??n.droppables){if(a.disabled||i&&!a.accepts(i))continue;let e=t??a.collisionDetector;if(!e)continue;c.push(a),a.shape;let n=q(()=>e({droppable:a,dragOperation:r}));n&&(a.collisionPriority!=null&&(n.priority=a.collisionPriority),s.push(n))}return c.length===0?Ml:(s.sort(jl),s)}get collisions(){return _l(this,Pl).value}};Nl=new WeakMap,Pl=new WeakMap;var Il=class{constructor(){this.registry=new Map}addEventListener(e,t){let{registry:n}=this,r=new Set(n.get(e));return r.add(t),n.set(e,r),()=>this.removeEventListener(e,t)}removeEventListener(e,t){let{registry:n}=this,r=new Set(n.get(e));r.delete(t),n.set(e,r)}dispatch(e,...t){let{registry:n}=this,r=n.get(e);if(r)for(let e of r)e(...t)}},Ll=class extends Il{constructor(e){super(),this.manager=e}dispatch(e,t){let n=[t,this.manager];super.dispatch(e,...n)}};function Rl(e,t=!0){let n=!1;return sl(ol({},e),{cancelable:t,get defaultPrevented(){return n},preventDefault(){t&&(n=!0)}})}var zl=class extends Ol{constructor(e){super(e);let t=(e,t)=>e.map(({id:e})=>e).join(``)===t.map(({id:e})=>e).join(``),n=[];this.destroy=Vs(()=>{let{dragOperation:t,collisionObserver:r}=e;t.status.initializing&&(n=[],r.enable())},()=>{let{collisionObserver:r,monitor:i}=e,{collisions:a}=r;if(r.isDisabled())return;let o=Rl({collisions:a});if(i.dispatch(`collision`,o),o.defaultPrevented||t(a,n))return;n=a;let[s]=a;q(()=>{s?.id!==e.dragOperation.target?.id&&(r.disable(),e.actions.setDropTarget(s?.id).then(()=>{r.enable()}))})})}},Bl=(e=>(e[e.Lowest=0]=`Lowest`,e[e.Low=1]=`Low`,e[e.Normal=2]=`Normal`,e[e.High=3]=`High`,e[e.Highest=4]=`Highest`,e))(Bl||{}),Vl=(e=>(e[e.Collision=0]=`Collision`,e[e.ShapeIntersection=1]=`ShapeIntersection`,e[e.PointerIntersection=2]=`PointerIntersection`,e))(Vl||{}),Hl,Ul,Wl,Gl,Kl,ql,Jl=[J],Yl,Xl;ql=[zs],Kl=[zs],Gl=[zs],Wl=[zs],Ul=[zs],Hl=[zs];var Zl=class{constructor(){Y(Yl,5,this),Z(this,Xl,Y(Yl,8,this,`idle`)),Y(Yl,11,this)}get current(){return this.value}get idle(){return this.value===`idle`}get initializing(){return this.value===`initializing`}get initialized(){let{value:e}=this;return e!==`idle`&&e!==`initialization-pending`}get dragging(){return this.value===`dragging`}get dropped(){return this.value===`dropped`}set(e){this.value=e}};Yl=ul(null),Xl=new WeakMap,X(Yl,4,`value`,Jl,Zl,Xl),X(Yl,2,`current`,ql,Zl),X(Yl,2,`idle`,Kl,Zl),X(Yl,2,`initializing`,Gl,Zl),X(Yl,2,`initialized`,Wl,Zl),X(Yl,2,`dragging`,Ul,Zl),X(Yl,2,`dropped`,Hl,Zl),ml(Yl,Zl);var Ql=class{constructor(e){this.manager=e}setDragSource(e){let{dragOperation:t}=this.manager;t.sourceIdentifier=typeof e==`string`||typeof e==`number`?e:e.id}setDropTarget(e){return q(()=>{let{dragOperation:t}=this.manager,n=e??null;if(t.targetIdentifier===n)return Promise.resolve(!1);t.targetIdentifier=n;let r=Rl({operation:t.snapshot()});return t.status.dragging&&this.manager.monitor.dispatch(`dragover`,r),this.manager.renderer.rendering.then(()=>r.defaultPrevented)})}start(e){return q(()=>{let{dragOperation:t}=this.manager;if(e.source!=null&&this.setDragSource(e.source),!t.source)throw Error(`Cannot start a drag operation without a drag source`);if(!t.status.idle)throw Error(`Cannot start a drag operation while another is active`);let n=new AbortController,{event:r,coordinates:i}=e;Ko(()=>{t.status.set(`initialization-pending`),t.shape=null,t.canceled=!1,t.activatorEvent=r??null,t.position.reset(i)});let a=Rl({operation:t.snapshot()});return this.manager.monitor.dispatch(`beforedragstart`,a),a.defaultPrevented?(t.reset(),n.abort(),n):(t.status.set(`initializing`),t.controller=n,this.manager.renderer.rendering.then(()=>{if(n.signal.aborted)return;let{status:e}=t;e.current===`initializing`&&(t.status.set(`dragging`),this.manager.monitor.dispatch(`dragstart`,{nativeEvent:r,operation:t.snapshot(),cancelable:!1}))}),n)})}move(e){return q(()=>{let{dragOperation:t}=this.manager,{status:n,controller:r}=t;if(!n.dragging||!r||r.signal.aborted)return;let i=Rl({nativeEvent:e.event,operation:t.snapshot(),by:e.by,to:e.to},e.cancelable??!0);(e.propagate??!0)&&this.manager.monitor.dispatch(`dragmove`,i),queueMicrotask(()=>{if(i.defaultPrevented)return;let n=e.to??{x:t.position.current.x+(e.by?.x??0),y:t.position.current.y+(e.by?.y??0)};t.position.current=n})})}stop(e={}){return q(()=>{let{dragOperation:t}=this.manager,{controller:n}=t;if(!n||n.signal.aborted)return;let r,i=()=>{let e={resume:()=>{},abort:()=>{}};return r=new Promise((t,n)=>{e.resume=t,e.abort=n}),e};n.abort();let a=()=>{this.manager.renderer.rendering.then(()=>{t.status.set(`dropped`);let e=q(()=>t.source?.status===`dropping`),r=()=>{t.controller===n&&(t.controller=void 0),t.reset()};if(e){let{source:e}=t,n=ls(()=>{e?.status===`idle`&&(n(),r())})}else this.manager.renderer.rendering.then(r)})};t.canceled=e.canceled??!1,this.manager.monitor.dispatch(`dragend`,{nativeEvent:e.event,operation:t.snapshot(),canceled:e.canceled??!1,suspend:i}),r?r.then(a).catch(()=>t.reset()):a()})}},$l,eu,tu,nu=[J],ru,iu,au,ou,su;tu=[J],eu=[J],$l=[J];var cu=class{constructor(e,t){Z(this,iu,Y(ru,8,this)),Y(ru,11,this),Z(this,au,Y(ru,12,this)),Y(ru,15,this),Z(this,ou,Y(ru,16,this)),Y(ru,19,this),Z(this,su,Y(ru,20,this)),Y(ru,23,this);let{effects:n,id:r,data:i={},disabled:a=!1,register:o=!0}=e,s=r;this.manager=t,this.id=r,this.data=i,this.disabled=a,this.effects=()=>[()=>{let{id:e,manager:t}=this;if(e!==s)return t?.registry.register(this),()=>t?.registry.unregister(this)},...n?.()??[]],this.register=this.register.bind(this),this.unregister=this.unregister.bind(this),this.destroy=this.destroy.bind(this),t&&o&&queueMicrotask(this.register)}register(){return this.manager?.registry.register(this)}unregister(){var e;(e=this.manager)==null||e.registry.unregister(this)}destroy(){var e;(e=this.manager)==null||e.registry.unregister(this)}};ru=ul(null),iu=new WeakMap,au=new WeakMap,ou=new WeakMap,su=new WeakMap,X(ru,4,`manager`,nu,cu,iu),X(ru,4,`id`,tu,cu,au),X(ru,4,`data`,eu,cu,ou),X(ru,4,`disabled`,$l,cu,su),ml(ru,cu);var lu=class{constructor(){this.map=$o(new Map),this.cleanupFunctions=new WeakMap,this.register=(e,t)=>{let n=this.map.peek(),r=n.get(e),i=()=>this.unregister(e,t);if(r===t)return i;r&&(this.cleanupFunctions.get(r)?.(),this.cleanupFunctions.delete(r));let a=new Map(n);a.set(e,t),this.map.value=a;let o=Vs(...t.effects());return this.cleanupFunctions.set(t,o),i},this.unregister=(e,t)=>{let n=this.map.peek();if(n.get(e)!==t)return;this.cleanupFunctions.get(t)?.(),this.cleanupFunctions.delete(t);let r=new Map(n);r.delete(e),this.map.value=r}}[Symbol.iterator](){return this.map.peek().values()}get value(){return this.map.value.values()}has(e){return this.map.value.has(e)}get(e){return this.map.value.get(e)}destroy(){for(let e of this)this.cleanupFunctions.get(e)?.(),e.destroy();this.map.value=new Map}},uu,du,fu,pu,mu,hu,gu,_u,vu,yu,bu,xu=class extends (gu=cu,hu=[J],mu=[J],pu=[J],fu=[zs],du=[zs],uu=[zs],gu){constructor(e,t){var n=e,{modifiers:r,type:i,sensors:a}=n,o=ll(n,[`modifiers`,`type`,`sensors`]);super(o,t),Y(_u,5,this),Z(this,vu,Y(_u,8,this)),Y(_u,11,this),Z(this,yu,Y(_u,12,this)),Y(_u,15,this),Z(this,bu,Y(_u,16,this,this.isDragSource?`dragging`:`idle`)),Y(_u,19,this),this.type=i,this.sensors=a,this.modifiers=r,this.alignment=o.alignment}get isDropping(){return this.status===`dropping`&&this.isDragSource}get isDragging(){return this.status===`dragging`&&this.isDragSource}get isDragSource(){return this.manager?.dragOperation.source?.id===this.id}};_u=ul(gu),vu=new WeakMap,yu=new WeakMap,bu=new WeakMap,X(_u,4,`type`,hu,xu,vu),X(_u,4,`modifiers`,mu,xu,yu),X(_u,4,`status`,pu,xu,bu),X(_u,2,`isDropping`,fu,xu),X(_u,2,`isDragging`,du,xu),X(_u,2,`isDragSource`,uu,xu),ml(_u,xu);var Su,Cu,wu,Tu,Eu,Du,Ou,ku,Au,ju,Mu,Nu,Pu,Fu=class extends (Ou=cu,Du=[J],Eu=[J],Tu=[J],wu=[J],Cu=[J],Su=[zs],Ou){constructor(e,t){var n=e,{accept:r,collisionDetector:i,collisionPriority:a,type:o}=n,s=ll(n,[`accept`,`collisionDetector`,`collisionPriority`,`type`]);super(s,t),Y(ku,5,this),Z(this,Au,Y(ku,8,this)),Y(ku,11,this),Z(this,ju,Y(ku,12,this)),Y(ku,15,this),Z(this,Mu,Y(ku,16,this)),Y(ku,19,this),Z(this,Nu,Y(ku,20,this)),Y(ku,23,this),Z(this,Pu,Y(ku,24,this)),Y(ku,27,this),this.accept=r,this.collisionDetector=i,this.collisionPriority=a,this.type=o}accepts(e){let{accept:t}=this;return t?typeof t==`function`?t(e):e.type?Array.isArray(t)?t.includes(e.type):e.type===t:!1:!0}get isDropTarget(){return this.manager?.dragOperation.target?.id===this.id}};ku=ul(Ou),Au=new WeakMap,ju=new WeakMap,Mu=new WeakMap,Nu=new WeakMap,Pu=new WeakMap,X(ku,4,`accept`,Du,Fu,Au),X(ku,4,`type`,Eu,Fu,ju),X(ku,4,`collisionDetector`,Tu,Fu,Mu),X(ku,4,`collisionPriority`,wu,Fu,Nu),X(ku,4,`shape`,Cu,Fu,Pu),X(ku,2,`isDropTarget`,Su,Fu),ml(ku,Fu);var Iu=class extends Dl{constructor(e,t){super(e,t),this.manager=e,this.options=t}},Lu=class extends AbortController{constructor(e,t){super(),this.constraints=e,this.onActivate=t,this.activated=!1;for(let t of e??[])t.controller=this}onEvent(e){if(!this.activated)if(this.constraints?.length)for(let t of this.constraints)t.onEvent(e);else this.activate(e)}activate(e){this.activated||(this.activated=!0,this.onActivate(e))}abort(e){this.activated=!1,super.abort(e)}},Ru,zu=class{constructor(e){this.options=e,Z(this,Ru)}set controller(e){vl(this,Ru,e),e.signal.addEventListener(`abort`,()=>this.abort())}activate(e){var t;(t=_l(this,Ru))==null||t.activate(e)}};Ru=new WeakMap;var Bu=class extends Dl{constructor(e,t){super(e,t),this.manager=e,this.options=t}apply(e){return e.transform}},Vu=class{constructor(e){this.draggables=new lu,this.droppables=new lu,this.plugins=new Al(e),this.sensors=new Al(e),this.modifiers=new Al(e)}register(e,t){if(e instanceof xu)return this.draggables.register(e.id,e);if(e instanceof Fu)return this.droppables.register(e.id,e);if(e.prototype instanceof Bu)return this.modifiers.register(e,t);if(e.prototype instanceof Iu)return this.sensors.register(e,t);if(e.prototype instanceof Dl)return this.plugins.register(e,t);throw Error(`Invalid instance type`)}unregister(e){if(e instanceof cu)return e instanceof xu?this.draggables.unregister(e.id,e):e instanceof Fu?this.droppables.unregister(e.id,e):()=>{};if(e.prototype instanceof Bu)return this.modifiers.unregister(e);if(e.prototype instanceof Iu)return this.sensors.unregister(e);if(e.prototype instanceof Dl)return this.plugins.unregister(e);throw Error(`Invalid instance type`)}destroy(){this.draggables.destroy(),this.droppables.destroy(),this.plugins.destroy(),this.sensors.destroy(),this.modifiers.destroy()}},Hu,Uu,Wu,Gu,Ku,qu,Ju,Yu,Xu=[zs],Zu,Qu,$u,ed,td,nd,rd,id,ad,od;Yu=[J],Ju=[J],qu=[J],Ku=[J],Gu=[J],Wu=[zs],Uu=[zs],Hu=[zs];var sd=class{constructor(e){Y(ed,5,this),Z(this,Zu),Z(this,Qu),Z(this,$u,new cc(void 0,(e,t)=>e&&t?e.equals(t):e===t)),this.status=new Zl,Z(this,td,Y(ed,8,this,!1)),Y(ed,11,this),Z(this,nd,Y(ed,12,this,null)),Y(ed,15,this),Z(this,rd,Y(ed,16,this,null)),Y(ed,19,this),Z(this,id,Y(ed,20,this,null)),Y(ed,23,this),Z(this,ad,Y(ed,24,this,[])),Y(ed,27,this),this.position=new Gc({x:0,y:0}),Z(this,od,{x:0,y:0}),vl(this,Zu,e)}get shape(){let{current:e,initial:t,previous:n}=_l(this,$u);return!e||!t?null:{current:e,initial:t,previous:n}}set shape(e){e?_l(this,$u).current=e:_l(this,$u).reset()}get source(){let e=this.sourceIdentifier;if(e==null)return null;let t=_l(this,Zu).registry.draggables.get(e);return t&&vl(this,Qu,t),t??_l(this,Qu)??null}get target(){let e=this.targetIdentifier;return e==null?null:_l(this,Zu).registry.droppables.get(e)??null}get transform(){let{x:e,y:t}=this.position.delta,n={x:e,y:t};for(let e of this.modifiers)n=e.apply(sl(ol({},this.snapshot()),{transform:n}));return vl(this,od,n),n}snapshot(){return q(()=>({source:this.source,target:this.target,activatorEvent:this.activatorEvent,transform:_l(this,od),shape:this.shape?lc(this.shape):null,position:lc(this.position),status:lc(this.status),canceled:this.canceled}))}reset(){Ko(()=>{this.status.set(`idle`),this.sourceIdentifier=null,this.targetIdentifier=null,_l(this,$u).reset(),this.position.reset({x:0,y:0}),vl(this,od,{x:0,y:0}),this.modifiers=[]})}};ed=ul(null),Zu=new WeakMap,Qu=new WeakMap,$u=new WeakMap,td=new WeakMap,nd=new WeakMap,rd=new WeakMap,id=new WeakMap,ad=new WeakMap,od=new WeakMap,X(ed,2,`shape`,Xu,sd),X(ed,4,`canceled`,Yu,sd,td),X(ed,4,`activatorEvent`,Ju,sd,nd),X(ed,4,`sourceIdentifier`,qu,sd,rd),X(ed,4,`targetIdentifier`,Ku,sd,id),X(ed,4,`modifiers`,Gu,sd,ad),X(ed,2,`source`,Wu,sd),X(ed,2,`target`,Uu,sd),X(ed,2,`transform`,Hu,sd),ml(ed,sd);var cd={get rendering(){return Promise.resolve()}},ld=class{constructor(e){this.destroy=()=>{this.dragOperation.status.idle||this.actions.stop({canceled:!0}),this.dragOperation.modifiers.forEach(e=>e.destroy()),this.registry.destroy(),this.collisionObserver.destroy()};let{plugins:t=[],sensors:n=[],modifiers:r=[],renderer:i=cd}=e??{},a=new Ll(this);this.registry=new Vu(this),this.monitor=a,this.renderer=i,this.actions=new Ql(this),this.dragOperation=new sd(this),this.collisionObserver=new Fl(this),this.plugins=[zl,...t],this.modifiers=r,this.sensors=n;let{destroy:o}=this,s=Vs(()=>{let e=q(()=>this.dragOperation.modifiers),t=this.modifiers;e!==t&&e.forEach(e=>e.destroy()),this.dragOperation.modifiers=(this.dragOperation.source?.modifiers)?.map(e=>{let{plugin:t,options:n}=Sl(e);return new t(this,n)})??t});this.destroy=()=>{s(),o()}}get plugins(){return this.registry.plugins.values}set plugins(e){this.registry.plugins.values=e}get modifiers(){return this.registry.modifiers.values}set modifiers(e){this.registry.modifiers.values=e}get sensors(){return this.registry.sensors.values}set sensors(e){this.registry.sensors.values=e}},ud=e=>{throw TypeError(e)},dd=(e,t,n)=>t.has(e)||ud(`Cannot `+n),Q=(e,t,n)=>(dd(e,t,`read from private field`),t.get(e)),fd=(e,t,n)=>t.has(e)?ud(`Cannot add the same private member more than once`):t instanceof WeakSet?t.add(e):t.set(e,n),pd=(e,t,n,r)=>(dd(e,t,`write to private field`),t.set(e,n),n),md=(e,t,n)=>(dd(e,t,`access private method`),n);function hd(e){return e?e instanceof KeyframeEffect?!0:`getKeyframes`in e&&typeof e.getKeyframes==`function`:!1}function gd(e,t){let n=e.getAnimations();if(n.length>0)for(let e of n){if(e.playState!==`running`)continue;let{effect:n}=e,r=(hd(n)?n.getKeyframes():[]).filter(t);if(r.length>0)return[r[r.length-1],e]}return null}function _d(e){let{width:t,height:n,top:r,left:i,bottom:a,right:o}=e.getBoundingClientRect();return{width:t,height:n,top:r,left:i,bottom:a,right:o}}function vd(e){let t=Object.prototype.toString.call(e);return t===`[object Window]`||t===`[object global]`}function yd(e){return`nodeType`in e}function bd(e){return e?vd(e)?e:yd(e)?`defaultView`in e?e.defaultView??window:e.ownerDocument?.defaultView??window:window:window}function xd(e){let{Document:t}=bd(e);return e instanceof t||`nodeType`in e&&e.nodeType===Node.DOCUMENT_NODE}function Sd(e){return!e||vd(e)?!1:e instanceof bd(e).HTMLElement||`namespaceURI`in e&&typeof e.namespaceURI==`string`&&e.namespaceURI.endsWith(`html`)}function Cd(e){return e instanceof bd(e).SVGElement||`namespaceURI`in e&&typeof e.namespaceURI==`string`&&e.namespaceURI.endsWith(`svg`)}function wd(e){return e?vd(e)?e.document:yd(e)?xd(e)?e:Sd(e)||Cd(e)?e.ownerDocument:document:document:document}function Td(e){let{documentElement:t}=wd(e),n=t.clientWidth,r=t.clientHeight;return{top:0,left:0,right:n,bottom:r,width:n,height:r}}function Ed(e,t){if(Dd(e)&&e.open===!1)return!1;let{overflow:n,overflowX:r,overflowY:i}=getComputedStyle(e);return n===`visible`&&r===`visible`&&i===`visible`}function Dd(e){return e.tagName===`DETAILS`}function Od(e,t=e.getBoundingClientRect(),n=0){let r=t,{ownerDocument:i}=e,a=i.defaultView??window,o=e.parentElement;for(;o&&o!==i.documentElement;){if(!Ed(o)){let e=o.getBoundingClientRect(),t=n*(e.bottom-e.top),i=n*(e.right-e.left),a=n*(e.bottom-e.top),s=n*(e.right-e.left);r={top:Math.max(r.top,e.top-t),right:Math.min(r.right,e.right+i),bottom:Math.min(r.bottom,e.bottom+a),left:Math.max(r.left,e.left-s),width:0,height:0},r.width=r.right-r.left,r.height=r.bottom-r.top}o=o.parentElement}let s=a.innerWidth,c=a.innerHeight,l=n*c,u=n*s;return r={top:Math.max(r.top,0-l),right:Math.min(r.right,s+u),bottom:Math.min(r.bottom,c+l),left:Math.max(r.left,0-u),width:0,height:0},r.width=r.right-r.left,r.height=r.bottom-r.top,r.width<0&&(r.width=0),r.height<0&&(r.height=0),r}function kd(e){return{x:e.clientX,y:e.clientY}}var Ad=typeof window<`u`&&window.document!==void 0&&window.document.createElement!==void 0;function jd(e=document,t=new Set){if(t.has(e))return[];t.add(e);let n=[e];for(let r of Array.from(e.querySelectorAll(`iframe, frame`)))try{let e=r.contentDocument;e&&!t.has(e)&&n.push(...jd(e,t))}catch{}try{let r=e.defaultView;if(r&&r!==window.top){let i=r.parent;i&&i.document&&i.document!==e&&n.push(...jd(i.document,t))}}catch{}return n}function Md(){return/^((?!chrome|android).)*safari/i.test(navigator.userAgent)}function Nd(e){let t=`input, textarea, select, canvas, [contenteditable]`,n=e.cloneNode(!0),r=Array.from(e.querySelectorAll(t));return Array.from(n.querySelectorAll(t)).forEach((e,t)=>{let n=r[t];Pd(e)&&Pd(n)&&(e.type!==`file`&&(e.value=n.value),e.type===`radio`&&e.name&&(e.name=`Cloned__${e.name}`)),Fd(e)&&Fd(n)&&n.width>0&&n.height>0&&e.getContext(`2d`)?.drawImage(n,0,0)}),n}function Pd(e){return`value`in e}function Fd(e){return e.tagName===`CANVAS`}function Id(e,{x:t,y:n}){let r=e.elementFromPoint(t,n);if(Ld(r)){let{contentDocument:e}=r;if(e){let{left:i,top:a}=r.getBoundingClientRect();return Id(e,{x:t-i,y:n-a})}}return r}function Ld(e){return e?.tagName===`IFRAME`}var Rd=new WeakMap;function zd(e){return!!e.closest(`\n      input:not([disabled]),\n      select:not([disabled]),\n      textarea:not([disabled]),\n      button:not([disabled]),\n      a[href],\n      [contenteditable]:not([contenteditable=\"false\"])\n    `)}var Bd=class{constructor(){this.entries=new Set,this.clear=()=>{for(let e of this.entries){let[t,{type:n,listener:r,options:i}]=e;t.removeEventListener(n,r,i)}this.entries.clear()}}bind(e,t){let n=Array.isArray(e)?e:[e],r=Array.isArray(t)?t:[t],i=[];for(let e of n)for(let t of r){let{type:n,listener:r,options:a}=t,o=[e,t];e.addEventListener(n,r,a),this.entries.add(o),i.push(o)}return function(){for(let[e,{type:t,listener:n,options:r}]of i)e.removeEventListener(t,n,r)}}};function Vd(e){let t=e?.ownerDocument.defaultView;if(t&&t.self!==t.parent)return t.frameElement}function Hd(e){let t=new Set,n=Vd(e);for(;n;)t.add(n),n=Vd(n);return t}function Ud(e,t){let n=setTimeout(e,t);return()=>clearTimeout(n)}function Wd(e,t){let n=()=>performance.now(),r,i;return function(...a){let o=this;i?(r?.(),r=Ud(()=>{e.apply(o,a),i=n()},t-(n()-i))):(e.apply(o,a),i=n())}}function Gd(e,t){return e===t?!0:!e||!t?!1:e.top==t.top&&e.left==t.left&&e.right==t.right&&e.bottom==t.bottom}function Kd(e,t=e.getBoundingClientRect()){let{width:n,height:r}=Od(e,t);return n>0&&r>0}var qd=Ad?ResizeObserver:class{observe(){}unobserve(){}disconnect(){}},Jd,Yd=class extends qd{constructor(e){super(t=>{if(!Q(this,Jd)){pd(this,Jd,!0);return}e(t,this)}),fd(this,Jd,!1)}};Jd=new WeakMap;var Xd=Array.from({length:100},(e,t)=>t/100),Zd=75,Qd,$d,ef,tf,nf,rf,af,of,sf,cf,lf,uf=class{constructor(e,t,n={debug:!1,skipInitial:!1}){this.element=e,this.callback=t,fd(this,sf),this.disconnect=()=>{var e,t,n;pd(this,af,!0),(e=Q(this,ef))==null||e.disconnect(),(t=Q(this,tf))==null||t.disconnect(),Q(this,nf).disconnect(),(n=Q(this,rf))==null||n.remove()},fd(this,Qd,!0),fd(this,$d),fd(this,ef),fd(this,tf),fd(this,nf),fd(this,rf),fd(this,af,!1),fd(this,of,Wd(()=>{var e;let{element:t}=this;if((e=Q(this,tf))==null||e.disconnect(),Q(this,af)||!Q(this,Qd)||!t.isConnected)return;let n=t.ownerDocument??document,{innerHeight:r,innerWidth:i}=n.defaultView??window,a=t.getBoundingClientRect(),{top:o,left:s,bottom:c,right:l}=Od(t,a),u=-Math.floor(o),d=-Math.floor(s),f=`${u}px ${-Math.floor(i-l)}px ${-Math.floor(r-c)}px ${d}px`;this.boundingClientRect=a,pd(this,tf,new IntersectionObserver(e=>{let[n]=e,{intersectionRect:r}=n;(n.intersectionRatio===1?Rc.intersectionRatio(r,Od(t)):n.intersectionRatio)!==1&&Q(this,of).call(this)},{threshold:Xd,rootMargin:f,root:n})),Q(this,tf).observe(t),md(this,sf,cf).call(this)},Zd)),this.boundingClientRect=e.getBoundingClientRect(),pd(this,Qd,Kd(e,this.boundingClientRect));let r=!0;this.callback=e=>{r&&(r=!1,n.skipInitial)||t(e)};let i=e.ownerDocument;n?.debug&&(pd(this,rf,document.createElement(`div`)),Q(this,rf).style.background=`rgba(0,0,0,0.15)`,Q(this,rf).style.position=`fixed`,Q(this,rf).style.pointerEvents=`none`,i.body.appendChild(Q(this,rf))),pd(this,nf,new IntersectionObserver(t=>{var n,r;let{boundingClientRect:i,isIntersecting:a}=t[t.length-1],{width:o,height:s}=i,c=Q(this,Qd);pd(this,Qd,a),!(!o&&!s)&&(c&&!a?((n=Q(this,tf))==null||n.disconnect(),this.callback(null),(r=Q(this,ef))==null||r.disconnect(),pd(this,ef,void 0),Q(this,rf)&&(Q(this,rf).style.visibility=`hidden`)):Q(this,of).call(this),a&&!Q(this,ef)&&(pd(this,ef,new Yd(Q(this,of))),Q(this,ef).observe(e)))},{threshold:Xd,root:i})),Q(this,Qd)&&!n.skipInitial&&this.callback(this.boundingClientRect),Q(this,nf).observe(e)}};Qd=new WeakMap,$d=new WeakMap,ef=new WeakMap,tf=new WeakMap,nf=new WeakMap,rf=new WeakMap,af=new WeakMap,of=new WeakMap,sf=new WeakSet,cf=function(){Q(this,af)||(md(this,sf,lf).call(this),!Gd(this.boundingClientRect,Q(this,$d))&&(this.callback(this.boundingClientRect),pd(this,$d,this.boundingClientRect)))},lf=function(){if(Q(this,rf)){let{top:e,left:t,width:n,height:r}=Od(this.element);Q(this,rf).style.overflow=`hidden`,Q(this,rf).style.visibility=`visible`,Q(this,rf).style.top=`${Math.floor(e)}px`,Q(this,rf).style.left=`${Math.floor(t)}px`,Q(this,rf).style.width=`${Math.floor(n)}px`,Q(this,rf).style.height=`${Math.floor(r)}px`}};var df=new WeakMap,ff=new WeakMap;function pf(e,t){let n=df.get(e);return n||={disconnect:new uf(e,t=>{let n=df.get(e);n&&n.callbacks.forEach(e=>e(t))},{skipInitial:!0}).disconnect,callbacks:new Set},n.callbacks.add(t),df.set(e,n),()=>{n.callbacks.delete(t),n.callbacks.size===0&&(df.delete(e),n.disconnect())}}function mf(e,t){let n=new Set;for(let r of e){let e=pf(r,t);n.add(e)}return()=>n.forEach(e=>e())}function hf(e,t){let n=e.ownerDocument;if(!ff.has(n)){let e=new AbortController,t=new Set;document.addEventListener(`scroll`,e=>t.forEach(t=>t(e)),{capture:!0,passive:!0,signal:e.signal}),ff.set(n,{disconnect:()=>e.abort(),listeners:t})}let{listeners:r,disconnect:i}=ff.get(n)??{};return!r||!i?()=>{}:(r.add(t),()=>{r.delete(t),r.size===0&&(i(),ff.delete(n))})}var gf,_f,vf,yf,bf=class{constructor(e,t,n){this.callback=t,fd(this,gf),fd(this,_f,!1),fd(this,vf),fd(this,yf,Wd(e=>{if(!Q(this,_f)&&e.target&&`contains`in e.target&&typeof e.target.contains==`function`){for(let t of Q(this,vf))if(e.target.contains(t)){this.callback(Q(this,gf).boundingClientRect);break}}},Zd));let r=Hd(e),i=mf(r,t),a=hf(e,Q(this,yf));pd(this,vf,r),pd(this,gf,new uf(e,t,n)),this.disconnect=()=>{Q(this,_f)||(pd(this,_f,!0),i(),a(),Q(this,gf).disconnect())}}};gf=new WeakMap,_f=new WeakMap,vf=new WeakMap,yf=new WeakMap;function xf(e){return`showPopover`in e&&`hidePopover`in e&&typeof e.showPopover==`function`&&typeof e.hidePopover==`function`}function Sf(e){try{xf(e)&&e.isConnected&&e.hasAttribute(`popover`)&&!e.matches(`:popover-open`)&&e.showPopover()}catch{}}function Cf(e){return!Ad||!e?!1:e===wd(e).scrollingElement}function wf(e){let t=bd(e),n=Cf(e)?Td(e):_d(e),r=Cf(e)?{height:t.innerHeight,width:t.innerWidth}:{height:e.clientHeight,width:e.clientWidth},i={current:{x:e.scrollLeft,y:e.scrollTop},max:{x:e.scrollWidth-r.width,y:e.scrollHeight-r.height}};return{rect:n,position:i,isTop:i.current.y<=0,isLeft:i.current.x<=0,isBottom:i.current.y>=i.max.y,isRight:i.current.x>=i.max.x}}function Tf(e,t){let{isTop:n,isBottom:r,isLeft:i,isRight:a,position:o}=wf(e),{x:s,y:c}=t??{x:0,y:0},l=!n&&o.current.y+c>0,u=!r&&o.current.y+c<o.max.y,d=!i&&o.current.x+s>0,f=!a&&o.current.x+s<o.max.x;return{top:l,bottom:u,left:d,right:f,x:d||f,y:l||u}}var Ef=class{constructor(e){this.scheduler=e,this.pending=!1,this.tasks=new Set,this.resolvers=new Set,this.flush=()=>{let{tasks:e,resolvers:t}=this;this.pending=!1,this.tasks=new Set,this.resolvers=new Set;for(let t of e)t();for(let e of t)e()}}schedule(e){return this.tasks.add(e),this.pending||(this.pending=!0,this.scheduler(this.flush)),new Promise(e=>this.resolvers.add(e))}},Df=new Ef(e=>{typeof requestAnimationFrame==`function`?requestAnimationFrame(e):e()}),Of=new Ef(e=>setTimeout(e,50)),kf=new Map,Af=kf.clear.bind(kf);function jf(e,t=!1){if(!t)return Mf(e);let n=kf.get(e);return n||(n=Mf(e),kf.set(e,n),Of.schedule(Af),n)}function Mf(e){return bd(e).getComputedStyle(e)}function Nf(e,t=jf(e,!0)){return t.position===`fixed`||t.position===`sticky`}function Pf(e,t=jf(e,!0)){let n=/(auto|scroll|overlay)/;return[`overflow`,`overflowX`,`overflowY`].some(e=>{let r=t[e];return typeof r==`string`?n.test(r):!1})}var Ff={excludeElement:!0};function If(e,t=Ff){let{limit:n,excludeElement:r}=t,i=new Set;function a(t){if(n!=null&&i.size>=n||!t)return i;if(xd(t)&&t.scrollingElement!=null&&!i.has(t.scrollingElement))return i.add(t.scrollingElement),i;if(!Sd(t))return Cd(t)?a(t.parentElement):i;if(i.has(t))return i;let o=jf(t,!0);if(r&&t===e||Pf(t,o)&&i.add(t),Nf(t,o)){let{scrollingElement:e}=t.ownerDocument;return e&&i.add(e),i}return a(t.parentNode)}return e?a(e):i}function Lf(e){let[t]=If(e,{limit:1});return t??null}function Rf(e,t=window.frameElement){let n={x:0,y:0,scaleX:1,scaleY:1};if(!e)return n;let r=Vd(e);for(;r;){if(r===t)return n;let e=_d(r),{x:i,y:a}=zf(r,e);n.x+=e.left,n.y+=e.top,n.scaleX*=i,n.scaleY*=a,r=Vd(r)}return n}function zf(e,t=_d(e)){let n=Math.round(t.width),r=Math.round(t.height);if(Sd(e))return{x:n/e.offsetWidth,y:r/e.offsetHeight};let i=jf(e,!0);return{x:(parseFloat(i.width)||n)/n,y:(parseFloat(i.height)||r)/r}}function Bf(e){if(e===`none`)return null;let t=e.split(` `),n=parseFloat(t[0]),r=parseFloat(t[1]);return isNaN(n)&&isNaN(r)?null:{x:isNaN(n)?r:n,y:isNaN(r)?n:r}}function Vf(e){if(e===`none`)return null;let[t,n,r=`0`]=e.split(` `),i={x:parseFloat(t),y:parseFloat(n),z:parseInt(r,10)};return isNaN(i.x)&&isNaN(i.y)?null:{x:isNaN(i.x)?0:i.x,y:isNaN(i.y)?0:i.y,z:isNaN(i.z)?0:i.z}}function Hf(e){let{scale:t,transform:n,translate:r}=e,i=Bf(t),a=Vf(r),o=Uf(n);if(!o&&!i&&!a)return null;let s={x:i?.x??1,y:i?.y??1},c={x:a?.x??0,y:a?.y??0},l={x:o?.x??0,y:o?.y??0,scaleX:o?.scaleX??1,scaleY:o?.scaleY??1};return{x:c.x+l.x,y:c.y+l.y,z:a?.z??0,scaleX:s.x*l.scaleX,scaleY:s.y*l.scaleY}}function Uf(e){if(e.startsWith(`matrix3d(`)){let t=e.slice(9,-1).split(/, /);return{x:+t[12],y:+t[13],scaleX:+t[0],scaleY:+t[5]}}else if(e.startsWith(`matrix(`)){let t=e.slice(7,-1).split(/, /);return{x:+t[4],y:+t[5],scaleX:+t[0],scaleY:+t[3]}}return null}var Wf=(e=>(e[e.Idle=0]=`Idle`,e[e.Forward=1]=`Forward`,e[e.Reverse=-1]=`Reverse`,e))(Wf||{}),Gf={x:.2,y:.2},Kf={x:10,y:10};function qf(e,t,n,r=25,i=Gf,a=Kf){let{x:o,y:s}=t,{rect:c,isTop:l,isBottom:u,isLeft:d,isRight:f}=wf(e),p=Rf(e),m=Hf(jf(e,!0)),h=m===null?!1:m?.scaleX<0,g=m===null?!1:m?.scaleY<0,_=new Rc(c.left*p.scaleX+p.x,c.top*p.scaleY+p.y,c.width*p.scaleX,c.height*p.scaleY),v={x:0,y:0},y={x:0,y:0},b={height:_.height*i.y,width:_.width*i.x};return(!l||g&&!u)&&s<=_.top+b.height&&n?.y!==1&&o>=_.left-a.x&&o<=_.right+a.x?(v.y=g?1:-1,y.y=r*Math.abs((_.top+b.height-s)/b.height)):(!u||g&&!l)&&s>=_.bottom-b.height&&n?.y!==-1&&o>=_.left-a.x&&o<=_.right+a.x&&(v.y=g?-1:1,y.y=r*Math.abs((_.bottom-b.height-s)/b.height)),(!f||h&&!d)&&o>=_.right-b.width&&n?.x!==-1&&s>=_.top-a.y&&s<=_.bottom+a.y?(v.x=h?-1:1,y.x=r*Math.abs((_.right-b.width-o)/b.width)):(!d||h&&!f)&&o<=_.left+b.width&&n?.x!==1&&s>=_.top-a.y&&s<=_.bottom+a.y&&(v.x=h?1:-1,y.x=r*Math.abs((_.left+b.width-o)/b.width)),{direction:v,speed:y}}function Jf(e){return`scrollIntoViewIfNeeded`in e&&typeof e.scrollIntoViewIfNeeded==`function`}function Yf(e,t=!1){if(Jf(e)){e.scrollIntoViewIfNeeded(t);return}if(!Sd(e))return e.scrollIntoView();var n=Lf(e);if(!Sd(n))return;let r=jf(n,!0),i=parseInt(r.getPropertyValue(`border-top-width`)),a=parseInt(r.getPropertyValue(`border-left-width`)),o=e.offsetTop-n.offsetTop<n.scrollTop,s=e.offsetTop-n.offsetTop+e.clientHeight-i>n.scrollTop+n.clientHeight,c=e.offsetLeft-n.offsetLeft<n.scrollLeft,l=e.offsetLeft-n.offsetLeft+e.clientWidth-a>n.scrollLeft+n.clientWidth,u=o&&!s;(o||s)&&t&&(n.scrollTop=e.offsetTop-n.offsetTop-n.clientHeight/2-i+e.clientHeight/2),(c||l)&&t&&(n.scrollLeft=e.offsetLeft-n.offsetLeft-n.clientWidth/2-a+e.clientWidth/2),(o||s||c||l)&&!t&&e.scrollIntoView(u)}function Xf(e,t,n){let{scaleX:r,scaleY:i,x:a,y:o}=t,s=e.left+a+(1-r)*parseFloat(n),c=e.top+o+(1-i)*parseFloat(n.slice(n.indexOf(` `)+1)),l=r?e.width*r:e.width,u=i?e.height*i:e.height;return{width:l,height:u,top:c,right:s+l,bottom:c+u,left:s}}function Zf(e,t,n){let{scaleX:r,scaleY:i,x:a,y:o}=t,s=e.left-a-(1-r)*parseFloat(n),c=e.top-o-(1-i)*parseFloat(n.slice(n.indexOf(` `)+1)),l=r?e.width/r:e.width,u=i?e.height/i:e.height;return{width:l,height:u,top:c,right:s+l,bottom:c+u,left:s}}function Qf({element:e,keyframes:t,options:n}){return e.animate(t,n).finished}function $f(e,t=jf(e).translate,n=!0){if(n){let t=gd(e,e=>`translate`in e);if(t){let{translate:e=``}=t[0];if(typeof e==`string`){let t=Vf(e);if(t)return t}}}if(t){let e=Vf(t);if(e)return e}return{x:0,y:0,z:0}}var ep=new Ef(e=>setTimeout(e,0)),tp=new Map,np=tp.clear.bind(tp);function rp(e){let t=e.ownerDocument,n=tp.get(t);if(n)return n;n=t.getAnimations(),tp.set(t,n),ep.schedule(np);let r=n.filter(t=>hd(t.effect)&&t.effect.target===e);return tp.set(e,r),n}function ip(e,t){let n=rp(e).filter(e=>{if(hd(e.effect)){let{target:n}=e.effect;if((n&&t.isValidTarget?.call(t,n))??!0)return e.effect.getKeyframes().some(e=>{for(let n of t.properties)if(e[n])return!0})}}).map(e=>{let{effect:t,currentTime:n}=e,r=t?.getComputedTiming().duration;if(!(e.pending||e.playState===`finished`)&&typeof r==`number`&&typeof n==`number`&&n<r)return e.currentTime=r,()=>{e.currentTime=n}});if(n.length>0)return()=>n.forEach(e=>e?.())}var ap=class extends Rc{constructor(e,t={}){let{frameTransform:n=Rf(e),ignoreTransforms:r,getBoundingClientRect:i=_d}=t,a=ip(e,{properties:[`transform`,`translate`,`scale`,`width`,`height`],isValidTarget:t=>(t!==e||Md())&&t.contains(e)}),o=i(e),{top:s,left:c,width:l,height:u}=o,d,f=jf(e),p=Hf(f),m={x:p?.scaleX??1,y:p?.scaleY??1},h=op(e,f);a?.(),p&&(d=Zf(o,p,f.transformOrigin),(r||h)&&(s=d.top,c=d.left,l=d.width,u=d.height));let g={width:d?.width??l,height:d?.height??u};if(h&&!r&&d){let e=Xf(d,h,f.transformOrigin);s=e.top,c=e.left,l=e.width,u=e.height,m.x=h.scaleX,m.y=h.scaleY}n&&(r||(c*=n.scaleX,l*=n.scaleX,s*=n.scaleY,u*=n.scaleY),c+=n.x,s+=n.y),super(c,s,l,u),this.scale=m,this.intrinsicWidth=g.width,this.intrinsicHeight=g.height}};function op(e,t){let n=e.getAnimations(),r=null;if(!n.length)return null;for(let e of n){if(e.playState!==`running`)continue;let n=hd(e.effect)?e.effect.getKeyframes():[],i=n[n.length-1];if(!i)continue;let{transform:a,translate:o,scale:s}=i;if(a||o||s){let e=Hf({transform:typeof a==`string`&&a?a:t.transform,translate:typeof o==`string`&&o?o:t.translate,scale:typeof s==`string`&&s?s:t.scale});e&&(r=r?{x:r.x+e.x,y:r.y+e.y,z:r.z??e.z,scaleX:r.scaleX*e.scaleX,scaleY:r.scaleY*e.scaleY}:e)}}return r}function sp(e){return`style`in e&&typeof e.style==`object`&&e.style!==null&&`setProperty`in e.style&&`removeProperty`in e.style&&typeof e.style.setProperty==`function`&&typeof e.style.removeProperty==`function`}var cp=class{constructor(e){this.element=e,this.initial=new Map}set(e,t=``){let{element:n}=this;if(sp(n))for(let[r,i]of Object.entries(e)){let e=`${t}${r}`;this.initial.has(e)||this.initial.set(e,n.style.getPropertyValue(e)),n.style.setProperty(e,typeof i==`string`?i:`${i}px`)}}remove(e,t=``){let{element:n}=this;if(sp(n))for(let r of e){let e=`${t}${r}`;n.style.removeProperty(e)}}reset(){let{element:e}=this;if(sp(e)){for(let[t,n]of this.initial)e.style.setProperty(t,n);e.getAttribute(`style`)===``&&e.removeAttribute(`style`)}}};function lp(e){return e?e instanceof bd(e).Element||yd(e)&&e.nodeType===Node.ELEMENT_NODE:!1}function up(e){if(!e)return!1;let{KeyboardEvent:t}=bd(e.target);return e instanceof t}function dp(e){if(!e)return!1;let{PointerEvent:t}=bd(e.target);return e instanceof t}function fp(e){if(!lp(e))return!1;let{tagName:t}=e;return t===`INPUT`||t===`TEXTAREA`||pp(e)}function pp(e){return e.hasAttribute(`contenteditable`)&&e.getAttribute(`contenteditable`)!==`false`}var mp={};function hp(e){let t=mp[e]==null?0:mp[e]+1;return mp[e]=t,`${e}-${t}`}var gp=({dragOperation:e,droppable:t})=>{let n=e.position.current;if(!n)return null;let{id:r}=t;return t.shape&&t.shape.containsPoint(n)?{id:r,value:1/Lc.distance(t.shape.center,n),type:Vl.PointerIntersection,priority:Bl.High}:null},_p=({dragOperation:e,droppable:t})=>{let{shape:n}=e;if(!t.shape||!n?.current)return null;let r=n.current.intersectionArea(t.shape);if(r){let{position:i}=e,a=Lc.distance(t.shape.center,i.current),o=r/(n.current.area+t.shape.area-r)/a;return{id:t.id,value:o,type:Vl.ShapeIntersection,priority:Bl.Normal}}return null},vp=e=>gp(e)??_p(e),yp=e=>{let{dragOperation:t,droppable:n}=e,{shape:r,position:i}=t;if(!n.shape)return null;let a=r?Rc.from(r.current.boundingRectangle).corners:void 0,o=Rc.from(n.shape.boundingRectangle).corners.reduce((e,t,n)=>e+Lc.distance(Lc.from(t),a?.[n]??i.current),0)/4;return{id:n.id,value:1/o,type:Vl.Collision,priority:Bl.Normal}},bp=Object.create,xp=Object.defineProperty,Sp=Object.defineProperties,Cp=Object.getOwnPropertyDescriptor,wp=Object.getOwnPropertyDescriptors,Tp=Object.getOwnPropertySymbols,Ep=Object.prototype.hasOwnProperty,Dp=Object.prototype.propertyIsEnumerable,Op=(e,t)=>(t=Symbol[e])?t:Symbol.for(`Symbol.`+e),kp=e=>{throw TypeError(e)},Ap=(e,t,n)=>t in e?xp(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,jp=(e,t)=>{for(var n in t||={})Ep.call(t,n)&&Ap(e,n,t[n]);if(Tp)for(var n of Tp(t))Dp.call(t,n)&&Ap(e,n,t[n]);return e},Mp=(e,t)=>Sp(e,wp(t)),Np=(e,t)=>xp(e,`name`,{value:t,configurable:!0}),Pp=(e,t)=>{var n={};for(var r in e)Ep.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(e!=null&&Tp)for(var r of Tp(e))t.indexOf(r)<0&&Dp.call(e,r)&&(n[r]=e[r]);return n},Fp=e=>[,,,bp(e?.[Op(`metadata`)]??null)],Ip=[`class`,`method`,`getter`,`setter`,`accessor`,`field`,`value`,`get`,`set`],Lp=e=>e!==void 0&&typeof e!=`function`?kp(`Function expected`):e,Rp=(e,t,n,r,i)=>({kind:Ip[e],name:t,metadata:r,addInitializer:e=>n._?kp(`Already initialized`):i.push(Lp(e||null))}),zp=(e,t)=>Ap(t,Op(`metadata`),e[3]),Bp=(e,t,n,r)=>{for(var i=0,a=e[t>>1],o=a&&a.length;i<o;i++)t&1?a[i].call(n):r=a[i].call(n,r);return r},Vp=(e,t,n,r,i,a)=>{var o,s,c,l,u,d=t&7,f=!!(t&8),p=!!(t&16),m=d>3?e.length+1:d?f?1:2:0,h=Ip[d+5],g=d>3&&(e[m-1]=[]),_=e[m]||(e[m]=[]),v=d&&(!p&&!f&&(i=i.prototype),d<5&&(d>3||!p)&&Cp(d<4?i:{get[n](){return $(this,a)},set[n](e){return Gp(this,a,e)}},n));d?p&&d<4&&Np(a,(d>2?`set `:d>1?`get `:``)+n):Np(i,n);for(var y=r.length-1;y>=0;y--)l=Rp(d,n,c={},e[3],_),d&&(l.static=f,l.private=p,u=l.access={has:p?e=>Up(i,e):e=>n in e},d^3&&(u.get=p?e=>(d^1?$:Kp)(e,i,d^4?a:v.get):e=>e[n]),d>2&&(u.set=p?(e,t)=>Gp(e,i,t,d^4?a:v.set):(e,t)=>e[n]=t)),s=(0,r[y])(d?d<4?p?a:v[h]:d>4?void 0:{get:v.get,set:v.set}:i,l),c._=1,d^4||s===void 0?Lp(s)&&(d>4?g.unshift(s):d?p?a=s:v[h]=s:i=s):typeof s!=`object`||!s?kp(`Object expected`):(Lp(o=s.get)&&(v.get=o),Lp(o=s.set)&&(v.set=o),Lp(o=s.init)&&g.unshift(o));return d||zp(e,i),v&&xp(i,n,v),p?d^4?a:v:i},Hp=(e,t,n)=>t.has(e)||kp(`Cannot `+n),Up=(e,t)=>Object(t)===t?e.has(t):kp(`Cannot use the \"in\" operator on this value`),$=(e,t,n)=>(Hp(e,t,`read from private field`),n?n.call(e):t.get(e)),Wp=(e,t,n)=>t.has(e)?kp(`Cannot add the same private member more than once`):t instanceof WeakSet?t.add(e):t.set(e,n),Gp=(e,t,n,r)=>(Hp(e,t,`write to private field`),r?r.call(e,n):t.set(e,n),n),Kp=(e,t,n)=>(Hp(e,t,`access private method`),n),qp={role:`button`,roleDescription:`draggable`},Jp=`dnd-kit-description`,Yp=`dnd-kit-announcement`,Xp={draggable:`To pick up a draggable item, press the space bar. While dragging, use the arrow keys to move the item in a given direction. Press space again to drop the item in its new position, or press escape to cancel.`},Zp={dragstart({operation:{source:e}}){if(e)return`Picked up draggable item ${e.id}.`},dragover({operation:{source:e,target:t}}){if(!(!e||e.id===t?.id))return t?`Draggable item ${e.id} was moved over droppable target ${t.id}.`:`Draggable item ${e.id} is no longer over a droppable target.`},dragend({operation:{source:e,target:t},canceled:n}){if(e)return n?`Dragging was cancelled. Draggable item ${e.id} was dropped.`:t?`Draggable item ${e.id} was dropped over droppable target ${t.id}`:`Draggable item ${e.id} was dropped.`}};function Qp(e){let t=e.tagName.toLowerCase();return[`input`,`select`,`textarea`,`a`,`button`].includes(t)}function $p(e,t){let n=document.createElement(`div`);return n.id=e,n.style.setProperty(`display`,`none`),n.textContent=t,n}function em(e){let t=document.createElement(`div`);return t.id=e,t.setAttribute(`role`,`status`),t.setAttribute(`aria-live`,`polite`),t.setAttribute(`aria-atomic`,`true`),t.style.setProperty(`position`,`fixed`),t.style.setProperty(`width`,`1px`),t.style.setProperty(`height`,`1px`),t.style.setProperty(`margin`,`-1px`),t.style.setProperty(`border`,`0`),t.style.setProperty(`padding`,`0`),t.style.setProperty(`overflow`,`hidden`),t.style.setProperty(`clip`,`rect(0 0 0 0)`),t.style.setProperty(`clip-path`,`inset(100%)`),t.style.setProperty(`white-space`,`nowrap`),t}var tm=[`dragover`,`dragmove`],nm=class extends Dl{constructor(e,t){super(e);let{id:n,idPrefix:{description:r=Jp,announcement:i=Yp}={},announcements:a=Zp,screenReaderInstructions:o=Xp,debounce:s=500}=t??{},c=n?`${r}-${n}`:hp(r),l=n?`${i}-${n}`:hp(i),u,d,f,p,m=(e=p)=>{!f||!e||f?.nodeValue!==e&&(f.nodeValue=e)},h=()=>Df.schedule(m),g=rm(h,s),_=Object.entries(a).map(([e,t])=>this.manager.monitor.addEventListener(e,(n,r)=>{let i=f;if(!i)return;let a=t?.(n,r);a&&i.nodeValue!==a&&(p=a,tm.includes(e)?g():(h(),g.cancel()))})),v=()=>{let e=[];u?.isConnected||(u=$p(c,o.draggable),e.push(u)),d?.isConnected||(d=em(l),f=document.createTextNode(``),d.appendChild(f),e.push(d)),e.length>0&&document.body.append(...e)},y=new Set;function b(){for(let e of y)e()}this.registerEffect(()=>{y.clear();for(let e of this.manager.registry.draggables.value){let t=e.handle??e.element;if(t){(!u||!d)&&y.add(v),(!Qp(t)||Md())&&!t.hasAttribute(`tabindex`)&&y.add(()=>t.setAttribute(`tabindex`,`0`)),!t.hasAttribute(`role`)&&t.tagName.toLowerCase()!==`button`&&y.add(()=>t.setAttribute(`role`,qp.role)),t.hasAttribute(`aria-roledescription`)||y.add(()=>t.setAttribute(`aria-roledescription`,qp.roleDescription)),t.hasAttribute(`aria-describedby`)||y.add(()=>t.setAttribute(`aria-describedby`,c));for(let n of[`aria-pressed`,`aria-grabbed`]){let r=String(e.isDragging);t.getAttribute(n)!==r&&y.add(()=>t.setAttribute(n,r))}let n=String(e.disabled);t.getAttribute(`aria-disabled`)!==n&&y.add(()=>t.setAttribute(`aria-disabled`,n))}}y.size>0&&Df.schedule(b)}),this.destroy=()=>{super.destroy(),u?.remove(),d?.remove(),_.forEach(e=>e())}}};function rm(e,t){let n,r=()=>{clearTimeout(n),n=setTimeout(e,t)};return r.cancel=()=>clearTimeout(n),r}var im=class extends Dl{constructor(e,t){super(e,t),this.manager=e;let n=Ls(()=>wd(this.manager.dragOperation.source?.element));this.destroy=ls(()=>{let{dragOperation:e}=this.manager,{cursor:t=`grabbing`,nonce:r}=this.options??{};if(e.status.initialized){let e=n.value,i=e.createElement(`style`);return r&&i.setAttribute(`nonce`,r),i.textContent=`* { cursor: ${t} !important; }`,e.head.appendChild(i),()=>i.remove()}})}},am=`data-dnd-`,om=`${am}dropping`,sm=`--dnd-`,cm=`${am}dragging`,lm=`${am}placeholder`,um=[cm,lm,`popover`,`aria-pressed`,`aria-grabbing`],dm=[`view-transition-name`],fm=`\n  :root [${cm}] {\n    position: fixed !important;\n    pointer-events: none !important;\n    touch-action: none;\n    z-index: calc(infinity);\n    will-change: translate;\n    top: var(${sm}top, 0px) !important;\n    left: var(${sm}left, 0px) !important;\n    right: unset !important;\n    bottom: unset !important;\n    width: var(${sm}width, auto);\n    max-width: var(${sm}width, auto);\n    height: var(${sm}height, auto);\n    max-height: var(${sm}height, auto);\n    transition: var(${sm}transition) !important;\n  }\n\n  :root [${lm}] {\n    transition: none;\n  }\n\n  :root [${lm}='hidden'] {\n    visibility: hidden;\n  }\n\n  [${cm}] * {\n    pointer-events: none !important;\n  }\n\n  [${cm}]:not([${om}]) {\n    translate: var(${sm}translate) !important;\n  }\n\n  [${cm}][style*='${sm}scale'] {\n    scale: var(${sm}scale) !important;\n    transform-origin: var(${sm}transform-origin) !important;\n  }\n\n  @layer {\n    :where([${cm}][popover]) {\n      overflow: visible;\n      background: unset;\n      border: unset;\n      margin: unset;\n      padding: unset;\n      color: inherit;\n\n      &:is(input, button) {\n        border: revert;\n        background: revert;\n      }\n    }\n  }\n  [${cm}]::backdrop, [${am}overlay]:not([${cm}]) {\n    display: none;\n    visibility: hidden;\n  }\n`.replace(/\\n+/g,` `).replace(/\\s+/g,` `).trim();function pm(e,t=`hidden`){return q(()=>{let{element:n,manager:r}=e;if(!n||!r)return;let i=mm(n,r.registry.droppables),a=[],o=Nd(n),{remove:s}=o;return hm(i,o,a),gm(o,t),o.remove=()=>{a.forEach(e=>e()),s.call(o)},o})}function mm(e,t){let n=new Map;for(let r of t)if(r.element&&(e===r.element||e.contains(r.element))){let e=`${am}${hp(`dom-id`)}`;r.element.setAttribute(e,``),n.set(r,e)}return n}function hm(e,t,n){for(let[r,i]of e){if(!r.element)continue;let e=`[${i}]`,a=t.matches(e)?t:t.querySelector(e);if(r.element.removeAttribute(i),!a)continue;let o=r.element;r.proxy=a,a.removeAttribute(i),Rd.set(o,a),n.push(()=>{Rd.delete(o),r.proxy=void 0})}}function gm(e,t=`hidden`){e.setAttribute(`inert`,`true`),e.setAttribute(`tab-index`,`-1`),e.setAttribute(`aria-hidden`,`true`),e.setAttribute(lm,t)}function _m(e,t){return e===t?!0:Vd(e)===Vd(t)}function vm(e){let{target:t}=e;`newState`in e&&e.newState===`closed`&&lp(t)&&t.hasAttribute(`popover`)&&requestAnimationFrame(()=>Sf(t))}function ym(e){return e.tagName===`TR`}var bm=new Map,xm,Sm,Cm,wm,Tm,Em,Dm,Om=class extends (Sm=Dl,xm=[J],Sm){constructor(e,t){super(e,t),Wp(this,Tm),Wp(this,wm,Bp(Cm,8,this)),Bp(Cm,11,this),this.state={initial:{},current:{}},this.registerEffect(Kp(this,Tm,Dm)),this.registerEffect(Kp(this,Tm,Em))}destroy(){super.destroy();for(let[e,t]of bm.entries())t.instances.has(this)&&(t.instances.delete(this),t.instances.size===0&&(t.cleanup(),bm.delete(e)))}};Cm=Fp(Sm),wm=new WeakMap,Tm=new WeakSet,Em=function(){let{state:e,manager:t,options:n}=this,{dragOperation:r}=t,{position:i,source:a,status:o}=r;if(o.idle){e.current={},e.initial={};return}if(!a)return;let{element:s,feedback:c}=a;if(!s||c===`none`||!o.initialized||o.initializing)return;let{initial:l}=e,u=this.overlay??s,d=Rf(u),f=Rf(s),p=!_m(s,u),m=new ap(s,{frameTransform:p?f:null,ignoreTransforms:!p}),h={x:f.scaleX/d.scaleX,y:f.scaleY/d.scaleY},{width:g,height:_,top:v,left:y}=m;p&&(g/=h.x,_/=h.y);let b,x,S=new cp(u),{transition:C,translate:w,boxSizing:T,paddingBlockStart:E,paddingBlockEnd:D,paddingInlineStart:O,paddingInlineEnd:ee,borderInlineStartWidth:te,borderInlineEndWidth:k,borderBlockStartWidth:ne,borderBlockEndWidth:re}=jf(s),ie=c===`clone`,ae=T===`content-box`,oe=ae?parseInt(O)+parseInt(ee)+parseInt(te)+parseInt(k):0,se=ae?parseInt(E)+parseInt(D)+parseInt(ne)+parseInt(re):0,A=c!==`move`&&!this.overlay?pm(a,ie?`clone`:`hidden`):null,j=q(()=>up(t.dragOperation.activatorEvent));if(w!==`none`){let e=Vf(w);e&&!l.translate&&(l.translate=e)}if(!l.transformOrigin){let e=q(()=>i.current);l.transformOrigin={x:(e.x-y*d.scaleX-d.x)/(g*d.scaleX),y:(e.y-v*d.scaleY-d.y)/(_*d.scaleY)}}let{transformOrigin:M}=l,ce=v*d.scaleY+d.y,N=y*d.scaleX+d.x;if(!l.coordinates&&(l.coordinates={x:N,y:ce},h.x!==1||h.y!==1)){let{scaleX:e,scaleY:t}=f,{x:n,y:r}=M;l.coordinates.x+=(g*e-g)*n,l.coordinates.y+=(_*t-_)*r}l.dimensions||={width:g,height:_},l.frameTransform||=d;let P={x:l.coordinates.x-N,y:l.coordinates.y-ce},le={width:(l.dimensions.width*l.frameTransform.scaleX-g*d.scaleX)*M.x,height:(l.dimensions.height*l.frameTransform.scaleY-_*d.scaleY)*M.y},ue={x:P.x/d.scaleX+le.width,y:P.y/d.scaleY+le.height},de={left:y+ue.x,top:v+ue.y};u.setAttribute(cm,`true`);let fe=q(()=>r.transform),pe=l.translate??{x:0,y:0},me=`${fe.x*d.scaleX+pe.x}px ${fe.y*d.scaleY+pe.y}px 0`,he=C?`${C}, translate 0ms linear`:``;S.set({width:g-oe,height:_-se,top:de.top,left:de.left,translate:me,transition:he,scale:p?`${h.x} ${h.y}`:``,\"transform-origin\":`${M.x*100}% ${M.y*100}%`},sm),A&&(s.insertAdjacentElement(`afterend`,A),n?.rootElement&&(typeof n.rootElement==`function`?n.rootElement(a):n.rootElement).appendChild(s)),xf(u)&&(u.hasAttribute(`popover`)||u.setAttribute(`popover`,`manual`),Sf(u),u.addEventListener(`beforetoggle`,vm));let ge=new ResizeObserver(()=>{if(!A)return;let e=new ap(A,{frameTransform:d,ignoreTransforms:!0}),t=M??{x:1,y:1},n=(g-e.width)*t.x+ue.x,i=(_-e.height)*t.y+ue.y;if(S.set({width:e.width-oe,height:e.height-se,top:v+i,left:y+n},sm),b?.takeRecords(),ym(s)&&ym(A)){let e=Array.from(s.cells),t=Array.from(A.cells);for(let[n,r]of e.entries()){let e=t[n];r.style.width=`${e.offsetWidth}px`}}r.shape=new ap(u)}),_e=new ap(u);q(()=>r.shape=_e);let ve=bd(u),ye=e=>{this.manager.actions.stop({event:e})};j&&ve.addEventListener(`resize`,ye),q(()=>a.status)===`idle`&&requestAnimationFrame(()=>a.status=`dragging`),A&&(ge.observe(A),b=new MutationObserver(e=>{let t=!1;for(let n of e){if(n.target!==s){t=!0;continue}if(n.type!==`attributes`)continue;let e=n.attributeName;if(e.startsWith(`aria-`)||um.includes(e))continue;let r=s.getAttribute(e);if(e===`style`){if(sp(s)&&sp(A)){let e=s.style;for(let t of Array.from(A.style))e.getPropertyValue(t)===``&&A.style.removeProperty(t);for(let t of Array.from(e)){if(dm.includes(t)||t.startsWith(sm))continue;let n=e.getPropertyValue(t);A.style.setProperty(t,n)}}}else r===null?A.removeAttribute(e):A.setAttribute(e,r)}t&&ie&&(A.innerHTML=s.innerHTML)}),b.observe(s,{attributes:!0,subtree:!0,childList:!0}),x=new MutationObserver(e=>{for(let t of e)if(t.addedNodes.length!==0)for(let e of Array.from(t.addedNodes)){if(e.contains(s)&&s.nextElementSibling!==A){s.insertAdjacentElement(`afterend`,A),Sf(u);return}if(e.contains(A)&&A.previousElementSibling!==s){A.insertAdjacentElement(`beforebegin`,s),Sf(u);return}}}),x.observe(s.ownerDocument.body,{childList:!0,subtree:!0}));let be=t.dragOperation.source?.id,xe=()=>{if(!j||be==null)return;let e=t.registry.draggables.get(be),n=e?.handle??e?.element;Sd(n)&&n.focus()},Se=()=>{b?.disconnect(),x?.disconnect(),ge.disconnect(),ve.removeEventListener(`resize`,ye),xf(u)&&(u.removeEventListener(`beforetoggle`,vm),u.removeAttribute(`popover`)),u.removeAttribute(cm),S.reset(),a.status=`idle`;let t=e.current.translate!=null;A&&(t||A.parentElement!==u.parentElement)&&u.isConnected&&A.replaceWith(u),A?.remove()},Ce=Vs(()=>{let{transform:t,status:n}=r;if(!(!t.x&&!t.y&&!e.current.translate)&&n.dragging){let n=l.translate??{x:0,y:0},i={x:t.x/d.scaleX+n.x,y:t.y/d.scaleY+n.y},a=e.current.translate,o=q(()=>r.modifiers),s=q(()=>r.shape?.current),c=j?`250ms cubic-bezier(0.25, 1, 0.5, 1)`:`0ms linear`;if(S.set({transition:`${C}, translate ${c}`,translate:`${i.x}px ${i.y}px 0`},sm),b?.takeRecords(),s&&s!==_e&&a&&!o.length){let e=Lc.delta(i,a);r.shape=Rc.from(s.boundingRectangle).translate(e.x*d.scaleX,e.y*d.scaleY)}else r.shape=new ap(u);e.current.translate=i}},function(){if(r.status.dropped){this.dispose(),a.status=`dropping`;let n=e.current.translate,r=n!=null;if(!n&&s!==u&&(n={x:0,y:0}),!n){Se();return}t.renderer.rendering.then(()=>{{Sf(u);let[,e]=gd(u,e=>`translate`in e)??[];e?.pause();let t=A??s,i={frameTransform:_m(u,t)?null:void 0},o=new ap(u,i),c=Vf(jf(u).translate)??n,l=new ap(t,i),d=Rc.delta(o,l,a.alignment),f={x:c.x-d.x,y:c.y-d.y},p=Math.round(o.intrinsicHeight)===Math.round(l.intrinsicHeight)?{}:{minHeight:[`${o.intrinsicHeight}px`,`${l.intrinsicHeight}px`],maxHeight:[`${o.intrinsicHeight}px`,`${l.intrinsicHeight}px`]},m=Math.round(o.intrinsicWidth)===Math.round(l.intrinsicWidth)?{}:{minWidth:[`${o.intrinsicWidth}px`,`${l.intrinsicWidth}px`],maxWidth:[`${o.intrinsicWidth}px`,`${l.intrinsicWidth}px`]};S.set({transition:C},sm),u.setAttribute(om,``),b?.takeRecords(),Qf({element:u,keyframes:Mp(jp(jp({},p),m),{translate:[`${c.x}px ${c.y}px 0`,`${f.x}px ${f.y}px 0`]}),options:{duration:r||u!==s?250:0,easing:`ease`}}).then(()=>{u.removeAttribute(om),e?.finish(),Se(),requestAnimationFrame(xe)})}})}});return()=>{Se(),Ce()}},Dm=function(){let{status:e,source:t,target:n}=this.manager.dragOperation,{nonce:r}=this.options??{};if(e.initializing){let e=wd(t?.element??null),i=wd(n?.element??null),a=new Set([e,i]);for(let e of a){let t=bm.get(e);if(!t){let n=document.createElement(`style`);n.textContent=fm,r&&n.setAttribute(`nonce`,r),e.head.prepend(n);let i=new MutationObserver(t=>{for(let r of t)if(r.type===`childList`){let t=Array.from(r.removedNodes);t.length>0&&t.includes(n)&&e.head.prepend(n)}});i.observe(e.head,{childList:!0}),t={cleanup:()=>{i.disconnect(),n.remove()},instances:new Set},bm.set(e,t)}t.instances.add(this)}}},Vp(Cm,4,`overlay`,xm,Om,wm),zp(Cm,Om),Om.configure=xl(Om);var km=Om,Am=!0,jm=!1,Mm,Nm,Pm,Fm=(Pm=[J],Wf.Forward),Im,Lm,Rm;Nm=(Mm=[J],Wf.Reverse);var zm=class{constructor(){Wp(this,Lm,Bp(Im,8,this,Am)),Bp(Im,11,this),Wp(this,Rm,Bp(Im,12,this,Am)),Bp(Im,15,this)}isLocked(e){return e===Wf.Idle?!1:e==null?this[Wf.Forward]===Am&&this[Wf.Reverse]===Am:this[e]===Am}unlock(e){e!==Wf.Idle&&(this[e]=jm)}};Im=Fp(null),Lm=new WeakMap,Rm=new WeakMap,Vp(Im,4,Fm,Pm,zm,Lm),Vp(Im,4,Nm,Mm,zm,Rm),zp(Im,zm);var Bm=[Wf.Forward,Wf.Reverse],Vm=class{constructor(){this.x=new zm,this.y=new zm}isLocked(){return this.x.isLocked()&&this.y.isLocked()}},Hm=class extends Dl{constructor(e){super(e);let t=$o(new Vm),n=null;this.signal=t,ls(()=>{let{status:r}=e.dragOperation;if(!r.initialized){n=null,t.value=new Vm;return}let{delta:i}=e.dragOperation.position;if(n){let e={x:Um(i.x,n.x),y:Um(i.y,n.y)},r=t.peek();Ko(()=>{for(let t of Jc)for(let n of Bm)e[t]===n&&r[t].unlock(n);t.value=r})}n=i})}get current(){return this.signal.peek()}};function Um(e,t){return Math.sign(e-t)}var Wm,Gm,Km,qm,Jm,Ym,Xm=class extends (Gm=Ol,Wm=[J],Gm){constructor(e){super(e),Wp(this,qm,Bp(Km,8,this,!1)),Bp(Km,11,this),Wp(this,Jm),Wp(this,Ym,()=>{if(!$(this,Jm))return;let{element:e,by:t}=$(this,Jm);t.y&&(e.scrollTop+=t.y),t.x&&(e.scrollLeft+=t.x)}),this.scroll=e=>{if(this.disabled)return!1;let t=this.getScrollableElements();if(!t)return Gp(this,Jm,void 0),!1;let{position:n}=this.manager.dragOperation,r=n?.current;if(r){let{by:n}=e??{},i=n?{x:Zm(n.x),y:Zm(n.y)}:void 0,a=i?void 0:this.scrollIntentTracker.current;if(a?.isLocked())return!1;for(let e of t){let t=Tf(e,n);if(t.x||t.y){let{speed:t,direction:o}=qf(e,r,i);if(a)for(let e of Jc)a[e].isLocked(o[e])&&(t[e]=0,o[e]=0);if(o.x||o.y){let{x:r,y:i}=n??o,a=r*t.x,s=i*t.y;if(a||s){let t=$(this,Jm)?.by;if(this.autoScrolling&&t&&(t.x&&!a||t.y&&!s))continue;return Gp(this,Jm,{element:e,by:{x:a,y:s}}),Df.schedule($(this,Ym)),!0}}}}}return Gp(this,Jm,void 0),!1};let t=null,n=null,r=Ls(()=>{let{position:n,source:r}=e.dragOperation;if(!n)return null;let i=Id(wd(r?.element),n.current);return i&&(t=i),i??t}),i=Ls(()=>{let t=r.value,{documentElement:i}=wd(t);if(!t||t===i){let{target:t}=e.dragOperation,r=t?.element;if(r){let e=If(r,{excludeElement:!1});return n=e,e}}if(t){let e=If(t,{excludeElement:!1});return this.autoScrolling&&n&&e.size<n?.size?n:(n=e,e)}return n=null,null},Rs);this.getScrollableElements=()=>i.value,this.scrollIntentTracker=new Hm(e),this.destroy=e.monitor.addEventListener(`dragmove`,t=>{this.disabled||t.defaultPrevented||!up(e.dragOperation.activatorEvent)||!t.by||this.scroll({by:t.by})&&t.preventDefault()})}};Km=Fp(Gm),qm=new WeakMap,Jm=new WeakMap,Ym=new WeakMap,Vp(Km,4,`autoScrolling`,Wm,Xm,qm),zp(Km,Xm);function Zm(e){return e>0?Wf.Forward:e<0?Wf.Reverse:Wf.Idle}var Qm=new class{constructor(e){this.scheduler=e,this.pending=!1,this.tasks=new Set,this.resolvers=new Set,this.flush=()=>{let{tasks:e,resolvers:t}=this;this.pending=!1,this.tasks=new Set,this.resolvers=new Set;for(let t of e)t();for(let e of t)e()}}schedule(e){return this.tasks.add(e),this.pending||(this.pending=!0,this.scheduler(this.flush)),new Promise(e=>this.resolvers.add(e))}}(e=>{typeof requestAnimationFrame==`function`?requestAnimationFrame(e):e()}),$m=10,eh=class extends Dl{constructor(e,t){super(e);let n=e.registry.plugins.get(Xm);if(!n)throw Error(`AutoScroller plugin depends on Scroller plugin`);this.destroy=ls(()=>{if(this.disabled)return;let{position:t,status:r}=e.dragOperation;if(r.dragging)if(n.scroll()){n.autoScrolling=!0;let e=setInterval(()=>Qm.schedule(n.scroll),$m);return()=>{clearInterval(e)}}else n.autoScrolling=!1})}},th={capture:!0,passive:!0},nh,rh=class extends Ol{constructor(e){super(e),Wp(this,nh),this.handleScroll=()=>{$(this,nh)??Gp(this,nh,setTimeout(()=>{this.manager.collisionObserver.forceUpdate(!1),Gp(this,nh,void 0)},50))};let{dragOperation:t}=this.manager;this.destroy=ls(()=>{if(t.status.dragging){let e=t.source?.element?.ownerDocument??document;return e.addEventListener(`scroll`,this.handleScroll,th),()=>{e.removeEventListener(`scroll`,this.handleScroll,th)}}})}};nh=new WeakMap;var ih=class extends Dl{constructor(e,t){super(e,t),this.manager=e,this.destroy=ls(()=>{let{dragOperation:e}=this.manager,{nonce:t}=this.options??{};if(e.status.initialized){let e=document.createElement(`style`);return t&&e.setAttribute(`nonce`,t),e.textContent=`* { user-select: none !important; -webkit-user-select: none !important; }`,document.head.appendChild(e),ah(),document.addEventListener(`selectionchange`,ah,{capture:!0}),()=>{document.removeEventListener(`selectionchange`,ah,{capture:!0}),e.remove()}}})}};function ah(){var e;(e=document.getSelection())==null||e.removeAllRanges()}var oh=Object.freeze({offset:10,keyboardCodes:{start:[`Space`,`Enter`],cancel:[`Escape`],end:[`Space`,`Enter`,`Tab`],up:[`ArrowUp`],down:[`ArrowDown`],left:[`ArrowLeft`],right:[`ArrowRight`]},preventActivation(e,t){let n=t.handle??t.element;return e.target!==n}}),sh,ch=class extends Iu{constructor(e,t){super(e),this.manager=e,this.options=t,Wp(this,sh,[]),this.listeners=new Bd,this.handleSourceKeyDown=(e,t,n)=>{if(this.disabled||e.defaultPrevented||!lp(e.target)||t.disabled)return;let{keyboardCodes:r=oh.keyboardCodes,preventActivation:i=oh.preventActivation}=n??{};r.start.includes(e.code)&&this.manager.dragOperation.status.idle&&(i?.(e,t)||this.handleStart(e,t,n))}}bind(e,t=this.options){return ls(()=>{let n=e.handle??e.element,r=n=>{up(n)&&this.handleSourceKeyDown(n,e,t)};if(n)return n.addEventListener(`keydown`,r),()=>{n.removeEventListener(`keydown`,r)}})}handleStart(e,t,n){let{element:r}=t;if(!r)throw Error(`Source draggable does not have an associated element`);e.preventDefault(),e.stopImmediatePropagation(),Yf(r);let{center:i}=new ap(r);if(this.manager.actions.start({event:e,coordinates:{x:i.x,y:i.y},source:t}).signal.aborted)return this.cleanup();this.sideEffects();let a=wd(r),o=[this.listeners.bind(a,[{type:`keydown`,listener:e=>this.handleKeyDown(e,t,n),options:{capture:!0}}])];$(this,sh).push(...o)}handleKeyDown(e,t,n){let{keyboardCodes:r=oh.keyboardCodes}=n??{};if(uh(e,[...r.end,...r.cancel])){e.preventDefault();let t=uh(e,r.cancel);this.handleEnd(e,t);return}uh(e,r.up)?this.handleMove(`up`,e):uh(e,r.down)&&this.handleMove(`down`,e),uh(e,r.left)?this.handleMove(`left`,e):uh(e,r.right)&&this.handleMove(`right`,e)}handleEnd(e,t){this.manager.actions.stop({event:e,canceled:t}),this.cleanup()}handleMove(e,t){let{shape:n}=this.manager.dragOperation,r=t.shiftKey?5:1,i={x:0,y:0},a=this.options?.offset??oh.offset;if(typeof a==`number`&&(a={x:a,y:a}),n){switch(e){case`up`:i={x:0,y:-a.y*r};break;case`down`:i={x:0,y:a.y*r};break;case`left`:i={x:-a.x*r,y:0};break;case`right`:i={x:a.x*r,y:0};break}(i.x||i.y)&&(t.preventDefault(),this.manager.actions.move({event:t,by:i}))}}sideEffects(){let e=this.manager.registry.plugins.get(eh);e?.disabled===!1&&(e.disable(),$(this,sh).push(()=>{e.enable()}))}cleanup(){$(this,sh).forEach(e=>e()),Gp(this,sh,[])}destroy(){this.cleanup(),this.listeners.clear()}};sh=new WeakMap,ch.configure=xl(ch),ch.defaults=oh;var lh=ch;function uh(e,t){return t.includes(e.code)}var dh,fh=class extends zu{constructor(){super(...arguments),Wp(this,dh)}onEvent(e){switch(e.type){case`pointerdown`:Gp(this,dh,kd(e));break;case`pointermove`:if(!$(this,dh))return;let{x:t,y:n}=kd(e),r={x:t-$(this,dh).x,y:n-$(this,dh).y},{tolerance:i}=this.options;if(i&&Kc(r,i)){this.abort();return}Kc(r,this.options.value)&&this.activate(e);break;case`pointerup`:this.abort();break}}abort(){Gp(this,dh,void 0)}};dh=new WeakMap;var ph,mh,hh=class extends zu{constructor(){super(...arguments),Wp(this,ph),Wp(this,mh)}onEvent(e){switch(e.type){case`pointerdown`:Gp(this,mh,kd(e)),Gp(this,ph,setTimeout(()=>this.activate(e),this.options.value));break;case`pointermove`:if(!$(this,mh))return;let{x:t,y:n}=kd(e);Kc({x:t-$(this,mh).x,y:n-$(this,mh).y},this.options.tolerance)&&this.abort();break;case`pointerup`:this.abort();break}}abort(){$(this,ph)&&(clearTimeout($(this,ph)),Gp(this,mh,void 0),Gp(this,ph,void 0))}};ph=new WeakMap,mh=new WeakMap;var gh=class{};gh.Delay=hh,gh.Distance=fh;var _h=Object.freeze({activationConstraints(e,t){let{pointerType:n,target:r}=e;if(!(n===`mouse`&&lp(r)&&(t.handle===r||t.handle?.contains(r))))return n===`touch`?[new gh.Delay({value:250,tolerance:5})]:fp(r)&&!e.defaultPrevented?[new gh.Delay({value:200,tolerance:0})]:[new gh.Delay({value:200,tolerance:10}),new gh.Distance({value:5})]},preventActivation(e,t){let{target:n}=e;return n===t.element||n===t.handle||!lp(n)||t.handle?.contains(n)?!1:zd(n)}}),vh,yh=class extends Iu{constructor(e,t){super(e),this.manager=e,this.options=t,Wp(this,vh,new Set),this.listeners=new Bd,this.latest={event:void 0,coordinates:void 0},this.handleMove=()=>{let{event:e,coordinates:t}=this.latest;!e||!t||this.manager.actions.move({event:e,to:t})},this.handleCancel=this.handleCancel.bind(this),this.handlePointerUp=this.handlePointerUp.bind(this),this.handleKeyDown=this.handleKeyDown.bind(this)}activationConstraints(e,t,n=this.options){let{activationConstraints:r=_h.activationConstraints}=n??{};return typeof r==`function`?r(e,t):r}bind(e,t=this.options){return ls(()=>{let n=new AbortController,{signal:r}=n,i=n=>{dp(n)&&this.handlePointerDown(n,e,t)},a=[e.handle??e.element];t?.activatorElements&&(a=Array.isArray(t.activatorElements)?t.activatorElements:t.activatorElements(e));for(let e of a)e&&(Th(e.ownerDocument.defaultView),e.addEventListener(`pointerdown`,i,{signal:r}));return()=>n.abort()})}handlePointerDown(e,t,n){if(this.disabled||!e.isPrimary||e.button!==0||!lp(e.target)||t.disabled||xh(e)||!this.manager.dragOperation.status.idle)return;let{preventActivation:r=_h.preventActivation}=n??{};if(r?.(e,t))return;let{target:i}=e,a=Sd(i)&&i.draggable&&i.getAttribute(`draggable`)===`true`,o=Rf(t.element),{x:s,y:c}=kd(e);this.initialCoordinates={x:s*o.scaleX+o.x,y:c*o.scaleY+o.y};let l=this.activationConstraints(e,t,n);e.sensor=this;let u=new Lu(l,e=>this.handleStart(t,e));u.signal.onabort=()=>this.handleCancel(e),u.onEvent(e),this.controller=u;let d=jd(),f=this.listeners.bind(d,[{type:`pointermove`,listener:e=>this.handlePointerMove(e,t)},{type:`pointerup`,listener:this.handlePointerUp,options:{capture:!0}},{type:`dragstart`,listener:a?this.handleCancel:Sh,options:{capture:!0}}]);$(this,vh).add(()=>{f(),this.initialCoordinates=void 0})}handlePointerMove(e,t){var n;if(this.controller?.activated===!1){(n=this.controller)==null||n.onEvent(e);return}if(this.manager.dragOperation.status.dragging){let n=kd(e),r=Rf(t.element);n.x=n.x*r.scaleX+r.x,n.y=n.y*r.scaleY+r.y,e.preventDefault(),e.stopPropagation(),this.latest.event=e,this.latest.coordinates=n,Df.schedule(this.handleMove)}}handlePointerUp(e){let{status:t}=this.manager.dragOperation;if(!t.idle){e.preventDefault(),e.stopPropagation();let n=!t.initialized;this.manager.actions.stop({event:e,canceled:n})}this.cleanup()}handleKeyDown(e){e.key===`Escape`&&(e.preventDefault(),this.handleCancel(e))}handleStart(e,t){let{manager:n,initialCoordinates:r}=this;if(!r||!n.dragOperation.status.idle||t.defaultPrevented)return;if(n.actions.start({coordinates:r,event:t,source:e}).signal.aborted)return this.cleanup();t.preventDefault();let i=wd(t.target);i.body.setPointerCapture(t.pointerId);let a=lp(t.target)?[t.target,i.body]:i.body,o=this.listeners.bind(a,[{type:`touchmove`,listener:Sh,options:{passive:!1}},{type:`click`,listener:Sh},{type:`contextmenu`,listener:Sh},{type:`keydown`,listener:this.handleKeyDown}]);$(this,vh).add(o)}handleCancel(e){let{dragOperation:t}=this.manager;t.status.initialized&&this.manager.actions.stop({event:e,canceled:!0}),this.cleanup()}cleanup(){this.latest={event:void 0,coordinates:void 0},$(this,vh).forEach(e=>e()),$(this,vh).clear()}destroy(){this.cleanup(),this.listeners.clear()}};vh=new WeakMap,yh.configure=xl(yh),yh.defaults=_h;var bh=yh;function xh(e){return`sensor`in e}function Sh(e){e.preventDefault()}function Ch(){}var wh=new WeakSet;function Th(e){!e||wh.has(e)||(e.addEventListener(`touchmove`,Ch,{capture:!1,passive:!1}),wh.add(e))}var Eh={modifiers:[],plugins:[nm,eh,im,km,ih],sensors:[bh,lh]},Dh=class extends ld{constructor(e={}){let{plugins:t=Eh.plugins,sensors:n=Eh.sensors,modifiers:r=[]}=e;super(Mp(jp({},e),{plugins:[rh,Xm,...t],sensors:n,modifiers:r}))}},Oh,kh,Ah,jh,Mh,Nh,Ph,Fh,Ih=class extends (jh=xu,Ah=[J],kh=[J],Oh=[J],jh){constructor(e,t){var n=e,{element:r,effects:i=()=>[],handle:a,feedback:o=`default`}=n,s=Pp(n,[`element`,`effects`,`handle`,`feedback`]);super(jp({effects:()=>[...i(),()=>{let{manager:e}=this;if(!e)return;let t=(this.sensors?.map(Sl)??[...e.sensors]).map(t=>{let n=t instanceof Iu?t:e.registry.register(t.plugin),r=t instanceof Iu?void 0:t.options;return n.bind(this,r)});return function(){t.forEach(e=>e())}}]},s),t),Wp(this,Nh,Bp(Mh,8,this)),Bp(Mh,11,this),Wp(this,Ph,Bp(Mh,12,this)),Bp(Mh,15,this),Wp(this,Fh,Bp(Mh,16,this)),Bp(Mh,19,this),this.element=r,this.handle=a,this.feedback=o}};Mh=Fp(jh),Nh=new WeakMap,Ph=new WeakMap,Fh=new WeakMap,Vp(Mh,4,`handle`,Ah,Ih,Nh),Vp(Mh,4,`element`,kh,Ih,Ph),Vp(Mh,4,`feedback`,Oh,Ih,Fh),zp(Mh,Ih);var Lh,Rh,zh,Bh,Vh,Hh,Uh,Wh,Gh,Kh,qh=class extends (zh=Fu,Rh=[J],Lh=[J],zh){constructor(e,t){var n=e,{element:r,effects:i=()=>[]}=n,a=Pp(n,[`element`,`effects`]);let{collisionDetector:o=vp}=a,s=e=>{let{manager:t,element:n}=this;if(!n||e===null){this.shape=void 0;return}if(!t)return;let r=new ap(n),i=q(()=>this.shape);return r&&i?.equals(r)?i:(this.shape=r,r)},c=$o(!1);super(Mp(jp({},a),{collisionDetector:o,effects:()=>[...i(),()=>{let{element:e,manager:t}=this;if(!t)return;let{dragOperation:n}=t,{source:r}=n;c.value=!!(r&&n.status.initialized&&e&&!this.disabled&&this.accepts(r))},()=>{let{element:e}=this;if(c.value&&e){let t=new bf(e,s);return()=>{t.disconnect(),this.shape=void 0}}},()=>{if(this.manager?.dragOperation.status.initialized)return()=>{this.shape=void 0}}]}),t),Wp(this,Gh),Wp(this,Vh,Bp(Bh,8,this)),Bp(Bh,11,this),Wp(this,Kh,Bp(Bh,12,this)),Bp(Bh,15,this),this.element=r,this.refreshShape=()=>s()}set element(e){Gp(this,Gh,e,Wh)}get element(){return this.proxy??$(this,Gh,Uh)}};Bh=Fp(zh),Vh=new WeakMap,Gh=new WeakSet,Kh=new WeakMap,Hh=Vp(Bh,20,`#element`,Rh,Gh,Vh),Uh=Hh.get,Wh=Hh.set,Vp(Bh,4,`proxy`,Lh,qh,Kh),zp(Bh,qh);var Jh=Object.create,Yh=Object.defineProperty,Xh=Object.defineProperties,Zh=Object.getOwnPropertyDescriptor,Qh=Object.getOwnPropertyDescriptors,$h=Object.getOwnPropertySymbols,eg=Object.prototype.hasOwnProperty,tg=Object.prototype.propertyIsEnumerable,ng=(e,t)=>(t=Symbol[e])?t:Symbol.for(`Symbol.`+e),rg=e=>{throw TypeError(e)},ig=(e,t,n)=>t in e?Yh(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,ag=(e,t)=>{for(var n in t||={})eg.call(t,n)&&ig(e,n,t[n]);if($h)for(var n of $h(t))tg.call(t,n)&&ig(e,n,t[n]);return e},og=(e,t)=>Xh(e,Qh(t)),sg=(e,t)=>{var n={};for(var r in e)eg.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(e!=null&&$h)for(var r of $h(e))t.indexOf(r)<0&&tg.call(e,r)&&(n[r]=e[r]);return n},cg=e=>[,,,Jh(null)],lg=[`class`,`method`,`getter`,`setter`,`accessor`,`field`,`value`,`get`,`set`],ug=e=>e!==void 0&&typeof e!=`function`?rg(`Function expected`):e,dg=(e,t,n,r,i)=>({kind:lg[e],name:t,metadata:r,addInitializer:e=>n._?rg(`Already initialized`):i.push(ug(e||null))}),fg=(e,t)=>ig(t,ng(`metadata`),e[3]),pg=(e,t,n,r)=>{for(var i=0,a=e[t>>1],o=a&&a.length;i<o;i++)t&1?a[i].call(n):r=a[i].call(n,r);return r},mg=(e,t,n,r,i,a)=>{for(var o,s,c,l,u,d=t&7,f=!1,p=!1,m=e.length+1,h=lg[d+5],g=e[m-1]=[],_=e[m]||(e[m]=[]),v=(i=i.prototype,Zh({get[n](){return gg(this,a)},set[n](e){return vg(this,a,e)}},n)),y=r.length-1;y>=0;y--)l=dg(d,n,c={},e[3],_),l.static=f,l.private=p,u=l.access={has:e=>n in e},u.get=e=>e[n],u.set=(e,t)=>e[n]=t,s=(0,r[y])({get:v.get,set:v.set},l),c._=1,s===void 0?ug(s)&&(v[h]=s):typeof s!=`object`||!s?rg(`Object expected`):(ug(o=s.get)&&(v.get=o),ug(o=s.set)&&(v.set=o),ug(o=s.init)&&g.unshift(o));return v&&Yh(i,n,v),i},hg=(e,t,n)=>t.has(e)||rg(`Cannot `+n),gg=(e,t,n)=>(hg(e,t,`read from private field`),t.get(e)),_g=(e,t,n)=>t.has(e)?rg(`Cannot add the same private member more than once`):t instanceof WeakSet?t.add(e):t.set(e,n),vg=(e,t,n,r)=>(hg(e,t,`write to private field`),t.set(e,n),n);function yg(e){return e instanceof $g||e instanceof Qg}var bg=10,xg=class extends Dl{constructor(e){super(e);let t=ls(()=>{let{dragOperation:t}=e;if(up(t.activatorEvent)&&yg(t.source)&&t.status.initialized){let t=e.registry.plugins.get(Xm);if(t)return t.disable(),()=>t.enable()}}),n=e.monitor.addEventListener(`dragmove`,(e,t)=>{queueMicrotask(()=>{if(this.disabled||e.defaultPrevented||!e.nativeEvent)return;let{dragOperation:n}=t;if(!up(e.nativeEvent)||!yg(n.source)||!n.shape)return;let{actions:r,collisionObserver:i,registry:a}=t,{by:o}=e;if(!o)return;let s=Sg(o),{source:c,target:l}=n,{center:u}=n.shape.current,d=[],f=[];Ko(()=>{for(let e of a.droppables){let{id:t}=e;if(!e.accepts(c)||t===l?.id&&yg(e)||!e.element)continue;let n=e.shape,r=new ap(e.element,{getBoundingClientRect:e=>Od(e,void 0,.2)});!r.height||!r.width||(s==`down`&&u.y+bg<r.center.y||s==`up`&&u.y-bg>r.center.y||s==`left`&&u.x-bg>r.center.x||s==`right`&&u.x+bg<r.center.x)&&(d.push(e),e.shape=r,f.push(()=>e.shape=n))}}),e.preventDefault(),i.disable();let p=i.computeCollisions(d,yp);Ko(()=>f.forEach(e=>e()));let[m]=p;if(!m)return;let{id:h}=m,{index:g,group:_}=c.sortable;r.setDropTarget(h).then(()=>{let{source:e,target:t,shape:a}=n;if(!e||!yg(e)||!a)return;let{index:o,group:s,target:c}=e.sortable,l=g!==o||_!==s,u=l?c:t?.element;if(!u)return;Yf(u);let d=new ap(u);if(!d)return;let f=Rc.delta(d,Rc.from(a.current.boundingRectangle),e.alignment);r.move({by:f}),l?r.setDropTarget(e.id).then(()=>i.enable()):i.enable()})})});this.destroy=()=>{n(),t()}}};function Sg(e){let{x:t,y:n}=e;if(t>0)return`right`;if(t<0)return`left`;if(n>0)return`down`;if(n<0)return`up`}var Cg=Object.defineProperty,wg=Object.defineProperties,Tg=Object.getOwnPropertyDescriptors,Eg=Object.getOwnPropertySymbols,Dg=Object.prototype.hasOwnProperty,Og=Object.prototype.propertyIsEnumerable,kg=(e,t,n)=>t in e?Cg(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,Ag=(e,t)=>{for(var n in t||={})Dg.call(t,n)&&kg(e,n,t[n]);if(Eg)for(var n of Eg(t))Og.call(t,n)&&kg(e,n,t[n]);return e},jg=(e,t)=>wg(e,Tg(t));function Mg(e,t,n){if(t===n)return e;let r=e.slice();return r.splice(n,0,r.splice(t,1)[0]),r}function Ng(e,t,n){let{source:r,target:i,canceled:a}=t.operation;if(!r||!i||a)return`preventDefault`in t&&t.preventDefault(),e;let o=(e,t)=>e===t||typeof e==`object`&&`id`in e&&e.id===t;if(Array.isArray(e)){let t=e.findIndex(e=>o(e,r.id)),s=e.findIndex(e=>o(e,i.id));if(t===-1||s===-1)return e;if(!a&&`index`in r&&typeof r.index==`number`){let i=r.index;if(i!==t)return n(e,t,i)}return n(e,t,s)}let s=Object.entries(e),c=-1,l,u=-1,d;for(let[e,t]of s)if(c===-1&&(c=t.findIndex(e=>o(e,r.id)),c!==-1&&(l=e)),u===-1&&(u=t.findIndex(e=>o(e,i.id)),u!==-1&&(d=e)),c!==-1&&u!==-1)break;if(!r.manager)return e;let{dragOperation:f}=r.manager,p=f.shape?.current.center??f.position.current;if(d==null&&i.id in e){let t=i.shape&&p.y>i.shape.center.y?e[i.id].length:0;d=i.id,u=t}if(l==null||d==null||l===d&&c===u)return`preventDefault`in t&&t.preventDefault(),e;if(l===d)return jg(Ag({},e),{[l]:n(e[l],c,u)});let m=i.shape&&Math.round(p.y)>Math.round(i.shape.center.y)?1:0,h=e[l][c];return jg(Ag({},e),{[l]:[...e[l].slice(0,c),...e[l].slice(c+1)],[d]:[...e[d].slice(0,u+m),h,...e[d].slice(u+m)]})}function Pg(e,t){return Ng(e,t,Mg)}var Fg=`__default__`,Ig=class extends Dl{constructor(e){super(e);let t=()=>{let t=new Map;for(let n of e.registry.droppables)if(n instanceof $g){let{sortable:e}=n,{group:r}=e,i=t.get(r);i||(i=new Set,t.set(r,i)),i.add(e)}for(let[e,n]of t)t.set(e,new Set(zg(n)));return t},n=[e.monitor.addEventListener(`dragover`,(e,n)=>{if(this.disabled)return;let{dragOperation:r}=n,{source:i,target:a}=r;if(!yg(i)||!yg(a)||i.sortable===a.sortable)return;let o=t(),s=i.sortable.group===a.sortable.group,c=o.get(i.sortable.group),l=s?c:o.get(a.sortable.group);!c||!l||queueMicrotask(()=>{e.defaultPrevented||n.renderer.rendering.then(()=>{let r=t();for(let[e,t]of o.entries()){let n=Array.from(t).entries();for(let[t,i]of n)if(i.index!==t||i.group!==e||!r.get(e)?.has(i))return}let u=i.sortable.element,d=a.sortable.element;if(!d||!u||!s&&a.id===i.sortable.group)return;let f=zg(c),p=s?f:zg(l),m=i.sortable.group??Fg,h=a.sortable.group??Fg,g={[m]:f,[h]:p},_=Pg(g,e);if(g===_)return;let v=_[h].indexOf(i.sortable),y=_[h].indexOf(a.sortable);n.collisionObserver.disable(),Lg(u,v,d,y),Ko(()=>{for(let[e,t]of _[m].entries())t.index=e;if(!s)for(let[e,t]of _[h].entries())t.group=a.sortable.group,t.index=e}),n.actions.setDropTarget(i.id).then(()=>n.collisionObserver.enable())})})}),e.monitor.addEventListener(`dragend`,(e,n)=>{if(!e.canceled)return;let{dragOperation:r}=n,{source:i}=r;yg(i)&&(i.sortable.initialIndex===i.sortable.index&&i.sortable.initialGroup===i.sortable.group||queueMicrotask(()=>{let e=t(),r=e.get(i.sortable.initialGroup);r&&n.renderer.rendering.then(()=>{for(let[t,n]of e.entries()){let e=Array.from(n).entries();for(let[n,r]of e)if(r.index!==n||r.group!==t)return}let t=zg(r),n=i.sortable.element,a=t[i.sortable.initialIndex],o=a?.element;!a||!o||!n||(Lg(n,a.index,o,i.index),Ko(()=>{for(let[t,n]of e.entries()){let e=Array.from(n).values();for(let t of e)t.index=t.initialIndex,t.group=t.initialGroup}}))})}))})];this.destroy=()=>{for(let e of n)e()}}};function Lg(e,t,n,r){let i=r<t?`afterend`:`beforebegin`;n.insertAdjacentElement(i,e)}function Rg(e,t){return e.index-t.index}function zg(e){return Array.from(e).sort(Rg)}var Bg=[xg,Ig],Vg={duration:250,easing:`cubic-bezier(0.25, 1, 0.5, 1)`,idle:!1},Hg=new dc,Ug,Wg=[J],Gg,Kg,qg,Jg,Yg,Xg;Ug=[J];var Zg=class{constructor(e,t){_g(this,Kg,pg(Gg,8,this)),pg(Gg,11,this),_g(this,qg),_g(this,Jg),_g(this,Yg,pg(Gg,12,this)),pg(Gg,15,this),_g(this,Xg),this.register=()=>(Ko(()=>{var e,t;(e=this.manager)==null||e.registry.register(this.droppable),(t=this.manager)==null||t.registry.register(this.draggable)}),()=>this.unregister()),this.unregister=()=>{Ko(()=>{var e,t;(e=this.manager)==null||e.registry.unregister(this.droppable),(t=this.manager)==null||t.registry.unregister(this.draggable)})},this.destroy=()=>{Ko(()=>{this.droppable.destroy(),this.draggable.destroy()})};var n=e,{effects:r=()=>[],group:i,index:a,sensors:o,type:s,transition:c=Vg,plugins:l=Bg}=n,u=sg(n,[`effects`,`group`,`index`,`sensors`,`type`,`transition`,`plugins`]);this.droppable=new $g(u,t,this),this.draggable=new Qg(og(ag({},u),{effects:()=>[()=>{let e=this.manager?.dragOperation.status;e?.initializing&&this.id===this.manager?.dragOperation.source?.id&&Hg.clear(this.manager),e?.dragging&&Hg.set(this.manager,this.id,q(()=>({initialIndex:this.index,initialGroup:this.group})))},()=>{let{index:e,group:t,manager:n}=this,r=gg(this,Jg),i=gg(this,qg);(e!==r||t!==i)&&(vg(this,Jg,e),vg(this,qg,t),this.animate())},()=>{let{target:e}=this,{feedback:t,isDragSource:n}=this.draggable;t==`move`&&n&&(this.droppable.disabled=!e)},()=>{let{manager:e}=this;for(let t of l)e?.registry.register(t)},...r()],type:s,sensors:o}),t,this),vg(this,Xg,u.element),this.manager=t,this.index=a,vg(this,Jg,a),this.group=i,vg(this,qg,i),this.type=s,this.transition=c}get initialIndex(){return Hg.get(this.manager,this.id)?.initialIndex??this.index}get initialGroup(){return Hg.get(this.manager,this.id)?.initialGroup??this.group}animate(){q(()=>{let{manager:e,transition:t}=this,{shape:n}=this.droppable;if(!e)return;let{idle:r}=e.dragOperation.status;!n||!t||r&&!t.idle||e.renderer.rendering.then(()=>{let{element:r}=this;if(!r)return;let i=this.refreshShape();if(!i)return;let a={x:n.boundingRectangle.left-i.boundingRectangle.left,y:n.boundingRectangle.top-i.boundingRectangle.top},{translate:o}=jf(r),s=$f(r,o,!1),c=$f(r,o);(a.x||a.y)&&Qf({element:r,keyframes:{translate:[`${s.x+a.x}px ${s.y+a.y}px ${s.z}`,`${c.x}px ${c.y}px ${c.z}`]},options:t}).then(()=>{e.dragOperation.status.dragging||(this.droppable.shape=void 0)})})})}get manager(){return this.draggable.manager}set manager(e){Ko(()=>{this.draggable.manager=e,this.droppable.manager=e})}set element(e){Ko(()=>{let t=gg(this,Xg),n=this.droppable.element,r=this.draggable.element;(!n||n===t)&&(this.droppable.element=e),(!r||r===t)&&(this.draggable.element=e),vg(this,Xg,e)})}get element(){let e=gg(this,Xg);if(e)return Rd.get(e)??e??this.droppable.element}set target(e){this.droppable.element=e}get target(){return this.droppable.element}set source(e){this.draggable.element=e}get source(){return this.draggable.element}get disabled(){return this.draggable.disabled&&this.droppable.disabled}set feedback(e){this.draggable.feedback=e}set disabled(e){Ko(()=>{this.droppable.disabled=e,this.draggable.disabled=e})}set data(e){Ko(()=>{this.droppable.data=e,this.draggable.data=e})}set handle(e){this.draggable.handle=e}set id(e){Ko(()=>{this.droppable.id=e,this.draggable.id=e})}get id(){return this.droppable.id}set sensors(e){this.draggable.sensors=e}set modifiers(e){this.draggable.modifiers=e}set collisionPriority(e){this.droppable.collisionPriority=e}set collisionDetector(e){this.droppable.collisionDetector=e??vp}set alignment(e){this.draggable.alignment=e}get alignment(){return this.draggable.alignment}set type(e){Ko(()=>{this.droppable.type=e,this.draggable.type=e})}get type(){return this.draggable.type}set accept(e){this.droppable.accept=e}get accept(){return this.droppable.accept}get isDropTarget(){return this.droppable.isDropTarget}get isDragSource(){return this.draggable.isDragSource}get isDragging(){return this.draggable.isDragging}get isDropping(){return this.draggable.isDropping}get status(){return this.draggable.status}refreshShape(){return this.droppable.refreshShape()}accepts(e){return this.droppable.accepts(e)}};Gg=cg(),Kg=new WeakMap,qg=new WeakMap,Jg=new WeakMap,Yg=new WeakMap,Xg=new WeakMap,mg(Gg,4,`index`,Wg,Zg,Kg),mg(Gg,4,`group`,Ug,Zg,Yg),fg(Gg,Zg);var Qg=class extends Ih{constructor(e,t,n){super(e,t),this.sortable=n}get index(){return this.sortable.index}},$g=class extends qh{constructor(e,t,n){super(e,t),this.sortable=n}},e_=Object.defineProperty,t_=Object.defineProperties,n_=Object.getOwnPropertyDescriptors,r_=Object.getOwnPropertySymbols,i_=Object.prototype.hasOwnProperty,a_=Object.prototype.propertyIsEnumerable,o_=(e,t,n)=>t in e?e_(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,s_=(e,t)=>{for(var n in t||={})i_.call(t,n)&&o_(e,n,t[n]);if(r_)for(var n of r_(t))a_.call(t,n)&&o_(e,n,t[n]);return e},c_=(e,t)=>t_(e,n_(t)),l_=class extends Bu{apply({transform:e}){if(!this.options)return e;let{axis:t,value:n}=this.options;return c_(s_({},e),{[t]:n})}};l_.configure=xl(l_);var u_=l_,d_=u_.configure({axis:`x`,value:0});u_.configure({axis:`y`,value:0});var f_=class extends Bu{apply({transform:e}){let{size:t=20}=this.options??{},n=typeof t==`number`?t:t.x,r=typeof t==`number`?t:t.y;return c_(s_({},e),{x:Math.ceil(e.x/n)*n,y:Math.ceil(e.y/r)*r})}};f_.configure=xl(f_);var p_=t({default:()=>m_}),m_=class extends nt{constructor(...e){super(...e),this.sortables=[],this.onDragEnd=()=>this.updateOrder()}static{this.targets=[`container`,`orderInput`]}connect(){this.manager=new Dh({modifiers:[d_]}),this.manager.monitor.addEventListener(`dragend`,this.onDragEnd),this.refreshSortables()}disconnect(){this.teardownSortables(),this.manager?.monitor.removeEventListener(`dragend`,this.onDragEnd),this.manager?.destroy?.(),this.manager=void 0}containerTargetConnected(){this.refreshSortables()}containerTargetDisconnected(){this.refreshSortables()}refreshSortables(){this.teardownSortables(),this.manager&&this.containerTargets.forEach((e,t)=>{Array.from(e.querySelectorAll(`[data-id]`)).forEach((e,n)=>{let r=new Zg({id:e.dataset.id||hp(`item`),index:n,group:t,element:e,handle:e.querySelector(`[data-handle]`)||void 0},this.manager);this.sortables.push(r)})})}teardownSortables(){this.sortables.forEach(e=>e.destroy()),this.sortables=[]}updateOrder(){if(!this.hasOrderInputTarget){this.dispatch(`update`);return}let e=this.containerTargets.flatMap((e,t)=>Array.from(e.querySelectorAll(`[data-id]`)).map(e=>({id:e.dataset.id,listIndex:t})));e&&(this.orderInputTarget.value=JSON.stringify(e),this.dispatch(`update`))}};window.Stimulus.load(Ce({\"./controllers/color-picker-controller.ts\":rt,\"./controllers/filter-input-controller.ts\":yt,\"./controllers/form-controller.ts\":St,\"./controllers/icon-picker-controller.ts\":wt,\"./controllers/incremental-search-controller.ts\":hn,\"./controllers/line-chart-controller.ts\":Ro,\"./controllers/permission-grid-controller.ts\":Bo,\"./controllers/slugger-controller.ts\":Ho,\"./controllers/sortable-controller.ts\":p_}))})();"
  },
  {
    "path": "resources/dist/emoji.js",
    "content": "(function(){function e(e){return e.replace(/(?:[_-])([a-z0-9])/g,(e,t)=>t.toUpperCase())}function t(t){return e(t.replace(/--/g,`-`).replace(/__/g,`_`))}function n(e){return e.charAt(0).toUpperCase()+e.slice(1)}function r(e){return e.replace(/([A-Z])/g,(e,t)=>`-${t.toLowerCase()}`)}function i(e){return e!=null}function a(e,t){return Object.prototype.hasOwnProperty.call(e,t)}function o(e,t){let n=c(e);return Array.from(n.reduce((e,n)=>(l(n,t).forEach(t=>e.add(t)),e),new Set))}function s(e,t){return c(e).reduce((e,n)=>(e.push(...u(n,t)),e),[])}function c(e){let t=[];for(;e;)t.push(e),e=Object.getPrototypeOf(e);return t.reverse()}function l(e,t){let n=e[t];return Array.isArray(n)?n:[]}function u(e,t){let n=e[t];return n?Object.keys(n).map(e=>[e,n[e]]):[]}typeof Object.getOwnPropertySymbols==`function`||Object.getOwnPropertyNames,(()=>{function e(e){function t(){return Reflect.construct(e,arguments,new.target)}return t.prototype=Object.create(e.prototype,{constructor:{value:t}}),Reflect.setPrototypeOf(t,e),t}function t(){let t=e(function(){this.a.call(this)});return t.prototype.a=function(){},new t}try{return t(),e}catch{return e=>class extends e{}}})(),Object.assign(Object.assign({enter:`Enter`,tab:`Tab`,esc:`Escape`,space:` `,up:`ArrowUp`,down:`ArrowDown`,left:`ArrowLeft`,right:`ArrowRight`,home:`Home`,end:`End`,page_up:`PageUp`,page_down:`PageDown`},d(`abcdefghijklmnopqrstuvwxyz`.split(``).map(e=>[e,e]))),d(`0123456789`.split(``).map(e=>[e,e])));function d(e){return e.reduce((e,[t,n])=>Object.assign(Object.assign({},e),{[t]:n}),{})}function f(e){return o(e,`classes`).reduce((e,t)=>Object.assign(e,p(t)),{})}function p(e){return{[`${e}Class`]:{get(){let{classes:t}=this;if(t.has(e))return t.get(e);{let n=t.getAttributeName(e);throw Error(`Missing attribute \"${n}\"`)}}},[`${e}Classes`]:{get(){return this.classes.getAll(e)}},[`has${n(e)}Class`]:{get(){return this.classes.has(e)}}}}function m(e){return o(e,`outlets`).reduce((e,t)=>Object.assign(e,ee(t)),{})}function h(e,t,n){return e.application.getControllerForElementAndIdentifier(t,n)}function g(e,t,n){let r=h(e,t,n);if(r||(e.application.router.proposeToConnectScopeForElementAndIdentifier(t,n),r=h(e,t,n),r))return r}function ee(e){let r=t(e);return{[`${r}Outlet`]:{get(){let t=this.outlets.find(e),n=this.outlets.getSelectorForOutletName(e);if(t){let n=g(this,t,e);if(n)return n;throw Error(`The provided outlet element is missing an outlet controller \"${e}\" instance for host controller \"${this.identifier}\"`)}throw Error(`Missing outlet element \"${e}\" for host controller \"${this.identifier}\". Stimulus couldn't find a matching outlet element using selector \"${n}\".`)}},[`${r}Outlets`]:{get(){let t=this.outlets.findAll(e);return t.length>0?t.map(t=>{let n=g(this,t,e);if(n)return n;console.warn(`The provided outlet element is missing an outlet controller \"${e}\" instance for host controller \"${this.identifier}\"`,t)}).filter(e=>e):[]}},[`${r}OutletElement`]:{get(){let t=this.outlets.find(e),n=this.outlets.getSelectorForOutletName(e);if(t)return t;throw Error(`Missing outlet element \"${e}\" for host controller \"${this.identifier}\". Stimulus couldn't find a matching outlet element using selector \"${n}\".`)}},[`${r}OutletElements`]:{get(){return this.outlets.findAll(e)}},[`has${n(r)}Outlet`]:{get(){return this.outlets.has(e)}}}}function te(e){return o(e,`targets`).reduce((e,t)=>Object.assign(e,ne(t)),{})}function ne(e){return{[`${e}Target`]:{get(){let t=this.targets.find(e);if(t)return t;throw Error(`Missing target element \"${e}\" for \"${this.identifier}\" controller`)}},[`${e}Targets`]:{get(){return this.targets.findAll(e)}},[`has${n(e)}Target`]:{get(){return this.targets.has(e)}}}}function _(e){let t=s(e,`values`);return t.reduce((e,t)=>Object.assign(e,re(t)),{valueDescriptorMap:{get(){return t.reduce((e,t)=>{let n=ie(t,this.identifier),r=this.data.getAttributeNameForKey(n.key);return Object.assign(e,{[r]:n})},{})}}})}function re(e,t){let r=ie(e,t),{key:i,name:a,reader:o,writer:s}=r;return{[a]:{get(){let e=this.data.get(i);return e===null?r.defaultValue:o(e)},set(e){e===void 0?this.data.delete(i):this.data.set(i,s(e))}},[`has${n(a)}`]:{get(){return this.data.has(i)||r.hasCustomDefaultValue}}}}function ie([e,t],n){return x({controller:n,token:e,typeDefinition:t})}function v(e){switch(e){case Array:return`array`;case Boolean:return`boolean`;case Number:return`number`;case Object:return`object`;case String:return`string`}}function y(e){switch(typeof e){case`boolean`:return`boolean`;case`number`:return`number`;case`string`:return`string`}if(Array.isArray(e))return`array`;if(Object.prototype.toString.call(e)===`[object Object]`)return`object`}function ae(e){let{controller:t,token:n,typeObject:r}=e,a=i(r.type),o=i(r.default),s=a&&o,c=a&&!o,l=!a&&o,u=v(r.type),d=y(e.typeObject.default);if(c)return u;if(l)return d;if(u!==d){let e=t?`${t}.${n}`:n;throw Error(`The specified default value for the Stimulus Value \"${e}\" must match the defined type \"${u}\". The provided default value of \"${r.default}\" is of type \"${d}\".`)}if(s)return u}function oe(e){let{controller:t,token:n,typeDefinition:r}=e,i=ae({controller:t,token:n,typeObject:r}),a=y(r),o=v(r),s=i||a||o;if(s)return s;let c=t?`${t}.${r}`:n;throw Error(`Unknown value type \"${c}\" for \"${n}\" value`)}function b(e){let t=v(e);if(t)return S[t];let n=a(e,`default`),r=a(e,`type`),i=e;if(n)return i.default;if(r){let{type:e}=i,t=v(e);if(t)return S[t]}return e}function x(t){let{token:n,typeDefinition:i}=t,a=`${r(n)}-value`,o=oe(t);return{type:o,key:a,name:e(a),get defaultValue(){return b(i)},get hasCustomDefaultValue(){return y(i)!==void 0},reader:se[o],writer:C[o]||C.default}}let S={get array(){return[]},boolean:!1,number:0,get object(){return{}},string:``},se={array(e){let t=JSON.parse(e);if(!Array.isArray(t))throw TypeError(`expected value of type \"array\" but instead got value \"${e}\" of type \"${y(t)}\"`);return t},boolean(e){return!(e==`0`||String(e).toLowerCase()==`false`)},number(e){return Number(e.replace(/_/g,``))},object(e){let t=JSON.parse(e);if(typeof t!=`object`||!t||Array.isArray(t))throw TypeError(`expected value of type \"object\" but instead got value \"${e}\" of type \"${y(t)}\"`);return t},string(e){return e}},C={default:ce,array:w,object:w};function w(e){return JSON.stringify(e)}function ce(e){return`${e}`}var T=class{constructor(e){this.context=e}static get shouldLoad(){return!0}static afterLoad(e,t){}get application(){return this.context.application}get scope(){return this.context.scope}get element(){return this.scope.element}get identifier(){return this.scope.identifier}get targets(){return this.scope.targets}get outlets(){return this.scope.outlets}get classes(){return this.scope.classes}get data(){return this.scope.data}initialize(){}connect(){}disconnect(){}dispatch(e,{target:t=this.element,detail:n={},prefix:r=this.identifier,bubbles:i=!0,cancelable:a=!0}={}){let o=r?`${r}:${e}`:e,s=new CustomEvent(o,{detail:n,bubbles:i,cancelable:a});return t.dispatchEvent(s),s}};T.blessings=[f,te,_,m],T.targets=[],T.outlets=[],T.values={};function E(e){if(typeof e!=`string`||!e)throw Error(`expected a non-empty string, got: `+e)}function D(e){if(typeof e!=`number`)throw Error(`expected a number, got: `+e)}let O=`emoji`,k=`keyvalue`,A=`favorites`,j=`tokens`,M=`count`,N=`group-order`,P=`eTag`,le=`skinTone`,F=`readonly`,I=`readwrite`,ue=`skinUnicodes`;function de(e,t){let n=new Set,r=[];for(let i of e){let e=t(i);n.has(e)||(n.add(e),r.push(i))}return r}function fe(e){return de(e,e=>e.unicode)}function pe(e){function t(t,n,r){let i=n?e.createObjectStore(t,{keyPath:n}):e.createObjectStore(t);if(r)for(let[e,[t,n]]of Object.entries(r))i.createIndex(e,t,{multiEntry:n});return i}t(k),t(O,`unicode`,{[j]:[`tokens`,!0],[N]:[[`group`,`order`]],[ue]:[`skinUnicodes`,!0]}),t(A,void 0,{[M]:[``]})}let L={},R={},z={};function me(e,t,n){n.onerror=()=>t(n.error),n.onblocked=()=>t(Error(`IDB blocked`)),n.onsuccess=()=>e(n.result)}async function he(e){let t=await new Promise((t,n)=>{let r=indexedDB.open(e,1);L[e]=r,r.onupgradeneeded=e=>{e.oldVersion<1&&pe(r.result)},me(t,n,r)});return t.onclose=()=>V(e),t}function ge(e){return R[e]||(R[e]=he(e)),R[e]}function B(e,t,n,r){return new Promise((i,a)=>{let o=e.transaction(t,n,{durability:`relaxed`}),s=typeof t==`string`?o.objectStore(t):t.map(e=>o.objectStore(e)),c;r(s,o,e=>{c=e}),o.oncomplete=()=>i(c),o.onerror=()=>a(o.error)})}function V(e){let t=L[e],n=t&&t.result;if(n){n.close();let t=z[e];if(t)for(let e of t)e()}delete L[e],delete R[e],delete z[e]}function _e(e){return new Promise((t,n)=>{V(e),me(t,n,indexedDB.deleteDatabase(e))})}function ve(e,t){let n=z[e];n||=z[e]=[],n.push(t)}let ye=new Set(`:D.XD.:'D.O:).:X.:P.;P.XP.:L.:Z.:j.8D.XO.8).:B.:O.:S.:'o.Dx.X(.D:.:C.>0).:3.</3.<3.\\\\M/.:E.8#`.split(`.`));function H(e){return e.split(/[\\s_]+/).map(e=>!e.match(/\\w/)||ye.has(e)?e.toLowerCase():e.replace(/[)(:,]/g,``).replace(/’/g,`'`).toLowerCase()).filter(Boolean)}function be(e){return e.filter(Boolean).map(e=>e.toLowerCase()).filter(e=>e.length>=2)}function xe(e){return e.map(({annotation:e,emoticon:t,group:n,order:r,shortcodes:i,skins:a,tags:o,emoji:s,version:c})=>{let l={annotation:e,group:n,order:r,tags:o,tokens:[...new Set(be([...(i||[]).map(H).flat(),...(o||[]).map(H).flat(),...H(e),t]))].sort(),unicode:s,version:c};if(t&&(l.emoticon=t),i&&(l.shortcodes=i),a){l.skinTones=[],l.skinUnicodes=[],l.skinVersions=[];for(let{tone:e,emoji:t,version:n}of a)l.skinTones.push(e),l.skinUnicodes.push(t),l.skinVersions.push(n)}return l})}function Se(e,t,n,r){e[t](n).onsuccess=e=>r&&r(e.target.result)}function U(e,t,n){Se(e,`get`,t,n)}function Ce(e,t,n){Se(e,`getAll`,t,n)}function W(e){e.commit&&e.commit()}function we(e,t){let n=e[0];for(let r=1;r<e.length;r++){let i=e[r];t(n)>t(i)&&(n=i)}return n}function Te(e,t){let n=we(e,e=>e.length),r=[];for(let i of n)e.some(e=>e.findIndex(e=>t(e)===t(i))===-1)||r.push(i);return r}async function Ee(e){return!await G(e,k,`url`)}async function De(e,t,n){let[r,i]=await Promise.all([P,`url`].map(t=>G(e,k,t)));return r===n&&i===t}async function Oe(e,t){return B(e,O,F,(e,n,r)=>{let i,a=()=>{e.getAll(i&&IDBKeyRange.lowerBound(i,!0),50).onsuccess=e=>{let n=e.target.result;for(let e of n)if(i=e.unicode,t(e))return r(e);if(n.length<50)return r();a()}};a()})}async function ke(e,t,n,r){try{let i=xe(t);await B(e,[O,k],I,([e,t],a)=>{let o,s,c=0;function l(){++c===2&&u()}function u(){if(!(o===r&&s===n)){e.clear();for(let t of i)e.put(t);t.put(r,P),t.put(n,`url`),W(a)}}U(t,P,e=>{o=e,l()}),U(t,`url`,e=>{s=e,l()})})}finally{}}async function Ae(e,t){return B(e,O,F,(e,n,r)=>{let i=IDBKeyRange.bound([t,0],[t+1,0],!1,!0);Ce(e.index(N),i,r)})}async function je(e,t){let n=be(H(t));return n.length?B(e,O,F,(e,t,r)=>{let i=[],a=()=>{i.length===n.length&&o()},o=()=>{r(Te(i,e=>e.unicode).sort((e,t)=>e.order<t.order?-1:1))};for(let t=0;t<n.length;t++){let r=n[t],o=t===n.length-1?IDBKeyRange.bound(r,r+`￿`,!1,!0):IDBKeyRange.only(r);Ce(e.index(j),o,e=>{i.push(e),a()})}}):[]}async function Me(e,t){let n=await je(e,t);return n.length?n.filter(e=>(e.shortcodes||[]).map(e=>e.toLowerCase()).includes(t.toLowerCase()))[0]||null:await Oe(e,e=>(e.shortcodes||[]).includes(t.toLowerCase()))||null}async function Ne(e,t){return B(e,O,F,(e,n,r)=>U(e,t,n=>{if(n)return r(n);U(e.index(ue),t,e=>r(e||null))}))}function G(e,t,n){return B(e,t,F,(e,t,r)=>U(e,n,r))}function Pe(e,t,n,r){return B(e,t,I,(e,t)=>{e.put(r,n),W(t)})}function Fe(e,t){return B(e,A,I,(e,n)=>U(e,t,r=>{e.put((r||0)+1,t),W(n)}))}function Ie(e,t,n){return n===0?[]:B(e,[A,O],F,([e,r],i,a)=>{let o=[];e.index(M).openCursor(void 0,`prev`).onsuccess=e=>{let i=e.target.result;if(!i)return a(o);function s(e){if(o.push(e),o.length===n)return a(o);i.continue()}let c=i.primaryKey,l=t.byName(c);if(l)return s(l);U(r,c,e=>{if(e)return s(e);i.continue()})}})}function Le(e,t){let n=new Map;for(let r of e){let e=t(r);for(let t of e){let e=n;for(let n=0;n<t.length;n++){let r=t.charAt(n),i=e.get(r);i||(i=new Map,e.set(r,i)),e=i}let i=e.get(``);i||(i=[],e.set(``,i)),i.push(r)}}return(e,t)=>{let r=n;for(let t=0;t<e.length;t++){let n=e.charAt(t),i=r.get(n);if(i)r=i;else return[]}if(t)return r.get(``)||[];let i=[],a=[r];for(;a.length;){let e=[...a.shift().entries()].sort((e,t)=>e[0]<t[0]?-1:1);for(let[t,n]of e)t===``?i.push(...n):a.push(n)}return i}}let Re=[`name`,`url`];function ze(e){let t=e&&Array.isArray(e),n=t&&e.length&&(!e[0]||Re.some(t=>!(t in e[0])));if(!t||n)throw Error(`Custom emojis are in the wrong format`)}function Be(e){ze(e);let t=(e,t)=>e.name.toLowerCase()<t.name.toLowerCase()?-1:1,n=e.sort(t),r=Le(e,e=>{let t=new Set;if(e.shortcodes)for(let n of e.shortcodes)for(let e of H(n))t.add(e);return t}),i=e=>r(e,!0),a=e=>r(e,!1),o=e=>{let n=H(e);return Te(n.map((e,t)=>(t<n.length-1?i:a)(e)),e=>e.name).sort(t)},s=new Map,c=new Map;for(let t of e){c.set(t.name.toLowerCase(),t);for(let e of t.shortcodes||[])s.set(e.toLowerCase(),t)}return{all:n,search:o,byShortcode:e=>s.get(e.toLowerCase()),byName:e=>c.get(e.toLowerCase())}}let Ve=typeof wrappedJSObject<`u`;function K(e){if(!e)return e;if(Ve&&(e=structuredClone(e)),delete e.tokens,e.skinTones){let t=e.skinTones.length;e.skins=Array(t);for(let n=0;n<t;n++)e.skins[n]={tone:e.skinTones[n],unicode:e.skinUnicodes[n],version:e.skinVersions[n]};delete e.skinTones,delete e.skinUnicodes,delete e.skinVersions}return e}function He(e){e||console.warn(`emoji-picker-element is more efficient if the dataSource server exposes an ETag header.`)}let Ue=[`annotation`,`emoji`,`group`,`order`,`version`];function We(e){if(!e||!Array.isArray(e)||!e[0]||typeof e[0]!=`object`||Ue.some(t=>!(t in e[0])))throw Error(`Emoji data is in the wrong format`)}function Ge(e,t){if(Math.floor(e.status/100)!==2)throw Error(`Failed to fetch: `+t+`:  `+e.status)}async function Ke(e){let t=await fetch(e,{method:`HEAD`});Ge(t,e);let n=t.headers.get(`etag`);return He(n),n}async function q(e){let t=await fetch(e);Ge(t,e);let n=t.headers.get(`etag`);He(n);let r=await t.json();return We(r),[n,r]}function qe(e){for(var t=``,n=new Uint8Array(e),r=n.byteLength,i=-1;++i<r;)t+=String.fromCharCode(n[i]);return t}function Je(e){for(var t=e.length,n=new ArrayBuffer(t),r=new Uint8Array(n),i=-1;++i<t;)r[i]=e.charCodeAt(i);return n}async function Ye(e){let t=Je(JSON.stringify(e)),n=qe(await crypto.subtle.digest(`SHA-1`,t));return btoa(n)}async function Xe(e,t){let n,r=await Ke(t);if(!r){let e=await q(t);r=e[0],n=e[1],r||=await Ye(n)}await De(e,t,r)||(n||=(await q(t))[1],await ke(e,n,t,r))}async function Ze(e,t){let[n,r]=await q(t);n||=await Ye(r),await ke(e,r,t,n)}async function Qe(e,t){try{await Xe(e,t)}catch(e){if(e.name!==`InvalidStateError`)throw e}}var $e=class{constructor({dataSource:e=`https://cdn.jsdelivr.net/npm/emoji-picker-element-data@^1/en/emojibase/data.json`,locale:t=`en`,customEmoji:n=[]}={}){this.dataSource=e,this.locale=t,this._dbName=`emoji-picker-element-${this.locale}`,this._db=void 0,this._lazyUpdate=void 0,this._custom=Be(n),this._clear=this._clear.bind(this),this._ready=this._init()}async _init(){let e=this._db=await ge(this._dbName);ve(this._dbName,this._clear);let t=this.dataSource;await Ee(e)?await Ze(e,t):this._lazyUpdate=Qe(e,t)}async ready(){let e=async()=>(this._ready||=this._init(),this._ready);await e(),this._db||await e()}async getEmojiByGroup(e){return D(e),await this.ready(),fe(await Ae(this._db,e)).map(K)}async getEmojiBySearchQuery(e){E(e),await this.ready();let t=this._custom.search(e),n=fe(await je(this._db,e)).map(K);return[...t,...n]}async getEmojiByShortcode(e){return E(e),await this.ready(),this._custom.byShortcode(e)||K(await Me(this._db,e))}async getEmojiByUnicodeOrName(e){return E(e),await this.ready(),this._custom.byName(e)||K(await Ne(this._db,e))}async getPreferredSkinTone(){return await this.ready(),await G(this._db,k,le)||0}async setPreferredSkinTone(e){return D(e),await this.ready(),Pe(this._db,k,le,e)}async incrementFavoriteEmojiCount(e){return E(e),await this.ready(),Fe(this._db,e)}async getTopFavoriteEmoji(e){return D(e),await this.ready(),(await Ie(this._db,this._custom,e)).map(K)}set customEmoji(e){this._custom=Be(e)}get customEmoji(){return this._custom.all}async _shutdown(){await this.ready();try{await this._lazyUpdate}catch{}}_clear(){this._db=this._ready=this._lazyUpdate=void 0}async close(){await this._shutdown(),await V(this._dbName)}async delete(){await this._shutdown(),await _e(this._dbName)}};let J=[[-1,`✨`,`custom`],[0,`😀`,`smileys-emotion`],[1,`👋`,`people-body`],[3,`🐱`,`animals-nature`],[4,`🍎`,`food-drink`],[5,`🏠️`,`travel-places`],[6,`⚽`,`activities`],[7,`📝`,`objects`],[8,`⛔️`,`symbols`],[9,`🏁`,`flags`]].map(([e,t,n])=>({id:e,emoji:t,name:n})),et=J.slice(1),tt=typeof requestIdleCallback==`function`?requestIdleCallback:setTimeout;function nt(e){return e.unicode.includes(`‍`)}let rt={\"🫪\":17,\"🫩\":16,\"🫨\":15.1,\"🫠\":14,\"🥲\":13.1,\"🥻\":12.1,\"🥰\":11,\"🤩\":5,\"👱‍♀️\":4,\"🤣\":3,\"👁️‍🗨️\":2,\"😀\":1,\"😐️\":.7,\"😃\":.6},it=[`😊`,`😒`,`❤️`,`👍️`,`😍`,`😂`,`😭`,`☺️`,`😔`,`😩`,`😏`,`💕`,`🙌`,`😘`],at=`\"Twemoji Mozilla\",\"Apple Color Emoji\",\"Segoe UI Emoji\",\"Segoe UI Symbol\",\"Noto Color Emoji\",\"EmojiOne Color\",\"Android Emoji\",sans-serif`,ot=(e,t)=>e<t?-1:e>t?1:0,st=(e,t)=>{let n=document.createElement(`canvas`);n.width=n.height=1;let r=n.getContext(`2d`,{willReadFrequently:!0});return r.textBaseline=`top`,r.font=`100px ${at}`,r.fillStyle=t,r.scale(.01,.01),r.fillText(e,0,0),r.getImageData(0,0,1,1).data},ct=(e,t)=>{let n=[...e].join(`,`);return n===[...t].join(`,`)&&!n.startsWith(`0,0,0,`)};function lt(e){let t=st(e,`#000`),n=st(e,`#fff`);return t&&n&&ct(t,n)}function ut(){let e=Object.entries(rt);try{for(let[t,n]of e)if(lt(t))return n}catch{}return e[0][1]}let dt,ft=()=>(dt||=new Promise(e=>tt(()=>e(ut()))),dt),pt=new Map;function mt(e,t){if(t===0)return e;let n=e.indexOf(`‍`);return n===-1?(e.endsWith(`️`)&&(e=e.substring(0,e.length-1)),e+`�d83c`+String.fromCodePoint(57339+t-1)):e.substring(0,n)+String.fromCodePoint(127995+t-1)+e.substring(n)}function Y(e){e.preventDefault(),e.stopPropagation()}function ht(e,t,n){return t+=e?-1:1,t<0?t=n.length-1:t>=n.length&&(t=0),t}function gt(e,t){let n=new Set,r=[];for(let i of e){let e=t(i);n.has(e)||(n.add(e),r.push(i))}return r}function _t(e,t){let n=e=>{let n={};for(let r of e)typeof r.tone==`number`&&r.version<=t&&(n[r.tone]=r.unicode);return n};return e.map(({unicode:e,skins:t,shortcodes:r,url:i,name:a,category:o,annotation:s})=>({unicode:e,name:a,shortcodes:r,url:i,category:o,annotation:s,id:e||a,skins:t&&n(t)}))}let X=requestAnimationFrame,vt=typeof ResizeObserver==`function`;function yt(e,t,n){let r;vt?(r=new ResizeObserver(n),r.observe(e)):X(n),t.addEventListener(`abort`,()=>{r&&r.disconnect()})}function bt(e){{let t=document.createRange();return t.selectNode(e.firstChild),t.getBoundingClientRect().width}}let xt;function St(e,t,n){let r=!0;for(let i of e){let e=n(i);if(!e)continue;let a=bt(e);xt===void 0&&(xt=bt(t));let o=a/1.8<xt;pt.set(i.unicode,o),o||(r=!1)}return r}function Ct(e){return gt(e,e=>e)}function wt(e){e&&(e.scrollTop=0)}function Z(e,t,n){let r=e.get(t);return r||(r=n(),e.set(t,r)),r}function Tt(e){return``+e}function Et(e){let t=document.createElement(`template`);return t.innerHTML=e,t}let Dt=new WeakMap,Ot=new WeakMap,kt=Symbol(`un-keyed`),At=`replaceChildren`in Element.prototype;function jt(e,t){At?e.replaceChildren(...t):(e.innerHTML=``,e.append(...t))}function Mt(e,t){let n=e.firstChild,r=0;for(;n;){if(t[r]!==n)return!0;n=n.nextSibling,r++}return r!==t.length}function Nt(e,t){let{targetNode:n}=t,{targetParentNode:r}=t,i=!1;r?i=Mt(r,e):(i=!0,t.targetNode=void 0,t.targetParentNode=r=n.parentNode),i&&jt(r,e)}function Pt(e,t){for(let n of t){let{targetNode:t,currentExpression:r,binding:{expressionIndex:i,attributeName:a,attributeValuePre:o,attributeValuePost:s}}=n,c=e[i];if(r!==c)if(n.currentExpression=c,a)if(c===null)t.removeAttribute(a);else{let e=o+Tt(c)+s;t.setAttribute(a,e)}else{let e;Array.isArray(c)?Nt(c,n):c instanceof Element?(e=c,t.replaceWith(e)):t.nodeValue=Tt(c),e&&(n.targetNode=e)}}}function Ft(e){let t=``,n=!1,r=!1,i=-1,a=new Map,o=[],s=0;for(let c=0,l=e.length;c<l;c++){let u=e[c];if(t+=u.slice(s),c===l-1)break;for(let e=0;e<u.length;e++)switch(u.charAt(e)){case`<`:u.charAt(e+1)===`/`?o.pop():(n=!0,o.push(++i));break;case`>`:n=!1,r=!1;break;case`=`:r=!0;break}let d=o[o.length-1],f=Z(a,d,()=>[]),p,m,h;if(r){let n=/(\\S+)=\"?([^\"=]*)$/.exec(u);p=n[1],m=n[2];let r=/^([^\">]*)(\"?)/.exec(e[c+1]);h=r[1],t=t.slice(0,-1*n[0].length),s=r[0].length}else s=0;let g={attributeName:p,attributeValuePre:m,attributeValuePost:h,expressionIndex:c};f.push(g),!n&&!r&&(t+=` `)}return{template:Et(t),elementsToBindings:a}}function It(e,t,n){for(let r=0;r<e.length;r++){let i=e[r],a={binding:i,targetNode:i.attributeName?t:t.firstChild,targetParentNode:void 0,currentExpression:void 0};n.push(a)}}function Lt(e,t){let n=[],r;if(t.size===1&&(r=t.get(0)))It(r,e,n);else{let r=document.createTreeWalker(e,NodeFilter.SHOW_ELEMENT),i=e,a=-1;do{let e=t.get(++a);e&&It(e,i,n)}while(i=r.nextNode())}return n}function Rt(e){let{template:t,elementsToBindings:n}=Z(Dt,e,()=>Ft(e)),r=t.cloneNode(!0).content.firstElementChild,i=Lt(r,n);return function(e){return Pt(e,i),r}}function zt(e){let t=Z(Ot,e,()=>new Map),n=kt;function r(e,...r){return Z(Z(t,e,()=>new Map),n,()=>Rt(e))(r)}function i(e,t,r){return e.map((e,i)=>{let a=n;n=r(e);try{return t(e,i)}finally{n=a}})}return{map:i,html:r}}function Bt(e,t,n,r,i,a,o,s,c){let{labelWithSkin:l,titleForEmoji:u,unicodeWithSkin:d}=n,{html:f,map:p}=zt(t);function m(e,n,r){return p(e,(e,i)=>f`<button role=\"${n?`option`:`menuitem`}\" aria-selected=\"${n?i===t.activeSearchItem:null}\" aria-label=\"${l(e,t.currentSkinTone)}\" title=\"${u(e)}\" class=\"${`emoji`+(n&&i===t.activeSearchItem?` active`:``)+(e.unicode?``:` custom-emoji`)}\" id=\"${`${r}-${e.id}`}\" style=\"${e.unicode?null:`--custom-emoji-background: url(${JSON.stringify(e.url)})`}\">${e.unicode?d(e,t.currentSkinTone):``}</button>`,e=>`${r}-${e.id}`)}let h=f`<section data-ref=\"rootElement\" class=\"picker\" aria-label=\"${t.i18n.regionLabel}\" style=\"${t.pickerStyle||``}\"><div class=\"pad-top\"></div><div class=\"search-row\"><div class=\"search-wrapper\"><input id=\"search\" class=\"search\" type=\"search\" role=\"combobox\" enterkeyhint=\"search\" placeholder=\"${t.i18n.searchLabel}\" autocapitalize=\"none\" autocomplete=\"off\" spellcheck=\"true\" aria-expanded=\"${!!(t.searchMode&&t.currentEmojis.length)}\" aria-controls=\"search-results\" aria-describedby=\"search-description\" aria-autocomplete=\"list\" aria-activedescendant=\"${t.activeSearchItemId?`emo-${t.activeSearchItemId}`:null}\" data-ref=\"searchElement\" data-on-input=\"onSearchInput\" data-on-keydown=\"onSearchKeydown\"><label class=\"sr-only\" for=\"search\">${t.i18n.searchLabel}</label> <span id=\"search-description\" class=\"sr-only\">${t.i18n.searchDescription}</span></div><div class=\"skintone-button-wrapper ${t.skinTonePickerExpandedAfterAnimation?`expanded`:``}\"><button id=\"skintone-button\" class=\"emoji ${t.skinTonePickerExpanded?`hide-focus`:``}\" aria-label=\"${t.skinToneButtonLabel}\" title=\"${t.skinToneButtonLabel}\" aria-describedby=\"skintone-description\" aria-haspopup=\"listbox\" aria-expanded=\"${t.skinTonePickerExpanded}\" aria-controls=\"skintone-list\" data-on-click=\"onClickSkinToneButton\">${t.skinToneButtonText||``}</button></div><span id=\"skintone-description\" class=\"sr-only\">${t.i18n.skinToneDescription}</span><div data-ref=\"skinToneDropdown\" id=\"skintone-list\" class=\"skintone-list hide-focus ${t.skinTonePickerExpanded?``:`hidden no-animate`}\" style=\"transform:translateY(${t.skinTonePickerExpanded?0:`calc(-1 * var(--num-skintones) * var(--total-emoji-size))`})\" role=\"listbox\" aria-label=\"${t.i18n.skinTonesLabel}\" aria-activedescendant=\"skintone-${t.activeSkinTone}\" aria-hidden=\"${!t.skinTonePickerExpanded}\" tabIndex=\"-1\" data-on-focusout=\"onSkinToneOptionsFocusOut\" data-on-click=\"onSkinToneOptionsClick\" data-on-keydown=\"onSkinToneOptionsKeydown\" data-on-keyup=\"onSkinToneOptionsKeyup\">${p(t.skinTones,(e,n)=>f`<div id=\"skintone-${n}\" class=\"emoji ${n===t.activeSkinTone?`active`:``}\" aria-selected=\"${n===t.activeSkinTone}\" role=\"option\" title=\"${t.i18n.skinTones[n]}\" aria-label=\"${t.i18n.skinTones[n]}\">${e}</div>`,e=>e)}</div></div><div class=\"nav\" role=\"tablist\" style=\"grid-template-columns:repeat(${t.groups.length},1fr)\" aria-label=\"${t.i18n.categoriesLabel}\" data-on-keydown=\"onNavKeydown\" data-on-click=\"onNavClick\">${p(t.groups,e=>f`<button role=\"tab\" class=\"nav-button\" aria-controls=\"tab-${e.id}\" aria-label=\"${t.i18n.categories[e.name]}\" aria-selected=\"${!t.searchMode&&t.currentGroup.id===e.id}\" title=\"${t.i18n.categories[e.name]}\" data-group-id=\"${e.id}\"><div class=\"nav-emoji emoji\">${e.emoji}</div></button>`,e=>e.id)}</div><div class=\"indicator-wrapper\"><div class=\"indicator\" style=\"transform:translateX(${(t.isRtl?-1:1)*t.currentGroupIndex*100}%)\"></div></div><div class=\"message ${t.message?``:`gone`}\" role=\"alert\" aria-live=\"polite\">${t.message||``}</div><div data-ref=\"tabpanelElement\" class=\"tabpanel ${!t.databaseLoaded||t.message?`gone`:``}\" role=\"${t.searchMode?`region`:`tabpanel`}\" aria-label=\"${t.searchMode?t.i18n.searchResultsLabel:t.i18n.categories[t.currentGroup.name]}\" id=\"${t.searchMode?null:`tab-${t.currentGroup.id}`}\" tabIndex=\"0\" data-on-click=\"onEmojiClick\"><div data-action=\"calculateEmojiGridStyle\">${p(t.currentEmojisWithCategories,(e,n)=>f`<div><div id=\"menu-label-${n}\" class=\"category ${t.currentEmojisWithCategories.length===1&&t.currentEmojisWithCategories[0].category===``?`gone`:``}\" aria-hidden=\"true\">${t.searchMode?t.i18n.searchResultsLabel:e.category?e.category:t.currentEmojisWithCategories.length>1?t.i18n.categories.custom:t.i18n.categories[t.currentGroup.name]}</div><div class=\"emoji-menu ${n!==0&&!t.searchMode&&t.currentGroup.id===-1?`visibility-auto`:``}\" style=\"${`--num-rows: ${Math.ceil(e.emojis.length/t.numColumns)}`}\" data-action=\"updateOnIntersection\" role=\"${t.searchMode?`listbox`:`menu`}\" aria-labelledby=\"menu-label-${n}\" id=\"${t.searchMode?`search-results`:null}\">${m(e.emojis,t.searchMode,`emo`)}</div></div>`,e=>e.category)}</div></div><div class=\"favorites onscreen emoji-menu ${t.message?`gone`:``}\" role=\"menu\" aria-label=\"${t.i18n.favoritesLabel}\" data-on-click=\"onEmojiClick\">${m(t.currentFavorites,!1,`fav`)}</div><button data-ref=\"baselineEmoji\" aria-hidden=\"true\" tabindex=\"-1\" class=\"abs-pos hidden emoji baseline-emoji\">😀</button></section>`,g=(t,n)=>{for(let r of e.querySelectorAll(`[${t}]`))n(r,r.getAttribute(t))};if(c){e.appendChild(h);for(let e of[`click`,`focusout`,`input`,`keydown`,`keyup`])g(`data-on-${e}`,(t,n)=>{t.addEventListener(e,r[n])});g(`data-ref`,(e,t)=>{a[t]=e}),o.addEventListener(`abort`,()=>{e.removeChild(h)})}g(`data-action`,(e,t)=>{let n=s.get(t);n||s.set(t,n=new WeakSet),n.has(e)||(n.add(e),i[t](e))})}let Q=typeof queueMicrotask==`function`?queueMicrotask:e=>Promise.resolve().then(e);function Vt(e){let t=!1,n,r=new Map,i=new Set,a,o=()=>{if(t)return;let e=[...i];i.clear();try{for(let t of e)t()}finally{a=!1,i.size&&(a=!0,Q(o))}},s=new Proxy({},{get(e,t){if(n){let e=r.get(t);e||(e=new Set,r.set(t,e)),e.add(n)}return e[t]},set(e,t,n){if(e[t]!==n){e[t]=n;let s=r.get(t);if(s){for(let e of s)i.add(e);a||(a=!0,Q(o))}}return!0}});return e.addEventListener(`abort`,()=>{t=!0}),{state:s,createEffect:e=>{let t=()=>{let r=n;n=t;try{return e()}finally{n=r}};return t()}}}function Ht(e,t,n){if(e.length!==t.length)return!1;for(let r=0;r<e.length;r++)if(!n(e[r],t[r]))return!1;return!0}let Ut=new WeakMap;function Wt(e,t,n){{let r=e.closest(`.tabpanel`),i=Ut.get(r);i||(i=new IntersectionObserver(n,{root:r,rootMargin:`50% 0px 50% 0px`,threshold:0}),Ut.set(r,i),t.addEventListener(`abort`,()=>{i.disconnect()})),i.observe(e)}}let Gt=[],{assign:$}=Object;function Kt(e,t){let n={},r=new AbortController,i=r.signal,{state:a,createEffect:o}=Vt(i),s=new Map;$(a,{skinToneEmoji:void 0,i18n:void 0,database:void 0,customEmoji:void 0,customCategorySorting:void 0,emojiVersion:void 0}),$(a,t),$(a,{initialLoad:!0,currentEmojis:[],currentEmojisWithCategories:[],rawSearchText:``,searchText:``,searchMode:!1,activeSearchItem:-1,message:void 0,skinTonePickerExpanded:!1,skinTonePickerExpandedAfterAnimation:!1,currentSkinTone:0,activeSkinTone:0,skinToneButtonText:void 0,pickerStyle:void 0,skinToneButtonLabel:``,skinTones:[],currentFavorites:[],defaultFavoriteEmojis:void 0,numColumns:8,isRtl:!1,currentGroupIndex:0,groups:et,databaseLoaded:!1,activeSearchItemId:void 0}),o(()=>{a.currentGroup!==a.groups[a.currentGroupIndex]&&(a.currentGroup=a.groups[a.currentGroupIndex])});let c=t=>{e.getElementById(t).focus()},l=t=>e.getElementById(`emo-${t.id}`),u=(e,t)=>{n.rootElement.dispatchEvent(new CustomEvent(e,{detail:t,bubbles:!0,composed:!0}))},d=(e,t)=>e.id===t.id,f=(e,t)=>{let{category:n,emojis:r}=e,{category:i,emojis:a}=t;return n===i?Ht(r,a,d):!1},p=e=>{Ht(a.currentEmojis,e,d)||(a.currentEmojis=e)},m=e=>{a.searchMode!==e&&(a.searchMode=e)},h=e=>{Ht(a.currentEmojisWithCategories,e,f)||(a.currentEmojisWithCategories=e)},g=(e,t)=>t&&e.skins&&e.skins[t]||e.unicode,ee={labelWithSkin:(e,t)=>Ct([e.name||g(e,t),e.annotation,...e.shortcodes||Gt].filter(Boolean)).join(`, `),titleForEmoji:e=>e.annotation||(e.shortcodes||Gt).join(`, `),unicodeWithSkin:g},te={onClickSkinToneButton:A,onEmojiClick:D,onNavClick:w,onNavKeydown:ce,onSearchKeydown:C,onSkinToneOptionsClick:k,onSkinToneOptionsFocusOut:N,onSkinToneOptionsKeydown:j,onSkinToneOptionsKeyup:M,onSearchInput:P},ne={calculateEmojiGridStyle:ie,updateOnIntersection:v},_=!0;o(()=>{Bt(e,a,ee,te,ne,n,i,s,_),_=!1}),a.emojiVersion||ft().then(e=>{e||(a.message=a.i18n.emojiUnsupportedMessage)}),o(()=>{async function e(){let e=!1,t=setTimeout(()=>{e=!0,a.message=a.i18n.loadingMessage},1e3);try{await a.database.ready(),a.databaseLoaded=!0}catch(e){console.error(e),a.message=a.i18n.networkErrorMessage}finally{clearTimeout(t),e&&(e=!1,a.message=``)}}a.database&&e()}),o(()=>{a.pickerStyle=`\n      --num-groups: ${a.groups.length}; \n      --indicator-opacity: ${a.searchMode?0:1}; \n      --num-skintones: 6;`}),o(()=>{a.customEmoji&&a.database&&re()}),o(()=>{a.customEmoji&&a.customEmoji.length?a.groups!==J&&(a.groups=J):a.groups!==et&&(a.currentGroupIndex&&a.currentGroupIndex--,a.groups=et)}),o(()=>{async function e(){a.databaseLoaded&&(a.currentSkinTone=await a.database.getPreferredSkinTone())}e()}),o(()=>{a.skinTones=[,,,,,,].fill().map((e,t)=>mt(a.skinToneEmoji,t))}),o(()=>{a.skinToneButtonText=a.skinTones[a.currentSkinTone]}),o(()=>{a.skinToneButtonLabel=a.i18n.skinToneLabel.replace(`{skinTone}`,a.i18n.skinTones[a.currentSkinTone])}),o(()=>{async function e(){let{database:e}=a;a.defaultFavoriteEmojis=(await Promise.all(it.map(t=>e.getEmojiByUnicodeOrName(t)))).filter(Boolean)}a.databaseLoaded&&e()});function re(){let{customEmoji:e,database:t}=a,n=e||Gt;t.customEmoji!==n&&(t.customEmoji=n)}o(()=>{async function e(){re();let{database:e,defaultFavoriteEmojis:t,numColumns:n}=a;a.currentFavorites=await x(gt([...await e.getTopFavoriteEmoji(n),...t],e=>e.unicode||e.name).slice(0,n))}a.databaseLoaded&&a.defaultFavoriteEmojis&&e()});function ie(e){yt(e,i,()=>{{let e=getComputedStyle(n.rootElement),t=parseInt(e.getPropertyValue(`--num-columns`),10),r=e.getPropertyValue(`direction`)===`rtl`;a.numColumns=t,a.isRtl=r}})}function v(e){Wt(e,i,e=>{for(let{target:t,isIntersecting:n}of e)t.classList.toggle(`onscreen`,n)})}o(()=>{async function e(){let{searchText:e,currentGroup:t,databaseLoaded:n,customEmoji:r}=a;if(!n)a.currentEmojis=[],a.searchMode=!1;else if(e.length>=2){let t=await se(e);a.searchText===e&&(p(t),m(!0))}else{let{id:e}=t;if(e!==-1||r&&r.length){let t=await S(e);a.currentGroup.id===e&&(p(t),m(!1))}}}e()});let y=()=>{X(()=>wt(n.tabpanelElement))};o(()=>{let{currentEmojis:e,emojiVersion:t}=a,n=e.filter(e=>e.unicode).filter(e=>nt(e)&&!pt.has(e.unicode));!t&&n.length?(p(e),X(()=>ae(n))):(p(t?e:e.filter(oe)),y())});function ae(e){St(e,n.baselineEmoji,l)?y():a.currentEmojis=[...a.currentEmojis]}function oe(e){return!e.unicode||!nt(e)||pt.get(e.unicode)}async function b(e){let t=a.emojiVersion||await ft();return e.filter(({version:e})=>!e||e<=t)}async function x(e){return _t(e,a.emojiVersion||await ft())}async function S(e){return x(await b(e===-1?a.customEmoji:await a.database.getEmojiByGroup(e)))}async function se(e){return x(await b(await a.database.getEmojiBySearchQuery(e)))}o(()=>{}),o(()=>{function e(){let{searchMode:e,currentEmojis:t}=a;if(e)return[{category:``,emojis:t}];let n=new Map;for(let e of t){let t=e.category||``,r=n.get(t);r||(r=[],n.set(t,r)),r.push(e)}return[...n.entries()].map(([e,t])=>({category:e,emojis:t})).sort((e,t)=>a.customCategorySorting(e.category,t.category))}h(e())}),o(()=>{a.activeSearchItemId=a.activeSearchItem!==-1&&a.currentEmojis[a.activeSearchItem].id}),o(()=>{let{rawSearchText:e}=a;tt(()=>{a.searchText=(e||``).trim(),a.activeSearchItem=-1})});function C(e){if(!a.searchMode||!a.currentEmojis.length)return;let t=t=>{Y(e),a.activeSearchItem=ht(t,a.activeSearchItem,a.currentEmojis)};switch(e.key){case`ArrowDown`:return t(!1);case`ArrowUp`:return t(!0);case`Enter`:if(a.activeSearchItem===-1)a.activeSearchItem=0;else return Y(e),E(a.currentEmojis[a.activeSearchItem].id)}}function w(e){let{target:t}=e,r=t.closest(`.nav-button`);if(!r)return;let i=parseInt(r.dataset.groupId,10);n.searchElement.value=``,a.rawSearchText=``,a.searchText=``,a.activeSearchItem=-1,a.currentGroupIndex=a.groups.findIndex(e=>e.id===i)}function ce(e){let{target:t,key:n}=e,r=t=>{t&&(Y(e),t.focus())};switch(n){case`ArrowLeft`:return r(t.previousElementSibling);case`ArrowRight`:return r(t.nextElementSibling);case`Home`:return r(t.parentElement.firstElementChild);case`End`:return r(t.parentElement.lastElementChild)}}async function T(e){let t=await a.database.getEmojiByUnicodeOrName(e),n=[...a.currentEmojis,...a.currentFavorites].find(t=>t.id===e),r=n.unicode&&g(n,a.currentSkinTone);return await a.database.incrementFavoriteEmojiCount(e),{emoji:t,skinTone:a.currentSkinTone,...r&&{unicode:r},...n.name&&{name:n.name}}}async function E(e){let t=T(e);u(`emoji-click-sync`,t),u(`emoji-click`,await t)}function D(e){let{target:t}=e;t.classList.contains(`emoji`)&&(Y(e),E(t.id.substring(4)))}function O(e){a.currentSkinTone=e,a.skinTonePickerExpanded=!1,c(`skintone-button`),u(`skin-tone-change`,{skinTone:e}),a.database.setPreferredSkinTone(e)}function k(e){let{target:{id:t}}=e,n=t&&t.match(/^skintone-(\\d)/);n&&(Y(e),O(parseInt(n[1],10)))}function A(e){a.skinTonePickerExpanded=!a.skinTonePickerExpanded,a.activeSkinTone=a.currentSkinTone,a.skinTonePickerExpanded&&(Y(e),X(()=>c(`skintone-list`)))}o(()=>{a.skinTonePickerExpanded?n.skinToneDropdown.addEventListener(`transitionend`,()=>{a.skinTonePickerExpandedAfterAnimation=!0},{once:!0}):a.skinTonePickerExpandedAfterAnimation=!1});function j(e){if(!a.skinTonePickerExpanded)return;let t=async t=>{Y(e),a.activeSkinTone=t};switch(e.key){case`ArrowUp`:return t(ht(!0,a.activeSkinTone,a.skinTones));case`ArrowDown`:return t(ht(!1,a.activeSkinTone,a.skinTones));case`Home`:return t(0);case`End`:return t(a.skinTones.length-1);case`Enter`:return Y(e),O(a.activeSkinTone);case`Escape`:return Y(e),a.skinTonePickerExpanded=!1,c(`skintone-button`)}}function M(e){if(a.skinTonePickerExpanded)switch(e.key){case` `:return Y(e),O(a.activeSkinTone)}}async function N(e){let{relatedTarget:t}=e;(!t||t.id!==`skintone-list`)&&(a.skinTonePickerExpanded=!1)}function P(e){a.rawSearchText=e.target.value}return{$set(e){$(a,e)},$destroy(){r.abort()}}}var qt={categoriesLabel:`Categories`,emojiUnsupportedMessage:`Your browser does not support color emoji.`,favoritesLabel:`Favorites`,loadingMessage:`Loading…`,networkErrorMessage:`Could not load emoji.`,regionLabel:`Emoji picker`,searchDescription:`When search results are available, press up or down to select and enter to choose.`,searchLabel:`Search`,searchResultsLabel:`Search results`,skinToneDescription:`When expanded, press up or down to select and enter to choose.`,skinToneLabel:`Choose a skin tone (currently {skinTone})`,skinTonesLabel:`Skin tones`,skinTones:[`Default`,`Light`,`Medium-Light`,`Medium`,`Medium-Dark`,`Dark`],categories:{custom:`Custom`,\"smileys-emotion\":`Smileys and emoticons`,\"people-body\":`People and body`,\"animals-nature\":`Animals and nature`,\"food-drink\":`Food and drink`,\"travel-places\":`Travel and places`,activities:`Activities`,objects:`Objects`,symbols:`Symbols`,flags:`Flags`}},Jt=`:host{--emoji-size:1.375rem;--emoji-padding:0.5rem;--category-emoji-size:var(--emoji-size);--category-emoji-padding:var(--emoji-padding);--indicator-height:3px;--input-border-radius:0.5rem;--input-border-size:1px;--input-font-size:1rem;--input-line-height:1.5;--input-padding:0.25rem;--num-columns:8;--outline-size:2px;--border-size:1px;--border-radius:0;--skintone-border-radius:1rem;--category-font-size:1rem;display:flex;width:min-content;height:400px}:host,:host(.light){color-scheme:light;--background:#fff;--border-color:#e0e0e0;--indicator-color:#385ac1;--input-border-color:#999;--input-font-color:#111;--input-placeholder-color:#999;--outline-color:#999;--category-font-color:#111;--button-active-background:#e6e6e6;--button-hover-background:#d9d9d9}:host(.dark){color-scheme:dark;--background:#222;--border-color:#444;--indicator-color:#5373ec;--input-border-color:#ccc;--input-font-color:#efefef;--input-placeholder-color:#ccc;--outline-color:#fff;--category-font-color:#efefef;--button-active-background:#555555;--button-hover-background:#484848}@media (prefers-color-scheme:dark){:host{color-scheme:dark;--background:#222;--border-color:#444;--indicator-color:#5373ec;--input-border-color:#ccc;--input-font-color:#efefef;--input-placeholder-color:#ccc;--outline-color:#fff;--category-font-color:#efefef;--button-active-background:#555555;--button-hover-background:#484848}}:host([hidden]){display:none}button{margin:0;padding:0;border:0;background:0 0;box-shadow:none;-webkit-tap-highlight-color:transparent}button::-moz-focus-inner{border:0}input{padding:0;margin:0;line-height:1.15;font-family:inherit}input[type=search]{-webkit-appearance:none}:focus{outline:var(--outline-color) solid var(--outline-size);outline-offset:calc(-1*var(--outline-size))}:host([data-js-focus-visible]) :focus:not([data-focus-visible-added]){outline:0}:focus:not(:focus-visible){outline:0}.hide-focus{outline:0}*{box-sizing:border-box}.picker{contain:content;display:flex;flex-direction:column;background:var(--background);border:var(--border-size) solid var(--border-color);border-radius:var(--border-radius);width:100%;height:100%;overflow:hidden;--total-emoji-size:calc(var(--emoji-size) + (2 * var(--emoji-padding)));--total-category-emoji-size:calc(var(--category-emoji-size) + (2 * var(--category-emoji-padding)))}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.hidden{opacity:0;pointer-events:none}.abs-pos{position:absolute;left:0;top:0}.gone{display:none!important}.skintone-button-wrapper,.skintone-list{background:var(--background);z-index:3}.skintone-button-wrapper.expanded{z-index:1}.skintone-list{position:absolute;inset-inline-end:0;top:0;z-index:2;overflow:visible;border-bottom:var(--border-size) solid var(--border-color);border-radius:0 0 var(--skintone-border-radius) var(--skintone-border-radius);will-change:transform;transition:transform .2s ease-in-out;transform-origin:center 0}@media (prefers-reduced-motion:reduce){.skintone-list{transition-duration:.001s}}@supports not (inset-inline-end:0){.skintone-list{right:0}}.skintone-list.no-animate{transition:none}.tabpanel{overflow-y:auto;scrollbar-gutter:stable;-webkit-overflow-scrolling:touch;will-change:transform;min-height:0;flex:1;contain:content}.emoji-menu{display:grid;grid-template-columns:repeat(var(--num-columns),var(--total-emoji-size));justify-content:space-around;align-items:flex-start;width:100%}.emoji-menu.visibility-auto{content-visibility:auto;contain-intrinsic-size:calc(var(--num-columns)*var(--total-emoji-size)) calc(var(--num-rows)*var(--total-emoji-size))}.category{padding:var(--emoji-padding);font-size:var(--category-font-size);color:var(--category-font-color)}.emoji,button.emoji{font-size:var(--emoji-size);display:flex;align-items:center;justify-content:center;border-radius:100%;height:var(--total-emoji-size);width:var(--total-emoji-size);line-height:1;overflow:hidden;font-family:var(--emoji-font-family);cursor:pointer}@media (hover:hover) and (pointer:fine){.emoji:hover,button.emoji:hover{background:var(--button-hover-background)}}.emoji.active,.emoji:active,button.emoji.active,button.emoji:active{background:var(--button-active-background)}.onscreen .custom-emoji::after{content:\"\";width:var(--emoji-size);height:var(--emoji-size);background-repeat:no-repeat;background-position:center center;background-size:contain;background-image:var(--custom-emoji-background)}.nav,.nav-button{align-items:center}.nav{display:grid;justify-content:space-between;contain:content}.nav-button{display:flex;justify-content:center}.nav-emoji{font-size:var(--category-emoji-size);width:var(--total-category-emoji-size);height:var(--total-category-emoji-size)}.indicator-wrapper{display:flex;border-bottom:1px solid var(--border-color)}.indicator{width:calc(100%/var(--num-groups));height:var(--indicator-height);opacity:var(--indicator-opacity);background-color:var(--indicator-color);will-change:transform,opacity;transition:opacity .1s linear,transform .25s ease-in-out}@media (prefers-reduced-motion:reduce){.indicator{will-change:opacity;transition:opacity .1s linear}}.pad-top,input.search{background:var(--background);width:100%}.pad-top{height:var(--emoji-padding);z-index:3}.search-row{display:flex;align-items:center;position:relative;padding-inline-start:var(--emoji-padding);padding-bottom:var(--emoji-padding)}.search-wrapper{flex:1;min-width:0}input.search{padding:var(--input-padding);border-radius:var(--input-border-radius);border:var(--input-border-size) solid var(--input-border-color);color:var(--input-font-color);font-size:var(--input-font-size);line-height:var(--input-line-height)}input.search::placeholder{color:var(--input-placeholder-color)}.favorites{overflow-y:auto;scrollbar-gutter:stable;display:flex;flex-direction:row;border-top:var(--border-size) solid var(--border-color);contain:content}.message{padding:var(--emoji-padding)}`;let Yt=[`customEmoji`,`customCategorySorting`,`database`,`dataSource`,`i18n`,`locale`,`skinToneEmoji`,`emojiVersion`],Xt=`:host{--emoji-font-family:${at}}`;var Zt=class extends HTMLElement{constructor(e){super(),this.attachShadow({mode:`open`});let t=document.createElement(`style`);t.textContent=Jt+Xt,this.shadowRoot.appendChild(t),this._ctx={locale:`en`,dataSource:`https://cdn.jsdelivr.net/npm/emoji-picker-element-data@^1/en/emojibase/data.json`,skinToneEmoji:`🖐️`,customCategorySorting:ot,customEmoji:null,i18n:qt,emojiVersion:null,...e};for(let e of Yt)e!==`database`&&Object.prototype.hasOwnProperty.call(this,e)&&(this._ctx[e]=this[e],delete this[e]);this._dbFlush()}connectedCallback(){$t(this),this._cmp||=Kt(this.shadowRoot,this._ctx)}disconnectedCallback(){$t(this),Q(()=>{if(!this.isConnected&&this._cmp){this._cmp.$destroy(),this._cmp=void 0;let{database:e}=this._ctx;e.close().catch(e=>console.error(e))}})}static get observedAttributes(){return[`locale`,`data-source`,`skin-tone-emoji`,`emoji-version`]}attributeChangedCallback(e,t,n){this._set(e.replace(/-([a-z])/g,(e,t)=>t.toUpperCase()),e===`emoji-version`?parseFloat(n):n)}_set(e,t){this._ctx[e]=t,this._cmp&&this._cmp.$set({[e]:t}),[`locale`,`dataSource`].includes(e)&&this._dbFlush()}_dbCreate(){let{locale:e,dataSource:t,database:n}=this._ctx;(!n||n.locale!==e||n.dataSource!==t)&&this._set(`database`,new $e({locale:e,dataSource:t}))}_dbFlush(){Q(()=>this._dbCreate())}};let Qt={};for(let e of Yt)Qt[e]={get(){return e===`database`&&this._dbCreate(),this._ctx[e]},set(t){if(e===`database`)throw Error(`database is read-only`);this._set(e,t)}};Object.defineProperties(Zt.prototype,Qt);function $t(e){e instanceof Zt||Object.setPrototypeOf(e,customElements.get(e.tagName.toLowerCase()).prototype)}customElements.get(`emoji-picker`)||customElements.define(`emoji-picker`,Zt);var en=class extends T{connect(){let e=this.element.querySelector(`emoji-picker`),t=e.shadowRoot;e.addEventListener(`focusout`,e=>e.stopPropagation()),this.element.addEventListener(`open`,()=>{t.querySelector(`input`)?.focus()})}};window.Stimulus.register(`emoji-picker`,en)})();"
  },
  {
    "path": "resources/dist/global.css",
    "content": ":root,[data-theme=light]{--csstools-color-scheme--light:initial;--palette-bg-hsl:250 38% 98%;--palette-bg:hsl(var(--palette-bg-hsl));--palette-surface-hsl:255 100% 100%;--palette-surface:hsl(var(--palette-surface-hsl));--palette-text-hsl:0 0% 0%;--palette-text:hsl(var(--palette-text-hsl));--palette-muted-hsl:250 20% 50%;--palette-muted:hsl(var(--palette-muted-hsl));--palette-fill:hsl(var(--palette-muted-hsl)/0.1);--palette-fill-soft:hsl(var(--palette-muted-hsl)/0.05);--palette-stroke:hsl(var(--palette-muted-hsl)/0.25);--palette-emphasis:#2f3641;--palette-emphasis-contrast:#fff;--palette-accent-h:252;--palette-accent-s:93%;--palette-accent-l:58%;--palette-accent:hsl(var(--palette-accent-h) var(--palette-accent-s) var(--palette-accent-l));--palette-accent-contrast:#fff;--palette-accent-soft:hsl(var(--palette-accent-h) var(--palette-accent-s) 92%);--palette-accent-text:hsl(var(--palette-accent-h) var(--palette-accent-s) calc(var(--palette-accent-l)*0.9));--palette-danger-h:0;--palette-danger-s:100%;--palette-danger-l:38%;--palette-danger:hsl(var(--palette-danger-h) var(--palette-danger-s) var(--palette-danger-l));--palette-danger-contrast:#fff;--palette-danger-soft:hsl(var(--palette-danger-h) var(--palette-danger-s) 92%);--palette-danger-text:hsl(var(--palette-danger-h) var(--palette-danger-s) calc(var(--palette-danger-l)*0.9));--palette-warning-h:60;--palette-warning-s:100%;--palette-warning-l:45%;--palette-warning:hsl(var(--palette-warning-h) var(--palette-warning-s) var(--palette-warning-l));--palette-warning-contrast:hsl(var(--palette-warning-h) var(--palette-warning-s) 15%);--palette-warning-soft:hsl(var(--palette-warning-h) var(--palette-warning-s) 92%);--palette-warning-text:hsl(var(--palette-warning-h) var(--palette-warning-s) 24%);--palette-success-h:127;--palette-success-s:100%;--palette-success-l:27%;--palette-success:hsl(var(--palette-success-h) var(--palette-success-s) var(--palette-success-l));--palette-success-contrast:#fff;--palette-success-soft:hsl(var(--palette-success-h) var(--palette-success-s) 92%);--palette-success-text:hsl(var(--palette-success-h) var(--palette-success-s) calc(var(--palette-success-l)*0.9));--palette-activity-h:23;--palette-activity-s:100%;--palette-activity-l:45%;--palette-activity:hsl(var(--palette-activity-h) var(--palette-activity-s) var(--palette-activity-l));--palette-activity-contrast:#fff;--palette-activity-soft:hsl(var(--palette-activity-h) var(--palette-activity-s) 92%);--palette-activity-text:hsl(var(--palette-activity-h) var(--palette-activity-s) calc(var(--palette-activity-l)*0.9));color-scheme:light}[data-theme=dark]{--csstools-color-scheme--light: ;--palette-bg-hsl:250 20% 8%;--palette-surface-hsl:250 20% 12%;--palette-text-hsl:250 20% 93%;--palette-muted-hsl:250 20% 60%;--palette-accent-soft:hsl(var(--palette-accent-h) var(--palette-accent-s) 20%);--palette-accent-text:hsl(var(--palette-accent-h) var(--palette-accent-s) calc(var(--palette-accent-l)*1.3));--palette-danger-soft:hsl(var(--palette-danger-h) var(--palette-danger-s) 10%);--palette-danger-text:hsl(var(--palette-danger-h) var(--palette-danger-s) calc(var(--palette-danger-l)*1.3));--palette-warning-soft:hsl(var(--palette-warning-h) var(--palette-warning-s) 10%);--palette-warning-text:hsl(var(--palette-warning-h) var(--palette-warning-s) 40%);--palette-success-soft:hsl(var(--palette-success-h) var(--palette-success-s) 10%);--palette-success-text:hsl(var(--palette-success-h) var(--palette-success-s) calc(var(--palette-success-l)*1.3));--palette-activity-soft:hsl(var(--palette-activity-h) var(--palette-activity-s) 10%);--palette-activity-text:hsl(var(--palette-activity-h) var(--palette-activity-s) calc(var(--palette-activity-l)*1.3));color-scheme:dark}:root{--shadow-sm:0 1px 4px var(--palette-fill);--shadow-md:0 0 0 1px var(--palette-fill),0 5px 15px var(--palette-stroke);--color-overlay:rgba(0,0,0,.5);--filter-hover:brightness(0.95);--filter-active:brightness(0.9);--ratio:1.5;--space-px:1px;--space-xxs:calc(var(--space-xs)/var(--ratio));--space-xs:calc(var(--space-sm)/var(--ratio));--space-sm:calc(var(--space-md)/var(--ratio));--space-md:1rem;--space-lg:calc(var(--space-md)*var(--ratio));--space-xl:calc(var(--space-lg)*var(--ratio));--space-xxl:calc(var(--space-xl)*var(--ratio));--space-xxxl:calc(var(--space-xxl)*var(--ratio));--space-gutter:max(var(--space-lg),min(3vw,var(--space-xl)));--font-system:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif;--font-text:var(--font-system);--font-display:var(--font-text);--font-mono:SFMono-Regular,Menlo,Monaco,Consolas,\"Courier New\",monospace;--weight-normal:400;--weight-medium:500;--weight-bold:600;--line-height-default:1.3;--line-height-condensed:1.2;--line-height-expanded:1.5;--measure:92ch;--z-index-header:100;--z-index-overlay:200;--z-index-alerts:300;--text-xxs:0.8rem;--text-xs:0.875rem;--text-sm:1rem;--text-md:1.125rem;--text-lg:1.5rem;--text-xl:2rem;--text-xxl:2.5rem;--radius:10px;--control-height:2.5em;--control-height-small:2.3em;--control-radius:var(--radius);--header-height:4rem}.menu,:root{--color-bg:var(--palette-bg);--color-surface:var(--palette-surface);--color-text:var(--palette-text);--color-muted:var(--palette-muted);--color-fill:var(--palette-fill);--color-fill-soft:var(--palette-fill-soft);--color-stroke:var(--palette-stroke);--color-emphasis:var(--palette-emphasis);--color-emphasis-contrast:var(--palette-emphasis-contrast);--color-accent:var(--palette-accent);--color-accent-contrast:var(--palette-accent-contrast);--color-accent-soft:var(--palette-accent-soft);--color-accent-text:var(--palette-accent-text);--color-danger:var(--palette-danger);--color-danger-contrast:var(--palette-danger-contrast);--color-danger-soft:var(--palette-danger-soft);--color-danger-text:var(--palette-danger-text);--color-warning:var(--palette-warning);--color-warning-contrast:var(--palette-warning-contrast);--color-warning-soft:var(--palette-warning-soft);--color-warning-text:var(--palette-warning-text);--color-success:var(--palette-success);--color-success-contrast:var(--palette-success-contrast);--color-success-soft:var(--palette-success-soft);--color-success-text:var(--palette-success-text);--color-activity:var(--palette-activity);--color-activity-contrast:var(--palette-activity-contrast);--color-activity-soft:var(--palette-activity-soft);--color-activity-text:var(--palette-activity-text)}*,:after,:before{box-sizing:border-box;margin:0}button,input,select,textarea{font:inherit}[hidden]{display:none!important}[role=list]{list-style:none;padding:0}button{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:transparent;border:0;color:inherit;cursor:pointer;margin:0;padding:0}@media (prefers-reduced-motion:reduce){*,:after,:before{animation-duration:.01ms!important;animation-iteration-count:1!important;scroll-behavior:auto!important;transition-duration:.01ms!important}}:root{scroll-behavior:auto}html{background:#f9f8fc;background:var(--color-bg);color:#000;color:var(--color-text);font-family:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif;font-family:var(--font-text);font-size:1rem;font-size:var(--text-sm);line-height:1.3;line-height:var(--line-height-default)}@media (max-width:40rem){html{font-size:95%}}input,select,textarea{color:#000;color:var(--color-text)}*{word-wrap:anywhere;-webkit-tap-highlight-color:transparent}.link,a{color:#4013f6;color:var(--color-accent-text);cursor:pointer;-webkit-text-decoration:none;text-decoration:none}.link:hover,a:not(.does-not-exist):hover{-webkit-text-decoration:underline;text-decoration:underline}hr{border:0;border-top:1px solid rgba(110,102,153,.25);border-top:1px solid var(--color-stroke);margin:2.25rem 0;margin:var(--space-xl) 0}mark,strong{font-weight:600;font-weight:var(--weight-bold)}mark{background:transparent;color:#4013f6;color:var(--color-accent-text)}code,pre{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Courier New,monospace;font-family:var(--font-mono)}:focus{outline:2px solid #5830f8;outline:2px solid var(--color-accent);outline-offset:2px}[tabindex=\"-1\"]:focus{outline:none}:focus:not(:focus-visible){outline:none}.waterhole{align-items:stretch;min-height:100vh;min-height:100svh}.waterhole,.waterhole__main{display:flex;flex-direction:column}.waterhole__main{flex-grow:1}.waterhole__main :not(textarea){scroll-margin-top:4rem;scroll-margin-top:var(--header-height)}@media (prefers-reduced-motion:no-preference){ui-popup>.enter-active,ui-popup>.leave-active{transition:transform .15s,opacity .15s}ui-popup>.enter-from,ui-popup>.leave-to{opacity:0;transform:scale(0)}ui-popup::part(backdrop),ui-popup>:nth-child(2){z-index:200;z-index:var(--z-index-overlay)}}[data-placement=bottom]{transform-origin:top center}[data-placement=bottom-start]{transform-origin:top left}[data-placement=bottom-end]{transform-origin:top right}[data-placement=top]{transform-origin:bottom center}[data-placement=top-start]{transform-origin:bottom left}[data-placement=top-end]{transform-origin:bottom right}[data-placement=left]{transform-origin:right center}[data-placement=left-start]{transform-origin:right top}[data-placement=left-end]{transform-origin:right bottom}[data-placement=right]{transform-origin:left center}[data-placement=right-start]{transform-origin:left top}[data-placement=right-end]{transform-origin:left bottom}[data-placement^=top]{margin-top:-.44444rem;margin-top:calc(var(--space-xs)*-1)}[data-placement^=bottom]{margin-top:.44444rem;margin-top:var(--space-xs)}[data-placement^=left]{margin-left:-.44444rem;margin-left:calc(var(--space-xs)*-1)}[data-placement^=right]{margin-left:.44444rem;margin-left:var(--space-xs)}.h1,.h2,.h3,h1,h2,h3{font-family:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif;font-family:var(--font-display)}.h4,.h5,.h6,h4,h5,h6{font-family:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif;font-family:var(--font-text)}.h1,h1{font-size:2.5rem;font-size:var(--text-xxl)}.h1,.h2,h1,h2{font-weight:600;font-weight:var(--weight-bold);line-height:1.2;line-height:var(--line-height-condensed)}.h2,h2{font-size:2rem;font-size:var(--text-xl)}.h3,h3{font-size:1.5rem;font-size:var(--text-lg)}.h3,.h4,h3,h4{font-weight:600;font-weight:var(--weight-bold)}.h4,h4{font-size:1.125rem;font-size:var(--text-md)}.h5,h5{font-size:1rem;font-size:var(--text-sm)}.h5,.h6,h5,h6{font-weight:600;font-weight:var(--weight-bold)}.h6,h6{font-size:.875rem;font-size:var(--text-xs)}.subtitle{color:#6e6699;color:var(--color-muted);font-family:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif;font-family:var(--font-text);font-size:.8rem;font-size:var(--text-xxs);font-weight:500;font-weight:var(--weight-medium);text-transform:uppercase}.lead{font-size:1.125rem;font-size:var(--text-md)}.content,.lead{line-height:1.5;line-height:var(--line-height-expanded)}.content{word-wrap:break-word}:where(.content) li p,:where(.content)>*+*{margin-top:1em}:where(.content)>*+h1{margin-top:1.8em}:where(.content)>*+h2{margin-top:1.8em}:where(.content)>*+h3{margin-top:1.8em}:where(.content)>*+h4{margin-top:1.8em}:where(.content)>*+h5{margin-top:1.8em}:where(.content)>*+h6{margin-top:1.8em}:where(.content) a{-webkit-text-decoration:underline;text-decoration:underline}:where(.content) ol{list-style-position:outside;padding-left:2em}:where(.content) ul{list-style:disc outside;padding-left:2em}:where(.content) ul ul{list-style-type:circle}:where(.content) ul ul ul{list-style-type:square}:where(.content) blockquote{border-left:5px solid rgba(110,102,153,.1);border-left:5px solid var(--color-fill);color:#6e6699;color:var(--color-muted);padding-left:1rem;padding-left:var(--space-md)}:where(.content) blockquote>*+*{margin-top:1em}:where(.content) figcaption{color:#6e6699;color:var(--color-muted);font-size:90%}:where(.content) code{background:rgba(110,102,153,.05);background:var(--color-fill-soft);border-radius:4px;color:#4013f6;color:var(--color-accent-text);font-family:SFMono-Regular,Menlo,Monaco,Consolas,Courier New,monospace;font-family:var(--font-mono);font-size:80%;padding:3px}:where(.content) pre code{background:rgba(110,102,153,.1);background:var(--color-fill);border-radius:10px;border-radius:var(--radius);color:inherit;display:block;overflow:auto;padding:1rem!important;padding:var(--space-md)!important}:where(.content) img:not([class]){height:auto;max-width:100%}:where(.content) video:not([class]){height:auto;max-width:100%}:where(.content) hr:not([class]){border:0;border-top:3px solid rgba(110,102,153,.25);border-top:3px solid var(--color-stroke);margin:2em 0}:where(.content--compact)>*+*{margin-top:1em}:where(.content--compact) h1,:where(.content--compact) h2,:where(.content--compact) h3,:where(.content--compact) h4,:where(.content--compact) h5,:where(.content--compact) h6{font-size:100%}.alert{align-items:flex-start;background:rgba(110,102,153,.1);background:var(--color-fill);border-radius:10px;border-radius:var(--radius);display:flex;font-size:.875rem;font-size:var(--text-xs);gap:.66667rem;gap:var(--space-sm);line-height:1.5;line-height:var(--line-height-expanded);padding:.66667rem 1rem;padding:var(--space-sm) var(--space-md)}.alert__icon{display:flex;font-size:1.125rem;font-size:var(--text-md);line-height:1.3;line-height:var(--line-height-default)}.alert__message{flex-grow:1;min-width:0}.alert__actions{margin-bottom:-.44444rem;margin-bottom:calc(var(--space-xs)*-1);margin-left:auto;margin-right:-.66667rem;margin-right:calc(var(--space-sm)*-1);margin-top:-.44444rem;margin-top:calc(var(--space-xs)*-1)}.alert__actions,.alerts{align-items:center;display:flex;gap:.66667rem;gap:var(--space-sm)}.alerts{flex-direction:column;left:0;padding:.66667rem;padding:var(--space-sm);pointer-events:none;position:fixed;right:0;top:0;z-index:300;z-index:var(--z-index-alerts)}.alerts>*{box-shadow:0 0 0 1px rgba(110,102,153,.1),0 5px 15px rgba(110,102,153,.25);box-shadow:var(--shadow-md);pointer-events:auto}@media (max-width:40rem){.alerts{align-items:stretch;bottom:0;top:auto}}@media (min-width:40.001rem){.alerts>*{max-width:50ch}}:root{--attribution-avatar-size:2.5em}.attribution{padding-left:calc(2.5em + 1rem);padding-left:calc(var(--attribution-avatar-size) + var(--space-md))}.attribution .avatar{height:2.5em;height:var(--attribution-avatar-size);margin-left:calc(-2.5em + -1rem);margin-left:calc((var(--attribution-avatar-size) + var(--space-md))*-1);position:absolute;width:2.5em;width:var(--attribution-avatar-size)}.attribution__link{color:inherit;font-weight:500;font-weight:var(--weight-medium);margin-right:.2963rem;margin-right:var(--space-xxs);-webkit-text-decoration:none!important;text-decoration:none!important}.attribution__link:hover .attribution__name{-webkit-text-decoration:underline;text-decoration:underline}.attribution__info{color:#6e6699;color:var(--color-muted);display:block;font-size:.875rem;font-size:var(--text-xs);margin-top:.2963rem;margin-top:var(--space-xxs)}.attribution__info>*+:before{content:\" \\00B7 \"}.avatar{aspect-ratio:1;background:rgba(110,102,153,.25);background:var(--color-stroke);border-radius:100%;height:100%;width:100%}.avatar text{fill:#f9f8fc;fill:var(--color-bg);font-size:50px}.badge,.tag{align-items:center;background:rgba(110,102,153,.1);background:var(--color-fill);border:0;border-radius:999px;color:#6e6699;color:var(--color-muted);display:inline-flex;flex-shrink:0;font-size:.8rem;font-size:var(--text-xxs);font-weight:500;font-weight:var(--weight-medium);justify-content:center;line-height:1.2;margin:0;min-height:1.7em;min-width:2.5ch;overflow:hidden;padding:0 .6em;text-align:center;text-overflow:ellipsis;vertical-align:middle;white-space:nowrap}.badge>.icon:first-child,.tag>.icon:first-child{margin-left:-.2em;margin-right:.2em}.badge .icon,.tag .icon{stroke-width:2}.group-badge{background-color:rgba(110,102,153,.1);background-color:var(--group-color,var(--color-fill));color:#6e6699;color:var(--group-color-constrast,var(--color-muted))}.badge--hidden{background-color:transparent;border:1px dashed rgba(110,102,153,.25);border:1px dashed var(--color-stroke)}.suspended-badge{background:#6e6699;background:var(--color-muted);color:#f9f8fc;color:var(--color-bg)}.tag{border-radius:4px;font-size:.875rem;font-size:var(--text-xs);font-weight:400;padding:0 .5em}.block-link{border-radius:10px;border-radius:var(--radius);color:inherit;display:block;padding:1rem;padding:var(--space-md);-webkit-text-decoration:none!important;text-decoration:none!important}.block-link:hover{background:rgba(110,102,153,.1);background:var(--color-fill)}.block-link:active{filter:brightness(.9);filter:var(--filter-active)}.breadcrumb{color:#6e6699;color:var(--color-muted);list-style:none;padding:0}.breadcrumb>*{display:inline}.breadcrumb>*+:before{color:#6e6699;color:var(--color-muted);content:\"\\203A\";margin:0 .2963rem;margin:0 var(--space-xxs)}.breadcrumb [aria-current=page],.btn{color:#000;color:var(--color-text)}.btn{--btn-height:var(--control-height);--btn-padding:calc(var(--btn-height)*0.4);align-items:center;background:rgba(110,102,153,.1);background:var(--color-fill);border:0;border-radius:10px;border-radius:var(--control-radius);display:inline-flex;flex-shrink:0;font-weight:500;font-weight:var(--weight-medium);gap:.2963rem;gap:var(--space-xxs);height:2.5em;height:var(--btn-height);justify-content:center;margin:0;padding:0 1em;padding:0 var(--btn-padding);-webkit-text-decoration:none!important;text-decoration:none!important;vertical-align:middle;white-space:nowrap}.btn.is-disabled,.btn:disabled{cursor:default;opacity:.5}.btn.is-inert{cursor:default}.btn:not(:disabled):not(.is-disabled):not(.is-inert){cursor:pointer}.btn.is-focused:not(:disabled):not(.is-disabled):not(.is-inert),.btn.is-hovered:not(:disabled):not(.is-disabled):not(.is-inert),.btn:not(:disabled):not(.is-disabled):not(.is-inert):focus,.btn:not(:disabled):not(.is-disabled):not(.is-inert):hover,.btn[aria-selected=true]:not(:disabled):not(.is-disabled):not(.is-inert){filter:brightness(.95);filter:var(--filter-hover)}.btn:not(:disabled):not(.is-disabled):not(.is-inert):active{filter:brightness(.9);filter:var(--filter-active);transform:scale(.97)}.btn--outline{background:transparent;border:1px solid rgba(110,102,153,.25);border:1px solid var(--color-stroke);color:#6e6699;color:var(--color-muted)}.btn--outline:hover:not(:disabled):not(.is-disabled):not(.is-inert){background:#f9f8fc;background:var(--color-bg)}.btn--transparent:disabled{background:transparent;color:#6e6699;color:var(--color-muted)}.btn--transparent.is-disabled,.btn--transparent:not(button):not(a):not([role=button]):not([role=link]),:where(:not([open]))>.btn--transparent:where(:not(:hover):not(.is-hovered):not(:focus):not(.is-focused)){background:transparent;color:#6e6699;color:var(--color-muted)}.btn--sm{--btn-height:var(--control-height-small);font-size:87.5%}.btn--narrow,.btn--sm{--btn-padding:calc(var(--btn-height)*0.3)}.btn--wide{--btn-padding:calc(var(--btn-height)*0.5)}.btn--start{margin-left:calc(var(--btn-padding)*-1)}.btn--end{margin-right:calc(var(--btn-padding)*-1)}.btn--icon{border-radius:100px;padding:0;position:relative;width:var(--btn-height)}.btn--icon .avatar{height:100%;width:100%}.btn--icon .icon{font-size:120%}.btn--icon .badge{position:absolute;right:0;top:0}.btn--icon .label{clip:rect(0 0 0 0);clip-path:inset(50%);height:1px;overflow:hidden;position:absolute;white-space:nowrap;width:1px}.btn.is-active,.btn[aria-pressed=true],:checked+.btn{background:#dfd8fe!important;background:var(--color-accent-soft)!important;border-color:transparent;color:#4013f6!important;color:var(--color-accent-text)!important}.btn-group{align-items:stretch;display:flex;gap:1px}.btn-group>:first-child:not(:only-child),.btn-group>:first-child:not(:only-child)>.btn{border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>:not(:first-child):not(:last-child),.btn-group>:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>:last-child:not(:only-child),.btn-group>:last-child:not(:only-child)>.btn{border-bottom-left-radius:0;border-top-left-radius:0}.card{--color-bg:var(--palette-surface);background:#fff;background:var(--color-bg);border-radius:10px;border-radius:var(--radius);box-shadow:0 1px 4px rgba(110,102,153,.1);box-shadow:var(--shadow-sm);overflow:clip}.card .card,.dialog .card{border:1px solid rgba(110,102,153,.1);border:1px solid var(--color-fill)}.card__header{background:#f9f8fc;background:var(--color-bg);border-radius:10px 10px 0 0;border-radius:var(--radius) var(--radius) 0 0;padding:.66667rem 1rem;padding:var(--space-sm) var(--space-md)}summary.card__header.is-disabled,summary.card__header:disabled{cursor:default;opacity:.5}summary.card__header.is-inert{cursor:default}summary.card__header:not(:disabled):not(.is-disabled):not(.is-inert){cursor:pointer}summary.card__header.is-focused:not(:disabled):not(.is-disabled):not(.is-inert),summary.card__header.is-hovered:not(:disabled):not(.is-disabled):not(.is-inert),summary.card__header:not(:disabled):not(.is-disabled):not(.is-inert):focus,summary.card__header:not(:disabled):not(.is-disabled):not(.is-inert):hover,summary.card__header[aria-selected=true]:not(:disabled):not(.is-disabled):not(.is-inert){filter:brightness(.95);filter:var(--filter-hover)}summary.card__header:not(:disabled):not(.is-disabled):not(.is-inert):active{filter:brightness(.9);filter:var(--filter-active);transform:scale(.97)}summary.card__header{outline:none!important;transform:none!important}summary.card__header::-webkit-details-marker,summary.card__header::marker{color:rgba(110,102,153,.25);color:var(--color-stroke);margin-right:.66667rem;margin-right:var(--space-sm)}summary.card__header:focus-visible{background:#dfd8fe;background:var(--color-accent-soft)}details.card:not([open]) .card__header{border-radius:10px;border-radius:var(--radius)}.card__body{padding:min(max(1.5rem,min(3vw,2.25rem)),1.5rem);padding:min(var(--space-gutter),var(--space-lg))}.card__row{padding:.66667rem 1rem;padding:var(--space-sm) var(--space-md)}:not(.divider)+.card__row{border-top:1px solid rgba(110,102,153,.1);border-top:1px solid var(--color-fill)}.card-list>*{margin-bottom:1.5rem;margin-bottom:var(--space-lg)}@media (max-width:40rem){.container .card:not(.card .card){border-radius:0;margin-left:calc(-1 * max(calc(1rem * 1.5), min(3vw, calc(calc(1rem * 1.5) * 1.5))))!important;margin-left:calc(var(--space-gutter)*-1)!important;margin-right:calc(-1 * max(calc(1rem * 1.5), min(3vw, calc(calc(1rem * 1.5) * 1.5))))!important;margin-right:calc(var(--space-gutter)*-1)!important}.container .card:not(.card .card)>.card__header{border-radius:0}}.card:target,.card__row:target{background:#ffffd6;background:var(--color-warning-soft)}.channel-label{align-items:center;color:inherit;display:inline-flex;gap:.2963rem;gap:var(--space-xxs);-webkit-text-decoration:none;text-decoration:none;vertical-align:bottom}a.channel-label:hover>:last-child{-webkit-text-decoration:underline;text-decoration:underline}.channel-picker .menu-item{gap:1rem;gap:var(--space-md);padding:.66667rem 1rem;padding:var(--space-sm) var(--space-md)}.channel-picker .menu-item>.icon:first-child{font-size:1.5rem;font-size:var(--text-lg)}.channel-picker .menu-heading{padding-left:1rem;padding-left:var(--space-md);padding-right:1rem;padding-right:var(--space-md)}.channel-picker .menu-heading:not(:first-child){padding-top:1rem;padding-top:var(--space-md)}.choice{align-items:flex-start;cursor:pointer;display:flex;gap:.66667rem;gap:var(--space-sm)}.choice>:disabled+*{color:#6e6699;color:var(--color-muted)}.choice-indent{margin-left:calc(.66667rem + 1.2em);margin-left:calc(var(--space-sm) + 1.2em)}input[type=checkbox],input[type=radio]{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#f9f8fc;background-color:var(--color-bg);border:2px solid rgba(110,102,153,.25);border:2px solid var(--color-stroke);color:currentColor;cursor:pointer;flex-shrink:0;font:inherit;height:1.2em;margin:0;position:relative;vertical-align:-.2em;width:1.2em}input[type=checkbox]:checked,input[type=radio]:checked{border:0}input[type=checkbox]:checked:before,input[type=radio]:checked:before{background:#fff;background:var(--color-accent-contrast);content:\"\";left:50%;position:absolute;top:50%;transform:translate(-50%,-50%)}input[type=checkbox]:not(:disabled):not([aria-disabled=true]):active,input[type=radio]:not(:disabled):not([aria-disabled=true]):active{filter:brightness(.9);filter:var(--filter-active)}input[type=checkbox]:not(:disabled):not([aria-disabled=true]):checked,input[type=radio]:not(:disabled):not([aria-disabled=true]):checked{background:#5830f8;background:var(--color-accent)}input[aria-disabled=true][type=checkbox],input[aria-disabled=true][type=radio],input[type=checkbox]:disabled,input[type=radio]:disabled{background:rgba(110,102,153,.25);background:var(--color-stroke);border:0}input[aria-disabled=true][type=checkbox]:checked:before,input[aria-disabled=true][type=radio]:checked:before,input[type=checkbox]:disabled:checked:before,input[type=radio]:disabled:checked:before{background:#6e6699;background:var(--color-muted)}input[type=checkbox]{border-radius:.3em}input[type=checkbox]:checked:before{clip-path:polygon(14% 44%,0 65%,50% 100%,100% 16%,80% 0,43% 62%);height:60%;width:60%}input[type=radio]{border-radius:100%}input[type=radio]:checked:before{border-radius:100%;height:40%;width:40%}.color-picker__picker{border-radius:10px;border-radius:var(--radius);box-shadow:0 0 0 1px rgba(110,102,153,.1),0 5px 15px rgba(110,102,153,.25);box-shadow:var(--shadow-md);margin-top:6px;position:absolute;z-index:200;z-index:var(--z-index-overlay)}.color-picker__swatch,.swatch{border:1px solid rgba(0,0,0,.1);border:1px solid hsl(var(--palette-text-hsl)/.1);border-radius:4px;display:inline-block;height:1.5em;vertical-align:middle;width:1.5em}.combobox{position:relative}.combobox__list{left:0;max-width:none!important;position:absolute;top:100%;width:100%!important}.dialog{--color-bg:var(--palette-surface);background:#fff;background:var(--color-bg);border-radius:10px;border-radius:var(--radius);box-shadow:0 1px 4px rgba(110,102,153,.1);box-shadow:var(--shadow-sm);color:#000;color:var(--color-text);margin-left:auto;margin-right:auto;max-width:100%}.dialog__header{align-items:center;display:flex;gap:1rem;gap:var(--space-md);padding:2.25rem;padding:var(--space-xl);padding-bottom:0}.dialog__body{padding:2.25rem;padding:var(--space-xl)}.dialog--sm{width:45ch}@media (max-width:40rem){.dialog__body,.dialog__header{padding:max(1.5rem,min(3vw,2.25rem));padding:var(--space-gutter)}.dialog__header{padding-bottom:0}.container .dialog{border-radius:0;margin-left:calc(-1 * max(calc(1rem * 1.5), min(3vw, calc(calc(1rem * 1.5) * 1.5))))!important;margin-left:calc(var(--space-gutter)*-1)!important;margin-right:calc(-1 * max(calc(1rem * 1.5), min(3vw, calc(calc(1rem * 1.5) * 1.5))))!important;margin-right:calc(var(--space-gutter)*-1)!important;max-width:none;width:calc(100% + 2 * max(calc(1rem * 1.5), min(3vw, calc((1rem * 1.5) * 1.5))));width:calc(100% + 2 * max(calc(1rem * 1.5), min(3vw, calc(calc(1rem * 1.5) * 1.5))));width:calc(100% + var(--space-gutter)*2)}}.divider{align-items:center;color:#6e6699;color:var(--color-muted);display:flex;font-size:.8rem;font-size:var(--text-xxs);font-weight:600;font-weight:var(--weight-bold);padding:.44444rem 0;padding:var(--space-xs) 0;text-transform:uppercase}.divider:after,.divider:before{border-bottom:2px solid;content:\"\";flex-grow:1}.divider:not(:empty):before{margin-right:5px}.divider:not(:empty):after{margin-left:5px}.field{align-items:flex-start;-moz-column-gap:1rem;column-gap:1rem;-moz-column-gap:var(--space-md);column-gap:var(--space-md);display:flex;flex-wrap:wrap;row-gap:.44444rem;row-gap:var(--space-xs)}.field>:first-child:not(:only-child){flex-basis:20%;flex-grow:1;flex-shrink:0;min-width:15ch}.field>:nth-child(2){flex-basis:calc(80% - 1rem);flex-basis:calc(80% - var(--space-md));flex-grow:999;margin:0;min-width:0}.stacked-fields .field>:first-child{flex-basis:100%}.field__label{font-size:.875rem;font-size:var(--text-xs);font-weight:500;font-weight:var(--weight-medium)}.field__description,.field__status{color:#6e6699;color:var(--color-muted);font-size:.875rem;font-size:var(--text-xs)}.field__status{font-weight:500;font-weight:var(--weight-medium)}.has-error .field__status{color:#ad0000;color:var(--color-danger-text)}\n\n/*!\n    Theme: GitHub\n    Description: Light theme as seen on github.com\n    Author: github.com\n    Maintainer: @Hirse\n    Updated: 2021-05-15\n    Outdated base version: https://github.com/primer/github-syntax-light\n    Current colors taken from GitHub's CSS\n  */:root .hljs,[data-theme=light] .hljs{color:#24292e}:root .hljs-doctag,:root .hljs-keyword,:root .hljs-meta .hljs-keyword,:root .hljs-template-tag,:root .hljs-template-variable,:root .hljs-type,:root .hljs-variable.language_,[data-theme=light] .hljs-doctag,[data-theme=light] .hljs-keyword,[data-theme=light] .hljs-meta .hljs-keyword,[data-theme=light] .hljs-template-tag,[data-theme=light] .hljs-template-variable,[data-theme=light] .hljs-type,[data-theme=light] .hljs-variable.language_{color:#d73a49}:root .hljs-title,:root .hljs-title.class_,:root .hljs-title.class_.inherited__,:root .hljs-title.function_,[data-theme=light] .hljs-title,[data-theme=light] .hljs-title.class_,[data-theme=light] .hljs-title.class_.inherited__,[data-theme=light] .hljs-title.function_{color:#6f42c1}:root .hljs-attr,:root .hljs-attribute,:root .hljs-literal,:root .hljs-meta,:root .hljs-number,:root .hljs-operator,:root .hljs-selector-attr,:root .hljs-selector-class,:root .hljs-selector-id,:root .hljs-variable,[data-theme=light] .hljs-attr,[data-theme=light] .hljs-attribute,[data-theme=light] .hljs-literal,[data-theme=light] .hljs-meta,[data-theme=light] .hljs-number,[data-theme=light] .hljs-operator,[data-theme=light] .hljs-selector-attr,[data-theme=light] .hljs-selector-class,[data-theme=light] .hljs-selector-id,[data-theme=light] .hljs-variable{color:#005cc5}:root .hljs-meta .hljs-string,:root .hljs-regexp,:root .hljs-string,[data-theme=light] .hljs-meta .hljs-string,[data-theme=light] .hljs-regexp,[data-theme=light] .hljs-string{color:#032f62}:root .hljs-built_in,:root .hljs-symbol,[data-theme=light] .hljs-built_in,[data-theme=light] .hljs-symbol{color:#e36209}:root .hljs-code,:root .hljs-comment,:root .hljs-formula,[data-theme=light] .hljs-code,[data-theme=light] .hljs-comment,[data-theme=light] .hljs-formula{color:#6a737d}:root .hljs-name,:root .hljs-quote,:root .hljs-selector-pseudo,:root .hljs-selector-tag,[data-theme=light] .hljs-name,[data-theme=light] .hljs-quote,[data-theme=light] .hljs-selector-pseudo,[data-theme=light] .hljs-selector-tag{color:#22863a}:root .hljs-subst,[data-theme=light] .hljs-subst{color:#24292e}:root .hljs-section,[data-theme=light] .hljs-section{color:#005cc5;font-weight:700}:root .hljs-bullet,[data-theme=light] .hljs-bullet{color:#735c0f}:root .hljs-emphasis,[data-theme=light] .hljs-emphasis{color:#24292e;font-style:italic}:root .hljs-strong,[data-theme=light] .hljs-strong{color:#24292e;font-weight:700}:root .hljs-addition,[data-theme=light] .hljs-addition{background-color:#f0fff4;color:#22863a}:root .hljs-deletion,[data-theme=light] .hljs-deletion{background-color:#ffeef0;color:#b31d28}\n\n/*!\n    Theme: GitHub Dark\n    Description: Dark theme as seen on github.com\n    Author: github.com\n    Maintainer: @Hirse\n    Updated: 2021-05-15\n    Outdated base version: https://github.com/primer/github-syntax-dark\n    Current colors taken from GitHub's CSS\n  */[data-theme=dark] .hljs{color:#c9d1d9}[data-theme=dark] .hljs-doctag,[data-theme=dark] .hljs-keyword,[data-theme=dark] .hljs-meta .hljs-keyword,[data-theme=dark] .hljs-template-tag,[data-theme=dark] .hljs-template-variable,[data-theme=dark] .hljs-type,[data-theme=dark] .hljs-variable.language_{color:#ff7b72}[data-theme=dark] .hljs-title,[data-theme=dark] .hljs-title.class_,[data-theme=dark] .hljs-title.class_.inherited__,[data-theme=dark] .hljs-title.function_{color:#d2a8ff}[data-theme=dark] .hljs-attr,[data-theme=dark] .hljs-attribute,[data-theme=dark] .hljs-literal,[data-theme=dark] .hljs-meta,[data-theme=dark] .hljs-number,[data-theme=dark] .hljs-operator,[data-theme=dark] .hljs-selector-attr,[data-theme=dark] .hljs-selector-class,[data-theme=dark] .hljs-selector-id,[data-theme=dark] .hljs-variable{color:#79c0ff}[data-theme=dark] .hljs-meta .hljs-string,[data-theme=dark] .hljs-regexp,[data-theme=dark] .hljs-string{color:#a5d6ff}[data-theme=dark] .hljs-built_in,[data-theme=dark] .hljs-symbol{color:#ffa657}[data-theme=dark] .hljs-code,[data-theme=dark] .hljs-comment,[data-theme=dark] .hljs-formula{color:#8b949e}[data-theme=dark] .hljs-name,[data-theme=dark] .hljs-quote,[data-theme=dark] .hljs-selector-pseudo,[data-theme=dark] .hljs-selector-tag{color:#7ee787}[data-theme=dark] .hljs-subst{color:#c9d1d9}[data-theme=dark] .hljs-section{color:#1f6feb;font-weight:700}[data-theme=dark] .hljs-bullet{color:#f2cc60}[data-theme=dark] .hljs-emphasis{color:#c9d1d9;font-style:italic}[data-theme=dark] .hljs-strong{color:#c9d1d9;font-weight:700}[data-theme=dark] .hljs-addition{background-color:#033a16;color:#aff5b4}[data-theme=dark] .hljs-deletion{background-color:#67060c;color:#ffdcd7}.icon{stroke-width:1.5;display:inline-block;flex-shrink:0;height:1.2em;-o-object-fit:contain;object-fit:contain;vertical-align:-.2em;width:1.2em}.icon--thin{stroke-width:1}.icon--thick{stroke-width:2}.icon--narrow{margin-left:-.2em;margin-right:-.2em}.with-icon{align-items:baseline;display:inline-flex;vertical-align:baseline}.with-icon>.icon{align-self:center;margin-right:.2em}.with-icon>.icon:not(:first-child){margin-left:.2em}.icon-tabler-lock circle{display:none}.dot{background:currentColor;border-radius:100%;display:inline-block;height:10px;margin:.3em .1em;vertical-align:-5px;width:10px}.input,input:where([type=date],[type=datetime-local],[type=email],[type=file],[type=month],[type=number],[type=password],[type=search],[type=tel],[type=text],[type=time],[type=url],[type=week]),select,textarea{-webkit-appearance:none;background:#f9f8fc;background:var(--color-bg);border:1px solid rgba(110,102,153,.25);border:1px solid var(--color-stroke);border-radius:10px;border-radius:var(--control-radius);box-sizing:border-box;color:#000;color:var(--color-text);display:block;height:2.5em;height:var(--control-height);padding:.55em .7em;width:100%}input:where([type=date],[type=datetime-local],[type=email],[type=file],[type=month],[type=number],[type=password],[type=search],[type=tel],[type=text],[type=time],[type=url],[type=week]):not(.does-not-exist):focus{border-color:#5830f8;border-color:var(--color-accent);box-shadow:inset 0 0 0 1px #5830f8;box-shadow:inset 0 0 0 1px var(--color-accent);outline:none}input:where([type=date],[type=datetime-local],[type=email],[type=file],[type=month],[type=number],[type=password],[type=search],[type=tel],[type=text],[type=time],[type=url],[type=week]):not(.does-not-exist):focus-within{border-color:#5830f8;border-color:var(--color-accent);box-shadow:inset 0 0 0 1px #5830f8;box-shadow:inset 0 0 0 1px var(--color-accent);outline:none}textarea:not(.does-not-exist):focus-within{border-color:#5830f8;border-color:var(--color-accent);box-shadow:inset 0 0 0 1px #5830f8;box-shadow:inset 0 0 0 1px var(--color-accent);outline:none}select:not(.does-not-exist):focus-within{border-color:#5830f8;border-color:var(--color-accent);box-shadow:inset 0 0 0 1px #5830f8;box-shadow:inset 0 0 0 1px var(--color-accent);outline:none}.input:focus-within{border-color:#5830f8;border-color:var(--color-accent);box-shadow:inset 0 0 0 1px #5830f8;box-shadow:inset 0 0 0 1px var(--color-accent);outline:none}.input:focus,select:not(.does-not-exist):focus,textarea:not(.does-not-exist):focus{border-color:#5830f8;border-color:var(--color-accent);box-shadow:inset 0 0 0 1px #5830f8;box-shadow:inset 0 0 0 1px var(--color-accent);outline:none}input:where([type=date],[type=datetime-local],[type=email],[type=file],[type=month],[type=number],[type=password],[type=search],[type=tel],[type=text],[type=time],[type=url],[type=week]):not(.does-not-exist):disabled{background:rgba(110,102,153,.1);background:var(--color-fill)}.input:disabled,select:not(.does-not-exist):disabled,textarea:not(.does-not-exist):disabled{background:rgba(110,102,153,.1);background:var(--color-fill)}.has-error input:where([type=date],[type=datetime-local],[type=email],[type=file],[type=month],[type=number],[type=password],[type=search],[type=tel],[type=text],[type=time],[type=url],[type=week]):not(.does-not-exist){border-color:#c20000;border-color:var(--color-danger)}.has-error .input,.has-error select:not(.does-not-exist),.has-error textarea:not(.does-not-exist){border-color:#c20000;border-color:var(--color-danger)}input[type=file]{overflow:hidden}input[type=file]:not(:disabled):not([readonly]){cursor:pointer}input[type=file]::file-selector-button{background:none;border:0;color:#4013f6;color:var(--color-accent-text);font-weight:600;font-weight:var(--weight-bold);margin:0;padding:0 1rem 0 0;padding:0 var(--space-md) 0 0;pointer-events:none;width:auto}::-moz-placeholder{color:#6e6699;color:var(--color-muted)}::placeholder{color:#6e6699;color:var(--color-muted)}.textarea,select[multiple],textarea{border-radius:10px;border-radius:var(--radius);height:auto;padding:.7rem}.select,select:not([multiple]){background-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' class='h-5 w-5' viewBox='0 0 20 20' fill='%23888'%3E%3Cpath fill-rule='evenodd' d='M10 3a1 1 0 0 1 .707.293l3 3a1 1 0 0 1-1.414 1.414L10 5.414 7.707 7.707a1 1 0 0 1-1.414-1.414l3-3A1 1 0 0 1 10 3zm-3.707 9.293a1 1 0 0 1 1.414 0L10 14.586l2.293-2.293a1 1 0 0 1 1.414 1.414l-3 3a1 1 0 0 1-1.414 0l-3-3a1 1 0 0 1 0-1.414z' clip-rule='evenodd'/%3E%3C/svg%3E\");background-position:center right .5em;background-repeat:no-repeat;background-size:1.25em;cursor:default;padding-right:2em}:invalid+.hide-if-invalid{visibility:hidden}.input-container{--input-container-padding-start:2.6em;--input-container-padding-end:2.6em;align-items:center;display:flex}.input-container>:not(input):not(.input):not(select):not(script){align-items:center;display:flex;justify-content:center;position:relative}.input-container>:not(input):not(.input):not(select):not(script):first-child{margin-right:calc(var(--input-container-padding-start)*-1);width:var(--input-container-padding-start)}.input-container>:not(input):not(.input):not(select):not(script):last-child{margin-left:calc(var(--input-container-padding-end)*-1);width:var(--input-container-padding-end)}.input-container>.input:nth-child(2),.input-container>input:not(.does-not-exist):nth-child(2),.input-container>select:not(.does-not-exist):nth-child(2){padding-left:var(--input-container-padding-start)}.input-container>.input:nth-last-child(2),.input-container>input:not(.does-not-exist):nth-last-child(2),.input-container>select:not(.does-not-exist):nth-last-child(2){padding-right:var(--input-container-padding-end)}.mention{border-radius:10px;border-radius:var(--radius);color:inherit;font-weight:600;font-weight:var(--weight-bold)}.mention--self{background:#ffffd6;background:var(--color-warning-soft);border-radius:4px;color:#7a7a00;color:var(--color-warning-text);margin:-2px;padding:2px}.menu{--color-bg:var(--palette-surface);--color-text:var(--palette-text);background:#fff;background:var(--color-bg);border-radius:10px;border-radius:var(--radius);box-shadow:0 0 0 1px rgba(110,102,153,.1),0 5px 15px rgba(110,102,153,.25);box-shadow:var(--shadow-md);color:#000;color:var(--color-text);display:block;font-size:.875rem;font-size:var(--text-xs);max-width:30ch;min-width:15ch;overflow:auto;padding:.44444rem;padding:var(--space-xs);width:-moz-max-content;width:max-content;z-index:200;z-index:var(--z-index-overlay)}.menu--lg{max-width:50ch}.menu-item{align-items:flex-start;-webkit-appearance:none;-moz-appearance:none;appearance:none;background:transparent;border:0;border-radius:6.66667px;border-radius:calc(var(--radius)*2/3);color:#000;color:var(--color-text);display:flex;gap:.44444rem;gap:var(--space-xs);margin:0;padding:.44444rem;padding:var(--space-xs);text-align:left;-webkit-text-decoration:none!important;text-decoration:none!important;transform:none!important;width:100%}.menu-item.is-disabled,.menu-item:disabled{cursor:default;opacity:.5}.menu-item.is-inert{cursor:default}.menu-item:not(:disabled):not(.is-disabled):not(.is-inert){cursor:pointer}.menu-item.is-focused:not(:disabled):not(.is-disabled):not(.is-inert),.menu-item.is-hovered:not(:disabled):not(.is-disabled):not(.is-inert),.menu-item:not(:disabled):not(.is-disabled):not(.is-inert):focus,.menu-item:not(:disabled):not(.is-disabled):not(.is-inert):hover,.menu-item[aria-selected=true]:not(:disabled):not(.is-disabled):not(.is-inert){filter:brightness(.95);filter:var(--filter-hover)}.menu-item:not(:disabled):not(.is-disabled):not(.is-inert):active{filter:brightness(.9);filter:var(--filter-active);transform:scale(.97)}.menu-item.is-focused:not(.is-inert),.menu-item.is-hovered:not(.is-inert),.menu-item:not(.is-inert):focus,.menu-item:not(.is-inert):hover,.menu-item[aria-selected=true]:not(.is-inert){background:rgba(110,102,153,.1);background:var(--color-fill);outline:none}.menu-item.color-danger.is-hovered:not(.is-inert),.menu-item.color-danger:not(.is-inert):hover{background:#c20000;background:var(--color-danger);color:#fff;color:var(--color-danger-contrast)}.menu-item.is-active,.menu-item[aria-checked=true],.menu-item[aria-current=page]{background:#dfd8fe;background:var(--color-accent-soft);color:#4013f6;color:var(--color-accent-text)}.menu-item__title{display:block;font-weight:500;font-weight:var(--weight-medium)}.menu-item__description{color:#6e6699;color:var(--color-muted);display:block;font-size:90%;margin-top:.2963rem;margin-top:var(--space-xxs)}.menu-item__check{margin-left:auto}.menu-item:not(.is-active):not([aria-current=page]):not([aria-checked=true]) .menu-item__check{visibility:hidden}.menu-divider{margin:.44444rem;margin:var(--space-xs)}.menu-divider+.menu-divider,.menu>.menu-divider:first-child,.menu>.menu-divider:last-child{display:none}.menu-heading{color:#6e6699;color:var(--color-muted);font-family:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif;font-family:var(--font-text);font-size:.8rem;font-size:var(--text-xxs);font-weight:500;font-weight:var(--weight-medium);padding:.44444rem;padding:var(--space-xs);text-transform:uppercase}.menu-sticky{background:#f9f8fc;background:var(--color-bg);margin-top:-.66667rem;margin-top:calc(var(--space-sm)*-1);padding-top:.44444rem;padding-top:var(--space-xs);position:sticky;top:-.66667rem;top:calc(var(--space-sm)*-1);z-index:1}ui-popup:has(>.drawer)::part(backdrop){background:rgba(0,0,0,.5);background:var(--color-overlay)}.drawer{background:#f9f8fc;background:var(--color-bg);box-shadow:0 0 0 1px rgba(110,102,153,.1),0 5px 15px rgba(110,102,153,.25);box-shadow:var(--shadow-md);height:100vh;left:0!important;margin:0;max-height:none!important;overflow:auto;padding:1.5rem;padding:var(--space-lg);position:fixed!important;top:0!important;width:min(70vw,30ch);z-index:200;z-index:var(--z-index-overlay)}.drawer.enter-from,.drawer.leave-to{opacity:1;transform:translateX(-100%)}.drawer--right{left:auto!important;right:0!important}.drawer--right.enter-from,.drawer--right.leave-to{transform:translateX(100%)}ui-modal{align-items:flex-start;bottom:0;justify-content:center;left:0;overflow:auto;padding:5.0625rem max(1.5rem,min(3vw,2.25rem));padding:var(--space-xxxl) var(--space-gutter);position:fixed;right:0;top:0;z-index:200;z-index:var(--z-index-overlay)}ui-modal:not([hidden]){display:flex}ui-modal .dialog{border:0;box-shadow:0 0 0 1px rgba(110,102,153,.1),0 5px 15px rgba(110,102,153,.25);box-shadow:var(--shadow-md)}ui-modal::part(backdrop){background:rgba(0,0,0,.5);background:var(--color-overlay)}ui-modal::part(content){max-width:100%}@media (prefers-reduced-motion:no-preference){ui-modal.enter-active,ui-modal.leave-active{transition:opacity .3s}ui-modal.enter-from,ui-modal.leave-to{opacity:0}ui-modal.enter-active::part(content),ui-modal.leave-active::part(content){transition:transform .3s cubic-bezier(.54,1.12,.38,1.11),opacity .3s}ui-modal.enter-from::part(content),ui-modal.leave-to::part(content){opacity:0;transform:scale(.7)}}.nav{list-style:none;margin-left:0;margin-right:0;padding-left:0;padding-right:0}.nav-heading{color:#6e6699;color:var(--color-muted);font-family:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif;font-family:var(--font-text);font-size:.8rem;font-size:var(--text-xxs);font-weight:500;font-weight:var(--weight-medium);margin-bottom:.44444rem;margin-bottom:var(--space-xs);padding:0 .66667rem;padding:0 var(--space-sm);text-transform:uppercase;width:100%}*+.nav-heading{margin-top:1.5rem;margin-top:var(--space-lg)}.nav-link{align-items:center;border-radius:10px;border-radius:var(--control-radius);color:#000;color:var(--color-text);display:flex;font-weight:500;font-weight:var(--weight-medium);gap:.44444rem;gap:var(--space-xs);min-height:2.5em;min-height:var(--control-height);padding:.44444rem .66667rem;padding:var(--space-xs) var(--space-sm);-webkit-text-decoration:none!important;text-decoration:none!important}.nav-link>:not(.icon){overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.nav-link>.icon{font-size:120%}.nav-link>[class^=icon-fa]{padding:2px}.nav-link>.badge{margin-left:auto}.nav-link.is-disabled,.nav-link:disabled{cursor:default;opacity:.5}.nav-link.is-inert{cursor:default}.nav-link:not(:disabled):not(.is-disabled):not(.is-inert){cursor:pointer}.nav-link.is-focused:not(:disabled):not(.is-disabled):not(.is-inert),.nav-link.is-hovered:not(:disabled):not(.is-disabled):not(.is-inert),.nav-link:not(:disabled):not(.is-disabled):not(.is-inert):focus,.nav-link:not(:disabled):not(.is-disabled):not(.is-inert):hover,.nav-link[aria-selected=true]:not(:disabled):not(.is-disabled):not(.is-inert){filter:brightness(.95);filter:var(--filter-hover)}.nav-link:not(:disabled):not(.is-disabled):not(.is-inert):active{filter:brightness(.9);filter:var(--filter-active);transform:scale(.97)}.nav-link{transform:none!important}.nav-link:focus,.nav-link:hover{background:rgba(110,102,153,.1);background:var(--color-fill)}.nav-link.is-active,.nav-link[aria-current=page]{background:#dfd8fe;background:var(--color-accent-soft);color:#4013f6;color:var(--color-accent-text)}.nav-link.is-active .badge,.nav-link[aria-current=page] .badge{background:inherit;color:inherit}@media (max-width:52rem){.collapsible-nav{flex:1 1 10ch;max-width:-moz-fit-content;max-width:fit-content;min-width:0}}@media (min-width:52.001rem){.collapsible-nav>:first-child{display:none}.collapsible-nav>:last-child{display:contents!important}}.placeholder{align-items:center;color:#6e6699;color:var(--color-muted);display:flex;flex-direction:column;gap:.66667rem;gap:var(--space-sm);padding:2.25rem 0;padding:var(--space-xl) 0;text-align:center}.placeholder>*{max-width:50ch}.placeholder__icon{stroke-width:1.5px;height:4em;width:4em}:root{--sidebar-width:25ch}.with-sidebar{align-items:flex-start;display:flex;gap:max(1.5rem,min(3vw,2.25rem));gap:var(--space-gutter);justify-content:center}.with-sidebar>:not(.sidebar){flex-grow:999;min-width:0}.sidebar{display:flex}@media (max-width:52rem){.with-sidebar{align-items:stretch;flex-direction:column;gap:1rem;gap:var(--space-md)}.with-sidebar>:not(.sidebar){width:100%}.sidebar{flex-direction:row;flex-wrap:wrap}.sidebar--bottom{align-items:center;background:#f9f8fc;background:var(--color-bg);bottom:0;flex-basis:auto!important;flex-direction:row!important;flex-wrap:nowrap!important;margin-left:calc(-1 * max(calc(1rem * 1.5), min(3vw, calc(calc(1rem * 1.5) * 1.5))));margin-left:calc(var(--space-gutter)*-1);margin-right:calc(-1 * max(calc(1rem * 1.5), min(3vw, calc(calc(1rem * 1.5) * 1.5))));margin-right:calc(var(--space-gutter)*-1);padding:.66667rem max(1.5rem,min(3vw,2.25rem));padding:var(--space-sm) var(--space-gutter);position:sticky;width:calc(100% + 2 * max(calc(1rem * 1.5), min(3vw, calc((1rem * 1.5) * 1.5))))!important;width:calc(100% + 2 * max(calc(1rem * 1.5), min(3vw, calc(calc(1rem * 1.5) * 1.5))))!important;width:calc(100% + var(--space-gutter)*2)!important;z-index:100;z-index:var(--z-index-header)}.sidebar--bottom.is-stuck{box-shadow:0 1px 4px rgba(110,102,153,.1);box-shadow:var(--shadow-sm)}}@media (min-width:52.001rem){.sidebar{flex-basis:25ch;flex-basis:var(--sidebar-width);flex-direction:column;flex-shrink:0}.sidebar--sticky{box-sizing:content-box;margin-bottom:calc(-1 * max(calc(1rem * 1.5), min(3vw, calc(calc(1rem * 1.5) * 1.5))));margin-bottom:calc(var(--space-gutter)*-1);margin-top:calc(-1 * max(calc(1rem * 1.5), min(3vw, calc(calc(1rem * 1.5) * 1.5))));margin-top:calc(var(--space-gutter)*-1);max-height:calc(100vh - 4rem - 2 * max(calc(1rem * 1.5), min(3vw, calc((1rem * 1.5) * 1.5))));max-height:calc(100vh - 4rem - 2 * max(calc(1rem * 1.5), min(3vw, calc(calc(1rem * 1.5) * 1.5))));max-height:calc(100vh - var(--header-height) - var(--space-gutter)*2);overflow:auto;padding-bottom:max(1.5rem,min(3vw,2.25rem));padding-bottom:var(--space-gutter);padding-top:max(1.5rem,min(3vw,2.25rem));padding-top:var(--space-gutter);position:sticky;top:4rem;top:var(--header-height)}.sidebar__collapsed{display:none}}.skip-link{background-color:#5830f8;background-color:var(--color-accent);color:#fff;color:var(--color-accent-contrast);padding:.44444rem;padding:var(--space-xs);position:absolute;top:-10rem}.skip-link:focus{left:.44444rem;left:var(--space-xs);position:absolute;top:.44444rem;top:var(--space-xs);z-index:200;z-index:var(--z-index-overlay)}.spinner{align-items:center;color:#6e6699;color:var(--color-muted);display:inline-flex;flex-direction:column;gap:.66667rem;gap:var(--space-sm)}.spinner:before{animation:spinner .6s linear infinite;border:.35em solid rgba(110,102,153,.1);border-top-color:rgba(110,102,153,.25);border:.35em solid var(--color-fill);border-radius:50%;border-top-color:var(--color-stroke);content:\"\";display:block;height:2em;margin:0 auto;width:2em}.spinner--sm{padding:0;vertical-align:-.3em}.spinner--sm:before{border-width:.25em;height:1.2em;width:1.2em}.spinner--block{display:flex;padding:1rem;padding:var(--space-md)}@keyframes spinner{0%{transform:rotate(0deg)}to{transform:rotate(359deg)}}.table-container{max-width:100%;overflow:auto;width:-moz-fit-content;width:fit-content}.table-container *{word-wrap:normal}.table-container table{border:0;width:100%}table{border-collapse:collapse;border-spacing:0}table,table td,table th{border:1px solid rgba(110,102,153,.1);border:1px solid var(--color-fill)}table td,table th{padding:.66667rem;padding:var(--space-sm);text-align:left}table th{font-weight:600;font-weight:var(--weight-bold)}.table,.table-container table{border-collapse:separate;font-size:.875rem;font-size:var(--text-xs)}.table-container table td,.table-container table th,.table:not(does-not-exist) td,.table:not(does-not-exist) th{border-width:1px 0 0;padding:.66667rem;padding:var(--space-sm);text-align:left}.table-container table th,.table:not(does-not-exist) th{font-weight:600;font-weight:var(--weight-bold)}.table-container table thead td,.table-container table thead th,.table:not(does-not-exist) thead td,.table:not(does-not-exist) thead th{border-width:0}td.choice-cell{min-width:4ch;padding:0;position:relative}td.choice-cell>*{align-items:center;bottom:0;cursor:pointer;display:flex;justify-content:center;left:0;position:absolute;right:0;top:0}.is-highlighted,td.choice-cell>.is-highlighted,td.choice-cell>:not(.is-disabled):hover{background:rgba(110,102,153,.1);background:var(--color-fill)}.tabs{align-items:center;display:flex;flex-wrap:wrap;margin-left:-.66667rem;margin-left:calc(var(--space-sm)*-1);margin-right:-.66667rem;margin-right:calc(var(--space-sm)*-1)}.tab{color:#6e6699;color:var(--color-muted);flex-shrink:0;font-weight:500;font-weight:var(--weight-medium);min-width:2ch;overflow:hidden;padding:.44444rem .66667rem;padding:var(--space-xs) var(--space-sm);position:relative;text-align:center;-webkit-text-decoration:none!important;text-decoration:none!important;text-overflow:ellipsis;white-space:nowrap}.tab.is-hovered:not(.is-disabled):not([aria-disabled=true]),.tab:not(.is-disabled):not([aria-disabled=true]):hover{color:#000;color:var(--color-text)}.tab.is-active,.tab[aria-current=page],.tab[aria-selected=true]{color:#4013f6!important;color:var(--color-accent-text)!important}.tab.is-active:after,.tab[aria-current=page]:after,.tab[aria-selected=true]:after{background:#dfd8fe;background:var(--color-accent-soft);border-radius:10px;border-radius:var(--radius);bottom:0;content:\"\";height:4px;left:50%;min-width:2ch;position:absolute;transform:translateX(-50%);width:calc(100% - 1.33333rem);width:calc(100% - var(--space-sm)*2)}.tab.is-disabled,.tab[aria-disabled=true]{cursor:default;opacity:.3}.tabs--vertical{align-items:stretch;flex-direction:column;flex-wrap:nowrap;font-size:.875rem;font-size:var(--text-xs);margin-right:0}.tabs--vertical .tab{display:block;padding:.2963rem .66667rem;padding:var(--space-xxs) var(--space-sm);text-align:left}.tabs--vertical .tab.is-active:after,.tabs--vertical .tab[aria-current=page]:after,.tabs--vertical .tab[aria-selected=true]:after{bottom:.2963rem;bottom:var(--space-xxs);height:calc(100% - .59259rem);height:calc(100% - var(--space-xxs)*2);left:0;min-width:0;transform:none;width:4px}ui-tooltip{clip:rect(0 0 0 0);clip-path:inset(50%);height:1px;overflow:hidden;position:absolute;white-space:nowrap;width:1px}.tooltip{--color-text:var(--color-emphasis-contrast);--color-muted:hsla(0,0%,100%,.5);--color-accent:var(--color-text);background:#2f3641;background:var(--color-emphasis);border-radius:10px;border-radius:var(--radius);box-shadow:1rem;box-shadow:var(--space-md);color:#fff;color:var(--color-emphasis-contrast);font-size:.875rem;font-size:var(--text-xs);font-weight:500;font-weight:var(--weight-medium);max-width:30ch;overflow:hidden;padding:.44444rem .66667rem;padding:var(--space-xs) var(--space-sm);text-align:center;z-index:200;z-index:var(--z-index-overlay)}.tooltip small{color:#6e6699;color:var(--color-muted);font-size:.8rem;font-size:var(--text-xxs)}.tooltip.enter-active,.tooltip.leave-active{transition:opacity .2s,transform .2s}.tooltip.enter-from,.tooltip.leave-to{opacity:0;transform:scale(.95)}.tooltip--block{font-size:.8rem;font-size:var(--text-xxs);padding:.44444rem .66667rem;padding:var(--space-xs) var(--space-sm);text-align:left}turbo-frame{display:block}.busy-spinner[aria-busy=true]{min-height:4em;position:relative;visibility:hidden}.busy-spinner[aria-busy=true]:after{animation:spinner .6s linear infinite;border:.35em solid rgba(110,102,153,.1);border-top-color:rgba(110,102,153,.25);border:.35em solid var(--color-fill);border-radius:50%;border-top-color:var(--color-stroke);content:\"\";height:2em;left:calc(50% - 1em);position:absolute;top:1rem;top:var(--space-md);visibility:visible;width:2em;z-index:2}.turbo-progress-bar{background-color:#5830f8;background-color:var(--color-accent)}.next-page{position:relative}.next-page:before{bottom:-100px;content:\"\";position:absolute;top:-100px}.user-label{align-items:baseline;color:inherit;display:inline-flex;gap:.2963rem;gap:var(--space-xxs);-webkit-text-decoration:none;text-decoration:none;vertical-align:baseline}.user-label .avatar{align-self:center;height:18px;width:18px}.emoji-picker{max-width:none;padding:0}.emoji-picker emoji-picker{--border-color:var(--color-stroke);--border-size:0;--background:var(--color-bg);--button-hover-background:var(--color-fill);--button-active-background:var(--color-fill);--category-font-color:var(--color-muted);--indicator-color:var(--color-accent);--input-border-color:var(--color-stroke);--input-border-radius:var(--radius);--input-font-color:var(--color-text);--input-placeholder-color:var(--color-muted);--input-padding:0.4em 0.7em;--outline-color:var(--color-accent)}.visually-hidden{clip:rect(0 0 0 0);clip-path:inset(50%);height:1px;overflow:hidden;position:absolute;white-space:nowrap;width:1px}.container{margin-left:auto;margin-right:auto;max-width:72rem;padding-left:max(1.5rem,min(3vw,2.25rem));padding-left:var(--space-gutter);padding-right:max(1.5rem,min(3vw,2.25rem));padding-right:var(--space-gutter);width:100%}.section{padding-bottom:max(1.5rem,min(3vw,2.25rem));padding-bottom:var(--space-gutter);padding-top:max(1.5rem,min(3vw,2.25rem));padding-top:var(--space-gutter)}.measure{max-width:92ch;max-width:var(--measure)}.gap-px{gap:1px;gap:var(--space-px)}.gap-x-px{-moz-column-gap:1px;column-gap:1px;-moz-column-gap:var(--space-px);column-gap:var(--space-px)}.gap-y-px{row-gap:1px;row-gap:var(--space-px)}.p-px{padding:1px;padding:var(--space-px)}.px-px{padding-left:1px;padding-left:var(--space-px);padding-right:1px;padding-right:var(--space-px)}.py-px{padding-bottom:1px;padding-bottom:var(--space-px);padding-top:1px;padding-top:var(--space-px)}.m-px{margin:1px;margin:var(--space-px)}.mx-px{margin-left:1px;margin-left:var(--space-px);margin-right:1px;margin-right:var(--space-px)}.my-px{margin-bottom:1px;margin-bottom:var(--space-px)}.mt-px,.my-px{margin-top:1px;margin-top:var(--space-px)}.-m-px{margin:-1px;margin:calc(var(--space-px)*-1)}.-mx-px{margin-left:-1px;margin-left:calc(var(--space-px)*-1);margin-right:-1px;margin-right:calc(var(--space-px)*-1)}.-my-px{margin-bottom:-1px;margin-bottom:calc(var(--space-px)*-1)}.-mt-px,.-my-px{margin-top:-1px;margin-top:calc(var(--space-px)*-1)}.gap-xxs{gap:.2963rem;gap:var(--space-xxs)}.gap-x-xxs{-moz-column-gap:.2963rem;column-gap:.2963rem;-moz-column-gap:var(--space-xxs);column-gap:var(--space-xxs)}.gap-y-xxs{row-gap:.2963rem;row-gap:var(--space-xxs)}.p-xxs{padding:.2963rem;padding:var(--space-xxs)}.px-xxs{padding-left:.2963rem;padding-left:var(--space-xxs);padding-right:.2963rem;padding-right:var(--space-xxs)}.py-xxs{padding-bottom:.2963rem;padding-bottom:var(--space-xxs);padding-top:.2963rem;padding-top:var(--space-xxs)}.m-xxs{margin:.2963rem;margin:var(--space-xxs)}.mx-xxs{margin-left:.2963rem;margin-left:var(--space-xxs);margin-right:.2963rem;margin-right:var(--space-xxs)}.my-xxs{margin-bottom:.2963rem;margin-bottom:var(--space-xxs)}.mt-xxs,.my-xxs{margin-top:.2963rem;margin-top:var(--space-xxs)}.-m-xxs{margin:-.2963rem;margin:calc(var(--space-xxs)*-1)}.-mx-xxs{margin-left:-.2963rem;margin-left:calc(var(--space-xxs)*-1);margin-right:-.2963rem;margin-right:calc(var(--space-xxs)*-1)}.-my-xxs{margin-bottom:-.2963rem;margin-bottom:calc(var(--space-xxs)*-1)}.-mt-xxs,.-my-xxs{margin-top:-.2963rem;margin-top:calc(var(--space-xxs)*-1)}.gap-xs{gap:.44444rem;gap:var(--space-xs)}.gap-x-xs{-moz-column-gap:.44444rem;column-gap:.44444rem;-moz-column-gap:var(--space-xs);column-gap:var(--space-xs)}.gap-y-xs{row-gap:.44444rem;row-gap:var(--space-xs)}.p-xs{padding:.44444rem;padding:var(--space-xs)}.px-xs{padding-left:.44444rem;padding-left:var(--space-xs);padding-right:.44444rem;padding-right:var(--space-xs)}.py-xs{padding-bottom:.44444rem;padding-bottom:var(--space-xs);padding-top:.44444rem;padding-top:var(--space-xs)}.m-xs{margin:.44444rem;margin:var(--space-xs)}.mx-xs{margin-left:.44444rem;margin-left:var(--space-xs);margin-right:.44444rem;margin-right:var(--space-xs)}.my-xs{margin-bottom:.44444rem;margin-bottom:var(--space-xs)}.mt-xs,.my-xs{margin-top:.44444rem;margin-top:var(--space-xs)}.-m-xs{margin:-.44444rem;margin:calc(var(--space-xs)*-1)}.-mx-xs{margin-left:-.44444rem;margin-left:calc(var(--space-xs)*-1);margin-right:-.44444rem;margin-right:calc(var(--space-xs)*-1)}.-my-xs{margin-bottom:-.44444rem;margin-bottom:calc(var(--space-xs)*-1)}.-mt-xs,.-my-xs{margin-top:-.44444rem;margin-top:calc(var(--space-xs)*-1)}.gap-sm{gap:.66667rem;gap:var(--space-sm)}.gap-x-sm{-moz-column-gap:.66667rem;column-gap:.66667rem;-moz-column-gap:var(--space-sm);column-gap:var(--space-sm)}.gap-y-sm{row-gap:.66667rem;row-gap:var(--space-sm)}.p-sm{padding:.66667rem;padding:var(--space-sm)}.px-sm{padding-left:.66667rem;padding-left:var(--space-sm);padding-right:.66667rem;padding-right:var(--space-sm)}.py-sm{padding-bottom:.66667rem;padding-bottom:var(--space-sm);padding-top:.66667rem;padding-top:var(--space-sm)}.m-sm{margin:.66667rem;margin:var(--space-sm)}.mx-sm{margin-left:.66667rem;margin-left:var(--space-sm);margin-right:.66667rem;margin-right:var(--space-sm)}.my-sm{margin-bottom:.66667rem;margin-bottom:var(--space-sm)}.mt-sm,.my-sm{margin-top:.66667rem;margin-top:var(--space-sm)}.-m-sm{margin:-.66667rem;margin:calc(var(--space-sm)*-1)}.-mx-sm{margin-left:-.66667rem;margin-left:calc(var(--space-sm)*-1);margin-right:-.66667rem;margin-right:calc(var(--space-sm)*-1)}.-my-sm{margin-bottom:-.66667rem;margin-bottom:calc(var(--space-sm)*-1)}.-mt-sm,.-my-sm{margin-top:-.66667rem;margin-top:calc(var(--space-sm)*-1)}.gap-md{gap:1rem;gap:var(--space-md)}.gap-x-md{-moz-column-gap:1rem;column-gap:1rem;-moz-column-gap:var(--space-md);column-gap:var(--space-md)}.gap-y-md{row-gap:1rem;row-gap:var(--space-md)}.p-md{padding:1rem;padding:var(--space-md)}.px-md{padding-left:1rem;padding-left:var(--space-md);padding-right:1rem;padding-right:var(--space-md)}.py-md{padding-bottom:1rem;padding-bottom:var(--space-md);padding-top:1rem;padding-top:var(--space-md)}.m-md{margin:1rem;margin:var(--space-md)}.mx-md{margin-left:1rem;margin-left:var(--space-md);margin-right:1rem;margin-right:var(--space-md)}.my-md{margin-bottom:1rem;margin-bottom:var(--space-md)}.mt-md,.my-md{margin-top:1rem;margin-top:var(--space-md)}.-m-md{margin:-1rem;margin:calc(var(--space-md)*-1)}.-mx-md{margin-left:-1rem;margin-left:calc(var(--space-md)*-1);margin-right:-1rem;margin-right:calc(var(--space-md)*-1)}.-my-md{margin-bottom:-1rem;margin-bottom:calc(var(--space-md)*-1)}.-mt-md,.-my-md{margin-top:-1rem;margin-top:calc(var(--space-md)*-1)}.gap-lg{gap:1.5rem;gap:var(--space-lg)}.gap-x-lg{-moz-column-gap:1.5rem;column-gap:1.5rem;-moz-column-gap:var(--space-lg);column-gap:var(--space-lg)}.gap-y-lg{row-gap:1.5rem;row-gap:var(--space-lg)}.p-lg{padding:1.5rem;padding:var(--space-lg)}.px-lg{padding-left:1.5rem;padding-left:var(--space-lg);padding-right:1.5rem;padding-right:var(--space-lg)}.py-lg{padding-bottom:1.5rem;padding-bottom:var(--space-lg);padding-top:1.5rem;padding-top:var(--space-lg)}.m-lg{margin:1.5rem;margin:var(--space-lg)}.mx-lg{margin-left:1.5rem;margin-left:var(--space-lg);margin-right:1.5rem;margin-right:var(--space-lg)}.my-lg{margin-bottom:1.5rem;margin-bottom:var(--space-lg)}.mt-lg,.my-lg{margin-top:1.5rem;margin-top:var(--space-lg)}.-m-lg{margin:-1.5rem;margin:calc(var(--space-lg)*-1)}.-mx-lg{margin-left:-1.5rem;margin-left:calc(var(--space-lg)*-1);margin-right:-1.5rem;margin-right:calc(var(--space-lg)*-1)}.-my-lg{margin-bottom:-1.5rem;margin-bottom:calc(var(--space-lg)*-1)}.-mt-lg,.-my-lg{margin-top:-1.5rem;margin-top:calc(var(--space-lg)*-1)}.gap-xl{gap:2.25rem;gap:var(--space-xl)}.gap-x-xl{-moz-column-gap:2.25rem;column-gap:2.25rem;-moz-column-gap:var(--space-xl);column-gap:var(--space-xl)}.gap-y-xl{row-gap:2.25rem;row-gap:var(--space-xl)}.p-xl{padding:2.25rem;padding:var(--space-xl)}.px-xl{padding-left:2.25rem;padding-left:var(--space-xl);padding-right:2.25rem;padding-right:var(--space-xl)}.py-xl{padding-bottom:2.25rem;padding-bottom:var(--space-xl);padding-top:2.25rem;padding-top:var(--space-xl)}.m-xl{margin:2.25rem;margin:var(--space-xl)}.mx-xl{margin-left:2.25rem;margin-left:var(--space-xl);margin-right:2.25rem;margin-right:var(--space-xl)}.my-xl{margin-bottom:2.25rem;margin-bottom:var(--space-xl)}.mt-xl,.my-xl{margin-top:2.25rem;margin-top:var(--space-xl)}.-m-xl{margin:-2.25rem;margin:calc(var(--space-xl)*-1)}.-mx-xl{margin-left:-2.25rem;margin-left:calc(var(--space-xl)*-1);margin-right:-2.25rem;margin-right:calc(var(--space-xl)*-1)}.-my-xl{margin-bottom:-2.25rem;margin-bottom:calc(var(--space-xl)*-1)}.-mt-xl,.-my-xl{margin-top:-2.25rem;margin-top:calc(var(--space-xl)*-1)}.gap-xxl{gap:3.375rem;gap:var(--space-xxl)}.gap-x-xxl{-moz-column-gap:3.375rem;column-gap:3.375rem;-moz-column-gap:var(--space-xxl);column-gap:var(--space-xxl)}.gap-y-xxl{row-gap:3.375rem;row-gap:var(--space-xxl)}.p-xxl{padding:3.375rem;padding:var(--space-xxl)}.px-xxl{padding-left:3.375rem;padding-left:var(--space-xxl);padding-right:3.375rem;padding-right:var(--space-xxl)}.py-xxl{padding-bottom:3.375rem;padding-bottom:var(--space-xxl);padding-top:3.375rem;padding-top:var(--space-xxl)}.m-xxl{margin:3.375rem;margin:var(--space-xxl)}.mx-xxl{margin-left:3.375rem;margin-left:var(--space-xxl);margin-right:3.375rem;margin-right:var(--space-xxl)}.my-xxl{margin-bottom:3.375rem;margin-bottom:var(--space-xxl)}.mt-xxl,.my-xxl{margin-top:3.375rem;margin-top:var(--space-xxl)}.-m-xxl{margin:-3.375rem;margin:calc(var(--space-xxl)*-1)}.-mx-xxl{margin-left:-3.375rem;margin-left:calc(var(--space-xxl)*-1);margin-right:-3.375rem;margin-right:calc(var(--space-xxl)*-1)}.-my-xxl{margin-bottom:-3.375rem;margin-bottom:calc(var(--space-xxl)*-1)}.-mt-xxl,.-my-xxl{margin-top:-3.375rem;margin-top:calc(var(--space-xxl)*-1)}.gap-xxxl{gap:5.0625rem;gap:var(--space-xxxl)}.gap-x-xxxl{-moz-column-gap:5.0625rem;column-gap:5.0625rem;-moz-column-gap:var(--space-xxxl);column-gap:var(--space-xxxl)}.gap-y-xxxl{row-gap:5.0625rem;row-gap:var(--space-xxxl)}.p-xxxl{padding:5.0625rem;padding:var(--space-xxxl)}.px-xxxl{padding-left:5.0625rem;padding-left:var(--space-xxxl);padding-right:5.0625rem;padding-right:var(--space-xxxl)}.py-xxxl{padding-bottom:5.0625rem;padding-bottom:var(--space-xxxl);padding-top:5.0625rem;padding-top:var(--space-xxxl)}.m-xxxl{margin:5.0625rem;margin:var(--space-xxxl)}.mx-xxxl{margin-left:5.0625rem;margin-left:var(--space-xxxl);margin-right:5.0625rem;margin-right:var(--space-xxxl)}.my-xxxl{margin-bottom:5.0625rem;margin-bottom:var(--space-xxxl)}.mt-xxxl,.my-xxxl{margin-top:5.0625rem;margin-top:var(--space-xxxl)}.-m-xxxl{margin:-5.0625rem;margin:calc(var(--space-xxxl)*-1)}.-mx-xxxl{margin-left:-5.0625rem;margin-left:calc(var(--space-xxxl)*-1);margin-right:-5.0625rem;margin-right:calc(var(--space-xxxl)*-1)}.-my-xxxl{margin-bottom:-5.0625rem;margin-bottom:calc(var(--space-xxxl)*-1)}.-mt-xxxl,.-my-xxxl{margin-top:-5.0625rem;margin-top:calc(var(--space-xxxl)*-1)}.gap-gutter{gap:max(1.5rem,min(3vw,2.25rem));gap:var(--space-gutter)}.gap-x-gutter{-moz-column-gap:max(1.5rem,min(3vw,2.25rem));column-gap:max(1.5rem,min(3vw,2.25rem));-moz-column-gap:var(--space-gutter);column-gap:var(--space-gutter)}.gap-y-gutter{row-gap:max(1.5rem,min(3vw,2.25rem));row-gap:var(--space-gutter)}.p-gutter{padding:max(1.5rem,min(3vw,2.25rem));padding:var(--space-gutter)}.px-gutter{padding-left:max(1.5rem,min(3vw,2.25rem));padding-left:var(--space-gutter);padding-right:max(1.5rem,min(3vw,2.25rem));padding-right:var(--space-gutter)}.py-gutter{padding-bottom:max(1.5rem,min(3vw,2.25rem));padding-bottom:var(--space-gutter);padding-top:max(1.5rem,min(3vw,2.25rem));padding-top:var(--space-gutter)}.m-gutter{margin:max(1.5rem,min(3vw,2.25rem));margin:var(--space-gutter)}.mx-gutter{margin-left:max(1.5rem,min(3vw,2.25rem));margin-left:var(--space-gutter);margin-right:max(1.5rem,min(3vw,2.25rem));margin-right:var(--space-gutter)}.my-gutter{margin-bottom:max(1.5rem,min(3vw,2.25rem));margin-bottom:var(--space-gutter)}.mt-gutter,.my-gutter{margin-top:max(1.5rem,min(3vw,2.25rem));margin-top:var(--space-gutter)}.-m-gutter{margin:calc(-1 * max(calc(1rem * 1.5), min(3vw, calc(calc(1rem * 1.5) * 1.5))));margin:calc(var(--space-gutter)*-1)}.-mx-gutter{margin-left:calc(-1 * max(calc(1rem * 1.5), min(3vw, calc(calc(1rem * 1.5) * 1.5))));margin-left:calc(var(--space-gutter)*-1);margin-right:calc(-1 * max(calc(1rem * 1.5), min(3vw, calc(calc(1rem * 1.5) * 1.5))));margin-right:calc(var(--space-gutter)*-1)}.-my-gutter{margin-bottom:calc(-1 * max(calc(1rem * 1.5), min(3vw, calc(calc(1rem * 1.5) * 1.5))));margin-bottom:calc(var(--space-gutter)*-1)}.-mt-gutter,.-my-gutter{margin-top:calc(-1 * max(calc(1rem * 1.5), min(3vw, calc(calc(1rem * 1.5) * 1.5))));margin-top:calc(var(--space-gutter)*-1)}.stack{display:flex;flex-direction:column}.stack.reverse{flex-direction:column-reverse}.row{align-items:center;display:flex}.row.reverse{flex-direction:row-reverse}.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(15ch,100%),1fr));grid-template-columns:repeat(auto-fill,minmax(min(var(--grid-min,15ch),100%),1fr))}.grid-fit{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(15ch,100%),1fr));grid-template-columns:repeat(auto-fit,minmax(min(var(--grid-min,15ch),100%),1fr))}.block{display:block;width:100%}.inline{display:inline;vertical-align:middle}.inline-block{display:inline-block}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.justify-start{justify-content:flex-start}.justify-between{justify-content:space-between}.align-center{align-items:center}.align-start{align-items:flex-start}.align-end{align-items:flex-end}.align-baseline{align-items:baseline}.align-stretch{align-items:stretch}.align-self-center{align-self:center}.align-self-start{align-self:flex-start}.align-self-end{align-self:flex-end}.align-self-baseline{align-self:baseline}.align-self-stretch{align-self:stretch}.push-center,.push-start{margin-right:auto}.push-center,.push-end{margin-left:auto}@media (max-width:40rem){.break-sm{flex-grow:1;order:999;width:100%}}.grow{flex-grow:1}.shrink{min-width:0}.no-shrink{flex-shrink:0}.full-width{width:100%}.full-height{height:100%}.wrap{flex-wrap:wrap}.wrap-reverse{flex-wrap:wrap-reverse}.nowrap{flex-wrap:nowrap;white-space:nowrap}.dividers>*+*{border-top:1px solid rgba(110,102,153,.25);border-top:1px solid var(--color-stroke);margin-top:1.5rem;margin-top:var(--space-lg);padding-top:1.5rem;padding-top:var(--space-lg)}.float-left{float:left}.float-right{float:right}.overflow-ellipsis{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.overflow-visible{overflow:visible}.overflow-hidden{overflow:hidden}.scrollable,.scrollable-x,.scrollable-y{-ms-overflow-style:none;overflow:auto;scrollbar-width:none}.scrollable-x::-webkit-scrollbar,.scrollable::-webkit-scrollbar{display:none}.scrollable-y::-webkit-scrollbar{display:none}.scrollable-x:after,.scrollable-x:before,.scrollable-y:after,.scrollable-y:before,.scrollable:after,.scrollable:before{content:\"\";display:block;flex-shrink:0;opacity:0;pointer-events:none;position:sticky;transition:opacity .5s;z-index:10}.scrollable-x:after,.scrollable-x:before{align-self:stretch;margin-left:-40px;width:40px}.scrollable-x:before{background-image:linear-gradient(90deg,#f9f8fc,transparent);background-image:linear-gradient(to right,var(--color-bg),transparent);left:0}.scrollable-x:after{background-image:linear-gradient(270deg,#f9f8fc,transparent);background-image:linear-gradient(to left,var(--color-bg),transparent);right:0}.scrollable-x.is-scrolled-left:after,.scrollable-x.is-scrolled-right:before{opacity:1}.scrollable-y:after,.scrollable-y:before{height:40px;margin-top:-40px;width:100%}.scrollable-y:before{background-image:linear-gradient(180deg,#f9f8fc,transparent);background-image:linear-gradient(to bottom,var(--color-bg),transparent);top:0}.scrollable-y:after{background-image:linear-gradient(0deg,#f9f8fc,transparent);background-image:linear-gradient(to top,var(--color-bg),transparent);bottom:0}.scrollable-y.is-scrolled-down:before,.scrollable-y.is-scrolled-up:after{opacity:1}.rotate-90{transform:rotate(90deg)}.rotate-180{transform:rotate(180deg)}.rotate-270{transform:rotate(270deg)}.flip-horizontal{transform:scaleX(-1)}.flip-vertical{transform:scaleY(-1)}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.color-text{color:#000;color:var(--color-text)}.color-muted{color:#6e6699;color:var(--color-muted)}.color-accent{color:#4013f6;color:var(--color-accent-text)}.color-success{color:#007a0e;color:var(--color-success-text)}.color-warning{color:#7a7a00;color:var(--color-warning-text)}.color-danger{color:#ad0000;color:var(--color-danger-text)}.color-activity{color:#d15000;color:var(--color-activity-text)}.color-inherit{color:inherit}.text-xxs{font-size:.8rem;font-size:var(--text-xxs)}.text-xs{font-size:.875rem;font-size:var(--text-xs)}.text-sm{font-size:1rem;font-size:var(--text-sm)}.text-md{font-size:1.125rem;font-size:var(--text-md)}.text-lg{font-size:1.5rem;font-size:var(--text-lg)}.text-xl{font-size:2rem;font-size:var(--text-xl)}.text-xxl{font-size:2.5rem;font-size:var(--text-xxl)}.animate-shake{animation:shake .8s}@keyframes shake{10%,90%{transform:translateX(-1px)}20%,80%{transform:translateX(2px)}30%,50%,70%{transform:translateX(-4px)}40%,60%{transform:translateX(4px)}}.animate-appear{animation:appear .5s}@keyframes appear{0%{opacity:0;transform:scale(.9)}to{opacity:1;transform:none}}.bg-accent,.bg-activity,.bg-danger,.bg-emphasis,.bg-success,.bg-warning{--color-fill:hsla(0,0%,100%,.1);--color-stroke:hsla(0,0%,100%,.25);--color-muted:var(--color-text);--color-accent:var(--color-text);--color-accent-text:var(--color-text);background-color:#f9f8fc;background-color:var(--color-bg);color:#000;color:var(--color-text)}.bg-fill{background-color:rgba(110,102,153,.1);background-color:var(--color-fill)}.bg-fill-soft{background-color:rgba(110,102,153,.05);background-color:var(--color-fill-soft)}.bg-emphasis{--color-bg:var(--palette-emphasis);--color-text:var(--palette-emphasis-contrast)}.bg-accent{--color-bg:var(--palette-accent);--color-text:var(--palette-accent-contrast)}.bg-accent-soft{background-color:#dfd8fe;background-color:var(--color-accent-soft);color:#4013f6;color:var(--color-accent-text)}.bg-success{--color-bg:var(--palette-success);--color-text:var(--palette-success-contrast)}.bg-success-soft{background-color:#d6ffdb;background-color:var(--color-success-soft);color:#007a0e;color:var(--color-success-text)}.bg-warning{--color-bg:var(--palette-warning);--color-text:var(--palette-warning-contrast)}.bg-warning-soft{background-color:#ffffd6;background-color:var(--color-warning-soft);color:#7a7a00;color:var(--color-warning-text)}.bg-danger{--color-bg:var(--palette-danger);--color-text:var(--palette-danger-contrast)}.bg-danger-soft{background-color:#ffd6d6;background-color:var(--color-danger-soft);color:#ad0000;color:var(--color-danger-text)}.bg-activity{--color-bg:var(--palette-activity);--color-text:var(--palette-activity-contrast)}.bg-activity-soft{background-color:#ffe6d6;background-color:var(--color-activity-soft);color:#d15000;color:var(--color-activity-text)}.no-pointer{pointer-events:none}.no-select{-moz-user-select:none;user-select:none;-webkit-user-select:none}.clickable.is-disabled,.clickable:disabled{cursor:default;opacity:.5}.clickable.is-inert{cursor:default}.clickable:not(:disabled):not(.is-disabled):not(.is-inert){cursor:pointer}.clickable.is-focused:not(:disabled):not(.is-disabled):not(.is-inert),.clickable.is-hovered:not(:disabled):not(.is-disabled):not(.is-inert),.clickable:not(:disabled):not(.is-disabled):not(.is-inert):focus,.clickable:not(:disabled):not(.is-disabled):not(.is-inert):hover,.clickable[aria-selected=true]:not(:disabled):not(.is-disabled):not(.is-inert){filter:brightness(.95);filter:var(--filter-hover)}.clickable:not(:disabled):not(.is-disabled):not(.is-inert):active{filter:brightness(.9);filter:var(--filter-active);transform:scale(.97)}.cursor-default{cursor:default!important}.cursor-help{cursor:help!important}.cursor-pointer{cursor:pointer!important}.js .no-js-only,.no-js .js-only{display:none!important}.rounded{border-radius:10px;border-radius:var(--radius)}.pill{border-radius:999px}.overlay-container{position:relative}.has-overlay:before,.overlay{bottom:0;left:0;position:absolute!important; /* !important to override .busy-spinner*/right:0;top:0}.has-overlay:before{content:\"\"}.truncated{max-height:15em;overflow:hidden;position:relative}.truncated__expander{align-items:flex-end;background:linear-gradient(180deg,transparent,#f9f8fc 60%);background:linear-gradient(to bottom,transparent,var(--color-bg) 60%);bottom:0;display:flex;height:5em;left:0;position:absolute;right:0;width:100%}.font-text{font-family:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif;font-family:var(--font-text)}.font-display{font-family:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif;font-family:var(--font-display)}.font-mono{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Courier New,monospace;font-family:var(--font-mono)}.weight-normal{font-weight:400;font-weight:var(--weight-normal)}.weight-medium{font-weight:500;font-weight:var(--weight-medium)}.weight-bold{font-weight:600;font-weight:var(--weight-bold)}.underline{-webkit-text-decoration:underline;text-decoration:underline}.no-underline{-webkit-text-decoration:none;text-decoration:none}@media (max-width:40rem){.hide-sm{display:none!important}}@media (min-width:40.001rem){.hide-md-up{display:none!important}}@media (max-width:52rem){.hide-md-down{display:none!important}}@media (min-width:52.001rem){.hide-lg-up{display:none!important}}@media (max-width:68rem){.hide-lg-down{display:none!important}}.hide-if-empty:empty,.hide-if-not-only-child:not(:only-child),[data-theme=dark] .light-only,[data-theme=light] .dark-only{display:none}.auth-button[data-provider=facebook]{background-color:#4267b2;color:#fff}.auth-button[data-provider^=twitter]{background-color:#1da1f2;color:#fff}.auth-button[data-provider=linkedin]{background-color:#0077b5;color:#fff}.auth-button[data-provider=google]{background-color:#fff;border:1px solid rgba(110,102,153,.25);border:1px solid var(--color-stroke);color:#555}.auth-button[data-provider=github]{background-color:#333;color:#fff}.auth-button[data-provider=gitlab]{background-color:#e24329;color:#fff}.auth-button[data-provider=bitbucket]{background-color:#205081;color:#fff}.channel-card__info,.channel-card__inner{flex-basis:30ch}.comment{--comment-padding:var(--space-lg);--comment-padding-left:var(--comment-padding);padding:1.5rem;padding:var(--comment-padding);padding-left:1.5rem;padding-left:var(--comment-padding-left);transition:background .2s}@media (min-width:40.001rem){.comment{--comment-padding-left:calc(var(--attribution-avatar-size) + var(--space-md) + var(--comment-padding))}.comment .attribution{padding-left:0}.comment .attribution__info{display:inline;margin-left:.2963rem;margin-left:var(--space-xxs)}}.comment.is-highlighted{background:#ffffd6;background:var(--color-warning-soft)}.comment.is-answer{border:2px solid #008a10;border:2px solid var(--color-success);border-radius:10px;border-radius:var(--radius);box-shadow:inset 0 0 10px 2px #d6ffdb;box-shadow:inset 0 0 10px 2px var(--color-success-soft)}.comment.is-removed{background:rgba(110,102,153,.1);background:var(--palette-fill);padding-top:1rem;padding-top:var(--space-md)}.comment.is-removed.is-expanded>.removed-banner{margin-bottom:1.5rem;margin-bottom:var(--space-lg)}.comment.is-removed.is-expanded>.removed-banner .icon-tabler-chevron-right{transform:rotate(90deg)}.comment.is-removed:not(.is-expanded){padding-bottom:1rem;padding-bottom:var(--space-md)}.comment.is-removed:not(.is-expanded)>.removed-banner~*{display:none}@media (max-width:40rem){.comment.is-removed:not(.is-expanded) .removed-banner__attribution{display:none}}@media (min-width:40.001rem){.comment__icon{margin-left:calc(-2.5em + -1rem);margin-left:calc((var(--attribution-avatar-size) + var(--space-md))*-1);position:absolute;text-align:right;width:2.5em;width:var(--attribution-avatar-size)}}.comment__parent{margin-top:.44444rem;margin-top:var(--space-xs);width:-moz-fit-content;width:fit-content}.comment__parent>a{color:#6e6699;color:var(--color-muted);font-size:.875rem;font-size:var(--text-xs);font-weight:500;font-weight:var(--weight-medium)}.comment__parent-tooltip{max-width:80ch;overflow:hidden;padding:0;text-align:left}.comment__parent-tooltip .comment{--comment-padding:var(--space-md)}.comment__parent-tooltip .comment>*+*{margin-top:.44444rem;margin-top:var(--space-xs)}.comment__replies{margin-top:1rem;margin-top:var(--space-md)}.comment__replies .comment__parent{display:none}.comment__replies .comment{--comment-padding:var(--space-md)}.comment-list .card__row{padding:0}.comment-list #page_1+.card__row{border-top:0}.composer{--height-transition-duration:0s;background:#f9f8fc;background:var(--color-bg);border-radius:10px;border-radius:var(--radius);bottom:-10px;bottom:calc(var(--radius)*-1);max-height:calc(100vh - 4rem);max-height:calc(100vh - var(--header-height));position:sticky;transition:box-shadow .2s,border .2s,height 0s,bottom .2s;transition:box-shadow .2s,border .2s,height var(--height-transition-duration),bottom .2s}.composer.is-static{height:70vh!important;position:static}.composer.is-static .composer__close{visibility:hidden;width:.44444rem;width:var(--space-xs)}.composer:not(.is-open){--height-transition-duration:0.2s;bottom:-4.75em;height:4.75em!important}.composer:not(.is-open) .composer__form{display:none}.composer.is-open{--color-bg:var(--palette-surface);--focus-shadow:0 0 0 transparent;box-shadow:0 0 0 transparent,0 0 0 1px rgba(110,102,153,.1),0 5px 15px rgba(110,102,153,.25);box-shadow:var(--focus-shadow),var(--shadow-md);height:300px;min-height:200px;padding-bottom:10px;padding-bottom:var(--radius);z-index:101;z-index:calc(var(--z-index-header) + 1)}.composer.is-open:focus-within{--focus-shadow:0 0 0 2px var(--color-accent)}.composer.is-open .composer__placeholder{display:none}@media (max-width:40rem){.composer.is-open{border-radius:0;margin-left:calc(-1 * max(calc(1rem * 1.5), min(3vw, calc(calc(1rem * 1.5) * 1.5))));margin-left:calc(var(--space-gutter)*-1);margin-right:calc(-1 * max(calc(1rem * 1.5), min(3vw, calc(calc(1rem * 1.5) * 1.5))));margin-right:calc(var(--space-gutter)*-1)}}.composer__placeholder{border:2px dashed rgba(110,102,153,.25);border:2px dashed var(--color-stroke);border-radius:10px;border-radius:var(--radius);cursor:text;padding:1rem;padding:var(--space-md);-webkit-text-decoration:none!important;text-decoration:none!important;transition:box-shadow .2s,border .2s,color .2s}.composer__placeholder:hover{border-color:transparent;box-shadow:0 0 0 1px rgba(110,102,153,.1),0 5px 15px rgba(110,102,153,.25);box-shadow:var(--shadow-md);color:inherit}.composer__placeholder .avatar{width:2.5em;width:var(--control-height)}.composer__form .text-editor{border:0;box-shadow:none;min-height:0}.composer__form .text-editor__input{resize:none}.composer__handle{cursor:ns-resize;height:20px;left:100px;position:absolute;right:100px;top:-5px;touch-action:none}.composer__handle:before{background:rgba(110,102,153,.1);background:var(--color-fill);border-radius:10px;border-radius:var(--radius);content:\"\";height:5px;left:50%;position:absolute;top:10px;transform:translateX(-50%);width:20%}.composer__toolbar{border-bottom:1px solid rgba(110,102,153,.25);border-bottom:1px solid var(--color-stroke);padding:.2963rem;padding:var(--space-xxs)}.composer__parent{padding-left:.66667rem;padding-left:var(--space-sm)}.emoji{display:inline-block}span.emoji{transform:scale(1.2)}img.emoji{height:1.2em;vertical-align:-.2em;width:1.2em}.icon .emoji{height:100%;vertical-align:0;width:100%}.flag-container{border:2px solid #e65800;border:2px solid var(--color-activity);border-radius:10px!important;border-radius:var(--radius)!important;box-shadow:inset 0 0 10px 2px #ffe6d6;box-shadow:inset 0 0 10px 2px var(--color-activity-soft);overflow:clip}.flag-container>.alert{border-radius:0}.flags-menu{max-width:50ch}.header{--color-bg:var(--palette-surface);background:#fff;background:var(--color-bg);box-shadow:0 1px 4px rgba(110,102,153,.1);box-shadow:var(--shadow-sm);position:sticky;top:-1px;transition:box-shadow .2s;z-index:100;z-index:var(--z-index-header)}.header .container{height:4rem;height:var(--header-height)}.header-breadcrumb{color:#6e6699;color:var(--color-muted);flex-shrink:99;margin-left:.44444rem;margin-left:var(--space-xs);transition:opacity .2s,transform .2s}.header-breadcrumb:before{content:\"\\203A\";margin:0 .44444rem;margin:0 var(--space-xs)}.header-breadcrumb[hidden]{display:block!important;opacity:0;transform:translateY(10px);transition:opacity .2s,transform .2s,visibility 0s .2s;visibility:hidden}.header-search__form{margin-right:.44444rem;margin-right:var(--space-xs)}.header-search__form input{width:30ch}.header-user{margin-left:.44444rem;margin-left:var(--space-xs)}.email-verification-banner{padding-bottom:1rem;padding-bottom:var(--space-md);padding-top:1rem;padding-top:var(--space-md)}@media (max-width:52rem){.index-sidebar .index-create-post{margin-left:auto}}.index-footer{margin-top:1.5rem;margin-top:var(--space-lg)}.moderation-menu,.notifications-menu{max-height:40em;max-width:100%;width:55ch}.moderation-menu .menu-item,.notifications-menu .menu-item{margin-top:2px}.notification strong{color:#4013f6;color:var(--color-accent-text);font-weight:500;font-weight:var(--weight-medium)}.notification.is-unread{background-color:#f9f8fc;background-color:var(--color-bg);background-image:linear-gradient(#ffe6d6,#ffe6d6);background-image:linear-gradient(var(--color-activity-soft),var(--color-activity-soft))}.notification.is-unread .notification__time,.notification.is-unread strong,.notification.is-unread>.icon{color:#d15000;color:var(--color-activity-text)}.notification-grid .choice{width:8ch}.alert--notification{background-color:#f9f8fc;background-color:var(--color-bg);background-image:linear-gradient(#ffe6d6,#ffe6d6);background-image:linear-gradient(var(--color-activity-soft),var(--color-activity-soft))}.alert--notification .alert__message{line-height:inherit;margin:-.44444rem;margin:calc(var(--space-xs)*-1)}.alert--notification .notification__time{display:none}.post-feed__pinned{--grid-min:31ch}.post-feed__refresh{position:sticky;top:5rem;top:calc(var(--header-height) + var(--space-md))}.post-feed__refresh>*{left:50%;position:absolute;transform:translateX(-50%)}.post-feed__refresh .btn{box-shadow:0 0 0 1px rgba(110,102,153,.1),0 5px 15px rgba(110,102,153,.25);box-shadow:var(--shadow-md)}.post-list>:first-child>.post-list-item:first-of-type{border-top:0}.post-list-item{padding:1rem;padding:var(--space-md)}@media (min-width:40.001rem){.post-list-item{padding-left:1.5rem;padding-left:var(--space-lg)}}.post-list-item__avatar{flex-shrink:0;-webkit-text-decoration:none!important;text-decoration:none!important;width:2.5rem}.post-list-item__avatar,.post-list-item__controls,.post-list-item__end{margin-top:3px}@media (max-width:40rem){.post-list-item__main{width:100%}.post-list-item__upper{flex-wrap:wrap;gap:.44444rem;gap:var(--space-xs)}.post-list-item__end{flex-direction:row-reverse}}.post-title-link{color:inherit;display:block;margin:-10px 0;padding:10px 0}.is-read .post-title-link,.not-logged-in .post-title-link:visited{color:#6e6699;color:var(--color-muted)}.is-read .post-list-item__unread{display:none}.post-cards .post-card{margin-bottom:1.5rem;margin-bottom:var(--space-lg)}[data-route=\"waterhole.channels.show\"] .post-feed__content .channel-label{display:none}.post-header .post-tags-summary .tag{font-size:1rem;font-size:var(--text-sm)}.post-header .attribution,.post-header .post-title{width:100%}.post-page .sidebar{flex-basis:16ch}#comments{scroll-margin-top:5.5rem;scroll-margin-top:calc(var(--space-lg) + var(--header-height))}.comments-pagination__pages{max-height:20rem}.reactions-menu{border-radius:999px;display:flex;flex-direction:row;max-width:none;min-width:0;overflow:visible}.reactions-menu button{padding:2px;transform-origin:bottom;transition:transform .2s}.reactions-menu button:hover{transform:scale(1.2)}.reactions-condensed .icon{filter:drop-shadow(1px 0 0 hsl(250,38%,98%));filter:drop-shadow(1px 0 0 var(--color-bg));margin-left:-4px;position:relative}.reactions-condensed[data-count=\"0\"]{display:none}.reaction[data-count=\"0\"] .icon{filter:grayscale(.9);opacity:.3}.search-results .post-list-item__title a:visited{color:#6e6699;color:var(--color-muted)}.text-editor{height:auto;min-height:15em;padding:0}.text-editor__toolbar{margin:.2963rem;margin:var(--space-xxs);margin-bottom:0}.is-previewing .text-editor__toolbar{pointer-events:none;z-index:1}.is-previewing .text-editor__toolbar>*{pointer-events:all}.is-previewing .text-editor__toolbar>:not(.text-editor__preview-button){display:none}.text-editor__expander{position:relative}.text-editor__input{background:transparent;border:0;box-shadow:none!important;max-height:70vh;outline:none;padding:1rem;padding:var(--space-md);padding-top:.44444rem;padding-top:var(--space-xs);position:relative;resize:vertical;width:100%}.text-editor__input[hidden]{display:inline!important;display:initial!important;visibility:hidden}.text-editor__preview{overflow:auto;padding:1rem;padding:var(--space-md)}.user-profile__card .avatar{width:12ch}.user-profile__content{flex-basis:60ch}.user-profile__controls{float:right;margin-left:-100%;position:relative}"
  },
  {
    "path": "resources/dist/global.js",
    "content": "(function(){var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),s=(e,n)=>{let r={};for(var i in e)t(r,i,{get:e[i],enumerable:!0});return n&&t(r,Symbol.toStringTag,{value:`Module`}),r},c=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},l=(n,r,a)=>(a=n==null?{}:e(i(n)),c(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n)),u=function(e,t,n,r,i){if(r===`m`)throw TypeError(`Private method is not writable`);if(r===`a`&&!i)throw TypeError(`Private accessor was defined without a setter`);if(typeof t==`function`?e!==t||!i:!t.has(e))throw TypeError(`Cannot write private member to an object whose class did not declare it`);return r===`a`?i.call(e,n):i?i.value=n:t.set(e,n),n},d=function(e,t,n,r){if(n===`a`&&!r)throw TypeError(`Private accessor was defined without a getter`);if(typeof t==`function`?e!==t||!r:!t.has(e))throw TypeError(`Cannot read private member from an object whose class did not declare it`);return n===`m`?r:n===`a`?r.call(e):r?r.value:t.get(e)},f,p=class{formatToParts(e){let t=[];for(let n of e)t.push({type:`element`,value:n}),t.push({type:`literal`,value:`, `});return t.slice(0,-1)}};let m=typeof Intl<`u`&&Intl.ListFormat||p,h=[[`years`,`year`],[`months`,`month`],[`weeks`,`week`],[`days`,`day`],[`hours`,`hour`],[`minutes`,`minute`],[`seconds`,`second`],[`milliseconds`,`millisecond`]],g={minimumIntegerDigits:2};var _=class{constructor(e,t={}){f.set(this,void 0);let n=String(t.style||`short`);n!==`long`&&n!==`short`&&n!==`narrow`&&n!==`digital`&&(n=`short`);let r=n===`digital`?`numeric`:n,i=t.hours||r;r=i===`2-digit`?`numeric`:i;let a=t.minutes||r;r=a===`2-digit`?`numeric`:a;let o=t.seconds||r;r=o===`2-digit`?`numeric`:o;let s=t.milliseconds||r;u(this,f,{locale:e,style:n,years:t.years||n===`digital`?`short`:n,yearsDisplay:t.yearsDisplay===`always`?`always`:`auto`,months:t.months||n===`digital`?`short`:n,monthsDisplay:t.monthsDisplay===`always`?`always`:`auto`,weeks:t.weeks||n===`digital`?`short`:n,weeksDisplay:t.weeksDisplay===`always`?`always`:`auto`,days:t.days||n===`digital`?`short`:n,daysDisplay:t.daysDisplay===`always`?`always`:`auto`,hours:i,hoursDisplay:t.hoursDisplay===`always`||n===`digital`?`always`:`auto`,minutes:a,minutesDisplay:t.minutesDisplay===`always`||n===`digital`?`always`:`auto`,seconds:o,secondsDisplay:t.secondsDisplay===`always`||n===`digital`?`always`:`auto`,milliseconds:s,millisecondsDisplay:t.millisecondsDisplay===`always`?`always`:`auto`},`f`)}resolvedOptions(){return d(this,f,`f`)}formatToParts(e){let t=[],n=d(this,f,`f`),r=n.style,i=n.locale;for(let[a,o]of h){let s=e[a];if(n[`${a}Display`]===`auto`&&!s)continue;let c=n[a],l=c===`2-digit`?g:c===`numeric`?{}:{style:`unit`,unit:o,unitDisplay:c},u=new Intl.NumberFormat(i,l).format(s);a===`months`&&(c===`narrow`||r===`narrow`&&u.endsWith(`m`))&&(u=u.replace(/(\\d+)m$/,`$1mo`)),t.push(u)}return new m(i,{type:`unit`,style:r===`digital`?`short`:r}).formatToParts(t)}format(e){return this.formatToParts(e).map(e=>e.value).join(``)}};f=new WeakMap;let v=/^[-+]?P(?:(\\d+)Y)?(?:(\\d+)M)?(?:(\\d+)W)?(?:(\\d+)D)?(?:T(?:(\\d+)H)?(?:(\\d+)M)?(?:(\\d+)S)?)?$/,y=[`year`,`month`,`week`,`day`,`hour`,`minute`,`second`,`millisecond`],b=e=>v.test(e);var x=class e{constructor(e=0,t=0,n=0,r=0,i=0,a=0,o=0,s=0){this.years=e,this.months=t,this.weeks=n,this.days=r,this.hours=i,this.minutes=a,this.seconds=o,this.milliseconds=s,this.years||=0,this.sign||=Math.sign(this.years),this.months||=0,this.sign||=Math.sign(this.months),this.weeks||=0,this.sign||=Math.sign(this.weeks),this.days||=0,this.sign||=Math.sign(this.days),this.hours||=0,this.sign||=Math.sign(this.hours),this.minutes||=0,this.sign||=Math.sign(this.minutes),this.seconds||=0,this.sign||=Math.sign(this.seconds),this.milliseconds||=0,this.sign||=Math.sign(this.milliseconds),this.blank=this.sign===0}abs(){return new e(Math.abs(this.years),Math.abs(this.months),Math.abs(this.weeks),Math.abs(this.days),Math.abs(this.hours),Math.abs(this.minutes),Math.abs(this.seconds),Math.abs(this.milliseconds))}static from(t){if(typeof t==`string`){let n=String(t).trim(),r=n.startsWith(`-`)?-1:1,i=n.match(v)?.slice(1).map(e=>(Number(e)||0)*r);return i?new e(...i):new e}else if(typeof t==`object`){let{years:n,months:r,weeks:i,days:a,hours:o,minutes:s,seconds:c,milliseconds:l}=t;return new e(n,r,i,a,o,s,c,l)}throw RangeError(`invalid duration`)}static compare(t,n){let r=Date.now(),i=Math.abs(ee(r,e.from(t)).getTime()-r),a=Math.abs(ee(r,e.from(n)).getTime()-r);return i>a?-1:i<a?1:0}toLocaleString(e,t){return new _(e,t).format(this)}};function ee(e,t){let n=new Date(e);return t.sign<0?(n.setUTCSeconds(n.getUTCSeconds()+t.seconds),n.setUTCMinutes(n.getUTCMinutes()+t.minutes),n.setUTCHours(n.getUTCHours()+t.hours),n.setUTCDate(n.getUTCDate()+t.weeks*7+t.days),n.setUTCMonth(n.getUTCMonth()+t.months),n.setUTCFullYear(n.getUTCFullYear()+t.years)):(n.setUTCFullYear(n.getUTCFullYear()+t.years),n.setUTCMonth(n.getUTCMonth()+t.months),n.setUTCDate(n.getUTCDate()+t.weeks*7+t.days),n.setUTCHours(n.getUTCHours()+t.hours),n.setUTCMinutes(n.getUTCMinutes()+t.minutes),n.setUTCSeconds(n.getUTCSeconds()+t.seconds)),n}function S(e,t=`second`,n=Date.now()){let r=e.getTime()-n;if(r===0)return new x;let i=Math.sign(r),a=Math.abs(r),o=Math.floor(a/1e3),s=Math.floor(o/60),c=Math.floor(s/60),l=Math.floor(c/24),u=Math.floor(l/30),d=Math.floor(u/12),f=y.indexOf(t)||y.length;return new x(f>=0?d*i:0,f>=1?(u-d*12)*i:0,0,f>=3?(l-u*30)*i:0,f>=4?(c-l*24)*i:0,f>=5?(s-c*60)*i:0,f>=6?(o-s*60)*i:0,f>=7?(a-o*1e3)*i:0)}function C(e,{relativeTo:t=Date.now()}={}){if(t=new Date(t),e.blank)return e;let n=e.sign,r=Math.abs(e.years),i=Math.abs(e.months),a=Math.abs(e.weeks),o=Math.abs(e.days),s=Math.abs(e.hours),c=Math.abs(e.minutes),l=Math.abs(e.seconds),u=Math.abs(e.milliseconds);u>=900&&(l+=Math.round(u/1e3)),(l||c||s||o||a||i||r)&&(u=0),l>=55&&(c+=Math.round(l/60)),(c||s||o||a||i||r)&&(l=0),c>=55&&(s+=Math.round(c/60)),(s||o||a||i||r)&&(c=0),o&&s>=12&&(o+=Math.round(s/24)),!o&&s>=21&&(o+=Math.round(s/24)),(o||a||i||r)&&(s=0);let d=t.getFullYear(),f=t.getMonth(),p=t.getDate();if(o>=27||r+i+o){let e=new Date(t);e.setDate(1),e.setMonth(f+i*n+1),e.setDate(0);let s=Math.max(0,p-e.getDate()),c=new Date(t);c.setFullYear(d+r*n),c.setDate(p-s),c.setMonth(f+i*n),c.setDate(p-s+o*n);let l=c.getFullYear()-t.getFullYear(),u=c.getMonth()-t.getMonth(),m=Math.abs(Math.round((Number(c)-Number(t))/864e5))+s,h=Math.abs(l*12+u);m<27?(o>=6?(a+=Math.round(o/7),o=0):o=m,i=r=0):h<=11?(i=h,r=0):(i=0,r=l*n),(i||r)&&(o=0)}return r&&(i=0),a>=4&&(i+=Math.round(a/4)),(i||r)&&(a=0),o&&a&&!i&&!r&&(a+=Math.round(o/7),o=0),new x(r*n,i*n,a*n,o*n,s*n,c*n,l*n,u*n)}function w(e,t){let n=C(e,t);if(n.blank)return[0,`second`];for(let e of y){if(e===`millisecond`)continue;let t=n[`${e}s`];if(t)return[t,e]}return[0,`second`]}var T=function(e,t,n,r){if(n===`a`&&!r)throw TypeError(`Private accessor was defined without a getter`);if(typeof t==`function`?e!==t||!r:!t.has(e))throw TypeError(`Cannot read private member from an object whose class did not declare it`);return n===`m`?r:n===`a`?r.call(e):r?r.value:t.get(e)},te=function(e,t,n,r,i){if(r===`m`)throw TypeError(`Private method is not writable`);if(r===`a`&&!i)throw TypeError(`Private accessor was defined without a setter`);if(typeof t==`function`?e!==t||!i:!t.has(e))throw TypeError(`Cannot write private member to an object whose class did not declare it`);return r===`a`?i.call(e,n):i?i.value=n:t.set(e,n),n},E,ne,re,D,ie,ae,oe,se,ce,le,O,k,ue,de,A,fe;let pe=globalThis.HTMLElement||null,me=new x,he=new x(0,0,0,0,0,1);var ge=class extends Event{constructor(e,t,n,r){super(`relative-time-updated`,{bubbles:!0,composed:!0}),this.oldText=e,this.newText=t,this.oldTitle=n,this.newTitle=r}};function _e(e){if(!e.date)return 1/0;if(e.format===`duration`||e.format===`elapsed`){let t=e.precision;if(t===`second`)return 1e3;if(t===`minute`)return 60*1e3}let t=Math.abs(Date.now()-e.date.getTime());return t<60*1e3?1e3:t<3600*1e3?60*1e3:3600*1e3}let ve=new class{constructor(){this.elements=new Set,this.time=1/0,this.timer=-1}observe(e){if(this.elements.has(e))return;this.elements.add(e);let t=e.date;if(t&&t.getTime()){let t=_e(e),n=Date.now()+t;n<this.time&&(clearTimeout(this.timer),this.timer=setTimeout(()=>this.update(),t),this.time=n)}}unobserve(e){this.elements.has(e)&&this.elements.delete(e)}update(){if(clearTimeout(this.timer),!this.elements.size)return;let e=1/0;for(let t of this.elements)e=Math.min(e,_e(t)),t.update();this.time=Math.min(3600*1e3,e),this.timer=setTimeout(()=>this.update(),this.time),this.time+=Date.now()}};var ye=class extends pe{constructor(){super(...arguments),E.add(this),ne.set(this,!1),re.set(this,!1),ie.set(this,this.shadowRoot?this.shadowRoot:this.attachShadow?this.attachShadow({mode:`open`}):this),fe.set(this,null)}static define(e=`relative-time`,t=customElements){return t.define(e,this),this}get timeZone(){return this.closest(`[time-zone]`)?.getAttribute(`time-zone`)||this.ownerDocument.documentElement.getAttribute(`time-zone`)||void 0}static get observedAttributes(){return[`second`,`minute`,`hour`,`weekday`,`day`,`month`,`year`,`time-zone-name`,`prefix`,`threshold`,`tense`,`precision`,`format`,`format-style`,`no-title`,`datetime`,`lang`,`title`,`aria-hidden`,`time-zone`]}get onRelativeTimeUpdated(){return T(this,fe,`f`)}set onRelativeTimeUpdated(e){T(this,fe,`f`)&&this.removeEventListener(`relative-time-updated`,T(this,fe,`f`)),te(this,fe,typeof e==`object`||typeof e==`function`?e:null,`f`),typeof e==`function`&&this.addEventListener(`relative-time-updated`,e)}get second(){let e=this.getAttribute(`second`);if(e===`numeric`||e===`2-digit`)return e}set second(e){this.setAttribute(`second`,e||``)}get minute(){let e=this.getAttribute(`minute`);if(e===`numeric`||e===`2-digit`)return e}set minute(e){this.setAttribute(`minute`,e||``)}get hour(){let e=this.getAttribute(`hour`);if(e===`numeric`||e===`2-digit`)return e}set hour(e){this.setAttribute(`hour`,e||``)}get weekday(){let e=this.getAttribute(`weekday`);if(e===`long`||e===`short`||e===`narrow`)return e;if(this.format===`datetime`&&e!==``)return this.formatStyle}set weekday(e){this.setAttribute(`weekday`,e||``)}get day(){let e=this.getAttribute(`day`)??`numeric`;if(e===`numeric`||e===`2-digit`)return e}set day(e){this.setAttribute(`day`,e||``)}get month(){let e=this.format,t=this.getAttribute(`month`);if(t!==``&&(t??=e===`datetime`?this.formatStyle:`short`,t===`numeric`||t===`2-digit`||t===`short`||t===`long`||t===`narrow`))return t}set month(e){this.setAttribute(`month`,e||``)}get year(){let e=this.getAttribute(`year`);if(e===`numeric`||e===`2-digit`)return e;if(!this.hasAttribute(`year`)&&new Date().getUTCFullYear()!==this.date?.getUTCFullYear())return`numeric`}set year(e){this.setAttribute(`year`,e||``)}get timeZoneName(){let e=this.getAttribute(`time-zone-name`);if(e===`long`||e===`short`||e===`shortOffset`||e===`longOffset`||e===`shortGeneric`||e===`longGeneric`)return e}set timeZoneName(e){this.setAttribute(`time-zone-name`,e||``)}get prefix(){return this.getAttribute(`prefix`)??(this.format===`datetime`?``:`on`)}set prefix(e){this.setAttribute(`prefix`,e)}get threshold(){let e=this.getAttribute(`threshold`);return e&&b(e)?e:`P30D`}set threshold(e){this.setAttribute(`threshold`,e)}get tense(){let e=this.getAttribute(`tense`);return e===`past`?`past`:e===`future`?`future`:`auto`}set tense(e){this.setAttribute(`tense`,e)}get precision(){let e=this.getAttribute(`precision`);return y.includes(e)?e:this.format===`micro`?`minute`:`second`}set precision(e){this.setAttribute(`precision`,e)}get format(){let e=this.getAttribute(`format`);return e===`datetime`?`datetime`:e===`relative`?`relative`:e===`duration`?`duration`:e===`micro`?`micro`:e===`elapsed`?`elapsed`:`auto`}set format(e){this.setAttribute(`format`,e)}get formatStyle(){let e=this.getAttribute(`format-style`);if(e===`long`)return`long`;if(e===`short`)return`short`;if(e===`narrow`)return`narrow`;let t=this.format;return t===`elapsed`||t===`micro`?`narrow`:t===`datetime`?`short`:`long`}set formatStyle(e){this.setAttribute(`format-style`,e)}get noTitle(){return this.hasAttribute(`no-title`)}set noTitle(e){this.toggleAttribute(`no-title`,e)}get datetime(){return this.getAttribute(`datetime`)||``}set datetime(e){this.setAttribute(`datetime`,e)}get date(){let e=Date.parse(this.datetime);return Number.isNaN(e)?null:new Date(e)}set date(e){this.datetime=e?.toISOString()||``}connectedCallback(){this.update()}disconnectedCallback(){ve.unobserve(this)}attributeChangedCallback(e,t,n){t!==n&&(e===`title`&&te(this,ne,n!==null&&(this.date&&T(this,E,`m`,ae).call(this,this.date))!==n,`f`),!T(this,re,`f`)&&!(e===`title`&&T(this,ne,`f`))&&te(this,re,(async()=>{await Promise.resolve(),this.update(),te(this,re,!1,`f`)})(),`f`))}update(){let e=T(this,ie,`f`).textContent||this.textContent||``,t=this.getAttribute(`title`)||``,n=t,r=this.date;if(typeof Intl>`u`||!Intl.DateTimeFormat||!r){T(this,ie,`f`).textContent=e;return}let i=Date.now();T(this,ne,`f`)||(n=T(this,E,`m`,ae).call(this,r)||``,n&&!this.noTitle&&this.setAttribute(`title`,n));let a=S(r,this.precision,i),o=T(this,E,`m`,oe).call(this,a),s=e,c=T(this,E,`m`,A).call(this,o);s=c?T(this,E,`m`,ue).call(this,r):o===`duration`?T(this,E,`m`,se).call(this,a):o===`relative`?T(this,E,`m`,ce).call(this,a):T(this,E,`m`,le).call(this,r),s?T(this,E,`m`,de).call(this,s):this.shadowRoot===T(this,ie,`f`)&&this.textContent&&T(this,E,`m`,de).call(this,this.textContent),(s!==e||n!==t)&&this.dispatchEvent(new ge(e,s,t,n)),o===`relative`||o===`duration`||c&&(T(this,E,`m`,O).call(this,r)||T(this,E,`m`,k).call(this,r))?ve.observe(this):ve.unobserve(this)}};ne=new WeakMap,re=new WeakMap,ie=new WeakMap,fe=new WeakMap,E=new WeakSet,D=function(){let e=this.closest(`[lang]`)?.getAttribute(`lang`)||this.ownerDocument.documentElement.getAttribute(`lang`);try{return new Intl.Locale(e??``).toString()}catch{return`default`}},ae=function(e){return new Intl.DateTimeFormat(T(this,E,`a`,D),{day:`numeric`,month:`short`,year:`numeric`,hour:`numeric`,minute:`2-digit`,timeZoneName:`short`,timeZone:this.timeZone}).format(e)},oe=function(e){let t=this.format;if(t===`datetime`)return`datetime`;if(t===`duration`||t===`elapsed`||t===`micro`)return`duration`;if((t===`auto`||t===`relative`)&&typeof Intl<`u`&&Intl.RelativeTimeFormat){let t=this.tense;if(t===`past`||t===`future`||x.compare(e,this.threshold)===1)return`relative`}return`datetime`},se=function(e){let t=T(this,E,`a`,D),n=this.format,r=this.formatStyle,i=this.tense,a=me;n===`micro`?(e=C(e),a=he,e.months===0&&(this.tense===`past`&&e.sign!==-1||this.tense===`future`&&e.sign!==1)&&(e=he)):(i===`past`&&e.sign!==-1||i===`future`&&e.sign!==1)&&(e=a);let o=`${this.precision}sDisplay`;return e.blank?a.toLocaleString(t,{style:r,[o]:`always`}):e.abs().toLocaleString(t,{style:r})},ce=function(e){let t=new Intl.RelativeTimeFormat(T(this,E,`a`,D),{numeric:`auto`,style:this.formatStyle}),n=this.tense;n===`future`&&e.sign!==1&&(e=me),n===`past`&&e.sign!==-1&&(e=me);let[r,i]=w(e);return i===`second`&&r<10?t.format(0,this.precision===`millisecond`?`second`:this.precision):t.format(r,i)},le=function(e){let t=new Intl.DateTimeFormat(T(this,E,`a`,D),{second:this.second,minute:this.minute,hour:this.hour,weekday:this.weekday,day:this.day,month:this.month,year:this.year,timeZoneName:this.timeZoneName,timeZone:this.timeZone});return`${this.prefix} ${t.format(e)}`.trim()},O=function(e){let t=new Date,n=new Intl.DateTimeFormat(T(this,E,`a`,D),{timeZone:this.timeZone,year:`numeric`,month:`2-digit`,day:`2-digit`});return n.format(t)===n.format(e)},k=function(e){let t=new Date,n=new Intl.DateTimeFormat(T(this,E,`a`,D),{timeZone:this.timeZone,year:`numeric`});return n.format(t)===n.format(e)},ue=function(e){let t={hour:`numeric`,minute:`2-digit`,timeZoneName:`short`,timeZone:this.timeZone};if(T(this,E,`m`,O).call(this,e)){let n=new Intl.RelativeTimeFormat(T(this,E,`a`,D),{numeric:`auto`}).format(0,`day`);n=n.charAt(0).toLocaleUpperCase(T(this,E,`a`,D))+n.slice(1);let r=new Intl.DateTimeFormat(T(this,E,`a`,D),t).format(e);return`${n} ${r}`}let n=Object.assign(Object.assign({},t),{day:`numeric`,month:`short`});return T(this,E,`m`,k).call(this,e)?new Intl.DateTimeFormat(T(this,E,`a`,D),n).format(e):new Intl.DateTimeFormat(T(this,E,`a`,D),Object.assign(Object.assign({},n),{year:`numeric`})).format(e)},de=function(e){if(this.hasAttribute(`aria-hidden`)&&this.getAttribute(`aria-hidden`)===`true`){let t=document.createElement(`span`);t.setAttribute(`aria-hidden`,`true`),t.textContent=e,T(this,ie,`f`).replaceChildren(t)}else T(this,ie,`f`).textContent=e},A=function(e){return e===`duration`?!1:this.ownerDocument.documentElement.getAttribute(`data-prefers-absolute-time`)===`true`||this.ownerDocument.body?.getAttribute(`data-prefers-absolute-time`)===`true`};let be=typeof globalThis<`u`?globalThis:window;try{be.RelativeTimeElement=ye.define()}catch(e){if(!(be.DOMException&&e instanceof DOMException&&e.name===`NotSupportedError`)&&!(e instanceof ReferenceError))throw e}var xe=class{constructor(e,t,n){this.eventTarget=e,this.eventName=t,this.eventOptions=n,this.unorderedBindings=new Set}connect(){this.eventTarget.addEventListener(this.eventName,this,this.eventOptions)}disconnect(){this.eventTarget.removeEventListener(this.eventName,this,this.eventOptions)}bindingConnected(e){this.unorderedBindings.add(e)}bindingDisconnected(e){this.unorderedBindings.delete(e)}handleEvent(e){let t=Se(e);for(let e of this.bindings)if(t.immediatePropagationStopped)break;else e.handleEvent(t)}hasBindings(){return this.unorderedBindings.size>0}get bindings(){return Array.from(this.unorderedBindings).sort((e,t)=>{let n=e.index,r=t.index;return n<r?-1:n>r?1:0})}};function Se(e){if(`immediatePropagationStopped`in e)return e;{let{stopImmediatePropagation:t}=e;return Object.assign(e,{immediatePropagationStopped:!1,stopImmediatePropagation(){this.immediatePropagationStopped=!0,t.call(this)}})}}var Ce=class{constructor(e){this.application=e,this.eventListenerMaps=new Map,this.started=!1}start(){this.started||(this.started=!0,this.eventListeners.forEach(e=>e.connect()))}stop(){this.started&&(this.started=!1,this.eventListeners.forEach(e=>e.disconnect()))}get eventListeners(){return Array.from(this.eventListenerMaps.values()).reduce((e,t)=>e.concat(Array.from(t.values())),[])}bindingConnected(e){this.fetchEventListenerForBinding(e).bindingConnected(e)}bindingDisconnected(e,t=!1){this.fetchEventListenerForBinding(e).bindingDisconnected(e),t&&this.clearEventListenersForBinding(e)}handleError(e,t,n={}){this.application.handleError(e,`Error ${t}`,n)}clearEventListenersForBinding(e){let t=this.fetchEventListenerForBinding(e);t.hasBindings()||(t.disconnect(),this.removeMappedEventListenerFor(e))}removeMappedEventListenerFor(e){let{eventTarget:t,eventName:n,eventOptions:r}=e,i=this.fetchEventListenerMapForEventTarget(t),a=this.cacheKey(n,r);i.delete(a),i.size==0&&this.eventListenerMaps.delete(t)}fetchEventListenerForBinding(e){let{eventTarget:t,eventName:n,eventOptions:r}=e;return this.fetchEventListener(t,n,r)}fetchEventListener(e,t,n){let r=this.fetchEventListenerMapForEventTarget(e),i=this.cacheKey(t,n),a=r.get(i);return a||(a=this.createEventListener(e,t,n),r.set(i,a)),a}createEventListener(e,t,n){let r=new xe(e,t,n);return this.started&&r.connect(),r}fetchEventListenerMapForEventTarget(e){let t=this.eventListenerMaps.get(e);return t||(t=new Map,this.eventListenerMaps.set(e,t)),t}cacheKey(e,t){let n=[e];return Object.keys(t).sort().forEach(e=>{n.push(`${t[e]?``:`!`}${e}`)}),n.join(`:`)}};let we={stop({event:e,value:t}){return t&&e.stopPropagation(),!0},prevent({event:e,value:t}){return t&&e.preventDefault(),!0},self({event:e,value:t,element:n}){return t?n===e.target:!0}},Te=/^(?:(?:([^.]+?)\\+)?(.+?)(?:\\.(.+?))?(?:@(window|document))?->)?(.+?)(?:#([^:]+?))(?::(.+))?$/;function Ee(e){let t=e.trim().match(Te)||[],n=t[2],r=t[3];return r&&![`keydown`,`keyup`,`keypress`].includes(n)&&(n+=`.${r}`,r=``),{eventTarget:j(t[4]),eventName:n,eventOptions:t[7]?De(t[7]):{},identifier:t[5],methodName:t[6],keyFilter:t[1]||r}}function j(e){if(e==`window`)return window;if(e==`document`)return document}function De(e){return e.split(`:`).reduce((e,t)=>Object.assign(e,{[t.replace(/^!/,``)]:!/^!/.test(t)}),{})}function Oe(e){if(e==window)return`window`;if(e==document)return`document`}function ke(e){return e.replace(/(?:[_-])([a-z0-9])/g,(e,t)=>t.toUpperCase())}function Ae(e){return ke(e.replace(/--/g,`-`).replace(/__/g,`_`))}function je(e){return e.charAt(0).toUpperCase()+e.slice(1)}function Me(e){return e.replace(/([A-Z])/g,(e,t)=>`-${t.toLowerCase()}`)}function Ne(e){return e.match(/[^\\s]+/g)||[]}function Pe(e){return e!=null}function Fe(e,t){return Object.prototype.hasOwnProperty.call(e,t)}let Ie=[`meta`,`ctrl`,`alt`,`shift`];var Le=class{constructor(e,t,n,r){this.element=e,this.index=t,this.eventTarget=n.eventTarget||e,this.eventName=n.eventName||Re(e)||N(`missing event name`),this.eventOptions=n.eventOptions||{},this.identifier=n.identifier||N(`missing identifier`),this.methodName=n.methodName||N(`missing method name`),this.keyFilter=n.keyFilter||``,this.schema=r}static forToken(e,t){return new this(e.element,e.index,Ee(e.content),t)}toString(){let e=this.keyFilter?`.${this.keyFilter}`:``,t=this.eventTargetName?`@${this.eventTargetName}`:``;return`${this.eventName}${e}${t}->${this.identifier}#${this.methodName}`}shouldIgnoreKeyboardEvent(e){if(!this.keyFilter)return!1;let t=this.keyFilter.split(`+`);if(this.keyFilterDissatisfied(e,t))return!0;let n=t.filter(e=>!Ie.includes(e))[0];return n?(Fe(this.keyMappings,n)||N(`contains unknown key filter: ${this.keyFilter}`),this.keyMappings[n].toLowerCase()!==e.key.toLowerCase()):!1}shouldIgnoreMouseEvent(e){if(!this.keyFilter)return!1;let t=[this.keyFilter];return!!this.keyFilterDissatisfied(e,t)}get params(){let e={},t=RegExp(`^data-${this.identifier}-(.+)-param$`,`i`);for(let{name:n,value:r}of Array.from(this.element.attributes)){let i=n.match(t),a=i&&i[1];a&&(e[ke(a)]=ze(r))}return e}get eventTargetName(){return Oe(this.eventTarget)}get keyMappings(){return this.schema.keyMappings}keyFilterDissatisfied(e,t){let[n,r,i,a]=Ie.map(e=>t.includes(e));return e.metaKey!==n||e.ctrlKey!==r||e.altKey!==i||e.shiftKey!==a}};let M={a:()=>`click`,button:()=>`click`,form:()=>`submit`,details:()=>`toggle`,input:e=>e.getAttribute(`type`)==`submit`?`click`:`input`,select:()=>`change`,textarea:()=>`input`};function Re(e){let t=e.tagName.toLowerCase();if(t in M)return M[t](e)}function N(e){throw Error(e)}function ze(e){try{return JSON.parse(e)}catch{return e}}var Be=class{constructor(e,t){this.context=e,this.action=t}get index(){return this.action.index}get eventTarget(){return this.action.eventTarget}get eventOptions(){return this.action.eventOptions}get identifier(){return this.context.identifier}handleEvent(e){let t=this.prepareActionEvent(e);this.willBeInvokedByEvent(e)&&this.applyEventModifiers(t)&&this.invokeWithEvent(t)}get eventName(){return this.action.eventName}get method(){let e=this.controller[this.methodName];if(typeof e==`function`)return e;throw Error(`Action \"${this.action}\" references undefined method \"${this.methodName}\"`)}applyEventModifiers(e){let{element:t}=this.action,{actionDescriptorFilters:n}=this.context.application,{controller:r}=this.context,i=!0;for(let[a,o]of Object.entries(this.eventOptions))if(a in n){let s=n[a];i&&=s({name:a,value:o,event:e,element:t,controller:r})}else continue;return i}prepareActionEvent(e){return Object.assign(e,{params:this.action.params})}invokeWithEvent(e){let{target:t,currentTarget:n}=e;try{this.method.call(this.controller,e),this.context.logDebugActivity(this.methodName,{event:e,target:t,currentTarget:n,action:this.methodName})}catch(t){let{identifier:n,controller:r,element:i,index:a}=this,o={identifier:n,controller:r,element:i,index:a,event:e};this.context.handleError(t,`invoking action \"${this.action}\"`,o)}}willBeInvokedByEvent(e){let t=e.target;return e instanceof KeyboardEvent&&this.action.shouldIgnoreKeyboardEvent(e)||e instanceof MouseEvent&&this.action.shouldIgnoreMouseEvent(e)?!1:this.element===t?!0:t instanceof Element&&this.element.contains(t)?this.scope.containsElement(t):this.scope.containsElement(this.action.element)}get controller(){return this.context.controller}get methodName(){return this.action.methodName}get element(){return this.scope.element}get scope(){return this.context.scope}},Ve=class{constructor(e,t){this.mutationObserverInit={attributes:!0,childList:!0,subtree:!0},this.element=e,this.started=!1,this.delegate=t,this.elements=new Set,this.mutationObserver=new MutationObserver(e=>this.processMutations(e))}start(){this.started||(this.started=!0,this.mutationObserver.observe(this.element,this.mutationObserverInit),this.refresh())}pause(e){this.started&&=(this.mutationObserver.disconnect(),!1),e(),this.started||=(this.mutationObserver.observe(this.element,this.mutationObserverInit),!0)}stop(){this.started&&=(this.mutationObserver.takeRecords(),this.mutationObserver.disconnect(),!1)}refresh(){if(this.started){let e=new Set(this.matchElementsInTree());for(let t of Array.from(this.elements))e.has(t)||this.removeElement(t);for(let t of Array.from(e))this.addElement(t)}}processMutations(e){if(this.started)for(let t of e)this.processMutation(t)}processMutation(e){e.type==`attributes`?this.processAttributeChange(e.target,e.attributeName):e.type==`childList`&&(this.processRemovedNodes(e.removedNodes),this.processAddedNodes(e.addedNodes))}processAttributeChange(e,t){this.elements.has(e)?this.delegate.elementAttributeChanged&&this.matchElement(e)?this.delegate.elementAttributeChanged(e,t):this.removeElement(e):this.matchElement(e)&&this.addElement(e)}processRemovedNodes(e){for(let t of Array.from(e)){let e=this.elementFromNode(t);e&&this.processTree(e,this.removeElement)}}processAddedNodes(e){for(let t of Array.from(e)){let e=this.elementFromNode(t);e&&this.elementIsActive(e)&&this.processTree(e,this.addElement)}}matchElement(e){return this.delegate.matchElement(e)}matchElementsInTree(e=this.element){return this.delegate.matchElementsInTree(e)}processTree(e,t){for(let n of this.matchElementsInTree(e))t.call(this,n)}elementFromNode(e){if(e.nodeType==Node.ELEMENT_NODE)return e}elementIsActive(e){return e.isConnected==this.element.isConnected?this.element.contains(e):!1}addElement(e){this.elements.has(e)||this.elementIsActive(e)&&(this.elements.add(e),this.delegate.elementMatched&&this.delegate.elementMatched(e))}removeElement(e){this.elements.has(e)&&(this.elements.delete(e),this.delegate.elementUnmatched&&this.delegate.elementUnmatched(e))}},He=class{constructor(e,t,n){this.attributeName=t,this.delegate=n,this.elementObserver=new Ve(e,this)}get element(){return this.elementObserver.element}get selector(){return`[${this.attributeName}]`}start(){this.elementObserver.start()}pause(e){this.elementObserver.pause(e)}stop(){this.elementObserver.stop()}refresh(){this.elementObserver.refresh()}get started(){return this.elementObserver.started}matchElement(e){return e.hasAttribute(this.attributeName)}matchElementsInTree(e){let t=this.matchElement(e)?[e]:[],n=Array.from(e.querySelectorAll(this.selector));return t.concat(n)}elementMatched(e){this.delegate.elementMatchedAttribute&&this.delegate.elementMatchedAttribute(e,this.attributeName)}elementUnmatched(e){this.delegate.elementUnmatchedAttribute&&this.delegate.elementUnmatchedAttribute(e,this.attributeName)}elementAttributeChanged(e,t){this.delegate.elementAttributeValueChanged&&this.attributeName==t&&this.delegate.elementAttributeValueChanged(e,t)}};function Ue(e,t,n){Ge(e,t).add(n)}function We(e,t,n){Ge(e,t).delete(n),Ke(e,t)}function Ge(e,t){let n=e.get(t);return n||(n=new Set,e.set(t,n)),n}function Ke(e,t){let n=e.get(t);n!=null&&n.size==0&&e.delete(t)}var qe=class{constructor(){this.valuesByKey=new Map}get keys(){return Array.from(this.valuesByKey.keys())}get values(){return Array.from(this.valuesByKey.values()).reduce((e,t)=>e.concat(Array.from(t)),[])}get size(){return Array.from(this.valuesByKey.values()).reduce((e,t)=>e+t.size,0)}add(e,t){Ue(this.valuesByKey,e,t)}delete(e,t){We(this.valuesByKey,e,t)}has(e,t){let n=this.valuesByKey.get(e);return n!=null&&n.has(t)}hasKey(e){return this.valuesByKey.has(e)}hasValue(e){return Array.from(this.valuesByKey.values()).some(t=>t.has(e))}getValuesForKey(e){let t=this.valuesByKey.get(e);return t?Array.from(t):[]}getKeysForValue(e){return Array.from(this.valuesByKey).filter(([t,n])=>n.has(e)).map(([e,t])=>e)}},Je=class{constructor(e,t,n,r){this._selector=t,this.details=r,this.elementObserver=new Ve(e,this),this.delegate=n,this.matchesByElement=new qe}get started(){return this.elementObserver.started}get selector(){return this._selector}set selector(e){this._selector=e,this.refresh()}start(){this.elementObserver.start()}pause(e){this.elementObserver.pause(e)}stop(){this.elementObserver.stop()}refresh(){this.elementObserver.refresh()}get element(){return this.elementObserver.element}matchElement(e){let{selector:t}=this;if(t){let n=e.matches(t);return this.delegate.selectorMatchElement?n&&this.delegate.selectorMatchElement(e,this.details):n}else return!1}matchElementsInTree(e){let{selector:t}=this;if(t){let n=this.matchElement(e)?[e]:[],r=Array.from(e.querySelectorAll(t)).filter(e=>this.matchElement(e));return n.concat(r)}else return[]}elementMatched(e){let{selector:t}=this;t&&this.selectorMatched(e,t)}elementUnmatched(e){let t=this.matchesByElement.getKeysForValue(e);for(let n of t)this.selectorUnmatched(e,n)}elementAttributeChanged(e,t){let{selector:n}=this;if(n){let t=this.matchElement(e),r=this.matchesByElement.has(n,e);t&&!r?this.selectorMatched(e,n):!t&&r&&this.selectorUnmatched(e,n)}}selectorMatched(e,t){this.delegate.selectorMatched(e,t,this.details),this.matchesByElement.add(t,e)}selectorUnmatched(e,t){this.delegate.selectorUnmatched(e,t,this.details),this.matchesByElement.delete(t,e)}},Ye=class{constructor(e,t){this.element=e,this.delegate=t,this.started=!1,this.stringMap=new Map,this.mutationObserver=new MutationObserver(e=>this.processMutations(e))}start(){this.started||(this.started=!0,this.mutationObserver.observe(this.element,{attributes:!0,attributeOldValue:!0}),this.refresh())}stop(){this.started&&=(this.mutationObserver.takeRecords(),this.mutationObserver.disconnect(),!1)}refresh(){if(this.started)for(let e of this.knownAttributeNames)this.refreshAttribute(e,null)}processMutations(e){if(this.started)for(let t of e)this.processMutation(t)}processMutation(e){let t=e.attributeName;t&&this.refreshAttribute(t,e.oldValue)}refreshAttribute(e,t){let n=this.delegate.getStringMapKeyForAttribute(e);if(n!=null){this.stringMap.has(e)||this.stringMapKeyAdded(n,e);let r=this.element.getAttribute(e);if(this.stringMap.get(e)!=r&&this.stringMapValueChanged(r,n,t),r==null){let t=this.stringMap.get(e);this.stringMap.delete(e),t&&this.stringMapKeyRemoved(n,e,t)}else this.stringMap.set(e,r)}}stringMapKeyAdded(e,t){this.delegate.stringMapKeyAdded&&this.delegate.stringMapKeyAdded(e,t)}stringMapValueChanged(e,t,n){this.delegate.stringMapValueChanged&&this.delegate.stringMapValueChanged(e,t,n)}stringMapKeyRemoved(e,t,n){this.delegate.stringMapKeyRemoved&&this.delegate.stringMapKeyRemoved(e,t,n)}get knownAttributeNames(){return Array.from(new Set(this.currentAttributeNames.concat(this.recordedAttributeNames)))}get currentAttributeNames(){return Array.from(this.element.attributes).map(e=>e.name)}get recordedAttributeNames(){return Array.from(this.stringMap.keys())}},Xe=class{constructor(e,t,n){this.attributeObserver=new He(e,t,this),this.delegate=n,this.tokensByElement=new qe}get started(){return this.attributeObserver.started}start(){this.attributeObserver.start()}pause(e){this.attributeObserver.pause(e)}stop(){this.attributeObserver.stop()}refresh(){this.attributeObserver.refresh()}get element(){return this.attributeObserver.element}get attributeName(){return this.attributeObserver.attributeName}elementMatchedAttribute(e){this.tokensMatched(this.readTokensForElement(e))}elementAttributeValueChanged(e){let[t,n]=this.refreshTokensForElement(e);this.tokensUnmatched(t),this.tokensMatched(n)}elementUnmatchedAttribute(e){this.tokensUnmatched(this.tokensByElement.getValuesForKey(e))}tokensMatched(e){e.forEach(e=>this.tokenMatched(e))}tokensUnmatched(e){e.forEach(e=>this.tokenUnmatched(e))}tokenMatched(e){this.delegate.tokenMatched(e),this.tokensByElement.add(e.element,e)}tokenUnmatched(e){this.delegate.tokenUnmatched(e),this.tokensByElement.delete(e.element,e)}refreshTokensForElement(e){let t=this.tokensByElement.getValuesForKey(e),n=this.readTokensForElement(e),r=Qe(t,n).findIndex(([e,t])=>!$e(e,t));return r==-1?[[],[]]:[t.slice(r),n.slice(r)]}readTokensForElement(e){let t=this.attributeName;return Ze(e.getAttribute(t)||``,e,t)}};function Ze(e,t,n){return e.trim().split(/\\s+/).filter(e=>e.length).map((e,r)=>({element:t,attributeName:n,content:e,index:r}))}function Qe(e,t){let n=Math.max(e.length,t.length);return Array.from({length:n},(n,r)=>[e[r],t[r]])}function $e(e,t){return e&&t&&e.index==t.index&&e.content==t.content}var et=class{constructor(e,t,n){this.tokenListObserver=new Xe(e,t,this),this.delegate=n,this.parseResultsByToken=new WeakMap,this.valuesByTokenByElement=new WeakMap}get started(){return this.tokenListObserver.started}start(){this.tokenListObserver.start()}stop(){this.tokenListObserver.stop()}refresh(){this.tokenListObserver.refresh()}get element(){return this.tokenListObserver.element}get attributeName(){return this.tokenListObserver.attributeName}tokenMatched(e){let{element:t}=e,{value:n}=this.fetchParseResultForToken(e);n&&(this.fetchValuesByTokenForElement(t).set(e,n),this.delegate.elementMatchedValue(t,n))}tokenUnmatched(e){let{element:t}=e,{value:n}=this.fetchParseResultForToken(e);n&&(this.fetchValuesByTokenForElement(t).delete(e),this.delegate.elementUnmatchedValue(t,n))}fetchParseResultForToken(e){let t=this.parseResultsByToken.get(e);return t||(t=this.parseToken(e),this.parseResultsByToken.set(e,t)),t}fetchValuesByTokenForElement(e){let t=this.valuesByTokenByElement.get(e);return t||(t=new Map,this.valuesByTokenByElement.set(e,t)),t}parseToken(e){try{return{value:this.delegate.parseValueForToken(e)}}catch(e){return{error:e}}}},tt=class{constructor(e,t){this.context=e,this.delegate=t,this.bindingsByAction=new Map}start(){this.valueListObserver||(this.valueListObserver=new et(this.element,this.actionAttribute,this),this.valueListObserver.start())}stop(){this.valueListObserver&&(this.valueListObserver.stop(),delete this.valueListObserver,this.disconnectAllActions())}get element(){return this.context.element}get identifier(){return this.context.identifier}get actionAttribute(){return this.schema.actionAttribute}get schema(){return this.context.schema}get bindings(){return Array.from(this.bindingsByAction.values())}connectAction(e){let t=new Be(this.context,e);this.bindingsByAction.set(e,t),this.delegate.bindingConnected(t)}disconnectAction(e){let t=this.bindingsByAction.get(e);t&&(this.bindingsByAction.delete(e),this.delegate.bindingDisconnected(t))}disconnectAllActions(){this.bindings.forEach(e=>this.delegate.bindingDisconnected(e,!0)),this.bindingsByAction.clear()}parseValueForToken(e){let t=Le.forToken(e,this.schema);if(t.identifier==this.identifier)return t}elementMatchedValue(e,t){this.connectAction(t)}elementUnmatchedValue(e,t){this.disconnectAction(t)}},nt=class{constructor(e,t){this.context=e,this.receiver=t,this.stringMapObserver=new Ye(this.element,this),this.valueDescriptorMap=this.controller.valueDescriptorMap}start(){this.stringMapObserver.start(),this.invokeChangedCallbacksForDefaultValues()}stop(){this.stringMapObserver.stop()}get element(){return this.context.element}get controller(){return this.context.controller}getStringMapKeyForAttribute(e){if(e in this.valueDescriptorMap)return this.valueDescriptorMap[e].name}stringMapKeyAdded(e,t){let n=this.valueDescriptorMap[t];this.hasValue(e)||this.invokeChangedCallback(e,n.writer(this.receiver[e]),n.writer(n.defaultValue))}stringMapValueChanged(e,t,n){let r=this.valueDescriptorNameMap[t];e!==null&&(n===null&&(n=r.writer(r.defaultValue)),this.invokeChangedCallback(t,e,n))}stringMapKeyRemoved(e,t,n){let r=this.valueDescriptorNameMap[e];this.hasValue(e)?this.invokeChangedCallback(e,r.writer(this.receiver[e]),n):this.invokeChangedCallback(e,r.writer(r.defaultValue),n)}invokeChangedCallbacksForDefaultValues(){for(let{key:e,name:t,defaultValue:n,writer:r}of this.valueDescriptors)n!=null&&!this.controller.data.has(e)&&this.invokeChangedCallback(t,r(n),void 0)}invokeChangedCallback(e,t,n){let r=`${e}Changed`,i=this.receiver[r];if(typeof i==`function`){let r=this.valueDescriptorNameMap[e];try{let e=r.reader(t),a=n;n&&(a=r.reader(n)),i.call(this.receiver,e,a)}catch(e){throw e instanceof TypeError&&(e.message=`Stimulus Value \"${this.context.identifier}.${r.name}\" - ${e.message}`),e}}}get valueDescriptors(){let{valueDescriptorMap:e}=this;return Object.keys(e).map(t=>e[t])}get valueDescriptorNameMap(){let e={};return Object.keys(this.valueDescriptorMap).forEach(t=>{let n=this.valueDescriptorMap[t];e[n.name]=n}),e}hasValue(e){let t=this.valueDescriptorNameMap[e],n=`has${je(t.name)}`;return this.receiver[n]}},rt=class{constructor(e,t){this.context=e,this.delegate=t,this.targetsByName=new qe}start(){this.tokenListObserver||(this.tokenListObserver=new Xe(this.element,this.attributeName,this),this.tokenListObserver.start())}stop(){this.tokenListObserver&&(this.disconnectAllTargets(),this.tokenListObserver.stop(),delete this.tokenListObserver)}tokenMatched({element:e,content:t}){this.scope.containsElement(e)&&this.connectTarget(e,t)}tokenUnmatched({element:e,content:t}){this.disconnectTarget(e,t)}connectTarget(e,t){var n;this.targetsByName.has(t,e)||(this.targetsByName.add(t,e),(n=this.tokenListObserver)==null||n.pause(()=>this.delegate.targetConnected(e,t)))}disconnectTarget(e,t){var n;this.targetsByName.has(t,e)&&(this.targetsByName.delete(t,e),(n=this.tokenListObserver)==null||n.pause(()=>this.delegate.targetDisconnected(e,t)))}disconnectAllTargets(){for(let e of this.targetsByName.keys)for(let t of this.targetsByName.getValuesForKey(e))this.disconnectTarget(t,e)}get attributeName(){return`data-${this.context.identifier}-target`}get element(){return this.context.element}get scope(){return this.context.scope}};function it(e,t){let n=ot(e);return Array.from(n.reduce((e,n)=>(st(n,t).forEach(t=>e.add(t)),e),new Set))}function at(e,t){return ot(e).reduce((e,n)=>(e.push(...ct(n,t)),e),[])}function ot(e){let t=[];for(;e;)t.push(e),e=Object.getPrototypeOf(e);return t.reverse()}function st(e,t){let n=e[t];return Array.isArray(n)?n:[]}function ct(e,t){let n=e[t];return n?Object.keys(n).map(e=>[e,n[e]]):[]}var lt=class{constructor(e,t){this.started=!1,this.context=e,this.delegate=t,this.outletsByName=new qe,this.outletElementsByName=new qe,this.selectorObserverMap=new Map,this.attributeObserverMap=new Map}start(){this.started||(this.outletDefinitions.forEach(e=>{this.setupSelectorObserverForOutlet(e),this.setupAttributeObserverForOutlet(e)}),this.started=!0,this.dependentContexts.forEach(e=>e.refresh()))}refresh(){this.selectorObserverMap.forEach(e=>e.refresh()),this.attributeObserverMap.forEach(e=>e.refresh())}stop(){this.started&&(this.started=!1,this.disconnectAllOutlets(),this.stopSelectorObservers(),this.stopAttributeObservers())}stopSelectorObservers(){this.selectorObserverMap.size>0&&(this.selectorObserverMap.forEach(e=>e.stop()),this.selectorObserverMap.clear())}stopAttributeObservers(){this.attributeObserverMap.size>0&&(this.attributeObserverMap.forEach(e=>e.stop()),this.attributeObserverMap.clear())}selectorMatched(e,t,{outletName:n}){let r=this.getOutlet(e,n);r&&this.connectOutlet(r,e,n)}selectorUnmatched(e,t,{outletName:n}){let r=this.getOutletFromMap(e,n);r&&this.disconnectOutlet(r,e,n)}selectorMatchElement(e,{outletName:t}){let n=this.selector(t),r=this.hasOutlet(e,t),i=e.matches(`[${this.schema.controllerAttribute}~=${t}]`);return n?r&&i&&e.matches(n):!1}elementMatchedAttribute(e,t){let n=this.getOutletNameFromOutletAttributeName(t);n&&this.updateSelectorObserverForOutlet(n)}elementAttributeValueChanged(e,t){let n=this.getOutletNameFromOutletAttributeName(t);n&&this.updateSelectorObserverForOutlet(n)}elementUnmatchedAttribute(e,t){let n=this.getOutletNameFromOutletAttributeName(t);n&&this.updateSelectorObserverForOutlet(n)}connectOutlet(e,t,n){var r;this.outletElementsByName.has(n,t)||(this.outletsByName.add(n,e),this.outletElementsByName.add(n,t),(r=this.selectorObserverMap.get(n))==null||r.pause(()=>this.delegate.outletConnected(e,t,n)))}disconnectOutlet(e,t,n){var r;this.outletElementsByName.has(n,t)&&(this.outletsByName.delete(n,e),this.outletElementsByName.delete(n,t),(r=this.selectorObserverMap.get(n))==null||r.pause(()=>this.delegate.outletDisconnected(e,t,n)))}disconnectAllOutlets(){for(let e of this.outletElementsByName.keys)for(let t of this.outletElementsByName.getValuesForKey(e))for(let n of this.outletsByName.getValuesForKey(e))this.disconnectOutlet(n,t,e)}updateSelectorObserverForOutlet(e){let t=this.selectorObserverMap.get(e);t&&(t.selector=this.selector(e))}setupSelectorObserverForOutlet(e){let t=this.selector(e),n=new Je(document.body,t,this,{outletName:e});this.selectorObserverMap.set(e,n),n.start()}setupAttributeObserverForOutlet(e){let t=this.attributeNameForOutletName(e),n=new He(this.scope.element,t,this);this.attributeObserverMap.set(e,n),n.start()}selector(e){return this.scope.outlets.getSelectorForOutletName(e)}attributeNameForOutletName(e){return this.scope.schema.outletAttributeForScope(this.identifier,e)}getOutletNameFromOutletAttributeName(e){return this.outletDefinitions.find(t=>this.attributeNameForOutletName(t)===e)}get outletDependencies(){let e=new qe;return this.router.modules.forEach(t=>{let n=t.definition.controllerConstructor;it(n,`outlets`).forEach(n=>e.add(n,t.identifier))}),e}get outletDefinitions(){return this.outletDependencies.getKeysForValue(this.identifier)}get dependentControllerIdentifiers(){return this.outletDependencies.getValuesForKey(this.identifier)}get dependentContexts(){let e=this.dependentControllerIdentifiers;return this.router.contexts.filter(t=>e.includes(t.identifier))}hasOutlet(e,t){return!!this.getOutlet(e,t)||!!this.getOutletFromMap(e,t)}getOutlet(e,t){return this.application.getControllerForElementAndIdentifier(e,t)}getOutletFromMap(e,t){return this.outletsByName.getValuesForKey(t).find(t=>t.element===e)}get scope(){return this.context.scope}get schema(){return this.context.schema}get identifier(){return this.context.identifier}get application(){return this.context.application}get router(){return this.application.router}},ut=class{constructor(e,t){this.logDebugActivity=(e,t={})=>{let{identifier:n,controller:r,element:i}=this;t=Object.assign({identifier:n,controller:r,element:i},t),this.application.logDebugActivity(this.identifier,e,t)},this.module=e,this.scope=t,this.controller=new e.controllerConstructor(this),this.bindingObserver=new tt(this,this.dispatcher),this.valueObserver=new nt(this,this.controller),this.targetObserver=new rt(this,this),this.outletObserver=new lt(this,this);try{this.controller.initialize(),this.logDebugActivity(`initialize`)}catch(e){this.handleError(e,`initializing controller`)}}connect(){this.bindingObserver.start(),this.valueObserver.start(),this.targetObserver.start(),this.outletObserver.start();try{this.controller.connect(),this.logDebugActivity(`connect`)}catch(e){this.handleError(e,`connecting controller`)}}refresh(){this.outletObserver.refresh()}disconnect(){try{this.controller.disconnect(),this.logDebugActivity(`disconnect`)}catch(e){this.handleError(e,`disconnecting controller`)}this.outletObserver.stop(),this.targetObserver.stop(),this.valueObserver.stop(),this.bindingObserver.stop()}get application(){return this.module.application}get identifier(){return this.module.identifier}get schema(){return this.application.schema}get dispatcher(){return this.application.dispatcher}get element(){return this.scope.element}get parentElement(){return this.element.parentElement}handleError(e,t,n={}){let{identifier:r,controller:i,element:a}=this;n=Object.assign({identifier:r,controller:i,element:a},n),this.application.handleError(e,`Error ${t}`,n)}targetConnected(e,t){this.invokeControllerMethod(`${t}TargetConnected`,e)}targetDisconnected(e,t){this.invokeControllerMethod(`${t}TargetDisconnected`,e)}outletConnected(e,t,n){this.invokeControllerMethod(`${Ae(n)}OutletConnected`,e,t)}outletDisconnected(e,t,n){this.invokeControllerMethod(`${Ae(n)}OutletDisconnected`,e,t)}invokeControllerMethod(e,...t){let n=this.controller;typeof n[e]==`function`&&n[e](...t)}};function dt(e){return ft(e,pt(e))}function ft(e,t){let n=gt(e),r=mt(e.prototype,t);return Object.defineProperties(n.prototype,r),n}function pt(e){return it(e,`blessings`).reduce((t,n)=>{let r=n(e);for(let e in r){let n=t[e]||{};t[e]=Object.assign(n,r[e])}return t},{})}function mt(e,t){return ht(t).reduce((n,r)=>{let i=P(e,t,r);return i&&Object.assign(n,{[r]:i}),n},{})}function P(e,t,n){let r=Object.getOwnPropertyDescriptor(e,n);if(!(r&&`value`in r)){let e=Object.getOwnPropertyDescriptor(t,n).value;return r&&(e.get=r.get||e.get,e.set=r.set||e.set),e}}let ht=typeof Object.getOwnPropertySymbols==`function`?e=>[...Object.getOwnPropertyNames(e),...Object.getOwnPropertySymbols(e)]:Object.getOwnPropertyNames,gt=(()=>{function e(e){function t(){return Reflect.construct(e,arguments,new.target)}return t.prototype=Object.create(e.prototype,{constructor:{value:t}}),Reflect.setPrototypeOf(t,e),t}function t(){let t=e(function(){this.a.call(this)});return t.prototype.a=function(){},new t}try{return t(),e}catch{return e=>class extends e{}}})();function _t(e){return{identifier:e.identifier,controllerConstructor:dt(e.controllerConstructor)}}var vt=class{constructor(e,t){this.application=e,this.definition=_t(t),this.contextsByScope=new WeakMap,this.connectedContexts=new Set}get identifier(){return this.definition.identifier}get controllerConstructor(){return this.definition.controllerConstructor}get contexts(){return Array.from(this.connectedContexts)}connectContextForScope(e){let t=this.fetchContextForScope(e);this.connectedContexts.add(t),t.connect()}disconnectContextForScope(e){let t=this.contextsByScope.get(e);t&&(this.connectedContexts.delete(t),t.disconnect())}fetchContextForScope(e){let t=this.contextsByScope.get(e);return t||(t=new ut(this,e),this.contextsByScope.set(e,t)),t}},yt=class{constructor(e){this.scope=e}has(e){return this.data.has(this.getDataKey(e))}get(e){return this.getAll(e)[0]}getAll(e){return Ne(this.data.get(this.getDataKey(e))||``)}getAttributeName(e){return this.data.getAttributeNameForKey(this.getDataKey(e))}getDataKey(e){return`${e}-class`}get data(){return this.scope.data}},bt=class{constructor(e){this.scope=e}get element(){return this.scope.element}get identifier(){return this.scope.identifier}get(e){let t=this.getAttributeNameForKey(e);return this.element.getAttribute(t)}set(e,t){let n=this.getAttributeNameForKey(e);return this.element.setAttribute(n,t),this.get(e)}has(e){let t=this.getAttributeNameForKey(e);return this.element.hasAttribute(t)}delete(e){if(this.has(e)){let t=this.getAttributeNameForKey(e);return this.element.removeAttribute(t),!0}else return!1}getAttributeNameForKey(e){return`data-${this.identifier}-${Me(e)}`}},xt=class{constructor(e){this.warnedKeysByObject=new WeakMap,this.logger=e}warn(e,t,n){let r=this.warnedKeysByObject.get(e);r||(r=new Set,this.warnedKeysByObject.set(e,r)),r.has(t)||(r.add(t),this.logger.warn(n,e))}};function St(e,t){return`[${e}~=\"${t}\"]`}var Ct=class{constructor(e){this.scope=e}get element(){return this.scope.element}get identifier(){return this.scope.identifier}get schema(){return this.scope.schema}has(e){return this.find(e)!=null}find(...e){return e.reduce((e,t)=>e||this.findTarget(t)||this.findLegacyTarget(t),void 0)}findAll(...e){return e.reduce((e,t)=>[...e,...this.findAllTargets(t),...this.findAllLegacyTargets(t)],[])}findTarget(e){let t=this.getSelectorForTargetName(e);return this.scope.findElement(t)}findAllTargets(e){let t=this.getSelectorForTargetName(e);return this.scope.findAllElements(t)}getSelectorForTargetName(e){return St(this.schema.targetAttributeForScope(this.identifier),e)}findLegacyTarget(e){let t=this.getLegacySelectorForTargetName(e);return this.deprecate(this.scope.findElement(t),e)}findAllLegacyTargets(e){let t=this.getLegacySelectorForTargetName(e);return this.scope.findAllElements(t).map(t=>this.deprecate(t,e))}getLegacySelectorForTargetName(e){let t=`${this.identifier}.${e}`;return St(this.schema.targetAttribute,t)}deprecate(e,t){if(e){let{identifier:n}=this,r=this.schema.targetAttribute,i=this.schema.targetAttributeForScope(n);this.guide.warn(e,`target:${t}`,`Please replace ${r}=\"${n}.${t}\" with ${i}=\"${t}\". The ${r} attribute is deprecated and will be removed in a future version of Stimulus.`)}return e}get guide(){return this.scope.guide}},wt=class{constructor(e,t){this.scope=e,this.controllerElement=t}get element(){return this.scope.element}get identifier(){return this.scope.identifier}get schema(){return this.scope.schema}has(e){return this.find(e)!=null}find(...e){return e.reduce((e,t)=>e||this.findOutlet(t),void 0)}findAll(...e){return e.reduce((e,t)=>[...e,...this.findAllOutlets(t)],[])}getSelectorForOutletName(e){let t=this.schema.outletAttributeForScope(this.identifier,e);return this.controllerElement.getAttribute(t)}findOutlet(e){let t=this.getSelectorForOutletName(e);if(t)return this.findElement(t,e)}findAllOutlets(e){let t=this.getSelectorForOutletName(e);return t?this.findAllElements(t,e):[]}findElement(e,t){return this.scope.queryElements(e).filter(n=>this.matchesElement(n,e,t))[0]}findAllElements(e,t){return this.scope.queryElements(e).filter(n=>this.matchesElement(n,e,t))}matchesElement(e,t,n){let r=e.getAttribute(this.scope.schema.controllerAttribute)||``;return e.matches(t)&&r.split(` `).includes(n)}},Tt=class e{constructor(e,t,n,r){this.targets=new Ct(this),this.classes=new yt(this),this.data=new bt(this),this.containsElement=e=>e.closest(this.controllerSelector)===this.element,this.schema=e,this.element=t,this.identifier=n,this.guide=new xt(r),this.outlets=new wt(this.documentScope,t)}findElement(e){return this.element.matches(e)?this.element:this.queryElements(e).find(this.containsElement)}findAllElements(e){return[...this.element.matches(e)?[this.element]:[],...this.queryElements(e).filter(this.containsElement)]}queryElements(e){return Array.from(this.element.querySelectorAll(e))}get controllerSelector(){return St(this.schema.controllerAttribute,this.identifier)}get isDocumentScope(){return this.element===document.documentElement}get documentScope(){return this.isDocumentScope?this:new e(this.schema,document.documentElement,this.identifier,this.guide.logger)}},Et=class{constructor(e,t,n){this.element=e,this.schema=t,this.delegate=n,this.valueListObserver=new et(this.element,this.controllerAttribute,this),this.scopesByIdentifierByElement=new WeakMap,this.scopeReferenceCounts=new WeakMap}start(){this.valueListObserver.start()}stop(){this.valueListObserver.stop()}get controllerAttribute(){return this.schema.controllerAttribute}parseValueForToken(e){let{element:t,content:n}=e;return this.parseValueForElementAndIdentifier(t,n)}parseValueForElementAndIdentifier(e,t){let n=this.fetchScopesByIdentifierForElement(e),r=n.get(t);return r||(r=this.delegate.createScopeForElementAndIdentifier(e,t),n.set(t,r)),r}elementMatchedValue(e,t){let n=(this.scopeReferenceCounts.get(t)||0)+1;this.scopeReferenceCounts.set(t,n),n==1&&this.delegate.scopeConnected(t)}elementUnmatchedValue(e,t){let n=this.scopeReferenceCounts.get(t);n&&(this.scopeReferenceCounts.set(t,n-1),n==1&&this.delegate.scopeDisconnected(t))}fetchScopesByIdentifierForElement(e){let t=this.scopesByIdentifierByElement.get(e);return t||(t=new Map,this.scopesByIdentifierByElement.set(e,t)),t}},Dt=class{constructor(e){this.application=e,this.scopeObserver=new Et(this.element,this.schema,this),this.scopesByIdentifier=new qe,this.modulesByIdentifier=new Map}get element(){return this.application.element}get schema(){return this.application.schema}get logger(){return this.application.logger}get controllerAttribute(){return this.schema.controllerAttribute}get modules(){return Array.from(this.modulesByIdentifier.values())}get contexts(){return this.modules.reduce((e,t)=>e.concat(t.contexts),[])}start(){this.scopeObserver.start()}stop(){this.scopeObserver.stop()}loadDefinition(e){this.unloadIdentifier(e.identifier);let t=new vt(this.application,e);this.connectModule(t);let n=e.controllerConstructor.afterLoad;n&&n.call(e.controllerConstructor,e.identifier,this.application)}unloadIdentifier(e){let t=this.modulesByIdentifier.get(e);t&&this.disconnectModule(t)}getContextForElementAndIdentifier(e,t){let n=this.modulesByIdentifier.get(t);if(n)return n.contexts.find(t=>t.element==e)}proposeToConnectScopeForElementAndIdentifier(e,t){let n=this.scopeObserver.parseValueForElementAndIdentifier(e,t);n?this.scopeObserver.elementMatchedValue(n.element,n):console.error(`Couldn't find or create scope for identifier: \"${t}\" and element:`,e)}handleError(e,t,n){this.application.handleError(e,t,n)}createScopeForElementAndIdentifier(e,t){return new Tt(this.schema,e,t,this.logger)}scopeConnected(e){this.scopesByIdentifier.add(e.identifier,e);let t=this.modulesByIdentifier.get(e.identifier);t&&t.connectContextForScope(e)}scopeDisconnected(e){this.scopesByIdentifier.delete(e.identifier,e);let t=this.modulesByIdentifier.get(e.identifier);t&&t.disconnectContextForScope(e)}connectModule(e){this.modulesByIdentifier.set(e.identifier,e),this.scopesByIdentifier.getValuesForKey(e.identifier).forEach(t=>e.connectContextForScope(t))}disconnectModule(e){this.modulesByIdentifier.delete(e.identifier),this.scopesByIdentifier.getValuesForKey(e.identifier).forEach(t=>e.disconnectContextForScope(t))}};let Ot={controllerAttribute:`data-controller`,actionAttribute:`data-action`,targetAttribute:`data-target`,targetAttributeForScope:e=>`data-${e}-target`,outletAttributeForScope:(e,t)=>`data-${e}-${t}-outlet`,keyMappings:Object.assign(Object.assign({enter:`Enter`,tab:`Tab`,esc:`Escape`,space:` `,up:`ArrowUp`,down:`ArrowDown`,left:`ArrowLeft`,right:`ArrowRight`,home:`Home`,end:`End`,page_up:`PageUp`,page_down:`PageDown`},kt(`abcdefghijklmnopqrstuvwxyz`.split(``).map(e=>[e,e]))),kt(`0123456789`.split(``).map(e=>[e,e])))};function kt(e){return e.reduce((e,[t,n])=>Object.assign(Object.assign({},e),{[t]:n}),{})}var At=class{constructor(e=document.documentElement,t=Ot){this.logger=console,this.debug=!1,this.logDebugActivity=(e,t,n={})=>{this.debug&&this.logFormattedMessage(e,t,n)},this.element=e,this.schema=t,this.dispatcher=new Ce(this),this.router=new Dt(this),this.actionDescriptorFilters=Object.assign({},we)}static start(e,t){let n=new this(e,t);return n.start(),n}async start(){await jt(),this.logDebugActivity(`application`,`starting`),this.dispatcher.start(),this.router.start(),this.logDebugActivity(`application`,`start`)}stop(){this.logDebugActivity(`application`,`stopping`),this.dispatcher.stop(),this.router.stop(),this.logDebugActivity(`application`,`stop`)}register(e,t){this.load({identifier:e,controllerConstructor:t})}registerActionOption(e,t){this.actionDescriptorFilters[e]=t}load(e,...t){(Array.isArray(e)?e:[e,...t]).forEach(e=>{e.controllerConstructor.shouldLoad&&this.router.loadDefinition(e)})}unload(e,...t){(Array.isArray(e)?e:[e,...t]).forEach(e=>this.router.unloadIdentifier(e))}get controllers(){return this.router.contexts.map(e=>e.controller)}getControllerForElementAndIdentifier(e,t){let n=this.router.getContextForElementAndIdentifier(e,t);return n?n.controller:null}handleError(e,t,n){var r;this.logger.error(`%s\n\n%o\n\n%o`,t,e,n),(r=window.onerror)==null||r.call(window,t,``,0,0,e)}logFormattedMessage(e,t,n={}){n=Object.assign({application:this},n),this.logger.groupCollapsed(`${e} #${t}`),this.logger.log(`details:`,Object.assign({},n)),this.logger.groupEnd()}};function jt(){return new Promise(e=>{document.readyState==`loading`?document.addEventListener(`DOMContentLoaded`,()=>e()):e()})}function Mt(e){return it(e,`classes`).reduce((e,t)=>Object.assign(e,Nt(t)),{})}function Nt(e){return{[`${e}Class`]:{get(){let{classes:t}=this;if(t.has(e))return t.get(e);{let n=t.getAttributeName(e);throw Error(`Missing attribute \"${n}\"`)}}},[`${e}Classes`]:{get(){return this.classes.getAll(e)}},[`has${je(e)}Class`]:{get(){return this.classes.has(e)}}}}function Pt(e){return it(e,`outlets`).reduce((e,t)=>Object.assign(e,It(t)),{})}function F(e,t,n){return e.application.getControllerForElementAndIdentifier(t,n)}function Ft(e,t,n){let r=F(e,t,n);if(r||(e.application.router.proposeToConnectScopeForElementAndIdentifier(t,n),r=F(e,t,n),r))return r}function It(e){let t=Ae(e);return{[`${t}Outlet`]:{get(){let t=this.outlets.find(e),n=this.outlets.getSelectorForOutletName(e);if(t){let n=Ft(this,t,e);if(n)return n;throw Error(`The provided outlet element is missing an outlet controller \"${e}\" instance for host controller \"${this.identifier}\"`)}throw Error(`Missing outlet element \"${e}\" for host controller \"${this.identifier}\". Stimulus couldn't find a matching outlet element using selector \"${n}\".`)}},[`${t}Outlets`]:{get(){let t=this.outlets.findAll(e);return t.length>0?t.map(t=>{let n=Ft(this,t,e);if(n)return n;console.warn(`The provided outlet element is missing an outlet controller \"${e}\" instance for host controller \"${this.identifier}\"`,t)}).filter(e=>e):[]}},[`${t}OutletElement`]:{get(){let t=this.outlets.find(e),n=this.outlets.getSelectorForOutletName(e);if(t)return t;throw Error(`Missing outlet element \"${e}\" for host controller \"${this.identifier}\". Stimulus couldn't find a matching outlet element using selector \"${n}\".`)}},[`${t}OutletElements`]:{get(){return this.outlets.findAll(e)}},[`has${je(t)}Outlet`]:{get(){return this.outlets.has(e)}}}}function Lt(e){return it(e,`targets`).reduce((e,t)=>Object.assign(e,Rt(t)),{})}function Rt(e){return{[`${e}Target`]:{get(){let t=this.targets.find(e);if(t)return t;throw Error(`Missing target element \"${e}\" for \"${this.identifier}\" controller`)}},[`${e}Targets`]:{get(){return this.targets.findAll(e)}},[`has${je(e)}Target`]:{get(){return this.targets.has(e)}}}}function zt(e){let t=at(e,`values`);return t.reduce((e,t)=>Object.assign(e,Bt(t)),{valueDescriptorMap:{get(){return t.reduce((e,t)=>{let n=Vt(t,this.identifier),r=this.data.getAttributeNameForKey(n.key);return Object.assign(e,{[r]:n})},{})}}})}function Bt(e,t){let n=Vt(e,t),{key:r,name:i,reader:a,writer:o}=n;return{[i]:{get(){let e=this.data.get(r);return e===null?n.defaultValue:a(e)},set(e){e===void 0?this.data.delete(r):this.data.set(r,o(e))}},[`has${je(i)}`]:{get(){return this.data.has(r)||n.hasCustomDefaultValue}}}}function Vt([e,t],n){return qt({controller:n,token:e,typeDefinition:t})}function Ht(e){switch(e){case Array:return`array`;case Boolean:return`boolean`;case Number:return`number`;case Object:return`object`;case String:return`string`}}function Ut(e){switch(typeof e){case`boolean`:return`boolean`;case`number`:return`number`;case`string`:return`string`}if(Array.isArray(e))return`array`;if(Object.prototype.toString.call(e)===`[object Object]`)return`object`}function Wt(e){let{controller:t,token:n,typeObject:r}=e,i=Pe(r.type),a=Pe(r.default),o=i&&a,s=i&&!a,c=!i&&a,l=Ht(r.type),u=Ut(e.typeObject.default);if(s)return l;if(c)return u;if(l!==u){let e=t?`${t}.${n}`:n;throw Error(`The specified default value for the Stimulus Value \"${e}\" must match the defined type \"${l}\". The provided default value of \"${r.default}\" is of type \"${u}\".`)}if(o)return l}function Gt(e){let{controller:t,token:n,typeDefinition:r}=e,i=Wt({controller:t,token:n,typeObject:r}),a=Ut(r),o=Ht(r),s=i||a||o;if(s)return s;let c=t?`${t}.${r}`:n;throw Error(`Unknown value type \"${c}\" for \"${n}\" value`)}function Kt(e){let t=Ht(e);if(t)return I[t];let n=Fe(e,`default`),r=Fe(e,`type`),i=e;if(n)return i.default;if(r){let{type:e}=i,t=Ht(e);if(t)return I[t]}return e}function qt(e){let{token:t,typeDefinition:n}=e,r=`${Me(t)}-value`,i=Gt(e);return{type:i,key:r,name:ke(r),get defaultValue(){return Kt(n)},get hasCustomDefaultValue(){return Ut(n)!==void 0},reader:Jt[i],writer:Yt[i]||Yt.default}}let I={get array(){return[]},boolean:!1,number:0,get object(){return{}},string:``},Jt={array(e){let t=JSON.parse(e);if(!Array.isArray(t))throw TypeError(`expected value of type \"array\" but instead got value \"${e}\" of type \"${Ut(t)}\"`);return t},boolean(e){return!(e==`0`||String(e).toLowerCase()==`false`)},number(e){return Number(e.replace(/_/g,``))},object(e){let t=JSON.parse(e);if(typeof t!=`object`||!t||Array.isArray(t))throw TypeError(`expected value of type \"object\" but instead got value \"${e}\" of type \"${Ut(t)}\"`);return t},string(e){return e}},Yt={default:Zt,array:Xt,object:Xt};function Xt(e){return JSON.stringify(e)}function Zt(e){return`${e}`}var L=class{constructor(e){this.context=e}static get shouldLoad(){return!0}static afterLoad(e,t){}get application(){return this.context.application}get scope(){return this.context.scope}get element(){return this.scope.element}get identifier(){return this.scope.identifier}get targets(){return this.scope.targets}get outlets(){return this.scope.outlets}get classes(){return this.scope.classes}get data(){return this.scope.data}initialize(){}connect(){}disconnect(){}dispatch(e,{target:t=this.element,detail:n={},prefix:r=this.identifier,bubbles:i=!0,cancelable:a=!0}={}){let o=r?`${r}:${e}`:e,s=new CustomEvent(o,{detail:n,bubbles:i,cancelable:a});return t.dispatchEvent(s),s}};L.blessings=[Mt,Lt,zt,Pt],L.targets=[],L.outlets=[],L.values={};var Qt=class extends Error{response;request;options;constructor(e,t,n){let r=`${e.status||e.status===0?e.status:``} ${e.statusText??``}`.trim(),i=r?`status code ${r}`:`an unknown error`;super(`Request failed with ${i}: ${t.method} ${t.url}`),this.name=`HTTPError`,this.response=e,this.request=t,this.options=n}},$t=class extends Error{name=`NonError`;value;constructor(e){let t=`Non-error value was thrown`;try{typeof e==`string`?t=e:e&&typeof e==`object`&&`message`in e&&typeof e.message==`string`&&(t=e.message)}catch{}super(t),this.value=e}},en=class extends Error{name=`ForceRetryError`;customDelay;code;customRequest;constructor(e){let t=e?.cause?e.cause instanceof Error?e.cause:new $t(e.cause):void 0;super(e?.code?`Forced retry: ${e.code}`:`Forced retry`,t?{cause:t}:void 0),this.customDelay=e?.delay,this.code=e?.code,this.customRequest=e?.request}};let tn=(()=>{let e=!1,t=!1,n=typeof globalThis.ReadableStream==`function`,r=typeof globalThis.Request==`function`;if(n&&r)try{t=new globalThis.Request(`https://empty.invalid`,{body:new globalThis.ReadableStream,method:`POST`,get duplex(){return e=!0,`half`}}).headers.has(`Content-Type`)}catch(e){if(e instanceof Error&&e.message===`unsupported BodyInit type`)return!1;throw e}return e&&!t})(),nn=typeof globalThis.AbortController==`function`,rn=typeof globalThis.AbortSignal==`function`&&typeof globalThis.AbortSignal.any==`function`,an=typeof globalThis.ReadableStream==`function`,on=typeof globalThis.FormData==`function`,sn=[`get`,`post`,`put`,`patch`,`head`,`delete`],cn={json:`application/json`,text:`text/*`,formData:`multipart/form-data`,arrayBuffer:`*/*`,blob:`*/*`,bytes:`*/*`},ln=2147483647,un=new TextEncoder().encode(`------WebKitFormBoundaryaxpyiPgbbPti10Rw`).length,dn=Symbol(`stop`);var fn=class{options;constructor(e){this.options=e}};let pn=e=>new fn(e),mn={json:!0,parseJson:!0,stringifyJson:!0,searchParams:!0,prefixUrl:!0,retry:!0,timeout:!0,hooks:!0,throwHttpErrors:!0,onDownloadProgress:!0,onUploadProgress:!0,fetch:!0,context:!0},hn={next:!0},gn={method:!0,headers:!0,body:!0,mode:!0,credentials:!0,cache:!0,redirect:!0,referrer:!0,referrerPolicy:!0,integrity:!0,keepalive:!0,signal:!0,window:!0,duplex:!0},_n=e=>{if(!e)return 0;if(e instanceof FormData){let t=0;for(let[n,r]of e)t+=un,t+=new TextEncoder().encode(`Content-Disposition: form-data; name=\"${n}\"`).length,t+=typeof r==`string`?new TextEncoder().encode(r).length:r.size;return t}if(e instanceof Blob)return e.size;if(e instanceof ArrayBuffer)return e.byteLength;if(typeof e==`string`)return new TextEncoder().encode(e).length;if(e instanceof URLSearchParams)return new TextEncoder().encode(e.toString()).length;if(`byteLength`in e)return e.byteLength;if(typeof e==`object`&&e)try{let t=JSON.stringify(e);return new TextEncoder().encode(t).length}catch{return 0}return 0},vn=(e,t,n)=>{let r,i=0;return e.pipeThrough(new TransformStream({transform(e,a){if(a.enqueue(e),r){i+=r.byteLength;let e=t===0?0:i/t;e>=1&&(e=1-2**-52),n?.({percent:e,totalBytes:Math.max(t,i),transferredBytes:i},r)}r=e},flush(){r&&(i+=r.byteLength,n?.({percent:1,totalBytes:Math.max(t,i),transferredBytes:i},r))}}))},yn=(e,t)=>{if(!e.body)return e;if(e.status===204)return new Response(null,{status:e.status,statusText:e.statusText,headers:e.headers});let n=Math.max(0,Number(e.headers.get(`content-length`))||0);return new Response(vn(e.body,n,t),{status:e.status,statusText:e.statusText,headers:e.headers})},bn=(e,t,n)=>{if(!e.body)return e;let r=_n(n??e.body);return new Request(e,{duplex:`half`,body:vn(e.body,r,t)})},R=e=>typeof e==`object`&&!!e,xn=(...e)=>{for(let t of e)if((!R(t)||Array.isArray(t))&&t!==void 0)throw TypeError(\"The `options` argument must be an object\");return En({},...e)},Sn=(e={},t={})=>{let n=new globalThis.Headers(e),r=t instanceof globalThis.Headers,i=new globalThis.Headers(t);for(let[e,t]of i.entries())r&&t===`undefined`||t===void 0?n.delete(e):n.set(e,t);return n};function Cn(e,t,n){return Object.hasOwn(t,n)&&t[n]===void 0?[]:En(e[n]??[],t[n]??[])}let wn=(e={},t={})=>({beforeRequest:Cn(e,t,`beforeRequest`),beforeRetry:Cn(e,t,`beforeRetry`),afterResponse:Cn(e,t,`afterResponse`),beforeError:Cn(e,t,`beforeError`)}),Tn=(e,t)=>{let n=new URLSearchParams;for(let r of[e,t])if(r!==void 0)if(r instanceof URLSearchParams)for(let[e,t]of r.entries())n.append(e,t);else if(Array.isArray(r))for(let e of r){if(!Array.isArray(e)||e.length!==2)throw TypeError(`Array search parameters must be provided in [[key, value], ...] format`);n.append(String(e[0]),String(e[1]))}else if(R(r))for(let[e,t]of Object.entries(r))t!==void 0&&n.append(e,String(t));else{let e=new URLSearchParams(r);for(let[t,r]of e.entries())n.append(t,r)}return n},En=(...e)=>{let t={},n={},r={},i,a=[];for(let o of e)if(Array.isArray(o))Array.isArray(t)||(t=[]),t=[...t,...o];else if(R(o)){for(let[e,n]of Object.entries(o)){if(e===`signal`&&n instanceof globalThis.AbortSignal){a.push(n);continue}if(e===`context`){if(n!=null&&(!R(n)||Array.isArray(n)))throw TypeError(\"The `context` option must be an object\");t={...t,context:n==null?{}:{...t.context,...n}};continue}if(e===`searchParams`){i=n==null?void 0:i===void 0?n:Tn(i,n);continue}R(n)&&e in t&&(n=En(t[e],n)),t={...t,[e]:n}}R(o.hooks)&&(r=wn(r,o.hooks),t.hooks=r),R(o.headers)&&(n=Sn(n,o.headers),t.headers=n)}return i!==void 0&&(t.searchParams=i),a.length>0&&(a.length===1?t.signal=a[0]:rn?t.signal=AbortSignal.any(a):t.signal=a.at(-1)),t.context===void 0&&(t.context={}),t},Dn=e=>sn.includes(e)?e.toUpperCase():e,On={limit:2,methods:[`get`,`put`,`head`,`delete`,`options`,`trace`],statusCodes:[408,413,429,500,502,503,504],afterStatusCodes:[413,429,503],maxRetryAfter:1/0,backoffLimit:1/0,delay:e=>.3*2**(e-1)*1e3,jitter:void 0,retryOnTimeout:!1},kn=(e={})=>{if(typeof e==`number`)return{...On,limit:e};if(e.methods&&!Array.isArray(e.methods))throw Error(`retry.methods must be an array`);if(e.methods&&=e.methods.map(e=>e.toLowerCase()),e.statusCodes&&!Array.isArray(e.statusCodes))throw Error(`retry.statusCodes must be an array`);let t=Object.fromEntries(Object.entries(e).filter(([,e])=>e!==void 0));return{...On,...t}};var An=class extends Error{request;constructor(e){super(`Request timed out: ${e.method} ${e.url}`),this.name=`TimeoutError`,this.request=e}};async function jn(e,t,n,r){return new Promise((i,a)=>{let o=setTimeout(()=>{n&&n.abort(),a(new An(e))},r.timeout);r.fetch(e,t).then(i).catch(a).then(()=>{clearTimeout(o)})})}async function Mn(e,{signal:t}){return new Promise((n,r)=>{t&&(t.throwIfAborted(),t.addEventListener(`abort`,i,{once:!0}));function i(){clearTimeout(a),r(t.reason)}let a=setTimeout(()=>{t?.removeEventListener(`abort`,i),n()},e)})}let Nn=(e,t)=>{let n={};for(let r in t)Object.hasOwn(t,r)&&!(r in gn)&&!(r in mn)&&(!(r in e)||r in hn)&&(n[r]=t[r]);return n},Pn=e=>e===void 0?!1:Array.isArray(e)?e.length>0:e instanceof URLSearchParams?e.size>0:typeof e==`object`?Object.keys(e).length>0:typeof e==`string`?e.trim().length>0:!!e;function Fn(e){return e instanceof Qt||e?.name===Qt.name}function In(e){return e instanceof An||e?.name===An.name}var Ln=class e{static create(t,n){let r=new e(t,n),i=r.#p(async()=>{if(typeof r.#i.timeout==`number`&&r.#i.timeout>ln)throw RangeError(`The \\`timeout\\` option cannot be greater than ${ln}`);await Promise.resolve();let e=await r.#m();for(let t of r.#i.hooks.afterResponse){let n=r.#u(e.clone()),i;try{i=await t(r.request,r.#h(),n,{retryCount:r.#n})}catch(t){throw r.#f(n),r.#f(e),t}if(i instanceof fn)throw r.#f(n),r.#f(e),new en(i.options);let a=i instanceof globalThis.Response?i:e;n!==a&&r.#f(n),e!==a&&r.#f(e),e=a}if(r.#u(e),!e.ok&&(typeof r.#i.throwHttpErrors==`function`?r.#i.throwHttpErrors(e.status):r.#i.throwHttpErrors)){let t=new Qt(e,r.request,r.#h());for(let e of r.#i.hooks.beforeError)t=await e(t,{retryCount:r.#n});throw t}if(r.#i.onDownloadProgress){if(typeof r.#i.onDownloadProgress!=`function`)throw TypeError(\"The `onDownloadProgress` option must be a function\");if(!an)throw Error(\"Streams are not supported in your environment. `ReadableStream` is missing.\");let t=e.clone();return r.#f(e),yn(t,r.#i.onDownloadProgress)}return e}).finally(()=>{let e=r.#a;r.#d(e?.body??void 0),r.#d(r.request.body??void 0)});for(let[e,t]of Object.entries(cn))e===`bytes`&&typeof globalThis.Response?.prototype?.bytes!=`function`||(i[e]=async()=>{r.request.headers.set(`accept`,r.request.headers.get(`accept`)||t);let a=await i;if(e===`json`){if(a.status===204)return``;let e=await a.text();return e===``?``:n.parseJson?n.parseJson(e):JSON.parse(e)}return a[e]()});return i}static#e(e){return e&&typeof e==`object`&&!Array.isArray(e)&&!(e instanceof URLSearchParams)?Object.fromEntries(Object.entries(e).filter(([,e])=>e!==void 0)):e}request;#t;#n=0;#r;#i;#a;#o;#s;constructor(t,n={}){if(this.#r=t,this.#i={...n,headers:Sn(this.#r.headers,n.headers),hooks:wn({beforeRequest:[],beforeRetry:[],beforeError:[],afterResponse:[]},n.hooks),method:Dn(n.method??this.#r.method??`GET`),prefixUrl:String(n.prefixUrl||``),retry:kn(n.retry),throwHttpErrors:n.throwHttpErrors??!0,timeout:n.timeout??1e4,fetch:n.fetch??globalThis.fetch.bind(globalThis),context:n.context??{}},typeof this.#r!=`string`&&!(this.#r instanceof URL||this.#r instanceof globalThis.Request))throw TypeError(\"`input` must be a string, URL, or Request\");if(this.#i.prefixUrl&&typeof this.#r==`string`){if(this.#r.startsWith(`/`))throw Error(\"`input` must not begin with a slash when using `prefixUrl`\");this.#i.prefixUrl.endsWith(`/`)||(this.#i.prefixUrl+=`/`),this.#r=this.#i.prefixUrl+this.#r}nn&&rn&&(this.#o=this.#i.signal??this.#r.signal,this.#t=new globalThis.AbortController,this.#i.signal=this.#o?AbortSignal.any([this.#o,this.#t.signal]):this.#t.signal),tn&&(this.#i.duplex=`half`),this.#i.json!==void 0&&(this.#i.body=this.#i.stringifyJson?.(this.#i.json)??JSON.stringify(this.#i.json),this.#i.headers.set(`content-type`,this.#i.headers.get(`content-type`)??`application/json`));let r=n.headers&&new globalThis.Headers(n.headers).has(`content-type`);if(this.#r instanceof globalThis.Request&&(on&&this.#i.body instanceof globalThis.FormData||this.#i.body instanceof URLSearchParams)&&!r&&this.#i.headers.delete(`content-type`),this.request=new globalThis.Request(this.#r,this.#i),Pn(this.#i.searchParams)){let t=`?`+(typeof this.#i.searchParams==`string`?this.#i.searchParams.replace(/^\\?/,``):new URLSearchParams(e.#e(this.#i.searchParams)).toString()),n=this.request.url.replace(/(?:\\?.*?)?(?=#|$)/,t);this.request=new globalThis.Request(n,this.#i)}if(this.#i.onUploadProgress){if(typeof this.#i.onUploadProgress!=`function`)throw TypeError(\"The `onUploadProgress` option must be a function\");if(!tn)throw Error(\"Request streams are not supported in your environment. The `duplex` option for `Request` is not available.\");this.request=this.#_(this.request,this.#i.body??void 0)}}#c(){let e=this.#i.retry.delay(this.#n),t=e;this.#i.retry.jitter===!0?t=Math.random()*e:typeof this.#i.retry.jitter==`function`&&(t=this.#i.retry.jitter(e),(!Number.isFinite(t)||t<0)&&(t=e));let n=this.#i.retry.backoffLimit??1/0;return Math.min(n,t)}async#l(e){if(this.#n++,this.#n>this.#i.retry.limit)throw e;let t=e instanceof Error?e:new $t(e);if(t instanceof en)return t.customDelay??this.#c();if(!this.#i.retry.methods.includes(this.request.method.toLowerCase()))throw e;if(this.#i.retry.shouldRetry!==void 0){let n=await this.#i.retry.shouldRetry({error:t,retryCount:this.#n});if(n===!1)throw e;if(n===!0)return this.#c()}if(In(e)&&!this.#i.retry.retryOnTimeout)throw e;if(Fn(e)){if(!this.#i.retry.statusCodes.includes(e.response.status))throw e;let t=e.response.headers.get(`Retry-After`)??e.response.headers.get(`RateLimit-Reset`)??e.response.headers.get(`X-RateLimit-Retry-After`)??e.response.headers.get(`X-RateLimit-Reset`)??e.response.headers.get(`X-Rate-Limit-Reset`);if(t&&this.#i.retry.afterStatusCodes.includes(e.response.status)){let e=Number(t)*1e3;Number.isNaN(e)?e=Date.parse(t)-Date.now():e>=Date.parse(`2024-01-01`)&&(e-=Date.now());let n=this.#i.retry.maxRetryAfter??e;return e<n?e:n}if(e.response.status===413)throw e}return this.#c()}#u(e){return this.#i.parseJson&&(e.json=async()=>this.#i.parseJson(await e.text())),e}#d(e){e&&e.cancel().catch(()=>void 0)}#f(e){this.#d(e.body??void 0)}async#p(e){try{return await e()}catch(t){let n=Math.min(await this.#l(t),ln);if(this.#n<1)throw t;if(await Mn(n,this.#o?{signal:this.#o}:{}),t instanceof en&&t.customRequest){let e=this.#i.signal?new globalThis.Request(t.customRequest,{signal:this.#i.signal}):new globalThis.Request(t.customRequest);this.#g(e)}for(let e of this.#i.hooks.beforeRetry){let n=await e({request:this.request,options:this.#h(),error:t,retryCount:this.#n});if(n instanceof globalThis.Request){this.#g(n);break}if(n instanceof globalThis.Response)return n;if(n===dn)return}return this.#p(e)}}async#m(){this.#t?.signal.aborted&&(this.#t=new globalThis.AbortController,this.#i.signal=this.#o?AbortSignal.any([this.#o,this.#t.signal]):this.#t.signal,this.request=new globalThis.Request(this.request,{signal:this.#i.signal}));for(let e of this.#i.hooks.beforeRequest){let t=await e(this.request,this.#h(),{retryCount:this.#n});if(t instanceof Response)return t;if(t instanceof globalThis.Request){this.#g(t);break}}let e=Nn(this.request,this.#i);return this.#a=this.request,this.request=this.#a.clone(),this.#i.timeout===!1?this.#i.fetch(this.#a,e):jn(this.#a,e,this.#t,this.#i)}#h(){if(!this.#s){let{hooks:e,...t}=this.#i;this.#s=Object.freeze(t)}return this.#s}#g(e){this.#s=void 0,this.request=this.#_(e)}#_(e,t){return!this.#i.onUploadProgress||!e.body?e:bn(e,this.#i.onUploadProgress,t??this.#i.body??void 0)}};\n/*! MIT License © Sindre Sorhus */\nlet Rn=e=>{let t=(t,n)=>Ln.create(t,xn(e,n));for(let n of sn)t[n]=(t,r)=>Ln.create(t,xn(e,r,{method:n}));return t.create=e=>Rn(xn(e)),t.extend=t=>(typeof t==`function`&&(t=t(e??{})),Rn(xn(e,t))),t.stop=dn,t.retry=pn,t};var zn=Rn(),Bn=class{constructor(e,t,{tabInsertsSuggestions:n,defaultFirstOption:r,scrollIntoViewOptions:i}={}){this.input=e,this.list=t,this.tabInsertsSuggestions=n??!0,this.defaultFirstOption=r??!1,this.scrollIntoViewOptions=i??{block:`nearest`,inline:`nearest`},this.isComposing=!1,t.id||=`combobox-${Math.random().toString().slice(2,6)}`,this.ctrlBindings=!!navigator.userAgent.match(/Macintosh/),this.keyboardEventHandler=e=>Vn(e,this),this.compositionEventHandler=e=>qn(e,this),this.inputHandler=this.clearSelection.bind(this),e.setAttribute(`role`,`combobox`),e.setAttribute(`aria-controls`,t.id),e.setAttribute(`aria-expanded`,`false`),e.setAttribute(`aria-autocomplete`,`list`),e.setAttribute(`aria-haspopup`,`listbox`)}destroy(){this.clearSelection(),this.stop(),this.input.removeAttribute(`role`),this.input.removeAttribute(`aria-controls`),this.input.removeAttribute(`aria-expanded`),this.input.removeAttribute(`aria-autocomplete`),this.input.removeAttribute(`aria-haspopup`)}start(){this.input.setAttribute(`aria-expanded`,`true`),this.input.addEventListener(`compositionstart`,this.compositionEventHandler),this.input.addEventListener(`compositionend`,this.compositionEventHandler),this.input.addEventListener(`input`,this.inputHandler),this.input.addEventListener(`keydown`,this.keyboardEventHandler),this.list.addEventListener(`click`,Hn),this.indicateDefaultOption()}stop(){this.clearSelection(),this.input.setAttribute(`aria-expanded`,`false`),this.input.removeEventListener(`compositionstart`,this.compositionEventHandler),this.input.removeEventListener(`compositionend`,this.compositionEventHandler),this.input.removeEventListener(`input`,this.inputHandler),this.input.removeEventListener(`keydown`,this.keyboardEventHandler),this.list.removeEventListener(`click`,Hn)}indicateDefaultOption(){var e;this.defaultFirstOption&&((e=Array.from(this.list.querySelectorAll(`[role=\"option\"]:not([aria-disabled=\"true\"])`)).filter(Kn)[0])==null||e.setAttribute(`data-combobox-option-default`,`true`))}navigate(e=1){let t=Array.from(this.list.querySelectorAll(`[aria-selected=\"true\"]`)).filter(Kn)[0],n=Array.from(this.list.querySelectorAll(`[role=\"option\"]`)).filter(Kn),r=n.indexOf(t);if(r===n.length-1&&e===1||r===0&&e===-1){this.clearSelection(),this.input.focus();return}let i=e===1?0:n.length-1;if(t&&r>=0){let t=r+e;t>=0&&t<n.length&&(i=t)}let a=n[i];if(a)for(let e of n)e.removeAttribute(`data-combobox-option-default`),a===e?(this.input.setAttribute(`aria-activedescendant`,a.id),a.setAttribute(`aria-selected`,`true`),Gn(a),a.scrollIntoView(this.scrollIntoViewOptions)):e.removeAttribute(`aria-selected`)}clearSelection(){this.input.removeAttribute(`aria-activedescendant`);for(let e of this.list.querySelectorAll(`[aria-selected=\"true\"]`))e.removeAttribute(`aria-selected`);this.indicateDefaultOption()}};function Vn(e,t){if(!(e.shiftKey||e.metaKey||e.altKey)&&!(!t.ctrlBindings&&e.ctrlKey)&&!t.isComposing)switch(e.key){case`Enter`:Un(t.input,t.list)&&e.preventDefault();break;case`Tab`:t.tabInsertsSuggestions&&Un(t.input,t.list)&&e.preventDefault();break;case`Escape`:t.clearSelection();break;case`ArrowDown`:t.navigate(1),e.preventDefault();break;case`ArrowUp`:t.navigate(-1),e.preventDefault();break;case`n`:t.ctrlBindings&&e.ctrlKey&&(t.navigate(1),e.preventDefault());break;case`p`:t.ctrlBindings&&e.ctrlKey&&(t.navigate(-1),e.preventDefault());break;default:if(e.ctrlKey)break;t.clearSelection()}}function Hn(e){if(!(e.target instanceof Element))return;let t=e.target.closest(`[role=\"option\"]`);t&&t.getAttribute(`aria-disabled`)!==`true`&&Wn(t,{event:e})}function Un(e,t){let n=t.querySelector(`[aria-selected=\"true\"], [data-combobox-option-default=\"true\"]`);return n?(n.getAttribute(`aria-disabled`)===`true`||n.click(),!0):!1}function Wn(e,t){e.dispatchEvent(new CustomEvent(`combobox-commit`,{bubbles:!0,detail:t}))}function Gn(e){e.dispatchEvent(new Event(`combobox-select`,{bubbles:!0}))}function Kn(e){return!e.hidden&&!(e instanceof HTMLInputElement&&e.type===`hidden`)&&(e.offsetWidth>0||e.offsetHeight>0)}function qn(e,t){t.isComposing=e.type===`compositionstart`,document.getElementById(t.input.getAttribute(`aria-controls`)||``)&&t.clearSelection()}let Jn=/\\s|\\(|\\[/;function Yn(e,t,n,{multiWord:r,lookBackIndex:i,lastMatchPosition:a}={multiWord:!1,lookBackIndex:0,lastMatchPosition:null}){let o=e.lastIndexOf(t,n-1);if(o===-1||o<i)return;if(r){if(a!=null){if(a===o)return;o=a-t.length}if(e[o+1]===` `&&n>=o+t.length+1||e.lastIndexOf(`\n`,n-1)>o||e.lastIndexOf(`.`,n-1)>o)return}else if(e.lastIndexOf(` `,n-1)>o)return;let s=e[o-1];if(!(s&&!Jn.test(s)))return{text:e.substring(o+t.length,n),position:o+t.length}}var Xn=class extends Event{constructor(){super(`update`)}};let Zn=new WeakMap;var Qn=class e extends EventTarget{#e=new MutationObserver(()=>this.#f());#t=new ResizeObserver(()=>this.#d());#n;#r=document.createElement(`div`);#i=document.createElement(`div`);static for(t){let n=Zn.get(t);return n||(n=new e(t),Zn.set(t,n)),n}constructor(e){super(),this.#n=new WeakRef(e),this.#r.style.position=`absolute`,this.#r.style.pointerEvents=`none`,this.#r.setAttribute(`aria-hidden`,`true`),this.#r.appendChild(this.#i),this.#i.style.pointerEvents=`none`,this.#i.style.userSelect=`none`,this.#i.style.overflow=`hidden`,this.#i.style.display=`block`,this.#i.style.visibility=`hidden`,e instanceof HTMLTextAreaElement?(this.#i.style.whiteSpace=`pre-wrap`,this.#i.style.wordWrap=`break-word`):(this.#i.style.whiteSpace=`nowrap`,this.#i.style.display=`table-cell`,this.#i.style.verticalAlign=`middle`),e.after(this.#r),this.#f(),this.#p(),this.#e.observe(e,{attributeFilter:[`style`,`dir`]}),this.#t.observe(e),document.addEventListener(`scroll`,this.#h,{capture:!0}),window.addEventListener(`resize`,this.#h,{capture:!0}),e.addEventListener(`input`,this.#m,{capture:!0})}get element(){return this.#i}forceUpdate(){this.#f(),this.#p()}disconnect(){this.#r?.remove(),this.#e.disconnect(),this.#t.disconnect(),document.removeEventListener(`scroll`,this.#h,{capture:!0}),window.removeEventListener(`resize`,this.#h,{capture:!0});let e=this.#a;e&&(e.removeEventListener(`input`,this.#m,{capture:!0}),Zn.delete(e))}get#a(){return this.#n?.deref()}#o(e){let t=this.#a;return t?e(t):this.disconnect()}#s=0;#c=0;#l(){this.#o(e=>{let t=window.getComputedStyle(e);this.#i.style.height=t.height,this.#i.style.width=t.width,e.clientHeight!==this.#i.clientHeight&&(this.#i.style.height=`calc(${t.height} + ${e.clientHeight-this.#i.clientHeight}px)`),e.clientWidth!==this.#i.clientWidth&&(this.#i.style.width=`calc(${t.width} + ${e.clientWidth-this.#i.clientWidth}px)`);let n=e.getBoundingClientRect(),r=this.#i.getBoundingClientRect();this.#s=this.#s+n.left-r.left,this.#c=this.#c+n.top-r.top,this.#i.style.transform=`translate(${this.#s}px, ${this.#c}px)`,this.#i.scrollTop=e.scrollTop,this.#i.scrollLeft=e.scrollLeft,this.dispatchEvent(new Xn)})}#u=!1;#d(){this.#u||(this.#u=!0,requestAnimationFrame(()=>{this.#l(),this.#u=!1}))}#f(){this.#o(e=>{let t=window.getComputedStyle(e);for(let e of $n)this.#i.style[e]=t[e];this.#d()})}#p(){this.#o(e=>{this.#i.textContent=e.value,this.#l()})}#m=()=>this.#p();#h=e=>{this.#o(t=>{(e.target===document||e.target===window||e.target instanceof Node&&e.target.contains(t))&&this.#d()})}};let $n=`direction.writingMode.unicodeBidi.textOrientation.boxSizing.borderTopWidth.borderRightWidth.borderBottomWidth.borderLeftWidth.borderStyle.paddingTop.paddingRight.paddingBottom.paddingLeft.fontStyle.fontVariant.fontWeight.fontStretch.fontSize.fontSizeAdjust.lineHeight.fontFamily.textAlign.textTransform.textIndent.textDecoration.letterSpacing.wordSpacing.tabSize.MozTabSize`.split(`.`);var er=class e{#e;#t;#n;constructor(e,t=0,n=t){this.#e=e,this.#t=t,this.#n=n}static fromSelection(t){let{selectionStart:n,selectionEnd:r}=t;return new e(t,n??void 0,r??void 0)}get collapsed(){return this.startOffset===this.endOffset}get commonAncestorContainer(){return this.#e}get endContainer(){return this.#e}get startContainer(){return this.#e}get startOffset(){return this.#t}get endOffset(){return this.#n}setStartOffset(e){this.#t=this.#a(e)}setEndOffset(e){this.#n=this.#a(e)}collapse(e=!1){e?this.setEndOffset(this.startOffset):this.setStartOffset(this.endOffset)}cloneContents(){return this.#o().cloneContents()}cloneRange(){return new e(this.#e,this.startOffset,this.endOffset)}getBoundingClientRect(){return this.#o().getBoundingClientRect()}getClientRects(){return this.#o().getClientRects()}toString(){return this.#o().toString()}getStyleClone(){return this.#r}get#r(){return Qn.for(this.#e)}get#i(){return this.#r}#a(e){return Math.max(0,Math.min(e,this.#e.value.length))}#o(){let e=document.createRange(),t=this.#i.element.childNodes[0];return t&&(e.setStart(t,this.startOffset),e.setEnd(t,this.endOffset)),e}};let tr=new WeakMap;var nr=class{constructor(e,t){this.expander=e,this.input=t,this.combobox=null,this.menu=null,this.match=null,this.justPasted=!1,this.lookBackIndex=0,this.oninput=this.onInput.bind(this),this.onpaste=this.onPaste.bind(this),this.onkeydown=this.onKeydown.bind(this),this.oncommit=this.onCommit.bind(this),this.onmousedown=this.onMousedown.bind(this),this.onblur=this.onBlur.bind(this),this.interactingWithList=!1,t.addEventListener(`paste`,this.onpaste),t.addEventListener(`input`,this.oninput),t.addEventListener(`keydown`,this.onkeydown),t.addEventListener(`blur`,this.onblur)}destroy(){this.input.removeEventListener(`paste`,this.onpaste),this.input.removeEventListener(`input`,this.oninput),this.input.removeEventListener(`keydown`,this.onkeydown),this.input.removeEventListener(`blur`,this.onblur)}dismissMenu(){this.deactivate()&&(this.lookBackIndex=this.input.selectionEnd||this.lookBackIndex)}activate(e,t){this.input!==document.activeElement&&this.input!==document.activeElement?.shadowRoot?.activeElement||(this.deactivate(),this.menu=t,t.id||=`text-expander-${Math.floor(Math.random()*1e5).toString()}`,this.expander.append(t),this.combobox=new Bn(this.input,t),this.expander.dispatchEvent(new Event(`text-expander-activate`)),this.positionMenu(t,e.position),this.combobox.start(),t.addEventListener(`combobox-commit`,this.oncommit),t.addEventListener(`mousedown`,this.onmousedown),this.combobox.navigate(1))}positionMenu(e,t){let n=Math.min(t,this.input.value.length),r=new er(this.input,n).getBoundingClientRect(),i={left:r.left,top:r.top+r.height},a=e.getBoundingClientRect(),o={left:i.left-a.left,top:i.top-a.top};if(o.left!==0||o.top!==0){let t=getComputedStyle(e);e.style.left=t.left?`calc(${t.left} + ${o.left}px)`:`${o.left}px`,e.style.top=t.top?`calc(${t.top} + ${o.top}px)`:`${o.top}px`}}deactivate(){let e=this.menu;return!e||!this.combobox?!1:(this.expander.dispatchEvent(new Event(`text-expander-deactivate`)),this.menu=null,e.removeEventListener(`combobox-commit`,this.oncommit),e.removeEventListener(`mousedown`,this.onmousedown),this.combobox.destroy(),this.combobox=null,e.remove(),!0)}onCommit({target:e}){let t=e;if(!(t instanceof HTMLElement)||!this.combobox)return;let n=this.match;if(!n)return;let r=this.input.value.substring(0,n.position-n.key.length),i=this.input.value.substring(n.position+n.text.length),a={item:t,key:n.key,value:null,continue:!1};if(!this.expander.dispatchEvent(new CustomEvent(`text-expander-value`,{cancelable:!0,detail:a}))||!a.value)return;let o=this.expander.getAttribute(`suffix`)??` `;a.continue&&(o=``);let s=`${a.value}${o}`;this.input.value=r+s+i;let c=r.length+s.length;this.deactivate(),this.input.focus({preventScroll:!0}),this.input.selectionStart=c,this.input.selectionEnd=c,a.continue||(this.lookBackIndex=c,this.match=null),this.expander.dispatchEvent(new CustomEvent(`text-expander-committed`,{cancelable:!1,detail:{input:this.input}}))}onBlur(){if(this.interactingWithList){this.interactingWithList=!1;return}this.deactivate()}onPaste(){this.justPasted=!0}isMatchStillValid(e){return e.position<=this.input.value.length}async onInput(){if(this.justPasted){this.justPasted=!1;return}let e=this.findMatch();if(e){this.match=e;let t=await this.notifyProviders(e);if(!this.match||!this.isMatchStillValid(e)){this.match=null,this.deactivate();return}t?this.activate(e,t):this.deactivate()}else this.match=null,this.deactivate()}findMatch(){let e=this.input.selectionEnd||0,t=this.input.value;e<=this.lookBackIndex&&(this.lookBackIndex=e-1);for(let{key:n,multiWord:r}of this.expander.keys){let i=Yn(t,n,e,{multiWord:r,lookBackIndex:this.lookBackIndex,lastMatchPosition:this.match?this.match.position:null});if(i)return{text:i.text,key:n,position:i.position}}}async notifyProviders(e){let t=[],n=new CustomEvent(`text-expander-change`,{cancelable:!0,detail:{provide:e=>t.push(e),text:e.text,key:e.key}});if(this.expander.dispatchEvent(n))return(await Promise.all(t)).filter(e=>e.matched).map(e=>e.fragment)[0]}onMousedown(){this.interactingWithList=!0}onKeydown(e){e.key===`Escape`&&(this.match=null,this.deactivate()&&(this.lookBackIndex=this.input.selectionEnd||this.lookBackIndex,e.stopImmediatePropagation(),e.preventDefault()))}},rr=class extends HTMLElement{get keys(){let e=this.getAttribute(`keys`),t=e?e.split(` `):[],n=this.getAttribute(`multiword`),r=n?n.split(` `):[],i=r.length===0&&this.hasAttribute(`multiword`);return t.map(e=>({key:e,multiWord:i||r.includes(e)}))}set keys(e){this.setAttribute(`keys`,e)}connectedCallback(){let e=this.querySelector(`input[type=\"text\"], textarea`);if(!(e instanceof HTMLInputElement||e instanceof HTMLTextAreaElement))return;let t=new nr(this,e);tr.set(this,t)}disconnectedCallback(){let e=tr.get(this);e&&(e.destroy(),tr.delete(this))}dismiss(){let e=tr.get(this);e&&e.dismissMenu()}};window.customElements.get(`text-expander`)||(window.TextExpanderElement=rr,window.customElements.define(`text-expander`,rr));var ir=Object.defineProperty,ar=(e,t,n)=>t in e?ir(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,or=(e,t,n)=>(ar(e,typeof t==`symbol`?t:t+``,n),n),sr=class extends HTMLElement{constructor(){super(...arguments),or(this,`onToggle`,e=>{let t=e.target;t.open&&this.querySelectorAll(`:scope > :is(ui-disclosure, details)`).forEach(e=>{e.toggleAttribute(`open`,e===t),this.required&&e.toggleAttribute(`disabled`,e===t)})})}connectedCallback(){this.addEventListener(`toggle`,this.onToggle,{capture:!0})}disconnectedCallback(){this.removeEventListener(`toggle`,this.onToggle,{capture:!0})}get required(){return this.hasAttribute(`required`)}},cr=Object.defineProperty,lr=(e,t,n)=>t in e?cr(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,ur=(e,t,n)=>(lr(e,typeof t==`symbol`?t:t+``,n),n);let dr=class e extends HTMLElement{constructor(){super(...arguments),ur(this,`timeouts`,new Map),ur(this,`index`,0)}connectedCallback(){this.hasAttribute(`role`)||this.setAttribute(`role`,`status`),this.hasAttribute(`aria-live`)||this.setAttribute(`aria-live`,`polite`),this.hasAttribute(`aria-relevant`)||this.setAttribute(`aria-relevant`,`additions`);for(let e of this.children)this.show(e)}show(t,n={}){let r=n.key||t.dataset.key||String(this.index++);this.dismiss(r),t.dataset.key=r,this.contains(t)||this.append(t);let i=Number(n.duration===void 0?t.dataset.duration||e.duration:n.duration);return i>0&&(this.startTimeout(t,i),t.addEventListener(`mouseenter`,this.clearTimeout.bind(this,t)),t.addEventListener(`focusin`,this.clearTimeout.bind(this,t)),t.addEventListener(`mouseleave`,this.startTimeout.bind(this,t,i)),t.addEventListener(`focusout`,this.startTimeout.bind(this,t,i))),r}dismiss(e){if(typeof e==`string`){this.querySelectorAll(`[data-key=\"${e}\"]`).forEach(e=>this.dismiss(e));return}e.remove(),this.clearTimeout(e)}clear(){Array.from(this.children).forEach(e=>{this.dismiss(e)})}speak(e){let t=document.createElement(`div`);Object.assign(t.style,{clip:`rect(0 0 0 0)`,clipPath:`inset(50%)`,height:`1px`,overflow:`hidden`,position:`absolute`,whiteSpace:`nowrap`,width:`1px`}),t.textContent=e,this.show(t)}startTimeout(e,t){this.clearTimeout(e),this.timeouts.set(e,window.setTimeout(()=>{this.dismiss(e)},t))}clearTimeout(e){this.timeouts.has(e)&&clearTimeout(this.timeouts.get(e))}};ur(dr,`duration`,1e4);let fr=dr;function pr(e){e._currentTransition&&=(e.classList.remove(...[`active`,`from`,`to`].map(t=>e._currentTransition+t)),null)}async function mr(e,t,n={}){let r=yr(n)+t+`-`,i=e.classList;pr(e),e._currentTransition=r,i.add(r+`active`,r+`from`),await _r(),i.add(r+`to`),i.remove(r+`from`),await vr(e),i.remove(r+`to`,r+`active`),e._currentTransition===r&&(e._currentTransition=null)}function hr(e,t={}){return mr(e,`enter`,t)}function gr(e,t={}){return mr(e,`leave`,t)}function _r(){return new Promise(e=>requestAnimationFrame(()=>requestAnimationFrame(()=>e())))}async function vr(e){if(getComputedStyle(e).transitionDuration.startsWith(`0s`))return;let t=!1,n=n=>t||=n.target===e;if(e.addEventListener(`transitionstart`,n),await _r(),e.removeEventListener(`transitionstart`,n),t)return new Promise(t=>{let n=r=>{r.target===e&&(t(),e.removeEventListener(`transitionend`,n),e.removeEventListener(`transitioncancel`,n))};e.addEventListener(`transitionend`,n),e.addEventListener(`transitioncancel`,n)})}function yr(e){return e!=null&&e.prefix?e.prefix+`-`:``}function br(e){return e.altKey||e.ctrlKey||e.metaKey||e.shiftKey||e.button!==void 0&&e.button!==0}var xr=Object.defineProperty,Sr=(e,t,n)=>t in e?xr(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,Cr=(e,t,n)=>(Sr(e,typeof t==`symbol`?t:t+``,n),n),wr=class extends HTMLElement{constructor(){super(...arguments),Cr(this,`onButtonClick`,e=>{!br(e)&&!this.disabled&&(this.open=!this.open,e.preventDefault())})}static get observedAttributes(){return[`open`]}connectedCallback(){this.content.hidden=!this.open,this.button.setAttribute(`aria-expanded`,String(this.open)),this.button.addEventListener(`click`,this.onButtonClick)}disconnectedCallback(){pr(this.content),this.button.removeAttribute(`aria-expanded`),this.button.removeEventListener(`click`,this.onButtonClick)}get open(){return this.hasAttribute(`open`)}set open(e){e?this.setAttribute(`open`,``):this.removeAttribute(`open`)}get disabled(){return this.hasAttribute(`disabled`)}attributeChangedCallback(e,t,n){e===`open`&&(n===null?this.wasClosed():this.wasOpened())}wasOpened(){this.content.hidden&&(this.content.hidden=!1,hr(this.content)),this.button.setAttribute(`aria-expanded`,`true`),this.dispatchEvent(new Event(`toggle`))}wasClosed(){this.content.hidden||gr(this.content).then(()=>this.content.hidden=!0),this.button.setAttribute(`aria-expanded`,`false`),this.dispatchEvent(new Event(`toggle`))}get button(){return this.querySelector(`button, [role=button]`)}get content(){return this.children[1]}},Tr=[`input:not([inert]):not([inert] *)`,`select:not([inert]):not([inert] *)`,`textarea:not([inert]):not([inert] *)`,`a[href]:not([inert]):not([inert] *)`,`button:not([inert]):not([inert] *)`,`[tabindex]:not(slot):not([inert]):not([inert] *)`,`audio[controls]:not([inert]):not([inert] *)`,`video[controls]:not([inert]):not([inert] *)`,`[contenteditable]:not([contenteditable=\"false\"]):not([inert]):not([inert] *)`,`details>summary:first-of-type:not([inert]):not([inert] *)`,`details:not([inert]):not([inert] *)`],Er=Tr.join(`,`),Dr=typeof Element>`u`,Or=Dr?function(){}:Element.prototype.matches||Element.prototype.msMatchesSelector||Element.prototype.webkitMatchesSelector,kr=!Dr&&Element.prototype.getRootNode?function(e){return e?.getRootNode?.call(e)}:function(e){return e?.ownerDocument},Ar=function(e,t){t===void 0&&(t=!0);var n=e?.getAttribute?.call(e,`inert`);return n===``||n===`true`||t&&e&&(typeof e.closest==`function`?e.closest(`[inert]`):Ar(e.parentNode))},jr=function(e){var t=e?.getAttribute?.call(e,`contenteditable`);return t===``||t===`true`},Mr=function(e,t,n){if(Ar(e))return[];var r=Array.prototype.slice.apply(e.querySelectorAll(Er));return t&&Or.call(e,Er)&&r.unshift(e),r=r.filter(n),r},Nr=function(e,t,n){for(var r=[],i=Array.from(e);i.length;){var a=i.shift();if(!Ar(a,!1))if(a.tagName===`SLOT`){var o=a.assignedElements(),s=Nr(o.length?o:a.children,!0,n);n.flatten?r.push.apply(r,s):r.push({scopeParent:a,candidates:s})}else{Or.call(a,Er)&&n.filter(a)&&(t||!e.includes(a))&&r.push(a);var c=a.shadowRoot||typeof n.getShadowRoot==`function`&&n.getShadowRoot(a),l=!Ar(c,!1)&&(!n.shadowRootFilter||n.shadowRootFilter(a));if(c&&l){var u=Nr(c===!0?a.children:c.children,!0,n);n.flatten?r.push.apply(r,u):r.push({scopeParent:a,candidates:u})}else i.unshift.apply(i,a.children)}}return r},Pr=function(e){return!isNaN(parseInt(e.getAttribute(`tabindex`),10))},Fr=function(e){if(!e)throw Error(`No node provided`);return e.tabIndex<0&&(/^(AUDIO|VIDEO|DETAILS)$/.test(e.tagName)||jr(e))&&!Pr(e)?0:e.tabIndex},Ir=function(e,t){var n=Fr(e);return n<0&&t&&!Pr(e)?0:n},Lr=function(e,t){return e.tabIndex===t.tabIndex?e.documentOrder-t.documentOrder:e.tabIndex-t.tabIndex},Rr=function(e){return e.tagName===`INPUT`},zr=function(e){return Rr(e)&&e.type===`hidden`},Br=function(e){return e.tagName===`DETAILS`&&Array.prototype.slice.apply(e.children).some(function(e){return e.tagName===`SUMMARY`})},Vr=function(e,t){for(var n=0;n<e.length;n++)if(e[n].checked&&e[n].form===t)return e[n]},Hr=function(e){if(!e.name)return!0;var t=e.form||kr(e),n=function(e){return t.querySelectorAll(`input[type=\"radio\"][name=\"`+e+`\"]`)},r;if(typeof window<`u`&&window.CSS!==void 0&&typeof window.CSS.escape==`function`)r=n(window.CSS.escape(e.name));else try{r=n(e.name)}catch(e){return console.error(`Looks like you have a radio button with a name attribute containing invalid CSS selector characters and need the CSS.escape polyfill: %s`,e.message),!1}var i=Vr(r,e.form);return!i||i===e},Ur=function(e){return Rr(e)&&e.type===`radio`},Wr=function(e){return Ur(e)&&!Hr(e)},Gr=function(e){var t=e&&kr(e),n=t?.host,r=!1;if(t&&t!==e){var i,a,o;for(r=!!((i=n)!=null&&(a=i.ownerDocument)!=null&&a.contains(n)||e!=null&&(o=e.ownerDocument)!=null&&o.contains(e));!r&&n;){var s,c;t=kr(n),n=t?.host,r=!!((s=n)!=null&&(c=s.ownerDocument)!=null&&c.contains(n))}}return r},Kr=function(e){var t=e.getBoundingClientRect(),n=t.width,r=t.height;return n===0&&r===0},qr=function(e,t){var n=t.displayCheck,r=t.getShadowRoot;if(n===`full-native`&&`checkVisibility`in e)return!e.checkVisibility({checkOpacity:!1,opacityProperty:!1,contentVisibilityAuto:!0,visibilityProperty:!0,checkVisibilityCSS:!0});if(getComputedStyle(e).visibility===`hidden`)return!0;var i=Or.call(e,`details>summary:first-of-type`)?e.parentElement:e;if(Or.call(i,`details:not([open]) *`))return!0;if(!n||n===`full`||n===`full-native`||n===`legacy-full`){if(typeof r==`function`){for(var a=e;e;){var o=e.parentElement,s=kr(e);if(o&&!o.shadowRoot&&r(o)===!0)return Kr(e);e=e.assignedSlot?e.assignedSlot:!o&&s!==e.ownerDocument?s.host:o}e=a}if(Gr(e))return!e.getClientRects().length;if(n!==`legacy-full`)return!0}else if(n===`non-zero-area`)return Kr(e);return!1},Jr=function(e){if(/^(INPUT|BUTTON|SELECT|TEXTAREA)$/.test(e.tagName))for(var t=e.parentElement;t;){if(t.tagName===`FIELDSET`&&t.disabled){for(var n=0;n<t.children.length;n++){var r=t.children.item(n);if(r.tagName===`LEGEND`)return Or.call(t,`fieldset[disabled] *`)?!0:!r.contains(e)}return!0}t=t.parentElement}return!1},Yr=function(e,t){return!(t.disabled||zr(t)||qr(t,e)||Br(t)||Jr(t))},Xr=function(e,t){return!(Wr(t)||Fr(t)<0||!Yr(e,t))},Zr=function(e){var t=parseInt(e.getAttribute(`tabindex`),10);return!!(isNaN(t)||t>=0)},Qr=function(e){var t=[],n=[];return e.forEach(function(e,r){var i=!!e.scopeParent,a=i?e.scopeParent:e,o=Ir(a,i),s=i?Qr(e.candidates):a;o===0?i?t.push.apply(t,s):t.push(a):n.push({documentOrder:r,tabIndex:o,item:e,isScope:i,content:s})}),n.sort(Lr).reduce(function(e,t){return t.isScope?e.push.apply(e,t.content):e.push(t.content),e},[]).concat(t)},$r=function(e,t){return t||={},Qr(t.getShadowRoot?Nr([e],t.includeContainer,{filter:Xr.bind(null,t),flatten:!1,getShadowRoot:t.getShadowRoot,shadowRootFilter:Zr}):Mr(e,t.includeContainer,Xr.bind(null,t)))},ei=function(e,t){return t||={},t.getShadowRoot?Nr([e],t.includeContainer,{filter:Yr.bind(null,t),flatten:!0,getShadowRoot:t.getShadowRoot}):Mr(e,t.includeContainer,Yr.bind(null,t))},ti=function(e,t){if(t||={},!e)throw Error(`No node provided`);return Or.call(e,Er)===!1?!1:Xr(t,e)},ni=Tr.concat(`iframe:not([inert]):not([inert] *)`).join(`,`),ri=function(e,t){if(t||={},!e)throw Error(`No node provided`);return Or.call(e,ni)===!1?!1:Yr(t,e)},ii=Object.defineProperty,ai=(e,t,n)=>t in e?ii(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,oi=(e,t,n)=>(ai(e,typeof t==`symbol`?t:t+``,n),n);let si=class e extends HTMLElement{constructor(){super(...arguments),oi(this,`search`,``),oi(this,`searchTimeout`),oi(this,`onKeyDown`,t=>{if(!this.hidden){if(t.key===`ArrowUp`){this.navigate(-1),t.preventDefault();return}if(t.key===`ArrowDown`){this.navigate(1),t.preventDefault();return}t.key.length>1||t.ctrlKey||t.metaKey||t.altKey||(this.search+=t.key.toLowerCase(),t.preventDefault(),clearTimeout(this.searchTimeout),this.searchTimeout=window.setTimeout(()=>{this.search=``},e.searchDelay),this.items.some(e=>e.textContent?.trim().toLowerCase().indexOf(this.search)===0?(e.focus(),!0):!1))}})}connectedCallback(){this.hasAttribute(`role`)||this.setAttribute(`role`,`menu`),this.hasAttribute(`tabindex`)||this.setAttribute(`tabindex`,`-1`),this.items.forEach(e=>{e.hasAttribute(`tabindex`)||e.setAttribute(`tabindex`,`-1`)}),this.addEventListener(`keydown`,this.onKeyDown)}disconnectedCallback(){this.removeEventListener(`keydown`,this.onKeyDown)}get items(){return Array.from(this.querySelectorAll(`[role^=menuitem]`)).filter(e=>ri(e))}navigate(e){var t;let n=this.items,r=(document.activeElement instanceof HTMLElement?n.indexOf(document.activeElement):-1)+e;r<0&&(r=n.length-1),r>=n.length&&(r=0),(t=n[r])==null||t.focus()}};oi(si,`searchDelay`,800);let ci=si;\n/*!\n* focus-trap 7.8.0\n* @license MIT, https://github.com/focus-trap/focus-trap/blob/master/LICENSE\n*/\nfunction li(e,t){(t==null||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n<t;n++)r[n]=e[n];return r}function ui(e){if(Array.isArray(e))return li(e)}function di(e,t){var n=typeof Symbol<`u`&&e[Symbol.iterator]||e[`@@iterator`];if(!n){if(Array.isArray(e)||(n=bi(e))||t){n&&(e=n);var r=0,i=function(){};return{s:i,n:function(){return r>=e.length?{done:!0}:{done:!1,value:e[r++]}},e:function(e){throw e},f:i}}throw TypeError(`Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}var a,o=!0,s=!1;return{s:function(){n=n.call(e)},n:function(){var e=n.next();return o=e.done,e},e:function(e){s=!0,a=e},f:function(){try{o||n.return==null||n.return()}finally{if(s)throw a}}}}function fi(e,t,n){return(t=yi(t))in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function pi(e){if(typeof Symbol<`u`&&e[Symbol.iterator]!=null||e[`@@iterator`]!=null)return Array.from(e)}function mi(){throw TypeError(`Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}function hi(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function gi(e){for(var t=1;t<arguments.length;t++){var n=arguments[t]==null?{}:arguments[t];t%2?hi(Object(n),!0).forEach(function(t){fi(e,t,n[t])}):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):hi(Object(n)).forEach(function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))})}return e}function _i(e){return ui(e)||pi(e)||bi(e)||mi()}function vi(e,t){if(typeof e!=`object`||!e)return e;var n=e[Symbol.toPrimitive];if(n!==void 0){var r=n.call(e,t);if(typeof r!=`object`)return r;throw TypeError(`@@toPrimitive must return a primitive value.`)}return(t===`string`?String:Number)(e)}function yi(e){var t=vi(e,`string`);return typeof t==`symbol`?t:t+``}function bi(e,t){if(e){if(typeof e==`string`)return li(e,t);var n={}.toString.call(e).slice(8,-1);return n===`Object`&&e.constructor&&(n=e.constructor.name),n===`Map`||n===`Set`?Array.from(e):n===`Arguments`||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?li(e,t):void 0}}var xi={getActiveTrap:function(e){return e?.length>0?e[e.length-1]:null},activateTrap:function(e,t){t!==xi.getActiveTrap(e)&&xi.pauseTrap(e);var n=e.indexOf(t);n===-1||e.splice(n,1),e.push(t)},deactivateTrap:function(e,t){var n=e.indexOf(t);n!==-1&&e.splice(n,1),xi.unpauseTrap(e)},pauseTrap:function(e){xi.getActiveTrap(e)?._setPausedState(!0)},unpauseTrap:function(e){var t=xi.getActiveTrap(e);t&&!t._isManuallyPaused()&&t._setPausedState(!1)}},Si=function(e){return e.tagName&&e.tagName.toLowerCase()===`input`&&typeof e.select==`function`},Ci=function(e){return e?.key===`Escape`||e?.key===`Esc`||e?.keyCode===27},wi=function(e){return e?.key===`Tab`||e?.keyCode===9},Ti=function(e){return wi(e)&&!e.shiftKey},Ei=function(e){return wi(e)&&e.shiftKey},Di=function(e){return setTimeout(e,0)},Oi=function(e){for(var t=arguments.length,n=Array(t>1?t-1:0),r=1;r<t;r++)n[r-1]=arguments[r];return typeof e==`function`?e.apply(void 0,n):e},ki=function(e){return e.target.shadowRoot&&typeof e.composedPath==`function`?e.composedPath()[0]:e.target},Ai=[],ji=function(e,t){var n=t?.document||document,r=t?.trapStack||Ai,i=gi({returnFocusOnDeactivate:!0,escapeDeactivates:!0,delayInitialFocus:!0,isolateSubtrees:!1,isKeyForward:Ti,isKeyBackward:Ei},t),a={containers:[],containerGroups:[],tabbableGroups:[],adjacentElements:new Set,alreadySilent:new Set,nodeFocusedBeforeActivation:null,mostRecentlyFocusedNode:null,active:!1,paused:!1,manuallyPaused:!1,delayInitialFocusTimer:void 0,recentNavEvent:void 0},o,s=function(e,t,n){return e&&e[t]!==void 0?e[t]:i[n||t]},c=function(e,t){var n=typeof t?.composedPath==`function`?t.composedPath():void 0;return a.containerGroups.findIndex(function(t){var r=t.container,i=t.tabbableNodes;return r.contains(e)||n?.includes(r)||i.find(function(t){return t===e})})},l=function(e){var t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{},r=t.hasFallback,a=r===void 0?!1:r,o=t.params,s=o===void 0?[]:o,c=i[e];if(typeof c==`function`&&(c=c.apply(void 0,_i(s))),c===!0&&(c=void 0),!c){if(c===void 0||c===!1)return c;throw Error(`\\`${e}\\` was specified but was not a node, or did not return a node`)}var l=c;if(typeof c==`string`){try{l=n.querySelector(c)}catch(t){throw Error(`\\`${e}\\` appears to be an invalid selector; error=\"${t.message}\"`)}if(!l&&!a)throw Error(`\\`${e}\\` as selector refers to no known node`)}return l},u=function(){var e=l(`initialFocus`,{hasFallback:!0});if(e===!1)return!1;if(e===void 0||e&&!ri(e,i.tabbableOptions))if(c(n.activeElement)>=0)e=n.activeElement;else{var t=a.tabbableGroups[0];e=t&&t.firstTabbableNode||l(`fallbackFocus`)}else e===null&&(e=l(`fallbackFocus`));if(!e)throw Error(`Your focus-trap needs to have at least one focusable element`);return e},d=function(){if(a.containerGroups=a.containers.map(function(e){var t=$r(e,i.tabbableOptions),n=ei(e,i.tabbableOptions),r=t.length>0?t[0]:void 0,a=t.length>0?t[t.length-1]:void 0,o=n.find(function(e){return ti(e)}),s=n.slice().reverse().find(function(e){return ti(e)});return{container:e,tabbableNodes:t,focusableNodes:n,posTabIndexesFound:!!t.find(function(e){return Fr(e)>0}),firstTabbableNode:r,lastTabbableNode:a,firstDomTabbableNode:o,lastDomTabbableNode:s,nextTabbableNode:function(e){var r=arguments.length>1&&arguments[1]!==void 0?arguments[1]:!0,i=t.indexOf(e);return i<0?r?n.slice(n.indexOf(e)+1).find(function(e){return ti(e)}):n.slice(0,n.indexOf(e)).reverse().find(function(e){return ti(e)}):t[i+(r?1:-1)]}}}),a.tabbableGroups=a.containerGroups.filter(function(e){return e.tabbableNodes.length>0}),a.tabbableGroups.length<=0&&!l(`fallbackFocus`))throw Error(`Your focus-trap must have at least one container with at least one tabbable node in it at all times`);if(a.containerGroups.find(function(e){return e.posTabIndexesFound})&&a.containerGroups.length>1)throw Error(`At least one node with a positive tabindex was found in one of your focus-trap's multiple containers. Positive tabindexes are only supported in single-container focus-traps.`)},f=function(e){var t=e.activeElement;if(t)return t.shadowRoot&&t.shadowRoot.activeElement!==null?f(t.shadowRoot):t},p=function(e){if(e!==!1&&e!==f(document)){if(!e||!e.focus){p(u());return}e.focus({preventScroll:!!i.preventScroll}),a.mostRecentlyFocusedNode=e,Si(e)&&e.select()}},m=function(e){var t=l(`setReturnFocus`,{params:[e]});return t||(t===!1?!1:e)},h=function(e){var t=e.target,n=e.event,r=e.isBackward,o=r===void 0?!1:r;t||=ki(n),d();var s=null;if(a.tabbableGroups.length>0){var u=c(t,n),f=u>=0?a.containerGroups[u]:void 0;if(u<0)s=o?a.tabbableGroups[a.tabbableGroups.length-1].lastTabbableNode:a.tabbableGroups[0].firstTabbableNode;else if(o){var p=a.tabbableGroups.findIndex(function(e){var n=e.firstTabbableNode;return t===n});if(p<0&&(f.container===t||ri(t,i.tabbableOptions)&&!ti(t,i.tabbableOptions)&&!f.nextTabbableNode(t,!1))&&(p=u),p>=0){var m=p===0?a.tabbableGroups.length-1:p-1,h=a.tabbableGroups[m];s=Fr(t)>=0?h.lastTabbableNode:h.lastDomTabbableNode}else wi(n)||(s=f.nextTabbableNode(t,!1))}else{var g=a.tabbableGroups.findIndex(function(e){var n=e.lastTabbableNode;return t===n});if(g<0&&(f.container===t||ri(t,i.tabbableOptions)&&!ti(t,i.tabbableOptions)&&!f.nextTabbableNode(t))&&(g=u),g>=0){var _=g===a.tabbableGroups.length-1?0:g+1,v=a.tabbableGroups[_];s=Fr(t)>=0?v.firstTabbableNode:v.firstDomTabbableNode}else wi(n)||(s=f.nextTabbableNode(t))}}else s=l(`fallbackFocus`);return s},g=function(e){if(!(c(ki(e),e)>=0)){if(Oi(i.clickOutsideDeactivates,e)){o.deactivate({returnFocus:i.returnFocusOnDeactivate});return}Oi(i.allowOutsideClick,e)||e.preventDefault()}},_=function(e){var t=ki(e),n=c(t,e)>=0;if(n||t instanceof Document)n&&(a.mostRecentlyFocusedNode=t);else{e.stopImmediatePropagation();var r,o=!0;if(a.mostRecentlyFocusedNode)if(Fr(a.mostRecentlyFocusedNode)>0){var s=c(a.mostRecentlyFocusedNode),l=a.containerGroups[s].tabbableNodes;if(l.length>0){var d=l.findIndex(function(e){return e===a.mostRecentlyFocusedNode});d>=0&&(i.isKeyForward(a.recentNavEvent)?d+1<l.length&&(r=l[d+1],o=!1):d-1>=0&&(r=l[d-1],o=!1))}}else a.containerGroups.some(function(e){return e.tabbableNodes.some(function(e){return Fr(e)>0})})||(o=!1);else o=!1;o&&(r=h({target:a.mostRecentlyFocusedNode,isBackward:i.isKeyBackward(a.recentNavEvent)})),p(r||a.mostRecentlyFocusedNode||u())}a.recentNavEvent=void 0},v=function(e){var t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:!1;a.recentNavEvent=e;var n=h({event:e,isBackward:t});n&&(wi(e)&&e.preventDefault(),p(n))},y=function(e){(i.isKeyForward(e)||i.isKeyBackward(e))&&v(e,i.isKeyBackward(e))},b=function(e){Ci(e)&&Oi(i.escapeDeactivates,e)!==!1&&(e.preventDefault(),o.deactivate())},x=function(e){c(ki(e),e)>=0||Oi(i.clickOutsideDeactivates,e)||Oi(i.allowOutsideClick,e)||(e.preventDefault(),e.stopImmediatePropagation())},ee=function(){if(a.active)return xi.activateTrap(r,o),a.delayInitialFocusTimer=i.delayInitialFocus?Di(function(){p(u())}):p(u()),n.addEventListener(`focusin`,_,!0),n.addEventListener(`mousedown`,g,{capture:!0,passive:!1}),n.addEventListener(`touchstart`,g,{capture:!0,passive:!1}),n.addEventListener(`click`,x,{capture:!0,passive:!1}),n.addEventListener(`keydown`,y,{capture:!0,passive:!1}),n.addEventListener(`keydown`,b),o},S=function(e){a.active&&!a.paused&&o._setSubtreeIsolation(!1),a.adjacentElements.clear(),a.alreadySilent.clear();var t=new Set,n=new Set,r=di(e),i;try{for(r.s();!(i=r.n()).done;){var s=i.value;t.add(s);for(var c=typeof ShadowRoot<`u`&&s.getRootNode()instanceof ShadowRoot,l=s;l;){t.add(l);var u=l.parentElement,d=[];u?d=u.children:!u&&c&&(d=l.getRootNode().children,u=l.getRootNode().host,c=typeof ShadowRoot<`u`&&u.getRootNode()instanceof ShadowRoot);var f=di(d),p;try{for(f.s();!(p=f.n()).done;){var m=p.value;n.add(m)}}catch(e){f.e(e)}finally{f.f()}l=u}}}catch(e){r.e(e)}finally{r.f()}t.forEach(function(e){n.delete(e)}),a.adjacentElements=n},C=function(){if(a.active)return n.removeEventListener(`focusin`,_,!0),n.removeEventListener(`mousedown`,g,!0),n.removeEventListener(`touchstart`,g,!0),n.removeEventListener(`click`,x,!0),n.removeEventListener(`keydown`,y,!0),n.removeEventListener(`keydown`,b),o},w=typeof window<`u`&&`MutationObserver`in window?new MutationObserver(function(e){e.some(function(e){return Array.from(e.removedNodes).some(function(e){return e===a.mostRecentlyFocusedNode})})&&p(u())}):void 0,T=function(){w&&(w.disconnect(),a.active&&!a.paused&&a.containers.map(function(e){w.observe(e,{subtree:!0,childList:!0})}))};return o={get active(){return a.active},get paused(){return a.paused},activate:function(e){if(a.active)return this;var t=s(e,`onActivate`),c=s(e,`onPostActivate`),l=s(e,`checkCanFocusTrap`),u=xi.getActiveTrap(r),p=!1;if(u&&!u.paused){var m;(m=u._setSubtreeIsolation)==null||m.call(u,!1),p=!0}try{l||d(),a.active=!0,a.paused=!1,a.nodeFocusedBeforeActivation=f(n),t?.();var h=function(){l&&d(),ee(),T(),i.isolateSubtrees&&o._setSubtreeIsolation(!0),c?.()};if(l)return l(a.containers.concat()).then(h,h),this;h()}catch(e){if(u===xi.getActiveTrap(r)&&p){var g;(g=u._setSubtreeIsolation)==null||g.call(u,!0)}throw e}return this},deactivate:function(e){if(!a.active)return this;var t=gi({onDeactivate:i.onDeactivate,onPostDeactivate:i.onPostDeactivate,checkCanReturnFocus:i.checkCanReturnFocus},e);clearTimeout(a.delayInitialFocusTimer),a.delayInitialFocusTimer=void 0,a.paused||o._setSubtreeIsolation(!1),a.alreadySilent.clear(),C(),a.active=!1,a.paused=!1,T(),xi.deactivateTrap(r,o);var n=s(t,`onDeactivate`),c=s(t,`onPostDeactivate`),l=s(t,`checkCanReturnFocus`),u=s(t,`returnFocus`,`returnFocusOnDeactivate`);n?.();var d=function(){Di(function(){u&&p(m(a.nodeFocusedBeforeActivation)),c?.()})};return u&&l?(l(m(a.nodeFocusedBeforeActivation)).then(d,d),this):(d(),this)},pause:function(e){return a.active?(a.manuallyPaused=!0,this._setPausedState(!0,e)):this},unpause:function(e){return!a.active||(a.manuallyPaused=!1,r[r.length-1]!==this)?this:this._setPausedState(!1,e)},updateContainerElements:function(e){return a.containers=[].concat(e).filter(Boolean).map(function(e){return typeof e==`string`?n.querySelector(e):e}),i.isolateSubtrees&&S(a.containers),a.active&&(d(),i.isolateSubtrees&&!a.paused&&o._setSubtreeIsolation(!0)),T(),this}},Object.defineProperties(o,{_isManuallyPaused:{value:function(){return a.manuallyPaused}},_setPausedState:{value:function(e,t){if(a.paused===e)return this;if(a.paused=e,e){var n=s(t,`onPause`),r=s(t,`onPostPause`);n?.(),C(),T(),o._setSubtreeIsolation(!1),r?.()}else{var i=s(t,`onUnpause`),c=s(t,`onPostUnpause`);i?.(),o._setSubtreeIsolation(!0),d(),ee(),T(),c?.()}return this}},_setSubtreeIsolation:{value:function(e){i.isolateSubtrees&&a.adjacentElements.forEach(function(t){if(e)switch(i.isolateSubtrees){case`aria-hidden`:(t.ariaHidden===`true`||t.getAttribute(`aria-hidden`)?.toLowerCase()===`true`)&&a.alreadySilent.add(t),t.setAttribute(`aria-hidden`,`true`);break;default:(t.inert||t.hasAttribute(`inert`))&&a.alreadySilent.add(t),t.setAttribute(`inert`,!0);break}else if(!a.alreadySilent.has(t))switch(i.isolateSubtrees){case`aria-hidden`:t.removeAttribute(`aria-hidden`);break;default:t.removeAttribute(`inert`);break}})}}}),o.updateContainerElements(e),o},Mi=Object.defineProperty,Ni=(e,t,n)=>t in e?Mi(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,Pi=(e,t,n)=>(Ni(e,typeof t==`symbol`?t:t+``,n),n);let Fi=class e extends HTMLElement{constructor(){super(),Pi(this,`focusTrap`),Pi(this,`connected`,!1);let t=document.createElement(`template`);t.innerHTML=`\n            <div part=\"backdrop\" style=\"position: fixed; top: 0; left: 0; right: 0; bottom: 0\"></div>\n            <div part=\"content\" style=\"z-index: 1\"><slot></slot></div>\n        `;let n=this.attachShadow({mode:`open`});n.appendChild(t.content.cloneNode(!0)),this.backdrop.addEventListener(`click`,()=>{var t;this.hasAttribute(`static`)?(t=e.attention)==null||t.call(e,n.children[1]):this.close()}),this.focusTrap=ji(this,{escapeDeactivates:!1,allowOutsideClick:!0,preventScroll:!0,initialFocus:()=>this.querySelector(`[autofocus]`)||void 0})}static get observedAttributes(){return[`open`]}connectedCallback(){var e,t,n,r,i,a;this.connected=!0,(e=this.content)!=null&&e.hasAttribute(`role`)||(t=this.content)==null||t.setAttribute(`role`,`dialog`),(n=this.content)!=null&&n.hasAttribute(`aria-modal`)||(r=this.content)==null||r.setAttribute(`aria-modal`,`true`),(i=this.content)!=null&&i.hasAttribute(`tabindex`)||(a=this.content)==null||a.setAttribute(`tabindex`,`-1`),this.addEventListener(`keydown`,e=>{e.key===`Escape`&&this.open&&(e.preventDefault(),e.stopPropagation(),this.close())}),this.open&&this.wasOpened()}disconnectedCallback(){this.connected=!1}get open(){return this.hasAttribute(`open`)}set open(e){e?this.setAttribute(`open`,``):this.removeAttribute(`open`)}close(){if(!this.open)return;let e=new Event(`beforeclose`,{cancelable:!0});this.dispatchEvent(e)&&(this.open=!1)}attributeChangedCallback(e,t,n){e!==`open`||!this.connected||(n===null?this.wasClosed():this.wasOpened())}async wasOpened(){document.documentElement.style.overflow=`hidden`,this.hidden=!1,hr(this),requestAnimationFrame(()=>this.focusTrap?.activate()),this.dispatchEvent(new Event(`open`))}wasClosed(){var e;document.documentElement.style.overflow=``,(e=this.focusTrap)==null||e.deactivate(),gr(this).then(()=>this.hidden=!0),this.dispatchEvent(new Event(`close`))}get backdrop(){return this.shadowRoot?.firstElementChild}get content(){return this.firstElementChild}};Pi(Fi,`attention`,e=>e.animate([{transform:`scale(1)`},{transform:`scale(1.1)`},{transform:`scale(1)`}],300));let Ii=Fi,Li=Math.min,z=Math.max,Ri=Math.round,zi=Math.floor,B=e=>({x:e,y:e}),Bi={left:`right`,right:`left`,bottom:`top`,top:`bottom`},Vi={start:`end`,end:`start`};function Hi(e,t,n){return z(e,Li(t,n))}function Ui(e,t){return typeof e==`function`?e(t):e}function Wi(e){return e.split(`-`)[0]}function Gi(e){return e.split(`-`)[1]}function Ki(e){return e===`x`?`y`:`x`}function qi(e){return e===`y`?`height`:`width`}let Ji=new Set([`top`,`bottom`]);function Yi(e){return Ji.has(Wi(e))?`y`:`x`}function Xi(e){return Ki(Yi(e))}function Zi(e,t,n){n===void 0&&(n=!1);let r=Gi(e),i=Xi(e),a=qi(i),o=i===`x`?r===(n?`end`:`start`)?`right`:`left`:r===`start`?`bottom`:`top`;return t.reference[a]>t.floating[a]&&(o=oa(o)),[o,oa(o)]}function Qi(e){let t=oa(e);return[$i(e),t,$i(t)]}function $i(e){return e.replace(/start|end/g,e=>Vi[e])}let ea=[`left`,`right`],ta=[`right`,`left`],na=[`top`,`bottom`],ra=[`bottom`,`top`];function ia(e,t,n){switch(e){case`top`:case`bottom`:return n?t?ta:ea:t?ea:ta;case`left`:case`right`:return t?na:ra;default:return[]}}function aa(e,t,n,r){let i=Gi(e),a=ia(Wi(e),n===`start`,r);return i&&(a=a.map(e=>e+`-`+i),t&&(a=a.concat(a.map($i)))),a}function oa(e){return e.replace(/left|right|bottom|top/g,e=>Bi[e])}function sa(e){return{top:0,right:0,bottom:0,left:0,...e}}function ca(e){return typeof e==`number`?{top:e,right:e,bottom:e,left:e}:sa(e)}function la(e){let{x:t,y:n,width:r,height:i}=e;return{width:r,height:i,top:n,left:t,right:t+r,bottom:n+i,x:t,y:n}}function ua(e,t,n){let{reference:r,floating:i}=e,a=Yi(t),o=Xi(t),s=qi(o),c=Wi(t),l=a===`y`,u=r.x+r.width/2-i.width/2,d=r.y+r.height/2-i.height/2,f=r[s]/2-i[s]/2,p;switch(c){case`top`:p={x:u,y:r.y-i.height};break;case`bottom`:p={x:u,y:r.y+r.height};break;case`right`:p={x:r.x+r.width,y:d};break;case`left`:p={x:r.x-i.width,y:d};break;default:p={x:r.x,y:r.y}}switch(Gi(t)){case`start`:p[o]-=f*(n&&l?-1:1);break;case`end`:p[o]+=f*(n&&l?-1:1);break}return p}let da=async(e,t,n)=>{let{placement:r=`bottom`,strategy:i=`absolute`,middleware:a=[],platform:o}=n,s=a.filter(Boolean),c=await(o.isRTL==null?void 0:o.isRTL(t)),l=await o.getElementRects({reference:e,floating:t,strategy:i}),{x:u,y:d}=ua(l,r,c),f=r,p={},m=0;for(let n=0;n<s.length;n++){let{name:a,fn:h}=s[n],{x:g,y:_,data:v,reset:y}=await h({x:u,y:d,initialPlacement:r,placement:f,strategy:i,middlewareData:p,rects:l,platform:o,elements:{reference:e,floating:t}});u=g??u,d=_??d,p={...p,[a]:{...p[a],...v}},y&&m<=50&&(m++,typeof y==`object`&&(y.placement&&(f=y.placement),y.rects&&(l=y.rects===!0?await o.getElementRects({reference:e,floating:t,strategy:i}):y.rects),{x:u,y:d}=ua(l,f,c)),n=-1)}return{x:u,y:d,placement:f,strategy:i,middlewareData:p}};async function fa(e,t){t===void 0&&(t={});let{x:n,y:r,platform:i,rects:a,elements:o,strategy:s}=e,{boundary:c=`clippingAncestors`,rootBoundary:l=`viewport`,elementContext:u=`floating`,altBoundary:d=!1,padding:f=0}=Ui(t,e),p=ca(f),m=o[d?u===`floating`?`reference`:`floating`:u],h=la(await i.getClippingRect({element:await(i.isElement==null?void 0:i.isElement(m))??!0?m:m.contextElement||await(i.getDocumentElement==null?void 0:i.getDocumentElement(o.floating)),boundary:c,rootBoundary:l,strategy:s})),g=u===`floating`?{x:n,y:r,width:a.floating.width,height:a.floating.height}:a.reference,_=await(i.getOffsetParent==null?void 0:i.getOffsetParent(o.floating)),v=await(i.isElement==null?void 0:i.isElement(_))&&await(i.getScale==null?void 0:i.getScale(_))||{x:1,y:1},y=la(i.convertOffsetParentRelativeRectToViewportRelativeRect?await i.convertOffsetParentRelativeRectToViewportRelativeRect({elements:o,rect:g,offsetParent:_,strategy:s}):g);return{top:(h.top-y.top+p.top)/v.y,bottom:(y.bottom-h.bottom+p.bottom)/v.y,left:(h.left-y.left+p.left)/v.x,right:(y.right-h.right+p.right)/v.x}}let pa=function(e){return e===void 0&&(e={}),{name:`flip`,options:e,async fn(t){var n;let{placement:r,middlewareData:i,rects:a,initialPlacement:o,platform:s,elements:c}=t,{mainAxis:l=!0,crossAxis:u=!0,fallbackPlacements:d,fallbackStrategy:f=`bestFit`,fallbackAxisSideDirection:p=`none`,flipAlignment:m=!0,...h}=Ui(e,t);if((n=i.arrow)!=null&&n.alignmentOffset)return{};let g=Wi(r),_=Yi(o),v=Wi(o)===o,y=await(s.isRTL==null?void 0:s.isRTL(c.floating)),b=d||(v||!m?[oa(o)]:Qi(o)),x=p!==`none`;!d&&x&&b.push(...aa(o,m,p,y));let ee=[o,...b],S=await fa(t,h),C=[],w=i.flip?.overflows||[];if(l&&C.push(S[g]),u){let e=Zi(r,a,y);C.push(S[e[0]],S[e[1]])}if(w=[...w,{placement:r,overflows:C}],!C.every(e=>e<=0)){let e=(i.flip?.index||0)+1,t=ee[e];if(t&&(!(u===`alignment`&&_!==Yi(t))||w.every(e=>Yi(e.placement)===_?e.overflows[0]>0:!0)))return{data:{index:e,overflows:w},reset:{placement:t}};let n=w.filter(e=>e.overflows[0]<=0).sort((e,t)=>e.overflows[1]-t.overflows[1])[0]?.placement;if(!n)switch(f){case`bestFit`:{let e=w.filter(e=>{if(x){let t=Yi(e.placement);return t===_||t===`y`}return!0}).map(e=>[e.placement,e.overflows.filter(e=>e>0).reduce((e,t)=>e+t,0)]).sort((e,t)=>e[1]-t[1])[0]?.[0];e&&(n=e);break}case`initialPlacement`:n=o;break}if(r!==n)return{reset:{placement:n}}}return{}}}},ma=new Set([`left`,`top`]);async function ha(e,t){let{placement:n,platform:r,elements:i}=e,a=await(r.isRTL==null?void 0:r.isRTL(i.floating)),o=Wi(n),s=Gi(n),c=Yi(n)===`y`,l=ma.has(o)?-1:1,u=a&&c?-1:1,d=Ui(t,e),{mainAxis:f,crossAxis:p,alignmentAxis:m}=typeof d==`number`?{mainAxis:d,crossAxis:0,alignmentAxis:null}:{mainAxis:d.mainAxis||0,crossAxis:d.crossAxis||0,alignmentAxis:d.alignmentAxis};return s&&typeof m==`number`&&(p=s===`end`?m*-1:m),c?{x:p*u,y:f*l}:{x:f*l,y:p*u}}let ga=function(e){return e===void 0&&(e=0),{name:`offset`,options:e,async fn(t){var n;let{x:r,y:i,placement:a,middlewareData:o}=t,s=await ha(t,e);return a===o.offset?.placement&&(n=o.arrow)!=null&&n.alignmentOffset?{}:{x:r+s.x,y:i+s.y,data:{...s,placement:a}}}}},_a=function(e){return e===void 0&&(e={}),{name:`shift`,options:e,async fn(t){let{x:n,y:r,placement:i}=t,{mainAxis:a=!0,crossAxis:o=!1,limiter:s={fn:e=>{let{x:t,y:n}=e;return{x:t,y:n}}},...c}=Ui(e,t),l={x:n,y:r},u=await fa(t,c),d=Yi(Wi(i)),f=Ki(d),p=l[f],m=l[d];if(a){let e=f===`y`?`top`:`left`,t=f===`y`?`bottom`:`right`,n=p+u[e],r=p-u[t];p=Hi(n,p,r)}if(o){let e=d===`y`?`top`:`left`,t=d===`y`?`bottom`:`right`,n=m+u[e],r=m-u[t];m=Hi(n,m,r)}let h=s.fn({...t,[f]:p,[d]:m});return{...h,data:{x:h.x-n,y:h.y-r,enabled:{[f]:a,[d]:o}}}}}},va=function(e){return e===void 0&&(e={}),{name:`size`,options:e,async fn(t){var n,r;let{placement:i,rects:a,platform:o,elements:s}=t,{apply:c=()=>{},...l}=Ui(e,t),u=await fa(t,l),d=Wi(i),f=Gi(i),p=Yi(i)===`y`,{width:m,height:h}=a.floating,g,_;d===`top`||d===`bottom`?(g=d,_=f===(await(o.isRTL==null?void 0:o.isRTL(s.floating))?`start`:`end`)?`left`:`right`):(_=d,g=f===`end`?`top`:`bottom`);let v=h-u.top-u.bottom,y=m-u.left-u.right,b=Li(h-u[g],v),x=Li(m-u[_],y),ee=!t.middlewareData.shift,S=b,C=x;if((n=t.middlewareData.shift)!=null&&n.enabled.x&&(C=y),(r=t.middlewareData.shift)!=null&&r.enabled.y&&(S=v),ee&&!f){let e=z(u.left,0),t=z(u.right,0),n=z(u.top,0),r=z(u.bottom,0);p?C=m-2*(e!==0||t!==0?e+t:z(u.left,u.right)):S=h-2*(n!==0||r!==0?n+r:z(u.top,u.bottom))}await c({...t,availableWidth:C,availableHeight:S});let w=await o.getDimensions(s.floating);return m!==w.width||h!==w.height?{reset:{rects:!0}}:{}}}};function ya(){return typeof window<`u`}function ba(e){return xa(e)?(e.nodeName||``).toLowerCase():`#document`}function V(e){var t;return(e==null||(t=e.ownerDocument)==null?void 0:t.defaultView)||window}function H(e){return((xa(e)?e.ownerDocument:e.document)||window.document)?.documentElement}function xa(e){return ya()?e instanceof Node||e instanceof V(e).Node:!1}function U(e){return ya()?e instanceof Element||e instanceof V(e).Element:!1}function W(e){return ya()?e instanceof HTMLElement||e instanceof V(e).HTMLElement:!1}function Sa(e){return!ya()||typeof ShadowRoot>`u`?!1:e instanceof ShadowRoot||e instanceof V(e).ShadowRoot}let Ca=new Set([`inline`,`contents`]);function wa(e){let{overflow:t,overflowX:n,overflowY:r,display:i}=G(e);return/auto|scroll|overlay|hidden|clip/.test(t+r+n)&&!Ca.has(i)}let Ta=new Set([`table`,`td`,`th`]);function Ea(e){return Ta.has(ba(e))}let Da=[`:popover-open`,`:modal`];function Oa(e){return Da.some(t=>{try{return e.matches(t)}catch{return!1}})}let ka=[`transform`,`translate`,`scale`,`rotate`,`perspective`],Aa=[`transform`,`translate`,`scale`,`rotate`,`perspective`,`filter`],ja=[`paint`,`layout`,`strict`,`content`];function Ma(e){let t=Pa(),n=U(e)?G(e):e;return ka.some(e=>n[e]?n[e]!==`none`:!1)||(n.containerType?n.containerType!==`normal`:!1)||!t&&(n.backdropFilter?n.backdropFilter!==`none`:!1)||!t&&(n.filter?n.filter!==`none`:!1)||Aa.some(e=>(n.willChange||``).includes(e))||ja.some(e=>(n.contain||``).includes(e))}function Na(e){let t=Ra(e);for(;W(t)&&!Ia(t);){if(Ma(t))return t;if(Oa(t))return null;t=Ra(t)}return null}function Pa(){return typeof CSS>`u`||!CSS.supports?!1:CSS.supports(`-webkit-backdrop-filter`,`none`)}let Fa=new Set([`html`,`body`,`#document`]);function Ia(e){return Fa.has(ba(e))}function G(e){return V(e).getComputedStyle(e)}function La(e){return U(e)?{scrollLeft:e.scrollLeft,scrollTop:e.scrollTop}:{scrollLeft:e.scrollX,scrollTop:e.scrollY}}function Ra(e){if(ba(e)===`html`)return e;let t=e.assignedSlot||e.parentNode||Sa(e)&&e.host||H(e);return Sa(t)?t.host:t}function za(e){let t=Ra(e);return Ia(t)?e.ownerDocument?e.ownerDocument.body:e.body:W(t)&&wa(t)?t:za(t)}function Ba(e,t,n){t===void 0&&(t=[]),n===void 0&&(n=!0);let r=za(e),i=r===e.ownerDocument?.body,a=V(r);if(i){let e=Va(a);return t.concat(a,a.visualViewport||[],wa(r)?r:[],e&&n?Ba(e):[])}return t.concat(r,Ba(r,[],n))}function Va(e){return e.parent&&Object.getPrototypeOf(e.parent)?e.frameElement:null}function Ha(e){let t=G(e),n=parseFloat(t.width)||0,r=parseFloat(t.height)||0,i=W(e),a=i?e.offsetWidth:n,o=i?e.offsetHeight:r,s=Ri(n)!==a||Ri(r)!==o;return s&&(n=a,r=o),{width:n,height:r,$:s}}function Ua(e){return U(e)?e:e.contextElement}function Wa(e){let t=Ua(e);if(!W(t))return B(1);let n=t.getBoundingClientRect(),{width:r,height:i,$:a}=Ha(t),o=(a?Ri(n.width):n.width)/r,s=(a?Ri(n.height):n.height)/i;return(!o||!Number.isFinite(o))&&(o=1),(!s||!Number.isFinite(s))&&(s=1),{x:o,y:s}}let Ga=B(0);function Ka(e){let t=V(e);return!Pa()||!t.visualViewport?Ga:{x:t.visualViewport.offsetLeft,y:t.visualViewport.offsetTop}}function qa(e,t,n){return t===void 0&&(t=!1),!n||t&&n!==V(e)?!1:t}function Ja(e,t,n,r){t===void 0&&(t=!1),n===void 0&&(n=!1);let i=e.getBoundingClientRect(),a=Ua(e),o=B(1);t&&(r?U(r)&&(o=Wa(r)):o=Wa(e));let s=qa(a,n,r)?Ka(a):B(0),c=(i.left+s.x)/o.x,l=(i.top+s.y)/o.y,u=i.width/o.x,d=i.height/o.y;if(a){let e=V(a),t=r&&U(r)?V(r):r,n=e,i=Va(n);for(;i&&r&&t!==n;){let e=Wa(i),t=i.getBoundingClientRect(),r=G(i),a=t.left+(i.clientLeft+parseFloat(r.paddingLeft))*e.x,o=t.top+(i.clientTop+parseFloat(r.paddingTop))*e.y;c*=e.x,l*=e.y,u*=e.x,d*=e.y,c+=a,l+=o,n=V(i),i=Va(n)}}return la({width:u,height:d,x:c,y:l})}function Ya(e,t){let n=La(e).scrollLeft;return t?t.left+n:Ja(H(e)).left+n}function Xa(e,t){let n=e.getBoundingClientRect();return{x:n.left+t.scrollLeft-Ya(e,n),y:n.top+t.scrollTop}}function Za(e){let{elements:t,rect:n,offsetParent:r,strategy:i}=e,a=i===`fixed`,o=H(r),s=t?Oa(t.floating):!1;if(r===o||s&&a)return n;let c={scrollLeft:0,scrollTop:0},l=B(1),u=B(0),d=W(r);if((d||!d&&!a)&&((ba(r)!==`body`||wa(o))&&(c=La(r)),W(r))){let e=Ja(r);l=Wa(r),u.x=e.x+r.clientLeft,u.y=e.y+r.clientTop}let f=o&&!d&&!a?Xa(o,c):B(0);return{width:n.width*l.x,height:n.height*l.y,x:n.x*l.x-c.scrollLeft*l.x+u.x+f.x,y:n.y*l.y-c.scrollTop*l.y+u.y+f.y}}function Qa(e){return Array.from(e.getClientRects())}function $a(e){let t=H(e),n=La(e),r=e.ownerDocument.body,i=z(t.scrollWidth,t.clientWidth,r.scrollWidth,r.clientWidth),a=z(t.scrollHeight,t.clientHeight,r.scrollHeight,r.clientHeight),o=-n.scrollLeft+Ya(e),s=-n.scrollTop;return G(r).direction===`rtl`&&(o+=z(t.clientWidth,r.clientWidth)-i),{width:i,height:a,x:o,y:s}}function eo(e,t){let n=V(e),r=H(e),i=n.visualViewport,a=r.clientWidth,o=r.clientHeight,s=0,c=0;if(i){a=i.width,o=i.height;let e=Pa();(!e||e&&t===`fixed`)&&(s=i.offsetLeft,c=i.offsetTop)}let l=Ya(r);if(l<=0){let e=r.ownerDocument,t=e.body,n=getComputedStyle(t),i=e.compatMode===`CSS1Compat`&&parseFloat(n.marginLeft)+parseFloat(n.marginRight)||0,o=Math.abs(r.clientWidth-t.clientWidth-i);o<=25&&(a-=o)}else l<=25&&(a+=l);return{width:a,height:o,x:s,y:c}}let to=new Set([`absolute`,`fixed`]);function no(e,t){let n=Ja(e,!0,t===`fixed`),r=n.top+e.clientTop,i=n.left+e.clientLeft,a=W(e)?Wa(e):B(1);return{width:e.clientWidth*a.x,height:e.clientHeight*a.y,x:i*a.x,y:r*a.y}}function ro(e,t,n){let r;if(t===`viewport`)r=eo(e,n);else if(t===`document`)r=$a(H(e));else if(U(t))r=no(t,n);else{let n=Ka(e);r={x:t.x-n.x,y:t.y-n.y,width:t.width,height:t.height}}return la(r)}function io(e,t){let n=Ra(e);return n===t||!U(n)||Ia(n)?!1:G(n).position===`fixed`||io(n,t)}function ao(e,t){let n=t.get(e);if(n)return n;let r=Ba(e,[],!1).filter(e=>U(e)&&ba(e)!==`body`),i=null,a=G(e).position===`fixed`,o=a?Ra(e):e;for(;U(o)&&!Ia(o);){let t=G(o),n=Ma(o);!n&&t.position===`fixed`&&(i=null),(a?!n&&!i:!n&&t.position===`static`&&i&&to.has(i.position)||wa(o)&&!n&&io(e,o))?r=r.filter(e=>e!==o):i=t,o=Ra(o)}return t.set(e,r),r}function oo(e){let{element:t,boundary:n,rootBoundary:r,strategy:i}=e,a=[...n===`clippingAncestors`?Oa(t)?[]:ao(t,this._c):[].concat(n),r],o=a[0],s=a.reduce((e,n)=>{let r=ro(t,n,i);return e.top=z(r.top,e.top),e.right=Li(r.right,e.right),e.bottom=Li(r.bottom,e.bottom),e.left=z(r.left,e.left),e},ro(t,o,i));return{width:s.right-s.left,height:s.bottom-s.top,x:s.left,y:s.top}}function so(e){let{width:t,height:n}=Ha(e);return{width:t,height:n}}function co(e,t,n){let r=W(t),i=H(t),a=n===`fixed`,o=Ja(e,!0,a,t),s={scrollLeft:0,scrollTop:0},c=B(0);function l(){c.x=Ya(i)}if(r||!r&&!a)if((ba(t)!==`body`||wa(i))&&(s=La(t)),r){let e=Ja(t,!0,a,t);c.x=e.x+t.clientLeft,c.y=e.y+t.clientTop}else i&&l();a&&!r&&i&&l();let u=i&&!r&&!a?Xa(i,s):B(0);return{x:o.left+s.scrollLeft-c.x-u.x,y:o.top+s.scrollTop-c.y-u.y,width:o.width,height:o.height}}function lo(e){return G(e).position===`static`}function uo(e,t){if(!W(e)||G(e).position===`fixed`)return null;if(t)return t(e);let n=e.offsetParent;return H(e)===n&&(n=n.ownerDocument.body),n}function fo(e,t){let n=V(e);if(Oa(e))return n;if(!W(e)){let t=Ra(e);for(;t&&!Ia(t);){if(U(t)&&!lo(t))return t;t=Ra(t)}return n}let r=uo(e,t);for(;r&&Ea(r)&&lo(r);)r=uo(r,t);return r&&Ia(r)&&lo(r)&&!Ma(r)?n:r||Na(e)||n}let po=async function(e){let t=this.getOffsetParent||fo,n=this.getDimensions,r=await n(e.floating);return{reference:co(e.reference,await t(e.floating),e.strategy),floating:{x:0,y:0,width:r.width,height:r.height}}};function mo(e){return G(e).direction===`rtl`}let ho={convertOffsetParentRelativeRectToViewportRelativeRect:Za,getDocumentElement:H,getClippingRect:oo,getOffsetParent:fo,getElementRects:po,getClientRects:Qa,getDimensions:so,getScale:Wa,isElement:U,isRTL:mo};function go(e,t){return e.x===t.x&&e.y===t.y&&e.width===t.width&&e.height===t.height}function _o(e,t){let n=null,r,i=H(e);function a(){var e;clearTimeout(r),(e=n)==null||e.disconnect(),n=null}function o(s,c){s===void 0&&(s=!1),c===void 0&&(c=1),a();let l=e.getBoundingClientRect(),{left:u,top:d,width:f,height:p}=l;if(s||t(),!f||!p)return;let m=zi(d),h=zi(i.clientWidth-(u+f)),g=zi(i.clientHeight-(d+p)),_=zi(u),v={rootMargin:-m+`px `+-h+`px `+-g+`px `+-_+`px`,threshold:z(0,Li(1,c))||1},y=!0;function b(t){let n=t[0].intersectionRatio;if(n!==c){if(!y)return o();n?o(!1,n):r=setTimeout(()=>{o(!1,1e-7)},1e3)}n===1&&!go(l,e.getBoundingClientRect())&&o(),y=!1}try{n=new IntersectionObserver(b,{...v,root:i.ownerDocument})}catch{n=new IntersectionObserver(b,v)}n.observe(e)}return o(!0),a}function vo(e,t,n,r){r===void 0&&(r={});let{ancestorScroll:i=!0,ancestorResize:a=!0,elementResize:o=typeof ResizeObserver==`function`,layoutShift:s=typeof IntersectionObserver==`function`,animationFrame:c=!1}=r,l=Ua(e),u=i||a?[...l?Ba(l):[],...Ba(t)]:[];u.forEach(e=>{i&&e.addEventListener(`scroll`,n,{passive:!0}),a&&e.addEventListener(`resize`,n)});let d=l&&s?_o(l,n):null,f=-1,p=null;o&&(p=new ResizeObserver(e=>{let[r]=e;r&&r.target===l&&p&&(p.unobserve(t),cancelAnimationFrame(f),f=requestAnimationFrame(()=>{var e;(e=p)==null||e.observe(t)})),n()}),l&&!c&&p.observe(l),p.observe(t));let m,h=c?Ja(e):null;c&&g();function g(){let t=Ja(e);h&&!go(h,t)&&n(),h=t,m=requestAnimationFrame(g)}return n(),()=>{var e;u.forEach(e=>{i&&e.removeEventListener(`scroll`,n),a&&e.removeEventListener(`resize`,n)}),d?.(),(e=p)==null||e.disconnect(),p=null,c&&cancelAnimationFrame(m)}}let yo=ga,bo=_a,xo=pa,So=va,Co=(e,t,n)=>{let r=new Map,i={platform:ho,...n},a={...i.platform,_c:r};return da(e,t,{...i,platform:a})};var wo=Object.defineProperty,To=(e,t,n)=>t in e?wo(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,Eo=(e,t,n)=>(To(e,typeof t==`symbol`?t:t+``,n),n),Do=class extends HTMLElement{constructor(){super(),Eo(this,`cleanup`),Eo(this,`onButtonClick`,e=>{!br(e)&&!this.disabled&&(this.open=!this.open,e.preventDefault())}),Eo(this,`onButtonKeyDown`,e=>{var t;e.key===`ArrowDown`&&!this.disabled&&(e.preventDefault(),this.open=!0,this.content&&((t=ei(this.content)[0])==null||t.focus()))}),Eo(this,`onKeyDown`,e=>{var t;e.key===`Escape`&&this.open&&(e.preventDefault(),e.stopPropagation(),this.open=!1,(t=this.button)==null||t.focus())}),Eo(this,`onFocusOut`,e=>{e.relatedTarget instanceof Node&&!this.contains(e.relatedTarget)&&(this.open=!1)}),Eo(this,`onContentClick`,e=>{var t;e.target instanceof Element&&e.target.closest(`[role=menuitem], [role=menuitemradio]`)&&(this.open=!1,(t=this.button)==null||t.focus())});let e=document.createElement(`template`);e.innerHTML=`\n            <div part=\"backdrop\" hidden style=\"position: fixed; top: 0; left: 0; right: 0; bottom: 0\"></div>\n            <slot></slot>\n        `,this.attachShadow({mode:`open`}).appendChild(e.content.cloneNode(!0)),this.backdrop.onclick=()=>this.open=!1}static get observedAttributes(){return[`open`]}connectedCallback(){var e,t,n,r;this.backdrop.hidden=!this.open,this.content&&(this.content.hidden=!this.open),(e=this.button)==null||e.setAttribute(`aria-expanded`,this.open?`true`:`false`),setTimeout(()=>{var e;this.content?.getAttribute(`role`)===`menu`&&((e=this.button)==null||e.setAttribute(`aria-haspopup`,`true`))}),(t=this.button)==null||t.addEventListener(`click`,this.onButtonClick),(n=this.button)==null||n.addEventListener(`keydown`,this.onButtonKeyDown),this.addEventListener(`keydown`,this.onKeyDown),this.addEventListener(`focusout`,this.onFocusOut),(r=this.content)==null||r.addEventListener(`click`,this.onContentClick)}disconnectedCallback(){var e,t,n,r,i;pr(this.backdrop),this.content&&pr(this.content),(e=this.button)==null||e.removeAttribute(`aria-expanded`),(t=this.button)==null||t.removeAttribute(`aria-haspopup`),(n=this.button)==null||n.removeEventListener(`click`,this.onButtonClick),(r=this.button)==null||r.removeEventListener(`keydown`,this.onButtonKeyDown),this.removeEventListener(`keydown`,this.onKeyDown),this.removeEventListener(`focusout`,this.onFocusOut),(i=this.content)==null||i.removeEventListener(`click`,this.onContentClick)}get open(){return this.hasAttribute(`open`)}set open(e){e?this.setAttribute(`open`,``):this.removeAttribute(`open`)}get disabled(){return this.hasAttribute(`disabled`)}attributeChangedCallback(e,t,n){e===`open`&&(n===null?this.wasClosed():this.wasOpened())}wasOpened(){var e,t;if(!((e=this.content)!=null&&e.hidden))return;this.content.hidden=!1,this.content.style.position=`fixed`,this.backdrop.hidden=!1,hr(this.content),hr(this.backdrop),(t=this.button)==null||t.setAttribute(`aria-expanded`,`true`),this.button&&(this.cleanup=vo(this.button,this.content,()=>{!this.button||!this.content||Co(this.button,this.content,{strategy:`fixed`,placement:this.getAttribute(`placement`)||`bottom`,middleware:[bo(),xo(),So({apply:({availableWidth:e,availableHeight:t,placement:n})=>{if(!this.content)return;this.content.dataset.placement=n,Object.assign(this.content.style,{maxWidth:``,maxHeight:``});let r=getComputedStyle(this.content);e-=parseInt(r.marginLeft)+parseInt(r.marginRight),(r.maxWidth===`none`||e<parseInt(r.maxWidth))&&(this.content.style.maxWidth=`${e}px`),t-=parseInt(r.marginTop)+parseInt(r.marginBottom),(r.maxHeight===`none`||t<parseInt(r.maxHeight))&&(this.content.style.maxHeight=`${t}px`)}})]}).then(({x:e,y:t})=>{this.content&&Object.assign(this.content.style,{left:`${e}px`,top:`${t}px`})})},{ancestorScroll:!1}));let n=this.content.querySelector(`[autofocus]`);n?n.focus():this.content.focus(),this.dispatchEvent(new Event(`open`))}wasClosed(){var e,t,n;(e=this.content)!=null&&e.hidden||((t=this.button)==null||t.setAttribute(`aria-expanded`,`false`),(n=this.cleanup)==null||n.call(this),gr(this.backdrop).then(()=>this.backdrop.hidden=!0),this.content&&gr(this.content).then(()=>this.content.hidden=!0),this.dispatchEvent(new Event(`close`)))}get backdrop(){return this.shadowRoot?.firstElementChild}get button(){return this.querySelector(`button, [role=button]`)}get content(){return this.children[1]}},Oo=Object.defineProperty,ko=(e,t,n)=>t in e?Oo(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,Ao=(e,t,n)=>(ko(e,typeof t==`symbol`?t:t+``,n),n),jo=class extends HTMLElement{constructor(){super(...arguments),Ao(this,`onInitialFocus`,()=>{this.removeAttribute(`tabindex`),this.focusControlAtIndex(0)}),Ao(this,`onKeyDown`,e=>{if(e.key!==`ArrowRight`&&e.key!==`ArrowLeft`&&e.key!==`Home`&&e.key!==`End`)return;let t=this.controls,n=this.controls.length,r=t.indexOf(e.target);if(r===-1)return;let i=0;e.key===`ArrowLeft`&&(i=r-1),e.key===`ArrowRight`&&(i=r+1),e.key===`End`&&(i=n-1),i<0&&(i=n-1),i>n-1&&(i=0),this.focusControlAtIndex(i),e.preventDefault()})}connectedCallback(){this.hasAttribute(`role`)||this.setAttribute(`role`,`toolbar`),this.setAttribute(`tabindex`,`0`),this.addEventListener(`focus`,this.onInitialFocus,{once:!0}),this.addEventListener(`keydown`,this.onKeyDown)}disconnectedCallback(){this.removeEventListener(`keydown`,this.onKeyDown)}focusControlAtIndex(e){this.controls.forEach((t,n)=>{n===e?(t.setAttribute(`tabindex`,`0`),t.focus()):t.setAttribute(`tabindex`,`-1`)})}get controls(){return ei(this)}},Mo=Object.defineProperty,No=(e,t,n)=>t in e?Mo(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,K=(e,t,n)=>(No(e,typeof t==`symbol`?t:t+``,n),n);let Po=class e extends HTMLElement{constructor(){super(...arguments),K(this,`parent`),K(this,`tooltip`),K(this,`timeout`),K(this,`disabledObserver`),K(this,`showing`,!1),K(this,`cleanup`),K(this,`prevInnerHTML`),K(this,`tabPressed`,!1),K(this,`onMouseEnter`,()=>{window.matchMedia(`(hover: hover) and (pointer: fine)`).matches&&this.afterDelay(this.show)}),K(this,`onFocus`,()=>{this.tabPressed&&this.show()}),K(this,`onMouseLeave`,this.afterDelay.bind(this,this.hide)),K(this,`onBlur`,this.hide.bind(this)),K(this,`onKeyDown`,e=>{e.key===`Tab`&&(this.tabPressed=!0),e.key===`Escape`&&this.hide()}),K(this,`onKeyUp`,e=>{e.key===`Tab`&&(this.tabPressed=!1)})}connectedCallback(){this.parent=this.parentNode,this.parent&&(this.parent.addEventListener(`mouseenter`,this.onMouseEnter),this.parent.addEventListener(`focus`,this.onFocus),this.parent.addEventListener(`mouseleave`,this.onMouseLeave),this.parent.addEventListener(`blur`,this.onBlur),this.parent.addEventListener(`click`,this.onBlur),this.disabledObserver=new MutationObserver(e=>{e.forEach(e=>{e.attributeName===`disabled`&&this.hide()})}),this.disabledObserver.observe(this.parent,{attributes:!0})),document.addEventListener(`keydown`,this.onKeyDown),document.addEventListener(`keyup`,this.onKeyUp),document.addEventListener(`scroll`,this.onBlur)}disconnectedCallback(){var e,t;(e=this.cleanup)==null||e.call(this),this.tooltip&&=(this.tooltip.remove(),void 0),this.parent&&=(this.parent.removeEventListener(`mouseenter`,this.onMouseEnter),this.parent.removeEventListener(`focus`,this.onFocus),this.parent.removeEventListener(`mouseleave`,this.onMouseLeave),this.parent.removeEventListener(`blur`,this.onBlur),this.parent.removeEventListener(`click`,this.onBlur),void 0),document.removeEventListener(`keydown`,this.onKeyDown),document.removeEventListener(`keyup`,this.onKeyUp),document.removeEventListener(`scroll`,this.onBlur),(t=this.disabledObserver)==null||t.disconnect(),clearTimeout(this.timeout)}get disabled(){return this.hasAttribute(`disabled`)}set disabled(e){e?this.setAttribute(`disabled`,``):this.removeAttribute(`disabled`)}show(){var t;if(this.disabled)return;let n=this.createTooltip();clearTimeout(this.timeout),this.showing||=(n.hidden=!1,hr(n),!0),this.innerHTML!==this.prevInnerHTML&&(this.prevInnerHTML=n.innerHTML=this.innerHTML),n.style.position=`absolute`,(t=this.cleanup)==null||t.call(this),this.cleanup=vo(this.parent,n,()=>Co(this.parent,n,{placement:this.getAttribute(`placement`)||e.placement,middleware:[bo(),xo()]}).then(({x:e,y:t,placement:r})=>{Object.assign(n.style,{left:`${e}px`,top:`${t}px`}),n.dataset.placement=r})),this.dispatchEvent(new Event(`open`))}hide(){var e;clearTimeout(this.timeout),(e=this.cleanup)==null||e.call(this),this.showing&&(this.showing=!1,this.tooltip&&gr(this.tooltip).then(()=>{this.tooltip&&(this.tooltip.hidden=!0)}),this.dispatchEvent(new Event(`close`)))}afterDelay(t){clearTimeout(this.timeout);let n=parseInt(this.getAttribute(`delay`)||``);this.timeout=window.setTimeout(t.bind(this),isNaN(n)?e.delay:n)}createTooltip(){return this.tooltip||(this.tooltip=document.createElement(`div`),this.tooltip.hidden=!0,this.tooltip.addEventListener(`mouseenter`,this.show.bind(this)),this.tooltip.addEventListener(`mouseleave`,this.afterDelay.bind(this,this.hide)),document.body.appendChild(this.tooltip)),this.tooltip.className=this.getAttribute(`tooltip-class`)||e.tooltipClass,this.tooltip}};K(Po,`delay`,100),K(Po,`placement`,`top`),K(Po,`tooltipClass`,`tooltip`);let Fo=Po;window.customElements.define(`ui-accordion`,sr),window.customElements.define(`ui-alerts`,fr),window.customElements.define(`ui-disclosure`,wr),window.customElements.define(`ui-menu`,ci),window.customElements.define(`ui-modal`,Ii),window.customElements.define(`ui-popup`,Do),window.customElements.define(`ui-toolbar`,jo),window.customElements.define(`ui-tooltip`,Fo);var Io=class{constructor(){this.title=``,this.count=0}initialize(){this.title=document.title,this.count=0}increment(){this.count++,this.update()}reset(){this.count=0,this.update()}update(){document.title=(this.count?`(${this.count}) `:``)+this.title}};Waterhole.documentTitle=new Io,document.addEventListener(`turbo:load`,()=>Waterhole.documentTitle.initialize()),window.addEventListener(`focus`,()=>Waterhole.documentTitle.reset());var Lo=class{constructor(){this.notificationCreatedEvent=`.Illuminate\\\\Notifications\\\\Events\\\\BroadcastNotificationCreated`}listenForWhisper(e,t){return this.listen(`.client-`+e,t)}notification(e){return this.listen(this.notificationCreatedEvent,e)}stopListeningForNotification(e){return this.stopListening(this.notificationCreatedEvent,e)}stopListeningForWhisper(e,t){return this.stopListening(`.client-`+e,t)}},Ro=class{constructor(e){this.namespace=e}format(e){return[`.`,`\\\\`].includes(e.charAt(0))?e.substring(1):(this.namespace&&(e=this.namespace+`.`+e),e.replace(/\\./g,`\\\\`))}setNamespace(e){this.namespace=e}};function zo(e){try{new e}catch(e){if(e instanceof Error&&e.message.includes(`is not a constructor`))return!1}return!0}var Bo=class extends Lo{constructor(e,t,n){super(),this.name=t,this.pusher=e,this.options=n,this.eventFormatter=new Ro(this.options.namespace),this.subscribe()}subscribe(){this.subscription=this.pusher.subscribe(this.name)}unsubscribe(){this.pusher.unsubscribe(this.name)}listen(e,t){return this.on(this.eventFormatter.format(e),t),this}listenToAll(e){return this.subscription.bind_global((t,n)=>{if(t.startsWith(`pusher:`))return;let r=String(this.options.namespace??``).replace(/\\./g,`\\\\`);e(t.startsWith(r)?t.substring(r.length+1):`.`+t,n)}),this}stopListening(e,t){return t?this.subscription.unbind(this.eventFormatter.format(e),t):this.subscription.unbind(this.eventFormatter.format(e)),this}stopListeningToAll(e){return e?this.subscription.unbind_global(e):this.subscription.unbind_global(),this}subscribed(e){return this.on(`pusher:subscription_succeeded`,()=>{e()}),this}error(e){return this.on(`pusher:subscription_error`,t=>{e(t)}),this}on(e,t){return this.subscription.bind(e,t),this}},Vo=class extends Bo{whisper(e,t){return this.pusher.channels.channels[this.name].trigger(`client-${e}`,t),this}},Ho=class extends Bo{whisper(e,t){return this.pusher.channels.channels[this.name].trigger(`client-${e}`,t),this}},Uo=class extends Vo{here(e){return this.on(`pusher:subscription_succeeded`,t=>{e(Object.keys(t.members).map(e=>t.members[e]))}),this}joining(e){return this.on(`pusher:member_added`,t=>{e(t.info)}),this}whisper(e,t){return this.pusher.channels.channels[this.name].trigger(`client-${e}`,t),this}leaving(e){return this.on(`pusher:member_removed`,t=>{e(t.info)}),this}},Wo=class extends Lo{constructor(e,t,n){super(),this.events={},this.listeners={},this.name=t,this.socket=e,this.options=n,this.eventFormatter=new Ro(this.options.namespace),this.subscribe()}subscribe(){this.socket.emit(`subscribe`,{channel:this.name,auth:this.options.auth||{}})}unsubscribe(){this.unbind(),this.socket.emit(`unsubscribe`,{channel:this.name,auth:this.options.auth||{}})}listen(e,t){return this.on(this.eventFormatter.format(e),t),this}stopListening(e,t){return this.unbindEvent(this.eventFormatter.format(e),t),this}subscribed(e){return this.on(`connect`,t=>{e(t)}),this}error(e){return this}on(e,t){return this.listeners[e]=this.listeners[e]||[],this.events[e]||(this.events[e]=(t,n)=>{this.name===t&&this.listeners[e]&&this.listeners[e].forEach(e=>e(n))},this.socket.on(e,this.events[e])),this.listeners[e].push(t),this}unbind(){Object.keys(this.events).forEach(e=>{this.unbindEvent(e)})}unbindEvent(e,t){this.listeners[e]=this.listeners[e]||[],t&&(this.listeners[e]=this.listeners[e].filter(e=>e!==t)),(!t||this.listeners[e].length===0)&&(this.events[e]&&(this.socket.removeListener(e,this.events[e]),delete this.events[e]),delete this.listeners[e])}},Go=class extends Wo{whisper(e,t){return this.socket.emit(`client event`,{channel:this.name,event:`client-${e}`,data:t}),this}},Ko=class extends Go{here(e){return this.on(`presence:subscribed`,t=>{e(t.map(e=>e.user_info))}),this}joining(e){return this.on(`presence:joining`,t=>e(t.user_info)),this}whisper(e,t){return this.socket.emit(`client event`,{channel:this.name,event:`client-${e}`,data:t}),this}leaving(e){return this.on(`presence:leaving`,t=>e(t.user_info)),this}},qo=class extends Lo{subscribe(){}unsubscribe(){}listen(e,t){return this}listenToAll(e){return this}stopListening(e,t){return this}subscribed(e){return this}error(e){return this}on(e,t){return this}},Jo=class extends qo{whisper(e,t){return this}},Yo=class extends qo{whisper(e,t){return this}},Xo=class extends Jo{here(e){return this}joining(e){return this}whisper(e,t){return this}leaving(e){return this}};let Zo=class e{constructor(e){this.setOptions(e),this.connect()}setOptions(t){this.options={...e._defaultOptions,...t,broadcaster:t.broadcaster};let n=this.csrfToken();n&&(this.options.auth.headers[`X-CSRF-TOKEN`]=n,this.options.userAuthentication.headers[`X-CSRF-TOKEN`]=n),n=this.options.bearerToken,n&&(this.options.auth.headers.Authorization=`Bearer `+n,this.options.userAuthentication.headers.Authorization=`Bearer `+n)}csrfToken(){var e;return typeof window<`u`&&(e=window.Laravel)!=null&&e.csrfToken?window.Laravel.csrfToken:this.options.csrfToken?this.options.csrfToken:typeof document<`u`&&typeof document.querySelector==`function`?document.querySelector(`meta[name=\"csrf-token\"]`)?.getAttribute(`content`)??null:null}};Zo._defaultOptions={auth:{headers:{}},authEndpoint:`/broadcasting/auth`,userAuthentication:{endpoint:`/broadcasting/user-auth`,headers:{}},csrfToken:null,bearerToken:null,host:null,key:null,namespace:`App.Events`};let Qo=Zo;var $o=class extends Qo{constructor(){super(...arguments),this.channels={}}connect(){if(typeof this.options.client<`u`)this.pusher=this.options.client;else if(this.options.Pusher)this.pusher=new this.options.Pusher(this.options.key,this.options);else if(typeof window<`u`&&typeof window.Pusher<`u`)this.pusher=new window.Pusher(this.options.key,this.options);else throw Error(`Pusher client not found. Should be globally available or passed via options.client`)}signin(){this.pusher.signin()}listen(e,t,n){return this.channel(e).listen(t,n)}channel(e){return this.channels[e]||(this.channels[e]=new Bo(this.pusher,e,this.options)),this.channels[e]}privateChannel(e){return this.channels[`private-`+e]||(this.channels[`private-`+e]=new Vo(this.pusher,`private-`+e,this.options)),this.channels[`private-`+e]}encryptedPrivateChannel(e){return this.channels[`private-encrypted-`+e]||(this.channels[`private-encrypted-`+e]=new Ho(this.pusher,`private-encrypted-`+e,this.options)),this.channels[`private-encrypted-`+e]}presenceChannel(e){return this.channels[`presence-`+e]||(this.channels[`presence-`+e]=new Uo(this.pusher,`presence-`+e,this.options)),this.channels[`presence-`+e]}leave(e){[e,`private-`+e,`private-encrypted-`+e,`presence-`+e].forEach(e=>{this.leaveChannel(e)})}leaveChannel(e){this.channels[e]&&(this.channels[e].unsubscribe(),delete this.channels[e])}socketId(){return this.pusher.connection.socket_id}connectionStatus(){let e=this.pusher.connection.state;switch(e){case`connected`:case`connecting`:return e;case`failed`:case`unavailable`:return`failed`;default:return`disconnected`}}onConnectionChange(e){let t=()=>{e(this.connectionStatus())},n=[`state_change`,`connected`,`disconnected`];return n.forEach(e=>{this.pusher.connection.bind(e,t)}),()=>{n.forEach(e=>{this.pusher.connection.unbind(e,t)})}}disconnect(){this.pusher.disconnect()}},es=class extends Qo{constructor(){super(...arguments),this.channels={}}connect(){this.socket=this.getSocketIO()(this.options.host??void 0,this.options),this.socket.io.on(`reconnect`,()=>{Object.values(this.channels).forEach(e=>{e.subscribe()})})}getSocketIO(){if(typeof this.options.client<`u`)return this.options.client;if(typeof window<`u`&&typeof window.io<`u`)return window.io;throw Error(`Socket.io client not found. Should be globally available or passed via options.client`)}listen(e,t,n){return this.channel(e).listen(t,n)}channel(e){return this.channels[e]||(this.channels[e]=new Wo(this.socket,e,this.options)),this.channels[e]}privateChannel(e){return this.channels[`private-`+e]||(this.channels[`private-`+e]=new Go(this.socket,`private-`+e,this.options)),this.channels[`private-`+e]}presenceChannel(e){return this.channels[`presence-`+e]||(this.channels[`presence-`+e]=new Ko(this.socket,`presence-`+e,this.options)),this.channels[`presence-`+e]}leave(e){[e,`private-`+e,`presence-`+e].forEach(e=>{this.leaveChannel(e)})}leaveChannel(e){this.channels[e]&&(this.channels[e].unsubscribe(),delete this.channels[e])}socketId(){return this.socket.id}connectionStatus(){return this.socket.connected?`connected`:this.socket.io._reconnecting?`reconnecting`:this.socket.id===void 0?`connecting`:`disconnected`}onConnectionChange(e){let t=()=>{e(this.connectionStatus())},n=[`connect`,`disconnect`,`connect_error`,`reconnect_attempt`,`reconnect`,`reconnect_error`,`reconnect_failed`];return n.forEach(e=>{this.socket.on(e,t)}),()=>{n.forEach(e=>{this.socket.off(e,t)})}}disconnect(){this.socket.disconnect()}},ts=class extends Qo{constructor(){super(...arguments),this.channels={}}connect(){}listen(e,t,n){return new qo}channel(e){return new qo}privateChannel(e){return new Jo}encryptedPrivateChannel(e){return new Yo}presenceChannel(e){return new Xo}leave(e){}leaveChannel(e){}socketId(){return`fake-socket-id`}connectionStatus(){return`connected`}onConnectionChange(e){return()=>{}}disconnect(){}},ns=class{constructor(e){this.options=e,this.connect(),this.options.withoutInterceptors||this.registerInterceptors()}channel(e){return this.connector.channel(e)}connect(){if(this.options.broadcaster===`reverb`)this.connector=new $o({...this.options,cluster:``});else if(this.options.broadcaster===`pusher`)this.connector=new $o(this.options);else if(this.options.broadcaster===`ably`)this.connector=new $o({...this.options,cluster:``,broadcaster:`pusher`});else if(this.options.broadcaster===`socket.io`)this.connector=new es(this.options);else if(this.options.broadcaster===`null`)this.connector=new ts(this.options);else if(typeof this.options.broadcaster==`function`&&zo(this.options.broadcaster))this.connector=new this.options.broadcaster(this.options);else throw Error(`Broadcaster ${typeof this.options.broadcaster} ${String(this.options.broadcaster)} is not supported.`)}disconnect(){this.connector.disconnect()}join(e){return this.connector.presenceChannel(e)}leave(e){this.connector.leave(e)}leaveChannel(e){this.connector.leaveChannel(e)}leaveAllChannels(){for(let e in this.connector.channels)this.leaveChannel(e)}listen(e,t,n){return this.connector.listen(e,t,n)}private(e){return this.connector.privateChannel(e)}encryptedPrivate(e){if(this.connectorSupportsEncryptedPrivateChannels(this.connector))return this.connector.encryptedPrivateChannel(e);throw Error(`Broadcaster ${typeof this.options.broadcaster} ${String(this.options.broadcaster)} does not support encrypted private channels.`)}connectorSupportsEncryptedPrivateChannels(e){return e instanceof $o||e instanceof ts}socketId(){return this.connector.socketId()}connectionStatus(){return this.connector.connectionStatus()}registerInterceptors(){typeof Vue<`u`&&Vue!=null&&Vue.http&&this.registerVueRequestInterceptor(),typeof axios==`function`&&this.registerAxiosRequestInterceptor(),typeof jQuery==`function`&&this.registerjQueryAjaxSetup(),typeof Turbo==`object`&&this.registerTurboRequestInterceptor()}registerVueRequestInterceptor(){Vue.http.interceptors.push((e,t)=>{this.socketId()&&e.headers.set(`X-Socket-ID`,this.socketId()),t()})}registerAxiosRequestInterceptor(){axios.interceptors.request.use(e=>(this.socketId()&&(e.headers[`X-Socket-Id`]=this.socketId()),e))}registerjQueryAjaxSetup(){typeof jQuery.ajax<`u`&&jQuery.ajaxPrefilter((e,t,n)=>{this.socketId()&&n.setRequestHeader(`X-Socket-Id`,this.socketId())})}registerTurboRequestInterceptor(){document.addEventListener(`turbo:before-fetch-request`,e=>{e.detail.fetchOptions.headers[`X-Socket-Id`]=this.socketId()})}},rs=l(o(((e,t)=>{\n/*!\n* Pusher JavaScript Library v8.4.0\n* https://pusher.com/\n*\n* Copyright 2020, Pusher\n* Released under the MIT licence.\n*/\n(function(n,r){typeof e==`object`&&typeof t==`object`?t.exports=r():typeof define==`function`&&define.amd?define([],r):typeof e==`object`?e.Pusher=r():n.Pusher=r()})(window,function(){return(function(e){var t={};function n(r){if(t[r])return t[r].exports;var i=t[r]={i:r,l:!1,exports:{}};return e[r].call(i.exports,i,i.exports,n),i.l=!0,i.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){typeof Symbol<`u`&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:`Module`}),Object.defineProperty(e,`__esModule`,{value:!0})},n.t=function(e,t){if(t&1&&(e=n(e)),t&8||t&4&&typeof e==`object`&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,`default`,{enumerable:!0,value:e}),t&2&&typeof e!=`string`)for(var i in e)n.d(r,i,function(t){return e[t]}.bind(null,i));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,`a`,t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p=``,n(n.s=2)})([(function(e,t,n){\"use strict\";var r=this&&this.__extends||(function(){var e=function(t,n){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n])},e(t,n)};return function(t,n){e(t,n);function r(){this.constructor=t}t.prototype=n===null?Object.create(n):(r.prototype=n.prototype,new r)}})();Object.defineProperty(t,`__esModule`,{value:!0});var i=256,a=function(){function e(e){e===void 0&&(e=`=`),this._paddingCharacter=e}return e.prototype.encodedLength=function(e){return this._paddingCharacter?(e+2)/3*4|0:(e*8+5)/6|0},e.prototype.encode=function(e){for(var t=``,n=0;n<e.length-2;n+=3){var r=e[n]<<16|e[n+1]<<8|e[n+2];t+=this._encodeByte(r>>>18&63),t+=this._encodeByte(r>>>12&63),t+=this._encodeByte(r>>>6&63),t+=this._encodeByte(r>>>0&63)}var i=e.length-n;if(i>0){var r=e[n]<<16|(i===2?e[n+1]<<8:0);t+=this._encodeByte(r>>>18&63),t+=this._encodeByte(r>>>12&63),i===2?t+=this._encodeByte(r>>>6&63):t+=this._paddingCharacter||``,t+=this._paddingCharacter||``}return t},e.prototype.maxDecodedLength=function(e){return this._paddingCharacter?e/4*3|0:(e*6+7)/8|0},e.prototype.decodedLength=function(e){return this.maxDecodedLength(e.length-this._getPaddingLength(e))},e.prototype.decode=function(e){if(e.length===0)return new Uint8Array;for(var t=this._getPaddingLength(e),n=e.length-t,r=new Uint8Array(this.maxDecodedLength(n)),a=0,o=0,s=0,c=0,l=0,u=0,d=0;o<n-4;o+=4)c=this._decodeChar(e.charCodeAt(o+0)),l=this._decodeChar(e.charCodeAt(o+1)),u=this._decodeChar(e.charCodeAt(o+2)),d=this._decodeChar(e.charCodeAt(o+3)),r[a++]=c<<2|l>>>4,r[a++]=l<<4|u>>>2,r[a++]=u<<6|d,s|=c&i,s|=l&i,s|=u&i,s|=d&i;if(o<n-1&&(c=this._decodeChar(e.charCodeAt(o)),l=this._decodeChar(e.charCodeAt(o+1)),r[a++]=c<<2|l>>>4,s|=c&i,s|=l&i),o<n-2&&(u=this._decodeChar(e.charCodeAt(o+2)),r[a++]=l<<4|u>>>2,s|=u&i),o<n-3&&(d=this._decodeChar(e.charCodeAt(o+3)),r[a++]=u<<6|d,s|=d&i),s!==0)throw Error(`Base64Coder: incorrect characters for decoding`);return r},e.prototype._encodeByte=function(e){var t=e;return t+=65,t+=25-e>>>8&6,t+=51-e>>>8&-75,t+=61-e>>>8&-15,t+=62-e>>>8&3,String.fromCharCode(t)},e.prototype._decodeChar=function(e){var t=i;return t+=(42-e&e-44)>>>8&-i+e-43+62,t+=(46-e&e-48)>>>8&-i+e-47+63,t+=(47-e&e-58)>>>8&-i+e-48+52,t+=(64-e&e-91)>>>8&-i+e-65+0,t+=(96-e&e-123)>>>8&-i+e-97+26,t},e.prototype._getPaddingLength=function(e){var t=0;if(this._paddingCharacter){for(var n=e.length-1;n>=0&&e[n]===this._paddingCharacter;n--)t++;if(e.length<4||t>2)throw Error(`Base64Coder: incorrect padding`)}return t},e}();t.Coder=a;var o=new a;function s(e){return o.encode(e)}t.encode=s;function c(e){return o.decode(e)}t.decode=c;var l=function(e){r(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t.prototype._encodeByte=function(e){var t=e;return t+=65,t+=25-e>>>8&6,t+=51-e>>>8&-75,t+=61-e>>>8&-13,t+=62-e>>>8&49,String.fromCharCode(t)},t.prototype._decodeChar=function(e){var t=i;return t+=(44-e&e-46)>>>8&-i+e-45+62,t+=(94-e&e-96)>>>8&-i+e-95+63,t+=(47-e&e-58)>>>8&-i+e-48+52,t+=(64-e&e-91)>>>8&-i+e-65+0,t+=(96-e&e-123)>>>8&-i+e-97+26,t},t}(a);t.URLSafeCoder=l;var u=new l;function d(e){return u.encode(e)}t.encodeURLSafe=d;function f(e){return u.decode(e)}t.decodeURLSafe=f,t.encodedLength=function(e){return o.encodedLength(e)},t.maxDecodedLength=function(e){return o.maxDecodedLength(e)},t.decodedLength=function(e){return o.decodedLength(e)}}),(function(e,t,n){\"use strict\";Object.defineProperty(t,`__esModule`,{value:!0});var r=`utf8: invalid string`,i=`utf8: invalid source encoding`;function a(e){for(var t=new Uint8Array(o(e)),n=0,r=0;r<e.length;r++){var i=e.charCodeAt(r);i<128?t[n++]=i:i<2048?(t[n++]=192|i>>6,t[n++]=128|i&63):i<55296?(t[n++]=224|i>>12,t[n++]=128|i>>6&63,t[n++]=128|i&63):(r++,i=(i&1023)<<10,i|=e.charCodeAt(r)&1023,i+=65536,t[n++]=240|i>>18,t[n++]=128|i>>12&63,t[n++]=128|i>>6&63,t[n++]=128|i&63)}return t}t.encode=a;function o(e){for(var t=0,n=0;n<e.length;n++){var i=e.charCodeAt(n);if(i<128)t+=1;else if(i<2048)t+=2;else if(i<55296)t+=3;else if(i<=57343){if(n>=e.length-1)throw Error(r);n++,t+=4}else throw Error(r)}return t}t.encodedLength=o;function s(e){for(var t=[],n=0;n<e.length;n++){var r=e[n];if(r&128){var a=void 0;if(r<224){if(n>=e.length)throw Error(i);var o=e[++n];if((o&192)!=128)throw Error(i);r=(r&31)<<6|o&63,a=128}else if(r<240){if(n>=e.length-1)throw Error(i);var o=e[++n],s=e[++n];if((o&192)!=128||(s&192)!=128)throw Error(i);r=(r&15)<<12|(o&63)<<6|s&63,a=2048}else if(r<248){if(n>=e.length-2)throw Error(i);var o=e[++n],s=e[++n],c=e[++n];if((o&192)!=128||(s&192)!=128||(c&192)!=128)throw Error(i);r=(r&15)<<18|(o&63)<<12|(s&63)<<6|c&63,a=65536}else throw Error(i);if(r<a||r>=55296&&r<=57343)throw Error(i);if(r>=65536){if(r>1114111)throw Error(i);r-=65536,t.push(String.fromCharCode(55296|r>>10)),r=56320|r&1023}}t.push(String.fromCharCode(r))}return t.join(``)}t.decode=s}),(function(e,t,n){e.exports=n(3).default}),(function(e,t,n){\"use strict\";n.r(t);class r{constructor(e,t){this.lastId=0,this.prefix=e,this.name=t}create(e){this.lastId++;var t=this.lastId,n=this.prefix+t,r=this.name+`[`+t+`]`,i=!1,a=function(){i||=(e.apply(null,arguments),!0)};return this[t]=a,{number:t,id:n,name:r,callback:a}}remove(e){delete this[e.number]}}var i=new r(`_pusher_script_`,`Pusher.ScriptReceivers`),a={VERSION:`8.4.0`,PROTOCOL:7,wsPort:80,wssPort:443,wsPath:``,httpHost:`sockjs.pusher.com`,httpPort:80,httpsPort:443,httpPath:`/pusher`,stats_host:`stats.pusher.com`,authEndpoint:`/pusher/auth`,authTransport:`ajax`,activityTimeout:12e4,pongTimeout:3e4,unavailableTimeout:1e4,userAuthentication:{endpoint:`/pusher/user-auth`,transport:`ajax`},channelAuthorization:{endpoint:`/pusher/auth`,transport:`ajax`},cdn_http:`http://js.pusher.com`,cdn_https:`https://js.pusher.com`,dependency_suffix:``};class o{constructor(e){this.options=e,this.receivers=e.receivers||i,this.loading={}}load(e,t,n){var r=this;if(r.loading[e]&&r.loading[e].length>0)r.loading[e].push(n);else{r.loading[e]=[n];var i=I.createScriptRequest(r.getPath(e,t)),a=r.receivers.create(function(t){if(r.receivers.remove(a),r.loading[e]){var n=r.loading[e];delete r.loading[e];for(var o=function(e){e||i.cleanup()},s=0;s<n.length;s++)n[s](t,o)}});i.send(a)}}getRoot(e){var t,n=I.getDocument().location.protocol;return t=e&&e.useTLS||n===`https:`?this.options.cdn_https:this.options.cdn_http,t.replace(/\\/*$/,``)+`/`+this.options.version}getPath(e,t){return this.getRoot(t)+`/`+e+this.options.suffix+`.js`}}var s=new r(`_pusher_dependencies`,`Pusher.DependenciesReceivers`),c=new o({cdn_http:a.cdn_http,cdn_https:a.cdn_https,version:a.VERSION,suffix:a.dependency_suffix,receivers:s});let l={baseUrl:`https://pusher.com`,urls:{authenticationEndpoint:{path:`/docs/channels/server_api/authenticating_users`},authorizationEndpoint:{path:`/docs/channels/server_api/authorizing-users/`},javascriptQuickStart:{path:`/docs/javascript_quick_start`},triggeringClientEvents:{path:`/docs/client_api_guide/client_events#trigger-events`},encryptedChannelSupport:{fullUrl:`https://github.com/pusher/pusher-js/tree/cc491015371a4bde5743d1c87a0fbac0feb53195#encrypted-channel-support`}}};var u={buildLogSuffix:function(e){let t=l.urls[e];if(!t)return``;let n;return t.fullUrl?n=t.fullUrl:t.path&&(n=l.baseUrl+t.path),n?`See: ${n}`:``}},d;(function(e){e.UserAuthentication=`user-authentication`,e.ChannelAuthorization=`channel-authorization`})(d||={});class f extends Error{constructor(e){super(e),Object.setPrototypeOf(this,new.target.prototype)}}class p extends Error{constructor(e){super(e),Object.setPrototypeOf(this,new.target.prototype)}}class m extends Error{constructor(e){super(e),Object.setPrototypeOf(this,new.target.prototype)}}class h extends Error{constructor(e){super(e),Object.setPrototypeOf(this,new.target.prototype)}}class g extends Error{constructor(e){super(e),Object.setPrototypeOf(this,new.target.prototype)}}class _ extends Error{constructor(e){super(e),Object.setPrototypeOf(this,new.target.prototype)}}class v extends Error{constructor(e){super(e),Object.setPrototypeOf(this,new.target.prototype)}}class y extends Error{constructor(e){super(e),Object.setPrototypeOf(this,new.target.prototype)}}class b extends Error{constructor(e,t){super(t),this.status=e,Object.setPrototypeOf(this,new.target.prototype)}}var x=function(e,t,n,r,i){let a=I.createXHR();for(var o in a.open(`POST`,n.endpoint,!0),a.setRequestHeader(`Content-Type`,`application/x-www-form-urlencoded`),n.headers)a.setRequestHeader(o,n.headers[o]);if(n.headersProvider!=null){let e=n.headersProvider();for(var o in e)a.setRequestHeader(o,e[o])}return a.onreadystatechange=function(){if(a.readyState===4)if(a.status===200){let e,t=!1;try{e=JSON.parse(a.responseText),t=!0}catch{i(new b(200,`JSON returned from ${r.toString()} endpoint was invalid, yet status code was 200. Data was: ${a.responseText}`),null)}t&&i(null,e)}else{let e=``;switch(r){case d.UserAuthentication:e=u.buildLogSuffix(`authenticationEndpoint`);break;case d.ChannelAuthorization:e=`Clients must be authorized to join private or presence channels. ${u.buildLogSuffix(`authorizationEndpoint`)}`;break}i(new b(a.status,`Unable to retrieve auth string from ${r.toString()} endpoint - received status: ${a.status} from ${n.endpoint}. ${e}`),null)}},a.send(t),a};function ee(e){return D(ne(e))}for(var S=String.fromCharCode,C=`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/`,w={},T=0,te=C.length;T<te;T++)w[C.charAt(T)]=T;var E=function(e){var t=e.charCodeAt(0);return t<128?e:t<2048?S(192|t>>>6)+S(128|t&63):S(224|t>>>12&15)+S(128|t>>>6&63)+S(128|t&63)},ne=function(e){return e.replace(/[^\\x00-\\x7F]/g,E)},re=function(e){var t=[0,2,1][e.length%3],n=e.charCodeAt(0)<<16|(e.length>1?e.charCodeAt(1):0)<<8|(e.length>2?e.charCodeAt(2):0);return[C.charAt(n>>>18),C.charAt(n>>>12&63),t>=2?`=`:C.charAt(n>>>6&63),t>=1?`=`:C.charAt(n&63)].join(``)},D=window.btoa||function(e){return e.replace(/[\\s\\S]{1,3}/g,re)};class ie{constructor(e,t,n,r){this.clear=t,this.timer=e(()=>{this.timer&&=r(this.timer)},n)}isRunning(){return this.timer!==null}ensureAborted(){this.timer&&=(this.clear(this.timer),null)}}var ae=ie;function oe(e){window.clearTimeout(e)}function se(e){window.clearInterval(e)}class ce extends ae{constructor(e,t){super(setTimeout,oe,e,function(e){return t(),null})}}class le extends ae{constructor(e,t){super(setInterval,se,e,function(e){return t(),e})}}var O={now(){return Date.now?Date.now():new Date().valueOf()},defer(e){return new ce(0,e)},method(e,...t){var n=Array.prototype.slice.call(arguments,1);return function(t){return t[e].apply(t,n.concat(arguments))}}};function k(e,...t){for(var n=0;n<t.length;n++){var r=t[n];for(var i in r)r[i]&&r[i].constructor&&r[i].constructor===Object?e[i]=k(e[i]||{},r[i]):e[i]=r[i]}return e}function ue(){for(var e=[`Pusher`],t=0;t<arguments.length;t++)typeof arguments[t]==`string`?e.push(arguments[t]):e.push(Te(arguments[t]));return e.join(` : `)}function de(e,t){var n=Array.prototype.indexOf;if(e===null)return-1;if(n&&e.indexOf===n)return e.indexOf(t);for(var r=0,i=e.length;r<i;r++)if(e[r]===t)return r;return-1}function A(e,t){for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&t(e[n],n,e)}function fe(e){var t=[];return A(e,function(e,n){t.push(n)}),t}function pe(e){var t=[];return A(e,function(e){t.push(e)}),t}function me(e,t,n){for(var r=0;r<e.length;r++)t.call(n||window,e[r],r,e)}function he(e,t){for(var n=[],r=0;r<e.length;r++)n.push(t(e[r],r,e,n));return n}function ge(e,t){var n={};return A(e,function(e,r){n[r]=t(e)}),n}function _e(e,t){t||=function(e){return!!e};for(var n=[],r=0;r<e.length;r++)t(e[r],r,e,n)&&n.push(e[r]);return n}function ve(e,t){var n={};return A(e,function(r,i){(t&&t(r,i,e,n)||r)&&(n[i]=r)}),n}function ye(e){var t=[];return A(e,function(e,n){t.push([n,e])}),t}function be(e,t){for(var n=0;n<e.length;n++)if(t(e[n],n,e))return!0;return!1}function xe(e,t){for(var n=0;n<e.length;n++)if(!t(e[n],n,e))return!1;return!0}function Se(e){return ge(e,function(e){return typeof e==`object`&&(e=Te(e)),encodeURIComponent(ee(e.toString()))})}function Ce(e){return he(ye(Se(ve(e,function(e){return e!==void 0}))),O.method(`join`,`=`)).join(`&`)}function we(e){var t=[],n=[];return(function e(r,i){var a,o,s;switch(typeof r){case`object`:if(!r)return null;for(a=0;a<t.length;a+=1)if(t[a]===r)return{$ref:n[a]};if(t.push(r),n.push(i),Object.prototype.toString.apply(r)===`[object Array]`)for(s=[],a=0;a<r.length;a+=1)s[a]=e(r[a],i+`[`+a+`]`);else for(o in s={},r)Object.prototype.hasOwnProperty.call(r,o)&&(s[o]=e(r[o],i+`[`+JSON.stringify(o)+`]`));return s;case`number`:case`string`:case`boolean`:return r}})(e,`$`)}function Te(e){try{return JSON.stringify(e)}catch{return JSON.stringify(we(e))}}class Ee{constructor(){this.globalLog=e=>{window.console&&window.console.log&&window.console.log(e)}}debug(...e){this.log(this.globalLog,e)}warn(...e){this.log(this.globalLogWarn,e)}error(...e){this.log(this.globalLogError,e)}globalLogWarn(e){window.console&&window.console.warn?window.console.warn(e):this.globalLog(e)}globalLogError(e){window.console&&window.console.error?window.console.error(e):this.globalLogWarn(e)}log(e,...t){var n=ue.apply(this,arguments);xn.log?xn.log(n):xn.logToConsole&&e.bind(this)(n)}}var j=new Ee,De=function(e,t,n,r,i){(n.headers!==void 0||n.headersProvider!=null)&&j.warn(`To send headers with the ${r.toString()} request, you must use AJAX, rather than JSONP.`);var a=e.nextAuthCallbackID.toString();e.nextAuthCallbackID++;var o=e.getDocument(),s=o.createElement(`script`);e.auth_callbacks[a]=function(e){i(null,e)};var c=`Pusher.auth_callbacks['`+a+`']`;s.src=n.endpoint+`?callback=`+encodeURIComponent(c)+`&`+t;var l=o.getElementsByTagName(`head`)[0]||o.documentElement;l.insertBefore(s,l.firstChild)};class Oe{constructor(e){this.src=e}send(e){var t=this,n=`Error loading `+t.src;t.script=document.createElement(`script`),t.script.id=e.id,t.script.src=t.src,t.script.type=`text/javascript`,t.script.charset=`UTF-8`,t.script.addEventListener?(t.script.onerror=function(){e.callback(n)},t.script.onload=function(){e.callback(null)}):t.script.onreadystatechange=function(){(t.script.readyState===`loaded`||t.script.readyState===`complete`)&&e.callback(null)},t.script.async===void 0&&document.attachEvent&&/opera/i.test(navigator.userAgent)?(t.errorScript=document.createElement(`script`),t.errorScript.id=e.id+`_error`,t.errorScript.text=e.name+`('`+n+`');`,t.script.async=t.errorScript.async=!1):t.script.async=!0;var r=document.getElementsByTagName(`head`)[0];r.insertBefore(t.script,r.firstChild),t.errorScript&&r.insertBefore(t.errorScript,t.script.nextSibling)}cleanup(){this.script&&(this.script.onload=this.script.onerror=null,this.script.onreadystatechange=null),this.script&&this.script.parentNode&&this.script.parentNode.removeChild(this.script),this.errorScript&&this.errorScript.parentNode&&this.errorScript.parentNode.removeChild(this.errorScript),this.script=null,this.errorScript=null}}class ke{constructor(e,t){this.url=e,this.data=t}send(e){if(!this.request){var t=Ce(this.data),n=this.url+`/`+e.number+`?`+t;this.request=I.createScriptRequest(n),this.request.send(e)}}cleanup(){this.request&&this.request.cleanup()}}var Ae={name:`jsonp`,getAgent:function(e,t){return function(n,r){var a=`http`+(t?`s`:``)+`://`+(e.host||e.options.host)+e.options.path,o=I.createJSONPRequest(a,n),s=I.ScriptReceivers.create(function(t,n){i.remove(s),o.cleanup(),n&&n.host&&(e.host=n.host),r&&r(t,n)});o.send(s)}}};function je(e,t,n){var r=e+(t.useTLS?`s`:``),i=t.useTLS?t.hostTLS:t.hostNonTLS;return r+`://`+i+n}function Me(e,t){return`/app/`+e+(`?protocol=`+a.PROTOCOL+`&client=js&version=`+a.VERSION+(t?`&`+t:``))}var Ne={getInitial:function(e,t){return je(`ws`,t,(t.httpPath||``)+Me(e,`flash=false`))}},Pe={getInitial:function(e,t){return je(`http`,t,(t.httpPath||`/pusher`)+Me(e))}},Fe={getInitial:function(e,t){return je(`http`,t,t.httpPath||`/pusher`)},getPath:function(e,t){return Me(e)}};class Ie{constructor(){this._callbacks={}}get(e){return this._callbacks[Le(e)]}add(e,t,n){var r=Le(e);this._callbacks[r]=this._callbacks[r]||[],this._callbacks[r].push({fn:t,context:n})}remove(e,t,n){if(!e&&!t&&!n){this._callbacks={};return}var r=e?[Le(e)]:fe(this._callbacks);t||n?this.removeCallback(r,t,n):this.removeAllCallbacks(r)}removeCallback(e,t,n){me(e,function(e){this._callbacks[e]=_e(this._callbacks[e]||[],function(e){return t&&t!==e.fn||n&&n!==e.context}),this._callbacks[e].length===0&&delete this._callbacks[e]},this)}removeAllCallbacks(e){me(e,function(e){delete this._callbacks[e]},this)}}function Le(e){return`_`+e}class M{constructor(e){this.callbacks=new Ie,this.global_callbacks=[],this.failThrough=e}bind(e,t,n){return this.callbacks.add(e,t,n),this}bind_global(e){return this.global_callbacks.push(e),this}unbind(e,t,n){return this.callbacks.remove(e,t,n),this}unbind_global(e){return e?(this.global_callbacks=_e(this.global_callbacks||[],t=>t!==e),this):(this.global_callbacks=[],this)}unbind_all(){return this.unbind(),this.unbind_global(),this}emit(e,t,n){for(var r=0;r<this.global_callbacks.length;r++)this.global_callbacks[r](e,t);var i=this.callbacks.get(e),a=[];if(n?a.push(t,n):t&&a.push(t),i&&i.length>0)for(var r=0;r<i.length;r++)i[r].fn.apply(i[r].context||window,a);else this.failThrough&&this.failThrough(e,t);return this}}class Re extends M{constructor(e,t,n,r,i){super(),this.initialize=I.transportConnectionInitializer,this.hooks=e,this.name=t,this.priority=n,this.key=r,this.options=i,this.state=`new`,this.timeline=i.timeline,this.activityTimeout=i.activityTimeout,this.id=this.timeline.generateUniqueID()}handlesActivityChecks(){return!!this.hooks.handlesActivityChecks}supportsPing(){return!!this.hooks.supportsPing}connect(){if(this.socket||this.state!==`initialized`)return!1;var e=this.hooks.urls.getInitial(this.key,this.options);try{this.socket=this.hooks.getSocket(e,this.options)}catch(e){return O.defer(()=>{this.onError(e),this.changeState(`closed`)}),!1}return this.bindListeners(),j.debug(`Connecting`,{transport:this.name,url:e}),this.changeState(`connecting`),!0}close(){return this.socket?(this.socket.close(),!0):!1}send(e){return this.state===`open`?(O.defer(()=>{this.socket&&this.socket.send(e)}),!0):!1}ping(){this.state===`open`&&this.supportsPing()&&this.socket.ping()}onOpen(){this.hooks.beforeOpen&&this.hooks.beforeOpen(this.socket,this.hooks.urls.getPath(this.key,this.options)),this.changeState(`open`),this.socket.onopen=void 0}onError(e){this.emit(`error`,{type:`WebSocketError`,error:e}),this.timeline.error(this.buildTimelineMessage({error:e.toString()}))}onClose(e){e?this.changeState(`closed`,{code:e.code,reason:e.reason,wasClean:e.wasClean}):this.changeState(`closed`),this.unbindListeners(),this.socket=void 0}onMessage(e){this.emit(`message`,e)}onActivity(){this.emit(`activity`)}bindListeners(){this.socket.onopen=()=>{this.onOpen()},this.socket.onerror=e=>{this.onError(e)},this.socket.onclose=e=>{this.onClose(e)},this.socket.onmessage=e=>{this.onMessage(e)},this.supportsPing()&&(this.socket.onactivity=()=>{this.onActivity()})}unbindListeners(){this.socket&&(this.socket.onopen=void 0,this.socket.onerror=void 0,this.socket.onclose=void 0,this.socket.onmessage=void 0,this.supportsPing()&&(this.socket.onactivity=void 0))}changeState(e,t){this.state=e,this.timeline.info(this.buildTimelineMessage({state:e,params:t})),this.emit(e,t)}buildTimelineMessage(e){return k({cid:this.id},e)}}class N{constructor(e){this.hooks=e}isSupported(e){return this.hooks.isSupported(e)}createConnection(e,t,n,r){return new Re(this.hooks,e,t,n,r)}}var ze=new N({urls:Ne,handlesActivityChecks:!1,supportsPing:!1,isInitialized:function(){return!!I.getWebSocketAPI()},isSupported:function(){return!!I.getWebSocketAPI()},getSocket:function(e){return I.createWebSocket(e)}}),Be={urls:Pe,handlesActivityChecks:!1,supportsPing:!0,isInitialized:function(){return!0}},Ve=k({getSocket:function(e){return I.HTTPFactory.createStreamingSocket(e)}},Be),He=k({getSocket:function(e){return I.HTTPFactory.createPollingSocket(e)}},Be),Ue={isSupported:function(){return I.isXHRSupported()}},We={ws:ze,xhr_streaming:new N(k({},Ve,Ue)),xhr_polling:new N(k({},He,Ue))},Ge=new N({file:`sockjs`,urls:Fe,handlesActivityChecks:!0,supportsPing:!1,isSupported:function(){return!0},isInitialized:function(){return window.SockJS!==void 0},getSocket:function(e,t){return new window.SockJS(e,null,{js_path:c.getPath(`sockjs`,{useTLS:t.useTLS}),ignore_null_origin:t.ignoreNullOrigin})},beforeOpen:function(e,t){e.send(JSON.stringify({path:t}))}}),Ke={isSupported:function(e){return I.isXDRSupported(e.useTLS)}},qe=new N(k({},Ve,Ke)),Je=new N(k({},He,Ke));We.xdr_streaming=qe,We.xdr_polling=Je,We.sockjs=Ge;var Ye=We;class Xe extends M{constructor(){super();var e=this;window.addEventListener!==void 0&&(window.addEventListener(`online`,function(){e.emit(`online`)},!1),window.addEventListener(`offline`,function(){e.emit(`offline`)},!1))}isOnline(){return window.navigator.onLine===void 0?!0:window.navigator.onLine}}var Ze=new Xe;class Qe{constructor(e,t,n){this.manager=e,this.transport=t,this.minPingDelay=n.minPingDelay,this.maxPingDelay=n.maxPingDelay,this.pingDelay=void 0}createConnection(e,t,n,r){r=k({},r,{activityTimeout:this.pingDelay});var i=this.transport.createConnection(e,t,n,r),a=null,o=function(){i.unbind(`open`,o),i.bind(`closed`,s),a=O.now()},s=e=>{if(i.unbind(`closed`,s),e.code===1002||e.code===1003)this.manager.reportDeath();else if(!e.wasClean&&a){var t=O.now()-a;t<2*this.maxPingDelay&&(this.manager.reportDeath(),this.pingDelay=Math.max(t/2,this.minPingDelay))}};return i.bind(`open`,o),i}isSupported(e){return this.manager.isAlive()&&this.transport.isSupported(e)}}let $e={decodeMessage:function(e){try{var t=JSON.parse(e.data),n=t.data;if(typeof n==`string`)try{n=JSON.parse(t.data)}catch{}var r={event:t.event,channel:t.channel,data:n};return t.user_id&&(r.user_id=t.user_id),r}catch(t){throw{type:`MessageParseError`,error:t,data:e.data}}},encodeMessage:function(e){return JSON.stringify(e)},processHandshake:function(e){var t=$e.decodeMessage(e);if(t.event===`pusher:connection_established`){if(!t.data.activity_timeout)throw`No activity timeout specified in handshake`;return{action:`connected`,id:t.data.socket_id,activityTimeout:t.data.activity_timeout*1e3}}else if(t.event===`pusher:error`)return{action:this.getCloseAction(t.data),error:this.getCloseError(t.data)};else throw`Invalid handshake`},getCloseAction:function(e){return e.code<4e3?e.code>=1002&&e.code<=1004?`backoff`:null:e.code===4e3?`tls_only`:e.code<4100?`refused`:e.code<4200?`backoff`:e.code<4300?`retry`:`refused`},getCloseError:function(e){return e.code!==1e3&&e.code!==1001?{type:`PusherError`,data:{code:e.code,message:e.reason||e.message}}:null}};var et=$e;class tt extends M{constructor(e,t){super(),this.id=e,this.transport=t,this.activityTimeout=t.activityTimeout,this.bindListeners()}handlesActivityChecks(){return this.transport.handlesActivityChecks()}send(e){return this.transport.send(e)}send_event(e,t,n){var r={event:e,data:t};return n&&(r.channel=n),j.debug(`Event sent`,r),this.send(et.encodeMessage(r))}ping(){this.transport.supportsPing()?this.transport.ping():this.send_event(`pusher:ping`,{})}close(){this.transport.close()}bindListeners(){var e={message:e=>{var t;try{t=et.decodeMessage(e)}catch(t){this.emit(`error`,{type:`MessageParseError`,error:t,data:e.data})}if(t!==void 0){switch(j.debug(`Event recd`,t),t.event){case`pusher:error`:this.emit(`error`,{type:`PusherError`,data:t.data});break;case`pusher:ping`:this.emit(`ping`);break;case`pusher:pong`:this.emit(`pong`);break}this.emit(`message`,t)}},activity:()=>{this.emit(`activity`)},error:e=>{this.emit(`error`,e)},closed:e=>{t(),e&&e.code&&this.handleCloseEvent(e),this.transport=null,this.emit(`closed`)}},t=()=>{A(e,(e,t)=>{this.transport.unbind(t,e)})};A(e,(e,t)=>{this.transport.bind(t,e)})}handleCloseEvent(e){var t=et.getCloseAction(e),n=et.getCloseError(e);n&&this.emit(`error`,n),t&&this.emit(t,{action:t,error:n})}}class nt{constructor(e,t){this.transport=e,this.callback=t,this.bindListeners()}close(){this.unbindListeners(),this.transport.close()}bindListeners(){this.onMessage=e=>{this.unbindListeners();var t;try{t=et.processHandshake(e)}catch(e){this.finish(`error`,{error:e}),this.transport.close();return}t.action===`connected`?this.finish(`connected`,{connection:new tt(t.id,this.transport),activityTimeout:t.activityTimeout}):(this.finish(t.action,{error:t.error}),this.transport.close())},this.onClosed=e=>{this.unbindListeners();var t=et.getCloseAction(e)||`backoff`,n=et.getCloseError(e);this.finish(t,{error:n})},this.transport.bind(`message`,this.onMessage),this.transport.bind(`closed`,this.onClosed)}unbindListeners(){this.transport.unbind(`message`,this.onMessage),this.transport.unbind(`closed`,this.onClosed)}finish(e,t){this.callback(k({transport:this.transport,action:e},t))}}class rt{constructor(e,t){this.timeline=e,this.options=t||{}}send(e,t){this.timeline.isEmpty()||this.timeline.send(I.TimelineTransport.getAgent(this,e),t)}}class it extends M{constructor(e,t){super(function(t,n){j.debug(`No callbacks on `+e+` for `+t)}),this.name=e,this.pusher=t,this.subscribed=!1,this.subscriptionPending=!1,this.subscriptionCancelled=!1}authorize(e,t){return t(null,{auth:``})}trigger(e,t){if(e.indexOf(`client-`)!==0)throw new f(`Event '`+e+`' does not start with 'client-'`);if(!this.subscribed){var n=u.buildLogSuffix(`triggeringClientEvents`);j.warn(`Client event triggered before channel 'subscription_succeeded' event . ${n}`)}return this.pusher.send_event(e,t,this.name)}disconnect(){this.subscribed=!1,this.subscriptionPending=!1}handleEvent(e){var t=e.event,n=e.data;t===`pusher_internal:subscription_succeeded`?this.handleSubscriptionSucceededEvent(e):t===`pusher_internal:subscription_count`?this.handleSubscriptionCountEvent(e):t.indexOf(`pusher_internal:`)!==0&&this.emit(t,n,{})}handleSubscriptionSucceededEvent(e){this.subscriptionPending=!1,this.subscribed=!0,this.subscriptionCancelled?this.pusher.unsubscribe(this.name):this.emit(`pusher:subscription_succeeded`,e.data)}handleSubscriptionCountEvent(e){e.data.subscription_count&&(this.subscriptionCount=e.data.subscription_count),this.emit(`pusher:subscription_count`,e.data)}subscribe(){this.subscribed||(this.subscriptionPending=!0,this.subscriptionCancelled=!1,this.authorize(this.pusher.connection.socket_id,(e,t)=>{e?(this.subscriptionPending=!1,j.error(e.toString()),this.emit(`pusher:subscription_error`,Object.assign({},{type:`AuthError`,error:e.message},e instanceof b?{status:e.status}:{}))):this.pusher.send_event(`pusher:subscribe`,{auth:t.auth,channel_data:t.channel_data,channel:this.name})}))}unsubscribe(){this.subscribed=!1,this.pusher.send_event(`pusher:unsubscribe`,{channel:this.name})}cancelSubscription(){this.subscriptionCancelled=!0}reinstateSubscription(){this.subscriptionCancelled=!1}}class at extends it{authorize(e,t){return this.pusher.config.channelAuthorizer({channelName:this.name,socketId:e},t)}}class ot{constructor(){this.reset()}get(e){return Object.prototype.hasOwnProperty.call(this.members,e)?{id:e,info:this.members[e]}:null}each(e){A(this.members,(t,n)=>{e(this.get(n))})}setMyID(e){this.myID=e}onSubscription(e){this.members=e.presence.hash,this.count=e.presence.count,this.me=this.get(this.myID)}addMember(e){return this.get(e.user_id)===null&&this.count++,this.members[e.user_id]=e.user_info,this.get(e.user_id)}removeMember(e){var t=this.get(e.user_id);return t&&(delete this.members[e.user_id],this.count--),t}reset(){this.members={},this.count=0,this.myID=null,this.me=null}}var st=function(e,t,n,r){function i(e){return e instanceof n?e:new n(function(t){t(e)})}return new(n||=Promise)(function(n,a){function o(e){try{c(r.next(e))}catch(e){a(e)}}function s(e){try{c(r.throw(e))}catch(e){a(e)}}function c(e){e.done?n(e.value):i(e.value).then(o,s)}c((r=r.apply(e,t||[])).next())})};class ct extends at{constructor(e,t){super(e,t),this.members=new ot}authorize(e,t){super.authorize(e,(e,n)=>st(this,void 0,void 0,function*(){if(!e)if(n=n,n.channel_data!=null){var r=JSON.parse(n.channel_data);this.members.setMyID(r.user_id)}else if(yield this.pusher.user.signinDonePromise,this.pusher.user.user_data!=null)this.members.setMyID(this.pusher.user.user_data.id);else{let e=u.buildLogSuffix(`authorizationEndpoint`);j.error(`Invalid auth response for channel '${this.name}', expected 'channel_data' field. ${e}, or the user should be signed in.`),t(`Invalid auth response`);return}t(e,n)}))}handleEvent(e){var t=e.event;if(t.indexOf(`pusher_internal:`)===0)this.handleInternalEvent(e);else{var n=e.data,r={};e.user_id&&(r.user_id=e.user_id),this.emit(t,n,r)}}handleInternalEvent(e){var t=e.event,n=e.data;switch(t){case`pusher_internal:subscription_succeeded`:this.handleSubscriptionSucceededEvent(e);break;case`pusher_internal:subscription_count`:this.handleSubscriptionCountEvent(e);break;case`pusher_internal:member_added`:var r=this.members.addMember(n);this.emit(`pusher:member_added`,r);break;case`pusher_internal:member_removed`:var i=this.members.removeMember(n);i&&this.emit(`pusher:member_removed`,i);break}}handleSubscriptionSucceededEvent(e){this.subscriptionPending=!1,this.subscribed=!0,this.subscriptionCancelled?this.pusher.unsubscribe(this.name):(this.members.onSubscription(e.data),this.emit(`pusher:subscription_succeeded`,this.members))}disconnect(){this.members.reset(),super.disconnect()}}var lt=n(1),ut=n(0);class dt extends at{constructor(e,t,n){super(e,t),this.key=null,this.nacl=n}authorize(e,t){super.authorize(e,(e,n)=>{if(e){t(e,n);return}let r=n.shared_secret;if(!r){t(Error(`No shared_secret key in auth payload for encrypted channel: ${this.name}`),null);return}this.key=(0,ut.decode)(r),delete n.shared_secret,t(null,n)})}trigger(e,t){throw new _(`Client events are not currently supported for encrypted channels`)}handleEvent(e){var t=e.event,n=e.data;if(t.indexOf(`pusher_internal:`)===0||t.indexOf(`pusher:`)===0){super.handleEvent(e);return}this.handleEncryptedEvent(t,n)}handleEncryptedEvent(e,t){if(!this.key){j.debug(`Received encrypted event before key has been retrieved from the authEndpoint`);return}if(!t.ciphertext||!t.nonce){j.error(\"Unexpected format for encrypted event, expected object with `ciphertext` and `nonce` fields, got: \"+t);return}let n=(0,ut.decode)(t.ciphertext);if(n.length<this.nacl.secretbox.overheadLength){j.error(`Expected encrypted event ciphertext length to be ${this.nacl.secretbox.overheadLength}, got: ${n.length}`);return}let r=(0,ut.decode)(t.nonce);if(r.length<this.nacl.secretbox.nonceLength){j.error(`Expected encrypted event nonce length to be ${this.nacl.secretbox.nonceLength}, got: ${r.length}`);return}let i=this.nacl.secretbox.open(n,r,this.key);if(i===null){j.debug(`Failed to decrypt an event, probably because it was encrypted with a different key. Fetching a new key from the authEndpoint...`),this.authorize(this.pusher.connection.socket_id,(t,a)=>{if(t){j.error(`Failed to make a request to the authEndpoint: ${a}. Unable to fetch new key, so dropping encrypted event`);return}if(i=this.nacl.secretbox.open(n,r,this.key),i===null){j.error(`Failed to decrypt event with new key. Dropping encrypted event`);return}this.emit(e,this.getDataToEmit(i))});return}this.emit(e,this.getDataToEmit(i))}getDataToEmit(e){let t=(0,lt.decode)(e);try{return JSON.parse(t)}catch{return t}}}class ft extends M{constructor(e,t){super(),this.state=`initialized`,this.connection=null,this.key=e,this.options=t,this.timeline=this.options.timeline,this.usingTLS=this.options.useTLS,this.errorCallbacks=this.buildErrorCallbacks(),this.connectionCallbacks=this.buildConnectionCallbacks(this.errorCallbacks),this.handshakeCallbacks=this.buildHandshakeCallbacks(this.errorCallbacks);var n=I.getNetwork();n.bind(`online`,()=>{this.timeline.info({netinfo:`online`}),(this.state===`connecting`||this.state===`unavailable`)&&this.retryIn(0)}),n.bind(`offline`,()=>{this.timeline.info({netinfo:`offline`}),this.connection&&this.sendActivityCheck()}),this.updateStrategy()}connect(){if(!(this.connection||this.runner)){if(!this.strategy.isSupported()){this.updateState(`failed`);return}this.updateState(`connecting`),this.startConnecting(),this.setUnavailableTimer()}}send(e){return this.connection?this.connection.send(e):!1}send_event(e,t,n){return this.connection?this.connection.send_event(e,t,n):!1}disconnect(){this.disconnectInternally(),this.updateState(`disconnected`)}isUsingTLS(){return this.usingTLS}startConnecting(){var e=(t,n)=>{t?this.runner=this.strategy.connect(0,e):n.action===`error`?(this.emit(`error`,{type:`HandshakeError`,error:n.error}),this.timeline.error({handshakeError:n.error})):(this.abortConnecting(),this.handshakeCallbacks[n.action](n))};this.runner=this.strategy.connect(0,e)}abortConnecting(){this.runner&&=(this.runner.abort(),null)}disconnectInternally(){this.abortConnecting(),this.clearRetryTimer(),this.clearUnavailableTimer(),this.connection&&this.abandonConnection().close()}updateStrategy(){this.strategy=this.options.getStrategy({key:this.key,timeline:this.timeline,useTLS:this.usingTLS})}retryIn(e){this.timeline.info({action:`retry`,delay:e}),e>0&&this.emit(`connecting_in`,Math.round(e/1e3)),this.retryTimer=new ce(e||0,()=>{this.disconnectInternally(),this.connect()})}clearRetryTimer(){this.retryTimer&&=(this.retryTimer.ensureAborted(),null)}setUnavailableTimer(){this.unavailableTimer=new ce(this.options.unavailableTimeout,()=>{this.updateState(`unavailable`)})}clearUnavailableTimer(){this.unavailableTimer&&this.unavailableTimer.ensureAborted()}sendActivityCheck(){this.stopActivityCheck(),this.connection.ping(),this.activityTimer=new ce(this.options.pongTimeout,()=>{this.timeline.error({pong_timed_out:this.options.pongTimeout}),this.retryIn(0)})}resetActivityCheck(){this.stopActivityCheck(),this.connection&&!this.connection.handlesActivityChecks()&&(this.activityTimer=new ce(this.activityTimeout,()=>{this.sendActivityCheck()}))}stopActivityCheck(){this.activityTimer&&this.activityTimer.ensureAborted()}buildConnectionCallbacks(e){return k({},e,{message:e=>{this.resetActivityCheck(),this.emit(`message`,e)},ping:()=>{this.send_event(`pusher:pong`,{})},activity:()=>{this.resetActivityCheck()},error:e=>{this.emit(`error`,e)},closed:()=>{this.abandonConnection(),this.shouldRetry()&&this.retryIn(1e3)}})}buildHandshakeCallbacks(e){return k({},e,{connected:e=>{this.activityTimeout=Math.min(this.options.activityTimeout,e.activityTimeout,e.connection.activityTimeout||1/0),this.clearUnavailableTimer(),this.setConnection(e.connection),this.socket_id=this.connection.id,this.updateState(`connected`,{socket_id:this.socket_id})}})}buildErrorCallbacks(){let e=e=>t=>{t.error&&this.emit(`error`,{type:`WebSocketError`,error:t.error}),e(t)};return{tls_only:e(()=>{this.usingTLS=!0,this.updateStrategy(),this.retryIn(0)}),refused:e(()=>{this.disconnect()}),backoff:e(()=>{this.retryIn(1e3)}),retry:e(()=>{this.retryIn(0)})}}setConnection(e){for(var t in this.connection=e,this.connectionCallbacks)this.connection.bind(t,this.connectionCallbacks[t]);this.resetActivityCheck()}abandonConnection(){if(this.connection){for(var e in this.stopActivityCheck(),this.connectionCallbacks)this.connection.unbind(e,this.connectionCallbacks[e]);var t=this.connection;return this.connection=null,t}}updateState(e,t){var n=this.state;if(this.state=e,n!==e){var r=e;r===`connected`&&(r+=` with new socket ID `+t.socket_id),j.debug(`State changed`,n+` -> `+r),this.timeline.info({state:e,params:t}),this.emit(`state_change`,{previous:n,current:e}),this.emit(e,t)}}shouldRetry(){return this.state===`connecting`||this.state===`connected`}}class pt{constructor(){this.channels={}}add(e,t){return this.channels[e]||(this.channels[e]=mt(e,t)),this.channels[e]}all(){return pe(this.channels)}find(e){return this.channels[e]}remove(e){var t=this.channels[e];return delete this.channels[e],t}disconnect(){A(this.channels,function(e){e.disconnect()})}}function mt(e,t){if(e.indexOf(`private-encrypted-`)===0){if(t.config.nacl)return P.createEncryptedChannel(e,t,t.config.nacl);throw new _(`Tried to subscribe to a private-encrypted- channel but no nacl implementation available. ${u.buildLogSuffix(`encryptedChannelSupport`)}`)}else if(e.indexOf(`private-`)===0)return P.createPrivateChannel(e,t);else if(e.indexOf(`presence-`)===0)return P.createPresenceChannel(e,t);else if(e.indexOf(`#`)===0)throw new p(`Cannot create a channel with name \"`+e+`\".`);else return P.createChannel(e,t)}var P={createChannels(){return new pt},createConnectionManager(e,t){return new ft(e,t)},createChannel(e,t){return new it(e,t)},createPrivateChannel(e,t){return new at(e,t)},createPresenceChannel(e,t){return new ct(e,t)},createEncryptedChannel(e,t,n){return new dt(e,t,n)},createTimelineSender(e,t){return new rt(e,t)},createHandshake(e,t){return new nt(e,t)},createAssistantToTheTransportManager(e,t,n){return new Qe(e,t,n)}};class ht{constructor(e){this.options=e||{},this.livesLeft=this.options.lives||1/0}getAssistant(e){return P.createAssistantToTheTransportManager(this,e,{minPingDelay:this.options.minPingDelay,maxPingDelay:this.options.maxPingDelay})}isAlive(){return this.livesLeft>0}reportDeath(){--this.livesLeft}}class gt{constructor(e,t){this.strategies=e,this.loop=!!t.loop,this.failFast=!!t.failFast,this.timeout=t.timeout,this.timeoutLimit=t.timeoutLimit}isSupported(){return be(this.strategies,O.method(`isSupported`))}connect(e,t){var n=this.strategies,r=0,i=this.timeout,a=null,o=(s,c)=>{c?t(null,c):(r+=1,this.loop&&(r%=n.length),r<n.length?(i&&(i*=2,this.timeoutLimit&&(i=Math.min(i,this.timeoutLimit))),a=this.tryStrategy(n[r],e,{timeout:i,failFast:this.failFast},o)):t(!0))};return a=this.tryStrategy(n[r],e,{timeout:i,failFast:this.failFast},o),{abort:function(){a.abort()},forceMinPriority:function(t){e=t,a&&a.forceMinPriority(t)}}}tryStrategy(e,t,n,r){var i=null,a=null;return n.timeout>0&&(i=new ce(n.timeout,function(){a.abort(),r(!0)})),a=e.connect(t,function(e,t){e&&i&&i.isRunning()&&!n.failFast||(i&&i.ensureAborted(),r(e,t))}),{abort:function(){i&&i.ensureAborted(),a.abort()},forceMinPriority:function(e){a.forceMinPriority(e)}}}}class _t{constructor(e){this.strategies=e}isSupported(){return be(this.strategies,O.method(`isSupported`))}connect(e,t){return vt(this.strategies,e,function(e,n){return function(r,i){if(n[e].error=r,r){yt(n)&&t(!0);return}me(n,function(e){e.forceMinPriority(i.transport.priority)}),t(null,i)}})}}function vt(e,t,n){var r=he(e,function(e,r,i,a){return e.connect(t,n(r,a))});return{abort:function(){me(r,bt)},forceMinPriority:function(e){me(r,function(t){t.forceMinPriority(e)})}}}function yt(e){return xe(e,function(e){return!!e.error})}function bt(e){!e.error&&!e.aborted&&(e.abort(),e.aborted=!0)}class xt{constructor(e,t,n){this.strategy=e,this.transports=t,this.ttl=n.ttl||1800*1e3,this.usingTLS=n.useTLS,this.timeline=n.timeline}isSupported(){return this.strategy.isSupported()}connect(e,t){var n=this.usingTLS,r=Ct(n),i=r&&r.cacheSkipCount?r.cacheSkipCount:0,a=[this.strategy];if(r&&r.timestamp+this.ttl>=O.now()){var o=this.transports[r.transport];o&&([`ws`,`wss`].includes(r.transport)||i>3?(this.timeline.info({cached:!0,transport:r.transport,latency:r.latency}),a.push(new gt([o],{timeout:r.latency*2+1e3,failFast:!0}))):i++)}var s=O.now(),c=a.pop().connect(e,function r(o,l){o?(Tt(n),a.length>0?(s=O.now(),c=a.pop().connect(e,r)):t(o)):(wt(n,l.transport.name,O.now()-s,i),t(null,l))});return{abort:function(){c.abort()},forceMinPriority:function(t){e=t,c&&c.forceMinPriority(t)}}}}function St(e){return`pusherTransport`+(e?`TLS`:`NonTLS`)}function Ct(e){var t=I.getLocalStorage();if(t)try{var n=t[St(e)];if(n)return JSON.parse(n)}catch{Tt(e)}return null}function wt(e,t,n,r){var i=I.getLocalStorage();if(i)try{i[St(e)]=Te({timestamp:O.now(),transport:t,latency:n,cacheSkipCount:r})}catch{}}function Tt(e){var t=I.getLocalStorage();if(t)try{delete t[St(e)]}catch{}}class Et{constructor(e,{delay:t}){this.strategy=e,this.options={delay:t}}isSupported(){return this.strategy.isSupported()}connect(e,t){var n=this.strategy,r,i=new ce(this.options.delay,function(){r=n.connect(e,t)});return{abort:function(){i.ensureAborted(),r&&r.abort()},forceMinPriority:function(t){e=t,r&&r.forceMinPriority(t)}}}}class Dt{constructor(e,t,n){this.test=e,this.trueBranch=t,this.falseBranch=n}isSupported(){return(this.test()?this.trueBranch:this.falseBranch).isSupported()}connect(e,t){return(this.test()?this.trueBranch:this.falseBranch).connect(e,t)}}class Ot{constructor(e){this.strategy=e}isSupported(){return this.strategy.isSupported()}connect(e,t){var n=this.strategy.connect(e,function(e,r){r&&n.abort(),t(e,r)});return n}}function kt(e){return function(){return e.isSupported()}}var At=function(e,t,n){var r={};function i(t,i,a,o,s){var c=n(e,t,i,a,o,s);return r[t]=c,c}var a=Object.assign({},t,{hostNonTLS:e.wsHost+`:`+e.wsPort,hostTLS:e.wsHost+`:`+e.wssPort,httpPath:e.wsPath}),o=Object.assign({},a,{useTLS:!0}),s=Object.assign({},t,{hostNonTLS:e.httpHost+`:`+e.httpPort,hostTLS:e.httpHost+`:`+e.httpsPort,httpPath:e.httpPath}),c={loop:!0,timeout:15e3,timeoutLimit:6e4},l=new ht({minPingDelay:1e4,maxPingDelay:e.activityTimeout}),u=new ht({lives:2,minPingDelay:1e4,maxPingDelay:e.activityTimeout}),d=i(`ws`,`ws`,3,a,l),f=i(`wss`,`ws`,3,o,l),p=i(`sockjs`,`sockjs`,1,s),m=i(`xhr_streaming`,`xhr_streaming`,1,s,u),h=i(`xdr_streaming`,`xdr_streaming`,1,s,u),g=i(`xhr_polling`,`xhr_polling`,1,s),_=i(`xdr_polling`,`xdr_polling`,1,s),v=new gt([d],c),y=new gt([f],c),b=new gt([p],c),x=new gt([new Dt(kt(m),m,h)],c),ee=new gt([new Dt(kt(g),g,_)],c),S=new gt([new Dt(kt(x),new _t([x,new Et(ee,{delay:4e3})]),ee)],c),C=new Dt(kt(S),S,b),w=t.useTLS?new _t([v,new Et(C,{delay:2e3})]):new _t([v,new Et(y,{delay:2e3}),new Et(C,{delay:5e3})]);return new xt(new Ot(new Dt(kt(d),w,C)),r,{ttl:18e5,timeline:t.timeline,useTLS:t.useTLS})},jt=(function(){var e=this;e.timeline.info(e.buildTimelineMessage({transport:e.name+(e.options.useTLS?`s`:``)})),e.hooks.isInitialized()?e.changeState(`initialized`):e.hooks.file?(e.changeState(`initializing`),c.load(e.hooks.file,{useTLS:e.options.useTLS},function(t,n){e.hooks.isInitialized()?(e.changeState(`initialized`),n(!0)):(t&&e.onError(t),e.onClose(),n(!1))})):e.onClose()}),Mt={getRequest:function(e){var t=new window.XDomainRequest;return t.ontimeout=function(){e.emit(`error`,new m),e.close()},t.onerror=function(t){e.emit(`error`,t),e.close()},t.onprogress=function(){t.responseText&&t.responseText.length>0&&e.onChunk(200,t.responseText)},t.onload=function(){t.responseText&&t.responseText.length>0&&e.onChunk(200,t.responseText),e.emit(`finished`,200),e.close()},t},abortRequest:function(e){e.ontimeout=e.onerror=e.onprogress=e.onload=null,e.abort()}};class Nt extends M{constructor(e,t,n){super(),this.hooks=e,this.method=t,this.url=n}start(e){this.position=0,this.xhr=this.hooks.getRequest(this),this.unloader=()=>{this.close()},I.addUnloadListener(this.unloader),this.xhr.open(this.method,this.url,!0),this.xhr.setRequestHeader&&this.xhr.setRequestHeader(`Content-Type`,`application/json`),this.xhr.send(e)}close(){this.unloader&&=(I.removeUnloadListener(this.unloader),null),this.xhr&&=(this.hooks.abortRequest(this.xhr),null)}onChunk(e,t){for(;;){var n=this.advanceBuffer(t);if(n)this.emit(`chunk`,{status:e,data:n});else break}this.isBufferTooLong(t)&&this.emit(`buffer_too_long`)}advanceBuffer(e){var t=e.slice(this.position),n=t.indexOf(`\n`);return n===-1?null:(this.position+=n+1,t.slice(0,n))}isBufferTooLong(e){return this.position===e.length&&e.length>262144}}var Pt;(function(e){e[e.CONNECTING=0]=`CONNECTING`,e[e.OPEN=1]=`OPEN`,e[e.CLOSED=3]=`CLOSED`})(Pt||={});var F=Pt,Ft=1;class It{constructor(e,t){this.hooks=e,this.session=Vt(1e3)+`/`+Ht(8),this.location=Lt(t),this.readyState=F.CONNECTING,this.openStream()}send(e){return this.sendRaw(JSON.stringify([e]))}ping(){this.hooks.sendHeartbeat(this)}close(e,t){this.onClose(e,t,!0)}sendRaw(e){if(this.readyState===F.OPEN)try{return I.createSocketRequest(`POST`,zt(Rt(this.location,this.session))).start(e),!0}catch{return!1}else return!1}reconnect(){this.closeStream(),this.openStream()}onClose(e,t,n){this.closeStream(),this.readyState=F.CLOSED,this.onclose&&this.onclose({code:e,reason:t,wasClean:n})}onChunk(e){if(e.status===200){this.readyState===F.OPEN&&this.onActivity();var t;switch(e.data.slice(0,1)){case`o`:t=JSON.parse(e.data.slice(1)||`{}`),this.onOpen(t);break;case`a`:t=JSON.parse(e.data.slice(1)||`[]`);for(var n=0;n<t.length;n++)this.onEvent(t[n]);break;case`m`:t=JSON.parse(e.data.slice(1)||`null`),this.onEvent(t);break;case`h`:this.hooks.onHeartbeat(this);break;case`c`:t=JSON.parse(e.data.slice(1)||`[]`),this.onClose(t[0],t[1],!0);break}}}onOpen(e){this.readyState===F.CONNECTING?(e&&e.hostname&&(this.location.base=Bt(this.location.base,e.hostname)),this.readyState=F.OPEN,this.onopen&&this.onopen()):this.onClose(1006,`Server lost session`,!0)}onEvent(e){this.readyState===F.OPEN&&this.onmessage&&this.onmessage({data:e})}onActivity(){this.onactivity&&this.onactivity()}onError(e){this.onerror&&this.onerror(e)}openStream(){this.stream=I.createSocketRequest(`POST`,zt(this.hooks.getReceiveURL(this.location,this.session))),this.stream.bind(`chunk`,e=>{this.onChunk(e)}),this.stream.bind(`finished`,e=>{this.hooks.onFinished(this,e)}),this.stream.bind(`buffer_too_long`,()=>{this.reconnect()});try{this.stream.start()}catch(e){O.defer(()=>{this.onError(e),this.onClose(1006,`Could not start streaming`,!1)})}}closeStream(){this.stream&&=(this.stream.unbind_all(),this.stream.close(),null)}}function Lt(e){var t=/([^\\?]*)\\/*(\\??.*)/.exec(e);return{base:t[1],queryString:t[2]}}function Rt(e,t){return e.base+`/`+t+`/xhr_send`}function zt(e){return e+(e.indexOf(`?`)===-1?`?`:`&`)+`t=`+ +new Date+`&n=`+ Ft++}function Bt(e,t){var n=/(https?:\\/\\/)([^\\/:]+)((\\/|:)?.*)/.exec(e);return n[1]+t+n[3]}function Vt(e){return I.randomInt(e)}function Ht(e){for(var t=[],n=0;n<e;n++)t.push(Vt(32).toString(32));return t.join(``)}var Ut=It,Wt={getReceiveURL:function(e,t){return e.base+`/`+t+`/xhr_streaming`+e.queryString},onHeartbeat:function(e){e.sendRaw(`[]`)},sendHeartbeat:function(e){e.sendRaw(`[]`)},onFinished:function(e,t){e.onClose(1006,`Connection interrupted (`+t+`)`,!1)}},Gt={getReceiveURL:function(e,t){return e.base+`/`+t+`/xhr`+e.queryString},onHeartbeat:function(){},sendHeartbeat:function(e){e.sendRaw(`[]`)},onFinished:function(e,t){t===200?e.reconnect():e.onClose(1006,`Connection interrupted (`+t+`)`,!1)}},Kt={getRequest:function(e){var t=new(I.getXHRAPI());return t.onreadystatechange=t.onprogress=function(){switch(t.readyState){case 3:t.responseText&&t.responseText.length>0&&e.onChunk(t.status,t.responseText);break;case 4:t.responseText&&t.responseText.length>0&&e.onChunk(t.status,t.responseText),e.emit(`finished`,t.status),e.close();break}},t},abortRequest:function(e){e.onreadystatechange=null,e.abort()}},qt={createStreamingSocket(e){return this.createSocket(Wt,e)},createPollingSocket(e){return this.createSocket(Gt,e)},createSocket(e,t){return new Ut(e,t)},createXHR(e,t){return this.createRequest(Kt,e,t)},createRequest(e,t,n){return new Nt(e,t,n)}};qt.createXDR=function(e,t){return this.createRequest(Mt,e,t)};var I={nextAuthCallbackID:1,auth_callbacks:{},ScriptReceivers:i,DependenciesReceivers:s,getDefaultStrategy:At,Transports:Ye,transportConnectionInitializer:jt,HTTPFactory:qt,TimelineTransport:Ae,getXHRAPI(){return window.XMLHttpRequest},getWebSocketAPI(){return window.WebSocket||window.MozWebSocket},setup(e){window.Pusher=e;var t=()=>{this.onDocumentBody(e.ready)};window.JSON?t():c.load(`json2`,{},t)},getDocument(){return document},getProtocol(){return this.getDocument().location.protocol},getAuthorizers(){return{ajax:x,jsonp:De}},onDocumentBody(e){document.body?e():setTimeout(()=>{this.onDocumentBody(e)},0)},createJSONPRequest(e,t){return new ke(e,t)},createScriptRequest(e){return new Oe(e)},getLocalStorage(){try{return window.localStorage}catch{return}},createXHR(){return this.getXHRAPI()?this.createXMLHttpRequest():this.createMicrosoftXHR()},createXMLHttpRequest(){return new(this.getXHRAPI())},createMicrosoftXHR(){return new ActiveXObject(`Microsoft.XMLHTTP`)},getNetwork(){return Ze},createWebSocket(e){return new(this.getWebSocketAPI())(e)},createSocketRequest(e,t){if(this.isXHRSupported())return this.HTTPFactory.createXHR(e,t);if(this.isXDRSupported(t.indexOf(`https:`)===0))return this.HTTPFactory.createXDR(e,t);throw`Cross-origin HTTP requests are not supported`},isXHRSupported(){var e=this.getXHRAPI();return!!e&&new e().withCredentials!==void 0},isXDRSupported(e){var t=e?`https:`:`http:`,n=this.getProtocol();return!!window.XDomainRequest&&n===t},addUnloadListener(e){window.addEventListener===void 0?window.attachEvent!==void 0&&window.attachEvent(`onunload`,e):window.addEventListener(`unload`,e,!1)},removeUnloadListener(e){window.addEventListener===void 0?window.detachEvent!==void 0&&window.detachEvent(`onunload`,e):window.removeEventListener(`unload`,e,!1)},randomInt(e){return Math.floor(function(){return(window.crypto||window.msCrypto).getRandomValues(new Uint32Array(1))[0]/2**32}()*e)}},Jt;(function(e){e[e.ERROR=3]=`ERROR`,e[e.INFO=6]=`INFO`,e[e.DEBUG=7]=`DEBUG`})(Jt||={});var Yt=Jt;class Xt{constructor(e,t,n){this.key=e,this.session=t,this.events=[],this.options=n||{},this.sent=0,this.uniqueID=0}log(e,t){e<=this.options.level&&(this.events.push(k({},t,{timestamp:O.now()})),this.options.limit&&this.events.length>this.options.limit&&this.events.shift())}error(e){this.log(Yt.ERROR,e)}info(e){this.log(Yt.INFO,e)}debug(e){this.log(Yt.DEBUG,e)}isEmpty(){return this.events.length===0}send(e,t){var n=k({session:this.session,bundle:this.sent+1,key:this.key,lib:`js`,version:this.options.version,cluster:this.options.cluster,features:this.options.features,timeline:this.events},this.options.params);return this.events=[],e(n,(e,n)=>{e||this.sent++,t&&t(e,n)}),!0}generateUniqueID(){return this.uniqueID++,this.uniqueID}}class Zt{constructor(e,t,n,r){this.name=e,this.priority=t,this.transport=n,this.options=r||{}}isSupported(){return this.transport.isSupported({useTLS:this.options.useTLS})}connect(e,t){if(!this.isSupported())return L(new y,t);if(this.priority<e)return L(new h,t);var n=!1,r=this.transport.createConnection(this.name,this.priority,this.options.key,this.options),i=null,a=function(){r.unbind(`initialized`,a),r.connect()},o=function(){i=P.createHandshake(r,function(e){n=!0,l(),t(null,e)})},s=function(e){l(),t(e)},c=function(){l(),t(new g(Te(r)))},l=function(){r.unbind(`initialized`,a),r.unbind(`open`,o),r.unbind(`error`,s),r.unbind(`closed`,c)};return r.bind(`initialized`,a),r.bind(`open`,o),r.bind(`error`,s),r.bind(`closed`,c),r.initialize(),{abort:()=>{n||(l(),i?i.close():r.close())},forceMinPriority:e=>{n||this.priority<e&&(i?i.close():r.close())}}}}function L(e,t){return O.defer(function(){t(e)}),{abort:function(){},forceMinPriority:function(){}}}let{Transports:Qt}=I;var $t=function(e,t,n,r,i,a){var o=Qt[n];if(!o)throw new v(n);var s=(!e.enabledTransports||de(e.enabledTransports,t)!==-1)&&(!e.disabledTransports||de(e.disabledTransports,t)===-1),c;return s?(i=Object.assign({ignoreNullOrigin:e.ignoreNullOrigin},i),c=new Zt(t,r,a?a.getAssistant(o):o,i)):c=en,c},en={isSupported:function(){return!1},connect:function(e,t){var n=O.defer(function(){t(new y)});return{abort:function(){n.ensureAborted()},forceMinPriority:function(){}}}};function tn(e){if(e==null)throw`You must pass an options object`;if(e.cluster==null)throw`Options object must provide a cluster`;`disableStats`in e&&j.warn(`The disableStats option is deprecated in favor of enableStats`)}let nn=(e,t)=>{var n=`socket_id=`+encodeURIComponent(e.socketId);for(var r in t.params)n+=`&`+encodeURIComponent(r)+`=`+encodeURIComponent(t.params[r]);if(t.paramsProvider!=null){let e=t.paramsProvider();for(var r in e)n+=`&`+encodeURIComponent(r)+`=`+encodeURIComponent(e[r])}return n};var rn=e=>{if(I.getAuthorizers()[e.transport]===void 0)throw`'${e.transport}' is not a recognized auth transport`;return(t,n)=>{let r=nn(t,e);I.getAuthorizers()[e.transport](I,r,e,d.UserAuthentication,n)}};let an=(e,t)=>{var n=`socket_id=`+encodeURIComponent(e.socketId);for(var r in n+=`&channel_name=`+encodeURIComponent(e.channelName),t.params)n+=`&`+encodeURIComponent(r)+`=`+encodeURIComponent(t.params[r]);if(t.paramsProvider!=null){let e=t.paramsProvider();for(var r in e)n+=`&`+encodeURIComponent(r)+`=`+encodeURIComponent(e[r])}return n};var on=e=>{if(I.getAuthorizers()[e.transport]===void 0)throw`'${e.transport}' is not a recognized auth transport`;return(t,n)=>{let r=an(t,e);I.getAuthorizers()[e.transport](I,r,e,d.ChannelAuthorization,n)}};let sn=(e,t,n)=>{let r={authTransport:t.transport,authEndpoint:t.endpoint,auth:{params:t.params,headers:t.headers}};return(t,i)=>{n(e.channel(t.channelName),r).authorize(t.socketId,i)}};function cn(e,t){let n={activityTimeout:e.activityTimeout||a.activityTimeout,cluster:e.cluster,httpPath:e.httpPath||a.httpPath,httpPort:e.httpPort||a.httpPort,httpsPort:e.httpsPort||a.httpsPort,pongTimeout:e.pongTimeout||a.pongTimeout,statsHost:e.statsHost||a.stats_host,unavailableTimeout:e.unavailableTimeout||a.unavailableTimeout,wsPath:e.wsPath||a.wsPath,wsPort:e.wsPort||a.wsPort,wssPort:e.wssPort||a.wssPort,enableStats:pn(e),httpHost:ln(e),useTLS:fn(e),wsHost:un(e),userAuthenticator:mn(e),channelAuthorizer:gn(e,t)};return`disabledTransports`in e&&(n.disabledTransports=e.disabledTransports),`enabledTransports`in e&&(n.enabledTransports=e.enabledTransports),`ignoreNullOrigin`in e&&(n.ignoreNullOrigin=e.ignoreNullOrigin),`timelineParams`in e&&(n.timelineParams=e.timelineParams),`nacl`in e&&(n.nacl=e.nacl),n}function ln(e){return e.httpHost?e.httpHost:e.cluster?`sockjs-${e.cluster}.pusher.com`:a.httpHost}function un(e){return e.wsHost?e.wsHost:dn(e.cluster)}function dn(e){return`ws-${e}.pusher.com`}function fn(e){return I.getProtocol()===`https:`?!0:e.forceTLS!==!1}function pn(e){return`enableStats`in e?e.enableStats:`disableStats`in e?!e.disableStats:!1}function mn(e){let t=Object.assign(Object.assign({},a.userAuthentication),e.userAuthentication);return`customHandler`in t&&t.customHandler!=null?t.customHandler:rn(t)}function hn(e,t){let n;return`channelAuthorization`in e?n=Object.assign(Object.assign({},a.channelAuthorization),e.channelAuthorization):(n={transport:e.authTransport||a.authTransport,endpoint:e.authEndpoint||a.authEndpoint},`auth`in e&&(`params`in e.auth&&(n.params=e.auth.params),`headers`in e.auth&&(n.headers=e.auth.headers)),`authorizer`in e&&(n.customHandler=sn(t,n,e.authorizer))),n}function gn(e,t){let n=hn(e,t);return`customHandler`in n&&n.customHandler!=null?n.customHandler:on(n)}class _n extends M{constructor(e){super(function(e,t){j.debug(`No callbacks on watchlist events for ${e}`)}),this.pusher=e,this.bindWatchlistInternalEvent()}handleEvent(e){e.data.events.forEach(e=>{this.emit(e.name,e)})}bindWatchlistInternalEvent(){this.pusher.connection.bind(`message`,e=>{e.event===`pusher_internal:watchlist_events`&&this.handleEvent(e)})}}function vn(){let e,t;return{promise:new Promise((n,r)=>{e=n,t=r}),resolve:e,reject:t}}var yn=vn;class bn extends M{constructor(e){super(function(e,t){j.debug(`No callbacks on user for `+e)}),this.signin_requested=!1,this.user_data=null,this.serverToUserChannel=null,this.signinDonePromise=null,this._signinDoneResolve=null,this._onAuthorize=(e,t)=>{if(e){j.warn(`Error during signin: ${e}`),this._cleanup();return}this.pusher.send_event(`pusher:signin`,{auth:t.auth,user_data:t.user_data})},this.pusher=e,this.pusher.connection.bind(`state_change`,({previous:e,current:t})=>{e!==`connected`&&t===`connected`&&this._signin(),e===`connected`&&t!==`connected`&&(this._cleanup(),this._newSigninPromiseIfNeeded())}),this.watchlist=new _n(e),this.pusher.connection.bind(`message`,e=>{e.event===`pusher:signin_success`&&this._onSigninSuccess(e.data),this.serverToUserChannel&&this.serverToUserChannel.name===e.channel&&this.serverToUserChannel.handleEvent(e)})}signin(){this.signin_requested||(this.signin_requested=!0,this._signin())}_signin(){this.signin_requested&&(this._newSigninPromiseIfNeeded(),this.pusher.connection.state===`connected`&&this.pusher.config.userAuthenticator({socketId:this.pusher.connection.socket_id},this._onAuthorize))}_onSigninSuccess(e){try{this.user_data=JSON.parse(e.user_data)}catch{j.error(`Failed parsing user data after signin: ${e.user_data}`),this._cleanup();return}if(typeof this.user_data.id!=`string`||this.user_data.id===``){j.error(`user_data doesn't contain an id. user_data: ${this.user_data}`),this._cleanup();return}this._signinDoneResolve(),this._subscribeChannels()}_subscribeChannels(){this.serverToUserChannel=new it(`#server-to-user-${this.user_data.id}`,this.pusher),this.serverToUserChannel.bind_global((e,t)=>{e.indexOf(`pusher_internal:`)===0||e.indexOf(`pusher:`)===0||this.emit(e,t)}),(e=>{e.subscriptionPending&&e.subscriptionCancelled?e.reinstateSubscription():!e.subscriptionPending&&this.pusher.connection.state===`connected`&&e.subscribe()})(this.serverToUserChannel)}_cleanup(){this.user_data=null,this.serverToUserChannel&&=(this.serverToUserChannel.unbind_all(),this.serverToUserChannel.disconnect(),null),this.signin_requested&&this._signinDoneResolve()}_newSigninPromiseIfNeeded(){if(!this.signin_requested||this.signinDonePromise&&!this.signinDonePromise.done)return;let{promise:e,resolve:t,reject:n}=yn();e.done=!1;let r=()=>{e.done=!0};e.then(r).catch(r),this.signinDonePromise=e,this._signinDoneResolve=t}}class R{static ready(){R.isReady=!0;for(var e=0,t=R.instances.length;e<t;e++)R.instances[e].connect()}static getClientFeatures(){return fe(ve({ws:I.Transports.ws},function(e){return e.isSupported({})}))}constructor(e,t){Sn(e),tn(t),this.key=e,this.config=cn(t,this),this.channels=P.createChannels(),this.global_emitter=new M,this.sessionID=I.randomInt(1e9),this.timeline=new Xt(this.key,this.sessionID,{cluster:this.config.cluster,features:R.getClientFeatures(),params:this.config.timelineParams||{},limit:50,level:Yt.INFO,version:a.VERSION}),this.config.enableStats&&(this.timelineSender=P.createTimelineSender(this.timeline,{host:this.config.statsHost,path:`/timeline/v2/`+I.TimelineTransport.name})),this.connection=P.createConnectionManager(this.key,{getStrategy:e=>I.getDefaultStrategy(this.config,e,$t),timeline:this.timeline,activityTimeout:this.config.activityTimeout,pongTimeout:this.config.pongTimeout,unavailableTimeout:this.config.unavailableTimeout,useTLS:!!this.config.useTLS}),this.connection.bind(`connected`,()=>{this.subscribeAll(),this.timelineSender&&this.timelineSender.send(this.connection.isUsingTLS())}),this.connection.bind(`message`,e=>{var t=e.event.indexOf(`pusher_internal:`)===0;if(e.channel){var n=this.channel(e.channel);n&&n.handleEvent(e)}t||this.global_emitter.emit(e.event,e.data)}),this.connection.bind(`connecting`,()=>{this.channels.disconnect()}),this.connection.bind(`disconnected`,()=>{this.channels.disconnect()}),this.connection.bind(`error`,e=>{j.warn(e)}),R.instances.push(this),this.timeline.info({instances:R.instances.length}),this.user=new bn(this),R.isReady&&this.connect()}channel(e){return this.channels.find(e)}allChannels(){return this.channels.all()}connect(){if(this.connection.connect(),this.timelineSender&&!this.timelineSenderTimer){var e=this.connection.isUsingTLS(),t=this.timelineSender;this.timelineSenderTimer=new le(6e4,function(){t.send(e)})}}disconnect(){this.connection.disconnect(),this.timelineSenderTimer&&=(this.timelineSenderTimer.ensureAborted(),null)}bind(e,t,n){return this.global_emitter.bind(e,t,n),this}unbind(e,t,n){return this.global_emitter.unbind(e,t,n),this}bind_global(e){return this.global_emitter.bind_global(e),this}unbind_global(e){return this.global_emitter.unbind_global(e),this}unbind_all(e){return this.global_emitter.unbind_all(),this}subscribeAll(){for(var e in this.channels.channels)this.channels.channels.hasOwnProperty(e)&&this.subscribe(e)}subscribe(e){var t=this.channels.add(e,this);return t.subscriptionPending&&t.subscriptionCancelled?t.reinstateSubscription():!t.subscriptionPending&&this.connection.state===`connected`&&t.subscribe(),t}unsubscribe(e){var t=this.channels.find(e);t&&t.subscriptionPending?t.cancelSubscription():(t=this.channels.remove(e),t&&t.subscribed&&t.unsubscribe())}send_event(e,t,n){return this.connection.send_event(e,t,n)}shouldUseTLS(){return this.config.useTLS}signin(){this.user.signin()}}R.instances=[],R.isReady=!1,R.logToConsole=!1,R.Runtime=I,R.ScriptReceivers=I.ScriptReceivers,R.DependenciesReceivers=I.DependenciesReceivers,R.auth_callbacks=I.auth_callbacks;var xn=t.default=R;function Sn(e){if(e==null)throw`You must pass your app key when you instantiate Pusher.`}I.setup(R)})])})}))(),1),is=s({FetchEnctype:()=>nc,FetchMethod:()=>Z,FetchRequest:()=>rc,FetchResponse:()=>Zs,FrameElement:()=>q,FrameLoadingStyle:()=>as,FrameRenderer:()=>zc,PageRenderer:()=>Ll,PageSnapshot:()=>Q,StreamActions:()=>lu,StreamElement:()=>uu,StreamSourceElement:()=>du,cache:()=>Kl,config:()=>Y,connectStreamSource:()=>Zl,disconnectStreamSource:()=>Ql,fetch:()=>$s,fetchEnctypeFromString:()=>tc,fetchMethodFromString:()=>ec,isSafe:()=>ic,morphBodyElements:()=>ru,morphChildren:()=>Wc,morphElements:()=>Uc,morphTurboFrameElements:()=>iu,navigator:()=>ql,registerAdapter:()=>Yl,renderStreamMessage:()=>$l,session:()=>$,setConfirmMethod:()=>tu,setFormMode:()=>nu,setProgressBarDelay:()=>eu,start:()=>Jl,visit:()=>Xl});\n/*!\nTurbo 8.0.21\nCopyright © 2026 37signals LLC\n*/\nlet as={eager:`eager`,lazy:`lazy`};var q=class e extends HTMLElement{static delegateConstructor=void 0;loaded=Promise.resolve();static get observedAttributes(){return[`disabled`,`loading`,`src`]}constructor(){super(),this.delegate=new e.delegateConstructor(this)}connectedCallback(){this.delegate.connect()}disconnectedCallback(){this.delegate.disconnect()}reload(){return this.delegate.sourceURLReloaded()}attributeChangedCallback(e){e==`loading`?this.delegate.loadingStyleChanged():e==`src`?this.delegate.sourceURLChanged():e==`disabled`&&this.delegate.disabledChanged()}get src(){return this.getAttribute(`src`)}set src(e){e?this.setAttribute(`src`,e):this.removeAttribute(`src`)}get refresh(){return this.getAttribute(`refresh`)}set refresh(e){e?this.setAttribute(`refresh`,e):this.removeAttribute(`refresh`)}get shouldReloadWithMorph(){return this.src&&this.refresh===`morph`}get loading(){return os(this.getAttribute(`loading`)||``)}set loading(e){e?this.setAttribute(`loading`,e):this.removeAttribute(`loading`)}get disabled(){return this.hasAttribute(`disabled`)}set disabled(e){e?this.setAttribute(`disabled`,``):this.removeAttribute(`disabled`)}get autoscroll(){return this.hasAttribute(`autoscroll`)}set autoscroll(e){e?this.setAttribute(`autoscroll`,``):this.removeAttribute(`autoscroll`)}get complete(){return!this.delegate.isLoading}get isActive(){return this.ownerDocument===document&&!this.isPreview}get isPreview(){return this.ownerDocument?.documentElement?.hasAttribute(`data-turbo-preview`)}};function os(e){switch(e.toLowerCase()){case`lazy`:return as.lazy;default:return as.eager}}let ss={enabled:!0,progressBarDelay:500,unvisitableExtensions:new Set(`.7z,.aac,.apk,.avi,.bmp,.bz2,.css,.csv,.deb,.dmg,.doc,.docx,.exe,.gif,.gz,.heic,.heif,.ico,.iso,.jpeg,.jpg,.js,.json,.m4a,.mkv,.mov,.mp3,.mp4,.mpeg,.mpg,.msi,.ogg,.ogv,.pdf,.pkg,.png,.ppt,.pptx,.rar,.rtf,.svg,.tar,.tif,.tiff,.txt,.wav,.webm,.webp,.wma,.wmv,.xls,.xlsx,.xml,.zip`.split(`,`))};function cs(e){if(e.getAttribute(`data-turbo-eval`)==`false`)return e;{let t=document.createElement(`script`),n=ks();return n&&(t.nonce=n),t.textContent=e.textContent,t.async=!1,ls(t,e),t}}function ls(e,t){for(let{name:n,value:r}of t.attributes)e.setAttribute(n,r)}function us(e){let t=document.createElement(`template`);return t.innerHTML=e,t.content}function J(e,{target:t,cancelable:n,detail:r}={}){let i=new CustomEvent(e,{cancelable:n,bubbles:!0,composed:!0,detail:r});return t&&t.isConnected?t.dispatchEvent(i):document.documentElement.dispatchEvent(i),i}function ds(e){e.preventDefault(),e.stopImmediatePropagation()}function fs(){return document.visibilityState===`hidden`?ms():ps()}function ps(){return new Promise(e=>requestAnimationFrame(()=>e()))}function ms(){return new Promise(e=>setTimeout(()=>e(),0))}function hs(e=``){return new DOMParser().parseFromString(e,`text/html`)}function gs(e,...t){let n=_s(e,t).replace(/^\\n/,``).split(`\n`),r=n[0].match(/^\\s+/),i=r?r[0].length:0;return n.map(e=>e.slice(i)).join(`\n`)}function _s(e,t){return e.reduce((e,n,r)=>{let i=t[r]==null?``:t[r];return e+n+i},``)}function vs(){return Array.from({length:36}).map((e,t)=>t==8||t==13||t==18||t==23?`-`:t==14?`4`:t==19?(Math.floor(Math.random()*4)+8).toString(16):Math.floor(Math.random()*16).toString(16)).join(``)}function ys(e,...t){for(let n of t.map(t=>t?.getAttribute(e)))if(typeof n==`string`)return n;return null}function bs(e,...t){return t.some(t=>t&&t.hasAttribute(e))}function xs(...e){for(let t of e)t.localName==`turbo-frame`&&t.setAttribute(`busy`,``),t.setAttribute(`aria-busy`,`true`)}function Ss(...e){for(let t of e)t.localName==`turbo-frame`&&t.removeAttribute(`busy`),t.removeAttribute(`aria-busy`)}function Cs(e,t=2e3){return new Promise(n=>{let r=()=>{e.removeEventListener(`error`,r),e.removeEventListener(`load`,r),n()};e.addEventListener(`load`,r,{once:!0}),e.addEventListener(`error`,r,{once:!0}),setTimeout(n,t)})}function ws(e){switch(e){case`replace`:return history.replaceState;case`advance`:case`restore`:return history.pushState}}function Ts(e){return e==`advance`||e==`replace`||e==`restore`}function Es(...e){let t=ys(`data-turbo-action`,...e);return Ts(t)?t:null}function Ds(e){return document.querySelector(`meta[name=\"${e}\"]`)}function Os(e){let t=Ds(e);return t&&t.content}function ks(){let e=Ds(`csp-nonce`);if(e){let{nonce:t,content:n}=e;return t==``?n:t}}function As(e,t){let n=Ds(e);return n||(n=document.createElement(`meta`),n.setAttribute(`name`,e),document.head.appendChild(n)),n.setAttribute(`content`,t),n}function js(e,t){if(e instanceof Element)return e.closest(t)||js(e.assignedSlot||e.getRootNode()?.host,t)}function Ms(e){return!!e&&e.closest(`[inert], :disabled, [hidden], details:not([open]), dialog:not([open])`)==null&&typeof e.focus==`function`}function Ns(e){return Array.from(e.querySelectorAll(`[autofocus]`)).find(Ms)}async function Ps(e,t){let n=t();return e(),await ps(),[n,t()]}function Fs(e){if(e===`_blank`)return!1;if(e){for(let t of document.getElementsByName(e))if(t instanceof HTMLIFrameElement)return!1;return!0}else return!0}function Is(e){let t=js(e,`a[href], a[xlink\\\\:href]`);if(!t||t.href.startsWith(`#`)||t.hasAttribute(`download`))return null;let n=t.getAttribute(`target`);return n&&n!==`_self`?null:t}function Ls(e,t){let n=null;return(...r)=>{clearTimeout(n),n=setTimeout(()=>e.apply(this,r),t)}}let Rs={\"aria-disabled\":{beforeSubmit:e=>{e.setAttribute(`aria-disabled`,`true`),e.addEventListener(`click`,ds)},afterSubmit:e=>{e.removeAttribute(`aria-disabled`),e.removeEventListener(`click`,ds)}},disabled:{beforeSubmit:e=>e.disabled=!0,afterSubmit:e=>e.disabled=!1}},Y={drive:ss,forms:new class{#e=null;constructor(e){Object.assign(this,e)}get submitter(){return this.#e}set submitter(e){this.#e=Rs[e]||e}}({mode:`on`,submitter:`disabled`})};function X(e){return new URL(e.toString(),document.baseURI)}function zs(e){let t;if(e.hash)return e.hash.slice(1);if(t=e.href.match(/#(.*)$/))return t[1]}function Bs(e,t){return X(t?.getAttribute(`formaction`)||e.getAttribute(`action`)||e.action)}function Vs(e){return(Ys(e).match(/\\.[^.]*$/)||[])[0]||``}function Hs(e,t){let n=Xs(t.origin+t.pathname);return Xs(e.href)===n||e.href.startsWith(n)}function Us(e,t){return Hs(e,t)&&!Y.drive.unvisitableExtensions.has(Vs(e))}function Ws(e){return X(e.getAttribute(`href`)||``)}function Gs(e){let t=zs(e);return t==null?e.href:e.href.slice(0,-(t.length+1))}function Ks(e){return Gs(e)}function qs(e,t){return X(e).href==X(t).href}function Js(e){return e.pathname.split(`/`).slice(1)}function Ys(e){return Js(e).slice(-1)[0]}function Xs(e){return e.endsWith(`/`)?e:e+`/`}var Zs=class{constructor(e){this.response=e}get succeeded(){return this.response.ok}get failed(){return!this.succeeded}get clientError(){return this.statusCode>=400&&this.statusCode<=499}get serverError(){return this.statusCode>=500&&this.statusCode<=599}get redirected(){return this.response.redirected}get location(){return X(this.response.url)}get isHTML(){return this.contentType&&this.contentType.match(/^(?:text\\/([^\\s;,]+\\b)?html|application\\/xhtml\\+xml)\\b/)}get statusCode(){return this.response.status}get contentType(){return this.header(`Content-Type`)}get responseText(){return this.response.clone().text()}get responseHTML(){return this.isHTML?this.response.clone().text():Promise.resolve(void 0)}header(e){return this.response.headers.get(e)}};let Qs=new class extends Set{constructor(e){super(),this.maxSize=e}add(e){if(this.size>=this.maxSize){let e=this.values().next().value;this.delete(e)}super.add(e)}}(20);function $s(e,t={}){let n=new Headers(t.headers||{}),r=vs();return Qs.add(r),n.append(`X-Turbo-Request-Id`,r),window.fetch(e,{...t,headers:n})}function ec(e){switch(e.toLowerCase()){case`get`:return Z.get;case`post`:return Z.post;case`put`:return Z.put;case`patch`:return Z.patch;case`delete`:return Z.delete}}let Z={get:`get`,post:`post`,put:`put`,patch:`patch`,delete:`delete`};function tc(e){switch(e.toLowerCase()){case nc.multipart:return nc.multipart;case nc.plain:return nc.plain;default:return nc.urlEncoded}}let nc={urlEncoded:`application/x-www-form-urlencoded`,multipart:`multipart/form-data`,plain:`text/plain`};var rc=class{abortController=new AbortController;#e=e=>{};constructor(e,t,n,r=new URLSearchParams,i=null,a=nc.urlEncoded){let[o,s]=ac(X(n),t,r,a);this.delegate=e,this.url=o,this.target=i,this.fetchOptions={credentials:`same-origin`,redirect:`follow`,method:t.toUpperCase(),headers:{...this.defaultHeaders},body:s,signal:this.abortSignal,referrer:this.delegate.referrer?.href},this.enctype=a}get method(){return this.fetchOptions.method}set method(e){let t=this.isSafe?this.url.searchParams:this.fetchOptions.body||new FormData,n=ec(e)||Z.get;this.url.search=``;let[r,i]=ac(this.url,n,t,this.enctype);this.url=r,this.fetchOptions.body=i,this.fetchOptions.method=n.toUpperCase()}get headers(){return this.fetchOptions.headers}set headers(e){this.fetchOptions.headers=e}get body(){return this.isSafe?this.url.searchParams:this.fetchOptions.body}set body(e){this.fetchOptions.body=e}get location(){return this.url}get params(){return this.url.searchParams}get entries(){return this.body?Array.from(this.body.entries()):[]}cancel(){this.abortController.abort()}async perform(){let{fetchOptions:e}=this;this.delegate.prepareRequest(this);let t=await this.#t(e);try{this.delegate.requestStarted(this),t.detail.fetchRequest?this.response=t.detail.fetchRequest.response:this.response=$s(this.url.href,e);let n=await this.response;return await this.receive(n)}catch(e){if(e.name!==`AbortError`)throw this.#n(e)&&this.delegate.requestErrored(this,e),e}finally{this.delegate.requestFinished(this)}}async receive(e){let t=new Zs(e);return J(`turbo:before-fetch-response`,{cancelable:!0,detail:{fetchResponse:t},target:this.target}).defaultPrevented?this.delegate.requestPreventedHandlingResponse(this,t):t.succeeded?this.delegate.requestSucceededWithResponse(this,t):this.delegate.requestFailedWithResponse(this,t),t}get defaultHeaders(){return{Accept:`text/html, application/xhtml+xml`}}get isSafe(){return ic(this.method)}get abortSignal(){return this.abortController.signal}acceptResponseType(e){this.headers.Accept=[e,this.headers.Accept].join(`, `)}async#t(e){let t=new Promise(e=>this.#e=e),n=J(`turbo:before-fetch-request`,{cancelable:!0,detail:{fetchOptions:e,url:this.url,resume:this.#e},target:this.target});return this.url=n.detail.url,n.defaultPrevented&&await t,n}#n(e){return!J(`turbo:fetch-request-error`,{target:this.target,cancelable:!0,detail:{request:this,error:e}}).defaultPrevented}};function ic(e){return ec(e)==Z.get}function ac(e,t,n,r){let i=Array.from(n).length>0?new URLSearchParams(oc(n)):e.searchParams;return ic(t)?[sc(e,i),null]:r==nc.urlEncoded?[e,i]:[e,n]}function oc(e){let t=[];for(let[n,r]of e)if(r instanceof File)continue;else t.push([n,r]);return t}function sc(e,t){return e.search=new URLSearchParams(oc(t)).toString(),e}var cc=class{started=!1;constructor(e,t){this.delegate=e,this.element=t,this.intersectionObserver=new IntersectionObserver(this.intersect)}start(){this.started||(this.started=!0,this.intersectionObserver.observe(this.element))}stop(){this.started&&(this.started=!1,this.intersectionObserver.unobserve(this.element))}intersect=e=>{e.slice(-1)[0]?.isIntersecting&&this.delegate.elementAppearedInViewport(this.element)}},lc=class{static contentType=`text/vnd.turbo-stream.html`;static wrap(e){return typeof e==`string`?new this(us(e)):e}constructor(e){this.fragment=uc(e)}};function uc(e){for(let t of e.querySelectorAll(`turbo-stream`)){let e=document.importNode(t,!0);for(let t of e.templateElement.content.querySelectorAll(`script`))t.replaceWith(cs(t));t.replaceWith(e)}return e}let dc=e=>e;var fc=class{keys=[];entries={};#e;constructor(e,t=dc){this.size=e,this.#e=t}has(e){return this.#e(e)in this.entries}get(e){if(this.has(e)){let t=this.read(e);return this.touch(e),t}}put(e,t){return this.write(e,t),this.touch(e),t}clear(){for(let e of Object.keys(this.entries))this.evict(e)}read(e){return this.entries[this.#e(e)]}write(e,t){this.entries[this.#e(e)]=t}touch(e){e=this.#e(e);let t=this.keys.indexOf(e);t>-1&&this.keys.splice(t,1),this.keys.unshift(e),this.trim()}trim(){for(let e of this.keys.splice(this.size))this.evict(e)}evict(e){delete this.entries[e]}},pc=class extends fc{#e=null;#t={};constructor(e=1,t=100){super(e,Ks),this.prefetchDelay=t}putLater(e,t,n){this.#e=setTimeout(()=>{t.perform(),this.put(e,t,n),this.#e=null},this.prefetchDelay)}put(e,t,n=mc){super.put(e,t),this.#t[Ks(e)]=new Date(new Date().getTime()+n)}clear(){super.clear(),this.#e&&clearTimeout(this.#e)}evict(e){super.evict(e),delete this.#t[e]}has(e){if(super.has(e)){let t=this.#t[Ks(e)];return t&&t>Date.now()}else return!1}};let mc=10*1e3,hc=new pc,gc={initialized:`initialized`,requesting:`requesting`,waiting:`waiting`,receiving:`receiving`,stopping:`stopping`,stopped:`stopped`};var _c=class e{state=gc.initialized;static confirmMethod(e){return Promise.resolve(confirm(e))}constructor(e,t,n,r=!1){let i=Cc(t,n),a=Sc(xc(t,n),i),o=vc(t,n),s=wc(t,n);this.delegate=e,this.formElement=t,this.submitter=n,this.fetchRequest=new rc(this,i,a,o,t,s),this.mustRedirect=r}get method(){return this.fetchRequest.method}set method(e){this.fetchRequest.method=e}get action(){return this.fetchRequest.url.toString()}set action(e){this.fetchRequest.url=X(e)}get body(){return this.fetchRequest.body}get enctype(){return this.fetchRequest.enctype}get isSafe(){return this.fetchRequest.isSafe}get location(){return this.fetchRequest.url}async start(){let{initialized:t,requesting:n}=gc,r=ys(`data-turbo-confirm`,this.submitter,this.formElement);if(!(typeof r==`string`&&!await(typeof Y.forms.confirm==`function`?Y.forms.confirm:e.confirmMethod)(r,this.formElement,this.submitter))&&this.state==t)return this.state=n,this.fetchRequest.perform()}stop(){let{stopping:e,stopped:t}=gc;if(this.state!=e&&this.state!=t)return this.state=e,this.fetchRequest.cancel(),!0}prepareRequest(e){if(!e.isSafe){let t=yc(Os(`csrf-param`))||Os(`csrf-token`);t&&(e.headers[`X-CSRF-Token`]=t)}this.requestAcceptsTurboStreamResponse(e)&&e.acceptResponseType(lc.contentType)}requestStarted(e){this.state=gc.waiting,this.submitter&&Y.forms.submitter.beforeSubmit(this.submitter),this.setSubmitsWith(),xs(this.formElement),J(`turbo:submit-start`,{target:this.formElement,detail:{formSubmission:this}}),this.delegate.formSubmissionStarted(this)}requestPreventedHandlingResponse(e,t){hc.clear(),this.result={success:t.succeeded,fetchResponse:t}}requestSucceededWithResponse(e,t){if(t.clientError||t.serverError){this.delegate.formSubmissionFailedWithResponse(this,t);return}if(hc.clear(),this.requestMustRedirect(e)&&bc(t)){let e=Error(`Form responses must redirect to another location`);this.delegate.formSubmissionErrored(this,e)}else this.state=gc.receiving,this.result={success:!0,fetchResponse:t},this.delegate.formSubmissionSucceededWithResponse(this,t)}requestFailedWithResponse(e,t){this.result={success:!1,fetchResponse:t},this.delegate.formSubmissionFailedWithResponse(this,t)}requestErrored(e,t){this.result={success:!1,error:t},this.delegate.formSubmissionErrored(this,t)}requestFinished(e){this.state=gc.stopped,this.submitter&&Y.forms.submitter.afterSubmit(this.submitter),this.resetSubmitterText(),Ss(this.formElement),J(`turbo:submit-end`,{target:this.formElement,detail:{formSubmission:this,...this.result}}),this.delegate.formSubmissionFinished(this)}setSubmitsWith(){if(!(!this.submitter||!this.submitsWith)){if(this.submitter.matches(`button`))this.originalSubmitText=this.submitter.innerHTML,this.submitter.innerHTML=this.submitsWith;else if(this.submitter.matches(`input`)){let e=this.submitter;this.originalSubmitText=e.value,e.value=this.submitsWith}}}resetSubmitterText(){if(!(!this.submitter||!this.originalSubmitText)){if(this.submitter.matches(`button`))this.submitter.innerHTML=this.originalSubmitText;else if(this.submitter.matches(`input`)){let e=this.submitter;e.value=this.originalSubmitText}}}requestMustRedirect(e){return!e.isSafe&&this.mustRedirect}requestAcceptsTurboStreamResponse(e){return!e.isSafe||bs(`data-turbo-stream`,this.submitter,this.formElement)}get submitsWith(){return this.submitter?.getAttribute(`data-turbo-submits-with`)}};function vc(e,t){let n=new FormData(e),r=t?.getAttribute(`name`),i=t?.getAttribute(`value`);return r&&n.append(r,i||``),n}function yc(e){if(e!=null){let t=(document.cookie?document.cookie.split(`; `):[]).find(t=>t.startsWith(e));if(t){let e=t.split(`=`).slice(1).join(`=`);return e?decodeURIComponent(e):void 0}}}function bc(e){return e.statusCode==200&&!e.redirected}function xc(e,t){let n=typeof e.action==`string`?e.action:null;return t?.hasAttribute(`formaction`)?t.getAttribute(`formaction`)||``:e.getAttribute(`action`)||n||``}function Sc(e,t){let n=X(e);return ic(t)&&(n.search=``),n}function Cc(e,t){return ec((t?.getAttribute(`formmethod`)||e.getAttribute(`method`)||``).toLowerCase())||Z.get}function wc(e,t){return tc(t?.getAttribute(`formenctype`)||e.enctype)}var Tc=class{constructor(e){this.element=e}get activeElement(){return this.element.ownerDocument.activeElement}get children(){return[...this.element.children]}hasAnchor(e){return this.getElementForAnchor(e)!=null}getElementForAnchor(e){return e?this.element.querySelector(`[id='${e}'], a[name='${e}']`):null}get isConnected(){return this.element.isConnected}get firstAutofocusableElement(){return Ns(this.element)}get permanentElements(){return Dc(this.element)}getPermanentElementById(e){return Ec(this.element,e)}getPermanentElementMapForSnapshot(e){let t={};for(let n of this.permanentElements){let{id:r}=n,i=e.getPermanentElementById(r);i&&(t[r]=[n,i])}return t}};function Ec(e,t){return e.querySelector(`#${t}[data-turbo-permanent]`)}function Dc(e){return e.querySelectorAll(`[id][data-turbo-permanent]`)}var Oc=class{started=!1;constructor(e,t){this.delegate=e,this.eventTarget=t}start(){this.started||=(this.eventTarget.addEventListener(`submit`,this.submitCaptured,!0),!0)}stop(){this.started&&=(this.eventTarget.removeEventListener(`submit`,this.submitCaptured,!0),!1)}submitCaptured=()=>{this.eventTarget.removeEventListener(`submit`,this.submitBubbled,!1),this.eventTarget.addEventListener(`submit`,this.submitBubbled,!1)};submitBubbled=e=>{if(!e.defaultPrevented){let t=e.target instanceof HTMLFormElement?e.target:void 0,n=e.submitter||void 0;t&&kc(t,n)&&Ac(t,n)&&this.delegate.willSubmitForm(t,n)&&(e.preventDefault(),e.stopImmediatePropagation(),this.delegate.formSubmitted(t,n))}}};function kc(e,t){return(t?.getAttribute(`formmethod`)||e.getAttribute(`method`))!=`dialog`}function Ac(e,t){return Fs(t?.getAttribute(`formtarget`)||e.getAttribute(`target`))}var jc=class{#e=e=>{};#t=e=>{};constructor(e,t){this.delegate=e,this.element=t}scrollToAnchor(e){let t=this.snapshot.getElementForAnchor(e);t?(this.focusElement(t),this.scrollToElement(t)):this.scrollToPosition({x:0,y:0})}scrollToAnchorFromLocation(e){this.scrollToAnchor(zs(e))}scrollToElement(e){e.scrollIntoView()}focusElement(e){e instanceof HTMLElement&&(e.hasAttribute(`tabindex`)?e.focus():(e.setAttribute(`tabindex`,`-1`),e.focus(),e.removeAttribute(`tabindex`)))}scrollToPosition({x:e,y:t}){this.scrollRoot.scrollTo(e,t)}scrollToTop(){this.scrollToPosition({x:0,y:0})}get scrollRoot(){return window}async render(e){let{isPreview:t,shouldRender:n,willRender:r,newSnapshot:i}=e,a=r;if(n)try{this.renderPromise=new Promise(e=>this.#e=e),this.renderer=e,await this.prepareToRenderSnapshot(e);let n=new Promise(e=>this.#t=e),r={resume:this.#t,render:this.renderer.renderElement,renderMethod:this.renderer.renderMethod};this.delegate.allowsImmediateRender(i,r)||await n,await this.renderSnapshot(e),this.delegate.viewRenderedSnapshot(i,t,this.renderer.renderMethod),this.delegate.preloadOnLoadLinksForView(this.element),this.finishRenderingSnapshot(e)}finally{delete this.renderer,this.#e(void 0),delete this.renderPromise}else a&&this.invalidate(e.reloadReason)}invalidate(e){this.delegate.viewInvalidated(e)}async prepareToRenderSnapshot(e){this.markAsPreview(e.isPreview),await e.prepareToRender()}markAsPreview(e){e?this.element.setAttribute(`data-turbo-preview`,``):this.element.removeAttribute(`data-turbo-preview`)}markVisitDirection(e){this.element.setAttribute(`data-turbo-visit-direction`,e)}unmarkVisitDirection(){this.element.removeAttribute(`data-turbo-visit-direction`)}async renderSnapshot(e){await e.render()}finishRenderingSnapshot(e){e.finishRendering()}},Mc=class extends jc{missing(){this.element.innerHTML=`<strong class=\"turbo-frame-error\">Content missing</strong>`}get snapshot(){return new Tc(this.element)}},Nc=class{constructor(e,t){this.delegate=e,this.element=t}start(){this.element.addEventListener(`click`,this.clickBubbled),document.addEventListener(`turbo:click`,this.linkClicked),document.addEventListener(`turbo:before-visit`,this.willVisit)}stop(){this.element.removeEventListener(`click`,this.clickBubbled),document.removeEventListener(`turbo:click`,this.linkClicked),document.removeEventListener(`turbo:before-visit`,this.willVisit)}clickBubbled=e=>{this.clickEventIsSignificant(e)?this.clickEvent=e:delete this.clickEvent};linkClicked=e=>{this.clickEvent&&this.clickEventIsSignificant(e)&&this.delegate.shouldInterceptLinkClick(e.target,e.detail.url,e.detail.originalEvent)&&(this.clickEvent.preventDefault(),e.preventDefault(),this.delegate.linkClickIntercepted(e.target,e.detail.url,e.detail.originalEvent)),delete this.clickEvent};willVisit=e=>{delete this.clickEvent};clickEventIsSignificant(e){let t=e.composed?e.target?.parentElement:e.target,n=Is(t)||t;return n instanceof Element&&n.closest(`turbo-frame, html`)==this.element}},Pc=class{started=!1;constructor(e,t){this.delegate=e,this.eventTarget=t}start(){this.started||=(this.eventTarget.addEventListener(`click`,this.clickCaptured,!0),!0)}stop(){this.started&&=(this.eventTarget.removeEventListener(`click`,this.clickCaptured,!0),!1)}clickCaptured=()=>{this.eventTarget.removeEventListener(`click`,this.clickBubbled,!1),this.eventTarget.addEventListener(`click`,this.clickBubbled,!1)};clickBubbled=e=>{if(e instanceof MouseEvent&&this.clickEventIsSignificant(e)){let t=Is(e.composedPath&&e.composedPath()[0]||e.target);if(t&&Fs(t.target)){let n=Ws(t);this.delegate.willFollowLinkToLocation(t,n,e)&&(e.preventDefault(),this.delegate.followedLinkToLocation(t,n))}}};clickEventIsSignificant(e){return!(e.target&&e.target.isContentEditable||e.defaultPrevented||e.which>1||e.altKey||e.ctrlKey||e.metaKey||e.shiftKey)}},Fc=class{constructor(e,t){this.delegate=e,this.linkInterceptor=new Pc(this,t)}start(){this.linkInterceptor.start()}stop(){this.linkInterceptor.stop()}canPrefetchRequestToLocation(e,t){return!1}prefetchAndCacheRequestToLocation(e,t){}willFollowLinkToLocation(e,t,n){return this.delegate.willSubmitFormLinkToLocation(e,t,n)&&(e.hasAttribute(`data-turbo-method`)||e.hasAttribute(`data-turbo-stream`))}followedLinkToLocation(e,t){let n=document.createElement(`form`);for(let[e,r]of t.searchParams)n.append(Object.assign(document.createElement(`input`),{type:`hidden`,name:e,value:r}));let r=Object.assign(t,{search:``});n.setAttribute(`data-turbo`,`true`),n.setAttribute(`action`,r.href),n.setAttribute(`hidden`,``);let i=e.getAttribute(`data-turbo-method`);i&&n.setAttribute(`method`,i);let a=e.getAttribute(`data-turbo-frame`);a&&n.setAttribute(`data-turbo-frame`,a);let o=Es(e);o&&n.setAttribute(`data-turbo-action`,o);let s=e.getAttribute(`data-turbo-confirm`);s&&n.setAttribute(`data-turbo-confirm`,s),e.hasAttribute(`data-turbo-stream`)&&n.setAttribute(`data-turbo-stream`,``),this.delegate.submittedFormLinkToLocation(e,t,n),document.body.appendChild(n),n.addEventListener(`turbo:submit-end`,()=>n.remove(),{once:!0}),requestAnimationFrame(()=>n.requestSubmit())}},Ic=class{static async preservingPermanentElements(e,t,n){let r=new this(e,t);r.enter(),await n(),r.leave()}constructor(e,t){this.delegate=e,this.permanentElementMap=t}enter(){for(let e in this.permanentElementMap){let[t,n]=this.permanentElementMap[e];this.delegate.enteringBardo(t,n),this.replaceNewPermanentElementWithPlaceholder(n)}}leave(){for(let e in this.permanentElementMap){let[t]=this.permanentElementMap[e];this.replaceCurrentPermanentElementWithClone(t),this.replacePlaceholderWithPermanentElement(t),this.delegate.leavingBardo(t)}}replaceNewPermanentElementWithPlaceholder(e){let t=Lc(e);e.replaceWith(t)}replaceCurrentPermanentElementWithClone(e){let t=e.cloneNode(!0);e.replaceWith(t)}replacePlaceholderWithPermanentElement(e){this.getPlaceholderById(e.id)?.replaceWith(e)}getPlaceholderById(e){return this.placeholders.find(t=>t.content==e)}get placeholders(){return[...document.querySelectorAll(`meta[name=turbo-permanent-placeholder][content]`)]}};function Lc(e){let t=document.createElement(`meta`);return t.setAttribute(`name`,`turbo-permanent-placeholder`),t.setAttribute(`content`,e.id),t}var Rc=class{#e=null;static renderElement(e,t){}constructor(e,t,n,r=!0){this.currentSnapshot=e,this.newSnapshot=t,this.isPreview=n,this.willRender=r,this.renderElement=this.constructor.renderElement,this.promise=new Promise((e,t)=>this.resolvingFunctions={resolve:e,reject:t})}get shouldRender(){return!0}get shouldAutofocus(){return!0}get reloadReason(){}prepareToRender(){}render(){}finishRendering(){this.resolvingFunctions&&(this.resolvingFunctions.resolve(),delete this.resolvingFunctions)}async preservingPermanentElements(e){await Ic.preservingPermanentElements(this,this.permanentElementMap,e)}focusFirstAutofocusableElement(){if(this.shouldAutofocus){let e=this.connectedSnapshot.firstAutofocusableElement;e&&e.focus()}}enteringBardo(e){this.#e||e.contains(this.currentSnapshot.activeElement)&&(this.#e=this.currentSnapshot.activeElement)}leavingBardo(e){e.contains(this.#e)&&this.#e instanceof HTMLElement&&(this.#e.focus(),this.#e=null)}get connectedSnapshot(){return this.newSnapshot.isConnected?this.newSnapshot:this.currentSnapshot}get currentElement(){return this.currentSnapshot.element}get newElement(){return this.newSnapshot.element}get permanentElementMap(){return this.currentSnapshot.getPermanentElementMapForSnapshot(this.newSnapshot)}get renderMethod(){return`replace`}},zc=class extends Rc{static renderElement(e,t){let n=document.createRange();n.selectNodeContents(e),n.deleteContents();let r=t,i=r.ownerDocument?.createRange();i&&(i.selectNodeContents(r),e.appendChild(i.extractContents()))}constructor(e,t,n,r,i,a=!0){super(t,n,r,i,a),this.delegate=e}get shouldRender(){return!0}async render(){await fs(),this.preservingPermanentElements(()=>{this.loadFrameElement()}),this.scrollFrameIntoView(),await fs(),this.focusFirstAutofocusableElement(),await fs(),this.activateScriptElements()}loadFrameElement(){this.delegate.willRenderFrame(this.currentElement,this.newElement),this.renderElement(this.currentElement,this.newElement)}scrollFrameIntoView(){if(this.currentElement.autoscroll||this.newElement.autoscroll){let e=this.currentElement.firstElementChild,t=Bc(this.currentElement.getAttribute(`data-autoscroll-block`),`end`),n=Vc(this.currentElement.getAttribute(`data-autoscroll-behavior`),`auto`);if(e)return e.scrollIntoView({block:t,behavior:n}),!0}return!1}activateScriptElements(){for(let e of this.newScriptElements){let t=cs(e);e.replaceWith(t)}}get newScriptElements(){return this.currentElement.querySelectorAll(`script`)}};function Bc(e,t){return e==`end`||e==`start`||e==`center`||e==`nearest`?e:t}function Vc(e,t){return e==`auto`||e==`smooth`?e:t}var Hc=(function(){let e=()=>{},t={morphStyle:`outerHTML`,callbacks:{beforeNodeAdded:e,afterNodeAdded:e,beforeNodeMorphed:e,afterNodeMorphed:e,beforeNodeRemoved:e,afterNodeRemoved:e,beforeAttributeUpdated:e},head:{style:`merge`,shouldPreserve:e=>e.getAttribute(`im-preserve`)===`true`,shouldReAppend:e=>e.getAttribute(`im-re-append`)===`true`,shouldRemove:e,afterHeadMorphed:e},restoreFocus:!0};function n(e,t,n={}){e=u(e);let o=d(t),c=l(e,o,n),f=i(c,()=>s(c,e,o,t=>t.morphStyle===`innerHTML`?(a(t,e,o),Array.from(e.childNodes)):r(t,e,o)));return c.pantry.remove(),f}function r(e,t,n){let r=d(t);return a(e,r,n,t,t.nextSibling),Array.from(r.childNodes)}function i(e,t){if(!e.config.restoreFocus)return t();let n=document.activeElement;if(!(n instanceof HTMLInputElement||n instanceof HTMLTextAreaElement))return t();let{id:r,selectionStart:i,selectionEnd:a}=n,o=t();return r&&r!==document.activeElement?.getAttribute(`id`)&&(n=e.target.querySelector(`[id=\"${r}\"]`),n?.focus()),n&&!n.selectionEnd&&a&&n.setSelectionRange(i,a),o}let a=(function(){function e(e,s,c,l=null,u=null){s instanceof HTMLTemplateElement&&c instanceof HTMLTemplateElement&&(s=s.content,c=c.content),l||=s.firstChild;for(let r of c.childNodes){if(l&&l!=u){let t=n(e,r,l,u);if(t){t!==l&&i(e,l,t),o(t,r,e),l=t.nextSibling;continue}}if(r instanceof Element){let t=r.getAttribute(`id`);if(e.persistentIds.has(t)){let n=a(s,t,l,e);o(n,r,e),l=n.nextSibling;continue}}let c=t(s,r,l,e);c&&(l=c.nextSibling)}for(;l&&l!=u;){let t=l;l=l.nextSibling,r(e,t)}}function t(e,t,n,r){if(r.callbacks.beforeNodeAdded(t)===!1)return null;if(r.idMap.has(t)){let i=document.createElement(t.tagName);return e.insertBefore(i,n),o(i,t,r),r.callbacks.afterNodeAdded(i),i}else{let i=document.importNode(t,!0);return e.insertBefore(i,n),r.callbacks.afterNodeAdded(i),i}}let n=(function(){function e(e,r,i,a){let o=null,s=r.nextSibling,c=0,l=i;for(;l&&l!=a;){if(n(l,r)){if(t(e,l,r))return l;o===null&&(e.idMap.has(l)||(o=l))}if(o===null&&s&&n(l,s)&&(c++,s=s.nextSibling,c>=2&&(o=void 0)),e.activeElementAndParents.includes(l))break;l=l.nextSibling}return o||null}function t(e,t,n){let r=e.idMap.get(t),i=e.idMap.get(n);if(!i||!r)return!1;for(let e of r)if(i.has(e))return!0;return!1}function n(e,t){let n=e,r=t;return n.nodeType===r.nodeType&&n.tagName===r.tagName&&(!n.getAttribute?.(`id`)||n.getAttribute?.(`id`)===r.getAttribute?.(`id`))}return e})();function r(e,t){if(e.idMap.has(t))c(e.pantry,t,null);else{if(e.callbacks.beforeNodeRemoved(t)===!1)return;t.parentNode?.removeChild(t),e.callbacks.afterNodeRemoved(t)}}function i(e,t,n){let i=t;for(;i&&i!==n;){let t=i;i=i.nextSibling,r(e,t)}return i}function a(e,t,n,r){let i=r.target.getAttribute?.(`id`)===t&&r.target||r.target.querySelector(`[id=\"${t}\"]`)||r.pantry.querySelector(`[id=\"${t}\"]`);return s(i,r),c(e,i,n),i}function s(e,t){let n=e.getAttribute(`id`);for(;e=e.parentNode;){let r=t.idMap.get(e);r&&(r.delete(n),r.size||t.idMap.delete(e))}}function c(e,t,n){if(e.moveBefore)try{e.moveBefore(t,n)}catch{e.insertBefore(t,n)}else e.insertBefore(t,n)}return e})(),o=(function(){function e(e,n,r){return r.ignoreActive&&e===document.activeElement?null:r.callbacks.beforeNodeMorphed(e,n)===!1?e:(e instanceof HTMLHeadElement&&r.head.ignore||(e instanceof HTMLHeadElement&&r.head.style!==`morph`?c(e,n,r):(t(e,n,r),o(e,r)||a(r,e,n))),r.callbacks.afterNodeMorphed(e,n),e)}function t(e,t,r){let a=t.nodeType;if(a===1){let a=e,s=t,c=a.attributes,l=s.attributes;for(let e of l)i(e.name,a,`update`,r)||a.getAttribute(e.name)!==e.value&&a.setAttribute(e.name,e.value);for(let e=c.length-1;0<=e;e--){let t=c[e];if(t&&!s.hasAttribute(t.name)){if(i(t.name,a,`remove`,r))continue;a.removeAttribute(t.name)}}o(a,r)||n(a,s,r)}(a===8||a===3)&&e.nodeValue!==t.nodeValue&&(e.nodeValue=t.nodeValue)}function n(e,t,n){if(e instanceof HTMLInputElement&&t instanceof HTMLInputElement&&t.type!==`file`){let a=t.value,o=e.value;r(e,t,`checked`,n),r(e,t,`disabled`,n),t.hasAttribute(`value`)?o!==a&&(i(`value`,e,`update`,n)||(e.setAttribute(`value`,a),e.value=a)):i(`value`,e,`remove`,n)||(e.value=``,e.removeAttribute(`value`))}else if(e instanceof HTMLOptionElement&&t instanceof HTMLOptionElement)r(e,t,`selected`,n);else if(e instanceof HTMLTextAreaElement&&t instanceof HTMLTextAreaElement){let r=t.value,a=e.value;if(i(`value`,e,`update`,n))return;r!==a&&(e.value=r),e.firstChild&&e.firstChild.nodeValue!==r&&(e.firstChild.nodeValue=r)}}function r(e,t,n,r){let a=t[n];if(a!==e[n]){let o=i(n,e,`update`,r);o||(e[n]=t[n]),a?o||e.setAttribute(n,``):i(n,e,`remove`,r)||e.removeAttribute(n)}}function i(e,t,n,r){return e===`value`&&r.ignoreActiveValue&&t===document.activeElement?!0:r.callbacks.beforeAttributeUpdated(e,t,n)===!1}function o(e,t){return!!t.ignoreActiveValue&&e===document.activeElement&&e!==document.body}return e})();function s(e,t,n,r){if(e.head.block){let i=t.querySelector(`head`),a=n.querySelector(`head`);if(i&&a){let t=c(i,a,e);return Promise.all(t).then(()=>r(Object.assign(e,{head:{block:!1,ignore:!0}})))}}return r(e)}function c(e,t,n){let r=[],i=[],a=[],o=[],s=new Map;for(let e of t.children)s.set(e.outerHTML,e);for(let t of e.children){let e=s.has(t.outerHTML),r=n.head.shouldReAppend(t),c=n.head.shouldPreserve(t);e||c?r?i.push(t):(s.delete(t.outerHTML),a.push(t)):n.head.style===`append`?r&&(i.push(t),o.push(t)):n.head.shouldRemove(t)!==!1&&i.push(t)}o.push(...s.values());let c=[];for(let t of o){let i=document.createRange().createContextualFragment(t.outerHTML).firstChild;if(n.callbacks.beforeNodeAdded(i)!==!1){if(`href`in i&&i.href||`src`in i&&i.src){let e,t=new Promise(function(t){e=t});i.addEventListener(`load`,function(){e()}),c.push(t)}e.appendChild(i),n.callbacks.afterNodeAdded(i),r.push(i)}}for(let t of i)n.callbacks.beforeNodeRemoved(t)!==!1&&(e.removeChild(t),n.callbacks.afterNodeRemoved(t));return n.head.afterHeadMorphed(e,{added:r,kept:a,removed:i}),c}let l=(function(){function e(e,t,a){let{persistentIds:o,idMap:c}=s(e,t),l=n(a),u=l.morphStyle||`outerHTML`;if(![`innerHTML`,`outerHTML`].includes(u))throw`Do not understand how to morph style ${u}`;return{target:e,newContent:t,config:l,morphStyle:u,ignoreActive:l.ignoreActive,ignoreActiveValue:l.ignoreActiveValue,restoreFocus:l.restoreFocus,idMap:c,persistentIds:o,pantry:r(),activeElementAndParents:i(e),callbacks:l.callbacks,head:l.head}}function n(e){let n=Object.assign({},t);return Object.assign(n,e),n.callbacks=Object.assign({},t.callbacks,e.callbacks),n.head=Object.assign({},t.head,e.head),n}function r(){let e=document.createElement(`div`);return e.hidden=!0,document.body.insertAdjacentElement(`afterend`,e),e}function i(e){let t=[],n=document.activeElement;if(n?.tagName!==`BODY`&&e.contains(n))for(;n&&(t.push(n),n!==e);)n=n.parentElement;return t}function a(e){let t=Array.from(e.querySelectorAll(`[id]`));return e.getAttribute?.(`id`)&&t.push(e),t}function o(e,t,n,r){for(let i of r){let r=i.getAttribute(`id`);if(t.has(r)){let t=i;for(;t;){let i=e.get(t);if(i??(i=new Set,e.set(t,i)),i.add(r),t===n)break;t=t.parentElement}}}}function s(e,t){let n=a(e),r=a(t),i=c(n,r),s=new Map;return o(s,i,e,n),o(s,i,t.__idiomorphRoot||t,r),{persistentIds:i,idMap:s}}function c(e,t){let n=new Set,r=new Map;for(let{id:t,tagName:i}of e)r.has(t)?n.add(t):r.set(t,i);let i=new Set;for(let{id:e,tagName:a}of t)i.has(e)?n.add(e):r.get(e)===a&&i.add(e);for(let e of n)i.delete(e);return i}return e})(),{normalizeElement:u,normalizeParent:d}=(function(){let e=new WeakSet;function t(e){return e instanceof Document?e.documentElement:e}function n(t){if(t==null)return document.createElement(`div`);if(typeof t==`string`)return n(i(t));if(e.has(t))return t;if(t instanceof Node){if(t.parentNode)return new r(t);{let e=document.createElement(`div`);return e.append(t),e}}else{let e=document.createElement(`div`);for(let n of[...t])e.append(n);return e}}class r{constructor(e){this.originalNode=e,this.realParentNode=e.parentNode,this.previousSibling=e.previousSibling,this.nextSibling=e.nextSibling}get childNodes(){let e=[],t=this.previousSibling?this.previousSibling.nextSibling:this.realParentNode.firstChild;for(;t&&t!=this.nextSibling;)e.push(t),t=t.nextSibling;return e}querySelectorAll(e){return this.childNodes.reduce((t,n)=>{if(n instanceof Element){n.matches(e)&&t.push(n);let r=n.querySelectorAll(e);for(let e=0;e<r.length;e++)t.push(r[e])}return t},[])}insertBefore(e,t){return this.realParentNode.insertBefore(e,t)}moveBefore(e,t){return this.realParentNode.moveBefore(e,t)}get __idiomorphRoot(){return this.originalNode}}function i(t){let n=new DOMParser,r=t.replace(/<svg(\\s[^>]*>|>)([\\s\\S]*?)<\\/svg>/gim,``);if(r.match(/<\\/html>/)||r.match(/<\\/head>/)||r.match(/<\\/body>/)){let i=n.parseFromString(t,`text/html`);if(r.match(/<\\/html>/))return e.add(i),i;{let t=i.firstChild;return t&&e.add(t),t}}else{let r=n.parseFromString(`<body><template>`+t+`</template></body>`,`text/html`).body.querySelector(`template`).content;return e.add(r),r}}return{normalizeElement:t,normalizeParent:n}})();return{morph:n,defaults:t}})();function Uc(e,t,{callbacks:n,...r}={}){Hc.morph(e,t,{...r,callbacks:new Jc(n)})}function Wc(e,t,n={}){Uc(e,t.childNodes,{...n,morphStyle:`innerHTML`})}function Gc(e,t){return e instanceof q&&e.shouldReloadWithMorph&&(!t||Kc(e,t))&&!e.closest(`[data-turbo-permanent]`)}function Kc(e,t){return t instanceof Element&&t.nodeName===`TURBO-FRAME`&&e.id===t.id&&(!t.getAttribute(`src`)||qs(e.src,t.getAttribute(`src`)))}function qc(e){return e.parentElement.closest(`turbo-frame[src][refresh=morph]`)}var Jc=class{#e;constructor({beforeNodeMorphed:e}={}){this.#e=e||(()=>!0)}beforeNodeAdded=e=>!(e.id&&e.hasAttribute(`data-turbo-permanent`)&&document.getElementById(e.id));beforeNodeMorphed=(e,t)=>{if(e instanceof Element)return!e.hasAttribute(`data-turbo-permanent`)&&this.#e(e,t)?!J(`turbo:before-morph-element`,{cancelable:!0,target:e,detail:{currentElement:e,newElement:t}}).defaultPrevented:!1};beforeAttributeUpdated=(e,t,n)=>!J(`turbo:before-morph-attribute`,{cancelable:!0,target:t,detail:{attributeName:e,mutationType:n}}).defaultPrevented;beforeNodeRemoved=e=>this.beforeNodeMorphed(e);afterNodeMorphed=(e,t)=>{e instanceof Element&&J(`turbo:morph-element`,{target:e,detail:{currentElement:e,newElement:t}})}},Yc=class extends zc{static renderElement(e,t){J(`turbo:before-frame-morph`,{target:e,detail:{currentElement:e,newElement:t}}),Wc(e,t,{callbacks:{beforeNodeMorphed:(t,n)=>Gc(t,n)&&qc(t)===e?(t.reload(),!1):!0}})}async preservingPermanentElements(e){return await e()}},Xc=class e{static animationDuration=300;static get defaultCSS(){return gs`\n      .turbo-progress-bar {\n        position: fixed;\n        display: block;\n        top: 0;\n        left: 0;\n        height: 3px;\n        background: #0076ff;\n        z-index: 2147483647;\n        transition:\n          width ${e.animationDuration}ms ease-out,\n          opacity ${e.animationDuration/2}ms ${e.animationDuration/2}ms ease-in;\n        transform: translate3d(0, 0, 0);\n      }\n    `}hiding=!1;value=0;visible=!1;constructor(){this.stylesheetElement=this.createStylesheetElement(),this.progressElement=this.createProgressElement(),this.installStylesheetElement(),this.setValue(0)}show(){this.visible||(this.visible=!0,this.installProgressElement(),this.startTrickling())}hide(){this.visible&&!this.hiding&&(this.hiding=!0,this.fadeProgressElement(()=>{this.uninstallProgressElement(),this.stopTrickling(),this.visible=!1,this.hiding=!1}))}setValue(e){this.value=e,this.refresh()}installStylesheetElement(){document.head.insertBefore(this.stylesheetElement,document.head.firstChild)}installProgressElement(){this.progressElement.style.width=`0`,this.progressElement.style.opacity=`1`,document.documentElement.insertBefore(this.progressElement,document.body),this.refresh()}fadeProgressElement(t){this.progressElement.style.opacity=`0`,setTimeout(t,e.animationDuration*1.5)}uninstallProgressElement(){this.progressElement.parentNode&&document.documentElement.removeChild(this.progressElement)}startTrickling(){this.trickleInterval||=window.setInterval(this.trickle,e.animationDuration)}stopTrickling(){window.clearInterval(this.trickleInterval),delete this.trickleInterval}trickle=()=>{this.setValue(this.value+Math.random()/100)};refresh(){requestAnimationFrame(()=>{this.progressElement.style.width=`${10+this.value*90}%`})}createStylesheetElement(){let t=document.createElement(`style`);t.type=`text/css`,t.textContent=e.defaultCSS;let n=ks();return n&&(t.nonce=n),t}createProgressElement(){let e=document.createElement(`div`);return e.className=`turbo-progress-bar`,e}},Zc=class extends Tc{detailsByOuterHTML=this.children.filter(e=>!tl(e)).map(e=>il(e)).reduce((e,t)=>{let{outerHTML:n}=t,r=n in e?e[n]:{type:Qc(t),tracked:$c(t),elements:[]};return{...e,[n]:{...r,elements:[...r.elements,t]}}},{});get trackedElementSignature(){return Object.keys(this.detailsByOuterHTML).filter(e=>this.detailsByOuterHTML[e].tracked).join(``)}getScriptElementsNotInSnapshot(e){return this.getElementsMatchingTypeNotInSnapshot(`script`,e)}getStylesheetElementsNotInSnapshot(e){return this.getElementsMatchingTypeNotInSnapshot(`stylesheet`,e)}getElementsMatchingTypeNotInSnapshot(e,t){return Object.keys(this.detailsByOuterHTML).filter(e=>!(e in t.detailsByOuterHTML)).map(e=>this.detailsByOuterHTML[e]).filter(({type:t})=>t==e).map(({elements:[e]})=>e)}get provisionalElements(){return Object.keys(this.detailsByOuterHTML).reduce((e,t)=>{let{type:n,tracked:r,elements:i}=this.detailsByOuterHTML[t];return n==null&&!r?[...e,...i]:i.length>1?[...e,...i.slice(1)]:e},[])}getMetaValue(e){let t=this.findMetaElementByName(e);return t?t.getAttribute(`content`):null}findMetaElementByName(e){return Object.keys(this.detailsByOuterHTML).reduce((t,n)=>{let{elements:[r]}=this.detailsByOuterHTML[n];return rl(r,e)?r:t},0)}};function Qc(e){if(el(e))return`script`;if(nl(e))return`stylesheet`}function $c(e){return e.getAttribute(`data-turbo-track`)==`reload`}function el(e){return e.localName==`script`}function tl(e){return e.localName==`noscript`}function nl(e){let t=e.localName;return t==`style`||t==`link`&&e.getAttribute(`rel`)==`stylesheet`}function rl(e,t){return e.localName==`meta`&&e.getAttribute(`name`)==t}function il(e){return e.hasAttribute(`nonce`)&&e.setAttribute(`nonce`,``),e}var Q=class e extends Tc{static fromHTMLString(e=``){return this.fromDocument(hs(e))}static fromElement(e){return this.fromDocument(e.ownerDocument)}static fromDocument({documentElement:e,body:t,head:n}){return new this(e,t,new Zc(n))}constructor(e,t,n){super(t),this.documentElement=e,this.headSnapshot=n}clone(){let t=this.element.cloneNode(!0),n=this.element.querySelectorAll(`select`),r=t.querySelectorAll(`select`);for(let[e,t]of n.entries()){let n=r[e];for(let e of n.selectedOptions)e.selected=!1;for(let e of t.selectedOptions)n.options[e.index].selected=!0}for(let e of t.querySelectorAll(`input[type=\"password\"]`))e.value=``;for(let e of t.querySelectorAll(`noscript`))e.remove();return new e(this.documentElement,t,this.headSnapshot)}get lang(){return this.documentElement.getAttribute(`lang`)}get dir(){return this.documentElement.getAttribute(`dir`)}get headElement(){return this.headSnapshot.element}get rootLocation(){return X(this.getSetting(`root`)??`/`)}get cacheControlValue(){return this.getSetting(`cache-control`)}get isPreviewable(){return this.cacheControlValue!=`no-preview`}get isCacheable(){return this.cacheControlValue!=`no-cache`}get isVisitable(){return this.getSetting(`visit-control`)!=`reload`}get prefersViewTransitions(){return(this.getSetting(`view-transition`)===`true`||this.headSnapshot.getMetaValue(`view-transition`)===`same-origin`)&&!window.matchMedia(`(prefers-reduced-motion: reduce)`).matches}get refreshMethod(){return this.getSetting(`refresh-method`)}get refreshScroll(){return this.getSetting(`refresh-scroll`)}getSetting(e){return this.headSnapshot.getMetaValue(`turbo-${e}`)}},al=class{#e=!1;#t=Promise.resolve();renderChange(e,t){return e&&this.viewTransitionsAvailable&&!this.#e?(this.#e=!0,this.#t=this.#t.then(async()=>{await document.startViewTransition(t).finished})):this.#t=this.#t.then(t),this.#t}get viewTransitionsAvailable(){return document.startViewTransition}};let ol={action:`advance`,historyChanged:!1,visitCachedSnapshot:()=>{},willRender:!0,updateHistory:!0,shouldCacheSnapshot:!0,acceptsStreamResponse:!1,refresh:{}},sl={visitStart:`visitStart`,requestStart:`requestStart`,requestEnd:`requestEnd`,visitEnd:`visitEnd`},cl={initialized:`initialized`,started:`started`,canceled:`canceled`,failed:`failed`,completed:`completed`},ll={networkFailure:0,timeoutFailure:-1,contentTypeMismatch:-2},ul={advance:`forward`,restore:`back`,replace:`none`};var dl=class{identifier=vs();timingMetrics={};followedRedirect=!1;historyChanged=!1;scrolled=!1;shouldCacheSnapshot=!0;acceptsStreamResponse=!1;snapshotCached=!1;state=cl.initialized;viewTransitioner=new al;constructor(e,t,n,r={}){this.delegate=e,this.location=t,this.restorationIdentifier=n||vs();let{action:i,historyChanged:a,referrer:o,snapshot:s,snapshotHTML:c,response:l,visitCachedSnapshot:u,willRender:d,updateHistory:f,shouldCacheSnapshot:p,acceptsStreamResponse:m,direction:h,refresh:g}={...ol,...r};this.action=i,this.historyChanged=a,this.referrer=o,this.snapshot=s,this.snapshotHTML=c,this.response=l,this.isPageRefresh=this.view.isPageRefresh(this),this.visitCachedSnapshot=u,this.willRender=d,this.updateHistory=f,this.scrolled=!d,this.shouldCacheSnapshot=p,this.acceptsStreamResponse=m,this.direction=h||ul[i],this.refresh=g}get adapter(){return this.delegate.adapter}get view(){return this.delegate.view}get history(){return this.delegate.history}get restorationData(){return this.history.getRestorationDataForIdentifier(this.restorationIdentifier)}start(){this.state==cl.initialized&&(this.recordTimingMetric(sl.visitStart),this.state=cl.started,this.adapter.visitStarted(this),this.delegate.visitStarted(this))}cancel(){this.state==cl.started&&(this.request&&this.request.cancel(),this.cancelRender(),this.state=cl.canceled)}complete(){this.state==cl.started&&(this.recordTimingMetric(sl.visitEnd),this.adapter.visitCompleted(this),this.state=cl.completed,this.followRedirect(),this.followedRedirect||this.delegate.visitCompleted(this))}fail(){this.state==cl.started&&(this.state=cl.failed,this.adapter.visitFailed(this),this.delegate.visitCompleted(this))}changeHistory(){if(!this.historyChanged&&this.updateHistory){let e=ws(this.location.href===this.referrer?.href?`replace`:this.action);this.history.update(e,this.location,this.restorationIdentifier),this.historyChanged=!0}}issueRequest(){this.hasPreloadedResponse()?this.simulateRequest():this.shouldIssueRequest()&&!this.request&&(this.request=new rc(this,Z.get,this.location),this.request.perform())}simulateRequest(){this.response&&(this.startRequest(),this.recordResponse(),this.finishRequest())}startRequest(){this.recordTimingMetric(sl.requestStart),this.adapter.visitRequestStarted(this)}recordResponse(e=this.response){if(this.response=e,e){let{statusCode:t}=e;fl(t)?this.adapter.visitRequestCompleted(this):this.adapter.visitRequestFailedWithStatusCode(this,t)}}finishRequest(){this.recordTimingMetric(sl.requestEnd),this.adapter.visitRequestFinished(this)}loadResponse(){if(this.response){let{statusCode:e,responseHTML:t}=this.response;this.render(async()=>{if(this.shouldCacheSnapshot&&this.cacheSnapshot(),this.view.renderPromise&&await this.view.renderPromise,fl(e)&&t!=null){let e=Q.fromHTMLString(t);await this.renderPageSnapshot(e,!1),this.adapter.visitRendered(this),this.complete()}else await this.view.renderError(Q.fromHTMLString(t),this),this.adapter.visitRendered(this),this.fail()})}}getCachedSnapshot(){let e=this.view.getCachedSnapshotForLocation(this.location)||this.getPreloadedSnapshot();if(e&&(!zs(this.location)||e.hasAnchor(zs(this.location)))&&(this.action==`restore`||e.isPreviewable))return e}getPreloadedSnapshot(){if(this.snapshotHTML)return Q.fromHTMLString(this.snapshotHTML)}hasCachedSnapshot(){return this.getCachedSnapshot()!=null}loadCachedSnapshot(){let e=this.getCachedSnapshot();if(e){let t=this.shouldIssueRequest();this.render(async()=>{this.cacheSnapshot(),this.isPageRefresh?this.adapter.visitRendered(this):(this.view.renderPromise&&await this.view.renderPromise,await this.renderPageSnapshot(e,t),this.adapter.visitRendered(this),t||this.complete())})}}followRedirect(){this.redirectedToLocation&&!this.followedRedirect&&this.response?.redirected&&(this.adapter.visitProposedToLocation(this.redirectedToLocation,{action:`replace`,response:this.response,shouldCacheSnapshot:!1,willRender:!1}),this.followedRedirect=!0)}prepareRequest(e){this.acceptsStreamResponse&&e.acceptResponseType(lc.contentType)}requestStarted(){this.startRequest()}requestPreventedHandlingResponse(e,t){}async requestSucceededWithResponse(e,t){let n=await t.responseHTML,{redirected:r,statusCode:i}=t;n==null?this.recordResponse({statusCode:ll.contentTypeMismatch,redirected:r}):(this.redirectedToLocation=t.redirected?t.location:void 0,this.recordResponse({statusCode:i,responseHTML:n,redirected:r}))}async requestFailedWithResponse(e,t){let n=await t.responseHTML,{redirected:r,statusCode:i}=t;n==null?this.recordResponse({statusCode:ll.contentTypeMismatch,redirected:r}):this.recordResponse({statusCode:i,responseHTML:n,redirected:r})}requestErrored(e,t){this.recordResponse({statusCode:ll.networkFailure,redirected:!1})}requestFinished(){this.finishRequest()}performScroll(){!this.scrolled&&!this.view.forceReloaded&&!this.view.shouldPreserveScrollPosition(this)&&(this.action==`restore`?this.scrollToRestoredPosition()||this.scrollToAnchor()||this.view.scrollToTop():this.scrollToAnchor()||this.view.scrollToTop(),this.scrolled=!0)}scrollToRestoredPosition(){let{scrollPosition:e}=this.restorationData;if(e)return this.view.scrollToPosition(e),!0}scrollToAnchor(){let e=zs(this.location);if(e!=null)return this.view.scrollToAnchor(e),!0}recordTimingMetric(e){this.timingMetrics[e]=new Date().getTime()}getTimingMetrics(){return{...this.timingMetrics}}hasPreloadedResponse(){return typeof this.response==`object`}shouldIssueRequest(){return this.action==`restore`?!this.hasCachedSnapshot():this.willRender}cacheSnapshot(){this.snapshotCached||=(this.view.cacheSnapshot(this.snapshot).then(e=>e&&this.visitCachedSnapshot(e)),!0)}async render(e){this.cancelRender(),await new Promise(e=>{this.frame=document.visibilityState===`hidden`?setTimeout(()=>e(),0):requestAnimationFrame(()=>e())}),await e(),delete this.frame}async renderPageSnapshot(e,t){await this.viewTransitioner.renderChange(this.view.shouldTransitionTo(e),async()=>{await this.view.renderPage(e,t,this.willRender,this),this.performScroll()})}cancelRender(){this.frame&&(cancelAnimationFrame(this.frame),delete this.frame)}};function fl(e){return e>=200&&e<300}var pl=class{progressBar=new Xc;constructor(e){this.session=e}visitProposedToLocation(e,t){Us(e,this.navigator.rootLocation)?this.navigator.startVisit(e,t?.restorationIdentifier||vs(),t):window.location.href=e.toString()}visitStarted(e){this.location=e.location,this.redirectedToLocation=null,e.loadCachedSnapshot(),e.issueRequest()}visitRequestStarted(e){this.progressBar.setValue(0),e.hasCachedSnapshot()||e.action!=`restore`?this.showVisitProgressBarAfterDelay():this.showProgressBar()}visitRequestCompleted(e){e.loadResponse(),e.response.redirected&&(this.redirectedToLocation=e.redirectedToLocation)}visitRequestFailedWithStatusCode(e,t){switch(t){case ll.networkFailure:case ll.timeoutFailure:case ll.contentTypeMismatch:return this.reload({reason:`request_failed`,context:{statusCode:t}});default:return e.loadResponse()}}visitRequestFinished(e){}visitCompleted(e){this.progressBar.setValue(1),this.hideVisitProgressBar()}pageInvalidated(e){this.reload(e)}visitFailed(e){this.progressBar.setValue(1),this.hideVisitProgressBar()}visitRendered(e){}linkPrefetchingIsEnabledForLocation(e){return!0}formSubmissionStarted(e){this.progressBar.setValue(0),this.showFormProgressBarAfterDelay()}formSubmissionFinished(e){this.progressBar.setValue(1),this.hideFormProgressBar()}showVisitProgressBarAfterDelay(){this.visitProgressBarTimeout=window.setTimeout(this.showProgressBar,this.session.progressBarDelay)}hideVisitProgressBar(){this.progressBar.hide(),this.visitProgressBarTimeout!=null&&(window.clearTimeout(this.visitProgressBarTimeout),delete this.visitProgressBarTimeout)}showFormProgressBarAfterDelay(){this.formProgressBarTimeout??=window.setTimeout(this.showProgressBar,this.session.progressBarDelay)}hideFormProgressBar(){this.progressBar.hide(),this.formProgressBarTimeout!=null&&(window.clearTimeout(this.formProgressBarTimeout),delete this.formProgressBarTimeout)}showProgressBar=()=>{this.progressBar.show()};reload(e){J(`turbo:reload`,{detail:e}),window.location.href=(this.redirectedToLocation||this.location)?.toString()||window.location.href}get navigator(){return this.session.navigator}},ml=class{selector=`[data-turbo-temporary]`;started=!1;start(){this.started||(this.started=!0,addEventListener(`turbo:before-cache`,this.removeTemporaryElements,!1))}stop(){this.started&&(this.started=!1,removeEventListener(`turbo:before-cache`,this.removeTemporaryElements,!1))}removeTemporaryElements=e=>{for(let e of this.temporaryElements)e.remove()};get temporaryElements(){return[...document.querySelectorAll(this.selector)]}},hl=class{constructor(e,t){this.session=e,this.element=t,this.linkInterceptor=new Nc(this,t),this.formSubmitObserver=new Oc(this,t)}start(){this.linkInterceptor.start(),this.formSubmitObserver.start()}stop(){this.linkInterceptor.stop(),this.formSubmitObserver.stop()}shouldInterceptLinkClick(e,t,n){return this.#t(e)}linkClickIntercepted(e,t,n){let r=this.#n(e);r&&r.delegate.linkClickIntercepted(e,t,n)}willSubmitForm(e,t){return e.closest(`turbo-frame`)==null&&this.#e(e,t)&&this.#t(e,t)}formSubmitted(e,t){let n=this.#n(e,t);n&&n.delegate.formSubmitted(e,t)}#e(e,t){let n=Bs(e,t),r=X(this.element.ownerDocument.querySelector(`meta[name=\"turbo-root\"]`)?.content??`/`);return this.#t(e,t)&&Us(n,r)}#t(e,t){if(e instanceof HTMLFormElement?this.session.submissionIsNavigatable(e,t):this.session.elementIsNavigatable(e)){let n=this.#n(e,t);return n?n!=e.closest(`turbo-frame`):!1}else return!1}#n(e,t){let n=t?.getAttribute(`data-turbo-frame`)||e.getAttribute(`data-turbo-frame`);if(n&&n!=`_top`){let e=this.element.querySelector(`#${n}:not([disabled])`);if(e instanceof q)return e}}},gl=class{location;restorationIdentifier=vs();restorationData={};started=!1;currentIndex=0;constructor(e){this.delegate=e}start(){this.started||(addEventListener(`popstate`,this.onPopState,!1),this.currentIndex=history.state?.turbo?.restorationIndex||0,this.started=!0,this.replace(new URL(window.location.href)))}stop(){this.started&&=(removeEventListener(`popstate`,this.onPopState,!1),!1)}push(e,t){this.update(history.pushState,e,t)}replace(e,t){this.update(history.replaceState,e,t)}update(e,t,n=vs()){e===history.pushState&&++this.currentIndex;let r={turbo:{restorationIdentifier:n,restorationIndex:this.currentIndex}};e.call(history,r,``,t.href),this.location=t,this.restorationIdentifier=n}getRestorationDataForIdentifier(e){return this.restorationData[e]||{}}updateRestorationData(e){let{restorationIdentifier:t}=this,n=this.restorationData[t];this.restorationData[t]={...n,...e}}assumeControlOfScrollRestoration(){this.previousScrollRestoration||(this.previousScrollRestoration=history.scrollRestoration??`auto`,history.scrollRestoration=`manual`)}relinquishControlOfScrollRestoration(){this.previousScrollRestoration&&(history.scrollRestoration=this.previousScrollRestoration,delete this.previousScrollRestoration)}onPopState=e=>{let{turbo:t}=e.state||{};if(this.location=new URL(window.location.href),t){let{restorationIdentifier:e,restorationIndex:n}=t;this.restorationIdentifier=e;let r=n>this.currentIndex?`forward`:`back`;this.delegate.historyPoppedToLocationWithRestorationIdentifierAndDirection(this.location,e,r),this.currentIndex=n}else this.currentIndex++,this.delegate.historyPoppedWithEmptyState(this.location)}},_l=class{started=!1;#e=null;constructor(e,t){this.delegate=e,this.eventTarget=t}start(){this.started||(this.eventTarget.readyState===`loading`?this.eventTarget.addEventListener(`DOMContentLoaded`,this.#t,{once:!0}):this.#t())}stop(){this.started&&=(this.eventTarget.removeEventListener(`mouseenter`,this.#n,{capture:!0,passive:!0}),this.eventTarget.removeEventListener(`mouseleave`,this.#r,{capture:!0,passive:!0}),this.eventTarget.removeEventListener(`turbo:before-fetch-request`,this.#a,!0),!1)}#t=()=>{this.eventTarget.addEventListener(`mouseenter`,this.#n,{capture:!0,passive:!0}),this.eventTarget.addEventListener(`mouseleave`,this.#r,{capture:!0,passive:!0}),this.eventTarget.addEventListener(`turbo:before-fetch-request`,this.#a,!0),this.started=!0};#n=e=>{if(Os(`turbo-prefetch`)===`false`)return;let t=e.target;if(t.matches&&t.matches(`a[href]:not([target^=_]):not([download])`)&&this.#s(t)){let e=t,n=Ws(e);if(this.delegate.canPrefetchRequestToLocation(e,n)){this.#e=e;let r=new rc(this,Z.get,n,new URLSearchParams,t);r.fetchOptions.priority=`low`,hc.putLater(n,r,this.#o)}}};#r=e=>{e.target===this.#e&&this.#i()};#i=()=>{hc.clear(),this.#e=null};#a=e=>{if(e.target.tagName!==`FORM`&&e.detail.fetchOptions.method===`GET`){let t=hc.get(e.detail.url);t&&(e.detail.fetchRequest=t),hc.clear()}};prepareRequest(e){let t=e.target;e.headers[`X-Sec-Purpose`]=`prefetch`;let n=t.closest(`turbo-frame`),r=t.getAttribute(`data-turbo-frame`)||n?.getAttribute(`target`)||n?.id;r&&r!==`_top`&&(e.headers[`Turbo-Frame`]=r)}requestSucceededWithResponse(){}requestStarted(e){}requestErrored(e){}requestFinished(e){}requestPreventedHandlingResponse(e,t){}requestFailedWithResponse(e,t){}get#o(){return Number(Os(`turbo-prefetch-cache-time`))||mc}#s(e){return!(!e.getAttribute(`href`)||vl(e)||yl(e)||bl(e)||xl(e)||Cl(e))}};let vl=e=>e.origin!==document.location.origin||![`http:`,`https:`].includes(e.protocol)||e.hasAttribute(`target`),yl=e=>e.pathname+e.search===document.location.pathname+document.location.search||e.href.startsWith(`#`),bl=e=>{if(e.getAttribute(`data-turbo-prefetch`)===`false`||e.getAttribute(`data-turbo`)===`false`)return!0;let t=js(e,`[data-turbo-prefetch]`);return!!(t&&t.getAttribute(`data-turbo-prefetch`)===`false`)},xl=e=>{let t=e.getAttribute(`data-turbo-method`);return!!(t&&t.toLowerCase()!==`get`||Sl(e)||e.hasAttribute(`data-turbo-confirm`)||e.hasAttribute(`data-turbo-stream`))},Sl=e=>e.hasAttribute(`data-remote`)||e.hasAttribute(`data-behavior`)||e.hasAttribute(`data-confirm`)||e.hasAttribute(`data-method`),Cl=e=>J(`turbo:before-prefetch`,{target:e,cancelable:!0}).defaultPrevented;var wl=class{constructor(e){this.delegate=e}proposeVisit(e,t={}){this.delegate.allowsVisitingLocationWithAction(e,t.action)&&this.delegate.visitProposedToLocation(e,t)}startVisit(e,t,n={}){this.stop(),this.currentVisit=new dl(this,X(e),t,{referrer:this.location,...n}),this.currentVisit.start()}submitForm(e,t){this.stop(),this.formSubmission=new _c(this,e,t,!0),this.formSubmission.start()}stop(){this.formSubmission&&(this.formSubmission.stop(),delete this.formSubmission),this.currentVisit&&(this.currentVisit.cancel(),delete this.currentVisit)}get adapter(){return this.delegate.adapter}get view(){return this.delegate.view}get rootLocation(){return this.view.snapshot.rootLocation}get history(){return this.delegate.history}formSubmissionStarted(e){typeof this.adapter.formSubmissionStarted==`function`&&this.adapter.formSubmissionStarted(e)}async formSubmissionSucceededWithResponse(e,t){if(e==this.formSubmission){let n=await t.responseHTML;if(n){let r=e.isSafe;r||this.view.clearSnapshotCache();let{statusCode:i,redirected:a}=t,o={action:this.#e(e,t),shouldCacheSnapshot:r,response:{statusCode:i,responseHTML:n,redirected:a}};this.proposeVisit(t.location,o)}}}async formSubmissionFailedWithResponse(e,t){let n=await t.responseHTML;if(n){let e=Q.fromHTMLString(n);t.serverError?await this.view.renderError(e,this.currentVisit):await this.view.renderPage(e,!1,!0,this.currentVisit),e.refreshScroll!==`preserve`&&this.view.scrollToTop(),this.view.clearSnapshotCache()}}formSubmissionErrored(e,t){console.error(t)}formSubmissionFinished(e){typeof this.adapter.formSubmissionFinished==`function`&&this.adapter.formSubmissionFinished(e)}linkPrefetchingIsEnabledForLocation(e){return typeof this.adapter.linkPrefetchingIsEnabledForLocation==`function`?this.adapter.linkPrefetchingIsEnabledForLocation(e):!0}visitStarted(e){this.delegate.visitStarted(e)}visitCompleted(e){this.delegate.visitCompleted(e),delete this.currentVisit}locationWithActionIsSamePage(e,t){return!1}get location(){return this.history.location}get restorationIdentifier(){return this.history.restorationIdentifier}#e(e,t){let{submitter:n,formElement:r}=e;return Es(n,r)||this.#t(t)}#t(e){return e.redirected&&e.location.href===this.location?.href?`replace`:`advance`}};let Tl={initial:0,loading:1,interactive:2,complete:3};var El=class{stage=Tl.initial;started=!1;constructor(e){this.delegate=e}start(){this.started||=(this.stage==Tl.initial&&(this.stage=Tl.loading),document.addEventListener(`readystatechange`,this.interpretReadyState,!1),addEventListener(`pagehide`,this.pageWillUnload,!1),!0)}stop(){this.started&&=(document.removeEventListener(`readystatechange`,this.interpretReadyState,!1),removeEventListener(`pagehide`,this.pageWillUnload,!1),!1)}interpretReadyState=()=>{let{readyState:e}=this;e==`interactive`?this.pageIsInteractive():e==`complete`&&this.pageIsComplete()};pageIsInteractive(){this.stage==Tl.loading&&(this.stage=Tl.interactive,this.delegate.pageBecameInteractive())}pageIsComplete(){this.pageIsInteractive(),this.stage==Tl.interactive&&(this.stage=Tl.complete,this.delegate.pageLoaded())}pageWillUnload=()=>{this.delegate.pageWillUnload()};get readyState(){return document.readyState}},Dl=class{started=!1;constructor(e){this.delegate=e}start(){this.started||=(addEventListener(`scroll`,this.onScroll,!1),this.onScroll(),!0)}stop(){this.started&&=(removeEventListener(`scroll`,this.onScroll,!1),!1)}onScroll=()=>{this.updatePosition({x:window.pageXOffset,y:window.pageYOffset})};updatePosition(e){this.delegate.scrollPositionChanged(e)}},Ol=class{render({fragment:e}){Ic.preservingPermanentElements(this,kl(e),()=>{Al(e,()=>{jl(()=>{document.documentElement.appendChild(e)})})})}enteringBardo(e,t){t.replaceWith(e.cloneNode(!0))}leavingBardo(){}};function kl(e){let t=Dc(document.documentElement),n={};for(let r of t){let{id:t}=r;for(let i of e.querySelectorAll(`turbo-stream`)){let e=Ec(i.templateElement.content,t);e&&(n[t]=[r,e])}}return n}async function Al(e,t){let n=`turbo-stream-autofocus-${vs()}`,r=Ml(e.querySelectorAll(`turbo-stream`)),i=null;if(r&&(i=r.id?r.id:n,r.id=i),t(),await fs(),(document.activeElement==null||document.activeElement==document.body)&&i){let e=document.getElementById(i);Ms(e)&&e.focus(),e&&e.id==n&&e.removeAttribute(`id`)}}async function jl(e){let[t,n]=await Ps(e,()=>document.activeElement),r=t&&t.id;if(r){let e=document.getElementById(r);Ms(e)&&e!=n&&e.focus()}}function Ml(e){for(let t of e){let e=Ns(t.templateElement.content);if(e)return e}return null}var Nl=class{sources=new Set;#e=!1;constructor(e){this.delegate=e}start(){this.#e||(this.#e=!0,addEventListener(`turbo:before-fetch-response`,this.inspectFetchResponse,!1))}stop(){this.#e&&(this.#e=!1,removeEventListener(`turbo:before-fetch-response`,this.inspectFetchResponse,!1))}connectStreamSource(e){this.streamSourceIsConnected(e)||(this.sources.add(e),e.addEventListener(`message`,this.receiveMessageEvent,!1))}disconnectStreamSource(e){this.streamSourceIsConnected(e)&&(this.sources.delete(e),e.removeEventListener(`message`,this.receiveMessageEvent,!1))}streamSourceIsConnected(e){return this.sources.has(e)}inspectFetchResponse=e=>{let t=Pl(e);t&&Fl(t)&&(e.preventDefault(),this.receiveMessageResponse(t))};receiveMessageEvent=e=>{this.#e&&typeof e.data==`string`&&this.receiveMessageHTML(e.data)};async receiveMessageResponse(e){let t=await e.responseHTML;t&&this.receiveMessageHTML(t)}receiveMessageHTML(e){this.delegate.receivedMessageFromStream(lc.wrap(e))}};function Pl(e){let t=e.detail?.fetchResponse;if(t instanceof Zs)return t}function Fl(e){return(e.contentType??``).startsWith(lc.contentType)}var Il=class extends Rc{static renderElement(e,t){let{documentElement:n,body:r}=document;n.replaceChild(t,r)}async render(){this.replaceHeadAndBody(),this.activateScriptElements()}replaceHeadAndBody(){let{documentElement:e,head:t}=document;e.replaceChild(this.newHead,t),this.renderElement(this.currentElement,this.newElement)}activateScriptElements(){for(let e of this.scriptElements){let t=e.parentNode;if(t){let n=cs(e);t.replaceChild(n,e)}}}get newHead(){return this.newSnapshot.headSnapshot.element}get scriptElements(){return document.documentElement.querySelectorAll(`script`)}},Ll=class extends Rc{static renderElement(e,t){document.body&&t instanceof HTMLBodyElement?document.body.replaceWith(t):document.documentElement.appendChild(t)}get shouldRender(){return this.newSnapshot.isVisitable&&this.trackedElementsAreIdentical}get reloadReason(){if(!this.newSnapshot.isVisitable)return{reason:`turbo_visit_control_is_reload`};if(!this.trackedElementsAreIdentical)return{reason:`tracked_element_mismatch`}}async prepareToRender(){this.#e(),await this.mergeHead()}async render(){this.willRender&&await this.replaceBody()}finishRendering(){super.finishRendering(),this.isPreview||this.focusFirstAutofocusableElement()}get currentHeadSnapshot(){return this.currentSnapshot.headSnapshot}get newHeadSnapshot(){return this.newSnapshot.headSnapshot}get newElement(){return this.newSnapshot.element}#e(){let{documentElement:e}=this.currentSnapshot,{dir:t,lang:n}=this.newSnapshot;n?e.setAttribute(`lang`,n):e.removeAttribute(`lang`),t?e.setAttribute(`dir`,t):e.removeAttribute(`dir`)}async mergeHead(){let e=this.mergeProvisionalElements(),t=this.copyNewHeadStylesheetElements();this.copyNewHeadScriptElements(),await e,await t,this.willRender&&this.removeUnusedDynamicStylesheetElements()}async replaceBody(){await this.preservingPermanentElements(async()=>{this.activateNewBody(),await this.assignNewBody()})}get trackedElementsAreIdentical(){return this.currentHeadSnapshot.trackedElementSignature==this.newHeadSnapshot.trackedElementSignature}async copyNewHeadStylesheetElements(){let e=[];for(let t of this.newHeadStylesheetElements)e.push(Cs(t)),document.head.appendChild(t);await Promise.all(e)}copyNewHeadScriptElements(){for(let e of this.newHeadScriptElements)document.head.appendChild(cs(e))}removeUnusedDynamicStylesheetElements(){for(let e of this.unusedDynamicStylesheetElements)document.head.removeChild(e)}async mergeProvisionalElements(){let e=[...this.newHeadProvisionalElements];for(let t of this.currentHeadProvisionalElements)this.isCurrentElementInElementList(t,e)||document.head.removeChild(t);for(let t of e)document.head.appendChild(t)}isCurrentElementInElementList(e,t){for(let[n,r]of t.entries()){if(e.tagName==`TITLE`){if(r.tagName!=`TITLE`)continue;if(e.innerHTML==r.innerHTML)return t.splice(n,1),!0}if(r.isEqualNode(e))return t.splice(n,1),!0}return!1}removeCurrentHeadProvisionalElements(){for(let e of this.currentHeadProvisionalElements)document.head.removeChild(e)}copyNewHeadProvisionalElements(){for(let e of this.newHeadProvisionalElements)document.head.appendChild(e)}activateNewBody(){document.adoptNode(this.newElement),this.removeNoscriptElements(),this.activateNewBodyScriptElements()}removeNoscriptElements(){for(let e of this.newElement.querySelectorAll(`noscript`))e.remove()}activateNewBodyScriptElements(){for(let e of this.newBodyScriptElements){let t=cs(e);e.replaceWith(t)}}async assignNewBody(){await this.renderElement(this.currentElement,this.newElement)}get unusedDynamicStylesheetElements(){return this.oldHeadStylesheetElements.filter(e=>e.getAttribute(`data-turbo-track`)===`dynamic`)}get oldHeadStylesheetElements(){return this.currentHeadSnapshot.getStylesheetElementsNotInSnapshot(this.newHeadSnapshot)}get newHeadStylesheetElements(){return this.newHeadSnapshot.getStylesheetElementsNotInSnapshot(this.currentHeadSnapshot)}get newHeadScriptElements(){return this.newHeadSnapshot.getScriptElementsNotInSnapshot(this.currentHeadSnapshot)}get currentHeadProvisionalElements(){return this.currentHeadSnapshot.provisionalElements}get newHeadProvisionalElements(){return this.newHeadSnapshot.provisionalElements}get newBodyScriptElements(){return this.newElement.querySelectorAll(`script`)}},Rl=class extends Ll{static renderElement(e,t){Uc(e,t,{callbacks:{beforeNodeMorphed:(e,t)=>Gc(e,t)&&!qc(e)?(e.reload(),!1):!0}}),J(`turbo:morph`,{detail:{currentElement:e,newElement:t}})}async preservingPermanentElements(e){return await e()}get renderMethod(){return`morph`}get shouldAutofocus(){return!1}},zl=class extends fc{constructor(e){super(e,Ks)}get snapshots(){return this.entries}},Bl=class extends jc{snapshotCache=new zl(10);lastRenderedLocation=new URL(location.href);forceReloaded=!1;shouldTransitionTo(e){return this.snapshot.prefersViewTransitions&&e.prefersViewTransitions}renderPage(e,t=!1,n=!0,r){let i=new(this.isPageRefresh(r)&&(r?.refresh?.method||this.snapshot.refreshMethod)===`morph`?Rl:Ll)(this.snapshot,e,t,n);return i.shouldRender?r?.changeHistory():this.forceReloaded=!0,this.render(i)}renderError(e,t){t?.changeHistory();let n=new Il(this.snapshot,e,!1);return this.render(n)}clearSnapshotCache(){this.snapshotCache.clear()}async cacheSnapshot(e=this.snapshot){if(e.isCacheable){this.delegate.viewWillCacheSnapshot();let{lastRenderedLocation:t}=this;await ms();let n=e.clone();return this.snapshotCache.put(t,n),n}}getCachedSnapshotForLocation(e){return this.snapshotCache.get(e)}isPageRefresh(e){return!e||this.lastRenderedLocation.pathname===e.location.pathname&&e.action===`replace`}shouldPreserveScrollPosition(e){return this.isPageRefresh(e)&&(e?.refresh?.scroll||this.snapshot.refreshScroll)===`preserve`}get snapshot(){return Q.fromElement(this.element)}},Vl=class{selector=`a[data-turbo-preload]`;constructor(e,t){this.delegate=e,this.snapshotCache=t}start(){document.readyState===`loading`?document.addEventListener(`DOMContentLoaded`,this.#e):this.preloadOnLoadLinksForView(document.body)}stop(){document.removeEventListener(`DOMContentLoaded`,this.#e)}preloadOnLoadLinksForView(e){for(let t of e.querySelectorAll(this.selector))this.delegate.shouldPreloadLink(t)&&this.preloadURL(t)}async preloadURL(e){let t=new URL(e.href);this.snapshotCache.has(t)||await new rc(this,Z.get,t,new URLSearchParams,e).perform()}prepareRequest(e){e.headers[`X-Sec-Purpose`]=`prefetch`}async requestSucceededWithResponse(e,t){try{let n=await t.responseHTML,r=Q.fromHTMLString(n);this.snapshotCache.put(e.url,r)}catch{}}requestStarted(e){}requestErrored(e){}requestFinished(e){}requestPreventedHandlingResponse(e,t){}requestFailedWithResponse(e,t){}#e=()=>{this.preloadOnLoadLinksForView(document.body)}},Hl=class{constructor(e){this.session=e}clear(){this.session.clearCache()}resetCacheControl(){this.#e(``)}exemptPageFromCache(){this.#e(`no-cache`)}exemptPageFromPreview(){this.#e(`no-preview`)}#e(e){As(`turbo-cache-control`,e)}},Ul=class{navigator=new wl(this);history=new gl(this);view=new Bl(this,document.documentElement);adapter=new pl(this);pageObserver=new El(this);cacheObserver=new ml;linkPrefetchObserver=new _l(this,document);linkClickObserver=new Pc(this,window);formSubmitObserver=new Oc(this,document);scrollObserver=new Dl(this);streamObserver=new Nl(this);formLinkClickObserver=new Fc(this,document.documentElement);frameRedirector=new hl(this,document.documentElement);streamMessageRenderer=new Ol;cache=new Hl(this);enabled=!0;started=!1;#e=150;constructor(e){this.recentRequests=e,this.preloader=new Vl(this,this.view.snapshotCache),this.debouncedRefresh=this.refresh,this.pageRefreshDebouncePeriod=this.pageRefreshDebouncePeriod}start(){this.started||(this.pageObserver.start(),this.cacheObserver.start(),this.linkPrefetchObserver.start(),this.formLinkClickObserver.start(),this.linkClickObserver.start(),this.formSubmitObserver.start(),this.scrollObserver.start(),this.streamObserver.start(),this.frameRedirector.start(),this.history.start(),this.preloader.start(),this.started=!0,this.enabled=!0)}disable(){this.enabled=!1}stop(){this.started&&=(this.pageObserver.stop(),this.cacheObserver.stop(),this.linkPrefetchObserver.stop(),this.formLinkClickObserver.stop(),this.linkClickObserver.stop(),this.formSubmitObserver.stop(),this.scrollObserver.stop(),this.streamObserver.stop(),this.frameRedirector.stop(),this.history.stop(),this.preloader.stop(),!1)}registerAdapter(e){this.adapter=e}visit(e,t={}){let n=t.frame?document.getElementById(t.frame):null;if(n instanceof q){let r=t.action||Es(n);n.delegate.proposeVisitIfNavigatedWithAction(n,r),n.src=e.toString()}else this.navigator.proposeVisit(X(e),t)}refresh(e,t={}){t=typeof t==`string`?{requestId:t}:t;let{method:n,requestId:r,scroll:i}=t,a=r&&this.recentRequests.has(r),o=e===document.baseURI;!a&&!this.navigator.currentVisit&&o&&this.visit(e,{action:`replace`,shouldCacheSnapshot:!1,refresh:{method:n,scroll:i}})}connectStreamSource(e){this.streamObserver.connectStreamSource(e)}disconnectStreamSource(e){this.streamObserver.disconnectStreamSource(e)}renderStreamMessage(e){this.streamMessageRenderer.render(lc.wrap(e))}clearCache(){this.view.clearSnapshotCache()}setProgressBarDelay(e){console.warn(\"Please replace `session.setProgressBarDelay(delay)` with `session.progressBarDelay = delay`. The function is deprecated and will be removed in a future version of Turbo.`\"),this.progressBarDelay=e}set progressBarDelay(e){Y.drive.progressBarDelay=e}get progressBarDelay(){return Y.drive.progressBarDelay}set drive(e){Y.drive.enabled=e}get drive(){return Y.drive.enabled}set formMode(e){Y.forms.mode=e}get formMode(){return Y.forms.mode}get location(){return this.history.location}get restorationIdentifier(){return this.history.restorationIdentifier}get pageRefreshDebouncePeriod(){return this.#e}set pageRefreshDebouncePeriod(e){this.refresh=Ls(this.debouncedRefresh.bind(this),e),this.#e=e}shouldPreloadLink(e){let t=e.hasAttribute(`data-turbo-method`),n=e.hasAttribute(`data-turbo-stream`),r=e.getAttribute(`data-turbo-frame`),i=r==`_top`?null:document.getElementById(r)||js(e,`turbo-frame:not([disabled])`);if(t||n||i instanceof q)return!1;{let t=new URL(e.href);return this.elementIsNavigatable(e)&&Us(t,this.snapshot.rootLocation)}}historyPoppedToLocationWithRestorationIdentifierAndDirection(e,t,n){this.enabled?this.navigator.startVisit(e,t,{action:`restore`,historyChanged:!0,direction:n}):this.adapter.pageInvalidated({reason:`turbo_disabled`})}historyPoppedWithEmptyState(e){this.history.replace(e),this.view.lastRenderedLocation=e,this.view.cacheSnapshot()}scrollPositionChanged(e){this.history.updateRestorationData({scrollPosition:e})}willSubmitFormLinkToLocation(e,t){return this.elementIsNavigatable(e)&&Us(t,this.snapshot.rootLocation)}submittedFormLinkToLocation(){}canPrefetchRequestToLocation(e,t){return this.elementIsNavigatable(e)&&Us(t,this.snapshot.rootLocation)&&this.navigator.linkPrefetchingIsEnabledForLocation(t)}willFollowLinkToLocation(e,t,n){return this.elementIsNavigatable(e)&&Us(t,this.snapshot.rootLocation)&&this.applicationAllowsFollowingLinkToLocation(e,t,n)}followedLinkToLocation(e,t){let n=this.getActionForLink(e),r=e.hasAttribute(`data-turbo-stream`);this.visit(t.href,{action:n,acceptsStreamResponse:r})}allowsVisitingLocationWithAction(e,t){return this.applicationAllowsVisitingLocation(e)}visitProposedToLocation(e,t){Wl(e),this.adapter.visitProposedToLocation(e,t)}visitStarted(e){e.acceptsStreamResponse||(xs(document.documentElement),this.view.markVisitDirection(e.direction)),Wl(e.location),this.notifyApplicationAfterVisitingLocation(e.location,e.action)}visitCompleted(e){this.view.unmarkVisitDirection(),Ss(document.documentElement),this.notifyApplicationAfterPageLoad(e.getTimingMetrics())}willSubmitForm(e,t){let n=Bs(e,t);return this.submissionIsNavigatable(e,t)&&Us(X(n),this.snapshot.rootLocation)}formSubmitted(e,t){this.navigator.submitForm(e,t)}pageBecameInteractive(){this.view.lastRenderedLocation=this.location,this.notifyApplicationAfterPageLoad()}pageLoaded(){this.history.assumeControlOfScrollRestoration()}pageWillUnload(){this.history.relinquishControlOfScrollRestoration()}receivedMessageFromStream(e){this.renderStreamMessage(e)}viewWillCacheSnapshot(){this.notifyApplicationBeforeCachingSnapshot()}allowsImmediateRender({element:e},t){let{defaultPrevented:n,detail:{render:r}}=this.notifyApplicationBeforeRender(e,t);return this.view.renderer&&r&&(this.view.renderer.renderElement=r),!n}viewRenderedSnapshot(e,t,n){this.view.lastRenderedLocation=this.history.location,this.notifyApplicationAfterRender(n)}preloadOnLoadLinksForView(e){this.preloader.preloadOnLoadLinksForView(e)}viewInvalidated(e){this.adapter.pageInvalidated(e)}frameLoaded(e){this.notifyApplicationAfterFrameLoad(e)}frameRendered(e,t){this.notifyApplicationAfterFrameRender(e,t)}applicationAllowsFollowingLinkToLocation(e,t,n){return!this.notifyApplicationAfterClickingLinkToLocation(e,t,n).defaultPrevented}applicationAllowsVisitingLocation(e){return!this.notifyApplicationBeforeVisitingLocation(e).defaultPrevented}notifyApplicationAfterClickingLinkToLocation(e,t,n){return J(`turbo:click`,{target:e,detail:{url:t.href,originalEvent:n},cancelable:!0})}notifyApplicationBeforeVisitingLocation(e){return J(`turbo:before-visit`,{detail:{url:e.href},cancelable:!0})}notifyApplicationAfterVisitingLocation(e,t){return J(`turbo:visit`,{detail:{url:e.href,action:t}})}notifyApplicationBeforeCachingSnapshot(){return J(`turbo:before-cache`)}notifyApplicationBeforeRender(e,t){return J(`turbo:before-render`,{detail:{newBody:e,...t},cancelable:!0})}notifyApplicationAfterRender(e){return J(`turbo:render`,{detail:{renderMethod:e}})}notifyApplicationAfterPageLoad(e={}){return J(`turbo:load`,{detail:{url:this.location.href,timing:e}})}notifyApplicationAfterFrameLoad(e){return J(`turbo:frame-load`,{target:e})}notifyApplicationAfterFrameRender(e,t){return J(`turbo:frame-render`,{detail:{fetchResponse:e},target:t,cancelable:!0})}submissionIsNavigatable(e,t){if(Y.forms.mode==`off`)return!1;{let n=t?this.elementIsNavigatable(t):!0;return Y.forms.mode==`optin`?n&&e.closest(`[data-turbo=\"true\"]`)!=null:n&&this.elementIsNavigatable(e)}}elementIsNavigatable(e){let t=js(e,`[data-turbo]`),n=js(e,`turbo-frame`);return Y.drive.enabled||n?t?t.getAttribute(`data-turbo`)!=`false`:!0:t?t.getAttribute(`data-turbo`)==`true`:!1}getActionForLink(e){return Es(e)||`advance`}get snapshot(){return this.view.snapshot}};function Wl(e){Object.defineProperties(e,Gl)}let Gl={absoluteURL:{get(){return this.toString()}}},$=new Ul(Qs),{cache:Kl,navigator:ql}=$;function Jl(){$.start()}function Yl(e){$.registerAdapter(e)}function Xl(e,t){$.visit(e,t)}function Zl(e){$.connectStreamSource(e)}function Ql(e){$.disconnectStreamSource(e)}function $l(e){$.renderStreamMessage(e)}function eu(e){console.warn(\"Please replace `Turbo.setProgressBarDelay(delay)` with `Turbo.config.drive.progressBarDelay = delay`. The top-level function is deprecated and will be removed in a future version of Turbo.`\"),Y.drive.progressBarDelay=e}function tu(e){console.warn(\"Please replace `Turbo.setConfirmMethod(confirmMethod)` with `Turbo.config.forms.confirm = confirmMethod`. The top-level function is deprecated and will be removed in a future version of Turbo.`\"),Y.forms.confirm=e}function nu(e){console.warn(\"Please replace `Turbo.setFormMode(mode)` with `Turbo.config.forms.mode = mode`. The top-level function is deprecated and will be removed in a future version of Turbo.`\"),Y.forms.mode=e}function ru(e,t){Rl.renderElement(e,t)}function iu(e,t){Yc.renderElement(e,t)}var au=Object.freeze({__proto__:null,navigator:ql,session:$,cache:Kl,PageRenderer:Ll,PageSnapshot:Q,FrameRenderer:zc,fetch:$s,config:Y,start:Jl,registerAdapter:Yl,visit:Xl,connectStreamSource:Zl,disconnectStreamSource:Ql,renderStreamMessage:$l,setProgressBarDelay:eu,setConfirmMethod:tu,setFormMode:nu,morphBodyElements:ru,morphTurboFrameElements:iu,morphChildren:Wc,morphElements:Uc}),ou=class extends Error{},su=class{fetchResponseLoaded=e=>Promise.resolve();#e=null;#t=()=>{};#n=!1;#r=!1;#i=new Set;#a=!1;action=null;constructor(e){this.element=e,this.view=new Mc(this,this.element),this.appearanceObserver=new cc(this,this.element),this.formLinkClickObserver=new Fc(this,this.element),this.linkInterceptor=new Nc(this,this.element),this.restorationIdentifier=vs(),this.formSubmitObserver=new Oc(this,this.element)}connect(){this.#n||(this.#n=!0,this.loadingStyle==as.lazy?this.appearanceObserver.start():this.#o(),this.formLinkClickObserver.start(),this.linkInterceptor.start(),this.formSubmitObserver.start())}disconnect(){this.#n&&(this.#n=!1,this.appearanceObserver.stop(),this.formLinkClickObserver.stop(),this.linkInterceptor.stop(),this.formSubmitObserver.stop(),this.element.hasAttribute(`recurse`)||this.#e?.cancel())}disabledChanged(){this.disabled?this.#e?.cancel():this.loadingStyle==as.eager&&this.#o()}sourceURLChanged(){this.#v(`src`)||(this.sourceURL||this.#e?.cancel(),this.element.isConnected&&(this.complete=!1),(this.loadingStyle==as.eager||this.#r)&&this.#o())}sourceURLReloaded(){let{refresh:e,src:t}=this.element;return this.#a=t&&e===`morph`,this.element.removeAttribute(`complete`),this.element.src=null,this.element.src=t,this.element.loaded}loadingStyleChanged(){this.loadingStyle==as.lazy?this.appearanceObserver.start():(this.appearanceObserver.stop(),this.#o())}async#o(){this.enabled&&this.isActive&&!this.complete&&this.sourceURL&&(this.element.loaded=this.#c(X(this.sourceURL)),this.appearanceObserver.stop(),await this.element.loaded,this.#r=!0)}async loadResponse(e){(e.redirected||e.succeeded&&e.isHTML)&&(this.sourceURL=e.response.url);try{let t=await e.responseHTML;if(t){let n=hs(t);Q.fromDocument(n).isVisitable?await this.#s(e,n):await this.#u(e)}}finally{this.#a=!1,this.fetchResponseLoaded=()=>Promise.resolve()}}elementAppearedInViewport(e){this.proposeVisitIfNavigatedWithAction(e,Es(e)),this.#o()}willSubmitFormLinkToLocation(e){return this.#_(e)}submittedFormLinkToLocation(e,t,n){let r=this.#h(e);r&&n.setAttribute(`data-turbo-frame`,r.id)}shouldInterceptLinkClick(e,t,n){return this.#_(e)}linkClickIntercepted(e,t){this.#l(e,t)}willSubmitForm(e,t){return e.closest(`turbo-frame`)==this.element&&this.#_(e,t)}formSubmitted(e,t){this.formSubmission&&this.formSubmission.stop(),this.formSubmission=new _c(this,e,t);let{fetchRequest:n}=this.formSubmission,r=this.#h(e,t);this.prepareRequest(n,r),this.formSubmission.start()}prepareRequest(e,t=this){e.headers[`Turbo-Frame`]=t.id,this.currentNavigationElement?.hasAttribute(`data-turbo-stream`)&&e.acceptResponseType(lc.contentType)}requestStarted(e){xs(this.element)}requestPreventedHandlingResponse(e,t){this.#t()}async requestSucceededWithResponse(e,t){await this.loadResponse(t),this.#t()}async requestFailedWithResponse(e,t){await this.loadResponse(t),this.#t()}requestErrored(e,t){console.error(t),this.#t()}requestFinished(e){Ss(this.element)}formSubmissionStarted({formElement:e}){xs(e,this.#h(e))}formSubmissionSucceededWithResponse(e,t){let n=this.#h(e.formElement,e.submitter);n.delegate.proposeVisitIfNavigatedWithAction(n,Es(e.submitter,e.formElement,n)),n.delegate.loadResponse(t),e.isSafe||$.clearCache()}formSubmissionFailedWithResponse(e,t){this.element.delegate.loadResponse(t),$.clearCache()}formSubmissionErrored(e,t){console.error(t)}formSubmissionFinished({formElement:e}){Ss(e,this.#h(e))}allowsImmediateRender({element:e},t){let{defaultPrevented:n,detail:{render:r}}=J(`turbo:before-frame-render`,{target:this.element,detail:{newFrame:e,...t},cancelable:!0});return this.view.renderer&&r&&(this.view.renderer.renderElement=r),!n}viewRenderedSnapshot(e,t,n){}preloadOnLoadLinksForView(e){$.preloadOnLoadLinksForView(e)}viewInvalidated(){}willRenderFrame(e,t){this.previousFrameElement=e.cloneNode(!0)}visitCachedSnapshot=({element:e})=>{let t=e.querySelector(`#`+this.element.id);t&&this.previousFrameElement&&t.replaceChildren(...this.previousFrameElement.children),delete this.previousFrameElement};async#s(e,t){let n=await this.extractForeignFrameElement(t.body),r=this.#a?Yc:zc;if(n){let t=new Tc(n),i=new r(this,this.view.snapshot,t,!1,!1);this.view.renderPromise&&await this.view.renderPromise,this.changeHistory(),await this.view.render(i),this.complete=!0,$.frameRendered(e,this.element),$.frameLoaded(this.element),await this.fetchResponseLoaded(e)}else this.#d(e)&&this.#f(e)}async#c(e){let t=new rc(this,Z.get,e,new URLSearchParams,this.element);return this.#e?.cancel(),this.#e=t,new Promise(e=>{this.#t=()=>{this.#t=()=>{},this.#e=null,e()},t.perform()})}#l(e,t,n){let r=this.#h(e,n);r.delegate.proposeVisitIfNavigatedWithAction(r,Es(n,e,r)),this.#b(e,()=>{r.src=t})}proposeVisitIfNavigatedWithAction(e,t=null){if(this.action=t,this.action){let t=Q.fromElement(e).clone(),{visitCachedSnapshot:n}=e.delegate;e.delegate.fetchResponseLoaded=async r=>{if(e.src){let{statusCode:i,redirected:a}=r,o={response:{statusCode:i,redirected:a,responseHTML:await r.responseHTML},visitCachedSnapshot:n,willRender:!1,updateHistory:!1,restorationIdentifier:this.restorationIdentifier,snapshot:t};this.action&&(o.action=this.action),$.visit(e.src,o)}}}}changeHistory(){if(this.action){let e=ws(this.action);$.history.update(e,X(this.element.src||``),this.restorationIdentifier)}}async#u(e){console.warn(`The response (${e.statusCode}) from <turbo-frame id=\"${this.element.id}\"> is performing a full page visit due to turbo-visit-control.`),await this.#m(e.response)}#d(e){this.element.setAttribute(`complete`,``);let t=e.response;return!J(`turbo:frame-missing`,{target:this.element,detail:{response:t,visit:async(e,t)=>{e instanceof Response?this.#m(e):$.visit(e,t)}},cancelable:!0}).defaultPrevented}#f(e){this.view.missing(),this.#p(e)}#p(e){throw new ou(`The response (${e.statusCode}) did not contain the expected <turbo-frame id=\"${this.element.id}\"> and will be ignored. To perform a full page visit instead, set turbo-visit-control to reload.`)}async#m(e){let t=new Zs(e),n=await t.responseHTML,{location:r,redirected:i,statusCode:a}=t;return $.visit(r,{response:{redirected:i,statusCode:a,responseHTML:n}})}#h(e,t){let n=ys(`data-turbo-frame`,t,e)||this.element.getAttribute(`target`),r=this.#x(n);return r instanceof q?r:this.element}async extractForeignFrameElement(e){let t,n=CSS.escape(this.id);try{if(t=cu(e.querySelector(`turbo-frame#${n}`),this.sourceURL),t)return t;if(t=cu(e.querySelector(`turbo-frame[src][recurse~=${n}]`),this.sourceURL),t)return await t.loaded,await this.extractForeignFrameElement(t)}catch(e){return console.error(e),new q}return null}#g(e,t){return Us(X(Bs(e,t)),this.rootLocation)}#_(e,t){let n=ys(`data-turbo-frame`,t,e)||this.element.getAttribute(`target`);if(e instanceof HTMLFormElement&&!this.#g(e,t)||!this.enabled||n==`_top`)return!1;if(n){let e=this.#x(n);if(e)return!e.disabled;if(n==`_parent`)return!1}return!(!$.elementIsNavigatable(e)||t&&!$.elementIsNavigatable(t))}get id(){return this.element.id}get disabled(){return this.element.disabled}get enabled(){return!this.disabled}get sourceURL(){if(this.element.src)return this.element.src}set sourceURL(e){this.#y(`src`,()=>{this.element.src=e??null})}get loadingStyle(){return this.element.loading}get isLoading(){return this.formSubmission!==void 0||this.#t()!==void 0}get complete(){return this.element.hasAttribute(`complete`)}set complete(e){e?this.element.setAttribute(`complete`,``):this.element.removeAttribute(`complete`)}get isActive(){return this.element.isActive&&this.#n}get rootLocation(){return X(this.element.ownerDocument.querySelector(`meta[name=\"turbo-root\"]`)?.content??`/`)}#v(e){return this.#i.has(e)}#y(e,t){this.#i.add(e),t(),this.#i.delete(e)}#b(e,t){this.currentNavigationElement=e,t(),delete this.currentNavigationElement}#x(e){if(e!=null){let t=e===`_parent`?this.element.parentElement.closest(`turbo-frame`):document.getElementById(e);if(t instanceof q)return t}}};function cu(e,t){if(e){let n=e.getAttribute(`src`);if(n!=null&&t!=null&&qs(n,t))throw Error(`Matching <turbo-frame id=\"${e.id}\"> element has a source URL which references itself`);if(e.ownerDocument!==document&&(e=document.importNode(e,!0)),e instanceof q)return e.connectedCallback(),e.disconnectedCallback(),e}}let lu={after(){this.removeDuplicateTargetSiblings(),this.targetElements.forEach(e=>e.parentElement?.insertBefore(this.templateContent,e.nextSibling))},append(){this.removeDuplicateTargetChildren(),this.targetElements.forEach(e=>e.append(this.templateContent))},before(){this.removeDuplicateTargetSiblings(),this.targetElements.forEach(e=>e.parentElement?.insertBefore(this.templateContent,e))},prepend(){this.removeDuplicateTargetChildren(),this.targetElements.forEach(e=>e.prepend(this.templateContent))},remove(){this.targetElements.forEach(e=>e.remove())},replace(){let e=this.getAttribute(`method`);this.targetElements.forEach(t=>{e===`morph`?Uc(t,this.templateContent):t.replaceWith(this.templateContent)})},update(){let e=this.getAttribute(`method`);this.targetElements.forEach(t=>{e===`morph`?Wc(t,this.templateContent):(t.innerHTML=``,t.append(this.templateContent))})},refresh(){let e=this.getAttribute(`method`),t=this.requestId,n=this.getAttribute(`scroll`);$.refresh(this.baseURI,{method:e,requestId:t,scroll:n})}};var uu=class e extends HTMLElement{static async renderElement(e){await e.performAction()}async connectedCallback(){try{await this.render()}catch(e){console.error(e)}finally{this.disconnect()}}async render(){return this.renderPromise??=(async()=>{let e=this.beforeRenderEvent;this.dispatchEvent(e)&&(await fs(),await e.detail.render(this))})()}disconnect(){try{this.remove()}catch{}}removeDuplicateTargetChildren(){this.duplicateChildren.forEach(e=>e.remove())}get duplicateChildren(){let e=this.targetElements.flatMap(e=>[...e.children]).filter(e=>!!e.getAttribute(`id`)),t=[...this.templateContent?.children||[]].filter(e=>!!e.getAttribute(`id`)).map(e=>e.getAttribute(`id`));return e.filter(e=>t.includes(e.getAttribute(`id`)))}removeDuplicateTargetSiblings(){this.duplicateSiblings.forEach(e=>e.remove())}get duplicateSiblings(){let e=this.targetElements.flatMap(e=>[...e.parentElement.children]).filter(e=>!!e.id),t=[...this.templateContent?.children||[]].filter(e=>!!e.id).map(e=>e.id);return e.filter(e=>t.includes(e.id))}get performAction(){if(this.action){let e=lu[this.action];if(e)return e;this.#e(`unknown action`)}this.#e(`action attribute is missing`)}get targetElements(){if(this.target)return this.targetElementsById;if(this.targets)return this.targetElementsByQuery;this.#e(`target or targets attribute is missing`)}get templateContent(){return this.templateElement.content.cloneNode(!0)}get templateElement(){if(this.firstElementChild===null){let e=this.ownerDocument.createElement(`template`);return this.appendChild(e),e}else if(this.firstElementChild instanceof HTMLTemplateElement)return this.firstElementChild;this.#e(`first child element must be a <template> element`)}get action(){return this.getAttribute(`action`)}get target(){return this.getAttribute(`target`)}get targets(){return this.getAttribute(`targets`)}get requestId(){return this.getAttribute(`request-id`)}#e(e){throw Error(`${this.description}: ${e}`)}get description(){return(this.outerHTML.match(/<[^>]+>/)??[])[0]??`<turbo-stream>`}get beforeRenderEvent(){return new CustomEvent(`turbo:before-stream-render`,{bubbles:!0,cancelable:!0,detail:{newStream:this,render:e.renderElement}})}get targetElementsById(){let e=this.ownerDocument?.getElementById(this.target);return e===null?[]:[e]}get targetElementsByQuery(){let e=this.ownerDocument?.querySelectorAll(this.targets);return e.length===0?[]:Array.prototype.slice.call(e)}},du=class extends HTMLElement{streamSource=null;connectedCallback(){this.streamSource=this.src.match(/^ws{1,2}:/)?new WebSocket(this.src):new EventSource(this.src),Zl(this.streamSource)}disconnectedCallback(){this.streamSource&&(this.streamSource.close(),Ql(this.streamSource))}get src(){return this.getAttribute(`src`)||``}};q.delegateConstructor=su,customElements.get(`turbo-frame`)===void 0&&customElements.define(`turbo-frame`,q),customElements.get(`turbo-stream`)===void 0&&customElements.define(`turbo-stream`,uu),customElements.get(`turbo-stream-source`)===void 0&&customElements.define(`turbo-stream-source`,du),(()=>{let e=document.currentScript;if(!e||e.hasAttribute(`data-turbo-suppress-warning`))return;let t=e.parentElement;for(;t;){if(t==document.body)return console.warn(gs`\n        You are loading Turbo from a <script> element inside the <body> element. This is probably not what you meant to do!\n\n        Load your application’s JavaScript bundle inside the <head> element instead. <script> elements in <body> are evaluated with each page change.\n\n        For more information, see: https://turbo.hotwired.dev/handbook/building#working-with-script-elements\n\n        ——\n        Suppress this warning by adding a \"data-turbo-suppress-warning\" attribute to: %s\n      `,e.outerHTML);t=t.parentElement}})(),window.Turbo={...au,StreamActions:lu},Jl();let fu=(e,t)=>e===`presence`?window.Echo.join(t):window.Echo[e](t);var pu=class extends HTMLElement{async connectedCallback(){Zl(this),this.subscription=fu(this.type,this.channel).listenToAll((e,t)=>{this.dispatchMessageEvent(t.streams)})}disconnectedCallback(){Ql(this),this.subscription&&=(window.Echo.leave(this.channel),null)}dispatchMessageEvent(e){let t=new MessageEvent(`message`,{data:e});return this.dispatchEvent(t)}get channel(){return this.getAttribute(`channel`)||``}get type(){return this.getAttribute(`type`)||`private`}};window.Pusher=rs.default,window.Waterhole.echoConfig.broadcaster&&(window.Echo=new ns({namespace:`Waterhole.Events`,...window.Waterhole.echoConfig}),window.Echo.registerTurboRequestInterceptor(),customElements.define(`turbo-echo-stream-source`,pu));var mu=class{constructor(e){this.children=[],this.parent=e}delete(e){let t=this.children.indexOf(e);return t===-1?!1:(this.children=this.children.slice(0,t).concat(this.children.slice(t+1)),this.children.length===0&&this.parent.delete(this),!0)}add(e){return this.children.push(e),this}},hu=class e{constructor(e){this.parent=null,this.children={},this.parent=e||null}get(e){return this.children[e]}insert(t){let n=this;for(let r=0;r<t.length;r+=1){let i=t[r],a=n.get(i);if(r===t.length-1)return a instanceof e&&(n.delete(a),a=null),a||(a=new mu(n),n.children[i]=a),a;a instanceof mu&&(a=null),a||(a=new e(n),n.children[i]=a),n=a}return n}delete(e){for(let t in this.children)if(this.children[t]===e){let e=delete this.children[t];return Object.keys(this.children).length===0&&this.parent&&this.parent.delete(this),e}return!1}};let gu={\"¡\":`1`,\"™\":`2`,\"£\":`3`,\"¢\":`4`,\"∞\":`5`,\"§\":`6`,\"¶\":`7`,\"•\":`8`,ª:`9`,º:`0`,\"–\":`-`,\"≠\":`=`,\"⁄\":`!`,\"€\":`@`,\"‹\":`#`,\"›\":`$`,ﬁ:`%`,ﬂ:`^`,\"‡\":`&`,\"°\":`*`,\"·\":`(`,\"‚\":`)`,\"—\":`_`,\"±\":`+`,œ:`q`,\"∑\":`w`,\"®\":`r`,\"†\":`t`,\"¥\":`y`,ø:`o`,π:`p`,\"“\":`[`,\"‘\":`]`,\"«\":`\\\\`,Œ:`Q`,\"„\":`W`,\"´\":`E`,\"‰\":`R`,ˇ:`T`,Á:`Y`,\"¨\":`U`,ˆ:`I`,Ø:`O`,\"∏\":`P`,\"”\":`{`,\"’\":`}`,\"»\":`|`,å:`a`,ß:`s`,\"∂\":`d`,ƒ:`f`,\"©\":`g`,\"˙\":`h`,\"∆\":`j`,\"˚\":`k`,\"¬\":`l`,\"…\":`;`,æ:`'`,Å:`A`,Í:`S`,Î:`D`,Ï:`F`,\"˝\":`G`,Ó:`H`,Ô:`J`,\"\":`K`,Ò:`L`,Ú:`:`,Æ:`\"`,Ω:`z`,\"≈\":`x`,ç:`c`,\"√\":`v`,\"∫\":`b`,µ:`m`,\"≤\":`,`,\"≥\":`.`,\"÷\":`/`,\"¸\":`Z`,\"˛\":`X`,Ç:`C`,\"◊\":`V`,ı:`B`,\"˜\":`N`,Â:`M`,\"¯\":`<`,\"˘\":`>`,\"¿\":`?`},_u={\"`\":`~`,1:`!`,2:`@`,3:`#`,4:`$`,5:`%`,6:`^`,7:`&`,8:`*`,9:`(`,0:`)`,\"-\":`_`,\"=\":`+`,\"[\":`{`,\"]\":`}`,\"\\\\\":`|`,\";\":`:`,\"'\":`\"`,\",\":`<`,\".\":`>`,\"/\":`?`,q:`Q`,w:`W`,e:`E`,r:`R`,t:`T`,y:`Y`,u:`U`,i:`I`,o:`O`,p:`P`,a:`A`,s:`S`,d:`D`,f:`F`,g:`G`,h:`H`,j:`J`,k:`K`,l:`L`,z:`Z`,x:`X`,c:`C`,v:`V`,b:`B`,n:`N`,m:`M`},vu={\" \":`Space`,\"+\":`Plus`};function yu(e,t=navigator.platform){let{ctrlKey:n,altKey:r,metaKey:i,shiftKey:a,key:o}=e,s=[],c=[n,r,i,a];for(let[e,t]of c.entries())t&&s.push(bu[e]);if(!bu.includes(o)){let e=s.includes(`Alt`)&&Su.test(t)?gu[o]??o:o,n=s.includes(`Shift`)&&Su.test(t)?_u[e]??e:e,r=vu[n]??n;s.push(r)}return s.join(`+`)}let bu=[`Control`,`Alt`,`Meta`,`Shift`];function xu(e,t){let n;return n=Cu(e,t),n=wu(n),n}let Su=/Mac|iPod|iPhone|iPad/i;function Cu(e,t){let n=t??(typeof window>`u`?void 0:window)?.navigator.platform??``,r=Su.test(n)?`Meta`:`Control`;return e.replace(`Mod`,r)}function wu(e){let t=e.split(`+`).pop(),n=[];for(let t of[`Control`,`Alt`,`Meta`,`Shift`])e.includes(t)&&n.push(t);return t&&n.push(t),n.join(`+`)}var Tu=class e{constructor({onReset:e}={}){this._path=[],this.timer=null,this.onReset=e}get path(){return this._path}get sequence(){return this._path.join(` `)}registerKeypress(e){this._path=[...this._path,yu(e)],this.startTimer()}reset(){var e;this.killTimer(),this._path=[],(e=this.onReset)==null||e.call(this)}killTimer(){this.timer!=null&&window.clearTimeout(this.timer),this.timer=null}startTimer(){this.killTimer(),this.timer=window.setTimeout(()=>this.reset(),e.CHORD_TIMEOUT)}};Tu.CHORD_TIMEOUT=1500;function Eu(e){if(!(e instanceof HTMLElement))return!1;let t=e.nodeName.toLowerCase(),n=(e.getAttribute(`type`)||``).toLowerCase();return t===`select`||t===`textarea`||t===`input`&&n!==`submit`&&n!==`reset`&&n!==`checkbox`&&n!==`radio`&&n!==`file`||e.isContentEditable}function Du(e,t){let n=new CustomEvent(`hotkey-fire`,{cancelable:!0,detail:{path:t}});e.dispatchEvent(n)&&(Eu(e)?e.focus():e.click())}function Ou(e){let t=[],n=[``],r=!1;for(let i=0;i<e.length;i++){if(r&&e[i]===`,`){t.push(n),n=[``],r=!1;continue}if(e[i]===` `){n.push(``),r=!1;continue}else r=e[i]!==`+`;n[n.length-1]+=e[i]}return t.push(n),t.map(e=>e.map(e=>xu(e)).filter(e=>e!==``)).filter(e=>e.length>0)}let ku=new hu,Au=new WeakMap,ju=ku,Mu=new Tu({onReset(){ju=ku}});function Nu(e){if(e.defaultPrevented||!(e.target instanceof Node))return;if(Eu(e.target)){let t=e.target;if(!t.id||!t.ownerDocument.querySelector(`[data-hotkey-scope=\"${t.id}\"]`))return}let t=ju.get(yu(e));if(!t){Mu.reset();return}if(Mu.registerKeypress(e),ju=t,t instanceof mu){let n=e.target,r=!1,i,a=Eu(n);for(let e=t.children.length-1;e>=0;--e){i=t.children[e];let o=i.getAttribute(`data-hotkey-scope`);if(!a&&!o||a&&n.id===o){r=!0;break}}i&&r&&(Du(i,Mu.path),e.preventDefault()),Mu.reset()}}function Pu(e,t){Object.keys(ku.children).length===0&&document.addEventListener(`keydown`,Nu);let n=Ou(t||e.getAttribute(`data-hotkey`)||``).map(t=>ku.insert(t).add(e));Au.set(e,n)}function Fu(e){e.target.querySelectorAll(`[data-hotkey]`).forEach(e=>{Pu(e)})}document.addEventListener(`DOMContentLoaded`,Fu),document.addEventListener(`turbo:render`,Fu),document.addEventListener(`turbo:frame-render`,Fu);function Iu(e){return e.altKey||e.ctrlKey||e.metaKey||e.shiftKey||e.button!==void 0&&e.button!==0}function Lu(e,t=1){let n=e.getBoundingClientRect();return-n.top/n.height<t&&(n.bottom-window.innerHeight)/n.height<t}function Ru(){return document.getElementById(`header`)?.offsetHeight||0}function zu(e){return document.getElementById(e)?.content?.firstElementChild?.cloneNode(!0)}function Bu(e){let t=document.cookie.match(RegExp(`(^|;\\\\s*)(`+e+`)=([^;]*)`));return t?decodeURIComponent(t[3]):null}function Vu(e){return Object.entries(e).map(([e,t])=>({identifier:e.match(/\\.\\/controllers\\/(.*)\\.ts$/)[1].replace(/\\//g,`--`).replace(/_/g,`-`).replace(/-controller$/,``),controllerConstructor:t.default}))}window.Turbo=is;let Hu=null;document.addEventListener(`turbo:before-render`,e=>{Hu=e.detail.newBody.querySelector(`#alerts`)}),document.addEventListener(`turbo:load`,e=>{Hu&&[...Hu.children].forEach(e=>Waterhole.alerts.show(e))}),document.addEventListener(`turbo:before-fetch-response`,async e=>{let t=e.detail.fetchResponse.response;if(t.ok||t.status===422)return;e.preventDefault();let{target:n}=e;if(n instanceof q){let e=zu(`frame-error`);e.querySelector(`button`)?.addEventListener(`click`,()=>{n.reload()}),n.replaceChildren(e)}else Waterhole.fetchError(t)}),document.addEventListener(`turbo:frame-missing`,async e=>{e.preventDefault();let{detail:t}=e;t.visit(t.response,{action:`replace`})}),Waterhole.fetchError=async function(e){let t;switch(e?.status){case 401:case 403:t=`forbidden-alert`;break;case 419:t=`session-expired-alert`;break;case 422:let n=zu(`template-alert-danger`);n.querySelector(`.alert__message`).textContent=(await e.json()).message,Waterhole.alerts.show(n,{key:`fetchError`});break;case 429:t=`too-many-requests-alert`;break;default:t=`fatal-error-alert`}if(t){let e=zu(t);e&&Waterhole.alerts.show(e,{key:`fetchError`})}};var Uu=s({default:()=>Wu}),Wu=class extends L{static{this.targets=[`frame`]}preload(){this.hasFrameTarget&&this.frameTarget?.setAttribute(`loading`,`eager`)}},Gu=s({default:()=>Ku}),Ku=class extends L{dismiss(e){let t=this.element.closest(`ui-alerts`);t?t.dismiss(this.element):this.element.remove()}},qu=s({default:()=>Yu});let Ju={};var Yu=class extends L{static{this.targets=[`parentTooltip`]}get commentId(){return this.element.getAttribute(`data-comment-id`)||``}get parentId(){return this.element.getAttribute(`data-parent-id`)||``}get parentElements(){return Array.from(document.querySelectorAll(`[data-comment-id=\"${this.parentId}\"]`))}connect(){Ju[this.commentId]&&this.toggleExpanded()}disconnect(){Ju[this.commentId]=this.element.classList.contains(`is-expanded`)}toggleExpanded(){this.element.classList.toggle(`is-expanded`)}highlightParent(){this.parentElements.forEach(e=>{e.classList.add(`is-highlighted`)}),this.parentTooltipTarget&&(this.parentTooltipTarget.disabled=this.parentElements.some(e=>Lu(e,.5)))}stopHighlightingParent(){this.parentElements.forEach(e=>{e.classList.remove(`is-highlighted`)})}},Xu=s({default:()=>Zu}),Zu=class extends L{connect(){this.element.addEventListener(`click`,e=>{let t=this.element.getAttribute(`aria-expanded`)===`false`;this.element.setAttribute(`aria-expanded`,String(t));let n=this.element.closest(`.comment`)?.querySelector(`.comment__replies`);n&&(n.hidden=!t),t||e.preventDefault()})}focusAfterLoad(){addEventListener(`turbo:frame-render`,e=>{let t=window.scrollY;e.target.querySelector(`.comment__replies`)?.focus(),window.scroll({top:t})},{once:!0})}},Qu=function(e,t,n,r){function i(e){return e instanceof n?e:new n(function(t){t(e)})}return new(n||=Promise)(function(n,a){function o(e){try{c(r.next(e))}catch(e){a(e)}}function s(e){try{c(r.throw(e))}catch(e){a(e)}}function c(e){e.done?n(e.value):i(e.value).then(o,s)}c((r=r.apply(e,t||[])).next())})};function $u(e){let t=0,n=0,r=e;do t+=r.offsetTop||0,n+=r.offsetLeft||0,r=r.offsetParent;while(r);return{top:t,left:n}}var ed=class{constructor(e){this.element=e}getHorizontalScroll(){return this.element.scrollLeft}getVerticalScroll(){return this.element.scrollTop}getMaxHorizontalScroll(){return this.element.scrollWidth-this.element.clientWidth}getMaxVerticalScroll(){return this.element.scrollHeight-this.element.clientHeight}getHorizontalElementScrollOffset(e,t){return $u(e).left-$u(t).left}getVerticalElementScrollOffset(e,t){return $u(e).top-$u(t).top}scrollTo(e,t){this.element.scrollLeft=e,this.element.scrollTop=t}},td=class{constructor(){this.element=window}getHorizontalScroll(){return window.scrollX||document.documentElement.scrollLeft}getVerticalScroll(){return window.scrollY||document.documentElement.scrollTop}getMaxHorizontalScroll(){return Math.max(document.body.scrollWidth,document.documentElement.scrollWidth,document.body.offsetWidth,document.documentElement.offsetWidth,document.body.clientWidth,document.documentElement.clientWidth)-window.innerWidth}getMaxVerticalScroll(){return Math.max(document.body.scrollHeight,document.documentElement.scrollHeight,document.body.offsetHeight,document.documentElement.offsetHeight,document.body.clientHeight,document.documentElement.clientHeight)-window.innerHeight}getHorizontalElementScrollOffset(e){return(window.scrollX||document.documentElement.scrollLeft)+e.getBoundingClientRect().left}getVerticalElementScrollOffset(e){return(window.scrollY||document.documentElement.scrollTop)+e.getBoundingClientRect().top}scrollTo(e,t){window.scrollTo(e,t)}};let nd={elements:[],cancelMethods:[],add:(e,t)=>{nd.elements.push(e),nd.cancelMethods.push(t)},remove:(e,t)=>{let n=nd.elements.indexOf(e);n>-1&&(t&&nd.cancelMethods[n](),nd.elements.splice(n,1),nd.cancelMethods.splice(n,1))}},rd=typeof window<`u`,id={cancelOnUserAction:!0,easing:e=>--e*e*e+1,elementToScroll:rd?window:null,horizontalOffset:0,maxDuration:3e3,minDuration:250,speed:500,verticalOffset:0};function ad(e){return Qu(this,arguments,void 0,function*(e,t={}){if(!rd)return new Promise(e=>{e(!1)});if(!window.Promise)throw`Browser doesn't support Promises, and animated-scroll-to depends on it, please provide a polyfill.`;let n,r,i,a=Object.assign(Object.assign({},id),t),o=a.elementToScroll===window,s=!!a.elementToScroll.nodeName;if(!o&&!s)throw`Element to scroll needs to be either window or DOM element.`;let c=[{property:`scroll-behavior`,value:`smooth`},{property:`scroll-snap-type`,value:`mandatory`}],l=o?document.documentElement:a.elementToScroll,u=getComputedStyle(l);c.forEach(({property:e,value:t})=>{let n=u.getPropertyValue(e);n.includes(t)&&console.warn(`${l.tagName} has \"${e}: ${n}\" which can break animated-scroll-to's animations`)});let d=o?new td:new ed(a.elementToScroll);if(e instanceof Element){if(i=e,s&&(!a.elementToScroll.contains(i)||a.elementToScroll.isSameNode(i)))throw`options.elementToScroll has to be a parent of scrollToElement`;n=d.getHorizontalElementScrollOffset(i,a.elementToScroll),r=d.getVerticalElementScrollOffset(i,a.elementToScroll)}else if(typeof e==`number`)n=d.getHorizontalScroll(),r=e;else if(Array.isArray(e)&&e.length===2)n=e[0]===null?d.getHorizontalScroll():e[0],r=e[1]===null?d.getVerticalScroll():e[1];else throw`Wrong function signature. Check documentation.\nAvailable method signatures are:\n  animateScrollTo(y:number, options)\n  animateScrollTo([x:number | null, y:number | null], options)\n  animateScrollTo(scrollToElement:Element, options)`;n+=a.horizontalOffset,r+=a.verticalOffset;let f=d.getMaxHorizontalScroll(),p=d.getHorizontalScroll();n>f&&(n=f);let m=n-p,h=d.getMaxVerticalScroll(),g=d.getVerticalScroll();r>h&&(r=h);let _=r-g,v=Math.abs(Math.round(m/1e3*a.speed)),y=Math.abs(Math.round(_/1e3*a.speed)),b=v>y?v:y;return b<a.minDuration?b=a.minDuration:b>a.maxDuration&&(b=a.maxDuration),new Promise((e,t)=>{m===0&&_===0&&e(!0),nd.remove(d.element,!0);let i,o=()=>{u(),cancelAnimationFrame(i),e(!1)};nd.add(d.element,o);let s=a.cancelOnUserAction?o:e=>e.preventDefault(),c=a.cancelOnUserAction?{passive:!0}:{passive:!1},l=[`wheel`,`touchstart`,`keydown`,`mousedown`],u=()=>{l.forEach(e=>{d.element.removeEventListener(e,s,c)})};l.forEach(e=>{d.element.addEventListener(e,s,c)});let f=Date.now(),h=()=>{var t=Date.now()-f,o=t/b;let s=Math.round(p+m*a.easing(o)),c=Math.round(g+_*a.easing(o));t<b&&(s!==n||c!==r)?(d.scrollTo(s,c),i=requestAnimationFrame(h)):(d.scrollTo(n,r),cancelAnimationFrame(i),u(),nd.remove(d.element,!1),e(!0))};i=requestAnimationFrame(h)})})}var od=ad,sd=s({default:()=>cd}),cd=class extends L{constructor(...e){super(...e),this.onHashChange=()=>{requestAnimationFrame(()=>{window.location.hash===`#reply`&&this.open()})}}static{this.targets=[`handle`]}connect(){let e=Number(localStorage.getItem(`composer_height`));e&&(this.element.style.height=e+`px`),window.addEventListener(`hashchange`,this.onHashChange),this.onHashChange()}disconnect(){window.removeEventListener(`hashchange`,this.onHashChange)}placeholderClick(e){Iu(e)||(e.preventDefault(),this.open(),this.scrollToBottom())}scrollToBottom(){this.element.style.position=`static`;let e=this.element.getBoundingClientRect().top,t=parseInt(getComputedStyle(this.element).height);this.element.style.position=``;let n=scrollY+e+t-window.innerHeight;scrollY>n||od(n,{minDuration:200,maxDuration:200})}open(){this.element.classList.add(`is-open`),setTimeout(()=>this.element.querySelector(`textarea`)?.focus())}close(){this.element.classList.remove(`is-open`),window.location.hash===`#reply`&&history.replaceState(null,``,` `)}submitEnd(e){e.detail.fetchResponse.contentType.startsWith(`text/vnd.turbo-stream.html`)&&this.close()}startResize(e){e.preventDefault();let t=this.element,n=e.clientY,r=t.offsetHeight,i=t.getBoundingClientRect().bottom,a=e=>{let a=r-(e.clientY-n);t.style.height=a+`px`,localStorage.setItem(`composer_height`,String(a)),window.scroll(0,window.scrollY+t.getBoundingClientRect().bottom-i)};document.addEventListener(`pointermove`,a),document.addEventListener(`pointerup`,()=>{document.removeEventListener(`pointermove`,a)},{once:!0})}},ld=s({default:()=>ud}),ud=class extends L{static{this.values={message:String}}async copy(e){e.preventDefault();let t=e.currentTarget.getAttribute(`href`)||``;try{if(await navigator.clipboard.writeText(t),this.hasMessageValue){let e=zu(`template-alert-success`);e.querySelector(`.alert__message`).textContent=this.messageValue,Waterhole.alerts.show(e,{key:`copy-link`})}}catch{window.prompt(``,t)}}},dd=s({default:()=>fd}),fd=class extends L{constructor(...e){super(...e),this.toggle=()=>{if(!this.element.open)return;let e=this.element.querySelector(`:is(button, [href], input, select, textarea, [tabindex]):not([hidden]):not([disabled]):not([type=\"hidden\"]):not([tabindex=\"-1\"])`);e&&e.focus()}}connect(){this.element.addEventListener(`toggle`,this.toggle)}disconnect(){this.element.removeEventListener(`toggle`,this.toggle)}},pd=s({default:()=>md}),md=class extends L{constructor(...e){super(...e),this.lockScrollPosition=e=>{e.target===e.currentTarget&&(this.anchor=this.element.nextElementSibling,this.anchor&&(this.top=this.anchor.getBoundingClientRect().top),this.observer?.disconnect(),this.observer=new MutationObserver(()=>this.restore()),this.observer.observe(document.body,{subtree:!0,childList:!0,attributes:!0}))},this.unlockScrollPosition=e=>{e&&e.target!==e.currentTarget||setTimeout(()=>{this.observer?.disconnect(),delete this.observer})}}connect(){this.element.addEventListener(`turbo:before-fetch-response`,this.lockScrollPosition),this.element.addEventListener(`turbo:frame-render`,this.unlockScrollPosition)}disconnect(){this.element.removeEventListener(`turbo:before-fetch-response`,this.lockScrollPosition),this.element.removeEventListener(`turbo:frame-render`,this.unlockScrollPosition),this.unlockScrollPosition()}restore(){this.anchor&&this.top&&window.scroll({top:window.scrollY+this.anchor.getBoundingClientRect().top-this.top})}},hd=s({default:()=>gd}),gd=class extends L{connect(){this.element.addEventListener(`submit`,()=>this.element.querySelector(`button[type=\"submit\"]`)?.setAttribute(`disabled`,``))}disconnect(){let e=this.element.querySelector(`input[name=email]`)?.value||``;document.addEventListener(`turbo:load`,()=>{let t=document.querySelector(`input[name=email]`);t&&(t.value=e)},{once:!0})}},_d=s({default:()=>vd}),vd=class extends L{constructor(...e){super(...e),this.onTextExpanderChange=(e=>{let{provide:t,text:n}=e.detail,r=new URL(this.userLookupUrlValue);r.searchParams.append(`q`,n),t(Waterhole.fetch(r).json().then(e=>{let t=document.createElement(`ul`);return t.setAttribute(`role`,`listbox`),t.className=`menu`,t.style.position=`absolute`,t.style.marginTop=`24px`,t.append(...e.map(({name:e,html:t,commentUrl:n,frameId:r})=>{let i=document.createElement(`li`);return i.setAttribute(`role`,`option`),i.id=`suggestion-${Math.floor(Math.random()*1e5).toString()}`,i.className=`menu-item`,i.dataset.value=e,i.dataset.commentUrl=n||``,i.dataset.frameId=r||``,i.innerHTML=t,i})),new MutationObserver(()=>{t.getBoundingClientRect().bottom>window.innerHeight&&(t.style.transform=`translateY(-100%)`,t.style.marginTop=`-12px`)}).observe(t,{attributes:!0,attributeFilter:[`style`]}),{matched:!!e.length,fragment:t}}))}),this.onTextExpanderValue=(e=>{let{item:t}=e.detail;e.detail.value=`@`+t.dataset.value.replace(/ /g,`\\xA0`);let{commentUrl:n,frameId:r}=t.dataset;n&&Xl(n,{frame:r})})}static{this.values={userLookupUrl:String}}connect(){this.element.addEventListener(`text-expander-change`,this.onTextExpanderChange),this.element.addEventListener(`text-expander-value`,this.onTextExpanderValue)}disconnect(){this.element.removeEventListener(`text-expander-change`,this.onTextExpanderChange),this.element.removeEventListener(`text-expander-value`,this.onTextExpanderValue)}},yd=s({default:()=>bd}),bd=class extends L{static{this.targets=[`frame`,`loading`]}connect(){this.frameTarget.removeAttribute(`disabled`)}loading(){this.element.open||(this.frameTarget.hidden=!0,this.loadingTarget.hidden=!1),this.show()}loaded(){this.frameTarget.hidden=!1,this.loadingTarget.hidden=!0,this.frameTarget.children.length?this.show():this.hide()}show(){this.element.open||(this.element.open=!0)}hide(e){e instanceof MouseEvent&&e.preventDefault(),this.element.open&&this.element.close()}},xd=s({default:()=>Sd}),Sd=class extends L{static{this.targets=[`badge`,`frame`,`sm`]}open(e){this.frameTarget.reload(),this.element.hasAttribute(`data-persistent-badge`)||(this.badgeTarget.hidden=!0),Waterhole.alerts.dismiss(`notification`),getComputedStyle(this.smTarget).display===`none`&&(window.Turbo.visit(e.currentTarget.href),this.element.open=!1)}},Cd=s({default:()=>wd}),wd=class extends L{static{this.targets=[`breadcrumb`,`title`]}initialize(){this.observer=new IntersectionObserver(e=>{this.hasBreadcrumbTarget&&(this.hideBreadcrumb&&cancelAnimationFrame(this.hideBreadcrumb),this.breadcrumbTarget.hidden=e[0].isIntersecting,e[0].isIntersecting||(this.breadcrumbTarget.innerHTML=e[0].target.innerHTML||``))},{rootMargin:`-${Ru()}px`})}connect(){this.hideBreadcrumb=requestAnimationFrame(()=>{this.hasBreadcrumbTarget&&(this.breadcrumbTarget.hidden=!0)})}titleTargetConnected(e){this.observer.observe(e)}titleTargetDisconnected(){this.observer.disconnect()}incrementDocumentTitle(){Waterhole.documentTitle.increment()}closeModal(){document.querySelector(`#modal-element`)?.close()}},Td=s({default:()=>Ed}),Ed=class extends L{appearAsRead(){this.element.classList.contains(`is-unread`)&&(this.element.classList.remove(`is-unread`,`is-new`),this.element.classList.add(`is-read`))}},Dd=s({default:()=>Od}),Od=class extends L{static{this.targets=[`newActivity`]}static{this.values={filter:String,channels:Array,publicChannels:Array}}connect(){this.channelsValue?.forEach(e=>{let t=this.publicChannelsValue?.includes(e)?`channel`:`private`;window.Echo[t](`Waterhole.Models.Channel.${e}`).listen(`NewComment`,()=>{this.filterValue===`latest`&&this.showNewActivity()}).listen(`NewPost`,()=>{(this.filterValue===`newest`||this.filterValue===`latest`)&&this.showNewActivity()})})}disconnect(){this.channelsValue?.forEach(e=>{window.Echo.leave(`Waterhole.Models.Channel.${e}`)})}showNewActivity(){this.newActivityTarget&&(this.newActivityTarget.hidden=!1,Waterhole.documentTitle.increment())}scrollToTop(){let e=Ru()+20;this.element.getBoundingClientRect().top<e&&od(this.element,{verticalOffset:-e})}},kd=s({default:()=>Ad}),Ad=class extends L{constructor(...e){super(...e),this.showPostOnFirstPage=()=>{document.getElementById(`page_1`)&&(this.postTarget.hidden=!1)},this.beforeStreamRender=e=>{let t=e.target;t.action===`remove`&&t.targets?.endsWith(`post_`+this.idValue)&&(window.history.back(),window.addEventListener(`popstate`,()=>{window.requestAnimationFrame(()=>{$l(t.outerHTML)})},{once:!0}),e.preventDefault())},this.onScroll=()=>{setTimeout(()=>{this.hasCurrentPageTarget&&(this.currentPageTarget.textContent=this.element.querySelector(`.comments-pagination [aria-current=\"page\"]`)?.textContent||`1`)}),this.hasCommentsLinksTarget&&this.hasCommentsPaginationTarget&&(this.commentsLinksTarget.hidden=this.postTarget.getBoundingClientRect().bottom<Ru()+10,this.commentsPaginationTarget.hidden=!this.commentsLinksTarget.hidden)}}static{this.targets=[`post`,`currentPage`,`commentsLinks`,`commentsPagination`]}static{this.values={id:Number}}connect(){document.addEventListener(`turbo:before-stream-render`,this.beforeStreamRender),document.addEventListener(`turbo:frame-render`,this.showPostOnFirstPage),window.addEventListener(`scroll`,this.onScroll,{passive:!0}),this.onScroll()}disconnect(){document.removeEventListener(`turbo:before-stream-render`,this.beforeStreamRender),document.removeEventListener(`turbo:frame-render`,this.showPostOnFirstPage),window.removeEventListener(`scroll`,this.onScroll)}},jd=s({default:()=>Md}),Md=class extends L{constructor(...e){super(...e),this.handleSelectionChange=()=>{setTimeout(this.updateQuoteButton.bind(this),100)}}static{this.targets=[`button`]}connect(){document.addEventListener(`mouseup`,this.handleSelectionChange)}disconnect(){document.removeEventListener(`mouseup`,this.handleSelectionChange)}async updateQuoteButton(){if(!this.hasButtonTarget)return;this.buttonTarget.hidden=!0;let e=window.getSelection();if(!e||e.isCollapsed||!e.anchorNode||!e.focusNode)return;let t=e.getRangeAt(0),n=t.commonAncestorContainer;if(n!==this.element&&!this.element.contains(n))return;this.buttonTarget.hidden=!1,this.buttonTarget.style.position=`absolute`,this.buttonTarget.style.zIndex=`var(--z-index-overlay)`;let r=e.anchorNode.compareDocumentPosition(e.focusNode),i=t.getClientRects(),a,o;if(r&Node.DOCUMENT_POSITION_PRECEDING||!r&&e.focusOffset<e.anchorOffset){let e=i[0];a=new DOMRect(e.left,e.top),o=`top`}else{let e=i[i.length-1];a=new DOMRect(e.right,e.bottom),o=`bottom`}Co({getBoundingClientRect:()=>a},this.buttonTarget,{placement:o,middleware:[yo(10),bo(),xo()]}).then(({x:e,y:t})=>{Object.assign(this.buttonTarget.style,{left:`${e}px`,top:`${t}px`})})}quoteSelectedText(){let e=document.createElement(`div`),t=window.getSelection();t&&(e.appendChild(t.getRangeAt(0).cloneContents()),e.querySelectorAll(`img`).forEach(e=>e.replaceWith(e.alt)),t.removeAllRanges(),setTimeout(()=>{this.dispatch(`quote-text`,{detail:{text:e.textContent},bubbles:!0,cancelable:!0})}))}},Nd=s({default:()=>Pd}),Pd=class extends L{constructor(...e){super(...e),this.toggle=e=>{let t=e.target,n=t.value;t instanceof HTMLInputElement&&[`checkbox`,`radio`].includes(t.type)&&!t.checked&&(n=``),this.thenTargets.forEach(e=>{if(!e.dataset.revealValue)e.hidden=!n;else{let t=e.dataset.revealValue;try{t=JSON.parse(t)}catch{}finally{t=Array.isArray(t)?t.map(e=>String(e)):[e.dataset.revealValue]}e.hidden=!t.includes(n)}})}}static{this.targets=[`if`,`then`]}ifTargetConnected(e){e.addEventListener(`change`,this.toggle),(e.type!==`radio`||e.checked)&&e.dispatchEvent(new Event(`change`))}ifTargetDisconnected(e){e.removeEventListener(`change`,this.toggle)}},Fd=s({default:()=>Id}),Id=class extends L{constructor(...e){super(...e),this.onScroll=()=>{let e=Array.from(this.links()),t=Ru();e.forEach(e=>e.removeAttribute(`aria-current`)),e.reverse().some(e=>{let n=e.hash.substring(1);if(!n)return;let r=document.getElementById(n);if(r&&r.getBoundingClientRect().top<=t+100)return e.setAttribute(`aria-current`,`page`),this.current!==e&&this.element&&this.element.scroll({top:e.offsetTop+e.offsetHeight/2-this.element.offsetHeight/2,left:e.offsetLeft+e.offsetWidth/2-this.element.offsetWidth/2,behavior:this.current?`smooth`:`auto`}),this.current=e,!0})}}connect(){this.onScroll(),window.addEventListener(`scroll`,this.onScroll)}disconnect(){window.removeEventListener(`scroll`,this.onScroll)}links(){return this.element.querySelectorAll(`a[href*=\"#\"]`)}},Ld=typeof global==`object`&&global&&global.Object===Object&&global,Rd=typeof self==`object`&&self&&self.Object===Object&&self,zd=Ld||Rd||Function(`return this`)(),Bd=zd.Symbol,Vd=Object.prototype,Hd=Vd.hasOwnProperty,Ud=Vd.toString,Wd=Bd?Bd.toStringTag:void 0;function Gd(e){var t=Hd.call(e,Wd),n=e[Wd];try{e[Wd]=void 0;var r=!0}catch{}var i=Ud.call(e);return r&&(t?e[Wd]=n:delete e[Wd]),i}var Kd=Gd,qd=Object.prototype.toString;function Jd(e){return qd.call(e)}var Yd=Jd,Xd=`[object Null]`,Zd=`[object Undefined]`,Qd=Bd?Bd.toStringTag:void 0;function $d(e){return e==null?e===void 0?Zd:Xd:Qd&&Qd in Object(e)?Kd(e):Yd(e)}var ef=$d;function tf(e){return typeof e==`object`&&!!e}var nf=tf,rf=`[object Symbol]`;function af(e){return typeof e==`symbol`||nf(e)&&ef(e)==rf}var of=af,sf=/\\s/;function cf(e){for(var t=e.length;t--&&sf.test(e.charAt(t)););return t}var lf=cf,uf=/^\\s+/;function df(e){return e&&e.slice(0,lf(e)+1).replace(uf,``)}var ff=df;function pf(e){var t=typeof e;return e!=null&&(t==`object`||t==`function`)}var mf=pf,hf=NaN,gf=/^[-+]0x[0-9a-f]+$/i,_f=/^0b[01]+$/i,vf=/^0o[0-7]+$/i,yf=parseInt;function bf(e){if(typeof e==`number`)return e;if(of(e))return hf;if(mf(e)){var t=typeof e.valueOf==`function`?e.valueOf():e;e=mf(t)?t+``:t}if(typeof e!=`string`)return e===0?e:+e;e=ff(e);var n=_f.test(e);return n||vf.test(e)?yf(e.slice(2),n?2:8):gf.test(e)?hf:+e}var xf=bf,Sf=function(){return zd.Date.now()},Cf=`Expected a function`,wf=Math.max,Tf=Math.min;function Ef(e,t,n){var r,i,a,o,s,c,l=0,u=!1,d=!1,f=!0;if(typeof e!=`function`)throw TypeError(Cf);t=xf(t)||0,mf(n)&&(u=!!n.leading,d=`maxWait`in n,a=d?wf(xf(n.maxWait)||0,t):a,f=`trailing`in n?!!n.trailing:f);function p(t){var n=r,a=i;return r=i=void 0,l=t,o=e.apply(a,n),o}function m(e){return l=e,s=setTimeout(_,t),u?p(e):o}function h(e){var n=e-c,r=e-l,i=t-n;return d?Tf(i,a-r):i}function g(e){var n=e-c,r=e-l;return c===void 0||n>=t||n<0||d&&r>=a}function _(){var e=Sf();if(g(e))return v(e);s=setTimeout(_,h(e))}function v(e){return s=void 0,f&&r?p(e):(r=i=void 0,o)}function y(){s!==void 0&&clearTimeout(s),l=0,r=c=i=s=void 0}function b(){return s===void 0?o:v(Sf())}function x(){var e=Sf(),n=g(e);if(r=arguments,i=this,c=e,n){if(s===void 0)return m(c);if(d)return clearTimeout(s),s=setTimeout(_,t),p(c)}return s===void 0&&(s=setTimeout(_,t)),o}return x.cancel=y,x.flush=b,x}var Df=Ef,Of=s({default:()=>kf}),kf=class extends L{constructor(...e){super(...e),this.input=Df(this.submit,250)}static{this.targets=[`submit`,`frame`]}submit(){this.submitTarget?.click()}frameTargetConnected(e){e.addEventListener(`turbo:before-frame-render`,t=>{e.hidden=!t.detail.newFrame.children.length})}},Af=s({default:()=>jf}),jf=class extends L{constructor(...e){super(...e),this.update=()=>{let e=this.unitTarget.value===`indefinite`;this.countTarget.disabled=e,e?this.countTarget.value=``:this.countTarget.value||(this.countTarget.value=`7`)}}static{this.targets=[`count`,`unit`]}connect(){this.update()}};function Mf(e,t){let n=e.value.slice(0,e.selectionStart??void 0),r=e.value.slice(e.selectionEnd??void 0),i=!0;e.contentEditable=`true`;try{i=document.execCommand(`insertText`,!1,t)}catch{i=!1}if(e.contentEditable=`false`,i&&!e.value.slice(0,e.selectionStart??void 0).endsWith(t)&&(i=!1),!i){try{document.execCommand(`ms-beginUndoUnit`)}catch{}e.value=n+t+r;try{document.execCommand(`ms-endUndoUnit`)}catch{}e.dispatchEvent(new CustomEvent(`change`,{bubbles:!0,cancelable:!0}))}}let Nf=new WeakMap;function Pf(e){let{currentTarget:t}=e,n=e.code===`KeyV`&&(e.ctrlKey||e.metaKey)&&e.shiftKey;(n||n&&e.altKey)&&Nf.set(t,!0)}function Ff(e){let{currentTarget:t}=e;Nf.delete(t)}function If(e){return Nf.get(e)??!1}function Lf(e,t,n){e.addEventListener(`keydown`,Pf);for(let r of t)r(e,n);e.addEventListener(`paste`,Ff)}function Rf(e){e.removeEventListener(`keydown`,Pf),e.removeEventListener(`paste`,Ff)}function zf(e){e.addEventListener(`paste`,Vf)}function Bf(e){e.removeEventListener(`paste`,Vf)}function Vf(e){let t=e.clipboardData,{currentTarget:n}=e;if(If(n)||!t||!Kf(t))return;let r=e.currentTarget;if(!(r instanceof HTMLTextAreaElement)||Uf(r))return;let i=t.getData(`text/plain`),a=t.getData(`text/html`),o=a.replace(/\\u00A0/g,` `).replace(/\\uC2A0/g,` `);if(!a||(i=i.trim(),!i))return;let s=new DOMParser().parseFromString(o,`text/html`),c=s.createTreeWalker(s.body,NodeFilter.SHOW_ALL,e=>e.parentNode&&Gf(e.parentNode)?NodeFilter.FILTER_REJECT:NodeFilter.FILTER_ACCEPT),l=Hf(i,c);l!==i&&(e.stopPropagation(),e.preventDefault(),Mf(r,l))}function Hf(e,t){let n=t.firstChild(),r=e,i=0,a=0,o=1e4;for(;n&&a<o;){a++;let e=Gf(n)?(n.textContent||``).replace(/[\\t\\n\\r ]+/g,` `):n?.wholeText||``;if(Wf(e)){n=t.nextNode();continue}if(!Gf(n)){i+=e.replace(/[\\t\\n\\r ]+/g,` `).trimStart().length,n=t.nextNode();continue}let o=r.indexOf(e,i);if(o>=0){let t=qf(n,e);r=r.slice(0,o)+t+r.slice(o+e.length),i=o+t.length}n=t.nextNode()}return a===o?e:r}function Uf(e){let t=e.selectionStart||0;return t===0?!1:e.value.substring(t-1,t)===`@`}function Wf(e){return!e||e?.trim().length===0}function Gf(e){return e.tagName?.toLowerCase()===`a`&&e.hasAttribute(`href`)}function Kf(e){return e.types.includes(`text/html`)}function qf(e,t){let n=e.href||``,r=``;return r=Xf(e)||Zf(e)?t:Jf(e)||Yf(n,t)?n:`[${t}](${n})`,r}function Jf(e){return e.className.indexOf(`commit-link`)>=0||!!e.getAttribute(`data-hovercard-type`)&&e.getAttribute(`data-hovercard-type`)!==`user`}function Yf(e,t){return e=e.slice(-1)===`/`?e.slice(0,-1):e,t=t.slice(-1)===`/`?t.slice(0,-1):t,e.toLowerCase()===t.toLowerCase()}function Xf(e){return e.textContent?.slice(0,1)===`@`&&e.getAttribute(`data-hovercard-type`)===`user`}function Zf(e){return e.textContent?.slice(0,1)===`@`&&e.getAttribute(`data-hovercard-type`)===`team`}function Qf(e){e.addEventListener(`dragover`,tp),e.addEventListener(`drop`,ep),e.addEventListener(`paste`,np)}function $f(e){e.removeEventListener(`dragover`,tp),e.removeEventListener(`drop`,ep),e.removeEventListener(`paste`,np)}function ep(e){let t=e.dataTransfer;if(!t||ip(t)||!ap(t))return;let n=op(t);if(!n.some(cp))return;e.stopPropagation(),e.preventDefault();let r=e.currentTarget;r instanceof HTMLTextAreaElement&&Mf(r,n.map(rp).join(``))}function tp(e){let t=e.dataTransfer;t&&(t.dropEffect=`link`)}function np(e){let{currentTarget:t}=e;if(If(t))return;let n=e.clipboardData;if(!n||!ap(n))return;let r=op(n);if(!r.some(cp))return;e.stopPropagation(),e.preventDefault();let i=e.currentTarget;i instanceof HTMLTextAreaElement&&Mf(i,r.map(rp).join(``))}function rp(e){return cp(e)?`\\n![](${e})\\n`:e}function ip(e){return Array.from(e.types).indexOf(`Files`)>=0}function ap(e){return Array.from(e.types).indexOf(`text/uri-list`)>=0}function op(e){return(e.getData(`text/uri-list`)||``).split(`\\r\n`)}let sp=/\\.(gif|png|jpe?g)$/i;function cp(e){return sp.test(e)}let lp=new WeakMap;function up(e,t){lp.set(e,t?.defaultPlainTextPaste?.urlLinks===!0),e.addEventListener(`paste`,fp)}function dp(e){e.removeEventListener(`paste`,fp)}function fp(e){let{currentTarget:t}=e,n=t,r=lp.get(n)??!1,i=If(n);if(!r&&i||r&&!i)return;let a=e.clipboardData;if(!a||!pp(a))return;let o=e.currentTarget;if(!(o instanceof HTMLTextAreaElement))return;let s=a.getData(`text/plain`);if(!s||!gp(s)||mp(o))return;let c=o.value.substring(o.selectionStart,o.selectionEnd);c.length&&(gp(c.trim())||(e.stopPropagation(),e.preventDefault(),Mf(o,hp(c,s.trim()))))}function pp(e){return Array.from(e.types).includes(`text/plain`)}function mp(e){let t=e.selectionStart||0;return t>1?e.value.substring(t-2,t)===`](`:!1}function hp(e,t){return`[${e}](${t})`}function gp(e){try{return _p(new URL(e).href).trim()===_p(e).trim()}catch{return!1}}function _p(e){return e.endsWith(`/`)?e.slice(0,e.length-1):e}function vp(e){e.addEventListener(`dragover`,xp),e.addEventListener(`drop`,bp),e.addEventListener(`paste`,Sp)}function yp(e){e.removeEventListener(`dragover`,xp),e.removeEventListener(`drop`,bp),e.removeEventListener(`paste`,Sp)}function bp(e){let t=e.dataTransfer;if(!t||Cp(t))return;let n=Dp(t);if(!n)return;e.stopPropagation(),e.preventDefault();let r=e.currentTarget;r instanceof HTMLTextAreaElement&&Mf(r,n)}function xp(e){let t=e.dataTransfer;t&&(t.dropEffect=`copy`)}function Sp(e){let{currentTarget:t}=e;if(If(t)||!e.clipboardData)return;let n=Dp(e.clipboardData);if(!n)return;e.stopPropagation(),e.preventDefault();let r=e.currentTarget;r instanceof HTMLTextAreaElement&&Mf(r,n)}function Cp(e){return Array.from(e.types).indexOf(`Files`)>=0}function wp(e){return(e.textContent||``).trim().replace(/\\|/g,`\\\\|`).replace(/\\n/g,` `)||`\\xA0`}function Tp(e){return Array.from(e.querySelectorAll(`td, th`)).map(wp)}function Ep(e){let t=Array.from(e.querySelectorAll(`tr`)),n=t.shift();if(!n)return``;let r=Tp(n),i=r.map(()=>`--`);return`\\n${`${r.join(` | `)}\\n${i.join(` | `)}\\n`}${t.map(e=>Array.from(e.querySelectorAll(`td`)).map(wp).join(` | `)).join(`\n`)}\\n\\n`}function Dp(e){if(Array.from(e.types).indexOf(`text/html`)===-1)return;let t=e.getData(`text/html`);if(!/<table/i.test(t))return;let n=t.substring(0,t.indexOf(`<table`)),r=t.lastIndexOf(`</table>`);if(!n||!r)return;let i=t.substring(r+8),a=new DOMParser().parseFromString(t,`text/html`).querySelector(`table`);if(a=!a||a.closest(`[data-paste-markdown-skip]`)?null:a,!a)return;let o=Ep(a);if(o)return[n,o,i].join(``).replace(/<meta.*?>/,``)}function Op(e){e.addEventListener(`paste`,Ap)}function kp(e){e.removeEventListener(`paste`,Ap)}function Ap(e){let{currentTarget:t}=e;if(If(t))return;let n=e.clipboardData;if(!n||!jp(n))return;let r=e.currentTarget;if(!(r instanceof HTMLTextAreaElement))return;let i=n.getData(`text/x-gfm`);i&&(e.stopPropagation(),e.preventDefault(),Mf(r,i))}function jp(e){return Array.from(e.types).indexOf(`text/x-gfm`)>=0}function Mp(e,t){return Lf(e,[vp,Qf,up,Op,zf],t),{unsubscribe:()=>{Rf(e),yp(e),Bf(e),$f(e),dp(e),kp(e)}}}var Np=o(((e,t)=>{var n=/[|\\\\{}()[\\]^$+*?.]/g;t.exports=function(e){if(typeof e!=`string`)throw TypeError(`Expected a string`);return e.replace(n,`\\\\$&`)}})),Pp=o((e=>{Object.defineProperty(e,`__esModule`,{value:!0}),e.default={bold:{prefix:`**`,suffix:`**`},italic:{prefix:`_`,suffix:`_`},strikethrough:{prefix:`~~`,suffix:`~~`},link:{prefix:{value:`[`,pattern:`\\\\[`,antipattern:`\\\\!\\\\[`},suffix:{value:function(e,t,n){return`](`+n+`)`},pattern:`\\\\]\\\\([^()]*?\\\\)`}},image:{prefix:`![`,suffix:{value:function(e,t,n){return`](`+n+`)`},pattern:`\\\\]\\\\([^()]*?\\\\)`}},header1:{prefix:{value:`# `,pattern:`# `,antipattern:`[#]{2,} `}},header2:{prefix:{value:`## `,pattern:`## `,antipattern:`[#]{3,} `}},header3:{prefix:{value:`### `,pattern:`### `,antipattern:`[#]{4,} `}},header4:{prefix:{value:`#### `,pattern:`#### `,antipattern:`[#]{5,} `}},header5:{prefix:{value:`##### `,pattern:`##### `,antipattern:`[#]{6,} `}},header6:{prefix:{value:`###### `,pattern:`###### `,antipattern:`[#]{7,} `}},code:{block:!0,prefix:\"```\\n\",suffix:\"\\n```\"},orderedList:{block:!0,multiline:!0,prefix:{value:function(e,t){return t+`. `},pattern:`[0-9]+\\\\. `}},unorderedList:{block:!0,multiline:!0,prefix:`- `},taskList:{block:!0,multiline:!0,prefix:{value:`- [ ] `,pattern:`- \\\\[[x ]{1}\\\\] `}},blockquote:{block:!0,multiline:!0,prefix:`> `}}})),Fp=o((e=>{Object.defineProperty(e,`__esModule`,{value:!0}),e.Formats=void 0;var t=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},n=function(){function e(e,t){var n=[],r=!0,i=!1,a=void 0;try{for(var o=e[Symbol.iterator](),s;!(r=(s=o.next()).done)&&(n.push(s.value),!(t&&n.length===t));r=!0);}catch(e){i=!0,a=e}finally{try{!r&&o.return&&o.return()}finally{if(i)throw a}}return n}return function(t,n){if(Array.isArray(t))return t;if(Symbol.iterator in Object(t))return e(t,n);throw TypeError(`Invalid attempt to destructure non-iterable instance`)}}(),r=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,`value`in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}(),i=o(Np()),a=o(Pp());function o(e){return e&&e.__esModule?e:{default:e}}function s(e,t){if(!(e instanceof t))throw TypeError(`Cannot call a class as a function`)}e.default=function(){function e(t){s(this,e),this.el=t}return r(e,[{key:`range`,value:function(e){var t=this.el;if(e==null)return[t.selectionStart||0,t.selectionEnd||0];this.focus();var r=n(e,2);return t.selectionStart=r[0],t.selectionEnd=r[1],this}},{key:`insert`,value:function(e){var t=!0;this.el.contentEditable=!0,this.focus();try{document.execCommand(`insertText`,!1,e)}catch{t=!1}if(this.el.contentEditable=!1,t)return this;try{document.execCommand(`ms-beginUndoUnit`)}catch{}var n=this.selection(),r=n.before,i=n.after;this.el.value=r+e+i;try{document.execCommand(`ms-endUndoUnit`)}catch{}var a=document.createEvent(`Event`);return a.initEvent(`input`,!0,!0),this.el.dispatchEvent(a),this}},{key:`focus`,value:function(){return document.activeElement!==this.el&&this.el.focus(),this}},{key:`selection`,value:function(){var e=n(this.range(),2),t=e[0],r=e[1],i=p(this.el.value);return{before:i.slice(0,t),content:i.slice(t,r),after:i.slice(r)}}},{key:`getFormat`,value:function(e){if((e===void 0?`undefined`:t(e))==`object`)return m(e);if(!a.default.hasOwnProperty(e))throw Error(`Invalid format `+e);return m(a.default[e])}},{key:`toggle`,value:function(e){if(this.hasFormat(e))return this.unformat(e);for(var t=arguments.length,n=Array(t>1?t-1:0),r=1;r<t;r++)n[r-1]=arguments[r];return this.format.apply(this,[e].concat(n))}},{key:`format`,value:function(e){for(var t=arguments.length,r=Array(t>1?t-1:0),i=1;i<t;i++)r[i-1]=arguments[i];var a=this.getFormat(e),o=a.prefix,s=a.suffix,c=a.multiline,l=this.selection(),d=l.before,f=l.content,p=l.after,m=c?f.split(`\n`):[f],h=n(this.range(),2),_=h[0],v=h[1];m=m.map(function(e,t){var n=g.apply(void 0,[o.value,e,t+1].concat(r)),i=g.apply(void 0,[s.value,e,t+1].concat(r));return!c||!f.length?(_+=n.length,v+=n.length):v+=n.length+i.length,n+e+i});var y=m.join(`\n`);if(a.block){var b=u(d,/\\n+$/),x=u(p,/^\\n+/);if(d)for(;b<2;)y=`\n`+y,_++,v++,b++;if(p)for(;x<2;)y+=`\n`,x++}return this.insert(y),this.range([_,v]),this}},{key:`unformat`,value:function(e){if(!this.hasFormat(e))return this;var t=this.getFormat(e),r=t.prefix,i=t.suffix,a=t.multiline,o=this.selection(),s=o.before,u=o.content,p=o.after,m=a?u.split(`\n`):[u],h=n(this.range(),2),g=h[0],_=h[1];(!a||m.length==1)&&l(s,r)&&c(p,i)&&(g-=f(s,r),_+=d(p,i),this.range([g,_]),m=[this.selection().content]),m=m.map(function(e){var t=d(e,r),n=f(e,i);return e.slice(t,e.length-n)});var v=m.join(`\n`);return this.insert(v),this.range([g,g+v.length]),this}},{key:`hasFormat`,value:function(e){var t=this.getFormat(e),n=t.prefix,r=t.suffix,i=t.multiline,a=this.selection(),o=a.before,s=a.content,u=a.after,d=s.split(`\n`);return!i||d.length==1?l(o,n)&&c(u,r)||c(s,n)&&l(s,r):d.filter(function(e){return c(e,n)&&l(e,r)}).length===d.length}}]),e}(),e.Formats=a.default;function c(e,t){var n=RegExp(`^`+t.pattern).test(e);if(t.antipattern){var r=RegExp(`^`+t.antipattern);n&&=!r.test(e)}return n}function l(e,t){var n=RegExp(t.pattern+`$`).test(e);if(t.antipattern){var r=RegExp(t.antipattern+`$`);n&&=!r.test(e)}return n}function u(e,t){var n=e.match(t);return n?n[0].length:0}function d(e,t){return u(e,RegExp(`^`+t.pattern))}function f(e,t){return u(e,RegExp(t.pattern+`$`))}function p(e){return e.replace(`\\r\n`,`\n`)}function m(e){var t=Object.assign({},e);return t.prefix=h(e.prefix),t.suffix=h(e.suffix),t}function h(){var e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:``;return(e===void 0?`undefined`:t(e))==`object`?e:{value:e,pattern:(0,i.default)(e)}}function g(e){for(var t=arguments.length,n=Array(t>1?t-1:0),r=1;r<t;r++)n[r-1]=arguments[r];return typeof e==`function`?e.apply(void 0,n):e}})),Ip=s({default:()=>zp}),Lp=l(Fp(),1);let Rp=Lp.default.default??Lp.default;var zp=class extends L{static{this.targets=[`input`,`preview`,`previewButton`,`hotkeyLabel`]}static{this.values={formatUrl:String}}inputTargetConnected(e){this.editor=new Rp(e),this.pasteMarkdown=Mp(e)}inputTargetDisconnected(){this.pasteMarkdown?.unsubscribe()}hotkeyLabelTargetConnected(e){let t=navigator.userAgent.match(/Macintosh/);e.innerText=e.innerText.split(`+`).map(e=>e===`Meta`?t?`⌘`:`Ctrl`:e===`Shift`&&t?`⇧`:e===`Alt`&&t?`⌥`:e.length===1?e.toUpperCase():e).join(t?``:`-`)}format(e){e.preventDefault(),this.editor?.toggle(e.params.format)}async togglePreview(){if(!this.inputTarget||!this.previewTarget)return;let e=!this.inputTarget.hidden;this.inputTarget.hidden=e,this.previewTarget.hidden=!e,this.previewButtonTarget?.setAttribute(`aria-pressed`,String(e)),this.element.classList.toggle(`is-previewing`,e),e&&(this.previewTarget.setAttribute(`aria-busy`,`true`),this.previewTarget.innerHTML=await Waterhole.fetch.post(this.formatUrlValue,{body:this.inputTarget.value}).text(),this.previewTarget.hidden=!1,this.previewTarget.setAttribute(`aria-busy`,`false`))}insertQuote(e){if(!this.inputTarget||!this.editor)return;let t=(this.inputTarget.selectionStart>0?`\n\n`:``)+`> `;this.editor.insert(t+e.detail.text.replace(/\\n/g,`\n> `)+`\n\n`)}insertEmoji(e){this.editor?.insert(e.detail.unicode||``),e.target.closest(`ui-popup`).open=!1}},Bp=s({default:()=>Hp});let Vp=`theme`;var Hp=class extends L{connect(){window.matchMedia(`(prefers-color-scheme: dark)`).addEventListener(`change`,e=>{localStorage.getItem(Vp)||this.apply(e.matches?`dark`:`light`)}),this.updateMenuItems()}set({params:{name:e}}){e?(localStorage.setItem(Vp,e),this.apply(e)):(localStorage.removeItem(Vp),this.apply(matchMedia(`(prefers-color-scheme: dark)`).matches?`dark`:`light`)),this.updateMenuItems(),this.dispatch(`change`,{detail:{name:e}})}apply(e){document.documentElement.dataset.theme=e}updateMenuItems(){let e=localStorage.getItem(Vp)||``;this.element.querySelectorAll(`[data-theme-name-param]`).forEach(t=>{t.setAttribute(`aria-checked`,t.getAttribute(`data-theme-name-param`)===e?`true`:`false`)})}},Up=s({default:()=>Wp}),Wp=class extends L{static{this.targets=[`expander`]}connect(){this.element.scrollHeight>this.element.offsetHeight&&(this.expanderTarget.hidden=!1)}expand(){this.element.style.maxHeight=`none`,this.expanderTarget.hidden=!0}},Gp=s({default:()=>Kp}),Kp=class extends L{connect(){this.element.id||(this.element.id=this.element.dataset.id||``)}reload(){this.element.reload()}disable(){this.element.disabled=!0}removeSrc(){this.element.removeAttribute(`src`)}},qp=s({default:()=>Yp});let Jp=Lp.default.default??Lp.default;var Yp=class extends L{constructor(...e){super(...e),this.onDrop=e=>{e.dataTransfer?.files.length&&(e.preventDefault(),Array.from(e.dataTransfer.files).forEach(e=>this.uploadFile(e)))},this.onPaste=e=>{e.clipboardData?.files.length&&(e.preventDefault(),Array.from(e.clipboardData.files).forEach(e=>this.uploadFile(e)))}}static{this.targets=[`input`]}static{this.values={url:String}}connect(){this.editor=new Jp(this.inputTarget),this.inputTarget.addEventListener(`drop`,this.onDrop),this.inputTarget.addEventListener(`paste`,this.onPaste)}disconnect(){this.inputTarget.removeEventListener(`drop`,this.onDrop),this.inputTarget.removeEventListener(`paste`,this.onPaste)}chooseFiles(){let e=document.createElement(`input`);e.type=`file`,e.multiple=!0,e.hidden=!0,document.body.appendChild(e),e.addEventListener(`change`,()=>{e.files&&Array.from(e.files).forEach(e=>this.uploadFile(e))}),e.click(),e.remove()}async uploadFile(e){let t=e.type.startsWith(`image/`)?`!`:``,n=`${t}[Uploading ${e.name}]()\\n`,r=``;this.editor?.insert(n);let i=new FormData;i.append(`file`,e);try{let n=await Waterhole.fetch.post(this.urlValue,{body:i}).json();r=`${t}[${e.name}](${n.url})\\n`}catch{}let a=this.inputTarget.value.indexOf(n);if(a===-1||!this.editor)return;let o=r.length-n.length,s=this.editor.range();this.editor.range([a,a+n.length]).insert(r).range([s[0]+o,s[1]+o])}},Xp=s({default:()=>Zp}),Zp=class extends L{constructor(...e){super(...e),this.onScroll=()=>{let e=this.element;e.classList.toggle(`is-scrolled-down`,e.scrollTop>0),e.classList.toggle(`is-scrolled-right`,e.scrollLeft>0),e.classList.toggle(`is-scrolled-up`,e.scrollTop<e.scrollHeight-e.offsetHeight),e.classList.toggle(`is-scrolled-left`,e.scrollLeft<e.scrollWidth-e.offsetWidth)}}connect(){this.element.addEventListener(`scroll`,this.onScroll,{passive:!0}),this.observer=new ResizeObserver(()=>this.onScroll()),this.observer.observe(this.element)}disconnect(){this.element.removeEventListener(`scroll`,this.onScroll),this.observer?.disconnect()}},Qp=Object.defineProperty,$p=(e,t,n)=>t in e?Qp(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,em=(e,t,n)=>($p(e,typeof t==`symbol`?t:t+``,n),n),tm=class{constructor(e,t,n={}){em(this,`el`),em(this,`callback`),em(this,`options`),em(this,`observer`),this.el=e,this.callback=t,this.options=n,this.start()}start(){this.stop();let e=getComputedStyle(this.el),t=[`top`,`right`,`bottom`,`left`].map(t=>e[t]===`auto`||!e[t]?`100%`:`${-1*parseInt(e[t])-1}px`).join(` `);this.observer=new IntersectionObserver(e=>{this.callback(!e[e.length-1].isIntersecting,this.el)},{threshold:[1],rootMargin:t,root:this.options.root}),this.observer.observe(this.el)}stop(){var e;(e=this.observer)==null||e.disconnect()}},nm=s({default:()=>rm}),rm=class extends L{connect(){this.observer=new tm(this.element,e=>{this.element.classList.toggle(`is-stuck`,e),this.element.classList.toggle(`is-unstuck`,!e)})}disconnect(){this.observer?.stop()}};Object.defineProperty(Waterhole,`alerts`,{get:()=>document.getElementById(`alerts`)}),window.Stimulus=At.start(),window.Stimulus.load(Vu({\"./controllers/action-menu-controller.ts\":Uu,\"./controllers/alert-controller.ts\":Gu,\"./controllers/comment-controller.ts\":qu,\"./controllers/comment-replies-controller.ts\":Xu,\"./controllers/composer-controller.ts\":sd,\"./controllers/copy-link-controller.ts\":ld,\"./controllers/details-focus-controller.ts\":dd,\"./controllers/load-backwards-controller.ts\":pd,\"./controllers/login-controller.ts\":hd,\"./controllers/mentions-controller.ts\":_d,\"./controllers/modal-controller.ts\":yd,\"./controllers/notifications-popup-controller.ts\":xd,\"./controllers/page-controller.ts\":Cd,\"./controllers/post-controller.ts\":Td,\"./controllers/post-feed-controller.ts\":Dd,\"./controllers/post-page-controller.ts\":kd,\"./controllers/quotable-controller.ts\":jd,\"./controllers/reveal-controller.ts\":Nd,\"./controllers/scrollspy-controller.ts\":Fd,\"./controllers/similar-posts-controller.ts\":Of,\"./controllers/suspend-duration-controller.ts\":Af,\"./controllers/text-editor-controller.ts\":Ip,\"./controllers/theme-controller.ts\":Bp,\"./controllers/truncated-controller.ts\":Up,\"./controllers/turbo-frame-controller.ts\":Gp,\"./controllers/uploads-controller.ts\":qp,\"./controllers/watch-scroll-controller.ts\":Xp,\"./controllers/watch-sticky-controller.ts\":nm})),Waterhole.fetch=zn.create({headers:{\"X-XSRF-TOKEN\":Bu(`XSRF-TOKEN`)||void 0},hooks:{beforeError:[e=>(Waterhole.fetchError(e.response),e)]}})})();"
  },
  {
    "path": "resources/dist/highlight.js",
    "content": "(function(){var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),s=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},c=(n,r,a)=>(a=n==null?{}:e(i(n)),s(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n)),l=o(((e,t)=>{function n(e){return e instanceof Map?e.clear=e.delete=e.set=function(){throw Error(`map is read-only`)}:e instanceof Set&&(e.add=e.clear=e.delete=function(){throw Error(`set is read-only`)}),Object.freeze(e),Object.getOwnPropertyNames(e).forEach(t=>{let r=e[t],i=typeof r;(i===`object`||i===`function`)&&!Object.isFrozen(r)&&n(r)}),e}var r=class{constructor(e){e.data===void 0&&(e.data={}),this.data=e.data,this.isMatchIgnored=!1}ignoreMatch(){this.isMatchIgnored=!0}};function i(e){return e.replace(/&/g,`&amp;`).replace(/</g,`&lt;`).replace(/>/g,`&gt;`).replace(/\"/g,`&quot;`).replace(/'/g,`&#x27;`)}function a(e,...t){let n=Object.create(null);for(let t in e)n[t]=e[t];return t.forEach(function(e){for(let t in e)n[t]=e[t]}),n}let o=e=>!!e.scope,s=(e,{prefix:t})=>{if(e.startsWith(`language:`))return e.replace(`language:`,`language-`);if(e.includes(`.`)){let n=e.split(`.`);return[`${t}${n.shift()}`,...n.map((e,t)=>`${e}${`_`.repeat(t+1)}`)].join(` `)}return`${t}${e}`};var c=class{constructor(e,t){this.buffer=``,this.classPrefix=t.classPrefix,e.walk(this)}addText(e){this.buffer+=i(e)}openNode(e){if(!o(e))return;let t=s(e.scope,{prefix:this.classPrefix});this.span(t)}closeNode(e){o(e)&&(this.buffer+=`</span>`)}value(){return this.buffer}span(e){this.buffer+=`<span class=\"${e}\">`}};let l=(e={})=>{let t={children:[]};return Object.assign(t,e),t};var u=class e{constructor(){this.rootNode=l(),this.stack=[this.rootNode]}get top(){return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){this.top.children.push(e)}openNode(e){let t=l({scope:e});this.add(t),this.stack.push(t)}closeNode(){if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)}walk(e){return this.constructor._walk(e,this.rootNode)}static _walk(e,t){return typeof t==`string`?e.addText(t):t.children&&(e.openNode(t),t.children.forEach(t=>this._walk(e,t)),e.closeNode(t)),e}static _collapse(t){typeof t!=`string`&&t.children&&(t.children.every(e=>typeof e==`string`)?t.children=[t.children.join(``)]:t.children.forEach(t=>{e._collapse(t)}))}},d=class extends u{constructor(e){super(),this.options=e}addText(e){e!==``&&this.add(e)}startScope(e){this.openNode(e)}endScope(){this.closeNode()}__addSublanguage(e,t){let n=e.root;t&&(n.scope=`language:${t}`),this.add(n)}toHTML(){return new c(this,this.options).value()}finalize(){return this.closeAllNodes(),!0}};function f(e){return e?typeof e==`string`?e:e.source:null}function p(e){return g(`(?=`,e,`)`)}function m(e){return g(`(?:`,e,`)*`)}function h(e){return g(`(?:`,e,`)?`)}function g(...e){return e.map(e=>f(e)).join(``)}function _(e){let t=e[e.length-1];return typeof t==`object`&&t.constructor===Object?(e.splice(e.length-1,1),t):{}}function v(...e){return`(`+(_(e).capture?``:`?:`)+e.map(e=>f(e)).join(`|`)+`)`}function y(e){return RegExp(e.toString()+`|`).exec(``).length-1}function b(e,t){let n=e&&e.exec(t);return n&&n.index===0}let x=/\\[(?:[^\\\\\\]]|\\\\.)*\\]|\\(\\??|\\\\([1-9][0-9]*)|\\\\./;function S(e,{joinWith:t}){let n=0;return e.map(e=>{n+=1;let t=n,r=f(e),i=``;for(;r.length>0;){let e=x.exec(r);if(!e){i+=r;break}i+=r.substring(0,e.index),r=r.substring(e.index+e[0].length),e[0][0]===`\\\\`&&e[1]?i+=`\\\\`+String(Number(e[1])+t):(i+=e[0],e[0]===`(`&&n++)}return i}).map(e=>`(${e})`).join(t)}let C=/\\b\\B/,w=`[a-zA-Z]\\\\w*`,T=`[a-zA-Z_]\\\\w*`,E=`\\\\b\\\\d+(\\\\.\\\\d+)?`,D=`(-?)(\\\\b0[xX][a-fA-F0-9]+|(\\\\b\\\\d+(\\\\.\\\\d*)?|\\\\.\\\\d+)([eE][-+]?\\\\d+)?)`,O=`\\\\b(0b[01]+)`,k=(e={})=>{let t=/^#![ ]*\\//;return e.binary&&(e.begin=g(t,/.*\\b/,e.binary,/\\b.*/)),a({scope:`meta`,begin:t,end:/$/,relevance:0,\"on:begin\":(e,t)=>{e.index!==0&&t.ignoreMatch()}},e)},A={begin:`\\\\\\\\[\\\\s\\\\S]`,relevance:0},j={scope:`string`,begin:`'`,end:`'`,illegal:`\\\\n`,contains:[A]},M={scope:`string`,begin:`\"`,end:`\"`,illegal:`\\\\n`,contains:[A]},N={begin:/\\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\\b/},P=function(e,t,n={}){let r=a({scope:`comment`,begin:e,end:t,contains:[]},n);r.contains.push({scope:`doctag`,begin:`[ ]*(?=(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):)`,end:/(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):/,excludeBegin:!0,relevance:0});let i=v(`I`,`a`,`is`,`so`,`us`,`to`,`at`,`if`,`in`,`it`,`on`,/[A-Za-z]+['](d|ve|re|ll|t|s|n)/,/[A-Za-z]+[-][a-z]+/,/[A-Za-z][a-z]{2,}/);return r.contains.push({begin:g(/[ ]+/,`(`,i,/[.]?[:]?([.][ ]|[ ])/,`){3}`)}),r},F=P(`//`,`$`),I=P(`/\\\\*`,`\\\\*/`),L=P(`#`,`$`),R={scope:`number`,begin:E,relevance:0},z={scope:`number`,begin:D,relevance:0},B={scope:`number`,begin:O,relevance:0},V={scope:`regexp`,begin:/\\/(?=[^/\\n]*\\/)/,end:/\\/[gimuy]*/,contains:[A,{begin:/\\[/,end:/\\]/,relevance:0,contains:[A]}]},H={scope:`title`,begin:w,relevance:0},U={scope:`title`,begin:T,relevance:0},W={begin:`\\\\.\\\\s*`+T,relevance:0};var G=Object.freeze({__proto__:null,APOS_STRING_MODE:j,BACKSLASH_ESCAPE:A,BINARY_NUMBER_MODE:B,BINARY_NUMBER_RE:O,COMMENT:P,C_BLOCK_COMMENT_MODE:I,C_LINE_COMMENT_MODE:F,C_NUMBER_MODE:z,C_NUMBER_RE:D,END_SAME_AS_BEGIN:function(e){return Object.assign(e,{\"on:begin\":(e,t)=>{t.data._beginMatch=e[1]},\"on:end\":(e,t)=>{t.data._beginMatch!==e[1]&&t.ignoreMatch()}})},HASH_COMMENT_MODE:L,IDENT_RE:w,MATCH_NOTHING_RE:C,METHOD_GUARD:W,NUMBER_MODE:R,NUMBER_RE:E,PHRASAL_WORDS_MODE:N,QUOTE_STRING_MODE:M,REGEXP_MODE:V,RE_STARTERS_RE:`!|!=|!==|%|%=|&|&&|&=|\\\\*|\\\\*=|\\\\+|\\\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\\\?|\\\\[|\\\\{|\\\\(|\\\\^|\\\\^=|\\\\||\\\\|=|\\\\|\\\\||~`,SHEBANG:k,TITLE_MODE:H,UNDERSCORE_IDENT_RE:T,UNDERSCORE_TITLE_MODE:U});function K(e,t){e.input[e.index-1]===`.`&&t.ignoreMatch()}function q(e,t){e.className!==void 0&&(e.scope=e.className,delete e.className)}function J(e,t){t&&e.beginKeywords&&(e.begin=`\\\\b(`+e.beginKeywords.split(` `).join(`|`)+`)(?!\\\\.)(?=\\\\b|\\\\s)`,e.__beforeBegin=K,e.keywords=e.keywords||e.beginKeywords,delete e.beginKeywords,e.relevance===void 0&&(e.relevance=0))}function ee(e,t){Array.isArray(e.illegal)&&(e.illegal=v(...e.illegal))}function te(e,t){if(e.match){if(e.begin||e.end)throw Error(`begin & end are not supported with match`);e.begin=e.match,delete e.match}}function Y(e,t){e.relevance===void 0&&(e.relevance=1)}let ne=(e,t)=>{if(!e.beforeMatch)return;if(e.starts)throw Error(`beforeMatch cannot be used with starts`);let n=Object.assign({},e);Object.keys(e).forEach(t=>{delete e[t]}),e.keywords=n.keywords,e.begin=g(n.beforeMatch,p(n.begin)),e.starts={relevance:0,contains:[Object.assign(n,{endsParent:!0})]},e.relevance=0,delete n.beforeMatch},re=[`of`,`and`,`for`,`in`,`not`,`or`,`if`,`then`,`parent`,`list`,`value`];function ie(e,t,n=`keyword`){let r=Object.create(null);return typeof e==`string`?i(n,e.split(` `)):Array.isArray(e)?i(n,e):Object.keys(e).forEach(function(n){Object.assign(r,ie(e[n],t,n))}),r;function i(e,n){t&&(n=n.map(e=>e.toLowerCase())),n.forEach(function(t){let n=t.split(`|`);r[n[0]]=[e,ae(n[0],n[1])]})}}function ae(e,t){return t?Number(t):oe(e)?0:1}function oe(e){return re.includes(e.toLowerCase())}let se={},X=e=>{console.error(e)},ce=(e,...t)=>{console.log(`WARN: ${e}`,...t)},Z=(e,t)=>{se[`${e}/${t}`]||(console.log(`Deprecated as of ${e}. ${t}`),se[`${e}/${t}`]=!0)},Q=Error();function le(e,t,{key:n}){let r=0,i=e[n],a={},o={};for(let e=1;e<=t.length;e++)o[e+r]=i[e],a[e+r]=!0,r+=y(t[e-1]);e[n]=o,e[n]._emit=a,e[n]._multi=!0}function ue(e){if(Array.isArray(e.begin)){if(e.skip||e.excludeBegin||e.returnBegin)throw X(`skip, excludeBegin, returnBegin not compatible with beginScope: {}`),Q;if(typeof e.beginScope!=`object`||e.beginScope===null)throw X(`beginScope must be object`),Q;le(e,e.begin,{key:`beginScope`}),e.begin=S(e.begin,{joinWith:``})}}function de(e){if(Array.isArray(e.end)){if(e.skip||e.excludeEnd||e.returnEnd)throw X(`skip, excludeEnd, returnEnd not compatible with endScope: {}`),Q;if(typeof e.endScope!=`object`||e.endScope===null)throw X(`endScope must be object`),Q;le(e,e.end,{key:`endScope`}),e.end=S(e.end,{joinWith:``})}}function fe(e){e.scope&&typeof e.scope==`object`&&e.scope!==null&&(e.beginScope=e.scope,delete e.scope)}function pe(e){fe(e),typeof e.beginScope==`string`&&(e.beginScope={_wrap:e.beginScope}),typeof e.endScope==`string`&&(e.endScope={_wrap:e.endScope}),ue(e),de(e)}function me(e){function t(t,n){return new RegExp(f(t),`m`+(e.case_insensitive?`i`:``)+(e.unicodeRegex?`u`:``)+(n?`g`:``))}class n{constructor(){this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0}addRule(e,t){t.position=this.position++,this.matchIndexes[this.matchAt]=t,this.regexes.push([t,e]),this.matchAt+=y(e)+1}compile(){this.regexes.length===0&&(this.exec=()=>null),this.matcherRe=t(S(this.regexes.map(e=>e[1]),{joinWith:`|`}),!0),this.lastIndex=0}exec(e){this.matcherRe.lastIndex=this.lastIndex;let t=this.matcherRe.exec(e);if(!t)return null;let n=t.findIndex((e,t)=>t>0&&e!==void 0),r=this.matchIndexes[n];return t.splice(0,n),Object.assign(t,r)}}class r{constructor(){this.rules=[],this.multiRegexes=[],this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(e){if(this.multiRegexes[e])return this.multiRegexes[e];let t=new n;return this.rules.slice(e).forEach(([e,n])=>t.addRule(e,n)),t.compile(),this.multiRegexes[e]=t,t}resumingScanAtSamePosition(){return this.regexIndex!==0}considerAll(){this.regexIndex=0}addRule(e,t){this.rules.push([e,t]),t.type===`begin`&&this.count++}exec(e){let t=this.getMatcher(this.regexIndex);t.lastIndex=this.lastIndex;let n=t.exec(e);if(this.resumingScanAtSamePosition()&&!(n&&n.index===this.lastIndex)){let t=this.getMatcher(0);t.lastIndex=this.lastIndex+1,n=t.exec(e)}return n&&(this.regexIndex+=n.position+1,this.regexIndex===this.count&&this.considerAll()),n}}function i(e){let t=new r;return e.contains.forEach(e=>t.addRule(e.begin,{rule:e,type:`begin`})),e.terminatorEnd&&t.addRule(e.terminatorEnd,{type:`end`}),e.illegal&&t.addRule(e.illegal,{type:`illegal`}),t}function o(n,r){let a=n;if(n.isCompiled)return a;[q,te,pe,ne].forEach(e=>e(n,r)),e.compilerExtensions.forEach(e=>e(n,r)),n.__beforeBegin=null,[J,ee,Y].forEach(e=>e(n,r)),n.isCompiled=!0;let s=null;return typeof n.keywords==`object`&&n.keywords.$pattern&&(n.keywords=Object.assign({},n.keywords),s=n.keywords.$pattern,delete n.keywords.$pattern),s||=/\\w+/,n.keywords&&=ie(n.keywords,e.case_insensitive),a.keywordPatternRe=t(s,!0),r&&(n.begin||=/\\B|\\b/,a.beginRe=t(a.begin),!n.end&&!n.endsWithParent&&(n.end=/\\B|\\b/),n.end&&(a.endRe=t(a.end)),a.terminatorEnd=f(a.end)||``,n.endsWithParent&&r.terminatorEnd&&(a.terminatorEnd+=(n.end?`|`:``)+r.terminatorEnd)),n.illegal&&(a.illegalRe=t(n.illegal)),n.contains||=[],n.contains=[].concat(...n.contains.map(function(e){return ge(e===`self`?n:e)})),n.contains.forEach(function(e){o(e,a)}),n.starts&&o(n.starts,r),a.matcher=i(a),a}if(e.compilerExtensions||=[],e.contains&&e.contains.includes(`self`))throw Error(\"ERR: contains `self` is not supported at the top-level of a language.  See documentation.\");return e.classNameAliases=a(e.classNameAliases||{}),o(e)}function he(e){return e?e.endsWithParent||he(e.starts):!1}function ge(e){return e.variants&&!e.cachedVariants&&(e.cachedVariants=e.variants.map(function(t){return a(e,{variants:null},t)})),e.cachedVariants?e.cachedVariants:he(e)?a(e,{starts:e.starts?a(e.starts):null}):Object.isFrozen(e)?a(e):e}var _e=`11.11.1`,ve=class extends Error{constructor(e,t){super(e),this.name=`HTMLInjectionError`,this.html=t}};let ye=i,be=a,xe=Symbol(`nomatch`),Se=function(e){let t=Object.create(null),i=Object.create(null),a=[],o=!0,s=`Could not find the language '{}', did you forget to load/include a language module?`,c={disableAutodetect:!0,name:`Plain text`,contains:[]},l={ignoreUnescapedHTML:!1,throwUnescapedHTML:!1,noHighlightRe:/^(no-?highlight)$/i,languageDetectRe:/\\blang(?:uage)?-([\\w-]+)\\b/i,classPrefix:`hljs-`,cssSelector:`pre code`,languages:null,__emitter:d};function u(e){return l.noHighlightRe.test(e)}function f(e){let t=e.className+` `;t+=e.parentNode?e.parentNode.className:``;let n=l.languageDetectRe.exec(t);if(n){let t=N(n[1]);return t||(ce(s.replace(`{}`,n[1])),ce(`Falling back to no-highlight mode for this block.`,e)),t?n[1]:`no-highlight`}return t.split(/\\s+/).find(e=>u(e)||N(e))}function _(e,t,n){let r=``,i=``;typeof t==`object`?(r=e,n=t.ignoreIllegals,i=t.language):(Z(`10.7.0`,`highlight(lang, code, ...args) has been deprecated.`),Z(`10.7.0`,`Please use highlight(code, options) instead.\nhttps://github.com/highlightjs/highlight.js/issues/2277`),i=e,r=t),n===void 0&&(n=!0);let a={code:r,language:i};z(`before:highlight`,a);let o=a.result?a.result:y(a.language,a.code,n);return o.code=a.code,z(`after:highlight`,o),o}function y(e,n,i,a){let c=Object.create(null);function u(e,t){return e.keywords[t]}function d(){if(!A.keywords){M.addText(P);return}let e=0;A.keywordPatternRe.lastIndex=0;let t=A.keywordPatternRe.exec(P),n=``;for(;t;){n+=P.substring(e,t.index);let r=D.case_insensitive?t[0].toLowerCase():t[0],i=u(A,r);if(i){let[e,a]=i;if(M.addText(n),n=``,c[r]=(c[r]||0)+1,c[r]<=7&&(F+=a),e.startsWith(`_`))n+=t[0];else{let n=D.classNameAliases[e]||e;m(t[0],n)}}else n+=t[0];e=A.keywordPatternRe.lastIndex,t=A.keywordPatternRe.exec(P)}n+=P.substring(e),M.addText(n)}function f(){if(P===``)return;let e=null;if(typeof A.subLanguage==`string`){if(!t[A.subLanguage]){M.addText(P);return}e=y(A.subLanguage,P,!0,j[A.subLanguage]),j[A.subLanguage]=e._top}else e=S(P,A.subLanguage.length?A.subLanguage:null);A.relevance>0&&(F+=e.relevance),M.__addSublanguage(e._emitter,e.language)}function p(){A.subLanguage==null?d():f(),P=``}function m(e,t){e!==``&&(M.startScope(t),M.addText(e),M.endScope())}function h(e,t){let n=1,r=t.length-1;for(;n<=r;){if(!e._emit[n]){n++;continue}let r=D.classNameAliases[e[n]]||e[n],i=t[n];r?m(i,r):(P=i,d(),P=``),n++}}function g(e,t){return e.scope&&typeof e.scope==`string`&&M.openNode(D.classNameAliases[e.scope]||e.scope),e.beginScope&&(e.beginScope._wrap?(m(P,D.classNameAliases[e.beginScope._wrap]||e.beginScope._wrap),P=``):e.beginScope._multi&&(h(e.beginScope,t),P=``)),A=Object.create(e,{parent:{value:A}}),A}function _(e,t,n){let i=b(e.endRe,n);if(i){if(e[`on:end`]){let n=new r(e);e[`on:end`](t,n),n.isMatchIgnored&&(i=!1)}if(i){for(;e.endsParent&&e.parent;)e=e.parent;return e}}if(e.endsWithParent)return _(e.parent,t,n)}function v(e){return A.matcher.regexIndex===0?(P+=e[0],1):(R=!0,0)}function x(e){let t=e[0],n=e.rule,i=new r(n),a=[n.__beforeBegin,n[`on:begin`]];for(let n of a)if(n&&(n(e,i),i.isMatchIgnored))return v(t);return n.skip?P+=t:(n.excludeBegin&&(P+=t),p(),!n.returnBegin&&!n.excludeBegin&&(P=t)),g(n,e),n.returnBegin?0:t.length}function C(e){let t=e[0],r=n.substring(e.index),i=_(A,e,r);if(!i)return xe;let a=A;A.endScope&&A.endScope._wrap?(p(),m(t,A.endScope._wrap)):A.endScope&&A.endScope._multi?(p(),h(A.endScope,e)):a.skip?P+=t:(a.returnEnd||a.excludeEnd||(P+=t),p(),a.excludeEnd&&(P=t));do A.scope&&M.closeNode(),!A.skip&&!A.subLanguage&&(F+=A.relevance),A=A.parent;while(A!==i.parent);return i.starts&&g(i.starts,e),a.returnEnd?0:t.length}function w(){let e=[];for(let t=A;t!==D;t=t.parent)t.scope&&e.unshift(t.scope);e.forEach(e=>M.openNode(e))}let T={};function E(t,r){let a=r&&r[0];if(P+=t,a==null)return p(),0;if(T.type===`begin`&&r.type===`end`&&T.index===r.index&&a===``){if(P+=n.slice(r.index,r.index+1),!o){let t=Error(`0 width match regex (${e})`);throw t.languageName=e,t.badRule=T.rule,t}return 1}if(T=r,r.type===`begin`)return x(r);if(r.type===`illegal`&&!i){let e=Error(`Illegal lexeme \"`+a+`\" for mode \"`+(A.scope||`<unnamed>`)+`\"`);throw e.mode=A,e}else if(r.type===`end`){let e=C(r);if(e!==xe)return e}if(r.type===`illegal`&&a===``)return P+=`\n`,1;if(L>1e5&&L>r.index*3)throw Error(`potential infinite loop, way more iterations than matches`);return P+=a,a.length}let D=N(e);if(!D)throw X(s.replace(`{}`,e)),Error(`Unknown language: \"`+e+`\"`);let O=me(D),k=``,A=a||O,j={},M=new l.__emitter(l);w();let P=``,F=0,I=0,L=0,R=!1;try{if(D.__emitTokens)D.__emitTokens(n,M);else{for(A.matcher.considerAll();;){L++,R?R=!1:A.matcher.considerAll(),A.matcher.lastIndex=I;let e=A.matcher.exec(n);if(!e)break;let t=E(n.substring(I,e.index),e);I=e.index+t}E(n.substring(I))}return M.finalize(),k=M.toHTML(),{language:e,value:k,relevance:F,illegal:!1,_emitter:M,_top:A}}catch(t){if(t.message&&t.message.includes(`Illegal`))return{language:e,value:ye(n),illegal:!0,relevance:0,_illegalBy:{message:t.message,index:I,context:n.slice(I-100,I+100),mode:t.mode,resultSoFar:k},_emitter:M};if(o)return{language:e,value:ye(n),illegal:!1,relevance:0,errorRaised:t,_emitter:M,_top:A};throw t}}function x(e){let t={value:ye(e),illegal:!1,relevance:0,_top:c,_emitter:new l.__emitter(l)};return t._emitter.addText(e),t}function S(e,n){n=n||l.languages||Object.keys(t);let r=x(e),i=n.filter(N).filter(F).map(t=>y(t,e,!1));i.unshift(r);let[a,o]=i.sort((e,t)=>{if(e.relevance!==t.relevance)return t.relevance-e.relevance;if(e.language&&t.language){if(N(e.language).supersetOf===t.language)return 1;if(N(t.language).supersetOf===e.language)return-1}return 0}),s=a;return s.secondBest=o,s}function C(e,t,n){let r=t&&i[t]||n;e.classList.add(`hljs`),e.classList.add(`language-${r}`)}function w(e){let t=null,n=f(e);if(u(n))return;if(z(`before:highlightElement`,{el:e,language:n}),e.dataset.highlighted){console.log(\"Element previously highlighted. To highlight again, first unset `dataset.highlighted`.\",e);return}if(e.children.length>0&&(l.ignoreUnescapedHTML||(console.warn(`One of your code blocks includes unescaped HTML. This is a potentially serious security risk.`),console.warn(`https://github.com/highlightjs/highlight.js/wiki/security`),console.warn(`The element with unescaped HTML:`),console.warn(e)),l.throwUnescapedHTML))throw new ve(`One of your code blocks includes unescaped HTML.`,e.innerHTML);t=e;let r=t.textContent,i=n?_(r,{language:n,ignoreIllegals:!0}):S(r);e.innerHTML=i.value,e.dataset.highlighted=`yes`,C(e,n,i.language),e.result={language:i.language,re:i.relevance,relevance:i.relevance},i.secondBest&&(e.secondBest={language:i.secondBest.language,relevance:i.secondBest.relevance}),z(`after:highlightElement`,{el:e,result:i,text:r})}function T(e){l=be(l,e)}let E=()=>{k(),Z(`10.6.0`,`initHighlighting() deprecated.  Use highlightAll() now.`)};function D(){k(),Z(`10.6.0`,`initHighlightingOnLoad() deprecated.  Use highlightAll() now.`)}let O=!1;function k(){function e(){k()}if(document.readyState===`loading`){O||window.addEventListener(`DOMContentLoaded`,e,!1),O=!0;return}document.querySelectorAll(l.cssSelector).forEach(w)}function A(n,r){let i=null;try{i=r(e)}catch(e){if(X(`Language definition for '{}' could not be registered.`.replace(`{}`,n)),o)X(e);else throw e;i=c}i.name||=n,t[n]=i,i.rawDefinition=r.bind(null,e),i.aliases&&P(i.aliases,{languageName:n})}function j(e){delete t[e];for(let t of Object.keys(i))i[t]===e&&delete i[t]}function M(){return Object.keys(t)}function N(e){return e=(e||``).toLowerCase(),t[e]||t[i[e]]}function P(e,{languageName:t}){typeof e==`string`&&(e=[e]),e.forEach(e=>{i[e.toLowerCase()]=t})}function F(e){let t=N(e);return t&&!t.disableAutodetect}function I(e){e[`before:highlightBlock`]&&!e[`before:highlightElement`]&&(e[`before:highlightElement`]=t=>{e[`before:highlightBlock`](Object.assign({block:t.el},t))}),e[`after:highlightBlock`]&&!e[`after:highlightElement`]&&(e[`after:highlightElement`]=t=>{e[`after:highlightBlock`](Object.assign({block:t.el},t))})}function L(e){I(e),a.push(e)}function R(e){let t=a.indexOf(e);t!==-1&&a.splice(t,1)}function z(e,t){let n=e;a.forEach(function(e){e[n]&&e[n](t)})}function B(e){return Z(`10.7.0`,`highlightBlock will be removed entirely in v12.0`),Z(`10.7.0`,`Please use highlightElement now.`),w(e)}for(let t in Object.assign(e,{highlight:_,highlightAuto:S,highlightAll:k,highlightElement:w,highlightBlock:B,configure:T,initHighlighting:E,initHighlightingOnLoad:D,registerLanguage:A,unregisterLanguage:j,listLanguages:M,getLanguage:N,registerAliases:P,autoDetection:F,inherit:be,addPlugin:L,removePlugin:R}),e.debugMode=function(){o=!1},e.safeMode=function(){o=!0},e.versionString=_e,e.regex={concat:g,lookahead:p,either:v,optional:h,anyNumberOfTimes:m},G)typeof G[t]==`object`&&n(G[t]);return Object.assign(e,G),e},$=Se({});$.newInstance=()=>Se({}),t.exports=$,$.HighlightJS=$,$.default=$})),u=o(((e,t)=>{function n(e){let t=e.regex,n=t.concat(/[\\p{L}_]/u,t.optional(/[\\p{L}0-9_.-]*:/u),/[\\p{L}0-9_.-]*/u),r=/[\\p{L}0-9._:-]+/u,i={className:`symbol`,begin:/&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;/},a={begin:/\\s/,contains:[{className:`keyword`,begin:/#?[a-z_][a-z1-9_-]+/,illegal:/\\n/}]},o=e.inherit(a,{begin:/\\(/,end:/\\)/}),s=e.inherit(e.APOS_STRING_MODE,{className:`string`}),c=e.inherit(e.QUOTE_STRING_MODE,{className:`string`}),l={endsWithParent:!0,illegal:/</,relevance:0,contains:[{className:`attr`,begin:r,relevance:0},{begin:/=\\s*/,relevance:0,contains:[{className:`string`,endsParent:!0,variants:[{begin:/\"/,end:/\"/,contains:[i]},{begin:/'/,end:/'/,contains:[i]},{begin:/[^\\s\"'=<>`]+/}]}]}]};return{name:`HTML, XML`,aliases:[`html`,`xhtml`,`rss`,`atom`,`xjb`,`xsd`,`xsl`,`plist`,`wsf`,`svg`],case_insensitive:!0,unicodeRegex:!0,contains:[{className:`meta`,begin:/<![a-z]/,end:/>/,relevance:10,contains:[a,c,s,o,{begin:/\\[/,end:/\\]/,contains:[{className:`meta`,begin:/<![a-z]/,end:/>/,contains:[a,o,c,s]}]}]},e.COMMENT(/<!--/,/-->/,{relevance:10}),{begin:/<!\\[CDATA\\[/,end:/\\]\\]>/,relevance:10},i,{className:`meta`,end:/\\?>/,variants:[{begin:/<\\?xml/,relevance:10,contains:[c]},{begin:/<\\?[a-z][a-z0-9]+/}]},{className:`tag`,begin:/<style(?=\\s|>)/,end:/>/,keywords:{name:`style`},contains:[l],starts:{end:/<\\/style>/,returnEnd:!0,subLanguage:[`css`,`xml`]}},{className:`tag`,begin:/<script(?=\\s|>)/,end:/>/,keywords:{name:`script`},contains:[l],starts:{end:/<\\/script>/,returnEnd:!0,subLanguage:[`javascript`,`handlebars`,`xml`]}},{className:`tag`,begin:/<>|<\\/>/},{className:`tag`,begin:t.concat(/</,t.lookahead(t.concat(n,t.either(/\\/>/,/>/,/\\s/)))),end:/\\/?>/,contains:[{className:`name`,begin:n,relevance:0,starts:l}]},{className:`tag`,begin:t.concat(/<\\//,t.lookahead(t.concat(n,/>/))),contains:[{className:`name`,begin:n,relevance:0},{begin:/>/,relevance:0,endsParent:!0}]}]}}t.exports=n})),d=o(((e,t)=>{function n(e){let t=e.regex,n={},r={begin:/\\$\\{/,end:/\\}/,contains:[`self`,{begin:/:-/,contains:[n]}]};Object.assign(n,{className:`variable`,variants:[{begin:t.concat(/\\$[\\w\\d#@][\\w\\d_]*/,`(?![\\\\w\\\\d])(?![$])`)},r]});let i={className:`subst`,begin:/\\$\\(/,end:/\\)/,contains:[e.BACKSLASH_ESCAPE]},a=e.inherit(e.COMMENT(),{match:[/(^|\\s)/,/#.*$/],scope:{2:`comment`}}),o={begin:/<<-?\\s*(?=\\w+)/,starts:{contains:[e.END_SAME_AS_BEGIN({begin:/(\\w+)/,end:/(\\w+)/,className:`string`})]}},s={className:`string`,begin:/\"/,end:/\"/,contains:[e.BACKSLASH_ESCAPE,n,i]};i.contains.push(s);let c={match:/\\\\\"/},l={className:`string`,begin:/'/,end:/'/},u={match:/\\\\'/},d={begin:/\\$?\\(\\(/,end:/\\)\\)/,contains:[{begin:/\\d+#[0-9a-f]+/,className:`number`},e.NUMBER_MODE,n]},f=e.SHEBANG({binary:`(${[`fish`,`bash`,`zsh`,`sh`,`csh`,`ksh`,`tcsh`,`dash`,`scsh`].join(`|`)})`,relevance:10}),p={className:`function`,begin:/\\w[\\w\\d_]*\\s*\\(\\s*\\)\\s*\\{/,returnBegin:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/\\w[\\w\\d_]*/})],relevance:0},m=[`if`,`then`,`else`,`elif`,`fi`,`time`,`for`,`while`,`until`,`in`,`do`,`done`,`case`,`esac`,`coproc`,`function`,`select`],h=[`true`,`false`],g={match:/(\\/[a-z._-]+)+/},_=[`break`,`cd`,`continue`,`eval`,`exec`,`exit`,`export`,`getopts`,`hash`,`pwd`,`readonly`,`return`,`shift`,`test`,`times`,`trap`,`umask`,`unset`],v=[`alias`,`bind`,`builtin`,`caller`,`command`,`declare`,`echo`,`enable`,`help`,`let`,`local`,`logout`,`mapfile`,`printf`,`read`,`readarray`,`source`,`sudo`,`type`,`typeset`,`ulimit`,`unalias`],y=`autoload.bg.bindkey.bye.cap.chdir.clone.comparguments.compcall.compctl.compdescribe.compfiles.compgroups.compquote.comptags.comptry.compvalues.dirs.disable.disown.echotc.echoti.emulate.fc.fg.float.functions.getcap.getln.history.integer.jobs.kill.limit.log.noglob.popd.print.pushd.pushln.rehash.sched.setcap.setopt.stat.suspend.ttyctl.unfunction.unhash.unlimit.unsetopt.vared.wait.whence.where.which.zcompile.zformat.zftp.zle.zmodload.zparseopts.zprof.zpty.zregexparse.zsocket.zstyle.ztcp`.split(`.`),b=`chcon.chgrp.chown.chmod.cp.dd.df.dir.dircolors.ln.ls.mkdir.mkfifo.mknod.mktemp.mv.realpath.rm.rmdir.shred.sync.touch.truncate.vdir.b2sum.base32.base64.cat.cksum.comm.csplit.cut.expand.fmt.fold.head.join.md5sum.nl.numfmt.od.paste.ptx.pr.sha1sum.sha224sum.sha256sum.sha384sum.sha512sum.shuf.sort.split.sum.tac.tail.tr.tsort.unexpand.uniq.wc.arch.basename.chroot.date.dirname.du.echo.env.expr.factor.groups.hostid.id.link.logname.nice.nohup.nproc.pathchk.pinky.printenv.printf.pwd.readlink.runcon.seq.sleep.stat.stdbuf.stty.tee.test.timeout.tty.uname.unlink.uptime.users.who.whoami.yes`.split(`.`);return{name:`Bash`,aliases:[`sh`,`zsh`],keywords:{$pattern:/\\b[a-z][a-z0-9._-]+\\b/,keyword:m,literal:h,built_in:[..._,...v,`set`,`shopt`,...y,...b]},contains:[f,e.SHEBANG(),p,d,a,o,g,s,c,l,u,n]}}t.exports=n})),f=o(((e,t)=>{function n(e){let t=e.regex,n=e.COMMENT(`//`,`$`,{contains:[{begin:/\\\\\\n/}]}),r=`decltype\\\\(auto\\\\)`,i=`[a-zA-Z_]\\\\w*::`,a=`(`+r+`|`+t.optional(i)+`[a-zA-Z_]\\\\w*`+t.optional(`<[^<>]+>`)+`)`,o={className:`type`,variants:[{begin:`\\\\b[a-z\\\\d_]*_t\\\\b`},{match:/\\batomic_[a-z]{3,6}\\b/}]},s={className:`string`,variants:[{begin:`(u8?|U|L)?\"`,end:`\"`,illegal:`\\\\n`,contains:[e.BACKSLASH_ESCAPE]},{begin:`(u8?|U|L)?'(\\\\\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4,8}|[0-7]{3}|\\\\S)|.)`,end:`'`,illegal:`.`},e.END_SAME_AS_BEGIN({begin:/(?:u8?|U|L)?R\"([^()\\\\ ]{0,16})\\(/,end:/\\)([^()\\\\ ]{0,16})\"/})]},c={className:`number`,variants:[{match:/\\b(0b[01']+)/},{match:/(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)((ll|LL|l|L)(u|U)?|(u|U)(ll|LL|l|L)?|f|F|b|B)/},{match:/(-?)\\b(0[xX][a-fA-F0-9]+(?:'[a-fA-F0-9]+)*(?:\\.[a-fA-F0-9]*(?:'[a-fA-F0-9]*)*)?(?:[pP][-+]?[0-9]+)?(l|L)?(u|U)?)/},{match:/(-?)\\b\\d+(?:'\\d+)*(?:\\.\\d*(?:'\\d*)*)?(?:[eE][-+]?\\d+)?/}],relevance:0},l={className:`meta`,begin:/#\\s*[a-z]+\\b/,end:/$/,keywords:{keyword:`if else elif endif define undef warning error line pragma _Pragma ifdef ifndef elifdef elifndef include`},contains:[{begin:/\\\\\\n/,relevance:0},e.inherit(s,{className:`string`}),{className:`string`,begin:/<.*?>/},n,e.C_BLOCK_COMMENT_MODE]},u={className:`title`,begin:t.optional(i)+e.IDENT_RE,relevance:0},d=t.optional(i)+e.IDENT_RE+`\\\\s*\\\\(`,f={keyword:`asm.auto.break.case.continue.default.do.else.enum.extern.for.fortran.goto.if.inline.register.restrict.return.sizeof.typeof.typeof_unqual.struct.switch.typedef.union.volatile.while._Alignas._Alignof._Atomic._Generic._Noreturn._Static_assert._Thread_local.alignas.alignof.noreturn.static_assert.thread_local._Pragma`.split(`.`),type:`float.double.signed.unsigned.int.short.long.char.void._Bool._BitInt._Complex._Imaginary._Decimal32._Decimal64._Decimal96._Decimal128._Decimal64x._Decimal128x._Float16._Float32._Float64._Float128._Float32x._Float64x._Float128x.const.static.constexpr.complex.bool.imaginary`.split(`.`),literal:`true false NULL`,built_in:`std string wstring cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set pair bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap priority_queue make_pair array shared_ptr abort terminate abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf future isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf endl initializer_list unique_ptr`},p=[l,o,n,e.C_BLOCK_COMMENT_MODE,c,s],m={variants:[{begin:/=/,end:/;/},{begin:/\\(/,end:/\\)/},{beginKeywords:`new throw return else`,end:/;/}],keywords:f,contains:p.concat([{begin:/\\(/,end:/\\)/,keywords:f,contains:p.concat([`self`]),relevance:0}]),relevance:0},h={begin:`(`+a+`[\\\\*&\\\\s]+)+`+d,returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:f,illegal:/[^\\w\\s\\*&:<>.]/,contains:[{begin:r,keywords:f,relevance:0},{begin:d,returnBegin:!0,contains:[e.inherit(u,{className:`title.function`})],relevance:0},{relevance:0,match:/,/},{className:`params`,begin:/\\(/,end:/\\)/,keywords:f,relevance:0,contains:[n,e.C_BLOCK_COMMENT_MODE,s,c,o,{begin:/\\(/,end:/\\)/,keywords:f,relevance:0,contains:[`self`,n,e.C_BLOCK_COMMENT_MODE,s,c,o]}]},o,n,e.C_BLOCK_COMMENT_MODE,l]};return{name:`C`,aliases:[`h`],keywords:f,disableAutodetect:!0,illegal:`</`,contains:[].concat(m,h,p,[l,{begin:e.IDENT_RE+`::`,keywords:f},{className:`class`,beginKeywords:`enum class struct union`,end:/[{;:<>=]/,contains:[{beginKeywords:`final class struct`},e.TITLE_MODE]}]),exports:{preprocessor:l,strings:s,keywords:f}}}t.exports=n})),p=o(((e,t)=>{function n(e){let t=e.regex,n=e.COMMENT(`//`,`$`,{contains:[{begin:/\\\\\\n/}]}),r=`decltype\\\\(auto\\\\)`,i=`[a-zA-Z_]\\\\w*::`,a=`(?!struct)(`+r+`|`+t.optional(i)+`[a-zA-Z_]\\\\w*`+t.optional(`<[^<>]+>`)+`)`,o={className:`type`,begin:`\\\\b[a-z\\\\d_]*_t\\\\b`},s={className:`string`,variants:[{begin:`(u8?|U|L)?\"`,end:`\"`,illegal:`\\\\n`,contains:[e.BACKSLASH_ESCAPE]},{begin:`(u8?|U|L)?'(\\\\\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4,8}|[0-7]{3}|\\\\S)|.)`,end:`'`,illegal:`.`},e.END_SAME_AS_BEGIN({begin:/(?:u8?|U|L)?R\"([^()\\\\ ]{0,16})\\(/,end:/\\)([^()\\\\ ]{0,16})\"/})]},c={className:`number`,variants:[{begin:`[+-]?(?:(?:[0-9](?:'?[0-9])*\\\\.(?:[0-9](?:'?[0-9])*)?|\\\\.[0-9](?:'?[0-9])*)(?:[Ee][+-]?[0-9](?:'?[0-9])*)?|[0-9](?:'?[0-9])*[Ee][+-]?[0-9](?:'?[0-9])*|0[Xx](?:[0-9A-Fa-f](?:'?[0-9A-Fa-f])*(?:\\\\.(?:[0-9A-Fa-f](?:'?[0-9A-Fa-f])*)?)?|\\\\.[0-9A-Fa-f](?:'?[0-9A-Fa-f])*)[Pp][+-]?[0-9](?:'?[0-9])*)(?:[Ff](?:16|32|64|128)?|(BF|bf)16|[Ll]|)`},{begin:`[+-]?\\\\b(?:0[Bb][01](?:'?[01])*|0[Xx][0-9A-Fa-f](?:'?[0-9A-Fa-f])*|0(?:'?[0-7])*|[1-9](?:'?[0-9])*)(?:[Uu](?:LL?|ll?)|[Uu][Zz]?|(?:LL?|ll?)[Uu]?|[Zz][Uu]|)`}],relevance:0},l={className:`meta`,begin:/#\\s*[a-z]+\\b/,end:/$/,keywords:{keyword:`if else elif endif define undef warning error line pragma _Pragma ifdef ifndef include`},contains:[{begin:/\\\\\\n/,relevance:0},e.inherit(s,{className:`string`}),{className:`string`,begin:/<.*?>/},n,e.C_BLOCK_COMMENT_MODE]},u={className:`title`,begin:t.optional(i)+e.IDENT_RE,relevance:0},d=t.optional(i)+e.IDENT_RE+`\\\\s*\\\\(`,f=`alignas.alignof.and.and_eq.asm.atomic_cancel.atomic_commit.atomic_noexcept.auto.bitand.bitor.break.case.catch.class.co_await.co_return.co_yield.compl.concept.const_cast|10.consteval.constexpr.constinit.continue.decltype.default.delete.do.dynamic_cast|10.else.enum.explicit.export.extern.false.final.for.friend.goto.if.import.inline.module.mutable.namespace.new.noexcept.not.not_eq.nullptr.operator.or.or_eq.override.private.protected.public.reflexpr.register.reinterpret_cast|10.requires.return.sizeof.static_assert.static_cast|10.struct.switch.synchronized.template.this.thread_local.throw.transaction_safe.transaction_safe_dynamic.true.try.typedef.typeid.typename.union.using.virtual.volatile.while.xor.xor_eq`.split(`.`),p=[`bool`,`char`,`char16_t`,`char32_t`,`char8_t`,`double`,`float`,`int`,`long`,`short`,`void`,`wchar_t`,`unsigned`,`signed`,`const`,`static`],m=`any.auto_ptr.barrier.binary_semaphore.bitset.complex.condition_variable.condition_variable_any.counting_semaphore.deque.false_type.flat_map.flat_set.future.imaginary.initializer_list.istringstream.jthread.latch.lock_guard.multimap.multiset.mutex.optional.ostringstream.packaged_task.pair.promise.priority_queue.queue.recursive_mutex.recursive_timed_mutex.scoped_lock.set.shared_future.shared_lock.shared_mutex.shared_timed_mutex.shared_ptr.stack.string_view.stringstream.timed_mutex.thread.true_type.tuple.unique_lock.unique_ptr.unordered_map.unordered_multimap.unordered_multiset.unordered_set.variant.vector.weak_ptr.wstring.wstring_view`.split(`.`),h=`abort.abs.acos.apply.as_const.asin.atan.atan2.calloc.ceil.cerr.cin.clog.cos.cosh.cout.declval.endl.exchange.exit.exp.fabs.floor.fmod.forward.fprintf.fputs.free.frexp.fscanf.future.invoke.isalnum.isalpha.iscntrl.isdigit.isgraph.islower.isprint.ispunct.isspace.isupper.isxdigit.labs.launder.ldexp.log.log10.make_pair.make_shared.make_shared_for_overwrite.make_tuple.make_unique.malloc.memchr.memcmp.memcpy.memset.modf.move.pow.printf.putchar.puts.realloc.scanf.sin.sinh.snprintf.sprintf.sqrt.sscanf.std.stderr.stdin.stdout.strcat.strchr.strcmp.strcpy.strcspn.strlen.strncat.strncmp.strncpy.strpbrk.strrchr.strspn.strstr.swap.tan.tanh.terminate.to_underlying.tolower.toupper.vfprintf.visit.vprintf.vsprintf`.split(`.`),g={type:p,keyword:f,literal:[`NULL`,`false`,`nullopt`,`nullptr`,`true`],built_in:[`_Pragma`],_type_hints:m},_={className:`function.dispatch`,relevance:0,keywords:{_hint:h},begin:t.concat(/\\b/,/(?!decltype)/,/(?!if)/,/(?!for)/,/(?!switch)/,/(?!while)/,e.IDENT_RE,t.lookahead(/(<[^<>]+>|)\\s*\\(/))},v=[_,l,o,n,e.C_BLOCK_COMMENT_MODE,c,s],y={variants:[{begin:/=/,end:/;/},{begin:/\\(/,end:/\\)/},{beginKeywords:`new throw return else`,end:/;/}],keywords:g,contains:v.concat([{begin:/\\(/,end:/\\)/,keywords:g,contains:v.concat([`self`]),relevance:0}]),relevance:0},b={className:`function`,begin:`(`+a+`[\\\\*&\\\\s]+)+`+d,returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:g,illegal:/[^\\w\\s\\*&:<>.]/,contains:[{begin:r,keywords:g,relevance:0},{begin:d,returnBegin:!0,contains:[u],relevance:0},{begin:/::/,relevance:0},{begin:/:/,endsWithParent:!0,contains:[s,c]},{relevance:0,match:/,/},{className:`params`,begin:/\\(/,end:/\\)/,keywords:g,relevance:0,contains:[n,e.C_BLOCK_COMMENT_MODE,s,c,o,{begin:/\\(/,end:/\\)/,keywords:g,relevance:0,contains:[`self`,n,e.C_BLOCK_COMMENT_MODE,s,c,o]}]},o,n,e.C_BLOCK_COMMENT_MODE,l]};return{name:`C++`,aliases:[`cc`,`c++`,`h++`,`hpp`,`hh`,`hxx`,`cxx`],keywords:g,illegal:`</`,classNameAliases:{\"function.dispatch\":`built_in`},contains:[].concat(y,b,_,v,[l,{begin:`\\\\b(deque|list|queue|priority_queue|pair|stack|vector|map|set|bitset|multiset|multimap|unordered_map|unordered_set|unordered_multiset|unordered_multimap|array|tuple|optional|variant|function|flat_map|flat_set)\\\\s*<(?!<)`,end:`>`,keywords:g,contains:[`self`,o]},{begin:e.IDENT_RE+`::`,keywords:g},{match:[/\\b(?:enum(?:\\s+(?:class|struct))?|class|struct|union)/,/\\s+/,/\\w+/],className:{1:`keyword`,3:`title.class`}}])}}t.exports=n})),m=o(((e,t)=>{function n(e){let t=[`bool`,`byte`,`char`,`decimal`,`delegate`,`double`,`dynamic`,`enum`,`float`,`int`,`long`,`nint`,`nuint`,`object`,`sbyte`,`short`,`string`,`ulong`,`uint`,`ushort`],n=[`public`,`private`,`protected`,`static`,`internal`,`protected`,`abstract`,`async`,`extern`,`override`,`unsafe`,`virtual`,`new`,`sealed`,`partial`],r={keyword:`abstract.as.base.break.case.catch.class.const.continue.do.else.event.explicit.extern.finally.fixed.for.foreach.goto.if.implicit.in.interface.internal.is.lock.namespace.new.operator.out.override.params.private.protected.public.readonly.record.ref.return.scoped.sealed.sizeof.stackalloc.static.struct.switch.this.throw.try.typeof.unchecked.unsafe.using.virtual.void.volatile.while`.split(`.`).concat(`add.alias.and.ascending.args.async.await.by.descending.dynamic.equals.file.from.get.global.group.init.into.join.let.nameof.not.notnull.on.or.orderby.partial.record.remove.required.scoped.select.set.unmanaged.value|0.var.when.where.with.yield`.split(`.`)),built_in:t,literal:[`default`,`false`,`null`,`true`]},i=e.inherit(e.TITLE_MODE,{begin:`[a-zA-Z](\\\\.?\\\\w)*`}),a={className:`number`,variants:[{begin:`\\\\b(0b[01']+)`},{begin:`(-?)\\\\b([\\\\d']+(\\\\.[\\\\d']*)?|\\\\.[\\\\d']+)(u|U|l|L|ul|UL|f|F|b|B)`},{begin:`(-?)(\\\\b0[xX][a-fA-F0-9']+|(\\\\b[\\\\d']+(\\\\.[\\\\d']*)?|\\\\.[\\\\d']+)([eE][-+]?[\\\\d']+)?)`}],relevance:0},o={className:`string`,begin:/\"\"\"(\"*)(?!\")(.|\\n)*?\"\"\"\\1/,relevance:1},s={className:`string`,begin:`@\"`,end:`\"`,contains:[{begin:`\"\"`}]},c=e.inherit(s,{illegal:/\\n/}),l={className:`subst`,begin:/\\{/,end:/\\}/,keywords:r},u=e.inherit(l,{illegal:/\\n/}),d={className:`string`,begin:/\\$\"/,end:`\"`,illegal:/\\n/,contains:[{begin:/\\{\\{/},{begin:/\\}\\}/},e.BACKSLASH_ESCAPE,u]},f={className:`string`,begin:/\\$@\"/,end:`\"`,contains:[{begin:/\\{\\{/},{begin:/\\}\\}/},{begin:`\"\"`},l]},p=e.inherit(f,{illegal:/\\n/,contains:[{begin:/\\{\\{/},{begin:/\\}\\}/},{begin:`\"\"`},u]});l.contains=[f,d,s,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,a,e.C_BLOCK_COMMENT_MODE],u.contains=[p,d,c,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,a,e.inherit(e.C_BLOCK_COMMENT_MODE,{illegal:/\\n/})];let m={variants:[o,f,d,s,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},h={begin:`<`,end:`>`,contains:[{beginKeywords:`in out`},i]},g=e.IDENT_RE+`(<`+e.IDENT_RE+`(\\\\s*,\\\\s*`+e.IDENT_RE+`)*>)?(\\\\[\\\\])?`,_={begin:`@`+e.IDENT_RE,relevance:0};return{name:`C#`,aliases:[`cs`,`c#`],keywords:r,illegal:/::/,contains:[e.COMMENT(`///`,`$`,{returnBegin:!0,contains:[{className:`doctag`,variants:[{begin:`///`,relevance:0},{begin:`<!--|-->`},{begin:`</?`,end:`>`}]}]}),e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:`meta`,begin:`#`,end:`$`,keywords:{keyword:`if else elif endif define undef warning error line region endregion pragma checksum`}},m,a,{beginKeywords:`class interface`,relevance:0,end:/[{;=]/,illegal:/[^\\s:,]/,contains:[{beginKeywords:`where class`},i,h,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{beginKeywords:`namespace`,relevance:0,end:/[{;=]/,illegal:/[^\\s:]/,contains:[i,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{beginKeywords:`record`,relevance:0,end:/[{;=]/,illegal:/[^\\s:]/,contains:[i,h,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:`meta`,begin:`^\\\\s*\\\\[(?=[\\\\w])`,excludeBegin:!0,end:`\\\\]`,excludeEnd:!0,contains:[{className:`string`,begin:/\"/,end:/\"/}]},{beginKeywords:`new return throw await else`,relevance:0},{className:`function`,begin:`(`+g+`\\\\s+)+`+e.IDENT_RE+`\\\\s*(<[^=]+>\\\\s*)?\\\\(`,returnBegin:!0,end:/\\s*[{;=]/,excludeEnd:!0,keywords:r,contains:[{beginKeywords:n.join(` `),relevance:0},{begin:e.IDENT_RE+`\\\\s*(<[^=]+>\\\\s*)?\\\\(`,returnBegin:!0,contains:[e.TITLE_MODE,h],relevance:0},{match:/\\(\\)/},{className:`params`,begin:/\\(/,end:/\\)/,excludeBegin:!0,excludeEnd:!0,keywords:r,relevance:0,contains:[m,a,e.C_BLOCK_COMMENT_MODE]},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},_]}}t.exports=n})),h=o(((e,t)=>{let n=e=>({IMPORTANT:{scope:`meta`,begin:`!important`},BLOCK_COMMENT:e.C_BLOCK_COMMENT_MODE,HEXCOLOR:{scope:`number`,begin:/#(([0-9a-fA-F]{3,4})|(([0-9a-fA-F]{2}){3,4}))\\b/},FUNCTION_DISPATCH:{className:`built_in`,begin:/[\\w-]+(?=\\()/},ATTRIBUTE_SELECTOR_MODE:{scope:`selector-attr`,begin:/\\[/,end:/\\]/,illegal:`$`,contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},CSS_NUMBER_MODE:{scope:`number`,begin:e.NUMBER_RE+`(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?`,relevance:0},CSS_VARIABLE:{className:`attr`,begin:/--[A-Za-z_][A-Za-z0-9_-]*/}}),r=`a.abbr.address.article.aside.audio.b.blockquote.body.button.canvas.caption.cite.code.dd.del.details.dfn.div.dl.dt.em.fieldset.figcaption.figure.footer.form.h1.h2.h3.h4.h5.h6.header.hgroup.html.i.iframe.img.input.ins.kbd.label.legend.li.main.mark.menu.nav.object.ol.optgroup.option.p.picture.q.quote.samp.section.select.source.span.strong.summary.sup.table.tbody.td.textarea.tfoot.th.thead.time.tr.ul.var.video`.split(`.`),i=`defs.g.marker.mask.pattern.svg.switch.symbol.feBlend.feColorMatrix.feComponentTransfer.feComposite.feConvolveMatrix.feDiffuseLighting.feDisplacementMap.feFlood.feGaussianBlur.feImage.feMerge.feMorphology.feOffset.feSpecularLighting.feTile.feTurbulence.linearGradient.radialGradient.stop.circle.ellipse.image.line.path.polygon.polyline.rect.text.use.textPath.tspan.foreignObject.clipPath`.split(`.`),a=[...r,...i],o=`any-hover.any-pointer.aspect-ratio.color.color-gamut.color-index.device-aspect-ratio.device-height.device-width.display-mode.forced-colors.grid.height.hover.inverted-colors.monochrome.orientation.overflow-block.overflow-inline.pointer.prefers-color-scheme.prefers-contrast.prefers-reduced-motion.prefers-reduced-transparency.resolution.scan.scripting.update.width.min-width.max-width.min-height.max-height`.split(`.`).sort().reverse(),s=`active.any-link.blank.checked.current.default.defined.dir.disabled.drop.empty.enabled.first.first-child.first-of-type.fullscreen.future.focus.focus-visible.focus-within.has.host.host-context.hover.indeterminate.in-range.invalid.is.lang.last-child.last-of-type.left.link.local-link.not.nth-child.nth-col.nth-last-child.nth-last-col.nth-last-of-type.nth-of-type.only-child.only-of-type.optional.out-of-range.past.placeholder-shown.read-only.read-write.required.right.root.scope.target.target-within.user-invalid.valid.visited.where`.split(`.`).sort().reverse(),c=[`after`,`backdrop`,`before`,`cue`,`cue-region`,`first-letter`,`first-line`,`grammar-error`,`marker`,`part`,`placeholder`,`selection`,`slotted`,`spelling-error`].sort().reverse(),l=`accent-color.align-content.align-items.align-self.alignment-baseline.all.anchor-name.animation.animation-composition.animation-delay.animation-direction.animation-duration.animation-fill-mode.animation-iteration-count.animation-name.animation-play-state.animation-range.animation-range-end.animation-range-start.animation-timeline.animation-timing-function.appearance.aspect-ratio.backdrop-filter.backface-visibility.background.background-attachment.background-blend-mode.background-clip.background-color.background-image.background-origin.background-position.background-position-x.background-position-y.background-repeat.background-size.baseline-shift.block-size.border.border-block.border-block-color.border-block-end.border-block-end-color.border-block-end-style.border-block-end-width.border-block-start.border-block-start-color.border-block-start-style.border-block-start-width.border-block-style.border-block-width.border-bottom.border-bottom-color.border-bottom-left-radius.border-bottom-right-radius.border-bottom-style.border-bottom-width.border-collapse.border-color.border-end-end-radius.border-end-start-radius.border-image.border-image-outset.border-image-repeat.border-image-slice.border-image-source.border-image-width.border-inline.border-inline-color.border-inline-end.border-inline-end-color.border-inline-end-style.border-inline-end-width.border-inline-start.border-inline-start-color.border-inline-start-style.border-inline-start-width.border-inline-style.border-inline-width.border-left.border-left-color.border-left-style.border-left-width.border-radius.border-right.border-right-color.border-right-style.border-right-width.border-spacing.border-start-end-radius.border-start-start-radius.border-style.border-top.border-top-color.border-top-left-radius.border-top-right-radius.border-top-style.border-top-width.border-width.bottom.box-align.box-decoration-break.box-direction.box-flex.box-flex-group.box-lines.box-ordinal-group.box-orient.box-pack.box-shadow.box-sizing.break-after.break-before.break-inside.caption-side.caret-color.clear.clip.clip-path.clip-rule.color.color-interpolation.color-interpolation-filters.color-profile.color-rendering.color-scheme.column-count.column-fill.column-gap.column-rule.column-rule-color.column-rule-style.column-rule-width.column-span.column-width.columns.contain.contain-intrinsic-block-size.contain-intrinsic-height.contain-intrinsic-inline-size.contain-intrinsic-size.contain-intrinsic-width.container.container-name.container-type.content.content-visibility.counter-increment.counter-reset.counter-set.cue.cue-after.cue-before.cursor.cx.cy.direction.display.dominant-baseline.empty-cells.enable-background.field-sizing.fill.fill-opacity.fill-rule.filter.flex.flex-basis.flex-direction.flex-flow.flex-grow.flex-shrink.flex-wrap.float.flood-color.flood-opacity.flow.font.font-display.font-family.font-feature-settings.font-kerning.font-language-override.font-optical-sizing.font-palette.font-size.font-size-adjust.font-smooth.font-smoothing.font-stretch.font-style.font-synthesis.font-synthesis-position.font-synthesis-small-caps.font-synthesis-style.font-synthesis-weight.font-variant.font-variant-alternates.font-variant-caps.font-variant-east-asian.font-variant-emoji.font-variant-ligatures.font-variant-numeric.font-variant-position.font-variation-settings.font-weight.forced-color-adjust.gap.glyph-orientation-horizontal.glyph-orientation-vertical.grid.grid-area.grid-auto-columns.grid-auto-flow.grid-auto-rows.grid-column.grid-column-end.grid-column-start.grid-gap.grid-row.grid-row-end.grid-row-start.grid-template.grid-template-areas.grid-template-columns.grid-template-rows.hanging-punctuation.height.hyphenate-character.hyphenate-limit-chars.hyphens.icon.image-orientation.image-rendering.image-resolution.ime-mode.initial-letter.initial-letter-align.inline-size.inset.inset-area.inset-block.inset-block-end.inset-block-start.inset-inline.inset-inline-end.inset-inline-start.isolation.justify-content.justify-items.justify-self.kerning.left.letter-spacing.lighting-color.line-break.line-height.line-height-step.list-style.list-style-image.list-style-position.list-style-type.margin.margin-block.margin-block-end.margin-block-start.margin-bottom.margin-inline.margin-inline-end.margin-inline-start.margin-left.margin-right.margin-top.margin-trim.marker.marker-end.marker-mid.marker-start.marks.mask.mask-border.mask-border-mode.mask-border-outset.mask-border-repeat.mask-border-slice.mask-border-source.mask-border-width.mask-clip.mask-composite.mask-image.mask-mode.mask-origin.mask-position.mask-repeat.mask-size.mask-type.masonry-auto-flow.math-depth.math-shift.math-style.max-block-size.max-height.max-inline-size.max-width.min-block-size.min-height.min-inline-size.min-width.mix-blend-mode.nav-down.nav-index.nav-left.nav-right.nav-up.none.normal.object-fit.object-position.offset.offset-anchor.offset-distance.offset-path.offset-position.offset-rotate.opacity.order.orphans.outline.outline-color.outline-offset.outline-style.outline-width.overflow.overflow-anchor.overflow-block.overflow-clip-margin.overflow-inline.overflow-wrap.overflow-x.overflow-y.overlay.overscroll-behavior.overscroll-behavior-block.overscroll-behavior-inline.overscroll-behavior-x.overscroll-behavior-y.padding.padding-block.padding-block-end.padding-block-start.padding-bottom.padding-inline.padding-inline-end.padding-inline-start.padding-left.padding-right.padding-top.page.page-break-after.page-break-before.page-break-inside.paint-order.pause.pause-after.pause-before.perspective.perspective-origin.place-content.place-items.place-self.pointer-events.position.position-anchor.position-visibility.print-color-adjust.quotes.r.resize.rest.rest-after.rest-before.right.rotate.row-gap.ruby-align.ruby-position.scale.scroll-behavior.scroll-margin.scroll-margin-block.scroll-margin-block-end.scroll-margin-block-start.scroll-margin-bottom.scroll-margin-inline.scroll-margin-inline-end.scroll-margin-inline-start.scroll-margin-left.scroll-margin-right.scroll-margin-top.scroll-padding.scroll-padding-block.scroll-padding-block-end.scroll-padding-block-start.scroll-padding-bottom.scroll-padding-inline.scroll-padding-inline-end.scroll-padding-inline-start.scroll-padding-left.scroll-padding-right.scroll-padding-top.scroll-snap-align.scroll-snap-stop.scroll-snap-type.scroll-timeline.scroll-timeline-axis.scroll-timeline-name.scrollbar-color.scrollbar-gutter.scrollbar-width.shape-image-threshold.shape-margin.shape-outside.shape-rendering.speak.speak-as.src.stop-color.stop-opacity.stroke.stroke-dasharray.stroke-dashoffset.stroke-linecap.stroke-linejoin.stroke-miterlimit.stroke-opacity.stroke-width.tab-size.table-layout.text-align.text-align-all.text-align-last.text-anchor.text-combine-upright.text-decoration.text-decoration-color.text-decoration-line.text-decoration-skip.text-decoration-skip-ink.text-decoration-style.text-decoration-thickness.text-emphasis.text-emphasis-color.text-emphasis-position.text-emphasis-style.text-indent.text-justify.text-orientation.text-overflow.text-rendering.text-shadow.text-size-adjust.text-transform.text-underline-offset.text-underline-position.text-wrap.text-wrap-mode.text-wrap-style.timeline-scope.top.touch-action.transform.transform-box.transform-origin.transform-style.transition.transition-behavior.transition-delay.transition-duration.transition-property.transition-timing-function.translate.unicode-bidi.user-modify.user-select.vector-effect.vertical-align.view-timeline.view-timeline-axis.view-timeline-inset.view-timeline-name.view-transition-name.visibility.voice-balance.voice-duration.voice-family.voice-pitch.voice-range.voice-rate.voice-stress.voice-volume.white-space.white-space-collapse.widows.width.will-change.word-break.word-spacing.word-wrap.writing-mode.x.y.z-index.zoom`.split(`.`).sort().reverse();function u(e){let t=e.regex,r=n(e),i={begin:/-(webkit|moz|ms|o)-(?=[a-z])/},u=/@-?\\w[\\w]*(-\\w+)*/,d=[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE];return{name:`CSS`,case_insensitive:!0,illegal:/[=|'\\$]/,keywords:{keyframePosition:`from to`},classNameAliases:{keyframePosition:`selector-tag`},contains:[r.BLOCK_COMMENT,i,r.CSS_NUMBER_MODE,{className:`selector-id`,begin:/#[A-Za-z0-9_-]+/,relevance:0},{className:`selector-class`,begin:`\\\\.[a-zA-Z-][a-zA-Z0-9_-]*`,relevance:0},r.ATTRIBUTE_SELECTOR_MODE,{className:`selector-pseudo`,variants:[{begin:`:(`+s.join(`|`)+`)`},{begin:`:(:)?(`+c.join(`|`)+`)`}]},r.CSS_VARIABLE,{className:`attribute`,begin:`\\\\b(`+l.join(`|`)+`)\\\\b`},{begin:/:/,end:/[;}{]/,contains:[r.BLOCK_COMMENT,r.HEXCOLOR,r.IMPORTANT,r.CSS_NUMBER_MODE,...d,{begin:/(url|data-uri)\\(/,end:/\\)/,relevance:0,keywords:{built_in:`url data-uri`},contains:[...d,{className:`string`,begin:/[^)]/,endsWithParent:!0,excludeEnd:!0}]},r.FUNCTION_DISPATCH]},{begin:t.lookahead(/@/),end:`[{;]`,relevance:0,illegal:/:/,contains:[{className:`keyword`,begin:u},{begin:/\\s/,endsWithParent:!0,excludeEnd:!0,relevance:0,keywords:{$pattern:/[a-z-]+/,keyword:`and or not only`,attribute:o.join(` `)},contains:[{begin:/[a-z-]+(?=:)/,className:`attribute`},...d,r.CSS_NUMBER_MODE]}]},{className:`selector-tag`,begin:`\\\\b(`+a.join(`|`)+`)\\\\b`}]}}t.exports=u})),g=o(((e,t)=>{function n(e){let t=e.regex,n={begin:/<\\/?[A-Za-z_]/,end:`>`,subLanguage:`xml`,relevance:0},r={begin:`^[-\\\\*]{3,}`,end:`$`},i={className:`code`,variants:[{begin:\"(`{3,})[^`](.|\\\\n)*?\\\\1`*[ ]*\"},{begin:`(~{3,})[^~](.|\\\\n)*?\\\\1~*[ ]*`},{begin:\"```\",end:\"```+[ ]*$\"},{begin:`~~~`,end:`~~~+[ ]*$`},{begin:\"`.+?`\"},{begin:`(?=^( {4}|\\\\t))`,contains:[{begin:`^( {4}|\\\\t)`,end:`(\\\\n)$`}],relevance:0}]},a={className:`bullet`,begin:`^[ \t]*([*+-]|(\\\\d+\\\\.))(?=\\\\s+)`,end:`\\\\s+`,excludeEnd:!0},o={begin:/^\\[[^\\n]+\\]:/,returnBegin:!0,contains:[{className:`symbol`,begin:/\\[/,end:/\\]/,excludeBegin:!0,excludeEnd:!0},{className:`link`,begin:/:\\s*/,end:/$/,excludeBegin:!0}]},s={variants:[{begin:/\\[.+?\\]\\[.*?\\]/,relevance:0},{begin:/\\[.+?\\]\\(((data|javascript|mailto):|(?:http|ftp)s?:\\/\\/).*?\\)/,relevance:2},{begin:t.concat(/\\[.+?\\]\\(/,/[A-Za-z][A-Za-z0-9+.-]*/,/:\\/\\/.*?\\)/),relevance:2},{begin:/\\[.+?\\]\\([./?&#].*?\\)/,relevance:1},{begin:/\\[.*?\\]\\(.*?\\)/,relevance:0}],returnBegin:!0,contains:[{match:/\\[(?=\\])/},{className:`string`,relevance:0,begin:`\\\\[`,end:`\\\\]`,excludeBegin:!0,returnEnd:!0},{className:`link`,relevance:0,begin:`\\\\]\\\\(`,end:`\\\\)`,excludeBegin:!0,excludeEnd:!0},{className:`symbol`,relevance:0,begin:`\\\\]\\\\[`,end:`\\\\]`,excludeBegin:!0,excludeEnd:!0}]},c={className:`strong`,contains:[],variants:[{begin:/_{2}(?!\\s)/,end:/_{2}/},{begin:/\\*{2}(?!\\s)/,end:/\\*{2}/}]},l={className:`emphasis`,contains:[],variants:[{begin:/\\*(?![*\\s])/,end:/\\*/},{begin:/_(?![_\\s])/,end:/_/,relevance:0}]},u=e.inherit(c,{contains:[]}),d=e.inherit(l,{contains:[]});c.contains.push(d),l.contains.push(u);let f=[n,s];return[c,l,u,d].forEach(e=>{e.contains=e.contains.concat(f)}),f=f.concat(c,l),{name:`Markdown`,aliases:[`md`,`mkdown`,`mkd`],contains:[{className:`section`,variants:[{begin:`^#{1,6}`,end:`$`,contains:f},{begin:`(?=^.+?\\\\n[=-]{2,}$)`,contains:[{begin:`^[=-]*$`},{begin:`^`,end:`\\\\n`,contains:f}]}]},n,a,c,l,{className:`quote`,begin:`^>\\\\s+`,contains:f,end:`$`},i,r,s,o,{scope:`literal`,match:/&([a-zA-Z0-9]+|#[0-9]{1,7}|#[Xx][0-9a-fA-F]{1,6});/}]}}t.exports=n})),_=o(((e,t)=>{function n(e){let t=e.regex;return{name:`Diff`,aliases:[`patch`],contains:[{className:`meta`,relevance:10,match:t.either(/^@@ +-\\d+,\\d+ +\\+\\d+,\\d+ +@@/,/^\\*\\*\\* +\\d+,\\d+ +\\*\\*\\*\\*$/,/^--- +\\d+,\\d+ +----$/)},{className:`comment`,variants:[{begin:t.either(/Index: /,/^index/,/={3,}/,/^-{3}/,/^\\*{3} /,/^\\+{3}/,/^diff --git/),end:/$/},{match:/^\\*{15}$/}]},{className:`addition`,begin:/^\\+/,end:/$/},{className:`deletion`,begin:/^-/,end:/$/},{className:`addition`,begin:/^!/,end:/$/}]}}t.exports=n})),v=o(((e,t)=>{function n(e){let t=e.regex,n=\"([a-zA-Z_]\\\\w*[!?=]?|[-+~]@|<<|>>|=~|===?|<=>|[<>]=?|\\\\*\\\\*|[-/+%^&*~`|]|\\\\[\\\\]=?)\",r=t.either(/\\b([A-Z]+[a-z0-9]+)+/,/\\b([A-Z]+[a-z0-9]+)+[A-Z]+/),i=t.concat(r,/(::\\w+)*/),a=[`include`,`extend`,`prepend`,`public`,`private`,`protected`,`raise`,`throw`],o={\"variable.constant\":[`__FILE__`,`__LINE__`,`__ENCODING__`],\"variable.language\":[`self`,`super`],keyword:[`alias`,`and`,`begin`,`BEGIN`,`break`,`case`,`class`,`defined`,`do`,`else`,`elsif`,`end`,`END`,`ensure`,`for`,`if`,`in`,`module`,`next`,`not`,`or`,`redo`,`require`,`rescue`,`retry`,`return`,`then`,`undef`,`unless`,`until`,`when`,`while`,`yield`,...a],built_in:[`proc`,`lambda`,`attr_accessor`,`attr_reader`,`attr_writer`,`define_method`,`private_constant`,`module_function`],literal:[`true`,`false`,`nil`]},s={className:`doctag`,begin:`@[A-Za-z]+`},c={begin:`#<`,end:`>`},l=[e.COMMENT(`#`,`$`,{contains:[s]}),e.COMMENT(`^=begin`,`^=end`,{contains:[s],relevance:10}),e.COMMENT(`^__END__`,e.MATCH_NOTHING_RE)],u={className:`subst`,begin:/#\\{/,end:/\\}/,keywords:o},d={className:`string`,contains:[e.BACKSLASH_ESCAPE,u],variants:[{begin:/'/,end:/'/},{begin:/\"/,end:/\"/},{begin:/`/,end:/`/},{begin:/%[qQwWx]?\\(/,end:/\\)/},{begin:/%[qQwWx]?\\[/,end:/\\]/},{begin:/%[qQwWx]?\\{/,end:/\\}/},{begin:/%[qQwWx]?</,end:/>/},{begin:/%[qQwWx]?\\//,end:/\\//},{begin:/%[qQwWx]?%/,end:/%/},{begin:/%[qQwWx]?-/,end:/-/},{begin:/%[qQwWx]?\\|/,end:/\\|/},{begin:/\\B\\?(\\\\\\d{1,3})/},{begin:/\\B\\?(\\\\x[A-Fa-f0-9]{1,2})/},{begin:/\\B\\?(\\\\u\\{?[A-Fa-f0-9]{1,6}\\}?)/},{begin:/\\B\\?(\\\\M-\\\\C-|\\\\M-\\\\c|\\\\c\\\\M-|\\\\M-|\\\\C-\\\\M-)[\\x20-\\x7e]/},{begin:/\\B\\?\\\\(c|C-)[\\x20-\\x7e]/},{begin:/\\B\\?\\\\?\\S/},{begin:t.concat(/<<[-~]?'?/,t.lookahead(/(\\w+)(?=\\W)[^\\n]*\\n(?:[^\\n]*\\n)*?\\s*\\1\\b/)),contains:[e.END_SAME_AS_BEGIN({begin:/(\\w+)/,end:/(\\w+)/,contains:[e.BACKSLASH_ESCAPE,u]})]}]},f=`[0-9](_?[0-9])*`,p={className:`number`,relevance:0,variants:[{begin:`\\\\b([1-9](_?[0-9])*|0)(\\\\.(${f}))?([eE][+-]?(${f})|r)?i?\\\\b`},{begin:`\\\\b0[dD][0-9](_?[0-9])*r?i?\\\\b`},{begin:`\\\\b0[bB][0-1](_?[0-1])*r?i?\\\\b`},{begin:`\\\\b0[oO][0-7](_?[0-7])*r?i?\\\\b`},{begin:`\\\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*r?i?\\\\b`},{begin:`\\\\b0(_?[0-7])+r?i?\\\\b`}]},m={variants:[{match:/\\(\\)/},{className:`params`,begin:/\\(/,end:/(?=\\))/,excludeBegin:!0,endsParent:!0,keywords:o}]},h={match:[/(include|extend)\\s+/,i],scope:{2:`title.class`},keywords:o},g={variants:[{match:[/class\\s+/,i,/\\s+<\\s+/,i]},{match:[/\\b(class|module)\\s+/,i]}],scope:{2:`title.class`,4:`title.class.inherited`},keywords:o},_={relevance:0,match:/\\b[A-Z][A-Z_0-9]+\\b/,className:`variable.constant`},v={match:[/def/,/\\s+/,n],scope:{1:`keyword`,3:`title.function`},contains:[m]},y=[d,g,h,{relevance:0,match:[i,/\\.new[. (]/],scope:{1:`title.class`}},_,{relevance:0,match:r,scope:`title.class`},v,{begin:e.IDENT_RE+`::`},{className:`symbol`,begin:e.UNDERSCORE_IDENT_RE+`(!|\\\\?)?:`,relevance:0},{className:`symbol`,begin:`:(?!\\\\s)`,contains:[d,{begin:n}],relevance:0},p,{className:`variable`,begin:`(\\\\$\\\\W)|((\\\\$|@@?)(\\\\w+))(?=[^@$?])(?![A-Za-z])(?![@$?'])`},{className:`params`,begin:/\\|(?!=)/,end:/\\|/,excludeBegin:!0,excludeEnd:!0,relevance:0,keywords:o},{begin:`(`+e.RE_STARTERS_RE+`|unless)\\\\s*`,keywords:`unless`,contains:[{className:`regexp`,contains:[e.BACKSLASH_ESCAPE,u],illegal:/\\n/,variants:[{begin:`/`,end:`/[a-z]*`},{begin:/%r\\{/,end:/\\}[a-z]*/},{begin:`%r\\\\(`,end:`\\\\)[a-z]*`},{begin:`%r!`,end:`![a-z]*`},{begin:`%r\\\\[`,end:`\\\\][a-z]*`}]}].concat(c,l),relevance:0}].concat(c,l);u.contains=y,m.contains=y;let b=[{begin:/^\\s*=>/,starts:{end:`$`,contains:y}},{className:`meta.prompt`,begin:`^([>?]>|[\\\\w#]+\\\\(\\\\w+\\\\):\\\\d+:\\\\d+[>*]|(\\\\w+-)?\\\\d+\\\\.\\\\d+\\\\.\\\\d+(p\\\\d+)?[^\\\\d][^>]+>)(?=[ ])`,starts:{end:`$`,keywords:o,contains:y}}];return l.unshift(c),{name:`Ruby`,aliases:[`rb`,`gemspec`,`podspec`,`thor`,`irb`],keywords:o,illegal:/\\/\\*/,contains:[e.SHEBANG({binary:`ruby`})].concat(b,l,y)}}t.exports=n})),y=o(((e,t)=>{function n(e){let t={keyword:[`break`,`case`,`chan`,`const`,`continue`,`default`,`defer`,`else`,`fallthrough`,`for`,`func`,`go`,`goto`,`if`,`import`,`interface`,`map`,`package`,`range`,`return`,`select`,`struct`,`switch`,`type`,`var`],type:[`bool`,`byte`,`complex64`,`complex128`,`error`,`float32`,`float64`,`int8`,`int16`,`int32`,`int64`,`string`,`uint8`,`uint16`,`uint32`,`uint64`,`int`,`uint`,`uintptr`,`rune`],literal:[`true`,`false`,`iota`,`nil`],built_in:[`append`,`cap`,`close`,`complex`,`copy`,`imag`,`len`,`make`,`new`,`panic`,`print`,`println`,`real`,`recover`,`delete`]};return{name:`Go`,aliases:[`golang`],keywords:t,illegal:`</`,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:`string`,variants:[e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,{begin:\"`\",end:\"`\"}]},{className:`number`,variants:[{match:/-?\\b0[xX]\\.[a-fA-F0-9](_?[a-fA-F0-9])*[pP][+-]?\\d(_?\\d)*i?/,relevance:0},{match:/-?\\b0[xX](_?[a-fA-F0-9])+((\\.([a-fA-F0-9](_?[a-fA-F0-9])*)?)?[pP][+-]?\\d(_?\\d)*)?i?/,relevance:0},{match:/-?\\b0[oO](_?[0-7])*i?/,relevance:0},{match:/-?\\.\\d(_?\\d)*([eE][+-]?\\d(_?\\d)*)?i?/,relevance:0},{match:/-?\\b\\d(_?\\d)*(\\.(\\d(_?\\d)*)?)?([eE][+-]?\\d(_?\\d)*)?i?/,relevance:0}]},{begin:/:=/},{className:`function`,beginKeywords:`func`,end:`\\\\s*(\\\\{|$)`,excludeEnd:!0,contains:[e.TITLE_MODE,{className:`params`,begin:/\\(/,end:/\\)/,endsParent:!0,keywords:t,illegal:/[\"']/}]}]}}t.exports=n})),b=o(((e,t)=>{function n(e){let t=e.regex;return{name:`GraphQL`,aliases:[`gql`],case_insensitive:!0,disableAutodetect:!1,keywords:{keyword:[`query`,`mutation`,`subscription`,`type`,`input`,`schema`,`directive`,`interface`,`union`,`scalar`,`fragment`,`enum`,`on`],literal:[`true`,`false`,`null`]},contains:[e.HASH_COMMENT_MODE,e.QUOTE_STRING_MODE,e.NUMBER_MODE,{scope:`punctuation`,match:/[.]{3}/,relevance:0},{scope:`punctuation`,begin:/[\\!\\(\\)\\:\\=\\[\\]\\{\\|\\}]{1}/,relevance:0},{scope:`variable`,begin:/\\$/,end:/\\W/,excludeEnd:!0,relevance:0},{scope:`meta`,match:/@\\w+/,excludeEnd:!0},{scope:`symbol`,begin:t.concat(/[_A-Za-z][_0-9A-Za-z]*/,t.lookahead(/\\s*:/)),relevance:0}],illegal:[/[;<']/,/BEGIN/]}}t.exports=n})),x=o(((e,t)=>{function n(e){let t=e.regex,n={className:`number`,relevance:0,variants:[{begin:/([+-]+)?[\\d]+_[\\d_]+/},{begin:e.NUMBER_RE}]},r=e.COMMENT();r.variants=[{begin:/;/,end:/$/},{begin:/#/,end:/$/}];let i={className:`variable`,variants:[{begin:/\\$[\\w\\d\"][\\w\\d_]*/},{begin:/\\$\\{(.*?)\\}/}]},a={className:`literal`,begin:/\\bon|off|true|false|yes|no\\b/},o={className:`string`,contains:[e.BACKSLASH_ESCAPE],variants:[{begin:`'''`,end:`'''`,relevance:10},{begin:`\"\"\"`,end:`\"\"\"`,relevance:10},{begin:`\"`,end:`\"`},{begin:`'`,end:`'`}]},s={begin:/\\[/,end:/\\]/,contains:[r,a,i,o,n,`self`],relevance:0},c=t.either(/[A-Za-z0-9_-]+/,/\"(\\\\\"|[^\"])*\"/,/'[^']*'/);return{name:`TOML, also INI`,aliases:[`toml`],case_insensitive:!0,illegal:/\\S/,contains:[r,{className:`section`,begin:/\\[+/,end:/\\]+/},{begin:t.concat(c,`(\\\\s*\\\\.\\\\s*`,c,`)*`,t.lookahead(/\\s*=\\s*[^#\\s]/)),className:`attr`,starts:{end:/$/,contains:[r,s,a,i,o,n]}}]}}t.exports=n})),S=o(((e,t)=>{var n=`[0-9](_*[0-9])*`,r=`\\\\.(${n})`,i=`[0-9a-fA-F](_*[0-9a-fA-F])*`,a={className:`number`,variants:[{begin:`(\\\\b(${n})((${r})|\\\\.)?|(${r}))[eE][+-]?(${n})[fFdD]?\\\\b`},{begin:`\\\\b(${n})((${r})[fFdD]?\\\\b|\\\\.([fFdD]\\\\b)?)`},{begin:`(${r})[fFdD]?\\\\b`},{begin:`\\\\b(${n})[fFdD]\\\\b`},{begin:`\\\\b0[xX]((${i})\\\\.?|(${i})?\\\\.(${i}))[pP][+-]?(${n})[fFdD]?\\\\b`},{begin:`\\\\b(0|[1-9](_*[0-9])*)[lL]?\\\\b`},{begin:`\\\\b0[xX](${i})[lL]?\\\\b`},{begin:`\\\\b0(_*[0-7])*[lL]?\\\\b`},{begin:`\\\\b0[bB][01](_*[01])*[lL]?\\\\b`}],relevance:0};function o(e,t,n){return n===-1?``:e.replace(t,r=>o(e,t,n-1))}function s(e){let t=e.regex,n=`[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*`,r=n+o(`(?:<`+n+`~~~(?:\\\\s*,\\\\s*[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*~~~)*>)?`,/~~~/g,2),i={keyword:`synchronized.abstract.private.var.static.if.const .for.while.strictfp.finally.protected.import.native.final.void.enum.else.break.transient.catch.instanceof.volatile.case.assert.package.default.public.try.switch.continue.throws.protected.public.private.module.requires.exports.do.sealed.yield.permits.goto.when`.split(`.`),literal:[`false`,`true`,`null`],type:[`char`,`boolean`,`long`,`float`,`int`,`byte`,`short`,`double`],built_in:[`super`,`this`]},s={className:`meta`,begin:`@`+n,contains:[{begin:/\\(/,end:/\\)/,contains:[`self`]}]},c={className:`params`,begin:/\\(/,end:/\\)/,keywords:i,relevance:0,contains:[e.C_BLOCK_COMMENT_MODE],endsParent:!0};return{name:`Java`,aliases:[`jsp`],keywords:i,illegal:/<\\/|#/,contains:[e.COMMENT(`/\\\\*\\\\*`,`\\\\*/`,{relevance:0,contains:[{begin:/\\w+@/,relevance:0},{className:`doctag`,begin:`@[A-Za-z]+`}]}),{begin:/import java\\.[a-z]+\\./,keywords:`import`,relevance:2},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{begin:/\"\"\"/,end:/\"\"\"/,className:`string`,contains:[e.BACKSLASH_ESCAPE]},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{match:[/\\b(?:class|interface|enum|extends|implements|new)/,/\\s+/,n],className:{1:`keyword`,3:`title.class`}},{match:/non-sealed/,scope:`keyword`},{begin:[t.concat(/(?!else)/,n),/\\s+/,n,/\\s+/,/=(?!=)/],className:{1:`type`,3:`variable`,5:`operator`}},{begin:[/record/,/\\s+/,n],className:{1:`keyword`,3:`title.class`},contains:[c,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{beginKeywords:`new throw return else`,relevance:0},{begin:[`(?:`+r+`\\\\s+)`,e.UNDERSCORE_IDENT_RE,/\\s*(?=\\()/],className:{2:`title.function`},keywords:i,contains:[{className:`params`,begin:/\\(/,end:/\\)/,keywords:i,relevance:0,contains:[s,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,a,e.C_BLOCK_COMMENT_MODE]},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},a,s]}}t.exports=s})),C=o(((e,t)=>{let n=`[A-Za-z$_][0-9A-Za-z$_]*`,r=`as.in.of.if.for.while.finally.var.new.function.do.return.void.else.break.catch.instanceof.with.throw.case.default.try.switch.continue.typeof.delete.let.yield.const.class.debugger.async.await.static.import.from.export.extends.using`.split(`.`),i=[`true`,`false`,`null`,`undefined`,`NaN`,`Infinity`],a=`Object.Function.Boolean.Symbol.Math.Date.Number.BigInt.String.RegExp.Array.Float32Array.Float64Array.Int8Array.Uint8Array.Uint8ClampedArray.Int16Array.Int32Array.Uint16Array.Uint32Array.BigInt64Array.BigUint64Array.Set.Map.WeakSet.WeakMap.ArrayBuffer.SharedArrayBuffer.Atomics.DataView.JSON.Promise.Generator.GeneratorFunction.AsyncFunction.Reflect.Proxy.Intl.WebAssembly`.split(`.`),o=[`Error`,`EvalError`,`InternalError`,`RangeError`,`ReferenceError`,`SyntaxError`,`TypeError`,`URIError`],s=[`setInterval`,`setTimeout`,`clearInterval`,`clearTimeout`,`require`,`exports`,`eval`,`isFinite`,`isNaN`,`parseFloat`,`parseInt`,`decodeURI`,`decodeURIComponent`,`encodeURI`,`encodeURIComponent`,`escape`,`unescape`],c=[`arguments`,`this`,`super`,`console`,`window`,`document`,`localStorage`,`sessionStorage`,`module`,`global`],l=[].concat(s,a,o);function u(e){let t=e.regex,u=(e,{after:t})=>{let n=`</`+e[0].slice(1);return e.input.indexOf(n,t)!==-1},d=n,f={begin:`<>`,end:`</>`},p=/<[A-Za-z0-9\\\\._:-]+\\s*\\/>/,m={begin:/<[A-Za-z0-9\\\\._:-]+/,end:/\\/[A-Za-z0-9\\\\._:-]+>|\\/>/,isTrulyOpeningTag:(e,t)=>{let n=e[0].length+e.index,r=e.input[n];if(r===`<`||r===`,`){t.ignoreMatch();return}r===`>`&&(u(e,{after:n})||t.ignoreMatch());let i,a=e.input.substring(n);if(i=a.match(/^\\s*=/)){t.ignoreMatch();return}if((i=a.match(/^\\s+extends\\s+/))&&i.index===0){t.ignoreMatch();return}}},h={$pattern:n,keyword:r,literal:i,built_in:l,\"variable.language\":c},g=`[0-9](_?[0-9])*`,_=`\\\\.(${g})`,v=`0|[1-9](_?[0-9])*|0[0-7]*[89][0-9]*`,y={className:`number`,variants:[{begin:`(\\\\b(${v})((${_})|\\\\.)?|(${_}))[eE][+-]?(${g})\\\\b`},{begin:`\\\\b(${v})\\\\b((${_})\\\\b|\\\\.)?|(${_})\\\\b`},{begin:`\\\\b(0|[1-9](_?[0-9])*)n\\\\b`},{begin:`\\\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*n?\\\\b`},{begin:`\\\\b0[bB][0-1](_?[0-1])*n?\\\\b`},{begin:`\\\\b0[oO][0-7](_?[0-7])*n?\\\\b`},{begin:`\\\\b0[0-7]+n?\\\\b`}],relevance:0},b={className:`subst`,begin:`\\\\$\\\\{`,end:`\\\\}`,keywords:h,contains:[]},x={begin:\".?html`\",end:``,starts:{end:\"`\",returnEnd:!1,contains:[e.BACKSLASH_ESCAPE,b],subLanguage:`xml`}},S={begin:\".?css`\",end:``,starts:{end:\"`\",returnEnd:!1,contains:[e.BACKSLASH_ESCAPE,b],subLanguage:`css`}},C={begin:\".?gql`\",end:``,starts:{end:\"`\",returnEnd:!1,contains:[e.BACKSLASH_ESCAPE,b],subLanguage:`graphql`}},w={className:`string`,begin:\"`\",end:\"`\",contains:[e.BACKSLASH_ESCAPE,b]},T={className:`comment`,variants:[e.COMMENT(/\\/\\*\\*(?!\\/)/,`\\\\*/`,{relevance:0,contains:[{begin:`(?=@[A-Za-z]+)`,relevance:0,contains:[{className:`doctag`,begin:`@[A-Za-z]+`},{className:`type`,begin:`\\\\{`,end:`\\\\}`,excludeEnd:!0,excludeBegin:!0,relevance:0},{className:`variable`,begin:d+`(?=\\\\s*(-)|$)`,endsParent:!0,relevance:0},{begin:/(?=[^\\n])\\s/,relevance:0}]}]}),e.C_BLOCK_COMMENT_MODE,e.C_LINE_COMMENT_MODE]},E=[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,x,S,C,w,{match:/\\$\\d+/},y];b.contains=E.concat({begin:/\\{/,end:/\\}/,keywords:h,contains:[`self`].concat(E)});let D=[].concat(T,b.contains),O=D.concat([{begin:/(\\s*)\\(/,end:/\\)/,keywords:h,contains:[`self`].concat(D)}]),k={className:`params`,begin:/(\\s*)\\(/,end:/\\)/,excludeBegin:!0,excludeEnd:!0,keywords:h,contains:O},A={variants:[{match:[/class/,/\\s+/,d,/\\s+/,/extends/,/\\s+/,t.concat(d,`(`,t.concat(/\\./,d),`)*`)],scope:{1:`keyword`,3:`title.class`,5:`keyword`,7:`title.class.inherited`}},{match:[/class/,/\\s+/,d],scope:{1:`keyword`,3:`title.class`}}]},j={relevance:0,match:t.either(/\\bJSON/,/\\b[A-Z][a-z]+([A-Z][a-z]*|\\d)*/,/\\b[A-Z]{2,}([A-Z][a-z]+|\\d)+([A-Z][a-z]*)*/,/\\b[A-Z]{2,}[a-z]+([A-Z][a-z]+|\\d)*([A-Z][a-z]*)*/),className:`title.class`,keywords:{_:[...a,...o]}},M={label:`use_strict`,className:`meta`,relevance:10,begin:/^\\s*['\"]use (strict|asm)['\"]/},N={variants:[{match:[/function/,/\\s+/,d,/(?=\\s*\\()/]},{match:[/function/,/\\s*(?=\\()/]}],className:{1:`keyword`,3:`title.function`},label:`func.def`,contains:[k],illegal:/%/},P={relevance:0,match:/\\b[A-Z][A-Z_0-9]+\\b/,className:`variable.constant`};function F(e){return t.concat(`(?!`,e.join(`|`),`)`)}let I={match:t.concat(/\\b/,F([...s,`super`,`import`].map(e=>`${e}\\\\s*\\\\(`)),d,t.lookahead(/\\s*\\(/)),className:`title.function`,relevance:0},L={begin:t.concat(/\\./,t.lookahead(t.concat(d,/(?![0-9A-Za-z$_(])/))),end:d,excludeBegin:!0,keywords:`prototype`,className:`property`,relevance:0},R={match:[/get|set/,/\\s+/,d,/(?=\\()/],className:{1:`keyword`,3:`title.function`},contains:[{begin:/\\(\\)/},k]},z=`(\\\\([^()]*(\\\\([^()]*(\\\\([^()]*\\\\)[^()]*)*\\\\)[^()]*)*\\\\)|`+e.UNDERSCORE_IDENT_RE+`)\\\\s*=>`,B={match:[/const|var|let/,/\\s+/,d,/\\s*/,/=\\s*/,/(async\\s*)?/,t.lookahead(z)],keywords:`async`,className:{1:`keyword`,3:`title.function`},contains:[k]};return{name:`JavaScript`,aliases:[`js`,`jsx`,`mjs`,`cjs`],keywords:h,exports:{PARAMS_CONTAINS:O,CLASS_REFERENCE:j},illegal:/#(?![$_A-z])/,contains:[e.SHEBANG({label:`shebang`,binary:`node`,relevance:5}),M,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,x,S,C,w,T,{match:/\\$\\d+/},y,j,{scope:`attr`,match:d+t.lookahead(`:`),relevance:0},B,{begin:`(`+e.RE_STARTERS_RE+`|\\\\b(case|return|throw)\\\\b)\\\\s*`,keywords:`return throw case`,relevance:0,contains:[T,e.REGEXP_MODE,{className:`function`,begin:z,returnBegin:!0,end:`\\\\s*=>`,contains:[{className:`params`,variants:[{begin:e.UNDERSCORE_IDENT_RE,relevance:0},{className:null,begin:/\\(\\s*\\)/,skip:!0},{begin:/(\\s*)\\(/,end:/\\)/,excludeBegin:!0,excludeEnd:!0,keywords:h,contains:O}]}]},{begin:/,/,relevance:0},{match:/\\s+/,relevance:0},{variants:[{begin:f.begin,end:f.end},{match:p},{begin:m.begin,\"on:begin\":m.isTrulyOpeningTag,end:m.end}],subLanguage:`xml`,contains:[{begin:m.begin,end:m.end,skip:!0,contains:[`self`]}]}]},N,{beginKeywords:`while if switch catch for`},{begin:`\\\\b(?!function)`+e.UNDERSCORE_IDENT_RE+`\\\\([^()]*(\\\\([^()]*(\\\\([^()]*\\\\)[^()]*)*\\\\)[^()]*)*\\\\)\\\\s*\\\\{`,returnBegin:!0,label:`func.def`,contains:[k,e.inherit(e.TITLE_MODE,{begin:d,className:`title.function`})]},{match:/\\.\\.\\./,relevance:0},L,{match:`\\\\$`+d,relevance:0},{match:[/\\bconstructor(?=\\s*\\()/],className:{1:`title.function`},contains:[k]},I,P,A,R,{match:/\\$[(.]/}]}}t.exports=u})),w=o(((e,t)=>{function n(e){let t={className:`attr`,begin:/\"(\\\\.|[^\\\\\"\\r\\n])*\"(?=\\s*:)/,relevance:1.01},n={match:/[{}[\\],:]/,className:`punctuation`,relevance:0},r=[`true`,`false`,`null`],i={scope:`literal`,beginKeywords:r.join(` `)};return{name:`JSON`,aliases:[`jsonc`],keywords:{literal:r},contains:[t,n,e.QUOTE_STRING_MODE,i,e.C_NUMBER_MODE,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE],illegal:`\\\\S`}}t.exports=n})),T=o(((e,t)=>{var n=`[0-9](_*[0-9])*`,r=`\\\\.(${n})`,i=`[0-9a-fA-F](_*[0-9a-fA-F])*`,a={className:`number`,variants:[{begin:`(\\\\b(${n})((${r})|\\\\.)?|(${r}))[eE][+-]?(${n})[fFdD]?\\\\b`},{begin:`\\\\b(${n})((${r})[fFdD]?\\\\b|\\\\.([fFdD]\\\\b)?)`},{begin:`(${r})[fFdD]?\\\\b`},{begin:`\\\\b(${n})[fFdD]\\\\b`},{begin:`\\\\b0[xX]((${i})\\\\.?|(${i})?\\\\.(${i}))[pP][+-]?(${n})[fFdD]?\\\\b`},{begin:`\\\\b(0|[1-9](_*[0-9])*)[lL]?\\\\b`},{begin:`\\\\b0[xX](${i})[lL]?\\\\b`},{begin:`\\\\b0(_*[0-7])*[lL]?\\\\b`},{begin:`\\\\b0[bB][01](_*[01])*[lL]?\\\\b`}],relevance:0};function o(e){let t={keyword:`abstract as val var vararg get set class object open private protected public noinline crossinline dynamic final enum if else do while for when throw try catch finally import package is in fun override companion reified inline lateinit init interface annotation data sealed internal infix operator out by constructor super tailrec where const inner suspend typealias external expect actual`,built_in:`Byte Short Char Int Long Boolean Float Double Void Unit Nothing`,literal:`true false null`},n={className:`keyword`,begin:/\\b(break|continue|return|this)\\b/,starts:{contains:[{className:`symbol`,begin:/@\\w+/}]}},r={className:`symbol`,begin:e.UNDERSCORE_IDENT_RE+`@`},i={className:`subst`,begin:/\\$\\{/,end:/\\}/,contains:[e.C_NUMBER_MODE]},o={className:`variable`,begin:`\\\\$`+e.UNDERSCORE_IDENT_RE},s={className:`string`,variants:[{begin:`\"\"\"`,end:`\"\"\"(?=[^\"])`,contains:[o,i]},{begin:`'`,end:`'`,illegal:/\\n/,contains:[e.BACKSLASH_ESCAPE]},{begin:`\"`,end:`\"`,illegal:/\\n/,contains:[e.BACKSLASH_ESCAPE,o,i]}]};i.contains.push(s);let c={className:`meta`,begin:`@(?:file|property|field|get|set|receiver|param|setparam|delegate)\\\\s*:(?:\\\\s*`+e.UNDERSCORE_IDENT_RE+`)?`},l={className:`meta`,begin:`@`+e.UNDERSCORE_IDENT_RE,contains:[{begin:/\\(/,end:/\\)/,contains:[e.inherit(s,{className:`string`}),`self`]}]},u=a,d=e.COMMENT(`/\\\\*`,`\\\\*/`,{contains:[e.C_BLOCK_COMMENT_MODE]}),f={variants:[{className:`type`,begin:e.UNDERSCORE_IDENT_RE},{begin:/\\(/,end:/\\)/,contains:[]}]},p=f;return p.variants[1].contains=[f],f.variants[1].contains=[p],{name:`Kotlin`,aliases:[`kt`,`kts`],keywords:t,contains:[e.COMMENT(`/\\\\*\\\\*`,`\\\\*/`,{relevance:0,contains:[{className:`doctag`,begin:`@[A-Za-z]+`}]}),e.C_LINE_COMMENT_MODE,d,n,r,c,l,{className:`function`,beginKeywords:`fun`,end:`[(]|$`,returnBegin:!0,excludeEnd:!0,keywords:t,relevance:5,contains:[{begin:e.UNDERSCORE_IDENT_RE+`\\\\s*\\\\(`,returnBegin:!0,relevance:0,contains:[e.UNDERSCORE_TITLE_MODE]},{className:`type`,begin:/</,end:/>/,keywords:`reified`,relevance:0},{className:`params`,begin:/\\(/,end:/\\)/,endsParent:!0,keywords:t,relevance:0,contains:[{begin:/:/,end:/[=,\\/]/,endsWithParent:!0,contains:[f,e.C_LINE_COMMENT_MODE,d],relevance:0},e.C_LINE_COMMENT_MODE,d,c,l,s,e.C_NUMBER_MODE]},d]},{begin:[/class|interface|trait/,/\\s+/,e.UNDERSCORE_IDENT_RE],beginScope:{3:`title.class`},keywords:`class interface trait`,end:/[:\\{(]|$/,excludeEnd:!0,illegal:`extends implements`,contains:[{beginKeywords:`public protected internal private constructor`},e.UNDERSCORE_TITLE_MODE,{className:`type`,begin:/</,end:/>/,excludeBegin:!0,excludeEnd:!0,relevance:0},{className:`type`,begin:/[,:]\\s*/,end:/[<\\(,){\\s]|$/,excludeBegin:!0,returnEnd:!0},c,l]},s,{className:`meta`,begin:`^#!/usr/bin/env`,end:`$`,illegal:`\n`},u]}}t.exports=o})),E=o(((e,t)=>{let n=e=>({IMPORTANT:{scope:`meta`,begin:`!important`},BLOCK_COMMENT:e.C_BLOCK_COMMENT_MODE,HEXCOLOR:{scope:`number`,begin:/#(([0-9a-fA-F]{3,4})|(([0-9a-fA-F]{2}){3,4}))\\b/},FUNCTION_DISPATCH:{className:`built_in`,begin:/[\\w-]+(?=\\()/},ATTRIBUTE_SELECTOR_MODE:{scope:`selector-attr`,begin:/\\[/,end:/\\]/,illegal:`$`,contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},CSS_NUMBER_MODE:{scope:`number`,begin:e.NUMBER_RE+`(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?`,relevance:0},CSS_VARIABLE:{className:`attr`,begin:/--[A-Za-z_][A-Za-z0-9_-]*/}}),r=`a.abbr.address.article.aside.audio.b.blockquote.body.button.canvas.caption.cite.code.dd.del.details.dfn.div.dl.dt.em.fieldset.figcaption.figure.footer.form.h1.h2.h3.h4.h5.h6.header.hgroup.html.i.iframe.img.input.ins.kbd.label.legend.li.main.mark.menu.nav.object.ol.optgroup.option.p.picture.q.quote.samp.section.select.source.span.strong.summary.sup.table.tbody.td.textarea.tfoot.th.thead.time.tr.ul.var.video`.split(`.`),i=`defs.g.marker.mask.pattern.svg.switch.symbol.feBlend.feColorMatrix.feComponentTransfer.feComposite.feConvolveMatrix.feDiffuseLighting.feDisplacementMap.feFlood.feGaussianBlur.feImage.feMerge.feMorphology.feOffset.feSpecularLighting.feTile.feTurbulence.linearGradient.radialGradient.stop.circle.ellipse.image.line.path.polygon.polyline.rect.text.use.textPath.tspan.foreignObject.clipPath`.split(`.`),a=[...r,...i],o=`any-hover.any-pointer.aspect-ratio.color.color-gamut.color-index.device-aspect-ratio.device-height.device-width.display-mode.forced-colors.grid.height.hover.inverted-colors.monochrome.orientation.overflow-block.overflow-inline.pointer.prefers-color-scheme.prefers-contrast.prefers-reduced-motion.prefers-reduced-transparency.resolution.scan.scripting.update.width.min-width.max-width.min-height.max-height`.split(`.`).sort().reverse(),s=`active.any-link.blank.checked.current.default.defined.dir.disabled.drop.empty.enabled.first.first-child.first-of-type.fullscreen.future.focus.focus-visible.focus-within.has.host.host-context.hover.indeterminate.in-range.invalid.is.lang.last-child.last-of-type.left.link.local-link.not.nth-child.nth-col.nth-last-child.nth-last-col.nth-last-of-type.nth-of-type.only-child.only-of-type.optional.out-of-range.past.placeholder-shown.read-only.read-write.required.right.root.scope.target.target-within.user-invalid.valid.visited.where`.split(`.`).sort().reverse(),c=[`after`,`backdrop`,`before`,`cue`,`cue-region`,`first-letter`,`first-line`,`grammar-error`,`marker`,`part`,`placeholder`,`selection`,`slotted`,`spelling-error`].sort().reverse(),l=`accent-color.align-content.align-items.align-self.alignment-baseline.all.anchor-name.animation.animation-composition.animation-delay.animation-direction.animation-duration.animation-fill-mode.animation-iteration-count.animation-name.animation-play-state.animation-range.animation-range-end.animation-range-start.animation-timeline.animation-timing-function.appearance.aspect-ratio.backdrop-filter.backface-visibility.background.background-attachment.background-blend-mode.background-clip.background-color.background-image.background-origin.background-position.background-position-x.background-position-y.background-repeat.background-size.baseline-shift.block-size.border.border-block.border-block-color.border-block-end.border-block-end-color.border-block-end-style.border-block-end-width.border-block-start.border-block-start-color.border-block-start-style.border-block-start-width.border-block-style.border-block-width.border-bottom.border-bottom-color.border-bottom-left-radius.border-bottom-right-radius.border-bottom-style.border-bottom-width.border-collapse.border-color.border-end-end-radius.border-end-start-radius.border-image.border-image-outset.border-image-repeat.border-image-slice.border-image-source.border-image-width.border-inline.border-inline-color.border-inline-end.border-inline-end-color.border-inline-end-style.border-inline-end-width.border-inline-start.border-inline-start-color.border-inline-start-style.border-inline-start-width.border-inline-style.border-inline-width.border-left.border-left-color.border-left-style.border-left-width.border-radius.border-right.border-right-color.border-right-style.border-right-width.border-spacing.border-start-end-radius.border-start-start-radius.border-style.border-top.border-top-color.border-top-left-radius.border-top-right-radius.border-top-style.border-top-width.border-width.bottom.box-align.box-decoration-break.box-direction.box-flex.box-flex-group.box-lines.box-ordinal-group.box-orient.box-pack.box-shadow.box-sizing.break-after.break-before.break-inside.caption-side.caret-color.clear.clip.clip-path.clip-rule.color.color-interpolation.color-interpolation-filters.color-profile.color-rendering.color-scheme.column-count.column-fill.column-gap.column-rule.column-rule-color.column-rule-style.column-rule-width.column-span.column-width.columns.contain.contain-intrinsic-block-size.contain-intrinsic-height.contain-intrinsic-inline-size.contain-intrinsic-size.contain-intrinsic-width.container.container-name.container-type.content.content-visibility.counter-increment.counter-reset.counter-set.cue.cue-after.cue-before.cursor.cx.cy.direction.display.dominant-baseline.empty-cells.enable-background.field-sizing.fill.fill-opacity.fill-rule.filter.flex.flex-basis.flex-direction.flex-flow.flex-grow.flex-shrink.flex-wrap.float.flood-color.flood-opacity.flow.font.font-display.font-family.font-feature-settings.font-kerning.font-language-override.font-optical-sizing.font-palette.font-size.font-size-adjust.font-smooth.font-smoothing.font-stretch.font-style.font-synthesis.font-synthesis-position.font-synthesis-small-caps.font-synthesis-style.font-synthesis-weight.font-variant.font-variant-alternates.font-variant-caps.font-variant-east-asian.font-variant-emoji.font-variant-ligatures.font-variant-numeric.font-variant-position.font-variation-settings.font-weight.forced-color-adjust.gap.glyph-orientation-horizontal.glyph-orientation-vertical.grid.grid-area.grid-auto-columns.grid-auto-flow.grid-auto-rows.grid-column.grid-column-end.grid-column-start.grid-gap.grid-row.grid-row-end.grid-row-start.grid-template.grid-template-areas.grid-template-columns.grid-template-rows.hanging-punctuation.height.hyphenate-character.hyphenate-limit-chars.hyphens.icon.image-orientation.image-rendering.image-resolution.ime-mode.initial-letter.initial-letter-align.inline-size.inset.inset-area.inset-block.inset-block-end.inset-block-start.inset-inline.inset-inline-end.inset-inline-start.isolation.justify-content.justify-items.justify-self.kerning.left.letter-spacing.lighting-color.line-break.line-height.line-height-step.list-style.list-style-image.list-style-position.list-style-type.margin.margin-block.margin-block-end.margin-block-start.margin-bottom.margin-inline.margin-inline-end.margin-inline-start.margin-left.margin-right.margin-top.margin-trim.marker.marker-end.marker-mid.marker-start.marks.mask.mask-border.mask-border-mode.mask-border-outset.mask-border-repeat.mask-border-slice.mask-border-source.mask-border-width.mask-clip.mask-composite.mask-image.mask-mode.mask-origin.mask-position.mask-repeat.mask-size.mask-type.masonry-auto-flow.math-depth.math-shift.math-style.max-block-size.max-height.max-inline-size.max-width.min-block-size.min-height.min-inline-size.min-width.mix-blend-mode.nav-down.nav-index.nav-left.nav-right.nav-up.none.normal.object-fit.object-position.offset.offset-anchor.offset-distance.offset-path.offset-position.offset-rotate.opacity.order.orphans.outline.outline-color.outline-offset.outline-style.outline-width.overflow.overflow-anchor.overflow-block.overflow-clip-margin.overflow-inline.overflow-wrap.overflow-x.overflow-y.overlay.overscroll-behavior.overscroll-behavior-block.overscroll-behavior-inline.overscroll-behavior-x.overscroll-behavior-y.padding.padding-block.padding-block-end.padding-block-start.padding-bottom.padding-inline.padding-inline-end.padding-inline-start.padding-left.padding-right.padding-top.page.page-break-after.page-break-before.page-break-inside.paint-order.pause.pause-after.pause-before.perspective.perspective-origin.place-content.place-items.place-self.pointer-events.position.position-anchor.position-visibility.print-color-adjust.quotes.r.resize.rest.rest-after.rest-before.right.rotate.row-gap.ruby-align.ruby-position.scale.scroll-behavior.scroll-margin.scroll-margin-block.scroll-margin-block-end.scroll-margin-block-start.scroll-margin-bottom.scroll-margin-inline.scroll-margin-inline-end.scroll-margin-inline-start.scroll-margin-left.scroll-margin-right.scroll-margin-top.scroll-padding.scroll-padding-block.scroll-padding-block-end.scroll-padding-block-start.scroll-padding-bottom.scroll-padding-inline.scroll-padding-inline-end.scroll-padding-inline-start.scroll-padding-left.scroll-padding-right.scroll-padding-top.scroll-snap-align.scroll-snap-stop.scroll-snap-type.scroll-timeline.scroll-timeline-axis.scroll-timeline-name.scrollbar-color.scrollbar-gutter.scrollbar-width.shape-image-threshold.shape-margin.shape-outside.shape-rendering.speak.speak-as.src.stop-color.stop-opacity.stroke.stroke-dasharray.stroke-dashoffset.stroke-linecap.stroke-linejoin.stroke-miterlimit.stroke-opacity.stroke-width.tab-size.table-layout.text-align.text-align-all.text-align-last.text-anchor.text-combine-upright.text-decoration.text-decoration-color.text-decoration-line.text-decoration-skip.text-decoration-skip-ink.text-decoration-style.text-decoration-thickness.text-emphasis.text-emphasis-color.text-emphasis-position.text-emphasis-style.text-indent.text-justify.text-orientation.text-overflow.text-rendering.text-shadow.text-size-adjust.text-transform.text-underline-offset.text-underline-position.text-wrap.text-wrap-mode.text-wrap-style.timeline-scope.top.touch-action.transform.transform-box.transform-origin.transform-style.transition.transition-behavior.transition-delay.transition-duration.transition-property.transition-timing-function.translate.unicode-bidi.user-modify.user-select.vector-effect.vertical-align.view-timeline.view-timeline-axis.view-timeline-inset.view-timeline-name.view-transition-name.visibility.voice-balance.voice-duration.voice-family.voice-pitch.voice-range.voice-rate.voice-stress.voice-volume.white-space.white-space-collapse.widows.width.will-change.word-break.word-spacing.word-wrap.writing-mode.x.y.z-index.zoom`.split(`.`).sort().reverse(),u=s.concat(c).sort().reverse();function d(e){let t=n(e),r=u,i=`[\\\\w-]+`,d=`(`+i+`|@\\\\{[\\\\w-]+\\\\})`,f=[],p=[],m=function(e){return{className:`string`,begin:`~?`+e+`.*?`+e}},h=function(e,t,n){return{className:e,begin:t,relevance:n}},g={$pattern:/[a-z-]+/,keyword:`and or not only`,attribute:o.join(` `)},_={begin:`\\\\(`,end:`\\\\)`,contains:p,keywords:g,relevance:0};p.push(e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,m(`'`),m(`\"`),t.CSS_NUMBER_MODE,{begin:`(url|data-uri)\\\\(`,starts:{className:`string`,end:`[\\\\)\\\\n]`,excludeEnd:!0}},t.HEXCOLOR,_,h(`variable`,`@@?`+i,10),h(`variable`,`@\\\\{`+i+`\\\\}`),h(`built_in`,\"~?`[^`]*?`\"),{className:`attribute`,begin:i+`\\\\s*:`,end:`:`,returnBegin:!0,excludeEnd:!0},t.IMPORTANT,{beginKeywords:`and not`},t.FUNCTION_DISPATCH);let v=p.concat({begin:/\\{/,end:/\\}/,contains:f}),y={beginKeywords:`when`,endsWithParent:!0,contains:[{beginKeywords:`and not`}].concat(p)},b={begin:d+`\\\\s*:`,returnBegin:!0,end:/[;}]/,relevance:0,contains:[{begin:/-(webkit|moz|ms|o)-/},t.CSS_VARIABLE,{className:`attribute`,begin:`\\\\b(`+l.join(`|`)+`)\\\\b`,end:/(?=:)/,starts:{endsWithParent:!0,illegal:`[<=$]`,relevance:0,contains:p}}]},x={className:`keyword`,begin:`@(import|media|charset|font-face|(-[a-z]+-)?keyframes|supports|document|namespace|page|viewport|host)\\\\b`,starts:{end:`[;{}]`,keywords:g,returnEnd:!0,contains:p,relevance:0}},S={className:`variable`,variants:[{begin:`@`+i+`\\\\s*:`,relevance:15},{begin:`@`+i}],starts:{end:`[;}]`,returnEnd:!0,contains:v}},C={variants:[{begin:`[\\\\.#:&\\\\[>]`,end:`[;{}]`},{begin:d,end:/\\{/}],returnBegin:!0,returnEnd:!0,illegal:`[<='$\"]`,relevance:0,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,y,h(`keyword`,`all\\\\b`),h(`variable`,`@\\\\{`+i+`\\\\}`),{begin:`\\\\b(`+a.join(`|`)+`)\\\\b`,className:`selector-tag`},t.CSS_NUMBER_MODE,h(`selector-tag`,d,0),h(`selector-id`,`#`+d),h(`selector-class`,`\\\\.`+d,0),h(`selector-tag`,`&`,0),t.ATTRIBUTE_SELECTOR_MODE,{className:`selector-pseudo`,begin:`:(`+s.join(`|`)+`)`},{className:`selector-pseudo`,begin:`:(:)?(`+c.join(`|`)+`)`},{begin:/\\(/,end:/\\)/,relevance:0,contains:v},{begin:`!important`},t.FUNCTION_DISPATCH]},w={begin:`[\\\\w-]+:(:)?(${r.join(`|`)})`,returnBegin:!0,contains:[C]};return f.push(e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,x,S,w,b,C,y,t.FUNCTION_DISPATCH),{name:`Less`,case_insensitive:!0,illegal:`[=>'/<($\"]`,contains:f}}t.exports=d})),D=o(((e,t)=>{function n(e){let t=`\\\\[=*\\\\[`,n=`\\\\]=*\\\\]`,r={begin:t,end:n,contains:[`self`]},i=[e.COMMENT(`--(?!`+t+`)`,`$`),e.COMMENT(`--`+t,n,{contains:[r],relevance:10})];return{name:`Lua`,aliases:[`pluto`],keywords:{$pattern:e.UNDERSCORE_IDENT_RE,literal:`true false nil`,keyword:`and break do else elseif end for goto if in local not or repeat return then until while`,built_in:`_G _ENV _VERSION __index __newindex __mode __call __metatable __tostring __len __gc __add __sub __mul __div __mod __pow __concat __unm __eq __lt __le assert collectgarbage dofile error getfenv getmetatable ipairs load loadfile loadstring module next pairs pcall print rawequal rawget rawset require select setfenv setmetatable tonumber tostring type unpack xpcall arg self coroutine resume yield status wrap create running debug getupvalue debug sethook getmetatable gethook setmetatable setlocal traceback setfenv getinfo setupvalue getlocal getregistry getfenv io lines write close flush open output type read stderr stdin input stdout popen tmpfile math log max acos huge ldexp pi cos tanh pow deg tan cosh sinh random randomseed frexp ceil floor rad abs sqrt modf asin min mod fmod log10 atan2 exp sin atan os exit setlocale date getenv difftime remove time clock tmpname rename execute package preload loadlib loaded loaders cpath config path seeall string sub upper len gfind rep find match char dump gmatch reverse byte format gsub lower table setn insert getn foreachi maxn foreach concat sort remove`},contains:i.concat([{className:`function`,beginKeywords:`function`,end:`\\\\)`,contains:[e.inherit(e.TITLE_MODE,{begin:`([_a-zA-Z]\\\\w*\\\\.)*([_a-zA-Z]\\\\w*:)?[_a-zA-Z]\\\\w*`}),{className:`params`,begin:`\\\\(`,endsWithParent:!0,contains:i}].concat(i)},e.C_NUMBER_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{className:`string`,begin:t,end:n,contains:[r],relevance:5}])}}t.exports=n})),O=o(((e,t)=>{function n(e){let t={className:`variable`,variants:[{begin:`\\\\$\\\\(`+e.UNDERSCORE_IDENT_RE+`\\\\)`,contains:[e.BACKSLASH_ESCAPE]},{begin:/\\$[@%<?\\^\\+\\*]/}]},n={className:`string`,begin:/\"/,end:/\"/,contains:[e.BACKSLASH_ESCAPE,t]},r={className:`variable`,begin:/\\$\\([\\w-]+\\s/,end:/\\)/,keywords:{built_in:`subst patsubst strip findstring filter filter-out sort word wordlist firstword lastword dir notdir suffix basename addsuffix addprefix join wildcard realpath abspath error warning shell origin flavor foreach if or and call eval file value`},contains:[t,n]},i={begin:`^`+e.UNDERSCORE_IDENT_RE+`\\\\s*(?=[:+?]?=)`},a={className:`meta`,begin:/^\\.PHONY:/,end:/$/,keywords:{$pattern:/[\\.\\w]+/,keyword:`.PHONY`}},o={className:`section`,begin:/^[^\\s]+:/,end:/$/,contains:[t]};return{name:`Makefile`,aliases:[`mk`,`mak`,`make`],keywords:{$pattern:/[\\w-]+/,keyword:`define endef undefine ifdef ifndef ifeq ifneq else endif include -include sinclude override export unexport private vpath`},contains:[e.HASH_COMMENT_MODE,t,n,r,i,a,o]}}t.exports=n})),k=o(((e,t)=>{function n(e){let t=e.regex,n=`abs.accept.alarm.and.atan2.bind.binmode.bless.break.caller.chdir.chmod.chomp.chop.chown.chr.chroot.class.close.closedir.connect.continue.cos.crypt.dbmclose.dbmopen.defined.delete.die.do.dump.each.else.elsif.endgrent.endhostent.endnetent.endprotoent.endpwent.endservent.eof.eval.exec.exists.exit.exp.fcntl.field.fileno.flock.for.foreach.fork.format.formline.getc.getgrent.getgrgid.getgrnam.gethostbyaddr.gethostbyname.gethostent.getlogin.getnetbyaddr.getnetbyname.getnetent.getpeername.getpgrp.getpriority.getprotobyname.getprotobynumber.getprotoent.getpwent.getpwnam.getpwuid.getservbyname.getservbyport.getservent.getsockname.getsockopt.given.glob.gmtime.goto.grep.gt.hex.if.index.int.ioctl.join.keys.kill.last.lc.lcfirst.length.link.listen.local.localtime.log.lstat.lt.ma.map.method.mkdir.msgctl.msgget.msgrcv.msgsnd.my.ne.next.no.not.oct.open.opendir.or.ord.our.pack.package.pipe.pop.pos.print.printf.prototype.push.q|0.qq.quotemeta.qw.qx.rand.read.readdir.readline.readlink.readpipe.recv.redo.ref.rename.require.reset.return.reverse.rewinddir.rindex.rmdir.say.scalar.seek.seekdir.select.semctl.semget.semop.send.setgrent.sethostent.setnetent.setpgrp.setpriority.setprotoent.setpwent.setservent.setsockopt.shift.shmctl.shmget.shmread.shmwrite.shutdown.sin.sleep.socket.socketpair.sort.splice.split.sprintf.sqrt.srand.stat.state.study.sub.substr.symlink.syscall.sysopen.sysread.sysseek.system.syswrite.tell.telldir.tie.tied.time.times.tr.truncate.uc.ucfirst.umask.undef.unless.unlink.unpack.unshift.untie.until.use.utime.values.vec.wait.waitpid.wantarray.warn.when.while.write.x|0.xor.y|0`.split(`.`),r=/[dualxmsipngr]{0,12}/,i={$pattern:/[\\w.]+/,keyword:n.join(` `)},a={className:`subst`,begin:`[$@]\\\\{`,end:`\\\\}`,keywords:i},o={begin:/->\\{/,end:/\\}/},s={scope:`attr`,match:/\\s+:\\s*\\w+(\\s*\\(.*?\\))?/},c={scope:`variable`,variants:[{begin:/\\$\\d/},{begin:t.concat(/[$%@](?!\")(\\^\\w\\b|#\\w+(::\\w+)*|\\{\\w+\\}|\\w+(::\\w*)*)/,`(?![A-Za-z])(?![@$%])`)},{begin:/[$%@](?!\")[^\\s\\w{=]|\\$=/,relevance:0}],contains:[s]},l={className:`number`,variants:[{match:/0?\\.[0-9][0-9_]+\\b/},{match:/\\bv?(0|[1-9][0-9_]*(\\.[0-9_]+)?|[1-9][0-9_]*)\\b/},{match:/\\b0[0-7][0-7_]*\\b/},{match:/\\b0x[0-9a-fA-F][0-9a-fA-F_]*\\b/},{match:/\\b0b[0-1][0-1_]*\\b/}],relevance:0},u=[e.BACKSLASH_ESCAPE,a,c],d=[/!/,/\\//,/\\|/,/\\?/,/'/,/\"/,/#/],f=(e,n,i=`\\\\1`)=>{let a=i===`\\\\1`?i:t.concat(i,n);return t.concat(t.concat(`(?:`,e,`)`),n,/(?:\\\\.|[^\\\\\\/])*?/,a,/(?:\\\\.|[^\\\\\\/])*?/,i,r)},p=(e,n,i)=>t.concat(t.concat(`(?:`,e,`)`),n,/(?:\\\\.|[^\\\\\\/])*?/,i,r),m=[c,e.HASH_COMMENT_MODE,e.COMMENT(/^=\\w/,/=cut/,{endsWithParent:!0}),o,{className:`string`,contains:u,variants:[{begin:`q[qwxr]?\\\\s*\\\\(`,end:`\\\\)`,relevance:5},{begin:`q[qwxr]?\\\\s*\\\\[`,end:`\\\\]`,relevance:5},{begin:`q[qwxr]?\\\\s*\\\\{`,end:`\\\\}`,relevance:5},{begin:`q[qwxr]?\\\\s*\\\\|`,end:`\\\\|`,relevance:5},{begin:`q[qwxr]?\\\\s*<`,end:`>`,relevance:5},{begin:`qw\\\\s+q`,end:`q`,relevance:5},{begin:`'`,end:`'`,contains:[e.BACKSLASH_ESCAPE]},{begin:`\"`,end:`\"`},{begin:\"`\",end:\"`\",contains:[e.BACKSLASH_ESCAPE]},{begin:/\\{\\w+\\}/,relevance:0},{begin:`-?\\\\w+\\\\s*=>`,relevance:0}]},l,{begin:`(\\\\/\\\\/|`+e.RE_STARTERS_RE+`|\\\\b(split|return|print|reverse|grep)\\\\b)\\\\s*`,keywords:`split return print reverse grep`,relevance:0,contains:[e.HASH_COMMENT_MODE,{className:`regexp`,variants:[{begin:f(`s|tr|y`,t.either(...d,{capture:!0}))},{begin:f(`s|tr|y`,`\\\\(`,`\\\\)`)},{begin:f(`s|tr|y`,`\\\\[`,`\\\\]`)},{begin:f(`s|tr|y`,`\\\\{`,`\\\\}`)}],relevance:2},{className:`regexp`,variants:[{begin:/(m|qr)\\/\\//,relevance:0},{begin:p(`(?:m|qr)?`,/\\//,/\\//)},{begin:p(`m|qr`,t.either(...d,{capture:!0}),/\\1/)},{begin:p(`m|qr`,/\\(/,/\\)/)},{begin:p(`m|qr`,/\\[/,/\\]/)},{begin:p(`m|qr`,/\\{/,/\\}/)}]}]},{className:`function`,beginKeywords:`sub method`,end:`(\\\\s*\\\\(.*?\\\\))?[;{]`,excludeEnd:!0,relevance:5,contains:[e.TITLE_MODE,s]},{className:`class`,beginKeywords:`class`,end:`[;{]`,excludeEnd:!0,relevance:5,contains:[e.TITLE_MODE,s,l]},{begin:`-\\\\w\\\\b`,relevance:0},{begin:`^__DATA__$`,end:`^__END__$`,subLanguage:`mojolicious`,contains:[{begin:`^@@.*`,end:`$`,className:`comment`}]}];return a.contains=m,o.contains=m,{name:`Perl`,aliases:[`pl`,`pm`],keywords:i,contains:m}}t.exports=n})),A=o(((e,t)=>{function n(e){let t={className:`built_in`,begin:`\\\\b(AV|CA|CF|CG|CI|CL|CM|CN|CT|MK|MP|MTK|MTL|NS|SCN|SK|UI|WK|XC)\\\\w+`},n=/[a-zA-Z@][a-zA-Z0-9_]*/,r=[`int`,`float`,`char`,`unsigned`,`signed`,`short`,`long`,`double`,`wchar_t`,`unichar`,`void`,`bool`,`BOOL`,`id|0`,`_Bool`],i=`while.export.sizeof.typedef.const.struct.for.union.volatile.static.mutable.if.do.return.goto.enum.else.break.extern.asm.case.default.register.explicit.typename.switch.continue.inline.readonly.assign.readwrite.self.@synchronized.id.typeof.nonatomic.IBOutlet.IBAction.strong.weak.copy.in.out.inout.bycopy.byref.oneway.__strong.__weak.__block.__autoreleasing.@private.@protected.@public.@try.@property.@end.@throw.@catch.@finally.@autoreleasepool.@synthesize.@dynamic.@selector.@optional.@required.@encode.@package.@import.@defs.@compatibility_alias.__bridge.__bridge_transfer.__bridge_retained.__bridge_retain.__covariant.__contravariant.__kindof._Nonnull._Nullable._Null_unspecified.__FUNCTION__.__PRETTY_FUNCTION__.__attribute__.getter.setter.retain.unsafe_unretained.nonnull.nullable.null_unspecified.null_resettable.class.instancetype.NS_DESIGNATED_INITIALIZER.NS_UNAVAILABLE.NS_REQUIRES_SUPER.NS_RETURNS_INNER_POINTER.NS_INLINE.NS_AVAILABLE.NS_DEPRECATED.NS_ENUM.NS_OPTIONS.NS_SWIFT_UNAVAILABLE.NS_ASSUME_NONNULL_BEGIN.NS_ASSUME_NONNULL_END.NS_REFINED_FOR_SWIFT.NS_SWIFT_NAME.NS_SWIFT_NOTHROW.NS_DURING.NS_HANDLER.NS_ENDHANDLER.NS_VALUERETURN.NS_VOIDRETURN`.split(`.`),a=[`false`,`true`,`FALSE`,`TRUE`,`nil`,`YES`,`NO`,`NULL`],o=[`dispatch_once_t`,`dispatch_queue_t`,`dispatch_sync`,`dispatch_async`,`dispatch_once`],s={\"variable.language\":[`this`,`super`],$pattern:n,keyword:i,literal:a,built_in:o,type:r},c={$pattern:n,keyword:[`@interface`,`@class`,`@protocol`,`@implementation`]};return{name:`Objective-C`,aliases:[`mm`,`objc`,`obj-c`,`obj-c++`,`objective-c++`],keywords:s,illegal:`</`,contains:[t,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.C_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,{className:`string`,variants:[{begin:`@\"`,end:`\"`,illegal:`\\\\n`,contains:[e.BACKSLASH_ESCAPE]}]},{className:`meta`,begin:/#\\s*[a-z]+\\b/,end:/$/,keywords:{keyword:`if else elif endif define undef warning error line pragma ifdef ifndef include`},contains:[{begin:/\\\\\\n/,relevance:0},e.inherit(e.QUOTE_STRING_MODE,{className:`string`}),{className:`string`,begin:/<.*?>/,end:/$/,illegal:`\\\\n`},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:`class`,begin:`(`+c.keyword.join(`|`)+`)\\\\b`,end:/(\\{|$)/,excludeEnd:!0,keywords:c,contains:[e.UNDERSCORE_TITLE_MODE]},{begin:`\\\\.`+e.UNDERSCORE_IDENT_RE,relevance:0}]}}t.exports=n})),j=o(((e,t)=>{function n(e){let t=e.regex,n=/(?![A-Za-z0-9])(?![$])/,r=t.concat(/[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*/,n),i=t.concat(/(\\\\?[A-Z][a-z0-9_\\x7f-\\xff]+|\\\\?[A-Z]+(?=[A-Z][a-z0-9_\\x7f-\\xff])){1,}/,n),a=t.concat(/[A-Z]+/,n),o={scope:`variable`,match:`\\\\$+`+r},s={scope:`meta`,variants:[{begin:/<\\?php/,relevance:10},{begin:/<\\?=/},{begin:/<\\?/,relevance:.1},{begin:/\\?>/}]},c={scope:`subst`,variants:[{begin:/\\$\\w+/},{begin:/\\{\\$/,end:/\\}/}]},l=e.inherit(e.APOS_STRING_MODE,{illegal:null}),u=e.inherit(e.QUOTE_STRING_MODE,{illegal:null,contains:e.QUOTE_STRING_MODE.contains.concat(c)}),d={begin:/<<<[ \\t]*(?:(\\w+)|\"(\\w+)\")\\n/,end:/[ \\t]*(\\w+)\\b/,contains:e.QUOTE_STRING_MODE.contains.concat(c),\"on:begin\":(e,t)=>{t.data._beginMatch=e[1]||e[2]},\"on:end\":(e,t)=>{t.data._beginMatch!==e[1]&&t.ignoreMatch()}},f=e.END_SAME_AS_BEGIN({begin:/<<<[ \\t]*'(\\w+)'\\n/,end:/[ \\t]*(\\w+)\\b/}),p=`[ \t\n]`,m={scope:`string`,variants:[u,l,d,f]},h={scope:`number`,variants:[{begin:`\\\\b0[bB][01]+(?:_[01]+)*\\\\b`},{begin:`\\\\b0[oO][0-7]+(?:_[0-7]+)*\\\\b`},{begin:`\\\\b0[xX][\\\\da-fA-F]+(?:_[\\\\da-fA-F]+)*\\\\b`},{begin:`(?:\\\\b\\\\d+(?:_\\\\d+)*(\\\\.(?:\\\\d+(?:_\\\\d+)*))?|\\\\B\\\\.\\\\d+)(?:[eE][+-]?\\\\d+)?`}],relevance:0},g=[`false`,`null`,`true`],_=`__CLASS__.__DIR__.__FILE__.__FUNCTION__.__COMPILER_HALT_OFFSET__.__LINE__.__METHOD__.__NAMESPACE__.__TRAIT__.die.echo.exit.include.include_once.print.require.require_once.array.abstract.and.as.binary.bool.boolean.break.callable.case.catch.class.clone.const.continue.declare.default.do.double.else.elseif.empty.enddeclare.endfor.endforeach.endif.endswitch.endwhile.enum.eval.extends.final.finally.float.for.foreach.from.global.goto.if.implements.instanceof.insteadof.int.integer.interface.isset.iterable.list.match|0.mixed.new.never.object.or.private.protected.public.readonly.real.return.string.switch.throw.trait.try.unset.use.var.void.while.xor.yield`.split(`.`),v=`Error|0.AppendIterator.ArgumentCountError.ArithmeticError.ArrayIterator.ArrayObject.AssertionError.BadFunctionCallException.BadMethodCallException.CachingIterator.CallbackFilterIterator.CompileError.Countable.DirectoryIterator.DivisionByZeroError.DomainException.EmptyIterator.ErrorException.Exception.FilesystemIterator.FilterIterator.GlobIterator.InfiniteIterator.InvalidArgumentException.IteratorIterator.LengthException.LimitIterator.LogicException.MultipleIterator.NoRewindIterator.OutOfBoundsException.OutOfRangeException.OuterIterator.OverflowException.ParentIterator.ParseError.RangeException.RecursiveArrayIterator.RecursiveCachingIterator.RecursiveCallbackFilterIterator.RecursiveDirectoryIterator.RecursiveFilterIterator.RecursiveIterator.RecursiveIteratorIterator.RecursiveRegexIterator.RecursiveTreeIterator.RegexIterator.RuntimeException.SeekableIterator.SplDoublyLinkedList.SplFileInfo.SplFileObject.SplFixedArray.SplHeap.SplMaxHeap.SplMinHeap.SplObjectStorage.SplObserver.SplPriorityQueue.SplQueue.SplStack.SplSubject.SplTempFileObject.TypeError.UnderflowException.UnexpectedValueException.UnhandledMatchError.ArrayAccess.BackedEnum.Closure.Fiber.Generator.Iterator.IteratorAggregate.Serializable.Stringable.Throwable.Traversable.UnitEnum.WeakReference.WeakMap.Directory.__PHP_Incomplete_Class.parent.php_user_filter.self.static.stdClass`.split(`.`),y={keyword:_,literal:(e=>{let t=[];return e.forEach(e=>{t.push(e),e.toLowerCase()===e?t.push(e.toUpperCase()):t.push(e.toLowerCase())}),t})(g),built_in:v},b=e=>e.map(e=>e.replace(/\\|\\d+$/,``)),x={variants:[{match:[/new/,t.concat(p,`+`),t.concat(`(?!`,b(v).join(`\\\\b|`),`\\\\b)`),i],scope:{1:`keyword`,4:`title.class`}}]},S=t.concat(r,`\\\\b(?!\\\\()`),C={variants:[{match:[t.concat(/::/,t.lookahead(/(?!class\\b)/)),S],scope:{2:`variable.constant`}},{match:[/::/,/class/],scope:{2:`variable.language`}},{match:[i,t.concat(/::/,t.lookahead(/(?!class\\b)/)),S],scope:{1:`title.class`,3:`variable.constant`}},{match:[i,t.concat(`::`,t.lookahead(/(?!class\\b)/))],scope:{1:`title.class`}},{match:[i,/::/,/class/],scope:{1:`title.class`,3:`variable.language`}}]},w={scope:`attr`,match:t.concat(r,t.lookahead(`:`),t.lookahead(/(?!::)/))},T={relevance:0,begin:/\\(/,end:/\\)/,keywords:y,contains:[w,o,C,e.C_BLOCK_COMMENT_MODE,m,h,x]},E={relevance:0,match:[/\\b/,t.concat(`(?!fn\\\\b|function\\\\b|`,b(_).join(`\\\\b|`),`|`,b(v).join(`\\\\b|`),`\\\\b)`),r,t.concat(p,`*`),t.lookahead(/(?=\\()/)],scope:{3:`title.function.invoke`},contains:[T]};T.contains.push(E);let D=[w,C,e.C_BLOCK_COMMENT_MODE,m,h,x],O={begin:t.concat(/#\\[\\s*\\\\?/,t.either(i,a)),beginScope:`meta`,end:/]/,endScope:`meta`,keywords:{literal:g,keyword:[`new`,`array`]},contains:[{begin:/\\[/,end:/]/,keywords:{literal:g,keyword:[`new`,`array`]},contains:[`self`,...D]},...D,{scope:`meta`,variants:[{match:i},{match:a}]}]};return{case_insensitive:!1,keywords:y,contains:[O,e.HASH_COMMENT_MODE,e.COMMENT(`//`,`$`),e.COMMENT(`/\\\\*`,`\\\\*/`,{contains:[{scope:`doctag`,match:`@[A-Za-z]+`}]}),{match:/__halt_compiler\\(\\);/,keywords:`__halt_compiler`,starts:{scope:`comment`,end:e.MATCH_NOTHING_RE,contains:[{match:/\\?>/,scope:`meta`,endsParent:!0}]}},s,{scope:`variable.language`,match:/\\$this\\b/},o,E,C,{match:[/const/,/\\s/,r],scope:{1:`keyword`,3:`variable.constant`}},x,{scope:`function`,relevance:0,beginKeywords:`fn function`,end:/[;{]/,excludeEnd:!0,illegal:`[$%\\\\[]`,contains:[{beginKeywords:`use`},e.UNDERSCORE_TITLE_MODE,{begin:`=>`,endsParent:!0},{scope:`params`,begin:`\\\\(`,end:`\\\\)`,excludeBegin:!0,excludeEnd:!0,keywords:y,contains:[`self`,O,o,C,e.C_BLOCK_COMMENT_MODE,m,h]}]},{scope:`class`,variants:[{beginKeywords:`enum`,illegal:/[($\"]/},{beginKeywords:`class interface trait`,illegal:/[:($\"]/}],relevance:0,end:/\\{/,excludeEnd:!0,contains:[{beginKeywords:`extends implements`},e.UNDERSCORE_TITLE_MODE]},{beginKeywords:`namespace`,relevance:0,end:`;`,illegal:/[.']/,contains:[e.inherit(e.UNDERSCORE_TITLE_MODE,{scope:`title.class`})]},{beginKeywords:`use`,relevance:0,end:`;`,contains:[{match:/\\b(as|const|function)\\b/,scope:`keyword`},e.UNDERSCORE_TITLE_MODE]},m,h]}}t.exports=n})),M=o(((e,t)=>{function n(e){return{name:`PHP template`,subLanguage:`xml`,contains:[{begin:/<\\?(php|=)?/,end:/\\?>/,subLanguage:`php`,contains:[{begin:`/\\\\*`,end:`\\\\*/`,skip:!0},{begin:`b\"`,end:`\"`,skip:!0},{begin:`b'`,end:`'`,skip:!0},e.inherit(e.APOS_STRING_MODE,{illegal:null,className:null,contains:null,skip:!0}),e.inherit(e.QUOTE_STRING_MODE,{illegal:null,className:null,contains:null,skip:!0})]}]}}t.exports=n})),N=o(((e,t)=>{function n(e){return{name:`Plain text`,aliases:[`text`,`txt`],disableAutodetect:!0}}t.exports=n})),P=o(((e,t)=>{function n(e){let t=e.regex,n=/[\\p{XID_Start}_]\\p{XID_Continue}*/u,r=`and.as.assert.async.await.break.case.class.continue.def.del.elif.else.except.finally.for.from.global.if.import.in.is.lambda.match.nonlocal|10.not.or.pass.raise.return.try.while.with.yield`.split(`.`),i={$pattern:/[A-Za-z]\\w+|__\\w+__/,keyword:r,built_in:`__import__.abs.all.any.ascii.bin.bool.breakpoint.bytearray.bytes.callable.chr.classmethod.compile.complex.delattr.dict.dir.divmod.enumerate.eval.exec.filter.float.format.frozenset.getattr.globals.hasattr.hash.help.hex.id.input.int.isinstance.issubclass.iter.len.list.locals.map.max.memoryview.min.next.object.oct.open.ord.pow.print.property.range.repr.reversed.round.set.setattr.slice.sorted.staticmethod.str.sum.super.tuple.type.vars.zip`.split(`.`),literal:[`__debug__`,`Ellipsis`,`False`,`None`,`NotImplemented`,`True`],type:[`Any`,`Callable`,`Coroutine`,`Dict`,`List`,`Literal`,`Generic`,`Optional`,`Sequence`,`Set`,`Tuple`,`Type`,`Union`]},a={className:`meta`,begin:/^(>>>|\\.\\.\\.) /},o={className:`subst`,begin:/\\{/,end:/\\}/,keywords:i,illegal:/#/},s={begin:/\\{\\{/,relevance:0},c={className:`string`,contains:[e.BACKSLASH_ESCAPE],variants:[{begin:/([uU]|[bB]|[rR]|[bB][rR]|[rR][bB])?'''/,end:/'''/,contains:[e.BACKSLASH_ESCAPE,a],relevance:10},{begin:/([uU]|[bB]|[rR]|[bB][rR]|[rR][bB])?\"\"\"/,end:/\"\"\"/,contains:[e.BACKSLASH_ESCAPE,a],relevance:10},{begin:/([fF][rR]|[rR][fF]|[fF])'''/,end:/'''/,contains:[e.BACKSLASH_ESCAPE,a,s,o]},{begin:/([fF][rR]|[rR][fF]|[fF])\"\"\"/,end:/\"\"\"/,contains:[e.BACKSLASH_ESCAPE,a,s,o]},{begin:/([uU]|[rR])'/,end:/'/,relevance:10},{begin:/([uU]|[rR])\"/,end:/\"/,relevance:10},{begin:/([bB]|[bB][rR]|[rR][bB])'/,end:/'/},{begin:/([bB]|[bB][rR]|[rR][bB])\"/,end:/\"/},{begin:/([fF][rR]|[rR][fF]|[fF])'/,end:/'/,contains:[e.BACKSLASH_ESCAPE,s,o]},{begin:/([fF][rR]|[rR][fF]|[fF])\"/,end:/\"/,contains:[e.BACKSLASH_ESCAPE,s,o]},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},l=`[0-9](_?[0-9])*`,u=`(\\\\b(${l}))?\\\\.(${l})|\\\\b(${l})\\\\.`,d=`\\\\b|${r.join(`|`)}`,f={className:`number`,relevance:0,variants:[{begin:`(\\\\b(${l})|(${u}))[eE][+-]?(${l})[jJ]?(?=${d})`},{begin:`(${u})[jJ]?`},{begin:`\\\\b([1-9](_?[0-9])*|0+(_?0)*)[lLjJ]?(?=${d})`},{begin:`\\\\b0[bB](_?[01])+[lL]?(?=${d})`},{begin:`\\\\b0[oO](_?[0-7])+[lL]?(?=${d})`},{begin:`\\\\b0[xX](_?[0-9a-fA-F])+[lL]?(?=${d})`},{begin:`\\\\b(${l})[jJ](?=${d})`}]},p={className:`comment`,begin:t.lookahead(/# type:/),end:/$/,keywords:i,contains:[{begin:/# type:/},{begin:/#/,end:/\\b\\B/,endsWithParent:!0}]},m={className:`params`,variants:[{className:``,begin:/\\(\\s*\\)/,skip:!0},{begin:/\\(/,end:/\\)/,excludeBegin:!0,excludeEnd:!0,keywords:i,contains:[`self`,a,f,c,e.HASH_COMMENT_MODE]}]};return o.contains=[c,f,a],{name:`Python`,aliases:[`py`,`gyp`,`ipython`],unicodeRegex:!0,keywords:i,illegal:/(<\\/|\\?)|=>/,contains:[a,f,{scope:`variable.language`,match:/\\bself\\b/},{beginKeywords:`if`,relevance:0},{match:/\\bor\\b/,scope:`keyword`},c,p,e.HASH_COMMENT_MODE,{match:[/\\bdef/,/\\s+/,n],scope:{1:`keyword`,3:`title.function`},contains:[m]},{variants:[{match:[/\\bclass/,/\\s+/,n,/\\s*/,/\\(\\s*/,n,/\\s*\\)/]},{match:[/\\bclass/,/\\s+/,n]}],scope:{1:`keyword`,3:`title.class`,6:`title.class.inherited`}},{className:`meta`,begin:/^[\\t ]*@/,end:/(?=#)|$/,contains:[f,m,c]}]}}t.exports=n})),F=o(((e,t)=>{function n(e){return{aliases:[`pycon`],contains:[{className:`meta.prompt`,starts:{end:/ |$/,starts:{end:`$`,subLanguage:`python`}},variants:[{begin:/^>>>(?=[ ]|$)/},{begin:/^\\.\\.\\.(?=[ ]|$)/}]}]}}t.exports=n})),I=o(((e,t)=>{function n(e){let t=e.regex,n=/(?:(?:[a-zA-Z]|\\.[._a-zA-Z])[._a-zA-Z0-9]*)|\\.(?!\\d)/,r=t.either(/0[xX][0-9a-fA-F]+\\.[0-9a-fA-F]*[pP][+-]?\\d+i?/,/0[xX][0-9a-fA-F]+(?:[pP][+-]?\\d+)?[Li]?/,/(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:[eE][+-]?\\d+)?[Li]?/),i=/[=!<>:]=|\\|\\||&&|:::?|<-|<<-|->>|->|\\|>|[-+*\\/?!$&|:<=>@^~]|\\*\\*/,a=t.either(/[()]/,/[{}]/,/\\[\\[/,/[[\\]]/,/\\\\/,/,/);return{name:`R`,keywords:{$pattern:n,keyword:`function if in break next repeat else for while`,literal:`NULL NA TRUE FALSE Inf NaN NA_integer_|10 NA_real_|10 NA_character_|10 NA_complex_|10`,built_in:`LETTERS letters month.abb month.name pi T F abs acos acosh all any anyNA Arg as.call as.character as.complex as.double as.environment as.integer as.logical as.null.default as.numeric as.raw asin asinh atan atanh attr attributes baseenv browser c call ceiling class Conj cos cosh cospi cummax cummin cumprod cumsum digamma dim dimnames emptyenv exp expression floor forceAndCall gamma gc.time globalenv Im interactive invisible is.array is.atomic is.call is.character is.complex is.double is.environment is.expression is.finite is.function is.infinite is.integer is.language is.list is.logical is.matrix is.na is.name is.nan is.null is.numeric is.object is.pairlist is.raw is.recursive is.single is.symbol lazyLoadDBfetch length lgamma list log max min missing Mod names nargs nzchar oldClass on.exit pos.to.env proc.time prod quote range Re rep retracemem return round seq_along seq_len seq.int sign signif sin sinh sinpi sqrt standardGeneric substitute sum switch tan tanh tanpi tracemem trigamma trunc unclass untracemem UseMethod xtfrm`},contains:[e.COMMENT(/#'/,/$/,{contains:[{scope:`doctag`,match:/@examples/,starts:{end:t.lookahead(t.either(/\\n^#'\\s*(?=@[a-zA-Z]+)/,/\\n^(?!#')/)),endsParent:!0}},{scope:`doctag`,begin:`@param`,end:/$/,contains:[{scope:`variable`,variants:[{match:n},{match:/`(?:\\\\.|[^`\\\\])+`/}],endsParent:!0}]},{scope:`doctag`,match:/@[a-zA-Z]+/},{scope:`keyword`,match:/\\\\[a-zA-Z]+/}]}),e.HASH_COMMENT_MODE,{scope:`string`,contains:[e.BACKSLASH_ESCAPE],variants:[e.END_SAME_AS_BEGIN({begin:/[rR]\"(-*)\\(/,end:/\\)(-*)\"/}),e.END_SAME_AS_BEGIN({begin:/[rR]\"(-*)\\{/,end:/\\}(-*)\"/}),e.END_SAME_AS_BEGIN({begin:/[rR]\"(-*)\\[/,end:/\\](-*)\"/}),e.END_SAME_AS_BEGIN({begin:/[rR]'(-*)\\(/,end:/\\)(-*)'/}),e.END_SAME_AS_BEGIN({begin:/[rR]'(-*)\\{/,end:/\\}(-*)'/}),e.END_SAME_AS_BEGIN({begin:/[rR]'(-*)\\[/,end:/\\](-*)'/}),{begin:`\"`,end:`\"`,relevance:0},{begin:`'`,end:`'`,relevance:0}]},{relevance:0,variants:[{scope:{1:`operator`,2:`number`},match:[i,r]},{scope:{1:`operator`,2:`number`},match:[/%[^%]*%/,r]},{scope:{1:`punctuation`,2:`number`},match:[a,r]},{scope:{2:`number`},match:[/[^a-zA-Z0-9._]|^/,r]}]},{scope:{3:`operator`},match:[n,/\\s+/,/<-/,/\\s+/]},{scope:`operator`,relevance:0,variants:[{match:i},{match:/%[^%]*%/}]},{scope:`punctuation`,relevance:0,match:a},{begin:\"`\",end:\"`\",contains:[{begin:/\\\\./}]}]}}t.exports=n})),L=o(((e,t)=>{function n(e){let t=e.regex,n=/(r#)?/,r=t.concat(n,e.UNDERSCORE_IDENT_RE),i=t.concat(n,e.IDENT_RE),a={className:`title.function.invoke`,relevance:0,begin:t.concat(/\\b/,/(?!let|for|while|if|else|match\\b)/,i,t.lookahead(/\\s*\\(/))},o=`([ui](8|16|32|64|128|size)|f(32|64))?`,s=`abstract.as.async.await.become.box.break.const.continue.crate.do.dyn.else.enum.extern.false.final.fn.for.if.impl.in.let.loop.macro.match.mod.move.mut.override.priv.pub.ref.return.self.Self.static.struct.super.trait.true.try.type.typeof.union.unsafe.unsized.use.virtual.where.while.yield`.split(`.`),c=[`true`,`false`,`Some`,`None`,`Ok`,`Err`],l=`drop .Copy.Send.Sized.Sync.Drop.Fn.FnMut.FnOnce.ToOwned.Clone.Debug.PartialEq.PartialOrd.Eq.Ord.AsRef.AsMut.Into.From.Default.Iterator.Extend.IntoIterator.DoubleEndedIterator.ExactSizeIterator.SliceConcatExt.ToString.assert!.assert_eq!.bitflags!.bytes!.cfg!.col!.concat!.concat_idents!.debug_assert!.debug_assert_eq!.env!.eprintln!.panic!.file!.format!.format_args!.include_bytes!.include_str!.line!.local_data_key!.module_path!.option_env!.print!.println!.select!.stringify!.try!.unimplemented!.unreachable!.vec!.write!.writeln!.macro_rules!.assert_ne!.debug_assert_ne!`.split(`.`),u=[`i8`,`i16`,`i32`,`i64`,`i128`,`isize`,`u8`,`u16`,`u32`,`u64`,`u128`,`usize`,`f32`,`f64`,`str`,`char`,`bool`,`Box`,`Option`,`Result`,`String`,`Vec`];return{name:`Rust`,aliases:[`rs`],keywords:{$pattern:e.IDENT_RE+`!?`,type:u,keyword:s,literal:c,built_in:l},illegal:`</`,contains:[e.C_LINE_COMMENT_MODE,e.COMMENT(`/\\\\*`,`\\\\*/`,{contains:[`self`]}),e.inherit(e.QUOTE_STRING_MODE,{begin:/b?\"/,illegal:null}),{className:`symbol`,begin:/'[a-zA-Z_][a-zA-Z0-9_]*(?!')/},{scope:`string`,variants:[{begin:/b?r(#*)\"(.|\\n)*?\"\\1(?!#)/},{begin:/b?'/,end:/'/,contains:[{scope:`char.escape`,match:/\\\\('|\\w|x\\w{2}|u\\w{4}|U\\w{8})/}]}]},{className:`number`,variants:[{begin:`\\\\b0b([01_]+)`+o},{begin:`\\\\b0o([0-7_]+)`+o},{begin:`\\\\b0x([A-Fa-f0-9_]+)`+o},{begin:`\\\\b(\\\\d[\\\\d_]*(\\\\.[0-9_]+)?([eE][+-]?[0-9_]+)?)`+o}],relevance:0},{begin:[/fn/,/\\s+/,r],className:{1:`keyword`,3:`title.function`}},{className:`meta`,begin:`#!?\\\\[`,end:`\\\\]`,contains:[{className:`string`,begin:/\"/,end:/\"/,contains:[e.BACKSLASH_ESCAPE]}]},{begin:[/let/,/\\s+/,/(?:mut\\s+)?/,r],className:{1:`keyword`,3:`keyword`,4:`variable`}},{begin:[/for/,/\\s+/,r,/\\s+/,/in/],className:{1:`keyword`,3:`variable`,5:`keyword`}},{begin:[/type/,/\\s+/,r],className:{1:`keyword`,3:`title.class`}},{begin:[/(?:trait|enum|struct|union|impl|for)/,/\\s+/,r],className:{1:`keyword`,3:`title.class`}},{begin:e.IDENT_RE+`::`,keywords:{keyword:`Self`,built_in:l,type:u}},{className:`punctuation`,begin:`->`},a]}}t.exports=n})),R=o(((e,t)=>{let n=e=>({IMPORTANT:{scope:`meta`,begin:`!important`},BLOCK_COMMENT:e.C_BLOCK_COMMENT_MODE,HEXCOLOR:{scope:`number`,begin:/#(([0-9a-fA-F]{3,4})|(([0-9a-fA-F]{2}){3,4}))\\b/},FUNCTION_DISPATCH:{className:`built_in`,begin:/[\\w-]+(?=\\()/},ATTRIBUTE_SELECTOR_MODE:{scope:`selector-attr`,begin:/\\[/,end:/\\]/,illegal:`$`,contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},CSS_NUMBER_MODE:{scope:`number`,begin:e.NUMBER_RE+`(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?`,relevance:0},CSS_VARIABLE:{className:`attr`,begin:/--[A-Za-z_][A-Za-z0-9_-]*/}}),r=`a.abbr.address.article.aside.audio.b.blockquote.body.button.canvas.caption.cite.code.dd.del.details.dfn.div.dl.dt.em.fieldset.figcaption.figure.footer.form.h1.h2.h3.h4.h5.h6.header.hgroup.html.i.iframe.img.input.ins.kbd.label.legend.li.main.mark.menu.nav.object.ol.optgroup.option.p.picture.q.quote.samp.section.select.source.span.strong.summary.sup.table.tbody.td.textarea.tfoot.th.thead.time.tr.ul.var.video`.split(`.`),i=`defs.g.marker.mask.pattern.svg.switch.symbol.feBlend.feColorMatrix.feComponentTransfer.feComposite.feConvolveMatrix.feDiffuseLighting.feDisplacementMap.feFlood.feGaussianBlur.feImage.feMerge.feMorphology.feOffset.feSpecularLighting.feTile.feTurbulence.linearGradient.radialGradient.stop.circle.ellipse.image.line.path.polygon.polyline.rect.text.use.textPath.tspan.foreignObject.clipPath`.split(`.`),a=[...r,...i],o=`any-hover.any-pointer.aspect-ratio.color.color-gamut.color-index.device-aspect-ratio.device-height.device-width.display-mode.forced-colors.grid.height.hover.inverted-colors.monochrome.orientation.overflow-block.overflow-inline.pointer.prefers-color-scheme.prefers-contrast.prefers-reduced-motion.prefers-reduced-transparency.resolution.scan.scripting.update.width.min-width.max-width.min-height.max-height`.split(`.`).sort().reverse(),s=`active.any-link.blank.checked.current.default.defined.dir.disabled.drop.empty.enabled.first.first-child.first-of-type.fullscreen.future.focus.focus-visible.focus-within.has.host.host-context.hover.indeterminate.in-range.invalid.is.lang.last-child.last-of-type.left.link.local-link.not.nth-child.nth-col.nth-last-child.nth-last-col.nth-last-of-type.nth-of-type.only-child.only-of-type.optional.out-of-range.past.placeholder-shown.read-only.read-write.required.right.root.scope.target.target-within.user-invalid.valid.visited.where`.split(`.`).sort().reverse(),c=[`after`,`backdrop`,`before`,`cue`,`cue-region`,`first-letter`,`first-line`,`grammar-error`,`marker`,`part`,`placeholder`,`selection`,`slotted`,`spelling-error`].sort().reverse(),l=`accent-color.align-content.align-items.align-self.alignment-baseline.all.anchor-name.animation.animation-composition.animation-delay.animation-direction.animation-duration.animation-fill-mode.animation-iteration-count.animation-name.animation-play-state.animation-range.animation-range-end.animation-range-start.animation-timeline.animation-timing-function.appearance.aspect-ratio.backdrop-filter.backface-visibility.background.background-attachment.background-blend-mode.background-clip.background-color.background-image.background-origin.background-position.background-position-x.background-position-y.background-repeat.background-size.baseline-shift.block-size.border.border-block.border-block-color.border-block-end.border-block-end-color.border-block-end-style.border-block-end-width.border-block-start.border-block-start-color.border-block-start-style.border-block-start-width.border-block-style.border-block-width.border-bottom.border-bottom-color.border-bottom-left-radius.border-bottom-right-radius.border-bottom-style.border-bottom-width.border-collapse.border-color.border-end-end-radius.border-end-start-radius.border-image.border-image-outset.border-image-repeat.border-image-slice.border-image-source.border-image-width.border-inline.border-inline-color.border-inline-end.border-inline-end-color.border-inline-end-style.border-inline-end-width.border-inline-start.border-inline-start-color.border-inline-start-style.border-inline-start-width.border-inline-style.border-inline-width.border-left.border-left-color.border-left-style.border-left-width.border-radius.border-right.border-right-color.border-right-style.border-right-width.border-spacing.border-start-end-radius.border-start-start-radius.border-style.border-top.border-top-color.border-top-left-radius.border-top-right-radius.border-top-style.border-top-width.border-width.bottom.box-align.box-decoration-break.box-direction.box-flex.box-flex-group.box-lines.box-ordinal-group.box-orient.box-pack.box-shadow.box-sizing.break-after.break-before.break-inside.caption-side.caret-color.clear.clip.clip-path.clip-rule.color.color-interpolation.color-interpolation-filters.color-profile.color-rendering.color-scheme.column-count.column-fill.column-gap.column-rule.column-rule-color.column-rule-style.column-rule-width.column-span.column-width.columns.contain.contain-intrinsic-block-size.contain-intrinsic-height.contain-intrinsic-inline-size.contain-intrinsic-size.contain-intrinsic-width.container.container-name.container-type.content.content-visibility.counter-increment.counter-reset.counter-set.cue.cue-after.cue-before.cursor.cx.cy.direction.display.dominant-baseline.empty-cells.enable-background.field-sizing.fill.fill-opacity.fill-rule.filter.flex.flex-basis.flex-direction.flex-flow.flex-grow.flex-shrink.flex-wrap.float.flood-color.flood-opacity.flow.font.font-display.font-family.font-feature-settings.font-kerning.font-language-override.font-optical-sizing.font-palette.font-size.font-size-adjust.font-smooth.font-smoothing.font-stretch.font-style.font-synthesis.font-synthesis-position.font-synthesis-small-caps.font-synthesis-style.font-synthesis-weight.font-variant.font-variant-alternates.font-variant-caps.font-variant-east-asian.font-variant-emoji.font-variant-ligatures.font-variant-numeric.font-variant-position.font-variation-settings.font-weight.forced-color-adjust.gap.glyph-orientation-horizontal.glyph-orientation-vertical.grid.grid-area.grid-auto-columns.grid-auto-flow.grid-auto-rows.grid-column.grid-column-end.grid-column-start.grid-gap.grid-row.grid-row-end.grid-row-start.grid-template.grid-template-areas.grid-template-columns.grid-template-rows.hanging-punctuation.height.hyphenate-character.hyphenate-limit-chars.hyphens.icon.image-orientation.image-rendering.image-resolution.ime-mode.initial-letter.initial-letter-align.inline-size.inset.inset-area.inset-block.inset-block-end.inset-block-start.inset-inline.inset-inline-end.inset-inline-start.isolation.justify-content.justify-items.justify-self.kerning.left.letter-spacing.lighting-color.line-break.line-height.line-height-step.list-style.list-style-image.list-style-position.list-style-type.margin.margin-block.margin-block-end.margin-block-start.margin-bottom.margin-inline.margin-inline-end.margin-inline-start.margin-left.margin-right.margin-top.margin-trim.marker.marker-end.marker-mid.marker-start.marks.mask.mask-border.mask-border-mode.mask-border-outset.mask-border-repeat.mask-border-slice.mask-border-source.mask-border-width.mask-clip.mask-composite.mask-image.mask-mode.mask-origin.mask-position.mask-repeat.mask-size.mask-type.masonry-auto-flow.math-depth.math-shift.math-style.max-block-size.max-height.max-inline-size.max-width.min-block-size.min-height.min-inline-size.min-width.mix-blend-mode.nav-down.nav-index.nav-left.nav-right.nav-up.none.normal.object-fit.object-position.offset.offset-anchor.offset-distance.offset-path.offset-position.offset-rotate.opacity.order.orphans.outline.outline-color.outline-offset.outline-style.outline-width.overflow.overflow-anchor.overflow-block.overflow-clip-margin.overflow-inline.overflow-wrap.overflow-x.overflow-y.overlay.overscroll-behavior.overscroll-behavior-block.overscroll-behavior-inline.overscroll-behavior-x.overscroll-behavior-y.padding.padding-block.padding-block-end.padding-block-start.padding-bottom.padding-inline.padding-inline-end.padding-inline-start.padding-left.padding-right.padding-top.page.page-break-after.page-break-before.page-break-inside.paint-order.pause.pause-after.pause-before.perspective.perspective-origin.place-content.place-items.place-self.pointer-events.position.position-anchor.position-visibility.print-color-adjust.quotes.r.resize.rest.rest-after.rest-before.right.rotate.row-gap.ruby-align.ruby-position.scale.scroll-behavior.scroll-margin.scroll-margin-block.scroll-margin-block-end.scroll-margin-block-start.scroll-margin-bottom.scroll-margin-inline.scroll-margin-inline-end.scroll-margin-inline-start.scroll-margin-left.scroll-margin-right.scroll-margin-top.scroll-padding.scroll-padding-block.scroll-padding-block-end.scroll-padding-block-start.scroll-padding-bottom.scroll-padding-inline.scroll-padding-inline-end.scroll-padding-inline-start.scroll-padding-left.scroll-padding-right.scroll-padding-top.scroll-snap-align.scroll-snap-stop.scroll-snap-type.scroll-timeline.scroll-timeline-axis.scroll-timeline-name.scrollbar-color.scrollbar-gutter.scrollbar-width.shape-image-threshold.shape-margin.shape-outside.shape-rendering.speak.speak-as.src.stop-color.stop-opacity.stroke.stroke-dasharray.stroke-dashoffset.stroke-linecap.stroke-linejoin.stroke-miterlimit.stroke-opacity.stroke-width.tab-size.table-layout.text-align.text-align-all.text-align-last.text-anchor.text-combine-upright.text-decoration.text-decoration-color.text-decoration-line.text-decoration-skip.text-decoration-skip-ink.text-decoration-style.text-decoration-thickness.text-emphasis.text-emphasis-color.text-emphasis-position.text-emphasis-style.text-indent.text-justify.text-orientation.text-overflow.text-rendering.text-shadow.text-size-adjust.text-transform.text-underline-offset.text-underline-position.text-wrap.text-wrap-mode.text-wrap-style.timeline-scope.top.touch-action.transform.transform-box.transform-origin.transform-style.transition.transition-behavior.transition-delay.transition-duration.transition-property.transition-timing-function.translate.unicode-bidi.user-modify.user-select.vector-effect.vertical-align.view-timeline.view-timeline-axis.view-timeline-inset.view-timeline-name.view-transition-name.visibility.voice-balance.voice-duration.voice-family.voice-pitch.voice-range.voice-rate.voice-stress.voice-volume.white-space.white-space-collapse.widows.width.will-change.word-break.word-spacing.word-wrap.writing-mode.x.y.z-index.zoom`.split(`.`).sort().reverse();function u(e){let t=n(e),r=c,i=s,u=`@[a-z-]+`,d={className:`variable`,begin:`(\\\\$[a-zA-Z-][a-zA-Z0-9_-]*)\\\\b`,relevance:0};return{name:`SCSS`,case_insensitive:!0,illegal:`[=/|']`,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,t.CSS_NUMBER_MODE,{className:`selector-id`,begin:`#[A-Za-z0-9_-]+`,relevance:0},{className:`selector-class`,begin:`\\\\.[A-Za-z0-9_-]+`,relevance:0},t.ATTRIBUTE_SELECTOR_MODE,{className:`selector-tag`,begin:`\\\\b(`+a.join(`|`)+`)\\\\b`,relevance:0},{className:`selector-pseudo`,begin:`:(`+i.join(`|`)+`)`},{className:`selector-pseudo`,begin:`:(:)?(`+r.join(`|`)+`)`},d,{begin:/\\(/,end:/\\)/,contains:[t.CSS_NUMBER_MODE]},t.CSS_VARIABLE,{className:`attribute`,begin:`\\\\b(`+l.join(`|`)+`)\\\\b`},{begin:`\\\\b(whitespace|wait|w-resize|visible|vertical-text|vertical-ideographic|uppercase|upper-roman|upper-alpha|underline|transparent|top|thin|thick|text|text-top|text-bottom|tb-rl|table-header-group|table-footer-group|sw-resize|super|strict|static|square|solid|small-caps|separate|se-resize|scroll|s-resize|rtl|row-resize|ridge|right|repeat|repeat-y|repeat-x|relative|progress|pointer|overline|outside|outset|oblique|nowrap|not-allowed|normal|none|nw-resize|no-repeat|no-drop|newspaper|ne-resize|n-resize|move|middle|medium|ltr|lr-tb|lowercase|lower-roman|lower-alpha|loose|list-item|line|line-through|line-edge|lighter|left|keep-all|justify|italic|inter-word|inter-ideograph|inside|inset|inline|inline-block|inherit|inactive|ideograph-space|ideograph-parenthesis|ideograph-numeric|ideograph-alpha|horizontal|hidden|help|hand|groove|fixed|ellipsis|e-resize|double|dotted|distribute|distribute-space|distribute-letter|distribute-all-lines|disc|disabled|default|decimal|dashed|crosshair|collapse|col-resize|circle|char|center|capitalize|break-word|break-all|bottom|both|bolder|bold|block|bidi-override|below|baseline|auto|always|all-scroll|absolute|table|table-cell)\\\\b`},{begin:/:/,end:/[;}{]/,relevance:0,contains:[t.BLOCK_COMMENT,d,t.HEXCOLOR,t.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,t.IMPORTANT,t.FUNCTION_DISPATCH]},{begin:`@(page|font-face)`,keywords:{$pattern:u,keyword:`@page @font-face`}},{begin:`@`,end:`[{;]`,returnBegin:!0,keywords:{$pattern:/[a-z-]+/,keyword:`and or not only`,attribute:o.join(` `)},contains:[{begin:u,className:`keyword`},{begin:/[a-z-]+(?=:)/,className:`attribute`},d,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,t.HEXCOLOR,t.CSS_NUMBER_MODE]},t.FUNCTION_DISPATCH]}}t.exports=u})),z=o(((e,t)=>{function n(e){return{name:`Shell Session`,aliases:[`console`,`shellsession`],contains:[{className:`meta.prompt`,begin:/^\\s{0,3}[/~\\w\\d[\\]()@-]*[>%$#][ ]?/,starts:{end:/[^\\\\](?=\\s*$)/,subLanguage:`bash`}}]}}t.exports=n})),B=o(((e,t)=>{function n(e){let t=e.regex,n=e.COMMENT(`--`,`$`),r={scope:`string`,variants:[{begin:/'/,end:/'/,contains:[{match:/''/}]}]},i={begin:/\"/,end:/\"/,contains:[{match:/\"\"/}]},a=[`true`,`false`,`unknown`],o=[`double precision`,`large object`,`with timezone`,`without timezone`],s=`bigint.binary.blob.boolean.char.character.clob.date.dec.decfloat.decimal.float.int.integer.interval.nchar.nclob.national.numeric.real.row.smallint.time.timestamp.varchar.varying.varbinary`.split(`.`),c=[`add`,`asc`,`collation`,`desc`,`final`,`first`,`last`,`view`],l=`abs.acos.all.allocate.alter.and.any.are.array.array_agg.array_max_cardinality.as.asensitive.asin.asymmetric.at.atan.atomic.authorization.avg.begin.begin_frame.begin_partition.between.bigint.binary.blob.boolean.both.by.call.called.cardinality.cascaded.case.cast.ceil.ceiling.char.char_length.character.character_length.check.classifier.clob.close.coalesce.collate.collect.column.commit.condition.connect.constraint.contains.convert.copy.corr.corresponding.cos.cosh.count.covar_pop.covar_samp.create.cross.cube.cume_dist.current.current_catalog.current_date.current_default_transform_group.current_path.current_role.current_row.current_schema.current_time.current_timestamp.current_path.current_role.current_transform_group_for_type.current_user.cursor.cycle.date.day.deallocate.dec.decimal.decfloat.declare.default.define.delete.dense_rank.deref.describe.deterministic.disconnect.distinct.double.drop.dynamic.each.element.else.empty.end.end_frame.end_partition.end-exec.equals.escape.every.except.exec.execute.exists.exp.external.extract.false.fetch.filter.first_value.float.floor.for.foreign.frame_row.free.from.full.function.fusion.get.global.grant.group.grouping.groups.having.hold.hour.identity.in.indicator.initial.inner.inout.insensitive.insert.int.integer.intersect.intersection.interval.into.is.join.json_array.json_arrayagg.json_exists.json_object.json_objectagg.json_query.json_table.json_table_primitive.json_value.lag.language.large.last_value.lateral.lead.leading.left.like.like_regex.listagg.ln.local.localtime.localtimestamp.log.log10.lower.match.match_number.match_recognize.matches.max.member.merge.method.min.minute.mod.modifies.module.month.multiset.national.natural.nchar.nclob.new.no.none.normalize.not.nth_value.ntile.null.nullif.numeric.octet_length.occurrences_regex.of.offset.old.omit.on.one.only.open.or.order.out.outer.over.overlaps.overlay.parameter.partition.pattern.per.percent.percent_rank.percentile_cont.percentile_disc.period.portion.position.position_regex.power.precedes.precision.prepare.primary.procedure.ptf.range.rank.reads.real.recursive.ref.references.referencing.regr_avgx.regr_avgy.regr_count.regr_intercept.regr_r2.regr_slope.regr_sxx.regr_sxy.regr_syy.release.result.return.returns.revoke.right.rollback.rollup.row.row_number.rows.running.savepoint.scope.scroll.search.second.seek.select.sensitive.session_user.set.show.similar.sin.sinh.skip.smallint.some.specific.specifictype.sql.sqlexception.sqlstate.sqlwarning.sqrt.start.static.stddev_pop.stddev_samp.submultiset.subset.substring.substring_regex.succeeds.sum.symmetric.system.system_time.system_user.table.tablesample.tan.tanh.then.time.timestamp.timezone_hour.timezone_minute.to.trailing.translate.translate_regex.translation.treat.trigger.trim.trim_array.true.truncate.uescape.union.unique.unknown.unnest.update.upper.user.using.value.values.value_of.var_pop.var_samp.varbinary.varchar.varying.versioning.when.whenever.where.width_bucket.window.with.within.without.year`.split(`.`),u=`abs.acos.array_agg.asin.atan.avg.cast.ceil.ceiling.coalesce.corr.cos.cosh.count.covar_pop.covar_samp.cume_dist.dense_rank.deref.element.exp.extract.first_value.floor.json_array.json_arrayagg.json_exists.json_object.json_objectagg.json_query.json_table.json_table_primitive.json_value.lag.last_value.lead.listagg.ln.log.log10.lower.max.min.mod.nth_value.ntile.nullif.percent_rank.percentile_cont.percentile_disc.position.position_regex.power.rank.regr_avgx.regr_avgy.regr_count.regr_intercept.regr_r2.regr_slope.regr_sxx.regr_sxy.regr_syy.row_number.sin.sinh.sqrt.stddev_pop.stddev_samp.substring.substring_regex.sum.tan.tanh.translate.translate_regex.treat.trim.trim_array.unnest.upper.value_of.var_pop.var_samp.width_bucket`.split(`.`),d=[`current_catalog`,`current_date`,`current_default_transform_group`,`current_path`,`current_role`,`current_schema`,`current_transform_group_for_type`,`current_user`,`session_user`,`system_time`,`system_user`,`current_time`,`localtime`,`current_timestamp`,`localtimestamp`],f=[`create table`,`insert into`,`primary key`,`foreign key`,`not null`,`alter table`,`add constraint`,`grouping sets`,`on overflow`,`character set`,`respect nulls`,`ignore nulls`,`nulls first`,`nulls last`,`depth first`,`breadth first`],p=u,m=[...l,...c].filter(e=>!u.includes(e)),h={scope:`variable`,match:/@[a-z0-9][a-z0-9_]*/},g={scope:`operator`,match:/[-+*/=%^~]|&&?|\\|\\|?|!=?|<(?:=>?|<|>)?|>[>=]?/,relevance:0},_={match:t.concat(/\\b/,t.either(...p),/\\s*\\(/),relevance:0,keywords:{built_in:p}};function v(e){return t.concat(/\\b/,t.either(...e.map(e=>e.replace(/\\s+/,`\\\\s+`))),/\\b/)}let y={scope:`keyword`,match:v(f),relevance:0};function b(e,{exceptions:t,when:n}={}){let r=n;return t||=[],e.map(e=>e.match(/\\|\\d+$/)||t.includes(e)?e:r(e)?`${e}|0`:e)}return{name:`SQL`,case_insensitive:!0,illegal:/[{}]|<\\//,keywords:{$pattern:/\\b[\\w\\.]+/,keyword:b(m,{when:e=>e.length<3}),literal:a,type:s,built_in:d},contains:[{scope:`type`,match:v(o)},y,_,h,r,i,e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE,n,g]}}t.exports=n})),V=o(((e,t)=>{function n(e){return e?typeof e==`string`?e:e.source:null}function r(e){return i(`(?=`,e,`)`)}function i(...e){return e.map(e=>n(e)).join(``)}function a(e){let t=e[e.length-1];return typeof t==`object`&&t.constructor===Object?(e.splice(e.length-1,1),t):{}}function o(...e){return`(`+(a(e).capture?``:`?:`)+e.map(e=>n(e)).join(`|`)+`)`}let s=e=>i(/\\b/,e,/\\w$/.test(e)?/\\b/:/\\B/),c=[`Protocol`,`Type`].map(s),l=[`init`,`self`].map(s),u=[`Any`,`Self`],d=[`actor`,`any`,`associatedtype`,`async`,`await`,/as\\?/,/as!/,`as`,`borrowing`,`break`,`case`,`catch`,`class`,`consume`,`consuming`,`continue`,`convenience`,`copy`,`default`,`defer`,`deinit`,`didSet`,`distributed`,`do`,`dynamic`,`each`,`else`,`enum`,`extension`,`fallthrough`,/fileprivate\\(set\\)/,`fileprivate`,`final`,`for`,`func`,`get`,`guard`,`if`,`import`,`indirect`,`infix`,/init\\?/,/init!/,`inout`,/internal\\(set\\)/,`internal`,`in`,`is`,`isolated`,`nonisolated`,`lazy`,`let`,`macro`,`mutating`,`nonmutating`,/open\\(set\\)/,`open`,`operator`,`optional`,`override`,`package`,`postfix`,`precedencegroup`,`prefix`,/private\\(set\\)/,`private`,`protocol`,/public\\(set\\)/,`public`,`repeat`,`required`,`rethrows`,`return`,`set`,`some`,`static`,`struct`,`subscript`,`super`,`switch`,`throws`,`throw`,/try\\?/,/try!/,`try`,`typealias`,/unowned\\(safe\\)/,/unowned\\(unsafe\\)/,`unowned`,`var`,`weak`,`where`,`while`,`willSet`],f=[`false`,`nil`,`true`],p=[`assignment`,`associativity`,`higherThan`,`left`,`lowerThan`,`none`,`right`],m=[`#colorLiteral`,`#column`,`#dsohandle`,`#else`,`#elseif`,`#endif`,`#error`,`#file`,`#fileID`,`#fileLiteral`,`#filePath`,`#function`,`#if`,`#imageLiteral`,`#keyPath`,`#line`,`#selector`,`#sourceLocation`,`#warning`],h=`abs.all.any.assert.assertionFailure.debugPrint.dump.fatalError.getVaList.isKnownUniquelyReferenced.max.min.numericCast.pointwiseMax.pointwiseMin.precondition.preconditionFailure.print.readLine.repeatElement.sequence.stride.swap.swift_unboxFromSwiftValueWithType.transcode.type.unsafeBitCast.unsafeDowncast.withExtendedLifetime.withUnsafeMutablePointer.withUnsafePointer.withVaList.withoutActuallyEscaping.zip`.split(`.`),g=o(/[/=\\-+!*%<>&|^~?]/,/[\\u00A1-\\u00A7]/,/[\\u00A9\\u00AB]/,/[\\u00AC\\u00AE]/,/[\\u00B0\\u00B1]/,/[\\u00B6\\u00BB\\u00BF\\u00D7\\u00F7]/,/[\\u2016-\\u2017]/,/[\\u2020-\\u2027]/,/[\\u2030-\\u203E]/,/[\\u2041-\\u2053]/,/[\\u2055-\\u205E]/,/[\\u2190-\\u23FF]/,/[\\u2500-\\u2775]/,/[\\u2794-\\u2BFF]/,/[\\u2E00-\\u2E7F]/,/[\\u3001-\\u3003]/,/[\\u3008-\\u3020]/,/[\\u3030]/),_=o(g,/[\\u0300-\\u036F]/,/[\\u1DC0-\\u1DFF]/,/[\\u20D0-\\u20FF]/,/[\\uFE00-\\uFE0F]/,/[\\uFE20-\\uFE2F]/),v=i(g,_,`*`),y=o(/[a-zA-Z_]/,/[\\u00A8\\u00AA\\u00AD\\u00AF\\u00B2-\\u00B5\\u00B7-\\u00BA]/,/[\\u00BC-\\u00BE\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u00FF]/,/[\\u0100-\\u02FF\\u0370-\\u167F\\u1681-\\u180D\\u180F-\\u1DBF]/,/[\\u1E00-\\u1FFF]/,/[\\u200B-\\u200D\\u202A-\\u202E\\u203F-\\u2040\\u2054\\u2060-\\u206F]/,/[\\u2070-\\u20CF\\u2100-\\u218F\\u2460-\\u24FF\\u2776-\\u2793]/,/[\\u2C00-\\u2DFF\\u2E80-\\u2FFF]/,/[\\u3004-\\u3007\\u3021-\\u302F\\u3031-\\u303F\\u3040-\\uD7FF]/,/[\\uF900-\\uFD3D\\uFD40-\\uFDCF\\uFDF0-\\uFE1F\\uFE30-\\uFE44]/,/[\\uFE47-\\uFEFE\\uFF00-\\uFFFD]/),b=o(y,/\\d/,/[\\u0300-\\u036F\\u1DC0-\\u1DFF\\u20D0-\\u20FF\\uFE20-\\uFE2F]/),x=i(y,b,`*`),S=i(/[A-Z]/,b,`*`),C=[`attached`,`autoclosure`,i(/convention\\(/,o(`swift`,`block`,`c`),/\\)/),`discardableResult`,`dynamicCallable`,`dynamicMemberLookup`,`escaping`,`freestanding`,`frozen`,`GKInspectable`,`IBAction`,`IBDesignable`,`IBInspectable`,`IBOutlet`,`IBSegueAction`,`inlinable`,`main`,`nonobjc`,`NSApplicationMain`,`NSCopying`,`NSManaged`,i(/objc\\(/,x,/\\)/),`objc`,`objcMembers`,`propertyWrapper`,`requires_stored_property_inits`,`resultBuilder`,`Sendable`,`testable`,`UIApplicationMain`,`unchecked`,`unknown`,`usableFromInline`,`warn_unqualified_access`],w=[`iOS`,`iOSApplicationExtension`,`macOS`,`macOSApplicationExtension`,`macCatalyst`,`macCatalystApplicationExtension`,`watchOS`,`watchOSApplicationExtension`,`tvOS`,`tvOSApplicationExtension`,`swift`];function T(e){let t={match:/\\s+/,relevance:0},n=e.COMMENT(`/\\\\*`,`\\\\*/`,{contains:[`self`]}),a=[e.C_LINE_COMMENT_MODE,n],g={match:[/\\./,o(...c,...l)],className:{2:`keyword`}},y={match:i(/\\./,o(...d)),relevance:0},T=d.filter(e=>typeof e==`string`).concat([`_|0`]),E={variants:[{className:`keyword`,match:o(...d.filter(e=>typeof e!=`string`).concat(u).map(s),...l)}]},D={$pattern:o(/\\b\\w+/,/#\\w+/),keyword:T.concat(m),literal:f},O=[g,y,E],k=[{match:i(/\\./,o(...h)),relevance:0},{className:`built_in`,match:i(/\\b/,o(...h),/(?=\\()/)}],A={match:/->/,relevance:0},j=[A,{className:`operator`,relevance:0,variants:[{match:v},{match:`\\\\.(\\\\.|${_})+`}]}],M=`([0-9]_*)+`,N=`([0-9a-fA-F]_*)+`,P={className:`number`,relevance:0,variants:[{match:`\\\\b(${M})(\\\\.(${M}))?([eE][+-]?(${M}))?\\\\b`},{match:`\\\\b0x(${N})(\\\\.(${N}))?([pP][+-]?(${M}))?\\\\b`},{match:/\\b0o([0-7]_*)+\\b/},{match:/\\b0b([01]_*)+\\b/}]},F=(e=``)=>({className:`subst`,variants:[{match:i(/\\\\/,e,/[0\\\\tnr\"']/)},{match:i(/\\\\/,e,/u\\{[0-9a-fA-F]{1,8}\\}/)}]}),I=(e=``)=>({className:`subst`,match:i(/\\\\/,e,/[\\t ]*(?:[\\r\\n]|\\r\\n)/)}),L=(e=``)=>({className:`subst`,label:`interpol`,begin:i(/\\\\/,e,/\\(/),end:/\\)/}),R=(e=``)=>({begin:i(e,/\"\"\"/),end:i(/\"\"\"/,e),contains:[F(e),I(e),L(e)]}),z=(e=``)=>({begin:i(e,/\"/),end:i(/\"/,e),contains:[F(e),L(e)]}),B={className:`string`,variants:[R(),R(`#`),R(`##`),R(`###`),z(),z(`#`),z(`##`),z(`###`)]},V=[e.BACKSLASH_ESCAPE,{begin:/\\[/,end:/\\]/,relevance:0,contains:[e.BACKSLASH_ESCAPE]}],H={begin:/\\/[^\\s](?=[^/\\n]*\\/)/,end:/\\//,contains:V},U=e=>{let t=i(e,/\\//),n=i(/\\//,e);return{begin:t,end:n,contains:[...V,{scope:`comment`,begin:`#(?!.*${n})`,end:/$/}]}},W={scope:`regexp`,variants:[U(`###`),U(`##`),U(`#`),H]},G={match:i(/`/,x,/`/)},K=[G,{className:`variable`,match:/\\$\\d+/},{className:`variable`,match:`\\\\$${b}+`}],q=[{match:/(@|#(un)?)available/,scope:`keyword`,starts:{contains:[{begin:/\\(/,end:/\\)/,keywords:w,contains:[...j,P,B]}]}},{scope:`keyword`,match:i(/@/,o(...C),r(o(/\\(/,/\\s+/)))},{scope:`meta`,match:i(/@/,x)}],J={match:r(/\\b[A-Z]/),relevance:0,contains:[{className:`type`,match:i(/(AV|CA|CF|CG|CI|CL|CM|CN|CT|MK|MP|MTK|MTL|NS|SCN|SK|UI|WK|XC)/,b,`+`)},{className:`type`,match:S,relevance:0},{match:/[?!]+/,relevance:0},{match:/\\.\\.\\./,relevance:0},{match:i(/\\s+&\\s+/,r(S)),relevance:0}]},ee={begin:/</,end:/>/,keywords:D,contains:[...a,...O,...q,A,J]};J.contains.push(ee);let te={begin:/\\(/,end:/\\)/,relevance:0,keywords:D,contains:[`self`,{match:i(x,/\\s*:/),keywords:`_|0`,relevance:0},...a,W,...O,...k,...j,P,B,...K,...q,J]},Y={begin:/</,end:/>/,keywords:`repeat each`,contains:[...a,J]},ne={begin:/\\(/,end:/\\)/,keywords:D,contains:[{begin:o(r(i(x,/\\s*:/)),r(i(x,/\\s+/,x,/\\s*:/))),end:/:/,relevance:0,contains:[{className:`keyword`,match:/\\b_\\b/},{className:`params`,match:x}]},...a,...O,...j,P,B,...q,J,te],endsParent:!0,illegal:/[\"']/},re={match:[/(func|macro)/,/\\s+/,o(G.match,x,v)],className:{1:`keyword`,3:`title.function`},contains:[Y,ne,t],illegal:[/\\[/,/%/]},ie={match:[/\\b(?:subscript|init[?!]?)/,/\\s*(?=[<(])/],className:{1:`keyword`},contains:[Y,ne,t],illegal:/\\[|%/},ae={match:[/operator/,/\\s+/,v],className:{1:`keyword`,3:`title`}},oe={begin:[/precedencegroup/,/\\s+/,S],className:{1:`keyword`,3:`title`},contains:[J],keywords:[...p,...f],end:/}/},se={match:[/class\\b/,/\\s+/,/func\\b/,/\\s+/,/\\b[A-Za-z_][A-Za-z0-9_]*\\b/],scope:{1:`keyword`,3:`keyword`,5:`title.function`}},X={match:[/class\\b/,/\\s+/,/var\\b/],scope:{1:`keyword`,3:`keyword`}},ce={begin:[/(struct|protocol|class|extension|enum|actor)/,/\\s+/,x,/\\s*/],beginScope:{1:`keyword`,3:`title.class`},keywords:D,contains:[Y,...O,{begin:/:/,end:/\\{/,keywords:D,contains:[{scope:`title.class.inherited`,match:S},...O],relevance:0}]};for(let e of B.variants){let t=e.contains.find(e=>e.label===`interpol`);t.keywords=D;let n=[...O,...k,...j,P,B,...K];t.contains=[...n,{begin:/\\(/,end:/\\)/,contains:[`self`,...n]}]}return{name:`Swift`,keywords:D,contains:[...a,re,ie,se,X,ce,ae,oe,{beginKeywords:`import`,end:/$/,contains:[...a],relevance:0},W,...O,...k,...j,P,B,...K,...q,J,te]}}t.exports=T})),H=o(((e,t)=>{function n(e){let t=`true false yes no null`,n=`[\\\\w#;/?:@&=+$,.~*'()[\\\\]]+`,r={className:`attr`,variants:[{begin:/[\\w*@][\\w*@ :()\\./-]*:(?=[ \\t]|$)/},{begin:/\"[\\w*@][\\w*@ :()\\./-]*\":(?=[ \\t]|$)/},{begin:/'[\\w*@][\\w*@ :()\\./-]*':(?=[ \\t]|$)/}]},i={className:`template-variable`,variants:[{begin:/\\{\\{/,end:/\\}\\}/},{begin:/%\\{/,end:/\\}/}]},a={className:`string`,relevance:0,begin:/'/,end:/'/,contains:[{match:/''/,scope:`char.escape`,relevance:0}]},o={className:`string`,relevance:0,variants:[{begin:/\"/,end:/\"/},{begin:/\\S+/}],contains:[e.BACKSLASH_ESCAPE,i]},s=e.inherit(o,{variants:[{begin:/'/,end:/'/,contains:[{begin:/''/,relevance:0}]},{begin:/\"/,end:/\"/},{begin:/[^\\s,{}[\\]]+/}]}),c={className:`number`,begin:`\\\\b[0-9]{4}(-[0-9][0-9]){0,2}([Tt \\\\t][0-9][0-9]?(:[0-9][0-9]){2})?(\\\\.[0-9]*)?([ \\\\t])*(Z|[-+][0-9][0-9]?(:[0-9][0-9])?)?\\\\b`},l={end:`,`,endsWithParent:!0,excludeEnd:!0,keywords:t,relevance:0},u={begin:/\\{/,end:/\\}/,contains:[l],illegal:`\\\\n`,relevance:0},d={begin:`\\\\[`,end:`\\\\]`,contains:[l],illegal:`\\\\n`,relevance:0},f=[r,{className:`meta`,begin:`^---\\\\s*$`,relevance:10},{className:`string`,begin:`[\\\\|>]([1-9]?[+-])?[ ]*\\\\n( +)[^ ][^\\\\n]*\\\\n(\\\\2[^\\\\n]+\\\\n?)*`},{begin:`<%[%=-]?`,end:`[%-]?%>`,subLanguage:`ruby`,excludeBegin:!0,excludeEnd:!0,relevance:0},{className:`type`,begin:`!\\\\w+!`+n},{className:`type`,begin:`!<`+n+`>`},{className:`type`,begin:`!`+n},{className:`type`,begin:`!!`+n},{className:`meta`,begin:`&`+e.UNDERSCORE_IDENT_RE+`$`},{className:`meta`,begin:`\\\\*`+e.UNDERSCORE_IDENT_RE+`$`},{className:`bullet`,begin:`-(?=[ ]|$)`,relevance:0},e.HASH_COMMENT_MODE,{beginKeywords:t,keywords:{literal:t}},c,{className:`number`,begin:e.C_NUMBER_RE+`\\\\b`,relevance:0},u,d,a,o],p=[...f];return p.pop(),p.push(s),l.contains=p,{name:`YAML`,case_insensitive:!0,aliases:[`yml`],contains:f}}t.exports=n})),U=o(((e,t)=>{let n=`[A-Za-z$_][0-9A-Za-z$_]*`,r=`as.in.of.if.for.while.finally.var.new.function.do.return.void.else.break.catch.instanceof.with.throw.case.default.try.switch.continue.typeof.delete.let.yield.const.class.debugger.async.await.static.import.from.export.extends.using`.split(`.`),i=[`true`,`false`,`null`,`undefined`,`NaN`,`Infinity`],a=`Object.Function.Boolean.Symbol.Math.Date.Number.BigInt.String.RegExp.Array.Float32Array.Float64Array.Int8Array.Uint8Array.Uint8ClampedArray.Int16Array.Int32Array.Uint16Array.Uint32Array.BigInt64Array.BigUint64Array.Set.Map.WeakSet.WeakMap.ArrayBuffer.SharedArrayBuffer.Atomics.DataView.JSON.Promise.Generator.GeneratorFunction.AsyncFunction.Reflect.Proxy.Intl.WebAssembly`.split(`.`),o=[`Error`,`EvalError`,`InternalError`,`RangeError`,`ReferenceError`,`SyntaxError`,`TypeError`,`URIError`],s=[`setInterval`,`setTimeout`,`clearInterval`,`clearTimeout`,`require`,`exports`,`eval`,`isFinite`,`isNaN`,`parseFloat`,`parseInt`,`decodeURI`,`decodeURIComponent`,`encodeURI`,`encodeURIComponent`,`escape`,`unescape`],c=[`arguments`,`this`,`super`,`console`,`window`,`document`,`localStorage`,`sessionStorage`,`module`,`global`],l=[].concat(s,a,o);function u(e){let t=e.regex,u=(e,{after:t})=>{let n=`</`+e[0].slice(1);return e.input.indexOf(n,t)!==-1},d=n,f={begin:`<>`,end:`</>`},p=/<[A-Za-z0-9\\\\._:-]+\\s*\\/>/,m={begin:/<[A-Za-z0-9\\\\._:-]+/,end:/\\/[A-Za-z0-9\\\\._:-]+>|\\/>/,isTrulyOpeningTag:(e,t)=>{let n=e[0].length+e.index,r=e.input[n];if(r===`<`||r===`,`){t.ignoreMatch();return}r===`>`&&(u(e,{after:n})||t.ignoreMatch());let i,a=e.input.substring(n);if(i=a.match(/^\\s*=/)){t.ignoreMatch();return}if((i=a.match(/^\\s+extends\\s+/))&&i.index===0){t.ignoreMatch();return}}},h={$pattern:n,keyword:r,literal:i,built_in:l,\"variable.language\":c},g=`[0-9](_?[0-9])*`,_=`\\\\.(${g})`,v=`0|[1-9](_?[0-9])*|0[0-7]*[89][0-9]*`,y={className:`number`,variants:[{begin:`(\\\\b(${v})((${_})|\\\\.)?|(${_}))[eE][+-]?(${g})\\\\b`},{begin:`\\\\b(${v})\\\\b((${_})\\\\b|\\\\.)?|(${_})\\\\b`},{begin:`\\\\b(0|[1-9](_?[0-9])*)n\\\\b`},{begin:`\\\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*n?\\\\b`},{begin:`\\\\b0[bB][0-1](_?[0-1])*n?\\\\b`},{begin:`\\\\b0[oO][0-7](_?[0-7])*n?\\\\b`},{begin:`\\\\b0[0-7]+n?\\\\b`}],relevance:0},b={className:`subst`,begin:`\\\\$\\\\{`,end:`\\\\}`,keywords:h,contains:[]},x={begin:\".?html`\",end:``,starts:{end:\"`\",returnEnd:!1,contains:[e.BACKSLASH_ESCAPE,b],subLanguage:`xml`}},S={begin:\".?css`\",end:``,starts:{end:\"`\",returnEnd:!1,contains:[e.BACKSLASH_ESCAPE,b],subLanguage:`css`}},C={begin:\".?gql`\",end:``,starts:{end:\"`\",returnEnd:!1,contains:[e.BACKSLASH_ESCAPE,b],subLanguage:`graphql`}},w={className:`string`,begin:\"`\",end:\"`\",contains:[e.BACKSLASH_ESCAPE,b]},T={className:`comment`,variants:[e.COMMENT(/\\/\\*\\*(?!\\/)/,`\\\\*/`,{relevance:0,contains:[{begin:`(?=@[A-Za-z]+)`,relevance:0,contains:[{className:`doctag`,begin:`@[A-Za-z]+`},{className:`type`,begin:`\\\\{`,end:`\\\\}`,excludeEnd:!0,excludeBegin:!0,relevance:0},{className:`variable`,begin:d+`(?=\\\\s*(-)|$)`,endsParent:!0,relevance:0},{begin:/(?=[^\\n])\\s/,relevance:0}]}]}),e.C_BLOCK_COMMENT_MODE,e.C_LINE_COMMENT_MODE]},E=[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,x,S,C,w,{match:/\\$\\d+/},y];b.contains=E.concat({begin:/\\{/,end:/\\}/,keywords:h,contains:[`self`].concat(E)});let D=[].concat(T,b.contains),O=D.concat([{begin:/(\\s*)\\(/,end:/\\)/,keywords:h,contains:[`self`].concat(D)}]),k={className:`params`,begin:/(\\s*)\\(/,end:/\\)/,excludeBegin:!0,excludeEnd:!0,keywords:h,contains:O},A={variants:[{match:[/class/,/\\s+/,d,/\\s+/,/extends/,/\\s+/,t.concat(d,`(`,t.concat(/\\./,d),`)*`)],scope:{1:`keyword`,3:`title.class`,5:`keyword`,7:`title.class.inherited`}},{match:[/class/,/\\s+/,d],scope:{1:`keyword`,3:`title.class`}}]},j={relevance:0,match:t.either(/\\bJSON/,/\\b[A-Z][a-z]+([A-Z][a-z]*|\\d)*/,/\\b[A-Z]{2,}([A-Z][a-z]+|\\d)+([A-Z][a-z]*)*/,/\\b[A-Z]{2,}[a-z]+([A-Z][a-z]+|\\d)*([A-Z][a-z]*)*/),className:`title.class`,keywords:{_:[...a,...o]}},M={label:`use_strict`,className:`meta`,relevance:10,begin:/^\\s*['\"]use (strict|asm)['\"]/},N={variants:[{match:[/function/,/\\s+/,d,/(?=\\s*\\()/]},{match:[/function/,/\\s*(?=\\()/]}],className:{1:`keyword`,3:`title.function`},label:`func.def`,contains:[k],illegal:/%/},P={relevance:0,match:/\\b[A-Z][A-Z_0-9]+\\b/,className:`variable.constant`};function F(e){return t.concat(`(?!`,e.join(`|`),`)`)}let I={match:t.concat(/\\b/,F([...s,`super`,`import`].map(e=>`${e}\\\\s*\\\\(`)),d,t.lookahead(/\\s*\\(/)),className:`title.function`,relevance:0},L={begin:t.concat(/\\./,t.lookahead(t.concat(d,/(?![0-9A-Za-z$_(])/))),end:d,excludeBegin:!0,keywords:`prototype`,className:`property`,relevance:0},R={match:[/get|set/,/\\s+/,d,/(?=\\()/],className:{1:`keyword`,3:`title.function`},contains:[{begin:/\\(\\)/},k]},z=`(\\\\([^()]*(\\\\([^()]*(\\\\([^()]*\\\\)[^()]*)*\\\\)[^()]*)*\\\\)|`+e.UNDERSCORE_IDENT_RE+`)\\\\s*=>`,B={match:[/const|var|let/,/\\s+/,d,/\\s*/,/=\\s*/,/(async\\s*)?/,t.lookahead(z)],keywords:`async`,className:{1:`keyword`,3:`title.function`},contains:[k]};return{name:`JavaScript`,aliases:[`js`,`jsx`,`mjs`,`cjs`],keywords:h,exports:{PARAMS_CONTAINS:O,CLASS_REFERENCE:j},illegal:/#(?![$_A-z])/,contains:[e.SHEBANG({label:`shebang`,binary:`node`,relevance:5}),M,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,x,S,C,w,T,{match:/\\$\\d+/},y,j,{scope:`attr`,match:d+t.lookahead(`:`),relevance:0},B,{begin:`(`+e.RE_STARTERS_RE+`|\\\\b(case|return|throw)\\\\b)\\\\s*`,keywords:`return throw case`,relevance:0,contains:[T,e.REGEXP_MODE,{className:`function`,begin:z,returnBegin:!0,end:`\\\\s*=>`,contains:[{className:`params`,variants:[{begin:e.UNDERSCORE_IDENT_RE,relevance:0},{className:null,begin:/\\(\\s*\\)/,skip:!0},{begin:/(\\s*)\\(/,end:/\\)/,excludeBegin:!0,excludeEnd:!0,keywords:h,contains:O}]}]},{begin:/,/,relevance:0},{match:/\\s+/,relevance:0},{variants:[{begin:f.begin,end:f.end},{match:p},{begin:m.begin,\"on:begin\":m.isTrulyOpeningTag,end:m.end}],subLanguage:`xml`,contains:[{begin:m.begin,end:m.end,skip:!0,contains:[`self`]}]}]},N,{beginKeywords:`while if switch catch for`},{begin:`\\\\b(?!function)`+e.UNDERSCORE_IDENT_RE+`\\\\([^()]*(\\\\([^()]*(\\\\([^()]*\\\\)[^()]*)*\\\\)[^()]*)*\\\\)\\\\s*\\\\{`,returnBegin:!0,label:`func.def`,contains:[k,e.inherit(e.TITLE_MODE,{begin:d,className:`title.function`})]},{match:/\\.\\.\\./,relevance:0},L,{match:`\\\\$`+d,relevance:0},{match:[/\\bconstructor(?=\\s*\\()/],className:{1:`title.function`},contains:[k]},I,P,A,R,{match:/\\$[(.]/}]}}function d(e){let t=e.regex,a=u(e),o=n,s=[`any`,`void`,`number`,`boolean`,`string`,`object`,`never`,`symbol`,`bigint`,`unknown`],d={begin:[/namespace/,/\\s+/,e.IDENT_RE],beginScope:{1:`keyword`,3:`title.class`}},f={beginKeywords:`interface`,end:/\\{/,excludeEnd:!0,keywords:{keyword:`interface extends`,built_in:s},contains:[a.exports.CLASS_REFERENCE]},p={className:`meta`,relevance:10,begin:/^\\s*['\"]use strict['\"]/},m={$pattern:n,keyword:r.concat([`type`,`interface`,`public`,`private`,`protected`,`implements`,`declare`,`abstract`,`readonly`,`enum`,`override`,`satisfies`]),literal:i,built_in:l.concat(s),\"variable.language\":c},h={className:`meta`,begin:`@`+o},g=(e,t,n)=>{let r=e.contains.findIndex(e=>e.label===t);if(r===-1)throw Error(`can not find mode to replace`);e.contains.splice(r,1,n)};Object.assign(a.keywords,m),a.exports.PARAMS_CONTAINS.push(h);let _=a.contains.find(e=>e.scope===`attr`),v=Object.assign({},_,{match:t.concat(o,t.lookahead(/\\s*\\?:/))});a.exports.PARAMS_CONTAINS.push([a.exports.CLASS_REFERENCE,_,v]),a.contains=a.contains.concat([h,d,f,v]),g(a,`shebang`,e.SHEBANG()),g(a,`use_strict`,p);let y=a.contains.find(e=>e.label===`func.def`);return y.relevance=0,Object.assign(a,{name:`TypeScript`,aliases:[`ts`,`tsx`,`mts`,`cts`]}),a}t.exports=d})),W=o(((e,t)=>{function n(e){let t=e.regex,n={className:`string`,begin:/\"(\"\"|[^/n])\"C\\b/},r={className:`string`,begin:/\"/,end:/\"/,illegal:/\\n/,contains:[{begin:/\"\"/}]},i=/\\d{1,2}\\/\\d{1,2}\\/\\d{4}/,a=/\\d{4}-\\d{1,2}-\\d{1,2}/,o=/(\\d|1[012])(:\\d+){0,2} *(AM|PM)/,s=/\\d{1,2}(:\\d{1,2}){1,2}/,c={className:`literal`,variants:[{begin:t.concat(/# */,t.either(a,i),/ *#/)},{begin:t.concat(/# */,s,/ *#/)},{begin:t.concat(/# */,o,/ *#/)},{begin:t.concat(/# */,t.either(a,i),/ +/,t.either(o,s),/ *#/)}]},l={className:`number`,relevance:0,variants:[{begin:/\\b\\d[\\d_]*((\\.[\\d_]+(E[+-]?[\\d_]+)?)|(E[+-]?[\\d_]+))[RFD@!#]?/},{begin:/\\b\\d[\\d_]*((U?[SIL])|[%&])?/},{begin:/&H[\\dA-F_]+((U?[SIL])|[%&])?/},{begin:/&O[0-7_]+((U?[SIL])|[%&])?/},{begin:/&B[01_]+((U?[SIL])|[%&])?/}]},u={className:`label`,begin:/^\\w+:/},d=e.COMMENT(/'''/,/$/,{contains:[{className:`doctag`,begin:/<\\/?/,end:/>/}]}),f=e.COMMENT(null,/$/,{variants:[{begin:/'/},{begin:/([\\t ]|^)REM(?=\\s)/}]});return{name:`Visual Basic .NET`,aliases:[`vb`],case_insensitive:!0,classNameAliases:{label:`symbol`},keywords:{keyword:`addhandler alias aggregate ansi as async assembly auto binary by byref byval call case catch class compare const continue custom declare default delegate dim distinct do each equals else elseif end enum erase error event exit explicit finally for friend from function get global goto group handles if implements imports in inherits interface into iterator join key let lib loop me mid module mustinherit mustoverride mybase myclass namespace narrowing new next notinheritable notoverridable of off on operator option optional order overloads overridable overrides paramarray partial preserve private property protected public raiseevent readonly redim removehandler resume return select set shadows shared skip static step stop structure strict sub synclock take text then throw to try unicode until using when where while widening with withevents writeonly yield`,built_in:`addressof and andalso await directcast gettype getxmlnamespace is isfalse isnot istrue like mod nameof new not or orelse trycast typeof xor cbool cbyte cchar cdate cdbl cdec cint clng cobj csbyte cshort csng cstr cuint culng cushort`,type:`boolean byte char date decimal double integer long object sbyte short single string uinteger ulong ushort`,literal:`true false nothing`},illegal:`//|\\\\{|\\\\}|endif|gosub|variant|wend|^\\\\$ `,contains:[n,r,c,l,u,d,f,{className:`meta`,begin:/[\\t ]*#(const|disable|else|elseif|enable|end|externalsource|if|region)\\b/,end:/$/,keywords:{keyword:`const disable else elseif enable end externalsource if region then`},contains:[f]}]}}t.exports=n})),G=o(((e,t)=>{function n(e){e.regex;let t=e.COMMENT(/\\(;/,/;\\)/);t.contains.push(`self`);let n=e.COMMENT(/;;/,/$/),r=`anyfunc,block,br,br_if,br_table,call,call_indirect,data,drop,elem,else,end,export,func,global.get,global.set,local.get,local.set,local.tee,get_global,get_local,global,if,import,local,loop,memory,memory.grow,memory.size,module,mut,nop,offset,param,result,return,select,set_global,set_local,start,table,tee_local,then,type,unreachable`.split(`,`),i={begin:[/(?:func|call|call_indirect)/,/\\s+/,/\\$[^\\s)]+/],className:{1:`keyword`,3:`title.function`}},a={className:`variable`,begin:/\\$[\\w_]+/},o={match:/(\\((?!;)|\\))+/,className:`punctuation`,relevance:0},s={className:`number`,relevance:0,match:/[+-]?\\b(?:\\d(?:_?\\d)*(?:\\.\\d(?:_?\\d)*)?(?:[eE][+-]?\\d(?:_?\\d)*)?|0x[\\da-fA-F](?:_?[\\da-fA-F])*(?:\\.[\\da-fA-F](?:_?[\\da-fA-D])*)?(?:[pP][+-]?\\d(?:_?\\d)*)?)\\b|\\binf\\b|\\bnan(?::0x[\\da-fA-F](?:_?[\\da-fA-D])*)?\\b/},c={match:/(i32|i64|f32|f64)(?!\\.)/,className:`type`},l={className:`keyword`,match:/\\b(f32|f64|i32|i64)(?:\\.(?:abs|add|and|ceil|clz|const|convert_[su]\\/i(?:32|64)|copysign|ctz|demote\\/f64|div(?:_[su])?|eqz?|extend_[su]\\/i32|floor|ge(?:_[su])?|gt(?:_[su])?|le(?:_[su])?|load(?:(?:8|16|32)_[su])?|lt(?:_[su])?|max|min|mul|nearest|neg?|or|popcnt|promote\\/f32|reinterpret\\/[fi](?:32|64)|rem_[su]|rot[lr]|shl|shr_[su]|store(?:8|16|32)?|sqrt|sub|trunc(?:_[su]\\/f(?:32|64))?|wrap\\/i64|xor))\\b/};return{name:`WebAssembly`,keywords:{$pattern:/[\\w.]+/,keyword:r},contains:[n,t,{match:[/(?:offset|align)/,/\\s*/,/=/],className:{1:`keyword`,3:`operator`}},a,o,i,e.QUOTE_STRING_MODE,c,l,s]}}t.exports=n})),K=c(o(((e,t)=>{var n=l();n.registerLanguage(`xml`,u()),n.registerLanguage(`bash`,d()),n.registerLanguage(`c`,f()),n.registerLanguage(`cpp`,p()),n.registerLanguage(`csharp`,m()),n.registerLanguage(`css`,h()),n.registerLanguage(`markdown`,g()),n.registerLanguage(`diff`,_()),n.registerLanguage(`ruby`,v()),n.registerLanguage(`go`,y()),n.registerLanguage(`graphql`,b()),n.registerLanguage(`ini`,x()),n.registerLanguage(`java`,S()),n.registerLanguage(`javascript`,C()),n.registerLanguage(`json`,w()),n.registerLanguage(`kotlin`,T()),n.registerLanguage(`less`,E()),n.registerLanguage(`lua`,D()),n.registerLanguage(`makefile`,O()),n.registerLanguage(`perl`,k()),n.registerLanguage(`objectivec`,A()),n.registerLanguage(`php`,j()),n.registerLanguage(`php-template`,M()),n.registerLanguage(`plaintext`,N()),n.registerLanguage(`python`,P()),n.registerLanguage(`python-repl`,F()),n.registerLanguage(`r`,I()),n.registerLanguage(`rust`,L()),n.registerLanguage(`scss`,R()),n.registerLanguage(`shell`,z()),n.registerLanguage(`sql`,B()),n.registerLanguage(`swift`,V()),n.registerLanguage(`yaml`,H()),n.registerLanguage(`typescript`,U()),n.registerLanguage(`vbnet`,W()),n.registerLanguage(`wasm`,G()),n.HighlightJS=n,n.default=n,t.exports=n}))(),1).default;window.hljs=K,document.addEventListener(`turbo:load`,()=>K.highlightAll()),document.addEventListener(`turbo:frame-load`,()=>K.highlightAll()),document.addEventListener(`turbo:before-stream-render`,e=>{let{detail:t}=e,n=t.render;t.render=e=>{n(e),K.highlightAll()}})})();"
  },
  {
    "path": "resources/js/bootstrap/custom-elements.ts",
    "content": "import '@github/text-expander-element';\nimport {\n    AccordionElement,\n    AlertsElement,\n    DisclosureElement,\n    MenuElement,\n    ModalElement,\n    PopupElement,\n    ToolbarElement,\n    TooltipElement,\n} from 'inclusive-elements';\n\nwindow.customElements.define('ui-accordion', AccordionElement);\nwindow.customElements.define('ui-alerts', AlertsElement);\nwindow.customElements.define('ui-disclosure', DisclosureElement);\nwindow.customElements.define('ui-menu', MenuElement);\nwindow.customElements.define('ui-modal', ModalElement);\nwindow.customElements.define('ui-popup', PopupElement);\nwindow.customElements.define('ui-toolbar', ToolbarElement);\nwindow.customElements.define('ui-tooltip', TooltipElement);\n"
  },
  {
    "path": "resources/js/bootstrap/document-title.ts",
    "content": "/**\n * Methods to manage a numeric prefix on the document title.\n */\nclass DocumentTitle {\n    private title: string = '';\n    private count: number = 0;\n\n    public initialize(): void {\n        this.title = document.title;\n        this.count = 0;\n    }\n\n    public increment(): void {\n        this.count++;\n        this.update();\n    }\n\n    public reset(): void {\n        this.count = 0;\n        this.update();\n    }\n\n    private update(): void {\n        document.title = (this.count ? `(${this.count}) ` : '') + this.title;\n    }\n}\n\nWaterhole.documentTitle = new DocumentTitle();\n\ndocument.addEventListener('turbo:load', () =>\n    Waterhole.documentTitle.initialize(),\n);\n\nwindow.addEventListener('focus', () => Waterhole.documentTitle.reset());\n"
  },
  {
    "path": "resources/js/bootstrap/echo.ts",
    "content": "import Echo from 'laravel-echo';\nimport Pusher from 'pusher-js';\nimport { TurboEchoStreamSourceElement } from '../elements/turbo-echo-stream-tag';\n\ndeclare global {\n    interface Window {\n        Pusher: typeof Pusher;\n        Echo: Echo<'pusher' | 'reverb'>;\n    }\n}\n\nwindow.Pusher = Pusher;\n\nif (window.Waterhole.echoConfig.broadcaster) {\n    window.Echo = new Echo({\n        namespace: 'Waterhole.Events',\n        ...window.Waterhole.echoConfig,\n    });\n\n    window.Echo.registerTurboRequestInterceptor();\n\n    customElements.define(\n        'turbo-echo-stream-source',\n        TurboEchoStreamSourceElement,\n    );\n}\n"
  },
  {
    "path": "resources/js/bootstrap/hotkeys.ts",
    "content": "import { install } from '@github/hotkey';\n\nfunction initHotkeys(e: Event) {\n    (e.target as Element)\n        .querySelectorAll<HTMLElement>('[data-hotkey]')\n        .forEach((el) => {\n            install(el);\n        });\n}\n\ndocument.addEventListener('DOMContentLoaded', initHotkeys);\ndocument.addEventListener('turbo:render', initHotkeys);\ndocument.addEventListener('turbo:frame-render', initHotkeys);\n"
  },
  {
    "path": "resources/js/bootstrap/turbo.ts",
    "content": "import * as Turbo from '@hotwired/turbo';\nimport { FrameElement, TurboFrameMissingEvent } from '@hotwired/turbo';\nimport { cloneFromTemplate } from '../utils';\nimport { AlertsElement } from 'inclusive-elements';\n\ndeclare global {\n    interface Window {\n        Turbo: any;\n    }\n}\n\nwindow.Turbo = Turbo;\n\nlet newAlerts: AlertsElement | null = null;\n\ndocument.addEventListener('turbo:before-render', (e) => {\n    newAlerts = e.detail.newBody.querySelector<AlertsElement>('#alerts');\n});\n\ndocument.addEventListener('turbo:load', (e) => {\n    if (!newAlerts) return;\n    [...newAlerts.children].forEach((el) =>\n        Waterhole.alerts.show(el as HTMLElement),\n    );\n});\n\ndocument.addEventListener('turbo:before-fetch-response', async (e) => {\n    const response = (e as CustomEvent).detail.fetchResponse.response;\n    if (response.ok || response.status === 422) return;\n    e.preventDefault();\n\n    const { target } = e;\n    if (target instanceof FrameElement) {\n        const el = cloneFromTemplate('frame-error');\n        el.querySelector('button')?.addEventListener('click', () => {\n            target.reload();\n        });\n        target.replaceChildren(el);\n    } else {\n        Waterhole.fetchError(response);\n    }\n});\n\ndocument.addEventListener('turbo:frame-missing', async (e) => {\n    e.preventDefault();\n    const { detail } = e as TurboFrameMissingEvent;\n    detail.visit(detail.response, { action: 'replace' });\n});\n\nWaterhole.fetchError = async function (response?: Response) {\n    // TODO: use messages instead of templates\n    let templateId;\n    switch (response?.status) {\n        case 401:\n        case 403:\n            templateId = 'forbidden-alert';\n            break;\n\n        case 419:\n            templateId = 'session-expired-alert';\n            break;\n\n        case 422:\n            const alert = cloneFromTemplate('template-alert-danger');\n            alert.querySelector('.alert__message')!.textContent = (\n                await response.json()\n            ).message;\n            Waterhole.alerts.show(alert, { key: 'fetchError' });\n            break;\n\n        case 429:\n            templateId = 'too-many-requests-alert';\n            break;\n\n        default:\n            templateId = 'fatal-error-alert';\n    }\n\n    if (templateId) {\n        const alert = cloneFromTemplate(templateId);\n\n        if (alert) {\n            Waterhole.alerts.show(alert, { key: 'fetchError' });\n        }\n    }\n};\n"
  },
  {
    "path": "resources/js/controllers/action-menu-controller.ts",
    "content": "import { Controller } from '@hotwired/stimulus';\nimport { FrameElement } from '@hotwired/turbo';\nimport { PopupElement } from 'inclusive-elements';\n\n/**\n * Controller for an action menu.\n *\n * @internal\n */\nexport default class extends Controller<PopupElement> {\n    static targets = ['frame'];\n\n    declare readonly hasFrameTarget: boolean;\n    declare readonly frameTarget?: FrameElement;\n\n    preload() {\n        if (this.hasFrameTarget) {\n            this.frameTarget?.setAttribute('loading', 'eager');\n        }\n    }\n}\n"
  },
  {
    "path": "resources/js/controllers/alert-controller.ts",
    "content": "import { Controller } from '@hotwired/stimulus';\nimport { AlertsElement } from 'inclusive-elements';\n\n/**\n * Controller for an alert.\n *\n * Provides an action for alerts to dismiss themselves.\n *\n * @internal\n */\nexport default class extends Controller<HTMLElement> {\n    dismiss(e: MouseEvent) {\n        // If this alert is contained inside a <ui-alerts> element, then we\n        // will dismiss it via that element. Otherwise, we can just straight up\n        // remove it from the DOM.\n        const container = this.element.closest<AlertsElement>('ui-alerts');\n        if (container) {\n            container.dismiss(this.element);\n        } else {\n            this.element.remove();\n        }\n    }\n}\n"
  },
  {
    "path": "resources/js/controllers/comment-controller.ts",
    "content": "import { Controller } from '@hotwired/stimulus';\nimport { TooltipElement } from 'inclusive-elements';\nimport { isElementInViewport } from '../utils';\n\nconst expanded: Record<string, boolean> = {};\n\n/**\n * Controller for the <x-waterhole::comment-full> component.\n *\n * @internal\n */\nexport default class extends Controller {\n    static targets = ['parentTooltip'];\n\n    declare readonly parentTooltipTarget?: TooltipElement;\n\n    get commentId(): string {\n        return this.element.getAttribute('data-comment-id') || '';\n    }\n\n    get parentId(): string {\n        return this.element.getAttribute('data-parent-id') || '';\n    }\n\n    get parentElements(): HTMLElement[] {\n        return Array.from(\n            document.querySelectorAll<HTMLElement>(\n                `[data-comment-id=\"${this.parentId}\"]`,\n            ),\n        );\n    }\n\n    connect() {\n        if (expanded[this.commentId]) {\n            this.toggleExpanded();\n        }\n    }\n\n    disconnect() {\n        expanded[this.commentId] =\n            this.element.classList.contains('is-expanded');\n    }\n\n    toggleExpanded() {\n        this.element.classList.toggle('is-expanded');\n    }\n\n    highlightParent() {\n        this.parentElements.forEach((el) => {\n            el.classList.add('is-highlighted');\n        });\n\n        if (this.parentTooltipTarget) {\n            this.parentTooltipTarget.disabled = this.parentElements.some((el) =>\n                isElementInViewport(el, 0.5),\n            );\n        }\n    }\n\n    stopHighlightingParent() {\n        this.parentElements.forEach((el) => {\n            el.classList.remove('is-highlighted');\n        });\n    }\n}\n"
  },
  {
    "path": "resources/js/controllers/comment-replies-controller.ts",
    "content": "import { Controller } from '@hotwired/stimulus';\nimport { FrameElement } from '@hotwired/turbo';\n\n/**\n * Controller for the <x-waterhole::comment-replies> component.\n *\n * @internal\n */\nexport default class extends Controller {\n    connect() {\n        this.element.addEventListener('click', (e) => {\n            const expanded =\n                this.element.getAttribute('aria-expanded') === 'false';\n            this.element.setAttribute('aria-expanded', String(expanded));\n            const controlled = this.element\n                .closest('.comment')\n                ?.querySelector<HTMLElement>('.comment__replies');\n            if (controlled) {\n                controlled.hidden = !expanded;\n            }\n            if (!expanded) {\n                e.preventDefault();\n            }\n        });\n    }\n\n    focusAfterLoad() {\n        addEventListener(\n            'turbo:frame-render',\n            (e) => {\n                // Safari will try to scroll down when we focus on the replies\n                // element (if it is tall), but we don't want that, so revert\n                // it afterwards.\n                const top = window.scrollY;\n                (e.target as FrameElement)\n                    .querySelector<HTMLElement>('.comment__replies')\n                    ?.focus();\n                window.scroll({ top });\n            },\n            { once: true },\n        );\n    }\n}\n"
  },
  {
    "path": "resources/js/controllers/composer-controller.ts",
    "content": "import { Controller } from '@hotwired/stimulus';\nimport animateScrollTo from 'animated-scroll-to';\nimport { shouldOpenInNewTab } from '../utils';\n\n/**\n * Controller for the <x-waterhole::composer> component.\n *\n * @internal\n */\nexport default class extends Controller<HTMLElement> {\n    static targets = ['handle'];\n\n    connect() {\n        const height = Number(localStorage.getItem('composer_height'));\n        if (height) {\n            this.element.style.height = height + 'px';\n        }\n\n        window.addEventListener('hashchange', this.onHashChange);\n        this.onHashChange();\n    }\n\n    disconnect() {\n        window.removeEventListener('hashchange', this.onHashChange);\n    }\n\n    private onHashChange = () => {\n        // Turbo sends the hashchange event before the hash has actually\n        // updated, so do this after a tick.\n        requestAnimationFrame(() => {\n            if (window.location.hash === '#reply') {\n                this.open();\n            }\n        });\n    };\n\n    placeholderClick(e: MouseEvent) {\n        if (shouldOpenInNewTab(e)) return;\n\n        e.preventDefault();\n\n        this.open();\n        this.scrollToBottom();\n    }\n\n    private scrollToBottom() {\n        this.element.style.position = 'static';\n        const composerTop = this.element.getBoundingClientRect().top;\n        const composerHeight = parseInt(getComputedStyle(this.element).height);\n        this.element.style.position = '';\n\n        const scrollTo =\n            scrollY + composerTop + composerHeight - window.innerHeight;\n        if (scrollY > scrollTo) return;\n\n        animateScrollTo(scrollTo, { minDuration: 200, maxDuration: 200 });\n    }\n\n    open() {\n        this.element.classList.add('is-open');\n        setTimeout(() => this.element.querySelector('textarea')?.focus());\n    }\n\n    close() {\n        this.element.classList.remove('is-open');\n\n        if (window.location.hash === '#reply') {\n            history.replaceState(null, '', ' ');\n        }\n    }\n\n    submitEnd(e: CustomEvent) {\n        if (\n            e.detail.fetchResponse.contentType.startsWith(\n                'text/vnd.turbo-stream.html',\n            )\n        ) {\n            this.close();\n        }\n    }\n\n    startResize(e: PointerEvent) {\n        e.preventDefault();\n\n        const el = this.element as HTMLElement;\n        const startY = e.clientY;\n        const startHeight = el.offsetHeight;\n        const startBottom = el.getBoundingClientRect().bottom;\n\n        const move = (e: PointerEvent) => {\n            const height = startHeight - (e.clientY - startY);\n            el.style.height = height + 'px';\n            localStorage.setItem('composer_height', String(height));\n            window.scroll(\n                0,\n                window.scrollY +\n                    el.getBoundingClientRect().bottom -\n                    startBottom,\n            );\n        };\n\n        document.addEventListener('pointermove', move);\n        document.addEventListener(\n            'pointerup',\n            () => {\n                document.removeEventListener('pointermove', move);\n            },\n            { once: true },\n        );\n    }\n}\n"
  },
  {
    "path": "resources/js/controllers/copy-link-controller.ts",
    "content": "import { Controller } from '@hotwired/stimulus';\nimport { cloneFromTemplate } from '../utils';\n\n/**\n * Controller to power a \"copy link\" button.\n */\nexport default class extends Controller {\n    static values = { message: String };\n\n    declare readonly hasMessageValue: boolean;\n    declare readonly messageValue: string;\n\n    async copy(e: Event) {\n        e.preventDefault();\n\n        const target = e.currentTarget as HTMLElement;\n        const url = target.getAttribute('href') || '';\n\n        try {\n            await navigator.clipboard.writeText(url);\n\n            if (this.hasMessageValue) {\n                const alert = cloneFromTemplate('template-alert-success');\n                alert.querySelector('.alert__message')!.textContent =\n                    this.messageValue;\n                Waterhole.alerts.show(alert, { key: 'copy-link' });\n            }\n        } catch (e) {\n            window.prompt('', url);\n        }\n    }\n}\n"
  },
  {
    "path": "resources/js/controllers/details-focus-controller.ts",
    "content": "import { Controller } from '@hotwired/stimulus';\n\nexport default class extends Controller<HTMLDetailsElement> {\n    private toggle = () => {\n        if (!this.element.open) {\n            return;\n        }\n\n        const target = this.element.querySelector<HTMLElement>(\n            ':is(button, [href], input, select, textarea, [tabindex])' +\n                ':not([hidden])' +\n                ':not([disabled])' +\n                ':not([type=\"hidden\"])' +\n                ':not([tabindex=\"-1\"])',\n        );\n\n        if (target) {\n            target.focus();\n        }\n    };\n\n    connect() {\n        this.element.addEventListener('toggle', this.toggle);\n    }\n\n    disconnect() {\n        this.element.removeEventListener('toggle', this.toggle);\n    }\n}\n"
  },
  {
    "path": "resources/js/controllers/load-backwards-controller.ts",
    "content": "import { Controller } from '@hotwired/stimulus';\n\n/**\n * Controller that will anchor the scroll position to the bottom of an element\n * during a Turbo Frame load.\n */\nexport default class extends Controller {\n    anchor?: HTMLElement;\n    top?: number;\n    observer?: MutationObserver;\n\n    connect() {\n        this.element.addEventListener(\n            'turbo:before-fetch-response',\n            this.lockScrollPosition,\n        );\n        this.element.addEventListener(\n            'turbo:frame-render',\n            this.unlockScrollPosition,\n        );\n    }\n\n    disconnect() {\n        this.element.removeEventListener(\n            'turbo:before-fetch-response',\n            this.lockScrollPosition,\n        );\n        this.element.removeEventListener(\n            'turbo:frame-render',\n            this.unlockScrollPosition,\n        );\n        this.unlockScrollPosition();\n    }\n\n    private lockScrollPosition = (e: Event) => {\n        if (e.target !== e.currentTarget) return;\n\n        this.anchor = this.element.nextElementSibling as HTMLElement;\n        if (this.anchor) {\n            this.top = this.anchor.getBoundingClientRect().top;\n        }\n\n        this.observer?.disconnect();\n        this.observer = new MutationObserver(() => this.restore());\n        this.observer.observe(document.body, {\n            subtree: true,\n            childList: true,\n            attributes: true,\n        });\n    };\n\n    restore() {\n        if (this.anchor && this.top) {\n            window.scroll({\n                top:\n                    window.scrollY +\n                    this.anchor.getBoundingClientRect().top -\n                    this.top,\n            });\n        }\n    }\n\n    private unlockScrollPosition = (e?: Event) => {\n        if (e && e.target !== e.currentTarget) return;\n\n        setTimeout(() => {\n            this.observer?.disconnect();\n            delete this.observer;\n        });\n    };\n}\n"
  },
  {
    "path": "resources/js/controllers/login-controller.ts",
    "content": "import { Controller } from '@hotwired/stimulus';\n\n/**\n * Controller for the login page.\n *\n * Carries over the value of the email input when you navigate away (eg. to the\n * register or forgot password page).\n *\n * @internal\n */\nexport default class extends Controller {\n    connect() {\n        this.element.addEventListener('submit', () =>\n            this.element\n                .querySelector('button[type=\"submit\"]')\n                ?.setAttribute('disabled', ''),\n        );\n    }\n\n    disconnect() {\n        const value =\n            this.element.querySelector<HTMLInputElement>('input[name=email]')\n                ?.value || '';\n\n        document.addEventListener(\n            'turbo:load',\n            () => {\n                const input =\n                    document.querySelector<HTMLInputElement>(\n                        'input[name=email]',\n                    );\n                if (input) {\n                    input.value = value;\n                }\n            },\n            { once: true },\n        );\n    }\n}\n"
  },
  {
    "path": "resources/js/controllers/mentions-controller.ts",
    "content": "import TextExpanderElement from '@github/text-expander-element';\nimport { Controller } from '@hotwired/stimulus';\nimport * as Turbo from '@hotwired/turbo';\n\ninterface UserLookupResult {\n    id: number;\n    name: string;\n    html: string;\n    commentUrl?: string;\n    frameId?: string;\n}\n\n/**\n * Controller to enable @mention suggestions on a <text-expander> element.\n *\n * @internal\n */\nexport default class extends Controller<TextExpanderElement> {\n    static values = {\n        userLookupUrl: String,\n    };\n\n    declare readonly userLookupUrlValue: string;\n\n    connect() {\n        this.element.addEventListener(\n            'text-expander-change',\n            this.onTextExpanderChange,\n        );\n        this.element.addEventListener(\n            'text-expander-value',\n            this.onTextExpanderValue,\n        );\n    }\n\n    disconnect() {\n        this.element.removeEventListener(\n            'text-expander-change',\n            this.onTextExpanderChange,\n        );\n        this.element.removeEventListener(\n            'text-expander-value',\n            this.onTextExpanderValue,\n        );\n    }\n\n    private onTextExpanderChange = ((event: CustomEvent) => {\n        const { provide, text } = event.detail;\n\n        const url = new URL(this.userLookupUrlValue);\n        url.searchParams.append('q', text);\n\n        provide(\n            Waterhole.fetch(url)\n                .json<UserLookupResult[]>()\n                .then((json) => {\n                    const listbox = document.createElement('ul');\n                    listbox.setAttribute('role', 'listbox');\n                    listbox.className = 'menu';\n                    listbox.style.position = 'absolute';\n                    listbox.style.marginTop = '24px';\n\n                    listbox.append(\n                        ...json.map(({ name, html, commentUrl, frameId }) => {\n                            const option = document.createElement('li');\n                            option.setAttribute('role', 'option');\n                            option.id = `suggestion-${Math.floor(\n                                Math.random() * 100000,\n                            ).toString()}`;\n                            option.className = 'menu-item';\n                            option.dataset.value = name;\n                            option.dataset.commentUrl = commentUrl || '';\n                            option.dataset.frameId = frameId || '';\n                            option.innerHTML = html;\n\n                            return option;\n                        }),\n                    );\n\n                    const observer = new MutationObserver(() => {\n                        if (\n                            listbox.getBoundingClientRect().bottom >\n                            window.innerHeight\n                        ) {\n                            listbox.style.transform = 'translateY(-100%)';\n                            listbox.style.marginTop = '-12px';\n                        }\n                    });\n\n                    observer.observe(listbox, {\n                        attributes: true,\n                        attributeFilter: ['style'],\n                    });\n\n                    return {\n                        matched: Boolean(json.length),\n                        fragment: listbox,\n                    };\n                }),\n        );\n    }) as EventListener;\n\n    private onTextExpanderValue = ((event: CustomEvent) => {\n        const { item } = event.detail;\n\n        event.detail.value = '@' + item.dataset.value.replace(/ /g, '\\xa0');\n\n        const { commentUrl, frameId } = item.dataset;\n        if (commentUrl) {\n            Turbo.visit(commentUrl, { frame: frameId });\n        }\n    }) as EventListener;\n}\n"
  },
  {
    "path": "resources/js/controllers/modal-controller.ts",
    "content": "import { Controller } from '@hotwired/stimulus';\nimport { FrameElement } from '@hotwired/turbo';\nimport { ModalElement } from 'inclusive-elements';\n\n/**\n * Controller for the modal element.\n */\nexport default class extends Controller<ModalElement> {\n    static targets = ['frame', 'loading'];\n\n    declare readonly frameTarget: FrameElement;\n    declare readonly loadingTarget: HTMLDivElement;\n\n    connect() {\n        this.frameTarget.removeAttribute('disabled');\n    }\n\n    loading() {\n        if (!this.element.open) {\n            this.frameTarget.hidden = true;\n            this.loadingTarget.hidden = false;\n        }\n\n        this.show();\n    }\n\n    loaded() {\n        this.frameTarget.hidden = false;\n        this.loadingTarget.hidden = true;\n\n        if (this.frameTarget.children.length) {\n            this.show();\n        } else {\n            this.hide();\n        }\n    }\n\n    show() {\n        if (!this.element.open) {\n            this.element.open = true;\n        }\n    }\n\n    hide(e?: Event) {\n        if (e instanceof MouseEvent) {\n            e.preventDefault();\n        }\n\n        if (this.element.open) {\n            this.element.close();\n        }\n    }\n}\n"
  },
  {
    "path": "resources/js/controllers/notifications-popup-controller.ts",
    "content": "import { Controller } from '@hotwired/stimulus';\nimport { FrameElement } from '@hotwired/turbo';\nimport { PopupElement } from 'inclusive-elements';\n\n/**\n * Controller for the notifications popup.\n *\n * @internal\n */\nexport default class extends Controller {\n    static targets = ['badge', 'frame', 'sm'];\n\n    declare readonly badgeTarget: HTMLElement;\n    declare readonly frameTarget: FrameElement;\n    declare readonly smTarget: HTMLElement;\n\n    open(e: MouseEvent) {\n        this.frameTarget.reload();\n\n        if (!this.element.hasAttribute('data-persistent-badge')) {\n            this.badgeTarget.hidden = true;\n        }\n\n        Waterhole.alerts.dismiss('notification');\n\n        // If we're on a small display, close the popup and navigate to the\n        // link's original target (the notifications page).\n        if (getComputedStyle(this.smTarget).display === 'none') {\n            window.Turbo.visit((e.currentTarget as HTMLAnchorElement).href);\n            (this.element as PopupElement).open = false;\n        }\n    }\n}\n"
  },
  {
    "path": "resources/js/controllers/page-controller.ts",
    "content": "import { Controller } from '@hotwired/stimulus';\nimport { ModalElement } from 'inclusive-elements';\nimport { getHeaderHeight } from '../utils';\n\n/**\n * Controller for the page.\n */\nexport default class extends Controller {\n    static targets = ['breadcrumb', 'title'];\n\n    declare readonly hasBreadcrumbTarget: boolean;\n    declare readonly breadcrumbTarget: HTMLElement;\n\n    declare observer: IntersectionObserver;\n\n    private hideBreadcrumb?: number;\n\n    initialize() {\n        this.observer = new IntersectionObserver(\n            (entries) => {\n                if (!this.hasBreadcrumbTarget) return;\n                if (this.hideBreadcrumb) {\n                    cancelAnimationFrame(this.hideBreadcrumb);\n                }\n                this.breadcrumbTarget.hidden = entries[0].isIntersecting;\n                if (!entries[0].isIntersecting) {\n                    this.breadcrumbTarget.innerHTML =\n                        entries[0].target.innerHTML || '';\n                }\n            },\n            { rootMargin: `-${getHeaderHeight()}px` },\n        );\n    }\n\n    connect() {\n        this.hideBreadcrumb = requestAnimationFrame(() => {\n            if (!this.hasBreadcrumbTarget) return;\n            this.breadcrumbTarget.hidden = true;\n        });\n    }\n\n    titleTargetConnected(element: HTMLElement) {\n        this.observer.observe(element);\n    }\n\n    titleTargetDisconnected() {\n        this.observer.disconnect();\n    }\n\n    incrementDocumentTitle() {\n        Waterhole.documentTitle.increment();\n    }\n\n    closeModal() {\n        document.querySelector<ModalElement>('#modal-element')?.close();\n    }\n}\n"
  },
  {
    "path": "resources/js/controllers/post-controller.ts",
    "content": "import { Controller } from '@hotwired/stimulus';\n\n/**\n * Controller for a post summary.\n *\n * @internal\n */\nexport default class extends Controller {\n    appearAsRead() {\n        if (this.element.classList.contains('is-unread')) {\n            this.element.classList.remove('is-unread', 'is-new');\n            this.element.classList.add('is-read');\n        }\n    }\n}\n"
  },
  {
    "path": "resources/js/controllers/post-feed-controller.ts",
    "content": "import { Controller } from '@hotwired/stimulus';\nimport animateScrollTo from 'animated-scroll-to';\nimport { getHeaderHeight } from '../utils';\n\n/**\n * Controller for the post feed.\n *\n * @internal\n */\nexport default class extends Controller {\n    static targets = ['newActivity'];\n\n    static values = {\n        filter: String,\n        channels: Array,\n        publicChannels: Array,\n    };\n\n    declare readonly newActivityTarget: HTMLElement;\n    declare readonly filterValue: string;\n    declare readonly channelsValue: number[];\n    declare readonly publicChannelsValue: number[];\n\n    connect() {\n        this.channelsValue?.forEach((id) => {\n            const method = this.publicChannelsValue?.includes(id)\n                ? 'channel'\n                : 'private';\n            window.Echo[method](`Waterhole.Models.Channel.${id}`)\n                .listen('NewComment', () => {\n                    if (this.filterValue === 'latest') {\n                        this.showNewActivity();\n                    }\n                })\n                .listen('NewPost', () => {\n                    if (\n                        this.filterValue === 'newest' ||\n                        this.filterValue === 'latest'\n                    ) {\n                        this.showNewActivity();\n                    }\n                });\n        });\n    }\n\n    disconnect() {\n        this.channelsValue?.forEach((id) => {\n            window.Echo.leave(`Waterhole.Models.Channel.${id}`);\n        });\n    }\n\n    showNewActivity() {\n        if (this.newActivityTarget) {\n            this.newActivityTarget.hidden = false;\n            Waterhole.documentTitle.increment();\n        }\n    }\n\n    scrollToTop() {\n        const offset = getHeaderHeight() + 20;\n        if (this.element.getBoundingClientRect().top < offset) {\n            animateScrollTo(this.element, { verticalOffset: -offset });\n        }\n    }\n}\n"
  },
  {
    "path": "resources/js/controllers/post-page-controller.ts",
    "content": "import { Controller } from '@hotwired/stimulus';\nimport { renderStreamMessage, StreamElement } from '@hotwired/turbo';\nimport { getHeaderHeight } from '../utils';\n\n/**\n * Controller for the post page.\n *\n * @internal\n */\nexport default class extends Controller {\n    static targets = [\n        'post',\n        'currentPage',\n        'commentsLinks',\n        'commentsPagination',\n    ];\n\n    static values = {\n        id: Number,\n    };\n\n    declare readonly postTarget: HTMLElement;\n    declare readonly hasCurrentPageTarget: boolean;\n    declare readonly currentPageTarget: HTMLElement;\n    declare readonly hasCommentsLinksTarget: boolean;\n    declare readonly commentsLinksTarget: HTMLElement;\n    declare readonly hasCommentsPaginationTarget: boolean;\n    declare readonly commentsPaginationTarget: HTMLElement;\n    declare readonly idValue: number;\n\n    connect() {\n        document.addEventListener(\n            'turbo:before-stream-render',\n            this.beforeStreamRender,\n        );\n        document.addEventListener(\n            'turbo:frame-render',\n            this.showPostOnFirstPage,\n        );\n\n        window.addEventListener('scroll', this.onScroll, { passive: true });\n        this.onScroll();\n    }\n\n    disconnect() {\n        document.removeEventListener(\n            'turbo:before-stream-render',\n            this.beforeStreamRender,\n        );\n        document.removeEventListener(\n            'turbo:frame-render',\n            this.showPostOnFirstPage,\n        );\n\n        window.removeEventListener('scroll', this.onScroll);\n    }\n\n    private showPostOnFirstPage = () => {\n        if (document.getElementById('page_1')) {\n            this.postTarget.hidden = false;\n        }\n    };\n\n    // If the post is deleted via an action, the returned Turbo Stream will try\n    // to remove it from the page. We will navigate back to the post feed before\n    // the stream is executed.\n    private beforeStreamRender = (e: Event) => {\n        const stream = e.target as StreamElement;\n        if (\n            stream.action === 'remove' &&\n            stream.targets?.endsWith('post_' + this.idValue)\n        ) {\n            window.history.back();\n            window.addEventListener(\n                'popstate',\n                () => {\n                    window.requestAnimationFrame(() => {\n                        renderStreamMessage(stream.outerHTML);\n                    });\n                },\n                { once: true },\n            );\n            e.preventDefault();\n        }\n    };\n\n    private onScroll = () => {\n        // Wait for the scrollspy controller to update which page\n        // is currently selected.\n        setTimeout(() => {\n            if (this.hasCurrentPageTarget) {\n                this.currentPageTarget.textContent =\n                    this.element.querySelector(\n                        '.comments-pagination [aria-current=\"page\"]',\n                    )?.textContent || '1';\n            }\n        });\n\n        if (this.hasCommentsLinksTarget && this.hasCommentsPaginationTarget) {\n            this.commentsLinksTarget.hidden =\n                this.postTarget.getBoundingClientRect().bottom <\n                getHeaderHeight() + 10;\n            this.commentsPaginationTarget.hidden =\n                !this.commentsLinksTarget.hidden;\n        }\n    };\n}\n"
  },
  {
    "path": "resources/js/controllers/quotable-controller.ts",
    "content": "import {\n    computePosition,\n    flip,\n    offset,\n    Placement,\n    shift,\n} from '@floating-ui/dom';\nimport { Controller } from '@hotwired/stimulus';\n\n/**\n * Controller for a quotable text body (eg. post body or comment body).\n *\n * When text is selected, the button target will be shown and positioned\n * adjacent to the selection.\n */\nexport default class extends Controller {\n    static targets = ['button'];\n\n    declare readonly hasButtonTarget: boolean;\n    declare readonly buttonTarget: HTMLButtonElement;\n\n    private handleSelectionChange = () => {\n        setTimeout(this.updateQuoteButton.bind(this), 100);\n    };\n\n    connect() {\n        document.addEventListener('mouseup', this.handleSelectionChange);\n    }\n\n    disconnect() {\n        document.removeEventListener('mouseup', this.handleSelectionChange);\n    }\n\n    async updateQuoteButton() {\n        if (!this.hasButtonTarget) return;\n        this.buttonTarget.hidden = true;\n\n        const selection = window.getSelection();\n\n        if (\n            !selection ||\n            selection.isCollapsed ||\n            !selection.anchorNode ||\n            !selection.focusNode\n        ) {\n            return;\n        }\n\n        const range = selection.getRangeAt(0);\n        const parent = range.commonAncestorContainer;\n\n        // If the selection spans outside of the content area, or there\n        // is no selection at all, we will not proceed.\n        if (parent !== this.element && !this.element.contains(parent)) {\n            return;\n        }\n\n        this.buttonTarget.hidden = false;\n        this.buttonTarget.style.position = 'absolute';\n        this.buttonTarget.style.zIndex = 'var(--z-index-overlay)';\n\n        // Place the quote button according to where the focus of the\n        // selection is (ie. where the selection began).\n        const position = selection.anchorNode.compareDocumentPosition(\n            selection.focusNode,\n        );\n        const rects = range.getClientRects();\n        let anchor: DOMRect;\n        let placement: Placement;\n\n        if (\n            position & Node.DOCUMENT_POSITION_PRECEDING ||\n            (!position && selection.focusOffset < selection.anchorOffset)\n        ) {\n            const rect = rects[0];\n            anchor = new DOMRect(rect.left, rect.top);\n            placement = 'top';\n        } else {\n            const rect = rects[rects.length - 1];\n            anchor = new DOMRect(rect.right, rect.bottom);\n            placement = 'bottom';\n        }\n\n        const virtualEl = { getBoundingClientRect: () => anchor };\n\n        computePosition(virtualEl, this.buttonTarget, {\n            placement,\n            middleware: [offset(10), shift(), flip()],\n        }).then(({ x, y }) => {\n            Object.assign(this.buttonTarget!.style, {\n                left: `${x}px`,\n                top: `${y}px`,\n            });\n        });\n    }\n\n    quoteSelectedText() {\n        const container = document.createElement('div');\n        const selection = window.getSelection();\n        if (!selection) return;\n\n        container.appendChild(selection.getRangeAt(0).cloneContents());\n        container\n            .querySelectorAll('img')\n            .forEach((el) => el.replaceWith(el.alt));\n\n        selection.removeAllRanges();\n\n        // Wait until the next tick so that the composer has had a chance to\n        // open (via turbo:before-fetch-request) before we dispatch the event.\n        setTimeout(() => {\n            this.dispatch('quote-text', {\n                detail: { text: container.textContent },\n                bubbles: true,\n                cancelable: true,\n            });\n        });\n    }\n}\n"
  },
  {
    "path": "resources/js/controllers/reveal-controller.ts",
    "content": "import { Controller } from '@hotwired/stimulus';\n\n/**\n * Controller to allow an element to show/hide based on the value of an input.\n *\n * The `if` target should point to the input or select element. `then` targets\n * point to the element(s) to be revealed. Optionally, a `data-reveal-value`\n * attribute can be added to each `then` target to only reveal it if the input\n * equals that value. Otherwise, the target will be revealed if the input has\n * a truthy value or is checked.\n */\nexport default class extends Controller {\n    static targets = ['if', 'then'];\n\n    declare readonly thenTargets: HTMLElement[];\n\n    ifTargetConnected(el: HTMLInputElement) {\n        el.addEventListener('change', this.toggle);\n\n        if (el.type !== 'radio' || el.checked) {\n            el.dispatchEvent(new Event('change'));\n        }\n    }\n\n    ifTargetDisconnected(el: HTMLElement) {\n        el.removeEventListener('change', this.toggle);\n    }\n\n    private toggle = (e: Event) => {\n        const source = e.target as HTMLInputElement | HTMLSelectElement;\n        let value = source.value;\n\n        if (\n            source instanceof HTMLInputElement &&\n            ['checkbox', 'radio'].includes(source.type) &&\n            !source.checked\n        ) {\n            value = '';\n        }\n\n        this.thenTargets.forEach((el) => {\n            if (!el.dataset.revealValue) el.hidden = !value;\n            else {\n                let values: any = el.dataset.revealValue;\n                try {\n                    values = JSON.parse(values);\n                } catch (e) {\n                    //\n                } finally {\n                    if (Array.isArray(values)) {\n                        values = values.map((v: any) => String(v));\n                    } else {\n                        values = [el.dataset.revealValue];\n                    }\n                }\n                el.hidden = !values.includes(value);\n            }\n        });\n    };\n}\n"
  },
  {
    "path": "resources/js/controllers/scrollspy-controller.ts",
    "content": "import { Controller } from '@hotwired/stimulus';\nimport { getHeaderHeight } from '../utils';\n\n/**\n * Controller to apply \"active\" nav link styles based on the scroll position.\n */\nexport default class extends Controller<HTMLElement> {\n    current?: HTMLElement;\n\n    connect() {\n        this.onScroll();\n\n        window.addEventListener('scroll', this.onScroll);\n    }\n\n    disconnect() {\n        window.removeEventListener('scroll', this.onScroll);\n    }\n\n    private onScroll = () => {\n        const links = Array.from(this.links());\n        const headerHeight = getHeaderHeight();\n\n        links.forEach((a) => a.removeAttribute('aria-current'));\n\n        links.reverse().some((a) => {\n            const id = a.hash.substring(1);\n            if (!id) return;\n            const el = document.getElementById(id);\n            if (el && el.getBoundingClientRect().top <= headerHeight + 100) {\n                a.setAttribute('aria-current', 'page');\n\n                if (this.current !== a && this.element) {\n                    this.element.scroll({\n                        top:\n                            a.offsetTop +\n                            a.offsetHeight / 2 -\n                            this.element.offsetHeight / 2,\n                        left:\n                            a.offsetLeft +\n                            a.offsetWidth / 2 -\n                            this.element.offsetWidth / 2,\n                        behavior: this.current ? 'smooth' : 'auto',\n                    });\n                }\n\n                this.current = a;\n                return true;\n            }\n        });\n    };\n\n    private links() {\n        return this.element.querySelectorAll<HTMLAnchorElement>('a[href*=\"#\"]');\n    }\n}\n"
  },
  {
    "path": "resources/js/controllers/similar-posts-controller.ts",
    "content": "import { Controller } from '@hotwired/stimulus';\nimport { FrameElement, TurboBeforeFrameRenderEvent } from '@hotwired/turbo';\nimport { debounce } from 'lodash-es';\n\n/**\n * Controller for displaying similar posts underneath the post title field.\n *\n * @internal\n */\nexport default class extends Controller<HTMLElement> {\n    static targets = ['submit', 'frame'];\n\n    declare readonly submitTarget?: HTMLButtonElement;\n\n    submit() {\n        this.submitTarget?.click();\n    }\n\n    frameTargetConnected(element: FrameElement) {\n        element.addEventListener('turbo:before-frame-render', (e) => {\n            element.hidden = !(e as TurboBeforeFrameRenderEvent).detail.newFrame\n                .children.length;\n        });\n    }\n\n    input = debounce(this.submit, 250);\n}\n"
  },
  {
    "path": "resources/js/controllers/suspend-duration-controller.ts",
    "content": "import { Controller } from '@hotwired/stimulus';\n\nexport default class extends Controller {\n    static targets = ['count', 'unit'];\n\n    declare readonly countTarget: HTMLInputElement;\n    declare readonly unitTarget: HTMLSelectElement;\n\n    connect() {\n        this.update();\n    }\n\n    update = () => {\n        const isIndefinite = this.unitTarget.value === 'indefinite';\n\n        this.countTarget.disabled = isIndefinite;\n\n        if (isIndefinite) {\n            this.countTarget.value = '';\n        } else if (!this.countTarget.value) {\n            this.countTarget.value = '7';\n        }\n    };\n}\n"
  },
  {
    "path": "resources/js/controllers/text-editor-controller.ts",
    "content": "import { subscribe as pasteMarkdown } from '@github/paste-markdown';\nimport { ActionEvent, Controller } from '@hotwired/stimulus';\nimport { PopupElement } from 'inclusive-elements';\nimport TextareaEditorModule from 'textarea-editor';\n\nconst TextareaEditor =\n    (TextareaEditorModule as any).default ?? TextareaEditorModule;\n\n/**\n * Controller for the <x-waterhole::text-editor> component.\n *\n * @internal\n */\nexport default class extends Controller {\n    static targets = ['input', 'preview', 'previewButton', 'hotkeyLabel'];\n\n    static values = {\n        formatUrl: String,\n    };\n\n    declare readonly inputTarget: HTMLTextAreaElement;\n    declare readonly previewTarget: HTMLDivElement;\n    declare readonly previewButtonTarget: HTMLButtonElement;\n\n    declare readonly formatUrlValue: string;\n\n    private editor?: typeof TextareaEditor;\n    private pasteMarkdown?: ReturnType<typeof pasteMarkdown>;\n\n    inputTargetConnected(input: HTMLInputElement) {\n        this.editor = new TextareaEditor(input);\n        this.pasteMarkdown = pasteMarkdown(input);\n    }\n\n    inputTargetDisconnected() {\n        this.pasteMarkdown?.unsubscribe();\n    }\n\n    hotkeyLabelTargetConnected(element: HTMLElement) {\n        const mac = navigator.userAgent.match(/Macintosh/);\n        element.innerText = element.innerText\n            .split('+')\n            .map((part) => {\n                if (part === 'Meta') return mac ? '⌘' : 'Ctrl';\n                if (part === 'Shift' && mac) return '⇧';\n                if (part === 'Alt' && mac) return '⌥';\n                if (part.length === 1) return part.toUpperCase();\n                return part;\n            })\n            .join(mac ? '' : '-');\n    }\n\n    format(e: ActionEvent) {\n        e.preventDefault();\n        this.editor?.toggle(e.params.format);\n    }\n\n    async togglePreview() {\n        if (!this.inputTarget || !this.previewTarget) return;\n\n        const previewing = !this.inputTarget.hidden;\n\n        this.inputTarget.hidden = previewing;\n        this.previewTarget.hidden = !previewing;\n        this.previewButtonTarget?.setAttribute(\n            'aria-pressed',\n            String(previewing),\n        );\n        this.element.classList.toggle('is-previewing', previewing);\n\n        if (!previewing) return;\n\n        this.previewTarget.setAttribute('aria-busy', 'true');\n        this.previewTarget.innerHTML = await Waterhole.fetch\n            .post(this.formatUrlValue, { body: this.inputTarget.value })\n            .text();\n        this.previewTarget.hidden = false;\n        this.previewTarget.setAttribute('aria-busy', 'false');\n    }\n\n    insertQuote(e: CustomEvent) {\n        if (!this.inputTarget || !this.editor) return;\n\n        let text = (this.inputTarget.selectionStart > 0 ? '\\n\\n' : '') + '> ';\n\n        this.editor.insert(\n            text + e.detail.text.replace(/\\n/g, '\\n> ') + '\\n\\n',\n        );\n    }\n\n    insertEmoji(e: CustomEvent) {\n        this.editor?.insert(e.detail.unicode || '');\n        (e.target as HTMLElement).closest<PopupElement>('ui-popup')!.open =\n            false;\n    }\n}\n"
  },
  {
    "path": "resources/js/controllers/theme-controller.ts",
    "content": "import { Controller } from '@hotwired/stimulus';\n\nconst STORAGE_KEY = 'theme';\n\n/**\n * Controller for the <x-waterhole::theme-selector> component.\n *\n * @internal\n */\nexport default class extends Controller {\n    connect() {\n        window\n            .matchMedia('(prefers-color-scheme: dark)')\n            .addEventListener('change', (e) => {\n                if (!localStorage.getItem(STORAGE_KEY)) {\n                    this.apply(e.matches ? 'dark' : 'light');\n                }\n            });\n\n        this.updateMenuItems();\n    }\n\n    set({ params: { name } }: any) {\n        if (!name) {\n            localStorage.removeItem(STORAGE_KEY);\n            this.apply(\n                matchMedia('(prefers-color-scheme: dark)').matches\n                    ? 'dark'\n                    : 'light',\n            );\n        } else {\n            localStorage.setItem(STORAGE_KEY, name);\n            this.apply(name);\n        }\n\n        this.updateMenuItems();\n        this.dispatch('change', { detail: { name } });\n    }\n\n    apply(name: 'dark' | 'light') {\n        document.documentElement.dataset.theme = name;\n    }\n\n    updateMenuItems() {\n        const saved = localStorage.getItem(STORAGE_KEY) || '';\n        this.element\n            .querySelectorAll('[data-theme-name-param]')\n            .forEach((el) => {\n                el.setAttribute(\n                    'aria-checked',\n                    el.getAttribute('data-theme-name-param') === saved\n                        ? 'true'\n                        : 'false',\n                );\n            });\n    }\n}\n"
  },
  {
    "path": "resources/js/controllers/truncated-controller.ts",
    "content": "import { Controller } from '@hotwired/stimulus';\n\n/**\n *\n *\n * @internal\n */\nexport default class extends Controller<HTMLElement> {\n    static targets = ['expander'];\n\n    declare readonly expanderTarget: HTMLButtonElement;\n\n    connect() {\n        if (this.element.scrollHeight > this.element.offsetHeight) {\n            this.expanderTarget.hidden = false;\n        }\n    }\n\n    expand() {\n        this.element.style.maxHeight = 'none';\n        this.expanderTarget.hidden = true;\n    }\n}\n"
  },
  {
    "path": "resources/js/controllers/turbo-frame-controller.ts",
    "content": "import { Controller } from '@hotwired/stimulus';\nimport { FrameElement } from '@hotwired/turbo';\n\n/**\n * Controller for some useful <turbo-frame> actions.\n */\nexport default class extends Controller<FrameElement> {\n    connect() {\n        if (!this.element.id) {\n            this.element.id = this.element.dataset.id || '';\n        }\n    }\n\n    reload() {\n        this.element.reload();\n    }\n\n    disable() {\n        this.element.disabled = true;\n    }\n\n    removeSrc() {\n        this.element.removeAttribute('src');\n    }\n}\n"
  },
  {
    "path": "resources/js/controllers/uploads-controller.ts",
    "content": "import { Controller } from '@hotwired/stimulus';\nimport TextareaEditorModule from 'textarea-editor';\n\nconst TextareaEditor =\n    (TextareaEditorModule as any).default ?? TextareaEditorModule;\n\n/**\n * Controller to power file uploads in the text editor.\n *\n * @internal\n */\nexport default class extends Controller<HTMLElement> {\n    static targets = ['input'];\n\n    static values = {\n        url: String,\n    };\n\n    declare readonly inputTarget: HTMLTextAreaElement;\n    declare readonly urlValue: string;\n\n    private editor?: typeof TextareaEditor;\n\n    connect() {\n        this.editor = new TextareaEditor(this.inputTarget);\n\n        this.inputTarget.addEventListener('drop', this.onDrop);\n        this.inputTarget.addEventListener('paste', this.onPaste);\n    }\n\n    disconnect() {\n        this.inputTarget.removeEventListener('drop', this.onDrop);\n        this.inputTarget.removeEventListener('paste', this.onPaste);\n    }\n\n    private onDrop = (e: DragEvent) => {\n        if (e.dataTransfer?.files.length) {\n            e.preventDefault();\n            Array.from(e.dataTransfer.files).forEach((file) =>\n                this.uploadFile(file),\n            );\n        }\n    };\n\n    private onPaste = (e: ClipboardEvent) => {\n        if (e.clipboardData?.files.length) {\n            e.preventDefault();\n            Array.from(e.clipboardData.files).forEach((file) =>\n                this.uploadFile(file),\n            );\n        }\n    };\n\n    chooseFiles() {\n        const input = document.createElement('input');\n        input.type = 'file';\n        input.multiple = true;\n        input.hidden = true;\n        document.body.appendChild(input);\n        input.addEventListener('change', () => {\n            if (input.files) {\n                Array.from(input.files).forEach((file) =>\n                    this.uploadFile(file),\n                );\n            }\n        });\n        input.click();\n        input.remove();\n    }\n\n    async uploadFile(file: File) {\n        const prefix = file.type.startsWith('image/') ? '!' : '';\n        const placeholder = `${prefix}[Uploading ${file.name}]()\\n`;\n        let replacement = '';\n\n        this.editor?.insert(placeholder);\n\n        const body = new FormData();\n        body.append('file', file);\n\n        try {\n            const data = await Waterhole.fetch\n                .post(this.urlValue, { body })\n                .json<{ url: string }>();\n\n            replacement = `${prefix}[${file.name}](${data.url})\\n`;\n        } catch (e) {}\n\n        const start = this.inputTarget.value.indexOf(placeholder);\n        if (start === -1 || !this.editor) return;\n\n        const delta = replacement.length - placeholder.length;\n        const range = this.editor.range();\n        this.editor\n            .range([start, start + placeholder.length])\n            .insert(replacement)\n            .range([range[0] + delta, range[1] + delta]);\n    }\n}\n"
  },
  {
    "path": "resources/js/controllers/watch-scroll-controller.ts",
    "content": "import { Controller } from '@hotwired/stimulus';\n\n/**\n *\n */\nexport default class extends Controller<HTMLElement> {\n    private observer?: ResizeObserver;\n\n    connect() {\n        this.element.addEventListener('scroll', this.onScroll, {\n            passive: true,\n        });\n\n        this.observer = new ResizeObserver(() => this.onScroll());\n        this.observer.observe(this.element);\n    }\n\n    disconnect() {\n        this.element.removeEventListener('scroll', this.onScroll);\n\n        this.observer?.disconnect();\n    }\n\n    private onScroll = () => {\n        const el = this.element;\n\n        el.classList.toggle('is-scrolled-down', el.scrollTop > 0);\n        el.classList.toggle('is-scrolled-right', el.scrollLeft > 0);\n        el.classList.toggle(\n            'is-scrolled-up',\n            el.scrollTop < el.scrollHeight - el.offsetHeight,\n        );\n        el.classList.toggle(\n            'is-scrolled-left',\n            el.scrollLeft < el.scrollWidth - el.offsetWidth,\n        );\n    };\n}\n"
  },
  {
    "path": "resources/js/controllers/watch-sticky-controller.ts",
    "content": "import { Controller } from '@hotwired/stimulus';\nimport StickyObserver from 'sticky-observer';\n\n/**\n * Controller to set up a StickyObserver.\n */\nexport default class extends Controller {\n    observer?: StickyObserver;\n\n    connect() {\n        this.observer = new StickyObserver(this.element, (stuck) => {\n            this.element.classList.toggle('is-stuck', stuck);\n            this.element.classList.toggle('is-unstuck', !stuck);\n        });\n    }\n\n    disconnect() {\n        this.observer?.stop();\n    }\n}\n"
  },
  {
    "path": "resources/js/cp/controllers/color-picker-controller.ts",
    "content": "import { Controller } from '@hotwired/stimulus';\n\n/**\n * Controller for the <x-waterhole::color-picker> component.\n *\n * Provides actions to show and hide the color picker on input focus/blur.\n * Also keeps the input, picker, and swatch in sync when the color is changed.\n *\n * @internal\n */\nexport default class extends Controller {\n    static targets = ['input', 'picker', 'swatch'];\n\n    declare readonly inputTarget: any;\n    declare readonly pickerTarget: any;\n    declare readonly swatchTarget: any;\n\n    private timeout?: number;\n\n    show() {\n        clearTimeout(this.timeout);\n        this.pickerTarget.hidden = false;\n    }\n\n    hide() {\n        clearTimeout(this.timeout);\n        this.timeout = window.setTimeout(\n            () => (this.pickerTarget.hidden = true),\n        );\n    }\n\n    colorChanged(e: CustomEvent) {\n        this.inputTarget.color = e.detail.value;\n        this.pickerTarget.color = e.detail.value;\n        this.swatchTarget.style.backgroundColor = e.detail.value;\n    }\n}\n"
  },
  {
    "path": "resources/js/cp/controllers/filter-input-controller.ts",
    "content": "import Combobox from '@github/combobox-nav';\nimport { Controller } from '@hotwired/stimulus';\nimport { set } from 'text-field-edit';\n\nconst RE_TOKEN = /([^\\s\"]*)\"([^\"]*)(?:\"|$)|[^\\s\"]+/gi;\n\n/**\n * Controller to hook up a filter input.\n *\n * A \"filter input\" is a combobox which allows entering a string of\n * space-separated \"tokens\". These tokens are suggested in a list, and the list\n * of suggestions is filtered according to the current token.\n *\n * For an example usage, see the CP users page.\n */\nexport default class extends Controller {\n    static targets = ['input', 'list'];\n\n    declare readonly inputTarget: HTMLInputElement;\n    declare readonly listTarget: HTMLElement;\n\n    combobox?: Combobox;\n\n    connect() {\n        this.combobox = new Combobox(this.inputTarget, this.listTarget);\n    }\n\n    focus() {\n        this.combobox?.start();\n        this.update();\n    }\n\n    blur() {\n        this.combobox?.stop();\n        this.listTarget!.hidden = true;\n    }\n\n    tokens() {\n        const matches = this.inputTarget.value.matchAll(RE_TOKEN);\n        return Array.from(matches).reverse();\n    }\n\n    currentToken() {\n        const start = this.inputTarget.selectionStart || 0;\n        return this.tokens().find(\n            (match) =>\n                match.index !== undefined &&\n                match.index < start &&\n                match.index + match[0].length >= start,\n        );\n    }\n\n    update() {\n        const tokens = this.tokens();\n        const token = this.currentToken();\n        const query = token && token[0].toLowerCase();\n        const children = Array.from(this.listTarget.children) as HTMLElement[];\n        const prefixes: string[] = [];\n\n        // Loop through the suggested and show/hide them based on what's entered\n        // as the current token (the \"query\"). Hide tokens that are already\n        // present in the search string. If the query is blank, only show the\n        // first instance of a particular token prefix. Otherwise, only show\n        // tokens that start with the query.\n        children.forEach((el) => {\n            const text =\n                (el.dataset.value || el.textContent)?.trim().toLowerCase() ||\n                '';\n\n            if (tokens.some((token) => token[0].toLowerCase() === text)) {\n                el.hidden = true;\n            } else if (query) {\n                el.hidden =\n                    !text.startsWith(query) ||\n                    query.includes(':') === text.endsWith(':');\n            } else {\n                el.hidden = prefixes.some((prefix) => text.startsWith(prefix));\n                prefixes.push(text);\n            }\n        });\n\n        this.listTarget.hidden = !children.some((el) => !el.hidden);\n    }\n\n    commit(e: CustomEvent) {\n        const el = e.target as HTMLElement;\n        const token = this.currentToken();\n        const replacement = (el.dataset.value || el.textContent)?.trim() || '';\n\n        set(\n            this.inputTarget,\n            this.inputTarget.value.slice(0, token?.index) +\n                replacement +\n                (replacement.endsWith(':') ? '' : ' '),\n        );\n    }\n\n    preventBlur(e: Event) {\n        e.preventDefault();\n    }\n}\n"
  },
  {
    "path": "resources/js/cp/controllers/form-controller.ts",
    "content": "import { Controller } from '@hotwired/stimulus';\n\n/**\n * Controller to submit a form.\n *\n * An example of where this is used is on the CP Structure page's\n * drag-and-drop ordering interface, in which the order-saving form is\n * targeted, and the submit action is triggered when dragging ends.\n */\nexport default class extends Controller {\n    static targets = ['form'];\n\n    declare readonly formTarget: HTMLFormElement;\n\n    submit() {\n        // Request submission of the form after a tick so that other event\n        // listeners have a chance to run first.\n        setTimeout(() => {\n            this.formTarget.requestSubmit();\n        });\n    }\n}\n"
  },
  {
    "path": "resources/js/cp/controllers/icon-picker-controller.ts",
    "content": "import { Controller } from '@hotwired/stimulus';\nimport { Picker } from 'emoji-picker-element';\nimport { PopupElement } from 'inclusive-elements';\n\n/**\n * Controller for the <x-waterhole::icon-picker> component.\n *\n * @internal\n */\nexport default class extends Controller {\n    static targets = ['preview', 'form', 'emoji'];\n\n    declare readonly previewTarget: HTMLElement;\n    declare readonly formTarget: HTMLElement;\n    declare readonly emojiPickerTarget: Picker;\n\n    change() {\n        this.previewTarget.hidden = true;\n        this.formTarget.hidden = false;\n    }\n\n    emojiTargetConnected(el: HTMLElement) {\n        const input = el.querySelector('input')!;\n        const popup = el.querySelector<PopupElement>('ui-popup')!;\n        const button = el.querySelector('button')!;\n        const picker = el.querySelector('emoji-picker')!;\n\n        input.type = 'hidden';\n        popup.hidden = false;\n\n        picker.addEventListener('emoji-click', (e) => {\n            input.value = e.detail.unicode || '';\n            button.innerHTML = input.value;\n            popup.open = false;\n        });\n    }\n}\n"
  },
  {
    "path": "resources/js/cp/controllers/incremental-search-controller.ts",
    "content": "import { Controller } from '@hotwired/stimulus';\nimport { debounce } from 'lodash-es';\n\n/**\n * Controller to power incremental search.\n *\n * Apply this controller to an <input> element and call the `input` action to\n * debounce a submission of the input's form. The only exception is when the\n * input is empty, in which case the form will submit immediately.\n *\n * For an example usage, see the CP Users page.\n */\nexport default class extends Controller<HTMLInputElement> {\n    input(e: InputEvent) {\n        if ((e.target as HTMLInputElement).value) {\n            this.debouncedSubmit();\n        } else {\n            this.submit();\n        }\n    }\n\n    submit() {\n        this.element.form?.requestSubmit();\n    }\n\n    debouncedSubmit = debounce(this.submit, 250);\n}\n"
  },
  {
    "path": "resources/js/cp/controllers/line-chart-controller.ts",
    "content": "import { Controller } from '@hotwired/stimulus';\nimport uPlot from 'uplot';\nimport 'uplot/dist/uPlot.min.css';\n\n/**\n * Controller for the line-chart dashboard widget.\n *\n * @internal\n */\nexport default class extends Controller {\n    static targets = [\n        'table',\n        'chart',\n        'summary',\n        'legend',\n        'legendAmount',\n        'legendPeriod',\n        'axis',\n    ];\n\n    declare readonly tableTarget: HTMLTableElement;\n    declare readonly chartTarget: HTMLElement;\n    declare readonly summaryTarget: HTMLElement;\n    declare readonly legendTarget: HTMLElement;\n    declare readonly legendAmountTarget: HTMLElement;\n    declare readonly legendPeriodTarget: HTMLElement;\n    declare readonly axisTarget: HTMLElement;\n\n    observer?: ResizeObserver;\n    uplot?: uPlot;\n\n    private getSize() {\n        return {\n            width: this.chartTarget.offsetWidth,\n            height: this.chartTarget.offsetHeight,\n        };\n    }\n\n    connect() {\n        this.tableTarget.hidden = true;\n        this.chartTarget.hidden = false;\n        this.axisTarget.hidden = false;\n\n        this.observer = new ResizeObserver(() => {\n            this.uplot?.setSize(this.getSize());\n        });\n\n        this.observer.observe(this.chartTarget!);\n\n        const cs = getComputedStyle(document.documentElement);\n\n        const options: uPlot.Options = {\n            ...this.getSize(),\n            padding: [1, 1, 1, 1],\n            series: [\n                {},\n                {\n                    stroke: cs.getPropertyValue('--color-stroke'),\n                    width: 2,\n                    points: { show: false },\n                },\n                {\n                    stroke: cs.getPropertyValue('--color-accent'),\n                    width: 2,\n                    points: { show: false },\n                },\n            ],\n            cursor: {\n                y: false,\n            },\n            axes: [{ show: false }, { show: false }],\n            scales: {\n                y: { range: (u, dataMin, dataMax) => [dataMin, dataMax] },\n            },\n            select: {\n                show: false,\n            } as uPlot.Select, // bug\n            legend: {\n                show: false,\n            },\n            hooks: {\n                init: [\n                    (u) => {\n                        u.over.addEventListener('mouseenter', () => {\n                            this.summaryTarget.hidden = true;\n                            this.legendTarget.hidden = false;\n                        });\n                        u.over.addEventListener('mouseleave', () => {\n                            this.summaryTarget.hidden = false;\n                            this.legendTarget.hidden = true;\n                        });\n                    },\n                ],\n                setCursor: [\n                    (u) => {\n                        const { idx } = u.cursor;\n                        this.legendAmountTarget.textContent =\n                            typeof idx === 'number'\n                                ? uPlot.fmtNum(u.data[2][idx] || 0)\n                                : '';\n                        this.legendPeriodTarget.textContent =\n                            typeof idx === 'number' ? ths[idx].textContent : '';\n                    },\n                ],\n            },\n        };\n\n        const ths = Array.from(\n            this.tableTarget.querySelectorAll<HTMLElement>('thead th'),\n        ).slice(1);\n\n        const data: uPlot.AlignedData = [\n            ths.map((th) => Number(th.dataset.timestamp)),\n            ...Array.from(this.tableTarget.querySelectorAll('tbody tr'))\n                .reverse()\n                .map((tr) =>\n                    Array.from(tr.querySelectorAll('td')).map((td) =>\n                        Number(td.textContent),\n                    ),\n                ),\n        ];\n\n        this.uplot = new uPlot(options, data, this.chartTarget!);\n    }\n\n    disconnect() {\n        this.observer?.disconnect();\n        this.uplot?.destroy();\n    }\n}\n"
  },
  {
    "path": "resources/js/cp/controllers/permission-grid-controller.ts",
    "content": "import { Controller } from '@hotwired/stimulus';\n\n/**\n * Controller for the <x-waterhole::permission-grid> component.\n *\n * @internal\n */\nexport default class extends Controller<HTMLElement> {\n    private disabled?: HTMLInputElement[];\n\n    connect() {\n        this.disabled = Array.from(\n            this.element.querySelectorAll('input:disabled'),\n        );\n        this.update();\n\n        this.element.addEventListener('click', this.click);\n        this.element.addEventListener('mouseover', this.mouseover);\n        this.element.addEventListener('mouseout', this.reset);\n    }\n\n    disconnect() {\n        this.element.removeEventListener('click', this.click);\n        this.element.removeEventListener('mouseover', this.mouseover);\n        this.element.removeEventListener('mouseout', this.reset);\n    }\n\n    private mouseover = (e: MouseEvent) => {\n        const target = e.target as HTMLElement;\n\n        if (target.closest('thead th')) {\n            const index = Array.from(target.parentElement!.children).indexOf(\n                target,\n            );\n            this.element\n                .querySelector('colgroup')!\n                .children[index].classList.add('is-highlighted');\n            this.element.style.cursor = 'pointer';\n        }\n\n        if (target.closest('tbody th')) {\n            target.closest('tr')!.classList.add('is-highlighted');\n            this.element.style.cursor = 'pointer';\n        }\n    };\n\n    private reset = () => {\n        this.element\n            .querySelectorAll('col, tr')\n            .forEach((el) => el.classList.remove('is-highlighted'));\n        this.element.style.cursor = '';\n    };\n\n    private click = (e: MouseEvent) => {\n        const target = e.target as HTMLElement;\n\n        if (target.closest('thead th')) {\n            const index = Array.from(target.parentElement!.children).indexOf(\n                target,\n            );\n            const checkboxes = Array.from(\n                this.element.querySelectorAll<HTMLInputElement>(\n                    `tbody tr td:nth-child(${\n                        index + 1\n                    }) input[type=\"checkbox\"]`,\n                ),\n            ).filter((checkbox) => !this.disabled?.includes(checkbox));\n\n            const checked = !checkboxes.find(\n                (checkbox) =>\n                    !checkbox.disabled &&\n                    checkbox.getAttribute('aria-disabled') !== 'true',\n            )?.checked;\n            checkboxes.forEach((el) => (el.checked = checked));\n        }\n\n        if (target.closest('tbody th')) {\n            const checkboxes = Array.from(\n                target\n                    .closest('tr')!\n                    .querySelectorAll<HTMLInputElement>(\n                        `td input[type=\"checkbox\"]`,\n                    ),\n            ).filter((checkbox) => !this.disabled?.includes(checkbox));\n\n            const checked = !checkboxes.find(\n                (checkbox) =>\n                    !checkbox.disabled &&\n                    checkbox.getAttribute('aria-disabled') !== 'true',\n            )?.checked;\n            checkboxes.forEach((el) => (el.checked = checked));\n        }\n\n        this.update();\n    };\n\n    update() {\n        this.element\n            .querySelectorAll<HTMLInputElement>(\n                'tbody td input[type=\"checkbox\"]',\n            )\n            .forEach((checkbox) => {\n                if (!this.disabled?.includes(checkbox)) {\n                    checkbox.disabled = false;\n                }\n            });\n\n        this.element\n            .querySelectorAll<HTMLInputElement>(\n                '[data-implied-by], [data-depends-on]',\n            )\n            .forEach((el) => {\n                el.dataset.impliedBy\n                    ?.trim()\n                    .split(/\\s+/)\n                    .filter(Boolean)\n                    .forEach((name) => {\n                        const ref = document.querySelector<HTMLInputElement>(\n                            `[name=\"${name}\"]:last-of-type`,\n                        );\n                        if (ref && ref.checked) {\n                            el.checked = true;\n                            el.disabled = true;\n                        }\n                    });\n\n                el.dataset.dependsOn\n                    ?.trim()\n                    .split(/\\s+/)\n                    .filter(Boolean)\n                    .forEach((name) => {\n                        const ref = document.querySelector<HTMLInputElement>(\n                            `[name=\"${name}\"]:last-of-type`,\n                        );\n                        if (ref && !ref.checked) {\n                            el.checked = false;\n                            el.disabled = true;\n                        }\n                    });\n            });\n    }\n}\n"
  },
  {
    "path": "resources/js/cp/controllers/slugger-controller.ts",
    "content": "import { Controller } from '@hotwired/stimulus';\nimport { slug } from '../../utils';\n\n/**\n * Controller to automatically populate a slug field as you type in a name.\n *\n * Call the `updateName` action on the name input, and the `updateSlug` action\n * on the slug input. Target the slug input as `slug`, and any elements that\n * should mirror the slug value (eg. a URL preview) as `mirror`.\n *\n * For an example usage, see the `ChannelName` and `ChannelSlug` form fields.\n */\nexport default class extends Controller {\n    static targets = ['slug', 'mirror'];\n\n    declare readonly slugTarget: HTMLInputElement;\n    declare readonly mirrorTargets: HTMLElement[];\n\n    locked: boolean = false;\n\n    updateName(e: Event) {\n        const input = e.target as HTMLInputElement;\n\n        if (!this.locked) {\n            this.slugTarget.value = slug(input.value);\n            this.mirror();\n        }\n    }\n\n    updateSlug(e: Event) {\n        const input = e.target as HTMLInputElement;\n\n        this.mirror();\n\n        this.locked = Boolean(input.value);\n    }\n\n    mirror() {\n        this.mirrorTargets.forEach((el) => {\n            el.textContent = this.slugTarget.value;\n        });\n    }\n}\n"
  },
  {
    "path": "resources/js/cp/controllers/sortable-controller.ts",
    "content": "import { Controller } from '@hotwired/stimulus';\nimport { DragDropManager } from '@dnd-kit/dom';\nimport { Sortable } from '@dnd-kit/dom/sortable';\nimport { generateUniqueId } from '@dnd-kit/dom/utilities';\nimport { RestrictToVerticalAxis } from '@dnd-kit/abstract/modifiers';\n\n/**\n * A controller to hook up a dnd-kit sortable instance.\n */\nexport default class extends Controller<HTMLElement> {\n    static targets = ['container', 'orderInput'];\n\n    declare readonly containerTargets: HTMLElement[];\n\n    declare readonly hasOrderInputTarget: boolean;\n    declare readonly orderInputTarget: HTMLInputElement;\n\n    private manager?: DragDropManager;\n    private sortables: Sortable[] = [];\n\n    connect() {\n        this.manager = new DragDropManager({\n            // @ts-ignore\n            modifiers: [RestrictToVerticalAxis],\n        });\n        this.manager.monitor.addEventListener('dragend', this.onDragEnd);\n\n        this.refreshSortables();\n    }\n\n    disconnect() {\n        this.teardownSortables();\n\n        this.manager?.monitor.removeEventListener('dragend', this.onDragEnd);\n        this.manager?.destroy?.();\n        this.manager = undefined;\n    }\n\n    containerTargetConnected() {\n        this.refreshSortables();\n    }\n\n    containerTargetDisconnected() {\n        this.refreshSortables();\n    }\n\n    private refreshSortables() {\n        this.teardownSortables();\n\n        if (!this.manager) return;\n\n        this.containerTargets.forEach((container, group) => {\n            const items = Array.from(\n                container.querySelectorAll<HTMLElement>('[data-id]'),\n            );\n\n            items.forEach((item, index) => {\n                const sortable = new Sortable(\n                    {\n                        id: item.dataset.id || generateUniqueId('item'),\n                        index,\n                        group,\n                        element: item,\n                        handle:\n                            item.querySelector<HTMLElement>('[data-handle]') ||\n                            undefined,\n                    },\n                    this.manager,\n                );\n\n                this.sortables.push(sortable);\n            });\n        });\n    }\n\n    private teardownSortables() {\n        this.sortables.forEach((sortable) => sortable.destroy());\n        this.sortables = [];\n    }\n\n    private onDragEnd = () => this.updateOrder();\n\n    private updateOrder() {\n        if (!this.hasOrderInputTarget) {\n            this.dispatch('update');\n            return;\n        }\n\n        const result = this.containerTargets.flatMap((list, i) =>\n            Array.from(list.querySelectorAll<HTMLElement>('[data-id]')).map(\n                (el) => {\n                    return { id: el.dataset.id, listIndex: i };\n                },\n            ),\n        );\n\n        if (result) {\n            this.orderInputTarget.value = JSON.stringify(result);\n            this.dispatch('update');\n        }\n    }\n}\n"
  },
  {
    "path": "resources/js/cp/index.ts",
    "content": "import '../../css/cp/app.css';\nimport 'vanilla-colorful/hex-alpha-color-picker.js';\nimport 'vanilla-colorful/hex-input.js';\nimport { buildStimulusDefinitions } from '../utils';\n\nwindow.Stimulus.load(\n    buildStimulusDefinitions(\n        import.meta.glob('./controllers/**/*.ts', { eager: true }),\n    ),\n);\n"
  },
  {
    "path": "resources/js/elements/turbo-echo-stream-tag.ts",
    "content": "import { connectStreamSource, disconnectStreamSource } from '@hotwired/turbo';\n\nconst subscribeTo = (type: string, channel: string) => {\n    if (type === 'presence') {\n        return window.Echo.join(channel);\n    }\n\n    return (window.Echo as any)[type](channel);\n};\n\nexport class TurboEchoStreamSourceElement extends HTMLElement {\n    subscription: any;\n\n    async connectedCallback() {\n        connectStreamSource(this);\n        this.subscription = subscribeTo(this.type, this.channel).listenToAll(\n            (event: string, e: any) => {\n                this.dispatchMessageEvent(e.streams);\n            },\n        );\n    }\n\n    disconnectedCallback() {\n        disconnectStreamSource(this);\n        if (this.subscription) {\n            window.Echo.leave(this.channel);\n            this.subscription = null;\n        }\n    }\n\n    dispatchMessageEvent(data: any) {\n        const event = new MessageEvent('message', { data });\n        return this.dispatchEvent(event);\n    }\n\n    get channel() {\n        return this.getAttribute('channel') || '';\n    }\n\n    get type() {\n        return this.getAttribute('type') || 'private';\n    }\n}\n"
  },
  {
    "path": "resources/js/emoji.ts",
    "content": "import { Controller } from '@hotwired/stimulus';\nimport 'emoji-picker-element';\nimport { PopupElement } from 'inclusive-elements';\n\nclass EmojiPickerController extends Controller<PopupElement> {\n    connect() {\n        const picker = this.element.querySelector('emoji-picker')!;\n        const pickerRoot = picker.shadowRoot!;\n\n        picker.addEventListener('focusout', (e) => e.stopPropagation());\n\n        this.element.addEventListener('open', () => {\n            pickerRoot.querySelector('input')?.focus();\n        });\n    }\n}\n\nwindow.Stimulus.register('emoji-picker', EmojiPickerController);\n"
  },
  {
    "path": "resources/js/env.d.ts",
    "content": "declare module 'textarea-editor' {\n    export default class TextareaEditor {\n        constructor(el: HTMLElement);\n        range(): [number, number];\n        range(range: [number, number]): TextareaEditor;\n        insert(text: string): TextareaEditor;\n        toggle(format: string | object, ...args: any): TextareaEditor;\n    }\n}\n"
  },
  {
    "path": "resources/js/highlight.ts",
    "content": "import { StreamElement } from '@hotwired/turbo';\nimport hljs from 'highlight.js/lib/common';\n\n(window as any).hljs = hljs;\n\ndocument.addEventListener('turbo:load', () => hljs.highlightAll());\ndocument.addEventListener('turbo:frame-load', () => hljs.highlightAll());\ndocument.addEventListener('turbo:before-stream-render', (e) => {\n    const { detail } = e as CustomEvent;\n    const fallback = detail.render;\n\n    detail.render = (stream: StreamElement) => {\n        fallback(stream);\n        hljs.highlightAll();\n    };\n});\n"
  },
  {
    "path": "resources/js/index.ts",
    "content": "import '../css/global/app.css';\nimport '@github/relative-time-element';\nimport { Application } from '@hotwired/stimulus';\nimport { AlertsElement } from 'inclusive-elements';\nimport ky from 'ky';\n\nimport './bootstrap/custom-elements';\nimport './bootstrap/document-title';\nimport './bootstrap/echo';\nimport './bootstrap/hotkeys';\nimport './bootstrap/turbo';\nimport { buildStimulusDefinitions, getCookie } from './utils';\n\ndeclare global {\n    const Waterhole: Waterhole;\n\n    interface Window {\n        Stimulus: Application;\n        Waterhole: Waterhole;\n    }\n}\n\nexport interface Waterhole {\n    userId: number;\n    debug: boolean;\n    alerts: AlertsElement;\n    fetch: typeof ky;\n    fetchError: (response?: Response) => void;\n    documentTitle: DocumentTitle;\n    echoConfig: any;\n}\n\nObject.defineProperty(Waterhole, 'alerts', {\n    get: () => document.getElementById('alerts'),\n});\n\nwindow.Stimulus = Application.start();\n\nwindow.Stimulus.load(\n    buildStimulusDefinitions(\n        import.meta.glob('./controllers/**/*.ts', { eager: true }),\n    ),\n);\n\nWaterhole.fetch = ky.create({\n    headers: { 'X-XSRF-TOKEN': getCookie('XSRF-TOKEN') || undefined },\n    hooks: {\n        beforeError: [\n            (error) => {\n                Waterhole.fetchError(error.response);\n                return error;\n            },\n        ],\n    },\n});\n"
  },
  {
    "path": "resources/js/utils.ts",
    "content": "/**\n * Determine whether the user is trying to open a link in a new tab.\n */\nexport function shouldOpenInNewTab(e: MouseEvent): boolean {\n    return (\n        e.altKey ||\n        e.ctrlKey ||\n        e.metaKey ||\n        e.shiftKey ||\n        (e.button !== undefined && e.button !== 0)\n    );\n}\n\n/**\n * Determine if an element is currently visible in the viewport.\n */\nexport function isElementInViewport(\n    el: HTMLElement,\n    proportion: number = 1,\n): boolean {\n    const rect = el.getBoundingClientRect();\n\n    return (\n        -rect.top / rect.height < proportion &&\n        (rect.bottom - window.innerHeight) / rect.height < proportion\n    );\n}\n\n/**\n * Get the height of the page header.\n */\nexport function getHeaderHeight(): number {\n    return document.getElementById('header')?.offsetHeight || 0;\n}\n\n/**\n * Create a slug out of the given string. Non-alphanumeric characters are\n * converted to hyphens.\n */\nexport function slug(string: string): string {\n    return string\n        .toLowerCase()\n        .replace(/[^a-z0-9]/gi, '-')\n        .replace(/-+/g, '-')\n        .replace(/-$|^-/g, '');\n}\n\n/**\n * Clone a <template> element's content.\n */\nexport function cloneFromTemplate(id: string): HTMLElement {\n    const template = document.getElementById(id) as HTMLTemplateElement;\n    return template?.content?.firstElementChild?.cloneNode(true) as HTMLElement;\n}\n\n/**\n * Get a cookie by name.\n */\nexport function getCookie(name: string): string | null {\n    const match = document.cookie.match(\n        new RegExp('(^|;\\\\s*)(' + name + ')=([^;]*)'),\n    );\n    return match ? decodeURIComponent(match[3]) : null;\n}\n\n/**\n * Build Stimulus definitions from an eager import.meta.glob map.\n */\nexport function buildStimulusDefinitions(\n    controllers: Record<string, { default: any }>,\n): { identifier: string; controllerConstructor: any }[] {\n    return Object.entries(controllers).map(([path, module]) => {\n        const identifier = path\n            .match(/\\.\\/controllers\\/(.*)\\.ts$/)![1]\n            .replace(/\\//g, '--')\n            .replace(/_/g, '-')\n            .replace(/-controller$/, '');\n\n        return { identifier, controllerConstructor: module.default };\n    });\n}\n"
  },
  {
    "path": "resources/views/actions/confirm.blade.php",
    "content": "<x-waterhole::layout :title=\"__('waterhole::system.confirm-action-title')\">\n    <div class=\"container section\">\n        <turbo-frame id=\"modal\">\n            <x-waterhole::dialog\n                :aria-label=\"__('waterhole::system.confirm-action-title')\"\n                class=\"dialog--sm confirm-action\"\n            >\n                <form action=\"{{ route('waterhole.actions.store') }}\" method=\"POST\">\n                    @csrf\n\n                    <input type=\"hidden\" name=\"action_class\" value=\"{{ get_class($action) }}\" />\n                    <input type=\"hidden\" name=\"actionable\" value=\"{{ $actionable }}\" />\n                    <input\n                        type=\"hidden\"\n                        name=\"return\"\n                        value=\"{{ old('return', request('return', url()->previous())) }}\"\n                    />\n\n                    @foreach ($models as $model)\n                        <input type=\"hidden\" name=\"id[]\" value=\"{{ $model->getKey() }}\" />\n                    @endforeach\n\n                    <div class=\"stack gap-xl\">\n                        <x-waterhole::validation-errors />\n\n                        @if ($isSimpleContent = is_string($content = $action->confirm($models)) || is_array($content))\n                            <div class=\"content\">\n                                @foreach (Arr::wrap($content) as $paragraph)\n                                    <p @if ($loop->first) class=\"h4\" @endif>{{ $paragraph }}</p>\n                                @endforeach\n                            </div>\n                        @else\n                            <div>{{ $content }}</div>\n                        @endif\n\n                        <div class=\"row gap-xs wrap justify-end\">\n                            <a\n                                href=\"{{ old('return', request('return', url()->previous())) }}\"\n                                class=\"btn\"\n                                data-action=\"modal#hide\"\n                            >\n                                {{ __('waterhole::system.cancel-button') }}\n                            </a>\n\n                            <button\n                                type=\"submit\"\n                                name=\"confirmed\"\n                                value=\"1\"\n                                class=\"btn {{ $action->destructive ? 'bg-danger' : 'bg-accent' }} btn--wide\"\n                                @if ($isSimpleContent) autofocus @endif\n                            >\n                                {{ $action->confirmButton($models) }}\n                            </button>\n                        </div>\n                    </div>\n                </form>\n            </x-waterhole::dialog>\n        </turbo-frame>\n    </div>\n</x-waterhole::layout>\n"
  },
  {
    "path": "resources/views/actions/menu.blade.php",
    "content": "<x-waterhole::layout>\n    <div class=\"section container\">\n        <div class=\"dialog dialog--sm dialog__body\">\n            <turbo-frame id=\"actions\">\n                <form action=\"{{ route('waterhole.actions.store') }}\" method=\"POST\">\n                    @csrf\n                    <input type=\"hidden\" name=\"actionable\" value=\"{{ request('actionable') }}\" />\n                    <input type=\"hidden\" name=\"id[]\" value=\"{{ request('id') }}\" />\n                    @if ($return = request('return'))\n                        <input type=\"hidden\" name=\"return\" value=\"{{ $return }}\" />\n                    @endif\n\n                    @foreach ($actions as $action)\n                        @if ($action instanceof Waterhole\\Actions\\Action)\n                            {{\n                                $action->render(\n                                    $models,\n                                    [\n                                        'class' => 'menu-item',\n                                        'role' => 'menuitem',\n                                        'data-turbo-frame' => '_parent',\n                                    ],\n                                    ellipsis: true,\n                                )\n                            }}\n                        @else\n                            {{ $action->render() }}\n                        @endif\n                    @endforeach\n                </form>\n            </turbo-frame>\n        </div>\n    </div>\n</x-waterhole::layout>\n"
  },
  {
    "path": "resources/views/auth/confirm-password.blade.php",
    "content": "<x-waterhole::layout :title=\"__('waterhole::auth.confirm-password-title')\">\n    <div class=\"container section\">\n        <x-waterhole::dialog\n            :title=\"__('waterhole::auth.confirm-password-title')\"\n            class=\"dialog--sm\"\n        >\n            {{--\n                Opt-out of Turbo so that any fragment that may be present in the\n                redirect URL will be followed.\n            --}}\n            <form method=\"POST\" action=\"{{ route('waterhole.confirm-password') }}\">\n                @csrf\n\n                <div class=\"stack gap-xl\">\n                    <p class=\"content\">\n                        {{ __('waterhole::auth.confirm-password-introduction') }}\n                    </p>\n\n                    <x-waterhole::field\n                        name=\"password\"\n                        :label=\"__('waterhole::auth.password-label')\"\n                    >\n                        <input\n                            type=\"password\"\n                            id=\"{{ $component->id }}\"\n                            name=\"password\"\n                            required\n                            autocomplete=\"current-password\"\n                            autofocus\n                        />\n                    </x-waterhole::field>\n\n                    <button type=\"submit\" class=\"btn bg-accent full-width\">\n                        {{ __('waterhole::auth.confirm-password-submit') }}\n                    </button>\n                </div>\n            </form>\n        </x-waterhole::dialog>\n    </div>\n</x-waterhole::layout>\n"
  },
  {
    "path": "resources/views/auth/forgot-password.blade.php",
    "content": "<x-waterhole::layout :title=\"__('waterhole::auth.forgot-password-title')\">\n    <div class=\"section\">\n        <x-waterhole::dialog\n            :title=\"__('waterhole::auth.forgot-password-title')\"\n            class=\"dialog--sm\"\n        >\n            <div class=\"stack gap-lg\">\n                <p class=\"content\">\n                    {{ __('waterhole::auth.forgot-password-introduction') }}\n                </p>\n\n                @if (session('status'))\n                    <x-waterhole::alert type=\"success\">\n                        {{ session('status') }}\n                    </x-waterhole::alert>\n                @else\n                    <form action=\"{{ route('waterhole.forgot-password') }}\" method=\"POST\">\n                        @csrf\n\n                        <div class=\"stack gap-lg\">\n                            <x-waterhole::field\n                                name=\"email\"\n                                :label=\"__('waterhole::auth.email-label')\"\n                            >\n                                <input\n                                    type=\"email\"\n                                    id=\"{{ $component->id }}\"\n                                    name=\"email\"\n                                    value=\"{{ old('email') }}\"\n                                    required\n                                    autofocus\n                                />\n                            </x-waterhole::field>\n\n                            <button type=\"submit\" class=\"btn bg-accent block\">\n                                {{ __('waterhole::auth.forgot-password-submit') }}\n                            </button>\n                        </div>\n                    </form>\n                @endif\n            </div>\n        </x-waterhole::dialog>\n    </div>\n</x-waterhole::layout>\n"
  },
  {
    "path": "resources/views/auth/login.blade.php",
    "content": "<x-waterhole::layout :title=\"__('waterhole::auth.login-title')\">\n    <div class=\"container section\">\n        <x-waterhole::dialog :title=\"__('waterhole::auth.login-title')\" class=\"dialog--sm\">\n            {{--\n                Opt-out of Turbo so that any fragment that may be present in the\n                redirect URL will be followed. Also, redirect URL may be external.\n            --}}\n            <form\n                action=\"{{ route('waterhole.login') }}\"\n                data-controller=\"login\"\n                data-turbo=\"false\"\n                method=\"POST\"\n            >\n                @csrf\n\n                <div class=\"stack gap-xl\">\n                    @section('email')\n                        <x-waterhole::field\n                            name=\"email\"\n                            :label=\"__('waterhole::auth.email-label')\"\n                        >\n                            <input\n                                type=\"email\"\n                                id=\"{{ $component->id }}\"\n                                name=\"email\"\n                                value=\"{{ old('email') }}\"\n                                required\n                                autofocus\n                            />\n                        </x-waterhole::field>\n                    @endsection\n\n                    @section('password')\n                        <x-waterhole::field\n                            name=\"password\"\n                            :label=\"__('waterhole::auth.password-label')\"\n                        >\n                            <input\n                                type=\"password\"\n                                id=\"{{ $component->id }}\"\n                                name=\"password\"\n                                required\n                                autocomplete=\"current-password\"\n                            />\n                        </x-waterhole::field>\n\n                        <div class=\"row justify-between gap-sm wrap\">\n                            <label for=\"remember_me\" class=\"choice\">\n                                <input id=\"remember_me\" type=\"checkbox\" name=\"remember\" />\n                                {{ __('waterhole::auth.remember-me-label') }}\n                            </label>\n\n                            <a href=\"{{ route('waterhole.forgot-password') }}\" data-turbo=\"true\">\n                                {{ __('waterhole::auth.forgot-password-link') }}\n                            </a>\n                        </div>\n                    @endsection\n\n                    @section('submit')\n                        <button type=\"submit\" class=\"btn bg-accent full-width\">\n                            {{ __('waterhole::auth.login-submit') }}\n                        </button>\n                    @endsection\n\n                    @section('sign-up-link')\n                        @if (Route::has('waterhole.register'))\n                            <p class=\"text-center\">\n                                {{ __('waterhole::auth.login-register-prompt') }}\n                                <a href=\"{{ route('waterhole.register') }}\" data-turbo=\"true\">\n                                    {{ __('waterhole::auth.login-register-link') }}\n                                </a>\n                            </p>\n                        @endif\n                    @endsection\n\n                    @components(Waterhole\\Extend\\Ui\\LoginPage::class)\n                </div>\n            </form>\n        </x-waterhole::dialog>\n    </div>\n</x-waterhole::layout>\n"
  },
  {
    "path": "resources/views/auth/register.blade.php",
    "content": "<x-waterhole::layout :title=\"__('waterhole::auth.register-title')\">\n    <div class=\"container section\">\n        <x-waterhole::dialog :title=\"__('waterhole::auth.register-title')\" class=\"dialog--sm\">\n            {{--\n                Opt-out of Turbo so that any fragment that may be present in the\n                redirect URL will be followed. Also, redirect URL may be external.\n            --}}\n            <form\n                action=\"{{ route('waterhole.register.submit') }}\"\n                data-turbo=\"false\"\n                method=\"POST\"\n            >\n                @csrf\n\n                @if (request('payload'))\n                    <input type=\"hidden\" name=\"payload\" value=\"{{ request('payload') }}\" />\n                @endif\n\n                <div class=\"stack gap-xl\">\n                    <x-waterhole::validation-errors />\n\n                    @unless ($form->payload)\n                        <x-waterhole::auth-buttons />\n                    @endunless\n\n                    @if ($form->payload || config('waterhole.auth.password_enabled'))\n                        @components($form->fields())\n\n                        <div>\n                            <button type=\"submit\" class=\"btn bg-accent full-width\">\n                                {{ __('waterhole::auth.register-submit') }}\n                            </button>\n                        </div>\n\n                        @unless ($form->payload)\n                            <p class=\"text-center\">\n                                {{ __('waterhole::auth.register-login-prompt') }}\n                                <a href=\"{{ route('waterhole.login') }}\">\n                                    {{ __('waterhole::auth.register-login-link') }}\n                                </a>\n                            </p>\n                        @endunless\n                    @endif\n                </div>\n            </form>\n        </x-waterhole::dialog>\n    </div>\n</x-waterhole::layout>\n"
  },
  {
    "path": "resources/views/auth/reset-password.blade.php",
    "content": "<x-waterhole::layout :title=\"__('waterhole::auth.reset-password-title')\">\n    <div class=\"section\">\n        <x-waterhole::dialog\n            :title=\"__('waterhole::auth.reset-password-title')\"\n            class=\"dialog--sm\"\n        >\n            <form\n                action=\"{{ route('waterhole.reset-password', ['token' => $request->route('token')]) }}\"\n                method=\"POST\"\n            >\n                @csrf\n\n                <div class=\"stack gap-xl\">\n                    <x-waterhole::validation-errors />\n\n                    <x-waterhole::field name=\"email\" :label=\"__('waterhole::auth.email-label')\">\n                        <input\n                            type=\"email\"\n                            id=\"{{ $component->id }}\"\n                            name=\"email\"\n                            value=\"{{ old('email', $request->email) }}\"\n                            required\n                            autofocus\n                        />\n                    </x-waterhole::field>\n\n                    <x-waterhole::field\n                        name=\"password\"\n                        :label=\"__('waterhole::auth.new-password-label')\"\n                    >\n                        <input\n                            type=\"password\"\n                            id=\"{{ $component->id }}\"\n                            name=\"password\"\n                            required\n                            autocomplete=\"new-password\"\n                        />\n                    </x-waterhole::field>\n\n                    <x-waterhole::field\n                        name=\"password_confirmation\"\n                        :label=\"__('waterhole::auth.confirm-password-label')\"\n                    >\n                        <input\n                            type=\"password\"\n                            id=\"{{ $component->id }}\"\n                            name=\"password_confirmation\"\n                            required\n                            autocomplete=\"new-password\"\n                        />\n                    </x-waterhole::field>\n\n                    <button type=\"submit\" class=\"btn bg-accent full-width\">\n                        {{ __('waterhole::auth.reset-password-submit') }}\n                    </button>\n                </div>\n            </form>\n        </x-waterhole::dialog>\n    </div>\n</x-waterhole::layout>\n"
  },
  {
    "path": "resources/views/comments/create.blade.php",
    "content": "@php\n    $title = __('waterhole::forum.create-comment-title');\n@endphp\n\n<x-waterhole::layout :title=\"$title.' - '.$post->title\">\n    <div class=\"section container stack gap-lg\">\n        <header class=\"stack gap-xs\">\n            <ol class=\"breadcrumb\">\n                <li>\n                    <a href=\"{{ $parent ? $parent->post_url : $post->url }}\">\n                        {{ $post->title }}\n                    </a>\n                </li>\n                <li aria-hidden=\"true\"></li>\n            </ol>\n\n            <h1 class=\"h3\">{{ $title }}</h1>\n        </header>\n\n        <x-waterhole::composer :post=\"$post\" :parent=\"$parent\" class=\"is-open is-static\" />\n    </div>\n</x-waterhole::layout>\n"
  },
  {
    "path": "resources/views/comments/edit.blade.php",
    "content": "<x-waterhole::layout :title=\"__('waterhole::forum.edit-comment-title')\">\n    <div class=\"container section\">\n        <turbo-frame id=\"@domid($comment)\" target=\"_top\">\n            <form method=\"POST\" action=\"{{ $comment->url }}\" class=\"comment\">\n                @csrf\n                @method('PATCH')\n\n                <div class=\"comment__main stack gap-md\">\n                    <x-waterhole::attribution\n                        :user=\"$comment->user\"\n                        :date=\"$comment->created_at\"\n                    />\n\n                    <x-waterhole::validation-errors />\n\n                    <x-waterhole::text-editor\n                        id=\"comment-body\"\n                        name=\"body\"\n                        :value=\"old('body', $comment->body)\"\n                        style=\"min-height: 40vh\"\n                    />\n\n                    <div class=\"row gap-xs wrap justify-end\">\n                        <a href=\"{{ $comment->post_url }}\" class=\"btn\">\n                            {{ __('waterhole::system.cancel-button') }}\n                        </a>\n\n                        <button\n                            type=\"submit\"\n                            class=\"btn bg-accent\"\n                            data-hotkey=\"Mod+Enter\"\n                            data-hotkey-scope=\"comment-body\"\n                        >\n                            {{ __('waterhole::system.save-changes-button') }}\n                        </button>\n                    </div>\n                </div>\n            </form>\n        </turbo-frame>\n    </div>\n</x-waterhole::layout>\n"
  },
  {
    "path": "resources/views/comments/show.blade.php",
    "content": "@php\n    $title = __('waterhole::forum.comment-number-title', ['number' => $comment->index + 1]);\n@endphp\n\n<x-waterhole::layout\n    :title=\"$title.' - '.$post->title\"\n    :seo=\"[\n        'description' => $comment->body_text,\n        'url' => $comment->post_url,\n        'type' => 'article',\n        'noindex' => ! $post->channel->structure->is_listed,\n        'schema' => false,\n    ]\"\n>\n    <div\n        class=\"container section\"\n        itemscope\n        itemtype=\"https://schema.org/DiscussionForumPosting\"\n        itemid=\"{{ $post->url }}\"\n    >\n        <meta itemprop=\"headline\" content=\"{{ $post->title }}\" />\n        <meta itemprop=\"datePublished\" content=\"{{ $post->created_at?->toAtomString() }}\" />\n        @if ($post->edited_at)\n            <meta itemprop=\"dateModified\" content=\"{{ $post->edited_at?->toAtomString() }}\" />\n        @endif\n\n        <meta itemprop=\"url\" content=\"{{ $post->url }}\" />\n        <meta itemprop=\"commentCount\" content=\"{{ $post->comment_count }}\" />\n        <span itemprop=\"author\" itemscope itemtype=\"https://schema.org/Person\" hidden>\n            <meta itemprop=\"name\" content=\"{{ Waterhole\\username($post->user) }}\" />\n            @if ($post->user)\n                <meta itemprop=\"url\" content=\"{{ $post->user->url }}\" />\n            @endif\n        </span>\n\n        <div class=\"measure stack gap-lg\">\n            <header class=\"stack gap-xs\">\n                <ol class=\"breadcrumb\">\n                    <li>\n                        <a href=\"{{ $comment->post_url }}\" class=\"inline-block\">\n                            {{ Waterhole\\emojify($post->title) }}\n                        </a>\n                    </li>\n                    <li aria-hidden=\"true\"></li>\n                </ol>\n\n                <h1 class=\"h3\">{{ $title }}</h1>\n            </header>\n\n            <x-waterhole::comment-frame\n                :comment=\"$comment\"\n                with-replies\n                class=\"card\"\n                with-structured-data\n            />\n\n            @can('waterhole.post.comment', $post)\n                <x-waterhole::composer :post=\"$post\" :parent=\"$comment\" />\n            @endcan\n        </div>\n    </div>\n</x-waterhole::layout>\n"
  },
  {
    "path": "resources/views/components/action-button.blade.php",
    "content": "@if ($actionInstance)\n    {{ $before ?? '' }}\n\n    <form\n        action=\"{{ route('waterhole.actions.store') }}\"\n        method=\"POST\"\n        {{ new Illuminate\\View\\ComponentAttributeBag($formAttributes) }}\n    >\n        @csrf\n        <input type=\"hidden\" name=\"actionable\" value=\"{{ $actionable }}\" />\n        <input type=\"hidden\" name=\"id[]\" value=\"{{ $for->id }}\" />\n\n        @isset($return)\n            <input type=\"hidden\" name=\"return\" value=\"{{ $return }}\" />\n        @endisset\n\n        {{ $actionInstance->render(collect([$for]), $attributes->getAttributes(), $icon) }}\n    </form>\n\n    {{ $after ?? '' }}\n@else\n    {{ $unauthorized ?? '' }}\n@endif\n"
  },
  {
    "path": "resources/views/components/action-buttons.blade.php",
    "content": "<div {{ $attributes->class('row') }}>\n    <form action=\"{{ route('waterhole.actions.store') }}\" method=\"POST\" class=\"row\">\n        @csrf\n        <input type=\"hidden\" name=\"actionable\" value=\"{{ $actionable }}\" />\n        <input type=\"hidden\" name=\"id[]\" value=\"{{ $for->getKey() }}\" />\n\n        @php\n            $menu = $limit !== null && $actions->count() > $limit;\n        @endphp\n\n        @foreach ($menu ? $actions->take($limit ? $limit - 1 : 0) : $actions as $action)\n            {{ $action->render(collect([$for]), ['class' => 'btn btn--transparent btn--icon'], true) }}\n        @endforeach\n    </form>\n\n    @if ($menu)\n        <x-waterhole::action-menu\n            :$for\n            :$context\n            :button-attributes=\"['class' => 'btn btn--transparent btn--icon']\"\n            placement=\"bottom-end\"\n        />\n    @endif\n</div>\n"
  },
  {
    "path": "resources/views/components/action-form.blade.php",
    "content": "@php\n    $actionable = $for ? get_class($for) : null;\n    $actions = resolve(\\Waterhole\\Extend\\Core\\Actions::class);\n@endphp\n\n@if ($for && $actionable && $actions->hasList($actionable))\n    <form action=\"{{ route('waterhole.actions.store') }}\" method=\"POST\" {{ $attributes }}>\n        @csrf\n        <input type=\"hidden\" name=\"actionable\" value=\"{{ $actionable }}\" />\n        <input type=\"hidden\" name=\"id[]\" value=\"{{ $for->id }}\" />\n\n        @isset($return)\n            <input type=\"hidden\" name=\"return\" value=\"{{ $return }}\" />\n        @endisset\n\n        @isset($action)\n            <input type=\"hidden\" name=\"action_class\" value=\"{{ $action }}\" />\n        @endisset\n\n        {{ $slot }}\n    </form>\n@endif\n"
  },
  {
    "path": "resources/views/components/action-menu.blade.php",
    "content": "<ui-popup data-controller=\"action-menu\" {{ $attributes->class('row') }}>\n    <a\n        href=\"{{ $url }}\"\n        role=\"button\"\n        data-action=\"mouseenter->action-menu#preload\"\n        {{ new Illuminate\\View\\ComponentAttributeBag($buttonAttributes) }}\n    >\n        @isset($button)\n            {{ $button }}\n        @else\n            @icon('tabler-dots')\n            <ui-tooltip>{{ __('waterhole::system.actions-button') }}</ui-tooltip>\n        @endisset\n    </a>\n\n    <ui-menu class=\"menu\" hidden>\n        <turbo-frame\n            id=\"actions\"\n            loading=\"lazy\"\n            src=\"{{ $url }}\"\n            data-action-menu-target=\"frame\"\n            class=\"busy-spinner\"\n        ></turbo-frame>\n    </ui-menu>\n</ui-popup>\n"
  },
  {
    "path": "resources/views/components/alert.blade.php",
    "content": "<div\n    {{\n        $attributes\n            ->merge(['role' => 'alert', 'data-controller' => 'alert'])\n            ->class(['alert', $type ? \"alert--$type bg-$type\" : null])\n    }}\n>\n    @if ($icon)\n        <div class=\"alert__icon\">\n            @icon($icon)\n        </div>\n    @endif\n\n    <div class=\"alert__message content\">\n        {{ $message ?? $slot }}\n    </div>\n\n    @if (! empty($action) || $dismissible)\n        <div class=\"alert__actions\">\n            {{ $action ?? '' }}\n\n            @if ($dismissible)\n                <button\n                    class=\"btn btn--transparent btn--icon\"\n                    data-action=\"alert#dismiss\"\n                    type=\"button\"\n                >\n                    @icon('tabler-x')\n                </button>\n            @endif\n        </div>\n    @endif\n</div>\n"
  },
  {
    "path": "resources/views/components/attribution.blade.php",
    "content": "<div class=\"attribution\">\n    <span class=\"attribution__user\">\n        <x-waterhole::user-link :user=\"$user\" class=\"attribution__link\">\n            <x-waterhole::avatar :user=\"$user\" />\n\n            @if ($user?->isOnline())\n                <span class=\"dot color-success\">\n                    <ui-tooltip>{{ __('waterhole::user.online-label') }}</ui-tooltip>\n                </span>\n            @endif\n\n            <span class=\"attribution__name\">{{ Waterhole\\username($user) }}</span>\n        </x-waterhole::user-link>\n\n        <x-waterhole::user-groups :user=\"$user\" />\n    </span>\n\n    <span class=\"attribution__info\">\n        @if ($user?->headline)\n            <span>{{ $user->headline }}</span>\n        @endif\n\n        @if ($displayDate = $editDate ?: $date)\n            <span>\n                {{-- format-ignore-start --}}\n                @if ($permalink)\n                    <a\n                        href=\"{{ $permalink }}\"\n                        class=\"color-inherit with-icon\"\n                        target=\"_top\"\n                    >\n                @else\n                    <span class=\"with-icon\">\n                @endif\n                    @if ($editDate)\n                        @icon('tabler-pencil', ['class' => 'icon--narrow text-xxs'])\n                    @endif\n\n                    <x-waterhole::relative-time :datetime=\"$displayDate\" title=\"\" />\n\n                    <ui-tooltip placement=\"bottom\" tooltip-class=\"tooltip tooltip--block\">\n                        @if ($date)\n                            <div>\n                                <small>{{ __('waterhole::forum.attribution-timestamp-created-label') }}</small>\n                                {{ $date->toDayDateTimeString() }}\n                            </div>\n                        @endif\n                        @if ($editDate)\n                            <div>\n                                <small>{{ __('waterhole::forum.attribution-timestamp-edited-label') }}</small>\n                                {{ $editDate->toDayDateTimeString() }}\n                            </div>\n                        @endif\n                    </ui-tooltip>\n                @if ($permalink)\n                    </a>\n                @else\n                    </span>\n                @endif\n                {{-- format-ignore-end --}}\n            </span>\n        @endif\n    </span>\n</div>\n"
  },
  {
    "path": "resources/views/components/avatar.blade.php",
    "content": "@if ($user?->avatar_url)\n    <img\n        src=\"{{ $user->avatar_url }}\"\n        alt=\"{{ $user->name }}\"\n        {{ $attributes->class('avatar') }}\n    />\n@else\n    <svg\n        {{ $attributes->class(['avatar', 'avatar--anonymous' => empty($user)]) }}\n        viewBox=\"0 0 100 100\"\n    >\n        <rect width=\"100%\" height=\"100%\" fill=\"{{ $color() }}\" />\n        <text x=\"50%\" y=\"50%\" dominant-baseline=\"central\" text-anchor=\"middle\">\n            {{ $user?->name ? mb_strtoupper(mb_substr($user->name, 0, 1)) : '?' }}\n        </text>\n    </svg>\n@endif\n"
  },
  {
    "path": "resources/views/components/cancel.blade.php",
    "content": "<a href=\"{{ old('return', request('return', $default)) }}\" {{ $attributes }}>\n    {{ __('waterhole::system.cancel-button') }}\n</a>\n"
  },
  {
    "path": "resources/views/components/channel-label.blade.php",
    "content": "@php\n    $tag = 'span';\n    $attributes = $attributes->class('channel-label');\n\n    if ($link) {\n        $tag = 'a';\n        $attributes = $attributes->merge(['href' => $channel->url]);\n    }\n@endphp\n\n@if ($channel)\n    <{{ $tag }} {{ $attributes }}>\n        @icon($channel->icon)\n        <span>{{ $channel->name }}</span>\n    </{{ $tag }}>\n@endif\n"
  },
  {
    "path": "resources/views/components/channel-picker.blade.php",
    "content": "<div {{ $attributes->class('channel-picker') }}>\n    @foreach ($items as $item)\n        @if ($item instanceof Waterhole\\Models\\StructureHeading)\n            <h4 class=\"menu-heading\">{{ $item->name }}</h4>\n        @elseif ($item instanceof Waterhole\\Models\\Channel)\n            <x-waterhole::menu-item\n                type=\"submit\"\n                :name=\"$name\"\n                :value=\"$item->id\"\n                :active=\"$item->id == $value\"\n                :label=\"$item->name\"\n                :description=\"new Illuminate\\Support\\HtmlString(strip_tags($item->description_html))\"\n                :icon=\"$item->icon\"\n                role=\"\"\n            />\n        @elseif ($item instanceof Waterhole\\Models\\StructureLink)\n            <x-waterhole::menu-item :icon=\"$item->icon\" :href=\"$item->href\" target=\"_blank\">\n                <x-slot name=\"label\">\n                    <span class=\"menu-item__title row gap-xs align-self-center\">\n                        {{ $item->name }}\n                        @icon('tabler-external-link', ['class' => 'color-muted'])\n                    </span>\n                </x-slot>\n            </x-waterhole::menu-item>\n        @endif\n    @endforeach\n\n    @if ($value)\n        <input type=\"hidden\" name=\"channel_id\" value=\"{{ $value }}\" />\n    @endif\n</div>\n"
  },
  {
    "path": "resources/views/components/collapsible-nav.blade.php",
    "content": "<ui-popup {{ $attributes->merge(['class' => 'collapsible-nav stack']) }}>\n    <button class=\"btn text-md {{ $buttonClass }}\">\n        @isset($button)\n            {{ $button }}\n        @elseif ($activeComponent)\n            @icon($activeComponent->icon)\n            <span class=\"overflow-ellipsis\">{{ $activeComponent->label }}</span>\n            @icon('tabler-selector')\n        @elseif (isset($empty))\n            {{ $empty }}\n        @else\n            @icon('tabler-menu-2')\n            <span>{{ __('waterhole::forum.menu-button') }}</span>\n        @endisset\n    </button>\n\n    <div hidden class=\"drawer\">\n        <nav class=\"nav {{ $navClass }}\">\n            @components($components)\n        </nav>\n    </div>\n</ui-popup>\n"
  },
  {
    "path": "resources/views/components/comment-answer-badge.blade.php",
    "content": "<span class=\"comment-answer-badge badge text-xs bg-success weight-medium float-right\">\n    @icon('tabler-check')\n    {{ __('waterhole::forum.comment-answer-badge') }}\n</span>\n"
  },
  {
    "path": "resources/views/components/comment-frame.blade.php",
    "content": "<turbo-frame\n    id=\"@domid($comment)\"\n    @if ($lazy) src=\"{{ $comment->url }}\" @endif\n    {{ $attributes }}\n>\n    @unless ($lazy)\n        <x-waterhole::comment-full\n            :comment=\"$comment\"\n            :with-replies=\"$withReplies\"\n            :with-structured-data=\"$withStructuredData\"\n        />\n    @endunless\n</turbo-frame>\n"
  },
  {
    "path": "resources/views/components/comment-full.blade.php",
    "content": "<x-waterhole::flag-container :subject=\"$comment\" :hide=\"$comment->trashed()\" {{ $attributes }}>\n    <article\n        {{\n            (new Illuminate\\View\\ComponentAttributeBag())\n                ->class('comment')\n                ->merge(\n                    $withStructuredData\n                        ? [\n                            'itemprop' => 'comment',\n                            'itemscope' => true,\n                            'itemtype' => 'https://schema.org/Comment',\n                        ]\n                        : [],\n                )\n                ->merge(resolve(Waterhole\\Extend\\Ui\\CommentAttributes::class)->build($comment))\n        }}\n        data-comment-id=\"{{ $comment->id }}\"\n        data-parent-id=\"{{ $comment->parent?->id }}\"\n        data-controller=\"comment\"\n        tabindex=\"-1\"\n    >\n        @if ($withStructuredData)\n            <meta itemprop=\"datePublished\" content=\"{{ $comment->created_at?->toAtomString() }}\" />\n            @if ($comment->edited_at)\n                <meta\n                    itemprop=\"dateModified\"\n                    content=\"{{ $comment->edited_at?->toAtomString() }}\"\n                />\n            @endif\n\n            <meta itemprop=\"url\" content=\"{{ $comment->url }}\" />\n            <span itemprop=\"author\" itemscope itemtype=\"https://schema.org/Person\" hidden>\n                <meta itemprop=\"name\" content=\"{{ Waterhole\\username($comment->user) }}\" />\n                @if ($comment->user)\n                    <meta itemprop=\"url\" content=\"{{ $comment->user->url }}\" />\n                @endif\n            </span>\n        @endif\n\n        @if ($comment->trashed())\n            <x-waterhole::removed-banner :subject=\"$comment\">\n                <x-slot name=\"lead\">\n                    <div class=\"comment__icon\">\n                        @icon('tabler-trash')\n                    </div>\n\n                    <button\n                        class=\"btn btn--sm btn--transparent btn--start btn--end -my-sm\"\n                        data-action=\"comment#toggleExpanded\"\n                    >\n                        {{ __('waterhole::forum.comment-removed-message') }}\n                        @icon('tabler-chevron-right', ['class' => 'icon--narrow text-xxs'])\n                    </button>\n                </x-slot>\n\n                <x-slot name=\"actions\">\n                    <x-waterhole::action-menu\n                        :for=\"$comment\"\n                        placement=\"bottom-end\"\n                        class=\"-my-sm\"\n                    />\n                </x-slot>\n            </x-waterhole::removed-banner>\n        @endif\n\n        <div class=\"comment__main stack gap-md\">\n            <header class=\"comment__header\">\n                @components(resolve(Waterhole\\Extend\\Ui\\CommentComponent::class)->header, compact('comment'))\n\n                <x-waterhole::attribution\n                    :user=\"$comment->user\"\n                    :date=\"$comment->created_at\"\n                    :edit-date=\"$comment->edited_at\"\n                    :permalink=\"$comment->url\"\n                />\n\n                @if ($comment->parent)\n                    <div\n                        class=\"comment__parent\"\n                        data-action=\"\n                            mouseenter->comment#highlightParent\n                            mouseleave->comment#stopHighlightingParent\n                            click->comment#stopHighlightingParent\n                        \"\n                    >\n                        <a\n                            href=\"{{ $comment->parent->post_url }}\"\n                            class=\"with-icon\"\n                            data-turbo-frame=\"_top\"\n                        >\n                            @icon('tabler-corner-down-right')\n                            <span>\n                                {{ __('waterhole::forum.comment-in-reply-to-link') }}\n                                <span class=\"user-label\">\n                                    <x-waterhole::avatar :user=\"$comment->parent->user\" />\n                                    <span>\n                                        {{ Waterhole\\username($comment->parent->user) }}\n                                    </span>\n                                </span>\n                            </span>\n                        </a>\n\n                        <ui-tooltip\n                            placement=\"top-start\"\n                            tooltip-class=\"tooltip comment__parent-tooltip\"\n                            data-comment-target=\"parentTooltip\"\n                            hidden\n                        >\n                            <div class=\"comment\">\n                                <div class=\"comment__inner\">\n                                    <x-waterhole::attribution\n                                        :user=\"$comment->parent->user\"\n                                        :date=\"$comment->parent->created_at\"\n                                    />\n\n                                    <div class=\"content\">\n                                        {{ Waterhole\\emojify(Str::limit($comment->parent->body_text, 200)) }}\n                                    </div>\n                                </div>\n                            </div>\n                        </ui-tooltip>\n                    </div>\n                @endif\n            </header>\n\n            <div\n                class=\"comment__body content @if ($truncate) content--compact truncated @endif\"\n                data-controller=\"quotable @if ($truncate) truncated @endif\"\n                @if ($withStructuredData) itemprop=\"text\" @endif\n            >\n                {{ $comment->body_html }}\n\n                @if ($truncate)\n                    <button\n                        type=\"button\"\n                        class=\"truncated__expander link weight-bold\"\n                        hidden\n                        data-truncated-target=\"expander\"\n                        data-action=\"truncated#expand\"\n                    >\n                        {{ __('waterhole::system.show-more-button') }}\n                    </button>\n                @endif\n\n                @if (! $comment->trashed())\n                    @can('waterhole.post.comment', $comment->post)\n                        <a\n                            href=\"{{\n                                route('waterhole.posts.comments.create', [\n                                    'post' => $comment->post,\n                                    'parent' => $comment->id,\n                                ])\n                            }}\"\n                            class=\"quotable-button btn bg-emphasis no-select\"\n                            data-turbo-frame=\"@domid($comment->post, 'comment_parent')\"\n                            data-quotable-target=\"button\"\n                            data-action=\"quotable#quoteSelectedText\"\n                            hidden\n                        >\n                            @icon('tabler-quote')\n                            <span>{{ __('waterhole::forum.quote-button') }}</span>\n                        </a>\n                    @endcan\n                @endif\n            </div>\n\n            <footer class=\"comment__footer row gap-xs wrap\">\n                @components(resolve(Waterhole\\Extend\\Ui\\CommentComponent::class)->footer, compact('comment', 'withReplies'))\n\n                <div class=\"row wrap push-end\">\n                    @components(resolve(Waterhole\\Extend\\Ui\\CommentComponent::class)->buttons, compact('comment', 'withReplies'))\n\n                    @if (! $comment->trashed())\n                        <x-waterhole::action-menu :for=\"$comment\" placement=\"bottom-end\" />\n                    @endif\n                </div>\n            </footer>\n        </div>\n\n        <turbo-frame\n            id=\"@domid($comment, 'replies')\"\n            class=\"busy-spinner\"\n            @unless ($withReplies) hidden @endunless\n        >\n            @if ($withReplies)\n                @if (count($comment->children))\n                    <ol\n                        role=\"list\"\n                        tabindex=\"-1\"\n                        class=\"comment__replies comment-list card bg-fill-soft text-xs\"\n                    >\n                        @foreach ($comment->children as $child)\n                            <li class=\"card__row\">\n                                <x-waterhole::comment-frame\n                                    :comment=\"$child\"\n                                    :with-structured-data=\"$withStructuredData\"\n                                />\n                            </li>\n                        @endforeach\n                    </ol>\n                @endif\n            @endif\n        </turbo-frame>\n    </article>\n</x-waterhole::flag-container>\n"
  },
  {
    "path": "resources/views/components/comment-mark-as-answer.blade.php",
    "content": "@if (! $comment->post->answer_id || $comment->isAnswer())\n    <x-waterhole::action-button\n        :for=\"$comment\"\n        :action=\"Waterhole\\Actions\\MarkAsAnswer::class\"\n        class=\"btn btn--sm btn--transparent\"\n        data-turbo-frame=\"_top\"\n    />\n@endif\n"
  },
  {
    "path": "resources/views/components/comment-reactions.blade.php",
    "content": "<x-waterhole::reactions :model=\"$comment\" />\n"
  },
  {
    "path": "resources/views/components/comment-replies.blade.php",
    "content": "@if ($comment->reply_count)\n    <a\n        href=\"{{ $comment->url }}\"\n        class=\"btn btn--sm btn--outline\"\n        data-controller=\"comment-replies\"\n        aria-expanded=\"{{ $withReplies ? 'true' : 'false' }}\"\n        data-action=\"comment-replies#focusAfterLoad\"\n    >\n        @icon('tabler-message-circle-2')\n        <span aria-hidden=\"true\">{{ $comment->reply_count }}</span>\n        <ui-tooltip class=\"visually-hidden\">\n            {{ __('waterhole::forum.comment-show-replies-button', ['count' => $comment->reply_count]) }}\n        </ui-tooltip>\n    </a>\n@endif\n"
  },
  {
    "path": "resources/views/components/comment-reply-button.blade.php",
    "content": "<a\n    @can(\"waterhole.post.comment\", $comment->post)\n        href=\"{{\n            route(\"waterhole.posts.comments.show\", [\n                \"post\" => $comment->post,\n                \"comment\" => $comment->id,\n            ])\n        }}#reply\"\n        data-turbo-frame=\"@domid($comment->post, \"comment_parent\")\"\n    @elseif (Route::has(\"waterhole.login\"))\n        href=\"{{ route(\"waterhole.login\", [\"return\" => $comment->post_url]) }}\"\n        data-turbo-frame=\"_top\"\n    @endcan\n    class=\"btn btn--sm btn--transparent\"\n>\n    @icon(\"tabler-share-3\", [\"class\" => \"flip-horizontal\"])\n    <span>{{ __(\"waterhole::forum.comment-reply-button\") }}</span>\n</a>\n"
  },
  {
    "path": "resources/views/components/comments-locked.blade.php",
    "content": "<div {{ $attributes }}>\n    @if ($post->is_locked)\n        <x-waterhole::alert icon=\"tabler-lock\">\n            {{ __('waterhole::forum.comments-locked-message') }}\n        </x-waterhole::alert>\n    @endif\n</div>\n"
  },
  {
    "path": "resources/views/components/composer.blade.php",
    "content": "<turbo-frame\n    id=\"composer\"\n    {{ $attributes->class('composer stack') }}\n    data-controller=\"composer\"\n    data-turbo-prefetch=\"false\"\n    data-action=\"\n        turbo:before-fetch-request->composer#open\n        turbo:frame-render->composer#open\n        turbo:submit-end->composer#submitEnd\n    \"\n>\n    <a\n        href=\"{{ route('waterhole.posts.comments.create', compact('post', 'parent')) }}\"\n        class=\"composer__placeholder row gap-md color-muted\"\n        data-action=\"composer#placeholderClick\"\n        data-hotkey=\"r\"\n    >\n        <x-waterhole::avatar :user=\"Auth::user()\" />\n        <span>\n            {{\n                $parent\n                    ? __('waterhole::forum.composer-reply-to-placeholder', Waterhole\\user_variables($parent->user))\n                    : __('waterhole::forum.composer-placeholder')\n            }}\n        </span>\n    </a>\n\n    <form\n        class=\"composer__form stack full-height\"\n        action=\"{{ route('waterhole.posts.comments.store', ['post' => $post]) }}\"\n        method=\"POST\"\n    >\n        @csrf\n\n        <div class=\"composer__handle js-only\" data-action=\"pointerdown->composer#startResize\"></div>\n\n        <div class=\"composer__toolbar row gap-xs\">\n            <button\n                type=\"button\"\n                class=\"btn btn--transparent btn--icon composer__close\"\n                data-action=\"composer#close\"\n                data-hotkey=\"Escape\"\n                data-hotkey-scope=\"new-comment\"\n            >\n                @icon('tabler-x')\n            </button>\n\n            <div class=\"h5 overflow-ellipsis\">\n                {{ __('waterhole::forum.create-comment-title') }}\n            </div>\n\n            {{--\n                [complete] is required to prevent this frame from automatically\n                reloading when the composer is reset after posting a comment\n            --}}\n            <turbo-frame\n                class=\"composer__parent nowrap row gap-xs text-xs pill bg-warning-soft\"\n                id=\"@domid($post, 'comment_parent')\"\n                complete\n            >\n                @if ($parent)\n                    <input type=\"hidden\" name=\"parent_id\" value=\"{{ $parent->id }}\" />\n\n                    <a\n                        href=\"{{ $parent->post_url }}\"\n                        data-turbo-frame=\"_top\"\n                        class=\"color-inherit\"\n                    >\n                        {{ __('waterhole::forum.composer-replying-to-label') }}\n                        <x-waterhole::user-label :user=\"$parent->user\" />\n                    </a>\n\n                    <button class=\"btn btn--sm btn--transparent btn--icon\" name=\"parent_id\">\n                        @icon('tabler-x')\n                        <ui-tooltip>\n                            {{ __('waterhole::forum.composer-clear-reply-button') }}\n                        </ui-tooltip>\n                    </button>\n                @endif\n            </turbo-frame>\n\n            <div class=\"grow\"></div>\n\n            @if ($errors->any())\n                <div class=\"color-danger weight-medium text-xs animate-shake\">\n                    {{ $errors->first() }}\n                </div>\n            @endif\n\n            <button\n                class=\"btn bg-accent\"\n                name=\"commit\"\n                value=\"1\"\n                data-hotkey=\"Mod+Enter\"\n                data-hotkey-scope=\"new-comment\"\n            >\n                {{ __('waterhole::forum.composer-submit') }}\n            </button>\n        </div>\n\n        <x-waterhole::text-editor\n            name=\"body\"\n            :value=\"old('body')\"\n            :placeholder=\"__('waterhole::forum.composer-placeholder')\"\n            id=\"new-comment\"\n            data-action=\"quotable:quote-text@document->text-editor#insertQuote\"\n            class=\"grow\"\n            :user-lookup-url=\"route('waterhole.user-lookup', ['post' => $post->id])\"\n        />\n    </form>\n</turbo-frame>\n"
  },
  {
    "path": "resources/views/components/cp/color-picker.blade.php",
    "content": "<div data-controller=\"color-picker\" class=\"color-picker\">\n    <hex-input\n        alpha\n        color=\"{{ $value }}\"\n        class=\"input-container\"\n        data-action=\"color-changed->color-picker#colorChanged\"\n        data-color-picker-target=\"input\"\n        class=\"color-picker__input-container\"\n    >\n        <span class=\"no-pointer\">\n            <span\n                class=\"color-picker__swatch\"\n                style=\"background-color: {{ $value }}\"\n                data-color-picker-target=\"swatch\"\n            ></span>\n        </span>\n\n        <input\n            type=\"text\"\n            name=\"{{ $name }}\"\n            value=\"{{ $value }}\"\n            id=\"{{ $id }}\"\n            class=\"color-picker__input\"\n            maxlength=\"6\"\n            pattern=\"[0-9A-Fa-f]{3}|[0-9A-Fa-f]{4}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8}\"\n            data-action=\"focus->color-picker#show blur->color-picker#hide\"\n        />\n    </hex-input>\n\n    <hex-alpha-color-picker\n        class=\"color-picker__picker\"\n        color=\"{{ $value }}\"\n        hidden\n        data-action=\"color-changed->color-picker#colorChanged focus->color-picker#show blur->color-picker#hide\"\n        data-color-picker-target=\"picker\"\n    ></hex-alpha-color-picker>\n</div>\n"
  },
  {
    "path": "resources/views/components/cp/group-row.blade.php",
    "content": "@props([\n    'group',\n])\n\n<li class=\"card__row row gap-md\">\n    @if ($group->isMember())\n        <span class=\"row gap-xxs text-xs color-muted\">\n            @icon('tabler-user')\n            <span>{{ __('waterhole::cp.structure-visibility-members-label') }}</span>\n        </span>\n    @elseif ($group->isGuest())\n        <span class=\"row gap-xxs text-xs color-muted\">\n            @icon('tabler-world')\n            <span>{{ __('waterhole::cp.structure-visibility-public-label') }}</span>\n        </span>\n    @else\n        <x-waterhole::group-badge :group=\"$group\" class=\"text-xs\" />\n    @endif\n\n    <div class=\"grow\"></div>\n\n    @unless ($group->isGuest() || $group->isMember())\n        <a href=\"{{ $group->users_url }}\" class=\"color-muted text-xs\">\n            {{ __('waterhole::cp.group-user-count', ['count' => $group->users_count]) }}\n        </a>\n    @endunless\n\n    <x-waterhole::action-buttons :for=\"$group\" :limit=\"2\" context=\"cp\" class=\"text-xs\" />\n</li>\n"
  },
  {
    "path": "resources/views/components/cp/icon-picker.blade.php",
    "content": "<div data-controller=\"icon-picker\" class=\"icon-picker\">\n    @if (is_string($value) && $content)\n        <div class=\"row gap-sm\" data-icon-picker-target=\"preview\">\n            @icon($value, ['class' => 'text-md'])\n            <button type=\"button\" class=\"btn\" data-action=\"icon-picker#change\">\n                {{ __('waterhole::system.icon-picker-change-button') }}\n            </button>\n        </div>\n    @endif\n\n    <div\n        class=\"row gap-sm align-start\"\n        data-icon-picker-target=\"form\"\n        data-controller=\"reveal\"\n        @if (is_string($value) && $content) hidden @endif\n    >\n        <select style=\"width: auto\" data-reveal-target=\"if\" name=\"{{ $name }}[type]\">\n            <option value=\"\">\n                {{ __('waterhole::system.icon-picker-none-option') }}\n            </option>\n            <option value=\"emoji\" @selected($type === 'emoji')>\n                {{ __('waterhole::system.icon-picker-emoji-option') }}\n            </option>\n            <option value=\"svg\" @selected($type === 'svg')>\n                {{ __('waterhole::system.icon-picker-svg-option') }}\n            </option>\n            <option value=\"file\" @selected($type === 'file')>\n                {{ __('waterhole::system.icon-picker-image-option') }}\n            </option>\n        </select>\n\n        <div\n            class=\"stack gap-xs full-width\"\n            data-reveal-target=\"then\"\n            data-reveal-value=\"emoji\"\n            data-icon-picker-target=\"emoji\"\n        >\n            <input\n                type=\"text\"\n                name=\"{{ $name }}[emoji]\"\n                @if ($type === 'emoji') value=\"{{ $content }}\" @endif\n                style=\"width: 5ch\"\n            />\n\n            <ui-popup data-controller=\"emoji-picker\" hidden>\n                <button type=\"button\" class=\"btn btn--icon\">\n                    @icon($type === 'emoji' && $content ? 'emoji:' . $content : 'tabler-mood-smile')\n                </button>\n                <div class=\"menu emoji-picker\" hidden>\n                    <emoji-picker></emoji-picker>\n                </div>\n            </ui-popup>\n        </div>\n\n        <div class=\"stack gap-xs full-width\" data-reveal-target=\"then\" data-reveal-value=\"svg\">\n            <input\n                type=\"text\"\n                list=\"icons\"\n                name=\"{{ $name }}[svg]\"\n                @if ($type === 'svg') value=\"{{ $content }}\" @endif\n            />\n\n            <div class=\"field__description\">\n                {{\n                    __('waterhole::system.icon-picker-svg-description', [\n                        'sets' => implode(', ', array_map(fn ($set) => $set['prefix'], app(BladeUI\\Icons\\Factory::class)->all())),\n                    ])\n                }}\n                <a\n                    href=\"https://blade-ui-kit.com/blade-icons#search\"\n                    target=\"_blank\"\n                    rel=\"noopener\"\n                    class=\"with-icon\"\n                >\n                    <span>{{ __('waterhole::system.icon-picker-svg-search-link') }}</span>\n                    @icon('tabler-external-link')\n                </a>\n            </div>\n\n            <datalist id=\"icons\">\n                @foreach (app(BladeUI\\Icons\\IconsManifest::class)->getManifest($sets = app(BladeUI\\Icons\\Factory::class)->all()) as $set => $paths)\n                    @foreach ($paths as $icons)\n                        @foreach ($icons as $icon)\n                            <option value=\"{{ $sets[$set]['prefix'] }}-{{ $icon }}\"></option>\n                        @endforeach\n                    @endforeach\n                @endforeach\n            </datalist>\n        </div>\n\n        <div class=\"stack gap-xs full-width\" data-reveal-target=\"then\" data-reveal-value=\"file\">\n            <input\n                type=\"file\"\n                name=\"{{ $name }}[file]\"\n                accept=\".jpg,.jpeg,.png,.bmp,.gif,.webp,.svg\"\n            />\n        </div>\n    </div>\n</div>\n"
  },
  {
    "path": "resources/views/components/cp/permission-grid.blade.php",
    "content": "<div class=\"table-container card\" tabindex=\"0\">\n    <table class=\"table permission-grid\" data-controller=\"permission-grid\">\n        <colgroup>\n            <col />\n            @foreach ($abilities as $ability)\n                <col />\n            @endforeach\n        </colgroup>\n        <thead>\n            <tr>\n                <td></td>\n                @foreach ($abilities as $ability)\n                    <th>{{ __('waterhole::system.ability-' . $ability) }}</th>\n                @endforeach\n            </tr>\n        </thead>\n        <tbody>\n            @foreach (Waterhole\\Models\\Group::where('id', '!=', Waterhole\\Models\\Group::ADMIN_ID)->get() as $group)\n                <tr data-group-id=\"{{ $group->getKey() }}\">\n                    <th>\n                        @if ($group->isCustom())\n                            <x-waterhole::group-badge :group=\"$group\" />\n                        @else\n                            {{ $group->name }}\n                        @endif\n                    </th>\n                    @foreach ($abilities as $ability)\n                        @if (($group->isGuest() && $ability !== 'view') || ($group->isMember() && $ability === 'moderate'))\n                            <td></td>\n                        @else\n                            <td class=\"choice-cell\">\n                                <label class=\"choice\">\n                                    <input\n                                        type=\"hidden\"\n                                        name=\"permissions[{{ $group->getMorphClass() }}:{{ $group->getKey() }}][{{ $ability }}]\"\n                                        value=\"0\"\n                                    />\n                                    <input\n                                        type=\"checkbox\"\n                                        name=\"permissions[{{ $group->getMorphClass() }}:{{ $group->getKey() }}][{{ $ability }}]\"\n                                        value=\"1\"\n                                        @checked(old(\"permissions.{$group->getMorphClass()}:{$group->getKey()}.$ability\", $scope ? Waterhole::permissions()->can($group, $ability, $scope) : in_array($ability, $defaults)))\n                                        data-implied-by=\"\n                                            @if ($group->isMember()) permissions[group:1][{{ $ability }}] @endif\n                                            @if ($group->isCustom()) permissions[group:2][{{ $ability }}] @endif\n                                        \"\n                                        data-depends-on=\"\n                                            @if ($ability !== 'view') permissions[group:{{ $group->getKey() }}][view] @endif\n                                        \"\n                                    />\n                                </label>\n                            </td>\n                        @endif\n                    @endforeach\n                </tr>\n            @endforeach\n        </tbody>\n    </table>\n</div>\n"
  },
  {
    "path": "resources/views/components/cp/structure-node.blade.php",
    "content": "<li\n    class=\"card__row cp-structure__node\"\n    data-id=\"{{ $node->id }}\"\n    data-content-type=\"{{ $node->content->getMorphClass() }}\"\n    aria-labelledby=\"label_{{ $node->id }}\"\n>\n    <div class=\"cp-structure__content row gap-md\">\n        <button type=\"button\" class=\"drag-handle\" data-handle>\n            @icon('tabler-grip-vertical')\n        </button>\n\n        @if ($node->content instanceof Waterhole\\Models\\Channel)\n            <x-waterhole::channel-label\n                :channel=\"$node->content\"\n                class=\"cp-structure__label\"\n                link\n                target=\"_blank\"\n                id=\"label_{{ $node->id }}\"\n            />\n            <span class=\"with-icon text-xs color-muted hide-sm\">\n                @icon('tabler-message-circle-2')\n                <span>{{ __('waterhole::cp.structure-channel-label') }}</span>\n            </span>\n        @elseif ($node->content instanceof Waterhole\\Models\\Page)\n            <a\n                href=\"{{ $node->content->url }}\"\n                class=\"cp-structure__label with-icon color-text\"\n                target=\"_blank\"\n                id=\"label_{{ $node->id }}\"\n            >\n                @icon($node->content->icon ?? null)\n                <span>{{ $node->content->name ?? 'Page' }}</span>\n            </a>\n            <span class=\"with-icon text-xs color-muted hide-sm\">\n                @icon('tabler-file-text')\n                <span>{{ __('waterhole::cp.structure-page-label') }}</span>\n            </span>\n        @elseif ($node->content instanceof Waterhole\\Models\\StructureHeading)\n            <span class=\"cp-structure__label color-muted\" id=\"label_{{ $node->id }}\">\n                {{ $node->content->name ?? __('waterhole::cp.structure-heading-label') }}\n            </span>\n        @elseif ($node->content instanceof Waterhole\\Models\\StructureLink)\n            <a\n                href=\"{{ $node->content->href }}\"\n                class=\"cp-structure__label with-icon color-text\"\n                target=\"_blank\"\n                id=\"label_{{ $node->id }}\"\n            >\n                @icon($node->content->icon ?? null)\n                <span>{{ $node->content->name ?? 'Link' }}</span>\n            </a>\n            <span class=\"with-icon text-xs color-muted hide-sm\">\n                @icon('tabler-link')\n                <span>{{ __('waterhole::cp.structure-link-label') }}</span>\n            </span>\n        @endif\n\n        <div class=\"grow\"></div>\n\n        @if (\n\n            method_exists($node->content, 'permissions') &&\n            ($recipients = Waterhole::permissions()\n                ->scope($node->content)\n                ->where('ability', 'view')\n                ->load('recipient')\n                ->filter(fn ($permission) => $permission->recipient instanceof Waterhole\\Models\\Group)->map\n                ->recipient)        )\n            @if ($recipients->contains(Waterhole\\Models\\Group::GUEST_ID))\n                <span class=\"with-icon text-xs color-muted hide-sm\">\n                    @icon('tabler-world')\n                    {{ __('waterhole::cp.structure-visibility-public-label') }}\n                </span>\n            @elseif ($recipients->contains(Waterhole\\Models\\Group::MEMBER_ID))\n                <span class=\"with-icon text-xs color-muted hide-sm\">\n                    @icon('tabler-user')\n                    {{ __('waterhole::cp.structure-visibility-members-label') }}\n                </span>\n            @else\n                <span class=\"hide-sm\">\n                    @forelse ($recipients as $group)\n                        <x-waterhole::group-badge :group=\"$group\" />\n                    @empty\n                        <x-waterhole::group-badge :group=\"Waterhole\\Models\\Group::admin()\" />\n                    @endforelse\n                </span>\n            @endif\n        @endif\n\n        <x-waterhole::action-buttons\n            :for=\"$node->content\"\n            context=\"cp\"\n            :limit=\"2\"\n            class=\"text-xs\"\n        />\n    </div>\n</li>\n"
  },
  {
    "path": "resources/views/components/cp/title.blade.php",
    "content": "<header class=\"cp-title stack gap-xs\">\n    @if ($parentTitle)\n        <ol class=\"breadcrumb\">\n            <li><a href=\"{{ $parentUrl }}\">{{ $parentTitle }}</a></li>\n            <li aria-hidden=\"true\"></li>\n        </ol>\n    @endif\n\n    <h1 class=\"h3\">{{ $title }}</h1>\n</header>\n"
  },
  {
    "path": "resources/views/components/cp/version.blade.php",
    "content": "@inject('license', Waterhole\\Licensing\\LicenseManager::class)\n\n<div class=\"cp__version text-xs row gap-xs mt-lg\">\n    <a href=\"https://waterhole.dev\" class=\"color-muted\" target=\"_blank\">\n        Waterhole {{ Waterhole::version() }}\n    </a>\n\n    @if ($license->valid())\n        <a\n            href=\"https://waterhole.dev/account/sites/{{ config('waterhole.system.site_key') }}\"\n            target=\"_blank\"\n            class=\"badge\"\n        >\n            {{ __('waterhole::cp.licensed-badge') }}\n        </a>\n    @elseif ($license->test())\n        <a href=\"https://waterhole.dev/docs/licensing\" target=\"_blank\" class=\"badge bg-warning\">\n            {{ __('waterhole::cp.trial-badge') }}\n        </a>\n    @else\n        <a href=\"https://waterhole.dev/docs/licensing\" target=\"_blank\" class=\"badge bg-danger\">\n            {{ __('waterhole::cp.unlicensed-badge') }}\n        </a>\n    @endif\n</div>\n"
  },
  {
    "path": "resources/views/components/cp.blade.php",
    "content": "<x-waterhole::layout :title=\"$title\" :assets=\"['cp']\" {{ $attributes->class('cp') }}>\n    <div hidden data-page-target=\"title\">{{ __('waterhole::cp.title') }}</div>\n\n    <div class=\"cp__layout section container with-sidebar\">\n        <div class=\"cp__sidebar sidebar sidebar--sticky\">\n            <x-waterhole::collapsible-nav\n                :components=\"Waterhole\\build_components([\n                    ...resolve(\\Waterhole\\Extend\\Ui\\CpNav::class)->items(),\n                    Waterhole\\View\\Components\\Cp\\Version::class,\n                ])\"\n            />\n        </div>\n\n        <div class=\"cp__content\">\n            {{ $slot }}\n        </div>\n    </div>\n</x-waterhole::layout>\n"
  },
  {
    "path": "resources/views/components/dialog.blade.php",
    "content": "<div {{ $attributes->class('dialog') }}>\n    @if ($title || isset($header))\n        <header class=\"dialog__header\">\n            @if ($title)\n                <h1 class=\"dialog__title h3\" id=\"dialog-title\">\n                    {{ $title }}\n                </h1>\n            @endif\n\n            {{ $header ?? '' }}\n        </header>\n    @endif\n\n    <div class=\"dialog__body\">\n        {{ $slot }}\n    </div>\n</div>\n"
  },
  {
    "path": "resources/views/components/email-verification.blade.php",
    "content": "<div class=\"email-verification-banner bg-warning-soft text-xs\">\n    <form\n        action=\"{{ route('waterhole.verify-email.resend') }}\"\n        method=\"POST\"\n        class=\"container content\"\n    >\n        @csrf\n\n        {!!\n            __('waterhole::auth.email-verification-sent-message', [\n                'email' => '<strong>' . e(Auth::user()->email) . '</strong>',\n            ])\n        !!}\n\n        <button type=\"submit\" class=\"link weight-bold color-accent\">\n            {{ __('waterhole::auth.email-verification-resend-button') }}\n        </button>\n    </form>\n</div>\n"
  },
  {
    "path": "resources/views/components/feed-filters.blade.php",
    "content": "<div {{ $attributes->class('row') }}>\n    <div class=\"tabs hide-sm\">\n        @components($firstComponents->all())\n\n        @if (count($overflowComponents))\n            <x-waterhole::selector\n                :value=\"$activeComponent\"\n                :options=\"$overflowComponents->all()\"\n                :label=\"fn($component) => $component->label\"\n                :href=\"fn($component) => $component->href\"\n                button-class=\"tab\"\n                placement=\"bottom-start\"\n            >\n                <x-slot name=\"button\">\n                    @icon('tabler-dots', ['aria-label' => __('waterhole::system.more-button')])\n                </x-slot>\n            </x-waterhole::selector>\n        @endif\n    </div>\n\n    <div class=\"tabs hide-md-up\">\n        <x-waterhole::selector\n            class=\"hide-md-up\"\n            :value=\"$activeComponent\"\n            :options=\"$components->all()\"\n            :label=\"fn($component) => $component->label\"\n            :href=\"fn($component) => $component->href\"\n            button-class=\"tab\"\n            placement=\"bottom-start\"\n        />\n    </div>\n</div>\n"
  },
  {
    "path": "resources/views/components/feed-top-period.blade.php",
    "content": "<x-waterhole::selector\n    :value=\"$currentPeriod\"\n    :options=\"[null, ...$periods]\"\n    :label=\"fn($period) => __('waterhole::forum.filter-top-' . ($period ?: 'all-time'))\"\n    :href=\"fn($period) => request()->fullUrlWithQuery(compact('period'))\"\n    button-class=\"tab\"\n    placement=\"bottom-start\"\n/>\n"
  },
  {
    "path": "resources/views/components/field.blade.php",
    "content": "<div {{ $attributes->class(['field', 'has-error' => $errors->has($name)]) }}>\n    @if ($label)\n        <label for=\"{{ $id }}\" class=\"field__label\">{{ $label }}</label>\n    @endif\n\n    <div class=\"grow stack gap-xs\">\n        <div>\n            {{ $slot }}\n        </div>\n\n        @if ($description)\n            <p class=\"field__description\">{{ $description }}</p>\n        @endif\n\n        @error($name)\n            <div class=\"field__status color-danger\">{{ $message }}</div>\n        @enderror\n    </div>\n</div>\n"
  },
  {
    "path": "resources/views/components/flag-container.blade.php",
    "content": "<div {{ $attributes->class($showBanner ? 'flag-container' : '') }}>\n    @if ($showBanner)\n        <x-waterhole::alert class=\"bg-activity-soft p-md wrap\" icon=\"tabler-flag\">\n            @if ($canModerate)\n                <ui-popup placement=\"bottom-start\" class=\"row\">\n                    <button\n                        type=\"button\"\n                        class=\"btn btn--sm btn--transparent btn--start btn--end color-inherit text-xs -my-xs\"\n                    >\n                        <x-waterhole::flag-summary :subject=\"$subject\" />\n                        @icon('tabler-chevron-down', ['class' => 'icon--narrow text-xxs'])\n                    </button>\n\n                    <div hidden class=\"menu flags-menu\" tabindex=\"-1\">\n                        @foreach ($subject->pendingFlags->sortByDesc('created_at') as $flag)\n                            <div class=\"menu-item is-inert\">\n                                <div>\n                                    <div class=\"menu-item__title\">\n                                        @if ($flag->createdBy)\n                                            <x-waterhole::user-label\n                                                :user=\"$flag->createdBy\"\n                                                link\n                                            />\n                                        @else\n                                            <span class=\"color-muted\">\n                                                {{ __('waterhole::forum.report-system-user') }}\n                                            </span>\n                                        @endif\n                                        ·\n                                        <x-waterhole::relative-time\n                                            :datetime=\"$flag->created_at\"\n                                        />\n                                    </div>\n                                    <div class=\"menu-item__description\">\n                                        {{\n                                            Lang::has($key = \"waterhole::forum.report-reason-$flag->reason-label\")\n                                                ? __($key)\n                                                : Str::headline($flag->reason)\n                                        }}\n                                        @if ($flag->note)\n                                                · {{ $flag->note }}\n                                        @endif\n                                    </div>\n                                </div>\n                            </div>\n                        @endforeach\n                    </div>\n                </ui-popup>\n            @else\n                <div class=\"weight-medium\">\n                    {{ __('waterhole::forum.pending-approval-title') }}\n                </div>\n            @endif\n\n            @if ($canModerate && $subject)\n                <x-slot:action>\n                    <div class=\"row gap-xs mx-xxs\">\n                        <x-waterhole::action-button\n                            :for=\"$subject\"\n                            action=\"Waterhole\\Actions\\DismissFlags\"\n                            class=\"btn\"\n                        />\n                        @if ($subject instanceof Waterhole\\Models\\Post)\n                            <x-waterhole::action-button\n                                :for=\"$subject\"\n                                action=\"Waterhole\\Actions\\TrashPost\"\n                                class=\"btn\"\n                            />\n                        @elseif ($subject instanceof Waterhole\\Models\\Comment)\n                            <x-waterhole::action-button\n                                :for=\"$subject\"\n                                action=\"Waterhole\\Actions\\RemoveComment\"\n                                class=\"btn\"\n                            />\n                        @endif\n                    </div>\n                </x-slot>\n            @endif\n        </x-waterhole::alert>\n    @endif\n\n    {{ $slot ?? '' }}\n</div>\n"
  },
  {
    "path": "resources/views/components/follow-button.blade.php",
    "content": "<x-waterhole::action-form :for=\"$followable\" {{ $attributes }}>\n    <button\n        type=\"submit\"\n        name=\"action_class\"\n        value=\"{{ $followable->isFollowed() ? Waterhole\\Actions\\Unfollow::class : ($followable->isIgnored() ? Waterhole\\Actions\\Unignore::class : Waterhole\\Actions\\Follow::class) }}\"\n        class=\"{{ $buttonClass }} {{ $followable->isFollowed() ? 'bg-warning-soft color-warning' : '' }}\"\n    >\n        @if ($followable->isFollowed())\n            @icon('tabler-bell')\n            <span>{{ __('waterhole::forum.follow-button-following') }}</span>\n        @elseif ($followable->isIgnored())\n            @icon('tabler-eye-off')\n            <span>{{ __('waterhole::forum.follow-button-ignored') }}</span>\n        @else\n            @icon('tabler-bell')\n            <span>{{ __('waterhole::forum.follow-button') }}</span>\n            <ui-tooltip placement=\"bottom\" delay=\"500\">\n                {{ __($localePrefix . '-follow-description') }}\n            </ui-tooltip>\n        @endif\n    </button>\n</x-waterhole::action-form>\n"
  },
  {
    "path": "resources/views/components/group-badge.blade.php",
    "content": "<span\n    {{ $attributes->class([\"badge group-badge\", \"badge--hidden\" => ! $group->is_public]) }}\n    @if ($group->is_public && $group->color)\n        style=\"--group-color: #{{ $group->color }}; --group-color-constrast: {{ Waterhole\\get_contrast_color($group->color) }}\"\n    @endif\n>\n    @if ($group->is_public && $group->icon)\n        @icon($group->icon)\n    @endif\n\n    <span>{{ $group->name }}</span>\n</span>\n"
  },
  {
    "path": "resources/views/components/header-breadcrumb.blade.php",
    "content": "<span\n    class=\"header-breadcrumb hide-md-down overflow-ellipsis shrink\"\n    data-page-target=\"breadcrumb\"\n    data-turbo-permanent\n    id=\"header-breadcrumb\"\n    hidden\n></span>\n"
  },
  {
    "path": "resources/views/components/header-guest.blade.php",
    "content": "@if (Route::has('waterhole.login'))\n    <a href=\"{{ route('waterhole.login') }}\" class=\"btn btn--icon btn--transparent hide-md-up\">\n        @icon('tabler-user-circle')\n        <ui-tooltip>{{ __('waterhole::forum.log-in') }}</ui-tooltip>\n    </a>\n\n    <div class=\"row hide-sm\">\n        <a\n            href=\"{{ route('waterhole.login') }}\"\n            class=\"header-login btn btn--transparent btn--narrow color-accent\"\n        >\n            {{ __('waterhole::forum.log-in') }}\n        </a>\n\n        @if (Route::has('waterhole.register'))\n            <a\n                href=\"{{ route('waterhole.register') }}\"\n                class=\"header-register btn btn--transparent btn--narrow color-accent\"\n            >\n                {{ __('waterhole::forum.register') }}\n            </a>\n        @endif\n    </div>\n@endif\n"
  },
  {
    "path": "resources/views/components/header-moderation.blade.php",
    "content": "<ui-popup placement=\"bottom-end\" data-controller=\"notifications-popup\" data-persistent-badge>\n    <a\n        href=\"{{ route('waterhole.moderation') }}\"\n        class=\"btn btn--icon btn--transparent\"\n        data-action=\"notifications-popup#open\"\n        data-turbo-prefetch=\"false\"\n        role=\"button\"\n    >\n        @icon('tabler-flag')\n\n        <x-waterhole::moderation-badge :user=\"Auth::user()\" />\n\n        <ui-tooltip>{{ __('waterhole::forum.moderation-title') }}</ui-tooltip>\n    </a>\n\n    @unless (request()->routeIs('waterhole.moderation'))\n        <ui-menu hidden class=\"menu moderation-menu\">\n            <turbo-frame\n                id=\"moderation\"\n                data-turbo-permanent\n                src=\"{{ route('waterhole.moderation') }}\"\n                loading=\"lazy\"\n                data-notifications-popup-target=\"frame\"\n                class=\"busy-spinner\"\n            ></turbo-frame>\n        </ui-menu>\n    @endunless\n\n    {{-- To detect the screen size and determine whether to open the popup vs. follow the link --}}\n    <div class=\"hide-sm\" data-notifications-popup-target=\"sm\"></div>\n</ui-popup>\n"
  },
  {
    "path": "resources/views/components/header-notifications.blade.php",
    "content": "<ui-popup placement=\"bottom-end\" data-controller=\"notifications-popup\">\n    <a\n        href=\"{{ route('waterhole.notifications.index') }}\"\n        class=\"btn btn--icon btn--transparent\"\n        data-action=\"notifications-popup#open\"\n        data-turbo-prefetch=\"false\"\n        role=\"button\"\n    >\n        @icon('tabler-bell')\n\n        <x-waterhole::notifications-badge :user=\"Auth::user()\" />\n\n        <ui-tooltip>{{ __('waterhole::notifications.title') }}</ui-tooltip>\n    </a>\n\n    @unless (request()->routeIs('waterhole.notifications.*'))\n        <ui-menu hidden class=\"menu notifications-menu\">\n            <turbo-frame\n                id=\"notifications\"\n                data-turbo-permanent\n                src=\"{{ route('waterhole.notifications.index') }}\"\n                loading=\"lazy\"\n                data-notifications-popup-target=\"frame\"\n                class=\"busy-spinner\"\n            ></turbo-frame>\n        </ui-menu>\n    @endunless\n\n    {{-- To detect the screen size and determine whether to open the popup vs. follow the link --}}\n    <div class=\"hide-sm\" data-notifications-popup-target=\"sm\"></div>\n\n    <x-turbo::stream-from\n        :source=\"Auth::user()\"\n        type=\"private\"\n        data-action=\"message->page#incrementDocumentTitle\"\n    />\n</ui-popup>\n"
  },
  {
    "path": "resources/views/components/header-search.blade.php",
    "content": "<a\n    href=\"{{ route('waterhole.search') }}\"\n    class=\"btn btn--icon btn--transparent header-search__button hide-lg-up\"\n>\n    @icon('tabler-search')\n    <ui-tooltip>{{ __('waterhole::forum.search-button') }}</ui-tooltip>\n</a>\n\n<x-waterhole::search-form class=\"header-search__form hide-md-down\" />\n"
  },
  {
    "path": "resources/views/components/header-title.blade.php",
    "content": "<a href=\"{{ route('waterhole.home') }}\" class=\"header-title nowrap h3\">\n    {{ config('waterhole.forum.name') }}\n</a>\n"
  },
  {
    "path": "resources/views/components/header-user.blade.php",
    "content": "@auth\n    <ui-popup placement=\"bottom-end\" class=\"header-user\">\n        <a href=\"{{ Auth::user()->url }}\" class=\"btn btn--icon\" role=\"button\">\n            <x-waterhole::avatar :user=\"Auth::user()\" />\n            <ui-tooltip>{{ Waterhole\\username(Auth::user()) }}</ui-tooltip>\n        </a>\n\n        <ui-menu class=\"menu\" hidden>\n            <h3 class=\"menu-heading\">{{ Waterhole\\username(Auth::user()) }}</h3>\n\n            @components(\\Waterhole\\Extend\\Ui\\UserMenu::class)\n\n            {{-- Disable Turbo as a means of clearing out the Drive cache --}}\n            <form action=\"{{ route('waterhole.logout') }}\" method=\"POST\" data-turbo=\"false\">\n                @csrf\n                <x-waterhole::menu-item\n                    icon=\"tabler-logout\"\n                    :label=\"__('waterhole::user.log-out-link')\"\n                />\n            </form>\n        </ui-menu>\n    </ui-popup>\n@endauth\n"
  },
  {
    "path": "resources/views/components/header.blade.php",
    "content": "<header\n    id=\"header\"\n    class=\"header\"\n    role=\"banner\"\n    data-controller=\"watch-sticky\"\n    data-page-target=\"header\"\n>\n    <div class=\"container row\">\n        @components(resolve(Waterhole\\Extend\\Ui\\Layout::class)->header)\n    </div>\n</header>\n"
  },
  {
    "path": "resources/views/components/html.blade.php",
    "content": "<!DOCTYPE html>\n<html\n    {{\n        $attributes->class(['no-js', Auth::check() ? 'logged-in' : 'not-logged-in'])->merge([\n            'lang' => app()->getLocale(),\n            'data-theme' => config('waterhole.design.theme', 'light'),\n        ])\n    }}\n>\n    <head>\n        <meta charset=\"utf-8\" />\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1\" />\n        <meta name=\"turbo-refresh-method\" content=\"morph\" />\n        <meta name=\"turbo-refresh-scroll\" content=\"preserve\" />\n\n        @php\n            $pageTitle = implode(' - ', array_filter([$title, $titleSuffix]));\n            $seoTitle = $seo['title'] ?? $pageTitle;\n            $description = str($seo['description'] ?? config('waterhole.seo.default_description'))\n                ->stripTags()\n                ->trim()\n                ->limit(160);\n            $url = $seo['url'] ?? request()->fullUrl();\n            $type = $seo['type'] ?? 'website';\n            $image = $seo['image'] ?? config('waterhole.seo.default_og_image');\n            $siteName = $seo['site_name'] ?? config('waterhole.forum.name');\n            $twitterCard = $seo['twitter_card'] ?? ($image ? 'summary_large_image' : 'summary');\n            $robots = $seo['robots'] ?? (! empty($seo['noindex']) ? 'noindex' : null);\n            $schemaOverride = $seo['schema'] ?? null;\n            $schema = null;\n            if ($schemaOverride !== false) {\n                $schemaDefaults = [\n                    '@context' => 'https://schema.org',\n                    '@type' => match ($type) {\n                        'article' => 'Article',\n                        'profile' => 'ProfilePage',\n                        'website' => 'WebSite',\n                        default => 'WebPage',\n                    },\n                    'name' => $seoTitle,\n                    'description' => $description,\n                    'url' => $url,\n                    'image' => $image,\n                ];\n                $pruneSchema = function ($value) use (&$pruneSchema) {\n                    return is_array($value) ? array_filter(array_map($pruneSchema, $value)) : e($value);\n                };\n                $schema = $pruneSchema(is_array($schemaOverride) ? array_replace_recursive($schemaDefaults, $schemaOverride) : $schemaDefaults);\n            }\n        @endphp\n\n        <title>{{ implode(' - ', array_filter([$title, $titleSuffix])) }}</title>\n\n        @if ($description)\n            <meta name=\"description\" content=\"{{ $description }}\" />\n        @endif\n\n        @if ($robots)\n            <meta name=\"robots\" content=\"{{ $robots }}\" />\n        @endif\n\n        <meta property=\"og:title\" content=\"{{ $seoTitle }}\" />\n        @if ($description)\n            <meta property=\"og:description\" content=\"{{ $description }}\" />\n        @endif\n\n        <meta property=\"og:type\" content=\"{{ $type }}\" />\n        <meta property=\"og:url\" content=\"{{ $url }}\" />\n        @if ($siteName)\n            <meta property=\"og:site_name\" content=\"{{ $siteName }}\" />\n        @endif\n\n        @if ($image)\n            <meta property=\"og:image\" content=\"{{ $image }}\" />\n        @endif\n\n        <meta name=\"twitter:card\" content=\"{{ $twitterCard }}\" />\n        <meta name=\"twitter:title\" content=\"{{ $seoTitle }}\" />\n        @if ($description)\n            <meta name=\"twitter:description\" content=\"{{ $description }}\" />\n        @endif\n\n        @if ($image)\n            <meta name=\"twitter:image\" content=\"{{ $image }}\" />\n        @endif\n\n        @if ($schema)\n            <script type=\"application/ld+json\">\n                @json($schema, JSON_UNESCAPED_SLASHES)\n            </script>\n        @endif\n\n        <script>\n            document.documentElement.className = document.documentElement.className.replace('no-js', 'js');\n\n            @if (!$attributes->get('data-theme') && !config('waterhole.design.theme'))\n                document.documentElement.dataset.theme = localStorage.getItem('theme')\n                || (matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');\n            @endif\n        </script>\n\n        @foreach (resolve(\\Waterhole\\Extend\\Assets\\Stylesheet::class)->urls(['default', 'default-' . App::getLocale(), ...$assets]) as $url)\n            <link href=\"{{ $url }}\" rel=\"stylesheet\" data-turbo-track=\"dynamic\" />\n        @endforeach\n\n        @foreach (resolve(\\Waterhole\\Extend\\Assets\\Script::class)->urls(['default', 'default-' . App::getLocale(), ...$assets]) as $url)\n            <script src=\"{{ $url }}\" defer data-turbo-track=\"dynamic\"></script>\n        @endforeach\n\n        <script>\n            window.Waterhole = @json($payload);\n        </script>\n\n        {{ $head ?? '' }}\n\n        @components(\\Waterhole\\Extend\\Ui\\DocumentHead::class, compact('title', 'assets'))\n    </head>\n\n    <body {{ $attributes->merge(['data-route' => request()->route()->getName(),]) }}>\n        {{ $slot }}\n\n        {{--\n            The persistent modal element contains a Turbo Frame which can be targeted to\n            display modal content. It uses a Stimulus controller such that when content\n            is loaded into the frame, the modal will be shown, or if the response\n            does not contain modal frame content, the modal will be hidden.\n        --}}\n        <ui-modal\n            id=\"modal-element\"\n            class=\"modal\"\n            hidden\n            data-controller=\"modal\"\n            data-action=\"\n                turbo:before-stream-render@document->modal#hide\n                turbo:before-render@document->modal#hide\"\n            data-turbo-permanent\n        >\n            {{-- https://github.com/hotwired/turbo/pull/445#issuecomment-995305287 --}}\n            <turbo-frame\n                data-id=\"modal\"\n                data-controller=\"turbo-frame\"\n                class=\"modal__frame\"\n                data-modal-target=\"frame\"\n                data-action=\"\n                    turbo:submit-start->modal#loading\n                    turbo:before-fetch-request->modal#loading\n                    turbo:frame-render->modal#loaded\"\n                aria-labelledby=\"dialog-title\"\n                disabled\n            ></turbo-frame>\n\n            <div class=\"dialog dialog__body dialog--sm\" data-modal-target=\"loading\">\n                <x-waterhole::spinner class=\"spinner--block\" />\n            </div>\n        </ui-modal>\n\n        {{--\n            The main alerts element. This element is accessible in JavaScript via\n            window.Waterhole.alerts. For API information:\n            https://github.com/tobyzerner/inclusive-elements/tree/master/src/alerts\n        --}}\n        <ui-alerts id=\"alerts\" class=\"alerts\" data-turbo-permanent>\n            @foreach (['success', 'warning', 'danger'] as $type)\n                @if ($message = session($type))\n                    <x-waterhole::alert\n                        :type=\"$type\"\n                        :message=\"$message\"\n                        :data-key=\"'flash-' . $type\"\n                    />\n                @endif\n            @endforeach\n        </ui-alerts>\n\n        {{--\n            Templates for fetch error alert messages. These are cloned into the\n            alerts element whenever there is a Turbo frame error in JavaScript\n        --}}\n        @foreach (['forbidden', 'too-many-requests', 'fatal-error', 'session-expired'] as $key)\n            <template id=\"{{ $key }}-alert\">\n                <x-waterhole::alert type=\"danger\">\n                    {{ __(\"waterhole::system.$key-message\") }}\n                </x-waterhole::alert>\n            </template>\n        @endforeach\n\n        @foreach (['success', 'warning', 'danger'] as $type)\n            <template id=\"template-alert-{{ $type }}\">\n                <x-waterhole::alert :$type />\n            </template>\n        @endforeach\n\n        <template id=\"frame-error\">\n            <div class=\"placeholder\">\n                @icon('tabler-alert-circle', ['class' => 'placeholder__icon'])\n                <p class=\"h4\">{{ __('waterhole::system.fatal-error-heading') }}</p>\n                <button class=\"btn btn--transparent color-accent\">\n                    {{ __('waterhole::system.try-again-button') }}\n                </button>\n            </div>\n        </template>\n    </body>\n</html>\n"
  },
  {
    "path": "resources/views/components/index-create-post.blade.php",
    "content": "@php\n    $enabled = $response === true || $response->allowed();\n    $tag = $enabled ? 'a' : 'span';\n    if ($enabled) {\n        $attributes = $attributes->merge(['href' => route('waterhole.posts.create', ['channel_id' => $channel?->id])]);\n    }\n@endphp\n\n<{{ $tag }}\n    {{ $attributes->class(['btn index-create-post', $enabled ? 'bg-accent' : 'is-disabled']) }}\n>\n    {{ __($channel->translations[($key = 'waterhole::forum.create-post-button')] ?? $key) }}\n\n    @unless ($enabled)\n        <ui-tooltip>\n            {{ $response->message() ?: __('waterhole::system.forbidden-message') }}\n        </ui-tooltip>\n    @endunless\n</{{ $tag }}>\n"
  },
  {
    "path": "resources/views/components/index-footer-language.blade.php",
    "content": "<x-waterhole::selector\n    placement=\"top-start\"\n    button-class=\"btn btn--transparent btn--sm\"\n    :value=\"$currentLocale\"\n    :options=\"array_keys($locales)\"\n    :label=\"fn($locale) => $locales[$locale] ?? $locale\"\n    :href=\"fn($locale) => '?locale='.$locale\"\n    data-turbo=\"false\"\n/>\n"
  },
  {
    "path": "resources/views/components/index-footer.blade.php",
    "content": "<div class=\"index-footer row gap-sm align-center wrap color-muted\">\n    @components(resolve(\\Waterhole\\Extend\\Ui\\IndexPage::class)->footer)\n</div>\n"
  },
  {
    "path": "resources/views/components/index-nav.blade.php",
    "content": "<x-waterhole::collapsible-nav :components=\"$nav->all()\" />\n"
  },
  {
    "path": "resources/views/components/index.blade.php",
    "content": "<div class=\"section container with-sidebar index-layout\">\n    <div class=\"index-sidebar sidebar sidebar--sticky gap-x-md gap-y-lg\">\n        @components(resolve(\\Waterhole\\Extend\\Ui\\IndexPage::class)->sidebar, compact('channel'))\n    </div>\n\n    <div>\n        {{ $slot }}\n    </div>\n</div>\n"
  },
  {
    "path": "resources/views/components/infinite-scroll.blade.php",
    "content": "@php\n    use Illuminate\\Contracts\\Pagination\\CursorPaginator;\n\n    $isCursor = $paginator instanceof CursorPaginator;\n    $current = $isCursor ? $paginator->cursor()?->encode() ?? \"1\" : $paginator->currentPage();\n    $direction = request()->query(\"direction\");\n@endphp\n\n<turbo-frame id=\"page_{{ $current }}_frame\" target=\"_top\" {{ $attributes }}>\n    @if (! $paginator->onFirstPage() && $direction !== \"forwards\")\n        <turbo-frame\n            id=\"page_{{ $isCursor ? $paginator->previousCursor()->encode() ?? \"1\" : $paginator->currentPage() - 1 }}_frame\"\n            src=\"{{ $paginator->appends(\"direction\", \"backwards\")->previousPageUrl() }}\"\n            loading=\"lazy\"\n            class=\"next-page busy-spinner\"\n            target=\"_top\"\n            data-controller=\"load-backwards\"\n        ></turbo-frame>\n    @endif\n\n    <div id=\"page_{{ $current }}\" tabindex=\"-1\"></div>\n\n    @if (! $isCursor && ! $paginator->onFirstPage() && $divider)\n        <div class=\"divider\">\n            {{ __(\"waterhole::system.page-number-heading\", [\"number\" => $paginator->currentPage()]) }}\n        </div>\n    @endif\n\n    {{ $slot ?? \"\" }}\n\n    @if ($paginator->hasMorePages() && $direction !== \"backwards\")\n        <turbo-frame\n            id=\"page_{{ $isCursor ? $paginator->nextCursor()->encode() : $paginator->currentPage() + 1 }}_frame\"\n            target=\"_top\"\n            class=\"next-page busy-spinner\"\n            @if ($paginator->onFirstPage() || $endless)\n                src=\"{{ $paginator->appends(\"direction\", \"forwards\")->nextPageUrl() }}\"\n                loading=\"lazy\"\n            @endif\n        >\n            <div class=\"text-center p-md\">\n                <a\n                    href=\"{{ $paginator->appends(\"direction\", \"forwards\")->nextPageUrl() }}\"\n                    class=\"btn\"\n                    data-turbo-frame=\"_self\"\n                >\n                    {{ __(\"waterhole::system.load-more-button\") }}\n                </a>\n            </div>\n        </turbo-frame>\n    @endif\n</turbo-frame>\n\n@php\n    $paginator->appends(\"direction\", null);\n@endphp\n"
  },
  {
    "path": "resources/views/components/layout.blade.php",
    "content": "<x-waterhole::html :$title :$assets :$seo {{ $attributes }}>\n    <x-slot name=\"head\">{{ $head ?? '' }}</x-slot>\n\n    <div class=\"waterhole\" data-controller=\"page\">\n        <a href=\"#main\" class=\"skip-link\">\n            {{ __('waterhole::system.skip-to-main-content-link') }}\n        </a>\n\n        @components(resolve(\\Waterhole\\Extend\\Ui\\Layout::class)->before)\n\n        <main id=\"main\" class=\"waterhole__main\" tabindex=\"-1\">\n            {{ $slot }}\n        </main>\n\n        @components(resolve(\\Waterhole\\Extend\\Ui\\Layout::class)->after)\n    </div>\n</x-waterhole::html>\n"
  },
  {
    "path": "resources/views/components/menu-item.blade.php",
    "content": "@php\n    $tag = $href ? 'a' : 'button';\n@endphp\n\n<{{ $tag }}\n    {{\n        $attributes->merge([\n            'class' => 'menu-item',\n            'href' => $href,\n            'role' => $active !== null ? 'menuitemradio' : 'menuitem',\n            'aria-checked' => $active && $tag === 'button' ? 'true' : null,\n            'aria-current' => $active && $tag === 'a' ? 'page' : null,\n        ])\n    }}\n>\n    @icon($icon)\n\n    @empty($description)\n        {{ $label }}\n    @else\n        <span>\n            <span class=\"menu-item__title\">{{ $label }}</span>\n            <span class=\"menu-item__description\">{{ $description }}</span>\n        </span>\n    @endempty\n\n    @if ($active)\n        @icon('tabler-check', ['class' => 'menu-item__check'])\n    @endif\n</{{ $tag }}>\n"
  },
  {
    "path": "resources/views/components/nav-link.blade.php",
    "content": "<a\n    {{\n        $attributes\n            ->merge(['href' => $href ?: ($route ? route($route) : null)])\n            ->class([$attributes->has('class') ? '' : 'nav-link', 'is-active' => $isActive])\n    }}\n>\n    @icon($icon)\n    <span class=\"label\">{{ $label }}</span>\n    {{ $slot ?? null }}\n    @isset($badge)\n        <span class=\"badge {{ $badgeClass }}\">{{ $badge }}</span>\n    @endisset\n</a>\n"
  },
  {
    "path": "resources/views/components/notification.blade.php",
    "content": "<turbo-frame id=\"@domid($notification)\">\n    {{--\n        We have to opt-out of Turbo altogether for notifications,\n        because there is a bug where it does not preserve anchors\n        upon redirection: https://github.com/hotwired/turbo/issues/211\n    --}}\n    <a\n        href=\"{{ route('waterhole.notifications.go', compact('notification')) }}\"\n        class=\"menu-item notification p-sm gap-sm @if (!$notification->read_at) is-unread @endif\"\n        role=\"menuitem\"\n        target=\"_top\"\n    >\n        @icon($notification->template->icon(), ['class' => 'color-muted text-md'])\n\n        <span class=\"shrink\">\n            {{ $notification->template->title() }}\n\n            <span class=\"menu-item__description overflow-ellipsis\">\n                @if ($user = $notification->template->sender())\n                    <x-waterhole::user-label :user=\"$notification->template->sender()\" />\n                    &middot;\n                @endif\n\n                <span>{{ Str::limit(strip_tags($notification->template->excerpt()), 200) }}</span>\n            </span>\n        </span>\n\n        <x-waterhole::relative-time\n            :datetime=\"$notification->created_at\"\n            class=\"notification__time text-xs color-muted push-end nowrap\"\n        />\n    </a>\n</turbo-frame>\n"
  },
  {
    "path": "resources/views/components/pinned-post.blade.php",
    "content": "<div\n    {{\n        $attributes\n            ->merge(['data-controller' => $attributes->prepends('post')])\n            ->class('card card__body stack gap-sm')\n            ->merge(resolve(\\Waterhole\\Extend\\Ui\\PostAttributes::class)->build($post))\n    }}\n>\n    <div class=\"row gap-x-sm\">\n        <x-waterhole::post-unread :post=\"$post\" />\n        <x-waterhole::channel-label :channel=\"$post->channel\" link class=\"text-xs\" />\n        <x-waterhole::action-menu :for=\"$post\" placement=\"bottom-end\" class=\"push-end -m-xs\" />\n    </div>\n\n    <div class=\"stack gap-xxs overlay-container\">\n        <h3 class=\"h4 weight-medium\">\n            <a\n                href=\"{{ $post->isUnread() ? $post->unread_url : $post->url }}\"\n                data-action=\"post#appearAsRead\"\n                class=\"post-title-link has-overlay\"\n            >\n                {{ Waterhole\\emojify($post->title) }}\n            </a>\n        </h3>\n\n        <div class=\"content text-xs measure color-muted\">\n            <p>{{ Waterhole\\emojify(Str::limit($post->body_text, 100)) }}</p>\n        </div>\n    </div>\n</div>\n"
  },
  {
    "path": "resources/views/components/post-activity.blade.php",
    "content": "@if ($post->lastComment)\n    <span>\n        <x-waterhole::user-link\n            :user=\"$post->lastComment->user\"\n            class=\"color-inherit inline-block\"\n        >\n            {{ Waterhole\\username($post->lastComment->user) }}\n        </x-waterhole::user-link>\n        {{ __('waterhole::forum.post-activity-replied') }}\n        <a\n            href=\"{{ $post->urlAtIndex($post->comment_count) }}#{{ dom_id($post->lastComment) }}\"\n            class=\"color-inherit\"\n        >\n            <x-waterhole::relative-time :datetime=\"$post->last_activity_at\" />\n        </a>\n    </span>\n@elseif ($post->user)\n    <span>\n        <x-waterhole::user-link :user=\"$post->user\" class=\"color-inherit inline-block\">\n            {{ Waterhole\\username($post->user) }}\n        </x-waterhole::user-link>\n        {{ __('waterhole::forum.post-activity-posted') }}\n        <a href=\"{{ $post->url }}\" class=\"color-inherit\">\n            <x-waterhole::relative-time :datetime=\"$post->created_at\" />\n        </a>\n    </span>\n@endif\n"
  },
  {
    "path": "resources/views/components/post-answer.blade.php",
    "content": "<div class=\"bg-success-soft rounded p-lg stack align-start gap-md\">\n    <div class=\"with-icon weight-medium\">\n        @icon('tabler-circle-check-filled', ['class' => 'text-md'])\n        <span>\n            {{ __('waterhole::forum.post-answered-by') }}\n            <x-waterhole::user-label :user=\"$post->answer->user\" link />\n        </span>\n    </div>\n\n    <div class=\"content color-text\">\n        {{ $post->answer->body_html }}\n    </div>\n\n    <a href=\"{{ $post->answer->post_url }}\" class=\"with-icon weight-medium\">\n        @icon('tabler-arrow-down')\n        {{ __('waterhole::forum.post-view-answer-link') }}\n    </a>\n</div>\n"
  },
  {
    "path": "resources/views/components/post-answered.blade.php",
    "content": "<span class=\"badge bg-success-soft\">\n    @icon('tabler-check')\n    <span>{{ __('waterhole::forum.post-answered-badge') }}</span>\n</span>\n"
  },
  {
    "path": "resources/views/components/post-attribution.blade.php",
    "content": "<x-waterhole::attribution\n    :user=\"$post->user\"\n    :date=\"$post->created_at\"\n    :edit-date=\"$post->edited_at\"\n/>\n"
  },
  {
    "path": "resources/views/components/post-card.blade.php",
    "content": "<article\n    {{\n        $attributes\n            ->class('card card__body post-card stack gap-md')\n            ->merge(resolve(\\Waterhole\\Extend\\Ui\\PostAttributes::class)->build($post))\n    }}\n    data-controller=\"post\"\n>\n    <header class=\"post-card__header row justify-between align-start\">\n        <div class=\"stack gap-lg\">\n            @empty($config['hide_author'])\n                <x-waterhole::post-attribution :$post />\n            @endempty\n\n            <h3 class=\"post-card__title\">\n                <a\n                    href=\"{{ $post->isUnread() ? $post->unread_url : $post->url }}\"\n                    data-action=\"post#appearAsRead\"\n                    class=\"post-title-link\"\n                >\n                    {{ Waterhole\\emojify($post->title) }}\n                </a>\n            </h3>\n        </div>\n\n        <x-waterhole::action-menu :for=\"$post\" placement=\"bottom-end\" />\n    </header>\n\n    <div\n        class=\"post-card__content content content--compact text-sm truncated\"\n        data-controller=\"truncated\"\n    >\n        {{ $post->body_html }}\n\n        <button\n            type=\"button\"\n            class=\"truncated__expander link weight-bold\"\n            hidden\n            data-truncated-target=\"expander\"\n            data-action=\"truncated#expand\"\n        >\n            {{ __('waterhole::system.show-more-button') }}\n        </button>\n    </div>\n\n    <div class=\"row gap-xs wrap\">\n        @components(\\Waterhole\\Extend\\Ui\\PostFooter::class, compact('post'))\n    </div>\n</article>\n"
  },
  {
    "path": "resources/views/components/post-channel.blade.php",
    "content": "<x-waterhole::channel-label :channel=\"$post->channel\" link />\n"
  },
  {
    "path": "resources/views/components/post-feed-channel.blade.php",
    "content": "<div {{ $attributes->class('channel-card card card__body row align-start gap-md') }}>\n    @icon($channel->icon, ['class' => 'channel-card__icon text-xxl'])\n\n    <div class=\"channel-card__inner grow row wrap gap-md\">\n        <div class=\"channel-card__info grow stack gap-xs\">\n            <h2 class=\"h3\">{{ $channel->name }}</h2>\n\n            @if ($description = $channel->description_html)\n                <div class=\"content measure\">{{ $description }}</div>\n            @endif\n        </div>\n\n        <div class=\"channel-card__controls row gap-xs justify-end\">\n            <x-waterhole::follow-button :followable=\"$channel\" />\n\n            <x-waterhole::action-menu placement=\"bottom-end\" :for=\"$channel\" />\n        </div>\n    </div>\n</div>\n"
  },
  {
    "path": "resources/views/components/post-feed-pinned.blade.php",
    "content": "<div class=\"post-feed__pinned grid-fit gap-md hide-if-empty\">\n    @foreach ($posts as $post)\n        <x-waterhole::pinned-post :$post />\n    @endforeach\n</div>\n"
  },
  {
    "path": "resources/views/components/post-feed-toolbar.blade.php",
    "content": "<div class=\"post-feed__toolbar row wrap-reverse justify-end gap-sm\">\n    @components(resolve(\\Waterhole\\Extend\\Ui\\PostFeed::class)->toolbar, compact('feed', 'channel'))\n</div>\n"
  },
  {
    "path": "resources/views/components/post-feed.blade.php",
    "content": "<turbo-frame\n    id=\"post-feed\"\n    class=\"post-feed stack gap-lg\"\n    target=\"_top\"\n    data-controller=\"post-feed\"\n    data-post-feed-filter-value=\"{{ $feed->currentFilter->handle() }}\"\n    data-post-feed-public-channels-value=\"@json($publicChannels)\"\n    data-post-feed-channels-value=\"@json($channels)\"\n>\n    @components(resolve(\\Waterhole\\Extend\\Ui\\PostFeed::class)->header, compact('feed', 'channel'))\n\n    <div>\n        <form\n            class=\"post-feed__refresh animate-appear\"\n            data-post-feed-target=\"newActivity\"\n            data-turbo-frame=\"post-feed\"\n            hidden\n        >\n            <div>\n                <button\n                    type=\"submit\"\n                    class=\"btn btn--sm bg-activity\"\n                    data-action=\"post-feed#scrollToTop\"\n                >\n                    @icon('tabler-refresh')\n                    <span>{{ __('waterhole::forum.post-feed-new-activity-button') }}</span>\n                </button>\n            </div>\n        </form>\n\n        @if ($posts->isNotEmpty())\n            <div class=\"post-feed__content {{ $feed->layout->wrapperClass() }}\">\n                <x-waterhole::infinite-scroll :paginator=\"$posts\">\n                    @foreach ($posts as $post)\n                        @if ($showLastVisit && $post->last_activity_at < session('previously_seen_at'))\n                            @once\n                                @if (! $loop->first)\n                                    <div class=\"divider color-accent feed__last-visit-divider\">\n                                        {{ __('waterhole::forum.post-feed-new-activity-heading') }}\n                                    </div>\n                                @endif\n                            @endonce\n                        @endif\n\n                        <x-dynamic-component\n                            :component=\"$feed->layout->itemComponent()\"\n                            :post=\"$post\"\n                        />\n                    @endforeach\n                </x-waterhole::infinite-scroll>\n            </div>\n        @else\n            <div class=\"placeholder\">\n                @icon('tabler-messages', ['class' => 'placeholder__icon'])\n                <p class=\"h4\">{{ __('waterhole::forum.post-feed-empty-message') }}</p>\n            </div>\n        @endif\n    </div>\n</turbo-frame>\n"
  },
  {
    "path": "resources/views/components/post-full.blade.php",
    "content": "<x-waterhole::flag-container\n    :subject=\"$post\"\n    :hide=\"$post->trashed()\"\n    {{\n    $attributes\n        ->class('card post-full')\n        ->merge(resolve(Waterhole\\Extend\\Ui\\PostAttributes::class)->build($post))\n}}\n>\n    <article class=\"post-full__inner p-gutter stack gap-xl\">\n        <meta itemprop=\"headline\" content=\"{{ $post->title }}\" />\n        <meta itemprop=\"datePublished\" content=\"{{ $post->created_at?->toAtomString() }}\" />\n        @if ($post->edited_at)\n            <meta itemprop=\"dateModified\" content=\"{{ $post->edited_at?->toAtomString() }}\" />\n        @endif\n\n        <meta itemprop=\"url\" content=\"{{ $post->url }}\" />\n        <span itemprop=\"author\" itemscope itemtype=\"https://schema.org/Person\" hidden>\n            <meta itemprop=\"name\" content=\"{{ Waterhole\\username($post->user) }}\" />\n            @if ($post->user)\n                <meta itemprop=\"url\" content=\"{{ $post->user->url }}\" />\n            @endif\n        </span>\n\n        @if ($post->trashed())\n            <x-waterhole::alert class=\"bg-fill color-muted p-md\" icon=\"tabler-trash\">\n                <x-waterhole::removed-banner :subject=\"$post\">\n                    <x-slot name=\"lead\">\n                        <strong>{{ __('waterhole::forum.post-removed-message') }}</strong>\n                    </x-slot>\n                </x-waterhole::removed-banner>\n            </x-waterhole::alert>\n        @endif\n\n        <header class=\"post-header row wrap align-center gap-x-md gap-y-xl\">\n            @components(resolve(Waterhole\\Extend\\Ui\\PostPage::class)->header, compact('post'))\n        </header>\n\n        <div class=\"post-body content text-md\" data-controller=\"quotable\" itemprop=\"text\">\n            {{ $post->body_html }}\n\n            @can('waterhole.post.comment', $post)\n                <a\n                    href=\"{{ route('waterhole.posts.comments.create', compact('post')) }}\"\n                    class=\"quotable-button btn bg-emphasis no-select\"\n                    data-turbo-frame=\"@domid($post, 'comment_parent')\"\n                    data-quotable-target=\"button\"\n                    data-action=\"quotable#quoteSelectedText\"\n                    hidden\n                >\n                    @icon('tabler-quote')\n                    <span>{{ __('waterhole::forum.quote-button') }}</span>\n                </a>\n            @endcan\n        </div>\n\n        <div class=\"row gap-xs text-md\">\n            @components(Waterhole\\Extend\\Ui\\PostFooter::class, compact('post'))\n        </div>\n\n        @components(resolve(Waterhole\\Extend\\Ui\\PostPage::class)->middle, compact('post'))\n    </article>\n</x-waterhole::flag-container>\n"
  },
  {
    "path": "resources/views/components/post-list-item.blade.php",
    "content": "<article\n    {{\n        $attributes\n            ->class('post-list-item card__row row align-start gap-md')\n            ->merge(resolve(\\Waterhole\\Extend\\Ui\\PostAttributes::class)->build($post))\n    }}\n    data-controller=\"post\"\n>\n    <x-waterhole::user-link :user=\"$post->user\" class=\"post-list-item__avatar\">\n        <x-waterhole::avatar :user=\"$post->user\" />\n        <ui-tooltip>\n            {{ Waterhole\\username($post->user) }}\n            {{ __('waterhole::forum.post-activity-posted') }}\n            <x-waterhole::relative-time :datetime=\"$post->created_at\" />\n        </ui-tooltip>\n    </x-waterhole::user-link>\n\n    <div class=\"post-list-item__inner grow stack gap-sm\">\n        <div class=\"post-list-item__upper row gap-sm align-start\">\n            <div class=\"post-list-item__main grow stack gap-xxs\">\n                <h3 class=\"post-list-item__title h4 weight-medium\">\n                    <a\n                        href=\"{{ $post->isUnread() ? $post->unread_url : $post->url }}\"\n                        data-action=\"post#appearAsRead\"\n                        class=\"post-title-link\"\n                    >\n                        {{ $title }}\n                    </a>\n                </h3>\n\n                <div class=\"post-list-item__info row wrap gap-y-xxs gap-x-sm text-xxs color-muted\">\n                    @components(resolve(\\Waterhole\\Extend\\Ui\\PostListItem::class)->info, compact('post'))\n                </div>\n            </div>\n\n            <div class=\"post-list-item__end row wrap justify-end gap-xs align-center\">\n                @components(resolve(\\Waterhole\\Extend\\Ui\\PostListItem::class)->secondary, compact('post', 'config'))\n            </div>\n        </div>\n\n        @if ($excerpt)\n            <div class=\"post-list-item__excerpt content text-xs measure\">\n                <p>{{ $excerpt }}</p>\n            </div>\n        @endif\n    </div>\n\n    <div class=\"post-list-item__controls hide-sm\">\n        <x-waterhole::action-menu :for=\"$post\" placement=\"bottom-end\" />\n    </div>\n</article>\n"
  },
  {
    "path": "resources/views/components/post-locked.blade.php",
    "content": "<span class=\"badge\">\n    @icon('tabler-lock')\n    <span>{{ __('waterhole::forum.post-locked-badge') }}</span>\n</span>\n"
  },
  {
    "path": "resources/views/components/post-notifications.blade.php",
    "content": "@switch($post->userState->notifications)\n    @case('follow')\n        <span class=\"badge bg-warning-soft color-warning\">\n            @icon('tabler-bell')\n            <span>{{ __('waterhole::forum.post-following-badge') }}</span>\n        </span>\n\n        @break\n    @case('ignore')\n        <span class=\"badge\">\n            @icon('tabler-eye-off')\n            <span>{{ __('waterhole::forum.post-ignored-badge') }}</span>\n        </span>\n@endswitch\n"
  },
  {
    "path": "resources/views/components/post-replies.blade.php",
    "content": "<a\n    href=\"{{ $post->url }}#comments\"\n    class=\"btn btn--sm btn--outline @if (!$post->comment_count) is-disabled @endif\"\n    data-action=\"post#appearAsRead\"\n>\n    @icon('tabler-message-circle-2')\n    <span>{{ Waterhole\\compact_number($post->comment_count) }}</span>\n    <ui-tooltip>\n        {{ __('waterhole::forum.post-comments-link', ['count' => $post->comment_count]) }}\n    </ui-tooltip>\n</a>\n"
  },
  {
    "path": "resources/views/components/post-sidebar.blade.php",
    "content": "<div {{ $attributes->class(\"row wrap gap-sm\") }}>\n    @php\n        $enabled = $response === true || $response->allowed();\n        $tag = $enabled ? \"a\" : \"span\";\n        $href = $post->urlAtIndex($post->comment_count) . \"#reply\";\n        if ($enabled && ! Auth::check()) {\n            $href = route(\"waterhole.login\", [\"return\" => $href]);\n        }\n    @endphp\n\n    <{{ $tag }}\n        class=\"btn grow {{ $enabled ? \"bg-accent\" : \"is-disabled\" }}\"\n        @if ($enabled)\n            href=\"{{ $href }}\"\n        @endif\n    >\n        @icon(\"tabler-message-circle\")\n\n        {{ __(\"waterhole::forum.post-comment-button\") }}\n\n        @unless ($enabled)\n            <ui-tooltip>\n                {{ $response->message() ?: __(\"waterhole::system.forbidden-message\") }}\n            </ui-tooltip>\n        @endunless\n    </{{ $tag }}>\n\n    <x-waterhole::action-menu\n        :for=\"$post\"\n        class=\"grow\"\n        :button-attributes=\"['class' => 'btn full-width']\"\n        placement=\"bottom-end\"\n    >\n        <x-slot name=\"button\">\n            @icon(\"tabler-settings\")\n            <span class=\"hide-sm\">{{ __(\"waterhole::system.controls-button\") }}</span>\n            @icon(\"tabler-chevron-down\")\n        </x-slot>\n    </x-waterhole::action-menu>\n\n    @auth\n        <div class=\"hide-sm grow\">\n            <x-waterhole::follow-button :followable=\"$post\" />\n        </div>\n    @endauth\n\n    @components(resolve(\\Waterhole\\Extend\\Ui\\PostPage::class)->sidebar, compact(\"post\"))\n</div>\n"
  },
  {
    "path": "resources/views/components/post-title.blade.php",
    "content": "<h1 data-page-target=\"title\" class=\"post-title\">\n    {{ Waterhole\\emojify($post->title) }}\n</h1>\n"
  },
  {
    "path": "resources/views/components/post-trash.blade.php",
    "content": "<span class=\"badge\">\n    @icon('tabler-trash')\n    <span>{{ __('waterhole::forum.post-trash-badge') }}</span>\n</span>\n"
  },
  {
    "path": "resources/views/components/post-unread.blade.php",
    "content": "<x-waterhole::action-form\n    :for=\"$post\"\n    :action=\"Waterhole\\Actions\\MarkAsRead::class\"\n    class=\"post-list-item__unread\"\n>\n    <button type=\"submit\" class=\"badge clickable @if ($isNotifiable) bg-activity @endif\">\n        @if ($isNotifiable)\n            @icon('tabler-bell')\n        @endif\n\n        @if ($post->isNew())\n            <span>{{ __('waterhole::forum.post-new-badge') }}</span>\n            <ui-tooltip placement=\"bottom\">\n                {{ __('waterhole::forum.post-new-badge-tooltip') }}\n                <br />\n                <small>{{ __('waterhole::forum.mark-as-read-instruction') }}</small>\n            </ui-tooltip>\n        @else\n            <span>{{ $post->unread_comments_count }}</span>\n            <ui-tooltip placement=\"bottom\">\n                {{ __('waterhole::forum.post-unread-comments-badge-tooltip', ['count' => $post->unread_comments_count]) }}\n                <br />\n                <small>{{ __('waterhole::forum.mark-as-read-instruction') }}</small>\n            </ui-tooltip>\n        @endif\n    </button>\n</x-waterhole::action-form>\n"
  },
  {
    "path": "resources/views/components/reaction-set-picker.blade.php",
    "content": "<select {{ $attributes }}>\n    <option value=\"default\" @selected(in_array($value, [null, '', 'default']))>\n        {{\n            __('waterhole::cp.reaction-set-picker-default', [\n                'name' => $default?->name ?? __('waterhole::cp.reaction-set-picker-none'),\n            ])\n        }}\n    </option>\n\n    <hr />\n\n    @foreach ($reactionSets as $reactionSet)\n        <option\n            value=\"{{ $reactionSet->id }}\"\n            @selected((string) $value === (string) $reactionSet->id)\n        >\n            {{ $reactionSet->name }}\n        </option>\n    @endforeach\n\n    @if ($reactionSets->isNotEmpty())\n        <hr />\n    @endif\n\n    <option value=\"none\" @selected($value === 'none')>\n        {{ __('waterhole::cp.reaction-set-picker-none') }}\n    </option>\n</select>\n"
  },
  {
    "path": "resources/views/components/reactions-condensed.blade.php",
    "content": "<span\n    {{\n        $attributes->merge([\n            'data-count' => ($count = $model->reactionCounts->sum('count')),\n            'class' => 'btn btn--sm btn--transparent is-inert reactions-condensed',\n        ])\n    }}\n>\n    <span class=\"row reverse\">\n        @foreach ($reactionTypes->take(3)->reverse() as $reactionType)\n            @icon($reactionType->icon)\n        @endforeach\n    </span>\n    {{ $count }}\n</span>\n"
  },
  {
    "path": "resources/views/components/reactions.blade.php",
    "content": "@php\n    use Illuminate\\View\\ComponentAttributeBag;\n@endphp\n\n<x-waterhole::action-form\n    :for=\"$model\"\n    :action=\"Waterhole\\Actions\\React::class\"\n    :return=\"$model->url\"\n    {{ $attributes->class('reactions row wrap gap-xxs') }}\n>\n    @foreach ($reactionTypes as $reactionType)\n        <{{ $component->isAuthorized ? 'button' : 'span' }}\n            {{\n                (new ComponentAttributeBag([\n                    'name' => 'reaction_type_id',\n                    'value' => $reactionType->id,\n                    'data-reaction-type' => $reactionType->id,\n                    'data-count' => ($count = $reactionCount($reactionType)),\n                ]))->class([\n                    'btn btn--sm btn--outline reaction',\n                    'is-active' => $userReacted($reactionType),\n                    'is-inert' => ! $component->isAuthorized,\n                ])\n            }}\n        >\n            @icon($reactionType->icon)\n            <span>{{ $count }}</span>\n\n            <ui-tooltip tooltip-class=\"tooltip tooltip--block\">\n                <strong>{{ $reactionType->name }}</strong>\n                @if ($count)\n                    <turbo-frame\n                        id=\"reactions\"\n                        src=\"{{ $model->reactionsUrl($reactionType) }}\"\n                        loading=\"lazy\"\n                    >\n                        Loading...\n                    </turbo-frame>\n                @endif\n            </ui-tooltip>\n        </{{ $component->isAuthorized ? 'button' : 'span' }}>\n    @endforeach\n\n    @if ($component->isAuthorized && $reactionSet->reactionTypes->count() > 1)\n        <ui-popup placement=\"top\" class=\"js-only\">\n            <button class=\"btn btn--sm btn--icon btn--transparent control\">\n                @icon('tabler-mood-plus')\n                <ui-tooltip>{{ __('waterhole::forum.add-reaction-button') }}</ui-tooltip>\n            </button>\n\n            <ui-menu class=\"menu reactions-menu\" hidden>\n                @foreach ($reactionSet->reactionTypes as $reactionType)\n                    <button\n                        class=\"text-xl reaction-type-{{ $reactionType->id }}\"\n                        name=\"reaction_type_id\"\n                        value=\"{{ $reactionType->id }}\"\n                        role=\"menuitemradio\"\n                    >\n                        @icon($reactionType->icon)\n                        <ui-tooltip>{{ $reactionType->name }}</ui-tooltip>\n                    </button>\n                @endforeach\n            </ui-menu>\n        </ui-popup>\n    @endif\n</x-waterhole::action-form>\n"
  },
  {
    "path": "resources/views/components/relative-time.blade.php",
    "content": "<relative-time\n    {{\n        $attributes->merge([\n            'datetime' => $dateTime->toIso8601String(),\n            'tense' => 'past',\n        ])\n    }}\n>\n    {{ $dateTime->toFormattedDateString() }}\n</relative-time>\n"
  },
  {
    "path": "resources/views/components/removed-banner.blade.php",
    "content": "<div class=\"removed-banner color-muted stack gap-xs full-width\">\n    <div class=\"removed-banner__summary row wrap gap-sm align-center\">\n        {{ $lead ?? '' }}\n\n        @if ($subject->deleted_reason)\n            <span class=\"text-xxs\">\n                {{\n                    Lang::has($key = \"waterhole::forum.report-reason-$subject->deleted_reason-label\")\n                        ? __($key)\n                        : Str::headline($subject->deleted_reason)\n                }}\n            </span>\n        @endif\n\n        <div class=\"grow\"></div>\n\n        @if ($canModerate = $subject->canModerate(Auth::user()))\n            <div class=\"removed-banner__attribution text-xxs push-end\">\n                @if ($subject->deletedBy)\n                    <span>{{ __('waterhole::forum.removed-by-label') }}</span>\n                    <x-waterhole::user-label :user=\"$subject->deletedBy\" />\n                @endif\n\n                <x-waterhole::relative-time :datetime=\"$subject->deleted_at\" />\n            </div>\n        @endif\n\n        {{ $actions ?? '' }}\n    </div>\n\n    @if ($subject->deleted_message && (Auth::id() === $subject->user_id || $canModerate))\n        <div class=\"removed-banner__details stack gap-xs text-xxs\">\n            <div class=\"stack gap-xxs\">\n                <span>{!! nl2br(e($subject->deleted_message)) !!}</span>\n            </div>\n        </div>\n    @endif\n</div>\n"
  },
  {
    "path": "resources/views/components/search-form.blade.php",
    "content": "<form\n    action=\"{{ route('waterhole.search') }}\"\n    role=\"search\"\n    {{ $attributes->class('input-container') }}\n>\n    @icon('tabler-search', ['class' => 'no-pointer color-muted'])\n\n    <input\n        class=\"pill\"\n        type=\"text\"\n        name=\"q\"\n        placeholder=\"{{ __('waterhole::forum.search-placeholder') }}\"\n        aria-label=\"{{ __('waterhole::forum.search-placeholder') }}\"\n        required\n        data-hotkey=\"/\"\n    />\n\n    <div class=\"hide-if-invalid\">\n        <button\n            type=\"submit\"\n            class=\"btn btn--icon btn--transparent btn--sm color-accent\"\n            aria-label=\"{{ __('waterhole::forum.search-button') }}\"\n        >\n            @icon('tabler-arrow-right')\n        </button>\n    </div>\n</form>\n"
  },
  {
    "path": "resources/views/components/selector.blade.php",
    "content": "<ui-popup {{ $attributes }}>\n    <button type=\"button\" class=\"{{ $buttonClass }}\">\n        @isset($button)\n            {{ $button }}\n        @else\n            <span>{{ $value ? $label($value) : $placeholder ?? $label(null) }}</span>\n            @icon('tabler-selector', ['class' => 'icon--narrow'])\n        @endisset\n    </button>\n\n    <ui-menu class=\"menu\" hidden>\n        @foreach ($options as $option)\n            <a\n                href=\"{{ $href($option) }}\"\n                class=\"menu-item\"\n                role=\"menuitemradio\"\n                @if ($value === $option) aria-checked=\"true\" @endif\n            >\n                {{ $label($option) }}\n                @if ($value === $option)\n                    @icon('tabler-check', ['class' => 'menu-item__check'])\n                @endif\n            </a>\n        @endforeach\n    </ui-menu>\n</ui-popup>\n"
  },
  {
    "path": "resources/views/components/spacer.blade.php",
    "content": "<div class=\"grow\"></div>\n"
  },
  {
    "path": "resources/views/components/text-editor-button.blade.php",
    "content": "<button\n    type=\"button\"\n    @if ($format)\n        data-action=\"text-editor#format\"\n        data-text-editor-format-param=\"{{ is_array($format) ? json_encode($format) : $format }}\"\n    @endif\n    @if ($hotkey)\n        data-hotkey-scope=\"{{ $id }}\"\n        data-hotkey=\"{{ $hotkey }}\"\n    @endif\n    {{ $attributes->class(\"btn btn--transparent btn--icon\") }}\n>\n    @icon($icon)\n    <ui-tooltip>\n        {{ $label }}\n        @if ($hotkey)\n            <small data-text-editor-target=\"hotkeyLabel\">{{ $hotkey }}</small>\n        @endif\n    </ui-tooltip>\n</button>\n"
  },
  {
    "path": "resources/views/components/text-editor.blade.php",
    "content": "<div\n    data-controller=\"text-editor uploads\"\n    data-text-editor-format-url-value=\"{{ route('waterhole.format') }}\"\n    data-uploads-url-value=\"{{ route('waterhole.upload') }}\"\n    {{ $attributes->class('input text-editor stack overlay-container') }}\n>\n    <ui-toolbar\n        class=\"text-editor__toolbar row js-only text-xxs scrollable-x\"\n        data-controller=\"watch-scroll\"\n    >\n        @components(\\Waterhole\\Extend\\Ui\\TextEditor::class, compact('id'))\n\n        <button\n            type=\"button\"\n            class=\"btn btn--transparent text-editor__preview-button push-end\"\n            aria-pressed=\"false\"\n            data-action=\"text-editor#togglePreview\"\n            data-text-editor-target=\"previewButton\"\n        >\n            {{ __('waterhole::system.text-editor-preview') }}\n        </button>\n    </ui-toolbar>\n\n    <div class=\"text-editor__content grow stack\">\n        <text-expander\n            keys=\"@\"\n            data-controller=\"mentions\"\n            data-mentions-user-lookup-url-value=\"{{ $userLookupUrl }}\"\n            class=\"text-editor__expander grow stack\"\n        >\n            <textarea\n                name=\"{{ $name }}\"\n                id=\"{{ $id }}\"\n                class=\"text-editor__input grow content\"\n                placeholder=\"{{ $placeholder }}\"\n                data-text-editor-target=\"input\"\n                data-uploads-target=\"input\"\n            >\n{{ $value }}</textarea\n            >\n        </text-expander>\n\n        <div\n            class=\"text-editor__preview content overlay busy-spinner\"\n            data-text-editor-target=\"preview\"\n            hidden\n        ></div>\n    </div>\n</div>\n"
  },
  {
    "path": "resources/views/components/theme-selector.blade.php",
    "content": "<ui-popup placement=\"bottom-end\" class=\"js-only\" data-controller=\"theme\">\n    <button type=\"button\" class=\"btn btn--icon btn--transparent\">\n        @icon('tabler-sun', ['class' => 'light-only'])\n        @icon('tabler-moon', ['class' => 'dark-only'])\n        <ui-tooltip>{{ __('waterhole::system.theme-button') }}</ui-tooltip>\n    </button>\n\n    <ui-menu class=\"menu\" hidden>\n        <button\n            type=\"button\"\n            class=\"menu-item\"\n            role=\"menuitemradio\"\n            data-action=\"theme#set\"\n            data-theme-name-param=\"light\"\n        >\n            @icon('tabler-sun')\n            <span>{{ __('waterhole::system.theme-light') }}</span>\n            @icon('tabler-check', ['class' => 'menu-item__check'])\n        </button>\n\n        <button\n            type=\"button\"\n            class=\"menu-item\"\n            role=\"menuitemradio\"\n            data-action=\"theme#set\"\n            data-theme-name-param=\"dark\"\n        >\n            @icon('tabler-moon')\n            <span>{{ __('waterhole::system.theme-dark') }}</span>\n            @icon('tabler-check', ['class' => 'menu-item__check'])\n        </button>\n\n        <button\n            type=\"button\"\n            class=\"menu-item\"\n            role=\"menuitemradio\"\n            data-action=\"theme#set\"\n            data-theme-name-param\n        >\n            @icon('tabler-device-desktop')\n            <span>{{ __('waterhole::system.theme-automatic') }}</span>\n            @icon('tabler-check', ['class' => 'menu-item__check'])\n        </button>\n    </ui-menu>\n</ui-popup>\n"
  },
  {
    "path": "resources/views/components/user-label.blade.php",
    "content": "<x-waterhole::user-link :user=\"$user\" {{ $attributes->class('user-label') }} :link=\"$link\">\n    <x-waterhole::avatar :user=\"$user\" />\n    <span>{{ Waterhole\\username($user) }}</span>\n</x-waterhole::user-link>\n"
  },
  {
    "path": "resources/views/components/user-link.blade.php",
    "content": "@if ($user && $link)\n    <a href=\"{{ $user->url }}\" {{ $attributes->merge(['data-turbo-frame' => '_top']) }}>\n        {{ $slot }}\n    </a>\n@else\n    <span {{ $attributes }}>{{ $slot }}</span>\n@endif\n"
  },
  {
    "path": "resources/views/components/user-profile.blade.php",
    "content": "<x-waterhole::layout\n    :title=\"$title\"\n    :seo=\"[\n        'description' => $user->headline ?: $user->bio_html,\n        'url' => $user->url,\n        'type' => 'profile',\n        'image' => $user->avatar_url,\n        'schema' => [\n            '@type' => 'ProfilePage',\n            'mainEntity' => [\n                '@type' => 'Person',\n                'name' => Waterhole\\username($user),\n                'url' => $user->url,\n                'image' => $user->avatar_url ?: null,\n            ],\n        ],\n    ]\"\n>\n    <x-slot name=\"head\">{{ $head ?? '' }}</x-slot>\n\n    <div class=\"section container user-profile stack gap-gutter\">\n        <div class=\"card card__body user-profile__card\">\n            <div class=\"user-profile__controls\">\n                <x-waterhole::action-menu\n                    :for=\"$user\"\n                    placement=\"bottom-end\"\n                    :button-attributes=\"['class' => 'btn']\"\n                >\n                    <x-slot name=\"button\">\n                        @icon('tabler-settings')\n                        <span>{{ __('waterhole::system.controls-button') }}</span>\n                        @icon('tabler-chevron-down')\n                    </x-slot>\n                </x-waterhole::action-menu>\n            </div>\n\n            <div class=\"row align-start gap-x-xl gap-y-md wrap\">\n                <x-waterhole::avatar :user=\"$user\" />\n\n                <div class=\"grow stack gap-xs user-profile__content\">\n                    <h1 class=\"h1 user-profile__name\" data-page-target=\"title\">\n                        {{ Waterhole\\username($user) }}\n                    </h1>\n\n                    @if ($user->headline)\n                        <p class=\"h4 user-profile__headline\">\n                            {{ Waterhole\\emojify($user->headline) }}\n                        </p>\n                    @endif\n\n                    @if ($user->bio_html)\n                        <div class=\"content user-profile__bio\">{{ $user->bio_html }}</div>\n                    @endif\n\n                    <div\n                        class=\"row gap-sm wrap align-center color-muted text-xs user-profile__info\"\n                    >\n                        @components(\\Waterhole\\Extend\\Ui\\UserInfo::class, compact('user'))\n                    </div>\n                </div>\n            </div>\n        </div>\n\n        <div class=\"with-sidebar\">\n            <div class=\"sidebar sidebar--sticky\">\n                <x-waterhole::collapsible-nav\n                    :components=\"Waterhole\\build_components(\\Waterhole\\Extend\\Ui\\UserNav::class, compact('user'))\"\n                />\n            </div>\n\n            <div>\n                {{ $slot }}\n            </div>\n        </div>\n    </div>\n</x-waterhole::layout>\n"
  },
  {
    "path": "resources/views/components/validation-errors.blade.php",
    "content": "@if ($errors->any())\n    <x-waterhole::alert type=\"danger\">\n        @if ($errors->count() === 1)\n            {{ $errors->first() }}\n        @else\n            <p class=\"weight-bold\">\n                {{ __('waterhole::system.validation-errors-message') }}\n            </p>\n            <ul>\n                @foreach ($errors->all() as $error)\n                    <li>{{ $error }}</li>\n                @endforeach\n            </ul>\n        @endif\n    </x-waterhole::alert>\n@endif\n"
  },
  {
    "path": "resources/views/cp/dashboard.blade.php",
    "content": "@inject(\"license\", Waterhole\\Licensing\\LicenseManager::class)\n\n<x-waterhole::cp :title=\"__('waterhole::cp.dashboard-title')\">\n    <div class=\"cp-dashboard stack gap-lg\">\n        @if ($license->invalid() && $license->production())\n            <x-waterhole::alert type=\"danger\" data-key=\"license\" data-duration=\"-1\">\n                {{\n                    $license->status() === 200\n                        ? __([\n                            \"waterhole::cp.license-\" . Str::kebab($license->error()) . \"-message\",\n                            \"waterhole::cp.license-invalid-message\",\n                        ])\n                        : __(\"waterhole::cp.license-error-message\", [\"status\" => $license->status()])\n                }}\n                <a\n                    href=\"https://waterhole.dev/docs/licensing\"\n                    target=\"_blank\"\n                    class=\"color-inherit nowrap weight-bold\"\n                >\n                    {{ __(\"waterhole::system.learn-more-link\") }}\n                </a>\n            </x-waterhole::alert>\n        @endif\n\n        @section(\"debug\")\n            <x-waterhole::alert type=\"warning\" icon=\"tabler-bug\">\n                {{ __(\"waterhole::cp.debug-mode-on-message\") }}\n                <a\n                    href=\"https://waterhole.dev/docs/configuration#debug-mode\"\n                    class=\"color-inherit weight-bold nowrap\"\n                    target=\"_blank\"\n                >\n                    {{ __(\"waterhole::system.learn-more-link\") }}\n                </a>\n            </x-waterhole::alert>\n        @endsection\n\n        @section(\"mail\")\n            <x-waterhole::alert type=\"warning\" icon=\"tabler-mail\">\n                {{ __(\"waterhole::cp.configure-mail-message\") }}\n                <a\n                    href=\"https://waterhole.dev/docs/configuration#mail-configuration\"\n                    class=\"color-inherit weight-bold nowrap\"\n                    target=\"_blank\"\n                >\n                    {{ __(\"waterhole::system.learn-more-link\") }}\n                </a>\n            </x-waterhole::alert>\n        @endsection\n\n        @if ($alerts = resolve(Waterhole\\Extend\\Ui\\CpAlerts::class)->components())\n            <div class=\"stack gap-sm\">\n                @components($alerts)\n            </div>\n        @endif\n\n        <div class=\"cp-dashboard__widgets\">\n            @foreach (config(\"waterhole.cp.widgets\", []) as $id => $widget)\n                <div\n                    style=\"\n                        @isset($widget[\"width\"])\n                            --cp-dashboard-widget-width: {{ is_numeric($widget[\"width\"]) ? $widget[\"width\"] * 100 . \"%\" : $widget[\"width\"] }};\n                        @endisset\n                        @isset($widget[\"height\"])\n                            --cp-dashboard-widget-height: {{ $widget[\"height\"] . (is_numeric($widget[\"height\"]) ? \"px\" : \"\") }};\n                        @endisset\n                    \"\n                >\n                    @empty($widget[\"component\"]::$lazy)\n                        @include(\"waterhole::cp.widget\")\n                    @else\n                        <turbo-frame\n                            id=\"widget_{{ $id }}\"\n                            src=\"{{ route(\"waterhole.cp.dashboard.widget\", compact(\"id\")) }}\"\n                            data-controller=\"turbo-frame\"\n                            data-action=\"turbo:frame-load->turbo-frame#removeSrc\"\n                            class=\"busy-spinner\"\n                        ></turbo-frame>\n                    @endempty\n                </div>\n            @endforeach\n        </div>\n    </div>\n\n    <div class=\"cp-help\">\n        <a\n            href=\"https://waterhole.dev/docs/dashboard\"\n            target=\"_blank\"\n            class=\"color-muted with-icon\"\n        >\n            @icon(\"tabler-help\")\n            Customize the Dashboard\n        </a>\n    </div>\n</x-waterhole::cp>\n"
  },
  {
    "path": "resources/views/cp/groups/form.blade.php",
    "content": "@php\n    $title = isset($group)\n        ? __('waterhole::cp.edit-group-title')\n        : __('waterhole::cp.create-group-title');\n@endphp\n\n<x-waterhole::cp :title=\"$title\">\n    <x-waterhole::cp.title\n        :parent-url=\"route('waterhole.cp.groups.index')\"\n        :parent-title=\"__('waterhole::cp.groups-title')\"\n        :title=\"$title\"\n    />\n\n    <form\n        method=\"POST\"\n        action=\"{{ isset($group) ? route('waterhole.cp.groups.update', compact('group')) : route('waterhole.cp.groups.store') }}\"\n        enctype=\"multipart/form-data\"\n    >\n        @csrf\n        @if (isset($group))\n            @method('PATCH')\n        @endif\n\n        <div class=\"stack gap-lg\">\n            <x-waterhole::validation-errors />\n\n            <div class=\"stack gap-md\">\n                @components($form->fields())\n            </div>\n\n            <div class=\"row gap-xs wrap\">\n                <button type=\"submit\" class=\"btn bg-accent btn--wide\">\n                    {{ isset($group) ? __('waterhole::system.save-changes-button') : __('waterhole::system.create-button') }}\n                </button>\n\n                <a href=\"{{ route('waterhole.cp.groups.index') }}\" class=\"btn\">\n                    {{ __('waterhole::system.cancel-button') }}\n                </a>\n            </div>\n        </div>\n    </form>\n</x-waterhole::cp>\n"
  },
  {
    "path": "resources/views/cp/groups/index.blade.php",
    "content": "<x-waterhole::cp :title=\"__('waterhole::cp.groups-title')\">\n    <div class=\"stack gap-lg\">\n        <div class=\"row gap-md\">\n            <h1 class=\"h3\">{{ __('waterhole::cp.groups-title') }}</h1>\n\n            <div class=\"grow\"></div>\n\n            <a\n                href=\"{{ route('waterhole.cp.groups.create') }}\"\n                type=\"button\"\n                class=\"btn bg-accent\"\n            >\n                @icon('tabler-plus')\n                <span>{{ __('waterhole::cp.create-group-button') }}</span>\n            </a>\n        </div>\n\n        @php\n            $systemGroups = $groups->filter(fn ($group) => ! $group->isCustom());\n            $customGroups = $groups->filter(fn ($group) => $group->isCustom())->sortBy('name');\n        @endphp\n\n        <ul class=\"card\" role=\"list\">\n            @foreach ($systemGroups as $group)\n                <x-waterhole::cp.group-row :group=\"$group\" />\n            @endforeach\n        </ul>\n\n        <ul class=\"card\" role=\"list\">\n            @foreach ($customGroups as $group)\n                <x-waterhole::cp.group-row :group=\"$group\" />\n            @endforeach\n        </ul>\n    </div>\n</x-waterhole::cp>\n"
  },
  {
    "path": "resources/views/cp/reactions/index.blade.php",
    "content": "<x-waterhole::cp :title=\"__('waterhole::cp.reactions-title')\">\n    <div class=\"stack gap-md\">\n        <div class=\"row gap-md\">\n            <h1 class=\"h3\">{{ __('waterhole::cp.reactions-title') }}</h1>\n\n            <div class=\"grow\"></div>\n\n            <a\n                href=\"{{ route('waterhole.cp.reaction-sets.create') }}\"\n                type=\"button\"\n                class=\"btn bg-accent\"\n            >\n                @icon('tabler-plus')\n                <span>{{ __('waterhole::cp.create-reaction-set-button') }}</span>\n            </a>\n        </div>\n\n        <ul class=\"card\" role=\"list\">\n            @if ($reactionSets->count())\n                @foreach ($reactionSets as $reactionSet)\n                    <li class=\"card__row row gap-xs\">\n                        <div class=\"row reverse text-md reactions-condensed\">\n                            @foreach ($reactionSet->reactionTypes->reverse() as $reactionType)\n                                @icon($reactionType->icon)\n                            @endforeach\n                        </div>\n\n                        {{ $reactionSet->name }}\n\n                        <div class=\"grow\"></div>\n\n                        <x-waterhole::action-buttons\n                            class=\"text-xs\"\n                            :for=\"$reactionSet\"\n                            :limit=\"2\"\n                            context=\"cp\"\n                        />\n                    </li>\n                @endforeach\n            @else\n                <li class=\"placeholder\">No Reaction Sets</li>\n            @endif\n        </ul>\n    </div>\n</x-waterhole::cp>\n"
  },
  {
    "path": "resources/views/cp/reactions/reaction-set.blade.php",
    "content": "@php\n    $title = isset($reactionSet)\n        ? __('waterhole::cp.edit-reaction-set-title')\n        : __('waterhole::cp.create-reaction-set-title');\n@endphp\n\n<x-waterhole::cp :title=\"$title\">\n    <x-waterhole::cp.title\n        :parent-url=\"route('waterhole.cp.reaction-sets.index')\"\n        :parent-title=\"__('waterhole::cp.reactions-title')\"\n        :title=\"$title\"\n    />\n\n    <div class=\"stack gap-md\">\n        <x-waterhole::validation-errors />\n\n        <details class=\"card\" open>\n            <summary class=\"card__header h5\">\n                {{ __('waterhole::cp.reaction-set-details-title') }}\n            </summary>\n\n            <form\n                method=\"POST\"\n                action=\"{{\n                    isset($reactionSet)\n                        ? route('waterhole.cp.reaction-sets.update', compact('reactionSet'))\n                        : route('waterhole.cp.reaction-sets.store')\n                }}\"\n                enctype=\"multipart/form-data\"\n                class=\"card__body\"\n            >\n                @csrf\n                @if (isset($reactionSet))\n                    @method('PATCH')\n                @endif\n\n                <div class=\"stack dividers\">\n                    @components($form->fields())\n\n                    <div class=\"row gap-xs wrap\">\n                        <button type=\"submit\" class=\"btn bg-accent btn--wide\">\n                            {{\n                                isset($reactionSet)\n                                    ? __('waterhole::system.save-changes-button')\n                                    : __('waterhole::system.continue-button')\n                            }}\n                        </button>\n\n                        <a href=\"{{ route('waterhole.cp.reaction-sets.index') }}\" class=\"btn\">\n                            {{ __('waterhole::system.cancel-button') }}\n                        </a>\n                    </div>\n                </div>\n            </form>\n        </details>\n\n        @isset($reactionSet)\n            <details class=\"card\" open>\n                <summary class=\"card__header h5\">\n                    {{ __('waterhole::cp.reaction-types-title') }}\n                </summary>\n\n                <turbo-frame id=\"reaction-types\">\n                    <div class=\"card__body stack gap-md\">\n                        <div\n                            data-controller=\"sortable form\"\n                            data-action=\"sortable:update->form#submit\"\n                        >\n                            <ul\n                                class=\"card sortable\"\n                                role=\"list\"\n                                data-sortable-target=\"container\"\n                                aria-label=\"{{ __('waterhole::cp.reaction-set-reactions-label') }}\"\n                            >\n                                @empty($reactionSet->reactionTypes->load('reactionSet'))\n                                    <li class=\"placeholder\">\n                                        {{ __('waterhole::cp.reaction-types-empty-message') }}\n                                    </li>\n                                @else\n                                    @foreach ($reactionSet->reactionTypes->load('reactionSet') as $reactionType)\n                                        <li\n                                            class=\"card__row row gap-sm\"\n                                            aria-label=\"{{ $reactionType->name }}\"\n                                            data-id=\"{{ $reactionType->id }}\"\n                                        >\n                                            <button type=\"button\" class=\"drag-handle\" data-handle>\n                                                @icon('tabler-grip-vertical')\n                                            </button>\n\n                                            @icon($reactionType->icon)\n                                            {{ $reactionType->name }}\n                                            <span class=\"color-muted text-xs\">\n                                                {{ $reactionType->score > 0 ? '+' : '' }}{{ $reactionType->score }}\n                                            </span>\n\n                                            <x-waterhole::action-buttons\n                                                class=\"row text-xs push-end -m-xxs\"\n                                                :for=\"$reactionType\"\n                                                :limit=\"2\"\n                                                context=\"cp\"\n                                            />\n                                        </li>\n                                    @endforeach\n                                @endempty\n                            </ul>\n\n                            <form\n                                action=\"{{ route('waterhole.cp.reaction-sets.reaction-types.reorder', compact('reactionSet')) }}\"\n                                method=\"post\"\n                                data-form-target=\"form\"\n                                hidden\n                            >\n                                @csrf\n                                <input\n                                    type=\"hidden\"\n                                    name=\"order\"\n                                    data-sortable-target=\"orderInput\"\n                                />\n                            </form>\n                        </div>\n\n                        <div>\n                            <a\n                                href=\"{{ route('waterhole.cp.reaction-sets.reaction-types.create', compact('reactionSet')) }}\"\n                                class=\"btn\"\n                                data-turbo-frame=\"modal\"\n                            >\n                                @icon('tabler-plus')\n                                {{ __('waterhole::cp.reaction-types-add-button') }}\n                            </a>\n                        </div>\n                    </div>\n                </turbo-frame>\n            </details>\n        @endisset\n    </div>\n</x-waterhole::cp>\n"
  },
  {
    "path": "resources/views/cp/reactions/reaction-type.blade.php",
    "content": "@php\n    $title = isset($reactionType)\n        ? __('waterhole::cp.edit-reaction-type-title')\n        : __('waterhole::cp.create-reaction-type-title');\n@endphp\n\n<x-waterhole::cp :title=\"$title\">\n    <turbo-frame id=\"modal\">\n        <x-waterhole::dialog :title=\"$title\" class=\"dialog--sm\">\n            <form\n                method=\"POST\"\n                action=\"{{\n                    isset($reactionType)\n                        ? route('waterhole.cp.reaction-sets.reaction-types.update', compact('reactionSet', 'reactionType'))\n                        : route('waterhole.cp.reaction-sets.reaction-types.store', compact('reactionSet'))\n                }}\"\n                enctype=\"multipart/form-data\"\n            >\n                @csrf\n                @if (isset($reactionType))\n                    @method('PATCH')\n                @endif\n\n                <div class=\"stack gap-lg\">\n                    <x-waterhole::validation-errors />\n\n                    @components($form->fields())\n\n                    <div class=\"row gap-xs wrap\">\n                        <button type=\"submit\" class=\"btn bg-accent btn--wide\">\n                            {{\n                                isset($reactionType)\n                                    ? __('waterhole::system.save-changes-button')\n                                    : __('waterhole::system.create-button')\n                            }}\n                        </button>\n\n                        <a href=\"{{ url()->previous() }}\" class=\"btn\" data-action=\"modal#hide\">\n                            {{ __('waterhole::system.cancel-button') }}\n                        </a>\n                    </div>\n                </div>\n            </form>\n        </x-waterhole::dialog>\n    </turbo-frame>\n</x-waterhole::cp>\n"
  },
  {
    "path": "resources/views/cp/structure/channel.blade.php",
    "content": "@php\n    $title = isset($channel)\n        ? __('waterhole::cp.edit-channel-title')\n        : __('waterhole::cp.create-channel-title');\n@endphp\n\n<x-waterhole::cp :title=\"$title\">\n    <x-waterhole::cp.title\n        :parent-url=\"route('waterhole.cp.structure')\"\n        :parent-title=\"__('waterhole::cp.structure-title')\"\n        :title=\"$title\"\n    />\n\n    <form\n        method=\"POST\"\n        action=\"{{ isset($channel) ? route('waterhole.cp.structure.channels.update', compact('channel')) : route('waterhole.cp.structure.channels.store') }}\"\n        enctype=\"multipart/form-data\"\n    >\n        @csrf\n        @if (isset($channel))\n            @method('PATCH')\n        @endif\n\n        <div class=\"stack gap-lg\" data-controller=\"slugger\">\n            <x-waterhole::validation-errors />\n\n            <ui-accordion class=\"stack gap-md\">\n                @components($form->fields())\n            </ui-accordion>\n\n            <div class=\"row gap-xs wrap\">\n                <button type=\"submit\" class=\"btn bg-accent btn--wide\">\n                    {{ isset($channel) ? __('waterhole::system.save-changes-button') : __('waterhole::system.create-button') }}\n                </button>\n\n                <a href=\"{{ route('waterhole.cp.structure') }}\" class=\"btn\">\n                    {{ __('waterhole::system.cancel-button') }}\n                </a>\n            </div>\n        </div>\n    </form>\n</x-waterhole::cp>\n"
  },
  {
    "path": "resources/views/cp/structure/delete-channel.blade.php",
    "content": "<div class=\"stack gap-lg\" data-controller=\"reveal\">\n    <h1 class=\"h4\">\n        {{ __('waterhole::cp.delete-channel-title') }}\n        <x-waterhole::channel-label :channel=\"$channel\" />\n    </h1>\n\n    @if ($postCount > 0)\n        <div class=\"stack gap-sm\">\n            <label class=\"choice\">\n                <input\n                    @checked(! request('move_posts'))\n                    data-reveal-target=\"if\"\n                    name=\"move_posts\"\n                    type=\"radio\"\n                    value=\"0\"\n                />\n                {{ __('waterhole::cp.delete-channel-posts-label', ['count' => $postCount]) }}\n            </label>\n\n            <label class=\"choice\">\n                <input\n                    type=\"radio\"\n                    name=\"move_posts\"\n                    value=\"1\"\n                    @checked(request('move_posts'))\n                    data-reveal-target=\"if\"\n                />\n                {{ __('waterhole::cp.move-to-channel-posts-label', ['count' => $postCount]) }}\n            </label>\n\n            <x-waterhole::channel-picker\n                name=\"channel_id\"\n                :exclude=\"[$channel->id]\"\n                :value=\"request('channel_id')\"\n                data-reveal-target=\"then\"\n                data-reveal-value=\"1\"\n            />\n        </div>\n    @endif\n</div>\n"
  },
  {
    "path": "resources/views/cp/structure/heading.blade.php",
    "content": "@php\n    $title = isset($heading)\n        ? __('waterhole::cp.edit-heading-title')\n        : __('waterhole::cp.create-heading-title');\n@endphp\n\n<x-waterhole::cp :title=\"$title\">\n    <x-waterhole::cp.title\n        :parent-url=\"route('waterhole.cp.structure')\"\n        :parent-title=\"__('waterhole::cp.structure-title')\"\n        :title=\"$title\"\n    />\n\n    <form\n        method=\"POST\"\n        action=\"{{ isset($heading) ? route('waterhole.cp.structure.headings.update', compact('heading')) : route('waterhole.cp.structure.headings.store') }}\"\n        class=\"card card__body\"\n    >\n        @csrf\n        @if (isset($heading))\n            @method('PATCH')\n        @endif\n\n        <div class=\"stack dividers\">\n            <x-waterhole::validation-errors />\n\n            <x-waterhole::field name=\"name\" :label=\"__('waterhole::cp.heading-name-label')\">\n                <input\n                    type=\"text\"\n                    name=\"name\"\n                    id=\"{{ $component->id }}\"\n                    value=\"{{ old('name', $heading->name ?? null) }}\"\n                    autofocus\n                />\n            </x-waterhole::field>\n\n            <div class=\"row gap-xs wrap\">\n                <button type=\"submit\" class=\"btn bg-accent btn--wide\">\n                    {{ isset($heading) ? __('waterhole::system.save-changes-button') : __('waterhole::system.create-button') }}\n                </button>\n\n                <a href=\"{{ route('waterhole.cp.structure') }}\" class=\"btn\">\n                    {{ __('waterhole::system.cancel-button') }}\n                </a>\n            </div>\n        </div>\n    </form>\n</x-waterhole::cp>\n"
  },
  {
    "path": "resources/views/cp/structure/index.blade.php",
    "content": "<x-waterhole::cp :title=\"__('waterhole::cp.structure-title')\">\n    <div\n        class=\"stack gap-md\"\n        data-controller=\"sortable form\"\n        data-action=\"sortable:update->form#submit\"\n    >\n        <div class=\"row gap-xs\">\n            <h1 class=\"h3\">{{ __('waterhole::cp.structure-title') }}</h1>\n\n            <div class=\"grow\"></div>\n\n            <ui-popup placement=\"bottom-end\">\n                <button type=\"button\" class=\"btn bg-accent\">\n                    @icon('tabler-plus')\n                    <span>{{ __('waterhole::system.create-button') }}</span>\n                    @icon('tabler-chevron-down')\n                </button>\n\n                <ui-menu class=\"menu\" hidden>\n                    <a\n                        href=\"{{ route('waterhole.cp.structure.channels.create') }}\"\n                        type=\"button\"\n                        class=\"menu-item\"\n                        role=\"menuitem\"\n                    >\n                        @icon('tabler-message-circle-2')\n                        <span>{{ __('waterhole::cp.structure-channel-label') }}</span>\n                    </a>\n                    <a\n                        href=\"{{ route('waterhole.cp.structure.pages.create') }}\"\n                        type=\"button\"\n                        class=\"menu-item\"\n                        role=\"menuitem\"\n                    >\n                        @icon('tabler-file-text')\n                        <span>{{ __('waterhole::cp.structure-page-label') }}</span>\n                    </a>\n                    <a\n                        href=\"{{ route('waterhole.cp.structure.links.create') }}\"\n                        class=\"menu-item\"\n                        role=\"menuitem\"\n                    >\n                        @icon('tabler-link')\n                        <span>{{ __('waterhole::cp.structure-link-label') }}</span>\n                    </a>\n                    <a\n                        href=\"{{ route('waterhole.cp.structure.headings.create') }}\"\n                        class=\"menu-item\"\n                        role=\"menuitem\"\n                    >\n                        @icon('tabler-hash')\n                        <span>{{ __('waterhole::cp.structure-heading-label') }}</span>\n                    </a>\n                </ui-menu>\n            </ui-popup>\n        </div>\n\n        <ul\n            class=\"card sortable\"\n            role=\"list\"\n            aria-label=\"{{ __('waterhole::cp.structure-navigation-title') }}\"\n            data-sortable-target=\"container\"\n        >\n            @foreach ($structure->where('is_listed', true) as $node)\n                <x-waterhole::cp.structure-node :node=\"$node\" />\n            @endforeach\n\n            <li class=\"placeholder hide-if-not-only-child\">\n                {{ __('waterhole::cp.structure-navigation-description') }}\n            </li>\n        </ul>\n\n        <div class=\"stack gap-md\" style=\"margin-top: var(--space-xl)\">\n            <h2 class=\"h4\">{{ __('waterhole::cp.structure-unlisted-title') }}</h2>\n\n            <ul\n                class=\"card sortable\"\n                role=\"list\"\n                aria-label=\"{{ __('waterhole::cp.structure-unlisted-title') }}\"\n                data-sortable-target=\"container\"\n            >\n                @foreach ($structure->where('is_listed', false) as $node)\n                    <x-waterhole::cp.structure-node :node=\"$node\" />\n                @endforeach\n\n                <li class=\"placeholder hide-if-not-only-child\">\n                    {{ __('waterhole::cp.structure-unlisted-description') }}\n                </li>\n            </ul>\n        </div>\n\n        <turbo-frame id=\"structure_form\" hidden>\n            <form\n                action=\"{{ route('waterhole.cp.structure') }}\"\n                method=\"post\"\n                data-form-target=\"form\"\n            >\n                @csrf\n                <input type=\"hidden\" name=\"order\" data-sortable-target=\"orderInput\" />\n            </form>\n        </turbo-frame>\n    </div>\n</x-waterhole::cp>\n"
  },
  {
    "path": "resources/views/cp/structure/link.blade.php",
    "content": "@php\n    $title = isset($link)\n        ? __('waterhole::cp.edit-link-title')\n        : __('waterhole::cp.create-link-title');\n@endphp\n\n<x-waterhole::cp :title=\"$title\">\n    <x-waterhole::cp.title\n        :parent-url=\"route('waterhole.cp.structure')\"\n        :parent-title=\"__('waterhole::cp.structure-title')\"\n        :title=\"$title\"\n    />\n\n    <form\n        method=\"POST\"\n        action=\"{{ isset($link) ? route('waterhole.cp.structure.links.update', compact('link')) : route('waterhole.cp.structure.links.store') }}\"\n        enctype=\"multipart/form-data\"\n    >\n        @csrf\n        @if (isset($link))\n            @method('PATCH')\n        @endif\n\n        <div class=\"stack gap-lg\">\n            <x-waterhole::validation-errors />\n\n            <div class=\"stack gap-md\">\n                @components($form->fields())\n            </div>\n\n            <div class=\"row gap-xs wrap\">\n                <button type=\"submit\" class=\"btn bg-accent btn--wide\">\n                    {{ isset($link) ? __('waterhole::system.save-changes-button') : __('waterhole::system.create-button') }}\n                </button>\n\n                <a href=\"{{ route('waterhole.cp.structure') }}\" class=\"btn\">\n                    {{ __('waterhole::system.cancel-button') }}\n                </a>\n            </div>\n        </div>\n    </form>\n</x-waterhole::cp>\n"
  },
  {
    "path": "resources/views/cp/structure/page.blade.php",
    "content": "@php\n    $title = isset($page)\n        ? __('waterhole::cp.edit-page-title')\n        : __('waterhole::cp.create-page-title');\n@endphp\n\n<x-waterhole::cp :title=\"$title\">\n    <x-waterhole::cp.title\n        :parent-url=\"route('waterhole.cp.structure')\"\n        :parent-title=\"__('waterhole::cp.structure-title')\"\n        :title=\"$title\"\n    />\n\n    <form\n        method=\"POST\"\n        action=\"{{ isset($page) ? route('waterhole.cp.structure.pages.update', compact('page')) : route('waterhole.cp.structure.pages.store') }}\"\n        enctype=\"multipart/form-data\"\n    >\n        @csrf\n        @if (isset($page))\n            @method('PATCH')\n        @endif\n\n        <div class=\"stack gap-lg\" data-controller=\"slugger\">\n            <x-waterhole::validation-errors />\n\n            <div class=\"stack gap-md\">\n                @components($form->fields())\n            </div>\n\n            <div class=\"row gap-xs wrap\">\n                <button type=\"submit\" class=\"btn bg-accent btn--wide\">\n                    {{ isset($page) ? __('waterhole::system.save-changes-button') : __('waterhole::system.create-button') }}\n                </button>\n\n                <a href=\"{{ route('waterhole.cp.structure') }}\" class=\"btn\">\n                    {{ __('waterhole::system.cancel-button') }}\n                </a>\n            </div>\n        </div>\n    </form>\n</x-waterhole::cp>\n"
  },
  {
    "path": "resources/views/cp/taxonomies/index.blade.php",
    "content": "<x-waterhole::cp :title=\"__('waterhole::cp.taxonomies-title')\">\n    <div class=\"stack gap-md\">\n        <div class=\"row gap-md\">\n            <h1 class=\"h3\">{{ __('waterhole::cp.taxonomies-title') }}</h1>\n\n            <div class=\"grow\"></div>\n\n            <a\n                href=\"{{ route('waterhole.cp.taxonomies.create') }}\"\n                type=\"button\"\n                class=\"btn bg-accent\"\n            >\n                @icon('tabler-plus')\n                <span>{{ __('waterhole::cp.create-taxonomy-button') }}</span>\n            </a>\n        </div>\n\n        <ul class=\"card\" role=\"list\">\n            @if ($taxonomies->count())\n                @foreach ($taxonomies as $taxonomy)\n                    <li class=\"card__row row gap-xs\">\n                        {{ $taxonomy->name }}\n\n                        <div class=\"grow\"></div>\n\n                        <x-waterhole::action-buttons\n                            class=\"row text-xs\"\n                            :for=\"$taxonomy\"\n                            :limit=\"2\"\n                        />\n                    </li>\n                @endforeach\n            @else\n                <li class=\"placeholder\">No Taxonomies</li>\n            @endif\n        </ul>\n    </div>\n</x-waterhole::cp>\n"
  },
  {
    "path": "resources/views/cp/taxonomies/tag.blade.php",
    "content": "@php\n    $title = isset($tag)\n        ? __('waterhole::cp.edit-tag-title')\n        : __('waterhole::cp.create-tag-title');\n@endphp\n\n<x-waterhole::cp :title=\"$title\">\n    <turbo-frame id=\"modal\">\n        <x-waterhole::dialog :title=\"$title\" class=\"dialog--sm\">\n            <form\n                method=\"POST\"\n                action=\"{{\n                    isset($tag)\n                        ? route('waterhole.cp.taxonomies.tags.update', compact('taxonomy', 'tag'))\n                        : route('waterhole.cp.taxonomies.tags.store', compact('taxonomy'))\n                }}\"\n                enctype=\"multipart/form-data\"\n                data-turbo-frame=\"tags\"\n            >\n                @csrf\n                @if (isset($tag))\n                    @method('PATCH')\n                @endif\n\n                <div class=\"stack gap-lg\">\n                    <x-waterhole::validation-errors />\n\n                    @components($form->fields())\n\n                    <div class=\"row gap-xs wrap\">\n                        <button type=\"submit\" class=\"btn bg-accent btn--wide\">\n                            {{\n                                isset($tag)\n                                    ? __('waterhole::system.save-changes-button')\n                                    : __('waterhole::system.create-button')\n                            }}\n                        </button>\n\n                        <a href=\"{{ url()->previous() }}\" class=\"btn\" data-action=\"modal#hide\">\n                            {{ __('waterhole::system.cancel-button') }}\n                        </a>\n                    </div>\n                </div>\n            </form>\n        </x-waterhole::dialog>\n    </turbo-frame>\n</x-waterhole::cp>\n"
  },
  {
    "path": "resources/views/cp/taxonomies/taxonomy.blade.php",
    "content": "@php\n    $title = isset($taxonomy)\n        ? __('waterhole::cp.edit-taxonomy-title')\n        : __('waterhole::cp.create-taxonomy-title');\n@endphp\n\n<x-waterhole::cp :title=\"$title\">\n    <x-waterhole::cp.title\n        :parent-url=\"route('waterhole.cp.taxonomies.index')\"\n        :parent-title=\"__('waterhole::cp.taxonomies-title')\"\n        :title=\"$title\"\n    />\n\n    <div class=\"stack gap-xl\">\n        <form\n            method=\"POST\"\n            action=\"{{\n                isset($taxonomy)\n                    ? route('waterhole.cp.taxonomies.update', compact('taxonomy'))\n                    : route('waterhole.cp.taxonomies.store')\n            }}\"\n            enctype=\"multipart/form-data\"\n            class=\"stack gap-md\"\n        >\n            @csrf\n            @if (isset($taxonomy))\n                @method('PATCH')\n            @endif\n\n            <x-waterhole::validation-errors />\n\n            @components($form->fields())\n\n            <div class=\"row gap-xs wrap\">\n                <button type=\"submit\" class=\"btn bg-accent btn--wide\">\n                    {{\n                        isset($taxonomy)\n                            ? __('waterhole::system.save-changes-button')\n                            : __('waterhole::system.continue-button')\n                    }}\n                </button>\n\n                <a href=\"{{ route('waterhole.cp.taxonomies.index') }}\" class=\"btn\">\n                    {{ __('waterhole::system.cancel-button') }}\n                </a>\n            </div>\n        </form>\n\n        @isset($taxonomy)\n            <details class=\"card\" open>\n                <summary class=\"card__header h5\">\n                    {{ __('waterhole::cp.taxonomy-tags-title') }}\n                </summary>\n                <turbo-frame id=\"tags\" data-action=\"turbo:frame-load->page#closeModal\">\n                    <div class=\"card__body stack gap-md\">\n                        <ul class=\"card\" role=\"list\">\n                            @foreach ($taxonomy->tags->load('taxonomy') as $tag)\n                                <x-waterhole::cp.tag-row :tag=\"$tag\" />\n                            @endforeach\n\n                            <li class=\"placeholder hide-if-not-only-child\" id=\"tag-list-end\">\n                                No Tags\n                            </li>\n                        </ul>\n\n                        <div>\n                            <a\n                                href=\"{{ route('waterhole.cp.taxonomies.tags.create', compact('taxonomy')) }}\"\n                                class=\"btn\"\n                                data-turbo-frame=\"modal\"\n                            >\n                                @icon('tabler-plus')\n                                Add\n                            </a>\n                        </div>\n                    </div>\n                </turbo-frame>\n            </details>\n        @endisset\n    </div>\n</x-waterhole::cp>\n"
  },
  {
    "path": "resources/views/cp/users/delete.blade.php",
    "content": "<div class=\"stack gap-lg\">\n    <h1 class=\"h3\">\n        {{ __('waterhole::cp.delete-user-title', ['count' => $users->count()]) }}\n        @if ($users->count() === 1)\n            <x-waterhole::user-label :user=\"$users[0]\" />\n        @endif\n    </h1>\n\n    <div class=\"stack gap-sm\">\n        <label class=\"choice\">\n            <input type=\"radio\" name=\"delete_content\" value=\"0\" checked />\n            {{ __('waterhole::cp.keep-user-content-label') }}\n        </label>\n\n        <label class=\"choice\">\n            <input type=\"radio\" name=\"delete_content\" value=\"1\" />\n            {{ __('waterhole::cp.delete-user-content-label') }}\n        </label>\n    </div>\n</div>\n"
  },
  {
    "path": "resources/views/cp/users/form.blade.php",
    "content": "@php\n    $title = isset($user)\n        ? __('waterhole::cp.edit-user-title')\n        : __('waterhole::cp.create-user-title');\n@endphp\n\n<x-waterhole::cp :title=\"$title\">\n    <x-waterhole::cp.title\n        :parent-url=\"route('waterhole.cp.users.index')\"\n        :parent-title=\"__('waterhole::cp.users-title')\"\n        :title=\"$title\"\n    />\n\n    <form\n        method=\"POST\"\n        action=\"{{ isset($user) ? route('waterhole.cp.users.update', compact('user')) : route('waterhole.cp.users.store') }}\"\n        enctype=\"multipart/form-data\"\n    >\n        @csrf\n        @isset($user)\n            @method('PATCH')\n        @endisset\n\n        @return\n\n        <div class=\"stack gap-lg\">\n            <x-waterhole::validation-errors />\n\n            <div class=\"stack gap-md\">\n                @components($form->fields())\n            </div>\n\n            <div class=\"row gap-xs wrap\">\n                <button type=\"submit\" class=\"btn bg-accent btn--wide\">\n                    {{ isset($user) ? __('waterhole::system.save-changes-button') : __('waterhole::system.create-button') }}\n                </button>\n\n                <x-waterhole::cancel :default=\"route('waterhole.cp.users.index')\" class=\"btn\" />\n            </div>\n        </div>\n    </form>\n</x-waterhole::cp>\n"
  },
  {
    "path": "resources/views/cp/users/index.blade.php",
    "content": "<x-waterhole::cp :title=\"__('waterhole::cp.users-title')\">\n    <div class=\"stack gap-md\">\n        <div class=\"row gap-sm wrap\">\n            <h1 class=\"h3\">\n                {{ __('waterhole::cp.users-title') }}\n            </h1>\n\n            <div class=\"grow\"></div>\n\n            <form\n                class=\"combobox break-sm\"\n                data-controller=\"filter-input\"\n                data-turbo-action=\"replace\"\n                data-turbo-frame=\"users_frame\"\n            >\n                <div class=\"input-container\">\n                    @icon('tabler-search', ['class' => 'no-pointer color-muted'])\n                    <input\n                        data-action=\"\n                            incremental-search#input\n                            focus->filter-input#focus\n                            blur->filter-input#blur\n                            filter-input#update\n                        \"\n                        data-controller=\"incremental-search\"\n                        data-filter-input-target=\"input\"\n                        name=\"q\"\n                        placeholder=\"{{ __('waterhole::cp.users-filter-placeholder') }}\"\n                        type=\"search\"\n                        value=\"{{ request('q') }}\"\n                    />\n                </div>\n\n                <ul\n                    class=\"menu combobox__list\"\n                    data-action=\"\n                        combobox-commit->filter-input#commit\n                        mousedown->filter-input#preventBlur\n                    \"\n                    data-filter-input-target=\"list\"\n                    hidden\n                    id=\"filter-suggestions\"\n                    role=\"listbox\"\n                >\n                    <li id=\"filter-group\" role=\"option\" class=\"menu-item\" data-value=\"group:\">\n                        <span class=\"menu-item__title\">group:</span>\n                        <span class=\"color-muted\">\n                            {{ __('waterhole::cp.users-filter-group-description') }}\n                        </span>\n                    </li>\n                    @foreach (Waterhole\\Models\\Group::selectable()->get() as $group)\n                        <li id=\"filter-group-{{ $group->id }}\" role=\"option\" class=\"menu-item\">\n                            <span class=\"menu-item__title\">\n                                group:{{ str_contains($group->name, ' ') ? '\"' . $group->name . '\"' : $group->name }}\n                            </span>\n                        </li>\n                    @endforeach\n\n                    <li role=\"option\" class=\"menu-item\" data-value=\"is:suspended\">\n                        <span class=\"menu-item__title\">is:suspended</span>\n                    </li>\n                </ul>\n            </form>\n\n            <a href=\"{{ route('waterhole.cp.users.create') }}\" type=\"button\" class=\"btn bg-accent\">\n                @icon('tabler-plus')\n                <span>{{ __('waterhole::cp.create-user-button') }}</span>\n            </a>\n        </div>\n\n        <turbo-frame id=\"users_frame\" target=\"_top\" class=\"stack gap-md\">\n            @if ($users->isNotEmpty())\n                <div class=\"card\">\n                    <div class=\"table-container full-width\" tabindex=\"0\">\n                        <table class=\"table\">\n                            <thead>\n                                <tr>\n                                    @foreach (['name', 'email', 'groups', 'created_at', 'last_seen_at'] as $column)\n                                        <th>\n                                            @if (in_array($column, $sortable))\n                                                <a\n                                                    href=\"{{\n                                                        request()->fullUrlWithQuery([\n                                                            'sort' => $column,\n                                                            'direction' => $sort === $column ? ($direction === 'asc' ? 'desc' : 'asc') : null,\n                                                            'page' => null,\n                                                        ])\n                                                    }}\"\n                                                    class=\"with-icon color-text\"\n                                                >\n                                                    <span>\n                                                        {{ __('waterhole::cp.users-' . str_replace('_', '-', $column) . '-column') }}\n                                                    </span>\n                                                    @if ($sort === $column)\n                                                        @icon('tabler-chevron-' . ($direction === 'asc' ? 'up' : 'down'))\n                                                    @endif\n                                                </a>\n                                            @else\n                                                {{ __('waterhole::cp.users-' . str_replace('_', '-', $column) . '-column') }}\n                                            @endif\n                                        </th>\n                                    @endforeach\n\n                                    <th style=\"width: 1px\"></th>\n                                </tr>\n                            </thead>\n\n                            <tbody>\n                                @foreach ($users as $user)\n                                    <tr>\n                                        <td>\n                                            <x-waterhole::user-label\n                                                :user=\"$user\"\n                                                class=\"color-text\"\n                                                link\n                                                target=\"_blank\"\n                                            />\n                                        </td>\n\n                                        <td>\n                                            <a href=\"mailto:{{ $user->email }}\">\n                                                {{ Str::limit($user->email, 20) }}\n                                            </a>\n                                        </td>\n\n                                        <td>\n                                            <x-waterhole::user-groups :user=\"$user\" />\n                                        </td>\n\n                                        <td>\n                                            <x-waterhole::relative-time\n                                                :datetime=\"$user->created_at\"\n                                            />\n                                        </td>\n\n                                        <td>\n                                            <x-waterhole::relative-time\n                                                :datetime=\"$user->last_seen_at\"\n                                            />\n                                        </td>\n\n                                        <td>\n                                            <x-waterhole::action-buttons\n                                                :for=\"$user\"\n                                                :limit=\"2\"\n                                                context=\"cp\"\n                                            />\n                                        </td>\n                                    </tr>\n                                @endforeach\n                            </tbody>\n                        </table>\n                    </div>\n                </div>\n\n                <div>\n                    {{ $users->withQueryString()->links('waterhole::pagination.default') }}\n                </div>\n            @else\n                <div class=\"placeholder card\">\n                    @icon('tabler-search', ['class' => 'placeholder__icon'])\n                    <h4>{{ __('waterhole::cp.users-empty-message') }}</h4>\n                </div>\n            @endif\n        </turbo-frame>\n    </div>\n</x-waterhole::cp>\n"
  },
  {
    "path": "resources/views/cp/users/suspend.blade.php",
    "content": "<div class=\"stack gap-lg\">\n    <h1 class=\"h3\">\n        {{ __('waterhole::user.suspend-user-title') }}\n        @if ($users->count() === 1)\n            <x-waterhole::user-label :user=\"$users[0]\" />\n        @endif\n    </h1>\n\n    <div class=\"stack gap-sm\" data-controller=\"reveal\">\n        <label class=\"choice\">\n            <input\n                type=\"radio\"\n                name=\"status\"\n                value=\"none\"\n                data-reveal-target=\"if\"\n                @checked(! $users[0]->suspended_until)\n            />\n            {{ __('waterhole::user.not-suspended-label') }}\n        </label>\n\n        <label class=\"choice\">\n            <input\n                type=\"radio\"\n                name=\"status\"\n                value=\"indefinite\"\n                data-reveal-target=\"if\"\n                @checked($indefinite = $users[0]->suspended_until?->year === 2038)\n            />\n            {{ __('waterhole::user.suspended-indefinitely-label') }}\n        </label>\n\n        <label class=\"choice\">\n            <input\n                type=\"radio\"\n                name=\"status\"\n                value=\"custom\"\n                data-reveal-target=\"if\"\n                @checked($users[0]->suspended_until && ! $indefinite)\n            />\n            {{ __('waterhole::user.suspended-until-label') }}\n        </label>\n\n        <span class=\"choice\" data-reveal-target=\"then\" data-reveal-value=\"custom\">\n            <input\n                type=\"datetime-local\"\n                name=\"suspended_until\"\n                value=\"{{ $indefinite ? '' : $users[0]->suspended_until }}\"\n            />\n        </span>\n    </div>\n</div>\n"
  },
  {
    "path": "resources/views/cp/widget.blade.php",
    "content": "<turbo-frame id=\"widget_{{ $id }}\">\n    @components([$widget['component']], compact('id') + $widget)\n</turbo-frame>\n"
  },
  {
    "path": "resources/views/forum/channel.blade.php",
    "content": "<x-waterhole::layout\n    :title=\"$channel->name\"\n    :data-channel=\"$channel->slug\"\n    :seo=\"[\n        'description' => $channel->description_text,\n        'url' => route('waterhole.channels.show', compact('channel')),\n        'noindex' => !$channel->structure->is_listed,\n        'schema' => ['@type' => 'CollectionPage'],\n    ]\"\n>\n    <x-slot name=\"head\">\n        <link\n            rel=\"alternate\"\n            type=\"application/rss+xml\"\n            href=\"{{ route('waterhole.rss.channel', compact('channel')) }}\"\n        />\n    </x-slot>\n\n    <x-waterhole::index :channel=\"$channel\">\n        <h1 class=\"visually-hidden\">{{ $channel->name }}</h1>\n\n        <x-waterhole::post-feed :feed=\"$feed\" :channel=\"$channel\" />\n    </x-waterhole::index>\n</x-waterhole::layout>\n"
  },
  {
    "path": "resources/views/forum/home.blade.php",
    "content": "<x-waterhole::layout>\n    <x-slot name=\"head\">\n        <link\n            rel=\"alternate\"\n            type=\"application/rss+xml\"\n            href=\"{{ route('waterhole.rss.posts') }}\"\n        />\n    </x-slot>\n\n    <x-waterhole::index>\n        <h1 class=\"visually-hidden\">{{ config('waterhole.forum.name') }}</h1>\n\n        <x-waterhole::post-feed :feed=\"$feed\" />\n    </x-waterhole::index>\n</x-waterhole::layout>\n"
  },
  {
    "path": "resources/views/forum/moderation.blade.php",
    "content": "<x-waterhole::layout>\n    <div class=\"container section\">\n        <turbo-frame id=\"moderation\" class=\"card card__body\">\n            <div class=\"row gap-xs justify-between menu-sticky\">\n                <h1 class=\"menu-heading\">{{ __('waterhole::forum.moderation-title') }}</h1>\n            </div>\n\n            @if ($pendingFlags->isNotEmpty())\n                <x-waterhole::infinite-scroll :paginator=\"$pendingFlags\">\n                    @foreach ($pendingFlags as $flag)\n                        <a\n                            href=\"{{ $flag->subject->flagUrl() }}\"\n                            class=\"menu-item notification p-sm gap-sm\"\n                            role=\"menuitem\"\n                        >\n                            @icon(\n                                $flag->subject instanceof Waterhole\\Models\\Post\n                                    ? $flag->subject->channel->icon\n                                    : 'tabler-message-circle-2',\n                                ['class' => 'color-muted text-md']\n                            )\n\n                            <span class=\"shrink\">\n                                <x-waterhole::flag-summary :subject=\"$flag->subject\" />\n\n                                <span class=\"menu-item__description overflow-ellipsis\">\n                                    <x-waterhole::user-label :user=\"$flag->subject->user\" />\n                                    ·\n                                    {{\n                                        Str::limit(\n                                            $flag->subject instanceof Waterhole\\Models\\Post\n                                                ? $flag->subject->title\n                                                : $flag->subject->post->title,\n                                            200,\n                                        )\n                                    }}\n                                </span>\n                            </span>\n\n                            <x-waterhole::relative-time\n                                :datetime=\"$flag->created_at\"\n                                class=\"notification__time text-xs color-muted push-end nowrap\"\n                            />\n                        </a>\n                    @endforeach\n                </x-waterhole::infinite-scroll>\n            @else\n                <div class=\"placeholder\">\n                    @icon('tabler-flag-check', ['class' => 'placeholder__icon'])\n                    <p class=\"h4\">{{ __('waterhole::forum.moderation-empty-message') }}</p>\n                </div>\n            @endif\n        </turbo-frame>\n    </div>\n</x-waterhole::layout>\n"
  },
  {
    "path": "resources/views/forum/notifications.blade.php",
    "content": "<x-waterhole::layout>\n    <div class=\"container section\">\n        <turbo-frame id=\"notifications\" class=\"card card__body\">\n            <div class=\"row gap-xs justify-between menu-sticky\">\n                <h1 class=\"menu-heading\">{{ __('waterhole::notifications.title') }}</h1>\n\n                <div class=\"row\">\n                    <form action=\"{{ route('waterhole.notifications.read') }}\" method=\"POST\">\n                        @csrf\n                        <button type=\"submit\" class=\"btn btn--icon btn--transparent\">\n                            @icon('tabler-check')\n                            <ui-tooltip>\n                                {{ __('waterhole::notifications.mark-all-as-read-button') }}\n                            </ui-tooltip>\n                        </button>\n                    </form>\n\n                    <a\n                        href=\"{{ route('waterhole.preferences.notifications') }}\"\n                        class=\"btn btn--icon btn--transparent\"\n                        data-turbo-frame=\"_top\"\n                    >\n                        @icon('tabler-settings')\n                        <ui-tooltip>\n                            {{ __('waterhole::notifications.preferences-button') }}\n                        </ui-tooltip>\n                    </a>\n                </div>\n            </div>\n\n            @if ($notifications->isNotEmpty())\n                <x-waterhole::infinite-scroll :paginator=\"$notifications\">\n                    @foreach ($notifications as $notification)\n                        <x-waterhole::notification :notification=\"$notification\" />\n                    @endforeach\n                </x-waterhole::infinite-scroll>\n            @else\n                <div class=\"placeholder\">\n                    @icon('tabler-bell', ['class' => 'placeholder__icon'])\n                    <p class=\"h4\">{{ __('waterhole::notifications.empty-message') }}</p>\n                </div>\n            @endif\n        </turbo-frame>\n    </div>\n</x-waterhole::layout>\n"
  },
  {
    "path": "resources/views/forum/page.blade.php",
    "content": "<x-waterhole::layout\n    :title=\"$page->name\"\n    :seo=\"[\n        'description' => $page->body_html,\n        'url' => route('waterhole.page', compact('page')),\n        'noindex' => ! $page->structure->is_listed,\n        'schema' => ['@type' => 'WebPage'],\n    ]\"\n>\n    <x-waterhole::index>\n        <div class=\"stack gap-xl measure card p-gutter\">\n            <h1 data-page-target=\"title\">{{ $page->name }}</h1>\n\n            <div class=\"content text-md\">\n                {{ $page->body_html }}\n            </div>\n        </div>\n    </x-waterhole::index>\n</x-waterhole::layout>\n"
  },
  {
    "path": "resources/views/forum/search.blade.php",
    "content": "@php\n    $title = __('waterhole::forum.search-results-title', ['query' => request('q')]);\n@endphp\n\n<x-waterhole::layout :title=\"$title\">\n    <div class=\"container section stack gap-xl\">\n        @if (request('q'))\n            <h1 hidden data-page-target=\"title\">\n                <span>{{ $title }}</span>\n            </h1>\n        @endif\n\n        <form\n            action=\"{{ route('waterhole.search') }}\"\n            class=\"lead row gap-xs card card__body\"\n            role=\"search\"\n        >\n            <div class=\"input-container full-width\">\n                @icon('tabler-search', ['class' => 'no-pointer'])\n                <input\n                    type=\"search\"\n                    name=\"q\"\n                    value=\"{{ request('q') }}\"\n                    placeholder=\"{{ __('waterhole::forum.search-placeholder') }}\"\n                    aria-label=\"{{ __('waterhole::forum.search-placeholder') }}\"\n                    autofocus\n                />\n            </div>\n            <button type=\"submit\" class=\"btn bg-accent\">\n                {{ __('waterhole::forum.search-button') }}\n            </button>\n        </form>\n\n        @isset($hits)\n            @if ($hits->count())\n                <div class=\"with-sidebar\">\n                    <div class=\"sidebar sidebar--sticky\">\n                        <x-waterhole::collapsible-nav\n                            :components=\"$channels\n                                ->map(fn($channel) => new Waterhole\\View\\Components\\NavLink(\n                                    label: $channel->name,\n                                    icon: $channel->icon,\n                                    badge: $results->channelHits[$channel->id] ?? null,\n                                    href: $selectedChannels->contains($channel) ? request()->fullUrlWithoutQuery(['channels', 'page']) : request()->fullUrlWithQuery(['channels' => $channel->id, 'page' => null]),\n                                    active: $selectedChannels->contains($channel)\n                                ))\n                            ->all()\"\n                        >\n                            <x-slot name=\"empty\">\n                                @icon('tabler-filter')\n                                <span>{{ __('waterhole::forum.search-filter-button') }}</span>\n                            </x-slot>\n                        </x-waterhole::collapsible-nav>\n                    </div>\n\n                    <div class=\"stack gap-md\">\n                        <div class=\"row gap-xs wrap justify-between\">\n                            <h2 class=\"h4\">\n                                {{ __('waterhole::forum.search-showing-results' . ($results->exhaustiveTotal ? '' : '-non-exhaustive') . '-title', ['total' => $results->total]) }}\n                            </h2>\n\n                            <x-waterhole::selector\n                                placement=\"bottom-end\"\n                                button-class=\"btn btn--sm btn--transparent color-accent\"\n                                :value=\"$currentSort\"\n                                :options=\"$sorts\"\n                                :label='fn($sort) => __(\"waterhole::forum.search-sort-$sort\")'\n                                :href=\"fn($sort) => request()->fullUrlWithQuery(['sort' => $sort, 'page' => null])\"\n                            />\n                        </div>\n\n                        <div class=\"card search-results post-list\">\n                            <x-waterhole::infinite-scroll :paginator=\"$hits\" divider>\n                                @foreach ($hits as $hit)\n                                    <x-waterhole::post-list-item\n                                        :post=\"$hit->post\"\n                                        :excerpt=\"$hit->body\"\n                                    />\n                                @endforeach\n                            </x-waterhole::infinite-scroll>\n                        </div>\n                    </div>\n                </div>\n            @else\n                <div class=\"placeholder\">\n                    @icon('tabler-search', ['class' => 'placeholder__icon'])\n\n                    <p class=\"h4\">\n                        {{ __('waterhole::forum.search-empty-message') }}\n                    </p>\n\n                    @if ($results->error)\n                        <p>{{ $results->error }}</p>\n                    @endif\n                </div>\n            @endif\n        @endisset\n    </div>\n</x-waterhole::layout>\n"
  },
  {
    "path": "resources/views/mail/email.blade.php",
    "content": "<x-mail::message>\n@foreach ($introLines as $line)\n{{ $line }}\n\n@endforeach\n\n@isset($actionText)\n<?php\n    $color = match ($level) {\n        'success', 'error' => $level,\n        default => 'primary',\n    };\n?>\n\n<x-mail::button :url=\"$actionUrl\" :color=\"$color\">\n{{ $actionText }}\n</x-mail::button>\n@endisset\n\n@foreach ($outroLines as $line)\n{{ $line }}\n\n@endforeach\n</x-mail::message>\n"
  },
  {
    "path": "resources/views/mail/html/button.blade.php",
    "content": "@props([\n    'url',\n    'color' => 'primary',\n    'align' => 'center',\n])\n<table class=\"action\" align=\"{{ $align }}\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" role=\"presentation\">\n<tr>\n<td align=\"{{ $align }}\">\n<table width=\"100%\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" role=\"presentation\">\n<tr>\n<td align=\"{{ $align }}\">\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" role=\"presentation\">\n<tr>\n<td>\n<a href=\"{{ $url }}\" class=\"button button-{{ $color }}\" target=\"_blank\" rel=\"noopener\">{{ $slot }}</a>\n</td>\n</tr>\n</table>\n</td>\n</tr>\n</table>\n</td>\n</tr>\n</table>\n"
  },
  {
    "path": "resources/views/mail/html/header.blade.php",
    "content": "@props(['url'])\n<tr>\n<td class=\"header\">\n<a href=\"{{ $url }}\" style=\"display: inline-block;\">\n{{ $slot }}\n</a>\n</td>\n</tr>\n"
  },
  {
    "path": "resources/views/mail/html/layout.blade.php",
    "content": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\n<head>\n<title>{{ config('waterhole.forum.name') }}</title>\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n<meta name=\"color-scheme\" content=\"light\">\n<meta name=\"supported-color-schemes\" content=\"light\">\n<style>\n@media only screen and (max-width: 600px) {\n.inner-body {\nwidth: 100% !important;\n}\n.footer {\nwidth: 100% !important;\n}\n}\n@media only screen and (max-width: 500px) {\n.button {\nwidth: 100% !important;\n}\n}\n</style>\n</head>\n<body>\n\n<table class=\"wrapper\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" role=\"presentation\">\n<tr>\n<td align=\"center\">\n<table class=\"content\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" role=\"presentation\">\n{{ $header ?? '' }}\n\n<!-- Email Body -->\n<tr>\n<td class=\"body\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"border: hidden !important;\">\n<table class=\"inner-body\" align=\"center\" width=\"570\" cellpadding=\"0\" cellspacing=\"0\" role=\"presentation\">\n<!-- Body content -->\n<tr>\n<td class=\"content-cell\">\n{{ $slot }}\n\n{{ $subcopy ?? '' }}\n</td>\n</tr>\n</table>\n</td>\n</tr>\n\n{{ $footer ?? '' }}\n</table>\n</td>\n</tr>\n</table>\n</body>\n</html>\n"
  },
  {
    "path": "resources/views/mail/html/link.blade.php",
    "content": "@props(['url'])\n<a href=\"{{ $url }}\" target=\"_blank\" rel=\"noopener\">{{ $slot }}</a>\n"
  },
  {
    "path": "resources/views/mail/html/message.blade.php",
    "content": "<x-mail::layout>\n{{-- Header --}}\n<x-slot:header>\n<x-mail::header :url=\"route('waterhole.home')\">\n{{ config('waterhole.forum.name') }}\n</x-mail::header>\n</x-slot:header>\n\n{{-- Body --}}\n{{ $slot }}\n\n{{-- Subcopy --}}\n@isset($subcopy)\n<x-slot:subcopy>\n<x-mail::subcopy>\n{{ $subcopy }}\n</x-mail::subcopy>\n</x-slot:subcopy>\n@endisset\n\n{{-- Footer --}}\n<x-slot:footer>\n<x-mail::footer/>\n</x-slot:footer>\n</x-mail::layout>\n"
  },
  {
    "path": "resources/views/mail/html/subcopy.blade.php",
    "content": "<table class=\"subcopy\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" role=\"presentation\">\n<tr>\n<td>\n{{ $slot }}\n</td>\n</tr>\n</table>\n"
  },
  {
    "path": "resources/views/mail/html/themes/default.css",
    "content": "/* Base */\n\nbody,\nbody *:not(html):not(style):not(br):not(tr):not(code) {\n  box-sizing: border-box;\n  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif,\n    'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';\n  position: relative;\n}\n\nbody {\n  -webkit-text-size-adjust: none;\n  background-color: #ffffff;\n  color: #718096;\n  height: 100%;\n  line-height: 1.4;\n  margin: 0;\n  padding: 0;\n  width: 100% !important;\n}\n\np,\nul,\nol,\nblockquote {\n  line-height: 1.4;\n  text-align: left;\n}\n\na {\n  color: #3869d4;\n}\n\na img {\n  border: none;\n}\n\n/* Typography */\n\nh1 {\n  color: #3d4852;\n  font-size: 18px;\n  font-weight: bold;\n  margin-top: 0;\n  text-align: left;\n}\n\nh2 {\n  font-size: 16px;\n  font-weight: bold;\n  margin-top: 0;\n  text-align: left;\n}\n\nh3 {\n  font-size: 14px;\n  font-weight: bold;\n  margin-top: 0;\n  text-align: left;\n}\n\np {\n  font-size: 16px;\n  line-height: 1.5em;\n  margin-top: 0;\n  text-align: left;\n}\n\np.sub {\n  font-size: 12px;\n}\n\nimg {\n  max-width: 100%;\n  height: auto;\n}\n\nimg.emoji {\n  height: 1.2em;\n  width: 1.2em;\n  vertical-align: -0.2em;\n}\n\npre {\n  white-space: pre-wrap;\n}\n\n/* Layout */\n\n.wrapper {\n  -premailer-cellpadding: 0;\n  -premailer-cellspacing: 0;\n  -premailer-width: 100%;\n  background-color: #edf2f7;\n  margin: 0;\n  padding: 0;\n  width: 100%;\n}\n\n.content {\n  -premailer-cellpadding: 0;\n  -premailer-cellspacing: 0;\n  -premailer-width: 100%;\n  margin: 0;\n  padding: 0;\n  width: 100%;\n}\n\n/* Header */\n\n.header {\n  padding: 25px 0;\n  text-align: center;\n}\n\n.header a {\n  color: #3d4852;\n  font-size: 19px;\n  font-weight: bold;\n  text-decoration: none;\n}\n\n/* Logo */\n\n.logo {\n  height: 75px;\n  max-height: 75px;\n  width: 75px;\n}\n\n/* Body */\n\n.body {\n  -premailer-cellpadding: 0;\n  -premailer-cellspacing: 0;\n  -premailer-width: 100%;\n  background-color: #edf2f7;\n  border-bottom: 1px solid #edf2f7;\n  border-top: 1px solid #edf2f7;\n  margin: 0;\n  padding: 0;\n  width: 100%;\n}\n\n.inner-body {\n  -premailer-cellpadding: 0;\n  -premailer-cellspacing: 0;\n  -premailer-width: 570px;\n  background-color: #ffffff;\n  border-color: #e8e5ef;\n  border-radius: 2px;\n  border-width: 1px;\n  box-shadow: 0 2px 0 rgba(0, 0, 150, 0.025), 2px 4px 0 rgba(0, 0, 150, 0.015);\n  margin: 0 auto;\n  padding: 0;\n  width: 570px;\n}\n\n/* Subcopy */\n\n.subcopy {\n  border-top: 1px solid #e8e5ef;\n  margin-top: 25px;\n  padding-top: 25px;\n}\n\n.subcopy p {\n  font-size: 14px;\n}\n\n.subcopy p:last-of-type {\n  margin-bottom: 0;\n  padding-bottom: 0;\n}\n\n/* Footer */\n\n.footer {\n  -premailer-cellpadding: 0;\n  -premailer-cellspacing: 0;\n  -premailer-width: 570px;\n  margin: 0 auto;\n  padding: 0;\n  text-align: center;\n  width: 570px;\n}\n\n.footer p {\n  color: #b0adc5;\n  font-size: 12px;\n  text-align: center;\n}\n\n.footer a {\n  color: #b0adc5;\n  text-decoration: underline;\n}\n\n/* Tables */\n\n.table table {\n  -premailer-cellpadding: 0;\n  -premailer-cellspacing: 0;\n  -premailer-width: 100%;\n  margin: 30px auto;\n  width: 100%;\n}\n\n.table th {\n  border-bottom: 1px solid #edeff2;\n  margin: 0;\n  padding-bottom: 8px;\n}\n\n.table td {\n  color: #74787e;\n  font-size: 15px;\n  line-height: 18px;\n  margin: 0;\n  padding: 10px 0;\n}\n\n.content-cell {\n  max-width: 100vw;\n  padding: 32px;\n}\n\n/* Buttons */\n\n.action {\n  -premailer-cellpadding: 0;\n  -premailer-cellspacing: 0;\n  -premailer-width: 100%;\n  margin: 30px auto;\n  padding: 0;\n  text-align: center;\n  width: 100%;\n}\n\n.button {\n  -webkit-text-size-adjust: none;\n  border-radius: 4px;\n  color: #fff;\n  display: inline-block;\n  overflow: hidden;\n  text-decoration: none;\n}\n\n.button-blue,\n.button-primary {\n  background-color: #2d3748;\n  border-bottom: 8px solid #2d3748;\n  border-left: 18px solid #2d3748;\n  border-right: 18px solid #2d3748;\n  border-top: 8px solid #2d3748;\n}\n\n.button-green,\n.button-success {\n  background-color: #48bb78;\n  border-bottom: 8px solid #48bb78;\n  border-left: 18px solid #48bb78;\n  border-right: 18px solid #48bb78;\n  border-top: 8px solid #48bb78;\n}\n\n.button-red,\n.button-error {\n  background-color: #e53e3e;\n  border-bottom: 8px solid #e53e3e;\n  border-left: 18px solid #e53e3e;\n  border-right: 18px solid #e53e3e;\n  border-top: 8px solid #e53e3e;\n}\n\n/* Panels */\n\n.panel {\n  margin: 21px 0;\n}\n\n.panel-content {\n  background-color: #edf2f7;\n  color: #718096;\n  padding: 16px;\n}\n\n.panel-content p {\n  color: #718096;\n}\n\n.panel-item {\n  padding: 0;\n}\n\n.panel-item p:last-of-type {\n  margin-bottom: 0;\n  padding-bottom: 0;\n}\n\n/* Utilities */\n\n.break-all {\n  word-break: break-all;\n}\n"
  },
  {
    "path": "resources/views/mail/notification.blade.php",
    "content": "<x-mail::message>\n{{ $title }}\n\n<x-mail::panel>\n@if (!empty($name))\n<p>\n@if (!empty($avatar))\n<img src=\"{{ $avatar }}\" width=\"32\" height=\"32\" style=\"border-radius: 100%; vertical-align: middle\">\n@endif\n<strong>{{ $name }}:</strong>\n</p>\n@endif\n\n@if ($excerpt instanceof Illuminate\\Contracts\\Support\\Htmlable)\n{!! preg_replace('/<script.*?\\/script>/s', '', $excerpt) !!}\n@else\n{{ $excerpt }}\n@endif\n</x-mail::panel>\n\n@isset($button)\n<x-mail::button :url=\"$url\" color=\"primary\">\n{{ $button }}\n</x-mail::button>\n@endisset\n\n<x-slot:subcopy>\n<p>\n{{ $reason }}<br>\n\n<x-mail::link :url=\"$unsubscribeUrl\">{{ $unsubscribeText }}</x-mail::link> &nbsp;\n<x-mail::link :url=\"route('waterhole.preferences.notifications')\">{{ __('waterhole::notifications.manage-notification-preferences-link') }}</x-mail::link>\n</p>\n</x-slot:subcopy>\n</x-mail::message>\n"
  },
  {
    "path": "resources/views/mail/text/button.blade.php",
    "content": "{{ $slot }}: {{ $url }}\n"
  },
  {
    "path": "resources/views/mail/text/header.blade.php",
    "content": "{{ $slot }} ({{ $url }})\n"
  },
  {
    "path": "resources/views/mail/text/layout.blade.php",
    "content": "{!! strip_tags($header ?? '') !!}\n\n{!! strip_tags($slot) !!}\n@isset($subcopy)\n\n{!! strip_tags($subcopy ?? '') !!}\n@endisset\n\n{!! strip_tags($footer ?? '') !!}\n"
  },
  {
    "path": "resources/views/mail/text/link.blade.php",
    "content": "{{ $slot }}: {{ $url }}\n"
  },
  {
    "path": "resources/views/mail/text/message.blade.php",
    "content": "<x-mail::layout>\n    {{-- Header --}}\n    <x-slot:header>\n        <x-mail::header :url=\"route('waterhole.home')\">\n            {{ config('waterhole.forum.name') }}\n        </x-mail::header>\n    </x-slot:header>\n\n    {{-- Body --}}\n    {{ $slot }}\n\n    {{-- Subcopy --}}\n    @isset($subcopy)\n        <x-slot:subcopy>\n            <x-mail::subcopy>\n                {{ $subcopy }}\n            </x-mail::subcopy>\n        </x-slot:subcopy>\n    @endisset\n</x-mail::layout>\n"
  },
  {
    "path": "resources/views/moderation/removal-reason.blade.php",
    "content": "<div class=\"stack gap-md\">\n    <h1 class=\"h4\">{{ $title }}</h1>\n\n    <details class=\"card\" open>\n        <summary class=\"card__header h5\">\n            {{ __('waterhole::forum.removal-reason-label') }}\n        </summary>\n        <div class=\"card__body stack gap-md\">\n            <label class=\"choice\">\n                <input type=\"radio\" name=\"deleted_reason\" value=\"\" @checked(! $selectedReason) />\n                {{ __('waterhole::forum.removal-reason-unspecified-label') }}\n            </label>\n\n            @foreach ($reasons as $reason)\n                <label class=\"choice\">\n                    <input\n                        type=\"radio\"\n                        name=\"deleted_reason\"\n                        value=\"{{ $reason }}\"\n                        @checked($selectedReason === $reason)\n                    />\n                    <span class=\"stack gap-xxs\">\n                        <span>\n                            {{\n                                Lang::has($key = \"waterhole::forum.report-reason-$reason-label\")\n                                    ? __($key)\n                                    : Str::headline($reason)\n                            }}\n                        </span>\n                        @if (Lang::has($key = \"waterhole::forum.report-reason-$reason-description\"))\n                            <small class=\"field__description\">\n                                {{ __($key) }}\n                            </small>\n                        @endif\n                    </span>\n                </label>\n            @endforeach\n        </div>\n    </details>\n\n    @if ($model->user)\n        <details class=\"card\" data-controller=\"details-focus\">\n            <summary class=\"card__header h5\">\n                {{ __('waterhole::forum.removal-message-label') }}\n            </summary>\n            <div class=\"card__body\">\n                <textarea name=\"deleted_message\" rows=\"3\">{{ old('deleted_message') }}</textarea>\n            </div>\n        </details>\n    @endif\n\n    @if ($model->user && $canSuspend)\n        <details class=\"card\">\n            <summary class=\"card__header h5\">\n                {{ __('waterhole::forum.user-actions-label') }}\n            </summary>\n\n            <div class=\"card__body stack gap-md\">\n                <div class=\"stack gap-xs\" data-controller=\"reveal\">\n                    <label class=\"choice\">\n                        <input\n                            type=\"checkbox\"\n                            name=\"suspend_user\"\n                            value=\"1\"\n                            data-reveal-target=\"if\"\n                            @checked(old('suspend_user'))\n                        />\n                        {{\n                            __('waterhole::forum.user-actions-suspend-label', [\n                                'user' => Waterhole\\username($model->user),\n                            ])\n                        }}\n                    </label>\n\n                    <div\n                        class=\"row gap-xs wrap choice-indent\"\n                        data-reveal-target=\"then\"\n                        data-controller=\"suspend-duration\"\n                    >\n                        <input\n                            type=\"number\"\n                            name=\"suspend_for\"\n                            min=\"1\"\n                            value=\"{{ old('suspend_for', 7) }}\"\n                            data-suspend-duration-target=\"count\"\n                            style=\"width: 6ch\"\n                        />\n                        <select\n                            name=\"suspend_unit\"\n                            data-suspend-duration-target=\"unit\"\n                            data-action=\"suspend-duration#update\"\n                            style=\"width: auto\"\n                        >\n                            <option value=\"days\" @selected(old('suspend_unit', 'days') === 'days')>\n                                {{ __('waterhole::forum.user-actions-suspend-days') }}\n                            </option>\n                            <option value=\"weeks\" @selected(old('suspend_unit') === 'weeks')>\n                                {{ __('waterhole::forum.user-actions-suspend-weeks') }}\n                            </option>\n                            <option\n                                value=\"indefinite\"\n                                @selected(old('suspend_unit') === 'indefinite')\n                            >\n                                {{ __('waterhole::forum.user-actions-suspend-indefinitely') }}\n                            </option>\n                        </select>\n                    </div>\n                </div>\n            </div>\n        </details>\n    @endif\n</div>\n"
  },
  {
    "path": "resources/views/moderation/report.blade.php",
    "content": "<div class=\"stack gap-lg\">\n    <h1 class=\"h4\">{{ $title }}</h1>\n\n    <div class=\"stack gap-md\">\n        @foreach ($reasons as $reason)\n            <label class=\"choice\">\n                <input\n                    type=\"radio\"\n                    name=\"reason\"\n                    value=\"{{ $reason }}\"\n                    @checked(old('reason') === $reason)\n                    @if ($loop->first) required @endif\n                />\n                <span class=\"stack gap-xxs\">\n                    <span>\n                        {{\n                            Lang::has($key = \"waterhole::forum.report-reason-$reason-label\")\n                                ? __($key)\n                                : Str::headline($reason)\n                        }}\n                    </span>\n                    @if (Lang::has($key = \"waterhole::forum.report-reason-$reason-description\"))\n                        <small class=\"field__description\">\n                            {{ __($key) }}\n                        </small>\n                    @endif\n                </span>\n            </label>\n        @endforeach\n    </div>\n\n    <textarea\n        name=\"note\"\n        rows=\"3\"\n        placeholder=\"{{ __('waterhole::forum.report-note-placeholder') }}\"\n    >\n{{ old('note') }}</textarea\n    >\n</div>\n"
  },
  {
    "path": "resources/views/pagination/default.blade.php",
    "content": "@if ($paginator->hasPages())\n    <nav class=\"pagination tabs\">\n        {{-- Previous Page Link --}}\n\n        @if ($paginator->onFirstPage())\n            <span class=\"tab\" aria-disabled=\"true\">\n                {{ __('waterhole::system.pagination-previous-link') }}\n            </span>\n        @else\n            <a class=\"tab\" href=\"{{ $paginator->previousPageUrl() }}\" rel=\"prev\">\n                {{ __('waterhole::system.pagination-previous-link') }}\n            </a>\n        @endif\n\n        {{-- Pagination Elements --}}\n        @foreach ($elements as $element)\n            {{-- \"Three Dots\" Separator --}}\n            @if (is_string($element))\n                <span class=\"tab\" aria-disabled=\"true\">{{ $element }}</span>\n            @endif\n\n            {{-- Array Of Links --}}\n            @if (is_array($element))\n                @foreach ($element as $page => $url)\n                    <a\n                        class=\"tab\"\n                        href=\"{{ $url }}\"\n                        @if ($page == $paginator->currentPage()) aria-current=\"page\" @endif\n                    >\n                        {{ $page }}\n                    </a>\n                @endforeach\n            @endif\n        @endforeach\n\n        {{-- Next Page Link --}}\n\n        @if ($paginator->hasMorePages())\n            <a class=\"tab\" href=\"{{ $paginator->nextPageUrl() }}\" rel=\"next\">\n                {{ __('waterhole::system.pagination-next-link') }}\n            </a>\n        @else\n            <span class=\"tab\" aria-disabled=\"true\" aria-label=\"\">\n                {{ __('waterhole::system.pagination-next-link') }}\n            </span>\n        @endif\n    </nav>\n@endif\n"
  },
  {
    "path": "resources/views/pagination/simple-default.blade.php",
    "content": "@if ($paginator->hasPages())\n    <nav class=\"pagination tabs justify-center\">\n        {{-- Previous Page Link --}}\n\n        @if ($paginator->onFirstPage())\n            <span class=\"tab\" aria-disabled=\"true\">\n                {{ __('waterhole::system.pagination-previous-link') }}\n            </span>\n        @else\n            <a class=\"tab\" href=\"{{ $paginator->previousPageUrl() }}\" rel=\"prev\">\n                {{ __('waterhole::system.pagination-previous-link') }}\n            </a>\n        @endif\n\n        {{-- Next Page Link --}}\n\n        @if ($paginator->hasMorePages())\n            <a class=\"tab\" href=\"{{ $paginator->nextPageUrl() }}\" rel=\"next\">\n                {{ __('waterhole::system.pagination-next-link') }}\n            </a>\n        @else\n            <span class=\"tab\" aria-disabled=\"true\">\n                {{ __('waterhole::system.pagination-next-link') }}\n            </span>\n        @endif\n    </nav>\n@endif\n"
  },
  {
    "path": "resources/views/posts/create.blade.php",
    "content": "@php\n    $title = __('waterhole::forum.create-post-title');\n@endphp\n\n<x-waterhole::layout :title=\"$title\">\n    <div class=\"container section measure\">\n        <form method=\"POST\" action=\"{{ route('waterhole.posts.store') }}\">\n            @csrf\n\n            {{-- Hidden submit button to handle Enter key --}}\n            <button name=\"commit\" type=\"submit\" value=\"1\" hidden></button>\n\n            @if (! $form->model->channel)\n                <x-waterhole::dialog class=\"measure\" :title=\"$title\">\n                    <x-waterhole::channel-picker id=\"channel_id\" name=\"channel_id\" show-links />\n                </x-waterhole::dialog>\n            @else\n                <x-waterhole::dialog :title=\"$title\">\n                    <x-slot name=\"header\">\n                        <ui-popup placement=\"bottom-start\">\n                            <button class=\"btn\" type=\"button\">\n                                <x-waterhole::channel-label :channel=\"$form->model->channel\" />\n                                @icon('tabler-selector')\n                            </button>\n\n                            <ui-menu class=\"menu menu--lg\" hidden>\n                                <x-waterhole::channel-picker\n                                    id=\"channel_id\"\n                                    name=\"channel_id\"\n                                    :value=\"$form->model->channel_id\"\n                                    show-links\n                                />\n                            </ui-menu>\n                        </ui-popup>\n                    </x-slot>\n\n                    <div class=\"stack gap-xl stacked-fields\">\n                        <x-waterhole::validation-errors />\n\n                        @if ($instructions = $form->model->channel->instructions_html)\n                            <div class=\"rounded p-lg bg-warning-soft content\">\n                                {{ $instructions }}\n                            </div>\n                        @endif\n\n                        @components($form->fields())\n\n                        <div>\n                            <button\n                                class=\"btn btn--wide bg-accent\"\n                                name=\"commit\"\n                                type=\"submit\"\n                                value=\"1\"\n                                data-hotkey=\"Mod+Enter\"\n                                data-hotkey-scope=\"post-body\"\n                            >\n                                {{ __('waterhole::forum.post-submit-button') }}\n                            </button>\n                        </div>\n                    </div>\n                </x-waterhole::dialog>\n            @endif\n        </form>\n    </div>\n</x-waterhole::layout>\n"
  },
  {
    "path": "resources/views/posts/edit.blade.php",
    "content": "@php\n    $title = __('waterhole::forum.edit-post-title');\n@endphp\n\n<x-waterhole::layout :title=\"$title\">\n    <div class=\"container section measure\">\n        <x-waterhole::dialog :title=\"$title\">\n            <form method=\"POST\" action=\"{{ route('waterhole.posts.update', ['post' => $post]) }}\">\n                @csrf\n                @method('PATCH')\n                @return($post->url)\n\n                <div class=\"stack gap-xl stacked-fields\">\n                    <x-waterhole::validation-errors />\n\n                    @components($form->fields())\n\n                    <div class=\"row gap-xs wrap\">\n                        <button\n                            type=\"submit\"\n                            class=\"btn bg-accent\"\n                            data-hotkey=\"Mod+Enter\"\n                            data-hotkey-scope=\"post-body\"\n                        >\n                            {{ __('waterhole::system.save-changes-button') }}\n                        </button>\n\n                        <x-waterhole::cancel :default=\"$post->url\" class=\"btn\" />\n                    </div>\n                </div>\n            </form>\n        </x-waterhole::dialog>\n    </div>\n</x-waterhole::layout>\n"
  },
  {
    "path": "resources/views/posts/move-to-channel.blade.php",
    "content": "<div class=\"stack gap-lg\">\n    <h1 class=\"h4\">\n        {{ __('waterhole::forum.move-post-title', ['count' => $posts->count()]) }}\n        @if ($posts->count() === 1)\n            {{ $posts[0]->title }}\n        @endif\n    </h1>\n\n    <x-waterhole::channel-picker\n        name=\"channel_id\"\n        value=\"{{ request('channel_id', $posts[0]->channel_id) }}\"\n    />\n</div>\n"
  },
  {
    "path": "resources/views/posts/show.blade.php",
    "content": "@php\n    $imageUpload = $post->attachments->first(fn ($upload) => $upload->type && str_starts_with($upload->type, 'image/'));\n    $ogImage = $imageUpload ? Storage::disk('public')->url('uploads/' . $imageUpload->filename) : null;\n@endphp\n\n<x-waterhole::layout\n    :title=\"$post->title\"\n    :seo=\"[\n        'description' => $post->body_text,\n        'url' => $post->url,\n        'type' => 'article',\n        'image' => $ogImage,\n        'noindex' => ! $post->channel->structure->is_listed,\n        'schema' => false,\n    ]\"\n>\n    <div\n        class=\"post-page section container with-sidebar\"\n        data-controller=\"post-page\"\n        data-post-page-id-value=\"{{ $post->id }}\"\n        itemscope\n        itemtype=\"https://schema.org/DiscussionForumPosting\"\n        itemid=\"{{ $post->url }}\"\n        {{ new Illuminate\\View\\ComponentAttributeBag(resolve(\\Waterhole\\Extend\\Ui\\PostAttributes::class)->build($post)) }}\n    >\n        <meta itemprop=\"commentCount\" content=\"{{ $post->comment_count }}\" />\n\n        <div class=\"stack gap-lg measure\">\n            <div data-post-page-target=\"post\" @if (!$comments->onFirstPage()) hidden @endif>\n                <x-waterhole::post-full :post=\"$post\" />\n            </div>\n\n            <section class=\"post-page__comments stack gap-md\">\n                @if ($comments->count())\n                    <h2 class=\"h4\" id=\"comments\">\n                        {{ __('waterhole::forum.post-comments-heading', ['count' => $post->comment_count]) }}\n                    </h2>\n                @endif\n\n                <x-waterhole::infinite-scroll\n                    :paginator=\"$comments\"\n                    divider\n                    endless\n                    class=\"comment-list card\"\n                >\n                    @foreach ($comments as $i => $comment)\n                        @if ($lastReadAt && $comment->created_at > $lastReadAt)\n                            @once\n                                <div class=\"divider color-activity\" id=\"unread\" tabindex=\"-1\">\n                                    {{ __('waterhole::forum.comments-unread-heading') }}\n                                </div>\n                            @endonce\n                        @endif\n\n                        <x-waterhole::comment-frame\n                            :comment=\"$comment\"\n                            class=\"card__row\"\n                            with-structured-data\n                        />\n                    @endforeach\n                </x-waterhole::infinite-scroll>\n            </section>\n\n            @if (! $comments->hasMorePages())\n                <div class=\"stack gap-md\" id=\"bottom\" tabindex=\"-1\">\n                    @components(resolve(\\Waterhole\\Extend\\Ui\\PostPage::class)->bottom, compact('post'))\n                </div>\n            @endif\n\n            @can('waterhole.post.comment', $post)\n                <div id=\"reply\" tabindex=\"-1\"></div>\n                <x-waterhole::composer :post=\"$post\" data-turbo-permanent />\n            @endcan\n        </div>\n\n        <div\n            class=\"sidebar sidebar--sticky sidebar--bottom overflow-visible stack gap-lg justify-between\"\n            data-controller=\"watch-sticky\"\n        >\n            <x-waterhole::post-sidebar :post=\"$post\" />\n\n            @if ($comments->total())\n                <div class=\"tabs tabs--vertical\" data-post-page-target=\"commentsLinks\" hidden>\n                    <a href=\"#comments\" class=\"tab with-icon\">\n                        @icon('tabler-message-circle-2')\n                        {{ __('waterhole::forum.post-comments-link', ['count' => $comments->total()]) }}\n                    </a>\n\n                    <a\n                        {{-- Exclude ?page=1 from the URL so that the page isn't needlessly reloaded. --}}\n                        href=\"{{ $lastLink = ($comments->lastPage() === 1 ? $post->url : $comments->url($comments->lastPage())) . '#bottom' }}\"\n                        class=\"tab with-icon hide-md-down\"\n                    >\n                        @icon('tabler-chevrons-down')\n                        {{ __('waterhole::system.pagination-last-link') }}\n                    </a>\n                </div>\n\n                <ui-popup class=\"collapsible-nav stack\" data-post-page-target=\"commentsPagination\">\n                    <button class=\"btn btn--transparent\">\n                        {{ __('waterhole::system.page-number-prefix') }}\n                        <span data-post-page-target=\"currentPage\">\n                            {{ $comments->currentPage() }}\n                        </span>\n                        @icon('tabler-selector')\n                    </button>\n\n                    <div hidden class=\"drawer drawer--right\">\n                        <nav class=\"comments-pagination tabs tabs--vertical gap-sm\">\n                            <a class=\"tab with-icon\" href=\"{{ $post->url }}#top\">\n                                @icon('tabler-chevrons-up', ['class' => 'icon--narrow'])\n                                {{ __('waterhole::forum.original-post-link') }}\n                            </a>\n\n                            <div\n                                class=\"scrollable-y stack comments-pagination__pages\"\n                                data-controller=\"scrollspy watch-scroll\"\n                            >\n                                @for ($page = 1; $page <= $comments->lastPage(); $page++)\n                                    <a\n                                        class=\"tab\"\n                                        {{-- Exclude ?page=1 from the URL so that the page isn't needlessly reloaded. --}}\n                                        href=\"{{ $page === 1 ? $post->url : $comments->url($page) }}#page_{{ $page }}\"\n                                        @if ($page == $comments->currentPage()) aria-current=\"page\" @endif\n                                    >\n                                        {{ $page }}\n                                    </a>\n                                @endfor\n                            </div>\n\n                            <a class=\"tab with-icon\" href=\"{{ $lastLink }}\">\n                                @icon('tabler-chevrons-down', ['class' => 'icon--narrow'])\n                                {{ __('waterhole::system.pagination-last-link') }}\n                            </a>\n                        </nav>\n                    </div>\n                </ui-popup>\n            @endif\n        </div>\n    </div>\n\n    <x-turbo::stream-from\n        :source=\"$post\"\n        :type=\"$post->channel->isPublic() ? 'channel' : 'private'\"\n    />\n</x-waterhole::layout>\n"
  },
  {
    "path": "resources/views/preferences/account.blade.php",
    "content": "@php\n    $title = __('waterhole::user.account-settings-title');\n@endphp\n\n<x-waterhole::user-profile :user=\"Auth::user()\" :title=\"$title\">\n    <h2 class=\"visually-hidden\">{{ $title }}</h2>\n\n    <div class=\"card card__body stack dividers\">\n        @section('name')\n            <div class=\"field\">\n                <h4 class=\"field__label\">\n                    {{ __('waterhole::auth.name-label') }}\n                </h4>\n                <div>\n                    {{ Auth::user()->name }}\n                </div>\n            </div>\n        @endsection\n\n        @section('email')\n            @if (! Auth::user()->originalUser())\n                <div class=\"field\">\n                    <h4 class=\"field__label\">\n                        {{ __('waterhole::auth.email-label') }}\n                    </h4>\n                    <form action=\"{{ route('waterhole.preferences.email') }}\" method=\"POST\">\n                        @csrf\n                        <x-waterhole::field name=\"email\">\n                            <div class=\"row gap-xs\">\n                                <input\n                                    class=\"grow\"\n                                    name=\"email\"\n                                    type=\"email\"\n                                    value=\"{{ old('email', Auth::user()->email) }}\"\n                                />\n                                <button class=\"btn\">\n                                    {{ __('waterhole::system.change-button') }}\n                                </button>\n                            </div>\n                        </x-waterhole::field>\n                    </form>\n                </div>\n            @endif\n        @endsection\n\n        @section('password')\n            @if (Route::has('waterhole.preferences.password'))\n                <div class=\"field\">\n                    <h4 class=\"field__label\">\n                        {{ __('waterhole::auth.password-label') }}\n                    </h4>\n                    <form action=\"{{ route('waterhole.preferences.password') }}\" method=\"POST\">\n                        @csrf\n                        <x-waterhole::field name=\"password\">\n                            <div class=\"row gap-xs\">\n                                <input\n                                    autocomplete=\"new-password\"\n                                    class=\"grow\"\n                                    name=\"password\"\n                                    placeholder=\"{{ __('waterhole::auth.new-password-label') }}\"\n                                    type=\"password\"\n                                />\n                                <button class=\"btn\">\n                                    {{ __('waterhole::system.change-button') }}\n                                </button>\n                            </div>\n                        </x-waterhole::field>\n                    </form>\n                </div>\n            @endif\n        @endsection\n\n        @section('delete')\n            <x-waterhole::action-button\n                :for=\"Auth::user()\"\n                :action=\"Waterhole\\Actions\\DeleteSelf::class\"\n                class=\"btn bg-danger\"\n            />\n        @endsection\n\n        @components(resolve(\\Waterhole\\Extend\\Ui\\Preferences::class)->account)\n    </div>\n</x-waterhole::user-profile>\n"
  },
  {
    "path": "resources/views/preferences/notifications.blade.php",
    "content": "@php\n    $title = __('waterhole::user.notification-preferences-title');\n@endphp\n\n<x-waterhole::user-profile :user=\"Auth::user()\" :title=\"$title\">\n    <h2 class=\"visually-hidden\">{{ $title }}</h2>\n\n    <form action=\"{{ route('waterhole.preferences.notifications') }}\" method=\"post\">\n        @csrf\n\n        <div class=\"stack gap-md\">\n            <x-waterhole::validation-errors />\n\n            <div class=\"card card__body stack dividers\">\n                <div class=\"field\">\n                    <h4 class=\"field__label\">\n                        {{ __('waterhole::user.notifications-label') }}\n                    </h4>\n                    <div class=\"notification-grid card\">\n                        @foreach (resolve(\\Waterhole\\Extend\\Core\\NotificationTypes::class)->values() as $type)\n                            @continue(! $type::availableFor(Auth::user()))\n                            @php\n                                $channels = (array) old('notification_channels.' . $type, Auth::user()->notification_channels[$type] ?? []);\n                            @endphp\n\n                            <div class=\"card__row row gap-xs\">\n                                <div>\n                                    {{ $type::description() }}\n                                </div>\n                                <div class=\"push-end row\">\n                                    <label class=\"choice\">\n                                        <input\n                                            name=\"notification_channels[{{ $type }}][]\"\n                                            type=\"checkbox\"\n                                            value=\"database\"\n                                            @checked(in_array('database', $channels))\n                                        />\n                                        {{ __('waterhole::user.notification-channel-web') }}\n                                    </label>\n                                    <label class=\"choice\">\n                                        <input\n                                            type=\"checkbox\"\n                                            name=\"notification_channels[{{ $type }}][]\"\n                                            value=\"mail\"\n                                            @checked(in_array('mail', $channels))\n                                        />\n                                        {{ __('waterhole::user.notification-channel-email') }}\n                                    </label>\n                                </div>\n                            </div>\n                        @endforeach\n                    </div>\n                </div>\n\n                <div class=\"field\">\n                    <h4 class=\"field__label\">\n                        {{ __('waterhole::user.notifications-following-label') }}\n                    </h4>\n                    <div>\n                        <input type=\"hidden\" name=\"follow_on_comment\" value=\"0\" />\n                        <label class=\"choice\">\n                            <input\n                                type=\"checkbox\"\n                                name=\"follow_on_comment\"\n                                value=\"1\"\n                                @checked(Auth::user()->follow_on_comment)\n                            />\n                            {{ __('waterhole::user.follow-on-comment-label') }}\n                        </label>\n                    </div>\n                </div>\n\n                <div>\n                    <button type=\"submit\" class=\"btn bg-accent btn--wide\">\n                        {{ __('waterhole::system.save-changes-button') }}\n                    </button>\n                </div>\n            </div>\n        </div>\n    </form>\n</x-waterhole::user-profile>\n"
  },
  {
    "path": "resources/views/preferences/profile.blade.php",
    "content": "@php\n    $title = __('waterhole::user.edit-profile-title');\n@endphp\n\n<x-waterhole::user-profile :user=\"Auth::user()\" :title=\"$title\">\n    <h2 class=\"visually-hidden\">{{ $title }}</h2>\n\n    <form\n        action=\"{{ route('waterhole.preferences.profile') }}\"\n        method=\"POST\"\n        enctype=\"multipart/form-data\"\n    >\n        @csrf\n\n        <div class=\"card card__body stack dividers\">\n            @components($form->fields())\n\n            <div>\n                <button type=\"submit\" class=\"btn bg-accent btn--wide\">\n                    {{ __('waterhole::system.save-changes-button') }}\n                </button>\n            </div>\n        </div>\n    </form>\n</x-waterhole::user-profile>\n"
  },
  {
    "path": "resources/views/reactions/list.blade.php",
    "content": "@php\n    $query = $model->reactions()->whereBelongsTo($reactionType);\n    $count = $query->count();\n    $reactions = $query\n        ->with('user')\n        ->latest()\n        ->take(20)\n        ->get();\n@endphp\n\n<turbo-frame id=\"reactions\">\n    <ul role=\"list\">\n        @foreach ($reactions as $reaction)\n            <li>{{ Waterhole\\username($reaction->user) }}</li>\n        @endforeach\n\n        @if ($count > 20)\n            <li>\n                {{ __('waterhole::system.user-list-overflow', ['count' => $count - 20]) }}\n            </li>\n        @endif\n    </ul>\n</turbo-frame>\n"
  },
  {
    "path": "resources/views/users/comments.blade.php",
    "content": "@php\n    $title = __('waterhole::user.user-comments-title', Waterhole\\user_variables($user));\n@endphp\n\n<x-waterhole::user-profile :user=\"$user\" :title=\"$title\">\n    <h2 class=\"visually-hidden\">{{ $title }}</h2>\n\n    <div class=\"stack gap-lg\">\n        <div class=\"row gap-xs wrap\">\n            <x-waterhole::feed-filters :feed=\"$comments\" />\n            <x-waterhole::feed-top-period :feed=\"$comments\" />\n        </div>\n\n        @php\n            $items = $comments->items();\n        @endphp\n\n        @if ($items->isNotEmpty())\n            <x-waterhole::infinite-scroll :paginator=\"$items\">\n                <ul role=\"list\" class=\"card-list\">\n                    @foreach ($items as $comment)\n                        <li class=\"card comment-card\">\n                            <ol class=\"color-muted text-xs card__header breadcrumb\">\n                                <li>\n                                    <x-waterhole::channel-label\n                                        :channel=\"$comment->post->channel\"\n                                    />\n                                </li>\n                                <li>\n                                    <a href=\"{{ $comment->post_url }}\" class=\"weight-medium\">\n                                        {{ $comment->post->title }}\n                                    </a>\n                                </li>\n                            </ol>\n                            <x-waterhole::comment-full :comment=\"$comment\" truncate />\n                        </li>\n                    @endforeach\n                </ul>\n            </x-waterhole::infinite-scroll>\n        @else\n            <div class=\"placeholder\">\n                @icon('tabler-messages', ['class' => 'placeholder__icon'])\n                <p class=\"h4\">\n                    {{ __('waterhole::user.comments-empty-message') }}\n                </p>\n            </div>\n        @endif\n    </div>\n</x-waterhole::user-profile>\n"
  },
  {
    "path": "resources/views/users/mention-suggestion.blade.php",
    "content": "<div class=\"grow row gap-md justify-between\">\n    <x-waterhole::user-label :$user />\n\n    @if ($user->comment_id)\n        <span class=\"with-icon color-muted text-xxs\">\n            @icon('tabler-share-3', ['class' => 'flip-horizontal'])\n            {{ __('waterhole::forum.comment-reply-button') }}\n        </span>\n    @endif\n</div>\n"
  },
  {
    "path": "resources/views/users/posts.blade.php",
    "content": "@php\n    $title = __('waterhole::user.user-posts-title', Waterhole\\user_variables($user));\n@endphp\n\n<x-waterhole::user-profile :user=\"$user\" :title=\"$title\">\n    <h2 class=\"visually-hidden\">{{ $title }}</h2>\n\n    <div class=\"stack gap-lg\">\n        <div class=\"row gap-xs wrap\">\n            <x-waterhole::feed-filters :feed=\"$posts\" />\n            <x-waterhole::feed-top-period :feed=\"$posts\" />\n        </div>\n\n        @php\n            $items = $posts->items();\n        @endphp\n\n        @if ($items->isNotEmpty())\n            <div class=\"{{ $posts->layout->wrapperClass() }}\">\n                <x-waterhole::infinite-scroll :paginator=\"$items\">\n                    @foreach ($items as $post)\n                        <x-dynamic-component\n                            :component=\"$posts->layout->itemComponent()\"\n                            :post=\"$post\"\n                        />\n                    @endforeach\n                </x-waterhole::infinite-scroll>\n            </div>\n        @else\n            <div class=\"placeholder\">\n                @icon('tabler-messages', ['class' => 'placeholder__icon'])\n                <p class=\"h4\">\n                    {{ __('waterhole::user.posts-empty-message') }}\n                </p>\n            </div>\n        @endif\n    </div>\n</x-waterhole::user-profile>\n"
  },
  {
    "path": "resources/views/widgets/feed.blade.php",
    "content": "<div class=\"card card__body stack gap-lg full-height\">\n    <h3 class=\"h4\">\n        <a\n            href=\"{{ $feed->getLink() }}\"\n            class=\"with-icon color-inherit\"\n            target=\"_blank\"\n            rel=\"noopener\"\n        >\n            @icon('tabler-rss')\n            {{ $title ?: $feed->getTitle() }}\n        </a>\n    </h3>\n\n    @foreach ($feed as $item)\n        @continue($loop->index >= $limit)\n\n        <article class=\"stack gap-xxs overlay-container\">\n            <a\n                href=\"{{ $item->getLink() }}\"\n                class=\"h6 color-accent block has-overlay\"\n                target=\"_blank\"\n                rel=\"noopener\"\n            >\n                {{ $item->getTitle() }}\n            </a>\n\n            <p class=\"color-muted text-xxs\">\n                <x-waterhole::relative-time :datetime=\"$item->getDateCreated()\" />\n                —\n                {{ Str::limit(htmlspecialchars_decode(strip_tags($item->getDescription() ?: $item->getContent()), 200)) }}\n            </p>\n        </article>\n    @endforeach\n</div>\n"
  },
  {
    "path": "resources/views/widgets/getting-started.blade.php",
    "content": "<div class=\"getting-started card card__body stack gap-md\">\n    <h2 class=\"h4\">{{ __('waterhole::cp.getting-started-title') }}</h2>\n\n    <div class=\"getting-started__grid grid\">\n        @foreach ($items as $key => $item)\n            <a\n                href=\"{{ $item['url'] }}\"\n                class=\"row gap-md align-start block-link\"\n                data-turbo-frame=\"_top\"\n            >\n                @icon($item['icon'], ['class' => 'text-xl no-shrink icon--thin'])\n                <div class=\"stack gap-xs\">\n                    <div class=\"h5 color-accent\">\n                        {{ __(\"waterhole::cp.getting-started-$key-title\") }}\n                    </div>\n                    <div class=\"color-muted text-xs\">\n                        {{ __(\"waterhole::cp.getting-started-$key-description\") }}\n                    </div>\n                </div>\n            </a>\n        @endforeach\n    </div>\n</div>\n"
  },
  {
    "path": "resources/views/widgets/line-chart.blade.php",
    "content": "<div class=\"card card__body line-chart-widget stack gap-xs\" data-controller=\"line-chart\">\n    <div class=\"line-chart-widget__head stack gap-xs\">\n        <div class=\"row justify-between\">\n            <h3 class=\"h4\">{{ $title }}</h3>\n\n            <x-waterhole::selector\n                placement=\"bottom-end\"\n                button-class=\"btn btn--sm btn--transparent\"\n                :value=\"$selectedPeriod\"\n                :options=\"array_keys($periods)\"\n                :label=\"fn($period) => __('waterhole::cp.period-'.str_replace('_', '-', $period))\"\n                :href=\"fn($period) => route('waterhole.cp.dashboard.widget', ['id' => $id]).'?period='.$period\"\n            />\n        </div>\n\n        <div class=\"row gap-sm align-baseline\" data-line-chart-target=\"summary\">\n            <span class=\"text-lg\">{{ Waterhole\\format_number($periodTotal) }}</span>\n            @if ($prevPeriodTotal && $periodTotal !== $prevPeriodTotal)\n                <span\n                    class=\"badge bg-{{ $color = $periodTotal < $prevPeriodTotal ? 'warning' : 'success' }}-soft color-{{ $color }}\"\n                >\n                    @icon($periodTotal < $prevPeriodTotal ? 'tabler-arrow-down' : 'tabler-arrow-up')\n                    {{ Waterhole\\format_number(abs(round(($periodTotal - $prevPeriodTotal) / $prevPeriodTotal)), ['style' => 'percent']) }}\n                </span>\n            @endif\n        </div>\n\n        <div class=\"row gap-sm align-baseline\" data-line-chart-target=\"legend\" hidden>\n            <span class=\"text-lg\" data-line-chart-target=\"legendAmount\"></span>\n            <span class=\"text-xs color-muted\" data-line-chart-target=\"legendPeriod\"></span>\n        </div>\n    </div>\n\n    <div class=\"table-container\" tabindex=\"0\" data-line-chart-target=\"table\">\n        <table class=\"table\">\n            <thead>\n                <tr>\n                    <th></th>\n                    @for ($i = $periodStart; $i < $periodEnd; $i = $i->add(1, $selectedUnit))\n                        <th data-timestamp=\"{{ $i->timestamp }}\">\n                            {{ $units[$selectedUnit]['label']($i) }}\n                        </th>\n                    @endfor\n                </tr>\n            </thead>\n            <tbody>\n                <tr>\n                    <th>{{ __('waterhole::cp.period-current-heading') }}</th>\n                    @for ($i = $periodStart; $i < $periodEnd; $i = $i->add(1, $selectedUnit))\n                        <td>\n                            {{ $results->where('date', '>=', $i)->where('date', '<', $i->add(1, $selectedUnit))->sum('count') }}\n                        </td>\n                    @endfor\n                </tr>\n                <tr>\n                    <th>{{ __('waterhole::cp.period-previous-heading') }}</th>\n                    @for ($i = $prevPeriodStart; $i < $prevPeriodEnd; $i = $i->add(1, $selectedUnit))\n                        <td>\n                            {{ $results->where('date', '>=', $i)->where('date', '<', $i->add(1, $selectedUnit))->sum('count') }}\n                        </td>\n                    @endfor\n                </tr>\n            </tbody>\n        </table>\n    </div>\n\n    <div data-line-chart-target=\"chart\" class=\"line-chart-widget__chart grow\" hidden></div>\n\n    <div\n        data-line-chart-target=\"axis\"\n        class=\"line-chart-widget__axis row gap-md justify-between color-muted text-xxs\"\n        aria-hidden=\"true\"\n        hidden\n    >\n        <div>{{ Str::before($units[$selectedUnit]['label']($periodStart), ' - ') }}</div>\n        <div>{{ Str::before($units[$selectedUnit]['label']($periodEnd), ' - ') }}</div>\n    </div>\n</div>\n"
  },
  {
    "path": "routes/api.php",
    "content": "<?php\n\nuse Illuminate\\Support\\Facades\\Route;\nuse Waterhole\\Http\\Controllers;\n\n// Route::get('user', Controllers\\Api\\CurrentUser::class);\n\n// Route::post('users/{id}/avatar', [Controllers\\Api\\AvatarController::class, 'upload']);\n// Route::delete('users/{id}/avatar', [Controllers\\Api\\AvatarController::class, 'remove']);\n\nresolve(Waterhole\\Extend\\Routing\\ApiRoutes::class);\n\nRoute::any('{uri?}', Controllers\\Api\\ApiController::class)->where('uri', '.*')->name('main');\n"
  },
  {
    "path": "routes/channels.php",
    "content": "<?php\n\nuse Illuminate\\Support\\Facades\\Broadcast;\nuse Waterhole\\Models\\Channel;\nuse Waterhole\\Models\\Post;\nuse Waterhole\\Models\\User;\n\nBroadcast::channel(User::class, function (User $actor, User $user) {\n    return $actor->id === $user->id;\n});\n\nBroadcast::channel(Channel::class, function (User $user, Channel $channel) {\n    return $channel->exists();\n});\n\nBroadcast::channel(Post::class, function (User $user, Post $post) {\n    return $post->exists();\n});\n"
  },
  {
    "path": "routes/cp.php",
    "content": "<?php\n\nuse Illuminate\\Support\\Facades\\Route;\nuse Waterhole\\Http\\Controllers\\Cp;\n\nRoute::get('/', [Cp\\DashboardController::class, 'index'])->name('dashboard');\nRoute::get('widget/{id}', [Cp\\DashboardController::class, 'widget'])->name('dashboard.widget');\n\nRoute::get('structure', [Cp\\StructureController::class, 'index'])->name('structure');\nRoute::post('structure', [Cp\\StructureController::class, 'saveOrder']);\n\nRoute::prefix('structure')\n    ->name('structure.')\n    ->group(function () {\n        Route::resource('headings', Cp\\StructureHeadingController::class)\n            ->only('create', 'store', 'edit', 'update')\n            ->parameter('heading', 'structure_heading');\n\n        Route::resource('links', Cp\\StructureLinkController::class)\n            ->only('create', 'store', 'edit', 'update')\n            ->parameter('link', 'structure_link');\n\n        Route::resource('channels', Cp\\ChannelController::class)->only(\n            'create',\n            'store',\n            'edit',\n            'update',\n        );\n\n        Route::resource('pages', Cp\\PageController::class)->only(\n            'create',\n            'store',\n            'edit',\n            'update',\n        );\n    });\n\nRoute::resource('taxonomies', Cp\\TaxonomyController::class)->only(\n    'index',\n    'create',\n    'store',\n    'edit',\n    'update',\n);\n\nRoute::resource('taxonomies.tags', Cp\\TagController::class)\n    ->only('create', 'store', 'edit', 'update')\n    ->scoped();\n\nRoute::resource('groups', Cp\\GroupController::class)->only(\n    'index',\n    'create',\n    'store',\n    'edit',\n    'update',\n);\n\nRoute::resource('users', Cp\\UserController::class)->only(\n    'index',\n    'create',\n    'store',\n    'edit',\n    'update',\n);\n\nRoute::resource('reaction-sets', Cp\\ReactionSetController::class)\n    ->only('index', 'create', 'store', 'edit', 'update')\n    ->parameters(['reaction-sets' => 'reactionSet']);\n\nRoute::resource('reaction-sets.reaction-types', Cp\\ReactionTypeController::class)\n    ->only('create', 'store', 'edit', 'update')\n    ->parameters(['reaction-sets' => 'reactionSet', 'reaction-types' => 'reactionType'])\n    ->scoped();\n\nRoute::post('reaction-sets/{reactionSet}/reaction-types/reorder', [\n    Cp\\ReactionTypeController::class,\n    'reorder',\n])->name('reaction-sets.reaction-types.reorder');\n\nresolve(Waterhole\\Extend\\Routing\\CpRoutes::class);\n"
  },
  {
    "path": "routes/forum.php",
    "content": "<?php\n\nuse Illuminate\\Support\\Facades\\Route;\nuse Waterhole\\Http\\Controllers\\ActionsController;\nuse Waterhole\\Http\\Controllers\\Auth\\ConfirmPasswordController;\nuse Waterhole\\Http\\Controllers\\Auth\\ForgotPasswordController;\nuse Waterhole\\Http\\Controllers\\Auth\\LoginController;\nuse Waterhole\\Http\\Controllers\\Auth\\LogoutController;\nuse Waterhole\\Http\\Controllers\\Auth\\RegisterController;\nuse Waterhole\\Http\\Controllers\\Auth\\ResetPasswordController;\nuse Waterhole\\Http\\Controllers\\Auth\\SsoController;\nuse Waterhole\\Http\\Controllers\\Auth\\VerifyEmailController;\nuse Waterhole\\Http\\Controllers\\FormatController;\nuse Waterhole\\Http\\Controllers\\Forum\\CommentController;\nuse Waterhole\\Http\\Controllers\\Forum\\IndexController;\nuse Waterhole\\Http\\Controllers\\Forum\\ModerationController;\nuse Waterhole\\Http\\Controllers\\Forum\\NotificationController;\nuse Waterhole\\Http\\Controllers\\Forum\\PostController;\nuse Waterhole\\Http\\Controllers\\Forum\\PreferencesController;\nuse Waterhole\\Http\\Controllers\\Forum\\RssController;\nuse Waterhole\\Http\\Controllers\\Forum\\SearchController;\nuse Waterhole\\Http\\Controllers\\Forum\\UserController;\nuse Waterhole\\Http\\Controllers\\ImpersonateController;\nuse Waterhole\\Http\\Controllers\\UploadController;\nuse Waterhole\\Http\\Controllers\\UserLookupController;\n\n// Feed\nRoute::get('/', [IndexController::class, 'home'])->name('home');\nRoute::get('channels/{channel:slug}', [IndexController::class, 'channel'])->name('channels.show');\nRoute::get('pages/{page:slug}', [IndexController::class, 'page'])->name('page');\n\n// RSS\nRoute::get('posts.rss', [RssController::class, 'posts'])->name('rss.posts');\nRoute::get('channels/{channel:slug}/posts.rss', [RssController::class, 'channel'])->name(\n    'rss.channel',\n);\n\n// Actions\nRoute::get('actions/menu', [ActionsController::class, 'menu'])->name('actions.menu');\nRoute::get('actions/confirm', [ActionsController::class, 'confirm'])->name('actions.create');\nRoute::post('actions/run', [ActionsController::class, 'run'])->name('actions.store');\n\n// Posts\nRoute::resource('posts', PostController::class)->only([\n    'show',\n    'create',\n    'store',\n    'edit',\n    'update',\n]);\n\n// Comments\nRoute::resource('posts.comments', CommentController::class)\n    ->only(['show', 'create', 'store', 'edit', 'update'])\n    ->scoped();\n\n// Reactions\nRoute::get('posts/{post}/reactions/{reactionType}', [PostController::class, 'reactions'])->name(\n    'posts.reactions',\n);\nRoute::get('comments/{comment}/reactions/{reactionType}', [\n    CommentController::class,\n    'reactions',\n])->name('comments.reactions');\n\n// Users\nRoute::get('users/{user}/posts', [UserController::class, 'posts'])->name('user.posts');\nRoute::get('users/{user}/comments', [UserController::class, 'comments'])->name('user.comments');\nRoute::resource('users', UserController::class)->only(['show']);\n\n// Preferences\nRoute::get('preferences', [PreferencesController::class, 'index'])->name('preferences');\n\nRoute::get('preferences/account', [PreferencesController::class, 'account'])->name(\n    'preferences.account',\n);\nRoute::post('preferences/email', [PreferencesController::class, 'changeEmail'])->name(\n    'preferences.email',\n);\n\nif (config('waterhole.auth.password_enabled', true)) {\n    Route::post('preferences/password', [PreferencesController::class, 'changePassword'])->name(\n        'preferences.password',\n    );\n}\n\nRoute::get('preferences/profile', [PreferencesController::class, 'profile'])->name(\n    'preferences.profile',\n);\nRoute::post('preferences/profile', [PreferencesController::class, 'saveProfile']);\n\nRoute::get('preferences/notifications', [PreferencesController::class, 'notifications'])->name(\n    'preferences.notifications',\n);\nRoute::post('preferences/notifications', [PreferencesController::class, 'saveNotifications']);\n\n// Notifications\nRoute::get('notifications/unsubscribe', [NotificationController::class, 'unsubscribe'])->name(\n    'notifications.unsubscribe',\n);\nRoute::post('notifications/read', [NotificationController::class, 'read'])->name(\n    'notifications.read',\n);\nRoute::get('notifications/{notification}/go', [NotificationController::class, 'go'])->name(\n    'notifications.go',\n);\nRoute::resource('notifications', NotificationController::class)->only(['index', 'show']);\n\n// Moderation\nRoute::get('moderation', ModerationController::class)->name('moderation');\n\n// Search\nif (config('waterhole.system.search_engine')) {\n    Route::get('search', SearchController::class)->name('search');\n}\n\n$authAvailable =\n    count(config('waterhole.auth.providers', [])) ||\n    config('waterhole.auth.password_enabled', true);\n\n// Register\nif (config('waterhole.auth.allow_registration', true) && $authAvailable) {\n    Route::get('register', [RegisterController::class, 'showRegistrationForm'])->name('register');\n}\n\nRoute::get('register/{payload}', [RegisterController::class, 'registerWithPayload'])->name(\n    'register.payload',\n);\nRoute::post('register', [RegisterController::class, 'register'])->name('register.submit');\n\n// Login\nif ($authAvailable) {\n    Route::get('login', [LoginController::class, 'showLoginForm'])->name('login');\n}\n\nif (config('waterhole.auth.password_enabled', true)) {\n    Route::post('login', [LoginController::class, 'login']);\n\n    // Forgot Password\n    Route::get('forgot-password', [ForgotPasswordController::class, 'showLinkRequestForm'])->name(\n        'forgot-password',\n    );\n    Route::post('forgot-password', [ForgotPasswordController::class, 'sendResetLinkEmail']);\n\n    // Reset Password\n    Route::get('reset-password/{token}', [ResetPasswordController::class, 'showResetForm'])->name(\n        'reset-password',\n    );\n    Route::post('reset-password/{token}', [ResetPasswordController::class, 'reset']);\n}\n\n// Verify Email\nRoute::get('verify-email/{id}', [VerifyEmailController::class, 'verify'])->name('verify-email');\nRoute::post('verify-email', [VerifyEmailController::class, 'resend'])->name('verify-email.resend');\n\n// Confirm Password\nRoute::get('confirm-password', [ConfirmPasswordController::class, 'showConfirmForm'])->name(\n    'confirm-password',\n);\nRoute::post('confirm-password', [ConfirmPasswordController::class, 'confirm']);\n\n// Logout\nRoute::post('logout', [LogoutController::class, 'logout'])->name('logout');\n\n// SSO\nRoute::get('auth/{provider}', [SsoController::class, 'login'])->name('sso.login');\nRoute::get('auth/{provider}/callback', [SsoController::class, 'callback'])->name('sso.callback');\n\n// Utils\nRoute::get('user-lookup/{post?}', UserLookupController::class)->name('user-lookup');\nRoute::post('format', FormatController::class)->name('format');\nRoute::post('upload', UploadController::class)->name('upload');\nRoute::get('impersonate/{user}', ImpersonateController::class)->name('impersonate');\n\nresolve(Waterhole\\Extend\\Routing\\ForumRoutes::class);\n"
  },
  {
    "path": "src/Actions/Action.php",
    "content": "<?php\n\nnamespace Waterhole\\Actions;\n\nuse Illuminate\\Contracts\\View\\View;\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\Support\\HtmlString;\nuse Illuminate\\View\\ComponentAttributeBag;\nuse Waterhole\\Models\\Model;\nuse Waterhole\\Models\\User;\n\n/**\n * Base class for an Action.\n *\n * Actions are a mechanism for performing tasks on one or more models – for\n * example, deleting comments, or locking a post. Each item's context menu is\n * made up of a list of Actions.\n *\n * To define a new action, extend this class, and override and implement methods\n * as required. Use the `Waterhole\\Extend\\Core\\Actions` extender to register an\n * action class for a model.\n */\nabstract class Action\n{\n    /**\n     * Whether the action can be applied to multiple models at once.\n     */\n    public bool $bulk = false;\n\n    /**\n     * Whether the action requires confirmation or user input before it is run.\n     */\n    public bool $confirm = false;\n\n    /**\n     * Whether the action is destructive, and should have a red appearance.\n     */\n    public bool $destructive = false;\n\n    /**\n     * Whether the action can logically be applied to the given model.\n     */\n    public function appliesTo(Model $model): bool\n    {\n        return true;\n    }\n\n    /**\n     * Whether a user is allowed to apply the action to the given model.\n     *\n     * By default, the action can be applied if the user is logged-in.\n     */\n    public function authorize(?User $user, Model $model): bool\n    {\n        return (bool) $user;\n    }\n\n    /**\n     * Whether the action should be listed in a menu for the given models.\n     */\n    public function shouldRender(Collection $models, ?string $context = null): bool\n    {\n        return true;\n    }\n\n    /**\n     * Whether the action should present a confirmation step for these models.\n     */\n    public function shouldConfirm(Collection $models): bool\n    {\n        return $this->confirm;\n    }\n\n    /**\n     * The label to be displayed in the action button.\n     */\n    abstract public function label(Collection $models): string;\n\n    /**\n     * The name of an icon to be displayed in the action button.\n     *\n     * @see \\Waterhole\\View\\Components\\Icon\n     */\n    public function icon(Collection $models): ?string\n    {\n        return null;\n    }\n\n    /**\n     * Any extra attributes for the action button.\n     */\n    public function attributes(Collection $models): array\n    {\n        return [];\n    }\n\n    /**\n     * Render the action button.\n     */\n    public function render(\n        Collection $models,\n        array $attributes,\n        bool $tooltip = false,\n        bool $ellipsis = false,\n    ): HtmlString {\n        // If the action requires confirmation, we will override the form's\n        // method and action to take the user to the confirmation route.\n        if ($confirm = $this->shouldConfirm($models)) {\n            $defaultAttributes = [\n                'formmethod' => 'GET',\n                'formaction' => route('waterhole.actions.create'),\n                'data-turbo-frame' => 'modal',\n            ];\n        }\n\n        $attributes = (new ComponentAttributeBag($this->attributes($models)))\n            ->merge($defaultAttributes ?? [])\n            ->merge($attributes);\n\n        if ($this->destructive) {\n            $attributes = $attributes->class('color-danger');\n        }\n\n        $class = e(static::class);\n        $content = $this->renderContent($models, $tooltip, $ellipsis && $confirm);\n\n        return new HtmlString(\n            <<<html\n                <button type=\"submit\" name=\"action_class\" value=\"$class\" $attributes>$content</button>\n            html\n            ,\n        );\n    }\n\n    /**\n     * Render the content of the action button.\n     */\n    protected function renderContent(\n        Collection $models,\n        bool $tooltip = false,\n        bool $ellipsis = false,\n    ): HtmlString {\n        $label = e($this->label($models));\n        $icon = ($iconName = $this->icon($models))\n            ? svg($iconName, 'icon icon-' . $iconName)->toHtml()\n            : '';\n        $tag = $tooltip ? 'ui-tooltip' : 'span';\n        $ellipsis = $ellipsis ? '...' : '';\n\n        return new HtmlString(\"$icon <$tag>$label$ellipsis</$tag>\");\n    }\n\n    /**\n     * Confirmation message or view to prompt the user with before the action\n     * is run.\n     */\n    public function confirm(Collection $models): null|string|array|HtmlString|View\n    {\n        return null;\n    }\n\n    /**\n     * Label to be displayed on the confirmation button.\n     */\n    public function confirmButton(Collection $models): string\n    {\n        return 'Confirm';\n    }\n\n    /**\n     * Run the action on the given models.\n     *\n     * You can optionally return a response, such as a redirect or a file\n     * download. If you don't return anything, Waterhole will keep the user on\n     * the current page.\n     */\n    public function run(Collection $models)\n    {\n        return null;\n    }\n\n    /**\n     * Stream partial updates to the page via Turbo Streams.\n     *\n     * Return an array of <turbo-stream> elements. The default implementation\n     * gets streams from the model's `streamRemoved` method if the action is\n     * destructive, and the `streamUpdated` method if it isn't.\n     *\n     * @see \\Waterhole\\View\\TurboStream\n     */\n    public function stream(Model $model): array\n    {\n        $method = $this->destructive ? 'streamRemoved' : 'streamUpdated';\n\n        if (method_exists($model, $method)) {\n            return $model->$method();\n        }\n\n        return [];\n    }\n}\n"
  },
  {
    "path": "src/Actions/Concerns/RemovesContent.php",
    "content": "<?php\n\nnamespace Waterhole\\Actions\\Concerns;\n\nuse Illuminate\\Contracts\\View\\View;\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\Validation\\Rule;\nuse Waterhole\\Models\\Comment;\nuse Waterhole\\Models\\Model;\nuse Waterhole\\Models\\User;\n\ntrait RemovesContent\n{\n    use ResolvesFlags;\n\n    public function shouldConfirm(Collection $models): bool\n    {\n        return request()->user()->id !== $models[0]->user_id;\n    }\n\n    public function confirm(Collection $models): View\n    {\n        $actor = request()->user();\n        $model = $models[0];\n        $author = $model->user;\n\n        return view('waterhole::moderation.removal-reason', [\n            'reasons' => config('waterhole.forum.report_reasons'),\n            'model' => $model,\n            'canModerate' => $model->canModerate($actor),\n            'canSuspend' => $author && ($actor?->can('waterhole.user.suspend', $author) ?? false),\n            'title' => $this->confirmButton($models),\n            'selectedReason' =>\n                old('deleted_reason') ??\n                $model->pendingFlags\n                    ->groupBy('reason')\n                    ->sortByDesc(fn($group) => $group->count())\n                    ->keys()\n                    ->first(),\n        ]);\n    }\n\n    public function run(Collection $models)\n    {\n        $actor = request()->user();\n\n        $data = request()->validate([\n            'deleted_reason' => [\n                'string',\n                'nullable',\n                Rule::in(config('waterhole.forum.report_reasons')),\n            ],\n            'deleted_message' => ['nullable', 'string'],\n            'suspend_user' => ['nullable', 'boolean'],\n            'suspend_for' => ['nullable', 'integer', 'min:1'],\n            'suspend_unit' => ['nullable', 'in:days,weeks,indefinite'],\n        ]);\n\n        $models->each(function (Model $model) use ($actor, $data) {\n            $isSelf = $actor->id === $model->user_id;\n\n            if ($model instanceof Comment && $model->isAnswer()) {\n                $model->post->answer()->dissociate()->save();\n            }\n\n            $model->update([\n                'deleted_by' => $actor->id,\n                'deleted_reason' => $isSelf ? null : $data['deleted_reason'] ?? null,\n                'deleted_message' => $isSelf ? null : $data['deleted_message'] ?? null,\n            ]);\n            $model->delete();\n        });\n\n        $this->suspendAuthorIfRequested($models, $actor, $data);\n\n        return $this->resolveFlags($models);\n    }\n\n    protected function suspendAuthorIfRequested(Collection $models, User $actor, array $data): void\n    {\n        $model = $models->first();\n\n        if (!$model) {\n            return;\n        }\n\n        $author = $model->user;\n\n        if (!$author || !$models->every(fn(Model $model) => $model->user_id === $author->id)) {\n            return;\n        }\n\n        if (($data['suspend_user'] ?? false) && $actor->can('waterhole.user.suspend', $author)) {\n            $amount = (int) ($data['suspend_for'] ?? 0);\n\n            $suspendedUntil = match ($data['suspend_unit'] ?? null) {\n                'indefinite' => '2038-01-01',\n                'days' => now()->addDays($amount)->toDateTimeString(),\n                'weeks' => now()->addWeeks($amount)->toDateTimeString(),\n                default => null,\n            };\n\n            if ($suspendedUntil) {\n                $author->update(['suspended_until' => $suspendedUntil]);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/Actions/Concerns/ResolvesFlags.php",
    "content": "<?php\n\nnamespace Waterhole\\Actions\\Concerns;\n\nuse Illuminate\\Http\\RedirectResponse;\nuse Illuminate\\Support\\Collection;\nuse Waterhole\\Models\\Flag;\nuse Waterhole\\Models\\Model;\nuse Waterhole\\View\\TurboStream;\n\ntrait ResolvesFlags\n{\n    protected bool $resolvedFlags = false;\n\n    protected function resolveFlags(Collection $models): RedirectResponse|null\n    {\n        $user = request()->user();\n\n        foreach ($models as $model) {\n            if (\n                method_exists($model, 'canModerate') &&\n                $model->canModerate($user) &&\n                $model->resolveFlags($user)\n            ) {\n                $this->resolvedFlags = true;\n            }\n        }\n\n        if ($this->resolvedFlags) {\n            $next = Flag::query()->pending()->with('subject')->oldest()->first();\n\n            if ($next?->subject) {\n                return redirect($next->subject->flagUrl());\n            }\n\n            session()->flash('success', __('waterhole::forum.moderation-finished-message'));\n        }\n\n        return null;\n    }\n\n    public function stream(Model $model): array\n    {\n        if ($this->resolvedFlags) {\n            return [TurboStream::refresh()];\n        }\n\n        return parent::stream($model);\n    }\n}\n"
  },
  {
    "path": "src/Actions/CopyImpersonationUrl.php",
    "content": "<?php\n\nnamespace Waterhole\\Actions;\n\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\Support\\Facades\\URL;\nuse Waterhole\\Models\\Model;\nuse Waterhole\\Models\\User;\n\n/**\n * Copy Impersonation URL action.\n *\n *\n */\nclass CopyImpersonationUrl extends Link\n{\n    public function authorize(?User $user, Model $model): bool\n    {\n        return $user?->isAdmin() && $user->isNot($model);\n    }\n\n    public function label(Collection $models): string\n    {\n        return __('waterhole::user.copy-impersonation-url-button');\n    }\n\n    public function icon(Collection $models): string\n    {\n        return 'tabler-spy';\n    }\n\n    public function attributes(Collection $models): array\n    {\n        return [\n            'data-turbo-frame' => '_top',\n            'data-turbo-prefetch' => 'false',\n            'data-controller' => 'copy-link',\n            'data-action' => 'copy-link#copy',\n            'data-copy-link-message-value' => __(\n                'waterhole::user.impersonation-url-copied-message',\n            ),\n        ];\n    }\n\n    public function url(Model $model): string\n    {\n        return URL::temporarySignedRoute('waterhole.impersonate', now()->addHour(), [\n            'user' => $model,\n        ]);\n    }\n}\n"
  },
  {
    "path": "src/Actions/CopyLink.php",
    "content": "<?php\n\nnamespace Waterhole\\Actions;\n\nuse Illuminate\\Support\\Collection;\nuse Waterhole\\Models\\Model;\nuse Waterhole\\Models\\User;\n\n/**\n * Copy Link action.\n *\n * This action applies to any model that has a URL attribute. It is a link to\n * the model's URL, progressively enhanced via Stimulus to copy the link to\n * the clipboard instead of navigating there.\n */\nclass CopyLink extends Link\n{\n    public function authorize(?User $user, Model $model): bool\n    {\n        return true;\n    }\n\n    public function shouldRender(Collection $models, ?string $context = null): bool\n    {\n        return $context !== 'cp';\n    }\n\n    public function label(Collection $models): string\n    {\n        return __('waterhole::system.copy-link-button');\n    }\n\n    public function icon(Collection $models): string\n    {\n        return 'tabler-link';\n    }\n\n    public function attributes(Collection $models): array\n    {\n        return [\n            'data-turbo-frame' => '_top',\n            'data-controller' => 'copy-link',\n            'data-action' => 'copy-link#copy',\n            'data-copy-link-message-value' => __('waterhole::system.link-copied-message'),\n        ];\n    }\n\n    public function url(Model $model): string\n    {\n        return $model->url;\n    }\n}\n"
  },
  {
    "path": "src/Actions/DeleteChannel.php",
    "content": "<?php\n\nnamespace Waterhole\\Actions;\n\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\Validation\\Rule;\nuse Illuminate\\View\\View;\nuse Waterhole\\Models\\Channel;\nuse Waterhole\\Models\\Model;\nuse Waterhole\\Models\\User;\n\nclass DeleteChannel extends Action\n{\n    public bool $confirm = true;\n    public bool $destructive = true;\n\n    public function authorize(?User $user, Model $model): bool\n    {\n        return $user && $user->can('waterhole.channel.delete', $model);\n    }\n\n    public function shouldRender(Collection $models, ?string $context = null): bool\n    {\n        return $context === 'cp';\n    }\n\n    public function label(Collection $models): string\n    {\n        return __('waterhole::system.delete-button');\n    }\n\n    public function icon(Collection $models): string\n    {\n        return 'tabler-trash';\n    }\n\n    public function confirm(Collection $models): View\n    {\n        $channel = $models[0];\n        $postCount = $channel->posts()->count();\n\n        return view('waterhole::cp.structure.delete-channel', compact('channel', 'postCount'));\n    }\n\n    public function confirmButton(Collection $models): string\n    {\n        return __('waterhole::system.delete-confirm-button');\n    }\n\n    public function run(Collection $models)\n    {\n        $data = request()->validate([\n            'move_posts' => ['boolean'],\n            'channel_id' => ['required_if:move_posts,1', Rule::exists(Channel::class, 'id')],\n        ]);\n\n        $models->each(function (Channel $channel) use ($data) {\n            if ($data['move_posts'] ?? false) {\n                $channel->posts()->update(['channel_id' => $data['channel_id']]);\n            }\n\n            $channel->delete();\n        });\n    }\n}\n"
  },
  {
    "path": "src/Actions/DeleteComment.php",
    "content": "<?php\n\nnamespace Waterhole\\Actions;\n\nuse Illuminate\\Support\\Collection;\nuse Waterhole\\Models\\Comment;\nuse Waterhole\\Models\\Model;\nuse Waterhole\\Models\\User;\n\nclass DeleteComment extends Action\n{\n    public bool $confirm = true;\n    public bool $destructive = true;\n\n    public function appliesTo($model): bool\n    {\n        return $model instanceof Comment && $model->trashed();\n    }\n\n    public function authorize(?User $user, Model $model): bool\n    {\n        return $user && $user->can('waterhole.comment.moderate', $model);\n    }\n\n    public function label(Collection $models): string\n    {\n        return __('waterhole::forum.delete-forever-button');\n    }\n\n    public function icon(Collection $models): string\n    {\n        return 'tabler-trash';\n    }\n\n    public function confirm(Collection $models): string\n    {\n        return __('waterhole::forum.delete-comment-confirm-message');\n    }\n\n    public function confirmButton(Collection $models): string\n    {\n        return __('waterhole::system.delete-confirm-button');\n    }\n\n    public function run(Collection $models)\n    {\n        $moderator = request()->user();\n\n        $models->each(function (Comment $comment) use ($moderator) {\n            $comment->resolveFlags($moderator);\n            $comment->forceDelete();\n        });\n\n        // If the action was initiated from the comment's page, we can't send\n        // the user back there. Send them to the comment's post instead.\n        if (request('return') === $models[0]->url) {\n            return redirect($models[0]->post->url);\n        }\n    }\n}\n"
  },
  {
    "path": "src/Actions/DeleteGroup.php",
    "content": "<?php\n\nnamespace Waterhole\\Actions;\n\nuse Illuminate\\Support\\Collection;\nuse Waterhole\\Models\\Group;\nuse Waterhole\\Models\\Model;\nuse Waterhole\\Models\\User;\n\nclass DeleteGroup extends Action\n{\n    public bool $confirm = true;\n    public bool $destructive = true;\n\n    public function authorize(?User $user, Model $model): bool\n    {\n        return $user &&\n            $model instanceof Group &&\n            $model->isCustom() &&\n            $user->can('waterhole.group.delete', $model);\n    }\n\n    public function label(Collection $models): string\n    {\n        return __('waterhole::system.delete-button');\n    }\n\n    public function icon(Collection $models): string\n    {\n        return 'tabler-trash';\n    }\n\n    public function confirm(Collection $models): string\n    {\n        return __('waterhole::cp.delete-group-confirm-message');\n    }\n\n    public function confirmButton(Collection $models): string\n    {\n        return __('waterhole::system.delete-confirm-button');\n    }\n\n    public function run(Collection $models)\n    {\n        $models->each->delete();\n    }\n}\n"
  },
  {
    "path": "src/Actions/DeletePost.php",
    "content": "<?php\n\nnamespace Waterhole\\Actions;\n\nuse Illuminate\\Support\\Collection;\nuse Waterhole\\Models\\Model;\nuse Waterhole\\Models\\Post;\nuse Waterhole\\Models\\User;\n\nclass DeletePost extends Action\n{\n    public bool $confirm = true;\n\n    public bool $destructive = true;\n\n    public function appliesTo($model): bool\n    {\n        return $model instanceof Post && $model->trashed();\n    }\n\n    public function authorize(?User $user, Model $model): bool\n    {\n        return $user && $user->can('waterhole.post.moderate', $model);\n    }\n\n    public function label(Collection $models): string\n    {\n        return __('waterhole::forum.delete-forever-button');\n    }\n\n    public function icon(Collection $models): string\n    {\n        return 'tabler-trash-x';\n    }\n\n    public function confirm(Collection $models): string\n    {\n        return __('waterhole::forum.delete-post-confirm-message');\n    }\n\n    public function confirmButton(Collection $models): string\n    {\n        return __('waterhole::system.delete-confirm-button');\n    }\n\n    public function run(Collection $models)\n    {\n        $moderator = request()->user();\n\n        $models->each(function (Post $post) use ($moderator) {\n            $post->resolveFlags($moderator);\n            $post->forceDelete();\n        });\n\n        session()->flash('success', __('waterhole::forum.delete-post-success-message'));\n\n        // If the action was initiated from the post's page, we can't send the\n        // user back there. Instead, send them to the forum index.\n        if (request('return') === $models[0]->url) {\n            return redirect('/');\n        }\n    }\n}\n"
  },
  {
    "path": "src/Actions/DeleteReactionSet.php",
    "content": "<?php\n\nnamespace Waterhole\\Actions;\n\nuse Illuminate\\Support\\Collection;\nuse Waterhole\\Models\\Model;\nuse Waterhole\\Models\\User;\n\nclass DeleteReactionSet extends Action\n{\n    public bool $confirm = true;\n    public bool $destructive = true;\n\n    public function authorize(?User $user, Model $model): bool\n    {\n        return $user && $user->can('waterhole.reaction-set.delete', $model);\n    }\n\n    public function label(Collection $models): string\n    {\n        return __('waterhole::system.delete-button');\n    }\n\n    public function icon(Collection $models): string\n    {\n        return 'tabler-trash';\n    }\n\n    public function confirm(Collection $models): string\n    {\n        return __('waterhole::cp.delete-reaction-set-confirm-message');\n    }\n\n    public function confirmButton(Collection $models): string\n    {\n        return __('waterhole::system.delete-confirm-button');\n    }\n\n    public function run(Collection $models)\n    {\n        $models->each->delete();\n    }\n}\n"
  },
  {
    "path": "src/Actions/DeleteReactionType.php",
    "content": "<?php\n\nnamespace Waterhole\\Actions;\n\nuse Illuminate\\Support\\Collection;\nuse Waterhole\\Models\\Model;\nuse Waterhole\\Models\\User;\n\nclass DeleteReactionType extends Action\n{\n    public bool $confirm = true;\n    public bool $destructive = true;\n\n    public function authorize(?User $user, Model $model): bool\n    {\n        return $user && $user->can('waterhole.reaction-type.delete', $model);\n    }\n\n    public function label(Collection $models): string\n    {\n        return __('waterhole::system.delete-button');\n    }\n\n    public function icon(Collection $models): string\n    {\n        return 'tabler-trash';\n    }\n\n    public function confirm(Collection $models): string\n    {\n        return __('waterhole::cp.delete-reaction-type-confirm-message');\n    }\n\n    public function confirmButton(Collection $models): string\n    {\n        return __('waterhole::system.delete-confirm-button');\n    }\n\n    public function run(Collection $models)\n    {\n        $models->each->delete();\n    }\n}\n"
  },
  {
    "path": "src/Actions/DeleteSelf.php",
    "content": "<?php\n\nnamespace Waterhole\\Actions;\n\nuse Illuminate\\Support\\Collection;\nuse Waterhole\\Models\\Model;\nuse Waterhole\\Models\\User;\n\nclass DeleteSelf extends Action\n{\n    public bool $confirm = true;\n    public bool $destructive = true;\n\n    public function appliesTo(Model $model): bool\n    {\n        return $model instanceof User && !$model->isRootAdmin() && !$model->originalUser();\n    }\n\n    public function authorize(?User $user, Model $model): bool\n    {\n        return (bool) $user?->is($model);\n    }\n\n    public function shouldRender(Collection $models, ?string $context = null): bool\n    {\n        return false;\n    }\n\n    public function label(Collection $models): string\n    {\n        return __('waterhole::user.delete-account-button');\n    }\n\n    public function confirm(Collection $models): array\n    {\n        return [\n            __('waterhole::user.delete-account-confirmation-title'),\n            __('waterhole::user.delete-account-confirmation-description'),\n        ];\n    }\n\n    public function confirmButton(Collection $models): string\n    {\n        return __('waterhole::user.delete-account-button');\n    }\n\n    public function run(Collection $models)\n    {\n        $models[0]->delete();\n\n        auth()->logout();\n\n        session()->flash('success', __('waterhole::user.delete-account-success-message'));\n\n        return redirect('/');\n    }\n}\n"
  },
  {
    "path": "src/Actions/DeleteStructure.php",
    "content": "<?php\n\nnamespace Waterhole\\Actions;\n\nuse Illuminate\\Support\\Collection;\nuse Waterhole\\Models\\Model;\nuse Waterhole\\Models\\User;\n\nclass DeleteStructure extends Action\n{\n    public bool $confirm = true;\n    public bool $destructive = true;\n\n    public function authorize(?User $user, Model $model): bool\n    {\n        return $user && $user->can('waterhole.structure.delete', $model);\n    }\n\n    public function label(Collection $models): string\n    {\n        return __('waterhole::system.delete-button');\n    }\n\n    public function icon(Collection $models): string\n    {\n        return 'tabler-trash';\n    }\n\n    public function confirm(Collection $models): null|string\n    {\n        return __('waterhole::cp.delete-structure-confirm-message');\n    }\n\n    public function confirmButton(Collection $models): string\n    {\n        return __('waterhole::system.delete-confirm-button');\n    }\n\n    public function run(Collection $models)\n    {\n        $models->each->delete();\n    }\n}\n"
  },
  {
    "path": "src/Actions/DeleteTag.php",
    "content": "<?php\n\nnamespace Waterhole\\Actions;\n\nuse Illuminate\\Support\\Collection;\nuse Waterhole\\Models\\Model;\nuse Waterhole\\Models\\Tag;\nuse Waterhole\\Models\\User;\n\nclass DeleteTag extends Action\n{\n    public bool $confirm = true;\n    public bool $destructive = true;\n\n    public function authorize(?User $user, Model $model): bool\n    {\n        return $user && $user->can('waterhole.tag.delete', $model);\n    }\n\n    public function label(Collection $models): string\n    {\n        return __('waterhole::system.delete-button');\n    }\n\n    public function icon(Collection $models): string\n    {\n        return 'tabler-trash';\n    }\n\n    public function confirm(Collection $models): string\n    {\n        return __('waterhole::cp.delete-tag-confirm-message');\n    }\n\n    public function confirmButton(Collection $models): string\n    {\n        return __('waterhole::system.delete-confirm-button');\n    }\n\n    public function run(Collection $models)\n    {\n        $models->each->delete();\n    }\n}\n"
  },
  {
    "path": "src/Actions/DeleteTaxonomy.php",
    "content": "<?php\n\nnamespace Waterhole\\Actions;\n\nuse Illuminate\\Support\\Collection;\nuse Waterhole\\Models\\Model;\nuse Waterhole\\Models\\User;\n\nclass DeleteTaxonomy extends Action\n{\n    public bool $confirm = true;\n    public bool $destructive = true;\n\n    public function authorize(?User $user, Model $model): bool\n    {\n        return $user && $user->can('waterhole.taxonomy.delete', $model);\n    }\n\n    public function label(Collection $models): string\n    {\n        return __('waterhole::system.delete-button');\n    }\n\n    public function icon(Collection $models): string\n    {\n        return 'tabler-trash';\n    }\n\n    public function confirm(Collection $models): string\n    {\n        return __('waterhole::cp.delete-taxonomy-confirm-message');\n    }\n\n    public function confirmButton(Collection $models): string\n    {\n        return __('waterhole::system.delete-confirm-button');\n    }\n\n    public function run(Collection $models)\n    {\n        $models->each->delete();\n    }\n}\n"
  },
  {
    "path": "src/Actions/DeleteUser.php",
    "content": "<?php\n\nnamespace Waterhole\\Actions;\n\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\Support\\Facades\\DB;\nuse Illuminate\\View\\View;\nuse Waterhole\\Models\\Model;\nuse Waterhole\\Models\\User;\n\nclass DeleteUser extends Action\n{\n    public bool $confirm = true;\n    public bool $destructive = true;\n\n    public function appliesTo(Model $model): bool\n    {\n        return $model instanceof User && !$model->isRootAdmin();\n    }\n\n    public function authorize(?User $user, Model $model): bool\n    {\n        return $user && $user->can('waterhole.user.delete', $model) && $user->isNot($model);\n    }\n\n    public function label(Collection $models): string\n    {\n        return __('waterhole::system.delete-button');\n    }\n\n    public function icon(Collection $models): string\n    {\n        return 'tabler-trash';\n    }\n\n    public function confirm(Collection $models): View\n    {\n        return view('waterhole::cp.users.delete', [\n            'users' => $models,\n        ]);\n    }\n\n    public function confirmButton(Collection $models): string\n    {\n        return __('waterhole::system.delete-confirm-button');\n    }\n\n    public function run(Collection $models)\n    {\n        DB::transaction(function () use ($models) {\n            if (request('delete_content')) {\n                $models->each(function (User $user) {\n                    $user->posts()->delete();\n                    $user->comments()->delete();\n                });\n            }\n\n            $models->each->delete();\n        });\n\n        session()->flash('success', __('waterhole::cp.delete-user-success-message'));\n\n        // If the action was initiated from the user's page, we can't send the\n        // user back there. Instead, send them to the forum index.\n        if (str_starts_with(request('return'), $models[0]->url)) {\n            return redirect('/');\n        }\n    }\n}\n"
  },
  {
    "path": "src/Actions/DismissFlags.php",
    "content": "<?php\n\nnamespace Waterhole\\Actions;\n\nuse Illuminate\\Support\\Collection;\nuse Waterhole\\Actions\\Concerns\\ResolvesFlags;\nuse Waterhole\\Models\\Model;\nuse Waterhole\\Models\\User;\n\nclass DismissFlags extends Action\n{\n    use ResolvesFlags;\n\n    public function authorize(?User $user, Model $model): bool\n    {\n        return method_exists($model, 'canModerate') &&\n            $model->canModerate($user) &&\n            $model->pendingFlags->isNotEmpty();\n    }\n\n    public function label(Collection $models): string\n    {\n        if ($this->isApproving($models[0])) {\n            return __('waterhole::forum.approve-button');\n        }\n\n        return __('waterhole::forum.flag-dismiss-button');\n    }\n\n    public function icon(Collection $models): ?string\n    {\n        if ($this->isApproving($models[0])) {\n            return 'tabler-check';\n        }\n\n        return 'tabler-flag-off';\n    }\n\n    public function run(Collection $models)\n    {\n        foreach ($models as $model) {\n            if ($this->isApproving($model)) {\n                $model->update(['is_approved' => true]);\n            }\n        }\n\n        return $this->resolveFlags($models);\n    }\n\n    private function isApproving($model): bool\n    {\n        return method_exists($model, 'isApproved') && !$model->isApproved();\n    }\n}\n"
  },
  {
    "path": "src/Actions/EditChannel.php",
    "content": "<?php\n\nnamespace Waterhole\\Actions;\n\nuse Illuminate\\Support\\Collection;\nuse Waterhole\\Models\\Model;\nuse Waterhole\\Models\\User;\n\nclass EditChannel extends Link\n{\n    public function authorize(?User $user, Model $model): bool\n    {\n        return $user && $user->can('waterhole.channel.edit', $model);\n    }\n\n    public function label(Collection $models): string\n    {\n        return __('waterhole::system.edit-link');\n    }\n\n    public function icon(Collection $models): string\n    {\n        return 'tabler-pencil';\n    }\n\n    public function url(Model $model): string\n    {\n        return $model->edit_url;\n    }\n}\n"
  },
  {
    "path": "src/Actions/EditComment.php",
    "content": "<?php\n\nnamespace Waterhole\\Actions;\n\nuse Illuminate\\Support\\Collection;\nuse Waterhole\\Models\\Comment;\nuse Waterhole\\Models\\Model;\nuse Waterhole\\Models\\User;\n\nclass EditComment extends Link\n{\n    public function authorize(?User $user, Model $model): bool\n    {\n        return $user && $user->can('waterhole.comment.edit', $model);\n    }\n\n    public function label(Collection $models): string\n    {\n        return __('waterhole::system.edit-link');\n    }\n\n    public function icon(Collection $models): string\n    {\n        return 'tabler-pencil';\n    }\n\n    public function url(Model $model): string\n    {\n        return $model->edit_url;\n    }\n}\n"
  },
  {
    "path": "src/Actions/EditGroup.php",
    "content": "<?php\n\nnamespace Waterhole\\Actions;\n\nuse Illuminate\\Support\\Collection;\nuse Waterhole\\Models\\Model;\nuse Waterhole\\Models\\User;\n\nclass EditGroup extends Link\n{\n    public function authorize(?User $user, Model $model): bool\n    {\n        return $user && $user->can('waterhole.group.edit', $model);\n    }\n\n    public function label(Collection $models): string\n    {\n        return __('waterhole::system.edit-link');\n    }\n\n    public function icon(Collection $models): string\n    {\n        return 'tabler-pencil';\n    }\n\n    public function url(Model $model): string\n    {\n        return $model->edit_url;\n    }\n}\n"
  },
  {
    "path": "src/Actions/EditPost.php",
    "content": "<?php\n\nnamespace Waterhole\\Actions;\n\nuse Illuminate\\Support\\Collection;\nuse Waterhole\\Models\\Model;\nuse Waterhole\\Models\\User;\n\nclass EditPost extends Link\n{\n    public function authorize(?User $user, Model $model): bool\n    {\n        return $user && $user->can('waterhole.post.edit', $model);\n    }\n\n    public function label(Collection $models): string\n    {\n        return __('waterhole::forum.edit-post-link');\n    }\n\n    public function icon(Collection $models): string\n    {\n        return 'tabler-pencil';\n    }\n\n    public function url(Model $model): string\n    {\n        return $model->edit_url;\n    }\n}\n"
  },
  {
    "path": "src/Actions/EditReactionSet.php",
    "content": "<?php\n\nnamespace Waterhole\\Actions;\n\nuse Illuminate\\Support\\Collection;\nuse Waterhole\\Models\\Model;\nuse Waterhole\\Models\\ReactionSet;\nuse Waterhole\\Models\\User;\n\nclass EditReactionSet extends Link\n{\n    public function authorize(?User $user, Model $model): bool\n    {\n        return $user && $user->can('waterhole.reaction-set.edit', $model);\n    }\n\n    public function label(Collection $models): string\n    {\n        return __('waterhole::system.edit-link');\n    }\n\n    public function icon(Collection $models): string\n    {\n        return 'tabler-pencil';\n    }\n\n    public function url(Model $model): string\n    {\n        return $model->edit_url;\n    }\n}\n"
  },
  {
    "path": "src/Actions/EditReactionType.php",
    "content": "<?php\n\nnamespace Waterhole\\Actions;\n\nuse Illuminate\\Support\\Collection;\nuse Waterhole\\Models\\Model;\nuse Waterhole\\Models\\User;\n\nclass EditReactionType extends Link\n{\n    public function authorize(?User $user, Model $model): bool\n    {\n        return $user && $user->can('waterhole.reaction-type.edit', $model);\n    }\n\n    public function label(Collection $models): string\n    {\n        return __('waterhole::system.edit-link');\n    }\n\n    public function icon(Collection $models): string\n    {\n        return 'tabler-pencil';\n    }\n\n    public function url(Model $model): string\n    {\n        return $model->edit_url;\n    }\n\n    public function attributes(Collection $models): array\n    {\n        return ['data-turbo-frame' => 'modal'];\n    }\n}\n"
  },
  {
    "path": "src/Actions/EditStructure.php",
    "content": "<?php\n\nnamespace Waterhole\\Actions;\n\nuse Illuminate\\Support\\Collection;\nuse Waterhole\\Models\\Model;\nuse Waterhole\\Models\\Page;\nuse Waterhole\\Models\\StructureHeading;\nuse Waterhole\\Models\\StructureLink;\nuse Waterhole\\Models\\User;\n\nclass EditStructure extends Link\n{\n    public function authorize(?User $user, Model $model): bool\n    {\n        return $user && $user->can('waterhole.structure.edit', $model);\n    }\n\n    public function label(Collection $models): string\n    {\n        return __('waterhole::system.edit-link');\n    }\n\n    public function icon(Collection $models): string\n    {\n        return 'tabler-pencil';\n    }\n\n    public function url(Model $model): string\n    {\n        return $model->edit_url;\n    }\n}\n"
  },
  {
    "path": "src/Actions/EditTag.php",
    "content": "<?php\n\nnamespace Waterhole\\Actions;\n\nuse Illuminate\\Support\\Collection;\nuse Waterhole\\Models\\Model;\nuse Waterhole\\Models\\User;\n\nclass EditTag extends Link\n{\n    public function authorize(?User $user, Model $model): bool\n    {\n        return $user && $user->can('waterhole.tag.edit', $model);\n    }\n\n    public function label(Collection $models): string\n    {\n        return __('waterhole::system.edit-link');\n    }\n\n    public function icon(Collection $models): string\n    {\n        return 'tabler-pencil';\n    }\n\n    public function url(Model $model): string\n    {\n        return $model->edit_url;\n    }\n\n    public function attributes(Collection $models): array\n    {\n        return ['data-turbo-frame' => 'modal'];\n    }\n}\n"
  },
  {
    "path": "src/Actions/EditTaxonomy.php",
    "content": "<?php\n\nnamespace Waterhole\\Actions;\n\nuse Illuminate\\Support\\Collection;\nuse Waterhole\\Models\\Model;\nuse Waterhole\\Models\\Taxonomy;\nuse Waterhole\\Models\\User;\n\nclass EditTaxonomy extends Link\n{\n    public function authorize(?User $user, Model $model): bool\n    {\n        return $user && $user->can('waterhole.taxonomy.edit', $model);\n    }\n\n    public function label(Collection $models): string\n    {\n        return __('waterhole::system.edit-link');\n    }\n\n    public function icon(Collection $models): string\n    {\n        return 'tabler-pencil';\n    }\n\n    public function url(Model $model): string\n    {\n        return $model->edit_url;\n    }\n}\n"
  },
  {
    "path": "src/Actions/EditUser.php",
    "content": "<?php\n\nnamespace Waterhole\\Actions;\n\nuse Illuminate\\Support\\Collection;\nuse Waterhole\\Models\\Model;\nuse Waterhole\\Models\\User;\n\nclass EditUser extends Link\n{\n    public function authorize(?User $user, Model $model): bool\n    {\n        return $user && $user->can('waterhole.user.edit', $model);\n    }\n\n    public function label(Collection $models): string\n    {\n        return __('waterhole::system.edit-link');\n    }\n\n    public function icon(Collection $models): string\n    {\n        return 'tabler-pencil';\n    }\n\n    public function url(Model $model): string\n    {\n        return $model->edit_url .\n            '?' .\n            http_build_query([\n                'return' => request('return', request()->fullUrl()),\n            ]);\n    }\n}\n"
  },
  {
    "path": "src/Actions/Follow.php",
    "content": "<?php\n\nnamespace Waterhole\\Actions;\n\nuse Illuminate\\Support\\Collection;\nuse Waterhole\\Models\\Model;\nuse Waterhole\\View\\Components\\FollowButton;\nuse Waterhole\\View\\TurboStream;\n\nclass Follow extends Action\n{\n    public function shouldRender(Collection $models, ?string $context = null): bool\n    {\n        return $context !== 'cp' && !$models->some->isFollowed();\n    }\n\n    public function label(Collection $models): string\n    {\n        return __('waterhole::forum.follow-button');\n    }\n\n    public function icon(Collection $models): string\n    {\n        return 'tabler-bell';\n    }\n\n    public function run(Collection $models)\n    {\n        $models->each->follow();\n    }\n\n    public function stream(Model $model): array\n    {\n        return [...parent::stream($model), TurboStream::replace(new FollowButton($model))];\n    }\n}\n"
  },
  {
    "path": "src/Actions/Ignore.php",
    "content": "<?php\n\nnamespace Waterhole\\Actions;\n\nuse Illuminate\\Support\\Collection;\nuse Waterhole\\Models\\Model;\nuse Waterhole\\View\\Components\\FollowButton;\nuse Waterhole\\View\\TurboStream;\n\nclass Ignore extends Action\n{\n    public function shouldRender(Collection $models, ?string $context = null): bool\n    {\n        return $context !== 'cp' && !$models->some->isIgnored();\n    }\n\n    public function label(Collection $models): string\n    {\n        return __('waterhole::forum.ignore-button');\n    }\n\n    public function icon(Collection $models): string\n    {\n        return 'tabler-eye-off';\n    }\n\n    public function run(Collection $models)\n    {\n        $models->each->ignore();\n    }\n\n    public function stream(Model $model): array\n    {\n        return [...parent::stream($model), TurboStream::replace(new FollowButton($model))];\n    }\n}\n"
  },
  {
    "path": "src/Actions/Link.php",
    "content": "<?php\n\nnamespace Waterhole\\Actions;\n\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\Support\\HtmlString;\nuse Illuminate\\View\\ComponentAttributeBag;\nuse Waterhole\\Models\\Model;\n\n/**\n * Base class for a \"link\" action.\n *\n * Some actions don't actually perform an action at all – they just redirect to\n * another location. A good example is the \"edit post\" action, which just sends\n * the user to the post's edit route.\n *\n * For cases like this, this class will render the action as an `<a>` element\n * rather than a `<button>`.\n */\nabstract class Link extends Action\n{\n    /**\n     * The URL to link to.\n     */\n    abstract public function url(Model $model): string;\n\n    public function render(\n        Collection $models,\n        array $attributes,\n        bool $tooltip = false,\n        bool $ellipsis = false,\n    ): HtmlString {\n        $link = e($this->url($models[0]));\n\n        $attributes = (new ComponentAttributeBag($attributes))->merge($this->attributes($models));\n\n        $content = $this->renderContent($models, $tooltip);\n\n        return new HtmlString(\n            <<<html\n                <a href=\"$link\" $attributes>$content</a>\n            html\n            ,\n        );\n    }\n}\n"
  },
  {
    "path": "src/Actions/Lock.php",
    "content": "<?php\n\nnamespace Waterhole\\Actions;\n\nuse Illuminate\\Support\\Collection;\nuse Waterhole\\Models\\Model;\nuse Waterhole\\Models\\Post;\nuse Waterhole\\Models\\User;\nuse Waterhole\\View\\Components\\CommentsLocked;\nuse Waterhole\\View\\TurboStream;\n\nclass Lock extends Action\n{\n    public function appliesTo(Model $model): bool\n    {\n        return $model instanceof Post && !$model->is_locked;\n    }\n\n    public function authorize(?User $user, Model $model): bool\n    {\n        return $user && $user->can('waterhole.post.moderate', $model);\n    }\n\n    public function label(Collection $models): string\n    {\n        return __('waterhole::forum.lock-comments-button');\n    }\n\n    public function icon(Collection $models): string\n    {\n        return 'tabler-lock';\n    }\n\n    public function run(Collection $models)\n    {\n        $models->each->update(['is_locked' => true]);\n    }\n\n    public function stream(Model $model): array\n    {\n        return [...parent::stream($model), TurboStream::replace(new CommentsLocked($model))];\n    }\n}\n"
  },
  {
    "path": "src/Actions/MarkAsAnswer.php",
    "content": "<?php\n\nnamespace Waterhole\\Actions;\n\nuse Illuminate\\Support\\Collection;\nuse Waterhole\\Models\\Model;\nuse Waterhole\\Models\\User;\n\nclass MarkAsAnswer extends Action\n{\n    public function authorize(?User $user, Model $model): bool\n    {\n        return (bool) $user?->can('waterhole.post.edit', $model->post);\n    }\n\n    public function shouldRender(Collection $models, ?string $context = null): bool\n    {\n        return false;\n    }\n\n    public function label(Collection $models): string\n    {\n        return $models[0]->isAnswer()\n            ? __('waterhole::forum.unmark-as-answer-button')\n            : __('waterhole::forum.mark-as-answer-button');\n    }\n\n    public function icon(Collection $models): ?string\n    {\n        return $models[0]->isAnswer() ? 'tabler-x' : 'tabler-check';\n    }\n\n    public function run(Collection $models)\n    {\n        [$comment] = $models;\n\n        $relationship = $comment->post->answer();\n\n        if ($comment->isAnswer()) {\n            $relationship->dissociate()->save();\n        } else {\n            $relationship->associate($comment)->save();\n        }\n\n        return redirect($models[0]->post->url);\n    }\n\n    public function stream(Model $model): array\n    {\n        return [];\n    }\n}\n"
  },
  {
    "path": "src/Actions/MarkAsRead.php",
    "content": "<?php\n\nnamespace Waterhole\\Actions;\n\nuse Illuminate\\Support\\Collection;\nuse Waterhole\\Models\\Model;\nuse Waterhole\\Models\\Post;\n\nclass MarkAsRead extends Action\n{\n    public function appliesTo(Model $model): bool\n    {\n        return $model instanceof Post && $model->isUnread();\n    }\n\n    public function label(Collection $models): string\n    {\n        return __('waterhole::forum.mark-as-read-button');\n    }\n\n    public function icon(Collection $models): ?string\n    {\n        return 'tabler-check';\n    }\n\n    public function run(Collection $models)\n    {\n        $models->each(function ($post) {\n            $post->userState->read()->save();\n            $post->refresh();\n        });\n    }\n}\n"
  },
  {
    "path": "src/Actions/MoveToChannel.php",
    "content": "<?php\n\nnamespace Waterhole\\Actions;\n\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\Support\\Facades\\Gate;\nuse Illuminate\\Validation\\Rule;\nuse Illuminate\\View\\View;\nuse Waterhole\\Models\\Channel;\nuse Waterhole\\Models\\Model;\nuse Waterhole\\Models\\User;\n\nclass MoveToChannel extends Action\n{\n    public bool $confirm = true;\n\n    public function authorize(?User $user, Model $model): bool\n    {\n        return $user && $user->can('waterhole.post.move', $model);\n    }\n\n    public function label(Collection $models): string\n    {\n        return __('waterhole::forum.move-to-channel-button');\n    }\n\n    public function icon(Collection $models): string\n    {\n        return 'tabler-arrow-right';\n    }\n\n    public function confirm(Collection $models): View\n    {\n        return view('waterhole::posts.move-to-channel', ['posts' => $models]);\n    }\n\n    public function confirmButton(Collection $models): string\n    {\n        return __('waterhole::system.save-changes-button');\n    }\n\n    public function run(Collection $models)\n    {\n        $data = request()->validate([\n            'channel_id' => ['required', Rule::exists(Channel::class, 'id')],\n        ]);\n\n        Gate::authorize('waterhole.channel.post', Channel::findOrFail($data['channel_id']));\n\n        $models->each->update($data);\n    }\n}\n"
  },
  {
    "path": "src/Actions/Pin.php",
    "content": "<?php\n\nnamespace Waterhole\\Actions;\n\nuse Illuminate\\Support\\Collection;\nuse Waterhole\\Models\\Model;\nuse Waterhole\\Models\\Post;\nuse Waterhole\\Models\\User;\n\nclass Pin extends Action\n{\n    public function appliesTo(Model $model): bool\n    {\n        return $model instanceof Post && !$model->is_pinned;\n    }\n\n    public function authorize(?User $user, Model $model): bool\n    {\n        return $user && $user->can('waterhole.post.moderate', $model);\n    }\n\n    public function label(Collection $models): string\n    {\n        return __('waterhole::forum.pin-to-top-button');\n    }\n\n    public function icon(Collection $models): string\n    {\n        return 'tabler-pin';\n    }\n\n    public function run(Collection $models): void\n    {\n        $models->each->update(['is_pinned' => true]);\n    }\n\n    public function stream(Model $model): array\n    {\n        return [];\n    }\n}\n"
  },
  {
    "path": "src/Actions/React.php",
    "content": "<?php\n\nnamespace Waterhole\\Actions;\n\nuse Illuminate\\Support\\Collection;\nuse ReflectionClass;\nuse Waterhole\\Models\\Comment;\nuse Waterhole\\Models\\Model;\nuse Waterhole\\Models\\Post;\nuse Waterhole\\Models\\User;\nuse Waterhole\\View\\Components\\Reactions;\nuse Waterhole\\View\\TurboStream;\n\nclass React extends Action\n{\n    public function authorize(?User $user, Model $model): bool\n    {\n        return $user &&\n            $user->can(\n                'waterhole.' . strtolower((new ReflectionClass($model))->getShortName()) . '.react',\n                $model,\n            );\n    }\n\n    public function shouldRender(Collection $models, ?string $context = null): bool\n    {\n        return false;\n    }\n\n    public function label(Collection $models): string\n    {\n        return 'React';\n    }\n\n    public function icon(Collection $models): string\n    {\n        return 'tabler-mood-smile';\n    }\n\n    public function run(Collection $models)\n    {\n        $models->each(function (Post|Comment $item) {\n            $reaction = $item->reactions()->firstOrNew([\n                'user_id' => request()->user()->id,\n                'reaction_type_id' => request('reaction_type_id'),\n            ]);\n\n            if ($reaction->exists) {\n                $reaction->delete();\n            } else {\n                $reaction->save();\n            }\n\n            $item->recalculateScore()->save();\n        });\n    }\n\n    public function stream(Model $model): array\n    {\n        // Don't use `fresh` because we want global scopes to apply.\n        $model = $model::findOrFail($model->getKey());\n\n        return [TurboStream::replace(new Reactions($model))];\n    }\n}\n"
  },
  {
    "path": "src/Actions/RemoveComment.php",
    "content": "<?php\n\nnamespace Waterhole\\Actions;\n\nuse Illuminate\\Support\\Collection;\nuse Waterhole\\Actions\\Concerns\\RemovesContent;\nuse Waterhole\\Models\\Comment;\nuse Waterhole\\Models\\Model;\nuse Waterhole\\Models\\User;\n\nclass RemoveComment extends Action\n{\n    use RemovesContent;\n\n    public function appliesTo($model): bool\n    {\n        return $model instanceof Comment && !$model->trashed();\n    }\n\n    public function authorize(?User $user, Model $model): bool\n    {\n        return $user && $user->can('waterhole.comment.delete', $model);\n    }\n\n    public function label(Collection $models): string\n    {\n        return __('waterhole::forum.remove-button');\n    }\n\n    public function icon(Collection $models): string\n    {\n        return 'tabler-trash';\n    }\n\n    public function confirmButton(Collection $models): string\n    {\n        return __('waterhole::forum.remove-button');\n    }\n}\n"
  },
  {
    "path": "src/Actions/Report.php",
    "content": "<?php\n\nnamespace Waterhole\\Actions;\n\nuse Illuminate\\Contracts\\View\\View;\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\Validation\\Rule;\nuse Waterhole\\Models\\Model;\nuse Waterhole\\Models\\User;\n\nclass Report extends Action\n{\n    public bool $confirm = true;\n\n    public function appliesTo($model): bool\n    {\n        if (!method_exists($model, 'flags')) {\n            return false;\n        }\n\n        return !method_exists($model, 'trashed') || !$model->trashed();\n    }\n\n    public function authorize(?User $user, Model $model): bool\n    {\n        return $user && !$user->requiresApproval() && $user->id !== $model->user_id;\n    }\n\n    public function label(Collection $models): string\n    {\n        return __('waterhole::forum.report-button');\n    }\n\n    public function icon(Collection $models): string\n    {\n        return 'tabler-flag';\n    }\n\n    public function confirm(Collection $models): View\n    {\n        return view('waterhole::moderation.report', [\n            'reasons' => config('waterhole.forum.report_reasons'),\n            'title' => $this->confirmButton($models),\n        ]);\n    }\n\n    public function confirmButton(Collection $models): string\n    {\n        return __('waterhole::forum.report-confirm-button');\n    }\n\n    public function run(Collection $models)\n    {\n        $actor = request()->user();\n\n        $data = request()->validate([\n            'reason' => ['required', 'string', Rule::in(config('waterhole.forum.report_reasons'))],\n            'note' => ['nullable', 'string'],\n        ]);\n\n        $models->each(function (Model $model) use ($actor, $data) {\n            $model->flags()->create([\n                'reason' => $data['reason'],\n                'note' => $data['note'] ?? null,\n                'created_by' => $actor?->getKey(),\n            ]);\n        });\n    }\n}\n"
  },
  {
    "path": "src/Actions/RestoreComment.php",
    "content": "<?php\n\nnamespace Waterhole\\Actions;\n\nuse Illuminate\\Support\\Collection;\nuse Waterhole\\Models\\Comment;\nuse Waterhole\\Models\\Model;\nuse Waterhole\\Models\\User;\n\nclass RestoreComment extends Action\n{\n    public function appliesTo($model): bool\n    {\n        return $model instanceof Comment && $model->trashed();\n    }\n\n    public function authorize(?User $user, Model $model): bool\n    {\n        return $user && $user->can('waterhole.comment.restore', $model);\n    }\n\n    public function label(Collection $models): string\n    {\n        return __('waterhole::forum.restore-button');\n    }\n\n    public function icon(Collection $models): string\n    {\n        return 'tabler-share-3';\n    }\n\n    public function run(Collection $models)\n    {\n        $models->each(function (Comment $comment) {\n            $comment->update([\n                'deleted_by' => null,\n                'deleted_reason' => null,\n                'deleted_message' => null,\n            ]);\n            $comment->restore();\n        });\n    }\n}\n"
  },
  {
    "path": "src/Actions/RestorePost.php",
    "content": "<?php\n\nnamespace Waterhole\\Actions;\n\nuse Illuminate\\Support\\Collection;\nuse Waterhole\\Models\\Model;\nuse Waterhole\\Models\\Post;\nuse Waterhole\\Models\\User;\n\nclass RestorePost extends Action\n{\n    public function appliesTo($model): bool\n    {\n        return $model instanceof Post && $model->trashed();\n    }\n\n    public function authorize(?User $user, Model $model): bool\n    {\n        return $user && $user->can('waterhole.post.delete', $model);\n    }\n\n    public function label(Collection $models): string\n    {\n        return __('waterhole::forum.restore-button');\n    }\n\n    public function icon(Collection $models): string\n    {\n        return 'tabler-share-3';\n    }\n\n    public function run(Collection $models)\n    {\n        $models->each(function (Post $post) {\n            $post->update([\n                'deleted_by' => null,\n                'deleted_reason' => null,\n                'deleted_message' => null,\n            ]);\n            $post->restore();\n        });\n    }\n}\n"
  },
  {
    "path": "src/Actions/SuspendUser.php",
    "content": "<?php\n\nnamespace Waterhole\\Actions;\n\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\View\\View;\nuse Waterhole\\Models\\Model;\nuse Waterhole\\Models\\User;\n\nclass SuspendUser extends Action\n{\n    public bool $confirm = true;\n\n    public function appliesTo(Model $model): bool\n    {\n        return $model instanceof User && !$model->isRootAdmin();\n    }\n\n    public function authorize(?User $user, Model $model): bool\n    {\n        return $user && $user->can('waterhole.user.suspend', $model) && $user->isNot($model);\n    }\n\n    public function label(Collection $models): string\n    {\n        return $models[0]->suspended_until?->isFuture()\n            ? __('waterhole::user.edit-suspension-button')\n            : __('waterhole::user.suspend-button');\n    }\n\n    public function icon(Collection $models): string\n    {\n        return 'tabler-ban';\n    }\n\n    public function confirm(Collection $models): View\n    {\n        return view('waterhole::cp.users.suspend', ['users' => $models]);\n    }\n\n    public function confirmButton(Collection $models): string\n    {\n        return __('waterhole::user.suspend-button');\n    }\n\n    public function run(Collection $models)\n    {\n        $data = request()->validate([\n            'status' => ['required', 'in:none,indefinite,custom'],\n            'suspended_until' => ['nullable', 'date'],\n        ]);\n\n        $models->each->update([\n            'suspended_until' => match ($data['status']) {\n                'none' => null,\n                'indefinite' => '2038-01-01',\n                'custom' => $data['suspended_until'],\n            },\n        ]);\n    }\n}\n"
  },
  {
    "path": "src/Actions/TrashPost.php",
    "content": "<?php\n\nnamespace Waterhole\\Actions;\n\nuse Illuminate\\Support\\Collection;\nuse Waterhole\\Actions\\Concerns\\RemovesContent;\nuse Waterhole\\Models\\Model;\nuse Waterhole\\Models\\Post;\nuse Waterhole\\Models\\User;\n\nclass TrashPost extends Action\n{\n    use RemovesContent;\n\n    public function appliesTo($model): bool\n    {\n        return $model instanceof Post && !$model->trashed();\n    }\n\n    public function authorize(?User $user, Model $model): bool\n    {\n        return $user && $user->can('waterhole.post.delete', $model);\n    }\n\n    public function label(Collection $models): string\n    {\n        return __('waterhole::forum.move-to-trash-button');\n    }\n\n    public function icon(Collection $models): string\n    {\n        return 'tabler-trash';\n    }\n\n    public function confirmButton(Collection $models): string\n    {\n        return __('waterhole::forum.move-to-trash-button');\n    }\n}\n"
  },
  {
    "path": "src/Actions/Unfollow.php",
    "content": "<?php\n\nnamespace Waterhole\\Actions;\n\nuse Illuminate\\Support\\Collection;\nuse Waterhole\\Models\\Model;\nuse Waterhole\\View\\Components\\FollowButton;\nuse Waterhole\\View\\TurboStream;\n\nclass Unfollow extends Action\n{\n    public function shouldRender(Collection $models, ?string $context = null): bool\n    {\n        return $context !== 'cp' && $models->some->isFollowed();\n    }\n\n    public function label(Collection $models): string\n    {\n        return __('waterhole::forum.unfollow-button');\n    }\n\n    public function icon(Collection $models): string\n    {\n        return 'tabler-bell-off';\n    }\n\n    public function run(Collection $models)\n    {\n        $models->each->unfollow();\n    }\n\n    public function stream(Model $model): array\n    {\n        return [...parent::stream($model), TurboStream::replace(new FollowButton($model))];\n    }\n}\n"
  },
  {
    "path": "src/Actions/Unignore.php",
    "content": "<?php\n\nnamespace Waterhole\\Actions;\n\nuse Illuminate\\Support\\Collection;\nuse Waterhole\\Models\\Model;\nuse Waterhole\\View\\Components\\FollowButton;\nuse Waterhole\\View\\TurboStream;\n\nclass Unignore extends Action\n{\n    public function shouldRender(Collection $models, ?string $context = null): bool\n    {\n        return $context !== 'cp' && $models->some->isIgnored();\n    }\n\n    public function label(Collection $models): string\n    {\n        return __('waterhole::forum.unignore-button');\n    }\n\n    public function icon(Collection $models): string\n    {\n        return 'tabler-eye';\n    }\n\n    public function run(Collection $models)\n    {\n        $models->each->unignore();\n    }\n\n    public function stream(Model $model): array\n    {\n        return [...parent::stream($model), TurboStream::replace(new FollowButton($model))];\n    }\n}\n"
  },
  {
    "path": "src/Actions/Unlock.php",
    "content": "<?php\n\nnamespace Waterhole\\Actions;\n\nuse Illuminate\\Support\\Collection;\nuse Waterhole\\Models\\Model;\nuse Waterhole\\Models\\Post;\nuse Waterhole\\Models\\User;\nuse Waterhole\\View\\Components\\CommentsLocked;\nuse Waterhole\\View\\TurboStream;\n\nclass Unlock extends Action\n{\n    public function appliesTo(Model $model): bool\n    {\n        return $model instanceof Post && $model->is_locked;\n    }\n\n    public function authorize(?User $user, Model $model): bool\n    {\n        return $user && $user->can('waterhole.post.moderate', $model);\n    }\n\n    public function label(Collection $models): string\n    {\n        return __('waterhole::forum.unlock-comments-button');\n    }\n\n    public function icon(Collection $models): string\n    {\n        return 'tabler-lock-open';\n    }\n\n    public function run(Collection $models)\n    {\n        $models->each->update(['is_locked' => false]);\n    }\n\n    public function stream(Model $model): array\n    {\n        return [...parent::stream($model), TurboStream::replace(new CommentsLocked($model))];\n    }\n}\n"
  },
  {
    "path": "src/Actions/Unpin.php",
    "content": "<?php\n\nnamespace Waterhole\\Actions;\n\nuse Illuminate\\Support\\Collection;\nuse Waterhole\\Models\\Model;\nuse Waterhole\\Models\\Post;\nuse Waterhole\\Models\\User;\n\nclass Unpin extends Action\n{\n    public function appliesTo(Model $model): bool\n    {\n        return $model instanceof Post && $model->is_pinned;\n    }\n\n    public function authorize(?User $user, Model $model): bool\n    {\n        return $user && $user->can('waterhole.post.moderate', $model);\n    }\n\n    public function label(Collection $models): string\n    {\n        return __('waterhole::forum.unpin-button');\n    }\n\n    public function icon(Collection $models): string\n    {\n        return 'tabler-pinned-off';\n    }\n\n    public function run(Collection $models): void\n    {\n        $models->each->update(['is_pinned' => false]);\n    }\n\n    public function stream(Model $model): array\n    {\n        return [];\n    }\n}\n"
  },
  {
    "path": "src/Api/Collections/StructureContentCollection.php",
    "content": "<?php\n\nnamespace Waterhole\\Api\\Collections;\n\nuse Tobyz\\JsonApiServer\\Context;\nuse Tobyz\\JsonApiServer\\Resource\\Collection;\nuse Waterhole\\Models\\Channel;\nuse Waterhole\\Models\\Page;\nuse Waterhole\\Models\\StructureHeading;\nuse Waterhole\\Models\\StructureLink;\n\nclass StructureContentCollection implements Collection\n{\n    public function name(): string\n    {\n        return 'structureContent';\n    }\n\n    public function resources(): array\n    {\n        return ['channels', 'pages', 'structureHeadings', 'structureLinks'];\n    }\n\n    public function resource(object $model, Context $context): ?string\n    {\n        return match (true) {\n            $model instanceof Channel => 'channels',\n            $model instanceof Page => 'pages',\n            $model instanceof StructureHeading => 'structureHeadings',\n            $model instanceof StructureLink => 'structureLinks',\n            default => null,\n        };\n    }\n\n    public function endpoints(): array\n    {\n        return [];\n    }\n}\n"
  },
  {
    "path": "src/Api/Resources/ChannelUsersResource.php",
    "content": "<?php\n\nnamespace Waterhole\\Api\\Resources;\n\nuse Tobyz\\JsonApiServer\\Context;\nuse Waterhole\\Extend\\Api\\ChannelUsersResource as ChannelUsersResourceExtender;\nuse Waterhole\\Models\\ChannelUser;\n\nclass ChannelUsersResource extends ExtendableResource\n{\n    public function __construct(ChannelUsersResourceExtender $extender)\n    {\n        $this->extender = $extender;\n    }\n\n    public function type(): string\n    {\n        return 'channelUsers';\n    }\n\n    public function newModel(Context $context): object\n    {\n        return new ChannelUser();\n    }\n}\n"
  },
  {
    "path": "src/Api/Resources/ChannelsResource.php",
    "content": "<?php\n\nnamespace Waterhole\\Api\\Resources;\n\nuse Tobyz\\JsonApiServer\\Context;\nuse Waterhole\\Extend\\Api\\ChannelsResource as ChannelsResourceExtender;\nuse Waterhole\\Models\\Channel;\n\nclass ChannelsResource extends ExtendableResource\n{\n    public function __construct(ChannelsResourceExtender $extender)\n    {\n        $this->extender = $extender;\n    }\n\n    public function type(): string\n    {\n        return 'channels';\n    }\n\n    public function newModel(Context $context): object\n    {\n        return new Channel();\n    }\n}\n"
  },
  {
    "path": "src/Api/Resources/CommentsResource.php",
    "content": "<?php\n\nnamespace Waterhole\\Api\\Resources;\n\nuse Tobyz\\JsonApiServer\\Context;\nuse Waterhole\\Extend\\Api\\CommentsResource as CommentsResourceExtender;\nuse Waterhole\\Models\\Comment;\n\nclass CommentsResource extends ExtendableResource\n{\n    public function __construct(CommentsResourceExtender $extender)\n    {\n        $this->extender = $extender;\n    }\n\n    public function type(): string\n    {\n        return 'comments';\n    }\n\n    public function newModel(Context $context): object\n    {\n        return new Comment();\n    }\n}\n"
  },
  {
    "path": "src/Api/Resources/ExtendableResource.php",
    "content": "<?php\n\nnamespace Waterhole\\Api\\Resources;\n\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Tobyz\\JsonApiServer\\Context;\nuse Tobyz\\JsonApiServer\\Laravel\\EloquentResource;\nuse Waterhole\\Extend\\Support\\Resource;\n\nabstract class ExtendableResource extends EloquentResource\n{\n    protected Resource $extender;\n\n    public function scope(Builder $query, Context $context): void\n    {\n        foreach ($this->extender->scope->values() as $callback) {\n            $callback($query, $context);\n        }\n    }\n\n    public function endpoints(): array\n    {\n        return $this->extender->endpoints->values();\n    }\n\n    public function fields(): array\n    {\n        return $this->extender->fields->values();\n    }\n\n    public function sorts(): array\n    {\n        return $this->extender->sorts->values();\n    }\n\n    public function filters(): array\n    {\n        return $this->extender->filters->values();\n    }\n}\n"
  },
  {
    "path": "src/Api/Resources/GroupsResource.php",
    "content": "<?php\n\nnamespace Waterhole\\Api\\Resources;\n\nuse Tobyz\\JsonApiServer\\Context;\nuse Waterhole\\Extend\\Api\\GroupsResource as GroupsResourceExtender;\nuse Waterhole\\Models\\Group;\n\nclass GroupsResource extends ExtendableResource\n{\n    public function __construct(GroupsResourceExtender $extender)\n    {\n        $this->extender = $extender;\n    }\n\n    public function type(): string\n    {\n        return 'groups';\n    }\n\n    public function newModel(Context $context): object\n    {\n        return new Group();\n    }\n}\n"
  },
  {
    "path": "src/Api/Resources/PagesResource.php",
    "content": "<?php\n\nnamespace Waterhole\\Api\\Resources;\n\nuse Tobyz\\JsonApiServer\\Context;\nuse Waterhole\\Extend\\Api\\PagesResource as PagesResourceExtender;\nuse Waterhole\\Models\\Page;\n\nclass PagesResource extends ExtendableResource\n{\n    public function __construct(PagesResourceExtender $extender)\n    {\n        $this->extender = $extender;\n    }\n\n    public function type(): string\n    {\n        return 'pages';\n    }\n\n    public function newModel(Context $context): object\n    {\n        return new Page();\n    }\n}\n"
  },
  {
    "path": "src/Api/Resources/PostUsersResource.php",
    "content": "<?php\n\nnamespace Waterhole\\Api\\Resources;\n\nuse Tobyz\\JsonApiServer\\Context;\nuse Waterhole\\Extend\\Api\\PostUsersResource as PostUsersResourceExtender;\nuse Waterhole\\Models\\PostUser;\n\nclass PostUsersResource extends ExtendableResource\n{\n    public function __construct(PostUsersResourceExtender $extender)\n    {\n        $this->extender = $extender;\n    }\n\n    public function type(): string\n    {\n        return 'postUsers';\n    }\n\n    public function newModel(Context $context): object\n    {\n        return new PostUser();\n    }\n}\n"
  },
  {
    "path": "src/Api/Resources/PostsResource.php",
    "content": "<?php\n\nnamespace Waterhole\\Api\\Resources;\n\nuse Tobyz\\JsonApiServer\\Context;\nuse Tobyz\\JsonApiServer\\Laravel\\SoftDeletes;\nuse Waterhole\\Extend\\Api\\PostsResource as PostsResourceExtender;\nuse Waterhole\\Models\\Post;\n\nclass PostsResource extends ExtendableResource\n{\n    use SoftDeletes;\n\n    public function __construct(PostsResourceExtender $extender)\n    {\n        $this->extender = $extender;\n    }\n\n    public function type(): string\n    {\n        return 'posts';\n    }\n\n    public function newModel(Context $context): object\n    {\n        return new Post();\n    }\n\n    public function count(object $query, Context $context): ?int\n    {\n        return null;\n    }\n}\n"
  },
  {
    "path": "src/Api/Resources/ReactionCountsResource.php",
    "content": "<?php\n\nnamespace Waterhole\\Api\\Resources;\n\nuse Illuminate\\Support\\Facades\\Auth;\nuse Tobyz\\JsonApiServer\\Context;\nuse Tobyz\\JsonApiServer\\Resource\\AbstractResource;\nuse Tobyz\\JsonApiServer\\Schema\\Field\\Attribute;\nuse Tobyz\\JsonApiServer\\Schema\\Field\\ToOne;\nuse Tobyz\\JsonApiServer\\Schema\\Type;\nuse Waterhole\\Models\\ReactionType;\n\nclass ReactionCountsResource extends AbstractResource\n{\n    public function type(): string\n    {\n        return 'reactionCounts';\n    }\n\n    public function getId(object $model, Context $context): string\n    {\n        return implode('-', [$model->content_type, $model->content_id, $model->id]);\n    }\n\n    public function fields(): array\n    {\n        return [\n            Attribute::make('count')->type(Type\\Integer::make()),\n\n            Attribute::make('userReacted')\n                ->type(Type\\Boolean::make())\n                ->visible(fn() => Auth::check()),\n\n            ToOne::make('reactionType')\n                ->get(fn(ReactionType $reactionType) => $reactionType)\n                ->includable(),\n        ];\n    }\n}\n"
  },
  {
    "path": "src/Api/Resources/ReactionSetsResource.php",
    "content": "<?php\n\nnamespace Waterhole\\Api\\Resources;\n\nuse Tobyz\\JsonApiServer\\Context;\nuse Waterhole\\Extend\\Api\\ReactionSetsResource as ReactionSetsResourceExtender;\nuse Waterhole\\Models\\ReactionSet;\n\nclass ReactionSetsResource extends ExtendableResource\n{\n    public function __construct(ReactionSetsResourceExtender $extender)\n    {\n        $this->extender = $extender;\n    }\n\n    public function type(): string\n    {\n        return 'reactionSets';\n    }\n\n    public function newModel(Context $context): object\n    {\n        return new ReactionSet();\n    }\n}\n"
  },
  {
    "path": "src/Api/Resources/ReactionTypesResource.php",
    "content": "<?php\n\nnamespace Waterhole\\Api\\Resources;\n\nuse Tobyz\\JsonApiServer\\Context;\nuse Waterhole\\Extend\\Api\\ReactionTypesResource as ReactionTypesResourceExtender;\nuse Waterhole\\Models\\ReactionType;\n\nclass ReactionTypesResource extends ExtendableResource\n{\n    public function __construct(ReactionTypesResourceExtender $extender)\n    {\n        $this->extender = $extender;\n    }\n\n    public function type(): string\n    {\n        return 'reactionTypes';\n    }\n\n    public function newModel(Context $context): object\n    {\n        return new ReactionType();\n    }\n}\n"
  },
  {
    "path": "src/Api/Resources/ReactionsResource.php",
    "content": "<?php\n\nnamespace Waterhole\\Api\\Resources;\n\nuse Tobyz\\JsonApiServer\\Context;\nuse Waterhole\\Extend\\Api\\ReactionsResource as ReactionsResourceExtender;\nuse Waterhole\\Models\\Reaction;\n\nclass ReactionsResource extends ExtendableResource\n{\n    public function __construct(ReactionsResourceExtender $extender)\n    {\n        $this->extender = $extender;\n    }\n\n    public function type(): string\n    {\n        return 'reactions';\n    }\n\n    public function newModel(Context $context): object\n    {\n        return new Reaction();\n    }\n}\n"
  },
  {
    "path": "src/Api/Resources/StructureHeadingsResource.php",
    "content": "<?php\n\nnamespace Waterhole\\Api\\Resources;\n\nuse Tobyz\\JsonApiServer\\Context;\nuse Waterhole\\Extend\\Api\\StructureHeadingsResource as StructureHeadingsResourceExtender;\nuse Waterhole\\Models\\StructureHeading;\n\nclass StructureHeadingsResource extends ExtendableResource\n{\n    public function __construct(StructureHeadingsResourceExtender $extender)\n    {\n        $this->extender = $extender;\n    }\n\n    public function type(): string\n    {\n        return 'structureHeadings';\n    }\n\n    public function newModel(Context $context): object\n    {\n        return new StructureHeading();\n    }\n}\n"
  },
  {
    "path": "src/Api/Resources/StructureLinksResource.php",
    "content": "<?php\n\nnamespace Waterhole\\Api\\Resources;\n\nuse Tobyz\\JsonApiServer\\Context;\nuse Waterhole\\Extend\\Api\\StructureLinksResource as StructureLinksResourceExtender;\nuse Waterhole\\Models\\StructureLink;\n\nclass StructureLinksResource extends ExtendableResource\n{\n    public function __construct(StructureLinksResourceExtender $extender)\n    {\n        $this->extender = $extender;\n    }\n\n    public function type(): string\n    {\n        return 'structureLinks';\n    }\n\n    public function newModel(Context $context): object\n    {\n        return new StructureLink();\n    }\n}\n"
  },
  {
    "path": "src/Api/Resources/StructureResource.php",
    "content": "<?php\n\nnamespace Waterhole\\Api\\Resources;\n\nuse Tobyz\\JsonApiServer\\Context;\nuse Waterhole\\Extend\\Api\\StructureResource as StructureResourceExtender;\nuse Waterhole\\Models\\Structure;\n\nclass StructureResource extends ExtendableResource\n{\n    public function __construct(StructureResourceExtender $extender)\n    {\n        $this->extender = $extender;\n    }\n\n    public function type(): string\n    {\n        return 'structure';\n    }\n\n    public function newModel(Context $context): object\n    {\n        return new Structure();\n    }\n}\n"
  },
  {
    "path": "src/Api/Resources/TagsResource.php",
    "content": "<?php\n\nnamespace Waterhole\\Api\\Resources;\n\nuse Tobyz\\JsonApiServer\\Context;\nuse Waterhole\\Extend\\Api\\TagsResource as TagsResourceExtender;\nuse Waterhole\\Models\\Tag;\n\nclass TagsResource extends ExtendableResource\n{\n    public function __construct(TagsResourceExtender $extender)\n    {\n        $this->extender = $extender;\n    }\n\n    public function type(): string\n    {\n        return 'tags';\n    }\n\n    public function newModel(Context $context): object\n    {\n        return new Tag();\n    }\n}\n"
  },
  {
    "path": "src/Api/Resources/TaxonomiesResource.php",
    "content": "<?php\n\nnamespace Waterhole\\Api\\Resources;\n\nuse Tobyz\\JsonApiServer\\Context;\nuse Waterhole\\Extend\\Api\\TaxonomiesResource as TaxonomiesResourceExtender;\nuse Waterhole\\Models\\Taxonomy;\n\nclass TaxonomiesResource extends ExtendableResource\n{\n    public function __construct(TaxonomiesResourceExtender $extender)\n    {\n        $this->extender = $extender;\n    }\n\n    public function type(): string\n    {\n        return 'taxonomies';\n    }\n\n    public function newModel(Context $context): object\n    {\n        return new Taxonomy();\n    }\n}\n"
  },
  {
    "path": "src/Api/Resources/UsersResource.php",
    "content": "<?php\n\nnamespace Waterhole\\Api\\Resources;\n\nuse Tobyz\\JsonApiServer\\Context;\nuse Waterhole\\Extend\\Api\\UsersResource as UsersResourceExtender;\nuse Waterhole\\Models\\User;\n\nclass UsersResource extends ExtendableResource\n{\n    public function __construct(UsersResourceExtender $extender)\n    {\n        $this->extender = $extender;\n    }\n\n    public function type(): string\n    {\n        return 'users';\n    }\n\n    public function newModel(Context $context): object\n    {\n        return new User();\n    }\n}\n"
  },
  {
    "path": "src/Auth/AuthenticatesWaterhole.php",
    "content": "<?php\n\nnamespace Waterhole\\Auth;\n\ninterface AuthenticatesWaterhole\n{\n    /**\n     * Create a Waterhole SSO payload representing this user.\n     */\n    public function toWaterholePayload(): ?SsoPayload;\n}\n"
  },
  {
    "path": "src/Auth/HasWaterholeUser.php",
    "content": "<?php\n\nnamespace Waterhole\\Auth;\n\nuse Illuminate\\Database\\Eloquent\\Relations\\HasOneThrough;\nuse Waterhole\\Models\\AuthProvider;\nuse Waterhole\\Models\\User;\nuse Waterhole\\Sso\\PendingUser;\n\ntrait HasWaterholeUser\n{\n    public static function bootHasWaterholeUser(): void\n    {\n        static::saved(function ($model) {\n            if ($model->wasChanged('email', 'email_verified_at')) {\n                $model->waterholeUser()->update([\n                    'email' => $model->email,\n                    'email_verified_at' => $model->email_verified_at,\n                ]);\n            }\n        });\n    }\n\n    public function waterholeUser(): HasOneThrough\n    {\n        return $this->hasOneThrough(\n            User::class,\n            AuthProvider::class,\n            firstKey: 'identifier',\n            secondKey: 'id',\n            localKey: $this->getAuthIdentifierName(),\n            secondLocalKey: 'user_id',\n        )->where('provider', $this->getWaterholeProviderName());\n    }\n\n    public function toWaterholeUser(): ?PendingUser\n    {\n        return new PendingUser(\n            identifier: $this->getAuthIdentifier(),\n            email: $this->email,\n            name: $this->name,\n        );\n    }\n\n    public function toWaterholePayload(): ?SsoPayload\n    {\n        if ($user = $this->toWaterholeUser()) {\n            return new SsoPayload($this->getWaterholeProviderName(), $user);\n        }\n\n        return null;\n    }\n\n    protected function getWaterholeProviderName(): string\n    {\n        return 'laravel';\n    }\n}\n"
  },
  {
    "path": "src/Auth/Providers.php",
    "content": "<?php\n\nnamespace Waterhole\\Auth;\n\nuse Illuminate\\Support\\Collection;\n\nclass Providers\n{\n    private const DEFAULTS = [\n        'facebook' => [\n            'icon' => 'tabler-brand-facebook',\n            'name' => 'Facebook',\n        ],\n        'twitter' => [\n            'icon' => 'tabler-brand-twitter',\n            'name' => 'Twitter',\n        ],\n        'twitter-oauth-2' => [\n            'icon' => 'tabler-brand-twitter',\n            'name' => 'Twitter',\n        ],\n        'linkedin' => [\n            'icon' => 'tabler-brand-linkedin',\n            'name' => 'LinkedIn',\n        ],\n        'google' => [\n            'icon' => 'waterhole-google',\n            'name' => 'Google',\n        ],\n        'github' => [\n            'icon' => 'tabler-brand-github',\n            'name' => 'GitHub',\n        ],\n        'gitlab' => [\n            'icon' => 'tabler-brand-gitlab',\n            'name' => 'GitLab',\n        ],\n        'bitbucket' => [\n            'icon' => 'tabler-brand-bitbucket',\n            'name' => 'Bitbucket',\n        ],\n    ];\n\n    private Collection $providers;\n\n    public function __construct(protected array $config)\n    {\n        $this->providers = collect($config)->mapWithKeys(\n            fn($value, $key) => is_numeric($key)\n                ? [$value => static::DEFAULTS[$value] ?? ['icon' => null, 'name' => $value]]\n                : [$key => $value],\n        );\n    }\n\n    public function all(): array\n    {\n        return $this->providers->all();\n    }\n\n    public function has(string $provider): bool\n    {\n        return $this->providers->has($provider);\n    }\n\n    public function sole(): ?string\n    {\n        return $this->providers->count() === 1 ? $this->providers->keys()->first() : null;\n    }\n}\n"
  },
  {
    "path": "src/Auth/SsoPayload.php",
    "content": "<?php\n\nnamespace Waterhole\\Auth;\n\nuse Illuminate\\Contracts\\Encryption\\DecryptException;\nuse Waterhole\\Sso\\PendingUser;\n\nclass SsoPayload\n{\n    private int $expiry;\n\n    public function __construct(public string $provider, public PendingUser $user)\n    {\n        $this->expiry = time() + 10 * 60;\n    }\n\n    public static function decrypt(string $value): static\n    {\n        try {\n            /** @var static $payload */\n            $payload = decrypt($value);\n        } catch (DecryptException) {\n            abort(400, 'Invalid payload');\n        }\n\n        if (time() > $payload->expiry) {\n            abort(400, 'Payload expired');\n        }\n\n        return $payload;\n    }\n\n    public function encrypt(): string\n    {\n        return encrypt($this);\n    }\n\n    public function __toString(): string\n    {\n        return $this->encrypt();\n    }\n}\n"
  },
  {
    "path": "src/Auth/SsoProvider.php",
    "content": "<?php\n\nnamespace Waterhole\\Auth;\n\nuse Illuminate\\Http\\RedirectResponse;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Support\\Str;\nuse Laravel\\Socialite\\Contracts\\Provider;\nuse Waterhole\\Sso\\Payload;\nuse Waterhole\\Sso\\WaterholeSso;\n\nfinal class SsoProvider implements Provider\n{\n    private ?SsoUser $user = null;\n\n    public function __construct(\n        private readonly Request $request,\n        private readonly string $url,\n        private readonly WaterholeSso $sso,\n    ) {}\n\n    public function redirect(): RedirectResponse\n    {\n        $this->storeNonce($nonce = $this->generateNonce());\n\n        $query = $this->sso->buildQuery($nonce, [\n            Payload::RETURN_URL => route('waterhole.sso.callback', ['provider' => 'sso']),\n        ]);\n\n        return redirect($this->url . (str_contains($this->url, '?') ? '&' : '?') . $query);\n    }\n\n    public function user(): SsoUser\n    {\n        if ($this->user) {\n            return $this->user;\n        }\n\n        $payload = $this->request->query('payload');\n        $sig = $this->request->query('sig');\n\n        if (!$this->sso->validate($payload, $sig)) {\n            abort(400, 'Invalid signature');\n        }\n\n        $payload = $this->sso->parse($payload);\n\n        if (!$this->validateNonce($payload->getNonce())) {\n            abort(400, 'Invalid nonce');\n        }\n\n        $this->expireNonce();\n\n        if (!$this->validatePayload($payload)) {\n            abort(400, 'Invalid payload');\n        }\n\n        return $this->user = new SsoUser($payload);\n    }\n\n    private function generateNonce(): string\n    {\n        return Str::random();\n    }\n\n    private function storeNonce(string $nonce): void\n    {\n        $this->request->session()->put('sso_nonce', [\n            'nonce' => $nonce,\n            'expiry' => time() + 10 * 60,\n        ]);\n    }\n\n    private function validateNonce(string $nonce): bool\n    {\n        $data = $this->request->session()->get('sso_nonce');\n\n        $storedNonce = $data['nonce'] ?? null;\n        $expiry = $data['expiry'] ?? null;\n\n        return $storedNonce === $nonce && time() < $expiry;\n    }\n\n    private function expireNonce(): void\n    {\n        $this->request->session()->forget('sso_nonce');\n    }\n\n    private function validatePayload(Payload $payload): bool\n    {\n        return $payload->get('identifier') && $payload->get('email') && $payload->get('name');\n    }\n}\n"
  },
  {
    "path": "src/Auth/SsoUser.php",
    "content": "<?php\n\nnamespace Waterhole\\Auth;\n\nuse Laravel\\Socialite\\Contracts\\User;\nuse Waterhole\\Sso\\Payload;\n\nclass SsoUser implements User\n{\n    public function __construct(private readonly Payload $payload) {}\n\n    public function getId()\n    {\n        return $this->payload->get('identifier');\n    }\n\n    public function getNickname()\n    {\n        return $this->payload->get('name');\n    }\n\n    public function getName()\n    {\n        return $this->payload->get('username');\n    }\n\n    public function getEmail()\n    {\n        return $this->payload->get('email');\n    }\n\n    public function getAvatar()\n    {\n        return $this->payload->get('avatar');\n    }\n\n    public function getGroups(): array\n    {\n        return (array) $this->payload->get('groups', []);\n    }\n}\n"
  },
  {
    "path": "src/Console/CacheClearCommand.php",
    "content": "<?php\n\nnamespace Waterhole\\Console;\n\nuse Illuminate\\Console\\Command;\nuse Waterhole\\Extend\\Assets\\Script;\nuse Waterhole\\Extend\\Assets\\Stylesheet;\nuse Waterhole\\Translation\\FluentTranslator;\n\nclass CacheClearCommand extends Command\n{\n    protected $signature = 'waterhole:cache:clear';\n\n    protected $description = 'Clear Waterhole caches';\n\n    public function handle()\n    {\n        app('waterhole.formatter')->flush();\n        app('waterhole.formatter.emoji')->flush();\n\n        app(FluentTranslator::class)->flush();\n\n        app(Script::class)->flush();\n        app(Stylesheet::class)->flush();\n\n        $this->info('Waterhole caches cleared!');\n    }\n}\n"
  },
  {
    "path": "src/Console/Concerns/ValidatesInput.php",
    "content": "<?php\n\nnamespace Waterhole\\Console\\Concerns;\n\nuse Illuminate\\Support\\Facades\\Validator;\n\ntrait ValidatesInput\n{\n    private function askValid(string $question, string $field, array $rules, bool $secret = false)\n    {\n        $value = $secret ? $this->secret($question) : $this->ask($question);\n\n        if ($message = $this->validateInput($rules, $field, $value)) {\n            $this->error($message);\n\n            return $this->askValid($question, $field, $rules, $secret);\n        }\n\n        return $value;\n    }\n\n    private function validateInput(array $rules, string $field, ?string $value): ?string\n    {\n        $validator = Validator::make([$field => $value], [$field => $rules]);\n\n        return $validator->fails() ? $validator->errors()->first($field) : null;\n    }\n}\n"
  },
  {
    "path": "src/Console/InstallCommand.php",
    "content": "<?php\n\nnamespace Waterhole\\Console;\n\nuse Exception;\nuse Illuminate\\Console\\Command;\nuse Illuminate\\Support\\Facades\\Hash;\nuse Illuminate\\Validation\\Rules\\Password;\nuse Waterhole\\Console\\Concerns\\ValidatesInput;\nuse Waterhole\\Database\\Seeders\\DefaultSeeder;\nuse Waterhole\\Models\\Group;\nuse Waterhole\\Models\\User;\n\nclass InstallCommand extends Command\n{\n    use ValidatesInput;\n\n    protected $signature = 'waterhole:install';\n\n    protected $description = 'Install Waterhole';\n\n    public function handle()\n    {\n        try {\n            if (User::find(1)) {\n                $this->error('Waterhole has already been installed.');\n                return;\n            }\n        } catch (Exception) {\n            //\n        }\n\n        $this->publish();\n        $this->migrate();\n        $this->seed();\n        $this->createAdmin();\n\n        $this->info('Waterhole successfully installed.');\n        $this->info('Check out your forum at: ' . route('waterhole.home'));\n    }\n\n    private function publish(): void\n    {\n        $this->call('vendor:publish', ['--tag' => 'waterhole-config']);\n    }\n\n    private function migrate(): void\n    {\n        $this->call('migrate');\n    }\n\n    private function seed(): void\n    {\n        $this->call('db:seed', [\n            '--class' => DefaultSeeder::class,\n            '--force' => true,\n        ]);\n    }\n\n    private function createAdmin(): void\n    {\n        $data = [\n            'name' => $this->askValid('Admin username', 'name', ['required', 'string', 'max:255']),\n            'email' => $this->askValid('Admin email', 'email', [\n                'required',\n                'string',\n                'email',\n                'max:255',\n            ]),\n            'password' => Hash::make(\n                $this->askValid(\n                    'Admin password',\n                    'password',\n                    ['required', Password::defaults()],\n                    secret: true,\n                ),\n            ),\n            'email_verified_at' => now(),\n        ];\n\n        User::create($data)->groups()->attach(Group::ADMIN_ID);\n\n        $this->info('Admin user created.');\n    }\n}\n"
  },
  {
    "path": "src/Console/MakeExtensionCommand.php",
    "content": "<?php\n\nnamespace Waterhole\\Console;\n\nuse Illuminate\\Console\\Command;\nuse Illuminate\\Filesystem\\Filesystem;\nuse Illuminate\\Support\\Str;\nuse Symfony\\Component\\Process\\PhpExecutableFinder;\nuse Waterhole\\Console\\Concerns\\ValidatesInput;\nuse Waterhole\\Waterhole;\n\nclass MakeExtensionCommand extends Command\n{\n    use ValidatesInput;\n\n    protected $signature = 'waterhole:make:extension {name : The name of the extension package}\n        {--path=extensions : The location where the extension should be created}';\n\n    protected $description = 'Create a new Waterhole extension';\n\n    public function __construct(protected Filesystem $files)\n    {\n        parent::__construct();\n    }\n\n    public function handle()\n    {\n        $name = $this->input->getArgument('name');\n\n        if (\n            !preg_match('~^[a-z0-9]([_.-]?[a-z0-9]+)*/[a-z0-9](([_.]?|-{0,2})[a-z0-9]+)*$~', $name)\n        ) {\n            $this->error('The name must be a valid Composer package name.');\n\n            return false;\n        }\n\n        [$vendor, $project] = explode('/', $name);\n\n        $pathOption = $this->input->getOption('path');\n        $path = $this->laravel->basePath($pathOption . \"/$vendor-$project\");\n\n        if ($this->files->exists($path)) {\n            $this->error('Extension already exists.');\n\n            return false;\n        }\n\n        if (!$this->files->isDirectory(dirname($path))) {\n            $this->files->makeDirectory(dirname($path), recursive: true, force: true);\n        }\n\n        $this->files->copyDirectory(__DIR__ . '/stubs/extension', $path);\n\n        $replacements = [\n            '{{ namespace }}' => ($namespace = Str::studly($vendor) . '\\\\' . Str::studly($project)),\n            '{{ namespace_escaped }}' => addslashes($namespace),\n            '{{ prefix }}' => ($prefix = Str::studly($project)),\n            '{{ name }}' => $name,\n            '{{ waterhole_version }}' => Waterhole::VERSION,\n        ];\n\n        foreach ($this->files->allFiles($path) as $file) {\n            $this->files->replaceInFile(\n                array_keys($replacements),\n                array_values($replacements),\n                $file,\n            );\n        }\n\n        $this->files->move(\n            \"$path/src/ServiceProvider.stub\",\n            \"$path/src/{$prefix}ServiceProvider.php\",\n        );\n\n        $this->info(sprintf('Extension [%s] created successfully.', $path));\n\n        $repositoryPath = $this->composerRepositoryPath($pathOption);\n\n        if ($this->addComposerRepository($repositoryPath)) {\n            $this->installExtension($name);\n        } else {\n            $this->warn(\n                sprintf(\n                    'Skipping install. Add a path repository and run composer require %s:dev-main.',\n                    $name,\n                ),\n            );\n        }\n    }\n\n    private function composerRepositoryPath(string $pathOption): string\n    {\n        $pathOption = rtrim($pathOption, '/');\n\n        if ($pathOption === '') {\n            $pathOption = 'extensions';\n        }\n\n        return Str::endsWith($pathOption, '/*') ? $pathOption : $pathOption . '/*';\n    }\n\n    private function addComposerRepository(string $repositoryPath): bool\n    {\n        $file = $this->laravel->basePath('composer.json');\n\n        $decoded = json_decode($this->files->get($file), true);\n\n        $repository = [\n            'type' => 'path',\n            'url' => $repositoryPath,\n        ];\n\n        if (in_array($repository, $decoded['repositories'] ?? [])) {\n            return true;\n        }\n\n        if (!$this->confirm(sprintf('Add a Composer path repository for [%s]?', $repositoryPath))) {\n            return false;\n        }\n\n        $decoded['repositories'][] = $repository;\n\n        $this->files->put($file, json_encode($decoded, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));\n\n        $this->info('Path repository added to composer.json.');\n\n        return true;\n    }\n\n    private function installExtension(string $name): void\n    {\n        $this->info('Installing your extension...');\n\n        $args = [\n            (new PhpExecutableFinder())->find(),\n            $this->laravel->basePath('vendor/bin/composer'),\n            'require',\n            \"$name:dev-main\",\n        ];\n\n        system(implode(' ', $args));\n    }\n}\n"
  },
  {
    "path": "src/Console/OpenApiCommand.php",
    "content": "<?php\n\nnamespace Waterhole\\Console;\n\nuse Illuminate\\Console\\Command;\nuse Illuminate\\Support\\Facades\\File;\nuse Tobyz\\JsonApiServer\\OpenApi\\OpenApiGenerator;\nuse Waterhole\\Extend\\Api\\JsonApi;\n\nclass OpenApiCommand extends Command\n{\n    protected $signature = 'waterhole:openapi';\n\n    protected $description = 'Generate an OpenAPI description of the Waterhole API';\n\n    public function handle(JsonApi $api): void\n    {\n        $generator = new OpenApiGenerator();\n\n        $spec = $generator->generate($api);\n\n        $spec['info'] = ['title' => config('waterhole.forum.name') . ' API'];\n        $spec['components']['securitySchemes']['BearerAuth'] = [\n            'type' => 'http',\n            'scheme' => 'bearer',\n        ];\n        $spec['security'][] = ['BearerAuth' => []];\n\n        File::put(base_path('waterhole-openapi.json'), json_encode($spec, JSON_PRETTY_PRINT));\n    }\n}\n"
  },
  {
    "path": "src/Console/ReformatCommand.php",
    "content": "<?php\n\nnamespace Waterhole\\Console;\n\nuse Exception;\nuse Illuminate\\Console\\Command;\n\nclass ReformatCommand extends Command\n{\n    protected $signature = 'waterhole:reformat';\n\n    protected $description = 'Reparse formatted content';\n\n    private static array $modelAttributes = [];\n\n    public function handle()\n    {\n        $this->info('The following content will be reformatted:');\n\n        $this->table(\n            ['Model', 'Attributes'],\n            collect(static::$modelAttributes)->map(\n                fn($attributes, $model) => [$model, implode(', ', $attributes)],\n            ),\n        );\n\n        if (!$this->confirm('Proceed?')) {\n            return;\n        }\n\n        foreach (static::$modelAttributes as $modelClass => $attributes) {\n            $this->info(\"Reformatting $modelClass...\");\n\n            $query = $modelClass::query();\n            $count = $query->count();\n\n            $bar = $this->output->createProgressBar($count);\n            $bar->start();\n\n            $query->chunk(1000, function ($models) use ($bar, $modelClass, $attributes) {\n                foreach ($models as $model) {\n                    $modelString = $modelClass . '#' . $model->getKey();\n\n                    try {\n                        foreach ($attributes as $attribute) {\n                            try {\n                                $model->setAttribute($attribute, $model->getAttribute($attribute));\n                            } catch (Exception $e) {\n                                $this->newLine();\n                                $this->warn(\n                                    \"Error parsing $modelString $attribute: \" . $e->getMessage(),\n                                );\n                            }\n                        }\n\n                        $model->saveQuietly();\n                    } catch (Exception $e) {\n                        $this->newLine();\n                        $this->warn(\"Error saving $modelString: \" . $e->getMessage());\n                    }\n\n                    $bar->advance();\n                }\n            });\n\n            $bar->finish();\n            $this->newLine(2);\n        }\n\n        $this->info('Content reformatted!');\n    }\n\n    public static function addModelAttribute(string $model, string $attribute): void\n    {\n        static::$modelAttributes[$model][] = $attribute;\n    }\n}\n"
  },
  {
    "path": "src/Console/stubs/extension/.editorconfig",
    "content": "root = true\n\n[*]\ncharset = utf-8\nend_of_line = lf\ninsert_final_newline = true\nindent_style = space\nindent_size = 4\ntrim_trailing_whitespace = true\n\n[*.md]\ntrim_trailing_whitespace = false\n\n[*.yml]\nindent_size = 2\n\n[{*.less, *.css}]\nindent_size = 2\n"
  },
  {
    "path": "src/Console/stubs/extension/.gitattributes",
    "content": "* text=auto\nCHANGELOG.md export-ignore\n"
  },
  {
    "path": "src/Console/stubs/extension/composer.json",
    "content": "{\n    \"name\": \"{{ name }}\",\n    \"type\": \"waterhole-extension\",\n    \"description\": \"\",\n    \"license\": \"MIT\",\n    \"require\": {\n        \"waterhole/core\": \"^{{ waterhole_version }}\"\n    },\n    \"autoload\": {\n        \"psr-4\": {\n            \"{{ namespace_escaped }}\\\\\": \"src/\"\n        }\n    },\n    \"config\": {\n        \"preferred-install\": \"dist\",\n        \"sort-packages\": true\n    },\n    \"extra\": {\n        \"laravel\": {\n            \"providers\": [\n                \"{{ namespace_escaped }}\\\\{{ prefix }}ServiceProvider\"\n            ]\n        },\n        \"waterhole\": {\n            \"name\": \"{{ prefix }}\"\n        }\n    }\n}\n"
  },
  {
    "path": "src/Console/stubs/extension/src/ServiceProvider.stub",
    "content": "<?php\n\nnamespace {{ namespace }};\n\nuse Waterhole\\Extend;\n\nclass {{ prefix }}ServiceProvider extends Extend\\ServiceProvider\n{\n    public function register()\n    {\n        //\n    }\n}\n"
  },
  {
    "path": "src/Database/Migration.php",
    "content": "<?php\n\nnamespace Waterhole\\Database;\n\nuse Illuminate\\Database\\Migrations\\Migration as BaseMigration;\n\nclass Migration extends BaseMigration\n{\n    public function getConnection()\n    {\n        return config('waterhole.system.database');\n    }\n}\n"
  },
  {
    "path": "src/Events/FlagReceived.php",
    "content": "<?php\n\nnamespace Waterhole\\Events;\n\nuse Illuminate\\Broadcasting\\PrivateChannel;\nuse Illuminate\\Contracts\\Broadcasting\\ShouldBroadcast;\nuse Illuminate\\Contracts\\Events\\ShouldDispatchAfterCommit;\nuse Waterhole\\Models\\User;\nuse Waterhole\\View\\Components\\ModerationBadge;\nuse Waterhole\\View\\TurboStream;\n\nclass FlagReceived implements ShouldBroadcast, ShouldDispatchAfterCommit\n{\n    public function __construct(protected User $user) {}\n\n    public function broadcastOn(): PrivateChannel\n    {\n        return new PrivateChannel($this->user->broadcastChannel());\n    }\n\n    public function broadcastWith(): array\n    {\n        return [\n            'streams' => TurboStream::replace(new ModerationBadge($this->user)),\n        ];\n    }\n}\n"
  },
  {
    "path": "src/Events/NewComment.php",
    "content": "<?php\n\nnamespace Waterhole\\Events;\n\nuse Illuminate\\Broadcasting\\Channel;\nuse Illuminate\\Broadcasting\\InteractsWithSockets;\nuse Illuminate\\Broadcasting\\PrivateChannel;\nuse Illuminate\\Contracts\\Broadcasting\\ShouldBroadcast;\nuse Illuminate\\Contracts\\Events\\ShouldDispatchAfterCommit;\nuse Illuminate\\Foundation\\Events\\Dispatchable;\nuse Waterhole\\Models\\Comment;\nuse Waterhole\\View\\Components\\CommentFrame;\nuse Waterhole\\View\\TurboStream;\n\nclass NewComment implements ShouldBroadcast, ShouldDispatchAfterCommit\n{\n    use Dispatchable;\n    use InteractsWithSockets;\n\n    public function __construct(protected Comment $comment) {}\n\n    public function broadcastOn()\n    {\n        $class = $this->comment->post->channel->isPublic() ? Channel::class : PrivateChannel::class;\n\n        return [\n            new $class($this->comment->post->broadcastChannel()),\n            new $class($this->comment->post->channel->broadcastChannel()),\n        ];\n    }\n\n    public function broadcastWith(): array\n    {\n        return [\n            'streams' => TurboStream::append(\n                (new CommentFrame($this->comment, lazy: true))->withAttributes([\n                    'class' => 'card__row',\n                ]),\n                '.comment-list',\n            ),\n        ];\n    }\n}\n"
  },
  {
    "path": "src/Events/NewPost.php",
    "content": "<?php\n\nnamespace Waterhole\\Events;\n\nuse Illuminate\\Broadcasting\\Channel;\nuse Illuminate\\Broadcasting\\InteractsWithSockets;\nuse Illuminate\\Broadcasting\\PrivateChannel;\nuse Illuminate\\Contracts\\Broadcasting\\ShouldBroadcast;\nuse Illuminate\\Contracts\\Events\\ShouldDispatchAfterCommit;\nuse Illuminate\\Foundation\\Events\\Dispatchable;\nuse Waterhole\\Models\\Channel as ChannelModel;\nuse Waterhole\\Models\\Post;\n\nclass NewPost implements ShouldBroadcast, ShouldDispatchAfterCommit\n{\n    use Dispatchable;\n    use InteractsWithSockets;\n\n    public function __construct(protected Post $post) {}\n\n    public function broadcastOn()\n    {\n        $class = in_array($this->post->channel->id, ChannelModel::allPermitted(null))\n            ? Channel::class\n            : PrivateChannel::class;\n\n        return new $class($this->post->channel->broadcastChannel());\n    }\n}\n"
  },
  {
    "path": "src/Events/NotificationReceived.php",
    "content": "<?php\n\nnamespace Waterhole\\Events;\n\nuse Illuminate\\Broadcasting\\PrivateChannel;\nuse Illuminate\\Contracts\\Broadcasting\\ShouldBroadcast;\nuse Illuminate\\Contracts\\Events\\ShouldDispatchAfterCommit;\nuse Waterhole\\Models\\Notification;\nuse Waterhole\\View\\Components\\NotificationAlert;\nuse Waterhole\\View\\Components\\NotificationsBadge;\nuse Waterhole\\View\\TurboStream;\n\nclass NotificationReceived implements ShouldBroadcast, ShouldDispatchAfterCommit\n{\n    public function __construct(protected Notification $notification) {}\n\n    public function broadcastOn()\n    {\n        return new PrivateChannel($this->notification->notifiable->broadcastChannel());\n    }\n\n    public function broadcastWith()\n    {\n        return [\n            'streams' => implode([\n                TurboStream::append(new NotificationAlert($this->notification), '#alerts'),\n                TurboStream::replace(new NotificationsBadge($this->notification->notifiable)),\n            ]),\n        ];\n    }\n}\n"
  },
  {
    "path": "src/Extend/Api/ChannelUsersResource.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Api;\n\nuse Tobyz\\JsonApiServer\\Schema\\Field\\Attribute;\nuse Tobyz\\JsonApiServer\\Schema\\Field\\ToOne;\nuse Tobyz\\JsonApiServer\\Schema\\Type;\nuse Waterhole\\Extend\\Support\\Resource;\n\n/**\n * Channel users JSON:API resource.\n *\n * Defines fields, endpoints, sorts, filters, and scope for channel users.\n */\nclass ChannelUsersResource extends Resource\n{\n    public function __construct()\n    {\n        parent::__construct();\n\n        $this->fields\n            ->add(ToOne::make('channel'), 'channel')\n\n            ->add(ToOne::make('user'), 'user')\n\n            ->add(\n                Attribute::make('notifications')\n                    ->type(Type\\Str::make()->enum(['normal', 'follow', 'ignore']))\n                    ->nullable(),\n                'notifications',\n            )\n\n            ->add(\n                Attribute::make('followedAt')->type(Type\\DateTime::make())->nullable(),\n                'followedAt',\n            );\n    }\n}\n"
  },
  {
    "path": "src/Extend/Api/ChannelsResource.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Api;\n\nuse Tobyz\\JsonApiServer\\Endpoint;\nuse Tobyz\\JsonApiServer\\Schema\\Field\\Attribute;\nuse Tobyz\\JsonApiServer\\Schema\\Field\\ToMany;\nuse Tobyz\\JsonApiServer\\Schema\\Field\\ToOne;\nuse Tobyz\\JsonApiServer\\Schema\\Type;\nuse Waterhole\\Extend\\Support\\Resource;\nuse Waterhole\\Models\\Channel;\nuse function Waterhole\\icon;\n\n/**\n * Channels JSON:API resource.\n *\n * Defines fields, endpoints, sorts, filters, and scope for channels.\n */\nclass ChannelsResource extends Resource\n{\n    public function __construct()\n    {\n        parent::__construct();\n\n        $this->endpoints->add(Endpoint\\Show::make(), 'show');\n\n        $this->fields\n            ->add(Attribute::make('name')->type(Type\\Str::make()), 'name')\n\n            ->add(\n                Attribute::make('iconHtml')\n                    ->type(Type\\Str::make()->format('html'))\n                    ->nullable()\n                    ->get(fn(Channel $channel) => icon($channel->icon)),\n                'iconHtml',\n            )\n\n            ->add(\n                Attribute::make('descriptionHtml')\n                    ->type(Type\\Str::make()->format('html'))\n                    ->nullable(),\n                'descriptionHtml',\n            )\n\n            ->add(\n                Attribute::make('instructionsHtml')\n                    ->type(Type\\Str::make()->format('html'))\n                    ->nullable(),\n                'instructionsHtml',\n            )\n\n            ->add(Attribute::make('url')->type(Type\\Str::make()->format('uri')), 'url')\n\n            ->add(\n                ToOne::make('postsReactionSet')->type('reactionSets')->nullable()->includable(),\n                'postsReactionSet',\n            )\n\n            ->add(\n                ToOne::make('commentsReactionSet')->type('reactionSets')->nullable()->includable(),\n                'commentsReactionSet',\n            )\n\n            ->add(ToMany::make('taxonomies')->includable(), 'taxonomies')\n\n            ->add(ToMany::make('posts'), 'posts')\n\n            ->add(ToOne::make('userState')->type('channelUsers')->includable(), 'userState');\n    }\n}\n"
  },
  {
    "path": "src/Extend/Api/CommentsResource.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Api;\n\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Tobyz\\JsonApiServer\\Endpoint;\nuse Tobyz\\JsonApiServer\\Laravel\\Filter\\WhereBelongsTo;\nuse Tobyz\\JsonApiServer\\Laravel\\Filter\\WhereNotNull;\nuse Tobyz\\JsonApiServer\\Laravel\\Sort\\SortColumn;\nuse Tobyz\\JsonApiServer\\Schema\\Field\\Attribute;\nuse Tobyz\\JsonApiServer\\Schema\\Field\\ToMany;\nuse Tobyz\\JsonApiServer\\Schema\\Field\\ToOne;\nuse Tobyz\\JsonApiServer\\Schema\\Type;\nuse Waterhole\\Extend\\Support\\Resource;\nuse function Tobyz\\JsonApiServer\\Laravel\\can;\n\n/**\n * Comments JSON:API resource.\n *\n * Defines fields, endpoints, sorts, filters, and scope for comments.\n */\nclass CommentsResource extends Resource\n{\n    public function __construct()\n    {\n        parent::__construct();\n\n        $this->scope->add(function (Builder $query) {\n            // Required to generate URLs\n            $query->with('post');\n        }, 'default');\n\n        $this->endpoints\n            ->add(Endpoint\\Index::make()->paginate()->defaultSort('-createdAt'), 'index')\n\n            ->add(Endpoint\\Show::make(), 'show');\n\n        $this->fields\n            ->add(ToOne::make('post')->includable(), 'post')\n\n            ->add(ToOne::make('parent')->type('comments')->nullable()->includable(), 'parent')\n\n            ->add(ToOne::make('user')->nullable()->includable(), 'user')\n\n            ->add(Attribute::make('body')->type(Type\\Str::make())->sparse(), 'body')\n\n            ->add(Attribute::make('bodyText')->type(Type\\Str::make())->sparse(), 'bodyText')\n\n            ->add(Attribute::make('bodyHtml')->type(Type\\Str::make()->format('html')), 'bodyHtml')\n\n            ->add(Attribute::make('createdAt')->type(Type\\DateTime::make()), 'createdAt')\n\n            ->add(Attribute::make('editedAt')->type(Type\\DateTime::make())->nullable(), 'editedAt')\n\n            ->add(Attribute::make('replyCount')->type(Type\\Integer::make()), 'replyCount')\n\n            ->add(\n                Attribute::make('deletedAt')->type(Type\\DateTime::make())->nullable(),\n                'deletedAt',\n            )\n\n            ->add(\n                ToOne::make('deletedBy')\n                    ->type('users')\n                    ->nullable()\n                    ->visible(can('waterhole.comment.moderate')),\n                'deletedBy',\n            )\n\n            ->add(\n                Attribute::make('deletedReason')->type(Type\\Str::make())->nullable(),\n                'deletedReason',\n            )\n\n            ->add(ToMany::make('replies')->type('comments'), 'replies')\n\n            ->add(Attribute::make('url')->type(Type\\Str::make()->format('uri')), 'url')\n\n            ->add(Attribute::make('postUrl')->type(Type\\Str::make()->format('uri')), 'postUrl')\n\n            ->add(ToMany::make('reactionCounts')->includable(), 'reactionCounts')\n\n            ->add(ToMany::make('reactions')->includable(), 'reactions');\n\n        $this->sorts\n            ->add(SortColumn::make('createdAt'), 'createdAt')\n            ->add(SortColumn::make('score'), 'score');\n\n        $this->filters\n            ->add(WhereBelongsTo::make('post'), 'post')\n            ->add(WhereBelongsTo::make('parent'), 'parent')\n            ->add(WhereBelongsTo::make('user'), 'user')\n            ->add(WhereNotNull::make('isRemoved')->column('deleted_at'), 'isRemoved');\n    }\n}\n"
  },
  {
    "path": "src/Extend/Api/GroupsResource.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Api;\n\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Support\\Facades\\Auth;\nuse Tobyz\\JsonApiServer\\Endpoint;\nuse Tobyz\\JsonApiServer\\Schema\\Field\\Attribute;\nuse Tobyz\\JsonApiServer\\Schema\\Field\\ToMany;\nuse Tobyz\\JsonApiServer\\Schema\\Type;\nuse Waterhole\\Extend\\Support\\Resource;\nuse Waterhole\\Models\\Group;\nuse function Waterhole\\icon;\n\n/**\n * Groups JSON:API resource.\n *\n * Defines fields, endpoints, sorts, filters, and scope for groups.\n */\nclass GroupsResource extends Resource\n{\n    public function __construct()\n    {\n        parent::__construct();\n\n        $this->scope->add(function (Builder $query) {\n            if (!Auth::user()?->can('waterhole.user.edit')) {\n                $query->where('is_public', true);\n            }\n        }, 'default');\n\n        $this->endpoints\n            ->add(Endpoint\\Index::make()->paginate(), 'index')\n\n            ->add(Endpoint\\Show::make(), 'show');\n\n        $this->fields\n            ->add(Attribute::make('name')->type(Type\\Str::make()), 'name')\n\n            ->add(Attribute::make('isPublic')->type(Type\\Boolean::make()), 'isPublic')\n\n            ->add(Attribute::make('color')->type(Type\\Str::make()), 'color')\n\n            ->add(\n                Attribute::make('iconHtml')\n                    ->type(Type\\Str::make()->format('html'))\n                    ->nullable()\n                    ->get(fn(Group $group) => icon($group->icon)),\n                'iconHtml',\n            )\n\n            ->add(ToMany::make('users'), 'users');\n    }\n}\n"
  },
  {
    "path": "src/Extend/Api/JsonApi.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Api;\n\nuse Tobyz\\JsonApiServer\\JsonApi as BaseJsonApi;\n\n/**\n * JSON:API server extender.\n *\n * Use this extender to register additional resources, collections, and\n * extensions.\n */\nclass JsonApi extends BaseJsonApi {}\n"
  },
  {
    "path": "src/Extend/Api/PagesResource.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Api;\n\nuse Tobyz\\JsonApiServer\\Endpoint;\nuse Tobyz\\JsonApiServer\\Schema\\Field\\Attribute;\nuse Tobyz\\JsonApiServer\\Schema\\Type;\nuse Waterhole\\Extend\\Support\\Resource;\nuse Waterhole\\Models\\Page;\nuse function Waterhole\\icon;\n\n/**\n * Pages JSON:API resource.\n *\n * Defines fields, endpoints, sorts, filters, and scope for pages.\n */\nclass PagesResource extends Resource\n{\n    public function __construct()\n    {\n        parent::__construct();\n\n        $this->endpoints->add(Endpoint\\Show::make(), 'show');\n\n        $this->fields\n            ->add(Attribute::make('name')->type(Type\\Str::make()), 'name')\n\n            ->add(\n                Attribute::make('iconHtml')\n                    ->type(Type\\Str::make()->format('html'))\n                    ->nullable()\n                    ->get(fn(Page $page) => icon($page->icon)),\n                'iconHtml',\n            )\n\n            ->add(\n                Attribute::make('bodyHtml')\n                    ->type(Type\\Str::make()->format('html'))\n                    ->nullable(),\n                'bodyHtml',\n            )\n\n            ->add(Attribute::make('url')->type(Type\\Str::make()->format('uri')), 'url');\n    }\n}\n"
  },
  {
    "path": "src/Extend/Api/PostUsersResource.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Api;\n\nuse Tobyz\\JsonApiServer\\Schema\\Field\\Attribute;\nuse Tobyz\\JsonApiServer\\Schema\\Field\\ToOne;\nuse Tobyz\\JsonApiServer\\Schema\\Type;\nuse Waterhole\\Extend\\Support\\Resource;\n\n/**\n * Post users JSON:API resource.\n *\n * Defines fields, endpoints, sorts, filters, and scope for post users.\n */\nclass PostUsersResource extends Resource\n{\n    public function __construct()\n    {\n        parent::__construct();\n\n        $this->fields\n            ->add(ToOne::make('post'), 'post')\n\n            ->add(ToOne::make('user'), 'user')\n\n            ->add(\n                Attribute::make('notifications')\n                    ->type(Type\\Str::make()->enum(['normal', 'follow', 'ignore']))\n                    ->nullable(),\n                'notifications',\n            )\n\n            ->add(\n                Attribute::make('lastReadAt')->type(Type\\DateTime::make())->nullable(),\n                'lastReadAt',\n            )\n\n            ->add(\n                Attribute::make('followedAt')->type(Type\\DateTime::make())->nullable(),\n                'followedAt',\n            )\n\n            ->add(\n                Attribute::make('mentionedAt')->type(Type\\DateTime::make())->nullable(),\n                'mentionedAt',\n            );\n    }\n}\n"
  },
  {
    "path": "src/Extend/Api/PostsResource.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Api;\n\nuse Illuminate\\Support\\Facades\\Auth;\nuse Tobyz\\JsonApiServer\\Endpoint;\nuse Tobyz\\JsonApiServer\\Laravel\\Filter\\Scope;\nuse Tobyz\\JsonApiServer\\Laravel\\Filter\\Where;\nuse Tobyz\\JsonApiServer\\Laravel\\Filter\\WhereBelongsTo;\nuse Tobyz\\JsonApiServer\\Laravel\\Filter\\WhereExists;\nuse Tobyz\\JsonApiServer\\Laravel\\Filter\\WhereHas;\nuse Tobyz\\JsonApiServer\\Laravel\\Filter\\WhereNull;\nuse Tobyz\\JsonApiServer\\Laravel\\Sort\\SortColumn;\nuse Tobyz\\JsonApiServer\\Schema\\Field\\Attribute;\nuse Tobyz\\JsonApiServer\\Schema\\Field\\ToMany;\nuse Tobyz\\JsonApiServer\\Schema\\Field\\ToOne;\nuse Tobyz\\JsonApiServer\\Schema\\Type;\nuse Waterhole\\Extend\\Support\\Resource;\nuse function Tobyz\\JsonApiServer\\Laravel\\can;\n\n/**\n * Posts JSON:API resource.\n *\n * Defines fields, endpoints, sorts, filters, and scope for posts.\n */\nclass PostsResource extends Resource\n{\n    public function __construct()\n    {\n        parent::__construct();\n\n        $this->scope->add(function ($query) {\n            $query->with('mentions', 'attachments');\n        }, 'default');\n\n        $this->endpoints\n            ->add(Endpoint\\Index::make()->paginate()->defaultSort('-createdAt'), 'index')\n\n            ->add(Endpoint\\Show::make(), 'show');\n\n        $this->fields\n            ->add(Attribute::make('title')->type(Type\\Str::make()), 'title')\n\n            ->add(Attribute::make('body')->type(Type\\Str::make())->sparse(), 'body')\n\n            ->add(Attribute::make('bodyText')->type(Type\\Str::make())->sparse(), 'bodyText')\n\n            ->add(Attribute::make('bodyHtml')->type(Type\\Str::make()->format('html')), 'bodyHtml')\n\n            ->add(Attribute::make('createdAt')->type(Type\\DateTime::make()), 'createdAt')\n\n            ->add(Attribute::make('editedAt')->type(Type\\DateTime::make())->nullable(), 'editedAt')\n\n            ->add(\n                Attribute::make('deletedAt')->type(Type\\DateTime::make())->nullable(),\n                'deletedAt',\n            )\n\n            ->add(\n                ToOne::make('deletedBy')\n                    ->type('users')\n                    ->nullable()\n                    ->visible(can('waterhole.post.moderate')),\n                'deletedBy',\n            )\n\n            ->add(\n                Attribute::make('deletedReason')->type(Type\\Str::make())->nullable(),\n                'deletedReason',\n            )\n\n            ->add(\n                Attribute::make('lastActivityAt')->type(Type\\DateTime::make())->nullable(),\n                'lastActivityAt',\n            )\n\n            ->add(Attribute::make('commentCount')->type(Type\\Integer::make()), 'commentCount')\n\n            ->add(Attribute::make('viewCount')->type(Type\\Integer::make()), 'viewCount')\n\n            ->add(Attribute::make('isLocked')->type(Type\\Boolean::make()), 'isLocked')\n\n            ->add(Attribute::make('isPinned')->type(Type\\Boolean::make()), 'isPinned')\n\n            ->add(Attribute::make('url')->type(Type\\Str::make()->format('uri')), 'url')\n\n            ->add(ToOne::make('channel')->includable(), 'channel')\n\n            ->add(ToOne::make('user')->includable()->nullable(), 'user')\n\n            ->add(ToMany::make('comments'), 'comments')\n\n            ->add(\n                ToOne::make('lastComment')\n                    ->type('comments')\n                    ->nullable()\n                    ->withoutLinkage()\n                    ->includable(),\n                'lastComment',\n            )\n\n            ->add(ToOne::make('answer')->type('comments')->nullable()->includable(), 'answer')\n\n            ->add(ToMany::make('tags')->includable(), 'tags')\n\n            ->add(ToMany::make('reactionCounts')->includable(), 'reactionCounts')\n\n            ->add(ToMany::make('reactions')->includable(), 'reactions')\n\n            ->add(\n                ToOne::make('userState')\n                    ->type('postUsers')\n                    ->nullable()\n                    ->visible(fn() => Auth::check())\n                    ->includable(),\n                'userState',\n            );\n\n        $this->sorts\n            ->add(SortColumn::make('title'), 'title')\n            ->add(SortColumn::make('createdAt'), 'createdAt')\n            ->add(SortColumn::make('lastActivityAt'), 'lastActivityAt')\n            ->add(SortColumn::make('commentCount'), 'commentCount')\n            ->add(SortColumn::make('viewCount'), 'viewCount')\n            ->add(SortColumn::make('score'), 'score')\n            ->add(SortColumn::make('hotness'), 'hotness');\n\n        $this->filters\n            ->add(Scope::make('unread'), 'unread')\n            ->add(Scope::make('following'), 'following')\n            ->add(Scope::make('ignoring'), 'ignoring')\n            ->add(Where::make('isLocked')->asBoolean(), 'isLocked')\n            ->add(Where::make('isPinned')->asBoolean(), 'isPinned')\n            ->add(WhereNull::make('isTrashed')->column('deleted_at'), 'isTrashed')\n            ->add(WhereBelongsTo::make('channel'), 'channel')\n            ->add(WhereBelongsTo::make('user'), 'user')\n            ->add(WhereHas::make('tags'), 'tags')\n            ->add(WhereExists::make('answer'), 'answer');\n    }\n}\n"
  },
  {
    "path": "src/Extend/Api/ReactionSetsResource.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Api;\n\nuse Tobyz\\JsonApiServer\\Schema\\Field\\Attribute;\nuse Tobyz\\JsonApiServer\\Schema\\Field\\ToMany;\nuse Tobyz\\JsonApiServer\\Schema\\Type;\nuse Waterhole\\Extend\\Support\\Resource;\n\n/**\n * Reaction sets JSON:API resource.\n *\n * Defines fields, endpoints, sorts, filters, and scope for reaction sets.\n */\nclass ReactionSetsResource extends Resource\n{\n    public function __construct()\n    {\n        parent::__construct();\n\n        $this->fields\n            ->add(Attribute::make('name')->type(Type\\Str::make()), 'name')\n\n            ->add(Attribute::make('allowMultiple')->type(Type\\Boolean::make()), 'allowMultiple')\n\n            ->add(ToMany::make('reactionTypes')->includable(), 'reactionTypes');\n    }\n}\n"
  },
  {
    "path": "src/Extend/Api/ReactionTypesResource.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Api;\n\nuse Tobyz\\JsonApiServer\\Schema\\Field\\Attribute;\nuse Tobyz\\JsonApiServer\\Schema\\Field\\ToOne;\nuse Tobyz\\JsonApiServer\\Schema\\Type;\nuse Waterhole\\Extend\\Support\\Resource;\nuse Waterhole\\Models\\ReactionType;\nuse function Waterhole\\icon;\n\n/**\n * Reaction types JSON:API resource.\n *\n * Defines fields, endpoints, sorts, filters, and scope for reaction types.\n */\nclass ReactionTypesResource extends Resource\n{\n    public function __construct()\n    {\n        parent::__construct();\n\n        $this->fields\n            ->add(ToOne::make('reactionSet'), 'reactionSet')\n\n            ->add(Attribute::make('name')->type(Type\\Str::make()), 'name')\n\n            ->add(\n                Attribute::make('iconHtml')\n                    ->type(Type\\Str::make()->format('html'))\n                    ->nullable()\n                    ->get(fn(ReactionType $reactionType) => icon($reactionType->icon)),\n                'iconHtml',\n            )\n\n            ->add(Attribute::make('score')->type(Type\\Integer::make()), 'score');\n    }\n}\n"
  },
  {
    "path": "src/Extend/Api/ReactionsResource.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Api;\n\nuse Tobyz\\JsonApiServer\\Schema\\Field\\Attribute;\nuse Tobyz\\JsonApiServer\\Schema\\Field\\ToOne;\nuse Tobyz\\JsonApiServer\\Schema\\Type;\nuse Waterhole\\Extend\\Support\\Resource;\n\n/**\n * Reactions JSON:API resource.\n *\n * Defines fields, endpoints, sorts, filters, and scope for reactions.\n */\nclass ReactionsResource extends Resource\n{\n    public function __construct()\n    {\n        parent::__construct();\n\n        $this->fields\n            ->add(\n                Attribute::make('createdAt')->type(Type\\DateTime::make())->nullable(),\n                'createdAt',\n            )\n\n            ->add(ToOne::make('reactionType'), 'reactionType')\n\n            ->add(ToOne::make('user'), 'user')\n\n            ->add(ToOne::make('content')->type(['posts', 'comments']), 'content');\n    }\n}\n"
  },
  {
    "path": "src/Extend/Api/StructureHeadingsResource.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Api;\n\nuse Tobyz\\JsonApiServer\\Endpoint;\nuse Tobyz\\JsonApiServer\\Schema\\Field\\Attribute;\nuse Tobyz\\JsonApiServer\\Schema\\Type;\nuse Waterhole\\Extend\\Support\\Resource;\n\n/**\n * Structure headings JSON:API resource.\n *\n * Defines fields, endpoints, sorts, filters, and scope for structure\n * headings.\n */\nclass StructureHeadingsResource extends Resource\n{\n    public function __construct()\n    {\n        parent::__construct();\n\n        $this->endpoints->add(Endpoint\\Show::make(), 'show');\n\n        $this->fields->add(Attribute::make('name')->type(Type\\Str::make()), 'name');\n    }\n}\n"
  },
  {
    "path": "src/Extend/Api/StructureLinksResource.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Api;\n\nuse Tobyz\\JsonApiServer\\Endpoint;\nuse Tobyz\\JsonApiServer\\Schema\\Field\\Attribute;\nuse Tobyz\\JsonApiServer\\Schema\\Type;\nuse Waterhole\\Extend\\Support\\Resource;\nuse Waterhole\\Models\\StructureLink;\nuse function Waterhole\\icon;\n\n/**\n * Structure links JSON:API resource.\n *\n * Defines fields, endpoints, sorts, filters, and scope for structure\n * links.\n */\nclass StructureLinksResource extends Resource\n{\n    public function __construct()\n    {\n        parent::__construct();\n\n        $this->endpoints->add(Endpoint\\Show::make(), 'show');\n\n        $this->fields\n            ->add(Attribute::make('name')->type(Type\\Str::make()), 'name')\n\n            ->add(\n                Attribute::make('iconHtml')\n                    ->type(Type\\Str::make()->format('html'))\n                    ->nullable()\n                    ->get(fn(StructureLink $link) => icon($link->icon)),\n                'iconHtml',\n            )\n\n            ->add(Attribute::make('href')->type(Type\\Str::make()->format('uri')), 'href');\n    }\n}\n"
  },
  {
    "path": "src/Extend/Api/StructureResource.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Api;\n\nuse Tobyz\\JsonApiServer\\Endpoint;\nuse Tobyz\\JsonApiServer\\Laravel\\Sort\\SortColumn;\nuse Tobyz\\JsonApiServer\\Schema\\Field\\Attribute;\nuse Tobyz\\JsonApiServer\\Schema\\Field\\ToOne;\nuse Tobyz\\JsonApiServer\\Schema\\Type;\nuse Waterhole\\Extend\\Support\\Resource;\n\n/**\n * Structure JSON:API resource.\n *\n * Defines fields, endpoints, sorts, filters, and scope for structure.\n */\nclass StructureResource extends Resource\n{\n    public function __construct()\n    {\n        parent::__construct();\n\n        $this->endpoints->add(Endpoint\\Index::make()->defaultSort('position'), 'index');\n\n        $this->fields\n            ->add(Attribute::make('position')->type(Type\\Integer::make()), 'position')\n\n            ->add(Attribute::make('isListed')->type(Type\\Boolean::make()), 'isListed')\n\n            ->add(ToOne::make('content')->collection('structureContent')->includable(), 'content');\n\n        $this->sorts->add(SortColumn::make('position'), 'position');\n    }\n}\n"
  },
  {
    "path": "src/Extend/Api/TagsResource.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Api;\n\nuse Tobyz\\JsonApiServer\\Schema\\Field\\Attribute;\nuse Tobyz\\JsonApiServer\\Schema\\Field\\ToOne;\nuse Tobyz\\JsonApiServer\\Schema\\Type;\nuse Waterhole\\Extend\\Support\\Resource;\n\n/**\n * Tags JSON:API resource.\n *\n * Defines fields, endpoints, sorts, filters, and scope for tags.\n */\nclass TagsResource extends Resource\n{\n    public function __construct()\n    {\n        parent::__construct();\n\n        $this->fields\n            ->add(Attribute::make('name')->type(Type\\Str::make()), 'name')\n\n            ->add(ToOne::make('taxonomy')->includable(), 'taxonomy');\n    }\n}\n"
  },
  {
    "path": "src/Extend/Api/TaxonomiesResource.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Api;\n\nuse Tobyz\\JsonApiServer\\Schema\\Field\\Attribute;\nuse Tobyz\\JsonApiServer\\Schema\\Field\\ToMany;\nuse Tobyz\\JsonApiServer\\Schema\\Type;\nuse Waterhole\\Extend\\Support\\Resource;\n\n/**\n * Taxonomies JSON:API resource.\n *\n * Defines fields, endpoints, sorts, filters, and scope for taxonomies.\n */\nclass TaxonomiesResource extends Resource\n{\n    public function __construct()\n    {\n        parent::__construct();\n\n        $this->fields\n            ->add(Attribute::make('name')->type(Type\\Str::make()), 'name')\n\n            ->add(ToMany::make('tags')->includable(), 'tags');\n    }\n}\n"
  },
  {
    "path": "src/Extend/Api/UsersResource.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Api;\n\nuse Illuminate\\Support\\Facades\\Auth;\nuse Tobyz\\JsonApiServer\\Endpoint;\nuse Tobyz\\JsonApiServer\\Laravel\\Filter\\Where;\nuse Tobyz\\JsonApiServer\\Laravel\\Sort\\SortColumn;\nuse Tobyz\\JsonApiServer\\Schema\\Field\\Attribute;\nuse Tobyz\\JsonApiServer\\Schema\\Field\\ToMany;\nuse Tobyz\\JsonApiServer\\Schema\\Type;\nuse Waterhole\\Extend\\Support\\Resource;\nuse Waterhole\\Models\\User;\n\n/**\n * Users JSON:API resource.\n *\n * Defines fields, endpoints, sorts, filters, and scope for users.\n */\nclass UsersResource extends Resource\n{\n    public function __construct()\n    {\n        parent::__construct();\n\n        $this->endpoints\n            ->add(Endpoint\\Index::make()->visible(fn() => Auth::user()?->isAdmin()), 'index')\n\n            ->add(Endpoint\\Show::make(), 'show');\n\n        $canViewPrivate = fn(User $user) => ($actor = Auth::user()) &&\n            ($actor->isAdmin() || $actor->is($user));\n\n        $this->fields\n            ->add(Attribute::make('name')->type(Type\\Str::make()), 'name')\n\n            ->add(\n                Attribute::make('email')\n                    ->type(Type\\Str::make()->format('email'))\n                    ->visible($canViewPrivate),\n                'email',\n            )\n\n            ->add(\n                Attribute::make('locale')->type(Type\\Str::make())->visible($canViewPrivate),\n                'locale',\n            )\n\n            ->add(Attribute::make('headline')->type(Type\\Str::make())->nullable(), 'headline')\n\n            ->add(Attribute::make('bio')->type(Type\\Str::make())->nullable(), 'bio')\n\n            ->add(Attribute::make('location')->type(Type\\Str::make())->nullable(), 'location')\n\n            ->add(\n                Attribute::make('website')\n                    ->type(Type\\Str::make()->format('uri'))\n                    ->nullable(),\n                'website',\n            )\n\n            ->add(\n                Attribute::make('avatarUrl')\n                    ->type(Type\\Str::make()->format('uri'))\n                    ->nullable(),\n                'avatarUrl',\n            )\n\n            ->add(\n                Attribute::make('createdAt')->type(Type\\DateTime::make())->nullable(),\n                'createdAt',\n            )\n\n            ->add(\n                Attribute::make('lastSeenAt')\n                    ->type(Type\\DateTime::make())\n                    ->nullable()\n                    ->visible(fn(User $user) => $user->show_online || $canViewPrivate($user)),\n                'lastSeenAt',\n            )\n\n            ->add(\n                Attribute::make('suspendedUntil')\n                    ->type(Type\\DateTime::make())\n                    ->nullable()\n                    ->visible($canViewPrivate),\n                'suspendedUntil',\n            )\n\n            ->add(Attribute::make('url')->type(Type\\Str::make()->format('uri')), 'url')\n\n            ->add(ToMany::make('posts'), 'posts')\n\n            ->add(ToMany::make('comments'), 'comments')\n\n            ->add(ToMany::make('groups')->includable(), 'groups');\n\n        $this->sorts\n            ->add(SortColumn::make('name'), 'name')\n\n            ->add(SortColumn::make('createdAt'), 'createdAt')\n\n            ->add(SortColumn::make('lastSeenAt'), 'lastSeenAt');\n\n        $this->filters\n            ->add(Where::make('id'), 'id')\n\n            ->add(Where::make('email'), 'email');\n\n        // name LIKE\n        // isSuspended\n        // groups)\n    }\n}\n"
  },
  {
    "path": "src/Extend/Assets/Locales.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Assets;\n\nuse Waterhole\\Extend\\Support\\UnorderedList;\n\n/**\n * List of locales exposed in the language selector and locale assets.\n *\n * Use this extender to add locale packs and expose them in the language picker.\n */\nclass Locales extends UnorderedList\n{\n    public function __construct()\n    {\n        $this->add('English', 'en');\n    }\n}\n"
  },
  {
    "path": "src/Extend/Assets/Script.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Assets;\n\nuse Waterhole\\Extend\\Support\\Assets;\n\n/**\n * JavaScript asset bundles.\n *\n * Add file paths or callbacks that return JS; bundles are concatenated\n * and cached.\n */\nclass Script extends Assets\n{\n    public function __construct()\n    {\n        $this->add(__DIR__ . '/../../../resources/dist/global.js');\n        $this->add(__DIR__ . '/../../../resources/dist/highlight.js');\n        $this->add(__DIR__ . '/../../../resources/dist/emoji.js');\n\n        $this->add(__DIR__ . '/../../../resources/dist/cp.js', 'cp');\n    }\n\n    protected function cacheKey(string $bundle): string\n    {\n        return \"waterhole.script.$bundle\";\n    }\n\n    protected function filePath(string $filename): string\n    {\n        return \"js/$filename.js\";\n    }\n}\n"
  },
  {
    "path": "src/Extend/Assets/Stylesheet.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Assets;\n\nuse Waterhole\\Extend\\Support\\Assets;\n\n/**\n * Stylesheet asset bundles.\n *\n * Add file paths or callbacks that return CSS; bundles are concatenated\n * and cached.\n */\nclass Stylesheet extends Assets\n{\n    public function __construct()\n    {\n        $this->add(__DIR__ . '/../../../resources/dist/global.css');\n\n        $this->add(__DIR__ . '/../../../resources/dist/cp.css', 'cp');\n    }\n\n    protected function cacheKey(string $bundle): string\n    {\n        return \"waterhole.stylesheet.$bundle\";\n    }\n\n    protected function filePath(string $filename): string\n    {\n        return \"css/$filename.css\";\n    }\n}\n"
  },
  {
    "path": "src/Extend/Core/Actions.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Core;\n\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\Support\\Facades\\Auth;\nuse Waterhole\\Actions as CoreActions;\nuse Waterhole\\Actions\\Action;\nuse Waterhole\\Extend\\Support\\OrderedList;\nuse Waterhole\\Models;\nuse Waterhole\\Models\\User;\nuse Waterhole\\View\\Components\\MenuDivider;\nuse function Waterhole\\resolve_all;\n\n/**\n * Action lists per model class.\n *\n * Resolves authorized actions for menus and buttons using model class names.\n */\nclass Actions\n{\n    /**\n     * @var array<class-string, OrderedList>\n     */\n    private array $lists = [];\n\n    public function __construct()\n    {\n        $this->registerDefaults();\n    }\n\n    public function for(string $modelClass): OrderedList\n    {\n        return $this->lists[$modelClass] ??= new OrderedList();\n    }\n\n    public function hasList(string $modelClass): bool\n    {\n        return isset($this->lists[$modelClass]);\n    }\n\n    /**\n     * Get a list of action instances that can be applied to the given model(s).\n     */\n    public function actionsFor($models, ?User $user = null): array\n    {\n        $models =\n            $models instanceof Collection\n                ? $models\n                : collect(is_array($models) ? $models : [$models]);\n\n        if ($models->isEmpty()) {\n            return [];\n        }\n\n        $user ??= Auth::user();\n\n        $modelClass = get_class($models->first());\n        $list = $this->lists[$modelClass] ?? null;\n\n        if (!$list) {\n            return [];\n        }\n\n        $actions = collect(resolve_all($list->items()));\n        $single = $models->count() <= 1;\n\n        return $actions\n            ->filter(\n                fn($action) => !$action instanceof Action ||\n                    (($single || $action->bulk) &&\n                        $models->every(\n                            fn($model) => $action->appliesTo($model) &&\n                                $action->authorize($user, $model),\n                        )),\n            )\n            ->all();\n    }\n\n    /**\n     * Determine if any action is available for the given model(s).\n     */\n    public function hasActions($models, ?User $user = null, ?string $context = null): bool\n    {\n        $models =\n            $models instanceof Collection\n                ? $models\n                : collect(is_array($models) ? $models : [$models]);\n\n        if ($models->isEmpty()) {\n            return false;\n        }\n\n        $user ??= Auth::user();\n\n        $modelClass = get_class($models->first());\n        $list = $this->lists[$modelClass] ?? null;\n\n        if (!$list) {\n            return false;\n        }\n\n        $single = $models->count() <= 1;\n\n        foreach (resolve_all($list->items()) as $action) {\n            if (!$action instanceof Action) {\n                continue;\n            }\n\n            if (!$single && !$action->bulk) {\n                continue;\n            }\n\n            if (\n                !$models->every(\n                    fn($model) => $action->appliesTo($model) && $action->authorize($user, $model),\n                )\n            ) {\n                continue;\n            }\n\n            if ($action->shouldRender($models, $context)) {\n                return true;\n            }\n        }\n\n        return false;\n    }\n\n    private function registerDefaults(): void\n    {\n        $this->for(Models\\Channel::class)\n            ->add(CoreActions\\Follow::class, 'follow')\n            ->add(CoreActions\\Unfollow::class, 'unfollow')\n            ->add(CoreActions\\Ignore::class, 'ignore')\n            ->add(CoreActions\\Unignore::class, 'unignore')\n            ->add(MenuDivider::class, 'divider')\n            ->add(CoreActions\\EditChannel::class, 'edit-channel')\n            ->add(CoreActions\\DeleteChannel::class, 'delete-channel');\n\n        $this->for(Models\\Comment::class)\n            ->add(CoreActions\\CopyLink::class, 'copy-link')\n            ->add(CoreActions\\Report::class, 'report')\n            ->add(MenuDivider::class, 'divider')\n            ->add(CoreActions\\EditComment::class, 'edit-comment')\n            ->add(CoreActions\\DismissFlags::class, 'dismiss-flags')\n            ->add(CoreActions\\RemoveComment::class, 'remove-comment')\n            ->add(CoreActions\\RestoreComment::class, 'restore-comment')\n            ->add(CoreActions\\DeleteComment::class, 'delete-comment')\n            ->add(CoreActions\\MarkAsAnswer::class, 'mark-as-answer')\n            ->add(CoreActions\\React::class, 'react');\n\n        $this->for(Models\\Group::class)\n            ->add(CoreActions\\EditGroup::class, 'edit-group')\n            ->add(CoreActions\\DeleteGroup::class, 'delete-group');\n\n        $this->for(Models\\Page::class)\n            ->add(CoreActions\\EditStructure::class, 'edit-structure')\n            ->add(CoreActions\\DeleteStructure::class, 'delete-structure');\n\n        $this->for(Models\\Post::class)\n            ->add(CoreActions\\CopyLink::class, 'copy-link')\n            ->add(CoreActions\\Report::class, 'report')\n            ->add(CoreActions\\MarkAsRead::class, 'mark-as-read')\n            ->add(CoreActions\\Follow::class, 'follow')\n            ->add(CoreActions\\Unfollow::class, 'unfollow')\n            ->add(CoreActions\\Ignore::class, 'ignore')\n            ->add(CoreActions\\Unignore::class, 'unignore')\n            ->add(MenuDivider::class, 'divider')\n            ->add(CoreActions\\EditPost::class, 'edit-post')\n            ->add(CoreActions\\Pin::class, 'pin')\n            ->add(CoreActions\\Unpin::class, 'unpin')\n            ->add(CoreActions\\Lock::class, 'lock')\n            ->add(CoreActions\\Unlock::class, 'unlock')\n            ->add(CoreActions\\MoveToChannel::class, 'move-to-channel')\n            ->add(CoreActions\\DismissFlags::class, 'dismiss-flags')\n            ->add(CoreActions\\TrashPost::class, 'trash-post')\n            ->add(CoreActions\\RestorePost::class, 'restore-post')\n            ->add(CoreActions\\DeletePost::class, 'delete-post')\n            ->add(CoreActions\\React::class, 'react');\n\n        $this->for(Models\\ReactionSet::class)\n            ->add(CoreActions\\EditReactionSet::class, 'edit-reaction-set')\n            ->add(CoreActions\\DeleteReactionSet::class, 'delete-reaction-set');\n\n        $this->for(Models\\ReactionType::class)\n            ->add(CoreActions\\EditReactionType::class, 'edit-reaction-type')\n            ->add(CoreActions\\DeleteReactionType::class, 'delete-reaction-type');\n\n        $this->for(Models\\StructureHeading::class)\n            ->add(CoreActions\\EditStructure::class, 'edit-structure')\n            ->add(CoreActions\\DeleteStructure::class, 'delete-structure');\n\n        $this->for(Models\\StructureLink::class)\n            ->add(CoreActions\\EditStructure::class, 'edit-structure')\n            ->add(CoreActions\\DeleteStructure::class, 'delete-structure');\n\n        $this->for(Models\\Tag::class)\n            ->add(CoreActions\\EditTag::class, 'edit-tag')\n            ->add(CoreActions\\DeleteTag::class, 'delete-tag');\n\n        $this->for(Models\\Taxonomy::class)\n            ->add(CoreActions\\EditTaxonomy::class, 'edit-taxonomy')\n            ->add(CoreActions\\DeleteTaxonomy::class, 'delete-taxonomy');\n\n        $this->for(Models\\User::class)\n            ->add(CoreActions\\DeleteSelf::class, 'delete-self')\n            ->add(MenuDivider::class, 'divider')\n            ->add(CoreActions\\EditUser::class, 'edit-user')\n            ->add(CoreActions\\CopyImpersonationUrl::class, 'copy-impersonation-url')\n            ->add(CoreActions\\SuspendUser::class, 'suspend-user')\n            ->add(CoreActions\\DeleteUser::class, 'delete-user');\n    }\n}\n"
  },
  {
    "path": "src/Extend/Core/Formatter.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Core;\n\n/**\n * An extender to register formatting callbacks.\n *\n * Waterhole uses the [TextFormatter](https://github.com/s9e/TextFormatter)\n * library to safely format markup in posts and comments. You can hook in to add\n * or remove formatting syntax and change rendered HTML.\n */\nclass Formatter\n{\n    /**\n     * Add a configuration callback to the formatter.\n     */\n    public function configure(callable $callback): static\n    {\n        app('waterhole.formatter')->configure($callback);\n\n        return $this;\n    }\n\n    /**\n     * Add a parsing callback to the formatter.\n     *\n     * In the parsing phase, user-submitted text is parsed into an XML document\n     * for storage in the database. Here you can perform runtime configuration\n     * on the parser, or make manual alterations to the `$text` before it is\n     * parsed.\n     */\n    public function parsing(callable $callback): static\n    {\n        app('waterhole.formatter')->parsing($callback);\n\n        return $this;\n    }\n\n    /**\n     * Add a rendering callback to the formatter.\n     *\n     * The rendering phase is when the XML document stored in the database is\n     * transformed into HTML. Here you can perform runtime configuration on\n     * the renderer, or make manual alterations to the `$xml` document before\n     * it is rendered.\n     */\n    public function rendering(callable $callback): static\n    {\n        app('waterhole.formatter')->rendering($callback);\n\n        return $this;\n    }\n}\n"
  },
  {
    "path": "src/Extend/Core/NotificationTypes.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Core;\n\nuse Waterhole\\Extend\\Support\\UnorderedList;\nuse Waterhole\\Notifications\\Mention;\nuse Waterhole\\Notifications\\NewComment;\nuse Waterhole\\Notifications\\NewFlag;\nuse Waterhole\\Notifications\\NewPost;\n\n/**\n * List of notification classes shown in user notification preferences.\n *\n * Use this extender to register notification classes so users can manage\n * them in preferences.\n */\nclass NotificationTypes extends UnorderedList\n{\n    public function __construct()\n    {\n        $this->add(NewPost::class, 'new-post');\n        $this->add(NewComment::class, 'new-comment');\n        $this->add(Mention::class, 'mention');\n        $this->add(NewFlag::class, 'new-flag');\n    }\n}\n"
  },
  {
    "path": "src/Extend/Core/PostFilters.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Core;\n\nuse Waterhole\\Extend\\Support\\Set;\nuse Waterhole\\Filters\\Alphabetical;\nuse Waterhole\\Filters\\Latest;\nuse Waterhole\\Filters\\Newest;\nuse Waterhole\\Filters\\Oldest;\nuse Waterhole\\Filters\\Top;\nuse Waterhole\\Filters\\Trending;\n\n/**\n * Post feed filter classes.\n *\n * Used by feed filter menus and channel configuration in the control panel.\n */\nclass PostFilters extends Set\n{\n    public function __construct()\n    {\n        $this->add(Latest::class);\n        $this->add(Newest::class);\n        $this->add(Oldest::class);\n        $this->add(Trending::class);\n        $this->add(Top::class);\n        $this->add(Alphabetical::class);\n    }\n}\n"
  },
  {
    "path": "src/Extend/Core/PostLayouts.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Core;\n\nuse Waterhole\\Extend\\Support\\Set;\nuse Waterhole\\Layouts\\CardsLayout;\nuse Waterhole\\Layouts\\ListLayout;\n\n/**\n * Post feed layout classes.\n *\n * Used when rendering feeds and when configuring channel layouts in the\n * control panel.\n */\nclass PostLayouts extends Set\n{\n    public function __construct()\n    {\n        $this->add(ListLayout::class);\n        $this->add(CardsLayout::class);\n    }\n}\n"
  },
  {
    "path": "src/Extend/Forms/ChannelForm.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Forms;\n\nuse Waterhole\\Extend\\Support\\ComponentList;\nuse Waterhole\\Forms\\Fields\\ChannelAnswers;\nuse Waterhole\\Forms\\Fields\\ChannelApproval;\nuse Waterhole\\Forms\\Fields\\ChannelDescription;\nuse Waterhole\\Forms\\Fields\\ChannelFilters;\nuse Waterhole\\Forms\\Fields\\ChannelIgnore;\nuse Waterhole\\Forms\\Fields\\ChannelInstructions;\nuse Waterhole\\Forms\\Fields\\ChannelLayout;\nuse Waterhole\\Forms\\Fields\\ChannelName;\nuse Waterhole\\Forms\\Fields\\ChannelReactions;\nuse Waterhole\\Forms\\Fields\\ChannelSimilarPosts;\nuse Waterhole\\Forms\\Fields\\ChannelSlug;\nuse Waterhole\\Forms\\Fields\\ChannelTaxonomies;\nuse Waterhole\\Forms\\Fields\\Icon;\nuse Waterhole\\Forms\\Fields\\Permissions;\nuse Waterhole\\Forms\\FormSection;\n\n/**\n * List of fields for the channel create/edit form.\n *\n * Use this extender to add, remove, or reorder fields when building the form.\n */\nclass ChannelForm extends ComponentList\n{\n    public ComponentList $details;\n    public ComponentList $features;\n    public ComponentList $layout;\n    public ComponentList $posting;\n    public ComponentList $permissions;\n\n    public function __construct()\n    {\n        $this->add(\n            fn($model) => new FormSection(\n                __('waterhole::cp.channel-details-title'),\n                $this->details->components(compact('model')),\n            ),\n            'details',\n        );\n\n        $this->details = (new ComponentList())\n            ->add(ChannelName::class, 'name')\n            ->add(ChannelSlug::class, 'slug')\n            ->add(Icon::class, 'icon')\n            ->add(ChannelDescription::class, 'description')\n            ->add(ChannelIgnore::class, 'ignore');\n\n        $this->add(\n            fn($model) => new FormSection(\n                __('waterhole::cp.channel-features-title'),\n                $this->features->components(compact('model')),\n                open: false,\n            ),\n            'features',\n        );\n\n        $this->features = (new ComponentList())\n            ->add(ChannelTaxonomies::class, 'taxonomies')\n            ->add(ChannelAnswers::class, 'answers')\n            ->add(ChannelReactions::class, 'reactions');\n\n        $this->add(\n            fn($model) => new FormSection(\n                __('waterhole::cp.channel-layout-title'),\n                $this->layout->components(compact('model')),\n                open: false,\n            ),\n            'layout',\n        );\n\n        $this->layout = (new ComponentList())\n            ->add(ChannelLayout::class, 'layout')\n            ->add(ChannelFilters::class, 'filters');\n\n        $this->add(\n            fn($model) => new FormSection(\n                __('waterhole::cp.channel-posting-title'),\n                $this->posting->components(compact('model')),\n                open: false,\n            ),\n            'posting',\n        );\n\n        $this->posting = (new ComponentList())\n            ->add(ChannelInstructions::class, 'instructions')\n            ->add(ChannelSimilarPosts::class, 'similar-posts');\n\n        $this->add(\n            fn($model) => new FormSection(\n                __('waterhole::cp.channel-permissions-title'),\n                $this->permissions->components(compact('model')),\n                open: false,\n            ),\n            'permissions',\n        );\n\n        $this->permissions = (new ComponentList())\n            ->add(Permissions::class, 'permissions')\n            ->add(ChannelApproval::class, 'approval');\n    }\n}\n"
  },
  {
    "path": "src/Extend/Forms/GroupForm.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Forms;\n\nuse Waterhole\\Extend\\Support\\ComponentList;\nuse Waterhole\\Forms\\Fields\\GroupAppearance;\nuse Waterhole\\Forms\\Fields\\GroupGlobalPermissions;\nuse Waterhole\\Forms\\Fields\\GroupName;\nuse Waterhole\\Forms\\Fields\\GroupRules;\nuse Waterhole\\Forms\\Fields\\GroupStructurePermissions;\nuse Waterhole\\Forms\\FormSection;\n\n/**\n * List of fields for the group create/edit form.\n *\n * Use this extender to add, remove, or reorder fields when building the form.\n */\nclass GroupForm extends ComponentList\n{\n    public ComponentList $details;\n    public ComponentList $permissions;\n\n    public function __construct()\n    {\n        $this->add(\n            fn($model) => new FormSection(\n                __('waterhole::cp.group-details-title'),\n                $this->details->components(compact('model')),\n            ),\n            'details',\n        );\n\n        $this->details = (new ComponentList())\n            ->add(GroupName::class, 'name')\n            ->add(GroupAppearance::class, 'appearance')\n            ->add(GroupRules::class, 'rules');\n\n        $this->add(\n            fn($model) => new FormSection(\n                __('waterhole::cp.group-permissions-title'),\n                $this->permissions->components(compact('model')),\n            ),\n            'permissions',\n        );\n\n        $this->permissions = (new ComponentList())\n            ->add(GroupGlobalPermissions::class, 'global')\n            ->add(GroupStructurePermissions::class, 'channel');\n    }\n}\n"
  },
  {
    "path": "src/Extend/Forms/PageForm.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Forms;\n\nuse Waterhole\\Extend\\Support\\ComponentList;\nuse Waterhole\\Forms\\Fields\\Icon;\nuse Waterhole\\Forms\\Fields\\PageBody;\nuse Waterhole\\Forms\\Fields\\PageName;\nuse Waterhole\\Forms\\Fields\\PageSlug;\nuse Waterhole\\Forms\\Fields\\Permissions;\nuse Waterhole\\Forms\\FormSection;\n\n/**\n * List of fields for the page create/edit form.\n *\n * Use this extender to add, remove, or reorder fields when building the form.\n */\nclass PageForm extends ComponentList\n{\n    public ComponentList $details;\n\n    public function __construct()\n    {\n        $this->add(\n            fn($model) => new FormSection(\n                __('waterhole::cp.page-details-title'),\n                $this->details->components(compact('model')),\n            ),\n            'details',\n        );\n\n        $this->details = (new ComponentList())\n            ->add(PageName::class, 'name')\n            ->add(PageSlug::class, 'slug')\n            ->add(Icon::class, 'icon')\n            ->add(PageBody::class, 'body');\n\n        $this->add(\n            fn($model) => new FormSection(\n                __('waterhole::cp.page-permissions-title'),\n                [new Permissions($model)],\n                open: false,\n            ),\n            'permissions',\n        );\n    }\n}\n"
  },
  {
    "path": "src/Extend/Forms/PostForm.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Forms;\n\nuse Waterhole\\Extend\\Support\\ComponentList;\nuse Waterhole\\Forms\\Fields\\PostBody;\nuse Waterhole\\Forms\\Fields\\PostTags;\nuse Waterhole\\Forms\\Fields\\PostTitle;\n\n/**\n * List of fields for the post create/edit form.\n *\n * Use this extender to add, remove, or reorder fields when building the form.\n */\nclass PostForm extends ComponentList\n{\n    public function __construct()\n    {\n        $this->add(PostTitle::class, 'title');\n        $this->add(PostTags::class, 'tags');\n        $this->add(PostBody::class, 'body');\n    }\n}\n"
  },
  {
    "path": "src/Extend/Forms/ReactionSetForm.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Forms;\n\nuse Waterhole\\Extend\\Support\\ComponentList;\nuse Waterhole\\Forms\\Fields\\ReactionSetDefaults;\nuse Waterhole\\Forms\\Fields\\ReactionSetName;\n\n/**\n * List of fields for the reaction set create/edit form.\n *\n * Use this extender to add, remove, or reorder fields when building the form.\n */\nclass ReactionSetForm extends ComponentList\n{\n    public function __construct()\n    {\n        $this->add(ReactionSetName::class, 'name');\n        $this->add(ReactionSetDefaults::class, 'defaults');\n    }\n}\n"
  },
  {
    "path": "src/Extend/Forms/ReactionTypeForm.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Forms;\n\nuse Waterhole\\Extend\\Support\\ComponentList;\nuse Waterhole\\Forms\\Fields\\Icon;\nuse Waterhole\\Forms\\Fields\\ReactionTypeName;\nuse Waterhole\\Forms\\Fields\\ReactionTypeScore;\n\n/**\n * List of fields for the reaction type create/edit form.\n *\n * Use this extender to add, remove, or reorder fields when building the form.\n */\nclass ReactionTypeForm extends ComponentList\n{\n    public function __construct()\n    {\n        $this->add(ReactionTypeName::class, 'name');\n        $this->add(Icon::class, 'icon');\n        $this->add(ReactionTypeScore::class, 'score');\n    }\n}\n"
  },
  {
    "path": "src/Extend/Forms/RegistrationForm.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Forms;\n\nuse Waterhole\\Extend\\Support\\ComponentList;\nuse Waterhole\\Forms\\Fields\\UserEmail;\nuse Waterhole\\Forms\\Fields\\UserName;\nuse Waterhole\\Forms\\Fields\\UserPassword;\n\n/**\n * List of fields for the registration form.\n *\n * Use this extender to add, remove, or reorder fields on the public\n * registration form.\n */\nclass RegistrationForm extends ComponentList\n{\n    public function __construct()\n    {\n        $this->add(UserName::class, 'name');\n        $this->add(UserEmail::class, 'email');\n        $this->add(UserPassword::class, 'password');\n    }\n}\n"
  },
  {
    "path": "src/Extend/Forms/StructureLinkForm.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Forms;\n\nuse Waterhole\\Extend\\Support\\ComponentList;\nuse Waterhole\\Forms\\Fields\\Icon;\nuse Waterhole\\Forms\\Fields\\Permissions;\nuse Waterhole\\Forms\\Fields\\StructureLinkName;\nuse Waterhole\\Forms\\Fields\\StructureLinkUrl;\nuse Waterhole\\Forms\\FormSection;\n\n/**\n * List of fields for the structure link create/edit form.\n *\n * Use this extender to add, remove, or reorder fields when building the form.\n */\nclass StructureLinkForm extends ComponentList\n{\n    public ComponentList $details;\n\n    public function __construct()\n    {\n        $this->add(\n            fn($model) => new FormSection(\n                __('waterhole::cp.link-details-title'),\n                $this->details->components(compact('model')),\n            ),\n            'details',\n        );\n\n        $this->details = (new ComponentList())\n            ->add(StructureLinkName::class, 'name')\n            ->add(Icon::class, 'icon')\n            ->add(StructureLinkUrl::class, 'url');\n\n        $this->add(\n            fn($model) => new FormSection(\n                __('waterhole::cp.link-permissions-title'),\n                [new Permissions($model)],\n                open: false,\n            ),\n            'permissions',\n        );\n    }\n}\n"
  },
  {
    "path": "src/Extend/Forms/TagForm.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Forms;\n\nuse Waterhole\\Extend\\Support\\ComponentList;\nuse Waterhole\\Forms\\Fields\\TagName;\n\n/**\n * List of fields for the tag create/edit form.\n *\n * Use this extender to add, remove, or reorder fields when building the form.\n */\nclass TagForm extends ComponentList\n{\n    public function __construct()\n    {\n        $this->add(TagName::class, 'name');\n    }\n}\n"
  },
  {
    "path": "src/Extend/Forms/TaxonomyForm.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Forms;\n\nuse Waterhole\\Extend\\Support\\ComponentList;\nuse Waterhole\\Forms\\Fields\\TaxonomyName;\nuse Waterhole\\Forms\\Fields\\TaxonomyOptions;\n\n/**\n * List of fields for the taxonomy create/edit form.\n *\n * Use this extender to add, remove, or reorder fields when building the form.\n */\nclass TaxonomyForm extends ComponentList\n{\n    public function __construct()\n    {\n        $this->add(TaxonomyName::class, 'name');\n        $this->add(TaxonomyOptions::class, 'options');\n    }\n}\n"
  },
  {
    "path": "src/Extend/Forms/UserForm.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Forms;\n\nuse Waterhole\\Extend\\Support\\ComponentList;\nuse Waterhole\\Forms\\Fields\\UserAvatar;\nuse Waterhole\\Forms\\Fields\\UserBio;\nuse Waterhole\\Forms\\Fields\\UserEmail;\nuse Waterhole\\Forms\\Fields\\UserGroups;\nuse Waterhole\\Forms\\Fields\\UserHeadline;\nuse Waterhole\\Forms\\Fields\\UserLocation;\nuse Waterhole\\Forms\\Fields\\UserName;\nuse Waterhole\\Forms\\Fields\\UserPassword;\nuse Waterhole\\Forms\\Fields\\UserShowOnline;\nuse Waterhole\\Forms\\Fields\\UserWebsite;\nuse Waterhole\\Forms\\FormSection;\n\n/**\n * List of fields for the control panel user create/edit form.\n *\n * Use this extender to add, remove, or reorder fields when building the form.\n */\nclass UserForm extends ComponentList\n{\n    public ComponentList $account;\n    public ComponentList $profile;\n\n    public function __construct()\n    {\n        $this->add(\n            fn($model) => new FormSection(\n                __('waterhole::cp.user-account-title'),\n                $this->account->components(compact('model')),\n            ),\n            'account',\n        );\n\n        $this->account = (new ComponentList())\n            ->add(UserName::class, 'name')\n            ->add(UserEmail::class, 'email')\n            ->add(UserPassword::class, 'password')\n            ->add(UserGroups::class, 'groups');\n\n        $this->add(\n            fn($model) => new FormSection(\n                __('waterhole::cp.user-profile-title'),\n                $this->profile->components(compact('model')),\n                open: false,\n            ),\n            'profile',\n        );\n\n        $this->profile = (new ComponentList())\n            ->add(UserAvatar::class, 'avatar')\n            ->add(UserHeadline::class, 'headline')\n            ->add(UserBio::class, 'bio')\n            ->add(UserLocation::class, 'location')\n            ->add(UserWebsite::class, 'website')\n            ->add(UserShowOnline::class, 'showOnline');\n    }\n}\n"
  },
  {
    "path": "src/Extend/Query/CommentQuery.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Query;\n\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Waterhole\\Extend\\Support\\Set;\nuse Waterhole\\Models\\Channel;\nuse Waterhole\\Models\\Post;\n\n/**\n * Comment query callbacks.\n *\n * Use this extender to eager load relations or constrain comment list results.\n */\nclass CommentQuery extends Set\n{\n    public Set $feed;\n    public Set $thread;\n\n    public function __construct()\n    {\n        $this->feed = new Set();\n        $this->thread = new Set();\n\n        $this->add(function (Builder $query) {\n            $query->with([\n                'user.groups',\n                'parent.user.groups',\n                'mentions',\n                'attachments',\n                'reactionCounts',\n            ]);\n        });\n\n        $this->feed->add(function (Builder $query) {\n            $query->with([\n                'post.userState',\n                'post.channel',\n                'post.channel.commentsReactionSet.reactionTypes',\n                'parent.post',\n            ]);\n\n            if (Channel::allPermitted(auth()->user(), 'moderate') !== []) {\n                $query->with(['pendingFlags.createdBy', 'deletedBy']);\n            }\n        });\n\n        $this->thread->add(function (Builder $query, ?Post $post = null) {\n            if ($post && $post->canModerate(auth()->user())) {\n                $query->with(['pendingFlags.createdBy', 'deletedBy']);\n            }\n        });\n    }\n}\n"
  },
  {
    "path": "src/Extend/Query/PostFeedQuery.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Query;\n\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Support\\Facades\\Auth;\nuse Waterhole\\Extend\\Support\\Set;\n\n/**\n * Base post feed query callbacks.\n *\n * Use this extender to eager load relations or constrain feed results globally.\n */\nclass PostFeedQuery extends Set\n{\n    public function __construct()\n    {\n        $this->add(function (Builder $query) {\n            $query->with([\n                'user.groups',\n                'channel.postsReactionSet.reactionTypes',\n                'lastComment.user',\n                'tags',\n                'reactionCounts',\n            ]);\n\n            if (Auth::check()) {\n                $query->with(['userState', 'channel.userState'])->withUnreadCommentsCount();\n            }\n        });\n    }\n}\n"
  },
  {
    "path": "src/Extend/Query/PostVisibilityScopes.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Query;\n\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Waterhole\\Extend\\Support\\UnorderedList;\nuse Waterhole\\Models\\Channel;\nuse Waterhole\\Models\\User;\n\n/**\n * Post visibility query callbacks.\n *\n * Use this extender to apply additional visibility constraints for posts.\n */\nclass PostVisibilityScopes extends UnorderedList\n{\n    public function __construct()\n    {\n        $this->add(function (Builder $query, ?User $user) {\n            if (!is_null($ids = Channel::allPermitted($user))) {\n                $query->whereIn('channel_id', $ids);\n            }\n        }, 'channel');\n    }\n}\n"
  },
  {
    "path": "src/Extend/Routing/ApiRoutes.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Routing;\n\n/**\n * Register additional routes inside the Waterhole API route group.\n *\n * Routes are registered inside the resolution callback using the Route facade\n * so the existing group middleware and prefixes apply.\n */\nclass ApiRoutes {}\n"
  },
  {
    "path": "src/Extend/Routing/CpRoutes.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Routing;\n\n/**\n * Register additional routes inside the control panel route group.\n *\n * Routes are registered inside the resolution callback using the Route facade\n * so the existing group middleware and prefixes apply.\n */\nclass CpRoutes {}\n"
  },
  {
    "path": "src/Extend/Routing/ForumRoutes.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Routing;\n\n/**\n * Register additional routes inside the forum route group.\n *\n * Routes are registered inside the resolution callback using the Route facade\n * so the existing group middleware and prefixes apply.\n */\nclass ForumRoutes {}\n"
  },
  {
    "path": "src/Extend/ServiceProvider.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend;\n\nuse Closure;\nuse Illuminate\\Support\\ServiceProvider as BaseServiceProvider;\nuse InvalidArgumentException;\nuse ReflectionFunction;\n\n/**\n * Extension service provider.\n *\n * Adds the `extend()` helper that wires extenders into the container.\n */\nclass ServiceProvider extends BaseServiceProvider\n{\n    protected function extend(callable $callback): void\n    {\n        $reflection = new ReflectionFunction(Closure::fromCallable($callback));\n        $parameters = $reflection->getParameters();\n\n        if (!$parameters) {\n            throw new InvalidArgumentException(\n                'Extension callbacks must type-hint at least one extender.',\n            );\n        }\n\n        $classes = [];\n\n        foreach ($parameters as $parameter) {\n            $type = $parameter->getType();\n\n            if (!$type instanceof \\ReflectionNamedType || $type->isBuiltin()) {\n                throw new InvalidArgumentException(\n                    'Extension callbacks must type-hint an extender class.',\n                );\n            }\n\n            $classes[] = $type->getName();\n        }\n\n        foreach ($classes as $class) {\n            if (!$this->app->bound($class)) {\n                $this->app->scoped($class);\n            }\n        }\n\n        if (count($classes) === 1) {\n            $class = $classes[0];\n\n            $this->app->extend($class, function ($instance) use ($callback) {\n                return $callback($instance) ?: $instance;\n            });\n\n            return;\n        }\n\n        $app = $this->app;\n        $executed = false;\n\n        foreach ($classes as $class) {\n            $this->app->extend($class, function ($instance) use (\n                $callback,\n                $classes,\n                $app,\n                &$executed,\n            ) {\n                if (!$executed) {\n                    $executed = true;\n                    $callback(...array_map(fn($class) => $app->make($class), $classes));\n                }\n\n                return $instance;\n            });\n        }\n    }\n}\n"
  },
  {
    "path": "src/Extend/Support/Assets.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Support;\n\nuse Illuminate\\Support\\Facades\\Cache;\nuse Illuminate\\Support\\Facades\\Storage;\n\n/**\n * Support class for asset extenders.\n *\n * Asset extenders use this to bundle, compile, and cache output.\n */\nabstract class Assets\n{\n    private array $assets = [];\n\n    /**\n     * Add an asset to a bundle.\n     *\n     * The `default` and `default-{locale}` bundles are loaded on every page\n     * that uses the Waterhole layout. The `cp` and `cp-{locale}` bundles\n     * are loaded on pages in the Control Panel.\n     */\n    public function add(string $file, string $bundle = 'default'): void\n    {\n        if (!in_array($file, $this->assets[$bundle] ?? [])) {\n            $this->assets[$bundle][] = $file;\n        }\n    }\n\n    /**\n     * Compile the given bundles and return their URLs.\n     */\n    public function urls(array $bundles): array\n    {\n        $files = [];\n\n        foreach ($bundles as $bundle) {\n            if (config('app.debug')) {\n                $this->flushBundle($bundle);\n            }\n\n            $key = $this->cacheKey($bundle);\n\n            $files = array_merge(\n                $files,\n                Cache::rememberForever($key, function () use ($bundle) {\n                    $assets = $this->assets[$bundle] ?? [];\n                    return $assets ? [$this->compile($assets, $bundle)] : [];\n                }),\n            );\n        }\n\n        $disk = config('waterhole.system.assets_disk');\n\n        return array_map(fn($file) => asset(Storage::disk($disk)->url($file)), $files);\n    }\n\n    /**\n     * Flush all bundles so they are regenerated on the next request.\n     */\n    public function flush(): void\n    {\n        foreach ($this->assets as $bundle => $files) {\n            $this->flushBundle($bundle);\n        }\n    }\n\n    /**\n     * Flush a specific bundle so that is it regenerated on the next request.\n     */\n    public function flushBundle(string $bundle): void\n    {\n        $key = $this->cacheKey($bundle);\n\n        if ($files = Cache::get($key)) {\n            Storage::disk(config('waterhole.system.assets_disk'))->delete($files);\n        }\n\n        Cache::forget($key);\n    }\n\n    abstract protected function cacheKey(string $bundle): string;\n\n    abstract protected function filePath(string $filename): string;\n\n    private function compile(array $assets, string $bundle): string\n    {\n        $content = '';\n\n        foreach ($assets as $source) {\n            if (is_callable($source) && ($output = $source())) {\n                $content .= \"$output\\n\";\n            } else {\n                $content .= file_get_contents($source) . \"\\n\";\n            }\n        }\n\n        $hash = substr(sha1($content), 0, 8);\n\n        Storage::disk(config('waterhole.system.assets_disk'))->put(\n            $compiled = $this->filePath(\"$bundle-$hash\"),\n            $content,\n        );\n\n        return $compiled;\n    }\n}\n"
  },
  {
    "path": "src/Extend/Support/Attributes.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Support;\n\nuse Illuminate\\View\\ComponentAttributeBag;\n\n/**\n * Support class for building HTML attributes from extenders.\n *\n * Use this to merge attributes from multiple extenders before rendering.\n */\nclass Attributes extends Set\n{\n    /**\n     * Get the resulting class list for the given model.\n     */\n    public function build($model): array\n    {\n        $attributes = new ComponentAttributeBag();\n\n        foreach ($this->values() as $callback) {\n            $attributes = $attributes->merge($callback($model));\n        }\n\n        return $attributes->getAttributes();\n    }\n}\n"
  },
  {
    "path": "src/Extend/Support/ComponentList.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Support;\n\nuse function Waterhole\\build_components;\n\n/**\n * Ordered list of UI components with helpers to resolve instances.\n *\n * Use this when an extender needs ordered components resolved into view\n * instances.\n */\nclass ComponentList extends OrderedList\n{\n    public function components(array $data = []): array\n    {\n        return build_components($this->items(), $data);\n    }\n}\n"
  },
  {
    "path": "src/Extend/Support/OrderedList.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Support;\n\n/**\n * Ordered list for extenders where item positions matter.\n *\n * Use this to register items with explicit ordering helpers.\n */\nclass OrderedList\n{\n    private array $items = [];\n    private ?array $sortedItems = null;\n\n    /**\n     * Add an item to the list.\n     */\n    public function add($content = null, ?string $key = null, int $position = 0): static\n    {\n        $this->items[$key ?? uniqid()] = compact('content', 'position');\n        $this->sortedItems = null;\n\n        return $this;\n    }\n\n    /**\n     * Replace an existing item in the list.\n     */\n    public function replace(string $key, $content): static\n    {\n        if (isset($this->items[$key])) {\n            $this->items[$key]['content'] = $content;\n            $this->sortedItems = null;\n        }\n\n        return $this;\n    }\n\n    /**\n     * Remove an item from the list.\n     */\n    public function remove(string $key): static\n    {\n        unset($this->items[$key]);\n        $this->sortedItems = null;\n\n        return $this;\n    }\n\n    /**\n     * Get an item's content.\n     */\n    public function get(string $key): mixed\n    {\n        return $this->items[$key]['content'] ?? null;\n    }\n\n    /**\n     * Get an item's position.\n     */\n    public function getPosition(string $key): ?int\n    {\n        return $this->items[$key]['position'] ?? null;\n    }\n\n    /**\n     * Get the resulting list in order.\n     */\n    public function items(): array\n    {\n        if ($this->sortedItems !== null) {\n            return $this->sortedItems;\n        }\n\n        return $this->sortedItems = collect($this->items)\n            ->sortBy('position')\n            ->map(fn($item) => $item['content'])\n            ->all();\n    }\n\n    /**\n     * Get the keys that have been registered.\n     */\n    public function keys(): array\n    {\n        return array_keys($this->items);\n    }\n\n    /**\n     * Get the resulting list.\n     */\n    public function values(): array\n    {\n        return array_values($this->items);\n    }\n}\n"
  },
  {
    "path": "src/Extend/Support/Resource.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Support;\n\n/**\n * JSON:API resource extender container.\n *\n * Holds fields, endpoints, sorts, filters, and scope callbacks for a resource.\n */\nclass Resource\n{\n    public UnorderedList $scope;\n    public UnorderedList $endpoints;\n    public UnorderedList $fields;\n    public UnorderedList $sorts;\n    public UnorderedList $filters;\n\n    public function __construct()\n    {\n        $this->scope = new UnorderedList();\n        $this->endpoints = new UnorderedList();\n        $this->fields = new UnorderedList();\n        $this->sorts = new UnorderedList();\n        $this->filters = new UnorderedList();\n    }\n}\n"
  },
  {
    "path": "src/Extend/Support/Set.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Support;\n\n/**\n * Simple set of values for extenders that only need uniqueness.\n *\n * Use this to register unique items without ordering.\n */\nclass Set\n{\n    private array $values = [];\n\n    /**\n     * Add a value to the set.\n     */\n    public function add(...$values): void\n    {\n        foreach ($values as $value) {\n            if (!in_array($value, $this->values)) {\n                $this->values[] = $value;\n            }\n        }\n    }\n\n    /**\n     * Remove a value from the set.\n     */\n    public function remove(...$values): void\n    {\n        $this->values = array_filter($this->values, fn($value) => !in_array($value, $values));\n    }\n\n    /**\n     * Determines if the set contains a value.\n     */\n    public function contains(...$values): bool\n    {\n        return !array_diff($values, $this->values);\n    }\n\n    /**\n     * Get the values as an array.\n     */\n    public function values(): array\n    {\n        return $this->values;\n    }\n}\n"
  },
  {
    "path": "src/Extend/Support/UnorderedList.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Support;\n\n/**\n * List for extenders where order does not matter.\n *\n * Use this to register items where ordering is not important.\n */\nclass UnorderedList\n{\n    private array $items = [];\n\n    /**\n     * Add an item to the list.\n     */\n    public function add($content = null, ?string $key = null): static\n    {\n        $this->items[$key ?? uniqid()] = $content;\n\n        return $this;\n    }\n\n    /**\n     * Remove an item from the list.\n     */\n    public function remove(string $key): static\n    {\n        unset($this->items[$key]);\n\n        return $this;\n    }\n\n    /**\n     * Get an item in the list.\n     */\n    public function get(string $key): mixed\n    {\n        return $this->items[$key] ?? null;\n    }\n\n    /**\n     * Get whether an item is present in the list.\n     */\n    public function has(string $key): bool\n    {\n        return isset($this->items[$key]);\n    }\n\n    /**\n     * Get the resulting list.\n     */\n    public function items(): array\n    {\n        return $this->items;\n    }\n\n    /**\n     * Get the keys that have been registered.\n     */\n    public function keys(): array\n    {\n        return array_keys($this->items);\n    }\n\n    /**\n     * Get the resulting list.\n     */\n    public function values(): array\n    {\n        return array_values($this->items);\n    }\n}\n"
  },
  {
    "path": "src/Extend/Ui/CommentAttributes.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Ui;\n\nuse Illuminate\\Support\\Arr;\nuse Illuminate\\Support\\Facades\\Auth;\nuse Waterhole\\Extend\\Support\\Attributes;\nuse Waterhole\\Models\\Comment;\n\n/**\n * HTML attributes applied to comment wrappers.\n *\n * Use this extender to add, remove, or reorder components rendered in this\n * region of the UI.\n */\nclass CommentAttributes extends Attributes\n{\n    public function __construct()\n    {\n        $this->add(\n            fn(Comment $comment) => [\n                'class' => Arr::toCssClasses([\n                    'is-unread' => $comment->isUnread(),\n                    'is-read' => $comment->isRead(),\n                    'is-mine' => $comment->user_id === Auth::id(),\n                    'is-answer' => $comment->isAnswer(),\n                    'has-replies' => $comment->reply_count,\n                    'is-removed' => $comment->trashed(),\n                ]),\n            ],\n        );\n    }\n}\n"
  },
  {
    "path": "src/Extend/Ui/CommentComponent.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Ui;\n\nuse Waterhole\\Extend\\Support\\ComponentList;\nuse Waterhole\\View\\Components\\CommentAnswerBadge;\nuse Waterhole\\View\\Components\\CommentMarkAsAnswer;\nuse Waterhole\\View\\Components\\CommentReactions;\nuse Waterhole\\View\\Components\\CommentReplies;\nuse Waterhole\\View\\Components\\CommentReplyButton;\n\n/**\n * Components rendered in comment headers, footers, and buttons.\n *\n * Use this extender to add, remove, or reorder components rendered in this\n * region of the UI.\n */\nclass CommentComponent\n{\n    public ComponentList $header;\n    public ComponentList $footer;\n    public ComponentList $buttons;\n\n    public function __construct()\n    {\n        $this->header = (new ComponentList())->add(CommentAnswerBadge::class, 'answer');\n\n        $this->footer = (new ComponentList())\n            ->add(CommentReactions::class, 'reactions')\n            ->add(CommentReplies::class, 'replies');\n\n        $this->buttons = (new ComponentList())\n            ->add(CommentMarkAsAnswer::class, 'answer')\n            ->add(CommentReplyButton::class, 'reply');\n    }\n}\n"
  },
  {
    "path": "src/Extend/Ui/CpAlerts.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Ui;\n\nuse Waterhole\\Extend\\Support\\ComponentList;\n\n/**\n * Alert components rendered on the control panel dashboard.\n *\n * Use this extender to add, remove, or reorder components rendered in this\n * region of the UI.\n */\nclass CpAlerts extends ComponentList\n{\n    public function __construct()\n    {\n        if (config('app.debug')) {\n            $this->add(null, 'debug');\n        }\n\n        if (!config('mail.from.address')) {\n            $this->add(null, 'mail');\n        }\n    }\n}\n"
  },
  {
    "path": "src/Extend/Ui/CpNav.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Ui;\n\nuse Waterhole\\Extend\\Support\\ComponentList;\nuse Waterhole\\View\\Components\\NavLink;\n\n/**\n * Navigation links rendered in the control panel sidebar.\n *\n * Use this extender to add, remove, or reorder components rendered in this\n * region of the UI.\n */\nclass CpNav extends ComponentList\n{\n    public function __construct()\n    {\n        $this->add(\n            fn() => new NavLink(\n                label: __('waterhole::cp.dashboard-title'),\n                icon: 'tabler-report-analytics',\n                route: 'waterhole.cp.dashboard',\n            ),\n            'dashboard',\n        );\n\n        $this->add(\n            fn() => new NavLink(\n                label: __('waterhole::cp.structure-title'),\n                icon: 'tabler-layout-list',\n                route: 'waterhole.cp.structure',\n                active: request()->routeIs('waterhole.cp.structure*'),\n            ),\n            'structure',\n        );\n\n        $this->add(\n            fn() => new NavLink(\n                label: __('waterhole::cp.taxonomies-title'),\n                icon: 'tabler-tags',\n                route: 'waterhole.cp.taxonomies.index',\n                active: request()->routeIs('waterhole.cp.taxonomies*'),\n            ),\n            'taxonomies',\n        );\n\n        $this->add(\n            fn() => new NavLink(\n                label: __('waterhole::cp.users-title'),\n                icon: 'tabler-user',\n                route: 'waterhole.cp.users.index',\n                active: request()->routeIs('waterhole.cp.users*'),\n            ),\n            'users',\n        );\n\n        $this->add(\n            fn() => new NavLink(\n                label: __('waterhole::cp.groups-title'),\n                icon: 'tabler-users',\n                route: 'waterhole.cp.groups.index',\n                active: request()->routeIs('waterhole.cp.groups*'),\n            ),\n            'groups',\n        );\n\n        $this->add(\n            fn() => new NavLink(\n                label: __('waterhole::cp.reactions-title'),\n                icon: 'tabler-mood-smile',\n                route: 'waterhole.cp.reaction-sets.index',\n                active: request()->routeIs('waterhole.cp.reaction*'),\n            ),\n            'reactions',\n        );\n    }\n}\n"
  },
  {
    "path": "src/Extend/Ui/DocumentHead.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Ui;\n\nuse Waterhole\\Extend\\Support\\ComponentList;\n\n/**\n * Components rendered inside the document <head>.\n *\n * Use this extender to add, remove, or reorder components rendered in this\n * region of the UI.\n */\nclass DocumentHead extends ComponentList {}\n"
  },
  {
    "path": "src/Extend/Ui/IndexPage.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Ui;\n\nuse Waterhole\\Extend\\Support\\ComponentList;\nuse Waterhole\\View\\Components\\IndexFooterLanguage;\nuse Waterhole\\View\\Components\\IndexNav;\n\n/**\n * Sidebar and footer components for index and channel pages.\n *\n * Use this extender to add, remove, or reorder components rendered in this\n * region of the UI.\n */\nclass IndexPage\n{\n    public ComponentList $sidebar;\n    public ComponentList $footer;\n\n    public function __construct()\n    {\n        $this->sidebar = (new ComponentList())->add(IndexNav::class, 'nav');\n\n        $this->footer = (new ComponentList())->add(IndexFooterLanguage::class, 'language');\n    }\n}\n"
  },
  {
    "path": "src/Extend/Ui/Layout.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Ui;\n\nuse Waterhole\\Extend\\Support\\ComponentList;\nuse Waterhole\\View\\Components\\EmailVerification;\nuse Waterhole\\View\\Components\\Header;\nuse Waterhole\\View\\Components\\HeaderBreadcrumb;\nuse Waterhole\\View\\Components\\HeaderGuest;\nuse Waterhole\\View\\Components\\HeaderModeration;\nuse Waterhole\\View\\Components\\HeaderNotifications;\nuse Waterhole\\View\\Components\\HeaderSearch;\nuse Waterhole\\View\\Components\\HeaderTitle;\nuse Waterhole\\View\\Components\\HeaderUser;\nuse Waterhole\\View\\Components\\Spacer;\nuse Waterhole\\View\\Components\\ThemeSelector;\n\n/**\n * Top-level layout slots (header, before, after) for forum pages.\n *\n * Use this extender to add, remove, or reorder components rendered in this\n * region of the UI.\n */\nclass Layout\n{\n    public ComponentList $header;\n    public ComponentList $before;\n    public ComponentList $after;\n\n    public function __construct()\n    {\n        $this->header = (new ComponentList())\n            ->add(HeaderTitle::class, 'title')\n            ->add(HeaderBreadcrumb::class, 'breadcrumb')\n            ->add(Spacer::class, 'spacer')\n            ->add(HeaderSearch::class, 'search')\n            ->add(ThemeSelector::class, 'theme')\n            ->add(HeaderModeration::class, 'moderation')\n            ->add(HeaderNotifications::class, 'notifications')\n            ->add(HeaderGuest::class, 'guest')\n            ->add(HeaderUser::class, 'user');\n\n        $this->before = (new ComponentList())\n            ->add(Header::class, 'header')\n            ->add(EmailVerification::class, 'email-verification');\n\n        $this->after = new ComponentList();\n    }\n}\n"
  },
  {
    "path": "src/Extend/Ui/LoginPage.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Ui;\n\nuse Waterhole\\Extend\\Support\\ComponentList;\nuse Waterhole\\View\\Components\\AuthButtons;\n\n/**\n * Components rendered on the login page.\n *\n * Use this extender to add, remove, or reorder components rendered in this\n * region of the UI.\n */\nclass LoginPage extends ComponentList\n{\n    public function __construct()\n    {\n        $this->add(AuthButtons::class, 'auth-buttons');\n\n        if (config('waterhole.auth.password_enabled', true)) {\n            $this->add(null, 'email');\n            $this->add(null, 'password');\n            $this->add(null, 'submit');\n            $this->add(null, 'sign-up-link');\n        }\n    }\n}\n"
  },
  {
    "path": "src/Extend/Ui/PostAttributes.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Ui;\n\nuse Illuminate\\Support\\Arr;\nuse Illuminate\\Support\\Facades\\Auth;\nuse Waterhole\\Extend\\Support\\Attributes;\nuse Waterhole\\Models\\Post;\n\n/**\n * HTML attributes applied to post wrappers.\n *\n * Use this extender to add, remove, or reorder components rendered in this\n * region of the UI.\n */\nclass PostAttributes extends Attributes\n{\n    public function __construct()\n    {\n        $this->add(\n            fn(Post $post) => [\n                'class' => Arr::toCssClasses([\n                    'is-unread' => $post->isUnread(),\n                    'is-read' => $post->isRead(),\n                    'is-new' => $post->isNew(),\n                    'is-mine' => $post->user_id === Auth::id(),\n                    'is-followed' => $post->isFollowed(),\n                    'is-ignored' => $post->isIgnored(),\n                    'has-replies' => $post->comment_count,\n                    'is-locked' => $post->is_locked,\n                ]),\n                'data-channel' => $post->channel->slug,\n            ],\n        );\n    }\n}\n"
  },
  {
    "path": "src/Extend/Ui/PostFeed.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Ui;\n\nuse Waterhole\\Extend\\Support\\ComponentList;\nuse Waterhole\\View\\Components\\FeedFilters;\nuse Waterhole\\View\\Components\\FeedTopPeriod;\nuse Waterhole\\View\\Components\\IndexCreatePost;\nuse Waterhole\\View\\Components\\PostFeedChannel;\nuse Waterhole\\View\\Components\\PostFeedPinned;\nuse Waterhole\\View\\Components\\PostFeedToolbar;\nuse Waterhole\\View\\Components\\Spacer;\nuse Waterhole\\View\\Components\\TagsFilter;\n\n/**\n * Header and toolbar components rendered with post feeds.\n *\n * Use this extender to add, remove, or reorder components rendered in this\n * region of the UI.\n */\nclass PostFeed\n{\n    public ComponentList $header;\n    public ComponentList $toolbar;\n\n    public function __construct()\n    {\n        $this->header = (new ComponentList())\n            ->add(PostFeedChannel::class, 'channel')\n            ->add(PostFeedPinned::class, 'pinned')\n            ->add(PostFeedToolbar::class, 'toolbar');\n\n        $this->toolbar = (new ComponentList())\n            ->add(FeedFilters::class, 'filters')\n            ->add(FeedTopPeriod::class, 'top-period')\n            ->add(Spacer::class, 'spacer')\n            ->add(TagsFilter::class, 'tags')\n            ->add(IndexCreatePost::class, 'create-post');\n    }\n}\n"
  },
  {
    "path": "src/Extend/Ui/PostFooter.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Ui;\n\nuse Waterhole\\Extend\\Support\\ComponentList;\nuse Waterhole\\View\\Components\\PostReactions;\nuse Waterhole\\View\\Components\\PostReplies;\n\n/**\n * Components rendered in a post footer.\n *\n * Use this extender to add, remove, or reorder components rendered in this\n * region of the UI.\n */\nclass PostFooter extends ComponentList\n{\n    public function __construct()\n    {\n        $this->add(PostReactions::class, 'reactions');\n        $this->add(PostReplies::class, 'replies');\n    }\n}\n"
  },
  {
    "path": "src/Extend/Ui/PostListItem.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Ui;\n\nuse Waterhole\\Extend\\Support\\ComponentList;\nuse Waterhole\\View\\Components\\PostActivity;\nuse Waterhole\\View\\Components\\PostAnswered;\nuse Waterhole\\View\\Components\\PostChannel;\nuse Waterhole\\View\\Components\\PostLocked;\nuse Waterhole\\View\\Components\\PostNotifications;\nuse Waterhole\\View\\Components\\PostReactionsCondensed;\nuse Waterhole\\View\\Components\\PostReplies;\nuse Waterhole\\View\\Components\\PostTagsSummary;\nuse Waterhole\\View\\Components\\PostTrash;\nuse Waterhole\\View\\Components\\PostUnread;\n\n/**\n * Components rendered for posts in the list layout.\n *\n * Use this extender to add, remove, or reorder components rendered in this\n * region of the UI.\n */\nclass PostListItem\n{\n    public ComponentList $info;\n    public ComponentList $secondary;\n\n    public function __construct()\n    {\n        $this->info = (new ComponentList())\n            ->add(PostUnread::class, 'unread')\n            ->add(PostTrash::class, 'trash')\n            ->add(PostChannel::class, 'channel')\n            ->add(PostAnswered::class, 'answered')\n            ->add(PostLocked::class, 'locked')\n            ->add(PostNotifications::class, 'notifications')\n            ->add(PostActivity::class, 'activity');\n\n        $this->secondary = (new ComponentList())\n            ->add(PostTagsSummary::class, 'tags')\n            ->add(PostReactionsCondensed::class, 'reactions')\n            ->add(PostReplies::class, 'replies');\n    }\n}\n"
  },
  {
    "path": "src/Extend/Ui/PostPage.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Ui;\n\nuse Waterhole\\Extend\\Support\\ComponentList;\nuse Waterhole\\View\\Components\\CommentsLocked;\nuse Waterhole\\View\\Components\\PostAnswer;\nuse Waterhole\\View\\Components\\PostAttribution;\nuse Waterhole\\View\\Components\\PostChannel;\nuse Waterhole\\View\\Components\\PostTagsSummary;\nuse Waterhole\\View\\Components\\PostTitle;\n\n/**\n * Components rendered on the post show page.\n *\n * Use this extender to add, remove, or reorder components rendered in this\n * region of the UI.\n */\nclass PostPage\n{\n    public ComponentList $header;\n    public ComponentList $sidebar;\n    public ComponentList $middle;\n    public ComponentList $bottom;\n\n    public function __construct()\n    {\n        $this->header = (new ComponentList())\n            ->add(PostChannel::class, 'channel')\n            ->add(PostTagsSummary::class, 'tags')\n            ->add(PostAttribution::class, 'attribution')\n            ->add(PostTitle::class, 'title');\n\n        $this->sidebar = new ComponentList();\n\n        $this->middle = (new ComponentList())->add(PostAnswer::class, 'answer');\n\n        $this->bottom = (new ComponentList())->add(CommentsLocked::class, 'locked');\n    }\n}\n"
  },
  {
    "path": "src/Extend/Ui/Preferences.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Ui;\n\nuse Waterhole\\Extend\\Support\\ComponentList;\n\n/**\n * Components rendered on the user preferences pages.\n *\n * Use this extender to add, remove, or reorder components on preferences pages.\n */\nclass Preferences\n{\n    public ComponentList $account;\n\n    public function __construct()\n    {\n        $this->account = (new ComponentList())\n            ->add(null, 'name')\n            ->add(null, 'email')\n            ->add(null, 'password')\n            ->add(null, 'delete');\n    }\n}\n"
  },
  {
    "path": "src/Extend/Ui/TextEditor.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Ui;\n\nuse Illuminate\\Support\\Facades\\Auth;\nuse Waterhole\\Extend\\Support\\ComponentList;\nuse Waterhole\\View\\Components\\TextEditorButton;\nuse Waterhole\\View\\Components\\TextEditorEmojiButton;\n\n/**\n * Toolbar buttons and controls for the text editor UI.\n *\n * Use this extender to add, remove, or reorder components rendered in this\n * region of the UI.\n */\nclass TextEditor extends ComponentList\n{\n    public function __construct()\n    {\n        $this->add(\n            fn(string $id) => new TextEditorButton(\n                icon: 'tabler-heading',\n                label: __('waterhole::system.text-editor-heading'),\n                id: $id,\n                format: 'header2',\n            ),\n            'heading',\n        );\n\n        $this->add(\n            fn(string $id) => new TextEditorButton(\n                icon: 'tabler-bold',\n                label: __('waterhole::system.text-editor-bold'),\n                id: $id,\n                format: 'bold',\n                hotkey: 'Meta+b',\n            ),\n            'bold',\n        );\n\n        $this->add(\n            fn(string $id) => new TextEditorButton(\n                icon: 'tabler-italic',\n                label: __('waterhole::system.text-editor-italic'),\n                id: $id,\n                format: 'italic',\n                hotkey: 'Meta+i',\n            ),\n            'italic',\n        );\n\n        $this->add(\n            fn(string $id) => new TextEditorButton(\n                icon: 'tabler-quote',\n                label: __('waterhole::system.text-editor-quote'),\n                id: $id,\n                format: 'blockquote',\n                hotkey: 'Meta+Shift+.',\n            ),\n            'quote',\n        );\n\n        $this->add(\n            fn(string $id) => new TextEditorButton(\n                icon: 'tabler-code',\n                label: __('waterhole::system.text-editor-code'),\n                id: $id,\n                format: 'code',\n                hotkey: 'Meta+e',\n            ),\n            'code',\n        );\n\n        $this->add(\n            fn(string $id) => new TextEditorButton(\n                icon: 'tabler-link',\n                label: __('waterhole::system.text-editor-link'),\n                id: $id,\n                format: 'link',\n                hotkey: 'Meta+k',\n            ),\n            'link',\n        );\n\n        $this->add(\n            fn(string $id) => new TextEditorButton(\n                icon: 'tabler-list',\n                label: __('waterhole::system.text-editor-bulleted-list'),\n                id: $id,\n                format: 'unorderedList',\n                hotkey: 'Meta+Shift+8',\n            ),\n            'bulletedList',\n        );\n\n        $this->add(\n            fn(string $id) => new TextEditorButton(\n                icon: 'tabler-list-numbers',\n                label: __('waterhole::system.text-editor-numbered-list'),\n                id: $id,\n                format: 'orderedList',\n                hotkey: 'Meta+Shift+7',\n            ),\n            'numberedList',\n        );\n\n        $this->add(\n            fn(string $id) => new TextEditorButton(\n                icon: 'tabler-at',\n                label: __('waterhole::system.text-editor-mention'),\n                id: $id,\n                format: '{\"prefix\":\"@\"}',\n            ),\n            'mention',\n        );\n\n        $this->add(fn() => new TextEditorEmojiButton(), 'emoji');\n\n        $this->add(\n            fn(string $id) => Auth::check()\n                ? (new TextEditorButton(\n                    icon: 'tabler-paperclip',\n                    label: __('waterhole::system.text-editor-attachment'),\n                ))->withAttributes(['data-action' => 'uploads#chooseFiles'])\n                : null,\n            'attachment',\n        );\n    }\n}\n"
  },
  {
    "path": "src/Extend/Ui/UserInfo.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Ui;\n\nuse Waterhole\\Extend\\Support\\ComponentList;\nuse Waterhole\\View\\Components\\UserGroups;\nuse Waterhole\\View\\Components\\UserJoined;\nuse Waterhole\\View\\Components\\UserLastSeen;\nuse Waterhole\\View\\Components\\UserLocation;\nuse Waterhole\\View\\Components\\UserWebsite;\n\n/**\n * Components rendered in the user profile info sidebar.\n *\n * Use this extender to add, remove, or reorder components rendered in this\n * region of the UI.\n */\nclass UserInfo extends ComponentList\n{\n    public function __construct()\n    {\n        $this->add(UserGroups::class, 'groups');\n        $this->add(UserLocation::class, 'location');\n        $this->add(UserWebsite::class, 'website');\n        $this->add(UserJoined::class, 'joined');\n        $this->add(UserLastSeen::class, 'lastSeen');\n    }\n}\n"
  },
  {
    "path": "src/Extend/Ui/UserMenu.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Ui;\n\nuse Illuminate\\Support\\Facades\\Auth;\nuse Waterhole\\Extend\\Support\\ComponentList;\nuse Waterhole\\View\\Components\\MenuDivider;\nuse Waterhole\\View\\Components\\MenuItem;\n\n/**\n * Items rendered in the user menu dropdown.\n *\n * Use this extender to add, remove, or reorder components rendered in this\n * region of the UI.\n */\nclass UserMenu extends ComponentList\n{\n    public function __construct()\n    {\n        $this->add(\n            fn() => new MenuItem(\n                icon: 'tabler-user',\n                label: __('waterhole::user.profile-link'),\n                href: Auth::user()->url,\n            ),\n            'profile',\n        );\n\n        $this->add(\n            fn() => new MenuItem(\n                icon: 'tabler-settings',\n                label: __('waterhole::user.preferences-link'),\n                href: route('waterhole.preferences'),\n            ),\n            'preferences',\n        );\n\n        $this->add(MenuDivider::class, 'divider');\n\n        $this->add(\n            fn() => Auth::user()->can('waterhole.administrate')\n                ? new MenuItem(\n                    icon: 'tabler-tool',\n                    label: __('waterhole::user.administration-link'),\n                    href: route('waterhole.cp.dashboard'),\n                )\n                : null,\n            'administration',\n        );\n    }\n}\n"
  },
  {
    "path": "src/Extend/Ui/UserNav.php",
    "content": "<?php\n\nnamespace Waterhole\\Extend\\Ui;\n\nuse Illuminate\\Support\\Facades\\Auth;\nuse Waterhole\\Extend\\Support\\ComponentList;\nuse Waterhole\\Models\\User;\nuse Waterhole\\View\\Components\\NavHeading;\nuse Waterhole\\View\\Components\\NavLink;\n\n/**\n * Links rendered in the user profile navigation.\n *\n * Use this extender to add, remove, or reorder components rendered in this\n * region of the UI.\n */\nclass UserNav extends ComponentList\n{\n    public function __construct()\n    {\n        $this->add(\n            fn(User $user) => new NavLink(\n                label: __('waterhole::user.posts-link'),\n                icon: 'tabler-layout-list',\n                badge: $user->posts_count,\n                href: route('waterhole.user.posts', compact('user')),\n                active: request()->routeIs('waterhole.user.posts'),\n            ),\n            'posts',\n        );\n\n        $this->add(\n            fn(User $user) => new NavLink(\n                label: __('waterhole::user.comments-link'),\n                icon: 'tabler-messages',\n                badge: $user->comments_count,\n                href: route('waterhole.user.comments', compact('user')),\n                active: request()->routeIs('waterhole.user.comments'),\n            ),\n            'comments',\n        );\n\n        $this->add(\n            fn(User $user) => $user->is(Auth::user())\n                ? new NavHeading(__('waterhole::user.preferences-heading'))\n                : null,\n            'preferences',\n        );\n\n        $this->add(\n            fn(User $user) => $user->is(Auth::user())\n                ? new NavLink(\n                    label: __('waterhole::user.account-settings-link'),\n                    icon: 'tabler-fingerprint',\n                    route: 'waterhole.preferences.account',\n                )\n                : null,\n            'account',\n        );\n\n        $this->add(\n            fn(User $user) => $user->is(Auth::user())\n                ? new NavLink(\n                    label: __('waterhole::user.edit-profile-link'),\n                    icon: 'tabler-user-circle',\n                    route: 'waterhole.preferences.profile',\n                )\n                : null,\n            'profile',\n        );\n\n        $this->add(\n            fn(User $user) => $user->is(Auth::user())\n                ? new NavLink(\n                    label: __('waterhole::user.notification-preferences-link'),\n                    icon: 'tabler-bell',\n                    route: 'waterhole.preferences.notifications',\n                )\n                : null,\n            'notifications',\n        );\n    }\n}\n"
  },
  {
    "path": "src/Feed/CommentFeed.php",
    "content": "<?php\n\nnamespace Waterhole\\Feed;\n\nuse Closure;\nuse Illuminate\\Http\\Request;\nuse Waterhole\\Extend;\nuse Waterhole\\Models\\Comment;\n\nclass CommentFeed extends Feed\n{\n    public function __construct(Request $request, array $filters, ?Closure $scope = null)\n    {\n        $query = Comment::query();\n\n        if ($scope) {\n            $scope($query);\n        }\n\n        $extender = resolve(Extend\\Query\\CommentQuery::class);\n\n        foreach ([...$extender->values(), ...$extender->feed->values()] as $scope) {\n            $scope($query);\n        }\n\n        parent::__construct($request, $query, $filters);\n    }\n}\n"
  },
  {
    "path": "src/Feed/Feed.php",
    "content": "<?php\n\nnamespace Waterhole\\Feed;\n\nuse Illuminate\\Contracts\\Pagination\\CursorPaginator;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Support\\Collection;\nuse RuntimeException;\nuse UnexpectedValueException;\nuse Waterhole\\Filters\\Filter;\n\n/**\n * A Feed is a paginated query that can have Filters applied to it.\n */\nclass Feed\n{\n    public Collection $filters;\n    public Filter $currentFilter;\n\n    public function __construct(\n        protected Request $request,\n        protected Builder $query,\n        array $filters,\n    ) {\n        $this->filters = collect($filters);\n\n        if (!$this->filters->count()) {\n            throw new RuntimeException('A feed must have at least 1 filter');\n        }\n\n        if ($query = $this->request->query('filter')) {\n            $currentFilter = $this->filters->first(\n                fn(Filter $filter) => $filter->handle() === $query,\n            );\n\n            if (!$currentFilter) {\n                abort(404);\n            }\n\n            $this->currentFilter = $currentFilter;\n        } else {\n            $this->currentFilter = $this->filters[0];\n        }\n    }\n\n    /**\n     * Get the paginated feed items.\n     */\n    public function items(): CursorPaginator\n    {\n        $query = $this->query->clone();\n\n        $this->currentFilter->apply($query);\n\n        // Crawlers can sometimes end up remembering an invalid pagination\n        // cursor, which will cause a 500 error - make it a 400 error instead.\n        try {\n            return $query->cursorPaginate();\n        } catch (UnexpectedValueException) {\n            abort(400);\n        }\n    }\n}\n"
  },
  {
    "path": "src/Feed/PostFeed.php",
    "content": "<?php\n\nnamespace Waterhole\\Feed;\n\nuse Closure;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Support\\Collection;\nuse Waterhole\\Extend\\Query\\PostFeedQuery;\nuse Waterhole\\Layouts\\Layout;\nuse Waterhole\\Models\\Post;\n\nclass PostFeed extends Feed\n{\n    public Collection $layouts;\n\n    public function __construct(\n        Request $request,\n        array $filters,\n        public Layout $layout,\n        ?Closure $scope = null,\n    ) {\n        $query = Post::query();\n\n        if ($scope) {\n            $scope($query);\n        }\n\n        foreach (resolve(PostFeedQuery::class)->values() as $scope) {\n            $scope($query);\n        }\n\n        $this->layout->scope($query);\n\n        parent::__construct($request, $query, $filters);\n    }\n}\n"
  },
  {
    "path": "src/Filters/Alphabetical.php",
    "content": "<?php\n\nnamespace Waterhole\\Filters;\n\n/**\n * A filter that sorts results by the `title` column alphabetically.\n */\nclass Alphabetical extends Filter\n{\n    public function label(): string\n    {\n        return __('waterhole::forum.filter-alphabetical');\n    }\n\n    public function apply($query): void\n    {\n        $query->orderBy('title');\n    }\n}\n"
  },
  {
    "path": "src/Filters/Filter.php",
    "content": "<?php\n\nnamespace Waterhole\\Filters;\n\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Support\\Str;\nuse ReflectionClass;\n\n/**\n * Base class for a Filter.\n *\n * A filter is a set of filtering or sorting criteria that can be applied to a\n * feed query, like \"Newest\" or \"Top\".\n *\n * Define a new filter by extending this class and implementing the methods.\n * Use the `PostFilters` and `CommentFilters` extenders to register a filter\n * for the appropriate feed types, making them available for configuration.\n */\nabstract class Filter\n{\n    /**\n     * The handle for the filter, used in query parameters.\n     */\n    public function handle(): string\n    {\n        return Str::kebab((new ReflectionClass($this))->getShortName());\n    }\n\n    /**\n     * The text label for the filter.\n     */\n    abstract public function label(): string;\n\n    /**\n     * Apply the filter to the feed query builder.\n     */\n    abstract public function apply(Builder $query): void;\n}\n"
  },
  {
    "path": "src/Filters/Following.php",
    "content": "<?php\n\nnamespace Waterhole\\Filters;\n\nuse Illuminate\\Database\\Eloquent\\Builder;\n\n/**\n * A filter that shows items that the user is following, with the most recently\n * followed at the top.\n */\nclass Following extends Filter\n{\n    public function label(): string\n    {\n        return __('waterhole::forum.filter-following');\n    }\n\n    public function apply(Builder $query): void\n    {\n        $query->following()->leftJoinRelation('userState')->latest('followed_at');\n    }\n}\n"
  },
  {
    "path": "src/Filters/Ignoring.php",
    "content": "<?php\n\nnamespace Waterhole\\Filters;\n\nuse Illuminate\\Database\\Eloquent\\Builder;\n\n/**\n * A filter that shows items that the user is ignoring, with the most recently\n * ignored at the top.\n */\nclass Ignoring extends Filter\n{\n    public const EXCLUDE_IGNORED_SCOPE = 'excludeIgnored';\n\n    public function label(): string\n    {\n        return __('waterhole::forum.filter-ignoring');\n    }\n\n    public function apply(Builder $query): void\n    {\n        $query\n            ->withoutGlobalScope(static::EXCLUDE_IGNORED_SCOPE)\n            ->ignoring()\n            ->leftJoinRelation('userState');\n    }\n}\n"
  },
  {
    "path": "src/Filters/Latest.php",
    "content": "<?php\n\nnamespace Waterhole\\Filters;\n\nuse Illuminate\\Database\\Eloquent\\Builder;\n\n/**\n * A filter that sorts results by most recently active.\n */\nclass Latest extends Filter\n{\n    public function label(): string\n    {\n        return __('waterhole::forum.filter-latest');\n    }\n\n    public function apply(Builder $query): void\n    {\n        $query->latest('last_activity_at');\n    }\n}\n"
  },
  {
    "path": "src/Filters/Newest.php",
    "content": "<?php\n\nnamespace Waterhole\\Filters;\n\nuse Illuminate\\Database\\Eloquent\\Builder;\n\n/**\n * A filter that sorts results by most recently created.\n */\nclass Newest extends Filter\n{\n    public function label(): string\n    {\n        return __('waterhole::forum.filter-newest');\n    }\n\n    public function apply(Builder $query): void\n    {\n        $query->latest();\n    }\n}\n"
  },
  {
    "path": "src/Filters/Oldest.php",
    "content": "<?php\n\nnamespace Waterhole\\Filters;\n\nuse Illuminate\\Database\\Eloquent\\Builder;\n\n/**\n * A filter that sorts results by least recently created.\n */\nclass Oldest extends Filter\n{\n    public function label(): string\n    {\n        return __('waterhole::forum.filter-oldest');\n    }\n\n    public function apply(Builder $query): void\n    {\n        $query->oldest();\n    }\n}\n"
  },
  {
    "path": "src/Filters/Top.php",
    "content": "<?php\n\nnamespace Waterhole\\Filters;\n\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Waterhole\\Models\\Post;\n\n/**\n * A filter that sorts results by their reaction score, with an optional\n * scoping by time period.\n */\nclass Top extends Filter\n{\n    public const PERIODS = ['year', 'quarter', 'month', 'week', 'day'];\n\n    public function label(): string\n    {\n        return __('waterhole::forum.filter-top');\n    }\n\n    public function apply(Builder $query): void\n    {\n        $query->orderByDesc('score');\n\n        if ($query->getModel() instanceof Post) {\n            $query->orderByDesc('comment_count');\n        }\n\n        if ($period = $this->currentPeriod()) {\n            $method = 'sub' . ucfirst($period);\n\n            $query->where('created_at', '>', now()->utc()->$method());\n        }\n    }\n\n    /**\n     * Get the currently selected time period.\n     */\n    public function currentPeriod(): ?string\n    {\n        if (in_array($period = request()->query('period'), static::PERIODS)) {\n            return $period;\n        }\n\n        return null;\n    }\n}\n"
  },
  {
    "path": "src/Filters/Trash.php",
    "content": "<?php\n\nnamespace Waterhole\\Filters;\n\nuse Illuminate\\Database\\Eloquent\\Builder;\n\n/**\n * A filter that ...\n */\nclass Trash extends Filter\n{\n    public const EXCLUDE_TRASHED_SCOPE = 'excludeTrashed';\n\n    public function label(): string\n    {\n        return __('waterhole::forum.filter-trash');\n    }\n\n    public function apply(Builder $query): void\n    {\n        $query\n            ->withoutGlobalScope(static::EXCLUDE_TRASHED_SCOPE)\n            ->onlyTrashed()\n            ->latest('deleted_at');\n    }\n}\n"
  },
  {
    "path": "src/Filters/Trending.php",
    "content": "<?php\n\nnamespace Waterhole\\Filters;\n\nuse Illuminate\\Database\\Eloquent\\Builder;\n\n/**\n * A filter that ...\n */\nclass Trending extends Filter\n{\n    public function label(): string\n    {\n        return __('waterhole::forum.filter-trending');\n    }\n\n    public function apply(Builder $query): void\n    {\n        $query->orderByDesc('hotness');\n    }\n}\n"
  },
  {
    "path": "src/Formatter/Context.php",
    "content": "<?php\n\nnamespace Waterhole\\Formatter;\n\nuse Waterhole\\Models\\Model;\nuse Waterhole\\Models\\User;\n\nclass Context\n{\n    public function __construct(public ?Model $model = null, public ?User $user = null) {}\n}\n"
  },
  {
    "path": "src/Formatter/FormatExternalLinks.php",
    "content": "<?php\n\nnamespace Waterhole\\Formatter;\n\nuse s9e\\TextFormatter\\Configurator;\nuse s9e\\TextFormatter\\Renderer;\nuse s9e\\TextFormatter\\Utils;\n\nclass FormatExternalLinks\n{\n    /**\n     * Formatter configuration callback.\n     */\n    public static function configure(Configurator $config): void\n    {\n        $dom = $config->tags['URL']->template->asDOM();\n\n        foreach ($dom->getElementsByTagName('a') as $a) {\n            $a->prependXslCopyOf('@target');\n            $a->prependXslCopyOf('@rel');\n        }\n\n        $dom->saveChanges();\n    }\n\n    /**\n     * Formatter rendering callback.\n     */\n    public static function rendering(Renderer $renderer, string &$xml, ?Context $context): void\n    {\n        $baseUrl = route('waterhole.home');\n        $basePath = '/' . config('waterhole.forum.path');\n        $nofollowAllowlist = config('waterhole.seo.nofollow_allow', []);\n        $nofollowRel = config('waterhole.seo.nofollow_rel', 'nofollow ugc');\n\n        $xml = Utils::replaceAttributes($xml, 'URL', function ($attributes) use (\n            $baseUrl,\n            $basePath,\n            $nofollowAllowlist,\n            $nofollowRel,\n        ) {\n            $url = $attributes['url'] ?? '';\n\n            if (!str_starts_with($url, $baseUrl) && !str_starts_with($url, $basePath)) {\n                $attributes['target'] = '_blank';\n\n                if (!static::isAllowlisted($url, $nofollowAllowlist)) {\n                    $attributes['rel'] = $nofollowRel;\n                }\n            }\n\n            return $attributes;\n        });\n    }\n\n    /**\n     * Determine whether an external URL should be exempt from nofollow.\n     */\n    private static function isAllowlisted(string $url, array $allowlist): bool\n    {\n        $host =\n            parse_url($url, PHP_URL_HOST) ?:\n            (str_starts_with($url, '//')\n                ? parse_url(\"https:$url\", PHP_URL_HOST)\n                : null);\n\n        if (!$host) {\n            return false;\n        }\n\n        $host = strtolower($host);\n\n        foreach ($allowlist as $allowed) {\n            $allowed = strtolower($allowed);\n\n            if ($host === $allowed || str_ends_with($host, '.' . $allowed)) {\n                return true;\n            }\n        }\n\n        return false;\n    }\n}\n"
  },
  {
    "path": "src/Formatter/FormatMentions.php",
    "content": "<?php\n\nnamespace Waterhole\\Formatter;\n\nuse s9e\\TextFormatter\\Configurator;\nuse s9e\\TextFormatter\\Parser\\Tag;\nuse s9e\\TextFormatter\\Renderer;\nuse s9e\\TextFormatter\\Utils;\nuse Waterhole\\Models\\User;\nuse function Waterhole\\username;\n\n/**\n * User mention parsing utilities.\n *\n * These allow users to be mentioned by their name using the @ prefix.\n */\nabstract class FormatMentions\n{\n    public const TAG_NAME = 'MENTION';\n\n    /**\n     * Formatter configuration callback.\n     *\n     * Set up a regular expression to parse plain-text mentions into an XML tag\n     * with a `name` attribute. Also apply a filter to the tag, to look up the\n     * username and populate an `id` attribute on the tag – or remove it if\n     * the user doesn't exist.\n     */\n    public static function configure(Configurator $config): void\n    {\n        $config->rendering->parameters['MENTION_URL'] = rtrim(\n            route('waterhole.users.show', ['user' => '_']),\n            '_',\n        );\n\n        $config->Preg->match('/\\B@(?<name>[^\\s]*[^\\s\\.,!?:;)\"\\'])/i', static::TAG_NAME);\n\n        $tag = $config->tags->add(static::TAG_NAME);\n        $tag->attributes->add('name');\n        $tag->attributes->add('id');\n        $tag->filterChain->prepend([static::class, 'filterMention']);\n\n        // data-hovercard-type=\"user\" is necessary to make @github/paste-markdown\n        // prevent mentions being converted into Markdown links when pasted.\n        $tag->template = <<<'xsl'\n            <xsl:choose>\n                <xsl:when test=\"@id\">\n                    <a href=\"{$MENTION_URL}{@id}\" data-user-id=\"{@id}\" data-hovercard-type=\"user\">\n                        <xsl:attribute name=\"class\">\n                            mention <xsl:if test=\"@id and @id = $USER_ID\">mention--self</xsl:if>\n                        </xsl:attribute>\n                        @<xsl:value-of select=\"@name\"/>\n                    </a>\n                </xsl:when>\n                <xsl:otherwise>\n                    <span class=\"mention\">\n                        @<xsl:value-of select=\"@name\"/>\n                    </span>\n                </xsl:otherwise>\n            </xsl:choose>\n        xsl;\n    }\n\n    /**\n     * Determine whether a mention tag should be kept.\n     */\n    public static function filterMention(Tag $tag): bool\n    {\n        $name = str_replace(\"\\xc2\\xa0\", ' ', $tag->getAttribute('name'));\n\n        $operator = (new User())->getConnection()->getDriverName() === 'pgsql' ? 'ilike' : 'like';\n\n        if ($user = User::firstWhere('name', $operator, $name)) {\n            $tag->setAttribute('id', $user->id);\n\n            return true;\n        }\n\n        return false;\n    }\n\n    /**\n     * Formatter rendering callback.\n     *\n     * It is possible for a user's name to change after the creation of content\n     * that mentions them. So whenever we render content, we go through the\n     * mention tags and update the `name` attribute to the user's current name.\n     * This assumes that the `mentions` relationship is already loaded on the\n     * content model – otherwise we would run into an N+1 query problem.\n     */\n    public static function rendering(Renderer $renderer, string &$xml, ?Context $context): void\n    {\n        $xml = Utils::replaceAttributes($xml, 'MENTION', function ($attributes) use ($context) {\n            if (isset($attributes['id'])) {\n                $attributes['name'] = username(\n                    $user = $context?->model?->mentions->find($attributes['id']),\n                );\n\n                if (!$user) {\n                    unset($attributes['id']);\n                }\n            }\n\n            return $attributes;\n        });\n    }\n\n    /**\n     * Get all the user IDs that have been mentioned in a piece of content.\n     *\n     * This is used in the `HasBody` model trait to populate the `mentions`\n     * relationship so that it can be eager loaded when the content is\n     * displayed, and the rendering function above can make use of the data.\n     */\n    public static function getMentionedUsers(string $xml): array\n    {\n        return Utils::getAttributeValues($xml, static::TAG_NAME, 'id');\n    }\n}\n"
  },
  {
    "path": "src/Formatter/FormatUploads.php",
    "content": "<?php\n\nnamespace Waterhole\\Formatter;\n\nuse Illuminate\\Support\\Facades\\Storage;\nuse s9e\\TextFormatter\\Configurator;\nuse s9e\\TextFormatter\\Renderer;\nuse s9e\\TextFormatter\\Utils;\n\n/**\n * Upload parsing utilities.\n */\nabstract class FormatUploads\n{\n    public const PROTOCOL = 'upload://';\n\n    /**\n     * Formatter configuration callback.\n     */\n    public static function configure(Configurator $config): void\n    {\n        $config->tags['IMG']->attributes\n            ->add('width', ['required' => false])\n            ->filterChain->append('#uint');\n\n        $config->tags['IMG']->attributes\n            ->add('height', ['required' => false])\n            ->filterChain->append('#uint');\n\n        $config->tags['IMG']->template = <<<xsl\n            <img src=\"{@src}\">\n                <xsl:copy-of select=\"@alt\"/>\n                <xsl:copy-of select=\"@title\"/>\n                <xsl:copy-of select=\"@width\"/>\n                <xsl:copy-of select=\"@height\"/>\n            </img>\n        xsl;\n    }\n\n    /**\n     * Formatter rendering callback.\n     */\n    public static function rendering(Renderer $renderer, string &$xml, ?Context $context): void\n    {\n        $xml = Utils::replaceAttributes($xml, 'IMG', function ($attributes) use ($context) {\n            if (str_starts_with($attributes['src'], static::PROTOCOL)) {\n                $filename = substr($attributes['src'], strlen(static::PROTOCOL));\n                if ($upload = $context?->model?->attachments->firstWhere('filename', $filename)) {\n                    $attributes['width'] = $upload->width;\n                    $attributes['height'] = $upload->height;\n                }\n            }\n            $attributes['src'] = static::expandUrl($attributes['src']);\n            return $attributes;\n        });\n\n        $xml = Utils::replaceAttributes($xml, 'URL', function ($attributes) {\n            $attributes['url'] = static::expandUrl($attributes['url']);\n            return $attributes;\n        });\n    }\n\n    private static function expandUrl(string $url): string\n    {\n        if (str_starts_with($url, static::PROTOCOL)) {\n            return Storage::disk(config('waterhole.uploads.disk'))->url(\n                'uploads/' . substr($url, strlen(static::PROTOCOL)),\n            );\n        }\n        return $url;\n    }\n\n    /**\n     * Get all the upload filenames that have been used in a piece of content.\n     *\n     * This is used in the `HasBody` model trait to populate the `uploads`\n     * relationship so that it can be eager loaded when the content is\n     * displayed, and the rendering function above can make use of the data.\n     */\n    public static function getAttachedUploads(string $xml): array\n    {\n        return collect([\n            ...Utils::getAttributeValues($xml, 'URL', 'url'),\n            ...Utils::getAttributeValues($xml, 'IMG', 'src'),\n        ])\n            ->filter(fn($url) => str_starts_with($url, static::PROTOCOL))\n            ->map(fn($url) => substr($url, strlen(static::PROTOCOL)))\n            ->all();\n    }\n}\n"
  },
  {
    "path": "src/Formatter/Formatter.php",
    "content": "<?php\n\nnamespace Waterhole\\Formatter;\n\nuse Illuminate\\Contracts\\Cache\\Repository;\nuse Illuminate\\Filesystem\\Filesystem;\nuse s9e\\TextFormatter\\Configurator;\nuse s9e\\TextFormatter\\Parser;\nuse s9e\\TextFormatter\\Renderer;\nuse s9e\\TextFormatter\\Unparser;\n\n/**\n * The Formatter parses plain text content and renders it as HTML.\n *\n * Waterhole uses the TextFormatter library to safely format markup. This class\n * is an abstraction around TextFormatter, enabling extension and caching of its\n * configuration and renderer.\n *\n * @link https://github.com/s9e/TextFormatter\n */\nclass Formatter\n{\n    protected array $configurationCallbacks = [];\n    protected array $parsingCallbacks = [];\n    protected array $renderingCallbacks = [];\n    private array $components;\n\n    public function __construct(\n        protected Filesystem $files,\n        protected Repository $cache,\n        protected string $cacheKey,\n    ) {}\n\n    /**\n     * Add a configuration callback to the formatter.\n     */\n    public function configure(callable $callback): void\n    {\n        $this->configurationCallbacks[] = $callback;\n    }\n\n    /**\n     * Parse plain text into an XML document for storage in the database.\n     */\n    public function parse(string $text, ?Context $context = null): string\n    {\n        $parser = $this->getParser();\n\n        foreach ($this->parsingCallbacks as $callback) {\n            $callback($parser, $text, $context);\n        }\n\n        return $parser->parse($text);\n    }\n\n    /**\n     * Add a parsing callback to the formatter.\n     */\n    public function parsing(callable $callback): void\n    {\n        $this->parsingCallbacks[] = $callback;\n    }\n\n    /**\n     * Transform a parsed XML document into HTML.\n     */\n    public function render(string $xml, ?Context $context = null): string\n    {\n        $renderer = $this->getRenderer();\n\n        foreach ($this->renderingCallbacks as $callback) {\n            $callback($renderer, $xml, $context);\n        }\n\n        return $renderer->render($xml);\n    }\n\n    /**\n     * Add a rendering callback to the formatter.\n     */\n    public function rendering(callable $callback): void\n    {\n        $this->renderingCallbacks[] = $callback;\n    }\n\n    /**\n     * Revert a parsed XML document back into plain text.\n     */\n    public function unparse(string $xml): string\n    {\n        return Unparser::unparse($xml);\n    }\n\n    /**\n     * Flush the formatter from the cache.\n     */\n    public function flush(): void\n    {\n        $this->cache->forget($this->cacheKey);\n    }\n\n    protected function getConfigurator(): Configurator\n    {\n        $configurator = new Configurator();\n\n        $configurator->tags->onDuplicate('replace');\n\n        foreach ($this->configurationCallbacks as $callback) {\n            $callback($configurator);\n        }\n\n        return $configurator;\n    }\n\n    protected function getComponent(string $name)\n    {\n        $this->components ??= $this->cache->rememberForever(\n            $this->cacheKey,\n            fn() => $this->getConfigurator()->finalize(),\n        );\n\n        return $this->components[$name];\n    }\n\n    protected function getParser(): Parser\n    {\n        return $this->getComponent('parser');\n    }\n\n    protected function getRenderer(): Renderer\n    {\n        return $this->getComponent('renderer');\n    }\n}\n"
  },
  {
    "path": "src/Forms/ChannelForm.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms;\n\nuse Waterhole\\Models\\Channel;\n\nclass ChannelForm extends Form\n{\n    public function __construct(Channel $channel)\n    {\n        parent::__construct($channel);\n    }\n\n    public function fields(): array\n    {\n        return resolve(\\Waterhole\\Extend\\Forms\\ChannelForm::class)->components([\n            'model' => $this->model,\n        ]);\n    }\n}\n"
  },
  {
    "path": "src/Forms/Concerns/ContainsFields.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms\\Concerns;\n\nuse Illuminate\\Validation\\Validator;\nuse Waterhole\\Forms\\Field;\nuse Waterhole\\Forms\\Form;\n\nuse function Waterhole\\build_components;\n\ntrait ContainsFields\n{\n    private array $fields;\n\n    abstract private function getComponents(): array;\n\n    public function validating(Validator $validator, Form $form): void\n    {\n        $this->call('validating', $validator, $form);\n    }\n\n    public function saving($model, Form $form): void\n    {\n        $this->call('saving', $model, $form);\n    }\n\n    public function saved($model, Form $form): void\n    {\n        $this->call('saved', $model, $form);\n    }\n\n    private function call(string $method, ...$arguments): void\n    {\n        foreach ($this->fields ??= build_components($this->getComponents()) as $component) {\n            if ($component instanceof Field) {\n                $component->$method(...$arguments);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/Forms/Field.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms;\n\nuse Illuminate\\Foundation\\Http\\FormRequest;\nuse Illuminate\\Validation\\Validator;\nuse Illuminate\\View\\Component;\n\nabstract class Field extends Component\n{\n    public function validating(Validator $validator): void {}\n\n    public function saving(FormRequest $request): void {}\n\n    public function saved(FormRequest $request): void {}\n}\n"
  },
  {
    "path": "src/Forms/Fields/ChannelAnswers.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms\\Fields;\n\nuse Illuminate\\Foundation\\Http\\FormRequest;\nuse Illuminate\\Validation\\Validator;\nuse Waterhole\\Forms\\Field;\nuse Waterhole\\Models\\Channel;\n\nclass ChannelAnswers extends Field\n{\n    public function __construct(public ?Channel $model) {}\n\n    public function render(): string\n    {\n        return <<<'blade'\n            <div role=\"group\" class=\"field\">\n                <div class=\"field__label with-icon\">\n                    @icon('tabler-check', ['class' => 'text-md'])\n                    {{ __('waterhole::cp.channel-answers-label') }}\n                </div>\n                <div>\n                    <input type=\"hidden\" name=\"answerable\" value=\"0\">\n                    <label class=\"choice\">\n                        <input\n                            type=\"checkbox\"\n                            name=\"answerable\"\n                            value=\"1\"\n                            @checked(old('answerable', $model->answerable))\n                        >\n                        <span class=\"stack gap-xxs\">\n                            <span>{{ __('waterhole::cp.channel-enable-answers-label') }}</span>\n                            <small class=\"field__description\">{{ __('waterhole::cp.channel-enable-answers-description') }}</small>\n                        </span>\n                    </label>\n                </div>\n            </div>\n        blade;\n    }\n\n    public function validating(Validator $validator): void\n    {\n        $validator->addRules(['answerable' => ['nullable', 'boolean']]);\n    }\n\n    public function saving(FormRequest $request): void\n    {\n        $this->model->answerable = $request->validated('answerable');\n    }\n}\n"
  },
  {
    "path": "src/Forms/Fields/ChannelApproval.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms\\Fields;\n\nuse Illuminate\\Foundation\\Http\\FormRequest;\nuse Illuminate\\Validation\\Validator;\nuse Waterhole\\Forms\\Field;\nuse Waterhole\\Models\\Channel;\n\nclass ChannelApproval extends Field\n{\n    public function __construct(public ?Channel $model) {}\n\n    public function render(): string\n    {\n        return <<<'blade'\n            <div role=\"group\" class=\"field\">\n                <div class=\"field__label\">\n                    {{ __('waterhole::cp.channel-approval-label') }}\n                </div>\n                <div class=\"stack gap-sm\">\n                    <input type=\"hidden\" name=\"require_approval_posts\" value=\"0\">\n                    <label class=\"choice\">\n                        <input\n                            type=\"checkbox\"\n                            name=\"require_approval_posts\"\n                            value=\"1\"\n                            @checked(old('require_approval_posts', $model->require_approval_posts ?? false))\n                        >\n                        <span>{{ __('waterhole::cp.channel-require-approval-posts-label') }}</span>\n                    </label>\n                    <input type=\"hidden\" name=\"require_approval_comments\" value=\"0\">\n                    <label class=\"choice\">\n                        <input\n                            type=\"checkbox\"\n                            name=\"require_approval_comments\"\n                            value=\"1\"\n                            @checked(old('require_approval_comments', $model->require_approval_comments ?? false))\n                        >\n                        <span>{{ __('waterhole::cp.channel-require-approval-comments-label') }}</span>\n                    </label>\n                    <div class=\"field__description\">\n                        {{ __('waterhole::cp.channel-approval-moderators-exempt') }}\n                    </div>\n                </div>\n            </div>\n        blade;\n    }\n\n    public function validating(Validator $validator): void\n    {\n        $validator->addRules([\n            'require_approval_posts' => ['nullable', 'boolean'],\n            'require_approval_comments' => ['nullable', 'boolean'],\n        ]);\n    }\n\n    public function saving(FormRequest $request): void\n    {\n        $this->model->require_approval_posts = $request->validated('require_approval_posts');\n        $this->model->require_approval_comments = $request->validated('require_approval_comments');\n    }\n}\n"
  },
  {
    "path": "src/Forms/Fields/ChannelDescription.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms\\Fields;\n\nuse Illuminate\\Foundation\\Http\\FormRequest;\nuse Illuminate\\Validation\\Validator;\nuse Waterhole\\Forms\\Field;\nuse Waterhole\\Models\\Channel;\n\nclass ChannelDescription extends Field\n{\n    public function __construct(public ?Channel $model) {}\n\n    public function render(): string\n    {\n        return <<<'blade'\n            <x-waterhole::field\n                name=\"description\"\n                :label=\"__('waterhole::cp.channel-description-label')\"\n                :description=\"__('waterhole::cp.channel-description-description')\"\n            >\n                <textarea\n                    id=\"{{ $component->id }}\"\n                    name=\"description\"\n                >{{ old('description', $model->description ?? '') }}</textarea>\n            </x-waterhole::field>\n        blade;\n    }\n\n    public function validating(Validator $validator): void\n    {\n        $validator->addRules(['description' => ['nullable', 'string']]);\n    }\n\n    public function saving(FormRequest $request): void\n    {\n        $this->model->description = $request->validated('description');\n    }\n}\n"
  },
  {
    "path": "src/Forms/Fields/ChannelFilters.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms\\Fields;\n\nuse Illuminate\\Foundation\\Http\\FormRequest;\nuse Illuminate\\Validation\\Rule;\nuse Illuminate\\Validation\\Validator;\nuse Waterhole\\Extend\\Core\\PostFilters;\nuse Waterhole\\Forms\\Field;\nuse Waterhole\\Models\\Channel;\n\nclass ChannelFilters extends Field\n{\n    public function __construct(public ?Channel $model) {}\n\n    public function render(): string\n    {\n        return <<<'blade'\n            <div role=\"group\" class=\"field\">\n                <div class=\"field__label\">\n                    {{ __('waterhole::cp.channel-filters-label') }}\n                </div>\n                <div data-controller=\"reveal\" class=\"stack gap-md\">\n                    <label class=\"choice\">\n                        <input\n                            type=\"checkbox\"\n                            id=\"custom_filters\"\n                            name=\"custom_filters\"\n                            value=\"1\"\n                            data-reveal-target=\"if\"\n                            @checked(old('custom_filters', $model->filters ?? false))\n                        >\n                        <span class=\"stack gap-xxs\">\n                            <span>{{ __('waterhole::cp.channel-custom-filters-label') }}</span>\n                            <small class=\"field__description\">{{ __('waterhole::cp.channel-custom-filters-description') }}</small>\n                        </span>\n                    </label>\n\n                    <div data-controller=\"sortable\" data-reveal-target=\"then\">\n                        <ul\n                            class=\"card sortable\"\n                            role=\"list\"\n                            data-sortable-target=\"container\"\n                            aria-label=\"{{ __('waterhole::cp.channel-filters-label') }}\"\n                        >\n                            @php\n                                $filters = old('filters', $model->filters ?? config('waterhole.forum.post_filters', []));\n\n                                $availableFilters = collect(Waterhole\\resolve_all(resolve(Waterhole\\Extend\\Core\\PostFilters::class)->values()))\n                                    ->sortBy(fn($filter) => ($k = array_search(get_class($filter), $filters)) === false ? INF : $k);\n                            @endphp\n\n                            @foreach ($availableFilters as $filter)\n                                <li\n                                    class=\"card__row row gap-md text-xs\"\n                                    aria-label=\"{{ $filter->label() }}\"\n                                    data-id=\"{{ $filter::class }}\"\n                                >\n                                    <button type=\"button\" class=\"drag-handle\" data-handle>\n                                        @icon('tabler-grip-vertical')\n                                    </button>\n\n                                    <label class=\"choice\">\n                                        <input\n                                            type=\"checkbox\"\n                                            name=\"filters[]\"\n                                            value=\"{{ $filter::class }}\"\n                                            @checked(in_array($filter::class, $filters))\n                                        >\n                                        {{ $filter->label() }}\n                                    </label>\n                                </li>\n                            @endforeach\n                        </ul>\n                    </div>\n                </div>\n            </div>\n        blade;\n    }\n\n    public function validating(Validator $validator): void\n    {\n        $validator->addRules([\n            'filters' => ['required_with:custom_filters', 'array'],\n            'filters.*' => ['string', 'distinct', Rule::in(resolve(PostFilters::class)->values())],\n        ]);\n    }\n\n    public function saving(FormRequest $request): void\n    {\n        $this->model->filters = $request->input('custom_filters')\n            ? $request->validated('filters')\n            : null;\n    }\n}\n"
  },
  {
    "path": "src/Forms/Fields/ChannelIgnore.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms\\Fields;\n\nuse Illuminate\\Foundation\\Http\\FormRequest;\nuse Illuminate\\Validation\\Validator;\nuse Waterhole\\Forms\\Field;\nuse Waterhole\\Models\\Channel;\n\nclass ChannelIgnore extends Field\n{\n    public function __construct(public ?Channel $model) {}\n\n    public function render(): string\n    {\n        return <<<'blade'\n            <div role=\"group\" class=\"field\">\n                <div class=\"field__label\">\n                    {{ __('waterhole::cp.channel-visibility-label') }}\n                </div>\n                <div>\n                    <input type=\"hidden\" name=\"ignore\" value=\"0\">\n                    <label class=\"choice\">\n                        <input\n                            type=\"checkbox\"\n                            name=\"ignore\"\n                            value=\"1\"\n                            @checked(old('ignore', $model->ignore ?? false))\n                        >\n                        <span class=\"stack gap-xxs\">\n                            <span>{{ __('waterhole::cp.channel-ignore-label') }}</span>\n                            <small class=\"field__description\">{{ __('waterhole::cp.channel-ignore-description') }}</small>\n                        </span>\n                    </label>\n                </div>\n            </div>\n        blade;\n    }\n\n    public function validating(Validator $validator): void\n    {\n        $validator->addRules(['ignore' => ['nullable', 'boolean']]);\n    }\n\n    public function saving(FormRequest $request): void\n    {\n        $this->model->ignore = $request->validated('ignore');\n    }\n}\n"
  },
  {
    "path": "src/Forms/Fields/ChannelInstructions.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms\\Fields;\n\nuse Illuminate\\Foundation\\Http\\FormRequest;\nuse Illuminate\\Validation\\Validator;\nuse Waterhole\\Forms\\Field;\nuse Waterhole\\Models\\Channel;\n\nclass ChannelInstructions extends Field\n{\n    public function __construct(public ?Channel $model) {}\n\n    public function render(): string\n    {\n        return <<<'blade'\n            <x-waterhole::field\n                name=\"instructions\"\n                :label=\"__('waterhole::cp.channel-instructions-label')\"\n                :description=\"__('waterhole::cp.channel-instructions-description')\"\n            >\n                <x-waterhole::text-editor\n                    name=\"instructions\"\n                    :id=\"$component->id\"\n                    :value=\"old('instructions', $model->instructions ?? '')\"\n                />\n            </x-waterhole::field>\n        blade;\n    }\n\n    public function validating(Validator $validator): void\n    {\n        $validator->addRules(['instructions' => ['nullable', 'string']]);\n    }\n\n    public function saving(FormRequest $request): void\n    {\n        $this->model->instructions = $request->validated('instructions');\n    }\n}\n"
  },
  {
    "path": "src/Forms/Fields/ChannelLayout.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms\\Fields;\n\nuse Illuminate\\Foundation\\Http\\FormRequest;\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\Validation\\Rule;\nuse Illuminate\\Validation\\Validator;\nuse Waterhole\\Extend\\Core\\PostLayouts;\nuse Waterhole\\Forms\\Field;\nuse Waterhole\\Models\\Channel;\nuse function Waterhole\\resolve_all;\n\nclass ChannelLayout extends Field\n{\n    public array $layouts;\n    public Collection $configFields;\n    public array $configModels = [];\n\n    public function __construct(public ?Channel $model)\n    {\n        $this->layouts = resolve_all(resolve(PostLayouts::class)->values());\n\n        $this->configFields = collect($this->layouts)\n            ->mapWithKeys(function ($layout) use ($model) {\n                if ($field = $layout->configField()) {\n                    return [\n                        get_class($layout) => resolve($field, [\n                            'model' => ($this->configModels[get_class($layout)] =\n                                (object) ($model->layout_config[get_class($layout)] ?? [])),\n                        ]),\n                    ];\n                }\n                return [];\n            })\n            ->filter();\n    }\n\n    public function render(): string\n    {\n        return <<<'blade'\n            <div role=\"group\" class=\"field\">\n                <div class=\"field__label\">\n                    {{ __('waterhole::cp.channel-layout-label') }}\n                </div>\n\n                <div class=\"stack gap-sm\" data-controller=\"reveal\">\n                    <div class=\"btn-group\">\n                        @foreach ($layouts as $layout)\n                            <div>\n                                <input\n                                    type=\"radio\"\n                                    name=\"layout\"\n                                    hidden\n                                    id=\"layout_{{ get_class($layout) }}\"\n                                    value=\"{{ get_class($layout) }}\"\n                                    @checked(old('layout', $model->layout) === get_class($layout))\n                                    data-reveal-target=\"if\"\n                                >\n                                <label class=\"btn\" for=\"layout_{{ get_class($layout) }}\">\n                                    @icon($layout->icon(), ['class' => 'text-md'])\n                                    {{ $layout->label() }}\n                                </label>\n                            </div>\n                        @endforeach\n                    </div>\n\n                    @foreach ($configFields as $layoutClass => $field)\n                        <div\n                            class=\"card card__body\"\n                            data-reveal-target=\"then\"\n                            data-reveal-value=\"{{ $layoutClass }}\"\n                        >\n                            @components([$field])\n                        </div>\n                    @endforeach\n                </div>\n            </div>\n        blade;\n    }\n\n    public function validating(Validator $validator): void\n    {\n        $validator->addRules([\n            'layout' => [Rule::in(array_map(fn($layout) => get_class($layout), $this->layouts))],\n        ]);\n\n        foreach ($this->configFields as $field) {\n            $field->validating($validator);\n        }\n    }\n\n    public function saving(FormRequest $request): void\n    {\n        $this->model->layout = $request->validated('layout');\n\n        foreach ($this->configFields as $field) {\n            $field->saving($request);\n        }\n\n        $this->model->layout_config = $this->configModels;\n    }\n}\n"
  },
  {
    "path": "src/Forms/Fields/ChannelLayoutCards.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms\\Fields;\n\nuse Illuminate\\Foundation\\Http\\FormRequest;\nuse Illuminate\\Validation\\Validator;\nuse Waterhole\\Forms\\Field;\n\nclass ChannelLayoutCards extends Field\n{\n    public function __construct(public object $model) {}\n\n    public function render()\n    {\n        return <<<'blade'\n            <label class=\"choice\">\n                <input type=\"hidden\" name=\"layout_config_cards[hide_author]\" value=\"1\">\n                <input type=\"checkbox\" name=\"layout_config_cards[hide_author]\" value=\"0\" @checked(!($model->hide_author ?? false))>\n                {{ __('waterhole::cp.channel-layout-show-author-label') }}\n            </label>\n        blade;\n    }\n\n    public function validating(Validator $validator): void\n    {\n        $validator->addRules([\n            'layout_config_cards.hide_author' => 'boolean',\n        ]);\n    }\n\n    public function saving(FormRequest $request): void\n    {\n        $this->model->hide_author = (bool) $request->validated('layout_config_cards.hide_author');\n    }\n}\n"
  },
  {
    "path": "src/Forms/Fields/ChannelLayoutList.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms\\Fields;\n\nuse Illuminate\\Foundation\\Http\\FormRequest;\nuse Illuminate\\Validation\\Validator;\nuse Waterhole\\Forms\\Field;\n\nclass ChannelLayoutList extends Field\n{\n    public function __construct(public object $model) {}\n\n    public function render()\n    {\n        return <<<'blade'\n            <div class=\"stack dividers\">\n                <div class=\"stack gap-sm\">\n                    <label class=\"choice\">\n                        <input type=\"hidden\" name=\"layout_config_list[show_excerpt]\" value=\"0\">\n                        <input type=\"checkbox\" name=\"layout_config_list[show_excerpt]\" value=\"1\" @checked($model->show_excerpt ?? false)>\n                        {{ __('waterhole::cp.channel-layout-show-excerpt-label') }}\n                    </label>\n                </div>\n            </div>\n        blade;\n    }\n\n    public function validating(Validator $validator): void\n    {\n        $validator->addRules([\n            'layout_config_list.show_excerpt' => 'boolean',\n        ]);\n    }\n\n    public function saving(FormRequest $request): void\n    {\n        $this->model->show_excerpt = (bool) $request->validated('layout_config_list.show_excerpt');\n    }\n}\n"
  },
  {
    "path": "src/Forms/Fields/ChannelName.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms\\Fields;\n\nuse Illuminate\\Foundation\\Http\\FormRequest;\nuse Illuminate\\Validation\\Validator;\nuse Waterhole\\Forms\\Field;\nuse Waterhole\\Models\\Channel;\n\nclass ChannelName extends Field\n{\n    public function __construct(public ?Channel $model) {}\n\n    public function render(): string\n    {\n        return <<<'blade'\n            <x-waterhole::field\n                name=\"name\"\n                :label=\"__('waterhole::cp.channel-name-label')\"\n            >\n                <input\n                    id=\"{{ $component->id }}\"\n                    name=\"name\"\n                    type=\"text\"\n                    value=\"{{ old('name', $model->name ?? '') }}\"\n                    data-action=\"slugger#updateName\"\n                    autofocus\n                >\n            </x-waterhole::field>\n        blade;\n    }\n\n    public function validating(Validator $validator): void\n    {\n        $validator->addRules(['name' => ['required', 'string', 'max:255']]);\n    }\n\n    public function saving(FormRequest $request): void\n    {\n        $this->model->name = $request->validated('name');\n    }\n}\n"
  },
  {
    "path": "src/Forms/Fields/ChannelReactions.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms\\Fields;\n\nuse Illuminate\\Foundation\\Http\\FormRequest;\nuse Illuminate\\Validation\\Validator;\nuse Waterhole\\Forms\\Field;\nuse Waterhole\\Models\\Channel;\nuse Waterhole\\View\\Components\\ReactionSetPicker;\n\nclass ChannelReactions extends Field\n{\n    public function __construct(public ?Channel $model) {}\n\n    public function render(): string\n    {\n        return <<<'blade'\n            <div class=\"field\">\n                <div class=\"field__label with-icon\">\n                    @icon('tabler-mood-smile', ['class' => 'text-md'])\n                    {{ __('waterhole::cp.channel-reactions-label') }}\n                </div>\n\n                <div class=\"stack gap-md\">\n                    <x-waterhole::field\n                        name=\"posts_reaction_set\"\n                        :label=\"__('waterhole::cp.channel-reactions-posts-label')\"\n                        class=\"grow color-muted align-center\"\n                    >\n                        @php $id = $component->id @endphp\n                        <x-waterhole::reaction-set-picker\n                            :id=\"$id\"\n                            name=\"posts_reaction_set\"\n                            :value=\"old('posts_reaction_set')\"\n                            :default=\"Waterhole\\Models\\ReactionSet::defaultPosts()\"\n                            :enabled=\"$model->posts_reactions_enabled\"\n                            :selected-id=\"$model->posts_reaction_set_id\"\n                        />\n                    </x-waterhole::field>\n\n                    <x-waterhole::field\n                        name=\"comments_reaction_set\"\n                        :label=\"__('waterhole::cp.channel-reactions-comments-label')\"\n                        class=\"grow color-muted align-center\"\n                    >\n                        @php $id = $component->id @endphp\n                        <x-waterhole::reaction-set-picker\n                            :id=\"$id\"\n                            name=\"comments_reaction_set\"\n                            :value=\"old('comments_reaction_set')\"\n                            :default=\"Waterhole\\Models\\ReactionSet::defaultComments()\"\n                            :enabled=\"$model->comments_reactions_enabled\"\n                            :selected-id=\"$model->comments_reaction_set_id\"\n                        />\n                    </x-waterhole::field>\n                </div>\n            </div>\n        blade;\n    }\n\n    public function validating(Validator $validator): void\n    {\n        $validator->addRules([\n            'posts_reaction_set' => ['nullable', ReactionSetPicker::rule()],\n            'comments_reaction_set' => ['nullable', ReactionSetPicker::rule()],\n        ]);\n    }\n\n    public function saving(FormRequest $request): void\n    {\n        $postsValue = $request->validated('posts_reaction_set');\n        $commentsValue = $request->validated('comments_reaction_set');\n\n        [$postsEnabled, $postsSetId] = ReactionSetPicker::resolveSelection($postsValue);\n        $this->model->posts_reactions_enabled = $postsEnabled;\n        $this->model->posts_reaction_set_id = $postsSetId;\n\n        [$commentsEnabled, $commentsSetId] = ReactionSetPicker::resolveSelection($commentsValue);\n        $this->model->comments_reactions_enabled = $commentsEnabled;\n        $this->model->comments_reaction_set_id = $commentsSetId;\n    }\n}\n"
  },
  {
    "path": "src/Forms/Fields/ChannelSimilarPosts.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms\\Fields;\n\nuse Illuminate\\Foundation\\Http\\FormRequest;\nuse Illuminate\\Validation\\Validator;\nuse Waterhole\\Forms\\Field;\nuse Waterhole\\Models\\Channel;\n\nclass ChannelSimilarPosts extends Field\n{\n    public function __construct(public ?Channel $model) {}\n\n    public function render(): string\n    {\n        return <<<'blade'\n            <div role=\"group\" class=\"field\">\n                <div class=\"field__label\">\n                    {{ __('waterhole::cp.channel-similar-posts-title') }}\n                </div>\n                <div>\n                    <input type=\"hidden\" name=\"show_similar_posts\" value=\"0\">\n                    <label class=\"choice\">\n                        <input\n                            type=\"checkbox\"\n                            name=\"show_similar_posts\"\n                            value=\"1\"\n                            @checked(old('show_similar_posts', $model->show_similar_posts ?? false))\n                        >\n                        <span class=\"stack gap-xxs\">\n                            <span>{{ __('waterhole::cp.channel-show-similar-posts-label') }}</span>\n                        </span>\n                    </label>\n                </div>\n            </div>\n        blade;\n    }\n\n    public function validating(Validator $validator): void\n    {\n        $validator->addRules(['show_similar_posts' => ['nullable', 'boolean']]);\n    }\n\n    public function saving(FormRequest $request): void\n    {\n        $this->model->show_similar_posts = $request->validated('show_similar_posts');\n    }\n}\n"
  },
  {
    "path": "src/Forms/Fields/ChannelSlug.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms\\Fields;\n\nuse Illuminate\\Foundation\\Http\\FormRequest;\nuse Illuminate\\Validation\\Rule;\nuse Illuminate\\Validation\\Validator;\nuse Waterhole\\Forms\\Field;\nuse Waterhole\\Models\\Channel;\n\nclass ChannelSlug extends Field\n{\n    public function __construct(public ?Channel $model) {}\n\n    public function render(): string\n    {\n        return <<<'blade'\n            <x-waterhole::field\n                name=\"slug\"\n                :label=\"__('waterhole::cp.channel-slug-label')\"\n            >\n                <input\n                    id=\"{{ $component->id }}\"\n                    name=\"slug\"\n                    type=\"text\"\n                    value=\"{{ old('slug', $model->slug ?? '') }}\"\n                    data-action=\"slugger#updateSlug\"\n                    data-slugger-target=\"slug\"\n                >\n\n                <x-slot:description>\n                    {{ __('waterhole::cp.channel-slug-url-label') }}\n                    {!! preg_replace('~^https?://~', '', str_replace('*', '<span data-slugger-target=\"mirror\">'.old('slug', $model->slug ?? '').'</span>', route('waterhole.channels.show', ['channel' => '*']))) !!}\n                </x-slot:description>\n            </x-waterhole::field>\n        blade;\n    }\n\n    public function validating(Validator $validator): void\n    {\n        $validator->addRules([\n            'slug' => [\n                'required',\n                'string',\n                'max:255',\n                Rule::unique(Channel::class)->ignore($this->model),\n            ],\n        ]);\n    }\n\n    public function saving(FormRequest $request): void\n    {\n        $this->model->slug = $request->validated('slug');\n    }\n}\n"
  },
  {
    "path": "src/Forms/Fields/ChannelTaxonomies.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms\\Fields;\n\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Foundation\\Http\\FormRequest;\nuse Illuminate\\Validation\\Rules\\Exists;\nuse Illuminate\\Validation\\Validator;\nuse Waterhole\\Forms\\Field;\nuse Waterhole\\Models\\Channel;\nuse Waterhole\\Models\\Taxonomy;\n\nclass ChannelTaxonomies extends Field\n{\n    public Collection $taxonomies;\n\n    public function __construct(public ?Channel $model)\n    {\n        $this->taxonomies = Taxonomy::all();\n    }\n\n    public function shouldRender(): bool\n    {\n        return $this->taxonomies->isNotEmpty();\n    }\n\n    public function render(): string\n    {\n        return <<<'blade'\n            <div role=\"group\" class=\"field\">\n                <div class=\"field__label with-icon\">\n                    @icon('tabler-tags', ['class' => 'text-md'])\n                    {{ __('waterhole::cp.channel-taxonomies-label') }}\n                </div>\n\n                <div class=\"card\">\n                    @foreach ($taxonomies as $taxonomy)\n                        <div class=\"card__row\">\n                            <label class=\"choice\">\n                                <input\n                                    type=\"checkbox\"\n                                    name=\"taxonomy_ids[]\"\n                                    value=\"{{ $taxonomy->id }}\"\n                                    @checked(in_array($taxonomy->id, old('taxonomy_ids', $model->taxonomies->modelKeys())))\n                                >\n                                <span>{{ $taxonomy->name }}</span>\n                            </label>\n                        </div>\n                    @endforeach\n                </div>\n            </div>\n        blade;\n    }\n\n    public function validating(Validator $validator): void\n    {\n        $validator->addRules([\n            'taxonomy_ids' => ['array'],\n            'taxonomy_ids.*' => ['required', 'integer', new Exists(Taxonomy::class, 'id')],\n        ]);\n    }\n\n    public function saved(FormRequest $request): void\n    {\n        $this->model->taxonomies()->sync($request->validated('taxonomy_ids'));\n    }\n}\n"
  },
  {
    "path": "src/Forms/Fields/GroupAppearance.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms\\Fields;\n\nuse Illuminate\\Foundation\\Http\\FormRequest;\nuse Illuminate\\Validation\\Validator;\nuse Waterhole\\Forms\\Field;\nuse Waterhole\\Models\\Group;\nuse Waterhole\\View\\Components\\Cp\\IconPicker;\n\nclass GroupAppearance extends Field\n{\n    public function __construct(public ?Group $model) {}\n\n    public function shouldRender(): bool\n    {\n        return !$this->model->isGuest() && !$this->model->isMember();\n    }\n\n    public function render(): string\n    {\n        return <<<'blade'\n            <div class=\"field\" data-controller=\"reveal\">\n                <div class=\"field__label\">{{ __('waterhole::cp.group-appearance-label') }}</div>\n\n                <div class=\"stack gap-lg\">\n                    <div>\n                        <input type=\"hidden\" name=\"is_public\" value=\"0\">\n                        <label class=\"choice\">\n                            <input\n                                data-reveal-target=\"if\"\n                                type=\"checkbox\"\n                                name=\"is_public\"\n                                value=\"1\"\n                                @checked(old('is_public', $model->is_public ?? null))\n                            >\n                            {{ __('waterhole::cp.group-show-as-badge-label') }}\n                        </label>\n                    </div>\n\n                    <div class=\"card card__body stack gap-lg\" data-reveal-target=\"then\">\n                        <x-waterhole::field\n                            name=\"color\"\n                            :label=\"__('waterhole::cp.group-color-label')\"\n                        >\n                            <x-waterhole::cp.color-picker\n                                name=\"color\"\n                                id=\"{{ $component->id }}\"\n                                value=\"{{ old('color', $model->color ?? null) }}\"\n                            />\n                        </x-waterhole::field>\n\n                        <x-waterhole::field\n                            name=\"icon\"\n                            :label=\"__('waterhole::cp.group-icon-label')\"\n                        >\n                            <x-waterhole::cp.icon-picker\n                                name=\"icon\"\n                                :value=\"old('icon', $model->icon ?? null)\"\n                            />\n                        </x-waterhole::field>\n                    </div>\n                </div>\n            </div>\n        blade;\n    }\n\n    public function validating(Validator $validator): void\n    {\n        $validator->addRules([\n            'is_public' => ['boolean'],\n            'color' => [\n                'nullable',\n                'string',\n                'regex:/^[a-f0-9]{3}|[a-f0-9]{4}|[a-f0-9]{6}|[a-f0-9]{8}$/i',\n            ],\n        ]);\n\n        $validator->addRules(IconPicker::validationRules());\n    }\n\n    public function saving(FormRequest $request): void\n    {\n        $this->model->is_public = $request->validated('is_public');\n        $this->model->color = $request->validated('color');\n    }\n\n    public function saved(FormRequest $request): void\n    {\n        $this->model->saveIcon($request->validated('icon'));\n    }\n}\n"
  },
  {
    "path": "src/Forms/Fields/GroupGlobalPermissions.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms\\Fields;\n\nuse Waterhole\\Forms\\Field;\nuse Waterhole\\Models\\Group;\n\nclass GroupGlobalPermissions extends Field\n{\n    public function __construct(public ?Group $model) {}\n\n    public function shouldRender(): bool\n    {\n        return $this->model->isCustom();\n    }\n\n    public function render(): string\n    {\n        return <<<'blade'\n            <div role=\"group\" class=\"field\">\n                <div class=\"field__label\">\n                    {{ __('waterhole::cp.group-global-permissions-title') }}\n                </div>\n                <div>\n                    <input type=\"hidden\" name=\"permissions[user][suspend]\" value=\"0\">\n                    <label class=\"choice\">\n                        <input\n                            type=\"checkbox\"\n                            name=\"permissions[user][suspend]\"\n                            value=\"1\"\n                            @checked(old('permissions.user.suspend', Waterhole::permissions()->can($model, 'suspend', Waterhole\\Models\\User::class)))\n                        >\n                        <span>{{ __('waterhole::cp.group-permission-suspend-users-label') }}</span>\n                    </label>\n                </div>\n            </div>\n        blade;\n    }\n}\n"
  },
  {
    "path": "src/Forms/Fields/GroupName.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms\\Fields;\n\nuse Illuminate\\Foundation\\Http\\FormRequest;\nuse Illuminate\\Validation\\Validator;\nuse Waterhole\\Forms\\Field;\nuse Waterhole\\Models\\Group;\n\nclass GroupName extends Field\n{\n    public function __construct(public ?Group $model) {}\n\n    public function shouldRender(): bool\n    {\n        return !$this->model->isGuest() && !$this->model->isMember();\n    }\n\n    public function render(): string\n    {\n        return <<<'blade'\n            <x-waterhole::field\n                name=\"name\"\n                :label=\"__('waterhole::cp.group-name-label')\"\n            >\n                <input\n                    type=\"text\"\n                    name=\"name\"\n                    id=\"{{ $component->id }}\"\n                    value=\"{{ old('name', $model->name ?? null) }}\"\n                    autofocus\n                >\n            </x-waterhole::field>\n        blade;\n    }\n\n    public function validating(Validator $validator): void\n    {\n        $validator->addRules(['name' => ['required', 'string', 'max:255']]);\n    }\n\n    public function saving(FormRequest $request): void\n    {\n        $this->model->name = $request->validated('name');\n    }\n}\n"
  },
  {
    "path": "src/Forms/Fields/GroupRules.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms\\Fields;\n\nuse Illuminate\\Foundation\\Http\\FormRequest;\nuse Illuminate\\Validation\\Validator;\nuse Waterhole\\Forms\\Field;\nuse Waterhole\\Models\\Group;\n\nclass GroupRules extends Field\n{\n    public function __construct(public ?Group $model) {}\n\n    public function shouldRender(): bool\n    {\n        return $this->model->isCustom();\n    }\n\n    public function render(): string\n    {\n        return <<<'blade'\n            <div class=\"field\">\n                <div class=\"field__label\">{{ __('waterhole::cp.group-rules-title') }}</div>\n\n                <div class=\"stack gap-sm\">\n                    <div>\n                        <input type=\"hidden\" name=\"auto_assign\" value=\"0\">\n                        <label class=\"choice\">\n                            <input\n                                type=\"checkbox\"\n                                name=\"auto_assign\"\n                                value=\"1\"\n                                @checked(old('auto_assign', $model->auto_assign ?? null))\n                            >\n                            {{ __('waterhole::cp.group-auto-assign-label') }}\n                        </label>\n                    </div>\n\n                    <div class=\"stack gap-sm\" data-controller=\"reveal\">\n                        <div>\n                            <input type=\"hidden\" name=\"rules[requires_approval]\" value=\"0\">\n                            <label class=\"choice\">\n                                <input\n                                    data-reveal-target=\"if\"\n                                    type=\"checkbox\"\n                                    name=\"rules[requires_approval]\"\n                                    value=\"1\"\n                                    @checked(old('rules.requires_approval', $model?->rules['requires_approval'] ?? false))\n                                >\n                                {{ __('waterhole::cp.group-rules-requires-approval-label') }}\n                            </label>\n                        </div>\n\n                        <div class=\"choice-indent\">\n                            <input type=\"hidden\" name=\"rules[remove_after_approval]\" value=\"0\">\n                            <label class=\"choice\" data-reveal-target=\"then\">\n                                <input\n                                    type=\"checkbox\"\n                                    name=\"rules[remove_after_approval]\"\n                                    value=\"1\"\n                                    @checked(old('rules.remove_after_approval', $model?->rules['remove_after_approval'] ?? false))\n                                >\n                                {{ __('waterhole::cp.group-rules-remove-after-approval-label') }}\n                            </label>\n                        </div>\n                    </div>\n                </div>\n            </div>\n        blade;\n    }\n\n    public function validating(Validator $validator): void\n    {\n        $validator->addRules([\n            'auto_assign' => ['boolean'],\n            'rules' => ['array'],\n            'rules.requires_approval' => ['boolean'],\n            'rules.remove_after_approval' => ['boolean'],\n        ]);\n    }\n\n    public function saving(FormRequest $request): void\n    {\n        $this->model->auto_assign = $request->validated('auto_assign');\n\n        $requiresApproval = $request->boolean('rules.requires_approval');\n\n        $this->model->rules = [\n            'requires_approval' => $requiresApproval,\n            'remove_after_approval' =>\n                $requiresApproval && $request->boolean('rules.remove_after_approval'),\n        ];\n    }\n}\n"
  },
  {
    "path": "src/Forms/Fields/GroupStructurePermissions.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms\\Fields;\n\nuse Illuminate\\Foundation\\Http\\FormRequest;\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\Validation\\Validator;\nuse Waterhole\\Forms\\Field;\nuse Waterhole\\Models\\Group;\nuse Waterhole\\Models\\Structure;\n\nclass GroupStructurePermissions extends Field\n{\n    public Collection $structure;\n    public Collection $abilities;\n\n    public function __construct(public ?Group $model)\n    {\n        $this->structure = Structure::with('content')->orderBy('position')->get();\n\n        // Construct an array of all abilities that apply to the structure\n        // content to use as columns for the permission grid.\n        $this->abilities = $this->structure\n            ->flatMap(\n                fn(Structure $node) => method_exists($node->content, 'abilities')\n                    ? $node->content->abilities()\n                    : [],\n            )\n            ->unique();\n\n        if ($this->model?->isGuest()) {\n            $this->abilities = $this->abilities->filter(fn($ability) => $ability === 'view');\n        } elseif ($this->model?->isMember()) {\n            $this->abilities = $this->abilities->reject(fn($ability) => $ability === 'moderate');\n        }\n    }\n\n    public function shouldRender(): bool\n    {\n        return !$this->model->isAdmin();\n    }\n\n    public function render(): string\n    {\n        return <<<'blade'\n            <div class=\"field\">\n                <div class=\"field__label\">\n                    {{ __('waterhole::cp.group-structure-permissions-title') }}\n                </div>\n                <div>\n                    <div class=\"table-container card\">\n                        <table\n                            class=\"table permission-grid\"\n                            data-controller=\"permission-grid\"\n                        >\n                            <colgroup>\n                                <col>\n                                @foreach ($abilities as $ability)\n                                    <col>\n                                @endforeach\n                            </colgroup>\n                            <thead>\n                                <tr>\n                                    <td></td>\n                                    @foreach ($abilities as $ability)\n                                        <th>{{ __(\"waterhole::system.ability-$ability\") }}</th>\n                                    @endforeach\n                                </tr>\n                            </thead>\n                            <tbody>\n                                @foreach ($structure as $node)\n                                    <tr>\n                                        <th>\n                                            @if ($node->content instanceof Waterhole\\Models\\Channel)\n                                                <x-waterhole::channel-label :channel=\"$node->content\"/>\n                                            @elseif ($node->content instanceof Waterhole\\Models\\StructureHeading)\n                                                <span class=\"subtitle\">{{ $node->content->name }}</span>\n                                            @else\n                                                <span class=\"with-icon\">\n                                                    @icon($node->content->icon ?? null)\n                                                    {{ $node->content->name }}\n                                                </span>\n                                            @endif\n                                        </th>\n                                        @foreach ($abilities as $ability)\n                                            @if (method_exists($node->content, 'abilities') && in_array($ability, $node->content->abilities()))\n                                                @php\n                                                    $key = $node->content->getMorphClass().':'.$node->content->getKey();\n                                                @endphp\n                                                <td class=\"choice-cell\">\n                                                    <label class=\"choice\">\n                                                        <input\n                                                            type=\"hidden\"\n                                                            name=\"permissions[{{ $key }}][{{ $ability }}]\"\n                                                            value=\"0\"\n                                                        >\n                                                        <input\n                                                            type=\"checkbox\"\n                                                            name=\"permissions[{{ $key }}][{{ $ability }}]\"\n                                                            value=\"1\"\n                                                            {{--\n                                                                If members are allowed, then this group *must* be allowed too,\n                                                                so disable the checkbox.\n                                                            --}}\n                                                            @disabled($model?->isMember() && Waterhole::permissions()->can(Waterhole\\Models\\Group::guest(), $ability, $node->content))\n                                                            @disabled($model?->isCustom() && Waterhole::permissions()->can(Waterhole\\Models\\Group::member(), $ability, $node->content))\n                                                            {{--\n                                                                Check this box if it was checked before, or if the ability is\n                                                                allowed for this group, or for members in general.\n                                                            --}}\n                                                            @checked(old(\"permissions.$key.$ability\", Waterhole::permissions()->can($model ?? Waterhole\\Models\\Group::member(), $ability, $node->content)))\n                                                            {{--\n                                                                Non-\"view\" abilities depend on the \"view\" ability being allowed.\n                                                            --}}\n                                                            @if ($ability !== 'view') data-depends-on=\"permissions[{{ $key }}][view]\" @endif\n                                                        >\n                                                    </label>\n                                                </td>\n                                            @else\n                                                <td></td>\n                                            @endif\n                                        @endforeach\n                                    </tr>\n                                @endforeach\n                            </tbody>\n                        </table>\n                    </div>\n                </div>\n            </div>\n        blade;\n    }\n\n    public function validating(Validator $validator): void\n    {\n        $validator->addRules(['permissions' => ['array']]);\n    }\n\n    public function saved(FormRequest $request): void\n    {\n        $this->model->savePermissions($request->validated('permissions'));\n    }\n}\n"
  },
  {
    "path": "src/Forms/Fields/Icon.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms\\Fields;\n\nuse Illuminate\\Foundation\\Http\\FormRequest;\nuse Illuminate\\Validation\\Validator;\nuse Waterhole\\Forms\\Field;\nuse Waterhole\\View\\Components\\Cp\\IconPicker;\n\nclass Icon extends Field\n{\n    public function __construct(public $model = null) {}\n\n    public function render(): string\n    {\n        return <<<'blade'\n            <x-waterhole::field\n                name=\"icon\"\n                :label=\"__('waterhole::system.icon-field-label')\"\n            >\n                <x-waterhole::cp.icon-picker\n                    name=\"icon\"\n                    :value=\"old('icon', $model->icon ?? null)\"\n                />\n            </x-waterhole::field>\n        blade;\n    }\n\n    public function validating(Validator $validator): void\n    {\n        $validator->addRules(IconPicker::validationRules());\n    }\n\n    public function saved(FormRequest $request): void\n    {\n        $this->model->saveIcon($request->validated('icon'));\n    }\n}\n"
  },
  {
    "path": "src/Forms/Fields/PageBody.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms\\Fields;\n\nuse Illuminate\\Foundation\\Http\\FormRequest;\nuse Illuminate\\Validation\\Validator;\nuse Waterhole\\Forms\\Field;\nuse Waterhole\\Models\\Page;\n\nclass PageBody extends Field\n{\n    public function __construct(public ?Page $model) {}\n\n    public function render(): string\n    {\n        return <<<'blade'\n            <x-waterhole::field\n                name=\"body\"\n                :label=\"__('waterhole::cp.page-body-label')\"\n            >\n                <x-waterhole::text-editor\n                    name=\"body\"\n                    id=\"{{ $component->id }}\"\n                    :value=\"old('body', $model->body ?? null)\"\n                />\n            </x-waterhole::field>\n        blade;\n    }\n\n    public function validating(Validator $validator): void\n    {\n        $validator->addRules(['body' => ['required', 'string']]);\n    }\n\n    public function saving(FormRequest $request): void\n    {\n        $this->model->body = $request->validated('body');\n    }\n}\n"
  },
  {
    "path": "src/Forms/Fields/PageName.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms\\Fields;\n\nuse Illuminate\\Foundation\\Http\\FormRequest;\nuse Illuminate\\Validation\\Validator;\nuse Waterhole\\Forms\\Field;\nuse Waterhole\\Models\\Page;\n\nclass PageName extends Field\n{\n    public function __construct(public ?Page $model) {}\n\n    public function render(): string\n    {\n        return <<<'blade'\n            <x-waterhole::field\n                name=\"name\"\n                :label=\"__('waterhole::cp.page-name-label')\"\n            >\n                <input\n                    type=\"text\"\n                    name=\"name\"\n                    id=\"{{ $component->id }}\"\n                    value=\"{{ old('name', $model->name ?? null) }}\"\n                    autofocus\n                    data-action=\"slugger#updateName\"\n                >\n            </x-waterhole::field>\n        blade;\n    }\n\n    public function validating(Validator $validator): void\n    {\n        $validator->addRules(['name' => ['required', 'string', 'max:255']]);\n    }\n\n    public function saving(FormRequest $request): void\n    {\n        $this->model->name = $request->validated('name');\n    }\n}\n"
  },
  {
    "path": "src/Forms/Fields/PageSlug.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms\\Fields;\n\nuse Illuminate\\Foundation\\Http\\FormRequest;\nuse Illuminate\\Validation\\Rule;\nuse Illuminate\\Validation\\Validator;\nuse Waterhole\\Forms\\Field;\nuse Waterhole\\Models\\Page;\n\nclass PageSlug extends Field\n{\n    public function __construct(public ?Page $model) {}\n\n    public function render(): string\n    {\n        return <<<'blade'\n            <x-waterhole::field\n                name=\"slug\"\n                :label=\"__('waterhole::cp.page-slug-label')\"\n            >\n                <input\n                    type=\"text\"\n                    name=\"slug\"\n                    id=\"{{ $component->id }}\"\n                    value=\"{{ old('slug', $model->slug ?? null) }}\"\n                    data-action=\"slugger#updateSlug\"\n                    data-slugger-target=\"slug\"\n                >\n                <x-slot:description>\n                    {{ __('waterhole::cp.page-slug-url-label') }}\n                    {!! preg_replace('~^https?://~', '', str_replace('*', '<span data-slugger-target=\"mirror\">'.old('slug', $model->slug ?? '').'</span>', route('waterhole.page', ['page' => '*']))) !!}\n                </x-slot:description>\n            </x-waterhole::field>\n        blade;\n    }\n\n    public function validating(Validator $validator): void\n    {\n        $validator->addRules([\n            'slug' => [\n                'required',\n                'string',\n                'max:255',\n                Rule::unique(Page::class)->ignore($this->model),\n            ],\n        ]);\n    }\n\n    public function saving(FormRequest $request): void\n    {\n        $this->model->slug = $request->validated('slug');\n    }\n}\n"
  },
  {
    "path": "src/Forms/Fields/Permissions.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms\\Fields;\n\nuse Illuminate\\Foundation\\Http\\FormRequest;\nuse Illuminate\\Validation\\Validator;\nuse Waterhole\\Forms\\Field;\nuse Waterhole\\Models\\Model;\n\nclass Permissions extends Field\n{\n    public function __construct(public ?Model $model) {}\n\n    public function render(): string\n    {\n        return <<<'blade'\n            <x-waterhole::cp.permission-grid\n                :abilities=\"$model->abilities()\"\n                :scope=\"$model->exists ? $model : null\"\n                :defaults=\"$model->defaultAbilities()\"\n            />\n        blade;\n    }\n\n    public function validating(Validator $validator): void\n    {\n        $validator->addRules(['permissions' => ['array']]);\n    }\n\n    public function saved(FormRequest $request): void\n    {\n        $this->model->savePermissions($request->validated('permissions'));\n    }\n}\n"
  },
  {
    "path": "src/Forms/Fields/PostBody.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms\\Fields;\n\nuse Illuminate\\Foundation\\Http\\FormRequest;\nuse Illuminate\\Validation\\Validator;\nuse Waterhole\\Forms\\Field;\nuse Waterhole\\Models\\Post;\n\nclass PostBody extends Field\n{\n    public function __construct(public ?Post $model) {}\n\n    public function render(): string\n    {\n        return <<<'blade'\n            @php\n                $label = __([\n                    \"waterhole.channel-{$model->channel->slug}-post-body-label\",\n                    'waterhole::forum.post-body-label',\n                ]);\n\n                $description = __([\n                    \"waterhole.channel-{$model->channel->slug}-post-body-description\",\n                    '',\n                ]);\n            @endphp\n\n            <x-waterhole::field id=\"post-body\" name=\"body\" :$label :$description>\n                <x-waterhole::text-editor\n                    name=\"body\"\n                    :id=\"$component->id\"\n                    :value=\"old('body', $model->body ?? '')\"\n                    style=\"min-height: 50vh\"\n                />\n            </x-waterhole::field>\n        blade;\n    }\n\n    public function validating(Validator $validator): void\n    {\n        $validator->addRules(['body' => ['required', 'string']]);\n    }\n\n    public function saving(FormRequest $request): void\n    {\n        $this->model->body = $request->validated('body');\n    }\n}\n"
  },
  {
    "path": "src/Forms/Fields/PostTags.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms\\Fields;\n\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Foundation\\Http\\FormRequest;\nuse Illuminate\\Support\\Facades\\Gate;\nuse Illuminate\\Validation\\Rule;\nuse Illuminate\\Validation\\Validator;\nuse Waterhole\\Forms\\Field;\nuse Waterhole\\Models\\Post;\nuse Waterhole\\Models\\Tag;\nuse Waterhole\\Models\\Taxonomy;\n\nclass PostTags extends Field\n{\n    public ?Collection $taxonomies;\n\n    public function __construct(public ?Post $model)\n    {\n        $this->taxonomies = $model->channel?->taxonomies\n            ->load('tags')\n            ->filter(\n                fn(Taxonomy $taxonomy) => Gate::allows(\n                    'waterhole.taxonomy.assign-tags',\n                    $taxonomy,\n                ) && $taxonomy->tags->count(),\n            );\n    }\n\n    public function shouldRender(): bool\n    {\n        return (bool) $this->taxonomies;\n    }\n\n    public function render(): string\n    {\n        return <<<'blade'\n            @foreach ($taxonomies as $taxonomy)\n                <x-waterhole::field\n                    name=\"tag_ids.{{ $taxonomy->id }}*\"\n                    :label=\"__($taxonomy->name)\"\n                >\n                    <select\n                        name=\"tag_ids[{{ $taxonomy->id }}][]\"\n                        id=\"{{ $component->id }}\"\n                        @if ($taxonomy->allow_multiple) multiple @endif\n                    >\n                        @unless ($taxonomy->allow_multiple) <option></option> @endunless\n                        @foreach ($taxonomy->tags as $tag)\n                            <option\n                                value=\"{{ $tag->id }}\"\n                                @selected(in_array($tag->id, old(\"tag_ids.$taxonomy->id\", $model->tags->modelKeys()) ?? []))\n                            >{{ $tag->name }}</option>\n                        @endforeach\n                    </select>\n                </x-waterhole::field>\n            @endforeach\n        blade;\n    }\n\n    public function validating(Validator $validator): void\n    {\n        foreach ($this->taxonomies as $taxonomy) {\n            $required = $taxonomy->is_required ? ['required'] : ['nullable'];\n\n            $validator->addRules([\n                \"tag_ids.$taxonomy->id\" => [...$required, 'array'],\n                \"tag_ids.$taxonomy->id.*\" => [\n                    ...$required,\n                    'integer',\n                    Rule::exists(Tag::class, 'id')->where('taxonomy_id', $taxonomy->id),\n                ],\n            ]);\n        }\n    }\n\n    public function saved(FormRequest $request): void\n    {\n        $this->model\n            ->tags()\n            ->detach(\n                $this->model->tags->filter(\n                    fn(Tag $tag) => $this->taxonomies->contains($tag->taxonomy_id),\n                ),\n            );\n\n        $this->model->tags()->attach(collect($request->validated('tag_ids'))->flatten()->filter());\n    }\n}\n"
  },
  {
    "path": "src/Forms/Fields/PostTitle.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms\\Fields;\n\nuse Illuminate\\Foundation\\Http\\FormRequest;\nuse Illuminate\\Support\\Str;\nuse Illuminate\\Validation\\Validator;\nuse Waterhole\\Forms\\Field;\nuse Waterhole\\Models\\Post;\nuse Waterhole\\Search\\Results;\nuse Waterhole\\Search\\Searcher;\n\nclass PostTitle extends Field\n{\n    public ?Results $similarPosts = null;\n\n    public function __construct(public ?Post $model)\n    {\n        if (\n            config('waterhole.system.search_engine') &&\n            !$model->exists &&\n            $model->channel->show_similar_posts &&\n            ($title = old('title')) &&\n            strlen($title) >= 10\n        ) {\n            $this->similarPosts = resolve(Searcher::class)->search(\n                q: $title,\n                limit: 3,\n                channelIds: [$model->channel_id],\n                in: ['title', 'body'],\n            );\n        }\n    }\n\n    public function render(): string\n    {\n        return <<<'blade'\n            <div class=\"stack gap-sm\" @if (!$model->exists) data-controller=\"similar-posts\" @endif>\n                <x-waterhole::field\n                    name=\"title\"\n                    :label=\"__($model->channel->translations[$key = 'waterhole::forum.post-title-label'] ?? $key)\"\n                    :description=\"__($model->channel->translations[$key = 'waterhole::forum.post-title-description'] ?? '')\">\n                    <input\n                        id=\"{{ $component->id }}\"\n                        name=\"title\"\n                        type=\"text\"\n                        value=\"{{ old('title', $model->title ?? '') }}\"\n                        data-action=\"similar-posts#input\"\n                    >\n                </x-waterhole::field>\n\n                @if (!$model->exists && $model->channel->show_similar_posts)\n                    <button\n                        type=\"submit\"\n                        hidden\n                        data-similar-posts-target=\"submit\"\n                        data-turbo-frame=\"similar-posts\"\n                    ></button>\n\n                    <turbo-frame id=\"similar-posts\" target=\"_top\" hidden data-similar-posts-target=\"frame\">\n                        @if (!empty($similarPosts->hits))\n                            <div class=\"bg-warning-soft p-md rounded stack gap-xs text-xs\">\n                                <p class=\"weight-bold\">\n                                    {{ __($model->channel->translations[$key = 'waterhole::forum.similar-posts-label'] ?? $key) }}\n                                </p>\n                                @foreach ($similarPosts->hits as $hit)\n                                    <p><a href=\"{{ $hit->post->url }}\">{{ $hit->post->title }}</a></p>\n                                @endforeach\n                            </div>\n                        @endif\n                    </turbo-frame>\n                @endif\n            </div>\n        blade;\n    }\n\n    public function validating(Validator $validator): void\n    {\n        $validator->addRules(['title' => ['required', 'string', 'max:255']]);\n    }\n\n    public function saving(FormRequest $request): void\n    {\n        $this->model->title = $request->validated('title');\n        $this->model->slug = Str::slug($this->model->title);\n    }\n}\n"
  },
  {
    "path": "src/Forms/Fields/ReactionSetDefaults.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms\\Fields;\n\nuse Illuminate\\Foundation\\Http\\FormRequest;\nuse Illuminate\\Validation\\Validator;\nuse Waterhole\\Forms\\Field;\nuse Waterhole\\Models\\ReactionSet;\n\nclass ReactionSetDefaults extends Field\n{\n    public function __construct(public ?ReactionSet $model) {}\n\n    public function render(): string\n    {\n        return <<<'blade'\n            <div class=\"field\">\n                <div class=\"field__label\">\n                    {{ __('waterhole::cp.reaction-set-usage-label') }}\n                </div>\n\n                <div class=\"stack gap-sm\">\n                    <label class=\"choice\">\n                        <input type=\"hidden\" name=\"is_default_posts\" value=\"0\">\n                        <input type=\"checkbox\" name=\"is_default_posts\" value=\"1\" @checked($model->is_default_posts)>\n                        {{ __('waterhole::cp.reaction-set-default-posts') }}\n                    </label>\n                    <label class=\"choice\">\n                        <input type=\"hidden\" name=\"is_default_comments\" value=\"0\">\n                        <input type=\"checkbox\" name=\"is_default_comments\" value=\"1\" @checked($model->is_default_comments)>\n                        {{ __('waterhole::cp.reaction-set-default-comments') }}\n                    </label>\n                </div>\n            </div>\n        blade;\n    }\n\n    public function validating(Validator $validator): void\n    {\n        $validator->addRules([\n            'is_default_posts' => ['boolean'],\n            'is_default_comments' => ['boolean'],\n        ]);\n    }\n\n    public function saving(FormRequest $request): void\n    {\n        $this->model->is_default_posts = $request->validated('is_default_posts');\n        $this->model->is_default_comments = $request->validated('is_default_comments');\n    }\n\n    public function saved(FormRequest $request): void\n    {\n        if ($this->model->is_default_posts) {\n            $update['is_default_posts'] = false;\n        }\n        if ($this->model->is_default_comments) {\n            $update['is_default_comments'] = false;\n        }\n\n        if (isset($update)) {\n            ReactionSet::where('id', '!=', $this->model->id)->update($update);\n        }\n    }\n}\n"
  },
  {
    "path": "src/Forms/Fields/ReactionSetName.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms\\Fields;\n\nuse Illuminate\\Foundation\\Http\\FormRequest;\nuse Illuminate\\Validation\\Validator;\nuse Waterhole\\Forms\\Field;\nuse Waterhole\\Models\\ReactionSet;\n\nclass ReactionSetName extends Field\n{\n    public function __construct(public ?ReactionSet $model) {}\n\n    public function render(): string\n    {\n        return <<<'blade'\n            <x-waterhole::field\n                name=\"name\"\n                :label=\"__('waterhole::cp.reaction-set-name-label')\"\n            >\n                <input\n                    type=\"text\"\n                    name=\"name\"\n                    id=\"{{ $component->id }}\"\n                    value=\"{{ old('name', $model->name ?? null) }}\"\n                    autofocus\n                >\n            </x-waterhole::field>\n        blade;\n    }\n\n    public function validating(Validator $validator): void\n    {\n        $validator->addRules(['name' => ['required', 'string', 'max:255']]);\n    }\n\n    public function saving(FormRequest $request): void\n    {\n        $this->model->name = $request->validated('name');\n    }\n}\n"
  },
  {
    "path": "src/Forms/Fields/ReactionTypeName.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms\\Fields;\n\nuse Illuminate\\Foundation\\Http\\FormRequest;\nuse Illuminate\\Validation\\Validator;\nuse Waterhole\\Forms\\Field;\nuse Waterhole\\Models\\ReactionType;\n\nclass ReactionTypeName extends Field\n{\n    public function __construct(public ?ReactionType $model) {}\n\n    public function render(): string\n    {\n        return <<<'blade'\n            <x-waterhole::field\n                name=\"name\"\n                :label=\"__('waterhole::cp.reaction-type-name-label')\"\n            >\n                <input\n                    type=\"text\"\n                    name=\"name\"\n                    id=\"{{ $component->id }}\"\n                    value=\"{{ old('name', $model->name ?? null) }}\"\n                    autofocus\n                >\n            </x-waterhole::field>\n        blade;\n    }\n\n    public function validating(Validator $validator): void\n    {\n        $validator->addRules(['name' => ['required', 'string', 'max:255']]);\n    }\n\n    public function saving(FormRequest $request): void\n    {\n        $this->model->name = $request->validated('name');\n    }\n}\n"
  },
  {
    "path": "src/Forms/Fields/ReactionTypeScore.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms\\Fields;\n\nuse Illuminate\\Foundation\\Http\\FormRequest;\nuse Illuminate\\Validation\\Validator;\nuse Waterhole\\Forms\\Field;\nuse Waterhole\\Models\\ReactionType;\n\nclass ReactionTypeScore extends Field\n{\n    public function __construct(public ?ReactionType $model) {}\n\n    public function render(): string\n    {\n        return <<<'blade'\n            <x-waterhole::field\n                name=\"score\"\n                :label=\"__('waterhole::cp.reaction-type-score-label')\"\n                :description=\"__('waterhole::cp.reaction-type-score-description')\"\n            >\n                <input\n                    type=\"number\"\n                    name=\"score\"\n                    id=\"{{ $component->id }}\"\n                    value=\"{{ old('score', $model->score ?? 1) }}\"\n                    style=\"width: 10ch\"\n                >\n            </x-waterhole::field>\n        blade;\n    }\n\n    public function validating(Validator $validator): void\n    {\n        $validator->addRules(['score' => ['required', 'integer']]);\n    }\n\n    public function saving(FormRequest $request): void\n    {\n        $this->model->score = $request->validated('score');\n    }\n}\n"
  },
  {
    "path": "src/Forms/Fields/StructureLinkName.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms\\Fields;\n\nuse Illuminate\\Foundation\\Http\\FormRequest;\nuse Illuminate\\Validation\\Validator;\nuse Waterhole\\Forms\\Field;\nuse Waterhole\\Models\\StructureLink;\n\nclass StructureLinkName extends Field\n{\n    public function __construct(public ?StructureLink $model) {}\n\n    public function render(): string\n    {\n        return <<<'blade'\n            <x-waterhole::field\n                name=\"name\"\n                :label=\"__('waterhole::cp.link-name-label')\"\n            >\n                <input\n                    id=\"{{ $component->id }}\"\n                    name=\"name\"\n                    type=\"text\"\n                    value=\"{{ old('name', $model->name ?? '') }}\"\n                >\n            </x-waterhole::field>\n        blade;\n    }\n\n    public function validating(Validator $validator): void\n    {\n        $validator->addRules(['name' => ['required', 'string', 'max:255']]);\n    }\n\n    public function saving(FormRequest $request): void\n    {\n        $this->model->name = $request->validated('name');\n    }\n}\n"
  },
  {
    "path": "src/Forms/Fields/StructureLinkUrl.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms\\Fields;\n\nuse Illuminate\\Foundation\\Http\\FormRequest;\nuse Illuminate\\Validation\\Validator;\nuse Waterhole\\Forms\\Field;\nuse Waterhole\\Models\\StructureLink;\n\nclass StructureLinkUrl extends Field\n{\n    public function __construct(public ?StructureLink $model) {}\n\n    public function render(): string\n    {\n        return <<<'blade'\n            <x-waterhole::field\n                name=\"url\"\n                :label=\"__('waterhole::cp.link-url-label')\"\n            >\n                <input\n                    type=\"text\"\n                    name=\"url\"\n                    id=\"{{ $component->id }}\"\n                    value=\"{{ old('url', $model->href ?? null) }}\"\n                >\n            </x-waterhole::field>\n        blade;\n    }\n\n    public function validating(Validator $validator): void\n    {\n        $validator->addRules(['url' => ['required', 'string', 'max:255']]);\n    }\n\n    public function saving(FormRequest $request): void\n    {\n        $this->model->href = $request->validated('url');\n    }\n}\n"
  },
  {
    "path": "src/Forms/Fields/TagName.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms\\Fields;\n\nuse Illuminate\\Foundation\\Http\\FormRequest;\nuse Illuminate\\Validation\\Validator;\nuse Waterhole\\Forms\\Field;\nuse Waterhole\\Models\\Tag;\n\nclass TagName extends Field\n{\n    public function __construct(public ?Tag $model) {}\n\n    public function render(): string\n    {\n        return <<<'blade'\n            <x-waterhole::field\n                name=\"name\"\n                :label=\"__('waterhole::cp.tag-name-label')\"\n            >\n                <input\n                    type=\"text\"\n                    name=\"name\"\n                    id=\"{{ $component->id }}\"\n                    value=\"{{ old('name', $model->name ?? null) }}\"\n                    autofocus\n                >\n            </x-waterhole::field>\n        blade;\n    }\n\n    public function validating(Validator $validator): void\n    {\n        $validator->addRules(['name' => ['required', 'string', 'max:255']]);\n    }\n\n    public function saving(FormRequest $request): void\n    {\n        $this->model->name = $request->validated('name');\n    }\n}\n"
  },
  {
    "path": "src/Forms/Fields/TaxonomyName.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms\\Fields;\n\nuse Illuminate\\Foundation\\Http\\FormRequest;\nuse Illuminate\\Validation\\Validator;\nuse Waterhole\\Forms\\Field;\nuse Waterhole\\Models\\Taxonomy;\n\nclass TaxonomyName extends Field\n{\n    public function __construct(public ?Taxonomy $model) {}\n\n    public function render(): string\n    {\n        return <<<'blade'\n            <x-waterhole::field\n                name=\"name\"\n                :label=\"__('waterhole::cp.taxonomy-name-label')\"\n            >\n                <input\n                    type=\"text\"\n                    name=\"name\"\n                    id=\"{{ $component->id }}\"\n                    value=\"{{ old('name', $model->name ?? null) }}\"\n                    autofocus\n                >\n            </x-waterhole::field>\n        blade;\n    }\n\n    public function validating(Validator $validator): void\n    {\n        $validator->addRules(['name' => ['required', 'string', 'max:255']]);\n    }\n\n    public function saving(FormRequest $request): void\n    {\n        $this->model->name = $request->validated('name');\n    }\n}\n"
  },
  {
    "path": "src/Forms/Fields/TaxonomyOptions.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms\\Fields;\n\nuse Illuminate\\Foundation\\Http\\FormRequest;\nuse Illuminate\\Validation\\Validator;\nuse Waterhole\\Forms\\Field;\nuse Waterhole\\Models\\Taxonomy;\n\nclass TaxonomyOptions extends Field\n{\n    public function __construct(public ?Taxonomy $model) {}\n\n    public function render(): string\n    {\n        return <<<'blade'\n            <div class=\"field\">\n                <div class=\"field__label\">\n                    {{ __('waterhole::cp.taxonomy-options-title') }}\n                </div>\n\n                <div class=\"stack gap-sm\">\n                    <label class=\"choice\">\n                        <input type=\"hidden\" name=\"is_required\" value=\"0\">\n                        <input type=\"checkbox\" name=\"is_required\" value=\"1\" @checked($model->is_required)>\n                        {{ __('waterhole::cp.taxonomy-required-label') }}\n                    </label>\n                    <label class=\"choice\">\n                        <input type=\"hidden\" name=\"allow_multiple\" value=\"0\">\n                        <input type=\"checkbox\" name=\"allow_multiple\" value=\"1\" @checked($model->allow_multiple)>\n                        {{ __('waterhole::cp.taxonomy-allow-multiple-label') }}\n                    </label>\n                </div>\n            </div>\n        blade;\n    }\n\n    public function validating(Validator $validator): void\n    {\n        $validator->addRules([\n            'is_required' => ['boolean'],\n            'allow_multiple' => ['boolean'],\n        ]);\n    }\n\n    public function saving(FormRequest $request): void\n    {\n        $this->model->is_required = $request->validated('is_required');\n        $this->model->allow_multiple = $request->validated('allow_multiple');\n    }\n}\n"
  },
  {
    "path": "src/Forms/Fields/UserAvatar.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms\\Fields;\n\nuse Illuminate\\Foundation\\Http\\FormRequest;\nuse Illuminate\\Validation\\Validator;\nuse Intervention\\Image\\Laravel\\Facades\\Image;\nuse Waterhole\\Forms\\Field;\nuse Waterhole\\Models\\User;\n\nclass UserAvatar extends Field\n{\n    public function __construct(public User $model) {}\n\n    public function render(): string\n    {\n        return <<<'blade'\n            <div class=\"field\" role=\"group\">\n                <div class=\"field__label\">{{ __('waterhole::user.avatar-label') }}</div>\n                <div class=\"row gap-md\">\n                    <x-waterhole::avatar :user=\"$model\" style=\"width: 10ch\"/>\n                    <div class=\"stack gap-md\">\n                        <x-waterhole::field name=\"avatar\">\n                            <input\n                                type=\"file\"\n                                name=\"avatar\"\n                                accept=\".jpg,.jpeg,.png,.bmp,.gif,.webp\"\n                            >\n                        </x-waterhole::field>\n                        @if ($model->avatar)\n                            <label class=\"choice\">\n                                <input type=\"checkbox\" name=\"remove_avatar\" value=\"1\">\n                                {{ __('waterhole::user.remove-avatar-label') }}\n                            </label>\n                        @endif\n                    </div>\n                </div>\n            </div>\n        blade;\n    }\n\n    public function validating(Validator $validator): void\n    {\n        $validator->addRules(['avatar' => ['nullable', 'image']]);\n    }\n\n    public function saved(FormRequest $request): void\n    {\n        if ($request->input('remove_avatar')) {\n            $this->model->removeAvatar();\n        } elseif ($file = $request->file('avatar')) {\n            $this->model->uploadAvatar(Image::read($file));\n        }\n    }\n}\n"
  },
  {
    "path": "src/Forms/Fields/UserBio.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms\\Fields;\n\nuse Illuminate\\Foundation\\Http\\FormRequest;\nuse Illuminate\\Validation\\Validator;\nuse Waterhole\\Forms\\Field;\nuse Waterhole\\Models\\User;\n\nclass UserBio extends Field\n{\n    public function __construct(public ?User $model) {}\n\n    public function render(): string\n    {\n        return <<<'blade'\n            <x-waterhole::field\n                name=\"bio\"\n                :label=\"__('waterhole::user.bio-label')\"\n                :description=\"__('waterhole::user.bio-description')\"\n            >\n                <textarea\n                    id=\"{{ $component->id }}\"\n                    type=\"text\"\n                    name=\"bio\"\n                    maxlength=\"255\"\n                >{{ old('bio', $model?->bio) }}</textarea>\n            </x-waterhole::field>\n        blade;\n    }\n\n    public function validating(Validator $validator): void\n    {\n        $validator->addRules(['bio' => ['nullable', 'string', 'max:255']]);\n    }\n\n    public function saving(FormRequest $request): void\n    {\n        $this->model->bio = $request->validated('bio');\n    }\n}\n"
  },
  {
    "path": "src/Forms/Fields/UserEmail.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms\\Fields;\n\nuse Illuminate\\Foundation\\Http\\FormRequest;\nuse Illuminate\\Validation\\Rule;\nuse Illuminate\\Validation\\Validator;\nuse Waterhole\\Auth\\SsoPayload;\nuse Waterhole\\Forms\\Field;\nuse Waterhole\\Models\\User;\n\nclass UserEmail extends Field\n{\n    public function __construct(public ?User $model, public ?SsoPayload $payload = null)\n    {\n        if ($payload) {\n            $model->email = $payload->user->email;\n        }\n    }\n\n    public function render(): string\n    {\n        return <<<'blade'\n            <x-waterhole::field\n                name=\"email\"\n                :label=\"__('waterhole::cp.user-email-label')\"\n            >\n                <input\n                    type=\"email\"\n                    name=\"email\"\n                    id=\"{{ $component->id }}\"\n                    value=\"{{ old('email', $model->email ?? null) }}\"\n                    @disabled($payload)\n                >\n            </x-waterhole::field>\n        blade;\n    }\n\n    public function validating(Validator $validator): void\n    {\n        if ($this->payload) {\n            return;\n        }\n\n        $validator->addRules([\n            'email' => [\n                'required',\n                'string',\n                'email',\n                'max:255',\n                Rule::unique(User::class)->ignore($this->model),\n            ],\n        ]);\n    }\n\n    public function saving(FormRequest $request): void\n    {\n        if ($this->payload) {\n            return;\n        }\n\n        $this->model->email = $request->validated('email');\n    }\n}\n"
  },
  {
    "path": "src/Forms/Fields/UserGroups.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms\\Fields;\n\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Foundation\\Http\\FormRequest;\nuse Illuminate\\Validation\\Rule;\nuse Illuminate\\Validation\\Validator;\nuse Waterhole\\Forms\\Field;\nuse Waterhole\\Models\\Group;\nuse Waterhole\\Models\\User;\n\nclass UserGroups extends Field\n{\n    public Collection $groups;\n\n    public function __construct(public ?User $model)\n    {\n        $this->groups = Group::selectable()->get();\n    }\n\n    public function render(): string\n    {\n        return <<<'blade'\n            <div class=\"field\">\n                <div class=\"field__label\">\n                    {{ __('waterhole::cp.user-groups-label') }}\n                </div>\n                <div class=\"stack gap-sm\">\n                    <input type=\"hidden\" name=\"groups\" value=\"\">\n\n                    @foreach ($groups as $group)\n                        <label class=\"choice\">\n                            <input\n                                type=\"checkbox\"\n                                name=\"groups[]\"\n                                value=\"{{ $group->id }}\"\n                                @checked(in_array($group->id, (array) old('groups', isset($model) ? $model->groups->pluck('id')->all() : [])))\n                                @disabled($enforce = $group->isAdmin() && $model?->isRootAdmin())\n                            >\n                            <x-waterhole::group-badge :group=\"$group\"/>\n                            @if ($enforce)\n                                <input type=\"hidden\" name=\"groups[]\" value=\"{{ $group->id }}\">\n                            @endif\n                        </label>\n                    @endforeach\n                </div>\n            </div>\n        blade;\n    }\n\n    public function validating(Validator $validator): void\n    {\n        $validator->addRules([\n            'groups' => [\n                'nullable',\n                'array',\n                function ($attribute, $value, $fail) {\n                    if ($this->model->isRootAdmin() && !in_array(Group::ADMIN_ID, $value)) {\n                        $fail('Cannot revoke the admin status of a root admin.');\n                    }\n                },\n            ],\n            'groups.*' => [\n                'integer',\n                Rule::exists(Group::class, 'id')->whereNotIn('id', [\n                    Group::GUEST_ID,\n                    Group::MEMBER_ID,\n                ]),\n            ],\n        ]);\n    }\n\n    public function saved(FormRequest $request): void\n    {\n        if ($request->has('groups')) {\n            $this->model->groups()->sync($request->validated('groups'));\n        }\n    }\n}\n"
  },
  {
    "path": "src/Forms/Fields/UserHeadline.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms\\Fields;\n\nuse Illuminate\\Foundation\\Http\\FormRequest;\nuse Illuminate\\Validation\\Validator;\nuse Waterhole\\Forms\\Field;\nuse Waterhole\\Models\\User;\n\nclass UserHeadline extends Field\n{\n    public function __construct(public ?User $model) {}\n\n    public function render(): string\n    {\n        return <<<'blade'\n            <x-waterhole::field\n                name=\"headline\"\n                :label=\"__('waterhole::user.headline-label')\"\n                :description=\"__('waterhole::user.headline-description')\"\n            >\n                <input\n                    id=\"{{ $component->id }}\"\n                    type=\"text\"\n                    name=\"headline\"\n                    value=\"{{ old('headline', $model?->headline) }}\"\n                    maxlength=\"30\"\n                >\n            </x-waterhole::field>\n        blade;\n    }\n\n    public function validating(Validator $validator): void\n    {\n        $validator->addRules(['headline' => ['nullable', 'string', 'max:30']]);\n    }\n\n    public function saving(FormRequest $request): void\n    {\n        $this->model->headline = $request->validated('headline');\n    }\n}\n"
  },
  {
    "path": "src/Forms/Fields/UserLocation.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms\\Fields;\n\nuse Illuminate\\Foundation\\Http\\FormRequest;\nuse Illuminate\\Validation\\Validator;\nuse Waterhole\\Forms\\Field;\nuse Waterhole\\Models\\User;\n\nclass UserLocation extends Field\n{\n    public function __construct(public ?User $model) {}\n\n    public function render(): string\n    {\n        return <<<'blade'\n            <x-waterhole::field\n                name=\"location\"\n                :label=\"__('waterhole::user.location-label')\"\n            >\n                <input\n                    id=\"{{ $component->id }}\"\n                    type=\"text\"\n                    name=\"location\"\n                    value=\"{{ old('location', $model?->location) }}\"\n                    maxlength=\"30\"\n                >\n            </x-waterhole::field>\n        blade;\n    }\n\n    public function validating(Validator $validator): void\n    {\n        $validator->addRules(['location' => ['nullable', 'string', 'max:30']]);\n    }\n\n    public function saving(FormRequest $request): void\n    {\n        $this->model->location = $request->validated('location');\n    }\n}\n"
  },
  {
    "path": "src/Forms/Fields/UserName.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms\\Fields;\n\nuse Illuminate\\Foundation\\Http\\FormRequest;\nuse Illuminate\\Validation\\Rule;\nuse Illuminate\\Validation\\Validator;\nuse Waterhole\\Auth\\SsoPayload;\nuse Waterhole\\Forms\\Field;\nuse Waterhole\\Models\\User;\n\nclass UserName extends Field\n{\n    public function __construct(public ?User $model, public ?SsoPayload $payload = null)\n    {\n        if ($payload) {\n            $model->name = $payload->user->name;\n        }\n    }\n\n    public function render(): string\n    {\n        return <<<'blade'\n            <x-waterhole::field\n                name=\"name\"\n                :label=\"__('waterhole::cp.user-name-label')\"\n            >\n                @if ($payload?->user->forceName ?? false)\n                    <span>{{ $payload->user->name }}</span>\n                @else\n                    <input\n                        type=\"text\"\n                        name=\"name\"\n                        id=\"{{ $component->id }}\"\n                        value=\"{{ old('name', $model->name ?? null) }}\"\n                        autofocus\n                    >\n                @endif\n            </x-waterhole::field>\n        blade;\n    }\n\n    public function validating(Validator $validator): void\n    {\n        if ($this->payload?->user->forceName ?? false) {\n            $validator->setData(\n                array_replace($validator->getData(), ['name' => $this->payload->user->name]),\n            );\n        }\n\n        $validator->addRules([\n            'name' => [\n                'required',\n                'string',\n                'max:255',\n                // Name cannot contain @, non-space whitespace characters, more\n                // than one space in a row, or only numbers.\n                'not_regex:/@|[^\\S ]|\\s{2,}|^\\d+$/',\n                'not_in:admin',\n                Rule::unique(User::class)->ignore($this->model),\n            ],\n        ]);\n    }\n\n    public function saving(FormRequest $request): void\n    {\n        $this->model->name = $request->validated('name');\n    }\n}\n"
  },
  {
    "path": "src/Forms/Fields/UserPassword.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms\\Fields;\n\nuse Illuminate\\Foundation\\Http\\FormRequest;\nuse Illuminate\\Support\\Facades\\Hash;\nuse Illuminate\\Validation\\Rules\\Password;\nuse Illuminate\\Validation\\Validator;\nuse Waterhole\\Auth\\SsoPayload;\nuse Waterhole\\Forms\\Field;\nuse Waterhole\\Models\\User;\n\nclass UserPassword extends Field\n{\n    public function __construct(public User $model, public ?SsoPayload $payload = null) {}\n\n    public function shouldRender(): bool\n    {\n        return !$this->payload;\n    }\n\n    public function render(): string\n    {\n        return <<<'blade'\n            <x-waterhole::field\n                name=\"password\"\n                :label=\"__('waterhole::cp.user-password-label')\"\n            >\n                @if ($model->exists)\n                    <div data-controller=\"reveal\" class=\"stack gap-sm\">\n                        <label class=\"choice\">\n                            <input type=\"checkbox\" data-reveal-target=\"if\">\n                            {{ __('waterhole::cp.user-set-password-label') }}\n                        </label>\n                @endif\n                        <input\n                            data-reveal-target=\"then\"\n                            type=\"password\"\n                            name=\"password\"\n                            id=\"{{ $component->id }}\"\n\n                        >\n                @if ($model->exists)\n                    </div>\n                @endif\n            </x-waterhole::field>\n        blade;\n    }\n\n    public function validating(Validator $validator): void\n    {\n        if ($this->payload) {\n            return;\n        }\n\n        $validator->addRules([\n            'password' => [$this->model->exists ? 'nullable' : 'required', Password::defaults()],\n        ]);\n    }\n\n    public function saving(FormRequest $request): void\n    {\n        if ($this->payload) {\n            return;\n        }\n\n        if ($password = $request->validated('password')) {\n            $this->model->password = Hash::make($password);\n        }\n    }\n}\n"
  },
  {
    "path": "src/Forms/Fields/UserShowOnline.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms\\Fields;\n\nuse Illuminate\\Foundation\\Http\\FormRequest;\nuse Illuminate\\Validation\\Validator;\nuse Waterhole\\Forms\\Field;\nuse Waterhole\\Models\\User;\n\nclass UserShowOnline extends Field\n{\n    public function __construct(public ?User $model) {}\n\n    public function render(): string\n    {\n        return <<<'blade'\n            <div role=\"group\" class=\"field\">\n                <div class=\"field__label\">{{ __('waterhole::user.privacy-title') }}</div>\n                <div>\n                    <input type=\"hidden\" name=\"show_online\" value=\"0\">\n                    <label for=\"show_online\" class=\"choice\">\n                        <input\n                            id=\"show_online\"\n                            type=\"checkbox\"\n                            name=\"show_online\"\n                            value=\"1\"\n                            @checked($model?->show_online)\n                        >\n                        {{ __('waterhole::user.show-online-label') }}\n                    </label>\n                </div>\n            </div>\n        blade;\n    }\n\n    public function validating(Validator $validator): void\n    {\n        $validator->addRules(['show_online' => ['boolean']]);\n    }\n\n    public function saving(FormRequest $request): void\n    {\n        $this->model->show_online = $request->validated('show_online');\n    }\n}\n"
  },
  {
    "path": "src/Forms/Fields/UserWebsite.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms\\Fields;\n\nuse Illuminate\\Foundation\\Http\\FormRequest;\nuse Illuminate\\Validation\\Validator;\nuse Waterhole\\Forms\\Field;\nuse Waterhole\\Models\\User;\n\nclass UserWebsite extends Field\n{\n    public function __construct(public ?User $model) {}\n\n    public function render(): string\n    {\n        return <<<'blade'\n            <x-waterhole::field name=\"website\" :label=\"__('waterhole::user.website-label')\">\n                <input\n                    id=\"{{ $component->id }}\"\n                    type=\"text\"\n                    name=\"website\"\n                    value=\"{{ old('website', $model?->website) }}\"\n                    maxlength=\"100\"\n                >\n            </x-waterhole::field>\n        blade;\n    }\n\n    public function validating(Validator $validator): void\n    {\n        $validator->addRules(['website' => ['nullable', 'string', 'max:100']]);\n    }\n\n    public function saving(FormRequest $request): void\n    {\n        $this->model->website = $request->validated('website');\n    }\n}\n"
  },
  {
    "path": "src/Forms/Form.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms;\n\nuse Illuminate\\Foundation\\Http\\FormRequest;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Support\\Facades\\DB;\nuse Waterhole\\Models\\Model;\n\nabstract class Form\n{\n    private array $fields;\n\n    public function __construct(public Model $model) {}\n\n    abstract public function fields(): array;\n\n    /**\n     * Validate the request and save the model.\n     */\n    public function submit(Request $request): bool\n    {\n        $formRequest = $this->validate($request);\n\n        return $this->save($formRequest);\n    }\n\n    /**\n     * Validate the request.\n     */\n    public function validate(Request $request): FormRequest\n    {\n        $validator = validator($request->all());\n\n        foreach ($this->getFields() as $field) {\n            $field->validating($validator);\n        }\n\n        $formRequest = FormRequest::createFrom($request);\n\n        $formRequest->setValidator($validator);\n\n        $validator->validate();\n\n        return $formRequest;\n    }\n\n    /**\n     * Save the model.\n     */\n    public function save(FormRequest $request): bool\n    {\n        return DB::transaction(function () use ($request) {\n            foreach ($this->getFields() as $field) {\n                $field->saving($request);\n            }\n\n            if (!$this->model->save()) {\n                return false;\n            }\n\n            foreach ($this->getFields() as $field) {\n                $field->saved($request);\n            }\n\n            return true;\n        });\n    }\n\n    private function getFields(): array\n    {\n        return $this->fields ??= array_filter(\n            $this->fields(),\n            fn($field) => $field?->shouldRender(),\n        );\n    }\n}\n"
  },
  {
    "path": "src/Forms/FormSection.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms;\n\nuse Illuminate\\Foundation\\Http\\FormRequest;\nuse Illuminate\\Validation\\Validator;\nuse function Waterhole\\build_components;\n\nclass FormSection extends Field\n{\n    public array $components;\n\n    public function __construct(public string $title, public array $items, public bool $open = true)\n    {\n        $this->components = build_components($items);\n    }\n\n    public function shouldRender(): bool\n    {\n        return !!array_filter($this->components, fn($component) => $component->shouldRender());\n    }\n\n    public function render(): string\n    {\n        return <<<'blade'\n            <details class=\"card\" {{ $attributes->merge(['open' => $open]) }}>\n                <summary class=\"card__header h5\">\n                    {{ $title }}\n                </summary>\n\n                <div class=\"card__body stack dividers\">\n                    @components($components)\n                </div>\n            </details>\n        blade;\n    }\n\n    public function validating(Validator $validator): void\n    {\n        $this->call('validating', $validator);\n    }\n\n    public function saving(FormRequest $request): void\n    {\n        $this->call('saving', $request);\n    }\n\n    public function saved(FormRequest $request): void\n    {\n        $this->call('saved', $request);\n    }\n\n    private function call(string $method, ...$arguments): void\n    {\n        foreach ($this->items as $item) {\n            if ($item instanceof Field) {\n                $item->$method(...$arguments);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/Forms/GroupForm.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms;\n\nuse Waterhole\\Models\\Group;\n\nclass GroupForm extends Form\n{\n    public function __construct(Group $group)\n    {\n        parent::__construct($group);\n    }\n\n    public function fields(): array\n    {\n        return resolve(\\Waterhole\\Extend\\Forms\\GroupForm::class)->components([\n            'model' => $this->model,\n        ]);\n    }\n}\n"
  },
  {
    "path": "src/Forms/PageForm.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms;\n\nuse Waterhole\\Models\\Page;\n\nclass PageForm extends Form\n{\n    public function __construct(Page $page)\n    {\n        parent::__construct($page);\n    }\n\n    public function fields(): array\n    {\n        return resolve(\\Waterhole\\Extend\\Forms\\PageForm::class)->components([\n            'model' => $this->model,\n        ]);\n    }\n}\n"
  },
  {
    "path": "src/Forms/PostForm.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms;\n\nuse Waterhole\\Models\\Post;\n\nclass PostForm extends Form\n{\n    public function __construct(Post $post)\n    {\n        parent::__construct($post);\n    }\n\n    public function fields(): array\n    {\n        return resolve(\\Waterhole\\Extend\\Forms\\PostForm::class)->components([\n            'model' => $this->model,\n        ]);\n    }\n}\n"
  },
  {
    "path": "src/Forms/ReactionSetForm.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms;\n\nuse Waterhole\\Models\\ReactionSet;\n\nclass ReactionSetForm extends Form\n{\n    public function __construct(ReactionSet $reactionSet)\n    {\n        parent::__construct($reactionSet);\n    }\n\n    public function fields(): array\n    {\n        return resolve(\\Waterhole\\Extend\\Forms\\ReactionSetForm::class)->components([\n            'model' => $this->model,\n        ]);\n    }\n}\n"
  },
  {
    "path": "src/Forms/ReactionTypeForm.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms;\n\nuse Waterhole\\Models\\ReactionType;\n\nclass ReactionTypeForm extends Form\n{\n    public function __construct(ReactionType $reactionType)\n    {\n        parent::__construct($reactionType);\n    }\n\n    public function fields(): array\n    {\n        return resolve(\\Waterhole\\Extend\\Forms\\ReactionTypeForm::class)->components([\n            'model' => $this->model,\n        ]);\n    }\n}\n"
  },
  {
    "path": "src/Forms/RegistrationForm.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms;\n\nuse Waterhole\\Auth\\SsoPayload;\nuse Waterhole\\Models\\User;\n\nclass RegistrationForm extends Form\n{\n    public function __construct(User $user, public ?SsoPayload $payload = null)\n    {\n        parent::__construct($user);\n    }\n\n    public function fields(): array\n    {\n        return resolve(\\Waterhole\\Extend\\Forms\\RegistrationForm::class)->components([\n            'model' => $this->model,\n            'payload' => $this->payload,\n        ]);\n    }\n}\n"
  },
  {
    "path": "src/Forms/StructureLinkForm.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms;\n\nuse Waterhole\\Models\\StructureLink;\n\nclass StructureLinkForm extends Form\n{\n    public function __construct(StructureLink $link)\n    {\n        parent::__construct($link);\n    }\n\n    public function fields(): array\n    {\n        return resolve(\\Waterhole\\Extend\\Forms\\StructureLinkForm::class)->components([\n            'model' => $this->model,\n        ]);\n    }\n}\n"
  },
  {
    "path": "src/Forms/TagForm.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms;\n\nuse Waterhole\\Models\\Tag;\n\nclass TagForm extends Form\n{\n    public function __construct(Tag $tag)\n    {\n        parent::__construct($tag);\n    }\n\n    public function fields(): array\n    {\n        return resolve(\\Waterhole\\Extend\\Forms\\TagForm::class)->components([\n            'model' => $this->model,\n        ]);\n    }\n}\n"
  },
  {
    "path": "src/Forms/TaxonomyForm.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms;\n\nuse Waterhole\\Forms\\Fields\\Permissions;\nuse Waterhole\\Models\\Taxonomy;\n\nclass TaxonomyForm extends Form\n{\n    public function __construct(Taxonomy $taxonomy)\n    {\n        parent::__construct($taxonomy);\n    }\n\n    public function fields(): array\n    {\n        return [\n            new FormSection(\n                __('waterhole::cp.taxonomy-details-title'),\n                resolve(\\Waterhole\\Extend\\Forms\\TaxonomyForm::class)->components([\n                    'model' => $this->model,\n                ]),\n            ),\n            new FormSection(\n                __('waterhole::cp.taxonomy-permissions-title'),\n                [new Permissions($this->model)],\n                open: false,\n            ),\n        ];\n    }\n}\n"
  },
  {
    "path": "src/Forms/UserForm.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms;\n\nuse Waterhole\\Models\\User;\n\nclass UserForm extends Form\n{\n    public function __construct(User $user)\n    {\n        parent::__construct($user);\n    }\n\n    public function fields(): array\n    {\n        return resolve(\\Waterhole\\Extend\\Forms\\UserForm::class)->components([\n            'model' => $this->model,\n        ]);\n    }\n}\n"
  },
  {
    "path": "src/Forms/UserProfileForm.php",
    "content": "<?php\n\nnamespace Waterhole\\Forms;\n\nuse Waterhole\\Models\\User;\n\nclass UserProfileForm extends Form\n{\n    public function __construct(User $user)\n    {\n        parent::__construct($user);\n    }\n\n    public function fields(): array\n    {\n        return resolve(\\Waterhole\\Extend\\Forms\\UserForm::class)->profile->components([\n            'model' => $this->model,\n        ]);\n    }\n}\n"
  },
  {
    "path": "src/Http/Controllers/ActionsController.php",
    "content": "<?php\n\nnamespace Waterhole\\Http\\Controllers;\n\nuse HotwiredLaravel\\TurboLaravel\\Http\\TurboResponseFactory;\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Validation\\ValidationException;\nuse Waterhole\\Actions\\Action;\nuse Waterhole\\Extend\\Core\\Actions;\nuse Waterhole\\View\\Components\\Alert;\nuse Waterhole\\View\\Components\\MenuDivider;\nuse Waterhole\\View\\TurboStream;\n\n/**\n * Controller for endpoints related to the Actions system.\n */\nclass ActionsController extends Controller\n{\n    /**\n     * Render the contents of the actions menu for a set of models.\n     */\n    public function menu(Request $request)\n    {\n        $models = $this->getModels($request);\n\n        $actions = collect(resolve(Actions::class)->actionsFor($models))\n            ->filter(\n                fn($action) => !$action instanceof Action ||\n                    $action->shouldRender($models, $request->input('context')),\n            )\n            ->values();\n\n        $actions = $actions->reject(\n            fn($action, $i) => $action instanceof MenuDivider &&\n                ($i === 0 || $i === $actions->count() - 1),\n        );\n\n        return view('waterhole::actions.menu', compact('actions', 'models'));\n    }\n\n    /**\n     * Prompt the user to confirm that they want to run an action.\n     *\n     * This view displays a dialog with a form containing the action's custom\n     * confirmation body, and buttons to confirm the action or go back to the\n     * previous page.\n     */\n    public function confirm(Request $request)\n    {\n        $models = $this->getModels($request);\n        $action = $this->getAction($models, $request);\n\n        return view('waterhole::actions.confirm', [\n            'action' => $action,\n            'actionable' => $request->input('actionable'),\n            'models' => $models,\n        ]);\n    }\n\n    /**\n     * Run an action.\n     */\n    public function run(Request $request)\n    {\n        $models = $this->getModels($request);\n        $action = $this->getAction($models, $request);\n\n        // If the action requires confirmation, but it has not been confirmed\n        // by pressing the submit button in the confirmation view, then we\n        // will redirect the user back to the confirmation view with all the\n        // same input.\n        if ($action->shouldConfirm($models) && !$request->has('confirmed')) {\n            return redirect()->route('waterhole.actions.create', $request->input());\n        }\n\n        // Attempt to run the action. If we catch a validation exception, we\n        // will redirect the user back to the confirmation view with all the\n        // same input, and the confirmation view can render the errors.\n        try {\n            $response = $action->run($models);\n        } catch (ValidationException $exception) {\n            throw $exception->redirectTo(route('waterhole.actions.create', $request->input()));\n        }\n\n        // If the client supports Turbo Streams, we will return streams for\n        // each of the actioned models from the action class. We will also\n        // add on streams for any alerts that the action may have flashed.\n        if (\n            $request->wantsTurboStream() &&\n            ($streams = $models->flatMap(fn($item) => $action->stream($item))->all())\n        ) {\n            foreach (['success', 'warning', 'danger'] as $type) {\n                if ($message = session()->get($type)) {\n                    $streams[] = TurboStream::append(\n                        (new Alert($type, $message))->withAttributes(['data-key' => \"flash-$type\"]),\n                        '#alerts',\n                    );\n                }\n            }\n\n            session()->ageFlashData();\n\n            return TurboResponseFactory::makeStream(implode(PHP_EOL, $streams));\n        }\n\n        if ($response) {\n            return $response;\n        }\n\n        return redirect($request->get('return', url()->previous()));\n    }\n\n    /**\n     * Retrieve the model instances that are to be actioned.\n     *\n     * The model instances are retrieved based on the model class name in the\n     * \"actionable\" input and the \"id\" inputs in the form submission.\n     */\n    private function getModels(Request $request): Collection\n    {\n        $actionable = $request->input('actionable');\n\n        $actions = resolve(Actions::class);\n\n        if (\n            !$actionable ||\n            !class_exists($actionable) ||\n            !is_subclass_of($actionable, \\Waterhole\\Models\\Model::class) ||\n            !$actions->hasList($actionable)\n        ) {\n            abort(400, \"The actionable [$actionable] does not exist\");\n        }\n\n        $models = $actionable::findMany((array) $request->input('id'));\n\n        if (!$models->count()) {\n            abort(400, 'No models found.');\n        }\n\n        return $models;\n    }\n\n    /**\n     * Get an instance of the action that is to be applied.\n     *\n     * The action that is to be applied is specified in the \"action_class\"\n     * input. However, before instantiating it, we ensure that it is one of\n     * the registered actions that is allowed to be applied to these models.\n     */\n    private function getAction(Collection $models, Request $request): Action\n    {\n        $actions = resolve(Actions::class)->actionsFor($models);\n        $requestedAction = $request->get('action_class');\n\n        foreach ($actions as $action) {\n            if (get_class($action) === $requestedAction) {\n                return $action;\n            }\n        }\n\n        abort(403, 'This action cannot be applied.');\n    }\n}\n"
  },
  {
    "path": "src/Http/Controllers/Api/ApiController.php",
    "content": "<?php\n\nnamespace Waterhole\\Http\\Controllers\\Api;\n\nuse Illuminate\\Support\\Facades\\DB;\nuse Psr\\Http\\Message\\ServerRequestInterface as Request;\nuse Throwable;\nuse Tobyz\\JsonApiServer\\Exception\\ErrorProvider;\nuse Waterhole\\Extend\\Api\\JsonApi;\nuse Waterhole\\Http\\Controllers\\Controller;\n\nclass ApiController extends Controller\n{\n    public function __construct(private readonly JsonApi $api) {}\n\n    public function __invoke(Request $request)\n    {\n        try {\n            return DB::transaction(fn() => $this->api->handle($request));\n        } catch (Throwable $e) {\n            // If debug mode is on, and the exception that has occurred is not\n            // JSON:API-friendly, then re-throw it so that it will bubble up\n            // to the Laravel exception handler which will show full info.\n            if (!$e instanceof ErrorProvider) {\n                if (config('app.debug')) {\n                    throw $e;\n                }\n\n                report($e);\n            }\n\n            return $this->api->error($e);\n        }\n    }\n}\n"
  },
  {
    "path": "src/Http/Controllers/Auth/ConfirmPasswordController.php",
    "content": "<?php\n\nnamespace Waterhole\\Http\\Controllers\\Auth;\n\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Support\\Facades\\Auth;\nuse Illuminate\\Validation\\ValidationException;\nuse Waterhole\\Http\\Controllers\\Controller;\n\nclass ConfirmPasswordController extends Controller\n{\n    public function __construct()\n    {\n        $this->middleware('waterhole.auth');\n    }\n\n    public function showConfirmForm()\n    {\n        return view('waterhole::auth.confirm-password');\n    }\n\n    public function confirm(Request $request)\n    {\n        if (\n            !Auth::guard('web')->validate([\n                'email' => $request->user()->email,\n                'password' => $request->password,\n            ])\n        ) {\n            throw ValidationException::withMessages([\n                'password' => __('waterhole::auth.password'),\n            ]);\n        }\n\n        $request->session()->put('auth.password_confirmed_at', time());\n\n        return redirect()->intended(route('waterhole.home'));\n    }\n}\n"
  },
  {
    "path": "src/Http/Controllers/Auth/ForgotPasswordController.php",
    "content": "<?php\n\nnamespace Waterhole\\Http\\Controllers\\Auth;\n\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Support\\Facades\\Password;\nuse Waterhole\\Http\\Controllers\\Controller;\n\nclass ForgotPasswordController extends Controller\n{\n    public function __construct()\n    {\n        $this->middleware('waterhole.guest');\n    }\n\n    public function showLinkRequestForm()\n    {\n        return view('waterhole::auth.forgot-password');\n    }\n\n    public function sendResetLinkEmail(Request $request)\n    {\n        $request->validate([\n            'email' => 'required|email',\n        ]);\n\n        // We will send the password reset link to this user. Once we have attempted\n        // to send the link, we will examine the response then see the message we\n        // need to show to the user. Finally, we'll send out a proper response.\n        $status = Password::sendResetLink($request->only('email'));\n\n        return $status == Password::RESET_LINK_SENT\n            ? back()->with('status', __(\"waterhole::$status\"))\n            : back()\n                ->withInput($request->only('email'))\n                ->withErrors(['email' => __(\"waterhole::$status\")]);\n    }\n}\n"
  },
  {
    "path": "src/Http/Controllers/Auth/LoginController.php",
    "content": "<?php\n\nnamespace Waterhole\\Http\\Controllers\\Auth;\n\nuse Illuminate\\Auth\\Events\\Lockout;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Support\\Facades\\Auth;\nuse Illuminate\\Support\\Facades\\RateLimiter;\nuse Illuminate\\Support\\Str;\nuse Illuminate\\Validation\\ValidationException;\nuse Waterhole\\Auth\\Providers;\nuse Waterhole\\Http\\Controllers\\Controller;\n\nclass LoginController extends Controller\n{\n    public function __construct()\n    {\n        $this->middleware('waterhole.guest');\n    }\n\n    public function showLoginForm(Request $request, Providers $providers)\n    {\n        if (!redirect()->getIntendedUrl()) {\n            // Copy any URL passed in the `return` query parameter into the session\n            // so that after the login is complete we can redirect back to it.\n            redirect()->setIntendedUrl($request->query('return', url()->previous()));\n        }\n\n        if (!config('waterhole.auth.password_enabled', true) && ($provider = $providers->sole())) {\n            return redirect()->route('waterhole.sso.login', compact('provider'));\n        }\n\n        return view('waterhole::auth.login');\n    }\n\n    public function login(Request $request)\n    {\n        $request->validate([\n            'email' => 'required|string|email',\n            'password' => 'required|string',\n        ]);\n\n        $this->ensureIsNotRateLimited($request);\n\n        if (!Auth::attempt($request->only('email', 'password'), $request->boolean('remember'))) {\n            RateLimiter::hit($this->throttleKey($request));\n\n            throw ValidationException::withMessages([\n                'email' => __('waterhole::auth.failed'),\n            ]);\n        }\n\n        RateLimiter::clear($this->throttleKey($request));\n\n        $request->session()->regenerate();\n\n        $request->session()->put('auth.password_confirmed_at', time());\n\n        return redirect()->intended(route('waterhole.home'));\n    }\n\n    private function ensureIsNotRateLimited(Request $request)\n    {\n        if (!RateLimiter::tooManyAttempts($this->throttleKey($request), 5)) {\n            return;\n        }\n\n        event(new Lockout($request));\n\n        $seconds = RateLimiter::availableIn($this->throttleKey($request));\n\n        throw ValidationException::withMessages([\n            'email' => trans('waterhole::auth.throttle', [\n                'seconds' => $seconds,\n                'minutes' => ceil($seconds / 60),\n            ]),\n        ]);\n    }\n\n    private function throttleKey(Request $request)\n    {\n        return Str::lower($request->input('email')) . '|' . $request->ip();\n    }\n}\n"
  },
  {
    "path": "src/Http/Controllers/Auth/LogoutController.php",
    "content": "<?php\n\nnamespace Waterhole\\Http\\Controllers\\Auth;\n\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Support\\Facades\\Auth;\nuse Waterhole\\Http\\Controllers\\Controller;\n\nclass LogoutController extends Controller\n{\n    public function __construct()\n    {\n        $this->middleware('waterhole.auth');\n    }\n\n    public function logout(Request $request)\n    {\n        Auth::logout();\n\n        $request->session()->invalidate();\n\n        $request->session()->regenerateToken();\n\n        return redirect(route('waterhole.home'));\n    }\n}\n"
  },
  {
    "path": "src/Http/Controllers/Auth/RegisterController.php",
    "content": "<?php\n\nnamespace Waterhole\\Http\\Controllers\\Auth;\n\nuse Illuminate\\Auth\\Events\\Registered;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Support\\Facades\\Auth;\nuse Intervention\\Image\\Laravel\\Facades\\Image;\nuse Waterhole\\Auth\\Providers;\nuse Waterhole\\Auth\\SsoPayload;\nuse Waterhole\\Forms\\RegistrationForm;\nuse Waterhole\\Http\\Controllers\\Controller;\nuse Waterhole\\Models\\Group;\nuse Waterhole\\Models\\User;\n\nclass RegisterController extends Controller\n{\n    public function __construct()\n    {\n        $this->middleware('waterhole.guest');\n    }\n\n    public function showRegistrationForm(Request $request, Providers $providers)\n    {\n        // Copy any URL passed in the `return` query parameter into the session\n        // so that after the registration is complete we can redirect back to it.\n        if (!redirect()->getIntendedUrl()) {\n            redirect()->setIntendedUrl($request->query('return', url()->previous()));\n        }\n\n        $form = $this->form(new User());\n\n        if (!config('waterhole.auth.password_enabled', true) && ($provider = $providers->sole())) {\n            return redirect()->route('waterhole.sso.login', compact('provider'));\n        }\n\n        return view('waterhole::auth.register', compact('form'));\n    }\n\n    public function registerWithPayload(string $payload)\n    {\n        $form = $this->form(new User(), $payload);\n\n        return view('waterhole::auth.register', compact('form'));\n    }\n\n    public function register(Request $request)\n    {\n        $form = $this->form($user = new User(), $request->input('payload'));\n\n        $form->submit($request);\n\n        if ($form->payload) {\n            $user->markEmailAsVerified();\n\n            if ($form->payload->user->avatar) {\n                $user->uploadAvatar(Image::read($form->payload->user->avatar));\n            }\n\n            if ($form->payload->user->groups) {\n                $user->groups()->sync($form->payload->user->groups);\n            }\n\n            $user->authProviders()->create([\n                'provider' => $form->payload->provider,\n                'identifier' => $form->payload->user->identifier,\n                'last_login_at' => now(),\n            ]);\n        } elseif (!config('waterhole.auth.password_enabled', true)) {\n            abort(400, 'Password registration is disabled');\n        }\n\n        $user\n            ->groups()\n            ->syncWithoutDetaching(Group::query()->where('auto_assign', true)->pluck('id'));\n\n        event(new Registered($user));\n\n        if (!$request->attributes->has('waterhole_original_user')) {\n            Auth::login($user);\n        }\n\n        // Remove the fragment so that the email verification notice at the top\n        // of the page is visible.\n        return redirect()->intended(route('waterhole.home'))->withoutFragment();\n    }\n\n    private function form(User $user, ?string $payload = null)\n    {\n        return new RegistrationForm($user, $payload ? SsoPayload::decrypt($payload) : null);\n    }\n}\n"
  },
  {
    "path": "src/Http/Controllers/Auth/ResetPasswordController.php",
    "content": "<?php\n\nnamespace Waterhole\\Http\\Controllers\\Auth;\n\nuse Illuminate\\Auth\\Events\\PasswordReset;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Support\\Facades\\Hash;\nuse Illuminate\\Support\\Facades\\Password;\nuse Illuminate\\Support\\Str;\nuse Illuminate\\Validation\\Rules;\nuse Waterhole\\Http\\Controllers\\Controller;\n\nclass ResetPasswordController extends Controller\n{\n    public function __construct()\n    {\n        $this->middleware('waterhole.guest');\n    }\n\n    public function showResetForm(Request $request)\n    {\n        return view('waterhole::auth.reset-password', ['request' => $request]);\n    }\n\n    public function reset(Request $request)\n    {\n        $request->validate([\n            'email' => 'required|email',\n            'password' => ['required', 'confirmed', Rules\\Password::defaults()],\n        ]);\n\n        // Here we will attempt to reset the user's password. If it is successful we\n        // will update the password on an actual user model and persist it to the\n        // database. Otherwise we will parse the error and return the response.\n        $status = Password::reset(\n            $request->only('email', 'password', 'password_confirmation') + [\n                'token' => $request->route('token'),\n            ],\n            function ($user) use ($request) {\n                $user\n                    ->forceFill([\n                        'password' => Hash::make($request->password),\n                        'remember_token' => Str::random(60),\n                    ])\n                    ->save();\n\n                event(new PasswordReset($user));\n            },\n        );\n\n        // If the password was successfully reset, we will redirect the user back to\n        // the application's home authenticated view. If there is an error we can\n        // redirect them back to where they came from with their error message.\n        return $status == Password::PASSWORD_RESET\n            ? redirect()\n                ->route('waterhole.login')\n                ->with('success', __(\"waterhole::$status\"))\n            : back()\n                ->withInput($request->only('email'))\n                ->withErrors(['token' => __(\"waterhole::$status\")]);\n    }\n}\n"
  },
  {
    "path": "src/Http/Controllers/Auth/SsoController.php",
    "content": "<?php\n\nnamespace Waterhole\\Http\\Controllers\\Auth;\n\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Support\\Facades\\Auth;\nuse Laravel\\Socialite\\Facades\\Socialite;\nuse Waterhole\\Auth\\Providers;\nuse Waterhole\\Auth\\SsoPayload;\nuse Waterhole\\Models\\AuthProvider;\nuse Waterhole\\Models\\User;\nuse Waterhole\\Sso\\PendingUser;\n\nclass SsoController\n{\n    public function login(Providers $providers, string $provider)\n    {\n        abort_unless($providers->has($provider), 400);\n\n        return Socialite::driver($provider)->redirect();\n    }\n\n    public function callback(Providers $providers, Request $request, string $provider)\n    {\n        abort_unless($providers->has($provider), 400);\n\n        $externalUser = Socialite::driver($provider)->user();\n\n        $payload = new SsoPayload(\n            $provider,\n            new PendingUser(\n                identifier: $externalUser->getId(),\n                email: $externalUser->getEmail(),\n                name: $externalUser->getNickname() ?: $externalUser->getName(),\n                avatar: $externalUser->getAvatar(),\n                groups: method_exists($externalUser, 'getGroups')\n                    ? $externalUser->getGroups()\n                    : null,\n            ),\n        );\n\n        $record = [\n            'provider' => $payload->provider,\n            'identifier' => $payload->user->identifier,\n        ];\n\n        if ($provider = AuthProvider::firstWhere($record)) {\n            $provider->touch('last_login_at');\n            $user = $provider->user;\n        } elseif ($user = User::firstWhere('email', $payload->user->email)) {\n            $user->authProviders()->create($record + ['last_login_at' => now()]);\n        }\n\n        if (isset($user)) {\n            Auth::login($user, true);\n\n            return redirect()->intended(route('waterhole.home'));\n        }\n\n        if (!config('waterhole.auth.allow_registration', true)) {\n            session()->flash('danger', __('waterhole::auth.failed'));\n\n            return redirect()->route('waterhole.login');\n        }\n\n        if (!redirect()->getIntendedUrl()) {\n            redirect()->setIntendedUrl($request->query('return', url()->previous()));\n        }\n\n        return redirect()->route('waterhole.register.payload', compact('payload'));\n    }\n}\n"
  },
  {
    "path": "src/Http/Controllers/Auth/VerifyEmailController.php",
    "content": "<?php\n\nnamespace Waterhole\\Http\\Controllers\\Auth;\n\nuse Illuminate\\Auth\\Access\\AuthorizationException;\nuse Illuminate\\Auth\\Events\\Verified;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Routing\\Middleware\\ThrottleRequests;\nuse Illuminate\\Routing\\Middleware\\ValidateSignature;\nuse Waterhole\\Http\\Controllers\\Controller;\n\nclass VerifyEmailController extends Controller\n{\n    public function __construct()\n    {\n        $this->middleware('waterhole.auth');\n        $this->middleware(ThrottleRequests::with(maxAttempts: 6));\n        $this->middleware(ValidateSignature::class)->only('verify');\n    }\n\n    public function verify(Request $request)\n    {\n        if (!hash_equals((string) $request->route('id'), (string) $request->user()->getKey())) {\n            throw new AuthorizationException();\n        }\n\n        $user = $request->user();\n        $user->email = $request->query('email');\n        $user->markEmailAsVerified();\n\n        event(new Verified($request->user()));\n\n        return redirect()\n            ->intended(route('waterhole.home'))\n            ->with('success', __('waterhole::auth.email-verification-success-message'));\n    }\n\n    public function resend(Request $request)\n    {\n        if ($request->user()->hasVerifiedEmail()) {\n            return redirect()->intended(route('waterhole.home'));\n        }\n\n        $request->user()->sendEmailVerificationNotification();\n\n        return back()->with(\n            'success',\n            __('waterhole::auth.email-verification-sent-message', [\n                'email' => $request->user()->email,\n            ]),\n        );\n    }\n}\n"
  },
  {
    "path": "src/Http/Controllers/Controller.php",
    "content": "<?php\n\nnamespace Waterhole\\Http\\Controllers;\n\nuse Illuminate\\Foundation\\Auth\\Access\\AuthorizesRequests;\nuse Illuminate\\Foundation\\Bus\\DispatchesJobs;\nuse Illuminate\\Foundation\\Validation\\ValidatesRequests;\nuse Illuminate\\Routing\\Controller as BaseController;\n\n/**\n * Base controller class.\n */\nclass Controller extends BaseController\n{\n    use AuthorizesRequests;\n    use DispatchesJobs;\n    use ValidatesRequests;\n}\n"
  },
  {
    "path": "src/Http/Controllers/Cp/ChannelController.php",
    "content": "<?php\n\nnamespace Waterhole\\Http\\Controllers\\Cp;\n\nuse Illuminate\\Http\\Request;\nuse Waterhole\\Forms\\ChannelForm;\nuse Waterhole\\Http\\Controllers\\Controller;\nuse Waterhole\\Models\\Channel;\n\n/**\n * Controller for CP channel management (create and update).\n *\n * Deletion is handled by the DeleteChannel action.\n */\nclass ChannelController extends Controller\n{\n    public function create()\n    {\n        $form = $this->form(new Channel());\n\n        return view('waterhole::cp.structure.channel', compact('form'));\n    }\n\n    public function store(Request $request)\n    {\n        $this->form(new Channel())->submit($request);\n\n        return redirect()->route('waterhole.cp.structure');\n    }\n\n    public function edit(Channel $channel)\n    {\n        $form = $this->form($channel);\n\n        return view('waterhole::cp.structure.channel', compact('form', 'channel'));\n    }\n\n    public function update(Channel $channel, Request $request)\n    {\n        $this->form($channel)->submit($request);\n\n        return redirect()->route('waterhole.cp.structure');\n    }\n\n    private function form(Channel $channel)\n    {\n        return new ChannelForm($channel);\n    }\n}\n"
  },
  {
    "path": "src/Http/Controllers/Cp/DashboardController.php",
    "content": "<?php\n\nnamespace Waterhole\\Http\\Controllers\\Cp;\n\nuse Waterhole\\Http\\Controllers\\Controller;\n\n/**\n * Controller for CP dashboard.\n */\nclass DashboardController extends Controller\n{\n    public function index()\n    {\n        return view('waterhole::cp.dashboard');\n    }\n\n    /**\n     * Render a widget by its position in the widget configuration.\n     *\n     * This is used for lazily-loaded widgets. Instead of rendering these\n     * widgets straight into the dashboard view, they are lazily-loaded in a\n     * Turbo Frame pointing to this route.\n     */\n    public function widget(int $id)\n    {\n        abort_unless($widget = config('waterhole.cp.widgets.' . $id), 404);\n\n        return view('waterhole::cp.widget', compact('id', 'widget'));\n    }\n}\n"
  },
  {
    "path": "src/Http/Controllers/Cp/GroupController.php",
    "content": "<?php\n\nnamespace Waterhole\\Http\\Controllers\\Cp;\n\nuse Illuminate\\Http\\Request;\nuse Waterhole\\Forms\\GroupForm;\nuse Waterhole\\Http\\Controllers\\Controller;\nuse Waterhole\\Models\\Group;\n\n/**\n * Controller for CP group management (list, create, and update).\n *\n * Deletion is handled by the DeleteGroup action.\n */\nclass GroupController extends Controller\n{\n    public function index()\n    {\n        $groups = Group::withCount('users')->get();\n\n        return view('waterhole::cp.groups.index', compact('groups'));\n    }\n\n    public function create()\n    {\n        $form = $this->form(new Group());\n\n        return view('waterhole::cp.groups.form', compact('form'));\n    }\n\n    public function store(Request $request)\n    {\n        $this->form(new Group())->submit($request);\n\n        return redirect()->route('waterhole.cp.groups.index');\n    }\n\n    public function edit(Group $group)\n    {\n        $form = $this->form($group);\n\n        return view('waterhole::cp.groups.form', compact('form', 'group'));\n    }\n\n    public function update(Group $group, Request $request)\n    {\n        $this->form($group)->submit($request);\n\n        return redirect()->route('waterhole.cp.groups.index');\n    }\n\n    private function form(Group $group)\n    {\n        return new GroupForm($group);\n    }\n}\n"
  },
  {
    "path": "src/Http/Controllers/Cp/PageController.php",
    "content": "<?php\n\nnamespace Waterhole\\Http\\Controllers\\Cp;\n\nuse Illuminate\\Http\\Request;\nuse Waterhole\\Forms\\PageForm;\nuse Waterhole\\Http\\Controllers\\Controller;\nuse Waterhole\\Models\\Page;\n\n/**\n * Controller for CP page management (create and update).\n *\n * Deletion is handled by the DeleteStructure action.\n */\nclass PageController extends Controller\n{\n    public function create()\n    {\n        $form = $this->form(new Page());\n\n        return view('waterhole::cp.structure.page', compact('form'));\n    }\n\n    public function store(Request $request)\n    {\n        $this->form(new Page())->submit($request);\n\n        return redirect()->route('waterhole.cp.structure');\n    }\n\n    public function edit(Page $page)\n    {\n        $form = $this->form($page);\n\n        return view('waterhole::cp.structure.page', compact('form', 'page'));\n    }\n\n    public function update(Page $page, Request $request)\n    {\n        $this->form($page)->submit($request);\n\n        return redirect()->route('waterhole.cp.structure');\n    }\n\n    private function form(Page $page)\n    {\n        return new PageForm($page);\n    }\n}\n"
  },
  {
    "path": "src/Http/Controllers/Cp/ReactionSetController.php",
    "content": "<?php\n\nnamespace Waterhole\\Http\\Controllers\\Cp;\n\nuse Illuminate\\Http\\Request;\nuse Waterhole\\Forms\\ReactionSetForm;\nuse Waterhole\\Http\\Controllers\\Controller;\nuse Waterhole\\Models\\ReactionSet;\n\n/**\n * Controller for CP reaction set management.\n */\nclass ReactionSetController extends Controller\n{\n    public function index()\n    {\n        return view('waterhole::cp.reactions.index', [\n            'reactionSets' => ReactionSet::with('reactionTypes')->get(),\n        ]);\n    }\n\n    public function create()\n    {\n        $form = $this->form(new ReactionSet());\n\n        return view('waterhole::cp.reactions.reaction-set', compact('form'));\n    }\n\n    public function store(Request $request)\n    {\n        $this->form($reactionSet = new ReactionSet())->submit($request);\n\n        return redirect($reactionSet->edit_url);\n    }\n\n    public function edit(ReactionSet $reactionSet)\n    {\n        $form = $this->form($reactionSet);\n\n        return view('waterhole::cp.reactions.reaction-set', compact('form', 'reactionSet'));\n    }\n\n    public function update(ReactionSet $reactionSet, Request $request)\n    {\n        $this->form($reactionSet)->submit($request);\n\n        return redirect($request->input('return', route('waterhole.cp.reaction-sets.index')))->with(\n            'success',\n            __('waterhole::cp.reaction-set-saved-message'),\n        );\n    }\n\n    private function form(ReactionSet $reactionSet)\n    {\n        return new ReactionSetForm($reactionSet);\n    }\n}\n"
  },
  {
    "path": "src/Http/Controllers/Cp/ReactionTypeController.php",
    "content": "<?php\n\nnamespace Waterhole\\Http\\Controllers\\Cp;\n\nuse Illuminate\\Http\\Request;\nuse Waterhole\\Forms\\ReactionTypeForm;\nuse Waterhole\\Http\\Controllers\\Controller;\nuse Waterhole\\Models\\ReactionSet;\nuse Waterhole\\Models\\ReactionType;\n\n/**\n * Controller for CP reaction type management.\n */\nclass ReactionTypeController extends Controller\n{\n    public function reorder(ReactionSet $reactionSet, Request $request)\n    {\n        $request['order'] = json_decode($request->input('order'), true);\n\n        $data = $request->validate(['order' => 'array']);\n\n        if ($data['order']) {\n            foreach ($data['order'] as $position => $id) {\n                ReactionType::whereKey($id)->update(compact('position'));\n            }\n        }\n\n        return redirect($reactionSet->edit_url);\n    }\n    public function create(ReactionSet $reactionSet)\n    {\n        $form = $this->form(new ReactionType(['icon' => 'emoji:']));\n\n        return view('waterhole::cp.reactions.reaction-type', compact('form', 'reactionSet'));\n    }\n\n    public function store(ReactionSet $reactionSet, Request $request)\n    {\n        $reactionType = new ReactionType();\n        $reactionType->reactionSet()->associate($reactionSet);\n\n        $this->form($reactionType)->submit($request);\n\n        return redirect($reactionSet->edit_url);\n    }\n\n    public function edit(ReactionSet $reactionSet, ReactionType $reactionType)\n    {\n        $form = $this->form($reactionType);\n\n        return view(\n            'waterhole::cp.reactions.reaction-type',\n            compact('form', 'reactionSet', 'reactionType'),\n        );\n    }\n\n    public function update(ReactionSet $reactionSet, ReactionType $reactionType, Request $request)\n    {\n        $this->form($reactionType)->submit($request);\n\n        return redirect($request->input('return', $reactionType->reactionSet->edit_url))->with(\n            'success',\n            __('waterhole::cp.reaction-type-saved-message'),\n        );\n    }\n\n    private function form(ReactionType $reactionType)\n    {\n        return new ReactionTypeForm($reactionType);\n    }\n}\n"
  },
  {
    "path": "src/Http/Controllers/Cp/StructureController.php",
    "content": "<?php\n\nnamespace Waterhole\\Http\\Controllers\\Cp;\n\nuse Illuminate\\Http\\Request;\nuse Waterhole\\Http\\Controllers\\Controller;\nuse Waterhole\\Models\\Structure;\n\n/**\n * Controller for the CP structure index.\n */\nclass StructureController extends Controller\n{\n    public function index()\n    {\n        $structure = Structure::with('content')->orderBy('position')->get();\n\n        return view('waterhole::cp.structure.index', compact('structure'));\n    }\n\n    public function saveOrder(Request $request)\n    {\n        $request['order'] = json_decode($request->input('order'), true);\n\n        $data = $request->validate([\n            'order' => 'array',\n            'order.*' => 'array:id,listIndex',\n        ]);\n\n        if ($data['order']) {\n            foreach ($data['order'] as $position => $node) {\n                Structure::whereKey($node['id'])->update([\n                    'position' => $position,\n                    'is_listed' => !$node['listIndex'],\n                ]);\n            }\n        }\n\n        return redirect()->route('waterhole.cp.structure');\n    }\n}\n"
  },
  {
    "path": "src/Http/Controllers/Cp/StructureHeadingController.php",
    "content": "<?php\n\nnamespace Waterhole\\Http\\Controllers\\Cp;\n\nuse Illuminate\\Http\\Request;\nuse Waterhole\\Http\\Controllers\\Controller;\nuse Waterhole\\Models\\StructureHeading;\n\n/**\n * Controller for CP structure heading management (create and update).\n *\n * Deletion is handled by the DeleteStructure action.\n */\nclass StructureHeadingController extends Controller\n{\n    public function create()\n    {\n        return view('waterhole::cp.structure.heading');\n    }\n\n    public function store(Request $request)\n    {\n        StructureHeading::create($request->validate(StructureHeading::rules()));\n\n        return redirect()->route('waterhole.cp.structure');\n    }\n\n    public function edit(StructureHeading $heading)\n    {\n        return view('waterhole::cp.structure.heading', compact('heading'));\n    }\n\n    public function update(StructureHeading $heading, Request $request)\n    {\n        $heading->update($request->validate(StructureHeading::rules($heading)));\n\n        return redirect()->route('waterhole.cp.structure');\n    }\n}\n"
  },
  {
    "path": "src/Http/Controllers/Cp/StructureLinkController.php",
    "content": "<?php\n\nnamespace Waterhole\\Http\\Controllers\\Cp;\n\nuse Illuminate\\Http\\Request;\nuse Waterhole\\Forms\\StructureLinkForm;\nuse Waterhole\\Http\\Controllers\\Controller;\nuse Waterhole\\Models\\StructureLink;\n\n/**\n * Controller for CP structure link management (create and update).\n *\n * Deletion is handled by the DeleteStructure action.\n */\nclass StructureLinkController extends Controller\n{\n    public function create()\n    {\n        $form = $this->form(new StructureLink());\n\n        return view('waterhole::cp.structure.link', compact('form'));\n    }\n\n    public function store(Request $request)\n    {\n        $this->form(new StructureLink())->submit($request);\n\n        return redirect()->route('waterhole.cp.structure');\n    }\n\n    public function edit(StructureLink $link)\n    {\n        $form = $this->form($link);\n\n        return view('waterhole::cp.structure.link', compact('form', 'link'));\n    }\n\n    public function update(StructureLink $link, Request $request)\n    {\n        $this->form($link)->submit($request);\n\n        return redirect()->route('waterhole.cp.structure');\n    }\n\n    private function form(StructureLink $link)\n    {\n        return new StructureLinkForm($link);\n    }\n}\n"
  },
  {
    "path": "src/Http/Controllers/Cp/TagController.php",
    "content": "<?php\n\nnamespace Waterhole\\Http\\Controllers\\Cp;\n\nuse HotwiredLaravel\\TurboLaravel\\Http\\TurboResponseFactory;\nuse Illuminate\\Http\\Request;\nuse Waterhole\\Forms\\TagForm;\nuse Waterhole\\Models\\Tag;\nuse Waterhole\\Models\\Taxonomy;\nuse Waterhole\\View\\Components\\Cp\\TagRow;\nuse Waterhole\\View\\TurboStream;\n\nclass TagController\n{\n    public function create(Taxonomy $taxonomy)\n    {\n        $form = $this->form(new Tag(['icon' => 'emoji:']));\n\n        return view('waterhole::cp.taxonomies.tag', compact('form', 'taxonomy'));\n    }\n\n    public function store(Taxonomy $taxonomy, Request $request)\n    {\n        $tag = new Tag();\n        $tag->taxonomy()->associate($taxonomy);\n\n        $this->form($tag)->submit($request);\n\n        if ($request->wantsTurboStream()) {\n            return TurboResponseFactory::makeStream(\n                TurboStream::before(new TagRow($tag), '#tag-list-end'),\n            );\n        }\n\n        return redirect($taxonomy->edit_url);\n    }\n\n    public function edit(Taxonomy $taxonomy, Tag $tag)\n    {\n        $form = $this->form($tag);\n\n        return view('waterhole::cp.taxonomies.tag', compact('form', 'taxonomy', 'tag'));\n    }\n\n    public function update(Taxonomy $taxonomy, Tag $tag, Request $request)\n    {\n        $this->form($tag)->submit($request);\n\n        if ($request->wantsTurboStream()) {\n            return TurboResponseFactory::makeStream(TurboStream::replace(new TagRow($tag)));\n        }\n\n        return redirect($request->input('return', $taxonomy->edit_url))->with(\n            'success',\n            __('waterhole::cp.tag-saved-message'),\n        );\n    }\n\n    private function form(Tag $tag)\n    {\n        return new TagForm($tag);\n    }\n}\n"
  },
  {
    "path": "src/Http/Controllers/Cp/TaxonomyController.php",
    "content": "<?php\n\nnamespace Waterhole\\Http\\Controllers\\Cp;\n\nuse Illuminate\\Http\\Request;\nuse Waterhole\\Forms\\TaxonomyForm;\nuse Waterhole\\Models\\Taxonomy;\n\nclass TaxonomyController\n{\n    public function index()\n    {\n        return view('waterhole::cp.taxonomies.index', [\n            'taxonomies' => Taxonomy::withCount('tags')->get(),\n        ]);\n    }\n\n    public function create()\n    {\n        $form = $this->form(new Taxonomy());\n\n        return view('waterhole::cp.taxonomies.taxonomy', compact('form'));\n    }\n\n    public function store(Request $request)\n    {\n        $this->form($taxonomy = new Taxonomy())->submit($request);\n\n        return redirect($taxonomy->edit_url);\n    }\n\n    public function edit(Taxonomy $taxonomy)\n    {\n        $form = $this->form($taxonomy);\n\n        return view('waterhole::cp.taxonomies.taxonomy', compact('form', 'taxonomy'));\n    }\n\n    public function update(Taxonomy $taxonomy, Request $request)\n    {\n        $this->form($taxonomy)->submit($request);\n\n        return redirect($request->input('return', route('waterhole.cp.taxonomies.index')))->with(\n            'success',\n            __('waterhole::cp.taxonomy-saved-message'),\n        );\n    }\n\n    private function form(Taxonomy $taxonomy)\n    {\n        return new TaxonomyForm($taxonomy);\n    }\n}\n"
  },
  {
    "path": "src/Http/Controllers/Cp/UserController.php",
    "content": "<?php\n\nnamespace Waterhole\\Http\\Controllers\\Cp;\n\nuse Illuminate\\Http\\Request;\nuse Waterhole\\Forms\\UserForm;\nuse Waterhole\\Http\\Controllers\\Controller;\nuse Waterhole\\Models\\User;\n\n/**\n * Controller for CP user management.\n */\nclass UserController extends Controller\n{\n    /**\n     * A map of sortable columns and their initial direction.\n     */\n    private const SORTABLE_COLUMNS = [\n        'name' => 'asc',\n        'created_at' => 'desc',\n        'last_seen_at' => 'desc',\n    ];\n\n    public function index(Request $request)\n    {\n        $query = User::with('groups');\n\n        // Explode the filter query into space-separated tokens (as long as the\n        // space is not within a pair of quotes). For each token, add a where\n        // clause to the query.\n        if ($q = $request->query('q')) {\n            $isPgsql = (new User())->getConnection()->getDriverName() === 'pgsql';\n            $likeOperator = $isPgsql ? 'ilike' : 'like';\n\n            preg_match_all('/(?:[^\\s\"]*)\"([^\"]*)(?:\"|$)|[^\\s\"]+/i', $q, $tokens, PREG_SET_ORDER);\n\n            foreach ($tokens as $token) {\n                if (str_starts_with($token[0], 'group:')) {\n                    if ($value = $token[1] ?? substr($token[0], strlen('group:'))) {\n                        $query->whereHas('groups', function ($query) use ($value) {\n                            $query->whereIn('name', explode(',', $value));\n                        });\n                    }\n                } elseif ($token[0] === 'is:suspended') {\n                    $query->where('suspended_until', '>', now());\n                } elseif (filter_var($token[0], FILTER_VALIDATE_EMAIL)) {\n                    $query->where('email', $token[0]);\n                } elseif (filter_var($token[0], FILTER_VALIDATE_INT)) {\n                    $query->where('id', $token[0]);\n                } else {\n                    $query->where('name', $likeOperator, $token[0] . '%');\n                }\n            }\n        }\n\n        // Apply sorting to the query. Ensure the requested sort and direction\n        // is valid before doing so.\n        if (!isset(self::SORTABLE_COLUMNS[($sort = $request->query('sort'))])) {\n            $sort = array_key_first(self::SORTABLE_COLUMNS);\n        }\n\n        $direction = $request->query('direction', self::SORTABLE_COLUMNS[$sort] ?? 'asc');\n\n        if (!in_array($direction, ['asc', 'desc'])) {\n            $direction = 'asc';\n        }\n\n        $query->orderBy($sort, $direction);\n\n        return view('waterhole::cp.users.index', [\n            'users' => $query->paginate(50),\n            'sort' => $sort,\n            'direction' => $direction,\n            'sortable' => array_keys(self::SORTABLE_COLUMNS),\n        ]);\n    }\n\n    public function create()\n    {\n        $form = $this->form(new User());\n\n        return view('waterhole::cp.users.form', compact('form'));\n    }\n\n    public function store(Request $request)\n    {\n        $this->form($user = new User())->submit($request);\n\n        $user->markEmailAsVerified();\n\n        return redirect()\n            ->route('waterhole.cp.users.index', ['sort' => 'created_at'])\n            ->with('success', __('waterhole::cp.user-created-message'));\n    }\n\n    public function edit(User $user)\n    {\n        $form = $this->form($user);\n\n        return view('waterhole::cp.users.form', compact('form', 'user'));\n    }\n\n    public function update(User $user, Request $request)\n    {\n        $this->form($user)->submit($request);\n\n        return redirect($request->input('return', route('waterhole.cp.users.index')))->with(\n            'success',\n            __('waterhole::cp.user-saved-message'),\n        );\n    }\n\n    private function form(User $user)\n    {\n        return new UserForm($user);\n    }\n}\n"
  },
  {
    "path": "src/Http/Controllers/FormatController.php",
    "content": "<?php\n\nnamespace Waterhole\\Http\\Controllers;\n\nuse Illuminate\\Http\\Request;\nuse Waterhole\\Formatter\\Formatter;\n\nuse function Waterhole\\emojify;\n\n/**\n * Controller to render plain-text content as HTML.\n *\n * This is used for the \"preview\" function in the text editor.\n */\nclass FormatController extends Controller\n{\n    public function __construct(private Formatter $formatter) {}\n\n    public function __invoke(Request $request): string\n    {\n        $xml = $this->formatter->parse((string) $request->getContent());\n\n        return $this->formatter->render($xml);\n    }\n}\n"
  },
  {
    "path": "src/Http/Controllers/Forum/CommentController.php",
    "content": "<?php\n\nnamespace Waterhole\\Http\\Controllers\\Forum;\n\nuse HotwiredLaravel\\TurboLaravel\\Http\\TurboResponseFactory;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Routing\\Middleware\\ThrottleRequests;\nuse Waterhole\\Extend;\nuse Waterhole\\Http\\Controllers\\Controller;\nuse Waterhole\\Models\\Comment;\nuse Waterhole\\Models\\Post;\nuse Waterhole\\Models\\ReactionType;\nuse Waterhole\\View\\Components\\CommentFrame;\nuse Waterhole\\View\\Components\\CommentFull;\nuse Waterhole\\View\\Components\\Composer;\nuse Waterhole\\View\\Components\\FollowButton;\nuse Waterhole\\View\\TurboStream;\nuse function HotwiredLaravel\\TurboLaravel\\dom_id;\n\n/**\n * Controller for comments (show, create, update).\n *\n * Deletion is handled by the DeleteComment action.\n */\nclass CommentController extends Controller\n{\n    public function __construct()\n    {\n        $this->middleware('waterhole.auth')->only('create', 'store', 'edit', 'update');\n        $this->middleware(ThrottleRequests::using('waterhole.create'))->only('store');\n    }\n\n    public function show(Post $post, Comment $comment, Request $request)\n    {\n        // Load the comment tree for this comment, load the necessary\n        // relationships, and pre-fill the `post` relationship for each comment.\n        $query = $comment->childrenAndSelf()->getQuery();\n\n        $extender = resolve(Extend\\Query\\CommentQuery::class);\n\n        foreach ([...$extender->values(), ...$extender->thread->values()] as $scope) {\n            $scope($query, $post);\n        }\n\n        $comment = $query\n            ->get()\n            ->each(function ($comment) use ($post) {\n                $comment->setRelation('post', $post);\n                $comment->parent?->setRelation('post', $post);\n            })\n            ->toTree()[0];\n\n        $request->user()?->markNotificationsRead($comment);\n\n        $post->userState?->read()->save();\n\n        return view('waterhole::comments.show', compact('post', 'comment'));\n    }\n\n    public function create(Post $post, Request $request)\n    {\n        $this->authorize('waterhole.comment.create');\n        $this->authorize('waterhole.post.comment', $post);\n\n        // Comments may be created in reply to a parent comment. The parent ID\n        // can either be specified in a query parameter, or it may be present\n        // in old POST data.\n        if ($parentId = $request->get('parent', $request->old('parent_id'))) {\n            $parent = $post->comments()->withoutTrashed()->findOrFail($parentId);\n        } else {\n            $parent = null;\n        }\n\n        return view('waterhole::comments.create', compact('post', 'parent'));\n    }\n\n    public function store(Post $post, Request $request)\n    {\n        // Only proceed with comment submission if the \"post\" button was\n        // explicitly clicked. This allows the form to be submitted for other\n        // purposes, such as clearing the parent comment.\n        if (!$request->input('commit')) {\n            return redirect()\n                ->route('waterhole.posts.comments.create', compact('post'))\n                ->withInput();\n        }\n\n        $this->authorize('waterhole.comment.create', Comment::class);\n        $this->authorize('waterhole.post.comment', $post);\n\n        $user = $request->user();\n\n        $data = Comment::validate($request->all());\n        $data['user_id'] = $user->id;\n        $data['is_approved'] =\n            $user->can('waterhole.channel.moderate', $post->channel) ||\n            (!$user->requiresApproval() && !$post->channel->require_approval_comments);\n\n        // Validation has already ensured that the parent comment exists, but\n        // we still need to make sure that it's a comment on the same post as\n        // we are creating a comment on.\n        if ($parentId = $data['parent_id'] ?? null) {\n            $parent = $post->comments()->withoutTrashed()->findOrFail($parentId);\n        }\n\n        $post->comments()->save($comment = new Comment($data));\n\n        $post->userState->read()->save();\n\n        if ($user->follow_on_comment && !$post->isFollowed()) {\n            $post->follow();\n            $wasFollowed = true;\n        }\n\n        // If the client supports Turbo Streams, we can append the new comment\n        // to the bottom of the page, and reset the comment composer. If the\n        // comment has a parent, send back a fresh version of that too. And if\n        // the post has been followed, refresh the post controls.\n        if ($request->wantsTurboStream()) {\n            $streams = [\n                TurboStream::append(\n                    (new CommentFrame($comment))->withAttributes(['class' => 'card__row']),\n                    '.comment-list',\n                ),\n                TurboStream::replace(new Composer($post)),\n            ];\n\n            if (isset($parent)) {\n                $streams[] = TurboStream::replace(new CommentFull($parent->refresh()));\n            }\n\n            if (isset($wasFollowed)) {\n                $streams[] = TurboStream::replace(new FollowButton($post));\n            }\n\n            return TurboResponseFactory::makeStream(implode($streams));\n        }\n\n        // If the comment was made in reply to another comment, then redirect\n        // to the new comment on the parent comment's page. Otherwise, redirect\n        // to the new comment on the post's page.\n        if (isset($parent)) {\n            return redirect($parent->url . '#' . dom_id($parent));\n        }\n\n        return redirect($comment->post_url);\n    }\n\n    public function edit(Post $post, Comment $comment)\n    {\n        $this->authorize('waterhole.comment.edit', $comment);\n\n        return view('waterhole::comments.edit', compact('comment'));\n    }\n\n    public function update(Post $post, Comment $comment, Request $request)\n    {\n        $this->authorize('waterhole.comment.edit', $comment);\n\n        $comment\n            ->fill(Comment::validate($request->all(), $comment))\n            ->markAsEdited()\n            ->save();\n\n        return redirect($comment->post_url);\n    }\n\n    public function reactions(Comment $comment, ReactionType $reactionType)\n    {\n        return view('waterhole::reactions.list', [\n            'model' => $comment,\n            'reactionType' => $reactionType,\n        ]);\n    }\n}\n"
  },
  {
    "path": "src/Http/Controllers/Forum/IndexController.php",
    "content": "<?php\n\nnamespace Waterhole\\Http\\Controllers\\Forum;\n\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Support\\Arr;\nuse Illuminate\\Support\\Facades\\Auth;\nuse Waterhole\\Feed\\PostFeed;\nuse Waterhole\\Filters\\Following;\nuse Waterhole\\Filters\\Ignoring;\nuse Waterhole\\Filters\\Trash;\nuse Waterhole\\Http\\Controllers\\Controller;\nuse Waterhole\\Http\\Middleware\\MaybeRequireLogin;\nuse Waterhole\\Models\\Channel;\nuse Waterhole\\Models\\Page;\nuse Waterhole\\Models\\Tag;\n\nuse function Waterhole\\resolve_all;\n\n/**\n * Controller for the forum index (home, channels, and pages).\n */\nclass IndexController extends Controller\n{\n    public function __construct()\n    {\n        $this->middleware(MaybeRequireLogin::class)->only('home');\n    }\n\n    public function home(Request $request)\n    {\n        // Hide posts that the user has ignored, and posts that are in channels\n        // that the user has ignored, to ensure the Home post feed is clean and\n        // relevant.\n        $scope = function (Builder $query) {\n            $this->scope($query);\n\n            $query->withGlobalScope(\n                Ignoring::EXCLUDE_IGNORED_SCOPE,\n                fn($query) => $query->whereNot->ignoring(),\n            );\n\n            $query->whereDoesntHave('channel', fn($query) => $query->ignoring());\n        };\n\n        $feed = new PostFeed(\n            request: $request,\n            filters: $this->resolveFilters(config('waterhole.forum.post_filters', [])),\n            layout: resolve(config('waterhole.forum.post_layout')),\n            scope: $scope,\n        );\n\n        return view('waterhole::forum.home')->with(compact('feed'));\n    }\n\n    public function channel(Channel $channel, Request $request)\n    {\n        $feed = new PostFeed(\n            request: $request,\n            filters: $this->resolveFilters(\n                $channel->filters ?: config('waterhole.forum.post_filters', []),\n            ),\n            layout: resolve($channel->layout ?: config('waterhole.forum.post_layout')),\n            scope: function (Builder $query) use ($channel) {\n                $this->scope($query);\n\n                $query->where('posts.channel_id', $channel->id);\n\n                $param = request('tags');\n                if ($param && ($ids = is_array($param) ? Arr::flatten($param) : [$param])) {\n                    Tag::findOrFail($ids);\n                    $query->whereRelation('tags', fn($query) => $query->whereKey($ids));\n                }\n            },\n        );\n\n        return view('waterhole::forum.channel', compact('channel', 'feed'));\n    }\n\n    public function page(Page $page)\n    {\n        return view('waterhole::forum.page', compact('page'));\n    }\n\n    private function scope(Builder $query)\n    {\n        $query->withGlobalScope(\n            Trash::EXCLUDE_TRASHED_SCOPE,\n            fn($query) => $query->withoutTrashed(),\n        );\n    }\n\n    private function resolveFilters(array $filters)\n    {\n        $filters = resolve_all($filters);\n\n        if ($user = Auth::user()) {\n            $filters[] = new Following();\n            $filters[] = new Ignoring();\n\n            if ($user->isAdmin() || Channel::allPermitted($user, 'moderate')) {\n                $filters[] = new Trash();\n            }\n        }\n\n        return $filters;\n    }\n}\n"
  },
  {
    "path": "src/Http/Controllers/Forum/ModerationController.php",
    "content": "<?php\n\nnamespace Waterhole\\Http\\Controllers\\Forum;\n\nuse Illuminate\\Database\\Eloquent\\Relations\\MorphTo;\nuse Illuminate\\Http\\Request;\nuse Waterhole\\Http\\Controllers\\Controller;\nuse Waterhole\\Models\\Channel;\nuse Waterhole\\Models\\Comment;\nuse Waterhole\\Models\\Flag;\nuse Waterhole\\Models\\Post;\n\nclass ModerationController extends Controller\n{\n    public function __construct()\n    {\n        $this->middleware('waterhole.auth');\n    }\n\n    public function __invoke(Request $request)\n    {\n        if (Channel::allPermitted($request->user(), 'moderate') === []) {\n            abort(403);\n        }\n\n        $pendingFlags = Flag::query()\n            ->pending()\n            ->selectRaw('min(id) as id, subject_type, subject_id, min(created_at) as created_at')\n            ->groupBy('subject_type', 'subject_id')\n            ->orderByRaw('count(*) desc')\n            ->oldest()\n            ->with([\n                'subject' => function (MorphTo $morph) {\n                    $morph->morphWith([\n                        Post::class => ['user', 'channel', 'pendingFlags'],\n                        Comment::class => ['user', 'post', 'channel', 'pendingFlags'],\n                    ]);\n                },\n            ])\n            ->cursorPaginate(10);\n\n        return view('waterhole::forum.moderation', compact('pendingFlags'));\n    }\n}\n"
  },
  {
    "path": "src/Http/Controllers/Forum/NotificationController.php",
    "content": "<?php\n\nnamespace Waterhole\\Http\\Controllers\\Forum;\n\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Routing\\Middleware\\ValidateSignature;\nuse Illuminate\\Support\\Facades\\Blade;\nuse Illuminate\\Support\\Facades\\Crypt;\nuse Waterhole\\Http\\Controllers\\Controller;\nuse Waterhole\\Models\\Notification;\nuse Waterhole\\View\\Components\\Notification as NotificationComponent;\n\n/**\n * Controller for notification management.\n */\nclass NotificationController extends Controller\n{\n    public function __construct()\n    {\n        $this->middleware('waterhole.auth');\n        $this->middleware(ValidateSignature::class)->only('unsubscribe');\n    }\n\n    public function index(Request $request)\n    {\n        $user = $request->user();\n\n        $user->update(['notifications_read_at' => now()]);\n\n        $groupType = \"COALESCE(group_type, CONCAT('', id))\";\n        $groupId = \"COALESCE(group_id, CONCAT('', id))\";\n\n        // Notifications can be grouped together by their subject. When listing\n        // notifications, we only show the most recent notification in each\n        // \"group\", so the user doesn't get overwhelmed by lots of activity.\n        $query = $user\n            ->notifications()\n            ->select('*')\n            ->selectRaw(\n                \"ROW_NUMBER() OVER(PARTITION BY type, $groupType, $groupId ORDER BY created_at DESC) AS r\",\n            );\n\n        $notifications = Notification::from('notifications')\n            ->withExpression('notifications', $query)\n            ->with('content')\n            ->where('r', 1)\n            ->latest()\n            ->cursorPaginate(10);\n\n        // Give notification types the opportunity to eager-load additional\n        // relationships.\n        $notifications->groupBy('type')->each(fn($models, $type) => $type::load($models));\n\n        return view('waterhole::forum.notifications', compact('notifications'));\n    }\n\n    public function show(Notification $notification)\n    {\n        return Blade::renderComponent(new NotificationComponent($notification));\n    }\n\n    public function go(Notification $notification)\n    {\n        Notification::groupedWith($notification)->update(['read_at' => now()]);\n\n        $notification->type::load(new Collection([$notification]));\n\n        return redirect($notification->template->groupedUrl());\n    }\n\n    public function read(Request $request)\n    {\n        $request\n            ->user()\n            ->unreadNotifications()\n            ->update(['read_at' => now()]);\n\n        return redirect()->route('waterhole.notifications.index');\n    }\n\n    public function unsubscribe(Request $request)\n    {\n        $payload = collect(Crypt::decrypt($request->query('payload')));\n\n        // An unsubscribe request will come in with the notification type,\n        // its user, and content. Find the matching notification in the database\n        // so we can reconstruct its template and call its unsubscribe method.\n        $notification = Notification::where(\n            $payload\n                ->only('type', 'notifiable_type', 'notifiable_id', 'content_type', 'content_id')\n                ->all(),\n        )->firstOrFail();\n\n        $notification->template->unsubscribe($notification->notifiable);\n\n        return redirect()\n            ->route('waterhole.home')\n            ->with('success', __('waterhole::notifications.unsubscribe-success-message'));\n    }\n}\n"
  },
  {
    "path": "src/Http/Controllers/Forum/PostController.php",
    "content": "<?php\n\nnamespace Waterhole\\Http\\Controllers\\Forum;\n\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Routing\\Middleware\\ThrottleRequests;\nuse Illuminate\\Support\\Facades\\Cache;\nuse Illuminate\\Support\\Facades\\Gate;\nuse Waterhole\\Extend;\nuse Waterhole\\Forms\\PostForm;\nuse Waterhole\\Http\\Controllers\\Controller;\nuse Waterhole\\Models\\Comment;\nuse Waterhole\\Models\\Post;\nuse Waterhole\\Models\\ReactionType;\n\n/**\n * Controller for a post (post page, create, and update).\n *\n * Deletion is handled by the DeletePost action. Only editing of the post\n * title/body is done through this controller - updating other attributes\n * (channel, locked) is done through various Actions.\n */\nclass PostController extends Controller\n{\n    public function __construct()\n    {\n        $this->middleware('waterhole.auth')->only('create', 'store', 'edit', 'update');\n        $this->middleware(ThrottleRequests::using('waterhole.create'))->only('store');\n    }\n\n    public function show(Post $post, Request $request)\n    {\n        $user = $request->user();\n\n        // If we've come here with reference to a particular comment, we will\n        // mark any notifications about that comment as read, and then redirect\n        // to the page on which that comment will be found. This allows comment\n        // permalinks to be constructed without having to know the comment\n        // page/index, which may be subject to change over time anyway.\n        if ($commentId = $request->query('comment')) {\n            $comment = $post->comments()->findOrFail($commentId);\n\n            $user?->markNotificationsRead($comment);\n\n            return redirect($comment->post_url);\n        }\n\n        $query = $post->comments()->getQuery();\n\n        $extender = resolve(Extend\\Query\\CommentQuery::class);\n\n        foreach ([...$extender->values(), ...$extender->thread->values()] as $scope) {\n            $scope($query, $post);\n        }\n\n        $comments = $query->oldest()->paginate();\n\n        // We already have an instance of the `post` relation for each comment,\n        // since we are on the post page!\n        $comments->getCollection()->each(function (Comment $comment) use ($post) {\n            $comment->setRelation('post', $post);\n            $comment->parent?->setRelation('post', $post);\n        });\n\n        $lastReadAt = $post->userState?->last_read_at;\n\n        $post->userState?->read()->save();\n\n        $user?->markNotificationsRead($post);\n\n        // Only increase the view count once per day per user/IP.\n        Cache::remember(\n            \"view:$post->id:\" . ($user ? \"user:$user->id\" : 'ip:' . $request->ip()),\n            60 * 60 * 24,\n            fn() => $post->increment('view_count'),\n        );\n\n        return view('waterhole::posts.show', compact('post', 'comments', 'lastReadAt'));\n    }\n\n    public function create()\n    {\n        $this->authorize('waterhole.post.create');\n\n        $form = new PostForm(new Post(['channel_id' => old('channel_id', request('channel_id'))]));\n\n        return view('waterhole::posts.create', compact('form'));\n    }\n\n    public function store(Request $request)\n    {\n        // Only proceed with post submission if the \"post\" button was\n        // explicitly clicked. This allows the form to be submitted for other\n        // purposes, such as selecting a different channel.\n        if (!$request->input('commit')) {\n            return redirect()\n                ->route('waterhole.posts.create', ['channel_id' => $request->input('channel_id')])\n                ->withInput();\n        }\n\n        $this->authorize('waterhole.post.create');\n\n        $post = new Post([\n            'user_id' => $request->user()->id,\n            'channel_id' => request('channel_id'),\n        ]);\n\n        Gate::authorize('waterhole.channel.post', $post->channel);\n\n        $user = $request->user();\n\n        $post->is_approved =\n            $user->can('waterhole.channel.moderate', $post->channel) ||\n            (!$user->requiresApproval() && !$post->channel->require_approval_posts);\n\n        if (!(new PostForm($post))->submit($request)) {\n            return redirect()->back()->withInput();\n        }\n\n        if ($user->follow_on_comment) {\n            $post->follow();\n        }\n\n        return redirect($post->url);\n    }\n\n    public function edit(Post $post)\n    {\n        $this->authorize('waterhole.post.edit', $post);\n\n        $form = new PostForm($post);\n\n        return view('waterhole::posts.edit', compact('post', 'form'));\n    }\n\n    public function update(Post $post, Request $request)\n    {\n        $this->authorize('waterhole.post.edit', $post);\n\n        $post->markAsEdited();\n\n        (new PostForm($post))->submit($request);\n\n        return redirect($request->input('return', $post->url));\n    }\n\n    public function reactions(Post $post, ReactionType $reactionType)\n    {\n        return view('waterhole::reactions.list', [\n            'model' => $post,\n            'reactionType' => $reactionType,\n        ]);\n    }\n}\n"
  },
  {
    "path": "src/Http/Controllers/Forum/PreferencesController.php",
    "content": "<?php\n\nnamespace Waterhole\\Http\\Controllers\\Forum;\n\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Support\\Facades\\Hash;\nuse Illuminate\\Validation\\Rules\\Password;\nuse Illuminate\\Validation\\Rules\\Unique;\nuse Waterhole\\Extend\\Core\\NotificationTypes;\nuse Waterhole\\Forms\\UserProfileForm;\nuse Waterhole\\Http\\Controllers\\Controller;\nuse Waterhole\\Models\\User;\n\n/**\n * Controller for user preferences views.\n */\nclass PreferencesController extends Controller\n{\n    public function __construct()\n    {\n        $this->middleware('waterhole.auth');\n        $this->middleware('waterhole.confirm-password')->only([\n            'account',\n            'changeEmail',\n            'changePassword',\n        ]);\n    }\n\n    public function index()\n    {\n        return redirect()->route('waterhole.preferences.profile');\n    }\n\n    public function account()\n    {\n        return view('waterhole::preferences.account');\n    }\n\n    public function changeEmail(Request $request)\n    {\n        if ($request->user()->originalUser()) {\n            abort(404);\n        }\n\n        $data = $request->validate([\n            'email' => ['required', 'string', 'email', 'max:255', new Unique(User::class)],\n        ]);\n\n        // Make a copy of the user because we don't want to set the email\n        // of the global instance, otherwise a subsequent call to `save` could\n        // actually save the new email before it's been verified.\n        (clone $request->user())->fill($data)->sendEmailVerificationNotification();\n\n        return redirect()\n            ->route('waterhole.preferences.account')\n            ->with('success', __('waterhole::auth.email-verification-sent-message', $data));\n    }\n\n    public function changePassword(Request $request)\n    {\n        $data = $request->validate([\n            'password' => ['required', Password::defaults()],\n        ]);\n\n        $request->user()->update(['password' => Hash::make($data['password'])]);\n\n        return redirect()\n            ->route('waterhole.preferences.account')\n            ->with('success', __('waterhole::passwords.reset'));\n    }\n\n    public function profile(Request $request)\n    {\n        $form = new UserProfileForm($request->user());\n\n        return view('waterhole::preferences.profile', compact('form'));\n    }\n\n    public function saveProfile(Request $request)\n    {\n        (new UserProfileForm($request->user()))->submit($request);\n\n        return redirect()\n            ->route('waterhole.preferences.profile')\n            ->with('success', __('waterhole::user.profile-saved-message'));\n    }\n\n    public function notifications()\n    {\n        return view('waterhole::preferences.notifications');\n    }\n\n    public function saveNotifications(Request $request)\n    {\n        $data = $request->validate([\n            'notification_channels' =>\n                'array:' . implode(',', resolve(NotificationTypes::class)->values()),\n            'notification_channels.*' => 'array:0,1',\n            'notification_channels.*.*' => 'in:database,mail',\n            'follow_on_comment' => 'boolean',\n        ]);\n\n        $request->user()->update($data);\n\n        return redirect()\n            ->route('waterhole.preferences.notifications')\n            ->with('success', __('waterhole::user.notification-preferences-saved-message'));\n    }\n}\n"
  },
  {
    "path": "src/Http/Controllers/Forum/RssController.php",
    "content": "<?php\n\nnamespace Waterhole\\Http\\Controllers\\Forum;\n\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Laminas\\Feed\\Writer\\Feed;\nuse Waterhole\\Http\\Controllers\\Controller;\nuse Waterhole\\Models\\Channel;\nuse Waterhole\\Models\\Post;\n\n/**\n * Controller for RSS feeds.\n */\nclass RssController extends Controller\n{\n    public function posts()\n    {\n        $posts = Post::whereRelation('channel', 'ignore', false)->latest()->take(20)->get();\n\n        $feed = new Feed();\n        $feed->setTitle(config('waterhole.forum.name'));\n        $feed->setLink(route('waterhole.home'));\n\n        return $this->postsFeed($feed, $posts);\n    }\n\n    public function channel(Channel $channel)\n    {\n        $posts = Post::whereBelongsTo($channel)->latest()->take(20)->get();\n\n        $feed = new Feed();\n        $feed->setTitle($channel->name . ' - ' . config('waterhole.forum.name'));\n        $feed->setLink($channel->url);\n\n        return $this->postsFeed($feed, $posts);\n    }\n\n    private function postsFeed(Feed $feed, Collection $posts)\n    {\n        $posts->load('user');\n\n        $feed->setDescription($feed->getTitle());\n        $feed->setFeedLink(url()->current(), 'rss');\n        $feed->setDateModified(now());\n\n        foreach ($posts as $post) {\n            $entry = $feed->createEntry();\n            $entry->setId((string) $post->id);\n            $entry->setTitle($post->title);\n            $entry->setLink($post->url);\n            $entry->setCommentLink($post->url . '#comments');\n            // $entry->setCommentFeedLink([\n            //     'uri' => route('waterhole.rss.post', compact('post')),\n            //     'type' => 'rss',\n            // ]);\n\n            if ($post->user) {\n                $entry->addAuthor([\n                    'name' => $post->user->name,\n                    'uri' => $post->user->url,\n                ]);\n            }\n\n            $entry->setDateCreated($post->created_at);\n\n            if ($post->edited_at) {\n                $entry->setDateModified($post->edited_at);\n            }\n\n            if ($content = (string) $post->body_html) {\n                $entry->setContent($content);\n            }\n\n            $feed->addEntry($entry);\n        }\n\n        // We use RSS over ATOM simply because Laminas Feed has an annoying\n        // bug (or feature?) where it encodes entry content as XHTML, rather\n        // than wrapping the HTML in CDATA. However, in the absence of the PHP\n        // tidy extension, self-closing tags like <br> and <img> result in\n        // invalid XML output. https://github.com/laminas/laminas-feed/issues/7\n        return response($feed->export('rss'), 200, ['Content-Type' => 'application/rss+xml']);\n    }\n}\n"
  },
  {
    "path": "src/Http/Controllers/Forum/SearchController.php",
    "content": "<?php\n\nnamespace Waterhole\\Http\\Controllers\\Forum;\n\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Pagination\\LengthAwarePaginator;\nuse Illuminate\\Pagination\\Paginator;\nuse Illuminate\\Routing\\Middleware\\ThrottleRequests;\nuse Waterhole\\Http\\Controllers\\Controller;\nuse Waterhole\\Models\\Channel;\nuse Waterhole\\Models\\Post;\nuse Waterhole\\Search\\Searcher;\n\n/**\n * Controller for the search interface.\n */\nclass SearchController extends Controller\n{\n    public const SORTS = ['relevance', 'latest', 'top'];\n\n    public function __construct()\n    {\n        $this->middleware(ThrottleRequests::using('waterhole.search'));\n    }\n\n    public function __invoke(Request $request, Searcher $searcher)\n    {\n        if (!($q = $request->input('q'))) {\n            return view('waterhole::forum.search');\n        }\n\n        $channels = $selectedChannels = Channel::all();\n\n        $currentSort = in_array($sort = $request->input('sort'), static::SORTS)\n            ? $sort\n            : static::SORTS[0];\n        $currentPage = Paginator::resolveCurrentPage();\n        $perPage = (new Post())->getPerPage();\n\n        if ($ids = explode(',', $request->query('channels', ''))) {\n            $selectedChannels = $channels->find($ids);\n        }\n\n        $results = $searcher->search(\n            q: $q,\n            limit: $perPage,\n            offset: ($currentPage - 1) * $perPage,\n            sort: $currentSort,\n            channelIds: $selectedChannels->modelKeys(),\n        );\n\n        // Depending on if we have an accurate idea of how many results there\n        // are or not, we will wrap the hits in an appropriate paginator\n        // instance.\n        $paginatorOptions = [\n            'path' => Paginator::resolveCurrentPath(),\n        ];\n\n        if ($results->exhaustiveTotal) {\n            $hits = new LengthAwarePaginator(\n                $results->hits,\n                $results->total,\n                $perPage,\n                $currentPage,\n                $paginatorOptions,\n            );\n        } else {\n            $hits = new Paginator($results->hits, $perPage, $currentPage, $paginatorOptions);\n        }\n\n        // In the sidebar, we will only display channels that contain hits, and\n        // we will sort them with the most hits at the top.\n        $channelsByHits = $channels\n            ->filter(fn($channel) => $results->channelHits[$channel->id])\n            ->sortByDesc(fn($channel) => $results->channelHits[$channel->id]);\n\n        return view('waterhole::forum.search', [\n            'hits' => $hits->withQueryString(),\n            'results' => $results,\n            'channels' => $channelsByHits,\n            'selectedChannels' => $selectedChannels,\n            'sorts' => static::SORTS,\n            'currentSort' => $currentSort,\n        ]);\n    }\n}\n"
  },
  {
    "path": "src/Http/Controllers/Forum/UserController.php",
    "content": "<?php\n\nnamespace Waterhole\\Http\\Controllers\\Forum;\n\nuse Illuminate\\Http\\Request;\nuse Waterhole\\Feed\\CommentFeed;\nuse Waterhole\\Feed\\PostFeed;\nuse Waterhole\\Http\\Controllers\\Controller;\nuse Waterhole\\Layouts;\nuse Waterhole\\Models\\User;\n\nuse function Waterhole\\resolve_all;\n\n/**\n * Controller for user profiles.\n */\nclass UserController extends Controller\n{\n    public function show(User $user)\n    {\n        return redirect()->route('waterhole.user.posts', compact('user'));\n    }\n\n    public function posts(User $user, Request $request)\n    {\n        $posts = new PostFeed(\n            request: $request,\n            filters: resolve_all(config('waterhole.users.post_filters', [])),\n            layout: resolve(Layouts\\CardsLayout::class),\n            scope: fn($query) => $query->whereBelongsTo($user),\n        );\n\n        return view('waterhole::users.posts', compact('user', 'posts'));\n    }\n\n    public function comments(User $user, Request $request)\n    {\n        $comments = new CommentFeed(\n            request: $request,\n            filters: resolve_all(config('waterhole.users.comment_filters', [])),\n            scope: fn($query) => $query->whereBelongsTo($user),\n        );\n\n        return view('waterhole::users.comments', compact('user', 'comments'));\n    }\n}\n"
  },
  {
    "path": "src/Http/Controllers/ImpersonateController.php",
    "content": "<?php\n\nnamespace Waterhole\\Http\\Controllers;\n\nuse Illuminate\\Routing\\Middleware\\ValidateSignature;\nuse Illuminate\\Support\\Facades\\Auth;\nuse Waterhole\\Models\\User;\n\n/**\n * Controller to impersonate users.\n */\nclass ImpersonateController extends Controller\n{\n    public function __construct()\n    {\n        $this->middleware(ValidateSignature::class);\n    }\n\n    public function __invoke(User $user)\n    {\n        Auth::login($user);\n\n        return redirect()->route('waterhole.home');\n    }\n}\n"
  },
  {
    "path": "src/Http/Controllers/UploadController.php",
    "content": "<?php\n\nnamespace Waterhole\\Http\\Controllers;\n\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Validation\\Rules\\File;\nuse Waterhole\\Formatter\\FormatUploads;\nuse Waterhole\\Models\\Upload;\n\nclass UploadController extends Controller\n{\n    public function __construct()\n    {\n        $this->middleware('waterhole.auth');\n    }\n\n    public function __invoke(Request $request)\n    {\n        $request->validate([\n            'file' => [\n                'required',\n                File::types(config('waterhole.uploads.allowed_mimetypes'))->max(\n                    config('waterhole.uploads.max_upload_size'),\n                ),\n            ],\n        ]);\n\n        $upload = Upload::fromFile($request->file('file'));\n\n        $request->user()->uploads()->save($upload);\n\n        return ['url' => FormatUploads::PROTOCOL . $upload->filename];\n    }\n}\n"
  },
  {
    "path": "src/Http/Controllers/UserLookupController.php",
    "content": "<?php\n\nnamespace Waterhole\\Http\\Controllers;\n\nuse Illuminate\\Http\\Request;\nuse Waterhole\\Models\\Post;\nuse Waterhole\\Models\\User;\n\n/**\n * Controller to look up users by name.\n *\n * This is used to populate the @mentions suggestion box in the text editor.\n * The return format is an array of objects with the following keys:\n *\n * - `id`: the user ID\n * - `name`: the user's name\n * - `html`: a rendering of the UserLabel component for the user\n */\nclass UserLookupController extends Controller\n{\n    const LIMIT = 5;\n\n    public function __invoke(?Post $post, Request $request)\n    {\n        if (!$post->exists) {\n            $post = null;\n        }\n\n        $search = $request->query('q');\n\n        if (!$search && !$post) {\n            return [];\n        }\n\n        // Construct a base query that selects the data we want and filters\n        // by name if a search query is present.\n        $users = User::select(['users.id', 'name', 'avatar']);\n\n        if ($search) {\n            $operator =\n                (new User())->getConnection()->getDriverName() === 'pgsql' ? 'ilike' : 'like';\n\n            $users\n                ->where('name', $operator, \"$search%\")\n                ->orderByRaw(\"CASE WHEN name $operator ? THEN 1 ELSE 0 END DESC\", [$search])\n                ->orderBy('name')\n                ->limit(static::LIMIT);\n        }\n\n        $main = User::select(['id', 'name', 'avatar']);\n\n        // If we are getting suggestions geared towards a post, we will clone\n        // the above query a couple times to specifically find users who posted\n        // or commented on the post.\n        if ($post) {\n            $commentsCreatedAt = $users->getGrammar()->wrap('comments.created_at');\n            $commentsId = $users->getGrammar()->wrap('comments.id');\n\n            $commentsQuery = $users\n                ->clone()\n                ->selectRaw(\"MAX($commentsCreatedAt) as created_at\")\n                ->selectRaw(\"MAX($commentsId) as comment_id\")\n                ->joinRelationship(\n                    'comments',\n                    fn($query) => $query->where('comments.post_id', $post->getKey()),\n                )\n                ->groupBy(['users.id', 'name', 'avatar'])\n                ->orderByRaw(\"MAX($commentsCreatedAt) DESC\");\n\n            $postQuery = $users\n                ->clone()\n                ->addSelect('posts.created_at')\n                ->selectRaw('NULL as comment_id')\n                ->joinRelationship(\n                    'posts',\n                    fn($query) => $query->where('posts.id', $post->getKey()),\n                );\n\n            if ($user = $request->user()) {\n                $commentsQuery->where('users.id', '!=', $user->id);\n                $postQuery->where('users.id', '!=', $user->id);\n            }\n\n            $sub = $commentsQuery->unionAll($postQuery)->latest('created_at');\n\n            // If there is a search query, then we still want to tack other\n            // users (that haven't posted here) onto the bottom of the results.\n            if ($search) {\n                $sub->unionAll(\n                    $users->selectRaw('NULL as created_at')->selectRaw('NULL as comment_id'),\n                );\n            }\n\n            $main->fromSub($sub, 'a')->selectRaw('MAX(comment_id) as comment_id');\n        } else {\n            $main->fromSub($users, 'a');\n        }\n\n        // Finally, select distinct users out of these unionized results, and\n        // present them in the desired format.\n        return $main\n            ->groupBy(['id', 'name', 'avatar'])\n            ->take(static::LIMIT)\n            ->get()\n            ->map(\n                fn(User $user) => [\n                    'id' => $user->id,\n                    'name' => $user->name,\n                    'html' => (string) view('waterhole::users.mention-suggestion', compact('user')),\n                    'commentUrl' => $user->comment_id\n                        ? route('waterhole.posts.comments.show', [\n                            'post' => $post,\n                            'comment' => $user->comment_id,\n                        ])\n                        : null,\n                    'frameId' => $user->comment_id ? dom_id($post, 'comment_parent') : null,\n                ],\n            );\n    }\n}\n"
  },
  {
    "path": "src/Http/Middleware/ActorSeen.php",
    "content": "<?php\n\nnamespace Waterhole\\Http\\Middleware;\n\nuse Closure;\nuse Illuminate\\Http\\Request;\n\n/**\n * Middleware to save the actor's \"last seen\" time so that we can use it to show\n * them what content is new since their last visit.\n */\nclass ActorSeen\n{\n    public function handle(Request $request, Closure $next)\n    {\n        if ($actor = $request->user()) {\n            $request->session()->put('previously_seen_at', $actor->last_seen_at);\n        }\n\n        return $next($request);\n    }\n\n    public function terminate(Request $request): void\n    {\n        if (($actor = $request->user()) && $actor->last_seen_at < now()->subMinutes(1)) {\n            $actor->update(['last_seen_at' => now()]);\n        }\n    }\n}\n"
  },
  {
    "path": "src/Http/Middleware/AuthGuard.php",
    "content": "<?php\n\nnamespace Waterhole\\Http\\Middleware;\n\nuse Closure;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Support\\Facades\\Auth;\n\nclass AuthGuard\n{\n    public function handle(Request $request, Closure $next, $guard = null)\n    {\n        Auth::shouldUse($guard ?? config('waterhole.auth.guard', 'web'));\n\n        return $next($request);\n    }\n}\n"
  },
  {
    "path": "src/Http/Middleware/Authenticate.php",
    "content": "<?php\n\nnamespace Waterhole\\Http\\Middleware;\n\nuse Illuminate\\Auth\\Middleware\\Authenticate as Middleware;\nuse Illuminate\\Http\\Request;\n\nclass Authenticate extends Middleware\n{\n    /**\n     * Get the path the user should be redirected to when they are not authenticated.\n     */\n    protected function redirectTo(Request $request): ?string\n    {\n        return $request->expectsJson() ? null : route('waterhole.login');\n    }\n}\n"
  },
  {
    "path": "src/Http/Middleware/AuthenticateWaterhole.php",
    "content": "<?php\n\nnamespace Waterhole\\Http\\Middleware;\n\nuse Closure;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Support\\Facades\\Auth;\nuse Waterhole\\Auth\\AuthenticatesWaterhole;\nuse Waterhole\\Models\\User;\n\nclass AuthenticateWaterhole\n{\n    public function handle(Request $request, Closure $next)\n    {\n        $originalUser = Auth::user();\n\n        if (\n            $originalUser instanceof AuthenticatesWaterhole &&\n            ($payload = $originalUser->toWaterholePayload())\n        ) {\n            $request->attributes->set('waterhole_original_user', $originalUser);\n\n            $record = [\n                'provider' => $payload->provider,\n                'identifier' => $payload->user->identifier,\n            ];\n\n            $user = User::whereRelation('authProviders', $record)->first();\n\n            if (!$user && ($user = User::firstWhere('email', $payload->user->email))) {\n                $user->authProviders()->create($record);\n            }\n\n            if (!$user && !$request->routeIs('waterhole.register*')) {\n                return redirect()->route('waterhole.register.payload', compact('payload'));\n            }\n\n            $guard = Auth::guard(config('waterhole.auth.guard', 'web'));\n\n            if ($user) {\n                $user->setOriginalUser($originalUser);\n\n                $guard->setUser($user);\n            } else {\n                // This is a macro defined in AuthServiceProvider. Unfortunately\n                // forgetUser() does not prevent the user from being re-authenticated\n                // from the session details, so we need to use this.\n                $guard->logoutOnce();\n            }\n        }\n\n        return $next($request);\n    }\n}\n"
  },
  {
    "path": "src/Http/Middleware/ContactOutpost.php",
    "content": "<?php\n\nnamespace Waterhole\\Http\\Middleware;\n\nuse Closure;\nuse Illuminate\\Support\\Facades\\Gate;\nuse Waterhole\\Licensing\\Outpost;\n\nclass ContactOutpost\n{\n    public function __construct(private Outpost $outpost) {}\n\n    public function handle($request, Closure $next)\n    {\n        if (Gate::allows('waterhole.administrate')) {\n            $this->outpost->contact();\n        }\n\n        return $next($request);\n    }\n}\n"
  },
  {
    "path": "src/Http/Middleware/Localize.php",
    "content": "<?php\n\nnamespace Waterhole\\Http\\Middleware;\n\nuse Closure;\nuse Illuminate\\Http\\Request;\nuse Waterhole\\Extend\\Assets\\Locales;\n\nclass Localize\n{\n    public const SESSION_KEY = 'locale';\n\n    public function handle(Request $request, Closure $next)\n    {\n        $user = $request->user();\n        $locales = resolve(Locales::class)->keys();\n\n        // Allow the locale to be set in a query parameter. If there is a\n        // logged-in user, update their preference in the database; otherwise,\n        // store the preference in the session.\n        if (in_array($locale = $request->query('locale'), $locales)) {\n            if ($user) {\n                $user->update(['locale' => $locale]);\n            } else {\n                session()->put(static::SESSION_KEY, $locale);\n            }\n\n            return back();\n        }\n\n        // Retrieve the user's locale preference, either from the user model if\n        // logged in, or from the session or browser preference otherwise.\n        if ($user) {\n            $locale = $user->preferredLocale();\n        } else {\n            $locale = session(static::SESSION_KEY, $request->getPreferredLanguage($locales));\n        }\n\n        if ($locale) {\n            app()->setLocale($locale);\n        }\n\n        return $next($request);\n    }\n}\n"
  },
  {
    "path": "src/Http/Middleware/MaybeRequireLogin.php",
    "content": "<?php\n\nnamespace Waterhole\\Http\\Middleware;\n\nuse Closure;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Support\\Facades\\Auth;\nuse Waterhole\\Models\\Channel;\nuse Waterhole\\Models\\Page;\nuse Waterhole\\Models\\PermissionCollection;\nuse Waterhole\\Models\\StructureLink;\n\n/**\n * Middleware to require guests to log in if there are no structure items\n * visible to the public.\n */\nclass MaybeRequireLogin\n{\n    public function __construct(protected PermissionCollection $permissions) {}\n\n    public function handle(Request $request, Closure $next)\n    {\n        if (\n            Auth::guest() &&\n            (!$this->permissions->can(null, 'view', Channel::class) &&\n                !$this->permissions->can(null, 'view', Page::class) &&\n                !$this->permissions->can(null, 'view', StructureLink::class))\n        ) {\n            return redirect()->route('waterhole.login');\n        }\n\n        return $next($request);\n    }\n}\n"
  },
  {
    "path": "src/Http/Middleware/MaybeRequirePassword.php",
    "content": "<?php\n\nnamespace Waterhole\\Http\\Middleware;\n\nuse Closure;\nuse Illuminate\\Auth\\Middleware\\RequirePassword;\nuse Illuminate\\Http\\Request;\n\n/**\n * Middleware to require password confirmation, but only if the user's account\n * has a password set.\n */\nclass MaybeRequirePassword\n{\n    public function __construct(private RequirePassword $middleware) {}\n\n    public function handle(Request $request, Closure $next)\n    {\n        if ($request->user()->password) {\n            return $this->middleware->handle($request, $next, 'waterhole.confirm-password');\n        }\n\n        return $next($request);\n    }\n}\n"
  },
  {
    "path": "src/Http/Middleware/PoweredByHeader.php",
    "content": "<?php\n\nnamespace Waterhole\\Http\\Middleware;\n\nuse Closure;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Http\\Response;\n\nclass PoweredByHeader\n{\n    public function handle(Request $request, Closure $next)\n    {\n        $response = $next($request);\n\n        if (config('waterhole.system.send_powered_by_header') && $response instanceof Response) {\n            $response->header('X-Powered-By', 'Waterhole');\n        }\n\n        return $response;\n    }\n}\n"
  },
  {
    "path": "src/Http/Middleware/RedirectIfAuthenticated.php",
    "content": "<?php\n\nnamespace Waterhole\\Http\\Middleware;\n\nuse Closure;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Support\\Facades\\Auth;\nuse Symfony\\Component\\HttpFoundation\\Response;\n\nclass RedirectIfAuthenticated\n{\n    /**\n     * Handle an incoming request.\n     *\n     * @param  \\Closure(\\Illuminate\\Http\\Request): (\\Symfony\\Component\\HttpFoundation\\Response)  $next\n     */\n    public function handle(Request $request, Closure $next, string ...$guards): Response\n    {\n        $guards = empty($guards) ? [null] : $guards;\n\n        foreach ($guards as $guard) {\n            if (Auth::guard($guard)->check()) {\n                return redirect()->route('waterhole.home');\n            }\n        }\n\n        return $next($request);\n    }\n}\n"
  },
  {
    "path": "src/Http/Middleware/StartSession.php",
    "content": "<?php\n\nnamespace Waterhole\\Http\\Middleware;\n\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Session\\Middleware\\StartSession as Middleware;\n\nclass StartSession extends Middleware\n{\n    protected function storeCurrentUrl(Request $request, $session)\n    {\n        if (!$request->headers->has('Turbo-Frame')) {\n            parent::storeCurrentUrl($request, $session);\n        }\n    }\n}\n"
  },
  {
    "path": "src/Http/Middleware/VerifyCsrfToken.php",
    "content": "<?php\n\nnamespace Waterhole\\Http\\Middleware;\n\nuse Illuminate\\Foundation\\Http\\Middleware\\VerifyCsrfToken as Middleware;\n\nclass VerifyCsrfToken extends Middleware\n{\n    /**\n     * Indicates whether the XSRF-TOKEN cookie should be set on the response.\n     *\n     * @var bool\n     */\n    protected $addHttpCookie = true;\n\n    /**\n     * The URIs that should be excluded from CSRF verification.\n     *\n     * @var array\n     */\n    protected $except = [];\n}\n"
  },
  {
    "path": "src/Layouts/CardsLayout.php",
    "content": "<?php\n\nnamespace Waterhole\\Layouts;\n\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Waterhole\\Forms\\Fields\\ChannelLayoutCards;\n\nclass CardsLayout extends Layout\n{\n    public function label(): string\n    {\n        return __('waterhole::system.layout-cards');\n    }\n\n    public function icon(): string\n    {\n        return 'tabler-layout-list';\n    }\n\n    public function wrapperClass(): string\n    {\n        return 'post-cards';\n    }\n\n    public function itemComponent(): string\n    {\n        return 'waterhole::post-card';\n    }\n\n    public function scope(Builder $query): void\n    {\n        $query->with('mentions', 'attachments');\n    }\n\n    public function configField(): string\n    {\n        return ChannelLayoutCards::class;\n    }\n}\n"
  },
  {
    "path": "src/Layouts/Layout.php",
    "content": "<?php\n\nnamespace Waterhole\\Layouts;\n\nuse Illuminate\\Database\\Eloquent\\Builder;\n\n/**\n * Base class for a Layout.\n */\nabstract class Layout\n{\n    /**\n     * The text label for the layout.\n     */\n    abstract public function label(): string;\n\n    /**\n     * The icon representing the layout.\n     */\n    public function icon(): ?string\n    {\n        return null;\n    }\n\n    /**\n     * Class to apply to the wrapper element.\n     */\n    public function wrapperClass(): ?string\n    {\n        return null;\n    }\n\n    /**\n     * The name of the component used to display a post in the layout.\n     */\n    abstract public function itemComponent(): string;\n\n    /**\n     * Apply a scope to the post feed query when this layout is active.\n     */\n    public function scope(Builder $query): void {}\n\n    /**\n     * Name of the Waterhole\\Forms\\Field class for configuration of the layout.\n     */\n    public function configField(): ?string\n    {\n        return null;\n    }\n}\n"
  },
  {
    "path": "src/Layouts/ListLayout.php",
    "content": "<?php\n\nnamespace Waterhole\\Layouts;\n\nuse Waterhole\\Forms\\Fields\\ChannelLayoutList;\n\nclass ListLayout extends Layout\n{\n    public function label(): string\n    {\n        return __('waterhole::system.layout-list');\n    }\n\n    public function icon(): string\n    {\n        return 'tabler-list';\n    }\n\n    public function wrapperClass(): string\n    {\n        return 'post-list card';\n    }\n\n    public function itemComponent(): string\n    {\n        return 'waterhole::post-list-item';\n    }\n\n    public function configField(): string\n    {\n        return ChannelLayoutList::class;\n    }\n}\n"
  },
  {
    "path": "src/Licensing/LicenseManager.php",
    "content": "<?php\n\nnamespace Waterhole\\Licensing;\n\nclass LicenseManager\n{\n    public function __construct(private Outpost $outpost) {}\n\n    public function response(?string $key = null, $default = null)\n    {\n        $response = $this->outpost->contact();\n\n        return data_get($response, $key, $default);\n    }\n\n    public function status(): int\n    {\n        return $this->response('status');\n    }\n\n    public function error(): ?string\n    {\n        return $this->response('waterhole.error');\n    }\n\n    public function public(): bool\n    {\n        return (bool) $this->response('public');\n    }\n\n    public function test(): bool\n    {\n        return $this->status() === 200 && !$this->public();\n    }\n\n    public function production(): bool\n    {\n        return !$this->test();\n    }\n\n    public function valid(): bool\n    {\n        return (bool) $this->response('waterhole.valid');\n    }\n\n    public function invalid(): bool\n    {\n        return !$this->valid();\n    }\n}\n"
  },
  {
    "path": "src/Licensing/Outpost.php",
    "content": "<?php\n\nnamespace Waterhole\\Licensing;\n\nuse Illuminate\\Contracts\\Cache\\Repository;\nuse Illuminate\\Http\\Client\\ConnectionException;\nuse Illuminate\\Http\\Client\\RequestException;\nuse Illuminate\\Support\\Arr;\nuse Illuminate\\Support\\Facades\\Http;\nuse Waterhole\\Waterhole;\n\nclass Outpost\n{\n    const ENDPOINT = 'https://api.waterhole.dev/v1/outpost';\n    const TIMEOUT = 5;\n    const CACHE_KEY = 'waterhole.outpost';\n\n    private array $response;\n\n    public function __construct(private Repository $cache) {}\n\n    public function contact(): array\n    {\n        return $this->response ??= $this->request();\n    }\n\n    private function request(): array\n    {\n        $cached = $this->cache->get(static::CACHE_KEY);\n        $payload = $this->payload();\n\n        if ($cached && !$this->payloadHasChanged($cached['payload'], $payload)) {\n            return $cached['response'];\n        }\n\n        try {\n            $response = Http::throw()\n                ->timeout(static::TIMEOUT)\n                ->connectTimeout(static::TIMEOUT)\n                ->post(static::ENDPOINT, $payload);\n\n            $json = $response->json();\n            $json['status'] = $response->status();\n            $expiry = now()->addHour();\n        } catch (RequestException $e) {\n            $json = ['status' => $e->response->status()];\n\n            if ($json['status'] === 422) {\n                $json['message'] = $e->response->json('message');\n            }\n\n            $expiry = match ($json['status']) {\n                429 => now()->addSeconds($e->response->header('Retry-After')[0]),\n                default => now()->addMinutes(5),\n            };\n\n            report($e);\n        } catch (ConnectionException $e) {\n            $json = ['status' => 0];\n            $expiry = now()->addMinutes(5);\n            report($e);\n        }\n\n        $this->cache->put(static::CACHE_KEY, ['payload' => $payload, 'response' => $json], $expiry);\n\n        return $json;\n    }\n\n    private function payload(): array\n    {\n        return [\n            'key' => config('waterhole.system.site_key'),\n            'host' => request()->getHost(),\n            'ip' => request()->server('SERVER_ADDR'),\n            'port' => request()->server('SERVER_PORT'),\n            'waterhole_version' => Waterhole::VERSION,\n            'php_version' => PHP_VERSION,\n            'packages' => [],\n        ];\n    }\n\n    private function payloadHasChanged($previous, $current): bool\n    {\n        $exclude = ['ip'];\n\n        return Arr::except($previous, $exclude) !== Arr::except($current, $exclude);\n    }\n}\n"
  },
  {
    "path": "src/Listeners/ReverifyInactiveUser.php",
    "content": "<?php\n\nnamespace Waterhole\\Listeners;\n\nuse Illuminate\\Auth\\Events\\Login;\nuse Waterhole\\Models\\User;\n\nclass ReverifyInactiveUser\n{\n    public function handle(Login $event): void\n    {\n        if (!($days = config('waterhole.users.reverify_after_inactive_days'))) {\n            return;\n        }\n\n        $user = $event->user;\n\n        if (!$user instanceof User || !$user->hasVerifiedEmail()) {\n            return;\n        }\n\n        $lastSeenAt = $user->last_seen_at ?: $user->created_at;\n\n        if ($lastSeenAt->isAfter(now()->subDays((int) $days))) {\n            return;\n        }\n\n        $user->update(['email_verified_at' => null]);\n        $user->sendEmailVerificationNotification();\n    }\n}\n"
  },
  {
    "path": "src/Mail/Markdown.php",
    "content": "<?php\n\nnamespace Waterhole\\Mail;\n\nuse Illuminate\\Contracts\\View\\Factory as ViewFactory;\nuse Illuminate\\Mail\\Markdown as BaseMarkdown;\n\nclass Markdown extends BaseMarkdown\n{\n    public function __construct(ViewFactory $view)\n    {\n        parent::__construct($view, [\n            'theme' => 'default',\n            'paths' => [__DIR__ . '/../../resources/views/mail'],\n        ]);\n    }\n}\n"
  },
  {
    "path": "src/Models/Attributes/FileAttribute.php",
    "content": "<?php\n\nnamespace Waterhole\\Models\\Attributes;\n\nuse Closure;\nuse Illuminate\\Database\\Eloquent\\Model;\nuse Illuminate\\Http\\File;\nuse Illuminate\\Http\\UploadedFile;\nuse Illuminate\\Support\\Facades\\Storage;\nuse Illuminate\\Support\\Str;\nuse Intervention\\Image\\Image as ImageObject;\nuse Intervention\\Image\\Interfaces\\EncodedImageInterface;\nuse Intervention\\Image\\Laravel\\Facades\\Image;\nuse function Waterhole\\is_absolute_url;\n\nclass FileAttribute\n{\n    public function __construct(\n        protected Model $model,\n        protected string $attribute,\n        protected string $directory,\n        protected ?string $disk = null,\n        protected ?Closure $encodeImage = null,\n    ) {\n        $this->disk ??= config('waterhole.uploads.disk');\n    }\n\n    public function upload(File|UploadedFile $file): void\n    {\n        if ($this->encodeImage && $this->isImageFile($file)) {\n            $this->storeImage(Image::read($file), $this->encodeImage);\n        }\n\n        $this->storeRawFile($file);\n    }\n\n    public function uploadImage(ImageObject $image): void\n    {\n        $encoder = $this->encodeImage ?? fn(ImageObject $image) => $image->toPng();\n\n        $this->storeImage($image, $encoder);\n    }\n\n    public function remove(): void\n    {\n        $value = $this->model->{$this->attribute};\n\n        if ($value) {\n            Storage::disk($this->disk)->delete($this->directory . '/' . $value);\n            $this->model->update([$this->attribute => null]);\n        }\n    }\n\n    public function url(): ?string\n    {\n        $value = $this->model->{$this->attribute};\n\n        if (!$value) {\n            return null;\n        }\n\n        if (is_absolute_url($value)) {\n            return $value;\n        }\n\n        return Storage::disk($this->disk)->url($this->directory . '/' . $value);\n    }\n\n    protected function storeRawFile(File|UploadedFile $file): void\n    {\n        $this->storeFileContents($file->getContent(), $file->extension());\n    }\n\n    protected function storeImage(ImageObject $image, Closure $encode): void\n    {\n        if (extension_loaded('exif')) {\n            $image->orient();\n        }\n\n        /** @var EncodedImageInterface $encodedImage */\n        $encodedImage = $encode($image);\n\n        $this->storeFileContents($encodedImage, Str::after($encodedImage->mediaType(), '/'));\n    }\n\n    protected function storeFileContents($contents, string $extension): void\n    {\n        $this->remove();\n\n        $filename = Str::random() . '.' . $extension;\n        $this->model->update([$this->attribute => $filename]);\n\n        Storage::disk($this->disk)->put($this->directory . '/' . $filename, $contents);\n    }\n\n    protected function isImageFile(File|UploadedFile $file): bool\n    {\n        return app('image')->driver()->supports($file->getMimeType());\n    }\n}\n"
  },
  {
    "path": "src/Models/AuthProvider.php",
    "content": "<?php\n\nnamespace Waterhole\\Models;\n\nuse Illuminate\\Database\\Eloquent\\Relations\\BelongsTo;\n\n/**\n * @property int $id\n * @property int $user_id\n * @property string $provider\n * @property string $identifier\n * @property \\Carbon\\Carbon $created_at\n * @property null|\\Carbon\\Carbon $last_login_at\n * @property-read User $user\n */\nclass AuthProvider extends Model\n{\n    const UPDATED_AT = null;\n\n    protected $casts = [\n        'last_login_at' => 'datetime',\n    ];\n\n    public function user(): BelongsTo\n    {\n        return $this->belongsTo(User::class);\n    }\n}\n"
  },
  {
    "path": "src/Models/Channel.php",
    "content": "<?php\n\nnamespace Waterhole\\Models;\n\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Casts\\Attribute;\nuse Illuminate\\Database\\Eloquent\\Factories\\HasFactory;\nuse Illuminate\\Database\\Eloquent\\Relations\\BelongsTo;\nuse Illuminate\\Database\\Eloquent\\Relations\\BelongsToMany;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasMany;\nuse Illuminate\\Support\\Str;\nuse Waterhole\\Database\\Factories\\ChannelFactory;\nuse Waterhole\\Models\\Concerns\\Followable;\nuse Waterhole\\Models\\Concerns\\HasIcon;\nuse Waterhole\\Models\\Concerns\\HasPermissions;\nuse Waterhole\\Models\\Concerns\\HasUserState;\nuse Waterhole\\Models\\Concerns\\Structurable;\nuse Waterhole\\Models\\Concerns\\UsesFormatter;\nuse Waterhole\\View\\Components\\PostFeedChannel;\nuse Waterhole\\View\\TurboStream;\n\n/**\n * @property int $id\n * @property string $name\n * @property string $slug\n * @property ?string $description\n * @property ?string $instructions\n * @property ?array $filters\n * @property ?string $layout\n * @property ?array $layout_config\n * @property bool $ignore\n * @property bool $answerable\n * @property bool $require_approval_posts\n * @property bool $require_approval_comments\n * @property ?array $translations\n * @property bool $posts_reactions_enabled\n * @property ?int $posts_reaction_set_id\n * @property bool $comments_reactions_enabled\n * @property ?int $comments_reaction_set_id\n * @property-read \\Illuminate\\Database\\Eloquent\\Collection $posts\n * @property-read \\Illuminate\\Database\\Eloquent\\Collection $newPosts\n * @property-read \\Illuminate\\Database\\Eloquent\\Collection $unreadPosts\n * @property-read ?ChannelUser $userState\n * @property-read ReactionSet $postsReactionSet\n * @property-read ReactionSet $commentsReactionSet\n * @property-read string $url\n * @property-read string $edit_url\n */\nclass Channel extends Model\n{\n    use HasFactory;\n    use Followable;\n    use HasIcon;\n    use HasPermissions;\n    use HasUserState;\n    use Structurable;\n    use UsesFormatter;\n\n    public $timestamps = false;\n\n    protected $casts = [\n        'filters' => 'json',\n        'layout_config' => 'json',\n        'ignore' => 'bool',\n        'answerable' => 'bool',\n        'translations' => 'json',\n        'require_approval_posts' => 'bool',\n        'require_approval_comments' => 'bool',\n        'posts_reactions_enabled' => 'bool',\n        'comments_reactions_enabled' => 'bool',\n    ];\n\n    protected static function booting(): void\n    {\n        static::creating(function (self $model) {\n            $model->slug ??= Str::slug($model->name);\n        });\n    }\n\n    protected static function newFactory(): ChannelFactory\n    {\n        return ChannelFactory::new();\n    }\n\n    /**\n     * Relationship with the channel's posts.\n     */\n    public function posts(): HasMany\n    {\n        return $this->hasMany(Post::class);\n    }\n\n    /**\n     * Relationship with posts that are followed and contain unread content.\n     */\n    public function unreadPosts(): HasMany\n    {\n        return $this->posts()->following()->unread();\n    }\n\n    /**\n     * Scope to select count of posts that are new since a channel was followed.\n     */\n    public function scopeWithNewPostsCount(Builder $query): void\n    {\n        $sub = Post::query()\n            ->selectRaw('COUNT(*)')\n            ->whereColumn('posts.channel_id', 'channels.id')\n            ->whereDoesntHave('userState')\n            ->where('created_at', '>', 'followed_at');\n\n        $query\n            ->leftJoinRelation('userState')\n            ->selectRaw(\n                'CASE WHEN followed_at IS NOT NULL THEN (' .\n                    $sub->toSql() .\n                    ') ELSE 0 END AS new_posts_count',\n                $sub->getBindings(),\n            );\n    }\n\n    /**\n     * Relationship with posts that are new since this channel was followed.\n     */\n    public function newPosts(): HasMany\n    {\n        return $this->posts()\n            ->whereDoesntHave('userState')\n            ->whereHas('channel.userState', function ($query) {\n                $query->whereColumn('posts.created_at', '>', 'followed_at');\n            });\n    }\n\n    public function postsReactionSet(): BelongsTo\n    {\n        return $this->belongsTo(ReactionSet::class, 'posts_reaction_set_id')->withDefault(\n            fn($model, $parent) => $parent->posts_reactions_enabled\n                ? ReactionSet::defaultPosts()\n                : null,\n        );\n    }\n\n    public function commentsReactionSet(): BelongsTo\n    {\n        return $this->belongsTo(ReactionSet::class, 'comments_reaction_set_id')->withDefault(\n            fn($model, $parent) => $parent->comments_reactions_enabled\n                ? ReactionSet::defaultComments()\n                : null,\n        );\n    }\n\n    public function taxonomies(): BelongsToMany\n    {\n        return $this->belongsToMany(Taxonomy::class);\n    }\n\n    public function abilities(): array\n    {\n        return ['view', 'comment', 'post', 'moderate'];\n    }\n\n    public function defaultAbilities(): array\n    {\n        return ['view', 'comment', 'post'];\n    }\n\n    protected function url(): Attribute\n    {\n        return Attribute::make(\n            get: fn() => route('waterhole.channels.show', ['channel' => $this]),\n        )->shouldCache();\n    }\n\n    protected function editUrl(): Attribute\n    {\n        return Attribute::make(\n            get: fn() => route('waterhole.cp.structure.channels.edit', ['channel' => $this]),\n        )->shouldCache();\n    }\n\n    public function scopeIgnoring(Builder $query): void\n    {\n        $query\n            ->leftJoinRelation('userState')\n            ->where('channel_user.notifications', 'ignore')\n            ->orWhere(\n                fn($query) => $query\n                    ->where('ignore', true)\n                    ->whereNull('channel_user.notifications'),\n            );\n    }\n\n    public function isIgnored(): bool\n    {\n        return $this->userState?->notifications === 'ignore' ||\n            (!$this->userState?->notifications && $this->ignore);\n    }\n\n    /**\n     * Get the Turbo Streams that should be sent when this post is updated.\n     */\n    public function streamUpdated(): array\n    {\n        return [TurboStream::replace(new PostFeedChannel($this))];\n    }\n}\n"
  },
  {
    "path": "src/Models/ChannelUser.php",
    "content": "<?php\n\nnamespace Waterhole\\Models;\n\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Relations\\BelongsTo;\n\n/**\n * @property int $channel_id\n * @property int $user_id\n * @property null|string $notifications\n * @property null|\\Carbon\\Carbon $followed_at\n * @property-read Channel $channel\n * @property-read User $user\n */\nclass ChannelUser extends Model\n{\n    public $timestamps = false;\n\n    protected $table = 'channel_user';\n\n    protected $casts = [\n        'followed_at' => 'datetime',\n    ];\n\n    public function channel(): BelongsTo\n    {\n        return $this->belongsTo(Channel::class);\n    }\n\n    public function user(): BelongsTo\n    {\n        return $this->belongsTo(User::class);\n    }\n\n    public function getKey(): string\n    {\n        return $this->channel_id . '-' . $this->user_id;\n    }\n\n    protected function setKeysForSaveQuery($query): Builder\n    {\n        return $query->where('channel_id', $this->channel_id)->where('user_id', $this->user_id);\n    }\n}\n"
  },
  {
    "path": "src/Models/Comment.php",
    "content": "<?php\n\nnamespace Waterhole\\Models;\n\nuse HotwiredLaravel\\TurboLaravel\\Models\\Broadcasts;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Casts\\Attribute;\nuse Illuminate\\Database\\Eloquent\\Factories\\HasFactory;\nuse Illuminate\\Database\\Eloquent\\Relations\\BelongsTo;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasMany;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasOneThrough;\nuse Illuminate\\Support\\Facades\\Notification;\nuse Illuminate\\Validation\\Rule;\nuse Staudenmeir\\LaravelAdjacencyList\\Eloquent\\HasRecursiveRelationships;\nuse Waterhole\\Database\\Factories\\CommentFactory;\nuse Waterhole\\Events\\NewComment;\nuse Waterhole\\Models\\Concerns\\Approvable;\nuse Waterhole\\Models\\Concerns\\Deletable;\nuse Waterhole\\Models\\Concerns\\Flaggable;\nuse Waterhole\\Models\\Concerns\\HasBody;\nuse Waterhole\\Models\\Concerns\\NotificationContent;\nuse Waterhole\\Models\\Concerns\\Reactable;\nuse Waterhole\\Models\\Concerns\\ValidatesData;\nuse Waterhole\\Notifications\\Mention;\nuse Waterhole\\Notifications\\NewComment as NewCommentNotification;\nuse Waterhole\\Scopes\\CommentIndexScope;\nuse Waterhole\\View\\Components;\nuse Waterhole\\View\\TurboStream;\nuse function HotwiredLaravel\\TurboLaravel\\dom_id;\n\n/**\n * @property int $id\n * @property int $post_id\n * @property null|int $parent_id\n * @property null|int $user_id\n * @property string $body\n * @property \\Carbon\\Carbon $created_at\n * @property null|\\Carbon\\Carbon $edited_at\n * @property int $reply_count\n * @property int $score\n * @property-read Post $post\n * @property-read null|User $user\n * @property-read \\Illuminate\\Database\\Eloquent\\Collection $replies\n * @property-read null|Comment $parent\n * @property-read string $url\n * @property-read string $edit_url\n * @property-read string $post_url\n */\nclass Comment extends Model\n{\n    use HasFactory;\n    use HasBody;\n    use Reactable;\n    use HasRecursiveRelationships;\n    use ValidatesData;\n    use Broadcasts;\n    use NotificationContent;\n    use Deletable;\n    use Approvable;\n    use Flaggable;\n\n    public const UPDATED_AT = null;\n\n    protected $casts = [\n        'edited_at' => 'datetime',\n        'is_pinned' => 'boolean',\n    ];\n\n    // Prevent recursion during serialization\n    protected $hidden = ['parent'];\n\n    protected static function booting(): void\n    {\n        static::addGlobalScope('visible', function ($query) {\n            $user = auth()->user();\n\n            if (app()->runningInConsole() && !app()->runningUnitTests() && !$user) {\n                return;\n            }\n\n            $query->visible($user);\n        });\n\n        // Whenever a comment is created or deleted, we will update the metadata\n        // (number of replies) of the post and any parent comment that this one\n        // was made in reply to.\n        $refreshMetadata = function (self $comment) {\n            $comment->post->refreshCommentMetadata()->save();\n            $comment->parent?->refreshReplyMetadata()->save();\n        };\n\n        static::created($refreshMetadata);\n        static::deleted($refreshMetadata);\n        static::restored($refreshMetadata);\n\n        static::updated(function (self $comment) use ($refreshMetadata) {\n            if ($comment->wasChanged('is_approved') && $comment->is_approved) {\n                $refreshMetadata($comment);\n            }\n        });\n\n        // By default, we calculate each comment's index (ie. how many comments\n        // came before it) when querying comments. Since this is an expensive\n        // thing to do, put it in a global scope so that it can be disabled.\n        static::addGlobalScope(new CommentIndexScope());\n    }\n\n    protected static function booted(): void\n    {\n        // Register the listener to deliver created events after the HasBody\n        // trait has been booted and has registered its listeners, to ensure\n        // the body is processed before delivering @mention notifications etc.\n        static::created(function (self $comment) {\n            $comment->deliverCreatedEvents();\n        });\n    }\n\n    protected static function newFactory(): CommentFactory\n    {\n        return CommentFactory::new();\n    }\n\n    protected function deliverCreatedEvents(): void\n    {\n        if (!$this->is_approved || !$this->post->is_approved) {\n            return;\n        }\n\n        broadcast(new NewComment($this))->toOthers();\n\n        // When a new comment is created, send notifications to mentioned\n        // users as well as the user the comment is in reply to.\n        $users = $this->mentions;\n\n        if ($this->parent?->user) {\n            $users->push($this->parent->user);\n        }\n\n        $users = $users->unique()->except($this->user_id);\n\n        $this->post->usersWereMentioned($users);\n\n        Notification::send($users, new Mention($this));\n\n        // Send out a \"new comment\" notification to all followers of this post,\n        // except for the user who made the comment.\n        Notification::send(\n            $this->post->followedBy->diff($users)->except($this->user_id),\n            new NewCommentNotification($this),\n        );\n    }\n\n    public function post(): BelongsTo\n    {\n        return $this->belongsTo(Post::class)->withoutGlobalScope('visible');\n    }\n\n    public function channel(): HasOneThrough\n    {\n        return $this->hasOneThrough(\n            Channel::class,\n            Post::class,\n            'id',\n            'id',\n            'post_id',\n            'channel_id',\n        )->withTrashedParents();\n    }\n\n    public function user(): BelongsTo\n    {\n        return $this->belongsTo(User::class);\n    }\n\n    public function replies(): HasMany\n    {\n        return $this->hasMany(self::class, 'parent_id');\n    }\n\n    public function parent(): BelongsTo\n    {\n        return $this->belongsTo(self::class);\n    }\n    public function scopeVisible(Builder $query, ?User $user): void\n    {\n        // Remove the default visible global scope which scopes for the\n        // currently authenticated user.\n        $query->withoutGlobalScope('visible');\n\n        // Ensure comments belong to a post which is visible to this user,\n        // unless we are getting comments for a specific post, in which case\n        // we assume the post visibility has already been checked.\n        $hasWherePostId = collect($query->getQuery()->wheres)\n            ->where('column', 'comments.post_id')\n            ->isNotEmpty();\n\n        if (!$hasWherePostId) {\n            $query->whereHas('post', fn($query) => $query->visible($user));\n        }\n\n        $moderationScope = fn(Builder $query, array $channelIds) => $query->orWhereHas(\n            'post',\n            fn(Builder $query) => $query->whereIn('channel_id', $channelIds),\n        );\n\n        $this->applyApprovalVisibility($query, $user, $moderationScope);\n\n        $this->applyDeletionVisibility($query, $user, $moderationScope);\n    }\n\n    /**\n     * Determine whether this comment is unread by the current user.\n     */\n    public function isUnread(): bool\n    {\n        return $this->post->userState && $this->post->userState->last_read_at < $this->created_at;\n    }\n\n    /**\n     * Determine whether this comment is read by the current user.\n     */\n    public function isRead(): bool\n    {\n        return $this->post->userState && !$this->isUnread();\n    }\n\n    /**\n     * Determine whether this comment has been marked as the answer.\n     */\n    public function isAnswer(): bool\n    {\n        return $this->post->answer_id === $this->id;\n    }\n\n    /**\n     * Mark this comment as having been edited just now.\n     */\n    public function markAsEdited(): static\n    {\n        $this->edited_at = now();\n\n        return $this;\n    }\n\n    /**\n     * Refresh the metadata about this comment's replies.\n     */\n    public function refreshReplyMetadata(): static\n    {\n        $this->reply_count = $this->replies()->count();\n\n        return $this;\n    }\n\n    public function getPerPage(): int\n    {\n        return config('waterhole.forum.comments_per_page', $this->perPage);\n    }\n\n    /**\n     * Get the Turbo Streams that should be sent when this comment is updated.\n     */\n    public function streamUpdated(): array\n    {\n        return [TurboStream::replace(new Components\\CommentFull($this))];\n    }\n\n    /**\n     * Get the Turbo Streams that should be sent when this comment is removed.\n     */\n    public function streamRemoved(): array\n    {\n        return [TurboStream::remove(new Components\\CommentFrame($this))];\n    }\n\n    protected function url(): Attribute\n    {\n        return Attribute::make(\n            get: fn() => route('waterhole.posts.comments.show', [\n                'post' => $this->post,\n                'comment' => $this,\n            ]),\n        )->shouldCache();\n    }\n\n    protected function editUrl(): Attribute\n    {\n        return Attribute::make(\n            get: fn() => route('waterhole.posts.comments.edit', [\n                'post' => $this->post,\n                'comment' => $this,\n            ]),\n        )->shouldCache();\n    }\n\n    protected function postUrl(): Attribute\n    {\n        return Attribute::make(\n            get: function () {\n                if (isset($this->index)) {\n                    return $this->post->urlAtIndex($this->index) . '#' . dom_id($this);\n                }\n\n                return $this->post->url . '?comment=' . $this->id;\n            },\n        )->shouldCache();\n    }\n\n    public function reactionsUrl(ReactionType $reactionType): string\n    {\n        return route('waterhole.comments.reactions', [\n            'comment' => $this,\n            'reactionType' => $reactionType,\n        ]);\n    }\n\n    public static function rules(?Comment $instance = null): array\n    {\n        return [\n            'parent_id' => ['nullable', Rule::exists(Comment::class, 'id')],\n            'body' => ['required', 'string'],\n        ];\n    }\n\n    public function reactionSet(): ?ReactionSet\n    {\n        return $this->post->channel->commentsReactionSet;\n    }\n\n    public function canModerate(?User $user): bool\n    {\n        return (bool) $user?->can('waterhole.comment.moderate', $this);\n    }\n\n    public function flagUrl(): string\n    {\n        return $this->post_url;\n    }\n}\n"
  },
  {
    "path": "src/Models/Concerns/Approvable.php",
    "content": "<?php\n\nnamespace Waterhole\\Models\\Concerns;\n\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Support\\Facades\\Notification;\nuse Waterhole\\Models\\Channel;\nuse Waterhole\\Models\\User;\nuse Waterhole\\Notifications\\ContentApproved;\n\n/**\n * @property bool $is_approved\n */\ntrait Approvable\n{\n    use Flaggable;\n\n    protected static function bootApprovable(): void\n    {\n        static::created(function (self $model) {\n            if (!$model->is_approved) {\n                $model->flags()->create(['reason' => 'approval']);\n            }\n        });\n\n        static::updated(function (self $model) {\n            if ($model->wasChanged('is_approved') && $model->is_approved) {\n                if ($model->user) {\n                    $groupsToRemove = $model->user->groups\n                        ->filter(\n                            fn($group) => ($group->rules['requires_approval'] ?? false) &&\n                                ($group->rules['remove_after_approval'] ?? false),\n                        )\n                        ->pluck('id');\n\n                    $model->user->groups()->detach($groupsToRemove);\n\n                    Notification::send($model->user, new ContentApproved($model));\n                }\n\n                if (method_exists($model, 'deliverCreatedEvents')) {\n                    $model->deliverCreatedEvents();\n                }\n            }\n        });\n    }\n\n    public function initializeApprovable(): void\n    {\n        $this->is_approved ??= true;\n\n        if (!isset($this->casts['is_approved'])) {\n            $this->casts['is_approved'] = 'boolean';\n        }\n    }\n\n    public function isApproved(): bool\n    {\n        return $this->is_approved;\n    }\n\n    protected function applyApprovalVisibility(\n        Builder $query,\n        ?User $user,\n        callable $moderationScope,\n    ): void {\n        if ($user?->isAdmin()) {\n            return;\n        }\n\n        $query->where(function (Builder $query) use ($user, $moderationScope) {\n            $query->where($query->qualifyColumn('is_approved'), true);\n\n            if (!$user) {\n                return;\n            }\n\n            $query->orWhere($query->qualifyColumn('user_id'), $user->id);\n\n            if (!is_null($channelIds = Channel::allPermitted($user, 'moderate'))) {\n                $moderationScope($query, $channelIds);\n            }\n        });\n    }\n}\n"
  },
  {
    "path": "src/Models/Concerns/Deletable.php",
    "content": "<?php\n\nnamespace Waterhole\\Models\\Concerns;\n\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Relations\\BelongsTo;\nuse Illuminate\\Database\\Eloquent\\SoftDeletes;\nuse Illuminate\\Support\\Facades\\Notification;\nuse Waterhole\\Models\\Channel;\nuse Waterhole\\Models\\Notification as NotificationModel;\nuse Waterhole\\Models\\User;\nuse Waterhole\\Notifications\\ContentRemoved;\n\n/**\n * @property null|\\Carbon\\Carbon $deleted_at\n * @property null|int $deleted_by\n * @property null|string $deleted_reason\n * @property null|string $deleted_message\n * @property-read null|User $deletedBy\n */\ntrait Deletable\n{\n    use SoftDeletes;\n\n    protected static function bootDeletable(): void\n    {\n        static::addGlobalScope(fn($query) => $query->withTrashed());\n\n        static::deleted(function (self $model) {\n            if ($model->deleted_by && $model->user && $model->deleted_by !== $model->user_id) {\n                Notification::send($model->user, new ContentRemoved($model));\n            }\n        });\n\n        static::restored(function (self $model) {\n            NotificationModel::query()\n                ->where('type', ContentRemoved::class)\n                ->whereMorphedTo('content', $model)\n                ->delete();\n        });\n    }\n\n    public function deletedBy(): BelongsTo\n    {\n        return $this->belongsTo(User::class, 'deleted_by');\n    }\n\n    protected function applyDeletionVisibility(\n        Builder $query,\n        ?User $user,\n        callable $moderationScope,\n    ): void {\n        if ($user?->isAdmin()) {\n            return;\n        }\n\n        $query->where(function (Builder $query) use ($user, $moderationScope) {\n            $query->whereNull($query->qualifyColumn('deleted_at'));\n\n            if (!$user) {\n                return;\n            }\n\n            $query->orWhere($query->qualifyColumn('user_id'), $user->id);\n\n            if (!is_null($channelIds = Channel::allPermitted($user, 'moderate'))) {\n                $moderationScope($query, $channelIds);\n            }\n        });\n    }\n}\n"
  },
  {
    "path": "src/Models/Concerns/Flaggable.php",
    "content": "<?php\n\nnamespace Waterhole\\Models\\Concerns;\n\nuse Illuminate\\Database\\Eloquent\\Relations\\MorphMany;\nuse Waterhole\\Models\\Flag;\nuse Waterhole\\Models\\User;\nuse Waterhole\\Notifications\\NewFlag;\n\ntrait Flaggable\n{\n    protected static function bootFlaggable(): void\n    {\n        static::forceDeleting(function (self $model) {\n            $model->pendingFlags()->delete();\n        });\n    }\n\n    public function flags(): MorphMany\n    {\n        return $this->morphMany(Flag::class, 'subject');\n    }\n\n    public function pendingFlags(): MorphMany\n    {\n        return $this->flags()->pending();\n    }\n\n    abstract public function canModerate(?User $user): bool;\n\n    public function flagUrl(): string\n    {\n        return $this->url;\n    }\n\n    public function resolveFlags(User $moderator): int\n    {\n        $resolved = $this->pendingFlags()\n            ->withoutGlobalScope('subjectPresent')\n            ->update([\n                'resolved_at' => now(),\n                'resolved_by' => $moderator->getKey(),\n            ]);\n\n        $this->notifications()->where('type', NewFlag::class)->delete();\n\n        return $resolved;\n    }\n}\n"
  },
  {
    "path": "src/Models/Concerns/Followable.php",
    "content": "<?php\n\nnamespace Waterhole\\Models\\Concerns;\n\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Relations\\BelongsToMany;\nuse Waterhole\\Models\\User;\n\n/**\n * Methods to make a model \"followable\" per user.\n *\n * A followable model can be \"followed\" or \"ignored\" by each user, which will\n * determine how they see and are notified about activity in the model. The\n * model must have user state (see `HasUserState`) with `notification` and\n * `followed_at` columns to store this state.\n *\n * @property-read \\Illuminate\\Database\\Eloquent\\Collection $followedBy\n * @property-read \\Illuminate\\Database\\Eloquent\\Collection $ignoredBy\n */\ntrait Followable\n{\n    /**\n     * Relationship with all users who are following this model.\n     */\n    public function followedBy(): BelongsToMany\n    {\n        return $this->belongsToMany(User::class)->wherePivot('notifications', 'follow');\n    }\n\n    /**\n     * Relationship with all users who are ignoring this model.\n     */\n    public function ignoredBy(): BelongsToMany\n    {\n        return $this->belongsToMany(User::class)->wherePivot('notifications', 'ignore');\n    }\n\n    /**\n     * Find only models that the current user is following.\n     */\n    public function scopeFollowing(Builder $query): void\n    {\n        $query->whereHas('userState', fn($query) => $query->where('notifications', 'follow'));\n    }\n\n    /**\n     * Find only models that the current user is ignoring.\n     */\n    public function scopeIgnoring(Builder $query): void\n    {\n        $query->whereHas('userState', fn($query) => $query->where('notifications', 'ignore'));\n    }\n\n    /**\n     * Save the current user's notification preference for this model.\n     */\n    protected function setNotifications(?string $value): void\n    {\n        if ($this->userState->notifications !== $value) {\n            $this->userState->notifications = $value;\n            $this->userState->followed_at = $value === 'follow' ? now() : null;\n            $this->userState->save();\n        }\n    }\n\n    /**\n     * Follow this model for the current user.\n     */\n    public function follow(): void\n    {\n        $this->setNotifications('follow');\n    }\n\n    /**\n     * Unfollow this model for the current user.\n     */\n    public function unfollow(): void\n    {\n        $this->setNotifications('normal');\n    }\n\n    /**\n     * Ignore this model for the current user.\n     */\n    public function ignore(): void\n    {\n        $this->setNotifications('ignore');\n    }\n\n    /**\n     * Unignore this model for the current user.\n     */\n    public function unignore(): void\n    {\n        $this->setNotifications('normal');\n    }\n\n    /**\n     * Whether the current user is following this model.\n     */\n    public function isFollowed(): bool\n    {\n        return $this->userState?->notifications === 'follow';\n    }\n\n    /**\n     * Whether the current user is ignoring this model.\n     */\n    public function isIgnored(): bool\n    {\n        return $this->userState?->notifications === 'ignore';\n    }\n}\n"
  },
  {
    "path": "src/Models/Concerns/HasBody.php",
    "content": "<?php\n\nnamespace Waterhole\\Models\\Concerns;\n\nuse Illuminate\\Database\\Eloquent\\Relations\\MorphToMany;\nuse Illuminate\\Support\\HtmlString;\nuse Waterhole\\Formatter\\FormatMentions;\nuse Waterhole\\Formatter\\FormatUploads;\nuse Waterhole\\Models\\Model;\nuse Waterhole\\Models\\Upload;\nuse Waterhole\\Models\\User;\n\n/**\n * Methods to give a model a formatted text `body`.\n *\n * This trait assumes a `body` column exists on the model. When this attribute\n * is set on the model, the content will be parsed into an XML document by the\n * Formatter, and stored in the database in this form. When the `body` attribute\n * is retrieved, it is unparsed back into the original plain-text version.\n *\n * This trait also adds a `mentions` relationship to store a list of the users\n * mentioned in the body using the @ prefix. This relationship can then be\n * loaded before the body is rendered so that the Formatter can substitute in\n * the most up-to-date usernames.\n *\n * @property string $body The original unformatted version of the body.\n * @property-read HtmlString $body_html The formatted HTML version of the body\n *   for the current user.\n * @property string $parsed_body The intermediary parsed XML document.\n * @property-read string $body_text The parsed body with formatting removed.\n * @property-read \\Illuminate\\Database\\Eloquent\\Collection $mentions\n * @property-read \\Illuminate\\Database\\Eloquent\\Collection $attachments\n */\ntrait HasBody\n{\n    use UsesFormatter;\n\n    public static function bootHasBody(): void\n    {\n        // Whenever the model is saved, sync the users and uploads mentioned in\n        // the body into their respective relationships. We register `created`\n        // and `updated` handlers instead of using the `saved` event, because we\n        // want this to run as early as possible.\n\n        $onSave = function (Model $model) {\n            if (!$model->wasRecentlyCreated && !$model->wasChanged('body')) {\n                return;\n            }\n\n            $model->mentions()->sync(\n                User::query()\n                    ->whereKey(FormatMentions::getMentionedUsers($model->parsed_body))\n                    ->pluck('id'),\n            );\n\n            $model->attachments()->sync(\n                Upload::query()\n                    ->whereIn('filename', FormatUploads::getAttachedUploads($model->parsed_body))\n                    ->pluck('id'),\n            );\n        };\n\n        static::created($onSave);\n        static::updated($onSave);\n\n        $onDelete = function (Model $model) {\n            $model->mentions()->detach();\n            $model->attachments()->detach();\n        };\n\n        if (method_exists(static::class, 'forceDeleted')) {\n            static::forceDeleted($onDelete);\n        } else {\n            static::deleted($onDelete);\n        }\n    }\n\n    /**\n     * Relationship with the users who were mentioned in the body.\n     */\n    public function mentions(): MorphToMany\n    {\n        return $this->morphToMany(User::class, 'content', 'mentions');\n    }\n\n    /**\n     * Relationship with the uploads that were attached in the body.\n     */\n    public function attachments(): MorphToMany\n    {\n        return $this->morphToMany(Upload::class, 'content', 'attachments');\n    }\n}\n"
  },
  {
    "path": "src/Models/Concerns/HasFileAttributes.php",
    "content": "<?php\n\nnamespace Waterhole\\Models\\Concerns;\n\nuse Closure;\nuse Waterhole\\Models\\Attributes\\FileAttribute;\n\n/**\n * Methods to associate uploaded files with a model.\n */\ntrait HasFileAttributes\n{\n    protected function fileAttribute(\n        string $attribute,\n        string $directory,\n        ?string $disk = null,\n        ?Closure $encodeImage = null,\n    ): FileAttribute {\n        return new FileAttribute($this, $attribute, $directory, $disk, $encodeImage);\n    }\n}\n"
  },
  {
    "path": "src/Models/Concerns/HasIcon.php",
    "content": "<?php\n\nnamespace Waterhole\\Models\\Concerns;\n\nuse Illuminate\\Http\\UploadedFile;\nuse Intervention\\Image\\Image;\nuse Waterhole\\Models\\Attributes\\FileAttribute;\n\n/**\n * Methods to manage a model's `icon` attribute.\n *\n * The `icon` attribute is stored as `type:value`, where `type` is one of the\n * following:\n *\n * - `emoji`, where `value` is an emoji character (eg. `emoji:😊`)\n * - `svg`, where `value` is the name of a Blade Icon (eg. `svg:tabler-heart`)\n * - `file`, where `value` is the path to an image file\n *\n * @property string $icon\n * @property ?string $icon_file The path to the icon file, if the icon is the\n *   `file` type.\n */\ntrait HasIcon\n{\n    use HasFileAttributes;\n\n    /**\n     * Save the icon using input from an <x-waterhole::icon-picker> component.\n     */\n    public function saveIcon(array $icon): void\n    {\n        if (empty($icon['type'])) {\n            $this->iconFile()->remove();\n            $this->icon = null;\n            $this->save();\n\n            return;\n        }\n\n        if ($icon['type'] === 'file') {\n            $file = $icon['file'] ?? null;\n\n            if ($file instanceof UploadedFile) {\n                $this->iconFile()->upload($file);\n            }\n        } else {\n            $this->iconFile()->remove();\n            $this->icon = $icon['type'] . ':' . ($icon[$icon['type']] ?? '');\n            $this->save();\n        }\n    }\n\n    public function iconFile(): FileAttribute\n    {\n        return $this->fileAttribute(\n            attribute: 'icon_file',\n            directory: 'icons',\n            encodeImage: fn(Image $image) => $image->scaleDown(50, 50)->toPng(),\n        );\n    }\n\n    public function getIconFileAttribute(): ?string\n    {\n        return str_starts_with($this->icon ?? '', 'file:') ? substr($this->icon, 5) : null;\n    }\n\n    public function setIconFileAttribute(?string $value): void\n    {\n        $this->attributes['icon'] = $value ? 'file:' . $value : null;\n    }\n}\n"
  },
  {
    "path": "src/Models/Concerns/HasPermissions.php",
    "content": "<?php\n\nnamespace Waterhole\\Models\\Concerns;\n\nuse Illuminate\\Database\\Eloquent\\Relations\\MorphMany;\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\Support\\Facades\\DB;\nuse Waterhole\\Models\\Group;\nuse Waterhole\\Models\\Permission;\nuse Waterhole\\Models\\PermissionCollection;\nuse Waterhole\\Models\\User;\nuse Waterhole\\Scopes\\PermittedScope;\nuse Waterhole\\Waterhole;\n\n/**\n * Methods to manage permissions on a model.\n *\n * This trait is distinct from `ReceivesPermissions` in that it is for models\n * that can be acted *upon*, rather than models that take the action (users\n * and groups).\n *\n * @property-read PermissionCollection $permissions\n */\ntrait HasPermissions\n{\n    public static function bootHasPermissions(): void\n    {\n        static::addGlobalScope('visible', new PermittedScope());\n\n        // Ensure model deletion cascades to permission records.\n        static::deleted(function (self $model) {\n            $model->permissions()->delete();\n        });\n    }\n\n    /**\n     * Relationship with the permission records pertaining to this model.\n     */\n    public function permissions(): MorphMany\n    {\n        return $this->morphMany(Permission::class, 'scope');\n    }\n\n    /**\n     * A list of abilities that can be applied to this model.\n     */\n    public function abilities(): array\n    {\n        return ['view'];\n    }\n\n    /**\n     * A list of abilities to check by default when creating a model.\n     */\n    public function defaultAbilities(): array\n    {\n        return ['view'];\n    }\n\n    /**\n     * Save the permissions to the database.\n     */\n    public function savePermissions(?array $grid): void\n    {\n        $this->permissions()->delete();\n\n        if (!$grid) {\n            return;\n        }\n\n        $this->permissions()->createMany(\n            collect($grid)->flatMap(function ($abilities, $recipient) {\n                [$type, $id] = explode(':', $recipient) + [null, null];\n\n                return collect($abilities)\n                    ->filter()\n                    ->map(\n                        fn($v, $ability) => [\n                            'recipient_type' => $type,\n                            'recipient_id' => $id,\n                            'ability' => $ability,\n                        ],\n                    )\n                    ->values();\n            }),\n        );\n    }\n\n    public function isPublic(string $ability = 'view'): bool\n    {\n        return Waterhole::permissions()->can(null, $ability, $this);\n    }\n\n    public function usersWithAbility(string $ability): ?Collection\n    {\n        if (\n            $this->isPublic($ability) ||\n            Waterhole::permissions()->can(Group::member(), $ability, $this)\n        ) {\n            return null;\n        }\n\n        $permissions = Waterhole::permissions()->scope($this)->where('ability', $ability);\n\n        $groupIds = $permissions\n            ->where('recipient_type', (new Group())->getMorphClass())\n            ->pluck('recipient_id');\n\n        $userIds = $permissions\n            ->where('recipient_type', (new User())->getMorphClass())\n            ->pluck('recipient_id');\n\n        $groupUserIds = DB::table('group_user')\n            ->whereIn('group_id', [...$groupIds, Group::ADMIN_ID])\n            ->pluck('user_id');\n\n        $userIds = $groupUserIds->merge($userIds)->unique()->values();\n\n        return User::with('groups')->findMany($userIds);\n    }\n\n    /**\n     * Get the model IDs that the given user has permission for.\n     *\n     * If the user is an admin, the result will be null, meaning there is no\n     * restriction on the models they have permission for.\n     */\n    public static function allPermitted(?User $user, string $ability = 'view'): ?array\n    {\n        if ($user?->isAdmin()) {\n            return null;\n        }\n\n        return Waterhole::permissions()->ids($user, $ability, static::class);\n    }\n}\n"
  },
  {
    "path": "src/Models/Concerns/HasUserState.php",
    "content": "<?php\n\nnamespace Waterhole\\Models\\Concerns;\n\nuse Illuminate\\Database\\Eloquent\\Relations\\HasOne;\nuse Illuminate\\Support\\Facades\\Auth;\nuse Waterhole\\Models\\User;\n\n/**\n * Methods to give a model per-user state.\n */\ntrait HasUserState\n{\n    /**\n     * Relationship with the state record for a user.\n     *\n     * Defaults to the current user when no user is specified, as is the case\n     * when eager-loading the relationship.\n     *\n     * The model representing the user state table is derived by appending\n     * `User` to the parent model name (eg. `PostUser`). Alternatively, it can\n     * be specified by setting the `userStateModel` property.\n     */\n    public function userState(?User $user = null): HasOne\n    {\n        $relation = $this->hasOne($this->userStateModel ?? get_class($this) . 'User');\n\n        if ($userId = $user->id ?? Auth::id()) {\n            $relation->withDefault(['user_id' => $userId]);\n        }\n\n        $relation->where($relation->qualifyColumn('user_id'), $userId);\n\n        return $relation;\n    }\n\n    /**\n     * Load the userState relationship for the given user.\n     */\n    public function loadUserState(User $user): static\n    {\n        $this->setRelation('userState', $this->userState($user)->getResults());\n\n        return $this;\n    }\n\n    public function getRelationValue($key)\n    {\n        if ($key === 'userState' && !Auth::check()) {\n            return null;\n        }\n\n        return parent::getRelationValue($key);\n    }\n}\n"
  },
  {
    "path": "src/Models/Concerns/NotificationContent.php",
    "content": "<?php\n\nnamespace Waterhole\\Models\\Concerns;\n\nuse Illuminate\\Database\\Eloquent\\Relations\\MorphMany;\nuse Waterhole\\Models\\Notification;\n\n/**\n * Trait for models that notifications can be about.\n */\ntrait NotificationContent\n{\n    public static function bootNotificationContent(): void\n    {\n        static::deleted(function (self $model) {\n            $model->notifications()->delete();\n            $model->groupedNotifications()->delete();\n        });\n    }\n\n    /**\n     * Relationship with the notifications about this model.\n     */\n    public function notifications(): MorphMany\n    {\n        return $this->morphMany(Notification::class, 'content');\n    }\n\n    /**\n     * Relationship with the notifications grouped by this model.\n     */\n    public function groupedNotifications(): MorphMany\n    {\n        return $this->morphMany(Notification::class, 'group');\n    }\n}\n"
  },
  {
    "path": "src/Models/Concerns/Reactable.php",
    "content": "<?php\n\nnamespace Waterhole\\Models\\Concerns;\n\nuse Illuminate\\Database\\Eloquent\\Relations\\HasManyThrough;\nuse Illuminate\\Database\\Eloquent\\Relations\\MorphMany;\nuse Illuminate\\Support\\Facades\\Auth;\nuse Waterhole\\Models\\Reaction;\nuse Waterhole\\Models\\ReactionSet;\nuse Waterhole\\Models\\ReactionType;\n\n/**\n * Methods to manage reactions on a model.\n */\ntrait Reactable\n{\n    /**\n     * Relationship with the reactions for this model.\n     */\n    public function reactions(): MorphMany\n    {\n        return $this->morphMany(Reaction::class, 'content');\n    }\n\n    /**\n     * Relationship with the reaction types for this model, including the\n     * count for each type and whether the current user has reacted.\n     */\n    public function reactionCounts(): HasManyThrough\n    {\n        $relation = $this->hasManyThrough(\n            ReactionType::class,\n            Reaction::class,\n            'content_id',\n            'id',\n            'id',\n            'reaction_type_id',\n        )\n            ->where('reactions.content_type', $this->getMorphClass())\n            ->select('reaction_types.*', 'reactions.content_type', 'reactions.content_id')\n            ->selectRaw('count(*) as count')\n            ->groupBy('reaction_types.id', 'reactions.content_type', 'reactions.content_id');\n\n        if ($user = Auth::user()) {\n            $userId = $relation->getGrammar()->wrap('reactions.user_id');\n            $relation->selectRaw(\"count(case when $userId = ? then 1 end) as user_reacted\", [\n                $user->id,\n            ]);\n        } else {\n            $relation->selectRaw('0 as user_reacted');\n        }\n\n        return $relation;\n    }\n\n    /**\n     * Get the reaction set that applies to this model.\n     */\n    abstract public function reactionSet(): ?ReactionSet;\n\n    /**\n     * Recalculate the score from the reactions.\n     */\n    public function recalculateScore(): static\n    {\n        $this->score = $this->reactions()\n            ->join('reaction_types', 'reaction_types.id', '=', 'reaction_type_id')\n            ->sum('reaction_types.score');\n\n        return $this;\n    }\n}\n"
  },
  {
    "path": "src/Models/Concerns/ReceivesPermissions.php",
    "content": "<?php\n\nnamespace Waterhole\\Models\\Concerns;\n\nuse Illuminate\\Database\\Eloquent\\Relations\\MorphMany;\nuse Illuminate\\Support\\Facades\\Cache;\nuse Waterhole\\Models\\Permission;\n\n/**\n * Methods to manage permissions that are granted to a model.\n *\n * This trait is distinct from `HasPermissions` in that it is for models that\n * take action (users and groups), rather than models that can be acted *upon*.\n *\n * @property-read \\Waterhole\\Models\\PermissionCollection $permissions\n */\ntrait ReceivesPermissions\n{\n    public static function bootReceivesPermissions(): void\n    {\n        // Ensure model deletion cascades to permission records.\n        static::deleted(function (self $model) {\n            $model->permissions()->delete();\n        });\n    }\n\n    /**\n     * Relationship with the permission records granted to this model.\n     */\n    public function permissions(): MorphMany\n    {\n        return $this->morphMany(Permission::class, 'recipient');\n    }\n\n    /**\n     * Save the permissions to the database.\n     */\n    public function savePermissions(?array $grid): void\n    {\n        $this->permissions()->delete();\n\n        Cache::forget('waterhole.permissions');\n        app()->forgetInstance('waterhole.permissions');\n\n        if (!$grid) {\n            return;\n        }\n\n        $this->permissions()->createMany(\n            collect($grid)->flatMap(function ($abilities, $scope) {\n                [$type, $id] = explode(':', $scope) + [null, null];\n\n                return collect($abilities)\n                    ->filter()\n                    ->map(\n                        fn($v, $ability) => [\n                            'scope_type' => $type,\n                            'scope_id' => $id,\n                            'ability' => $ability,\n                        ],\n                    )\n                    ->values();\n            }),\n        );\n    }\n}\n"
  },
  {
    "path": "src/Models/Concerns/Structurable.php",
    "content": "<?php\n\nnamespace Waterhole\\Models\\Concerns;\n\nuse Illuminate\\Database\\Eloquent\\Model;\nuse Illuminate\\Database\\Eloquent\\Relations\\MorphOne;\nuse Waterhole\\Models\\Structure;\n\n/**\n * Methods for models that can be a part of the forum \"structure\", like\n * channels, pages, links, and headings.\n *\n * Structurable models are associated with a \"node\" in the `structure` table,\n * which gives them a position within the structure.\n *\n * @property-read Structure $structure\n */\ntrait Structurable\n{\n    public static function bootStructurable(): void\n    {\n        // When a structurable model is created or deleted, create or delete\n        // its corresponding \"node\" within the structure table.\n        static::created(function (Model $model) {\n            $model->structure()->create([\n                'position' => ($pos = Structure::max('position')) ? $pos + 1 : 0,\n            ]);\n        });\n\n        static::deleted(function (Model $model) {\n            $model->structure()->delete();\n        });\n    }\n\n    /**\n     * Relationship with the node for this model within the forum structure.\n     */\n    public function structure(): MorphOne\n    {\n        return $this->morphOne(Structure::class, 'content');\n    }\n}\n"
  },
  {
    "path": "src/Models/Concerns/UsesFormatter.php",
    "content": "<?php\n\nnamespace Waterhole\\Models\\Concerns;\n\nuse Illuminate\\Support\\HtmlString;\nuse Waterhole\\Console\\ReformatCommand;\nuse Waterhole\\Formatter\\Context;\nuse Waterhole\\Formatter\\Formatter;\nuse Waterhole\\Models\\User;\nuse function Waterhole\\remove_formatting;\n\ntrait UsesFormatter\n{\n    /** @var Formatter[] */\n    protected static array $formatters = [];\n\n    private array $renderCache = [];\n\n    /**\n     * Render an attribute as HTML for the given user.\n     */\n    public function format(string $attribute, ?User $user = null): HtmlString|string\n    {\n        $key = $attribute . ':' . ($user->id ?? 0);\n        $value = $this->attributes[$attribute] ?? '';\n\n        if (!isset($this->renderCache[$key])) {\n            $this->renderCache[$key] = rescue(\n                fn() => $value && str_starts_with($value, '<')\n                    ? new HtmlString(\n                        static::$formatters[$attribute]->render($value, new Context($this, $user)),\n                    )\n                    : ($value ?:\n                    ''),\n                '',\n            );\n        }\n\n        return $this->renderCache[$key];\n    }\n\n    /**\n     * Set a formatter instance for this model.\n     */\n    public static function getFormatter(string $attribute): Formatter\n    {\n        return static::$formatters[$attribute];\n    }\n\n    /**\n     * Set a formatter instance for this model.\n     */\n    public static function setFormatter(string $attribute, Formatter $formatter): void\n    {\n        static::$formatters[$attribute] = $formatter;\n\n        ReformatCommand::addModelAttribute(static::class, $attribute);\n    }\n\n    public function getAttribute($key)\n    {\n        if (isset(static::$formatters[$key])) {\n            return !empty($this->attributes[$key])\n                ? static::$formatters[$key]->unparse($this->attributes[$key])\n                : null;\n        }\n\n        if (str_starts_with($key, 'parsed_')) {\n            $attribute = substr($key, 7);\n\n            if (isset(static::$formatters[$attribute])) {\n                return $this->attributes[$attribute];\n            }\n        }\n\n        if (str_ends_with($key, '_html')) {\n            $attribute = substr($key, 0, -5);\n\n            if (isset(static::$formatters[$attribute])) {\n                return $this->format($attribute);\n            }\n        }\n\n        if (str_ends_with($key, '_text')) {\n            $attribute = substr($key, 0, -5);\n\n            if (isset(static::$formatters[$attribute])) {\n                return remove_formatting($this->attributes[$attribute]);\n            }\n        }\n\n        return parent::getAttribute($key);\n    }\n\n    public function setAttribute($key, $value)\n    {\n        if (str_starts_with($key, 'parsed_')) {\n            $this->attributes[substr($key, 7)] = $value;\n        } elseif ($formatter = static::$formatters[$key] ?? null) {\n            $this->attributes[$key] = $value ? $formatter->parse($value, new Context($this)) : '';\n        } else {\n            return parent::setAttribute($key, $value);\n        }\n    }\n}\n"
  },
  {
    "path": "src/Models/Concerns/ValidatesData.php",
    "content": "<?php\n\nnamespace Waterhole\\Models\\Concerns;\n\n/**\n * Methods for models that have validation rules.\n */\ntrait ValidatesData\n{\n    /**\n     * Validate an array of data against the model's validation rules.\n     */\n    public static function validate(array $data, ?self $instance = null): array\n    {\n        $validator = validator(\n            $data,\n            static::rules($instance),\n            static::messages($instance),\n            static::customAttributes($instance),\n        );\n\n        // foreach (Validation::values() as $callback) {\n        //     $callback(static::class, $validator, $instance);\n        // }\n\n        return $validator->validate();\n    }\n\n    /**\n     * The model's validation rules.\n     */\n    protected static function rules(?self $instance = null): array\n    {\n        return [];\n    }\n\n    /**\n     * The model's validation messages.\n     */\n    protected static function messages(?self $instance = null): array\n    {\n        return [];\n    }\n\n    /**\n     * The model's validation custom attribute translations.\n     */\n    protected static function customAttributes(?self $instance = null): array\n    {\n        return [];\n    }\n}\n"
  },
  {
    "path": "src/Models/Flag.php",
    "content": "<?php\n\nnamespace Waterhole\\Models;\n\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Relations\\BelongsTo;\nuse Illuminate\\Database\\Eloquent\\Relations\\MorphTo;\nuse Illuminate\\Support\\Facades\\Auth;\nuse Illuminate\\Support\\Facades\\Notification;\nuse Waterhole\\Events\\FlagReceived;\nuse Waterhole\\Notifications\\NewFlag;\nuse Waterhole\\Models\\Support\\MorphTypeCache;\n\n/**\n * @property int $id\n * @property string $subject_type\n * @property int $subject_id\n * @property string $reason\n * @property null|int $created_by\n * @property null|int $resolved_by\n * @property null|\\Carbon\\Carbon $resolved_at\n * @property null|string $note\n * @property \\Carbon\\Carbon $created_at\n * @property \\Carbon\\Carbon $updated_at\n * @property-read Post|Comment $subject\n * @property-read null|User $createdBy\n * @property-read null|User $resolvedBy\n */\nclass Flag extends Model\n{\n    protected $casts = [\n        'resolved_at' => 'datetime',\n    ];\n\n    protected static function booting(): void\n    {\n        static::addGlobalScope('subjectPresent', function ($query) {\n            $query->whereHasMorph(\n                'subject',\n                MorphTypeCache::forTrait(Concerns\\Flaggable::class),\n                fn($query) => $query->withoutTrashed(),\n            );\n        });\n\n        static::addGlobalScope('visible', function ($query) {\n            $query->visible(Auth::user());\n        });\n\n        static::created(function (self $flag) {\n            if (empty(($moderators = $flag->subject->channel->usersWithAbility('moderate')))) {\n                return;\n            }\n\n            $moderators->each(fn(User $user) => event(new FlagReceived($user)));\n\n            Notification::send($moderators, new NewFlag($flag));\n        });\n    }\n\n    public function subject(): MorphTo\n    {\n        return $this->morphTo();\n    }\n\n    public function createdBy(): BelongsTo\n    {\n        return $this->belongsTo(User::class, 'created_by');\n    }\n\n    public function resolvedBy(): BelongsTo\n    {\n        return $this->belongsTo(User::class, 'resolved_by');\n    }\n\n    public function scopePending(Builder $query): void\n    {\n        $query->whereNull('resolved_at');\n    }\n\n    public function scopeVisible(Builder $query, ?User $user): void\n    {\n        // Remove the default visible global scope which scopes for the\n        // currently authenticated user.\n        $query->withoutGlobalScope('visible');\n\n        if (is_null($channelIds = Channel::allPermitted($user, 'moderate'))) {\n            return;\n        }\n\n        if (empty($channelIds)) {\n            $query->whereRaw('1 = 0');\n            return;\n        }\n\n        $query->whereHasMorph(\n            'subject',\n            '*',\n            fn($query) => $query->whereHas('channel', fn($query) => $query->whereKey($channelIds)),\n        );\n    }\n}\n"
  },
  {
    "path": "src/Models/Group.php",
    "content": "<?php\n\nnamespace Waterhole\\Models;\n\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Casts\\Attribute;\nuse Illuminate\\Database\\Eloquent\\Relations\\BelongsToMany;\nuse Waterhole\\Models\\Concerns\\HasIcon;\nuse Waterhole\\Models\\Concerns\\ReceivesPermissions;\nuse Waterhole\\Models\\Concerns\\ValidatesData;\n\n/**\n * @property int $id\n * @property string $name\n * @property bool $is_public\n * @property null|string $color\n * @property string $edit_url\n * @property-read \\Illuminate\\Database\\Eloquent\\Collection $users\n */\nclass Group extends Model\n{\n    use HasIcon;\n    use ReceivesPermissions;\n    use ValidatesData;\n\n    public const GUEST_ID = 1;\n    public const MEMBER_ID = 2;\n    public const ADMIN_ID = 3;\n\n    public $timestamps = false;\n\n    protected $casts = [\n        'is_public' => 'bool',\n        'auto_assign' => 'bool',\n        'rules' => 'array',\n    ];\n\n    private static array $instances = [];\n\n    /**\n     * Relationship with the group's users.\n     */\n    public function users(): BelongsToMany\n    {\n        return $this->belongsToMany(User::class);\n    }\n\n    /**\n     * Whether this group is the Guest group.\n     */\n    public function isGuest(): bool\n    {\n        return $this->getKey() === static::GUEST_ID;\n    }\n\n    /**\n     * Whether this group is the Member group.\n     */\n    public function isMember(): bool\n    {\n        return $this->getKey() === static::MEMBER_ID;\n    }\n\n    /**\n     * Whether this group is the Admin group.\n     */\n    public function isAdmin(): bool\n    {\n        return $this->getKey() === static::ADMIN_ID;\n    }\n\n    /**\n     * Whether this group is a custom (user-defined) group.\n     */\n    public function isCustom(): bool\n    {\n        return !$this->isGuest() && !$this->isMember() && !$this->isAdmin();\n    }\n\n    /**\n     * Get an instance of the Guest group.\n     */\n    public static function guest(): static\n    {\n        return static::$instances[static::GUEST_ID] ??= static::findOrFail(static::GUEST_ID);\n    }\n\n    /**\n     * Get an instance of the Member group.\n     */\n    public static function member(): static\n    {\n        return static::$instances[static::MEMBER_ID] ??= static::findOrFail(static::MEMBER_ID);\n    }\n\n    /**\n     * Get an instance of the Admin group.\n     */\n    public static function admin(): static\n    {\n        return static::$instances[static::ADMIN_ID] ??= static::findOrFail(static::ADMIN_ID);\n    }\n\n    /**\n     * Get only custom (user-defined) groups.\n     */\n    public function scopeCustom(Builder $query)\n    {\n        $query->whereKeyNot([static::GUEST_ID, static::MEMBER_ID, static::ADMIN_ID]);\n    }\n\n    /**\n     * Get only groups that can be selected for users (admin + custom groups).\n     */\n    public function scopeSelectable(Builder $query)\n    {\n        $query->whereKeyNot([static::GUEST_ID, static::MEMBER_ID]);\n    }\n\n    protected function editUrl(): Attribute\n    {\n        return Attribute::make(\n            get: fn() => route('waterhole.cp.groups.edit', ['group' => $this]),\n        )->shouldCache();\n    }\n\n    protected function usersUrl(): Attribute\n    {\n        return Attribute::make(\n            get: fn() => route('waterhole.cp.users.index', [\n                'q' => $this->isMember()\n                    ? null\n                    : 'group:' .\n                        (str_contains($this->name, ' ') ? '\"' . $this->name . '\"' : $this->name),\n            ]),\n        )->shouldCache();\n    }\n}\n"
  },
  {
    "path": "src/Models/Model.php",
    "content": "<?php\n\nnamespace Waterhole\\Models;\n\nuse Illuminate\\Database\\Eloquent\\Model as Eloquent;\n\n/**\n * Base class for a Waterhole Eloquent model.\n *\n * Waterhole Models are unguarded by default. The preferred convention to\n * keep the application secure is to ensure all data is validated (often via\n * the `ValidatesData` trait) prior to filling.\n */\nabstract class Model extends Eloquent\n{\n    protected static $unguarded = true;\n\n    protected static string $connectionName;\n\n    public function getConnectionName()\n    {\n        return static::$connectionName ??=\n            config('waterhole.system.database') ?: config('database.default');\n    }\n}\n"
  },
  {
    "path": "src/Models/Notification.php",
    "content": "<?php\n\nnamespace Waterhole\\Models;\n\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Casts\\Attribute;\nuse Illuminate\\Database\\Eloquent\\Relations\\BelongsTo;\nuse Illuminate\\Database\\Eloquent\\Relations\\MorphTo;\nuse Illuminate\\Notifications\\DatabaseNotification;\nuse Illuminate\\Support\\Facades\\Auth;\nuse Staudenmeir\\LaravelCte\\Eloquent\\QueriesExpressions;\nuse Waterhole\\Notifications\\Notification as NotificationTemplate;\n\n/**\n * @property string $id\n * @property string $type\n * @property string $notifiable_type\n * @property int $notifiable_id\n * @property array $data\n * @property null|int $sender_id\n * @property null|string $group_type\n * @property null|int $group_id\n * @property null|string $content_type\n * @property null|int $content_id\n * @property null|\\Carbon\\Carbon $created_at\n * @property null|\\Carbon\\Carbon $updated_at\n * @property null|\\Carbon\\Carbon $read_at\n * @property-read null|NotificationTemplate $template\n * @property-read null|User $sender\n * @property-read null|Model $group\n * @property-read null|Model $content\n */\nclass Notification extends DatabaseNotification\n{\n    use QueriesExpressions;\n\n    public function getConnectionName()\n    {\n        return config('waterhole.system.database');\n    }\n\n    /**\n     * Relationship with the user whose action caused the notification to be\n     * sent.\n     */\n    public function sender(): BelongsTo\n    {\n        return $this->belongsTo(User::class);\n    }\n\n    /**\n     * Relationship with the notification's group.\n     */\n    public function group(): MorphTo\n    {\n        return $this->morphTo();\n    }\n\n    /**\n     * Relationship with the notification's content.\n     */\n    public function content(): MorphTo\n    {\n        return $this->morphTo();\n    }\n\n    /**\n     * Query notifications that have the same type and group as a notification.\n     */\n    public function scopeGroupedWith(Builder $query, Notification $notification): void\n    {\n        if ($notification->group_type && $notification->group_id) {\n            $query->where($notification->only(['type', 'group_type', 'group_id']));\n        } else {\n            $query->whereKey($notification->getKey());\n        }\n    }\n\n    /**\n     * Only allow users to view their own notifications.\n     */\n    public function resolveRouteBinding($value, $field = null)\n    {\n        return $this->whereKey($value)->whereMorphedTo('notifiable', Auth::user())->firstOrFail();\n    }\n\n    protected function template(): Attribute\n    {\n        return Attribute::make(\n            get: fn() => $this->type::fromNotificationModel($this),\n        )->shouldCache();\n    }\n\n    protected function url(): Attribute\n    {\n        return Attribute::make(\n            get: fn() => route('waterhole.notifications.show', ['notification' => $this]),\n        )->shouldCache();\n    }\n}\n"
  },
  {
    "path": "src/Models/Page.php",
    "content": "<?php\n\nnamespace Waterhole\\Models;\n\nuse Illuminate\\Database\\Eloquent\\Casts\\Attribute;\nuse Illuminate\\Database\\Eloquent\\Factories\\HasFactory;\nuse Illuminate\\Support\\Str;\nuse Waterhole\\Database\\Factories\\PageFactory;\nuse Waterhole\\Models\\Concerns\\HasBody;\nuse Waterhole\\Models\\Concerns\\HasIcon;\nuse Waterhole\\Models\\Concerns\\HasPermissions;\nuse Waterhole\\Models\\Concerns\\Structurable;\n\n/**\n * @property int $id\n * @property string $name\n * @property string $slug\n * @property string $url\n * @property string $edit_url\n */\nclass Page extends Model\n{\n    use HasFactory;\n    use HasBody;\n    use HasIcon;\n    use HasPermissions;\n    use Structurable;\n\n    public $timestamps = false;\n\n    protected static function booting(): void\n    {\n        static::creating(function (self $model) {\n            $model->slug ??= Str::slug($model->name);\n        });\n    }\n\n    protected static function newFactory(): PageFactory\n    {\n        return PageFactory::new();\n    }\n\n    protected function url(): Attribute\n    {\n        return Attribute::make(\n            get: fn() => route('waterhole.page', ['page' => $this]),\n        )->shouldCache();\n    }\n\n    protected function editUrl(): Attribute\n    {\n        return Attribute::make(\n            get: fn() => route('waterhole.cp.structure.pages.edit', ['page' => $this]),\n        )->shouldCache();\n    }\n}\n"
  },
  {
    "path": "src/Models/Permission.php",
    "content": "<?php\n\nnamespace Waterhole\\Models;\n\nuse Illuminate\\Database\\Eloquent\\Relations\\MorphTo;\nuse Illuminate\\Support\\Facades\\Cache;\n\n/**\n * @property int $id\n * @property string $scope_type\n * @property int $scope_id\n * @property string $recipient_type\n * @property int $recipient_id\n * @property string $ability\n * @property null|Model $scope\n * @property Model $recipient\n */\nclass Permission extends Model\n{\n    public $timestamps = false;\n\n    protected static function booting(): void\n    {\n        $flushCache = function () {\n            Cache::forget('waterhole.permissions');\n            app()->forgetInstance('waterhole.permissions');\n        };\n\n        static::saved($flushCache);\n        static::deleted($flushCache);\n    }\n\n    /**\n     * Relationship with the model that this permission is for.\n     */\n    public function scope(): MorphTo\n    {\n        return $this->morphTo();\n    }\n\n    /**\n     * Relationship with the user or group that receives this permission.\n     */\n    public function recipient(): MorphTo\n    {\n        return $this->morphTo();\n    }\n\n    public function newCollection(array $models = []): PermissionCollection\n    {\n        return new PermissionCollection($models);\n    }\n}\n"
  },
  {
    "path": "src/Models/PermissionCollection.php",
    "content": "<?php\n\nnamespace Waterhole\\Models;\n\nuse Closure;\nuse Illuminate\\Database\\Eloquent\\Collection;\n\nclass PermissionCollection extends Collection\n{\n    private array $resultCache = [];\n    private array $idsCache = [];\n\n    /**\n     * Get the permission records pertaining to a specific model.\n     */\n    public function scope(Model|string $model): static\n    {\n        if (is_string($model)) {\n            if (class_exists($model)) {\n                $model = (new $model())->getMorphClass();\n            }\n\n            return $this->where('scope_type', $model);\n        }\n\n        return $this->where('scope_type', $model->getMorphClass())->where(\n            'scope_id',\n            $model->getKey(),\n        );\n    }\n\n    public function can(User|Group|null $recipient, string $ability, Model|string $scope): bool\n    {\n        // Admins are granted every permission.\n        if ($recipient instanceof User && $recipient->isAdmin()) {\n            return true;\n        }\n\n        $cacheKey = $this->cacheKey($recipient, $ability, $scope);\n\n        return $this->resultCache[$cacheKey] ??= $this->some(\n            $this->callback($recipient, $ability, $scope),\n        );\n    }\n\n    public function ids(User|Group|null $recipient, string $ability, string $scope): array\n    {\n        $cacheKey = $this->cacheKey($recipient, $ability, $scope);\n\n        return $this->idsCache[$cacheKey] ??= $this->filter(\n            $this->callback($recipient, $ability, $scope),\n        )\n            ->pluck('scope_id')\n            ->all();\n    }\n\n    private function callback(\n        User|Group|null $recipient,\n        string $ability,\n        Model|string $scope,\n    ): Closure {\n        $recipients = [[(new Group())->getMorphClass(), Group::GUEST_ID]];\n\n        if (\n            $recipient &&\n            ($recipient instanceof User || $recipient->getKey() !== Group::GUEST_ID)\n        ) {\n            $recipients[] = [(new Group())->getMorphClass(), Group::MEMBER_ID];\n            $recipients[] = [$recipient->getMorphClass(), $recipient->getKey()];\n\n            if ($recipient instanceof User) {\n                $recipients = array_merge(\n                    $recipients,\n                    $recipient->groups\n                        ->map(fn($group) => [$group->getMorphClass(), $group->getKey()])\n                        ->all(),\n                );\n            }\n        }\n\n        $scopeType = is_string($scope) ? (new $scope())->getMorphClass() : $scope->getMorphClass();\n        $scopeId = is_string($scope) ? null : $scope->getKey();\n\n        // Use loose (==) comparison for IDs below, as in some environments\n        // they can be returned as strings instead of ints.\n        return function ($item) use ($ability, $recipients, $scopeType, $scopeId) {\n            if (\n                $item['ability'] !== $ability ||\n                $item['scope_type'] !== $scopeType ||\n                ($scopeId && $item['scope_id'] != $scopeId)\n            ) {\n                return false;\n            }\n\n            foreach ($recipients as [$recipientType, $recipientId]) {\n                if (\n                    $item['recipient_type'] === $recipientType &&\n                    $item['recipient_id'] == $recipientId\n                ) {\n                    return true;\n                }\n            }\n\n            return false;\n        };\n    }\n\n    private function cacheKey(\n        User|Group|null $recipient,\n        string $ability,\n        Model|string $scope,\n    ): string {\n        $recipientType = $recipient?->getMorphClass();\n        $recipientId = $recipient?->getKey();\n\n        $scopeType = is_string($scope) ? (new $scope())->getMorphClass() : $scope->getMorphClass();\n        $scopeId = is_string($scope) ? null : $scope->getKey();\n\n        return \"$recipientType|$recipientId|$ability|$scopeType|$scopeId\";\n    }\n}\n"
  },
  {
    "path": "src/Models/Post.php",
    "content": "<?php\n\nnamespace Waterhole\\Models;\n\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Casts\\Attribute;\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Database\\Eloquent\\Factories\\HasFactory;\nuse Illuminate\\Database\\Eloquent\\Relations\\BelongsTo;\nuse Illuminate\\Database\\Eloquent\\Relations\\BelongsToMany;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasMany;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasOne;\nuse Illuminate\\Support\\Facades\\Auth;\nuse Illuminate\\Support\\Facades\\Notification;\nuse Waterhole\\Database\\Factories\\PostFactory;\nuse Waterhole\\Events\\NewPost;\nuse Waterhole\\Extend;\nuse Waterhole\\Models\\Concerns\\Approvable;\nuse Waterhole\\Models\\Concerns\\Deletable;\nuse Waterhole\\Models\\Concerns\\Flaggable;\nuse Waterhole\\Models\\Concerns\\Followable;\nuse Waterhole\\Models\\Concerns\\HasBody;\nuse Waterhole\\Models\\Concerns\\HasUserState;\nuse Waterhole\\Models\\Concerns\\NotificationContent;\nuse Waterhole\\Models\\Concerns\\Reactable;\nuse Waterhole\\Notifications\\Mention;\nuse Waterhole\\Notifications\\NewPost as NewPostNotification;\nuse Waterhole\\View\\Components;\nuse Waterhole\\View\\TurboStream;\n\n/**\n * @property int $id\n * @property int $channel_id\n * @property null|int $user_id\n * @property null|string $title\n * @property null|string $slug\n * @property null|\\Carbon\\Carbon $created_at\n * @property null|\\Carbon\\Carbon $edited_at\n * @property null|\\Carbon\\Carbon $last_activity_at\n * @property int $comment_count\n * @property int $score\n * @property bool $is_locked\n * @property null|int $answer_id\n * @property bool $is_pinned\n * @property-read Channel $channel\n * @property-read null|User $user\n * @property-read null|User $deletedBy\n * @property-read \\Illuminate\\Database\\Eloquent\\Collection $comments\n * @property-read \\Illuminate\\Database\\Eloquent\\Collection $unreadComments\n * @property-read \\Illuminate\\Database\\Eloquent\\Collection $tags\n * @property-read null|Comment $lastComment\n * @property-read null|PostUser $userState\n * @property-read null|Comment $answer\n * @property-read string $url\n * @property-read string $edit_url\n * @property-read string $unread_url\n * @property-read null|int $unread_comments_count\n */\nclass Post extends Model\n{\n    use HasFactory;\n    use Followable;\n    use HasBody;\n    use Reactable;\n    use HasUserState;\n    use NotificationContent;\n    use Deletable;\n    use Approvable;\n    use Flaggable;\n\n    public const UPDATED_AT = null;\n\n    protected $casts = [\n        'edited_at' => 'datetime',\n        'last_activity_at' => 'datetime',\n        'is_locked' => 'boolean',\n        'is_pinned' => 'boolean',\n    ];\n\n    public static function booting(): void\n    {\n        static::addGlobalScope('visible', function ($query) {\n            $user = Auth::user();\n\n            if (app()->runningInConsole() && !app()->runningUnitTests() && !$user) {\n                return;\n            }\n\n            $query->visible($user);\n        });\n\n        static::creating(function (Post $post) {\n            $post->last_activity_at ??= now();\n        });\n\n        // Delete comments one at a time to trigger event listeners.\n        static::forceDeleting(function (self $post) {\n            $post->comments()->lazy()->each->delete();\n        });\n\n        static::saving(function (self $post) {\n            $sign = $post->score <=> 0;\n            $seconds = ($post->created_at ?: now())->unix() - 1134028003;\n            $post->hotness = round(\n                $sign * log10(max(abs($post->score ?: 0), 1)) + $seconds / 45000,\n                10,\n            );\n        });\n    }\n\n    protected static function booted(): void\n    {\n        // Register the listener to deliver created events after the HasBody\n        // trait has been booted and has registered its listeners, to ensure\n        // the body is processed before delivering @mention notifications etc.\n        static::created(function (self $post) {\n            $post->deliverCreatedEvents();\n        });\n    }\n\n    protected static function newFactory(): PostFactory\n    {\n        return PostFactory::new();\n    }\n\n    protected function deliverCreatedEvents(): void\n    {\n        if (!$this->is_approved) {\n            return;\n        }\n\n        broadcast(new NewPost($this))->toOthers();\n\n        // When a new post is created, send notifications to mentioned users.\n        $users = $this->mentions\n            ->except($this->user_id)\n            ->filter(fn(User $user) => Post::visible($user)->whereKey($this->id)->exists());\n\n        $this->usersWereMentioned($users);\n\n        Notification::send($users, new Mention($this));\n\n        // Send out a \"new post\" notification to all followers of this post's\n        // channel, except for the user who created the post.\n        Notification::send(\n            $this->channel->followedBy->except($this->user_id),\n            new NewPostNotification($this),\n        );\n    }\n\n    /**\n     * Update the user state for any users mentioned in this post.\n     */\n    public function usersWereMentioned(Collection $users): void\n    {\n        $postUserRows = $users\n            ->map(\n                fn(User $user) => [\n                    'post_id' => $this->getKey(),\n                    'user_id' => $user->getKey(),\n                    'mentioned_at' => now(),\n                ],\n            )\n            ->all();\n\n        PostUser::upsert($postUserRows, ['post_id', 'user_id'], ['mentioned_at']);\n    }\n\n    /**\n     * Query posts that are unread for the current user.\n     */\n    public function scopeUnread(Builder $query)\n    {\n        $query->whereDoesntHave('userState', function ($query) {\n            $query->whereColumn('last_read_at', '>=', 'last_activity_at');\n        });\n    }\n\n    /**\n     * Scope to select count of comments that are unread.\n     */\n    public function scopeWithUnreadCommentsCount(Builder $query): void\n    {\n        $query\n            ->leftJoinRelation('userState')\n            ->selectSub(\n                Comment::query()\n                    ->withoutGlobalScope('visible')\n                    ->selectRaw('COUNT(*)')\n                    ->whereColumn('comments.post_id', 'posts.id')\n                    ->whereColumn('comments.created_at', '>', 'last_read_at'),\n                'unread_comments_count',\n            );\n    }\n\n    /**\n     * Relationship with the post's channel.\n     */\n    public function channel(): BelongsTo\n    {\n        return $this->belongsTo(Channel::class)->withoutGlobalScope('visible');\n    }\n\n    /**\n     * Relationship with the post's author.\n     */\n    public function user(): BelongsTo\n    {\n        return $this->belongsTo(User::class);\n    }\n\n    /**\n     * Relationship with the post's comments.\n     */\n    public function comments(): HasMany\n    {\n        return $this->hasMany(Comment::class);\n    }\n\n    /**\n     * Relationship with the post's most recent comment.\n     */\n    public function lastComment(): HasOne\n    {\n        return $this->hasOne(Comment::class)->latestOfMany();\n    }\n\n    /**\n     * Relationship with the post's answer comment.\n     */\n    public function answer(): BelongsTo\n    {\n        return $this->belongsTo(Comment::class, 'answer_id');\n    }\n\n    /**\n     * Relationship with the post's tags.\n     */\n    public function tags(): BelongsToMany\n    {\n        return $this->belongsToMany(Tag::class);\n    }\n\n    public function scopeVisible(Builder $query, ?User $user): void\n    {\n        $query->withoutGlobalScope('visible');\n\n        $moderationScope = fn(Builder $query, array $channelIds) => $query->orWhereIn(\n            'channel_id',\n            $channelIds,\n        );\n\n        $this->applyApprovalVisibility($query, $user, $moderationScope);\n\n        $this->applyDeletionVisibility($query, $user, $moderationScope);\n\n        foreach (resolve(Extend\\Query\\PostVisibilityScopes::class)->values() as $scope) {\n            $query->where(fn($inner) => $scope($inner, $user));\n        }\n    }\n\n    /**\n     * Generate a URL for a particular comment index in this post.\n     */\n    public function urlAtIndex(int $index = 0): string\n    {\n        $params = ['post' => $this];\n\n        if (($page = floor($index / (new Comment())->getPerPage()) + 1) > 1) {\n            $params['page'] = $page;\n        }\n\n        return route('waterhole.posts.show', $params);\n    }\n\n    /**\n     * Mark this post as having been edited just now.\n     */\n    public function markAsEdited(): static\n    {\n        $this->edited_at = now();\n\n        return $this;\n    }\n\n    /**\n     * Refresh the metadata about this post's comments.\n     */\n    public function refreshCommentMetadata(): static\n    {\n        $publicComments = $this->comments()->visible(null);\n\n        $this->last_activity_at =\n            (clone $publicComments)->latest()->value('created_at') ?: $this->created_at;\n\n        $this->comment_count = (clone $publicComments)->count();\n\n        return $this;\n    }\n\n    /**\n     * Determine whether this post contains any new activity for the current user.\n     */\n    public function isUnread(): bool\n    {\n        return $this->userState && $this->last_activity_at > $this->userState->last_read_at;\n    }\n\n    /**\n     * Determine whether this post has been fully read by the current user.\n     */\n    public function isRead(): bool\n    {\n        return $this->userState && !$this->isUnread();\n    }\n\n    /**\n     * Determine whether this post has never before been seen by the current user.\n     */\n    public function isNew(): bool\n    {\n        return $this->userState && !$this->userState->last_read_at;\n    }\n\n    /**\n     * Get the Turbo Streams that should be sent when this post is updated.\n     */\n    public function streamUpdated(): array\n    {\n        $streams = [\n            TurboStream::replace(new Components\\PostListItem($this)),\n            TurboStream::replace(new Components\\PostCard($this)),\n            TurboStream::replace(new Components\\PostFull($this)),\n            TurboStream::replace(new Components\\PostSidebar($this)),\n        ];\n\n        if ($this->is_pinned) {\n            $streams[] = TurboStream::replace(new Components\\PinnedPost($this));\n        }\n\n        return $streams;\n    }\n\n    /**\n     * Get the Turbo Streams that should be sent when this post is removed.\n     */\n    public function streamRemoved(): array\n    {\n        $streams = [\n            TurboStream::remove(new Components\\PostListItem($this)),\n            TurboStream::remove(new Components\\PostCard($this)),\n        ];\n\n        if ($this->is_pinned) {\n            $streams[] = TurboStream::remove(new Components\\PinnedPost($this));\n        }\n\n        return $streams;\n    }\n\n    public function getPerPage(): int\n    {\n        return config('waterhole.forum.posts_per_page', $this->perPage);\n    }\n\n    public function getRouteKey(): string\n    {\n        return $this->id . ($this->slug ? '-' . $this->slug : '');\n    }\n\n    public function resolveRouteBinding($value, $field = null)\n    {\n        return $this->select('*')->whereKey(explode('-', $value)[0])->firstOrFail();\n    }\n\n    protected function url(): Attribute\n    {\n        return Attribute::make(\n            get: fn() => route('waterhole.posts.show', ['post' => $this]),\n        )->shouldCache();\n    }\n\n    protected function editUrl(): Attribute\n    {\n        return Attribute::make(\n            get: fn() => route('waterhole.posts.edit', ['post' => $this]),\n        )->shouldCache();\n    }\n\n    protected function unreadUrl(): Attribute\n    {\n        return Attribute::make(\n            get: function () {\n                if ($this->isNew()) {\n                    return $this->url;\n                }\n\n                $fragment = $this->unread_comments_count ? '#unread' : '#bottom';\n\n                return $this->urlAtIndex($this->comment_count - $this->unread_comments_count - 1) .\n                    $fragment;\n            },\n        )->shouldCache();\n    }\n\n    public function reactionsUrl(ReactionType $reactionType): string\n    {\n        return route('waterhole.posts.reactions', [\n            'post' => $this,\n            'reactionType' => $reactionType,\n        ]);\n    }\n\n    public function reactionSet(): ?ReactionSet\n    {\n        return $this->channel->postsReactionSet;\n    }\n\n    public function canModerate(?User $user): bool\n    {\n        return (bool) $user?->can('waterhole.post.moderate', $this);\n    }\n}\n"
  },
  {
    "path": "src/Models/PostUser.php",
    "content": "<?php\n\nnamespace Waterhole\\Models;\n\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Relations\\BelongsTo;\n\n/**\n * @property int $post_id\n * @property int $user_id\n * @property null|\\Carbon\\Carbon $last_read_at\n * @property null|string $notifications\n * @property null|\\Carbon\\Carbon $followed_at\n * @property null|\\Carbon\\Carbon $mentioned_at\n * @property-read Post $post\n * @property-read User $user\n */\nclass PostUser extends Model\n{\n    public $timestamps = false;\n\n    public $incrementing = false;\n\n    protected $table = 'post_user';\n\n    protected $casts = [\n        'last_read_at' => 'datetime',\n        'followed_at' => 'datetime',\n        'mentioned_at' => 'datetime',\n    ];\n\n    /**\n     * Mark this post as having been read by the user.\n     */\n    public function read(): static\n    {\n        $this->last_read_at = now();\n\n        return $this;\n    }\n\n    public function post(): BelongsTo\n    {\n        return $this->belongsTo(Post::class);\n    }\n\n    public function user(): BelongsTo\n    {\n        return $this->belongsTo(User::class);\n    }\n\n    public function getKey(): string\n    {\n        return $this->post_id . '-' . $this->user_id;\n    }\n\n    protected function setKeysForSaveQuery($query): Builder\n    {\n        return $query->where('post_id', $this->post_id)->where('user_id', $this->user_id);\n    }\n}\n"
  },
  {
    "path": "src/Models/Reaction.php",
    "content": "<?php\n\nnamespace Waterhole\\Models;\n\nuse Illuminate\\Database\\Eloquent\\Relations\\BelongsTo;\nuse Illuminate\\Database\\Eloquent\\Relations\\MorphTo;\n\n/**\n * @property int $id\n * @property-read ReactionType $reactionType\n * @property-read User $user\n * @property-read Model $content\n */\nclass Reaction extends Model\n{\n    public function user(): BelongsTo\n    {\n        return $this->belongsTo(User::class);\n    }\n\n    public function reactionType(): BelongsTo\n    {\n        return $this->belongsTo(ReactionType::class);\n    }\n\n    public function content(): MorphTo\n    {\n        return $this->morphTo();\n    }\n}\n"
  },
  {
    "path": "src/Models/ReactionSet.php",
    "content": "<?php\n\nnamespace Waterhole\\Models;\n\nuse Illuminate\\Database\\Eloquent\\Casts\\Attribute;\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasMany;\n\n/**\n * @property int $id\n * @property string $name\n * @property bool $is_default_posts\n * @property bool $is_default_comments\n * @property bool $allow_multiple\n * @property \\Carbon\\Carbon $created_at\n * @property \\Carbon\\Carbon $updated_at\n * @property-read \\Illuminate\\Database\\Eloquent\\Collection $reactionTypes\n * @property-read string $edit_url\n */\nclass ReactionSet extends Model\n{\n    private static Collection $defaults;\n\n    protected $casts = [\n        'is_default_posts' => 'bool',\n        'is_default_comments' => 'bool',\n        'allow_multiple' => 'bool',\n        'allow_custom' => 'bool',\n    ];\n\n    public function reactionTypes(): HasMany\n    {\n        return $this->hasMany(ReactionType::class)->orderBy('position');\n    }\n\n    protected function editUrl(): Attribute\n    {\n        return Attribute::make(\n            get: fn() => route('waterhole.cp.reaction-sets.edit', ['reactionSet' => $this]),\n        )->shouldCache();\n    }\n\n    private static function defaults(): Collection\n    {\n        return static::$defaults ??= static::query()\n            ->where('is_default_posts', true)\n            ->orWhere('is_default_comments', true)\n            ->get();\n    }\n\n    public static function defaultPosts(): ?static\n    {\n        return static::defaults()->firstWhere('is_default_posts', true);\n    }\n\n    public static function defaultComments(): ?static\n    {\n        return static::defaults()->firstWhere('is_default_comments', true);\n    }\n}\n"
  },
  {
    "path": "src/Models/ReactionType.php",
    "content": "<?php\n\nnamespace Waterhole\\Models;\n\nuse Illuminate\\Database\\Eloquent\\Casts\\Attribute;\nuse Illuminate\\Database\\Eloquent\\Relations\\BelongsTo;\nuse Waterhole\\Models\\Concerns\\HasIcon;\n\n/**\n * @property int $id\n * @property int $reaction_set_id\n * @property string $name\n * @property int $score\n * @property int $position\n * @property \\Carbon\\Carbon $created_at\n * @property \\Carbon\\Carbon $updated_at\n * @property-read ReactionSet $reactionSet\n * @property-read string $edit_url\n */\nclass ReactionType extends Model\n{\n    use HasIcon;\n\n    public static function booting(): void\n    {\n        static::creating(function (ReactionType $reactionType) {\n            $reactionType->position =\n                $reactionType->reactionSet->reactionTypes()->max('position') + 1;\n        });\n    }\n\n    public function reactionSet(): BelongsTo\n    {\n        return $this->belongsTo(ReactionSet::class);\n    }\n\n    protected function editUrl(): Attribute\n    {\n        return Attribute::make(\n            get: fn() => route('waterhole.cp.reaction-sets.reaction-types.edit', [\n                'reactionSet' => $this->reactionSet,\n                'reactionType' => $this,\n            ]),\n        )->shouldCache();\n    }\n}\n"
  },
  {
    "path": "src/Models/Structure.php",
    "content": "<?php\n\nnamespace Waterhole\\Models;\n\nuse Illuminate\\Database\\Eloquent\\Relations\\MorphTo;\nuse Waterhole\\Models\\Support\\MorphTypeCache;\n\n/**\n * @property int $id\n * @property int $position\n * @property string $content_type\n * @property int $content_id\n * @property bool $is_listed\n * @property-read Model $content\n */\nclass Structure extends Model\n{\n    protected $table = 'structure';\n\n    public $timestamps = false;\n\n    protected $casts = [\n        'is_listed' => 'bool',\n    ];\n\n    protected static function booting()\n    {\n        static::addGlobalScope('hasVisibleContent', function ($query) {\n            $query->whereHasMorph(\n                'content',\n                MorphTypeCache::forTrait(Concerns\\Structurable::class),\n            );\n        });\n    }\n\n    public function content(): MorphTo\n    {\n        return $this->morphTo();\n    }\n}\n"
  },
  {
    "path": "src/Models/StructureHeading.php",
    "content": "<?php\n\nnamespace Waterhole\\Models;\n\nuse Illuminate\\Database\\Eloquent\\Casts\\Attribute;\nuse Waterhole\\Models\\Concerns\\Structurable;\nuse Waterhole\\Models\\Concerns\\ValidatesData;\n\n/**\n * @property int $id\n * @property string $name\n * @property-read string $edit_url\n */\nclass StructureHeading extends Model\n{\n    use Structurable;\n    use ValidatesData;\n\n    public $timestamps = false;\n\n    protected function editUrl(): Attribute\n    {\n        return Attribute::make(\n            get: fn() => route('waterhole.cp.structure.headings.edit', ['heading' => $this]),\n        )->shouldCache();\n    }\n\n    public static function rules(?StructureHeading $instance = null): array\n    {\n        return [\n            'name' => ['nullable', 'string', 'max:255'],\n        ];\n    }\n}\n"
  },
  {
    "path": "src/Models/StructureLink.php",
    "content": "<?php\n\nnamespace Waterhole\\Models;\n\nuse Illuminate\\Database\\Eloquent\\Casts\\Attribute;\nuse Waterhole\\Models\\Concerns\\HasIcon;\nuse Waterhole\\Models\\Concerns\\HasPermissions;\nuse Waterhole\\Models\\Concerns\\Structurable;\n\n/**\n * @property int $id\n * @property string $name\n * @property string $href\n * @property-read string $edit_url\n */\nclass StructureLink extends Model\n{\n    use HasIcon;\n    use HasPermissions;\n    use Structurable;\n\n    public $timestamps = false;\n\n    protected function editUrl(): Attribute\n    {\n        return Attribute::make(\n            get: fn() => route('waterhole.cp.structure.links.edit', ['link' => $this]),\n        )->shouldCache();\n    }\n}\n"
  },
  {
    "path": "src/Models/Support/MorphTypeCache.php",
    "content": "<?php\n\nnamespace Waterhole\\Models\\Support;\n\nuse Illuminate\\Database\\Eloquent\\Relations\\Relation;\n\nfinal class MorphTypeCache\n{\n    /**\n     * Get morph-mapped classes that use a given trait.\n     *\n     * @param class-string $trait\n     * @return array<int, class-string>\n     */\n    public static function forTrait(string $trait): array\n    {\n        static $cache = [];\n\n        return $cache[$trait] ??= collect(Relation::morphMap())\n            ->filter(fn(string $class) => in_array($trait, class_uses_recursive($class), true))\n            ->values()\n            ->all();\n    }\n}\n"
  },
  {
    "path": "src/Models/Tag.php",
    "content": "<?php\n\nnamespace Waterhole\\Models;\n\nuse Illuminate\\Database\\Eloquent\\Casts\\Attribute;\nuse Illuminate\\Database\\Eloquent\\Relations\\BelongsTo;\nuse Waterhole\\View\\Components\\Cp\\TagRow;\nuse Waterhole\\View\\TurboStream;\n\n/**\n * @property int $id\n * @property int $taxonomy_id\n * @property string $name\n * @property \\Carbon\\Carbon $created_at\n * @property \\Carbon\\Carbon $updated_at\n * @property-read Taxonomy $taxonomy\n * @property-read string $edit_url\n */\nclass Tag extends Model\n{\n    protected static function booted(): void\n    {\n        static::addGlobalScope(function ($query) {\n            $query->whereHas('taxonomy');\n        });\n    }\n\n    public function taxonomy(): BelongsTo\n    {\n        return $this->belongsTo(Taxonomy::class);\n    }\n\n    protected function editUrl(): Attribute\n    {\n        return Attribute::make(\n            get: fn() => route('waterhole.cp.taxonomies.tags.edit', [\n                'taxonomy' => $this->taxonomy_id,\n                'tag' => $this,\n            ]),\n        )->shouldCache();\n    }\n\n    /**\n     * Get the Turbo Streams that should be sent when this tag is removed.\n     */\n    public function streamRemoved(): array\n    {\n        return [TurboStream::remove(new TagRow($this))];\n    }\n}\n"
  },
  {
    "path": "src/Models/Taxonomy.php",
    "content": "<?php\n\nnamespace Waterhole\\Models;\n\nuse Illuminate\\Database\\Eloquent\\Casts\\Attribute;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasMany;\nuse Illuminate\\Support\\Facades\\Auth;\nuse Waterhole\\Models\\Concerns\\HasPermissions;\n\n/**\n * @property int $id\n * @property string $name\n * @property bool $allow_multiple\n * @property bool $is_required\n * @property \\Carbon\\Carbon $created_at\n * @property \\Carbon\\Carbon $updated_at\n * @property-read \\Illuminate\\Database\\Eloquent\\Collection $tags\n * @property-read string $edit_url\n */\nclass Taxonomy extends Model\n{\n    use HasPermissions;\n\n    protected static function booted(): void\n    {\n        static::addGlobalScope(function ($query) {\n            if ($ids = static::allPermitted(Auth::user())) {\n                $query->whereKey($ids);\n            }\n        });\n    }\n\n    public function tags(): HasMany\n    {\n        return $this->hasMany(Tag::class)->orderBy('name');\n    }\n\n    protected function editUrl(): Attribute\n    {\n        return Attribute::make(\n            get: fn() => route('waterhole.cp.taxonomies.edit', ['taxonomy' => $this]),\n        )->shouldCache();\n    }\n\n    public function abilities(): array\n    {\n        return ['view', 'assign-tags'];\n    }\n\n    public function defaultAbilities(): array\n    {\n        return ['view'];\n    }\n}\n"
  },
  {
    "path": "src/Models/Upload.php",
    "content": "<?php\n\nnamespace Waterhole\\Models;\n\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Prunable;\nuse Illuminate\\Database\\Eloquent\\Relations\\MorphToMany;\nuse Illuminate\\Http\\File;\nuse Illuminate\\Http\\UploadedFile;\nuse Illuminate\\Support\\Facades\\Storage;\nuse Intervention\\Image\\Laravel\\Facades\\Image;\n\n/**\n * @property int $id\n * @property null|int $user_id\n * @property string $filename\n * @property null|string $type\n * @property null|int $width\n * @property null|int $height\n * @property \\Carbon\\Carbon $created_at\n * @property \\Carbon\\Carbon $updated_at\n * @property-read \\Illuminate\\Database\\Eloquent\\Collection $posts\n * @property-read \\Illuminate\\Database\\Eloquent\\Collection $comments\n */\nclass Upload extends Model\n{\n    use Prunable;\n\n    public static function fromFile(File|UploadedFile $file): static\n    {\n        $attributes = [\n            'filename' => $file->hashName(),\n            'type' => $file->getMimeType(),\n        ];\n\n        if (app('image')->driver()->supports($attributes['type'])) {\n            $image = Image::read($file);\n            $attributes['width'] = $image->width();\n            $attributes['height'] = $image->height();\n        }\n\n        Storage::disk(config('waterhole.uploads.disk'))->putFile('uploads', $file);\n\n        // @phpstan-ignore-next-line\n        return new static($attributes);\n    }\n\n    protected static function booted(): void\n    {\n        static::deleted(function (self $upload) {\n            Storage::disk(config('waterhole.uploads.disk'))->delete('uploads/' . $upload->filename);\n        });\n    }\n\n    public function posts(): MorphToMany\n    {\n        return $this->morphedByMany(Post::class, 'content', 'attachments');\n    }\n\n    public function comments(): MorphToMany\n    {\n        return $this->morphedByMany(Comment::class, 'content', 'attachments');\n    }\n\n    public function prunable(): Builder\n    {\n        return static::whereNotExists(function ($query) {\n            $query->select('*')->from('attachments')->whereColumn('upload_id', 'id');\n        });\n    }\n}\n"
  },
  {
    "path": "src/Models/User.php",
    "content": "<?php\n\nnamespace Waterhole\\Models;\n\nuse Illuminate\\Auth\\Authenticatable;\nuse Illuminate\\Auth\\MustVerifyEmail;\nuse Illuminate\\Auth\\Passwords\\CanResetPassword;\nuse Illuminate\\Contracts\\Auth\\Access\\Authorizable as AuthorizableContract;\nuse Illuminate\\Contracts\\Auth\\Authenticatable as AuthenticatableContract;\nuse Illuminate\\Contracts\\Auth\\CanResetPassword as CanResetPasswordContract;\nuse Illuminate\\Contracts\\Auth\\MustVerifyEmail as MustVerifyEmailContract;\nuse Illuminate\\Contracts\\Translation\\HasLocalePreference;\nuse Illuminate\\Database\\Eloquent\\Casts\\AsArrayObject;\nuse Illuminate\\Database\\Eloquent\\Casts\\Attribute;\nuse Illuminate\\Database\\Eloquent\\Factories\\HasFactory;\nuse Illuminate\\Database\\Eloquent\\Relations\\BelongsToMany;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasMany;\nuse Illuminate\\Database\\Eloquent\\Relations\\MorphMany;\nuse Illuminate\\Database\\Query\\Expression;\nuse Illuminate\\Foundation\\Auth\\Access\\Authorizable;\nuse Illuminate\\Notifications\\Notifiable;\nuse Illuminate\\Support\\Facades\\Notification as NotificationFacade;\nuse Intervention\\Image\\Image;\nuse Laravel\\Sanctum\\HasApiTokens;\nuse Waterhole\\Auth\\AuthenticatesWaterhole;\nuse Waterhole\\Database\\Factories\\UserFactory;\nuse Waterhole\\Extend\\Core\\NotificationTypes;\nuse Waterhole\\Models\\Attributes\\FileAttribute;\nuse Waterhole\\Models\\Concerns\\HasFileAttributes;\nuse Waterhole\\Models\\Concerns\\ReceivesPermissions;\nuse Waterhole\\Models\\Concerns\\UsesFormatter;\nuse Waterhole\\Notifications\\ResetPassword;\nuse Waterhole\\Notifications\\VerifyEmail;\n\n/**\n * @property int $id\n * @property string $name\n * @property string $email\n * @property null|\\Carbon\\Carbon $email_verified_at\n * @property null|string $password\n * @property null|string $remember_token\n * @property null|string $locale\n * @property null|string $headline\n * @property null|string $bio\n * @property null|string $location\n * @property null|string $website\n * @property null|string $avatar\n * @property null|\\Carbon\\Carbon $created_at\n * @property null|\\Carbon\\Carbon $last_seen_at\n * @property null|\\Carbon\\Carbon $suspended_until\n * @property bool $show_online\n * @property null|\\Illuminate\\Database\\Eloquent\\Casts\\ArrayObject $notification_channels\n * @property null|\\Carbon\\Carbon $notifications_read_at\n * @property bool $follow_on_comment\n * @property-read string $url\n * @property-read string $edit_url\n * @property-read string $avatar_url\n * @property-read int $unread_notification_count\n * @property-read \\Illuminate\\Database\\Eloquent\\Collection $posts\n * @property-read \\Illuminate\\Database\\Eloquent\\Collection $comments\n * @property-read \\Illuminate\\Database\\Eloquent\\Collection $groups\n * @property-read \\Illuminate\\Database\\Eloquent\\Collection $notifications\n */\nclass User extends Model implements\n    AuthenticatableContract,\n    AuthorizableContract,\n    MustVerifyEmailContract,\n    CanResetPasswordContract,\n    HasLocalePreference\n{\n    use HasFactory;\n    use Authenticatable;\n    use Authorizable;\n    use CanResetPassword;\n    use HasFileAttributes;\n    use MustVerifyEmail;\n    use Notifiable;\n    use ReceivesPermissions;\n    use UsesFormatter;\n    use HasApiTokens;\n\n    public const UPDATED_AT = null;\n\n    protected $hidden = ['password', 'remember_token'];\n\n    protected $casts = [\n        'email_verified_at' => 'datetime',\n        'last_seen_at' => 'datetime',\n        'show_online' => 'boolean',\n        'notification_channels' => AsArrayObject::class,\n        'notifications_read_at' => 'datetime',\n        'follow_on_comment' => 'boolean',\n        'suspended_until' => 'datetime',\n    ];\n\n    protected ?AuthenticatesWaterhole $originalUser = null;\n\n    protected static function booted(): void\n    {\n        static::creating(function (User $user) {\n            $user->follow_on_comment ??= true;\n            $user->notification_channels ??= collect(\n                resolve(NotificationTypes::class)->values(),\n            )->mapWithKeys(fn($type) => [$type => ['database', 'mail']]);\n        });\n    }\n\n    protected static function newFactory(): UserFactory\n    {\n        return UserFactory::new();\n    }\n\n    /**\n     * Relationship with the user's posts.\n     */\n    public function posts(): HasMany\n    {\n        return $this->hasMany(Post::class);\n    }\n\n    /**\n     * Relationship with the user's comments.\n     */\n    public function comments(): HasMany\n    {\n        return $this->hasMany(Comment::class);\n    }\n\n    /**\n     * Relationship with the user's selected groups.\n     */\n    public function groups(): BelongsToMany\n    {\n        return $this->belongsToMany(Group::class)->orderBy('name');\n    }\n\n    /**\n     * Relationship with the user's notifications.\n     */\n    public function notifications(): MorphMany\n    {\n        return $this->morphMany(Notification::class, 'notifiable');\n    }\n\n    /**\n     * Relationship with the user's reactions.\n     */\n    public function reactions(): HasMany\n    {\n        return $this->hasMany(Reaction::class);\n    }\n\n    /**\n     * Relationship with the user's external authentication providers.\n     */\n    public function authProviders(): HasMany\n    {\n        return $this->hasMany(AuthProvider::class);\n    }\n\n    /**\n     * Relationship with the user's file uploads.\n     */\n    public function uploads(): HasMany\n    {\n        return $this->hasMany(Upload::class);\n    }\n\n    /**\n     * Mark the user's notifications about a specific subject as read.\n     */\n    public function markNotificationsRead(Model $model): static\n    {\n        $this->unreadNotifications()\n            ->where(\n                fn($query) => $query\n                    ->whereMorphedTo('group', $model)\n                    ->orWhereMorphedTo('content', $model),\n            )\n            ->update(['read_at' => now()]);\n\n        return $this;\n    }\n\n    /**\n     * Get the user's preferred locale.\n     */\n    public function preferredLocale(): ?string\n    {\n        return $this->locale;\n    }\n\n    /**\n     * Upload a new avatar.\n     */\n    public function uploadAvatar(Image $image): static\n    {\n        $this->avatarFile()->uploadImage($image);\n\n        return $this;\n    }\n\n    /**\n     * Remove the user's avatar.\n     */\n    public function removeAvatar(): static\n    {\n        $this->avatarFile()->remove();\n\n        return $this;\n    }\n\n    public function avatarFile(): FileAttribute\n    {\n        return $this->fileAttribute(\n            attribute: 'avatar',\n            directory: 'avatars',\n            encodeImage: fn(Image $image) => $image->cover(200, 200)->toPng(),\n        );\n    }\n\n    /**\n     * Send an email verification notification to the user.\n     */\n    public function sendEmailVerificationNotification(): void\n    {\n        NotificationFacade::route('mail', $this->email)->notify(\n            new VerifyEmail($this, $this->email),\n        );\n    }\n\n    /**\n     * Only send notification emails to a verified address.\n     */\n    public function routeNotificationForMail(): ?string\n    {\n        return $this->hasVerifiedEmail() ? $this->email : null;\n    }\n\n    /**\n     * Send a password reset notification to the user.\n     */\n    public function sendPasswordResetNotification($token): void\n    {\n        $this->notify(new ResetPassword($token));\n    }\n\n    /**\n     * Determine whether this user is an admin.\n     */\n    public function isAdmin(): bool\n    {\n        return $this->groups->contains(Group::ADMIN_ID);\n    }\n\n    /**\n     * Determine whether this user is the root admin.\n     */\n    public function isRootAdmin(): bool\n    {\n        return $this->id === 1;\n    }\n\n    public function requiresApproval(): bool\n    {\n        return $this->groups->some(fn(Group $group) => $group->rules['requires_approval'] ?? false);\n    }\n\n    /**\n     * Determine whether the user is online (active in the last 5 minutes).\n     */\n    public function isOnline(): bool\n    {\n        return $this->show_online && $this->last_seen_at?->isAfter(now()->subMinutes(5));\n    }\n\n    public function isSuspended(): bool\n    {\n        return (bool) $this->suspended_until?->isFuture();\n    }\n\n    protected function url(): Attribute\n    {\n        return Attribute::make(\n            get: fn() => route('waterhole.users.show', ['user' => $this]),\n        )->shouldCache();\n    }\n\n    protected function editUrl(): Attribute\n    {\n        return Attribute::make(\n            get: fn() => route('waterhole.cp.users.edit', ['user' => $this]),\n        )->shouldCache();\n    }\n\n    protected function avatarUrl(): Attribute\n    {\n        return Attribute::make(get: fn() => $this->avatarFile()->url())->shouldCache();\n    }\n\n    protected function unreadNotificationCount(): Attribute\n    {\n        return Attribute::make(\n            get: function () {\n                $query = $this->unreadNotifications();\n\n                if ($this->notifications_read_at) {\n                    $query->where('notifications.created_at', '>', $this->notifications_read_at);\n                }\n\n                $groupType = \"COALESCE(group_type, CONCAT('', id))\";\n                $groupId = \"COALESCE(group_id, CONCAT('', id))\";\n\n                return $query\n                    ->distinct()\n                    ->count(new Expression(\"CONCAT(type, '\\x1f', $groupType, '\\x1f', $groupId)\"));\n            },\n        )->shouldCache();\n    }\n\n    public function broadcastChannelRoute(): string\n    {\n        return 'Waterhole.Models.User.{user}';\n    }\n\n    public function broadcastChannel(): string\n    {\n        return 'Waterhole.Models.User.' . $this->getKey();\n    }\n\n    /**\n     * Get the original user that was used to authenticate this Waterhole request.\n     */\n    public function originalUser(): ?AuthenticatesWaterhole\n    {\n        return $this->originalUser;\n    }\n\n    public function setOriginalUser(AuthenticatesWaterhole $user): static\n    {\n        $this->originalUser = $user;\n\n        return $this;\n    }\n}\n"
  },
  {
    "path": "src/Notifications/ContentApproved.php",
    "content": "<?php\n\nnamespace Waterhole\\Notifications;\n\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Support\\HtmlString;\nuse Waterhole\\Models\\Comment;\nuse Waterhole\\Models\\Model;\nuse Waterhole\\Models\\Post;\nuse function Waterhole\\emojify;\n\nclass ContentApproved extends Notification\n{\n    public function __construct(protected Post|Comment $subject) {}\n\n    public function via($notifiable): array\n    {\n        return ['database'];\n    }\n\n    public function content(): Model\n    {\n        return $this->subject;\n    }\n\n    public function icon(): string\n    {\n        return $this->subject->channel->icon;\n    }\n\n    public function title(): HtmlString\n    {\n        if ($this->subject instanceof Post) {\n            return new HtmlString(\n                __('waterhole::notifications.post-approved-title', [\n                    'post' => '<strong>' . emojify($this->subject->title) . '</strong>',\n                ]),\n            );\n        }\n\n        return new HtmlString(\n            __('waterhole::notifications.comment-approved-title', [\n                'post' => '<strong>' . emojify($this->subject->post->title) . '</strong>',\n            ]),\n        );\n    }\n\n    public function excerpt(): HtmlString\n    {\n        return $this->subject->body_html;\n    }\n\n    public function url(): string\n    {\n        return $this->subject instanceof Post ? $this->subject->url : $this->subject->post_url;\n    }\n\n    public function button(): string\n    {\n        return $this->subject instanceof Post\n            ? __('waterhole::notifications.view-post-button')\n            : __('waterhole::notifications.view-comment-button');\n    }\n\n    public static function load(Collection $notifications): void\n    {\n        $notifications->loadMorph('content', [\n            Post::class => ['user', 'channel'],\n            Comment::class => ['user', 'channel', 'post'],\n        ]);\n    }\n}\n"
  },
  {
    "path": "src/Notifications/ContentRemoved.php",
    "content": "<?php\n\nnamespace Waterhole\\Notifications;\n\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Support\\Facades\\Lang;\nuse Illuminate\\Support\\HtmlString;\nuse Illuminate\\Support\\Str;\nuse Waterhole\\Models\\Comment;\nuse Waterhole\\Models\\Model;\nuse Waterhole\\Models\\Post;\nuse function Waterhole\\emojify;\n\nclass ContentRemoved extends Notification\n{\n    public function __construct(protected Post|Comment $subject) {}\n\n    public function via($notifiable): array\n    {\n        return ['database'];\n    }\n\n    public function content(): Model\n    {\n        return $this->subject;\n    }\n\n    public function icon(): string\n    {\n        return 'tabler-trash';\n    }\n\n    public function title(): HtmlString\n    {\n        if ($this->subject instanceof Post) {\n            return new HtmlString(\n                __('waterhole::notifications.post-removed-title', [\n                    'post' => '<strong>' . emojify($this->subject->title) . '</strong>',\n                ]),\n            );\n        }\n\n        return new HtmlString(\n            __('waterhole::notifications.comment-removed-title', [\n                'post' => '<strong>' . emojify($this->subject->post->title) . '</strong>',\n            ]),\n        );\n    }\n\n    public function excerpt(): ?string\n    {\n        $message = $this->subject->deleted_message;\n\n        if (!$message && $this->subject->deleted_reason) {\n            $message = Lang::has(\n                $key = \"waterhole::forum.report-reason-{$this->subject->deleted_reason}-label\",\n            )\n                ? __($key)\n                : Str::headline($this->subject->deleted_reason);\n        }\n\n        return $message;\n    }\n\n    public function url(): string\n    {\n        return $this->subject instanceof Post ? $this->subject->url : $this->subject->post_url;\n    }\n\n    public function button(): string\n    {\n        return $this->subject instanceof Post\n            ? __('waterhole::notifications.view-post-button')\n            : __('waterhole::notifications.view-comment-button');\n    }\n\n    public static function load(Collection $notifications): void\n    {\n        $notifications->loadMorph('content', [\n            Comment::class => ['post'],\n        ]);\n    }\n}\n"
  },
  {
    "path": "src/Notifications/DatabaseChannel.php",
    "content": "<?php\n\nnamespace Waterhole\\Notifications;\n\nuse Illuminate\\Notifications\\Channels\\DatabaseChannel as BaseDatabaseChannel;\nuse Illuminate\\Notifications\\Notification;\nuse Waterhole\\Events\\NotificationReceived;\nuse Waterhole\\Models\\Notification as WaterholeNotification;\n\nclass DatabaseChannel extends BaseDatabaseChannel\n{\n    public function send($notifiable, Notification $notification)\n    {\n        $model = parent::send($notifiable, $notification);\n\n        if ($model instanceof WaterholeNotification) {\n            broadcast(new NotificationReceived($model));\n        }\n\n        return $notification;\n    }\n\n    protected function buildPayload($notifiable, Notification $notification): array\n    {\n        $payload = parent::buildPayload($notifiable, $notification);\n\n        // We will add a few things specific to Waterhole's notifications system\n        // into the database payload, if they are present. See the base\n        // Notification class for a description of each of these.\n        if (method_exists($notification, 'sender') && ($sender = $notification->sender())) {\n            $payload['sender_id'] = $sender->getKey();\n        }\n\n        if (method_exists($notification, 'group') && ($group = $notification->group())) {\n            $payload['group_type'] = $group->getMorphClass();\n            $payload['group_id'] = $group->getKey();\n        }\n\n        if (method_exists($notification, 'content') && ($content = $notification->content())) {\n            $payload['content_type'] = $content->getMorphClass();\n            $payload['content_id'] = $content->getKey();\n        }\n\n        return $payload;\n    }\n}\n"
  },
  {
    "path": "src/Notifications/Mention.php",
    "content": "<?php\n\nnamespace Waterhole\\Notifications;\n\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Support\\HtmlString;\nuse Waterhole\\Models\\Comment;\nuse Waterhole\\Models\\Post;\nuse Waterhole\\Models\\User;\n\nuse function Waterhole\\emojify;\n\nclass Mention extends Notification\n{\n    protected Post $post;\n\n    public function __construct(protected Post|Comment $content)\n    {\n        $this->post = $this->content instanceof Post ? $this->content : $this->content->post;\n    }\n\n    public function shouldSend($notifiable): bool\n    {\n        return !$this->post->ignoredBy->contains($notifiable) &&\n            !$this->post->channel->ignoredBy->contains($notifiable);\n    }\n\n    public function content(): Post|Comment\n    {\n        return $this->content;\n    }\n\n    public function sender(): ?User\n    {\n        return $this->content->user;\n    }\n\n    public function icon(): string\n    {\n        return 'tabler-at';\n    }\n\n    public function title(): HtmlString\n    {\n        return new HtmlString(\n            __('waterhole::notifications.mention-title', [\n                'post' => '<strong>' . emojify($this->post->title) . '</strong>',\n            ]),\n        );\n    }\n\n    public function excerpt(): HtmlString\n    {\n        return $this->content->body_html;\n    }\n\n    public function url(): string\n    {\n        return $this->content instanceof Post ? $this->content->url : $this->content->post_url;\n    }\n\n    public function button(): string\n    {\n        return __(\n            $this->content instanceof Post\n                ? 'waterhole::notifications.view-post-button'\n                : 'waterhole::notifications.view-comment-button',\n        );\n    }\n\n    public function reason(): string\n    {\n        return __('waterhole::notifications.mention-reason');\n    }\n\n    public function unsubscribeText(): string\n    {\n        return __('waterhole::notifications.mention-unsubscribe');\n    }\n\n    public static function description(): string\n    {\n        return __('waterhole::notifications.mention-description');\n    }\n\n    public static function load(Collection $notifications): void\n    {\n        $notifications->loadMorph('content', [\n            Post::class => ['user'],\n            Comment::class => ['post', 'user'],\n        ]);\n    }\n}\n"
  },
  {
    "path": "src/Notifications/NewComment.php",
    "content": "<?php\n\nnamespace Waterhole\\Notifications;\n\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Support\\HtmlString;\nuse Waterhole\\Models\\Comment;\nuse Waterhole\\Models\\Post;\nuse Waterhole\\Models\\User;\n\nuse function Waterhole\\emojify;\n\nclass NewComment extends Notification\n{\n    public function __construct(protected Comment $comment) {}\n\n    public function content(): Comment\n    {\n        return $this->comment;\n    }\n\n    public function sender(): ?User\n    {\n        return $this->comment->user;\n    }\n\n    public function icon(): string\n    {\n        return 'tabler-message-circle-2';\n    }\n\n    public function title(): HtmlString\n    {\n        return new HtmlString(\n            __('waterhole::notifications.new-comment-title', [\n                'post' => '<strong>' . emojify($this->comment->post->title) . '</strong>',\n            ]),\n        );\n    }\n\n    public function excerpt(): HtmlString\n    {\n        return $this->comment->body_html;\n    }\n\n    public function url(): string\n    {\n        return $this->comment->post_url;\n    }\n\n    public function group(): Post\n    {\n        return $this->comment->post;\n    }\n\n    public function groupedUrl(): string\n    {\n        return $this->comment->post->unread_url;\n    }\n\n    public function button(): string\n    {\n        return __('waterhole::notifications.view-comment-button');\n    }\n\n    public function reason(): string\n    {\n        return __('waterhole::notifications.new-comment-reason');\n    }\n\n    public function unsubscribeText(): string\n    {\n        return __('waterhole::notifications.new-comment-unsubscribe');\n    }\n\n    public function unsubscribe(User $user): void\n    {\n        $this->comment->post->loadUserState($user)->unfollow();\n    }\n\n    public static function description(): string\n    {\n        return __('waterhole::notifications.new-comment-description');\n    }\n\n    public static function load(Collection $notifications): void\n    {\n        $notifications->load([\n            'content.post' => fn($query) => $query->withUnreadCommentsCount(),\n            'content.user',\n        ]);\n    }\n}\n"
  },
  {
    "path": "src/Notifications/NewFlag.php",
    "content": "<?php\n\nnamespace Waterhole\\Notifications;\n\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Support\\Facades\\Lang;\nuse Illuminate\\Support\\HtmlString;\nuse Illuminate\\Support\\Str;\nuse Waterhole\\Models\\Channel;\nuse Waterhole\\Models\\Comment;\nuse Waterhole\\Models\\Flag;\nuse Waterhole\\Models\\Model;\nuse Waterhole\\Models\\Post;\nuse Waterhole\\Models\\User;\nuse function Waterhole\\emojify;\n\nclass NewFlag extends Notification\n{\n    public function __construct(protected Flag $flag) {}\n\n    public function content(): Flag\n    {\n        return $this->flag;\n    }\n\n    public function sender(): ?User\n    {\n        return $this->flag->createdBy;\n    }\n\n    public function icon(): string\n    {\n        return 'tabler-flag';\n    }\n\n    public function title(): HtmlString\n    {\n        $post =\n            $this->flag->subject instanceof Post\n                ? $this->flag->subject\n                : $this->flag->subject->post;\n\n        $key =\n            $this->flag->subject instanceof Comment\n                ? 'waterhole::notifications.flagged-comment-title'\n                : 'waterhole::notifications.flagged-post-title';\n\n        return new HtmlString(\n            __($key, ['post' => '<strong>' . emojify($post->title) . '</strong>']),\n        );\n    }\n\n    public function excerpt(): string\n    {\n        $reason = Lang::has($key = \"waterhole::forum.report-reason-{$this->flag->reason}-label\")\n            ? __($key)\n            : Str::headline($this->flag->reason);\n\n        $excerpt = Str::limit(strip_tags($this->flag->subject->body_html ?? ''), 200);\n\n        return $excerpt ? $reason . ' - ' . $excerpt : $reason;\n    }\n\n    public function url(): ?string\n    {\n        return $this->flag->subject?->flagUrl();\n    }\n\n    public function button(): ?string\n    {\n        return $this->flag->subject instanceof Comment\n            ? __('waterhole::notifications.view-comment-button')\n            : __('waterhole::notifications.view-post-button');\n    }\n\n    public function group(): ?Model\n    {\n        return $this->flag->subject;\n    }\n\n    public function groupedUrl(): ?string\n    {\n        return $this->flag->subject?->flagUrl();\n    }\n\n    public function reason(): string\n    {\n        return __('waterhole::notifications.new-flag-reason');\n    }\n\n    public function unsubscribeText(): string\n    {\n        return __('waterhole::notifications.new-flag-unsubscribe');\n    }\n\n    public static function description(): string\n    {\n        return __('waterhole::notifications.new-flag-description');\n    }\n\n    public static function availableFor(User $user): bool\n    {\n        return Channel::allPermitted($user, 'moderate') !== [];\n    }\n\n    public static function load(Collection $notifications): void\n    {\n        $notifications->load(['content.createdBy', 'content.subject']);\n\n        $notifications->loadMorph('content.subject', [\n            Comment::class => ['post'],\n        ]);\n    }\n}\n"
  },
  {
    "path": "src/Notifications/NewPost.php",
    "content": "<?php\n\nnamespace Waterhole\\Notifications;\n\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Support\\HtmlString;\nuse Waterhole\\Models\\Post;\nuse Waterhole\\Models\\User;\n\nuse function Waterhole\\emojify;\n\nclass NewPost extends Notification\n{\n    public function __construct(protected Post $post) {}\n\n    public function content(): Post\n    {\n        return $this->post;\n    }\n\n    public function sender(): ?User\n    {\n        return $this->post->user;\n    }\n\n    public function icon(): string\n    {\n        return $this->post->channel->icon;\n    }\n\n    public function title(): HtmlString\n    {\n        return new HtmlString(\n            __('waterhole::notifications.new-post-title', [\n                'channel' => emojify($this->post->channel->name),\n                'post' => '<strong>' . emojify($this->post->title) . '</strong>',\n            ]),\n        );\n    }\n\n    public function excerpt(): string\n    {\n        return $this->post->body_html;\n    }\n\n    public function url(): string\n    {\n        return $this->post->url;\n    }\n\n    public function button(): string\n    {\n        return __('waterhole::notifications.view-post-button');\n    }\n\n    public function reason(): string\n    {\n        return __('waterhole::notifications.new-post-reason');\n    }\n\n    public function unsubscribeText(): string\n    {\n        return __('waterhole::notifications.new-post-unsubscribe');\n    }\n\n    public function unsubscribe(User $user): void\n    {\n        $this->post->channel->loadUserState($user)->unfollow();\n    }\n\n    public static function description(): string\n    {\n        return __('waterhole::notifications.new-post-description');\n    }\n\n    public static function load(Collection $notifications): void\n    {\n        $notifications->load('content.user', 'content.channel');\n    }\n}\n"
  },
  {
    "path": "src/Notifications/Notification.php",
    "content": "<?php\n\nnamespace Waterhole\\Notifications;\n\nuse Illuminate\\Bus\\Queueable;\nuse Illuminate\\Contracts\\Queue\\ShouldQueue;\nuse Illuminate\\Contracts\\Support\\Htmlable;\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Mail\\Mailable;\nuse Illuminate\\Notifications\\Notification as BaseNotification;\nuse Illuminate\\Support\\Facades\\Crypt;\nuse Illuminate\\Support\\Facades\\URL;\nuse Waterhole\\Mail\\Markdown;\nuse Waterhole\\Models\\Model;\nuse Waterhole\\Models\\Notification as NotificationModel;\nuse Waterhole\\Models\\User;\n\n/**\n * Base class for a Waterhole notification.\n *\n * Waterhole builds on top of the Laravel Notifications system to make\n * implementing notifications even easier. Waterhole takes care of all the\n * boilerplate: managing user preferences, generating HTML emails, handling\n * secure unsubscribe links, and rendering notifications.\n *\n * To define a new Waterhole Notification type, extend this class and implement\n * the methods to describe your notification content.\n */\nabstract class Notification extends BaseNotification implements ShouldQueue\n{\n    use Queueable;\n\n    public static function fromNotificationModel(NotificationModel $notification): ?self\n    {\n        // @phpstan-ignore new.static\n        return $notification->content ? new static($notification->content) : null;\n    }\n\n    /**\n     * Determine whether the notification should be sent to this user.\n     */\n    public function shouldSend($notifiable): bool\n    {\n        return true;\n    }\n\n    /**\n     * Get the notification's delivery channels.\n     *\n     * For Waterhole notifications, each user is able to set a preference for\n     * which channels they would like to receive their notifications on.\n     */\n    public function via($notifiable): array\n    {\n        return $notifiable->notification_channels[get_class($this)] ?? [];\n    }\n\n    /**\n     * Get the array representation of the notification.\n     */\n    public function toArray(): array\n    {\n        return [];\n    }\n\n    /**\n     * Get the mail representation of the notification.\n     */\n    public function toMail($notifiable): ?Mailable\n    {\n        if (!($to = $notifiable->routeNotificationFor('mail'))) {\n            return null;\n        }\n\n        $title = $this->title();\n        $sender = $this->sender();\n\n        $markdown = resolve(Markdown::class);\n        $view = 'waterhole::mail.notification';\n        $data = [\n            'title' => $title,\n            'avatar' => $sender?->avatar_url,\n            'name' => $sender?->name,\n            'excerpt' => $this->excerpt(),\n            'button' => $this->button(),\n            'url' => $this->url(),\n            'reason' => $this->reason(),\n            'unsubscribeText' => $this->unsubscribeText(),\n            'unsubscribeUrl' => $this->unsubscribeUrl($notifiable),\n        ];\n\n        return (new Mailable())\n            ->to($to)\n            ->subject(html_entity_decode(strip_tags($title), ENT_QUOTES, 'UTF-8'))\n            ->view($markdown->render($view, $data))\n            ->text($markdown->renderText($view, $data));\n    }\n\n    /**\n     * The model associated with the individual notification instance.\n     *\n     * This must be the same as your notification's constructor argument. It\n     * will be used to reconstruct the notification instance after the\n     * notification is read from the database.\n     */\n    public function content(): ?Model\n    {\n        return null;\n    }\n\n    /**\n     * The user whose action caused the notification to be sent.\n     *\n     * If present, the user's avatar will be displayed alongside the\n     * notification. In the case of a notification for a new comment, it is the\n     * author of the comment.\n     */\n    public function sender(): ?User\n    {\n        return null;\n    }\n\n    /**\n     * The name of an icon to represent the notification.\n     */\n    public function icon(): ?string\n    {\n        return null;\n    }\n\n    /**\n     * The title of the notification.\n     *\n     * This can be a plain string or an `HtmlString` if you want to wrap\n     * certain words in `<strong>`, for example. Just be sure to escape user-\n     * generated content if returning an `HtmlString`.\n     */\n    abstract public function title(): string|Htmlable;\n\n    /**\n     * An excerpt from the notification content.\n     */\n    public function excerpt(): null|string|Htmlable\n    {\n        return null;\n    }\n\n    /**\n     * The URL that the user should be taken to when they click the\n     * notification.\n     */\n    public function url(): ?string\n    {\n        return null;\n    }\n\n    /**\n     * The button text to be displayed on the action button in the notification\n     * email.\n     */\n    public function button(): ?string\n    {\n        return null;\n    }\n\n    /**\n     * A common model to group multiple notifications together.\n     *\n     * For example, for a notification about a new comment, this could be the\n     * post of the comment, causing notifications for multiple new comments\n     * on the same post to be grouped together on the user's notification list.\n     */\n    public function group(): ?Model\n    {\n        return null;\n    }\n\n    /**\n     * The URL that the user should be taken to when they click a grouped\n     * notification.\n     */\n    public function groupedUrl(): ?string\n    {\n        return $this->url();\n    }\n\n    /**\n     * The reason that the user is receiving the notification.\n     */\n    public function reason(): ?string\n    {\n        return null;\n    }\n\n    /**\n     * The label of the unsubscribe link for this notification type.\n     */\n    public function unsubscribeText(): ?string\n    {\n        return __('waterhole::notifications.unsubscribe-link');\n    }\n\n    /**\n     * The URL to unsubscribe the user from this notification type.\n     */\n    public function unsubscribeUrl($notifiable): string\n    {\n        return URL::signedRoute('waterhole.notifications.unsubscribe', [\n            'payload' => Crypt::encrypt([\n                'type' => get_class($this),\n                'notifiable_type' => $notifiable->getMorphClass(),\n                'notifiable_id' => $notifiable->getKey(),\n                'content_type' => $this->content()?->getMorphClass(),\n                'content_id' => $this->content()?->getKey(),\n            ]),\n        ]);\n    }\n\n    /**\n     * Unsubscribe the user from this notification type.\n     */\n    public function unsubscribe(User $user): void\n    {\n        $type = get_class($this);\n\n        if ($channels = $user->notification_channels[$type] ?? null) {\n            $user->notification_channels[$type] = collect($channels)->reject('mail');\n            $user->save();\n        }\n    }\n\n    /**\n     * A description of this notification type in the user preferences.\n     */\n    public static function description(): ?string\n    {\n        return null;\n    }\n\n    /**\n     * Determine whether this notification type should appear in preferences.\n     */\n    public static function availableFor(User $user): bool\n    {\n        return true;\n    }\n\n    /**\n     * Load additional relationships onto the notifications models before\n     * displaying the notification list.\n     */\n    public static function load(Collection $notifications): void {}\n}\n"
  },
  {
    "path": "src/Notifications/ResetPassword.php",
    "content": "<?php\n\nnamespace Waterhole\\Notifications;\n\nuse Illuminate\\Notifications\\Messages\\MailMessage;\nuse Illuminate\\Notifications\\Notification;\n\nclass ResetPassword extends Notification\n{\n    public function __construct(public string $token) {}\n\n    public function via(): array\n    {\n        return ['mail'];\n    }\n\n    public function toMail($notifiable): MailMessage\n    {\n        $resetUrl = $this->resetUrl($notifiable);\n        $minutes = config('auth.passwords.' . config('auth.defaults.passwords') . '.expire');\n\n        return (new MailMessage())\n            ->markdown('waterhole::mail.email')\n            ->subject(__('waterhole::auth.reset-password-mail-subject'))\n            ->line(\n                __('waterhole::auth.reset-password-mail-body', [\n                    'forum' => config('waterhole.forum.name'),\n                    'minutes' => $minutes,\n                ]),\n            )\n            ->action(__('waterhole::auth.reset-password-mail-button'), $resetUrl);\n    }\n\n    protected function resetUrl($notifiable): string\n    {\n        return url(\n            route(\n                'waterhole.reset-password',\n                [\n                    'token' => $this->token,\n                    'email' => $notifiable->getEmailForPasswordReset(),\n                ],\n                false,\n            ),\n        );\n    }\n}\n"
  },
  {
    "path": "src/Notifications/VerifyEmail.php",
    "content": "<?php\n\nnamespace Waterhole\\Notifications;\n\nuse Illuminate\\Notifications\\Messages\\MailMessage;\nuse Illuminate\\Notifications\\Notification;\nuse Illuminate\\Support\\Facades\\URL;\nuse Waterhole\\Models\\User;\n\nclass VerifyEmail extends Notification\n{\n    public function __construct(private User $user, private string $email) {}\n\n    public function via(): array\n    {\n        return ['mail'];\n    }\n\n    public function toMail(): MailMessage\n    {\n        $verificationUrl = $this->verificationUrl();\n\n        return (new MailMessage())\n            ->markdown('waterhole::mail.email')\n            ->subject(__('waterhole::auth.email-verification-mail-subject'))\n            ->line(\n                __('waterhole::auth.email-verification-mail-body', [\n                    'forum' => config('waterhole.forum.name'),\n                ]),\n            )\n            ->action(__('waterhole::auth.email-verification-mail-button'), $verificationUrl);\n    }\n\n    protected function verificationUrl(): string\n    {\n        return URL::temporarySignedRoute(\n            'waterhole.verify-email',\n            now()->addMinutes(config('auth.verification.expire', 60)),\n            [\n                'id' => $this->user->getKey(),\n                'email' => $this->email,\n            ],\n        );\n    }\n}\n"
  },
  {
    "path": "src/Policies/CommentPolicy.php",
    "content": "<?php\n\nnamespace Waterhole\\Policies;\n\nuse Waterhole\\Models\\Comment;\nuse Waterhole\\Models\\User;\n\nclass CommentPolicy\n{\n    /**\n     * Any user can create a comment.\n     */\n    public function create(): bool\n    {\n        return true;\n    }\n\n    /**\n     * Users who can moderate a post can moderate its comments.\n     */\n    public function moderate(User $user, Comment $comment): bool\n    {\n        return $user->can('waterhole.post.moderate', $comment->post);\n    }\n\n    /**\n     * Users can edit their own comments. Users who can moderate a post can\n     * edit its comments.\n     */\n    public function edit(User $user, Comment $comment): bool\n    {\n        if ($comment->trashed()) {\n            return false;\n        }\n\n        if ($this->moderate($user, $comment)) {\n            return true;\n        }\n\n        if ($comment->user_id !== $user->id) {\n            return false;\n        }\n\n        $limitMinutes = config('waterhole.forum.edit_time_limit');\n\n        if ($limitMinutes === null) {\n            return true;\n        }\n\n        return $comment->created_at->diffInMinutes(now()) <= $limitMinutes;\n    }\n\n    /**\n     * Users can delete their own comments.\n     */\n    public function delete(User $user, Comment $comment): bool\n    {\n        return $comment->user_id === $user->id || $this->moderate($user, $comment);\n    }\n\n    /**\n     * Users can restore their own deleted comments.\n     */\n    public function restore(User $user, Comment $comment): bool\n    {\n        return $this->moderate($user, $comment) ||\n            ($comment->user_id === $user->id && $comment->deleted_by === $user->id);\n    }\n\n    /**\n     * Any user can react to a comment.\n     */\n    public function react(User $user, Comment $comment): bool\n    {\n        return !$comment->trashed();\n    }\n}\n"
  },
  {
    "path": "src/Policies/PostPolicy.php",
    "content": "<?php\n\nnamespace Waterhole\\Policies;\n\nuse Waterhole\\Models\\Post;\nuse Waterhole\\Models\\User;\n\nclass PostPolicy\n{\n    /**\n     * Any user can create a post.\n     */\n    public function create(): bool\n    {\n        return true;\n    }\n\n    /**\n     * Users can moderate a post and its comments if they have permission.\n     */\n    public function moderate(User $user, Post $post): bool\n    {\n        return $user->can('waterhole.channel.moderate', $post->channel);\n    }\n\n    /**\n     * Users can edit their own posts. Users with the `moderate` ability can\n     * edit any posts.\n     */\n    public function edit(?User $user, Post $post): bool\n    {\n        if (!$user || $post->trashed()) {\n            return false;\n        }\n\n        if ($this->moderate($user, $post)) {\n            return true;\n        }\n\n        if ($post->user_id !== $user->id) {\n            return false;\n        }\n\n        $limitMinutes = config('waterhole.forum.edit_time_limit');\n\n        if ($limitMinutes === null) {\n            return true;\n        }\n\n        return $post->created_at->diffInMinutes(now()) <= $limitMinutes;\n    }\n\n    /**\n     * Users can delete their own posts if they're uncommented on. Users with\n     * the `moderate` ability can delete any posts.\n     */\n    public function delete(User $user, Post $post): bool\n    {\n        return ($post->user_id === $user->id && $post->comment_count === 0) ||\n            $this->moderate($user, $post);\n    }\n\n    /**\n     * Users can move their own posts if they're uncommented on. Users with\n     * the `moderate` ability can move any posts.\n     */\n    public function move(User $user, Post $post): bool\n    {\n        return $this->delete($user, $post);\n    }\n\n    /**\n     * Users can comment on a post if they have permission and the post is not\n     * locked.\n     */\n    public function comment(User $user, Post $post): bool\n    {\n        return !$post->trashed() &&\n            $post->is_approved &&\n            $user->can('waterhole.channel.comment', $post->channel) &&\n            (!$post->is_locked || $this->moderate($user, $post));\n    }\n\n    /**\n     * Any user can react to a post.\n     */\n    public function react(User $user, Post $post): bool\n    {\n        return !$post->trashed();\n    }\n}\n"
  },
  {
    "path": "src/Policies/UserPolicy.php",
    "content": "<?php\n\nnamespace Waterhole\\Policies;\n\nuse Waterhole\\Models\\User;\nuse Waterhole\\Waterhole;\n\nclass UserPolicy\n{\n    /**\n     * Users can suspend other users if they have permission and the target\n     * doesn't have the same permission.\n     */\n    public function suspend(User $user, User $target): bool\n    {\n        return Waterhole::permissions()->can($user, 'suspend', User::class) &&\n            !Waterhole::permissions()->can($target, 'suspend', User::class);\n    }\n}\n"
  },
  {
    "path": "src/Providers/ApiServiceProvider.php",
    "content": "<?php\n\nnamespace Waterhole\\Providers;\n\nuse Illuminate\\Support\\ServiceProvider;\nuse Tobyz\\JsonApiServer\\Extension\\Atomic\\Atomic;\nuse Waterhole\\Api\\Collections;\nuse Waterhole\\Api\\Resources;\nuse Waterhole\\Extend\\Api\\JsonApi as JsonApiExtender;\n\nclass ApiServiceProvider extends ServiceProvider\n{\n    public function register(): void\n    {\n        $this->app->bind(JsonApiExtender::class, function ($container) {\n            $api = new JsonApiExtender(route('waterhole.api.main'));\n\n            $api->extension(new Atomic());\n\n            $api->resource($container->make(Resources\\ChannelsResource::class));\n            $api->resource($container->make(Resources\\ChannelUsersResource::class));\n            $api->resource($container->make(Resources\\CommentsResource::class));\n            $api->resource($container->make(Resources\\GroupsResource::class));\n            $api->resource($container->make(Resources\\PagesResource::class));\n            $api->resource($container->make(Resources\\PostsResource::class));\n            $api->resource($container->make(Resources\\PostUsersResource::class));\n            $api->resource($container->make(Resources\\ReactionSetsResource::class));\n            $api->resource($container->make(Resources\\ReactionsResource::class));\n            $api->resource($container->make(Resources\\ReactionCountsResource::class));\n            $api->resource($container->make(Resources\\ReactionTypesResource::class));\n            $api->resource($container->make(Resources\\StructureResource::class));\n            $api->resource($container->make(Resources\\StructureHeadingsResource::class));\n            $api->resource($container->make(Resources\\StructureLinksResource::class));\n            $api->resource($container->make(Resources\\TagsResource::class));\n            $api->resource($container->make(Resources\\TaxonomiesResource::class));\n            $api->resource($container->make(Resources\\UsersResource::class));\n\n            $api->collection($container->make(Collections\\StructureContentCollection::class));\n\n            return $api;\n        });\n\n        $this->app->alias(JsonApiExtender::class, 'waterhole.json-api');\n    }\n}\n"
  },
  {
    "path": "src/Providers/AppServiceProvider.php",
    "content": "<?php\n\nnamespace Waterhole\\Providers;\n\nuse BladeUI\\Icons\\Factory;\nuse Illuminate\\Database\\Eloquent\\Relations\\Relation;\nuse Illuminate\\Foundation\\Console\\AboutCommand;\nuse Illuminate\\Notifications\\ChannelManager;\nuse Illuminate\\Support\\ServiceProvider;\nuse Waterhole\\Models;\nuse Waterhole\\Notifications\\DatabaseChannel;\nuse Waterhole\\Waterhole;\n\nclass AppServiceProvider extends ServiceProvider\n{\n    protected array $configFiles = [\n        'api',\n        'auth',\n        'cp',\n        'design',\n        'forum',\n        'seo',\n        'system',\n        'uploads',\n        'users',\n    ];\n\n    public function register()\n    {\n        collect($this->configFiles)->each(function ($config) {\n            $this->mergeConfigFrom(__DIR__ . \"/../../config/$config.php\", \"waterhole.$config\");\n        });\n\n        $this->callAfterResolving(Factory::class, function (Factory $factory) {\n            $factory->add('waterhole', [\n                'path' => __DIR__ . '/../../resources/icons',\n                'prefix' => 'waterhole',\n            ]);\n        });\n    }\n\n    public function boot()\n    {\n        Relation::morphMap([\n            'channel' => Models\\Channel::class,\n            'comment' => Models\\Comment::class,\n            'group' => Models\\Group::class,\n            'page' => Models\\Page::class,\n            'post' => Models\\Post::class,\n            'structureHeading' => Models\\StructureHeading::class,\n            'structureLink' => Models\\StructureLink::class,\n            'taxonomy' => Models\\Taxonomy::class,\n            'user' => Models\\User::class,\n        ]);\n\n        $this->loadMigrationsFrom(__DIR__ . '/../../database/migrations');\n\n        collect($this->configFiles)->each(function ($config) {\n            $this->publishes(\n                [__DIR__ . \"/../../config/$config.php\" => config_path(\"waterhole/$config.php\")],\n                'waterhole-config',\n            );\n        });\n\n        // Override the notifications database channel with our own instance.\n        // This is necessary to extract special fields from the notification\n        // data array, and assign them to real columns in the database so that\n        // they can be used to power relationships.\n        resolve(ChannelManager::class)->extend('database', function () {\n            return new DatabaseChannel();\n        });\n\n        $this->addAboutCommandInfo();\n    }\n\n    private function addAboutCommandInfo(): void\n    {\n        AboutCommand::add('Environment', [\n            'Waterhole Version' => Waterhole::version(),\n        ]);\n    }\n}\n"
  },
  {
    "path": "src/Providers/AuthServiceProvider.php",
    "content": "<?php\n\nnamespace Waterhole\\Providers;\n\nuse Illuminate\\Auth\\Access\\Response;\nuse Illuminate\\Auth\\SessionGuard;\nuse Illuminate\\Foundation\\Support\\Providers\\AuthServiceProvider as ServiceProvider;\nuse Illuminate\\Support\\Facades\\Cache;\nuse Illuminate\\Support\\Facades\\Gate;\nuse Laravel\\Socialite\\Facades\\Socialite;\nuse Waterhole\\Auth\\Providers;\nuse Waterhole\\Auth\\SsoProvider;\nuse Waterhole\\Models\\Permission;\nuse Waterhole\\Models\\PermissionCollection;\nuse Waterhole\\Models\\User;\nuse Waterhole\\Policies;\nuse Waterhole\\Sso\\WaterholeSso;\nuse Waterhole\\Waterhole;\n\nclass AuthServiceProvider extends ServiceProvider\n{\n    public function register()\n    {\n        $this->app->singleton(\n            Providers::class,\n            fn() => new Providers(config('waterhole.auth.providers')),\n        );\n\n        $this->app->scoped(\n            'waterhole.permissions',\n            fn() => Cache::rememberForever('waterhole.permissions', fn() => Permission::all()),\n        );\n\n        $this->app->alias('waterhole.permissions', PermissionCollection::class);\n\n        $this->app->singleton(\n            WaterholeSso::class,\n            fn() => new WaterholeSso(config('waterhole.auth.sso.secret')),\n        );\n\n        // Make our policies singletons, to improve performance when we do a lot\n        // of auth checks in a single page load.\n        $this->app->singleton(Policies\\CommentPolicy::class);\n        $this->app->singleton(Policies\\PostPolicy::class);\n        $this->app->singleton(Policies\\UserPolicy::class);\n    }\n\n    public function boot()\n    {\n        SessionGuard::macro('logoutOnce', function () {\n            $this->user = null; // @phpstan-ignore-line\n            $this->loggedOut = true; // @phpstan-ignore-line\n        });\n\n        Socialite::extend(\n            'sso',\n            fn() => $this->app->make(SsoProvider::class, [\n                'url' => config('waterhole.auth.sso.url'),\n            ]),\n        );\n\n        Gate::before(function (null|object $user, $ability, $arguments) {\n            if (!str_starts_with($ability, 'waterhole.') || !$user instanceof User) {\n                return null;\n            }\n\n            // Treat users who haven't verified their email like guests.\n            if ($user->exists && !$user->hasVerifiedEmail()) {\n                return Gate::forUser(null)->allows($ability, $arguments) ?:\n                    Response::deny(__('waterhole::auth.email-verification-required-message'));\n            }\n\n            // Treat suspended users like guests.\n            if ($user->isSuspended()) {\n                return Gate::forUser(null)->allows($ability, $arguments) ?:\n                    Response::deny(__('waterhole::user.suspended-message'));\n            }\n        });\n\n        Gate::after(function (null|object $user, $ability, $result, $arguments) {\n            if (!str_starts_with($ability, 'waterhole.') || ($user && !$user instanceof User)) {\n                return null;\n            }\n\n            $ability = substr($ability, strlen('waterhole.'));\n            if ($result === null && strpos($ability, '.') && isset($arguments[0])) {\n                return Waterhole::permissions()->can(\n                    $user,\n                    explode('.', $ability)[1],\n                    $arguments[0],\n                );\n            }\n\n            // Allow administrators to perform all gated actions\n            // (unless explicitly prohibited by a policy).\n            if ($result === null && $user?->isAdmin()) {\n                return true;\n            }\n        });\n\n        // We don't want to register policies in the usual way because they are\n        // too restrictive - extensions wouldn't be able to add or override\n        // abilities. Instead, we define each ability absolutely.\n\n        Gate::define('waterhole.comment.create', [Policies\\CommentPolicy::class, 'create']);\n        Gate::define('waterhole.comment.edit', [Policies\\CommentPolicy::class, 'edit']);\n        Gate::define('waterhole.comment.delete', [Policies\\CommentPolicy::class, 'delete']);\n        Gate::define('waterhole.comment.restore', [Policies\\CommentPolicy::class, 'restore']);\n        Gate::define('waterhole.comment.moderate', [Policies\\CommentPolicy::class, 'moderate']);\n        Gate::define('waterhole.comment.react', [Policies\\CommentPolicy::class, 'react']);\n\n        Gate::define('waterhole.post.create', [Policies\\PostPolicy::class, 'create']);\n        Gate::define('waterhole.post.edit', [Policies\\PostPolicy::class, 'edit']);\n        Gate::define('waterhole.post.delete', [Policies\\PostPolicy::class, 'delete']);\n        Gate::define('waterhole.post.move', [Policies\\PostPolicy::class, 'move']);\n        Gate::define('waterhole.post.comment', [Policies\\PostPolicy::class, 'comment']);\n        Gate::define('waterhole.post.react', [Policies\\PostPolicy::class, 'react']);\n        Gate::define('waterhole.post.moderate', [Policies\\PostPolicy::class, 'moderate']);\n\n        Gate::define('waterhole.user.suspend', [Policies\\UserPolicy::class, 'suspend']);\n    }\n}\n"
  },
  {
    "path": "src/Providers/BroadcastServiceProvider.php",
    "content": "<?php\n\nnamespace Waterhole\\Providers;\n\nuse Illuminate\\Support\\Facades\\Broadcast;\nuse Illuminate\\Support\\ServiceProvider;\n\nclass BroadcastServiceProvider extends ServiceProvider\n{\n    public function boot()\n    {\n        Broadcast::routes();\n\n        require __DIR__ . '/../../routes/channels.php';\n    }\n}\n"
  },
  {
    "path": "src/Providers/ConsoleServiceProvider.php",
    "content": "<?php\n\nnamespace Waterhole\\Providers;\n\nuse Illuminate\\Console\\Application as Artisan;\nuse Illuminate\\Support\\ServiceProvider;\nuse Waterhole\\Console;\n\nclass ConsoleServiceProvider extends ServiceProvider\n{\n    protected array $commands = [\n        Console\\CacheClearCommand::class,\n        Console\\OpenApiCommand::class,\n        Console\\InstallCommand::class,\n        Console\\MakeExtensionCommand::class,\n        Console\\ReformatCommand::class,\n    ];\n\n    public function boot()\n    {\n        Artisan::starting(function ($artisan) {\n            $artisan->resolveCommands($this->commands);\n        });\n    }\n}\n"
  },
  {
    "path": "src/Providers/EventServiceProvider.php",
    "content": "<?php\n\nnamespace Waterhole\\Providers;\n\nuse Illuminate\\Auth\\Events\\Login;\nuse Illuminate\\Foundation\\Support\\Providers\\EventServiceProvider as ServiceProvider;\nuse Waterhole\\Extend\\Assets\\Script;\nuse Waterhole\\Extend\\Assets\\Stylesheet;\nuse Waterhole\\Listeners\\ReverifyInactiveUser;\n\nclass EventServiceProvider extends ServiceProvider\n{\n    protected $listen = [\n        'cache:clearing' => [[Script::class, 'flush'], [Stylesheet::class, 'flush']],\n        Login::class => [ReverifyInactiveUser::class],\n    ];\n}\n"
  },
  {
    "path": "src/Providers/FormatterServiceProvider.php",
    "content": "<?php\n\nnamespace Waterhole\\Providers;\n\nuse Illuminate\\Support\\ServiceProvider;\nuse s9e\\TextFormatter\\Configurator;\nuse s9e\\TextFormatter\\Renderer;\nuse Waterhole\\Formatter\\Context;\nuse Waterhole\\Formatter\\FormatExternalLinks;\nuse Waterhole\\Formatter\\FormatMentions;\nuse Waterhole\\Formatter\\Formatter;\nuse Waterhole\\Formatter\\FormatUploads;\nuse Waterhole\\Models\\Channel;\nuse Waterhole\\Models\\Comment;\nuse Waterhole\\Models\\Page;\nuse Waterhole\\Models\\Post;\nuse Waterhole\\Models\\User;\n\nclass FormatterServiceProvider extends ServiceProvider\n{\n    public function register()\n    {\n        $this->app->singleton('waterhole.formatter', function ($app) {\n            $formatter = new Formatter(\n                $app->make('files'),\n                $app->make('cache.store'),\n                'waterhole.formatter',\n            );\n\n            $formatter->configure(function (Configurator $config) {\n                $config->rootRules->enableAutoLineBreaks();\n                $config->urlConfig->allowScheme('mailto');\n                $config->urlConfig->allowScheme('upload');\n                $config->Escaper;\n                $config->Autoemail;\n                $config->Autolink;\n                $config->Litedown;\n                $config->PipeTables;\n                $config->TaskLists;\n                $config->Autovideo;\n                $config->Autoimage;\n\n                $this->configureEmoji($config);\n            });\n\n            $formatter->rendering(function (Renderer $renderer, string &$xml, ?Context $context) {\n                $renderer->setParameter('USER_ID', $context->user->id ?? null);\n            });\n\n            $formatter->configure([FormatExternalLinks::class, 'configure']);\n            $formatter->rendering([FormatExternalLinks::class, 'rendering']);\n\n            $formatter->configure([FormatMentions::class, 'configure']);\n            $formatter->rendering([FormatMentions::class, 'rendering']);\n\n            $formatter->configure([FormatUploads::class, 'configure']);\n            $formatter->rendering([FormatUploads::class, 'rendering']);\n\n            return $formatter;\n        });\n\n        $this->app->alias('waterhole.formatter', Formatter::class);\n\n        $this->app->singleton('waterhole.formatter.emoji', function ($app) {\n            $formatter = new Formatter(\n                $app->make('files'),\n                $app->make('cache.store'),\n                'waterhole.formatter.emoji',\n            );\n\n            $formatter->configure($this->configureEmoji(...));\n\n            return $formatter;\n        });\n    }\n\n    public function boot()\n    {\n        $formatter = $this->app->make('waterhole.formatter');\n\n        Comment::setFormatter('body', $formatter);\n        Page::setFormatter('body', $formatter);\n        Post::setFormatter('body', $formatter);\n        Channel::setFormatter('description', $formatter);\n        Channel::setFormatter('instructions', $formatter);\n        User::setFormatter('bio', $formatter);\n    }\n\n    private function configureEmoji(Configurator $config): void\n    {\n        // Allow the <mark> element for highlighting search results\n        $config->HTMLElements->allowElement('mark');\n\n        $tag = $config->Emoji->getTag();\n\n        if ($url = config('waterhole.design.emoji_url')) {\n            $tag->template = <<<html\n                <img alt=\"{.}\" class=\"emoji\" draggable=\"false\" src=\"$url\"/>\n            html;\n        } else {\n            $tag->template = '<span class=\"emoji\"><xsl:value-of select=\".\"/></span>';\n        }\n    }\n}\n"
  },
  {
    "path": "src/Providers/MailServiceProvider.php",
    "content": "<?php\n\nnamespace Waterhole\\Providers;\n\nuse Illuminate\\Support\\ServiceProvider;\nuse Waterhole\\Mail\\Markdown;\n\nclass MailServiceProvider extends ServiceProvider\n{\n    public function register(): void\n    {\n        $this->app->singleton(Markdown::class);\n    }\n}\n"
  },
  {
    "path": "src/Providers/RouteServiceProvider.php",
    "content": "<?php\n\nnamespace Waterhole\\Providers;\n\nuse Illuminate\\Cache\\RateLimiting\\Limit;\nuse Illuminate\\Foundation\\Support\\Providers\\RouteServiceProvider as ServiceProvider;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Support\\Facades\\RateLimiter;\nuse Illuminate\\Support\\Facades\\Route;\nuse Laravel\\Sanctum\\Http\\Middleware\\CheckAbilities;\n\nclass RouteServiceProvider extends ServiceProvider\n{\n    public function boot()\n    {\n        Route::aliasMiddleware('waterhole.auth', \\Waterhole\\Http\\Middleware\\Authenticate::class);\n\n        Route::aliasMiddleware(\n            'waterhole.guest',\n            \\Waterhole\\Http\\Middleware\\RedirectIfAuthenticated::class,\n        );\n\n        Route::aliasMiddleware(\n            'waterhole.confirm-password',\n            \\Waterhole\\Http\\Middleware\\MaybeRequirePassword::class,\n        );\n\n        Route::middlewareGroup('waterhole.web', [\n            \\Illuminate\\Cookie\\Middleware\\EncryptCookies::class,\n            \\Illuminate\\Cookie\\Middleware\\AddQueuedCookiesToResponse::class,\n            \\Illuminate\\Session\\Middleware\\StartSession::class,\n            \\Illuminate\\View\\Middleware\\ShareErrorsFromSession::class,\n            \\Illuminate\\Foundation\\Http\\Middleware\\VerifyCsrfToken::class,\n            \\Waterhole\\Http\\Middleware\\AuthenticateWaterhole::class,\n            \\Waterhole\\Http\\Middleware\\AuthGuard::class,\n            \\Illuminate\\Routing\\Middleware\\SubstituteBindings::class,\n            \\HotwiredLaravel\\TurboLaravel\\Http\\Middleware\\TurboMiddleware::class,\n            \\Waterhole\\Http\\Middleware\\ContactOutpost::class,\n            \\Waterhole\\Http\\Middleware\\ActorSeen::class,\n            \\Waterhole\\Http\\Middleware\\Localize::class,\n            \\Waterhole\\Http\\Middleware\\PoweredByHeader::class,\n        ]);\n\n        Route::middlewareGroup('waterhole.cp', [\n            'waterhole.auth',\n            \\Illuminate\\Auth\\Middleware\\Authorize::using('waterhole.administrate'),\n            \\Waterhole\\Http\\Middleware\\MaybeRequirePassword::class,\n        ]);\n\n        Route::middlewareGroup('waterhole.api', [\n            ...!config('waterhole.api.public', false)\n                ? ['waterhole.auth:sanctum', CheckAbilities::class . ':waterhole']\n                : [\\Waterhole\\Http\\Middleware\\AuthGuard::class . ':sanctum'],\n            \\Illuminate\\Routing\\Middleware\\ThrottleRequests::using('waterhole.api'),\n        ]);\n\n        $this->configureRateLimiting();\n\n        $this->routes(function () {\n            Route::middleware(['waterhole.web'])\n                ->name('waterhole.')\n                ->domain(config('waterhole.system.domain'))\n                ->prefix(config('waterhole.forum.path'))\n                ->group(__DIR__ . '/../../routes/forum.php');\n\n            Route::middleware(['waterhole.web', 'waterhole.cp'])\n                ->name('waterhole.cp.')\n                ->domain(config('waterhole.system.domain'))\n                ->prefix(config('waterhole.cp.path', 'cp'))\n                ->group(__DIR__ . '/../../routes/cp.php');\n\n            if (config('waterhole.api.enabled', true)) {\n                Route::middleware(['waterhole.api'])\n                    ->name('waterhole.api.')\n                    ->domain(config('waterhole.system.domain'))\n                    ->prefix(config('waterhole.api.path', 'api'))\n                    ->group(__DIR__ . '/../../routes/api.php');\n            }\n        });\n    }\n\n    protected function configureRateLimiting()\n    {\n        RateLimiter::for('waterhole.create', function (Request $request) {\n            if ($request->user()?->isAdmin() || !$request->input('commit')) {\n                return Limit::none();\n            }\n\n            return Limit::perMinute(config('waterhole.forum.create_per_minute', 3))->by(\n                $request->user()->id,\n            );\n        });\n\n        RateLimiter::for('waterhole.search', function (Request $request) {\n            if ($request->user()?->isAdmin() || !$request->input('q')) {\n                return Limit::none();\n            }\n\n            return Limit::perMinute(config('waterhole.forum.search_per_minute', 10))->by(\n                $request->user()?->id ?: $request->ip(),\n            );\n        });\n\n        RateLimiter::for('waterhole.api', function (Request $request) {\n            if ($request->user()?->isAdmin()) {\n                return Limit::none();\n            }\n\n            return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());\n        });\n    }\n}\n"
  },
  {
    "path": "src/Providers/SearchServiceProvider.php",
    "content": "<?php\n\nnamespace Waterhole\\Providers;\n\nuse Illuminate\\Support\\ServiceProvider;\nuse Waterhole\\Search\\EngineInterface;\nuse Waterhole\\Search\\FullTextSearchEngine;\n\nclass SearchServiceProvider extends ServiceProvider\n{\n    public function register()\n    {\n        $engine = config('waterhole.system.search_engine');\n\n        if ($engine === 'full_text') {\n            $this->app->bind(EngineInterface::class, FullTextSearchEngine::class);\n        }\n    }\n}\n"
  },
  {
    "path": "src/Providers/TranslationServiceProvider.php",
    "content": "<?php\n\nnamespace Waterhole\\Providers;\n\nuse Closure;\nuse Illuminate\\Contracts\\Translation\\Translator as TranslatorContract;\nuse Illuminate\\Foundation\\Application;\nuse Illuminate\\Support\\ServiceProvider;\nuse Waterhole\\Translation\\FluentTranslator;\nuse Waterhole\\Translation\\ValidationTranslator;\n\nclass TranslationServiceProvider extends ServiceProvider\n{\n    public function register()\n    {\n        $this->loadTranslationsFrom(__DIR__ . '/../../lang', 'waterhole');\n\n        // Extend the Laravel translator to load validation messages from\n        // the `waterhole` namespace if this is a Waterhole request. This way\n        // we can provide comprehensive translations in the Waterhole package\n        // without the user having to manually load them into their skeleton.\n        $this->app->extend('translator', function (TranslatorContract $translator) {\n            return new ValidationTranslator($translator);\n        });\n\n        // On top of that, extend the translator to support loading Fluent\n        // translations.\n        $this->app->extend(\n            'translator',\n            fn(TranslatorContract $translator, Application $app) => new FluentTranslator(\n                baseTranslator: $translator,\n                files: $app['files'],\n                path: $app['path.lang'],\n                locale: $app->getLocale(),\n                fallback: $app->getFallbackLocale(),\n                bundleOptions: ['allowOverrides' => true],\n                cachePath: config('app.debug') ? null : $app->storagePath('waterhole/translations'),\n                functions: [\n                    'COMPACT_NUMBER' => Closure::fromCallable('Waterhole\\\\compact_number'),\n                ],\n            ),\n        );\n\n        $this->app->alias('translator', FluentTranslator::class);\n    }\n}\n"
  },
  {
    "path": "src/Providers/ViewServiceProvider.php",
    "content": "<?php\n\nnamespace Waterhole\\Providers;\n\nuse Illuminate\\Support\\Facades\\Blade;\nuse Illuminate\\Support\\ServiceProvider;\n\nclass ViewServiceProvider extends ServiceProvider\n{\n    public function boot()\n    {\n        $this->loadViewsFrom(__DIR__ . '/../../resources/views', 'waterhole');\n\n        Blade::componentNamespace('Waterhole\\\\View\\\\Components', 'waterhole');\n\n        $this->registerComponentsDirective();\n    }\n\n    private function registerComponentsDirective(): void\n    {\n        /**\n         * The `@components` directive loops through an array of components and\n         * renders them, optionally passing in data. A component can be any of:\n         *\n         * - An `Illuminate\\View\\Component` instance\n         * - The name of a `Illuminate\\View\\Component` class\n         * - The name of an anonymous component view\n         * - `null`, in which case the directive will yield a section named\n         *   with the corresponding key\n         * - A closure that receives the data and returns any of the above\n         *\n         * If a component/view can't be found, and debug mode is on, a warning\n         * will be logged to the browser console.\n         */\n        Blade::directive('components', function (string $expression): string {\n            [$components, $data] = str_contains($expression, ',')\n                ? array_map('trim', explode(',', $expression, 2))\n                : [$expression, ''];\n\n            return implode(\"\\n\", [\n                '<?php $_components = ' . $components . '; ?>',\n                '<?php foreach (Waterhole\\build_components($_components, ' .\n                ($data ?: '[]') .\n                ') as $key => $instance): ?>',\n                '<?php if ($instance === null && !is_numeric($key) && $__env->hasSection($key)): ?>',\n                '<?php echo $__env->yieldContent($key); ?>',\n                '<?php endif; ?>',\n                '<?php if ($instance instanceof Illuminate\\View\\Component && $instance->shouldRender()): ?>',\n                '<?php $__env->startComponent($instance->resolveView(), $instance->data()); ?>',\n                '<?php echo $__env->renderComponent(); ?>',\n                '<?php elseif ($instance instanceof Illuminate\\Support\\HtmlString): ?>',\n                '<?php echo $instance; ?>',\n                '<?php endif; ?>',\n                '<?php endforeach; ?>',\n            ]);\n        });\n\n        Blade::directive('icon', function (string $expression) {\n            return '<?php echo Waterhole\\icon(' . $expression . '); ?>';\n        });\n\n        Blade::directive('return', function (string $expression): string {\n            return '<?php echo Waterhole\\return_field(' . $expression . '); ?>';\n        });\n    }\n}\n"
  },
  {
    "path": "src/Providers/WaterholeServiceProvider.php",
    "content": "<?php\n\nnamespace Waterhole\\Providers;\n\nuse Illuminate\\Support\\AggregateServiceProvider;\n\nclass WaterholeServiceProvider extends AggregateServiceProvider\n{\n    protected $providers = [\n        ApiServiceProvider::class,\n        AppServiceProvider::class,\n        AuthServiceProvider::class,\n        BroadcastServiceProvider::class,\n        ConsoleServiceProvider::class,\n        EventServiceProvider::class,\n        FormatterServiceProvider::class,\n        MailServiceProvider::class,\n        RouteServiceProvider::class,\n        SearchServiceProvider::class,\n        TranslationServiceProvider::class,\n        ViewServiceProvider::class,\n    ];\n}\n"
  },
  {
    "path": "src/Scopes/CommentIndexScope.php",
    "content": "<?php\n\nnamespace Waterhole\\Scopes;\n\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Model;\nuse Illuminate\\Database\\Eloquent\\Scope;\nuse Illuminate\\Support\\Facades\\DB;\n\n/**\n * Scope to calculate each comment's index (ie. how many comments came before it)\n * in the select clause.\n */\nclass CommentIndexScope implements Scope\n{\n    public function apply(Builder $builder, Model $model)\n    {\n        if ($builder->getQuery()->columns) {\n            return;\n        }\n\n        $builder\n            ->select($builder->qualifyColumn('*'))\n            ->selectSub(\n                DB::connection(config('waterhole.system.database'))\n                    ->table('comments', 'ci')\n                    ->selectRaw('count(*)')\n                    ->whereColumn('ci.post_id', $builder->qualifyColumn('post_id'))\n                    ->whereColumn('ci.id', '<', $builder->qualifyColumn('id')),\n                'index',\n            );\n    }\n}\n"
  },
  {
    "path": "src/Scopes/PermittedScope.php",
    "content": "<?php\n\nnamespace Waterhole\\Scopes;\n\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Model;\nuse Illuminate\\Database\\Eloquent\\Scope;\nuse Illuminate\\Support\\Facades\\Auth;\nuse Waterhole\\Models\\User;\n\n/**\n * Scope to restrict model visibility according to the permission system.\n *\n * Without any parameters, this scope will restrict query results to only those\n * models with IDs that the user (or their groups) has been granted permission\n * in the `permissions` table.\n *\n * The model to get IDs for, as well as the key in the current query that\n * corresponds to those IDs, can be customized so that for example, posts can\n * be filtered by their `channel_id`.\n */\nclass PermittedScope implements Scope\n{\n    public function __construct(\n        private ?string $model = null,\n        private string $key = 'id',\n        private string $ability = 'view',\n        private ?User $user = null,\n    ) {}\n\n    public function apply(Builder $builder, Model $model)\n    {\n        $user = $this->user ?: Auth::user();\n\n        if (app()->runningInConsole() && !app()->runningUnitTests() && !$user) {\n            return;\n        }\n\n        $model = $this->model ?: $model;\n\n        // If the list of IDs is null, then the user must be an administrator,\n        // and therefore there are no restrictions to apply.\n        if (!is_null($ids = $model::allPermitted($user, $this->ability))) {\n            $qualifier = $model instanceof Model ? $model : new $model();\n\n            $builder->whereIn($qualifier->qualifyColumn($this->key), $ids);\n        }\n    }\n}\n"
  },
  {
    "path": "src/Search/EngineInterface.php",
    "content": "<?php\n\nnamespace Waterhole\\Search;\n\ninterface EngineInterface\n{\n    public function search(\n        string $q,\n        int $limit,\n        int $offset = 0,\n        ?string $sort = null,\n        array $channelIds = [],\n        array $in = ['title', 'body', 'comments'],\n    ): Results;\n}\n"
  },
  {
    "path": "src/Search/FullTextSearchEngine.php",
    "content": "<?php\n\nnamespace Waterhole\\Search;\n\nuse Exception;\nuse Waterhole\\Models\\Channel;\nuse Waterhole\\Models\\Post;\nuse function Waterhole\\remove_formatting;\n\nclass FullTextSearchEngine implements EngineInterface\n{\n    public function search(\n        string $q,\n        int $limit,\n        int $offset = 0,\n        ?string $sort = null,\n        array $channelIds = [],\n        array $in = ['title', 'body', 'comments'],\n    ): Results {\n        // Build a query that finds posts that match the search query, or that\n        // contain comments that match the search query. This will form the\n        // basis of both the search results, and the channel hit breakdown.\n        $query = Post::where(function ($query) use ($in, $q) {\n            if (in_array('title', $in)) {\n                $query->orWhereFullText('posts.title', $q);\n            }\n            if (in_array('body', $in)) {\n                $query->orWhereFullText('posts.body', $q);\n            }\n            if (in_array('comments', $in)) {\n                $query->orWhereFullText('comments.body', $q);\n            }\n        });\n\n        if (in_array('comments', $in)) {\n            $query->leftJoin('comments', 'comments.post_id', '=', 'posts.id');\n        }\n\n        // Get a breakdown of each channel and how many hits were found within\n        // them. Even if we're filtering by certain channels, we still report\n        // the number of hits in all channels so that they can be displayed\n        // in the sidebar.\n        $connection = $query->getConnection();\n        $grammar = $query->getGrammar();\n        $isPgsql = $connection->getDriverName() === 'pgsql';\n\n        $channels = Channel::query()\n            ->select('id')\n            ->selectSub(\n                $query\n                    ->clone()\n                    ->selectRaw('count(distinct ' . $grammar->wrap('posts.id') . ')')\n                    ->whereColumn('posts.channel_id', 'channels.id'),\n                'hits',\n            )\n            ->get();\n\n        // Rather than running an additional COUNT query to get the total number\n        // of search results, we can sum together the channel breakdown numbers.\n        if ($channelIds) {\n            $query->whereIn('channel_id', $channelIds);\n            $total = $channels->find($channelIds)->sum('hits');\n        } else {\n            $total = $channels->sum('hits');\n        }\n\n        // Now it's time to set up the query to actually get the search result\n        // information we need. We will wrap a final \"outer\" query around this\n        // to ensure that we only get one result per post, even if it contains\n        // multiple relevant comments inside.\n        $query->select(['posts.id as post_id', 'posts.title', 'posts.body as post_body']);\n        $score = [];\n\n        if (in_array('title', $in)) {\n            $score[] = 'tscore';\n            $sql = $this->getScoreSql($isPgsql, $grammar->wrap('posts.title'));\n            $query->selectRaw(\"$sql * 10 as tscore\", [$q]);\n        }\n\n        if (in_array('body', $in)) {\n            $score[] = 'pscore';\n            $sql = $this->getScoreSql($isPgsql, $grammar->wrap('posts.body'));\n            $query->selectRaw(\"$sql as pscore\", [$q]);\n        }\n\n        if (in_array('comments', $in)) {\n            $score[] = 'cscore';\n            $sql = $this->getScoreSql($isPgsql, $grammar->wrap('posts.body'));\n            $query\n                ->addSelect('comments.id as comment_id')\n                ->addSelect('comments.body as comment_body')\n                ->selectRaw(\n                    'ROW_NUMBER() OVER (PARTITION BY ' .\n                        $grammar->wrap('posts.id') .\n                        \" ORDER BY $sql DESC) as r\",\n                    [$q],\n                )\n                ->selectRaw(\"$sql as cscore\", [$q]);\n        } else {\n            $query->selectRaw('1 as r');\n        }\n\n        switch ($sort) {\n            case 'latest':\n                $query->orderByDesc('posts.created_at');\n                break;\n\n            case 'top':\n                $query->orderByDesc('posts.score');\n                break;\n\n            default:\n                if ($score) {\n                    $query->orderByRaw(implode(' + ', $score) . ' desc');\n                }\n        }\n\n        // Finally we get the query results and map them into Hit instances.\n        // For each hit we highlight the relevant words and truncate the body\n        // to the most relevant part.\n        $highlighter = new Highlighter($q);\n\n        $hits = $connection\n            ->table($query, 'p')\n            ->where('r', 1)\n            ->take($limit)\n            ->skip($offset)\n            ->get()\n            ->map(function ($row) use ($highlighter) {\n                $title = $highlighter->highlight($row->title);\n\n                try {\n                    $body = $highlighter->highlight(\n                        $highlighter->truncate(\n                            remove_formatting(\n                                ($row->pscore ?? 1) >= ($row->cscore ?? 0)\n                                    ? $row->post_body\n                                    : $row->comment_body,\n                            ),\n                        ),\n                    );\n                } catch (Exception) {\n                    $body = '';\n                }\n\n                return new Hit($row->post_id, $title, $body);\n            });\n\n        if (!$isPgsql && $this->containsShortWords($q)) {\n            $error = __('waterhole::forum.search-keywords-too-short-message');\n        }\n\n        return new Results(\n            hits: $hits->all(),\n            total: $total,\n            exhaustiveTotal: true,\n            channelHits: $channels->pluck('hits', 'id')->toArray(),\n            error: $error ?? null,\n        );\n    }\n\n    private function containsShortWords(string $q): bool\n    {\n        preg_match_all('/\\w+/', $q, $matches);\n\n        return collect($matches[0])->some(fn($word) => strlen($word) < 3);\n    }\n\n    private function getScoreSql(bool $isPgsql, string $column): string\n    {\n        if ($isPgsql) {\n            $dictionary = match (config('app.locale')) {\n                'es' => 'spanish',\n                'fr' => 'french',\n                'de' => 'german',\n                'it' => 'italian',\n                'pt', 'pt-br' => 'portuguese',\n                'nl' => 'dutch',\n                'ru' => 'russian',\n                default => 'english',\n            };\n\n            return \"ts_rank(to_tsvector('$dictionary', $column), plainto_tsquery('$dictionary', ?))\";\n        }\n\n        return \"MATCH ($column) AGAINST (?)\";\n    }\n}\n"
  },
  {
    "path": "src/Search/Highlighter.php",
    "content": "<?php\n\nnamespace Waterhole\\Search;\n\nuse Illuminate\\Support\\HtmlString;\nuse function Waterhole\\emojify;\n\nclass Highlighter\n{\n    private ?string $re;\n\n    public function __construct(string $q)\n    {\n        $this->re = $this->buildRegularExpression($q);\n    }\n\n    /**\n     * Highlight matching words in the text with `<mark>` tags.\n     */\n    public function highlight(string $text): HtmlString\n    {\n        if (!$this->re) {\n            return new HtmlString(e($text));\n        }\n\n        return new HtmlString(\n            emojify(\n                preg_replace_callback(\n                    $this->re,\n                    fn(array $matches) => \"<mark>$matches[0]</mark>\",\n                    $text,\n                ),\n            ),\n        );\n    }\n\n    /**\n     * Truncate text surrounding the first match.\n     */\n    public function truncate(string $text, int $chars = 100): string\n    {\n        $start = 0;\n\n        if ($this->re) {\n            preg_match($this->re, $text, $matches, PREG_OFFSET_CAPTURE);\n            if (isset($matches[0][1])) {\n                $start = max(0, $matches[0][1] - $chars);\n            }\n        }\n\n        if ($start > 0) {\n            $text = '...' . substr($text, strpos($text, ' ', $start) + 1);\n        }\n\n        if (strlen($text) > $chars * 2) {\n            $text = substr($text, 0, strrpos(substr($text, 0, $chars * 2), ' ')) . '...';\n        }\n\n        return $text;\n    }\n\n    private function buildRegularExpression(string $q): ?string\n    {\n        if (!trim($q)) {\n            return null;\n        }\n\n        preg_match_all('/\"[^\"]+\"|[\\w*]+/u', $q, $phrases);\n\n        $phrases = array_map(function ($phrase) {\n            $phrase = preg_replace('/^\"|\"$/', '', $phrase);\n            $phrase = preg_quote($phrase, '/');\n            $phrase = preg_replace('/\\s+/', '\\\\W+', $phrase);\n            $phrase = preg_replace('/\\\\*/', '\\\\w+', $phrase);\n\n            return $phrase;\n        }, $phrases[0]);\n\n        return '/' . implode('|', $phrases) . '/iu';\n    }\n}\n"
  },
  {
    "path": "src/Search/Hit.php",
    "content": "<?php\n\nnamespace Waterhole\\Search;\n\nuse Illuminate\\Support\\HtmlString;\nuse Waterhole\\Models\\Post;\n\nclass Hit\n{\n    public ?Post $post;\n\n    public function __construct(\n        public int $postId,\n        public HtmlString $title,\n        public HtmlString $body,\n    ) {}\n}\n"
  },
  {
    "path": "src/Search/Results.php",
    "content": "<?php\n\nnamespace Waterhole\\Search;\n\nclass Results\n{\n    public function __construct(\n        public array $hits,\n        public ?int $total = null,\n        public bool $exhaustiveTotal = false,\n        public array $channelHits = [],\n        public ?string $error = null,\n    ) {}\n}\n"
  },
  {
    "path": "src/Search/Searcher.php",
    "content": "<?php\n\nnamespace Waterhole\\Search;\n\nuse Waterhole\\Extend\\Query\\PostFeedQuery;\nuse Waterhole\\Models\\Post;\n\nclass Searcher\n{\n    public function __construct(protected EngineInterface $engine) {}\n\n    public function search(\n        string $q,\n        int $limit,\n        int $offset = 0,\n        ?string $sort = null,\n        array $channelIds = [],\n        array $in = ['title', 'body', 'comments'],\n    ): Results {\n        $results = $this->engine->search(\n            q: $q,\n            limit: $limit,\n            offset: $offset,\n            sort: $sort,\n            channelIds: $channelIds,\n            in: $in,\n        );\n\n        // The engine has given us a Results object with an array of Hits,\n        // each of which contain a post ID and highlighted title/body text. So\n        // we still need to retrieve and set the Post model for each hit.\n        $query = Post::whereIn('id', collect($results->hits)->map->postId);\n\n        foreach (resolve(PostFeedQuery::class)->values() as $scope) {\n            $scope($query);\n        }\n\n        $postsById = $query->get()->keyBy('id');\n\n        foreach ($results->hits as $k => $hit) {\n            if ($hit->post = $postsById[$hit->postId] ?? null) {\n                $hit->post->title = $hit->title;\n            } else {\n                unset($results->hits[$k]);\n            }\n        }\n\n        return $results;\n    }\n}\n"
  },
  {
    "path": "src/Translation/FluentTranslator.php",
    "content": "<?php\n\nnamespace Waterhole\\Translation;\n\nuse Countable;\nuse Illuminate\\Contracts\\Translation\\Loader;\nuse Illuminate\\Contracts\\Translation\\Translator as TranslatorContract;\nuse Illuminate\\Filesystem\\Filesystem;\nuse Illuminate\\Support\\Traits\\ForwardsCalls;\nuse Illuminate\\Translation\\MessageSelector;\nuse Major\\Fluent\\Bundle\\FluentBundle;\nuse Major\\Fluent\\Node\\Syntax\\FluentResource;\nuse Major\\Fluent\\Parser\\FluentParser;\n\n/**\n * Translator decorator that adds support for Fluent translations.\n *\n * This class is adapted from the `laravel-fluent` package. This version of the\n * class adds support for:\n *\n * - Loading namespaced translations from .ftl files, and allowing them to be\n *   overridden by the consumer.\n * - Adding functions to FluentBundle instances.\n * - Accept an array of keys and use the first one that exists.\n * - Cache parsed Fluent bundles.\n *\n * @link https://github.com/jrmajor/laravel-fluent/pull/2\n *\n * @author Jeremiah Major\n * @license MIT\n */\nfinal class FluentTranslator implements TranslatorContract\n{\n    use ForwardsCalls;\n\n    /** @var array<string, array<string, FluentBundle|false>> */\n    private array $loaded = [];\n\n    public function __construct(\n        protected TranslatorContract $baseTranslator,\n        protected Filesystem $files,\n        protected string $path,\n        protected string $locale,\n        protected string $fallback,\n        /** @var array{strict: bool, useIsolating: bool, allowOverrides: bool} */\n        protected array $bundleOptions,\n        protected ?string $cachePath = null,\n        protected array $functions = [],\n    ) {}\n\n    public function hasForLocale(string $key, ?string $locale = null): bool\n    {\n        return $this->has($key, $locale, false);\n    }\n\n    public function has(string $key, ?string $locale = null, bool $fallback = true): bool\n    {\n        return $this->get($key, [], $locale, $fallback) !== $key;\n    }\n\n    /**\n     * @param  string|array  $key\n     * @param  array<string, mixed>  $replace\n     * @param  ?string  $locale\n     * @return string|array<string, mixed>\n     */\n    public function get(\n        $key,\n        array $replace = [],\n        $locale = null,\n        bool $fallback = true,\n    ): string|array {\n        $locale ??= $this->locale;\n        $keys = (array) $key;\n\n        foreach ($keys as $k) {\n            if (!$k) {\n                continue;\n            }\n\n            if (str_contains($k, '.')) {\n                [$namespace, $group, $item] = $this->parseKey($k);\n\n                $message = $this->getBundle($namespace, $locale, $group)?->message($item, $replace);\n\n                if ($fallback && $this->fallback !== $locale) {\n                    $message ??= $this->getBundle($namespace, $this->fallback, $group)?->message(\n                        $item,\n                        $replace,\n                    );\n                }\n\n                if ($message) {\n                    return $message;\n                }\n            }\n\n            if ($this->baseTranslator->has($k, $locale, $fallback)) {\n                return $this->baseTranslator->get($k, $replace, $locale, $fallback);\n            }\n        }\n\n        return last($keys);\n    }\n\n    private function getBundle(?string $namespace, string $locale, string $group): ?FluentBundle\n    {\n        return $this->loaded[$namespace][$locale][$group] ??\n            $this->loadFtl($namespace, $locale, $group) ?:\n            null;\n    }\n\n    private function loadFtl(?string $namespace, string $locale, string $group): ?FluentBundle\n    {\n        if (is_null($namespace) || $namespace === '*') {\n            $bundle = $this->loadPath($this->path, $locale, $group);\n        } else {\n            $bundle = $this->loadNamespaced($locale, $group, $namespace);\n        }\n\n        return ($this->loaded[$namespace][$locale][$group] = $bundle) ?: null;\n    }\n\n    protected function loadPath(string $path, string $locale, string $group): FluentBundle|false\n    {\n        $full = \"{$path}/{$locale}/{$group}.ftl\";\n\n        $getBody = function () use ($full) {\n            if (!$this->files->exists($full)) {\n                return null;\n            }\n\n            $parser = new FluentParser(strict: true);\n\n            return $parser->parse($this->files->get($full))->body;\n        };\n\n        $cacheFile = $this->cachePath ? $this->cachePath . '/' . sha1($full) : null;\n\n        if ($cacheFile && $this->files->exists($cacheFile)) {\n            $body = unserialize(file_get_contents($cacheFile));\n        } else {\n            $body = $getBody();\n            if ($cacheFile) {\n                $this->files->ensureDirectoryExists($this->cachePath);\n                $this->files->put($cacheFile, serialize($body));\n            }\n        }\n\n        if (!$body) {\n            return false;\n        }\n\n        $bundle = new FluentBundle($locale, ...$this->bundleOptions);\n\n        foreach ($this->functions as $name => $function) {\n            $bundle->addFunction($name, $function);\n        }\n\n        return $bundle->addResource(new FluentResource($body));\n    }\n\n    public function flush(): void\n    {\n        if ($this->cachePath) {\n            foreach ($this->files->glob(\"{$this->cachePath}/*\") as $file) {\n                $this->files->delete($file);\n            }\n        }\n    }\n\n    protected function loadNamespaced(\n        string $locale,\n        string $group,\n        string $namespace,\n    ): FluentBundle|false {\n        $hints = $this->getLoader()->namespaces();\n\n        if (isset($hints[$namespace])) {\n            if ($bundle = $this->loadPath($hints[$namespace], $locale, $group)) {\n                if ($this->bundleOptions['allowOverrides'] ?? false) {\n                    return $this->loadNamespaceOverrides($bundle, $locale, $group, $namespace);\n                }\n\n                return $bundle;\n            }\n\n            if ($this->bundleOptions['allowOverrides'] ?? false) {\n                return $this->loadPath(\"{$this->path}/vendor/{$namespace}\", $locale, $group);\n            }\n        }\n\n        return false;\n    }\n\n    protected function loadNamespaceOverrides(\n        FluentBundle $bundle,\n        $locale,\n        $group,\n        $namespace,\n    ): FluentBundle {\n        if (\n            $this->files->exists($full = \"{$this->path}/vendor/{$namespace}/{$locale}/{$group}.ftl\")\n        ) {\n            return $bundle->addFtl($this->files->get($full));\n        }\n\n        return $bundle;\n    }\n\n    /**\n     * @param  string  $key\n     * @param  Countable|int|array<mixed, mixed>  $number\n     * @param  array<string, mixed>  $replace\n     * @param  ?string  $locale\n     * @return string\n     */\n    public function choice($key, $number, array $replace = [], $locale = null)\n    {\n        return $this->baseTranslator->choice(...func_get_args());\n    }\n\n    /**\n     * @param  array<mixed, mixed>  $lines\n     */\n    public function addLines(array $lines, string $locale, string $namespace = '*'): void\n    {\n        $this->baseTranslator->addLines(...func_get_args());\n    }\n\n    public function load(string $namespace, string $group, string $locale): void\n    {\n        $this->baseTranslator->load($namespace, $group, $locale);\n    }\n\n    public function addNamespace(string $namespace, string $hint): void\n    {\n        $this->baseTranslator->addNamespace($namespace, $hint);\n    }\n\n    public function addJsonPath(string $path): void\n    {\n        $this->baseTranslator->addJsonPath($path);\n    }\n\n    /**\n     * @return string[]\n     */\n    public function parseKey(string $key): array\n    {\n        return $this->baseTranslator->parseKey($key);\n    }\n\n    public function getSelector(): MessageSelector\n    {\n        return $this->baseTranslator->getSelector();\n    }\n\n    public function setSelector(MessageSelector $selector): void\n    {\n        $this->baseTranslator->setSelector($selector);\n    }\n\n    public function getLoader(): Loader\n    {\n        return $this->baseTranslator->getLoader();\n    }\n\n    public function locale(): string\n    {\n        return $this->getLocale();\n    }\n\n    public function getLocale(): string\n    {\n        return $this->locale;\n    }\n\n    /**\n     * @param  string  $locale\n     */\n    public function setLocale($locale): void\n    {\n        $this->locale = $locale;\n        $this->baseTranslator->setLocale($locale);\n    }\n\n    public function getFallback(): string\n    {\n        return $this->fallback;\n    }\n\n    public function setFallback(string $locale): void\n    {\n        $this->fallback = $locale;\n        $this->baseTranslator->setFallback($locale);\n    }\n\n    public function __call($method, $parameters)\n    {\n        return $this->forwardCallTo($this->baseTranslator, $method, $parameters);\n    }\n}\n"
  },
  {
    "path": "src/Translation/ValidationTranslator.php",
    "content": "<?php\n\nnamespace Waterhole\\Translation;\n\nuse Illuminate\\Contracts\\Translation\\Translator as TranslatorContract;\nuse Illuminate\\Support\\Traits\\ForwardsCalls;\nuse Waterhole\\Waterhole;\n\nfinal class ValidationTranslator implements TranslatorContract\n{\n    use ForwardsCalls;\n\n    public function __construct(protected TranslatorContract $baseTranslator) {}\n\n    public function parseKey($key): array\n    {\n        if (Waterhole::isWaterholeRoute() && str_starts_with($key, 'validation.')) {\n            $key = \"waterhole::$key\";\n        }\n\n        return $this->baseTranslator->parseKey($key);\n    }\n\n    public function get($key, array $replace = [], $locale = null)\n    {\n        return $this->baseTranslator->get($key, $replace, $locale);\n    }\n\n    public function choice($key, $number, array $replace = [], $locale = null)\n    {\n        return $this->baseTranslator->choice($key, $number, $replace, $locale);\n    }\n\n    public function getLocale()\n    {\n        return $this->baseTranslator->getLocale();\n    }\n\n    public function setLocale($locale)\n    {\n        $this->baseTranslator->setLocale($locale);\n    }\n\n    public function __call($method, $parameters)\n    {\n        return $this->forwardCallTo($this->baseTranslator, $method, $parameters);\n    }\n}\n"
  },
  {
    "path": "src/View/Components/ActionButton.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\nuse Waterhole\\Actions\\Action;\nuse Waterhole\\Extend\\Core\\Actions;\n\nclass ActionButton extends Component\n{\n    public ?string $actionable;\n    public ?Action $actionInstance;\n\n    public function __construct(\n        public $for,\n        public string $action,\n        public ?string $return = null,\n        public bool $icon = false,\n        public array $formAttributes = [],\n    ) {\n        $this->actionable = get_class($for);\n\n        $this->actionInstance = collect(resolve(Actions::class)->actionsFor([$for]))\n            ->filter(fn($a) => $a instanceof $action)\n            ->first();\n    }\n\n    public function shouldRender()\n    {\n        return (bool) $this->actionInstance;\n    }\n\n    public function render()\n    {\n        return $this->view('waterhole::components.action-button');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/ActionButtons.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\View\\Component;\nuse Waterhole\\Actions\\Action;\nuse Waterhole\\Extend\\Core\\Actions;\nuse Waterhole\\Models\\Model;\n\nclass ActionButtons extends Component\n{\n    public string $actionable;\n    public Collection $actions;\n\n    public function __construct(\n        public Model $for,\n        ?array $only = null,\n        ?array $exclude = null,\n        public ?int $limit = null,\n        public ?string $context = null,\n    ) {\n        $this->actionable = get_class($for);\n\n        $actions = collect(resolve(Actions::class)->actionsFor($for));\n\n        if (isset($only)) {\n            $actions = $actions->filter(fn($action) => in_array(get_class($action), $only));\n        }\n\n        if (isset($exclude)) {\n            $actions = $actions->reject(fn($action) => in_array(get_class($action), $exclude));\n        }\n\n        $models = collect([$for]);\n\n        $this->actions = $actions\n            ->filter(\n                fn($action) => !$action instanceof Action ||\n                    $action->shouldRender($models, $context),\n            )\n            ->values()\n            ->reject(fn($action, $i) => $action instanceof MenuDivider && $i === 0);\n    }\n\n    public function render()\n    {\n        return $this->view('waterhole::components.action-buttons');\n    }\n\n    public function shouldRender(): bool\n    {\n        return $this->actions->isNotEmpty();\n    }\n}\n"
  },
  {
    "path": "src/View/Components/ActionForm.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\Support\\Facades\\Auth;\nuse Illuminate\\View\\Component;\nuse Waterhole\\Actions\\React;\n\nclass ActionForm extends Component\n{\n    public bool $isAuthorized = false;\n\n    public function __construct(\n        public $for,\n        public ?string $action = null,\n        public ?string $return = null,\n    ) {\n        if ($user = Auth::user()) {\n            $this->isAuthorized = resolve(React::class)->authorize($user, $for);\n        }\n    }\n\n    public function render()\n    {\n        return $this->view('waterhole::components.action-form');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/ActionMenu.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\nuse Waterhole\\Extend\\Core\\Actions;\nuse Waterhole\\Models\\Model;\n\nclass ActionMenu extends Component\n{\n    public string $url;\n\n    public function __construct(\n        protected Model $for,\n        protected ?string $context = null,\n        public array $buttonAttributes = ['class' => 'btn btn--transparent btn--icon text-xs'],\n    ) {\n        $return = request('return', request()->fullUrl());\n\n        $this->url = route('waterhole.actions.menu', [\n            'actionable' => get_class($for),\n            'id' => $for->getKey(),\n            'context' => $context,\n            'return' => $return,\n        ]);\n    }\n\n    public function shouldRender(): bool\n    {\n        return resolve(Actions::class)->hasActions($this->for, context: $this->context);\n    }\n\n    public function render()\n    {\n        return $this->view('waterhole::components.action-menu');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/Alert.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\Contracts\\Support\\Htmlable;\nuse Illuminate\\View\\Component;\n\nclass Alert extends Component\n{\n    public const ICONS = [\n        'success' => 'tabler-check',\n        'warning' => 'tabler-alert-triangle',\n        'danger' => 'tabler-alert-circle',\n    ];\n\n    public function __construct(\n        public ?string $type = null,\n        public null|string|Htmlable $message = null,\n        public ?string $icon = null,\n        public bool $dismissible = false,\n    ) {\n        $this->icon = $icon ?? (static::ICONS[explode('-', $type)[0]] ?? null);\n    }\n\n    public function render()\n    {\n        return $this->view('waterhole::components.alert');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/Attribution.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse DateTimeInterface;\nuse Illuminate\\View\\Component;\nuse Waterhole\\Models\\User;\n\nclass Attribution extends Component\n{\n    public function __construct(\n        public ?User $user,\n        public ?DateTimeInterface $date = null,\n        public ?string $permalink = null,\n        public ?DateTimeInterface $editDate = null,\n    ) {}\n\n    public function render()\n    {\n        return $this->view('waterhole::components.attribution');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/AuthButtons.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\nuse Waterhole\\Auth\\Providers;\n\nclass AuthButtons extends Component\n{\n    public function __construct(public Providers $providers) {}\n\n    public function shouldRender(): bool\n    {\n        return (bool) $this->providers->all();\n    }\n\n    public function render(): string\n    {\n        return <<<'blade'\n            <div class=\"stack gap-sm\">\n                @foreach ($providers->all() as $provider => $config)\n                    <a\n                        href=\"{{ route('waterhole.sso.login', compact('provider')) }}\"\n                        class=\"btn auth-button\"\n                        data-provider=\"{{ $provider }}\"\n                    >\n                        @icon($config['icon'])\n                        {{ __('waterhole::auth.continue-with-provider', ['provider' => $config['name']]) }}\n                    </a>\n                @endforeach\n            </div>\n        blade;\n    }\n}\n"
  },
  {
    "path": "src/View/Components/Avatar.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\nuse Waterhole\\Models\\User;\n\nclass Avatar extends Component\n{\n    protected static array $colorCache = [];\n\n    public function __construct(public ?User $user = null) {}\n\n    public function render()\n    {\n        return $this->view('waterhole::components.avatar');\n    }\n\n    public function color(): string\n    {\n        if (!$this->user) {\n            return 'transparent';\n        }\n\n        $name = $this->user->name;\n\n        if (!isset(static::$colorCache[$name])) {\n            $len = strlen($name);\n            $hue = 0;\n            for ($i = 0; $i < $len; $i++) {\n                $hue += ord($name[$i]);\n            }\n\n            static::$colorCache[$name] = 'hsl(' . $hue % 360 . ' 50% 50% / 0.5)';\n        }\n\n        return static::$colorCache[$name];\n    }\n}\n"
  },
  {
    "path": "src/View/Components/Cancel.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\n\nclass Cancel extends Component\n{\n    public function __construct(public ?string $default = null) {}\n\n    public function render()\n    {\n        return $this->view('waterhole::components.cancel');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/ChannelLabel.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\nuse Waterhole\\Models\\Channel;\n\nclass ChannelLabel extends Component\n{\n    public function __construct(public ?Channel $channel, public bool $link = false) {}\n\n    public function render()\n    {\n        return $this->view('waterhole::components.channel-label');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/ChannelPicker.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\Support\\Facades\\Gate;\nuse Illuminate\\View\\Component;\nuse Waterhole\\Models\\Channel;\nuse Waterhole\\Models\\Structure;\nuse Waterhole\\Models\\StructureHeading;\nuse Waterhole\\Models\\StructureLink;\n\nclass ChannelPicker extends Component\n{\n    public Collection $items;\n    public ?Channel $selectedChannel;\n\n    public function __construct(\n        public string $name,\n        public ?string $value = null,\n        array $exclude = [],\n        bool $showLinks = false,\n    ) {\n        $this->items = Structure::with('content')\n            ->whereMorphedTo('content', Channel::class)\n            ->orWhereMorphedTo('content', StructureHeading::class)\n            ->when(\n                $showLinks,\n                fn($query) => $query->orWhereMorphedTo('content', StructureLink::class),\n            )\n            ->orderBy('position')\n            ->get()\n            ->toBase()\n            ->map->content->except($exclude)\n            ->filter(\n                fn($item) => !$item instanceof Channel ||\n                    Gate::allows('waterhole.channel.post', $item),\n            );\n\n        // Filter out headings with no items after them\n        $this->items = $this->items->filter(function ($item, $i) {\n            if ($item instanceof StructureHeading) {\n                return isset($this->items[$i + 1]) &&\n                    !($this->items[$i + 1] instanceof StructureHeading);\n            }\n            return true;\n        });\n\n        $this->selectedChannel = $this->items->first(\n            fn($item) => $item instanceof Channel && $item->id == $value,\n        );\n    }\n\n    public function render()\n    {\n        return $this->view('waterhole::components.channel-picker');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/CollapsibleNav.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\n\nclass CollapsibleNav extends Component\n{\n    public ?NavLink $activeComponent;\n\n    public function __construct(\n        public array $components,\n        public string $buttonClass = '',\n        public string $navClass = '',\n    ) {\n        $this->activeComponent = collect($components)->firstWhere(\n            fn($component) => $component instanceof NavLink && $component->isActive,\n        );\n    }\n\n    public function render()\n    {\n        return $this->view('waterhole::components.collapsible-nav');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/CommentAnswerBadge.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\nuse Waterhole\\Models\\Comment;\n\nclass CommentAnswerBadge extends Component\n{\n    public function __construct(public Comment $comment) {}\n\n    public function shouldRender(): bool\n    {\n        return $this->comment->post->channel->answerable && $this->comment->isAnswer();\n    }\n\n    public function render()\n    {\n        return $this->view('waterhole::components.comment-answer-badge');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/CommentFrame.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\nuse Waterhole\\Models\\Comment;\nuse Waterhole\\View\\Components\\Concerns\\Streamable;\n\nclass CommentFrame extends Component\n{\n    use Streamable;\n\n    public function __construct(\n        public Comment $comment,\n        public bool $withReplies = false,\n        public bool $lazy = false,\n        public bool $withStructuredData = false,\n    ) {}\n\n    public function render()\n    {\n        return $this->view('waterhole::components.comment-frame');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/CommentFull.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\nuse Waterhole\\Models\\Comment;\nuse Waterhole\\View\\Components\\Concerns\\Streamable;\n\nclass CommentFull extends Component\n{\n    use Streamable;\n\n    public function __construct(\n        public Comment $comment,\n        public bool $withReplies = false,\n        public bool|int $truncate = false,\n        public bool $withStructuredData = false,\n    ) {}\n\n    public function render()\n    {\n        return $this->view('waterhole::components.comment-full');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/CommentMarkAsAnswer.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\nuse Waterhole\\Models\\Comment;\n\nclass CommentMarkAsAnswer extends Component\n{\n    public function __construct(public Comment $comment) {}\n\n    public function shouldRender(): bool\n    {\n        return $this->comment->post->channel->answerable && !$this->comment->trashed();\n    }\n\n    public function render()\n    {\n        return $this->view('waterhole::components.comment-mark-as-answer');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/CommentReactions.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\nuse Waterhole\\Models\\Comment;\n\nclass CommentReactions extends Component\n{\n    public function __construct(public Comment $comment) {}\n\n    public function render()\n    {\n        return $this->view('waterhole::components.comment-reactions');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/CommentReplies.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\nuse Waterhole\\Models\\Comment;\n\nclass CommentReplies extends Component\n{\n    public function __construct(public Comment $comment, public bool $withReplies = false) {}\n\n    public function render()\n    {\n        return $this->view('waterhole::components.comment-replies');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/CommentReplyButton.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\Contracts\\View\\View;\nuse Illuminate\\Support\\Facades\\Auth;\nuse Illuminate\\View\\Component;\nuse Waterhole\\Models\\Comment;\nuse Waterhole\\Models\\User;\n\nclass CommentReplyButton extends Component\n{\n    public function __construct(public Comment $comment) {}\n\n    public function shouldRender(): bool\n    {\n        if ($this->comment->trashed()) {\n            return false;\n        }\n\n        // If the user is logged in, only render the reply button if they can\n        // post a comment. If they're a guest, only render the reply button if\n        // a normal user would be able to post a comment.\n        $user = Auth::user() ?: new User();\n\n        return $user->can('waterhole.post.comment', $this->comment->post);\n    }\n\n    public function render(): View\n    {\n        return $this->view('waterhole::components.comment-reply-button');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/CommentsLocked.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\nuse Waterhole\\Models\\Post;\nuse Waterhole\\View\\Components\\Concerns\\Streamable;\n\nclass CommentsLocked extends Component\n{\n    use Streamable;\n\n    public function __construct(public Post $post) {}\n\n    public function render()\n    {\n        return $this->view('waterhole::components.comments-locked');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/Composer.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\nuse Waterhole\\Models\\Comment;\nuse Waterhole\\Models\\Post;\nuse Waterhole\\View\\Components\\Concerns\\Streamable;\n\nclass Composer extends Component\n{\n    use Streamable;\n\n    public function __construct(public Post $post, public ?Comment $parent = null)\n    {\n        $this->parent = $parent?->exists ? $parent : null;\n    }\n\n    public function render()\n    {\n        return $this->view('waterhole::components.composer');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/Concerns/Streamable.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components\\Concerns;\n\nuse Illuminate\\Database\\Eloquent\\Model;\nuse ReflectionClass;\nuse function HotwiredLaravel\\TurboLaravel\\dom_id;\n\ntrait Streamable\n{\n    public function streamableClassName(): ?string\n    {\n        $name = (new ReflectionClass($this))->getShortName();\n        $properties = $this->extractPublicProperties();\n\n        if ($model = collect($properties)->first(fn($value) => $value instanceof Model)) {\n            return dom_id($model, $name);\n        }\n\n        return $name;\n    }\n\n    /**\n     * Automatically set the class attribute on the attribute bag.\n     */\n    public function data(): array\n    {\n        $this->setClassAttribute();\n\n        return parent::data();\n    }\n\n    /**\n     * Automatically set the class attribute on the attribute bag.\n     */\n    public function withAttributes(array $attributes): static\n    {\n        parent::withAttributes($attributes);\n\n        $this->setClassAttribute();\n\n        return $this;\n    }\n\n    private function setClassAttribute(): void\n    {\n        $this->attributes = $this->attributes ?: $this->newAttributeBag();\n\n        if ($class = $this->streamableClassName()) {\n            $this->attributes['class'] .= \" $class\";\n        }\n    }\n}\n"
  },
  {
    "path": "src/View/Components/Cp/ColorPicker.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components\\Cp;\n\nuse Illuminate\\View\\Component;\n\nclass ColorPicker extends Component\n{\n    public function __construct(\n        public ?string $name = null,\n        public ?string $id = null,\n        public ?string $value = null,\n    ) {\n        $this->value = '#' . ltrim($value, '#');\n    }\n\n    public function render()\n    {\n        return $this->view('waterhole::components.cp.color-picker');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/Cp/IconPicker.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components\\Cp;\n\nuse Illuminate\\View\\Component;\n\nclass IconPicker extends Component\n{\n    public ?string $type = null;\n    public ?string $content = null;\n\n    public function __construct(\n        public ?string $name = null,\n        public ?string $id = null,\n        public null|string|array $value = null,\n    ) {\n        if (is_array($value)) {\n            $this->type = $value['type'] ?? null;\n            $this->content = $value[$this->type] ?? null;\n        } elseif ($value) {\n            [$this->type, $this->content] = explode(':', $value, 2) + [null, null];\n        }\n    }\n\n    public function render()\n    {\n        return $this->view('waterhole::components.cp.icon-picker');\n    }\n\n    public static function validationRules(): array\n    {\n        return [\n            'icon' => ['array:type,emoji,svg,file'],\n            'icon.type' => ['nullable', 'in:emoji,svg,file'],\n            'icon.file' => ['nullable', 'image:allow_svg'],\n        ];\n    }\n}\n"
  },
  {
    "path": "src/View/Components/Cp/PermissionGrid.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components\\Cp;\n\nuse Illuminate\\View\\Component;\nuse Waterhole\\Models\\Model;\n\nclass PermissionGrid extends Component\n{\n    public function __construct(\n        public array $abilities,\n        public ?Model $scope,\n        public array $defaults = [],\n    ) {}\n\n    public function render()\n    {\n        return $this->view('waterhole::components.cp.permission-grid');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/Cp/StructureNode.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components\\Cp;\n\nuse Illuminate\\View\\Component;\nuse Waterhole\\Models\\Structure;\n\nclass StructureNode extends Component\n{\n    public function __construct(public Structure $node) {}\n\n    public function render()\n    {\n        return $this->view('waterhole::components.cp.structure-node');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/Cp/TagRow.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components\\Cp;\n\nuse Illuminate\\View\\Component;\nuse Waterhole\\Models\\Tag;\nuse Waterhole\\View\\Components\\Concerns\\Streamable;\n\nclass TagRow extends Component\n{\n    use Streamable;\n\n    public function __construct(public Tag $tag) {}\n\n    public function render(): string\n    {\n        return <<<'blade'\n            <li {{ $attributes->class('card__row row gap-sm') }}>\n                {{ Waterhole\\emojify($tag->name) }}\n\n                <x-waterhole::action-buttons\n                    class=\"push-end row -m-sm text-xs\"\n                    :for=\"$tag\"\n                    :limit=\"2\"\n                    context=\"cp\"\n                />\n            </li>\n        blade;\n    }\n}\n"
  },
  {
    "path": "src/View/Components/Cp/Version.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components\\Cp;\n\nuse Illuminate\\Contracts\\View\\View;\nuse Illuminate\\View\\Component;\n\nclass Version extends Component\n{\n    public function render(): View\n    {\n        return $this->view('waterhole::components.cp.version');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/Cp.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\n\nclass Cp extends Component\n{\n    public function __construct(public ?string $title = null) {}\n\n    public function render()\n    {\n        return $this->view('waterhole::components.cp');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/Dialog.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\n\nclass Dialog extends Component\n{\n    public function __construct(public ?string $title = null) {}\n\n    public function render()\n    {\n        return $this->view('waterhole::components.dialog');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/EmailVerification.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\Contracts\\Auth\\MustVerifyEmail;\nuse Illuminate\\View\\Component;\n\nclass EmailVerification extends Component\n{\n    public function shouldRender()\n    {\n        $user = auth()->user();\n\n        return $user instanceof MustVerifyEmail &&\n            !$user->hasVerifiedEmail() &&\n            !$user->originalUser();\n    }\n\n    public function render()\n    {\n        return $this->view('waterhole::components.email-verification');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/FeedFilters.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\View\\Component;\nuse Waterhole\\Feed\\Feed;\nuse Waterhole\\Filters\\Filter;\n\nclass FeedFilters extends Component\n{\n    public Collection $components;\n    public Collection $firstComponents;\n    public Collection $overflowComponents;\n    public NavLink $activeComponent;\n\n    public function __construct(public Feed $feed, public int $limit = 4)\n    {\n        $this->components = $feed->filters->map(\n            fn($filter) => (new NavLink(\n                label: $filter->label(),\n                href: $this->url($filter),\n                active: $feed->currentFilter === $filter,\n            ))->withAttributes(['class' => 'tab']),\n        );\n\n        $this->firstComponents = $this->components->take($limit);\n        $this->overflowComponents = $this->components->slice($limit);\n        $this->activeComponent = $this->components->first->isActive;\n    }\n\n    public function render()\n    {\n        return $this->view('waterhole::components.feed-filters');\n    }\n\n    public function url(Filter $filter): string\n    {\n        return request()->fullUrlWithQuery(['filter' => $filter->handle(), 'page' => null]);\n    }\n}\n"
  },
  {
    "path": "src/View/Components/FeedTopPeriod.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\nuse Waterhole\\Feed\\Feed;\nuse Waterhole\\Filters\\Top;\n\nclass FeedTopPeriod extends Component\n{\n    public ?array $periods = null;\n    public ?string $currentPeriod = null;\n\n    public function __construct(public Feed $feed)\n    {\n        $filter = $feed->currentFilter;\n\n        if ($filter instanceof Top) {\n            $this->periods = $filter::PERIODS;\n            $this->currentPeriod = $filter->currentPeriod();\n        }\n    }\n\n    public function shouldRender(): bool\n    {\n        return (bool) $this->periods;\n    }\n\n    public function render()\n    {\n        return $this->view('waterhole::components.feed-top-period');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/Field.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\n\nclass Field extends Component\n{\n    public function __construct(\n        public string $name,\n        public ?string $label = null,\n        public ?string $description = null,\n        public ?string $id = null,\n    ) {\n        $this->id ??= $name;\n    }\n\n    public function render()\n    {\n        return $this->view('waterhole::components.field');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/FlagContainer.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\Support\\Facades\\Auth;\nuse Illuminate\\View\\Component;\nuse Waterhole\\Models\\Model;\n\nclass FlagContainer extends Component\n{\n    public bool $canModerate;\n    public bool $showBanner;\n\n    public function __construct(public Model $subject, public bool $hide = false)\n    {\n        $this->canModerate = $subject->canModerate(Auth::user());\n\n        $this->showBanner = $this->canModerate\n            ? $subject->pendingFlags->isNotEmpty()\n            : !$subject->is_approved && !$this->hide;\n    }\n\n    public function render()\n    {\n        return $this->view('waterhole::components.flag-container');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/FlagSummary.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\Support\\Facades\\Lang;\nuse Illuminate\\Support\\Str;\nuse Illuminate\\View\\Component;\nuse Waterhole\\Models\\Model;\n\nclass FlagSummary extends Component\n{\n    public string $summary;\n\n    public function __construct(public Model $subject)\n    {\n        $flags = $this->subject->pendingFlags;\n        $total = $flags->count();\n\n        $this->summary = $flags\n            ->groupBy('reason')\n            ->sortByDesc(fn($group) => $group->count())\n            ->map(function ($group, $reason) use ($total) {\n                $label = Lang::has($key = \"waterhole::forum.report-reason-$reason-label\")\n                    ? __($key)\n                    : Str::headline($reason);\n                $count = $group->count();\n\n                if ($total <= 1) {\n                    return $label;\n                }\n\n                return sprintf('%s (%d)', $label, $count);\n            })\n            ->join(', ');\n    }\n\n    public function render()\n    {\n        return $this->summary;\n    }\n}\n"
  },
  {
    "path": "src/View/Components/FollowButton.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\Support\\Facades\\Auth;\nuse Illuminate\\View\\Component;\nuse ReflectionClass;\nuse Waterhole\\View\\Components\\Concerns\\Streamable;\n\nclass FollowButton extends Component\n{\n    use Streamable;\n\n    public $followable;\n    public string $localePrefix;\n\n    public function __construct($followable, public string $buttonClass = 'btn block')\n    {\n        $this->followable = $followable;\n        $this->localePrefix =\n            'waterhole::forum.' . strtolower((new ReflectionClass($followable))->getShortName());\n    }\n\n    public function shouldRender(): bool\n    {\n        return Auth::check();\n    }\n\n    public function render()\n    {\n        return $this->view('waterhole::components.follow-button');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/GroupBadge.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\nuse Waterhole\\Models\\Group;\n\nclass GroupBadge extends Component\n{\n    public function __construct(public Group $group) {}\n\n    public function render()\n    {\n        return $this->view('waterhole::components.group-badge');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/Header.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\n\nclass Header extends Component\n{\n    public function render()\n    {\n        return $this->view('waterhole::components.header');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/HeaderBreadcrumb.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\n\nclass HeaderBreadcrumb extends Component\n{\n    public function render()\n    {\n        return $this->view('waterhole::components.header-breadcrumb');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/HeaderGuest.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\Support\\Facades\\Auth;\nuse Illuminate\\View\\Component;\n\nclass HeaderGuest extends Component\n{\n    public function shouldRender()\n    {\n        return Auth::guest();\n    }\n\n    public function render()\n    {\n        return $this->view('waterhole::components.header-guest');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/HeaderModeration.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\nuse Waterhole\\Models\\Channel;\n\nclass HeaderModeration extends Component\n{\n    public function shouldRender()\n    {\n        return Channel::allPermitted(auth()->user(), 'moderate') !== [];\n    }\n\n    public function render()\n    {\n        return $this->view('waterhole::components.header-moderation');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/HeaderNotifications.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\Support\\Facades\\Auth;\nuse Illuminate\\View\\Component;\n\nclass HeaderNotifications extends Component\n{\n    public function shouldRender()\n    {\n        return Auth::check();\n    }\n\n    public function render()\n    {\n        return $this->view('waterhole::components.header-notifications');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/HeaderSearch.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\Support\\Facades\\Auth;\nuse Illuminate\\View\\Component;\nuse Waterhole\\Models\\Channel;\nuse Waterhole\\Waterhole;\n\nclass HeaderSearch extends Component\n{\n    public function shouldRender(): bool\n    {\n        if (!config('waterhole.system.search_engine')) {\n            return false;\n        }\n\n        return Auth::check() || Waterhole::permissions()->can(null, 'view', Channel::class);\n    }\n\n    public function render()\n    {\n        return $this->view('waterhole::components.header-search');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/HeaderTitle.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\n\nclass HeaderTitle extends Component\n{\n    public function render()\n    {\n        return $this->view('waterhole::components.header-title');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/HeaderUser.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\n\nclass HeaderUser extends Component\n{\n    public function render()\n    {\n        return $this->view('waterhole::components.header-user');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/Html.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\Support\\Facades\\Auth;\nuse Illuminate\\View\\Component;\n\nclass Html extends Component\n{\n    public array $payload;\n\n    public function __construct(\n        public ?string $title = null,\n        public ?string $titleSuffix = null,\n        public array $assets = [],\n        public array $seo = [],\n    ) {\n        $this->titleSuffix ??= config('waterhole.forum.name');\n\n        $this->payload = [\n            'userId' => Auth::id(),\n            'debug' => config('app.debug'),\n            'echoConfig' => config('waterhole.system.echo_config'),\n        ];\n    }\n\n    public function render()\n    {\n        return $this->view('waterhole::components.html');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/Index.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\nuse Waterhole\\Models\\Channel;\n\nclass Index extends Component\n{\n    public function __construct(public ?Channel $channel = null)\n    {\n        $this->channel = $channel?->exists ? $channel : null;\n    }\n\n    public function render()\n    {\n        return $this->view('waterhole::components.index');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/IndexCreatePost.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\Auth\\Access\\Response;\nuse Illuminate\\Support\\Facades\\Auth;\nuse Illuminate\\Support\\Facades\\Gate;\nuse Illuminate\\View\\Component;\nuse Waterhole\\Models\\Channel;\nuse Waterhole\\Models\\User;\n\nclass IndexCreatePost extends Component\n{\n    public bool|Response $response;\n\n    public function __construct(public ?Channel $channel = null)\n    {\n        $gate = Gate::forUser(Auth::user() ?: new User());\n\n        $this->response = $channel\n            ? $gate->inspect('waterhole.channel.post', $channel)\n            : $gate->inspect('waterhole.post.create');\n    }\n\n    public function render()\n    {\n        return $this->view('waterhole::components.index-create-post');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/IndexFooter.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\n\nclass IndexFooter extends Component\n{\n    public function render()\n    {\n        return $this->view('waterhole::components.index-footer');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/IndexFooterLanguage.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\nuse Waterhole\\Extend\\Assets\\Locales;\n\nclass IndexFooterLanguage extends Component\n{\n    public array $locales;\n    public string $currentLocale;\n\n    public function __construct()\n    {\n        $this->locales = resolve(Locales::class)->items();\n        $this->currentLocale = app()->getLocale();\n    }\n\n    public function shouldRender(): bool\n    {\n        return count($this->locales) > 1;\n    }\n\n    public function render()\n    {\n        return $this->view('waterhole::components.index-footer-language');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/IndexNav.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\View\\Component;\nuse Waterhole\\Models\\Channel;\nuse Waterhole\\Models\\Page;\nuse Waterhole\\Models\\Structure;\nuse Waterhole\\Models\\StructureHeading;\nuse Waterhole\\Models\\StructureLink;\n\nuse function Waterhole\\is_absolute_url;\n\nclass IndexNav extends Component\n{\n    public Collection $nav;\n\n    public function __construct()\n    {\n        $structure = Structure::query()\n            ->where('is_listed', true)\n            ->with('content')\n            ->orderBy('position')\n            ->get()\n            ->filter(fn(Structure $node) => $node->content);\n\n        $this->nav = collect([\n            new NavLink(\n                label: __('waterhole::forum.feed-link'),\n                icon: 'tabler-news',\n                route: 'waterhole.home',\n            ),\n            ...$structure\n                ->map(function (Structure $node) {\n                    if ($node->content instanceof StructureHeading) {\n                        return new NavHeading($node->content->name ?: '');\n                    } elseif ($node->content instanceof Channel) {\n                        return new NavLink(\n                            label: $node->content->name,\n                            icon: $node->content->icon,\n                            href: $node->content->url,\n                        );\n                    } elseif ($node->content instanceof StructureLink) {\n                        return (new NavLink(\n                            label: $node->content->name,\n                            icon: $node->content->icon,\n                            href: $node->content->href,\n                        ))->withAttributes(\n                            is_absolute_url($node->content->href) ? ['target' => '_blank'] : [],\n                        );\n                    } elseif ($node->content instanceof Page) {\n                        return new NavLink(\n                            label: $node->content->name,\n                            icon: $node->content->icon,\n                            href: $node->content->url,\n                        );\n                    }\n\n                    return null;\n                })\n                ->filter(),\n        ]);\n\n        // Filter out headings with no items after them\n        $this->nav = $this->nav->filter(function ($item, $i) {\n            if ($item instanceof NavHeading) {\n                return isset($this->nav[$i + 1]) && !($this->nav[$i + 1] instanceof NavHeading);\n            }\n\n            return true;\n        });\n\n        $this->nav->push(new IndexFooter());\n    }\n\n    public function render()\n    {\n        return $this->view('waterhole::components.index-nav');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/InfiniteScroll.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\Contracts\\Pagination\\CursorPaginator;\nuse Illuminate\\Contracts\\Pagination\\Paginator;\nuse Illuminate\\View\\Component;\n\nclass InfiniteScroll extends Component\n{\n    public function __construct(\n        public Paginator|CursorPaginator $paginator,\n        public bool $divider = false,\n        public bool $endless = false,\n    ) {}\n\n    public function render()\n    {\n        return $this->view('waterhole::components.infinite-scroll');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/Layout.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\n\nclass Layout extends Component\n{\n    public function __construct(\n        public ?string $title = null,\n        public array $assets = [],\n        public array $seo = [],\n    ) {}\n\n    public function render()\n    {\n        return $this->view('waterhole::components.layout');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/MenuDivider.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\Support\\HtmlString;\nuse Illuminate\\View\\Component;\n\nclass MenuDivider extends Component\n{\n    public function render()\n    {\n        return new HtmlString('<hr class=\"menu-divider\">');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/MenuItem.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\Support\\HtmlString;\nuse Illuminate\\View\\Component;\n\nclass MenuItem extends Component\n{\n    public function __construct(\n        public ?bool $active = null,\n        public ?string $icon = null,\n        public ?string $label = null,\n        public null|string|HtmlString $description = null,\n        public ?string $href = null,\n    ) {}\n\n    public function render()\n    {\n        return $this->view('waterhole::components.menu-item');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/ModerationBadge.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\nuse Waterhole\\Models\\Flag;\nuse Waterhole\\Models\\User;\nuse Waterhole\\View\\Components\\Concerns\\Streamable;\n\nclass ModerationBadge extends Component\n{\n    use Streamable;\n\n    public int $count;\n\n    public function __construct(public User $user)\n    {\n        $this->count = Flag::query()\n            ->visible($user)\n            ->pending()\n            ->select('subject_type', 'subject_id')\n            ->distinct()\n            ->count();\n    }\n\n    public function render()\n    {\n        return <<<'blade'\n            <span\n                {{ $attributes->class('badge bg-activity')->merge([\n                    'data-notifications-popup-target' => 'badge',\n                    'hidden' => !$count\n                ]) }}\n            >{{ $count }}</span>\n        blade;\n    }\n}\n"
  },
  {
    "path": "src/View/Components/NavHeading.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\n\nclass NavHeading extends Component\n{\n    public function __construct(public string $heading) {}\n\n    public function render()\n    {\n        return <<<'blade'\n            <h3 {{ $attributes->class('nav-heading') }}>{{ $heading }}</h3>\n        blade;\n    }\n}\n"
  },
  {
    "path": "src/View/Components/NavLink.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Closure;\nuse Illuminate\\Support\\Facades\\Request;\nuse Illuminate\\View\\Component;\n\nclass NavLink extends Component\n{\n    public bool $isActive;\n\n    public function __construct(\n        public string $label,\n        public ?string $icon = null,\n        public ?string $badge = null,\n        public ?string $route = null,\n        public ?string $href = null,\n        public bool|Closure|null $active = null,\n        public ?string $badgeClass = null,\n    ) {\n        $this->isActive = $this->isActive();\n    }\n\n    public function render()\n    {\n        return $this->view('waterhole::components.nav-link');\n    }\n\n    private function isActive(): bool\n    {\n        if (is_callable($this->active)) {\n            return ($this->active)();\n        }\n\n        if (isset($this->active)) {\n            return $this->active;\n        }\n\n        if ($this->route) {\n            return Request::routeIs($this->route);\n        } elseif ($this->href) {\n            return Request::fullUrlIs($this->href . '*');\n        }\n\n        return false;\n    }\n}\n"
  },
  {
    "path": "src/View/Components/Notification.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\nuse Waterhole\\Models\\Notification as NotificationModel;\n\nclass Notification extends Component\n{\n    public function __construct(public NotificationModel $notification) {}\n\n    public function shouldRender()\n    {\n        return $this->notification->template;\n    }\n\n    public function render()\n    {\n        return $this->view('waterhole::components.notification');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/NotificationAlert.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\nuse Waterhole\\Models\\Notification;\n\nclass NotificationAlert extends Component\n{\n    public function __construct(public Notification $notification) {}\n\n    public function render()\n    {\n        return <<<'blade'\n            <x-waterhole::alert class=\"alert--notification\" dismissible data-key=\"notification\">\n                <turbo-frame id=\"@domid($notification)\" src=\"{{ $notification->url }}\">\n                    <div class=\"spinner spinner--sm\"></div>\n                </turbo-frame>\n            </x-waterhole::alert>\n        blade;\n    }\n}\n"
  },
  {
    "path": "src/View/Components/NotificationsBadge.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\nuse Waterhole\\Models\\User;\nuse Waterhole\\View\\Components\\Concerns\\Streamable;\n\nclass NotificationsBadge extends Component\n{\n    use Streamable;\n\n    public int $count;\n\n    public function __construct(public User $user)\n    {\n        $this->count = $user->unread_notification_count;\n    }\n\n    public function render()\n    {\n        return <<<'blade'\n            <span\n                {{ $attributes->class('badge bg-activity')->merge([\n                    'data-notifications-popup-target' => 'badge',\n                    'hidden' => !$count\n                ]) }}\n            >{{ $count }}</span>\n        blade;\n    }\n}\n"
  },
  {
    "path": "src/View/Components/PinnedPost.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\nuse Waterhole\\Models\\Post;\nuse Waterhole\\View\\Components\\Concerns\\Streamable;\n\nclass PinnedPost extends Component\n{\n    use Streamable;\n\n    public function __construct(public Post $post) {}\n\n    public function render()\n    {\n        return $this->view('waterhole::components.pinned-post');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/PostActivity.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\nuse Waterhole\\Models\\Post;\n\nclass PostActivity extends Component\n{\n    public function __construct(public Post $post) {}\n\n    public function render()\n    {\n        return $this->view('waterhole::components.post-activity');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/PostAnswer.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\nuse Waterhole\\Models\\Post;\n\nclass PostAnswer extends Component\n{\n    public function __construct(public Post $post) {}\n\n    public function shouldRender(): bool\n    {\n        return $this->post->channel->answerable &&\n            $this->post->answer &&\n            $this->post->answer->index > 0;\n    }\n\n    public function render()\n    {\n        return $this->view('waterhole::components.post-answer');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/PostAnswered.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\nuse Waterhole\\Models\\Post;\n\nclass PostAnswered extends Component\n{\n    public function __construct(public Post $post) {}\n\n    public function shouldRender(): bool\n    {\n        return $this->post->channel->answerable && $this->post->answer_id;\n    }\n\n    public function render()\n    {\n        return $this->view('waterhole::components.post-answered');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/PostAttribution.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\nuse Waterhole\\Models\\Post;\n\nclass PostAttribution extends Component\n{\n    public function __construct(public Post $post) {}\n\n    public function render()\n    {\n        return $this->view('waterhole::components.post-attribution');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/PostCard.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\nuse Waterhole\\Layouts\\CardsLayout;\nuse Waterhole\\Models\\Post;\nuse Waterhole\\View\\Components\\Concerns\\Streamable;\n\nclass PostCard extends Component\n{\n    use Streamable;\n\n    public array $config;\n\n    public function __construct(public Post $post)\n    {\n        $this->config = $post->channel->layout_config[CardsLayout::class] ?? [];\n    }\n\n    public function render()\n    {\n        return $this->view('waterhole::components.post-card');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/PostChannel.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\nuse Waterhole\\Models\\Post;\n\nclass PostChannel extends Component\n{\n    public function __construct(public Post $post) {}\n\n    public function render()\n    {\n        return $this->view('waterhole::components.post-channel');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/PostFeed.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\Contracts\\Pagination\\CursorPaginator;\nuse Illuminate\\View\\Component;\nuse Waterhole\\Filters\\Latest;\nuse Waterhole\\Filters\\Newest;\nuse Waterhole\\Models\\Channel;\n\nclass PostFeed extends Component\n{\n    public bool $showLastVisit;\n    public CursorPaginator $posts;\n    public ?array $publicChannels;\n    public array $channels;\n\n    public function __construct(\n        public \\Waterhole\\Feed\\PostFeed $feed,\n        public ?Channel $channel = null,\n    ) {\n        $this->channel = $channel?->exists ? $channel : null;\n\n        $filter = $feed->currentFilter;\n        $this->showLastVisit = $filter instanceof Newest || $filter instanceof Latest;\n\n        $this->publicChannels = Channel::allPermitted(null);\n        $this->channels = $this->channel ? [$this->channel->id] : Channel::pluck('id')->all();\n        $this->posts = $feed->items()->withQueryString();\n    }\n\n    public function render()\n    {\n        return $this->view('waterhole::components.post-feed');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/PostFeedChannel.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\nuse Waterhole\\Models\\Channel;\nuse Waterhole\\View\\Components\\Concerns\\Streamable;\n\nclass PostFeedChannel extends Component\n{\n    use Streamable;\n\n    public function __construct(public ?Channel $channel = null) {}\n\n    public function shouldRender(): bool\n    {\n        return (bool) $this->channel;\n    }\n\n    public function render()\n    {\n        return $this->view('waterhole::components.post-feed-channel');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/PostFeedPinned.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\View\\Component;\nuse Waterhole\\Feed\\PostFeed;\nuse Waterhole\\Models\\Channel;\nuse Waterhole\\Models\\Post;\nuse Waterhole\\View\\Components\\Concerns\\Streamable;\n\nclass PostFeedPinned extends Component\n{\n    use Streamable;\n\n    public Collection $posts;\n\n    public function __construct(public PostFeed $feed, public ?Channel $channel = null)\n    {\n        $query = Post::withoutTrashed()->where('is_pinned', true)->whereNot->ignoring();\n\n        if ($channel) {\n            $query->whereBelongsTo($channel);\n        } else {\n            $query->whereDoesntHave('channel', fn($query) => $query->ignoring());\n        }\n\n        $query->with(['channel.userState', 'userState']);\n        $query->withUnreadCommentsCount();\n\n        $this->posts = $query->latest()->get();\n    }\n\n    public function shouldRender(): bool\n    {\n        return $this->posts->isNotEmpty();\n    }\n\n    public function render()\n    {\n        return $this->view('waterhole::components.post-feed-pinned');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/PostFeedToolbar.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\nuse Waterhole\\Feed\\PostFeed;\nuse Waterhole\\Models\\Channel;\n\nclass PostFeedToolbar extends Component\n{\n    public function __construct(public PostFeed $feed, public ?Channel $channel = null) {}\n\n    public function render()\n    {\n        return $this->view('waterhole::components.post-feed-toolbar');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/PostFull.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\nuse Waterhole\\Models\\Post;\nuse Waterhole\\View\\Components\\Concerns\\Streamable;\n\nclass PostFull extends Component\n{\n    use Streamable;\n\n    public function __construct(public Post $post) {}\n\n    public function render()\n    {\n        return $this->view('waterhole::components.post-full');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/PostListItem.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\Support\\HtmlString;\nuse Illuminate\\Support\\Str;\nuse Illuminate\\View\\Component;\nuse Waterhole\\Layouts\\ListLayout;\nuse Waterhole\\Models\\Post;\nuse Waterhole\\View\\Components\\Concerns\\Streamable;\n\nuse function Waterhole\\emojify;\n\nclass PostListItem extends Component\n{\n    use Streamable;\n\n    public array $config;\n    public HtmlString|string $title;\n\n    public function __construct(public Post $post, public string|HtmlString|null $excerpt = null)\n    {\n        $this->config = $post->channel->layout_config[ListLayout::class] ?? [];\n\n        if ($this->config['show_excerpt'] ?? false) {\n            $this->excerpt ??= emojify(Str::limit($post->body_text, 200));\n        }\n\n        $this->title = $post->title;\n\n        if (!$this->title instanceof HtmlString) {\n            $this->title = emojify($this->title);\n        }\n    }\n\n    public function render()\n    {\n        return $this->view('waterhole::components.post-list-item');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/PostLocked.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\nuse Waterhole\\Models\\Post;\n\nclass PostLocked extends Component\n{\n    public function __construct(public Post $post) {}\n\n    public function shouldRender()\n    {\n        return $this->post->is_locked;\n    }\n\n    public function render()\n    {\n        return $this->view('waterhole::components.post-locked');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/PostNotifications.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\nuse Waterhole\\Models\\Post;\n\nclass PostNotifications extends Component\n{\n    public function __construct(public Post $post) {}\n\n    public function shouldRender()\n    {\n        return $this->post->userState?->notifications;\n    }\n\n    public function render()\n    {\n        return $this->view('waterhole::components.post-notifications');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/PostReactions.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\nuse Waterhole\\Models\\Post;\n\nclass PostReactions extends Component\n{\n    public function __construct(public Post $post) {}\n\n    public function render()\n    {\n        return <<<'blade'\n            <x-waterhole::reactions :model=\"$post\"/>\n        blade;\n    }\n}\n"
  },
  {
    "path": "src/View/Components/PostReactionsCondensed.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\nuse Waterhole\\Models\\Post;\n\nclass PostReactionsCondensed extends Component\n{\n    public function __construct(public Post $post) {}\n\n    public function render()\n    {\n        return <<<'blade'\n            <x-waterhole::reactions-condensed :model=\"$post\"/>\n        blade;\n    }\n}\n"
  },
  {
    "path": "src/View/Components/PostReplies.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\nuse Waterhole\\Models\\Post;\n\nclass PostReplies extends Component\n{\n    public function __construct(public Post $post) {}\n\n    public function render()\n    {\n        return $this->view('waterhole::components.post-replies');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/PostSidebar.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\Auth\\Access\\Response;\nuse Illuminate\\Support\\Facades\\Auth;\nuse Illuminate\\Support\\Facades\\Gate;\nuse Illuminate\\Support\\Facades\\Route;\nuse Illuminate\\View\\Component;\nuse Waterhole\\Models\\Post;\nuse Waterhole\\Models\\User;\nuse Waterhole\\View\\Components\\Concerns\\Streamable;\n\nclass PostSidebar extends Component\n{\n    use Streamable;\n\n    public bool|Response $response;\n\n    public function __construct(public Post $post)\n    {\n        $gate = Gate::forUser(Auth::user() ?: (Route::has('waterhole.login') ? new User() : null));\n\n        $this->response = $gate->inspect('waterhole.post.comment', $post);\n    }\n\n    public function render()\n    {\n        return $this->view('waterhole::components.post-sidebar');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/PostTagsSummary.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\View\\Component;\nuse Waterhole\\Models\\Post;\n\nclass PostTagsSummary extends Component\n{\n    public Collection $tags;\n    public int $limit = 3;\n\n    public function __construct(public Post $post)\n    {\n        $this->tags = $post->tags->sortBy('taxonomy_id');\n    }\n\n    public function shouldRender(): bool\n    {\n        return $this->tags->isNotEmpty();\n    }\n\n    public function render(): string\n    {\n        return <<<'blade'\n            <div class=\"post-tags-summary row gap-xxs\">\n                @foreach ($tags->take($limit) as $tag)\n                    <a\n                        href=\"{{ route('waterhole.channels.show', ['channel' => $post->channel]) . '?'. Arr::query(['tags' => [$tag->taxonomy_id => $tag->id]]) }}\"\n                        class=\"tag\"\n                        data-tag-id=\"{{ $tag->id }}\"\n                    >{{ Waterhole\\emojify($tag->name) }}</a>\n                @endforeach\n                @if ($tags->count() > $limit)\n                    <span class=\"cursor-default\">\n                        +{{ $tags->count() - $limit }}\n                        <ui-tooltip>\n                            @foreach ($tags->slice($limit) as $tag)\n                                <span data-tag-id=\"{{ $tag->id }}\">\n                                    {{ Waterhole\\emojify($tag->name) }}\n                                </span>\n                            @endforeach\n                        </ui-tooltip>\n                    </span>\n                @endif\n            </div>\n        blade;\n    }\n}\n"
  },
  {
    "path": "src/View/Components/PostTitle.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\nuse Waterhole\\Models\\Post;\n\nclass PostTitle extends Component\n{\n    public function __construct(public Post $post) {}\n\n    public function render()\n    {\n        return $this->view('waterhole::components.post-title');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/PostTrash.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\nuse Waterhole\\Models\\Post;\n\nclass PostTrash extends Component\n{\n    public function __construct(public Post $post) {}\n\n    public function shouldRender(): bool\n    {\n        return $this->post->trashed();\n    }\n\n    public function render()\n    {\n        return $this->view('waterhole::components.post-trash');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/PostUnread.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\nuse Waterhole\\Models\\Post;\n\nclass PostUnread extends Component\n{\n    public bool $isNotifiable;\n\n    public function __construct(public Post $post)\n    {\n        $this->isNotifiable =\n            $post->isFollowed() ||\n            (!$post->isIgnored() &&\n                $post->userState?->mentioned_at > $post->userState?->last_read_at) ||\n            ($post->channel->isFollowed() &&\n                $post->last_activity_at > $post->channel->userState->followed_at &&\n                !$post->userState->last_read_at);\n    }\n\n    public function shouldRender()\n    {\n        return $this->post->isUnread() &&\n            (!$this->post->isNew() || $this->isNotifiable) &&\n            $this->post->unread_comments_count;\n    }\n\n    public function render()\n    {\n        return $this->view('waterhole::components.post-unread');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/ReactionSetPicker.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Closure;\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\View\\Component;\nuse Waterhole\\Models\\ReactionSet;\n\nclass ReactionSetPicker extends Component\n{\n    public Collection $reactionSets;\n\n    public function __construct(\n        public ?string $value = null,\n        public ?ReactionSet $default = null,\n        public ?bool $enabled = null,\n        public ?int $selectedId = null,\n    ) {\n        $this->reactionSets = ReactionSet::all();\n        $this->value ??= $this->resolveValue();\n    }\n\n    public function render()\n    {\n        return $this->view('waterhole::components.reaction-set-picker');\n    }\n\n    public static function rule(): Closure\n    {\n        return function (string $attribute, mixed $value, Closure $fail): void {\n            if ($value === null || $value === '' || $value === 'default' || $value === 'none') {\n                return;\n            }\n\n            if (!ReactionSet::whereKey($value)->exists()) {\n                $fail(__('validation.exists', ['attribute' => $attribute]));\n            }\n        };\n    }\n\n    public static function resolveSelection(mixed $selection): array\n    {\n        $enabled = $selection !== 'none';\n        $reactionSetId = in_array($selection, [null, '', 'default', 'none'], true)\n            ? null\n            : $selection;\n\n        return [$enabled, $reactionSetId];\n    }\n\n    private function resolveValue(): ?string\n    {\n        if ($this->enabled === null) {\n            return $this->value;\n        }\n\n        if (!$this->enabled) {\n            return 'none';\n        }\n\n        return $this->selectedId ? (string) $this->selectedId : 'default';\n    }\n}\n"
  },
  {
    "path": "src/View/Components/Reactions.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\Support\\Facades\\Auth;\nuse Illuminate\\View\\Component;\nuse Waterhole\\Actions\\React;\nuse Waterhole\\Models\\Model;\nuse Waterhole\\Models\\ReactionSet;\nuse Waterhole\\Models\\ReactionType;\nuse Waterhole\\View\\Components\\Concerns\\Streamable;\n\nclass Reactions extends Component\n{\n    use Streamable;\n\n    public Model $model;\n    public ?ReactionSet $reactionSet;\n    public Collection $reactionTypes;\n\n    public function __construct(Model $model)\n    {\n        $this->model = $model;\n        $this->reactionSet = $model->reactionSet();\n        $this->reactionTypes = new Collection();\n\n        if (!$this->reactionSet?->exists) {\n            return;\n        }\n\n        $this->reactionTypes = $this->reactionSet->reactionTypes->sortByDesc(\n            $this->reactionCount(...),\n        );\n\n        if (count($this->reactionTypes) > 1) {\n            $this->reactionTypes = $this->reactionTypes->filter($this->reactionCount(...));\n        }\n    }\n\n    public function shouldRender(): bool\n    {\n        return $this->reactionSet?->exists &&\n            ($this->model->reactionCounts ||\n                resolve(React::class)->authorize(Auth::user(), $this->model));\n    }\n\n    public function reactionCount(ReactionType $reactionType): int\n    {\n        return $this->model->reactionCounts->find($reactionType->id)?->count ?? 0;\n    }\n\n    public function userReacted(ReactionType $reactionType): bool\n    {\n        return (bool) $this->model->reactionCounts->find($reactionType->id)?->user_reacted;\n    }\n\n    public function render()\n    {\n        return $this->view('waterhole::components.reactions');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/ReactionsCondensed.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nclass ReactionsCondensed extends Reactions\n{\n    public function shouldRender(): bool\n    {\n        return (bool) count($this->reactionTypes);\n    }\n\n    public function render()\n    {\n        if (count($this->reactionSet->reactionTypes) > 1) {\n            return $this->view('waterhole::components.reactions-condensed');\n        }\n\n        return '<x-waterhole::reactions :model=\"$model\"/>';\n    }\n}\n"
  },
  {
    "path": "src/View/Components/RelativeTime.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Carbon\\Carbon;\nuse DateTimeInterface;\nuse Illuminate\\View\\Component;\n\nclass RelativeTime extends Component\n{\n    public ?Carbon $dateTime;\n\n    public function __construct(?DateTimeInterface $datetime)\n    {\n        $this->dateTime = $datetime ? new Carbon($datetime) : null;\n    }\n\n    public function shouldRender(): bool\n    {\n        return (bool) $this->dateTime;\n    }\n\n    public function render()\n    {\n        return $this->view('waterhole::components.relative-time');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/RemovedBanner.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\nuse Waterhole\\Models\\Model;\n\nclass RemovedBanner extends Component\n{\n    public function __construct(public Model $subject) {}\n\n    public function render()\n    {\n        return $this->view('waterhole::components.removed-banner');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/Selector.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Closure;\nuse Illuminate\\View\\Component;\n\nclass Selector extends Component\n{\n    public function __construct(\n        public array $options,\n        public Closure $label,\n        public Closure $href,\n        public $value = null,\n        public string $buttonClass = '',\n        public ?string $placeholder = null,\n    ) {}\n\n    public function render()\n    {\n        return $this->view('waterhole::components.selector');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/Spacer.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\n\nclass Spacer extends Component\n{\n    public function render()\n    {\n        return $this->view('waterhole::components.spacer');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/Spinner.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\n\nclass Spinner extends Component\n{\n    public function render(): string\n    {\n        return <<<'blade'\n            <div {{ $attributes\n                ->class('spinner')\n                ->merge(['role' => 'status', 'aria-label' => __('waterhole::system.loading')]) }}></div>\n        blade;\n    }\n}\n"
  },
  {
    "path": "src/View/Components/TagsFilter.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\nuse Waterhole\\Feed\\PostFeed;\nuse Waterhole\\Models\\Channel;\nuse Waterhole\\Models\\Taxonomy;\n\nclass TagsFilter extends Component\n{\n    public int $value;\n\n    public function __construct(public PostFeed $feed, public ?Channel $channel)\n    {\n        $this->value = (int) request('tag_id');\n    }\n\n    public function shouldRender(): bool\n    {\n        return (bool) $this->channel?->taxonomies->isNotEmpty();\n    }\n\n    public function render(): string\n    {\n        return <<<'blade'\n            <div class=\"row gap-xxs wrap\">\n                @foreach ($channel->taxonomies->load('tags') as $taxonomy)\n                    @if ($id = request(\"tags.$taxonomy->id\"))\n                        <a href=\"{{ $href($taxonomy, null) }}\" class=\"tab is-active\">\n                            {{ $taxonomy->tags->find($id)?->name }}\n                            @icon('tabler-x')\n                        </a>\n                    @else\n                        <x-waterhole::selector\n                            button-class=\"tab\"\n                            placement=\"bottom-end\"\n                            :options=\"$taxonomy->tags->modelKeys()\"\n                            :placeholder=\"__($taxonomy->name)\"\n                            :label=\"fn($id) => $taxonomy->tags->find($id)->name ?? ''\"\n                            :href=\"fn($id) => $href($taxonomy, $id)\"\n                        />\n                    @endif\n                @endforeach\n            </div>\n        blade;\n    }\n\n    public function href(Taxonomy $taxonomy, $id)\n    {\n        return request()->fullUrlWithQuery([\n            'tags' => array_replace(request('tags', []), [$taxonomy->id => $id]),\n        ]);\n    }\n}\n"
  },
  {
    "path": "src/View/Components/TextEditor.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\n\nclass TextEditor extends Component\n{\n    public function __construct(\n        public string $name,\n        public ?string $id = null,\n        public ?string $value = null,\n        public ?string $placeholder = null,\n        public ?string $userLookupUrl = null,\n    ) {\n        $this->id = $id ?: 'text-editor-' . uniqid();\n        $this->userLookupUrl ??= route('waterhole.user-lookup');\n    }\n\n    public function render()\n    {\n        return $this->view('waterhole::components.text-editor');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/TextEditorButton.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\Contracts\\View\\View;\nuse Illuminate\\View\\Component;\n\nclass TextEditorButton extends Component\n{\n    public function __construct(\n        public string $icon,\n        public string $label,\n        public ?string $id = null,\n        public ?string $format = null,\n        public ?string $hotkey = null,\n    ) {}\n\n    public function render(): View\n    {\n        return $this->view('waterhole::components.text-editor-button');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/TextEditorEmojiButton.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\n\nclass TextEditorEmojiButton extends Component\n{\n    public function render(): string\n    {\n        return <<<'blade'\n            <ui-popup\n                class=\"hide-sm\"\n                data-controller=\"emoji-picker\"\n            >\n                <button\n                    type=\"button\"\n                    class=\"btn btn--transparent btn--icon\"\n                >\n                    @icon('tabler-mood-smile')\n                    <ui-tooltip>{{ __('waterhole::system.text-editor-emoji') }}</ui-tooltip>\n                </button>\n\n                <div class=\"menu emoji-picker\" hidden>\n                    <emoji-picker data-action=\"emoji-click->text-editor#insertEmoji\"></emoji-picker>\n                </div>\n            </ui-popup>\n        blade;\n    }\n}\n"
  },
  {
    "path": "src/View/Components/ThemeSelector.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\n\nclass ThemeSelector extends Component\n{\n    public function shouldRender(): bool\n    {\n        return !config('waterhole.design.theme');\n    }\n\n    public function render()\n    {\n        return $this->view('waterhole::components.theme-selector');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/UserGroups.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Support\\Facades\\Auth;\nuse Illuminate\\View\\Component;\nuse Waterhole\\Models\\User;\n\nclass UserGroups extends Component\n{\n    public ?Collection $groups;\n\n    public function __construct(public ?User $user)\n    {\n        $this->groups = $this->user?->groups->where('is_public', true);\n\n        if (Auth::user()?->can('waterhole.user.edit', $user)) {\n            $this->groups?->push(...$this->user->groups->where('is_public', false));\n        }\n    }\n\n    public function shouldRender(): bool\n    {\n        return $this->groups?->isNotEmpty() || $this->user?->isSuspended();\n    }\n\n    public function render()\n    {\n        return <<<'blade'\n            <span>\n                @foreach ($groups as $group)\n                    <x-waterhole::group-badge :group=\"$group\"/>\n                @endforeach\n\n                @if ($user?->isSuspended() && Gate::allows('waterhole.user.suspend', $user))\n                    <span class=\"badge suspended-badge\">\n                        @icon('tabler-ban')\n                        {{ __('waterhole::user.suspended-badge') }}\n                    </span>\n                @endif\n            </span>\n        blade;\n    }\n}\n"
  },
  {
    "path": "src/View/Components/UserJoined.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\nuse Waterhole\\Models\\User;\n\nclass UserJoined extends Component\n{\n    public function __construct(public User $user) {}\n\n    public function shouldRender()\n    {\n        return $this->user->created_at;\n    }\n\n    public function render()\n    {\n        return <<<'blade'\n            <span class=\"with-icon\">\n                @icon('tabler-calendar')\n                <span>\n                    {{ __('waterhole::user.user-joined-text') }}\n                    <x-waterhole::relative-time :datetime=\"$user->created_at\"/>\n                </span>\n            </span>\n        blade;\n    }\n}\n"
  },
  {
    "path": "src/View/Components/UserLabel.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\nuse Waterhole\\Models\\User;\n\nclass UserLabel extends Component\n{\n    public function __construct(public ?User $user = null, public bool $link = false) {}\n\n    public function render()\n    {\n        return $this->view('waterhole::components.user-label');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/UserLastSeen.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\nuse Waterhole\\Models\\User;\n\nclass UserLastSeen extends Component\n{\n    public function __construct(public User $user) {}\n\n    public function shouldRender()\n    {\n        return $this->user->show_online;\n    }\n\n    public function render()\n    {\n        return <<<'blade'\n            @if ($user->isOnline())\n                <span class=\"row gap-xxs color-success weight-medium\">\n                    <span class=\"dot\"></span>\n                    {{ __('waterhole::user.online-label') }}\n                </span>\n            @elseif ($user->last_seen_at)\n                <span class=\"with-icon\">\n                    @icon('tabler-eye')\n                    <span>\n                        {{ __('waterhole::user.user-last-seen-text') }}\n                        <x-waterhole::relative-time :datetime=\"$user->last_seen_at\"/>\n                    </span>\n                </span>\n            @endif\n        blade;\n    }\n}\n"
  },
  {
    "path": "src/View/Components/UserLink.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\nuse Waterhole\\Models\\User;\n\nclass UserLink extends Component\n{\n    public function __construct(public ?User $user = null, public bool $link = true) {}\n\n    public function render()\n    {\n        return $this->view('waterhole::components.user-link');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/UserLocation.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\nuse Waterhole\\Models\\User;\n\nclass UserLocation extends Component\n{\n    public function __construct(public User $user) {}\n\n    public function shouldRender()\n    {\n        return $this->user->location;\n    }\n\n    public function render()\n    {\n        return <<<'blade'\n            <span class=\"with-icon\">\n                @icon('tabler-map-pin')\n                <span>{{ $user->location }}</span>\n            </span>\n        blade;\n    }\n}\n"
  },
  {
    "path": "src/View/Components/UserProfile.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\nuse Waterhole\\Models\\User;\n\nclass UserProfile extends Component\n{\n    public function __construct(public User $user, public ?string $title = null)\n    {\n        $this->title = $title ?: $user->name;\n\n        $user->loadCount('posts', 'comments');\n    }\n\n    public function render()\n    {\n        return $this->view('waterhole::components.user-profile');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/UserWebsite.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\nuse Waterhole\\Models\\User;\n\nclass UserWebsite extends Component\n{\n    public string|null|false $host;\n\n    public function __construct(public User $user)\n    {\n        $this->host = $user->website ? parse_url($user->website, PHP_URL_HOST) : null;\n    }\n\n    public function shouldRender()\n    {\n        return $this->host;\n    }\n\n    public function render()\n    {\n        return <<<'blade'\n            <a\n                href=\"{{ $user->website }}\"\n                class=\"with-icon color-muted\"\n                rel=\"noopener nofollow ugc\"\n            >\n                @icon('tabler-link')\n                <span>{{ $host }}</span>\n            </a>\n        blade;\n    }\n}\n"
  },
  {
    "path": "src/View/Components/ValidationErrors.php",
    "content": "<?php\n\nnamespace Waterhole\\View\\Components;\n\nuse Illuminate\\View\\Component;\n\nclass ValidationErrors extends Component\n{\n    public function render()\n    {\n        return $this->view('waterhole::components.validation-errors');\n    }\n}\n"
  },
  {
    "path": "src/View/TurboStream.php",
    "content": "<?php\n\nnamespace Waterhole\\View;\n\nuse Exception;\nuse Illuminate\\Support\\Facades\\Blade;\nuse Illuminate\\View\\Component;\nuse Illuminate\\View\\ComponentAttributeBag;\n\n/**\n * Helper class for making <turbo-stream> elements.\n */\nabstract class TurboStream\n{\n    /**\n     * Make a Turbo Stream to replace a streamable component.\n     */\n    public static function replace(Component $component): ?string\n    {\n        if (!($class = static::getClassName($component))) {\n            return null;\n        }\n\n        return static::stream($component, 'replace', ['targets' => \".$class\"]);\n    }\n\n    /**\n     * Make a Turbo Stream to remove a streamable component.\n     */\n    public static function remove(Component $component): ?string\n    {\n        if (!($class = static::getClassName($component))) {\n            return null;\n        }\n\n        return <<<html\n            <turbo-stream action=\"remove\" targets=\".$class\"></turbo-stream>\n        html;\n    }\n\n    /**\n     * Make a Turbo Stream to append a component to a target.\n     */\n    public static function append(Component $component, string $targets): string\n    {\n        return static::stream($component, 'append', compact('targets'));\n    }\n\n    /**\n     * Make a Turbo Stream to prepend a component to a target.\n     */\n    public static function prepend(Component $component, string $targets): string\n    {\n        return static::stream($component, 'prepend', compact('targets'));\n    }\n\n    /**\n     * Make a Turbo Stream to insert a component before a target.\n     */\n    public static function before(Component $component, string $targets): string\n    {\n        return static::stream($component, 'before', compact('targets'));\n    }\n\n    /**\n     * Make a Turbo Stream to insert a component after a target.\n     */\n    public static function after(Component $component, string $targets): string\n    {\n        return static::stream($component, 'after', compact('targets'));\n    }\n\n    public static function refresh(): string\n    {\n        return '<turbo-stream action=\"refresh\"></turbo-stream>';\n    }\n\n    private static function stream(Component $component, string $action, array $attributes): string\n    {\n        $attributes = new ComponentAttributeBag($attributes);\n        $content = trim(Blade::renderComponent($component));\n\n        return <<<html\n            <turbo-stream action=\"$action\" $attributes>\n                <template>$content</template>\n            </turbo-stream>\n        html;\n    }\n\n    private static function getClassName(Component $component)\n    {\n        if (!method_exists($component, 'streamableClassName')) {\n            throw new Exception(get_class($component) . ' is not streamable');\n        }\n\n        return $component->streamableClassName();\n    }\n}\n"
  },
  {
    "path": "src/Waterhole.php",
    "content": "<?php\n\nnamespace Waterhole;\n\nuse Composer\\InstalledVersions;\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\Support\\Facades\\Route;\nuse Throwable;\nuse Waterhole\\Models\\PermissionCollection;\n\nabstract class Waterhole\n{\n    public const VERSION = '0.6.2';\n\n    public static function version(): string\n    {\n        try {\n            return InstalledVersions::getPrettyVersion('waterhole/core');\n        } catch (Throwable) {\n            return static::VERSION;\n        }\n    }\n\n    public static function isWaterholeRoute(): bool\n    {\n        return Route::currentRouteNamed('waterhole.*');\n    }\n\n    public static function isForumRoute(): bool\n    {\n        return static::isWaterholeRoute() && !static::isCpRoute() && !static::isApiRoute();\n    }\n\n    public static function isCpRoute(): bool\n    {\n        return Route::currentRouteNamed('waterhole.cp.*');\n    }\n\n    public static function isApiRoute(): bool\n    {\n        return Route::currentRouteNamed('waterhole.api.*');\n    }\n\n    public static function permissions(): PermissionCollection\n    {\n        return app('waterhole.permissions');\n    }\n\n    public static function hasPendingMigrations(): bool\n    {\n        $migrator = app('migrator');\n        $files = $migrator->getMigrationFiles(__DIR__ . '/../database/migrations');\n        $repository = $migrator->getRepository();\n\n        if (!$repository->repositoryExists()) {\n            return true;\n        }\n\n        $ran = $repository->getRan();\n\n        return Collection::make($files)\n            ->reject(fn($file) => in_array(str_replace('.php', '', basename($file)), $ran))\n            ->isNotEmpty();\n    }\n}\n"
  },
  {
    "path": "src/Widgets/Feed.php",
    "content": "<?php\n\nnamespace Waterhole\\Widgets;\n\nuse Illuminate\\Support\\Facades\\Cache;\nuse Illuminate\\Support\\Facades\\Http;\nuse Illuminate\\View\\Component;\nuse Laminas\\Feed\\Reader\\Feed\\FeedInterface;\nuse Laminas\\Feed\\Reader\\Reader;\n\nclass Feed extends Component\n{\n    public static bool $lazy = true;\n\n    public FeedInterface $feed;\n\n    public function __construct(\n        public string $url,\n        public int $limit = 3,\n        public ?string $title = null,\n    ) {\n        // TODO: be smarter about caching (ie. HTTP Conditional GET)\n        $content = Cache::remember(\n            'waterhole.feed.' . sha1($url),\n            60 * 60 * 6,\n            fn() => Http::throw()->get($url)->body(),\n        );\n\n        $this->feed = Reader::importString($content);\n    }\n\n    public function render()\n    {\n        return view('waterhole::widgets.feed');\n    }\n}\n"
  },
  {
    "path": "src/Widgets/GettingStarted.php",
    "content": "<?php\n\nnamespace Waterhole\\Widgets;\n\nuse Illuminate\\View\\Component;\n\nclass GettingStarted extends Component\n{\n    public array $items;\n\n    public function __construct()\n    {\n        $this->items = [\n            'strategy' => [\n                'url' => 'https://waterhole.dev/docs',\n                'icon' => 'tabler-book',\n            ],\n            'structure' => [\n                'url' => route('waterhole.cp.structure'),\n                'icon' => 'tabler-layout-list',\n            ],\n            'groups' => [\n                'url' => route('waterhole.cp.groups.index'),\n                'icon' => 'tabler-users',\n            ],\n            'design' => [\n                'url' => 'https://waterhole.dev/community',\n                'icon' => 'tabler-bulb',\n            ],\n        ];\n    }\n\n    public function render()\n    {\n        return view('waterhole::widgets.getting-started');\n    }\n}\n"
  },
  {
    "path": "src/Widgets/LineChart.php",
    "content": "<?php\n\nnamespace Waterhole\\Widgets;\n\nuse Carbon\\CarbonImmutable;\nuse Illuminate\\Database\\Eloquent\\Builder as EloquentBuilder;\nuse Illuminate\\Database\\Query\\Builder as QueryBuilder;\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\View\\Component;\n\nclass LineChart extends Component\n{\n    public static bool $lazy = true;\n\n    public int $id;\n    public string $title;\n    public array $periods;\n    public array $units;\n    public string $selectedPeriod;\n    public string $selectedUnit;\n    public CarbonImmutable $periodStart;\n    public CarbonImmutable $periodEnd;\n    public CarbonImmutable $prevPeriodStart;\n    public CarbonImmutable $prevPeriodEnd;\n    public Collection $results;\n    public int $periodTotal;\n    public int $prevPeriodTotal;\n\n    public function __construct(\n        int $id,\n        string $title,\n        $model,\n        string $column = 'created_at',\n        string $defaultPeriod = 'last_7_days',\n    ) {\n        $this->id = $id;\n\n        if ($title) {\n            if (($translation = __($title)) !== $title) {\n                $title = $translation;\n            }\n            $this->title = $title;\n        }\n\n        $this->periods = [\n            'today' => [\n                'start' => fn(CarbonImmutable $date) => $date->startOfDay(),\n                'end' => fn(CarbonImmutable $date) => $date->endOfDay(),\n                'units' => ['hour'],\n            ],\n            'last_7_days' => [\n                'start' => fn(CarbonImmutable $date) => $date->startOfDay()->subDays(6),\n                'end' => fn(CarbonImmutable $date) => $date->endOfDay(),\n                'units' => ['day', 'hour'],\n            ],\n            'last_4_weeks' => [\n                'start' => fn(CarbonImmutable $date) => $date->startOfWeek()->subWeeks(3),\n                'end' => fn(CarbonImmutable $date) => $date->endOfWeek(),\n                'units' => ['day', 'week'],\n            ],\n            'last_3_months' => [\n                'start' => fn(CarbonImmutable $date) => $date->startOfMonth()->subMonths(2),\n                'end' => fn(CarbonImmutable $date) => $date->endOfMonth(),\n                'units' => ['week', 'month'],\n            ],\n            'last_12_months' => [\n                'start' => fn(CarbonImmutable $date) => $date->startOfMonth()->subMonths(11),\n                'end' => fn(CarbonImmutable $date) => $date->endOfMonth(),\n                'units' => ['month', 'week'],\n            ],\n            'this_month' => [\n                'start' => fn(CarbonImmutable $date) => $date->startOfMonth(),\n                'end' => fn(CarbonImmutable $date) => $date->endOfMonth(),\n                'units' => ['day', 'week'],\n            ],\n            'this_quarter' => [\n                'start' => fn(CarbonImmutable $date) => $date->startOfQuarter(),\n                'end' => fn(CarbonImmutable $date) => $date->endOfQuarter(),\n                'units' => ['day', 'week'],\n            ],\n            'this_year' => [\n                'start' => fn(CarbonImmutable $date) => $date->startOfYear(),\n                'end' => fn(CarbonImmutable $date) => $date->endOfYear(),\n                'units' => ['month', 'week'],\n            ],\n            'all_time' => [\n                'start' => fn() => CarbonImmutable::parse($model::min($column)),\n                'end' => fn(CarbonImmutable $date) => $date,\n                'units' => ['month', 'year'],\n            ],\n        ];\n\n        $builder =\n            $model instanceof QueryBuilder || $model instanceof EloquentBuilder\n                ? $model\n                : $model::query();\n\n        $isPgsql = $builder->getConnection()->getDriverName() === 'pgsql';\n\n        $this->units = [\n            'hour' => [\n                'format' => $isPgsql ? 'YYYY-MM-DD HH24:00:00' : '%Y-%m-%d %H:00:00',\n                'label' => fn(CarbonImmutable $date) => $date->isoFormat('LT'),\n            ],\n            'day' => [\n                'format' => $isPgsql ? 'YYYY-MM-DD' : '%Y-%m-%d',\n                'label' => fn(CarbonImmutable $date) => $date->isoFormat('D MMM'),\n            ],\n            'week' => [\n                'format' => $isPgsql ? 'YYYY\"W\"IW' : '%YW%v',\n                'label' => fn(CarbonImmutable $date) => $date->isoFormat('D MMM') .\n                    ' - ' .\n                    $date->addDays(6)->isoFormat('D MMM'),\n            ],\n            'month' => [\n                'format' => $isPgsql ? 'YYYY-MM-01' : '%Y-%m-01',\n                'label' => fn(CarbonImmutable $date) => $date->isoFormat('MMM Y'),\n            ],\n            'year' => [\n                'format' => $isPgsql ? 'YYYY' : '%Y',\n                'label' => fn(CarbonImmutable $date) => $date->isoFormat('Y'),\n            ],\n        ];\n\n        $this->selectedPeriod = isset($this->periods[($p = request('period'))])\n            ? $p\n            : $defaultPeriod;\n\n        $period = $this->periods[$this->selectedPeriod];\n        $now = CarbonImmutable::now();\n        $this->periodStart = $period['start']($now);\n        $this->periodEnd = $period['end']($now);\n        $this->prevPeriodStart = $period['start']($this->periodStart->subSecond());\n        $this->prevPeriodEnd = $period['end']($this->periodStart->subSecond());\n\n        $this->selectedUnit = $period['units'][0];\n\n        $unit = $this->units[$this->selectedUnit];\n\n        $this->results = $builder\n            ->selectRaw(\n                $isPgsql\n                    ? \"to_char($column, ?) as time_group\"\n                    : \"DATE_FORMAT($column, ?) as time_group\",\n                [$unit['format']],\n            )\n            ->selectRaw('COUNT(*) as count')\n            ->where($column, '>=', $this->prevPeriodStart)\n            ->where($column, '<', $this->periodEnd)\n            ->groupBy('time_group')\n            ->get(['count', 'time_group'])\n            ->map(\n                fn($row) => [\n                    'count' => $row['count'],\n                    'date' => CarbonImmutable::parse($row['time_group']),\n                ],\n            );\n\n        $this->periodTotal = $this->results\n            ->where('date', '>=', $this->periodStart)\n            ->where('date', '<', $this->periodEnd)\n            ->sum('count');\n\n        $this->prevPeriodTotal = $this->results\n            ->where('date', '>=', $this->prevPeriodStart)\n            ->where('date', '<', $this->prevPeriodEnd)\n            ->sum('count');\n    }\n\n    public function render()\n    {\n        return view('waterhole::widgets.line-chart');\n    }\n}\n"
  },
  {
    "path": "src/helpers.php",
    "content": "<?php\n\nnamespace Waterhole;\n\nuse BladeUI\\Icons\\Exceptions\\SvgNotFound;\nuse Closure;\nuse Exception;\nuse Illuminate\\Support\\Arr;\nuse Illuminate\\Support\\Facades\\Storage;\nuse Illuminate\\Support\\HtmlString;\nuse Illuminate\\View\\AnonymousComponent;\nuse Illuminate\\View\\ComponentAttributeBag;\nuse Major\\Fluent\\Formatters\\Number\\NumberFormatter;\nuse Major\\Fluent\\Formatters\\Number\\Options;\nuse s9e\\TextFormatter\\Utils;\nuse Waterhole\\Extend\\Support\\ComponentList;\nuse Waterhole\\Models\\User;\n\n/**\n * Format a number.\n */\nfunction format_number(float $number, array $options = []): string\n{\n    return (new NumberFormatter(app()->getLocale()))->format($number, new Options(...$options));\n}\n\n/**\n * Format a number in compact notation.\n */\nfunction compact_number(float $number): string\n{\n    if ($number >= 100) {\n        $key = 'waterhole::system.compact-number-1' . str_repeat('0', floor(log10($number)));\n\n        if (($format = __($key, [], app()->getLocale())) !== $key) {\n            [$numberFormat, $unit] = str_split($format, strrpos($format, '0') + 1);\n            $split = explode('.', $numberFormat);\n            $digits = strlen($split[0]);\n            $fractionDigits = count($split) > 1 ? strlen($split[1]) : 0;\n            $threshold = pow(10, $digits);\n\n            while ($number >= $threshold) {\n                $number /= 10;\n            }\n\n            $formattedNumber = (new NumberFormatter(app()->getLocale()))->format(\n                $number,\n                new Options(maximumFractionDigits: $fractionDigits),\n            );\n\n            return $formattedNumber . $unit;\n        }\n    }\n\n    return (string) $number;\n}\n\n/**\n * Replace Emoji characters in a plain-text string.\n */\nfunction emojify(?string $text): HtmlString|string\n{\n    if (!$text) {\n        return '';\n    }\n\n    $formatter = app('waterhole.formatter.emoji');\n\n    return new HtmlString($formatter->render($formatter->parse($text)));\n}\n\n/**\n * Strip the formatting of an intermediate representation and return plain text.\n */\nfunction remove_formatting(?string $xml): string\n{\n    if (!$xml) {\n        return '';\n    }\n\n    try {\n        return Utils::removeFormatting($xml);\n    } catch (Exception $e) {\n        return '';\n    }\n}\n\n/**\n * Get the best contrast color for text on a background color.\n */\nfunction get_contrast_color(string $hex): string\n{\n    $hex = ltrim($hex, '#');\n    $r = hexdec(substr($hex, 0, 2));\n    $g = hexdec(substr($hex, 2, 2));\n    $b = hexdec(substr($hex, 4, 2));\n    $yiq = ($r * 299 + $g * 587 + $b * 114) / 1000;\n\n    return $yiq >= 128 ? '#000' : '#fff';\n}\n\n/**\n * Resolve a collection of services from the container.\n *\n * Items that do not exist will be logged and skipped.\n */\nfunction resolve_all(array $names, array ...$parameters): array\n{\n    return array_filter(\n        array_map(\n            fn($name) => is_object($name) ? $name : rescue(fn() => resolve($name, ...$parameters)),\n            $names,\n        ),\n    );\n}\n\nfunction return_field(?string $default = null): string\n{\n    if ($return = old('return', request('return', $default))) {\n        return '<input type=\"hidden\" name=\"return\" value=\"' . e($return) . '\">';\n    }\n\n    return '';\n}\n\nfunction username(?User $user): string\n{\n    return $user->name ?? __('waterhole::system.deleted-user');\n}\n\nfunction user_variables(?User $user): array\n{\n    return [\n        'userName' => username($user),\n    ];\n}\n\nfunction build_components(array|string|ComponentList $components, array $data = []): array\n{\n    if (is_string($components) && class_exists($components)) {\n        $components = resolve($components);\n    }\n\n    if ($components instanceof ComponentList) {\n        $components = $components->items();\n    }\n\n    return array_map(function ($component) use ($data) {\n        if ($component instanceof Closure) {\n            $component = app()->call($component, $data);\n        }\n        if (is_object($component)) {\n            return $component;\n        } elseif (class_exists($component)) {\n            return $component::resolve($data);\n        } elseif (view()->exists($component)) {\n            return new AnonymousComponent($component, $data);\n        }\n    }, (array) $components);\n}\n\nfunction icon(?string $icon, array $attributes = []): string\n{\n    if (!$icon) {\n        return '';\n    }\n\n    $attributes['class'] = ($attributes['class'] ?? '') . ' icon';\n\n    if (str_starts_with($icon, 'emoji:')) {\n        return sprintf(\n            '<span %s>%s</span>',\n            new ComponentAttributeBag($attributes),\n            emojify(substr($icon, 6)),\n        );\n    }\n\n    if (str_starts_with($icon, 'file:')) {\n        return sprintf(\n            '<img src=\"%s\" alt=\"\" %s>',\n            e(Storage::disk(config('waterhole.uploads.disk'))->url('icons/' . substr($icon, 5))),\n            new ComponentAttributeBag($attributes),\n        );\n    }\n\n    if (str_starts_with($icon, 'svg:')) {\n        $icon = substr($icon, 4);\n    }\n\n    $attributes['class'] .= \" icon-$icon\";\n\n    try {\n        return svg($icon, $attributes['class'], Arr::except($attributes, 'class'))->toHtml();\n    } catch (SvgNotFound $e) {\n        if (config('app.debug')) {\n            return '<script>console.warn(\"Icon [' . e($icon) . '] not found\")</script>';\n        }\n    }\n\n    return '';\n}\n\nfunction is_absolute_url(string $path): bool\n{\n    return str_starts_with($path, 'https://') || str_starts_with($path, 'http://');\n}\n"
  },
  {
    "path": "tests/Feature/ExtendActionsTest.php",
    "content": "<?php\n\nuse Illuminate\\Foundation\\Testing\\RefreshDatabase;\nuse Illuminate\\Support\\Facades\\URL;\nuse Waterhole\\Actions\\Action;\nuse Waterhole\\Database\\Seeders\\GroupsSeeder;\nuse Waterhole\\Extend;\nuse Waterhole\\Models\\Channel;\nuse Waterhole\\Models\\Comment;\nuse Waterhole\\Models\\Page;\nuse Waterhole\\Models\\Post;\nuse Waterhole\\Models\\ReactionSet;\nuse Waterhole\\Models\\ReactionType;\nuse Waterhole\\Models\\StructureHeading;\nuse Waterhole\\Models\\StructureLink;\nuse Waterhole\\Models\\Tag;\nuse Waterhole\\Models\\Taxonomy;\nuse Waterhole\\Models\\User;\n\nuses(RefreshDatabase::class);\n\nbeforeEach(function () {\n    $this->seed(GroupsSeeder::class);\n});\n\nclass ExtendTestAction extends Action\n{\n    public function authorize(?User $user, \\Waterhole\\Models\\Model $model): bool\n    {\n        return true;\n    }\n\n    public function label(\\Illuminate\\Support\\Collection $models): string\n    {\n        return 'Extend Test Action';\n    }\n}\n\ndescribe('Actions extenders', function () {\n    test('add action', function (string $actionable, callable $makeModel) {\n        extend(function (Extend\\Core\\Actions $actions) use ($actionable) {\n            $actions->for($actionable)->add(ExtendTestAction::class, 'extend-test');\n        });\n\n        $model = $makeModel();\n\n        $this->get(\n            URL::route('waterhole.actions.menu', [\n                'actionable' => $actionable,\n                'id' => $model->id,\n            ]),\n        )->assertSeeText('Extend Test Action');\n\n        if ($actionable === Post::class) {\n            $this->get(\n                URL::route('waterhole.actions.create', [\n                    'actionable' => $actionable,\n                    'id' => $model->id,\n                    'action_class' => ExtendTestAction::class,\n                ]),\n            )->assertOk();\n\n            $this->post(URL::route('waterhole.actions.store'), [\n                'actionable' => $actionable,\n                'id' => $model->id,\n                'action_class' => ExtendTestAction::class,\n            ]);\n        }\n    })->with('actionables');\n});\n\ndataset('actionables', [\n    'channel' => [Channel::class, fn() => Channel::factory()->public()->create()],\n    'comment' => [\n        Comment::class,\n        fn() => Comment::factory()\n            ->for(Post::factory()->for(Channel::factory()->public()))\n            ->create(),\n    ],\n    'group' => [\n        \\Waterhole\\Models\\Group::class,\n        fn() => \\Waterhole\\Models\\Group::create(['name' => 'Test Group', 'is_public' => true]),\n    ],\n    'page' => [Page::class, fn() => Page::factory()->public()->create()],\n    'post' => [\n        Post::class,\n        fn() => Post::factory()\n            ->for(Channel::factory()->public())\n            ->create(),\n    ],\n    'reactionSet' => [\n        ReactionSet::class,\n        fn() => ReactionSet::create(['name' => 'Test Reaction Set']),\n    ],\n    'reactionType' => [\n        ReactionType::class,\n        fn() => ReactionType::create([\n            'reaction_set_id' => ReactionSet::create(['name' => 'Test Reaction Set'])->id,\n            'name' => 'Test Reaction Type',\n            'score' => 1,\n            'position' => 0,\n        ]),\n    ],\n    'structureHeading' => [\n        StructureHeading::class,\n        fn() => StructureHeading::create(['name' => 'Test Heading']),\n    ],\n    'structureLink' => [\n        StructureLink::class,\n        fn() => tap(\n            StructureLink::create(['name' => 'Test Link', 'href' => 'https://example.com']),\n            fn($link) => $link->savePermissions(['group:1' => ['view' => true]]),\n        ),\n    ],\n    'tag' => [\n        Tag::class,\n        fn() => Tag::create([\n            'taxonomy_id' => tap(\n                Taxonomy::create(['name' => 'Test Taxonomy']),\n                fn($taxonomy) => $taxonomy->savePermissions(['group:1' => ['view' => true]]),\n            )->id,\n            'name' => 'Test Tag',\n        ]),\n    ],\n    'taxonomy' => [\n        Taxonomy::class,\n        fn() => tap(\n            Taxonomy::create(['name' => 'Test Taxonomy']),\n            fn($taxonomy) => $taxonomy->savePermissions(['group:1' => ['view' => true]]),\n        ),\n    ],\n    'user' => [User::class, fn() => User::factory()->create()],\n]);\n"
  },
  {
    "path": "tests/Feature/ExtendApiTest.php",
    "content": "<?php\n\nuse Illuminate\\Foundation\\Testing\\RefreshDatabase;\nuse Tobyz\\JsonApiServer\\Schema\\Field\\Attribute;\nuse Tobyz\\JsonApiServer\\Schema\\Type;\nuse Waterhole\\Database\\Seeders\\GroupsSeeder;\nuse Waterhole\\Extend;\nuse Waterhole\\Models\\Channel;\nuse Waterhole\\Models\\ChannelUser;\nuse Waterhole\\Models\\Comment;\nuse Waterhole\\Models\\Group;\nuse Waterhole\\Models\\Page;\nuse Waterhole\\Models\\Post;\nuse Waterhole\\Models\\PostUser;\nuse Waterhole\\Models\\Reaction;\nuse Waterhole\\Models\\ReactionSet;\nuse Waterhole\\Models\\ReactionType;\nuse Waterhole\\Models\\StructureHeading;\nuse Waterhole\\Models\\StructureLink;\nuse Waterhole\\Models\\Tag;\nuse Waterhole\\Models\\Taxonomy;\nuse Waterhole\\Models\\User;\n\nuses(RefreshDatabase::class);\n\nbeforeEach(function () {\n    $this->seed(GroupsSeeder::class);\n});\n\ndescribe('API extenders', function () {\n    test('add resource', function () {\n        $resource = new class extends Tobyz\\JsonApiServer\\Resource\\AbstractResource implements\n            Tobyz\\JsonApiServer\\Resource\\Findable\n        {\n            public function type(): string\n            {\n                return 'test';\n            }\n\n            public function endpoints(): array\n            {\n                return [Tobyz\\JsonApiServer\\Endpoint\\Show::make()];\n            }\n\n            public function find(string $id, Tobyz\\JsonApiServer\\Context $context): ?object\n            {\n                return (object) ['id' => $id];\n            }\n        };\n\n        extend(function (Extend\\Api\\JsonApi $api) use ($resource) {\n            $api->resource($resource);\n        });\n\n        jsonApi('GET', '/api/test/1')->assertOk();\n    });\n\n    test('add field to api resources with endpoints', function (\n        string $extenderClass,\n        callable $makeRequest,\n    ) {\n        app()->extend($extenderClass, function ($resource) {\n            $resource->fields->add(\n                Attribute::make('extendTest')\n                    ->type(Type\\Str::make())\n                    ->get(fn($model) => (string) $model->getKey()),\n                'extendTest',\n            );\n\n            return $resource;\n        });\n\n        [$response, $path, $value] = $makeRequest($this);\n\n        $response->assertOk();\n        $response->assertJsonPath($path, $value);\n    })->with('api resources with endpoints');\n\n    test('add field to api related resources', function (\n        string $extenderClass,\n        callable $makeRequest,\n    ) {\n        app()->extend($extenderClass, function ($resource) {\n            $resource->fields->add(\n                Attribute::make('extendTest')\n                    ->type(Type\\Str::make())\n                    ->get(fn($model) => (string) $model->getKey()),\n                'extendTest',\n            );\n\n            return $resource;\n        });\n\n        [$response, $type, $id] = $makeRequest($this);\n\n        $response->assertOk();\n\n        $included = collect($response->json('included'));\n\n        $match = $included->first(\n            fn($resource) => $resource['type'] === $type && $resource['id'] === (string) $id,\n        );\n\n        expect($match)->not->toBeNull();\n        expect($match['attributes']['extendTest'])->toBe((string) $id);\n    })->with('api resources with relations');\n});\n\ndataset('api resources with endpoints', [\n    'channels' => [\n        Extend\\Api\\ChannelsResource::class,\n        function ($test) {\n            $channel = Channel::factory()->public()->create();\n\n            return [\n                jsonApi('GET', \"/api/channels/$channel->id\"),\n                'data.attributes.extendTest',\n                (string) $channel->getKey(),\n            ];\n        },\n    ],\n    'comments' => [\n        Extend\\Api\\CommentsResource::class,\n        function ($test) {\n            $comment = Comment::factory()\n                ->for(Post::factory()->for(Channel::factory()->public()))\n                ->create();\n\n            return [\n                jsonApi('GET', \"/api/comments/$comment->id\"),\n                'data.attributes.extendTest',\n                (string) $comment->getKey(),\n            ];\n        },\n    ],\n    'groups' => [\n        Extend\\Api\\GroupsResource::class,\n        function ($test) {\n            $group = Group::create(['name' => 'Test Group', 'is_public' => true]);\n\n            return [\n                jsonApi('GET', \"/api/groups/$group->id\"),\n                'data.attributes.extendTest',\n                (string) $group->getKey(),\n            ];\n        },\n    ],\n    'pages' => [\n        Extend\\Api\\PagesResource::class,\n        function ($test) {\n            $page = Page::factory()->public()->create();\n\n            return [\n                jsonApi('GET', \"/api/pages/$page->id\"),\n                'data.attributes.extendTest',\n                (string) $page->getKey(),\n            ];\n        },\n    ],\n    'posts' => [\n        Extend\\Api\\PostsResource::class,\n        function ($test) {\n            $post = Post::factory()\n                ->for(Channel::factory()->public())\n                ->create();\n\n            return [\n                jsonApi('GET', \"/api/posts/$post->id\"),\n                'data.attributes.extendTest',\n                (string) $post->getKey(),\n            ];\n        },\n    ],\n    'structure headings' => [\n        Extend\\Api\\StructureHeadingsResource::class,\n        function ($test) {\n            $heading = StructureHeading::create(['name' => 'Test Heading']);\n\n            return [\n                jsonApi('GET', \"/api/structureHeadings/$heading->id\"),\n                'data.attributes.extendTest',\n                (string) $heading->getKey(),\n            ];\n        },\n    ],\n    'structure links' => [\n        Extend\\Api\\StructureLinksResource::class,\n        function ($test) {\n            $link = StructureLink::create(['name' => 'Test Link', 'href' => 'https://example.com']);\n            $link->savePermissions(['group:1' => ['view' => true]]);\n\n            return [\n                jsonApi('GET', \"/api/structureLinks/$link->id\"),\n                'data.attributes.extendTest',\n                (string) $link->getKey(),\n            ];\n        },\n    ],\n    'structure' => [\n        Extend\\Api\\StructureResource::class,\n        function ($test) {\n            $heading = StructureHeading::create(['name' => 'Test Heading']);\n            $structure = $heading->structure;\n\n            return [\n                jsonApi('GET', '/api/structure'),\n                'data.0.attributes.extendTest',\n                (string) $structure->getKey(),\n            ];\n        },\n    ],\n    'users' => [\n        Extend\\Api\\UsersResource::class,\n        function ($test) {\n            $user = User::factory()->create();\n\n            return [\n                jsonApi('GET', \"/api/users/$user->id\"),\n                'data.attributes.extendTest',\n                (string) $user->getKey(),\n            ];\n        },\n    ],\n]);\n\ndataset('api resources with relations', [\n    'channel users' => [\n        Extend\\Api\\ChannelUsersResource::class,\n        function ($test) {\n            $user = User::factory()->create();\n            $channel = Channel::factory()->public()->create();\n            $channelUser = ChannelUser::create([\n                'channel_id' => $channel->id,\n                'user_id' => $user->id,\n                'notifications' => 'follow',\n            ]);\n\n            $test->actingAs($user);\n\n            return [\n                jsonApi('GET', \"/api/channels/$channel->id?include=userState\"),\n                'channelUsers',\n                $channelUser->getKey(),\n            ];\n        },\n    ],\n    'post users' => [\n        Extend\\Api\\PostUsersResource::class,\n        function ($test) {\n            $user = User::factory()->create();\n            $post = Post::factory()\n                ->for(Channel::factory()->public())\n                ->create();\n            $postUser = PostUser::create([\n                'post_id' => $post->id,\n                'user_id' => $user->id,\n                'notifications' => 'follow',\n            ]);\n\n            $test->actingAs($user);\n\n            return [\n                jsonApi('GET', \"/api/posts/$post->id?include=userState\"),\n                'postUsers',\n                $postUser->getKey(),\n            ];\n        },\n    ],\n    'reaction sets' => [\n        Extend\\Api\\ReactionSetsResource::class,\n        function ($test) {\n            $reactionSet = ReactionSet::create(['name' => 'Test Reaction Set']);\n            ReactionType::create([\n                'reaction_set_id' => $reactionSet->id,\n                'name' => 'Test Reaction Type',\n                'score' => 1,\n                'position' => 0,\n            ]);\n\n            $channel = Channel::factory()\n                ->public()\n                ->create(['posts_reaction_set_id' => $reactionSet->id]);\n\n            return [\n                jsonApi('GET', \"/api/channels/$channel->id?include=postsReactionSet\"),\n                'reactionSets',\n                $reactionSet->getKey(),\n            ];\n        },\n    ],\n    'reaction types' => [\n        Extend\\Api\\ReactionTypesResource::class,\n        function ($test) {\n            $reactionSet = ReactionSet::create(['name' => 'Test Reaction Set']);\n            $reactionType = ReactionType::create([\n                'reaction_set_id' => $reactionSet->id,\n                'name' => 'Test Reaction Type',\n                'score' => 1,\n                'position' => 0,\n            ]);\n\n            $channel = Channel::factory()\n                ->public()\n                ->create(['posts_reaction_set_id' => $reactionSet->id]);\n\n            return [\n                jsonApi('GET', \"/api/channels/$channel->id?include=postsReactionSet.reactionTypes\"),\n                'reactionTypes',\n                $reactionType->getKey(),\n            ];\n        },\n    ],\n    'reactions' => [\n        Extend\\Api\\ReactionsResource::class,\n        function ($test) {\n            $user = User::factory()->create();\n            $post = Post::factory()\n                ->for(Channel::factory()->public())\n                ->create();\n            $reactionSet = ReactionSet::create(['name' => 'Test Reaction Set']);\n            $reactionType = ReactionType::create([\n                'reaction_set_id' => $reactionSet->id,\n                'name' => 'Test Reaction Type',\n                'score' => 1,\n                'position' => 0,\n            ]);\n            $reaction = Reaction::create([\n                'user_id' => $user->id,\n                'reaction_type_id' => $reactionType->id,\n                'content_id' => $post->id,\n                'content_type' => $post->getMorphClass(),\n            ]);\n\n            return [\n                jsonApi('GET', \"/api/posts/$post->id?include=reactions\"),\n                'reactions',\n                $reaction->getKey(),\n            ];\n        },\n    ],\n    'taxonomies' => [\n        Extend\\Api\\TaxonomiesResource::class,\n        function ($test) {\n            $taxonomy = Taxonomy::create(['name' => 'Test Taxonomy']);\n            $taxonomy->savePermissions(['group:1' => ['view' => true]]);\n            Tag::create(['taxonomy_id' => $taxonomy->id, 'name' => 'Test Tag']);\n\n            $channel = Channel::factory()->public()->create();\n            $channel->taxonomies()->attach($taxonomy);\n\n            return [\n                jsonApi('GET', \"/api/channels/$channel->id?include=taxonomies.tags\"),\n                'taxonomies',\n                $taxonomy->getKey(),\n            ];\n        },\n    ],\n    'tags' => [\n        Extend\\Api\\TagsResource::class,\n        function ($test) {\n            $taxonomy = Taxonomy::create(['name' => 'Test Taxonomy']);\n            $taxonomy->savePermissions(['group:1' => ['view' => true]]);\n            $tag = Tag::create(['taxonomy_id' => $taxonomy->id, 'name' => 'Test Tag']);\n\n            $channel = Channel::factory()->public()->create();\n            $channel->taxonomies()->attach($taxonomy);\n\n            return [\n                jsonApi('GET', \"/api/channels/$channel->id?include=taxonomies.tags\"),\n                'tags',\n                $tag->getKey(),\n            ];\n        },\n    ],\n]);\n"
  },
  {
    "path": "tests/Feature/ExtendAssetsTest.php",
    "content": "<?php\n\nuse Illuminate\\Foundation\\Testing\\RefreshDatabase;\nuse Illuminate\\Support\\Facades\\Storage;\nuse Waterhole\\Database\\Seeders\\GroupsSeeder;\nuse Waterhole\\Extend;\nuse Waterhole\\Models\\Channel;\n\nuses(RefreshDatabase::class);\n\nbeforeEach(function () {\n    $this->seed(GroupsSeeder::class);\n});\n\ndescribe('Assets extenders', function () {\n    test('add stylesheet', function () {\n        Storage::fake('public');\n        config(['app.debug' => true]);\n\n        $tempFile = tempnam(sys_get_temp_dir(), 'wh');\n        file_put_contents($tempFile, 'body{}');\n\n        extend(function (Extend\\Assets\\Stylesheet $styles) use ($tempFile) {\n            $styles->add($tempFile);\n        });\n\n        $urls = app(Extend\\Assets\\Stylesheet::class)->urls(['default']);\n\n        $file = Storage::disk('public')->get(explode('/storage/', $urls[0])[1]);\n        expect($file)->toContain('body{}');\n\n        @unlink($tempFile);\n    });\n\n    test('add script', function () {\n        Storage::fake('public');\n        config(['app.debug' => true]);\n\n        $tempFile = tempnam(sys_get_temp_dir(), 'wh');\n        file_put_contents($tempFile, 'console.log(\"test\");');\n\n        extend(function (Extend\\Assets\\Script $scripts) use ($tempFile) {\n            $scripts->add($tempFile);\n        });\n\n        $urls = app(Extend\\Assets\\Script::class)->urls(['default']);\n\n        $file = Storage::disk('public')->get(explode('/storage/', $urls[0])[1]);\n        expect($file)->toContain('console.log(\"test\");');\n\n        @unlink($tempFile);\n    });\n\n    test('add locale', function () {\n        Channel::factory()->public()->create();\n\n        extend(function (Extend\\Assets\\Locales $locales) {\n            $locales->add('French', 'fr');\n        });\n\n        $this->get('/')->assertSeeText('French');\n    });\n});\n"
  },
  {
    "path": "tests/Feature/ExtendCoreTest.php",
    "content": "<?php\n\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Foundation\\Testing\\RefreshDatabase;\nuse Illuminate\\Support\\Facades\\URL;\nuse Waterhole\\Database\\Seeders\\GroupsSeeder;\nuse Waterhole\\Extend;\nuse Waterhole\\Filters\\Filter;\nuse Waterhole\\Layouts\\Layout;\nuse Waterhole\\Models\\Channel;\nuse Waterhole\\Models\\Group;\nuse Waterhole\\Models\\Post;\nuse Waterhole\\Models\\User;\nuse Waterhole\\Notifications\\Notification;\n\nuses(RefreshDatabase::class);\n\nbeforeEach(function () {\n    $this->seed(GroupsSeeder::class);\n});\n\nclass ExtendTestPostLayout extends Layout\n{\n    public function label(): string\n    {\n        return 'Extend Test Layout';\n    }\n\n    public function itemComponent(): string\n    {\n        return 'waterhole::post-list-item';\n    }\n\n    public function wrapperClass(): string\n    {\n        return 'extend-test-layout';\n    }\n}\n\nclass ExtendTestPostFilter extends Filter\n{\n    public const TITLE = 'Extend Test Visible';\n\n    public function handle(): string\n    {\n        return 'extend-test-filter';\n    }\n\n    public function label(): string\n    {\n        return 'Extend Test Filter';\n    }\n\n    public function apply(Builder $query): void\n    {\n        $query->where('posts.title', self::TITLE);\n    }\n}\n\nclass ExtendTestNotification extends Notification\n{\n    public static function description(): ?string\n    {\n        return 'Extend Test Notification';\n    }\n\n    public function title(): string\n    {\n        return 'Extend Test Notification';\n    }\n}\n\ndescribe('Core extenders', function () {\n    test('add post layout', function () {\n        extend(function (Extend\\Core\\PostLayouts $layouts) {\n            $layouts->add(ExtendTestPostLayout::class);\n        });\n\n        $admin = User::factory()->create();\n        $admin->groups()->attach(Group::ADMIN_ID);\n\n        $this->actingAs($admin)\n            ->get(URL::route('waterhole.cp.structure.channels.create'))\n            ->assertSeeText('Extend Test Layout');\n\n        $channel = Channel::factory()\n            ->public()\n            ->create(['layout' => ExtendTestPostLayout::class]);\n\n        Post::factory()\n            ->for($channel)\n            ->create(['title' => 'Layout Post']);\n\n        $this->get(URL::route('waterhole.channels.show', $channel))->assertSee(\n            'extend-test-layout',\n        );\n    });\n\n    test('add post filter', function () {\n        extend(function (Extend\\Core\\PostFilters $filters) {\n            $filters->add(ExtendTestPostFilter::class);\n        });\n\n        $admin = User::factory()->create();\n        $admin->groups()->attach(Group::ADMIN_ID);\n\n        $this->actingAs($admin)\n            ->get(URL::route('waterhole.cp.structure.channels.create'))\n            ->assertSeeText('Extend Test Filter');\n\n        $channel = Channel::factory()\n            ->public()\n            ->create(['filters' => [ExtendTestPostFilter::class]]);\n\n        Post::factory()\n            ->for($channel)\n            ->create(['title' => ExtendTestPostFilter::TITLE]);\n\n        Post::factory()\n            ->for($channel)\n            ->create(['title' => 'Other Post']);\n\n        $response = $this->get(\n            URL::route('waterhole.channels.show', $channel) .\n                '?filter=' .\n                (new ExtendTestPostFilter())->handle(),\n        );\n\n        $response->assertSeeText(ExtendTestPostFilter::TITLE);\n        $response->assertDontSeeText('Other Post');\n    });\n\n    test('add notification type', function () {\n        extend(function (Extend\\Core\\NotificationTypes $types) {\n            $types->add(ExtendTestNotification::class, 'extend-test');\n        });\n\n        $user = User::factory()->create();\n\n        $this->actingAs($user)\n            ->get(URL::route('waterhole.preferences.notifications'))\n            ->assertSeeText('Extend Test Notification');\n    });\n\n    test('add formatter hooks', function () {\n        $configured = false;\n        $parsed = false;\n        $rendered = false;\n\n        extend(function (Extend\\Core\\Formatter $formatter) use (\n            &$configured,\n            &$parsed,\n            &$rendered,\n        ) {\n            $formatter->configure(function () use (&$configured) {\n                $configured = true;\n            });\n\n            $formatter->parsing(function ($parser, string &$text) use (&$parsed) {\n                $parsed = true;\n                $text = '**Parsed**';\n            });\n\n            $formatter->rendering(function ($renderer, string &$xml) use (&$rendered) {\n                $rendered = true;\n                $xml = '<r><B>Rendered</B></r>';\n            });\n        });\n\n        app(Extend\\Core\\Formatter::class);\n\n        $formatter = app(\\Waterhole\\Formatter\\Formatter::class);\n        $formatter->flush();\n\n        $xml = $formatter->parse('Hello world');\n        $html = $formatter->render($xml);\n\n        expect($configured)->toBeTrue();\n        expect($parsed)->toBeTrue();\n        expect($rendered)->toBeTrue();\n        expect($xml)->toContain('Parsed');\n        expect($html)->toContain('Rendered');\n    });\n});\n"
  },
  {
    "path": "tests/Feature/ExtendFormsTest.php",
    "content": "<?php\n\nuse Illuminate\\Foundation\\Http\\FormRequest;\nuse Illuminate\\Foundation\\Testing\\RefreshDatabase;\nuse Illuminate\\Support\\Facades\\URL;\nuse Illuminate\\Validation\\Validator;\nuse Waterhole\\Database\\Seeders\\GroupsSeeder;\nuse Waterhole\\Extend;\nuse Waterhole\\Forms\\Field;\nuse Waterhole\\Models\\Channel;\nuse Waterhole\\Models\\Group;\nuse Waterhole\\Models\\Model;\nuse Waterhole\\Models\\ReactionSet;\nuse Waterhole\\Models\\Taxonomy;\nuse Waterhole\\Models\\User;\n\nuses(RefreshDatabase::class);\n\nbeforeEach(function () {\n    $this->seed(GroupsSeeder::class);\n    ExtendTestPersistField::reset();\n});\n\nfunction extendTestAdminUser(): User\n{\n    $admin = User::factory()->create();\n    $admin->groups()->attach(Group::ADMIN_ID);\n\n    return $admin;\n}\n\ndescribe('Form extenders', function () {\n    test('add form field', function (\n        string $extenderClass,\n        callable $extendField,\n        callable $makeRequest,\n    ) {\n        $marker = 'Extend Test Field';\n\n        app()->extend($extenderClass, function ($extender) use ($extendField, $marker) {\n            $extendField($extender, $marker);\n\n            return $extender;\n        });\n\n        $response = $makeRequest($this);\n\n        $response->assertSeeText($marker);\n    })->with('form_extenders');\n\n    test('field callbacks run on registration submit', function () {\n        app()->extend(Extend\\Forms\\RegistrationForm::class, function ($extender) {\n            $extender->add(ExtendTestPersistField::class, 'extend-test');\n\n            return $extender;\n        });\n\n        $this->get(URL::route('waterhole.register'))->assertSeeText(ExtendTestPersistField::MARKER);\n\n        $this->post(URL::route('waterhole.register.submit'), [\n            'name' => 'Test User',\n            'email' => 'test@example.com',\n            'password' => 'password',\n        ])->assertSessionHasErrors('extend_test_headline');\n\n        $this->post(URL::route('waterhole.register.submit'), [\n            'name' => 'Test User',\n            'email' => 'test@example.com',\n            'password' => 'password',\n            'extend_test_headline' => 'Extended headline',\n        ])->assertRedirect(URL::route('waterhole.home'));\n\n        $user = User::where('email', 'test@example.com')->firstOrFail();\n\n        expect($user->headline)->toBe('Extended headline');\n        expect(ExtendTestPersistField::$savedCalled)->toBeTrue();\n        expect(ExtendTestPersistField::$savedUserId)->toBe($user->id);\n    });\n});\n\ndataset('form_extenders', [\n    'channel form' => [\n        Extend\\Forms\\ChannelForm::class,\n        fn($extender, $marker) => $extender->add(ExtendTestRenderField::class, 'extend-test'),\n        fn($test) => $test\n            ->actingAs(extendTestAdminUser())\n            ->get(URL::route('waterhole.cp.structure.channels.create')),\n    ],\n    'page form' => [\n        Extend\\Forms\\PageForm::class,\n        fn($extender, $marker) => $extender->add(ExtendTestRenderField::class, 'extend-test'),\n        fn($test) => $test\n            ->actingAs(extendTestAdminUser())\n            ->get(URL::route('waterhole.cp.structure.pages.create')),\n    ],\n    'structure link form' => [\n        Extend\\Forms\\StructureLinkForm::class,\n        fn($extender, $marker) => $extender->add(ExtendTestRenderField::class, 'extend-test'),\n        fn($test) => $test\n            ->actingAs(extendTestAdminUser())\n            ->get(URL::route('waterhole.cp.structure.links.create')),\n    ],\n    'taxonomy form' => [\n        Extend\\Forms\\TaxonomyForm::class,\n        fn($extender, $marker) => $extender->add(ExtendTestRenderField::class, 'extend-test'),\n        fn($test) => $test\n            ->actingAs(extendTestAdminUser())\n            ->get(URL::route('waterhole.cp.taxonomies.create')),\n    ],\n    'tag form' => [\n        Extend\\Forms\\TagForm::class,\n        fn($extender, $marker) => $extender->add(ExtendTestRenderField::class, 'extend-test'),\n        function ($test) {\n            $taxonomy = Taxonomy::create(['name' => 'Test Taxonomy']);\n\n            return $test\n                ->actingAs(extendTestAdminUser())\n                ->get(URL::route('waterhole.cp.taxonomies.tags.create', $taxonomy));\n        },\n    ],\n    'group form' => [\n        Extend\\Forms\\GroupForm::class,\n        fn($extender, $marker) => $extender->add(ExtendTestRenderField::class, 'extend-test'),\n        fn($test) => $test\n            ->actingAs(extendTestAdminUser())\n            ->get(URL::route('waterhole.cp.groups.create')),\n    ],\n    'user form' => [\n        Extend\\Forms\\UserForm::class,\n        fn($extender, $marker) => $extender->add(ExtendTestRenderField::class, 'extend-test'),\n        fn($test) => $test\n            ->actingAs(extendTestAdminUser())\n            ->get(URL::route('waterhole.cp.users.create')),\n    ],\n    'reaction set form' => [\n        Extend\\Forms\\ReactionSetForm::class,\n        fn($extender, $marker) => $extender->add(ExtendTestRenderField::class, 'extend-test'),\n        fn($test) => $test\n            ->actingAs(extendTestAdminUser())\n            ->get(URL::route('waterhole.cp.reaction-sets.create')),\n    ],\n    'reaction type form' => [\n        Extend\\Forms\\ReactionTypeForm::class,\n        fn($extender, $marker) => $extender->add(ExtendTestRenderField::class, 'extend-test'),\n        function ($test) {\n            $reactionSet = ReactionSet::create(['name' => 'Test Reaction Set']);\n\n            return $test->actingAs(extendTestAdminUser())->get(\n                URL::route('waterhole.cp.reaction-sets.reaction-types.create', [\n                    'reactionSet' => $reactionSet,\n                ]),\n            );\n        },\n    ],\n    'registration form' => [\n        Extend\\Forms\\RegistrationForm::class,\n        fn($extender, $marker) => $extender->add(ExtendTestRenderField::class, 'extend-test'),\n        fn($test) => $test->get(URL::route('waterhole.register')),\n    ],\n    'post form' => [\n        Extend\\Forms\\PostForm::class,\n        fn($extender, $marker) => $extender->add(ExtendTestRenderField::class, 'extend-test'),\n        function ($test) {\n            $channel = Channel::factory()->public()->create();\n\n            return $test\n                ->actingAs(extendTestAdminUser())\n                ->get(URL::route('waterhole.posts.create', ['channel_id' => $channel->id]));\n        },\n    ],\n]);\n\nclass ExtendTestRenderField extends Field\n{\n    public const MARKER = 'Extend Test Field';\n\n    public function __construct(public ?Model $model) {}\n\n    public function render(): string\n    {\n        return '<div>' . self::MARKER . '</div>';\n    }\n}\n\nclass ExtendTestPersistField extends Field\n{\n    public const MARKER = 'Extend Test Headline';\n\n    public static bool $savedCalled = false;\n    public static ?int $savedUserId = null;\n\n    public static function reset(): void\n    {\n        self::$savedCalled = false;\n        self::$savedUserId = null;\n    }\n\n    public function __construct(public ?User $model) {}\n\n    public function render(): string\n    {\n        return <<<'blade'\n            <x-waterhole::field name=\"extend_test_headline\" label=\"Extend Test Headline\">\n                <input\n                    type=\"text\"\n                    name=\"extend_test_headline\"\n                    id=\"{{ $component->id }}\"\n                    value=\"{{ old('extend_test_headline', $model->headline ?? '') }}\"\n                >\n            </x-waterhole::field>\n        blade;\n    }\n\n    public function validating(Validator $validator): void\n    {\n        $validator->addRules([\n            'extend_test_headline' => ['required', 'string', 'max:255'],\n        ]);\n    }\n\n    public function saving(FormRequest $request): void\n    {\n        $this->model->headline = $request->validated('extend_test_headline');\n    }\n\n    public function saved(FormRequest $request): void\n    {\n        self::$savedCalled = true;\n        self::$savedUserId = $this->model->id;\n    }\n}\n"
  },
  {
    "path": "tests/Feature/ExtendQueryTest.php",
    "content": "<?php\n\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Foundation\\Testing\\RefreshDatabase;\nuse Illuminate\\Support\\Facades\\URL;\nuse Waterhole\\Database\\Seeders\\GroupsSeeder;\nuse Waterhole\\Extend;\nuse Waterhole\\Models\\Channel;\nuse Waterhole\\Models\\Post;\n\nuses(RefreshDatabase::class);\n\nbeforeEach(function () {\n    $this->seed(GroupsSeeder::class);\n});\n\ndescribe('Query extenders', function () {\n    test('extend post feed query', function () {\n        extend(function (Extend\\Query\\PostFeedQuery $queries) {\n            $queries->add(function (Builder $query) {\n                $query->where('posts.title', 'Extend Test Feed Scope');\n            });\n        });\n\n        $channel = Channel::factory()->public()->create();\n\n        Post::factory()\n            ->for($channel)\n            ->create(['title' => 'Extend Test Feed Scope']);\n        Post::factory()\n            ->for($channel)\n            ->create(['title' => 'Other Post']);\n\n        $this->get(URL::route('waterhole.channels.show', $channel))\n            ->assertSeeText('Extend Test Feed Scope')\n            ->assertDontSeeText('Other Post');\n    });\n\n    test('add post visibility scope', function () {\n        extend(function (Extend\\Query\\PostVisibilityScopes $scopes) {\n            $scopes->add(function (Builder $query) {\n                $query->where('posts.title', '!=', 'Extend Test Hidden');\n            }, 'extend-test');\n        });\n\n        $channel = Channel::factory()->public()->create();\n\n        Post::factory()\n            ->for($channel)\n            ->create(['title' => 'Extend Test Hidden']);\n        Post::factory()\n            ->for($channel)\n            ->create(['title' => 'Visible Post']);\n\n        $this->get(URL::route('waterhole.channels.show', $channel))\n            ->assertSeeText('Visible Post')\n            ->assertDontSeeText('Extend Test Hidden');\n    });\n});\n"
  },
  {
    "path": "tests/Feature/ExtendRoutingTest.php",
    "content": "<?php\n\nuse Illuminate\\Foundation\\Testing\\RefreshDatabase;\nuse Illuminate\\Routing\\RouteCollection;\nuse Illuminate\\Support\\Facades\\Route;\nuse Waterhole\\Database\\Seeders\\GroupsSeeder;\nuse Waterhole\\Extend;\nuse Waterhole\\Models\\Group;\nuse Waterhole\\Models\\User;\nuse Waterhole\\Providers\\RouteServiceProvider;\n\nuses(RefreshDatabase::class);\n\nbeforeEach(function () {\n    $this->seed(GroupsSeeder::class);\n});\n\ndescribe('Routing extenders', function () {\n    test('add api route', function () {\n        extend(function (Extend\\Routing\\ApiRoutes $routes) {\n            Route::get('extend-test', fn() => response()->json(['ok' => true]));\n        });\n\n        Route::setRoutes(new RouteCollection());\n        app()->register(RouteServiceProvider::class, true);\n\n        $this->get('/api/extend-test')\n            ->assertOk()\n            ->assertJson(['ok' => true]);\n    });\n\n    test('add forum route', function () {\n        extend(function (Extend\\Routing\\ForumRoutes $routes) {\n            Route::get('extend-test', fn() => 'ok');\n        });\n\n        Route::setRoutes(new RouteCollection());\n        app()->register(RouteServiceProvider::class, true);\n\n        $this->get('/extend-test')->assertSeeText('ok');\n    });\n\n    test('add cp route', function () {\n        extend(function (Extend\\Routing\\CpRoutes $routes) {\n            Route::get('extend-test', fn() => 'ok');\n        });\n\n        Route::setRoutes(new RouteCollection());\n        app()->register(RouteServiceProvider::class, true);\n\n        $admin = User::factory()->create();\n        $admin->groups()->attach(Group::ADMIN_ID);\n\n        $this->actingAs($admin)->get('/cp/extend-test')->assertSeeText('ok');\n    });\n});\n"
  },
  {
    "path": "tests/Feature/ExtendUiTest.php",
    "content": "<?php\n\nuse Illuminate\\Foundation\\Testing\\RefreshDatabase;\nuse Illuminate\\Support\\Facades\\URL;\nuse Illuminate\\Support\\HtmlString;\nuse Waterhole\\Database\\Seeders\\GroupsSeeder;\nuse Waterhole\\Extend;\nuse Waterhole\\Models\\Channel;\nuse Waterhole\\Models\\Comment;\nuse Waterhole\\Models\\Group;\nuse Waterhole\\Models\\Post;\nuse Waterhole\\Models\\User;\n\nuses(RefreshDatabase::class);\n\nbeforeEach(function () {\n    $this->seed(GroupsSeeder::class);\n});\n\ndescribe('Ui extenders', function () {\n    test('extend index page components', function () {\n        extend(function (Extend\\Ui\\IndexPage $index) {\n            $index->sidebar->add(new HtmlString('Extend Index Sidebar'));\n            $index->footer->add(new HtmlString('Extend Index Footer'));\n        });\n\n        extend(function (Extend\\Ui\\PostFeed $feed) {\n            $feed->header->add(new HtmlString('Extend Feed Header'));\n            $feed->toolbar->add(new HtmlString('Extend Feed Toolbar'));\n        });\n\n        extend(function (Extend\\Ui\\PostListItem $items) {\n            $items->info->add(new HtmlString('Extend List Info'));\n            $items->secondary->add(new HtmlString('Extend List Secondary'));\n        });\n\n        extend(function (Extend\\Ui\\PostAttributes $attributes) {\n            $attributes->add(fn(Post $post) => ['data-marker' => 'extend-post-attr']);\n        });\n\n        extend(function (Extend\\Ui\\DocumentHead $head) {\n            $head->add(new HtmlString('<meta name=\"extend-test\" content=\"1\">'));\n        });\n\n        extend(function (Extend\\Ui\\UserMenu $menu) {\n            $menu->add(new HtmlString('Extend User Menu'));\n        });\n\n        $channel = Channel::factory()->public()->create();\n\n        Post::factory()\n            ->for($channel)\n            ->create(['title' => 'Index Post']);\n\n        $user = User::factory()->create();\n\n        $this->actingAs($user)\n            ->get('/')\n            ->assertSeeText('Extend Index Sidebar')\n            ->assertSeeText('Extend Index Footer')\n            ->assertSeeText('Extend Feed Header')\n            ->assertSeeText('Extend Feed Toolbar')\n            ->assertSeeText('Extend List Info')\n            ->assertSeeText('Extend List Secondary')\n            ->assertSeeHtml('data-marker=\"extend-post-attr\"')\n            ->assertSeeHtml('<meta name=\"extend-test\" content=\"1\">')\n            ->assertSeeText('Extend User Menu');\n    });\n\n    test('extend post page components', function () {\n        extend(function (Extend\\Ui\\PostPage $page) {\n            $page->header->add(new HtmlString('Extend Post Header'));\n            $page->sidebar->add(new HtmlString('Extend Post Sidebar'));\n            $page->middle->add(new HtmlString('Extend Post Middle'));\n            $page->bottom->add(new HtmlString('Extend Post Bottom'));\n        });\n\n        extend(function (Extend\\Ui\\PostFooter $footer) {\n            $footer->add(new HtmlString('Extend Post Footer'));\n        });\n\n        extend(function (Extend\\Ui\\CommentComponent $comments) {\n            $comments->header->add(new HtmlString('Extend Comment Header'));\n            $comments->footer->add(new HtmlString('Extend Comment Footer'));\n            $comments->buttons->add(new HtmlString('Extend Comment Buttons'));\n        });\n\n        extend(function (Extend\\Ui\\CommentAttributes $attributes) {\n            $attributes->add(\n                fn(Comment $comment) => ['data-comment-marker' => 'extend-comment-attr'],\n            );\n        });\n\n        extend(function (Extend\\Ui\\PostAttributes $attributes) {\n            $attributes->add(fn(Post $post) => ['data-post-marker' => 'extend-post-page-attr']);\n        });\n\n        $post = Post::factory()\n            ->for(Channel::factory()->public())\n            ->has(Comment::factory())\n            ->create();\n\n        $this->get(URL::route('waterhole.posts.show', $post))\n            ->assertSeeText('Extend Post Header')\n            ->assertSeeText('Extend Post Sidebar')\n            ->assertSeeText('Extend Post Middle')\n            ->assertSeeText('Extend Post Bottom')\n            ->assertSeeText('Extend Post Footer')\n            ->assertSeeText('Extend Comment Header')\n            ->assertSeeText('Extend Comment Footer')\n            ->assertSeeText('Extend Comment Buttons')\n            ->assertSeeHtml('data-comment-marker=\"extend-comment-attr\"')\n            ->assertSeeHtml('data-post-marker=\"extend-post-page-attr\"');\n    });\n\n    test('extend user profile components', function () {\n        extend(function (Extend\\Ui\\UserNav $nav) {\n            $nav->add(new HtmlString('Extend User Nav'));\n        });\n\n        extend(function (Extend\\Ui\\UserInfo $info) {\n            $info->add(new HtmlString('Extend User Info'));\n        });\n\n        $user = User::factory()->create();\n\n        $this->get(URL::route('waterhole.user.posts', $user))\n            ->assertSeeText('Extend User Nav')\n            ->assertSeeText('Extend User Info');\n    });\n\n    test('extend text editor components', function () {\n        extend(function (Extend\\Ui\\TextEditor $editor) {\n            $editor->add(new HtmlString('Extend Text Editor'));\n        });\n\n        $channel = Channel::factory()->public()->create();\n\n        $admin = User::factory()->create();\n        $admin->groups()->attach(Group::ADMIN_ID);\n\n        $this->actingAs($admin)\n            ->get(URL::route('waterhole.posts.create', ['channel_id' => $channel->id]))\n            ->assertSeeText('Extend Text Editor');\n    });\n\n    test('extend cp components', function () {\n        extend(function (Extend\\Ui\\CpNav $nav) {\n            $nav->add(new HtmlString('Extend Cp Nav'));\n        });\n\n        extend(function (Extend\\Ui\\CpAlerts $alerts) {\n            $alerts->add(new HtmlString('Extend Cp Alert'));\n        });\n\n        $admin = User::factory()->create();\n        $admin->groups()->attach(Group::ADMIN_ID);\n\n        $this->actingAs($admin)\n            ->get(URL::route('waterhole.cp.dashboard'))\n            ->assertSeeText('Extend Cp Nav')\n            ->assertSeeText('Extend Cp Alert');\n    });\n\n    test('extend login page components', function () {\n        extend(function (Extend\\Ui\\LoginPage $login) {\n            $login->add(new HtmlString('Extend Login Page'));\n        });\n\n        $this->get('login')->assertSeeText('Extend Login Page');\n    });\n\n    test('extend preferences components', function () {\n        $marker = 'Extend Test Preference';\n\n        extend(function (Extend\\Ui\\Preferences $preferences) use ($marker) {\n            $preferences->account->add(new HtmlString($marker), 'extend-test');\n        });\n\n        $this->actingAs(User::factory()->create())\n            ->get(URL::route('waterhole.preferences.account'))\n            ->assertSeeText($marker);\n    });\n});\n\ndescribe('Layout extender', function () {\n    test('add header component', function () {\n        extend(function (Extend\\Ui\\Layout $layout) {\n            $layout->header->add(new HtmlString('hello world'));\n        });\n\n        $this->get('login')->assertSeeText('hello world');\n    });\n\n    test('add before component', function () {\n        extend(function (Extend\\Ui\\Layout $layout) {\n            $layout->before->add(new HtmlString('hello world'));\n        });\n\n        $this->get('login')->assertSeeText('hello world');\n    });\n\n    test('add after component', function () {\n        extend(function (Extend\\Ui\\Layout $layout) {\n            $layout->after->add(new HtmlString('hello world'));\n        });\n\n        $this->get('login')->assertSeeText('hello world');\n    });\n});\n"
  },
  {
    "path": "tests/Feature/JsonApiTest.php",
    "content": "<?php\n\nuse Illuminate\\Foundation\\Testing\\RefreshDatabase;\nuse Illuminate\\Routing\\RouteCollection;\nuse Illuminate\\Support\\Facades\\Route;\nuse Laravel\\Sanctum\\Sanctum;\nuse Waterhole\\Database\\Seeders\\DefaultSeeder;\nuse Waterhole\\Database\\Seeders\\GroupsSeeder;\nuse Waterhole\\Models\\Channel;\nuse Waterhole\\Models\\Comment;\nuse Waterhole\\Models\\Group;\nuse Waterhole\\Models\\Page;\nuse Waterhole\\Models\\Post;\nuse Waterhole\\Models\\ReactionType;\nuse Waterhole\\Models\\StructureHeading;\nuse Waterhole\\Models\\StructureLink;\nuse Waterhole\\Models\\Tag;\nuse Waterhole\\Models\\Taxonomy;\nuse Waterhole\\Models\\User;\nuse Waterhole\\Providers\\RouteServiceProvider;\n\nuses(RefreshDatabase::class);\n\nbeforeEach(function () {\n    $this->seed(GroupsSeeder::class);\n});\n\ndescribe('api/channels', function () {\n    test('retrieve channel', function () {\n        $channel = Channel::factory()->public()->create();\n\n        $response = jsonApi('GET', \"/api/channels/$channel->id\");\n\n        $response->assertOk();\n        $response->assertJson([\n            'data' => [\n                'type' => 'channels',\n                'id' => $channel->id,\n                'attributes' => ['name' => $channel->name, 'url' => $channel->url],\n            ],\n        ]);\n    });\n\n    test('retrieve channel user state', function () {\n        $this->actingAs(User::factory()->create());\n\n        $channel = Channel::factory()->public()->create();\n\n        $response = jsonApi('GET', \"/api/channels/$channel->id?include=userState\");\n\n        $response->assertOk();\n        $response->assertJson([\n            'included' => [['type' => 'channelUsers', 'attributes' => ['notifications' => null]]],\n        ]);\n    });\n});\n\ndescribe('api/comments', function () {\n    test('list comments', function () {\n        Post::factory()\n            ->for(Channel::factory()->public())\n            ->has(Comment::factory(2))\n            ->create();\n\n        $response = jsonApi('GET', '/api/comments');\n\n        $response->assertOk();\n        $response->assertJsonCount(2, 'data');\n    });\n\n    test('retrieve comment', function () {\n        $comment = Comment::factory()\n            ->for(Post::factory()->for(Channel::factory()->public()))\n            ->create();\n\n        $response = jsonApi('GET', \"/api/comments/$comment->id\");\n\n        $response->assertOk();\n        $response->assertJson(['data' => ['type' => 'comments', 'id' => $comment->id]]);\n    });\n\n    test('hides deletedBy for comments from author', function () {\n        $author = User::factory()->create();\n        $moderator = User::factory()->create();\n        $channel = Channel::factory()->public()->create();\n        $channel->savePermissions([\n            'group:1' => ['view' => true],\n            \"user:{$moderator->id}\" => ['moderate' => true],\n        ]);\n\n        $post = Post::factory()->for($channel)->create();\n        $comment = Comment::factory()->for($author)->for($post)->create();\n        $comment->update([\n            'deleted_by' => $moderator->id,\n            'deleted_at' => now(),\n        ]);\n\n        $this->actingAs($author);\n\n        $response = jsonApi('GET', \"/api/comments/$comment->id\");\n\n        $response->assertOk();\n        $response->assertJsonMissingPath('data.relationships.deletedBy');\n    });\n\n    test('shows deletedBy for comments to moderators', function () {\n        $moderator = User::factory()->create();\n        $channel = Channel::factory()->public()->create();\n        $channel->savePermissions([\n            'group:1' => ['view' => true],\n            \"user:{$moderator->id}\" => ['moderate' => true],\n        ]);\n\n        $post = Post::factory()->for($channel)->create();\n        $comment = Comment::factory()->for($post)->create();\n        $comment->update([\n            'deleted_by' => $moderator->id,\n            'deleted_at' => now(),\n        ]);\n\n        $this->actingAs($moderator);\n\n        $response = jsonApi('GET', \"/api/comments/$comment->id\");\n\n        $response->assertOk();\n        $response->assertJsonPath('data.relationships.deletedBy.data.id', (string) $moderator->id);\n    });\n});\n\ndescribe('api/groups', function () {\n    test('list groups', function () {\n        $response = jsonApi('GET', '/api/groups');\n\n        $response->assertOk();\n        $response->assertJsonCount(1, 'data');\n    });\n\n    test('retrieve group', function () {\n        $group = Group::custom()->firstOrFail();\n\n        $response = jsonApi('GET', \"/api/groups/$group->id\");\n\n        $response->assertOk();\n        $response->assertJson(['data' => ['type' => 'groups', 'id' => $group->id]]);\n    });\n});\n\ndescribe('api/pages', function () {\n    test('retrieve page', function () {\n        $page = Page::factory()->public()->create();\n\n        $response = jsonApi('GET', \"/api/pages/$page->id\");\n\n        $response->assertOk();\n        $response->assertJson(['data' => ['type' => 'pages', 'id' => $page->id]]);\n    });\n});\n\ndescribe('api/posts', function () {\n    test('list posts', function () {\n        Post::factory(2)\n            ->for(Channel::factory()->public())\n            ->create();\n\n        $response = jsonApi('GET', '/api/posts?include=channel');\n\n        $response->assertOk();\n        $response->assertJsonCount(2, 'data');\n        $response->assertJsonCount(1, 'included');\n    });\n\n    test('retrieve post', function () {\n        $post = Post::factory()\n            ->for(Channel::factory()->public())\n            ->create();\n\n        $response = jsonApi('GET', \"/api/posts/$post->id\");\n\n        $response->assertOk();\n        $response->assertJson(['data' => ['type' => 'posts', 'id' => $post->id]]);\n    });\n\n    test('hides deletedBy for posts from author', function () {\n        $author = User::factory()->create();\n        $moderator = User::factory()->create();\n        $channel = Channel::factory()->create();\n        $channel->savePermissions([\n            'group:1' => ['view' => true],\n            \"user:{$moderator->id}\" => ['moderate' => true],\n        ]);\n\n        $post = Post::factory()->for($author)->for($channel)->create();\n        $post->update([\n            'deleted_by' => $moderator->id,\n            'deleted_at' => now(),\n        ]);\n\n        $this->actingAs($author);\n\n        $response = jsonApi('GET', \"/api/posts/$post->id\");\n\n        $response->assertOk();\n        $response->assertJsonMissingPath('data.relationships.deletedBy');\n    });\n\n    test('shows deletedBy for posts to moderators', function () {\n        $moderator = User::factory()->create();\n        $channel = Channel::factory()->public()->create();\n        $channel->savePermissions([\n            'group:1' => ['view' => true],\n            \"user:{$moderator->id}\" => ['moderate' => true],\n        ]);\n\n        $post = Post::factory()->for($channel)->create();\n        $post->update([\n            'deleted_by' => $moderator->id,\n            'deleted_at' => now(),\n        ]);\n\n        $this->actingAs($moderator);\n\n        $response = jsonApi('GET', \"/api/posts/$post->id\");\n\n        $response->assertOk();\n        $response->assertJsonPath('data.relationships.deletedBy.data.id', (string) $moderator->id);\n    });\n\n    test('retrieve post user state', function () {\n        $this->actingAs(User::factory()->create());\n\n        $post = Post::factory()\n            ->for(Channel::factory()->public())\n            ->create();\n\n        $response = jsonApi('GET', \"/api/posts/$post->id?include=userState\");\n\n        $response->assertOk();\n        $response->assertJson([\n            'included' => [['type' => 'postUsers', 'attributes' => ['notifications' => null]]],\n        ]);\n    });\n\n    test('retrieve post tags and taxonomies', function () {\n        $admin = User::factory()->create();\n        $admin->groups()->attach(Group::ADMIN_ID);\n        $this->actingAs($admin);\n\n        $taxonomy = Taxonomy::create(['name' => 'Topics', 'allow_multiple' => true]);\n        $tag = Tag::create(['name' => 'Feature', 'taxonomy_id' => $taxonomy->id]);\n\n        $post = Post::factory()\n            ->for(Channel::factory()->public())\n            ->create();\n\n        $post->tags()->attach($tag);\n\n        $response = jsonApi('GET', \"/api/posts/$post->id?include=tags,tags.taxonomy\");\n\n        $response->assertOk();\n        $response->assertJsonFragment(['type' => 'tags', 'id' => (string) $tag->id]);\n        $response->assertJsonFragment(['type' => 'taxonomies', 'id' => (string) $taxonomy->id]);\n    });\n\n    test('retrieve post reactions and reaction counts', function () {\n        $this->seed(DefaultSeeder::class);\n\n        $user = User::factory()->create();\n        $reactionType = ReactionType::firstOrFail();\n\n        $post = Post::factory()\n            ->for(Channel::factory()->public())\n            ->create();\n\n        $reaction = $post->reactions()->create([\n            'reaction_type_id' => $reactionType->id,\n            'user_id' => $user->id,\n        ]);\n\n        $response = jsonApi(\n            'GET',\n            \"/api/posts/$post->id?include=reactions,reactionCounts,reactionCounts.reactionType\",\n        );\n\n        $response->assertOk();\n        $response->assertJsonFragment(['type' => 'reactions', 'id' => (string) $reaction->id]);\n        $response->assertJsonFragment(['type' => 'reactionCounts']);\n        $response->assertJsonFragment([\n            'type' => 'reactionTypes',\n            'id' => (string) $reactionType->id,\n        ]);\n    });\n});\n\ndescribe('api/structure', function () {\n    test('list structure', function () {\n        Channel::factory()->public()->create();\n\n        Page::factory()->public()->create();\n\n        $response = jsonApi('GET', '/api/structure?include=content');\n\n        $response->assertOk();\n        $response->assertJsonCount(2, 'data');\n        $response->assertJsonCount(2, 'included');\n    });\n});\n\ndescribe('api/structure headings and links', function () {\n    test('retrieve structure heading', function () {\n        $heading = StructureHeading::create(['name' => 'Heading']);\n\n        $response = jsonApi('GET', \"/api/structureHeadings/$heading->id\");\n\n        $response->assertOk();\n        $response->assertJson([\n            'data' => [\n                'type' => 'structureHeadings',\n                'id' => $heading->id,\n            ],\n        ]);\n    });\n\n    test('retrieve structure link', function () {\n        $link = StructureLink::create([\n            'name' => 'Waterhole',\n            'href' => 'https://waterhole.dev',\n        ]);\n\n        $link->savePermissions(['group:1' => ['view' => true]]);\n\n        $response = jsonApi('GET', \"/api/structureLinks/$link->id\");\n\n        $response->assertOk();\n        $response->assertJson([\n            'data' => [\n                'type' => 'structureLinks',\n                'id' => $link->id,\n            ],\n        ]);\n    });\n});\n\ndescribe('api/users', function () {\n    test('list users', function () {\n        User::factory()->create();\n\n        $this->actingAs(User::factory()->admin()->create());\n\n        $response = jsonApi('GET', '/api/users');\n\n        $response->assertOk();\n        $response->assertJsonCount(2, 'data');\n    });\n\n    test('retrieve user', function () {\n        $user = User::factory()->create();\n\n        $response = jsonApi('GET', \"/api/users/$user->id\");\n\n        $response->assertOk();\n        $response->assertJson(['data' => ['type' => 'users', 'id' => $user->id]]);\n    });\n\n    test('hides private user fields from guests', function () {\n        $user = User::factory()->create([\n            'locale' => 'fr',\n            'show_online' => false,\n            'last_seen_at' => now()->subHour(),\n            'suspended_until' => now()->addDay(),\n        ]);\n\n        $response = jsonApi('GET', \"/api/users/$user->id\");\n\n        $response->assertOk();\n        $response->assertJsonMissingPath('data.attributes.email');\n        $response->assertJsonMissingPath('data.attributes.locale');\n        $response->assertJsonMissingPath('data.attributes.lastSeenAt');\n        $response->assertJsonMissingPath('data.attributes.suspendedUntil');\n    });\n\n    test('shows private user fields to the user', function () {\n        $user = User::factory()->create([\n            'locale' => 'fr',\n            'show_online' => false,\n            'last_seen_at' => now()->subHour(),\n            'suspended_until' => now()->addDay(),\n        ]);\n\n        $this->actingAs($user);\n\n        $response = jsonApi('GET', \"/api/users/$user->id\");\n\n        $response->assertOk();\n        $response->assertJsonPath('data.attributes.email', $user->email);\n        $response->assertJsonPath('data.attributes.locale', $user->locale);\n        $response->assertJsonPath(\n            'data.attributes.lastSeenAt',\n            $user->last_seen_at->toIso8601String(),\n        );\n        $response->assertJsonPath(\n            'data.attributes.suspendedUntil',\n            $user->suspended_until->toIso8601String(),\n        );\n    });\n\n    test('shows private user fields to admins', function () {\n        $admin = User::factory()->create();\n        $admin->groups()->attach(Group::ADMIN_ID);\n\n        $user = User::factory()->create([\n            'locale' => 'fr',\n            'show_online' => false,\n            'last_seen_at' => now()->subHour(),\n            'suspended_until' => now()->addDay(),\n        ]);\n\n        $this->actingAs($admin);\n\n        $response = jsonApi('GET', \"/api/users/$user->id\");\n\n        $response->assertOk();\n        $response->assertJsonPath('data.attributes.email', $user->email);\n        $response->assertJsonPath('data.attributes.locale', $user->locale);\n        $response->assertJsonPath(\n            'data.attributes.lastSeenAt',\n            $user->last_seen_at->toIso8601String(),\n        );\n        $response->assertJsonPath(\n            'data.attributes.suspendedUntil',\n            $user->suspended_until->toIso8601String(),\n        );\n    });\n});\n\ndescribe('api config', function () {\n    test('disables api routes when disabled', function () {\n        config(['waterhole.api.enabled' => false]);\n\n        Route::setRoutes(new RouteCollection());\n        app()->register(RouteServiceProvider::class, true);\n\n        $this->get('/api/posts')->assertNotFound();\n    });\n\n    test('uses configured api path', function () {\n        config([\n            'waterhole.api.enabled' => true,\n            'waterhole.api.path' => 'v2',\n        ]);\n\n        Route::setRoutes(new RouteCollection());\n        app()->register(RouteServiceProvider::class, true);\n\n        jsonApi('GET', '/api/posts')->assertNotFound();\n        jsonApi('GET', '/v2/posts')->assertOk();\n    });\n\n    test('applies public api middleware settings', function () {\n        config([\n            'waterhole.api.enabled' => true,\n            'waterhole.api.public' => false,\n        ]);\n\n        Route::setRoutes(new RouteCollection());\n        app()->register(RouteServiceProvider::class, true);\n\n        $response = jsonApi('GET', '/api/posts');\n\n        expect($response->getStatusCode())->toBeIn([401, 403]);\n    });\n\n    test('supports sanctum token auth', function () {\n        config([\n            'waterhole.api.enabled' => true,\n            'waterhole.api.public' => false,\n        ]);\n\n        Route::setRoutes(new RouteCollection());\n        app()->register(RouteServiceProvider::class, true);\n\n        Sanctum::actingAs(User::factory()->create(), ['waterhole']);\n\n        jsonApi('GET', '/api/posts')->assertOk();\n    });\n});\n"
  },
  {
    "path": "tests/Feature/Permissions/CommentPermissionsTest.php",
    "content": "<?php\n\nuse Illuminate\\Foundation\\Testing\\RefreshDatabase;\nuse Illuminate\\Support\\Facades\\Gate;\nuse Waterhole\\Database\\Seeders\\GroupsSeeder;\nuse Waterhole\\Models\\Channel;\nuse Waterhole\\Models\\Comment;\nuse Waterhole\\Models\\Post;\nuse Waterhole\\Models\\User;\n\nuses(RefreshDatabase::class);\n\nbeforeEach(function () {\n    $this->seed(GroupsSeeder::class);\n});\n\ndescribe('gate', function () {\n    test('comment edit time limit applies to authors', function () {\n        config(['waterhole.forum.edit_time_limit' => 5]);\n\n        $user = User::factory()->create();\n        $channel = Channel::factory()->public()->create();\n\n        $post = Post::factory()\n            ->for($channel)\n            ->for($user)\n            ->create([\n                'created_at' => now()->subMinutes(10),\n                'last_activity_at' => now()->subMinutes(10),\n            ]);\n\n        $comment = Comment::factory()\n            ->for($post)\n            ->for($user)\n            ->create([\n                'created_at' => now()->subMinutes(10),\n            ]);\n\n        $recentComment = Comment::factory()\n            ->for($post)\n            ->for($user)\n            ->create([\n                'created_at' => now()->subMinutes(3),\n            ]);\n\n        expect(Gate::forUser($user)->allows('waterhole.comment.edit', $comment))->toBeFalse();\n        expect(Gate::forUser($user)->allows('waterhole.comment.edit', $recentComment))->toBeTrue();\n    });\n});\n\ndescribe('api', function () {\n    test('comments on posts in private channels are not visible', function () {\n        $channel = Channel::factory()->create();\n        $post = Post::factory()->for($channel)->create();\n        $comment = Comment::factory()->for($post)->create();\n\n        jsonApi('GET', \"/api/comments/$comment->id\")->assertNotFound();\n    });\n});\n\ndescribe('console', function () {\n    test(\n        'comments on posts in private channels are visible without an authenticated user',\n        function () {\n            $channel = Channel::factory()->create();\n            $post = Post::factory()->for($channel)->create();\n            $comment = Comment::factory()->for($post)->create();\n\n            $env = app()['env'];\n            app()->instance('env', 'production');\n\n            try {\n                auth()->logout();\n                expect(Comment::query()->whereKey($comment)->exists())->toBeTrue();\n            } finally {\n                app()->instance('env', $env);\n            }\n        },\n    );\n});\n"
  },
  {
    "path": "tests/Feature/Permissions/GlobalPermissionsTest.php",
    "content": "<?php\n\nuse Illuminate\\Foundation\\Testing\\RefreshDatabase;\nuse Illuminate\\Support\\Facades\\Gate;\nuse Waterhole\\Database\\Seeders\\GroupsSeeder;\nuse Waterhole\\Models\\Channel;\nuse Waterhole\\Models\\Group;\nuse Waterhole\\Models\\User;\n\nuses(RefreshDatabase::class);\n\nbeforeEach(function () {\n    $this->seed(GroupsSeeder::class);\n});\n\ndescribe('gate before', function () {\n    test('unverified users are treated as guests', function () {\n        $memberOnly = Channel::factory()->create();\n        $memberOnly->savePermissions(['group:2' => ['view' => true]]);\n\n        $public = Channel::factory()->public()->create();\n\n        $user = User::factory()->create(['email_verified_at' => null]);\n\n        expect(Gate::forUser($user)->allows('waterhole.channel.view', $memberOnly))->toBeFalse();\n        expect(Gate::forUser($user)->allows('waterhole.channel.view', $public))->toBeTrue();\n    });\n\n    test('suspended users are treated as guests', function () {\n        $memberOnly = Channel::factory()->create();\n        $memberOnly->savePermissions(['group:2' => ['view' => true]]);\n\n        $public = Channel::factory()->public()->create();\n\n        $user = User::factory()->create(['suspended_until' => now()->addDay()]);\n\n        expect(Gate::forUser($user)->allows('waterhole.channel.view', $memberOnly))->toBeFalse();\n        expect(Gate::forUser($user)->allows('waterhole.channel.view', $public))->toBeTrue();\n    });\n});\n\ndescribe('cp access', function () {\n    test('non-admin users are forbidden', function () {\n        $user = User::factory()->create(['email_verified_at' => now()]);\n\n        $this->actingAs($user)->get(route('waterhole.cp.dashboard'))->assertForbidden();\n    });\n\n    test('admins are allowed', function () {\n        $admin = User::factory()->create(['email_verified_at' => now()]);\n        $admin->groups()->attach(Group::ADMIN_ID);\n\n        $this->actingAs($admin)->get(route('waterhole.cp.dashboard'))->assertOk();\n    });\n});\n"
  },
  {
    "path": "tests/Feature/Permissions/PostPermissionsTest.php",
    "content": "<?php\n\nuse Illuminate\\Foundation\\Testing\\RefreshDatabase;\nuse Illuminate\\Support\\Facades\\Gate;\nuse Waterhole\\Database\\Seeders\\GroupsSeeder;\nuse Waterhole\\Models\\Channel;\nuse Waterhole\\Models\\Post;\nuse Waterhole\\Models\\User;\n\nuses(RefreshDatabase::class);\n\nbeforeEach(function () {\n    $this->seed(GroupsSeeder::class);\n});\n\ndescribe('gate', function () {\n    test('post edit time limit applies to authors', function () {\n        config(['waterhole.forum.edit_time_limit' => 5]);\n\n        $user = User::factory()->create();\n        $channel = Channel::factory()->public()->create();\n\n        $post = Post::factory()\n            ->for($channel)\n            ->for($user)\n            ->create([\n                'created_at' => now()->subMinutes(10),\n                'last_activity_at' => now()->subMinutes(10),\n            ]);\n\n        $recentPost = Post::factory()\n            ->for($channel)\n            ->for($user)\n            ->create([\n                'created_at' => now()->subMinutes(3),\n                'last_activity_at' => now()->subMinutes(3),\n            ]);\n\n        expect(Gate::forUser($user)->allows('waterhole.post.edit', $post))->toBeFalse();\n        expect(Gate::forUser($user)->allows('waterhole.post.edit', $recentPost))->toBeTrue();\n    });\n\n    test('post edit time limit of zero denies editing', function () {\n        config(['waterhole.forum.edit_time_limit' => 0]);\n\n        $user = User::factory()->create();\n        $channel = Channel::factory()->public()->create();\n\n        $post = Post::factory()\n            ->for($channel)\n            ->for($user)\n            ->create([\n                'created_at' => now(),\n                'last_activity_at' => now(),\n            ]);\n\n        expect(Gate::forUser($user)->allows('waterhole.post.edit', $post))->toBeFalse();\n    });\n});\n\ndescribe('api', function () {\n    test('posts in private channels are not visible', function () {\n        $channel = Channel::factory()->create();\n        $post = Post::factory()->for($channel)->create();\n\n        jsonApi('GET', \"/api/posts/$post->id\")->assertNotFound();\n    });\n});\n\ndescribe('forum', function () {\n    test('posts in private channels are not visible', function () {\n        $channel = Channel::factory()->create();\n        $post = Post::factory()->for($channel)->create();\n\n        $this->get(route('waterhole.posts.show', ['post' => $post]))->assertNotFound();\n    });\n});\n\ndescribe('console', function () {\n    test('posts in private channels are visible without an authenticated user', function () {\n        $channel = Channel::factory()->create();\n        $post = Post::factory()->for($channel)->create();\n\n        $env = app()['env'];\n        app()->instance('env', 'production');\n\n        try {\n            auth()->logout();\n            expect(Post::query()->whereKey($post)->exists())->toBeTrue();\n        } finally {\n            app()->instance('env', $env);\n        }\n    });\n});\n"
  },
  {
    "path": "tests/Feature/Permissions/StructurePermissionsTest.php",
    "content": "<?php\n\nuse Illuminate\\Foundation\\Testing\\RefreshDatabase;\nuse Illuminate\\Support\\Facades\\Gate;\nuse Waterhole\\Database\\Seeders\\GroupsSeeder;\nuse Waterhole\\Models\\Channel;\nuse Waterhole\\Models\\Page;\nuse Waterhole\\Models\\Post;\nuse Waterhole\\Models\\StructureLink;\nuse Waterhole\\Models\\User;\nuse Waterhole\\Waterhole;\n\nuses(RefreshDatabase::class);\n\nbeforeEach(function () {\n    $this->seed(GroupsSeeder::class);\n});\n\ndescribe('gate', function () {\n    test('guests are denied gated abilities without guest permissions', function () {\n        $channel = Channel::factory()->create();\n        $channel->savePermissions(['group:2' => ['view' => true]]);\n\n        $user = User::factory()->create();\n\n        expect(Waterhole::permissions()->can(null, 'view', $channel))->toBeFalse();\n        expect(Waterhole::permissions()->can($user, 'view', $channel))->toBeTrue();\n\n        expect(Waterhole::permissions()->ids(null, 'view', Channel::class))->toBeEmpty();\n        expect(Waterhole::permissions()->ids($user, 'view', Channel::class))->toEqual([\n            $channel->id,\n        ]);\n\n        expect(Gate::forUser(null)->allows('waterhole.channel.view', $channel))->toBeFalse();\n    });\n});\n\ndescribe('api', function () {\n    test('structure includes only public content', function () {\n        $publicChannel = Channel::factory()->public()->create();\n        $privateChannel = Channel::factory()->create();\n\n        $publicPage = Page::factory()->public()->create();\n        $privatePage = Page::factory()->create();\n\n        $publicLink = StructureLink::create([\n            'name' => 'Docs',\n            'href' => 'https://example.test/docs',\n        ]);\n        $publicLink->savePermissions(['group:1' => ['view' => true]]);\n\n        $privateLink = StructureLink::create([\n            'name' => 'Private',\n            'href' => 'https://example.test/private',\n        ]);\n\n        $response = jsonApi('GET', '/api/structure?include=content');\n\n        $response->assertOk();\n        $response->assertJsonFragment([\n            'type' => 'channels',\n            'id' => (string) $publicChannel->id,\n        ]);\n        $response->assertJsonFragment([\n            'type' => 'pages',\n            'id' => (string) $publicPage->id,\n        ]);\n        $response->assertJsonFragment([\n            'type' => 'structureLinks',\n            'id' => (string) $publicLink->id,\n        ]);\n        $response->assertJsonMissingExact([\n            'type' => 'channels',\n            'id' => (string) $privateChannel->id,\n        ]);\n        $response->assertJsonMissingExact([\n            'type' => 'pages',\n            'id' => (string) $privatePage->id,\n        ]);\n        $response->assertJsonMissingExact([\n            'type' => 'structureLinks',\n            'id' => (string) $privateLink->id,\n        ]);\n    });\n\n    test('cannot retrieve private channels', function () {\n        $channel = Channel::factory()->create();\n\n        jsonApi('GET', \"/api/channels/$channel->id\")->assertNotFound();\n    });\n\n    test('cannot retrieve private pages', function () {\n        $page = Page::factory()->create();\n\n        jsonApi('GET', \"/api/pages/$page->id\")->assertNotFound();\n    });\n\n    test('cannot retrieve private structure links', function () {\n        $link = StructureLink::create([\n            'name' => 'Private',\n            'href' => 'https://example.test/private',\n        ]);\n\n        jsonApi('GET', \"/api/structureLinks/$link->id\")->assertNotFound();\n    });\n});\n\ndescribe('forum', function () {\n    test('private channel is not visible', function () {\n        $channel = Channel::factory()->create();\n\n        $this->get(route('waterhole.channels.show', ['channel' => $channel]))->assertNotFound();\n    });\n\n    test('home feed hides posts in private channels', function () {\n        $public = Channel::factory()->public()->create();\n        $private = Channel::factory()->create();\n\n        Post::factory()\n            ->for($public)\n            ->create([\n                'title' => 'Public Post',\n                'body' => 'Public body',\n            ]);\n\n        Post::factory()\n            ->for($private)\n            ->create([\n                'title' => 'Private Post',\n                'body' => 'Private body',\n            ]);\n\n        $response = $this->get(route('waterhole.home'));\n\n        $response->assertOk();\n        $response->assertSee('Public Post');\n        $response->assertDontSee('Private Post');\n    });\n\n    test('private pages are not visible', function () {\n        $page = Page::factory()->create();\n\n        $this->get(route('waterhole.page', ['page' => $page]))->assertNotFound();\n    });\n});\n"
  },
  {
    "path": "tests/Feature/Permissions/UserPermissionsTest.php",
    "content": "<?php\n\nuse Illuminate\\Foundation\\Testing\\RefreshDatabase;\nuse Illuminate\\Support\\Facades\\Gate;\nuse Waterhole\\Database\\Seeders\\GroupsSeeder;\nuse Waterhole\\Models\\User;\n\nuses(RefreshDatabase::class);\n\nbeforeEach(function () {\n    $this->seed(GroupsSeeder::class);\n});\n\ndescribe('gate', function () {\n    test('users can suspend others only when they have permission', function () {\n        $actor = User::factory()->create();\n        $target = User::factory()->create();\n\n        $actor->savePermissions(['user' => ['suspend' => true]]);\n\n        expect(Gate::forUser($actor)->allows('waterhole.user.suspend', $target))->toBeTrue();\n    });\n\n    test('users cannot suspend other users with the same permission', function () {\n        $actor = User::factory()->create();\n        $target = User::factory()->create();\n\n        $actor->savePermissions(['user' => ['suspend' => true]]);\n        $target->savePermissions(['user' => ['suspend' => true]]);\n\n        expect(Gate::forUser($actor)->allows('waterhole.user.suspend', $target))->toBeFalse();\n    });\n});\n"
  },
  {
    "path": "tests/Feature/SearchTest.php",
    "content": "<?php\n\nuse Illuminate\\Foundation\\Testing\\RefreshDatabase;\nuse Illuminate\\Routing\\RouteCollection;\nuse Illuminate\\Support\\Facades\\DB;\nuse Illuminate\\Support\\Facades\\Route;\nuse Waterhole\\Database\\Seeders\\GroupsSeeder;\nuse Waterhole\\Models\\Channel;\nuse Waterhole\\Models\\Post;\nuse Waterhole\\Providers\\RouteServiceProvider;\nuse Waterhole\\Providers\\SearchServiceProvider;\n\nuses(RefreshDatabase::class);\n\ndescribe('Search engine configuration', function () {\n    beforeEach(function () {\n        $this->seed(GroupsSeeder::class);\n    });\n\n    test('full-text search route returns matches', function () {\n        $driver = DB::connection()->getDriverName();\n\n        if (!in_array($driver, ['mysql', 'mariadb', 'pgsql'], true)) {\n            $this->markTestSkipped('Full-text search requires MySQL/MariaDB/PostgreSQL.');\n        }\n\n        Post::factory()\n            ->for(Channel::factory()->public()->create())\n            ->create([\n                'title' => 'Waterhole search term',\n                'body' => 'Other content',\n            ]);\n\n        $this->get('/search?q=waterhole')->assertOk()->assertSeeText('Waterhole');\n    });\n\n    test('disables search routes and header when engine is null', function () {\n        config()->set('waterhole.system.search_engine', null);\n\n        app()->register(SearchServiceProvider::class, true);\n\n        Route::setRoutes(new RouteCollection());\n        app()->register(RouteServiceProvider::class, true);\n\n        Channel::factory()->public()->create();\n\n        $this->get('/')\n            ->assertOk()\n            ->assertDontSeeHtml('role=\"search\"')\n            ->assertDontSee('header-search__button');\n\n        $this->get('/search')->assertNotFound();\n    });\n});\n"
  },
  {
    "path": "tests/Feature/SeoTest.php",
    "content": "<?php\n\nuse Illuminate\\Foundation\\Testing\\RefreshDatabase;\nuse Waterhole\\Database\\Seeders\\GroupsSeeder;\nuse Waterhole\\Models\\Channel;\nuse Waterhole\\Models\\Comment;\nuse Waterhole\\Models\\Post;\nuse Waterhole\\Models\\User;\n\nuses(RefreshDatabase::class);\n\nbeforeEach(function () {\n    $this->seed(GroupsSeeder::class);\n\n    config([\n        'waterhole.forum.name' => 'Waterhole',\n        'waterhole.seo.default_description' => 'Default SEO description.',\n        'waterhole.seo.default_og_image' => 'https://example.com/default-og.png',\n    ]);\n});\n\ntest('home page includes seo tags', function () {\n    Channel::factory()->public()->create();\n\n    $this->get('/')\n        ->assertSeeHtml('<meta name=\"description\" content=\"Default SEO description.\" />')\n        ->assertSeeHtml('<meta property=\"og:type\" content=\"website\" />')\n        ->assertSeeHtml('\"@type\":\"WebSite\"');\n});\n\ntest('post page includes seo tags', function () {\n    $user = User::factory()->create();\n    $post = Post::factory()\n        ->for(Channel::factory()->public())\n        ->for($user)\n        ->create([\n            'title' => 'SEO Post Title',\n            'body' => 'SEO body text for description.',\n        ]);\n    Comment::factory()->for($post)->for($user)->create();\n\n    $description = \\Illuminate\\Support\\Str::limit($post->body_text ?? '', 160);\n\n    $this->get($post->url)\n        ->assertSeeHtml('<meta property=\"og:title\" content=\"SEO Post Title - Waterhole\" />')\n        ->assertSeeHtml('<meta name=\"description\" content=\"' . e($description) . '\" />')\n        ->assertSeeHtml('<meta property=\"og:image\" content=\"https://example.com/default-og.png\" />')\n        ->assertSee('itemtype=\"https://schema.org/DiscussionForumPosting\"', false)\n        ->assertSee('itemprop=\"headline\"', false)\n        ->assertSee('itemprop=\"text\"', false)\n        ->assertSee('itemprop=\"author\"', false)\n        ->assertSee('itemtype=\"https://schema.org/Comment\"', false)\n        ->assertDontSee('\"@type\":\"DiscussionForumPosting\"', false);\n});\n\ntest('standalone comment page includes discussion schema fields', function () {\n    $postAuthor = User::factory()->create();\n    $commentAuthor = User::factory()->create();\n    $post = Post::factory()\n        ->for(Channel::factory()->public())\n        ->for($postAuthor)\n        ->create(['title' => 'SEO Post Title']);\n    $comment = Comment::factory()\n        ->for($post)\n        ->for($commentAuthor)\n        ->create(['body' => 'SEO comment body text.']);\n\n    $this->get($comment->url)\n        ->assertSee('itemtype=\"https://schema.org/DiscussionForumPosting\"', false)\n        ->assertSee('itemprop=\"headline\"', false)\n        ->assertSee('itemprop=\"text\"', false)\n        ->assertSee('itemprop=\"author\"', false)\n        ->assertSee('itemtype=\"https://schema.org/Comment\"', false)\n        ->assertDontSee('\"@type\":\"DiscussionForumPosting\"', false);\n});\n"
  },
  {
    "path": "tests/Pest.php",
    "content": "<?php\n\nuse Tests\\TestCase;\nuse Tobyz\\JsonApiServer\\JsonApi;\n\nfunction jsonApi($method, $uri, array $data = [], array $headers = [])\n{\n    return test()->json(\n        $method,\n        $uri,\n        $data,\n        array_merge(\n            [\n                'Content-Type' => JsonApi::MEDIA_TYPE,\n                'Accept' => JsonApi::MEDIA_TYPE,\n            ],\n            $headers,\n        ),\n    );\n}\n\nfunction extend(callable $callback): void\n{\n    $class = (new ReflectionFunction($callback))->getParameters()[0]->getType()->getName();\n\n    app()->extend($class, function ($instance) use ($callback) {\n        return $callback($instance) ?: $instance;\n    });\n}\n\npest()->extend(TestCase::class)->in('Feature');\n"
  },
  {
    "path": "tests/TestCase.php",
    "content": "<?php\n\nnamespace Tests;\n\nuse Orchestra\\Testbench\\TestCase as BaseTestCase;\n\nabstract class TestCase extends BaseTestCase\n{\n    protected $enablesPackageDiscoveries = true;\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n    \"compilerOptions\": {\n        \"target\": \"es6\",\n        \"module\": \"esnext\",\n        \"moduleResolution\": \"node\",\n        \"strict\": true,\n        \"sourceMap\": true,\n        \"resolveJsonModule\": true,\n        \"esModuleInterop\": true,\n        \"lib\": [\"esnext\", \"DOM\", \"DOM.Iterable\"],\n        \"types\": [\"node\", \"tsdown/client\"]\n    },\n    \"include\": [\"resources/js/**/*.ts\", \"resources/js/**/*.d.ts\"]\n}\n"
  },
  {
    "path": "tsdown.config.ts",
    "content": "import { defineConfig, UserConfig } from 'tsdown';\nimport postcss from 'rollup-plugin-postcss';\n\nconst dev = process.env.DEV === '1';\n\nfunction defineBundle(\n    name: string,\n    path: string,\n    options: Partial<UserConfig> = {},\n): UserConfig {\n    return {\n        name,\n        entry: { [name]: path },\n        platform: 'browser',\n        format: 'iife',\n        inlineOnly: false,\n        minify: !dev,\n        clean: !dev,\n        outDir: 'resources/dist',\n        outputOptions: { entryFileNames: '[name].js' },\n        plugins: [postcss({ extract: true, minimize: !dev })],\n        ...options,\n    };\n}\n\nexport default defineConfig([\n    defineBundle('global', 'resources/js/index.ts', { watch: dev }),\n    defineBundle('cp', 'resources/js/cp/index.ts', { watch: dev }),\n    defineBundle('emoji', 'resources/js/emoji.ts'),\n    defineBundle('highlight', 'resources/js/highlight.ts'),\n]);\n"
  }
]