SEO : add tombi mori plugin

This commit is contained in:
isUnknown 2025-05-13 09:03:14 +02:00
parent df2843123f
commit 8f9e75126e
64 changed files with 3719 additions and 44 deletions

View file

@ -0,0 +1,100 @@
<template>
<div>
<div class="k-facebook-preview">
<div class="k-facebook-preview__image" v-if="ogImage">
<img :src="ogImage" class="k-facebook-preview__img" />
</div>
<div class="k-facebook-preview__content">
<span class="k-facebook-preview__url">{{ host }}</span>
<span class="k-facebook-preview__title">{{ ogTitle }}</span>
<p class="k-facebook-preview__description">{{ ogDescription }}</p>
</div>
</div>
<a
class="k-seo-preview__debugger"
href="https://developers.facebook.com/tools/debug/"
aria-label="Facebook Sharing Debugger"
target="_blank"
rel="noopener noreferrer"
>
{{ $t('open-debugger') }}
<k-icon type="open" />
</a>
</div>
</template>
<script>
export default {
props: {
ogTitle: String,
url: String,
ogDescription: String,
ogImage: String
},
computed: {
host() {
return new URL(this.url).host
}
}
}
</script>
<style lang="scss">
.k-facebook-preview {
background: #f0f2f5;
border: 1px solid #ced0d4;
overflow: hidden;
border-radius: var(--rounded);
&__image {
width: 100%;
height: 0;
padding-bottom: 52.355%;
position: relative;
img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
}
&__content {
padding: 0.75rem 1rem;
}
&__title,
&__description,
&__url {
display: -webkit-box;
-webkit-box-orient: vertical;
overflow: hidden;
-webkit-line-clamp: 1;
}
&__url {
color: #65676b;
font-size: 0.75rem;
text-transform: uppercase;
line-height: 1.1;
margin-bottom: 0.25rem;
}
&__title {
font-weight: 600;
line-height: 1.1765;
font-size: 1rem;
color: #050505;
margin: 0.3125rem 0;
}
&__description {
line-height: 1.3333;
color: #65676b;
font-size: 0.875rem;
}
}
</style>

View file

@ -0,0 +1,115 @@
<template>
<div>
<div class="k-google-search-preview">
<span class="k-google-search-preview__url">
<span>{{ origin }}</span>
<span
v-for="(breadcrumb, index) in breadcrumbs"
:key="index"
class="k-google-search-preview__url__breadcrumb"
>
{{ breadcrumb }}
</span>
</span>
<h2 class="k-google-search-preview__headline">{{ title }}</h2>
<p class="k-google-search-preview__paragraph">
{{ description }}
</p>
</div>
<a
class="k-seo-preview__debugger"
href="https://search.google.com/search-console"
aria-label="Google Search Console"
target="_blank"
rel="noopener noreferrer"
>
{{ $t('open-search-console') }}
<k-icon type="open" />
</a>
</div>
</template>
<script>
export default {
props: {
title: String,
url: String,
description: String
},
computed: {
origin() {
return new URL(this.url).origin
},
breadcrumbs() {
return this.url.split('/').slice(3)
}
}
}
</script>
<style lang="scss">
.k-google-search-preview {
padding: 1em;
background: #fff;
border: 1px solid #ccc;
letter-spacing: -0.005em;
border-radius: var(--rounded);
&__headline,
&__paragraph {
display: -webkit-box;
-webkit-box-orient: vertical;
overflow: hidden;
}
&__headline {
margin-top: 0;
margin-bottom: 0.25em;
font-size: 1.25em;
font-weight: normal;
color: #1a0dab;
-webkit-line-clamp: 1;
&:hover {
text-decoration: underline;
}
}
&__url {
display: inline-block;
margin-bottom: 0.5em;
font-size: 0.875em;
line-height: 1.3;
color: #202124;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
max-width: 100%;
> * {
margin-right: 0.25em;
}
&__breadcrumb {
color: #5f6368;
display: inline-block;
&::before {
content: ' ';
}
}
.k-icon {
margin-left: 0.1em;
}
}
&__paragraph {
margin: 0;
font-size: 0.875em;
line-height: 1.3em;
color: #3c4043;
-webkit-line-clamp: 3;
}
}
</style>

View file

@ -0,0 +1,93 @@
<template>
<div class="k-slack-preview">
<div class="k-slack-preview__content">
<div class="k-slack-preview__site-name">{{ ogSiteName || origin }}</div>
<span class="k-slack-preview__title">{{ ogTitle }}</span>
<p class="k-slack-preview__description">{{ ogDescription }}</p>
</div>
<div class="k-slack-preview__image" v-if="ogImage">
<img :src="ogImage" />
</div>
</div>
</template>
<script>
export default {
props: {
ogTitle: String,
ogSiteName: String,
ogDescription: String,
ogImage: String
},
computed: {
origin() {
return new URL(this.url).origin
}
}
}
</script>
<style lang="scss">
.k-slack-preview {
max-width: 32.5rem;
position: relative;
padding-left: 1rem;
line-height: 1.46666667;
font-size: 0.9375rem;
&::before {
position: absolute;
content: '';
top: 0;
left: 0;
bottom: 0;
width: 0.25rem;
border-radius: 0.5rem;
background: #ddd;
}
&__site-name {
display: flex;
align-items: center;
color: #717274;
}
&__title {
font-weight: 700;
display: block;
color: #0576b9;
cursor: pointer;
&:hover {
text-decoration: underline;
}
}
&__description {
color: #2c2d30;
}
&__image {
border-radius: 0.5rem;
max-width: 22.5rem;
overflow: hidden;
position: relative;
margin-top: 0.5rem;
&::before {
border-radius: 0.5rem;
content: '';
inset: 0;
z-index: 2;
position: absolute;
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1);
}
img {
width: 100%;
height: 100%;
display: block;
}
}
}
</style>

View file

@ -0,0 +1,150 @@
https://github.com/getkirby/kirby/blob/main/panel/src/components/Views/Pages/PageView.vue
<template>
<k-panel-inside
:data-has-tabs="tabs.length > 1"
:data-id="model.id"
:data-locked="isLocked"
:data-template="blueprint"
class="k-page-view"
>
<template #topbar>
<k-prev-next v-if="model.id" :prev="prev" :next="next" />
</template>
<k-header
:editable="permissions.changeTitle && !isLocked"
class="k-page-view-header"
@edit="$dialog(id + '/changeTitle')"
>
{{ model.title }}
<template #buttons>
<k-button-group>
<k-button
v-if="permissions.preview && model.previewUrl"
:link="model.previewUrl"
:title="$t('open')"
icon="open"
target="_blank"
variant="filled"
size="sm"
class="k-page-view-preview"
/>
<k-button
:disabled="isLocked === true"
:dropdown="true"
:title="$t('settings')"
icon="cog"
variant="filled"
size="sm"
class="k-page-view-options"
@click="$refs.settings.toggle()"
/>
<k-dropdown-content ref="settings" :options="$dropdown(id)" align-x="end" />
<k-languages-dropdown />
<k-button
v-if="status"
v-bind="statusBtn"
class="k-page-view-status"
variant="filled"
@click="$dialog(id + '/changeStatus')"
/>
<k-button
class="k-page-view-status k-page-view-robots"
v-if="robots && robots.active"
v-bind="robotsBtn"
@click="openSeoTab"
/>
</k-button-group>
<k-form-buttons :lock="lock" />
</template>
</k-header>
<k-model-tabs :tab="tab.name" :tabs="tabs" />
<k-sections
:blueprint="blueprint"
:empty="$t('page.blueprint', { blueprint: $esc(blueprint) })"
:lock="lock"
:parent="id"
:tab="tab"
/>
</k-panel-inside>
</template>
<script>
export default {
extends: 'k-page-view',
data() {
return {
dirty: false,
robots: {
active: false,
state: []
}
}
},
async mounted() {
await this.handleLoad()
},
methods: {
openSeoTab() {
panel.view.open(panel.view.path + '?tab=seo')
},
async handleLoad(changes) {
if (!panel.view.props.tabs.some((tab) => tab.name === 'seo')) return
const page = this.model.id.replaceAll('/', '+')
this.robots = await panel.api.post(`/k-seo/${page}/robots`, changes ?? this.changes)
}
},
computed: {
changes() {
return this.$store.getters['content/changes']() // TODO: new panel API for changes?
},
robotsBtn() {
const btn = {
responsive: true,
size: 'sm',
icon: 'robots',
theme: 'positive',
text: this.$t('indicator-index'),
variant: 'filled'
}
if (this.robots.state.includes('no')) {
btn.text = this.$t('indicator-any')
btn.theme = 'notice'
btn.icon = 'robots-off'
}
if (this.robots.state.includes('noindex')) {
btn.text = this.$t('indicator-noindex')
btn.theme = 'negative'
}
return btn
}
},
watch: {
changes(changes) {
if (Object.keys(changes).some((key) => key.includes('robots')) || this.dirty) {
this.dirty = false
this.handleLoad(changes)
if (changes) this.dirty = true
}
}
}
}
</script>
<style lang="scss">
.k-page-view-robots {
--color-green-boost: -15%;
}
</style>