diff --git a/docs/customization.md b/docs/customization.md
index fb5a3d7a35e..8529e7fb290 100644
--- a/docs/customization.md
+++ b/docs/customization.md
@@ -83,7 +83,7 @@ One of my (the author's) favourite features with Hyde is its automatic navigatio
#### How it works:
The sidebar works by creating a list of all the documentation pages.
-The navigation menu is a bit more sophisticated, it adds all the top-level Blade and Markdown pages. It also adds an automatic link to the docs if there is an `index.md` or `readme.md` in the `_docs` directory.
+The navigation menu is a bit more sophisticated, it adds all the top-level Blade and Markdown pages. It also adds an automatic link to the docs if there is an `index.md` in the `_docs` directory.
#### Reordering Items
Sadly, Hyde is not intelligent enough to determine what order items should be in (blame Dr Jekyll for this), so you will probably want to set a custom order.
diff --git a/docs/documentation-pages.md b/docs/documentation-pages.md
index f7a6bf419c5..612b2935c96 100644
--- a/docs/documentation-pages.md
+++ b/docs/documentation-pages.md
@@ -17,9 +17,9 @@ What is "the rest", you may ask? Well, for starters:
- Hyde compiles your Markdown content into a beautiful static HTML page based on [the Lagrafo frontend](https://github.com/caendesilva/lagrafo)
- A sidebar (which is responsive) is automatically created based on your Markdown files
- - If you have an `index.md` or `readme.md` in the `_docs/` directory, it will be used as the sidebar header
+ - If you have an `index.md` in the `_docs/` directory, it will be used as the sidebar header
- You can even [customize the order and labels](#sidebar-page-order) of sidebar items
-- If you have an `index.md` or `readme.md` in the `_docs/` directory,
+- If you have an `index.md` in the `_docs/` directory,
a link to it will be added to the site navigation menu named "Docs".
- If you have a Torchlight API token in your .env file, Hyde will even automatically enable Syntax Highlighting for you.
See more about this in the [extensions page](extensions.html#torchlight).
@@ -171,7 +171,7 @@ for example to specify a version like the Hyde docs does, you can specify the ou
### Automatic navigation menu
-By default, a link to the documentation page is added to the navigation menu when an index.md or readme.md file is found in the `_docs` directory.
+By default, a link to the documentation page is added to the navigation menu when an index.md file is found in the `_docs` directory.
In version v0.38.0-beta and lower, this link had the internal priority of 500 putting it to the left of the automatic menu. In v0.39.0-beta and higher, the priority is set to 1000 to be placed at the end of the menu. See the reasoning behind this in [this GitHub issue](https://github.com/hydephp/develop/issues/24).
You can customize the priority using the following config value in the `config/docs.php` file:
diff --git a/packages/framework/resources/views/components/navigation/navigation-link.blade.php b/packages/framework/resources/views/components/navigation/navigation-link.blade.php
index dc58cd710b9..133a396679b 100644
--- a/packages/framework/resources/views/components/navigation/navigation-link.blade.php
+++ b/packages/framework/resources/views/components/navigation/navigation-link.blade.php
@@ -1,6 +1,11 @@
-getRoute()->getRouteKey() === $item->route->getRouteKey();
+@endphp
+
+ $item['current']
+ , 'border-l-4 border-indigo-500 md:border-none font-medium -ml-6 pl-5 md:ml-0 md:pl-0 bg-gray-100 dark:bg-gray-800 md:bg-transparent dark:md:bg-transparent'=> $isCurrent
])>
- {{ $item['title'] }}
+ {{ $item->title }}
\ No newline at end of file
diff --git a/packages/framework/resources/views/components/navigation/navigation-toggle-button.blade.php b/packages/framework/resources/views/components/navigation/navigation-toggle-button.blade.php
deleted file mode 100644
index e69de29bb2d..00000000000
diff --git a/packages/framework/resources/views/layouts/navigation.blade.php b/packages/framework/resources/views/layouts/navigation.blade.php
index 9df79fdebc3..4d33f6ca874 100644
--- a/packages/framework/resources/views/layouts/navigation.blade.php
+++ b/packages/framework/resources/views/layouts/navigation.blade.php
@@ -1,6 +1,6 @@
@php
-$links = Hyde\Framework\Actions\GeneratesNavigationMenu::getNavigationLinks($currentPage);
-$homeRoute = ($links[array_search('Home', array_column($links, 'title'))])['route'] ?? Hyde::pageLink('index.html');
+$navigation = \Hyde\Framework\Modules\Navigation\NavigationMenu::create($page->getRoute());
+$homeRoute = $navigation->homeRoute->getLink($currentPage);
@endphp
@@ -21,7 +21,7 @@
- @foreach ($links as $item)
+ @foreach ($navigation->items as $item)
@include('hyde::components.navigation.navigation-link')
diff --git a/packages/framework/src/Actions/GeneratesNavigationMenu.php b/packages/framework/src/Actions/GeneratesNavigationMenu.php
deleted file mode 100644
index 87cf69efefe..00000000000
--- a/packages/framework/src/Actions/GeneratesNavigationMenu.php
+++ /dev/null
@@ -1,188 +0,0 @@
-currentPage = $currentPage;
-
- $this->links = $this->getLinks();
- }
-
- /**
- * Create the link array.
- *
- * @return array
- */
- protected function getLinks(): array
- {
- $links = $this->getLinksFromConfig();
-
- // Automatically add top level pages
- foreach ($this->getListOfCustomPages() as $slug) {
- $title = $this->getTitleFromSlug($slug);
- // Only add the automatic link if it is not present in the config array
- if (! in_array($title, array_column($links, 'title'))) {
- $links[] = [
- 'title' => $title,
- 'route' => $this->getRelativeRoutePathForSlug($slug),
- 'current' => $this->currentPage == $slug,
- 'priority' => $slug == 'index' ? 100 : 999,
- ];
- }
- }
-
- // Add extra links
-
- // If the documentation feature is enabled...
- if (Features::hasDocumentationPages()) {
- // And there is no link to the docs...
- if (! in_array('Docs', array_column($links, 'title'))) {
- // But a suitable file exists...
- if (file_exists(Hyde::getDocumentationPagePath('/index.md')) || file_exists(Hyde::getDocumentationPagePath('/readme.md'))) {
- // Then we can add a link.
- $links[] = [
- 'title' => 'Docs',
- 'route' => $this->getRelativeRoutePathForSlug(
- file_exists(Hyde::getDocumentationPagePath('/index.md'))
- ? DocumentationPage::getOutputDirectory().'/index'
- : DocumentationPage::getOutputDirectory().'/readme'
- ),
- 'current' => false,
- 'priority' => config('docs.navigation_link_priority', 1000),
- ];
- }
- }
- }
-
- // Remove config defined blacklisted links
- foreach ($links as $key => $link) {
- if (in_array(Str::slug($link['title']), config('hyde.navigation_menu_blacklist', []))) {
- unset($links[$key]);
- }
- }
-
- // Sort
-
- $columns = array_column($links, 'priority');
- array_multisort($columns, SORT_ASC, $links);
-
- return $links;
- }
-
- /**
- * Get the custom navigation links from the config, if there are any.
- *
- * @return array
- */
- public function getLinksFromConfig(): array
- {
- $configLinks = config('hyde.navigation_menu_links', []);
-
- $links = [];
-
- if (sizeof($configLinks) > 0) {
- foreach ($configLinks as $link) {
- $links[] = [
- 'title' => $link['title'],
- 'route' => $link['destination'] ?? $this->getRelativeRoutePathForSlug($link['slug']),
- 'current' => isset($link['slug']) && $this->currentPage == $link['slug'],
- 'priority' => $link['priority'] ?? 999,
- ];
- }
- }
-
- return $links;
- }
-
- /**
- * Get the page title.
- *
- * @param string $slug
- * @return string
- */
- public function getTitleFromSlug(string $slug): string
- {
- if ($slug == 'index') {
- return 'Home';
- }
-
- return Hyde::makeTitle($slug);
- }
-
- /**
- * Get a list of all the top level pages.
- *
- * @return array
- */
- protected function getListOfCustomPages(): array
- {
- return array_unique(
- array_merge(
- CollectionService::getBladePageFiles(),
- CollectionService::getMarkdownPageFiles()
- )
- );
- }
-
- /**
- * Inject the proper number of `../` before the links.
- *
- * @param string $slug
- * @return string
- */
- protected function getRelativeRoutePathForSlug(string $slug): string
- {
- return Hyde::relativeLink($slug.'.html', $this->currentPage);
- }
-
- /**
- * Static helper to get the array of navigation links.
- *
- * @param string $currentPage
- * @return array
- */
- public static function getNavigationLinks(string $currentPage = 'index'): array
- {
- $generator = new self($currentPage);
-
- return $generator->links;
- }
-}
diff --git a/packages/framework/src/Concerns/CanBeInNavigation.php b/packages/framework/src/Concerns/CanBeInNavigation.php
new file mode 100644
index 00000000000..117ad7aec5b
--- /dev/null
+++ b/packages/framework/src/Concerns/CanBeInNavigation.php
@@ -0,0 +1,118 @@
+slug === 'index';
+ }
+
+ if ($this instanceof AbstractMarkdownPage) {
+ if ($this->markdown->matter('navigation.hidden', false)) {
+ return false;
+ }
+ }
+
+ if (in_array($this->slug, config('hyde.navigation.exclude', ['404']))) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * The relative priority, determining the position of the item in the menu.
+ *
+ * @return int
+ */
+ public function navigationMenuPriority(): int
+ {
+ if ($this instanceof AbstractMarkdownPage) {
+ if ($this->matter('navigation.priority') !== null) {
+ return $this->matter('navigation.priority');
+ }
+ }
+
+ if (array_key_exists($this->slug, config('hyde.navigation.order', []))) {
+ return (int) config('hyde.navigation.order.'.$this->slug);
+ }
+
+ if ($this instanceof DocumentationPage) {
+ return 100;
+ }
+
+ if ($this->slug === 'index') {
+ return 0;
+ }
+
+ if ($this->slug === 'posts') {
+ return 10;
+ }
+
+ return 1000;
+ }
+
+ /**
+ * The page title to display in the navigation menu.
+ *
+ * @return string
+ */
+ public function navigationMenuTitle(): string
+ {
+ if ($this instanceof AbstractMarkdownPage) {
+ if ($this->matter('navigation.title') !== null) {
+ return $this->matter('navigation.title');
+ }
+
+ if ($this->matter('title') !== null) {
+ return $this->matter('title');
+ }
+ }
+
+ if ($this->slug === 'index') {
+ if ($this instanceof DocumentationPage) {
+ return 'Docs';
+ }
+
+ return 'Home';
+ }
+
+ if (isset($this->title) && ! blank($this->title)) {
+ return $this->title;
+ }
+
+ return Hyde::makeTitle($this->slug);
+ }
+
+ /**
+ * Not yet implemented.
+ *
+ * If an item returns a route collection,
+ * it will automatically be made into a dropdown.
+ *
+ * @return \Illuminate\Support\Collection<\Hyde\Framework\Modules\Routing\Route>
+ */
+ // public function navigationMenuChildren(): Collection;
+}
diff --git a/packages/framework/src/Contracts/AbstractMarkdownPage.php b/packages/framework/src/Contracts/AbstractMarkdownPage.php
index e15aaa80ff2..866af51b201 100644
--- a/packages/framework/src/Contracts/AbstractMarkdownPage.php
+++ b/packages/framework/src/Contracts/AbstractMarkdownPage.php
@@ -48,4 +48,9 @@ public function markdown(): MarkdownDocument
{
return $this->markdown;
}
+
+ public function matter(string $key = null, mixed $default = null): mixed
+ {
+ return $this->markdown->matter($key, $default);
+ }
}
diff --git a/packages/framework/src/Contracts/AbstractPage.php b/packages/framework/src/Contracts/AbstractPage.php
index 0d9b678a691..d44ea2e4cb4 100644
--- a/packages/framework/src/Contracts/AbstractPage.php
+++ b/packages/framework/src/Contracts/AbstractPage.php
@@ -2,9 +2,9 @@
namespace Hyde\Framework\Contracts;
+use Hyde\Framework\Concerns\CanBeInNavigation;
use Hyde\Framework\Concerns\HasPageMetadata;
use Hyde\Framework\Modules\Routing\Route;
-use Hyde\Framework\Modules\Routing\RouteContract;
use Hyde\Framework\Services\CollectionService;
use Illuminate\Support\Collection;
@@ -22,6 +22,7 @@
abstract class AbstractPage implements PageContract
{
use HasPageMetadata;
+ use CanBeInNavigation;
public static string $sourceDirectory;
public static string $outputDirectory;
@@ -119,7 +120,7 @@ public function getCurrentPagePath(): string
}
/** @inheritDoc */
- public function getRoute(): RouteContract
+ public function getRoute(): Route
{
return new Route($this);
}
diff --git a/packages/framework/src/Facades/Route.php b/packages/framework/src/Facades/Route.php
index 502481be458..e2a9174edb1 100644
--- a/packages/framework/src/Facades/Route.php
+++ b/packages/framework/src/Facades/Route.php
@@ -4,7 +4,6 @@
use Hyde\Framework\Contracts\PageContract;
use Hyde\Framework\Modules\Routing\Route as RouteModel;
-use Hyde\Framework\Modules\Routing\RouteContract;
use Hyde\Framework\Modules\Routing\RouteFacadeContract;
use Illuminate\Support\Collection;
@@ -15,25 +14,25 @@
class Route implements RouteFacadeContract
{
/** @inheritDoc */
- public static function get(string $routeKey): ?RouteContract
+ public static function get(string $routeKey): ?RouteModel
{
return RouteModel::get($routeKey);
}
/** @inheritDoc */
- public static function getFromKey(string $routeKey): ?RouteContract
+ public static function getFromKey(string $routeKey): ?RouteModel
{
return RouteModel::getFromKey($routeKey);
}
/** @inheritDoc */
- public static function getFromSource(string $sourceFilePath): ?RouteContract
+ public static function getFromSource(string $sourceFilePath): ?RouteModel
{
return RouteModel::getFromSource($sourceFilePath);
}
/** @inheritDoc */
- public static function getFromModel(PageContract $page): ?RouteContract
+ public static function getFromModel(PageContract $page): ?RouteModel
{
return RouteModel::getFromModel($page);
}
diff --git a/packages/framework/src/Models/NavItem.php b/packages/framework/src/Models/NavItem.php
new file mode 100644
index 00000000000..9580b9d41ad
--- /dev/null
+++ b/packages/framework/src/Models/NavItem.php
@@ -0,0 +1,64 @@
+route = $route;
+ $this->title = $title;
+ $this->priority = $priority;
+ $this->hidden = $hidden;
+ }
+
+ public static function fromRoute(RouteContract $route): static
+ {
+ return new static(
+ $route,
+ $route->getSourceModel()->navigationMenuTitle(),
+ $route->getSourceModel()->navigationMenuPriority(),
+ ! $route->getSourceModel()->showInNavigation()
+ );
+ }
+
+ /**
+ * Resolve a link to the navigation item.
+ *
+ * @param string $currentPage
+ * @return string
+ */
+ public function resolveLink(string $currentPage = ''): string
+ {
+ return $this->route->getLink($currentPage);
+ }
+
+ public function __toString(): string
+ {
+ return $this->resolveLink();
+ }
+}
diff --git a/packages/framework/src/Models/Parsers/MarkdownPageParser.php b/packages/framework/src/Models/Parsers/MarkdownPageParser.php
index b0427d75b2c..309fde1f0ed 100644
--- a/packages/framework/src/Models/Parsers/MarkdownPageParser.php
+++ b/packages/framework/src/Models/Parsers/MarkdownPageParser.php
@@ -19,6 +19,7 @@ class MarkdownPageParser extends AbstractPageParser
/** @deprecated v0.44.x (handled in constructor) */
public string $title = '';
+ public array $matter;
public string $body;
public function execute(): void
@@ -27,13 +28,14 @@ public function execute(): void
Hyde::getMarkdownPagePath("/$this->slug.md")
))->get();
+ $this->matter = $document->matter;
$this->body = $document->body;
}
public function get(): MarkdownPage
{
return new MarkdownPage(
- matter: [],
+ matter: $this->matter,
body: $this->body,
title: $this->title,
slug: $this->slug
diff --git a/packages/framework/src/Modules/Navigation/NavigationMenu.php b/packages/framework/src/Modules/Navigation/NavigationMenu.php
new file mode 100644
index 00000000000..e468c30b6de
--- /dev/null
+++ b/packages/framework/src/Modules/Navigation/NavigationMenu.php
@@ -0,0 +1,70 @@
+items = new Collection();
+ $this->homeRoute = $this->getHomeRoute();
+ }
+
+ public static function create(RouteContract $currentRoute): static
+ {
+ return (new static())->setCurrentRoute($currentRoute)->generate()->filter()->sort();
+ }
+
+ public function setCurrentRoute(RouteContract $currentRoute): self
+ {
+ $this->currentRoute = $currentRoute;
+
+ return $this;
+ }
+
+ public function generate(): self
+ {
+ Router::getInstance()->getRoutes()->each(function (Route $route) {
+ $this->items->push(NavItem::fromRoute($route));
+ });
+
+ return $this;
+ }
+
+ public function filter(): self
+ {
+ $this->items = $this->items->reject(function (NavItem $item) {
+ return $item->hidden;
+ })->values();
+
+ return $this;
+ }
+
+ public function sort(): self
+ {
+ $this->items = $this->items->sortBy('priority')->values();
+
+ return $this;
+ }
+
+ /** @internal */
+ public function getHomeRoute(): Route
+ {
+ return Route::get('index') ?? Route::get('404') ?? new Route(new MarkdownPage);
+ }
+}
diff --git a/packages/framework/src/Modules/Routing/Route.php b/packages/framework/src/Modules/Routing/Route.php
index 355a4942745..b112f876945 100644
--- a/packages/framework/src/Modules/Routing/Route.php
+++ b/packages/framework/src/Modules/Routing/Route.php
@@ -94,7 +94,7 @@ public static function getFromSource(string $sourceFilePath): ?static
}
/** @inheritDoc */
- public static function getFromModel(PageContract $page): ?RouteContract
+ public static function getFromModel(PageContract $page): static
{
return $page->getRoute();
}
diff --git a/packages/framework/tests/Feature/Concerns/CanBeInNavigationTest.php b/packages/framework/tests/Feature/Concerns/CanBeInNavigationTest.php
new file mode 100644
index 00000000000..e29ad54df7a
--- /dev/null
+++ b/packages/framework/tests/Feature/Concerns/CanBeInNavigationTest.php
@@ -0,0 +1,237 @@
+mock(MarkdownPost::class)->makePartial();
+
+ $this->assertFalse($page->showInNavigation());
+ }
+
+ public function test_show_in_navigation_returns_true_for_documentation_page_if_slug_is_index()
+ {
+ $page = $this->mock(DocumentationPage::class)->makePartial();
+ $page->slug = 'index';
+
+ $this->assertTrue($page->showInNavigation());
+ }
+
+ public function test_show_in_navigation_returns_false_for_documentation_page_if_slug_is_not_index()
+ {
+ $page = $this->mock(DocumentationPage::class)->makePartial();
+ $page->slug = 'not-index';
+
+ $this->assertFalse($page->showInNavigation());
+ }
+
+ public function test_show_in_navigation_returns_false_for_abstract_markdown_page_if_matter_navigation_hidden_is_true()
+ {
+ $page = $this->mock(AbstractMarkdownPage::class)->makePartial();
+ $page->markdown = $this->mock(MarkdownDocument::class)->makePartial();
+ $page->markdown->shouldReceive('matter')->with('navigation.hidden', false)->andReturn(true);
+
+ $this->assertFalse($page->showInNavigation());
+ }
+
+ public function test_show_in_navigation_returns_true_for_abstract_markdown_page_if_matter_navigation_hidden_is_false()
+ {
+ $page = $this->mock(AbstractMarkdownPage::class)->makePartial();
+ $page->slug = 'foo';
+ $page->markdown = $this->mock(MarkdownDocument::class)->makePartial();
+ $page->markdown->shouldReceive('matter')->with('navigation.hidden', false)->andReturn(false);
+
+ $this->assertTrue($page->showInNavigation());
+ }
+
+ public function test_show_in_navigation_returns_true_for_abstract_markdown_page_if_matter_navigation_hidden_is_not_set()
+ {
+ $page = $this->mock(AbstractMarkdownPage::class)->makePartial();
+ $page->slug = 'foo';
+ $page->markdown = $this->mock(MarkdownDocument::class)->makePartial();
+ $page->markdown->shouldReceive('matter')->with('navigation.hidden', false)->andReturn(null);
+
+ $this->assertTrue($page->showInNavigation());
+ }
+
+ public function test_show_in_navigation_returns_false_if_slug_is_present_in_config_hyde_navigation_exclude()
+ {
+ $page = $this->mock(MarkdownPage::class)->makePartial();
+ $page->markdown = new MarkdownDocument();
+ $page->slug = 'foo';
+
+ $this->assertTrue($page->showInNavigation());
+
+ config(['hyde.navigation.exclude' => ['foo']]);
+ $this->assertFalse($page->showInNavigation());
+ }
+
+ public function test_show_in_navigation_returns_true_if_slug_is_404()
+ {
+ $page = $this->mock(MarkdownPage::class)->makePartial();
+ $page->markdown = new MarkdownDocument();
+ $page->slug = '404';
+
+ $this->assertFalse($page->showInNavigation());
+ }
+
+ public function test_show_in_navigation_defaults_to_true_if_all_checks_pass()
+ {
+ $page = $this->mock(MarkdownPage::class)->makePartial();
+ $page->markdown = new MarkdownDocument();
+ $page->slug = 'foo';
+
+ $this->assertTrue($page->showInNavigation());
+ }
+
+ public function test_navigation_menu_priority_returns_front_matter_value_of_navigation_priority_if_abstract_markdown_page_and_not_null()
+ {
+ $page = $this->mock(AbstractMarkdownPage::class)->makePartial();
+ $page->markdown = $this->mock(MarkdownDocument::class)->makePartial();
+ $page->markdown->shouldReceive('matter')->with('navigation.priority', null)->andReturn(1);
+ $this->assertEquals(1, $page->navigationMenuPriority());
+ }
+
+ public function test_navigation_menu_priority_returns_specified_config_value_if_slug_exists_in_config_hyde_navigation_order()
+ {
+ $page = $this->mock(MarkdownPage::class)->makePartial();
+ $page->markdown = new MarkdownDocument();
+ $page->slug = 'foo';
+
+ $this->assertEquals(1000, $page->navigationMenuPriority());
+
+ config(['hyde.navigation.order' => ['foo' => 1]]);
+ $this->assertEquals(1, $page->navigationMenuPriority());
+ }
+
+ public function test_navigation_menu_priority_gives_precedence_to_front_matter_over_config_hyde_navigation_order()
+ {
+ $page = $this->mock(AbstractMarkdownPage::class)->makePartial();
+ $page->markdown = $this->mock(MarkdownDocument::class)->makePartial();
+ $page->markdown->shouldReceive('matter')->with('navigation.priority', null)->andReturn(1);
+ $page->slug = 'foo';
+
+ $this->assertEquals(1, $page->navigationMenuPriority());
+
+ config(['hyde.navigation.order' => ['foo' => 2]]);
+ $this->assertEquals(1, $page->navigationMenuPriority());
+ }
+
+ public function test_navigation_menu_priority_returns_100_for_documentation_page()
+ {
+ $page = $this->mock(DocumentationPage::class)->makePartial();
+ $page->markdown = new MarkdownDocument();
+ $page->slug = 'foo';
+
+ $this->assertEquals(100, $page->navigationMenuPriority());
+ }
+
+ public function test_navigation_menu_priority_returns_0_if_slug_is_index()
+ {
+ $page = $this->mock(MarkdownPage::class)->makePartial();
+ $page->markdown = new MarkdownDocument();
+ $page->slug = 'index';
+
+ $this->assertEquals(0, $page->navigationMenuPriority());
+ }
+
+ public function test_navigation_menu_priority_does_not_return_0_if_slug_is_index_but_model_is_documentation_page()
+ {
+ $page = $this->mock(DocumentationPage::class)->makePartial();
+ $page->markdown = new MarkdownDocument();
+ $page->slug = 'index';
+
+ $this->assertEquals(100, $page->navigationMenuPriority());
+ }
+
+ public function test_navigation_menu_priority_returns_10_if_slug_is_posts()
+ {
+ $page = $this->mock(MarkdownPage::class)->makePartial();
+ $page->markdown = new MarkdownDocument();
+ $page->slug = 'posts';
+
+ $this->assertEquals(10, $page->navigationMenuPriority());
+ }
+
+ public function test_navigation_menu_priority_defaults_to_1000_if_no_other_conditions_are_met()
+ {
+ $page = $this->mock(MarkdownPage::class)->makePartial();
+ $page->markdown = new MarkdownDocument();
+ $page->slug = 'foo';
+
+ $this->assertEquals(1000, $page->navigationMenuPriority());
+ }
+
+ public function test_navigation_menu_title_returns_navigation_title_matter_if_set()
+ {
+ $page = $this->mock(AbstractMarkdownPage::class)->makePartial();
+ $page->markdown = $this->mock(MarkdownDocument::class)->makePartial();
+ $page->markdown->shouldReceive('matter')->with('navigation.title', null)->andReturn('foo');
+ $this->assertEquals('foo', $page->navigationMenuTitle());
+ }
+
+ public function test_navigation_menu_title_returns_title_matter_if_set()
+ {
+ $page = $this->mock(AbstractMarkdownPage::class)->makePartial();
+ $page->markdown = $this->mock(MarkdownDocument::class)->makePartial();
+ $page->markdown->matter = ['title' => 'foo'];
+ $this->assertEquals('foo', $page->navigationMenuTitle());
+ }
+
+ public function test_navigation_menu_title_navigation_title_has_precedence_over_title()
+ {
+ $page = $this->mock(AbstractMarkdownPage::class)->makePartial();
+ $page->markdown = $this->mock(MarkdownDocument::class)->makePartial();
+ $page->markdown->matter = ['title' => 'foo', 'navigation.title' => 'bar'];
+ $this->assertEquals('bar', $page->navigationMenuTitle());
+ }
+
+ public function test_navigation_menu_title_returns_docs_if_slug_is_index_and_model_is_documentation_page()
+ {
+ $page = $this->mock(DocumentationPage::class)->makePartial();
+ $page->markdown = new MarkdownDocument();
+ $page->slug = 'index';
+
+ $this->assertEquals('Docs', $page->navigationMenuTitle());
+ }
+
+ public function test_navigation_menu_title_returns_home_if_slug_is_index_and_model_is_not_documentation_page()
+ {
+ $page = $this->mock(MarkdownPage::class)->makePartial();
+ $page->markdown = new MarkdownDocument();
+ $page->slug = 'index';
+
+ $this->assertEquals('Home', $page->navigationMenuTitle());
+ }
+
+ public function test_navigation_menu_title_returns_title_if_title_is_set_and_not_empty()
+ {
+ $page = $this->mock(MarkdownPage::class)->makePartial();
+ $page->markdown = new MarkdownDocument();
+ $page->title = 'foo';
+ $page->slug = 'bar';
+
+ $this->assertEquals('foo', $page->navigationMenuTitle());
+ }
+
+ public function test_navigation_menu_title_falls_back_to_hyde_make_title_from_slug()
+ {
+ $page = $this->mock(MarkdownPage::class)->makePartial();
+ $page->markdown = new MarkdownDocument();
+ $page->slug = 'foo';
+
+ $this->assertEquals('Foo', $page->navigationMenuTitle());
+ }
+}
diff --git a/packages/framework/tests/Feature/GeneratesNavigationMenuTest.php b/packages/framework/tests/Feature/GeneratesNavigationMenuTest.php
deleted file mode 100644
index 51bbf766b50..00000000000
--- a/packages/framework/tests/Feature/GeneratesNavigationMenuTest.php
+++ /dev/null
@@ -1,78 +0,0 @@
-assertIsArray($array);
- }
-
- public function test_generated_links_include_documentation_pages()
- {
- Hyde::touch(('_docs/index.md'));
-
- $generator = new GeneratesNavigationMenu('index');
- $this->assertIsArray($generator->links);
-
- $this->assertContains('docs/index.html', Arr::flatten($generator->links));
-
- unlink(Hyde::path('_docs/index.md'));
- }
-
- public function test_get_links_from_config_method()
- {
- $generator = new GeneratesNavigationMenu(currentPage: 'foo/bar');
-
- Config::set('hyde.navigation_menu_links', [
- [
- 'title' => 'GNMTestExt',
- 'destination' => 'https://example.org/test',
- 'priority' => 800,
- ],
- [
- 'title' => 'GNMTestInt',
- 'slug' => 'foo/bar',
- ],
- ]);
-
- $result = $generator->getLinksFromConfig();
-
- $this->assertCount(2, $result);
-
- $this->assertEquals($result[0], [
- 'title' => 'GNMTestExt',
- 'route' => 'https://example.org/test',
- 'current' => false,
- 'priority' => 800,
- ]);
-
- $this->assertEquals([
- 'title' => 'GNMTestInt',
- 'route' => '../foo/bar.html',
- 'current' => true,
- 'priority' => 999,
- ], $result[1]);
- }
-
- public function test_files_starting_with_underscores_are_ignored()
- {
- Hyde::touch(('_pages/_foo.md'));
- Hyde::touch(('_pages/_foo.blade.php'));
-
- $array = GeneratesNavigationMenu::getNavigationLinks();
- $this->assertIsArray($array);
- $this->assertCount(1, $array);
-
- unlink(Hyde::path('_pages/_foo.md'));
- unlink(Hyde::path('_pages/_foo.blade.php'));
- }
-}
diff --git a/packages/framework/tests/Feature/NavigationMenuTest.php b/packages/framework/tests/Feature/NavigationMenuTest.php
new file mode 100644
index 00000000000..5656bda5d65
--- /dev/null
+++ b/packages/framework/tests/Feature/NavigationMenuTest.php
@@ -0,0 +1,140 @@
+assertInstanceOf(NavigationMenu::class, $menu);
+ }
+
+ public function test_home_route()
+ {
+ $menu = new NavigationMenu();
+
+ $this->assertInstanceOf(Route::class, $menu->homeRoute);
+ $this->assertEquals('index', $menu->homeRoute->getRouteKey());
+ }
+
+ public function test_set_current_route()
+ {
+ $menu = new NavigationMenu();
+
+ $this->assertFalse(isset($menu->currentRoute));
+ $menu->setCurrentRoute(Route::get('index'));
+ $this->assertTrue(isset($menu->currentRoute));
+ $this->assertInstanceOf(Route::class, $menu->currentRoute);
+ $this->assertEquals('index', $menu->currentRoute->getRouteKey());
+ }
+
+ public function test_generate_method_creates_collection_of_nav_items()
+ {
+ $menu = new NavigationMenu();
+
+ $this->assertInstanceOf(Collection::class, $menu->items);
+ $this->assertEmpty($menu->items);
+ }
+
+ public function test_generate_method_adds_route_items()
+ {
+ $menu = new NavigationMenu();
+ $menu->generate();
+
+ $expected = collect([
+ NavItem::fromRoute(Route::get('404')),
+ NavItem::fromRoute(Route::get('index')),
+ ]);
+
+ $this->assertEquals($expected, $menu->items);
+ }
+
+ public function test_sort_method_sorts_items_by_priority()
+ {
+ $menu = new NavigationMenu();
+ $menu->generate()->sort();
+
+ $expected = collect([
+ NavItem::fromRoute(Route::get('index')),
+ NavItem::fromRoute(Route::get('404')),
+ ]);
+
+ $this->assertEquals($expected, $menu->items);
+ }
+
+ public function test_filter_method_removes_items_with_hidden_property_set_to_true()
+ {
+ $menu = new NavigationMenu();
+ $menu->generate()->filter();
+
+ $expected = collect([
+ NavItem::fromRoute(Route::get('index')),
+ ]);
+
+ $this->assertEquals($expected, $menu->items);
+ }
+
+ public function test_static_create_method_creates_new_processed_collection()
+ {
+ Hyde::touch('_pages/foo.md');
+ $menu = NavigationMenu::create(Route::get('index'));
+
+ $this->assertInstanceOf(NavigationMenu::class, $menu);
+ $this->assertEquals(
+ (new NavigationMenu())->setCurrentRoute(Route::get('index'))->generate()->filter()->sort(),
+ NavigationMenu::create(Route::get('index'))
+ );
+ }
+
+ public function test_created_collection_is_sorted_by_navigation_menu_priority()
+ {
+ Hyde::touch('_pages/foo.md');
+ Hyde::touch('_docs/index.md');
+
+ $menu = NavigationMenu::create(Route::get('index'));
+
+ $expected = collect([
+ NavItem::fromRoute(Route::get('index')),
+ NavItem::fromRoute(Route::get('docs/index')),
+ NavItem::fromRoute(Route::get('foo')),
+ ]);
+
+ $this->assertEquals($expected, $menu->items);
+
+ Hyde::unlink('_pages/foo.md');
+ Hyde::unlink('_docs/index.md');
+ }
+
+ public function test_is_sorted_automatically_when_using_navigation_menu_create()
+ {
+ Hyde::touch('_pages/foo.md');
+
+ $menu = NavigationMenu::create(Route::get('index'));
+
+ $expected = collect([
+ NavItem::fromRoute(Route::get('index')),
+ NavItem::fromRoute(Route::get('foo')),
+ ]);
+
+ $this->assertEquals($expected, $menu->items);
+
+ Hyde::unlink('_pages/foo.md');
+ }
+
+ public function test_collection_only_contains_nav_items()
+ {
+ $this->assertContainsOnlyInstancesOf(NavItem::class, NavigationMenu::create(Route::get('index'))->items);
+ }
+}
diff --git a/packages/framework/tests/Unit/NavItemTest.php b/packages/framework/tests/Unit/NavItemTest.php
new file mode 100644
index 00000000000..40008b361ab
--- /dev/null
+++ b/packages/framework/tests/Unit/NavItemTest.php
@@ -0,0 +1,56 @@
+createMock(RouteContract::class);
+ $route->method('getSourceModel')->willReturn($this->createMock(PageContract::class));
+ $route->method('getLink')->willReturn('/');
+
+ $item = new NavItem($route, 'Test', 500, true);
+
+ $this->assertSame($route, $item->route);
+ $this->assertSame('Test', $item->title);
+ $this->assertSame(500, $item->priority);
+ $this->assertTrue($item->hidden);
+ }
+
+ public function testFromRoute()
+ {
+ $route = Route::get('index');
+ $item = NavItem::fromRoute($route);
+
+ $this->assertSame($route, $item->route);
+ $this->assertSame('Home', $item->title);
+ $this->assertSame(0, $item->priority);
+ $this->assertFalse($item->hidden);
+ }
+
+ public function testResolveLink()
+ {
+ $route = Route::get('index');
+ $item = NavItem::fromRoute($route);
+
+ $this->assertSame('index.html', $item->resolveLink());
+ }
+
+ public function test__toString()
+ {
+ $route = Route::get('index');
+ $item = NavItem::fromRoute($route);
+
+ $this->assertSame('index.html', (string) $item);
+ }
+}
diff --git a/tests/TestCase.php b/tests/TestCase.php
index c48d649bf32..ed7de403dc1 100644
--- a/tests/TestCase.php
+++ b/tests/TestCase.php
@@ -24,8 +24,24 @@ protected function setUp(): void
if (! static::$booted) {
$this->resetApplication();
- Hyde::macro('touch', function (string $path) {
- return touch(Hyde::path($path));
+ Hyde::macro('touch', function (string|array $path) {
+ if (is_array($path)) {
+ foreach ($path as $p) {
+ touch(Hyde::path($p));
+ }
+ } else {
+ return touch(Hyde::path($path));
+ }
+ });
+
+ Hyde::macro('unlink', function (string|array $path) {
+ if (is_array($path)) {
+ foreach ($path as $p) {
+ unlink(Hyde::path($p));
+ }
+ } else {
+ return unlink(Hyde::path($path));
+ }
});
static::$booted = true;