mirror of
https://github.com/prurigro/hypothetical.git
synced 2024-11-23 00:14:10 -05:00
Handle metadata in the database for a few reasons: the method is the same between traditional and vue, the user has control over the values, we can create dynamic titles a bit more easily, and with vue the values are populated before the SPA loads so search engines can pick it up more easily
This commit is contained in:
parent
338a2517b2
commit
2f5ed84e2b
19 changed files with 204 additions and 54 deletions
|
@ -1,7 +1,6 @@
|
|||
DEFAULT_LANGUAGE=en
|
||||
|
||||
APP_NAME='Hypothetical'
|
||||
APP_DESC='A website template'
|
||||
APP_ENV=local
|
||||
APP_KEY=
|
||||
APP_DEBUG=true
|
||||
|
|
|
@ -10,6 +10,12 @@ class Dashboard
|
|||
* @return array
|
||||
*/
|
||||
public static $menu = [
|
||||
[
|
||||
'title' => 'Metadata',
|
||||
'type' => 'edit',
|
||||
'model' => 'meta'
|
||||
],
|
||||
|
||||
[
|
||||
'title' => 'Blog',
|
||||
'type' => 'edit',
|
||||
|
|
|
@ -6,10 +6,16 @@ use Newsletter;
|
|||
use App\Models\Blog;
|
||||
use App\Models\Contact;
|
||||
use App\Models\Subscriptions;
|
||||
use App\Models\Meta;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ApiController extends Controller {
|
||||
|
||||
public function getMeta($path = null)
|
||||
{
|
||||
return Meta::getData($path);
|
||||
}
|
||||
|
||||
public function getBlogEntries()
|
||||
{
|
||||
return Blog::getBlogEntries();
|
||||
|
|
54
app/Models/Meta.php
Normal file
54
app/Models/Meta.php
Normal file
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
class Meta extends DashboardModel
|
||||
{
|
||||
protected $table = 'meta';
|
||||
|
||||
public static $create = false;
|
||||
|
||||
public static $items_per_page = 0;
|
||||
|
||||
public static $dashboard_help_text = 'The path must start with a forward slash (eg: "/" or "/pagename")';
|
||||
|
||||
public static $dashboard_type = 'edit';
|
||||
|
||||
public static $dashboard_display = [ 'title', 'path' ];
|
||||
|
||||
public static $dashboard_columns = [
|
||||
[ 'name' => 'path', 'required' => true, 'unique' => true, 'type' => 'string' ],
|
||||
[ 'name' => 'title', 'required' => true, 'unique' => false, 'type' => 'string' ],
|
||||
[ 'name' => 'description', 'required' => true, 'unique' => false, 'type' => 'text' ],
|
||||
[ 'name' => 'keywords', 'required' => true, 'unique' => false, 'type' => 'string' ]
|
||||
];
|
||||
|
||||
public static function getData($path)
|
||||
{
|
||||
if (!preg_match('/^\//', $path)) {
|
||||
$path = "/$path";
|
||||
}
|
||||
|
||||
if (preg_match('/^\/(dashboard|login|register)/', $path)) {
|
||||
$page = [
|
||||
'title' => 'Dashboard' . ' | ' . env('APP_NAME'),
|
||||
'description' => '',
|
||||
'keywords' => ''
|
||||
];
|
||||
} else {
|
||||
$page = self::select('title', 'description', 'keywords')->where('path', "$path")->first();
|
||||
|
||||
if ($page == null) {
|
||||
$page = [
|
||||
'title' => 'Page Not Found' . ' | ' . env('APP_NAME'),
|
||||
'description' => 'The requested page cannot be found',
|
||||
'keywords' => ''
|
||||
];
|
||||
} else {
|
||||
$page['title'] = $page['title'] . ' | ' . env('APP_NAME');
|
||||
}
|
||||
}
|
||||
|
||||
return $page;
|
||||
}
|
||||
}
|
31
database/migrations/2024_04_03_164823_create_meta_table.php
Normal file
31
database/migrations/2024_04_03_164823_create_meta_table.php
Normal file
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('meta', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('path')->nullable();
|
||||
$table->text('title')->nullable();
|
||||
$table->text('description')->nullable();
|
||||
$table->text('keywords')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('meta');
|
||||
}
|
||||
};
|
|
@ -13,6 +13,6 @@ class DatabaseSeeder extends Seeder
|
|||
*/
|
||||
public function run(): void
|
||||
{
|
||||
|
||||
$this->call(MetaSeeder::class);
|
||||
}
|
||||
}
|
||||
|
|
53
database/seeders/MetaSeeder.php
Normal file
53
database/seeders/MetaSeeder.php
Normal file
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use DB;
|
||||
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||
use Illuminate\Database\Seeder;
|
||||
use App\Models\Meta;
|
||||
|
||||
class MetaSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
// Delete the table
|
||||
DB::table('meta')->delete();
|
||||
|
||||
// Page metadata
|
||||
$pages = [
|
||||
[
|
||||
'path' => '/',
|
||||
'title' => 'Home',
|
||||
'description' => '',
|
||||
'keywords' => ''
|
||||
],
|
||||
|
||||
[
|
||||
'path' => '/blog',
|
||||
'title' => 'Blog',
|
||||
'description' => '',
|
||||
'keywords' => ''
|
||||
],
|
||||
|
||||
[
|
||||
'path' => '/contact',
|
||||
'title' => 'Contact',
|
||||
'description' => '',
|
||||
'keywords' => ''
|
||||
]
|
||||
];
|
||||
|
||||
foreach ($pages as $page) {
|
||||
Meta::create([
|
||||
'path' => $page['path'],
|
||||
'title' => $page['title'],
|
||||
'description' => $page['description'],
|
||||
'keywords' => $page['keywords']
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -69,9 +69,6 @@
|
|||
|
||||
data() {
|
||||
return {
|
||||
metaTitle: "Contact",
|
||||
metaDescription: "Contact Us",
|
||||
metaKeywords: "contact",
|
||||
submitting: false,
|
||||
errorCount: 0,
|
||||
submitSuccess: false,
|
||||
|
|
|
@ -10,13 +10,6 @@
|
|||
export default {
|
||||
mixins: [
|
||||
BasePageMixin
|
||||
],
|
||||
|
||||
data() {
|
||||
return {
|
||||
metaTitle: "Page Not Found",
|
||||
metaDescription: "The requested page cannot be found"
|
||||
};
|
||||
}
|
||||
]
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -15,12 +15,6 @@
|
|||
|
||||
components: {
|
||||
"subscription-form": SubscriptionFormSection
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
metaKeywords: "home"
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -75,6 +75,7 @@ const store = createStore({
|
|||
appLang: env.appLang,
|
||||
appDefaultLang: env.appDefaultLang,
|
||||
firstLoad: true,
|
||||
firstPage: true,
|
||||
lastPath: "",
|
||||
supportsWebP: null
|
||||
},
|
||||
|
@ -96,6 +97,10 @@ const store = createStore({
|
|||
return state.firstLoad;
|
||||
},
|
||||
|
||||
getFirstPage: state => {
|
||||
return state.firstPage;
|
||||
},
|
||||
|
||||
getLastPath: state => {
|
||||
return state.lastPath;
|
||||
},
|
||||
|
@ -115,6 +120,10 @@ const store = createStore({
|
|||
state.firstLoad = value;
|
||||
},
|
||||
|
||||
setFirstPage(state, value) {
|
||||
state.firstPage = value;
|
||||
},
|
||||
|
||||
setLastPath(state, value) {
|
||||
state.lastPath = value;
|
||||
},
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
export default {
|
||||
data() {
|
||||
return {
|
||||
metaTitle: "",
|
||||
metaDescription: "",
|
||||
metaKeywords: "",
|
||||
|
||||
metaTags: {
|
||||
"title": [ "name", "title" ],
|
||||
"description": [ "name", "description" ],
|
||||
|
@ -21,14 +17,6 @@ export default {
|
|||
},
|
||||
|
||||
computed: {
|
||||
pageTitle() {
|
||||
return this.metaTitle === "" ? env.appName : `${this.metaTitle} | ${env.appName}`;
|
||||
},
|
||||
|
||||
pageDescription() {
|
||||
return this.metaDescription === "" ? env.appDesc : this.metaDescription;
|
||||
},
|
||||
|
||||
fullPath() {
|
||||
return document.location.origin + this.$route.path;
|
||||
}
|
||||
|
@ -43,22 +31,22 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
updateMetaData() {
|
||||
updateMetadata(meta) {
|
||||
let metaContent;
|
||||
|
||||
document.title = this.pageTitle;
|
||||
document.title = meta.title;
|
||||
$("link[rel=canonical]").attr("href", this.fullPath);
|
||||
|
||||
Object.keys(this.metaTags).forEach((name) => {
|
||||
switch (this.metaTags[name][1]) {
|
||||
case "title":
|
||||
metaContent = this.pageTitle;
|
||||
metaContent = meta.title;
|
||||
break;
|
||||
case "description":
|
||||
metaContent = this.pageDescription;
|
||||
metaContent = meta.description;
|
||||
break;
|
||||
case "keywords":
|
||||
metaContent = this.metaKeywords;
|
||||
metaContent = meta.keywords;
|
||||
break;
|
||||
case "url":
|
||||
metaContent = this.fullPath;
|
||||
|
@ -69,10 +57,24 @@ export default {
|
|||
|
||||
this.updateMetaTag(this.metaTags[name][0], name, metaContent);
|
||||
});
|
||||
},
|
||||
|
||||
fetchMetadata() {
|
||||
this.$http.get(`/api/meta${this.$route.path}${env.apiToken}`).then((response) => {
|
||||
this.updateMetadata(response.data);
|
||||
}).catch((error) => {
|
||||
console.log("error fetching metadata");
|
||||
this.updateMetadata({ title: appName, description: "", keywords: "" });
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.updateMetaData();
|
||||
// Don't fetch metadata on the first page load as this is handled by the page render
|
||||
if (this.$store.getters.getFirstPage) {
|
||||
this.$store.commit("setFirstPage", false);
|
||||
} else {
|
||||
this.fetchMetadata();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,3 +1 @@
|
|||
@extends('templates.error', [
|
||||
'title' => 'Page Not Found'
|
||||
])
|
||||
@extends('templates.error')
|
||||
|
|
|
@ -1,8 +1,15 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="{{ Language::getSessionLanguage() }}">
|
||||
@php
|
||||
$page_title = (isset($title) ? $title . ' - ' : '') . env('APP_NAME');
|
||||
// Determine whether the device is mobile
|
||||
$device_mobile = !is_null(Request::header('User-Agent')) && (preg_match('/Mobi/', Request::header('User-Agent')) || preg_match('/iP(hone|ad|od);/', Request::header('User-Agent')));
|
||||
|
||||
// If an overridden title has been set (error pages) then use that, otherwise use the Meta model to populate metadata
|
||||
if (isset($title)) {
|
||||
$meta = [ 'title' => $title . ' - ' . env('APP_NAME'), 'description' => '', 'keywords' => '' ];
|
||||
} else {
|
||||
$meta = App\Models\Meta::getData(Request::path());
|
||||
}
|
||||
@endphp
|
||||
|
||||
<head>
|
||||
|
@ -11,22 +18,23 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
|
||||
<meta name="theme-color" content="#fcfcfc" />
|
||||
|
||||
<title>{{ $page_title }}</title>
|
||||
<title>{{ $meta['title'] }}</title>
|
||||
|
||||
<meta name="title" content="{{ $page_title }}" />
|
||||
<meta name="description" content="{{ env('APP_DESC') }}" />
|
||||
<meta name="dc:title" content="{{ $page_title }}" />
|
||||
<meta name="dc:description" content="{{ env('APP_DESC') }}" />
|
||||
<meta name="title" content="{{ $meta['title'] }}" />
|
||||
<meta name="description" content="{{ $meta['description'] }}" />
|
||||
<meta name="keywords" content="{{ $meta['keywords'] }}" />
|
||||
<meta name="dc:title" content="{{ $meta['title'] }}" />
|
||||
<meta name="dc:description" content="{{ $meta['description'] }}" />
|
||||
|
||||
<meta property="og:type" content="article" />
|
||||
<meta property="og:title" content="{{ $page_title }}" />
|
||||
<meta property="og:description" content="{{ env('APP_DESC') }}" />
|
||||
<meta property="og:title" content="{{ $meta['title'] }}" />
|
||||
<meta property="og:description" content="{{ $meta['description'] }}" />
|
||||
<meta property="og:url" content="{{ Request::url() }}" />
|
||||
<meta property="og:image" content="{{ asset('/img/logo.png') }}" />
|
||||
|
||||
<meta name="twitter:card" content="summary" />
|
||||
<meta name="twitter:title" content="{{ $page_title }}" />
|
||||
<meta name="twitter:description" content="{{ env('APP_DESC') }}" />
|
||||
<meta name="twitter:title" content="{{ $meta['title'] }}" />
|
||||
<meta name="twitter:description" content="{{ $meta['description'] }}" />
|
||||
<meta name="twitter:image" content="{{ asset('/img/logo.png') }}" />
|
||||
|
||||
<link rel="shortcut icon" href="{{ URL::to('/') }}/favicon.ico?version={{ Version::get() }}" />
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
@extends('templates.base', [ 'title' => 'Dashboard' ])
|
||||
@extends('templates.base')
|
||||
|
||||
@php
|
||||
$current_page = preg_match('/\/settings$/', Request::url()) ? 'settings' : preg_replace([ '/https?:\/\/[^\/]*\/dashboard\/[^\/]*\//', '/\/.*/' ], [ '', '' ], Request::url());
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
<script>
|
||||
var env = {
|
||||
appName: "{!! env('APP_NAME') !!}",
|
||||
appDesc: "{!! env('APP_DESC') !!}",
|
||||
appLang: "{{ Language::getSessionLanguage() }}",
|
||||
appDefaultLang: "{{ env('DEFAULT_LANGUAGE') }}",
|
||||
apiToken: "{{ Auth::check() ? '?api_token=' . Auth::user()->api_token : '' }}",
|
||||
|
|
|
@ -10,6 +10,7 @@ use Illuminate\Support\Facades\Route;
|
|||
*/
|
||||
|
||||
Route::get('/blog-entries', 'App\Http\Controllers\ApiController@getBlogEntries');
|
||||
Route::get('/meta/{path?}', 'App\Http\Controllers\ApiController@getMeta');
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
@extends('templates.public', [ 'title' => 'Blog' ])
|
||||
@extends('templates.public')
|
||||
|
||||
@section('content')
|
||||
<div class="blog-page-component">
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
@extends('templates.public', [ 'title' => 'Contact' ])
|
||||
@extends('templates.public')
|
||||
|
||||
@section('content')
|
||||
<div class="contact-page-component">
|
||||
|
|
Loading…
Reference in a new issue