diff --git a/README.md b/README.md index 028b12d..5797518 100644 --- a/README.md +++ b/README.md @@ -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. @@ -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. diff --git a/src/Models/FlatfileNode.php b/src/Models/FlatfileNode.php index 9498f7c..f35c715 100644 --- a/src/Models/FlatfileNode.php +++ b/src/Models/FlatfileNode.php @@ -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', @@ -39,6 +46,7 @@ class FlatfileNode extends Model implements Documentable 'data' => 'json', 'parent_id' => 'string', 'panel_id' => 'string', + 'locale' => 'string', ]; protected function casts(): array @@ -47,6 +55,7 @@ protected function casts(): array 'type' => NodeType::class, 'data' => 'array', 'active' => 'boolean', + 'locale' => 'string', ]; } @@ -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'); } @@ -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; @@ -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() ; @@ -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; } @@ -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(); @@ -264,7 +295,7 @@ 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)) ); } @@ -272,6 +303,7 @@ public function resolveRouteBindingQuery($query, $value, $field = null): \Illumi { return parent::resolveRouteBindingQuery($query, $value, $field) ->where('panel_id', KnowledgeBase::panel()->getId()) + ->where('locale', App::getLocale()) ; }