diff --git a/packages/framework/src/Pages/VirtualPage.php b/packages/framework/src/Pages/VirtualPage.php index 45a1ef5c393..1e5a93c65dc 100644 --- a/packages/framework/src/Pages/VirtualPage.php +++ b/packages/framework/src/Pages/VirtualPage.php @@ -4,10 +4,12 @@ namespace Hyde\Pages; +use Hyde\Framework\Actions\AnonymousViewCompiler; use Hyde\Markdown\Models\FrontMatter; use Hyde\Pages\Concerns\HydePage; use Hyde\Pages\Contracts\DynamicPage; use Illuminate\Support\Facades\View; +use function str_ends_with; /** * A virtual page is a page that does not have a source file. @@ -70,7 +72,10 @@ public function getBladeView(): string public function compile(): string { if (! $this->contents && $this->view) { - // TODO This needs to support Blade files if we're gonna use it for pagination. + if (str_ends_with($this->view, '.blade.php')) { + return AnonymousViewCompiler::call($this->view, $this->matter->toArray()); + } + return View::make($this->getBladeView(), $this->matter->toArray())->render(); } diff --git a/packages/publications/src/Actions/CreatesNewPublicationType.php b/packages/publications/src/Actions/CreatesNewPublicationType.php index e652ef4c529..b44c2861142 100644 --- a/packages/publications/src/Actions/CreatesNewPublicationType.php +++ b/packages/publications/src/Actions/CreatesNewPublicationType.php @@ -40,7 +40,7 @@ protected function handleCreate(): void 'list.blade.php', $this->sortField ?? '__createdAt', $this->sortAscending ?? true, - $this->pageSize ?? 25, + $this->pageSize ?? 0, $this->fields->toArray() ))->save($this->outputPath); @@ -50,16 +50,21 @@ protected function handleCreate(): void protected function createDetailTemplate(): void { - $this->savePublicationFile('detail.blade.php', '/../publications/resources/views/publication_detail.blade.php'); + $this->publishPublicationFile('detail', 'publication_detail'); } protected function createListTemplate(): void { - $this->savePublicationFile('list.blade.php', '/../publications/resources/views/publication_list.blade.php'); + $this->publishPublicationFile('list', $this->usesPagination() ? 'publication_paginated_list' : 'publication_list'); } - protected function savePublicationFile(string $filename, string $viewPath): void + protected function publishPublicationFile(string $filename, string $viewName): void { - copy(Hyde::vendorPath($viewPath), Hyde::path("$this->directoryName/$filename")); + copy(Hyde::vendorPath("/../publications/resources/views/$viewName.blade.php"), Hyde::path("$this->directoryName/$filename.blade.php")); + } + + protected function usesPagination(): bool + { + return $this->pageSize > 0; } } diff --git a/packages/publications/src/Commands/MakePublicationTypeCommand.php b/packages/publications/src/Commands/MakePublicationTypeCommand.php index 246367d67f5..63fd82ad496 100644 --- a/packages/publications/src/Commands/MakePublicationTypeCommand.php +++ b/packages/publications/src/Commands/MakePublicationTypeCommand.php @@ -13,6 +13,7 @@ use Hyde\Publications\PublicationFieldTypes; use Illuminate\Support\Collection; use Illuminate\Support\Str; +use function in_array; use InvalidArgumentException; use function is_dir; use function is_file; @@ -30,12 +31,10 @@ class MakePublicationTypeCommand extends ValidatingCommand { /** @var string */ - protected $signature = 'make:publicationType - {name? : The name of the publication type to create} - {--use-defaults : Select the default options wherever possible}'; + protected $signature = 'make:publicationType {name? : The name of the publication type to create}'; /** @var string */ - protected $description = 'Create a new publication type definition'; + protected $description = 'Create a new publication type'; protected Collection $fields; @@ -44,14 +43,18 @@ public function safeHandle(): int $this->title('Creating a new Publication Type!'); $title = $this->getTitle(); - $this->validateStorageDirectory(Str::slug($title)); - $this->fields = $this->captureFieldsDefinitions(); - - [$sortField, $sortAscending, $pageSize] = ($this->getPaginationSettings()); + $this->captureFieldsDefinitions(); $canonicalField = $this->getCanonicalField(); + $sortField = $this->getSortField(); + $sortAscending = $this->getSortDirection(); + $pageSize = $this->getPageSize(); + + if ($this->fields->first()->name === '__createdAt') { + $this->fields->shift(); + } $creator = new CreatesNewPublicationType($title, $this->fields, $canonicalField->name, $sortField, $sortAscending, $pageSize); $this->output->writeln("Saving publication data to [{$creator->getOutputPath()}]"); @@ -74,24 +77,19 @@ protected function validateStorageDirectory(string $directoryName): void } } - protected function captureFieldsDefinitions(): Collection + protected function captureFieldsDefinitions(): void { - $this->line('You now need to define the fields in your publication type:'); - $this->fields = Collection::make(); + $this->line('Now please define the fields for your publication type:'); - $this->addCreatedAtMetaField(); + $this->fields = Collection::make([ + new PublicationFieldDefinition(PublicationFieldTypes::Datetime, '__createdAt'), + ]); do { $this->fields->add($this->captureFieldDefinition()); - if ($this->option('use-defaults') === true) { - $addAnother = false; - } else { - $addAnother = $this->confirm("Field #{$this->getCount(-1)} added! Add another field?"); - } + $addAnother = $this->confirm(sprintf('Field #%d added! Add another field?', $this->getCount() - 1)); } while ($addAnother); - - return $this->fields; } protected function captureFieldDefinition(): PublicationFieldDefinition @@ -115,7 +113,12 @@ protected function captureFieldDefinition(): PublicationFieldDefinition protected function getFieldName(?string $message = null): string { - $selected = Str::kebab(trim($this->askWithValidation('name', $message ?? "Enter name for field #{$this->getCount()}", ['required']))); + $message ??= "Enter name for field #{$this->getCount()}"; + $default = $this->input->isInteractive() ? null : 'Example Field'; + + $selected = Str::kebab(trim($this->askWithValidation( + 'name', $message, ['required'], default: $default + ))); if ($this->checkIfFieldIsDuplicate($selected)) { return $this->getFieldName("Try again: Enter name for field #{$this->getCount()}"); @@ -126,11 +129,11 @@ protected function getFieldName(?string $message = null): string protected function getFieldType(): PublicationFieldTypes { - $options = PublicationFieldTypes::names(); - - $choice = $this->choice("Enter type for field #{$this->getCount()}", $options, 'String'); - - return PublicationFieldTypes::from(strtolower($choice)); + return PublicationFieldTypes::from(strtolower($this->choice( + "Enter type for field #{$this->getCount()}", + PublicationFieldTypes::names(), + 'String' + ))); } protected function getTagGroup(): string @@ -152,75 +155,56 @@ protected function getTagGroup(): string protected function getCanonicalField(): PublicationFieldDefinition { - $selectableFields = $this->fields->reject(function (PublicationFieldDefinition $field): bool { - return ! in_array($field->type, PublicationFieldTypes::canonicable()); - }); - - if ($this->option('use-defaults')) { - return $selectableFields->first(); - } + $options = $this->availableCanonicableFieldNames(); - $options = $selectableFields->pluck('name'); - - $selected = $this->choice('Choose a canonical name field (this will be used to generate filenames, so the values need to be unique)', + return $this->fields->firstWhere('name', $this->choice( + 'Choose a canonical name field (this will be used to generate filenames, so the values need to be unique)', $options->toArray(), $options->first() - ); - - return $this->fields->firstWhere('name', $selected); - } - - protected function checkIfFieldIsDuplicate($name): bool - { - if ($this->fields->where('name', $name)->count() > 0) { - $this->error("Field name [$name] already exists!"); - - return true; - } - - return false; - } - - protected function addCreatedAtMetaField(): void - { - $this->fields->add(new PublicationFieldDefinition(PublicationFieldTypes::Datetime, '__createdAt')); - } - - /** @deprecated Since the pagination settings object is deprecated we should just inline these */ - protected function getPaginationSettings(): array - { - if ($this->option('use-defaults') || ! $this->confirm('Would you like to enable pagination?')) { - return [null, null, null]; - } - - $this->info("Okay, let's set up pagination! Tip: You can just hit enter to accept the default values."); - - return [$this->getSortField(), $this->getSortDirection(), $this->getPageSize()]; + )); } protected function getSortField(): string { - return $this->choice('Choose the default field you wish to sort by', $this->fields->pluck('name')->toArray(), 0); + return $this->choice('Choose the field you wish to sort by', $this->availableCanonicableFieldNames()->toArray(), 0); } protected function getSortDirection(): bool { $options = ['Ascending' => true, 'Descending' => false]; - return $options[$this->choice('Choose the default sort direction', array_keys($options), 'Ascending')]; + return $options[$this->choice('Choose the sort direction', array_keys($options), 'Ascending')]; } protected function getPageSize(): int { return (int) $this->askWithValidation('pageSize', - 'Enter the page size (0 for no limit)', + 'How many links should be shown on the listing page? (any value above 0 will enable pagination)', ['required', 'integer', 'between:0,100'], - 25 + 0 ); } - protected function getCount(int $offset = 0): int + protected function checkIfFieldIsDuplicate($name): bool { - return $this->fields->count() + $offset; + if ($this->fields->where('name', $name)->count() > 0) { + $this->error("Field name [$name] already exists!"); + + return true; + } + + return false; + } + + protected function getCount(): int + { + return $this->fields->count(); + } + + protected function availableCanonicableFieldNames(): Collection + { + return $this->fields->reject(function (PublicationFieldDefinition $field): bool { + return ! in_array($field->type, PublicationFieldTypes::canonicable()); + })->pluck('name'); } } diff --git a/packages/publications/src/PublicationsExtension.php b/packages/publications/src/PublicationsExtension.php index bb7e9219bc8..c2154d3d43b 100644 --- a/packages/publications/src/PublicationsExtension.php +++ b/packages/publications/src/PublicationsExtension.php @@ -11,6 +11,7 @@ use Hyde\Publications\Models\PublicationPage; use Hyde\Publications\Models\PublicationType; use function range; +use function str_ends_with; /** * @see \Hyde\Publications\Testing\Feature\PublicationsExtensionTest @@ -70,10 +71,14 @@ protected static function generatePublicationPaginatedListingPagesForType(Public foreach (range(1, $paginator->totalPages()) as $page) { $paginator->setCurrentPage($page); + $listTemplate = $type->listTemplate; + if (str_ends_with($listTemplate, '.blade.php')) { + $listTemplate = "{$type->getDirectory()}/$listTemplate"; + } $listingPage = new VirtualPage("{$type->getDirectory()}/page-$page", [ 'publicationType' => $type, 'paginatorPage' => $page, 'title' => $type->name.' - Page '.$page, - ], view: $type->listTemplate); + ], view: $listTemplate); $instance->put($listingPage->getSourcePath(), $listingPage); } } diff --git a/packages/publications/tests/Feature/CreatesNewPublicationTypeTest.php b/packages/publications/tests/Feature/CreatesNewPublicationTypeTest.php index 3bc15d9907d..a9fa791bad4 100644 --- a/packages/publications/tests/Feature/CreatesNewPublicationTypeTest.php +++ b/packages/publications/tests/Feature/CreatesNewPublicationTypeTest.php @@ -70,7 +70,7 @@ public function test_create_with_default_parameters() "listTemplate": "list.blade.php", "sortField": "__createdAt", "sortAscending": true, - "pageSize": 25, + "pageSize": 0, "fields": [] } JSON, file_get_contents(Hyde::path('test-publication/schema.json')) @@ -79,7 +79,7 @@ public function test_create_with_default_parameters() public function test_it_creates_list_and_detail_pages() { - $creator = new \Hyde\Publications\Actions\CreatesNewPublicationType( + $creator = new CreatesNewPublicationType( 'Test Publication', new Collection(), 'canonical', @@ -88,5 +88,33 @@ public function test_it_creates_list_and_detail_pages() $this->assertFileExists(Hyde::path('test-publication/detail.blade.php')); $this->assertFileExists(Hyde::path('test-publication/list.blade.php')); + + $this->assertFileEquals(__DIR__.'/../../resources/views/publication_detail.blade.php', + Hyde::path('test-publication/detail.blade.php') + ); + $this->assertFileEquals(__DIR__.'/../../resources/views/publication_list.blade.php', + Hyde::path('test-publication/list.blade.php') + ); + } + + public function test_it_uses_the_paginated_list_view_when_pagination_is_enabled() + { + $creator = new CreatesNewPublicationType( + 'Test Publication', + new Collection(), + 'canonical', + pageSize: 10, + ); + $creator->create(); + + $this->assertFileExists(Hyde::path('test-publication/detail.blade.php')); + $this->assertFileExists(Hyde::path('test-publication/list.blade.php')); + + $this->assertFileEquals(__DIR__.'/../../resources/views/publication_detail.blade.php', + Hyde::path('test-publication/detail.blade.php') + ); + $this->assertFileEquals(__DIR__.'/../../resources/views/publication_paginated_list.blade.php', + Hyde::path('test-publication/list.blade.php') + ); } } diff --git a/packages/publications/tests/Feature/MakePublicationTypeCommandTest.php b/packages/publications/tests/Feature/MakePublicationTypeCommandTest.php index 1dd31105aef..faffbc7f80d 100644 --- a/packages/publications/tests/Feature/MakePublicationTypeCommandTest.php +++ b/packages/publications/tests/Feature/MakePublicationTypeCommandTest.php @@ -18,6 +18,9 @@ */ class MakePublicationTypeCommandTest extends TestCase { + protected const selectPageSizeQuestion = 'How many links should be shown on the listing page? (any value above 0 will enable pagination)'; + protected const selectCanonicalNameQuestion = 'Choose a canonical name field (this will be used to generate filenames, so the values need to be unique)'; + protected function setUp(): void { parent::setUp(); @@ -50,20 +53,22 @@ public function test_command_creates_publication_type() 'Tag', ], true) ->expectsConfirmation('Field #1 added! Add another field?') - ->expectsConfirmation('Would you like to enable pagination?', 'yes') - ->expectsChoice('Choose the default field you wish to sort by', '__createdAt', [ + + ->expectsChoice(self::selectCanonicalNameQuestion, 'publication-title', [ '__createdAt', 'publication-title', ]) - ->expectsChoice('Choose the default sort direction', 'Ascending', [ - 'Ascending', - 'Descending', - ]) - ->expectsQuestion('Enter the page size (0 for no limit)', 10) - ->expectsChoice('Choose a canonical name field (this will be used to generate filenames, so the values need to be unique)', 'publication-title', [ + + ->expectsChoice('Choose the field you wish to sort by', '__createdAt', [ '__createdAt', 'publication-title', ]) + ->expectsChoice('Choose the sort direction', 'Ascending', [ + 'Ascending', + 'Descending', + ]) + ->expectsQuestion(self::selectPageSizeQuestion, 10) + ->expectsOutputToContain('Creating a new Publication Type!') ->expectsOutput('Saving publication data to [test-publication/schema.json]') ->expectsOutput('Publication type created successfully!') @@ -81,10 +86,6 @@ public function test_command_creates_publication_type() "sortAscending": true, "pageSize": 10, "fields": [ - { - "type": "datetime", - "name": "__createdAt" - }, { "type": "string", "name": "publication-title" @@ -97,22 +98,51 @@ public function test_command_creates_publication_type() $this->assertFileExists(Hyde::path('test-publication/detail.blade.php')); $this->assertFileExists(Hyde::path('test-publication/list.blade.php')); + + $this->assertStringContainsString('paginator', file_get_contents(Hyde::path('test-publication/list.blade.php'))); } public function test_with_default_values() { - $this->artisan('make:publicationType --use-defaults') - ->expectsQuestion('Publication type name', 'Test Publication') - ->expectsQuestion('Enter name for field #1', 'foo') - ->expectsChoice('Enter type for field #1', 'String', PublicationFieldTypes::names()) - ->expectsOutput('Saving publication data to [test-publication/schema.json]') - ->expectsOutput('Publication type created successfully!') - ->assertExitCode(0); + // When running this command with the no-interaction flag in an actual console, no questions are asked. + // However, when running it in a test, the questions are still asked, presumably due to a vendor bug. + + $this->withoutMockingConsoleOutput(); + + $this->assertSame(0, $this->artisan('make:publicationType "Test Publication" --no-interaction')); + + $this->assertFileExists(Hyde::path('test-publication/schema.json')); + $this->assertEquals( + <<<'JSON' + { + "name": "Test Publication", + "canonicalField": "__createdAt", + "detailTemplate": "detail.blade.php", + "listTemplate": "list.blade.php", + "sortField": "__createdAt", + "sortAscending": true, + "pageSize": 0, + "fields": [ + { + "type": "string", + "name": "example-field" + } + ] + } + JSON, + file_get_contents(Hyde::path('test-publication/schema.json')) + ); + + $this->assertFileExists(Hyde::path('test-publication/detail.blade.php')); + $this->assertFileExists(Hyde::path('test-publication/list.blade.php')); + + $this->assertStringNotContainsString('paginator', file_get_contents(Hyde::path('test-publication/list.blade.php'))); } public function test_with_multiple_fields_of_the_same_name() { $this->artisan('make:publicationType "Test Publication"') + ->expectsQuestion('Enter name for field #1', 'foo') ->expectsChoice('Enter type for field #1', 'String', PublicationFieldTypes::names()) @@ -125,12 +155,16 @@ public function test_with_multiple_fields_of_the_same_name() ->expectsConfirmation('Field #2 added! Add another field?') - ->expectsConfirmation('Would you like to enable pagination?') - ->expectsChoice('Choose a canonical name field (this will be used to generate filenames, so the values need to be unique)', 'foo', [ + ->expectsChoice(self::selectCanonicalNameQuestion, 'foo', [ '__createdAt', 'bar', 'foo', ]) + + ->expectsChoice('Choose the field you wish to sort by', '__createdAt', ['__createdAt', 'foo', 'bar']) + ->expectsChoice('Choose the sort direction', 'Ascending', ['Ascending', 'Descending']) + ->expectsQuestion(self::selectPageSizeQuestion, 0) + ->assertExitCode(0); } @@ -166,11 +200,18 @@ public function testWithTagFieldInput() 'bar' => ['foo', 'baz'], ])->save(); - $this->artisan('make:publicationType "Test Publication" --use-defaults') + $this->artisan('make:publicationType "Test Publication"') ->expectsQuestion('Enter name for field #1', 'MyTag') ->expectsChoice('Enter type for field #1', 'Tag', ['String', 'Datetime', 'Boolean', 'Integer', 'Float', 'Image', 'Array', 'Text', 'Url', 'Tag']) ->expectsChoice('Enter tag group for field #1', 'foo', ['bar', 'foo'], true) + + ->expectsConfirmation('Field #1 added! Add another field?') + ->expectsChoice(self::selectCanonicalNameQuestion, '__createdAt', ['__createdAt']) + ->expectsChoice('Choose the field you wish to sort by', '__createdAt', ['__createdAt']) + ->expectsChoice('Choose the sort direction', 'Ascending', ['Ascending', 'Descending']) + ->expectsQuestion(self::selectPageSizeQuestion, 0) + ->assertSuccessful(); $this->assertFileExists(Hyde::path('test-publication/schema.json')); @@ -183,12 +224,8 @@ public function testWithTagFieldInput() "listTemplate": "list.blade.php", "sortField": "__createdAt", "sortAscending": true, - "pageSize": 25, + "pageSize": 0, "fields": [ - { - "type": "datetime", - "name": "__createdAt" - }, { "type": "tag", "name": "my-tag", @@ -207,7 +244,7 @@ public function testWithTagFieldInputButNoTags() config(['app.throw_on_console_exception' => false]); $this->directory('test-publication'); - $this->artisan('make:publicationType "Test Publication" --use-defaults') + $this->artisan('make:publicationType "Test Publication"') ->expectsQuestion('Enter name for field #1', 'MyTag') ->expectsChoice('Enter type for field #1', 'Tag', ['String', 'Datetime', 'Boolean', 'Integer', 'Float', 'Image', 'Array', 'Text', 'Url', 'Tag'], true) @@ -235,8 +272,13 @@ public function testWithTagFieldInputButNoTagsCanPromptToCreateTags() ->expectsOutput("Okay, we're back on track!") ->expectsChoice('Enter tag group for field #1', 'foo', ['foo'], true) ->expectsConfirmation('Field #1 added! Add another field?') - ->expectsConfirmation('Would you like to enable pagination?') - ->expectsChoice('Choose a canonical name field (this will be used to generate filenames, so the values need to be unique)', '__createdAt', ['__createdAt']) + + ->expectsChoice(self::selectCanonicalNameQuestion, '__createdAt', ['__createdAt']) + + ->expectsChoice('Choose the field you wish to sort by', '__createdAt', ['__createdAt']) + ->expectsChoice('Choose the sort direction', 'Ascending', ['Ascending', 'Descending']) + ->expectsQuestion(self::selectPageSizeQuestion, 0) + ->doesntExpectOutput('Error: Can not create a tag field without any tag groups defined in tags.json') ->assertSuccessful(); diff --git a/packages/publications/tests/Feature/StaticSiteBuilderPublicationModuleTest.php b/packages/publications/tests/Feature/StaticSiteBuilderPublicationModuleTest.php index a761ce0e9f3..af11f287aa7 100644 --- a/packages/publications/tests/Feature/StaticSiteBuilderPublicationModuleTest.php +++ b/packages/publications/tests/Feature/StaticSiteBuilderPublicationModuleTest.php @@ -126,10 +126,9 @@ public function testCompilingWithPublicationTypeThatUsesThePublishedPaginatedVie { $this->directory('test-publication'); - (new CreatesNewPublicationType('Test Publication', collect([])))->create(); + (new CreatesNewPublicationType('Test Publication', collect([]), pageSize: 10))->create(); $type = PublicationType::get('test-publication'); - $type->listTemplate = 'hyde-publications::publication_paginated_list'; $type->pageSize = 2; $type->save(); @@ -161,9 +160,33 @@ public function testCompilingWithPublicationTypeThatUsesThePaginatedVendorViews( $this->directory('test-publication'); (new CreatesNewPublicationType('Test Publication', collect([])))->create(); - // TODO assert the paginated template was published once we implement that - $this->markTestIncomplete(); + $type = PublicationType::get('test-publication'); + $type->listTemplate = 'hyde-publications::publication_paginated_list'; + $type->pageSize = 2; + $type->save(); + + foreach (range(1, 5) as $i) { + $this->file("test-publication/publication-$i.md", "## Test publication $i"); + } + + $this->artisan('build')->assertSuccessful(); + + $this->assertSame([ + 'index.html', + 'page-1.html', + 'page-2.html', + 'page-3.html', + 'publication-1.html', + 'publication-2.html', + 'publication-3.html', + 'publication-4.html', + 'publication-5.html', + ], $this->getFilenamesInDirectory('_site/test-publication')); + + // TODO test that the pagination links are correct + + $this->resetSite(); } protected function getAllFields(): Collection