feat: intégration plugin Kirby SEO
All checks were successful
Deploy / Deploy to Production (push) Successful in 22s

- Ajout de tobimori/kirby-seo via Composer
- snippet('seo/head') dans header.php (remplace les meta manuels)
- snippet('seo/schemas') dans footer.php pour JSON-LD
- Onglet SEO ajouté dans site.yml et tous les blueprints de pages
- Configuration SEO dans config.php (sitemap, robots, canonicalBase TODO)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
isUnknown 2026-03-25 12:59:18 +01:00
parent baab2fb3a1
commit 58c31ea391
133 changed files with 9201 additions and 253 deletions

View file

@ -0,0 +1,34 @@
type: group
fields:
_metaHeadline:
label: seo.page.meta.headline
type: headline
numbered: false
metaTitle:
label: seo.fields.titleOverwrite.label
type: seo-writer
ai: title
placeholder: "{{ page.title }}"
metaTemplate:
extends: seo/fields/title-template
label: seo.fields.metaTitleTemplate.label
help: seo.fields.metaTitleTemplate.help
width: 2/3
placeholder: "{{ page.metadata.metaTemplate }}"
useTitleTemplate:
label: seo.fields.useTitleTemplate.label
type: toggle
help: seo.fields.useTitleTemplate.help
width: 1/3
default: true
text:
- "{{ t('seo.fields.useTitleTemplate.no') }}"
- "{{ t('seo.fields.useTitleTemplate.yes') }}"
metaDescription:
label: seo.fields.metaDescription.label
type: seo-writer
ai: description
help: seo.fields.metaDescription.help
placeholder: "{{ page.metadata.metaDescription }}"
_seoLine1:
type: line

View file

@ -0,0 +1,42 @@
type: group
fields:
_ogHeadline:
label: seo.page.og.headline
type: headline
numbered: false
help: seo.site.og.headline.help
ogTemplate:
extends: seo/fields/title-template
label: seo.fields.ogTitleTemplate.label
width: 2/3
help: seo.fields.metaTitleTemplate.help
placeholder: "{{ page.metadata.ogTemplate }}"
useOgTemplate:
label: seo.fields.useTitleTemplate.label
type: toggle
help: seo.fields.useTitleTemplate.help
width: 1/3
default: true
text:
- "{{ t('seo.fields.useTitleTemplate.no') }}"
- "{{ t('seo.fields.useTitleTemplate.yes') }}"
ogDescription:
label: seo.fields.ogDescription.label
type: seo-writer
ai: og-description
placeholder: "{{ page.metadata.ogDescription }}"
ogImage:
label: seo.fields.ogImage.label
extends: seo/fields/og-image
empty: seo.fields.ogImage.empty
cropOgImage:
label: seo.fields.cropOgImage.label
type: select
width: 1/1
placeholder: "{{ t('seo.common.default') }} {{ site.cropOgImage.toBool ? t('seo.common.yes') : t('seo.common.no') }}"
options:
"true": "{{ t('seo.common.yes') }}"
"false": "{{ t('seo.common.no') }}"
help: seo.fields.cropOgImage.help
_seoLine2:
type: line

View file

@ -0,0 +1,31 @@
<?php
use Kirby\Cms\App;
use Kirby\Toolkit\Str;
return function (App $kirby) {
$blueprint = [
'type' => 'files',
'multiple' => false,
'uploads' => [],
'query' => 'model.images'
];
if ($parent = option('tobimori.seo.files.parent')) {
$blueprint['uploads'] = [
'parent' => $parent
];
$blueprint['query'] = "{$parent}.images";
}
if ($template = option('tobimori.seo.files.template')) {
$blueprint['uploads'] = [
...$blueprint['uploads'],
'template' => $template
];
$blueprint['query'] = "{$blueprint['query']}.filterBy('template', '{$template}')";
}
return $blueprint;
};

View file

@ -0,0 +1,56 @@
<?php
use Kirby\Cms\App;
use Kirby\Toolkit\A;
use Kirby\Toolkit\Str;
use tobimori\Seo\Meta;
use tobimori\Seo\Seo;
return function (App $kirby) {
if (!Seo::option('robots.active') || !Seo::option('robots.pageSettings')) {
return [
'type' => 'hidden'
];
}
$fields = [
'_robotsHeadline' => [
'label' => 'seo.fields.robots.label',
'type' => 'headline',
'numbered' => false,
]
];
$page = Meta::currentPage();
foreach ($kirby->option('tobimori.seo.robots.types') as $robots) {
$upper = Str::ucfirst($robots);
$fields["robots{$upper}"] = [
'label' => "seo.fields.robots.{$robots}.label",
'type' => 'toggles',
'help' => "seo.fields.robots.{$robots}.help",
'width' => '1/2',
'default' => 'default',
'reset' => false,
'options' => [
'default' => $page ?
A::join([
t('seo.common.default'),
$page->metadata()->get("robots{$upper}", ['fields'])->toBool() ? t('seo.common.yes') : t('seo.common.no')
], ' ')
: t('seo.common.default'),
'true' => t('seo.common.yes'),
'false' => t('seo.common.no'),
]
];
}
$fields['_seoLine3'] = [
'type' => 'line'
];
return [
'type' => 'group',
'fields' => $fields,
];
};

View file

@ -0,0 +1,49 @@
<?php
use Kirby\Cms\App;
return function (App $kirby) {
if (!$kirby->option('tobimori.seo.robots.active') || !$kirby->option('tobimori.seo.robots.pageSettings')) {
return [
'type' => 'hidden'
];
}
$fields = [
'_robotsHeadline' => [
'label' => 'seo.fields.robots.label',
'type' => 'headline',
'numbered' => false,
]
];
foreach ($kirby->option('tobimori.seo.robots.types') as $robots) {
$index = $kirby->option('tobimori.seo.robots.index');
if (is_callable($index)) {
$index = $index();
}
$fields["robots{$robots}"] = [
'label' => "seo.fields.robots.{$robots}.label",
'type' => 'toggles',
'help' => "seo.fields.robots.{$robots}.help",
'width' => '1/2',
'default' => 'default',
'reset' => false,
'options' => [
'default' => t('seo.common.default') . ' ' . ($index ? t('seo.common.yes') : t('seo.common.no')),
'true' => t('seo.common.yes'),
'false' => t('seo.common.no'),
]
];
}
$fields['_seoLine3'] = [
'type' => 'line'
];
return [
'type' => 'group',
'fields' => $fields,
];
};

View file

@ -0,0 +1,30 @@
<?php
/**
* Social Media Accounts field
* Allows social media account list to be filled by config options
*/
use tobimori\Seo\Seo;
return function () {
$fields = [];
foreach (Seo::option('socialMedia') as $key => $value) {
if ($value) {
$fields[$key] = [
'label' => ucfirst($key),
'type' => 'url',
'icon' => strtolower($key),
'placeholder' => $value
];
}
}
return [
'label' => 'seo.fields.socialMediaAccounts.label',
'type' => 'object',
'help' => 'seo.fields.socialMediaAccounts.help',
'fields' => $fields
];
};

View file

@ -0,0 +1,6 @@
type: seo-writer
nodes:
- seoTemplateTitle
- seoTemplateSiteTitle
toolbar:
inline: false