Wire admin studio SSR and search infrastructure
This commit is contained in:
@@ -4,7 +4,10 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Support\Seo;
|
||||
|
||||
final class SeoData
|
||||
use Illuminate\Contracts\Support\Arrayable;
|
||||
|
||||
/** @implements Arrayable<string, mixed> */
|
||||
final class SeoData implements Arrayable
|
||||
{
|
||||
/**
|
||||
* @param array<string, mixed> $attributes
|
||||
|
||||
@@ -82,6 +82,8 @@ final class SeoDataBuilder
|
||||
url: $attributes['og_url'] ?? null,
|
||||
image: $attributes['og_image'] ?? null,
|
||||
imageAlt: $attributes['og_image_alt'] ?? null,
|
||||
imageWidth: isset($attributes['og_image_width']) ? (int) $attributes['og_image_width'] : null,
|
||||
imageHeight: isset($attributes['og_image_height']) ? (int) $attributes['og_image_height'] : null,
|
||||
);
|
||||
$builder->twitter(
|
||||
card: $attributes['twitter_card'] ?? null,
|
||||
@@ -121,7 +123,7 @@ final class SeoDataBuilder
|
||||
|
||||
public function canonical(?string $canonical): self
|
||||
{
|
||||
$this->canonical = $this->absoluteUrl($canonical);
|
||||
$this->canonical = $this->absolutePageUrl($canonical);
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -141,13 +143,13 @@ final class SeoDataBuilder
|
||||
|
||||
public function prev(?string $prev): self
|
||||
{
|
||||
$this->prev = $this->absoluteUrl($prev);
|
||||
$this->prev = $this->absolutePageUrl($prev);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function next(?string $next): self
|
||||
{
|
||||
$this->next = $this->absoluteUrl($next);
|
||||
$this->next = $this->absolutePageUrl($next);
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -168,7 +170,7 @@ final class SeoDataBuilder
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function og(?string $type = null, ?string $title = null, ?string $description = null, ?string $url = null, ?string $image = null, ?string $imageAlt = null): self
|
||||
public function og(?string $type = null, ?string $title = null, ?string $description = null, ?string $url = null, ?string $image = null, ?string $imageAlt = null, ?int $imageWidth = null, ?int $imageHeight = null): self
|
||||
{
|
||||
$this->og = array_filter([
|
||||
'type' => $this->clean($type),
|
||||
@@ -177,6 +179,8 @@ final class SeoDataBuilder
|
||||
'url' => $this->absoluteUrl($url),
|
||||
'image' => $this->absoluteUrl($image),
|
||||
'image_alt' => $this->clean($imageAlt),
|
||||
'image_width' => $imageWidth,
|
||||
'image_height' => $imageHeight,
|
||||
], fn (mixed $value): bool => $value !== null && $value !== '');
|
||||
|
||||
return $this;
|
||||
@@ -199,7 +203,7 @@ final class SeoDataBuilder
|
||||
$title = $this->title ?: (string) config('seo.default_title', 'Skinbase');
|
||||
$description = $this->description ?: (string) config('seo.default_description', 'Skinbase');
|
||||
$robots = $this->robots ?: (string) config('seo.default_robots', 'index,follow');
|
||||
$canonical = $this->canonical ?: url()->current();
|
||||
$canonical = $this->canonical ?: $this->defaultCanonicalUrl();
|
||||
$fallbackImage = $this->absoluteUrl((string) config('seo.fallback_image_path', '/gfx/skinbase_back_001.webp'));
|
||||
$image = $this->og['image'] ?? $this->twitter['image'] ?? $fallbackImage;
|
||||
|
||||
@@ -246,6 +250,8 @@ final class SeoDataBuilder
|
||||
'og_url' => $ogUrl,
|
||||
'og_image' => $image,
|
||||
'og_image_alt' => $this->og['image_alt'] ?? null,
|
||||
'og_image_width' => $this->og['image_width'] ?? null,
|
||||
'og_image_height' => $this->og['image_height'] ?? null,
|
||||
'og_site_name' => (string) config('seo.site_name', 'Skinbase'),
|
||||
'twitter_card' => $twitterCard,
|
||||
'twitter_title' => $this->twitter['title'] ?? $ogTitle,
|
||||
@@ -325,4 +331,45 @@ final class SeoDataBuilder
|
||||
|
||||
return url($url);
|
||||
}
|
||||
|
||||
private function absolutePageUrl(?string $url): ?string
|
||||
{
|
||||
$url = $this->clean($url);
|
||||
if ($url === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$base = rtrim((string) config('app.url', url('/')), '/');
|
||||
|
||||
if (preg_match('/^https?:\/\//i', $url) === 1) {
|
||||
$parts = parse_url($url);
|
||||
if ($parts === false) {
|
||||
return $base;
|
||||
}
|
||||
|
||||
$path = $parts['path'] ?? '/';
|
||||
$query = isset($parts['query']) ? '?' . $parts['query'] : '';
|
||||
$fragment = isset($parts['fragment']) ? '#' . $parts['fragment'] : '';
|
||||
|
||||
return $base . ($path !== '' ? $path : '/') . $query . $fragment;
|
||||
}
|
||||
|
||||
if ($url[0] === '?') {
|
||||
return $this->defaultCanonicalUrl() . $url;
|
||||
}
|
||||
|
||||
if ($url[0] !== '/') {
|
||||
$url = '/' . $url;
|
||||
}
|
||||
|
||||
return $base . $url;
|
||||
}
|
||||
|
||||
private function defaultCanonicalUrl(): string
|
||||
{
|
||||
$request = app()->bound('request') ? app('request') : null;
|
||||
$path = $request?->getPathInfo() ?: '/';
|
||||
|
||||
return $this->absolutePageUrl($path) ?? rtrim((string) config('app.url', url('/')), '/');
|
||||
}
|
||||
}
|
||||
@@ -52,12 +52,15 @@ final class SeoFactory
|
||||
$image = $thumbs['xl']['url'] ?? $thumbs['lg']['url'] ?? $thumbs['md']['url'] ?? null;
|
||||
$keywords = $artwork->tags->pluck('name')->filter()->unique()->values()->all();
|
||||
|
||||
$imageWidth = $thumbs['xl']['width'] ?? $thumbs['lg']['width'] ?? null;
|
||||
$imageHeight = $thumbs['xl']['height'] ?? $thumbs['lg']['height'] ?? null;
|
||||
|
||||
return SeoDataBuilder::make()
|
||||
->title(sprintf('%s by %s — %s', $title, $authorName, config('seo.site_name', 'Skinbase')))
|
||||
->description($description)
|
||||
->keywords($keywords)
|
||||
->canonical($canonical)
|
||||
->og(type: 'article', image: $image)
|
||||
->og(type: 'article', image: $image, imageAlt: sprintf('%s by %s', $title, $authorName), imageWidth: is_int($imageWidth) ? $imageWidth : null, imageHeight: is_int($imageHeight) ? $imageHeight : null)
|
||||
->addJsonLd(array_filter([
|
||||
'@context' => 'https://schema.org',
|
||||
'@type' => 'ImageObject',
|
||||
@@ -147,6 +150,17 @@ final class SeoFactory
|
||||
->build();
|
||||
}
|
||||
|
||||
public function simplePage(string $title, string $description, string $canonical, bool $indexable = true): SeoData
|
||||
{
|
||||
return SeoDataBuilder::make()
|
||||
->title($title)
|
||||
->description($description)
|
||||
->canonical($canonical)
|
||||
->indexable($indexable)
|
||||
->og(type: 'website')
|
||||
->build();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
* @return array<string, mixed>
|
||||
|
||||
Reference in New Issue
Block a user