forked from potsda.mn/mobilizon
Fix title & meta
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
parent
d2e74695f6
commit
040faf4451
|
@ -34,6 +34,7 @@
|
||||||
"vue-apollo": "^3.0.0-rc.6",
|
"vue-apollo": "^3.0.0-rc.6",
|
||||||
"vue-class-component": "^7.0.2",
|
"vue-class-component": "^7.0.2",
|
||||||
"vue-i18n": "^8.14.0",
|
"vue-i18n": "^8.14.0",
|
||||||
|
"vue-meta": "^2.3.1",
|
||||||
"vue-property-decorator": "^8.1.0",
|
"vue-property-decorator": "^8.1.0",
|
||||||
"vue-router": "^3.0.6",
|
"vue-router": "^3.0.6",
|
||||||
"vue2-leaflet": "^2.0.3"
|
"vue2-leaflet": "^2.0.3"
|
||||||
|
|
|
@ -10,12 +10,14 @@ import { apolloProvider } from './vue-apollo';
|
||||||
import { NotifierPlugin } from '@/plugins/notifier';
|
import { NotifierPlugin } from '@/plugins/notifier';
|
||||||
import filters from '@/filters';
|
import filters from '@/filters';
|
||||||
import messages from '@/i18n/index';
|
import messages from '@/i18n/index';
|
||||||
|
import VueMeta from 'vue-meta';
|
||||||
|
|
||||||
Vue.config.productionTip = false;
|
Vue.config.productionTip = false;
|
||||||
|
|
||||||
Vue.use(Buefy);
|
Vue.use(Buefy);
|
||||||
Vue.use(NotifierPlugin);
|
Vue.use(NotifierPlugin);
|
||||||
Vue.use(filters);
|
Vue.use(filters);
|
||||||
|
Vue.use(VueMeta);
|
||||||
|
|
||||||
const language = (window.navigator as any).userLanguage || window.navigator.language;
|
const language = (window.navigator as any).userLanguage || window.navigator.language;
|
||||||
|
|
||||||
|
|
|
@ -265,6 +265,14 @@ import { RouteName } from '@/router';
|
||||||
query: TAGS,
|
query: TAGS,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
metaInfo() {
|
||||||
|
return {
|
||||||
|
// if no subcomponents specify a metaInfo.title, this title will be used
|
||||||
|
title: (this.$props.isUpdate ? this.$t('Event edition') : this.$t('Event creation')) as string,
|
||||||
|
// all titles will be injected into this template
|
||||||
|
titleTemplate: '%s | Mobilizon',
|
||||||
|
};
|
||||||
|
},
|
||||||
})
|
})
|
||||||
export default class EditEvent extends Vue {
|
export default class EditEvent extends Vue {
|
||||||
@Prop({ type: Boolean, default: false }) isUpdate!: boolean;
|
@Prop({ type: Boolean, default: false }) isUpdate!: boolean;
|
||||||
|
|
|
@ -268,6 +268,15 @@ import { RouteName } from '@/router';
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
metaInfo() {
|
||||||
|
return {
|
||||||
|
// if no subcomponents specify a metaInfo.title, this title will be used
|
||||||
|
// @ts-ignore
|
||||||
|
title: this.eventTitle,
|
||||||
|
// all titles will be injected into this template
|
||||||
|
titleTemplate: '%s | Mobilizon',
|
||||||
|
};
|
||||||
|
},
|
||||||
})
|
})
|
||||||
export default class Event extends EventMixin {
|
export default class Event extends EventMixin {
|
||||||
@Prop({ type: String, required: true }) uuid!: string;
|
@Prop({ type: String, required: true }) uuid!: string;
|
||||||
|
@ -282,6 +291,11 @@ export default class Event extends EventMixin {
|
||||||
EventVisibility = EventVisibility;
|
EventVisibility = EventVisibility;
|
||||||
RouteName = RouteName;
|
RouteName = RouteName;
|
||||||
|
|
||||||
|
get eventTitle() {
|
||||||
|
if (!this.event) return undefined;
|
||||||
|
return this.event.title;
|
||||||
|
}
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
this.identity = this.currentActor;
|
this.identity = this.currentActor;
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,14 @@ import { RouteName } from '@/router';
|
||||||
query: FETCH_EVENTS,
|
query: FETCH_EVENTS,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
metaInfo() {
|
||||||
|
return {
|
||||||
|
// if no subcomponents specify a metaInfo.title, this title will be used
|
||||||
|
title: this.$t('Explore') as string,
|
||||||
|
// all titles will be injected into this template
|
||||||
|
titleTemplate: '%s | Mobilizon',
|
||||||
|
};
|
||||||
|
},
|
||||||
})
|
})
|
||||||
export default class Explore extends Vue {
|
export default class Explore extends Vue {
|
||||||
events: IEvent[] = [];
|
events: IEvent[] = [];
|
||||||
|
|
|
@ -111,6 +111,14 @@ import EventCard from '@/components/Event/EventCard.vue';
|
||||||
update: data => data.loggedUser.participations.map(participation => new Participant(participation)),
|
update: data => data.loggedUser.participations.map(participation => new Participant(participation)),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
metaInfo() {
|
||||||
|
return {
|
||||||
|
// if no subcomponents specify a metaInfo.title, this title will be used
|
||||||
|
title: this.$t('My events') as string,
|
||||||
|
// all titles will be injected into this template
|
||||||
|
titleTemplate: '%s | Mobilizon',
|
||||||
|
};
|
||||||
|
},
|
||||||
})
|
})
|
||||||
export default class MyEvents extends Vue {
|
export default class MyEvents extends Vue {
|
||||||
futurePage: number = 1;
|
futurePage: number = 1;
|
||||||
|
|
|
@ -150,6 +150,15 @@ import { IConfig } from '@/types/config.model';
|
||||||
EventListCard,
|
EventListCard,
|
||||||
EventCard,
|
EventCard,
|
||||||
},
|
},
|
||||||
|
metaInfo() {
|
||||||
|
return {
|
||||||
|
// if no subcomponents specify a metaInfo.title, this title will be used
|
||||||
|
// @ts-ignore
|
||||||
|
title: this.instanceName,
|
||||||
|
// all titles will be injected into this template
|
||||||
|
titleTemplate: '%s | Mobilizon',
|
||||||
|
};
|
||||||
|
},
|
||||||
})
|
})
|
||||||
export default class Home extends Vue {
|
export default class Home extends Vue {
|
||||||
events: IEvent[] = [];
|
events: IEvent[] = [];
|
||||||
|
@ -169,6 +178,11 @@ export default class Home extends Vue {
|
||||||
// : this.loggedPerson.name;
|
// : this.loggedPerson.name;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
get instanceName() {
|
||||||
|
if (!this.config) return undefined;
|
||||||
|
return this.config.name;
|
||||||
|
}
|
||||||
|
|
||||||
isToday(date: Date) {
|
isToday(date: Date) {
|
||||||
return (new Date(date)).toDateString() === (new Date()).toDateString();
|
return (new Date(date)).toDateString() === (new Date()).toDateString();
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,6 +81,14 @@ import { IConfig } from '@/types/config.model';
|
||||||
query: CURRENT_USER_CLIENT,
|
query: CURRENT_USER_CLIENT,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
metaInfo() {
|
||||||
|
return {
|
||||||
|
// if no subcomponents specify a metaInfo.title, this title will be used
|
||||||
|
title: this.$t('Login on Mobilizon!') as string,
|
||||||
|
// all titles will be injected into this template
|
||||||
|
titleTemplate: '%s | Mobilizon',
|
||||||
|
};
|
||||||
|
},
|
||||||
})
|
})
|
||||||
export default class Login extends Vue {
|
export default class Login extends Vue {
|
||||||
@Prop({ type: String, required: false, default: '' }) email!: string;
|
@Prop({ type: String, required: false, default: '' }) email!: string;
|
||||||
|
|
|
@ -105,7 +105,16 @@ import { CREATE_USER } from '@/graphql/user';
|
||||||
import { Component, Prop, Vue } from 'vue-property-decorator';
|
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||||
import { RouteName } from '@/router';
|
import { RouteName } from '@/router';
|
||||||
|
|
||||||
@Component
|
@Component({
|
||||||
|
metaInfo() {
|
||||||
|
return {
|
||||||
|
// if no subcomponents specify a metaInfo.title, this title will be used
|
||||||
|
title: this.$t('Register an account on Mobilizon!') as string,
|
||||||
|
// all titles will be injected into this template
|
||||||
|
titleTemplate: '%s | Mobilizon',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
})
|
||||||
export default class Register extends Vue {
|
export default class Register extends Vue {
|
||||||
@Prop({ type: String, required: false, default: '' }) email!: string;
|
@Prop({ type: String, required: false, default: '' }) email!: string;
|
||||||
@Prop({ type: String, required: false, default: '' }) password!: string;
|
@Prop({ type: String, required: false, default: '' }) password!: string;
|
||||||
|
|
12
js/yarn.lock
12
js/yarn.lock
|
@ -3817,6 +3817,11 @@ deepmerge@^1.5.2:
|
||||||
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-1.5.2.tgz#10499d868844cdad4fee0842df8c7f6f0c95a753"
|
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-1.5.2.tgz#10499d868844cdad4fee0842df8c7f6f0c95a753"
|
||||||
integrity sha512-95k0GDqvBjZavkuvzx/YqVLv/6YYa17fz6ILMSf7neqQITCPbnfEnQvEgMPNjH4kgobe7+WIL0yJEHku+H3qtQ==
|
integrity sha512-95k0GDqvBjZavkuvzx/YqVLv/6YYa17fz6ILMSf7neqQITCPbnfEnQvEgMPNjH4kgobe7+WIL0yJEHku+H3qtQ==
|
||||||
|
|
||||||
|
deepmerge@^4.0.0:
|
||||||
|
version "4.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.1.1.tgz#ee0866e4019fe62c1276b9062d4c4803d9aea14c"
|
||||||
|
integrity sha512-+qO5WbNBKBaZez95TffdUDnGIo4+r5kmsX8aOb7PDHvXsTbghAmleuxjs6ytNaf5Eg4FGBXDS5vqO61TRi6BMg==
|
||||||
|
|
||||||
default-gateway@^4.2.0:
|
default-gateway@^4.2.0:
|
||||||
version "4.2.0"
|
version "4.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-4.2.0.tgz#167104c7500c2115f6dd69b0a536bb8ed720552b"
|
resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-4.2.0.tgz#167104c7500c2115f6dd69b0a536bb8ed720552b"
|
||||||
|
@ -12502,6 +12507,13 @@ vue-loader@^15.7.0:
|
||||||
vue-hot-reload-api "^2.3.0"
|
vue-hot-reload-api "^2.3.0"
|
||||||
vue-style-loader "^4.1.0"
|
vue-style-loader "^4.1.0"
|
||||||
|
|
||||||
|
vue-meta@^2.3.1:
|
||||||
|
version "2.3.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/vue-meta/-/vue-meta-2.3.1.tgz#32a1c2634f49433f30e7e7a028aa5e5743f84f6a"
|
||||||
|
integrity sha512-hnZvDNvLh+PefJLfYkZhG6cSBNKikgQyiEK8lI/P2qscM1DC/qHHOfdACPQ/VDnlaWU9VlcobCTNyVtssTR4XQ==
|
||||||
|
dependencies:
|
||||||
|
deepmerge "^4.0.0"
|
||||||
|
|
||||||
vue-property-decorator@^8.1.0:
|
vue-property-decorator@^8.1.0:
|
||||||
version "8.2.2"
|
version "8.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/vue-property-decorator/-/vue-property-decorator-8.2.2.tgz#ac895e9508ee1bf86e3a28568d94d842c2c8e42f"
|
resolved "https://registry.yarnpkg.com/vue-property-decorator/-/vue-property-decorator-8.2.2.tgz#ac895e9508ee1bf86e3a28568d94d842c2c8e42f"
|
||||||
|
|
|
@ -7,6 +7,7 @@ defmodule MobilizonWeb.PageView do
|
||||||
alias Mobilizon.Service.ActivityPub.{Converter, Utils}
|
alias Mobilizon.Service.ActivityPub.{Converter, Utils}
|
||||||
alias Mobilizon.Service.Metadata
|
alias Mobilizon.Service.Metadata
|
||||||
alias Mobilizon.Service.MetadataUtils
|
alias Mobilizon.Service.MetadataUtils
|
||||||
|
alias Mobilizon.Service.Metadata.Instance
|
||||||
|
|
||||||
def render("actor.activity-json", %{conn: %{assigns: %{object: actor}}}) do
|
def render("actor.activity-json", %{conn: %{assigns: %{object: actor}}}) do
|
||||||
public_key = Utils.pem_to_public_key_pem(actor.keys)
|
public_key = Utils.pem_to_public_key_pem(actor.keys)
|
||||||
|
@ -80,6 +81,8 @@ defmodule MobilizonWeb.PageView do
|
||||||
|
|
||||||
def render("index.html", _assigns) do
|
def render("index.html", _assigns) do
|
||||||
with {:ok, index_content} <- File.read(index_file_path()) do
|
with {:ok, index_content} <- File.read(index_file_path()) do
|
||||||
|
tags = Instance.build_tags() |> MetadataUtils.stringify_tags()
|
||||||
|
index_content = String.replace(index_content, "<!--server-generated-meta-->", tags)
|
||||||
{:safe, index_content}
|
{:safe, index_content}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
46
lib/service/metadata/instance.ex
Normal file
46
lib/service/metadata/instance.ex
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
defmodule Mobilizon.Service.Metadata.Instance do
|
||||||
|
@moduledoc """
|
||||||
|
Generates metadata for every other page that isn't event/actor/comment
|
||||||
|
"""
|
||||||
|
|
||||||
|
alias Phoenix.HTML
|
||||||
|
alias Phoenix.HTML.Tag
|
||||||
|
alias Mobilizon.Config
|
||||||
|
alias MobilizonWeb.Endpoint
|
||||||
|
|
||||||
|
def build_tags() do
|
||||||
|
description = process_description(Config.instance_description())
|
||||||
|
title = "#{Config.instance_name()} - Mobilizon"
|
||||||
|
|
||||||
|
instance_json_ld = """
|
||||||
|
<script type="application/ld+json">{
|
||||||
|
"@context": "http://schema.org",
|
||||||
|
"@type": "WebSite",
|
||||||
|
"name": "#{title}",
|
||||||
|
"url": "#{Endpoint.url()}",
|
||||||
|
"potentialAction": {
|
||||||
|
"@type": "SearchAction",
|
||||||
|
"target": "#{Endpoint.url()}/search/{search_term}",
|
||||||
|
"query-input": "required name=search_term"
|
||||||
|
}
|
||||||
|
}</script>
|
||||||
|
"""
|
||||||
|
|
||||||
|
[
|
||||||
|
Tag.content_tag(:title, title),
|
||||||
|
Tag.tag(:meta, name: "description", content: description),
|
||||||
|
Tag.tag(:meta, property: "og:title", content: title),
|
||||||
|
Tag.tag(:meta, property: "og:url", content: Endpoint.url()),
|
||||||
|
Tag.tag(:meta, property: "og:description", content: description),
|
||||||
|
Tag.tag(:meta, property: "og:type", content: "website"),
|
||||||
|
HTML.raw(instance_json_ld)
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
defp process_description(description) do
|
||||||
|
description
|
||||||
|
|> HtmlSanitizeEx.strip_tags()
|
||||||
|
|> String.slice(0..200)
|
||||||
|
|> (&"#{&1}…").()
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue