Skip to content

Commit

Permalink
Do not pass anchor as a parameter to the route. (#28)
Browse files Browse the repository at this point in the history
* Do not pass anchor as a parameter to the route.

* Add withAnchor method for Link class, so we can an anchor dropdown to our schema

* Fix styling

* Some functionality for default linkpicker input.

* Use empty array if default state is null, since fillForm expects us to return an array and not null

* Revert default state, since it breaks normal behavior

* Add anchor link option in LinkCollection

* Fix styling

* Fix styling

* Set correct locale when fetching anchor list

* Fix styling

---------

Co-authored-by: jyrkidn <[email protected]>
Co-authored-by: Thibaut Degezelle <[email protected]>
Co-authored-by: thibautdeg <[email protected]>
  • Loading branch information
4 people authored Dec 27, 2024
1 parent ecbd87d commit 56d427c
Show file tree
Hide file tree
Showing 6 changed files with 213 additions and 22 deletions.
30 changes: 30 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,36 @@ If you want to add a "tel:" option in the link picker, you need to add a route l
LinkCollection::addTelephoneLink();
```

## Adding the 'anchor' link

If you want to add an "anchor" option in the link picker, you need to add a route like this, in your `AppServiceProvider`:

```php
LinkCollection::addAnchorLink();
```

Or change some of the default values:
```php
LinkCollection::addAnchorLink(
routeName: 'anchor',
group: 'General',
label: 'Anchor link',
description: 'Link to achor on current page',
);
```

This will fetch the [Architect](https://github.com/codedor/filament-architect) fields from the current page and use them as options for the anchor link. If you want to modify this behavior, you can add a `anchorList` method to your model:

```php
public function anchorList(): array
{
return [
'first-section' => 'First section',
'second-section' => 'Second section',
];
}
```

## The Link object

You can pass a callback to the `linkPicker()` function, this callback has one parameter called `$link`, this is a `Codedor\LinkPicker\Link` object. With this object you can configure the link to your needs.
Expand Down
8 changes: 8 additions & 0 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,11 @@ parameters:
message: "#^Access to an undefined property Livewire\\\\Component\\:\\:\\$mountedFormComponentActionsData\\.$#"
count: 1
path: src/Filament/LinkPickerInput.php
-
message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:getTranslation\\(\\)\\.$#"
count: 1
path: src/Filament/LinkPickerInput.php
-
message: "#^Call to an undefined method Illuminate\\\\Support\\\\HigherOrderCollectionProxy\\<string, Codedor\\\\FilamentArchitect\\\\Engines\\\\Architect, Illuminate\\\\Support\\\\Collection\\<\\(int\\|string\\)\\, Codedor\\\\FilamentArchitect\\\\Engines\\\\Architect\\>\\>\\:\\:anchorList\\(\\)\\.$#"
count: 1
path: src/LinkCollection.php
85 changes: 63 additions & 22 deletions src/Filament/LinkPickerInput.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@
use Filament\Forms\Get;
use Filament\Forms\Set;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Routing\Route;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Optional;
use Illuminate\Support\Reflector;
use Illuminate\Support\Str;
use ReflectionParameter;
Expand Down Expand Up @@ -49,18 +51,19 @@ protected function setUp(): void

$actionNestingIndex = array_key_last($livewire->mountedFormComponentActions);

$schema->each(function (Field $field) use (&$state, $statePath, $get, $actionNestingIndex, $livewire) {
$fieldStatePath = $field->statePath;
$schema
->each(function (Field $field) use (&$state, $statePath, $get, $actionNestingIndex, $livewire) {
$fieldStatePath = $field->statePath;

data_fill(
$state,
$fieldStatePath,
data_get(
$livewire->mountedFormComponentActionsData[$actionNestingIndex] ?? [],
"{$statePath}.{$fieldStatePath}"
) ?? $get("{$statePath}.{$fieldStatePath}") ?? null
);
});
data_fill(
$state,
$fieldStatePath,
data_get(
$livewire->mountedFormComponentActionsData[$actionNestingIndex] ?? [],
"{$statePath}.{$fieldStatePath}"
) ?? $get("{$statePath}.{$fieldStatePath}") ?? null
);
});

return $state;
})
Expand Down Expand Up @@ -177,18 +180,11 @@ private function getFormSchemaForRoute(?string $selectedRoute): Collection

$schema = $link->getSchema();

$routeParameters = $this->routeParameters($link->getRoute());

// If the schema is empty, we'll check if there are any parameters
if ($schema->isEmpty()) {
$route = $link->getRoute();

$schema = collect($route->signatureParameters())
->filter(function (ReflectionParameter $parameter) {
$className = Reflector::getParameterClassName($parameter);

return $parameter->getType()
&& class_exists($className)
&& is_subclass_of($className, Model::class);
})
$schema = $routeParameters
->map(function (ReflectionParameter $parameter) {
$model = Reflector::getParameterClassName($parameter);

Expand All @@ -198,19 +194,64 @@ private function getFormSchemaForRoute(?string $selectedRoute): Collection
->searchable()
->options($model::query()
->when(method_exists($model, 'linkPickerParameterQuery'), fn ($query) => $model::linkPickerParameterQuery($query))
->withoutGlobalScopes()
->pluck(
$model::$linkPickerTitleField ?? 'id',
(new $model)->getKeyName(),
)
);
)
->live();
});
}

if ($anchorData = $link->getWithAnchors()) {
if (! data_get($anchorData, 'parameter')) {
$anchorData['parameter'] = $routeParameters->first()->name;
}

if (! data_get($anchorData, 'model')) {
$anchorData['model'] = Reflector::getParameterClassName($routeParameters->first());
}

$schema->add(
Select::make('parameters.anchor')
->hidden(fn (Get $get) => ! $get("parameters.{$anchorData['parameter']}"))
->options(function (Get $get) use ($anchorData) {
/**
* @var Model $record
*/
$record = $anchorData['model']::find($get("parameters.{$anchorData['parameter']}"));

if (method_exists($record, 'isTranslatableAttribute') && $record->isTranslatableAttribute($anchorData['field'])) {
return optional($record->getTranslation($anchorData['field'], referer_locale()))->anchorList();
}

return $record->{$anchorData['field']}->anchorList();
})
);
}

return $schema
->prepend($routeField)
->add(
Checkbox::make('newTab')
->label(__('filament-link-picker::input.new tab label'))
);
}

protected function routeParameters(Optional|Route $route): Collection
{
if ($route instanceof Optional) {
return collect();
}

return collect($route->signatureParameters())
->filter(function (ReflectionParameter $parameter) {
$className = Reflector::getParameterClassName($parameter);

return $parameter->getType()
&& class_exists($className)
&& is_subclass_of($className, Model::class);
});
}
}
21 changes: 21 additions & 0 deletions src/Link.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ class Link

protected array $parameters = [];

protected ?array $withAnchors = null;

public function __construct(
protected string $routeName,
protected ?string $label = null,
Expand Down Expand Up @@ -87,6 +89,22 @@ public function getSchema(): Collection
));
}

public function withAnchors(string $field = 'body', ?string $model = null, ?string $parameter = null)
{
$this->withAnchors = [
'field' => $field,
'model' => $model,
'parameter' => $parameter,
];

return $this;
}

public function getWithAnchors(): ?array
{
return $this->withAnchors;
}

public function group(string $group): self
{
$this->group = $group;
Expand Down Expand Up @@ -184,6 +202,9 @@ public function resolveParameters(array $parameters)
{
$route = $this->getRoute();

if (isset($parameters['anchor'])) {
unset($parameters['anchor']);
}
$route->parameters = $parameters;
$bindings = $route->bindingFields();

Expand Down
80 changes: 80 additions & 0 deletions src/LinkCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,17 @@

namespace Codedor\LinkPicker;

use Codedor\FilamentArchitect\Engines\Architect;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Resources\Pages\Page;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Str;
use Throwable;

/**
* @template TKey of array-key
Expand Down Expand Up @@ -102,6 +109,79 @@ public function addTelephoneLink(
);
}

public function addAnchorLink(
string $routeName = 'anchor',
string $group = 'General',
string $label = 'Anchor link',
string $description = 'Link to achor on current page',
): self {
return $this->addLink(
Link::make($routeName, $label)
->group($group)
->description($description)
->schema(function () {
return Select::make('anchor')
->label('Anchor')
->options(function (?Model $record) {
$request = Request::create(request()->header('referer'));

$locale = $request->query('locale');

if (filled($locale)) {
$locale = Str::of($locale)
->before('-tab')
->after('-')
->toString();
}

if (! $record) {
try {
$route = Route::getRoutes()->match($request);

/** @var Page $component */
$component = Str::replace('@__invoke', '', $route->action['uses']);

$resource = $component::getResource();
$model = $resource::getModel();

$record = $model::find($route->parameter('record'));
} catch (Throwable $e) {
return [];
}
}

if (! $record) {
return [];
}

if (method_exists($record, 'anchorList')) {
return $record->anchorList();
}

if (class_exists(Architect::class)) {
if (method_exists($record, 'setLocale')) {
$record->setLocale($locale);
}

return collect($record?->getFillable())
->map(fn ($field) => $record->getAttributeValue($field))
->filter(fn ($value) => $value instanceof Architect)
->map->anchorList()
->flatMap(fn ($values) => $values);
}

return [];
})
->required();
})
->buildUsing(function (Link $link) {
$anchor = $link->getParameter('anchor');

return "#{$anchor}";
})
);
}

/**
* Returns a flattened collection of all routes.
*
Expand Down
11 changes: 11 additions & 0 deletions src/helpers.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,14 @@ function ($matches) {
);
}
}

if (! function_exists('referer_locale')) {
function referer_locale(): ?string
{
$referer = request()->headers->get('referer');

preg_match('/locale=-(.*)-tab/', $referer, $matches);

return $matches[1] ?? null;
}
}

0 comments on commit 56d427c

Please sign in to comment.