Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[2.x] Dynamic blogging related page generation #1843

Closed
wants to merge 142 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
142 commits
Select commit Hold shift + click to select a range
3081e17
Create DynamicBlogPostPageHelper.php
caendesilva Jul 12, 2024
6ca4ae6
Document added helper class as internal and experimental
caendesilva Jul 12, 2024
ebef9c9
Draft helper methods for author page generation
caendesilva Jul 12, 2024
5f7e4b5
Hook into dynamic blog post author page generation
caendesilva Jul 12, 2024
4fb7edd
Refactor to return array of generated pages
caendesilva Jul 12, 2024
34e3e57
Fix formatting
caendesilva Jul 12, 2024
92cf166
Implement author page generation check
caendesilva Jul 12, 2024
f2caa1b
Return empty array
caendesilva Jul 12, 2024
e90fd55
Create DynamicAuthorPagesTest.php
caendesilva Jul 12, 2024
b1cb307
Draft setup method to test
caendesilva Jul 12, 2024
75b2463
Add author configuration setup
caendesilva Jul 12, 2024
697613f
Specify test coverage for test
caendesilva Jul 12, 2024
6466c39
Set up the sample blog posts
caendesilva Jul 12, 2024
993029f
Draft test for dynamic author pages generation
caendesilva Jul 12, 2024
2893a08
Extract page creation logic into separate test helper method
caendesilva Jul 12, 2024
ea7b0e1
Add todo
caendesilva Jul 12, 2024
0494655
Create PostAuthorPage.php
caendesilva Jul 12, 2024
5acd7ea
Test covers PostAuthorPage
caendesilva Jul 12, 2024
6155343
Mark PostAuthorPage as experimental
caendesilva Jul 12, 2024
acb4917
Add todo
caendesilva Jul 12, 2024
d002120
Break out todo
caendesilva Jul 12, 2024
b22a7ae
Generate the author pages
caendesilva Jul 12, 2024
7701fcf
Check that there are posts
caendesilva Jul 12, 2024
c71962f
Check that blogging is enabled
caendesilva Jul 12, 2024
b74ef17
Run the discovery in a kernel callback
caendesilva Jul 12, 2024
276d5ed
Revert "Run the discovery in a kernel callback"
caendesilva Jul 12, 2024
9f44fd6
Actually save the files in the feature test
caendesilva Jul 12, 2024
029aaf5
Update to expect the real order
caendesilva Jul 12, 2024
170170f
Add newline to method chain
caendesilva Jul 12, 2024
195ea02
Only includes author with posts
caendesilva Jul 12, 2024
303687c
Add newline
caendesilva Jul 12, 2024
ce043fd
Create PostAuthorsPage.php
caendesilva Jul 12, 2024
96dec43
Test covers PostAuthorsPage
caendesilva Jul 12, 2024
e54bfe3
Introduce local variable
caendesilva Jul 12, 2024
3fd708a
Construct page with identifier
caendesilva Jul 12, 2024
9e34aaa
Construct page with author collection
caendesilva Jul 12, 2024
d3cda6e
Define the authors property
caendesilva Jul 12, 2024
6e1a3ee
Prepend the post authors listing page
caendesilva Jul 12, 2024
48ec8b7
Add early return for empty state
caendesilva Jul 12, 2024
4c299fc
Construct post author pages with the author instance
caendesilva Jul 12, 2024
1dc5091
Expect the authors listing page
caendesilva Jul 12, 2024
b76256e
Ignore coverage for code too early to test
caendesilva Jul 12, 2024
b539efe
Publish the parent method
caendesilva Jul 12, 2024
f0d8133
Add todo
caendesilva Jul 12, 2024
7777bd0
Pluralize base route key
caendesilva Jul 12, 2024
8df87f3
Extract method for the base authors route key
caendesilva Jul 12, 2024
724f96b
Add todo
caendesilva Jul 12, 2024
22614ed
Create authors.blade.php
caendesilva Jul 12, 2024
cab1443
Create author.blade.php
caendesilva Jul 12, 2024
582bd0f
Return the authors view
caendesilva Jul 12, 2024
86ae1d4
Return the author view
caendesilva Jul 12, 2024
57a7f76
Compact the front matter
caendesilva Jul 12, 2024
c154757
Import used functions
caendesilva Jul 12, 2024
4f3be38
Annotate the props types
caendesilva Jul 12, 2024
68229b5
Draft authors list
caendesilva Jul 12, 2024
a72cb70
Revert "Draft authors list"
caendesilva Jul 12, 2024
bd0d565
Create the initial authors listing layout
caendesilva Jul 12, 2024
67eb1fa
Create the initial author page layout
caendesilva Jul 12, 2024
70ff86c
Use the dynamic route assembler
caendesilva Jul 12, 2024
9456e36
Add structured data and dark mode support
caendesilva Jul 12, 2024
2556b72
Add structured data and dark mode support
caendesilva Jul 12, 2024
645f1c0
Use the existing article excerpt component
caendesilva Jul 12, 2024
ae188be
Remove job title offering little value
caendesilva Jul 13, 2024
ea4bd05
Remove type not supported in schema
caendesilva Jul 13, 2024
2feefce
Always use avatars if one has one to match the layout
caendesilva Jul 13, 2024
b9dc71b
Fall back to default if avatar can't be loaded
caendesilva Jul 13, 2024
1bdc962
Expand compact call
caendesilva Jul 13, 2024
72b949a
Hide authors listing from navigation
caendesilva Jul 13, 2024
1c0c1a8
Add todo
caendesilva Jul 13, 2024
e627a95
Add structured breadcrumb data
caendesilva Jul 13, 2024
4977d11
Update author profile page to use structured microdata
caendesilva Jul 13, 2024
8b4ed1d
Add missing structured data to the article excerpt
caendesilva Jul 13, 2024
4e239c4
Normalize data access syntax
caendesilva Jul 13, 2024
d2d5806
Add a route helper to get the canonical URL
caendesilva Jul 13, 2024
75431bd
Try to use a qualified URL as desired by Google Rich Data
caendesilva Jul 13, 2024
2133848
Remove unreachable null fallback for already evaluated value
caendesilva Jul 13, 2024
4bad064
Normalize data access
caendesilva Jul 13, 2024
3046bb3
Capitalize first part in section
caendesilva Jul 13, 2024
7beb88d
Rename the helper class
caendesilva Jul 13, 2024
6cce763
Reformat Blade code
caendesilva Jul 13, 2024
611db55
Create FeatureServiceProvider.php
caendesilva Jul 13, 2024
ae25ff7
Register FeatureServiceProvider
caendesilva Jul 13, 2024
91bb339
Test covers FeatureServiceProvider
caendesilva Jul 13, 2024
9776fa8
Bind BlogPostAuthorPages as singleton
caendesilva Jul 13, 2024
d39320f
Revert "Bind BlogPostAuthorPages as singleton"
caendesilva Jul 13, 2024
7d68325
Revert "Test covers FeatureServiceProvider"
caendesilva Jul 13, 2024
4643ccf
Revert "Register FeatureServiceProvider"
caendesilva Jul 13, 2024
9979cee
Revert "Create FeatureServiceProvider.php"
caendesilva Jul 13, 2024
a5da0ee
Add line breaks
caendesilva Jul 13, 2024
976af55
Swap order
caendesilva Jul 13, 2024
d49980d
Move filtering to feature check
caendesilva Jul 13, 2024
659ce16
Build the site in the test
caendesilva Jul 13, 2024
f6ea442
Test the relevant pages were built and contain the expected content
caendesilva Jul 13, 2024
9d22f64
Assert file does not exist
caendesilva Jul 13, 2024
6495cf1
Add todo
caendesilva Jul 13, 2024
ad03f72
Put author listing page as the index page
caendesilva Jul 13, 2024
d5a8a1b
Use the route helper
caendesilva Jul 13, 2024
8abfcef
Use the function shorthands
caendesilva Jul 13, 2024
e73d3f5
Normalize byline capitalization
caendesilva Jul 13, 2024
232aad1
Set author base key in the page class
caendesilva Jul 13, 2024
8c5c22f
Test the route keys
caendesilva Jul 13, 2024
93670e7
Add workaround to retain information in the identifiers
caendesilva Jul 13, 2024
ea06391
Revert "Add workaround to retain information in the identifiers"
caendesilva Jul 13, 2024
1a521a5
Set virtual source directory to get unique identifiers
caendesilva Jul 13, 2024
9de5693
Merge branch '2.x-dev' into generate-author-pages
caendesilva Jul 13, 2024
45cb90c
Extract a static property for setting the view
caendesilva Jul 13, 2024
9e6f94e
Remove code coverage ignores
caendesilva Jul 13, 2024
4ffe537
Add crosslink annotations between related classes
caendesilva Jul 13, 2024
57b594b
Add helper to set the base directory for the author pages
caendesilva Jul 13, 2024
c9ad189
Add public static property to control navigation visibility
caendesilva Jul 13, 2024
2dcb076
Refactor to add helper methods in the page classes for routing
caendesilva Jul 13, 2024
a3a8113
Merge branch '2.x-dev' into generate-author-pages
caendesilva Jul 13, 2024
79aa2b3
Document test driven feature spec
caendesilva Jul 13, 2024
e345ebb
Merge branch '2.x-dev' into generate-author-pages
caendesilva Jul 17, 2024
3d03f9c
Merge branch '2.x-dev' into generate-author-pages
caendesilva Jul 17, 2024
6a9506e
Add missing test namespace
caendesilva Jul 17, 2024
79cebd9
Fix missing import
caendesilva Jul 17, 2024
0a0a3af
Merge branch '2.x-dev' into generate-author-pages
caendesilva Jul 23, 2024
e78dd2e
Evaluate compact call
caendesilva Jul 23, 2024
c0e64d9
Merge branch '2.x-dev' into generate-author-pages
caendesilva Jul 23, 2024
c5a7960
Use the navigation visibility property
caendesilva Jul 23, 2024
013dbda
Merge branch '2.x-dev' into generate-author-pages
caendesilva Jul 24, 2024
ef5cdae
Refactor JSON-LD to Microdata
caendesilva Jul 24, 2024
54c2c56
Remove author breadcrumbs
caendesilva Jul 24, 2024
449e2af
Clean up formatting
caendesilva Jul 24, 2024
b6bc6e7
Remove disallowed itemprop
caendesilva Jul 24, 2024
d293c90
Merge branch '2.x-dev' into generate-author-pages
caendesilva Jul 24, 2024
f70d99f
Removed unused route helpers
caendesilva Jul 24, 2024
c38ff01
Use the blog post feed component
caendesilva Jul 24, 2024
eb94f83
Rename test
caendesilva Jul 24, 2024
ee12486
Create some more realistic test fixture data with Claude
caendesilva Jul 24, 2024
42fafb5
Update test avatars
caendesilva Jul 24, 2024
bfa511a
Support local avatars
caendesilva Jul 24, 2024
70c633c
Fix fallback logic
caendesilva Jul 24, 2024
c1d6687
Merge branch '2.x-dev' into generate-author-pages
caendesilva Jul 24, 2024
d980e3c
Merge branch '2.x-dev' into generate-author-pages
caendesilva Jul 24, 2024
356c4c5
Fix wrong crosslink annotations
caendesilva Jul 24, 2024
3ebb758
Explicitly set the titles
caendesilva Jul 24, 2024
c635502
Use flex instead of grid to center items
caendesilva Jul 24, 2024
6f1cbe3
Update header
caendesilva Jul 24, 2024
6b1c07a
More semantic HTML
caendesilva Jul 24, 2024
c43373f
Standard text size
caendesilva Jul 24, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
<meta itemprop="image" content="{{ $post->image }}">
@endif

@isset($post->image)
<meta itemprop="image" content="{{ $post->image }}">
@endisset

<header>
<a href="{{ $post->getRoute() }}" class="block w-fit">
<h2 itemprop="headline" class="text-2xl font-bold text-gray-700 hover:text-gray-900 dark:text-gray-200 dark:hover:text-white transition-colors duration-75">
Expand All @@ -25,18 +29,18 @@
@endisset
@isset($post->author)
<span itemprop="author" itemscope itemtype="https://schema.org/Person">
<span class="opacity-75">by</span>
<span class="opacity-75">{{ isset($post->date) ? 'by' : 'By'}}</span>
<span itemprop="name">
{{ $post->author->name ?? $post->author->username }}
{{ $post->author->name }}
</span>
</span>
@endisset
</footer>

@if($post->data('description') !== null)
@isset($post->description)
<section role="doc-abstract" aria-label="Excerpt">
<p itemprop="description" class="leading-relaxed my-1">
{{ $post->data('description') }}
{{ $post->description }}
</p>
</section>
@endisset
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
by author
{{ isset($page->date) ? 'by' : 'By'}} author
<address itemprop="author" itemscope itemtype="https://schema.org/Person" aria-label="The post author" style="display: inline;">
@if($page->author->website)
<a href="{{ $page->author->website }}" rel="author" itemprop="url" aria-label="The author's website">
Expand Down
37 changes: 37 additions & 0 deletions packages/framework/resources/views/pages/author.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
@props([/** @var \Hyde\Framework\Features\Blogging\Models\PostAuthor */ 'author'])
@use('Hyde\Framework\Features\Blogging\DynamicPages\PostAuthorPage')
@use('Hyde\Framework\Features\Blogging\DynamicPages\PostAuthorsPage')
@extends('hyde::layouts.app')
@section('content')

<main id="content" class="mx-auto max-w-7xl py-16 px-8" itemscope itemtype="https://schema.org/ProfilePage">
<div class="flex flex-col items-center" itemprop="mainEntity" itemscope itemtype="https://schema.org/Person">
@if($author->avatar)
<img src="{{ asset($author->avatar) }}" alt="{{ $author->name }}" class="w-32 h-32 rounded-full mb-4" itemprop="image">
@endif
<h1 class="text-3xl font-bold mb-2 text-gray-900 dark:text-white" itemprop="name">{{ $author->name }}</h1>
@if($author->bio)
<p class="text-gray-600 dark:text-gray-300 mb-4" itemprop="description">{{ $author->bio }}</p>
@endif
@if($author->website)
<a href="{{ $author->website }}" class="text-blue-600 dark:text-blue-400 hover:underline mb-4" itemprop="url">Website</a>
@endif
@if($author->socials)
<div class="flex space-x-4 mb-6">
@foreach($author->socials as $platform => $handle)
<a href="https://{{ $platform }}.com/{{ $handle }}" class="text-gray-600 dark:text-gray-300 hover:text-gray-800 dark:hover:text-white" itemprop="sameAs">
{{ ucfirst($platform) }}
</a>
@endforeach
</div>
@endif
</div>

<div class="mt-12">
<h2 class="text-2xl font-semibold mb-4 text-gray-900 dark:text-white">Posts by {{ $author->name }}</h2>
<ul class="space-y-4">
@include('hyde::components.blog-post-feed', ['posts' => $author->getPosts()])
</ul>
</div>
</main>
@endsection
41 changes: 41 additions & 0 deletions packages/framework/resources/views/pages/authors.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
@props([/** @var \Illuminate\Support\Collection<\Hyde\Framework\Features\Blogging\Models\PostAuthor> */ 'authors'])
@use('Illuminate\Support\Str')
@extends('hyde::layouts.app')
@section('content')
@php
// If any author has an avatar, we'll use avatars for all authors, so the layout looks consistent.
$usesAvatars = $authors->contains(fn ($author) => $author->avatar);
// The avatar fallback can be changed here (and it also works if a set avatar can't be loaded).
$avatarFallback = Asset::hasMediaFile('avatar.png') ? Asset::mediaLink('avatar.png') : 'https://cdn.jsdelivr.net/gh/hydephp/cdn-static@master/avatar.png';
@endphp

<main id="content" class="mx-auto max-w-7xl py-16 px-8">
<header class="mb-8">
<h1 class="text-center text-3xl font-bold">Our Authors</h1>
</header>

<section class="flex flex-wrap justify-center gap-8">
@foreach($authors as $author)
<article class="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-md w-full md:w-1/2 lg:w-1/3" itemscope itemtype="https://schema.org/Person">
<header class="flex flex-col items-center">
@if($usesAvatars)
<img src="{{ asset($author->avatar ?? $avatarFallback) }}" alt="{{ $author->name }}" class="w-24 h-24 rounded-full mb-4" itemprop="image" onerror="this.onerror=null; this.src='{{ $avatarFallback }}'">
@endif
<h2 class="text-xl font-semibold mb-2 text-gray-900 dark:text-white" itemprop="name">{{ $author->name }}</h2>
</header>
@if($author->bio)
<p class="text-gray-600 dark:text-gray-300 mb-4 text-center" itemprop="description">{{ Str::limit($author->bio, 100) }}</p>
@endif
<nav>
<a href="{{ route(\Hyde\Framework\Features\Blogging\BlogPostAuthorPages::authorBaseRouteKey()."/$author->username") }}" class="text-blue-600 dark:text-blue-400 hover:underline" itemprop="url">View Profile</a>
</nav>
<footer class="mt-4">
<p class="text-sm text-gray-500 dark:text-gray-400">
{{ $author->getPosts()->count() }} {{ Str::plural('post', $author->getPosts()->count()) }}
</p>
</footer>
</article>
@endforeach
</section>
</main>
@endsection
7 changes: 7 additions & 0 deletions packages/framework/src/Foundation/HydeCoreExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use Hyde\Foundation\Kernel\PageCollection;
use Hyde\Foundation\Concerns\HydeExtension;
use Hyde\Facades\Features;
use Hyde\Framework\Features\Blogging\BlogPostAuthorPages;
use Hyde\Framework\Features\Documentation\DocumentationSearchPage;
use Hyde\Framework\Features\Documentation\DocumentationSearchIndex;

Expand Down Expand Up @@ -41,5 +42,11 @@ public function discoverPages(PageCollection $collection): void
$collection->addPage(new DocumentationSearchPage());
}
}

if (BlogPostAuthorPages::canGenerateAuthorPages()) {
foreach (BlogPostAuthorPages::generateAuthorPages() as $page) {
$collection->addPage($page);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php

declare(strict_types=1);

namespace Hyde\Framework\Features\Blogging;

use Hyde\Hyde;
use Hyde\Enums\Feature;
use Hyde\Pages\MarkdownPost;
use Hyde\Framework\Features\Blogging\Models\PostAuthor;
use Hyde\Framework\Features\Blogging\DynamicPages\PostAuthorPage;
use Hyde\Framework\Features\Blogging\DynamicPages\PostAuthorsPage;

/**
* General helper class for the dynamic blog post author pages feature.
*
* @internal Initial class to help with dynamic blogging related pages, like author pages, tag pages, etc.
*
* @experimental The code here will later be moved to a more appropriate place.
*/
class BlogPostAuthorPages
{
public static function canGenerateAuthorPages(): bool
{
// Todo: Also check that this feature is enabled

return Hyde::hasFeature(Feature::MarkdownPosts)
&& MarkdownPost::all()->isNotEmpty()
&& Hyde::authors()->filter(fn (PostAuthor $author): bool => $author->getPosts()->isNotEmpty())->isNotEmpty();
}

/** @return array<\Hyde\Framework\Features\Blogging\DynamicPages\PostAuthorPage> */
public static function generateAuthorPages(): array
{
// Todo: This does not find authors that have no author config, we should add those to the underlying collection!

$authors = Hyde::authors()
// This filtering is opinionated, and we can configure it, but for now it only includes authors with posts
// Todo: Unless the "guest" author has been modified, we should filter that too
->filter(fn (PostAuthor $author): bool => $author->getPosts()->isNotEmpty());

return $authors
->map(fn (PostAuthor $author): PostAuthorPage => new PostAuthorPage($author))
->prepend(new PostAuthorsPage($authors))
->all();
}

public static function authorBaseRouteKey(): string
{
// Todo: Allow customizing this
// Todo: Set author base key in the page class

return 'authors';
}

/**
* Set the base directory for the author pages. Defaults to 'authors'.
*
* @experimental
*
* @codeCoverageIgnore
*/
public static function setAuthorBaseRouteKey(string $key): void
{
PostAuthorsPage::$sourceDirectory = $key;
PostAuthorsPage::$outputDirectory = $key;
PostAuthorPage::$sourceDirectory = $key;
PostAuthorPage::$outputDirectory = $key;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

declare(strict_types=1);

namespace Hyde\Framework\Features\Blogging\DynamicPages;

use Hyde\Pages\InMemoryPage;
use Hyde\Framework\Features\Blogging\Models\PostAuthor;

/**
* @experimental
*
* @see \Hyde\Framework\Features\Blogging\BlogPostAuthorPages Which generates these pages.
* @see \Hyde\Framework\Features\Blogging\DynamicPages\PostAuthorsPage For the index page of all authors.
*/
class PostAuthorPage extends InMemoryPage
{
protected PostAuthor $author;

public static string $sourceDirectory = 'authors';
public static string $outputDirectory = 'authors';
public static string $layout = 'hyde::pages.author';

public function __construct(PostAuthor $author)
{
parent::__construct($author->username, [
'author' => $author,
'title' => $author->name,
'navigation' => [
'visible' => PostAuthorsPage::$showInNavigation,
],
]);
}

public function getBladeView(): string
{
return static::$layout;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

declare(strict_types=1);

namespace Hyde\Framework\Features\Blogging\DynamicPages;

use Hyde\Pages\InMemoryPage;
use Illuminate\Support\Collection;

/**
* @experimental
*
* @see \Hyde\Framework\Features\Blogging\BlogPostAuthorPages Which generates these pages.
* @see \Hyde\Framework\Features\Blogging\DynamicPages\PostAuthorPage For the individual author pages.
*/
class PostAuthorsPage extends InMemoryPage
{
/** @var \Illuminate\Support\Collection<\Hyde\Framework\Features\Blogging\Models\PostAuthor> */
protected Collection $authors;

public static string $sourceDirectory = 'authors';
public static string $outputDirectory = 'authors';
public static string $layout = 'hyde::pages.authors';
public static bool $showInNavigation = false;

public function __construct(Collection $authors)
{
parent::__construct('index', [
'authors' => $authors,
'title' => 'Authors',
'navigation' => [
'visible' => static::$showInNavigation,
],
]);

$this->authors = $authors;
}

public function getBladeView(): string
{
return static::$layout;
}
}
12 changes: 12 additions & 0 deletions packages/framework/src/Support/Models/Route.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,24 @@ public function __toString(): string

/**
* Generate a link to the route destination, relative to the current route, and supports pretty URLs.
*
* This is the recommended method to use when generating links to other pages in your site.
*/
public function getLink(): string
{
return Hyde::relativeLink($this->page->getLink());
}

/**
* Get the canonical URL for the route, if a site URL is set, falling back to a relative link.
*
* While this can decrease portability, it can be useful for SEO purposes when used correctly.
*/
public function getUrl(): string
{
return $this->page->getCanonicalUrl() ?? $this->getLink();
}

public function getPage(): HydePage
{
return $this->page;
Expand Down
Loading