Added opengraph/meta tags

This commit is contained in:
Mikko Ahlroth 2024-04-13 17:30:20 +03:00
parent 981bc757e4
commit f88c5b46fc
8 changed files with 160 additions and 13 deletions

View file

@ -1,7 +1,7 @@
import gleam/option
import gloss/paths.{type PathConfiguration}
import gloss/rendering/views.{
type BaseView, type FeedView, type ListPageView, type PageView,
type BaseView, type FeedView, type ListPageView, type MetaView, type PageView,
type SinglePostView,
}
import gloss/compiler.{type CompileDatabase, type Compiler}
@ -13,6 +13,7 @@ import gloss/rendering/database.{type RenderDatabase} as _
pub type Views {
Views(
base: fn(Database, Configuration) -> BaseView,
meta: fn(Database, Configuration) -> MetaView,
single_post_full: fn(Database, Configuration) -> SinglePostView,
single_post_list: fn(Database, Configuration) -> SinglePostView,
page: fn(Database, Configuration) -> PageView,

View file

@ -4,6 +4,7 @@ import gloss/rendering/views/base
import gloss/rendering/views/list_page
import gloss/rendering/views/page
import gloss/rendering/views/feed
import gloss/rendering/views/meta
import gloss/renderer
import gloss/paths
import gloss/parser
@ -12,6 +13,7 @@ import gloss/compiler
const default_views = config.Views(
base: base.generate,
meta: meta.generate,
single_post_full: single_post.full_view,
single_post_list: single_post.list_view,
page: page.generate,

View file

@ -61,3 +61,10 @@ pub fn comparator(a: Post, b: Post) -> Order {
}
}
}
pub fn to_iso8601(post: Post) -> String {
case post.date {
JustDate(d) -> date.format_iso(d)
DateTime(luxon: l, ..) -> luxon.to_iso(l)
}
}

View file

@ -4,7 +4,7 @@ import gleam/result
import gleam/int
import lustre/element.{type Element}
import gloss/rendering/views.{
type BaseView, type FeedView, type ListPageView, type PageView,
type BaseView, type FeedView, type ListPageView, type MetaView, type PageView,
type SinglePostView, ListInfo,
}
import gloss/rendering/database.{
@ -22,6 +22,7 @@ import gloss/utils/date
pub type Views {
Views(
base: BaseView,
meta: MetaView,
single_post_full: SinglePostView,
page: PageView,
list_page: ListPageView,
@ -37,6 +38,7 @@ pub fn render(
let views =
Views(
base: config.rendering.views.base(db, config),
meta: config.rendering.views.meta(db, config),
single_post_full: config.rendering.views.single_post_full(db, config),
page: config.rendering.views.page(db, config),
list_page: config.rendering.views.list_page(db, config),
@ -76,7 +78,11 @@ pub fn render_posts(
|> list.map(fn(post_with_id) {
let assert Ok(content) = dict.get(post_contents, post_with_id.id)
let rendered =
views.base(views.single_post_full(content), post_with_id.post.title)
views.base(
views.single_post_full(content),
views.meta(views.Post(post_with_id.post)),
post_with_id.post.title,
)
RenderedSinglePost(post_with_id.post, rendered)
})
}
@ -87,7 +93,12 @@ pub fn render_pages(
views: Views,
) {
list.map(compiled_pages, fn(page) {
let rendered = views.base(views.page(page), page.orig.title)
let rendered =
views.base(
views.page(page),
views.meta(views.Page(page.orig)),
page.orig.title,
)
RenderedPage(page.orig, rendered)
})
}
@ -233,7 +244,12 @@ fn pageify_posts(
extra_header: extra_header,
)
let page_content = views.base(views.list_page(info), title_prefix)
let page_content =
views.base(
views.list_page(info),
views.meta(views.Other(title_prefix, "")),
title_prefix,
)
ListPage(page: page, content: page_content)
})
}

View file

@ -1,8 +1,19 @@
import lustre/element.{type Element}
import gloss/compiler.{type CompiledPage, type CompiledPost}
import gloss/models/post
import gloss/models/page
pub type BaseView =
fn(Element(Nil), String) -> Element(Nil)
fn(Element(Nil), List(Element(Nil)), String) -> Element(Nil)
pub type PageType {
Post(post.Post)
Page(page.Page)
Other(title: String, description: String)
}
pub type MetaView =
fn(PageType) -> List(Element(Nil))
pub type SinglePostView =
fn(CompiledPost) -> Element(Nil)

View file

@ -26,8 +26,8 @@ pub type PreRendered {
pub fn generate(db: Database, config: Configuration) {
let pre_rendered = pre_render(db, config)
fn(inner: Element(Nil), title_prefix: String) {
view(db, config, pre_rendered, inner, title_prefix)
fn(inner: Element(Nil), extra_meta: List(Element(Nil)), title_prefix: String) {
view(db, config, pre_rendered, inner, extra_meta, title_prefix)
}
}
@ -44,6 +44,7 @@ fn view(
config: Configuration,
pre_rendered: PreRendered,
inner: Element(Nil),
extra_meta: List(Element(Nil)),
title_prefix: String,
) {
let title_text = case title_prefix {
@ -71,6 +72,7 @@ fn view(
option.Some(url) -> link([rel("me"), value(url)])
_ -> element.none()
},
..extra_meta
]),
body([], [
header([id("title"), role("banner")], [

View file

@ -11,7 +11,6 @@ import gloss/models/post
import gloss/config.{type Configuration}
import gloss/compiler.{type CompiledPost}
import gloss/utils/luxon
import gloss/utils/date
pub fn generate(_db: Database, config: Configuration) {
fn(posts: List(CompiledPost)) {
@ -43,10 +42,7 @@ pub fn generate(_db: Database, config: Configuration) {
),
rights(config.rendering.copyright),
..list.map(posts, fn(post) {
let date_str = case post.orig.date {
post.JustDate(d) -> date.format_iso(d)
post.DateTime(luxon: l, ..) -> luxon.to_iso(l)
}
let date_str = post.to_iso8601(post.orig)
entry([
title(post.orig.title),

View file

@ -0,0 +1,112 @@
import gleam/dict
import gleam/list
import gleam/option
import gleam/result
import lustre/element/html
import lustre/attribute
import gloss/rendering/views.{type PageType, Other, Page, Post}
import gloss/models/post
import gloss/models/page
import gloss/config.{type Configuration}
import gloss/models/database.{type Database}
pub fn generate(_db: Database, config: Configuration) {
fn(page_type: PageType) {
list.flatten([
[
name_meta("author", config.author.name),
name_meta("description", description(page_type)),
// Schema.org
itemprop_meta("name", title(page_type)),
itemprop_meta("author", config.author.name),
itemprop_meta("headline", title(page_type)),
itemprop_meta("description", description(page_type)),
// Twitter/X card
name_meta("twitter:card", "summary_large_image"),
name_meta("twitter:site", ""),
name_meta("twitter:title", title(page_type)),
name_meta("twitter:description", description(page_type)),
name_meta("twitter:creator", ""),
// Open Graph
property_meta("og:title", title(page_type)),
property_meta("og:type", case page_type {
Post(..) | Page(..) -> "article"
_ -> "website"
}),
property_meta("og:description", description(page_type)),
property_meta("og:site_name", config.blog_name),
],
case page_type {
Post(post) -> {
let timestamp = post.to_iso8601(post)
let image =
post.headers
|> dict.from_list()
|> dict.get("image")
|> option.from_result()
[
property_meta("article:published_time", timestamp),
itemprop_meta("datePublished", timestamp),
property_meta(
"og:url",
config.blog_url <> config.paths.single_post(post),
),
..case image {
option.Some(image) -> [
name_meta("twitter:image", image),
property_meta("og:image", image),
itemprop_meta("image", image),
]
option.None -> []
}
]
}
Page(page) -> [
property_meta("og:url", config.blog_url <> config.paths.page(page)),
]
_ -> []
},
])
}
}
fn name_meta(name: String, content: String) {
meta("name", name, content)
}
fn itemprop_meta(itemprop: String, content: String) {
meta("itemprop", itemprop, content)
}
fn property_meta(property: String, content: String) {
meta("property", property, content)
}
fn meta(attr_name: String, attr_value: String, content: String) {
html.meta([
attribute.attribute(attr_name, attr_value),
attribute.attribute("content", content),
])
}
fn description(page_type: PageType) {
case page_type {
Post(post.Post(headers: headers, ..))
| Page(page.Page(headers: headers, ..)) ->
headers
|> dict.from_list()
|> dict.get("description")
|> result.unwrap("")
Other(description: description, ..) -> description
}
}
fn title(page_type: PageType) {
case page_type {
Post(post.Post(title: title, ..))
| Page(page.Page(title: title, ..))
| Other(title: title, ..) -> title
}
}