Update (seo)

Add (lqip)
This commit is contained in:
2023-09-25 02:41:40 +08:00
parent ef9c5a1407
commit 3aa6367e83
20 changed files with 163 additions and 38 deletions

View File

@@ -5,12 +5,22 @@
use App\Http\Controllers\Controller;
use App\Models\Category;
use App\Models\Post;
use Artesaos\SEOTools\Facades\SEOTools;
use Illuminate\Http\Request;
class FrontListController extends Controller
{
public function index(Request $request)
{
$title = 'Latest News from EchoScoop';
SEOTools::metatags();
SEOTools::twitter();
SEOTools::opengraph();
SEOTools::jsonLd();
SEOTools::setTitle($title, false);
$posts = Post::where('status', 'publish')->orderBy('published_at', 'desc')->simplePaginate(10) ?? collect();
return view('front.post_list', compact('posts'));
@@ -22,6 +32,14 @@ public function category(Request $request, $category_slug)
$posts = $category?->posts()->where('status', 'publish')->orderBy('published_at', 'desc')->simplePaginate(10) ?? collect();
$title = $category->name.' News from EchoScoop';
SEOTools::metatags();
SEOTools::twitter();
SEOTools::opengraph();
SEOTools::jsonLd();
SEOTools::setTitle($title, false);
return view('front.post_list', compact('category', 'posts'));
}
}

View File

@@ -4,6 +4,9 @@
use App\Http\Controllers\Controller;
use App\Models\Post;
use Artesaos\SEOTools\Facades\JsonLdMulti;
use Artesaos\SEOTools\Facades\OpenGraph;
use Artesaos\SEOTools\Facades\SEOMeta;
use GrahamCampbell\Markdown\Facades\Markdown;
use Illuminate\Http\Request;
use Symfony\Component\DomCrawler\Crawler;
@@ -25,6 +28,46 @@ public function index(Request $request, $slug)
$content = $this->injectTableOfContents($content);
$content = $this->injectFeaturedImage($post, $content);
$post_description = $post->excerpt.' '.$post->title.' by EchoScoop.';
SEOMeta::setTitle($post->title, false);
SEOMeta::setDescription($post_description);
SEOMeta::addMeta('article:published_time', $post->published_at->format('Y-m-d'), 'property');
SEOMeta::addMeta('article:section', $post->category->name, 'property');
SEOMeta::setRobots('INDEX, FOLLOW, MAX-IMAGE-PREVIEW:LARGE, MAX-SNIPPET:-1, MAX-VIDEO-PREVIEW:-1');
OpenGraph::setDescription($post_description);
OpenGraph::setTitle($post->title);
OpenGraph::setUrl(url()->current());
OpenGraph::addProperty('type', 'article');
OpenGraph::addProperty('locale', 'en_US');
OpenGraph::addImage($post->featured_image_cdn);
$jsonld_multi = JsonLdMulti::newJsonLd();
$jsonld_multi->setTitle($post->title)
->setDescription($post_description)
->setType('Article')
->addValue('headline', $post->title)
->addImage($post->featured_image_cdn)
->addValue('author', [
'type' => 'Person',
'name' => $post->author->name,
'url' => config('app.url'),
])
->addValue('publisher', [
'type' => 'Organization',
'name' => 'EchoScoop',
'logo' => [
'type' => 'ImageObject',
'url' => asset('echoscoop-logo-512x512.png'),
],
])
->addValue('datePublished', $post->published_at->format('Y-m-d'))
->addValue('dateCreated', $post->created_at->format('Y-m-d'))
->addValue('dateModified', $post->updated_at->format('Y-m-d'))
->addValue('description', $post_description)
->addValue('articleBody', trim(preg_replace('/\s\s+/', ' ', strip_tags($content))));
return view('front.single_post', compact('post', 'content'));
}
@@ -32,22 +75,23 @@ private function injectBootstrapClasses($content)
{
$crawler = new Crawler($content);
// Handle Headings
$crawler->filter('h1')->each(function (Crawler $node) {
$node->getNode(0)->setAttribute('class', trim($node->attr('class').' display-6 fw-bolder mt-3 mb-4'));
});
$crawler->filter('h2')->each(function (Crawler $node) {
$node->getNode(0)->setAttribute('class', trim($node->attr('class').'h4 mb-3'));
$node->getNode(0)->setAttribute('class', trim($node->attr('class').' h4 mb-3'));
});
$crawler->filter('h3')->each(function (Crawler $node) {
$node->getNode(0)->setAttribute('class', trim($node->attr('class').'h6 mb-2'));
$node->getNode(0)->setAttribute('class', trim($node->attr('class').' h6 mb-3'));
});
// Handle Paragraphs
$crawler->filter('p')->each(function (Crawler $pNode) {
$precedingHeaders = $pNode->previousAll()->filter('h2');
// If there are no preceding <h2> tags, just process the <p>
if (! $precedingHeaders->count()) {
$existingClasses = $pNode->attr('class');
$newClasses = trim($existingClasses.' mt-2 mb-4');
@@ -70,12 +114,18 @@ private function injectBootstrapClasses($content)
}
});
// Handle Lists
$crawler->filter('ul')->each(function (Crawler $node) {
$node->getNode(0)->setAttribute('class', trim($node->attr('class').'py-2'));
$node->getNode(0)->setAttribute('class', trim($node->attr('class').' py-2'));
});
$crawler->filter('ol')->each(function (Crawler $node) {
$node->getNode(0)->setAttribute('class', trim($node->attr('class').'py-2'));
$node->getNode(0)->setAttribute('class', trim($node->attr('class').' py-2'));
});
// Handle Tables
$crawler->filter('table')->each(function (Crawler $node) {
$node->getNode(0)->setAttribute('class', trim($node->attr('class').' table table-bordered mb-4'));
});
// Convert the modified DOM back to string
@@ -117,16 +167,16 @@ private function injectTableOfContents($html)
private function injectFeaturedImage($post, $content)
{
if (! is_empty($post->featured_image)) {
if (! empty($post->featured_image)) {
$featured_image_alt = strtolower($post->short_title);
$featured_image_alt_caps = strtoupper($post->short_title);
$featured_image = "<div class=\"w-100 d-flex justify-content-center\"><figure class=\"text-center\"><img decoding=\"async\" class=\"img-fluid rounded mb-2 shadow\" src=\"{$post->featured_image_cdn}\" alt=\"{$featured_image_alt}\"><figcaption class=\"text-secondary small\">{$featured_image_alt_caps}</figcaption></figure></div>";
// Update the HTML structure
$featured_image = "<div class=\"w-100 d-flex justify-content-center\"><figure class=\"text-center\"><img decoding=\"async\" class=\"img-fluid rounded mb-2 shadow lqip-blur\" src=\"{$post->featured_image_lqip_cdn}\" data-src=\"{$post->featured_image_cdn}\" alt=\"{$featured_image_alt}\"><figcaption class=\"text-secondary small\">{$featured_image_alt_caps}</figcaption></figure></div>";
$content = preg_replace('/(<\/h1>)/', '$1'.$featured_image, $content, 1);
}
return $content;
}
}

View File

@@ -208,7 +208,9 @@ public static function handle($post)
$yPosition += $fontSize + $padding;
}
$filename = $post->slug.'-'.epoch_now_timestamp().'.jpg';
$epoch_now_timestamp = epoch_now_timestamp();
$filename = $post->slug.'-'.$epoch_now_timestamp.'.jpg';
$ok = OSSUploader::uploadFile('r2', 'post_images/', $filename, (string) $canvas->stream('jpeg'));
@@ -216,18 +218,20 @@ public static function handle($post)
// Clone the main image for LQIP version
$lqipImage = clone $canvas;
// Create the LQIP version of the image
$lqipImage->fit(10, 10, function ($constraint) {
$constraint->aspectRatio();
});
// Apply blur
$lqipImage->blur(5); // Adjust to the desired blur level
$lqipImage->encode('jpg', 5);
// Pixelate the image
$lqipImage->pixelate(30); // Adjust this value based on the desired pixelation level
// Encode the image to a low-quality JPG
$lqipImage->encode('webp', 5);
// LQIP filename
$lqip_filename = $post->slug.'-'.epoch_now_timestamp().'_lqip.jpg';
$lqip_filename = $post->slug.'-'.$epoch_now_timestamp.'_lqip.webp';
// Upload the LQIP version using OSSUploader
$lqip_ok = OSSUploader::uploadFile('r2', 'post_images/', $lqip_filename, (string) $lqipImage->stream('jpeg'));
$lqip_ok = OSSUploader::uploadFile('r2', 'post_images/', $lqip_filename, (string) $lqipImage->stream('webp'));
if ($ok && $lqip_ok) {

View File

@@ -69,9 +69,9 @@ public function getFeaturedImageLqipCdnAttribute()
$extension = pathinfo($this->featured_image, PATHINFO_EXTENSION);
// Append "_lqip" before the extension to create the LQIP image URL
$lqipFeaturedImage = str_replace(".{$extension}", "_lqip.{$extension}", $this->featured_image);
$lqipFeaturedImage = str_replace(".{$extension}", '_lqip.webp', $this->featured_image);
return 'https://'.Storage::disk('r2')->url($lqipFeaturedImage);
return 'https://'.Storage::disk('r2')->url($lqipFeaturedImage).'?a=bc';
}

View File

@@ -14,7 +14,7 @@
'description' => 'Distilling world news into bite-sized scoops.', // set false to total remove
'separator' => ' - ',
'keywords' => [],
'canonical' => false, // Set to null or 'full' to use Url::full(), set to 'current' to use Url::current(), set false to total remove
'canonical' => 'current', // Set to null or 'full' to use Url::full(), set to 'current' to use Url::current(), set false to total remove
'robots' => false, // Set to 'all', 'none' or any combination of index/noindex and follow/nofollow
],
/*

View File

@@ -0,0 +1 @@
import{_ as i}from"./vue-8b92bff6.js";const n={name:"LqipLoader",mounted(){this.initLqipLoading()},methods:{initLqipLoading(){const e=document.getElementsByTagName("img");for(let t=0;t<e.length;t++)e[t].getAttribute("data-src")&&(e[t].onload=function(){this.classList.remove("lqip-blur")},e[t].setAttribute("src",e[t].getAttribute("data-src")))}}};function o(e,t,r,s,a,c){return null}const m=i(n,[["render",o]]);export{m as default};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@@ -58,20 +58,31 @@
"src": "resources/js/app-auth.js"
},
"resources/js/app-front.js": {
"file": "assets/app-front-83914abf.js",
"dynamicImports": [
"resources/js/vue/front/LqipLoader.vue"
],
"file": "assets/app-front-c970ee86.js",
"imports": [
"_vue-8b92bff6.js"
],
"isEntry": true,
"src": "resources/js/app-front.js"
},
"resources/js/vue/front/LqipLoader.vue": {
"file": "assets/LqipLoader-2067e882.js",
"imports": [
"_vue-8b92bff6.js"
],
"isDynamicEntry": true,
"src": "resources/js/vue/front/LqipLoader.vue"
},
"resources/sass/app-auth.scss": {
"file": "assets/app-auth-d32494d2.css",
"isEntry": true,
"src": "resources/sass/app-auth.scss"
},
"resources/sass/app-front.scss": {
"file": "assets/app-front-48c01c04.css",
"file": "assets/app-front-f3aa4148.css",
"isEntry": true,
"src": "resources/sass/app-front.scss"
},

Binary file not shown.

View File

@@ -19,3 +19,19 @@ .nav-scroller .nav {
.hover-text-decoration-underline:hover {
text-decoration: underline !important;
}
/* lqip start */
img {
transition: opacity 0.5s ease;
}
img.lqip-blur {
opacity: 1; /* or any value less than 1 to make it slightly transparent */
}
img.lqip-blur {
filter: blur(20px);
-webkit-filter: blur(20px);
}
/* lqip end */

View File

@@ -0,0 +1,25 @@
<template>
<!-- Empty template -->
</template>
<script>
export default {
name: "LqipLoader",
mounted() {
this.initLqipLoading();
},
methods: {
initLqipLoading() {
const imgDefer = document.getElementsByTagName("img");
for (let i = 0; i < imgDefer.length; i++) {
if (imgDefer[i].getAttribute("data-src")) {
imgDefer[i].onload = function () {
this.classList.remove("lqip-blur");
};
imgDefer[i].setAttribute("src", imgDefer[i].getAttribute("data-src"));
}
}
},
},
};
</script>

View File

@@ -11,6 +11,7 @@
@yield('content')
</main>
@include('front.layouts.partials.footer')
<lqip-loader></lqip-loader>
</div>
</body>

View File

@@ -1,6 +1,5 @@
@if ($enabled)
<link rel="preconnect" href="https://www.googletagmanager.com"/>
<link rel="preconnect" href="https://www.googletagmanager.com" />
<script>
window.dataLayer = window.dataLayer || [];
@unless (empty($dataLayer->toArray()))