diff --git a/app/Classes/Bundle.php b/app/Classes/Bundle.php index 561ff03..e68fc3b 100644 --- a/app/Classes/Bundle.php +++ b/app/Classes/Bundle.php @@ -164,11 +164,13 @@ public function exists(): bool /** * Load everything */ - public function load(): void + public function load(): self { $this->loadAttachments(); $this->loadMetadata(); $this->loadMarkdown(); + + return $this; } /** @@ -208,14 +210,11 @@ public function repair(): bool public function touch() { - $this->metadata()->set('lastModified', now()->toIso8601String()); - $this->saveMetadata(); + // $parent = $this->getParent(); - $parent = $this->getParent(); - - if (!empty($parent)) { - $parent->touch(); - } + // if (!empty($parent)) { + // $parent->touch(); + // } } public function render() @@ -223,9 +222,6 @@ public function render() $renderer = BundleRenderer::getBundleRendererFor($this); $result = $renderer->render(); - $this->metadata()->set('lastRendered', now()->toIso8601String()); - $this->metadata()->save(); - return $result; } @@ -275,6 +271,33 @@ public static function findBundles(FilesystemAdapter $disk, ?string $path = '/', return $bundles; } + public static function getFeedItems(FilesystemAdapter $disk) + { + $subBundles = Bundle::findBundles($disk, '/', true); + + $subBundles = collect($subBundles) + ->filter(fn (Bundle $bundle) => !empty($bundle->metadata()->get('date'))) + ->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->load()) + ->take(10); + + return $subBundles; + } + + public static function renderFeed(FilesystemAdapter $disk) + { + $lastBundles = static::getFeedItems($disk); + + return [ + '/index.xml' => (string) view('feed', [ + 'bundles' => $lastBundles, + 'lastBuildDate' => now()->toRssString(), + ]), + ]; + } + private function repairCover() { $cover = $this->metadata()->get('cover'); diff --git a/app/Console/Commands/Bundle/Render.php b/app/Console/Commands/Bundle/Render.php index 01ecc25..fa4d1cd 100644 --- a/app/Console/Commands/Bundle/Render.php +++ b/app/Console/Commands/Bundle/Render.php @@ -36,6 +36,7 @@ public function __construct() { --r|recursive : Also render sub-bundles } { --source-disk= : Use specified content disk - Defaults to ' . env('CONTENT_DISK') . ' } { --target-disk= : Use specified rendering disk - Defaults to ' . env('DIST_DISK') . ' } + { --validate : Perform validations before rendering } { path? : Path to a specific bundle to render - Default to / } '; @@ -51,6 +52,8 @@ public function handle() ->selectDisk() ->renderAssets() ->clearCache() + ->validate() + ->renderFeed() ->render() ->deploy(); } @@ -137,6 +140,23 @@ private function clearCache(): self return $this; } + private function validate(): self + { + if (!$this->option('validate')) { + return $this; + } + + $result = $this->call('bundle:validate', ['--recursive' => true]); + + if (!empty($result)) { + if (confirm('Validation errors have occurred. Cancel process?')) { + exit(1); + } + } + + return $this; + } + /** * Collect a list of bundles to render */ @@ -153,7 +173,7 @@ private function getBundles() $this->output->write('Collecting bundles... '); if ($this->option('recursive')) { - $bundles = Bundle::findBundles($this->sourceDisk, $path, true); + $bundles = Bundle::findBundles($this->sourceDisk, $path, true, false); } else { $bundles = [new Bundle($path, $this->sourceDisk)]; } @@ -163,6 +183,19 @@ private function getBundles() return $bundles; } + private function renderFeed(): self + { + $result = Bundle::renderFeed($this->sourceDisk); + + foreach ($result as $path => $content) { + $this->output->write(sprintf('Rendering %s as feed... ', $path)); + $this->targetDisk->put($path, $content); + $this->info('OK'); + } + + return $this; + } + /** * Perform rendering */ @@ -185,7 +218,6 @@ private function render(): self private function handleBundle(Bundle $bundle, $progress) { $progress->label(sprintf('Rendering %s ...', $bundle->getPath())); - $progress->hint('Rendering the bundle'); if ($this->option('dry-run')) { return; @@ -198,8 +230,6 @@ private function handleBundle(Bundle $bundle, $progress) $path = Str::finish($path, '/') . 'index.html'; } - $progress->hint(sprintf('Storing %s', $path)); - $this->targetDisk->put($path, $content); } } diff --git a/app/Services/BundleRenderers/Renderers/BaseRenderer.php b/app/Services/BundleRenderers/Renderers/BaseRenderer.php index 99e4cb5..8020e80 100644 --- a/app/Services/BundleRenderers/Renderers/BaseRenderer.php +++ b/app/Services/BundleRenderers/Renderers/BaseRenderer.php @@ -7,7 +7,11 @@ use App\Classes\Link; use App\Services\BundleRenderers\Contracts\RendersBundle; use Carbon\Carbon; +use DOMXPath; use Exception; +use Illuminate\Support\Facades\Cache; +use Illuminate\Support\Str; +use Masterminds\HTML5; abstract class BaseRenderer implements RendersBundle { @@ -44,17 +48,17 @@ public function render() $this->prepareRender($currentPage); if ($currentPage === 1) { - $result[$this->bundle->getPath()] = $this->renderView(); + $result[$this->bundle->getPath()] = $this->renderPage(); } $page = sprintf('%spage/%s/', $this->bundle->getPath(), $currentPage); - $result[$page] = $this->renderView(); + $result[$page] = $this->renderPage(); } } else { $this->prepareRender(1); - $result[$this->bundle->getPath()] = $this->renderView(); + $result[$this->bundle->getPath()] = $this->renderPage(); } return $result; @@ -100,6 +104,18 @@ protected function prepareRender(int $currentPage = 1) data_set($this->viewData, 'cover', $cover ? $cover->render() : null); data_set($this->viewData, 'body', $this->bundle->markdown()->render()); + data_set($this->viewData, 'feedIsValid', Cache::get('feed_is_valid', false)); + data_set($this->viewData, 'cssIsValid', Cache::get('css_is_valid', false)); + + if ($currentPage === 1) { + $path = $this->bundle->getPath(); + } else { + $path = sprintf('%spage/%s/', $this->bundle->getPath(), $currentPage); + } + + $cacheKey = sprintf('html_is_valid_%s', Str::slug($path)); + + data_set($this->viewData, 'htmlIsValid', Cache::get($cacheKey, false)); $this->handlePagination($currentPage); } @@ -215,10 +231,39 @@ protected function collectSubBundles() return $subBundles; } - protected function renderView(?string $view = 'article') + /** + * Renders a full-page. Validates HTML + */ + protected function renderPage(?string $view = 'article') { - $html = (string) view($view, $this->viewData); + $html = $this->renderView($view); + + $html5 = new HTML5([ + // Required tu use xpath, see + // https://github.com/Masterminds/html5-php/issues/123 + 'disable_html_ns' => true, + ]); + + $dom = $html5->loadHTML($html); + $xpath = new DOMXPath($dom); + $spans = $xpath->query('//pre/code/span[@class="line" and normalize-space(.) = ""]'); + + foreach ($spans as $span) { + if ($span->isSameNode($span->parentNode->lastChild)) { + $span->parentNode->removeChild($span); + } + } + + $html = $html5->saveHTML($dom); return $html; } + + /** + * Render specific view + */ + protected function renderView(string $view) + { + return (string) view($view, $this->viewData); + } } diff --git a/app/Services/BundleRenderers/Renderers/DossierRenderer.php b/app/Services/BundleRenderers/Renderers/DossierRenderer.php index 1c5b23e..846a0c4 100644 --- a/app/Services/BundleRenderers/Renderers/DossierRenderer.php +++ b/app/Services/BundleRenderers/Renderers/DossierRenderer.php @@ -20,7 +20,7 @@ public function render() data_set($this->viewData, 'showToc', true); data_set($this->viewData, 'dossier', $dossier); - return [$this->bundle->getPath() => $this->renderView()]; + return [$this->bundle->getPath() => $this->renderPage()]; } /** diff --git a/app/Services/BundleRenderers/Renderers/TermRenderer.php b/app/Services/BundleRenderers/Renderers/TermRenderer.php index e46ae22..722fbcb 100644 --- a/app/Services/BundleRenderers/Renderers/TermRenderer.php +++ b/app/Services/BundleRenderers/Renderers/TermRenderer.php @@ -27,7 +27,7 @@ public function render() data_set($this->viewData, 'relations', $relations); - return [$this->bundle->getPath() => $this->renderView('term')]; + return [$this->bundle->getPath() => $this->renderPage()]; } /**