diff --git a/app/Providers/BundleRendererServiceProvider.php b/app/Providers/BundleRendererServiceProvider.php index b2b57b2..2b85564 100644 --- a/app/Providers/BundleRendererServiceProvider.php +++ b/app/Providers/BundleRendererServiceProvider.php @@ -7,9 +7,7 @@ class BundleRendererServiceProvider extends ServiceProvider { - protected array $bundleRenderers = [ - \App\Services\BundleRenderers\Renderers\BlogRenderer::class, - ]; + protected array $bundleRenderers = []; /** * Register services. diff --git a/app/Services/BundleRenderers/BundleRendererFactory.php b/app/Services/BundleRenderers/BundleRendererFactory.php index 729a0a0..9175877 100644 --- a/app/Services/BundleRenderers/BundleRendererFactory.php +++ b/app/Services/BundleRenderers/BundleRendererFactory.php @@ -3,7 +3,7 @@ namespace App\Services\BundleRenderers; use App\Classes\Bundle; -use App\Exceptions\BundleRendererCannotBeFound; +use App\Services\BundleRenderers\Renderers\ListRenderer; class BundleRendererFactory { @@ -23,7 +23,7 @@ public function getBundleRendererFor(Bundle $bundle) } } - throw new BundleRendererCannotBeFound(); + return new ListRenderer($bundle); } /** diff --git a/app/Services/BundleRenderers/Renderers/BaseRenderer.php b/app/Services/BundleRenderers/Renderers/BaseRenderer.php index 9b0710c..e50483f 100644 --- a/app/Services/BundleRenderers/Renderers/BaseRenderer.php +++ b/app/Services/BundleRenderers/Renderers/BaseRenderer.php @@ -2,14 +2,32 @@ namespace App\Services\BundleRenderers\Renderers; +use App\Classes\AttachmentsManager; use App\Classes\Bundle; use App\Services\BundleRenderers\Contracts\RendersBundle; +use Carbon\Carbon; +use Exception; +use Illuminate\Support\Facades\Cache; abstract class BaseRenderer implements RendersBundle { + protected array $viewData = []; + public function __construct(protected Bundle $bundle) { $bundle->load(); + + $this->shareCommonDataWithView(); + } + + /** + * Renders a card HTML view of the bundle, suitable to display in lists + */ + public function renderCard() + { + return view('article-card', [ + 'bundle' => $this->bundle, + ]); } /** @@ -19,4 +37,122 @@ public static function make(Bundle $bundle): RendersBundle { return new static($bundle); } + + /** + * Renders a complete HTML view of the bundle + */ + protected function prepareRender() + { + $this->bundle->markdown()->lint(); + + $coverRef = $this->bundle->metadata()->get('cover'); + $cover = null; + + if (!empty($coverRef)) { + $cover = $this->bundle->attachments(AttachmentsManager::Images)->getComponentByRef($coverRef, 'article'); + } + + $date = $this->bundle->metadata()->get('date'); + + if (!empty($date)) { + data_set($this->viewData, 'date', Carbon::parse($date)); + } + + data_set($this->viewData, 'cover', $cover ? $cover->render() : null); + data_set($this->viewData, 'body', $this->bundle->markdown()->render()); + + $this->handlePagination(); + } + + /** + * Pre-fill view with data used by all renderers + */ + protected function shareCommonDataWithView(): void + { + data_set($this->viewData, 'siteTitle', $this->bundle->getSiteTitle()); + data_set($this->viewData, 'articleTitle', $this->bundle->getArticleTitle()); + data_set($this->viewData, 'bundle', $this->bundle); + } + + protected function handlePagination() + { + $itemsPerPage = config('pagination.itemsPerPage'); + $currentPage = $this->bundle->getCurrentPage(); + $lastModified = $this->bundle->lastModified(); + + $cacheKey = sprintf('subBundles_%s', base64_encode($this->bundle->getPath())); + $cacheKeyLm = sprintf('subBundles_%s_lastModified', base64_encode($this->bundle->getPath())); + + if ( + Cache::has($cacheKeyLm) + && Cache::has($cacheKey) + && $lastModified + && Cache::get($cacheKeyLm)->gte($lastModified) + ) { + $subBundles = Cache::get($cacheKey); + } else { + $subBundles = $this->collectSubBundles(); + + Cache::put($cacheKey, $subBundles, now()->addWeek()); + Cache::put($cacheKeyLm, $lastModified, now()->addWeek()); + } + + if (!$this->bundle->exists() && empty($subBundles)) { + throw new Exception(sprintf( + 'Page %s does not exist in bundle %s', + $this->bundle->getCurrentPage(), + $this->bundle->getPath() + )); + } + + $totalPages = 1; + + if (!empty($subBundles)) { + $chunks = array_chunk($subBundles, $itemsPerPage); + + if (!array_key_exists($currentPage - 1, $chunks) && $currentPage > 1) { + throw new Exception(sprintf( + 'Page %s does not exist in bundle %s', + $currentPage - 1, + $this->bundle->getPath() + )); + } + + $currentChunk = $chunks[$currentPage - 1]; + $totalPages = count($chunks); + } else { + $currentChunk = []; + } + + $bundles = []; + + if (!empty($currentChunk)) { + $bundles = array_map(fn (string $path) => new Bundle($path, $this->bundle->getDisk()), $currentChunk); + } + + $data = [ + 'root' => $this->bundle->getPath(), + 'itemsPerPage' => $itemsPerPage, + 'currentPage' => $currentPage, + 'totalPages' => $totalPages, + 'items' => $bundles, + ]; + + data_set($this->viewData, 'pagination', $data); + } + + protected function collectSubBundles() + { + $subBundles = Bundle::findBundles($this->bundle->getDisk(), $this->bundle->getPath(), true); + + $subBundles = collect($subBundles) + ->filter(fn (Bundle $bundle) => !empty($bundle->metadata()->get('date')) && $bundle->getPath() !== $this->bundle->getPath()) + ->sort(function (Bundle $bundleA, Bundle $bundleB) { + return Carbon::parse($bundleA->metadata()->get('date'))->lt(Carbon::parse($bundleB->metadata()->get('date'))); + }) + ->map(fn (Bundle $bundle) => $bundle->getPath()) + ->toArray(); + + return $subBundles; + } } diff --git a/app/Services/BundleRenderers/Renderers/BlogRenderer.php b/app/Services/BundleRenderers/Renderers/BlogRenderer.php deleted file mode 100644 index d14b9c8..0000000 --- a/app/Services/BundleRenderers/Renderers/BlogRenderer.php +++ /dev/null @@ -1,32 +0,0 @@ - $this->bundle->metadata()->get('title'), - 'date' => Carbon::parse($this->bundle->metadata()->get('date'))->format('d/m/Y'), - 'body' => $this->bundle->markdown()->render(), - ]); - } - - /** - * Return a boolean value indicating if this creator in particular can - * create bundles for specified section - */ - public static function handles(Bundle $bundle): bool - { - $parts = preg_split('#/#', $bundle->getPath(), -1, PREG_SPLIT_NO_EMPTY); - - return $parts && $parts[0] === 'blog' && count($parts) === 5; - } -} diff --git a/app/Services/BundleRenderers/Renderers/ListRenderer.php b/app/Services/BundleRenderers/Renderers/ListRenderer.php new file mode 100644 index 0000000..eabdcf5 --- /dev/null +++ b/app/Services/BundleRenderers/Renderers/ListRenderer.php @@ -0,0 +1,29 @@ +prepareRender(); + + return view('article', $this->viewData); + } + + /** + * Return a boolean value indicating if this creator in particular can + * create bundles for specified section + */ + public static function handles(Bundle $bundle): bool + { + // This renderer is used by default when no other renderer claimed it + // can handle the path + return false; + } +}