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

Pagination support #140

Merged
merged 31 commits into from
Jul 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
921b532
Add `pagination_route` config.
jesseleite Jul 11, 2023
846bed8
Detect and generate paginated pages (wip).
jesseleite Jul 11, 2023
80da33a
Merge branch 'master' of github.com:statamic/ssg into pagination
jesseleite Jul 11, 2023
c58b4a8
Shhh
jesseleite Jul 11, 2023
62a3c95
Fix paginator state issue.
jesseleite Jul 11, 2023
b5891b9
Clear pagination state for every new page, unless we’re intending to …
jesseleite Jul 11, 2023
bc78235
Throw separate exceptions for each paginated page’s error (if any).
jesseleite Jul 11, 2023
9a07457
Simplify request pagination logic.
jesseleite Jul 11, 2023
70d6745
Hook up pagination links.
jesseleite Jul 11, 2023
2e039c8
Unused.
jesseleite Jul 11, 2023
780452a
Only extend in console.
jesseleite Jul 12, 2023
7a0ee3b
Support `{page_name}` off collection tag in pagination route.
jesseleite Jul 12, 2023
e9b3a8a
Fix clearing of page query param when using custom `page_name` on tag.
jesseleite Jul 12, 2023
4af8e34
Fix stray `?` by removing query param input instead of setting null.
jesseleite Jul 12, 2023
9de096a
Add basic `ssg:generate` test.
jesseleite Jul 12, 2023
cfbb5bb
Test `$request->forget()`.
jesseleite Jul 12, 2023
efbe00a
Fix test suite failure due to mock causing `class_exists(Fork::class)…
jesseleite Jul 13, 2023
840f44c
Pass for windows?
jesseleite Jul 13, 2023
cf9b46a
Debug paths
jesseleite Jul 13, 2023
02e9d66
Windows pls
jesseleite Jul 13, 2023
ad8d66b
Undo windows path debugging.
jesseleite Jul 13, 2023
20df99a
Test that it can generate to custom destination.
jesseleite Jul 13, 2023
9df9539
Assert actual generated paths.
jesseleite Jul 13, 2023
a1523c8
Test that it generates pagination properly.
jesseleite Jul 13, 2023
c8ddd37
Test that it generates pagination with custom `page_name` and `pagina…
jesseleite Jul 13, 2023
7cdb146
Add `Localized\GenerateTest` coverage.
jesseleite Jul 13, 2023
459d048
run the test causing the issue in isolation instead
jasonvarga Jul 13, 2023
853e6e1
do it this way
jasonvarga Jul 13, 2023
c7ab397
Document pagination routing.
jesseleite Jul 13, 2023
c25b846
simplify bootstrapping
jasonvarga Jul 13, 2023
e9a806f
avoid needing to duplicate views across site fixtures
jasonvarga Jul 13, 2023
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.DS_Store
.phpunit.result.cache
/vendor
/composer.lock
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ You can also exclude single routes, or route groups with wildcards. This will ov
],
```

### Dynamically adding routes
### Dynamic Routes

You may add URLs dynamically by providing a closure that returns an array to the `addUrls` method.

Expand All @@ -81,6 +81,16 @@ class AppServiceProvider extends Provider
}
```

### Pagination Routes

Wherever pagination is detected in your antlers templates (eg. if you use the `paginate` param on the `collection` tag), multiple pages will automatically be generated with `/articles/page/2` style urls.

You may configure a custom routing style in `config/statamic/ssg.php`:

```php
'pagination_route' => '{url}/{page_name}/{page_number}',
```


## Post-generation callback

Expand Down
13 changes: 13 additions & 0 deletions config/ssg.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,19 @@
//
],

/*
|--------------------------------------------------------------------------
| Pagination Route
|--------------------------------------------------------------------------
|
| Here you may define how paginated entries are routed. This will take
| effect wherever pagination is detected in your antlers templates,
| like if you use the `paginate` param on the `collection` tag.
|
*/

'pagination_route' => '{url}/{page_name}/{page_number}',

/*
|--------------------------------------------------------------------------
| Glide
Expand Down
2 changes: 2 additions & 0 deletions phpunit.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,7 @@
<env name="CACHE_DRIVER" value="array"/>
<env name="SESSION_DRIVER" value="array"/>
<env name="QUEUE_DRIVER" value="sync"/>
<env name="APP_URL" value="http://cool-runnings.com" />
<env name="APP_KEY" value="base64:QeU8nSJFKtBB3Y9SdxH0U4xH/1rsFd4zNfOLTeK/DUw=" />
</php>
</phpunit>
39 changes: 39 additions & 0 deletions src/LengthAwarePaginator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

namespace Statamic\StaticSite;

use Statamic\Extensions\Pagination\LengthAwarePaginator as StatamicLengthAwarePaginator;
use Statamic\Facades\URL;
use Statamic\Support\Arr;
use Statamic\Support\Str;

class LengthAwarePaginator extends StatamicLengthAwarePaginator
{
public function url($page)
{
if ($page <= 0) {
$page = 1;
}

$url = self::generatePaginatedUrl($this->path(), $this->getPageName(), $page);

if (Str::contains($this->path(), '?') || count($this->query)) {
$url .= '?'.Arr::query($this->query);
}

return $url.$this->buildFragment();
}

public static function generatePaginatedUrl($url, $pageName, $pageNumber)
{
$route = config('statamic.ssg.pagination_route');

$url = str_replace('{url}', $url, $route);

$url = str_replace('{page_name}', $pageName, $url);

$url = str_replace('{page_number}', $pageNumber, $url);

return URL::makeRelative($url);
}
}
71 changes: 69 additions & 2 deletions src/Page.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@
use Illuminate\Filesystem\Filesystem;
use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Http\RedirectResponse;
use Statamic\Facades\Blink;

class Page
{
protected $files;
protected $config;
protected $content;
protected $paginationPageName;
protected $paginationCurrentPage;

public function __construct(Filesystem $files, array $config, $content)
{
Expand All @@ -33,14 +36,24 @@ public function isGeneratable()
public function generate($request)
{
try {
return $this->write($request);
$generatedPage = $this->write($request);
} catch (Exception $e) {
throw new NotGeneratedException($this, $e);
}

if ($paginator = $this->detectPaginator($request)) {
$this->writePaginatedPages($request, $paginator);
}

return $generatedPage;
}

protected function write($request)
{
if ($this->paginationPageName) {
$request->merge([$this->paginationPageName => $this->paginationCurrentPage]);
}

try {
$response = $this->content->toResponse($request);
} catch (HttpResponseException $e) {
Expand All @@ -59,6 +72,23 @@ protected function write($request)
return new GeneratedPage($this, $response);
}

protected function writePaginatedPages($request, $paginator)
{
collect(range(1, $paginator->lastPage()))->each(function ($pageNumber) use ($request) {
$page = clone $this;

try {
$page
->setPaginationCurrentPage($pageNumber)
->write($request);
} catch (Exception $e) {
throw new NotGeneratedException($page, $e);
}
});

$this->clearPaginator($request);
}

public function directory()
{
return dirname($this->path());
Expand All @@ -85,7 +115,17 @@ public function path()

public function url()
{
return $this->content->urlWithoutRedirect();
$url = $this->content->urlWithoutRedirect();

if ($this->paginationCurrentPage) {
$url = LengthAwarePaginator::generatePaginatedUrl(
$url,
$this->paginationPageName,
$this->paginationCurrentPage
);
}

return $url;
}

public function site()
Expand All @@ -97,4 +137,31 @@ public function is404()
{
return $this->url() === '/404';
}

public function setPaginationCurrentPage($currentPage)
{
$this->paginationCurrentPage = $currentPage;

return $this;
}

protected function detectPaginator($request)
{
if ($paginator = Blink::get('tag-paginator')) {
$this->paginationPageName = $paginator->getPageName();
}

$this->clearPaginator($request);

return $paginator;
}

protected function clearPaginator($request)
{
Blink::forget('tag-paginator');

if ($this->paginationPageName) {
$request->forget($this->paginationPageName);
}
}
}
7 changes: 7 additions & 0 deletions src/Request.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,11 @@ public function path()

return ltrim($path, '/');
}

public function forget(string $inputKey)
{
$this->getInputSource()->remove($inputKey);

return $this;
}
}
17 changes: 16 additions & 1 deletion src/ServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@

use Illuminate\Support\ServiceProvider as LaravelServiceProvider;
use Spatie\Fork\Fork;
use Statamic\Extensions\Pagination\LengthAwarePaginator as StatamicLengthAwarePaginator;

class ServiceProvider extends LaravelServiceProvider
{
public function register()
{
$this->app->bind('fork-installed', fn () => class_exists(Fork::class));

$this->app->bind(Tasks::class, function ($app) {
return $app->runningInConsole() && class_exists(Fork::class)
return $app->runningInConsole() && $app['fork-installed']
? new ConcurrentTasks(new Fork)
: new ConsecutiveTasks;
});
Expand All @@ -33,5 +36,17 @@ public function boot()
Commands\StaticSiteServe::class,
]);
}

if ($this->app->runningInConsole()) {
$this->app->extend(StatamicLengthAwarePaginator::class, function ($paginator) {
return $this->app->makeWith(LengthAwarePaginator::class, [
'items' => $paginator->getCollection(),
'total' => $paginator->total(),
'perPage' => $paginator->perPage(),
'currentPage' => $paginator->currentPage(),
'options' => $paginator->getOptions(),
]);
});
}
}
}
77 changes: 77 additions & 0 deletions tests/Concerns/RunsGeneratorCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?php

namespace Tests\Concerns;

use Statamic\Facades\Path;

trait RunsGeneratorCommand
{
protected function setUp(): void
{
parent::setUp();

$this->destination = storage_path('app/static');

$this->cleanUpDestination();
}

public function tearDown(): void
{
$this->cleanUpDestination();

parent::tearDown();
}

protected function generate()
{
$this->assertFalse($this->files->exists($this->destination));

$this
->artisan('statamic:ssg:generate')
->doesntExpectOutputToContain('pages not generated');

$this->assertTrue($this->files->exists($this->destination));

return $this->getGeneratedFilesAtPath($this->destination);
}

protected function relativePath($path)
{
return str_replace(Path::tidy($this->destination.'/'), '', Path::tidy($path));
}

protected function destinationPath($path)
{
return Path::tidy($this->destination.'/'.$path);
}

protected function getGeneratedFilesAtPath($path)
{
return collect($this->files->allFiles($path))
->mapWithKeys(fn ($file) => [$this->relativePath($file->getPathname()) => $file->getContents()])
->all();
}

protected function cleanUpDestination($destination = null)
{
$destination ??= $this->destination;

if ($this->files->exists($destination)) {
$this->files->deleteDirectory($destination);
}
}

protected function assertStringContainsStrings($needleStrings, $haystackString)
{
foreach ($needleStrings as $string) {
$this->assertStringContainsString($string, $haystackString);
}
}

protected function assertStringNotContainsStrings($needleStrings, $haystackString)
{
foreach ($needleStrings as $string) {
$this->assertStringNotContainsString($string, $haystackString);
}
}
}
4 changes: 4 additions & 0 deletions tests/Fixtures/resources/views/articles/index.antlers.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<h1>Articles Index Page Title</h1>
{{ collection:articles sort="date:asc" }}
<a href="{{ permalink }}">{{ title }}</a>
{{ /collection:articles }}
2 changes: 2 additions & 0 deletions tests/Fixtures/resources/views/articles/show.antlers.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<h1>Article Title: {{ title }}</h1>
<p>Content: {{ content }}</p>
2 changes: 2 additions & 0 deletions tests/Fixtures/resources/views/default.antlers.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<h1>Page Title: {{ title }}</h1>
<p>Content: {{ content }}</p>
1 change: 1 addition & 0 deletions tests/Fixtures/resources/views/errors/404.antlers.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<h1>404!</h1>
2 changes: 2 additions & 0 deletions tests/Fixtures/resources/views/layout.antlers.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{{ seo_pro:meta }}
{{ template_content }}
16 changes: 16 additions & 0 deletions tests/Fixtures/site-localized/content/collections/articles.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
title: Articles
sites:
- default
- french
route:
default: 'articles/{slug}'
french: 'le-articles/{slug}'
template: articles.show
mount: b9e4bfe3-9c12-4553-b7ef-f43c22ffaa63
taxonomies:
- topics
revisions: false
date: true
date_behavior:
past: public
future: private
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
id: a356f052-06a3-4f37-8376-2f136fe973c3
title: One
---
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
id: ca648856-ff12-407d-a523-72074f5997e1
title: Two
---
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
id: 312dff5e-4d4e-44bd-ac47-80ef58278675
title: Three
---
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
id: d813ea6f-a637-4c6b-a724-c9e5f70e23de
title: Four
---
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
id: 3a2e2f92-f93e-481d-b120-c63ff09c1775
title: Five
---
Loading