Full Code of Z3d0X/filament-fabricator for AI

4.x a28e68ce883c cached
88 files
193.2 KB
50.9k tokens
166 symbols
1 requests
Download .txt
Showing preview only (216K chars total). Download the full file or copy to clipboard to get everything.
Repository: Z3d0X/filament-fabricator
Branch: 4.x
Commit: a28e68ce883c
Files: 88
Total size: 193.2 KB

Directory structure:
gitextract_u1aprfa_/

├── .devcontainer/
│   ├── config/
│   │   └── xdebug.ini
│   └── devcontainer.json
├── .editorconfig
├── .gitattributes
├── .github/
│   ├── CONTRIBUTING.md
│   ├── ISSUE_TEMPLATE/
│   │   └── config.yml
│   ├── SECURITY.md
│   ├── dependabot.yml
│   └── workflows/
│       ├── dependabot-auto-merge.yml
│       ├── fix-php-code-style-issues.yml
│       ├── phpstan.yml
│       ├── run-tests.yml
│       └── update-changelog.yml
├── .gitignore
├── .vscode/
│   └── settings.json
├── CHANGELOG.md
├── LICENSE.md
├── README.md
├── bootstrap/
│   └── app.php
├── composer.json
├── config/
│   └── filament-fabricator.php
├── database/
│   ├── factories/
│   │   └── ModelFactory.php
│   └── migrations/
│       ├── create_pages_table.php.stub
│       └── fix_slug_unique_constraint_on_pages_table.php.stub
├── docs/
│   └── README.md
├── phpstan-baseline.neon
├── phpstan.neon.dist
├── phpunit.xml.dist
├── pint.json
├── rector.php
├── resources/
│   ├── lang/
│   │   ├── ar/
│   │   │   └── page-resource.php
│   │   ├── en/
│   │   │   └── page-resource.php
│   │   ├── fr/
│   │   │   └── page-resource.php
│   │   ├── id/
│   │   │   └── page-resource.php
│   │   ├── nl/
│   │   │   └── page-resource.php
│   │   ├── pl/
│   │   │   └── page-resource.php
│   │   ├── ru/
│   │   │   └── page-resource.php
│   │   ├── tr/
│   │   │   └── page-resource.php
│   │   └── uk/
│   │       └── page-resource.php
│   └── views/
│       ├── components/
│       │   ├── forms/
│       │   │   └── components/
│       │   │       ├── page-builder/
│       │   │       │   ├── dropdown-block-picker.blade.php
│       │   │       │   └── modal-block-picker.blade.php
│       │   │       └── page-builder.blade.php
│       │   ├── layouts/
│       │   │   └── base.blade.php
│       │   └── page-blocks.blade.php
│       ├── preview.blade.php
│       └── tests/
│           └── fixtures/
│               └── blade-wrapper.blade.php
├── routes/
│   └── web.php
├── src/
│   ├── Commands/
│   │   ├── Aliases/
│   │   │   ├── MakeLayoutCommand.php
│   │   │   └── MakePageBlockCommand.php
│   │   ├── ClearRoutesCacheCommand.php
│   │   ├── MakeLayoutCommand.php
│   │   └── MakePageBlockCommand.php
│   ├── Enums/
│   │   └── BlockPickerStyle.php
│   ├── Facades/
│   │   └── FilamentFabricator.php
│   ├── FilamentFabricatorManager.php
│   ├── FilamentFabricatorPlugin.php
│   ├── FilamentFabricatorServiceProvider.php
│   ├── Forms/
│   │   └── Components/
│   │       └── PageBuilder.php
│   ├── Helpers.php
│   ├── Http/
│   │   └── Controllers/
│   │       └── PageController.php
│   ├── Layouts/
│   │   └── Layout.php
│   ├── Listeners/
│   │   └── OptimizeWithLaravel.php
│   ├── Models/
│   │   ├── Concerns/
│   │   │   └── HandlesPageUrls.php
│   │   ├── Contracts/
│   │   │   ├── HasPageUrls.php
│   │   │   └── Page.php
│   │   └── Page.php
│   ├── Observers/
│   │   └── PageRoutesObserver.php
│   ├── PageBlocks/
│   │   └── PageBlock.php
│   ├── Resources/
│   │   ├── PageResource/
│   │   │   └── Pages/
│   │   │       ├── Concerns/
│   │   │       │   └── HasPreviewModal.php
│   │   │       ├── CreatePage.php
│   │   │       ├── EditPage.php
│   │   │       ├── ListPages.php
│   │   │       └── ViewPage.php
│   │   └── PageResource.php
│   ├── Services/
│   │   └── PageRoutesService.php
│   └── View/
│       ├── LayoutRenderHook.php
│       └── ResourceSchemaSlot.php
├── stubs/
│   ├── Layout.stub
│   ├── LayoutView.stub
│   ├── PageBlock.stub
│   └── PageBlockView.stub
└── tests/
    ├── Commands/
    │   └── ClearRoutesCacheCommand.test.php
    ├── ExampleTest.php
    ├── Fixtures/
    │   └── PageBuilderTestComponent.php
    ├── Forms/
    │   └── Components/
    │       └── PageBuilder.test.php
    ├── Observers/
    │   └── PageRoutesObserver.test.php
    ├── Pest.php
    └── TestCase.php

================================================
FILE CONTENTS
================================================

================================================
FILE: .devcontainer/config/xdebug.ini
================================================
zend_extension=xdebug.so
xdebug.mode=develop,debug,profile,trace,gcstats
xdebug.discover_client_host=1
xdebug.client_port=9003


================================================
FILE: .devcontainer/devcontainer.json
================================================
{
    "image": "mcr.microsoft.com/devcontainers/php:8.3",
    "forwardPorts": [
        8000,
        5173,
        9000,
        9003
    ],
    "remoteEnv": {
        "COMPOSER_MEMORY_LIMIT": "-1",
        "COMPOSER_PROCESS_TIMEOUT": "0",
        "PHPUNIT_TELEMETRY": "off"
    },
    "features": {
        "ghcr.io/devcontainers/features/node:1": {}
    }
}

================================================
FILE: .editorconfig
================================================
root = true

[*]
charset = utf-8
indent_size = 4
indent_style = space
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

[*.md]
trim_trailing_whitespace = false

[*.{yml,yaml}]
indent_size = 2

[*.{neon,neon.dist}]
indent_size = 4


================================================
FILE: .gitattributes
================================================
* text=auto eol=lf

*.blade.php diff=html
*.css diff=css
*.html diff=html
*.md diff=markdown
*.php diff=php

# Path-based git attributes
# https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html

# Ignore all test and documentation with "export-ignore".
/.github            export-ignore
/.gitattributes     export-ignore
/.gitignore         export-ignore
/phpunit.xml.dist   export-ignore
/art                export-ignore
/docs               export-ignore
/tests              export-ignore
/.editorconfig      export-ignore
/.php_cs.dist.php   export-ignore
/psalm.xml          export-ignore
/psalm.xml.dist     export-ignore
/testbench.yaml     export-ignore
/UPGRADING.md       export-ignore
/phpstan.neon.dist  export-ignore
/phpstan-baseline.neon  export-ignore
/.travis.yml        export-ignore
/.scrutinizer.yml   export-ignore


/.idea                  export-ignore
/.prettierrc            export-ignore
/.package-lock.json     export-ignore
/.editorconfig          export-ignore
/.php_cs.dist.php       export-ignore
/.vscode                export-ignore
/images                 export-ignore
/package.json           export-ignore
/postcss.config.js      export-ignore
/phpunit.xml.dist       export-ignore
/pint.json              export-ignore
/psalm.xml              export-ignore
/psalm.xml.dist         export-ignore
/tailwind.config.js     export-ignore
/testbench.yaml         export-ignore
/UPGRADING.md           export-ignore


## GITATTRIBUTES FOR WEB PROJECTS
#
# These settings are for any web project.
#
# Details per file setting:
#   text    These files should be normalized (i.e. convert CRLF to LF).
#   binary  These files are binary and should be left untouched.
#
# Note that binary is a macro for -text -diff.
######################################################################

# Auto detect
##   Handle line endings automatically for files detected as
##   text and leave all files detected as binary untouched.
##   This will handle all files NOT defined below.

# Source code
*.js              text
*.mjs             text
*.cjs             text
*.json            text
*.ls              text
*.map             text -diff
*.sass            text
*.scss            text diff=css
*.sh              text eol=lf
.husky/*          text eol=lf
*.sql             text
*.ts              text

# Docker
Dockerfile        text

# Documentation
*.ipynb           text eol=lf
*.markdown        text diff=markdown
*.md              text diff=markdown
*.mdwn            text diff=markdown
*.mdown           text diff=markdown
*.mkd             text diff=markdown
*.mkdn            text diff=markdown
*.mdtxt           text
*.mdtext          text
*.txt             text
AUTHORS           text
CHANGELOG         text
CHANGES           text
CONTRIBUTING      text
COPYING           text
copyright         text
*COPYRIGHT*       text
INSTALL           text
license           text
LICENSE           text
NEWS              text
readme            text
*README*          text
TODO              text

# Templates
*.dot             text
*.ejs             text
*.erb             text
*.haml            text
*.handlebars      text
*.hbs             text
*.hbt             text
*.jade            text
*.latte           text
*.mustache        text
*.njk             text
*.phtml           text
*.svelte          text
*.tmpl            text
*.tpl             text
*.twig            text
*.vue             text

# Configs
*.cnf             text
*.conf            text
*.config          text
.editorconfig     text
*.env             text
.gitattributes    text
.gitconfig        text
.htaccess         text
*.lock            text -diff
package.json      text eol=lf
package-lock.json text eol=lf -diff
pnpm-lock.yaml    text eol=lf -diff
.prettierrc       text
yarn.lock         text -diff
*.toml            text
*.yaml            text
*.yml             text
browserslist      text
Makefile          text
makefile          text
# Fixes syntax highlighting on GitHub to allow comments
tsconfig.json     linguist-language=JSON-with-Comments

# Heroku
Procfile          text

# Graphics
*.ai              binary
*.bmp             binary
*.eps             binary
*.gif             binary
*.gifv            binary
*.ico             binary
*.jng             binary
*.jp2             binary
*.jpg             binary
*.jpeg            binary
*.jpx             binary
*.jxr             binary
*.pdf             binary
*.png             binary
*.psb             binary
*.psd             binary
# SVG treated as an asset (binary) by default.
*.svg             text
# If you want to treat it as binary,
# use the following line instead.
*.svgz            binary
*.tif             binary
*.tiff            binary
*.wbmp            binary
*.webp            binary

# Audio
*.kar             binary
*.m4a             binary
*.mid             binary
*.midi            binary
*.mp3             binary
*.ogg             binary
*.ra              binary

# Video
*.3gpp            binary
*.3gp             binary
*.as              binary
*.asf             binary
*.asx             binary
*.avi             binary
*.fla             binary
*.flv             binary
*.m4v             binary
*.mng             binary
*.mov             binary
*.mp4             binary
*.mpeg            binary
*.mpg             binary
*.ogv             binary
*.swc             binary
*.swf             binary
*.webm            binary

# Archives
*.7z              binary
*.gz              binary
*.jar             binary
*.rar             binary
*.tar             binary
*.zip             binary

# Fonts
*.ttf             binary
*.eot             binary
*.otf             binary
*.woff            binary
*.woff2           binary

# Executables
*.exe             binary
*.pyc             binary
# Prevents massive diffs caused by vendored, minified files
**/.yarn/releases/**   binary
**/.yarn/plugins/**    binary

# RC files (like .babelrc or .eslintrc)
*.*rc             text

# Ignore files (like .npmignore or .gitignore)
*.*ignore         text


================================================
FILE: .github/CONTRIBUTING.md
================================================
# Contributing

Contributions are **welcome** and will be fully **credited**.

Please read and understand the contribution guide before creating an issue or pull request.

## Etiquette

This project is open source, and as such, the maintainers give their free time to build and maintain the source code
held within. They make the code freely available in the hope that it will be of use to other developers. It would be
extremely unfair for them to suffer abuse or anger for their hard work.

Please be considerate towards maintainers when raising issues or presenting pull requests. Let's show the
world that developers are civilized and selfless people.

It's the duty of the maintainer to ensure that all submissions to the project are of sufficient
quality 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.

## Viability

When requesting or submitting new features, first consider whether it might be useful to others. Open
source projects are used by many developers, who may have entirely different needs to your own. Think about
whether or not your feature is likely to be used by other users of the project.

## Procedure

Before filing an issue:

- Attempt to replicate the problem, to ensure that it wasn't a coincidental incident.
- Check to make sure your feature suggestion isn't already present within the project.
- Check the pull requests tab to ensure that the bug doesn't have a fix in progress.
- Check the pull requests tab to ensure that the feature isn't already in progress.

Before submitting a pull request:

- Check the codebase to ensure that your feature doesn't already exist.
- Check the pull requests to ensure that another person hasn't already submitted the feature or fix.

## Requirements

If the project maintainer has any additional requirements, you will find them listed here.

- **[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).

- **Add tests!** - Your patch won't be accepted if it doesn't have tests.

- **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date.

- **Consider our release cycle** - We try to follow [SemVer v2.0.0](https://semver.org/). Randomly breaking public APIs is not an option.

- **One pull request per feature** - If you want to do more than one thing, send multiple pull requests.

- **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.

**Happy coding**!


================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
  - name: Ask a question
    url: https://github.com/Z3d0X/filament-fabricator/discussions/new?category=q-a
    about: Ask the community for help
  - name: Request a feature
    url: https://github.com/Z3d0X/filament-fabricator/discussions/new?category=ideas
    about: Share ideas for new features
  - name: Report a security issue
    url: https://github.com/Z3d0X/filament-fabricator/security/policy
    about: Learn how to notify us for sensitive bugs
  - name: Report a bug
    url: https://github.com/Z3d0X/filament-fabricator/issues/new
    about: Report a reproducable bug


================================================
FILE: .github/SECURITY.md
================================================
# Security Policy

If you discover any security related issues, please email ziyaan2010@gmail.com instead of using the issue tracker.


================================================
FILE: .github/dependabot.yml
================================================
# Please see the documentation for all configuration options:
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates

version: 2
updates:

  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "weekly"
    labels:
      - "dependencies"

================================================
FILE: .github/workflows/dependabot-auto-merge.yml
================================================
name: dependabot-auto-merge
on: pull_request_target

permissions:
  pull-requests: write
  contents: write

jobs:
  dependabot:
    runs-on: ubuntu-latest
    if: ${{ github.actor == 'dependabot[bot]' }}
    steps:

      - name: Dependabot metadata
        id: metadata
        uses: dependabot/fetch-metadata@v2.5.0
        with:
          github-token: "${{ secrets.GITHUB_TOKEN }}"

      - name: Auto-merge Dependabot PRs for semver-minor updates
        if: ${{steps.metadata.outputs.update-type == 'version-update:semver-minor'}}
        run: gh pr merge --auto --merge "$PR_URL"
        env:
          PR_URL: ${{github.event.pull_request.html_url}}
          GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}

      - name: Auto-merge Dependabot PRs for semver-patch updates
        if: ${{steps.metadata.outputs.update-type == 'version-update:semver-patch'}}
        run: gh pr merge --auto --merge "$PR_URL"
        env:
          PR_URL: ${{github.event.pull_request.html_url}}
          GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}

================================================
FILE: .github/workflows/fix-php-code-style-issues.yml
================================================
name: Fix PHP code style issues

on: [push]

jobs:
  php-code-styling:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v6
        with:
          ref: ${{ github.head_ref }}

      - name: Fix PHP code style issues
        uses: aglipanci/laravel-pint-action@2.6

      - name: Commit changes
        uses: stefanzweifel/git-auto-commit-action@v7
        with:
          commit_message: Fix styling

================================================
FILE: .github/workflows/phpstan.yml
================================================
name: PHPStan

on:
  push:
    paths:
      - '**.php'
      - 'phpstan.neon.dist'

jobs:
  phpstan:
    name: phpstan
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.2'
          coverage: none

      - name: Install composer dependencies
        uses: ramsey/composer-install@v3

      - name: Run PHPStan
        run: ./vendor/bin/phpstan


================================================
FILE: .github/workflows/run-tests.yml
================================================
name: run-tests

on:
  push:
    branches:
      - 1.x
      - 2.x
  pull_request:
    branches:
      - 1.x
      - 2.x

jobs:
  test:
    runs-on: ${{ matrix.os }}

    strategy:
      fail-fast: true
      matrix:
        os: [ubuntu-latest]
        php: [8.1, '8.2', '8.3']
        laravel: ['10.*', '11.*', '12.*', '13.*']
        stability: [prefer-stable]
        include:
          - laravel: 12.*
            testbench: 10.*
          - laravel: 11.*
            testbench: 9.*
          - laravel: 10.*
            testbench: 8.*
          - laravel: 13.*
            testbench: 11.*
        exclude:
          - laravel: 12.*
            php: 8.1
          - laravel: 11.*
            php: 8.1
          - laravel: 13.*
            php: 8.1
          - laravel: 13.*
            php: '8.2'

    name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }}

    steps:
      - name: Checkout code
        uses: actions/checkout@v6

      - name: Cache dependencies
        uses: actions/cache@v5
        with:
          path: ~/.composer/cache/files
          key: dependencies-laravel-${{ matrix.laravel }}-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }}

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: ${{ matrix.php }}
          extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo
          coverage: none

      - name: Setup problem matchers
        run: |
          echo "::add-matcher::${{ runner.tool_cache }}/php.json"
          echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"

      - name: Install dependencies
        run: |
          composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update
          composer update --${{ matrix.stability }} --prefer-dist --no-interaction

      - name: Execute tests
        run: ./vendor/bin/pest


================================================
FILE: .github/workflows/update-changelog.yml
================================================
name: "Update Changelog"

on:
  release:
    types: [released]

jobs:
  update:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v6

      - name: Update Changelog
        uses: stefanzweifel/changelog-updater-action@v1
        with:
          latest-version: ${{ github.event.release.name }}
          release-notes: ${{ github.event.release.body }}

      - name: Commit updated CHANGELOG
        uses: stefanzweifel/git-auto-commit-action@v7
        with:
          branch: ${{ github.event.release.target_commitish }}
          commit_message: Update CHANGELOG
          file_pattern: CHANGELOG.md


================================================
FILE: .gitignore
================================================
.idea
.php_cs
.php_cs.cache
.phpunit.result.cache
build
composer.lock
coverage
phpunit.xml
phpstan.neon
testbench.yaml
vendor
node_modules
.php-cs-fixer.cache
**/.DS_Store


================================================
FILE: .vscode/settings.json
================================================
{
	"intelephense.environment.includePaths": [
		"/workspaces/filament-fabricator/vendor"
	]
}


================================================
FILE: CHANGELOG.md
================================================
# Changelog

All notable changes to `filament-fabricator` will be documented in this file.

## v4.0.0 - Filament v5 Compatibility - 2026-02-16

### What's Changed

* 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
* Migrate to Filament v5 by @Voltra in https://github.com/Z3d0X/filament-fabricator/pull/260

**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v3.0.3...v4.0.0

## v3.0.3 - 2025-12-31

### What's Changed

* hotfix: 3.0.2 mishaps by @Voltra in https://github.com/Z3d0X/filament-fabricator/pull/257

**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v3.0.2...v3.0.3

## v3.0.2 - 2025-12-23

### What's Changed

* build(deps): bump actions/checkout from 5 to 6 by @dependabot[bot] in https://github.com/Z3d0X/filament-fabricator/pull/252
* build(deps): bump actions/cache from 4 to 5 by @dependabot[bot] in https://github.com/Z3d0X/filament-fabricator/pull/253
* hotfix: Filament v4 migration mishaps by @Voltra in https://github.com/Z3d0X/filament-fabricator/pull/254

**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v3.0.1...v3.0.2

## v3.0.1 - 2025-10-24

**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v2.7.0...v2.7.1

### What's Changed

* build(deps): bump actions/cache from 3 to 4 by @dependabot[bot] in https://github.com/Z3d0X/filament-fabricator/pull/239
* build(deps): bump actions/checkout from 4 to 5 by @dependabot[bot] in https://github.com/Z3d0X/filament-fabricator/pull/238
* fix: wrong argument used on getPageUrlFromId breaks page listing on f… by @Reapious in https://github.com/Z3d0X/filament-fabricator/pull/245
* fix: update PageBuilder for Filament v4 compatibility by @mckenziearts in https://github.com/Z3d0X/filament-fabricator/pull/244
* Add polish translations by @KaminskiDaniell in https://github.com/Z3d0X/filament-fabricator/pull/247
* build(deps): bump stefanzweifel/git-auto-commit-action from 6 to 7 by @dependabot[bot] in https://github.com/Z3d0X/filament-fabricator/pull/246

### New Contributors

* @Reapious made their first contribution in https://github.com/Z3d0X/filament-fabricator/pull/245
* @mckenziearts made their first contribution in https://github.com/Z3d0X/filament-fabricator/pull/244
* @KaminskiDaniell made their first contribution in https://github.com/Z3d0X/filament-fabricator/pull/247

**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v3.0...v3.0.1

## v3.0 - Filament v4 Support - 2025-09-29

### What's Changed

* Migrate 3.x codebase to Filament v4 by @Voltra in https://github.com/Z3d0X/filament-fabricator/pull/237

**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v2.6.1...v3.0

## v2.6.1 - 2025-09-09

### What's Changed

* hotfix: Revert changes made in v2.6.0 by @Voltra in https://github.com/Z3d0X/filament-fabricator/pull/236

**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v2.6.0...v2.6.1

## v2.5.1 - 2025-08-03

### What's Changed

* 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
* 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
* Fix: bind page route when runnning unit tests by @thecrazybob in https://github.com/Z3d0X/filament-fabricator/pull/224
* Fix: resolve PHP 8 deprecation warning in namespace handling by @thecrazybob in https://github.com/Z3d0X/filament-fabricator/pull/219
* Add dutch translations by @lbovit in https://github.com/Z3d0X/filament-fabricator/pull/220
* build(deps): bump stefanzweifel/git-auto-commit-action from 5 to 6 by @dependabot[bot] in https://github.com/Z3d0X/filament-fabricator/pull/229
* Fix issue when removing old urls from uncached pages by @Voltra in https://github.com/Z3d0X/filament-fabricator/commit/2e09ce5d6b5a9cb04c9d1c9a17f519e76febb7e4

### New Contributors

* @thecrazybob made their first contribution in https://github.com/Z3d0X/filament-fabricator/pull/224
* @lbovit made their first contribution in https://github.com/Z3d0X/filament-fabricator/pull/220

**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v2.5...v2.5.1

## v2.5 - 2025-03-04

### What's Changed

* 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
* build(deps): bump aglipanci/laravel-pint-action from 2.4 to 2.5 by @dependabot in https://github.com/Z3d0X/filament-fabricator/pull/211
* Laravel 12.x Compatibility by @laravel-shift in https://github.com/Z3d0X/filament-fabricator/pull/216
* Null Page Reference in FilamentFabricator Block Preloading by @rsandipermana in https://github.com/Z3d0X/filament-fabricator/pull/214

### New Contributors

* @rsandipermana made their first contribution in https://github.com/Z3d0X/filament-fabricator/pull/214

**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v2.4.3...v2.5

## v2.4.3 - 2025-01-22

### What's Changed

* Add Indonesian language translations for page resource by @cuinc99 in https://github.com/Z3d0X/filament-fabricator/pull/208

### New Contributors

* @cuinc99 made their first contribution in https://github.com/Z3d0X/filament-fabricator/pull/208

**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v2.4.2...v2.4.3

## v2.4.2 - 2025-01-08

### What's Changed

* Rename ResourcheSchemaSlot.php to ResourceSchemaSlot.php by @rezadindar in https://github.com/Z3d0X/filament-fabricator/pull/206
* proposal: Replace the default middleware list by the 'web' middleware group by @Voltra in https://github.com/Z3d0X/filament-fabricator/pull/207

### New Contributors

* @rezadindar made their first contribution in https://github.com/Z3d0X/filament-fabricator/pull/206

**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v2.4.1...v2.4.2

## v2.4.1 - 2025-01-07

### What's Changed

* Add constants for layout render hooks by @Voltra in https://github.com/Z3d0X/filament-fabricator/pull/193
* Fix n+1 query by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/199
* Fix modal picker style by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/200
* Fix PHPStan by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/201
* hotfix: Proper clearing of the ID<->URI mappings by @Voltra in https://github.com/Z3d0X/filament-fabricator/pull/204
* Add constants for page resource schema slots by @Voltra in https://github.com/Z3d0X/filament-fabricator/pull/203
* feat ( Localization ) : add Turkish lang. by @AzizEmir in https://github.com/Z3d0X/filament-fabricator/pull/202

### New Contributors

* @AzizEmir made their first contribution in https://github.com/Z3d0X/filament-fabricator/pull/202

**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v2.4.0...v2.4.1

## v2.4.0 - 2024-12-24

### What's Changed

* "Smart" route URLs caching by @Voltra in https://github.com/Z3d0X/filament-fabricator/pull/119

**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v2.3.0...v2.4.0

## v2.3.0 - 2024-12-22

### What's Changed

* allow layout live switching by @phpsa in https://github.com/Z3d0X/filament-fabricator/pull/188
* Add exception for runningInConsole in FilamentFabricatorServiceProvid… by @yolanmees in https://github.com/Z3d0X/filament-fabricator/pull/160
* 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

### New Contributors

* @phpsa made their first contribution in https://github.com/Z3d0X/filament-fabricator/pull/188
* @yolanmees made their first contribution in https://github.com/Z3d0X/filament-fabricator/pull/160

**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v2.2.2...v2.3.0

## v2.2.2 - 2024-05-12

### What's Changed

* 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
* 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
* Allow string IDs by @rojtjo in https://github.com/Z3d0X/filament-fabricator/pull/158

### New Contributors

* @rojtjo made their first contribution in https://github.com/Z3d0X/filament-fabricator/pull/158

**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v2.2.1...v2.2.2

## v2.2.1 - 2024-04-15

### What's Changed

* 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
* Add note to README regarding the plugin assets by @pboivin in https://github.com/Z3d0X/filament-fabricator/pull/149
* Fixed incorrect table name during migration by @witaway in https://github.com/Z3d0X/filament-fabricator/pull/151

### New Contributors

* @pboivin made their first contribution in https://github.com/Z3d0X/filament-fabricator/pull/149
* @witaway made their first contribution in https://github.com/Z3d0X/filament-fabricator/pull/151

**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v2.2.0...v2.2.1

## v2.2.0 - 2024-03-12

### Laravel 11.x compatibility added

#### What's Changed

* build(deps): bump ramsey/composer-install from 2 to 3 by @dependabot in https://github.com/Z3d0X/filament-fabricator/pull/143
* Laravel 11.x Compatibility by @laravel-shift in https://github.com/Z3d0X/filament-fabricator/pull/142

**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v2.1.1...v2.2.0

## v2.1.1 - 2024-02-19

### What's Changed

* Fix Resource Registration by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/140

**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v2.1.0...v2.1.1

## v2.1.0 - 2024-02-09

### What's Changed

* Fix Slug Unique Constraint by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/135
* Feature Block Picker Styles by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/136

Note: 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

**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v2.0.6...v2.1.0

## v2.0.6 - 2024-02-03

### What's Changed

* Fix octane issues change registering package from scoped to singleton by @ksimenic in https://github.com/Z3d0X/filament-fabricator/pull/130

### New Contributors

* @ksimenic made their first contribution in https://github.com/Z3d0X/filament-fabricator/pull/130

**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v2.0.5...v2.0.6

## v2.0.5 - 2024-01-18

### What's Changed

* Respect app locale for slug generation by @flolanger in https://github.com/Z3d0X/filament-fabricator/pull/109
* 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
* [ar]: Add Arabic translations by @mohamedsabil83 in https://github.com/Z3d0X/filament-fabricator/pull/127

### New Contributors

* @flolanger made their first contribution in https://github.com/Z3d0X/filament-fabricator/pull/109
* @mohamedsabil83 made their first contribution in https://github.com/Z3d0X/filament-fabricator/pull/127

**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v2.0.4...v2.0.5

## v2.0.4 - 2023-11-26

### What's Changed

* Update README.md by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/105
* Fix getLabel() and getPluralLabel() deprecation by @devhoussam1998 in https://github.com/Z3d0X/filament-fabricator/pull/113
* Allow easier subclassing of the Page model by @Voltra in https://github.com/Z3d0X/filament-fabricator/pull/115

### New Contributors

* @devhoussam1998 made their first contribution in https://github.com/Z3d0X/filament-fabricator/pull/113
* @Voltra made their first contribution in https://github.com/Z3d0X/filament-fabricator/pull/115

**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v2.0.3...v2.0.4

## v2.0.3 - 2023-10-14

### What's Changed

- build(deps): bump stefanzweifel/git-auto-commit-action from 4 to 5 by @dependabot in https://github.com/Z3d0X/filament-fabricator/pull/97
- fix: default layout resolving by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/103

**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v2.0.2...v2.0.3

## v1.2.2 - 2023-10-14

### What's Changed

- fix: default layout resolving by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/103

**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v1.2.1...v1.2.2

## v2.0.2 - 2023-09-25

### What's Changed

- [Docs]: Remove Peek from installation instructions by @viraljetani in https://github.com/Z3d0X/filament-fabricator/pull/87
- build(deps): bump actions/checkout from 3 to 4 by @dependabot in https://github.com/Z3d0X/filament-fabricator/pull/89
- Update PageResource.php by @RibesAlexandre in https://github.com/Z3d0X/filament-fabricator/pull/93
- Fix: Lazy Loading in Model Strict Mode by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/96

### New Contributors

- @viraljetani made their first contribution in https://github.com/Z3d0X/filament-fabricator/pull/87
- @RibesAlexandre made their first contribution in https://github.com/Z3d0X/filament-fabricator/pull/93

**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v2.0.1...v2.0.2

## v2.0.1 - 2023-08-20

### What's Changed

- Fix deprecated code by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/83

**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v2.0.0...v2.0.1

## v1.1.5 - 2023-04-18

### What's Changed

- Prevented lazy loading. by @danielbehrendt in https://github.com/Z3d0X/filament-fabricator/pull/56

### New Contributors

- @danielbehrendt made their first contribution in https://github.com/Z3d0X/filament-fabricator/pull/56

**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v1.1.4...v1.1.5

## v1.1.4 - 2023-03-30

### What's Changed

- 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
- Translatable Resource Labels in https://github.com/Z3d0X/filament-fabricator/commit/073fb9d4935951b6f59fd01b8426ddf8321344be

**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v1.1.3...v1.1.4

## v1.1.3 - 2023-03-05

### What's Changed

- Fix: Page Middleware by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/51

**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v1.1.2...v1.1.3

## v1.1.2 - 2023-02-27

### What's Changed

- Make `$page` instance accessible in blocks by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/45
- Shorter Commands by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/46
- Fix Routing Urls by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/47

**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v1.1.1...v1.1.2

## v1.1.1 - 2023-02-17

### What's Changed

- Fix: ignore deleted blocks by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/41
- Refactor to PageController by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/42

**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v1.1.0...v1.1.1

## v1.1.0 - 2023-02-15

### What's Changed

- Laravel 10 Support by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/38

**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v1.0.4...v1.1.0

## v1.0.4 - 2023-02-14

### What's Changed

- 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
- Fix custom PageResource not being considered on Pages by @lucasgiovanny in https://github.com/Z3d0X/filament-fabricator/pull/37

### New Contributors

- @lucasgiovanny made their first contribution in https://github.com/Z3d0X/filament-fabricator/pull/37

**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v1.0.3...v1.0.4

## v1.0.3 - 2023-01-07

### What's Changed

- build(deps): bump ramsey/composer-install from 1 to 2 by @dependabot in https://github.com/Z3d0X/filament-fabricator/pull/28
- Add a save button at the top of the page by @jvkassi in https://github.com/Z3d0X/filament-fabricator/pull/31
- 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
- Configurable Database Table & Migration Down Method by @mrlinnth in https://github.com/Z3d0X/filament-fabricator/pull/19
- Fix: Frontend Routing by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/34

### New Contributors

- @jvkassi made their first contribution in https://github.com/Z3d0X/filament-fabricator/pull/31
- @mrlinnth made their first contribution in https://github.com/Z3d0X/filament-fabricator/pull/19

**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v1.0.2...v1.0.3

## v1.0.2 - 2022-11-14

### What's Changed

- Fix: Homepage Routing by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/24

**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v1.0.1...v1.0.2

## v1.0.1 - 2022-11-13

### What's Changed

- 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
- Feature: Configurable `PageResource` by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/22
- Fix: Hide page urls when routing disabled by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/23

**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v1.0.0...v1.0.1

## v1.0.0 - 2022-10-25

### What's Changed

- Feature: `PageResource` Translations by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/7
- Feature: `PageBuilder` Field by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/8
- Feature: Base Layout by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/10
- Feature: Configurable `Page` Model by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/11
- Fix: HomePage Routing by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/12
- 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
- Feature: Nested Pages by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/13
- adding prefix for pages by @MuhamadSelim in https://github.com/Z3d0X/filament-fabricator/pull/14
- fixing Dynamic Page Model for FilamentFabricatorManager.php setPageUr… by @MuhamadSelim in https://github.com/Z3d0X/filament-fabricator/pull/16
- Feature: Page Model Contract by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/17
- Feature: PageResource custom fieldslots by @Z3d0X in https://github.com/Z3d0X/filament-fabricator/pull/18

### New Contributors

- @Z3d0X made their first contribution in https://github.com/Z3d0X/filament-fabricator/pull/7
- @dependabot made their first contribution in https://github.com/Z3d0X/filament-fabricator/pull/15
- @MuhamadSelim made their first contribution in https://github.com/Z3d0X/filament-fabricator/pull/14

**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/compare/v0.1.0...v1.0.0

## v0.1.0 - 2022-09-19

**Full Changelog**: https://github.com/Z3d0X/filament-fabricator/commits/v0.1.0

## 1.0.0 - 202X-XX-XX

- initial release


================================================
FILE: LICENSE.md
================================================
The MIT License (MIT)

Copyright (c) Z3d0X <ziyaan2010@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.


================================================
FILE: README.md
================================================
# Block-Based Page Builder Skeleton for your Filament Apps

[![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)
[![Total Downloads](https://img.shields.io/packagist/dt/z3d0x/filament-fabricator.svg?style=for-the-badge)](https://packagist.org/packages/z3d0x/filament-fabricator)

<p align="center">
  <img alt="fabricator banner" src="https://raw.githubusercontent.com/z3d0x/filament-fabricator/2.x/art/banner.jpg" />
</p>

**_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).

## Compatibility

| Fabricator                                                   | Filament | PHP  |
| ------------------------------------------------------------ | -------- | ---- |
| [1.x](https://github.com/z3d0x/filament-fabricator/tree/1.x) | ^2.0     | ^8.0 |
| [2.x](https://github.com/z3d0x/filament-fabricator/tree/2.x) | ^3.0     | ^8.1 |
| [3.x](https://github.com/z3d0x/filament-fabricator/tree/3.x) | ^4.0     | ^8.2 |
| [4.x](https://github.com/z3d0x/filament-fabricator/tree/4.x) | ^5.0     | ^8.3 |

## Installation

You can install the package via composer:

```bash
composer require z3d0x/filament-fabricator
```

After that run the install command:

```bash
php artisan filament-fabricator:install
```

Register a `FilamentFabricatorPlugin` instance in your Panel provider:

```php
use Z3d0X\FilamentFabricator\FilamentFabricatorPlugin;

//..

public function panel(Panel $panel): Panel
{
    return $panel
        // ...
        ->plugins([
            FilamentFabricatorPlugin::make(),
        ]);
}
```

Then, publish the registered plugin assets:

```
php artisan filament:assets
```

## Documentation

Documentation can be viewed at: https://filamentphp.com/plugins/z3d0x-fabricator

## Screenshots

<img alt="fabricator-index" src="https://raw.githubusercontent.com/z3d0x/filament-fabricator/2.x/art/list-screenshot.png">
<img alt="fabricator-edit-1" src="https://raw.githubusercontent.com/z3d0x/filament-fabricator/2.x/art/edit-screenshot-1.png">
<img alt="fabricator-edit-2" src="https://raw.githubusercontent.com/z3d0x/filament-fabricator/2.x/art/edit-screenshot-2.png">

## Migrate

### From 3.x to 4.x

- Relies on PHP 8.3 as the minimum version

### From 2.x to 3.x

- There is no longer a default value for the `pages.layout` database column
- `FilamentFabricatorManager#getPageUrlFromId` no longer has a `prefixSlash` parameter
- Relies on PHP 8.2 as the minimum version

### From Filament v4 to Filament v5

Following [the upgrade guide from Filament](https://filamentphp.com/docs/5.x/upgrade-guide) should be enough.

In case it isn't, you can run the following commands:

```bash
composer require filament/upgrade:"^5.0" -W --dev

./vendor/bin/filament-v5

# Run the commands output by the upgrade script, they are unique to your app
composer require filament/filament:"^5.0" z3d0x/filament-fabricator:"^5.0" -W --no-update
composer update
```

### From Filament v3 to Filament v4

Following [the upgrade guide from Filament](https://filamentphp.com/docs/4.x/upgrade-guide) should be enough.

In case it isn't, you can run the following commands:

```bash
composer require filament/upgrade:"^4.0" -W --dev

./vendor/bin/filament-v4

# Run the commands output by the upgrade script, they are unique to your app
composer require filament/filament:"^4.0" z3d0x/filament-fabricator:"^4.0" -W --no-update
composer update
```

For more info on breaking changes, please refer to the [CHANGELOG](CHANGELOG.md)

## Changelog

Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.

## Contributing

Please see [CONTRIBUTING](.github/CONTRIBUTING.md) for details.

## Security Vulnerabilities

Please review [our security policy](../../security/policy) on how to report security vulnerabilities.

## Credits

- [ZedoX](https://github.com/Z3d0X)
- [Voltra](https://github.com/Voltra)
- [Patrick Boivin](https://github.com/pboivin) - Filament Peek
- [All Contributors](../../contributors)

## License

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.


================================================
FILE: bootstrap/app.php
================================================
<?php

use Filament\FilamentServiceProvider;
use Filament\Support\SupportServiceProvider;
use Livewire\LivewireServiceProvider;
use Orchestra\Testbench\Concerns\CreatesApplication;
use Orchestra\Testbench\Foundation\Application;
use Z3d0X\FilamentFabricator\FilamentFabricatorServiceProvider;

$basePathLocator = new class
{
    use CreatesApplication;
};

$app = (new Application($basePathLocator::applicationBasePath()))
    ->configure([
        'enables_package_discoveries' => true,
    ])
    ->createApplication();

$app->register(LivewireServiceProvider::class);
$app->register(FilamentServiceProvider::class);
$app->register(SupportServiceProvider::class);
$app->register(FilamentFabricatorServiceProvider::class);

return $app;


================================================
FILE: composer.json
================================================
{
    "name": "z3d0x/filament-fabricator",
    "description": "Block-Based Page Builder Skeleton for your Filament Apps",
    "keywords": [
        "Z3d0X",
        "laravel",
        "filament-fabricator"
    ],
    "homepage": "https://github.com/z3d0x/filament-fabricator",
    "license": "MIT",
    "authors": [
        {
            "name": "Ziyaan Hassan",
            "email": "ziyaan2010@gmail.com",
            "role": "Developer"
        }
    ],
    "require": {
        "php": "^8.3",
        "filament/filament": "^5.0",
        "illuminate/contracts": "^11.0 | ^12.0 | ^13.0",
        "livewire/livewire": "^4.0",
        "pboivin/filament-peek": "^4.0",
        "spatie/laravel-package-tools": "^1.13.5"
    },
    "require-dev": {
        "driftingly/rector-laravel": "^2.0",
        "filament/upgrade": "^5.0",
        "larastan/larastan": "^2.9 | ^3.7 | dev-l13",
        "laravel/pint": "^1.24",
        "nunomaduro/collision": "^8.0",
        "orchestra/testbench": "^10.0 | ^11.0",
        "pestphp/pest": "^4.0",
        "pestphp/pest-plugin-laravel": "^4.0",
        "pestphp/pest-plugin-livewire": "^4.0",
        "phpstan/phpstan-deprecation-rules": "^2.0",
        "phpstan/phpstan-phpunit": "^2.0",
        "rector/rector": "^2.1",
        "spatie/laravel-ray": "^1.26"
    },
    "autoload": {
        "psr-4": {
            "Z3d0X\\FilamentFabricator\\": "src",
            "Z3d0X\\FilamentFabricator\\Database\\Factories\\": "database/factories"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "Z3d0X\\FilamentFabricator\\Tests\\": "tests"
        }
    },
    "scripts": {
        "pint": "@php ./vendor/bin/pint",
        "rector": [
            "@php ./vendor/bin/rector",
            "@pint"
        ],
        "test:pest": "@php ./vendor/bin/pest --parallel",
        "test:phpstan": "@php ./vendor/bin/phpstan analyse",
        "test": [
            "@test:pest",
            "@test:phpstan"
        ]
    },
    "config": {
        "sort-packages": true,
        "allow-plugins": {
            "composer/package-versions-deprecated": true,
            "pestphp/pest-plugin": true
        }
    },
    "extra": {
        "laravel": {
            "providers": [
                "Z3d0X\\FilamentFabricator\\FilamentFabricatorServiceProvider"
            ],
            "aliases": {
                "FilamentFabricator": "Z3d0X\\FilamentFabricator\\Facades\\FilamentFabricator"
            }
        }
    },
    "minimum-stability": "dev",
    "prefer-stable": true
}


================================================
FILE: config/filament-fabricator.php
================================================
<?php

use Z3d0X\FilamentFabricator\Models\Page;
use Z3d0X\FilamentFabricator\Resources\PageResource;

// config for Z3d0X/FilamentFabricator
return [
    'routing' => [
        /**
         * Whether routing should be automatically handled.
         * Disable if you want finer and manual control over how the routing to your pages is done.
         */
        'enabled' => true,

        /**
         * The prefix to use for all pages' routes.
         * Leave to null if you don't want them to have a prefix.
         * A prefix set to '/pages' means that a page of slug 'page-1'
         * would be accessed through `/pages/page-1` if routing is enabled.
         */
        'prefix' => null, //    /pages
    ],

    'layouts' => [
        /**
         * The base namespace for all your
         * filament-fabricator page layouts
         */
        'namespace' => 'App\\Filament\\Fabricator\\Layouts',

        /**
         * The path to your layouts (folder or glob)
         * This is used when auto-registering them
         */
        'path' => app_path('Filament/Fabricator/Layouts'),

        /**
         * A list of layout classes you want to manually register
         * in addition to those that are auto-registered
         */
        'register' => [
            //
        ],
    ],

    'page-blocks' => [
        /**
         * The base namespace for all your filament-fabricator page blocks
         */
        'namespace' => 'App\\Filament\\Fabricator\\PageBlocks',

        /**
         * The path to your blocks (folder or glob)
         * This is used when auto-registering them
         */
        'path' => app_path('Filament/Fabricator/PageBlocks'),

        /**
         * A list of block classes you want to manually register
         * in addition to those that are auto-registered
         */
        'register' => [
            //
        ],
    ],

    /**
     * The middleware(s) to apply to your pages when routing is enabled
     */
    'middleware' => [
        'web',
    ],

    /**
     * The page model to be used by the package.
     * Replace this if you ever extend it
     */
    'page-model' => Page::class,

    /**
     * The page filament resource to be used by the package.
     * Replace this if you ever extend it
     */
    'page-resource' => PageResource::class,

    /**
     * Whether you want to have a view page as part of your PageResource
     */
    'enable-view-page' => false,

    /**
     * Whether to hook into artisan's core commands to clear and refresh page route caches along with the rest.
     * Disable for manual control over cache.
     *
     * This is the list of commands that will be hooked into:
     *  - cache:clear        -> clear routes cache
     *  - config:cache       -> refresh routes cache
     *  - config:clear       -> clear routes cache
     *  - optimize           -> refresh routes cache
     *  - optimize:clear     -> clear routes cache
     *  - route:clear        -> clear routes cache
     */
    'hook-to-commands' => true,

    /**
     * This is the name of the table that will be created by the migration and
     * used by the above page-model shipped with this package.
     */
    'table_name' => 'pages',
];


================================================
FILE: database/factories/ModelFactory.php
================================================
<?php

namespace Z3d0X\FilamentFabricator\Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;

/*
class ModelFactory extends Factory
{
    protected $model = YourModel::class;

    public function definition()
    {
        return [

        ];
    }
}
*/


================================================
FILE: database/migrations/create_pages_table.php.stub
================================================
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up()
    {
        Schema::create(config('filament-fabricator.table_name', 'pages'), function (Blueprint $table) {
            $table->id();
            $table->string('title')->index();
            $table->string('slug')->unique();
            $table->string('layout')->index();
            $table->json('blocks');
            $table->foreignId('parent_id')->nullable()->constrained(config('filament-fabricator.table_name', 'pages'))->cascadeOnDelete()->cascadeOnUpdate();
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::dropIfExists(config('filament-fabricator.table_name', 'pages'));
    }
};


================================================
FILE: database/migrations/fix_slug_unique_constraint_on_pages_table.php.stub
================================================
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up()
    {
        Schema::table(config('filament-fabricator.table_name', 'pages'), function (Blueprint $table) {
            $table->dropUnique(['slug']);
            $table->unique(['slug', 'parent_id']);
        });
    }

    public function down()
    {
        Schema::table(config('filament-fabricator.table_name', 'pages'), function (Blueprint $table) {
            $table->dropUnique(['slug', 'parent_id']);
            $table->unique(['slug']);
        });
    }
};


================================================
FILE: docs/README.md
================================================
## Introduction

<p align="center" class="filament-hidden">
  <img alt="fabricator banner" src="https://raw.githubusercontent.com/z3d0x/filament-fabricator/2.x/art/banner.jpg" />
</p>

**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).


## Screenshots

<img alt="fabricator-index" src="https://raw.githubusercontent.com/z3d0x/filament-fabricator/2.x/art/list-screenshot.png">
<img alt="fabricator-edit-1" src="https://raw.githubusercontent.com/z3d0x/filament-fabricator/2.x/art/edit-screenshot-1.png">
<img alt="fabricator-edit-2" src="https://raw.githubusercontent.com/z3d0x/filament-fabricator/2.x/art/edit-screenshot-2.png">

## Installation

Once you have [Filament Panels](https://filamentphp.com/docs/3.x/panels/installation) configured. You can install this package via composer:
```bash
composer require z3d0x/filament-fabricator
```

After that run the install command: (this will publish the config & migrations)
```bash
php artisan filament-fabricator:install
```
Register a `FilamentFabricatorPlugin` instance in your Panel provider:

```php
use Z3d0X\FilamentFabricator\FilamentFabricatorPlugin;

//..

public function panel(Panel $panel): Panel
{
    return $panel
        // ...
        ->plugins([
            FilamentFabricatorPlugin::make(),
        ]);
}
```

Then, publish the registered plugin assets:

```
php artisan filament:assets
```


To get started create a [Layout](#layouts) and then [Page Blocks](#page-blocks)

## Layouts

### Creating a Layout

Use the following command to create a new Layout:
```bash
php artisan filament-fabricator:layout DefaultLayout
```

This will create the following Layout class:
```php
use Z3d0X\FilamentFabricator\Layouts\Layout;

class DefaultLayout extends Layout
{
    protected static ?string $name = 'default';
}
```

and its corresponding blade component:
```blade
@props(['page'])
<x-filament-fabricator::layouts.base :title="$page->title">
    {{-- Header Here --}}

    <x-filament-fabricator::page-blocks :blocks="$page->blocks" />

     {{-- Footer Here --}}
</x-filament-fabricator::layouts.base>
```
You 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

> Pro Tip 💡:  Use the `$page` instance to build your layout

### Base Layouts

You 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:

```php
use Z3d0X\FilamentFabricator\Facades\FilamentFabricator;
use Illuminate\Foundation\Vite;

//Add custom tags (like `<meta>` & `<link>`)
FilamentFabricator::pushMeta([
    new HtmlString('<link rel="manifest" href="/site.webmanifest" />'),
]);

//Register scripts
FilamentFabricator::registerScripts([
    'https://unpkg.com/browse/tippy.js@6.3.7/dist/tippy.esm.js', //external url
    mix('js/app.js'), //laravel-mix
    app(Vite::class)('resources/css/app.js'), //vite
    asset('js/app.js'), // asset from public folder
]);

//Register styles
FilamentFabricator::registerStyles([
    'https://unpkg.com/tippy.js@6/dist/tippy.css', //external url
    mix('css/app.css'), //laravel-mix
    app(Vite::class)('resources/css/app.css'), //vite
    asset('css/app.css'), // asset from public folder
]);

FilamentFabricator::favicon(asset('favicon.ico'));
```

Apart 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:
- `filament-fabricator::head.start` - after `<head>`
- `filament-fabricator::head.end` - before `</head>`
- `filament-fabricator::body.start` - after `<body>`
- `filament-fabricator::body.end` - before `</body>`
- `filament-fabricator::scripts.start` - before scripts are defined
- `filament-fabricator::scripts.end` - after scripts are defined

> 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.

> Pro Tip 💡:  You might prefer using the corresponding constants defined in `\Z3d0X\FilamentFabricator\View\LayoutRenderHook` instead of hard-coded strings.

## Page Blocks

### Creating a Page Block

Use the following command to create a new Page Block:
```bash
php artisan filament-fabricator:block MyBlock
```
This will create the following Page Block class (& its corresponding blade component view):
```php
use Filament\Forms\Components\Builder\Block;
use Z3d0X\FilamentFabricator\PageBlocks\PageBlock;

class MyBlock extends PageBlock
{
    protected static string $name = 'my-block';

    public static function defineBlock(Block $block): Block
    {
        return block
            ->schema([
                //
            ]);
    }

    public static function mutateData(array $data): array
    {
        return $data;
    }
}
```

> 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)
> ```blade
> {{-- `my-block.blade.php` --}}
> @aware(['page']) // make sure this line exists, in order to access `$page`
>
> @dump($page)
> ```

### Page Block Schema

Define your block schema in this method:
```php
public static function defineBlock(Block $block): Block
```
You may use any [Fields](https://filamentphp.com/docs/3.x/forms/fields/getting-started#available-fields) to make up your schema.

> Pro Tip 💡:  You can conditionally allow blocks based on a layout using:
> ```php
> Block::make('foo')
>     ->visible(fn ($get) => $get('../layout') == 'special')
> ```

### Mutate Data

By default, your blade component will receive raw data from all the fields as props

Example:
```php
//Given the following schema
public static function defineBlock(Block $block): Block
{
    return block
        ->schema([
            TextInput::make('name'),
        ]);
}
```
```blade
{{-- Your blade component would receive the following props --}}
@dump($name)
```

However you may customize this behavior using:
```php
//`$data` is the raw block data.
public static function mutateData(array $data): array
```
The array keys from this would be your blade component props.

Example:
```php
// `MyBlock.php`
public static function mutateData(array $data): array
{
    return ['foo' => 'bar'];
}
```
```blade
{{--- `my-block.blade.php` --}}
@dump($foo) // 'bar'
```

### Preload data

In some cases, you might want to preload some data for your blocks before mutating the data and then rendering it.

This is something you can do on a block type/class level:

```php
/**
 * Hook used to mass-preload related data to reduce the number of DB queries.
 * For instance, to load model objects/data from their IDs
 *
 * @param  (array{
 *     type: string,
 *     data: array,
 * })[]  $blocks  - The array of blocks' data for the given page and the given block type
 */
public static function preloadRelatedData(Page $page, array &$blocks): void
```

Note that your preload logic is run once per block type/class. It helps avoid N+1 query problems.

You 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.

It can be useful, for instance, when you want to preload related models based on an array of IDs.

For instance:
```php
use App\Models\SomeModel;
use Z3d0X\FilamentFabricator\Helpers;

// [...]

/**
 * @param  (array{
 *     type: string,
 *     data: array{
 *          title: string,
 *          items: array{
 *              title: string,
 *              ref: int,
 *          }[]
 *     },
 * })[]  $blocks  - The array of blocks' data for the given page and the given block type
 */
#[\Override]
public static function preloadRelatedData(Page $page, array &$blocks): void {
    Helpers::preloadRelatedModels(
        blocks: $blocks,
        property: 'items',
        subProperty: 'ref',
        modelClass: SomeModel::class,
    );

    // now $blocks[0]['data']['items'][0]['ref'] is the related instance of SomeModel
}
```

## Page Builder

Underneath 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:
```php
use Z3d0X\FilamentFabricator\Forms\Components\PageBuilder;

PageBuilder::configureUsing(function (PageBuilder $builder) {
    $builder->collapsible(); //You can use any method supported by the Builder field
});
```

### Block Picker Styles

In 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()`.
Currently there are two styles available:
- `BlockPickerStyle::Dropdown` (default)
- `BlockPickerStyle::Modal`


```php
use Z3d0X\FilamentFabricator\Enums\BlockPickerStyle;
use Z3d0X\FilamentFabricator\Forms\Components\PageBuilder;

PageBuilder::configureUsing(function (PageBuilder $builder) {
    $builder->blockPickerStyle(BlockPickerStyle::Modal);
});
```

an alternative one-liner way of changing block picker style is using `blockPickerStyle()` method when registering the `FilamentFabricatorPlugin` in your Panel provider:

```php
use Z3d0X\FilamentFabricator\Enums\BlockPickerStyle;
use Z3d0X\FilamentFabricator\FilamentFabricatorPlugin;

//..

public function panel(Panel $panel): Panel
{
    return $panel
        // ...
        ->plugins([
            FilamentFabricatorPlugin::make()
                ->blockPickerStyle(BlockPickerStyle::Modal),
        ]);
}
```

> 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


## Page Resource

### Customize Navigation

You may use the following methods in the `boot()` of a ServiceProvider to customize the navigation item of `PageResource`
```php
use Z3d0X\FilamentFabricator\Resources\PageResource;

PageResource::navigationGroup('Blog');
PageResource::navigationSort(1);
PageResource::navigationIcon('heroicon-o-cube');
```

### Authorization

To enforce policies, after generating a policy, you would need to register `\Z3d0X\FilamentFabricator\Models\Page` to use that policy in the `AuthServiceProvider`.

```php
<?php

namespace App\Providers;

use App\Policies\PagePolicy;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Z3d0X\FilamentFabricator\Models\Page;

class AuthServiceProvider extends ServiceProvider
{
    protected $policies = [
        Page::class => PagePolicy::class,
    ];
    //...
}
```
> If you are using [Shield](https://filamentphp.com/plugins/bezhansalleh-shield) just register the `PagePolicy` generated by it

## Caching

By default, routes will be cached in a lazy manner. That means that a page needs to be hit before it's cached.

If you so choose, you can also force all pages to be cached by running the following command:
```bash
php artisan filament-fabricator:clear-routes-cache
```

By running the following command instead, you'll ensure the data is fresh before it's cached:
```bash
php artisan filament-fabricator:clear-routes-cache --refresh
```

## Configuration

### Auto-routing

By default, your pages' routing will be done automatically for you so you don't have to worry about it.

To do that the package registers a fallback route which, when hit, will render your page.

If 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`.

### Route prefix

If you so desire, you can add a prefix to be used in all your pages' routes. This is used in conjunction with auto-routing.

For 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`.

> **Warning:** When changing the route prefix in the config, you'll want to run the `php artisan filament-fabricator:clear-routes-cache --refresh` command

### Hooking the route cache into Laravel's lifecycle

By default routes are properly cached, cleared, and refreshed whenever you would expect it to.

This is achieved by hooking into the following core commands:

- `cache:clear`        -> clear routes cache
- `config:cache`       -> refresh routes cache
- `config:clear`       -> clear routes cache
- `optimize`           -> refresh routes cache
- `optimize:clear`     -> clear routes cache
- `route:clear`        -> clear routes cache

If 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.


## Compatibility
| Fabricator | Filament | PHP |
|------|----------|--------|
| [1.x](https://github.com/z3d0x/filament-fabricator/tree/1.x) | ^2.0 | ^8.0 |
| [2.x](https://github.com/z3d0x/filament-fabricator/tree/2.x) | ^3.0 | ^8.1 |
| [3.x](https://github.com/z3d0x/filament-fabricator/tree/3.x) | ^4.0 | ^8.2 |


================================================
FILE: phpstan-baseline.neon
================================================


================================================
FILE: phpstan.neon.dist
================================================
includes:
    - vendor/larastan/larastan/extension.neon
    - vendor/nesbot/carbon/extension.neon
    - vendor/phpstan/phpstan-phpunit/extension.neon
    - vendor/phpstan/phpstan-phpunit/rules.neon
    - vendor/phpstan/phpstan-deprecation-rules/rules.neon

parameters:
    level: 5
    paths:
        - src
        - config
        - database
        - routes
    tmpDir: build/phpstan
    treatPhpDocTypesAsCertain: false



================================================
FILE: phpunit.xml.dist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
    backupGlobals="false"
    backupStaticAttributes="false"
    bootstrap="vendor/autoload.php"
    colors="true"
    convertErrorsToExceptions="true"
    convertNoticesToExceptions="true"
    convertWarningsToExceptions="true"
    processIsolation="false"
    stopOnFailure="false"
    executionOrder="random"
    failOnWarning="true"
    failOnRisky="true"
    failOnEmptyTestSuite="true"
    beStrictAboutOutputDuringTests="true"
    verbose="true"
    debug="true"
>
    <php>
        <env name="APP_KEY" value="base64:+1JX1LrX2QMk/Gaxv5r89uiy9JWOMKSKhUlwVrJL1A8="/>
        <env name="PHPUNIT_TELEMETRY" value="off"/>
    </php>
	<source ignoreIndirectDeprecations="true" restrictNotices="true" restrictWarnings="true">
		<include>
            <directory suffix=".php">./src</directory>
            <directory suffix=".php">./routes</directory>
        </include>
	</source>
    <testsuites>
        <testsuite name="Z3d0X Test Suite">
            <directory suffix=".test.php">tests/</directory>
        </testsuite>
    </testsuites>
    <coverage>
        <include>
            <directory suffix=".php">./src</directory>
        </include>
        <report>
            <html outputDirectory="build/coverage"/>
            <text outputFile="build/coverage.txt"/>
            <clover outputFile="build/logs/clover.xml"/>
        </report>
    </coverage>
    <logging>
        <junit outputFile="build/report.junit.xml"/>
    </logging>
</phpunit>


================================================
FILE: pint.json
================================================
{
    "preset": "laravel",
    "rules": {
        "blank_line_before_statement": true,
        "concat_space": {
            "spacing": "one"
        },
        "method_argument_space": true,
        "single_trait_insert_per_statement": true
    }
}


================================================
FILE: rector.php
================================================
<?php

declare(strict_types=1);

use Rector\CodeQuality\Rector\Empty_\SimplifyEmptyCheckOnEmptyArrayRector;
use Rector\CodeQuality\Rector\FunctionLike\SimplifyUselessVariableRector;
use Rector\Config\RectorConfig;
use Rector\DeadCode\Rector\If_\RemoveAlwaysTrueIfConditionRector;
use Rector\DeadCode\Rector\Stmt\RemoveUnreachableStatementRector;
use Rector\Php74\Rector\Closure\ClosureToArrowFunctionRector;
use Rector\Php83\Rector\ClassMethod\AddOverrideAttributeToOverriddenMethodsRector;
use Rector\Strict\Rector\Empty_\DisallowedEmptyRuleFixerRector;
use Rector\TypeDeclaration\Rector\Property\TypedPropertyFromStrictConstructorRector;
use RectorLaravel\Set\LaravelLevelSetList;
use RectorLaravel\Set\LaravelSetProvider;

return RectorConfig::configure()
    ->withPaths([
        __DIR__ . '/bootstrap',
        __DIR__ . '/config',
        __DIR__ . '/routes',
        __DIR__ . '/src',
        __DIR__ . '/tests',
    ])
    ->withCache()
    ->withParallel()
    ->withRules([
        AddOverrideAttributeToOverriddenMethodsRector::class,
        TypedPropertyFromStrictConstructorRector::class,
    ])
    ->withSkip([
        // Only add classes that give false positives here
        ClosureToArrowFunctionRector::class,
        DisallowedEmptyRuleFixerRector::class,
        RemoveAlwaysTrueIfConditionRector::class,
        RemoveUnreachableStatementRector::class,
        SimplifyEmptyCheckOnEmptyArrayRector::class,
        SimplifyUselessVariableRector::class,
    ])
    ->withPhpSets()
    ->withPreparedSets(
        deadCode: true,
        codeQuality: true,
        earlyReturn: true,
    )
    ->withSetProviders(LaravelSetProvider::class)
    ->withComposerBased(laravel: true, phpunit: true)
    ->withSets([
        LaravelLevelSetList::UP_TO_LARAVEL_110,
    ]);


================================================
FILE: resources/lang/ar/page-resource.php
================================================
<?php

return [
    'labels' => [
        'blocks' => 'الوحدات',
        'layout' => 'المخطط',
        'page' => 'صفحة',
        'pages' => 'صفحات',
        'parent' => 'الرئيسي',
        'slug' => 'الرابط المميز',
        'title' => 'العنوان',
        'url' => 'الرابط',
    ],

    'errors' => [
        'slug_starts_or_ends_with_slash' => 'الرابط المميز لا يمكن أن يبدأ أو ينتهي بشرطة مائلة.',
    ],

    'actions' => [
        'save' => 'حفظ',
        'visit' => 'زيارة',
    ],
];


================================================
FILE: resources/lang/en/page-resource.php
================================================
<?php

return [
    'labels' => [
        'blocks' => 'Blocks',
        'layout' => 'Layout',
        'page' => 'Page',
        'pages' => 'Pages',
        'parent' => 'Parent',
        'slug' => 'Slug',
        'title' => 'Title',
        'url' => 'URL',
    ],

    'errors' => [
        'slug_starts_or_ends_with_slash' => 'The slug cannot start or end with a slash.',
    ],

    'actions' => [
        'save' => 'Save',
        'visit' => 'Visit',
    ],
];


================================================
FILE: resources/lang/fr/page-resource.php
================================================
<?php

return [
    'labels' => [
        'blocks' => 'Blocs',
        'layout' => 'Mise en page',
        'page' => 'Page',
        'pages' => 'Pages',
        'parent' => 'Parent',
        'slug' => 'Slug',
        'title' => 'Titre',
        'url' => 'URL',
    ],

    'errors' => [
        'slug_starts_or_ends_with_slash' => 'Le slug ne peut pas commencer ou se terminer par un slash.',
    ],

    'actions' => [
        'save' => 'Enregistrer',
        'visit' => 'Visiter',
    ],
];


================================================
FILE: resources/lang/id/page-resource.php
================================================
<?php

return [
    'labels' => [
        'blocks' => 'Blok',
        'layout' => 'Tata Letak',
        'page' => 'Halaman',
        'pages' => 'Halaman-halaman',
        'parent' => 'Induk',
        'slug' => 'Slug',
        'title' => 'Judul',
        'url' => 'URL',
    ],

    'errors' => [
        'slug_starts_or_ends_with_slash' => 'Slug tidak boleh diawali atau diakhiri dengan garis miring.',
    ],

    'actions' => [
        'save' => 'Simpan',
        'visit' => 'Kunjungi',
    ],
];


================================================
FILE: resources/lang/nl/page-resource.php
================================================
<?php

return [
    'labels' => [
        'blocks' => 'Blokken',
        'layout' => 'Ontwerp',
        'page' => 'Pagina',
        'pages' => 'Pagina\'s',
        'parent' => 'Bovenliggend',
        'slug' => 'Slug',
        'title' => 'Titel',
        'url' => 'URL',
    ],

    'errors' => [
        'slug_starts_or_ends_with_slash' => 'De slug mag niet beginnen of eindigen met een schuine streep.',
    ],

    'actions' => [
        'save' => 'Opslaan',
        'visit' => 'Bezoeken',
    ],
];


================================================
FILE: resources/lang/pl/page-resource.php
================================================
<?php

return [
    'labels' => [
        'blocks' => 'Bloki',
        'layout' => 'Układ',
        'page' => 'Strona',
        'pages' => 'Strony',
        'parent' => 'Element nadrzędny',
        'slug' => 'Slug',
        'title' => 'Tytuł',
        'url' => 'URL',
    ],

    'errors' => [
        'slug_starts_or_ends_with_slash' => 'Slug nie może zaczynać się ani kończyć znakiem „/”.',
    ],

    'actions' => [
        'save' => 'Zapisz',
        'visit' => 'Odwiedź',
    ],
];


================================================
FILE: resources/lang/ru/page-resource.php
================================================
<?php

return [
    'labels' => [
        'blocks' => 'Блоки',
        'layout' => 'Макет',
        'page' => 'Страница',
        'pages' => 'Страницы',
        'parent' => 'Родительская страница',
        'slug' => 'Ссылка',
        'title' => 'Название',
        'url' => 'URL',
    ],

    'errors' => [
        'slug_starts_or_ends_with_slash' => 'Ссылка не может начинаться или заканчиваться косой чертой.',
    ],

    'actions' => [
        'save' => 'Сохранить',
        'visit' => 'Посетить',
    ],
];


================================================
FILE: resources/lang/tr/page-resource.php
================================================
<?php

return [
    'labels' => [
        'blocks' => 'Bloklar',
        'layout' => 'Düzen',
        'page' => 'Sayfa',
        'pages' => 'Sayfalar',
        'parent' => 'Üst',
        'slug' => 'Kısa Ad',
        'title' => 'Başlık',
        'url' => 'URL',
    ],

    'errors' => [
        'slug_starts_or_ends_with_slash' => 'Kısa ad bir eğik çizgi ile başlayamaz veya bitirilemez.',
    ],

    'actions' => [
        'save' => 'Kaydet',
        'visit' => 'Siteyi Aç',
    ],
];


================================================
FILE: resources/lang/uk/page-resource.php
================================================
<?php

return [
    'labels' => [
        'blocks' => 'Блоки',
        'layout' => 'Макет',
        'page' => 'Сторінка',
        'pages' => 'Сторінки',
        'parent' => 'Батьківська сторінка',
        'slug' => 'Посилання',
        'title' => 'Назва',
        'url' => 'URL',
    ],

    'errors' => [
        'slug_starts_or_ends_with_slash' => 'Посилання не може починатися або закінчуватися косою межею.',
    ],

    'actions' => [
        'save' => 'Зберегти',
        'visit' => 'Відвідати',
    ],
];


================================================
FILE: resources/views/components/forms/components/page-builder/dropdown-block-picker.blade.php
================================================
@props([
    'action',
    'afterItem' => null,
    'blocks',
    'columns' => null,
    'key',
    'statePath',
    'trigger',
    'width' => null,
])

<x-filament::dropdown
    :width="$width"
    {{ $attributes->class(['fi-fo-builder-block-picker']) }}
>
    <x-slot name="trigger">
        {{ $trigger }}
    </x-slot>

    <x-filament::dropdown.list>
        <div class="grid gap-1 {{ $columns ? 'grid-cols-' . ($columns['default'] ?? 1) : 'grid-cols-1' }}">
            @foreach ($blocks as $block)
                @php
                    $wireClickActionArguments = ['block' => $block->getName()];

                    if (filled($afterItem)) {
                        $wireClickActionArguments['afterItem'] = $afterItem;
                    }

                    $wireClickActionArguments = \Illuminate\Support\Js::from($wireClickActionArguments);

                    $wireClickAction = "mountAction('{$action->getName()}', {$wireClickActionArguments}, { schemaComponent: '{$key}' })";
                @endphp

                <x-filament::dropdown.list.item
                    :icon="$block->getIcon()"
                    x-on:click="close"
                    :wire:click="$wireClickAction"
                >
                    {{ $block->getLabel() }}
                </x-filament::dropdown.list.item>
            @endforeach
        </div>
    </x-filament::dropdown.list>
</x-filament::dropdown>


================================================
FILE: resources/views/components/forms/components/page-builder/modal-block-picker.blade.php
================================================
@props([
    'action',
    'afterItem' => null,
    'blocks',
    'columns' => null,
    'key',
    'statePath',
    'trigger',
    'width' => null,
])

<x-filament::modal
    :width="$width"
    {{ $attributes->class(['fi-fo-builder-block-picker']) }}
>
    <x-slot name="trigger">
        <div class="flex justify-center w-full">
            {{ $trigger }}
        </div>
    </x-slot>

    <div class="grid gap-4" style="grid-template-columns: repeat({{ $columns['lg'] ?? $columns['default'] ?? 3 }}, minmax(0, 1fr));">
        @foreach ($blocks as $block)
            @php
                $wireClickActionArguments = ['block' => $block->getName()];

                if (filled($afterItem)) {
                    $wireClickActionArguments['afterItem'] = $afterItem;
                }

                $wireClickActionArguments = \Illuminate\Support\Js::from($wireClickActionArguments);

                $wireClickAction = "mountAction('{$action->getName()}', {$wireClickActionArguments}, { schemaComponent: '{$key}' })";
            @endphp


            <button
                type="button"
                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"
                x-on:click="close"
                wire:click="{{ $wireClickAction }}"
            >
                @if ($icon = $block->getIcon())
                    <x-filament::icon
                        :icon="$icon"
                        class="size-8 text-gray-400 dark:text-gray-500"
                    />
                @endif
                <div>
                    {{ $block->getLabel() }}
                </div>
            </button>
        @endforeach
    </div>
</x-filament::modal>



================================================
FILE: resources/views/components/forms/components/page-builder.blade.php
================================================
@php
    use Filament\Actions\Action;
    use Z3d0X\FilamentFabricator\Enums\BlockPickerStyle;
    use Illuminate\Support\MessageBag;
    use Illuminate\Support\ViewErrorBag;

    $containers = $getChildComponentContainers();
    $blockPickerBlocks = $getBlockPickerBlocks();
    $blockPickerColumns = $getBlockPickerColumns();
    $blockPickerWidth = $getBlockPickerWidth();
    $blockPickerStyle = $getBlockPickerStyle();

    $addAction = $getAction($getAddActionName());
    $addBetweenAction = $getAction($getAddBetweenActionName());
    $cloneAction = $getAction($getCloneActionName());
    $collapseAllAction = $getAction($getCollapseAllActionName());
    $expandAllAction = $getAction($getExpandAllActionName());
    $deleteAction = $getAction($getDeleteActionName());
    $moveDownAction = $getAction($getMoveDownActionName());
    $moveUpAction = $getAction($getMoveUpActionName());
    $reorderAction = $getAction($getReorderActionName());
    $extraItemActions = $getExtraItemActions();

    $isAddable = $isAddable();
    $isCloneable = $isCloneable();
    $isCollapsible = $isCollapsible();
    $isDeletable = $isDeletable();
    $isReorderableWithButtons = $isReorderableWithButtons();
    $isReorderableWithDragAndDrop = $isReorderableWithDragAndDrop();

    $key = $getKey();
    $statePath = $getStatePath();
@endphp

<x-dynamic-component :component="$getFieldWrapperView()" :field="$field">
    <div
        x-data="{}"
        {{
            $attributes
                ->merge($getExtraAttributes(), escape: false)
                ->class(['fi-fo-builder grid gap-y-4'])
        }}
    >
        @if ($isCollapsible && ($collapseAllAction->isVisible() || $expandAllAction->isVisible()))
            <div
                @class([
                    'flex gap-x-3',
                    'hidden' => count($containers) < 2,
                ])
            >
                @if ($collapseAllAction->isVisible())
                    <span
                        x-on:click="$dispatch('builder-collapse', '{{ $statePath }}')"
                    >
                        {{ $collapseAllAction }}
                    </span>
                @endif

                @if ($expandAllAction->isVisible())
                    <span
                        x-on:click="$dispatch('builder-expand', '{{ $statePath }}')"
                    >
                        {{ $expandAllAction }}
                    </span>
                @endif
            </div>
        @endif

        @if (count($containers))
            <ul
                x-sortable
                data-sortable-animation-duration="{{ $getReorderAnimationDuration() }}"
                wire:end.stop="{{ 'mountAction(\'reorder\', { items: $event.target.sortable.toArray() }, { schemaComponent: \'' . $key . '\' })' }}"
                class="space-y-4"
            >
                @php
                    $hasBlockLabels = $hasBlockLabels();
                    $hasBlockNumbers = $hasBlockNumbers();
                @endphp

                @foreach ($containers as $uuid => $item)
                    @php
                        $visibleExtraItemActions = array_filter(
                            $extraItemActions,
                            fn (Action $action): bool => $action(['item' => $uuid])->isVisible(),
                        );
                    @endphp

                    <li
                        wire:key="{{ $this->getId() }}.{{ $item->getStatePath() }}.{{ $field::class }}.item"
                        x-data="{
                            isCollapsed: @js($isCollapsed($item)),
                        }"
                        x-on:builder-expand.window="$event.detail === '{{ $statePath }}' && (isCollapsed = false)"
                        x-on:builder-collapse.window="$event.detail === '{{ $statePath }}' && (isCollapsed = true)"
                        x-on:expand="isCollapsed = false"
                        x-sortable-item="{{ $uuid }}"
                        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"
                        x-bind:class="{ 'fi-collapsed overflow-hidden': isCollapsed }"
                    >
                        @if ($isReorderableWithDragAndDrop || $isReorderableWithButtons || $hasBlockLabels || $isCloneable || $isDeletable || $isCollapsible || count($visibleExtraItemActions))
                            <div
                                class="fi-fo-builder-item-header flex items-center gap-x-3 overflow-hidden px-4 py-3"
                            >
                                @if ($isReorderableWithDragAndDrop || $isReorderableWithButtons)
                                    <ul class="flex items-center gap-x-3">
                                        @if ($isReorderableWithDragAndDrop)
                                            <li x-sortable-handle>
                                                {{ $reorderAction }}
                                            </li>
                                        @endif

                                        @if ($isReorderableWithButtons)
                                            <li>
                                                {{ $moveUpAction(['item' => $uuid])->disabled($loop->first) }}
                                            </li>

                                            <li>
                                                {{ $moveDownAction(['item' => $uuid])->disabled($loop->last) }}
                                            </li>
                                        @endif
                                    </ul>
                                @endif

                                @if ($hasBlockLabels)
                                    <h4
                                        @if ($isCollapsible)
                                            x-on:click.stop="isCollapsed = !isCollapsed"
                                        @endif
                                        @class([
                                            'text-sm font-medium text-gray-950 dark:text-white',
                                            'truncate' => $isBlockLabelTruncated(),
                                            'cursor-pointer select-none' => $isCollapsible,
                                        ])
                                    >
                                        {{ $item->getParentComponent()->getLabel($item->getRawState(), $uuid) }}

                                        @if ($hasBlockNumbers)
                                            {{ $loop->iteration }}
                                        @endif
                                    </h4>
                                @endif

                                @if ($isCloneable || $isDeletable || $isCollapsible || count($visibleExtraItemActions))
                                    <ul
                                        class="ms-auto flex items-center gap-x-3"
                                    >
                                        @foreach ($visibleExtraItemActions as $extraItemAction)
                                            <li>
                                                {{ $extraItemAction(['item' => $uuid]) }}
                                            </li>
                                        @endforeach

                                        @if ($isCloneable)
                                            <li>
                                                {{ $cloneAction(['item' => $uuid]) }}
                                            </li>
                                        @endif

                                        @if ($isDeletable)
                                            <li>
                                                {{ $deleteAction(['item' => $uuid]) }}
                                            </li>
                                        @endif

                                        @if ($isCollapsible)
                                            <li
                                                class="relative transition"
                                                x-on:click.stop="isCollapsed = !isCollapsed"
                                                x-bind:class="{ '-rotate-180': isCollapsed }"
                                            >
                                                <div
                                                    class="transition"
                                                    x-bind:class="{ 'opacity-0 pointer-events-none': isCollapsed }"
                                                >
                                                    {{ $getAction('collapse') }}
                                                </div>

                                                <div
                                                    class="absolute inset-0 rotate-180 transition"
                                                    x-bind:class="{ 'opacity-0 pointer-events-none': ! isCollapsed }"
                                                >
                                                    {{ $getAction('expand') }}
                                                </div>
                                            </li>
                                        @endif
                                    </ul>
                                @endif
                            </div>
                        @endif

                        <div
                            x-show="! isCollapsed"
                            class="fi-fo-builder-item-content border-t border-gray-100 p-4 dark:border-white/10"
                        >
                            {{ $item }}
                        </div>
                    </li>

                    @if (! $loop->last)
                        @if ($isAddable && $addBetweenAction->isVisible())
                            <li class="relative -top-2 !mt-0 h-0">
                                <div
                                    class="flex w-full justify-center opacity-0 transition duration-75 hover:opacity-100"
                                >
                                    <div
                                        class="fi-fo-builder-block-picker-ctn rounded-lg bg-white dark:bg-gray-900"
                                    >
                                        @if ($blockPickerStyle === BlockPickerStyle::Dropdown)
                                            <x-filament-fabricator::forms.components.page-builder.dropdown-block-picker
                                                :action="$addBetweenAction"
                                                :after-item="$uuid"
                                                :columns="$blockPickerColumns"
                                                :blocks="$blockPickerBlocks"
                                                :key="$key"
                                                :state-path="$statePath"
                                                :width="$blockPickerWidth"
                                            >
                                                <x-slot name="trigger">
                                                    {{ $addBetweenAction }}
                                                </x-slot>
                                            </x-filament-fabricator::forms.components.page-builder.dropdown-block-picker>
                                        @elseif ($blockPickerStyle === BlockPickerStyle::Modal)
                                            <x-filament-fabricator::forms.components.page-builder.modal-block-picker
                                                :action="$addBetweenAction"
                                                :after-item="$uuid"
                                                :columns="$blockPickerColumns"
                                                :blocks="$blockPickerBlocks"
                                                :key="$key"
                                                :state-path="$statePath"
                                                :width="$blockPickerWidth"
                                            >
                                                <x-slot name="trigger">
                                                    {{ $addBetweenAction }}
                                                </x-slot>
                                            </x-filament-fabricator::forms.components.page-builder.modal-block-picker>
                                        @endif
                                    </div>
                                </div>
                            </li>
                        @elseif (filled($labelBetweenItems = $getLabelBetweenItems()))
                            <li
                                class="relative border-t border-gray-200 dark:border-white/10"
                            >
                                <span
                                    class="absolute -top-3 left-3 px-1 text-sm font-medium"
                                >
                                    {{ $labelBetweenItems }}
                                </span>
                            </li>
                        @endif
                    @endif
                @endforeach
            </ul>
        @endif

        @if ($isAddable)
            @if ($blockPickerStyle === BlockPickerStyle::Dropdown)
                <x-filament-fabricator::forms.components.page-builder.dropdown-block-picker
                    :action="$addAction"
                    :blocks="$blockPickerBlocks"
                    :columns="$blockPickerColumns"
                    :key="$key"
                    :state-path="$statePath"
                    :width="$blockPickerWidth"
                    class="flex justify-center"
                >
                    <x-slot name="trigger">
                        {{ $addAction }}
                    </x-slot>
                </x-filament-fabricator::forms.components.page-builder.dropdown-block-picker>
            @elseif ($blockPickerStyle === BlockPickerStyle::Modal)
                <x-filament-fabricator::forms.components.page-builder.modal-block-picker
                    :action="$addAction"
                    :blocks="$blockPickerBlocks"
                    :columns="$blockPickerColumns"
                    :key="$key"
                    :state-path="$statePath"
                    :width="$blockPickerWidth"
                    class="flex justify-center"
                >
                    <x-slot name="trigger">
                        {{ $addAction }}
                    </x-slot>
                </x-filament-fabricator::forms.components.page-builder.modal-block-picker>
            @endif
        @endif
    </div>
</x-dynamic-component>


================================================
FILE: resources/views/components/layouts/base.blade.php
================================================
@props([
    'page',
    'title' => null,
    'dir' => 'ltr',
])

@use(Z3d0X\FilamentFabricator\View\LayoutRenderHook)

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}" dir="{{ $dir }}" class="filament-fabricator">

<head>
    {{ \Filament\Support\Facades\FilamentView::renderHook(LayoutRenderHook::HEAD_START) }}

    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="csrf-token" content="{{ csrf_token() }}">

    @foreach (\Z3d0X\FilamentFabricator\Facades\FilamentFabricator::getMeta() as $tag)
        {{ $tag }}
    @endforeach

    @if ($favicon = \Z3d0X\FilamentFabricator\Facades\FilamentFabricator::getFavicon())
        <link rel="icon" href="{{ $favicon }}">
    @endif

    <title>{{ $title ? "{$title} - " : null }} {{ config('app.name') }}</title>


    <style>
        [x-cloak=""],
        [x-cloak="x-cloak"],
        [x-cloak="1"] {
            display: none !important;
        }
    </style>


    @foreach (\Z3d0X\FilamentFabricator\Facades\FilamentFabricator::getStyles() as $name => $path)
        @if (\Illuminate\Support\Str::of($path)->startsWith('<'))
            {!! $path !!}
        @else
            <link rel="stylesheet" href="{{ $path }}" />
        @endif
    @endforeach

    {{ \Filament\Support\Facades\FilamentView::renderHook(LayoutRenderHook::HEAD_END) }}
</head>

<body class="filament-fabricator-body">
    {{ \Filament\Support\Facades\FilamentView::renderHook(LayoutRenderHook::BODY_START) }}

    {{ $slot }}

    {{ \Filament\Support\Facades\FilamentView::renderHook(LayoutRenderHook::SCRIPTS_START) }}

    @foreach (\Z3d0X\FilamentFabricator\Facades\FilamentFabricator::getScripts() as $name => $path)
        @if (\Illuminate\Support\Str::of($path)->startsWith('<'))
            {!! $path !!}
        @else
            <script defer src="{{ $path }}"></script>
        @endif
    @endforeach

    @stack('scripts')

    {{ \Filament\Support\Facades\FilamentView::renderHook(LayoutRenderHook::SCRIPTS_END) }}

    {{ \Filament\Support\Facades\FilamentView::renderHook(LayoutRenderHook::BODY_END) }}
</body>

</html>


================================================
FILE: resources/views/components/page-blocks.blade.php
================================================
@aware(['page'])
@props(['blocks' => []])

@php
    $groups = \Z3d0X\FilamentFabricator\Helpers::arrayRefsGroupBy($blocks, 'type');

    foreach ($groups as $blockType => &$group) {
        /**
         * @var class-string<\Z3d0X\FilamentFabricator\PageBlocks\PageBlock> $blockClass
         */
        $blockClass = \Z3d0X\FilamentFabricator\Facades\FilamentFabricator::getPageBlockFromName($blockType);

        if (!empty($blockClass) && $page !== null) {
            $blockClass::preloadRelatedData($page, $group);
        }
    }
@endphp

@foreach ($blocks as $blockData)
    @php
        $pageBlock = \Z3d0X\FilamentFabricator\Facades\FilamentFabricator::getPageBlockFromName($blockData['type']);
    @endphp

    @isset($pageBlock)
        <x-dynamic-component :component="$pageBlock::getComponent()" :attributes="new \Illuminate\View\ComponentAttributeBag($pageBlock::mutateData($blockData['data']))" />
    @endisset
@endforeach


================================================
FILE: resources/views/preview.blade.php
================================================
@props(['page', 'component'])
<x-dynamic-component
    :component="$component"
    :page="$page"
/>


================================================
FILE: resources/views/tests/fixtures/blade-wrapper.blade.php
================================================
<div>
    {{ $this->form }}
</div>


================================================
FILE: routes/web.php
================================================
<?php

use Illuminate\Support\Facades\Route;
use Z3d0X\FilamentFabricator\Facades\FilamentFabricator;
use Z3d0X\FilamentFabricator\Http\Controllers\PageController;

if (config('filament-fabricator.routing.enabled')) {
    Route::middleware(config('filament-fabricator.middleware') ?? [])
        ->prefix(FilamentFabricator::getRoutingPrefix())
        ->group(function () {
            Route::get('/{filamentFabricatorPage?}', PageController::class)
                ->where('filamentFabricatorPage', '.*')
                ->fallback();
        });
}


================================================
FILE: src/Commands/Aliases/MakeLayoutCommand.php
================================================
<?php

namespace Z3d0X\FilamentFabricator\Commands\Aliases;

use Z3d0X\FilamentFabricator\Commands;

/**
 * @deprecated
 * @see Commands\MakeLayoutCommand
 */
class MakeLayoutCommand extends Commands\MakeLayoutCommand
{
    protected $hidden = true;

    protected $signature = 'make:filament-fabricator-layout {name?} {--F|force}';
}


================================================
FILE: src/Commands/Aliases/MakePageBlockCommand.php
================================================
<?php

namespace Z3d0X\FilamentFabricator\Commands\Aliases;

use Z3d0X\FilamentFabricator\Commands;

/**
 * @deprecated
 * @see Commands\MakePageBlockCommand
 */
class MakePageBlockCommand extends Commands\MakePageBlockCommand
{
    protected $hidden = true;

    protected $signature = 'make:filament-fabricator-page-block {name?} {--F|force}';
}


================================================
FILE: src/Commands/ClearRoutesCacheCommand.php
================================================
<?php

namespace Z3d0X\FilamentFabricator\Commands;

use Illuminate\Console\Command;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Cache;
use Z3d0X\FilamentFabricator\Facades\FilamentFabricator;
use Z3d0X\FilamentFabricator\Models\Contracts\Page as PageContract;
use Z3d0X\FilamentFabricator\Services\PageRoutesService;

class ClearRoutesCacheCommand extends Command
{
    protected $signature = 'filament-fabricator:clear-routes-cache {--R|refresh}';

    protected $description = 'Clear the routes\' cache';

    public function __construct(protected PageRoutesService $pageRoutesService)
    {
        parent::__construct();
    }

    public function handle(): int
    {
        $shouldRefresh = (bool) $this->option('refresh');

        /**
         * @var array<array-key,PageContract&Model> $pages
         */
        $pages = FilamentFabricator::getPageModel()::query()
            ->whereNull('parent_id')
            ->with('allChildren')
            ->get();

        foreach ($pages as $page) {
            $this->clearPageCache($page, $shouldRefresh);

            if ($shouldRefresh) {
                $this->pageRoutesService->updateUrlsOf($page);
            }
        }

        return static::SUCCESS;
    }

    protected function clearPageCache(PageContract $page, bool $shouldRefresh = false)
    {
        $this->pageRoutesService->removeUrlsOf($page);
        $argSets = $page->getAllUrlCacheKeysArgs();

        foreach ($argSets as $args) {
            $key = $page->getUrlCacheKey($args);
            Cache::forget($key);

            if ($shouldRefresh) {
                // Caches the URL before returning it
                /* $noop = */ $page->getUrl($args);
            }
        }

        $childPages = $page->allChildren;

        if (filled($childPages)) {
            foreach ($childPages as $childPage) {
                $this->clearPageCache($childPage, $shouldRefresh);
            }
        }
    }
}


================================================
FILE: src/Commands/MakeLayoutCommand.php
================================================
<?php

namespace Z3d0X\FilamentFabricator\Commands;

use Filament\Support\Commands\Concerns\CanManipulateFiles;
use Illuminate\Console\Command;
use Illuminate\Support\Str;

use function Laravel\Prompts\text;

class MakeLayoutCommand extends Command
{
    use CanManipulateFiles;

    protected $signature = 'filament-fabricator:layout {name?} {--F|force}';

    protected $description = 'Create a new filament-fabricator layout';

    public function handle(): int
    {
        $layout = (string) Str::of($this->argument('name') ?? text(
            label: 'What is the layout name?',
            placeholder: 'DefaultLayout',
            required: true,
        ))
            ->trim('/')
            ->trim('\\')
            ->trim(' ')
            ->replace('/', '\\');

        $layoutClass = (string) Str::of($layout)->afterLast('\\');

        $layoutNamespace = Str::of($layout)->contains('\\') ?
            (string) Str::of($layout)->beforeLast('\\') :
            '';

        $shortName = Str::of($layout)
            ->beforeLast('Layout')
            ->explode('\\')
            ->map(fn ($segment) => Str::kebab($segment))
            ->implode('.');

        $view = Str::of($layout)
            ->beforeLast('Layout')
            ->prepend('components\\filament-fabricator\\layouts\\')
            ->explode('\\')
            ->map(fn ($segment) => Str::kebab($segment))
            ->implode('.');

        $path = app_path(
            (string) Str::of($layout)
                ->prepend('Filament\\Fabricator\\Layouts\\')
                ->replace('\\', '/')
                ->append('.php'),
        );

        $viewPath = resource_path(
            (string) Str::of($view)
                ->replace('.', '/')
                ->prepend('views/')
                ->append('.blade.php'),
        );

        $files = [$path, $viewPath];

        if (! $this->option('force') && $this->checkForCollision($files)) {
            return static::INVALID;
        }

        $this->copyStubToApp('Layout', $path, [
            'class' => $layoutClass,
            'namespace' => 'App\\Filament\\Fabricator\\Layouts' . ($layoutNamespace !== '' ? "\\{$layoutNamespace}" : ''),
            'shortName' => $shortName,
        ]);

        $this->copyStubToApp('LayoutView', $viewPath);

        $this->info("Successfully created {$layout}!");

        return static::SUCCESS;
    }
}


================================================
FILE: src/Commands/MakePageBlockCommand.php
================================================
<?php

namespace Z3d0X\FilamentFabricator\Commands;

use Filament\Support\Commands\Concerns\CanManipulateFiles;
use Illuminate\Console\Command;
use Illuminate\Support\Str;

use function Laravel\Prompts\text;

class MakePageBlockCommand extends Command
{
    use CanManipulateFiles;

    protected $signature = 'filament-fabricator:block {name?} {--F|force}';

    protected $description = 'Create a new filament-fabricator page block';

    public function handle(): int
    {
        $pageBlock = (string) Str::of($this->argument('name') ?? text(
            label: 'What is the block name?',
            placeholder: 'HeroBlock',
            required: true,
        ))
            ->trim('/')
            ->trim('\\')
            ->trim(' ')
            ->replace('/', '\\');

        $pageBlockClass = (string) Str::of($pageBlock)->afterLast('\\');

        $pageBlockNamespace = Str::of($pageBlock)->contains('\\') ?
            (string) Str::of($pageBlock)->beforeLast('\\') :
            '';

        $shortName = Str::of($pageBlock)
            ->beforeLast('Block')
            ->explode('\\')
            ->map(fn ($segment) => Str::kebab($segment))
            ->implode('.');

        $view = Str::of($pageBlock)
            ->beforeLast('Block')
            ->prepend('components\\filament-fabricator\\page-blocks\\')
            ->explode('\\')
            ->map(fn ($segment) => Str::kebab($segment))
            ->implode('.');

        $path = app_path(
            (string) Str::of($pageBlock)
                ->prepend('Filament\\Fabricator\\PageBlocks\\')
                ->replace('\\', '/')
                ->append('.php'),
        );

        $viewPath = resource_path(
            (string) Str::of($view)
                ->replace('.', '/')
                ->prepend('views/')
                ->append('.blade.php'),
        );

        $files = [$path, $viewPath];

        if (! $this->option('force') && $this->checkForCollision($files)) {
            return static::INVALID;
        }

        $this->copyStubToApp('PageBlock', $path, [
            'class' => $pageBlockClass,
            'namespace' => 'App\\Filament\\Fabricator\\PageBlocks' . ($pageBlockNamespace !== '' ? "\\{$pageBlockNamespace}" : ''),
            'shortName' => $shortName,
        ]);

        $this->copyStubToApp('PageBlockView', $viewPath);

        $this->info("Successfully created {$pageBlock}!");

        return static::SUCCESS;
    }
}


================================================
FILE: src/Enums/BlockPickerStyle.php
================================================
<?php

namespace Z3d0X\FilamentFabricator\Enums;

enum BlockPickerStyle
{
    case Dropdown;
    case Modal;
}


================================================
FILE: src/Facades/FilamentFabricator.php
================================================
<?php

namespace Z3d0X\FilamentFabricator\Facades;

use Closure;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Facade;
use Z3d0X\FilamentFabricator\FilamentFabricatorManager;
use Z3d0X\FilamentFabricator\Models\Contracts\Page as PageContract;

/**
 * @method static void registerComponent(string $class, string $baseClass)
 * @method static void registerLayout(string $layout)
 * @method static void registerPageBlock(string $pageBlock)
 * @method static void registerSchemaSlot(string $name, array|Closure $schema)
 * @method static void pushMeta(array $meta)
 * @method static void registerScripts(array $scripts)
 * @method static void registerStyles(array $styles)
 * @method static void favicon(?string $favicon)
 * @method static ?string getLayoutFromName(string $layoutName)
 * @method static ?string getPageBlockFromName(string $name)
 * @method static array getLayouts()
 * @method static string getDefaultLayoutName()
 * @method static array getPageBlocks()
 * @method static array getPageBlocksRaw()
 * @method static array|Closure getSchemaSlot(string $name)
 * @method static array getMeta()
 * @method static array getScripts()
 * @method static array getStyles()
 * @method static ?string getFavicon()
 * @method static class-string<PageContract&Model> getPageModel()
 * @method static ?string getRoutingPrefix()
 * @method static array getPageUrls()
 * @method static ?string getPageUrlFromId(int $id, bool $prefixSlash = false)
 *
 * @see FilamentFabricatorManager
 */
class FilamentFabricator extends Facade
{
    protected static function getFacadeAccessor()
    {
        return 'filament-fabricator';
    }
}


================================================
FILE: src/FilamentFabricatorManager.php
================================================
<?php

namespace Z3d0X\FilamentFabricator;

use Closure;
use Exception;
use Filament\Forms\Components\Builder\Block;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use InvalidArgumentException;
use Z3d0X\FilamentFabricator\Layouts\Layout;
use Z3d0X\FilamentFabricator\Models\Contracts\Page as PageContract;
use Z3d0X\FilamentFabricator\Models\Page;
use Z3d0X\FilamentFabricator\PageBlocks\PageBlock;
use Z3d0X\FilamentFabricator\Services\PageRoutesService;

class FilamentFabricatorManager
{
    const ID = 'filament-fabricator';

    /** @var Collection<string, class-string<PageBlock>> */
    protected Collection $pageBlocks;

    /** @var Collection<string, class-string<Layout>> */
    protected Collection $layouts;

    protected array $schemaSlot = [];

    protected array $meta = [];

    protected array $scripts = [];

    protected array $styles = [];

    protected ?string $favicon = 'favicon.ico';

    protected array $pageUrls = [];

    /**
     * @note It's only separated to not cause a major version change.
     * In the next major release, feel free to make it a constructor promoted property
     */
    protected PageRoutesService $routesService;

    public function __construct(?PageRoutesService $routesService = null)
    {
        $this->routesService = $routesService ?? resolve(PageRoutesService::class);

        /** @var Collection<string, class-string<PageBlock>> */
        $pageBlocks = collect([]);

        /** @var Collection<string, class-string<Layout>> */
        $layouts = collect([]);

        $this->pageBlocks = $pageBlocks;
        $this->layouts = $layouts;
    }

    /**
     * @param  class-string  $class
     * @param  class-string<Layout>|class-string<PageBlock>  $baseClass
     */
    public function registerComponent(string $class, string $baseClass): void
    {
        match ($baseClass) {
            Layout::class => static::registerLayout($class),
            PageBlock::class => static::registerPageBlock($class),
            default => throw new Exception('Invalid class type'),
        };
    }

    /** @param  class-string<Layout>  $layout */
    public function registerLayout(string $layout): void
    {
        if (! is_subclass_of($layout, Layout::class)) {
            throw new InvalidArgumentException("{$layout} must extend " . Layout::class);
        }

        $this->layouts->put($layout::getName(), $layout);
    }

    /** @param  class-string<PageBlock>  $pageBlock */
    public function registerPageBlock(string $pageBlock): void
    {
        if (! is_subclass_of($pageBlock, PageBlock::class)) {
            throw new InvalidArgumentException("{$pageBlock} must extend " . PageBlock::class);
        }

        $this->pageBlocks->put($pageBlock::getName(), $pageBlock);
    }

    public function registerSchemaSlot(string $name, array|Closure $schema): void
    {
        $this->schemaSlot[$name] = $schema;
    }

    public function pushMeta(array $meta): void
    {
        $this->meta = array_merge($this->meta, $meta);
    }

    public function registerScripts(array $scripts): void
    {
        $this->scripts = array_merge($this->scripts, $scripts);
    }

    public function registerStyles(array $styles): void
    {
        $this->styles = array_merge($this->styles, $styles);
    }

    public function favicon(string $favicon): void
    {
        $this->favicon = $favicon;
    }

    /**
     * @return class-string<Layout>|null
     */
    public function getLayoutFromName(string $layoutName): ?string
    {
        return $this->layouts->get($layoutName);
    }

    /**
     * @return class-string<PageBlock>|null
     */
    public function getPageBlockFromName(string $name): ?string
    {
        return $this->pageBlocks->get($name);
    }

    /**
     * Get the list of registered layout labels/names
     *
     * @return string[]
     */
    public function getLayouts(): array
    {
        return $this->layouts->map(fn ($layout) => $layout::getLabel())->toArray();
    }

    public function getDefaultLayoutName(): ?string
    {
        return $this->layouts->keys()->first();
    }

    /**
     * @return Block[]
     */
    public function getPageBlocks(): array
    {
        return $this->pageBlocks->map(fn ($block) => $block::getBlockSchema())->toArray();
    }

    public function getPageBlocksRaw(): array
    {
        return $this->pageBlocks->toArray();
    }

    public function getSchemaSlot(string $name): array|Closure
    {
        return $this->schemaSlot[$name] ?? [];
    }

    public function getMeta(): array
    {
        return array_unique($this->meta);
    }

    public function getScripts(): array
    {
        return $this->scripts;
    }

    public function getStyles(): array
    {
        return $this->styles;
    }

    public function getFavicon(): ?string
    {
        return $this->favicon;
    }

    /** @return class-string<PageContract> */
    public function getPageModel(): string
    {
        return config('filament-fabricator.page-model') ?? Page::class;
    }

    public function getRoutingPrefix(): ?string
    {
        $prefix = config('filament-fabricator.routing.prefix');

        if (is_null($prefix)) {
            return null;
        }

        $prefix = Str::start($prefix, '/');

        if ($prefix === '/') {
            return $prefix;
        }

        return rtrim($prefix, '/');
    }

    /**
     * @return string[]
     */
    public function getPageUrls(): array
    {
        return $this->routesService->getAllUrls();
    }

    public function getPageUrlFromId(int|string $id, array $args = []): ?string
    {
        /** @var (PageContract&Model)|null $page */
        $page = $this->getPageModel()::query()->find($id);

        return $page?->getUrl($args);
    }
}


================================================
FILE: src/FilamentFabricatorPlugin.php
================================================
<?php

namespace Z3d0X\FilamentFabricator;

use Closure;
use Filament\Contracts\Plugin;
use Filament\Panel;
use Pboivin\FilamentPeek\FilamentPeekPlugin;
use Z3d0X\FilamentFabricator\Enums\BlockPickerStyle;

class FilamentFabricatorPlugin implements Plugin
{
    public const ID = 'filament-fabricator';

    protected BlockPickerStyle|Closure|null $blockPickerStyle = null;

    public static function make(): static
    {
        return app(static::class);
    }

    public function getId(): string
    {
        return static::ID;
    }

    public function register(Panel $panel): void
    {
        $panel->resources(array_filter([
            config('filament-fabricator.page-resource'),
        ]));

        if (! $panel->hasPlugin(FilamentPeekPlugin::ID)) {
            // Automatically register FilamentPeekPlugin if it is not already registered
            $panel->plugin(FilamentPeekPlugin::make());
        }
    }

    public function boot(Panel $panel): void
    {
        //
    }

    public function blockPickerStyle(?BlockPickerStyle $style): static
    {
        $this->blockPickerStyle = $style;

        return $this;
    }

    public function getBlockPickerStyle(): ?BlockPickerStyle
    {
        return $this->blockPickerStyle;
    }

    public static function get(): static
    {
        /** @var static $plugin */
        $plugin = filament(app(static::class)->getId());

        return $plugin;
    }
}


================================================
FILE: src/FilamentFabricatorServiceProvider.php
================================================
<?php

namespace Z3d0X\FilamentFabricator;

use Illuminate\Console\Events\CommandFinished;
use Illuminate\Filesystem\Filesystem;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Str;
use ReflectionClass;
use Spatie\LaravelPackageTools\Commands\InstallCommand;
use Spatie\LaravelPackageTools\Package;
use Spatie\LaravelPackageTools\PackageServiceProvider;
use Symfony\Component\Finder\SplFileInfo;
use Z3d0X\FilamentFabricator\Commands\ClearRoutesCacheCommand;
use Z3d0X\FilamentFabricator\Commands\MakeLayoutCommand;
use Z3d0X\FilamentFabricator\Commands\MakePageBlockCommand;
use Z3d0X\FilamentFabricator\Facades\FilamentFabricator;
use Z3d0X\FilamentFabricator\Layouts\Layout;
use Z3d0X\FilamentFabricator\Listeners\OptimizeWithLaravel;
use Z3d0X\FilamentFabricator\Observers\PageRoutesObserver;
use Z3d0X\FilamentFabricator\PageBlocks\PageBlock;
use Z3d0X\FilamentFabricator\Services\PageRoutesService;

class FilamentFabricatorServiceProvider extends PackageServiceProvider
{
    public function configurePackage(Package $package): void
    {
        $package->name(FilamentFabricatorManager::ID)
            ->hasConfigFile()
            ->hasMigrations(
                'create_pages_table',
                'fix_slug_unique_constraint_on_pages_table',
            )
            ->hasRoute('web')
            ->hasViews()
            ->hasTranslations()
            ->hasCommands($this->getCommands())
            ->hasInstallCommand(function (InstallCommand $installCommand) {
                $installCommand
                    ->startWith(fn (InstallCommand $installCommand) => $installCommand->call('filament:upgrade'))
                    ->publishConfigFile()
                    ->publishMigrations()
                    ->askToRunMigrations()
                    ->askToStarRepoOnGitHub('z3d0x/filament-fabricator');
            });
    }

    protected function getCommands(): array
    {
        $commands = [
            MakeLayoutCommand::class,
            MakePageBlockCommand::class,
            ClearRoutesCacheCommand::class,
        ];

        $aliases = [];

        foreach ($commands as $command) {
            $class = 'Z3d0X\\FilamentFabricator\\Commands\\Aliases\\' . class_basename($command);

            if (! class_exists($class)) {
                continue;
            }

            $aliases[] = $class;
        }

        return array_merge($commands, $aliases);
    }

    public function packageRegistered(): void
    {
        parent::packageRegistered();

        $this->app->singleton('filament-fabricator', function () {
            return resolve(FilamentFabricatorManager::class);
        });
    }

    public function bootingPackage(): void
    {
        if (! $this->app->runningInConsole() || $this->app->runningUnitTests()) {
            Route::bind('filamentFabricatorPage', function ($value) {
                /**
                 * @var PageRoutesService $routesService
                 */
                $routesService = resolve(PageRoutesService::class);

                return $routesService->findPageOrFail($value);
            });

            $this->registerComponentsFromDirectory(
                Layout::class,
                config('filament-fabricator.layouts.register'),
                config('filament-fabricator.layouts.path'),
                config('filament-fabricator.layouts.namespace')
            );

            $this->registerComponentsFromDirectory(
                PageBlock::class,
                config('filament-fabricator.page-blocks.register'),
                config('filament-fabricator.page-blocks.path'),
                config('filament-fabricator.page-blocks.namespace')
            );
        }
    }

    public function packageBooted()
    {
        parent::packageBooted();

        FilamentFabricator::getPageModel()::observe(PageRoutesObserver::class);

        if ((bool) config('filament-fabricator.hook-to-commands')) {
            Event::listen(CommandFinished::class, OptimizeWithLaravel::class);
        }
    }

    /**
     * @template T of (class-string<Layout>|class-string<PageBlock>)
     *
     * @param  T  $baseClass
     * @param  T[]  $register  - The components to register taken from the user's config file
     */
    protected function registerComponentsFromDirectory(string $baseClass, array $register, ?string $directory, ?string $namespace): void
    {
        if (blank($directory) || blank($namespace)) {
            return;
        }

        $filesystem = app(Filesystem::class);

        if ((! $filesystem->exists($directory)) && (! Str::of($directory)->contains('*'))) {
            return;
        }

        $namespace = Str::of($namespace);

        collect($filesystem->allFiles($directory))
            ->lazy()
            ->map(function (SplFileInfo $file) use ($namespace): string {
                /**
                 * @var ?string $variableNamespace
                 */
                $variableNamespace = $namespace->contains('*') ? str_ireplace(
                    ['\\' . $namespace->before('*'), $namespace->after('*')],
                    ['', ''],
                    Str::of($file->getPath())
                        ->after(base_path())
                        ->replace(['/'], ['\\']),
                ) : null;

                return $namespace
                    ->append('\\', $file->getRelativePathname())
                    ->when($variableNamespace, fn ($namespace) => $namespace->replace('*', $variableNamespace))
                    ->replace(['/', '.php'], ['\\', ''])
                    ->toString();
            })
            ->concat($register)
            ->filter(fn (string $class): bool => is_subclass_of($class, $baseClass) && (! (new ReflectionClass($class))->isAbstract()))
            ->each(fn (string $class) => FilamentFabricator::registerComponent($class, $baseClass))
            ->all();
    }
}


================================================
FILE: src/Forms/Components/PageBuilder.php
================================================
<?php

namespace Z3d0X\FilamentFabricator\Forms\Components;

use Filament\Forms\Components\Builder;
use Filament\Forms\Concerns\InteractsWithForms;
use Filament\Pages\Concerns\InteractsWithFormActions;
use Z3d0X\FilamentFabricator\Enums\BlockPickerStyle;
use Z3d0X\FilamentFabricator\Facades\FilamentFabricator;
use Z3d0X\FilamentFabricator\FilamentFabricatorPlugin;

class PageBuilder extends Builder
{
    use InteractsWithFormActions;
    use InteractsWithForms;

    protected string $view = 'filament-fabricator::components.forms.components.page-builder';

    protected BlockPickerStyle $blockPickerStyle = BlockPickerStyle::Dropdown;

    protected function setUp(): void
    {
        parent::setUp();

        $this->blocks(FilamentFabricator::getPageBlocks());

        $this->mutateDehydratedStateUsing(static function (?array $state): array {
            if (! is_array($state)) {
                return [];
            }

            $registerPageBlockNames = array_keys(FilamentFabricator::getPageBlocksRaw());

            return collect($state)
                ->filter(fn (array $block) => in_array($block['type'], $registerPageBlockNames, true))
                ->values()
                ->toArray();
        });

        $blockPickerStyle = FilamentFabricatorPlugin::get()->getBlockPickerStyle();

        if (! is_null($blockPickerStyle)) {
            $this->blockPickerStyle($blockPickerStyle);
        }
    }

    public function blockPickerStyle(BlockPickerStyle $style): static
    {
        if ($style === BlockPickerStyle::Modal) {
            $this->blockPickerColumns(3);
        }

        $this->blockPickerStyle = $style;

        return $this;
    }

    public function getBlockPickerStyle(): BlockPickerStyle
    {
        return $this->blockPickerStyle;
    }
}


================================================
FILE: src/Helpers.php
================================================
<?php

namespace Z3d0X\FilamentFabricator;

use Closure;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;

abstract class Helpers
{
    /**
     * Group an array of associative arrays by a given key
     *
     * @param  array[]  $arr
     * @return array[]
     */
    public static function arrayRefsGroupBy(array &$arr, string $key): array
    {
        $ret = [];

        foreach ($arr as &$item) {
            $ret[$item[$key]][] = &$item;
        }

        return $ret;
    }

    /**
     * Helper function to make it easier to preload related models
     * via references in a block's data. Usually used inside `PageBlock::preloadRelatedData`
     *
     * @template TModel of Model
     *
     * @param array{
     *     type: string,
     *     data: array<string, mixed>,
     * }[] $blocks
     * @param  class-string<TModel>  $modelClass
     * @param  null|Closure(Builder):Builder  $editQuery
     */
    public static function preloadRelatedModels(
        array &$blocks,
        string $property,
        string $modelClass,
        ?string $subProperty = null,
        ?Closure $editQuery = null,
        string $primaryKeyColumn = 'id',
    ): void {
        $editQuery ??= fn (Builder $builder) => $builder;
        $targetsSubProperty = $subProperty !== null;

        $ids = collect($blocks)
            ->lazy()
            ->map(function ($block) use ($targetsSubProperty, $property, $subProperty) {
                $collection = collect($block['data'][$property]);

                if ($targetsSubProperty) {
                    $collection = $collection->pluck($subProperty);
                }

                return $collection->all();
            })
            ->flatten()
            ->filter()
            ->unique()
            ->toArray();

        $query = $modelClass::query()
            ->whereIn($primaryKeyColumn, $ids);

        $query = $editQuery($query);

        /**
         * @var TModel[] $models
         */
        $models = $query->get();

        $models = collect($models)->groupBy($primaryKeyColumn);

        foreach ($blocks as &$block) {
            if ($targetsSubProperty) {
                foreach ($block['data'][$property] as &$item) {
                    $rawData = $item[$subProperty];
                    $item[$subProperty] = is_array($rawData)
                        ? array_map(fn ($key) => data_get($models, (string) $key)->first(), $rawData)
                        : data_get($models, (string) $rawData)->first();
                }
            } else {
                $rawData = $block['data'][$property];
                $block['data'][$property] = is_array($rawData)
                    ? array_map(fn ($key) => data_get($models, (string) $key)->first(), $rawData)
                    : data_get($models, (string) $rawData)->first();
            }
        }
    }
}


================================================
FILE: src/Http/Controllers/PageController.php
================================================
<?php

namespace Z3d0X\FilamentFabricator\Http\Controllers;

use Exception;
use Illuminate\Support\Facades\Blade;
use Z3d0X\FilamentFabricator\Facades\FilamentFabricator;
use Z3d0X\FilamentFabricator\Layouts\Layout;
use Z3d0X\FilamentFabricator\Models\Contracts\Page;
use Z3d0X\FilamentFabricator\Services\PageRoutesService;

class PageController
{
    public function __invoke(?Page $filamentFabricatorPage = null): string
    {
        // Handle root (home) page
        if (blank($filamentFabricatorPage)) {
            /**
             * @var PageRoutesService $routesService
             */
            $routesService = resolve(PageRoutesService::class);

            /** @var Page $filamentFabricatorPage */
            $filamentFabricatorPage = $routesService->findPageOrFail('/');
        }

        /** @var ?class-string<Layout> $layout */
        $layout = FilamentFabricator::getLayoutFromName($filamentFabricatorPage->layout);

        if (! isset($layout)) {
            throw new Exception("Filament Fabricator: Layout \"{$filamentFabricatorPage->layout}\" not found");
        }

        /** @var string $component */
        $component = $layout::getComponent();

        return Blade::render(
            <<<'BLADE'
            <x-dynamic-component
                :component="$component"
                :page="$page"
            />
            BLADE,
            ['component' => $component, 'page' => $filamentFabricatorPage]
        );
    }
}


================================================
FILE: src/Layouts/Layout.php
================================================
<?php

namespace Z3d0X\FilamentFabricator\Layouts;

use Illuminate\Support\Str;

abstract class Layout
{
    protected static ?string $component = null;

    protected static ?string $name = null;

    public static function getName(): string
    {
        return static::$name;
    }

    public static function getLabel(): string
    {
        return Str::headline(static::$name);
    }

    public static function getComponent(): string
    {
        return static::$component ?? ('filament-fabricator.layouts.' . static::getName());
    }
}


================================================
FILE: src/Listeners/OptimizeWithLaravel.php
================================================
<?php

namespace Z3d0X\FilamentFabricator\Listeners;

use Illuminate\Console\Command;
use Illuminate\Console\Events\CommandFinished;
use Illuminate\Support\Facades\Artisan;
use Z3d0X\FilamentFabricator\Commands\ClearRoutesCacheCommand;

class OptimizeWithLaravel
{
    public const COMMANDS = [
        'cache:clear',
        'config:cache',
        'config:clear',
        'optimize',
        'optimize:clear',
        'route:clear',
    ];

    public const REFRESH_COMMANDS = [
        'config:cache',
        'optimize',
    ];

    public function handle(CommandFinished $event): void
    {
        if (! $this->shouldHandleEvent($event)) {
            return;
        }

        if ($this->shouldRefresh($event)) {
            $this->refresh();
        } else {
            $this->clear();
        }
    }

    public function shouldHandleEvent(CommandFinished $event)
    {
        return $event->exitCode === Command::SUCCESS
            && in_array($event->command, static::COMMANDS);
    }

    public function shouldRefresh(CommandFinished $event)
    {
        return in_array($event->command, static::REFRESH_COMMANDS);
    }

    public function refresh()
    {
        $this->callCommand([
            '--refresh' => true,
        ]);
    }

    public function clear()
    {
        $this->callCommand();
    }

    public function callCommand(array $params = [])
    {
        Artisan::call(ClearRoutesCacheCommand::class, $params);
    }
}


================================================
FILE: src/Models/Concerns/HandlesPageUrls.php
================================================
<?php

namespace Z3d0X\FilamentFabricator\Models\Concerns;

use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Str;
use Z3d0X\FilamentFabricator\Facades\FilamentFabricator;
use Z3d0X\FilamentFabricator\Models\Contracts\Page;

trait HandlesPageUrls
{
    /**
     * Get the default arguments for URL generation
     */
    public function getDefaultUrlCacheArgs(): array
    {
        return [];
    }

    /**
     * Get the cache key for the URL determined by this entity and the provided arguments
     *
     * @param  array<string, mixed>  $args
     */
    public function getUrlCacheKey(array $args = []): string
    {
        // $keyArgs = collect($this->getDefaultUrlArgs())->merge($args)->all();
        $id = $this->id;

        return "filament-fabricator::page-url--$id";
    }

    /**
     * Get the URL determined by this entity and the provided arguments
     *
     * @param  array<string, mixed>  $args
     */
    public function getUrl(array $args = []): string
    {
        $cacheKey = $this->getUrlCacheKey($args);

        // NOTE: Users must run the command that clears the routes cache if the routing prefix ever changes

        return Cache::rememberForever($cacheKey, function () use ($args) {
            /**
             * @var ?Page $parent
             */
            $parent = $this->parent;

            // If there's no parent page, then the "parent" URI is just the routing prefix.
            $parentUri = is_null($parent) ? (FilamentFabricator::getRoutingPrefix() ?? '/') : $parent->getUrl($args);

            // Every URI in cache has a leading slash, this ensures it's
            // present even if the prefix doesn't have it set explicitly
            $parentUri = Str::start($parentUri, '/');

            // This page's part of the URL (i.e. its URI) is defined as the slug.
            // For the same reasons as above, we need to add a leading slash.
            $selfUri = $this->slug;
            $selfUri = Str::start($selfUri, '/');

            // If the parent URI is the root, then we have nothing to glue on.
            // Therefore the page's URL is simply its URI.
            // This avoids having two consecutive slashes.
            if ($parentUri === '/') {
                return $selfUri;
            }

            // Remove any trailing slash in the parent URI since
            // every URIs we'll use has a leading slash.
            // This avoids having two consecutive slashes.
            $parentUri = rtrim($parentUri, '/');

            return "{$parentUri}{$selfUri}";
        });
    }

    /**
     * Get all the available argument sets for the available cache keys
     *
     * @return array<string, mixed>[]
     */
    public function getAllUrlCacheKeysArgs(): array
    {
        // By default, the entire list of available URL cache keys
        // is simply a list containing the default one since we can't
        // magically infer all the possible state for the library user's customizations.
        return [
            $this->getDefaultUrlCacheArgs(),
        ];
    }

    /**
     * Get all the available URLs for this entity
     *
     * @return string[]
     */
    public function getAllUrls(): array
    {
        return array_map([$this, 'getUrl'], $this->getAllUrlCacheKeysArgs());
    }

    /**
     * Get all the cache keys for the available URLs for this entity
     *
     * @return string[]
     */
    public function getAllUrlCacheKeys(): array
    {
        return array_map([$this, 'getUrlCacheKey'], $this->getAllUrlCacheKeysArgs());
    }
}


================================================
FILE: src/Models/Contracts/HasPageUrls.php
================================================
<?php

namespace Z3d0X\FilamentFabricator\Models\Contracts;

interface HasPageUrls
{
    /**
     * Get the default arguments for URL generation
     *
     * @invariant HasPageUrls#getDefaultUrlCacheArgs() must always return the same value, regardless of app/user configuration
     */
    public function getDefaultUrlCacheArgs(): array;

    /**
     * Get the cache key for the URL determined by this entity and the provided arguments
     */
    public function getUrlCacheKey(array $args = []): string;

    /**
     * Get the URL determined by this entity and the provided arguments
     */
    public function getUrl(array $args = []): string;

    /**
     * Get all the available argument sets for the available cache keys
     *
     * @return array[]
     */
    public function getAllUrlCacheKeysArgs(): array;

    /**
     * Get all the available URLs for this entity
     *
     * @return string[]
     */
    public function getAllUrls(): array;

    /**
     * Get all the cache keys for the available URLs for this entity
     *
     * @return string[]
     */
    public function getAllUrlCacheKeys(): array;
}


================================================
FILE: src/Models/Contracts/Page.php
================================================
<?php

namespace Z3d0X\FilamentFabricator\Models\Contracts;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Carbon;

/**
 * @phpstan-require-extends Model
 *
 * @property-read int|string $id
 * @property-read string $title
 * @property-read string $slug
 * @property-read string $layout
 * @property-read array $blocks
 * @property-read int|string|null $parent_id
 * @property-read static|null $parent
 * @property-read Collection<array-key, static&Model> $children
 * @property-read Collection<array-key, static&Model> $allChildren
 * @property-read Carbon $created_at
 * @property-read Carbon $updated_at
 */
interface Page extends HasPageUrls
{
    public function parent(): BelongsTo;

    public function children(): HasMany;

    public function allChildren(): HasMany;

    /** @return Builder */
    public static function query();
}


================================================
FILE: src/Models/Page.php
================================================
<?php

namespace Z3d0X\FilamentFabricator\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Z3d0X\FilamentFabricator\Models\Concerns\HandlesPageUrls;
use Z3d0X\FilamentFabricator\Models\Contracts\Page as Contract;

class Page extends Model implements Contract
{
    use HandlesPageUrls;

    public function __construct(array $attributes = [])
    {
        if (blank($this->table)) {
            $this->setTable(config('filament-fabricator.table_name', 'pages'));
        }

        parent::__construct($attributes);
    }

    protected $guarded = [];

    public function parent(): BelongsTo
    {
        return $this->belongsTo(static::class, 'parent_id');
    }

    public function children(): HasMany
    {
        return $this->hasMany(static::class, 'parent_id');
    }

    public function allChildren(): HasMany
    {
        return $this->children()
            ->select('id', 'slug', 'title', 'parent_id')
            ->with('allChildren:id,slug,title,parent_id');
    }

    protected function casts(): array
    {
        return array_merge(parent::casts(), [
            'blocks' => 'array',
            'parent_id' => 'integer',
        ]);
    }
}


================================================
FILE: src/Observers/PageRoutesObserver.php
================================================
<?php

namespace Z3d0X\FilamentFabricator\Observers;

use Illuminate\Database\Eloquent\Model;
use Z3d0X\FilamentFabricator\Models\Contracts\Page as PageContract;
use Z3d0X\FilamentFabricator\Services\PageRoutesService;

class PageRoutesObserver
{
    public function __construct(
        protected PageRoutesService $pageRoutesService
    ) {}

    /**
     * Handle the Page "created" event.
     */
    public function created(PageContract&Model $page): void
    {
        // Creating the page simply requires setting the URLs in all caches.
        // This will be done properly through the update procedure.
        $this->pageRoutesService->updateUrlsOf($page);
    }

    /**
     * Handle the Page "updated" event.
     */
    public function updated(PageContract&Model $page): void
    {
        // If the parent_id has changed, and if the relationship has already been loaded
        // then after an update we might not read the right parent. That's why we always
        // load it on update, this ensures we clear the old URLs properly (they were cached)
        // and set the new ones properly (we have the right parent to do so).
        if ($page->wasChanged('parent_id')) {
            $page->load('parent');
        }

        $this->pageRoutesService->updateUrlsOf($page);
    }

    /**
     * Handle the Page "deleting" event.
     */
    public function deleting(PageContract&Model $page): void
    {
        // We do the logic in `deleting` instead of `deleted` since we need access to the object
        // both in memory and in database (e.g. to load relationship data).

        // Before properly deleting it, remove its URLs from
        // all the mappings and caches.
        $this->pageRoutesService->removeUrlsOf($page);

        // Doubly-linked list style deletion:
        //      - Re-attache the given page children to the parent of the given page
        //      - Promote the pages to a "root page" (i.e. page with no parent) if the given page had no parent

        // Only load one level of children since they're the ones that will be re-attached
        $page->load('children');
        $children = $page->children;

        foreach ($children as $childPage) {
            /**
             * @var Model|PageContract $childPage
             */

            // We use `?? null` followed by `?: null` to go around the cast to integer
            // and make sure we have `null` instead of `0` when there's no parent.
            $parentId = $page->parent_id ?? null;
            $parentId = $parentId ?: null;

            // Using update, instead of associate or dissociate, we trigger DB events (which we need)
            $childPage->update([
                'parent_id' => $parentId,
            ]);
        }
    }

    /**
     * Handle the Page "deleted" event.
     */
    public function deleted(PageContract&Model $page): void
    {
        // Since `deleting` is called before `deleted`, and
        // since everything is handled there, do nothing.
    }

    /**
     * Handle the Page "restored" event.
     */
    public function restored(PageContract&Model $page): void
    {
        $this->pageRoutesService->updateUrlsOf($page);
    }

    /**
     * Handle the Page "force deleted" event.
     */
    public function forceDeleting(PageContract&Model $page): void
    {
        // You always go through `deleting` before going through `forceDeleting`.
        // Since everything is properly handled in `deleting`, do nothing here.
    }

    /**
     * Handle the Page "force deleted" event.
     */
    public function forceDeleted(PageContract&Model $page): void
    {
        // You always go through `deleted` before going through `forceDeleted`.
        // Since everything is properly handled in `deleted`, do nothing here.
    }
}


================================================
FILE: src/PageBlocks/PageBlock.php
================================================
<?php

namespace Z3d0X\FilamentFabricator\PageBlocks;

use Filament\Forms\Components\Builder\Block;
use Z3d0X\FilamentFabricator\Models\Contracts\Page;

abstract class PageBlock
{
    protected static string $name = '';

    protected static ?string $component = null;

    /**
     * Create a minimally pre-configured {@link Block} instance for this {@link PageBlock}
     */
    protected static function createNewBlock(): Block
    {
        return Block::make(static::getName());
    }

    /**
     * Get a fully configured {@link Block} instance for this {@link PageBlock}
     */
    public static function getBlockSchema(): Block
    {
        return static::defineBlock(
            static::createNewBlock(),
        );
    }

    /**
     * Configure the given {@link Block} instance for this {@link PageBlock}
     *
     * @param  $block  - The block instance to configure
     */
    abstract public static function defineBlock(Block $block): Block;

    public static function getComponent(): string
    {
        return static::$component ?? ('filament-fabricator.page-blocks.' . static::getName());
    }

    /**
     * The unique identifier name used to refer to this {@link PageBlock} type
     */
    public static function getName(): string
    {
        return static::$name;
    }

    public static function mutateData(array $data): array
    {
        return $data;
    }

    /**
     * Hook used to mass-preload related data to reduce the number of DB queries.
     * For instance, to load model objects/data from their IDs
     *
     * @param  (array{
     *     type: string,
     *     data: array<string, mixed>,
     * })[]  $blocks  - The array of blocks' data for the given page and the given block type
     */
    public static function preloadRelatedData(Page $page, array &$blocks): void {}
}


================================================
FILE: src/Resources/PageResource/Pages/Concerns/HasPreviewModal.php
================================================
<?php

namespace Z3d0X\FilamentFabricator\Resources\PageResource\Pages\Concerns;

use Exception;
use Pboivin\FilamentPeek\Pages\Concerns\HasPreviewModal as BaseHasPreviewModal;
use Z3d0X\FilamentFabricator\Facades\FilamentFabricator;

trait HasPreviewModal
{
    use BaseHasPreviewModal;

    protected function getPreviewModalView(): ?string
    {
        return 'filament-fabricator::preview';
    }

    protected function getPreviewModalDataRecordKey(): ?string
    {
        return 'page';
    }

    protected function mutatePreviewModalData(array $data): array
    {
        $layoutName = $this->data['layout'] ?? null;
        if (empty($layoutName)) {
            return [];
        }

        $layout = FilamentFabricator::getLayoutFromName($layoutName);

        if (empty($layout)) {
            throw new Exception("Filament Fabricator: Layout \"{$layoutName}\" not found");
        }

        /** @var string $component */
        $component = $layout::getComponent();

        $data['component'] = $component;

        return $data;
    }
}


================================================
FILE: src/Resources/PageResource/Pages/CreatePage.php
================================================
<?php

namespace Z3d0X\FilamentFabricator\Resources\PageResource\Pages;

use Filament\Resources\Pages\CreateRecord;
use Pboivin\FilamentPeek\Pages\Actions\PreviewAction;
use Z3d0X\FilamentFabricator\Resources\PageResource;
use Z3d0X\FilamentFabricator\Resources\PageResource\Pages\Concerns\HasPreviewModal;

class CreatePage extends CreateRecord
{
    use HasPreviewModal;

    protected static string $resource = PageResource::class;

    public static function getResource(): string
    {
        return config('filament-fabricator.page-resource') ?? static::$resource;
    }

    protected function getActions(): array
    {
        return [
            PreviewAction::make(),
        ];
    }
}


================================================
FILE: src/Resources/PageResource/Pages/EditPage.php
================================================
<?php

namespace Z3d0X\FilamentFabricator\Resources\PageResource\Pages;

use Filament\Actions\Action;
use Filament\Actions\DeleteAction;
use Filament\Actions\ViewAction;
use Filament\Resources\Pages\EditRecord;
use Illuminate\Database\Eloquent\Model;
use Pboivin\FilamentPeek\Pages\Actions\PreviewAction;
use Z3d0X\FilamentFabricator\Facades\FilamentFabricator;
use Z3d0X\FilamentFabricator\Models\Contracts\Page as PageContract;
use Z3d0X\FilamentFabricator\Resources\PageResource;
use Z3d0X\FilamentFabricator\Resources\PageResource\Pages\Concerns\HasPreviewModal;

class EditPage extends EditRecord
{
    use HasPreviewModal;

    protected static string $resource = PageResource::class;

    public static function getResource(): string
    {
        return config('filament-fabricator.page-resource') ?? static::$resource;
    }

    protected function getActions(): array
    {
        return [
            PreviewAction::make(),

            ViewAction::make()
                ->visible(config('filament-fabricator.enable-view-page')),

            DeleteAction::make(),

            Action::make('visit')
                ->label(__('filament-fabricator::page-resource.actions.visit'))
                ->url(function () {
                    /** @var PageContract&Model $page */
                    $page = $this->getRecord();

                    return FilamentFabricator::getPageUrlFromId($page->id);
                })
                ->icon('heroicon-o-arrow-top-right-on-square')
                ->openUrlInNewTab()
                ->color('success')
                ->visible(config('filament-fabricator.routing.enabled')),

            Action::make('save')
                ->action('save')
                ->label(__('filament-fabricator::page-resource.actions.save')),
        ];
    }
}


================================================
FILE: src/Resources/PageResource/Pages/ListPages.php
================================================
<?php

namespace Z3d0X\FilamentFabricator\Resources\PageResource\Pages;

use Filament\Actions\CreateAction;
use Filament\Resources\Pages\ListRecords;
use Z3d0X\FilamentFabricator\Resources\PageResource;

class ListPages extends ListRecords
{
    protected static string $resource = PageResource::class;

    public static function getResource(): string
    {
        return config('filament-fabricator.page-resource') ?? static::$resource;
    }

    protected function getActions(): array
    {
        return [
            CreateAction::make(),
        ];
    }
}


================================================
FILE: src/Resources/PageResource/Pages/ViewPage.php
================================================
<?php

namespace Z3d0X\FilamentFabricator\Resources\PageResource\Pages;

use Filament\Actions\Action;
use Filament\Actions\EditAction;
use Filament\Resources\Pages\ViewRecord;
use Illuminate\Database\Eloquent\Model;
use Z3d0X\FilamentFabricator\Facades\FilamentFabricator;
use Z3d0X\FilamentFabricator\Models\Contracts\Page as PageContract;
use Z3d0X\FilamentFabricator\Resources\PageResource;

class ViewPage extends ViewRecord
{
    protected static string $resource = PageResource::class;

    public static function getResource(): string
    {
        return config('filament-fabricator.page-resource') ?? static::$resource;
    }

    protected function getActions(): array
    {
        return [
            EditAction::make(),

            Action::make('visit')
                ->label(__('filament-fabricator::page-resource.actions.visit'))
                ->url(function () {
                    /** @var PageContract&Model $page */
                    $page = $this->getRecord();

                    return FilamentFabricator::getPageUrlFromId($page->id);
                })
                ->icon('heroicon-o-arrow-top-right-on-square')
                ->openUrlInNewTab()
                ->color('success')
                ->visible(config('filament-fabricator.routing.enabled')),
        ];
    }
}


================================================
FILE: src/Resources/PageResource.php
================================================
<?php

namespace Z3d0X\FilamentFabricator\Resources;

use Closure;
use Filament\Actions\Action;
use Filament\Actions\EditAction;
use Filament\Actions\ViewAction;
use Filament\Forms\Components\Hidden;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Infolists\Components\TextEntry;
use Filament\Resources\Resource;
use Filament\Schemas\Components\Group;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Components\Utilities\Get;
use Filament\Schemas\Components\Utilities\Set;
use Filament\Schemas\Schema;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Str;
use Illuminate\Validation\Rules\Unique;
use Z3d0X\FilamentFabricator\Facades\FilamentFabricator;
use Z3d0X\FilamentFabricator\Forms\Components\PageBuilder;
use Z3d0X\FilamentFabricator\Models\Contracts\Page as PageContract;
use Z3d0X\FilamentFabricator\Resources\PageResource\Pages\CreatePage;
use Z3d0X\FilamentFabricator\Resources\PageResource\Pages\EditPage;
use Z3d0X\FilamentFabricator\Resources\PageResource\Pages\ListPages;
use Z3d0X\FilamentFabricator\Resources\PageResource\Pages\ViewPage;
use Z3d0X\FilamentFabricator\View\ResourceSchemaSlot;

class PageResource extends Resource
{
    protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-document-text';

    protected static ?string $recordTitleAttribute = 'title';

    public static function getModel(): string
    {
        return FilamentFabricator::getPageModel();
    }

    public static function form(Schema $schema): Schema
    {
        return $schema
            ->columns(3)
            ->components([
                Group::make()
                    ->schema([
                        Group::make()->schema(FilamentFabricator::getSchemaSlot(ResourceSchemaSlot::BLOCKS_BEFORE)),

                        PageBuilder::make('blocks')
                            ->label(__('filament-fabricator::page-resource.labels.blocks')),

                        Group::make()->schema(FilamentFabricator::getSchemaSlot(ResourceSchemaSlot::BLOCKS_AFTER)),
                    ])
                    ->columnSpan(2),

                Group::make()
                    ->columnSpan(1)
                    ->schema([
                        Group::make()->schema(FilamentFabricator::getSchemaSlot(ResourceSchemaSlot::SIDEBAR_BEFORE)),

                        Section::make()
                            ->schema([
                                TextEntry::make('page_url')
                                    ->label(__('filament-fabricator::page-resource.labels.url'))
                                    ->visible(fn (?PageContract $record) => config('filament-fabricator.routing.enabled') && filled($record))
                                    ->state(fn (?PageContract $record) => FilamentFabricator::getPageUrlFromId($record?->id)),

                                TextInput::make('title')
                                    ->label(__('filament-fabricator::page-resource.labels.title'))
                                    ->afterStateUpdated(function (Get $get, Set $set, ?string $state, ?PageContract $record) {
                                        if (! $get('is_slug_changed_manually') && filled($state) && blank($record)) {
                                            $set('slug', Str::slug($state, language: config('app.locale', 'en')));
                                        }
                                    })
                                    ->debounce('500ms')
                                    ->required(),

                                Hidden::make('is_slug_changed_manually')
                                    ->default(false)
                                    ->dehydrated(false),

                                TextInput::make('slug')
                                    ->label(__('filament-fabricator::page-resource.labels.slug'))
                                    ->unique(ignoreRecord: true, modifyRuleUsing: fn (Unique $rule, Get $get) => $rule->where('parent_id', $get('parent_id')))
                                    ->afterStateUpdated(function (Set $set) {
                                        $set('is_slug_changed_manually', true);
                                    })
                                    ->rule(function ($state) {
                                        return function (string $attribute, $value, Closure $fail) use ($state) {
                                            if ($state !== '/' && (Str::startsWith($value, '/') || Str::endsWith($value, '/'))) {
                                                $fail(__('filament-fabricator::page-resource.errors.slug_starts_or_ends_with_slash'));
                                            }
                                        };
                                    })
                                    ->required(),

                                Select::make('layout')
                                    ->label(__('filament-fabricator::page-resource.labels.layout'))
                                    ->options(FilamentFabricator::getLayouts())
                                    ->default(fn () => FilamentFabricator::getDefaultLayoutName())
                                    ->live()
                                    ->required(),

                                Select::make('parent_id')
                                    ->label(__('filament-fabricator::page-resource.labels.parent'))
                                    ->searchable()
                                    ->preload()
                                    ->reactive()
                                    ->suffixAction(
                                        fn ($get, $context) => Action::make($context . '-parent')
                                            ->icon('heroicon-o-arrow-top-right-on-square')
                                            ->url(fn () => PageResource::getUrl($context, ['record' => $get('parent_id')]))
                                            ->openUrlInNewTab()
                                            ->visible(fn () => filled($get('parent_id')))
                                    )
                                    ->relationship(
                                        'parent',
                                        'title',
                                        function (Builder $query, ?PageContract $record) {
                                            if (filled($record)) {
                                                $query->where('id', '!=', $record->id);
                                            }
                                        }
                                    ),
                            ]),

                        Group::make()->schema(FilamentFabricator::getSchemaSlot(ResourceSchemaSlot::SIDEBAR_AFTER)),
                    ]),

            ]);
    }

    public static function table(Table $table): Table
    {
        return $table
            ->columns([
                TextColumn::make('title')
                    ->label(__('filament-fabricator::page-resource.labels.title'))
                    ->searchable()
                    ->sortable(),

                TextColumn::make('url')
                    ->label(__('filament-fabricator::page-resource.labels.url'))
                    ->toggleable()
                    ->getStateUsing(fn (?PageContract $record) => FilamentFabricator::getPageUrlFromId($record->id) ?: null)
                    ->url(fn (?PageContract $record) => FilamentFabricator::getPageUrlFromId($record->id) ?: null, true)
                    ->visible(config('filament-fabricator.routing.enabled')),

                TextColumn::make('layout')
                    ->label(__('filament-fabricator::page-resource.labels.layout'))
                    ->badge()
                    ->toggleable()
                    ->sortable(),

                TextColumn::make('parent.title')
                    ->label(__('filament-fabricator::page-resource.labels.parent'))
                    ->toggleable(isToggledHiddenByDefault: true)
                    ->formatStateUsing(fn ($state) => $state ?? '-')
                    ->url(fn (?PageContract $record) => filled($record->parent_id) ? PageResource::getUrl('edit', ['record' => $record->parent_id]) : null),
            ])
            ->filters([
                SelectFilter::make('layout')
                    ->label(__('filament-fabricator::page-resource.labels.layout'))
                    ->options(FilamentFabricator::getLayouts()),
            ])
            ->recordActions([
                ViewAction::make()
                    ->visible(config('filament-fabricator.enable-view-page')),
                EditAction::make(),
                Action::make('visit')
                    ->label(__('filament-fabricator::page-resource.actions.visit'))
                    ->url(fn (?PageContract $record) => FilamentFabricator::getPageUrlFromId($record->id) ?: null)
                    ->icon('heroicon-o-arrow-top-right-on-square')
                    ->openUrlInNewTab()
                    ->color('success')
                    ->visible(config('filament-fabricator.routing.enabled')),
            ])
            ->toolbarActions([]);
    }

    public static function getModelLabel(): string
    {
        return __('filament-fabricator::page-resource.labels.page');
    }

    public static function getPluralModelLabel(): string
    {
        return __('filament-fabricator::page-resource.labels.pages');
    }

    public static function getPages(): array
    {
        return array_filter([
            'index' => ListPages::route('/'),
            'create' => CreatePage::route('/create'),
            'view' => config('filament-fabricator.enable-view-page') ? ViewPage::route('/{record}') : null,
            'edit' => EditPage::route('/{record}/edit'),
        ]);
    }
}


================================================
FILE: src/Services/PageRoutesService.php
================================================
<?php

namespace Z3d0X\FilamentFabricator\Services;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Str;
use Z3d0X\FilamentFabricator\Facades\FilamentFabricator;
use Z3d0X\FilamentFabricator\Models\Contracts\Page;

// The service is a coordinator between all route caches
// There are three layers of caches that have to be in sync:
// - PageRoutesService::URI_TO_ID_MAPPING that maps from URI to ID (many-to-one)
// - PageRoutesService::ID_TO_URI_MAPPING that maps from ID to URIS (one-to-many)
// - Page::getUrl (and thus Page::getAllUrl)
//
// Syncing and consistency should be handle via the service.
// As such, it's responsible for maintaining internal consistency
// and hiding/encapsulating implementation details.
//
// It relies on the extension points defined by Z3d0X\FilamentFabricator\Models\Contracts\HasPageUrls
class PageRoutesService
{
    protected const URI_TO_ID_MAPPING = 'filament-fabricator::PageRoutesService::uri-to-id';

    protected const ID_TO_URI_MAPPING = 'filament-fabricator::PageRoutesService::id-to-uri';

    /**
     * Get the ID of the Page model to which the given URI is associated, -1 if non matches
     *
     * @return int|string The page's ID, or -1 on failure
     */
    public function getPageIdFromUri(string $uri): int|string
    {
        // Query the (URI -> ID) mapping based on the user provided URI.
        // The mapping expect a URI that starts with a /
        // thus we "normalize" the URI by ensuring it starts with one.
        // Not doing so would result in a false negative.
        $mapping = $this->getUriToIdMapping();
        $uri = Str::start($uri, '/');

        return $mapping[$uri] ?? -1;
    }

    /**
     * Get an instance of your Page model from a URI, or NULL if none matches
     *
     * @return null|(Page&Model)
     */
    public function getPageFromUri(string $uri): ?Page
    {
        $id = $this->getPageIdFromUri($uri);

        // We know the getPageIdFromUri uses -1 as a "sentinel" value
        // for when the page is not found, so return null in those cases
        if ($id === -1) {
            return null;
        }

        /** @var null|(Page&Model) */
        return FilamentFabricator::getPageModel()::find($id);
    }

    /**
     * Update the cached URLs for the given page (as well as all its descendants')
     */
    public function updateUrlsOf(Page&Model $page): void
    {
        // We mutate the mapping without events to ensure we don't have "concurrent"
        // modifications of the same mapping. This allows us to skip the use of locks
        // in an environment where only unrelated pages can be modified by separate
        // users at the same time, which is a responsibility the library users
        // should enforce themselves.
        FilamentFabricator::getPageModel()::withoutEvents(function () use ($page) {
            $mapping = $this->getUriToIdMapping();
            $this->updateUrlsAndDescendantsOf($page, $mapping);
            $this->replaceUriToIdMapping($mapping);
        });
    }

    /**
     * Remove the cached URLs for the given page
     */
    public function removeUrlsOf(Page $page): void
    {
        // First remove the entries from the (ID -> URI) mapping
        $idToUrlsMapping = $this->getIdToUrisMapping();
        // NOTE: We should never be here without the page being
        // in the URLs mapping. Remove this once the
        // lifecycle issue has been dealt with
        $urls = $idToUrlsMapping[$page->id] ?? [];
        $idToUrlsMapping[$page->id] = null;
        unset($idToUrlsMapping[$page->id]);
        $this->replaceIdToUriMapping($idToUrlsMapping);

        // Then remove the entries from the (URI -> ID) mapping
        $uriToIdMapping = $this->getUriToIdMapping();
        foreach ($urls as $uri) {
            unset($uriToIdMapping[$uri]);
        }
        $this->replaceUriToIdMapping($uriToIdMapping);

        // Finally, clear the page's local caches of its own URL.
        // This means that Page::getAllUrls() and such will now compute
        // fresh values.
        $this->forgetPageLocalCache($page);
    }

    /**
     * Get an instance of your Page model from a URI, or throw if there is none
     */
    public function findPageOrFail(string $uri): Page&Model
    {
        $id = $this->getPageIdFromUri($uri);

        // If the page doesn't exists, we know getPageIdFromUri
        // will return -1. Thus, findOrFail will fail as expected.
        /** @var Page&Model */
        return FilamentFabricator::getPageModel()::findOrFail($id);
    }

    /**
     * Get the list of all the registered URLs
     *
     * @return string[]
     */
    public function getAllUrls(): array
    {
        $uriToIdMapping = $this->getUriToIdMapping();

        // $uriToIdMapping is an associative array that maps URIs to IDs.
        // Thus, the list of URLs is the keys of that array.
        // Since PHP handles keys very weirdly when using array_keys,
        // we simply get its array_values to have a truly regular array
        // instead of an associative array where the keys are all numbers
        // but possibly non-sorted.

        /* @phpstan-ignore arrayValues.list (This ensures the keys in the array are numerical and sorted) */
        return array_values(array_keys($uriToIdMapping));
    }

    /**
     * Get the URI -> ID mapping
     *
     * @return array<string, int|string>
     */
    protected function getUriToIdMapping(): array
    {
        // The mapping will be cached for most requests.
        // The very first person hitting the cache when it's not readily available
        // will sadly have to recompute the whole thing.
        return Cache::rememberForever(static::URI_TO_ID_MAPPING, function () {
            // Even though we technically have 2 separate caches
            // we want them to not really be independent.
            // Here we ensure our initial state depends on the other
            // cache's initial state.
            $idsToUrisMapping = $this->getIdToUrisMapping();
            $uriToIdMapping = [];

            // We simply "reverse" the one-to-many mapping to a many-to-one
            foreach ($idsToUrisMapping as $id => $uris) {
                foreach ($uris as $uri) {
                    $uriToIdMapping[$uri] = $id;
                }
            }

            return $uriToIdMapping;
        });
    }

    /**
     * Get the ID -> URI[] mapping
     *
     * @return array<int|string, string[]>
     */
    protected function getIdToUrisMapping(): array
    {
        // The mapping will be cached for most requests.
        // The very first person hitting the cache when it's not readily available
        // will sadly have to recompute the whole thing.
        // This could be a critical section and bottleneck depending on the use cases.
        // Any optimization to this can greatly improve the entire package's performances
        // in one fell swoop.
        return Cache::rememberForever(static::ID_TO_URI_MAPPING, function () {
            $mapping = FilamentFabricator::getPageModel()::query()
                ->with('parent')
                ->get()
                ->toBase()
                ->mapWithKeys(function (Page $page): array { // @phpstan-ignore-line
                    // Note that this also has the benefits of computing
                    // the page's local caches.
                    return [$page->id => $page->getAllUrls()];
                })
                ->all();

            return $mapping;
        });
    }

    /**
     * Get the cached URIs for the given page
     *
     * @return string[]
     */
    protected function getUrisForPage(Page $page): array
    {
        $mapping = $this->getIdToUrisMapping();

        return $mapping[$page->id] ?? [];
    }

    /**
     * Update routine for the given page
     *
     * @param  array  $uriToIdMapping  - The URI -> ID mapping (as a reference, to be modified in-place)
     * @return void
     */
    protected function updateUrlsAndDescendantsOf(Page&Model $page, array &$uriToIdMapping)
    {
        // First ensure consistency by removing any trace of the old URLs
        // for the given page. Whether local or in the URI to ID mapping.
        $this->unsetOldUrlsOf($page, $uriToIdMapping);

        // These URLs will always be fresh since we unset the old ones just above
        $urls = $page->getAllUrls();

        foreach ($urls as $uri) {
            $id = $uriToIdMapping[$uri] ?? -1;

            // If while iterating the fresh URLs we encounter one
            // that is already mapped to the right page ID
            // then there's nothing to do for this URL
            // and thus continue onward with the next one.
            if ($id === $page->id) {
                continue;
            }

            // Otherwise, we have a URI that already exists
            // and is mapped to the wrong ID, or it wasn't
            // in the mapping yet. In both cases we just need
            // to add it to the mapping at the correct spot.
            $uriToIdMapping[$uri] = $page->id;
        }

        // Since we're recursing down the tree, we preload the relationships
        // once, and traverse down the tree. This helps with performances.
        // TODO: Make it work with loadMissing instead of load to reduce the number of useless DB queries
        $page->load(['allChildren']);
        foreach ($page->allChildren as $childPage) {
            /**
             * @var Page&Model $childPage
             */

            // A change in a parent page will always result
            // in a change to its descendant. As such,
            // we need to recompute everything that's
            // a descendant of this page.
            $this->updateUrlsAndDescendantsOf($childPage, $uriToIdMapping);
        }
    }

    /**
     * Remove old URLs of the given page from the cached mappings
     *
     * @param  array  $uriToIdMapping  - The URI -> ID mapping (as a reference, to be modified in-place)
     * @return void
     */
    protected function unsetOldUrlsOf(Page $page, array &$uriToIdMapping)
    {
        // When we're hitting this path, caches haven't been invalidated yet.
        // Thus, we don't need to query the mappings to get the old URLs.
        $oldUrlSet = collect($page->getAllUrls())->lazy()->sort()->all();

        // Once we're done collecting the previous URLs, and since we want
        // to unset ALL old URLs for this given page, we might as well
        // forget its local caches here.
        $this->forgetPageLocalCache($page);

        // Since we just forgot the page's local caches, this doesn't
        // return the old set of URLs, but instead computes and caches
        // the new URLs based on the page's currently loaded data.
        $newUrlSet = collect($page->getAllUrls())->lazy()->sort()->all();

        // The old URLs are those that are present in the $oldUrlSet
        // but are not present in $newUrlSet. Hence the use of array_diff
        // whose role is to return exactly that. Also note we sorted the arrays
        // in order to make sure the diff algorithm has every chances to be
        // optimal in performance.
        $oldUrls = array_diff($oldUrlSet, $newUrlSet);

        // Simply go through the list of old URLs and remove them from the mapping.
        // This is one of the reasons we pass it by reference.
        foreach ($oldUrls as $oldUrl) {
            unset($uriToIdMapping[$oldUrl]);
        }

        $idToUrlsMapping = $this->getIdToUrisMapping();
        $idToUrlsMapping[$page->id] = $newUrlSet;
        $this->replaceIdToUriMapping($idToUrlsMapping);
    }

    /**
     * Forget all URL caches tied to the page (cf. Page::getAllUrlCacheKeys)
     */
    protected function forgetPageLocalCache(Page $page)
    {
        // The page local caches are simply those behind the
        // URL cache keys. Compute the keys, forget the caches.
        $cacheKeys = $page->getAllUrlCacheKeys();
        foreach ($cacheKeys as $cacheKey) {
            Cache::forget($cacheKey);
        }
    }

    /**
     * Completely replaced the cached ID -> URI[] mapping
     *
     * @param  array<int|string, string[]>  $idToUriMapping
     */
    protected function replaceIdToUriMapping(array $idToUriMapping): void
    {
        if (empty($idToUriMapping)) {
            // If the new mapping is empty, that means we've been
            // cleaning the last entries. Therefore we must
            // forget the cached data to properly clear it out
            // and also allow proper cache invalidation
            Cache::forget(static::ID_TO_URI_MAPPING);
        } else {
            // Replace the ID -> URI[] mapping with the given one.
            // This is done "atomically" with regards to the cache.
            // Note that concurrent read and writes can result in lost updates.
            // And thus in an invalid state.
            Cache::forever(static::ID_TO_URI_MAPPING, $idToUriMapping);
        }
    }

    /**
     * Completely replace the cached URI -> ID mapping
     *
     * @param  array<string, int|string>  $uriToIdMapping
     */
    protected function replaceUriToIdMapping(array $uriToIdMapping): void
    {
        if (empty($uriToIdMapping)) {
            // If the new mapping is empty, that means we've been
            // cleaning the last entries. Therefore we must
            // forget the cached data to properly clear it out
            // and also allow proper cache invalidation
            Cache::forget(static::URI_TO_ID_MAPPING);
        } else {
            // Replace the URI -> ID mapping with the given one.
            // This is done "atomically" with regards to the cache.
            // Note that concurrent read and writes can result in lost updates.
            // And thus in an invalid state.
            Cache::forever(static::URI_TO_ID_MAPPING, $uriToIdMapping);
        }
    }
}


================================================
FILE: src/View/LayoutRenderHook.php
================================================
<?php

namespace Z3d0X\FilamentFabricator\View;

class LayoutRenderHook
{
    public const HEAD_START = 'filament-fabricator::head.start';

    public const HEAD_END = 'filament-fabricator::head.end';

    public const BODY_START = 'filament-fabricator::body.start';

    public const SCRIPTS_START = 'filament-fabricator::scripts.start';

    public const SCRIPTS_END = 'filament-fabricator::scripts.end';

    public const BODY_END = 'filament-fabricator::body.end';
}


================================================
FILE: src/View/ResourceSchemaSlot.php
================================================
<?php

namespace Z3d0X\FilamentFabricator\View;

class ResourceSchemaSlot
{
    public const BLOCKS_BEFORE = 'blocks.before';

    public const BLOCKS_AFTER = 'blocks.after';

    public const SIDEBAR_BEFORE = 'sidebar.before';

    public const SIDEBAR_AFTER = 'sidebar.after';
}


================================================
FILE: stubs/Layout.stub
================================================
<?php

namespace {{ namespace }};

use Z3d0X\FilamentFabricator\Layouts\Layout;

class {{ class }} extends Layout
{
    protected static ?string $name = '{{ shortName }}';
}

================================================
FILE: stubs/LayoutView.stub
================================================
@props(['page'])
<x-filament-fabricator::layouts.base :page="$page" :title="$page->title">
    {{-- Header Here --}}

    <x-filament-fabricator::page-blocks :blocks="$page->blocks" />

     {{-- Footer Here --}}
</x-filament-fabricator::layouts.base>


================================================
FILE: stubs/PageBlock.stub
================================================
<?php

namespace {{ namespace }};

use Filament\Forms\Components\Builder\Block;
use Z3d0X\FilamentFabricator\PageBlocks\PageBlock;

class {{ class }} extends PageBlock
{
    protected static string $name = '{{ shortName }}';

    public static function defineBlock(Block $block): Block
    {
        return $block
            ->schema([
                //
            ]);
    }

    public static function mutateData(array $data): array
    {
        return $data;
    }
}


================================================
FILE: stubs/PageBlockView.stub
================================================
@aware(['page'])
<div class="px-4 py-4 md:py-8">
    <div class="max-w-7xl mx-auto">
        //
    </div>
</div>


================================================
FILE: tests/Commands/ClearRoutesCacheCommand.test.php
================================================
<?php

use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Config;
use Z3d0X\FilamentFabricator\Commands\ClearRoutesCacheCommand;
use Z3d0X\FilamentFabricator\Models\Page;
use Z3d0X\FilamentFabricator\Services\PageRoutesService;

use function Pest\Laravel\artisan;

describe(ClearRoutesCacheCommand::class, function () {
    beforeEach(function () {
        Config::set('filament-fabricator.routing.prefix');
    });

    it('can be resolved through the container', function () {
        $command = resolve(ClearRoutesCacheCommand::class);

        expect($command)->toBeInstanceOf(ClearRoutesCacheCommand::class);
    });

    it('clears all route caches', function () {
        /**
         * @var PageRoutesService $service
         */
        $service = resolve(PageRoutesService::class);

        /**
         * @var Page $page
         */
        $page = Page::create([
            'layout' => 'default',
            'title' => 'My title',
            'slug' => 'my-slug',
            'blocks' => [],
            'parent_id' => null,
        ]);

        /**
         * @var Page $child
         */
        $child = Page::create([
            'layout' => 'default',
            'title' => 'My child page',
            'slug' => 'my-child-page',
            'blocks' => [],
            'parent_id' => $page->id,
        ]);

        $service->getAllUrls(); // ensure everything is cached beforehand

        artisan('filament-fabricator:clear-routes-cache')
            ->assertSuccessful();

        expect(Cache::get('filament-fabricator::PageRoutesService::uri-to-id'))->toBeEmpty();
        expect(Cache::get('filament-fabricator::PageRoutesService::id-to-uri'))->toBeEmpty();

        $cacheKeys = [...$page->getAllUrlCacheKeys(), ...$child->getAllUrlCacheKeys()];

        expect($cacheKeys)->not->toBeEmpty();

        expect(
            collect($cacheKeys)
                ->every(fn (string $cacheKey) => ! Cache::has($cacheKey))
        )->toBeTrue();
    });

    it('refreshes the cache properly', function (string $flag, string $newPrefix) {
        /**
         * @var PageRoutesService $service
         */
        $service = resolve(PageRoutesService::class);

        /**
         * @var Page $page
         */
        $page = Page::create([
            'layout' => 'default',
            'title' => 'My title',
            'slug' => 'my-slug',
            'blocks' => [],
            'parent_id' => null,
        ]);

        /**
         * @var Page $child
         */
        $child = Page::create([
            'layout' => 'default',
            'title' => 'My child page',
            'slug' => 'my-child-page',
            'blocks' => [],
            'parent_id' => $page->id,
        ]);

        $urls = collect([...$page->getAllUrls(), ...$child->getAllUrls()])->sort()->toArray();

        $prevUTI = Cache::get('filament-fabricator::PageRoutesService::uri-to-id');
        $prevUTI = collect($prevUTI)->sort()->toArray();

        $prevITU = Cache::get('filament-fabricator::PageRoutesService::id-to-uri');
        $prevITU = collect($prevITU)->sort()->toArray();

        Config::set('filament-fabricator.routing.prefix', $newPrefix);

        artisan('filament-fabricator:clear-routes-cache', [
            $flag => true,
        ])
            ->assertSuccessful();

        $newUrls = collect([...$page->getAllUrls(), ...$child->getAllUrls()])->sort()->toArray();
        expect($newUrls)->not->toEqualCanonicalizing($urls);
        expect($newUrls)->not->toBeEmpty();
        expect(
            collect($newUrls)
                ->every(fn (string $url) => str_starts_with($url, "/$newPrefix"))
        )->toBeTrue();

        $newUTI = Cache::get('filament-fabricator::PageRoutesService::uri-to-id');
        $newUTI = collect($newUTI)->sort()->toArray();
        expect($newUTI)->not->toEqual($prevUTI);
        expect($newUTI)->not->toBeEmpty();
        expect(
            collect($newUTI)
                ->keys()
                ->every(fn (string $uri) => str_starts_with($uri, "/$newPrefix"))
        );

        $newITU = Cache::get('filament-fabricator::PageRoutesService::id-to-uri');
        $newITU = collect($newITU)->sort()->toArray();
        expect($newITU)->not->toEqual($prevITU);
        expect($newITU)->not->toBeEmpty();
        expect(
            collect($newITU)
                ->values()
                ->flatten()
                ->every(fn (string $uri) => str_starts_with($uri, "/$newPrefix"))
        );
    })->with([
        ['--refresh', 'newprefix'],
        ['-R', 'np'],
    ]);
});


================================================
FILE: tests/ExampleTest.php
================================================
<?php

it('can test', function () {
    expect(true)->toBeTrue();
});


================================================
FILE: tests/Fixtures/PageBuilderTestComponent.php
================================================
<?php

namespace Z3d0X\FilamentFabricator\Tests\Fixtures;

use Filament\Forms\Concerns\InteractsWithForms;
use Filament\Forms\Contracts\HasForms;
use Filament\Schemas\Schema;
use Illuminate\Support\MessageBag;
use Illuminate\Support\ViewErrorBag;
use Livewire\Component;
use Z3d0X\FilamentFabricator\Forms\Components\PageBuilder;

class PageBuilderTestComponent extends Component implements HasForms
{
    use InteractsWithForms;

    public ?array $data = [];

    protected MessageBag $msgBag;

    protected ViewErrorBag $errors;

    public function __construct()
    {
        $this->msgBag = new MessageBag;
        $this->errors = new ViewErrorBag;
        $this->errors->put('default', $this->msgBag);
    }

    public function form(Schema $form): Schema
    {
        return $form->schema([
            PageBuilder::make('blocks')
                ->blocks([]),
        ])->statePath('data');
    }

    public function getErrorBag()
    {
        return $this->errors->getBag('default');
    }

    public function getViewErrorBag()
    {
        return $this->errors;
    }

    public function mount(): void
    {
        $this->form->fill();
    }

    public function save()
    {
        $this->form->getState();
    }

    public function render()
    {
        return view('filament-fabricator::tests.fixtures.blade-wrapper');
    }
}


================================================
FILE: tests/Forms/Components/PageBuilder.test.php
================================================
<?php

use Filament\Facades\Filament;
use Filament\Panel;
use Z3d0X\FilamentFabricator\FilamentFabricatorPlugin;
use Z3d0X\FilamentFabricator\Forms\Components\PageBuilder;
use Z3d0X\FilamentFabricator\Tests\Fixtures\PageBuilderTestComponent;

use function Pest\Livewire\livewire;

describe(PageBuilder::class, function () {
    beforeEach(function () {
        Filament::setCurrentPanel(
            Panel::make()
                ->id('test')
                ->plugins([
                    FilamentFabricatorPlugin::make(),
                ]),
        );
    });

    it('renders without throwing an exception', function () {
        // TODO: Make the test run

        livewire(PageBuilderTestComponent::class)
            ->fillForm([
                'data' => [
                    ['title' => 'Test Item'], // make sure at least one item exists
                ],
            ])
            ->assertSeeHtml('class="fi-fo-builder-item')
            ->assertSchemaExists('blocks');
    })->skip('Proper Livewire unit testing isn\'t possible atm');
});


================================================
FILE: tests/Observers/PageRoutesObserver.test.php
================================================
<?php

use Z3d0X\FilamentFabricator\Facades\FilamentFabricator;
use Z3d0X\FilamentFabricator\Models\Page;
use Z3d0X\FilamentFabricator\Observers\PageRoutesObserver;

describe(PageRoutesObserver::class, function () {
    beforeEach(function () {
        // Cleanup the table before every test
        Page::query()->delete();
    });

    describe('#created($page)', function () {
        it('properly adds all the page\'s URLs to the mapping', function () {
            $beforeUrls = FilamentFabricator::getPageUrls();

            /**
             * @var Page $page
             */
            $page = Page::create([
                'layout' => 'default',
                'title' => 'My title',
                'slug' => 'my-slug',
                'blocks' => [],
                'parent_id' => null,
            ]);

            $sortUrls = fn (array $urls) => collect($urls)
                ->sort()
                ->values()
                ->toArray();

            $afterUrls = $sortUrls(FilamentFabricator::getPageUrls());

            expect($afterUrls)->not->toEqual($beforeUrls);

            $pageUrls = $sortUrls($page->getAllUrls());

            expect($afterUrls)->toEqual($pageUrls);
        });

        it('properly works on child pages', function () {
            /**
             * @var Page $page
             */
            $page = Page::create([
                'layout' => 'default',
                'title' => 'My title',
                'slug' => 'my-slug',
                'blocks' => [],
                'parent_id' => null,
            ]);

            /**
             * @var Page $page
             */
            $child = Page::create([
                'layout' => 'default',
                'title' => 'My stuff',
                'slug' => 'my-stuff',
                'blocks' => [],
                'parent_id' => $page->id,
            ]);

            $sortUrls = fn (array $urls) => collect($urls)
                ->sort()
                ->values()
                ->toArray();

            $allUrls = FilamentFabricator::getPageUrls();
            $allUrls = $sortUrls($allUrls);

            $fromPages = $sortUrls([
                ...$page->getAllUrls(),
                ...$child->getAllUrls(),
            ]);

            $expectedUrls = $sortUrls([
                '/my-slug',
                '/my-slug/my-stuff',
            ]);

            expect($allUrls)->toEqual($expectedUrls);
            expect($fromPages)->toEqual($expectedUrls);

            /**
             * @var Page $page
             */
            $descendant = Page::create([
                'layout' => 'default',
                'title' => 'Abc xyz',
                'slug' => 'abc-xyz',
                'blocks' => [],
                'parent_id' => $child->id,
            ]);

            $allUrls = FilamentFabricator::getPageUrls();
            $allUrls = $sortUrls($allUrls);

            $fromPages = $sortUrls([
                ...$page->getAllUrls(),
                ...$child->getAllUrls(),
                ...$descendant->getAllUrls(),
            ]);

            $expectedUrls = $sortUrls([
                '/my-slug',
                '/my-slug/my-stuff',
                '/my-slug/my-stuff/abc-xyz',
            ]);

            expect($allUrls)->toEqual($expectedUrls);
            expect($fromPages)->toEqual($expectedUrls);
        });
    });

    describe('#updated($page)', function () {
        it('removes the old URLs from the mapping', function () {
            /**
             * @var Page $page
             */
            $page = Page::create([
                'layout' => 'default',
                'title' => 'My title',
                'slug' => 'my-slug',
                'blocks' => [],
                'parent_id' => null,
            ]);

            $oldUrls = $page->getAllUrls();

            $page->slug = 'not-my-slug';
            $page->save();

            $allUrls = FilamentFabricator::getPageUrls();

            expect($allUrls)->not->toContain(...$oldUrls);
        });

        it('adds the new URLs to the mapping', function () {
            /**
             * @var Page $page
             */
            $page = Page::create([
                'layout' => 'default',
                'title' => 'My title',
                'slug' => 'my-slug',
                'blocks' => [],
                'parent_id' => null,
            ]);

            $page->slug = 'not-my-slug';
            $page->save();

            $sortUrls = fn (array $urls) => collect($urls)
                ->sort()
                ->values()
                ->toArray();

            $expected = $sortUrls(['/not-my-slug']);

            $newUrls = $sortUrls($page->getAllUrls());

            $allUrls = $sortUrls(FilamentFabricator::getPageUrls());

            expect($allUrls)->toEqual($expected);
            expect($newUrls)->toEqual($expected);
        });

        it('properly updates all child (and descendant) routes', function () {
            /**
             * @var Page $page
             */
            $page = Page::create([
                'layout' => 'default',
                'title' => 'My title',
                'slug' => 'my-slug',
                'blocks' => [],
                'parent_id' => null,
            ]);

            $child1 = Page::create([
                'layout' => 'default',
                'title' => 'My child 1',
                'slug' => 'child-1',
                'blocks' => [],
                'parent_id' => $page->id,
            ]);

            $child2 = Page::create([
                'layout' => 'default',
                'title' => 'My child 2',
                'slug' => 'child-2',
                'blocks' => [],
                'parent_id' => $page->id,
            ]);

            $child3 = Page::create([
                'layout' => 'default',
                'title' => 'My child 3',
                'slug' => 'child-3',
                'blocks' => [],
                'parent_id' => $page->id,
            ]);

            $childOfChild = Page::create([
                'layout' => 'default',
                'title' => 'Subchild 1',
                'slug' => 'subchild-1',
                'blocks' => [],
                'parent_id' => $child2->id,
            ]);

            $sortUrls = fn (array $urls) => collect($urls)
                ->sort()
                ->values()
                ->toArray();

            /**
             * @var Page[] $descendants
             */
            $descendants = [$child1, $child2, $child3, $childOfChild];
            $pages = [$page, ...$descendants];
            $oldUrlSets = array_map(fn (Page $page) => $page->getAllUrls(), $descendants);

            $page->slug = 'not-my-slug';
            $page->save();

            foreach ($descendants as $descendant) {
                $descendant->refresh();
            }

            $newUrlSets = array_map(fn (Page $page) => $page->getAllUrls(), $descendants);

            expect($newUrlSets)->not->toEqual($oldUrlSets);

            $allUrls = $sortUrls(FilamentFabricator::getPageUrls());

            $fromPages = $sortUrls(collect($pages)->flatMap(fn (Page $page) => $page->getAllUrls())->toArray());

            $expectedUrls = $sortUrls([
                '/not-my-slug',
                '/not-my-slug/child-1',
                '/not-my-slug/child-2',
                '/not-my-slug/child-3',
                '/not-my-slug/child-2/subchild-1',
            ]);

            expect($allUrls)->toEqual($expectedUrls);
            expect($fromPages)->toEqual($expectedUrls);

            $child2->slug = 'not-child-2-xyz';
            $child2->save();

            foreach ($descendants as $descendant) {
                $descendant->refresh();
            }

            $allUrls = $sortUrls(FilamentFabricator::getPageUrls());
            $fromPages = $sortUrls(collect($pages)->flatMap(fn (Page $page) => $page->getAllUrls())->toArray());

            $expectedUrls = $sortUrls([
                '/not-my-slug',
                '/not-my-slug/child-1',
                '/not-my-slug/not-child-2-xyz',
                '/not-my-slug/child-3',
                '/not-my-slug/not-child-2-xyz/subchild-1',
            ]);

            expect($allUrls)->toEqual($expectedUrls);
            expect($fromPages)->toEqual($expectedUrls);
        });

        it('properly updates itself and descendants when changing which page is the parent (BelongsTo#associate and BelongsTo#dissociate)', function () {
            /**
             * @var Page $page
             */
            $page = Page::create([
                'layout' => 'default',
                'title' => 'My title',
                'slug' => 'my-slug',
                'blocks' => [],
                'parent_id' => null,
            ]);

            /**
             * @var Page $child1
             */
            $child1 = Page::create([
                'layout' => 'default',
                'title' => 'My child 1',
                'slug' => 'child-1',
                'blocks' => [],
                'parent_id' => $page->id,
            ]);

            /**
             * @var Page $child2
             */
            $child2 = Page::create([
                'layout' => 'default',
                'title' => 'My child 2',
                'slug' => 'child-2',
                'blocks' => [],
                'parent_id' => $page->id,
            ]);

            /**
             * @var Page $child3
             */
            $child3 = Page::create([
                'layout' => 'default',
                'title' => 'My child 3',
                'slug' => 'child-3',
                'blocks' => [],
                'parent_id' => $page->id,
            ]);

            /**
             * @var Page $childOfChild
             */
            $childOfChild = Page::create([
                'layout' => 'default',
                'title' => 'Subchild 1',
                'slug' => 'subchild-1',
                'blocks' => [],
                'parent_id' => $child2->id,
            ]);

            $sortUrls = fn (array $urls) => collect($urls)
                ->sort()
                ->values()
                ->toArray();

            /**
             * @var Page[] $descendants
             */
            $descendants = [$child1, $child2, $child3, $childOfChild];
            $pages = [$page, ...$descendants];
            $oldUrlSets = array_map(fn (Page $page) => $page->getAllUrls(), $descendants);

            $child2->parent()->associate($child1);
            $child2->save();

            $child3->parent()->dissociate();
            $child3->save();

            foreach ($descendants as $descendant) {
                $descendant->refresh();
            }

            $newUrlSets = array_map(fn (Page $page) => $page->getAllUrls(), $descendants);

            $fromPages = $sortUrls(collect($pages)->flatMap(fn (Page $page) => $page->getAllUrls())->toArray());

            expect($newUrlSets)->not->toEqual($oldUrlSets);

            $allUrls = $sortUrls(FilamentFabricator::getPageUrls());

            $expectedUrls = $sortUrls([
                '/my-slug',
                '/my-slug/child-1',
                '/my-slug/child-1/child-2',
                '/child-3',
                '/my-slug/child-1/child-2/subchild-1',
            ]);

            expect($allUrls)->toEqual($expectedUrls);
            expect($fromPages)->toEqual($expectedUrls);
        });

        it('properly updates itself and descendants when changing which page is the parent (Model#update)', function () {
            /**
             * @var Page $page
             */
            $page = Page::create([
                'layout' => 'default',
                'title' => 'My title',
                'slug' => 'my-slug',
                'blocks' => [],
                'parent_id' => null,
            ]);

            /**
             * @var Page $child1
             */
            $child1 = Page::create([
                'layout' => 'default',
                'title' => 'My child 1',
                'slug' => 'child-1',
                'blocks' => [],
                'parent_id' => $page->id,
            ]);

            /**
             * @var Page $child2
             */
            $child2 = Page::create([
                'layout' => 'default',
                'title' => 'My child 2',
                'slug' => 'child-2',
                'blocks' => [],
                'parent_id' => $page->id,
            ]);

            /**
             * @var Page $child3
             */
            $child3 = Page::create([
                'layout' => 'default',
                'title' => 'My child 3',
                'slug' => 'child-3',
                'blocks' => [],
                'parent_id' => $page->id,
            ]);

            /**
             * @var Page $childOfChild
             */
            $childOfChild = Page::create([
                'layout' => 'default',
                'title' => 'Subchild 1',
                'slug' => 'subchild-1'
Download .txt
gitextract_u1aprfa_/

├── .devcontainer/
│   ├── config/
│   │   └── xdebug.ini
│   └── devcontainer.json
├── .editorconfig
├── .gitattributes
├── .github/
│   ├── CONTRIBUTING.md
│   ├── ISSUE_TEMPLATE/
│   │   └── config.yml
│   ├── SECURITY.md
│   ├── dependabot.yml
│   └── workflows/
│       ├── dependabot-auto-merge.yml
│       ├── fix-php-code-style-issues.yml
│       ├── phpstan.yml
│       ├── run-tests.yml
│       └── update-changelog.yml
├── .gitignore
├── .vscode/
│   └── settings.json
├── CHANGELOG.md
├── LICENSE.md
├── README.md
├── bootstrap/
│   └── app.php
├── composer.json
├── config/
│   └── filament-fabricator.php
├── database/
│   ├── factories/
│   │   └── ModelFactory.php
│   └── migrations/
│       ├── create_pages_table.php.stub
│       └── fix_slug_unique_constraint_on_pages_table.php.stub
├── docs/
│   └── README.md
├── phpstan-baseline.neon
├── phpstan.neon.dist
├── phpunit.xml.dist
├── pint.json
├── rector.php
├── resources/
│   ├── lang/
│   │   ├── ar/
│   │   │   └── page-resource.php
│   │   ├── en/
│   │   │   └── page-resource.php
│   │   ├── fr/
│   │   │   └── page-resource.php
│   │   ├── id/
│   │   │   └── page-resource.php
│   │   ├── nl/
│   │   │   └── page-resource.php
│   │   ├── pl/
│   │   │   └── page-resource.php
│   │   ├── ru/
│   │   │   └── page-resource.php
│   │   ├── tr/
│   │   │   └── page-resource.php
│   │   └── uk/
│   │       └── page-resource.php
│   └── views/
│       ├── components/
│       │   ├── forms/
│       │   │   └── components/
│       │   │       ├── page-builder/
│       │   │       │   ├── dropdown-block-picker.blade.php
│       │   │       │   └── modal-block-picker.blade.php
│       │   │       └── page-builder.blade.php
│       │   ├── layouts/
│       │   │   └── base.blade.php
│       │   └── page-blocks.blade.php
│       ├── preview.blade.php
│       └── tests/
│           └── fixtures/
│               └── blade-wrapper.blade.php
├── routes/
│   └── web.php
├── src/
│   ├── Commands/
│   │   ├── Aliases/
│   │   │   ├── MakeLayoutCommand.php
│   │   │   └── MakePageBlockCommand.php
│   │   ├── ClearRoutesCacheCommand.php
│   │   ├── MakeLayoutCommand.php
│   │   └── MakePageBlockCommand.php
│   ├── Enums/
│   │   └── BlockPickerStyle.php
│   ├── Facades/
│   │   └── FilamentFabricator.php
│   ├── FilamentFabricatorManager.php
│   ├── FilamentFabricatorPlugin.php
│   ├── FilamentFabricatorServiceProvider.php
│   ├── Forms/
│   │   └── Components/
│   │       └── PageBuilder.php
│   ├── Helpers.php
│   ├── Http/
│   │   └── Controllers/
│   │       └── PageController.php
│   ├── Layouts/
│   │   └── Layout.php
│   ├── Listeners/
│   │   └── OptimizeWithLaravel.php
│   ├── Models/
│   │   ├── Concerns/
│   │   │   └── HandlesPageUrls.php
│   │   ├── Contracts/
│   │   │   ├── HasPageUrls.php
│   │   │   └── Page.php
│   │   └── Page.php
│   ├── Observers/
│   │   └── PageRoutesObserver.php
│   ├── PageBlocks/
│   │   └── PageBlock.php
│   ├── Resources/
│   │   ├── PageResource/
│   │   │   └── Pages/
│   │   │       ├── Concerns/
│   │   │       │   └── HasPreviewModal.php
│   │   │       ├── CreatePage.php
│   │   │       ├── EditPage.php
│   │   │       ├── ListPages.php
│   │   │       └── ViewPage.php
│   │   └── PageResource.php
│   ├── Services/
│   │   └── PageRoutesService.php
│   └── View/
│       ├── LayoutRenderHook.php
│       └── ResourceSchemaSlot.php
├── stubs/
│   ├── Layout.stub
│   ├── LayoutView.stub
│   ├── PageBlock.stub
│   └── PageBlockView.stub
└── tests/
    ├── Commands/
    │   └── ClearRoutesCacheCommand.test.php
    ├── ExampleTest.php
    ├── Fixtures/
    │   └── PageBuilderTestComponent.php
    ├── Forms/
    │   └── Components/
    │       └── PageBuilder.test.php
    ├── Observers/
    │   └── PageRoutesObserver.test.php
    ├── Pest.php
    └── TestCase.php
Download .txt
SYMBOL INDEX (166 symbols across 31 files)

FILE: src/Commands/Aliases/MakeLayoutCommand.php
  class MakeLayoutCommand (line 11) | class MakeLayoutCommand extends Commands\MakeLayoutCommand

FILE: src/Commands/Aliases/MakePageBlockCommand.php
  class MakePageBlockCommand (line 11) | class MakePageBlockCommand extends Commands\MakePageBlockCommand

FILE: src/Commands/ClearRoutesCacheCommand.php
  class ClearRoutesCacheCommand (line 12) | class ClearRoutesCacheCommand extends Command
    method __construct (line 18) | public function __construct(protected PageRoutesService $pageRoutesSer...
    method handle (line 23) | public function handle(): int
    method clearPageCache (line 46) | protected function clearPageCache(PageContract $page, bool $shouldRefr...

FILE: src/Commands/MakeLayoutCommand.php
  class MakeLayoutCommand (line 11) | class MakeLayoutCommand extends Command
    method handle (line 19) | public function handle(): int

FILE: src/Commands/MakePageBlockCommand.php
  class MakePageBlockCommand (line 11) | class MakePageBlockCommand extends Command
    method handle (line 19) | public function handle(): int

FILE: src/Facades/FilamentFabricator.php
  class FilamentFabricator (line 38) | class FilamentFabricator extends Facade
    method getFacadeAccessor (line 40) | protected static function getFacadeAccessor()

FILE: src/FilamentFabricatorManager.php
  class FilamentFabricatorManager (line 18) | class FilamentFabricatorManager
    method __construct (line 46) | public function __construct(?PageRoutesService $routesService = null)
    method registerComponent (line 64) | public function registerComponent(string $class, string $baseClass): void
    method registerLayout (line 74) | public function registerLayout(string $layout): void
    method registerPageBlock (line 84) | public function registerPageBlock(string $pageBlock): void
    method registerSchemaSlot (line 93) | public function registerSchemaSlot(string $name, array|Closure $schema...
    method pushMeta (line 98) | public function pushMeta(array $meta): void
    method registerScripts (line 103) | public function registerScripts(array $scripts): void
    method registerStyles (line 108) | public function registerStyles(array $styles): void
    method favicon (line 113) | public function favicon(string $favicon): void
    method getLayoutFromName (line 121) | public function getLayoutFromName(string $layoutName): ?string
    method getPageBlockFromName (line 129) | public function getPageBlockFromName(string $name): ?string
    method getLayouts (line 139) | public function getLayouts(): array
    method getDefaultLayoutName (line 144) | public function getDefaultLayoutName(): ?string
    method getPageBlocks (line 152) | public function getPageBlocks(): array
    method getPageBlocksRaw (line 157) | public function getPageBlocksRaw(): array
    method getSchemaSlot (line 162) | public function getSchemaSlot(string $name): array|Closure
    method getMeta (line 167) | public function getMeta(): array
    method getScripts (line 172) | public function getScripts(): array
    method getStyles (line 177) | public function getStyles(): array
    method getFavicon (line 182) | public function getFavicon(): ?string
    method getPageModel (line 188) | public function getPageModel(): string
    method getRoutingPrefix (line 193) | public function getRoutingPrefix(): ?string
    method getPageUrls (line 213) | public function getPageUrls(): array
    method getPageUrlFromId (line 218) | public function getPageUrlFromId(int|string $id, array $args = []): ?s...

FILE: src/FilamentFabricatorPlugin.php
  class FilamentFabricatorPlugin (line 11) | class FilamentFabricatorPlugin implements Plugin
    method make (line 17) | public static function make(): static
    method getId (line 22) | public function getId(): string
    method register (line 27) | public function register(Panel $panel): void
    method boot (line 39) | public function boot(Panel $panel): void
    method blockPickerStyle (line 44) | public function blockPickerStyle(?BlockPickerStyle $style): static
    method getBlockPickerStyle (line 51) | public function getBlockPickerStyle(): ?BlockPickerStyle
    method get (line 56) | public static function get(): static

FILE: src/FilamentFabricatorServiceProvider.php
  class FilamentFabricatorServiceProvider (line 25) | class FilamentFabricatorServiceProvider extends PackageServiceProvider
    method configurePackage (line 27) | public function configurePackage(Package $package): void
    method getCommands (line 49) | protected function getCommands(): array
    method packageRegistered (line 72) | public function packageRegistered(): void
    method bootingPackage (line 81) | public function bootingPackage(): void
    method packageBooted (line 109) | public function packageBooted()
    method registerComponentsFromDirectory (line 126) | protected function registerComponentsFromDirectory(string $baseClass, ...

FILE: src/Forms/Components/PageBuilder.php
  class PageBuilder (line 12) | class PageBuilder extends Builder
    method setUp (line 21) | protected function setUp(): void
    method blockPickerStyle (line 47) | public function blockPickerStyle(BlockPickerStyle $style): static
    method getBlockPickerStyle (line 58) | public function getBlockPickerStyle(): BlockPickerStyle

FILE: src/Helpers.php
  class Helpers (line 9) | abstract class Helpers
    method arrayRefsGroupBy (line 17) | public static function arrayRefsGroupBy(array &$arr, string $key): array
    method preloadRelatedModels (line 41) | public static function preloadRelatedModels(

FILE: src/Http/Controllers/PageController.php
  class PageController (line 12) | class PageController
    method __invoke (line 14) | public function __invoke(?Page $filamentFabricatorPage = null): string

FILE: src/Layouts/Layout.php
  class Layout (line 7) | abstract class Layout
    method getName (line 13) | public static function getName(): string
    method getLabel (line 18) | public static function getLabel(): string
    method getComponent (line 23) | public static function getComponent(): string

FILE: src/Listeners/OptimizeWithLaravel.php
  class OptimizeWithLaravel (line 10) | class OptimizeWithLaravel
    method handle (line 26) | public function handle(CommandFinished $event): void
    method shouldHandleEvent (line 39) | public function shouldHandleEvent(CommandFinished $event)
    method shouldRefresh (line 45) | public function shouldRefresh(CommandFinished $event)
    method refresh (line 50) | public function refresh()
    method clear (line 57) | public function clear()
    method callCommand (line 62) | public function callCommand(array $params = [])

FILE: src/Models/Concerns/HandlesPageUrls.php
  type HandlesPageUrls (line 10) | trait HandlesPageUrls
    method getDefaultUrlCacheArgs (line 15) | public function getDefaultUrlCacheArgs(): array
    method getUrlCacheKey (line 25) | public function getUrlCacheKey(array $args = []): string
    method getUrl (line 38) | public function getUrl(array $args = []): string
    method getAllUrlCacheKeysArgs (line 83) | public function getAllUrlCacheKeysArgs(): array
    method getAllUrls (line 98) | public function getAllUrls(): array
    method getAllUrlCacheKeys (line 108) | public function getAllUrlCacheKeys(): array

FILE: src/Models/Contracts/HasPageUrls.php
  type HasPageUrls (line 5) | interface HasPageUrls
    method getDefaultUrlCacheArgs (line 12) | public function getDefaultUrlCacheArgs(): array;
    method getUrlCacheKey (line 17) | public function getUrlCacheKey(array $args = []): string;
    method getUrl (line 22) | public function getUrl(array $args = []): string;
    method getAllUrlCacheKeysArgs (line 29) | public function getAllUrlCacheKeysArgs(): array;
    method getAllUrls (line 36) | public function getAllUrls(): array;
    method getAllUrlCacheKeys (line 43) | public function getAllUrlCacheKeys(): array;

FILE: src/Models/Contracts/Page.php
  type Page (line 27) | interface Page extends HasPageUrls
    method parent (line 29) | public function parent(): BelongsTo;
    method children (line 31) | public function children(): HasMany;
    method allChildren (line 33) | public function allChildren(): HasMany;
    method query (line 36) | public static function query();

FILE: src/Models/Page.php
  class Page (line 11) | class Page extends Model implements Contract
    method __construct (line 15) | public function __construct(array $attributes = [])
    method parent (line 26) | public function parent(): BelongsTo
    method children (line 31) | public function children(): HasMany
    method allChildren (line 36) | public function allChildren(): HasMany
    method casts (line 43) | protected function casts(): array

FILE: src/Observers/PageRoutesObserver.php
  class PageRoutesObserver (line 9) | class PageRoutesObserver
    method __construct (line 11) | public function __construct(
    method created (line 18) | public function created(PageContract&Model $page): void
    method updated (line 28) | public function updated(PageContract&Model $page): void
    method deleting (line 44) | public function deleting(PageContract&Model $page): void
    method deleted (line 81) | public function deleted(PageContract&Model $page): void
    method restored (line 90) | public function restored(PageContract&Model $page): void
    method forceDeleting (line 98) | public function forceDeleting(PageContract&Model $page): void
    method forceDeleted (line 107) | public function forceDeleted(PageContract&Model $page): void

FILE: src/PageBlocks/PageBlock.php
  class PageBlock (line 8) | abstract class PageBlock
    method createNewBlock (line 17) | protected static function createNewBlock(): Block
    method getBlockSchema (line 25) | public static function getBlockSchema(): Block
    method defineBlock (line 37) | abstract public static function defineBlock(Block $block): Block;
    method getComponent (line 39) | public static function getComponent(): string
    method getName (line 47) | public static function getName(): string
    method mutateData (line 52) | public static function mutateData(array $data): array
    method preloadRelatedData (line 66) | public static function preloadRelatedData(Page $page, array &$blocks):...

FILE: src/Resources/PageResource.php
  class PageResource (line 34) | class PageResource extends Resource
    method getModel (line 40) | public static function getModel(): string
    method form (line 45) | public static function form(Schema $schema): Schema
    method table (line 138) | public static function table(Table $table): Table
    method getModelLabel (line 186) | public static function getModelLabel(): string
    method getPluralModelLabel (line 191) | public static function getPluralModelLabel(): string
    method getPages (line 196) | public static function getPages(): array

FILE: src/Resources/PageResource/Pages/Concerns/HasPreviewModal.php
  type HasPreviewModal (line 9) | trait HasPreviewModal
    method getPreviewModalView (line 13) | protected function getPreviewModalView(): ?string
    method getPreviewModalDataRecordKey (line 18) | protected function getPreviewModalDataRecordKey(): ?string
    method mutatePreviewModalData (line 23) | protected function mutatePreviewModalData(array $data): array

FILE: src/Resources/PageResource/Pages/CreatePage.php
  class CreatePage (line 10) | class CreatePage extends CreateRecord
    method getResource (line 16) | public static function getResource(): string
    method getActions (line 21) | protected function getActions(): array

FILE: src/Resources/PageResource/Pages/EditPage.php
  class EditPage (line 16) | class EditPage extends EditRecord
    method getResource (line 22) | public static function getResource(): string
    method getActions (line 27) | protected function getActions(): array

FILE: src/Resources/PageResource/Pages/ListPages.php
  class ListPages (line 9) | class ListPages extends ListRecords
    method getResource (line 13) | public static function getResource(): string
    method getActions (line 18) | protected function getActions(): array

FILE: src/Resources/PageResource/Pages/ViewPage.php
  class ViewPage (line 13) | class ViewPage extends ViewRecord
    method getResource (line 17) | public static function getResource(): string
    method getActions (line 22) | protected function getActions(): array

FILE: src/Services/PageRoutesService.php
  class PageRoutesService (line 22) | class PageRoutesService
    method getPageIdFromUri (line 33) | public function getPageIdFromUri(string $uri): int|string
    method getPageFromUri (line 50) | public function getPageFromUri(string $uri): ?Page
    method updateUrlsOf (line 67) | public function updateUrlsOf(Page&Model $page): void
    method removeUrlsOf (line 84) | public function removeUrlsOf(Page $page): void
    method findPageOrFail (line 112) | public function findPageOrFail(string $uri): Page&Model
    method getAllUrls (line 127) | public function getAllUrls(): array
    method getUriToIdMapping (line 147) | protected function getUriToIdMapping(): array
    method getIdToUrisMapping (line 176) | protected function getIdToUrisMapping(): array
    method getUrisForPage (line 205) | protected function getUrisForPage(Page $page): array
    method updateUrlsAndDescendantsOf (line 218) | protected function updateUrlsAndDescendantsOf(Page&Model $page, array ...
    method unsetOldUrlsOf (line 268) | protected function unsetOldUrlsOf(Page $page, array &$uriToIdMapping)
    method forgetPageLocalCache (line 305) | protected function forgetPageLocalCache(Page $page)
    method replaceIdToUriMapping (line 320) | protected function replaceIdToUriMapping(array $idToUriMapping): void
    method replaceUriToIdMapping (line 342) | protected function replaceUriToIdMapping(array $uriToIdMapping): void

FILE: src/View/LayoutRenderHook.php
  class LayoutRenderHook (line 5) | class LayoutRenderHook

FILE: src/View/ResourceSchemaSlot.php
  class ResourceSchemaSlot (line 5) | class ResourceSchemaSlot

FILE: tests/Fixtures/PageBuilderTestComponent.php
  class PageBuilderTestComponent (line 13) | class PageBuilderTestComponent extends Component implements HasForms
    method __construct (line 23) | public function __construct()
    method form (line 30) | public function form(Schema $form): Schema
    method getErrorBag (line 38) | public function getErrorBag()
    method getViewErrorBag (line 43) | public function getViewErrorBag()
    method mount (line 48) | public function mount(): void
    method save (line 53) | public function save()
    method render (line 58) | public function render()

FILE: tests/TestCase.php
  class TestCase (line 15) | class TestCase extends Orchestra
    method setUp (line 17) | protected function setUp(): void
    method getPackageProviders (line 26) | protected function getPackageProviders($app)
    method getEnvironmentSetUp (line 40) | public function getEnvironmentSetUp($app)
Condensed preview — 88 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (211K chars).
[
  {
    "path": ".devcontainer/config/xdebug.ini",
    "chars": 127,
    "preview": "zend_extension=xdebug.so\nxdebug.mode=develop,debug,profile,trace,gcstats\nxdebug.discover_client_host=1\nxdebug.client_por"
  },
  {
    "path": ".devcontainer/devcontainer.json",
    "chars": 360,
    "preview": "{\n    \"image\": \"mcr.microsoft.com/devcontainers/php:8.3\",\n    \"forwardPorts\": [\n        8000,\n        5173,\n        9000"
  },
  {
    "path": ".editorconfig",
    "chars": 258,
    "preview": "root = true\n\n[*]\ncharset = utf-8\nindent_size = 4\nindent_style = space\nend_of_line = lf\ninsert_final_newline = true\ntrim_"
  },
  {
    "path": ".gitattributes",
    "chars": 6026,
    "preview": "* 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-base"
  },
  {
    "path": ".github/CONTRIBUTING.md",
    "chars": 2972,
    "preview": "# Contributing\n\nContributions are **welcome** and will be fully **credited**.\n\nPlease read and understand the contributi"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "chars": 624,
    "preview": "blank_issues_enabled: false\ncontact_links:\n  - name: Ask a question\n    url: https://github.com/Z3d0X/filament-fabricato"
  },
  {
    "path": ".github/SECURITY.md",
    "chars": 134,
    "preview": "# Security Policy\n\nIf you discover any security related issues, please email ziyaan2010@gmail.com instead of using the i"
  },
  {
    "path": ".github/dependabot.yml",
    "chars": 321,
    "preview": "# Please see the documentation for all configuration options:\n# https://help.github.com/github/administering-a-repositor"
  },
  {
    "path": ".github/workflows/dependabot-auto-merge.yml",
    "chars": 1029,
    "preview": "name: dependabot-auto-merge\non: pull_request_target\n\npermissions:\n  pull-requests: write\n  contents: write\n\njobs:\n  depe"
  },
  {
    "path": ".github/workflows/fix-php-code-style-issues.yml",
    "chars": 448,
    "preview": "name: Fix PHP code style issues\n\non: [push]\n\njobs:\n  php-code-styling:\n    runs-on: ubuntu-latest\n\n    steps:\n      - na"
  },
  {
    "path": ".github/workflows/phpstan.yml",
    "chars": 471,
    "preview": "name: PHPStan\n\non:\n  push:\n    paths:\n      - '**.php'\n      - 'phpstan.neon.dist'\n\njobs:\n  phpstan:\n    name: phpstan\n "
  },
  {
    "path": ".github/workflows/run-tests.yml",
    "chars": 2032,
    "preview": "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"
  },
  {
    "path": ".github/workflows/update-changelog.yml",
    "chars": 651,
    "preview": "name: \"Update Changelog\"\n\non:\n  release:\n    types: [released]\n\njobs:\n  update:\n    runs-on: ubuntu-latest\n\n    steps:\n "
  },
  {
    "path": ".gitignore",
    "chars": 172,
    "preview": ".idea\n.php_cs\n.php_cs.cache\n.phpunit.result.cache\nbuild\ncomposer.lock\ncoverage\nphpunit.xml\nphpstan.neon\ntestbench.yaml\nv"
  },
  {
    "path": ".vscode/settings.json",
    "chars": 94,
    "preview": "{\n\t\"intelephense.environment.includePaths\": [\n\t\t\"/workspaces/filament-fabricator/vendor\"\n\t]\n}\n"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 19827,
    "preview": "# Changelog\n\nAll notable changes to `filament-fabricator` will be documented in this file.\n\n## v4.0.0 - Filament v5 Comp"
  },
  {
    "path": "LICENSE.md",
    "chars": 1090,
    "preview": "The MIT License (MIT)\n\nCopyright (c) Z3d0X <ziyaan2010@gmail.com>\n\nPermission is hereby granted, free of charge, to any "
  },
  {
    "path": "README.md",
    "chars": 4463,
    "preview": "# Block-Based Page Builder Skeleton for your Filament Apps\n\n[![Latest Version on Packagist](https://img.shields.io/packa"
  },
  {
    "path": "bootstrap/app.php",
    "chars": 738,
    "preview": "<?php\n\nuse Filament\\FilamentServiceProvider;\nuse Filament\\Support\\SupportServiceProvider;\nuse Livewire\\LivewireServicePr"
  },
  {
    "path": "composer.json",
    "chars": 2524,
    "preview": "{\n    \"name\": \"z3d0x/filament-fabricator\",\n    \"description\": \"Block-Based Page Builder Skeleton for your Filament Apps\""
  },
  {
    "path": "config/filament-fabricator.php",
    "chars": 3221,
    "preview": "<?php\n\nuse Z3d0X\\FilamentFabricator\\Models\\Page;\nuse Z3d0X\\FilamentFabricator\\Resources\\PageResource;\n\n// config for Z3d"
  },
  {
    "path": "database/factories/ModelFactory.php",
    "chars": 277,
    "preview": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\Database\\Factories;\n\nuse Illuminate\\Database\\Eloquent\\Factories\\Factory;\n\n/*\nc"
  },
  {
    "path": "database/migrations/create_pages_table.php.stub",
    "chars": 842,
    "preview": "<?php\n\nuse Illuminate\\Database\\Migrations\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Fa"
  },
  {
    "path": "database/migrations/fix_slug_unique_constraint_on_pages_table.php.stub",
    "chars": 668,
    "preview": "<?php\n\nuse Illuminate\\Database\\Migrations\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Fa"
  },
  {
    "path": "docs/README.md",
    "chars": 13542,
    "preview": "## Introduction\n\n<p align=\"center\" class=\"filament-hidden\">\n  <img alt=\"fabricator banner\" src=\"https://raw.githubuserco"
  },
  {
    "path": "phpstan-baseline.neon",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "phpstan.neon.dist",
    "chars": 424,
    "preview": "includes:\n    - vendor/larastan/larastan/extension.neon\n    - vendor/nesbot/carbon/extension.neon\n    - vendor/phpstan/p"
  },
  {
    "path": "phpunit.xml.dist",
    "chars": 1632,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit\n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:noName"
  },
  {
    "path": "pint.json",
    "chars": 250,
    "preview": "{\n    \"preset\": \"laravel\",\n    \"rules\": {\n        \"blank_line_before_statement\": true,\n        \"concat_space\": {\n       "
  },
  {
    "path": "rector.php",
    "chars": 1789,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nuse Rector\\CodeQuality\\Rector\\Empty_\\SimplifyEmptyCheckOnEmptyArrayRector;\nuse Rector\\C"
  },
  {
    "path": "resources/lang/ar/page-resource.php",
    "chars": 487,
    "preview": "<?php\n\nreturn [\n    'labels' => [\n        'blocks' => 'الوحدات',\n        'layout' => 'المخطط',\n        'page' => 'صفحة',"
  },
  {
    "path": "resources/lang/en/page-resource.php",
    "chars": 463,
    "preview": "<?php\n\nreturn [\n    'labels' => [\n        'blocks' => 'Blocks',\n        'layout' => 'Layout',\n        'page' => 'Page',\n"
  },
  {
    "path": "resources/lang/fr/page-resource.php",
    "chars": 493,
    "preview": "<?php\n\nreturn [\n    'labels' => [\n        'blocks' => 'Blocs',\n        'layout' => 'Mise en page',\n        'page' => 'Pa"
  },
  {
    "path": "resources/lang/id/page-resource.php",
    "chars": 499,
    "preview": "<?php\n\nreturn [\n    'labels' => [\n        'blocks' => 'Blok',\n        'layout' => 'Tata Letak',\n        'page' => 'Halam"
  },
  {
    "path": "resources/lang/nl/page-resource.php",
    "chars": 502,
    "preview": "<?php\n\nreturn [\n    'labels' => [\n        'blocks' => 'Blokken',\n        'layout' => 'Ontwerp',\n        'page' => 'Pagin"
  },
  {
    "path": "resources/lang/pl/page-resource.php",
    "chars": 488,
    "preview": "<?php\n\nreturn [\n    'labels' => [\n        'blocks' => 'Bloki',\n        'layout' => 'Układ',\n        'page' => 'Strona',\n"
  },
  {
    "path": "resources/lang/ru/page-resource.php",
    "chars": 512,
    "preview": "<?php\n\nreturn [\n    'labels' => [\n        'blocks' => 'Блоки',\n        'layout' => 'Макет',\n        'page' => 'Страница'"
  },
  {
    "path": "resources/lang/tr/page-resource.php",
    "chars": 487,
    "preview": "<?php\n\nreturn [\n    'labels' => [\n        'blocks' => 'Bloklar',\n        'layout' => 'Düzen',\n        'page' => 'Sayfa',"
  },
  {
    "path": "resources/lang/uk/page-resource.php",
    "chars": 512,
    "preview": "<?php\n\nreturn [\n    'labels' => [\n        'blocks' => 'Блоки',\n        'layout' => 'Макет',\n        'page' => 'Сторінка'"
  },
  {
    "path": "resources/views/components/forms/components/page-builder/dropdown-block-picker.blade.php",
    "chars": 1415,
    "preview": "@props([\n    'action',\n    'afterItem' => null,\n    'blocks',\n    'columns' => null,\n    'key',\n    'statePath',\n    'tr"
  },
  {
    "path": "resources/views/components/forms/components/page-builder/modal-block-picker.blade.php",
    "chars": 1908,
    "preview": "@props([\n    'action',\n    'afterItem' => null,\n    'blocks',\n    'columns' => null,\n    'key',\n    'statePath',\n    'tr"
  },
  {
    "path": "resources/views/components/forms/components/page-builder.blade.php",
    "chars": 14887,
    "preview": "@php\n    use Filament\\Actions\\Action;\n    use Z3d0X\\FilamentFabricator\\Enums\\BlockPickerStyle;\n    use Illuminate\\Suppor"
  },
  {
    "path": "resources/views/components/layouts/base.blade.php",
    "chars": 2155,
    "preview": "@props([\n    'page',\n    'title' => null,\n    'dir' => 'ltr',\n])\n\n@use(Z3d0X\\FilamentFabricator\\View\\LayoutRenderHook)\n\n"
  },
  {
    "path": "resources/views/components/page-blocks.blade.php",
    "chars": 938,
    "preview": "@aware(['page'])\n@props(['blocks' => []])\n\n@php\n    $groups = \\Z3d0X\\FilamentFabricator\\Helpers::arrayRefsGroupBy($block"
  },
  {
    "path": "resources/views/preview.blade.php",
    "chars": 100,
    "preview": "@props(['page', 'component'])\n<x-dynamic-component\n    :component=\"$component\"\n    :page=\"$page\"\n/>\n"
  },
  {
    "path": "resources/views/tests/fixtures/blade-wrapper.blade.php",
    "chars": 35,
    "preview": "<div>\n    {{ $this->form }}\n</div>\n"
  },
  {
    "path": "routes/web.php",
    "chars": 551,
    "preview": "<?php\n\nuse Illuminate\\Support\\Facades\\Route;\nuse Z3d0X\\FilamentFabricator\\Facades\\FilamentFabricator;\nuse Z3d0X\\Filament"
  },
  {
    "path": "src/Commands/Aliases/MakeLayoutCommand.php",
    "chars": 335,
    "preview": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\Commands\\Aliases;\n\nuse Z3d0X\\FilamentFabricator\\Commands;\n\n/**\n * @deprecated\n"
  },
  {
    "path": "src/Commands/Aliases/MakePageBlockCommand.php",
    "chars": 348,
    "preview": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\Commands\\Aliases;\n\nuse Z3d0X\\FilamentFabricator\\Commands;\n\n/**\n * @deprecated\n"
  },
  {
    "path": "src/Commands/ClearRoutesCacheCommand.php",
    "chars": 1966,
    "preview": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\Commands;\n\nuse Illuminate\\Console\\Command;\nuse Illuminate\\Database\\Eloquent\\Mo"
  },
  {
    "path": "src/Commands/MakeLayoutCommand.php",
    "chars": 2394,
    "preview": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\Commands;\n\nuse Filament\\Support\\Commands\\Concerns\\CanManipulateFiles;\nuse Illu"
  },
  {
    "path": "src/Commands/MakePageBlockCommand.php",
    "chars": 2448,
    "preview": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\Commands;\n\nuse Filament\\Support\\Commands\\Concerns\\CanManipulateFiles;\nuse Illu"
  },
  {
    "path": "src/Enums/BlockPickerStyle.php",
    "chars": 111,
    "preview": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\Enums;\n\nenum BlockPickerStyle\n{\n    case Dropdown;\n    case Modal;\n}\n"
  },
  {
    "path": "src/Facades/FilamentFabricator.php",
    "chars": 1655,
    "preview": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\Facades;\n\nuse Closure;\nuse Illuminate\\Database\\Eloquent\\Model;\nuse Illuminate\\"
  },
  {
    "path": "src/FilamentFabricatorManager.php",
    "chars": 5826,
    "preview": "<?php\n\nnamespace Z3d0X\\FilamentFabricator;\n\nuse Closure;\nuse Exception;\nuse Filament\\Forms\\Components\\Builder\\Block;\nuse"
  },
  {
    "path": "src/FilamentFabricatorPlugin.php",
    "chars": 1433,
    "preview": "<?php\n\nnamespace Z3d0X\\FilamentFabricator;\n\nuse Closure;\nuse Filament\\Contracts\\Plugin;\nuse Filament\\Panel;\nuse Pboivin\\"
  },
  {
    "path": "src/FilamentFabricatorServiceProvider.php",
    "chars": 5934,
    "preview": "<?php\n\nnamespace Z3d0X\\FilamentFabricator;\n\nuse Illuminate\\Console\\Events\\CommandFinished;\nuse Illuminate\\Filesystem\\Fil"
  },
  {
    "path": "src/Forms/Components/PageBuilder.php",
    "chars": 1800,
    "preview": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\Forms\\Components;\n\nuse Filament\\Forms\\Components\\Builder;\nuse Filament\\Forms\\C"
  },
  {
    "path": "src/Helpers.php",
    "chars": 2877,
    "preview": "<?php\n\nnamespace Z3d0X\\FilamentFabricator;\n\nuse Closure;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Databa"
  },
  {
    "path": "src/Http/Controllers/PageController.php",
    "chars": 1465,
    "preview": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\Http\\Controllers;\n\nuse Exception;\nuse Illuminate\\Support\\Facades\\Blade;\nuse Z3"
  },
  {
    "path": "src/Layouts/Layout.php",
    "chars": 545,
    "preview": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\Layouts;\n\nuse Illuminate\\Support\\Str;\n\nabstract class Layout\n{\n    protected s"
  },
  {
    "path": "src/Listeners/OptimizeWithLaravel.php",
    "chars": 1458,
    "preview": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\Listeners;\n\nuse Illuminate\\Console\\Command;\nuse Illuminate\\Console\\Events\\Comm"
  },
  {
    "path": "src/Models/Concerns/HandlesPageUrls.php",
    "chars": 3563,
    "preview": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\Models\\Concerns;\n\nuse Illuminate\\Support\\Facades\\Cache;\nuse Illuminate\\Support"
  },
  {
    "path": "src/Models/Contracts/HasPageUrls.php",
    "chars": 1131,
    "preview": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\Models\\Contracts;\n\ninterface HasPageUrls\n{\n    /**\n     * Get the default argu"
  },
  {
    "path": "src/Models/Contracts/Page.php",
    "chars": 1051,
    "preview": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\Models\\Contracts;\n\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Da"
  },
  {
    "path": "src/Models/Page.php",
    "chars": 1276,
    "preview": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\Models;\n\nuse Illuminate\\Database\\Eloquent\\Model;\nuse Illuminate\\Database\\Eloqu"
  },
  {
    "path": "src/Observers/PageRoutesObserver.php",
    "chars": 3797,
    "preview": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\Observers;\n\nuse Illuminate\\Database\\Eloquent\\Model;\nuse Z3d0X\\FilamentFabricat"
  },
  {
    "path": "src/PageBlocks/PageBlock.php",
    "chars": 1832,
    "preview": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\PageBlocks;\n\nuse Filament\\Forms\\Components\\Builder\\Block;\nuse Z3d0X\\FilamentFa"
  },
  {
    "path": "src/Resources/PageResource/Pages/Concerns/HasPreviewModal.php",
    "chars": 1056,
    "preview": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\Resources\\PageResource\\Pages\\Concerns;\n\nuse Exception;\nuse Pboivin\\FilamentPee"
  },
  {
    "path": "src/Resources/PageResource/Pages/CreatePage.php",
    "chars": 699,
    "preview": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\Resources\\PageResource\\Pages;\n\nuse Filament\\Resources\\Pages\\CreateRecord;\nuse "
  },
  {
    "path": "src/Resources/PageResource/Pages/EditPage.php",
    "chars": 1804,
    "preview": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\Resources\\PageResource\\Pages;\n\nuse Filament\\Actions\\Action;\nuse Filament\\Actio"
  },
  {
    "path": "src/Resources/PageResource/Pages/ListPages.php",
    "chars": 566,
    "preview": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\Resources\\PageResource\\Pages;\n\nuse Filament\\Actions\\CreateAction;\nuse Filament"
  },
  {
    "path": "src/Resources/PageResource/Pages/ViewPage.php",
    "chars": 1313,
    "preview": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\Resources\\PageResource\\Pages;\n\nuse Filament\\Actions\\Action;\nuse Filament\\Actio"
  },
  {
    "path": "src/Resources/PageResource.php",
    "chars": 10008,
    "preview": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\Resources;\n\nuse Closure;\nuse Filament\\Actions\\Action;\nuse Filament\\Actions\\Edi"
  },
  {
    "path": "src/Services/PageRoutesService.php",
    "chars": 13985,
    "preview": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\Services;\n\nuse Illuminate\\Database\\Eloquent\\Model;\nuse Illuminate\\Support\\Faca"
  },
  {
    "path": "src/View/LayoutRenderHook.php",
    "chars": 471,
    "preview": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\View;\n\nclass LayoutRenderHook\n{\n    public const HEAD_START = 'filament-fabric"
  },
  {
    "path": "src/View/ResourceSchemaSlot.php",
    "chars": 281,
    "preview": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\View;\n\nclass ResourceSchemaSlot\n{\n    public const BLOCKS_BEFORE = 'blocks.bef"
  },
  {
    "path": "stubs/Layout.stub",
    "chars": 173,
    "preview": "<?php\n\nnamespace {{ namespace }};\n\nuse Z3d0X\\FilamentFabricator\\Layouts\\Layout;\n\nclass {{ class }} extends Layout\n{\n    "
  },
  {
    "path": "stubs/LayoutView.stub",
    "chars": 252,
    "preview": "@props(['page'])\n<x-filament-fabricator::layouts.base :page=\"$page\" :title=\"$page->title\">\n    {{-- Header Here --}}\n\n  "
  },
  {
    "path": "stubs/PageBlock.stub",
    "chars": 473,
    "preview": "<?php\n\nnamespace {{ namespace }};\n\nuse Filament\\Forms\\Components\\Builder\\Block;\nuse Z3d0X\\FilamentFabricator\\PageBlocks\\"
  },
  {
    "path": "stubs/PageBlockView.stub",
    "chars": 114,
    "preview": "@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",
    "chars": 4578,
    "preview": "<?php\n\nuse Illuminate\\Support\\Facades\\Cache;\nuse Illuminate\\Support\\Facades\\Config;\nuse Z3d0X\\FilamentFabricator\\Command"
  },
  {
    "path": "tests/ExampleTest.php",
    "chars": 70,
    "preview": "<?php\n\nit('can test', function () {\n    expect(true)->toBeTrue();\n});\n"
  },
  {
    "path": "tests/Fixtures/PageBuilderTestComponent.php",
    "chars": 1352,
    "preview": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\Tests\\Fixtures;\n\nuse Filament\\Forms\\Concerns\\InteractsWithForms;\nuse Filament\\"
  },
  {
    "path": "tests/Forms/Components/PageBuilder.test.php",
    "chars": 1055,
    "preview": "<?php\n\nuse Filament\\Facades\\Filament;\nuse Filament\\Panel;\nuse Z3d0X\\FilamentFabricator\\FilamentFabricatorPlugin;\nuse Z3d"
  },
  {
    "path": "tests/Observers/PageRoutesObserver.test.php",
    "chars": 21072,
    "preview": "<?php\n\nuse Z3d0X\\FilamentFabricator\\Facades\\FilamentFabricator;\nuse Z3d0X\\FilamentFabricator\\Models\\Page;\nuse Z3d0X\\Fila"
  },
  {
    "path": "tests/Pest.php",
    "chars": 296,
    "preview": "<?php\n\nuse Z3d0X\\FilamentFabricator\\Tests\\TestCase;\n\nbeforeAll(function () {\n    if (empty(config('app.key'))) {\n       "
  },
  {
    "path": "tests/TestCase.php",
    "chars": 1560,
    "preview": "<?php\n\nnamespace Z3d0X\\FilamentFabricator\\Tests;\n\nuse BladeUI\\Heroicons\\BladeHeroiconsServiceProvider;\nuse BladeUI\\Icons"
  }
]

About this extraction

This page contains the full source code of the Z3d0X/filament-fabricator GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 88 files (193.2 KB), approximately 50.9k tokens, and a symbol index with 166 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!