[
  {
    "path": ".devcontainer/config/xdebug.ini",
    "content": "zend_extension=xdebug.so\nxdebug.mode=develop,debug,profile,trace,gcstats\nxdebug.discover_client_host=1\nxdebug.client_port=9003\n"
  },
  {
    "path": ".devcontainer/devcontainer.json",
    "content": "{\n    \"image\": \"mcr.microsoft.com/devcontainers/php:8.3\",\n    \"forwardPorts\": [\n        8000,\n        5173,\n        9000,\n        9003\n    ],\n    \"remoteEnv\": {\n        \"COMPOSER_MEMORY_LIMIT\": \"-1\",\n        \"COMPOSER_PROCESS_TIMEOUT\": \"0\",\n        \"PHPUNIT_TELEMETRY\": \"off\"\n    },\n    \"features\": {\n        \"ghcr.io/devcontainers/features/node:1\": {}\n    }\n}"
  },
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\ncharset = utf-8\nindent_size = 4\nindent_style = space\nend_of_line = lf\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.md]\ntrim_trailing_whitespace = false\n\n[*.{yml,yaml}]\nindent_size = 2\n\n[*.{neon,neon.dist}]\nindent_size = 4\n"
  },
  {
    "path": ".gitattributes",
    "content": "* text=auto eol=lf\n\n*.blade.php diff=html\n*.css diff=css\n*.html diff=html\n*.md diff=markdown\n*.php diff=php\n\n# Path-based git attributes\n# https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html\n\n# Ignore all test and documentation with \"export-ignore\".\n/.github            export-ignore\n/.gitattributes     export-ignore\n/.gitignore         export-ignore\n/phpunit.xml.dist   export-ignore\n/art                export-ignore\n/docs               export-ignore\n/tests              export-ignore\n/.editorconfig      export-ignore\n/.php_cs.dist.php   export-ignore\n/psalm.xml          export-ignore\n/psalm.xml.dist     export-ignore\n/testbench.yaml     export-ignore\n/UPGRADING.md       export-ignore\n/phpstan.neon.dist  export-ignore\n/phpstan-baseline.neon  export-ignore\n/.travis.yml        export-ignore\n/.scrutinizer.yml   export-ignore\n\n\n/.idea                  export-ignore\n/.prettierrc            export-ignore\n/.package-lock.json     export-ignore\n/.editorconfig          export-ignore\n/.php_cs.dist.php       export-ignore\n/.vscode                export-ignore\n/images                 export-ignore\n/package.json           export-ignore\n/postcss.config.js      export-ignore\n/phpunit.xml.dist       export-ignore\n/pint.json              export-ignore\n/psalm.xml              export-ignore\n/psalm.xml.dist         export-ignore\n/tailwind.config.js     export-ignore\n/testbench.yaml         export-ignore\n/UPGRADING.md           export-ignore\n\n\n## GITATTRIBUTES FOR WEB PROJECTS\n#\n# These settings are for any web project.\n#\n# Details per file setting:\n#   text    These files should be normalized (i.e. convert CRLF to LF).\n#   binary  These files are binary and should be left untouched.\n#\n# Note that binary is a macro for -text -diff.\n######################################################################\n\n# Auto detect\n##   Handle line endings automatically for files detected as\n##   text and leave all files detected as binary untouched.\n##   This will handle all files NOT defined below.\n\n# Source code\n*.js              text\n*.mjs             text\n*.cjs             text\n*.json            text\n*.ls              text\n*.map             text -diff\n*.sass            text\n*.scss            text diff=css\n*.sh              text eol=lf\n.husky/*          text eol=lf\n*.sql             text\n*.ts              text\n\n# Docker\nDockerfile        text\n\n# Documentation\n*.ipynb           text eol=lf\n*.markdown        text diff=markdown\n*.md              text diff=markdown\n*.mdwn            text diff=markdown\n*.mdown           text diff=markdown\n*.mkd             text diff=markdown\n*.mkdn            text diff=markdown\n*.mdtxt           text\n*.mdtext          text\n*.txt             text\nAUTHORS           text\nCHANGELOG         text\nCHANGES           text\nCONTRIBUTING      text\nCOPYING           text\ncopyright         text\n*COPYRIGHT*       text\nINSTALL           text\nlicense           text\nLICENSE           text\nNEWS              text\nreadme            text\n*README*          text\nTODO              text\n\n# Templates\n*.dot             text\n*.ejs             text\n*.erb             text\n*.haml            text\n*.handlebars      text\n*.hbs             text\n*.hbt             text\n*.jade            text\n*.latte           text\n*.mustache        text\n*.njk             text\n*.phtml           text\n*.svelte          text\n*.tmpl            text\n*.tpl             text\n*.twig            text\n*.vue             text\n\n# Configs\n*.cnf             text\n*.conf            text\n*.config          text\n.editorconfig     text\n*.env             text\n.gitattributes    text\n.gitconfig        text\n.htaccess         text\n*.lock            text -diff\npackage.json      text eol=lf\npackage-lock.json text eol=lf -diff\npnpm-lock.yaml    text eol=lf -diff\n.prettierrc       text\nyarn.lock         text -diff\n*.toml            text\n*.yaml            text\n*.yml             text\nbrowserslist      text\nMakefile          text\nmakefile          text\n# Fixes syntax highlighting on GitHub to allow comments\ntsconfig.json     linguist-language=JSON-with-Comments\n\n# Heroku\nProcfile          text\n\n# Graphics\n*.ai              binary\n*.bmp             binary\n*.eps             binary\n*.gif             binary\n*.gifv            binary\n*.ico             binary\n*.jng             binary\n*.jp2             binary\n*.jpg             binary\n*.jpeg            binary\n*.jpx             binary\n*.jxr             binary\n*.pdf             binary\n*.png             binary\n*.psb             binary\n*.psd             binary\n# SVG treated as an asset (binary) by default.\n*.svg             text\n# If you want to treat it as binary,\n# use the following line instead.\n*.svgz            binary\n*.tif             binary\n*.tiff            binary\n*.wbmp            binary\n*.webp            binary\n\n# Audio\n*.kar             binary\n*.m4a             binary\n*.mid             binary\n*.midi            binary\n*.mp3             binary\n*.ogg             binary\n*.ra              binary\n\n# Video\n*.3gpp            binary\n*.3gp             binary\n*.as              binary\n*.asf             binary\n*.asx             binary\n*.avi             binary\n*.fla             binary\n*.flv             binary\n*.m4v             binary\n*.mng             binary\n*.mov             binary\n*.mp4             binary\n*.mpeg            binary\n*.mpg             binary\n*.ogv             binary\n*.swc             binary\n*.swf             binary\n*.webm            binary\n\n# Archives\n*.7z              binary\n*.gz              binary\n*.jar             binary\n*.rar             binary\n*.tar             binary\n*.zip             binary\n\n# Fonts\n*.ttf             binary\n*.eot             binary\n*.otf             binary\n*.woff            binary\n*.woff2           binary\n\n# Executables\n*.exe             binary\n*.pyc             binary\n# Prevents massive diffs caused by vendored, minified files\n**/.yarn/releases/**   binary\n**/.yarn/plugins/**    binary\n\n# RC files (like .babelrc or .eslintrc)\n*.*rc             text\n\n# Ignore files (like .npmignore or .gitignore)\n*.*ignore         text\n"
  },
  {
    "path": ".github/CONTRIBUTING.md",
    "content": "# Contributing\n\nContributions are **welcome** and will be fully **credited**.\n\nPlease read and understand the contribution guide before creating an issue or pull request.\n\n## Etiquette\n\nThis project is open source, and as such, the maintainers give their free time to build and maintain the source code\nheld within. They make the code freely available in the hope that it will be of use to other developers. It would be\nextremely unfair for them to suffer abuse or anger for their hard work.\n\nPlease be considerate towards maintainers when raising issues or presenting pull requests. Let's show the\nworld that developers are civilized and selfless people.\n\nIt's the duty of the maintainer to ensure that all submissions to the project are of sufficient\nquality to benefit the project. Many developers have different skillsets, strengths, and weaknesses. Respect the maintainer's decision, and do not be upset or abusive if your submission is not used.\n\n## Viability\n\nWhen requesting or submitting new features, first consider whether it might be useful to others. Open\nsource projects are used by many developers, who may have entirely different needs to your own. Think about\nwhether or not your feature is likely to be used by other users of the project.\n\n## Procedure\n\nBefore filing an issue:\n\n- Attempt to replicate the problem, to ensure that it wasn't a coincidental incident.\n- Check to make sure your feature suggestion isn't already present within the project.\n- Check the pull requests tab to ensure that the bug doesn't have a fix in progress.\n- Check the pull requests tab to ensure that the feature isn't already in progress.\n\nBefore submitting a pull request:\n\n- Check the codebase to ensure that your feature doesn't already exist.\n- Check the pull requests to ensure that another person hasn't already submitted the feature or fix.\n\n## Requirements\n\nIf the project maintainer has any additional requirements, you will find them listed here.\n\n- **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](https://pear.php.net/package/PHP_CodeSniffer).\n\n- **Add tests!** - Your patch won't be accepted if it doesn't have tests.\n\n- **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date.\n\n- **Consider our release cycle** - We try to follow [SemVer v2.0.0](https://semver.org/). Randomly breaking public APIs is not an option.\n\n- **One pull request per feature** - If you want to do more than one thing, send multiple pull requests.\n\n- **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](https://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting.\n\n**Happy coding**!\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\ncontact_links:\n  - name: Ask a question\n    url: https://github.com/Z3d0X/filament-fabricator/discussions/new?category=q-a\n    about: Ask the community for help\n  - name: Request a feature\n    url: https://github.com/Z3d0X/filament-fabricator/discussions/new?category=ideas\n    about: Share ideas for new features\n  - name: Report a security issue\n    url: https://github.com/Z3d0X/filament-fabricator/security/policy\n    about: Learn how to notify us for sensitive bugs\n  - name: Report a bug\n    url: https://github.com/Z3d0X/filament-fabricator/issues/new\n    about: Report a reproducable bug\n"
  },
  {
    "path": ".github/SECURITY.md",
    "content": "# Security Policy\n\nIf you discover any security related issues, please email ziyaan2010@gmail.com instead of using the issue tracker.\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "# Please see the documentation for all configuration options:\n# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates\n\nversion: 2\nupdates:\n\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n    labels:\n      - \"dependencies\""
  },
  {
    "path": ".github/workflows/dependabot-auto-merge.yml",
    "content": "name: dependabot-auto-merge\non: pull_request_target\n\npermissions:\n  pull-requests: write\n  contents: write\n\njobs:\n  dependabot:\n    runs-on: ubuntu-latest\n    if: ${{ github.actor == 'dependabot[bot]' }}\n    steps:\n\n      - name: Dependabot metadata\n        id: metadata\n        uses: dependabot/fetch-metadata@v2.5.0\n        with:\n          github-token: \"${{ secrets.GITHUB_TOKEN }}\"\n\n      - name: Auto-merge Dependabot PRs for semver-minor updates\n        if: ${{steps.metadata.outputs.update-type == 'version-update:semver-minor'}}\n        run: gh pr merge --auto --merge \"$PR_URL\"\n        env:\n          PR_URL: ${{github.event.pull_request.html_url}}\n          GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}\n\n      - name: Auto-merge Dependabot PRs for semver-patch updates\n        if: ${{steps.metadata.outputs.update-type == 'version-update:semver-patch'}}\n        run: gh pr merge --auto --merge \"$PR_URL\"\n        env:\n          PR_URL: ${{github.event.pull_request.html_url}}\n          GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}"
  },
  {
    "path": ".github/workflows/fix-php-code-style-issues.yml",
    "content": "name: Fix PHP code style issues\n\non: [push]\n\njobs:\n  php-code-styling:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v6\n        with:\n          ref: ${{ github.head_ref }}\n\n      - name: Fix PHP code style issues\n        uses: aglipanci/laravel-pint-action@2.6\n\n      - name: Commit changes\n        uses: stefanzweifel/git-auto-commit-action@v7\n        with:\n          commit_message: Fix styling"
  },
  {
    "path": ".github/workflows/phpstan.yml",
    "content": "name: PHPStan\n\non:\n  push:\n    paths:\n      - '**.php'\n      - 'phpstan.neon.dist'\n\njobs:\n  phpstan:\n    name: phpstan\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\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 composer dependencies\n        uses: ramsey/composer-install@v3\n\n      - name: Run PHPStan\n        run: ./vendor/bin/phpstan\n"
  },
  {
    "path": ".github/workflows/run-tests.yml",
    "content": "name: run-tests\n\non:\n  push:\n    branches:\n      - 1.x\n      - 2.x\n  pull_request:\n    branches:\n      - 1.x\n      - 2.x\n\njobs:\n  test:\n    runs-on: ${{ matrix.os }}\n\n    strategy:\n      fail-fast: true\n      matrix:\n        os: [ubuntu-latest]\n        php: [8.1, '8.2', '8.3']\n        laravel: ['10.*', '11.*', '12.*', '13.*']\n        stability: [prefer-stable]\n        include:\n          - laravel: 12.*\n            testbench: 10.*\n          - laravel: 11.*\n            testbench: 9.*\n          - laravel: 10.*\n            testbench: 8.*\n          - laravel: 13.*\n            testbench: 11.*\n        exclude:\n          - laravel: 12.*\n            php: 8.1\n          - laravel: 11.*\n            php: 8.1\n          - laravel: 13.*\n            php: 8.1\n          - laravel: 13.*\n            php: '8.2'\n\n    name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }}\n\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v6\n\n      - name: Cache dependencies\n        uses: actions/cache@v5\n        with:\n          path: ~/.composer/cache/files\n          key: dependencies-laravel-${{ matrix.laravel }}-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }}\n\n      - name: Setup PHP\n        uses: shivammathur/setup-php@v2\n        with:\n          php-version: ${{ matrix.php }}\n          extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo\n          coverage: none\n\n      - name: Setup problem matchers\n        run: |\n          echo \"::add-matcher::${{ runner.tool_cache }}/php.json\"\n          echo \"::add-matcher::${{ runner.tool_cache }}/phpunit.json\"\n\n      - name: Install dependencies\n        run: |\n          composer require \"laravel/framework:${{ matrix.laravel }}\" \"orchestra/testbench:${{ matrix.testbench }}\" --no-interaction --no-update\n          composer update --${{ matrix.stability }} --prefer-dist --no-interaction\n\n      - name: Execute tests\n        run: ./vendor/bin/pest\n"
  },
  {
    "path": ".github/workflows/update-changelog.yml",
    "content": "name: \"Update Changelog\"\n\non:\n  release:\n    types: [released]\n\njobs:\n  update:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v6\n\n      - name: Update Changelog\n        uses: stefanzweifel/changelog-updater-action@v1\n        with:\n          latest-version: ${{ github.event.release.name }}\n          release-notes: ${{ github.event.release.body }}\n\n      - name: Commit updated CHANGELOG\n        uses: stefanzweifel/git-auto-commit-action@v7\n        with:\n          branch: ${{ github.event.release.target_commitish }}\n          commit_message: Update CHANGELOG\n          file_pattern: CHANGELOG.md\n"
  },
  {
    "path": ".gitignore",
    "content": ".idea\n.php_cs\n.php_cs.cache\n.phpunit.result.cache\nbuild\ncomposer.lock\ncoverage\nphpunit.xml\nphpstan.neon\ntestbench.yaml\nvendor\nnode_modules\n.php-cs-fixer.cache\n**/.DS_Store\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n\t\"intelephense.environment.includePaths\": [\n\t\t\"/workspaces/filament-fabricator/vendor\"\n\t]\n}\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\nAll notable changes to `filament-fabricator` will be documented in this file.\n\n## v4.0.0 - Filament v5 Compatibility - 2026-02-16\n\n### What's Changed\n\n* build(deps): bump dependabot/fetch-metadata from 2.4.0 to 2.5.0 by @dependabot[bot] in https://github.com/Z3d0X/filament-fabricator/pull/258\n* Migrate to Filament v5 by @Voltra in https://github.com/Z3d0X/filament-fabricator/pull/260\n\n**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v3.0.3...v4.0.0\n\n## v3.0.3 - 2025-12-31\n\n### What's Changed\n\n* hotfix: 3.0.2 mishaps by @Voltra in https://github.com/Z3d0X/filament-fabricator/pull/257\n\n**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v3.0.2...v3.0.3\n\n## v3.0.2 - 2025-12-23\n\n### What's Changed\n\n* build(deps): bump actions/checkout from 5 to 6 by @dependabot[bot] in https://github.com/Z3d0X/filament-fabricator/pull/252\n* build(deps): bump actions/cache from 4 to 5 by @dependabot[bot] in https://github.com/Z3d0X/filament-fabricator/pull/253\n* hotfix: Filament v4 migration mishaps by @Voltra in https://github.com/Z3d0X/filament-fabricator/pull/254\n\n**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v3.0.1...v3.0.2\n\n## v3.0.1 - 2025-10-24\n\n**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v2.7.0...v2.7.1\n\n### What's Changed\n\n* build(deps): bump actions/cache from 3 to 4 by @dependabot[bot] in https://github.com/Z3d0X/filament-fabricator/pull/239\n* build(deps): bump actions/checkout from 4 to 5 by @dependabot[bot] in https://github.com/Z3d0X/filament-fabricator/pull/238\n* fix: wrong argument used on getPageUrlFromId breaks page listing on f… by @Reapious in https://github.com/Z3d0X/filament-fabricator/pull/245\n* fix: update PageBuilder for Filament v4 compatibility by @mckenziearts in https://github.com/Z3d0X/filament-fabricator/pull/244\n* Add polish translations by @KaminskiDaniell in https://github.com/Z3d0X/filament-fabricator/pull/247\n* build(deps): bump stefanzweifel/git-auto-commit-action from 6 to 7 by @dependabot[bot] in https://github.com/Z3d0X/filament-fabricator/pull/246\n\n### New Contributors\n\n* @Reapious made their first contribution in https://github.com/Z3d0X/filament-fabricator/pull/245\n* @mckenziearts made their first contribution in https://github.com/Z3d0X/filament-fabricator/pull/244\n* @KaminskiDaniell made their first contribution in https://github.com/Z3d0X/filament-fabricator/pull/247\n\n**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v3.0...v3.0.1\n\n## v3.0 - Filament v4 Support - 2025-09-29\n\n### What's Changed\n\n* Migrate 3.x codebase to Filament v4 by @Voltra in https://github.com/Z3d0X/filament-fabricator/pull/237\n\n**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v2.6.1...v3.0\n\n## v2.6.1 - 2025-09-09\n\n### What's Changed\n\n* hotfix: Revert changes made in v2.6.0 by @Voltra in https://github.com/Z3d0X/filament-fabricator/pull/236\n\n**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v2.6.0...v2.6.1\n\n## v2.5.1 - 2025-08-03\n\n### What's Changed\n\n* build(deps): bump dependabot/fetch-metadata from 2.3.0 to 2.4.0 by @dependabot[bot] in https://github.com/Z3d0X/filament-fabricator/pull/225\n* build(deps): bump aglipanci/laravel-pint-action from 2.5 to 2.6 by @dependabot[bot] in https://github.com/Z3d0X/filament-fabricator/pull/231\n* Fix: bind page route when runnning unit tests by @thecrazybob in https://github.com/Z3d0X/filament-fabricator/pull/224\n* Fix: resolve PHP 8 deprecation warning in namespace handling by @thecrazybob in https://github.com/Z3d0X/filament-fabricator/pull/219\n* Add dutch translations by @lbovit in https://github.com/Z3d0X/filament-fabricator/pull/220\n* build(deps): bump stefanzweifel/git-auto-commit-action from 5 to 6 by @dependabot[bot] in https://github.com/Z3d0X/filament-fabricator/pull/229\n* Fix issue when removing old urls from uncached pages by @Voltra in https://github.com/Z3d0X/filament-fabricator/commit/2e09ce5d6b5a9cb04c9d1c9a17f519e76febb7e4\n\n### New Contributors\n\n* @thecrazybob made their first contribution in https://github.com/Z3d0X/filament-fabricator/pull/224\n* @lbovit made their first contribution in https://github.com/Z3d0X/filament-fabricator/pull/220\n\n**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v2.5...v2.5.1\n\n## v2.5 - 2025-03-04\n\n### What's Changed\n\n* build(deps): bump dependabot/fetch-metadata from 2.2.0 to 2.3.0 by @dependabot in https://github.com/Z3d0X/filament-fabricator/pull/210\n* build(deps): bump aglipanci/laravel-pint-action from 2.4 to 2.5 by @dependabot in https://github.com/Z3d0X/filament-fabricator/pull/211\n* Laravel 12.x Compatibility by @laravel-shift in https://github.com/Z3d0X/filament-fabricator/pull/216\n* Null Page Reference in FilamentFabricator Block Preloading by @rsandipermana in https://github.com/Z3d0X/filament-fabricator/pull/214\n\n### New Contributors\n\n* @rsandipermana made their first contribution in https://github.com/Z3d0X/filament-fabricator/pull/214\n\n**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v2.4.3...v2.5\n\n## v2.4.3 - 2025-01-22\n\n### What's Changed\n\n* Add Indonesian language translations for page resource by @cuinc99 in https://github.com/Z3d0X/filament-fabricator/pull/208\n\n### New Contributors\n\n* @cuinc99 made their first contribution in https://github.com/Z3d0X/filament-fabricator/pull/208\n\n**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v2.4.2...v2.4.3\n\n## v2.4.2 - 2025-01-08\n\n### What's Changed\n\n* Rename ResourcheSchemaSlot.php to ResourceSchemaSlot.php by @rezadindar in https://github.com/Z3d0X/filament-fabricator/pull/206\n* proposal: Replace the default middleware list by the 'web' middleware group by @Voltra in https://github.com/Z3d0X/filament-fabricator/pull/207\n\n### New Contributors\n\n* @rezadindar made their first contribution in https://github.com/Z3d0X/filament-fabricator/pull/206\n\n**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v2.4.1...v2.4.2\n\n## v2.4.1 - 2025-01-07\n\n### What's Changed\n\n* Add constants for layout render hooks by @Voltra in https://github.com/Z3d0X/filament-fabricator/pull/193\n* Fix n+1 query by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/199\n* Fix modal picker style by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/200\n* Fix PHPStan by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/201\n* hotfix: Proper clearing of the ID<->URI mappings by @Voltra in https://github.com/Z3d0X/filament-fabricator/pull/204\n* Add constants for page resource schema slots by @Voltra in https://github.com/Z3d0X/filament-fabricator/pull/203\n* feat ( Localization ) : add Turkish lang. by @AzizEmir in https://github.com/Z3d0X/filament-fabricator/pull/202\n\n### New Contributors\n\n* @AzizEmir made their first contribution in https://github.com/Z3d0X/filament-fabricator/pull/202\n\n**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v2.4.0...v2.4.1\n\n## v2.4.0 - 2024-12-24\n\n### What's Changed\n\n* \"Smart\" route URLs caching by @Voltra in https://github.com/Z3d0X/filament-fabricator/pull/119\n\n**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v2.3.0...v2.4.0\n\n## v2.3.0 - 2024-12-22\n\n### What's Changed\n\n* allow layout live switching by @phpsa in https://github.com/Z3d0X/filament-fabricator/pull/188\n* Add exception for runningInConsole in FilamentFabricatorServiceProvid… by @yolanmees in https://github.com/Z3d0X/filament-fabricator/pull/160\n* Add a hook to allow mass-preload/batch-load of related data when rendering a page's blocks by @Voltra in https://github.com/Z3d0X/filament-fabricator/pull/166\n\n### New Contributors\n\n* @phpsa made their first contribution in https://github.com/Z3d0X/filament-fabricator/pull/188\n* @yolanmees made their first contribution in https://github.com/Z3d0X/filament-fabricator/pull/160\n\n**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v2.2.2...v2.3.0\n\n## v2.2.2 - 2024-05-12\n\n### What's Changed\n\n* build(deps): bump aglipanci/laravel-pint-action from 2.3.1 to 2.4 by @dependabot in https://github.com/Z3d0X/filament-fabricator/pull/152\n* build(deps): bump dependabot/fetch-metadata from 2.0.0 to 2.1.0 by @dependabot in https://github.com/Z3d0X/filament-fabricator/pull/156\n* Allow string IDs by @rojtjo in https://github.com/Z3d0X/filament-fabricator/pull/158\n\n### New Contributors\n\n* @rojtjo made their first contribution in https://github.com/Z3d0X/filament-fabricator/pull/158\n\n**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v2.2.1...v2.2.2\n\n## v2.2.1 - 2024-04-15\n\n### What's Changed\n\n* build(deps): bump dependabot/fetch-metadata from 1.6.0 to 2.0.0 by @dependabot in https://github.com/Z3d0X/filament-fabricator/pull/148\n* Add note to README regarding the plugin assets by @pboivin in https://github.com/Z3d0X/filament-fabricator/pull/149\n* Fixed incorrect table name during migration by @witaway in https://github.com/Z3d0X/filament-fabricator/pull/151\n\n### New Contributors\n\n* @pboivin made their first contribution in https://github.com/Z3d0X/filament-fabricator/pull/149\n* @witaway made their first contribution in https://github.com/Z3d0X/filament-fabricator/pull/151\n\n**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v2.2.0...v2.2.1\n\n## v2.2.0 - 2024-03-12\n\n### Laravel 11.x compatibility added\n\n#### What's Changed\n\n* build(deps): bump ramsey/composer-install from 2 to 3 by @dependabot in https://github.com/Z3d0X/filament-fabricator/pull/143\n* Laravel 11.x Compatibility by @laravel-shift in https://github.com/Z3d0X/filament-fabricator/pull/142\n\n**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v2.1.1...v2.2.0\n\n## v2.1.1 - 2024-02-19\n\n### What's Changed\n\n* Fix Resource Registration by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/140\n\n**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v2.1.0...v2.1.1\n\n## v2.1.0 - 2024-02-09\n\n### What's Changed\n\n* Fix Slug Unique Constraint by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/135\n* Feature Block Picker Styles by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/136\n\nNote: to apply the fix for unique slug issue from #135 please publish the migrations using `php artisan vendor:publish --tag=filament-fabricator-migrations` & then run migrations\n\n**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v2.0.6...v2.1.0\n\n## v2.0.6 - 2024-02-03\n\n### What's Changed\n\n* Fix octane issues change registering package from scoped to singleton by @ksimenic in https://github.com/Z3d0X/filament-fabricator/pull/130\n\n### New Contributors\n\n* @ksimenic made their first contribution in https://github.com/Z3d0X/filament-fabricator/pull/130\n\n**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v2.0.5...v2.0.6\n\n## v2.0.5 - 2024-01-18\n\n### What's Changed\n\n* Respect app locale for slug generation by @flolanger in https://github.com/Z3d0X/filament-fabricator/pull/109\n* build(deps): bump aglipanci/laravel-pint-action from 2.3.0 to 2.3.1 by @dependabot in https://github.com/Z3d0X/filament-fabricator/pull/124\n* [ar]: Add Arabic translations by @mohamedsabil83 in https://github.com/Z3d0X/filament-fabricator/pull/127\n\n### New Contributors\n\n* @flolanger made their first contribution in https://github.com/Z3d0X/filament-fabricator/pull/109\n* @mohamedsabil83 made their first contribution in https://github.com/Z3d0X/filament-fabricator/pull/127\n\n**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v2.0.4...v2.0.5\n\n## v2.0.4 - 2023-11-26\n\n### What's Changed\n\n* Update README.md by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/105\n* Fix getLabel() and getPluralLabel() deprecation by @devhoussam1998 in https://github.com/Z3d0X/filament-fabricator/pull/113\n* Allow easier subclassing of the Page model by @Voltra in https://github.com/Z3d0X/filament-fabricator/pull/115\n\n### New Contributors\n\n* @devhoussam1998 made their first contribution in https://github.com/Z3d0X/filament-fabricator/pull/113\n* @Voltra made their first contribution in https://github.com/Z3d0X/filament-fabricator/pull/115\n\n**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v2.0.3...v2.0.4\n\n## v2.0.3 - 2023-10-14\n\n### What's Changed\n\n- build(deps): bump stefanzweifel/git-auto-commit-action from 4 to 5 by @dependabot in https://github.com/Z3d0X/filament-fabricator/pull/97\n- fix: default layout resolving by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/103\n\n**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v2.0.2...v2.0.3\n\n## v1.2.2 - 2023-10-14\n\n### What's Changed\n\n- fix: default layout resolving by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/103\n\n**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v1.2.1...v1.2.2\n\n## v2.0.2 - 2023-09-25\n\n### What's Changed\n\n- [Docs]: Remove Peek from installation instructions by @viraljetani in https://github.com/Z3d0X/filament-fabricator/pull/87\n- build(deps): bump actions/checkout from 3 to 4 by @dependabot in https://github.com/Z3d0X/filament-fabricator/pull/89\n- Update PageResource.php by @RibesAlexandre in https://github.com/Z3d0X/filament-fabricator/pull/93\n- Fix: Lazy Loading in Model Strict Mode by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/96\n\n### New Contributors\n\n- @viraljetani made their first contribution in https://github.com/Z3d0X/filament-fabricator/pull/87\n- @RibesAlexandre made their first contribution in https://github.com/Z3d0X/filament-fabricator/pull/93\n\n**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v2.0.1...v2.0.2\n\n## v2.0.1 - 2023-08-20\n\n### What's Changed\n\n- Fix deprecated code by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/83\n\n**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v2.0.0...v2.0.1\n\n## v1.1.5 - 2023-04-18\n\n### What's Changed\n\n- Prevented lazy loading. by @danielbehrendt in https://github.com/Z3d0X/filament-fabricator/pull/56\n\n### New Contributors\n\n- @danielbehrendt made their first contribution in https://github.com/Z3d0X/filament-fabricator/pull/56\n\n**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v1.1.4...v1.1.5\n\n## v1.1.4 - 2023-03-30\n\n### What's Changed\n\n- build(deps): bump aglipanci/laravel-pint-action from 2.1.0 to 2.2.0 by @dependabot in https://github.com/Z3d0X/filament-fabricator/pull/54\n- Translatable Resource Labels in https://github.com/Z3d0X/filament-fabricator/commit/073fb9d4935951b6f59fd01b8426ddf8321344be\n\n**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v1.1.3...v1.1.4\n\n## v1.1.3 - 2023-03-05\n\n### What's Changed\n\n- Fix: Page Middleware by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/51\n\n**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v1.1.2...v1.1.3\n\n## v1.1.2 - 2023-02-27\n\n### What's Changed\n\n- Make `$page` instance accessible in blocks by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/45\n- Shorter Commands by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/46\n- Fix Routing Urls by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/47\n\n**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v1.1.1...v1.1.2\n\n## v1.1.1 - 2023-02-17\n\n### What's Changed\n\n- Fix: ignore deleted blocks by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/41\n- Refactor to PageController by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/42\n\n**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v1.1.0...v1.1.1\n\n## v1.1.0 - 2023-02-15\n\n### What's Changed\n\n- Laravel 10 Support by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/38\n\n**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v1.0.4...v1.1.0\n\n## v1.0.4 - 2023-02-14\n\n### What's Changed\n\n- build(deps): bump dependabot/fetch-metadata from 1.3.5 to 1.3.6 by @dependabot in https://github.com/Z3d0X/filament-fabricator/pull/35\n- Fix custom PageResource not being considered on Pages by @lucasgiovanny in https://github.com/Z3d0X/filament-fabricator/pull/37\n\n### New Contributors\n\n- @lucasgiovanny made their first contribution in https://github.com/Z3d0X/filament-fabricator/pull/37\n\n**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v1.0.3...v1.0.4\n\n## v1.0.3 - 2023-01-07\n\n### What's Changed\n\n- build(deps): bump ramsey/composer-install from 1 to 2 by @dependabot in https://github.com/Z3d0X/filament-fabricator/pull/28\n- Add a save button at the top of the page by @jvkassi in https://github.com/Z3d0X/filament-fabricator/pull/31\n- build(deps): bump aglipanci/laravel-pint-action from 1.0.0 to 2.1.0 by @dependabot in https://github.com/Z3d0X/filament-fabricator/pull/32\n- Configurable Database Table & Migration Down Method by @mrlinnth in https://github.com/Z3d0X/filament-fabricator/pull/19\n- Fix: Frontend Routing by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/34\n\n### New Contributors\n\n- @jvkassi made their first contribution in https://github.com/Z3d0X/filament-fabricator/pull/31\n- @mrlinnth made their first contribution in https://github.com/Z3d0X/filament-fabricator/pull/19\n\n**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v1.0.2...v1.0.3\n\n## v1.0.2 - 2022-11-14\n\n### What's Changed\n\n- Fix: Homepage Routing by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/24\n\n**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v1.0.1...v1.0.2\n\n## v1.0.1 - 2022-11-13\n\n### What's Changed\n\n- build(deps): bump dependabot/fetch-metadata from 1.3.4 to 1.3.5 by @dependabot in https://github.com/Z3d0X/filament-fabricator/pull/21\n- Feature: Configurable `PageResource` by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/22\n- Fix: Hide page urls when routing disabled by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/23\n\n**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v1.0.0...v1.0.1\n\n## v1.0.0 - 2022-10-25\n\n### What's Changed\n\n- Feature: `PageResource` Translations by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/7\n- Feature: `PageBuilder` Field by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/8\n- Feature: Base Layout by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/10\n- Feature: Configurable `Page` Model by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/11\n- Fix: HomePage Routing by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/12\n- build(deps): bump dependabot/fetch-metadata from 1.3.3 to 1.3.4 by @dependabot in https://github.com/Z3d0X/filament-fabricator/pull/15\n- Feature: Nested Pages by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/13\n- adding prefix for pages by @MuhamadSelim in https://github.com/Z3d0X/filament-fabricator/pull/14\n- fixing Dynamic Page Model for FilamentFabricatorManager.php setPageUr… by @MuhamadSelim in https://github.com/Z3d0X/filament-fabricator/pull/16\n- Feature: Page Model Contract by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/17\n- Feature: PageResource custom fieldslots by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/18\n\n### New Contributors\n\n- @Z3d0X made their first contribution in https://github.com/Z3d0X/filament-fabricator/pull/7\n- @dependabot made their first contribution in https://github.com/Z3d0X/filament-fabricator/pull/15\n- @MuhamadSelim made their first contribution in https://github.com/Z3d0X/filament-fabricator/pull/14\n\n**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v0.1.0...v1.0.0\n\n## v0.1.0 - 2022-09-19\n\n**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/commits/v0.1.0\n\n## 1.0.0 - 202X-XX-XX\n\n- initial release\n"
  },
  {
    "path": "LICENSE.md",
    "content": "The MIT License (MIT)\n\nCopyright (c) Z3d0X <ziyaan2010@gmail.com>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Block-Based Page Builder Skeleton for your Filament Apps\n\n[![Latest Version on Packagist](https://img.shields.io/packagist/v/z3d0x/filament-fabricator.svg?style=for-the-badge)](https://packagist.org/packages/z3d0x/filament-fabricator)\n[![Total Downloads](https://img.shields.io/packagist/dt/z3d0x/filament-fabricator.svg?style=for-the-badge)](https://packagist.org/packages/z3d0x/filament-fabricator)\n\n<p align=\"center\">\n  <img alt=\"fabricator banner\" src=\"https://raw.githubusercontent.com/z3d0x/filament-fabricator/2.x/art/banner.jpg\" />\n</p>\n\n**_What is Filament Fabricator?_** Filament Fabricator is simply said a block-based page builder skeleton. Filament Fabricator takes care of the PageResource & frontend routing, so you can focus on what really matters: your [Layouts](https://filamentphp.com/plugins/z3d0x-fabricator#layouts) & [Page Blocks](https://filamentphp.com/plugins/z3d0x-fabricator#page-blocks).\n\n## Compatibility\n\n| Fabricator                                                   | Filament | PHP  |\n| ------------------------------------------------------------ | -------- | ---- |\n| [1.x](https://github.com/z3d0x/filament-fabricator/tree/1.x) | ^2.0     | ^8.0 |\n| [2.x](https://github.com/z3d0x/filament-fabricator/tree/2.x) | ^3.0     | ^8.1 |\n| [3.x](https://github.com/z3d0x/filament-fabricator/tree/3.x) | ^4.0     | ^8.2 |\n| [4.x](https://github.com/z3d0x/filament-fabricator/tree/4.x) | ^5.0     | ^8.3 |\n\n## Installation\n\nYou can install the package via composer:\n\n```bash\ncomposer require z3d0x/filament-fabricator\n```\n\nAfter that run the install command:\n\n```bash\nphp artisan filament-fabricator:install\n```\n\nRegister a `FilamentFabricatorPlugin` instance in your Panel provider:\n\n```php\nuse Z3d0X\\FilamentFabricator\\FilamentFabricatorPlugin;\n\n//..\n\npublic function panel(Panel $panel): Panel\n{\n    return $panel\n        // ...\n        ->plugins([\n            FilamentFabricatorPlugin::make(),\n        ]);\n}\n```\n\nThen, publish the registered plugin assets:\n\n```\nphp artisan filament:assets\n```\n\n## Documentation\n\nDocumentation can be viewed at: https://filamentphp.com/plugins/z3d0x-fabricator\n\n## Screenshots\n\n<img alt=\"fabricator-index\" src=\"https://raw.githubusercontent.com/z3d0x/filament-fabricator/2.x/art/list-screenshot.png\">\n<img alt=\"fabricator-edit-1\" src=\"https://raw.githubusercontent.com/z3d0x/filament-fabricator/2.x/art/edit-screenshot-1.png\">\n<img alt=\"fabricator-edit-2\" src=\"https://raw.githubusercontent.com/z3d0x/filament-fabricator/2.x/art/edit-screenshot-2.png\">\n\n## Migrate\n\n### From 3.x to 4.x\n\n- Relies on PHP 8.3 as the minimum version\n\n### From 2.x to 3.x\n\n- There is no longer a default value for the `pages.layout` database column\n- `FilamentFabricatorManager#getPageUrlFromId` no longer has a `prefixSlash` parameter\n- Relies on PHP 8.2 as the minimum version\n\n### From Filament v4 to Filament v5\n\nFollowing [the upgrade guide from Filament](https://filamentphp.com/docs/5.x/upgrade-guide) should be enough.\n\nIn case it isn't, you can run the following commands:\n\n```bash\ncomposer require filament/upgrade:\"^5.0\" -W --dev\n\n./vendor/bin/filament-v5\n\n# Run the commands output by the upgrade script, they are unique to your app\ncomposer require filament/filament:\"^5.0\" z3d0x/filament-fabricator:\"^5.0\" -W --no-update\ncomposer update\n```\n\n### From Filament v3 to Filament v4\n\nFollowing [the upgrade guide from Filament](https://filamentphp.com/docs/4.x/upgrade-guide) should be enough.\n\nIn case it isn't, you can run the following commands:\n\n```bash\ncomposer require filament/upgrade:\"^4.0\" -W --dev\n\n./vendor/bin/filament-v4\n\n# Run the commands output by the upgrade script, they are unique to your app\ncomposer require filament/filament:\"^4.0\" z3d0x/filament-fabricator:\"^4.0\" -W --no-update\ncomposer update\n```\n\nFor more info on breaking changes, please refer to the [CHANGELOG](CHANGELOG.md)\n\n## Changelog\n\nPlease see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.\n\n## Contributing\n\nPlease see [CONTRIBUTING](.github/CONTRIBUTING.md) for details.\n\n## Security Vulnerabilities\n\nPlease review [our security policy](../../security/policy) on how to report security vulnerabilities.\n\n## Credits\n\n- [ZedoX](https://github.com/Z3d0X)\n- [Voltra](https://github.com/Voltra)\n- [Patrick Boivin](https://github.com/pboivin) - Filament Peek\n- [All Contributors](../../contributors)\n\n## License\n\nThe MIT License (MIT). Please see [License File](LICENSE.md) for more information.\n"
  },
  {
    "path": "bootstrap/app.php",
    "content": "<?php\n\nuse Filament\\FilamentServiceProvider;\nuse Filament\\Support\\SupportServiceProvider;\nuse Livewire\\LivewireServiceProvider;\nuse Orchestra\\Testbench\\Concerns\\CreatesApplication;\nuse Orchestra\\Testbench\\Foundation\\Application;\nuse Z3d0X\\FilamentFabricator\\FilamentFabricatorServiceProvider;\n\n$basePathLocator = new class\n{\n    use CreatesApplication;\n};\n\n$app = (new Application($basePathLocator::applicationBasePath()))\n    ->configure([\n        'enables_package_discoveries' => true,\n    ])\n    ->createApplication();\n\n$app->register(LivewireServiceProvider::class);\n$app->register(FilamentServiceProvider::class);\n$app->register(SupportServiceProvider::class);\n$app->register(FilamentFabricatorServiceProvider::class);\n\nreturn $app;\n"
  },
  {
    "path": "composer.json",
    "content": "{\n    \"name\": \"z3d0x/filament-fabricator\",\n    \"description\": \"Block-Based Page Builder Skeleton for your Filament Apps\",\n    \"keywords\": [\n        \"Z3d0X\",\n        \"laravel\",\n        \"filament-fabricator\"\n    ],\n    \"homepage\": \"https://github.com/z3d0x/filament-fabricator\",\n    \"license\": \"MIT\",\n    \"authors\": [\n        {\n            \"name\": \"Ziyaan Hassan\",\n            \"email\": \"ziyaan2010@gmail.com\",\n            \"role\": \"Developer\"\n        }\n    ],\n    \"require\": {\n        \"php\": \"^8.3\",\n        \"filament/filament\": \"^5.0\",\n        \"illuminate/contracts\": \"^11.0 | ^12.0 | ^13.0\",\n        \"livewire/livewire\": \"^4.0\",\n        \"pboivin/filament-peek\": \"^4.0\",\n        \"spatie/laravel-package-tools\": \"^1.13.5\"\n    },\n    \"require-dev\": {\n        \"driftingly/rector-laravel\": \"^2.0\",\n        \"filament/upgrade\": \"^5.0\",\n        \"larastan/larastan\": \"^2.9 | ^3.7 | dev-l13\",\n        \"laravel/pint\": \"^1.24\",\n        \"nunomaduro/collision\": \"^8.0\",\n        \"orchestra/testbench\": \"^10.0 | ^11.0\",\n        \"pestphp/pest\": \"^4.0\",\n        \"pestphp/pest-plugin-laravel\": \"^4.0\",\n        \"pestphp/pest-plugin-livewire\": \"^4.0\",\n        \"phpstan/phpstan-deprecation-rules\": \"^2.0\",\n        \"phpstan/phpstan-phpunit\": \"^2.0\",\n        \"rector/rector\": \"^2.1\",\n        \"spatie/laravel-ray\": \"^1.26\"\n    },\n    \"autoload\": {\n        \"psr-4\": {\n            \"Z3d0X\\\\FilamentFabricator\\\\\": \"src\",\n            \"Z3d0X\\\\FilamentFabricator\\\\Database\\\\Factories\\\\\": \"database/factories\"\n        }\n    },\n    \"autoload-dev\": {\n        \"psr-4\": {\n            \"Z3d0X\\\\FilamentFabricator\\\\Tests\\\\\": \"tests\"\n        }\n    },\n    \"scripts\": {\n        \"pint\": \"@php ./vendor/bin/pint\",\n        \"rector\": [\n            \"@php ./vendor/bin/rector\",\n            \"@pint\"\n        ],\n        \"test:pest\": \"@php ./vendor/bin/pest --parallel\",\n        \"test:phpstan\": \"@php ./vendor/bin/phpstan analyse\",\n        \"test\": [\n            \"@test:pest\",\n            \"@test:phpstan\"\n        ]\n    },\n    \"config\": {\n        \"sort-packages\": true,\n        \"allow-plugins\": {\n            \"composer/package-versions-deprecated\": true,\n            \"pestphp/pest-plugin\": true\n        }\n    },\n    \"extra\": {\n        \"laravel\": {\n            \"providers\": [\n                \"Z3d0X\\\\FilamentFabricator\\\\FilamentFabricatorServiceProvider\"\n            ],\n            \"aliases\": {\n                \"FilamentFabricator\": \"Z3d0X\\\\FilamentFabricator\\\\Facades\\\\FilamentFabricator\"\n            }\n        }\n    },\n    \"minimum-stability\": \"dev\",\n    \"prefer-stable\": true\n}\n"
  },
  {
    "path": "config/filament-fabricator.php",
    "content": "<?php\n\nuse Z3d0X\\FilamentFabricator\\Models\\Page;\nuse Z3d0X\\FilamentFabricator\\Resources\\PageResource;\n\n// config for Z3d0X/FilamentFabricator\nreturn [\n    'routing' => [\n        /**\n         * Whether routing should be automatically handled.\n         * Disable if you want finer and manual control over how the routing to your pages is done.\n         */\n        'enabled' => true,\n\n        /**\n         * The prefix to use for all pages' routes.\n         * Leave to null if you don't want them to have a prefix.\n         * A prefix set to '/pages' means that a page of slug 'page-1'\n         * would be accessed through `/pages/page-1` if routing is enabled.\n         */\n        'prefix' => null, //    /pages\n    ],\n\n    'layouts' => [\n        /**\n         * The base namespace for all your\n         * filament-fabricator page layouts\n         */\n        'namespace' => 'App\\\\Filament\\\\Fabricator\\\\Layouts',\n\n        /**\n         * The path to your layouts (folder or glob)\n         * This is used when auto-registering them\n         */\n        'path' => app_path('Filament/Fabricator/Layouts'),\n\n        /**\n         * A list of layout classes you want to manually register\n         * in addition to those that are auto-registered\n         */\n        'register' => [\n            //\n        ],\n    ],\n\n    'page-blocks' => [\n        /**\n         * The base namespace for all your filament-fabricator page blocks\n         */\n        'namespace' => 'App\\\\Filament\\\\Fabricator\\\\PageBlocks',\n\n        /**\n         * The path to your blocks (folder or glob)\n         * This is used when auto-registering them\n         */\n        'path' => app_path('Filament/Fabricator/PageBlocks'),\n\n        /**\n         * A list of block classes you want to manually register\n         * in addition to those that are auto-registered\n         */\n        'register' => [\n            //\n        ],\n    ],\n\n    /**\n     * The middleware(s) to apply to your pages when routing is enabled\n     */\n    'middleware' => [\n        'web',\n    ],\n\n    /**\n     * The page model to be used by the package.\n     * Replace this if you ever extend it\n     */\n    'page-model' => Page::class,\n\n    /**\n     * The page filament resource to be used by the package.\n     * Replace this if you ever extend it\n     */\n    'page-resource' => PageResource::class,\n\n    /**\n     * Whether you want to have a view page as part of your PageResource\n     */\n    'enable-view-page' => false,\n\n    /**\n     * Whether to hook into artisan's core commands to clear and refresh page route caches along with the rest.\n     * Disable for manual control over cache.\n     *\n     * This is the list of commands that will be hooked into:\n     *  - cache:clear        -> clear routes cache\n     *  - config:cache       -> refresh routes cache\n     *  - config:clear       -> clear routes cache\n     *  - optimize           -> refresh routes cache\n     *  - optimize:clear     -> clear routes cache\n     *  - route:clear        -> clear routes cache\n     */\n    'hook-to-commands' => true,\n\n    /**\n     * This is the name of the table that will be created by the migration and\n     * used by the above page-model shipped with this package.\n     */\n    'table_name' => 'pages',\n];\n"
  },
  {
    "path": "database/factories/ModelFactory.php",
    "content": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\Database\\Factories;\n\nuse Illuminate\\Database\\Eloquent\\Factories\\Factory;\n\n/*\nclass ModelFactory extends Factory\n{\n    protected $model = YourModel::class;\n\n    public function definition()\n    {\n        return [\n\n        ];\n    }\n}\n*/\n"
  },
  {
    "path": "database/migrations/create_pages_table.php.stub",
    "content": "<?php\n\nuse Illuminate\\Database\\Migrations\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nreturn new class extends Migration\n{\n    public function up()\n    {\n        Schema::create(config('filament-fabricator.table_name', 'pages'), function (Blueprint $table) {\n            $table->id();\n            $table->string('title')->index();\n            $table->string('slug')->unique();\n            $table->string('layout')->index();\n            $table->json('blocks');\n            $table->foreignId('parent_id')->nullable()->constrained(config('filament-fabricator.table_name', 'pages'))->cascadeOnDelete()->cascadeOnUpdate();\n            $table->timestamps();\n        });\n    }\n\n    public function down()\n    {\n        Schema::dropIfExists(config('filament-fabricator.table_name', 'pages'));\n    }\n};\n"
  },
  {
    "path": "database/migrations/fix_slug_unique_constraint_on_pages_table.php.stub",
    "content": "<?php\n\nuse Illuminate\\Database\\Migrations\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nreturn new class extends Migration\n{\n    public function up()\n    {\n        Schema::table(config('filament-fabricator.table_name', 'pages'), function (Blueprint $table) {\n            $table->dropUnique(['slug']);\n            $table->unique(['slug', 'parent_id']);\n        });\n    }\n\n    public function down()\n    {\n        Schema::table(config('filament-fabricator.table_name', 'pages'), function (Blueprint $table) {\n            $table->dropUnique(['slug', 'parent_id']);\n            $table->unique(['slug']);\n        });\n    }\n};\n"
  },
  {
    "path": "docs/README.md",
    "content": "## Introduction\n\n<p align=\"center\" class=\"filament-hidden\">\n  <img alt=\"fabricator banner\" src=\"https://raw.githubusercontent.com/z3d0x/filament-fabricator/2.x/art/banner.jpg\" />\n</p>\n\n**What is Filament Fabricator?** Filament Fabricator is simply said a block-based page builder skeleton.  Filament Fabricator takes care of the `PageResource` & frontend routing, so you can focus on what really matters: your [Layouts](#layouts) & [Page Blocks](#page-blocks).\n\n\n## Screenshots\n\n<img alt=\"fabricator-index\" src=\"https://raw.githubusercontent.com/z3d0x/filament-fabricator/2.x/art/list-screenshot.png\">\n<img alt=\"fabricator-edit-1\" src=\"https://raw.githubusercontent.com/z3d0x/filament-fabricator/2.x/art/edit-screenshot-1.png\">\n<img alt=\"fabricator-edit-2\" src=\"https://raw.githubusercontent.com/z3d0x/filament-fabricator/2.x/art/edit-screenshot-2.png\">\n\n## Installation\n\nOnce you have [Filament Panels](https://filamentphp.com/docs/3.x/panels/installation) configured. You can install this package via composer:\n```bash\ncomposer require z3d0x/filament-fabricator\n```\n\nAfter that run the install command: (this will publish the config & migrations)\n```bash\nphp artisan filament-fabricator:install\n```\nRegister a `FilamentFabricatorPlugin` instance in your Panel provider:\n\n```php\nuse Z3d0X\\FilamentFabricator\\FilamentFabricatorPlugin;\n\n//..\n\npublic function panel(Panel $panel): Panel\n{\n    return $panel\n        // ...\n        ->plugins([\n            FilamentFabricatorPlugin::make(),\n        ]);\n}\n```\n\nThen, publish the registered plugin assets:\n\n```\nphp artisan filament:assets\n```\n\n\nTo get started create a [Layout](#layouts) and then [Page Blocks](#page-blocks)\n\n## Layouts\n\n### Creating a Layout\n\nUse the following command to create a new Layout:\n```bash\nphp artisan filament-fabricator:layout DefaultLayout\n```\n\nThis will create the following Layout class:\n```php\nuse Z3d0X\\FilamentFabricator\\Layouts\\Layout;\n\nclass DefaultLayout extends Layout\n{\n    protected static ?string $name = 'default';\n}\n```\n\nand its corresponding blade component:\n```blade\n@props(['page'])\n<x-filament-fabricator::layouts.base :title=\"$page->title\">\n    {{-- Header Here --}}\n\n    <x-filament-fabricator::page-blocks :blocks=\"$page->blocks\" />\n\n     {{-- Footer Here --}}\n</x-filament-fabricator::layouts.base>\n```\nYou may edit this layout blade file however you want, as long as you are using the `filament-fabricator::page-blocks` to show the page blocks\n\n> Pro Tip 💡:  Use the `$page` instance to build your layout\n\n### Base Layouts\n\nYou may noticed that layouts created are wrapped in a `filament-fabricator::layouts.base` component. This is the [Base Layout](https://github.com/Z3d0X/filament-fabricator/blob/main/resources/views/components/layouts/base.blade.php). You can use the following, in the `boot()` of a ServiceProvider, to inject additional data to the base layout:\n\n```php\nuse Z3d0X\\FilamentFabricator\\Facades\\FilamentFabricator;\nuse Illuminate\\Foundation\\Vite;\n\n//Add custom tags (like `<meta>` & `<link>`)\nFilamentFabricator::pushMeta([\n    new HtmlString('<link rel=\"manifest\" href=\"/site.webmanifest\" />'),\n]);\n\n//Register scripts\nFilamentFabricator::registerScripts([\n    'https://unpkg.com/browse/tippy.js@6.3.7/dist/tippy.esm.js', //external url\n    mix('js/app.js'), //laravel-mix\n    app(Vite::class)('resources/css/app.js'), //vite\n    asset('js/app.js'), // asset from public folder\n]);\n\n//Register styles\nFilamentFabricator::registerStyles([\n    'https://unpkg.com/tippy.js@6/dist/tippy.css', //external url\n    mix('css/app.css'), //laravel-mix\n    app(Vite::class)('resources/css/app.css'), //vite\n    asset('css/app.css'), // asset from public folder\n]);\n\nFilamentFabricator::favicon(asset('favicon.ico'));\n```\n\nApart from these this plugin also adds the following [Filament's Render Hooks](https://filamentphp.com/docs/3.x/support/render-hooks) to the base layout:\n- `filament-fabricator::head.start` - after `<head>`\n- `filament-fabricator::head.end` - before `</head>`\n- `filament-fabricator::body.start` - after `<body>`\n- `filament-fabricator::body.end` - before `</body>`\n- `filament-fabricator::scripts.start` - before scripts are defined\n- `filament-fabricator::scripts.end` - after scripts are defined\n\n> Pro Tip 💡:  Using a base layout is completely optional, if you don't need it you may just remove it from the generated layout blade file. If you prefer, You may also use your own base layout.\n\n> Pro Tip 💡:  You might prefer using the corresponding constants defined in `\\Z3d0X\\FilamentFabricator\\View\\LayoutRenderHook` instead of hard-coded strings.\n\n## Page Blocks\n\n### Creating a Page Block\n\nUse the following command to create a new Page Block:\n```bash\nphp artisan filament-fabricator:block MyBlock\n```\nThis will create the following Page Block class (& its corresponding blade component view):\n```php\nuse Filament\\Forms\\Components\\Builder\\Block;\nuse Z3d0X\\FilamentFabricator\\PageBlocks\\PageBlock;\n\nclass MyBlock extends PageBlock\n{\n    protected static string $name = 'my-block';\n\n    public static function defineBlock(Block $block): Block\n    {\n        return block\n            ->schema([\n                //\n            ]);\n    }\n\n    public static function mutateData(array $data): array\n    {\n        return $data;\n    }\n}\n```\n\n> Pro Tip 💡:  You can access the `$page` instance in the block, by using the [`@aware` blade directive](https://laravel.com/docs/blade#accessing-parent-data)\n> ```blade\n> {{-- `my-block.blade.php` --}}\n> @aware(['page']) // make sure this line exists, in order to access `$page`\n>\n> @dump($page)\n> ```\n\n### Page Block Schema\n\nDefine your block schema in this method:\n```php\npublic static function defineBlock(Block $block): Block\n```\nYou may use any [Fields](https://filamentphp.com/docs/3.x/forms/fields/getting-started#available-fields) to make up your schema.\n\n> Pro Tip 💡:  You can conditionally allow blocks based on a layout using:\n> ```php\n> Block::make('foo')\n>     ->visible(fn ($get) => $get('../layout') == 'special')\n> ```\n\n### Mutate Data\n\nBy default, your blade component will receive raw data from all the fields as props\n\nExample:\n```php\n//Given the following schema\npublic static function defineBlock(Block $block): Block\n{\n    return block\n        ->schema([\n            TextInput::make('name'),\n        ]);\n}\n```\n```blade\n{{-- Your blade component would receive the following props --}}\n@dump($name)\n```\n\nHowever you may customize this behavior using:\n```php\n//`$data` is the raw block data.\npublic static function mutateData(array $data): array\n```\nThe array keys from this would be your blade component props.\n\nExample:\n```php\n// `MyBlock.php`\npublic static function mutateData(array $data): array\n{\n    return ['foo' => 'bar'];\n}\n```\n```blade\n{{--- `my-block.blade.php` --}}\n@dump($foo) // 'bar'\n```\n\n### Preload data\n\nIn some cases, you might want to preload some data for your blocks before mutating the data and then rendering it.\n\nThis is something you can do on a block type/class level:\n\n```php\n/**\n * Hook used to mass-preload related data to reduce the number of DB queries.\n * For instance, to load model objects/data from their IDs\n *\n * @param  (array{\n *     type: string,\n *     data: array,\n * })[]  $blocks  - The array of blocks' data for the given page and the given block type\n */\npublic static function preloadRelatedData(Page $page, array &$blocks): void\n```\n\nNote that your preload logic is run once per block type/class. It helps avoid N+1 query problems.\n\nYou get a mutable reference to an array of block render data that you can mutate with the data you preloaded. That being said, do keep in mind that you're working with references, you will need to throw a few `&` around to properly change your data.\n\nIt can be useful, for instance, when you want to preload related models based on an array of IDs.\n\nFor instance:\n```php\nuse App\\Models\\SomeModel;\nuse Z3d0X\\FilamentFabricator\\Helpers;\n\n// [...]\n\n/**\n * @param  (array{\n *     type: string,\n *     data: array{\n *          title: string,\n *          items: array{\n *              title: string,\n *              ref: int,\n *          }[]\n *     },\n * })[]  $blocks  - The array of blocks' data for the given page and the given block type\n */\n#[\\Override]\npublic static function preloadRelatedData(Page $page, array &$blocks): void {\n    Helpers::preloadRelatedModels(\n        blocks: $blocks,\n        property: 'items',\n        subProperty: 'ref',\n        modelClass: SomeModel::class,\n    );\n\n    // now $blocks[0]['data']['items'][0]['ref'] is the related instance of SomeModel\n}\n```\n\n## Page Builder\n\nUnderneath the hood `PageBuilder` is just a Filament's [Builder](https://filamentphp.com/docs/3.x/forms/fields/builder) field. Like other filament fields this field also has methods that can be used to modify it. You may configure it like this:\n```php\nuse Z3d0X\\FilamentFabricator\\Forms\\Components\\PageBuilder;\n\nPageBuilder::configureUsing(function (PageBuilder $builder) {\n    $builder->collapsible(); //You can use any method supported by the Builder field\n});\n```\n\n### Block Picker Styles\n\nIn addition to [customizations available in Filament's Builder](https://filamentphp.com/docs/3.x/forms/fields/builder#customizing-the-block-picker) `PageBuilder`, also includes a new method `blockPickerStyle()`.\nCurrently there are two styles available:\n- `BlockPickerStyle::Dropdown` (default)\n- `BlockPickerStyle::Modal`\n\n\n```php\nuse Z3d0X\\FilamentFabricator\\Enums\\BlockPickerStyle;\nuse Z3d0X\\FilamentFabricator\\Forms\\Components\\PageBuilder;\n\nPageBuilder::configureUsing(function (PageBuilder $builder) {\n    $builder->blockPickerStyle(BlockPickerStyle::Modal);\n});\n```\n\nan alternative one-liner way of changing block picker style is using `blockPickerStyle()` method when registering the `FilamentFabricatorPlugin` in your Panel provider:\n\n```php\nuse Z3d0X\\FilamentFabricator\\Enums\\BlockPickerStyle;\nuse Z3d0X\\FilamentFabricator\\FilamentFabricatorPlugin;\n\n//..\n\npublic function panel(Panel $panel): Panel\n{\n    return $panel\n        // ...\n        ->plugins([\n            FilamentFabricatorPlugin::make()\n                ->blockPickerStyle(BlockPickerStyle::Modal),\n        ]);\n}\n```\n\n> Pro Tip 💡: `BlockPickerStyle::Modal` works best when icons are assigned to blocks. https://filamentphp.com/docs/3.x/forms/fields/builder#setting-a-blocks-icon\n\n\n## Page Resource\n\n### Customize Navigation\n\nYou may use the following methods in the `boot()` of a ServiceProvider to customize the navigation item of `PageResource`\n```php\nuse Z3d0X\\FilamentFabricator\\Resources\\PageResource;\n\nPageResource::navigationGroup('Blog');\nPageResource::navigationSort(1);\nPageResource::navigationIcon('heroicon-o-cube');\n```\n\n### Authorization\n\nTo enforce policies, after generating a policy, you would need to register `\\Z3d0X\\FilamentFabricator\\Models\\Page` to use that policy in the `AuthServiceProvider`.\n\n```php\n<?php\n\nnamespace App\\Providers;\n\nuse App\\Policies\\PagePolicy;\nuse Illuminate\\Foundation\\Support\\Providers\\AuthServiceProvider as ServiceProvider;\nuse Z3d0X\\FilamentFabricator\\Models\\Page;\n\nclass AuthServiceProvider extends ServiceProvider\n{\n    protected $policies = [\n        Page::class => PagePolicy::class,\n    ];\n    //...\n}\n```\n> If you are using [Shield](https://filamentphp.com/plugins/bezhansalleh-shield) just register the `PagePolicy` generated by it\n\n## Caching\n\nBy default, routes will be cached in a lazy manner. That means that a page needs to be hit before it's cached.\n\nIf you so choose, you can also force all pages to be cached by running the following command:\n```bash\nphp artisan filament-fabricator:clear-routes-cache\n```\n\nBy running the following command instead, you'll ensure the data is fresh before it's cached:\n```bash\nphp artisan filament-fabricator:clear-routes-cache --refresh\n```\n\n## Configuration\n\n### Auto-routing\n\nBy default, your pages' routing will be done automatically for you so you don't have to worry about it.\n\nTo do that the package registers a fallback route which, when hit, will render your page.\n\nIf you want manual control over how your pages are rendered, you can disable this by setting the `routing.enabled` config option in your config file to `false`.\n\n### Route prefix\n\nIf you so desire, you can add a prefix to be used in all your pages' routes. This is used in conjunction with auto-routing.\n\nFor instance: If a page has a slug `page-1`, and the prefix is set to `/pages`, then you'll access that page at the URL `/pages/page-1`.\n\n> **Warning:** When changing the route prefix in the config, you'll want to run the `php artisan filament-fabricator:clear-routes-cache --refresh` command\n\n### Hooking the route cache into Laravel's lifecycle\n\nBy default routes are properly cached, cleared, and refreshed whenever you would expect it to.\n\nThis is achieved by hooking into the following core commands:\n\n- `cache:clear`        -> clear routes cache\n- `config:cache`       -> refresh routes cache\n- `config:clear`       -> clear routes cache\n- `optimize`           -> refresh routes cache\n- `optimize:clear`     -> clear routes cache\n- `route:clear`        -> clear routes cache\n\nIf you don't want this behavior, you can opt out of it by setting the `hook-to-commands` config option to `false` in your config file.\n\n\n## Compatibility\n| Fabricator | Filament | PHP |\n|------|----------|--------|\n| [1.x](https://github.com/z3d0x/filament-fabricator/tree/1.x) | ^2.0 | ^8.0 |\n| [2.x](https://github.com/z3d0x/filament-fabricator/tree/2.x) | ^3.0 | ^8.1 |\n| [3.x](https://github.com/z3d0x/filament-fabricator/tree/3.x) | ^4.0 | ^8.2 |\n"
  },
  {
    "path": "phpstan-baseline.neon",
    "content": ""
  },
  {
    "path": "phpstan.neon.dist",
    "content": "includes:\n    - vendor/larastan/larastan/extension.neon\n    - vendor/nesbot/carbon/extension.neon\n    - vendor/phpstan/phpstan-phpunit/extension.neon\n    - vendor/phpstan/phpstan-phpunit/rules.neon\n    - vendor/phpstan/phpstan-deprecation-rules/rules.neon\n\nparameters:\n    level: 5\n    paths:\n        - src\n        - config\n        - database\n        - routes\n    tmpDir: build/phpstan\n    treatPhpDocTypesAsCertain: false\n\n"
  },
  {
    "path": "phpunit.xml.dist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit\n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:noNamespaceSchemaLocation=\"vendor/phpunit/phpunit/phpunit.xsd\"\n    backupGlobals=\"false\"\n    backupStaticAttributes=\"false\"\n    bootstrap=\"vendor/autoload.php\"\n    colors=\"true\"\n    convertErrorsToExceptions=\"true\"\n    convertNoticesToExceptions=\"true\"\n    convertWarningsToExceptions=\"true\"\n    processIsolation=\"false\"\n    stopOnFailure=\"false\"\n    executionOrder=\"random\"\n    failOnWarning=\"true\"\n    failOnRisky=\"true\"\n    failOnEmptyTestSuite=\"true\"\n    beStrictAboutOutputDuringTests=\"true\"\n    verbose=\"true\"\n    debug=\"true\"\n>\n    <php>\n        <env name=\"APP_KEY\" value=\"base64:+1JX1LrX2QMk/Gaxv5r89uiy9JWOMKSKhUlwVrJL1A8=\"/>\n        <env name=\"PHPUNIT_TELEMETRY\" value=\"off\"/>\n    </php>\n\t<source ignoreIndirectDeprecations=\"true\" restrictNotices=\"true\" restrictWarnings=\"true\">\n\t\t<include>\n            <directory suffix=\".php\">./src</directory>\n            <directory suffix=\".php\">./routes</directory>\n        </include>\n\t</source>\n    <testsuites>\n        <testsuite name=\"Z3d0X Test Suite\">\n            <directory suffix=\".test.php\">tests/</directory>\n        </testsuite>\n    </testsuites>\n    <coverage>\n        <include>\n            <directory suffix=\".php\">./src</directory>\n        </include>\n        <report>\n            <html outputDirectory=\"build/coverage\"/>\n            <text outputFile=\"build/coverage.txt\"/>\n            <clover outputFile=\"build/logs/clover.xml\"/>\n        </report>\n    </coverage>\n    <logging>\n        <junit outputFile=\"build/report.junit.xml\"/>\n    </logging>\n</phpunit>\n"
  },
  {
    "path": "pint.json",
    "content": "{\n    \"preset\": \"laravel\",\n    \"rules\": {\n        \"blank_line_before_statement\": true,\n        \"concat_space\": {\n            \"spacing\": \"one\"\n        },\n        \"method_argument_space\": true,\n        \"single_trait_insert_per_statement\": true\n    }\n}\n"
  },
  {
    "path": "rector.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nuse Rector\\CodeQuality\\Rector\\Empty_\\SimplifyEmptyCheckOnEmptyArrayRector;\nuse Rector\\CodeQuality\\Rector\\FunctionLike\\SimplifyUselessVariableRector;\nuse Rector\\Config\\RectorConfig;\nuse Rector\\DeadCode\\Rector\\If_\\RemoveAlwaysTrueIfConditionRector;\nuse Rector\\DeadCode\\Rector\\Stmt\\RemoveUnreachableStatementRector;\nuse Rector\\Php74\\Rector\\Closure\\ClosureToArrowFunctionRector;\nuse Rector\\Php83\\Rector\\ClassMethod\\AddOverrideAttributeToOverriddenMethodsRector;\nuse Rector\\Strict\\Rector\\Empty_\\DisallowedEmptyRuleFixerRector;\nuse Rector\\TypeDeclaration\\Rector\\Property\\TypedPropertyFromStrictConstructorRector;\nuse RectorLaravel\\Set\\LaravelLevelSetList;\nuse RectorLaravel\\Set\\LaravelSetProvider;\n\nreturn RectorConfig::configure()\n    ->withPaths([\n        __DIR__ . '/bootstrap',\n        __DIR__ . '/config',\n        __DIR__ . '/routes',\n        __DIR__ . '/src',\n        __DIR__ . '/tests',\n    ])\n    ->withCache()\n    ->withParallel()\n    ->withRules([\n        AddOverrideAttributeToOverriddenMethodsRector::class,\n        TypedPropertyFromStrictConstructorRector::class,\n    ])\n    ->withSkip([\n        // Only add classes that give false positives here\n        ClosureToArrowFunctionRector::class,\n        DisallowedEmptyRuleFixerRector::class,\n        RemoveAlwaysTrueIfConditionRector::class,\n        RemoveUnreachableStatementRector::class,\n        SimplifyEmptyCheckOnEmptyArrayRector::class,\n        SimplifyUselessVariableRector::class,\n    ])\n    ->withPhpSets()\n    ->withPreparedSets(\n        deadCode: true,\n        codeQuality: true,\n        earlyReturn: true,\n    )\n    ->withSetProviders(LaravelSetProvider::class)\n    ->withComposerBased(laravel: true, phpunit: true)\n    ->withSets([\n        LaravelLevelSetList::UP_TO_LARAVEL_110,\n    ]);\n"
  },
  {
    "path": "resources/lang/ar/page-resource.php",
    "content": "<?php\n\nreturn [\n    'labels' => [\n        'blocks' => 'الوحدات',\n        'layout' => 'المخطط',\n        'page' => 'صفحة',\n        'pages' => 'صفحات',\n        'parent' => 'الرئيسي',\n        'slug' => 'الرابط المميز',\n        'title' => 'العنوان',\n        'url' => 'الرابط',\n    ],\n\n    'errors' => [\n        'slug_starts_or_ends_with_slash' => 'الرابط المميز لا يمكن أن يبدأ أو ينتهي بشرطة مائلة.',\n    ],\n\n    'actions' => [\n        'save' => 'حفظ',\n        'visit' => 'زيارة',\n    ],\n];\n"
  },
  {
    "path": "resources/lang/en/page-resource.php",
    "content": "<?php\n\nreturn [\n    'labels' => [\n        'blocks' => 'Blocks',\n        'layout' => 'Layout',\n        'page' => 'Page',\n        'pages' => 'Pages',\n        'parent' => 'Parent',\n        'slug' => 'Slug',\n        'title' => 'Title',\n        'url' => 'URL',\n    ],\n\n    'errors' => [\n        'slug_starts_or_ends_with_slash' => 'The slug cannot start or end with a slash.',\n    ],\n\n    'actions' => [\n        'save' => 'Save',\n        'visit' => 'Visit',\n    ],\n];\n"
  },
  {
    "path": "resources/lang/fr/page-resource.php",
    "content": "<?php\n\nreturn [\n    'labels' => [\n        'blocks' => 'Blocs',\n        'layout' => 'Mise en page',\n        'page' => 'Page',\n        'pages' => 'Pages',\n        'parent' => 'Parent',\n        'slug' => 'Slug',\n        'title' => 'Titre',\n        'url' => 'URL',\n    ],\n\n    'errors' => [\n        'slug_starts_or_ends_with_slash' => 'Le slug ne peut pas commencer ou se terminer par un slash.',\n    ],\n\n    'actions' => [\n        'save' => 'Enregistrer',\n        'visit' => 'Visiter',\n    ],\n];\n"
  },
  {
    "path": "resources/lang/id/page-resource.php",
    "content": "<?php\n\nreturn [\n    'labels' => [\n        'blocks' => 'Blok',\n        'layout' => 'Tata Letak',\n        'page' => 'Halaman',\n        'pages' => 'Halaman-halaman',\n        'parent' => 'Induk',\n        'slug' => 'Slug',\n        'title' => 'Judul',\n        'url' => 'URL',\n    ],\n\n    'errors' => [\n        'slug_starts_or_ends_with_slash' => 'Slug tidak boleh diawali atau diakhiri dengan garis miring.',\n    ],\n\n    'actions' => [\n        'save' => 'Simpan',\n        'visit' => 'Kunjungi',\n    ],\n];\n"
  },
  {
    "path": "resources/lang/nl/page-resource.php",
    "content": "<?php\n\nreturn [\n    'labels' => [\n        'blocks' => 'Blokken',\n        'layout' => 'Ontwerp',\n        'page' => 'Pagina',\n        'pages' => 'Pagina\\'s',\n        'parent' => 'Bovenliggend',\n        'slug' => 'Slug',\n        'title' => 'Titel',\n        'url' => 'URL',\n    ],\n\n    'errors' => [\n        'slug_starts_or_ends_with_slash' => 'De slug mag niet beginnen of eindigen met een schuine streep.',\n    ],\n\n    'actions' => [\n        'save' => 'Opslaan',\n        'visit' => 'Bezoeken',\n    ],\n];\n"
  },
  {
    "path": "resources/lang/pl/page-resource.php",
    "content": "<?php\n\nreturn [\n    'labels' => [\n        'blocks' => 'Bloki',\n        'layout' => 'Układ',\n        'page' => 'Strona',\n        'pages' => 'Strony',\n        'parent' => 'Element nadrzędny',\n        'slug' => 'Slug',\n        'title' => 'Tytuł',\n        'url' => 'URL',\n    ],\n\n    'errors' => [\n        'slug_starts_or_ends_with_slash' => 'Slug nie może zaczynać się ani kończyć znakiem „/”.',\n    ],\n\n    'actions' => [\n        'save' => 'Zapisz',\n        'visit' => 'Odwiedź',\n    ],\n];\n"
  },
  {
    "path": "resources/lang/ru/page-resource.php",
    "content": "<?php\n\nreturn [\n    'labels' => [\n        'blocks' => 'Блоки',\n        'layout' => 'Макет',\n        'page' => 'Страница',\n        'pages' => 'Страницы',\n        'parent' => 'Родительская страница',\n        'slug' => 'Ссылка',\n        'title' => 'Название',\n        'url' => 'URL',\n    ],\n\n    'errors' => [\n        'slug_starts_or_ends_with_slash' => 'Ссылка не может начинаться или заканчиваться косой чертой.',\n    ],\n\n    'actions' => [\n        'save' => 'Сохранить',\n        'visit' => 'Посетить',\n    ],\n];\n"
  },
  {
    "path": "resources/lang/tr/page-resource.php",
    "content": "<?php\n\nreturn [\n    'labels' => [\n        'blocks' => 'Bloklar',\n        'layout' => 'Düzen',\n        'page' => 'Sayfa',\n        'pages' => 'Sayfalar',\n        'parent' => 'Üst',\n        'slug' => 'Kısa Ad',\n        'title' => 'Başlık',\n        'url' => 'URL',\n    ],\n\n    'errors' => [\n        'slug_starts_or_ends_with_slash' => 'Kısa ad bir eğik çizgi ile başlayamaz veya bitirilemez.',\n    ],\n\n    'actions' => [\n        'save' => 'Kaydet',\n        'visit' => 'Siteyi Aç',\n    ],\n];\n"
  },
  {
    "path": "resources/lang/uk/page-resource.php",
    "content": "<?php\n\nreturn [\n    'labels' => [\n        'blocks' => 'Блоки',\n        'layout' => 'Макет',\n        'page' => 'Сторінка',\n        'pages' => 'Сторінки',\n        'parent' => 'Батьківська сторінка',\n        'slug' => 'Посилання',\n        'title' => 'Назва',\n        'url' => 'URL',\n    ],\n\n    'errors' => [\n        'slug_starts_or_ends_with_slash' => 'Посилання не може починатися або закінчуватися косою межею.',\n    ],\n\n    'actions' => [\n        'save' => 'Зберегти',\n        'visit' => 'Відвідати',\n    ],\n];\n"
  },
  {
    "path": "resources/views/components/forms/components/page-builder/dropdown-block-picker.blade.php",
    "content": "@props([\n    'action',\n    'afterItem' => null,\n    'blocks',\n    'columns' => null,\n    'key',\n    'statePath',\n    'trigger',\n    'width' => null,\n])\n\n<x-filament::dropdown\n    :width=\"$width\"\n    {{ $attributes->class(['fi-fo-builder-block-picker']) }}\n>\n    <x-slot name=\"trigger\">\n        {{ $trigger }}\n    </x-slot>\n\n    <x-filament::dropdown.list>\n        <div class=\"grid gap-1 {{ $columns ? 'grid-cols-' . ($columns['default'] ?? 1) : 'grid-cols-1' }}\">\n            @foreach ($blocks as $block)\n                @php\n                    $wireClickActionArguments = ['block' => $block->getName()];\n\n                    if (filled($afterItem)) {\n                        $wireClickActionArguments['afterItem'] = $afterItem;\n                    }\n\n                    $wireClickActionArguments = \\Illuminate\\Support\\Js::from($wireClickActionArguments);\n\n                    $wireClickAction = \"mountAction('{$action->getName()}', {$wireClickActionArguments}, { schemaComponent: '{$key}' })\";\n                @endphp\n\n                <x-filament::dropdown.list.item\n                    :icon=\"$block->getIcon()\"\n                    x-on:click=\"close\"\n                    :wire:click=\"$wireClickAction\"\n                >\n                    {{ $block->getLabel() }}\n                </x-filament::dropdown.list.item>\n            @endforeach\n        </div>\n    </x-filament::dropdown.list>\n</x-filament::dropdown>\n"
  },
  {
    "path": "resources/views/components/forms/components/page-builder/modal-block-picker.blade.php",
    "content": "@props([\n    'action',\n    'afterItem' => null,\n    'blocks',\n    'columns' => null,\n    'key',\n    'statePath',\n    'trigger',\n    'width' => null,\n])\n\n<x-filament::modal\n    :width=\"$width\"\n    {{ $attributes->class(['fi-fo-builder-block-picker']) }}\n>\n    <x-slot name=\"trigger\">\n        <div class=\"flex justify-center w-full\">\n            {{ $trigger }}\n        </div>\n    </x-slot>\n\n    <div class=\"grid gap-4\" style=\"grid-template-columns: repeat({{ $columns['lg'] ?? $columns['default'] ?? 3 }}, minmax(0, 1fr));\">\n        @foreach ($blocks as $block)\n            @php\n                $wireClickActionArguments = ['block' => $block->getName()];\n\n                if (filled($afterItem)) {\n                    $wireClickActionArguments['afterItem'] = $afterItem;\n                }\n\n                $wireClickActionArguments = \\Illuminate\\Support\\Js::from($wireClickActionArguments);\n\n                $wireClickAction = \"mountAction('{$action->getName()}', {$wireClickActionArguments}, { schemaComponent: '{$key}' })\";\n            @endphp\n\n\n            <button\n                type=\"button\"\n                class=\"flex flex-col items-center justify-center border border-gray-200 dark:border-white/10 size-full gap-4 whitespace-nowrap rounded-md p-2 text-sm transition-colors duration-75 outline-none hover:bg-gray-50 focus-visible:bg-gray-50 dark:hover:bg-white/5 dark:focus-visible:bg-white/5\"\n                x-on:click=\"close\"\n                wire:click=\"{{ $wireClickAction }}\"\n            >\n                @if ($icon = $block->getIcon())\n                    <x-filament::icon\n                        :icon=\"$icon\"\n                        class=\"size-8 text-gray-400 dark:text-gray-500\"\n                    />\n                @endif\n                <div>\n                    {{ $block->getLabel() }}\n                </div>\n            </button>\n        @endforeach\n    </div>\n</x-filament::modal>\n\n"
  },
  {
    "path": "resources/views/components/forms/components/page-builder.blade.php",
    "content": "@php\n    use Filament\\Actions\\Action;\n    use Z3d0X\\FilamentFabricator\\Enums\\BlockPickerStyle;\n    use Illuminate\\Support\\MessageBag;\n    use Illuminate\\Support\\ViewErrorBag;\n\n    $containers = $getChildComponentContainers();\n    $blockPickerBlocks = $getBlockPickerBlocks();\n    $blockPickerColumns = $getBlockPickerColumns();\n    $blockPickerWidth = $getBlockPickerWidth();\n    $blockPickerStyle = $getBlockPickerStyle();\n\n    $addAction = $getAction($getAddActionName());\n    $addBetweenAction = $getAction($getAddBetweenActionName());\n    $cloneAction = $getAction($getCloneActionName());\n    $collapseAllAction = $getAction($getCollapseAllActionName());\n    $expandAllAction = $getAction($getExpandAllActionName());\n    $deleteAction = $getAction($getDeleteActionName());\n    $moveDownAction = $getAction($getMoveDownActionName());\n    $moveUpAction = $getAction($getMoveUpActionName());\n    $reorderAction = $getAction($getReorderActionName());\n    $extraItemActions = $getExtraItemActions();\n\n    $isAddable = $isAddable();\n    $isCloneable = $isCloneable();\n    $isCollapsible = $isCollapsible();\n    $isDeletable = $isDeletable();\n    $isReorderableWithButtons = $isReorderableWithButtons();\n    $isReorderableWithDragAndDrop = $isReorderableWithDragAndDrop();\n\n    $key = $getKey();\n    $statePath = $getStatePath();\n@endphp\n\n<x-dynamic-component :component=\"$getFieldWrapperView()\" :field=\"$field\">\n    <div\n        x-data=\"{}\"\n        {{\n            $attributes\n                ->merge($getExtraAttributes(), escape: false)\n                ->class(['fi-fo-builder grid gap-y-4'])\n        }}\n    >\n        @if ($isCollapsible && ($collapseAllAction->isVisible() || $expandAllAction->isVisible()))\n            <div\n                @class([\n                    'flex gap-x-3',\n                    'hidden' => count($containers) < 2,\n                ])\n            >\n                @if ($collapseAllAction->isVisible())\n                    <span\n                        x-on:click=\"$dispatch('builder-collapse', '{{ $statePath }}')\"\n                    >\n                        {{ $collapseAllAction }}\n                    </span>\n                @endif\n\n                @if ($expandAllAction->isVisible())\n                    <span\n                        x-on:click=\"$dispatch('builder-expand', '{{ $statePath }}')\"\n                    >\n                        {{ $expandAllAction }}\n                    </span>\n                @endif\n            </div>\n        @endif\n\n        @if (count($containers))\n            <ul\n                x-sortable\n                data-sortable-animation-duration=\"{{ $getReorderAnimationDuration() }}\"\n                wire:end.stop=\"{{ 'mountAction(\\'reorder\\', { items: $event.target.sortable.toArray() }, { schemaComponent: \\'' . $key . '\\' })' }}\"\n                class=\"space-y-4\"\n            >\n                @php\n                    $hasBlockLabels = $hasBlockLabels();\n                    $hasBlockNumbers = $hasBlockNumbers();\n                @endphp\n\n                @foreach ($containers as $uuid => $item)\n                    @php\n                        $visibleExtraItemActions = array_filter(\n                            $extraItemActions,\n                            fn (Action $action): bool => $action(['item' => $uuid])->isVisible(),\n                        );\n                    @endphp\n\n                    <li\n                        wire:key=\"{{ $this->getId() }}.{{ $item->getStatePath() }}.{{ $field::class }}.item\"\n                        x-data=\"{\n                            isCollapsed: @js($isCollapsed($item)),\n                        }\"\n                        x-on:builder-expand.window=\"$event.detail === '{{ $statePath }}' && (isCollapsed = false)\"\n                        x-on:builder-collapse.window=\"$event.detail === '{{ $statePath }}' && (isCollapsed = true)\"\n                        x-on:expand=\"isCollapsed = false\"\n                        x-sortable-item=\"{{ $uuid }}\"\n                        class=\"fi-fo-builder-item rounded-xl bg-white shadow-sm ring-1 ring-gray-950/5 dark:bg-white/5 dark:ring-white/10\"\n                        x-bind:class=\"{ 'fi-collapsed overflow-hidden': isCollapsed }\"\n                    >\n                        @if ($isReorderableWithDragAndDrop || $isReorderableWithButtons || $hasBlockLabels || $isCloneable || $isDeletable || $isCollapsible || count($visibleExtraItemActions))\n                            <div\n                                class=\"fi-fo-builder-item-header flex items-center gap-x-3 overflow-hidden px-4 py-3\"\n                            >\n                                @if ($isReorderableWithDragAndDrop || $isReorderableWithButtons)\n                                    <ul class=\"flex items-center gap-x-3\">\n                                        @if ($isReorderableWithDragAndDrop)\n                                            <li x-sortable-handle>\n                                                {{ $reorderAction }}\n                                            </li>\n                                        @endif\n\n                                        @if ($isReorderableWithButtons)\n                                            <li>\n                                                {{ $moveUpAction(['item' => $uuid])->disabled($loop->first) }}\n                                            </li>\n\n                                            <li>\n                                                {{ $moveDownAction(['item' => $uuid])->disabled($loop->last) }}\n                                            </li>\n                                        @endif\n                                    </ul>\n                                @endif\n\n                                @if ($hasBlockLabels)\n                                    <h4\n                                        @if ($isCollapsible)\n                                            x-on:click.stop=\"isCollapsed = !isCollapsed\"\n                                        @endif\n                                        @class([\n                                            'text-sm font-medium text-gray-950 dark:text-white',\n                                            'truncate' => $isBlockLabelTruncated(),\n                                            'cursor-pointer select-none' => $isCollapsible,\n                                        ])\n                                    >\n                                        {{ $item->getParentComponent()->getLabel($item->getRawState(), $uuid) }}\n\n                                        @if ($hasBlockNumbers)\n                                            {{ $loop->iteration }}\n                                        @endif\n                                    </h4>\n                                @endif\n\n                                @if ($isCloneable || $isDeletable || $isCollapsible || count($visibleExtraItemActions))\n                                    <ul\n                                        class=\"ms-auto flex items-center gap-x-3\"\n                                    >\n                                        @foreach ($visibleExtraItemActions as $extraItemAction)\n                                            <li>\n                                                {{ $extraItemAction(['item' => $uuid]) }}\n                                            </li>\n                                        @endforeach\n\n                                        @if ($isCloneable)\n                                            <li>\n                                                {{ $cloneAction(['item' => $uuid]) }}\n                                            </li>\n                                        @endif\n\n                                        @if ($isDeletable)\n                                            <li>\n                                                {{ $deleteAction(['item' => $uuid]) }}\n                                            </li>\n                                        @endif\n\n                                        @if ($isCollapsible)\n                                            <li\n                                                class=\"relative transition\"\n                                                x-on:click.stop=\"isCollapsed = !isCollapsed\"\n                                                x-bind:class=\"{ '-rotate-180': isCollapsed }\"\n                                            >\n                                                <div\n                                                    class=\"transition\"\n                                                    x-bind:class=\"{ 'opacity-0 pointer-events-none': isCollapsed }\"\n                                                >\n                                                    {{ $getAction('collapse') }}\n                                                </div>\n\n                                                <div\n                                                    class=\"absolute inset-0 rotate-180 transition\"\n                                                    x-bind:class=\"{ 'opacity-0 pointer-events-none': ! isCollapsed }\"\n                                                >\n                                                    {{ $getAction('expand') }}\n                                                </div>\n                                            </li>\n                                        @endif\n                                    </ul>\n                                @endif\n                            </div>\n                        @endif\n\n                        <div\n                            x-show=\"! isCollapsed\"\n                            class=\"fi-fo-builder-item-content border-t border-gray-100 p-4 dark:border-white/10\"\n                        >\n                            {{ $item }}\n                        </div>\n                    </li>\n\n                    @if (! $loop->last)\n                        @if ($isAddable && $addBetweenAction->isVisible())\n                            <li class=\"relative -top-2 !mt-0 h-0\">\n                                <div\n                                    class=\"flex w-full justify-center opacity-0 transition duration-75 hover:opacity-100\"\n                                >\n                                    <div\n                                        class=\"fi-fo-builder-block-picker-ctn rounded-lg bg-white dark:bg-gray-900\"\n                                    >\n                                        @if ($blockPickerStyle === BlockPickerStyle::Dropdown)\n                                            <x-filament-fabricator::forms.components.page-builder.dropdown-block-picker\n                                                :action=\"$addBetweenAction\"\n                                                :after-item=\"$uuid\"\n                                                :columns=\"$blockPickerColumns\"\n                                                :blocks=\"$blockPickerBlocks\"\n                                                :key=\"$key\"\n                                                :state-path=\"$statePath\"\n                                                :width=\"$blockPickerWidth\"\n                                            >\n                                                <x-slot name=\"trigger\">\n                                                    {{ $addBetweenAction }}\n                                                </x-slot>\n                                            </x-filament-fabricator::forms.components.page-builder.dropdown-block-picker>\n                                        @elseif ($blockPickerStyle === BlockPickerStyle::Modal)\n                                            <x-filament-fabricator::forms.components.page-builder.modal-block-picker\n                                                :action=\"$addBetweenAction\"\n                                                :after-item=\"$uuid\"\n                                                :columns=\"$blockPickerColumns\"\n                                                :blocks=\"$blockPickerBlocks\"\n                                                :key=\"$key\"\n                                                :state-path=\"$statePath\"\n                                                :width=\"$blockPickerWidth\"\n                                            >\n                                                <x-slot name=\"trigger\">\n                                                    {{ $addBetweenAction }}\n                                                </x-slot>\n                                            </x-filament-fabricator::forms.components.page-builder.modal-block-picker>\n                                        @endif\n                                    </div>\n                                </div>\n                            </li>\n                        @elseif (filled($labelBetweenItems = $getLabelBetweenItems()))\n                            <li\n                                class=\"relative border-t border-gray-200 dark:border-white/10\"\n                            >\n                                <span\n                                    class=\"absolute -top-3 left-3 px-1 text-sm font-medium\"\n                                >\n                                    {{ $labelBetweenItems }}\n                                </span>\n                            </li>\n                        @endif\n                    @endif\n                @endforeach\n            </ul>\n        @endif\n\n        @if ($isAddable)\n            @if ($blockPickerStyle === BlockPickerStyle::Dropdown)\n                <x-filament-fabricator::forms.components.page-builder.dropdown-block-picker\n                    :action=\"$addAction\"\n                    :blocks=\"$blockPickerBlocks\"\n                    :columns=\"$blockPickerColumns\"\n                    :key=\"$key\"\n                    :state-path=\"$statePath\"\n                    :width=\"$blockPickerWidth\"\n                    class=\"flex justify-center\"\n                >\n                    <x-slot name=\"trigger\">\n                        {{ $addAction }}\n                    </x-slot>\n                </x-filament-fabricator::forms.components.page-builder.dropdown-block-picker>\n            @elseif ($blockPickerStyle === BlockPickerStyle::Modal)\n                <x-filament-fabricator::forms.components.page-builder.modal-block-picker\n                    :action=\"$addAction\"\n                    :blocks=\"$blockPickerBlocks\"\n                    :columns=\"$blockPickerColumns\"\n                    :key=\"$key\"\n                    :state-path=\"$statePath\"\n                    :width=\"$blockPickerWidth\"\n                    class=\"flex justify-center\"\n                >\n                    <x-slot name=\"trigger\">\n                        {{ $addAction }}\n                    </x-slot>\n                </x-filament-fabricator::forms.components.page-builder.modal-block-picker>\n            @endif\n        @endif\n    </div>\n</x-dynamic-component>\n"
  },
  {
    "path": "resources/views/components/layouts/base.blade.php",
    "content": "@props([\n    'page',\n    'title' => null,\n    'dir' => 'ltr',\n])\n\n@use(Z3d0X\\FilamentFabricator\\View\\LayoutRenderHook)\n\n<!DOCTYPE html>\n<html lang=\"{{ str_replace('_', '-', app()->getLocale()) }}\" dir=\"{{ $dir }}\" class=\"filament-fabricator\">\n\n<head>\n    {{ \\Filament\\Support\\Facades\\FilamentView::renderHook(LayoutRenderHook::HEAD_START) }}\n\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <meta name=\"csrf-token\" content=\"{{ csrf_token() }}\">\n\n    @foreach (\\Z3d0X\\FilamentFabricator\\Facades\\FilamentFabricator::getMeta() as $tag)\n        {{ $tag }}\n    @endforeach\n\n    @if ($favicon = \\Z3d0X\\FilamentFabricator\\Facades\\FilamentFabricator::getFavicon())\n        <link rel=\"icon\" href=\"{{ $favicon }}\">\n    @endif\n\n    <title>{{ $title ? \"{$title} - \" : null }} {{ config('app.name') }}</title>\n\n\n    <style>\n        [x-cloak=\"\"],\n        [x-cloak=\"x-cloak\"],\n        [x-cloak=\"1\"] {\n            display: none !important;\n        }\n    </style>\n\n\n    @foreach (\\Z3d0X\\FilamentFabricator\\Facades\\FilamentFabricator::getStyles() as $name => $path)\n        @if (\\Illuminate\\Support\\Str::of($path)->startsWith('<'))\n            {!! $path !!}\n        @else\n            <link rel=\"stylesheet\" href=\"{{ $path }}\" />\n        @endif\n    @endforeach\n\n    {{ \\Filament\\Support\\Facades\\FilamentView::renderHook(LayoutRenderHook::HEAD_END) }}\n</head>\n\n<body class=\"filament-fabricator-body\">\n    {{ \\Filament\\Support\\Facades\\FilamentView::renderHook(LayoutRenderHook::BODY_START) }}\n\n    {{ $slot }}\n\n    {{ \\Filament\\Support\\Facades\\FilamentView::renderHook(LayoutRenderHook::SCRIPTS_START) }}\n\n    @foreach (\\Z3d0X\\FilamentFabricator\\Facades\\FilamentFabricator::getScripts() as $name => $path)\n        @if (\\Illuminate\\Support\\Str::of($path)->startsWith('<'))\n            {!! $path !!}\n        @else\n            <script defer src=\"{{ $path }}\"></script>\n        @endif\n    @endforeach\n\n    @stack('scripts')\n\n    {{ \\Filament\\Support\\Facades\\FilamentView::renderHook(LayoutRenderHook::SCRIPTS_END) }}\n\n    {{ \\Filament\\Support\\Facades\\FilamentView::renderHook(LayoutRenderHook::BODY_END) }}\n</body>\n\n</html>\n"
  },
  {
    "path": "resources/views/components/page-blocks.blade.php",
    "content": "@aware(['page'])\n@props(['blocks' => []])\n\n@php\n    $groups = \\Z3d0X\\FilamentFabricator\\Helpers::arrayRefsGroupBy($blocks, 'type');\n\n    foreach ($groups as $blockType => &$group) {\n        /**\n         * @var class-string<\\Z3d0X\\FilamentFabricator\\PageBlocks\\PageBlock> $blockClass\n         */\n        $blockClass = \\Z3d0X\\FilamentFabricator\\Facades\\FilamentFabricator::getPageBlockFromName($blockType);\n\n        if (!empty($blockClass) && $page !== null) {\n            $blockClass::preloadRelatedData($page, $group);\n        }\n    }\n@endphp\n\n@foreach ($blocks as $blockData)\n    @php\n        $pageBlock = \\Z3d0X\\FilamentFabricator\\Facades\\FilamentFabricator::getPageBlockFromName($blockData['type']);\n    @endphp\n\n    @isset($pageBlock)\n        <x-dynamic-component :component=\"$pageBlock::getComponent()\" :attributes=\"new \\Illuminate\\View\\ComponentAttributeBag($pageBlock::mutateData($blockData['data']))\" />\n    @endisset\n@endforeach\n"
  },
  {
    "path": "resources/views/preview.blade.php",
    "content": "@props(['page', 'component'])\n<x-dynamic-component\n    :component=\"$component\"\n    :page=\"$page\"\n/>\n"
  },
  {
    "path": "resources/views/tests/fixtures/blade-wrapper.blade.php",
    "content": "<div>\n    {{ $this->form }}\n</div>\n"
  },
  {
    "path": "routes/web.php",
    "content": "<?php\n\nuse Illuminate\\Support\\Facades\\Route;\nuse Z3d0X\\FilamentFabricator\\Facades\\FilamentFabricator;\nuse Z3d0X\\FilamentFabricator\\Http\\Controllers\\PageController;\n\nif (config('filament-fabricator.routing.enabled')) {\n    Route::middleware(config('filament-fabricator.middleware') ?? [])\n        ->prefix(FilamentFabricator::getRoutingPrefix())\n        ->group(function () {\n            Route::get('/{filamentFabricatorPage?}', PageController::class)\n                ->where('filamentFabricatorPage', '.*')\n                ->fallback();\n        });\n}\n"
  },
  {
    "path": "src/Commands/Aliases/MakeLayoutCommand.php",
    "content": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\Commands\\Aliases;\n\nuse Z3d0X\\FilamentFabricator\\Commands;\n\n/**\n * @deprecated\n * @see Commands\\MakeLayoutCommand\n */\nclass MakeLayoutCommand extends Commands\\MakeLayoutCommand\n{\n    protected $hidden = true;\n\n    protected $signature = 'make:filament-fabricator-layout {name?} {--F|force}';\n}\n"
  },
  {
    "path": "src/Commands/Aliases/MakePageBlockCommand.php",
    "content": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\Commands\\Aliases;\n\nuse Z3d0X\\FilamentFabricator\\Commands;\n\n/**\n * @deprecated\n * @see Commands\\MakePageBlockCommand\n */\nclass MakePageBlockCommand extends Commands\\MakePageBlockCommand\n{\n    protected $hidden = true;\n\n    protected $signature = 'make:filament-fabricator-page-block {name?} {--F|force}';\n}\n"
  },
  {
    "path": "src/Commands/ClearRoutesCacheCommand.php",
    "content": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\Commands;\n\nuse Illuminate\\Console\\Command;\nuse Illuminate\\Database\\Eloquent\\Model;\nuse Illuminate\\Support\\Facades\\Cache;\nuse Z3d0X\\FilamentFabricator\\Facades\\FilamentFabricator;\nuse Z3d0X\\FilamentFabricator\\Models\\Contracts\\Page as PageContract;\nuse Z3d0X\\FilamentFabricator\\Services\\PageRoutesService;\n\nclass ClearRoutesCacheCommand extends Command\n{\n    protected $signature = 'filament-fabricator:clear-routes-cache {--R|refresh}';\n\n    protected $description = 'Clear the routes\\' cache';\n\n    public function __construct(protected PageRoutesService $pageRoutesService)\n    {\n        parent::__construct();\n    }\n\n    public function handle(): int\n    {\n        $shouldRefresh = (bool) $this->option('refresh');\n\n        /**\n         * @var array<array-key,PageContract&Model> $pages\n         */\n        $pages = FilamentFabricator::getPageModel()::query()\n            ->whereNull('parent_id')\n            ->with('allChildren')\n            ->get();\n\n        foreach ($pages as $page) {\n            $this->clearPageCache($page, $shouldRefresh);\n\n            if ($shouldRefresh) {\n                $this->pageRoutesService->updateUrlsOf($page);\n            }\n        }\n\n        return static::SUCCESS;\n    }\n\n    protected function clearPageCache(PageContract $page, bool $shouldRefresh = false)\n    {\n        $this->pageRoutesService->removeUrlsOf($page);\n        $argSets = $page->getAllUrlCacheKeysArgs();\n\n        foreach ($argSets as $args) {\n            $key = $page->getUrlCacheKey($args);\n            Cache::forget($key);\n\n            if ($shouldRefresh) {\n                // Caches the URL before returning it\n                /* $noop = */ $page->getUrl($args);\n            }\n        }\n\n        $childPages = $page->allChildren;\n\n        if (filled($childPages)) {\n            foreach ($childPages as $childPage) {\n                $this->clearPageCache($childPage, $shouldRefresh);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/Commands/MakeLayoutCommand.php",
    "content": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\Commands;\n\nuse Filament\\Support\\Commands\\Concerns\\CanManipulateFiles;\nuse Illuminate\\Console\\Command;\nuse Illuminate\\Support\\Str;\n\nuse function Laravel\\Prompts\\text;\n\nclass MakeLayoutCommand extends Command\n{\n    use CanManipulateFiles;\n\n    protected $signature = 'filament-fabricator:layout {name?} {--F|force}';\n\n    protected $description = 'Create a new filament-fabricator layout';\n\n    public function handle(): int\n    {\n        $layout = (string) Str::of($this->argument('name') ?? text(\n            label: 'What is the layout name?',\n            placeholder: 'DefaultLayout',\n            required: true,\n        ))\n            ->trim('/')\n            ->trim('\\\\')\n            ->trim(' ')\n            ->replace('/', '\\\\');\n\n        $layoutClass = (string) Str::of($layout)->afterLast('\\\\');\n\n        $layoutNamespace = Str::of($layout)->contains('\\\\') ?\n            (string) Str::of($layout)->beforeLast('\\\\') :\n            '';\n\n        $shortName = Str::of($layout)\n            ->beforeLast('Layout')\n            ->explode('\\\\')\n            ->map(fn ($segment) => Str::kebab($segment))\n            ->implode('.');\n\n        $view = Str::of($layout)\n            ->beforeLast('Layout')\n            ->prepend('components\\\\filament-fabricator\\\\layouts\\\\')\n            ->explode('\\\\')\n            ->map(fn ($segment) => Str::kebab($segment))\n            ->implode('.');\n\n        $path = app_path(\n            (string) Str::of($layout)\n                ->prepend('Filament\\\\Fabricator\\\\Layouts\\\\')\n                ->replace('\\\\', '/')\n                ->append('.php'),\n        );\n\n        $viewPath = resource_path(\n            (string) Str::of($view)\n                ->replace('.', '/')\n                ->prepend('views/')\n                ->append('.blade.php'),\n        );\n\n        $files = [$path, $viewPath];\n\n        if (! $this->option('force') && $this->checkForCollision($files)) {\n            return static::INVALID;\n        }\n\n        $this->copyStubToApp('Layout', $path, [\n            'class' => $layoutClass,\n            'namespace' => 'App\\\\Filament\\\\Fabricator\\\\Layouts' . ($layoutNamespace !== '' ? \"\\\\{$layoutNamespace}\" : ''),\n            'shortName' => $shortName,\n        ]);\n\n        $this->copyStubToApp('LayoutView', $viewPath);\n\n        $this->info(\"Successfully created {$layout}!\");\n\n        return static::SUCCESS;\n    }\n}\n"
  },
  {
    "path": "src/Commands/MakePageBlockCommand.php",
    "content": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\Commands;\n\nuse Filament\\Support\\Commands\\Concerns\\CanManipulateFiles;\nuse Illuminate\\Console\\Command;\nuse Illuminate\\Support\\Str;\n\nuse function Laravel\\Prompts\\text;\n\nclass MakePageBlockCommand extends Command\n{\n    use CanManipulateFiles;\n\n    protected $signature = 'filament-fabricator:block {name?} {--F|force}';\n\n    protected $description = 'Create a new filament-fabricator page block';\n\n    public function handle(): int\n    {\n        $pageBlock = (string) Str::of($this->argument('name') ?? text(\n            label: 'What is the block name?',\n            placeholder: 'HeroBlock',\n            required: true,\n        ))\n            ->trim('/')\n            ->trim('\\\\')\n            ->trim(' ')\n            ->replace('/', '\\\\');\n\n        $pageBlockClass = (string) Str::of($pageBlock)->afterLast('\\\\');\n\n        $pageBlockNamespace = Str::of($pageBlock)->contains('\\\\') ?\n            (string) Str::of($pageBlock)->beforeLast('\\\\') :\n            '';\n\n        $shortName = Str::of($pageBlock)\n            ->beforeLast('Block')\n            ->explode('\\\\')\n            ->map(fn ($segment) => Str::kebab($segment))\n            ->implode('.');\n\n        $view = Str::of($pageBlock)\n            ->beforeLast('Block')\n            ->prepend('components\\\\filament-fabricator\\\\page-blocks\\\\')\n            ->explode('\\\\')\n            ->map(fn ($segment) => Str::kebab($segment))\n            ->implode('.');\n\n        $path = app_path(\n            (string) Str::of($pageBlock)\n                ->prepend('Filament\\\\Fabricator\\\\PageBlocks\\\\')\n                ->replace('\\\\', '/')\n                ->append('.php'),\n        );\n\n        $viewPath = resource_path(\n            (string) Str::of($view)\n                ->replace('.', '/')\n                ->prepend('views/')\n                ->append('.blade.php'),\n        );\n\n        $files = [$path, $viewPath];\n\n        if (! $this->option('force') && $this->checkForCollision($files)) {\n            return static::INVALID;\n        }\n\n        $this->copyStubToApp('PageBlock', $path, [\n            'class' => $pageBlockClass,\n            'namespace' => 'App\\\\Filament\\\\Fabricator\\\\PageBlocks' . ($pageBlockNamespace !== '' ? \"\\\\{$pageBlockNamespace}\" : ''),\n            'shortName' => $shortName,\n        ]);\n\n        $this->copyStubToApp('PageBlockView', $viewPath);\n\n        $this->info(\"Successfully created {$pageBlock}!\");\n\n        return static::SUCCESS;\n    }\n}\n"
  },
  {
    "path": "src/Enums/BlockPickerStyle.php",
    "content": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\Enums;\n\nenum BlockPickerStyle\n{\n    case Dropdown;\n    case Modal;\n}\n"
  },
  {
    "path": "src/Facades/FilamentFabricator.php",
    "content": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\Facades;\n\nuse Closure;\nuse Illuminate\\Database\\Eloquent\\Model;\nuse Illuminate\\Support\\Facades\\Facade;\nuse Z3d0X\\FilamentFabricator\\FilamentFabricatorManager;\nuse Z3d0X\\FilamentFabricator\\Models\\Contracts\\Page as PageContract;\n\n/**\n * @method static void registerComponent(string $class, string $baseClass)\n * @method static void registerLayout(string $layout)\n * @method static void registerPageBlock(string $pageBlock)\n * @method static void registerSchemaSlot(string $name, array|Closure $schema)\n * @method static void pushMeta(array $meta)\n * @method static void registerScripts(array $scripts)\n * @method static void registerStyles(array $styles)\n * @method static void favicon(?string $favicon)\n * @method static ?string getLayoutFromName(string $layoutName)\n * @method static ?string getPageBlockFromName(string $name)\n * @method static array getLayouts()\n * @method static string getDefaultLayoutName()\n * @method static array getPageBlocks()\n * @method static array getPageBlocksRaw()\n * @method static array|Closure getSchemaSlot(string $name)\n * @method static array getMeta()\n * @method static array getScripts()\n * @method static array getStyles()\n * @method static ?string getFavicon()\n * @method static class-string<PageContract&Model> getPageModel()\n * @method static ?string getRoutingPrefix()\n * @method static array getPageUrls()\n * @method static ?string getPageUrlFromId(int $id, bool $prefixSlash = false)\n *\n * @see FilamentFabricatorManager\n */\nclass FilamentFabricator extends Facade\n{\n    protected static function getFacadeAccessor()\n    {\n        return 'filament-fabricator';\n    }\n}\n"
  },
  {
    "path": "src/FilamentFabricatorManager.php",
    "content": "<?php\n\nnamespace Z3d0X\\FilamentFabricator;\n\nuse Closure;\nuse Exception;\nuse Filament\\Forms\\Components\\Builder\\Block;\nuse Illuminate\\Database\\Eloquent\\Model;\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\Support\\Str;\nuse InvalidArgumentException;\nuse Z3d0X\\FilamentFabricator\\Layouts\\Layout;\nuse Z3d0X\\FilamentFabricator\\Models\\Contracts\\Page as PageContract;\nuse Z3d0X\\FilamentFabricator\\Models\\Page;\nuse Z3d0X\\FilamentFabricator\\PageBlocks\\PageBlock;\nuse Z3d0X\\FilamentFabricator\\Services\\PageRoutesService;\n\nclass FilamentFabricatorManager\n{\n    const ID = 'filament-fabricator';\n\n    /** @var Collection<string, class-string<PageBlock>> */\n    protected Collection $pageBlocks;\n\n    /** @var Collection<string, class-string<Layout>> */\n    protected Collection $layouts;\n\n    protected array $schemaSlot = [];\n\n    protected array $meta = [];\n\n    protected array $scripts = [];\n\n    protected array $styles = [];\n\n    protected ?string $favicon = 'favicon.ico';\n\n    protected array $pageUrls = [];\n\n    /**\n     * @note It's only separated to not cause a major version change.\n     * In the next major release, feel free to make it a constructor promoted property\n     */\n    protected PageRoutesService $routesService;\n\n    public function __construct(?PageRoutesService $routesService = null)\n    {\n        $this->routesService = $routesService ?? resolve(PageRoutesService::class);\n\n        /** @var Collection<string, class-string<PageBlock>> */\n        $pageBlocks = collect([]);\n\n        /** @var Collection<string, class-string<Layout>> */\n        $layouts = collect([]);\n\n        $this->pageBlocks = $pageBlocks;\n        $this->layouts = $layouts;\n    }\n\n    /**\n     * @param  class-string  $class\n     * @param  class-string<Layout>|class-string<PageBlock>  $baseClass\n     */\n    public function registerComponent(string $class, string $baseClass): void\n    {\n        match ($baseClass) {\n            Layout::class => static::registerLayout($class),\n            PageBlock::class => static::registerPageBlock($class),\n            default => throw new Exception('Invalid class type'),\n        };\n    }\n\n    /** @param  class-string<Layout>  $layout */\n    public function registerLayout(string $layout): void\n    {\n        if (! is_subclass_of($layout, Layout::class)) {\n            throw new InvalidArgumentException(\"{$layout} must extend \" . Layout::class);\n        }\n\n        $this->layouts->put($layout::getName(), $layout);\n    }\n\n    /** @param  class-string<PageBlock>  $pageBlock */\n    public function registerPageBlock(string $pageBlock): void\n    {\n        if (! is_subclass_of($pageBlock, PageBlock::class)) {\n            throw new InvalidArgumentException(\"{$pageBlock} must extend \" . PageBlock::class);\n        }\n\n        $this->pageBlocks->put($pageBlock::getName(), $pageBlock);\n    }\n\n    public function registerSchemaSlot(string $name, array|Closure $schema): void\n    {\n        $this->schemaSlot[$name] = $schema;\n    }\n\n    public function pushMeta(array $meta): void\n    {\n        $this->meta = array_merge($this->meta, $meta);\n    }\n\n    public function registerScripts(array $scripts): void\n    {\n        $this->scripts = array_merge($this->scripts, $scripts);\n    }\n\n    public function registerStyles(array $styles): void\n    {\n        $this->styles = array_merge($this->styles, $styles);\n    }\n\n    public function favicon(string $favicon): void\n    {\n        $this->favicon = $favicon;\n    }\n\n    /**\n     * @return class-string<Layout>|null\n     */\n    public function getLayoutFromName(string $layoutName): ?string\n    {\n        return $this->layouts->get($layoutName);\n    }\n\n    /**\n     * @return class-string<PageBlock>|null\n     */\n    public function getPageBlockFromName(string $name): ?string\n    {\n        return $this->pageBlocks->get($name);\n    }\n\n    /**\n     * Get the list of registered layout labels/names\n     *\n     * @return string[]\n     */\n    public function getLayouts(): array\n    {\n        return $this->layouts->map(fn ($layout) => $layout::getLabel())->toArray();\n    }\n\n    public function getDefaultLayoutName(): ?string\n    {\n        return $this->layouts->keys()->first();\n    }\n\n    /**\n     * @return Block[]\n     */\n    public function getPageBlocks(): array\n    {\n        return $this->pageBlocks->map(fn ($block) => $block::getBlockSchema())->toArray();\n    }\n\n    public function getPageBlocksRaw(): array\n    {\n        return $this->pageBlocks->toArray();\n    }\n\n    public function getSchemaSlot(string $name): array|Closure\n    {\n        return $this->schemaSlot[$name] ?? [];\n    }\n\n    public function getMeta(): array\n    {\n        return array_unique($this->meta);\n    }\n\n    public function getScripts(): array\n    {\n        return $this->scripts;\n    }\n\n    public function getStyles(): array\n    {\n        return $this->styles;\n    }\n\n    public function getFavicon(): ?string\n    {\n        return $this->favicon;\n    }\n\n    /** @return class-string<PageContract> */\n    public function getPageModel(): string\n    {\n        return config('filament-fabricator.page-model') ?? Page::class;\n    }\n\n    public function getRoutingPrefix(): ?string\n    {\n        $prefix = config('filament-fabricator.routing.prefix');\n\n        if (is_null($prefix)) {\n            return null;\n        }\n\n        $prefix = Str::start($prefix, '/');\n\n        if ($prefix === '/') {\n            return $prefix;\n        }\n\n        return rtrim($prefix, '/');\n    }\n\n    /**\n     * @return string[]\n     */\n    public function getPageUrls(): array\n    {\n        return $this->routesService->getAllUrls();\n    }\n\n    public function getPageUrlFromId(int|string $id, array $args = []): ?string\n    {\n        /** @var (PageContract&Model)|null $page */\n        $page = $this->getPageModel()::query()->find($id);\n\n        return $page?->getUrl($args);\n    }\n}\n"
  },
  {
    "path": "src/FilamentFabricatorPlugin.php",
    "content": "<?php\n\nnamespace Z3d0X\\FilamentFabricator;\n\nuse Closure;\nuse Filament\\Contracts\\Plugin;\nuse Filament\\Panel;\nuse Pboivin\\FilamentPeek\\FilamentPeekPlugin;\nuse Z3d0X\\FilamentFabricator\\Enums\\BlockPickerStyle;\n\nclass FilamentFabricatorPlugin implements Plugin\n{\n    public const ID = 'filament-fabricator';\n\n    protected BlockPickerStyle|Closure|null $blockPickerStyle = null;\n\n    public static function make(): static\n    {\n        return app(static::class);\n    }\n\n    public function getId(): string\n    {\n        return static::ID;\n    }\n\n    public function register(Panel $panel): void\n    {\n        $panel->resources(array_filter([\n            config('filament-fabricator.page-resource'),\n        ]));\n\n        if (! $panel->hasPlugin(FilamentPeekPlugin::ID)) {\n            // Automatically register FilamentPeekPlugin if it is not already registered\n            $panel->plugin(FilamentPeekPlugin::make());\n        }\n    }\n\n    public function boot(Panel $panel): void\n    {\n        //\n    }\n\n    public function blockPickerStyle(?BlockPickerStyle $style): static\n    {\n        $this->blockPickerStyle = $style;\n\n        return $this;\n    }\n\n    public function getBlockPickerStyle(): ?BlockPickerStyle\n    {\n        return $this->blockPickerStyle;\n    }\n\n    public static function get(): static\n    {\n        /** @var static $plugin */\n        $plugin = filament(app(static::class)->getId());\n\n        return $plugin;\n    }\n}\n"
  },
  {
    "path": "src/FilamentFabricatorServiceProvider.php",
    "content": "<?php\n\nnamespace Z3d0X\\FilamentFabricator;\n\nuse Illuminate\\Console\\Events\\CommandFinished;\nuse Illuminate\\Filesystem\\Filesystem;\nuse Illuminate\\Support\\Facades\\Event;\nuse Illuminate\\Support\\Facades\\Route;\nuse Illuminate\\Support\\Str;\nuse ReflectionClass;\nuse Spatie\\LaravelPackageTools\\Commands\\InstallCommand;\nuse Spatie\\LaravelPackageTools\\Package;\nuse Spatie\\LaravelPackageTools\\PackageServiceProvider;\nuse Symfony\\Component\\Finder\\SplFileInfo;\nuse Z3d0X\\FilamentFabricator\\Commands\\ClearRoutesCacheCommand;\nuse Z3d0X\\FilamentFabricator\\Commands\\MakeLayoutCommand;\nuse Z3d0X\\FilamentFabricator\\Commands\\MakePageBlockCommand;\nuse Z3d0X\\FilamentFabricator\\Facades\\FilamentFabricator;\nuse Z3d0X\\FilamentFabricator\\Layouts\\Layout;\nuse Z3d0X\\FilamentFabricator\\Listeners\\OptimizeWithLaravel;\nuse Z3d0X\\FilamentFabricator\\Observers\\PageRoutesObserver;\nuse Z3d0X\\FilamentFabricator\\PageBlocks\\PageBlock;\nuse Z3d0X\\FilamentFabricator\\Services\\PageRoutesService;\n\nclass FilamentFabricatorServiceProvider extends PackageServiceProvider\n{\n    public function configurePackage(Package $package): void\n    {\n        $package->name(FilamentFabricatorManager::ID)\n            ->hasConfigFile()\n            ->hasMigrations(\n                'create_pages_table',\n                'fix_slug_unique_constraint_on_pages_table',\n            )\n            ->hasRoute('web')\n            ->hasViews()\n            ->hasTranslations()\n            ->hasCommands($this->getCommands())\n            ->hasInstallCommand(function (InstallCommand $installCommand) {\n                $installCommand\n                    ->startWith(fn (InstallCommand $installCommand) => $installCommand->call('filament:upgrade'))\n                    ->publishConfigFile()\n                    ->publishMigrations()\n                    ->askToRunMigrations()\n                    ->askToStarRepoOnGitHub('z3d0x/filament-fabricator');\n            });\n    }\n\n    protected function getCommands(): array\n    {\n        $commands = [\n            MakeLayoutCommand::class,\n            MakePageBlockCommand::class,\n            ClearRoutesCacheCommand::class,\n        ];\n\n        $aliases = [];\n\n        foreach ($commands as $command) {\n            $class = 'Z3d0X\\\\FilamentFabricator\\\\Commands\\\\Aliases\\\\' . class_basename($command);\n\n            if (! class_exists($class)) {\n                continue;\n            }\n\n            $aliases[] = $class;\n        }\n\n        return array_merge($commands, $aliases);\n    }\n\n    public function packageRegistered(): void\n    {\n        parent::packageRegistered();\n\n        $this->app->singleton('filament-fabricator', function () {\n            return resolve(FilamentFabricatorManager::class);\n        });\n    }\n\n    public function bootingPackage(): void\n    {\n        if (! $this->app->runningInConsole() || $this->app->runningUnitTests()) {\n            Route::bind('filamentFabricatorPage', function ($value) {\n                /**\n                 * @var PageRoutesService $routesService\n                 */\n                $routesService = resolve(PageRoutesService::class);\n\n                return $routesService->findPageOrFail($value);\n            });\n\n            $this->registerComponentsFromDirectory(\n                Layout::class,\n                config('filament-fabricator.layouts.register'),\n                config('filament-fabricator.layouts.path'),\n                config('filament-fabricator.layouts.namespace')\n            );\n\n            $this->registerComponentsFromDirectory(\n                PageBlock::class,\n                config('filament-fabricator.page-blocks.register'),\n                config('filament-fabricator.page-blocks.path'),\n                config('filament-fabricator.page-blocks.namespace')\n            );\n        }\n    }\n\n    public function packageBooted()\n    {\n        parent::packageBooted();\n\n        FilamentFabricator::getPageModel()::observe(PageRoutesObserver::class);\n\n        if ((bool) config('filament-fabricator.hook-to-commands')) {\n            Event::listen(CommandFinished::class, OptimizeWithLaravel::class);\n        }\n    }\n\n    /**\n     * @template T of (class-string<Layout>|class-string<PageBlock>)\n     *\n     * @param  T  $baseClass\n     * @param  T[]  $register  - The components to register taken from the user's config file\n     */\n    protected function registerComponentsFromDirectory(string $baseClass, array $register, ?string $directory, ?string $namespace): void\n    {\n        if (blank($directory) || blank($namespace)) {\n            return;\n        }\n\n        $filesystem = app(Filesystem::class);\n\n        if ((! $filesystem->exists($directory)) && (! Str::of($directory)->contains('*'))) {\n            return;\n        }\n\n        $namespace = Str::of($namespace);\n\n        collect($filesystem->allFiles($directory))\n            ->lazy()\n            ->map(function (SplFileInfo $file) use ($namespace): string {\n                /**\n                 * @var ?string $variableNamespace\n                 */\n                $variableNamespace = $namespace->contains('*') ? str_ireplace(\n                    ['\\\\' . $namespace->before('*'), $namespace->after('*')],\n                    ['', ''],\n                    Str::of($file->getPath())\n                        ->after(base_path())\n                        ->replace(['/'], ['\\\\']),\n                ) : null;\n\n                return $namespace\n                    ->append('\\\\', $file->getRelativePathname())\n                    ->when($variableNamespace, fn ($namespace) => $namespace->replace('*', $variableNamespace))\n                    ->replace(['/', '.php'], ['\\\\', ''])\n                    ->toString();\n            })\n            ->concat($register)\n            ->filter(fn (string $class): bool => is_subclass_of($class, $baseClass) && (! (new ReflectionClass($class))->isAbstract()))\n            ->each(fn (string $class) => FilamentFabricator::registerComponent($class, $baseClass))\n            ->all();\n    }\n}\n"
  },
  {
    "path": "src/Forms/Components/PageBuilder.php",
    "content": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\Forms\\Components;\n\nuse Filament\\Forms\\Components\\Builder;\nuse Filament\\Forms\\Concerns\\InteractsWithForms;\nuse Filament\\Pages\\Concerns\\InteractsWithFormActions;\nuse Z3d0X\\FilamentFabricator\\Enums\\BlockPickerStyle;\nuse Z3d0X\\FilamentFabricator\\Facades\\FilamentFabricator;\nuse Z3d0X\\FilamentFabricator\\FilamentFabricatorPlugin;\n\nclass PageBuilder extends Builder\n{\n    use InteractsWithFormActions;\n    use InteractsWithForms;\n\n    protected string $view = 'filament-fabricator::components.forms.components.page-builder';\n\n    protected BlockPickerStyle $blockPickerStyle = BlockPickerStyle::Dropdown;\n\n    protected function setUp(): void\n    {\n        parent::setUp();\n\n        $this->blocks(FilamentFabricator::getPageBlocks());\n\n        $this->mutateDehydratedStateUsing(static function (?array $state): array {\n            if (! is_array($state)) {\n                return [];\n            }\n\n            $registerPageBlockNames = array_keys(FilamentFabricator::getPageBlocksRaw());\n\n            return collect($state)\n                ->filter(fn (array $block) => in_array($block['type'], $registerPageBlockNames, true))\n                ->values()\n                ->toArray();\n        });\n\n        $blockPickerStyle = FilamentFabricatorPlugin::get()->getBlockPickerStyle();\n\n        if (! is_null($blockPickerStyle)) {\n            $this->blockPickerStyle($blockPickerStyle);\n        }\n    }\n\n    public function blockPickerStyle(BlockPickerStyle $style): static\n    {\n        if ($style === BlockPickerStyle::Modal) {\n            $this->blockPickerColumns(3);\n        }\n\n        $this->blockPickerStyle = $style;\n\n        return $this;\n    }\n\n    public function getBlockPickerStyle(): BlockPickerStyle\n    {\n        return $this->blockPickerStyle;\n    }\n}\n"
  },
  {
    "path": "src/Helpers.php",
    "content": "<?php\n\nnamespace Z3d0X\\FilamentFabricator;\n\nuse Closure;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Model;\n\nabstract class Helpers\n{\n    /**\n     * Group an array of associative arrays by a given key\n     *\n     * @param  array[]  $arr\n     * @return array[]\n     */\n    public static function arrayRefsGroupBy(array &$arr, string $key): array\n    {\n        $ret = [];\n\n        foreach ($arr as &$item) {\n            $ret[$item[$key]][] = &$item;\n        }\n\n        return $ret;\n    }\n\n    /**\n     * Helper function to make it easier to preload related models\n     * via references in a block's data. Usually used inside `PageBlock::preloadRelatedData`\n     *\n     * @template TModel of Model\n     *\n     * @param array{\n     *     type: string,\n     *     data: array<string, mixed>,\n     * }[] $blocks\n     * @param  class-string<TModel>  $modelClass\n     * @param  null|Closure(Builder):Builder  $editQuery\n     */\n    public static function preloadRelatedModels(\n        array &$blocks,\n        string $property,\n        string $modelClass,\n        ?string $subProperty = null,\n        ?Closure $editQuery = null,\n        string $primaryKeyColumn = 'id',\n    ): void {\n        $editQuery ??= fn (Builder $builder) => $builder;\n        $targetsSubProperty = $subProperty !== null;\n\n        $ids = collect($blocks)\n            ->lazy()\n            ->map(function ($block) use ($targetsSubProperty, $property, $subProperty) {\n                $collection = collect($block['data'][$property]);\n\n                if ($targetsSubProperty) {\n                    $collection = $collection->pluck($subProperty);\n                }\n\n                return $collection->all();\n            })\n            ->flatten()\n            ->filter()\n            ->unique()\n            ->toArray();\n\n        $query = $modelClass::query()\n            ->whereIn($primaryKeyColumn, $ids);\n\n        $query = $editQuery($query);\n\n        /**\n         * @var TModel[] $models\n         */\n        $models = $query->get();\n\n        $models = collect($models)->groupBy($primaryKeyColumn);\n\n        foreach ($blocks as &$block) {\n            if ($targetsSubProperty) {\n                foreach ($block['data'][$property] as &$item) {\n                    $rawData = $item[$subProperty];\n                    $item[$subProperty] = is_array($rawData)\n                        ? array_map(fn ($key) => data_get($models, (string) $key)->first(), $rawData)\n                        : data_get($models, (string) $rawData)->first();\n                }\n            } else {\n                $rawData = $block['data'][$property];\n                $block['data'][$property] = is_array($rawData)\n                    ? array_map(fn ($key) => data_get($models, (string) $key)->first(), $rawData)\n                    : data_get($models, (string) $rawData)->first();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/Http/Controllers/PageController.php",
    "content": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\Http\\Controllers;\n\nuse Exception;\nuse Illuminate\\Support\\Facades\\Blade;\nuse Z3d0X\\FilamentFabricator\\Facades\\FilamentFabricator;\nuse Z3d0X\\FilamentFabricator\\Layouts\\Layout;\nuse Z3d0X\\FilamentFabricator\\Models\\Contracts\\Page;\nuse Z3d0X\\FilamentFabricator\\Services\\PageRoutesService;\n\nclass PageController\n{\n    public function __invoke(?Page $filamentFabricatorPage = null): string\n    {\n        // Handle root (home) page\n        if (blank($filamentFabricatorPage)) {\n            /**\n             * @var PageRoutesService $routesService\n             */\n            $routesService = resolve(PageRoutesService::class);\n\n            /** @var Page $filamentFabricatorPage */\n            $filamentFabricatorPage = $routesService->findPageOrFail('/');\n        }\n\n        /** @var ?class-string<Layout> $layout */\n        $layout = FilamentFabricator::getLayoutFromName($filamentFabricatorPage->layout);\n\n        if (! isset($layout)) {\n            throw new Exception(\"Filament Fabricator: Layout \\\"{$filamentFabricatorPage->layout}\\\" not found\");\n        }\n\n        /** @var string $component */\n        $component = $layout::getComponent();\n\n        return Blade::render(\n            <<<'BLADE'\n            <x-dynamic-component\n                :component=\"$component\"\n                :page=\"$page\"\n            />\n            BLADE,\n            ['component' => $component, 'page' => $filamentFabricatorPage]\n        );\n    }\n}\n"
  },
  {
    "path": "src/Layouts/Layout.php",
    "content": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\Layouts;\n\nuse Illuminate\\Support\\Str;\n\nabstract class Layout\n{\n    protected static ?string $component = null;\n\n    protected static ?string $name = null;\n\n    public static function getName(): string\n    {\n        return static::$name;\n    }\n\n    public static function getLabel(): string\n    {\n        return Str::headline(static::$name);\n    }\n\n    public static function getComponent(): string\n    {\n        return static::$component ?? ('filament-fabricator.layouts.' . static::getName());\n    }\n}\n"
  },
  {
    "path": "src/Listeners/OptimizeWithLaravel.php",
    "content": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\Listeners;\n\nuse Illuminate\\Console\\Command;\nuse Illuminate\\Console\\Events\\CommandFinished;\nuse Illuminate\\Support\\Facades\\Artisan;\nuse Z3d0X\\FilamentFabricator\\Commands\\ClearRoutesCacheCommand;\n\nclass OptimizeWithLaravel\n{\n    public const COMMANDS = [\n        'cache:clear',\n        'config:cache',\n        'config:clear',\n        'optimize',\n        'optimize:clear',\n        'route:clear',\n    ];\n\n    public const REFRESH_COMMANDS = [\n        'config:cache',\n        'optimize',\n    ];\n\n    public function handle(CommandFinished $event): void\n    {\n        if (! $this->shouldHandleEvent($event)) {\n            return;\n        }\n\n        if ($this->shouldRefresh($event)) {\n            $this->refresh();\n        } else {\n            $this->clear();\n        }\n    }\n\n    public function shouldHandleEvent(CommandFinished $event)\n    {\n        return $event->exitCode === Command::SUCCESS\n            && in_array($event->command, static::COMMANDS);\n    }\n\n    public function shouldRefresh(CommandFinished $event)\n    {\n        return in_array($event->command, static::REFRESH_COMMANDS);\n    }\n\n    public function refresh()\n    {\n        $this->callCommand([\n            '--refresh' => true,\n        ]);\n    }\n\n    public function clear()\n    {\n        $this->callCommand();\n    }\n\n    public function callCommand(array $params = [])\n    {\n        Artisan::call(ClearRoutesCacheCommand::class, $params);\n    }\n}\n"
  },
  {
    "path": "src/Models/Concerns/HandlesPageUrls.php",
    "content": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\Models\\Concerns;\n\nuse Illuminate\\Support\\Facades\\Cache;\nuse Illuminate\\Support\\Str;\nuse Z3d0X\\FilamentFabricator\\Facades\\FilamentFabricator;\nuse Z3d0X\\FilamentFabricator\\Models\\Contracts\\Page;\n\ntrait HandlesPageUrls\n{\n    /**\n     * Get the default arguments for URL generation\n     */\n    public function getDefaultUrlCacheArgs(): array\n    {\n        return [];\n    }\n\n    /**\n     * Get the cache key for the URL determined by this entity and the provided arguments\n     *\n     * @param  array<string, mixed>  $args\n     */\n    public function getUrlCacheKey(array $args = []): string\n    {\n        // $keyArgs = collect($this->getDefaultUrlArgs())->merge($args)->all();\n        $id = $this->id;\n\n        return \"filament-fabricator::page-url--$id\";\n    }\n\n    /**\n     * Get the URL determined by this entity and the provided arguments\n     *\n     * @param  array<string, mixed>  $args\n     */\n    public function getUrl(array $args = []): string\n    {\n        $cacheKey = $this->getUrlCacheKey($args);\n\n        // NOTE: Users must run the command that clears the routes cache if the routing prefix ever changes\n\n        return Cache::rememberForever($cacheKey, function () use ($args) {\n            /**\n             * @var ?Page $parent\n             */\n            $parent = $this->parent;\n\n            // If there's no parent page, then the \"parent\" URI is just the routing prefix.\n            $parentUri = is_null($parent) ? (FilamentFabricator::getRoutingPrefix() ?? '/') : $parent->getUrl($args);\n\n            // Every URI in cache has a leading slash, this ensures it's\n            // present even if the prefix doesn't have it set explicitly\n            $parentUri = Str::start($parentUri, '/');\n\n            // This page's part of the URL (i.e. its URI) is defined as the slug.\n            // For the same reasons as above, we need to add a leading slash.\n            $selfUri = $this->slug;\n            $selfUri = Str::start($selfUri, '/');\n\n            // If the parent URI is the root, then we have nothing to glue on.\n            // Therefore the page's URL is simply its URI.\n            // This avoids having two consecutive slashes.\n            if ($parentUri === '/') {\n                return $selfUri;\n            }\n\n            // Remove any trailing slash in the parent URI since\n            // every URIs we'll use has a leading slash.\n            // This avoids having two consecutive slashes.\n            $parentUri = rtrim($parentUri, '/');\n\n            return \"{$parentUri}{$selfUri}\";\n        });\n    }\n\n    /**\n     * Get all the available argument sets for the available cache keys\n     *\n     * @return array<string, mixed>[]\n     */\n    public function getAllUrlCacheKeysArgs(): array\n    {\n        // By default, the entire list of available URL cache keys\n        // is simply a list containing the default one since we can't\n        // magically infer all the possible state for the library user's customizations.\n        return [\n            $this->getDefaultUrlCacheArgs(),\n        ];\n    }\n\n    /**\n     * Get all the available URLs for this entity\n     *\n     * @return string[]\n     */\n    public function getAllUrls(): array\n    {\n        return array_map([$this, 'getUrl'], $this->getAllUrlCacheKeysArgs());\n    }\n\n    /**\n     * Get all the cache keys for the available URLs for this entity\n     *\n     * @return string[]\n     */\n    public function getAllUrlCacheKeys(): array\n    {\n        return array_map([$this, 'getUrlCacheKey'], $this->getAllUrlCacheKeysArgs());\n    }\n}\n"
  },
  {
    "path": "src/Models/Contracts/HasPageUrls.php",
    "content": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\Models\\Contracts;\n\ninterface HasPageUrls\n{\n    /**\n     * Get the default arguments for URL generation\n     *\n     * @invariant HasPageUrls#getDefaultUrlCacheArgs() must always return the same value, regardless of app/user configuration\n     */\n    public function getDefaultUrlCacheArgs(): array;\n\n    /**\n     * Get the cache key for the URL determined by this entity and the provided arguments\n     */\n    public function getUrlCacheKey(array $args = []): string;\n\n    /**\n     * Get the URL determined by this entity and the provided arguments\n     */\n    public function getUrl(array $args = []): string;\n\n    /**\n     * Get all the available argument sets for the available cache keys\n     *\n     * @return array[]\n     */\n    public function getAllUrlCacheKeysArgs(): array;\n\n    /**\n     * Get all the available URLs for this entity\n     *\n     * @return string[]\n     */\n    public function getAllUrls(): array;\n\n    /**\n     * Get all the cache keys for the available URLs for this entity\n     *\n     * @return string[]\n     */\n    public function getAllUrlCacheKeys(): array;\n}\n"
  },
  {
    "path": "src/Models/Contracts/Page.php",
    "content": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\Models\\Contracts;\n\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Database\\Eloquent\\Model;\nuse Illuminate\\Database\\Eloquent\\Relations\\BelongsTo;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasMany;\nuse Illuminate\\Support\\Carbon;\n\n/**\n * @phpstan-require-extends Model\n *\n * @property-read int|string $id\n * @property-read string $title\n * @property-read string $slug\n * @property-read string $layout\n * @property-read array $blocks\n * @property-read int|string|null $parent_id\n * @property-read static|null $parent\n * @property-read Collection<array-key, static&Model> $children\n * @property-read Collection<array-key, static&Model> $allChildren\n * @property-read Carbon $created_at\n * @property-read Carbon $updated_at\n */\ninterface Page extends HasPageUrls\n{\n    public function parent(): BelongsTo;\n\n    public function children(): HasMany;\n\n    public function allChildren(): HasMany;\n\n    /** @return Builder */\n    public static function query();\n}\n"
  },
  {
    "path": "src/Models/Page.php",
    "content": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\Models;\n\nuse Illuminate\\Database\\Eloquent\\Model;\nuse Illuminate\\Database\\Eloquent\\Relations\\BelongsTo;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasMany;\nuse Z3d0X\\FilamentFabricator\\Models\\Concerns\\HandlesPageUrls;\nuse Z3d0X\\FilamentFabricator\\Models\\Contracts\\Page as Contract;\n\nclass Page extends Model implements Contract\n{\n    use HandlesPageUrls;\n\n    public function __construct(array $attributes = [])\n    {\n        if (blank($this->table)) {\n            $this->setTable(config('filament-fabricator.table_name', 'pages'));\n        }\n\n        parent::__construct($attributes);\n    }\n\n    protected $guarded = [];\n\n    public function parent(): BelongsTo\n    {\n        return $this->belongsTo(static::class, 'parent_id');\n    }\n\n    public function children(): HasMany\n    {\n        return $this->hasMany(static::class, 'parent_id');\n    }\n\n    public function allChildren(): HasMany\n    {\n        return $this->children()\n            ->select('id', 'slug', 'title', 'parent_id')\n            ->with('allChildren:id,slug,title,parent_id');\n    }\n\n    protected function casts(): array\n    {\n        return array_merge(parent::casts(), [\n            'blocks' => 'array',\n            'parent_id' => 'integer',\n        ]);\n    }\n}\n"
  },
  {
    "path": "src/Observers/PageRoutesObserver.php",
    "content": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\Observers;\n\nuse Illuminate\\Database\\Eloquent\\Model;\nuse Z3d0X\\FilamentFabricator\\Models\\Contracts\\Page as PageContract;\nuse Z3d0X\\FilamentFabricator\\Services\\PageRoutesService;\n\nclass PageRoutesObserver\n{\n    public function __construct(\n        protected PageRoutesService $pageRoutesService\n    ) {}\n\n    /**\n     * Handle the Page \"created\" event.\n     */\n    public function created(PageContract&Model $page): void\n    {\n        // Creating the page simply requires setting the URLs in all caches.\n        // This will be done properly through the update procedure.\n        $this->pageRoutesService->updateUrlsOf($page);\n    }\n\n    /**\n     * Handle the Page \"updated\" event.\n     */\n    public function updated(PageContract&Model $page): void\n    {\n        // If the parent_id has changed, and if the relationship has already been loaded\n        // then after an update we might not read the right parent. That's why we always\n        // load it on update, this ensures we clear the old URLs properly (they were cached)\n        // and set the new ones properly (we have the right parent to do so).\n        if ($page->wasChanged('parent_id')) {\n            $page->load('parent');\n        }\n\n        $this->pageRoutesService->updateUrlsOf($page);\n    }\n\n    /**\n     * Handle the Page \"deleting\" event.\n     */\n    public function deleting(PageContract&Model $page): void\n    {\n        // We do the logic in `deleting` instead of `deleted` since we need access to the object\n        // both in memory and in database (e.g. to load relationship data).\n\n        // Before properly deleting it, remove its URLs from\n        // all the mappings and caches.\n        $this->pageRoutesService->removeUrlsOf($page);\n\n        // Doubly-linked list style deletion:\n        //      - Re-attache the given page children to the parent of the given page\n        //      - Promote the pages to a \"root page\" (i.e. page with no parent) if the given page had no parent\n\n        // Only load one level of children since they're the ones that will be re-attached\n        $page->load('children');\n        $children = $page->children;\n\n        foreach ($children as $childPage) {\n            /**\n             * @var Model|PageContract $childPage\n             */\n\n            // We use `?? null` followed by `?: null` to go around the cast to integer\n            // and make sure we have `null` instead of `0` when there's no parent.\n            $parentId = $page->parent_id ?? null;\n            $parentId = $parentId ?: null;\n\n            // Using update, instead of associate or dissociate, we trigger DB events (which we need)\n            $childPage->update([\n                'parent_id' => $parentId,\n            ]);\n        }\n    }\n\n    /**\n     * Handle the Page \"deleted\" event.\n     */\n    public function deleted(PageContract&Model $page): void\n    {\n        // Since `deleting` is called before `deleted`, and\n        // since everything is handled there, do nothing.\n    }\n\n    /**\n     * Handle the Page \"restored\" event.\n     */\n    public function restored(PageContract&Model $page): void\n    {\n        $this->pageRoutesService->updateUrlsOf($page);\n    }\n\n    /**\n     * Handle the Page \"force deleted\" event.\n     */\n    public function forceDeleting(PageContract&Model $page): void\n    {\n        // You always go through `deleting` before going through `forceDeleting`.\n        // Since everything is properly handled in `deleting`, do nothing here.\n    }\n\n    /**\n     * Handle the Page \"force deleted\" event.\n     */\n    public function forceDeleted(PageContract&Model $page): void\n    {\n        // You always go through `deleted` before going through `forceDeleted`.\n        // Since everything is properly handled in `deleted`, do nothing here.\n    }\n}\n"
  },
  {
    "path": "src/PageBlocks/PageBlock.php",
    "content": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\PageBlocks;\n\nuse Filament\\Forms\\Components\\Builder\\Block;\nuse Z3d0X\\FilamentFabricator\\Models\\Contracts\\Page;\n\nabstract class PageBlock\n{\n    protected static string $name = '';\n\n    protected static ?string $component = null;\n\n    /**\n     * Create a minimally pre-configured {@link Block} instance for this {@link PageBlock}\n     */\n    protected static function createNewBlock(): Block\n    {\n        return Block::make(static::getName());\n    }\n\n    /**\n     * Get a fully configured {@link Block} instance for this {@link PageBlock}\n     */\n    public static function getBlockSchema(): Block\n    {\n        return static::defineBlock(\n            static::createNewBlock(),\n        );\n    }\n\n    /**\n     * Configure the given {@link Block} instance for this {@link PageBlock}\n     *\n     * @param  $block  - The block instance to configure\n     */\n    abstract public static function defineBlock(Block $block): Block;\n\n    public static function getComponent(): string\n    {\n        return static::$component ?? ('filament-fabricator.page-blocks.' . static::getName());\n    }\n\n    /**\n     * The unique identifier name used to refer to this {@link PageBlock} type\n     */\n    public static function getName(): string\n    {\n        return static::$name;\n    }\n\n    public static function mutateData(array $data): array\n    {\n        return $data;\n    }\n\n    /**\n     * Hook used to mass-preload related data to reduce the number of DB queries.\n     * For instance, to load model objects/data from their IDs\n     *\n     * @param  (array{\n     *     type: string,\n     *     data: array<string, mixed>,\n     * })[]  $blocks  - The array of blocks' data for the given page and the given block type\n     */\n    public static function preloadRelatedData(Page $page, array &$blocks): void {}\n}\n"
  },
  {
    "path": "src/Resources/PageResource/Pages/Concerns/HasPreviewModal.php",
    "content": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\Resources\\PageResource\\Pages\\Concerns;\n\nuse Exception;\nuse Pboivin\\FilamentPeek\\Pages\\Concerns\\HasPreviewModal as BaseHasPreviewModal;\nuse Z3d0X\\FilamentFabricator\\Facades\\FilamentFabricator;\n\ntrait HasPreviewModal\n{\n    use BaseHasPreviewModal;\n\n    protected function getPreviewModalView(): ?string\n    {\n        return 'filament-fabricator::preview';\n    }\n\n    protected function getPreviewModalDataRecordKey(): ?string\n    {\n        return 'page';\n    }\n\n    protected function mutatePreviewModalData(array $data): array\n    {\n        $layoutName = $this->data['layout'] ?? null;\n        if (empty($layoutName)) {\n            return [];\n        }\n\n        $layout = FilamentFabricator::getLayoutFromName($layoutName);\n\n        if (empty($layout)) {\n            throw new Exception(\"Filament Fabricator: Layout \\\"{$layoutName}\\\" not found\");\n        }\n\n        /** @var string $component */\n        $component = $layout::getComponent();\n\n        $data['component'] = $component;\n\n        return $data;\n    }\n}\n"
  },
  {
    "path": "src/Resources/PageResource/Pages/CreatePage.php",
    "content": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\Resources\\PageResource\\Pages;\n\nuse Filament\\Resources\\Pages\\CreateRecord;\nuse Pboivin\\FilamentPeek\\Pages\\Actions\\PreviewAction;\nuse Z3d0X\\FilamentFabricator\\Resources\\PageResource;\nuse Z3d0X\\FilamentFabricator\\Resources\\PageResource\\Pages\\Concerns\\HasPreviewModal;\n\nclass CreatePage extends CreateRecord\n{\n    use HasPreviewModal;\n\n    protected static string $resource = PageResource::class;\n\n    public static function getResource(): string\n    {\n        return config('filament-fabricator.page-resource') ?? static::$resource;\n    }\n\n    protected function getActions(): array\n    {\n        return [\n            PreviewAction::make(),\n        ];\n    }\n}\n"
  },
  {
    "path": "src/Resources/PageResource/Pages/EditPage.php",
    "content": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\Resources\\PageResource\\Pages;\n\nuse Filament\\Actions\\Action;\nuse Filament\\Actions\\DeleteAction;\nuse Filament\\Actions\\ViewAction;\nuse Filament\\Resources\\Pages\\EditRecord;\nuse Illuminate\\Database\\Eloquent\\Model;\nuse Pboivin\\FilamentPeek\\Pages\\Actions\\PreviewAction;\nuse Z3d0X\\FilamentFabricator\\Facades\\FilamentFabricator;\nuse Z3d0X\\FilamentFabricator\\Models\\Contracts\\Page as PageContract;\nuse Z3d0X\\FilamentFabricator\\Resources\\PageResource;\nuse Z3d0X\\FilamentFabricator\\Resources\\PageResource\\Pages\\Concerns\\HasPreviewModal;\n\nclass EditPage extends EditRecord\n{\n    use HasPreviewModal;\n\n    protected static string $resource = PageResource::class;\n\n    public static function getResource(): string\n    {\n        return config('filament-fabricator.page-resource') ?? static::$resource;\n    }\n\n    protected function getActions(): array\n    {\n        return [\n            PreviewAction::make(),\n\n            ViewAction::make()\n                ->visible(config('filament-fabricator.enable-view-page')),\n\n            DeleteAction::make(),\n\n            Action::make('visit')\n                ->label(__('filament-fabricator::page-resource.actions.visit'))\n                ->url(function () {\n                    /** @var PageContract&Model $page */\n                    $page = $this->getRecord();\n\n                    return FilamentFabricator::getPageUrlFromId($page->id);\n                })\n                ->icon('heroicon-o-arrow-top-right-on-square')\n                ->openUrlInNewTab()\n                ->color('success')\n                ->visible(config('filament-fabricator.routing.enabled')),\n\n            Action::make('save')\n                ->action('save')\n                ->label(__('filament-fabricator::page-resource.actions.save')),\n        ];\n    }\n}\n"
  },
  {
    "path": "src/Resources/PageResource/Pages/ListPages.php",
    "content": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\Resources\\PageResource\\Pages;\n\nuse Filament\\Actions\\CreateAction;\nuse Filament\\Resources\\Pages\\ListRecords;\nuse Z3d0X\\FilamentFabricator\\Resources\\PageResource;\n\nclass ListPages extends ListRecords\n{\n    protected static string $resource = PageResource::class;\n\n    public static function getResource(): string\n    {\n        return config('filament-fabricator.page-resource') ?? static::$resource;\n    }\n\n    protected function getActions(): array\n    {\n        return [\n            CreateAction::make(),\n        ];\n    }\n}\n"
  },
  {
    "path": "src/Resources/PageResource/Pages/ViewPage.php",
    "content": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\Resources\\PageResource\\Pages;\n\nuse Filament\\Actions\\Action;\nuse Filament\\Actions\\EditAction;\nuse Filament\\Resources\\Pages\\ViewRecord;\nuse Illuminate\\Database\\Eloquent\\Model;\nuse Z3d0X\\FilamentFabricator\\Facades\\FilamentFabricator;\nuse Z3d0X\\FilamentFabricator\\Models\\Contracts\\Page as PageContract;\nuse Z3d0X\\FilamentFabricator\\Resources\\PageResource;\n\nclass ViewPage extends ViewRecord\n{\n    protected static string $resource = PageResource::class;\n\n    public static function getResource(): string\n    {\n        return config('filament-fabricator.page-resource') ?? static::$resource;\n    }\n\n    protected function getActions(): array\n    {\n        return [\n            EditAction::make(),\n\n            Action::make('visit')\n                ->label(__('filament-fabricator::page-resource.actions.visit'))\n                ->url(function () {\n                    /** @var PageContract&Model $page */\n                    $page = $this->getRecord();\n\n                    return FilamentFabricator::getPageUrlFromId($page->id);\n                })\n                ->icon('heroicon-o-arrow-top-right-on-square')\n                ->openUrlInNewTab()\n                ->color('success')\n                ->visible(config('filament-fabricator.routing.enabled')),\n        ];\n    }\n}\n"
  },
  {
    "path": "src/Resources/PageResource.php",
    "content": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\Resources;\n\nuse Closure;\nuse Filament\\Actions\\Action;\nuse Filament\\Actions\\EditAction;\nuse Filament\\Actions\\ViewAction;\nuse Filament\\Forms\\Components\\Hidden;\nuse Filament\\Forms\\Components\\Select;\nuse Filament\\Forms\\Components\\TextInput;\nuse Filament\\Infolists\\Components\\TextEntry;\nuse Filament\\Resources\\Resource;\nuse Filament\\Schemas\\Components\\Group;\nuse Filament\\Schemas\\Components\\Section;\nuse Filament\\Schemas\\Components\\Utilities\\Get;\nuse Filament\\Schemas\\Components\\Utilities\\Set;\nuse Filament\\Schemas\\Schema;\nuse Filament\\Tables\\Columns\\TextColumn;\nuse Filament\\Tables\\Filters\\SelectFilter;\nuse Filament\\Tables\\Table;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Support\\Str;\nuse Illuminate\\Validation\\Rules\\Unique;\nuse Z3d0X\\FilamentFabricator\\Facades\\FilamentFabricator;\nuse Z3d0X\\FilamentFabricator\\Forms\\Components\\PageBuilder;\nuse Z3d0X\\FilamentFabricator\\Models\\Contracts\\Page as PageContract;\nuse Z3d0X\\FilamentFabricator\\Resources\\PageResource\\Pages\\CreatePage;\nuse Z3d0X\\FilamentFabricator\\Resources\\PageResource\\Pages\\EditPage;\nuse Z3d0X\\FilamentFabricator\\Resources\\PageResource\\Pages\\ListPages;\nuse Z3d0X\\FilamentFabricator\\Resources\\PageResource\\Pages\\ViewPage;\nuse Z3d0X\\FilamentFabricator\\View\\ResourceSchemaSlot;\n\nclass PageResource extends Resource\n{\n    protected static string|\\BackedEnum|null $navigationIcon = 'heroicon-o-document-text';\n\n    protected static ?string $recordTitleAttribute = 'title';\n\n    public static function getModel(): string\n    {\n        return FilamentFabricator::getPageModel();\n    }\n\n    public static function form(Schema $schema): Schema\n    {\n        return $schema\n            ->columns(3)\n            ->components([\n                Group::make()\n                    ->schema([\n                        Group::make()->schema(FilamentFabricator::getSchemaSlot(ResourceSchemaSlot::BLOCKS_BEFORE)),\n\n                        PageBuilder::make('blocks')\n                            ->label(__('filament-fabricator::page-resource.labels.blocks')),\n\n                        Group::make()->schema(FilamentFabricator::getSchemaSlot(ResourceSchemaSlot::BLOCKS_AFTER)),\n                    ])\n                    ->columnSpan(2),\n\n                Group::make()\n                    ->columnSpan(1)\n                    ->schema([\n                        Group::make()->schema(FilamentFabricator::getSchemaSlot(ResourceSchemaSlot::SIDEBAR_BEFORE)),\n\n                        Section::make()\n                            ->schema([\n                                TextEntry::make('page_url')\n                                    ->label(__('filament-fabricator::page-resource.labels.url'))\n                                    ->visible(fn (?PageContract $record) => config('filament-fabricator.routing.enabled') && filled($record))\n                                    ->state(fn (?PageContract $record) => FilamentFabricator::getPageUrlFromId($record?->id)),\n\n                                TextInput::make('title')\n                                    ->label(__('filament-fabricator::page-resource.labels.title'))\n                                    ->afterStateUpdated(function (Get $get, Set $set, ?string $state, ?PageContract $record) {\n                                        if (! $get('is_slug_changed_manually') && filled($state) && blank($record)) {\n                                            $set('slug', Str::slug($state, language: config('app.locale', 'en')));\n                                        }\n                                    })\n                                    ->debounce('500ms')\n                                    ->required(),\n\n                                Hidden::make('is_slug_changed_manually')\n                                    ->default(false)\n                                    ->dehydrated(false),\n\n                                TextInput::make('slug')\n                                    ->label(__('filament-fabricator::page-resource.labels.slug'))\n                                    ->unique(ignoreRecord: true, modifyRuleUsing: fn (Unique $rule, Get $get) => $rule->where('parent_id', $get('parent_id')))\n                                    ->afterStateUpdated(function (Set $set) {\n                                        $set('is_slug_changed_manually', true);\n                                    })\n                                    ->rule(function ($state) {\n                                        return function (string $attribute, $value, Closure $fail) use ($state) {\n                                            if ($state !== '/' && (Str::startsWith($value, '/') || Str::endsWith($value, '/'))) {\n                                                $fail(__('filament-fabricator::page-resource.errors.slug_starts_or_ends_with_slash'));\n                                            }\n                                        };\n                                    })\n                                    ->required(),\n\n                                Select::make('layout')\n                                    ->label(__('filament-fabricator::page-resource.labels.layout'))\n                                    ->options(FilamentFabricator::getLayouts())\n                                    ->default(fn () => FilamentFabricator::getDefaultLayoutName())\n                                    ->live()\n                                    ->required(),\n\n                                Select::make('parent_id')\n                                    ->label(__('filament-fabricator::page-resource.labels.parent'))\n                                    ->searchable()\n                                    ->preload()\n                                    ->reactive()\n                                    ->suffixAction(\n                                        fn ($get, $context) => Action::make($context . '-parent')\n                                            ->icon('heroicon-o-arrow-top-right-on-square')\n                                            ->url(fn () => PageResource::getUrl($context, ['record' => $get('parent_id')]))\n                                            ->openUrlInNewTab()\n                                            ->visible(fn () => filled($get('parent_id')))\n                                    )\n                                    ->relationship(\n                                        'parent',\n                                        'title',\n                                        function (Builder $query, ?PageContract $record) {\n                                            if (filled($record)) {\n                                                $query->where('id', '!=', $record->id);\n                                            }\n                                        }\n                                    ),\n                            ]),\n\n                        Group::make()->schema(FilamentFabricator::getSchemaSlot(ResourceSchemaSlot::SIDEBAR_AFTER)),\n                    ]),\n\n            ]);\n    }\n\n    public static function table(Table $table): Table\n    {\n        return $table\n            ->columns([\n                TextColumn::make('title')\n                    ->label(__('filament-fabricator::page-resource.labels.title'))\n                    ->searchable()\n                    ->sortable(),\n\n                TextColumn::make('url')\n                    ->label(__('filament-fabricator::page-resource.labels.url'))\n                    ->toggleable()\n                    ->getStateUsing(fn (?PageContract $record) => FilamentFabricator::getPageUrlFromId($record->id) ?: null)\n                    ->url(fn (?PageContract $record) => FilamentFabricator::getPageUrlFromId($record->id) ?: null, true)\n                    ->visible(config('filament-fabricator.routing.enabled')),\n\n                TextColumn::make('layout')\n                    ->label(__('filament-fabricator::page-resource.labels.layout'))\n                    ->badge()\n                    ->toggleable()\n                    ->sortable(),\n\n                TextColumn::make('parent.title')\n                    ->label(__('filament-fabricator::page-resource.labels.parent'))\n                    ->toggleable(isToggledHiddenByDefault: true)\n                    ->formatStateUsing(fn ($state) => $state ?? '-')\n                    ->url(fn (?PageContract $record) => filled($record->parent_id) ? PageResource::getUrl('edit', ['record' => $record->parent_id]) : null),\n            ])\n            ->filters([\n                SelectFilter::make('layout')\n                    ->label(__('filament-fabricator::page-resource.labels.layout'))\n                    ->options(FilamentFabricator::getLayouts()),\n            ])\n            ->recordActions([\n                ViewAction::make()\n                    ->visible(config('filament-fabricator.enable-view-page')),\n                EditAction::make(),\n                Action::make('visit')\n                    ->label(__('filament-fabricator::page-resource.actions.visit'))\n                    ->url(fn (?PageContract $record) => FilamentFabricator::getPageUrlFromId($record->id) ?: null)\n                    ->icon('heroicon-o-arrow-top-right-on-square')\n                    ->openUrlInNewTab()\n                    ->color('success')\n                    ->visible(config('filament-fabricator.routing.enabled')),\n            ])\n            ->toolbarActions([]);\n    }\n\n    public static function getModelLabel(): string\n    {\n        return __('filament-fabricator::page-resource.labels.page');\n    }\n\n    public static function getPluralModelLabel(): string\n    {\n        return __('filament-fabricator::page-resource.labels.pages');\n    }\n\n    public static function getPages(): array\n    {\n        return array_filter([\n            'index' => ListPages::route('/'),\n            'create' => CreatePage::route('/create'),\n            'view' => config('filament-fabricator.enable-view-page') ? ViewPage::route('/{record}') : null,\n            'edit' => EditPage::route('/{record}/edit'),\n        ]);\n    }\n}\n"
  },
  {
    "path": "src/Services/PageRoutesService.php",
    "content": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\Services;\n\nuse Illuminate\\Database\\Eloquent\\Model;\nuse Illuminate\\Support\\Facades\\Cache;\nuse Illuminate\\Support\\Str;\nuse Z3d0X\\FilamentFabricator\\Facades\\FilamentFabricator;\nuse Z3d0X\\FilamentFabricator\\Models\\Contracts\\Page;\n\n// The service is a coordinator between all route caches\n// There are three layers of caches that have to be in sync:\n// - PageRoutesService::URI_TO_ID_MAPPING that maps from URI to ID (many-to-one)\n// - PageRoutesService::ID_TO_URI_MAPPING that maps from ID to URIS (one-to-many)\n// - Page::getUrl (and thus Page::getAllUrl)\n//\n// Syncing and consistency should be handle via the service.\n// As such, it's responsible for maintaining internal consistency\n// and hiding/encapsulating implementation details.\n//\n// It relies on the extension points defined by Z3d0X\\FilamentFabricator\\Models\\Contracts\\HasPageUrls\nclass PageRoutesService\n{\n    protected const URI_TO_ID_MAPPING = 'filament-fabricator::PageRoutesService::uri-to-id';\n\n    protected const ID_TO_URI_MAPPING = 'filament-fabricator::PageRoutesService::id-to-uri';\n\n    /**\n     * Get the ID of the Page model to which the given URI is associated, -1 if non matches\n     *\n     * @return int|string The page's ID, or -1 on failure\n     */\n    public function getPageIdFromUri(string $uri): int|string\n    {\n        // Query the (URI -> ID) mapping based on the user provided URI.\n        // The mapping expect a URI that starts with a /\n        // thus we \"normalize\" the URI by ensuring it starts with one.\n        // Not doing so would result in a false negative.\n        $mapping = $this->getUriToIdMapping();\n        $uri = Str::start($uri, '/');\n\n        return $mapping[$uri] ?? -1;\n    }\n\n    /**\n     * Get an instance of your Page model from a URI, or NULL if none matches\n     *\n     * @return null|(Page&Model)\n     */\n    public function getPageFromUri(string $uri): ?Page\n    {\n        $id = $this->getPageIdFromUri($uri);\n\n        // We know the getPageIdFromUri uses -1 as a \"sentinel\" value\n        // for when the page is not found, so return null in those cases\n        if ($id === -1) {\n            return null;\n        }\n\n        /** @var null|(Page&Model) */\n        return FilamentFabricator::getPageModel()::find($id);\n    }\n\n    /**\n     * Update the cached URLs for the given page (as well as all its descendants')\n     */\n    public function updateUrlsOf(Page&Model $page): void\n    {\n        // We mutate the mapping without events to ensure we don't have \"concurrent\"\n        // modifications of the same mapping. This allows us to skip the use of locks\n        // in an environment where only unrelated pages can be modified by separate\n        // users at the same time, which is a responsibility the library users\n        // should enforce themselves.\n        FilamentFabricator::getPageModel()::withoutEvents(function () use ($page) {\n            $mapping = $this->getUriToIdMapping();\n            $this->updateUrlsAndDescendantsOf($page, $mapping);\n            $this->replaceUriToIdMapping($mapping);\n        });\n    }\n\n    /**\n     * Remove the cached URLs for the given page\n     */\n    public function removeUrlsOf(Page $page): void\n    {\n        // First remove the entries from the (ID -> URI) mapping\n        $idToUrlsMapping = $this->getIdToUrisMapping();\n        // NOTE: We should never be here without the page being\n        // in the URLs mapping. Remove this once the\n        // lifecycle issue has been dealt with\n        $urls = $idToUrlsMapping[$page->id] ?? [];\n        $idToUrlsMapping[$page->id] = null;\n        unset($idToUrlsMapping[$page->id]);\n        $this->replaceIdToUriMapping($idToUrlsMapping);\n\n        // Then remove the entries from the (URI -> ID) mapping\n        $uriToIdMapping = $this->getUriToIdMapping();\n        foreach ($urls as $uri) {\n            unset($uriToIdMapping[$uri]);\n        }\n        $this->replaceUriToIdMapping($uriToIdMapping);\n\n        // Finally, clear the page's local caches of its own URL.\n        // This means that Page::getAllUrls() and such will now compute\n        // fresh values.\n        $this->forgetPageLocalCache($page);\n    }\n\n    /**\n     * Get an instance of your Page model from a URI, or throw if there is none\n     */\n    public function findPageOrFail(string $uri): Page&Model\n    {\n        $id = $this->getPageIdFromUri($uri);\n\n        // If the page doesn't exists, we know getPageIdFromUri\n        // will return -1. Thus, findOrFail will fail as expected.\n        /** @var Page&Model */\n        return FilamentFabricator::getPageModel()::findOrFail($id);\n    }\n\n    /**\n     * Get the list of all the registered URLs\n     *\n     * @return string[]\n     */\n    public function getAllUrls(): array\n    {\n        $uriToIdMapping = $this->getUriToIdMapping();\n\n        // $uriToIdMapping is an associative array that maps URIs to IDs.\n        // Thus, the list of URLs is the keys of that array.\n        // Since PHP handles keys very weirdly when using array_keys,\n        // we simply get its array_values to have a truly regular array\n        // instead of an associative array where the keys are all numbers\n        // but possibly non-sorted.\n\n        /* @phpstan-ignore arrayValues.list (This ensures the keys in the array are numerical and sorted) */\n        return array_values(array_keys($uriToIdMapping));\n    }\n\n    /**\n     * Get the URI -> ID mapping\n     *\n     * @return array<string, int|string>\n     */\n    protected function getUriToIdMapping(): array\n    {\n        // The mapping will be cached for most requests.\n        // The very first person hitting the cache when it's not readily available\n        // will sadly have to recompute the whole thing.\n        return Cache::rememberForever(static::URI_TO_ID_MAPPING, function () {\n            // Even though we technically have 2 separate caches\n            // we want them to not really be independent.\n            // Here we ensure our initial state depends on the other\n            // cache's initial state.\n            $idsToUrisMapping = $this->getIdToUrisMapping();\n            $uriToIdMapping = [];\n\n            // We simply \"reverse\" the one-to-many mapping to a many-to-one\n            foreach ($idsToUrisMapping as $id => $uris) {\n                foreach ($uris as $uri) {\n                    $uriToIdMapping[$uri] = $id;\n                }\n            }\n\n            return $uriToIdMapping;\n        });\n    }\n\n    /**\n     * Get the ID -> URI[] mapping\n     *\n     * @return array<int|string, string[]>\n     */\n    protected function getIdToUrisMapping(): array\n    {\n        // The mapping will be cached for most requests.\n        // The very first person hitting the cache when it's not readily available\n        // will sadly have to recompute the whole thing.\n        // This could be a critical section and bottleneck depending on the use cases.\n        // Any optimization to this can greatly improve the entire package's performances\n        // in one fell swoop.\n        return Cache::rememberForever(static::ID_TO_URI_MAPPING, function () {\n            $mapping = FilamentFabricator::getPageModel()::query()\n                ->with('parent')\n                ->get()\n                ->toBase()\n                ->mapWithKeys(function (Page $page): array { // @phpstan-ignore-line\n                    // Note that this also has the benefits of computing\n                    // the page's local caches.\n                    return [$page->id => $page->getAllUrls()];\n                })\n                ->all();\n\n            return $mapping;\n        });\n    }\n\n    /**\n     * Get the cached URIs for the given page\n     *\n     * @return string[]\n     */\n    protected function getUrisForPage(Page $page): array\n    {\n        $mapping = $this->getIdToUrisMapping();\n\n        return $mapping[$page->id] ?? [];\n    }\n\n    /**\n     * Update routine for the given page\n     *\n     * @param  array  $uriToIdMapping  - The URI -> ID mapping (as a reference, to be modified in-place)\n     * @return void\n     */\n    protected function updateUrlsAndDescendantsOf(Page&Model $page, array &$uriToIdMapping)\n    {\n        // First ensure consistency by removing any trace of the old URLs\n        // for the given page. Whether local or in the URI to ID mapping.\n        $this->unsetOldUrlsOf($page, $uriToIdMapping);\n\n        // These URLs will always be fresh since we unset the old ones just above\n        $urls = $page->getAllUrls();\n\n        foreach ($urls as $uri) {\n            $id = $uriToIdMapping[$uri] ?? -1;\n\n            // If while iterating the fresh URLs we encounter one\n            // that is already mapped to the right page ID\n            // then there's nothing to do for this URL\n            // and thus continue onward with the next one.\n            if ($id === $page->id) {\n                continue;\n            }\n\n            // Otherwise, we have a URI that already exists\n            // and is mapped to the wrong ID, or it wasn't\n            // in the mapping yet. In both cases we just need\n            // to add it to the mapping at the correct spot.\n            $uriToIdMapping[$uri] = $page->id;\n        }\n\n        // Since we're recursing down the tree, we preload the relationships\n        // once, and traverse down the tree. This helps with performances.\n        // TODO: Make it work with loadMissing instead of load to reduce the number of useless DB queries\n        $page->load(['allChildren']);\n        foreach ($page->allChildren as $childPage) {\n            /**\n             * @var Page&Model $childPage\n             */\n\n            // A change in a parent page will always result\n            // in a change to its descendant. As such,\n            // we need to recompute everything that's\n            // a descendant of this page.\n            $this->updateUrlsAndDescendantsOf($childPage, $uriToIdMapping);\n        }\n    }\n\n    /**\n     * Remove old URLs of the given page from the cached mappings\n     *\n     * @param  array  $uriToIdMapping  - The URI -> ID mapping (as a reference, to be modified in-place)\n     * @return void\n     */\n    protected function unsetOldUrlsOf(Page $page, array &$uriToIdMapping)\n    {\n        // When we're hitting this path, caches haven't been invalidated yet.\n        // Thus, we don't need to query the mappings to get the old URLs.\n        $oldUrlSet = collect($page->getAllUrls())->lazy()->sort()->all();\n\n        // Once we're done collecting the previous URLs, and since we want\n        // to unset ALL old URLs for this given page, we might as well\n        // forget its local caches here.\n        $this->forgetPageLocalCache($page);\n\n        // Since we just forgot the page's local caches, this doesn't\n        // return the old set of URLs, but instead computes and caches\n        // the new URLs based on the page's currently loaded data.\n        $newUrlSet = collect($page->getAllUrls())->lazy()->sort()->all();\n\n        // The old URLs are those that are present in the $oldUrlSet\n        // but are not present in $newUrlSet. Hence the use of array_diff\n        // whose role is to return exactly that. Also note we sorted the arrays\n        // in order to make sure the diff algorithm has every chances to be\n        // optimal in performance.\n        $oldUrls = array_diff($oldUrlSet, $newUrlSet);\n\n        // Simply go through the list of old URLs and remove them from the mapping.\n        // This is one of the reasons we pass it by reference.\n        foreach ($oldUrls as $oldUrl) {\n            unset($uriToIdMapping[$oldUrl]);\n        }\n\n        $idToUrlsMapping = $this->getIdToUrisMapping();\n        $idToUrlsMapping[$page->id] = $newUrlSet;\n        $this->replaceIdToUriMapping($idToUrlsMapping);\n    }\n\n    /**\n     * Forget all URL caches tied to the page (cf. Page::getAllUrlCacheKeys)\n     */\n    protected function forgetPageLocalCache(Page $page)\n    {\n        // The page local caches are simply those behind the\n        // URL cache keys. Compute the keys, forget the caches.\n        $cacheKeys = $page->getAllUrlCacheKeys();\n        foreach ($cacheKeys as $cacheKey) {\n            Cache::forget($cacheKey);\n        }\n    }\n\n    /**\n     * Completely replaced the cached ID -> URI[] mapping\n     *\n     * @param  array<int|string, string[]>  $idToUriMapping\n     */\n    protected function replaceIdToUriMapping(array $idToUriMapping): void\n    {\n        if (empty($idToUriMapping)) {\n            // If the new mapping is empty, that means we've been\n            // cleaning the last entries. Therefore we must\n            // forget the cached data to properly clear it out\n            // and also allow proper cache invalidation\n            Cache::forget(static::ID_TO_URI_MAPPING);\n        } else {\n            // Replace the ID -> URI[] mapping with the given one.\n            // This is done \"atomically\" with regards to the cache.\n            // Note that concurrent read and writes can result in lost updates.\n            // And thus in an invalid state.\n            Cache::forever(static::ID_TO_URI_MAPPING, $idToUriMapping);\n        }\n    }\n\n    /**\n     * Completely replace the cached URI -> ID mapping\n     *\n     * @param  array<string, int|string>  $uriToIdMapping\n     */\n    protected function replaceUriToIdMapping(array $uriToIdMapping): void\n    {\n        if (empty($uriToIdMapping)) {\n            // If the new mapping is empty, that means we've been\n            // cleaning the last entries. Therefore we must\n            // forget the cached data to properly clear it out\n            // and also allow proper cache invalidation\n            Cache::forget(static::URI_TO_ID_MAPPING);\n        } else {\n            // Replace the URI -> ID mapping with the given one.\n            // This is done \"atomically\" with regards to the cache.\n            // Note that concurrent read and writes can result in lost updates.\n            // And thus in an invalid state.\n            Cache::forever(static::URI_TO_ID_MAPPING, $uriToIdMapping);\n        }\n    }\n}\n"
  },
  {
    "path": "src/View/LayoutRenderHook.php",
    "content": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\View;\n\nclass LayoutRenderHook\n{\n    public const HEAD_START = 'filament-fabricator::head.start';\n\n    public const HEAD_END = 'filament-fabricator::head.end';\n\n    public const BODY_START = 'filament-fabricator::body.start';\n\n    public const SCRIPTS_START = 'filament-fabricator::scripts.start';\n\n    public const SCRIPTS_END = 'filament-fabricator::scripts.end';\n\n    public const BODY_END = 'filament-fabricator::body.end';\n}\n"
  },
  {
    "path": "src/View/ResourceSchemaSlot.php",
    "content": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\View;\n\nclass ResourceSchemaSlot\n{\n    public const BLOCKS_BEFORE = 'blocks.before';\n\n    public const BLOCKS_AFTER = 'blocks.after';\n\n    public const SIDEBAR_BEFORE = 'sidebar.before';\n\n    public const SIDEBAR_AFTER = 'sidebar.after';\n}\n"
  },
  {
    "path": "stubs/Layout.stub",
    "content": "<?php\n\nnamespace {{ namespace }};\n\nuse Z3d0X\\FilamentFabricator\\Layouts\\Layout;\n\nclass {{ class }} extends Layout\n{\n    protected static ?string $name = '{{ shortName }}';\n}"
  },
  {
    "path": "stubs/LayoutView.stub",
    "content": "@props(['page'])\n<x-filament-fabricator::layouts.base :page=\"$page\" :title=\"$page->title\">\n    {{-- Header Here --}}\n\n    <x-filament-fabricator::page-blocks :blocks=\"$page->blocks\" />\n\n     {{-- Footer Here --}}\n</x-filament-fabricator::layouts.base>\n"
  },
  {
    "path": "stubs/PageBlock.stub",
    "content": "<?php\n\nnamespace {{ namespace }};\n\nuse Filament\\Forms\\Components\\Builder\\Block;\nuse Z3d0X\\FilamentFabricator\\PageBlocks\\PageBlock;\n\nclass {{ class }} extends PageBlock\n{\n    protected static string $name = '{{ shortName }}';\n\n    public static function defineBlock(Block $block): Block\n    {\n        return $block\n            ->schema([\n                //\n            ]);\n    }\n\n    public static function mutateData(array $data): array\n    {\n        return $data;\n    }\n}\n"
  },
  {
    "path": "stubs/PageBlockView.stub",
    "content": "@aware(['page'])\n<div class=\"px-4 py-4 md:py-8\">\n    <div class=\"max-w-7xl mx-auto\">\n        //\n    </div>\n</div>\n"
  },
  {
    "path": "tests/Commands/ClearRoutesCacheCommand.test.php",
    "content": "<?php\n\nuse Illuminate\\Support\\Facades\\Cache;\nuse Illuminate\\Support\\Facades\\Config;\nuse Z3d0X\\FilamentFabricator\\Commands\\ClearRoutesCacheCommand;\nuse Z3d0X\\FilamentFabricator\\Models\\Page;\nuse Z3d0X\\FilamentFabricator\\Services\\PageRoutesService;\n\nuse function Pest\\Laravel\\artisan;\n\ndescribe(ClearRoutesCacheCommand::class, function () {\n    beforeEach(function () {\n        Config::set('filament-fabricator.routing.prefix');\n    });\n\n    it('can be resolved through the container', function () {\n        $command = resolve(ClearRoutesCacheCommand::class);\n\n        expect($command)->toBeInstanceOf(ClearRoutesCacheCommand::class);\n    });\n\n    it('clears all route caches', function () {\n        /**\n         * @var PageRoutesService $service\n         */\n        $service = resolve(PageRoutesService::class);\n\n        /**\n         * @var Page $page\n         */\n        $page = Page::create([\n            'layout' => 'default',\n            'title' => 'My title',\n            'slug' => 'my-slug',\n            'blocks' => [],\n            'parent_id' => null,\n        ]);\n\n        /**\n         * @var Page $child\n         */\n        $child = Page::create([\n            'layout' => 'default',\n            'title' => 'My child page',\n            'slug' => 'my-child-page',\n            'blocks' => [],\n            'parent_id' => $page->id,\n        ]);\n\n        $service->getAllUrls(); // ensure everything is cached beforehand\n\n        artisan('filament-fabricator:clear-routes-cache')\n            ->assertSuccessful();\n\n        expect(Cache::get('filament-fabricator::PageRoutesService::uri-to-id'))->toBeEmpty();\n        expect(Cache::get('filament-fabricator::PageRoutesService::id-to-uri'))->toBeEmpty();\n\n        $cacheKeys = [...$page->getAllUrlCacheKeys(), ...$child->getAllUrlCacheKeys()];\n\n        expect($cacheKeys)->not->toBeEmpty();\n\n        expect(\n            collect($cacheKeys)\n                ->every(fn (string $cacheKey) => ! Cache::has($cacheKey))\n        )->toBeTrue();\n    });\n\n    it('refreshes the cache properly', function (string $flag, string $newPrefix) {\n        /**\n         * @var PageRoutesService $service\n         */\n        $service = resolve(PageRoutesService::class);\n\n        /**\n         * @var Page $page\n         */\n        $page = Page::create([\n            'layout' => 'default',\n            'title' => 'My title',\n            'slug' => 'my-slug',\n            'blocks' => [],\n            'parent_id' => null,\n        ]);\n\n        /**\n         * @var Page $child\n         */\n        $child = Page::create([\n            'layout' => 'default',\n            'title' => 'My child page',\n            'slug' => 'my-child-page',\n            'blocks' => [],\n            'parent_id' => $page->id,\n        ]);\n\n        $urls = collect([...$page->getAllUrls(), ...$child->getAllUrls()])->sort()->toArray();\n\n        $prevUTI = Cache::get('filament-fabricator::PageRoutesService::uri-to-id');\n        $prevUTI = collect($prevUTI)->sort()->toArray();\n\n        $prevITU = Cache::get('filament-fabricator::PageRoutesService::id-to-uri');\n        $prevITU = collect($prevITU)->sort()->toArray();\n\n        Config::set('filament-fabricator.routing.prefix', $newPrefix);\n\n        artisan('filament-fabricator:clear-routes-cache', [\n            $flag => true,\n        ])\n            ->assertSuccessful();\n\n        $newUrls = collect([...$page->getAllUrls(), ...$child->getAllUrls()])->sort()->toArray();\n        expect($newUrls)->not->toEqualCanonicalizing($urls);\n        expect($newUrls)->not->toBeEmpty();\n        expect(\n            collect($newUrls)\n                ->every(fn (string $url) => str_starts_with($url, \"/$newPrefix\"))\n        )->toBeTrue();\n\n        $newUTI = Cache::get('filament-fabricator::PageRoutesService::uri-to-id');\n        $newUTI = collect($newUTI)->sort()->toArray();\n        expect($newUTI)->not->toEqual($prevUTI);\n        expect($newUTI)->not->toBeEmpty();\n        expect(\n            collect($newUTI)\n                ->keys()\n                ->every(fn (string $uri) => str_starts_with($uri, \"/$newPrefix\"))\n        );\n\n        $newITU = Cache::get('filament-fabricator::PageRoutesService::id-to-uri');\n        $newITU = collect($newITU)->sort()->toArray();\n        expect($newITU)->not->toEqual($prevITU);\n        expect($newITU)->not->toBeEmpty();\n        expect(\n            collect($newITU)\n                ->values()\n                ->flatten()\n                ->every(fn (string $uri) => str_starts_with($uri, \"/$newPrefix\"))\n        );\n    })->with([\n        ['--refresh', 'newprefix'],\n        ['-R', 'np'],\n    ]);\n});\n"
  },
  {
    "path": "tests/ExampleTest.php",
    "content": "<?php\n\nit('can test', function () {\n    expect(true)->toBeTrue();\n});\n"
  },
  {
    "path": "tests/Fixtures/PageBuilderTestComponent.php",
    "content": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\Tests\\Fixtures;\n\nuse Filament\\Forms\\Concerns\\InteractsWithForms;\nuse Filament\\Forms\\Contracts\\HasForms;\nuse Filament\\Schemas\\Schema;\nuse Illuminate\\Support\\MessageBag;\nuse Illuminate\\Support\\ViewErrorBag;\nuse Livewire\\Component;\nuse Z3d0X\\FilamentFabricator\\Forms\\Components\\PageBuilder;\n\nclass PageBuilderTestComponent extends Component implements HasForms\n{\n    use InteractsWithForms;\n\n    public ?array $data = [];\n\n    protected MessageBag $msgBag;\n\n    protected ViewErrorBag $errors;\n\n    public function __construct()\n    {\n        $this->msgBag = new MessageBag;\n        $this->errors = new ViewErrorBag;\n        $this->errors->put('default', $this->msgBag);\n    }\n\n    public function form(Schema $form): Schema\n    {\n        return $form->schema([\n            PageBuilder::make('blocks')\n                ->blocks([]),\n        ])->statePath('data');\n    }\n\n    public function getErrorBag()\n    {\n        return $this->errors->getBag('default');\n    }\n\n    public function getViewErrorBag()\n    {\n        return $this->errors;\n    }\n\n    public function mount(): void\n    {\n        $this->form->fill();\n    }\n\n    public function save()\n    {\n        $this->form->getState();\n    }\n\n    public function render()\n    {\n        return view('filament-fabricator::tests.fixtures.blade-wrapper');\n    }\n}\n"
  },
  {
    "path": "tests/Forms/Components/PageBuilder.test.php",
    "content": "<?php\n\nuse Filament\\Facades\\Filament;\nuse Filament\\Panel;\nuse Z3d0X\\FilamentFabricator\\FilamentFabricatorPlugin;\nuse Z3d0X\\FilamentFabricator\\Forms\\Components\\PageBuilder;\nuse Z3d0X\\FilamentFabricator\\Tests\\Fixtures\\PageBuilderTestComponent;\n\nuse function Pest\\Livewire\\livewire;\n\ndescribe(PageBuilder::class, function () {\n    beforeEach(function () {\n        Filament::setCurrentPanel(\n            Panel::make()\n                ->id('test')\n                ->plugins([\n                    FilamentFabricatorPlugin::make(),\n                ]),\n        );\n    });\n\n    it('renders without throwing an exception', function () {\n        // TODO: Make the test run\n\n        livewire(PageBuilderTestComponent::class)\n            ->fillForm([\n                'data' => [\n                    ['title' => 'Test Item'], // make sure at least one item exists\n                ],\n            ])\n            ->assertSeeHtml('class=\"fi-fo-builder-item')\n            ->assertSchemaExists('blocks');\n    })->skip('Proper Livewire unit testing isn\\'t possible atm');\n});\n"
  },
  {
    "path": "tests/Observers/PageRoutesObserver.test.php",
    "content": "<?php\n\nuse Z3d0X\\FilamentFabricator\\Facades\\FilamentFabricator;\nuse Z3d0X\\FilamentFabricator\\Models\\Page;\nuse Z3d0X\\FilamentFabricator\\Observers\\PageRoutesObserver;\n\ndescribe(PageRoutesObserver::class, function () {\n    beforeEach(function () {\n        // Cleanup the table before every test\n        Page::query()->delete();\n    });\n\n    describe('#created($page)', function () {\n        it('properly adds all the page\\'s URLs to the mapping', function () {\n            $beforeUrls = FilamentFabricator::getPageUrls();\n\n            /**\n             * @var Page $page\n             */\n            $page = Page::create([\n                'layout' => 'default',\n                'title' => 'My title',\n                'slug' => 'my-slug',\n                'blocks' => [],\n                'parent_id' => null,\n            ]);\n\n            $sortUrls = fn (array $urls) => collect($urls)\n                ->sort()\n                ->values()\n                ->toArray();\n\n            $afterUrls = $sortUrls(FilamentFabricator::getPageUrls());\n\n            expect($afterUrls)->not->toEqual($beforeUrls);\n\n            $pageUrls = $sortUrls($page->getAllUrls());\n\n            expect($afterUrls)->toEqual($pageUrls);\n        });\n\n        it('properly works on child pages', function () {\n            /**\n             * @var Page $page\n             */\n            $page = Page::create([\n                'layout' => 'default',\n                'title' => 'My title',\n                'slug' => 'my-slug',\n                'blocks' => [],\n                'parent_id' => null,\n            ]);\n\n            /**\n             * @var Page $page\n             */\n            $child = Page::create([\n                'layout' => 'default',\n                'title' => 'My stuff',\n                'slug' => 'my-stuff',\n                'blocks' => [],\n                'parent_id' => $page->id,\n            ]);\n\n            $sortUrls = fn (array $urls) => collect($urls)\n                ->sort()\n                ->values()\n                ->toArray();\n\n            $allUrls = FilamentFabricator::getPageUrls();\n            $allUrls = $sortUrls($allUrls);\n\n            $fromPages = $sortUrls([\n                ...$page->getAllUrls(),\n                ...$child->getAllUrls(),\n            ]);\n\n            $expectedUrls = $sortUrls([\n                '/my-slug',\n                '/my-slug/my-stuff',\n            ]);\n\n            expect($allUrls)->toEqual($expectedUrls);\n            expect($fromPages)->toEqual($expectedUrls);\n\n            /**\n             * @var Page $page\n             */\n            $descendant = Page::create([\n                'layout' => 'default',\n                'title' => 'Abc xyz',\n                'slug' => 'abc-xyz',\n                'blocks' => [],\n                'parent_id' => $child->id,\n            ]);\n\n            $allUrls = FilamentFabricator::getPageUrls();\n            $allUrls = $sortUrls($allUrls);\n\n            $fromPages = $sortUrls([\n                ...$page->getAllUrls(),\n                ...$child->getAllUrls(),\n                ...$descendant->getAllUrls(),\n            ]);\n\n            $expectedUrls = $sortUrls([\n                '/my-slug',\n                '/my-slug/my-stuff',\n                '/my-slug/my-stuff/abc-xyz',\n            ]);\n\n            expect($allUrls)->toEqual($expectedUrls);\n            expect($fromPages)->toEqual($expectedUrls);\n        });\n    });\n\n    describe('#updated($page)', function () {\n        it('removes the old URLs from the mapping', function () {\n            /**\n             * @var Page $page\n             */\n            $page = Page::create([\n                'layout' => 'default',\n                'title' => 'My title',\n                'slug' => 'my-slug',\n                'blocks' => [],\n                'parent_id' => null,\n            ]);\n\n            $oldUrls = $page->getAllUrls();\n\n            $page->slug = 'not-my-slug';\n            $page->save();\n\n            $allUrls = FilamentFabricator::getPageUrls();\n\n            expect($allUrls)->not->toContain(...$oldUrls);\n        });\n\n        it('adds the new URLs to the mapping', function () {\n            /**\n             * @var Page $page\n             */\n            $page = Page::create([\n                'layout' => 'default',\n                'title' => 'My title',\n                'slug' => 'my-slug',\n                'blocks' => [],\n                'parent_id' => null,\n            ]);\n\n            $page->slug = 'not-my-slug';\n            $page->save();\n\n            $sortUrls = fn (array $urls) => collect($urls)\n                ->sort()\n                ->values()\n                ->toArray();\n\n            $expected = $sortUrls(['/not-my-slug']);\n\n            $newUrls = $sortUrls($page->getAllUrls());\n\n            $allUrls = $sortUrls(FilamentFabricator::getPageUrls());\n\n            expect($allUrls)->toEqual($expected);\n            expect($newUrls)->toEqual($expected);\n        });\n\n        it('properly updates all child (and descendant) routes', function () {\n            /**\n             * @var Page $page\n             */\n            $page = Page::create([\n                'layout' => 'default',\n                'title' => 'My title',\n                'slug' => 'my-slug',\n                'blocks' => [],\n                'parent_id' => null,\n            ]);\n\n            $child1 = Page::create([\n                'layout' => 'default',\n                'title' => 'My child 1',\n                'slug' => 'child-1',\n                'blocks' => [],\n                'parent_id' => $page->id,\n            ]);\n\n            $child2 = Page::create([\n                'layout' => 'default',\n                'title' => 'My child 2',\n                'slug' => 'child-2',\n                'blocks' => [],\n                'parent_id' => $page->id,\n            ]);\n\n            $child3 = Page::create([\n                'layout' => 'default',\n                'title' => 'My child 3',\n                'slug' => 'child-3',\n                'blocks' => [],\n                'parent_id' => $page->id,\n            ]);\n\n            $childOfChild = Page::create([\n                'layout' => 'default',\n                'title' => 'Subchild 1',\n                'slug' => 'subchild-1',\n                'blocks' => [],\n                'parent_id' => $child2->id,\n            ]);\n\n            $sortUrls = fn (array $urls) => collect($urls)\n                ->sort()\n                ->values()\n                ->toArray();\n\n            /**\n             * @var Page[] $descendants\n             */\n            $descendants = [$child1, $child2, $child3, $childOfChild];\n            $pages = [$page, ...$descendants];\n            $oldUrlSets = array_map(fn (Page $page) => $page->getAllUrls(), $descendants);\n\n            $page->slug = 'not-my-slug';\n            $page->save();\n\n            foreach ($descendants as $descendant) {\n                $descendant->refresh();\n            }\n\n            $newUrlSets = array_map(fn (Page $page) => $page->getAllUrls(), $descendants);\n\n            expect($newUrlSets)->not->toEqual($oldUrlSets);\n\n            $allUrls = $sortUrls(FilamentFabricator::getPageUrls());\n\n            $fromPages = $sortUrls(collect($pages)->flatMap(fn (Page $page) => $page->getAllUrls())->toArray());\n\n            $expectedUrls = $sortUrls([\n                '/not-my-slug',\n                '/not-my-slug/child-1',\n                '/not-my-slug/child-2',\n                '/not-my-slug/child-3',\n                '/not-my-slug/child-2/subchild-1',\n            ]);\n\n            expect($allUrls)->toEqual($expectedUrls);\n            expect($fromPages)->toEqual($expectedUrls);\n\n            $child2->slug = 'not-child-2-xyz';\n            $child2->save();\n\n            foreach ($descendants as $descendant) {\n                $descendant->refresh();\n            }\n\n            $allUrls = $sortUrls(FilamentFabricator::getPageUrls());\n            $fromPages = $sortUrls(collect($pages)->flatMap(fn (Page $page) => $page->getAllUrls())->toArray());\n\n            $expectedUrls = $sortUrls([\n                '/not-my-slug',\n                '/not-my-slug/child-1',\n                '/not-my-slug/not-child-2-xyz',\n                '/not-my-slug/child-3',\n                '/not-my-slug/not-child-2-xyz/subchild-1',\n            ]);\n\n            expect($allUrls)->toEqual($expectedUrls);\n            expect($fromPages)->toEqual($expectedUrls);\n        });\n\n        it('properly updates itself and descendants when changing which page is the parent (BelongsTo#associate and BelongsTo#dissociate)', function () {\n            /**\n             * @var Page $page\n             */\n            $page = Page::create([\n                'layout' => 'default',\n                'title' => 'My title',\n                'slug' => 'my-slug',\n                'blocks' => [],\n                'parent_id' => null,\n            ]);\n\n            /**\n             * @var Page $child1\n             */\n            $child1 = Page::create([\n                'layout' => 'default',\n                'title' => 'My child 1',\n                'slug' => 'child-1',\n                'blocks' => [],\n                'parent_id' => $page->id,\n            ]);\n\n            /**\n             * @var Page $child2\n             */\n            $child2 = Page::create([\n                'layout' => 'default',\n                'title' => 'My child 2',\n                'slug' => 'child-2',\n                'blocks' => [],\n                'parent_id' => $page->id,\n            ]);\n\n            /**\n             * @var Page $child3\n             */\n            $child3 = Page::create([\n                'layout' => 'default',\n                'title' => 'My child 3',\n                'slug' => 'child-3',\n                'blocks' => [],\n                'parent_id' => $page->id,\n            ]);\n\n            /**\n             * @var Page $childOfChild\n             */\n            $childOfChild = Page::create([\n                'layout' => 'default',\n                'title' => 'Subchild 1',\n                'slug' => 'subchild-1',\n                'blocks' => [],\n                'parent_id' => $child2->id,\n            ]);\n\n            $sortUrls = fn (array $urls) => collect($urls)\n                ->sort()\n                ->values()\n                ->toArray();\n\n            /**\n             * @var Page[] $descendants\n             */\n            $descendants = [$child1, $child2, $child3, $childOfChild];\n            $pages = [$page, ...$descendants];\n            $oldUrlSets = array_map(fn (Page $page) => $page->getAllUrls(), $descendants);\n\n            $child2->parent()->associate($child1);\n            $child2->save();\n\n            $child3->parent()->dissociate();\n            $child3->save();\n\n            foreach ($descendants as $descendant) {\n                $descendant->refresh();\n            }\n\n            $newUrlSets = array_map(fn (Page $page) => $page->getAllUrls(), $descendants);\n\n            $fromPages = $sortUrls(collect($pages)->flatMap(fn (Page $page) => $page->getAllUrls())->toArray());\n\n            expect($newUrlSets)->not->toEqual($oldUrlSets);\n\n            $allUrls = $sortUrls(FilamentFabricator::getPageUrls());\n\n            $expectedUrls = $sortUrls([\n                '/my-slug',\n                '/my-slug/child-1',\n                '/my-slug/child-1/child-2',\n                '/child-3',\n                '/my-slug/child-1/child-2/subchild-1',\n            ]);\n\n            expect($allUrls)->toEqual($expectedUrls);\n            expect($fromPages)->toEqual($expectedUrls);\n        });\n\n        it('properly updates itself and descendants when changing which page is the parent (Model#update)', function () {\n            /**\n             * @var Page $page\n             */\n            $page = Page::create([\n                'layout' => 'default',\n                'title' => 'My title',\n                'slug' => 'my-slug',\n                'blocks' => [],\n                'parent_id' => null,\n            ]);\n\n            /**\n             * @var Page $child1\n             */\n            $child1 = Page::create([\n                'layout' => 'default',\n                'title' => 'My child 1',\n                'slug' => 'child-1',\n                'blocks' => [],\n                'parent_id' => $page->id,\n            ]);\n\n            /**\n             * @var Page $child2\n             */\n            $child2 = Page::create([\n                'layout' => 'default',\n                'title' => 'My child 2',\n                'slug' => 'child-2',\n                'blocks' => [],\n                'parent_id' => $page->id,\n            ]);\n\n            /**\n             * @var Page $child3\n             */\n            $child3 = Page::create([\n                'layout' => 'default',\n                'title' => 'My child 3',\n                'slug' => 'child-3',\n                'blocks' => [],\n                'parent_id' => $page->id,\n            ]);\n\n            /**\n             * @var Page $childOfChild\n             */\n            $childOfChild = Page::create([\n                'layout' => 'default',\n                'title' => 'Subchild 1',\n                'slug' => 'subchild-1',\n                'blocks' => [],\n                'parent_id' => $child2->id,\n            ]);\n\n            $sortUrls = fn (array $urls) => collect($urls)\n                ->sort()\n                ->values()\n                ->toArray();\n\n            /**\n             * @var Page[] $descendants\n             */\n            $descendants = [$child1, $child2, $child3, $childOfChild];\n            $pages = [$page, ...$descendants];\n            $oldUrlSets = array_map(fn (Page $page) => $page->getAllUrls(), $descendants);\n\n            $child2->update([\n                'parent_id' => $child1->id,\n            ]);\n\n            $child3->update([\n                'parent_id' => null,\n            ]);\n\n            foreach ($descendants as $descendant) {\n                $descendant->refresh();\n            }\n\n            $newUrlSets = array_map(fn (Page $page) => $page->getAllUrls(), $descendants);\n\n            $fromPages = $sortUrls(collect($pages)->flatMap(fn (Page $page) => $page->getAllUrls())->toArray());\n\n            expect($newUrlSets)->not->toEqual($oldUrlSets);\n\n            $allUrls = $sortUrls(FilamentFabricator::getPageUrls());\n\n            $expectedUrls = $sortUrls([\n                '/my-slug',\n                '/my-slug/child-1',\n                '/my-slug/child-1/child-2',\n                '/child-3',\n                '/my-slug/child-1/child-2/subchild-1',\n            ]);\n\n            expect($allUrls)->toEqual($expectedUrls);\n            expect($fromPages)->toEqual($expectedUrls);\n        });\n\n        it('properly updates itself and descendants when changing which page is the parent (manual change and Model#save)', function () {\n            /**\n             * @var Page $page\n             */\n            $page = Page::create([\n                'layout' => 'default',\n                'title' => 'My title',\n                'slug' => 'my-slug',\n                'blocks' => [],\n                'parent_id' => null,\n            ]);\n\n            /**\n             * @var Page $child1\n             */\n            $child1 = Page::create([\n                'layout' => 'default',\n                'title' => 'My child 1',\n                'slug' => 'child-1',\n                'blocks' => [],\n                'parent_id' => $page->id,\n            ]);\n\n            /**\n             * @var Page $child2\n             */\n            $child2 = Page::create([\n                'layout' => 'default',\n                'title' => 'My child 2',\n                'slug' => 'child-2',\n                'blocks' => [],\n                'parent_id' => $page->id,\n            ]);\n\n            /**\n             * @var Page $child3\n             */\n            $child3 = Page::create([\n                'layout' => 'default',\n                'title' => 'My child 3',\n                'slug' => 'child-3',\n                'blocks' => [],\n                'parent_id' => $page->id,\n            ]);\n\n            /**\n             * @var Page $childOfChild\n             */\n            $childOfChild = Page::create([\n                'layout' => 'default',\n                'title' => 'Subchild 1',\n                'slug' => 'subchild-1',\n                'blocks' => [],\n                'parent_id' => $child2->id,\n            ]);\n\n            $sortUrls = fn (array $urls) => collect($urls)\n                ->sort()\n                ->values()\n                ->toArray();\n\n            $descendants = [$child1, $child2, $child3, $childOfChild];\n            $pages = [$page, ...$descendants];\n            $oldUrlSets = array_map(fn (Page $page) => $page->getAllUrls(), $descendants);\n\n            $child2->parent_id = $child1->id;\n            $child2->save();\n\n            $child3->parent_id = null;\n            $child3->save();\n\n            foreach ($descendants as $descendant) {\n                $descendant->refresh();\n            }\n\n            $newUrlSets = array_map(fn (Page $page) => $page->getAllUrls(), $descendants);\n\n            $fromPages = $sortUrls(collect($pages)->flatMap(fn (Page $page) => $page->getAllUrls())->toArray());\n\n            expect($newUrlSets)->not->toEqual($oldUrlSets);\n\n            $allUrls = FilamentFabricator::getPageUrls();\n            $allUrls = $sortUrls($allUrls);\n\n            $expectedUrls = $sortUrls([\n                '/my-slug',\n                '/my-slug/child-1',\n                '/my-slug/child-1/child-2',\n                '/child-3',\n                '/my-slug/child-1/child-2/subchild-1',\n            ]);\n\n            expect($allUrls)->toEqual($expectedUrls);\n            expect($fromPages)->toEqual($expectedUrls);\n        });\n    });\n\n    describe('#deleting($page)', function () {\n        it('removes the page\\'s URLs from the mapping', function () {\n            /**\n             * @var Page $page\n             */\n            $page = Page::create([\n                'layout' => 'default',\n                'title' => 'My title',\n                'slug' => 'my-slug',\n                'blocks' => [],\n                'parent_id' => null,\n            ]);\n\n            $beforeUrls = FilamentFabricator::getPageUrls();\n\n            $page->delete();\n\n            $afterUrls = FilamentFabricator::getPageUrls();\n\n            expect($afterUrls)->not->toEqual($beforeUrls);\n\n            expect($afterUrls)->toBeEmpty();\n        });\n\n        it('sets the childrens\\' parent to null if $page had no parent', function () {\n            /**\n             * @var Page $page\n             */\n            $page = Page::create([\n                'layout' => 'default',\n                'title' => 'My title',\n                'slug' => 'my-slug',\n                'blocks' => [],\n                'parent_id' => null,\n            ]);\n\n            /**\n             * @var Page $child\n             */\n            $child = Page::create([\n                'layout' => 'default',\n                'title' => 'My child page',\n                'slug' => 'my-child-page',\n                'blocks' => [],\n                'parent_id' => $page->id,\n            ]);\n\n            $page->delete();\n\n            $child->refresh();\n\n            expect($child->parent_id)->toBeNull();\n\n            $urls = FilamentFabricator::getPageUrls();\n\n            $expected = ['/my-child-page'];\n\n            expect($urls)->toEqual($expected);\n            expect($child->getAllUrls())->toEqual($expected);\n        });\n\n        it('attaches the children to $page\\'s parent if it had one', function () {\n            /**\n             * @var Page $page\n             */\n            $page = Page::create([\n                'layout' => 'default',\n                'title' => 'My title',\n                'slug' => 'my-slug',\n                'blocks' => [],\n                'parent_id' => null,\n            ]);\n\n            /**\n             * @var Page $child\n             */\n            $child = Page::create([\n                'layout' => 'default',\n                'title' => 'My child page',\n                'slug' => 'my-child-page',\n                'blocks' => [],\n                'parent_id' => $page->id,\n            ]);\n\n            /**\n             * @var Page $descendant\n             */\n            $descendant = Page::create([\n                'layout' => 'default',\n                'title' => 'My sub page',\n                'slug' => 'my-sub-page',\n                'blocks' => [],\n                'parent_id' => $child->id,\n            ]);\n\n            $sortUrls = fn (array $urls) => collect($urls)\n                ->sort()\n                ->values()\n                ->toArray();\n\n            $child->delete();\n            $descendant->refresh();\n            $page->refresh();\n\n            expect($descendant->parent_id)->toBe($page->id);\n\n            $urls = $sortUrls(FilamentFabricator::getPageUrls());\n\n            $expected = $sortUrls([\n                '/my-slug',\n                '/my-slug/my-sub-page',\n            ]);\n\n            $fromPages = $sortUrls(collect([$page, $descendant])->flatMap(fn (Page $page) => $page->getAllUrls())->toArray());\n\n            expect($urls)->toEqual($expected);\n            expect($fromPages)->toEqual($expected);\n        });\n    });\n});\n"
  },
  {
    "path": "tests/Pest.php",
    "content": "<?php\n\nuse Z3d0X\\FilamentFabricator\\Tests\\TestCase;\n\nbeforeAll(function () {\n    if (empty(config('app.key'))) {\n        config()->set('app.key', 'base64:' . base64_encode(random_bytes(32)));\n    }\n});\n\nuses(TestCase::class)->in(__DIR__)->beforeEach(function () {\n    $this->withSession([]);\n});\n"
  },
  {
    "path": "tests/TestCase.php",
    "content": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\Tests;\n\nuse BladeUI\\Heroicons\\BladeHeroiconsServiceProvider;\nuse BladeUI\\Icons\\BladeIconsServiceProvider;\nuse Filament\\FilamentServiceProvider;\nuse Filament\\Forms\\FormsServiceProvider;\nuse Filament\\Support\\SupportServiceProvider;\nuse Illuminate\\Database\\Eloquent\\Factories\\Factory;\nuse Livewire\\LivewireServiceProvider;\nuse Orchestra\\Testbench\\TestCase as Orchestra;\nuse Z3d0X\\FilamentFabricator\\FilamentFabricatorServiceProvider;\n\nclass TestCase extends Orchestra\n{\n    protected function setUp(): void\n    {\n        parent::setUp();\n\n        Factory::guessFactoryNamesUsing(\n            fn (string $modelName) => 'Z3d0X\\\\FilamentFabricator\\\\Database\\\\Factories\\\\' . class_basename($modelName) . 'Factory'\n        );\n    }\n\n    protected function getPackageProviders($app)\n    {\n        return [\n            LivewireServiceProvider::class,\n            FilamentServiceProvider::class,\n            FormsServiceProvider::class,\n            SupportServiceProvider::class,\n            BladeIconsServiceProvider::class,\n            BladeHeroiconsServiceProvider::class,\n\n            FilamentFabricatorServiceProvider::class,\n        ];\n    }\n\n    public function getEnvironmentSetUp($app)\n    {\n        config()->set('database.default', 'testing');\n\n        $migration = include __DIR__ . '/../database/migrations/create_pages_table.php.stub';\n        $migration->up();\n\n        $migration = include __DIR__ . '/../database/migrations/fix_slug_unique_constraint_on_pages_table.php.stub';\n        $migration->up();\n\n    }\n}\n"
  }
]