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\Http\Controllers\Controller;
use App\Models\Category; use App\Models\Category;
use App\Models\Post; use App\Models\Post;
use Artesaos\SEOTools\Facades\SEOTools;
use Illuminate\Http\Request; use Illuminate\Http\Request;
class FrontListController extends Controller class FrontListController extends Controller
{ {
public function index(Request $request) 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(); $posts = Post::where('status', 'publish')->orderBy('published_at', 'desc')->simplePaginate(10) ?? collect();
return view('front.post_list', compact('posts')); 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(); $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')); return view('front.post_list', compact('category', 'posts'));
} }
} }

View File

@@ -4,6 +4,9 @@
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Post; 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 GrahamCampbell\Markdown\Facades\Markdown;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Symfony\Component\DomCrawler\Crawler; use Symfony\Component\DomCrawler\Crawler;
@@ -25,6 +28,46 @@ public function index(Request $request, $slug)
$content = $this->injectTableOfContents($content); $content = $this->injectTableOfContents($content);
$content = $this->injectFeaturedImage($post, $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')); return view('front.single_post', compact('post', 'content'));
} }
@@ -32,6 +75,7 @@ private function injectBootstrapClasses($content)
{ {
$crawler = new Crawler($content); $crawler = new Crawler($content);
// Handle Headings
$crawler->filter('h1')->each(function (Crawler $node) { $crawler->filter('h1')->each(function (Crawler $node) {
$node->getNode(0)->setAttribute('class', trim($node->attr('class').' display-6 fw-bolder mt-3 mb-4')); $node->getNode(0)->setAttribute('class', trim($node->attr('class').' display-6 fw-bolder mt-3 mb-4'));
}); });
@@ -41,13 +85,13 @@ private function injectBootstrapClasses($content)
}); });
$crawler->filter('h3')->each(function (Crawler $node) { $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) { $crawler->filter('p')->each(function (Crawler $pNode) {
$precedingHeaders = $pNode->previousAll()->filter('h2'); $precedingHeaders = $pNode->previousAll()->filter('h2');
// If there are no preceding <h2> tags, just process the <p>
if (! $precedingHeaders->count()) { if (! $precedingHeaders->count()) {
$existingClasses = $pNode->attr('class'); $existingClasses = $pNode->attr('class');
$newClasses = trim($existingClasses.' mt-2 mb-4'); $newClasses = trim($existingClasses.' mt-2 mb-4');
@@ -70,6 +114,7 @@ private function injectBootstrapClasses($content)
} }
}); });
// Handle Lists
$crawler->filter('ul')->each(function (Crawler $node) { $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'));
}); });
@@ -78,6 +123,11 @@ private function injectBootstrapClasses($content)
$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 // Convert the modified DOM back to string
$modifiedContent = ''; $modifiedContent = '';
foreach ($crawler as $domElement) { foreach ($crawler as $domElement) {
@@ -117,16 +167,16 @@ private function injectTableOfContents($html)
private function injectFeaturedImage($post, $content) 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 = strtolower($post->short_title);
$featured_image_alt_caps = strtoupper($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); $content = preg_replace('/(<\/h1>)/', '$1'.$featured_image, $content, 1);
} }
return $content; return $content;
} }
} }

View File

@@ -208,7 +208,9 @@ public static function handle($post)
$yPosition += $fontSize + $padding; $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')); $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 // Clone the main image for LQIP version
$lqipImage = clone $canvas; $lqipImage = clone $canvas;
// Create the LQIP version of the image // Apply blur
$lqipImage->fit(10, 10, function ($constraint) { $lqipImage->blur(5); // Adjust to the desired blur level
$constraint->aspectRatio();
});
$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
$lqip_filename = $post->slug.'-'.epoch_now_timestamp().'_lqip.jpg'; $lqip_filename = $post->slug.'-'.$epoch_now_timestamp.'_lqip.webp';
// Upload the LQIP version using OSSUploader // 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) { if ($ok && $lqip_ok) {

View File

@@ -69,9 +69,9 @@ public function getFeaturedImageLqipCdnAttribute()
$extension = pathinfo($this->featured_image, PATHINFO_EXTENSION); $extension = pathinfo($this->featured_image, PATHINFO_EXTENSION);
// Append "_lqip" before the extension to create the LQIP image URL // 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 'description' => 'Distilling world news into bite-sized scoops.', // set false to total remove
'separator' => ' - ', 'separator' => ' - ',
'keywords' => [], '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 '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" "src": "resources/js/app-auth.js"
}, },
"resources/js/app-front.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": [ "imports": [
"_vue-8b92bff6.js" "_vue-8b92bff6.js"
], ],
"isEntry": true, "isEntry": true,
"src": "resources/js/app-front.js" "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": { "resources/sass/app-auth.scss": {
"file": "assets/app-auth-d32494d2.css", "file": "assets/app-auth-d32494d2.css",
"isEntry": true, "isEntry": true,
"src": "resources/sass/app-auth.scss" "src": "resources/sass/app-auth.scss"
}, },
"resources/sass/app-front.scss": { "resources/sass/app-front.scss": {
"file": "assets/app-front-48c01c04.css", "file": "assets/app-front-f3aa4148.css",
"isEntry": true, "isEntry": true,
"src": "resources/sass/app-front.scss" "src": "resources/sass/app-front.scss"
}, },

Binary file not shown.

View File

@@ -19,3 +19,19 @@ .nav-scroller .nav {
.hover-text-decoration-underline:hover { .hover-text-decoration-underline:hover {
text-decoration: underline !important; 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') @yield('content')
</main> </main>
@include('front.layouts.partials.footer') @include('front.layouts.partials.footer')
<lqip-loader></lqip-loader>
</div> </div>
</body> </body>

View File

@@ -1,4 +1,3 @@
@if ($enabled) @if ($enabled)
<link rel="preconnect" href="https://www.googletagmanager.com" /> <link rel="preconnect" href="https://www.googletagmanager.com" />
<script> <script>