diff --git a/packages/framework/src/Concerns/CanBeInNavigation.php b/packages/framework/src/Concerns/CanBeInNavigation.php deleted file mode 100644 index 869463564cd..00000000000 --- a/packages/framework/src/Concerns/CanBeInNavigation.php +++ /dev/null @@ -1,113 +0,0 @@ -identifier === 'index' && ! in_array('docs', config('hyde.navigation.exclude', [])); - } - - if ($this instanceof AbstractMarkdownPage) { - if ($this->matter('navigation.hidden', false)) { - return false; - } - } - - if (in_array($this->identifier, 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 ($this instanceof DocumentationPage) { - return (int) config('hyde.navigation.order.docs', 100); - } - - if ($this->identifier === 'index') { - return (int) config('hyde.navigation.order.index', 0); - } - - if ($this->identifier === 'posts') { - return (int) config('hyde.navigation.order.posts', 10); - } - - if (array_key_exists($this->identifier, config('hyde.navigation.order', []))) { - return (int) config('hyde.navigation.order.'.$this->identifier); - } - - return 999; - } - - /** - * 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->identifier === 'index') { - if ($this instanceof DocumentationPage) { - return config('hyde.navigation.labels.docs', 'Docs'); - } - - return config('hyde.navigation.labels.home', 'Home'); - } - - return $this->title; - } - - /** - * Not yet implemented. - * - * If an item returns a route collection, - * it will automatically be made into a dropdown. - * - * @return \Illuminate\Support\Collection<\Hyde\Framework\Models\Route> - */ - // public function navigationMenuChildren(): Collection; -} diff --git a/packages/framework/src/Concerns/HasPageMetadata.php b/packages/framework/src/Concerns/HasPageMetadata.php deleted file mode 100644 index 9e613ef5329..00000000000 --- a/packages/framework/src/Concerns/HasPageMetadata.php +++ /dev/null @@ -1,101 +0,0 @@ -getRoute()->getQualifiedUrl(); - } - - /** - * @return string[] - * - * @psalm-return list - */ - public function getDynamicMetadata(): array - { - $array = []; - - if ($this->canUseCanonicalUrl()) { - $array[] = ''; - } - - if (Features::sitemap()) { - $array[] = ''; - } - - if (Features::rss()) { - $array[] = $this->makeRssFeedLink(); - } - - if (isset($this->title)) { - if ($this->hasTwitterTitleInConfig()) { - $array[] = ''; - } - if ($this->hasOpenGraphTitleInConfig()) { - $array[] = ''; - } - } - - if ($this instanceof MarkdownPost) { - $array[] = "\n"; - foreach ($this->getMetadata() as $name => $content) { - $array[] = Meta::name($name, $content); - } - foreach ($this->getMetaProperties() as $property => $content) { - $array[] = Meta::property($property, $content); - } - } - - return $array; - } - - public function renderPageMetadata(): string - { - return Meta::render( - withMergedData: $this->getDynamicMetadata() - ); - } - - public function canUseCanonicalUrl(): bool - { - return Hyde::hasSiteUrl() && isset($this->identifier); - } - - public function hasTwitterTitleInConfig(): bool - { - return str_contains(json_encode(config('hyde.meta', [])), 'twitter:title'); - } - - public function hasOpenGraphTitleInConfig(): bool - { - return str_contains(json_encode(config('hyde.meta', [])), 'og:title'); - } - - protected function makeRssFeedLink(): string - { - return sprintf( - '', - RssFeedService::getDescription(), - Hyde::url(RssFeedService::getDefaultOutputFilename()) - ); - } -} diff --git a/packages/framework/src/Contracts/AbstractPage.php b/packages/framework/src/Contracts/AbstractPage.php index 6822bbedb4c..dcd01397ed5 100644 --- a/packages/framework/src/Contracts/AbstractPage.php +++ b/packages/framework/src/Contracts/AbstractPage.php @@ -3,11 +3,15 @@ namespace Hyde\Framework\Contracts; use Hyde\Framework\Actions\SourceFileParser; -use Hyde\Framework\Concerns\CanBeInNavigation; -use Hyde\Framework\Concerns\HasPageMetadata; +use Hyde\Framework\Helpers\Features; +use Hyde\Framework\Helpers\Meta; +use Hyde\Framework\Hyde; use Hyde\Framework\Models\FrontMatter; +use Hyde\Framework\Models\Pages\DocumentationPage; +use Hyde\Framework\Models\Pages\MarkdownPost; use Hyde\Framework\Models\Route; use Hyde\Framework\Services\DiscoveryService; +use Hyde\Framework\Services\RssFeedService; use Illuminate\Support\Collection; /** @@ -23,9 +27,6 @@ */ abstract class AbstractPage implements PageContract, CompilableContract { - use HasPageMetadata; - use CanBeInNavigation; - public static string $sourceDirectory; public static string $outputDirectory; public static string $fileExtension; @@ -158,4 +159,181 @@ public function getBladeView(): string /** @inheritDoc */ abstract public function compile(): string; + + public function getCanonicalUrl(): string + { + return $this->getRoute()->getQualifiedUrl(); + } + + /** + * @return string[] + * + * @psalm-return list + */ + public function getDynamicMetadata(): array + { + $array = []; + + if ($this->canUseCanonicalUrl()) { + $array[] = ''; + } + + if (Features::sitemap()) { + $array[] = ''; + } + + if (Features::rss()) { + $array[] = $this->makeRssFeedLink(); + } + + if (isset($this->title)) { + if ($this->hasTwitterTitleInConfig()) { + $array[] = ''; + } + if ($this->hasOpenGraphTitleInConfig()) { + $array[] = ''; + } + } + + if ($this instanceof MarkdownPost) { + $array[] = "\n"; + foreach ($this->getMetadata() as $name => $content) { + $array[] = Meta::name($name, $content); + } + foreach ($this->getMetaProperties() as $property => $content) { + $array[] = Meta::property($property, $content); + } + } + + return $array; + } + + public function renderPageMetadata(): string + { + return Meta::render( + withMergedData: $this->getDynamicMetadata() + ); + } + + public function canUseCanonicalUrl(): bool + { + return Hyde::hasSiteUrl() && isset($this->identifier); + } + + public function hasTwitterTitleInConfig(): bool + { + return str_contains(json_encode(config('hyde.meta', [])), 'twitter:title'); + } + + public function hasOpenGraphTitleInConfig(): bool + { + return str_contains(json_encode(config('hyde.meta', [])), 'og:title'); + } + + protected function makeRssFeedLink(): string + { + return sprintf( + '', + RssFeedService::getDescription(), + Hyde::url(RssFeedService::getDefaultOutputFilename()) + ); + } + + /** + * Should the item should be displayed in the navigation menu? + * + * @return bool + */ + public function showInNavigation(): bool + { + if ($this instanceof MarkdownPost) { + return false; + } + + if ($this instanceof DocumentationPage) { + return $this->identifier === 'index' && ! in_array('docs', config('hyde.navigation.exclude', [])); + } + + if ($this instanceof AbstractMarkdownPage) { + if ($this->matter('navigation.hidden', false)) { + return false; + } + } + + if (in_array($this->identifier, 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 ($this instanceof DocumentationPage) { + return (int) config('hyde.navigation.order.docs', 100); + } + + if ($this->identifier === 'index') { + return (int) config('hyde.navigation.order.index', 0); + } + + if ($this->identifier === 'posts') { + return (int) config('hyde.navigation.order.posts', 10); + } + + if (array_key_exists($this->identifier, config('hyde.navigation.order', []))) { + return (int) config('hyde.navigation.order.'.$this->identifier); + } + + return 999; + } + + /** + * 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->identifier === 'index') { + if ($this instanceof DocumentationPage) { + return config('hyde.navigation.labels.docs', 'Docs'); + } + + return config('hyde.navigation.labels.home', 'Home'); + } + + return $this->title; + } + + /** + * Not yet implemented. + * + * If an item returns a route collection, + * it will automatically be made into a dropdown. + * + * @return \Illuminate\Support\Collection<\Hyde\Framework\Models\Route> + */ + // public function navigationMenuChildren(): Collection; } diff --git a/packages/framework/tests/Feature/AbstractPageTest.php b/packages/framework/tests/Feature/AbstractPageTest.php index 61ca1ff7eaf..48c8cb44488 100644 --- a/packages/framework/tests/Feature/AbstractPageTest.php +++ b/packages/framework/tests/Feature/AbstractPageTest.php @@ -24,10 +24,21 @@ * @covers \Hyde\Framework\Contracts\AbstractPage * @covers \Hyde\Framework\Contracts\AbstractMarkdownPage * - * @backupStaticAttributes enabled + * @see \Hyde\Framework\Testing\Unit\AbstractPageMetadataTest */ class AbstractPageTest extends TestCase { + protected function tearDown(): void + { + BladePage::$sourceDirectory = '_pages'; + MarkdownPage::$sourceDirectory = '_pages'; + MarkdownPost::$sourceDirectory = '_posts'; + DocumentationPage::$sourceDirectory = '_docs'; + MarkdownPage::$fileExtension = '.md'; + + parent::tearDown(); + } + public function test_get_source_directory_returns_static_property() { MarkdownPage::$sourceDirectory = 'foo'; @@ -375,4 +386,167 @@ public function test_markdown_page_magic_set_method_does_not_override_actual_cla $this->assertEquals('baz', $page->identifier); $this->assertEquals('bar', $page->matter('identifier')); } + + public function test_show_in_navigation_returns_false_for_markdown_post() + { + $page = $this->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->identifier = '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->identifier = '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 = MarkdownPage::make('foo', ['navigation.hidden' => true]); + + $this->assertFalse($page->showInNavigation()); + } + + public function test_show_in_navigation_returns_true_for_abstract_markdown_page_if_matter_navigation_hidden_is_false() + { + $page = MarkdownPage::make('foo', ['navigation.hidden' => 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 = MarkdownPage::make('foo', ['navigation.hidden' => null]); + + $this->assertTrue($page->showInNavigation()); + } + + public function test_show_in_navigation_returns_false_if_slug_is_present_in_config_hyde_navigation_exclude() + { + $page = MarkdownPage::make('foo'); + + $this->assertTrue($page->showInNavigation()); + + config(['hyde.navigation.exclude' => ['foo']]); + $this->assertFalse($page->showInNavigation()); + } + + public function test_show_in_navigation_returns_false_if_slug_is_404() + { + $page = MarkdownPage::make('404'); + $this->assertFalse($page->showInNavigation()); + } + + public function test_show_in_navigation_defaults_to_true_if_all_checks_pass() + { + $page = MarkdownPage::make('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 = MarkdownPage::make('foo', ['navigation.priority' => 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 = MarkdownPage::make('foo'); + $this->assertEquals(999, $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 = MarkdownPage::make('foo', ['navigation.priority' => 1]); + + $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 = DocumentationPage::make('foo'); + $this->assertEquals(100, $page->navigationMenuPriority()); + } + + public function test_navigation_menu_priority_returns_0_if_slug_is_index() + { + $page = MarkdownPage::make('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 = DocumentationPage::make('index'); + $this->assertEquals(100, $page->navigationMenuPriority()); + } + + public function test_navigation_menu_priority_returns_10_if_slug_is_posts() + { + $page = MarkdownPage::make('posts'); + $this->assertEquals(10, $page->navigationMenuPriority()); + } + + public function test_navigation_menu_priority_defaults_to_999_if_no_other_conditions_are_met() + { + $page = MarkdownPage::make('foo'); + $this->assertEquals(999, $page->navigationMenuPriority()); + } + + public function test_navigation_menu_title_returns_navigation_title_matter_if_set() + { + $page = MarkdownPage::make('foo', ['navigation.title' => 'foo']); + $this->assertEquals('foo', $page->navigationMenuTitle()); + } + + public function test_navigation_menu_title_returns_title_matter_if_set() + { + $page = MarkdownPage::make('foo', ['title' => 'foo']); + $this->assertEquals('foo', $page->navigationMenuTitle()); + } + + public function test_navigation_menu_title_navigation_title_has_precedence_over_title() + { + $page = MarkdownPage::make('foo', ['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 = DocumentationPage::make('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 = MarkdownPage::make('index'); + $this->assertEquals('Home', $page->navigationMenuTitle()); + } + + public function test_navigation_menu_title_returns_title_if_title_is_set_and_not_empty() + { + $page = MarkdownPage::make('bar', ['title' => 'foo']); + $this->assertEquals('foo', $page->navigationMenuTitle()); + } + + public function test_navigation_menu_title_falls_back_to_hyde_make_title_from_slug() + { + $page = MarkdownPage::make('foo'); + $this->assertEquals('Foo', $page->navigationMenuTitle()); + } } diff --git a/packages/framework/tests/Feature/Concerns/CanBeInNavigationTest.php b/packages/framework/tests/Feature/Concerns/CanBeInNavigationTest.php deleted file mode 100644 index 1fa1bf0008b..00000000000 --- a/packages/framework/tests/Feature/Concerns/CanBeInNavigationTest.php +++ /dev/null @@ -1,177 +0,0 @@ -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->identifier = '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->identifier = '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 = MarkdownPage::make('foo', ['navigation.hidden' => true]); - - $this->assertFalse($page->showInNavigation()); - } - - public function test_show_in_navigation_returns_true_for_abstract_markdown_page_if_matter_navigation_hidden_is_false() - { - $page = MarkdownPage::make('foo', ['navigation.hidden' => 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 = MarkdownPage::make('foo', ['navigation.hidden' => null]); - - $this->assertTrue($page->showInNavigation()); - } - - public function test_show_in_navigation_returns_false_if_slug_is_present_in_config_hyde_navigation_exclude() - { - $page = MarkdownPage::make('foo'); - - $this->assertTrue($page->showInNavigation()); - - config(['hyde.navigation.exclude' => ['foo']]); - $this->assertFalse($page->showInNavigation()); - } - - public function test_show_in_navigation_returns_false_if_slug_is_404() - { - $page = MarkdownPage::make('404'); - $this->assertFalse($page->showInNavigation()); - } - - public function test_show_in_navigation_defaults_to_true_if_all_checks_pass() - { - $page = MarkdownPage::make('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 = MarkdownPage::make('foo', ['navigation.priority' => 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 = MarkdownPage::make('foo'); - $this->assertEquals(999, $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 = MarkdownPage::make('foo', ['navigation.priority' => 1]); - - $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 = DocumentationPage::make('foo'); - $this->assertEquals(100, $page->navigationMenuPriority()); - } - - public function test_navigation_menu_priority_returns_0_if_slug_is_index() - { - $page = MarkdownPage::make('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 = DocumentationPage::make('index'); - $this->assertEquals(100, $page->navigationMenuPriority()); - } - - public function test_navigation_menu_priority_returns_10_if_slug_is_posts() - { - $page = MarkdownPage::make('posts'); - $this->assertEquals(10, $page->navigationMenuPriority()); - } - - public function test_navigation_menu_priority_defaults_to_999_if_no_other_conditions_are_met() - { - $page = MarkdownPage::make('foo'); - $this->assertEquals(999, $page->navigationMenuPriority()); - } - - public function test_navigation_menu_title_returns_navigation_title_matter_if_set() - { - $page = MarkdownPage::make('foo', ['navigation.title' => 'foo']); - $this->assertEquals('foo', $page->navigationMenuTitle()); - } - - public function test_navigation_menu_title_returns_title_matter_if_set() - { - $page = MarkdownPage::make('foo', ['title' => 'foo']); - $this->assertEquals('foo', $page->navigationMenuTitle()); - } - - public function test_navigation_menu_title_navigation_title_has_precedence_over_title() - { - $page = MarkdownPage::make('foo', ['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 = DocumentationPage::make('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 = MarkdownPage::make('index'); - $this->assertEquals('Home', $page->navigationMenuTitle()); - } - - public function test_navigation_menu_title_returns_title_if_title_is_set_and_not_empty() - { - $page = MarkdownPage::make('bar', ['title' => 'foo']); - $this->assertEquals('foo', $page->navigationMenuTitle()); - } - - public function test_navigation_menu_title_falls_back_to_hyde_make_title_from_slug() - { - $page = MarkdownPage::make('foo'); - $this->assertEquals('Foo', $page->navigationMenuTitle()); - } -} diff --git a/packages/framework/tests/Unit/HasPageMetadataRssFeedLinkTest.php b/packages/framework/tests/Unit/AbstractPageMetadataRssFeedLinkTest.php similarity index 94% rename from packages/framework/tests/Unit/HasPageMetadataRssFeedLinkTest.php rename to packages/framework/tests/Unit/AbstractPageMetadataRssFeedLinkTest.php index 96dc8df5a86..1a7c12d3584 100644 --- a/packages/framework/tests/Unit/HasPageMetadataRssFeedLinkTest.php +++ b/packages/framework/tests/Unit/AbstractPageMetadataRssFeedLinkTest.php @@ -9,9 +9,9 @@ use Hyde\Testing\TestCase; /** - * @covers \Hyde\Framework\Concerns\HasPageMetadata::getDynamicMetadata + * @covers \Hyde\Framework\Contracts\AbstractPage::getDynamicMetadata */ -class HasPageMetadataRssFeedLinkTest extends TestCase +class AbstractPageMetadataRssFeedLinkTest extends TestCase { protected function setUp(): void { diff --git a/packages/framework/tests/Feature/Concerns/HasPageMetadataTest.php b/packages/framework/tests/Unit/AbstractPageMetadataTest.php similarity index 96% rename from packages/framework/tests/Feature/Concerns/HasPageMetadataTest.php rename to packages/framework/tests/Unit/AbstractPageMetadataTest.php index 80891bd02b3..6d8508cd05c 100644 --- a/packages/framework/tests/Feature/Concerns/HasPageMetadataTest.php +++ b/packages/framework/tests/Unit/AbstractPageMetadataTest.php @@ -1,17 +1,17 @@