Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,15 @@ Below is a list of currently available options in the front matter.



#### Order
Allows you to define the sort order of the documentation page. Lower numbers appear first.

```md
---
order: 1
---
```

#### Group
Allows you to define the group (and it's title) of the documentation page.

Expand All @@ -384,6 +393,14 @@ parent: my-parent

So for a file in `docs/en/prologue/getting-started/intro.md`, the parent would be `getting-started`.

You can also use the **ID** of another documentation page to nest it under that page, regardless of directory structure. The ID is the path relative to the locale directory, separated by dots (e.g. `fleet.intro` for `fleet/intro.md`).

```md
---
parent: fleet.intro
---
```



And that's it! You've created a simple knowledge base inside Filament.
Expand Down
70 changes: 51 additions & 19 deletions src/Models/FlatfileNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ class FlatfileNode extends Model implements Documentable

public $incrementing = false;

protected static function booted(): void
{
static::addGlobalScope('locale', function (Builder $builder) {
$builder->where('locale', App::getLocale());
});
}

protected $schema = [
'id' => 'string',
'slug' => 'string',
Expand All @@ -39,6 +46,7 @@ class FlatfileNode extends Model implements Documentable
'data' => 'json',
'parent_id' => 'string',
'panel_id' => 'string',
'locale' => 'string',
];

protected function casts(): array
Expand All @@ -47,6 +55,7 @@ protected function casts(): array
'type' => NodeType::class,
'data' => 'array',
'active' => 'boolean',
'locale' => 'string',
];
}

Expand Down Expand Up @@ -165,6 +174,7 @@ public function getFallbackLocale(): string

public function toNavigationItem(): NavigationItem
{

if ($this->type === NodeType::Group) {
throw new \Exception('Cannot convert a group to a navigation item');
}
Expand All @@ -173,16 +183,20 @@ public function toNavigationItem(): NavigationItem
->icon($this->getIcon())
->sort($this->getOrder())
->url($this->getUrl())
->isActiveWhen(fn () => url()->current() === $this->getUrl())
->isActiveWhen(fn() => url()->current() === $this->getUrl())
;

if ($parent = $this->parent()) {


match ($parent->getType()) {
NodeType::Group => $item->group($parent->getTitle()),
default => $item
->parentItem($parent->getTitle())
->group($parent->parent()?->getTitle()),
->group($parent->parent()?->getTitle() ?? $parent->getData()['group'] ?? null),
};
} elseif ($group = ($this->getData()['group'] ?? null)) {
$item->group($group);
}

return $item;
Expand All @@ -195,7 +209,7 @@ public function toNavigationGroup(): NavigationGroup
}

$canHaveIcon = $this
->children()->where(fn (FlatfileNode $child) => $child->children()->isNotEmpty())
->children()->where(fn(FlatfileNode $child) => $child->children()->isNotEmpty())
->isEmpty()
;

Expand All @@ -216,7 +230,7 @@ public function getAnchors(): array
$slug = $node->getSlug();
$next = $node->next();

if (! method_exists($next, 'getLiteral')) {
if (!method_exists($next, 'getLiteral')) {
continue;
}

Expand All @@ -234,28 +248,45 @@ public function getRows(): array
$paths = app(KnowledgeBaseRegistry::class)->getDocsPaths();

foreach ($paths as $panelId => $path) {
// Get localized docs path
$localizedPath = str($path)
->rtrim(DIRECTORY_SEPARATOR)
->append(DIRECTORY_SEPARATOR)
->append($this->getLocale())
// Scan for all locale directories
$locales = collect(File::directories($path))
->map(fn(string $dir) => basename($dir))
;

// Get fallback locale docs path
if (! File::exists($localizedPath)) {
foreach ($locales as $locale) {
$localizedPath = str($path)
->rtrim(DIRECTORY_SEPARATOR)
->append(DIRECTORY_SEPARATOR)
->append($this->getFallbackLocale())
->append($locale)
;
}

// No docs present
if (! File::exists($localizedPath)) {
continue;
}
if (!File::exists($localizedPath)) {
continue;
}

$localeRows = FlatfileParser::make($panelId, $localizedPath)->get();

// Filter out auto-generated directory groups
$localeRows = $localeRows->reject(fn(array $row) => $row['type'] === NodeType::Group);

$rows->push(...FlatfileParser::make($panelId, $localizedPath)->get());
// Namespace IDs with locale to prevents collision and add locale data
$localeRows = $localeRows->map(function (array $row) use ($locale, $panelId) {
$row['id'] = "$locale.{$row['id']}";
$row['locale'] = $locale;

// Fix parent linkage from frontmatter
$data = json_decode($row['data'], true);
if (isset($data['parent'])) {
$row['parent_id'] = "$locale.{$panelId}.{$data['parent']}";
} elseif ($row['parent_id']) {
$row['parent_id'] = "$locale.{$row['parent_id']}";
}

return $row;
});

$rows->push(...$localeRows);
}
}

return $rows->toArray();
Expand All @@ -264,14 +295,15 @@ public function getRows(): array
public function scopeType(Builder $query, NodeType ...$types): Builder
{
return $query->where(
fn (Builder $query) => collect($types)->each(fn (NodeType $type) => $query->orWhere('type', $type))
fn(Builder $query) => collect($types)->each(fn(NodeType $type) => $query->orWhere('type', $type))
);
}

public function resolveRouteBindingQuery($query, $value, $field = null): \Illuminate\Contracts\Database\Eloquent\Builder
{
return parent::resolveRouteBindingQuery($query, $value, $field)
->where('panel_id', KnowledgeBase::panel()->getId())
->where('locale', App::getLocale())
;
}

Expand Down